391 Commits

Author SHA1 Message Date
5c7763df33 Update 03_Strings_and_Characters.md 2019-07-15 06:29:27 -05:00
ed7eb9e045 Update 22_Generics.md 2019-07-15 05:24:12 -05:00
15ee26ca6d Update 04_revision_history.md 2019-07-15 05:23:45 -05:00
dca10b1aa0 修正翻译错误 (#965)
* 修正翻译错误

* Update 14_Initialization.md
2019-07-15 04:50:12 -05:00
2d10a8d6ec 修改翻译 (#964) 2019-07-14 20:57:21 -07:00
a6d6e468f6 Merge pull request #963 from Nemocdz/gh-pages
fix 一处格式问题
2019-07-12 11:38:40 +08:00
72708963ab fix typo 2019-07-12 11:37:23 +08:00
22265fa31d Merge remote-tracking branch 'upstream/gh-pages' into gh-pages 2019-07-12 11:36:26 +08:00
771c5b3da0 trigger gitbook update 2019-07-11 10:39:39 +08:00
0369884f87 Update contributors.md 2019-07-10 20:24:13 +08:00
75e0837fb0 Update contributors.md 2019-07-10 20:22:55 +08:00
0afcd2ebf1 Update README.md 2019-07-10 18:32:54 +08:00
d07fdbfb98 更新 Revision History (#962)
* 术语表更新

* 更新文档内术语

* 术语表更新

* update Control Flow

* update Version Compatibility

* update Methods

* update Types

* 术语表更新

* update Types

* Update 03_Types.md

* update links

* update Links

* 统一版本历史记录格式和翻译

* fix typo

* fix typo

* Update 04_revision_history.md

* update Document Revision History

* update Revision History&Types
2019-07-10 18:07:50 +08:00
21e80f09b5 update Revision History&Types 2019-07-10 17:01:13 +08:00
2b140081de Merge remote-tracking branch 'upstream/gh-pages' into gh-pages 2019-07-10 16:17:49 +08:00
60cc810915 Update SUMMARY.md 2019-07-10 15:59:55 +08:00
68e51d7f57 完成翻译:不透明类型 (#961)
* 新增翻译:不透明类型

* 优化格式
2019-07-10 15:57:13 +08:00
4381756f0f Merge pull request #960 from lal603743923/gh-pages
Update 03_a_swift_tour.md
2019-07-10 14:48:33 +08:00
96f3c84b8a Update 03_a_swift_tour.md
修正翻译错误.
2019-07-09 20:57:20 +08:00
cc029194be 04_Collection_Types (#959)
* 04_Collection_Types 5.1初版

* 校对以后的更新

* 第二次校对以后的修正
2019-07-07 11:08:11 +08:00
ae117bdfbc #939 & #949 (#958)
* Protocol -  protocol type downcasting

* Protocols - 统一用语

* Properties - Shorthand getter

* Properties - 标点及语气调整。

* Update 10_Properties.md
2019-07-03 20:36:45 -07:00
5fc4252bfc Update FUNDING.yml 2019-07-03 14:12:24 +08:00
9ec61ba488 Create FUNDING.yml 2019-07-03 14:09:16 +08:00
fc89e3ca5d Merge pull request #957 from SunsetWan/sunsetwan-translation
Complete the translation of issue  942 and 941
2019-07-01 16:52:49 +08:00
9a629a57e3 Merge branch 'sunsetwan-translation' of https://github.com/SunsetWan/the-swift-programming-language-in-chinese into sunsetwan-translation 2019-07-01 13:41:53 +08:00
787687cb1e Refine the translation. 2019-07-01 13:40:57 +08:00
b699654ab8 update Document Revision History 2019-07-01 00:23:20 +08:00
d902be8367 Merge branch 'gh-pages' into sunsetwan-translation 2019-07-01 00:09:07 +08:00
d90c50ec74 Refine the translation. 2019-07-01 00:06:35 +08:00
4894951c3b 22 Generics Swift 5.1 更新 (#956) 2019-06-30 07:25:08 -05:00
b5defb4c0e Complete the translation of issue 942 and 941. 2019-06-30 18:55:05 +08:00
b1887d01ac Merge remote-tracking branch 'upstream/gh-pages' into gh-pages 2019-06-30 00:10:02 +08:00
5f0da139f0 统一版本历史记录翻译及格式 (#955)
* 术语表更新

* 更新文档内术语

* 术语表更新

* update Control Flow

* update Version Compatibility

* update Methods

* update Types

* 术语表更新

* update Types

* Update 03_Types.md

* update links

* update Links

* 统一版本历史记录格式和翻译

* fix typo

* fix typo

* Update 04_revision_history.md
2019-06-29 11:00:30 -05:00
53eaaeb19c Update 04_revision_history.md 2019-06-29 11:00:19 -05:00
c9c9bb980e fix typo 2019-06-29 23:45:34 +08:00
51ceab425c Merge remote-tracking branch 'upstream/gh-pages' into gh-pages
# Conflicts:
#	source/chapter1/04_revision_history.md
2019-06-29 23:39:14 +08:00
16cae8adf6 fix typo 2019-06-29 23:34:09 +08:00
bab43e7db6 统一版本历史记录格式和翻译 2019-06-29 23:33:23 +08:00
4cace74528 Complete the translation of issue 937 and 935. (#954) 2019-06-29 08:28:58 -05:00
b807a6edb9 Complete the translation of issue 937 and 935. 2019-06-29 19:34:54 +08:00
1a6d56d228 统一链接格式 (#953)
* 术语表更新

* 更新文档内术语

* 术语表更新

* update Control Flow

* update Version Compatibility

* update Methods

* update Types

* 术语表更新

* update Types

* Update 03_Types.md

* update links

* update Links
2019-06-27 17:35:37 -05:00
004fc0906d Merge remote-tracking branch 'upstream/gh-pages' into gh-pages
# Conflicts:
#	source/chapter2/11_Methods.md
#	source/chapter3/03_Types.md
2019-06-28 00:59:06 +08:00
850cf97f83 Merge branch 'gh-pages' of github.com:Nemocdz/the-swift-programming-language-in-chinese into gh-pages 2019-06-28 00:54:45 +08:00
d6862fa4dc update Links 2019-06-28 00:54:02 +08:00
d3efb989d3 update links 2019-06-28 00:09:46 +08:00
0f2ab87dca Delete config.json 2019-06-26 11:16:25 -05:00
9a6f016f45 Delete change_cdn.py 2019-06-26 11:16:11 -05:00
2164db3b8b Update index.html 2019-06-26 11:16:01 -05:00
fb2dfa6764 Delete googleb0a4f5a22e9cb82f.html 2019-06-26 11:15:16 -05:00
d8068f4325 Delete CNAME 2019-06-26 11:14:35 -05:00
4e4e291956 更新文档翻译 (#952)
* 术语表更新

* 更新文档内术语

* 术语表更新

* update Control Flow

* update Version Compatibility

* update Methods

* update Types

* 术语表更新

* update Types

* Update 03_Types.md
2019-06-26 11:05:31 -05:00
2f430495d4 Update 03_Types.md 2019-06-26 11:05:20 -05:00
7b123505db Merge remote-tracking branch 'upstream/gh-pages' into gh-pages
# Conflicts:
#	README.md
#	source/chapter3/06_Declarations.md
2019-06-26 23:55:25 +08:00
e7def896cb update Types 2019-06-26 23:42:11 +08:00
5fbfc38fd7 remove extra > 2019-06-26 10:02:48 -05:00
b26f8aacff 术语表更新 2019-06-26 22:42:22 +08:00
b76f8cf111 Translate Chapter3 06_Declarations and 07_Attributes (#951)
* 完成 Chapter3 Declarations Swift5.1 Update

* Update 07_Attributes.md
2019-06-26 09:37:39 -05:00
ca87420665 update Types 2019-06-26 21:20:01 +08:00
93c390dd5a update Methods 2019-06-26 19:44:58 +08:00
7020682acd update Version Compatibility 2019-06-26 19:31:54 +08:00
aa91ad4692 update Control Flow 2019-06-26 19:16:32 +08:00
367cece0f5 术语更新 (#950)
* 术语表更新

* 更新文档内术语

* 术语表更新
2019-06-25 10:00:42 -05:00
3f0cc6c047 术语表更新 2019-06-25 22:49:28 +08:00
65dc6b133f 更新文档内术语 2019-06-25 22:43:20 +08:00
b58d744852 术语表更新 2019-06-25 22:40:17 +08:00
e9aaa026c6 Merge pull request #949 from Nemocdz/gh-pages
remove unused files
2019-06-23 23:03:11 +08:00
1167a215c9 remove unused files 2019-06-23 23:00:55 +08:00
f5c9d9c5e5 Update README.md 2019-06-23 09:30:05 -05:00
71f0757ba0 Merge pull request #931 from Nemocdz/gh-pages
更新 Swift 5.1 文档(beta)
2019-06-23 11:25:24 +08:00
afc8cddb0e update document 2019-06-23 11:17:57 +08:00
a3a7770b9a Merge remote-tracking branch 'upstream/gh-pages' into gh-pages
# Conflicts:
#	source/chapter2/09_Structures_And_Classes.md
2019-06-23 10:59:04 +08:00
1ffeeeedaf Update 14_Initialization.md 2019-06-16 07:43:03 -05:00
b991a4eb75 Update 04_Expressions.md (#929)
有几处的binary被错误翻译成了二进制,本意应为二元
2019-05-19 11:28:54 +08:00
e3c9bd7992 Update 02_version_compatibility.md (#926) 2019-05-03 18:57:44 -05:00
425d534e7f Update 25_Access_Control.md (#925) 2019-04-21 11:44:58 -05:00
e0af10bc8e Update 21_Protocols.md (#924) 2019-04-20 21:43:29 -05:00
3210e30972 Revert "test gitbook anchor"
This reverts commit e82cea3866.
2019-04-19 12:28:19 -05:00
e82cea3866 test gitbook anchor 2019-04-19 12:23:34 -05:00
497d68be37 fix invalid url (#923) 2019-04-15 08:37:38 -05:00
cb2ebd282b Update 01_The_Basics.md 2019-04-12 07:24:30 -05:00
9fd540b8d7 校对: chapter2/03_Strings_and_Characters 方法名变化 (#921)
index(of:) 已更名为: firstIndex(of:)
2019-04-11 21:32:20 -05:00
86690d4472 Update contributors.md 2019-04-11 19:26:46 -05:00
7fcb93a763 Update README.md 2019-04-08 23:08:42 -05:00
d885f9e1fd Update README.md 2019-04-08 23:08:10 -05:00
7fcd04ec5b 替换原有 Swift5 Beat 版本电子书为正式版 (#919) 2019-04-07 10:13:06 -05:00
09eb20007b Update README.md 2019-04-04 18:21:36 -05:00
72c51e42bb Fix revision link (#917)
* temp fix swift5 - swift3 link

* 补充余下文档外链修改
2019-04-04 16:40:43 -05:00
bc806e9b20 modify readme (#918) 2019-04-04 16:40:21 -05:00
5c2873d295 追加最近才添加的 Swift5 改动主要贡献者 (#916) 2019-04-03 09:51:38 -05:00
2760b1993e trigger gitbook build 2019-04-02 14:12:14 -05:00
f6e0bfa622 remove blank 2019-04-02 09:53:33 -05:00
3201f23a17 replace all _ with - 2019-04-02 09:47:36 -05:00
5d3bf7bbcf update anchor 2019-04-02 09:40:12 -05:00
61e3a3243f test gitbook anchor 2019-04-02 09:36:19 -05:00
099837dbc6 test gitbook anchor 2019-04-02 09:30:07 -05:00
8c866e4435 combine chapter3 article 10 2019-04-02 09:08:59 -05:00
75e3d9d98d 已完成 Chapter3 Expressions 前半部分 (#914)
* 已完成 Chapter3 Expressions 前半部分

* 已完成 Chapter3 Expressions 部分
2019-04-02 08:50:41 -05:00
37bcb2969a 更新10_Summary_of_the_Grammar(Satements) (#915)
* 第三章 10_Summary_of_the_Grammar(从 Declarations 到 Generic Parameters and Arguments)

* 10_Summary_of_the_Grammar(from Declarations -> Generic Parameters and Arguments)的翻译 (#911)

* Update 10_Summary_of_the_Grammar(from Lexical Structure -> Types).md.

去掉"可选"前的空格

* 更新10_Summary_of_the_Grammar(Satements),按原文顺序,追加在原10_Summary_of_the_Grammar(from Declarations -> Generic Parameters and Arguments).md 前面并更名为10_Summary_of_the_Grammar(from Statements -> Generic Parameters and Arguments).md
2019-04-01 15:21:40 -05:00
307ddedd43 更新10_Summary_of_the_Grammar(from Lexical Structure -> Types).md (#913)
* 第三章 10_Summary_of_the_Grammar(从 Declarations 到 Generic Parameters and Arguments)

* 10_Summary_of_the_Grammar(from Declarations -> Generic Parameters and Arguments)的翻译 (#911)

* Update 10_Summary_of_the_Grammar(from Lexical Structure -> Types).md.

去掉"可选"前的空格
2019-03-30 21:57:16 -05:00
4d6ef27d54 第三章 10_Summary_of_the_Grammar(从 Declarations 到 Generic Parameters and Arguments) (#910)
* 第三章 10_Summary_of_the_Grammar(从 Declarations 到 Generic Parameters and Arguments)

* 10_Summary_of_the_Grammar(from Declarations -> Generic Parameters and Arguments)的翻译 (#911)
2019-03-30 17:32:56 -05:00
34990cf782 10_Summary_of_the_Grammar(from Lexical Structure -> Types)的翻译 (#911)
* 10_Summary_of_the_Grammar(from Lexical Structure -> Types)的翻译

* Update 10_Summary_of_the_Grammar(from Lexical Structure -> Types).md

1. 更新添加转义字符
2. 格式修正

* Update 10_Summary_of_the_Grammar(from Lexical Structure -> Types).md

调整格式

* Update 10_Summary_of_the_Grammar(from Lexical Structure -> Types).md

1. 修改转义

* Update 10_Summary_of_the_Grammar(from Lexical Structure -> Types).md

* Update 10_Summary_of_the_Grammar(from Lexical Structure -> Types).md

1. 修正转义

* Update 10_Summary_of_the_Grammar(from Lexical Structure -> Types).md

1.修正多行注释

* Update 10_Summary_of_the_Grammar(from Lexical Structure -> Types).md
2019-03-30 10:38:16 -05:00
2b4fddc049 Update 10_Summary_of_the_Grammar(from Lexical Structure -> Types).md 2019-03-30 10:37:58 -05:00
cd435f5a28 Update 10_Summary_of_the_Grammar(from Lexical Structure -> Types).md
1.修正多行注释
2019-03-30 20:18:14 +08:00
10bc54af00 Update 10_Summary_of_the_Grammar(from Lexical Structure -> Types).md
1. 修正转义
2019-03-30 20:09:50 +08:00
4ef5e74cbb Update 10_Summary_of_the_Grammar(from Lexical Structure -> Types).md 2019-03-30 07:05:57 -05:00
212b685b24 Update 10_Summary_of_the_Grammar(from Lexical Structure -> Types).md
1. 修改转义
2019-03-30 20:01:04 +08:00
3e2520a893 Update 10_Summary_of_the_Grammar(from Lexical Structure -> Types).md
调整格式
2019-03-30 17:50:05 +08:00
44c325d3ce Update 10_Summary_of_the_Grammar(from Lexical Structure -> Types).md
1. 更新添加转义字符
2. 格式修正
2019-03-30 17:45:18 +08:00
9ee2363352 10_Summary_of_the_Grammar(from Lexical Structure -> Types)的翻译 2019-03-30 13:47:42 +08:00
835959d62b 05_Statements (#909)
* 修复文章排版问题

* 修复图片饮用链接失效问题

* 增加缺失的标点

* 初次修改

* 修改 链接文件格式
2019-03-29 07:46:15 -05:00
c23b0f796d 第三章 02_Lexical_Structure (#908)
* 第一遍翻译

* fixed anchor

* fixed anchor

* 修改 链接文件格式

* 修改 跳转链接格式

* 修改 字符串参考 跳转链接

* 修改运算符跳转链接

* 修改跳转链接

* 修改跳转链接
2019-03-28 16:41:22 -05:00
aaaf3abb2f Merge pull request #905 from SwiftGGTeam/wuqiuhao-patch-1
Update contributors.md
2019-03-27 11:15:23 -05:00
c22767f242 Update contributors.md 2019-03-28 00:02:08 +08:00
55acc0b7f7 Merge pull request #904 from dzyding/gh-pages
03_Types
2019-03-27 08:12:40 -05:00
90d6f1012b Merge pull request #903 from wuqiuhao/feature/advanced_operators
Swift5.0 AdvancedOperators 章节更新
2019-03-27 08:11:36 -05:00
4005616424 Update 26_Advanced_Operators.md 2019-03-27 08:11:22 -05:00
453ac5e365 Merge pull request #902 from BigNerdCoding/stringMissingContent
追加最近才添加的 Swift5 改动
2019-03-27 08:08:47 -05:00
f1151f62aa Merge pull request #901 from BigNerdCoding/fixLinkIssues
修复语言参考部分的链接问题
2019-03-27 08:08:31 -05:00
8e2641a0bf Merge pull request #900 from BigNerdCoding/addContentLink
补充一些缺失标题链接
2019-03-27 08:07:41 -05:00
edz
c02ecdcc09 避免冲突 2019-03-27 20:12:00 +08:00
edz
93cffd5ff1 避免冲突 2019-03-27 20:08:28 +08:00
edz
c634f574a1 避免冲突 2019-03-27 20:03:36 +08:00
7ced6d2dd7 update 2019-03-27 18:21:09 +08:00
bf38ce27c6 解决冲突 & 更新Swift5.0 AdvancedOperators 章节 2019-03-27 18:17:48 +08:00
edz
fc716d45e8 两个电脑产生的冲突 2019-03-27 18:07:28 +08:00
edz
5968f31d1f swift5 modify 2019-03-27 17:58:25 +08:00
91afca8367 追加最近才添加的 Swift5 改动 2019-03-27 16:13:45 +08:00
7b603a307f 补充一些缺失标题链接 2019-03-27 14:59:18 +08:00
edz
d29d719469 Swift5 新版本维护 2019-03-27 14:19:47 +08:00
4cb21c38f0 修复语言参考部分的链接问题 2019-03-27 14:11:44 +08:00
3acdfbca8c fix none text anchor 2019-03-27 00:33:34 -05:00
964478ed72 fix none head anchor 2019-03-27 00:29:15 -05:00
fc86ccb932 fix all anchor format 2019-03-27 00:14:08 -05:00
cd06ac6f52 test gitbook anchor 2019-03-26 23:40:52 -05:00
5fca0fa704 fix chapter 3 relative links 2019-03-26 23:25:08 -05:00
f65cd95306 partly fix chapter 3 relative links 2019-03-26 22:57:04 -05:00
1d01a9900b fix quota format 2019-03-26 22:44:42 -05:00
810eba1150 fix regex replacing mistakes 2019-03-26 22:16:34 -05:00
7eb0b94d85 fix relative links 2019-03-26 22:08:00 -05:00
edz
6f99dca741 Merge remote-tracking branch 'upstream/gh-pages' into gh-pages 2019-03-27 09:23:07 +08:00
adc4897eff Update .gitbook.yaml 2019-03-26 19:50:23 -05:00
c27d3a44cd Create .gitbook.yaml 2019-03-26 19:49:34 -05:00
b9d9e62555 Merge pull request #898 from chenmingbiao/gh-pages
feature:Swift5-Enumerations
2019-03-26 13:51:46 -05:00
7b26ec50c3 feature:Swift5-Enumerations 2019-03-26 23:44:05 +08:00
7032b5c6fa update revision history content (#895)
* update revision history content

* 按照校对意见重新整理提交内容

1. 统一链接格式
2. 补充句号
3. 添加遗漏内容 & 修改其他格式问题

* 添加缺失的链接
2019-03-26 07:15:24 -05:00
b46d6e1337 修复繁体第十章 Properties 冲突 (#897) 2019-03-26 07:14:08 -05:00
6a881a91db Merge pull request #896 from BigNerdCoding/fixConflict
修复原有文件冲突标示
2019-03-26 15:41:06 +08:00
55bd47a9b1 修复原有文件冲突标示 2019-03-26 15:14:04 +08:00
33bddddebb 添加缺失的链接 2019-03-26 15:10:57 +08:00
64050823a2 按照校对意见重新整理提交内容
1. 统一链接格式
2. 补充句号
3. 添加遗漏内容 & 修改其他格式问题
2019-03-26 10:56:01 +08:00
899f708be2 update revision history content 2019-03-25 21:25:46 +08:00
582d450a75 更新英文原版链接 (#894) 2019-03-10 06:31:03 -05:00
125fdaf25f Update attributes.md. Nothing updated in Swift 5. Re-corrected some translation. (#893) 2019-03-09 08:10:57 -06:00
afc5131f59 Nothing updated in Swift 5. Format-updated. (#892) 2019-03-07 17:33:26 -06:00
a4c5b36ff1 Update 11_Methods.md 2019-02-25 07:53:17 -06:00
a93c26c7a4 Update chapter3_expressions.md with Swift 5 (#891) 2019-02-25 07:51:10 -06:00
0d9f9f64c2 Update chapter3_declaration.md with Swift 5 (#890) 2019-02-17 06:25:37 -06:00
b6a56e5ebc update compatibility chapter (#889) 2019-02-15 14:25:00 -06:00
0a30944f53 完成 chapter3/07_Attributes 翻译 (#884)
* chapter2/05_Control_Flow 优化翻译细节

* chapter3/07_Attributes 粗译

* chapter3/07_Attributes 完成校稿
2019-02-15 08:05:48 -06:00
096d239c83 Basic operator (#888)
* 修改第二章部分细节

* update control flow

* 调整 Functions 章节细节

* 重新整理翻译工作主要贡献者记录

* 更新贡献者记录文件

1. 移除与官方文档无关内容
2. 归档主要贡献者记录并附录在文档最后一页

* 删除多余内容

* 更新 ReferenceManual AboutTheLanguageReference

* 更新 README & 添加文档

* update basic chapter Swift5
2019-02-15 08:05:25 -06:00
3857433e94 Update README.md 2019-02-11 08:11:49 -06:00
4a965cdb05 Update README.md 2019-02-11 08:11:24 -06:00
f44291ad8e 添加文件 & 更新 README (#887)
* 修改第二章部分细节

* update control flow

* 调整 Functions 章节细节

* 重新整理翻译工作主要贡献者记录

* 更新贡献者记录文件

1. 移除与官方文档无关内容
2. 归档主要贡献者记录并附录在文档最后一页

* 删除多余内容

* 更新 ReferenceManual AboutTheLanguageReference

* 更新 README & 添加文档
2019-02-11 08:10:40 -06:00
1abd1157fd update swift 5.0 (#886) 2019-02-03 11:55:37 -06:00
de559ce2d8 update swift 5.0 2019-02-04 01:25:04 +08:00
4ac5ae6485 Chapter3/05_Statements (#885)
* draft 1

* Fix typo

* Fix encoding issue
2019-02-03 09:28:16 -06:00
8b9f8ba4cb Update README.md 2019-02-02 21:36:57 -06:00
daeda4aab1 Update README.md 2019-02-02 07:41:23 -06:00
9b43c08e47 Update README.md 2019-02-01 21:22:23 -06:00
719d83d33b Update README.md 2019-02-01 21:22:01 -06:00
6ccc8227ef ReferenceManual AboutTheLanguageReference 更新 (#882)
* 修改第二章部分细节

* update control flow

* 调整 Functions 章节细节

* 重新整理翻译工作主要贡献者记录

* 更新贡献者记录文件

1. 移除与官方文档无关内容
2. 归档主要贡献者记录并附录在文档最后一页

* 删除多余内容

* 更新 ReferenceManual AboutTheLanguageReference
2019-01-30 07:30:48 -06:00
fead955824 修正一些编辑中的小错误 (#883)
* 04_Collection_Types 优化翻译细节

* 修正注释引号使用

* 05_Control_Flow 更新代码

* 修正注释中引号的使用
2019-01-29 07:22:07 -06:00
10395aaee1 归档到目前的贡献者 (#881)
* 修改第二章部分细节

* update control flow

* 调整 Functions 章节细节

* 重新整理翻译工作主要贡献者记录

* 更新贡献者记录文件

1. 移除与官方文档无关内容
2. 归档主要贡献者记录并附录在文档最后一页
2019-01-27 07:00:21 -06:00
3224ccac8f feature:4.2-Protocols (#861) 2019-01-27 05:58:10 -06:00
8c267a3de8 chapter2 23~26 细节优化 (#860)
* 23_Automatic_Reference_Counting 修正编辑格式,更新代码

* 24_Memory_Safety 删除多余空格与空行

* 25_Access_Control 修正标点使用,更新代码

* 26_Advanced_Operators 修正编辑小错误
2019-01-22 09:04:23 -06:00
4c3ad3d2f8 tiny changes (#859) 2019-01-20 20:54:45 -06:00
1343749921 22_Generics 修正编辑错误,补充注释翻译 (#858)
* 22_Generics 移除翻译记录、目录

* 22_Generics 代码块后添加空行,补充标题链接,移除冗余空行

* 22_Generics 修正注释翻译,修正编辑错误

* 22_Generics 补充句号
2019-01-19 07:35:18 -06:00
1493185ba4 修正编辑错误 (#857)
* 19_Nested_Types 去除多余空格

* 清除行末多余空格

* 20_Extensions 标题后添加空行

* 20_Extensions 修正注释翻译

* 20_Extensions 修正行内代码块引用方式

* 21_Protocols 修正编辑中的错误
2019-01-18 08:47:32 -06:00
85b457b8ca 调整 Properties 章节细节 (#856) 2019-01-17 19:45:19 -06:00
3529a8c94b 翻译错误 (#855) 2019-01-15 09:18:57 -06:00
52a95688ad 修正代码注释中引号的使用 (#854)
* 修正代码注释中引号的使用

* 去除中文与引号之间的空格
2019-01-13 22:43:10 -06:00
60e700582a Merge pull request #853 from bqlin/development
优化部分翻译
2019-01-14 10:21:03 +08:00
4081d8d286 18_Type_Casting 移除多余空行 2019-01-14 09:47:37 +08:00
ff3880f81c 17_Error_Handling 优化翻译 2019-01-14 09:47:03 +08:00
740196487e 26_Advanced_Operators (#848)
* 4.2更新

* 校对之后的修改

* 4.2 对应更新维护

* 删除多余空格
2019-01-07 07:34:06 -06:00
7dee72fe3d 校对以后的修改 2019-01-07 20:58:15 +08:00
1ec921bb88 修正标点错误 (#852) 2019-01-05 10:24:40 -06:00
3acf9a1bc4 22-Generics (#849)
* Update 19 Nested types for Swift 4.2

* Revert "Update 19 Nested types for Swift 4.2"

This reverts commit 7e0fbe87ed.

* Update 16_Optional_Chaining

* Update 16_Optional_Chaining

* Update 16_Optional_Chaining

* 新增部分翻译

* 增加翻译内容

* 完成初步翻译

* 修改部分内容

* 更新翻译

* 删除标题以及原文链接

* 修正相关的链接
2019-01-04 10:41:18 -06:00
a6a14ebcbe 修正标点与空格错误 (#851)
* 修正 source/chapter2/04_Collection_Types.md 错误的空格与标点

* 修正 source/chapter2/05_Control_Flow.md 错误的空格与标点使用

* 修正 source/chapter2/06_Functions.md 去除多余空格
2019-01-04 10:36:11 -06:00
a8d4536826 06 Functions 更新 (#850)
* 修改第二章部分细节

* update control flow

* 调整 Functions 章节细节
2019-01-03 08:48:41 -06:00
a49587cd09 修正所有资源 URL (#847) 2019-01-03 08:47:07 -06:00
ddda95cc6b 删除多余空格 2018-12-31 14:13:35 +08:00
75495c45c8 4.2 对应更新维护 2018-12-31 14:10:39 +08:00
a76cbc957b Merge remote-tracking branch 'upstream/gh-pages' into gh-pages 2018-12-31 14:07:22 +08:00
d068d8f1df Swift 4.2 类型转换 (#846)
* Swift 4.2 类型转换

* 更新
2018-12-27 09:33:40 -06:00
5adeb1c9cc 更新 2018-12-27 17:29:48 +08:00
18d2cc447c Swift 4.2 类型转换 2018-12-27 15:56:23 +08:00
9f027f7614 更新第五章控制流 (#844)
* 修改第二章部分细节

* update control flow
2018-12-23 08:38:48 -07:00
a175a2cae8 Initialization 章节 Swift 4.2 校对 (#842)
* translate

* translate

* translate

* translate

* translate
2018-12-19 20:26:34 -06:00
72e24680a0 String (#838)
* 修改第二章部分细节

* update string chapter

* fix some issues

* fix some issues

* fix some issues
2018-12-16 09:52:02 -06:00
edz
9f412c8526 Merge remote-tracking branch 'upstream/gh-pages' into gh-pages 2018-12-14 15:53:50 +08:00
2c2097e940 最新文档中这里需要用! 来获取值 (#837) 2018-12-09 20:26:54 -06:00
2f66d833d7 Update 13_Inheritance for Swift 4.2 (#831)
* Update 19 Nested types for Swift 4.2

* Revert "Update 19 Nested types for Swift 4.2"

This reverts commit 7e0fbe87ed.

* Update 16_Optional_Chaining

* Update 16_Optional_Chaining

* Update 16_Optional_Chaining

* Update 13_Inheritance for Swift 4.2
2018-12-06 10:54:18 -06:00
6c3269d13f 修改 Access Control(访问控制)章节中,协议-协议遵循部分的内容。 (#833)
* 1.修改 Access Control(访问控制)章节中,协议-协议遵循部分的内容,修改原因的详细解释见:https://github.com/pmtao/TechTalk/issues/2
其中原小标题《协议一致性》,建议改为《协议遵循》,意思更准确也更符合大部分类似技术资料的叫法。
-----
提交人:镜画者,发现有任何问题请联系我:pmtnmd@gmail.com。
Commit by Meler Paine, found any problem please contact: pmtnmd@gmail.com.

* 统一协议遵循的叫法,将翻译中所有类似含义的叫法,如:“协议一致性”、“符合”、“遵从”、“遵守”、“采纳”、“满足”等统一修改为“遵循”。
-----
提交人:镜画者,发现有任何问题请联系我:pmtnmd@gmail.com。
Commit by Meler Paine, found any problem please contact: pmtnmd@gmail.com.
2018-12-05 21:10:28 -06:00
d1ddc1a8ec Update 23_automatic_reference_counting for swift4.2 (#832) 2018-12-03 09:00:29 -06:00
51f79344ee Update README.md 2018-11-27 09:43:53 -06:00
73d1b558f3 Update README.md 2018-11-27 09:43:31 -06:00
18d24826d2 20_Extensions (#822)
* 4.2更新

* 校对之后的修改
2018-11-27 09:40:10 -06:00
884f50e5dc Update 16_Optional_Chaining.md 2018-11-18 12:02:11 -06:00
150626ec6b Update 16_Optional_Chaining for Swift 4.2 (#828)
* Update 19 Nested types for Swift 4.2

* Revert "Update 19 Nested types for Swift 4.2"

This reverts commit 7e0fbe87ed.

* Update 16_Optional_Chaining

* Update 16_Optional_Chaining

* Update 16_Optional_Chaining
2018-11-18 11:22:44 -06:00
0cdb353cfc Update "12_Subscripts" for Swift 4.2 (#829)
* 修复文章排版问题

* 修复图片饮用链接失效问题

* 增加缺失的标点
2018-11-18 11:22:34 -06:00
d8dc27c992 修改第二章部分细节 (#830) 2018-11-18 11:22:20 -06:00
c1be0a2755 Merge pull request #826 from WAMaker/gh-pages
Update "17_Error_Handling" for Swift 4.2
2018-11-07 21:19:33 +08:00
378e36152f Merge pull request #827 from Adolf-L/gh-pages
Update "07_Closures" for Swift 4.2
2018-11-06 23:50:11 -05:00
f27276223c Merge remote-tracking branch 'upstream/gh-pages' into gh-pages 2018-11-07 10:12:44 +08:00
1b7b7ed1ab 校对之后的修改 2018-11-07 09:59:00 +08:00
8f612f188f Merge pull request #824 from mobilefellow/class_struct
Update "09_Classes_And_Structures" for Swift 4.2
2018-11-06 16:50:42 +08:00
f0ef29ef58 修复文章格式错误 2018-11-06 16:48:18 +08:00
611f78f28c 修正空格 2018-11-06 11:25:08 +08:00
76b78304bd Swift4.2 update chapter2 _07Closure 2018-11-06 11:15:56 +08:00
8417b49bcd Merge pull request #825 from YiYiZheng/gh-pages
19_Nested Types for Swift 4.2
2018-11-04 07:15:33 -06:00
dc10749c04 Merge remote-tracking branch 'upstream/gh-pages' into gh-pages 2018-11-04 15:55:44 +08:00
f86e8daa25 update Error Handling for Swift 4.2 2018-11-04 15:53:11 +08:00
be8dae5181 减少不必要的空格 2018-11-03 16:05:47 +08:00
143d28f759 09_Classes_And_Structures: Update with BigNerdCoding's suggestions. 2018-10-31 15:59:05 +08:00
d2bbb6d6b7 Rename "09_Classes_And_Structures.md" to "09_Structures_And_Classes.md" 2018-10-30 16:57:30 +08:00
ba99f9d349 Update "09_Classes_And_Structures" for Swift 4.2 2018-10-30 16:56:20 +08:00
90c7949aa0 Merge pull request #823 from YiYiZheng/gh-pages
15 Deinitialization for Swift 4.2
2018-10-29 09:56:07 +08:00
1b0dafc062 update 15 Deinitialization for Swift 4.2 2018-10-28 19:08:33 +08:00
84da873846 4.2更新 2018-10-25 15:01:55 +08:00
a078759330 Update README.md 2018-10-23 22:35:03 -05:00
c08703f216 Merge pull request #819 from Nemocdz/issue24
Memory Safety 章节 Swift 4.2 校对
2018-10-23 20:18:07 +08:00
ad1835712f Merge pull request #820 from YiYiZheng/gh-pages
08章 Enumerations Swift 4.2 翻译+校对
2018-10-23 20:17:50 +08:00
0f428e93f6 修复图片失效问题 2018-10-21 21:45:58 +08:00
0a734e697a 根据 Swift 4.2 官方文档进行修改。1 新增 “枚举成员的遍历” 小节 2 对一些翻译词眼进行了统一 3 修正了一些格式问题 2018-10-21 21:27:50 +08:00
3a73b1d92b fix: memory safety typo 2018-10-18 21:26:18 +08:00
6f199069a5 feat: memory safety update swift 4.2 2018-10-18 21:09:16 +08:00
517a9fdb05 Merge pull request #817 from BigNerdCoding/gh-pages
新版前期准备工作
2018-10-15 20:39:04 +08:00
ae89afc25d Update README.md 2018-10-14 09:10:03 -05:00
d7655dbc29 Update README.md 2018-10-14 09:09:29 -05:00
adfa42decf Update README.md 2018-10-14 09:01:18 -05:00
f22f44cef7 根据 CR 反馈修改部分内容 2018-10-14 12:38:01 +08:00
06e5383739 新版书籍翻译准备工作 2018-10-14 12:19:39 +08:00
514e7be186 修改 Readme 增加术语表 2018-10-14 10:52:22 +08:00
4a8365d78c 统一中文括号使用,更新目录 (#783)
* 更新 01_The_Basics 目录

* 右后 01_The_Basics.md 目录层级,只显示二级目录

* 中文语句中统一使用中文括号

* 更新目录
2018-05-07 19:22:47 +08:00
08a76e561f 移除行末空格 (#782) 2018-05-06 19:13:01 +08:00
a3f0cae500 update chapter 3 parts 04-10 for Swift 4.1 (#781) 2018-04-15 11:03:49 +08:00
da6d08cd6e markdown 格式和标点符号的一些修正 (#780)
* 修正全角逗号、句号的使用

* 修正逗号使用

* 修正一处代码空格错误

* 修正斜体范围,引用的空格使用

* 修正示例代码错误

* 修正标点,修正示例代码

* 修正标点

* 修正标点

* 添加 Swift 3.1 的更新

* 修改 Swift 3.0.1 位置

* 添加 Swift 4.0.3 更新

* 添加 Swift 4.1 更新

* 修正示例代码

* 修正 markdown 引用语法,优化翻译语句

* 修正示例代码

* 修正标点使用,优化翻译语句

* 修正示例代码

* 修正示例代码

* 优化翻译语句,修正示例代码语法

* 更新示例代码以符合 Swift 4.1

* 优化 markdown 引用格式的使用

* 优化 markdown 行内代码块使用,代码块与正文使用空格分隔

* 人工校验 markdown 行内代码块使用

* 中英文空格分隔

* 移除行末空格

* 人工校验 markdown 行内代码块使用

* 修正 markdown 无序列表使用

* 统一 markdown 行间代码块的使用

* 人工修正顿号与句号的使用

* 人工修改双引号的使用

* 行内代码块使用“`代码块`”格式
2018-04-15 11:02:03 +08:00
c4e5f11769 翻译细节与 markdown 统一格式修改 (#779)
* 修正全角逗号、句号的使用

* 修正逗号使用

* 修正一处代码空格错误

* 修正斜体范围,引用的空格使用

* 修正示例代码错误

* 修正标点,修正示例代码

* 修正标点

* 修正标点

* 添加 Swift 3.1 的更新

* 修改 Swift 3.0.1 位置

* 添加 Swift 4.0.3 更新

* 添加 Swift 4.1 更新

* 修正示例代码

* 修正 markdown 引用语法,优化翻译语句

* 修正示例代码

* 修正标点使用,优化翻译语句

* 修正示例代码

* 修正示例代码

* 优化翻译语句,修正示例代码语法

* 更新示例代码以符合 Swift 4.1

* 优化 markdown 引用格式的使用

* 优化 markdown 行内代码块使用,代码块与正文使用空格分隔

* 人工校验 markdown 行内代码块使用

* 中英文空格分隔

* 移除行末空格

* 人工校验 markdown 行内代码块使用

* 修正 markdown 无序列表使用
2018-04-14 10:32:56 +08:00
78e2e93ae0 chapter2 12_Subscripts.md 替换4.0前过时的方法 (#776) 2018-04-12 16:10:53 +08:00
ec82cbd6fe Translations for Swift 4.1 (#777)
* Translations for Chapter 3 part 01 for Swift 4.0/4.1

* update chapter 3 part 02 for swift 4.1

* update chapter 3 part 03 for Swift 4.1

* update chapter 1 part 01 for Swift 4.1

* update chapter 1 part 02 for Swift 4.1

* update chapter 1 part 03 for Swift 4.1

* update chapter 2 part 01 for Swift 4.1

* update chapter 2 part 02 for Swift 4.1

* update chapter 2 part 3 for Swift 4.1

* update "summary" and corrected file names

* update chapter 2 part 4 for Swift 4.1

* update chapter 2 part 5 for Swift 4.1

* update chapter 2 part 6 for Swift 4.1

* update chapter 2 parts 06-11 and other parts' errors

* update chapter 2 parts 12-14

* update chapter 2 parts 14-19

* update all chapter 2

* update whole chapter 1

* update some parts of chapter 3
2018-04-12 16:10:36 +08:00
53ac4e8a87 修正标点、更新 04_revision_history.md (#775)
* 修正全角逗号、句号的使用

* 修正逗号使用

* 修正一处代码空格错误

* 修正斜体范围,引用的空格使用

* 修正示例代码错误

* 修正标点,修正示例代码

* 修正标点

* 修正标点

* 添加 Swift 3.1 的更新

* 修改 Swift 3.0.1 位置

* 添加 Swift 4.0.3 更新

* 添加 Swift 4.1 更新

* 修正示例代码
2018-03-01 11:26:22 +08:00
73ea47e3eb Update README.md 2018-02-21 08:17:05 -06:00
067bc9634d 中文逗号、句号使用修正 (#773)
* 修正全角逗号、句号的使用

* 修正逗号使用

* 修正一处代码空格错误

* 修正斜体范围,引用的空格使用

* 修正示例代码错误
2018-02-19 22:48:55 +08:00
d59123919f Merge pull request #772 from bqlin/develop
去除冗余空格与空行
2018-02-14 22:10:11 +08:00
34f8d0c6b9 去除行末冗余空格 2018-02-14 19:53:19 +08:00
ad1d3944bc 去除冗余空行 2018-02-14 19:41:29 +08:00
90f8d2cbbd 去除多余空行 2018-02-14 18:36:07 +08:00
ceb6979c82 Merge pull request #771 from bqlin/develop
去除标题空行
2018-02-08 10:48:36 +08:00
0cbec45c9f 优化标题换行 2018-02-07 18:45:31 +08:00
d9617c415f 修正标点 2018-02-07 14:42:43 +08:00
fd1aecdb89 Merge pull request #755 from muhlenXi/develop
Finish initialization chapter translate
2018-02-01 14:49:14 +08:00
210d43551f Update README.md 2018-01-29 22:39:25 -06:00
025d16c3ba 20,Nested Types,for Swift 4.0 (#758) 2017-10-20 10:16:08 +08:00
c78fa92988 Merge pull request #756 from kemchenj/develop
Language Guide 大部分章节更新至 Swift 4.0
2017-09-29 09:51:36 -05:00
495214b832 24,Memory Safe,词汇调整
”专属访问权“ -> “访问独占权”
2017-09-29 17:52:36 +08:00
68ea29968a 2,Basic Operators,for Swift 4.0 2017-09-29 10:18:23 +08:00
98805bb7c4 1,The Basics,for Swift 4.0 2017-09-29 10:18:04 +08:00
6db217d2c7 24,Memory Safe,for Swift 4.0 2017-09-28 22:55:26 +08:00
8a628beaa8 18,Error Handling,for Swift 4.0 2017-09-28 22:55:26 +08:00
1342d15a16 23,Generics,for Swift 4.0 2017-09-28 22:55:26 +08:00
7d5e310e86 03,Strings and Characters,for Swift 4.0 2017-09-28 22:55:26 +08:00
cd90e8a170 25,Access Control,for Swift 4
1. public、extension 等关键字全部改回使用英文
2. 更新关于 private 的内容
2017-09-28 22:55:26 +08:00
048631e6b8 09,Classes and Structures,for Swift 4 2017-09-28 22:55:26 +08:00
aef5000b0e 11,Methods,for Swift 4.0 2017-09-28 22:55:26 +08:00
6f4ff95741 21,Extensions,for Swift 4.0 2017-09-28 22:55:26 +08:00
12b4818fdb 05,Control Flow,for Swift 4 2017-09-28 22:55:26 +08:00
24a0c355ee 07,Closures,for Swift 4 2017-09-28 22:55:26 +08:00
e5b7c926f3 13,Inheritance,for Swift 4 2017-09-28 22:55:26 +08:00
d222669668 18,Error Handling,for Swift 4.0 2017-09-28 22:55:26 +08:00
00b6cfcb2c 10,Properties,for Swift 4.0 2017-09-28 22:55:26 +08:00
f5bc40e272 08,Enumerations,for Swift 4.0 2017-09-28 22:55:26 +08:00
0d8dc74a94 06,Functions,for Swift 4.0 2017-09-28 22:55:26 +08:00
f728ffeacd 17,Optional Chaining,for Swift 4.0 2017-09-28 22:55:26 +08:00
bfd072a14d 19,Type Casting,for Swift 4.0 2017-09-28 22:55:26 +08:00
da73d25416 12,Subscripts,for Swift 4.0 2017-09-28 22:55:26 +08:00
bc2c9aaef0 15,Deinitialization,for Swift 4.0 2017-09-28 22:55:26 +08:00
f869dc842c Resolving 01_a_about_Swift file conflicts 2017-09-27 14:41:50 +08:00
70ebf4fe50 Merge remote-tracking branch 'origin/develop' into develop 2017-09-27 14:35:03 +08:00
2a33ed0b68 Complete the translation and proofreading of the a_swift_tour file 2017-09-27 14:17:22 +08:00
f8aaac1837 finish half of the a _swift_tour file 2017-09-27 14:17:22 +08:00
2ae623f0e0 Add and finish 02 version compatibility file translate. 2017-09-27 14:17:22 +08:00
ba4905fc7c Finish initialization chapter translate 2017-09-27 14:17:22 +08:00
2b2a898640 Complete the translation and proofreading of the a_swift_tour file 2017-09-27 12:06:59 +08:00
01bf831dbb finish half of the a _swift_tour file 2017-09-26 14:38:15 +08:00
2dd31dffae Merge pull request #754 from WhistlingArrow/develop
修改了Swift4.0对扩展声明部分的补充
2017-09-25 23:10:41 +08:00
7ae7164290 Merge pull request #753 from rain2540/gh-pages
关于 Swift(About Swift)针对 Swift 4.0 更新
2017-09-25 23:08:57 +08:00
a387c8de17 Add and finish 02 version compatibility file translate. 2017-09-25 11:08:14 +08:00
a1b00d6121 Finish initialization chapter translate 2017-09-24 18:10:42 +08:00
bc4f5b5c44 修改了Swift4.0对扩展声明部分的补充 2017-09-22 14:53:52 +08:00
7861f9901a feat (01_swift) : update for Swift 4.0 2017-09-21 14:58:59 +08:00
a49e63768b Merge pull request #712 from xqin/gh-pages
修正 `比较运算符` 中的翻译错误
2017-09-21 10:03:02 +08:00
b33fd51696 修正 比较运算符 中的翻译错误
英文原句: 
```
It doesn’t matter that "zebra" isn’t less than "apple", because the comparison is already determined by the tuples’ first elements.
```

目前的翻译: 
```
所以"zebra"小于"apple"没有任何影响,因为元组的比较已经被第一个元素决定了。
```

修正后的翻译: 
```
所以`"zebra"`大于`"apple"`对结果没有任何影响,因为元组的比较结果已经被第一个元素决定了。
```

原翻译错误的地方:  `isn't less` 翻译为中文为 `不小于` 或者 `大于`, 而目前翻译的是 `小于`, 很明显是错误的.
且 `zebra` 是大于 `apple` 的, 所以这里翻译成 `小于` 是不可能正确的.
2017-09-02 01:48:21 +08:00
1d2564dbd0 Merge pull request #711 from magicdict/master 2017-09-02 01:13:44 +08:00
0c18c51c8a Protocol Composition 2017-09-01 14:59:22 +08:00
9c0a744c47 断言和先决条件 2017-09-01 14:44:41 +08:00
2db98b316e 具有泛型 Where 子句的关联类型
泛型下标
2017-09-01 11:47:47 +08:00
b79439d416 “多行字符串字面量” 的追加 (#709)
Swift 4.0 “多行字符串字面量”的对应
2017-09-01 10:53:17 +08:00
a104f980b4 “多行字符串字面量” 的追加
Swift 4.0 “多行字符串字面量”的对应
2017-09-01 10:42:52 +08:00
dde3afbd07 Fix some error and typo. (#705)
* Fix chess board size.

Fix chess board size.

* Fix quotation marks.

Fix quotation marks.

* Fix code error.

Fix code error.

* Update punctuation.

Update punctuation.

* Remove wrong punctuation.

Remove wrong punctuation.
2017-06-29 19:49:17 -05:00
5aef50b6a1 Typo. #701 2017-05-31 12:16:02 +08:00
b91f75b529 Typo (#700) (#700) 2017-05-23 17:21:49 +08:00
c04b28ba60 Typo (#699) 2017-05-17 22:05:09 +08:00
fd88deac6e Merge pull request #697 from SketchK/develop
fix syntax mistake
2017-04-21 23:51:36 +08:00
0c87be8409 fix syntax mistake 2017-04-21 22:06:15 +08:00
c74f8bc7ee Merge pull request #695 from qhd/develop-3.1-Initialization
14, Initialization, for 3.1
2017-04-21 18:36:13 +08:00
544a1c697a Merge pull request #696 from SketchK/develop
check: about swift && a swift tour
2017-04-21 18:36:04 +08:00
ee858e3bb0 check: about swift && a swift tour 2017-04-21 18:00:42 +08:00
b2a0c3aa64 05, Control Flow, for 3.1 (#694)
* 翻译<控制流>中3.1更新的部分

* 格式修正
2017-04-18 12:16:18 +08:00
db8f2d65a9 14, Initialization, for 3.1 2017-04-18 12:08:13 +08:00
d18f3d02e1 05, Control Flow, for 3.1 (#694)
* 翻译<控制流>中3.1更新的部分

* 格式修正
2017-04-18 10:31:49 +08:00
eda2310aab 23, generics, for 3.1 (#691)
* 翻译<泛型>中3.1添加的部分

* 去掉在上文添加的两个空格

* 英文添加加空格或单引号

* 在`xxx`前后添加空格
2017-04-10 12:09:42 +08:00
3290b3460c Fix #688 2017-03-16 16:19:06 +08:00
5e14a60046 Update 25_Advanced_Operators.md (#686)
修正一句不恰当的描述和一处过时的代码示例。
2017-03-14 16:19:03 +08:00
80e357c01c Update 22_Protocols.md (#685)
改正示例代码中的两处错误
2017-02-17 10:11:17 +08:00
ef7dc20a57 Update 24_Access_Control.md 2017-01-25 20:38:48 +08:00
141ca2ee6e Typo. fixed #683 2017-01-24 13:36:21 +08:00
d03d2dfc75 Typo. fixed #683 2017-01-24 11:52:23 +08:00
e86602b95c Fix typo. close #682 2017-01-20 12:47:50 +08:00
c678d198e4 Update 23_Generics.md (#680)
参考官方文档,修改一些语法上的问题
2017-01-04 13:34:06 +08:00
59e630f19e Update 22_Protocols.md (#678)
语法不对,参考官方文档修改过来了
2017-01-03 18:29:54 +08:00
33e4f02ee2 Update 22_Protocols.md (#679)
官方文档的写法是这样的
2017-01-03 18:29:32 +08:00
1fb769e4df 删除 计算字符数量 部分多余的翻译 (修正简体中文部分) (#676)
* 删除 计算字符数量 部分多余的翻译

* 删除 计算字符数量 部分多余的翻译 (修正简体中文部分)
2016-12-27 18:21:14 +08:00
ea125ddb18 删除 计算字符数量 部分多余的翻译 (#675) 2016-12-27 17:53:48 +08:00
Leo
de4a848d6f fix sorted() (#674) 2016-12-26 19:51:31 +08:00
Don
ef577dbad4 Update 07_Closures.md (#672)
incrementor -> incrementer
2016-12-09 11:10:58 +08:00
fc6dc8c973 Merge branch 'gh-pages' of github.com:numbbbbb/the-swift-programming-language-in-chinese into gh-pages 2016-11-15 10:48:05 +08:00
85ee2bc5cf replace new cover 2016-11-15 10:47:50 +08:00
9b3a98b473 修复枚举中的. 2016-11-15 09:59:00 +08:00
7d3aa4650d 添加未翻译的片段 (#670) 2016-11-14 16:08:24 +08:00
0b2daf4c77 25,advanced opertors, for 3.0.1 2016-11-13 20:19:35 +08:00
e130059ba0 24, access control 2016-11-13 20:04:29 +08:00
882d38c792 23, generics, for 3.0.1 2016-11-13 16:51:04 +08:00
fe606ddc93 22, protocol, for 3.0.1 2016-11-13 16:31:02 +08:00
3112212509 21, extension, for 3.0.1 2016-11-13 15:55:56 +08:00
91da2d6ebf 20, nest types, for 3.0.1 2016-11-13 15:45:47 +08:00
dc3b19aa6f 19,type casting ,for 3.0.1 2016-11-13 15:42:51 +08:00
4f24490fe4 18,error handling ,for 3.0.1 2016-11-13 15:27:37 +08:00
db9afa4d0b 17, optional chaining, for 3.0.1 2016-11-13 13:47:47 +08:00
03d6026823 16, automatic reference counting, for 3.0.1 2016-11-13 13:04:48 +08:00
203e04a136 15, deinitialization, for 3.0.1 2016-11-13 08:40:17 +08:00
e5799258ad 14, initialization, for 3.0.1 2016-11-13 01:44:53 +08:00
0ef78137fe 13, inheritance, for 3.0.1 2016-11-13 01:15:18 +08:00
0d0d772723 12, subscripts, for 3.0.1 2016-11-13 01:07:20 +08:00
5ac4199715 11, methods , for 3.0.1 2016-11-13 01:01:43 +08:00
4c01c83f95 10, properties, for 3.0.1 2016-11-12 20:51:54 +08:00
9869c0448b 09, classes and structure, for 3.0.1 2016-11-12 20:38:36 +08:00
f8201be713 08,enumerations , for 3.0.1 2016-11-12 20:26:11 +08:00
eedc29b425 07, closure, for 3.0.1 2016-11-12 20:15:51 +08:00
a296757ea8 06, functions, for 3.0.1 2016-11-12 20:02:53 +08:00
ce5501a4e8 05,control flow, for 3.0.1 2016-11-12 12:40:51 +08:00
786108db04 04, collection types, for 3.0.1 2016-11-12 10:43:35 +08:00
70d5c57c96 03, String and characters, for 3.0.1 2016-11-11 21:34:35 +08:00
2da452f6bf 02,basic operators, for 3.0.1 2016-11-11 21:08:36 +08:00
98de761133 01,the basics, for swift 3.0.1 2016-11-11 12:47:10 +08:00
1d0c833c35 update revision history for 3.0.1 2016-11-10 08:45:34 +08:00
c122ef5d7d modify MacOS to macOS 2016-11-10 08:02:39 +08:00
869b339375 for 3.0.1 2016-11-10 00:00:07 +08:00
e59b912d69 Merge pull request #669 from Realank/gh-pages
[Control Flow] Fix Typo
2016-11-08 08:58:51 +08:00
41a2f8b71f Merge remote-tracking branch 'numbbbbb/gh-pages' into gh-pages 2016-11-07 11:05:30 -08:00
fe0c1fc4aa fix typo 2016-11-07 11:02:30 -08:00
2f77ef47f9 Merge pull request #668 from CoCodeIsLife/develop
修改代码块儿bug
2016-10-26 21:57:55 +08:00
b38540f643 修改代码块儿bug 2016-10-24 14:52:47 +08:00
aef3d29f2c #666, collection for swift 3.0 2016-10-09 07:00:45 +08:00
0a792fa7a6 update revision history for swift 3.0 2016-10-07 17:25:34 +08:00
990803d796 for 3.0 2016-10-06 16:31:03 +08:00
833c6078da fixed format 2016-09-27 06:46:33 +08:00
f472b7d84a fixed #664 2016-09-27 06:41:32 +08:00
2d50b83a34 error handling for swift 3.0 2016-09-24 07:23:10 +08:00
31a43a71b8 Update 08_Enumerations.md 2016-09-24 06:59:47 +08:00
9c27e01947 enum for swift3.0 2016-09-24 06:57:01 +08:00
fa3f0bc8ac fixed #661 2016-09-23 23:12:19 +08:00
50f692c277 Update README.md 2016-09-23 23:05:35 +08:00
1d00ea6156 Merge pull request #657 from floydzhang315/gh-pages
Add 3.0 cover image
2016-09-23 14:07:27 +08:00
9250e9132e Add 3.0 cover image 2016-09-23 14:03:06 +08:00
1cd9134cbe Update README.md 2016-09-23 10:32:06 +08:00
dcbc857436 Merge pull request #656 from chenmingbiao/gh-pages
添加 protocol 章节校对日期
2016-09-23 10:23:21 +08:00
d5629a91e6 添加protocol章节校对日期 2016-09-23 10:09:00 +08:00
5feabef88f Merge remote-tracking branch 'numbbbbb/gh-pages' into gh-pages 2016-09-14 10:02:57 +08:00
64a2be3b3a Merge remote-tracking branch 'numbbbbb/gh-pages' into gh-pages 2016-09-14 09:39:59 +08:00
5739f5d957 Update to swift 3.0 final 2016-09-14 09:39:41 +08:00
90 changed files with 16936 additions and 14740 deletions

5
.gitbook.yaml Normal file
View File

@ -0,0 +1,5 @@
root: ./source/
structure:
readme: ../README.md
summary: SUMMARY.md

12
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,12 @@
# These are supported funding model platforms
github: [SketchK, numbbbbb]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

1
CNAME
View File

@ -1 +0,0 @@
gg.swiftguide.cn

294
README.md
View File

@ -3,228 +3,138 @@
中文版 Apple 官方 Swift 教程《The Swift Programming Language》
[英文原版](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/index.html#//apple_ref/doc/uid/TP40014097-CH3-ID0)
**如果想帮忙翻译或者校对请加QQ群480616051谢谢**
[英文原版在线版](https://docs.swift.org/swift-book/)
# 在线阅读
使用 Gitbook 制作,可以直接[在线阅读](http://swiftguide.cn/)。
使用 GitBook 制作,可以直接 [在线阅读](https://swiftgg.gitbook.io/swift/)。
# 当前阶段
已经更新到 Swift 3.0。 2016-09-23
# 3.0 译者记录
相关[issue](https://github.com/numbbbbb/the-swift-programming-language-in-chinese/issues/628)
- Functions: [crayygy](https://github.com/crayygy)
- Control Flow: [Realank](https://github.com/Realank)
- Closures: [LanfordCai](https://github.com/LanfordCai)
- Protocols: [chenmingbiao](https://github.com/chenmingbiao)
- The Basics:[chenmingbiao](https://github.com/chenmingbiao)
- Advanced Operators: [mmoaay](https://github.com/mmoaay)
Language Reference:
- Attributes: [WhoJave](https://github.com/WhoJave)
- Statements: [chenmingjia](https://github.com/chenmingjia)
- Declarations: [chenmingjia](https://github.com/chenmingjia)
- Expressions: [chenmingjia](https://github.com/chenmingjia)
- Types: [lettleprince](https://github.com/lettleprince)
- Generic Parameters and Arguments: [chenmingjia](https://github.com/chenmingjia)
# 2.1 & 2.2 译者记录
详见各章节开头位置。
# 2.0 译者记录
- About Swift [xtymichael](https://github.com/xtymichael)
- A Swift Tour[xtymichael](https://github.com/xtymichael)
- The Basics[xtymichael](https://github.com/xtymichael)
- Basic Operators[AlanMelody](https://github.com/AlanMelody)
- Strings and Characters[DianQK](https://github.com/DianQK)
- Collection Types[AlanMelody](https://github.com/AlanMelody)
- Control Flow[AlanMelody](https://github.com/AlanMelody)
- Functions[dreamkidd](https://github.com/dreamkidd)
- Closures[100mango](https://github.com/100mango)
- Enumerations[futantan](https://github.com/futantan)
- Classes and Structures[SkyJean](https://github.com/SkyJean)
- Properties[yangsiy](https://github.com/yangsiy)
- Methods[DianQK](https://github.com/DianQK)
- Subscripts[shanksyang](https://github.com/shanksyang)
- Inheritance[shanksyang](https://github.com/shanksyang)
- Initialization[chenmingbiao](https://github.com/chenmingbiao)
- Deinitialization[chenmingbiao](https://github.com/chenmingbiao)
- Automatic Reference Counting[Channe](https://github.com/Channe)
- Optional Chaining[lyojo](https://github.com/lyojo)
- Error Handling[lyojo](https://github.com/lyojo)
- Type Casting[yangsiy](https://github.com/yangsiy)
- Nested Types[SergioChan](https://github.com/SergioChan)
- Extensions[shanksyang](https://github.com/shanksyang)
- Protocols[futantan](https://github.com/futantan)
- Generics[SergioChan](https://github.com/SergioChan)
- Access Control[mmoaay](https://github.com/mmoaay)
- Advanced Operators[buginux](https://github.com/buginux)
- About the Language Reference[KYawn](https://github.com/KYawn)
- Lexical Structure[buginux](https://github.com/buginux)
- Types[EudeMorgen](https://github.com/EudeMorgen)
- Expressions[EudeMorgen](https://github.com/EudeMorgen)
- Statements[littledogboy](https://github.com/littledogboy)
- Declarations[Lenhoon](https://github.com/Lenhoon)
- Attributes[KYawn](https://github.com/KYawn)
- Patterns[ray16897188](https://github.com/ray16897188)
- Generic Parameters and Arguments[wardenNScaiyi](https://github.com/wardenNScaiyi)
- Summary of the Grammar[miaosiqi](https://github.com/miaosiqi)
# 1.0 译者记录
* 欢迎使用 Swift
* 关于 Swift ([numbbbbb])
* Swift 初见 ([numbbbbb])
* Swift 教程
* 基础部分 ([numbbbbb], [lyuka], [JaySurplus])
* 基本操作符 ([xielingwang])
* 字符串和字符 ([wh1100717])
* 集合类型 ([zqp])
* 控制流 ([vclwei], [coverxit], [NicePiao])
* 函数 ([honghaoz])
* 闭包 ([wh1100717])
* 枚举 ([yankuangshi])
* 类和结构体 ([JaySurplus])
* 属性 ([shinyzhu])
* 方法 ([pp-prog])
* 下标 ([siemenliu])
* 继承 ([Hawstein])
* 构造过程 ([lifedim])
* 析构过程 ([bruce0505])
* 自动引用计数 ([TimothyYe])
* 可选链 ([Jasonbroker])
* 类型检查 ([xiehurricane])
* 嵌套类型 ([Lin-H])
* 扩展 ([lyuka])
* 协议 ([geek5nan])
* 泛型 ([takalard])
* 高级操作符 ([xielingwang])
* 语言参考
* 关于语言参考 ([dabing1022])
* 词法结构 ([superkam])
* 类型 ([lyuka])
* 表达式 ([sg552] )
* 语句 ([coverxit])
* 声明 ([marsprince])
* 特性 ([Hawstein])
* 模式 ([honghaoz])
* 泛型参数 ([fd5788])
* 语法总结 ([StanZhai])
- 更新到 Swift 5.12019-07-10
- 更新到 Swift 5.02019-04-05
- 更新到 Swift 4.22019-01-29
- 更新到 Swift 4.12018-04-12感谢 [@Mylittleswift](https://github.com/Mylittleswift)
- 更新到 Swift 3.02016-09-23
# 贡献力量
如果想做出贡献的话,你可以:
- 参与翻译
- 帮忙校对,挑错别字、病句等等
- 提出修改建议
- 提出术语翻译建议
# 翻译建议
如果你愿意一起校对的话,请仔细阅读:
如果你有兴趣参与项目,请仔细阅读说明
- 使用markdown进行翻译文件名必须使用英文因为中文的话gitbook编译的时候会出问题
- 翻译后的文档请放到source文件夹下的对应章节中然后pull request即可我会用gitbook编译成网页
- 工作分支为gh-pages用于GitHub的pages服务
- fork过去之后新建一个分支进行翻译完成后pull request这个分支没问题的话我会合并到gh-pages分支中
- 有其他任何问题都欢迎发issue我看到了会尽快回复
排版格式和流程说明:
谢谢!
- 翻译排版格式要求参考 SwiftGG [排版指南](https://github.com/SwiftGGTeam/translation/blob/master/SwiftGG%20排版指南.md)
- Pull Request 发起方式参考 SwiftGG [Pull Request 说明](https://github.com/SwiftGGTeam/translation/blob/master/%E7%BF%BB%E8%AF%91%E6%B5%81%E7%A8%8B%E6%A6%82%E8%BF%B0%E5%8F%8APR%E8%AF%B4%E6%98%8E.md#%E5%A6%82%E4%BD%95%E5%8F%91%E8%B5%B7-pull-request)
# 关于术语
原版文档差异比较:
翻译术语的时候请参考这个流程:
翻译时可以通过 Calibre 软件对 [document 目录下](https://github.com/SwiftGGTeam/the-swift-programming-language-in-chinese/tree/gh-pages/document) 不同版本的文档进行 diff检查待更新部分。
- 尽量保证和已翻译的内容一致
- 尽量先搜索,一般来说编程语言的大部分术语是一样的,可以参考[微软官方术语搜索](http://www.microsoft.com/Language/zh-cn/Search.aspx)
- 如果以上两条都没有找到合适的结果,请自己决定一个合适的翻译或者直接使用英文原文,后期校对的时候会进行统一
diff 操作如下:
# 参考流程
将最新文档加入到 Calibre 中,点击 **Edit Book**,然后在编辑界面选择 **File** -> **Compare to other book** 检查各模块的更新内容,详见 [链接](https://manual.calibre-ebook.com/diff.html)。
有些朋友可能不太清楚如何帮忙翻译,我这里写一个简单的流程,大家可以参考一下
其他说明
1. 首先fork我的项目
2. 把fork过去的项目也就是你的项目clone到你的本地
3. 在命令行运行 `git branch develop` 来创建一个新分支
4. 运行 `git checkout develop` 来切换到新分支
5. 运行 `git remote add upstream https://github.com/numbbbbb/the-swift-programming-language-in-chinese.git` 把我的库添加为远端库
6. 运行 `git remote update`更新
7. 运行 `git fetch upstream gh-pages` 拉取我的库的更新到本地
8. 运行 `git rebase upstream/gh-pages` 将我的更新合并到你的分支
- 相关术语请严格按照术语表来翻译,如果有问题可以发 Issue 大家一起讨论
- 使用 Markdown 进行翻译,文件名必须使用英文
- 翻译后的文档请放到 source 文件夹下的对应章节中,然后 Pull Request 即可,我们会用 GitBook 编译成网页
- 有其他任何问题都欢迎发 Issue
这是一个初始化流程只需要做一遍就行之后请一直在develop分支进行修改。
# 术语表
如果修改过程中我的库有了更新请重复6、7、8步。
翻译术语的时候请参考这个对照表:
修改之后首先push到你的库然后登录GitHub在你的库的首页可以看到一个 `pull request` 按钮,点击它,填写一些说明信息,然后提交即可。
| 术语 | 备选翻译 |
| --- | --- |
| argument | 实参 |
| parameter | 形参 |
| associated type | 关联类型 |
| range | 区间 |
| type property | 类型属性 |
| unary operator | 一元运算符 |
| binary operator | 二元运算符 |
| ternary operator | 三元运算符 |
| labeled statement | 具名语句 |
| conform protocol | 遵循协议 |
| availability-condition | 可用性条件 |
| fallthrough | 贯穿 |
| branch statement | 分支语句 |
| control transfer statement | 控制传递语句 |
| type annotation | 类型注解 |
| type identifier | 类型标识符 |
| metatype type | 元类型 |
| protocol composition type | 复合协议类型 |
| associated value | 关联值 |
| raw value | 原始值 |
| computed property | 计算属性 |
| stored property | 存储属性 |
| operator | 运算符 |
| playground | 不翻译 |
| array | 数组 |
| dictionary | 字典 |
| list | 列表 |
| statement | 语句 |
| expression | 表达式 |
| optional | 可选 |
| implicitly unwrapped optional | 隐式解包可选值 |
| optional binding | 可选绑定 |
| optional chaining | 可选链 |
| collection | 集合 |
| convention | 约定 |
| iterate | 迭代 |
| nest | 嵌套 |
| inheritance | 继承 |
| override | 重写 |
| base class | 基类 |
| designated initializer | 指定构造器 |
| convenience initializer | 便利构造器 |
| automatic reference counting | 自动引用计数 |
| type inference | 类型推断 |
| type casting | 类型转换 |
| unwrapped | 解包 |
| wrapped | 包装 |
| note | 注意 |
| closure | 闭包 |
| tuple | 元组 |
| first-class | 一等 |
| deinitializer | 析构器 |
| initializer | 构造器 |
| initialization | 构造过程 |
| deinitialization | 析构过程 |
| getter | 不翻译 |
| setter | 不翻译 |
| subscript | 下标 |
| property | 属性 |
| attribute | 特性或者属性,根据上下文 |
| method | 方法 |
| enumeration | 枚举 |
| structure | 结构体 |
| protocol | 协议 |
| extension | 扩展 |
| generic | 泛型 |
| literal value | 字面量 |
| alias | 别名 |
| assertion | 断言 |
| conditional compilation | 条件编译 |
| opaque type | 不透明类型 |
| function | 函数 |
| runtime | 运行时 |
# 贡献者
# 开源协议
基于[WTFPL](http://en.wikipedia.org/wiki/WTFPL)协议开源。
[贡献者列表](https://github.com/SwiftGGTeam/the-swift-programming-language-in-chinese/blob/gh-pages/source/contributors.md),感谢大家!
[numbbbbb]:https://github.com/numbbbbb
[stanzhai]:https://github.com/stanzhai
[coverxit]:https://github.com/coverxit
[wh1100717]:https://github.com/wh1100717
[TimothyYe]:https://github.com/TimothyYe
[honghaoz]:https://github.com/honghaoz
[lyuka]:https://github.com/lyuka
[JaySurplus]:https://github.com/JaySurplus
[Hawstein]:https://github.com/Hawstein
[geek5nan]:https://github.com/geek5nan
[yankuangshi]:https://github.com/yankuangshi
[xielingwang]:https://github.com/xielingwang
[yulingtianxia]:https://github.com/yulingtianxia
[twlkyao]:https://github.com/twlkyao
[dabing1022]:https://github.com/dabing1022
[vclwei]:https://github.com/vclwei
[fd5788]:https://github.com/fd5788
[siemenliu]:https://github.com/siemenliu
[youkugems]:https://github.com/youkugems
[haolloyin]:https://github.com/haolloyin
[wxstars]:https://github.com/wxstars
[IceskYsl]:https://github.com/IceskYsl
[sg552]:https://github.com/sg552
[superkam]:https://github.com/superkam
[zac1st1k]:https://github.com/zac1st1k
[bzsy]:https://github.com/bzsy
[pyanfield]:https://github.com/pyanfield
[ericzyh]:https://github.com/ericzyh
[peiyucn]:https://github.com/peiyucn
[sunfiled]:https://github.com/sunfiled
[lzw120]:https://github.com/lzw120
[viztor]:https://github.com/viztor
[wongzigii]:https://github.com/wongzigii
[umcsdon]:https://github.com/umcsdon
[zq54zquan]:https://github.com/zq54zquan
[xiehurricane]:https://github.com/xiehurricane
[Jasonbroker]:https://github.com/Jasonbroker
[tualatrix]:https://github.com/tualatrix
[pp-prog]:https://github.com/pp-prog
[088haizi]:https://github.com/088haizi
[baocaixiong]:https://github.com/baocaixiong
[yeahdongcn]:https://github.com/yeahdongcn
[shinyzhu]:https://github.com/shinyzhu
[lslxdx]:https://github.com/lslxdx
[Evilcome]:https://github.com/Evilcome
[zqp]:https://github.com/zqp
[NicePiao]:https://github.com/NicePiao
[LunaticM]:https://github.com/LunaticM
[menlongsheng]:https://github.com/menlongsheng
[lifedim]:https://github.com/lifedim
[happyming]:https://github.com/happyming
[bruce0505]:https://github.com/bruce0505
[Lin-H]:https://github.com/Lin-H
[takalard]:https://github.com/takalard
[dabing1022]:https://github.com/dabing1022
[marsprince]:https://github.com/marsprince
# 协议
和 [苹果官方文档](https://swift.org/documentation/) 协议一致:[Creative Commons Attribution 4.0 International (CC BY 4.0) License](https://creativecommons.org/licenses/by/4.0/)。

View File

@ -1,22 +0,0 @@
#!/usr/bin/python
# coding:utf-8
import os
def iter(path):
for root, dirs, files in os.walk(path):
for fn in files:
if fn.endswith(".html"):
with open(root + '/' + fn, 'r') as f:
content = f.read()
content = content.replace('<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.1.3/ace.js"></script>', '<script src="http://cdn.bootcss.com/ace/1.1.3/ace.js"></script>').replace('<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.1.3/mode-javascript.js"></script>', '<script src="http://cdn.bootcss.com/ace/1.1.3/mode-javascript.js"></script>')
insert_pos = content.find("</li>", content.find("Generated using GitBook")) + 6
content = content[:insert_pos] + '''<li style="margin-left:15%;"> <iframe src="http://ghbtns.com/github-btn.html?user=numbbbbb&repo=the-swift-programming-language-in-chinese&type=watch&count=true&size=large"
allowtransparency="true" frameborder="0" scrolling="0" width="170" height="30"></iframe></li>''' + content[insert_pos:]
content.replace(r'<title>.*?</title>', "<title>《The Swift Programming Language》完整中文版</title>")
with open(root + '/' + fn, 'w') as f:
f.write(content)
iter(os.getcwd())

View File

@ -1,9 +0,0 @@
{
"name": "The Swift Programming Language 中文版",
"introduction": "中文版《The Swift Programming Language》",
"path": {
"content": "source",
"toc": "source/SUMMARY.md",
"readme": "source/README.md"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 838 KiB

After

Width:  |  Height:  |  Size: 882 KiB

BIN
cover/cover_3.0.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 882 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1 +0,0 @@
google-site-verification: googleb0a4f5a22e9cb82f.html

View File

@ -1,19 +1,8 @@
<!DOCTYPE HTML>
<html lang="en-US" manifest="./manifest.appcache">
<head>
<meta http-equiv="refresh" content="0; url=http://wiki.jikexueyuan.com/project/swift/" />
<meta http-equiv="refresh" content="0; url=https://swiftgg.gitbook.io/swift/" />
</head>
<body>
<script type="text/javascript">
var _bdhmProtocol = (("https:" == document.location.protocol) ? " https://" : " http://");
document.write(unescape("%3Cscript src='" + _bdhmProtocol + "hm.baidu.com/h.js%3F21e159ce3496d7b5f80aa3c4f1370b04' type='text/javascript'%3E%3C/script%3E"));
</script>
</body>
</html>

View File

@ -154,14 +154,14 @@ let yenSign: Character = "¥"
通過調用全局`countElements`函數,並將字符串作為參數進行傳遞,可以獲取該字符串的字符數量。
```swift
let unusualMenagerie = "Koala □謠Snail □□Penguin □笠Dromedary □□
let unusualMenagerie = "Koala □謠Snail □□Penguin □笠Dromedary □□
println("unusualMenagerie has \(countElements(unusualMenagerie)) characters")
// 打印輸出:"unusualMenagerie has 40 characters"
```
> 注意:
不同的 Unicode 字符以及相同 Unicode 字符的不同表示方式可能需要不同數量的內存空間來存儲。所以 Swift 中的字符在一個字符串中並不一定佔用相同的內存空間。因此字符串的長度不得不通過迭代字符串中每一個字符的長度來進行計算。如果您正在處理一個長字符串,需要注意`countElements`函數必須遍歷字符串中的字符以精準計算字符串的長度。
> 另外需要注意的是通過`countElements`返回的字符數量並不總是與包含相同字符的`NSString`的`length`屬性相同。`NSString`的`length`屬性是基於利用 UTF-16 表示的十六位代碼單元數字,而不是基於 Unicode 字符。為了解決這個問題,`NSString`的`length`屬性在被 Swift 的`String`訪問時會成為`utf16count`。
> 另外需要注意的是通過`countElements`返回的字符數量並不總是與包含相同字符的`NSString`的`length`屬性相同。`NSString`的`length`屬性是基於利用 UTF-16 表示的十六位代碼單元數字,而不是基於 Unicode 字符。
<a name="concatenating_strings_and_characters"></a>
## 連接字符串和字符 (Concatenating Strings and Characters)
@ -336,7 +336,7 @@ Swift 提供了幾種不同的方式來訪問字符串的 Unicode 表示。
下面由`D``o``g``!``□栨`DOG FACE`Unicode 標量為`U+1F436`)組成的字符串中的每一個字符代表著一種不同的表示:
```swift
let dogString = "Dog!□皂
let dogString = "Dog!□皂
```
<a name="UTF-8"></a>

View File

@ -193,14 +193,9 @@ struct AlternativeRect {
只有 getter 沒有 setter 的計算屬性就是*只讀計算屬性*。只讀計算屬性總是返回一個值,可以通過點運算符訪問,但不能設置新的值。
<<<<<<< HEAD
> 注意:
> 必須使用`var`關鍵字定義計算屬性,包括只讀計算屬性,因為他們的值不是固定的。`let`關鍵字只用來聲明常量屬性,表示初始化後再也無法修改的值。
=======
> 注意:
>
> 必須使用`var`關鍵字定義計算屬性,包括只讀計算屬性,因為它們的值不是固定的。`let`關鍵字只用來聲明常量屬性,表示初始化後再也無法修改的值。
>>>>>>> a516af6a531a104ec88da0d236ecf389a5ec72af
只讀計算屬性的聲明可以去掉`get`關鍵字和花括號:
@ -237,14 +232,9 @@ println("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
類似地,`didSet`觀察器會將舊的屬性值作為參數傳入,可以為該參數命名或者使用默認參數名`oldValue`
<<<<<<< HEAD
> 注意:
> `willSet`和`didSet`觀察器在屬性初始化過程中不會被調用,他們只會當屬性的值在初始化之外的地方被設置時被調用。
=======
> 注意:
>
> `willSet`和`didSet`觀察器在屬性初始化過程中不會被調用,它們只會當屬性的值在初始化之外的地方被設置時被調用。
>>>>>>> a516af6a531a104ec88da0d236ecf389a5ec72af
這裡是一個`willSet``didSet`的實際例子,其中定義了一個名為`StepCounter`的類,用來統計當人步行時的總步數,可以跟計步器或其他日常鍛煉的統計裝置的輸入數據配合使用。

View File

@ -100,7 +100,7 @@ struct Matrix {
init(rows: Int, columns: Int) {
self.rows = rows
self.columns = columns
grid = Array(count: rows * columns, repeatedValue: 0.0)
grid = Array(repeating: 0.0, count: rows * columns)
}
func indexIsValidForRow(row: Int, column: Int) -> Bool {
return row >= 0 && row < rows && column >= 0 && column < columns

View File

@ -1,76 +1,22 @@
> 已同步更新到 Swift 2.2
> 2016.9.23: 已经更新到 Swift 3.0。
# 3.0 更新说明
Swift 3.0 是自 Swift 开源以来第一个大的版本更新。从语言角度不兼容之前的 Swift 2.2 和 Swift 2.3 版本。Swift 3.0 的更新说明,大家可以查看[官方 blog 的说明](https://swift.org/blog/swift-3-0-released/),也可以关注 [SwiftGG](http://swift.gg) 最新的文章。学习官方文档,是掌握语言特性点的最佳途径,感谢翻译的小伙伴们为 Swift 社区所做贡献!
# 2.0 新的开始
# 3.0 译者记录
相关[issue](https://github.com/numbbbbb/the-swift-programming-language-in-chinese/issues/628)
- Functions: [crayygy](https://github.com/crayygy)
- Control Flow: [Realank](https://github.com/Realank)
- Closures: [LanfordCai](https://github.com/LanfordCai)
- Protocols: [chenmingbiao](https://github.com/chenmingbiao)
- The Basics:[chenmingbiao](https://github.com/chenmingbiao)
- Advanced Operators: [mmoaay](https://github.com/mmoaay)
> Swift 兴趣交流群:`131595168`, `146932759`, `151336833`, `153549217`. **加入一个群即可,请勿重复添加**
Language Reference:
- Attributes: [WhoJave](https://github.com/WhoJave)
- Statements: [chenmingjia](https://github.com/chenmingjia)
- Declarations: [chenmingjia](https://github.com/chenmingjia)
- Expressions: [chenmingjia](https://github.com/chenmingjia)
- Types: [lettleprince](https://github.com/lettleprince)
- Generic Parameters and Arguments: [chenmingjia](https://github.com/chenmingjia)
> <a target='_blank' href="http://swiftweekly.cn">订阅 Swift 开发者周报,每周获取最新 Swift 资源</a>
<!-- -->
> 如果您觉得这个项目不错,请<a target='_blank' href="https://github.com/numbbbbb/the-swift-programming-language-in-chinese">点击Star一下</a>,您的支持是我们最大的动力。
## 1
开源项目完成难,维护更难。
大家看到的是发布时的瞩目和荣耀,却没有看到项目本身质量不高、错误频出。这并不是翻译者和校对者的问题,他们已经付出了足够的努力。真正的问题在我,没有建立起长期的维护团队,因此后期的校对和更新都难以实施。
1.0发布之后我们就再也没能和苹果的文档同步。语法错误、编译不通过、语言不通顺阅读量直线下降最低时每天只有不到1000人访问。
6月9日calvingit发了一个issue“准备翻译2.0版本吗”,我没有回复,应该已经没人关注这个项目了吧,我想。
## 2
我错了。
![1](https://cloud.githubusercontent.com/assets/1472352/10558349/74eb84de-74fe-11e5-99d2-155dfacff068.png)
![2](https://cloud.githubusercontent.com/assets/1472352/10558351/79101476-74fe-11e5-82d5-feb815d0b86b.png)
![3](https://cloud.githubusercontent.com/assets/1472352/10558353/7c983272-74fe-11e5-8397-97c5176261ca.png)
![4](https://cloud.githubusercontent.com/assets/1472352/10558354/7cd63ae0-74fe-11e5-9b9d-1d7ec6516319.png)
![5](https://cloud.githubusercontent.com/assets/1472352/10558358/7fe50914-74fe-11e5-9b2b-a7b9129ea638.png)
![6](https://cloud.githubusercontent.com/assets/1472352/10558360/83a8b064-74fe-11e5-9c2f-2e79d3309f62.png)
在我没有任何回复的情况下,不到一天时间,有五位朋友报名。看到这些回复的时候我真的很惊讶,也很感动,无论这个项目存在多少问题,只要有人关注,有人愿意为它付出,那我还有什么理由放弃呢?
6月28日8点55分Swift 2.0翻译正式启动。按下发送按钮后我不停的刷新页面半个小时过去了一个回复都没有。“还是不行啊”“如果再过一个小时没人回复我就把issue删掉”类似的念头不断出现又不断消失。
9:35xtymichael第一个回复而且一下就认领了三篇接下来就是不断的回复认领到中午已经有超过一半章节被认领。
第二天早晨37个章节全部认领完毕。
## 3
经过一个多月的努力我们终于完成了文档的更新。听起来似乎没什么确实从1到n总是没有从0到1那么振奋人心。不过真正参与了才知道修改往往比创造更麻烦一个需要耐心一个需要激情前者往往得不到应有的重视。
但是我还是想尽最大可能去感谢他们这个项目能走到今天靠的不是我是那个issue是那些回复是这几十个兄弟在工作学习的空闲敲下的每一个字符。而我能做的只是在每篇文章的开头那个所有人都会忽略的地方加上他们的ID。
下次你再打开这篇文档可以多看看那些列在最上方的ID哪怕不去follow和star只是看一眼就好。他们的所有努力和付出就存在于这短暂的一瞥中。
Swift 2.0 参与者名单(按照章节顺序):
- [xtymichael](https://github.com/xtymichael)
- [AlanMelody](https://github.com/AlanMelody)
- [DianQK](https://github.com/DianQK)
- [dreamkidd](https://github.com/dreamkidd)
- [100mango](https://github.com/100mango)
- [futantan](https://github.com/futantan)
- [SkyJean](https://github.com/SkyJean)
- [yangsiy](https://github.com/yangsiy)
- [shanksyang](https://github.com/shanksyang)
- [chenmingbiao](https://github.com/chenmingbiao)
- [Channe](https://github.com/Channe)
- [lyojo](https://github.com/lyojo)
- [SergioChan](https://github.com/SergioChan)
- [mmoaay](https://github.com/mmoaay)
- [buginux](https://github.com/buginux)
- [KYawn](https://github.com/KYawn)
- [EudeMorgen](https://github.com/EudeMorgen)
- [littledogboy](https://github.com/littledogboy)
- [Lenhoon](https://github.com/Lenhoon)
- [ray16897188](https://github.com/ray16897188)
- [wardenNScaiyi](https://github.com/wardenNScaiyi)
- [miaosiqi](https://github.com/miaosiqi)
- [Realank](https://github.com/Realank)
最后,感谢[极客学院](http://wiki.jikexueyuan.com)提供的wiki系统在国内访问起来速度很快优化后的样式看起来也更舒服。
感谢阅读!

View File

@ -1,11 +1,11 @@
# Summary
* [欢迎使用 Swift](chapter1/chapter1.md)
* [关于 Swift](chapter1/01_swift.md)
* [Swift 初见](chapter1/02_a_swift_tour.md)
* [Swift 版本历史记录](chapter1/03_revision_history.md)
* [Swift 1.0 发布内容](v1.0.md)
* [Swift 教程](chapter2/chapter2.md)
* 欢迎使用 Swift
* [关于 Swift](chapter1/01_about_swift.md)
* [版本兼容性](chapter1/02_version_compatibility.md)
* [Swift 初见](chapter1/03_a_swift_tour.md)
* [Swift 版本历史记录](chapter1/04_revision_history.md)
* Swift 教程
* [基础部分](chapter2/01_The_Basics.md)
* [基本运算符](chapter2/02_Basic_Operators.md)
* [字符串和字符](chapter2/03_Strings_and_Characters.md)
@ -14,39 +14,35 @@
* [函数](chapter2/06_Functions.md)
* [闭包](chapter2/07_Closures.md)
* [枚举](chapter2/08_Enumerations.md)
* [类和结构体](chapter2/09_Classes_and_Structures.md)
* [类和结构体](chapter2/09_Structures_And_Classes.md)
* [属性](chapter2/10_Properties.md)
* [方法](chapter2/11_Methods.md)
* [下标](chapter2/12_Subscripts.md)
* [继承](chapter2/13_Inheritance.md)
* [构造过程](chapter2/14_Initialization.md)
* [析构过程](chapter2/15_Deinitialization.md)
* [自动引用计数](chapter2/16_Automatic_Reference_Counting.md)
* [可选链](chapter2/17_Optional_Chaining.md)
* [错误处理](chapter2/18_Error_Handling.md)
* [类型转换](chapter2/19_Type_Casting.md)
* [嵌套类型](chapter2/20_Nested_Types.md)
* [扩展](chapter2/21_Extensions.md)
* [协议](chapter2/22_Protocols.md)
* [](chapter2/23_Generics.md)
* [访问控制](chapter2/24_Access_Control.md)
* [高级运算符](chapter2/25_Advanced_Operators.md)
* [可选链](chapter2/16_Optional_Chaining.md)
* [错误处理](chapter2/17_Error_Handling.md)
* [类型转换](chapter2/18_Type_Casting.md)
* [嵌套类型](chapter2/19_Nested_Types.md)
* [扩展](chapter2/20_Extensions.md)
* [协议](chapter2/21_Protocols.md)
* [泛型](chapter2/22_Generics.md)
* [不透明类](chapter2/27_Opaque_Types.md)
* [自动引用计数](chapter2/23_Automatic_Reference_Counting.md)
* [内存安全](chapter2/24_Memory_Safety.md)
* [访问控制](chapter2/25_Access_Control.md)
* [高级运算符](chapter2/26_Advanced_Operators.md)
* 语言参考
* [关于语言参考](chapter3/01_About_the_Language_Reference.md)
* [词法结构](chapter3/02_Lexical_Structure.md)
* [类型](chapter3/03_Types.md)
* [表达式](chapter3/04_Expressions.md)
* [语句](chapter3/10_Statements.md)
* [声明](chapter3/05_Declarations.md)
* [特性](chapter3/06_Attributes.md)
* [模式](chapter3/07_Patterns.md)
* [泛型参数](chapter3/08_Generic_Parameters_and_Arguments.md)
* [语法总结](chapter3/09_Summary_of_the_Grammar.md)
* 苹果官方Blog官方翻译
* [Access Control 权限控制的黑与白](chapter4/01_Access_Control.md)
* [造个类型不是梦-白话Swift类型创建](chapter4/02_Type_Custom.md)
* [WWDC里面的那个“大炮打气球”](chapter4/03_Ballons.md)
* [Swift与C语言指针友好合作](chapter4/04_Interacting_with_C_Pointers.md)
* [引用类型和值类型的恩怨](chapter4/05_Value_and_Reference_Types.md)
* [访问控制和Protected](chapter4/06_Access_Control_and_Protected.md)
* [可选类型完美解决占位问题](chapter4/07_Optional_Case_Study.md)
* [语句](chapter3/05_Statements.md)
* [声明](chapter3/06_Declarations.md)
* [特性](chapter3/07_Attributes.md)
* [模式](chapter3/08_Patterns.md)
* [泛型参数](chapter3/09_Generic_Parameters_and_Arguments.md)
* [语法总结](chapter3/10_Summary_of_the_Grammar.md)
* 翻译贡献者
* [翻译贡献者](contributors.md)

View File

@ -0,0 +1,20 @@
# 关于 Swift
Swift 是一种非常好的编写软件的方式,无论是手机,台式机,服务器,还是其他运行代码的设备。它是一种安全,快速和互动的编程语言,将现代编程语言的精华和苹果工程师文化的智慧,以及来自开源社区的多样化贡献结合了起来。编译器对性能进行了优化,编程语言对开发进行了优化,两者互不干扰,鱼与熊掌兼得。
Swift 对于初学者来说也很友好。它是第一个既满足工业标准又像脚本语言一样充满表现力和趣味的系统编程语言。它支持代码预览playgrounds这个革命性的特性可以允许程序员在不编译和运行应用程序的前提下运行 Swift 代码并实时查看结果。
Swift 通过采用现代编程模式来避免大量常见编程错误:
* 变量始终在使用前初始化。
* 检查数组索引超出范围的错误。
* 检查整数是否溢出。
* 可选值确保明确处理 `nil` 值。
* 内存被自动管理。
* 错误处理允许从意外故障控制恢复。
Swift 代码被编译和优化,以充分利用现代硬件。语法和标准库是基于指导原则设计的,编写代码的明显方式也应该是最好的。安全性和速度的结合使得 Swift 成为从 “Helloworld” 到整个操作系统的绝佳选择。
Swift 将强大的类型推理和模式匹配与现代轻巧的语法相结合,使复杂的想法能够以清晰简洁的方式表达。因此,代码不仅更容易编写,而且易于阅读和维护。
Swift 已经进行了多年,并且随着新特性和功能的不断发展。我们对 Swift 的目标是雄心勃勃的。我们迫不及待想看到你用它创建出的东西。

View File

@ -1,22 +0,0 @@
# 关于 SwiftAbout Swift
-----------------
> 1.0
> 翻译:[numbbbbb](https://github.com/numbbbbb)
> 校对:[yeahdongcn](https://github.com/yeahdongcn)
> 2.0
> 翻译+校对:[xtymichael](https://github.com/xtymichael)
Swift 是一种新的编程语言,用于编写 iOSOS X 和 watchOS应用程序。Swift 结合了 C 和 Objective-C 的优点并且不受 C 兼容性的限制。Swift 采用安全的编程模式并添加了很多新特性这将使编程更简单更灵活也更有趣。Swift 是基于成熟而且倍受喜爱的 Cocoa 和 Cocoa Touch 框架,它的降临将重新定义软件开发。
Swift 的开发从很久之前就开始了。为了给 Swift 打好基础苹果公司改进了编译器调试器和框架结构。我们使用自动引用计数Automatic Reference Counting, ARC来简化内存管理。我们在 Foundation 和 Cocoa 的基础上构建框架栈使其完全现代化和标准化。
Objective-C 本身支持块、集合语法和模块,所以框架可以轻松支持现代编程语言技术。正是得益于这些基础工作,我们现在才能发布这样一个用于未来苹果软件开发的新语言。
Objective-C 开发者对 Swift 并不会感到陌生。它采用了 Objective-C 的命名参数以及动态对象模型,可以无缝对接到现有的 Cocoa 框架,并且可以兼容 Objective-C 代码。在此基础之上Swift 还有许多新特性并且支持过程式编程和面向对象编程。
Swift 对于初学者来说也很友好。它是第一个既满足工业标准又像脚本语言一样充满表现力和趣味的脚本语言。它支持代码预览,这个革命性的特性可以允许程序员在不编译和运行应用程序的前提下运行 Swift 代码并实时查看结果。
Swift 将现代编程语言的精华和苹果工程师文化的智慧结合了起来。编译器对性能进行了优化编程语言对开发进行了优化两者互不干扰鱼与熊掌兼得。Swift 既可以用于开发 “hello, world” 这样的小程序,也可以用于开发一套完整的操作系统。所有的这些特性让 Swift 对于开发者和苹果来说都是一项值得的投资。
Swift 是编写 iOSOS X 和 watchOS应用的极佳手段并将伴随着新的特性和功能持续演进。我们对 Swift 充满信心,你还在等什么!

View File

@ -1,818 +0,0 @@
# Swift 初见A Swift Tour
---
> 1.0
> 翻译:[numbbbbb](https://github.com/numbbbbb)
> 校对:[shinyzhu](https://github.com/shinyzhu), [stanzhai](https://github.com/stanzhai)
> 2.0
> 翻译+校对:[xtymichael](https://github.com/xtymichael)
> 2.2
> 翻译:[175](https://github.com/Brian175)2016-04-09 校对:[SketchK](https://github.com/SketchK)2016-05-11
本页内容包括:
- [简单值Simple Values](#simple_values)
- [控制流Control Flow](#control_flow)
- [函数和闭包Functions and Closures](#functions_and_closures)
- [对象和类Objects and Classes](#objects_and_classes)
- [枚举和结构体Enumerations and Structures](#enumerations_and_structures)
- [协议和扩展Protocols and Extensions](#protocols_and_extensions)
- [错误处理Error Handling](#error_handling)
- [泛型Generics](#generics)
通常来说编程语言教程中的第一个程序应该在屏幕上打印“Hello, world”。在 Swift 中,可以用一行代码实现:
```swift
print("Hello, world!")
```
如果你写过 C 或者 Objective-C 代码,那你应该很熟悉这种形式——在 Swift 中,这行代码就是一个完整的程序。你不需要为了输入输出或者字符串处理导入一个单独的库。全局作用域中的代码会被自动当做程序的入口点,所以你也不需要`main()`函数。你同样不需要在每个语句结尾写上分号。
这个教程会通过一系列编程例子来让你对 Swift 有初步了解,如果你有什么不理解的地方也不用担心——任何本章介绍的内容都会在后面的章节中详细讲解。
> 注意:
> 在 Mac 上,下载 Playground 并双击文件在 Xcode 里打开:[https://developer.apple.com/go/?id=swift-tour](https://developer.apple.com/go/?id=swift-tour)
<a name="simple_values"></a>
## 简单值
使用`let`来声明常量,使用`var`来声明变量。一个常量的值,在编译的时候,并不需要有明确的值,但是你只能为它赋值一次。也就是说你可以用常量来表示这样一个值:你只需要决定一次,但是需要使用很多次。
```swift
var myVariable = 42
myVariable = 50
let myConstant = 42
```
常量或者变量的类型必须和你赋给它们的值一样。然而,你不用明确地声明类型,声明的同时赋值的话,编译器会自动推断类型。在上面的例子中,编译器推断出`myVariable`是一个整数integer因为它的初始值是整数。
如果初始值没有提供足够的信息(或者没有初始值),那你需要在变量后面声明类型,用冒号分割。
```swift
let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble: Double = 70
```
> 练习:
> 创建一个常量,显式指定类型为`Float`并指定初始值为4。
值永远不会被隐式转换为其他类型。如果你需要把一个值转换成其他类型,请显式转换。
```swift
let label = "The width is"
let width = 94
let widthLabel = label + String(width)
```
> 练习:
> 删除最后一行中的`String`,错误提示是什么?
有一种更简单的把值转换成字符串的方法:把值写到括号中,并且在括号之前写一个反斜杠。例如:
```swift
let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."
```
> 练习:
> 使用`\()`来把一个浮点计算转换成字符串,并加上某人的名字,和他打个招呼。
使用方括号`[]`来创建数组和字典并使用下标或者键key来访问元素。最后一个元素后面允许有个逗号。
```swift
var shoppingList = ["catfish", "water", "tulips", "blue paint"]
shoppingList[1] = "bottle of water"
var occupations = [
"Malcolm": "Captain",
"Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"
```
要创建一个空数组或者字典,使用初始化语法。
```swift
let emptyArray = [String]()
let emptyDictionary = [String: Float]()
```
如果类型信息可以被推断出来,你可以用`[]``[:]`来创建空数组和空字典——就像你声明变量或者给函数传参数的时候一样。
```swift
shoppingList = []
occupations = [:]
```
<a name="control_flow"></a>
## 控制流
使用`if``switch`来进行条件操作,使用`for-in``for``while``repeat-while`来进行循环。包裹条件和循环变量括号可以省略,但是语句体的大括号是必须的。
```swift
let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
if score > 50 {
teamScore += 3
} else {
teamScore += 1
}
}
print(teamScore)
```
`if`语句中,条件必须是一个布尔表达式——这意味着像`if score { ... }`这样的代码将报错,而不会隐形地与 0 做对比。
你可以一起使用`if``let`来处理值缺失的情况。这些值可由可选值来代表。一个可选的值是一个具体的值或者是`nil`以表示值缺失。在类型后面加一个问号来标记这个变量的值是可选的。
```swift
var optionalString: String? = "Hello"
print(optionalString == nil)
var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
greeting = "Hello, \(name)"
}
```
> 练习:
> 把`optionalName`改成`nil`greeting会是什么添加一个`else`语句,当`optionalName`是`nil`时给greeting赋一个不同的值。
如果变量的可选值是`nil`,条件会判断为`false`,大括号中的代码会被跳过。如果不是`nil`,会将值赋给`let`后面的常量,这样代码块中就可以使用这个值了。
另一种处理可选值的方法是通过使用 ?? 操作符来提供一个默认值。如果可选值缺失的话,可以使用默认值来代替。
```swift
let nickName: String? = nil
let fullName: String = "John Appleseed"
let informalGreeting = "Hi \(nickName ?? fullName)"
```
`switch`支持任意类型的数据以及各种比较操作——不仅仅是整数以及测试相等。
```swift
let vegetable = "red pepper"
switch vegetable {
case "celery":
print("Add some raisins and make ants on a log.")
case "cucumber", "watercress":
print("That would make a good tea sandwich.")
case let x where x.hasSuffix("pepper"):
print("Is it a spicy \(x)?")
default:
print("Everything tastes good in soup.")
}
```
> 练习:
> 删除`default`语句,看看会有什么错误?
注意`let`在上述例子的等式中是如何使用的,它将匹配等式的值赋给常量`x`
运行`switch`中匹配到的子句之后,程序会退出`switch`语句,并不会继续向下运行,所以不需要在每个子句结尾写`break`
你可以使用`for-in`来遍历字典,需要两个变量来表示每个键值对。字典是一个无序的集合,所以他们的键和值以任意顺序迭代结束。
```swift
let interestingNumbers = [
"Prime": [2, 3, 5, 7, 11, 13],
"Fibonacci": [1, 1, 2, 3, 5, 8],
"Square": [1, 4, 9, 16, 25],
]
var largest = 0
for (kind, numbers) in interestingNumbers {
for number in numbers {
if number > largest {
largest = number
}
}
}
print(largest)
```
> 练习:
> 添加另一个变量来记录最大数字的种类(kind),同时仍然记录这个最大数字的值。
使用`while`来重复运行一段代码直到不满足条件。循环条件也可以在结尾,保证能至少循环一次。
```swift
var n = 2
while n < 100 {
n = n * 2
}
print(n)
var m = 2
repeat {
m = m * 2
} while m < 100
print(m)
```
你可以在循环中使用`..<`来表示范围。
```swift
var total = 0
for i in 0..<4 {
total += i
}
print(total)
```
使用`..<`创建的范围不包含上界,如果想包含的话需要使用`...`
<a name="functions_and_closures"></a>
## 函数和闭包
使用`func`来声明一个函数,使用名字和参数来调用函数。使用`->`来指定函数返回值的类型。
```swift
func greet(name: String, day: String) -> String {
return "Hello \(name), today is \(day)."
}
greet("Bob", day: "Tuesday")
```
> 练习:
> 删除`day`参数,添加一个参数来表示今天吃了什么午饭。
使用元组来让一个函数返回多个值。该元组的元素可以用名称或数字来表示。
```swift
func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
var min = scores[0]
var max = scores[0]
var sum = 0
for score in scores {
if score > max {
max = score
} else if score < min {
min = score
}
sum += score
}
return (min, max, sum)
}
let statistics = calculateStatistics([5, 3, 100, 3, 9])
print(statistics.sum)
print(statistics.2)
```
函数可以带有可变个数的参数,这些参数在函数内表现为数组的形式:
```swift
func sumOf(numbers: Int...) -> Int {
var sum = 0
for number in numbers {
sum += number
}
return sum
}
sumOf()
sumOf(42, 597, 12)
```
> 练习:
> 写一个计算参数平均值的函数。
函数可以嵌套。被嵌套的函数可以访问外侧函数的变量,你可以使用嵌套函数来重构一个太长或者太复杂的函数。
```swift
func returnFifteen() -> Int {
var y = 10
func add() {
y += 5
}
add()
return y
}
returnFifteen()
```
函数是第一等类型,这意味着函数可以作为另一个函数的返回值。
```swift
func makeIncrementer() -> (Int -> Int) {
func addOne(number: Int) -> Int {
return 1 + number
}
return addOne
}
var increment = makeIncrementer()
increment(7)
```
函数也可以当做参数传入另一个函数。
```swift
func hasAnyMatches(list: [Int], condition: Int -> Bool) -> Bool {
for item in list {
if condition(item) {
return true
}
}
return false
}
func lessThanTen(number: Int) -> Bool {
return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(numbers, condition: lessThanTen)
```
函数实际上是一种特殊的闭包:它是一段能之后被调取的代码。闭包中的代码能访问闭包所建作用域中能得到的变量和函数,即使闭包是在一个不同的作用域被执行的 - 你已经在嵌套函数例子中所看到。你可以使用`{}`来创建一个匿名闭包。使用`in`将参数和返回值类型声明与闭包函数体进行分离。
```swift
numbers.map({
(number: Int) -> Int in
let result = 3 * number
return result
})
```
> 练习:
> 重写闭包对所有奇数返回0。
有很多种创建更简洁的闭包的方法。如果一个闭包的类型已知,比如作为一个回调函数,你可以忽略参数的类型和返回值。单个语句闭包会把它语句的值当做结果返回。
```swift
let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers)
```
你可以通过参数位置而不是参数名字来引用参数——这个方法在非常短的闭包中非常有用。当一个闭包作为最后一个参数传给一个函数的时候,它可以直接跟在括号后面。当一个闭包是传给函数的唯一参数,你可以完全忽略括号。
```swift
let sortedNumbers = numbers.sort { $0 > $1 }
print(sortedNumbers)
```
<a name="objects_and_classes"></a>
## 对象和类
使用`class`和类名来创建一个类。类中属性的声明和常量、变量声明一样,唯一的区别就是它们的上下文是类。同样,方法和函数声明也一样。
```swift
class Shape {
var numberOfSides = 0
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
```
> 练习:
> 使用`let`添加一个常量属性,再添加一个接收一个参数的方法。
要创建一个类的实例,在类名后面加上括号。使用点语法来访问实例的属性和方法。
```swift
var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()
```
这个版本的`Shape`类缺少了一些重要的东西:一个构造函数来初始化类实例。使用`init`来创建一个构造器。
```swift
class NamedShape {
var numberOfSides: Int = 0
var name: String
init(name: String) {
self.name = name
}
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
```
注意`self`被用来区别实例变量。当你创建实例的时候,像传入函数参数一样给类传入构造器的参数。每个属性都需要赋值——无论是通过声明(就像`numberOfSides`)还是通过构造器(就像`name`)。
如果你需要在删除对象之前进行一些清理工作,使用`deinit`创建一个析构函数。
子类的定义方法是在它们的类名后面加上父类的名字,用冒号分割。创建类的时候并不需要一个标准的根类,所以你可以忽略父类。
子类如果要重写父类的方法的话,需要用`override`标记——如果没有添加`override`就重写父类方法的话编译器会报错。编译器同样会检测`override`标记的方法是否确实在父类中。
```swift
class Square: NamedShape {
var sideLength: Double
init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 4
}
func area() -> Double {
return sideLength * sideLength
}
override func simpleDescription() -> String {
return "A square with sides of length \(sideLength)."
}
}
let test = Square(sideLength: 5.2, name: "my test square")
test.area()
test.simpleDescription()
```
> 练习:
> 创建`NamedShape`的另一个子类`Circle`,构造器接收两个参数,一个是半径一个是名称,在子类`Circle`中实现`area()`和`simpleDescription()`方法。
除了储存简单的属性之外,属性可以有 getter 和 setter 。
```swift
class EquilateralTriangle: NamedShape {
var sideLength: Double = 0.0
init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 3
}
var perimeter: Double {
get {
return 3.0 * sideLength
}
set {
sideLength = newValue / 3.0
}
}
override func simpleDescription() -> String {
return "An equilateral triagle with sides of length \(sideLength)."
}
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
print(triangle.perimeter)
triangle.perimeter = 9.9
print(triangle.sideLength)
```
`perimeter`的 setter 中,新值的名字是`newValue`。你可以在`set`之后显式的设置一个名字。
注意`EquilateralTriangle`类的构造器执行了三步:
1. 设置子类声明的属性值
2. 调用父类的构造器
3. 改变父类定义的属性值。其他的工作比如调用方法、getters和setters也可以在这个阶段完成。
如果你不需要计算属性,但是仍然需要在设置一个新值之前或者之后运行代码,使用`willSet``didSet`
比如,下面的类确保三角形的边长总是和正方形的边长相同。
```swift
class TriangleAndSquare {
var triangle: EquilateralTriangle {
willSet {
square.sideLength = newValue.sideLength
}
}
var square: Square {
willSet {
triangle.sideLength = newValue.sideLength
}
}
init(size: Double, name: String) {
square = Square(sideLength: size, name: name)
triangle = EquilateralTriangle(sideLength: size, name: name)
}
}
var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
print(triangleAndSquare.square.sideLength)
print(triangleAndSquare.triangle.sideLength)
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)
```
处理变量的可选值时,你可以在操作(比如方法、属性和子脚本)之前加`?`。如果`?`之前的值是`nil``?`后面的东西都会被忽略,并且整个表达式返回`nil`。否则,`?`之后的东西都会被运行。在这两种情况下,整个表达式的值也是一个可选值。
```swift
let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength = optionalSquare?.sideLength
```
<a name="enumerations_and_structure"></a>
## 枚举和结构体
使用`enum`来创建一个枚举。就像类和其他所有命名类型一样,枚举可以包含方法。
```swift
enum Rank: Int {
case Ace = 1
case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten
case Jack, Queen, King
func simpleDescription() -> String {
switch self {
case .Ace:
return "ace"
case .Jack:
return "jack"
case .Queen:
return "queen"
case .King:
return "king"
default:
return String(self.rawValue)
}
}
}
let ace = Rank.Ace
let aceRawValue = ace.rawValue
```
> 练习:
> 写一个函数,通过比较它们的原始值来比较两个`Rank`值。
默认情况下Swift 按照从 0 开始每次加 1 的方式为原始值进行赋值,不过你可以通过显式赋值进行改变。在上面的例子中,`Ace`被显式赋值为 1并且剩下的原始值会按照顺序赋值。你也可以使用字符串或者浮点数作为枚举的原始值。使用`rawValue`属性来访问一个枚举成员的原始值。
使用`init?(rawValue:)`初始化构造器在原始值和枚举值之间进行转换。
```swift
if let convertedRank = Rank(rawValue: 3) {
let threeDescription = convertedRank.simpleDescription()
}
```
枚举的成员值是实际值,并不是原始值的另一种表达方法。实际上,如果没有比较有意义的原始值,你就不需要提供原始值。
```swift
enum Suit {
case Spades, Hearts, Diamonds, Clubs
func simpleDescription() -> String {
switch self {
case .Spades:
return "spades"
case .Hearts:
return "hearts"
case .Diamonds:
return "diamonds"
case .Clubs:
return "clubs"
}
}
}
let hearts = Suit.Hearts
let heartsDescription = hearts.simpleDescription()
```
> 练习:
> 给`Suit`添加一个`color()`方法,对`spades`和`clubs`返回“black”对`hearts`和`diamonds`返回“red”。
注意,有两种方式可以引用`Hearts`成员:给`hearts`常量赋值时,枚举成员`Suit.Hearts`需要用全名来引用,因为常量没有显式指定类型。在`switch`里,枚举成员使用缩写`.Hearts`来引用,因为`self`的值已经知道是一个`suit`。已知变量类型的情况下你可以使用缩写。
使用`struct`来创建一个结构体。结构体和类有很多相同的地方,比如方法和构造器。它们之间最大的一个区别就是结构体是传值,类是传引用。
```swift
struct Card {
var rank: Rank
var suit: Suit
func simpleDescription() -> String {
return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
}
}
let threeOfSpades = Card(rank: .Three, suit: .Spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()
```
> 练习:
> 给`Card`添加一个方法,创建一副完整的扑克牌并把每张牌的 rank 和 suit 对应起来。
一个枚举成员的实例可以有实例值。相同枚举成员的实例可以有不同的值。创建实例的时候传入值即可。实例值和原始值是不同的:枚举成员的原始值对于所有实例都是相同的,而且你是在定义枚举的时候设置原始值。
例如,考虑从服务器获取日出和日落的时间。服务器会返回正常结果或者错误信息。
```swift
enum ServerResponse {
case Result(String, String)
case Failure(String)
}
let success = ServerResponse.Result("6:00 am", "8:09 pm")
let failure = ServerResponse.Failure("Out of cheese.")
switch success {
case let .Result(sunrise, sunset):
let serverResponse = "Sunrise is at \(sunrise) and sunset is at \(sunset)."
case let .Failure(message):
print("Failure... \(message)")
}
```
> 练习:
> 给`ServerResponse`和`switch`添加第三种情况。
注意日升和日落时间是如何从`ServerResponse`中提取到并与`switch``case`相匹配的。
<a name="protocols_and_extensions"></a>
## 协议和扩展
使用`protocol`来声明一个协议。
```swift
protocol ExampleProtocol {
var simpleDescription: String { get }
mutating func adjust()
}
```
类、枚举和结构体都可以实现协议。
```swift
class SimpleClass: ExampleProtocol {
var simpleDescription: String = "A very simple class."
var anotherProperty: Int = 69105
func adjust() {
simpleDescription += " Now 100% adjusted."
}
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription
struct SimpleStructure: ExampleProtocol {
var simpleDescription: String = "A simple structure"
mutating func adjust() {
simpleDescription += " (adjusted)"
}
}
var b = SimpleStructure()
b.adjust()
let bDescription = b.simpleDescription
```
> 练习:
> 写一个实现这个协议的枚举。
注意声明`SimpleStructure`时候`mutating`关键字用来标记一个会修改结构体的方法。`SimpleClass`的声明不需要标记任何方法,因为类中的方法通常可以修改类属性(类的性质)。
使用`extension`来为现有的类型添加功能,比如新的方法和计算属性。你可以使用扩展在别处修改定义,甚至是从外部库或者框架引入的一个类型,使得这个类型遵循某个协议。
```swift
extension Int: ExampleProtocol {
var simpleDescription: String {
return "The number \(self)"
}
mutating func adjust() {
self += 42
}
}
print(7.simpleDescription)
```
> 练习:
> 给`Double`类型写一个扩展,添加`absoluteValue`功能。
你可以像使用其他命名类型一样使用协议名——例如,创建一个有不同类型但是都实现一个协议的对象集合。当你处理类型是协议的值时,协议外定义的方法不可用。
```swift
let protocolValue: ExampleProtocol = a
print(protocolValue.simpleDescription)
// print(protocolValue.anotherProperty) // Uncomment to see the error
```
即使`protocolValue`变量运行时的类型是`simpleClass`,编译器会把它的类型当做`ExampleProtocol`。这表示你不能调用类在它实现的协议之外实现的方法或者属性。
<a name="error_handling"></a>
## 错误处理
使用采用`ErrorType`协议的类型来表示错误。
```swift
enum PrinterError: ErrorType {
case OutOfPaper
case NoToner
case OnFire
}
```
使用`throw`来抛出一个错误并使用`throws`来表示一个可以抛出错误的函数。如果在函数中抛出一个错误,这个函数会立刻返回并且调用该函数的代码会进行错误处理。
```swift
func sendToPrinter(printerName: String) throws -> String {
if printerName == "Never Has Toner" {
throw PrinterError.NoToner
}
return "Job sent"
}
```
有多种方式可以用来进行错误处理。一种方式是使用`do-catch`。在`do`代码块中,使用`try`来标记可以抛出错误的代码。在`catch`代码块中,除非你另外命名,否则错误会自动命名为`error`
```swift
do {
let printerResponse = try sendToPrinter("Bi Sheng")
print(printerResponse)
} catch {
print(error)
}
```
> 练习:
> 将 printer name 改为`"Never Has Toner"`使`sendToPrinter(_:)`函数抛出错误。
可以使用多个`catch`块来处理特定的错误。参照 switch 中的`case`风格来写`catch`
```swift
do {
let printerResponse = try sendToPrinter("Gutenberg")
print(printerResponse)
} catch PrinterError.OnFire {
print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
print("Printer error: \(printerError).")
} catch {
print(error)
}
```
> 练习:
> 在`do`代码块中添加抛出错误的代码。你需要抛出哪种错误来使第一个`catch`块进行接收?怎么使第二个和第三个`catch`进行接收呢?
另一种处理错误的方式使用`try?`将结果转换为可选的。如果函数抛出错误,该错误会被抛弃并且结果为`nil`。否则的话,结果会是一个包含函数返回值的可选值。
```swift
let printerSuccess = try? sendToPrinter("Mergenthaler")
let printerFailure = try? sendToPrinter("Never Has Toner")
```
使用`defer`代码块来表示在函数返回前,函数中最后执行的代码。无论函数是否会抛出错误,这段代码都将执行。使用`defer`,可以把函数调用之初就要执行的代码和函数调用结束时的扫尾代码写在一起,虽然这两者的执行时机截然不同。
```swift
var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]
func fridgeContains(itemName: String) -> Bool {
fridgeIsOpen = true
defer {
fridgeIsOpen = false
}
let result = fridgeContent.contains(itemName)
return result
}
fridgeContains("banana")
print(fridgeIsOpen)
```
<a name="generics"></a>
## 泛型
在尖括号里写一个名字来创建一个泛型函数或者类型。
```swift
func repeatItem<Item>(item: Item, numberOfTimes: Int) -> [Item] {
var result = [Item]()
for _ in 0..<numberOfTimes {
result.append(item)
}
return result
}
repeatItem("knock", numberOfTimes:4)
```
你也可以创建泛型函数、方法、类、枚举和结构体。
```swift
// Reimplement the Swift standard library's optional type
enum OptionalValue<Wrapped> {
case None
case Some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .None
possibleInteger = .Some(100)
```
在类型名后面使用`where`来指定对类型的需求,比如,限定类型实现某一个协议,限定两个类型是相同的,或者限定某个类必须有一个特定的父类。
```swift
func anyCommonElements <T: SequenceType, U: SequenceType where T.Generator.Element: Equatable, T.Generator.Element == U.Generator.Element> (lhs: T, _ rhs: U) -> Bool {
for lhsItem in lhs {
for rhsItem in rhs {
if lhsItem == rhsItem {
return true
}
}
}
return false
}
anyCommonElements([1, 2, 3], [3])
```
> 练习:
> 修改`anyCommonElements(_:_:)`函数来创建一个函数,返回一个数组,内容是两个序列的共有元素。
`<T: Equatable>``<T where T: Equatable>`是等价的。

View File

@ -0,0 +1,11 @@
# 版本兼容性
本书描述的是在 Xcode 11 中的默认 Swift 版本 Swift 5.1。你可以使用 Xcode11 来构建 Swift 5.1、Swift 4.2 或 Swift 4 写的项目。
当您使用 Xcode 11 构建 Swift 4 和 Swift 4.2 代码时,除了下面的功能仅支持 Swift 5.1,其他大多数功能都依然可用。
* 返回值是不透明类型的函数依赖 Swift 5.1 运行时。
* **try?** 表达式不会为已返回可选类型的代码引入额外的可选类型层级。
* 大数字的整型字面量初始化代码的类型将会被正确推导,例如 **UInt64(0xffff_ffff_ffff_ffff)** 将会被推导为整型类型而非溢出。
用 Swift 5.1 写的项目可以依赖用 Swift 4.2 或 Swift 4 写的项目反之亦然。这意味着如果你将一个大的项目分解成多个框架framework你可以每次一个框架地迁移 Swift 4 代码到 Swift 5.1。

View File

@ -0,0 +1,808 @@
# Swift 初见
通常来说编程语言教程中的第一个程序应该在屏幕上打印“Hello, world”。在 Swift 中,可以用一行代码实现:
```swift
print("Hello, world!")
```
如果你写过 C 或者 Objective-C 代码,那你应该很熟悉这种形式——在 Swift 中,这行代码就是一个完整的程序。你不需要为了输入输出或者字符串处理导入一个单独的库。全局作用域中的代码会被自动当做程序的入口点,所以你也不需要 `main()` 函数。你同样不需要在每个语句结尾写上分号。
这个教程会通过一系列编程例子来让你对 Swift 有初步了解,如果你有什么不理解的地方也不用担心——任何本章介绍的内容都会在后面的章节中详细讲解到。
> 注意
>
> 最好的体验是把这一章作为 Playground 文件在 Xcode 中打开。 Playgrounds 允许你可以编辑代码并立刻看到输出结果。
>
> [Download Playground](https://docs.swift.org/swift-book/GuidedTour/GuidedTour.playground.zip)
## 简单值 {#simple-values}
使用 `let` 来声明常量,使用 `var` 来声明变量。一个常量的值,在编译的时候,并不需要有明确的值,但是你只能为它赋值一次。这说明你可以用一个常量来命名一个值,一次赋值就即可在多个地方使用。
```swift
var myVariable = 42
myVariable = 50
let myConstant = 42
```
常量或者变量的类型必须和你赋给它们的值一样。然而,你不用明确地声明类型。当你通过一个值来声明变量和常量时,编译器会自动推断其类型。在上面的例子中,编译器推断出 `myVariable` 是一个整数类型,因为它的初始值是整数。
如果初始值没有提供足够的信息(或者没有初始值),那你需要在变量后面声明类型,用冒号分割。
```swift
let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble: Double = 70
```
> 练习
>
> 创建一个常量,显式指定类型为 `Float` 并指定初始值为 4。
值永远不会被隐式转换为其他类型。如果你需要把一个值转换成其他类型,请显式转换。
```swift
let label = "The width is"
let width = 94
let widthLabel = label + String(width)
```
> 练习
>
> 删除最后一行中的 `String`,错误提示是什么?
有一种更简单的把值转换成字符串的方法:把值写到括号中,并且在括号之前写一个反斜杠(\)。例如:
```swift
let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."
```
> 练习
>
> 使用 `\()` 来把一个浮点计算转换成字符串,并加上某人的名字,和他打个招呼。
使用三个双引号(`"""`)来包含多行字符串内容,字符串中的内容(包括引号、空格、换行符等)都会保留下来。举个例子:
```swift
let quotation = """
I said "I have \(apples) apples."
And then I said "I have \(apples + oranges) pieces of fruit."
"""
```
使用方括号 `[]` 来创建数组和字典并使用下标或者键key来访问元素。最后一个元素后面允许有个逗号。
```swift
var shoppingList = ["catfish", "water", "tulips", "blue paint"]
shoppingList[1] = "bottle of water"
var occupations = [
"Malcolm": "Captain",
"Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"
```
使用初始化语法来创建一个空数组或者空字典。
```swift
let emptyArray = [String]()
let emptyDictionary = [String: Float]()
```
如果类型信息可以被推断出来,你可以用 `[]``[:]` 来创建空数组和空字典——就像你声明变量或者给函数传参数的时候一样。
```swift
shoppingList = []
occupations = [:]
```
## 控制流 {#control-flow}
使用 `if``switch` 来进行条件操作,使用 `for-in``while``repeat-while` 来进行循环。包裹条件和循环变量的括号可以省略,但是语句体的大括号是必须的。
```swift
let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
if score > 50 {
teamScore += 3
} else {
teamScore += 1
}
}
print(teamScore)
```
`if` 语句中,条件必须是一个布尔表达式——这意味着像 `if score { ... }` 这样的代码将报错,而不会隐形地与 0 做对比。
你可以一起使用 `if``let` 一起来处理值缺失的情况。这些值可由可选值来代表。一个可选的值是一个具体的值或者是 `nil` 以表示值缺失。在类型后面加一个问号(`?`)来标记这个变量的值是可选的。
```swift
var optionalString: String? = "Hello"
print(optionalString == nil)
var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
greeting = "Hello, \(name)"
}
```
> 练习
>
> 把 `optionalName` 改成 `nil`greeting 会是什么?添加一个 `else` 语句,当 `optionalName` 是 `nil` 时给 greeting 赋一个不同的值。
如果变量的可选值是 `nil`,条件会判断为 `false`,大括号中的代码会被跳过。如果不是 `nil`,会将值解包并赋给 `let` 后面的常量,这样代码块中就可以使用这个值了。
另一种处理可选值的方法是通过使用 `??` 操作符来提供一个默认值。如果可选值缺失的话,可以使用默认值来代替。
```swift
let nickName: String? = nil
let fullName: String = "John Appleseed"
let informalGreeting = "Hi \(nickName ?? fullName)"
```
`switch` 支持任意类型的数据以及各种比较操作——不仅仅是整数以及测试相等。
```swift
let vegetable = "red pepper"
switch vegetable {
case "celery":
print("Add some raisins and make ants on a log.")
case "cucumber", "watercress":
print("That would make a good tea sandwich.")
case let x where x.hasSuffix("pepper"):
print("Is it a spicy \(x)?")
default:
print("Everything tastes good in soup.")
}
```
> 练习
>
> 删除 `default` 语句,看看会有什么错误?
注意 `let` 在上述例子的等式中是如何使用的,它将匹配等式的值赋给常量 `x`
运行 `switch` 中匹配到的 `case` 语句之后,程序会退出 `switch` 语句,并不会继续向下运行,所以不需要在每个子句结尾写 `break`
你可以使用 `for-in` 来遍历字典,需要一对儿变量来表示每个键值对。字典是一个无序的集合,所以他们的键和值以任意顺序迭代结束。
```swift
let interestingNumbers = [
"Prime": [2, 3, 5, 7, 11, 13],
"Fibonacci": [1, 1, 2, 3, 5, 8],
"Square": [1, 4, 9, 16, 25],
]
var largest = 0
for (kind, numbers) in interestingNumbers {
for number in numbers {
if number > largest {
largest = number
}
}
}
print(largest)
```
> 练习
>
> 添加另一个变量来记录最大数字的种类kind同时仍然记录这个最大数字的值。
使用 `while` 来重复运行一段代码直到条件改变。循环条件也可以在结尾,保证能至少循环一次。
```swift
var n = 2
while n < 100 {
n *= 2
}
print(n)
var m = 2
repeat {
m *= 2
} while m < 100
print(m)
```
你可以在循环中使用 `..<` 来表示下标范围。
```swift
var total = 0
for i in 0..<4 {
total += i
}
print(total)
```
使用 `..<` 创建的范围不包含上界,如果想包含的话需要使用 `...`
## 函数和闭包 {#functions-and-closures}
使用 `func` 来声明一个函数,使用名字和参数来调用函数。使用 `->` 来指定函数返回值的类型。
```swift
func greet(person: String, day: String) -> String {
return "Hello \(person), today is \(day)."
}
greet(person:"Bob", day: "Tuesday")
```
> 练习
>
> 删除 `day` 参数,添加一个参数来表示今天吃了什么午饭。
默认情况下,函数使用它们的参数名称作为它们参数的标签,在参数名称前可以自定义参数标签,或者使用 `_` 表示不使用参数标签。
```swift
func greet(_ person: String, on day: String) -> String {
return "Hello \(person), today is \(day)."
}
greet("John", on: "Wednesday")
```
使用元组来生成复合值,比如让一个函数返回多个值。该元组的元素可以用名称或数字来获取。
```swift
func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
var min = scores[0]
var max = scores[0]
var sum = 0
for score in scores {
if score > max {
max = score
} else if score < min {
min = score
}
sum += score
}
return (min, max, sum)
}
let statistics = calculateStatistics(scores:[5, 3, 100, 3, 9])
print(statistics.sum)
print(statistics.2)
```
函数可以嵌套。被嵌套的函数可以访问外侧函数的变量,你可以使用嵌套函数来重构一个太长或者太复杂的函数。
```swift
func returnFifteen() -> Int {
var y = 10
func add() {
y += 5
}
add()
return y
}
returnFifteen()
```
函数是第一等类型,这意味着函数可以作为另一个函数的返回值。
```swift
func makeIncrementer() -> ((Int) -> Int) {
func addOne(number: Int) -> Int {
return 1 + number
}
return addOne
}
var increment = makeIncrementer()
increment(7)
```
函数也可以当做参数传入另一个函数。
```swift
func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
for item in list {
if condition(item) {
return true
}
}
return false
}
func lessThanTen(number: Int) -> Bool {
return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(list: numbers, condition: lessThanTen)
```
函数实际上是一种特殊的闭包:它是一段能之后被调取的代码。闭包中的代码能访问闭包作用域中的变量和函数,即使闭包是在一个不同的作用域被执行的 - 你已经在嵌套函数的例子中看过了。你可以使用 `{}` 来创建一个匿名闭包。使用 `in` 将参数和返回值类型的声明与闭包函数体进行分离。
```swift
numbers.map({
(number: Int) -> Int in
let result = 3 * number
return result
})
```
> 练习
>
> 重写闭包,对所有奇数返回 0。
有很多种创建更简洁的闭包的方法。如果一个闭包的类型已知,比如作为一个代理的回调,你可以忽略参数,返回值,甚至两个都忽略。单个语句闭包会把它语句的值当做结果返回。
```swift
let mappedNumbers = numbers.map({ number in 3 * number })
print(mappedNumbers)
```
你可以通过参数位置而不是参数名字来引用参数——这个方法在非常短的闭包中非常有用。当一个闭包作为最后一个参数传给一个函数的时候,它可以直接跟在括号后面。当一个闭包是传给函数的唯一参数,你可以完全忽略括号。
```swift
let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)
```
## 对象和类 {#objects-and-classes}
使用 `class` 和类名来创建一个类。类中属性的声明和常量、变量声明一样,唯一的区别就是它们的上下文是类。同样,方法和函数声明也一样。
```swift
class Shape {
var numberOfSides = 0
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
```
> 练习
>
> 使用 `let` 添加一个常量属性,再添加一个接收一个参数的方法。
要创建一个类的实例,在类名后面加上括号。使用点语法来访问实例的属性和方法。
```swift
var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()
```
这个版本的 `Shape` 类缺少了一些重要的东西:一个构造函数来初始化类实例。使用 `init` 来创建一个构造器。
```swift
class NamedShape {
var numberOfSides: Int = 0
var name: String
init(name: String) {
self.name = name
}
func simpleDescription() -> String {
return "A shape with \(numberOfSides) sides."
}
}
```
注意 `self` 被用来区别实例变量 `name` 和构造器的参数 `name`。当你创建实例的时候,像传入函数参数一样给类传入构造器的参数。每个属性都需要赋值——无论是通过声明(就像 `numberOfSides`)还是通过构造器(就像 `name`)。
如果你需要在对象释放之前进行一些清理工作,使用 `deinit` 创建一个析构函数。
子类的定义方法是在它们的类名后面加上父类的名字,用冒号分割。创建类的时候并不需要一个标准的根类,所以你可以根据需要添加或者忽略父类。
子类如果要重写父类的方法的话,需要用 `override` 标记——如果没有添加 `override` 就重写父类方法的话编译器会报错。编译器同样会检测 `override` 标记的方法是否确实在父类中。
```swift
class Square: NamedShape {
var sideLength: Double
init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 4
}
func area() -> Double {
return sideLength * sideLength
}
override func simpleDescription() -> String {
return "A square with sides of length \(sideLength)."
}
}
let test = Square(sideLength: 5.2, name: "my test square")
test.area()
test.simpleDescription()
```
> 练习
>
> 创建 `NamedShape` 的另一个子类 `Circle`,构造器接收两个参数,一个是半径一个是名称,在子类 `Circle` 中实现 `area()` 和 `simpleDescription()` 方法。
除了储存简单的属性之外,属性可以有 getter 和 setter 。
```swift
class EquilateralTriangle: NamedShape {
var sideLength: Double = 0.0
init(sideLength: Double, name: String) {
self.sideLength = sideLength
super.init(name: name)
numberOfSides = 3
}
var perimeter: Double {
get {
return 3.0 * sideLength
}
set {
sideLength = newValue / 3.0
}
}
override func simpleDescription() -> String {
return "An equilateral triangle with sides of length \(sideLength)."
}
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
print(triangle.perimeter)
triangle.perimeter = 9.9
print(triangle.sideLength)
```
`perimeter` 的 setter 中,新值的名字是 `newValue`。你可以在 `set` 之后显式的设置一个名字。
注意 `EquilateralTriangle` 类的构造器执行了三步:
1. 设置子类声明的属性值
2. 调用父类的构造器
3. 改变父类定义的属性值。其他的工作比如调用方法、getters 和 setters 也可以在这个阶段完成。
如果你不需要计算属性,但是仍然需要在设置一个新值之前或者之后运行代码,使用 `willSet``didSet`。写入的代码会在属性值发生改变时调用,但不包含构造器中发生值改变的情况。比如,下面的类确保三角形的边长总是和正方形的边长相同。
```swift
class TriangleAndSquare {
var triangle: EquilateralTriangle {
willSet {
square.sideLength = newValue.sideLength
}
}
var square: Square {
willSet {
triangle.sideLength = newValue.sideLength
}
}
init(size: Double, name: String) {
square = Square(sideLength: size, name: name)
triangle = EquilateralTriangle(sideLength: size, name: name)
}
}
var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
print(triangleAndSquare.square.sideLength)
print(triangleAndSquare.triangle.sideLength)
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)
```
处理变量的可选值时,你可以在操作(比如方法、属性和子脚本)之前加 `?`。如果 `?` 之前的值是 `nil``?` 后面的东西都会被忽略,并且整个表达式返回 `nil`。否则,`?` 之后的东西都会被运行。在这两种情况下,整个表达式的值也是一个可选值。
```swift
let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength = optionalSquare?.sideLength
```
## 枚举和结构体 {#enumerations-and-structure}
使用 `enum` 来创建一个枚举。就像类和其他所有命名类型一样,枚举可以包含方法。
```swift
enum Rank: Int {
case ace = 1
case two, three, four, five, six, seven, eight, nine, ten
case jack, queen, king
func simpleDescription() -> String {
switch self {
case .ace:
return "ace"
case .jack:
return "jack"
case .queen:
return "queen"
case .king:
return "king"
default:
return String(self.rawValue)
}
}
}
let ace = Rank.ace
let aceRawValue = ace.rawValue
```
> 练习
>
> 写一个函数,通过比较它们的原始值来比较两个 `Rank` 值。
默认情况下Swift 按照从 0 开始每次加 1 的方式为原始值进行赋值,不过你可以通过显式赋值进行改变。在上面的例子中,`Ace` 被显式赋值为 1并且剩下的原始值会按照顺序赋值。你也可以使用字符串或者浮点数作为枚举的原始值。使用 `rawValue` 属性来访问一个枚举成员的原始值。
使用 `init?(rawValue:)` 初始化构造器来创建一个带有原始值的枚举成员。如果存在与原始值相应的枚举成员就返回该枚举成员,否则就返回 `nil`
```swift
if let convertedRank = Rank(rawValue: 3) {
let threeDescription = convertedRank.simpleDescription()
}
```
枚举的关联值是实际值,并不是原始值的另一种表达方法。实际上,如果没有比较有意义的原始值,你就不需要提供原始值。
```swift
enum Suit {
case spades, hearts, diamonds, clubs
func simpleDescription() -> String {
switch self {
case .spades:
return "spades"
case .hearts:
return "hearts"
case .diamonds:
return "diamonds"
case .clubs:
return "clubs"
}
}
}
let hearts = Suit.hearts
let heartsDescription = hearts.simpleDescription()
```
> 练习
>
> 给 `Suit` 添加一个 `color()` 方法,对 `spades` 和 `clubs` 返回 “black” ,对 `hearts` 和 `diamonds` 返回 “red” 。
注意在上面的例子中用了两种方式引用 `hearts` 枚举成员:给 `hearts` 常量赋值时,枚举成员 `Suit.hearts` 需要用全名来引用,因为常量没有显式指定类型。在 `switch` 里,枚举成员使用缩写 `.hearts` 来引用,因为 `self` 的值已经是一个 `suit` 类型,在已知变量类型的情况下可以使用缩写。
如果枚举成员的实例有原始值,那么这些值是在声明的时候就已经决定了,这意味着不同枚举实例的枚举成员总会有一个相同的原始值。当然我们也可以为枚举成员设定关联值,关联值是在创建实例时决定的。这意味着不同的枚举成员的关联值都可以不同。你可以把关联值想象成枚举成员的寄存属性。例如,考虑从服务器获取日出和日落的时间的情况。服务器会返回正常结果或者错误信息。
```swift
enum ServerResponse {
case result(String, String)
case failure(String)
}
let success = ServerResponse.result("6:00 am", "8:09 pm")
let failure = ServerResponse.failure("Out of cheese.")
switch success {
case let .result(sunrise, sunset):
print("Sunrise is at \(sunrise) and sunset is at \(sunset)")
case let .failure(message):
print("Failure... \(message)")
}
```
> 练习
>
> 给 `ServerResponse` 和 switch 添加第三种情况。
注意日升和日落时间是如何从 `ServerResponse` 中提取到并与 `switch``case` 相匹配的。
使用 `struct` 来创建一个结构体。结构体和类有很多相同的地方,包括方法和构造器。它们之间最大的一个区别就是结构体是传值,类是传引用。
```swift
struct Card {
var rank: Rank
var suit: Suit
func simpleDescription() -> String {
return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
}
}
let threeOfSpades = Card(rank: .three, suit: .spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()
```
> 练习
>
> 给 `Card` 添加一个方法,创建一副完整的扑克牌并把每张牌的 rank 和 suit 对应起来。
## 协议和扩展 {#protocols-and-extensions}
使用 `protocol` 来声明一个协议。
```swift
protocol ExampleProtocol {
var simpleDescription: String { get }
mutating func adjust()
}
```
类、枚举和结构体都可以遵循协议。
```swift
class SimpleClass: ExampleProtocol {
var simpleDescription: String = "A very simple class."
var anotherProperty: Int = 69105
func adjust() {
simpleDescription += " Now 100% adjusted."
}
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription
struct SimpleStructure: ExampleProtocol {
var simpleDescription: String = "A simple structure"
mutating func adjust() {
simpleDescription += " (adjusted)"
}
}
var b = SimpleStructure()
b.adjust()
let bDescription = b.simpleDescription
```
> 练习
>
> 写一个实现这个协议的枚举。
注意声明 `SimpleStructure` 时候 `mutating` 关键字用来标记一个会修改结构体的方法。`SimpleClass` 的声明不需要标记任何方法,因为类中的方法通常可以修改类属性(类的性质)。
使用 `extension` 来为现有的类型添加功能,比如新的方法和计算属性。你可以使用扩展让某个在别处声明的类型来遵守某个协议,这同样适用于从外部库或者框架引入的类型。
```swift
extension Int: ExampleProtocol {
var simpleDescription: String {
return "The number \(self)"
}
mutating func adjust() {
self += 42
}
}
print(7.simpleDescription)
```
> 练习
>
> 给 `Double` 类型写一个扩展,添加 `absoluteValue` 属性。
你可以像使用其他命名类型一样使用协议名——例如,创建一个有不同类型但是都实现一个协议的对象集合。当你处理类型是协议的值时,协议外定义的方法不可用。
```swift
let protocolValue: ExampleProtocol = a
print(protocolValue.simpleDescription)
// print(protocolValue.anotherProperty) // 去掉注释可以看到错误
```
即使 `protocolValue` 变量运行时的类型是 `simpleClass` ,编译器还是会把它的类型当做 `ExampleProtocol`。这表示你不能调用在协议之外的方法或者属性。
## 错误处理 {#error-handling}
使用采用 `Error` 协议的类型来表示错误。
```swift
enum PrinterError: Error {
case outOfPaper
case noToner
case onFire
}
```
使用 `throw` 来抛出一个错误和使用 `throws` 来表示一个可以抛出错误的函数。如果在函数中抛出一个错误,这个函数会立刻返回并且调用该函数的代码会进行错误处理。
```swift
func send(job: Int, toPrinter printerName: String) throws -> String {
if printerName == "Never Has Toner" {
throw PrinterError.noToner
}
return "Job sent"
}
```
有多种方式可以用来进行错误处理。一种方式是使用 `do-catch` 。在 `do` 代码块中,使用 `try` 来标记可以抛出错误的代码。在 `catch` 代码块中,除非你另外命名,否则错误会自动命名为 `error`
```swift
do {
let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
print(printerResponse)
} catch {
print(error)
}
```
> 练习
>
> 将 printer name 改为 `"Never Has Toner"` 使 `send(job:toPrinter:)` 函数抛出错误。
可以使用多个 `catch` 块来处理特定的错误。参照 switch 中的 `case` 风格来写 `catch`
```swift
do {
let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
print(printerResponse)
} catch PrinterError.onFire {
print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
print("Printer error: \(printerError).")
} catch {
print(error)
}
```
> 练习
>
> 在 `do` 代码块中添加抛出错误的代码。你需要抛出哪种错误来使第一个 `catch` 块进行接收?怎么使第二个和第三个 `catch` 进行接收呢?
另一种处理错误的方式使用 `try?` 将结果转换为可选的。如果函数抛出错误,该错误会被抛弃并且结果为 `nil`。否则,结果会是一个包含函数返回值的可选值。
```swift
let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")
```
使用 `defer` 代码块来表示在函数返回前,函数中最后执行的代码。无论函数是否会抛出错误,这段代码都将执行。使用 `defer`,可以把函数调用之初就要执行的代码和函数调用结束时的扫尾代码写在一起,虽然这两者的执行时机截然不同。
```swift
var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]
func fridgeContains(_ food: String) -> Bool {
fridgeIsOpen = true
defer {
fridgeIsOpen = false
}
let result = fridgeContent.contains(food)
return result
}
fridgeContains("banana")
print(fridgeIsOpen)
```
## 泛型 {#generics}
在尖括号里写一个名字来创建一个泛型函数或者类型。
```swift
func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {
var result = [Item]()
for _ in 0..<numberOfTimes {
result.append(item)
}
return result
}
makeArray(repeating: "knock", numberOfTimes: 4)
```
你也可以创建泛型函数、方法、类、枚举和结构体。
```swift
// 重新实现 Swift 标准库中的可选类型
enum OptionalValue<Wrapped> {
case none
case some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .none
possibleInteger = .some(100)
```
在类型名后面使用 `where` 来指定对类型的一系列需求,比如,限定类型实现某一个协议,限定两个类型是相同的,或者限定某个类必须有一个特定的父类。
```swift
func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool
where T.Iterator.Element: Equatable, T.Iterator.Element == U.Iterator.Element {
for lhsItem in lhs {
for rhsItem in rhs {
if lhsItem == rhsItem {
return true
}
}
}
return false
}
anyCommonElements([1, 2, 3], [3])
```
> 练习
>
> 修改 `anyCommonElements(_:_:)` 函数来创建一个函数,返回一个数组,内容是两个序列的共有元素。
`<T: Equatable>``<T> ... where T: Equatable>` 的写法是等价的。

View File

@ -1,575 +0,0 @@
# Swift 文档修订历史
---
> 1.0
> 翻译:[成都老码团队翻译组-Arya](http://weibo.com/littlekok/)
> 校对:[成都老码团队翻译组-Oberyn](http://weibo.com/u/5241713117)
[changkun](http://changkun.us/about/)
>
> 1.1
> 翻译:[成都老码团队翻译组-Arya](http://weibo.com/littlekok/)
> 校对:[成都老码团队翻译组-Oberyn](http://weibo.com/u/5241713117)
[changkun](http://changkun.us/about/)
>
> 1.2
> 翻译:[成都老码团队翻译组-Arya](http://weibo.com/littlekok/)
> 校对:[成都老码团队翻译组-Oberyn](http://weibo.com/u/5241713117)
[changkun](http://changkun.us/about/)
>
> 2.0
> 翻译+校对:[changkun](http://changkun.us/about/)
>
> 2.1
> 翻译+校对:[changkun](http://changkun.us/about/)
>
> 2.2
> 翻译+校对:[changkun](http://changkun.us/about/)
本页面根据 [Document Revision History](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/RevisionHistory.html) 进行适配更新。
本页内容包括:
- [Swift 2.2 更新](#swift_2_2)
- [Swift 2.1 更新](#swift_2_1)
- [Swift 2.0 更新](#swift_2_0)
- [Swift 1.2 更新](#swift_1_2)
- [Swift 1.1 更新](#swift_1_1)
- [Swift 1.0 更新](#swift_1_0)
<a name="swift_2_2"></a>
### Swift 2.2 更新
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
<thead>
<tr>
<th scope="col" width="100">发布日期</th>
<th scope="col">语法变更记录</th>
</tr>
</thead>
<tbody>
<tr>
<td scope="row">2016-03-21</td>
<td><ul class="list-bullet">
<li>
更新至 Swift 2.2。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID539">编译配置语句</a>一节中关于如何根据 Swift 版本进行条件编译。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID400">显示成员表达式</a>一节中关于如何区分只有参数名不同的方法和构造器的信息。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID547">选择器表达式</a>一节中针对 Objective-C 选择器的 <code>#selector</code> 语法。
</li>
<li>
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Generics.html#//apple_ref/doc/uid/TP40014097-CH26-ID189">关联类型</a><a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID374">协议关联类型</a>声明,使用 <code>associatedtype</code> 关键词修改了对于关联类型的讨论。
</li>
<li>
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-ID224">可失败构造器</a>一节中关于当构造器在实例完全初始化之前返回 <code>nil</code>的相关信息。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/BasicOperators.html#//apple_ref/doc/uid/TP40014097-CH6-ID70">比较运算符</a>一节中关于比较元组的信息。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-ID413">关键字和标点符号</a>一节中关于使用关键字作为外部参数名的信息。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-ID413">声明特性</a>一节中关于<code>@objc</code>特性的讨论,并指出枚举(Enumeration)和枚举用例(Enumaration Case)。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-ID418">操作符</a>一节中对于自定义运算符的讨论包含了<code>.</code>
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID531">重新抛出错误的函数和方法</a>一节中关于重新抛出错误函数不能直接抛出错误的笔记。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html#//apple_ref/doc/uid/TP40014097-CH14-ID262">属性观察器</a>一节中关于当作为 in-out 参数传递属性时,属性观察器的调用行为。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/GuidedTour.html#//apple_ref/doc/uid/TP40014097-CH2-ID1">Swift 初见</a>一节中关于错误处理的内容。
</li>
<li>
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html#//apple_ref/doc/uid/TP40014097-CH20-ID53">弱引用</a>一节中的图片用以更清楚的展示重新分配过程。
</li>
<li>
删除了 C 语言风格的 <code>for</code> 循环,<code>++</code> 前缀和后缀运算符,以及<code>--</code> 前缀和后缀运算符。
</li>
<li>
删除了对变量函数参数和柯里化函数的特殊语法的讨论。
</li>
</ul>
</td>
</tr>
</tbody>
</table>
<a name="swift_2_1"></a>
### Swift 2.1 更新
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
<thead>
<tr>
<th scope="col" width="100">发布日期</th>
<th scope="col">语法变更记录</th>
</tr>
</thead>
<tbody>
<tr>
<td scope="row">2015-10-20</td>
<td><ul class="list-bullet">
<li>
更新至 Swift 2.1。
</li>
<li>
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-ID292">字符串插值(String Interprolation)</a><a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-ID417">字符串字面量(String Literals)</a>小节,现在字符串插值可包含字符串字面量。
</li>
<li>
增加了在<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-ID546">非逃逸闭包(Nonescaping Closures)</a>一节中关于 <code>@noescape</code> 属性的相关内容。
</li>
<li>
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Attributes.html#//apple_ref/doc/uid/TP40014097-CH35-ID348">声明特性(Declaration Attributes)</a><a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID539">编译配置语句(Build Configuration Statement)</a>小节中与 tvOS 相关的信息。
</li>
<li>
增加了 <a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID545">In-Out 参数(In-Out Parameters)</a>小节中与 in-out 参数行为相关的信息。
</li>
<li>
增加了在<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID544">捕获列表(Capture Lists)</a>一节中关于指定闭包捕获列表被捕获时捕获值的相关内容。
</li>
<li>
更新了通过<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/OptionalChaining.html#//apple_ref/doc/uid/TP40014097-CH21-ID248">可选链式调用访问属性(Accessing Properties Through Optional Chaining)</a>一节,阐明了如何通过可选链式调用进行赋值。
</li>
<li>
改进了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-ID543">自闭包(Autoclosure)</a>一节中对自闭包的讨论。
</li>
<li>
<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/GuidedTour.html#//apple_ref/doc/uid/TP40014097-CH2-ID1">Swift 初见(A Swift Tour)</a>一节中更新了一个使用<code>??</code>操作符的例子。
</li>
</ul>
</td>
</tr>
</tbody>
</table>
<a name="swift_2_0"></a>
### Swift 2.0 更新
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
<thead>
<tr>
<th scope="col" width="100">发布日期</th>
<th scope="col">语法变更记录</th>
</tr>
</thead>
<tbody>
<tr>
<td scope="row">2015-09-16</td>
<td><ul class="list-bullet">
<li>
更新至 Swift 2.0。
</li>
<li>
增加了对于错误处理相关内容,包括 <a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html#//apple_ref/doc/uid/TP40014097-CH42-ID508">错误处理</a>一章、<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID533">Do 语句</a><a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID518">Throw 语句</a><a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID532">Defer 语句</a>以及<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID516">try 运算符</a> 的多个小节。
</li>
<li>
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html#//apple_ref/doc/uid/TP40014097-CH42-ID509">表示并抛出错误</a>一节,现在所有类型均可遵循 <code>ErrorType</code> 协议。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html#//apple_ref/doc/uid/TP40014097-CH42-ID542">将错误转换成可选值</a>一节 <code>try?</code> 关键字的相关信息。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html#//apple_ref/doc/uid/TP40014097-CH12-ID145">枚举</a>一章的<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html#//apple_ref/doc/uid/TP40014097-CH12-ID536">递归枚举</a>一节和<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID351">声明</a>一章的<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID365">任意类型用例的枚举</a>一节中关于递归枚举的内容。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html#//apple_ref/doc/uid/TP40014097-CH9-ID120">控制流</a>一章中a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html#//apple_ref/doc/uid/TP40014097-CH9-ID523">检查 API 可用性</a>一节和<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID428">语句</a>一章中<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID522">可用性条件</a>一节中关于 API 可用性检查的内容。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html#//apple_ref/doc/uid/TP40014097-CH9-ID120">控制流</a>一章的<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html#//apple_ref/doc/uid/TP40014097-CH9-ID525">早期退出</a>一节和<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID428">语句</a>一章的<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID524">guard语句</a>中关于新 <code>guard</code> 语句的内容。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID267">协议</a>一章中<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID521">协议扩展</a>一节中关于协议扩展的内容。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AccessControl.html#//apple_ref/doc/uid/TP40014097-CH41-ID3">访问控制</a>一章中<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AccessControl.html#//apple_ref/doc/uid/TP40014097-CH41-ID519">单元测试 target 的访问级别</a>一节中关于单元测试的访问控制相关的内容。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Patterns.html#//apple_ref/doc/uid/TP40014097-CH36-ID419">模式</a>一章中<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Patterns.html#//apple_ref/doc/uid/TP40014097-CH36-ID520">可选模式</a>一节中的新可选模式。
</li>
<li>
更新了 <a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html#//apple_ref/doc/uid/TP40014097-CH9-ID126">Repeat-While</a> 一节中关于<code>repeat-while</code>循环的信息。
</li>
<li>
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-ID285">字符串和字符</a>一章,现在<code>String</code>在 Swift 标准库中不再遵循<code>CollectionType</code>协议。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-ID314">打印常量和变量</a>一节中关于新 Swift 标准库中关于 <code>print(_:separator:terminator)</code> 的信息。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html#//apple_ref/doc/uid/TP40014097-CH12-ID145">枚举</a>一章中<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html#//apple_ref/doc/uid/TP40014097-CH12-ID535">原始值的隐式赋值</a>一节和<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID351">声明</a>一章的<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID366">包含原始值类型的枚举</a>一节中关于包含<code>String</code>原始值的枚举用例的行为。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-ID543">自闭包</a>一节中关于<code>@autoclosure</code>特性的相关信息,包括它的<code>@autoclosure(escaping)</code>形式。
</li>
<li>
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Attributes.html#//apple_ref/doc/uid/TP40014097-CH35-ID348">声明特性</a>一节中关于<code>@avaliable</code><code>warn_unused_result</code>特性的相关内容。
</li>
<li>
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Attributes.html#//apple_ref/doc/uid/TP40014097-CH35-ID350">类型特性</a>一节中关于<code>@convention</code>特性的相关信息。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-ID333">可选绑定</a>一节中关于使用<code>where</code>子句进行多可选绑定的内容。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-ID292">字符串字面量</a>一节中关于在编译时使用 <code>+</code> 运算符凭借字符串字面量的相关信息。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Types.html#//apple_ref/doc/uid/TP40014097-CH31-ID455">元类型</a>一节中关于元类型值的比较和使用它们通过构造器表达式构造实例。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-ID336">断言调试</a>一节中关于用户定义断言是被警用的相关内容。
</li>
<li>
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Attributes.html#//apple_ref/doc/uid/TP40014097-CH35-ID348">声明特性</a>一节中,对<code>@NSManaged</code>特性的讨论,现在这个特性可以被应用到一个确定实例方法。
</li>
<li>
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Functions.html#//apple_ref/doc/uid/TP40014097-CH10-ID171">可变参数</a>一节,现在可变参数可以声明在函数参数列表的任意位置中。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-ID229">重写可失败构造器</a>一节中,关于非可失败构造器相当于一个可失败构造器通过父类构造器的结果进行强制拆包的相关内容。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID365">任意类型用例的枚举</a>一节中关于枚举用例作为函数的内容。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID399">构造器表达式</a>一节中关于显式引用一个构造器的内容。
</li>
<li>
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID538">编译控制语句</a>一节中关于编译信息以及行控制语句的相关信息。
</li>
<li>
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Types.html#//apple_ref/doc/uid/TP40014097-CH31-ID455">元类型</a>一节中关于如何从元类型值中构造类实例。
</li>
<li>
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html#//apple_ref/doc/uid/TP40014097-CH20-ID53">弱引用</a>一节中关于弱引用作为缓存的显存的不足。
</li>
<li>
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-ID292">类型特性</a>一节,提到了存储型特性其实是懒加载。
</li>
<li>
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html#//apple_ref/doc/uid/TP40014097-CH14-ID264">捕获类型</a>一节,阐明了变量和常量在闭包中如何被捕获。
</li>
<li>
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Attributes.html#//apple_ref/doc/uid/TP40014097-CH35-ID348">声明特性</a>一节用以描述如何在类中使用<code>@objc</code>关键字。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html#//apple_ref/doc/uid/TP40014097-CH42-ID512">错误处理</a>一节中关于执行<code>throw</code>语句的性能的讨论。增加了 Do 语句一节中相似的信息。
</li>
<li>
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID533">类型特性</a>一节中关于类、结构体和枚举的存储型和计算型特性的信息。
</li>
<li>
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID441">Break 语句</a>一节中关于带标签的 break 语句。
</li>
<li>
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html#//apple_ref/doc/uid/TP40014097-CH14-ID262">属性观察器</a>一节,阐明了<code>willSet</code><code>didSet</code>观察器的行为。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AccessControl.html#//apple_ref/doc/uid/TP40014097-CH41-ID5">访问级</a>一节中关于<code>private</code>作用域访问的相关信息。
</li>
<li>
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html#//apple_ref/doc/uid/TP40014097-CH20-ID53">弱引用</a>一节中关于若应用在垃圾回收系统和 ARC 之间的区别。
</li>
<li>
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-ID295">字符串字面量中特殊字符</a>一节中对 Unicode 标量更精确的定义。
</li>
</ul>
</td>
</tr>
</tbody>
</table>
<a name="swift_1_2"></a>
### Swift 1.2 更新
<a name="xcode6_3"></a>
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
<thead>
<tr>
<th scope="col" width="100">发布日期</th>
<th scope="col">语法变更记录</th>
</tr>
</thead>
<tbody>
<tr>
<td scope="row">2015-4-8</td>
<td><ul class="list-bullet">
<li>
更新至 Swift 1.2。
</li>
<li>
Swift现在自身提供了一个<code>Set</code>集合类型,更多信息请看<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html#//apple_ref/doc/uid/TP40014097-CH8-ID484">集合</a>
</li>
<li>
<code>@autoclosure</code>现在是一个参数声明的属性,而不是参数类型的属性。这里还有一个新的参数声明属性<code>@noescape</code>。更多信息,请看<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Attributes.html#//apple_ref/doc/uid/TP40014097-CH35-ID348">属性声明</a>
</li>
<li>
对于类型属性和方法现在可以使用<code>static</code>关键字作为声明描述符,更多信息,请看<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID483">类型变量属性</a>
</li>
<li>
Swift现在包含一个<code>as?</code><code>as!</code>的向下可失败类型转换运算符。更多信息,请看<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID283">协议遵循性检查</a>
</li>
<li>
增加了一个新的指导章节,它是关于<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-ID495">字符串索引</a>
</li>
<li>
<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-ID37">溢出运算符</a>中移除了溢出除运算符(&/)和求余溢出运算符(&%)。
</li>
<li>
更新了常量和常量属性在声明和构造时的规则,更多信息,请看<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID355">常量声明</a>
</li>
<li>
更新了字符串字面量中Unicode标量集的定义请看<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-ID295">字符串字面量中的特殊字符</a>
</li>
<li>
更新了<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/BasicOperators.html#//apple_ref/doc/uid/TP40014097-CH6-ID73">区间运算符</a>章节来提示当半开区间运算符含有相同的起止索引时,其区间为空。
</li>
<li>
更新了<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-ID104">闭包引用类型</a>章节来澄清对于变量的捕获规则
</li>
<li>
更新了<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-ID38">值溢出</a>章节来澄清有符号整数和无符号整数的溢出行为
</li>
<li>
更新了<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID369">协议声明</a>章节来澄清协议声明时的作用域和成员
</li>
<li>
更新了<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html#//apple_ref/doc/uid/TP40014097-CH20-ID58">捕获列表</a>章节来澄清对于闭包捕获列表中的弱引用和无主引用的使用语法。
</li>
<li>
更新了<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-ID418">运算符</a>章节来明确指明一些例子来说明自定义运算符所支持的特性如数学运算符各种符号Unicode符号块等
</li>
<li>
在函数作用域中的常量声明时可以不被初始化,它必须在第一次使用前被赋值。更多的信息,请看<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID355">常量声明</a>
</li>
<li>
在构造器中,常量属性有且仅能被赋值一次。更多信息,请看<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-ID212">在构造过程中给常量属性赋值</a>
</li>
<li>
多个可选绑定现在可以在<code>if</code>语句后面以逗号分隔的赋值列表的方式出现,更多信息,请看<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-ID333">可选绑定</a>
</li>
<li>
一个<a link="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID405">可选链表达式</a>必须出现在后缀表达式中
</li>
<li>
协议类型转换不再局限于<code>@obj</code>修饰的协议了
</li>
<li>
在运行时可能会失败的类型转换可以使用<code>as?</code><code>as!</code>运算符,而确保不会失败的类型转换现在使用<code>as</code>运算符。更多信息,请看<a link="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID388">类型转换运算符</a>
</li>
</ul>
</td>
</tr>
</tbody>
</table>
<a name="swift_1_1"></a>
### Swift 1.1 更新
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
<thead>
<tr>
<th scope="col" width="100">发布日期</th>
<th scope="col">语法变更记录</th>
</tr>
</thead>
<tbody>
<tr>
<td scope="row">2014-10-16</td>
<td><ul class="list-bullet">
<li>
更新至 Swift 1.1。
</li>
<li>
增加了关于<a href="http://developer.apple.com/library/etc/redirect/xcode/devtools/419f35/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html">失败构造器(Failable Initializers)</a>的完整章节。
</li>
<li>
增加了协议中关于失败构造器要求的描述。
</li>
<li>
常量和变量的 <code>Any</code> 类型现可以包含函数实例。更新了关于 <code>Any</code> 相关的示例来展示如何在 <code>switch</code> 语句中如何检查并转换到一个函数类型。
</li>
<li>
带有原始值的枚举类型增加了一个<code>rawValue</code>属性替代<code>toRaw()</code>方法,同时使用了一个以<code>rawValue</code>为参数的失败构造器来替代<code>fromRaw()</code>方法。更多的信息,请看<a href="http://developer.apple.com/library/etc/redirect/xcode/devtools/419f35/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html">原始值(Raw Values)</a><a href="http://developer.apple.com/library/etc/redirect/xcode/devtools/419f35/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html">带原始值的枚举类型(Enumerations with Cases of a Raw-Value Type)</a>部分。
</li>
<li>
自定义运算符现在可以包含`?`字符,更新的<a href="http://developer.apple.com/library/etc/redirect/xcode/devtools/419f35/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html">运算符(Operators)</a>章节描述了改进后的规则,并且从<a href="http://developer.apple.com/library/etc/redirect/xcode/devtools/419f35/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html">自定义运算符(Custom Operators)</a>章节删除了重复的运算符有效字符集合
</li>
</ul>
</td>
</tr>
</tbody>
</table>
<a name="swift_1_0"></a>
### Swift 1.0 更新
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
<thead>
<tr>
<th scope="col" width="100">发布日期</th>
<th scope="col">语法变更记录</th>
</tr>
</thead>
<tbody>
<tr>
<td scope="row">2014-08-18</td>
<td><ul class="list-bullet">
<li>
发布新的文档用以详述 Swift 1.0苹果公司针对iOS和OS X应用的全新开发语言。
</li>
<li>
在章节协议中,增加新的小节:<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-XID_397">对构造器的规定Initializer Requirements</a>
</li>
<li>
在章节协议中,增加新的小节:<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-XID_409">类专属协议class-only protocols</a>
</li>
<li>
<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-XID_494">断言(assertions)</a>现在可以使用字符串内插语法,并删除了文档中有冲突的注释
</li>
<li>
更新了<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-XID_428">连接字符串和字符Concatenating Strings and Characters</a>小节来说明一个事实,那就是字符串和字符不能再用<code>+</code>号运算符或者复合加法运算符<code>+=</code>相互连接,这两种运算符现在只能用于字符串之间相连。请使用<code>String</code>类型的<code>append</code>方法在一个字符串的尾部增加单个字符
</li>
<li>
<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Attributes.html#//apple_ref/doc/uid/TP40014097-CH35-XID_516">声明特性Declaration Attributes</a>章节增加了关于<code>availability</code>特性的一些信息
</li>
<li>
<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-XID_478">可选类型Optionals</a> 若有值时,不再隐式的转换为 <code>true</code>,同样,若无值时,也不再隐式的转换为 <code>false</code>, 这是为了避免在判别 optional <code>Bool</code> 的值时产生困惑。 替代的方案是,用<code>==</code><code>!=</code> 运算符显式地去判断Optinal是否是 <code>nil</code>,以确认其是否包含值。
</li>
<li>
Swift新增了一个 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/BasicOperators.html#//apple_ref/doc/uid/TP40014097-CH6-XID_124" data-id="//apple_ref/doc/uid/TP40014097-CH6-XID_124">Nil合并运算符Nil Coalescing Operator</a> (<code>a ?? b</code>), 该表达式中如果Optional <code>a</code>的值存在则取得它并返回若Optional <code>a</code><code>nil</code>,则返回默认值 <code>b</code>
</li>
<li>
更新和扩展 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-XID_434">字符串的比较Comparing Strings</a> 章节,用以反映和展示'字符串和字符的比较',以及'前缀prefix/后缀(postfix)比较'都开始基于扩展字符集(extended grapheme clusters)规范的等价比较.
</li>
<li>
现在,你可以通过 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/OptionalChaining.html#//apple_ref/doc/uid/TP40014097-CH21-XID_356">可选链Optional Chaining</a>给属性设值将其赋给一个下标脚注subscript; 或调用一个变异mutating方法或运算符。对此章节——<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/OptionalChaining.html#//apple_ref/doc/uid/TP40014097-CH21-XID_364">通过可选链访问属性Accessing Properties Through Optional Chaining</a>的内容已经被相应的更新。而章节——<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/OptionalChaining.html#//apple_ref/doc/uid/TP40014097-CH21-XID_361">通过可选链调用方法Calling Methods Through Optional Chaining</a>中,关于检查方法调用是否成功的例子,已被扩展为展示如何检查一个属性是否被设值成功。
</li>
<li>
在章节可选链中,增加一个新的小节 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/OptionalChaining.html#//apple_ref/doc/uid/TP40014097-CH21-XID_364">访问可选类型的下标脚注Accessing Subscripts of Optional Type</a>
</li>
<li>
更新章节 <a href="../chapter2/04_Collection_Types.md#访问和修改数组" data-id="访问和修改数组">访问和修改数组(Accessing and Modifying an Array)</a> 以标示:从该版本起,不能再通过<code>+=</code> 运算符给一个数组添加一个新的项。. 对应的替代方案是, 使<code>append</code> 方法, 或者通过<code>+=</code>运算符来添加一个<b>只有一个项的数组</b>single-item Array.</li>
<li>
添加了一个提示:在 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/BasicOperators.html#//apple_ref/doc/uid/TP40014097-CH6-XID_126">范围运算符Range Operators</a>中,比如, <code>a...b</code><code>a..&lt;b</code> ,起始值<code>a</code>不能大于结束值<code>b</code>.
</li>
<li>
重写了<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Inheritance.html#//apple_ref/doc/uid/TP40014097-CH17-XID_293">继承Inheritance</a> 这一章删除了本章中关于构造器重写的介绍性报道转而将更多的注意力放到新增的部分——子类的新功能以及如何通过重写overrides修改已有的功能。另外小节 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Inheritance.html#//apple_ref/doc/uid/TP40014097-CH17-XID_301">重写属性的Getters和SettersOverriding Property Getters and Setters</a> 中的例子已经被替换为展示如何重写一个 <code>description</code> 属性. (而关于如何在子类的构造器中修改继承属性的默认值的例子,已经被移到 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Inheritance.html#//apple_ref/doc/uid/TP40014097-CH17-XID_293">构造过程Initialization</a> 这一章.)
</li>
<li>
更新了 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-XID_331">构造器的继承与重写Initializer Inheritance and Overriding</a> 小节以标示: 重写一个特定的构造器必须使用 <code>override</code> 修饰符.
</li>
<li>
更新 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-XID_339"> Required构造器Required Initializers</a> 小节以标示:<code>required</code> 修饰符现在需要出现在所有子类的required构造器的声明中, 而required构造器的实现现在可以仅从父类自动继承。
</li>
<li>
中置Infix<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-XID_80">运算符函数Operator Functions</a> 不再需要<code>@infix</code> 属性.
</li>
<li>
<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/RevisionHistory.html#//apple_ref/doc/uid/TP40014097-CH40-XID_1631">前置和后置运算符(Prefix and Postfix Operators)</a><code>@prefix</code><code>@postfix</code> 属性,已变更为 <code>prefix</code><code>postfix</code> 声明修饰符declaration modifiers.
</li>
<li>
增加一条注解当Prefix和postfix运算符被作用于同一个操作数时关于<a href="AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-XID_81" data-id="//apple_ref/doc/uid/TP40014097-CH27-XID_81">前置和后置运算符(Prefix and Postfix Operators)</a>的顺序(postfix运算符会先被执行)
</li>
<li>
在运算符函数Operator functions <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-XID_82" data-id="//apple_ref/doc/uid/TP40014097-CH27-XID_82">组合赋值运算符Compound Assignment Operators</a> 不再使用 <code>@assignment</code> 属性来定义函数.
</li>
<li>
在这个版本中,在定义<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-XID_85">自定义操作符Custom Operators</a> 时,<b>修饰符Modifiers的出现顺序发生变化</b>。比如, 现在,你该编写 <code>prefix operator</code> 而不是 <code>operator prefix</code>.
</li>
<li>
增加信息:关于<code>dynamic</code> 声明修饰符declaration modifier于章节 <a href="Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-XID_705" data-id="//apple_ref/doc/uid/TP40014097-CH34-XID_705">声明修饰符Declaration Modifiers</a>.
</li>
<li>
增加信息:<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-XID_886">字面量Literals</a> 的类型推导type inference
</li>
<li>
为章节<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-XID_597">Curried Functions</a>添加了更多的信息.
</li>
<li>
加入新的章节 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AccessControl.html#//apple_ref/doc/uid/TP40014097-CH41-XID_29">权限控制Access Control</a>.
</li>
<li>
更新了章节 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-XID_413">字符串和字符Strings and Characters</a> 用以表明在Swift中<code>Character</code> 类型现在代表的是扩展字符集(extended grapheme cluster)中的一个Unicode为此新增了小节 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-XID_431">Extended Grapheme Clusters</a> 。同时,为小节 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-XID_428">Unicode标量Unicode Scalars</a><a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-XID_434">字符串比较Comparing Strings</a>增加了更多内容.
</li>
<li>
更新章节<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-XID_856">字符串字面量String Literals</a>在一个字符串中Unicode标量Unicode scalars<code>\u{n}</code>的形式来表示, <code>n</code> 是一个最大可以有8位的16进制数hexadecimal digits
</li>
<li>
<code>NSString</code> <code>length</code> 属性已被映射到Swift的内建 <code>String</code>类型。(注意,这两属性的类型是<code>utf16Count</code>,而非 <code>utf16count</code>.
</li>
<li>
Swift的内建 <code>String</code> 类型不再拥有 <code>uppercaseString</code><code>lowercaseString</code> 属性.其对应部分在章节 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-XID_413">字符串和字符Strings and Characters</a>已经被删除, 并且各种对应的代码用例也已被更新.
</li>
<li>
加入新的章节 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-XID_315">没有外部名的构造器参数Initializer Parameters Without External Names</a>.
</li>
<li>
加入新的章节 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-XID_339"> Required构造器Required Initializers</a>.
</li>
<li>
加入新的章节 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Functions.html#//apple_ref/doc/uid/TP40014097-CH10-XID_252">可选元祖(函数)返回类型 Optional Tuple Return Types</a>.
</li>
<li>
更新章节 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-XID_453">类型标注Type Annotations</a> 多个相关变量可以用“类型标注”type annotaion在同一行中声明为同一类型。
</li>
<li>
<code>@optional</code>, <code>@lazy</code>, <code>@final</code>, <code>@required</code> 等关键字被更新为 <code>optional</code>, <code>lazy</code>, <code>final</code>, <code>required</code> <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-XID_705">参见声明修饰符Declaration Modifiers</a>.
</li>
<li>
更新整本书 —— 引用 <code>..&lt;</code> 作为<a href="BasicOperators.html#//apple_ref/doc/uid/TP40014097-CH6-XID_128" data-id="//apple_ref/doc/uid/TP40014097-CH6-XID_128">区间运算符Half-Open Range Operator</a> (取代原先的<code>..</code> ).
</li>
<li>
更新了小节 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html#//apple_ref/doc/uid/TP40014097-CH8-XID_185">读取和修改字典Accessing and Modifying a Dictionary</a> <code>Dictionary</code> 现在早呢更加了一个 Boolean型的属性 <code>isEmpty</code>
</li>
<li>
解释了哪些字符(集)可被用来定义<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-XID_85">自定义操作符 Custom Operators</a>
</li>
<li>
<code>nil</code> 和布尔运算中的 <code>true</code><code>false</code> 现在被定义为字面量<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-XID_886">Literals</a>.
</li>
<li>
Swift 中的数组 <code>Array</code> 类型从现在起具备了完整的值语义。具体信息被更新到 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html#//apple_ref/doc/uid/TP40014097-CH8-XID_170">集合的可变性Mutability of Collections</a><a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html#//apple_ref/doc/uid/TP40014097-CH8-XID_172">数组Arrays</a> 两小节,以反映这个新的变化. 此外,还解释了如何 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ClassesAndStructures.html#//apple_ref/doc/uid/TP40014097-CH13-XID_150">给Strings, Arrays和Dictionaries进行赋值和拷贝 Assignment and Copy Behavior for Strings, Arrays, and Dictionaries</a>.
</li>
<li>
<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html#//apple_ref/doc/uid/TP40014097-CH8-XID_173">数组类型速记语法Array Type Shorthand Syntax</a><code>SomeType[]</code>.更新为<code>[SomeType]</code>
</li>
<li>
加入新的小节:<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html#//apple_ref/doc/uid/TP40014097-CH8-XID_182">字典类型的速记语法Dictionary Type Shorthand Syntax)</a>. <code>[KeyType: ValueType]</code>.
</li>
<li>
加入新的小节:<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html#//apple_ref/doc/uid/TP40014097-CH8-XID_189">字典键类型的哈希值Hash Values for Dictionary Key Types)</a>.
</li>
<li>
例子 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-XID_154">闭包表达式 (Closure Expressions)</a> 中使用新的全局函数 <code>sorted</code> 取代原先的全局函数 <code>sort</code> 去展示如何返回一个全新的数组.
</li>
<li>
更新关于 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-XID_320">结构体逐一成员构造器 Memberwise Initializers for Structure Types</a> 的描述:即使结构体的成员<b>没有默认值</b>,逐一成员构造器也可以自动获得。
</li>
<li>
<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/BasicOperators.html#//apple_ref/doc/uid/TP40014097-CH6-XID_128">区间运算符Half-Open Range Operator</a><code>..</code>更新到<code>..&lt;</code>
</li>
<li>
添加一个例子 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Generics.html#//apple_ref/doc/uid/TP40014097-CH26-XID_285">扩展一个泛型Extending a Generic Type</a>
</li>
</ul>
</td>
</tr>
</tbody>
</table>

View File

@ -0,0 +1,267 @@
# Swift 文档修订历史
### 2019-06-03
* 更新至 Swift 5.1。
* 在 [不透明类型](../chapter2/27_Opaque_Types.md) 篇章中新增了有关函数返回值遵循指定协议,而不需要提供指定返回类型的内容。
* 新增 [隐式返回的函数](../chapter2/06_Functions.md#functions-with-an-implicit-return) 和 [简化 Getter 声明](../chapter2/10_Properties.md#shorthand-getter-declaration) 章节,其中包含函数省略 `return` 的内容。
* 在 [类型下标](../chapter2/12_Subscripts.md#type-subscripts) 章节中新增有关在类型中使用下标的内容。
* 更新 [结构体的逐一成员构造器](../chapter2/14_Initialization.md#memberwise-initializers-for-structure-types) 章节,现在逐一成员构造器支持在属性有默认值时省略形参。
* 在 [动态查找成员](../chapter3/07_Attributes.md#dynamicmemberlookup) 章节中新增了有关在运行时用 key path 查找动态成员的内容。
* 更新 [自身类型](../chapter3/03_Types.md#self-type-h) 章节,现在 `Self` 可以指向当前类,结构体或者枚举声明时的类型。
### 2019-03-25
* 更新至 Swift 5。
* 新增 [拓展字符串分隔符](../chapter2/03_Strings_And_Characters.md#extended-string-delimiters) 章节。更新 [字符串字面量](../chapter3/03_Lexical_Structure.md#string-literal) 章节,拓展有关字符串分隔符的内容。
* 新增 [动态调用](../chapter3/07_Attributes.md#dynamiccallable) 章节,其中包含使用 `dynamicCallable` 属性动态调用实例作为函数的内容。
* 新增 [unknown](../chapter3/07_Attributes.md#unknown) 和 [未来枚举匹配](../chapter3/05_Statements.md#future-case2) 章节,其中包含了使用 `unknown` 来处理未来枚举可能发生改变的情形。
* 在 [Key-Path 表达式](../chapter3/04_Expressions.md#key-path-expression) 章节新增了有关标示 key path (\\.self) 的内容。
* 在 [可选编译块](../chapter3/05_Statements.md#Conditional-Compilation-Block) 章节新增了有关小于比较符 `<` 的内容。
### 2018-09-17
* 更新至 Swift 4.2。
* 在 [遍历枚举情形](../chapter2/08_Enumerations.md#iterating-over-enumeration-cases) 章节新增了有关访问所有枚举情形的内容。
* 在 [编译诊断](../chapter3/05_Statements.md#compile-time-diagnostic-statement) 章节新增了有关 `#error``#warning` 的内容。
* 在 [属性声明](../chapter3/07_Attributes.md#Ideclaration-attributes) 章节中新增了有关 `inlinable``usableFromInline` 属性的内容。
* 在 [属性声明](../chapter3/07_Attributes.md#Ideclaration-attributes) 章节中新增了有关 `requires_stored_property_inits``warn_unqualified_access` 属性的内容。
* 在 [可选编译块](../chapter3/05_Statements.md#Conditional-Compilation-Block) 章节新增了有关如何根据 Swift 编译器版本对代码进行对应编译处理的内容。
* 在 [字面量语法](../chapter3/04_Expressions.md#literal-expression) 章节新增了有关 `#dsohandle` 的内容。
### 2018-03-29
* 更新至 Swift 4.1。
* 在 [等价运算符](../chapter2/26_Advanced_Operators.md#equivalence-operators) 章节新增了有关等价运算符的合成实现的内容。
* 在 [声明](../chapter3/06_Declarations.md) 篇章中 [申明拓展](../chapter3/06_Declarations.md#extension-declaration) 章节和 [协议](../chapter2/21_Protocols.md) 篇章中 [有条件地遵循协议](../chapter2/21_Protocols.md#Conditionally-Conforming-to-a-Protocol) 章节新增了有关协议有条件遵循的内容。
* 在 [关联类型约束中使用协议](../chapter2/22_Generics.md#using-a-protocol-in-its-associated-types-constraints) 章节中新增了有关递归协议约束的内容。
* 在 [条件编译块](../chapter3/05_Statements.md#Conditional-Compilation-Block) 章节中新增了有关 `canImport()``targetEnvironment()` 平台条件的内容。
### 2017-12-04
* 更新至 Swift 4.0.3。
* 更新 [Key-Path 表达式](../chapter3/04_Expressions.md#key-path-expression) 章节,现在 key path 支持下标子路径。
### 2017-09-19
* 更新至 Swift 4.0。
* 在 [内存安全](../chapter2/24_MemorySafety.md) 章节新增了有关内存互斥访问的内容。
* 新增 [带有泛型 Where 子句联类型](../chapter2/22_Generics.md#associated-types-with-a-generic-where-clause) 章节,现在可以使用泛型 `where` 子句约束关联类型。
* 在 [字符串和字符](../chapter2/03_Strings_And_Characters.md) 篇章中 [字面量](../chapter2/03_Strings_And_Characters.md#string-literals) 章节以及 [词法结构](../chapter3/02_Lexical_Structure.md) 篇章的 [字符串字面量](../chapter3/02_Lexical_Structure.md#string-literal) 章节中新增了有关多行字符串字面量的内容。
* 更新 [声明属性](../chapter3/07_Attributes.md#Ideclaration-attributes) 中 `objc` 属性的讨论,现在该属性会在更少的位置被推断出来。
* 新增 [范型下标](../chapter2/22_Generics.md#generic-subscripts) 章节,现在下标也支持范型特性了。
* 更新 [协议](../chapter2/21_Protocols.md) 篇章中 [协议组合](../chapter2/21_Protocols.md#protocol-composition) 章节和 [类型](../chapter3/03_Types.md) 篇章中 [协议组合类型](../chapter3/03_Types.md#protocol-composition-type-h) 章节的讨论,现在协议组合类型支持进行父类约束了。
* 更新 [拓展声明](../chapter3/06_Declarations.md#extension-declaration) 中有关协议扩展的讨论,现在它们不支持 `final` 特性了。
* 在 [断言和前置条件](../chapter2/01_TheBasics.md#assertions-and-preconditions) 章节中新增了部分前置条件和致命错误的内容。
### 2017-03-27
* 更新至 Swift 3.1。
* 新增 [范型 Where 子句扩展](../chapter2/22_Generics.md#extensions-with-a-generic-where-clause) 章节,包含需要的扩展内容。
* 在 [For-In 循环](../chapter2/05_Control_Flow.md#for-in-loops) 章节中新增了区间迭代的例子。
* 在 [到可失败构造器](http://typora-app/chapter2/14_Initialization.md#failable-initializers) 章节中新增了可失败数值转换的例子。
* 在 [声明特性](../chapter3/07_Attributes.md#Ideclaration-attributes) 章节中新增了有关使用 Swift 语言版本的 `available` 特性的内容 。
* 更新 [函数类型](../chapter3/03_Types.md#function-type-h) 章节中的讨论,注意在写函数类型时不允许使用参数标签。
* 更新 [条件编译块](../chapter3/05_Statements.md#Conditional-Compilation-Block) 章节中的 Swift 语言版本号的讨论,现在可以使用可选的补丁版本号。
* 更新 [函数类型](../chapter3/03_Types.md#function-type-h) 章节的讨论,现在 Swift 区分了采用多参数的函数和采用元组类型的单个参数的函数。
* 在 [表达式](../chapter3/04_Expressions.md) 篇章中删除了动态表达式的章节,现在 `type(of:)` 是 Swift 标准库函数。
### 2016-10-27
* 更新至 Swift 3.0.1。
* 更新 [自动引用计数](../chapter2/23_Automatic_Reference_Counting.md) 章节中有关 weak 和 unowned 引用的讨论。
* 在 [声明标识符](../chapter3/06_Declarations.md#declaration-modifiers) 章节中新增了有关新的标识符 `unowned``unowend(safe)``unowned(unsafe)` 的内容。
* 在 [Any 和 AnyObject 的类型转换](../chapter2/18_Type_Casting.md#type-casting-for-any-and-anyobject) 章节中新增了一处说明,有关使用类型 `Any` 作为可选值。
* 更新 [表达式](../chapter3/04_Expressions.md) 章节,把括号表达式和元组表达式的描述分开。
### 2016-09-13
* 更新至 Swift 3.0。
* 更新 [函数](../chapter2/06_Functions.md) 篇章和 [函数声明](../chapter3/06_Declarations.md#function-declaration) 章节中有关函数的讨论,所有函数参数默认都有函数标签。
* 更新 [高级操作符](../chapter2/26_Advanced_Operators.md) 篇章中有关操作符的讨论,现在你可以作为类型函数来实现,替代之前的全局函数实现方式。
* 在 [访问控制](../chapter2/25_Access_Control.md) 章节中新增有关对新的访问级别描述符 `open``fileprivate` 的内容。
* 更新 [函数声明](../chapter3/06_Declarations.md#function-declaration) 章节中有关 `inout` 的讨论,注意它现在出现在参数类型的前面,而不是在参数名称的前面。
* 更新 [逃逸闭包](../chapter2/07_Closures.md#escaping-closures) 和 [自动闭包](../chapter2/07_Closures.md#autoclosures) 章节还有 [属性](../chapter3/07_Attributes.md) 篇章中有关 `@noescape``@autoclosure` 的讨论,现在他们是类型属性,而不是定义属性。
* 在 [高级操作符](../chapter2/26_Advanced_Operators.md) 篇章中 [自定义中缀操作符的优先级](./chapter2/26_Advanced_Operators.md#precedence-and-associativity-for-custom-infix-operators) 章节和 [定义](../chapter3/06_Declarations.md) 篇章中 [优先级组声明](../chapter3/06_Declarations.md#precedence-group-declaration-modifiers) 章节中新增了有关操作符优先级组的内容。
* 更新一些讨论,使用 macOS 替换掉 OS X Error 替换掉 ErrorProtocol。更新一些协议名称比如使用 ExpressibleByStringLiteral 替换掉 StringLiteralConvertible。
* 更新 [泛型](../chapter2/22_Generics.md) 篇章中 [泛型 Where 语句](../chapter2/22_Generics.md#extensions-with-a-generic-where-clause) 章节和 [泛型形参和实参](../chapter3/09_Generic_Parameters_And_Arguments.md) 篇章的讨论,现在泛型的 where 语句写在一个声明的最后。
* 更新 [逃逸闭包](../chapter2/07_Closures.md#escaping-closures) 章节中的讨论,现在闭包默认为非逃逸的。
* 更新 [基础部分](../chapter2/01_TheBasics.md) 篇章中 [可选绑定](../chapter2/01_TheBasics.md#optional-binding) 章节和 [语句](../chapter3/05_Statements.md) 篇章中 [While 语句](../chapter3/05_Statements.md#while-statement) 章节中的讨论,现在 if`while``guard` 语句使用逗号分隔条件列表,不需要使用 `where` 语句。
* 在 [控制流](../chapter2/05_Control_Flow.md) 篇章中 [Switch](../chapter2/05_Control_Flow.md#switch) 章节和 [语句](../chapter3/05_Statements.md) 篇章中 [Switch 语句](../chapter3/05_Statements.md#switch-statement) 章节中新增了 switch cases 可以使用多模式的内容。
* 更新 [函数类型](../chapter3/03_Types.md#function-type-h) 章节有关现在函数参数标签不包含在函数类型中的讨论。
* 更新 [协议](../chapter2/21_Protocols.md) 篇章中 [协议组合](../chapter2/21_Protocols.md#protocol-composition) 章节和 [类型](../chapter3/03_Types.md) 篇章中 [协议组合类型](../chapter3/03_Types.md#protocol-composition-type-h) 章节中有关使用新的 Protocol1 & Protocol2 语法的内容。
* 更新动态类型表达式章节中使用新的 `type(of:)` 表达式的讨论。
* 更新 [行控制表达式](../chapter3/05_Statements.md#line-control-statement) 章节中使用 `#sourceLocation(file:line:)` 表达式的讨论。
* 更新 [永不返回函数](../chapter3/06_Declarations.md#functions-that-never-return) 章节中使用 新的 `Never` 类型的讨论。
* 在 [字面量表达式](../chapter3/04_Expressions.md#literal-expression) 章节中新增了有关 `playground` 字面量的内容。
* 更新 [In-Out 参数](../chapter3/06_Declarations.md#in-out_parameters) 章节,标明只有非逃逸闭包能捕获 `in-out` 参数。
* 更新 [默认参数值](../chapter2/06_Functions.md#default-parameter-values) 章节,现在默认参数不能在调用时候重新排序。
* 更新 [属性](../chapter3/07_Attributes.md) 篇章中有关属性参数使用分号的说明。
* 在 [重新抛出函数和方法](../chapter3/06_Declarations.md#rethrowing-functions-and-methods) 章节中新增了有关在 catch 代码块中抛出错误的重新抛出函数的内容。
* 在 [Selector 表达式](../chapter3/04_Expressions.md#selector-expression7) 章节中新增了中有关访问 Objective-C 中 Selector 的 getter 和 setter 的内容。
* 在 [类型别名声明](../chapter3/06_Declarations.md#type-alias-declaration) 章节中中新增了有关泛型类型别名和在协议内使用类型别名的内容。
* 更新 [函数类型](../chapter3/03_Types.md#function-type-h) 章节中有关函数类型的讨论,标明函数类型作为参数类型必须使用括号包裹。
* 更新 [属性](../chapter3/07_Attributes.md) 篇章,标明 `@IBAction``@IBOutlet``@NSManaged` 隐式含有 `@objc` 属性。
* 在 [声明属性](../chapter3/07_Attributes.md#Ideclaration-attributes) 章节中新增了有关 `@GKInspectable` 的内容。
* 更新 [可选协议要求](../chapter2/21_Protocols.md#optional-protocol-requirements) 章节中有关只能在与 `Objective-C` 交互的代码中才能使用可选协议要求的内容。
* 删除 [函数声明](../chapter3/06_Declarations.md#function-declaration) 章节中有关显式使用 `let` 关键字作为函数参数的内容。
* 删除 [语句](../chapter3/05_Statements.md) 章节中有关 `Boolean` 协议的内容, 现在这个协议已经被 Swift 标准库删除。
* 更正 [声明属性](../chapter3/07_Attributes.md#Ideclaration-attributes) 章节中有关 `@NSApplicationMain` 协议的内容。
### 2016-03-21
* 更新至 Swift 2.2。
* 在 [编译配置语句](../chapter3/05_Statements.md#Conditional-Compilation-Block) 章节新增了中有关如何根据 Swift 版本进行条件编译。
* 在 [显示成员表达式](../chapter3/04_Expressions.md#explicit-member-expression) 章节中新增了有关如何区分只有参数名不同的方法和构造器的内容。
* 在 [选择器表达式](../chapter3/04_Expressions.md#selector-expression7) 章节中新增了了针对 Objective-C 选择器的 `#selector` 语法。
* 更新 [关联类型](../chapter2/22_Generics.md#associated-types) 和 [协议关联类型声明](../chapter3/06_Declarations.md#protocol_associated_type_declaration) 章节中有关使用 `associatedtype` 关键词修饰关联类型的讨论。
* 更新 [可失败构造器](../chapter2/14_Initialization.md#failable-initializers) 章节中有关当构造器在实例完全初始化之前返回 `nil` 的相关内容。
* 在 [比较运算符](../chapter2/BasicOperators.md#comparison-operators) 章节中新增了比较元组的内容。
* 在 [关键字和标点符号](../chapter3/02_Lexical_Structure.md#keywords-and-punctuation) 章节中新增了使用关键字作为外部参数名的内容。
* 更新 [声明特性](../chapter3/07_Attributes.md#Ideclaration-attributes) 章节中有关 `@objc` 特性的讨论,并指出枚举和枚举用例。
* 更新 [操作符](../chapter3/02_Lexical_Structure.md#operator) 章节中对于自定义运算符的包含了 `.` 的讨论。
* 在 [重新抛出错误的函数和方法](../chapter3/06_Declarations.md#rethrowing-functions-and-methods) 章节中新增了一处说明,重新抛出错误函数不能直接抛出错误。
* 在 [属性观察器](../chapter2/10_Properties.md#property-observers) 章节中新增了一处说明,当作为 in-out 参数传递属性时,属性观察器的调用行为。
* 在 [Swift 初见](./03_a_swift_tour.md) 篇章中新增了错误处理的章节。
* 更新 [弱引用](../chapter2/23_Automatic_Reference_Counting.md#weak-references) 章节中的图片用以更清楚的展示重新分配过程。
* 删除 C 语言风格的 `for` 循环,`++` 前缀和后缀运算符,以及 `--` 前缀和后缀运算符。
* 删除对变量函数参数和柯里化函数的特殊语法的讨论。
### 2015-10-20
* 更新至 Swift 2.1。
* 更新 [字符串插值](../chapter2/03_Strings_And_Characters.md#string-interpolation) 和 [字符串字面量](../chapter3/02_Lexical_Structure.md#string-literal) 章节,现在字符串插值可包含字符串字面量。
* 在 [逃逸闭包](../chapter2/07_Closures.md#escaping-closures) 章节中新增了有关 `@noescape` 属性的相关内容。
* 更新 [声明特性](../chapter3/07_Attributes.md#Ideclaration-attributes) 和 [编译配置语句](../chapter3/05_Statements.md#Conditional-Compilation-Block) 章节中与 tvOS 相关的内容。
* 在 [In-Out 参数](../chapter3/06_Declarations.md#in-out_parameters) 章节中新增了与 in-out 参数行为相关的内容。
* 在 [捕获列表](../chapter3/04_Expressions.md#capture-lists) 章节新增了有关指定闭包捕获列表被捕获时捕获值的相关内容。
* 更新 [可选链式调用访问属性](../chapter2/16_Optional_Chaining.md#accessing-properties-through-optional-chaining) 章节,阐明了如何通过可选链式调用进行赋值。
* 改进 [自动闭包](../chapter2/07_Closures.md#autoclosures) 章节中对自闭包的讨论。
* 在 [Swift 初见](./03_a_swift_tour.md) 篇章中新增了一个使用 `??` 操作符的例子。
### 2015-09-16
* 更新至 Swift 2.0。
* 在 [错误处理](../chapter2/17_Error_Handling.md) 篇章中新增了有关错误处理的相关内容,包括 [Do 语句](../chapter3/05_Statements.md#do-statement)、 [Throw 语句](../chapter3/05_Statements.md#throw-statement)、 [Defer 语句](../chapter3/05_Statements.md##defer-statements) 以及 [try 运算符](../chapter3/04_Expressions.md#try-operator)。
* 更新 [错误表示和抛出](../chapter2/17_Error_Handling.md#representing-and-throwing-errors) 章节,现在所有类型都可以遵循 `ErrorType` 协议了。
* 在 [将错误装换成可选值](../chapter2/17_Error_Handling.md#converting_errors_to_optional_values) 篇章增加了 `try?` 关键字相关内容。
* 在 [枚举](../chapter2/08_Enumerations.md) 篇章的 [递归枚举](../chapter2/08_Enumerations.md#recursive-enumerations) 章节以及以及 [声明](../chapter3/06_Declarations.md) 篇章的 [任意类型用例的枚举](../chapter3/06_Declarations.md#enumerations-with-cases-of-any-type) 章节中新增了递归枚举相关内容。
* 在 [控制流](../chapter2/05_Control_Flow.md) 篇章的 [API 可用性检查](../chapter2/05_Control_Flow.md#checking-api-availability) 章节和 [语句](../chapter3/05_Statements.md) 篇章的 [可用性条件](../chapter3/05_Statements.md#availability-condition) 章节中新增了有关 API 可用性检查相关的内容。
* 在 [控制流](../chapter2/05_Control_Flow.md) 篇章的 [尽早退出](../chapter2/05_Control_Flow.md#early-exit) 章节和 [语句](../chapter3/05_Statements.md) 篇章的 [Guard 语句](../chapter3/05_Statements.md#guard-statement) 章节新增了与 `guard` 语句相关的内容。
* 在 [协议](../chapter2/21_Protocols.md) 篇章中 [协议扩展](../chapter2/21_Protocols.md#protocol-extensions) 章节中新增了有关协议扩展的内容。
* 在 [访问控制](../chapter2/25_Access_Control.md) 篇章的 [单元测试 target 的访问级别](../chapter2/25_Access_Control.md#access-levels-for-unit-test-targets) 章节中新增了有关单元测试访问控制相关的内容。
* 在 [模式](../chapter3/08_Patterns.md) 篇章的 [可选模式](../chapter3/08_Patterns.md#optional-pattern) 章节中新增了可选模式相关内容。
* 更新 [Repeat-While](../chapter2/05_Control_Flow.md#repeat-while) 章节中有关 `repeat-while` 循环相关的内容。
* 更新 [字符串和字符](../chapter2/03_Strings_And_Characters.md) 章节,现在 `String` 类型在 Swift 标准库中不再遵循 `CollectionType` 协议。
* 在 [常量与变量打印](../chapter2/01_TheBasics.md#printing) 章节中新增了新 Swift 标准库中有关 `print(_:separator:terminator) ` 相关内容。
* 在 [枚举](../chapter2/08_Enumerations.md) 篇章的 [原始值的隐式赋值](../chapter2/08_Enumerations.md#implicitly-assigned-raw-values) 章节和 [声明](../chapter3/06_Declarations.md) 篇章的 [包含原始值类型的枚举](../chapter3/06_Declarations.md#enumerations-with-cases-of-a-raw-value-type) 章节中新增了有关包含 `String` 原始值的枚举用例的行为相关内容。
* 在 [自动闭包](../chapter2/07_Closures.md#autoclosures) 章节中新增了有关 `@autoclosure` 特性的相关内容,包括它的 `@autoclosure(escaping)` 形式。
* 更新 [声明特性](../chapter3/07_Attributes.md#Ideclaration-attributes) 章节中有关 `@avaliable``warn_unused_result` 特性的相关内容。
* 更新 [类型特性](../chapter3/07_Attributes.md#type-attributes) 章节中有关 `@convention` 特性的相关内容。
* 在 [可选绑定](../chapter2/01_TheBasics.md#optional-binding) 章节中新增了有关使用 `where` 子句进行多可选绑定的相关内容。
* 在 [字符串字面量](../chapter3/02_Lexical_Structure.md#string-literal) 章节中新增了有关在编译时使用 `+` 运算符拼接字符串字面量的相关内容。
* 在 [元类型](../chapter3/03_Types.md#metatype-type-h) 章节中新增了有关元类型值的比较和使用它们通过构造器表达式构造实例相关内容。
* 在 [断言调试](../chapter2/01_TheBasics.md#debugging-with-assertions) 章节中新增了一处说明,有关用户定义断言何时会失效。
* 更新 [声明特性](../chapter3/07_Attributes.md#Ideclaration-attributes) 章节中对 `@NSManaged` 特性的讨论,现在这个特性可以被应用到一个确定实例方法。
* 更新 [可变参数](../chapter2/06_Functions.md#variadic-parameters) 章节,现在可变参数可以声明在函数参数列表的任意位置中。
* 在 [重写可失败构造器](../chapter2/14_Initialization.md#overriding-a-failable-initializer) 章节中新增了有关非可失败构造器相当于一个可失败构造器通过父类构造器的结果进行强制拆包的相关内容。
* 在 [任意类型用例的枚举](../chapter3/06_Declarations.md#enumerations-with-cases-of-any-type) 章节中新增了有关枚举用例作为函数的内容。
* 在 [构造器表达式](../chapter3/04_Expressions.md#initializer-expression) 章节中新增了有关显式引用一个构造器相关内容。
* 在 [编译控制语句](../chapter3/05_Statements.md#compiler-control-statements) 章节中新增了有关编译内容以及行控制语句相关内容。
* 在 [元类型](../chapter3/03_Types.md#metatype-type-h) 章节新增了一处说明,有关如何从元类型值中构造类实例相关内容。
* 在 [弱引用](../chapter2/23_Automatic_Reference_Counting.md#weak-references) 章节新增了一处说明,有关弱引用作为缓存所存在的不足。
* 更新 [类型特性](../chapter2/10_Properties.md#type-properties) 章节,提到了存储型特性其实是懒加载。
* 更新 [捕获类型](../chapter2/07_Closures.md#capturing_values) 章节,阐明了变量和常量在闭包中如何被捕获。
* 更新 [声明特性](../chapter3/07_Attributes.md#Ideclaration-attributes) 章节,用以描述何时在类中使用 `@objc` 关键字。
* 在 [错误处理](../chapter2/17_Error_Handling.md#handling-errors) 章节中新增了一处说明,有关执行 `throw` 语句的性能。在 [Do 语句](../chapter3/05_Statements.md#do-statement) 章节的 do 语句部分也新增了类似内容。
* 更新 [类型特性](../chapter2/10_Properties.md#type-properties) 章节中有关类、结构体和枚举的存储型和计算型特性相关的内容。
* 更新 [Break 语句](../chapter3/05_Statements.md#break_statement) 章节中有关带标签的 break 语句相关内容。
* 在 [属性观察器](../chapter2/10_Properties.md#property-observers) 章节更新了一处说明,用来明确 `willSet``didSet` 观察器的行为。
* 在 [访问级别](../chapter2/25_Access_Control.md#access-levels) 章节新增了有关 `private` 作用域的相关内容说明。
* 在 [弱引用](../chapter2/23_Automatic_Reference_Counting.md#weak-references) 章节新增了有关弱应用在垃圾回收系统和 ARC 之间的区别的说明。
* 更新 [字符串字面量中特殊字符](../chapter2/03_Strings_And_Characters.md#special-characters-in-string-literals) 章节,对 Unicode 标量更精确定义。
### 2015-04-08
* 更新至 Swift 1.2。
* Swift 现在自身提供了一个 `Set` 集合类型,更多内容,请看 [Sets](../chapter2/CollectionTypes.md#sets) 。
* `@autoclosure` 现在是一个参数声明的属性,而不是参数类型的属性。这里还有一个新的参数声明属性 `@noescape`。更多内容,请看 [属性声明](../chapter3/07_Attributes.md#Ideclaration-attributes) 。
* 对于类型属性和方法现在可以使用 `static` 关键字作为声明描述符,更多内容,请看 [类型变量属性](../chapter3/06_Declarations.md#type-variable-properties)。
* Swift 现在包含一个 `as?``as!` 的向下可失败类型转换运算符。更多内容,请看 [协议遵循性检查](../chapter2/21_Protocols.md#checking-for-protocol-conformance)。
* 新增 [字符串索引](../chapter2/03_Strings_And_Characters.md#string-indices) 的新指导章节。
* 在 [溢出运算符](../chapter2/26_Advanced_Operators.md#overflow-operators) 一节中删除了溢出除运算符(`&/`)和求余溢出运算符(`&%`)。
* 更新常量和常量属性在声明和构造时的规则,更多内容,请看 [常量声明](../chapter3/06_Declarations.md#constant-declaration) 。
* 更新字符串字面量中 Unicode 标量集的定义,请看 [字符串字面量中的特殊字符](../chapter2/03_Strings_And_Characters.md#special-characters-in-string-literals) 。
* 更新 [区间运算符](../chapter2/BasicOperators.md#range-operators) 章节,注意当半开区间运算符含有相同的起止索引时,其区间为空。
* 更新 [闭包引用类型](../chapter2/07_Closures.md#closures-are-reference-types) 章节,对于变量的捕获规则进行了阐明。
* 更新 [值溢出](../chapter2/26_Advanced_Operators.md#value-overflow) 章节,对有符号整数和无符号整数的溢出行为进行了阐明。
* 更新 [协议声明](../chapter3/06_Declarations.md#protocol-declaration) 章节,对协议声明时的作用域和成员等内容进行了阐明。
* 更新 [捕获列表](../chapter2/23_Automatic_Reference_Counting.md#defining-a-capture-list) 章节,对于闭包捕获列表中的弱引用和无主引用的使用语法进行了阐明。
* 更新 [运算符](../chapter3/02_Lexical_Structure.md#operator) 章节明确指明一些例子来说明自定义运算符所支持的特性如数学运算符各种符号Unicode 符号块等。
* 在函数作用域中的常量声明时可以不被初始化,它必须在第一次使用前被赋值。更多的内容,请看 [常量声明](../chapter3/06_Declarations.md#constant-declaration)。
* 在构造器中,常量属性有且仅能被赋值一次。更多内容,请看 [在构造过程中给常量属性赋值](../chapter2/14_Initialization.md{#assigning-constant-properties-during-initialization)。
* 多个可选绑定现在可以在`if`语句后面以逗号分隔的赋值列表的方式出现,更多内容,请看 [可选绑定](../chapter2/01_TheBasics.md#optional-binding)。
* 一个 [可选链表达式](../chapter3/04_Expressions.md#optional-chaining-expression) 必须出现在后缀表达式中。
* 协议类型转换不再局限于 `@obj` 修饰的协议了。
* 在运行时可能会失败的类型转换可以使用 `as?``as!` 运算符,而确保不会失败的类型转换现在使用 `as` 运算符。更多内容,请看 [类型转换运算符](../chapter3/04_Expressions.md#type-casting-operator)。
### 2014-10-16
* 更新至 Swift 1.1。
* 新增 [失败构造器](../chapter2/14_Initialization.md#failable-initializers) 的完整指引。
* 在协议中新增了 [失败构造器要求](../chapter2/21_Protocols.md#failable-initializer-requirements) 的描述。
* 常量和变量的 `Any` 类型现可以包含函数实例。更新了有关 `Any` 相关的示例来展示如何在 `switch` 语句中如何检查并转换到一个函数类型。
* 带有原始值的枚举类型增加了一个 `rawValue` 属性替代 `toRaw()` 方法,同时使用了一个以 `rawValue` 为参数的失败构造器来替代 `fromRaw()` 方法。更多的内容,请看 [原始值](../chapter2/08_Enumerations.md#raw-values) 和 [带原始值的枚举类型](../chapter3/06_Declarations.md#enumerations-with-cases-of-a-raw-value-type)。
* 新增 [Failable Initializer](../chapter3/06_Declarations.md#failable-initializers) 的参考章节,它可以触发初始化失败。
* 自定义运算符现在可以包含 `?` 字符,更新了 [运算符](../chapter3/02_Lexical_Structure.md#operator) 涉及改进后的规则的部分,并且在 [自定义运算符](../chapter2/26_Advanced_Operators.md#custom-operators) 章节中删除了重复的运算符有效字符集合。
### 2014-08-18
* 描述 Swift 1.0 的新文档。Swift 是苹果公司发布的全新编程语言,用于 iOS 和 OS X 应用开发。
* 在协议中新增了 [对构造器的规定](../chapter2/21_Protocols.md#initializer-requirements) 章节。
* 新增 [类专属协议](../chapter2/21_Protocols.md#class-only-protocol) 章节。
* [断言](../chapter2/01_TheBasics.md#assertions-and-preconditions) 现在可以使用字符串内插语法,并删除了文档中有冲突的注释。
* 更新 [连接字符串和字符](../chapter2/03_Strings_And_Characters.md#concatenating-strings-and-characters) 章节来说明字符串和字符不能再用 `+` 号运算符或者复合加法运算符 `+=` 相互连接,这两种运算符现在只能用于字符串之间相连。请使用 `String` 类型的 `append` 方法在一个字符串的尾部增加单个字符。
* 在 [属性申明](../chapter3/07_Attributes.md#Ideclaration-attributes) 章节增加了有关 `availability` 特性的一些内容。
* [可选类型](../chapter2/01_TheBasics.md#optionals) 若有值时,不再隐式的转换为 `true`,同样,若无值时,也不再隐式的转换为 `false`,这是为了避免在判别 optional `Bool` 的值时产生困惑。 替代的方案是,用`==``!=` 运算符显式地去判断 Optinal 是否是 `nil`,以确认其是否包含值。
* Swift 新增了一个 [Nil 合并运算符](../chapter2/BasicOperators.md#nil-coalescing-operator) (`a ?? b`) , 该表达式中,如果 Optional `a` 的值存在,则取得它并返回,若 Optional `a``nil`,则返回默认值 `b`
* 更新和扩展 [字符串的比较](../chapter2/03_Strings_And_Characters.md#comparing-strings) ,用以反映和展示'字符串和字符的比较',以及'前缀prefix/后缀postfix比较'都开始基于扩展字符集extended grapheme clusters规范的等价比较。
* 现在,你可以通过下标赋值或者 [可选调用链](../chapter2/16_Optional_Chaining.md) 中的可变方法和操作符来给属性设值。相应地更新了有关 [通过可选链接访问属性](../chapter2/16_Optional_Chaining.md#accessing-properties-through-optional-chaining) 的内容,并扩展了 [通过可选链接调用方法](../chapter2/16_Optional_Chaining.md#calling-methods-through-optional-chaining) 时检查方法调用成功的示例,以显示如何检查属性设置是否成功。
* 在可选链中新增了 [访问可选类型的下标脚注](../chapter2/16_Optional_Chaining.md#accessing-subscripts-through-optional-chaining) 章节。
* 更新 [访问和修改数组](../chapter2/CollectionTypes.md#accessing-and-modifying-a-dictionary) 章节以标示,从该版本起,不能再通过 `+=` 运算符给一个数组新增一个新的项。对应的替代方案是,使 `append` 方法,或者通过 `+=` 运算符来新增一个只有一个项的数组。
* 新增一处说明,在 [范围运算符](../chapter2/BasicOperators.md#range-operators) 中,比如, `a..b``a..<b` ,起始值 `a` 不能大于结束值 `b`
* 重写 [继承](../chapter2/13_Inheritance.md) 篇章删除了本章中有关构造器重写的介绍性报道转而将更多的注意力放到新增的部分——子类的新功能以及如何通过重写overrides修改已有的功能。另外 [重写属性的 Getters 和 Setters](../chapter2/13_Inheritance.md#overriding-property-etters-and-setters) 中的例子已经被替换为展示如何重写一个 `description` 属性。 (而有关如何在子类的构造器中修改继承属性的默认值的例子,已经被移到 [构造过程](../chapter2/14_Initialization.md) 篇章。)
* 更新 [构造器的继承与重写](../chapter2/14_Initialization.md#initializer-inheritance-and-overriding) 章节以标示: 重写一个特定的构造器必须使用 `override` 修饰符。
* 更新 [Required 构造器](../chapter2/14_Initialization.md#required-initializers) 章节以标示:`required` 修饰符现在需要出现在所有子类的 required 构造器的声明中,而 required 构造器的实现,现在可以仅从父类自动继承。
* 中置Infix的 [运算符函数](../chapter2/26_Advanced_Operators.md#operator-functions) 不再需要 `@infix` 属性。
* [前置和后置运算符](../chapter2/26_Advanced_Operators.md#prefix-and-postfix-operators) 的 `@prefix``@postfix` 属性,已变更为 `prefix``postfix` 声明修饰符。
* 新增一处说明,在 Prefix 和 postfix 运算符被作用于同一个操作数时 [前置和后置运算符](../chapter2/26_Advanced_Operators.md#prefix-and-postfix-operators) 的执行顺序。
* [组合赋值运算符](../chapter2/26_Advanced_Operators.md#compound-assignment-operators) 的运算符函数不再使用 `@assignment` 属性来定义函数。
* 在定义 [自定义操作符](../chapter2/26_Advanced_Operators.md#custom-operators) 时,`修饰符Modifiers的出现顺序发生变化`。比如现在,你该编写 `prefix operator` 而不是 `operator prefix`
* 在 [声明修饰符](../chapter3/06_Declarations.md#declaration-modifiers) 章节新增了有关 `dynamic` 声明修饰符的内容。
* 新增有关 [字面量](../chapter3/02_Lexical_Structure.md#literal) 类型推导内容的内容。
* 新增更多有关柯里化函数的内容。
* 新增 [权限控制](../chapter2/25_Access_Control.md) 篇章。
* 更新 [字符串和字符](../chapter2/03_Strings_And_Characters.md) 章节,在 Swift 中现在 `Character` 类型代表的是扩展字符集extended grapheme cluster中的一个 Unicode为此新增了 [Extended Grapheme Clusters](../chapter2/03_Strings_And_Characters.md#extended-grapheme-clusters) 章节。同时,[Unicode 标量](../chapter2/03_Strings_And_Characters.md#unicode-scalars-representation) 和 [字符串比较](../chapter2/03_Strings_And_Characters.md#comparing-strings) 章节新增了更多内容。
* 更新 [字符串字面量](../chapter2/03_Strings_And_Characters.md#string-literals) 章节在一个字符串中Unicode 标量Unicode scalars`\u{n}`的形式来表示,`n` 是一个最大可以有8位的16进制数。
* `NSString` `length` 属性已被映射到 Swift 的内建 `String`类型。(注意,这两属性的类型是`utf16Count`,而非 `utf16count`)。
* Swift 的内建 `String` 类型不再拥有 `uppercaseString``lowercaseString` 属性。在 [字符串和字符](../chapter2/03_Strings_And_Characters.md) 章节中删除了对应部分,并更新了各种对应的代码用例。
* 新增 [没有外部名的构造器参数](../chapter2/14_Initialization.md#initializer-parameters-without-external-names) 章节。
* 新增 [Required 构造器](../chapter2/14_Initialization.md#required-initializers) 章节。
* 新增 [可选元组返回类型](../chapter2/06_Functions.md#optional-tuple-return-types) 章节。
* 更新 [类型注解](../chapter2/01_TheBasics.md#type-annotations) 章节,多个相关变量可以用"类型注解”在同一行中声明为同一类型。
* `@optional`, `@lazy`, `@final`, `@required` 等关键字被更新为 `optional`, `lazy`, `final`, `required` 参见 [声明修饰符](../chapter3/06_Declarations.md#declaration-modifiers)。
* 更新了整本书中有关 `..<` 的引用,从半闭区间改为了 [半开区间](../chapter2/BasicOperators.md#half-open-range-operator)。
* 更新 [读取和修改字典](../chapter2/CollectionTypes.md#accessing-and-modifying-a-dictionary) 章节, `Dictionary` 现在增加了一个 Boolean 型的属性:`isEmpty`
* 解释了哪些字符(集)可被用来定义 [自定义操作符](../chapter2/26_Advanced_Operators.md#custom-operators)。
* `nil` 和布尔运算中的 `true``false` 现在被定义为 [字面量](../chapter3/02_Lexical_Structure.md#literal)。
* Swift 中的数组 `Array` 类型从现在起具备了完整的值语义。具体内容被更新到 [集合的可变性](../chapter2/CollectionTypes.md#mutability-of-collections) 和 [数组](../chapter2/CollectionTypes.md#arrays) 两小节,以反映这个新的变化。 此外,还解释了如何给 Strings, Arrays 和 Dictionaries 进行赋值和拷贝。
* [数组类型速记语法](../chapter2/CollectionTypes.md#array-type-shorthand-syntax) 从 `SomeType []` 更新为 ` [SomeType]`
* 新增 [字典类型的速记语法](../chapter2/CollectionTypes.md#dictionary-type-shorthand-syntax) 章节,现在书写格式为: ` [KeyType: ValueType]`
* 新增 [字典键类型的哈希值](../chapter2/CollectionTypes.md#hash-values-for-set-types) 章节。
* [闭包表达式](../chapter2/07_Closures.md#closure-expressions) 示例中使用新的全局函数 `sorted` 取代原先的全局函数 `sort` 去展示如何返回一个全新的数组。
* 更新 [结构体逐一成员构造器](../chapter2/14_Initialization.md#memberwise-initializers-for-structure-types) 章节,即使结构体的成员 `没有默认值`,逐一成员构造器也可以自动获得。
* [半开区间运算符](../chapter2/BasicOperators.md#half-open-range-operator) 中`..` 更新为 `..<`
* 新增 [泛型拓展](../chapter2/22_Generics.md#extending-a-generic-type) 的示例。

View File

@ -1,4 +1,3 @@
# 欢迎使用 Swift
在本章中您将了解 Swift 的特性和开发历史,并对 Swift 有一个初步的了解。

File diff suppressed because it is too large Load Diff

View File

@ -1,476 +1,479 @@
# 基本运算符Basic Operators
-----------------
> 1.0
> 翻译:[XieLingWang](https://github.com/xielingwang)
> 校对:[EvilCome](https://github.com/Evilcome)
> 2.0
> 翻译+校对:[JackAlan](https://github.com/AlanMelody)
> 2.1
> 校对:[shanks](http://codebuild.me)
> 2.2
> 翻译+校对:[Cee](https://github.com/Cee) 校对:[SketchK](https://github.com/SketchK)2016-05-11
本页包含内容:
- [术语](#terminology)
- [赋值运算符](#assignment_operator)
- [算术运算符](#arithmetic_operators)
- [组合赋值运算符](#compound_assignment_operators)
- [比较运算符](#comparison_operators)
- [三目运算符](#ternary_conditional_operator)
- [空合运算符](#nil_coalescing_operator)
- [区间运算符](#range_operators)
- [逻辑运算符](#logical_operators)
运算符是检查、改变、合并值的特殊符号或短语。例如,加号(`+`)将两个数相加(如 `let i = 1 + 2`)。更复杂的运算例子包括逻辑与运算符 `&&`(如 `if enteredDoorCode && passedRetinaScan`)。
Swift 支持大部分标准 C 语言的运算符,且改进许多特性来减少常规编码错误。如:赋值符(`=`)不返回值,以防止把想要判断相等运算符(`==`)的地方写成赋值符导致的错误。算术运算符(`+``-``*``/``%`等)会检测并不允许值溢出,以此来避免保存变量时由于变量大于或小于其类型所能承载的范围时导致的异常结果。当然允许你使用 Swift 的溢出运算符来实现溢出。详情参见[溢出运算符](../chapter2/25_Advanced_Operators.html#overflow_operators)。
区别于 C 语言,在 Swift 中你可以对浮点数进行取余运算(`%`Swift 还提供了 C 语言没有的表达两数之间的值的区间运算符(`a..<b``a...b`),这方便我们表达一个区间内的数值。
本章节只描述了 Swift 中的基本运算符,[高级运算符](../chapter2/25_Advanced_Operators.html)这章会包含 Swift 中的高级运算符,及如何自定义运算符,及如何进行自定义类型的运算符重载。
<a name="terminology"></a>
## 术语
运算符分为一元、二元和三元运算符。
- 一元运算符对单一操作对象操作(如 `-a`)。一元运算符分前置运算符和后置运算符,前置运算符需紧跟在操作对象之前(如 `!b`),后置运算符需紧跟在操作对象之后(如 `c!`)。
- 二元运算符操作两个操作对象(如 `2 + 3`),是中置的,因为它们出现在两个操作对象之间。
- 三元运算符操作三个操作对象,和 C 语言一样Swift 只有一个三元运算符,就是三目运算符(`a ? b : c`)。
受运算符影响的值叫操作数,在表达式 `1 + 2` 中,加号 `+` 是二元运算符,它的两个操作数是值 `1``2`
<a name="assignment_operator"></a>
## 赋值运算符
赋值运算(`a = b`),表示用 `b` 的值来初始化或更新 `a` 的值
```swift
let b = 10
var a = 5
a = b
// a 现在等于 10
```
如果赋值的右边是一个多元组,它的元素可以马上被分解成多个常量或变量:
```swift
let (x, y) = (1, 2)
// 现在 x 等于 1y 等于 2
```
与 C 语言和 Objective-C 不同Swift 的赋值操作并不返回任何值。所以以下代码是错误的
```swift
if x = y {
// 此句错误, 因为 x = y 并不返回任何值
}
```
这个特性使你无法把(`==`)错写成(`=`),由于 `if x = y` 是错误代码Swift 能帮你避免此类错误发生
<a name="arithmetic_operators"></a>
## 算术运算符
Swift 中所有数值类型都支持了基本的四则算术运算:
- 加法(`+`
- 减法(`-`
- 乘法(`*`
- 除法(`/`
```swift
1 + 2 // 等于 3
5 - 3 // 等于 2
2 * 3 // 等于 6
10.0 / 2.5 // 等于 4.0
```
与 C 语言和 Objective-C 不同的是Swift 默认情况下不允许在数值运算中出现溢出情况。但是你可以使用 Swift 的溢出运算符来实现溢出运算(如 `a &+ b`)。详情参见[溢出运算符](../chapter2/25_Advanced_Operators.html#overflow_operators)。
加法运算符也可用于 `String` 的拼接:
```swift
"hello, " + "world" // 等于 "hello, world"
```
### 求余运算符
求余运算(`a % b`)是计算 `b` 的多少倍刚刚好可以容入`a`,返回多出来的那部分(余数)。
> 注意:
求余运算(`%`)在其他语言也叫取模运算。然而严格说来,我们看该运算符对负数的操作结果,「求余」比「取模」更合适些。
我们来谈谈取余是怎么回事,计算 `9 % 4`,你先计算出 `4` 的多少倍会刚好可以容入 `9`
![Art/remainderInteger_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/remainderInteger_2x.png "Art/remainderInteger_2x.png")
你可以在 `9` 中放入两个 `4`,那余数是 1用橙色标出
Swift 中可以表达为:
```swift
9 % 4 // 等于 1
```
为了得到 `a % b` 的结果,`%` 计算了以下等式,并输出`余数`作为结果:
a = (b × 倍数) + 余数
`倍数`取最大值的时候,就会刚好可以容入 `a` 中。
`9``4` 代入等式中,我们得 `1`
9 = (4 × 2) + 1
同样的方法,我们来计算 `-9 % 4`
```swift
-9 % 4 // 等于 -1
```
`-9``4` 代入等式,`-2` 是取到的最大整数:
-9 = (4 × -2) + -1
余数是 `-1`
在对负数 `b` 求余时,`b` 的符号会被忽略。这意味着 `a % b``a % -b` 的结果是相同的。
### 浮点数求余计算
不同于 C 语言和 Objective-CSwift 中是可以对浮点数进行求余的。
```swift
8 % 2.5 // 等于 0.5
```
这个例子中,`8` 除以 `2.5` 等于 `3``0.5`,所以结果是一个 `Double` 型的值为 `0.5`
![Art/remainderFloat_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/remainderFloat_2x.png "Art/remainderFloat_2x.png")
### 一元负号运算符
数值的正负号可以使用前缀 `-`(即一元负号)来切换
```swift
let three = 3
let minusThree = -three // minusThree 等于 -3
let plusThree = -minusThree // plusThree 等于 3, 或 "负负3"
```
一元负号(`-`)写在操作数之前,中间没有空格。
### 一元正号运算符
一元正号(`+`)不做任何改变地返回操作数的值:
```swift
let minusSix = -6
let alsoMinusSix = +minusSix // alsoMinusSix 等于 -6
```
虽然一元 `+` 什么都不会改变,但当你在使用一元负号来表达负数时,你可以使用一元正号来表达正数,如此你的代码会具有对称美。
<a name="compound_assignment_operators"></a>
## 组合赋值运算符
如同 C 语言Swift 也提供把其他运算符和赋值运算(`=`)组合的组合赋值运算符,组合加运算(`+=`)是其中一个例子:
```swift
var a = 1
a += 2
// a 现在是 3
```
表达式 `a += 2``a = a + 2` 的简写,一个组合加运算就是把加法运算和赋值运算组合成进一个运算符里,同时完成两个运算任务。
> 注意:
复合赋值运算没有返回值,`let b = a += 2`这类代码是错误。这不同于上面提到的自增和自减运算符。
在[表达式](../chapter3/04_Expressions.html)章节里有复合运算符的完整列表。
<a name="comparison_operators"></a>
## 比较运算符Comparison Operators
所有标准 C 语言中的比较运算都可以在 Swift 中使用:
- 等于(`a == b`
- 不等于(`a != b`
- 大于(`a > b`
- 小于(`a < b`
- 大于等于(`a >= b`
- 小于等于(`a <= b`
> 注意:
Swift 也提供恒等(`===`)和不恒等(`!==`)这两个比较符来判断两个对象是否引用同一个对象实例。更多细节在[类与结构](../chapter2/09_Classes_and_Structures.html)。
每个比较运算都返回了一个标识表达式是否成立的布尔值:
```swift
1 == 1 // true, 因为 1 等于 1
2 != 1 // true, 因为 2 不等于 1
2 > 1 // true, 因为 2 大于 1
1 < 2 // true, 因为 1 小于2
1 >= 1 // true, 因为 1 大于等于 1
2 <= 1 // false, 因为 2 并不小于等于 1
```
比较运算多用于条件语句,如`if`条件:
```swift
let name = "world"
if name == "world" {
print("hello, world")
} else {
print("I'm sorry \(name), but I don't recognize you")
}
// 输出 "hello, world", 因为 `name` 就是等于 "world"
```
关于 `if` 语句,请看[控制流](../chapter2/05_Control_Flow.html)。
当元组中的值可以比较时,你也可以使用这些运算符来比较它们的大小。例如,因为 `Int``String` 类型的值可以比较,所以类型为 `(Int, String)` 的元组也可以被比较。相反,`Bool` 不能被比较,也意味着存有布尔类型的元组不能被比较。
比较元组大小会按照从左到右、逐值比较的方式,直到发现有两个值不等时停止。如果所有的值都相等,那么这一对元组我们就称它们是相等的。例如
```swift
(1, "zebra") < (2, "apple") // true因为 1 小于 2
(3, "apple") < (3, "bird") // true因为 3 等于 3但是 apple 小于 bird
(4, "dog") == (4, "dog") // true因为 4 等于 4dog 等于 dog
```
> 注意:
Swift 标准库只能比较七个以内元素的元组比较函数。如果你的元组元素超过七个时,你需要自己实现比较运算符。
<a name="ternary_conditional_operator"></a>
## 三目运算符Ternary Conditional Operator
三目运算符的特殊在于它是有三个操作数的运算符,它的形式是 `问题 ? 答案 1 : 答案 2`。它简洁地表达根据 `问题`成立与否作出二选一的操作。如果 `问题` 成立,返回 `答案 1` 的结果;反之返回 `答案 2` 的结果
目运算符是以下代码的缩写形式:
```swift
if question {
answer1
} else {
answer2
}
```
这里有个计算表格行高的例子。如果有表头,那行高应比内容高度要高出 50 点;如果没有表头,只需高出 20 点:
```swift
let contentHeight = 40
let hasHeader = true
let rowHeight = contentHeight + (hasHeader ? 50 : 20)
// rowHeight 现在是 90
```
上面的写法比下面的代码更简洁:
```swift
let contentHeight = 40
let hasHeader = true
var rowHeight = contentHeight
if hasHeader {
rowHeight = rowHeight + 50
} else {
rowHeight = rowHeight + 20
}
// rowHeight 现在是 90
```
第一段代码例子使用了三目运算,所以一行代码就能让我们得到正确答案。这比第二段代码简洁得多,无需将 `rowHeight` 定义成变量,因为它的值无需在 `if` 语句中改变。
三目运算提供有效率且便捷的方式来表达二选一的选择。需要注意的事,过度使用三目运算符会使简洁的代码变的难懂。我们应避免在一个组合语句中使用多个三目运算符。
<a name="nil_coalescing_operator"></a>
## 空合运算符Nil Coalescing Operator
空合运算符(`a ?? b`)将对可选类型 `a` 进行空判断,如果 `a` 包含一个值就进行解封,否则就返回一个默认值 `b`。表达式 `a` 必须是 Optional 类型。默认值 `b` 的类型必须要和 `a` 存储值的类型保持一致。
空合运算符是对以下代码的简短表达方法:
```swift
a != nil ? a! : b
```
上述代码使用了三目运算符。当可选类型 `a` 的值不为空时,进行强制解封(`a!`),访问 `a` 中的值;反之返回默认值 `b`。无疑空合运算符(`??`)提供了一种更为优雅的方式去封装条件判断和解封两种行为,显得简洁以及更具可读性。
> 注意:
如果 `a` 为非空值(`non-nil`),那么值 `b` 将不会被计算。这也就是所谓的短路求值。
下文例子采用空合运算符,实现了在默认颜色名和可选自定义颜色名之间抉择:
```swift
let defaultColorName = "red"
var userDefinedColorName: String? //默认值为 nil
var colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName 的值为空,所以 colorNameToUse 的值为 "red"
```
`userDefinedColorName` 变量被定义为一个可选的 `String` 类型,默认值为 `nil`。由于 `userDefinedColorName` 是一个可选类型,我们可以使用空合运算符去判断其值。在上一个例子中,通过空合运算符为一个名为 `colorNameToUse` 的变量赋予一个字符串类型初始值。
由于 `userDefinedColorName` 值为空,因此表达式 `userDefinedColorName ?? defaultColorName` 返回 `defaultColorName` 的值,即 `red`
另一种情况,分配一个非空值(`non-nil`)给 `userDefinedColorName`,再次执行空合运算,运算结果为封包在 `userDefaultColorName` 中的值,而非默认值。
```swift
userDefinedColorName = "green"
colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName 非空,因此 colorNameToUse 的值为 "green"
```
<a name="range_operators"></a>
## 区间运算符Range Operators
Swift 提供了两个方便表达一个区间的值的运算符。
### 闭区间运算符
闭区间运算符(`a...b`)定义一个包含从 `a``b`(包括 `a``b`)的所有值的区间。`a` 的值不能超过 `b`
闭区间运算符在迭代一个区间的所有值时是非常有用的,如在 `for-in` 循环中:
```swift
for index in 1...5 {
print("\(index) * 5 = \(index * 5)")
}
// 1 * 5 = 5
// 2 * 5 = 10
// 3 * 5 = 15
// 4 * 5 = 20
// 5 * 5 = 25
```
关于 `for-in`,请看[控制流](../chapter2/05_Control_Flow.html)。
### 半开区间运算符
半开区间(`a..<b`)定义一个从 `a``b` 但不包括 `b` 的区间。
之所以称为半开区间,是因为该区间包含第一个值而不包括最后的值。
半开区间的实用性在于当你使用一个从 0 开始的列表如数组非常方便地从0数到列表的长度。
```swift
let names = ["Anna", "Alex", "Brian", "Jack"]
let count = names.count
for i in 0..<count {
print("第 \(i + 1) 个人叫 \(names[i])")
}
// 第 1 个人叫 Anna
// 第 2 个人叫 Alex
// 第 3 个人叫 Brian
// 第 4 个人叫 Jack
```
数组有 4 个元素,但 `0..<count` 只数到3最后一个元素的下标因为它是半开区间。关于数组请查阅[数组](../chapter2/04_Collection_Types.html#arrays)。
<a name="logical_operators"></a>
## 逻辑运算Logical Operators
逻辑运算的操作对象是逻辑布尔值。Swift 支持基于 C 语言的三个标准逻辑运算。
- 逻辑非(`!a`
- 逻辑与(`a && b`
- 逻辑`a || b`
### 逻辑非
逻辑非运算(`!a`)对一个布尔值取反,使得 `true``false``false``true`
它是一个前置运算符,需紧跟在操作数之前,且不加空格。读作 `非 a` ,例子如下:
```swift
let allowedEntry = false
if !allowedEntry {
print("ACCESS DENIED")
}
// 输出 "ACCESS DENIED"
```
`if !allowedEntry` 语句可以读作「如果非 allowedEntry」接下一行代码只有在「非 allowedEntry」为 `true`,即 `allowEntry``false` 时被执行。
在示例代码中,小心地选择布尔常量或变量有助于代码的可读性,并且避免使用双重逻辑非运算,或混乱的逻辑语句
### 逻辑与
逻辑与(`a && b`)表达了只有 `a``b` 的值都为 `true` 时,整个表达式的值才会是 `true`
只要任意一个值为 `false`,整个表达式的值就为 `false`。事实上,如果第一个值为 `false`那么是不去计算第二个值的因为它已经不可能影响整个表达式的结果了。这被称做「短路计算short-circuit evaluation
以下例子,只有两个 `Bool` 值都为 `true` 的时候才允许进入 if
```swift
let enteredDoorCode = true
let passedRetinaScan = false
if enteredDoorCode && passedRetinaScan {
print("Welcome!")
} else {
print("ACCESS DENIED")
}
// 输出 "ACCESS DENIED"
```
### 逻辑或
逻辑或(`a || b`)是一个由两个连续的 `|` 组成的中置运算符。它表示了两个逻辑表达式的其中一个为 `true`,整个表达式就为 `true`
逻辑运算类似,逻辑或也是「短路计算」的,当左端的表达式为 `true` 时,将不计算右边的表达式了,因为它不可能改变整个表达式的值了
以下示例代码中,第一个布尔值(`hasDoorKey`)为 `false`,但第二个值(`knowsOverridePassword`)为 `true`,所以整个表达是 `true`,于是允许进入:
```swift
let hasDoorKey = false
let knowsOverridePassword = true
if hasDoorKey || knowsOverridePassword {
print("Welcome!")
} else {
print("ACCESS DENIED")
}
// 输出 "Welcome!"
```
### 逻辑运算符组合计算
我们可以组合多个逻辑运算来表达一个复合逻辑:
```swift
if enteredDoorCode && passedRetinaScan || hasDoorKey || knowsOverridePassword {
print("Welcome!")
} else {
print("ACCESS DENIED")
}
// 输出 "Welcome!"
```
这个例子使用了含多个 `&&``||` 的复合逻辑。但无论怎样,`&&``||` 始终只能操作两个值。所以这实际是三个简单逻辑连续操作的结果。我们来解读一下:
如果我们输入了正确的密码并通过了视网膜扫描,或者我们有一把有效的钥匙,又或者我们知道紧急情况下重置的密码,我们就能把门打开进入。
前两种情况,我们都不满足,所以前两个简单逻辑的结果是 `false`,但是我们知道紧急情况下重置的密码的,所以整个复杂表达式的值还是 `true`
> 注意:
Swift 逻辑操作符 `&&``||` 是左结合的,这意味着拥有多元逻辑操作符的复合表达式优先计算最左边的子表达式。
### 使用括号来明确优先级
为了一个复杂表达式更容易读懂,在合适的地方使用括号来明确优先级是很有效的,虽然它并非必要的。在上个关于门的权限的例子中,我们给第一个部分加个括号,使它看起来逻辑更明确:
```swift
if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword {
print("Welcome!")
} else {
print("ACCESS DENIED")
}
// 输出 "Welcome!"
```
这括号使得前两个值被看成整个逻辑表达中独立的一个部分。虽然有括号和没括号的输出结果是一样的,但对于读代码的人来说有括号的代码更清晰。可读性比简洁性更重要,请在可以让你代码变清晰的地方加个括号吧!
# 基本运算符
*运算符*是检查、改变、合并值的特殊符号或短语。例如,加号(`+`)将两个数相加(如 `let i = 1 + 2`)。更复杂的运算例子包括逻辑与运算符 `&&`(如 `if enteredDoorCode && passedRetinaScan`)。
Swift 支持大部分标准 C 语言的运算符,且为了减少常见编码错误做了部分改进。如:赋值符(`=`)不再有返回值,这样就消除了手误将判等运算符(`==`)写成赋值符导致代码错误的缺陷。算术运算符(`+``-``*``/``%` 等)的结果会被检测并禁止值溢出,以此来避免保存变量时由于变量大于或小于其类型所能承载的范围时导致的异常结果。当然允许你使用 Swift 的溢出运算符来实现溢出。详情参见 [溢出运算符](./26_Advanced_Operators.md#overflow_operators)。
Swift 还提供了 C 语言没有的区间运算符,例如 `a..<b``a...b`,这方便我们表达一个区间内的数值。
本章节只描述了 Swift 中的基本运算符,[高级运算符](./26_Advanced_Operators.md) 这章会包含 Swift 中的高级运算符,及如何自定义运算符,及如何进行自定义类型的运算符重载。
## 术语 {#terminology}
运算符分为一元、二元和三元运算符:
- *一元*运算符对单一操作对象操作(如 `-a`)。一元运算符分前置运算符和后置运算符,*前置运算符*需紧跟在操作对象之前(如 `!b`*后置运算符*需紧跟在操作对象之后(如 `c!`)。
- *二元*运算符操作两个操作对象(如 `2 + 3`),是*中置*的,因为它们出现在两个操作对象之间。
- *三元*运算符操作三个操作对象,和 C 语言一样Swift 只有一个三元运算符,就是三目运算符(`a ? b : c`)。
受运算符影响的值叫*操作数*,在表达式 `1 + 2` 中,加号 `+` 是二元运算符,它的两个操作数是值 `1``2`
## 赋值运算符 {#assignment-operator}
*赋值运算符*`a = b`),表示用 `b` 的值来初始化或更新 `a` 的值:
```swift
let b = 10
var a = 5
a = b
// a 现在等于 10
```
如果赋值的右边是一个多元组,它的元素可以马上被分解成多个常量或变量:
```swift
let (x, y) = (1, 2)
// 现在 x 等于 1y 等于 2
```
与 C 语言和 Objective-C 不同Swift 的赋值操作并不返回任何值。所以下面语句是无效的:
```swift
if x = y {
// 此句错误,因为 x = y 并不返回任何值
}
```
通过将 `if x = y` 标记为无效语句Swift 能帮你避免把 `==`)错写成(`=`)这类错误的出现。
## 算术运算符 {#arithmetic-operators}
Swift 中所有数值类型都支持了基本的四则*算术运算符*
- 加法(`+`
- 减法(`-`
- 乘法(`*`
- 除法(`/`
```swift
1 + 2 // 等于 3
5 - 3 // 等于 2
2 * 3 // 等于 6
10.0 / 2.5 // 等于 4.0
```
与 C 语言和 Objective-C 不同的是Swift 默认情况下不允许在数值运算中出现溢出情况。但是你可以使用 Swift 的溢出运算符来实现溢出运算(如 `a &+ b`)。详情参见 [溢出运算符](./26_Advanced_Operators.md#overflow_operators)。
加法运算符也可用于 `String` 的拼接
```swift
"hello, " + "world" // 等于 "hello, world"
```
### 求余运算符 {#remainder-operator}
*求余运算符*`a % b`)是计算 `b` 的多少倍刚刚好可以容入 `a`,返回多出来的那部分(余数)
> 注意
>
> 求余运算符(`%`)在其他语言也叫*取模运算符*。但是严格说来,我们看该运算符对负数的操作结果,「求余」比「取模」更合适些。
我们来谈谈取余是怎么回事,计算 `9 % 4`,你先计算出 `4` 的多少倍会刚好可以容入 `9` 中:
![Art/remainderInteger_2x.png](https://docs.swift.org/swift-book/_images/remainderInteger_2x.png "Art/remainderInteger_2x.png")
你可以在 `9` 中放入两个 `4`,那余数是 1用橙色标出
在 Swift 中可以表达为:
```swift
9 % 4 // 等于 1
```
为了得到 `a % b` 的结果,`%` 计算了以下等式,并输出 `余数`作为结果:
a = (b × 倍数) + 余数
`倍数`取最大值的时候,就会刚好可以容入 `a` 中。
`9``4` 代入等式中,我们得 `1`
9 = (4 × 2) + 1
同样的方法,我们来计算 `-9 % 4`
```swift
-9 % 4 // 等于 -1
```
`-9``4` 代入等式,`-2` 是取到的最大整数
-9 = (4 × -2) + -1
余数是 `-1`
对负数 `b` 求余时,`b` 的符号会被忽略。这意味着 `a % b``a % -b` 的结果是相同的。
### 一元负号运算符 {#unary-minus-operator}
数值的正负号可以使用前缀 `-`(即*一元负号符*)来切换:
```swift
let three = 3
let minusThree = -three // minusThree 等于 -3
let plusThree = -minusThree // plusThree 等于 3, 或 "负负3"
```
一元负号符(`-`)写在操作数之前,中间没有空格。
### 一元正号运算符 {#unary-plus-operator}
*一元正号符*`+`)不做任何改变地返回操作数的值
```swift
let minusSix = -6
let alsoMinusSix = +minusSix // alsoMinusSix 等于 -6
```
虽然一元正号符什么都不会改变,但当你在使用一元负号来表达负数时,你可以使用一元正号来表达正数,如此你的代码会具有对称美。
## 组合赋值运算符 {#compound-assignment-operators}
如同 C 语言Swift 也提供把其他运算符和赋值运算(`=`)组合的*组合赋值运算符*,组合加运算(`+=`)是其中一个例子:
```swift
var a = 1
a += 2
// a 现在是 3
```
表达式 `a += 2``a = a + 2` 的简写,一个组合加运算就是把加法运算和赋值运算组合成进一个运算符里,同时完成两个运算任务。
> 注意
>
> 复合赋值运算没有返回值,`let b = a += 2` 这类代码是错误。这不同于上面提到的自增和自减运算符。
更多 Swift 标准库运算符的信息,请看 [运算符声明](https://developer.apple.com/documentation/swift/operator_declarations)。
## 比较运算符Comparison Operators {#comparison-operators}
所有标准 C 语言中的*比较运算符*都可以在 Swift 中使用
- 等于(`a == b`
- 不等于(`a != b`
- 大于(`a > b`
- 小于(`a < b`
- 大于等于(`a >= b`
- 小于等于(`a <= b`
> 注意
>
> Swift 也提供恒等(`===`)和不恒等(`!==`)这两个比较符来判断两个对象是否引用同一个对象实例。更多细节在 [类与结构](./09_Classes_and_Structures.md) 章节的 **Identity Operators** 部分。
每个比较运算都返回了一个标识表达式是否成立的布尔值:
```swift
1 == 1 // true, 因为 1 等于 1
2 != 1 // true, 因为 2 不等于 1
2 > 1 // true, 因为 2 大于 1
1 < 2 // true, 因为 1 小于2
1 >= 1 // true, 因为 1 大于等于 1
2 <= 1 // false, 因为 2 并不小于等于 1
```
比较运算多用于条件语句,如 `if` 条件:
```swift
let name = "world"
if name == "world" {
print("hello, world")
} else {
print("I'm sorry \(name), but I don't recognize you")
}
// 输出“hello, world", 因为 `name` 就是等于 "world”
```
关于 `if` 语句,请看 [控制流](./05_Control_Flow.md)
如果两个元组的元素相同,且长度相同的话,元组就可以被比较。比较元组大小会按照从左到右、逐值比较的方式,直到发现有两个值不等时停止。如果所有的值都相等,那么这一对元组我们就称它们是相等的。例如:
```swift
(1, "zebra") < (2, "apple") // true因为 1 小于 2
(3, "apple") < (3, "bird") // true因为 3 等于 3但是 apple 小于 bird
(4, "dog") == (4, "dog") // true因为 4 等于 4dog 等于 dog
```
在上面的例子中,你可以看到,在第一行中从左到右的比较行为。因为 `1` 小于 `2`,所以 `(1, "zebra")` 小于 `(2, "apple")`,不管元组剩下的值如何。所以 `"zebra"` 大于 `"apple"` 对结果没有任何影响,因为元组的比较结果已经被第一个元素决定了。不过,当元组的第一个元素相同时候,第二个元素将会用作比较-第二行和第三行代码就发生了这样的比较。
当元组中的元素都可以被比较时,你也可以使用这些运算符来比较它们的大小。例如,像下面展示的代码,你可以比较两个类型为 `(String, Int)` 的元组,因为 `Int``String` 类型的值可以比较。相反,`Bool` 不能被比较,也意味着存有布尔类型的元组不能被比较。
```swift
("blue", -1) < ("purple", 1) // 正常,比较的结果为 true
("blue", false) < ("purple", true) // 错误,因为 < 不能比较布尔类型
```
> 注意
>
> Swift 标准库只能比较七个以内元素的元组比较函数。如果你的元组元素超过七个时,你需要自己实现比较运算符。
## 三元运算符Ternary Conditional Operator {#ternary-conditional-operator}
*三元运算符*的特殊在于它是有三个操作数的运算符,它的形式是 `问题 ? 答案 1 : 答案 2`。它简洁地表达根据 `问题`成立与否作出二选一的操作。如果 `问题` 成立,返回 `答案 1` 的结果;反之返回 `答案 2` 的结果。
三元运算符是以下代码的缩写形式:
```swift
if question {
answer1
} else {
answer2
}
```
这里有个计算表格行高的例子。如果有表头,那行高应比内容高度要高出 50 点;如果没有表头,只需高出 20 点:
```swift
let contentHeight = 40
let hasHeader = true
let rowHeight = contentHeight + (hasHeader ? 50 : 20)
// rowHeight 现在是 90
```
上面的写法比下面的代码更简洁
```swift
let contentHeight = 40
let hasHeader = true
var rowHeight = contentHeight
if hasHeader {
rowHeight = rowHeight + 50
} else {
rowHeight = rowHeight + 20
}
// rowHeight 现在是 90
```
第一段代码例子使用了三元运算,所以一行代码就能让我们得到正确答案。这比第二段代码简洁得多,无需将 `rowHeight` 定义成变量,因为它的值无需在 `if` 语句中改变
元运算为二选一场景提供了一个非常便捷的表达形式。不过需要注意的是,滥用三元运算符会降低代码可读性。所以我们应避免在一个复合语句中使用多个三元运算符。
## 空合运算符Nil Coalescing Operator {#nil-coalescing-operator}
*空合运算符*`a ?? b`)将对可选类型 `a` 进行空判断,如果 `a` 包含一个值就进行解包,否则就返回一个默认值 `b`。表达式 `a` 必须是 Optional 类型。默认值 `b` 的类型必须要和 `a` 存储值的类型保持一致。
空合运算符是对以下代码的简短表达方法:
```swift
a != nil ? a! : b
```
上述代码使用了三元运算符。当可选类型 `a` 的值不为空时,进行强制解封(`a!`),访问 `a` 中的值;反之返回默认值 `b`。无疑空合运算符(`??`)提供了一种更为优雅的方式去封装条件判断和解封两种行为,显得简洁以及更具可读性。
> 注意
>
> 如果 `a` 为非空值(`non-nil`),那么值 `b` 将不会被计算。这也就是所谓的*短路求值*。
下文例子采用空合运算符,实现了在默认颜色名和可选自定义颜色名之间抉择:
```swift
let defaultColorName = "red"
var userDefinedColorName: String? //默认值为 nil
var colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName 的值为空,所以 colorNameToUse 的值为 "red"
```
`userDefinedColorName` 变量被定义为一个可选的 `String` 类型,默认值为 `nil`。由于 `userDefinedColorName` 是一个可选类型,我们可以使用空合运算符去判断其值。在上一个例子中,通过空合运算符为一个名为 `colorNameToUse` 的变量赋予一个字符串类型初始值。
由于 `userDefinedColorName` 值为空,因此表达式 `userDefinedColorName ?? defaultColorName` 返回 `defaultColorName` 的值,即 `red`
如果你分配一个非空值(`non-nil`)给 `userDefinedColorName`,再次执行空合运算,运算结果为封包在 `userDefaultColorName` 中的值,而非默认值。
```swift
userDefinedColorName = "green"
colorNameToUse = userDefinedColorName ?? defaultColorName
// userDefinedColorName 非空,因此 colorNameToUse 的值为 "green"
```
## 区间运算符Range Operators {#range-operators}
Swift 提供了几种方便表达一个区间的值的*区间运算符*。
### 闭区间运算符 {#closed-range-operator}
*闭区间运算符*`a...b`)定义一个包含从 `a``b`(包括 `a``b`)的所有值的区间。`a` 的值不能超过 `b`
闭区间运算符在迭代一个区间的所有值时是非常有用的,如在 `for-in` 循环中:
```swift
for index in 1...5 {
print("\(index) * 5 = \(index * 5)")
}
// 1 * 5 = 5
// 2 * 5 = 10
// 3 * 5 = 15
// 4 * 5 = 20
// 5 * 5 = 25
```
关于 `for-in` 循环,请看 [控制流](./05_Control_Flow.md)。
### 半开区间运算符 {#half-open-range-operator}
*半开区间运算符*`a..<b`)定义一个从 `a` `b` 但不包括 `b` 的区间
之所以称为*半开区间*,是因为该区间包含第一个值而不包括最后的值。
半开区间的实用性在于当你使用一个从 0 开始的列表如数组非常方便地从0数到列表的长度。
```swift
let names = ["Anna", "Alex", "Brian", "Jack"]
let count = names.count
for i in 0..<count {
print("第 \(i + 1) 个人叫 \(names[i])")
}
// 第 1 个人叫 Anna
// 第 2 个人叫 Alex
// 第 3 个人叫 Brian
// 第 4 个人叫 Jack
```
数组有 4 个元素,但 `0..<count` 只数到3最后一个元素的下标因为它是半开区间。关于数组请查阅 [数组](./04_Collection_Types.md#arrays)。
### 单侧区间 {#one-sided-ranges}
闭区间操作符有另一个表达形式,可以表达往一侧无限延伸的区间 —— 例如,一个包含了数组从索引 2 到结尾的所有值的区间。在这些情况下,你可以省略掉区间操作符一侧的值。这种区间叫做单侧区间,因为操作符只有一侧有值。例如:
```swift
for name in names[2...] {
print(name)
}
// Brian
// Jack
for name in names[...2] {
print(name)
}
// Anna
// Alex
// Brian
```
半开区间操作符也有单侧表达形式,附带上它的最终值。就像你使用区间去包含一个值,最终值并不会落在区间内。例如:
```swift
for name in names[..<2] {
print(name)
}
// Anna
// Alex
```
单侧区间不止可以在下标里使用,也可以在别的情境下使用。你不能遍历省略了初始值的单侧区间,因为遍历的开端并不明显。你可以遍历一个省略最终值的单侧区间;然而,由于这种区间无限延伸的特性,请保证你在循环里有一个结束循环的分支。你也可以查看一个单侧区间是否包含某个特定的值,就像下面展示的那样。
```swift
let range = ...5
range.contains(7) // false
range.contains(4) // true
range.contains(-1) // true
```
## 逻辑运算符Logical Operators {#logical-operators}
*逻辑运算符*的操作对象是逻辑布尔值。Swift 支持基于 C 语言的三个标准逻辑运算。
- 逻辑`!a`
- 逻辑与(`a && b`
- 逻辑或(`a || b`
### 逻辑非运算符
*逻辑非运算符*`!a`)对一个布尔值取反,使得 `true``false``false``true`
它是一个前置运算符,需紧跟在操作数之前,且不加空格。读作 `非 a` ,例子如下:
```swift
let allowedEntry = false
if !allowedEntry {
print("ACCESS DENIED")
}
// 输出“ACCESS DENIED”
```
`if !allowedEntry` 语句可以读作「如果非 allowedEntry」接下一行代码只有在「非 allowedEntry」为 `true`,即 `allowEntry``false` 时被执行
在示例代码中,小心地选择布尔常量或变量有助于代码的可读性,并且避免使用双重逻辑非运算,或混乱的逻辑语句。
### 逻辑与运算符 #{logical_and_operator}
*逻辑与运算符*`a && b`)表达了只有 `a``b` 的值都为 `true` 时,整个表达式的值才会是 `true`
只要任意一个值为 `false`,整个表达式的值就为 `false`。事实上,如果第一个值为 `false`,那么是不去计算第二个值的,因为它已经不可能影响整个表达式的结果了。这被称做*短路计算short-circuit evaluation*。
以下例子,只有两个 `Bool` 值都为 `true` 的时候才允许进入 if
```swift
let enteredDoorCode = true
let passedRetinaScan = false
if enteredDoorCode && passedRetinaScan {
print("Welcome!")
} else {
print("ACCESS DENIED")
}
// 输出“ACCESS DENIED”
```
### 逻辑或运算符 #{logical_or_operator}
逻辑运算符(`a || b`)是一个由两个连续的 `|` 组成的中置运算符。它表示了两个逻辑表达式的其中一个为 `true`,整个表达式就为 `true`
同逻辑与运算符类似,逻辑或也是「短路计算」的,当左端的表达式为 `true` 时,将不计算右边的表达式了,因为它不可能改变整个表达式的值了。
以下示例代码中,第一个布尔值(`hasDoorKey`)为 `false`,但第二个值(`knowsOverridePassword`)为 `true`,所以整个表达是 `true`,于是允许进入:
```swift
let hasDoorKey = false
let knowsOverridePassword = true
if hasDoorKey || knowsOverridePassword {
print("Welcome!")
} else {
print("ACCESS DENIED")
}
// 输出“Welcome!”
```
### 逻辑运算符组合计算 {#combining-logical-operators}
我们可以组合多个逻辑运算符来表达一个复合逻辑:
```swift
if enteredDoorCode && passedRetinaScan || hasDoorKey || knowsOverridePassword {
print("Welcome!")
} else {
print("ACCESS DENIED")
}
// 输出“Welcome!”
```
这个例子使用了含多个 `&&``||` 的复合逻辑。但无论怎样,`&&``||` 始终只能操作两个值。所以这实际是三个简单逻辑连续操作的结果。我们来解读一下:
如果我们输入了正确的密码并通过了视网膜扫描,或者我们有一把有效的钥匙,又或者我们知道紧急情况下重置的密码,我们就能把门打开进入
前两种情况,我们都不满足,所以前两个简单逻辑的结果是 `false`,但是我们是知道紧急情况下重置的密码的,所以整个复杂表达式的值还是 `true`
> 注意
>
> Swift 逻辑操作符 `&&` 和 `||` 是左结合的,这意味着拥有多元逻辑操作符的复合表达式优先计算最左边的子表达式。
### 使用括号来明确优先级 {#explicit-parentheses}
为了一个复杂表达式更容易读懂,在合适的地方使用括号来明确优先级是很有效的,虽然它并非必要的。在上个关于门的权限的例子中,我们给第一个部分加个括号,使它看起来逻辑更明确:
```swift
if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword {
print("Welcome!")
} else {
print("ACCESS DENIED")
}
// 输出“Welcome!”
```
这括号使得前两个值被看成整个逻辑表达中独立的一个部分。虽然有括号和没括号的输出结果是一样的,但对于读代码的人来说有括号的代码更清晰。可读性比简洁性更重要,请在可以让你代码变清晰的地方加个括号吧!

View File

@ -1,74 +1,126 @@
# 字符串和字符Strings and Characters
---
# 字符串和字符
> 1.0
> 翻译:[wh1100717](https://github.com/wh1100717)
> 校对:[Hawstein](https://github.com/Hawstein)
*字符串*是是一系列字符的集合,例如 `"hello, world"``"albatross"`。Swift 的字符串通过 `String` 类型来表示。而 `String` 内容的访问方式有多种,例如以 `Character` 值的集合。
> 2.0
> 翻译+校对:[DianQK](https://github.com/DianQK)
Swift 的 `String``Character` 类型提供了一种快速且兼容 Unicode 的方式来处理代码中的文本内容。创建和操作字符串的语法与 C 语言中字符串操作相似,轻量并且易读。通过 `+` 符号就可以非常简单的实现两个字符串的拼接操作。与 Swift 中其他值一样,能否更改字符串的值,取决于其被定义为常量还是变量。你可以在已有字符串中插入常量、变量、字面量和表达式从而形成更长的字符串,这一过程也被成为字符串插值。尤其是在为显示、存储和打印创建自定义字符串值时,字符串插值操作尤其有用。
> 2.1
> 翻译:[DianQK](https://github.com/DianQK)
> 校对:[shanks](http://codebuild.me), [Realank](https://github.com/Realank),
尽管语法简易,但 Swift 中的 `String` 类型的实现却很快速和现代化。每一个字符串都是由编码无关的 Unicode 字符组成,并支持访问字符的多种 Unicode 表示形式。
> 2.2
> 校对:[SketchK](https://github.com/SketchK)
> 3.0
> 校对:[CMB](https://github.com/chenmingbiao)
> 注意
>
> 版本日期2016-09-13
> Swift 的 `String` 类型与 Foundation `NSString` 类进行了无缝桥接。Foundation 还对 `String` 进行扩展使其可以访问 `NSString` 类型中定义的方法。这意味着调用那些 `NSString` 的方法,你无需进行任何类型转换。
>
> 更多关于在 Foundation 和 Cocoa 中使用 `String` 的信息请查看 *[Bridging Between String and NSString](https://developer.apple.com/documentation/swift/string#2919514)*。
本页包含内容:
## 字符串字面量 {#string-literals}
- [字符串字面量](#string_literals)
- [初始化空字符串](#initializing_an_empty_string)
- [字符串可变性](#string_mutability)
- [字符串是值类型](#strings_are_value_types)
- [使用字符](#working_with_characters)
- [连接字符串和字符](#concatenating_strings_and_characters)
- [字符串插值](#string_interpolation)
- [Unicode](#unicode)
- [计算字符数量](#counting_characters)
- [访问和修改字符串](#accessing_and_modifying_a_string)
- [比较字符串](#comparing_strings)
- [字符串的 Unicode 表示形式](#unicode_representations_of_strings)
你可以在代码里使用一段预定义的字符串值作为字符串字面量。字符串字面量是由一对双引号包裹着的具有固定顺序的字符集。
`String`是例如"hello, world""albatross"这样的有序的`Character`(字符)类型的值的集合。通过`String`类型来表示。
一个`String`的内容可以用许多方式读取,它包括一个`Character`值的集合。
创建和操作字符串的语法与 C 语言中字符串操作相似,轻量并且易读。
字符串连接操作只需要简单地通过`+`符号将两个字符串相连即可。与 Swift 中其他值一样,能否更改字符串的值,取决于其被定义为常量还是变量。你也可以在字符串内插过程中使用字符串插入常量、变量、字面量表达成更长的字符串,这样可以很容易的创建自定义的字符串值,进行展示、存储以及打印。
尽管语法简易,但`String`类型是一种快速、现代化的字符串实现。
每一个字符串都是由编码无关的 Unicode 字符组成,并支持访问字符的多种 Unicode 表示形式representations
> 注意:
> Swift 的`String`类型与 Foundation `NSString`类进行了无缝桥接。就像 [`AnyObject`类型](./19_Type_Casting.html#anyobject) 中提到的一样,在使用 Cocoa 中的 Foundation 框架时,您可以将创建的任何字符串的值转换成`NSString`,并调用任意的`NSString` API。您也可以在任意要求传入`NSString`实例作为参数的 API 中用`String`类型的值代替。
> 更多关于在 Foundation 和 Cocoa 中使用`String`的信息请查看 *[Using Swift with Cocoa and Objective-C (Swift 2.1)](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216)*。
<a name="string_literals"></a>
## 字符串字面量String Literals
您可以在您的代码中包含一段预定义的字符串值作为字符串字面量。字符串字面量是由双引号 (`""`) 包裹着的具有固定顺序的文本字符集。
字符串字面量可以用于为常量和变量提供初始值:
```swift
let someString = "Some string literal value"
```
注意`someString`常量通过字符串字面量进行初始化Swift 会推断该常量为`String`类型
注意Swift 之所以推断 `someString` 常量字符串类型,是因为它使用了字面量方式进行初始化。
> 注意:
更多关于在字符串字面量中使用特殊字符的信息,请查看 [字符串字面量的特殊字符](#special_characters_in_string_literals) 。
### 多行字符串字面量 {#multiline-string-literals}
如果你需要一个字符串是跨越多行的,那就使用多行字符串字面量 — 由一对三个双引号包裹着的具有固定顺序的文本字符集:
<a name="initializing_an_empty_string"></a>
## 初始化空字符串 (Initializing an Empty String)
```swift
let quotation = """
The White Rabbit put on his spectacles. "Where shall I begin,
please your Majesty?" he asked.
要创建一个空字符串作为初始值,可以将空的字符串字面量赋值给变量,也可以初始化一个新的`String`实例:
"Begin at the beginning," the King said gravely, "and go on
till you come to the end; then stop."
"""
```
一个多行字符串字面量包含了所有的在开启和关闭引号(`"""`)中的行。这个字符从开启引号(`"""`)之后的第一行开始,到关闭引号(`"""`)之前为止。这就意味着字符串开启引号之后(`"""`)或者结束引号(`"""`)之前都没有换行符号。(译者:下面两个字符串其实是一样的,虽然第二个使用了多行字符串的形式)
```swift
let singleLineString = "These are the same."
let multilineString = """
These are the same.
"""
```
如果你的代码中,多行字符串字面量包含换行符的话,则多行字符串字面量中也会包含换行符。如果你想换行,以便加强代码的可读性,但是你又不想在你的多行字符串字面量中出现换行符的话,你可以用在行尾写一个反斜杠(`\`)作为续行符。
```swift
let softWrappedQuotation = """
The White Rabbit put on his spectacles. "Where shall I begin, \
please your Majesty?" he asked.
"Begin at the beginning," the King said gravely, "and go on \
till you come to the end; then stop."
"""
```
为了让一个多行字符串字面量开始和结束于换行符,请将换行写在第一行和最后一行,例如:
```swift
let lineBreaks = """
This string starts with a line break.
It also ends with a line break.
"""
```
一个多行字符串字面量能够缩进来匹配周围的代码。关闭引号(`"""`)之前的空白字符串告诉 Swift 编译器其他各行多少空白字符串需要忽略。然而,如果你在某行的前面写的空白字符串超出了关闭引号(`"""`)之前的空白字符串,则超出部分将被包含在多行字符串字面量中。
![](https://docs.swift.org/swift-book/_images/multilineStringWhitespace_2x.png)
在上面的例子中,尽管整个多行字符串字面量都是缩进的(源代码缩进),第一行和最后一行没有以空白字符串开始(实际的变量值)。中间一行的缩进用空白字符串(源代码缩进)比关闭引号(`"""`之前的空白字符串多所以它的行首将有4个空格。
### 字符串字面量的特殊字符 {#special-characters-in-string-literals}
字符串字面量可以包含以下特殊字符:
* 转义字符 `\0`(空字符)、`\\`(反斜线)、`\t`(水平制表符)、`\n`(换行符)、`\r`(回车符)、`\"`(双引号)、`\'`(单引号)。
* Unicode 标量,写成 `\u{n}`(u 为小写),其中 `n` 为任意一到八位十六进制数且可用的 Unicode 位码。
下面的代码为各种特殊字符的使用示例。
`wiseWords` 常量包含了两个双引号。
`dollarSign``blackHeart``sparklingHeart` 常量演示了三种不同格式的 Unicode 标量:
```swift
let wiseWords = "\"Imagination is more important than knowledge\" - Einstein"
// "Imageination is more important than knowledge" - Enistein
let dollarSign = "\u{24}" // $Unicode 标量 U+0024
let blackHeart = "\u{2665}" // ♥Unicode 标量 U+2665
let sparklingHeart = "\u{1F496}" // 💖Unicode 标量 U+1F496
```
由于多行字符串字面量使用了三个双引号,而不是一个,所以你可以在多行字符串字面量里直接使用双引号(`"`)而不必加上转义符(`\`)。要在多行字符串字面量中使用 `"""` 的话,就需要使用至少一个转义符(`\`
```swift
let threeDoubleQuotes = """
Escaping the first quote \"""
Escaping all three quotes \"\"\"
"""
```
### 扩展字符串分隔符 {#extended-string-delimiters}
您可以将字符串文字放在扩展分隔符中,这样字符串中的特殊字符将会被直接包含而非转义后的效果。将字符串放在引号(`"`)中并用数字符号(``)括起来。例如,打印字符串文字 `"Line 1 \nLine 2"` 会打印换行符转义序列(`\n`)而不是给文字换行。
如果需要字符串文字中字符的特殊效果,请匹配转义字符(`\`)后面添加与起始位置个数相匹配的 `#` 符。 例如,如果您的字符串是 `"Line 1 \nLine 2"` 并且您想要换行,则可以使用 `“Line 1 \#nLine 2”` 来代替。 同样,`###"Line1 \###nLine2"###` 也可以实现换行效果。
扩展分隔符创建的字符串文字也可以是多行字符串文字。 您可以使用扩展分隔符在多行字符串中包含文本 `"""`,覆盖原有的结束文字的默认行为。例如:
```swift
let threeMoreDoubleQuotationMarks = #"""
Here are three more double quotes: """
"""#
```
## 初始化空字符串 {#initializing-an-empty-string}
要创建一个空字符串作为初始值,可以将空的字符串字面量赋值给变量,也可以初始化一个新的 `String` 实例:
```swift
var emptyString = "" // 空字符串字面量
@ -76,20 +128,18 @@ var anotherEmptyString = String() // 初始化方法
// 两个字符串均为空并等价。
```
可以通过检查`Bool`类型的`isEmpty`属性来判断该字符串是否为空:
可以通过检查 `Bool` 类型的 `isEmpty` 属性来判断该字符串是否为空:
```swift
if emptyString.isEmpty {
print("Nothing to see here")
}
// 打印输出:"Nothing to see here"
// 打印输出:Nothing to see here
```
## 字符串可变性 {#string-mutability}
<a name="string_mutability"></a>
## 字符串可变性 (String Mutability)
您可以通过将一个特定字符串分配给一个变量来对其进行修改,或者分配给一个常量来保证其不会被修改:
你可以通过将一个特定字符串分配给一个变量来对其进行修改,或者分配给一个常量来保证其不会被修改:
```swift
var variableString = "Horse"
@ -98,35 +148,27 @@ variableString += " and carriage"
let constantString = "Highlander"
constantString += " and another Highlander"
// 这会报告一个编译错误 (compile-time error) - 常量字符串不可以被修改。
// 这会报告一个编译错误compile-time error - 常量字符串不可以被修改。
```
> 注意
在 Objective-C 和 Cocoa 中,您需要通过选择两个不同的类(`NSString``NSMutableString`)来指定字符串是否可以被修改。
> 注意
>
> 在 Objective-C 和 Cocoa 中,需要通过选择两个不同的类(`NSString` 和 `NSMutableString`)来指定字符串是否可以被修改。
## 字符串是值类型 {#strings-are-value-types}
<a name="strings_are_value_types"></a>
## 字符串是值类型Strings Are Value Types
在 Swift 中 `String` 类型是*值类型*。如果你创建了一个新的字符串,那么当其进行常量、变量赋值操作,或在函数/方法中传递时,会进行值拷贝。在前述任一情况下,都会对已有字符串值创建新副本,并对该新副本而非原始字符串进行传递或赋值操作。值类型在 [结构体和枚举是值类型](./09_Classes_and_Structures.md#structures_and_enumerations_are_value_types) 中进行了详细描述。
Swift `String`类型是值类型
如果您创建了一个新的字符串,那么当其进行常量、变量赋值操作,或在函数/方法中传递时,会进行值拷贝。
任何情况下,都会对已有字符串值创建新副本,并对该新副本进行传递或赋值操作。
值类型在 [结构体和枚举是值类型](./09_Classes_and_Structures.html#structures_and_enumerations_are_value_types) 中进行了详细描述。
Swift 默认拷贝字符串的行为保证了在函数/方法向你传递的字符串所属权属于你,无论该值来自于哪里。你可以确信传递的字符串不会被修改,除非你自己去修改它
Swift 默认字符串拷贝的方式保证了在函数/方法中传递的是字符串的值
很明显无论该值来自于哪里,都是您独自拥有的。
您可以确信传递的字符串不会被修改,除非你自己去修改它。
在实际编译时Swift 编译器会优化字符串的使用,使实际的复制只发生在绝对必要的情况下,这意味着你将字符串作为值类型的同时可以获得极高的性能
在实际编译时Swift 编译器会优化字符串的使用,使实际的复制只发生在绝对必要的情况下,这意味着您将字符串作为值类型的同时可以获得极高的性能。
## 使用字符 {#working-with-characters}
<a name="working_with_characters"></a>
## 使用字符Working with Characters
您可通过`for-in`循环来遍历字符串中的`characters`属性来获取每一个字符的值:
你可通过 `for-in` 循环来遍历字符串,获取字符串中每一个字符的值:
```swift
for character in "Dog!🐶".characters {
for character in "Dog!🐶" {
print(character)
}
// D
@ -136,25 +178,24 @@ for character in "Dog!🐶".characters {
// 🐶
```
`for-in`循环在 [For 循环](./05_Control_Flow.html#for_loops) 中进行了详细描述。
`for-in` 循环在 [For 循环](./05_Control_Flow.md#for_loops) 中进行了详细描述。
另外,通过标明一个`Character`类型并用字符字面量进行赋值,可以建立一个独立的字符常量或变量:
另外,通过标明一个 `Character` 类型并用字符字面量进行赋值,可以建立一个独立的字符常量或变量:
```swift
let exclamationMark: Character = "!"
```
字符串可以通过传递一个值类型为`Character`的数组作为自变量来初始化:
字符串可以通过传递一个值类型为 `Character` 的数组作为自变量来初始化:
```swift
let catCharacters: [Character] = ["C", "a", "t", "!", "🐱"]
let catString = String(catCharacters)
print(catString)
// 打印输出:"Cat!🐱"
// 打印输出:Cat!🐱
```
<a name="concatenating_strings_and_characters"></a>
## 连接字符串和字符 (Concatenating Strings and Characters)
## 连接字符串和字符 {#concatenating-strings-and-characters}
字符串可以通过加法运算符(`+`)相加在一起(或称“连接”)创建一个新的字符串:
@ -165,7 +206,7 @@ var welcome = string1 + string2
// welcome 现在等于 "hello there"
```
也可以通过加法赋值运算符 (`+=`) 将一个字符串添加到一个已经存在字符串变量上:
也可以通过加法赋值运算符`+=`将一个字符串添加到一个已经存在字符串变量上:
```swift
var instruction = "look over"
@ -173,7 +214,7 @@ instruction += string2
// instruction 现在等于 "look over there"
```
可以用`append()`方法将一个字符附加到一个字符串变量的尾部:
可以用 `append()` 方法将一个字符附加到一个字符串变量的尾部:
```swift
let exclamationMark: Character = "!"
@ -181,82 +222,75 @@ welcome.append(exclamationMark)
// welcome 现在等于 "hello there!"
```
> 注意
您不能将一个字符串或者字符添加到一个已经存在的字符变量上,因为字符变量只能包含一个字符。
> 注意
>
> 你不能将一个字符串或者字符添加到一个已经存在的字符变量上,因为字符变量只能包含一个字符。
如果你需要使用多行字符串字面量来拼接字符串,并且你需要字符串每一行都以换行符结尾,包括最后一行:
<a name="string_interpolation"></a>
## 字符串插值 (String Interpolation)
```swift
let badStart = """
one
two
"""
let end = """
three
"""
print(badStart + end)
// 打印两行:
// one
// twothree
字符串插值是一种构建新字符串的方式,可以在其中包含常量、变量、字面量和表达式。
您插入的字符串字面量的每一项都在以反斜线为前缀的圆括号中:
let goodStart = """
one
two
"""
print(goodStart + end)
// 打印三行:
// one
// two
// three
```
上面的代码,把 `badStart``end` 拼接起来的字符串非我们想要的结果。因为 `badStart` 最后一行没有换行符,它与 `end` 的第一行结合到了一起。相反的,`goodStart` 的每一行都以换行符结尾,所以它与 `end` 拼接的字符串总共有三行,正如我们期望的那样。
## 字符串插值 {#string-interpolation}
*字符串插值*是一种构建新字符串的方式,可以在其中包含常量、变量、字面量和表达式。**字符串字面量**和**多行字符串字面量**都可以使用字符串插值。你插入的字符串字面量的每一项都在以反斜线为前缀的圆括号中:
```swift
let multiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
// message is "3 times 2.5 is 7.5"
// message "3 times 2.5 is 7.5"
```
在上面的例子中,`multiplier`作为`\(multiplier)`被插入到一个字符串常量量中。
当创建字符串执行插值计算时此占位符会被替换为`multiplier`实际的值。
在上面的例子中,`multiplier` 作为 `\(multiplier)` 被插入到一个字符串常量量中。当创建字符串执行插值计算时此占位符会被替换为 `multiplier` 实际的值。
`multiplier`的值也作为字符串中后面表达式的一部分。
该表达式计算`Double(multiplier) * 2.5`的值并将结果 (`7.5`) 插入到字符串中。
在这个例子中,表达式写为`\(Double(multiplier) * 2.5)`并包含在字符串字面量中。
`multiplier` 的值也作为字符串中后面表达式的一部分。该表达式计算 `Double(multiplier) * 2.5` 的值并将结果(`7.5`)插入到字符串中。在这个例子中,表达式写为 `\(Double(multiplier) * 2.5)` 并包含在字符串字面量中。
> 注意
> 插值字符串中写在括号中的表达式不能包含非转义反斜杠 (`\`),并且不能包含回车或换行符。不过,插值字符串可以包含其他字面量。
> 注意
>
> 插值字符串中写在括号中的表达式不能包含非转义反斜杠(`\`),并且不能包含回车或换行符。不过,插值字符串可以包含其他字面量。
## Unicode {#unicode}
<a name="unicode"></a>
## Unicode
*Unicode*是一个用于在不同书写系统中对文本进行编码、表示和处理的国际标准。它使你可以用标准格式表示来自任意语言几乎所有的字符并能够对文本文件或网页这样的外部资源中的字符进行读写操作。Swift 的 `String``Character` 类型是完全兼容 Unicode 标准的。
Unicode 是一个国际标准,用于文本的编码和表示。
它使您可以用标准格式表示来自任意语言几乎所有的字符,并能够对文本文件或网页这样的外部资源中的字符进行读写操作。
Swift 的`String``Character`类型是完全兼容 Unicode 标准的。
### Unicode 标量 {#unicode-scalars}
Swift 的 `String` 类型是基于 *Unicode 标量* 建立的。Unicode 标量是对应字符或者修饰符的唯一的 21 位数字,例如 `U+0061` 表示小写的拉丁字母(`LATIN SMALL LETTER A`"`a`"`U+1F425` 表示小鸡表情(`FRONT-FACING BABY CHICK`"`🐥`")。
<a name="unicode_scalars"></a>
### Unicode 标量Unicode Scalars
请注意,并非所有 21 位 Unicode 标量值都分配给字符,某些标量被保留用于将来分配或用于 UTF-16 编码。已分配的标量值通常也有一个名称,例如上面示例中的 LATIN SMALL LETTER A 和 FRONT-FACING BABY CHICK。
Swift 的`String`类型是基于 *Unicode 标量* 建立的。
Unicode 标量是对应字符或者修饰符的唯一的21位数字例如`U+0061`表示小写的拉丁字母(`LATIN SMALL LETTER A`)("`a`")`U+1F425`表示小鸡表情(`FRONT-FACING BABY CHICK`) ("`🐥`")。
### 可扩展的字形群集 {#extended-grapheme-clusters}
> 注意:
> Unicode *码位(code poing)* 的范围是`U+0000`到`U+D7FF`或者`U+E000`到`U+10FFFF`。Unicode 标量不包括 Unicode *代理项(surrogate pair)* 码位,其码位范围是`U+D800`到`U+DFFF`。
每一个 Swift 的 `Character` 类型代表一个*可扩展的字形群*。而一个可扩展的字形群构成了人类可读的单个字符,它由一个或多个(当组合时) Unicode 标量的序列组成。
注意不是所有的21位 Unicode 标量都代表一个字符,因为有一些标量是留作未来分配的。已经代表一个典型字符的标量都有自己的名字,例如上面例子中的`LATIN SMALL LETTER A``FRONT-FACING BABY CHICK`
举个例子,字母 `é` 可以用单一的 Unicode 标量 `é`(`LATIN SMALL LETTER E WITH ACUTE`, 或者 `U+00E9`)来表示。然而一个标准的字母 `e`(`LATIN SMALL LETTER E` 或者 `U+0065`) 加上一个急促重音(`COMBINING ACTUE ACCENT`)的标量(`U+0301`),这样一对标量就表示了同样的字母 `
这个急促重音的标量形象的将 `e` 转换成了 `é`
<a name="special_characters_in_string_literals"></a>
### 字符串字面量的特殊字符 (Special Characters in String Literals)
字符串字面量可以包含以下特殊字符:
* 转义字符`\0`(空字符)、`\\`(反斜线)、`\t`(水平制表符)、`\n`(换行符)、`\r`(回车符)、`\"`(双引号)、`\'`(单引号)。
* Unicode 标量,写成`\u{n}`(u为小写),其中`n`为任意一到八位十六进制数且可用的 Unicode 位码。
下面的代码为各种特殊字符的使用示例。
`wiseWords`常量包含了两个双引号。
`dollarSign``blackHeart``sparklingHeart`常量演示了三种不同格式的 Unicode 标量:
```swift
let wiseWords = "\"Imagination is more important than knowledge\" - Einstein"
// "Imageination is more important than knowledge" - Enistein
let dollarSign = "\u{24}" // $, Unicode 标量 U+0024
let blackHeart = "\u{2665}" // ♥, Unicode 标量 U+2665
let sparklingHeart = "\u{1F496}" // 💖, Unicode 标量 U+1F496
```
<a name="extended_grapheme_clusters"></a>
### 可扩展的字形群集(Extended Grapheme Clusters)
每一个 Swift 的`Character`类型代表一个可扩展的字形群。
一个可扩展的字形群是一个或多个可生成人类可读的字符 Unicode 标量的有序排列。
举个例子,字母`é`可以用单一的 Unicode 标量`é`(`LATIN SMALL LETTER E WITH ACUTE`, 或者`U+00E9`)来表示。然而一个标准的字母`e`(`LATIN SMALL LETTER E`或者`U+0065`) 加上一个急促重音(`COMBINING ACTUE ACCENT`)的标量(`U+0301`),这样一对标量就表示了同样的字母`é`
这个急促重音的标量形象的将`e`转换成了`é`
在这两种情况中,字母`é`代表了一个单一的 Swift 的`Character`值,同时代表了一个可扩展的字形群。
在第一种情况,这个字形群包含一个单一标量;而在第二种情况,它是包含两个标量的字形群:
在这两种情况中,字母 `é` 代表了一个单一的 Swift 的 `Character` 值,同时代表了一个可扩展的字形群。在第一种情况,这个字形群包含一个单一标量;而在第二种情况,它是包含两个标量的字形群:
```swift
let eAcute: Character = "\u{E9}" // é
@ -264,10 +298,7 @@ let combinedEAcute: Character = "\u{65}\u{301}" // e 后面加上 ́
// eAcute 是 é, combinedEAcute 是 é
```
可扩展的字符群集是一个灵活的方法,用许多复杂的脚本字符表示单一的`Character`值。
例如,来自朝鲜语字母表的韩语音节能表示为组合或分解的有序排列。
在 Swift 都会表示为同一个单一的`Character`值:
可扩展的字集是一个许多复杂的脚本字符表示为单个字符值的灵活方式。例如,来自朝鲜语字母表的韩语音节能表示为组合或分解的有序排列。在 Swift 都会表示为同一个单一的 `Character` 值:
```swift
let precomposed: Character = "\u{D55C}" // 한
@ -275,68 +306,64 @@ let decomposed: Character = "\u{1112}\u{1161}\u{11AB}" // ᄒ, ᅡ, ᆫ
// precomposed 是 한, decomposed 是 한
```
可拓展的字符群集可以使包围记号(例如`COMBINING ENCLOSING CIRCLE`或者`U+20DD`)的标量包围其他 Unicode 标量,作为一个单一的`Character`值:
可拓展的字符群集可以使包围记号例如 `COMBINING ENCLOSING CIRCLE` 或者 `U+20DD`的标量包围其他 Unicode 标量,作为一个单一的 `Character` 值:
```swift
let enclosedEAcute: Character = "\u{E9}\u{20DD}"
// enclosedEAcute 是 é⃝
```
地域性指示符号的 Unicode 标量可以组合成一个单一的`Character`值,例如`REGIONAL INDICATOR SYMBOL LETTER U`(`U+1F1FA`)和`REGIONAL INDICATOR SYMBOL LETTER S`(`U+1F1F8`)
地域性指示符号的 Unicode 标量可以组合成一个单一的 `Character` 值,例如 `REGIONAL INDICATOR SYMBOL LETTER U`(`U+1F1FA`)和 `REGIONAL INDICATOR SYMBOL LETTER S`(`U+1F1F8`)
```swift
let regionalIndicatorForUS: Character = "\u{1F1FA}\u{1F1F8}"
// regionalIndicatorForUS 是 🇺🇸
```
<a name="counting_characters"></a>
## 计算字符数量 (Counting Characters)
## 计算字符数量 {#counting-characters}
如果想要获得一个字符串中`Character`值的数量,可以使用字符串的`characters`属性的`count`属性:
如果想要获得一个字符串中 `Character` 值的数量,可以使用 `count` 属性:
```swift
let unusualMenagerie = "Koala 🐨, Snail 🐌, Penguin 🐧, Dromedary 🐪"
print("unusualMenagerie has \(unusualMenagerie.characters.count) characters")
// 打印输出 "unusualMenagerie has 40 characters"
print("unusualMenagerie has \(unusualMenagerie.count) characters")
// 打印输出unusualMenagerie has 40 characters
```
注意在 Swift 中,使用可拓展的字符群集作为`Character`值来连接或改变字符串时,并不一定会更改字符串的字符数量。
注意在 Swift 中,使用可拓展的字符群集作为 `Character` 值来连接或改变字符串时,并不一定会更改字符串的字符数量。
例如,如果你用四个字符的单词`cafe`初始化一个新的字符串,然后添加一个`COMBINING ACTUE ACCENT`(`U+0301`)作为字符串的结尾。最终这个字符串的字符数量仍然是`4`,因为第四个字符是`é`,而不是`e`
例如,如果你用四个字符的单词 `cafe` 初始化一个新的字符串,然后添加一个 `COMBINING ACTUE ACCENT`(`U+0301`)作为字符串的结尾。最终这个字符串的字符数量仍然是 `4`,因为第四个字符是 `é`,而不是 `e`
```swift
var word = "cafe"
print("the number of characters in \(word) is \(word.characters.count)")
// 打印输出 "the number of characters in cafe is 4"
print("the number of characters in \(word) is \(word.count)")
// 打印输出the number of characters in cafe is 4
word += "\u{301}" // COMBINING ACUTE ACCENT, U+0301
word += "\u{301}" // 拼接一个重音,U+0301
print("the number of characters in \(word) is \(word.characters.count)")
// 打印输出 "the number of characters in café is 4"
print("the number of characters in \(word) is \(word.count)")
// 打印输出the number of characters in café is 4
```
> 注意
> 可扩展的字符群集可以组成一个或者多个 Unicode 标量。这意味着不同的字符以及相同字符的不同表示方式可能需要不同数量的内存空间来存储。所以 Swift 中的字符在一个字符串中并不一定占用相同的内存空间数量。因此在没有获取字符串的可扩展的字符群的范围时候,就不能计算出字符串的字符数量。如果您正在处理一个长字符串,需要注意`characters`属性必须遍历全部的 Unicode 标量,来确定字符串的字符数量。
> 注意
>
> 可扩展的字形群可以由多个 Unicode 标量组成。这意味着不同的字符以及相同字符的不同表示方式可能需要不同数量的内存空间来存储。所以 Swift 中的字符在一个字符串中并不一定占用相同的内存空间数量。因此在没有获取字符串的可扩展的字符群的范围时候,就不能计算出字符串的字符数量。如果你正在处理一个长字符串,需要注意 `count` 属性必须遍历全部的 Unicode 标量,来确定字符串的字符数量。
>
> 另外需要注意的是通过`characters`属性返回的字符数量并不总是与包含相同字符的`NSString``length`属性相同。`NSString``length`属性是利用 UTF-16 表示的十六位代码单元数字,而不是 Unicode 可扩展的字符群集。作为佐证,当一个`NSString`的`length`属性被一个Swift的`String`值访问时,实际上是调用了`utf16Count`。
> 另外需要注意的是通过 `count` 属性返回的字符数量并不总是与包含相同字符的 `NSString``length` 属性相同。`NSString``length` 属性是利用 UTF-16 表示的十六位代码单元数字,而不是 Unicode 可扩展的字符群集。
<a name="accessing_and_modifying_a_string"></a>
## 访问和修改字符串 (Accessing and Modifying a String)
## 访问和修改字符串 {#accessing-and-modifying-a-string}
你可以通过字符串的属性和方法来访问和修改它,当然也可以用下标语法完成。
<a name="string_indices"></a>
### 字符串索引 (String Indices)
### 字符串索引 {#string-indices}
每一个`String`值都有一个关联的索引(*index*)类型,`String.Index`,它对应着字符串中的每一个`Character`的位置。
每一个 `String` 值都有一个关联的索引*index*类型,`String.Index`,它对应着字符串中的每一个 `Character` 的位置。
前面提到,不同的字符可能会占用不同数量的内存空间,所以要知道`Character`的确定位置,就必须从`String`开头遍历每一个 Unicode 标量直到结尾。因此Swift 的字符串不能用整数(integer)做索引。
前面提到,不同的字符可能会占用不同数量的内存空间,所以要知道 `Character` 的确定位置,就必须从 `String` 开头遍历每一个 Unicode 标量直到结尾。因此Swift 的字符串不能用整数integer做索引。
使用`startIndex`属性可以获取一个`String`的第一个`Character`的索引。使用`endIndex`属性可以获取最后一个`Character`的后一个位置的索引。因此,`endIndex`属性不能作为一个字符串的有效下标。如果`String`是空串,`startIndex``endIndex`是相等的。
使用 `startIndex` 属性可以获取一个 `String` 的第一个 `Character` 的索引。使用 `endIndex` 属性可以获取最后一个 `Character` 的后一个位置的索引。因此,`endIndex` 属性不能作为一个字符串的有效下标。如果 `String` 是空串,`startIndex``endIndex` 是相等的。
通过调用 `String``index(before:)``index(after:)` 方法,可以立即得到前面或后面的一个索引。还可以通过调用 `index(_:offsetBy:)` 方法来获取对应偏移量的索引,这种方式可以避免多次调用 `index(before:)``index(after:)` 方法。
通过调用 `String``index(before:)``index(after:)` 方法,可以立即得到前面或后面的一个索引。还可以通过调用 `index(_:offsetBy:)` 方法来获取对应偏移量的索引,这种方式可以避免多次调用 `index(before:)``index(after:)` 方法。
你可以使用下标语法来访问 `String` 特定索引的 `Character`
@ -360,29 +387,29 @@ greeting[greeting.endIndex] // error
greeting.index(after: endIndex) // error
```
使用 `characters` 属性的 `indices` 属性会创建一个包含全部索引的范围(`Range`),用来在一个字符串中访问单个字符。
使用 `indices` 属性会创建一个包含全部索引的范围`Range`,用来在一个字符串中访问单个字符。
```swift
for index in greeting.characters.indices {
for index in greeting.indices {
print("\(greeting[index]) ", terminator: "")
}
// 打印输出 "G u t e n T a g ! "
// 打印输出G u t e n T a g !
```
> 注意
> 您可以使用 `startIndex` 和 `endIndex` 属性或者 `index(before:)` 、`index(after:)` 和 `index(_:offsetBy:)` 方法在任意一个确认的并遵循 `Collection` 协议的类型里面,如上文所示是使用在 `String` 中,您也可以使用在 `Array`、`Dictionary` 和 `Set`中。
> 注意
>
> 你可以使用 `startIndex` 和 `endIndex` 属性或者 `index(before:)` 、`index(after:)` 和 `index(_:offsetBy:)` 方法在任意一个确认的并遵循 `Collection` 协议的类型里面,如上文所示是使用在 `String` 中,你也可以使用在 `Array`、`Dictionary` 和 `Set` 中。
<a name="inserting_and_removing"></a>
### 插入和删除 (Inserting and Removing)
### 插入和删除 {#inserting-and-removing}
调用 `insert(_:atIndex:)` 方法可以在一个字符串的指定索引插入一个字符,调用 `insert(contentsOf:at:)` 方法可以在一个字符串的指定索引插入一个段字符串。
调用 `insert(_:at:)` 方法可以在一个字符串的指定索引插入一个字符,调用 `insert(contentsOf:at:)` 方法可以在一个字符串的指定索引插入一个段字符串。
```swift
var welcome = "hello"
welcome.insert("!", at: welcome.endIndex)
// welcome 变量现在等于 "hello!"
welcome.insert(contentsOf:" there".characters, at: welcome.index(before: welcome.endIndex))
welcome.insert(contentsOf:" there", at: welcome.index(before: welcome.endIndex))
// welcome 变量现在等于 "hello there!"
```
@ -391,25 +418,47 @@ welcome.insert(contentsOf:" there".characters, at: welcome.index(before: welcome
```swift
welcome.remove(at: welcome.index(before: welcome.endIndex))
// welcome 现在等于 "hello there"
let range = welcome.index(welcome.endIndex, offsetBy: -6)..<welcome.endIndex
welcome.removeSubrange(range)
// welcome 现在等于 "hello"
```
> 注意
> 您可以使用 `insert(_:at:)`、`insert(contentsOf:at:)`、`remove(at:)` 和 `removeSubrange(_:)` 方法在任意一个确认的并遵循 `RangeReplaceableCollection` 协议的类型里面,如上文所示是使用在 `String` 中,您也可以使用在 `Array`、`Dictionary` 和 `Set` 中。
> 注意
>
> 你可以使用 `insert(_:at:)`、`insert(contentsOf:at:)`、`remove(at:)` 和 `removeSubrange(_:)` 方法在任意一个确认的并遵循 `RangeReplaceableCollection` 协议的类型里面,如上文所示是使用在 `String` 中,你也可以使用在 `Array`、`Dictionary` 和 `Set` 中。
## 子字符串 {#substrings}
<a name="comparing_strings"></a>
## 比较字符串 (Comparing Strings)
当你从字符串中获取一个子字符串 —— 例如,使用下标或者 `prefix(_:)` 之类的方法 —— 就可以得到一个 `SubString` 的实例,而非另外一个 `String`。Swift 里的 `SubString` 绝大部分函数都跟 `String` 一样,意味着你可以使用同样的方式去操作 `SubString``String`。然而,跟 `String` 不同的是,你只有在短时间内需要操作字符串时,才会使用 `SubString`。当你需要长时间保存结果时,就把 `SubString` 转化为 `String` 的实例:
```swift
let greeting = "Hello, world!"
let index = greeting.firstIndex(of: ",") ?? greeting.endIndex
let beginning = greeting[..<index]
// beginning 的值为 "Hello"
// 把结果转化为 String 以便长期存储。
let newString = String(beginning)
```
就像 `String`,每一个 `SubString` 都会在内存里保存字符集。而 `String``SubString` 的区别在于性能优化上,`SubString` 可以重用原 `String` 的内存空间,或者另一个 `SubString` 的内存空间(`String` 也有同样的优化,但如果两个 `String` 共享内存的话,它们就会相等)。这一优化意味着你在修改 `String``SubString` 之前都不需要消耗性能去复制内存。就像前面说的那样,`SubString` 不适合长期存储 —— 因为它重用了原 `String` 的内存空间,原 `String` 的内存空间必须保留直到它的 `SubString` 不再被使用为止。
上面的例子,`greeting` 是一个 `String`,意味着它在内存里有一片空间保存字符集。而由于 `beginning``greeting``SubString`,它重用了 `greeting` 的内存空间。相反,`newString` 是一个 `String` —— 它是使用 `SubString` 创建的,拥有一片自己的内存空间。下面的图展示了他们之间的关系:
![](https://docs.swift.org/swift-book/_images/stringSubstring_2x.png)
> 注意
>
> `String` 和 `SubString` 都遵循 `StringProtocol<//apple_ref/swift/intf/s:s14StringProtocolP>` 协议,这意味着操作字符串的函数使用 `StringProtocol` 会更加方便。你可以传入 `String` 或 `SubString` 去调用函数。
## 比较字符串 {#comparing-strings}
Swift 提供了三种方式来比较文本值:字符串字符相等、前缀相等和后缀相等。
<a name="string_and_character_equality"></a>
### 字符串/字符相等 (String and Character Equality)
### 字符串/字符相等 {#string-and-character-equality}
字符串/字符可以用等于操作符(`==`)和不等于操作符(`!=`),详细描述在[比较运算符](./02_Basic_Operators.html#comparison_operators)
字符串/字符可以用等于操作符`==`和不等于操作符`!=`,详细描述在 [比较运算符](./02_Basic_Operators.md#comparison_operators)
```swift
let quotation = "We're a lot alike, you and I."
@ -417,12 +466,12 @@ let sameQuotation = "We're a lot alike, you and I."
if quotation == sameQuotation {
print("These two strings are considered equal")
}
// 打印输出 "These two strings are considered equal"
// 打印输出These two strings are considered equal
```
如果两个字符串(或者两个字符)的可扩展的字形群集是标准相等,那就认为它们是相等的。在这个情况下,即使可扩展的字形群集是有不同的 Unicode 标量构成的,只要它们有同样的语言意义和外观,就认为它们标准相等。
如果两个字符串(或者两个字符)的可扩展的字形群集是标准相等,那就认为它们是相等的。只要可扩展的字形群集有同样的语言意义和外观认为它们标准相等,即使它们是由不同的 Unicode 标量构成
例如,`LATIN SMALL LETTER E WITH ACUTE`(`U+00E9`)就是标准相等于`LATIN SMALL LETTER E`(`U+0065`)后面加上`COMBINING ACUTE ACCENT`(`U+0301`)。这两个字符群集都是表示字符`é`的有效方式,所以它们被认为是标准相等的:
例如,`LATIN SMALL LETTER E WITH ACUTE`(`U+00E9`)就是标准相等于 `LATIN SMALL LETTER E`(`U+0065`)后面加上 `COMBINING ACUTE ACCENT`(`U+0301`)。这两个字符群集都是表示字符 `é` 的有效方式,所以它们被认为是标准相等的:
```swift
// "Voulez-vous un café?" 使用 LATIN SMALL LETTER E WITH ACUTE
@ -434,10 +483,10 @@ let combinedEAcuteQuestion = "Voulez-vous un caf\u{65}\u{301}?"
if eAcuteQuestion == combinedEAcuteQuestion {
print("These two strings are considered equal")
}
// 打印输出 "These two strings are considered equal"
// 打印输出These two strings are considered equal
```
相反,英语中的`LATIN CAPITAL LETTER A`(`U+0041`,或者`A`)不等于俄语中的`CYRILLIC CAPITAL LETTER A`(`U+0410`,或者`A`)。两个字符看着是一样的,但却有不同的语言意义:
相反,英语中的 `LATIN CAPITAL LETTER A`(`U+0041`,或者 `A`)不等于俄语中的 `CYRILLIC CAPITAL LETTER A`(`U+0410`,或者 `A`)。两个字符看着是一样的,但却有不同的语言意义:
```swift
let latinCapitalLetterA: Character = "\u{41}"
@ -447,17 +496,16 @@ let cyrillicCapitalLetterA: Character = "\u{0410}"
if latinCapitalLetterA != cyrillicCapitalLetterA {
print("These two characters are not equivalent")
}
// 打印 "These two characters are not equivalent"
// 打印These two characters are not equivalent
```
> 注意
> 在 Swift 中,字符串和字符并不区分地域(not locale-sensitive)。
> 注意
>
> 在 Swift 中字符串和字符并不区分地域not locale-sensitive
### 前缀/后缀相等 {#prefix-and-suffix-equality}
<a name="prefix_and_suffix_equality"></a>
### 前缀/后缀相等 (Prefix and Suffix Equality)
通过调用字符串的`hasPrefix(_:)`/`hasSuffix(_:)`方法来检查字符串是否拥有特定前缀/后缀,两个方法均接收一个`String`类型的参数,并返回一个布尔值。
通过调用字符串的 `hasPrefix(_:)`/`hasSuffix(_:)` 方法来检查字符串是否拥有特定前缀/后缀,两个方法均接收一个 `String` 类型的参数,并返回一个布尔值。
下面的例子以一个字符串数组表示莎士比亚话剧《罗密欧与朱丽叶》中前两场的场景位置:
@ -477,7 +525,7 @@ let romeoAndJuliet = [
]
```
可以调用`hasPrefix(_:)`方法来计算话剧中第一幕的场景数:
可以调用 `hasPrefix(_:)` 方法来计算话剧中第一幕的场景数:
```swift
var act1SceneCount = 0
@ -487,10 +535,10 @@ for scene in romeoAndJuliet {
}
}
print("There are \(act1SceneCount) scenes in Act 1")
// 打印输出 "There are 5 scenes in Act 1"
// 打印输出There are 5 scenes in Act 1
```
相似地,可以用`hasSuffix(_:)`方法来计算发生在不同地方的场景数:
相似地,可以用 `hasSuffix(_:)` 方法来计算发生在不同地方的场景数:
```swift
var mansionCount = 0
@ -503,39 +551,34 @@ for scene in romeoAndJuliet {
}
}
print("\(mansionCount) mansion scenes; \(cellCount) cell scenes")
// 打印输出 "6 mansion scenes; 2 cell scenes"
// 打印输出6 mansion scenes; 2 cell scenes
```
> 注意
> `hasPrefix(_:)`和`hasSuffix(_:)`方法都是在每个字符串中逐字符比较其可扩展的字符群集是否标准相等,详细描述在[字符串/字符相等](#string_and_character_equality)。
> 注意
>
> `hasPrefix(_:)` 和 `hasSuffix(_:)` 方法都是在每个字符串中逐字符比较其可扩展的字符群集是否标准相等,详细描述在 [字符串/字符相等](#string_and_character_equality)。
## 字符串的 Unicode 表示形式 {#unicode-representations-of-strings}
<a name="unicode_representations_of_strings"></a>
## 字符串的 Unicode 表示形式Unicode Representations of Strings
当一个 Unicode 字符串被写进文本文件或者其他储存时,字符串中的 Unicode 标量会用 Unicode 定义的几种 `编码格式`encoding forms编码。每一个字符串中的小块编码都被称 `代码单元`code units。这些包括 UTF-8 编码格式(编码字符串为 8 位的代码单元), UTF-16 编码格式(编码字符串位 16 位的代码单元),以及 UTF-32 编码格式编码字符串32位的代码单元
当一个 Unicode 字符串被写进文本文件或者其他储存时,字符串中的 Unicode 标量会用 Unicode 定义的几种`编码格式`encoding forms编码。每一个字符串中的小块编码都被称`代码单元`code units。这些包括 UTF-8 编码格式编码字符串为8位的代码单元 UTF-16 编码格式编码字符串位16位的代码单元以及 UTF-32 编码格式编码字符串32位的代码单元
Swift 提供了几种不同的方式来访问字符串的 Unicode 表示形式。
您可以利用`for-in`来对字符串进行遍历,从而以 Unicode 可扩展的字符群集的方式访问每一个`Character`值。
该过程在 [使用字符](#working_with_characters) 中进行了描述。
Swift 提供了几种不同的方式来访问字符串的 Unicode 表示形式。你可以利用 `for-in` 来对字符串进行遍历,从而以 Unicode 可扩展的字符群集的方式访问每一个 `Character` 值。该过程在 [使用字符](#working_with_characters) 中进行了描述
另外,能够以其他三种 Unicode 兼容的方式访问字符串的值:
* UTF-8 代码单元集合 (利用字符串的`utf8`属性进行访问)
* UTF-16 代码单元集合 (利用字符串的`utf16`属性进行访问)
* 21位的 Unicode 标量值集合,也就是字符串的 UTF-32 编码格式 (利用字符串的`unicodeScalars`属性进行访问)
* UTF-8 代码单元集合利用字符串的 `utf8` 属性进行访问
* UTF-16 代码单元集合利用字符串的 `utf16` 属性进行访问
* 21 位的 Unicode 标量值集合,也就是字符串的 UTF-32 编码格式利用字符串的 `unicodeScalars` 属性进行访问
下面由`D`,`o`,`g`,`‼`(`DOUBLE EXCLAMATION MARK`, Unicode 标量 `U+203C`)和`🐶`(`DOG FACE`Unicode 标量为`U+1F436`)组成的字符串中的每一个字符代表着一种不同的表示:
下面由 `D`,`o`,`g`,`‼`(`DOUBLE EXCLAMATION MARK`, Unicode 标量 `U+203C`)和 `🐶`(`DOG FACE`Unicode 标量为 `U+1F436`)组成的字符串中的每一个字符代表着一种不同的表示:
```swift
let dogString = "Dog‼🐶"
```
<a name="UTF-8_representation"></a>
### UTF-8 表示
### UTF-8 表示 {#UTF-8-representation}
可以通过遍历`String``utf8`属性来访问它的`UTF-8`表示。
其为`String.UTF8View`类型的属性,`UTF8View`是无符号8位 (`UInt8`) 值的集合,每一个`UInt8`值都是一个字符的 UTF-8 表示:
可以通过遍历 `String``utf8` 属性来访问它的 `UTF-8` 表示。其为 `String.UTF8View` 类型的属性,`UTF8View` 是无符号 8 位(`UInt8`)值的集合,每一个 `UInt8` 值都是一个字符的 UTF-8 表示:
<table style='text-align:center'>
<tr height="77">
@ -574,7 +617,6 @@ let dogString = "Dog‼🐶"
</tr>
</table>
```swift
for codeUnit in dogString.utf8 {
print("\(codeUnit) ", terminator: "")
@ -583,16 +625,11 @@ print("")
// 68 111 103 226 128 188 240 159 144 182
```
上面的例子中前三个10进制`codeUnit`值 (`68`, `111`, `103`) 代表了字符`D``o``g`,它们的 UTF-8 表示与 ASCII 表示相同。
接下来的三个10进制`codeUnit`值 (`226`, `128`, `188`) 是`DOUBLE EXCLAMATION MARK`的3字节 UTF-8 表示。
最后的四个`codeUnit`值 (`240`, `159`, `144`, `182`) 是`DOG FACE`的4字节 UTF-8 表示。
上面的例子中,前三个 10 进制 `codeUnit` 值(`68``111``103`代表了字符 `D``o` `g`,它们的 UTF-8 表示与 ASCII 表示相同。接下来的三个 10 进制 `codeUnit` 值(`226``128``188`)是 `DOUBLE EXCLAMATION MARK` 的3字节 UTF-8 表示。最后的四个 `codeUnit` 值(`240``159``144``182`)是 `DOG FACE` 的4字节 UTF-8 表示。
### UTF-16 表示 {#UTF-16-representation}
<a name="UTF-16_representation"></a>
### UTF-16 表示
您可以通过遍历`String``utf16`属性来访问它的`UTF-16`表示。
其为`String.UTF16View`类型的属性,`UTF16View`是无符号16位 (`UInt16`) 值的集合,每一个`UInt16`都是一个字符的 UTF-16 表示:
你可以通过遍历 `String``utf16` 属性来访问它的 `UTF-16` 表示。其为 `String.UTF16View` 类型的属性,`UTF16View` 是无符号16位`UInt16`)值的集合,每一个 `UInt16` 都是一个字符的 UTF-16 表示:
<table style='text-align:center'>
<tr height="77">
@ -623,7 +660,6 @@ print("")
</tr>
</table>
```swift
for codeUnit in dogString.utf16 {
print("\(codeUnit) ", terminator: "")
@ -632,22 +668,17 @@ print("")
// 68 111 103 8252 55357 56374
```
同样,前三个`codeUnit`值 (`68`, `111`, `103`) 代表了字符`D``o``g`,它们的 UTF-16 代码单元和 UTF-8 完全相同(因为这些 Unicode 标量表示 ASCII 字符)。
同样,前三个 `codeUnit` 值(`68``111``103`代表了字符 `D``o``g`,它们的 UTF-16 代码单元和 UTF-8 完全相同(因为这些 Unicode 标量表示 ASCII 字符)。
第四个`codeUnit`值 (`8252`) 是一个等于十六进制`203C`的的十进制值。这个代表了`DOUBLE EXCLAMATION MARK`字符的 Unicode 标量值`U+203C`。这个字符在 UTF-16 中可以用一个代码单元表示。
第四个 `codeUnit` 值(`8252`是一个等于十六进制 `203C` 的的十进制值。这个代表了 `DOUBLE EXCLAMATION MARK` 字符的 Unicode 标量值 `U+203C`。这个字符在 UTF-16 中可以用一个代码单元表示。
第五和第六个`codeUnit`值 (`55357``56374`) 是`DOG FACE`字符的 UTF-16 表示。
第一个值为`U+D83D`(十进制值为`55357`),第二个值为`U+DC36`(十进制值为`56374`)。
第五和第六个 `codeUnit` 值(`55357``56374`)是 `DOG FACE` 字符的 UTF-16 表示。第一个值为 `U+D83D`(十进制值为 `55357`),第二个值为 `U+DC36`(十进制值为 `56374`)。
<a name="unicode_scalars_representation"></a>
### Unicode 标量表示 (Unicode Scalars Representation)
### Unicode 标量表示 {#unicode-scalars-representation}
可以通过遍历`String`值的`unicodeScalars`属性来访问它的 Unicode 标量表示。
其为`UnicodeScalarView`类型的属性,`UnicodeScalarView``UnicodeScalar`类型的值的集合。
`UnicodeScalar`是21位的 Unicode 代码点。
每一个`UnicodeScalar`拥有一个`value`属性可以返回对应的21位数值`UInt32`来表示:
可以通过遍历 `String` 值的 `unicodeScalars` 属性来访问它的 Unicode 标量表示。其为 `UnicodeScalarView` 类型的属性,`UnicodeScalarView``UnicodeScalar` 类型的值的集合。
每一个 `UnicodeScalar` 拥有一个 `value` 属性,可以返回对应的 21 位数值,用 `UInt32` 来表示:
<table style='text-align:center'>
<tr height="77">
@ -676,7 +707,6 @@ print("")
</tr>
</table>
```swift
for scalar in dogString.unicodeScalars {
print("\(scalar.value) ", terminator: "")
@ -685,12 +715,13 @@ print("")
// 68 111 103 8252 128054
```
前三个`UnicodeScalar`值(`68`, `111`, `103`)的`value`属性仍然代表字符`D``o``g`
第四个`codeUnit`值(`8252`)仍然是一个等于十六进制`203C`的十进制值。这个代表了`DOUBLE EXCLAMATION MARK`字符的 Unicode 标量`U+203C`
前三个 `UnicodeScalar` 值(`68``111``103`)的 `value` 属性仍然代表字符 `D``o``g`
五个`UnicodeScalar`值的`value`属性,`128054`是一个十六进制`1F436`的十进制表示。其等同于`DOG FACE`的 Unicode 标量`U+1F436`
四个 `codeUnit` 值(`8252`)仍然是一个等于十六进制 `203C` 的十进制值。这个代表了 `DOUBLE EXCLAMATION MARK` 字符的 Unicode 标量 `U+203C`
作为查询它们的`value`属性的一种替代方法,每个`UnicodeScalar`值也可以用来构建一个新的`String`值,比如在字符串插值中使用:
第五个 `UnicodeScalar` 值的 `value` 属性,`128054`,是一个十六进制 `1F436` 的十进制表示。其等同于 `DOG FACE` 的 Unicode 标量 `U+1F436`
作为查询它们的 `value` 属性的一种替代方法,每个 `UnicodeScalar` 值也可以用来构建一个新的 `String` 值,比如在字符串插值中使用:
```swift
for scalar in dogString.unicodeScalars {

File diff suppressed because it is too large Load Diff

View File

@ -1,78 +1,16 @@
# 控制流Control Flow
-----------------
# 控制流
> 1.0
> 翻译:[vclwei](https://github.com/vclwei), [coverxit](https://github.com/coverxit), [NicePiao](https://github.com/NicePiao)
> 校对:[coverxit](https://github.com/coverxit), [stanzhai](https://github.com/stanzhai)
Swift 提供了多种流程控制结构,包括可以多次执行任务的 `while` 循环,基于特定条件选择执行不同代码分支的 `if``guard``switch` 语句,还有控制流程跳转到其他代码位置的 `break``continue` 语句。
> 2.0
> 翻译+校对:[JackAlan](https://github.com/AlanMelody)
Swift 还提供了 `for-in` 循环用来更简单地遍历数组Array字典Dictionary区间Range字符串String和其他序列类型。
> 2.1
> 翻译:[Prayer](https://github.com/futantan)
> 校对:[shanks](http://codebuild.me)
Swift 的 `switch` 语句比许多类 C 语言要更加强大。case 还可以匹配很多不同的模式包括范围匹配元组tuple和特定类型匹配。`switch` 语句的 case 中匹配的值可以声明为临时常量或变量,在 case 作用域内使用,也可以配合 `where` 来描述更复杂的匹配条件。
> 2.2
> 翻译:[LinusLing](https://github.com/linusling)
> 校对:[SketchK](https://github.com/SketchK)
## For-In 循环 {#for-in-loops}
> 3.0
> 翻译:[Realank](https://github.com/realank) 2016-09-13
你可以使用 `for-in` 循环来遍历一个集合中的所有元素,例如数组中的元素、范围内的数字或者字符串中的字符。
本页包含内容:
- [For-In 循环](#for_in_loops)
- [While 循环](#while_loops)
- [条件语句](#conditional_statement)
- [控制转移语句Control Transfer Statements](#control_transfer_statements)
- [提前退出](#early_exit)
- [检测 API 可用性](#checking_api_availability)
Swift提供了多种流程控制结构包括可以多次执行任务的`while`循环,基于特定条件选择执行不同代码分支的`if``guard``switch`语句,还有控制流程跳转到其他代码位置的`break``continue`语句。
Swift 还提供了`for-in`循环用来更简单地遍历数组array字典dictionary区间range字符串string和其他序列类型。
Swift 的`switch`语句比 C 语言中更加强大。在 C 语言中,如果某个 case 不小心漏写了`break`,这个 case 就会贯穿至下一个 caseSwift 无需写`break`所以不会发生这种贯穿的情况。case 还可以匹配很多不同的模式包括间隔匹配interval match元组tuple和转换到特定类型。`switch`语句的 case 中匹配的值可以绑定成临时常量或变量在case体内使用也可以用`where`来描述更复杂的匹配条件。
<a name="for_in_loops"></a>
## For-In 循环
你可以使用`for-in`循环来遍历一个集合中的所有元素,例如数字范围、数组中的元素或者字符串中的字符。
下面的例子用来输出乘 5 乘法表前面一部分内容:
```swift
for index in 1...5 {
print("\(index) times 5 is \(index * 5)")
}
// 1 times 5 is 5
// 2 times 5 is 10
// 3 times 5 is 15
// 4 times 5 is 20
// 5 times 5 is 25
```
例子中用来进行遍历的元素是使用闭区间操作符(`...`)表示的从`1``5`的数字区间。`index`被赋值为闭区间中的第一个数字(`1`),然后循环中的语句被执行一次。在本例中,这个循环只包含一个语句,用来输出当前`index`值所对应的乘 5 乘法表的结果。该语句执行后,`index`的值被更新为闭区间中的第二个数字(`2`),之后`print(_:separator:terminator:)`函数会再执行一次。整个过程会进行到闭区间结尾为止。
上面的例子中,`index`是一个每次循环遍历开始时被自动赋值的常量。这种情况下,`index`在使用前不需要声明,只需要将它包含在循环的声明中,就可以对其进行隐式声明,而无需使用`let`关键字声明。
如果你不需要区间序列内每一项的值,你可以使用下划线(`_`)替代变量名来忽略这个值:
```swift
let base = 3
let power = 10
var answer = 1
for _ in 1...power {
answer *= base
}
print("\(base) to the power of \(power) is \(answer)")
// 输出 "3 to the power of 10 is 59049"
```
这个例子计算 base 这个数的 power 次幂(本例中,是`3``10`次幂),从`1``3``0`次幂)开始做`3`的乘法, 进行`10`次,使用`1``10`的闭区间循环。这个计算并不需要知道每一次循环中计数器具体的值,只需要执行了正确的循环次数即可。下划线符号`_`(替代循环中的变量)能够忽略当前值,并且不提供循环遍历时对值的访问。
使用`for-in`遍历一个数组所有元素:
以下例子使用 `for-in` 遍历一个数组所有元素:
```swift
let names = ["Anna", "Alex", "Brian", "Jack"]
@ -85,44 +23,102 @@ for name in names {
// Hello, Jack!
```
你也可以通过遍历一个字典来访问它的键值对。遍历字典时,字典的每项元素会以`(key, value)`元组的形式返回,你可以在`for-in`循环中使用显式的常量名称来解读`(key, value)`元组。下面的例子中,字典的键key解读为常量`animalName`,字典的值会被解读为常量`legCount`
你也可以通过遍历一个字典来访问它的键值对。遍历字典时,字典的每项元素会以 `(key, value)` 元组的形式返回,你可以在 `for-in` 循环中使用显式的常量名称来解读 `(key, value)` 元组。下面的例子中,字典的键声明会为 `animalName` 常量,字典的值会声明为 `legCount` 常量
```swift
let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
for (animalName, legCount) in numberOfLegs {
print("\(animalName)s have \(legCount) legs")
}
// ants have 6 legs
// cats have 4 legs
// ants have 6 legs
// spiders have 8 legs
```
字典元素的遍历顺序和插入顺序可能不同,字典的内容在内部是无序的,所以遍历元素时不能保证顺序。关于数组和字典,详情参见[集合类型](./04_Collection_Types.html)。
字典的内容理论上是无序的,遍历元素时的顺序是无法确定的。将元素插入字典的顺序并不会决定它们被遍历的顺序。关于数组和字典的细节,参见 [集合类型](./04_Collection_Types.md)。
<a name="while_loops"></a>
## While 循环
`for-in` 循环还可以使用数字范围。下面的例子用来输出乘法表的一部分内容:
`while`循环会一直运行一段语句直到条件变成`false`。这类循环适合使用在第一次迭代前迭代次数未知的情况下。Swift 提供两种`while`循环形式:
```swift
for index in 1...5 {
print("\(index) times 5 is \(index * 5)")
}
// 1 times 5 is 5
// 2 times 5 is 10
// 3 times 5 is 15
// 4 times 5 is 20
// 5 times 5 is 25
```
* `while`循环,每次在循环开始时计算条件是否符合;
* `repeat-while`循环,每次在循环结束时计算条件是否符合。
例子中用来进行遍历的元素是使用闭区间操作符(`...`)表示的从 `1``5` 的数字区间。`index` 被赋值为闭区间中的第一个数字(`1`),然后循环中的语句被执行一次。在本例中,这个循环只包含一个语句,用来输出当前 `index` 值所对应的乘 5 乘法表的结果。该语句执行后,`index` 的值被更新为闭区间中的第二个数字(`2`),之后 `print(_:separator:terminator:)` 函数会再执行一次。整个过程会进行到闭区间结尾为止。
<a name="while"></a>
###While
上面的例子中,`index` 是一个每次循环遍历开始时被自动赋值的常量。这种情况下,`index` 在使用前不需要声明,只需要将它包含在循环的声明中,就可以对其进行隐式声明,而无需使用 `let` 关键字声明。
`while`循环从计算一个条件开始。如果条件为`true`,会重复运行一段语句,直到条件变为`false`
如果你不需要区间序列内每一项的值,你可以使用下划线(`_`)替代变量名来忽略这个值:
```swift
let base = 3
let power = 10
var answer = 1
for _ in 1...power {
answer *= base
}
print("\(base) to the power of \(power) is \(answer)")
// 输出“3 to the power of 10 is 59049”
```
这个例子计算 base 这个数的 power 次幂(本例中,是 `3``10` 次幂),从 `1``3``0` 次幂)开始做 `3` 的乘法, 进行 `10` 次,使用 `1``10` 的闭区间循环。这个计算并不需要知道每一次循环中计数器具体的值,只需要执行了正确的循环次数即可。下划线符号 `_` (替代循环中的变量)能够忽略当前值,并且不提供循环遍历时对值的访问。
在某些情况下,你可能不想使用包括两个端点的闭区间。想象一下,你在一个手表上绘制分钟的刻度线。总共 `60` 个刻度,从 `0` 分开始。使用半开区间运算符(`..<`)来表示一个左闭右开的区间。有关区间的更多信息,请参阅 [区间运算符](./02_Basic_Operators.md#range_operators)。
```swift
let minutes = 60
for tickMark in 0..<minutes {
// 每一分钟都渲染一个刻度线60次
}
```
一些用户可能在其 UI 中可能需要较少的刻度。他们可以每 5 分钟作为一个刻度。使用 `stride(from:to:by:)` 函数跳过不需要的标记。
```swift
let minuteInterval = 5
for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {
// 每5分钟渲染一个刻度线0, 5, 10, 15 ... 45, 50, 55
}
```
可以在闭区间使用 `stride(from:through:by:)` 起到同样作用:
```swift
let hours = 12
let hourInterval = 3
for tickMark in stride(from: 3, through: hours, by: hourInterval) {
// 每3小时渲染一个刻度线3, 6, 9, 12
}
```
## While 循环 {#while-loops}
`while` 循环会一直运行一段语句直到条件变成 `false`。这类循环适合使用在第一次迭代前迭代次数未知的情况下。Swift 提供两种 `while` 循环形式:
* `while` 循环,每次在循环开始时计算条件是否符合;
* `repeat-while` 循环,每次在循环结束时计算条件是否符合。
### While {#while}
`while` 循环从计算一个条件开始。如果条件为 `true`,会重复运行一段语句,直到条件变为 `false`
下面是 `while` 循环的一般格式:
```
while condition {
```swift
while condition {
statements
}
```
下面的例子来玩一个叫做蛇和梯子的小游戏,也叫做滑道和梯子:
下面的例子来玩一个叫做*蛇和梯子*也叫做*滑道和梯子*)的小游戏
![image](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/snakesAndLadders_2x.png)
![image](https://docs.swift.org/swift-book/_images/snakesAndLadders_2x.png)
游戏的规则如下:
@ -131,7 +127,7 @@ while condition {
* 如果在某轮结束,你移动到了梯子的底部,可以顺着梯子爬上去;
* 如果在某轮结束,你移动到了蛇的头部,你会顺着蛇的身体滑下去。
游戏盘面可以使用一个`Int`数组来表达。数组的长度由一个`finalSquare`常量储存,用来初始化数组和检测最终胜利条件。游戏盘面由 26 个 `Int` 0 值初始化,而不是 25 个(由`0``25`,一共 26 个):
游戏盘面可以使用一个 `Int` 数组来表达。数组的长度由一个 `finalSquare` 常量储存,用来初始化数组和检测最终胜利条件。游戏盘面由 26 个 `Int` 0 值初始化,而不是 25 个(由 `0``25`,一共 26 个):
```swift
let finalSquare = 25
@ -145,7 +141,7 @@ board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
```
3 号方格是梯子的底部,会让你向上移动到 11 号方格,我们使用`board[03]`等于`+08`(来表示`11``3`之间的差值)。使用一元正运算符(`+i`是为了和一元负运算符(`-i`对称,为了让盘面代码整齐,小于 10 的数字都使用 0 补齐(这些风格上的调整都不是必的,只是为了让代码看起来更加整洁)。
3 号方格是梯子的底部,会让你向上移动到 11 号方格,我们使用 `board[03]` 等于 `+08`(来表示 `11``3` 之间的差值)。为了对齐语句,这里使用一元正运算符(`+i`)和一元负运算符(`-i`并且小于 10 的数字都使用 0 补齐(这些语法的技巧不是必的,只是为了让代码看起来更加整洁)。
玩家由左下角空白处编号为 0 的方格开始游戏。玩家第一次掷骰子后才会进入游戏盘面:
@ -166,27 +162,27 @@ while square < finalSquare {
print("Game over!")
```
本例中使用了最简单的方法来模拟掷骰子。 `diceRoll`的值并不是一个随机数,而是以`0`为初始值,之后每一次`while`循环,`diceRoll`的值增加 1 ,然后检测是否超出了最大值。当`diceRoll`的值等于 7 时,就超过了骰子的最大值,会被重置为`1`。所以`diceRoll`的取值顺序会一直是 `1` `2``3``4``5``6``1``2` 等。
本例中使用了最简单的方法来模拟掷骰子。`diceRoll` 的值并不是一个随机数,而是以 `0` 为初始值,之后每一次 `while` 循环,`diceRoll` 的值增加 1 ,然后检测是否超出了最大值。当 `diceRoll` 的值等于 7 时,就超过了骰子的最大值,会被重置为 `1`。所以 `diceRoll` 的取值顺序会一直是 `1``2``3``4``5``6``1``2` 等。
掷完骰子后,玩家向前移动`diceRoll`个方格,如果玩家移动超过了第 25 个方格,这个时候游戏将会结束,为了应对这种情况,代码会首先判断`square`的值是否小于`board``count`属性,只有小于才会在`board[square]`上增加`square`,来向前或向后移动(遇到了梯子或者蛇)。
掷完骰子后,玩家向前移动 `diceRoll` 个方格,如果玩家移动超过了第 25 个方格,这个时候游戏将会结束,为了应对这种情况,代码会首先判断 `square` 的值是否小于 `board``count` 属性,只有小于才会在 `board[square]` 上增加 `square`,来向前或向后移动(遇到了梯子或者蛇)。
> 注意
> 如果没有这个检测(`square < board.count``board[square]`可能会越界访问`board`数组,导致错误。如果`square`等于`26` 代码会去尝试访问`board[26]`,超过数组的长度。
> 注意
>
> 如果没有这个检测(`square < board.count``board[square]` 可能会越界访问 `board` 数组,导致运行时错误。
当本轮`while`循环运行完毕,会再检测循环条件是否需要再运行一次循环。如果玩家移动到或者超过第 25 个方格,循环条件结果为`false`,此时游戏结束。
当本轮 `while` 循环运行完毕,会再检测循环条件是否需要再运行一次循环。如果玩家移动到或者超过第 25 个方格,循环条件结果为 `false`,此时游戏结束。
`while` 循环比较适合本例中的这种情况,因为在 `while` 循环开始时,我们并不知道游戏要跑多久,只有在达成指定条件时循环才会结束。
### Repeat-While {#repeat-while}
<a name="repeat_while"></a>
###Repeat-While
`while` 循环的另外一种形式是 `repeat-while`,它和 `while` 的区别是在判断循环条件之前,先执行一次循环的代码块。然后重复循环直到条件为 `false`
`while`循环的另外一种形式是`repeat-while`,它和`while`的区别是在判断循环条件之前,先执行一次循环的代码块。然后重复循环直到条件为`false`
> 注意
>
> Swift 语言的 `repeat-while` 循环和其他语言中的 `do-while` 循环是类似的。
> 注意
> Swift语言的`repeat-while`循环和其他语言中的`do-while`循环是类似的。
下面是 `repeat-while`循环的一般格式:
下面是 `repeat-while` 循环的一般格式
```swift
repeat {
@ -194,9 +190,9 @@ repeat {
} while condition
```
还是蛇和梯子的游戏,使用`repeat-while`循环来替代`while`循环。`finalSquare``board``square``diceRoll`的值初始化同`while`循环时一样:
还是*蛇和梯子*的游戏,使用 `repeat-while` 循环来替代 `while` 循环。`finalSquare``board``square``diceRoll` 的值初始化同 `while` 循环时一样:
``` swift
```swift
let finalSquare = 25
var board = [Int](repeating: 0, count: finalSquare + 1)
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
@ -205,9 +201,9 @@ var square = 0
var diceRoll = 0
```
`repeat-while`的循环版本,循环中_第一步_就需要去检测是否在梯子或者蛇的方块上。没有梯子会让玩家直接上到第 25 个方格,所以玩家不会通过梯子直接赢得游戏。这样在循环开始时先检测是否踩在梯子或者蛇上是安全的。
`repeat-while` 的循环版本,循环中*第一步*就需要去检测是否在梯子或者蛇的方块上。没有梯子会让玩家直接上到第 25 个方格,所以玩家不会通过梯子直接赢得游戏。这样在循环开始时先检测是否踩在梯子或者蛇上是安全的。
游戏开始时,玩家在第 0 个方格上,`board[0]`一直等于 0 不会有什么影响:
游戏开始时,玩家在第 0 个方格上,`board[0]` 一直等于 0 不会有什么影响:
```swift
repeat {
@ -222,33 +218,31 @@ repeat {
print("Game over!")
```
检测完玩家是否踩在梯子或者蛇上之后,开始掷骰子,然后玩家向前移动`diceRoll`个方格,本轮循环结束。
检测完玩家是否踩在梯子或者蛇上之后,开始掷骰子,然后玩家向前移动 `diceRoll` 个方格,本轮循环结束。
循环条件(`while square < finalSquare`)和`while`方式相同,但是只会在循环结束后进行计算。在这个游戏中,`repeat-while`表现得比`while`循环更好。`repeat-while`方式会在条件判断`square`没有超出后直接运行`square += board[square]`,这种方式可以去掉`while`版本中的数组越界判断
循环条件(`while square < finalSquare`)和 `while` 方式相同,但是只会在循环结束后进行计算。在这个游戏中,`repeat-while` 表现得比 `while` 循环更好。`repeat-while` 方式会在条件判断 `square` 没有超出后直接运行 `square += board[square]`,这种方式可以比起前面 `while` 循环的版本,可以省去数组越界的检查
<a name="conditional_statement"></a>
## 条件语句
## 条件语句 {#conditional-statement}
根据特定的条件执行特定的代码通常是十分有用的。当错误发生时,你可能想运行额外的代码;或者,当值太大或太小时,向用户显示一条消息。要实现这些功能,你就需要使用*条件语句*。
Swift 提供两种类型的条件语句:`if`语句和`switch`语句。通常,当条件较为简单且可能的情况很少时,使用`if`语句。而`switch`语句更适用于条件较复杂、有更多排列组合的时候。并且`switch`在需要用到模式匹配pattern-matching的情况下会更有用。
Swift 提供两种类型的条件语句:`if` 语句和 `switch` 语句。通常,当条件较为简单且可能的情况很少时,使用 `if` 语句。而 `switch` 语句更适用于条件较复杂、有更多排列组合的时候。并且 `switch` 在需要用到模式匹配pattern-matching的情况下会更有用。
<a name="if"></a>
### If
### If {#if}
`if`语句最简单的形式就是只包含一个条件,只有该条件为`true`时,才执行相关代码:
`if` 语句最简单的形式就是只包含一个条件,只有该条件为 `true` 时,才执行相关代码:
```swift
var temperatureInFahrenheit = 30
if temperatureInFahrenheit <= 32 {
print("It's very cold. Consider wearing a scarf.")
}
// 输出 "It's very cold. Consider wearing a scarf."
// 输出It's very cold. Consider wearing a scarf.
```
上面的例子会判断温度是否小于等于 32 华氏度(水的冰点)。如果是,则打印一条消息;否则,不打印任何消息,继续执行`if`块后面的代码。
上面的例子会判断温度是否小于等于 32 华氏度(水的冰点)。如果是,则打印一条消息;否则,不打印任何消息,继续执行 `if` 块后面的代码。
当然,`if`语句允许二选一执行,叫做`else`从句。也就是当条件为`false`时,执行 *else 语句*
当然,`if` 语句允许二选一执行,叫做 `else` 从句。也就是当条件为 `false` 时,执行 *else 语句*
```swift
temperatureInFahrenheit = 40
@ -257,12 +251,12 @@ if temperatureInFahrenheit <= 32 {
} else {
print("It's not that cold. Wear a t-shirt.")
}
// 输出 "It's not that cold. Wear a t-shirt."
// 输出It's not that cold. Wear a t-shirt.
```
显然,这两条分支中总有一条会被执行。由于温度已升至 40 华氏度,不算太冷,没必要再围围巾。因此,`else`分支就被触发了。
显然,这两条分支中总有一条会被执行。由于温度已升至 40 华氏度,不算太冷,没必要再围围巾。因此,`else` 分支就被触发了。
你可以把多个`if`语句链接在一起,来实现更多分支:
你可以把多个 `if` 语句链接在一起,来实现更多分支:
```swift
temperatureInFahrenheit = 90
@ -273,12 +267,12 @@ if temperatureInFahrenheit <= 32 {
} else {
print("It's not that cold. Wear a t-shirt.")
}
// 输出 "It's really warm. Don't forget to wear sunscreen."
// 输出It's really warm. Don't forget to wear sunscreen.
```
在上面的例子中,额外的`if`语句用于判断是不是特别热。而最后的`else`语句被保留了下来,用于打印既不冷也不热时的消息。
在上面的例子中,额外的 `if` 语句用于判断是不是特别热。而最后的 `else` 语句被保留了下来,用于打印既不冷也不热时的消息。
实际上,当不需要完整判断情况的时候,最后的`else`语句是可选的:
实际上,当不需要完整判断情况的时候,最后的 `else` 语句是可选的:
```swift
temperatureInFahrenheit = 72
@ -289,14 +283,13 @@ if temperatureInFahrenheit <= 32 {
}
```
在这个例子中,由于既不冷也不热,所以不会触发`if`或`else if`分支,也就不会打印任何消息。
在这个例子中,由于既不冷也不热,所以不会触发 `if``else if` 分支,也就不会打印任何消息。
<a name="switch"></a>
### Switch
### Switch {#switch}
`switch`语句会尝试把某个值与若干个模式pattern进行匹配。根据第一个匹配成功的模式`switch`语句会执行对应的代码。当有可能的情况较多时,通常用`switch`语句替换`if`语句。
`switch` 语句会尝试把某个值与若干个模式pattern进行匹配。根据第一个匹配成功的模式`switch` 语句会执行对应的代码。当有可能的情况较多时,通常用 `switch` 语句替换 `if` 语句。
`switch`语句最简单的形式就是把某个值与一个或若干个相同类型的值作比较:
`switch` 语句最简单的形式就是把某个值与一个或若干个相同类型的值作比较:
```swift
switch some value to consider {
@ -310,15 +303,13 @@ default:
}
```
`switch` 语句由*多个 case* 构成,每个由 `case` 关键字开始。为了匹配某些更特定的值Swift 提供了几种方法来进行更复杂的模式匹配,这些模式将在本节的稍后部分提到。
`switch`语句由*多个 case* 构成,每个由`case`关键字开始。为了匹配某些更特定的值Swift 提供了几种方法来进行更复杂的模式匹配,这些模式将在本节的稍后部分提到
`if` 语句类似,每一个 case 都是代码执行的一条分支。`switch` 语句会决定哪一条分支应该被执行,这个流程被称作根据给定的值*切换switching*
与`if`语句类似,每一个 case 都是代码执行的一条分支。`switch`语句会决定哪一条分支应该被执行,这个流程被称作根据给定的值*切换(switching)*
`switch`语句必须是完备的。这就是说,每一个可能的值都必须至少有一个 case 分支与之对应。在某些不可能涵盖所有值的情况下,你可以使用默认(`default`)分支来涵盖其它所有没有对应的值,这个默认分支必须在`switch`语句的最后面。
下面的例子使用`switch`语句来匹配一个名为`someCharacter`的小写字符:
`switch` 语句必须是完备的。这就是说,每一个可能的值都必须至少有一个 case 分支与之对应。在某些不可能涵盖所有值的情况下,你可以使用默认(`default`)分支来涵盖其它所有没有对应的值,这个默认分支必须在 `switch` 语句的最后面
下面的例子使用 `switch` 语句来匹配一个名为 `someCharacter` 的小写字符:
```swift
let someCharacter: Character = "z"
@ -330,38 +321,37 @@ case "z":
default:
print("Some other character")
}
// Prints "The last letter of the alphabet"
// 输出“The last letter of the alphabet
```
在这个例子中,第一个 case 分支用于匹配第一个英文字母`a`,第二个 case 分支用于匹配最后一个字母`z`
因为`switch`语句必须有一个case分支用于覆盖所有可能的字符而不仅仅是所有的英文字母所以switch语句使用`default`分支来匹配除了`a`和`z`外的所有值这个分支保证了swith语句的完备性。
在这个例子中,第一个 case 分支用于匹配第一个英文字母 `a`,第二个 case 分支用于匹配最后一个字母 `z`。因为 `switch` 语句必须有一个 case 分支用于覆盖所有可能的字符,而不仅仅是所有的英文字母,所以 switch 语句使用 `default` 分支来匹配除了 `a``z` 外的所有值,这个分支保证了 swith 语句的完备性
#### 不存在隐式的贯穿 {#no-implicit-fallthrough}
<a name="no_implicit_fallthrough"></a>
#### 不存在隐式的贯穿No Implicit Fallthrough
与 C 和 Objective-C 中的 `switch` 语句不同,在 Swift 中,当匹配的 case 分支中的代码执行完毕后,程序会终止 `switch` 语句,而不会继续执行下一个 case 分支。这也就是说,不需要在 case 分支中显式地使用 `break` 语句。这使得 `switch` 语句更安全、更易用,也避免了漏写 `break` 语句导致多个语言被执行的错误。
与 C 和 Objective-C 中的`switch`语句不同,在 Swift 中,当匹配的 case 分支中的代码执行完毕后,程序会终止`switch`语句,而不会继续执行下一个 case 分支。这也就是说,不需要在 case 分支中显式地使用`break`语句。这使得`switch`语句更安全、更易用,也避免了因忘记写`break`语句而产生的错误。
> 注意:
虽然在Swift中`break`不是必须的,但你依然可以在 case 分支中的代码执行完毕前使用`break`跳出,详情请参见[Switch 语句中的 break](#break_in_a_switch_statement)。
> 注意
>
> 虽然在 Swift 中 `break` 不是必须的,但你依然可以在 case 分支中的代码执行完毕前使用 `break` 跳出,详情请参见 [Switch 语句中的 break](#break_in_a_switch_statement)。
每一个 case 分支都*必须*包含至少一条语句。像下面这样书写代码是无效的,因为第一个 case 分支是空的:
```swift
let anotherCharacter: Character = "a"
switch anotherCharacter {
case "a": // Invalid, the case has an empty body
case "a": // 无效,这个分支下面没有语句
case "A":
print("The letter A")
default:
print("Not the letter A")
}
// This will report a compile-time error.
// 这段代码会报编译错误
```
不像 C 语言里的`switch`语句,在 Swift 中,`switch`语句不会一起匹配`"a"`和`"A"`。相反的,上面的代码会引起编译期错误:`case "a": 不包含任何可执行语句`——这就避免了意外地从一个 case 分支贯穿到另外一个,使得代码更安全、也更直观。
不像 C 语言里的 `switch` 语句,在 Swift 中,`switch` 语句不会一起匹配 `"a"``"A"`。相反的,上面的代码会引起编译期错误:`case "a": 不包含任何可执行语句 `——这就避免了意外地从一个 case 分支贯穿到另外一个,使得代码更安全、也更直观。
为了让单个 case 同时匹配 `a``A`,可以将这个两个值组合成一个复合匹配,并且用逗号分开:
为了让单个case同时匹配`a`和`A`,可以将这个两个值组合成一个复合匹配,并且用逗号分开:
```swift
let anotherCharacter: Character = "a"
switch anotherCharacter {
@ -370,22 +360,23 @@ case "a", "A":
default:
print("Not the letter A")
}
// Prints "The letter A
// 输出“The letter A
```
为了可读性,符合匹配可以写成多行形式,详情请参考[复合匹配Compound Cases](#compound_cases)
> 注意:
如果想要显式贯穿case分支请使用`fallthrough`语句,详情请参考[贯穿Fallthrough](#fallthrough)。
为了可读性,符合匹配可以写成多行形式,详情请参考 [复合匹配](#compound_cases)
<a name="interval_matching"></a>
#### 区间匹配
> 注意
>
> 如果想要显式贯穿 case 分支,请使用 `fallthrough` 语句,详情请参考 [贯穿](#fallthrough)。
#### 区间匹配 {#interval-matching}
case 分支的模式也可以是一个值的区间。下面的例子展示了如何使用区间匹配来输出任意数字对应的自然语言格式:
```swift
let approximateCount = 62
let countedThings = "moons orbiting Saturn"
var naturalCount: String
let naturalCount: String
switch approximateCount {
case 0:
naturalCount = "no"
@ -401,50 +392,45 @@ default:
naturalCount = "many"
}
print("There are \(naturalCount) \(countedThings).")
// 输出 "There are dozens of moons orbiting Saturn."
// 输出There are dozens of moons orbiting Saturn.
```
在上例中,`approximateCount`在一个`switch`声明中被评估。每一个`case`都与之进行比较。因为`approximateCount`落在了 12 到 100 的区间,所以`naturalCount`等于`"dozens of"`值,并且此后的执行跳出了`switch`语句。
在上例中,`approximateCount` 在一个 `switch` 声明中被评估。每一个 `case` 都与之进行比较。因为 `approximateCount` 落在了 12 到 100 的区间,所以 `naturalCount` 等于 `"dozens of"` 值,并且此后的执行跳出了 `switch` 语句。
#### 元组 {#tuples}
我们可以使用元组在同一个 `switch` 语句中测试多个值。元组中的元素可以是值,也可以是区间。另外,使用下划线(`_`)来匹配所有可能的值。
<a name="tuples"></a>
#### 元组Tuple
我们可以使用元组在同一个`switch`语句中测试多个值。元组中的元素可以是值,也可以是区间。另外,使用下划线(`_`)来匹配所有可能的值。
下面的例子展示了如何使用一个`(Int, Int)`类型的元组来分类下图中的点(x, y)
下面的例子展示了如何使用一个 `(Int, Int)` 类型的元组来分类下图中的点 (x, y)
```swift
let somePoint = (1, 1)
switch somePoint {
case (0, 0):
print("(0, 0) is at the origin")
print("\(somePoint) is at the origin")
case (_, 0):
print("(\(somePoint.0), 0) is on the x-axis")
print("\(somePoint) is on the x-axis")
case (0, _):
print("(0, \(somePoint.1)) is on the y-axis")
print("\(somePoint) is on the y-axis")
case (-2...2, -2...2):
print("(\(somePoint.0), \(somePoint.1)) is inside the box")
print("\(somePoint) is inside the box")
default:
print("(\(somePoint.0), \(somePoint.1)) is outside of the box")
print("\(somePoint) is outside of the box")
}
// 输出 "(1, 1) is inside the box"
// 输出(1, 1) is inside the box
```
![image](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/coordinateGraphSimple_2x.png)
![image](https://docs.swift.org/swift-book/_images/coordinateGraphSimple_2x.png)
在上面的例子中,`switch`语句会判断某个点是否是原点(0, 0),是否在红色的x轴上,是否在橘黄色的y轴上是否在一个以原点为中心的4x4的蓝色矩形里或者在这个矩形外面。
在上面的例子中,`switch` 语句会判断某个点是否是原点 (0, 0),是否在红色的 x 轴上,是否在橘黄色的 y 轴上是否在一个以原点为中心的4x4的蓝色矩形里或者在这个矩形外面。
不像 C 语言Swift 允许多个 case 匹配同一个值。实际上,在这个例子中,点(0, 0)可以匹配所有_四个 case_。但是如果存在多个匹配那么只会执行第一个被匹配到的 case 分支。考虑点(0, 0)会首先匹配`case (0, 0)`,因此剩下的能够匹配的分支都会被忽视掉。
不像 C 语言Swift 允许多个 case 匹配同一个值。实际上,在这个例子中,点 (0, 0)可以匹配所有_四个 case_。但是如果存在多个匹配那么只会执行第一个被匹配到的 case 分支。考虑点 (0, 0)会首先匹配 `case (0, 0)`,因此剩下的能够匹配的分支都会被忽视掉。
#### 值绑定Value Bindings {#value-bindings}
<a name="value_bindings"></a>
#### 值绑定Value Bindings
case 分支允许将匹配的值声明为临时常量或变量,并且在 case 分支体内使用 —— 这种行为被称为*值绑定*value binding),因为匹配的值在 case 分支体内,与临时的常量或变量绑定。
case 分支允许将匹配的值绑定到一个临时的常量或变量并且在case分支体内使用 —— 这种行为被称为*值绑定*value binding因为匹配的值在case分支体内与临时的常量或变量绑定。
下面的例子展示了如何在一个`(Int, Int)`类型的元组中使用值绑定来分类下图中的点(x, y)
下面的例子将下图中的点 (x, y),使用 `(Int, Int)` 类型的元组表示,然后分类表示:
```swift
let anotherPoint = (2, 0)
@ -456,25 +442,24 @@ case (0, let y):
case let (x, y):
print("somewhere else at (\(x), \(y))")
}
// 输出 "on the x-axis with an x value of 2"
// 输出on the x-axis with an x value of 2
```
![image](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/coordinateGraphMedium_2x.png)
![image](https://docs.swift.org/swift-book/_images/coordinateGraphMedium_2x.png)
在上面的例子中,`switch`语句会判断某个点是否在红色的x轴上,是否在橘黄色的y轴上,或者不在坐标轴上。
在上面的例子中,`switch` 语句会判断某个点是否在红色的 x 轴上,是否在橘黄色的 y 轴上,或者不在坐标轴上。
这三个 case 都声明了常量`x`和`y`的占位符,用于临时获取元组`anotherPoint`的一个或两个值。第一个 case ——`case (let x, 0)`将匹配一个纵坐标为`0`的点,并把这个点的横坐标赋给临时的常量`x`。类似的,第二个 case ——`case (0, let y)`将匹配一个横坐标为`0`的点,并把这个点的纵坐标赋给临时的常量`y`
这三个 case 都声明了常量 `x``y` 的占位符,用于临时获取元组 `anotherPoint` 的一个或两个值。第一个 case ——`case (let x, 0)` 将匹配一个纵坐标为 `0` 的点,并把这个点的横坐标赋给临时的常量 `x`。类似的,第二个 case ——`case (0, let y)` 将匹配一个横坐标为 `0` 的点,并把这个点的纵坐标赋给临时的常量 `y`
一旦声明了这些临时的常量,它们就可以在其对应的 case 分支里使用。在这个例子中,它们用于打印给定点的类型。
请注意,这个`switch`语句不包含默认分支。这是因为最后一个 case ——`case let(x, y)`声明了一个可以匹配余下所有值的元组。这使得`switch`语句已经完备了,因此不需要再书写默认分支。
请注意,这个 `switch` 语句不包含默认分支。这是因为最后一个 case ——`case let(x, y)` 声明了一个可以匹配余下所有值的元组。这使得 `switch` 语句已经完备了,因此不需要再书写默认分支。
<a name="where"></a>
#### Where
#### Where {#where}
case 分支的模式可以使用`where`语句来判断额外的条件。
case 分支的模式可以使用 `where` 语句来判断额外的条件。
下面的例子把下图中的点(x, y)进行了分类:
下面的例子把下图中的点 (x, y)进行了分类:
```swift
let yetAnotherPoint = (1, -1)
@ -486,21 +471,20 @@ case let (x, y) where x == -y:
case let (x, y):
print("(\(x), \(y)) is just some arbitrary point")
}
// 输出 "(1, -1) is on the line x == -y"
// 输出(1, -1) is on the line x == -y
```
![image](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/coordinateGraphComplex_2x.png)
![image](https://docs.swift.org/swift-book/_images/coordinateGraphComplex_2x.png)
在上面的例子中,`switch`语句会判断某个点是否在绿色的对角线`x == y`上,是否在紫色的对角线`x == -y`上,或者不在对角线上。
在上面的例子中,`switch` 语句会判断某个点是否在绿色的对角线 `x == y` 上,是否在紫色的对角线 `x == -y` 上,或者不在对角线上。
这三个 case 都声明了常量`x``y`的占位符,用于临时获取元组`yetAnotherPoint`的两个值。这两个常量被用作`where`语句的一部分,从而创建一个动态的过滤器(filter)。当且仅当`where`语句的条件为`true`时,匹配到的 case 分支才会被执行。
这三个 case 都声明了常量 `x``y` 的占位符,用于临时获取元组 `yetAnotherPoint` 的两个值。这两个常量被用作 `where` 语句的一部分,从而创建一个动态的过滤器filter。当且仅当 `where` 语句的条件为 `true` 时,匹配到的 case 分支才会被执行。
就像是值绑定中的例子,由于最后一个 case 分支匹配了余下所有可能的值,`switch`语句就已经完备了,因此不需要再书写默认分支。
就像是值绑定中的例子,由于最后一个 case 分支匹配了余下所有可能的值,`switch` 语句就已经完备了,因此不需要再书写默认分支。
<a name="compound_cases"></a>
#### 复合匹配Compound Cases
#### 复合型 Cases {#compound-cases}
当多个条件可以使用同一种方法来处理时可以将这几种可能放在同一个case后面并且用逗号隔开。当case后面的任意一种模式匹配的时候这条分支就会被匹配。并且如果匹配列表过长还可以分行书写
当多个条件可以使用同一种方法来处理时,可以将这几种可能放在同一个 `case` 后面,并且用逗号隔开。当 case 后面的任意一种模式匹配的时候,这条分支就会被匹配。并且,如果匹配列表过长,还可以分行书写:
```swift
let someCharacter: Character = "e"
@ -513,9 +497,11 @@ case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
default:
print("\(someCharacter) is not a vowel or a consonant")
}
// 输出“e is a vowel”
```
这个switch语句中的第一个case匹配了英语中的五个小写原因字母。相似的第二个case匹配了英语中所有的小写辅音字母。最终default分支匹配了其它所有字符。
这个 `switch` 语句中的第一个 case匹配了英语中的五个小写元音字母。相似的,第二个 case 匹配了英语中所有的小写辅音字母。最终,`default` 分支匹配了其它所有字符。
复合匹配同样可以包含值绑定。复合匹配里所有的匹配模式,都必须包含相同的值绑定。并且每一个绑定都必须获取到相同类型的值。这保证了,无论复合匹配中的哪个模式发生了匹配,分支体内的代码,都能获取到绑定的值,并且绑定的值都有一样的类型。
```swift
@ -526,13 +512,12 @@ case (let distance, 0), (0, let distance):
default:
print("Not on an axis")
}
// 输出“On an axis, 9 from the origin”
```
上面的case有两个模式`(let distance, 0)`匹配了在x轴上的值,`(0, let distance)`匹配了在y轴上的值。两个模式都绑定了`distance`,并且`distance`在两种模式下都是整型——这意味着分支体内的代码只要case匹配都可以获取到`distance`值
上面的 case 有两个模式:`(let distance, 0)` 匹配了在 x 轴上的值,`(0, let distance)` 匹配了在 y 轴上的值。两个模式都绑定了 `distance`,并且 `distance` 在两种模式下,都是整型——这意味着分支体内的代码,只要 case 匹配,都可以获取到 `distance` 值。
<a name="control_transfer_statements"></a>
## 控制转移语句Control Transfer Statements
## 控制转移语句 {#control-transfer-statements}
控制转移语句改变你代码的执行顺序通过它可以实现代码的跳转。Swift 有五种控制转移语句:
@ -542,19 +527,18 @@ default:
- `return`
- `throw`
我们将会在下面讨论`continue`、`break`和`fallthrough`语句。`return`语句将会在[函数](./06_Functions.html)章节讨论,`throw`语句会在[错误抛出](./18_Error_Handling.html#throwing_errors)章节讨论。
我们将会在下面讨论 `continue``break``fallthrough` 语句。`return` 语句将会在 [函数](./06_Functions.md) 章节讨论,`throw` 语句会在 [错误抛出](./18_Error_Handling.md#throwing_errors) 章节讨论。
<a name="continue"></a>
### Continue
### Continue {#continue}
`continue`语句告诉一个循环体立刻停止本次循环,重新开始下次循环。就好像在说“本次循环我已经执行完了”,但是并不会离开整个循环体。
`continue` 语句告诉一个循环体立刻停止本次循环,重新开始下次循环。就好像在说“本次循环我已经执行完了”,但是并不会离开整个循环体。
下面的例子把一个小写字符串中的元音字母和空格字符移除,生成了一个含义模糊的短句:
```swift
let puzzleInput = "great minds think alike"
var puzzleOutput = ""
for character in puzzleInput.characters {
for character in puzzleInput {
switch character {
case "a", "e", "i", "o", "u", " ":
continue
@ -563,32 +547,30 @@ for character in puzzleInput.characters {
}
}
print(puzzleOutput)
// 输出 "grtmndsthnklk"
// 输出grtmndsthnklk
```
在上面的代码中,只要匹配到元音字母或者空格字符,就调用`continue`语句,使本次循环结束,重新开始下次循环。这种行为使`switch`匹配到元音字母和空格字符时不做处理,而不是让每一个匹配到的字符都被打印。
在上面的代码中,只要匹配到元音字母或者空格字符,就调用 `continue` 语句,使本次循环结束,重新开始下次循环。这种行为使 `switch` 匹配到元音字母和空格字符时不做处理,而不是让每一个匹配到的字符都被打印。
<a name="break"></a>
### Break
### Break {#break}
`break`语句会立刻结束整个控制流的执行。当你想要更早的结束一个`switch`代码块或者一个循环体时,你都可以使用`break`语句。
`break` 语句会立刻结束整个控制流的执行。`break` 可以在 `switch` 或循环语句中使用,用来提前结束 `switch` 或循环语句。
<a name="break_in_a_loop_statement"></a>
#### 循环语句中的 break
#### 循环语句中的 break {#break-in-a-loop-statement}
当在一个循环体中使用`break`时,会立刻中断该循环体的执行,然后跳转到表示循环体结束的大括号(`}`)后的第一行代码。不会再有本次循环的代码被执行,也不会再有下次的循环产生。
当在一个循环体中使用 `break` 时,会立刻中断该循环体的执行,然后跳转到表示循环体结束的大括号`}`后的第一行代码。不会再有本次循环的代码被执行,也不会再有下次的循环产生。
<a name="break_in_a_switch_statement"></a>
#### Switch 语句中的 break
#### Switch 语句中的 break {#break-in-a-switch-statement}
当在一个`switch`代码块中使用`break`时,会立即中断该`switch`代码块的执行,并且跳转到表示`switch`代码块结束的大括号(`}`)后的第一行代码。
当在一个 `switch` 代码块中使用 `break` 时,会立即中断该 `switch` 代码块的执行,并且跳转到表示 `switch` 代码块结束的大括号`}`后的第一行代码。
这种特性可以被用来匹配或者忽略一个或多个分支。因为 Swift 的`switch`需要包含所有的分支而且不允许有为空的分支,有时为了使你的意图更明显,需要特意匹配或者忽略某个分支。那么当你想忽略某个分支时,可以在该分支内写上`break`语句。当那个分支被匹配到时,分支内的`break`语句立即结束`switch`代码块。
这种特性可以被用来匹配或者忽略一个或多个分支。因为 Swift 的 `switch` 需要包含所有的分支而且不允许有为空的分支,有时为了使你的意图更明显,需要特意匹配或者忽略某个分支。那么当你想忽略某个分支时,可以在该分支内写上 `break` 语句。当那个分支被匹配到时,分支内的 `break` 语句立即结束 `switch` 代码块。
>注意
当一个`switch`分支仅仅包含注释时,会被报编译时错误。注释不是代码语句而且也不能让`switch`分支达到被忽略的效果。你应该使用`break`来忽略某个分支。
> 注意
>
> 当一个 `switch` 分支仅仅包含注释时,会被报编译时错误。注释不是代码语句而且也不能让 `switch` 分支达到被忽略的效果。你应该使用 `break` 来忽略某个分支。
下面的例子通过`switch`来判断一个`Character`值是否代表下面四种语言之一。为了简洁,多个值被包含在了同一个分支情况中。
下面的例子通过 `switch` 来判断一个 `Character` 值是否代表下面四种语言之一。为了简洁,多个值被包含在了同一个分支情况中。
```swift
let numberSymbol: Character = "三" // 简体中文里的数字 3
@ -610,21 +592,20 @@ if let integerValue = possibleIntegerValue {
} else {
print("An integer value could not be found for \(numberSymbol).")
}
// 输出 "The integer value of 三 is 3."
// 输出The integer value of 三 is 3.
```
这个例子检查`numberSymbol`是否是拉丁,阿拉伯,中文或者泰语中的`1``4`之一。如果被匹配到,该`switch`分支语句给`Int?`类型变量`possibleIntegerValue`设置一个整数值。
这个例子检查 `numberSymbol` 是否是拉丁,阿拉伯,中文或者泰语中的 `1``4` 之一。如果被匹配到,该 `switch` 分支语句给 `Int?` 类型变量 `possibleIntegerValue` 设置一个整数值。
当`switch`代码块执行完后,接下来的代码通过使用可选绑定来判断`possibleIntegerValue`是否曾经被设置过值。因为是可选类型的缘故,`possibleIntegerValue`有一个隐式的初始值`nil`,所以仅仅当`possibleIntegerValue`曾被`switch`代码块的前四个分支中的某个设置过一个值时,可选的绑定才会被判定为成功。
`switch` 代码块执行完后,接下来的代码通过使用可选绑定来判断 `possibleIntegerValue` 是否曾经被设置过值。因为是可选类型的缘故,`possibleIntegerValue` 有一个隐式的初始值 `nil`,所以仅仅当 `possibleIntegerValue` 曾被 `switch` 代码块的前四个分支中的某个设置过一个值时,可选的绑定才会被判定为成功。
在上面的例子中,想要把`Character`所有的的可能性都枚举出来是不现实的,所以使用`default`分支来包含所有上面没有匹配到字符的情况。由于这个`default`分支不需要执行任何动作,所以它只写了一条`break`语句。一旦落入到`default`分支中后,`break`语句就完成了该分支的所有代码操作,代码继续向下,开始执行`if let`语句。
在上面的例子中,想要把 `Character` 所有的的可能性都枚举出来是不现实的,所以使用 `default` 分支来包含所有上面没有匹配到字符的情况。由于这个 `default` 分支不需要执行任何动作,所以它只写了一条 `break` 语句。一旦落入到 `default` 分支中后,`break` 语句就完成了该分支的所有代码操作,代码继续向下,开始执行 `if let` 语句。
<a name="fallthrough"></a>
### 贯穿Fallthrough
### 贯穿Fallthrough {#fallthrough}
Swift 中的`switch`不会从上一个 case 分支落入到下一个 case 分支中。相反,只要第一个匹配到的 case 分支完成了它需要执行的语句,整个`switch`代码块完成了它的执行。相比之下C 语言要求你显式地插入`break`语句到每个 case 分支的末尾来阻止自动落入到下一个 case 分支中。Swift 的这种避免默认落入到下一个分支中的特性意味着它的`switch` 功能要比 C 语言的更加清晰和可预测,可以避免无意识地执行多个 case 分支从而引发的错误。
Swift 里,`switch` 语句不会从上一个 case 分支跳转到下一个 case 分支中。相反,只要第一个匹配到的 case 分支完成了它需要执行的语句,整个 `switch` 代码块完成了它的执行。相比之下C 语言要求你显式地插入 `break` 语句到每个 case 分支的末尾来阻止自动落入到下一个 case 分支中。Swift 的这种避免默认落入到下一个分支中的特性意味着它的 `switch` 功能要比 C 语言的更加清晰和可预测,可以避免无意识地执行多个 case 分支从而引发的错误。
如果你确实需要 C 风格的贯穿的特性,你可以在每个需要该特性的 case 分支中使用`fallthrough`关键字。下面的例子使用`fallthrough`来创建一个数字的描述语句。
如果你确实需要 C 风格的贯穿的特性,你可以在每个需要该特性的 case 分支中使用 `fallthrough` 关键字。下面的例子使用 `fallthrough` 来创建一个数字的描述语句。
```swift
let integerToDescribe = 5
@ -637,32 +618,34 @@ default:
description += " an integer."
}
print(description)
// 输出 "The number 5 is a prime number, and also an integer."
// 输出The number 5 is a prime number, and also an integer.
```
这个例子定义了一个`String`类型的变量`description`并且给它设置了一个初始值。函数使用`switch`逻辑来判断`integerToDescribe`变量的值。当`integerToDescribe`的值属于列表中的质数之一时,该函数在`description`后添加一段文字,来表明这个数字是一个质数。然后它使用`fallthrough`关键字来“贯穿”到`default`分支中。`default`分支在`description`的最后添加一段额外的文字,至此`switch`代码块执行完了。
这个例子定义了一个 `String` 类型的变量 `description` 并且给它设置了一个初始值。函数使用 `switch` 逻辑来判断 `integerToDescribe` 变量的值。当 `integerToDescribe` 的值属于列表中的质数之一时,该函数在 `description` 后添加一段文字,来表明这个数字是一个质数。然后它使用 `fallthrough` 关键字来“贯穿”到 `default` 分支中。`default` 分支在 `description` 的最后添加一段额外的文字,至此 `switch` 代码块执行完了。
如果`integerToDescribe`的值不属于列表中的任何质数,那么它不会匹配到第一个`switch`分支。而这里没有其他特别的分支情况,所以`integerToDescribe`匹配到`default`分支中。
如果 `integerToDescribe` 的值不属于列表中的任何质数,那么它不会匹配到第一个 `switch` 分支。而这里没有其他特别的分支情况,所以 `integerToDescribe` 匹配到 `default` 分支中。
当`switch`代码块执行完后,使用`print(_:separator:terminator:)`函数打印该数字的描述。在这个例子中,数字`5`被准确的识别为了一个质数。
`switch` 代码块执行完后,使用 `print(_:separator:terminator:)` 函数打印该数字的描述。在这个例子中,数字 `5` 被准确的识别为了一个质数。
> 注意
> `fallthrough`关键字不会检查它下一个将会落入执行的 case 中的匹配条件。`fallthrough`简单地使代码继续连接到下一个 case 中的代码,这和 C 语言标准中的`switch`语句特性是一样的。
> 注意
>
> `fallthrough` 关键字不会检查它下一个将会落入执行的 case 中的匹配条件。`fallthrough` 简单地使代码继续连接到下一个 case 中的代码,这和 C 语言标准中的 `switch` 语句特性是一样的。
<a name="labeled_statements"></a>
### 带标签的语句
### 带标签的语句 {#labeled-statements}
在 Swift 中,你可以在循环体和条件语句中嵌套循环体和条件语句来创造复杂的控制流结构。并且,循环体和条件语句都可以使用`break`语句来提前结束整个代码块。因此,显式地指明`break`语句想要终止的是哪个循环体或者条件语句,会很有用。类似地,如果你有许多嵌套的循环体,显式指明`continue`语句想要影响哪一个循环体也会非常有用。
在 Swift 中,你可以在循环体和条件语句中嵌套循环体和条件语句来创造复杂的控制流结构。并且,循环体和条件语句都可以使用 `break` 语句来提前结束整个代码块。因此,显式地指明 `break` 语句想要终止的是哪个循环体或者条件语句,会很有用。类似地,如果你有许多嵌套的循环体,显式指明 `continue` 语句想要影响哪一个循环体也会非常有用。
为了实现这个目的,你可以使用标签(*statement label*)来标记一个循环体或者条件语句,对于一个条件语句,你可以使用`break`加标签的方式,来结束这个被标记的语句。对于一个循环语句,你可以使用`break`或者`continue`加标签,来结束或者继续这条被标记语句的执行。
为了实现这个目的,你可以使用标签(*statement label*)来标记一个循环体或者条件语句,对于一个条件语句,你可以使用 `break` 加标签的方式,来结束这个被标记的语句。对于一个循环语句,你可以使用 `break` 或者 `continue` 加标签,来结束或者继续这条被标记语句的执行。
声明一个带标签的语句是通过在该语句的关键词的同一行前面放置一个标签,作为这个语句的前导关键字(introducor keyword),并且该标签后面跟随一个冒号。下面是一个针对`while`循环体的标签语法,同样的规则适用于所有的循环体和条件语句。
声明一个带标签的语句是通过在该语句的关键词的同一行前面放置一个标签,作为这个语句的前导关键字introducor keyword,并且该标签后面跟随一个冒号。下面是一个针对 `while` 循环体的标签语法,同样的规则适用于所有的循环体和条件语句。
> `label name`: while `condition` {
> `statements`
> }
```swift
label name: while condition {
statements
}
```
下面的例子是前面章节中*蛇和梯子*的适配版本,在此版本中,我们将使用一个带有标签的`while`循环体中调用`break`和`continue`语句。这次,游戏增加了一条额外的规则:
下面的例子是前面章节中*蛇和梯子*的适配版本,在此版本中,我们将使用一个带有标签的 `while` 循环体中调用 `break``continue` 语句。这次,游戏增加了一条额外的规则:
- 为了获胜,你必须*刚好*落在第 25 个方块中。
@ -670,9 +653,9 @@ print(description)
游戏的棋盘和之前一样:
![image](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/snakesAndLadders_2x.png)
![image](https://docs.swift.org/swift-book/_images/snakesAndLadders_2x.png)
`finalSquare`、`board`、`square`和`diceRoll`值被和之前一样的方式初始化:
`finalSquare``board``square``diceRoll` 值被和之前一样的方式初始化:
```swift
let finalSquare = 25
@ -683,9 +666,9 @@ var square = 0
var diceRoll = 0
```
这个版本的游戏使用`while`循环和`switch`语句来实现游戏的逻辑。`while`循环有一个标签名`gameLoop`,来表明它是游戏的主循环。
这个版本的游戏使用 `while` 循环和 `switch` 语句来实现游戏的逻辑。`while` 循环有一个标签名 `gameLoop`,来表明它是游戏的主循环。
该`while`循环体的条件判断语句是`while square !=finalSquare`这表明你必须刚好落在方格25中。
`while` 循环体的条件判断语句是 `while square !=finalSquare`这表明你必须刚好落在方格25中。
```swift
gameLoop: while square != finalSquare {
@ -693,13 +676,13 @@ gameLoop: while square != finalSquare {
if diceRoll == 7 { diceRoll = 1 }
switch square + diceRoll {
case finalSquare:
// diceRoll will move us to the final square, so the game is over
// 骰子数刚好使玩家移动到最终的方格里,游戏结束。
break gameLoop
case let newSquare where newSquare > finalSquare:
// diceRoll will move us beyond the final square, so roll again
// 骰子数将会使玩家的移动超出最后的方格,那么这种移动是不合法的,玩家需要重新掷骰子
continue gameLoop
default:
// this is a valid move, so find out its effect
// 合法移动,做正常的处理
square += diceRoll
square += board[square]
}
@ -707,56 +690,59 @@ gameLoop: while square != finalSquare {
print("Game over!")
```
每次循环迭代开始时掷骰子。与之前玩家掷完骰子就立即移动不同,这里使用了`switch`语句来考虑每次移动可能产生的结果,从而决定玩家本次是否能够移动。
每次循环迭代开始时掷骰子。与之前玩家掷完骰子就立即移动不同,这里使用了 `switch` 语句来考虑每次移动可能产生的结果,从而决定玩家本次是否能够移动。
- 如果骰子数刚好使玩家移动到最终的方格里,游戏结束。`break gameLoop`语句跳转控制去执行`while`循环体后的第一行代码,意味着游戏结束。
- 如果骰子数将会使玩家的移动超出最后的方格,那么这种移动是不合法的,玩家需要重新掷骰子。`continue gameLoop`语句结束本次`while`循环,开始下一次循环。
- 在剩余的所有情况中,骰子数产生的都是合法的移动。玩家向前移动 `diceRoll` 个方格,然后游戏逻辑再处理玩家当前是否处于蛇头或者梯子的底部。接着本次循环结束,控制跳转到`while`循环体的条件判断语句处,再决定是否需要继续执行下次循环。
- 如果骰子数刚好使玩家移动到最终的方格里,游戏结束。`break gameLoop` 语句跳转控制去执行 `while` 循环体后的第一行代码,意味着游戏结束。
- 如果骰子数将会使玩家的移动超出最后的方格,那么这种移动是不合法的,玩家需要重新掷骰子。`continue gameLoop` 语句结束本次 `while` 循环,开始下一次循环。
- 在剩余的所有情况中,骰子数产生的都是合法的移动。玩家向前移动 `diceRoll` 个方格,然后游戏逻辑再处理玩家当前是否处于蛇头或者梯子的底部。接着本次循环结束,控制跳转到 `while` 循环体的条件判断语句处,再决定是否需要继续执行下次循环。
>注意
如果上述的`break`语句没有使用`gameLoop`标签,那么它将会中断`switch`语句而不是`while`循环。使用`gameLoop`标签清晰的表明了`break`想要中断的是哪个代码块。
同时请注意,当调用`continue gameLoop`去跳转到下一次循环迭代时,这里使用`gameLoop`标签并不是严格必须的。因为在这个游戏中,只有一个循环体,所以`continue`语句会影响到哪个循环体是没有歧义的。然而,`continue`语句使用`gameLoop`标签也是没有危害的。这样做符合标签的使用规则,同时参照旁边的`break gameLoop`,能够使游戏的逻辑更加清晰和易于理解
> 注意
>
> 如果上述的 `break` 语句没有使用 `gameLoop` 标签,那么它将会中断 `switch` 语句而不是 `while` 循环。使用 `gameLoop` 标签清晰的表明了 `break` 想要中断的是哪个代码块
>
> 同时请注意,当调用 `continue gameLoop` 去跳转到下一次循环迭代时,这里使用 `gameLoop` 标签并不是严格必须的。因为在这个游戏中,只有一个循环体,所以 `continue` 语句会影响到哪个循环体是没有歧义的。然而,`continue` 语句使用 `gameLoop` 标签也是没有危害的。这样做符合标签的使用规则,同时参照旁边的 `break gameLoop`,能够使游戏的逻辑更加清晰和易于理解。
<a name="early_exit"></a>
## 提前退出
像`if`语句一样,`guard`的执行取决于一个表达式的布尔值。我们可以使用`guard`语句来要求条件必须为真时,以执行`guard`语句后的代码。不同于`if`语句,一个`guard`语句总是有一个`else`从句,如果条件不为真则执行`else`从句中的代码。
## 提前退出 {#early-exit}
`if` 语句一样,`guard` 的执行取决于一个表达式的布尔值。我们可以使用 `guard` 语句来要求条件必须为真时,以执行 `guard` 语句后的代码。不同于 `if` 语句,一个 `guard` 语句总是有一个 `else` 从句,如果条件不为真则执行 `else` 从句中的代码。
```swift
func greet(person: [String: String]) {
guard let name = person["name"] else {
return
}
print("Hello \(name)")
guard let location = person["location"] else {
print("I hope the weather is nice near you.")
return
}
print("I hope the weather is nice in \(location).")
guard let name = person["name"] else {
return
}
print("Hello \(name)!")
guard let location = person["location"] else {
print("I hope the weather is nice near you.")
return
}
print("I hope the weather is nice in \(location).")
}
greet(["name": "John"])
// 输出 "Hello John!"
// 输出 "I hope the weather is nice near you."
greet(["name": "Jane", "location": "Cupertino"])
// 输出 "Hello Jane!"
// 输出 "I hope the weather is nice in Cupertino."
greet(person: ["name": "John"])
// 输出“Hello John!”
// 输出“I hope the weather is nice near you.”
greet(person: ["name": "Jane", "location": "Cupertino"])
// 输出“Hello Jane!”
// 输出“I hope the weather is nice in Cupertino.”
```
如果`guard`语句的条件被满足,则继续执行`guard`语句大括号后的代码。将变量或者常量的可选绑定作为`guard`语句的条件,都可以保护`guard`语句后面的代码。
如果 `guard` 语句的条件被满足,则继续执行 `guard` 语句大括号后的代码。将变量或者常量的可选绑定作为 `guard` 语句的条件,都可以保护 `guard` 语句后面的代码。
如果条件不被满足,在`else`分支上的代码就会被执行。这个分支必须转移控制以退出`guard`语句出现的代码段。它可以用控制转移语句如`return`,`break`,`continue`或者`throw`做这件事,或者调用一个不返回的方法或函数,例如`fatalError()`
如果条件不被满足,在 `else` 分支上的代码就会被执行。这个分支必须转移控制以退出 `guard` 语句出现的代码段。它可以用控制转移语句如 `return``break``continue` 或者 `throw` 做这件事,或者调用一个不返回的方法或函数,例如 `fatalError()`
相比于可以实现同样功能的`if`语句,按需使用`guard`语句会提升我们代码的可读性。它可以使你的代码连贯的被执行而不需要将它包在`else`块中,它可以使你在紧邻条件判断的地方,处理违规的情况。
相比于可以实现同样功能的 `if` 语句,按需使用 `guard` 语句会提升我们代码的可读性。它可以使你的代码连贯的被执行而不需要将它包在 `else` 块中,它可以使你在紧邻条件判断的地方,处理违规的情况。
<a name="checking_api_availability"></a>
## 检测 API 可用性
## 检测 API 可用性 {#checking-api-availability}
Swift内置支持检查 API 可用性这可以确保我们不会在当前部署机器上不小心地使用了不可用的API。
Swift 内置支持检查 API 可用性,这可以确保我们不会在当前部署机器上,不小心地使用了不可用的 API。
编译器使用 SDK 中的可用信息来验证我们的代码中使用的所有 API 在项目指定的部署目标上是否可用。如果我们尝试使用一个不可用的 APISwift 会在编译时报错。
我们在`if`或`guard`语句中使用`可用性条件availability condition)`去有条件的执行一段代码来在运行时判断调用的API是否可用。编译器使用从可用性条件语句中获取的信息去验证在这个代码块中调用的 API 是否可用。
我们在 `if``guard` 语句中使用 `可用性条件availability condition)`去有条件的执行一段代码,来在运行时判断调用的 API 是否可用。编译器使用从可用性条件语句中获取的信息去验证,在这个代码块中调用的 API 是否可用。
```swift
if #available(iOS 10, macOS 10.12, *) {
@ -766,15 +752,14 @@ if #available(iOS 10, macOS 10.12, *) {
}
```
以上可用性条件指定,在iOS中`if`语句的代码块仅仅在 iOS 10 及更高的系统下运行;在 macOS中仅在 macOS 10.12 及更高才运行。最后一个参数,`*`是必须的用于指定在所有其它平台中如果版本号高于你的设备指定的最低版本if语句的代码块将会运行。
以上可用性条件指定,`if` 语句的代码块仅仅在 iOS 10 macOS 10.12 及更高版本才运行。最后一个参数,`*`是必须的用于指定在所有其它平台中如果版本号高于你的设备指定的最低版本if 语句的代码块将会运行。
在它一般的形式中,可用性条件使用了一个平台名字和版本的列表。平台名字可以是`iOS``macOS``watchOS`和`tvOS`——请访问[声明属性](../chapter3/06_Attributes.html)来获取完整列表。除了指定像 iOS 8的主板本号,我们可以指定像iOS 8.3 以及 macOS 10.10.3的子版本号。
在它一般的形式中,可用性条件使用了一个平台名字和版本的列表。平台名字可以是 `iOS``macOS``watchOS``tvOS`——请访问 [声明属性](../chapter3/06_Attributes.html) 来获取完整列表。除了指定像 iOS 8 或 macOS 10.10 的大版本号,可以指定像 iOS 11.2.6 以及 macOS 10.13.3 的小版本号。
```swift
if #available(platform name version, ..., *) {
statements to execute if the APIs are available
if #available(平台名称 版本号, ..., *) {
APIs 可用,语句将执行
} else {
fallback statements to execute if the APIs are unavailable
APIs 不可用,语句将不执行
}
```

View File

@ -1,47 +1,18 @@
# 函数Functions
-----------------
# 函数
> 1.0
> 翻译:[honghaoz](https://github.com/honghaoz)
> 校对:[LunaticM](https://github.com/LunaticM)
> 2.0
> 翻译+校对:[dreamkidd](https://github.com/dreamkidd)
> 2.1
> 翻译:[DianQK](https://github.com/DianQK)
> 定稿:[shanks](http://codebuild.me)
> 2.2
> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-12
> 3.0
> 翻译: [crayygy](https://github.com/crayygy) 2016-09-12
本页包含内容:
- [函数定义与调用Defining and Calling Functions](#Defining_and_Calling_Functions)
- [函数参数与返回值Function Parameters and Return Values](#Function_Parameters_and_Return_Values)
- [函数参数标签和参数名称 (Function Argument Labels and Parameter Names) ](#Function_Argument_Labels_and_Parameter_Names)
- [函数类型Function Types](#Function_Types)
- [嵌套函数Nested Functions](#Nested_Functions)
`函数` 是一段完成特定任务的独立代码片段。你可以通过给函数命名来标识某个函数的功能,这个名字可以被用来在需要的时候"调用"这个函数来完成它的任务。
*函数*是一段完成特定任务的独立代码片段。你可以通过给函数命名来标识某个函数的功能,这个名字可以被用来在需要的时候“调用”这个函数来完成它的任务。
Swift 统一的函数语法非常的灵活,可以用来表示任何函数,包括从最简单的没有参数名字的 C 风格函数,到复杂的带局部和外部参数名的 Objective-C 风格函数。参数可以提供默认值,以简化函数调用。参数也可以既当做传入参数,也当做传出参数,也就是说,一旦函数执行结束,传入的参数值将被修改。
在 Swift 中,每个函数都有一个由函数的参数值类型和返回值类型组成的类型。你可以把函数类型当做任何其他普通变量类型一样处理,这样就可以更简单地把函数当做别的函数的参数,也可以从其他函数中返回函数。函数的定义可以写在其他函数定义中,这样可以在嵌套函数范围内实现功能封装。
<a name="Defining_and_Calling_Functions"></a>
## 函数的定义与调用 (Defining and Calling Functions)
## 函数的定义与调用 {#Defining-and-Calling-Functions}
当你定义一个函数时,你可以定义一个或多个有名字和类型的值,作为函数的输入称为*参数**parameters*也可以定义某种类型的值作为函数执行结束时的输出称为 *返回* 类型,*return* type
当你定义一个函数时,你可以定义一个或多个有名字和类型的值,作为函数的输入称为*参数*,也可以定义某种类型的值作为函数执行结束时的输出称为*返回类型*
每个函数有个函数名,用来描述函数执行的任务。要使用一个函数时,用函数名来“调用”这个函数,并传给它匹配的输入值(称作 *实参* *arguments*)。函数的实参必须与函数参数表里参数的顺序一致。
下面例子中的函数的名字是`sayHello(_:)`,之所以叫这个名字,是因为这个函数用一个人的名字当做输入,并返回向这个人问候的语句。为了完成这个任务,你需要定义一个输入参数——一个叫做 `personName``String` 值,和一个包含给这个人问候语的 `String` 类型的返回值:
每个函数有个*函数名*,用来描述函数执行的任务。要使用一个函数时,用函数名来“调用”这个函数,并传给它匹配的输入值(称作*实参*)。函数的实参必须与函数参数表里参数的顺序一致。
下面例子中的函数的名字是 `greet(person:)`,之所以叫这个名字,是因为这个函数用一个人的名字当做输入,并返回向这个人问候的语句。为了完成这个任务,你需要定义一个输入参数——一个叫做 `person``String` 值,和一个包含给这个人问候语的 `String` 类型的返回值:
```swift
func greet(person: String) -> String {
@ -56,20 +27,20 @@ func greet(person: String) -> String {
```swift
print(greet(person: "Anna"))
// 打印 "Hello, Anna!"
// 打印Hello, Anna!
print(greet(person: "Brian"))
// 打印 "Hello, Brian!"
// 打印Hello, Brian!
```
调用 `sayHello(_:)` 函数时,在圆括号中传给它一个 `String` 类型的实参,例如 `sayHello("Anna")`。正如上面所示,因为这个函数返回一个 `String` 类型的值,所以`sayHello` 可以被包含在 `print(_:separator:terminator:)` 的调用中,用来输出这个函数的返回值。
调用 `greet(person:)` 函数时,在圆括号中传给它一个 `String` 类型的实参,例如 `greet(person: "Anna")`。正如上面所示,因为这个函数返回一个 `String` 类型的值,所以 `greet` 可以被包含在 `print(_:separator:terminator:)` 的调用中,用来输出这个函数的返回值。
>注意
`print(_:separator:terminator:)` 函数的第一个参数并没有设置一个标签,而其他的参数因为已经有了默认值,因此是可选的。关于这些函数语法上的变化详见下方关于 函数参数标签和参数名 以及 默认参数值。
> 注意
>
> `print(_:separator:terminator:)` 函数的第一个参数并没有设置一个标签,而其他的参数因为已经有了默认值,因此是可选的。关于这些函数语法上的变化详见下方关于 函数参数标签和参数名以及默认参数值。
`greet(person:)` 的函数体中,先定义了一个新的名为 `greeting``String` 常量,同时,把对 `personName` 的问候消息赋值给了 `greeting` 。然后用 `return` 关键字把这个问候返回出去。一旦 `return greeting` 被调用,该函数结束它的执行并返回 `greeting` 的当前值。
`sayHello(_:)` 的函数体中,先定义了一个新的名为 `greeting``String` 常量,同时,把对 `personName` 的问候消息赋值给了 `greeting` 。然后用 `return` 关键字把这个问候返回出去。一旦 `return greeting` 调用,该函数结束它的执行并返回 `greeting` 的当前值
你可以用不同的输入值多次调用 `sayHello(_:)`。上面的例子展示的是用`"Anna"``"Brian"`调用的结果,该函数分别返回了不同的结果。
你可以用不同的输入值多次调用 `greet(person:)`。上面的例子展示的是用 `"Anna"``"Brian"` 调用的结果,该函数分别返回了不同的结果
为了简化这个函数的定义,可以将问候消息的创建和返回写成一句:
@ -78,16 +49,14 @@ func greetAgain(person: String) -> String {
return "Hello again, " + person + "!"
}
print(greetAgain(person: "Anna"))
// 打印 "Hello again, Anna!"
// 打印Hello again, Anna!
```
<a name="Function_Parameters_and_Return_Values"></a>
## 函数参数与返回值 (Function Parameters and Return Values)
## 函数参数与返回值 {#Function-Parameters-and-Return-Values}
函数参数与返回值在 Swift 中非常的灵活。你可以定义任何类型的函数,包括从只带一个未名参数的简单函数到复杂的带有表达性参数名和不同参数选项的复杂函数。
<a name="functions_without_parameters"></a>
### 无参数函数 (Functions Without Parameters)
### 无参数函数 {#functions-without-parameters}
函数可以没有参数。下面这个函数就是一个无参数函数,当被调用时,它返回固定的 `String` 消息:
@ -96,13 +65,12 @@ func sayHelloWorld() -> String {
return "hello, world"
}
print(sayHelloWorld())
// 打印 "hello, world"
// 打印hello, world
```
尽管这个函数没有参数,但是定义中在函数名后还是需要一对圆括号。当被调用时,也需要在函数名后写一对圆括号。
<a name="functions_with_multiple_parameters"></a>
### 多参数函数 (Functions With Multiple Parameters)
### 多参数函数 {#functions-with-multiple-parameters}
函数可以有多种输入参数,这些参数被包含在函数的括号之中,以逗号分隔。
@ -117,58 +85,56 @@ func greet(person: String, alreadyGreeted: Bool) -> String {
}
}
print(greet(person: "Tim", alreadyGreeted: true))
// 打印 "Hello again, Tim!"
// 打印Hello again, Tim!
```
你可以通过在括号内使用逗号分隔来传递一个`String`参数值和一个标识为`alreadyGreeted``Bool`值,来调用`sayHello(_:alreadyGreeted:)`函数。注意这个函数和上面`greet(person:)`是不同的。虽然它们都有着同样的名字`greet`,但是`greet(person:alreadyGreeted:)`函数需要两个参数,而`greet(person:)`只需要一个参数。
你可以通过在括号内使用逗号分隔来传递一个 `String` 参数值和一个标识为 `alreadyGreeted``Bool` 值,来调用 `greet(person:alreadyGreeted:)` 函数。注意这个函数和上面 `greet(person:)` 是不同的。虽然它们都有着同样的名字 `greet`,但是 `greet(person:alreadyGreeted:)` 函数需要两个参数,而 `greet(person:)` 只需要一个参数。
<a name="functions_without_return_values"></a>
### 无返回值函数 (Functions Without Return Values)
### 无返回值函数 {#functions-without-return-values}
函数可以没有返回值。下面是 `sayHello(_:)` 函数的另一个版本,`sayGoodbye(_:)`这个函数直接打印一个`String`值,而不是返回它:
函数可以没有返回值。下面是 `greet(person:)` 函数的另一个版本,这个函数直接打印一个 `String` 值,而不是返回它:
```swift
func greet(person: String) {
print("Hello, \(person)!")
}
greet(person: "Dave")
// 打印 "Hello, Dave!"
// 打印Hello, Dave!
```
因为这个函数不需要返回值,所以这个函数的定义中没有返回箭头(->)和返回类型。
>注意
严格上来说,虽然没有返回值被定义,`sayGoodbye(_:)` 函数依然返回了值。没有定义返回类型的函数会返回一个特殊的`Void`值。它其实是一个空的元组tuple没有任何元素可以写成()。
> 注意
>
> 严格地说,即使没有明确定义返回值,该 `greet(Person)` 函数仍然返回一个值。没有明确定义返回类型的函数的返回一个 `Void` 类型特殊值,该值为一个空元组,写成 ()。
被调用时,一个函数的返回值可以被忽略:
调用函数时,可以忽略该函数的返回值:
```swift
func printAndCount(string: String) -> Int {
print(string)
return string.characters.count
return string.count
}
func printWithoutCounting(string: String) {
let _ = printAndCount(string: string)
}
printAndCount(string: "hello, world")
// 打印 "hello, world" 并且返回值 12
// 打印hello, world”,并且返回值 12
printWithoutCounting(string: "hello, world")
// 打印 "hello, world" 但是没有返回任何值
// 打印hello, world”,但是没有返回任何值
```
第一个函数 `printAndCount(_:)`,输出一个字符串并返回 `Int` 类型的字符数。第二个函数 `printWithoutCounting`调用了第一个函数,但是忽略了它的返回值。当第二个函数被调用时,消息依然会由第一个函数输出,但是返回值不会被用到。
第一个函数 `printAndCount(string:)`,输出一个字符串并返回 `Int` 类型的字符数。第二个函数 `printWithoutCounting(string:)` 调用了第一个函数,但是忽略了它的返回值。当第二个函数被调用时,消息依然会由第一个函数输出,但是返回值不会被用到。
>注意
返回值可以被忽略但定义了有返回值的函数必须返回一个值如果在函数定义底部没有返回任何值将导致编译时错误compile-time error
> 注意
>
> 返回值可以被忽略,但定义了有返回值的函数必须返回一个值,如果在函数定义底部没有返回任何值,将导致编译时错误。
<a name="functions_with_multiple_return_values"></a>
### 多重返回值函数 (Functions with Multiple Return Values)
### 多重返回值函数 {#functions-with-multiple-return-values}
你可以用元组tuple类型让多个值作为一个复合值从函数中返回。
下例中定义了一个名为 `minMax(_:)` 的函数,作用是在一个 `Int` 类型的数组中找出最小值与最大值。
下例中定义了一个名为 `minMax(array:)` 的函数,作用是在一个 `Int` 类型的数组中找出最小值与最大值。
```swift
func minMax(array: [Int]) -> (min: Int, max: Int) {
@ -185,33 +151,31 @@ func minMax(array: [Int]) -> (min: Int, max: Int) {
}
```
`minMax(_:)` 函数返回一个包含两个 `Int` 值的元组,这些值被标记为 `min``max` ,以便查询函数的返回值时可以通过名字访问它们。
`minMax(array:)` 函数返回一个包含两个 `Int` 值的元组,这些值被标记为 `min``max` ,以便查询函数的返回值时可以通过名字访问它们。
`minMax(_:)` 的函数体中,在开始的时候设置两个工作变量 `currentMin``currentMax` 的值为数组中的第一个数。然后函数会遍历数组中剩余的值并检查该值是否比 `currentMin``currentMax` 更小或更大。最后数组中的最小值与最大值作为一个包含两个 `Int` 值的元组返回。
`minMax(array:)` 的函数体中,在开始的时候设置两个工作变量 `currentMin``currentMax` 的值为数组中的第一个数。然后函数会遍历数组中剩余的值并检查该值是否比 `currentMin``currentMax` 更小或更大。最后数组中的最小值与最大值作为一个包含两个 `Int` 值的元组返回。
因为元组的成员值已被命名,因此可以通过 `.` 语法来检索找到的最小值与最大值:
```swift
let bounds = minMax(array: [8, -6, 2, 109, 3, 71])
print("min is \(bounds.min) and max is \(bounds.max)")
// 打印 "min is -6 and max is 109"
// 打印min is -6 and max is 109
```
需要注意的是,元组的成员不需要在元组从函数中返回时命名,因为它们的名字已经在函数返回类型中指定了。
<a name="optional_tuple_return_types"></a>
### 可选元组返回类型 (Optional Tuple Return Types)
### 可选元组返回类型 {#optional-tuple-return-types}
如果函数返回的元组类型有可能整个元组都“没有值”,你可以使用可选的 `optional` 元组返回类型反映整个元组可以是`nil`的事实。你可以通过在元组类型的右括号后放置一个问号来定义一个可选元组,例如 `(Int, Int)?``(String, Int, Bool)?`
如果函数返回的元组类型有可能整个元组都“没有值”,你可以使用*可选的* 元组返回类型反映整个元组可以是 `nil` 的事实。你可以通过在元组类型的右括号后放置一个问号来定义一个可选元组,例如 `(Int, Int)?``(String, Int, Bool)?`
>注意
可选元组类型如 `(Int, Int)?` 与元组包含可选类型如 `(Int?, Int?)` 是不同的.可选的元组类型,整个元组是可选的,而不只是元组中的每个元素值。
> 注意
>
> 可选元组类型如 `(Int, Int)?` 与元组包含可选类型如 `(Int?, Int?)` 是不同的。可选的元组类型,整个元组是可选的,而不只是元组中的每个元素值。
前面的 `minMax(array:)` 函数返回了一个包含两个 `Int` 值的元组。但是函数不会对传入的数组执行任何安全检查,如果 `array` 参数是一个空数组,如上定义的 `minMax(array:)` 在试图访问 `array[0]` 时会触发一个运行时错误。
前面的 `minMax(_:)` 函数返回了一个包含两个 `Int` 值的元组。但是函数不会对传入的数组执行任何安全检查,如果 `array` 参数是一个空数组,如上定义的 `minMax(_:)` 在试图访问 `array[0]` 时会触发一个运行时错误(runtime error)。
为了安全地处理这个“空数组”问题,将 `minMax(_:)` 函数改写为使用可选元组返回类型,并且当数组为空时返回 `nil`
为了安全地处理这个“空数组”问题,将 `minMax(array:)` 函数改写为使用可选元组返回类型,并且当数组为空时返回 `nil`
```swift
func minMax(array: [Int]) -> (min: Int, max: Int)? {
@ -229,40 +193,59 @@ func minMax(array: [Int]) -> (min: Int, max: Int)? {
}
```
你可以使用可选绑定来检查 `minMax(_:)` 函数返回的是一个存在的元组值还是 `nil`
你可以使用可选绑定来检查 `minMax(array:)` 函数返回的是一个存在的元组值还是 `nil`
```swift
if let bounds = minMax(array: [8, -6, 2, 109, 3, 71]) {
print("min is \(bounds.min) and max is \(bounds.max)")
}
// 打印 "min is -6 and max is 109"
// 打印min is -6 and max is 109
```
<a name="Function_Argument_Labels_and_Parameter_Names"></a>
## 函数参数标签和参数名称 (Function Argument Labels and Parameter Names)
### 隐式返回的函数 {#functions-with-an-implicit-return}
如果一个函数的整个函数体是一个单行表达式,这个函数可以隐式地返回这个表达式。举个例子,以下的函数有着同样的作用:
每个函数参数都有一个参数标签( argument label )以及一个参数名称( parameter name )。参数标签在调用函数的时候使用;调用的时候需要将函数的参数标签写在对应的参数前面。参数名称在函数的实现中使用。默认情况下,函数参数使用参数名称来作为它们的参数标签。
```
func greeting(for person: String) -> String {
"Hello, " + person + "!"
}
print(greeting(for: "Dave"))
// 打印 "Hello, Dave!"
func anotherGreeting(for person: String) -> String {
return "Hello, " + person + "!"
}
print(anotherGreeting(for: "Dave"))
// 打印 "Hello, Dave!"
```
`greeting(for:)` 函数的完整定义是打招呼内容的返回,这就意味着它能使用隐式返回这样更简短的形式。`anothergreeting(for:)` 函数返回同样的内容,却因为 `return` 关键字显得函数更长。任何一个可以被写成一行 `return` 语句的函数都可以忽略 `return`
正如你将会在 [简略的 Getter 声明](./10_Properties.md) 里看到的, 一个属性的 getter 也可以使用隐式返回的形式。
## 函数参数标签和参数名称 {#Function-Argument-Labels-and-Parameter-Names}
每个函数参数都有一个*参数标签argument label*以及一个*参数名称parameter name*。参数标签在调用函数的时候使用;调用的时候需要将函数的参数标签写在对应的参数前面。参数名称在函数的实现中使用。默认情况下,函数参数使用参数名称来作为它们的参数标签。
```swift
func someFunction(firstParameterName: Int, secondParameterName: Int) {
// In the function body, firstParameterName and secondParameterName
// refer to the argument values for the first and second parameters.
// 在函数体内,firstParameterName secondParameterName 代表参数中的第一个和第二个参数值
}
someFunction(firstParameterName: 1, secondParameterName: 2)
```
所有的参数都必须有一个独一无二的名字。虽然多个参数拥有同样的参数标签是可能的,但是一个唯一的函数标签能够使你的代码更具可读性。
<a name="specifying_argument_labels"></a>
### 参数标签 (Specifying Argument Labels)
### 指定参数标签 {#specifying-argument-labels}
你可以在数名称前指定它的参数标签,中间以空格分隔:
你可以在数名称前指定它的参数标签,中间以空格分隔:
```swift
func someFunction(argumentLabel parameterName: Int) {
// In the function body, parameterName refers to the argument value
// for that parameter.
// 在函数体内parameterName 代表参数值
}
```
@ -273,46 +256,41 @@ func greet(person: String, from hometown: String) -> String {
return "Hello \(person)! Glad you could visit from \(hometown)."
}
print(greet(person: "Bill", from: "Cupertino"))
// Prints "Hello Bill! Glad you could visit from Cupertino."
// 打印“Hello Bill! Glad you could visit from Cupertino.
```
参数标签的使用能够让一个函数在调用时更有表达力,更类似自然语言,并且仍保持了函数内部的可读性以及清晰的意图。
<a name="omitting_argument_labels"></a>
### 忽略参数标签(Omitting Argument Labels)
### 忽略参数标签 {#omitting-argument-labels}
如果你不希望为某个参数添加一个标签,可以使用一个下划线(`_`)来代替一个明确的参数标签。
如果你不希望为某个参数添加一个标签,可以使用一个下划线`_`来代替一个明确的参数标签。
```swift
func someFunction(_ firstParameterName: Int, secondParameterName: Int) {
// In the function body, firstParameterName and secondParameterName
// refer to the argument values for the first and second parameters.
// 在函数体内,firstParameterName secondParameterName 代表参数中的第一个和第二个参数值
}
someFunction(1, secondParameterName: 2)
```
如果一个参数有一个标签,那么在调用的时候必须使用标签来标记这个参数。
<a name="default_parameter_values"></a>
### 默认参数值 (Default Parameter Values)
### 默认参数值 {#default-parameter-values}
你可以在函数体中通过给参数赋值来为任意一个参数定义默认值Deafult Values)。当默认值被定义后,调用这个函数时可以忽略这个参数。
你可以在函数体中通过给参数赋值来为任意一个参数定义*默认值Deafult Value*。当默认值被定义后,调用这个函数时可以忽略这个参数。
```swift
func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) {
// If you omit the second argument when calling this function, then
// the value of parameterWithDefault is 12 inside the function body.
// 如果你在调用时候不传第二个参数parameterWithDefault 会值为 12 传入到函数体中。
}
someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6) // parameterWithDefault is 6
someFunction(parameterWithoutDefault: 4) // parameterWithDefault is 12
someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6) // parameterWithDefault = 6
someFunction(parameterWithoutDefault: 4) // parameterWithDefault = 12
```
将不带有默认值的参数放在函数参数列表的最前。一般来说,没有默认值的参数更加的重要,将不带默认值的参数放在最前保证在函数调用时,非默认参数的顺序是一致的,同时也使得相同的函数在不同情况下调用时显得更为清晰。
<a name="variadic_parameters"></a>
### 可变参数 (Variadic Parameters)
### 可变参数 {#variadic-parameters}
一个可变参数variadic parameter可以接受零个或多个值。函数调用时你可以用可变参数来指定函数参数可以被传入不确定数量的输入值。通过在变量类型名后面加入`...`)的方式来定义可变参数。
一个*可变参数variadic parameter*可以接受零个或多个值。函数调用时,你可以用可变参数来指定函数参数可以被传入不确定数量的输入值。通过在变量类型名后面加入(`...`)的方式来定义可变参数。
可变参数的传入值在函数体中变为此类型的一个数组。例如,一个叫做 `numbers``Double...` 型可变参数,在函数体内可以当做一个叫 `numbers``[Double]` 型的数组常量。
@ -327,26 +305,26 @@ func arithmeticMean(_ numbers: Double...) -> Double {
return total / Double(numbers.count)
}
arithmeticMean(1, 2, 3, 4, 5)
// returns 3.0, which is the arithmetic mean of these five numbers
// 返回 3.0, 是这 5 个数的平均数。
arithmeticMean(3, 8.25, 18.75)
// returns 10.0, which is the arithmetic mean of these three numbers
// 返回 10.0, 是这 3 个数的平均数。
```
>注意
一个函数最多只能拥有一个可变参数。
> 注意
>
> 一个函数最多只能拥有一个可变参数。
<a name="in_out_parameters"></a>
### 输入输出参数In-Out Parameters
### 输入输出参数 {#in-out-parameters}
函数参数默认是常量。试图在函数体中更改参数值将会导致编译错误(compile-time error)。这意味着你不能错误地更改参数值。如果你想要一个函数可以修改参数的值并且想要在这些修改在函数调用结束后仍然存在那么就应该把这个参数定义为输入输出参数In-Out Parameters
函数参数默认是常量。试图在函数体中更改参数值将会导致编译错误。这意味着你不能错误地更改参数值。如果你想要一个函数可以修改参数的值,并且想要在这些修改在函数调用结束后仍然存在,那么就应该把这个参数定义为*输入输出参数In-Out Parameters*
定义一个输入输出参数时,在参数定义前加 `inout` 关键字。一个`输入输出参数`有传入函数的值,这个值被函数修改,然后被传出函数,替换原来的值。想获取更多的关于输入输出参数的细节和相关的编译器优化,请查看`输入输出参数`一节。
定义一个输入输出参数时,在参数定义前加 `inout` 关键字。一个 `输入输出参数`有传入函数的值,这个值被函数修改,然后被传出函数,替换原来的值。想获取更多的关于输入输出参数的细节和相关的编译器优化,请查看 [输入输出参数](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID545) 一节。
你只能传递变量给输入输出参数。你不能传入常量或者字面量literal value,因为这些量是不能被修改的。当传入的参数作为输入输出参数时,需要在参数名前加 `&` 符,表示这个值可以被函数修改。
>注意
输入输出参数不能有默认值,而且可变参数不能用 `inout` 标记。
你只能传递变量给输入输出参数。你不能传入常量或者字面量,因为这些量是不能被修改的。当传入的参数作为输入输出参数时,需要在参数名前加 `&` 符,表示这个值可以被函数修改。
> 注意
>
> 输入输出参数不能有默认值,而且可变参数不能用 `inout` 标记。
下例中,`swapTwoInts(_:_:)` 函数有两个分别叫做 `a``b` 的输入输出参数:
@ -367,19 +345,18 @@ var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// Prints "someInt is now 107, and anotherInt is now 3"
// 打印“someInt is now 107, and anotherInt is now 3
```
从上面这个例子中,我们可以看到 `someInt``anotherInt` 的原始值在 `swapTwoInts(_:_:)` 函数中被修改,尽管它们的定义在函数体外。
>注意
输入输出参数和返回值是不一样的。上面的 `swapTwoInts` 函数并没有定义任何返回值,但仍然修改了 `someInt``anotherInt` 的值。输入输出参数是函数对函数体外产生影响的另一种方式。
> 注意
>
> 输入输出参数和返回值是不一样的。上面的 `swapTwoInts` 函数并没有定义任何返回值,但仍然修改了 `someInt` 和 `anotherInt` 的值。输入输出参数是函数对函数体外产生影响的另一种方式。
## 函数类型 {#Function-Types}
<a name="Function_Types"></a>
## 函数类型 (Function Types)
每个函数都有种特定的`函数类型`,函数的类型由函数的参数类型和返回类型组成。
每个函数都有种特定的*函数类型*,函数的类型由函数的参数类型和返回类型组成。
例如:
@ -394,7 +371,9 @@ func multiplyTwoInts(_ a: Int, _ b: Int) -> Int {
这个例子中定义了两个简单的数学函数:`addTwoInts``multiplyTwoInts`。这两个函数都接受两个 `Int` 值, 返回一个 `Int` 值。
这两个函数的类型是 `(Int, Int) -> Int`,可以解读为“这个函数类型有两个 `Int` 型的参数并返回一个 `Int` 型的值。”。
这两个函数的类型是 `(Int, Int) -> Int`,可以解读为:
“这个函数类型有两个 `Int` 型的参数并返回一个 `Int` 型的值”。
下面是另一个例子,一个没有参数,也没有返回值的函数:
@ -406,8 +385,7 @@ func printHelloWorld() {
这个函数的类型是:`() -> Void`,或者叫“没有参数,并返回 `Void` 类型的函数”。
<a name="using_function_types"></a>
### 使用函数类型 (Using Function Types)
### 使用函数类型 {#using-function-types}
在 Swift 中,使用函数类型就像使用其他类型一样。例如,你可以定义一个类型为函数的常量或变量,并将适当的函数赋值给它:
@ -419,7 +397,7 @@ var mathFunction: (Int, Int) -> Int = addTwoInts
”定义一个叫做 `mathFunction` 的变量,类型是‘一个有两个 `Int` 型的参数并返回一个 `Int` 型的值的函数’,并让这个新变量指向 `addTwoInts` 函数”。
`addTwoInts``mathFunction` 有同样的类型,所以这个赋值过程在 Swift 类型检查(type-check)中是允许的。
`addTwoInts``mathFunction` 有同样的类型,所以这个赋值过程在 Swift 类型检查type-check中是允许的。
现在,你可以用 `mathFunction` 来调用被赋值的函数了:
@ -430,7 +408,6 @@ print("Result: \(mathFunction(2, 3))")
有相同匹配类型的不同函数可以被赋值给同一个变量,就像非函数类型的变量一样:
```swift
mathFunction = multiplyTwoInts
print("Result: \(mathFunction(2, 3))")
@ -444,8 +421,7 @@ let anotherMathFunction = addTwoInts
// anotherMathFunction 被推断为 (Int, Int) -> Int 类型
```
<a name="function_types_as_parameter_types"></a>
### 函数类型作为参数类型 (Function Types as Parameter Types)
### 函数类型作为参数类型 {#function-types-as-parameter-types}
你可以用 `(Int, Int) -> Int` 这样的函数类型作为另一个函数的参数类型。这样你可以将函数的一部分实现留给函数的调用者来提供。
@ -456,7 +432,7 @@ func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
print("Result: \(mathFunction(a, b))")
}
printMathResult(addTwoInts, 3, 5)
// 打印 "Result: 8"
// 打印Result: 8
```
这个例子定义了 `printMathResult(_:_:_:)` 函数,它有三个参数:第一个参数叫 `mathFunction`,类型是 `(Int, Int) -> Int`,你可以传入任何这种类型的函数;第二个和第三个参数叫 `a``b`,它们的类型都是 `Int`,这两个值作为已给出的函数的输入值。
@ -465,12 +441,11 @@ printMathResult(addTwoInts, 3, 5)
`printMathResult(_:_:_:)` 函数的作用就是输出另一个适当类型的数学函数的调用结果。它不关心传入函数是如何实现的,只关心传入的函数是不是一个正确的类型。这使得 `printMathResult(_:_:_:)` 能以一种类型安全type-safe的方式将一部分功能转给调用者实现。
<a name="function_types_as_return_types"></a>
### 函数类型作为返回类型 (Function Types as Return Types)
### 函数类型作为返回类型 {#function-types-as-return-types}
你可以用函数类型作为另一个函数的返回类型。你需要做的是在返回箭头(->)后写一个完整的函数类型。
下面的这个例子中定义了两个简单函数,分别是 `stepForward``stepBackward``stepForward`函数返回一个比输入值大 `1` 的值。`stepBackward` 函数返回一个比输入值小 `1` 的值。这两个函数的类型都是 `(Int) -> Int`
下面的这个例子中定义了两个简单函数,分别是 `stepForward(_:)``stepBackward(_:)``stepForward(_:)` 函数返回一个比输入值大 `1` 的值。`stepBackward(_:)` 函数返回一个比输入值小 `1` 的值。这两个函数的类型都是 `(Int) -> Int`
```swift
func stepForward(_ input: Int) -> Int {
@ -481,7 +456,7 @@ func stepBackward(_ input: Int) -> Int {
}
```
如下名为 `chooseStepFunction(_:)` 的函数,它的返回类型是 `(Int) -> Int` 类型的函数。`chooseStepFunction(_:)` 根据布尔值 `backwards` 来返回 `stepForward(_:)` 函数或 `stepBackward(_:)` 函数:
如下名为 `chooseStepFunction(backward:)` 的函数,它的返回类型是 `(Int) -> Int` 类型的函数。`chooseStepFunction(backward:)` 根据布尔值 `backwards` 来返回 `stepForward(_:)` 函数或 `stepBackward(_:)` 函数:
```swift
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
@ -489,7 +464,7 @@ func chooseStepFunction(backward: Bool) -> (Int) -> Int {
}
```
你现在可以用 `chooseStepFunction(_:)` 来获得两个函数其中的一个:
你现在可以用 `chooseStepFunction(backward:)` 来获得两个函数其中的一个:
```swift
var currentValue = 3
@ -499,8 +474,7 @@ let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
上面这个例子中计算出从 `currentValue` 逐渐接近到0是需要向正数走还是向负数走。`currentValue` 的初始值是 `3`,这意味着 `currentValue > 0` 为真true这将使得 `chooseStepFunction(_:)` 返回 `stepBackward(_:)` 函数。一个指向返回的函数的引用保存在了 `moveNearerToZero` 常量中。
现在moveNearerToZero 指向了正确的函数,它可以被用来数到零:
现在,`moveNearerToZero` 指向了正确的函数,它可以被用来数到零:
```swift
print("Counting to zero:")
@ -516,14 +490,13 @@ print("zero!")
// zero!
```
<a name="Nested_Functions"></a>
## 嵌套函数 (Nested Functions)
## 嵌套函数 {#Nested-Functions}
到目前为止本章中你所见到的所有函数都叫`全局`函数global functions它们定义在`全局域`中。你也可以把函数定义在别的函数体中,称作 `嵌套函数`nested functions
到目前为止本章中你所见到的所有函数都叫*全局函数global functions*,它们定义在全局域中。你也可以把函数定义在别的函数体中,称作 *嵌套函数nested functions*
默认情况下嵌套函数是对外界不可见的但是可以被它们的外围函数enclosing function调用。一个外围函数也可以返回它的某一个嵌套函数使得这个函数可以在其他域中被使用。
你可以用返回嵌套函数的方式重写 `chooseStepFunction(_:)` 函数:
你可以用返回嵌套函数的方式重写 `chooseStepFunction(backward:)` 函数:
```swift
func chooseStepFunction(backward: Bool) -> (Int) -> Int {

View File

@ -1,40 +1,14 @@
# 闭包Closures
-----------------
# 闭包
> 1.0
> 翻译:[wh1100717](https://github.com/wh1100717)
> 校对:[lyuka](https://github.com/lyuka)
*闭包*是自包含的函数代码块可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块blocks以及其他一些编程语言中的匿名函数Lambdas比较相似。
> 2.0
> 翻译+校对:[100mango](https://github.com/100mango)
> 2.1
> 翻译:[100mango](https://github.com/100mango), [magicdict](https://github.com/magicdict)
> 校对:[shanks](http://codebuild.me)
>
> 2.2
> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-12
>
> 3.0
> 翻译:[Lanford](https://github.com/LanfordCai) 2016-09-19
本页包含内容:
- [闭包表达式Closure Expressions](#closure_expressions)
- [尾随闭包Trailing Closures](#trailing_closures)
- [值捕获Capturing Values](#capturing_values)
- [闭包是引用类型Closures Are Reference Types](#closures_are_reference_types)
- [逃逸闭包(Escaping Closures) ](#escaping_closures)
- [自动闭包Autoclosures](#autoclosures)
*闭包*是自包含的函数代码块可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块blocks以及其他一些编程语言中的匿名函数比较相似。
闭包可以捕获和存储其所在上下文中任意常量和变量的引用。*闭合、包裹*常量和变量所谓闭包也。Swift 会为你管理在捕获过程中涉及到的所有内存操作。
闭包可以捕获和存储其所在上下文中任意常量和变量的引用。被称为*包裹*常量和变量。 Swift 会为你管理在捕获过程中涉及到的所有内存操作。
> 注意
> 如果你不熟悉捕获capturing这个概念也不用担心你可以在[值捕获](#capturing_values)章节对其进行详细了解。
>
> 如果你不熟悉捕获capturing这个概念也不用担心在 [值捕获](#capturing_values) 章节有它更详细的介绍。
在[函数](./06_Functions.html)章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采如下三种形式之一:
[函数](./06_Functions.md) 章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采如下三种形式之一:
* 全局函数是一个有名字但不会捕获任何值的闭包
* 嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
@ -45,20 +19,17 @@ Swift 的闭包表达式拥有简洁的风格,并鼓励在常见场景中进
* 利用上下文推断参数和返回值类型
* 隐式返回单表达式闭包,即单表达式闭包可以省略 `return` 关键字
* 参数名称缩写
* 尾随Trailing闭包语法
* 尾随闭包语法
<a name="closure_expressions"></a>
## 闭包表达式Closure Expressions
## 闭包表达式 {#closure-expressions}
[嵌套函数](./06_Functions.md#Nested_Functions) 作为复杂函数的一部分时,它自包含代码块式的定义和命名形式在使用上带来了方便。当然,编写未完整声明和没有函数名的类函数结构代码是很有用的,尤其是在编码中涉及到函数作为参数的那些方法时。
[嵌套函数](./06_Functions.html#nested_function)是一个在较复杂函数中方便进行命名和定义自包含代码模块的方式。当然,有时候编写小巧的没有完整定义和命名的类函数结构也是很有用处的,尤其是在你处理一些函数并需要将另外一些函数作为该函数的参数时
*闭包表达式*是一种构建内联闭包的方式,它的语法简洁。在保证不丢失它语法清晰明了的同时,闭包表达式提供了几种优化的语法简写形式。下面通过对 `sorted(by:)` 这一个案例的多次迭代改进来展示这个过程,每次迭代都使用了更加简明的方式描述了相同功能。
*闭包表达式*是一种利用简洁语法构建内联闭包的方式。闭包表达式提供了一些语法优化,使得撰写闭包变得简单明了。下面闭包表达式的例子通过使用几次迭代展示了 `sorted(by:)` 方法定义和语法优化的方式。每一次迭代都用更简洁的方式描述了相同的功能。
### 排序方法 {#the-sorted-function}
<a name="the_sorted_function"></a>
### sorted 方法The Sorted Method
Swift 标准库提供了名为 `sorted(by:)` 的方法,它会根据你所提供的用于排序的闭包函数将已知类型数组中的值进行排序。一旦排序完成,`sorted(by:)` 方法会返回一个与原数组大小相同,包含同类型元素且元素已正确排序的新数组。原数组不会被 `sorted(by:)` 方法修改。
Swift 标准库提供了名为 `sorted(by:)` 的方法,它会基于你提供的排序闭包表达式的判断结果对数组中的值(类型确定)进行排序。一旦它完成排序过程,`sorted(by:)` 方法会返回一个与旧数组类型大小相同类型的新数组,该数组的元素有着正确的排序顺序。原数组不会被 `sorted(by:)` 方法修改。
下面的闭包表达式示例使用 `sorted(by:)` 方法对一个 `String` 类型的数组进行字母逆序排序。以下是初始数组:
@ -66,7 +37,7 @@ Swift 标准库提供了名为 `sorted(by:)` 的方法,它会根据你所提
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
```
`sorted(by:)` 方法接受一个闭包,该闭包函数需要传入与数组元素类型相同的两个值,并返回一个布尔类型值来表明当排序结束后传入的第一个参数排在第二个参数前面还是后面。如果第一个参数值出现在第二个参数值*前面*,排序闭包函数需要返回`true`,反之返回`false`
`sorted(by:)` 方法接受一个闭包,该闭包函数需要传入与数组元素类型相同的两个值,并返回一个布尔类型值来表明当排序结束后传入的第一个参数排在第二个参数前面还是后面。如果第一个参数值出现在第二个参数值*前面*,排序闭包函数需要返回 `true`,反之返回 `false`
该例子对一个 `String` 类型的数组进行排序,因此排序闭包函数类型需为 `(String, String) -> Bool`
@ -84,18 +55,17 @@ var reversedNames = names.sorted(by: backward)
然而,以这种方式来编写一个实际上很简单的表达式(`a > b`),确实太过繁琐了。对于这个例子来说,利用闭包表达式语法可以更好地构造一个内联排序闭包。
<a name="closure_expression_syntax"></a>
### 闭包表达式语法Closure Expression Syntax
### 闭包表达式语法 {#closure-expression-syntax}
闭包表达式语法有如下的一般形式:
```swift
{ (parameters) -> returnType in
{ (parameters) -> return type in
statements
}
```
闭包表达式参数可以是inout参数但不能设定默认值。也可以使用具名的可变参数(译者注:但是如果可变参数不放在参数列表的最后一位的话,调用闭包的时时编译器将报错。可参考[这里](http://stackoverflow.com/questions/39548852/swift-3-0-closure-expression-what-if-the-variadic-parameters-not-at-the-last-pl)。元组也可以作为参数和返回值。
*闭包表达式参数* 可以是 in-out 参数,但不能设定默认值。如果你命名了可变参数,也可以使用此可变参数。元组也可以作为参数和返回值。
下面的例子展示了之前 `backward(_:_:)` 函数对应的闭包表达式版本的代码:
@ -107,7 +77,7 @@ reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
需要注意的是内联闭包参数和返回值类型声明与 `backward(_:_:)` 函数类型声明相同。在这两种方式中,都写成了 `(s1: String, s2: String) -> Bool`。然而在内联闭包表达式中,函数和返回值类型都写在*大括号内*,而不是大括号外。
闭包的函数体部分由关键字`in`引入。该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。
闭包的函数体部分由关键字 `in` 引入。该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。
由于这个闭包的函数体部分如此短,以至于可以将其改写成一行代码:
@ -117,8 +87,7 @@ reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1
该例中 `sorted(by:)` 方法的整体调用保持不变,一对圆括号仍然包裹住了方法的整个参数。然而,参数现在变成了内联闭包。
<a name="inferring_type_from_context"></a>
### 根据上下文推断类型Inferring Type From Context
### 根据上下文推断类型 {#inferring-type-from-context}
因为排序闭包函数是作为 `sorted(by:)` 方法的参数传入的Swift 可以推断其参数和返回值的类型。`sorted(by:)` 方法被一个字符串数组调用,因此其参数必须是 `(String, String) -> Bool` 类型的函数。这意味着 `(String, String)``Bool` 类型并不需要作为闭包表达式定义的一部分。因为所有的类型都可以被正确推断,返回箭头(`->`)和围绕在参数周围的括号也可以被省略:
@ -130,8 +99,7 @@ reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
尽管如此,你仍然可以明确写出有着完整格式的闭包。如果完整格式的闭包能够提高代码的可读性,则我们更鼓励采用完整格式的闭包。而在 `sorted(by:)` 方法这个例子里,显然闭包的目的就是排序。由于这个闭包是为了处理字符串数组的排序,因此读者能够推测出这个闭包是用于字符串处理的。
<a name="implicit_returns_from_single_expression_closures"></a>
### 单表达式闭包隐式返回Implicit Returns From Single-Expression Closures
### 单表达式闭包的隐式返回 {#implicit-returns-from-single-expression-closures}
单行表达式闭包可以通过省略 `return` 关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为:
@ -141,34 +109,31 @@ reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
在这个例子中,`sorted(by:)` 方法的参数类型明确了闭包必须返回一个 `Bool` 类型值。因为闭包函数体只包含了一个单一表达式(`s1 > s2`),该表达式返回 `Bool` 类型值,因此这里没有歧义,`return` 关键字可以省略。
<a name="shorthand_argument_names"></a>
### 参数名称缩写Shorthand Argument Names
### 参数名称缩写 {#shorthand-argument-names}
Swift 自动为内联闭包提供了参数名称缩写功能,你可以直接通过 `$0``$1``$2` 来顺序调用闭包的参数,以此类推。
如果你在闭包表达式中使用参数名称缩写,你可以在闭包定义中省略参数列表,并且对应参数名称缩写的类型会通过函数类型进行推断。`in`关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:
如果你在闭包表达式中使用参数名称缩写,你可以在闭包定义中省略参数列表,并且对应参数名称缩写的类型会通过函数类型进行推断。`in` 关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:
```swift
reversedNames = names.sorted(by: { $0 > $1 } )
```
在这个例子中,`$0``$1`表示闭包中第一个和第二个 `String` 类型的参数。
在这个例子中,`$0``$1` 表示闭包中第一个和第二个 `String` 类型的参数。
<a name="operator_methods"></a>
### 运算符方法Operator Methods
### 运算符方法 {#operator-methods}
实际上还有一种更简短的方式来编写上面例子中的闭包表达式。Swift 的 `String` 类型定义了关于大于号(`>`)的字符串实现,其作为一个函数接受两个 `String` 类型的参数并返回 `Bool` 类型的值。而这正好与 `sorted(by:)` 方法的参数需要的函数类型相符合。因此你可以简单地传递一个大于号Swift 可以自动推断出你想使用大于号的字符串函数实现:
实际上还有一种更*简短的*方式来编写上面例子中的闭包表达式。Swift 的 `String` 类型定义了关于大于号(`>`)的字符串实现,其作为一个函数接受两个 `String` 类型的参数并返回 `Bool` 类型的值。而这正好与 `sorted(by:)` 方法的参数需要的函数类型相符合。因此你可以简单地传递一个大于号Swift 可以自动推断找到系统自带的那个字符串函数实现:
```swift
reversedNames = names.sorted(by: >)
```
更多关于运算符方法的内容请查看[运算符方法](./25_Advanced_Operators.html#operator_methods)。
更多关于运算符方法的内容请查看 [运算符方法](./26_Advanced_Operators.md#operator_methods)。
<a name="trailing_closures"></a>
## 尾随闭包Trailing Closures
## 尾随闭包 {#trailing-closures}
如果你需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用*尾随闭包*来增强函数的可读性。尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,你不用写出它的参数标签:
如果你需要将一个很长的闭包表达式作为最后一个参数传递给函数,将这个闭包替换成为尾随闭包的形式很有用。尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,你不用写出它的参数标签:
```swift
func someFunctionThatTakesAClosure(closure: () -> Void) {
@ -186,7 +151,7 @@ someFunctionThatTakesAClosure() {
}
```
在[闭包表达式语法](#closure_expression_syntax)一节中作为 `sorted(by:)` 方法参数的字符串排序闭包可以改写为
[闭包表达式语法](#closure_expression_syntax) 上章节中的字符串排序闭包可以作为尾随包的形式改写在 `sorted(by:)` 方法圆括号的外面
```swift
reversedNames = names.sorted() { $0 > $1 }
@ -202,7 +167,7 @@ reversedNames = names.sorted { $0 > $1 }
当提供给数组的闭包应用于每个数组元素后,`map(_:)` 方法将返回一个新的数组,数组中包含了与原数组中的元素一一对应的映射后的值。
下例介绍了如何在 `map(_:)` 方法中使用尾随闭包将 `Int` 类型数组 `[16, 58, 510]` 转换为包含对应 `String` 类型的值的数组`["OneSix", "FiveEight", "FiveOneZero"]`
下例介绍了如何在 `map(_:)` 方法中使用尾随闭包将 `Int` 类型数组 `[16, 58, 510]` 转换为包含对应 `String` 类型的值的数组 `["OneSix", "FiveEight", "FiveOneZero"]`
```swift
let digitNames = [
@ -238,7 +203,8 @@ let strings = numbers.map {
闭包表达式在每次被调用的时候创建了一个叫做 `output` 的字符串并返回。其使用求余运算符(`number % 10`)计算最后一位数字并利用 `digitNames` 字典获取所映射的字符串。这个闭包能够用于创建任意正整数的字符串表示。
> 注意
> 字典 `digitNames` 下标后跟着一个叹号(`!`因为字典下标返回一个可选值optional value表明该键不存在时会查找失败。在上例中由于可以确定 `number % 10` 总是 `digitNames` 字典的有效下标,因此叹号可以用于强制解包 (force-unwrap) 存储在下标的可选类型的返回值中的`String`类型的值。
>
> 字典 `digitNames` 下标后跟着一个叹号(`!`因为字典下标返回一个可选值optional value表明该键不存在时会查找失败。在上例中由于可以确定 `number % 10` 总是 `digitNames` 字典的有效下标因此叹号可以用于强制解包force-unwrap存储在下标的可选类型的返回值中的 `String` 类型的值。
`digitNames` 字典中获取的字符串被添加到 `output` 的*前部*,逆序建立了一个字符串版本的数字。(在表达式 `number % 10` 中,如果 `number``16`,则返回 `6``58` 返回 `8``510` 返回 `0`。)
@ -248,14 +214,13 @@ let strings = numbers.map {
在上面的例子中,通过尾随闭包语法,优雅地在函数后封装了闭包的具体功能,而不再需要将整个闭包包裹在 `map(_:)` 方法的括号内。
<a name="capturing_values"></a>
## 值捕获Capturing Values
## 值捕获 {#capturing-values}
闭包可以在其被定义的上下文中*捕获*常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
Swift 中,可以捕获值的闭包的最简单形式是嵌套函数,也就是定义在其他函数的函数体内的函数。嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。
举个例子,这有一个叫做 `makeIncrementor` 的函数,其包含了一个叫做 `incrementor` 的嵌套函数。嵌套函数 `incrementor()` 从上下文中捕获了两个值,`runningTotal``amount`。捕获这些值之后,`makeIncrementor``incrementor` 作为闭包返回。每次调用 `incrementor` 时,其会以 `amount` 作为增量增加 `runningTotal` 的值。
举个例子,这有一个叫做 `makeIncrementer` 的函数,其包含了一个叫做 `incrementer` 的嵌套函数。嵌套函数 `incrementer()` 从上下文中捕获了两个值,`runningTotal``amount`。捕获这些值之后,`makeIncrementer``incrementer` 作为闭包返回。每次调用 `incrementer` 时,其会以 `amount` 作为增量增加 `runningTotal` 的值。
```swift
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
@ -268,11 +233,11 @@ func makeIncrementer(forIncrement amount: Int) -> () -> Int {
}
```
`makeIncrementor` 返回类型为 `() -> Int`。这意味着其返回的是一个*函数*,而非一个简单类型的值。该函数在每次调用时不接受参数,只返回一个 `Int` 类型的值。关于函数返回其他函数的内容,请查看[函数类型作为返回类型](./06_Functions.html#function_types_as_return_types)。
`makeIncrementer` 返回类型为 `() -> Int`。这意味着其返回的是一个*函数*,而非一个简单类型的值。该函数在每次调用时不接受参数,只返回一个 `Int` 类型的值。关于函数返回其他函数的内容,请查看 [函数类型作为返回类型](./06_Functions.md#function_types_as_return_types)。
`makeIncrementer(forIncrement:)` 函数定义了一个初始值为 `0` 的整型变量 `runningTotal`,用来存储当前总计数值。该值为 `incrementor` 的返回值。
`makeIncrementer(forIncrement:)` 函数定义了一个初始值为 `0` 的整型变量 `runningTotal`,用来存储当前总计数值。该值为 `incrementer` 的返回值。
`makeIncrementer(forIncrement:)` 有一个 `Int` 类型的参数,其外部参数名为 `forIncrement`,内部参数名为 `amount`,该参数表示每次 `incrementor` 被调用时 `runningTotal` 将要增加的量。`makeIncrementer` 函数还定义了一个嵌套函数 `incrementor`,用来执行实际的增加操作。该函数简单地使 `runningTotal` 增加 `amount`,并将其返回。
`makeIncrementer(forIncrement:)` 有一个 `Int` 类型的参数,其外部参数名为 `forIncrement`,内部参数名为 `amount`,该参数表示每次 `incrementer` 被调用时 `runningTotal` 将要增加的量。`makeIncrementer` 函数还定义了一个嵌套函数 `incrementer`,用来执行实际的增加操作。该函数简单地使 `runningTotal` 增加 `amount`,并将其返回。
如果我们单独考虑嵌套函数 `incrementer()`,会发现它有些不同寻常:
@ -286,16 +251,18 @@ func incrementer() -> Int {
`incrementer()` 函数并没有任何参数,但是在函数体内访问了 `runningTotal``amount` 变量。这是因为它从外围函数捕获了 `runningTotal``amount` 变量的*引用*。捕获引用保证了 `runningTotal``amount` 变量在调用完 `makeIncrementer` 后不会消失,并且保证了在下一次执行 `incrementer` 函数时,`runningTotal` 依旧存在。
> 注意
>
> 为了优化如果一个值不会被闭包改变或者在闭包创建后不会改变Swift 可能会改为捕获并保存一份对值的拷贝。
>
> Swift 也会负责被捕获变量的所有内存管理工作,包括释放不再需要的变量。
下面是一个使用 `makeIncrementor` 的例子:
下面是一个使用 `makeIncrementer` 的例子:
```swift
let incrementByTen = makeIncrementor(forIncrement: 10)
let incrementByTen = makeIncrementer(forIncrement: 10)
```
该例子定义了一个叫做 `incrementByTen` 的常量,该常量指向一个每次调用会将其 `runningTotal` 变量增加 `10``incrementor` 函数。调用这个函数多次可以得到以下结果:
该例子定义了一个叫做 `incrementByTen` 的常量,该常量指向一个每次调用会将其 `runningTotal` 变量增加 `10``incrementer` 函数。调用这个函数多次可以得到以下结果:
```swift
incrementByTen()
@ -306,10 +273,10 @@ incrementByTen()
// 返回的值为30
```
如果你创建了另一个 `incrementor`,它会有属于自己的引用,指向一个全新、独立的 `runningTotal` 变量:
如果你创建了另一个 `incrementer`,它会有属于自己的引用,指向一个全新、独立的 `runningTotal` 变量:
```swift
let incrementBySeven = makeIncrementor(forIncrement: 7)
let incrementBySeven = makeIncrementer(forIncrement: 7)
incrementBySeven()
// 返回的值为7
```
@ -321,11 +288,11 @@ incrementByTen()
// 返回的值为40
```
> 注意
> 如果你将闭包赋值给一个类实例的属性并且该闭包通过访问该实例或其成员而捕获了该实例你将在闭包和该实例间创建一个循环强引用。Swift 使用捕获列表来打破这种循环强引用。更多信息,请参考[闭包引起的循环强引用](./16_Automatic_Reference_Counting.html#strong_reference_cycles_for_closures)。
> 注意
>
> 如果你将闭包赋值给一个类实例的属性并且该闭包通过访问该实例或其成员而捕获了该实例你将在闭包和该实例间创建一个循环强引用。Swift 使用捕获列表来打破这种循环强引用。更多信息,请参考 [闭包引起的循环强引用](./23_Automatic_Reference_Counting.md#strong_reference_cycles_for_closures)。
<a name="closures_are_reference_types"></a>
## 闭包是引用类型Closures Are Reference Types
## 闭包是引用类型 {#closures-are-reference-types}
上面的例子中,`incrementBySeven``incrementByTen` 都是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量的值。这是因为函数和闭包都是*引用类型*。
@ -339,10 +306,9 @@ alsoIncrementByTen()
// 返回的值为50
```
<a name="escaping_closures"></a>
## 逃逸闭包(Escaping Closures)
## 逃逸闭包 {#escaping-closures}
当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中*逃逸*。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 `@escaping`,用来指明这个闭包是允许“逃逸”出这个函数的。
当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中*逃逸*。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 `@escaping`,用来指明这个闭包是允许“逃逸”出这个函数的。
一种能使闭包“逃逸”出函数的方法是,将这个闭包保存在一个函数外部定义的变量中。举个例子,很多启动异步操作的函数接受一个闭包参数作为 completion handler。这类函数会在异步操作开始之后立刻返回但是闭包直到异步操作结束后才会被调用。在这种情况下闭包需要“逃逸”出函数因为闭包需要在函数返回之后被调用。例如
@ -357,7 +323,6 @@ func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
将一个闭包标记为 `@escaping` 意味着你必须在闭包中显式地引用 `self`。比如说,在下面的代码中,传递到 `someFunctionWithEscapingClosure(_:)` 中的闭包是一个逃逸闭包,这意味着它需要显式地引用 `self`。相对的,传递到 `someFunctionWithNonescapingClosure(_:)` 中的闭包是一个非逃逸闭包,这意味着它可以隐式引用 `self`
```swift
func someFunctionWithNonescapingClosure(closure: () -> Void) {
closure()
@ -374,15 +339,14 @@ class SomeClass {
let instance = SomeClass()
instance.doSomething()
print(instance.x)
// 打印出 "200"
// 打印出200
completionHandlers.first?()
print(instance.x)
// 打印出 "100"
// 打印出100
```
<a name="autoclosures"></a>
## 自动闭包Autoclosures
## 自动闭包 {#autoclosures}
*自动闭包*是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包。
@ -393,16 +357,16 @@ print(instance.x)
```swift
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
print(customersInLine.count)
// 打印出 "5"
// 打印出“5”
let customerProvider = { customersInLine.remove(at: 0) }
print(customersInLine.count)
// 打印出 "5"
// 打印出“5”
print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
print(customersInLine.count)
// 打印出 "4"
// 打印出“4”
```
尽管在闭包的代码中,`customersInLine` 的第一个元素被移除了,不过在闭包被调用之前,这个元素是不会被移除的。如果这个闭包永远不被调用,那么在闭包里面的表达式将永远不会执行,那意味着列表中的元素永远不会被移除。请注意,`customerProvider` 的类型不是 `String`,而是 `() -> String`,一个没有参数且返回值为 `String` 的函数。
@ -415,7 +379,7 @@ func serve(customer customerProvider: () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// 打印出 "Now serving Alex!"
// 打印出Now serving Alex!
```
上面的 `serve(customer:)` 函数接受一个返回顾客名字的显式的闭包。下面这个版本的 `serve(customer:)` 完成了相同的操作,不过它并没有接受一个显式的闭包,而是通过将参数标记为 `@autoclosure` 来接收一个自动闭包。现在你可以将该函数当作接受 `String` 类型参数(而非闭包)的函数来调用。`customerProvider` 参数将自动转化为一个闭包,因为该参数被标记了 `@autoclosure` 特性。
@ -426,16 +390,17 @@ func serve(customer customerProvider: @autoclosure () -> String) {
print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// 打印出 "Now serving Ewa!"
// 打印Now serving Ewa!
```
> 注意
>
> 过度使用 `autoclosures` 会让你的代码变得难以理解。上下文和函数名应该能够清晰地表明求值是被延迟执行的。
如果你想让一个自动闭包可以“逃逸”,则应该同时使用 `@autoclosure``@escaping` 属性。`@escaping` 属性的讲解见上面的[逃逸闭包](#escaping_closures)。
如果你想让一个自动闭包可以“逃逸”,则应该同时使用 `@autoclosure``@escaping` 属性。`@escaping` 属性的讲解见上面的 [逃逸闭包](#escaping_closures)。
```swift
// customersInLine is ["Barry", "Daniella"]
// customersInLine i= ["Barry", "Daniella"]
var customerProviders: [() -> String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
customerProviders.append(customerProvider)
@ -444,13 +409,12 @@ collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))
print("Collected \(customerProviders.count) closures.")
// 打印出 "Collected 2 closures."
// 打印Collected 2 closures.
for customerProvider in customerProviders {
print("Now serving \(customerProvider())!")
}
// 打印出 "Now serving Barry!"
// 打印出 "Now serving Daniella!"
// 打印Now serving Barry!
// 打印Now serving Daniella!
```
在上面的代码中,`collectCustomerProviders(_:)` 函数并没有调用传入的 `customerProvider` 闭包,而是将闭包追加到了 `customerProviders` 数组中。这个数组定义在函数作用域范围外,这意味着数组内的闭包能够在函数返回之后被调用。因此,`customerProvider` 参数必须允许“逃逸”出函数作用域。

View File

@ -1,42 +1,18 @@
# 枚举Enumerations
---
> 1.0
> 翻译:[yankuangshi](https://github.com/yankuangshi)
> 校对:[shinyzhu](https://github.com/shinyzhu)
> 2.0
> 翻译+校对:[futantan](https://github.com/futantan)
> 2.1
> 翻译:[Channe](https://github.com/Channe)
> 校对:[shanks](http://codebuild.me)
> 2.2
> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-13
本页内容包含:
- [枚举语法Enumeration Syntax](#enumeration_syntax)
- [使用 Switch 语句匹配枚举值Matching Enumeration Values with a Switch Statement](#matching_enumeration_values_with_a_switch_statement)
- [关联值Associated Values](#associated_values)
- [原始值Raw Values](#raw_values)
- [递归枚举Recursive Enumerations](#recursive_enumerations)
# 枚举
*枚举*为一组相关的值定义了一个共同的类型,使你可以在你的代码中以类型安全的方式来使用这些值。
如果你熟悉 C 语言,你会知道在 C 语言中枚举会为一组整型值分配相关联的名称。Swift 中的枚举更加灵活,不必给每一个枚举成员提供一个值。如果给枚举成员提供一个值(称为原始值),则该值的类型可以是字符串字符,或是一个整型值或浮点数。
如果你熟悉 C 语言,你会知道在 C 语言中枚举会为一组整型值分配相关联的名称。Swift 中的枚举更加灵活,不必给每一个枚举成员提供一个值。如果给枚举成员提供一个值(称为原始值),则该值的类型可以是字符串字符,或是一个整型值或浮点数。
此外枚举成员可以指定任意类型的关联值存储到枚举成员中就像其他语言中的联合体unions和变体variants。每一个枚举成员都可以有适当类型的关联值。
此外,枚举成员可以指定*任意*类型的关联值存储到枚举成员中就像其他语言中的联合体unions和变体variants你可以在一个枚举中定义一组相关的枚举成员,每一个枚举成员都可以有适当类型的关联值。
在 Swift 中枚举类型是一等first-class类型。它们采用了很多在传统上只被类class所支持的特性例如计算属性computed properties用于提供枚举值的附加信息实例方法instance methods用于提供和枚举值相关联的功能。枚举也可以定义构造函数initializers来提供一个初始值可以在原始实现的基础上扩展它们的功能还可以遵协议protocols来提供标准的功能。
在 Swift 中枚举类型是一等first-class类型。它们采用了很多在传统上只被类class所支持的特性例如计算属性computed properties用于提供枚举值的附加信息实例方法instance methods用于提供和枚举值相关联的功能。枚举也可以定义构造函数initializers来提供一个初始值可以在原始实现的基础上扩展它们的功能还可以遵协议protocols来提供标准的功能。
了解更多相关信息,请参见[属性Properties](./10_Properties.html)[方法Methods](./11_Methods.html)[构造过程Initialization](./14_Initialization.html)[扩展Extensions](./21_Extensions.html)和[协议Protocols](./22_Protocols.html)。
了解更多相关信息,请参见 [属性](./10_Properties.md)[方法](./11_Methods.md)[构造过程](./14_Initialization.md)[扩展](./20_Extensions.md) 和 [协议](./21_Protocols.md)。
<a name="enumeration_syntax"></a>
## 枚举语法
## 枚举语法 {#enumeration-syntax}
使用`enum`关键词来创建枚举并且把它们的整个定义放在一对大括号内:
使用 `enum` 关键词来创建枚举并且把它们的整个定义放在一对大括号内:
```swift
enum SomeEnumeration {
@ -48,239 +24,264 @@ enum SomeEnumeration {
```swift
enum CompassPoint {
case North
case South
case East
case West
case north
case south
case east
case west
}
```
枚举中定义的值(如 `North``South``East``West`)是这个枚举的*成员值*(或*成员*)。你使用`case`关键字来定义一个新的枚举成员值。
枚举中定义的值(如 `north``south``east``west`)是这个枚举的*成员值*(或*成员*)。你可以使用 `case` 关键字来定义一个新的枚举成员值。
> 注意
> 与 C 和 Objective-C 不同Swift 的枚举成员在被创建时不会被赋予一个默认的整型值。在上面的`CompassPoint`例子中,`North``South``East`和`West`不会被隐式地赋值为`0``1``2`和`3`。相反,这些枚举成员本身就是完备的值,这些值的类型是已经明确定义好的`CompassPoint`类型。
> 注意
>
> 与 C 和 Objective-C 不同Swift 的枚举成员在被创建时不会被赋予一个默认的整型值。在上面的 `CompassPoint` 例子中,`north``south``east` 和 `west` 不会被隐式地赋值为 `0``1``2` 和 `3`。相反,这些枚举成员本身就是完备的值,这些值的类型是已经明确定义好的 `CompassPoint` 类型。
多个成员值可以出现在同一行上,用逗号隔开:
```swift
enum Planet {
case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}
```
每个枚举定义了一个全新的类型。像 Swift 中其他类型一样,它们的名字(例如`CompassPoint``Planet`应该以一个大写字母开头。给枚举类型起一个单数名字而不是复数名字,以便于读起来更加容易理解
每个枚举定义了一个全新的类型。像 Swift 中其他类型一样,它们的名字(例如 `CompassPoint``Planet`)以一个大写字母开头。给枚举类型起一个单数名字而不是复数名字,以便于:
```swift
var directionToHead = CompassPoint.West
var directionToHead = CompassPoint.west
```
`directionToHead`的类型可以在它被`CompassPoint`的某个值初始化时推断出来。一旦`directionToHead`被声明为`CompassPoint`类型,你可以使用更简短的点语法将其设置为另一个`CompassPoint`的值:
`directionToHead` 的类型可以在它被 `CompassPoint` 的某个值初始化时推断出来。一旦 `directionToHead` 被声明为 `CompassPoint` 类型,你可以使用更简短的点语法将其设置为另一个 `CompassPoint` 的值:
```swift
directionToHead = .East
directionToHead = .east
```
`directionToHead`的类型已知时,再次为其赋值可以省略枚举类型名。在使用具有显式类型的枚举值时,这种写法让代码具有更好的可读性。
`directionToHead` 的类型已知时,再次为其赋值可以省略枚举类型名。在使用具有显式类型的枚举值时,这种写法让代码具有更好的可读性。
<a name="matching_enumeration_values_with_a_switch_statement"></a>
## 使用 Switch 语句匹配枚举值
## 使用 Switch 语句匹配枚举值 {#matching-enumeration-values-with-a-switch-statement}
你可以使用`switch`语句匹配单个枚举值:
你可以使用 `switch` 语句匹配单个枚举值:
```swift
directionToHead = .South
directionToHead = .south
switch directionToHead {
case .North:
print("Lots of planets have a north")
case .South:
print("Watch out for penguins")
case .East:
print("Where the sun rises")
case .West:
print("Where the skies are blue")
case .north:
print("Lots of planets have a north")
case .south:
print("Watch out for penguins")
case .east:
print("Where the sun rises")
case .west:
print("Where the skies are blue")
}
// 输出 "Watch out for penguins”
// 打印“Watch out for penguins”
```
你可以这样理解这段代码:
“判断`directionToHead`的值。当它等于`.North`,打印`“Lots of planets have a north”`。当它等于`.South`,打印`“Watch out for penguins”`。”
“判断 `directionToHead` 的值。当它等于 `.north`,打印 `“Lots of planets have a north”`。当它等于 `.south`,打印 `“Watch out for penguins”`。”
……以此类推。
正如在[控制流Control Flow](./05_Control_Flow.html)中介绍的那样,在判断一个枚举类型的值时,`switch`语句必须穷举所有情况。如果忽略了`.West`这种情况,上面那段代码将无法通过编译,因为它没有考虑到`CompassPoint`的全部成员。强制穷举确保了枚举成员不会被意外遗漏。
正如在 [控制流](./05_Control_Flow.md) 中介绍的那样,在判断一个枚举类型的值时,`switch` 语句必须穷举所有情况。如果忽略了 `.west` 这种情况,上面那段代码将无法通过编译,因为它没有考虑到 `CompassPoint` 的全部成员。强制穷举确保了枚举成员不会被意外遗漏。
当不需要匹配每个枚举成员的时候,你可以提供一个`default`分支来涵盖所有未明确处理的枚举成员:
当不需要匹配每个枚举成员的时候,你可以提供一个 `default` 分支来涵盖所有未明确处理的枚举成员:
```swift
let somePlanet = Planet.Earth
let somePlanet = Planet.earth
switch somePlanet {
case .Earth:
case .earth:
print("Mostly harmless")
default:
print("Not a safe place for humans")
}
// 输出 "Mostly harmless”
// 打印“Mostly harmless”
```
<a name="associated_values"></a>
## 关联值Associated Values
## 枚举成员的遍历 {#iterating-over-enumeration-cases}
上一小节的例子演示了如何定义和分类枚举的成员。你可以为`Planet.Earth`设置一个常量或者变量,并在赋值之后查看这个值。然而,有时候能够把其他类型的*关联值*和成员值一起存储起来会很有用。这能让你连同成员值一起存储额外的自定义信息,并且你每次在代码中使用该枚举成员时,还可以修改这个关联值。
在一些情况下,你会需要得到一个包含枚举所有成员的集合。可以通过如下代码实现:
令枚举遵循 `CaseIterable` 协议。Swift 会生成一个 `allCases` 属性,用于表示一个包含枚举所有成员的集合。下面是一个例子:
```swift
enum Beverage: CaseIterable {
case coffee, tea, juice
}
let numberOfChoices = Beverage.allCases.count
print("\(numberOfChoices) beverages available")
// 打印“3 beverages available”
```
在前面的例子中,通过 `Beverage.allCases` 可以访问到包含 `Beverage` 枚举所有成员的集合。`allCases` 的使用方法和其它一般集合一样——集合中的元素是枚举类型的实例,所以在上面的情况中,这些元素是 `Beverage` 值。在前面的例子中,统计了总共有多少个枚举成员。而在下面的例子中,则使用 `for` 循环来遍历所有枚举成员。
```swift
for beverage in Beverage.allCases {
print(beverage)
}
// coffee
// tea
// juice
```
在前面的例子中,使用的语法表明这个枚举遵循 [CaseIterable](https://developer.apple.com/documentation/swift/caseiterable) 协议。想了解 protocols 相关信息,请参见 [协议](./21_Protocols.md)。
## 关联值 {#associated-values}
枚举语法那一小节的例子演示了如何定义和分类枚举的成员。你可以为 `Planet.earth` 设置一个常量或者变量,并在赋值之后查看这个值。然而,有时候把其他类型的值和成员值一起存储起来会很有用。这额外的信息称为*关联值*,并且你每次在代码中使用该枚举成员时,还可以修改这个关联值。
你可以定义 Swift 枚举来存储任意类型的关联值如果需要的话每个枚举成员的关联值类型可以各不相同。枚举的这种特性跟其他语言中的可识别联合discriminated unions标签联合tagged unions或者变体variants相似。
例如,假设一个库存跟踪系统需要利用两种不同类型的条形码来跟踪商品。有些商品上标有使用`0``9`的数字的 UPC-A 格式的一维条形码。每一个条形码都有一个代表数字系统的数字,该数字后接五位代表厂商代码的数字,接下来是五位代表“产品代码”的数字。最后一个数字是检查位,用来验证代码是否被正确扫描:
例如,假设一个库存跟踪系统需要利用两种不同类型的条形码来跟踪商品。有些商品上标有使用 `0``9` 的数字的 UPC 格式的一维条形码。每一个条形码都有一个代表数字系统的数字,该数字后接五位代表厂商代码的数字,接下来是五位代表“产品代码”的数字。最后一个数字是检查位,用来验证代码是否被正确扫描:
<img width="252" height="120" alt="" src="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/barcode_UPC_2x.png">
<img width="252" height="120" alt="" src="https://docs.swift.org/swift-book/_images/barcode_UPC_2x.png">
其他商品上标有 QR 码格式的二维码,它可以使用任何 ISO 8859-1 字符,并且可以编码一个最多拥有 2,953 个字符的字符串:
<img width="169" height="169" alt="" src="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/barcode_QR_2x.png">
<img width="169" height="169" alt="" src="https://docs.swift.org/swift-book/_images/barcode_QR_2x.png">
这便于库存跟踪系统用包含四个整型值的元组存储 UPC-A 码,以及用任意长度的字符串储存 QR 码。
这便于库存跟踪系统用包含四个整型值的元组存储 UPC 码,以及用任意长度的字符串储存 QR 码。
在 Swift 中,使用如下方式定义表示两种商品条形码的枚举:
```swift
enum Barcode {
case UPCA(Int, Int, Int, Int)
case QRCode(String)
case upc(Int, Int, Int, Int)
case qrCode(String)
}
```
以上代码可以这么理解:
“定义一个名为`Barcode`的枚举类型,它的一个成员值是具有`(IntIntIntInt)`类型关联值的`UPCA`,另一个成员值是具有`String`类型关联值的`QRCode`。”
“定义一个名为 `Barcode` 的枚举类型,它的一个成员值是具有 `(IntIntIntInt)` 类型关联值的 `upc`,另一个成员值是具有 `String` 类型关联值的 `qrCode`。”
这个定义不提供任何`Int``String`类型的关联值,它只是定义了,当`Barcode`常量和变量等于`Barcode.UPCA``Barcode.QRCode`时,可以存储的关联值的类型。
这个定义不提供任何 `Int``String` 类型的关联值,它只是定义了,当 `Barcode` 常量和变量等于 `Barcode.upc``Barcode.qrCode` 时,可以存储的关联值的类型。
然后可以使用任意一种条形码类型创建新的条形码,例如:
然后可以使用任意一种条形码类型创建新的条形码,例如:
```swift
var productBarcode = Barcode.UPCA(8, 85909, 51226, 3)
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
```
上面的例子创建了一个名为`productBarcode`的变量,并将`Barcode.UPCA`赋值给它,关联的元组值为`(8, 85909, 51226, 3)`
上面的例子创建了一个名为 `productBarcode` 的变量,并将 `Barcode.upc` 赋值给它,关联的元组值为 `(8, 85909, 51226, 3)`
同一个商品可以被分配一个不同类型的条形码,例如:
```swift
productBarcode = .QRCode("ABCDEFGHIJKLMNOP")
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
```
这时,原始的`Barcode.UPCA`和其整数关联值被新的`Barcode.QRCode`和其字符串关联值所替代。`Barcode`类型的常量和变量可以存储一个`.UPCA`或者一个`.QRCode`(连同它们的关联值),但是在同一时间只能存储这两个值中的一个。
这时,原始的 `Barcode.upc` 和其整数关联值被新的 `Barcode.qrCode` 和其字符串关联值所替代。`Barcode` 类型的常量和变量可以存储一个 `.upc` 或者一个 `.qrCode`(连同它们的关联值),但是在同一时间只能存储这两个值中的一个。
像先前那样,可以使用一个 switch 语句来检查不同的条形码类型。然而,这一次,关联值可以被提取出来作为 switch 语句的一部分。你可以在`switch`的 case 分支代码中提取每个关联值作为一个常量(用`let`前缀)或者作为一个变量(用`var`前缀)来使用:
可以使用一个 switch 语句来检查不同的条形码类型,和之前使用 Switch 语句来匹配枚举值的例子一样。然而,这一次,关联值可以被提取出来作为 switch 语句的一部分。你可以在 `switch` 的 case 分支代码中提取每个关联值作为一个常量(用 `let` 前缀)或者作为一个变量(用 `var` 前缀)来使用:
```swift
switch productBarcode {
case .UPCA(let numberSystem, let manufacturer, let product, let check):
print("UPC-A: \(numberSystem), \(manufacturer), \(product), \(check).")
case .QRCode(let productCode):
case .upc(let numberSystem, let manufacturer, let product, let check):
print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case .qrCode(let productCode):
print("QR code: \(productCode).")
}
// 输出 "QR code: ABCDEFGHIJKLMNOP."
// 打印“QR code: ABCDEFGHIJKLMNOP.
```
如果一个枚举成员的所有关联值都被提取为常量,或者都被提取为变量,为了简洁,你可以只在成员名称前标注一个`let`或者`var`
如果一个枚举成员的所有关联值都被提取为常量,或者都被提取为变量,为了简洁,你可以只在成员名称前标注一个 `let` 或者 `var`
```swift
switch productBarcode {
case let .UPCA(numberSystem, manufacturer, product, check):
print("UPC-A: \(numberSystem), \(manufacturer), \(product), \(check).")
case let .QRCode(productCode):
case let .upc(numberSystem, manufacturer, product, check):
print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case let .qrCode(productCode):
print("QR code: \(productCode).")
}
// 输出 "QR code: ABCDEFGHIJKLMNOP."
// 打印“QR code: ABCDEFGHIJKLMNOP.
```
<a name="raw_values"></a>
## 原始值Raw Values
## 原始值 {#raw-values}
在[关联值](#associated_values)小节的条形码例子中,演示了如何声明存储不同类型关联值的枚举成员。作为关联值的替代选择,枚举成员可以被默认值(称为*原始值*)预填充,这些原始值的类型必须相同。
[关联值](#associated_values) 小节的条形码例子中,演示了如何声明存储不同类型关联值的枚举成员。作为关联值的替代选择,枚举成员可以被默认值(称为*原始值*)预填充,这些原始值的类型必须相同。
这是一个使用 ASCII 码作为原始值的枚举:
```swift
enum ASCIIControlCharacter: Character {
case Tab = "\t"
case LineFeed = "\n"
case CarriageReturn = "\r"
case tab = "\t"
case lineFeed = "\n"
case carriageReturn = "\r"
}
```
枚举类型`ASCIIControlCharacter`的原始值类型被定义为`Character`,并设置了一些比较常见的 ASCII 控制字符。`Character`的描述详见[字符串和字符](./03_Strings_and_Characters.html)部分。
枚举类型 `ASCIIControlCharacter` 的原始值类型被定义为 `Character`,并设置了一些比较常见的 ASCII 控制字符。`Character` 的描述详见 [字符串和字符](./03_Strings_and_Characters.md) 部分。
原始值可以是字符串、字符,或者任意整型值或浮点型值。每个原始值在枚举声明中必须是唯一的。
原始值可以是字符串,字符,或者任意整型值或浮点型值。每个原始值在枚举声明中必须是唯一的。
> 注意
> 注意
>
> 原始值和关联值是不同的。原始值是在定义枚举时被预先填充的值,像上述三个 ASCII 码。对于一个特定的枚举成员,它的原始值始终不变。关联值是创建一个基于枚举成员的常量或变量时才设置的值,枚举成员的关联值可以变化。
<a name="implicitly_assigned_raw_values"></a>
### 原始值的隐式赋值Implicitly Assigned Raw Values
### 原始值的隐式赋值 {#implicitly-assigned-raw-values}
在使用原始值为整数或者字符串类型的枚举时不需要显式地为每一个枚举成员设置原始值Swift 将会自动为你赋值。
例如,当使用整数作为原始值时,隐式赋值的值依次递增`1`。如果第一个枚举成员没有设置原始值,其原始值将为`0`
例如,当使用整数作为原始值时,隐式赋值的值依次递增 `1`。如果第一个枚举成员没有设置原始值,其原始值将为 `0`
下面的枚举是对之前`Planet`这个枚举的一个细化,利用整型的原始值来表示每个行星在太阳系中的顺序:
下面的枚举是对之前 `Planet` 这个枚举的一个细化,利用整型的原始值来表示每个行星在太阳系中的顺序:
```swift
enum Planet: Int {
case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
```
在上面的例子中,`Plant.Mercury`的显式原始值为`1``Planet.Venus`的隐式原始值为`2`,依次类推。
在上面的例子中,`Plant.mercury` 的显式原始值为 `1``Planet.venus` 的隐式原始值为 `2`,依次类推。
当使用字符串作为枚举类型的原始值时,每个枚举成员的隐式原始值为该枚举成员的名称。
下面的例子是`CompassPoint`枚举的细化,使用字符串类型的原始值来表示各个方向的名称:
下面的例子是 `CompassPoint` 枚举的细化,使用字符串类型的原始值来表示各个方向的名称:
```swift
enum CompassPoint: String {
case North, South, East, West
case north, south, east, west
}
```
上面例子中,`CompassPoint.South`拥有隐式原始值`South`,依次类推。
上面例子中,`CompassPoint.south` 拥有隐式原始值 `south`,依次类推。
使用枚举成员的`rawValue`属性可以访问该枚举成员的原始值:
使用枚举成员的 `rawValue` 属性可以访问该枚举成员的原始值:
```swift
let earthsOrder = Planet.Earth.rawValue
let earthsOrder = Planet.earth.rawValue
// earthsOrder 值为 3
let sunsetDirection = CompassPoint.West.rawValue
// sunsetDirection 值为 "West"
let sunsetDirection = CompassPoint.west.rawValue
// sunsetDirection 值为 "west"
```
<a name="initializing_from_a_raw_value"></a>
### 使用原始值初始化枚举实例Initializing from a Raw Value
### 使用原始值初始化枚举实例 {#initializing-from-a-raw-value}
如果在定义枚举类型的时候使用了原始值,那么将会自动获得一个初始化方法,这个方法接收一个叫做`rawValue`的参数,参数类型即为原始值类型,返回值则是枚举成员或`nil`。你可以使用这个初始化方法来创建一个新的枚举实例。
如果在定义枚举类型的时候使用了原始值,那么将会自动获得一个初始化方法,这个方法接收一个叫做 `rawValue` 的参数,参数类型即为原始值类型,返回值则是枚举成员或 `nil`。你可以使用这个初始化方法来创建一个新的枚举实例。
这个例子利用原始值`7`创建了枚举成员`Uranus`
这个例子利用原始值 `7` 创建了枚举成员 `Uranus`
```swift
let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet 类型为 Planet? 值为 Planet.Uranus
// possiblePlanet 类型为 Planet? 值为 Planet.uranus
```
然而,并非所有`Int`值都可以找到一个匹配的行星。因此,原始值构造器总是返回一个*可选*的枚举成员。在上面的例子中,`possiblePlanet``Planet?`类型,或者说“可选的`Planet`”。
然而,并非所有 `Int` 值都可以找到一个匹配的行星。因此,原始值构造器总是返回一个*可选*的枚举成员。在上面的例子中,`possiblePlanet``Planet?` 类型,或者说“可选的 `Planet`”。
> 注意
> 原始值构造器是一个可失败构造器,因为并不是每一个原始值都有与之对应的枚举成员。更多信息请参见[可失败构造器](../chapter3/05_Declarations.html#failable_initializers)
> 注意
>
> 原始值构造器是一个可失败构造器,因为并不是每一个原始值都有与之对应的枚举成员。更多信息请参见 [可失败构造器](../chapter3/05_Declarations.html#failable_initializers)
如果你试图寻找一个位置为`9`的行星,通过原始值构造器返回的可选`Planet`值将是`nil`
如果你试图寻找一个位置为 `11` 的行星,通过原始值构造器返回的可选 `Planet` 值将是 `nil`
```swift
let positionToFind = 9
let positionToFind = 11
if let somePlanet = Planet(rawValue: positionToFind) {
switch somePlanet {
case .Earth:
case .earth:
print("Mostly harmless")
default:
print("Not a safe place for humans")
@ -288,62 +289,60 @@ if let somePlanet = Planet(rawValue: positionToFind) {
} else {
print("There isn't a planet at position \(positionToFind)")
}
// 输出 "There isn't a planet at position 9
// 打印“There isn't a planet at position 11”
```
这个例子使用了可选绑定optional binding试图通过原始值`9`来访问一个行星。`if let somePlanet = Planet(rawValue: 9)`语句创建了一个可选`Planet`,如果可选`Planet`的值存在,就会赋值给`somePlanet`。在这个例子中,无法检索到位置为`9`的行星,所以`else`分支被执行。
这个例子使用了可选绑定optional binding试图通过原始值 `11` 来访问一个行星。`if let somePlanet = Planet(rawValue: 11)` 语句创建了一个可选 `Planet`,如果可选 `Planet` 的值存在,就会赋值给 `somePlanet`。在这个例子中,无法检索到位置为 `11` 的行星,所以 `else` 分支被执行。
<a name="recursive_enumerations"></a>
## 递归枚举Recursive Enumerations
## 递归枚举 {#recursive-enumerations}
*递归枚举recursive enumeration*是一种枚举类型,它有一个或多个枚举成员使用该枚举类型的实例作为关联值。使用递归枚举时,编译器会插入一个间接层。你可以在枚举成员前加上`indirect`来表示该成员可递归。
*递归枚举*是一种枚举类型,它有一个或多个枚举成员使用该枚举类型的实例作为关联值。使用递归枚举时,编译器会插入一个间接层。你可以在枚举成员前加上 `indirect` 来表示该成员可递归。
例如,下面的例子中,枚举类型存储了简单的算术表达式:
```swift
enum ArithmeticExpression {
case Number(Int)
indirect case Addition(ArithmeticExpression, ArithmeticExpression)
indirect case Multiplication(ArithmeticExpression, ArithmeticExpression)
case number(Int)
indirect case addition(ArithmeticExpression, ArithmeticExpression)
indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}
```
你也可以在枚举类型开头加上`indirect`关键字来表明它的所有成员都是可递归的:
你也可以在枚举类型开头加上 `indirect` 关键字来表明它的所有成员都是可递归的:
```swift
indirect enum ArithmeticExpression {
case Number(Int)
case Addition(ArithmeticExpression, ArithmeticExpression)
case Multiplication(ArithmeticExpression, ArithmeticExpression)
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
}
```
上面定义的枚举类型可以存储三种算术表达式:纯数字、两个表达式相加、两个表达式相乘。枚举成员`Addition``Multiplication`的关联值也是算术表达式——这些关联值使得嵌套表达式成为可能。例如,表达式`(5 + 4) * 2`,乘号右边是一个数字,左边则是另一个表达式。因为数据是嵌套的,因而用来存储数据的枚举类型也需要支持这种嵌套——这意味着枚举类型需要支持递归。下面的代码展示了使用`ArithmeticExpression `这个递归枚举创建表达式`(5 + 4) * 2`
上面定义的枚举类型可以存储三种算术表达式:纯数字、两个表达式相加、两个表达式相乘。枚举成员 `addition``multiplication` 的关联值也是算术表达式——这些关联值使得嵌套表达式成为可能。例如,表达式 `(5 + 4) * 2`,乘号右边是一个数字,左边则是另一个表达式。因为数据是嵌套的,因而用来存储数据的枚举类型也需要支持这种嵌套——这意味着枚举类型需要支持递归。下面的代码展示了使用 `ArithmeticExpression` 这个递归枚举创建表达式 `(5 + 4) * 2`
```swift
let five = ArithmeticExpression.Number(5)
let four = ArithmeticExpression.Number(4)
let sum = ArithmeticExpression.Addition(five, four)
let product = ArithmeticExpression.Multiplication(sum, ArithmeticExpression.Number(2))
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
```
要操作具有递归性质的数据结构,使用递归函数是一种直截了当的方式。例如,下面是一个对算术表达式求值的函数:
```swift
func evaluate(expression: ArithmeticExpression) -> Int {
func evaluate(_ expression: ArithmeticExpression) -> Int {
switch expression {
case .Number(let value):
case let .number(value):
return value
case .Addition(let left, let right):
case let .addition(left, right):
return evaluate(left) + evaluate(right)
case .Multiplication(let left, let right):
case let .multiplication(left, right):
return evaluate(left) * evaluate(right)
}
}
print(evaluate(product))
// 输出 "18"
// 打印“18
```
该函数如果遇到纯数字,就直接返回该数字的值。如果遇到的是加法或乘法运算,则分别计算左边表达式和右边表达式的值,然后相加或相乘。

View File

@ -1,304 +0,0 @@
# 类和结构体Classes and Structures
> 1.0
> 翻译:[JaySurplus](https://github.com/JaySurplus)
> 校对:[sg552](https://github.com/sg552)
> 2.0
> 翻译+校对:[SkyJean](https://github.com/SkyJean)
> 2.1
> 校对:[shanks](http://codebuild.me)2015-10-29
> 2.2
> 校对:[SketchK](https://github.com/SketchK) 2016-05-13
本页包含内容:
- [类和结构体对比](#comparing_classes_and_structures)
- [结构体和枚举是值类型](#structures_and_enumerations_are_value_types)
- [类是引用类型](#classes_are_reference_types)
- [类和结构体的选择](#choosing_between_classes_and_structures)
- [字符串(String)、数组(Array)、和字典(Dictionary)类型的赋值与复制行为](#assignment_and_copy_behavior_for_strings_arrays_and_dictionaries)
*类*和*结构体*是人们构建代码所用的一种通用且灵活的构造体。我们可以使用完全相同的语法规则来为类和结构体定义属性(常量、变量)和添加方法,从而扩展类和结构体的功能。
与其他编程语言所不同的是Swift 并不要求你为自定义类和结构去创建独立的接口和实现文件。你所要做的是在一个单一文件中定义一个类或者结构体,系统将会自动生成面向其它代码的外部接口。
> 注意
> 通常一个`类`的实例被称为`对象`。然而在 Swift 中,类和结构体的关系要比在其他语言中更加的密切,本章中所讨论的大部分功能都可以用在类和结构体上。因此,我们会主要使用`实例`而不是`对象`。
<a name="comparing_classes_and_structures"></a>
###类和结构体对比
Swift 中类和结构体有很多共同点。共同处在于:
* 定义属性用于存储值
* 定义方法用于提供功能
* 定义下标操作使得可以通过下标语法来访问实例所包含的值
* 定义构造器用于生成初始化值
* 通过扩展以增加默认实现的功能
* 实现协议以提供某种标准功能
更多信息请参见[属性](./10_Properties.html)[方法](./11_Methods.html)[下标](./12_Subscripts.html)[构造过程](./14_Initialization.html)[扩展](./21_Extensions.html),和[协议](./22_Protocols.html)。
与结构体相比,类还有如下的附加功能:
* 继承允许一个类继承另一个类的特征
* 类型转换允许在运行时检查和解释一个类实例的类型
* 析构器允许一个类实例释放任何其所被分配的资源
* 引用计数允许对一个类的多次引用
更多信息请参见[继承](./13_Inheritance.html)[类型转换](./19_Type_Casting.html)[析构过程](./15_Deinitialization.html),和[自动引用计数](./16_Automatic_Reference_Counting.html)。
> 注意
> 结构体总是通过被复制的方式在代码中传递,不使用引用计数。
<a name="definition_syntax"></a>
### 定义语法
类和结构体有着类似的定义方式。我们通过关键字`class``struct`来分别表示类和结构体,并在一对大括号中定义它们的具体内容:
```swift
class SomeClass {
// class definition goes here
}
struct SomeStructure {
// structure definition goes here
}
```
> 注意
> 在你每次定义一个新类或者结构体的时候,实际上你是定义了一个新的 Swift 类型。因此请使用`UpperCamelCase`这种方式来命名(如`SomeClass`和`SomeStructure`等),以便符合标准 Swift 类型的大写命名风格(如`String``Int`和`Bool`)。相反的,请使用`lowerCamelCase`这种方式为属性和方法命名(如`framerate`和`incrementCount`),以便和类型名区分。
以下是定义结构体和定义类的示例:
```swift
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
```
在上面的示例中我们定义了一个名为`Resolution`的结构体,用来描述一个显示器的像素分辨率。这个结构体包含了两个名为`width``height`的存储属性。存储属性是被捆绑和存储在类或结构体中的常量或变量。当这两个属性被初始化为整数`0`的时候,它们会被推断为`Int`类型。
在上面的示例中我们还定义了一个名为`VideoMode`的类,用来描述一个视频显示器的特定模式。这个类包含了四个变量存储属性。第一个是`分辨率`,它被初始化为一个新的`Resolution`结构体的实例,属性类型被推断为`Resolution`。新`VideoMode`实例同时还会初始化其它三个属性,它们分别是,初始值为`false``interlaced`,初始值为`0.0``frameRate`,以及值为可选`String``name``name`属性会被自动赋予一个默认值`nil`,意为“没有`name`值”,因为它是一个可选类型。
<a name="class_and_structure_instances"></a>
### 类和结构体实例
`Resolution`结构体和`VideoMode`类的定义仅描述了什么是`Resolution``VideoMode`。它们并没有描述一个特定的分辨率resolution或者视频模式video mode。为了描述一个特定的分辨率或者视频模式我们需要生成一个它们的实例。
生成结构体和类实例的语法非常相似:
```swift
let someResolution = Resolution()
let someVideoMode = VideoMode()
```
结构体和类都使用构造器语法来生成新的实例。构造器语法的最简单形式是在结构体或者类的类型名称后跟随一对空括号,如`Resolution()``VideoMode()`。通过这种方式所创建的类或者结构体实例,其属性均会被初始化为默认值。[构造过程](./14_Initialization.html)章节会对类和结构体的初始化进行更详细的讨论。
<a name="accessing_properties"></a>
### 属性访问
通过使用*点语法**dot syntax*),你可以访问实例的属性。其语法规则是,实例名后面紧跟属性名,两者通过点号(`.`)连接:
```swift
print("The width of someResolution is \(someResolution.width)")
// 输出 "The width of someResolution is 0"
```
在上面的例子中,`someResolution.width`引用`someResolution``width`属性,返回`width`的初始值`0`
你也可以访问子属性,如`VideoMode``Resolution`属性的`width`属性:
```swift
print("The width of someVideoMode is \(someVideoMode.resolution.width)")
// 输出 "The width of someVideoMode is 0"
```
你也可以使用点语法为变量属性赋值:
```swift
someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// 输出 "The width of someVideoMode is now 1280"
```
> 注意
> 与 Objective-C 语言不同的是Swift 允许直接设置结构体属性的子属性。上面的最后一个例子,就是直接设置了`someVideoMode`中`resolution`属性的`width`这个子属性,以上操作并不需要重新为整个`resolution`属性设置新值。
<a name="memberwise_initializers_for_structure_types"></a>
### 结构体类型的成员逐一构造器Memberwise Initializers for Structure Types
所有结构体都有一个自动生成的*成员逐一构造器*,用于初始化新结构体实例中成员的属性。新实例中各个属性的初始值可以通过属性的名称传递到成员逐一构造器之中:
```swift
let vga = Resolution(width:640, height: 480)
```
与结构体不同,类实例没有默认的成员逐一构造器。[构造过程](./14_Initialization.html)章节会对构造器进行更详细的讨论。
<a name="structures_and_enumerations_are_value_types"></a>
## 结构体和枚举是值类型
*值类型*被赋予给一个变量、常量或者被传递给一个函数的时候,其值会被*拷贝*。
在之前的章节中,我们已经大量使用了值类型。实际上,在 Swift 中所有的基本类型整数Integer、浮点数floating-point、布尔值Boolean、字符串string)、数组array和字典dictionary都是值类型并且在底层都是以结构体的形式所实现。
在 Swift 中,所有的结构体和枚举类型都是值类型。这意味着它们的实例,以及实例中所包含的任何值类型属性,在代码中传递的时候都会被复制。
请看下面这个示例,其使用了前一个示例中的`Resolution`结构体:
```swift
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
```
在以上示例中,声明了一个名为`hd`的常量,其值为一个初始化为全高清视频分辨率(`1920` 像素宽,`1080` 像素高)的`Resolution`实例。
然后示例中又声明了一个名为`cinema`的变量,并将`hd`赋值给它。因为`Resolution`是一个结构体,所以`cinema`的值其实是`hd`的一个拷贝副本,而不是`hd`本身。尽管`hd``cinema`有着相同的宽width和高height但是在幕后它们是两个完全不同的实例。
下面,为了符合数码影院放映的需求(`2048` 像素宽,`1080` 像素高),`cinema``width`属性需要作如下修改:
```swift
cinema.width = 2048
```
这里,将会显示`cinema``width`属性确已改为了`2048`
```swift
print("cinema is now \(cinema.width) pixels wide")
// 输出 "cinema is now 2048 pixels wide"
```
然而,初始的`hd`实例中`width`属性还是`1920`
```swift
print("hd is still \(hd.width) pixels wide")
// 输出 "hd is still 1920 pixels wide"
```
在将`hd`赋予给`cinema`的时候,实际上是将`hd`中所存储的值进行拷贝,然后将拷贝的数据存储到新的`cinema`实例中。结果就是两个完全独立的实例碰巧包含有相同的数值。由于两者相互独立,因此将`cinema``width`修改为`2048`并不会影响`hd`中的`width`的值。
枚举也遵循相同的行为准则:
```swift
enum CompassPoint {
case North, South, East, West
}
var currentDirection = CompassPoint.West
let rememberedDirection = currentDirection
currentDirection = .East
if rememberedDirection == .West {
print("The remembered direction is still .West")
}
// 输出 "The remembered direction is still .West"
```
上例中`rememberedDirection`被赋予了`currentDirection`的值,实际上它被赋予的是值的一个拷贝。赋值过程结束后再修改`currentDirection`的值并不影响`rememberedDirection`所储存的原始值的拷贝。
<a name="classes_are_reference_types"></a>
## 类是引用类型
与值类型不同,引用类型在被赋予到一个变量、常量或者被传递到一个函数时,其值不会被拷贝。因此,引用的是已存在的实例本身而不是其拷贝。
请看下面这个示例,其使用了之前定义的`VideoMode`类:
```swift
let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0
```
以上示例中,声明了一个名为`tenEighty`的常量,其引用了一个`VideoMode`类的新实例。在之前的示例中这个视频模式video mode被赋予了HD分辨率`1920`*`1080`)的一个拷贝(即`hd`实例)。同时设置为`interlaced`,命名为`“1080i”`。最后,其帧率是`25.0`帧每秒。
然后,`tenEighty`被赋予名为`alsoTenEighty`的新常量,同时对`alsoTenEighty`的帧率进行修改:
```swift
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
```
因为类是引用类型,所以`tenEight``alsoTenEight`实际上引用的是相同的`VideoMode`实例。换句话说,它们是同一个实例的两种叫法。
下面,通过查看`tenEighty``frameRate`属性,我们会发现它正确的显示了所引用的`VideoMode`实例的新帧率,其值为`30.0`
```swift
print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// 输出 "The frameRate property of theEighty is now 30.0"
```
需要注意的是`tenEighty``alsoTenEighty`被声明为常量而不是变量。然而你依然可以改变`tenEighty.frameRate``alsoTenEighty.frameRate`,因为`tenEighty``alsoTenEighty`这两个常量的值并未改变。它们并不“存储”这个`VideoMode`实例,而仅仅是对`VideoMode`实例的引用。所以,改变的是被引用的`VideoMode``frameRate`属性,而不是引用`VideoMode`的常量的值。
<a name="identity_operators"></a>
### 恒等运算符
因为类是引用类型,有可能有多个常量和变量在幕后同时引用同一个类实例。(对于结构体和枚举来说,这并不成立。因为它们作为值类型,在被赋予到常量、变量或者传递到函数时,其值总是会被拷贝。)
如果能够判定两个常量或者变量是否引用同一个类实例将会很有帮助。为了达到这个目的Swift 内建了两个恒等运算符:
* 等价于(`===`
* 不等价于(`!==`
运用这两个运算符检测两个常量或者变量是否引用同一个实例:
```swift
if tenEighty === alsoTenEighty {
print("tenEighty and alsoTenEighty refer to the same Resolution instance.")
}
//输出 "tenEighty and alsoTenEighty refer to the same Resolution instance."
```
请注意,“等价于”(用三个等号表示,`===`)与“等于”(用两个等号表示,`==`)的不同:
* “等价于”表示两个类类型class type的常量或者变量引用同一个类实例。
* “等于”表示两个实例的值“相等”或“相同”,判定时要遵照设计者定义的评判标准,因此相对于“相等”来说,这是一种更加合适的叫法。
当你在定义你的自定义类和结构体的时候,你有义务来决定判定两个实例“相等”的标准。在章节[等价操作符](./25_Advanced_Operators.html#equivalence_operators)中将会详细介绍实现自定义“等于”和“不等于”运算符的流程。
<a name="pointers"></a>
### 指针
如果你有 CC++ 或者 Objective-C 语言的经验,那么你也许会知道这些语言使用*指针*来引用内存中的地址。一个引用某个引用类型实例的 Swift 常量或者变量,与 C 语言中的指针类似,但是并不直接指向某个内存地址,也不要求你使用星号(`*`来表明你在创建一个引用。Swift 中的这些引用与其它的常量或变量的定义方式相同。
<a name="choosing_between_classes_and_structures"></a>
## 类和结构体的选择
在你的代码中,你可以使用类和结构体来定义你的自定义数据类型。
然而,结构体实例总是通过值传递,类实例总是通过引用传递。这意味两者适用不同的任务。当你在考虑一个工程项目的数据结构和功能的时候,你需要决定每个数据结构是定义成类还是结构体。
按照通用的准则,当符合一条或多条以下条件时,请考虑构建结构体:
* 该数据结构的主要目的是用来封装少量相关简单数据值。
* 有理由预计该数据结构的实例在被赋值或传递时,封装的数据将会被拷贝而不是被引用。
* 该数据结构中储存的值类型属性,也应该被拷贝,而不是被引用。
* 该数据结构不需要去继承另一个既有类型的属性或者行为。
举例来说,以下情境中适合使用结构体:
* 几何形状的大小,封装一个`width`属性和`height`属性,两者均为`Double`类型。
* 一定范围内的路径,封装一个`start`属性和`length`属性,两者均为`Int`类型。
* 三维坐标系内一点,封装`x``y``z`属性,三者均为`Double`类型。
在所有其它案例中,定义一个类,生成一个它的实例,并通过引用来管理和传递。实际中,这意味着绝大部分的自定义数据构造都应该是类,而非结构体。
<a name="assignment_and_copy_behavior_for_strings_arrays_and_dictionaries"></a>
## 字符串(String)、数组(Array)、和字典(Dictionary)类型的赋值与复制行为
Swift 中,许多基本类型,诸如`String``Array``Dictionary`类型均以结构体的形式实现。这意味着被赋值给新的常量或变量,或者被传入函数或方法中时,它们的值会被拷贝。
Objective-C 中`NSString``NSArray``NSDictionary`类型均以类的形式实现,而并非结构体。它们在被赋值或者被传入函数或方法时,不会发生值拷贝,而是传递现有实例的引用。
> 注意
> 以上是对字符串、数组、字典的“拷贝”行为的描述。在你的代码中拷贝行为看起来似乎总会发生。然而Swift 在幕后只在绝对必要时才执行实际的拷贝。Swift 管理所有的值拷贝以确保性能最优化,所以你没必要去回避赋值来保证性能最优化。

View File

@ -0,0 +1,250 @@
# 结构体和类
*结构体*和*类*作为一种通用而又灵活的结构,成为了人们构建代码的基础。你可以使用定义常量、变量和函数的语法,为你的结构体和类定义属性、添加方法。
与其他编程语言所不同的是Swift 并不要求你为自定义的结构体和类的接口与实现代码分别创建文件。你只需在单一的文件中定义一个结构体或者类,系统将会自动生成面向其它代码的外部接口。
> 注意
>
> 通常一个*类*的实例被称为*对象*。然而相比其他语言Swift 中结构体和类的功能更加相近,本章中所讨论的大部分功能都可以用在结构体或者类上。因此,这里会使用*实例*这个更通用的术语。
## 结构体和类对比 {#comparing-structures-and-classes}
Swift 中结构体和类有很多共同点。两者都可以:
* 定义属性用于存储值
* 定义方法用于提供功能
* 定义下标操作用于通过下标语法访问它们的值
* 定义构造器用于设置初始值
* 通过扩展以增加默认实现之外的功能
* 遵循协议以提供某种标准功能
更多信息请参见 [属性](./10_Properties.md)、[方法](./11_Methods.md)、[下标](./12_Subscripts.md)、[构造过程](./14_Initialization.md)、[扩展](./20_Extensions.md) 和 [协议](./21_Protocols.md)。
与结构体相比,类还有如下的附加功能:
* 继承允许一个类继承另一个类的特征
* 类型转换允许在运行时检查和解释一个类实例的类型
* 析构器允许一个类实例释放任何其所被分配的资源
* 引用计数允许对一个类的多次引用
更多信息请参见 [继承](./13_Inheritance.md)、[类型转换](./18_Type_Casting.md)、[析构过程](./15_Deinitialization.md) 和 [自动引用计数](./23_Automatic_Reference_Counting.md)。
类支持的附加功能是以增加复杂性为代价的。作为一般准则,优先使用结构体,因为它们更容易理解,仅在适当或必要时才使用类。实际上,这意味着你的大多数自定义数据类型都会是结构体和枚举。更多详细的比较参见 [在结构和类之间进行选择](https://developer.apple.com/documentation/swift/choosing_between_structures_and_classes)。
### 类型定义的语法 {#definition-syntax}
结构体和类有着相似的定义方式。你通过 `struct` 关键字引入结构体,通过 `class` 关键字引入类,并将它们的具体定义放在一对大括号中:
```swift
struct SomeStructure {
// 在这里定义结构体
}
class SomeClass {
// 在这里定义类
}
```
> 注意
>
> 每当你定义一个新的结构体或者类时,你都是定义了一个新的 Swift 类型。请使用 `UpperCamelCase` 这种方式来命名类型(如这里的 `SomeClass` 和 `SomeStructure`),以便符合标准 Swift 类型的大写命名风格(如 `String``Int` 和 `Bool`)。请使用 `lowerCamelCase` 这种方式来命名属性和方法(如 `framerate` 和 `incrementCount`),以便和类型名区分。
以下是定义结构体和定义类的示例:
```swift
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
```
在上面的示例中定义了一个名为 `Resolution` 的结构体,用来描述基于像素的分辨率。这个结构体包含了名为 `width``height` 的两个存储属性。存储属性是与结构体或者类绑定的,并存储在其中的常量或变量。当这两个属性被初始化为整数 `0` 的时候,它们会被推断为 `Int` 类型。
在上面的示例还定义了一个名为 `VideoMode` 的类,用来描述视频显示器的某个特定视频模式。这个类包含了四个可变的存储属性。第一个, `resolution`,被初始化为一个新的 `Resolution` 结构体的实例,属性类型被推断为 `Resolution`。新 `VideoMode` 实例同时还会初始化其它三个属性,它们分别是初始值为 `false``interlaced`(意为“非隔行视频”),初始值为 `0.0``frameRate`,以及值为可选 `String``name`。因为 `name` 是一个可选类型,它会被自动赋予一个默认值 `nil`,意为“没有 `name` 值”。
### 结构体和类的实例 {#class-and-structure-instances}
`Resolution` 结构体和 `VideoMode` 类的定义仅描述了什么是 `Resolution``VideoMode`。它们并没有描述一个特定的分辨率resolution或者视频模式video mode。为此你需要创建结构体或者类的一个实例。
创建结构体和类实例的语法非常相似:
```swift
let someResolution = Resolution()
let someVideoMode = VideoMode()
```
结构体和类都使用构造器语法来创建新的实例。构造器语法的最简单形式是在结构体或者类的类型名称后跟随一对空括号,如 `Resolution()``VideoMode()`。通过这种方式所创建的类或者结构体实例,其属性均会被初始化为默认值。[构造过程](./14_Initialization.md) 章节会对类和结构体的初始化进行更详细的讨论。
### 属性访问 {#accessing-properties}
你可以通过使用*点语法*访问实例的属性。其语法规则是,实例名后面紧跟属性名,两者以点号(`.`)分隔,不带空格:
```swift
print("The width of someResolution is \(someResolution.width)")
// 打印 "The width of someResolution is 0"
```
在上面的例子中,`someResolution.width` 引用 `someResolution``width` 属性,返回 `width` 的初始值 `0`
你也可以访问子属性,如 `VideoMode``resolution` 属性的 `width` 属性:
```swift
print("The width of someVideoMode is \(someVideoMode.resolution.width)")
// 打印 "The width of someVideoMode is 0"
```
你也可以使用点语法为可变属性赋值:
```swift
someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// 打印 "The width of someVideoMode is now 1280"
```
### 结构体类型的成员逐一构造器 {#memberwise-initializers-for-structure-types}
所有结构体都有一个自动生成的*成员逐一构造器*,用于初始化新结构体实例中成员的属性。新实例中各个属性的初始值可以通过属性的名称传递到成员逐一构造器之中:
```swift
let vga = Resolution(width: 640, height: 480)
```
与结构体不同,类实例没有默认的成员逐一构造器。[构造过程](./14_Initialization.md) 章节会对构造器进行更详细的讨论。
## 结构体和枚举是值类型 {#structures-and-enumerations-are-value-types}
*值类型*是这样一种类型,当它被赋值给一个变量、常量或者被传递给一个函数的时候,其值会被*拷贝*。
在之前的章节中你已经大量使用了值类型。实际上Swift 中所有的基本类型整数integer、浮点数floating-point number、布尔值boolean、字符串string)、数组array和字典dictionary都是值类型其底层也是使用结构体实现的。
Swift 中所有的结构体和枚举类型都是值类型。这意味着它们的实例,以及实例中所包含的任何值类型的属性,在代码中传递的时候都会被复制。
> 注意
>
> 标准库定义的集合,例如数组,字典和字符串,都对复制进行了优化以降低性能成本。新集合不会立即复制,而是跟原集合共享同一份内存,共享同样的元素。在集合的某个副本要被修改前,才会复制它的元素。而你在代码中看起来就像是立即发生了复制。
请看下面这个示例,其使用了上一个示例中的 `Resolution` 结构体:
```swift
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
```
在以上示例中,声明了一个名为 `hd` 的常量,其值为一个初始化为全高清视频分辨率(`1920` 像素宽,`1080` 像素高)的 `Resolution` 实例。
然后示例中又声明了一个名为 `cinema` 的变量,并将 `hd` 赋值给它。因为 `Resolution` 是一个结构体,所以会先创建一个现有实例的副本,然后将副本赋值给 `cinema` 。尽管 `hd``cinema` 有着相同的宽width和高height但是在幕后它们是两个完全不同的实例。
下面,为了符合数码影院放映的需求(`2048` 像素宽,`1080` 像素高),`cinema``width` 属性被修改为稍微宽一点的 2K 标准:
```swift
cinema.width = 2048
```
查看 `cinema``width` 属性,它的值确实改为了 `2048`
```swift
print("cinema is now \(cinema.width) pixels wide")
// 打印 "cinema is now 2048 pixels wide"
```
然而,初始的 `hd` 实例中 `width` 属性还是 `1920`
```swift
print("hd is still \(hd.width) pixels wide")
// 打印 "hd is still 1920 pixels wide"
```
`hd` 赋值给 `cinema` 时,`hd` 中所存储的*值*会拷贝到新的 `cinema` 实例中。结果就是两个完全独立的实例包含了相同的数值。由于两者相互独立,因此将 `cinema``width` 修改为 `2048` 并不会影响 `hd` 中的 `width` 的值,如下图所示:
![sharedStateStruct_2x](https://docs.swift.org/swift-book/_images/sharedStateStruct_2x.png)
枚举也遵循相同的行为准则:
```swift
enum CompassPoint {
case north, south, east, west
mutating func turnNorth() {
self = .north
}
}
var currentDirection = CompassPoint.west
let rememberedDirection = currentDirection
currentDirection.turnNorth()
print("The current direction is \(currentDirection)")
print("The remembered direction is \(rememberedDirection)")
// 打印 "The current direction is north"
// 打印 "The remembered direction is west"
```
`rememberedDirection` 被赋予了 `currentDirection` 的值,实际上它被赋予的是值的一个拷贝。赋值过程结束后再修改 `currentDirection` 的值并不影响 `rememberedDirection` 所储存的原始值的拷贝。
## 类是引用类型 {#classes-are-reference-types}
与值类型不同,*引用类型*在被赋予到一个变量、常量或者被传递到一个函数时,其值不会被拷贝。因此,使用的是已存在实例的引用,而不是其拷贝。
请看下面这个示例,其使用了之前定义的 `VideoMode` 类:
```swift
let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0
```
以上示例中,声明了一个名为 `tenEighty` 的常量,并让其引用一个 `VideoMode` 类的新实例。它的视频模式video mode被赋值为之前创建的 HD 分辨率(`1920`\*`1080`)的一个拷贝。然后将它设置为隔行视频,名字设为 `“1080i”`,并将帧率设置为 `25.0` 帧每秒。
接下来,将 `tenEighty` 赋值给一个名为 `alsoTenEighty` 的新常量,并修改 `alsoTenEighty` 的帧率:
```swift
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
```
因为类是引用类型,所以 `tenEight``alsoTenEight` 实际上引用的是同一个 `VideoMode` 实例。换句话说,它们是同一个实例的两种叫法,如下图所示:
![sharedStateClass_2x](https://docs.swift.org/swift-book/_images/sharedStateClass_2x.png)
通过查看 `tenEighty``frameRate` 属性,可以看到它正确地显示了底层的 `VideoMode` 实例的新帧率 `30.0`
```swift
print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// 打印 "The frameRate property of theEighty is now 30.0"
```
这个例子也显示了为何引用类型更加难以理解。如果 `tenEighty``alsoTenEighty` 在你代码中的位置相距很远,那么就很难找到所有修改视频模式的地方。无论在哪使用 `tenEighty`,你都要考虑使用 `alsoTenEighty` 的代码,反之亦然。相反,值类型就更容易理解了,因为你的源码中与同一个值交互的代码都很近。
需要注意的是 `tenEighty``alsoTenEighty` 被声明为常量而不是变量。然而你依然可以改变 `tenEighty.frameRate``alsoTenEighty.frameRate`,这是因为 `tenEighty``alsoTenEighty` 这两个常量的值并未改变。它们并不“存储”这个 `VideoMode` 实例,而仅仅是对 `VideoMode` 实例的引用。所以,改变的是底层 `VideoMode` 实例的 `frameRate` 属性,而不是指向 `VideoMode` 的常量引用的值。
### 恒等运算符 {#identity-operators}
因为类是引用类型,所以多个常量和变量可能在幕后同时引用同一个类实例。(对于结构体和枚举来说,这并不成立。因为它们作为值类型,在被赋予到常量、变量或者传递到函数时,其值总是会被拷贝。)
判定两个常量或者变量是否引用同一个类实例有时很有用。为了达到这个目的Swift 提供了两个恒等运算符:
* 相同(`===`
* 不相同(`!==`
使用这两个运算符检测两个常量或者变量是否引用了同一个实例:
```swift
if tenEighty === alsoTenEighty {
print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
}
// 打印 "tenEighty and alsoTenEighty refer to the same VideoMode instance."
```
请注意,“相同”(用三个等号表示,`===`)与“等于”(用两个等号表示,`==`的不同。“相同”表示两个类类型class type的常量或者变量引用同一个类实例。“等于”表示两个实例的值“相等”或“等价”判定时要遵照设计者定义的评判标准。
当在定义你的自定义结构体和类的时候,你有义务来决定判定两个实例“相等”的标准。在章节 [等价操作符](./26_Advanced_Operators.md#equivalence_operators) 中将会详细介绍实现自定义 == 和 !== 运算符的流程。
### 指针 {#pointers}
如果你有 CC++ 或者 Objective-C 语言的经验,那么你也许会知道这些语言使用*指针*来引用内存中的地址。Swift 中引用了某个引用类型实例的常量或变量,与 C 语言中的指针类似,不过它并不直接指向某个内存地址,也不要求你使用星号(`*`来表明你在创建一个引用。相反Swift 中引用的定义方式与其它的常量或变量的一样。如果需要直接与指针交互,你可以使用标准库提供的指针和缓冲区类型 —— 参见 [手动管理内存](https://developer.apple.com/documentation/swift/swift_standard_library/manual_memory_management)。

View File

@ -1,47 +1,18 @@
# 属性 (Properties)
---
# 属性
> 1.0
> 翻译:[shinyzhu](https://github.com/shinyzhu)
> 校对:[pp-prog](https://github.com/pp-prog) [yangsiy](https://github.com/yangsiy)
*属性*将值与特定的类、结构体或枚举关联。存储属性会将常量和变量存储为实例的一部分,而计算属性则是直接计算(而不是存储)值。计算属性可以用于类、结构体和枚举,而存储属性只能用于类和结构体。
存储属性和计算属性通常与特定类型的实例关联。但是,属性也可以直接与类型本身关联,这种属性称为类型属性。
> 2.0
> 翻译+校对:[yangsiy](https://github.com/yangsiy)
另外,还可以定义属性观察器来监控属性值的变化,以此来触发自定义的操作。属性观察器可以添加到类本身定义的存储属性上,也可以添加到从父类继承的属性上。
> 2.1
> 翻译:[buginux](https://github.com/buginux)
> 校对:[shanks](http://codebuild.me)2015-10-29
> 2.2
> 翻译:[saitjr](https://github.com/saitjr)2016-04-11[SketchK](https://github.com/SketchK) 2016-05-13
本页包含内容:
- [存储属性Stored Properties](#stored_properties)
- [计算属性Computed Properties](#computed_properties)
- [属性观察器Property Observers](#property_observers)
- [全局变量和局部变量Global and Local Variables](#global_and_local_variables)
- [类型属性Type Properties](#type_properties)
*属性*将值跟特定的类、结构或枚举关联。存储属性存储常量或变量作为实例的一部分,而计算属性计算(不是存储)一个值。计算属性可以用于类、结构体和枚举,存储属性只能用于类和结构体。
存储属性和计算属性通常与特定类型的实例关联。但是,属性也可以直接作用于类型本身,这种属性称为类型属性。
另外,还可以定义属性观察器来监控属性值的变化,以此来触发一个自定义的操作。属性观察器可以添加到自己定义的存储属性上,也可以添加到从父类继承的属性上。
<a name="stored_properties"></a>
## 存储属性
## 存储属性 {#stored-properties}
简单来说,一个存储属性就是存储在特定类或结构体实例里的一个常量或变量。存储属性可以是*变量存储属性*(用关键字 `var` 定义),也可以是*常量存储属性*(用关键字 `let` 定义)。
可以在定义存储属性的时候指定默认值,请参考[默认构造器](./14_Initialization.html#default_initializers)一节。也可以在构造过程中设置或修改存储属性的值,甚至修改常量存储属性的值,请参考[构造过程中常量属性的修改](./14_Initialization.html#assigning_constant_properties_during_initialization)一节。
可以在定义存储属性的时候指定默认值,请参考 [默认构造器](./14_Initialization.md#default_initializers) 一节。也可以在构造过程中设置或修改存储属性的值,甚至修改常量存储属性的值,请参考 [构造过程中常量属性的修改](./14_Initialization.md#assigning_constant_properties_during_initialization) 一节。
下面的例子定义了一个名为 `FixedLengthRange` 的结构体,该结构体用于描述整数的范围,且这个范围值在被创建后不能被修改.
下面的例子定义了一个名为 `FixedLengthRange` 的结构体,该结构体用于描述整数的区间,且这个范围值在被创建后不能被修改
```swift
struct FixedLengthRange {
@ -49,42 +20,41 @@ struct FixedLengthRange {
let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// 该区间表示整数012
// 该区间表示整数 012
rangeOfThreeItems.firstValue = 6
// 该区间现在表示整数678
// 该区间现在表示整数 678
```
`FixedLengthRange` 的实例包含一个名为 `firstValue` 的变量存储属性和一个名为 `length` 的常量存储属性。在上面的例子中,`length` 在创建实例的时候被初始化,因为它是一个常量存储属性,所以之后无法修改它的值
`FixedLengthRange` 的实例包含一个名为 `firstValue` 的变量存储属性和一个名为 `length` 的常量存储属性。在上面的例子中,`length` 在创建实例的时候被初始化,且之后无法修改它的值,因为它是一个常量存储属性。
<a name="stored_properties_of_constant_structure_instances"></a>
### 常量结构体的存储属性
### 常量结构体实例的存储属性 {#stored-properties-of-constant-structure-instances}
如果创建了一个结构体实例并将其赋值给一个常量,则无法修改该实例的任何属性,即使有属性被声明为变量也不行
如果创建了一个结构体实例并将其赋值给一个常量,则无法修改该实例的任何属性,即使被声明为可变属性也不行:
```swift
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// 该区间表示整数0123
// 该区间表示整数 0123
rangeOfFourItems.firstValue = 6
// 尽管 firstValue 是个变属性,这里还是会报错
// 尽管 firstValue 是个变属性,这里还是会报错
```
因为 `rangeOfFourItems` 被声明成了常量(用 `let` 关键字),即使 `firstValue` 是一个变属性,也无法再修改它了。
因为 `rangeOfFourItems` 被声明成了常量(用 `let` 关键字),所以即使 `firstValue` 是一个变属性,也无法再修改它了。
这种行为是由于结构体struct属于*值类型*。当值类型的实例被声明为常量的时候,它的所有属性也就成了常量。
这种行为是由于结构体属于*值类型*。当值类型的实例被声明为常量的时候,它的所有属性也就成了常量。
属于*引用类型*的类class则不一样。把一个引用类型的实例赋给一个常量后,然可以修改该实例的变属性。
属于*引用类型*的类则不一样。把一个引用类型的实例赋给一个常量后,然可以修改该实例的变属性。
<a name="lazy_stored_properties"></a>
### 延迟存储属性
### 延时加载存储属性 {#lazy-stored-properties}
延迟存储属性是指当第一次被调用的时候才会计算其初始值的属性。在属性声明前使用 `lazy` 来标示一个延存储属性。
*延时加载存储属性*是指当第一次被调用的时候才会计算其初始值的属性。在属性声明前使用 `lazy` 来标示一个延时加载存储属性。
> 注意
> 必须将延迟存储属性声明成变量(使用 `var` 关键字),因为属性的初始值可能在实例构造完成之后才会得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。
> 注意
>
> 必须将延时加载属性声明成变量(使用 `var` 关键字),因为属性的初始值可能在实例构造完成之后才会得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延时加载。
延迟属性很有用,当属性的值依赖于在实例的构造过程结束后才会知道影响值的外部因素时,或者当获得属性的初始值需要复杂或大量计算时,可以只在需要的时候计算
当属性的值依赖于一些外部因素且这些外部因素只有在构造过程结束后才会知道的时候,延时加载属性就会很有用。或者当获得属性的值因为需要复杂或大量计算,而需要采用需要的时候计算的方式,延时加载属性也会很有用
下面的例子使用了延存储属性来避免复杂类中不必要的初始化。例子中定义了 `DataImporter``DataManager` 两个类,下面是部分代码:
下面的例子使用了延时加载存储属性来避免复杂类中不必要的初始化工作。例子中定义了 `DataImporter``DataManager` 两个类,下面是部分代码:
```swift
class DataImporter {
@ -108,32 +78,31 @@ manager.data.append("Some more data")
// DataImporter 实例的 importer 属性还没有被创建
```
`DataManager` 类包含一个名为 `data` 的存储属性,初始值是一个空的字符串`String`数组。这里没有给出全部代码,只需知道 `DataManager` 类的目的是管理和提供对这个字符串数组的访问即可。
`DataManager` 类包含一个名为 `data` 的存储属性,初始值是一个空的字符串数组。这里没有给出全部代码,只需知道 `DataManager` 类的目的是管理和提供对这个字符串数组的访问即可。
`DataManager` 的一个功能是从文件导入数据。功能由 `DataImporter` 类提供,`DataImporter` 完成初始化需要消耗不少时间:因为它的实例在初始化时可能要打开文件,还要读取文件内容到内存。
`DataManager` 的一个功能是从文件导入数据。这个功能由 `DataImporter` 类提供,`DataImporter` 完成初始化需要消耗不少时间:因为它的实例在初始化时可能要打开文件读取文件中的内容到内存
`DataManager` 管理数据时也可能不从文件中导入数据。所以当 `DataManager` 的实例被创建时,没必要创建一个 `DataImporter` 的实例,更明智的做法是第一次用到 `DataImporter` 的时候才去创建它。
由于使用了 `lazy` `importer` 属性只有在第一次被访问的时候才被创建。比如访问它的属性 `fileName` 时:
由于使用了 `lazy``DataImporter` 的实例 `importer` 属性只有在第一次被访问的时候才被创建。比如访问它的属性 `fileName` 时:
```swift
print(manager.importer.fileName)
// DataImporter 实例的 importer 属性现在被创建了
// 输出 "data.txt”
// 输出data.txt”
```
> 注意
> 注意
>
> 如果一个被标记为 `lazy` 的属性在没有初始化时就同时被多个线程访问,则无法保证该属性只会被初始化一次。
<a name="stored_properties_and_instance_variables"></a>
### 存储属性和实例变量
### 存储属性和实例变量 {#stored-properties-and-instance-variables}
如果您有过 Objective-C 经验,应该知道 Objective-C 为类实例存储值和引用提供两种方法。除了属性之外,还可以使用实例变量作为属性值的后端存储
如果您有过 Objective-C 经验,应该知道 Objective-C 为类实例存储值和引用提供两种方法。除了属性之外,还可以使用实例变量作为一个备份存储将变量值赋值给属性。
Swift 编程语言中把这些理论统一用属性来实现。Swift 中的属性没有对应的实例变量,属性的后端存储也无法直接访问。这就避免了不同场景下访问方式的困扰,同时也将属性的定义简化成一个语句。属性的全部信息——包括命名、类型和内存管理特征——都在唯一一个地方(类型定义中)定义
Swift 编程语言中把这些理论统一用属性来实现。Swift 中的属性没有对应的实例变量,属性的备份存储也无法直接访问。这就避免了不同场景下访问方式的困扰,同时也将属性的定义简化成一个语句。属性的全部信息——包括命名、类型和内存管理特征——作为类型定义的一部分,都定义在一个地方
<a name="computed_properties"></a>
## 计算属性
## 计算属性 {#computed-properties}
除存储属性外,类、结构体和枚举可以定义*计算属性*。计算属性不直接存储值,而是提供一个 getter 和一个可选的 setter来间接获取和设置其他属性或变量的值。
@ -164,7 +133,7 @@ var square = Rect(origin: Point(x: 0.0, y: 0.0),
let initialSquareCenter = square.center
square.center = Point(x: 15.0, y: 15.0)
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// 输出 "square.origin is now at (10.0, 10.0)”
// 打印“square.origin is now at (10.0, 10.0)”
```
这个例子定义了 3 个结构体来描述几何形状:
@ -173,20 +142,19 @@ print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
- `Size` 封装了一个 `width` 和一个 `height`
- `Rect` 表示一个有原点和尺寸的矩形
`Rect`也提供了一个名为`center` 的计算属性。一个矩形的中心点可以从原点(`origin`)和大小`size`)算出,所以不需要将它以显式声明的 `Point` 来保存。`Rect` 的计算属性 `center` 提供了自定义的 getter 和 setter 来获取和设置矩形的中心点,就像它有一个存储属性一样。
`Rect` 也提供了一个名为 `center` 的计算属性。一个 `Rect` 的中心点可以从 `origin`原点)和 `size`(大小)算出,所以不需要将中心点以 `Point` 类型的值来保存。`Rect` 的计算属性 `center` 提供了自定义的 getter 和 setter 来获取和设置矩形的中心点,就像它有一个存储属性一样。
上述例子中创建了一个名为 `square``Rect` 实例,初始值原点是 `(0, 0)`,宽度高度都是 `10`。如下图中蓝色正方形所示。
`square``center` 属性可以通过点运算符(`square.center`)来访问,这会调用该属性的 getter 来获取它的值。跟直接返回已经存在的值不同getter 实际上通过计算然后返回一个新的 `Point` 来表示 `square` 的中心点。如代码所示,它正确返回了中心点 `(5, 5)`
`center` 属性之后被设置了一个新的值 `(15, 15)`,表示向右上方移动正方形到如下图橙色正方形所示的位置。设置属性`center`的值会调用它的 setter 来修改属性 `origin``x``y` 的值,从而实现移动正方形到新的位置。
`center` 属性之后被设置了一个新的值 `(15, 15)`,表示向右上方移动正方形到如下图橙色正方形所示的位置。设置属性 `center` 的值会调用它的 setter 来修改属性 `origin``x``y` 的值,从而实现移动正方形到新的位置。
<img src="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/computedProperties_2x.png" alt="Computed Properties sample" width="388" height="387" />
<img src="https://docs.swift.org/swift-book/_images/computedProperties_2x.png" alt="Computed Properties sample" width="388" height="387" />
<a name="shorthand_setter_declaration"></a>
### 便捷 setter 声明
### 简化 Setter 声明 {#shorthand-setter-declaration}
如果计算属性的 setter 没有定义表示新值的参数名,则可以使用默认名称 `newValue`。下面是使用了便捷 setter 声明的 `Rect` 结构体代码:
如果计算属性的 setter 没有定义表示新值的参数名,则可以使用默认名称 `newValue`。下面是使用了简化 setter 声明的 `Rect` 结构体代码:
```swift
struct AlternativeRect {
@ -206,12 +174,34 @@ struct AlternativeRect {
}
```
<a name="readonly_computed_properties"></a>
### 只读计算属性
### 简化 Getter 声明 {#shorthand-getter-declaration}
如果整个 getter 是单一表达式getter 会隐式地返回这个表达式结果。下面是另一个版本的 `Rect` 结构体,用到了简化的 getter 和 setter 声明:
只有 getter 没有 setter 的计算属性就是*只读计算属性*。只读计算属性总是返回一个值,可以通过点运算符访问,但不能设置新的值。
```swift
struct CompactRect {
var origin = Point()
var size = Size()
var center: Point {
get {
Point(x: origin.x + (size.width / 2),
y: origin.y + (size.height / 2))
}
set {
origin.x = newValue.x - (size.width / 2)
origin.y = newValue.y - (size.height / 2)
}
}
}
```
> 注意
在 getter 中忽略 `return` 与在函数中忽略 `return` 的规则相同,请参考 [隐式返回的函数](./06_Functions.md/#functions-with-an-implicit-return)。
### 只读计算属性 {#readonly-computed-properties}
只有 getter 没有 setter 的计算属性叫*只读计算属性*。只读计算属性总是返回一个值,可以通过点运算符访问,但不能设置新的值。
> 注意
>
> 必须使用 `var` 关键字定义计算属性,包括只读计算属性,因为它们的值不是固定的。`let` 关键字只用来声明常量属性,表示初始化后再也无法修改的值。
只读计算属性的声明可以去掉 `get` 关键字和花括号:
@ -225,31 +215,31 @@ struct Cuboid {
}
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
// 输出 "the volume of fourByFiveByTwo is 40.0"
// 打印“the volume of fourByFiveByTwo is 40.0
```
这个例子定义了一个名为 `Cuboid` 的结构体,表示三维空间的立方体,包含 `width``height``depth` 属性。结构体还有一个名为 `volume` 的只读计算属性用来返回立方体的体积。为 `volume` 提供 setter 毫无意义,因为无法确定如何修改 `width``height``depth` 三者的值来匹配新的 `volume`。然而,`Cuboid` 提供一个只读计算属性来让外部用户直接获取体积是很有用的。
<a name="property_observers"></a>
## 属性观察器
## 属性观察器 {#property-observers}
*属性观察器*监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,即使新值和当前值相同的时候也不例外。
属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,即使新值和当前值相同的时候也不例外。
可以为除了延存储属性之外的其他存储属性添加属性观察器,也可以通过重写属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器。你不必为非重写的计算属性添加属性观察器,因为可以通过它的 setter 直接监控和响应值的变化。 属性重写请参考[重写](./13_Inheritance.html#overriding)。
可以为除了延时加载存储属性之外的其他存储属性添加属性观察器,也可以在子类中通过重写属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器。你不必为非重写的计算属性添加属性观察器,因为可以直接通过它的 setter 监控和响应值的变化。属性重写请参考 [重写](./13_Inheritance.md#overriding)。
可以为属性添加如下的一个或全部观察器:
可以为属性添加其中一个或两个观察器:
- `willSet` 在新的值被设置之前调用
- `didSet` 在新的值被设置之后立即调用
- `didSet` 在新的值被设置之后调用
`willSet` 观察器会将新的属性值作为常量参数传入,在 `willSet` 的实现代码中可以为这个参数指定一个名称,如果不指定则参数仍然可用,这时使用默认名称 `newValue` 表示。
同样,`didSet` 观察器会将旧的属性值作为参数传入,可以为该参数命名或者使用默认参数名 `oldValue`。如果在 `didSet` 方法中再次对该属性赋值,那么新值会覆盖旧的值。
同样,`didSet` 观察器会将旧的属性值作为参数传入,可以为该参数指定一个名称或者使用默认参数名 `oldValue`。如果在 `didSet` 方法中再次对该属性赋值,那么新值会覆盖旧的值。
> 注意
> 父类的属性在子类的构造器中被赋值时,它在父类中的 `willSet` 和 `didSet` 观察器会被调用,随后才会调用子类的观察器。在父类初始化方法调用之前,子类给属性赋值时,观察器不会被调用。
> 有关构造器代理的更多信息,请参考[值类型的构造器代理](./14_Initialization.html#initializer_delegation_for_value_types)和[类的构造器代理规则](./14_Initialization.html#initializer_delegation_for_class_types)
> 注意
>
> 在父类初始化方法调用之后,在子类构造器中给父类的属性赋值时,会调用父类属性的 `willSet` 和 `didSet` 观察器。而在父类初始化方法调用之前,给子类的属性赋值时不会调用子类属性的观察器
>
> 有关构造器代理的更多信息,请参考 [值类型的构造器代理](./14_Initialization.md#initializer_delegation_for_value_types) 和 [类的构造器代理](./14_Initialization.md#initializer_delegation_for_class_types)。
下面是一个 `willSet``didSet` 实际运用的例子,其中定义了一个名为 `StepCounter` 的类,用来统计一个人步行时的总步数。这个类可以跟计步器或其他日常锻炼的统计装置的输入数据配合使用。
@ -257,28 +247,28 @@ print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
print(" totalSteps 的值设置为 \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
print("增加了 \(totalSteps - oldValue) ")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// About to set totalSteps to 200
// Added 200 steps
// totalSteps 的值设置为 200
// 增加了 200 步
stepCounter.totalSteps = 360
// About to set totalSteps to 360
// Added 160 steps
// totalSteps 的值设置为 360
// 增加了 160 步
stepCounter.totalSteps = 896
// About to set totalSteps to 896
// Added 536 steps
// totalSteps 的值设置为 896
// 增加了 536 步
```
`StepCounter` 类定义了一个 `Int` 类型的属性 `totalSteps`它是一个存储属性,包含 `willSet``didSet` 观察器。
`StepCounter` 类定义了一个 `totalSteps``Int` 类型的属性。它是一个存储属性,包含 `willSet``didSet` 观察器。
`totalSteps` 被设置新值的时候,它的 `willSet``didSet` 观察器都会被调用,即使新值和当前值完全相同时也会被调用。
@ -286,42 +276,43 @@ stepCounter.totalSteps = 896
`didSet` 观察器在 `totalSteps` 的值改变后被调用,它把新值和旧值进行对比,如果总步数增加了,就输出一个消息表示增加了多少步。`didSet` 没有为旧值提供自定义名称,所以默认值 `oldValue` 表示旧值的参数名。
>注意
>
>如果将属性通过 in-out 方式传入函数,`willSet` 和 `didSet` 也会调用。这是因为 in-out 参数采用了拷入拷出模式:即在函数内部使用的是参数的 copy函数结束后又对参数重新赋值。关于 in-out 参数详细的介绍,请参考[输入输出参数](../chapter3/05_Declarations.html#in-out_parameters)
> 注意
>
> 如果将带有观察器的属性通过 in-out 方式传入函数,`willSet` 和 `didSet` 也会调用。这是因为 in-out 参数采用了拷入拷出内存模式:即在函数内部使用的是参数的 copy函数结束后又对参数重新赋值。关于 in-out 参数详细的介绍,请参考 [输入输出参数](../chapter3/05_Declarations.html#in-out_parameters)
<a name="global_and_local_variables"></a>
##全局变量和局部变量
## 全局变量和局部变量 {#global-and-local-variables}
计算属性和属性观察器所描述的功能也可以用于*全局变量*和*局部变量*。全局变量是在函数、方法、闭包或任何类型之外定义的变量。局部变量是在函数、方法或闭包内部定义的变量。
计算属性和观察属性所描述的功能也可以用于*全局变量*和*局部变量*。全局变量是在函数、方法、闭包或任何类型之外定义的变量。局部变量是在函数、方法或闭包内部定义的变量。
前面章节提到的全局或局部变量都属于存储型变量,跟存储属性类似,它为特定类型的值提供存储空间,并允许读取和写入。
前面章节提到的全局或局部变量都属于*存储型变量*,跟存储属性类似,它为特定类型的值提供存储空间,并允许读取和写入。
另外,在全局或局部范围都可以定义计算型变量和为存储型变量定义观察器。计算型变量跟计算属性一样,返回一个计算结果而不是存储值,声明格式也完全一样。
另外,在全局或局部范围都可以定义*计算型变量*和为存储型变量定义观察器。计算型变量跟计算属性一样,返回一个计算结果而不是存储值,声明格式也完全一样。
> 注意
> 全局的常量或变量都是延迟计算的,跟[延迟存储属性](#lazy_stored_properties)相似,不同的地方在于,全局的常量或变量不需要标记`lazy`修饰符。
> 部范围的常量或变量从不延迟计算。
> 注意
>
> 局的常量或变量都是延迟计算的,跟 [延时加载存储属性](#lazy_stored_properties) 相似,不同的地方在于,全局的常量或变量不需要标记 `lazy` 修饰符
>
> 局部范围的常量和变量从不延迟计算。
<a name="type_properties"></a>
##类型属性
## 类型属性 {#type-properties}
实例属性属于一个特定类型的实例,每创建一个实例,实例都拥有属于自己的一套属性值,实例之间的属性相互独立。
也可以为类型本身定义属性,无论创建了多少个该类型的实例,这些属性都只有唯一一份。这种属性就是*类型属性*。
也可以为类型本身定义属性,无论创建了多少个该类型的实例,这些属性都只有唯一一份。这种属性就是*类型属性*。
类型属性用于定义某个类型所有实例共享的数据,比如所有实例都能用的一个常量(就像 C 语言中的静态常量),或者所有实例都能访问的一个变量(就像 C 语言中的静态变量)。
类型属性用于定义某个类型所有实例共享的数据,比如*所有*实例都能用的一个常量(就像 C 语言中的静态常量),或者所有实例都能访问的一个变量(就像 C 语言中的静态变量)。
存储型类型属性可以是变量或常量,计算型类型属性跟实例的计算型属性一样只能定义成变量属性。
> 注意
> 跟实例的存储型属性不同,必须给存储型类型属性指定默认值,因为类型本身没有构造器,也就无法在初始化过程中使用构造器给类型属性赋值。
> 注意
>
> 跟实例的存储型属性不同,必须给存储型类型属性指定默认值,因为类型本身没有构造器,也就无法在初始化过程中使用构造器给类型属性赋值。
>
> 存储型类型属性是延迟初始化的,它们只有在第一次被访问的时候才会被初始化。即使它们被多个线程同时访问,系统也保证只会对其进行一次初始化,并且不需要对其使用 `lazy` 修饰符。
<a name="type_property_syntax"></a>
###类型属性语法
### 类型属性语法 {#type-property-syntax}
在 C 或 Objective-C 中,与某个类型关联的静态常量和静态变量,是作为全局(*global*)静态变量定义的。但是在 Swift 中,类型属性是作为类型定义的一部分写在类型最外层的花括号内,因此它的作用范围也就在类型支持的范围内。
在 C 或 Objective-C 中,与某个类型关联的静态常量和静态变量,是作为 *global*(全局)静态变量定义的。但是在 Swift 中,类型属性是作为类型定义的一部分写在类型最外层的花括号内,因此它的作用范围也就在类型支持的范围内。
使用关键字 `static` 来定义类型属性。在为类定义计算型类型属性时,可以改用关键字 `class` 来支持子类对父类的实现进行重写。下面的例子演示了存储型和计算型类型属性的语法:
@ -349,31 +340,31 @@ class SomeClass {
}
```
> 注意
> 例子中的计算型类型属性是只读的,但也可以定义可读可写的计算型类型属性,跟计算型实例属性的语法相同。
> 注意
>
> 例子中的计算型类型属性是只读的,但也可以定义可读可写的计算型类型属性,跟计算型实例属性的语法相同。
<a name="querying_and_setting_type_properties"></a>
###获取和设置类型属性的值
### 获取和设置类型属性的值 {#querying-and-setting-type-properties}
跟实例属性一样,类型属性也是通过点运算符来访问。但是,类型属性是通过类型本身来访问,而不是通过实例。比如:
跟实例属性一样,类型属性也是通过点运算符来访问。但是,类型属性是通过*类型*本身来访问,而不是通过实例。比如:
```swift
print(SomeStructure.storedTypeProperty)
// 输出 "Some value."
// 打印“Some value.
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// 输出 "Another value.”
// 打印“Another value.”
print(SomeEnumeration.computedTypeProperty)
// 输出 "6"
// 打印“6”
print(SomeClass.computedTypeProperty)
// 输出 "27"
// 打印“27
```
下面的例子定义了一个结构体,使用两个存储型类型属性来表示两个声道的音量,每个声道具有 `0``10` 之间的整数音量。
下图展示了如何把两个声道结合来模拟立体声的音量。当声道的音量是 `0`,没有一个灯会亮;当声道的音量是 `10`,所有灯点亮。本图中,左声道的音量是 `9`,右声道的音量是 `7`
<img src="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/staticPropertiesVUMeter_2x.png" alt="Static Properties VUMeter" width="243" height="357" />
<img src="https://docs.swift.org/swift-book/_images/staticPropertiesVUMeter_2x.png" alt="Static Properties VUMeter" width="243" height="357" />
上面所描述的声道模型使用 `AudioChannel` 结构体的实例来表示:
@ -384,7 +375,7 @@ struct AudioChannel {
var currentLevel: Int = 0 {
didSet {
if currentLevel > AudioChannel.thresholdLevel {
// 将当前音量限制在值之内
// 将当前音量限制在值之内
currentLevel = AudioChannel.thresholdLevel
}
if currentLevel > AudioChannel.maxInputLevelForAllChannels {
@ -396,9 +387,9 @@ struct AudioChannel {
}
```
结构 `AudioChannel` 定义了 2 个存储型类型属性来实现上述功能。第一个是 `thresholdLevel`,表示音量的最大上限阈值,它是一个值为 `10` 的常量,对所有实例都可见,如果音量高于 `10`,则取最大上限值 `10`(见后面描述)。
`AudioChannel` 结构定义了 2 个存储型类型属性来实现上述功能。第一个是 `thresholdLevel`,表示音量的最大上限阈值,它是一个值为 `10` 的常量,对所有实例都可见,如果音量高于 `10`,则取最大上限值 `10`(见后面描述)。
第二个类型属性是变量存储型属性 `maxInputLevelForAllChannels`,它用来表示所有 `AudioChannel` 实例的最大音量,初始值是`0`
第二个类型属性是变量存储型属性 `maxInputLevelForAllChannels`,它用来表示所有 `AudioChannel` 实例的最大输入音量,初始值是 `0`
`AudioChannel` 也定义了一个名为 `currentLevel` 的存储型实例属性,表示当前声道现在的音量,取值为 `0``10`
@ -407,8 +398,9 @@ struct AudioChannel {
- 如果 `currentLevel` 的新值大于允许的阈值 `thresholdLevel`,属性观察器将 `currentLevel` 的值限定为阈值 `thresholdLevel`
- 如果修正后的 `currentLevel` 值大于静态类型属性 `maxInputLevelForAllChannels` 的值,属性观察器就将新值保存在 `maxInputLevelForAllChannels` 中。
> 注意
> 在第一个检查过程中,`didSet` 属性观察器将 `currentLevel` 设置成了不同的值,但这不会造成属性观察器被再次调用。
> 注意
>
> 在第一个检查过程中,`didSet` 属性观察器将 `currentLevel` 设置成了不同的值,但这不会造成属性观察器被再次调用。
可以使用结构体 `AudioChannel` 创建两个声道 `leftChannel``rightChannel`,用以表示立体声系统的音量:
@ -422,9 +414,9 @@ var rightChannel = AudioChannel()
```swift
leftChannel.currentLevel = 7
print(leftChannel.currentLevel)
// 输出 "7"
// 输出“7”
print(AudioChannel.maxInputLevelForAllChannels)
// 输出 "7"
// 输出“7”
```
如果试图将右声道的 `currentLevel` 设置成 `11`,它会被修正到最大值 `10`,同时 `maxInputLevelForAllChannels` 的值也会更新到 `10`
@ -432,7 +424,7 @@ print(AudioChannel.maxInputLevelForAllChannels)
```swift
rightChannel.currentLevel = 11
print(rightChannel.currentLevel)
// 输出 "10"
// 输出10
print(AudioChannel.maxInputLevelForAllChannels)
// 输出 "10"
// 输出10
```

View File

@ -1,311 +1,255 @@
# 方法Methods
-----------------
> 1.0
> 翻译:[pp-prog](https://github.com/pp-prog)
> 校对:[zqp](https://github.com/zqp)
> 2.0
> 翻译+校对:[DianQK](https://github.com/DianQK)
> 2.1
> 翻译:[DianQK](https://github.com/DianQK)[Realank](https://github.com/Realank) 校对:[shanks](http://codebuild.me)2016-01-18
# 方法
*方法*是与某些特定类型相关联的函数。类、结构体、枚举都可以定义实例方法;实例方法为给定类型的实例封装了具体的任务与功能。类、结构体、枚举也可以定义类型方法;类型方法与类型本身相关联。类型方法与 Objective-C 中的类方法class methods相似。
结构体和枚举能够定义方法是 Swift 与 C/Objective-C 的主要区别之一。在 Objective-C 中,类是唯一能定义方法的类型。但在 Swift 中,你不仅能选择是否要定义一个类/结构体/枚举,还能灵活地在你创建的类型(类/结构体/枚举)上定义方法。
## 实例方法Instance Methods {#instance-methods}
*实例方法*是属于某个特定类、结构体或者枚举类型实例的方法。实例方法提供访问和修改实例属性的方法或提供与实例目的相关的功能,并以此来支撑实例的功能。实例方法的语法与函数完全一致,详情参见 [函数](./06_Functions.md)。
实例方法要写在它所属的类型的前后大括号之间。实例方法能够隐式访问它所属类型的所有的其他实例方法和属性。实例方法只能被它所属的类的某个特定实例调用。实例方法不能脱离于现存的实例而被调用。
下面的例子,定义一个很简单的 `Counter` 类,`Counter` 能被用来对一个动作发生的次数进行计数:
```swift
class Counter {
var count = 0
func increment() {
count += 1
}
func increment(by amount: Int) {
count += amount
}
func reset() {
count = 0
}
}
```
`Counter` 类定义了三个实例方法:
- `increment` 让计数器按一递增;
- `increment(by: Int)` 让计数器按一个指定的整数值递增;
- `reset` 将计数器重置为0。
`Counter` 这个类还声明了一个可变属性 `count`,用它来保持对当前计数器值的追踪。
和调用属性一样用点语法dot syntax调用实例方法
```swift
let counter = Counter()
// 初始计数值是0
counter.increment()
// 计数值现在是1
counter.increment(by: 5)
// 计数值现在是6
counter.reset()
// 计数值现在是0
```
函数参数可以同时有一个局部名称(在函数体内部使用)和一个外部名称(在调用函数时使用),详情参见 [指定外部参数名](./06_Functions.md#specifying_external_parameter_names)。方法参数也一样,因为方法就是函数,只是这个函数与某个类型相关联了。
### self 属性 {#the-self-property}
类型的每一个实例都有一个隐含属性叫做 `self``self` 完全等同于该实例本身。你可以在一个实例的实例方法中使用这个隐含的 `self` 属性来引用当前实例。
上面例子中的 `increment` 方法还可以这样写:
```swift
func increment() {
self.count += 1
}
```
实际上,你不必在你的代码里面经常写 `self`。不论何时,只要在一个方法中使用一个已知的属性或者方法名称,如果你没有明确地写 `self`Swift 假定你是指当前实例的属性或者方法。这种假定在上面的 `Counter` 中已经示范了:`Counter` 中的三个实例方法中都使用的是 `count`(而不是 `self.count`)。
使用这条规则的主要场景是实例方法的某个参数名称与实例的某个属性名称相同的时候。在这种情况下,参数名称享有优先权,并且在引用属性时必须使用一种更严格的方式。这时你可以使用 `self` 属性来区分参数名称和属性名称。
下面的例子中,`self` 消除方法参数 `x` 和实例属性 `x` 之间的歧义:
```swift
struct Point {
var x = 0.0, y = 0.0
func isToTheRightOf(x: Double) -> Bool {
return self.x > x
}
}
let somePoint = Point(x: 4.0, y: 5.0)
if somePoint.isToTheRightOf(x: 1.0) {
print("This point is to the right of the line where x == 1.0")
}
// 打印“This point is to the right of the line where x == 1.0”
```
如果不使用 `self` 前缀Swift会认为 `x` 的两个用法都引用了名为 `x` 的方法参数。
### 在实例方法中修改值类型 {#modifying-value-types-from-within-instance-methods}
结构体和枚举是*值类型*。默认情况下,值类型的属性不能在它的实例方法中被修改。
但是,如果你确实需要在某个特定的方法中修改结构体或者枚举的属性,你可以为这个方法选择 `可变mutating`行为,然后就可以从其方法内部改变它的属性;并且这个方法做的任何改变都会在方法执行结束时写回到原始结构中。方法还可以给它隐含的 `self` 属性赋予一个全新的实例,这个新实例在方法结束时会替换现存实例。
要使用 `可变`方法,将关键字 `mutating` 放到方法的 `func` 关键字之前就可以了:
```swift
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0)
print("The point is now at (\(somePoint.x), \(somePoint.y))")
// 打印“The point is now at (3.0, 4.0)”
```
上面的 `Point` 结构体定义了一个可变方法 `moveByxy :)` 来移动 `Point` 实例到给定的位置。该方法被调用时修改了这个点,而不是返回一个新的点。方法定义时加上了 `mutating` 关键字,从而允许修改属性。
注意不能在结构体类型的常量a constant of structure type上调用可变方法因为其属性不能被改变即使属性是变量属性详情参见 [常量结构体的存储属性](./10_Properties.md#stored_properties_of_constant_structure_instances)
```swift
let fixedPoint = Point(x: 3.0, y: 3.0)
fixedPoint.moveBy(x: 2.0, y: 3.0)
// 这里将会报告一个错误
```
### 在可变方法中给 self 赋值 {#assigning-to-self-within-a-mutating-method}
可变方法能够赋给隐含属性 `self` 一个全新的实例。上面 `Point` 的例子可以用下面的方式改写:
```swift
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
self = Point(x: x + deltaX, y: y + deltaY)
}
}
```
新版的可变方法 `moveBy(x:y:)` 创建了一个新的结构体实例,它的 x 和 y 的值都被设定为目标值。调用这个版本的方法和调用上个版本的最终结果是一样的。
枚举的可变方法可以把 `self` 设置为同一枚举类型中不同的成员:
```swift
enum TriStateSwitch {
case off, low, high
mutating func next() {
switch self {
case .off:
self = .low
case .low:
self = .high
case .high:
self = .off
}
}
}
var ovenLight = TriStateSwitch.low
ovenLight.next()
// ovenLight 现在等于 .high
ovenLight.next()
// ovenLight 现在等于 .off
```
上面的例子中定义了一个三态切换的枚举。每次调用 `next()` 方法时,开关在不同的电源状态(`off`, `low`, `high`)之间循环切换。
## 类型方法 {#type-methods}
实例方法是被某个类型的实例调用的方法。你也可以定义在类型本身上调用的方法,这种方法就叫做*类型方法*。在方法的 `func` 关键字之前加上关键字 `static`,来指定类型方法。类还可以用关键字 `class` 来指定,从而允许子类重写父类该方法的实现。
> 注意
>
> 2.2
> 校对:[SketchK](https://github.com/SketchK) 2016-05-13
本页包含内容:
- [实例方法(Instance Methods)](#instance_methods)
- [类型方法(Type Methods)](#type_methods)
**方法**是与某些特定类型相关联的函数。类、结构体、枚举都可以定义实例方法;实例方法为给定类型的实例封装了具体的任务与功能。类、结构体、枚举也可以定义类型方法;类型方法与类型本身相关联。类型方法与 Objective-C 中的类方法class methods相似。
结构体和枚举能够定义方法是 Swift 与 C/Objective-C 的主要区别之一。在 Objective-C 中,类是唯一能定义方法的类型。但在 Swift 中,你不仅能选择是否要定义一个类/结构体/枚举,还能灵活地在你创建的类型(类/结构体/枚举)上定义方法。
<a name="instance_methods"></a>
## 实例方法 (Instance Methods)
**实例方法**是属于某个特定类、结构体或者枚举类型实例的方法。实例方法提供访问和修改实例属性的方法或提供与实例目的相关的功能,并以此来支撑实例的功能。实例方法的语法与函数完全一致,详情参见[函数](./06_Functions.md)
实例方法要写在它所属的类型的前后大括号之间。实例方法能够隐式访问它所属类型的所有的其他实例方法和属性。实例方法只能被它所属的类的某个特定实例调用。实例方法不能脱离于现存的实例而被调用
下面的例子,定义一个很简单的`Counter`类,`Counter`能被用来对一个动作发生的次数进行计数:
```swift
class Counter {
var count = 0
func increment() {
count += 1
}
func incrementBy(amount: Int) {
count += amount
}
func reset() {
count = 0
}
}
```
`Counter`类定义了三个实例方法:
- `increment`让计数器按一递增;
- `incrementBy(amount: Int)`让计数器按一个指定的整数值递增;
- `reset`将计数器重置为0。
`Counter`这个类还声明了一个可变属性`count`,用它来保持对当前计数器值的追踪。
和调用属性一样用点语法dot syntax调用实例方法
```swift
let counter = Counter()
// 初始计数值是0
counter.increment()
// 计数值现在是1
counter.incrementBy(5)
// 计数值现在是6
counter.reset()
// 计数值现在是0
```
<a name="local_and_external_parameter"></a>
### 方法的局部参数名称和外部参数名称 (Local and External Parameter Names for Methods)
函数参数可以同时有一个局部名称(在函数体内部使用)和一个外部名称(在调用函数时使用),详情参见[指定外部参数名](./06_Functions.html#specifying_external_parameter_names)。方法参数也一样(因为方法就是函数,只是这个函数与某个类型相关联了)。
Swift 中的方法和 Objective-C 中的方法极其相似。像在 Objective-C 中一样Swift 中方法的名称通常用一个介词指向方法的第一个参数,比如:`with``for``by`等等。前面的`Counter`类的例子中`incrementBy(_:)`方法就是这样的。介词的使用让方法在被调用时能像一个句子一样被解读。
具体来说Swift 默认仅给方法的第一个参数名称一个局部参数名称;默认同时给第二个和后续的参数名称局部参数名称和外部参数名称。这个约定与典型的命名和调用约定相适应,与你在写 Objective-C 的方法时很相似。这个约定还让富于表达性的方法在调用时不需要再限定参数名称。
看看下面这个`Counter`的另一个版本(它定义了一个更复杂的`incrementBy(_:)`方法):
```swift
class Counter {
var count: Int = 0
func incrementBy(amount: Int, numberOfTimes: Int) {
count += amount * numberOfTimes
}
}
```
`incrementBy(_:numberOfTimes:)`方法有两个参数: `amount``numberOfTimes`。默认情况下Swift 只把`amount`当作一个局部名称,但是把`numberOfTimes`即看作局部名称又看作外部名称。下面调用这个方法:
```swift
let counter = Counter()
counter.incrementBy(5, numberOfTimes: 3)
// counter 的值现在是 15
```
你不必为第一个参数值再定义一个外部变量名:因为从函数名`incrementBy(_numberOfTimes:)`已经能很清楚地看出它的作用。但是第二个参数,就要被一个外部参数名称所限定,以便在方法被调用时明确它的作用。
上面描述的这种默认行为意味着在 Swift 中,定义方法使用了与 Objective-C 同样的语法风格,并且方法将以自然且富于表达性的方式被调用。
<a name="modifying_external_parameter_name_behavior_for_methods"></a>
### 修改方法的外部参数名称(Modifying External Parameter Name Behavior for Methods)
有时为方法的第一个参数提供一个外部参数名称是非常有用的,尽管这不是默认的行为。你自己可以为第一个参数添加一个显式的外部名称。
相反,如果你不想为方法的第二个及后续的参数提供一个外部名称,可以通过使用下划线(`_`)作为该参数的显式外部名称,这样做将覆盖默认行为。
<a name="the_self_property"></a>
### self 属性(The self Property)
类型的每一个实例都有一个隐含属性叫做`self``self`完全等同于该实例本身。你可以在一个实例的实例方法中使用这个隐含的`self`属性来引用当前实例。
上面例子中的`increment`方法还可以这样写:
```swift
func increment() {
self.count += 1
}
```
实际上,你不必在你的代码里面经常写`self`。不论何时,只要在一个方法中使用一个已知的属性或者方法名称,如果你没有明确地写`self`Swift 假定你是指当前实例的属性或者方法。这种假定在上面的`Counter`中已经示范了:`Counter`中的三个实例方法中都使用的是`count`(而不是`self.count`)。
使用这条规则的主要场景是实例方法的某个参数名称与实例的某个属性名称相同的时候。在这种情况下,参数名称享有优先权,并且在引用属性时必须使用一种更严格的方式。这时你可以使用`self`属性来区分参数名称和属性名称。
下面的例子中,`self`消除方法参数`x`和实例属性`x`之间的歧义:
```swift
struct Point {
var x = 0.0, y = 0.0
func isToTheRightOfX(x: Double) -> Bool {
return self.x > x
}
}
let somePoint = Point(x: 4.0, y: 5.0)
if somePoint.isToTheRightOfX(1.0) {
print("This point is to the right of the line where x == 1.0")
}
// 打印输出: This point is to the right of the line where x == 1.0
```
如果不使用`self`前缀Swift 就认为两次使用的`x`都指的是名称为`x`的函数参数。
<a name="modifying_value_types_from_within_instance_methods"></a>
### 在实例方法中修改值类型(Modifying Value Types from Within Instance Methods)
结构体和枚举是**值类型**。默认情况下,值类型的属性不能在它的实例方法中被修改。
但是,如果你确实需要在某个特定的方法中修改结构体或者枚举的属性,你可以为这个方法选择`可变(mutating)`行为,然后就可以从其方法内部改变它的属性;并且这个方法做的任何改变都会在方法执行结束时写回到原始结构中。方法还可以给它隐含的`self`属性赋予一个全新的实例,这个新实例在方法结束时会替换现存实例。
要使用`可变`方法,将关键字`mutating` 放到方法的`func`关键字之前就可以了:
```swift
struct Point {
var x = 0.0, y = 0.0
mutating func moveByX(deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveByX(2.0, y: 3.0)
print("The point is now at (\(somePoint.x), \(somePoint.y))")
// 打印输出: "The point is now at (3.0, 4.0)"
```
上面的`Point`结构体定义了一个可变方法 `moveByX(_:y:)` 来移动`Point`实例到给定的位置。该方法被调用时修改了这个点,而不是返回一个新的点。方法定义时加上了`mutating`关键字,从而允许修改属性。
注意不能在结构体类型的常量a constant of structure type上调用可变方法因为其属性不能被改变即使属性是变量属性详情参见[常量结构体的存储属性](./10_Properties.html#stored_properties_of_constant_structure_instances)
```swift
let fixedPoint = Point(x: 3.0, y: 3.0)
fixedPoint.moveByX(2.0, y: 3.0)
// 这里将会报告一个错误
```
<a name="assigning_to_self_within_a_mutating_method"></a>
### 在可变方法中给 self 赋值(Assigning to self Within a Mutating Method)
可变方法能够赋给隐含属性`self`一个全新的实例。上面`Point`的例子可以用下面的方式改写:
```swift
struct Point {
var x = 0.0, y = 0.0
mutating func moveByX(deltaX: Double, y deltaY: Double) {
self = Point(x: x + deltaX, y: y + deltaY)
}
}
```
新版的可变方法`moveByX(_:y:)`创建了一个新的结构体实例,它的 x 和 y 的值都被设定为目标值。调用这个版本的方法和调用上个版本的最终结果是一样的。
枚举的可变方法可以把`self`设置为同一枚举类型中不同的成员:
```swift
enum TriStateSwitch {
case Off, Low, High
mutating func next() {
switch self {
case Off:
self = Low
case Low:
self = High
case High:
self = Off
}
}
}
var ovenLight = TriStateSwitch.Low
ovenLight.next()
// ovenLight 现在等于 .High
ovenLight.next()
// ovenLight 现在等于 .Off
```
上面的例子中定义了一个三态开关的枚举。每次调用`next()`方法时,开关在不同的电源状态(`Off``Low``High`)之间循环切换。
<a name="type_methods"></a>
## 类型方法 (Type Methods)
实例方法是被某个类型的实例调用的方法。你也可以定义在类型本身上调用的方法,这种方法就叫做**类型方法**Type Methods。在方法的`func`关键字之前加上关键字`static`,来指定类型方法。类还可以用关键字`class`来允许子类重写父类的方法实现。
> 注意
> 在 Objective-C 中,你只能为 Objective-C 的类类型classes定义类型方法type-level methods。在 Swift 中,你可以为所有的类、结构体和枚举定义类型方法。每一个类型方法都被它所支持的类型显式包含。
类型方法和实例方法一样用点语法调用。但是,你是在类型上调用这个方法,而不是在实例上调用。下面是如何在`SomeClass`类上调用类型方法的例子:
```swift
class SomeClass {
class func someTypeMethod() {
// type method implementation goes here
}
}
SomeClass.someTypeMethod()
```
在类型方法的方法体body`self`指向这个类型本身,而不是类型的某个实例。这意味着你可以用`self`来消除类型属性和类型方法参数之间的歧义(类似于我们在前面处理实例属性和实例方法参数时做的那样)。
一般来说,在类型方法的方法体中,任何未限定的方法和属性名称,可以被本类中其他的类型方法和类型属性引用。一个类型方法可以直接通过类型方法的名称调用本类中的其它类型方法,而无需在方法名称前面加上类型名称。类似地,在结构体和枚举中,也能够直接通过类型属性的名称访问本类中的类型属性,而不需要前面加上类型名称。
下面的例子定义了一个名为`LevelTracker`结构体。它监测玩家的游戏发展情况(游戏的不同层次或阶段)。这是一个单人游戏,但也可以存储多个玩家在同一设备上的游戏信息。
游戏初始时,所有的游戏等级(除了等级 1都被锁定。每次有玩家完成一个等级这个等级就对这个设备上的所有玩家解锁。`LevelTracker`结构体用类型属性和方法监测游戏的哪个等级已经被解锁。它还监测每个玩家的当前等级。
```swift
struct LevelTracker {
static var highestUnlockedLevel = 1
static func unlockLevel(level: Int) {
if level > highestUnlockedLevel { highestUnlockedLevel = level }
}
static func levelIsUnlocked(level: Int) -> Bool {
return level <= highestUnlockedLevel
}
var currentLevel = 1
mutating func advanceToLevel(level: Int) -> Bool {
if LevelTracker.levelIsUnlocked(level) {
currentLevel = level
return true
} else {
return false
}
}
}
```
`LevelTracker`监测玩家已解锁的最高等级。这个值被存储在类型属性`highestUnlockedLevel`中。
`LevelTracker`还定义了两个类型方法与`highestUnlockedLevel`配合工作。第一个类型方法是`unlockLevel`,一旦新等级被解锁,它会更新`highestUnlockedLevel`的值。第二个类型方法是`levelIsUnlocked`,如果某个给定的等级已经被解锁,它将返回`true`。(注意,尽管我们没有使用类似`LevelTracker.highestUnlockedLevel`的写法,这个类型方法还是能够访问类型属性`highestUnlockedLevel`
除了类型属性和类型方法,`LevelTracker`还监测每个玩家的进度。它用实例属性`currentLevel`来监测每个玩家当前的等级。
为了便于管理`currentLevel`属性,`LevelTracker`定义了实例方法`advanceToLevel`。这个方法会在更新`currentLevel`之前检查所请求的新等级是否已经解锁。`advanceToLevel`方法返回布尔值以指示是否能够设置`currentLevel`
下面,`Player`类使用`LevelTracker`来监测和更新每个玩家的发展进度:
```swift
class Player {
var tracker = LevelTracker()
let playerName: String
func completedLevel(level: Int) {
LevelTracker.unlockLevel(level + 1)
tracker.advanceToLevel(level + 1)
}
init(name: String) {
playerName = name
}
}
```
`Player`类创建一个新的`LevelTracker`实例来监测这个用户的进度。它提供了`completedLevel`方法,一旦玩家完成某个指定等级就调用它。这个方法为所有玩家解锁下一等级,并且将当前玩家的进度更新为下一等级。(我们忽略了`advanceToLevel`返回的布尔值,因为之前调用`LevelTracker.unlockLevel`时就知道了这个等级已经被解锁了)。
你还可以为一个新的玩家创建一个`Player`的实例,然后看这个玩家完成等级一时发生了什么:
```swift
var player = Player(name: "Argyrios")
player.completedLevel(1)
print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)")
// 打印输出highest unlocked level is now 2
```
如果你创建了第二个玩家,并尝试让他开始一个没有被任何玩家解锁的等级,那么试图设置玩家当前等级将会失败:
```swift
player = Player(name: "Beto")
if player.tracker.advanceToLevel(6) {
print("player is now on level 6")
} else {
print("level 6 has not yet been unlocked")
}
// 打印输出level 6 has not yet been unlocked
```
> 在 Objective-C 中,你只能为 Objective-C 的类类型classes定义类型方法type-level methods。在 Swift 中,你可以为所有的类、结构体和枚举定义类型方法。每一个类型方法都被它所支持的类型显式包含。
类型方法和实例方法一样用点语法调用。但是,你是在类型上调用这个方法,而不是在实例上调用。下面是如何在 `SomeClass` 类上调用类型方法的例子:
```swift
class SomeClass {
class func someTypeMethod() {
// 在这里实现类型方法
}
}
SomeClass.someTypeMethod()
```
在类型方法的方法体body`self` 属性指向这个类型本身,而不是类型的某个实例。这意味着你可以用 `self` 来消除类型属性和类型方法参数之间的歧义(类似于我们在前面处理实例属性和实例方法参数时做的那样)。
一般来说,在类型方法的方法体中,任何未限定的方法和属性名称,可以被本类中其他的类型方法和类型属性引用。一个类型方法可以直接通过类型方法的名称调用本类中的其它类型方法,而无需在方法名称前面加上类型名称。类似地,在结构体和枚举中,也能够直接通过类型属性的名称访问本类中的类型属性,而不需要前面加上类型名称
下面的例子定义了一个名为 `LevelTracker` 结构体。它监测玩家的游戏发展情况(游戏的不同层次或阶段)。这是一个单人游戏,但也可以存储多个玩家在同一设备上的游戏信息
游戏初始时,所有的游戏等级(除了等级 1都被锁定。每次有玩家完成一个等级这个等级就对这个设备上的所有玩家解锁。`LevelTracker` 结构体用类型属性和方法监测游戏的哪个等级已经被解锁。它还监测每个玩家的当前等级。
```swift
struct LevelTracker {
static var highestUnlockedLevel = 1
var currentLevel = 1
static func unlock(_ level: Int) {
if level > highestUnlockedLevel { highestUnlockedLevel = level }
}
static func isUnlocked(_ level: Int) -> Bool {
return level <= highestUnlockedLevel
}
@discardableResult
mutating func advance(to level: Int) -> Bool {
if LevelTracker.isUnlocked(level) {
currentLevel = level
return true
} else {
return false
}
}
}
```
`LevelTracker` 监测玩家已解锁的最高等级。这个值被存储在类型属性 `highestUnlockedLevel` 中。
`LevelTracker` 还定义了两个类型方法与 `highestUnlockedLevel` 配合工作。第一个类型方法是 `unlock(_:)`,一旦新等级被解锁,它会更新 `highestUnlockedLevel` 的值。第二个类型方法是 `isUnlocked(_:)`,如果某个给定的等级已经被解锁,它将返回 `true`。(注意,尽管我们没有使用类似 `LevelTracker.highestUnlockedLevel` 的写法,这个类型方法还是能够访问类型属性 `highestUnlockedLevel`
除了类型属性和类型方法,`LevelTracker` 还监测每个玩家的进度。它用实例属性 `currentLevel` 来监测每个玩家当前的等级。
为了便于管理 `currentLevel` 属性,`LevelTracker` 定义了实例方法 `advance(to:)`。这个方法会在更新 `currentLevel` 之前检查所请求的新等级是否已经解锁。`advance(to:)` 方法返回布尔值以指示是否能够设置 `currentLevel`。因为允许在调用 `advance(to:)` 时候忽略返回值,不会产生编译警告,所以函数被标注为 `@discardableResult` 属性,更多关于属性信息,请参考 [特性](../chapter3/07_Attributes.html)章节。
下面,`Player` 类使用 `LevelTracker` 来监测和更新每个玩家的发展进度:
```swift
class Player {
var tracker = LevelTracker()
let playerName: String
func complete(level: Int) {
LevelTracker.unlock(level + 1)
tracker.advance(to: level + 1)
}
init(name: String) {
playerName = name
}
}
```
`Player` 类创建一个新的 `LevelTracker` 实例来监测这个用户的进度。它提供了 `complete(level:)` 方法,一旦玩家完成某个指定等级就调用它。这个方法为所有玩家解锁下一等级,并且将当前玩家的进度更新为下一等级。(我们忽略了 `advance(to:)` 返回的布尔值,因为之前调用 `LevelTracker.unlock(_:)` 时就知道了这个等级已经被解锁了)。
你还可以为一个新的玩家创建一个 `Player` 的实例,然后看这个玩家完成等级一时发生了什么:
```swift
var player = Player(name: "Argyrios")
player.complete(level: 1)
print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)")
// 打印“highest unlocked level is now 2”
```
如果你创建了第二个玩家,并尝试让他开始一个没有被任何玩家解锁的等级,那么试图设置玩家当前等级将会失败:
```swift
player = Player(name: "Beto")
if player.tracker.advance(to: 6) {
print("player is now on level 6")
} else {
print("level 6 has not yet been unlocked")
}
// 打印“level 6 has not yet been unlocked”
```

View File

@ -1,50 +1,27 @@
# 下标Subscripts
-----------------
# 下标
> 1.0
> 翻译:[siemenliu](https://github.com/siemenliu)
> 校对:[zq54zquan](https://github.com/zq54zquan)
> 2.0
> 翻译+校对:[shanks](http://codebuild.me)
> 2.1
> 翻译+校对:[shanks](http://codebuild.me)[Realank](https://github.com/Realank)
> 2.2
> 校对:[SketchK](https://github.com/SketchK) 2016-05-13
本页包含内容:
- [下标语法](#subscript_syntax)
- [下标用法](#subscript_usage)
- [下标选项](#subscript_options)
*下标* subscripts可以定义在类class、结构体structure和枚举enumeration是访问集合collection列表list或序列sequence中元素的快捷方式。可以使用下标的索引设置和获取值而不需要再调用对应的存取方法。举例来说用下标访问一个`Array`实例中的元素可以写作`someArray[index]`,访问`Dictionary`实例中的元素可以写作`someDictionary[key]`
*下标*可以定义在类、结构体和枚举中,是访问集合、列表或序列中元素的快捷方式。可以使用下标的索引,设置和获取值,而不需要再调用对应的存取方法。举例来说,用下标访问一个 `Array` 实例中的元素可以写作 `someArray[index]`,访问 `Dictionary` 实例中的元素可以写作 `someDictionary[key]`
一个类型可以定义多个下标,通过不同索引类型进行重载。下标不限于一维,你可以定义具有多个入参的下标满足自定义类型的需求。
<a name="subscript_syntax"></a>
## 下标语法
## 下标语法 {#subscript-syntax}
下标允许你通过在实例名称后面的方括号中传入一个或者多个索引值来对实例进行存取。语法类似于实例方法语法和计算型属性语法的混合。与定义实例方法类似,定义下标使用`subscript`关键字,指定一个或多个输入参数和返回类型;与实例方法不同的是,下标可以设定为读写或只读。这种行为由 getter 和 setter 实现,有点类似计算型属性:
下标允许你通过在实例名称后面的方括号中传入一个或者多个索引值来对实例进行存取。语法类似于实例方法语法和计算型属性语法的混合。与定义实例方法类似,定义下标使用 `subscript` 关键字,指定一个或多个输入参数和返回类型;与实例方法不同的是,下标可以设定为读写或只读。这种行为由 getter 和 setter 实现,有点类似计算型属性:
```swift
subscript(index: Int) -> Int {
get {
// 返回一个适当的 Int 类型的值
}
set(newValue) {
// 执行适当的赋值操作
}
}
```
`newValue`的类型和下标的返回类型相同。如同计算型属性,可以不指定 setter 的参数(`newValue`。如果不指定参数setter 会提供一个名为`newValue`的默认参数。
`newValue` 的类型和下标的返回类型相同。如同计算型属性,可以不指定 setter 的参数(`newValue`。如果不指定参数setter 会提供一个名为 `newValue` 的默认参数。
如同只读计算型属性,可以省略只读下标的`get`关键字:
如同只读计算型属性,可以省略只读下标的 `get` 关键字:
```swift
subscript(index: Int) -> Int {
@ -52,7 +29,7 @@ subscript(index: Int) -> Int {
}
```
下面代码演示了只读下标的实现,这里定义了一个`TimesTable`结构体,用来表示传入整数的乘法表:
下面代码演示了只读下标的实现,这里定义了一个 `TimesTable` 结构体,用来表示传入整数的乘法表:
```swift
struct TimesTable {
@ -63,43 +40,43 @@ struct TimesTable {
}
let threeTimesTable = TimesTable(multiplier: 3)
print("six times three is \(threeTimesTable[6])")
// 输出 "six times three is 18"
// 打印“six times three is 18
```
在上例中,创建了一个`TimesTable`实例,用来表示整数`3`的乘法表。数值`3`被传递给结构体的构造函数,作为实例成员`multiplier`的值。
在上例中,创建了一个 `TimesTable` 实例,用来表示整数 `3` 的乘法表。数值 `3` 被传递给结构体的构造函数,作为实例成员 `multiplier` 的值。
你可以通过下标访问`threeTimesTable`实例,例如上面演示的`threeTimesTable[6]`。这条语句查询了`3`的乘法表中的第六个元素,返回`3``6`倍即`18`
你可以通过下标访问 `threeTimesTable` 实例,例如上面演示的 `threeTimesTable[6]`。这条语句查询了 `3` 的乘法表中的第六个元素,返回 `3``6` 倍即 `18`
> 注意
> `TimesTable`例子基于一个固定的数学公式,对`threeTimesTable[someIndex]`进行赋值操作并不合适,因此下标定义为只读的。
> 注意
>
> `TimesTable` 例子基于一个固定的数学公式,对 `threeTimesTable[someIndex]` 进行赋值操作并不合适,因此下标定义为只读的。
<a name="subscript_usage"></a>
## 下标用法
## 下标用法 {#subscript-usage}
下标的确切含义取决于使用场景。下标通常作为访问集合collection列表list或序列sequence中元素的快捷方式。你可以针对自己特定的类或结构体的功能来自由地以最恰当的方式实现下标。
下标的确切含义取决于使用场景。下标通常作为访问集合,列表或序列中元素的快捷方式。你可以针对自己特定的类或结构体的功能来自由地以最恰当的方式实现下标。
例如Swift 的`Dictionary`类型实现下标用于对其实例中储存的值进行存取操作。为字典设值时,在下标中使用和字典的键类型相同的键,并把一个和字典的值类型相同的值赋给这个下标:
例如Swift 的 `Dictionary` 类型实现下标用于对其实例中储存的值进行存取操作。为字典设值时,在下标中使用和字典的键类型相同的键,并把一个和字典的值类型相同的值赋给这个下标:
```swift
var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
numberOfLegs["bird"] = 2
```
上例定义一个名为`numberOfLegs`的变量,并用一个包含三对键值的字典字面量初始化它。`numberOfLegs`字典的类型被推断为`[String: Int]`。字典创建完成后,该例子通过下标将`String`类型的键`bird``Int`类型的值`2`添加到字典中。
上例定义一个名为 `numberOfLegs` 的变量,并用一个包含三对键值的字典字面量初始化它。`numberOfLegs` 字典的类型被推断为 `[String: Int]`。字典创建完成后,该例子通过下标将 `String` 类型的键 `bird``Int` 类型的值 `2` 添加到字典中。
更多关于`Dictionary`下标的信息请参考[读取和修改字典](./04_Collection_Types.html#accessing_and_modifying_a_dictionary)
更多关于 `Dictionary` 下标的信息请参考 [读取和修改字典](./04_Collection_Types.md#accessing_and_modifying_a_dictionary)
> 注意
> Swift 的`Dictionary`类型的下标接受并返回可选类型的值。上例中的`numberOfLegs`字典通过下标返回的是一个`Int?`或者说“可选的int”。`Dictionary`类型之所以如此实现下标,是因为不是每个键都有个对应的值,同时这也提供了一种通过键删除对应值的方式,只需将键对应的值赋值为`nil`即可。
> 注意
>
> Swift 的 `Dictionary` 类型的下标接受并返回可选类型的值。上例中的 `numberOfLegs` 字典通过下标返回的是一个 `Int?` 或者说“可选的 int”。`Dictionary` 类型之所以如此实现下标,是因为不是每个键都有个对应的值,同时这也提供了一种通过键删除对应值的方式,只需将键对应的值赋值为 `nil` 即可。
<a name="subscript_options"></a>
## 下标选项
## 下标选项 {#subscript-options}
下标可以接受任意数量的入参,并且这些入参可以是任意类型。下标的返回值也可以是任意类型。下标可以使用变量参数和可变参数,但不能使用输入输出参数,也不能给参数设置默认值
下标可以接受任意数量的入参,并且这些入参可以是任意类型。下标的返回值也可以是任意类型。下标可以使用可变参数,并且可以提供默认参数数值,但不能使用输入输出参数。
一个类或结构体可以根据自身需要提供多个下标实现,使用下标时将通过入参的数量和类型进行区分,自动匹配合适的下标,这就是*下标的重载*。
虽然接受单一入参的下标是最常见的,但也可以根据情况定义接受多个入参的下标。例如下例定义了一个`Matrix`结构体,用于表示一个`Double`类型的二维矩阵。`Matrix`结构体的下标接受两个整型参数:
虽然接受单一入参的下标是最常见的,但也可以根据情况定义接受多个入参的下标。例如下例定义了一个 `Matrix` 结构体,用于表示一个 `Double` 类型的二维矩阵。`Matrix` 结构体的下标接受两个整型参数:
```swift
struct Matrix {
@ -108,51 +85,51 @@ struct Matrix {
init(rows: Int, columns: Int) {
self.rows = rows
self.columns = columns
grid = Array(count: rows * columns, repeatedValue: 0.0)
grid = Array(repeating: 0.0, count: rows * columns)
}
func indexIsValidForRow(row: Int, column: Int) -> Bool {
func indexIsValid(row: Int, column: Int) -> Bool {
return row >= 0 && row < rows && column >= 0 && column < columns
}
subscript(row: Int, column: Int) -> Double {
get {
assert(indexIsValidForRow(row, column: column), "Index out of range")
assert(indexIsValid(row: row, column: column), "Index out of range")
return grid[(row * columns) + column]
}
set {
assert(indexIsValidForRow(row, column: column), "Index out of range")
assert(indexIsValid(row: row, column: column), "Index out of range")
grid[(row * columns) + column] = newValue
}
}
}
```
`Matrix`提供了一个接受两个入参的构造方法,入参分别是`rows``columns`,创建了一个足够容纳`rows * columns``Double`类型的值的数组。通过传入数组长度和初始值`0.0`到数组的构造器,将矩阵中每个位置的值初始化为`0.0`。关于数组的这种构造方法请参考[创建一个数组](./04_Collection_Types.html#creating_an_empty_array)。
`Matrix` 提供了一个接受两个入参的构造方法,入参分别是 `rows``columns`,创建了一个足够容纳 `rows * columns``Double` 类型的值的数组。通过传入数组长度和初始值 `0.0` 到数组的构造器,将矩阵中每个位置的值初始化为 `0.0`。关于数组的这种构造方法请参考 [创建一个带有默认值的数组](./04_Collection_Types.md#creating_an_array_with_a_default_value)。
你可以通过传入合适的`row``column`的数量来构造一个新的`Matrix`实例:
你可以通过传入合适的 `row``column` 的数量来构造一个新的 `Matrix` 实例:
```swift
var matrix = Matrix(rows: 2, columns: 2)
```
上例中创建了一个`Matrix`实例来表示两行两列的矩阵。该`Matrix`实例的`grid`数组按照从左上到右下的阅读顺序将矩阵扁平化存储:
上例中创建了一个 `Matrix` 实例来表示两行两列的矩阵。该 `Matrix` 实例的 `grid` 数组按照从左上到右下的阅读顺序将矩阵扁平化存储:
![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/subscriptMatrix01_2x.png)
![](https://docs.swift.org/swift-book/_images/subscriptMatrix01_2x.png)
`row``column`的值传入下标来为矩阵设值,下标的入参使用逗号分隔:
`row``column` 的值传入下标来为矩阵设值,下标的入参使用逗号分隔:
```swift
matrix[0, 1] = 1.5
matrix[1, 0] = 3.2
```
上面两条语句分别调用下标的 setter 将矩阵右上角位置(即`row``0``column``1`的位置)的值设置为`1.5`,将矩阵左下角位置(即`row``1``column``0`的位置)的值设置为`3.2`
上面两条语句分别调用下标的 setter 将矩阵右上角位置(即 `row``0``column``1` 的位置)的值设置为 `1.5`,将矩阵左下角位置(即 `row``1``column``0` 的位置)的值设置为 `3.2`
![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/subscriptMatrix02_2x.png)
![](https://docs.swift.org/swift-book/_images/subscriptMatrix02_2x.png)
`Matrix`下标的 getter 和 setter 中都含有断言,用来检查下标入参`row``column`的值是否有效。为了方便进行断言,`Matrix`包含了一个名为`indexIsValidForRow(_:column:)`的便利方法,用来检查入参`row``column`的值是否在矩阵范围内:
`Matrix` 下标的 getter 和 setter 中都含有断言,用来检查下标入参 `row``column` 的值是否有效。为了方便进行断言,`Matrix` 包含了一个名为 `indexIsValid(row:column:)` 的便利方法,用来检查入参 `row``column` 的值是否在矩阵范围内:
```swift
func indexIsValidForRow(row: Int, column: Int) -> Bool {
func indexIsValid(row: Int, column: Int) -> Bool {
return row >= 0 && row < rows && column >= 0 && column < columns
}
```
@ -162,4 +139,18 @@ func indexIsValidForRow(row: Int, column: Int) -> Bool {
```swift
let someValue = matrix[2, 2]
// 断言将会触发,因为 [2, 2] 已经超过了 matrix 的范围
```
```
## 类型下标{#type-subscripts}
正如上节所述,实例下标是在特定类型的一个实例上调用的下标。你也可以定义一种在这个类型本身上调用的下标。这种下标的类型被称作类型下标。你可以通过在 `subscript` 关键字之前写下 `static` 关键字的方式来表示一个类型下标。类可以使用 `class` 关键字来允许子类重写父类中对那个下标的实现。下面的例子展示了如何定义和调用一个类型下标:
```
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
static subscript(n: Int) -> Planet {
return Planet(rawValue: n)!
}
}
let mars = Planet[4]
print(mars)
```

View File

@ -1,241 +1,221 @@
# 继承Inheritance
-------------------
> 1.0
> 翻译:[Hawstein](https://github.com/Hawstein)
> 校对:[menlongsheng](https://github.com/menlongsheng)
> 2.02.1
> 翻译+校对:[shanks](http://codebuild.me)
# 继承
一个类可以*继承*另一个类的方法,属性和其它特性。当一个类继承其它类时,继承类叫*子类*,被继承类叫*超类(或父类)*。在 Swift 中,继承是区分「类」与其它类型的一个基本特征。
在 Swift 中类可以调用和访问超类的方法、属性和下标并且可以重写这些方法属性和下标来优化或修改它们的行为。Swift 会检查你的重写定义在超类中是否有匹配的定义,以此确保你的重写行为是正确的。
可以为类中继承来的属性添加属性观察器,这样一来,当属性值改变时,类就会被通知到。可以为任何属性添加属性观察器,无论它原本被定义为存储型属性还是计算型属性。
## 定义一个基类 {#defining-a-base-class}
不继承于其它类的类,称之为*基类*。
> 注意
>
> 2.2
> 校对:[SketchK](https://github.com/SketchK) 2016-05-13
本页包含内容:
- [定义一个基类Defining a Base Class](#defining_a_base_class)
- [子类生成Subclassing](#subclassing)
- [重写Overriding](#overriding)
- [防止重写Preventing Overrides](#preventing_overrides)
一个类可以*继承inherit*另一个类的方法methods属性properties和其它特性。当一个类继承其它类时继承类叫*子类subclass*,被继承类叫*超类或父类superclass*。在 Swift 中,继承是区分「类」与其它类型的一个基本特征。
在 Swift 中类可以调用和访问超类的方法属性和下标subscripts并且可以重写override这些方法属性和下标来优化或修改它们的行为。Swift 会检查你的重写定义在超类中是否有匹配的定义,以此确保你的重写行为是正确的。
可以为类中继承来的属性添加属性观察器property observers这样一来当属性值改变时类就会被通知到。可以为任何属性添加属性观察器无论它原本被定义为存储型属性stored property还是计算型属性computed property
<a name="defining_a_base_class"></a>
## 定义一个基类Defining a Base Class
不继承于其它类的类,称之为*基类base class*。
> 注意
Swift 中的类并不是从一个通用的基类继承而来。如果你不为你定义的类指定一个超类的话,这个类就自动成为基类。
下面的例子定义了一个`Vehicle`的基类。这个基类声明了一个名为`currentSpeed `,默认值是`0.0`的存储属性(属性类型推断为`Double`)。`currentSpeed`属性的值被一个`String`类型的只读计算型属性`description`使用,用来创建车辆的描述。
`Vehicle`基类也定义了一个名为`makeNoise`的方法。这个方法实际上不为`Vehicle`实例做任何事,但之后将会被`Vehicle`的子类定制:
```swift
class Vehicle {
var currentSpeed = 0.0
var description: String {
return "traveling at \(currentSpeed) miles per hour"
}
func makeNoise() {
// 什么也不做-因为车辆不一定会有噪音
}
}
```
您可以用初始化语法创建一个`Vehicle`的新实例,即类名后面跟一个空括号:
```swift
let someVehicle = Vehicle()
```
现在已经创建了一个`Vehicle`的新实例,你可以访问它的`description`属性来打印车辆的当前速度:
```swift
print("Vehicle: \(someVehicle.description)")
// Vehicle: traveling at 0.0 miles per hour
```
`Vehicle`类定义了一个通用特性的车辆类,实际上没什么用处。为了让它变得更加有用,需要完善它从而能够描述一个更加具体类型的车辆
<a name="subclassing"></a>
## 子类生成Subclassing
*子类生成Subclassing*指的是在一个已有类的基础上创建一个新的类。子类继承超类的特性,并且可以进一步完善。你还可以为子类添加新的特性。
为了指明某个类的超类,将超类名写在子类名的后面,用冒号分隔:
```swift
class SomeClass: SomeSuperclass {
// 这里是子类的定义
}
```
下一个例子,定义一个叫`Bicycle`的子类,继承成父类`Vehicle`
```swift
class Bicycle: Vehicle {
var hasBasket = false
}
```
新的`Bicycle`类自动获得`Vehicle`类的所有特性,比如`currentSpeed``description`属性,还有它的`makeNoise()`方法。
除了它所继承的特性,`Bicycle`类还定义了一个默认值为`false`的存储型属性`hasBasket`(属性推断为`Bool`)。
默认情况下,你创建任何新的`Bicycle`实例将不会有一个篮子(即`hasBasket`属性默认为`false`),创建该实例之后,你可以为特定的`Bicycle`实例设置`hasBasket`属性为`ture`
```swift
let bicycle = Bicycle()
bicycle.hasBasket = true
```
你还可以修改`Bicycle`实例所继承的`currentSpeed`属性,和查询实例所继承的`description`属性:
```swift
bicycle.currentSpeed = 15.0
print("Bicycle: \(bicycle.description)")
// Bicycle: traveling at 15.0 miles per hour
```
子类可以继续被其它类继承,下面的示例为`Bicycle`创建了一个名为`Tandem`(双人自行车)的子类:
```swift
class Tandem: Bicycle {
var currentNumberOfPassengers = 0
}
```
`Tandem``Bicycle`继承了所有的属性与方法,这又使它同时继承了`Vehicle`的所有属性与方法。`Tandem`也增加了一个新的叫做`currentNumberOfPassengers`的存储型属性,默认值为`0`
如果你创建了一个`Tandem`的实例,你可以使用它所有的新属性和继承的属性,还能查询从`Vehicle`继承来的只读属性`description`
```swift
let tandem = Tandem()
tandem.hasBasket = true
tandem.currentNumberOfPassengers = 2
tandem.currentSpeed = 22.0
print("Tandem: \(tandem.description)")
// Tandem: traveling at 22.0 miles per hour
```
<a name="overriding"></a>
## 重写Overriding
子类可以为继承来的实例方法instance method类方法class method实例属性instance property或下标subscript提供自己定制的实现implementation。我们把这种行为叫*重写overriding*。
如果要重写某个特性,你需要在重写定义的前面加上`override`关键字。这么做,你就表明了你是想提供一个重写版本,而非错误地提供了一个相同的定义。意外的重写行为可能会导致不可预知的错误,任何缺少`override`关键字的重写都会在编译时被诊断为错误。
`override`关键字会提醒 Swift 编译器去检查该类的超类(或其中一个父类)是否有匹配重写版本的声明。这个检查可以确保你的重写定义是正确的。
### 访问超类的方法,属性及下标
当你在子类中重写超类的方法,属性或下标时,有时在你的重写版本中使用已经存在的超类实现会大有裨益。比如,你可以完善已有实现的行为,或在一个继承来的变量中存储一个修改过的值。
在合适的地方,你可以通过使用`super`前缀来访问超类版本的方法,属性或下标:
* 在方法`someMethod()`的重写实现中,可以通过`super.someMethod()`来调用超类版本的`someMethod()`方法。
* 在属性`someProperty`的 getter 或 setter 的重写实现中,可以通过`super.someProperty`来访问超类版本的`someProperty`属性。
* 在下标的重写实现中,可以通过`super[someIndex]`来访问超类版本中的相同下标。
### 重写方法
在子类中,你可以重写继承来的实例方法或类方法,提供一个定制或替代的方法实现。
下面的例子定义了`Vehicle`的一个新的子类,叫`Train`,它重写了从`Vehicle`类继承来的`makeNoise()`方法:
```swift
class Train: Vehicle {
override func makeNoise() {
print("Choo Choo")
}
}
```
如果你创建一个`Train`的新实例,并调用了它的`makeNoise()`方法,你就会发现`Train`版本的方法被调用:
```swift
let train = Train()
train.makeNoise()
// 打印 "Choo Choo"
```
### 重写属性
你可以重写继承来的实例属性或类型属性,提供自己定制的 getter 和 setter或添加属性观察器使重写的属性可以观察属性值什么时候发生改变。
#### 重写属性的 Getters 和 Setters
你可以提供定制的 getter或 setter来重写任意继承来的属性无论继承来的属性是存储型的还是计算型的属性。子类并不知道继承来的属性是存储型的还是计算型的它只知道继承来的属性会有一个名字和类型。你在重写一个属性时必需将它的名字和类型都写出来。这样才能使编译器去检查你重写的属性是与超类中同名同类型的属性相匹配的。
你可以将一个继承来的只读属性重写为一个读写属性,只需要在重写版本的属性里提供 getter 和 setter 即可。但是,你不可以将一个继承来的读写属性重写为一个只读属性。
> 注意
如果你在重写属性中提供了 setter那么你也一定要提供 getter。如果你不想在重写版本中的 getter 里修改继承来的属性值,你可以直接通过`super.someProperty`来返回继承来的值,其中`someProperty`是你要重写的属性的名字。
以下的例子定义了一个新类,叫`Car`,它是`Vehicle`的子类。这个类引入了一个新的存储型属性叫做`gear`,默认值为整数`1``Car`类重写了继承自`Vehicle``description`属性,提供包含当前档位的自定义描述:
```swift
class Car: Vehicle {
var gear = 1
override var description: String {
return super.description + " in gear \(gear)"
}
}
```
重写的`description`属性首先要调用`super.description`返回`Vehicle`类的`description`属性。之后,`Car`类版本的`description`在末尾增加了一些额外的文本来提供关于当前档位的信息。
如果你创建了`Car`的实例并且设置了它的`gear``currentSpeed`属性,你可以看到它的`description`返回了`Car`中的自定义描述:
```swift
let car = Car()
car.currentSpeed = 25.0
car.gear = 3
print("Car: \(car.description)")
// Car: traveling at 25.0 miles per hour in gear 3
```
<a name="overriding_property_observers"></a>
#### 重写属性观察器Property Observer
你可以通过重写属性为一个继承来的属性添加属性观察器。这样一来,当继承来的属性值发生改变时,你就会被通知到,无论那个属性原本是如何实现的。关于属性观察器的更多内容,请看[属性观察器](../chapter2/10_Properties.html#property_observers)。
> 注意
你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。这些属性的值是不可以被设置的,所以,为它们提供`willSet``didSet`实现是不恰当。
此外还要注意,你不可以同时提供重写的 setter 和重写的属性观察器。如果你想观察属性值的变化,并且你已经为那个属性提供了定制的 setter那么你在 setter 中就可以观察到任何值变化了。
下面的例子定义了一个新类叫`AutomaticCar`,它是`Car`的子类。`AutomaticCar`表示自动挡汽车,它可以根据当前的速度自动选择合适的挡位:
```swift
class AutomaticCar: Car {
override var currentSpeed: Double {
didSet {
gear = Int(currentSpeed / 10.0) + 1
}
}
}
```
当你设置`AutomaticCar``currentSpeed`属性,属性的`didSet`观察器就会自动地设置`gear`属性,为新的速度选择一个合适的挡位。具体来说就是,属性观察器将新的速度值除以`10`,然后向下取得最接近的整数值,最后加`1`来得到档位`gear`的值。例如,速度为`35.0`时,挡位为`4`
```swift
let automatic = AutomaticCar()
automatic.currentSpeed = 35.0
print("AutomaticCar: \(automatic.description)")
// AutomaticCar: traveling at 35.0 miles per hour in gear 4
```
<a name="preventing_overrides"></a>
## 防止重写
你可以通过把方法,属性或下标标记为*`final`*来防止它们被重写,只需要在声明关键字前加上`final`修饰符即可(例如:`final var``final func``final class func`,以及`final subscript`)。
如果你重写了带有`final`标记的方法,属性或下标,在编译时会报错。在类扩展中的方法,属性或下标也可以在扩展的定义里标记为 final 的。
你可以通过在关键字`class`前添加`final`修饰符(`final class`)来将整个类标记为 final 的。这样的类是不可被继承的,试图继承这样的类会导致编译报错。
> Swift 中的类并不是从一个通用的基类继承而来的。如果你不为自己定义的类指定一个超类的话,这个类就会自动成为基类。
下面的例子定义了一个叫 `Vehicle` 的基类。这个基类声明了一个名为 `currentSpeed`,默认值是 `0.0` 的存储型属性(属性类型推断为 `Double`)。`currentSpeed` 属性的值被一个 `String` 类型的只读计算型属性 `description` 使用,用来创建对于车辆的描述。
`Vehicle` 基类还定义了一个名为 `makeNoise` 的方法。这个方法实际上不为 `Vehicle` 实例做任何事,但之后将会被 `Vehicle` 的子类定制:
```swift
class Vehicle {
var currentSpeed = 0.0
var description: String {
return "traveling at \(currentSpeed) miles per hour"
}
func makeNoise() {
// 什么也不做——因为车辆不一定会有噪音
}
}
```
可以用初始化语法创建一个 `Vehicle` 的新实例,即类名后面跟一个空括号:
```swift
let someVehicle = Vehicle()
```
现在已经创建了一个 `Vehicle` 的新实例,你可以访问它的 `description` 属性来打印车辆的当前速度:
```swift
print("Vehicle: \(someVehicle.description)")
// 打印“Vehicle: traveling at 0.0 miles per hour”
```
`Vehicle` 类定义了一个具有通用特性的车辆类,但实际上对于它本身来说没什么用处。为了让它变得更加有用,还需要进一步完善它,从而能够描述一个具体类型的车辆。
## 子类生成 {#subclassing}
*子类生成*指的是在一个已有类的基础上创建一个新的类。子类继承超类的特性,并且可以进一步完善。你还可以为子类添加新的特性。
为了指明某个类的超类,将超类名写在子类名的后面,用冒号分隔:
```swift
class SomeClass: SomeSuperclass {
// 这里是子类的定义
}
```
下一个例子,定义了一个叫 `Bicycle` 的子类,继承自父类 `Vehicle`
```swift
class Bicycle: Vehicle {
var hasBasket = false
}
```
新的 `Bicycle` 类自动继承 `Vehicle` 类的所有特性,比如 `currentSpeed``description` 属性,还有 `makeNoise()` 方法
除了所继承的特性,`Bicycle` 类还定义了一个默认值为 `false` 的存储型属性 `hasBasket`(属性推断为 `Bool`)。
默认情况下,你创建的所有新的 `Bicycle` 实例不会有一个篮子(即 `hasBasket` 属性默认为 `false`)。创建该实例之后,你可以为 `Bicycle` 实例设置 `hasBasket` 属性为 `ture`
```swift
let bicycle = Bicycle()
bicycle.hasBasket = true
```
你还可以修改 `Bicycle` 实例所继承的 `currentSpeed` 属性,和查询实例所继承的 `description` 属性:
```swift
bicycle.currentSpeed = 15.0
print("Bicycle: \(bicycle.description)")
// 打印“Bicycle: traveling at 15.0 miles per hour”
```
子类还可以继续被其它类继承,下面的示例为 `Bicycle` 创建了一个名为 `Tandem`(双人自行车)的子类:
```swift
class Tandem: Bicycle {
var currentNumberOfPassengers = 0
}
```
`Tandem``Bicycle` 继承了所有的属性与方法,这又使它同时继承了 `Vehicle` 的所有属性与方法。`Tandem` 也增加了一个新的叫做 `currentNumberOfPassengers` 的存储型属性,默认值为 `0`
如果你创建了一个 `Tandem` 的实例,你可以使用它所有的新属性和继承的属性,还能查询从 `Vehicle` 继承来的只读属性 `description`
```swift
let tandem = Tandem()
tandem.hasBasket = true
tandem.currentNumberOfPassengers = 2
tandem.currentSpeed = 22.0
print("Tandem: \(tandem.description)")
// 打印“Tandem: traveling at 22.0 miles per hour”
```
## 重写 {#overriding}
子类可以为继承来的实例方法,类方法,实例属性,类属性,或下标提供自己定制的实现。我们把这种行为叫*重写*。
如果要重写某个特性,你需要在重写定义的前面加上 `override` 关键字。这么做,就表明了你是想提供一个重写版本,而非错误地提供了一个相同的定义。意外的重写行为可能会导致不可预知的错误,任何缺少 `override` 关键字的重写都会在编译时被认定为错误。
`override` 关键字会提醒 Swift 编译器去检查该类的超类(或其中一个父类)是否有匹配重写版本的声明。这个检查可以确保你的重写定义是正确的。
### 访问超类的方法,属性及下标 {#accessing-superclass-methods-properties-and-subscripts}
当你在子类中重写超类的方法,属性或下标时,有时在你的重写版本中使用已经存在的超类实现会大有裨益。比如,你可以完善已有实现的行为,或在一个继承来的变量中存储一个修改过的值
在合适的地方,你可以通过使用 `super` 前缀来访问超类版本的方法,属性或下标
* 在方法 `someMethod()` 的重写实现中,可以通过 `super.someMethod()` 来调用超类版本的 `someMethod()` 方法。
* 在属性 `someProperty` 的 getter 或 setter 的重写实现中,可以通过 `super.someProperty` 来访问超类版本的 `someProperty` 属性。
* 在下标的重写实现中,可以通过 `super[someIndex]` 来访问超类版本中的相同下标。
### 重写方法 {#overriding-methods}
在子类中,你可以重写继承来的实例方法或类方法,提供一个定制或替代的方法实现。
下面的例子定义了 `Vehicle` 的一个新的子类,叫 `Train`,它重写了从 `Vehicle` 类继承来的 `makeNoise()` 方法:
```swift
class Train: Vehicle {
override func makeNoise() {
print("Choo Choo")
}
}
```
如果你创建一个 `Train` 的新实例,并调用了它的 `makeNoise()` 方法,你就会发现 `Train` 版本的方法被调用:
```swift
let train = Train()
train.makeNoise()
// 打印“Choo Choo”
```
### 重写属性 {#overriding-properties}
你可以重写继承来的实例属性或类型属性,提供自己定制的 getter 和 setter或添加属性观察器使重写的属性可以观察到底层的属性值什么时候发生改变。
#### 重写属性的 Getters 和 Setters {#overriding-property-etters-and-setters}
你可以提供定制的 getter或 setter来重写任何一个继承来的属性无论这个属性是存储型还是计算型属性。子类并不知道继承来的属性是存储型的还是计算型的它只知道继承来的属性会有一个名字和类型。你在重写一个属性时必须将它的名字和类型都写出来。这样才能使编译器去检查你重写的属性是与超类中同名同类型的属性相匹配的。
你可以将一个继承来的只读属性重写为一个读写属性,只需要在重写版本的属性里提供 getter 和 setter 即可。但是,你不可以将一个继承来的读写属性重写为一个只读属性。
> 注意
>
> 如果你在重写属性中提供了 setter那么你也一定要提供 getter。如果你不想在重写版本中的 getter 里修改继承来的属性值,你可以直接通过 `super.someProperty` 来返回继承来的值,其中 `someProperty` 是你要重写的属性的名字。
以下的例子定义了一个新类,叫 `Car`,它是 `Vehicle` 的子类。这个类引入了一个新的存储型属性叫做 `gear`,默认值为整数 `1``Car` 类重写了继承自 `Vehicle``description` 属性,提供包含当前档位的自定义描述:
```swift
class Car: Vehicle {
var gear = 1
override var description: String {
return super.description + " in gear \(gear)"
}
}
```
重写的 `description` 属性首先要调用 `super.description` 返回 `Vehicle` 类的 `description` 属性。之后,`Car` 类版本的 `description` 在末尾增加了一些额外的文本来提供关于当前档位的信息。
如果你创建了 `Car` 的实例并且设置了它的 `gear``currentSpeed` 属性,你可以看到它的 `description` 返回了 `Car` 中的自定义描述:
```swift
let car = Car()
car.currentSpeed = 25.0
car.gear = 3
print("Car: \(car.description)")
// 打印“Car: traveling at 25.0 miles per hour in gear 3”
```
#### 重写属性观察器 {#overriding-property-observers}
你可以通过重写属性为一个继承来的属性添加属性观察器。这样一来,无论被继承属性原本是如何实现的,当其属性值发生改变时,你就会被通知到。关于属性观察器的更多内容,请看 [属性观察器](../chapter2/10_Properties.html#property_observers)。
> 注意
>
> 你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。这些属性的值是不可以被设置的,所以,为它们提供 `willSet` 或 `didSet` 实现也是不恰当。
此外还要注意,你不可以同时提供重写的 setter 和重写的属性观察器。如果你想观察属性值的变化,并且你已经为那个属性提供了定制的 setter那么你在 setter 中就可以观察到任何值变化了。
下面的例子定义了一个新类叫 `AutomaticCar`,它是 `Car` 的子类。`AutomaticCar` 表示自动档汽车,它可以根据当前的速度自动选择合适的档位:
```swift
class AutomaticCar: Car {
override var currentSpeed: Double {
didSet {
gear = Int(currentSpeed / 10.0) + 1
}
}
}
```
当你设置 `AutomaticCar``currentSpeed` 属性,属性的 `didSet` 观察器就会自动地设置 `gear` 属性,为新的速度选择一个合适的档位。具体来说就是,属性观察器将新的速度值除以 `10`,然后向下取得最接近的整数值,最后加 `1` 来得到档位 `gear` 的值。例如,速度为 `35.0` 时,档位为 `4`
```swift
let automatic = AutomaticCar()
automatic.currentSpeed = 35.0
print("AutomaticCar: \(automatic.description)")
// 打印“AutomaticCar: traveling at 35.0 miles per hour in gear 4”
```
## 防止重写 {#preventing-overrides}
你可以通过把方法,属性或下标标记为 *`final`* 来防止它们被重写,只需要在声明关键字前加上 `final` 修饰符即可(例如:`final var``final func``final class func` 以及 `final subscript`)。
任何试图对带有 `final` 标记的方法、属性或下标进行重写的代码,都会在编译时会报错。在类扩展中的方法,属性或下标也可以在扩展的定义里标记为 `final`
可以通过在关键字 `class` 前添加 `final` 修饰符(`final class`)来将整个类标记为 final 。这样的类是不可被继承的,试图继承这样的类会导致编译报错。

File diff suppressed because it is too large Load Diff

View File

@ -1,32 +1,12 @@
# 析构过程Deinitialization
---------------------------
# 析构过程
> 1.0
> 翻译:[bruce0505](https://github.com/bruce0505)
> 校对:[fd5788](https://github.com/fd5788)
*析构器*只适用于类类型,当一个类的实例被释放之前,析构器会被立即调用。析构器用关键字 `deinit` 来标示,类似于构造器要用 `init` 来标示。
> 2.0
> 翻译+校对:[chenmingbiao](https://github.com/chenmingbiao)
## 析构过程原理 {#how-deinitialization-works}
> 2.1
> 校对:[shanks](http://codebuild.me)2015-10-31
>
> 2.2
> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-14
Swift 会自动释放不再需要的实例以释放资源。如 [自动引用计数](./23_Automatic_Reference_Counting.md) 章节中所讲述Swift 通过*自动引用计数ARC)* 处理实例的内存管理。通常当你的实例被释放时不需要手动地去清理。但是,当使用自己的资源时,你可能需要进行一些额外的清理。例如,如果创建了一个自定义的类来打开一个文件,并写入一些数据,你可能需要在类实例被释放之前手动去关闭该文件。
本页包含内容
- [析构过程原理](#how_deinitialization_works)
- [析构器实践](#deinitializers_in_action)
*析构器*只适用于类类型,当一个类的实例被释放之前,析构器会被立即调用。析构器用关键字`deinit`来标示,类似于构造器要用`init`来标示。
<a name="how_deinitialization_works"></a>
##析构过程原理
Swift 会自动释放不再需要的实例以释放资源。如[自动引用计数](./16_Automatic_Reference_Counting.html)章节中所讲述Swift 通过`自动引用计数ARC`处理实例的内存管理。通常当你的实例被释放时不需要手动地去清理。但是,当使用自己的资源时,你可能需要进行一些额外的清理。例如,如果创建了一个自定义的类来打开一个文件,并写入一些数据,你可能需要在类实例被释放之前手动去关闭该文件。
在类的定义中,每个类最多只能有一个析构器,而且析构器不带任何参数,如下所示:
在类的定义中,每个类最多只能有一个析构器,而且析构器不带任何参数和圆括号,如下所示
```swift
deinit {
@ -34,84 +14,83 @@ deinit {
}
```
析构器是在实例释放发生前被自动调用。你不能主动调用析构器。子类继承了父类的析构器,并且在子类析构器实现的最后,父类的析构器会被自动调用。即使子类没有提供自己的析构器,父类的析构器也同样会被调用。
析构器是在实例释放发生前被自动调用。你不能主动调用析构器。子类继承了父类的析构器,并且在子类析构器实现的最后,父类的析构器会被自动调用。即使子类没有提供自己的析构器,父类的析构器也同样会被调用。
因为直到实例的析构器被调用后,实例才会被释放,所以析构器可以访问实例的所有属性,并且可以根据那些属性可以修改它的行为(比如查找一个需要被关闭的文件)。
<a name="deinitializers_in_action"></a>
##析构器实践
## 析构器实践 {#deinitializers-in-action}
这是一个析构器实践的例子。这个例子描述了一个简单的游戏,这里定义了两种新类型,分别是`Bank``Player``Bank`类管理一种虚拟硬币,确保流通的硬币数量永远不可能超过 10,000。在游戏中有且只能有一个`Bank`存在,因此`Bank`用类来实现,并使用类型属性和类型方法来存储和管理其当前状态。
这是一个析构器实践的例子。这个例子描述了一个简单的游戏,这里定义了两种新类型,分别是 `Bank``Player``Bank` 类管理一种虚拟硬币,确保流通的硬币数量永远不可能超过 10,000。在游戏中有且只能有一个 `Bank` 存在,因此 `Bank` 用类来实现,并使用类型属性和类型方法来存储和管理其当前状态。
```swift
class Bank {
static var coinsInBank = 10_000
static func vendCoins(numberOfCoinsRequested: Int) -> Int {
static func distribute(coins numberOfCoinsRequested: Int) -> Int {
let numberOfCoinsToVend = min(numberOfCoinsRequested, coinsInBank)
coinsInBank -= numberOfCoinsToVend
return numberOfCoinsToVend
}
static func receiveCoins(coins: Int) {
static func receive(coins: Int) {
coinsInBank += coins
}
}
```
`Bank`使用`coinsInBank`属性来跟踪它当前拥有的硬币数量。`Bank`还提供了两个方法,`vendCoins(_:)``receiveCoins(_:)`,分别用来处理硬币的分发和收集。
`Bank` 使用 `coinsInBank` 属性来跟踪它当前拥有的硬币数量。`Bank` 还提供了两个方法,`distribute(coins:)``receive(coins:)`,分别用来处理硬币的分发和收集。
`vendCoins(_:)`方法在`Bank`对象分发硬币之前检查是否有足够的硬币。如果硬币不足,`Bank`对象会返回一个比请求时小的数字(如果`Bank`对象中没有硬币了就返回`0`)。`vendCoins`方法返回一个整型值,表示提供的硬币的实际数量。
`distribute(coins:)` 方法在 `Bank` 对象分发硬币之前检查是否有足够的硬币。如果硬币不足,`Bank` 对象会返回一个比请求时小的数字(如果 `Bank` 对象中没有硬币了就返回 `0`)。方法返回一个整型值,表示提供的硬币的实际数量。
`receiveCoins(_:)`方法只是将`Bank`对象接收到的硬币数目加回硬币存储中。
`receive(coins:)` 方法只是将 `Bank` 实例接收到的硬币数目加回硬币存储中。
`Player`类描述了游戏中的一个玩家。每一个玩家在任意时间都有一定数量的硬币存储在他们的钱包中。这通过玩家的`coinsInPurse`属性来表示:
`Player` 类描述了游戏中的一个玩家。每一个玩家在任意时间都有一定数量的硬币存储在他们的钱包中。这通过玩家的 `coinsInPurse` 属性来表示:
```swift
class Player {
var coinsInPurse: Int
init(coins: Int) {
coinsInPurse = Bank.vendCoins(coins)
coinsInPurse = Bank.distribute(coins: coins)
}
func winCoins(coins: Int) {
coinsInPurse += Bank.vendCoins(coins)
func win(coins: Int) {
coinsInPurse += Bank.distribute(coins: coins)
}
deinit {
Bank.receiveCoins(coinsInPurse)
Bank.receive(coins: coinsInPurse)
}
}
```
每个`Player`实例在初始化的过程中,都从`Bank`对象获取指定数量的硬币。如果没有足够的硬币可用,`Player`实例可能会收到比指定数量少的硬币.
每个 `Player` 实例在初始化的过程中,都从 `Bank` 对象获取指定数量的硬币。如果没有足够的硬币可用,`Player` 实例可能会收到比指定数量少的硬币
`Player`类定义了一个`winCoins(_:)`方法,该方法从`Bank`对象获取一定数量的硬币,并把它们添加到玩家的钱包。`Player`类还实现了一个析构器,这个析构器在`Player`实例释放前被调用。在这里,析构器的作用只是将玩家的所有硬币都返还给`Bank`对象:
`Player` 类定义了一个 `win(coins:)` 方法,该方法从 `Bank` 对象获取一定数量的硬币,并把它们添加到玩家的钱包。`Player` 类还实现了一个析构器,这个析构器在 `Player` 实例释放前被调用。在这里,析构器的作用只是将玩家的所有硬币都返还给 `Bank` 对象:
```swift
var playerOne: Player? = Player(coins: 100)
print("A new player has joined the game with \(playerOne!.coinsInPurse) coins")
// 打印 "A new player has joined the game with 100 coins"
// 打印A new player has joined the game with 100 coins
print("There are now \(Bank.coinsInBank) coins left in the bank")
// 打印 "There are now 9900 coins left in the bank"
// 打印There are now 9900 coins left in the bank
```
创建一个`Player`实例的时候,会向`Bank`对象请求 100 个硬币,如果有足够的硬币可用的话。这个`Player`实例存储在一个名为`playerOne`的可选类型的变量中。这里使用了一个可选类型的变量,因为玩家可以随时离开游戏,设置为可选使你可以追踪玩家当前是否在游戏中。
创建一个 `Player` 实例的时候,会向 `Bank` 对象申请得到 100 个硬币,前提是有足够的硬币可用。这个 `Player` 实例存储在一个名为 `playerOne` 的可选类型的变量中。这里使用了一个可选类型的变量,因为玩家可以随时离开游戏,设置为可选使你可以追踪玩家当前是否在游戏中。
因为`playerOne`是可选的,所以访问其`coinsInPurse`属性来打印钱包中的硬币数量时,使用感叹号(`!`解包:
因为 `playerOne` 是可选的,所以访问其 `coinsInPurse` 属性来打印钱包中的硬币数量和调用 `win(coins:)` 方法时,使用感叹号(`!`强制解包:
```swift
playerOne!.winCoins(2_000)
playerOne!.win(coins: 2_000)
print("PlayerOne won 2000 coins & now has \(playerOne!.coinsInPurse) coins")
// 输出 "PlayerOne won 2000 coins & now has 2100 coins"
// 打印“PlayerOne won 2000 coins & now has 2100 coins
print("The bank now only has \(Bank.coinsInBank) coins left")
// 输出 "The bank now only has 7900 coins left"
// 打印“The bank now only has 7900 coins left
```
这里,玩家已经赢得了 2,000 枚硬币,所以玩家的钱包中现在有 2,100 枚硬币,而`Bank`对象只剩余 7,900 枚硬币。
这里,玩家已经赢得了 2,000 枚硬币,所以玩家的钱包中现在有 2,100 枚硬币,而 `Bank` 对象只剩余 7,900 枚硬币。
```swift
playerOne = nil
print("PlayerOne has left the game")
// 打印 "PlayerOne has left the game"
// 打印PlayerOne has left the game
print("The bank now has \(Bank.coinsInBank) coins")
// 打印 "The bank now has 10000 coins"
// 打印The bank now has 10000 coins
```
玩家现在已经离开了游戏。这通过将可选类型的`playerOne`变量设置为`nil`来表示,意味着“没有`Player`实例”。当这一切发生时,`playerOne`变量对`Player`实例的引用被破坏了。没有其它属性或者变量引用`Player`实例,因此该实例会被释放,以便回收内存。在这之前,该实例的析构器被自动调用,玩家的硬币被返还给银行。
玩家现在已经离开了游戏。这通过将可选类型的 `playerOne` 变量设置为 `nil` 来表示,意味着“没有 `Player` 实例”。当这一切发生时,`playerOne` 变量对 `Player` 实例的引用被破坏了。没有其它属性或者变量引用 `Player` 实例,因此该实例会被释放,以便回收内存。在这之前,该实例的析构器被自动调用,玩家的硬币被返还给银行。

View File

@ -1,591 +0,0 @@
# 自动引用计数Automatic Reference Counting
-----------------
> 1.0
> 翻译:[TimothyYe](https://github.com/TimothyYe)
> 校对:[Hawstein](https://github.com/Hawstein)
> 2.0
> 翻译+校对:[Channe](https://github.com/Channe)
> 2.1
> 翻译:[Channe](https://github.com/Channe)
> 校对:[shanks](http://codebuild.me)[Realank](https://github.com/Realank) 2016-01-23
>
> 2.2
> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-14
本页包含内容:
- [自动引用计数的工作机制](#how_arc_works)
- [自动引用计数实践](#arc_in_action)
- [类实例之间的循环强引用](#strong_reference_cycles_between_class_instances)
- [解决实例之间的循环强引用](#resolving_strong_reference_cycles_between_class_instances)
- [闭包引起的循环强引用](#strong_reference_cycles_for_closures)
- [解决闭包引起的循环强引用](#resolving_strong_reference_cycles_for_closures)
Swift 使用自动引用计数ARC机制来跟踪和管理你的应用程序的内存。通常情况下Swift 内存管理机制会一直起作用你无须自己来考虑内存的管理。ARC 会在类的实例不再被使用时,自动释放其占用的内存。
然而在少数情况下为了能帮助你管理内存ARC 需要更多的,代码之间关系的信息。本章描述了这些情况,并且为你示范怎样才能使 ARC 来管理你的应用程序的所有内存。
> 注意
引用计数仅仅应用于类的实例。结构体和枚举类型是值类型,不是引用类型,也不是通过引用的方式存储和传递。
<a name="how_arc_works"></a>
## 自动引用计数的工作机制
当你每次创建一个类的新的实例的时候ARC 会分配一块内存来储存该实例信息。内存中会包含实例的类型信息,以及这个实例所有相关的存储型属性的值。
此外当实例不再被使用时ARC 释放实例所占用的内存,并让释放的内存能挪作他用。这确保了不再被使用的实例,不会一直占用内存空间。
然而,当 ARC 收回和释放了正在被使用中的实例,该实例的属性和方法将不能再被访问和调用。实际上,如果你试图访问这个实例,你的应用程序很可能会崩溃。
为了确保使用中的实例不会被销毁ARC 会跟踪和计算每一个实例正在被多少属性常量和变量所引用。哪怕实例的引用数为1ARC都不会销毁这个实例。
为了使上述成为可能,无论你将实例赋值给属性、常量或变量,它们都会创建此实例的强引用。之所以称之为“强”引用,是因为它会将实例牢牢地保持住,只要强引用还在,实例是不允许被销毁的。
<a name="arc_in_action"></a>
## 自动引用计数实践
下面的例子展示了自动引用计数的工作机制。例子以一个简单的`Person`类开始,并定义了一个叫`name`的常量属性:
```swift
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
```
`Person`类有一个构造函数,此构造函数为实例的`name`属性赋值,并打印一条消息以表明初始化过程生效。`Person`类也拥有一个析构函数,这个析构函数会在实例被销毁时打印一条消息。
接下来的代码片段定义了三个类型为`Person?`的变量,用来按照代码片段中的顺序,为新的`Person`实例建立多个引用。由于这些变量是被定义为可选类型(`Person?`,而不是`Person`),它们的值会被自动初始化为`nil`,目前还不会引用到`Person`类的实例。
```swift
var reference1: Person?
var reference2: Person?
var reference3: Person?
```
现在你可以创建`Person`类的新实例,并且将它赋值给三个变量中的一个:
```swift
reference1 = Person(name: "John Appleseed")
// prints "John Appleseed is being initialized”
```
应当注意到当你调用`Person`类的构造函数的时候,`“John Appleseed is being initialized”`会被打印出来。由此可以确定构造函数被执行。
由于`Person`类的新实例被赋值给了`reference1`变量,所以`reference1``Person`类的新实例之间建立了一个强引用。正是因为这一个强引用ARC 会保证`Person`实例被保持在内存中不被销毁。
如果你将同一个`Person`实例也赋值给其他两个变量,该实例又会多出两个强引用:
```swift
reference2 = reference1
reference3 = reference1
```
现在这一个`Person`实例已经有三个强引用了。
如果你通过给其中两个变量赋值`nil`的方式断开两个强引用(包括最先的那个强引用),只留下一个强引用,`Person`实例不会被销毁:
```swift
reference1 = nil
reference2 = nil
```
在你清楚地表明不再使用这个`Person`实例时即第三个也就是最后一个强引用被断开时ARC 会销毁它:
```swift
reference3 = nil
// 打印 “John Appleseed is being deinitialized”
```
<a name="strong_reference_cycles_between_class_instances"></a>
## 类实例之间的循环强引用
在上面的例子中ARC 会跟踪你所新创建的`Person`实例的引用数量,并且会在`Person`实例不再被需要时销毁它。
然而,我们可能会写出一个类实例的强引用数永远不能变成`0`的代码。如果两个类实例互相持有对方的强引用,因而每个实例都让对方一直存在,就是这种情况。这就是所谓的循环强引用。
你可以通过定义类之间的关系为弱引用或无主引用,以替代强引用,从而解决循环强引用的问题。具体的过程在[解决类实例之间的循环强引用](#resolving_strong_reference_cycles_between_class_instances)中有描述。不管怎样,在你学习怎样解决循环强引用之前,很有必要了解一下它是怎样产生的。
下面展示了一个不经意产生循环强引用的例子。例子定义了两个类:`Person``Apartment`,用来建模公寓和它其中的居民:
```swift
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
```
```swift
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
```
每一个`Person`实例有一个类型为`String`,名字为`name`的属性,并有一个可选的初始化为`nil``apartment`属性。`apartment`属性是可选的,因为一个人并不总是拥有公寓。
类似的,每个`Apartment`实例有一个叫`unit`,类型为`String`的属性,并有一个可选的初始化为`nil``tenant`属性。`tenant`属性是可选的,因为一栋公寓并不总是有居民。
这两个类都定义了析构函数,用以在类实例被析构的时候输出信息。这让你能够知晓`Person``Apartment`的实例是否像预期的那样被销毁。
接下来的代码片段定义了两个可选类型的变量`john``unit4A`,并分别被设定为下面的`Apartment``Person`的实例。这两个变量都被初始化为`nil`,这正是可选的优点:
```swift
var john: Person?
var unit4A: Apartment?
```
现在你可以创建特定的`Person``Apartment`实例并将赋值给`john``unit4A`变量:
```swift
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
```
在两个实例被创建和赋值后,下图表现了强引用的关系。变量`john`现在有一个指向`Person`实例的强引用,而变量`unit4A`有一个指向`Apartment`实例的强引用:
![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/referenceCycle01_2x.png)
现在你能够将这两个实例关联在一起,这样人就能有公寓住了,而公寓也有了房客。注意感叹号是用来展开和访问可选变量`john``unit4A`中的实例,这样实例的属性才能被赋值:
```swift
john!.apartment = unit4A
unit4A!.tenant = john
```
在将两个实例联系在一起之后,强引用的关系如图所示:
![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/referenceCycle02_2x.png)
不幸的是,这两个实例关联后会产生一个循环强引用。`Person`实例现在有了一个指向`Apartment`实例的强引用,而`Apartment`实例也有了一个指向`Person`实例的强引用。因此,当你断开`john``unit4A`变量所持有的强引用时,引用计数并不会降为`0`,实例也不会被 ARC 销毁:
```swift
john = nil
unit4A = nil
```
注意,当你把这两个变量设为`nil`时,没有任何一个析构函数被调用。循环强引用会一直阻止`Person``Apartment`类实例的销毁,这就在你的应用程序中造成了内存泄漏。
在你将`john``unit4A`赋值为`nil`后,强引用关系如下图:
![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/referenceCycle03_2x.png)
`Person``Apartment`实例之间的强引用关系保留了下来并且不会被断开。
<a name="resolving_strong_reference_cycles_between_class_instances"></a>
## 解决实例之间的循环强引用
Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题弱引用weak reference和无主引用unowned reference
弱引用和无主引用允许循环引用中的一个实例引用另外一个实例而不保持强引用。这样实例能够互相引用而不产生循环强引用。
对于生命周期中会变为`nil`的实例使用弱引用。相反地,对于初始化赋值后再也不会被赋值为`nil`的实例,使用无主引用。
<a name="weak_references"></a>
### 弱引用
弱引用不会对其引用的实例保持强引用,因而不会阻止 ARC 销毁被引用的实例。这个特性阻止了引用变为循环强引用。声明属性或者变量时,在前面加上`weak`关键字表明这是一个弱引用。
在实例的生命周期中,如果某些时候引用没有值,那么弱引用可以避免循环强引用。如果引用总是有值,则可以使用无主引用,在[无主引用](#unowned_references)中有描述。在上面`Apartment`的例子中,一个公寓的生命周期中,有时是没有“居民”的,因此适合使用弱引用来解决循环强引用。
> 注意
> 弱引用必须被声明为变量,表明其值能在运行时被修改。弱引用不能被声明为常量。
因为弱引用可以没有值,你必须将每一个弱引用声明为可选类型。在 Swift 中,推荐使用可选类型描述可能没有值的类型。
因为弱引用不会保持所引用的实例即使引用存在实例也有可能被销毁。因此ARC 会在引用的实例被销毁后自动将其赋值为`nil`。你可以像其他可选值一样,检查弱引用的值是否存在,你将永远不会访问已销毁的实例的引用。
下面的例子跟上面`Person``Apartment`的例子一致,但是有一个重要的区别。这一次,`Apartment``tenant`属性被声明为弱引用:
```swift
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
```
```swift
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
```
然后跟之前一样,建立两个变量(`john``unit4A`)之间的强引用,并关联两个实例:
```swift
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = john
```
现在,两个关联在一起的实例的引用关系如下图所示:
![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/weakReference01_2x.png)
`Person`实例依然保持对`Apartment`实例的强引用,但是`Apartment`实例只持有对`Person`实例的弱引用。这意味着当你断开`john`变量所保持的强引用时,再也没有指向`Person`实例的强引用了:
![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/weakReference02_2x.png)
由于再也没有指向`Person`实例的强引用,该实例会被销毁:
```swift
john = nil
// 打印 “John Appleseed is being deinitialized”
```
唯一剩下的指向`Apartment`实例的强引用来自于变量`unit4A`。如果你断开这个强引用,再也没有指向`Apartment`实例的强引用了:
![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/weakReference03_2x.png)
由于再也没有指向`Apartment`实例的强引用,该实例也会被销毁:
```swift
unit4A = nil
// 打印 “Apartment 4A is being deinitialized”
```
上面的两段代码展示了变量`john``unit4A`在被赋值为`nil`后,`Person`实例和`Apartment`实例的析构函数都打印出“销毁”的信息。这证明了引用循环被打破了。
> 注意
在使用垃圾收集的系统里,弱指针有时用来实现简单的缓冲机制,因为没有强引用的对象只会在内存压力触发垃圾收集时才被销毁。但是在 ARC 中,一旦值的最后一个强引用被移除,就会被立即销毁,这导致弱引用并不适合上面的用途。
<a name="unowned_references"></a>
### 无主引用
和弱引用类似无主引用不会牢牢保持住引用的实例。和弱引用不同的是无主引用是永远有值的。因此无主引用总是被定义为非可选类型non-optional type。你可以在声明属性或者变量时在前面加上关键字`unowned`表示这是一个无主引用。
由于无主引用是非可选类型,你不需要在使用它的时候将它展开。无主引用总是可以被直接访问。不过 ARC 无法在实例被销毁后将无主引用设为`nil`,因为非可选类型的变量不允许被赋值为`nil`
> 注意
> 如果你试图在实例被销毁后,访问该实例的无主引用,会触发运行时错误。使用无主引用,你必须确保引用始终指向一个未销毁的实例。
> 还需要注意的是如果你试图访问实例已经被销毁的无主引用Swift 确保程序会直接崩溃,而不会发生无法预期的行为。所以你应当避免这样的事情发生。
下面的例子定义了两个类,`Customer``CreditCard`,模拟了银行客户和客户的信用卡。这两个类中,每一个都将另外一个类的实例作为自身的属性。这种关系可能会造成循环强引用。
`Customer``CreditCard`之间的关系与前面弱引用例子中`Apartment``Person`的关系略微不同。在这个数据模型中,一个客户可能有或者没有信用卡,但是一张信用卡总是关联着一个客户。为了表示这种关系,`Customer`类有一个可选类型的`card`属性,但是`CreditCard`类有一个非可选类型的`customer`属性。
此外,只能通过将一个`number`值和`customer`实例传递给`CreditCard`构造函数的方式来创建`CreditCard`实例。这样可以确保当创建`CreditCard`实例时总是有一个`customer`实例与之关联。
由于信用卡总是关联着一个客户,因此将`customer`属性定义为无主引用,用以避免循环强引用:
```swift
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit { print("\(name) is being deinitialized") }
}
```
```swift
class CreditCard {
let number: UInt64
unowned let customer: Customer
init(number: UInt64, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("Card #\(number) is being deinitialized") }
}
```
> 注意
> `CreditCard`类的`number`属性被定义为`UInt64`类型而不是`Int`类型,以确保`number`属性的存储量在 32 位和 64 位系统上都能足够容纳 16 位的卡号。
下面的代码片段定义了一个叫`john`的可选类型`Customer`变量,用来保存某个特定客户的引用。由于是可选类型,所以变量被初始化为`nil`
```swift
var john: Customer?
```
现在你可以创建`Customer`类的实例,用它初始化`CreditCard`实例,并将新创建的`CreditCard`实例赋值为客户的`card`属性:
```swift
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
```
在你关联两个实例后,它们的引用关系如下图所示:
![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/unownedReference01_2x.png)
`Customer`实例持有对`CreditCard`实例的强引用,而`CreditCard`实例持有对`Customer`实例的无主引用。
由于`customer`的无主引用,当你断开`john`变量持有的强引用时,再也没有指向`Customer`实例的强引用了:
![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/unownedReference02_2x.png)
由于再也没有指向`Customer`实例的强引用,该实例被销毁了。其后,再也没有指向`CreditCard`实例的强引用,该实例也随之被销毁了:
```swift
john = nil
// 打印 “John Appleseed is being deinitialized”
// 打印 ”Card #1234567890123456 is being deinitialized”
```
最后的代码展示了在`john`变量被设为`nil``Customer`实例和`CreditCard`实例的构造函数都打印出了“销毁”的信息。
<a name="unowned_references_and_implicitly_unwrapped_optional_properties"></a>
###无主引用以及隐式解析可选属性
上面弱引用和无主引用的例子涵盖了两种常用的需要打破循环强引用的场景。
`Person``Apartment`的例子展示了两个属性的值都允许为`nil`,并会潜在的产生循环强引用。这种场景最适合用弱引用来解决。
`Customer``CreditCard`的例子展示了一个属性的值允许为`nil`,而另一个属性的值不允许为`nil`,这也可能会产生循环强引用。这种场景最适合通过无主引用来解决。
然而,存在着第三种场景,在这种场景中,两个属性都必须有值,并且初始化完成后永远不会为`nil`。在这种场景中,需要一个类使用无主属性,而另外一个类使用隐式解析可选属性。
这使两个属性在初始化完成后能被直接访问(不需要可选展开),同时避免了循环引用。这一节将为你展示如何建立这种关系。
下面的例子定义了两个类,`Country``City`,每个类将另外一个类的实例保存为属性。在这个模型中,每个国家必须有首都,每个城市必须属于一个国家。为了实现这种关系,`Country`类拥有一个`capitalCity`属性,而`City`类有一个`country`属性:
```swift
class Country {
let name: String
var capitalCity: City!
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
}
```
```swift
class City {
let name: String
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
}
```
为了建立两个类的依赖关系,`City`的构造函数接受一个`Country`实例作为参数,并且将实例保存到`country`属性。
`Country`的构造函数调用了`City`的构造函数。然而,只有`Country`的实例完全初始化后,`Country`的构造函数才能把`self`传给`City`的构造函数。(在[两段式构造过程](./14_Initialization.html#two_phase_initialization)中有具体描述)
为了满足这种需求,通过在类型结尾处加上感叹号(`City!`)的方式,将`Country``capitalCity`属性声明为隐式解析可选类型的属性。这意味着像其他可选类型一样,`capitalCity`属性的默认值为`nil`,但是不需要展开它的值就能访问它。(在[隐式解析可选类型](./01_The_Basics.html#implicityly_unwrapped_optionals)中有描述)
由于`capitalCity`默认值为`nil`,一旦`Country`的实例在构造函数中给`name`属性赋值后,整个初始化过程就完成了。这意味着一旦`name`属性被赋值后,`Country`的构造函数就能引用并传递隐式的`self``Country`的构造函数在赋值`capitalCity`时,就能将`self`作为参数传递给`City`的构造函数。
以上的意义在于你可以通过一条语句同时创建`Country``City`的实例,而不产生循环强引用,并且`capitalCity`的属性能被直接访问,而不需要通过感叹号来展开它的可选值:
```swift
var country = Country(name: "Canada", capitalName: "Ottawa")
print("\(country.name)'s capital city is called \(country.capitalCity.name)")
// 打印 “Canada's capital city is called Ottawa”
```
在上面的例子中,使用隐式解析可选值意味着满足了类的构造函数的两个构造阶段的要求。`capitalCity`属性在初始化完成后,能像非可选值一样使用和存取,同时还避免了循环强引用。
<a name="strong_reference_cycles_for_closures"></a>
##闭包引起的循环强引用
前面我们看到了循环强引用是在两个类实例属性互相保持对方的强引用时产生的,还知道了如何用弱引用和无主引用来打破这些循环强引用。
循环强引用还会发生在当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了这个类实例时。这个闭包体中可能访问了实例的某个属性,例如`self.someProperty`,或者闭包中调用了实例的某个方法,例如`self.someMethod()`。这两种情况都导致了闭包“捕获”`self`,从而产生了循环强引用。
循环强引用的产生,是因为闭包和类相似,都是引用类型。当你把一个闭包赋值给某个属性时,你是将这个闭包的引用赋值给了属性。实质上,这跟之前的问题是一样的——两个强引用让彼此一直有效。但是,和两个类实例不同,这次一个是类实例,另一个是闭包。
Swift 提供了一种优雅的方法来解决这个问题,称之为`闭包捕获列表`closure capture list。同样的在学习如何用闭包捕获列表打破循环强引用之前先来了解一下这里的循环强引用是如何产生的这对我们很有帮助。
下面的例子为你展示了当一个闭包引用了`self`后是如何产生一个循环强引用的。例子中定义了一个叫`HTMLElement`的类,用一种简单的模型表示 HTML 文档中的一个单独的元素:
```swift
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: Void -> String = {
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
}
```
`HTMLElement`类定义了一个`name`属性来表示这个元素的名称,例如代表段落的`“p”`,或者代表换行的`“br”``HTMLElement`还定义了一个可选属性`text`,用来设置 HTML 元素呈现的文本。
除了上面的两个属性,`HTMLElement`还定义了一个`lazy`属性`asHTML`。这个属性引用了一个将`name``text`组合成 HTML 字符串片段的闭包。该属性是`Void -> String`类型,或者可以理解为“一个没有参数,返回`String`的函数”。
默认情况下,闭包赋值给了`asHTML`属性,这个闭包返回一个代表 HTML 标签的字符串。如果`text`值存在,该标签就包含可选值`text`;如果`text`不存在,该标签就不包含文本。对于段落元素,根据`text``“some text”`还是`nil`,闭包会返回`"<p>some text</p>"`或者`"<p />"`
可以像实例方法那样去命名、使用`asHTML`属性。然而,由于`asHTML`是闭包而不是实例方法,如果你想改变特定 HTML 元素的处理方式的话,可以用自定义的闭包来取代默认值。
例如,可以将一个闭包赋值给`asHTML`属性,这个闭包能在`text`属性是`nil`时使用默认文本,这是为了避免返回一个空的 HTML 标签:
```swift
let heading = HTMLElement(name: "h1")
let defaultText = "some default text"
heading.asHTML = {
return "<\(heading.name)>\(heading.text ?? defaultText)</\(heading.name)>"
}
print(heading.asHTML())
// 打印 “<h1>some default text</h1>”
```
> 注意
`asHTML`声明为`lazy`属性,因为只有当元素确实需要被处理为 HTML 输出的字符串时,才需要使用`asHTML`。也就是说,在默认的闭包中可以使用`self`,因为只有当初始化完成以及`self`确实存在后,才能访问`lazy`属性。
`HTMLElement`类只提供了一个构造函数,通过`name``text`(如果有的话)参数来初始化一个新元素。该类也定义了一个析构函数,当`HTMLElement`实例被销毁时,打印一条消息。
下面的代码展示了如何用`HTMLElement`类创建实例并打印消息:
```swift
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// 打印 “<p>hello, world</p>”
```
> 注意
上面的`paragraph`变量定义为可选类型的`HTMLElement`,因此我们可以赋值`nil`给它来演示循环强引用。
不幸的是,上面写的`HTMLElement`类产生了类实例和作为`asHTML`默认值的闭包之间的循环强引用。循环强引用如下图所示:
![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/closureReferenceCycle01_2x.png)
实例的`asHTML`属性持有闭包的强引用。但是,闭包在其闭包体内使用了`self`(引用了`self.name``self.text`),因此闭包捕获了`self`,这意味着闭包又反过来持有了`HTMLElement`实例的强引用。这样两个对象就产生了循环强引用。(更多关于闭包捕获值的信息,请参考[值捕获](./07_Closures.html#capturing_values))。
> 注意
虽然闭包多次使用了`self`,它只捕获`HTMLElement`实例的一个强引用。
如果设置`paragraph`变量为`nil`,打破它持有的`HTMLElement`实例的强引用,`HTMLElement`实例和它的闭包都不会被销毁,也是因为循环强引用:
```swift
paragraph = nil
```
注意,`HTMLElement`的析构函数中的消息并没有被打印,证明了`HTMLElement`实例并没有被销毁。
<a name="resolving_strong_reference_cycles_for_closures"></a>
##解决闭包引起的循环强引用
在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。捕获列表定义了闭包体内捕获一个或者多个引用类型的规则。跟解决两个类实例间的循环强引用一样,声明每个捕获的引用为弱引用或无主引用,而不是强引用。应当根据代码关系来决定使用弱引用还是无主引用。
> 注意
Swift 有如下要求:只要在闭包内使用`self`的成员,就要用`self.someProperty`或者`self.someMethod()`(而不只是`someProperty``someMethod()`)。这提醒你可能会一不小心就捕获了`self`
<a name="defining_a_capture_list"></a>
###定义捕获列表
捕获列表中的每一项都由一对元素组成,一个元素是`weak``unowned`关键字,另一个元素是类实例的引用(例如`self`)或初始化过的变量(如`delegate = self.delegate!`)。这些项在方括号中用逗号分开。
如果闭包有参数列表和返回类型,把捕获列表放在它们前面:
```swift
lazy var someClosure: (Int, String) -> String = {
[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
// 这里是闭包的函数体
}
```
如果闭包没有指明参数列表或者返回类型,即它们会通过上下文推断,那么可以把捕获列表和关键字`in`放在闭包最开始的地方:
```swift
lazy var someClosure: Void -> String = {
[unowned self, weak delegate = self.delegate!] in
// 这里是闭包的函数体
}
```
<a name="weak_and_unowned_references"></a>
###弱引用和无主引用
在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为`无主引用`
相反的,在被捕获的引用可能会变为`nil`时,将闭包内的捕获定义为`弱引用`。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为`nil`。这使我们可以在闭包体内检查它们是否存在。
> 注意
如果被捕获的引用绝对不会变为`nil`,应该用无主引用,而不是弱引用。
前面的`HTMLElement`例子中,无主引用是正确的解决循环强引用的方法。这样编写`HTMLElement`类来避免循环强引用:
```swift
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: Void -> String = {
[unowned self] in
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
}
```
上面的`HTMLElement`实现和之前的实现一致,除了在`asHTML`闭包中多了一个捕获列表。这里,捕获列表是`[unowned self]`,表示“将`self`捕获为无主引用而不是强引用”。
和之前一样,我们可以创建并打印`HTMLElement`实例:
```swift
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// 打印 “<p>hello, world</p>”
```
使用捕获列表后引用关系如下图所示:
![](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/closureReferenceCycle02_2x.png)
这一次,闭包以无主引用的形式捕获`self`,并不会持有`HTMLElement`实例的强引用。如果将`paragraph`赋值为`nil``HTMLElement`实例将会被销毁,并能看到它的析构函数打印出的消息:
```swift
paragraph = nil
// 打印 “p is being deinitialized”
```
你可以查看[捕获列表](../chapter3/04_Expressions.html)章节,获取更多关于捕获列表的信息。

View File

@ -0,0 +1,374 @@
# 可选链式调用
*可选链式调用*是一种可以在当前值可能为 `nil` 的可选值上请求和调用属性、方法及下标的方法。如果可选值有值,那么调用就会成功;如果可选值是 `nil`,那么调用将返回 `nil`。多个调用可以连接在一起形成一个调用链,如果其中任何一个节点为 `nil`,整个调用链都会失败,即返回 `nil`
> 注意
>
> Swift 的可选链式调用和 Objective-C 中向 `nil` 发送消息有些相像,但是 Swift 的可选链式调用可以应用于任意类型,并且能检查调用是否成功。
## 使用可选链式调用代替强制展开 {#optional-chaining-as-an-alternative-to-forced-unwrapping}
通过在想调用的属性、方法,或下标的可选值后面放一个问号(`?`),可以定义一个可选链。这一点很像在可选值后面放一个叹号(`!`)来强制展开它的值。它们的主要区别在于当可选值为空时可选链式调用只会调用失败,然而强制展开将会触发运行时错误。
为了反映可选链式调用可以在空值(`nil`)上调用的事实,不论这个调用的属性、方法及下标返回的值是不是可选值,它的返回结果都是一个可选值。你可以利用这个返回值来判断你的可选链式调用是否调用成功,如果调用有返回值则说明调用成功,返回 `nil` 则说明调用失败。
这里需要特别指出,可选链式调用的返回结果与原本的返回结果具有相同的类型,但是被包装成了一个可选值。例如,使用可选链式调用访问属性,当可选链式调用成功时,如果属性原本的返回结果是 `Int` 类型,则会变为 `Int?` 类型。
下面几段代码将解释可选链式调用和强制展开的不同。
首先定义两个类 `Person``Residence`
```swift
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
```
`Residence` 有一个 `Int` 类型的属性 `numberOfRooms`,其默认值为 `1``Person` 具有一个可选的 `residence` 属性,其类型为 `Residence?`
假如你创建了一个新的 `Person` 实例,它的 `residence` 属性由于是可选类型而将被初始化为 `nil`,在下面的代码中,`john` 有一个值为 `nil``residence` 属性:
```swift
let john = Person()
```
如果使用叹号(`!`)强制展开获得这个 `john``residence` 属性中的 `numberOfRooms` 值,会触发运行时错误,因为这时 `residence` 没有可以展开的值:
```swift
let roomCount = john.residence!.numberOfRooms
// 这会引发运行时错误
```
`john.residence` 为非 `nil` 值的时候,上面的调用会成功,并且把 `roomCount` 设置为 `Int` 类型的房间数量。正如上面提到的,当 `residence``nil` 的时候,上面这段代码会触发运行时错误。
可选链式调用提供了另一种访问 `numberOfRooms` 的方式,使用问号(`?`)来替代原来的叹号(`!`
```swift
if let roomCount = john.residence?.numberOfRooms {
print("John's residence has \(roomCount) room(s).")
} else {
print("Unable to retrieve the number of rooms.")
}
// 打印“Unable to retrieve the number of rooms.”
```
`residence` 后面添加问号之后Swift 就会在 `residence` 不为 `nil` 的情况下访问 `numberOfRooms`
因为访问 `numberOfRooms` 有可能失败,可选链式调用会返回 `Int?` 类型,或称为“可选的 `Int`”。如上例所示,当 `residence``nil` 的时候,可选的 `Int` 将会为 `nil`,表明无法访问 `numberOfRooms`。访问成功时,可选的 `Int` 值会通过可选绑定展开,并赋值给非可选类型的 `roomCount` 常量。
要注意的是,即使 `numberOfRooms` 是非可选的 `Int` 时,这一点也成立。只要使用可选链式调用就意味着 `numberOfRooms` 会返回一个 `Int?` 而不是 `Int`
可以将一个 `Residence` 的实例赋给 `john.residence`,这样它就不再是 `nil` 了:
```swift
john.residence = Residence()
```
`john.residence` 现在包含一个实际的 `Residence` 实例,而不再是 `nil`。如果你试图使用先前的可选链式调用访问 `numberOfRooms`,它现在将返回值为 `1``Int?` 类型的值:
```swift
if let roomCount = john.residence?.numberOfRooms {
print("John's residence has \(roomCount) room(s).")
} else {
print("Unable to retrieve the number of rooms.")
}
// 打印“John's residence has 1 room(s).”
```
## 为可选链式调用定义模型类 {#defining-model-classes-for-optional-chaining}
通过使用可选链式调用可以调用多层属性、方法和下标。这样可以在复杂的模型中向下访问各种子属性,并且判断能否访问子属性的属性、方法和下标。
下面这段代码定义了四个模型类,这些例子包括多层可选链式调用。为了方便说明,在 `Person``Residence` 的基础上增加了 `Room` 类和 `Address` 类,以及相关的属性、方法以及下标。
`Person` 类的定义基本保持不变:
```swift
class Person {
var residence: Residence?
}
```
`Residence` 类比之前复杂些,增加了一个名为 `rooms` 的变量属性,该属性被初始化为 `[Room]` 类型的空数组:
```swift
class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
get {
return rooms[i]
}
set {
rooms[i] = newValue
}
}
func printNumberOfRooms() {
print("The number of rooms is \(numberOfRooms)")
}
var address: Address?
}
```
现在 `Residence` 有了一个存储 `Room` 实例的数组,`numberOfRooms` 属性被实现为计算型属性,而不是存储型属性。`numberOfRooms` 属性简单地返回 `rooms` 数组的 `count` 属性的值。
`Residence` 还提供了访问 `rooms` 数组的快捷方式,即提供可读写的下标来访问 `rooms` 数组中指定位置的元素。
此外,`Residence` 还提供了 `printNumberOfRooms` 方法,这个方法的作用是打印 `numberOfRooms` 的值。
最后,`Residence` 还定义了一个可选属性 `address`,其类型为 `Address?``Address` 类的定义在下面会说明。
`Room` 类是一个简单类,其实例被存储在 `rooms` 数组中。该类只包含一个属性 `name`,以及一个用于将该属性设置为适当的房间名的初始化函数:
```swift
class Room {
let name: String
init(name: String) { self.name = name }
}
```
最后一个类是 `Address`,这个类有三个 `String?` 类型的可选属性。`buildingName` 以及 `buildingNumber` 属性分别表示大厦的名称和号码,第三个属性 `street` 表示大厦所在街道的名称:
```swift
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if buildingName != nil {
return buildingName
} else if let buildingNumber = buildingNumber, let street = street {
return "\(buildingNumber) \(street)"
} else {
return nil
}
}
}
```
`Address` 类提供了 `buildingIdentifier()` 方法,返回值为 `String?`。 如果 `buildingName` 有值则返回 `buildingName`。或者,如果 `buildingNumber``street` 均有值,则返回两者拼接得到的字符串。否则,返回 `nil`
## 通过可选链式调用访问属性 {#accessing-properties-through-optional-chaining}
正如 [使用可选链式调用代替强制展开](#optional_chaining_as_an_alternative_to_forced_unwrapping) 中所述,可以通过可选链式调用在一个可选值上访问它的属性,并判断访问是否成功。
使用前面定义过的类,创建一个 `Person` 实例,然后像之前一样,尝试访问 `numberOfRooms` 属性:
```swift
let john = Person()
if let roomCount = john.residence?.numberOfRooms {
print("John's residence has \(roomCount) room(s).")
} else {
print("Unable to retrieve the number of rooms.")
}
// 打印“Unable to retrieve the number of rooms.”
```
因为 `john.residence``nil`,所以这个可选链式调用依旧会像先前一样失败。
还可以通过可选链式调用来设置属性值:
```swift
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
john.residence?.address = someAddress
```
在这个例子中,通过 `john.residence` 来设定 `address` 属性也会失败,因为 `john.residence` 当前为 `nil`
上面代码中的赋值过程是可选链式调用的一部分,这意味着可选链式调用失败时,等号右侧的代码不会被执行。对于上面的代码来说,很难验证这一点,因为像这样赋值一个常量没有任何副作用。下面的代码完成了同样的事情,但是它使用一个函数来创建 `Address` 实例然后将该实例返回用于赋值。该函数会在返回前打印“Function was called”这使你能验证等号右侧的代码是否被执行。
```swift
func createAddress() -> Address {
print("Function was called.")
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
return someAddress
}
john.residence?.address = createAddress()
```
没有任何打印消息,可以看出 `createAddress()` 函数并未被执行。
## 通过可选链式调用来调用方法 {#calling-methods-through-optional-chaining}
可以通过可选链式调用来调用方法,并判断是否调用成功,即使这个方法没有返回值。
`Residence` 类中的 `printNumberOfRooms()` 方法打印当前的 `numberOfRooms` 值,如下所示:
```swift
func printNumberOfRooms() {
print("The number of rooms is \(numberOfRooms)")
}
```
这个方法没有返回值。然而,没有返回值的方法具有隐式的返回类型 `Void`,如 [无返回值函数](./06_Functions.md#functions_without_return_values) 中所述。这意味着没有返回值的方法也会返回 `()`,或者说空的元组。
如果在可选值上通过可选链式调用来调用这个方法,该方法的返回类型会是 `Void?`,而不是 `Void`,因为通过可选链式调用得到的返回值都是可选的。这样我们就可以使用 `if` 语句来判断能否成功调用 `printNumberOfRooms()` 方法,即使方法本身没有定义返回值。通过判断返回值是否为 `nil` 可以判断调用是否成功:
```swift
if john.residence?.printNumberOfRooms() != nil {
print("It was possible to print the number of rooms.")
} else {
print("It was not possible to print the number of rooms.")
}
// 打印“It was not possible to print the number of rooms.”
```
同样的,可以据此判断通过可选链式调用为属性赋值是否成功。在上面的 [通过可选链式调用访问属性](#accessing_properties_through_optional_chaining) 的例子中,我们尝试给 `john.residence` 中的 `address` 属性赋值,即使 `residence``nil`。通过可选链式调用给属性赋值会返回 `Void?`,通过判断返回值是否为 `nil` 就可以知道赋值是否成功:
```swift
if (john.residence?.address = someAddress) != nil {
print("It was possible to set the address.")
} else {
print("It was not possible to set the address.")
}
// 打印“It was not possible to set the address.”
```
## 通过可选链式调用访问下标 {#accessing-subscripts-through-optional-chaining}
通过可选链式调用,我们可以在一个可选值上访问下标,并且判断下标调用是否成功。
> 注意
>
> 通过可选链式调用访问可选值的下标时,应该将问号放在下标方括号的前面而不是后面。可选链式调用的问号一般直接跟在可选表达式的后面。
下面这个例子用下标访问 `john.residence` 属性存储的 `Residence` 实例的 `rooms` 数组中的第一个房间的名称,因为 `john.residence``nil`,所以下标调用失败了:
```swift
if let firstRoomName = john.residence?[0].name {
print("The first room name is \(firstRoomName).")
} else {
print("Unable to retrieve the first room name.")
}
// 打印“Unable to retrieve the first room name.”
```
在这个例子中,问号直接放在 `john.residence` 的后面,并且在方括号的前面,因为 `john.residence` 是可选值。
类似的,可以通过下标,用可选链式调用来赋值:
```swift
john.residence?[0] = Room(name: "Bathroom")
```
这次赋值同样会失败,因为 `residence` 目前是 `nil`
如果你创建一个 `Residence` 实例,并为其 `rooms` 数组添加一些 `Room` 实例,然后将 `Residence` 实例赋值给 `john.residence`,那就可以通过可选链和下标来访问数组中的元素:
```swift
let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "Living Room"))
johnsHouse.rooms.append(Room(name: "Kitchen"))
john.residence = johnsHouse
if let firstRoomName = john.residence?[0].name {
print("The first room name is \(firstRoomName).")
} else {
print("Unable to retrieve the first room name.")
}
// 打印“The first room name is Living Room.”
```
### 访问可选类型的下标 {#accessing-subscripts-of-optional-type}
如果下标返回可选类型值,比如 Swift 中 `Dictionary` 类型的键的下标,可以在下标的结尾括号后面放一个问号来在其可选返回值上进行可选链式调用:
```swift
var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
testScores["Dave"]?[0] = 91
testScores["Bev"]?[0] += 1
testScores["Brian"]?[0] = 72
// "Dave" 数组现在是 [91, 82, 84]"Bev" 数组现在是 [80, 94, 81]
```
上面的例子中定义了一个 `testScores` 数组,包含了两个键值对,分别把 `String` 类型的键映射到一个 `Int` 值的数组。这个例子用可选链式调用把 `"Dave"` 数组中第一个元素设为 `91`,把 `"Bev"` 数组的第一个元素 `+1`,然后尝试把 `"Brian"` 数组中的第一个元素设为 `72`。前两个调用成功,因为 `testScores` 字典中包含 `"Dave"``"Bev"` 这两个键。但是 `testScores` 字典中没有 `"Brian"` 这个键,所以第三个调用失败。
## 连接多层可选链式调用 {#linking-multiple-levels-of-chaining}
可以通过连接多个可选链式调用在更深的模型层级中访问属性、方法以及下标。然而,多层可选链式调用不会增加返回值的可选层级。
也就是说:
+ 如果你访问的值不是可选的,可选链式调用将会返回可选值。
+ 如果你访问的值就是可选的,可选链式调用不会让可选返回值变得“更可选”。
因此:
+ 通过可选链式调用访问一个 `Int` 值,将会返回 `Int?`,无论使用了多少层可选链式调用。
+ 类似的,通过可选链式调用访问 `Int?` 值,依旧会返回 `Int?` 值,并不会返回 `Int??`
下面的例子尝试访问 `john` 中的 `residence` 属性中的 `address` 属性中的 `street` 属性。这里使用了两层可选链式调用,`residence` 以及 `address` 都是可选值:
```swift
if let johnsStreet = john.residence?.address?.street {
print("John's street name is \(johnsStreet).")
} else {
print("Unable to retrieve the address.")
}
// 打印“Unable to retrieve the address.”
```
`john.residence` 现在包含一个有效的 `Residence` 实例。然而,`john.residence.address` 的值当前为 `nil`。因此,调用 `john.residence?.address?.street` 会失败。
需要注意的是,上面的例子中,`street` 的属性为 `String?``john.residence?.address?.street` 的返回值也依然是 `String?`,即使已经使用了两层可选链式调用。
如果为 `john.residence.address` 赋值一个 `Address` 实例,并且为 `address` 中的 `street` 属性设置一个有效值,我们就能过通过可选链式调用来访问 `street` 属性:
```swift
let johnsAddress = Address()
johnsAddress.buildingName = "The Larches"
johnsAddress.street = "Laurel Street"
john.residence?.address = johnsAddress
if let johnsStreet = john.residence?.address?.street {
print("John's street name is \(johnsStreet).")
} else {
print("Unable to retrieve the address.")
}
// 打印“John's street name is Laurel Street.”
```
在上面的例子中,因为 `john.residence` 包含一个有效的 `Address` 实例,所以对 `john.residence``address` 属性赋值将会成功。
## 在方法的可选返回值上进行可选链式调用 {#chaining-on-methods-with-optional-return-values}
上面的例子展示了如何在一个可选值上通过可选链式调用来获取它的属性值。我们还可以在一个可选值上通过可选链式调用来调用方法,并且可以根据需要继续在方法的可选返回值上进行可选链式调用。
在下面的例子中,通过可选链式调用来调用 `Address``buildingIdentifier()` 方法。这个方法返回 `String?` 类型的值。如上所述,通过可选链式调用来调用该方法,最终的返回值依旧会是 `String?` 类型:
```swift
if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
print("John's building identifier is \(buildingIdentifier).")
}
// 打印“John's building identifier is The Larches.”
```
如果要在该方法的返回值上进行可选链式调用,在方法的圆括号后面加上问号即可:
```swift
if let beginsWithThe =
john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
if beginsWithThe {
print("John's building identifier begins with \"The\".")
} else {
print("John's building identifier does not begin with \"The\".")
}
}
// 打印“John's building identifier begins with "The".”
```
> 注意
>
> 在上面的例子中,在方法的圆括号后面加上问号是因为你要在 `buildingIdentifier()` 方法的可选返回值上进行可选链式调用,而不是 `buildingIdentifier()` 方法本身。

View File

@ -0,0 +1,263 @@
# 错误处理
*错误处理Error handling* 是响应错误以及从错误中恢复的过程。Swift 在运行时提供了抛出、捕获、传递和操作可恢复错误recoverable errors的一等支持first-class support
某些操作无法保证总是执行完所有代码或生成有用的结果。可选类型用来表示值缺失,但是当某个操作失败时,理解造成失败的原因有助于你的代码作出相应的应对。
举个例子,假如有个从磁盘上的某个文件读取数据并进行处理的任务,该任务会有多种可能失败的情况,包括指定路径下文件并不存在,文件不具有可读权限,或者文件编码格式不兼容。区分这些不同的失败情况可以让程序处理并解决某些错误,然后把它解决不了的错误报告给用户。
> 注意
>
> Swift 中的错误处理涉及到错误处理模式,这会用到 Cocoa 和 Objective-C 中的 `NSError`。更多详情参见 [用 Swift 解决 Cocoa 错误](https://developer.apple.com/documentation/swift/cocoa_design_patterns/handling_cocoa_errors_in_swift)。
## 表示与抛出错误 {#representing-and-throwing-errors}
在 Swift 中,错误用遵循 `Error` 协议的类型的值来表示。这个空协议表明该类型可以用于错误处理。
Swift 的枚举类型尤为适合构建一组相关的错误状态,枚举的关联值还可以提供错误状态的额外信息。例如,在游戏中操作自动贩卖机时,你可以这样表示可能会出现的错误状态:
```swift
enum VendingMachineError: Error {
case invalidSelection //选择无效
case insufficientFunds(coinsNeeded: Int) //金额不足
case outOfStock //缺货
}
```
抛出一个错误可以让你表明有意外情况发生,导致正常的执行流程无法继续执行。抛出错误使用 `throw` 语句。例如,下面的代码抛出一个错误,提示贩卖机还需要 `5` 个硬币:
```swift
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
```
## 处理错误 {#handling-errors}
某个错误被抛出时,附近的某部分代码必须负责处理这个错误,例如纠正这个问题、尝试另外一种方式、或是向用户报告错误。
Swift 中有 `4` 种处理错误的方式。你可以把函数抛出的错误传递给调用此函数的代码、用 `do-catch` 语句处理错误、将错误作为可选类型处理、或者断言此错误根本不会发生。每种方式在下面的小节中都有描述。
当一个函数抛出一个错误时,你的程序流程会发生改变,所以重要的是你能迅速识别代码中会抛出错误的地方。为了标识出这些地方,在调用一个能抛出错误的函数、方法或者构造器之前,加上 `try` 关键字,或者 `try?``try!` 这种变体。这些关键字在下面的小节中有具体讲解。
> 注意
>
> Swift 中的错误处理和其他语言中用 `try``catch` 和 `throw` 进行异常处理很像。和其他语言中(包括 Objective-C 的异常处理不同的是Swift 中的错误处理并不涉及解除调用栈,这是一个计算代价高昂的过程。就此而言,`throw` 语句的性能特性是可以和 `return` 语句相媲美的。
### 用 throwing 函数传递错误 {#propagating-errors-using-throwing-functions}
为了表示一个函数、方法或构造器可以抛出错误,在函数声明的参数之后加上 `throws` 关键字。一个标有 `throws` 关键字的函数被称作 *throwing 函数*。如果这个函数指明了返回值类型,`throws` 关键词需要写在返回箭头(`->`)的前面。
```swift
func canThrowErrors() throws -> String
func cannotThrowErrors() -> String
```
一个 throwing 函数可以在其内部抛出错误,并将错误传递到函数被调用时的作用域。
> 注意
>
> 只有 throwing 函数可以传递错误。任何在某个非 throwing 函数内部抛出的错误只能在函数内部处理。
下面的例子中,`VendingMachine` 类有一个 `vend(itemNamed:)` 方法,如果请求的物品不存在、缺货或者投入金额小于物品价格,该方法就会抛出一个相应的 `VendingMachineError`
```swift
struct Item {
var price: Int
var count: Int
}
class VendingMachine {
var inventory = [
"Candy Bar": Item(price: 12, count: 7),
"Chips": Item(price: 10, count: 4),
"Pretzels": Item(price: 7, count: 11)
]
var coinsDeposited = 0
func vend(itemNamed name: String) throws {
guard let item = inventory[name] else {
throw VendingMachineError.invalidSelection
}
guard item.count > 0 else {
throw VendingMachineError.outOfStock
}
guard item.price <= coinsDeposited else {
throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited)
}
coinsDeposited -= item.price
var newItem = item
newItem.count -= 1
inventory[name] = newItem
print("Dispensing \(name)")
}
}
```
`vend(itemNamed:)` 方法的实现中使用了 `guard` 语句来确保在购买某个物品所需的条件中有任一条件不满足时,能提前退出方法并抛出相应的错误。由于 `throw` 语句会立即退出方法,所以物品只有在所有条件都满足时才会被售出。
因为 `vend(itemNamed:)` 方法会传递出它抛出的任何错误,在你的代码中调用此方法的地方,必须要么直接处理这些错误——使用 `do-catch` 语句,`try?``try!`;要么继续将这些错误传递下去。例如下面例子中,`buyFavoriteSnack(person:vendingMachine:)` 同样是一个 throwing 函数,任何由 `vend(itemNamed:)` 方法抛出的错误会一直被传递到 `buyFavoriteSnack(person:vendingMachine:)` 函数被调用的地方。
```swift
let favoriteSnacks = [
"Alice": "Chips",
"Bob": "Licorice",
"Eve": "Pretzels",
]
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
let snackName = favoriteSnacks[person] ?? "Candy Bar"
try vendingMachine.vend(itemNamed: snackName)
}
```
上例中,`buyFavoriteSnack(person:vendingMachine:)` 函数会查找某人最喜欢的零食,并通过调用 `vend(itemNamed:)` 方法来尝试为他们购买。因为 `vend(itemNamed:)` 方法能抛出错误,所以在调用的它时候在它前面加了 `try` 关键字。
`throwing` 构造器能像 `throwing` 函数一样传递错误。例如下面代码中的 `PurchasedSnack` 构造器在构造过程中调用了 throwing 函数,并且通过传递到它的调用者来处理这些错误。
```swift
struct PurchasedSnack {
let name: String
init(name: String, vendingMachine: VendingMachine) throws {
try vendingMachine.vend(itemNamed: name)
self.name = name
}
}
```
### 用 Do-Catch 处理错误 {#handling-errors-using-do-Catch}
你可以使用一个 `do-catch` 语句运行一段闭包代码来处理错误。如果在 `do` 子句中的代码抛出了一个错误,这个错误会与 `catch` 子句做匹配,从而决定哪条子句能处理它。
下面是 `do-catch` 语句的一般形式:
```swift
do {
try expression
statements
} catch pattern 1 {
statements
} catch pattern 2 where condition {
statements
} catch {
statements
}
```
`catch` 后面写一个匹配模式来表明这个子句能处理什么样的错误。如果一条 `catch` 子句没有指定匹配模式,那么这条子句可以匹配任何错误,并且把错误绑定到一个名字为 `error` 的局部常量。关于模式匹配的更多信息请参考 [模式](../chapter3/07_Patterns.html)。
举例来说,下面的代码处理了 `VendingMachineError` 枚举类型的全部三种情况:
```swift
var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
do {
try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
print("Success! Yum.")
} catch VendingMachineError.invalidSelection {
print("Invalid Selection.")
} catch VendingMachineError.outOfStock {
print("Out of Stock.")
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
} catch {
print("Unexpected error: \(error).")
}
// 打印“Insufficient funds. Please insert an additional 2 coins.”
```
上面的例子中,`buyFavoriteSnack(person:vendingMachine:)` 函数在一个 `try` 表达式中被调用,是因为它能抛出错误。如果错误被抛出,相应的执行会马上转移到 `catch` 子句中,并判断这个错误是否要被继续传递下去。如果错误没有被匹配,它会被最后一个 `catch` 语句捕获,并赋值给一个 `error` 常量。如果没有错误被抛出,`do` 子句中余下的语句就会被执行。
`catch` 子句不必将 `do` 子句中的代码所抛出的每一个可能的错误都作处理。如果所有 `catch` 子句都未处理错误,错误就会传递到周围的作用域。然而,错误还是必须要被某个周围的作用域处理的。在不会抛出错误的函数中,必须用 `do-catch` 语句处理错误。而能够抛出错误的函数既可以使用 `do-catch` 语句处理,也可以让调用方来处理错误。如果错误传递到了顶层作用域却依然没有被处理,你会得到一个运行时错误。
以下面的代码为例,不是 `VendingMachineError` 中申明的错误会在调用函数的地方被捕获:
```swift
func nourish(with item: String) throws {
do {
try vendingMachine.vend(itemNamed: item)
} catch is VendingMachineError {
print("Invalid selection, out of stock, or not enough money.")
}
}
do {
try nourish(with: "Beet-Flavored Chips")
} catch {
print("Unexpected non-vending-machine-related error: \(error)")
}
// 打印“Invalid selection, out of stock, or not enough money.”
```
如果 `vend(itemNamed:)` 抛出的是一个 `VendingMachineError` 类型的错误,`nourish(with:)` 会打印一条消息,否则 `nourish(with:)` 会将错误抛给它的调用方。这个错误之后会被通用的 `catch` 语句捕获。
### 将错误转换成可选值 {#converting_errors_to_optional_values}
可以使用 `try?` 通过将错误转换成一个可选值来处理错误。如果是在计算 `try?` 表达式时抛出错误,该表达式的结果就为 `nil`。例如,在下面的代码中,`x``y` 有着相同的数值和等价的含义:
```swift
func someThrowingFunction() throws -> Int {
// ...
}
let x = try? someThrowingFunction()
let y: Int?
do {
y = try someThrowingFunction()
} catch {
y = nil
}
```
如果 `someThrowingFunction()` 抛出一个错误,`x``y` 的值是 `nil`。否则 `x``y` 的值就是该函数的返回值。注意,无论 `someThrowingFunction()` 的返回值类型是什么类型,`x``y` 都是这个类型的可选类型。例子中此函数返回一个整型,所以 `x``y` 是可选整型。
如果你想对所有的错误都采用同样的方式来处理,用 `try?` 就可以让你写出简洁的错误处理代码。例如,下面的代码用几种方式来获取数据,如果所有方式都失败了则返回 `nil`
```swift
func fetchData() -> Data? {
if let data = try? fetchDataFromDisk() { return data }
if let data = try? fetchDataFromServer() { return data }
return nil
}
```
### 禁用错误传递 {#disabling_error_propagation}
有时你知道某个 `throwing` 函数实际上在运行时是不会抛出错误的,在这种情况下,你可以在表达式前面写 `try!` 来禁用错误传递,这会把调用包装在一个不会有错误抛出的运行时断言中。如果真的抛出了错误,你会得到一个运行时错误。
例如,下面的代码使用了 `loadImage(atPath:)` 函数,该函数从给定的路径加载图片资源,如果图片无法载入则抛出一个错误。在这种情况下,因为图片是和应用绑定的,运行时不会有错误抛出,所以适合禁用错误传递。
```swift
let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")
```
## 指定清理操作 {#specifying-cleanup-actions}
你可以使用 `defer` 语句在即将离开当前代码块时执行一系列语句。该语句让你能执行一些必要的清理工作,不管是以何种方式离开当前代码块的——无论是由于抛出错误而离开,或是由于诸如 `return``break` 的语句。例如,你可以用 `defer` 语句来确保文件描述符得以关闭,以及手动分配的内存得以释放。
`defer` 语句将代码的执行延迟到当前的作用域退出之前。该语句由 `defer` 关键字和要被延迟执行的语句组成。延迟执行的语句不能包含任何控制转移语句,例如 `break``return` 语句,或是抛出一个错误。延迟执行的操作会按照它们声明的顺序从后往前执行——也就是说,第一条 `defer` 语句中的代码最后才执行,第二条 `defer` 语句中的代码倒数第二个执行,以此类推。最后一条语句会第一个执行。
```swift
func processFile(filename: String) throws {
if exists(filename) {
let file = open(filename)
defer {
close(file)
}
while let line = try file.readline() {
// 处理文件。
}
// close(file) 会在这里被调用,即作用域的最后。
}
}
```
上面的代码使用一条 `defer` 语句来确保 `open(_:)` 函数有一个相应的对 `close(_:)` 函数的调用。
> 注意
>
> 即使没有涉及到错误处理的代码,你也可以使用 `defer` 语句。

View File

@ -1,404 +0,0 @@
# 可选链式调用Optional Chaining
-----------------
> 1.0
> 翻译:[Jasonbroker](https://github.com/Jasonbroker)
> 校对:[numbbbbb](https://github.com/numbbbbb), [stanzhai](https://github.com/stanzhai)
> 2.0
> 翻译+校对:[lyojo](https://github.com/lyojo)
> 2.1
> 校对:[shanks](http://codebuild.me)2015-10-31
>
> 2.2
> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-15
本页包含内容:
- [使用可选链式调用代替强制展开](#optional_chaining_as_an_alternative_to_forced_unwrapping)
- [为可选链式调用定义模型类](#defining_model_classes_for_optional_chaining)
- [通过可选链式调用访问属性](#accessing_properties_through_optional_chaining)
- [通过可选链式调用调用方法](#calling_methods_through_optional_chaining)
- [通过可选链式调用访问下标](#accessing_subscripts_through_optional_chaining)
- [连接多层可选链式调用](#linking_multiple_levels_of_chaining)
- [在方法的可选返回值上进行可选链式调用](#chaining_on_methods_with_optional_return_values)
可选链式调用Optional Chaining是一种可以在当前值可能为`nil`的可选值上请求和调用属性、方法及下标的方法。如果可选值有值,那么调用就会成功;如果可选值是`nil`,那么调用将返回`nil`。多个调用可以连接在一起形成一个调用链,如果其中任何一个节点为`nil`,整个调用链都会失败,即返回`nil`
> 注意
Swift 的可选链式调用和 Objective-C 中向`nil`发送消息有些相像,但是 Swift 的可选链式调用可以应用于任意类型,并且能检查调用是否成功。
<a name="optional_chaining_as_an_alternative_to_forced_unwrapping"></a>
## 使用可选链式调用代替强制展开
通过在想调用的属性、方法、或下标的可选值optional value后面放一个问号`?`),可以定义一个可选链。这一点很像在可选值后面放一个叹号(`!`)来强制展开它的值。它们的主要区别在于当可选值为空时可选链式调用只会调用失败,然而强制展开将会触发运行时错误。
为了反映可选链式调用可以在空值(`nil`)上调用的事实,不论这个调用的属性、方法及下标返回的值是不是可选值,它的返回结果都是一个可选值。你可以利用这个返回值来判断你的可选链式调用是否调用成功,如果调用有返回值则说明调用成功,返回`nil`则说明调用失败。
特别地,可选链式调用的返回结果与原本的返回结果具有相同的类型,但是被包装成了一个可选值。例如,使用可选链式调用访问属性,当可选链式调用成功时,如果属性原本的返回结果是`Int`类型,则会变为`Int?`类型。
下面几段代码将解释可选链式调用和强制展开的不同。
首先定义两个类`Person``Residence`
```swift
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
```
`Residence`有一个`Int`类型的属性`numberOfRooms`,其默认值为`1``Person`具有一个可选的`residence`属性,其类型为`Residence?`
假如你创建了一个新的`Person`实例,它的`residence`属性由于是是可选型而将初始化为`nil`,在下面的代码中,`john`有一个值为`nil``residence`属性:
```swift
let john = Person()
```
如果使用叹号(`!`)强制展开获得这个`john``residence`属性中的`numberOfRooms`值,会触发运行时错误,因为这时`residence`没有可以展开的值:
```swift
let roomCount = john.residence!.numberOfRooms
// 这会引发运行时错误
```
`john.residence`为非`nil`值的时候,上面的调用会成功,并且把`roomCount`设置为`Int`类型的房间数量。正如上面提到的,当`residence``nil`的时候上面这段代码会触发运行时错误。
可选链式调用提供了另一种访问`numberOfRooms`的方式,使用问号(`?`)来替代原来的叹号(`!`
```swift
if let roomCount = john.residence?.numberOfRooms {
print("John's residence has \(roomCount) room(s).")
} else {
print("Unable to retrieve the number of rooms.")
}
// 打印 “Unable to retrieve the number of rooms.”
```
`residence`后面添加问号之后Swift 就会在`residence`不为`nil`的情况下访问`numberOfRooms`
因为访问`numberOfRooms`有可能失败,可选链式调用会返回`Int?`类型,或称为“可选的 `Int`”。如上例所示,当`residence``nil`的时候,可选的`Int`将会为`nil`,表明无法访问`numberOfRooms`。访问成功时,可选的`Int`值会通过可选绑定展开,并赋值给非可选类型的`roomCount`常量。
要注意的是,即使`numberOfRooms`是非可选的`Int`时,这一点也成立。只要使用可选链式调用就意味着`numberOfRooms`会返回一个`Int?`而不是`Int`
可以将一个`Residence`的实例赋给`john.residence`,这样它就不再是`nil`了:
```swift
john.residence = Residence()
```
`john.residence`现在包含一个实际的`Residence`实例,而不再是`nil`。如果你试图使用先前的可选链式调用访问`numberOfRooms`,它现在将返回值为`1``Int?`类型的值:
```swift
if let roomCount = john.residence?.numberOfRooms {
print("John's residence has \(roomCount) room(s).")
} else {
print("Unable to retrieve the number of rooms.")
}
// 打印 “John's residence has 1 room(s).”
```
<a name="defining_model_classes_for_optional_chaining"></a>
## 为可选链式调用定义模型类
通过使用可选链式调用可以调用多层属性、方法和下标。这样可以在复杂的模型中向下访问各种子属性,并且判断能否访问子属性的属性、方法或下标。
下面这段代码定义了四个模型类,这些例子包括多层可选链式调用。为了方便说明,在`Person``Residence`的基础上增加了`Room`类和`Address`类,以及相关的属性、方法以及下标。
`Person`类的定义基本保持不变:
```swift
class Person {
var residence: Residence?
}
```
`Residence`类比之前复杂些,增加了一个名为`rooms`的变量属性,该属性被初始化为`[Room]`类型的空数组:
```swift
class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
get {
return rooms[i]
}
set {
rooms[i] = newValue
}
}
func printNumberOfRooms() {
print("The number of rooms is \(numberOfRooms)")
}
var address: Address?
}
```
现在`Residence`有了一个存储`Room`实例的数组,`numberOfRooms`属性被实现为计算型属性,而不是存储型属性。`numberOfRooms`属性简单地返回`rooms`数组的`count`属性的值。
`Residence`还提供了访问`rooms`数组的快捷方式,即提供可读写的下标来访问`rooms`数组中指定位置的元素。
此外,`Residence`还提供了`printNumberOfRooms()`方法,这个方法的作用是打印`numberOfRooms`的值。
最后,`Residence`还定义了一个可选属性`address`,其类型为`Address?``Address`类的定义在下面会说明。
`Room`类是一个简单类,其实例被存储在`rooms`数组中。该类只包含一个属性`name`,以及一个用于将该属性设置为适当的房间名的初始化函数:
```swift
class Room {
let name: String
init(name: String) { self.name = name }
}
```
最后一个类是`Address`,这个类有三个`String?`类型的可选属性。`buildingName`以及`buildingNumber`属性分别表示某个大厦的名称和号码,第三个属性`street`表示大厦所在街道的名称:
```swift
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if buildingName != nil {
return buildingName
} else if buildingNumber != nil && street != nil {
return "\(buildingNumber) \(street)"
} else {
return nil
}
}
}
```
`Address`类提供了`buildingIdentifier()`方法,返回值为`String?`。 如果`buildingName`有值则返回`buildingName`。或者,如果`buildingNumber``street`均有值则返回`buildingNumber`。否则,返回`nil`
<a name="accessing_properties_through_optional_chaining"></a>
## 通过可选链式调用访问属性
正如[使用可选链式调用代替强制展开](#optional_chaining_as_an_alternative_to_forced_unwrapping)中所述,可以通过可选链式调用在一个可选值上访问它的属性,并判断访问是否成功。
下面的代码创建了一个`Person`实例,然后像之前一样,尝试访问`numberOfRooms`属性:
```swift
let john = Person()
if let roomCount = john.residence?.numberOfRooms {
print("John's residence has \(roomCount) room(s).")
} else {
print("Unable to retrieve the number of rooms.")
}
// 打印 “Unable to retrieve the number of rooms.”
```
因为`john.residence``nil`,所以这个可选链式调用依旧会像先前一样失败。
还可以通过可选链式调用来设置属性值:
```swift
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
john.residence?.address = someAddress
```
在这个例子中,通过`john.residence`来设定`address`属性也会失败,因为`john.residence`当前为`nil`
上面代码中的赋值过程是可选链式调用的一部分,这意味着可选链式调用失败时,等号右侧的代码不会被执行。对于上面的代码来说,很难验证这一点,因为像这样赋值一个常量没有任何副作用。下面的代码完成了同样的事情,但是它使用一个函数来创建`Address`实例然后将该实例返回用于赋值。该函数会在返回前打印“Function was called”这使你能验证等号右侧的代码是否被执行。
```swift
func createAddress() -> Address {
print("Function was called.")
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
return someAddress
}
john.residence?.address = createAddress()
```
没有任何打印消息,可以看出`createAddress()`函数并未被执行。
<a name="calling_methods_through_optional_chaining"></a>
## 通过可选链式调用调用方法
可以通过可选链式调用来调用方法,并判断是否调用成功,即使这个方法没有返回值。
`Residence`类中的`printNumberOfRooms()`方法打印当前的`numberOfRooms`值,如下所示:
```swift
func printNumberOfRooms() {
print("The number of rooms is \(numberOfRooms)")
}
```
这个方法没有返回值。然而,没有返回值的方法具有隐式的返回类型`Void`,如[无返回值函数](./06_Functions.html#functions_without_return_values)中所述。这意味着没有返回值的方法也会返回`()`,或者说空的元组。
如果在可选值上通过可选链式调用来调用这个方法,该方法的返回类型会是`Void?`,而不是`Void`,因为通过可选链式调用得到的返回值都是可选的。这样我们就可以使用`if`语句来判断能否成功调用`printNumberOfRooms()`方法,即使方法本身没有定义返回值。通过判断返回值是否为`nil`可以判断调用是否成功:
```swift
if john.residence?.printNumberOfRooms() != nil {
print("It was possible to print the number of rooms.")
} else {
print("It was not possible to print the number of rooms.")
}
// 打印 “It was not possible to print the number of rooms.”
```
同样的,可以据此判断通过可选链式调用为属性赋值是否成功。在上面的[通过可选链式调用访问属性](#accessing_properties_through_optional_chaining)的例子中,我们尝试给`john.residence`中的`address`属性赋值,即使`residence``nil`。通过可选链式调用给属性赋值会返回`Void?`,通过判断返回值是否为`nil`就可以知道赋值是否成功:
```swift
if (john.residence?.address = someAddress) != nil {
print("It was possible to set the address.")
} else {
print("It was not possible to set the address.")
}
// 打印 “It was not possible to set the address.”
```
<a name="accessing_subscripts_through_optional_chaining"></a>
## 通过可选链式调用访问下标
通过可选链式调用,我们可以在一个可选值上访问下标,并且判断下标调用是否成功。
> 注意
通过可选链式调用访问可选值的下标时,应该将问号放在下标方括号的前面而不是后面。可选链式调用的问号一般直接跟在可选表达式的后面。
下面这个例子用下标访问`john.residence`属性存储的`Residence`实例的`rooms`数组中的第一个房间的名称,因为`john.residence``nil`,所以下标调用失败了:
```swift
if let firstRoomName = john.residence?[0].name {
print("The first room name is \(firstRoomName).")
} else {
print("Unable to retrieve the first room name.")
}
// 打印 “Unable to retrieve the first room name.”
```
在这个例子中,问号直接放在`john.residence`的后面,并且在方括号的前面,因为`john.residence`是可选值。
类似的,可以通过下标,用可选链式调用来赋值:
```swift
john.residence?[0] = Room(name: "Bathroom")
```
这次赋值同样会失败,因为`residence`目前是`nil`
如果你创建一个`Residence`实例,并为其`rooms`数组添加一些`Room`实例,然后将`Residence`实例赋值给`john.residence`,那就可以通过可选链和下标来访问数组中的元素:
```swift
let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "Living Room"))
johnsHouse.rooms.append(Room(name: "Kitchen"))
john.residence = johnsHouse
if let firstRoomName = john.residence?[0].name {
print("The first room name is \(firstRoomName).")
} else {
print("Unable to retrieve the first room name.")
}
// 打印 “The first room name is Living Room.”
```
<a name="accessing_subscripts_of_optional_type"></a>
### 访问可选类型的下标
如果下标返回可选类型值,比如 Swift 中`Dictionary`类型的键的下标,可以在下标的结尾括号后面放一个问号来在其可选返回值上进行可选链式调用:
```swift
var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
testScores["Dave"]?[0] = 91
testScores["Bev"]?[0] += 1
testScores["Brian"]?[0] = 72
// "Dave" 数组现在是 [91, 82, 84]"Bev" 数组现在是 [80, 94, 81]
```
上面的例子中定义了一个`testScores`数组,包含了两个键值对,把`String`类型的键映射到一个`Int`值的数组。这个例子用可选链式调用把`"Dave"`数组中第一个元素设为`91`,把`"Bev"`数组的第一个元素`+1`,然后尝试把`"Brian"`数组中的第一个元素设为`72`。前两个调用成功,因为`testScores`字典中包含`"Dave"``"Bev"`这两个键。但是`testScores`字典中没有`"Brian"`这个键,所以第三个调用失败。
<a name="linking_multiple_levels_of_chaining"></a>
## 连接多层可选链式调用
可以通过连接多个可选链式调用在更深的模型层级中访问属性、方法以及下标。然而,多层可选链式调用不会增加返回值的可选层级。
也就是说:
+ 如果你访问的值不是可选的,可选链式调用将会返回可选值。
+ 如果你访问的值就是可选的,可选链式调用不会让可选返回值变得“更可选”。
因此:
+ 通过可选链式调用访问一个`Int`值,将会返回`Int?`,无论使用了多少层可选链式调用。
+ 类似的,通过可选链式调用访问`Int?`值,依旧会返回`Int?`值,并不会返回`Int??`
下面的例子尝试访问`john`中的`residence`属性中的`address`属性中的`street`属性。这里使用了两层可选链式调用,`residence`以及`address`都是可选值:
```swift
if let johnsStreet = john.residence?.address?.street {
print("John's street name is \(johnsStreet).")
} else {
print("Unable to retrieve the address.")
}
// 打印 “Unable to retrieve the address.”
```
`john.residence`现在包含一个有效的`Residence`实例。然而,`john.residence.address`的值当前为`nil`。因此,调用`john.residence?.address?.street`会失败。
需要注意的是,上面的例子中,`street`的属性为`String?``john.residence?.address?.street`的返回值也依然是`String?`,即使已经使用了两层可选链式调用。
如果为`john.residence.address`赋值一个`Address`实例,并且为`address`中的`street`属性设置一个有效值,我们就能过通过可选链式调用来访问`street`属性:
```swift
let johnsAddress = Address()
johnsAddress.buildingName = "The Larches"
johnsAddress.street = "Laurel Street"
john.residence?.address = johnsAddress
if let johnsStreet = john.residence?.address?.street {
print("John's street name is \(johnsStreet).")
} else {
print("Unable to retrieve the address.")
}
// 打印 “John's street name is Laurel Street.”
```
在上面的例子中,因为`john.residence`包含一个有效的`Residence`实例,所以对`john.residence``address`属性赋值将会成功。
<a name="chaining_on_methods_with_optional_return_values"></a>
## 在方法的可选返回值上进行可选链式调用
上面的例子展示了如何在一个可选值上通过可选链式调用来获取它的属性值。我们还可以在一个可选值上通过可选链式调用来调用方法,并且可以根据需要继续在方法的可选返回值上进行可选链式调用。
在下面的例子中,通过可选链式调用来调用`Address``buildingIdentifier()`方法。这个方法返回`String?`类型的值。如上所述,通过可选链式调用来调用该方法,最终的返回值依旧会是`String?`类型:
```swift
if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
print("John's building identifier is \(buildingIdentifier).")
}
// 打印 “John's building identifier is The Larches.”
```
如果要在该方法的返回值上进行可选链式调用,在方法的圆括号后面加上问号即可:
```swift
if let beginsWithThe =
john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
if beginsWithThe {
print("John's building identifier begins with \"The\".")
} else {
print("John's building identifier does not begin with \"The\".")
}
}
// 打印 “John's building identifier begins with "The".”
```
> 注意
在上面的例子中,在方法的圆括号后面加上问号是因为你要在`buildingIdentifier()`方法的可选返回值上进行可选链式调用,而不是方法本身。

View File

@ -1,252 +0,0 @@
# 错误处理Error Handling
-----------------
> 2.1
> 翻译+校对:[lyojo](https://github.com/lyojo) [ray16897188](https://github.com/ray16897188) 2015-10-23
> 校对:[shanks](http://codebuild.me) 2015-10-24
>
> 2.2
> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-15
本页包含内容:
- [表示并抛出错误](#representing_and_throwing_errors)
- [处理错误](#handling_errors)
- [指定清理操作](#specifying_cleanup_actions)
*错误处理Error handling*是响应错误以及从错误中恢复的过程。Swift 提供了在运行时对可恢复错误的抛出、捕获、传递和操作的一流支持。
某些操作无法保证总是执行完所有代码或总是生成有用的结果。可选类型可用来表示值缺失,但是当某个操作失败时,最好能得知失败的原因,从而可以作出相应的应对。
举个例子,假如有个从磁盘上的某个文件读取数据并进行处理的任务,该任务会有多种可能失败的情况,包括指定路径下文件并不存在,文件不具有可读权限,或者文件编码格式不兼容。区分这些不同的失败情况可以让程序解决并处理某些错误,然后把它解决不了的错误报告给用户。
> 注意
Swift 中的错误处理涉及到错误处理模式,这会用到 Cocoa 和 Objective-C 中的`NSError`。关于这个类的更多信息请参见 [Using Swift with Cocoa and Objective-C (Swift 2.2)](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 中的[错误处理](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html#//apple_ref/doc/uid/TP40014216-CH7-ID10)。
<a name="representing_and_throwing_errors"></a>
##表示并抛出错误
在 Swift 中,错误用符合`ErrorType`协议的类型的值来表示。这个空协议表明该类型可以用于错误处理。
Swift 的枚举类型尤为适合构建一组相关的错误状态,枚举的关联值还可以提供错误状态的额外信息。例如,你可以这样表示在一个游戏中操作自动贩卖机时可能会出现的错误状态:
```swift
enum VendingMachineError: ErrorType {
case InvalidSelection //选择无效
case InsufficientFunds(coinsNeeded: Int) //金额不足
case OutOfStock //缺货
}
```
抛出一个错误可以让你表明有意外情况发生,导致正常的执行流程无法继续执行。抛出错误使用`throw`关键字。例如,下面的代码抛出一个错误,提示贩卖机还需要`5`个硬币:
```swift
throw VendingMachineError.InsufficientFunds(coinsNeeded: 5)
```
<a name="handling_errors"></a>
##处理错误
某个错误被抛出时,附近的某部分代码必须负责处理这个错误,例如纠正这个问题、尝试另外一种方式、或是向用户报告错误。
Swift 中有`4`种处理错误的方式。你可以把函数抛出的错误传递给调用此函数的代码、用`do-catch`语句处理错误、将错误作为可选类型处理、或者断言此错误根本不会发生。每种方式在下面的小节中都有描述。
当一个函数抛出一个错误时,你的程序流程会发生改变,所以重要的是你能迅速识别代码中会抛出错误的地方。为了标识出这些地方,在调用一个能抛出错误的函数、方法或者构造器之前,加上`try`关键字,或者`try?``try!`这种变体。这些关键字在下面的小节中有具体讲解。
> 注意
> Swift 中的错误处理和其他语言中用`try``catch`和`throw`进行异常处理很像。和其他语言中(包括 Objective-C 的异常处理不同的是Swift 中的错误处理并不涉及解除调用栈,这是一个计算代价高昂的过程。就此而言,`throw`语句的性能特性是可以和`return`语句相媲美的。
<a name="propagating_errors_using_throwing_functions"></a>
### 用 throwing 函数传递错误
为了表示一个函数、方法或构造器可以抛出错误,在函数声明的参数列表之后加上`throws`关键字。一个标有`throws`关键字的函数被称作*throwing 函数*。如果这个函数指明了返回值类型,`throws`关键词需要写在箭头(`->`)的前面。
```swift
func canThrowErrors() throws -> String
func cannotThrowErrors() -> String
```
一个 throwing 函数可以在其内部抛出错误,并将错误传递到函数被调用时的作用域。
> 注意
只有 throwing 函数可以传递错误。任何在某个非 throwing 函数内部抛出的错误只能在函数内部处理。
下面的例子中,`VendingMechine`类有一个`vend(itemNamed:)`方法,如果请求的物品不存在、缺货或者投入金额小于物品价格,该方法就会抛出一个相应的`VendingMachineError`
```swift
struct Item {
var price: Int
var count: Int
}
class VendingMachine {
var inventory = [
"Candy Bar": Item(price: 12, count: 7),
"Chips": Item(price: 10, count: 4),
"Pretzels": Item(price: 7, count: 11)
]
var coinsDeposited = 0
func dispenseSnack(snack: String) {
print("Dispensing \(snack)")
}
func vend(itemNamed name: String) throws {
guard let item = inventory[name] else {
throw VendingMachineError.InvalidSelection
}
guard item.count > 0 else {
throw VendingMachineError.OutOfStock
}
guard item.price <= coinsDeposited else {
throw VendingMachineError.InsufficientFunds(coinsNeeded: item.price - coinsDeposited)
}
coinsDeposited -= item.price
var newItem = item
newItem.count -= 1
inventory[name] = newItem
dispenseSnack(name)
}
}
```
`vend(itemNamed:)`方法的实现中使用了`guard`语句来提前退出方法,确保在购买某个物品所需的条件中,有任一条件不满足时,能提前退出方法并抛出相应的错误。由于`throw`语句会立即退出方法,所以物品只有在所有条件都满足时才会被售出。
因为`vend(itemNamed:)`方法会传递出它抛出的任何错误,在你的代码中调用此方法的地方,必须要么直接处理这些错误——使用`do-catch`语句,`try?``try!`;要么继续将这些错误传递下去。例如下面例子中,`buyFavoriteSnack(_:vendingMachine:)`同样是一个 throwing 函数,任何由`vend(itemNamed:)`方法抛出的错误会一直被传递到`buyFavoriteSnack(_:vendingMachine:)`函数被调用的地方。
```swift
let favoriteSnacks = [
"Alice": "Chips",
"Bob": "Licorice",
"Eve": "Pretzels",
]
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
let snackName = favoriteSnacks[person] ?? "Candy Bar"
try vendingMachine.vend(itemNamed: snackName)
}
```
上例中,`buyFavoriteSnack(_:vendingMachine:)`函数会查找某人最喜欢的零食,并通过调用`vend(itemNamed:)`方法来尝试为他们购买。因为`vend(itemNamed:)`方法能抛出错误,所以在调用的它时候在它前面加了`try`关键字。
throwing构造器能像throwing函数一样传递错误.例如下面代码中的`PurchasedSnack`构造器在构造过程中调用了throwing函数,并且通过传递到它的调用者来处理这些错误。
```swift
struct PurchasedSnack {
let name: String
init(name: String, vendingMachine: VendingMachine) throws {
try vendingMachine.vend(itemNamed: name)
self.name = name
}
}
```
###用 Do-Catch 处理错误
可以使用一个`do-catch`语句运行一段闭包代码来处理错误。如果在`do`子句中的代码抛出了一个错误,这个错误会与`catch`子句做匹配,从而决定哪条子句能处理它。
下面是`do-catch`语句的一般形式:
```swift
do {
try expression
statements
} catch pattern 1 {
statements
} catch pattern 2 where condition {
statements
}
```
`catch`后面写一个匹配模式来表明这个子句能处理什么样的错误。如果一条`catch`子句没有指定匹配模式,那么这条子句可以匹配任何错误,并且把错误绑定到一个名字为`error`的局部常量。关于模式匹配的更多信息请参考 [模式](../chapter3/07_Patterns.html)。
`catch`子句不必将`do`子句中的代码所抛出的每一个可能的错误都作处理。如果所有`catch`子句都未处理错误,错误就会传递到周围的作用域。然而,错误还是必须要被某个周围的作用域处理的——要么是一个外围的`do-catch`错误处理语句,要么是一个 throwing 函数的内部。举例来说,下面的代码处理了`VendingMachineError`枚举类型的全部枚举值,但是所有其它的错误就必须由它周围的作用域处理:
```swift
var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
do {
try buyFavoriteSnack("Alice", vendingMachine: vendingMachine)
} catch VendingMachineError.InvalidSelection {
print("Invalid Selection.")
} catch VendingMachineError.OutOfStock {
print("Out of Stock.")
} catch VendingMachineError.InsufficientFunds(let coinsNeeded) {
print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
}
// 打印 “Insufficient funds. Please insert an additional 2 coins.”
```
上面的例子中,`buyFavoriteSnack(_:vendingMachine:)`函数在一个`try`表达式中调用,因为它能抛出错误。如果错误被抛出,相应的执行会马上转移到`catch`子句中,并判断这个错误是否要被继续传递下去。如果没有错误抛出,`do`子句中余下的语句就会被执行。
###将错误转换成可选值
可以使用`try?`通过将错误转换成一个可选值来处理错误。如果在评估`try?`表达式时一个错误被抛出,那么表达式的值就是`nil`。例如,在下面的代码中,`x``y`有着相同的数值和等价的含义:
```swift
func someThrowingFunction() throws -> Int {
// ...
}
let x = try? someThrowingFunction()
let y: Int?
do {
y = try someThrowingFunction()
} catch {
y = nil
}
```
如果`someThrowingFunction()`抛出一个错误,`x``y`的值是`nil`。否则`x``y`的值就是该函数的返回值。注意,无论`someThrowingFunction()`的返回值类型是什么类型,`x``y`都是这个类型的可选类型。例子中此函数返回一个整型,所以`x``y`是可选整型。
如果你想对所有的错误都采用同样的方式来处理,用`try?`就可以让你写出简洁的错误处理代码。例如,下面的代码用几种方式来获取数据,如果所有方式都失败了则返回`nil`
```swift
func fetchData() -> Data? {
if let data = try? fetchDataFromDisk() { return data }
if let data = try? fetchDataFromServer() { return data }
return nil
}
```
### 禁用错误传递
有时你知道某个 throwing 函数实际上在运行时是不会抛出错误的,在这种情况下,你可以在表达式前面写`try!`来禁用错误传递,这会把调用包装在一个不会有错误抛出的运行时断言中。如果真的抛出了错误,你会得到一个运行时错误。
例如,下面的代码使用了`loadImage(_:)`函数,该函数从给定的路径加载图片资源,如果图片无法载入则抛出一个错误。在这种情况下,因为图片是和应用绑定的,运行时不会有错误抛出,所以适合禁用错误传递:
```swift
let photo = try! loadImage("./Resources/John Appleseed.jpg")
```
<a name="specifying_cleanup_actions"></a>
##指定清理操作
可以使用`defer`语句在即将离开当前代码块时执行一系列语句。该语句让你能执行一些必要的清理工作,不管是以何种方式离开当前代码块的——无论是由于抛出错误而离开,还是由于诸如`return`或者`break`的语句。例如,你可以用`defer`语句来确保文件描述符得以关闭,以及手动分配的内存得以释放。
`defer`语句将代码的执行延迟到当前的作用域退出之前。该语句由`defer`关键字和要被延迟执行的语句组成。延迟执行的语句不能包含任何控制转移语句,例如`break`或是`return`语句,或是抛出一个错误。延迟执行的操作会按照它们被指定时的顺序的相反顺序执行——也就是说,第一条`defer`语句中的代码会在第二条`defer`语句中的代码被执行之后才执行,以此类推。
```swift
func processFile(filename: String) throws {
if exists(filename) {
let file = open(filename)
defer {
close(file)
}
while let line = try file.readline() {
// 处理文件。
}
// close(file) 会在这里被调用,即作用域的最后。
}
}
```
上面的代码使用一条`defer`语句来确保`open(_:)`函数有一个相应的对`close(_:)`函数的调用。
> 注意
> 即使没有涉及到错误处理,你也可以使用`defer`语句。

View File

@ -1,35 +1,12 @@
# 类型转换Type Casting
-----------------
# 类型转换
> 1.0
> 翻译:[xiehurricane](https://github.com/xiehurricane)
> 校对:[happyming](https://github.com/happyming)
*类型转换*可以判断实例的类型,也可以将实例看做是其父类或者子类的实例。
> 2.0
> 翻译+校对:[yangsiy](https://github.com/yangsiy)
类型转换在 Swift 中使用 `is``as` 操作符实现。这两个操作符分别提供了一种简单达意的方式去检查值的类型或者转换它的类型。
> 2.1
> 校对:[shanks](http://codebuild.me)2015-11-01
>
> 2.2
> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-16
你也可以用它来检查一个类型是否遵循了某个协议,就像在 [检验协议遵循](./21_Protocols.md#checking_for_protocol_conformance) 部分讲述的一样。
本页包含内容:
- [定义一个类层次作为例子](#defining_a_class_hierarchy_for_type_casting)
- [检查类型](#checking_type)
- [向下转型Downcasting](#downcasting)
- [`Any` 和 `AnyObject` 的类型转换](#type_casting_for_any_and_anyobject)
_类型转换_ 可以判断实例的类型,也可以将实例看做是其父类或者子类的实例。
类型转换在 Swift 中使用 `is``as` 操作符实现。这两个操作符提供了一种简单达意的方式去检查值的类型或者转换它的类型。
你也可以用它来检查一个类型是否实现了某个协议,就像在[检验协议的一致性](./22_Protocols.html#checking_for_protocol_conformance)部分讲述的一样。
<a name="defining_a_class_hierarchy_for_type_casting"></a>
## 定义一个类层次作为例子
## 为类型转换定义类层次 {#defining-a-class-hierarchy-for-type-casting}
你可以将类型转换用在类和子类的层次结构上,检查特定类实例的类型并且转换这个类实例的类型成为这个层次结构中的其他类型。下面的三个代码段定义了一个类层次和一个包含了这些类实例的数组,作为类型转换的例子。
@ -79,10 +56,9 @@ let library = [
在幕后 `library` 里存储的媒体项依然是 `Movie``Song` 类型的。但是,若你迭代它,依次取出的实例会是 `MediaItem` 类型的,而不是 `Movie``Song` 类型。为了让它们作为原本的类型工作,你需要检查它们的类型或者向下转换它们到其它类型,就像下面描述的一样。
<a name="checking_type"></a>
## 检查类型Checking Type
## 检查类型 {#checking-type}
用类型检查操作符(`is`)来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回 `true`,否则返回 `false`
*类型检查操作符*`is`)来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回 `true`,否则返回 `false`
下面的例子定义了两个变量,`movieCount``songCount`,用来计算数组 `library``Movie``Song` 类型的实例数量:
@ -99,23 +75,22 @@ for item in library {
}
print("Media library contains \(movieCount) movies and \(songCount) songs")
// 打印 “Media library contains 2 movies and 3 songs”
// 打印“Media library contains 2 movies and 3 songs”
```
示例迭代了数组 `library` 中的所有项。每一次,`for-in` 循环设置
`item` 为数组中的下一个 `MediaItem`
`item` 常量为数组中的下一个 `MediaItem` 实例
若当前 `MediaItem` 是一个 `Movie` 类型的实例,`item is Movie` 返回
`true`,否则返回 `false`。同样的,`item is Song` 检查 `item` 是否为 `Song` 类型的实例。在循环结束后,`movieCount``songCount` 的值就是被找到的属于各自类型的实例的数量。
<a name="downcasting"></a>
## 向下转型Downcasting
## 向下转型 {#downcasting}
某类型的一个常量或变量可能在幕后实际上属于一个子类。当确定是这种情况时,你可以尝试向下转到它的子类型,用类型转换操作符(`as?``as!`)。
某类型的一个常量或变量可能在幕后实际上属于一个子类。当确定是这种情况时,你可以尝试用*类型转换操作符*`as?``as!`向下转到它的子类型
因为向下转型可能会失败,类型转型操作符带有两种不同形式。条件形式conditional form`as?` 返回一个你试图向下转成的类型的可选值optional value。强制形式 `as!` 把试图向下转型和强制解包force-unwraps转换结果结合为一个操作。
因为向下转型可能会失败,类型转型操作符带有两种不同形式。条件形式 `as?` 返回一个你试图向下转成的类型的可选值。强制形式 `as!` 把试图向下转型和强制解包转换结果结合为一个操作。
当你不确定向下转型可以成功时,用类型转换的条件形式(`as?`)。条件形式的类型转换总是返回一个可选值optional value,并且若下转是不可能的,可选值将是 `nil`。这使你能够检查向下转型是否成功。
当你不确定向下转型可以成功时,用类型转换的条件形式(`as?`)。条件形式的类型转换总是返回一个可选值,并且若下转是不可能的,可选值将是 `nil`。这使你能够检查向下转型是否成功。
只有你可以确定向下转型一定会成功时,才使用强制形式(`as!`)。当你试图向下转型为一个不正确的类型时,强制形式的类型转换会触发一个运行时错误。
@ -126,17 +101,17 @@ print("Media library contains \(movieCount) movies and \(songCount) songs")
```swift
for item in library {
if let movie = item as? Movie {
print("Movie: '\(movie.name)', dir. \(movie.director)")
print("Movie: \(movie.name), dir. \(movie.director)")
} else if let song = item as? Song {
print("Song: '\(song.name)', by \(song.artist)")
print("Song: \(song.name), by \(song.artist)")
}
}
// Movie: 'Casablanca', dir. Michael Curtiz
// Song: 'Blue Suede Shoes', by Elvis Presley
// Movie: 'Citizen Kane', dir. Orson Welles
// Song: 'The One And Only', by Chesney Hawkes
// Song: 'Never Gonna Give You Up', by Rick Astley
// Movie: Casablanca, dir. Michael Curtiz
// Song: Blue Suede Shoes, by Elvis Presley
// Movie: Citizen Kane, dir. Orson Welles
// Song: The One And Only, by Chesney Hawkes
// Song: Never Gonna Give You Up, by Rick Astley
```
示例首先试图将 `item` 下转为 `Movie`。因为 `item` 是一个 `MediaItem`
@ -150,63 +125,18 @@ for item in library {
若向下转型成功,然后 `movie` 的属性将用于打印一个 `Movie` 实例的描述,包括它的导演的名字 `director`。相似的原理被用来检测 `Song` 实例,当 `Song` 被找到时则打印它的描述(包含 `artist` 的名字)。
> 注意
> 注意
>
> 转换没有真的改变实例或它的值。根本的实例保持不变;只是简单地把它作为它被转换成的类型来使用。
<a name="type_casting_for_any_and_anyobject"></a>
## `Any``AnyObject` 的类型转换
## `Any``AnyObject` 的类型转换 {#type-casting-for-any-and-anyobject}
Swift 为不确定类型提供了两种特殊的类型别名:
* `AnyObject` 可以表示任何类类型的实例。
* `Any` 可以表示任何类型,包括函数类型。
* `AnyObject` 可以表示任何类类型的实例。
> 注意
> 只有当你确实需要它们的行为和功能时才使用 `Any``AnyObject`。在你的代码里使用你期望的明确类型总是更好的。
<a name="anyobject"></a>
### `AnyObject` 类型
当我们使用 Cocoa APIs 时,我们会接收到一个 `[AnyObject]` 类型的数组或者说“一个任意类型对象的数组”。Objective-C现在支持明确的数组类型但早期版本的Objective-C并没有这个功能。不管怎样你都可以确信API提供的信息能够正确的表明数组中的元素类型。
在这些情况下,你可以使用强制形式的类型转换(`as!`)来下转数组中的每一项到比 `AnyObject` 更明确的类型不需要可选解包optional unwrapping
下面的示例定义了一个 `[AnyObject]` 类型的数组并填入三个 `Movie` 类型的实例:
```swift
let someObjects: [AnyObject] = [
Movie(name: "2001: A Space Odyssey", director: "Stanley Kubrick"),
Movie(name: "Moon", director: "Duncan Jones"),
Movie(name: "Alien", director: "Ridley Scott")
]
```
因为知道这个数组只包含 `Movie` 实例,你可以直接用(`as!`)下转并解包到非可选的 `Movie` 类型:
```swift
for object in someObjects {
let movie = object as! Movie
print("Movie: '\(movie.name)', dir. \(movie.director)")
}
// Movie: '2001: A Space Odyssey', dir. Stanley Kubrick
// Movie: 'Moon', dir. Duncan Jones
// Movie: 'Alien', dir. Ridley Scott
```
为了变为一个更简短的形式,下转 `someObjects` 数组为 `[Movie]` 类型而不是下转数组中的每一项:
```swift
for movie in someObjects as! [Movie] {
print("Movie: '\(movie.name)', dir. \(movie.director)")
}
// Movie: '2001: A Space Odyssey', dir. Stanley Kubrick
// Movie: 'Moon', dir. Duncan Jones
// Movie: 'Alien', dir. Ridley Scott
```
<a name="any"></a>
### `Any` 类型
只有当你确实需要它们的行为和功能时才使用 `Any``AnyObject`。最好还是在代码中指明需要使用的类型。
这里有个示例,使用 `Any` 类型来和混合的不同类型一起工作,包括函数类型和非类类型。它创建了一个可以存储 `Any` 类型的数组 `things`
@ -223,7 +153,7 @@ things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
things.append({ (name: String) -> String in "Hello, \(name)" })
```
`things` 数组包含两个 `Int` 值,两个 `Double` 值,一个 `String` 值,一个元组 `(Double, Double)`,一个`Movie`实例“Ghostbusters”以及一个接受 `String` 值并返回另一个 `String` 值的闭包表达式。
`things` 数组包含两个 `Int` 值,两个 `Double` 值,一个 `String` 值,一个元组 `(Double, Double)`,一个 `Movie` 实例“Ghostbusters”以及一个接受 `String` 值并返回另一个 `String` 值的闭包表达式。
你可以在 `switch` 表达式的 `case` 中使用 `is``as` 操作符来找出只知道是 `Any``AnyObject` 类型的常量或变量的具体类型。下面的示例迭代 `things` 数组中的每一项,并用 `switch` 语句查找每一项的类型。有几个 `switch` 语句的 `case` 绑定它们匹配到的值到一个指定类型的常量,从而可以打印这些值:
@ -245,8 +175,8 @@ for thing in things {
case let (x, y) as (Double, Double):
print("an (x, y) point at \(x), \(y)")
case let movie as Movie:
print("a movie called '\(movie.name)', dir. \(movie.director)")
case let stringConverter as String -> String:
print("a movie called \(movie.name), dir. \(movie.director)")
case let stringConverter as (String) -> String:
print(stringConverter("Michael"))
default:
print("something else")
@ -259,6 +189,17 @@ for thing in things {
// a positive double value of 3.14159
// a string value of "hello"
// an (x, y) point at 3.0, 5.0
// a movie called 'Ghostbusters', dir. Ivan Reitman
// a movie called Ghostbusters, dir. Ivan Reitman
// Hello, Michael
```
> 注意
>
> `Any` 类型可以表示所有类型的值包括可选类型。Swift 会在你用 `Any` 类型来表示一个可选值的时候,给你一个警告。如果你确实想使用 `Any` 类型来承载可选值,你可以使用 `as` 操作符显式转换为 `Any`,如下所示:
```swift
let optionalNumber: Int? = 3
things.append(optionalNumber) // 警告
things.append(optionalNumber as Any) // 没有警告
```

View File

@ -0,0 +1,85 @@
# 嵌套类型
枚举常被用于为特定类或结构体实现某些功能。类似地枚举可以方便的定义工具类或结构体从而为某个复杂的类型所使用。为了实现这种功能Swift 允许你定义*嵌套类型*,可以在支持的类型中定义嵌套的枚举、类和结构体。
要在一个类型中嵌套另一个类型,将嵌套类型的定义写在其外部类型的 `{}` 内,而且可以根据需要定义多级嵌套。
## 嵌套类型实践 {#nested-types-in-action}
下面这个例子定义了一个结构体 `BlackjackCard`(二十一点),用来模拟 `BlackjackCard` 中的扑克牌点数。`BlackjackCard` 结构体包含两个嵌套定义的枚举类型 `Suit``Rank`
`BlackjackCard` 中,`Ace` 牌可以表示 `1` 或者 `11``Ace` 牌的这一特征通过一个嵌套在 `Rank` 枚举中的结构体 `Values` 来表示:
```swift
struct BlackjackCard {
// 嵌套的 Suit 枚举
enum Suit: Character {
case spades = "♠", hearts = "♡", diamonds = "♢", clubs = "♣"
}
// 嵌套的 Rank 枚举
enum Rank: Int {
case two = 2, three, four, five, six, seven, eight, nine, ten
case jack, queen, king, ace
struct Values {
let first: Int, second: Int?
}
var values: Values {
switch self {
case .ace:
return Values(first: 1, second: 11)
case .jack, .queen, .king:
return Values(first: 10, second: nil)
default:
return Values(first: self.rawValue, second: nil)
}
}
}
// BlackjackCard 的属性和方法
let rank: Rank, suit: Suit
var description: String {
var output = "suit is \(suit.rawValue),"
output += " value is \(rank.values.first)"
if let second = rank.values.second {
output += " or \(second)"
}
return output
}
}
```
`Suit` 枚举用来描述扑克牌的四种花色,并用一个 `Character` 类型的原始值表示花色符号。
`Rank` 枚举用来描述扑克牌从 `Ace`~`10`,以及 `J``Q``K`,这 `13` 种牌,并用一个 `Int` 类型的原始值表示牌的面值。(这个 `Int` 类型的原始值未用于 `Ace``J``Q``K``4` 种牌。)
如上所述,`Rank` 枚举在内部定义了一个嵌套结构体 `Values`。结构体 `Values` 中定义了两个属性,用于反映只有 `Ace` 有两个数值,其余牌都只有一个数值:
- `first` 的类型为 `Int`
- `second` 的类型为 `Int?`,或者说“可选 `Int`
`Rank` 还定义了一个计算型属性 `values`,它将会返回一个 `Values` 结构体的实例。这个计算型属性会根据牌的面值,用适当的数值去初始化 `Values` 实例。对于 `J``Q``K``Ace` 这四种牌,会使用特殊数值。对于数字面值的牌,使用枚举实例的 `Int` 类型的原始值。
`BlackjackCard` 结构体拥有两个属性——`rank``suit`。它也同样定义了一个计算型属性 `description``description` 属性用 `rank``suit` 中的内容来构建对扑克牌名字和数值的描述。该属性使用可选绑定来检查可选类型 `second` 是否有值,若有值,则在原有的描述中增加对 `second` 的描述。
因为 `BlackjackCard` 是一个没有自定义构造器的结构体,在 [结构体的逐一成员构造器](./14_Initialization.md#memberwise_initializers_for_structure_types) 中可知,结构体有默认的成员构造器,所以你可以用默认的构造器去初始化新常量 `theAceOfSpades`
```swift
let theAceOfSpades = BlackjackCard(rank: .ace, suit: .spades)
print("theAceOfSpades: \(theAceOfSpades.description)")
// 打印“theAceOfSpades: suit is ♠, value is 1 or 11”
```
尽管 `Rank``Suit` 嵌套在 `BlackjackCard` 中,但它们的类型仍可从上下文中推断出来,所以在初始化实例时能够单独通过成员名称(`.ace``.spades`)引用枚举实例。在上面的例子中,`description` 属性正确地反映了黑桃 A 牌具有 `1``11` 两个值。
## 引用嵌套类型 {#referring-to-nested-types}
在外部引用嵌套类型时,在嵌套类型的类型名前加上其外部类型的类型名作为前缀:
```swift
let heartsSymbol = BlackjackCard.Suit.hearts.rawValue
// 红心符号为“♡”
```
对于上面这个例子,这样可以使 `Suit``Rank``Values` 的名字尽可能的短,因为它们的名字可以由定义它们的上下文来限定。

View File

@ -0,0 +1,271 @@
# 扩展
*扩展*可以给一个现有的类,结构体,枚举,还有协议添加新的功能。它还拥有不需要访问被扩展类型源代码就能完成扩展的能力(即*逆向建模*)。扩展和 Objective-C 的分类很相似。(与 Objective-C 分类不同的是Swift 扩展是没有名字的。)
Swift 中的扩展可以:
- 添加计算型实例属性和计算型类属性
- 定义实例方法和类方法
- 提供新的构造器
- 定义下标
- 定义和使用新的嵌套类型
- 使已经存在的类型遵循conform一个协议
在 Swift 中,你甚至可以扩展协议以提供其需要的实现,或者添加额外功能给遵循的类型所使用。你可以从 [协议扩展](https://docs.swift.org/swift-book/LanguageGuide/Protocols.html#ID521) 获取更多细节。
> 注意
>
> 扩展可以给一个类型添加新的功能,但是不能重写已经存在的功能。
## 扩展的语法 {#extension-syntax}
使用 `extension` 关键字声明扩展:
```swift
extension SomeType {
// 在这里给 SomeType 添加新的功能
}
```
扩展可以扩充一个现有的类型,给它添加一个或多个协议。协议名称的写法和类或者结构体一样:
```swift
extension SomeType: SomeProtocol, AnotherProtocol {
// 协议所需要的实现写在这里
}
```
这种遵循协议的方式在 [使用扩展遵循协议](https://docs.swift.org/swift-book/LanguageGuide/Protocols.html#ID277) 中有描述。
扩展可以使用在现有范型类型上,就像 [扩展范型类型](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID185) 中描述的一样。你还可以使用扩展给泛型类型有条件的添加功能,就像 [扩展一个带有 Where 字句的范型](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID553) 中描述的一样。
> 注意
>
> 对一个现有的类型,如果你定义了一个扩展来添加新的功能,那么这个类型的所有实例都可以使用这个新功能,包括那些在扩展定义之前就存在的实例。
## 计算型属性 {#computed-properties}
扩展可以给现有类型添加计算型实例属性和计算型类属性。这个例子给 Swift 内建的 `Double` 类型添加了五个计算型实例属性,从而提供与距离单位相关工作的基本支持:
```swift
extension Double {
var km: Double { return self * 1_000.0 }
var m: Double { return self }
var cm: Double { return self / 100.0 }
var mm: Double { return self / 1_000.0 }
var ft: Double { return self / 3.28084 }
}
let oneInch = 25.4.mm
print("One inch is \(oneInch) meters")
// 打印“One inch is 0.0254 meters”
let threeFeet = 3.ft
print("Three feet is \(threeFeet) meters")
// 打印“Three feet is 0.914399970739201 meters”
```
这些计算型属性表示的含义是把一个 `Double` 值看作是某单位下的长度值。即使它们被实现为计算型属性,但这些属性的名字仍可紧接一个浮点型字面值,从而通过点语法来使用,并以此实现距离转换。
在上述例子中,`Double` 类型的 `1.0` 代表的是“一米”。这就是为什么计算型属性 `m` 返回的是 `self`——表达式 `1.m` 被认为是计算一个 `Double` 类型的 `1.0`
其它单位则需要一些单位换算。一千米等于 1,000 米,所以计算型属性 `km` 要把值乘以 `1_000.00` 来实现千米到米的单位换算。类似地,一米有 3.28084 英尺,所以计算型属性 `ft` 要把对应的 `Double` 值除以 `3.28084`,来实现英尺到米的单位换算。
这些属性都是只读的计算型属性,所以为了简便,它们的表达式里面都不包含 `get` 关键字。它们使用 `Double` 作为返回值类型,并可用于所有接受 `Double` 类型的数学计算中:
```swift
let aMarathon = 42.km + 195.m
print("A marathon is \(aMarathon) meters long")
// 打印“A marathon is 42195.0 meters long”
```
> 注意
>
> 扩展可以添加新的计算属性,但是它们不能添加存储属性,或向现有的属性添加属性观察者。
## 构造器 {#initializers}
扩展可以给现有的类型添加新的构造器。它使你可以把自定义类型作为参数来供其他类型的构造器使用,或者在类型的原始实现上添加额外的构造选项。
扩展可以给一个类添加新的便利构造器,但是它们不能给类添加新的指定构造器或者析构器。指定构造器和析构器必须始终由类的原始实现提供。
如果你使用扩展给一个值类型添加构造器只是用于给所有的存储属性提供默认值,并且没有定义任何自定义构造器,那么你可以在该值类型扩展的构造器中使用默认构造器和成员构造器。如果你把构造器写到了值类型的原始实现中,就像 [值类型的构造器委托](https://docs.swift.org/swift-book/LanguageGuide/Initialization.html#ID215) 中所描述的,那么就不属于在扩展中添加构造器。
如果你使用扩展给另一个模块中定义的结构体添加构造器,那么新的构造器直到定义模块中使用一个构造器之前,不能访问 `self`
在下面的例子中,自定义了一个的 `Rect` 结构体用来表示一个几何矩形。这个例子中还定义了两个给予支持的结构体 `Size``Point`,它们都把属性的默认值设置为 `0.0`
```swift
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
}
```
因为 `Rect` 结构体给所有的属性都提供了默认值,所以它自动获得了一个默认构造器和一个成员构造器,就像 [默认构造器](https://docs.swift.org/swift-book/LanguageGuide/Initialization.html#ID213) 中描述的一样。这些构造器可以用来创建新的 `Rect` 实例:
```swift
let defaultRect = Rect()
let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0),
size: Size(width: 5.0, height: 5.0))
```
你可以通过扩展 `Rect` 结构体来提供一个允许指定 point 和 size 的构造器:
```swift
extension Rect {
init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
```
这个新的构造器首先根据提供的 `center``size` 计算一个适当的原点。然后这个构造器调用结构体自带的成员构造器 `init(origin:size:)`,它会将新的 origin 和 size 值储存在适当的属性中:
```swift
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
size: Size(width: 3.0, height: 3.0))
// centerRect 的 origin 是 (2.5, 2.5) 并且它的 size 是 (3.0, 3.0)
```
> 注意
>
> 如果你通过扩展提供一个新的构造器,你有责任确保每个通过该构造器创建的实例都是初始化完整的。
## 方法 {#methods}
扩展可以给现有类型添加新的实例方法和类方法。在下面的例子中,给 `Int` 类型添加了一个新的实例方法叫做 `repetitions`
```swift
extension Int {
func repetitions(task: () -> Void) {
for _ in 0..<self {
task()
}
}
}
```
`repetitions(task:)` 方法仅接收一个 `() -> Void` 类型的参数,它表示一个没有参数没有返回值的方法。
定义了这个扩展之后,你可以对任意整形数值调用 `repetitions(task:)` 方法,来执行对应次数的任务:
```swift
3.repetitions {
print("Hello!")
}
// Hello!
// Hello!
// Hello!
```
### 可变实例方法 {#mutating-instance-methods}
通过扩展添加的实例方法同样也可以修改(或 *mutating改变*)实例本身。结构体和枚举的方法,若是可以修改 `self` 或者它自己的属性,则必须将这个实例方法标记为 `mutating`,就像是改变了方法的原始实现。
在下面的例子中,对 Swift 的 `Int` 类型添加了一个新的 mutating 方法,叫做 `square`,它将原始值求平方:
```swift
extension Int {
mutating func square() {
self = self * self
}
}
var someInt = 3
someInt.square()
// someInt 现在是 9
```
## 下标 {#subscripts}
扩展可以给现有的类型添加新的下标。下面的例子中,对 Swift 的 `Int` 类型添加了一个整数类型的下标。下标 `[n]` 从数字右侧开始,返回小数点后的第 `n` 位:
- `123456789[0]` 返回 `9`
- `123456789[1]` 返回 `8`
……以此类推:
```swift
extension Int {
subscript(digitIndex: Int) -> Int {
var decimalBase = 1
for _ in 0..<digitIndex {
decimalBase *= 10
}
return (self / decimalBase) % 10
}
}
746381295[0]
// 返回 5
746381295[1]
// 返回 9
746381295[2]
// 返回 2
746381295[8]
// 返回 7
```
如果操作的 `Int` 值没有足够的位数满足所请求的下标,那么下标的现实将返回 `0`,将好像在数字的左边补上了 0
```swift
746381295[9]
// 返回 0就好像你进行了这个请求
0746381295[9]
```
## 嵌套类型 {#nested-yypes}
扩展可以给现有的类,结构体,还有枚举添加新的嵌套类型:
```swift
extension Int {
enum Kind {
case negative, zero, positive
}
var kind: Kind {
switch self {
case 0:
return .zero
case let x where x > 0:
return .positive
default:
return .negative
}
}
}
```
这个例子给 `Int` 添加了一个新的嵌套枚举。这个枚举叫做 `Kind`,表示特定整数所代表的数字类型。具体来说,它表示数字是负的、零的还是正的。
这个例子同样给 `Int` 添加了一个新的计算型实例属性,叫做 `kind`,它返回被操作整数所对应的 `Kind` 枚举 case 分支。
现在,任意 `Int` 的值都可以使用这个嵌套类型:
```swift
func printIntegerKinds(_ numbers: [Int]) {
for number in numbers {
switch number.kind {
case .negative:
print("- ", terminator: "")
case .zero:
print("0 ", terminator: "")
case .positive:
print("+ ", terminator: "")
}
}
print("")
}
printIntegerKinds([3, 19, -27, 0, -6, 0, 7])
// 打印“+ + - 0 - 0 + ”
```
方法 `printIntegerKinds(_:)`,使用一个 `Int` 类型的数组作为输入,然后依次迭代这些值。对于数组中的每一个整数,方法会检查它的 `kind` 计算型属性,然后打印适当的描述。
> 注意
>
> `number.kind` 已经被认为是 `Int.Kind` 类型。所以,在 `switch` 语句中所有的 `Int.Kind` case 分支可以被缩写,就像使用 `.negative` 替代 `Int.Kind.negative.`。

View File

@ -1,105 +0,0 @@
# 嵌套类型Nested Types
-----------------
> 1.0
> 翻译:[Lin-H](https://github.com/Lin-H)
> 校对:[shinyzhu](https://github.com/shinyzhu)
> 2.0
> 翻译+校对:[SergioChan](https://github.com/SergioChan)
> 2.1
> 校对:[shanks](http://codebuild.me)2015-11-01
>
> 2.2
> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-16
本页包含内容:
- [嵌套类型实践](#nested_types_in_action)
- [引用嵌套类型](#referring_to_nested_types)
枚举常被用于为特定类或结构体实现某些功能。类似的枚举可以方便的定义工具类或结构体从而为某个复杂的类型所使用。为了实现这种功能Swift 允许你定义嵌套类型,可以在支持的类型中定义嵌套的枚举、类和结构体。
要在一个类型中嵌套另一个类型,将嵌套类型的定义写在其外部类型的`{}`内,而且可以根据需要定义多级嵌套。
<a name="nested_types_in_action"></a>
## 嵌套类型实践
下面这个例子定义了一个结构体`BlackjackCard`(二十一点),用来模拟`BlackjackCard`中的扑克牌点数。`BlackjackCard`结构体包含两个嵌套定义的枚举类型`Suit``Rank`
`BlackjackCard`中,`Ace`牌可以表示`1`或者`11``Ace`牌的这一特征通过一个嵌套在`Rank`枚举中的结构体`Values`来表示:
```swift
struct BlackjackCard {
// 嵌套的 Suit 枚举
enum Suit: Character {
case Spades = "♠", Hearts = "♡", Diamonds = "♢", Clubs = "♣"
}
// 嵌套的 Rank 枚举
enum Rank: Int {
case Two = 2, Three, Four, Five, Six, Seven, Eight, Nine, Ten
case Jack, Queen, King, Ace
struct Values {
let first: Int, second: Int?
}
var values: Values {
switch self {
case .Ace:
return Values(first: 1, second: 11)
case .Jack, .Queen, .King:
return Values(first: 10, second: nil)
default:
return Values(first: self.rawValue, second: nil)
}
}
}
// BlackjackCard 的属性和方法
let rank: Rank, suit: Suit
var description: String {
var output = "suit is \(suit.rawValue),"
output += " value is \(rank.values.first)"
if let second = rank.values.second {
output += " or \(second)"
}
return output
}
}
```
`Suit`枚举用来描述扑克牌的四种花色,并用一个`Character`类型的原始值表示花色符号。
`Rank`枚举用来描述扑克牌从`Ace`~`10`,以及`J``Q``K`,这`13`种牌,并用一个`Int`类型的原始值表示牌的面值。(这个`Int`类型的原始值未用于`Ace``J``Q``K``4`种牌。)
如上所述,`Rank`枚举在内部定义了一个嵌套结构体`Values`。结构体`Values`中定义了两个属性,用于反映只有`Ace`有两个数值,其余牌都只有一个数值:
- `first`的类型为`Int`
- `second`的类型为`Int?`或者说“optional `Int`
`Rank`还定义了一个计算型属性`values`,它将会返回一个`Values`结构体的实例。这个计算型属性会根据牌的面值,用适当的数值去初始化`Values`实例。对于`J``Q``K``Ace`这四种牌,会使用特殊数值。对于数字面值的牌,使用枚举实例的原始值。
`BlackjackCard`结构体拥有两个属性——`rank``suit`。它也同样定义了一个计算型属性`description``description`属性用`rank``suit`中的内容来构建对扑克牌名字和数值的描述。该属性使用可选绑定来检查可选类型`second`是否有值,若有值,则在原有的描述中增加对`second`的描述。
因为`BlackjackCard`是一个没有自定义构造器的结构体,在[结构体的逐一成员构造器](./14_Initialization.html#memberwise_initializers_for_structure_types)中可知,结构体有默认的成员构造器,所以你可以用默认的构造器去初始化新常量`theAceOfSpades`
```swift
let theAceOfSpades = BlackjackCard(rank: .Ace, suit: .Spades)
print("theAceOfSpades: \(theAceOfSpades.description)")
// 打印 “theAceOfSpades: suit is ♠, value is 1 or 11”
```
尽管`Rank``Suit`嵌套在`BlackjackCard`中,但它们的类型仍可从上下文中推断出来,所以在初始化实例时能够单独通过成员名称(`.Ace``.Spades`)引用枚举实例。在上面的例子中,`description`属性正确地反映了黑桃A牌具有`1``11`两个值。
<a name="referring_to_nested_types"></a>
## 引用嵌套类型
在外部引用嵌套类型时,在嵌套类型的类型名前加上其外部类型的类型名作为前缀:
```swift
let heartsSymbol = BlackjackCard.Suit.Hearts.rawValue
// 红心符号为 “♡”
```
对于上面这个例子,这样可以使`Suit``Rank``Values`的名字尽可能的短,因为它们的名字可以由定义它们的上下文来限定。

View File

@ -1,306 +0,0 @@
# 扩展Extensions
----
> 1.0
> 翻译:[lyuka](https://github.com/lyuka)
> 校对:[Hawstein](https://github.com/Hawstein)
> 2.0
> 翻译+校对:[shanks](http://codebuild.me)
> 2.1
> 校对:[shanks](http://codebuild.me)
>
> 2.2
> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-16
本页包含内容:
- [扩展语法](#extension_syntax)
- [计算型属性](#computed_properties)
- [构造器](#initializers)
- [方法](#methods)
- [下标](#subscripts)
- [嵌套类型](#nested_types)
*扩展* 就是为一个已有的类、结构体、枚举类型或者协议类型添加新功能。这包括在没有权限获取原始源代码的情况下扩展类型的能力(即 *逆向建模* )。扩展和 Objective-C 中的分类类似。(与 Objective-C 不同的是Swift 的扩展没有名字。)
Swift 中的扩展可以:
- 添加计算型属性和计算型类型属性
- 定义实例方法和类型方法
- 提供新的构造器
- 定义下标
- 定义和使用新的嵌套类型
- 使一个已有类型符合某个协议
在 Swift 中,你甚至可以对协议进行扩展,提供协议要求的实现,或者添加额外的功能,从而可以让符合协议的类型拥有这些功能。你可以从[协议扩展](./22_Protocols.html#protocol_extensions)获取更多的细节。
> 注意
扩展可以为一个类型添加新的功能,但是不能重写已有的功能。
<a name="extension_syntax"></a>
## 扩展语法Extension Syntax
使用关键字 `extension` 来声明扩展:
```swift
extension SomeType {
// 为 SomeType 添加的新功能写到这里
}
```
可以通过扩展来扩展一个已有类型,使其采纳一个或多个协议。在这种情况下,无论是类还是结构体,协议名字的书写方式完全一样:
```swift
extension SomeType: SomeProtocol, AnotherProctocol {
// 协议实现写到这里
}
```
通过这种方式添加协议一致性的详细描述请参阅[利用扩展添加协议一致性](./22_Protocols.html#adding_protocol_conformance_with_an_extension)。
> 注意
如果你通过扩展为一个已有类型添加新功能,那么新功能对该类型的所有已有实例都是可用的,即使它们是在这个扩展定义之前创建的。
<a name="computed_properties"></a>
## 计算型属性Computed Properties
扩展可以为已有类型添加计算型实例属性和计算型类型属性。下面的例子为 Swift 的内建 `Double` 类型添加了五个计算型实例属性,从而提供与距离单位协作的基本支持:
```swift
extension Double {
var km: Double { return self * 1_000.0 }
var m : Double { return self }
var cm: Double { return self / 100.0 }
var mm: Double { return self / 1_000.0 }
var ft: Double { return self / 3.28084 }
}
let oneInch = 25.4.mm
print("One inch is \(oneInch) meters")
// 打印 “One inch is 0.0254 meters”
let threeFeet = 3.ft
print("Three feet is \(threeFeet) meters")
// 打印 “Three feet is 0.914399970739201 meters”
```
这些计算型属性表达的含义是把一个 `Double` 值看作是某单位下的长度值。即使它们被实现为计算型属性,但这些属性的名字仍可紧接一个浮点型字面值,从而通过点语法来使用,并以此实现距离转换。
在上述例子中,`Double``1.0` 用来表示“1米”。这就是为什么计算型属性 `m` 返回 `self`,即表达式 `1.m` 被认为是计算 `Double``1.0`
其它单位则需要一些单位换算。一千米等于 1,000 米,所以计算型属性 `km` 要把值乘以 `1_000.00` 来实现千米到米的单位换算。类似地,一米有 3.28024 英尺,所以计算型属性 `ft` 要把对应的 `Double` 值除以 `3.28024` 来实现英尺到米的单位换算。
这些属性是只读的计算型属性,为了更简洁,省略了 `get` 关键字。它们的返回值是 `Double`,而且可以用于所有接受 `Double` 值的数学计算中:
```swift
let aMarathon = 42.km + 195.m
print("A marathon is \(aMarathon) meters long")
// 打印 “A marathon is 42195.0 meters long”
```
> 注意
扩展可以添加新的计算型属性,但是不可以添加存储型属性,也不可以为已有属性添加属性观察器。
<a name="initializers"></a>
## 构造器Initializers
扩展可以为已有类型添加新的构造器。这可以让你扩展其它类型,将你自己的定制类型作为其构造器参数,或者提供该类型的原始实现中未提供的额外初始化选项。
扩展能为类添加新的便利构造器,但是它们不能为类添加新的指定构造器或析构器。指定构造器和析构器必须总是由原始的类实现来提供。
> 注意
如果你使用扩展为一个值类型添加构造器,同时该值类型的原始实现中未定义任何定制的构造器且所有存储属性提供了默认值,那么我们就可以在扩展中的构造器里调用默认构造器和逐一成员构造器。
正如在[值类型的构造器代理](./14_Initialization.html#initializer_delegation_for_value_types)中描述的,如果你把定制的构造器写在值类型的原始实现中,上述规则将不再适用。
下面的例子定义了一个用于描述几何矩形的结构体 `Rect`。这个例子同时定义了两个辅助结构体 `Size``Point`,它们都把 `0.0` 作为所有属性的默认值:
```swift
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
}
```
因为结构体 `Rect` 未提供定制的构造器,因此它会获得一个逐一成员构造器。又因为它为所有存储型属性提供了默认值,它又会获得一个默认构造器。详情请参阅[默认构造器](./14_Initialization.html#default_initializers)。这些构造器可以用于构造新的 `Rect` 实例:
```swift
let defaultRect = Rect()
let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0),
size: Size(width: 5.0, height: 5.0))
```
你可以提供一个额外的接受指定中心点和大小的构造器来扩展 `Rect` 结构体:
```swift
extension Rect {
init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
```
这个新的构造器首先根据提供的 `center``size` 的值计算一个合适的原点。然后调用该结构体的逐一成员构造器 `init(origin:size:)`,该构造器将新的原点和大小的值保存到了相应的属性中:
```swift
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
size: Size(width: 3.0, height: 3.0))
// centerRect 的原点是 (2.5, 2.5),大小是 (3.0, 3.0)
```
> 注意
如果你使用扩展提供了一个新的构造器,你依旧有责任确保构造过程能够让实例完全初始化。
<a name="methods"></a>
## 方法Methods
扩展可以为已有类型添加新的实例方法和类型方法。下面的例子为 `Int` 类型添加了一个名为 `repetitions` 的实例方法:
```swift
extension Int {
func repetitions(task: () -> Void) {
for _ in 0..<self {
task()
}
}
}
```
这个 `repetitions(:_)` 方法接受一个 `() -> Void` 类型的单参数,表示没有参数且没有返回值的函数。
定义该扩展之后,你就可以对任意整数调用 `repetitions(_:)` 方法,将闭包中的任务执行整数对应的次数:
```swift
3.repetitions({
print("Hello!")
})
// Hello!
// Hello!
// Hello!
```
可以使用尾随闭包让调用更加简洁:
```swift
3.repetitions {
print("Goodbye!")
}
// Goodbye!
// Goodbye!
// Goodbye!
```
<a name="mutating_instance_methods"></a>
### 可变实例方法Mutating Instance Methods
通过扩展添加的实例方法也可以修改该实例本身。结构体和枚举类型中修改 `self` 或其属性的方法必须将该实例方法标注为 `mutating`,正如来自原始实现的可变方法一样。
下面的例子为 Swift 的 `Int` 类型添加了一个名为 `square` 的可变方法,用于计算原始值的平方值:
```swift
extension Int {
mutating func square() {
self = self * self
}
}
var someInt = 3
someInt.square()
// someInt 的值现在是 9
```
<a name="subscripts"></a>
## 下标Subscripts
扩展可以为已有类型添加新下标。这个例子为 Swift 内建类型 `Int` 添加了一个整型下标。该下标 `[n]` 返回十进制数字从右向左数的第 `n` 个数字:
- `123456789[0]` 返回 `9`
- `123456789[1]` 返回 `8`
……以此类推。
```swift
extension Int {
subscript(digitIndex: Int) -> Int {
var decimalBase = 1
for _ in 0..<digitIndex {
decimalBase *= 10
}
return (self / decimalBase) % 10
}
}
746381295[0]
// 返回 5
746381295[1]
// 返回 9
746381295[2]
// 返回 2
746381295[8]
// 返回 7
```
如果该 `Int` 值没有足够的位数,即下标越界,那么上述下标实现会返回 `0`,犹如在数字左边自动补 `0`
```swift
746381295[9]
// 返回 0即等同于
0746381295[9]
```
<a name="nested_types"></a>
## 嵌套类型Nested Types
扩展可以为已有的类、结构体和枚举添加新的嵌套类型:
```swift
extension Int {
enum Kind {
case Negative, Zero, Positive
}
var kind: Kind {
switch self {
case 0:
return .Zero
case let x where x > 0:
return .Positive
default:
return .Negative
}
}
}
```
该例子为 `Int` 添加了嵌套枚举。这个名为 `Kind` 的枚举表示特定整数的类型。具体来说,就是表示整数是正数、零或者负数。
这个例子还为 `Int` 添加了一个计算型实例属性,即 `kind`,用来根据整数返回适当的 `Kind` 枚举成员。
现在,这个嵌套枚举可以和任意 `Int` 值一起使用了:
```swift
func printIntegerKinds(numbers: [Int]) {
for number in numbers {
switch number.kind {
case .Negative:
print("- ", terminator: "")
case .Zero:
print("0 ", terminator: "")
case .Positive:
print("+ ", terminator: "")
}
}
print("")
}
printIntegerKinds([3, 19, -27, 0, -6, 0, 7])
// 打印 “+ + - 0 - 0 + ”
```
函数 `printIntegerKinds(_:)` 接受一个 `Int` 数组,然后对该数组进行迭代。在每次迭代过程中,对当前整数的计算型属性 `kind` 的值进行评估,并打印出适当的描述。
> 注意
由于已知 `number.kind``Int.Kind` 类型,因此在 `switch` 语句中,`Int.Kind` 中的所有成员值都可以使用简写形式,例如使用 `. Negative` 而不是 `Int.Kind.Negative`

View File

@ -1,48 +1,10 @@
# 协议Protocols
-----------------
# 协议
> 1.0
> 翻译:[geek5nan](https://github.com/geek5nan)
> 校对:[dabing1022](https://github.com/dabing1022)
*协议* 定义了一个蓝图,规定了用来实现某一特定任务或者功能的方法、属性,以及其他需要的东西。类、结构体或枚举都可以遵循协议,并为协议定义的这些要求提供具体实现。某个类型能够满足某个协议的要求,就可以说该类型*遵循*这个协议。
> 2.0
> 翻译+校对:[futantan](https://github.com/futantan)
除了遵循协议的类型必须实现的要求外,还可以对协议进行扩展,通过扩展来实现一部分要求或者实现一些附加功能,这样遵循协议的类型就能够使用这些功能。
> 2.1
> 翻译:[小铁匠Linus](https://github.com/kevin833752)
> 校对:[shanks](http://codebuild.me)
>
> 2.2
> 翻译+校对:[SketchK](https://github.com/SketchK)
>
> 3.0
> 校对:[CMB](https://github.com/chenmingbiao)
本页包含内容:
- [协议语法Protocol Syntax](#protocol_syntax)
- [属性要求Property Requirements](#property_requirements)
- [方法要求Method Requirements](#method_requirements)
- [Mutating 方法要求Mutating Method Requirements](#mutating_method_requirements)
- [构造器要求Initializer Requirements](#initializer_requirements)
- [协议作为类型Protocols as Types](#protocols_as_types)
- [委托代理模式Delegation](#delegation)
- [通过扩展添加协议一致性Adding Protocol Conformance with an Extension](#adding_protocol_conformance_with_an_extension)
- [通过扩展采纳协议Declaring Protocol Adoption with an Extension](#declaring_protocol_adoption_with_an_extension)
- [协议类型的集合Collections of Protocol Types](#collections_of_protocol_types)
- [协议的继承Protocol Inheritance](#protocol_inheritance)
- [类类型专属协议Class-Only Protocol](#class_only_protocol)
- [协议合成Protocol Composition](#protocol_composition)
- [检查协议一致性Checking for Protocol Conformance](#checking_for_protocol_conformance)
- [可选的协议要求Optional Protocol Requirements](#optional_protocol_requirements)
- [协议扩展Protocol Extensions](#protocol_extensions)
协议定义了一个蓝图,规定了用来实现某一特定任务或者功能的方法、属性,以及其他需要的东西。类、结构体或枚举都可以采纳协议,并为协议定义的这些要求提供具体实现。某个类型能够满足某个协议的要求,就可以说该类型“符合”这个协议。
除了采纳协议的类型必须实现的要求外,还可以对协议进行扩展,通过扩展来实现一部分要求或者实现一些附加功能,这样采纳协议的类型就能够使用这些功能。
<a name="protocol_syntax"></a>
## 协议语法
## 协议语法 {#protocol-syntax}
协议的定义方式与类、结构体和枚举的定义非常相似:
@ -52,7 +14,7 @@ protocol SomeProtocol {
}
```
要让自定义类型采纳某个协议,在定义类型时,需要在类型名称后加上协议名称,中间以冒号(`:`)分隔。采纳多个协议时,各协议之间用逗号(`,`)分隔:
要让自定义类型遵循某个协议,在定义类型时,需要在类型名称后加上协议名称,中间以冒号(`:`)分隔。遵循多个协议时,各协议之间用逗号(`,`)分隔:
```swift
struct SomeStructure: FirstProtocol, AnotherProtocol {
@ -60,7 +22,7 @@ struct SomeStructure: FirstProtocol, AnotherProtocol {
}
```
拥有父类的类在采纳协议时,应该将父类名放在协议名之前,以逗号分隔:
若一个拥有父类的类在遵循协议时,应该将父类名放在协议名之前,以逗号分隔:
```swift
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
@ -68,14 +30,13 @@ class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
}
```
<a name="property_requirements"></a>
## 属性要求
## 属性要求 {#property-requirements}
协议可以要求采纳协议的类型提供特定名称和类型的实例属性或类型属性。协议不指定属性是存储属性还是计算属性,它只指定属性的名称和类型。此外,协议还指定属性是可读的还是可读可写的。
协议可以要求遵循协议的类型提供特定名称和类型的实例属性或类型属性。协议不指定属性是存储属性还是计算属性,它只指定属性的名称和类型。此外,协议还指定属性是*可读*的还是*可读可写的*
如果协议要求属性是可读可写的,那么该属性不能是常量属性或只读的计算型属性。如果协议只要求属性是可读的,那么该属性不仅可以是可读的,如果代码需要的话,还可以是可写的。
协议总是用 `var` 关键字来声明变量属性,在类型声明后加上 `{ set get }` 来表示属性是可读可写的,可读属性则用 `{ get }` 来表示:
协议总是用 `var` 关键字来声明变量属性,在类型声明后加上 `{ set get }` 来表示属性是*可读可写*的,*可读*属性则用 `{ get }` 来表示:
```swift
protocol SomeProtocol {
@ -84,7 +45,7 @@ protocol SomeProtocol {
}
```
在协议中定义类型属性时,总是使用 `static` 关键字作为前缀。当类类型采纳协议时,除了 `static` 关键字,还可以使用 `class` 关键字来声明类型属性:
在协议中定义类型属性时,总是使用 `static` 关键字作为前缀。当类类型遵循协议时,除了 `static` 关键字,还可以使用 `class` 关键字来声明类型属性:
```swift
protocol AnotherProtocol {
@ -100,23 +61,23 @@ protocol FullyNamed {
}
```
`FullyNamed` 协议除了要求采纳协议的类型提供 `fullName` 属性外,并没有其他特别的要求。这个协议表示,任何采纳 `FullyNamed` 的类型,都必须有一个可读的 `String` 类型的实例属性 `fullName`
`FullyNamed` 协议除了要求遵循协议的类型提供 `fullName` 属性外,并没有其他特别的要求。这个协议表示,任何遵循 `FullyNamed` 的类型,都必须有一个可读的 `String` 类型的实例属性 `fullName`
下面是一个采纳 `FullyNamed` 协议的简单结构体:
下面是一个遵循 `FullyNamed` 协议的简单结构体:
```swift
struct Person: FullyNamed {
var fullName: String
var fullName: String
}
let john = Person(fullName: "John Appleseed")
// john.fullName 为 "John Appleseed"
```
这个例子中定义了一个叫做 `Person` 的结构体,用来表示一个具有名字的人。从第一行代码可以看出,它采纳`FullyNamed` 协议。
这个例子中定义了一个叫做 `Person` 的结构体,用来表示一个具有名字的人。从第一行代码可以看出,它遵循`FullyNamed` 协议。
`Person` 结构体的每一个实例都有一个 `String` 类型的存储型属性 `fullName`。这正好满足了 `FullyNamed` 协议的要求,也就意味着 `Person` 结构体正确地符合了协议。(如果协议要求未被完全满足,在编译时会报错。)
下面是一个更为复杂的类,它采纳并符合`FullyNamed` 协议:
下面是一个更为复杂的类,它采纳并遵循`FullyNamed` 协议:
```swift
class Starship: FullyNamed {
@ -131,17 +92,16 @@ class Starship: FullyNamed {
}
}
var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
// ncc1701.fullName "USS Enterprise"
// ncc1701.fullName "USS Enterprise"
```
`Starship` 类把 `fullName` 属性实现为只读的计算属性。每一个 `Starship` 类的实例都有一个名为 `name` 的非可选属性和一个名为 `prefix` 的可选属性。 当 `prefix` 存在时,计算属性 `fullName` 会将 `prefix` 插入到 `name` 之前,从而为星际飞船构建一个全名
`Starship` 类把 `fullName` 为只读的计算属性来实现。每一个 `Starship` 类的实例都有一个名为 `name` 的非可选属性和一个名为 `prefix` 的可选属性。 当 `prefix` 存在时,计算属性 `fullName` 会将 `prefix` 插入到 `name` 之前,从而得到一个带有 `prefix``fullName`
<a name="method_requirements"></a>
## 方法要求
## 方法要求 {#method-requirements}
协议可以要求采纳协议的类型实现某些指定的实例方法或类方法。这些方法作为协议的一部分,像普通方法一样放在协议的定义中,但是不需要大括号和方法体。可以在协议中定义具有可变参数的方法,和普通方法的定义方式相同。但是,不支持为协议中的方法的参数提供默认
协议可以要求遵循协议的类型实现某些指定的实例方法或类方法。这些方法作为协议的一部分,像普通方法一样放在协议的定义中,但是不需要大括号和方法体。可以在协议中定义具有可变参数的方法,和普通方法的定义方式相同。但是,不支持为协议中的方法提供默认参数
正如属性要求中所述,在协议中定义类方法的时候,总是使用 `static` 关键字作为前缀。当类类型采纳协议时,除了 `static` 关键字,还可以使用 `class` 关键字作为前缀:
正如属性要求中所述,在协议中定义类方法的时候,总是使用 `static` 关键字作为前缀。即使在类实现时,类方法要求使用 `class``static` 作为关键字前缀,前面的规则仍然适用
```swift
protocol SomeProtocol {
@ -153,26 +113,26 @@ protocol SomeProtocol {
```swift
protocol RandomNumberGenerator {
func random() -> Double
func random() -> Double
}
```
`RandomNumberGenerator` 协议要求采纳协议的类型必须拥有一个名为 `random` 返回值类型为 `Double` 的实例方法。尽管这里并未指明,但是我们假设返回值 `[0.0,1.0)` 区间内
`RandomNumberGenerator` 协议要求遵循协议的类型必须拥有一个名为 `random` 返回值类型为 `Double` 的实例方法。尽管这里并未指明,但是我们假设返回值是从 `0.0` 到(但不包括)`1.0`
`RandomNumberGenerator` 协议并不关心每一个随机数是怎样生成的,它只要求必须提供一个随机数生成器。
如下所示,下边是一个采纳并符合 `RandomNumberGenerator` 协议的类。该类实现了一个叫做 *线性同余生成器linear congruential generator* 的伪随机数算法。
如下所示,下边是一个遵循并符合 `RandomNumberGenerator` 协议的类。该类实现了一个叫做 *线性同余生成器linear congruential generator* 的伪随机数算法。
```swift
class LinearCongruentialGenerator: RandomNumberGenerator {
var lastRandom = 42.0
let m = 139968.0
let a = 3877.0
let c = 29573.0
func random() -> Double {
lastRandom = ((lastRandom * a + c) % m)
return lastRandom / m
}
var lastRandom = 42.0
let m = 139968.0
let a = 3877.0
let c = 29573.0
func random() -> Double {
lastRandom = ((lastRandom * a + c).truncatingRemainder(dividingBy:m))
return lastRandom / m
}
}
let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
@ -181,23 +141,23 @@ print("And another one: \(generator.random())")
// 打印 “And another one: 0.729023776863283”
```
<a name="mutating_method_requirements"></a>
## Mutating 方法要求
## 异变方法要求 {#mutating-method-requirements}
有时需要在方法中改变方法所属的实例。例如,在值类型(即结构体和枚举)的实例方法中,将 `mutating` 关键字作为方法的前缀,写在 `func` 关键字之前,表示可以在该方法中修改它所属的实例以及实例的任意属性的值。这一过程在[在实例方法中修改值类型](./11_Methods.html#modifying_value_types_from_within_instance_methods)章节中有详细描述。
有时需要在方法中改变(或*异变*方法所属的实例。例如,在值类型(即结构体和枚举)的实例方法中,将 `mutating` 关键字作为方法的前缀,写在 `func` 关键字之前,表示可以在该方法中修改它所属的实例以及实例的任意属性的值。这一过程在 [在实例方法中修改值类型](./11_Methods.md#modifying_value_types_from_within_instance_methods) 章节中有详细描述。
如果你在协议中定义了一个实例方法,该方法会改变采纳该协议的类型的实例,那么在定义协议时需要在方法前加 `mutating` 关键字。这使得结构体和枚举能够采纳此协议并满足此方法要求。
如果你在协议中定义了一个实例方法,该方法会改变遵循该协议的类型的实例,那么在定义协议时需要在方法前加 `mutating` 关键字。这使得结构体和枚举能够遵循此协议并满足此方法要求。
> 注意
> 注意
>
> 实现协议中的 `mutating` 方法时,若是类类型,则不用写 `mutating` 关键字。而对于结构体和枚举,则必须写 `mutating` 关键字。
如下所示,`Togglable` 协议只要求实现一个名为 `toggle` 的实例方法。根据名称的暗示`toggle()` 方法将改变实例属性,从而切换采纳该协议类型的实例的状态。
如下所示,`Togglable` 协议只定义了一个名为 `toggle` 的实例方法。顾名思义`toggle()` 方法将改变实例属性,从而切换遵循该协议类型的实例的状态。
`toggle()` 方法在定义的时候,使用 `mutating` 关键字标记,这表明当它被调用时,该方法将会改变采纳协议的类型的实例:
`toggle()` 方法在定义的时候,使用 `mutating` 关键字标记,这表明当它被调用时,该方法将会改变遵循协议的类型的实例:
```swift
protocol Togglable {
mutating func toggle()
mutating func toggle()
}
```
@ -207,25 +167,24 @@ protocol Togglable {
```swift
enum OnOffSwitch: Togglable {
case Off, On
mutating func toggle() {
switch self {
case Off:
self = On
case On:
self = Off
}
}
case off, on
mutating func toggle() {
switch self {
case .off:
self = .on
case .on:
self = .off
}
}
}
var lightSwitch = OnOffSwitch.Off
var lightSwitch = OnOffSwitch.off
lightSwitch.toggle()
// lightSwitch 现在的值为 .On
// lightSwitch 现在的值为 .on
```
<a name="initializer_requirements"></a>
## 构造器要求
## 构造器要求 {#initializer-requirements}
协议可以要求采纳协议的类型实现指定的构造器。你可以像编写普通构造器那样,在协议的定义里写下构造器的声明,但不需要写花括号和构造器的实体:
协议可以要求遵循协议的类型实现指定的构造器。你可以像编写普通构造器那样,在协议的定义里写下构造器的声明,但不需要写花括号和构造器的实体:
```swift
protocol SomeProtocol {
@ -233,9 +192,9 @@ protocol SomeProtocol {
}
```
### 构造器要求在类中的实现
### 协议构造器要求的类实现 {#class-implementations-of-protocol-initializer-requirements}
你可以在采纳协议的类中实现构造器,无论是作为指定构造器,还是作为便利构造器。无论哪种情况,你都必须为构造器实现标上 `required` 修饰符:
你可以在遵循协议的类中实现构造器,无论是作为指定构造器,还是作为便利构造器。无论哪种情况,你都必须为构造器实现标上 `required` 修饰符:
```swift
class SomeClass: SomeProtocol {
@ -247,10 +206,11 @@ class SomeClass: SomeProtocol {
使用 `required` 修饰符可以确保所有子类也必须提供此构造器实现,从而也能符合协议。
关于 `required` 构造器的更多内容,请参考[必要构造器](./14_Initialization.html#required_initializers)。
关于 `required` 构造器的更多内容,请参考 [必要构造器](./14_Initialization.md#required_initializers)。
> 注意
> 如果类已经被标记为 `final`,那么不需要在协议构造器的实现中使用 `required` 修饰符,因为 `final` 类不能有子类。关于 `final` 修饰符的更多内容,请参见[防止重写](./13_Inheritance.html#preventing_overrides)。
> 注意
>
> 如果类已经被标记为 `final`,那么不需要在协议构造器的实现中使用 `required` 修饰符,因为 `final` 类不能有子类。关于 `final` 修饰符的更多内容,请参见 [防止重写](./13_Inheritance.md#preventing_overrides)。
如果一个子类重写了父类的指定构造器,并且该构造器满足了某个协议的要求,那么该构造器的实现需要同时标注 `required``override` 修饰符:
@ -266,7 +226,7 @@ class SomeSuperClass {
}
class SomeSubClass: SomeSuperClass, SomeProtocol {
// 因为采纳协议,需要加上 required
// 因为遵循协议,需要加上 required
// 因为继承自父类,需要加上 override
required override init() {
// 这里是构造器的实现部分
@ -274,16 +234,15 @@ class SomeSubClass: SomeSuperClass, SomeProtocol {
}
```
### 可失败构造器要求
### 可失败构造器要求 {#failable-initializer-requirements}
协议还可以为采纳协议的类型定义可失败构造器要求,详见[可失败构造器](./14_Initialization.html#failable_initializers)。
协议还可以为遵循协议的类型定义可失败构造器要求,详见 [可失败构造器](./14_Initialization.md#failable_initializers)。
采纳协议的类型可以通过可失败构造器(`init?`)或非可失败构造器(`init`)来满足协议中定义的可失败构造器要求。协议中定义的非可失败构造器要求可以通过非可失败构造器(`init`)或隐式解包可失败构造器(`init!`)来满足。
遵循协议的类型可以通过可失败构造器(`init?`)或非可失败构造器(`init`)来满足协议中定义的可失败构造器要求。协议中定义的非可失败构造器要求可以通过非可失败构造器(`init`)或隐式解包可失败构造器(`init!`)来满足。
<a name="protocols_as_types"></a>
## 协议作为类型
## 协议作为类型 {#protocols-as-types}
尽管协议本身并未实现任何功能,但是协议可以被当做一个成熟的类型来使用。
尽管协议本身并未实现任何功能,但是协议可以被当做一个功能完备的类型来使用。协议作为类型使用,有时被称作「存在类型」,这个名词来自「存在着一个类型 T该类型遵循协议 T」。
协议可以像其他普通类型一样使用,使用场景如下:
@ -291,39 +250,40 @@ class SomeSubClass: SomeSuperClass, SomeProtocol {
* 作为常量、变量或属性的类型
* 作为数组、字典或其他容器中的元素类型
> 注意
> 注意
>
> 协议是一种类型,因此协议类型的名称应与其他类型(例如 `Int``Double``String`)的写法相同,使用大写字母开头的驼峰式写法,例如(`FullyNamed``RandomNumberGenerator`)。
下面是将协议作为类型使用的例子:
```swift
class Dice {
let sides: Int
let generator: RandomNumberGenerator
init(sides: Int, generator: RandomNumberGenerator) {
self.sides = sides
self.generator = generator
}
func roll() -> Int {
return Int(generator.random() * Double(sides)) + 1
}
let sides: Int
let generator: RandomNumberGenerator
init(sides: Int, generator: RandomNumberGenerator) {
self.sides = sides
self.generator = generator
}
func roll() -> Int {
return Int(generator.random() * Double(sides)) + 1
}
}
```
例子中定义了一个 `Dice` 类,用来代表桌游中拥有 N 个面的骰子。`Dice` 的实例含有 `sides``generator` 两个属性,前者是整型,用来表示骰子有几个面,后者为骰子提供一个随机数生成器,从而生成随机点数。
`generator` 属性的类型为 `RandomNumberGenerator`,因此任何采纳`RandomNumberGenerator` 协议的类型的实例都可以赋值给 `generator`,除此之外并无其他要求。
`generator` 属性的类型为 `RandomNumberGenerator`,因此任何遵循`RandomNumberGenerator` 协议的类型的实例都可以赋值给 `generator`,除此之外并无其他要求。并且由于其类型是 `RandomNumberGenerator`,在 `Dice` 类中与 `generator` 交互的代码,必须适用于所有 `generator` 实例都遵循的方法。这句话的意思是不能使用由 `generator` 底层类型提供的任何方法或属性。但是你可以通过向下转型,从协议类型转换成底层实现类型,比如从父类向下转型为子类。请参考 [向下转型](./18_Type_Casting#downcasting)。
`Dice` 类还有一个构造器,用来设置初始状态。构造器有一个名为 `generator`,类型为 `RandomNumberGenerator` 的形参。在调用构造方法创建 `Dice` 的实例时,可以传入任何采纳 `RandomNumberGenerator` 协议的实例给 `generator`
`Dice` 类还有一个构造器,用来设置初始状态。构造器有一个名为 `generator`,类型为 `RandomNumberGenerator` 的形参。在调用构造方法创建 `Dice` 的实例时,可以传入任何遵循 `RandomNumberGenerator` 协议的实例给 `generator`
`Dice` 类提供了一个名为 `roll` 的实例方法,用来模拟骰子的面值。它先调用 `generator``random()` 方法来生成一个 `[0.0,1.0)` 区间内的随机数,然后使用这个随机数生成正确的骰子面值。因为 `generator` 采纳`RandomNumberGenerator` 协议,可以确保它有个 `random()` 方法可供调用。
`Dice` 类提供了一个名为 `roll` 的实例方法,用来模拟骰子的面值。它先调用 `generator``random()` 方法来生成一个 `[0.0,1.0)` 区间内的随机数,然后使用这个随机数生成正确的骰子面值。因为 `generator` 遵循`RandomNumberGenerator` 协议,可以确保它有个 `random()` 方法可供调用。
下面的例子展示了如何使用 `LinearCongruentialGenerator` 的实例作为随机数生成器来创建一个六面骰子:
```swift
var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
for _ in 1...5 {
print("Random dice roll is \(d6.roll())")
print("Random dice roll is \(d6.roll())")
}
// Random dice roll is 3
// Random dice roll is 5
@ -332,66 +292,66 @@ for _ in 1...5 {
// Random dice roll is 4
```
<a name="delegation"></a>
## 委托(代理)模式
## 委托 {#delegation}
委托是一种设计模式,它允许类或结构体将一些需要它们负责的功能委托给其他类型的实例。委托模式的实现很简单:定义协议来封装那些需要被委托的功能,这样就能确保采纳协议的类型能提供这些功能。委托模式可以用来响应特定的动作,或者接收外部数据源提供的数据,而无需关心外部数据源的类型。
*委托*是一种设计模式,它允许类或结构体将一些需要它们负责的功能委托给其他类型的实例。委托模式的实现很简单:定义协议来封装那些需要被委托的功能,这样就能确保遵循协议的类型能提供这些功能。委托模式可以用来响应特定的动作,或者接收外部数据源提供的数据,而无需关心外部数据源的类型。
下面的例子定义了两个基于骰子游戏的协议:
```swift
protocol DiceGame {
var dice: Dice { get }
func play()
var dice: Dice { get }
func play()
}
protocol DiceGameDelegate {
func gameDidStart(game: DiceGame)
func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll:Int)
func gameDidEnd(game: DiceGame)
func gameDidStart(_ game: DiceGame)
func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
func gameDidEnd(_ game: DiceGame)
}
```
`DiceGame` 协议可以被任意涉及骰子的游戏采纳。`DiceGameDelegate` 协议可以被任意类型采纳,用来追踪 `DiceGame` 的游戏过程
`DiceGame` 协议可以被任意涉及骰子的游戏遵循
如下所示,`SnakesAndLadders` 是 [控制流](./05_Control_Flow.html) 章节引入的蛇梯棋游戏的新版本。新版本使用 `Dice` 实例作为骰子,并且实现了 `DiceGame``DiceGameDelegate` 协议,后者用来记录游戏的过程:
`DiceGameDelegate` 协议可以被任意类型遵循,用来追踪 `DiceGame` 的游戏过程。为了防止强引用导致的循环引用问题,可以把协议声明为弱引用,更多相关的知识请看 [类实例之间的循环强引用](./23_Automatic_Reference_Counting.md#strong_reference_cycles_between_class_instances),当协议标记为类专属可以使 `SnakesAndLadders` 类在声明协议时强制要使用弱引用。若要声明类专属的协议就必须继承于 `AnyObject` ,更多请看 [类专属的协议](#class_only_protocol)。
如下所示,`SnakesAndLadders` 是 [控制流](./05_Control_Flow.md) 章节引入的蛇梯棋游戏的新版本。新版本使用 `Dice` 实例作为骰子,并且实现了 `DiceGame``DiceGameDelegate` 协议,后者用来记录游戏的过程:
```swift
class SnakesAndLadders: DiceGame {
let finalSquare = 25
let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
var square = 0
var board: [Int]
init() {
board = [Int](count: finalSquare + 1, repeatedValue: 0)
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
}
var delegate: DiceGameDelegate?
func play() {
square = 0
delegate?.gameDidStart(self)
gameLoop: while square != finalSquare {
let diceRoll = dice.roll()
delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)
switch square + diceRoll {
case finalSquare:
break gameLoop
case let newSquare where newSquare > finalSquare:
continue gameLoop
default:
square += diceRoll
square += board[square]
}
}
delegate?.gameDidEnd(self)
}
let finalSquare = 25
let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
var square = 0
var board: [Int]
init() {
board = Array(repeating: 0, count: finalSquare + 1)
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
}
var delegate: DiceGameDelegate?
func play() {
square = 0
delegate?.gameDidStart(self)
gameLoop: while square != finalSquare {
let diceRoll = dice.roll()
delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)
switch square + diceRoll {
case finalSquare:
break gameLoop
case let newSquare where newSquare > finalSquare:
continue gameLoop
default:
square += diceRoll
square += board[square]
}
}
delegate?.gameDidEnd(self)
}
}
```
关于这个蛇梯棋游戏的详细描述请参阅 [控制流](./05_Control_Flow.html) 章节中的 [Break](./05_Control_Flow.html#break) 部分
关于这个*蛇梯棋*游戏的详细描述请参阅 [中断(Break](./05_Control_Flow.md#break)。
这个版本的游戏封装到了 `SnakesAndLadders` 类中,该类采纳`DiceGame` 协议,并且提供了相应的可读的 `dice` 属性和 `play()` 方法。( `dice` 属性在构造之后就不再改变,且协议只要求 `dice` 为可读的,因此将 `dice` 声明为常量属性。)
这个版本的游戏封装到了 `SnakesAndLadders` 类中,该类遵循`DiceGame` 协议,并且提供了相应的可读的 `dice` 属性和 `play()` 方法。( `dice` 属性在构造之后就不再改变,且协议只要求 `dice` 为可读的,因此将 `dice` 声明为常量属性。)
游戏使用 `SnakesAndLadders` 类的 `init()` 构造器来初始化游戏。所有的游戏逻辑被转移到了协议中的 `play()` 方法,`play()` 方法使用协议要求的 `dice` 属性提供骰子摇出的值。
@ -401,23 +361,23 @@ class SnakesAndLadders: DiceGame {
因为 `delegate` 是一个 `DiceGameDelegate` 类型的可选属性,因此在 `play()` 方法中通过可选链式调用来调用它的方法。若 `delegate` 属性为 `nil`,则调用方法会优雅地失败,并不会产生错误。若 `delegate` 不为 `nil`,则方法能够被调用,并传递 `SnakesAndLadders` 实例作为参数。
如下示例定义了 `DiceGameTracker` 类,它采纳`DiceGameDelegate` 协议:
如下示例定义了 `DiceGameTracker` 类,它遵循`DiceGameDelegate` 协议:
```swift
class DiceGameTracker: DiceGameDelegate {
var numberOfTurns = 0
func gameDidStart(game: DiceGame) {
func gameDidStart(_ game: DiceGame) {
numberOfTurns = 0
if game is SnakesAndLadders {
print("Started a new game of Snakes and Ladders")
}
print("The game is using a \(game.dice.sides)-sided dice")
}
func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
numberOfTurns += 1
print("Rolled a \(diceRoll)")
}
func gameDidEnd(game: DiceGame) {
func gameDidEnd(_ game: DiceGame) {
print("The game lasted for \(numberOfTurns) turns")
}
}
@ -425,7 +385,7 @@ class DiceGameTracker: DiceGameDelegate {
`DiceGameTracker` 实现了 `DiceGameDelegate` 协议要求的三个方法,用来记录游戏已经进行的轮数。当游戏开始时,`numberOfTurns` 属性被赋值为 `0`,然后在每新一轮中递增,游戏结束后,打印游戏的总轮数。
`gameDidStart(_:)` 方法从 `game` 参数获取游戏信息并打印。`game` 参数是 `DiceGame` 类型而不是 `SnakeAndLadders` 类型,所以在方法中只能访问 `DiceGame` 协议中的内容。当然了,`SnakeAndLadders` 的方法也可以在类型转换之后调用。在上例代码中,通过 `is` 操作符检查 `game` 是否为 `SnakesAndLadders` 类型的实例,如果是,则打印出相应的消息。
`gameDidStart(_:)` 方法从 `game` 参数获取游戏信息并打印。`game` 参数是 `DiceGame` 类型而不是 `SnakeAndLadders` 类型,所以在 `gameDidStart(_:)` 方法中只能访问 `DiceGame` 协议中的内容。当然了,`SnakeAndLadders` 的方法也可以在类型转换之后调用。在上例代码中,通过 `is` 操作符检查 `game` 是否为 `SnakesAndLadders` 类型的实例,如果是,则打印出相应的消息。
无论当前进行的是何种游戏,由于 `game` 符合 `DiceGame` 协议,可以确保 `game` 含有 `dice` 属性。因此在 `gameDidStart(_:)` 方法中可以通过传入的 `game` 参数来访问 `dice` 属性,进而打印出 `dice``sides` 属性的值。
@ -445,13 +405,13 @@ game.play()
// The game lasted for 4 turns
```
<a name="adding_protocol_conformance_with_an_extension"></a>
## 通过扩展添加协议一致性
## 在扩展里添加协议遵循 {#adding-protocol-conformance-with-an-extension}
即便无法修改源代码,依然可以通过扩展令已有类型采纳并符合协议。扩展可以为已有类型添加属性、方法、下标以及构造器,因此可以符合协议中的相应要求。详情请在[扩展](./21_Extensions.html)章节中查看。
即便无法修改源代码,依然可以通过扩展令已有类型遵循并符合协议。扩展可以为已有类型添加属性、方法、下标以及构造器,因此可以符合协议中的相应要求。详情请在 [扩展](./20_Extensions.md) 章节中查看。
> 注意
> 通过扩展令已有类型采纳并符合协议时,该类型的所有实例也会随之获得协议中定义的各项功能。
> 注意
>
> 通过扩展令已有类型遵循并符合协议时,该类型的所有实例也会随之获得协议中定义的各项功能。
例如下面这个 `TextRepresentable` 协议,任何想要通过文本表示一些内容的类型都可以实现该协议。这些想要表示的内容可以是实例本身的描述,也可以是实例当前状态的文本描述:
@ -461,17 +421,17 @@ protocol TextRepresentable {
}
```
可以通过扩展,令先前提到的 `Dice`采纳并符合 `TextRepresentable` 协议:
可以通过扩展,令先前提到的 `Dice`可以扩展来采纳和遵循 `TextRepresentable` 协议:
```swift
extension Dice: TextRepresentable {
var textualDescription: String {
return "A \(sides)-sided dice"
}
return "A \(sides)-sided dice"
}
}
```
通过扩展采纳并符合协议,和在原始定义中采纳并符合协议的效果完全相同。协议名称写在类型名之后,以冒号隔开,然后在扩展的大括号内实现协议要求的内容。
通过扩展遵循并采纳协议,和在原始定义中遵循并符合协议的效果完全相同。协议名称写在类型名之后,以冒号隔开,然后在扩展的大括号内实现协议要求的内容。
现在所有 `Dice` 的实例都可以看做 `TextRepresentable` 类型:
@ -481,22 +441,39 @@ print(d12.textualDescription)
// 打印 “A 12-sided dice”
```
同样,`SnakesAndLadders` 类也可以通过扩展采纳并符合 `TextRepresentable` 协议:
同样,`SnakesAndLadders` 类也可以通过扩展采纳和遵循 `TextRepresentable` 协议:
```swift
extension SnakesAndLadders: TextRepresentable {
var textualDescription: String {
return "A game of Snakes and Ladders with \(finalSquare) squares"
}
return "A game of Snakes and Ladders with \(finalSquare) squares"
}
}
print(game.textualDescription)
// 打印 “A game of Snakes and Ladders with 25 squares”
```
<a name="declaring_protocol_adoption_with_an_extension"></a>
## 通过扩展采纳协议
## 有条件地遵循协议 {#Conditionally-Conforming-to-a-Protocol}
当一个类型已经符合了某个协议中的所有要求,却还没有声明采纳该协议时可以通过扩展体的扩展来采纳协议
泛型类型可能只在某些情况下满足一个协议的要求,比如当类型的泛型形式参数遵循对应协议时。你可以通过扩展类型时列出限制让泛型类型有条件地遵循某协议。在你采纳协议的名字后面写泛型 `where` 分句。更多关于泛型 `where` 分句,见 [泛型 Where 分句](./22_Generics.md##where_clauses)。
下面的扩展让 `Array` 类型只要在存储遵循 `TextRepresentable` 协议的元素时就遵循 `TextRepresentable` 协议。
```swift
extension Array: TextRepresentable where Element: TextRepresentable {
var textualDescription: String {
let itemsAsText = self.map { $0.textualDescription }
return "[" + itemsAsText.joined(separator: ", ") + "]"
}
}
let myDice = [d6, d12]
print(myDice.textualDescription)
// 打印 "[A 6-sided dice, A 12-sided dice]"
```
## 在扩展里声明采纳协议 {#declaring-protocol-adoption-with-an-extension}
当一个类型已经符合了某个协议中的所有要求,却还没有声明采纳该协议时,可以通过空的扩展来让它采纳该协议:
```swift
struct Hamster {
@ -517,13 +494,13 @@ print(somethingTextRepresentable.textualDescription)
// 打印 “A hamster named Simon”
```
> 注意
> 即使满足了协议的所有要求,类型也不会自动采纳协议,必须显式地采纳协议。
> 注意
>
> 即使满足了协议的所有要求,类型也不会自动遵循协议,必须显式地遵循协议。
<a name="collections_of_protocol_types"></a>
## 协议类型的集合
## 协议类型的集合 {#collections-of-protocol-types}
协议类型可以在数组或者字典这样的集合中使用,在[协议类型](./22_Protocols.html##protocols_as_types)提到了这样的用法。下面的例子创建了一个元素类型为 `TextRepresentable` 的数组:
协议类型可以在数组或者字典这样的集合中使用,在 [协议类型](./21_Protocols.md##protocols_as_types) 提到了这样的用法。下面的例子创建了一个元素类型为 `TextRepresentable` 的数组:
```swift
let things: [TextRepresentable] = [game, d12, simonTheHamster]
@ -533,19 +510,18 @@ let things: [TextRepresentable] = [game, d12, simonTheHamster]
```swift
for thing in things {
print(thing.textualDescription)
print(thing.textualDescription)
}
// A game of Snakes and Ladders with 25 squares
// A 12-sided dice
// A hamster named Simon
```
`thing``TextRepresentable` 类型而不是 `Dice``DiceGame``Hamster` 等类型,即使实例在幕后确实是这些类型中的一种。由于 `thing``TextRepresentable` 类型,任何 `TextRepresentable` 的实例都有一个 `textualDescription` 属性,所以在每次循环中可以安全地访问 `thing.textualDescription`
注意 `thing` 常量`TextRepresentable` 类型而不是 `Dice``DiceGame``Hamster` 等类型,即使实例在幕后确实是这些类型中的一种。由于 `thing``TextRepresentable` 类型,任何 `TextRepresentable` 的实例都有一个 `textualDescription` 属性,所以在每次循环中可以安全地访问 `thing.textualDescription`
<a name="protocol_inheritance"></a>
## 协议的继承
## 协议的继承 {#protocol-inheritance}
协议能够继承一个或多个其他协议,可以在继承的协议的基础上增加新的要求。协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔:
协议能够*继承*一个或多个其他协议,可以在继承的协议的基础上增加新的要求。协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔:
```swift
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
@ -557,13 +533,13 @@ protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
```swift
protocol PrettyTextRepresentable: TextRepresentable {
var prettyTextualDescription: String { get }
var prettyTextualDescription: String { get }
}
```
例子中定义了一个新的协议 `PrettyTextRepresentable`,它继承自 `TextRepresentable` 协议。任何采纳 `PrettyTextRepresentable` 协议的类型在满足该协议的要求时,也必须满足 `TextRepresentable` 协议的要求。在这个例子中,`PrettyTextRepresentable` 协议额外要求采纳协议的类型提供一个返回值为 `String` 类型的 `prettyTextualDescription` 属性。
例子中定义了一个新的协议 `PrettyTextRepresentable`,它继承自 `TextRepresentable` 协议。任何遵循 `PrettyTextRepresentable` 协议的类型在满足该协议的要求时,也必须满足 `TextRepresentable` 协议的要求。在这个例子中,`PrettyTextRepresentable` 协议额外要求遵循协议的类型提供一个返回值为 `String` 类型的 `prettyTextualDescription` 属性。
如下所示,扩展 `SnakesAndLadders`,使其采纳并符合 `PrettyTextRepresentable` 协议:
如下所示,扩展 `SnakesAndLadders`,使其遵循并符合 `PrettyTextRepresentable` 协议:
```swift
extension SnakesAndLadders: PrettyTextRepresentable {
@ -584,7 +560,7 @@ extension SnakesAndLadders: PrettyTextRepresentable {
}
```
上述扩展令 `SnakesAndLadders` 采纳`PrettyTextRepresentable` 协议,并提供了协议要求的 `prettyTextualDescription` 属性。每个 `PrettyTextRepresentable` 类型同时也是 `TextRepresentable` 类型,所以在 `prettyTextualDescription` 的实现中,可以访问 `textualDescription` 属性。然后,拼接上了冒号和换行符。接着,遍历数组中的元素,拼接一个几何图形来表示每个棋盘方格的内容:
上述扩展令 `SnakesAndLadders` 遵循`PrettyTextRepresentable` 协议,并提供了协议要求的 `prettyTextualDescription` 属性。每个 `PrettyTextRepresentable` 类型同时也是 `TextRepresentable` 类型,所以在 `prettyTextualDescription` 的实现中,可以访问 `textualDescription` 属性。然后,拼接上了冒号和换行符。接着,遍历数组中的元素,拼接一个几何图形来表示每个棋盘方格的内容:
* 当从数组中取出的元素的值大于 `0` 时,用 `▲` 表示。
* 当从数组中取出的元素的值小于 `0` 时,用 `▼` 表示。
@ -598,26 +574,27 @@ print(game.prettyTextualDescription)
// ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○
```
<a name="class_only_protocol"></a>
## 类类型专属协议
## 类专属的协议 {#class-only-protocol}
可以在协议的继承列表中,通过添加 `class` 关键字来限制协议只能被类类型采纳,而结构体或枚举不能采纳该协议。`class` 关键字必须第一个出现在协议的继承列表中,在其他继承的协议之前:
通过添加 `AnyObject` 关键字到协议的继承列表,就可以限制协议只能被类类型采纳(以及非结构体或者非枚举的类型)。
```swift
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
// 这里是类类型专属协议的定义部分
protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
// 这里是类专属协议的定义部分
}
```
在以上例子中,协议 `SomeClassOnlyProtocol` 只能被类类型采纳。如果尝试让结构体或枚举类型采纳该协议,则会导致编译错误。
在以上例子中,协议 `SomeClassOnlyProtocol` 只能被类类型采纳。如果尝试让结构体或枚举类型采纳 `SomeClassOnlyProtocol`,则会导致编译错误。
> 注意
> 当协议定义的要求需要采纳协议的类型必须是引用语义而非值语义时,应该采用类类型专属协议。关于引用语义和值语义的更多内容,请查看[结构体和枚举是值类型](./09_Classes_and_Structures.html#structures_and_enumerations_are_value_types)和[类是引用类型](./09_Classes_and_Structures.html#classes_are_reference_types)。
> 注意
>
> 当协议定义的要求需要遵循协议的类型必须是引用语义而非值语义时,应该采用类类型专属协议。关于引用语义和值语义的更多内容,请查看 [结构体和枚举是值类型](./09_Classes_and_Structures.md#structures_and_enumerations_are_value_types) 和 [类是引用类型](./09_Classes_and_Structures.md#classes_are_reference_types)。
<a name="protocol_composition"></a>
## 协议合成
## 协议合成 {#protocol-composition}
有时候需要同时采纳多个协议,你可以将多个协议采用 `SomeProtocol & AnotherProtocol` 这样的格式进行组合,称为 *协议合成protocol composition*。你可以罗列任意多个你想要采纳的协议,以与符号(`&`)分隔
要求一个类型同时遵循多个协议是很有用的。你可以使用*协议组合*来复合多个协议到一个要求里。协议组合行为就和你定义的临时局部协议一样拥有构成中所有协议的需求。协议组合不定义任何新的协议类型
协议组合使用 `SomeProtocol & AnotherProtocol` 的形式。你可以列举任意数量的协议,用和符号(`&`)分开。除了协议列表,协议组合也能包含类类型,这允许你标明一个需要的父类。
下面的例子中,将 `Named``Aged` 两个协议按照上述语法组合成一个协议,作为函数参数的类型:
@ -642,21 +619,48 @@ wishHappyBirthday(to: birthdayPerson)
`Named` 协议包含 `String` 类型的 `name` 属性。`Aged` 协议包含 `Int` 类型的 `age` 属性。`Person` 结构体采纳了这两个协议。
`wishHappyBirthday(_:)` 函数的参数 `celebrator` 的类型为 `Named & Aged`这意味着它不关心参数的具体类型,只要参数符合这两个协议即可。
`wishHappyBirthday(to:)` 函数的参数 `celebrator` 的类型为 `Named & Aged` 这意味着“任何同时遵循 Named 和 Aged 的协议”。它不关心参数的具体类型,只要参数符合这两个协议即可。
上面的例子创建了一个名为 `birthdayPerson``Person` 的实例,作为参数传递给了 `wishHappyBirthday(_:)` 函数。因为 `Person` 同时符合这两个协议,所以这个参数合法,函数将打印生日问候语。
上面的例子创建了一个名为 `birthdayPerson``Person` 的实例,作为参数传递给了 `wishHappyBirthday(to:)` 函数。因为 `Person` 同时符合这两个协议,所以这个参数合法,函数将打印生日问候语。
> 注意
> 协议合成并不会生成新的、永久的协议类型,而是将多个协议中的要求合成到一个只在局部作用域有效的临时协议中。
这里有一个例子:将 Location 类和前面的 Named 协议进行组合:
<a name="checking_for_protocol_conformance"></a>
## 检查协议一致性
```swift
class Location {
var latitude: Double
var longitude: Double
init(latitude: Double, longitude: Double) {
self.latitude = latitude
self.longitude = longitude
}
}
class City: Location, Named {
var name: String
init(name: String, latitude: Double, longitude: Double) {
self.name = name
super.init(latitude: latitude, longitude: longitude)
}
}
func beginConcert(in location: Location & Named) {
print("Hello, \(location.name)!")
}
let seattle = City(name: "Seattle", latitude: 47.6, longitude: -122.3)
beginConcert(in: seattle)
// 打印 "Hello, Seattle!"
```
你可以使用[类型转换](./20_Type_Casting.html)中描述的 `is``as` 操作符来检查协议一致性,即是否符合某协议,并且可以转换到指定的协议类型。检查和转换到某个协议类型在语法上和类型的检查和转换完全相同:
`beginConcert(in:)` 函数接受一个类型为 `Location & Named` 的参数,这意味着“任何 Location 的子类,并且遵循 Named 协议”。例如City 就满足这样的条件。
* `is` 用来检查实例是否符合某个协议,若符合则返回 `true`,否则返回 `false`
* `as?` 返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回 `nil`
* `as!` 将实例强制向下转换到某个协议类型,如果强转失败,会引发运行时错误。
将 birthdayPerson 传入 `beginConcert(in:)` 函数是不合法的,因为 Person 不是 Location 的子类。同理,如果你新建一个类继承于 Location但是没有遵循 Named 协议,而用这个类的实例去调用 `beginConcert(in:)` 函数也是非法的
## 检查协议一致性 {#checking-for-protocol-conformance}
你可以使用 [类型转换](./18_Type_Casting.md) 中描述的 `is``as` 操作符来检查协议一致性,即是否符合某协议,并且可以转换到指定的协议类型。检查和转换协议的语法与检查和转换类型是完全一样的:
* `is` 用来检查实例是否符合某个协议,若符合则返回 `true`,否则返回 `false`
* `as?` 返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回 `nil`
* `as!` 将实例强制向下转换到某个协议类型,如果强转失败,将触发运行时错误。
下面的例子定义了一个 `HasArea` 协议,该协议定义了一个 `Double` 类型的可读属性 `area`
@ -666,7 +670,7 @@ protocol HasArea {
}
```
如下所示,`Circle` 类和 `Country` 类都采纳`HasArea` 协议:
如下所示,`Circle` 类和 `Country` 类都遵循`HasArea` 协议:
```swift
class Circle: HasArea {
@ -681,14 +685,14 @@ class Country: HasArea {
}
```
`Circle` 类把 `area` 属性实现为基于存储型属性 `radius` 的计算型属性。`Country` 类则把 `area` 属性实现为存储型属性。这两个类都正确地符合`HasArea` 协议。
`Circle` 类把 `area` 属性实现为基于存储型属性 `radius` 的计算型属性。`Country` 类则把 `area` 属性实现为存储型属性。这两个类都正确地遵循`HasArea` 协议。
如下所示,`Animal` 是一个未采纳 `HasArea` 协议的类:
如下所示,`Animal` 是一个未遵循 `HasArea` 协议的类:
```swift
class Animal {
var legs: Int
init(legs: Int) { self.legs = legs }
var legs: Int
init(legs: Int) { self.legs = legs }
}
```
@ -696,9 +700,9 @@ class Animal {
```swift
let objects: [AnyObject] = [
Circle(radius: 2.0),
Country(area: 243_610),
Animal(legs: 4)
Circle(radius: 2.0),
Country(area: 243_610),
Animal(legs: 4)
]
```
@ -723,31 +727,28 @@ for object in objects {
`objects` 数组中的元素的类型并不会因为强转而丢失类型信息,它们仍然是 `Circle``Country``Animal` 类型。然而,当它们被赋值给 `objectWithArea` 常量时,只被视为 `HasArea` 类型,因此只有 `area` 属性能够被访问。
<a name="optional_protocol_requirements"></a>
## 可选的协议要求
## 可选的协议要求 {#optional-protocol-requirements}
协议可以定义可选要求,采纳协议的类型可以选择是否实现这些要求。在协议中使用 `optional` 关键字作为前缀来定义可选要求。使用可选要求时(例如,可选的方法或者属性),它们的类型会自动变成可选的。比如,一个类型为 `(Int) -> String` 的方法会变成 `((Int) -> String)?`。需要注意的是整个函数类型是可选的,而不是函数的返回值
协议可以定义*可选要求*,遵循协议的类型可以选择是否实现这些要求。在协议中使用 `optional` 关键字作为前缀来定义可选要求。可选要求用在你需要和 Objective-C 打交道的代码中。协议和可选要求都必须带上 `@objc` 属性。标记 `@objc` 特性的协议只能被继承自 Objective-C 类的类或者 `@objc` 类遵循,其他类以及结构体和枚举均不能遵循这种协议
协议中的可选要求可通过可选链式调用来使用,因为采纳协议的类型可能没有实现这些可选要求。类似 `someOptionalMethod?(someArgument)` 这样,你可以在可选方法名称后加上 `?` 来调用可选方法。详细内容可在[可选链式调用](./17_Optional_Chaining.html)章节中查看
使用可选要求时(例如,可选的方法或者属性),它们的类型会自动变成可选的。比如,一个类型为 `(Int) -> String` 的方法会变成 `((Int) -> String)?`。需要注意的是整个函数类型是可选的,而不是函数的返回值
> 注意
> 可选的协议要求只能用在标记 `@objc` 特性的协议中。
> 该特性表示协议将暴露给 Objective-C 代码,详情参见[`Using Swift with Cocoa and Objective-C(Swift 2.2)`](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216)。即使你不打算和 Objective-C 有什么交互,如果你想要指定可选的协议要求,那么还是要为协议加上 `@objc` 特性。
> 还需要注意的是,标记 `@objc` 特性的协议只能被继承自 Objective-C 类的类或者 `@objc` 类采纳,其他类以及结构体和枚举均不能采纳这种协议。
协议中的可选要求可通过可选链式调用来使用,因为遵循协议的类型可能没有实现这些可选要求。类似 `someOptionalMethod?(someArgument)` 这样,你可以在可选方法名称后加上 `?` 来调用可选方法。详细内容可在 [可选链式调用](./16_Optional_Chaining.md) 章节中查看。
下面的例子定义了一个名为 `Counter` 的用于整数计数的类,它使用外部的数据源来提供每次的增量。数据源由 `CounterDataSource` 协议定义,包含两个可选要求:
下面的例子定义了一个名为 `Counter` 的用于整数计数的类,它使用外部的数据源来提供每次的增量。数据源由 `CounterDataSource` 协议定义,包含两个可选要求:
```swift
@objc protocol CounterDataSource {
optional func incrementForCount(count: Int) -> Int
optional var fixedIncrement: Int { get }
@objc optional func increment(forCount count: Int) -> Int
@objc optional var fixedIncrement: Int { get }
}
```
`CounterDataSource` 协议定义了一个可选方法 `incrementForCount(_:)` 和一个可选属性 `fiexdIncrement`,它们使用了不同的方法来从数据源中获取适当的增量值。
`CounterDataSource` 协议定义了一个可选方法 `increment(forCount:)` 和一个可选属性 `fiexdIncrement`,它们使用了不同的方法来从数据源中获取适当的增量值。
> 注意
> 严格来讲,`CounterDataSource` 协议中的方法和属性都是可选的,因此采纳协议的类可以不实现这些要求,尽管技术上允许这样做,不过最好不要这样写。
> 注意
>
> 严格来讲,`CounterDataSource` 协议中的方法和属性都是可选的,因此遵循协议的类可以不实现这些要求,尽管技术上允许这样做,不过最好不要这样写。
`Counter` 类含有 `CounterDataSource?` 类型的可选属性 `dataSource`,如下所示:
@ -756,7 +757,7 @@ class Counter {
var count = 0
var dataSource: CounterDataSource?
func increment() {
if let amount = dataSource?.incrementForCount?(count) {
if let amount = dataSource?.increment?(forCount: count) {
count += amount
} else if let amount = dataSource?.fixedIncrement {
count += amount
@ -765,20 +766,19 @@ class Counter {
}
```
`Counter` 类使用变量属性 `count` 来存储当前值。该类还定义了一个 `increment()` 方法,每次调用该方法的时候,将会增加 `count` 的值。
`Counter` 类使用变量属性 `count` 来存储当前值。该类还定义了一个 `increment` 方法,每次调用该方法的时候,将会增加 `count` 的值。
`increment()` 方法首先试图使用 `incrementForCount(_:)` 方法来得到每次的增量。`increment()` 方法使用可选链式调用来尝试调用 `incrementForCount(_:)`,并将当前的 `count` 值作为参数传入。
`increment()` 方法首先试图使用 `increment(forCount:)` 方法来得到每次的增量。`increment()` 方法使用可选链式调用来尝试调用 `increment(forCount:)`,并将当前的 `count` 值作为参数传入。
这里使用了两层可选链式调用。首先,由于 `dataSource` 可能为 `nil`,因此在 `dataSource` 后边加上了 `?`,以此表明只在 `dataSource` 非空时才去调用 `incrementForCount(_:)` 方法。其次,即使 `dataSource` 存在,也无法保证其是否实现了 `incrementForCount(_:)` 方法,因为这个方法是可选的。因此,`incrementForCount(_:)` 方法同样使用可选链式调用进行调用,只有在该方法被实现的情况下才能调用它,所以在 `incrementForCount(_:)` 方法后边也加上了 `?`
这里使用了两层可选链式调用。首先,由于 `dataSource` 可能为 `nil`,因此在 `dataSource` 后边加上了 `?`,以此表明只在 `dataSource` 非空时才去调用 `increment(forCount:)` 方法。其次,即使 `dataSource` 存在,也无法保证其是否实现了 `increment(forCount:)` 方法,因为这个方法是可选的。因此,`increment(forCount:)` 方法同样使用可选链式调用进行调用,只有在该方法被实现的情况下才能调用它,所以在 `increment(forCount:)` 方法后边也加上了 `?`
调用 `increment(forCount:)` 方法在上述两种情形下都有可能失败,所以返回值为 `Int?` 类型。虽然在 `CounterDataSource` 协议中,`increment(forCount:)` 的返回值类型是非可选 `Int`。另外,即使这里使用了两层可选链式调用,最后的返回结果依旧是单层的可选类型。关于这一点的更多信息,请查阅 [连接多层可选链式调用](./16_Optional_Chaining)。
调用 `incrementForCount(_:)` 方法在上述两种情形下都有可能失败,所以返回值为 `Int?` 类型。虽然在 `CounterDataSource` 协议中,`incrementForCount(_:)` 的返回值类型是非可选 `Int`。另外,即使这里使用了两层可选链式调用,最后的返回结果依旧是单层的可选类型,即 `Int?` 而不是 `Int??`。关于这一点的更多信息,请查阅[连接多层可选链式调用](./17_Optional_Chaining)
调用 `increment(forCount:)` 方法后,`Int?` 型的返回值通过可选绑定解包并赋值给常量 `amount`。如果可选值确实包含一个数值,也就是说,数据源和方法都存在,数据源方法返回了一个有效值。之后便将解包后的 `amount` 加到 `count` 上,增量操作完成。
在调用 `incrementForCount(_:)` 方法后,`Int?` 型的返回值通过可选绑定解包并赋值给常量 `amount`。如果可选值确实包含一个数值,也就是说,数据源和方法都存在,数据源方法返回了一个有效值。之后便将解包后的 `amount` 加到 `count` 上,增量操作完成
如果没有从 `increment(forCount:)` 方法获取到值,可能由于 `dataSource``nil`,或者它并没有实现 `increment(forCount:)` 方法,那么 `increment()` 方法将试图从数据源的 `fixedIncrement` 属性中获取增量。`fixedIncrement` 是一个可选属性,因此属性值是一个 `Int?` 值,即使该属性在 `CounterDataSource` 协议中的类型是非可选的 `Int`
如果没有从 `incrementForCount(_:)` 方法获取到值,可能由于 `dataSource``nil`,或者它并没有实现 `incrementForCount(_:)` 方法,那么 `increment()` 方法将试图从数据源的 `fixedIncrement` 属性中获取增量。`fixedIncrement` 是一个可选属性,因此属性值是一个 `Int?` 值,即使该属性在 `CounterDataSource` 协议中的类型是非可选的 `Int`
下面的例子展示了 `CounterDataSource` 的简单实现。`ThreeSource` 类采纳了 `CounterDataSource` 协议,它实现了可选属性 `fixedIncrement`,每次会返回 `3`
下面的例子展示了 `CounterDataSource` 的简单实现`ThreeSource` 类遵循了 `CounterDataSource` 协议,它实现了可选属性 `fixedIncrement`,每次会返回 `3`
```swift
class ThreeSource: NSObject, CounterDataSource {
@ -801,13 +801,13 @@ for _ in 1...4 {
// 12
```
上述代码新建了一个 `Counter` 实例,并将它的数据源设置为一个 `ThreeSource` 的实例,然后调用 `increment()` 方法四次。和预期一样,每次调用都会将 `count` 的值增加 `3`.
上述代码新建了一个 `Counter` 实例,并将它的数据源设置为一个 `ThreeSource` 的实例,然后调用 `increment()` 方法 `4` 次。按照预期预期一样,每次调用都会将 `count` 的值增加 `3`.
下面是一个更为复杂的数据源 `TowardsZeroSource`,它将使得最后的值变为 `0`
```swift
@objc class TowardsZeroSource: NSObject, CounterDataSource {
func incrementForCount(count: Int) -> Int {
class TowardsZeroSource: NSObject, CounterDataSource {
func increment(forCount count: Int) -> Int {
if count == 0 {
return 0
} else if count < 0 {
@ -819,7 +819,7 @@ for _ in 1...4 {
}
```
`TowardsZeroSource` 实现了 `CounterDataSource` 协议中的 `incrementForCount(_:)` 方法,以 `count` 参数为依据,计算出每次的增量。如果 `count` 已经为 `0`,此方法返回 `0`,以此表明之后不应再有增量操作发生。
`TowardsZeroSource` 实现了 `CounterDataSource` 协议中的 `increment(forCount:)` 方法,以 `count` 参数为依据,计算出每次的增量。如果 `count` 已经为 `0`,此方法返回 `0`,以此表明之后不应再有增量操作发生。
你可以使用 `TowardsZeroSource` 实例将 `Counter` 实例来从 `-4` 增加到 `0`。一旦增加到 `0`,数值便不会再有变动:
@ -837,10 +837,9 @@ for _ in 1...5 {
// 0
```
<a name="protocol_extensions"></a>
## 协议扩展
## 协议扩展 {#protocol-extensions}
协议可以通过扩展来为采纳协议的类型提供属性、方法以及下标的实现。通过这种方式,你可以基于协议本身来实现这些功能,而无需在每个采纳协议的类型中都重复同样的实现,也无需使用全局函数。
协议可以通过扩展来为遵循协议的类型提供属性、方法以及下标的实现。通过这种方式,你可以基于协议本身来实现这些功能,而无需在每个遵循协议的类型中都重复同样的实现,也无需使用全局函数。
例如,可以扩展 `RandomNumberGenerator` 协议来提供 `randomBool()` 方法。该方法使用协议中定义的 `random()` 方法来返回一个随机的 `Bool` 值:
@ -852,7 +851,7 @@ extension RandomNumberGenerator {
}
```
通过协议扩展,所有采纳协议的类型,都能自动获得这个扩展所增加的方法实现无需任何额外修改:
通过协议扩展,所有遵循协议的类型,都能自动获得这个扩展所增加的方法实现无需任何额外修改:
```swift
let generator = LinearCongruentialGenerator()
@ -862,15 +861,15 @@ print("And here's a random Boolean: \(generator.randomBool())")
// 打印 “And here's a random Boolean: true”
```
<a name="providing_default_implementations"></a>
### 提供默认实现
### 提供默认实现 {#providing-default-implementations}
可以通过协议扩展来为协议要求的属性、方法以及下标提供默认的实现。如果采纳协议的类型为这些要求提供了自己的实现,那么这些自定义实现将会替代扩展中的默认实现被使用。
可以通过协议扩展来为协议要求的属性、方法以及下标提供默认的实现。如果遵循协议的类型为这些要求提供了自己的实现,那么这些自定义实现将会替代扩展中的默认实现被使用。
> 注意
> 通过协议扩展为协议要求提供的默认实现和可选的协议要求不同。虽然在这两种情况下,采纳协议的类型都无需自己实现这些要求,但是通过扩展提供的默认实现可以直接调用,而无需使用可选链式调用。
> 注意
>
> 通过协议扩展为协议要求提供的默认实现和可选的协议要求不同。虽然在这两种情况下,遵循协议的类型都无需自己实现这些要求,但是通过扩展提供的默认实现可以直接调用,而无需使用可选链式调用。
例如,`PrettyTextRepresentable` 协议继承自 `TextRepresentable` 协议,可以为其提供一个默认的 `prettyTextualDescription` 属性,只是简单地返回 `textualDescription` 属性的值:
例如,`PrettyTextRepresentable` 协议继承自 `TextRepresentable` 协议,可以为其提供一个默认的 `prettyTextualDescription` 属性简单地返回 `textualDescription` 属性的值:
```swift
extension PrettyTextRepresentable {
@ -880,39 +879,43 @@ extension PrettyTextRepresentable {
}
```
<a name="adding_constraints_to_protocol_extensions"></a>
### 为协议扩展添加限制条件
### 为协议扩展添加限制条件 {#adding-constraints-to-protocol-extensions}
在扩展协议的时候,可以指定一些限制条件,只有采纳协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用 `where` 子句来描述,正如[Where子句](./23_Generics.html#where_clauses)中所描述的。
在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用 `where` 子句来描述,正如 [泛型 Where 子句](./22_Generics.md#where_clauses) 中所描述的。
例如,你可以扩展 `CollectionType` 协议,但是只适用于集合中的元素采纳`TextRepresentable` 协议的情况
例如,你可以扩展 `Collection` 协议,适用于集合中的元素遵循`Equatable` 协议的情况。通过限制集合元素遵 `Equatable` 协议, 作为标准库的一部分, 你可以使用 `==``!=` 操作符来检查两个元素的等价性和非等价性。
```swift
extension CollectionType where Generator.Element: TextRepresentable {
var textualDescription: String {
let itemsAsText = self.map { $0.textualDescription }
return "[" + itemsAsText.joinWithSeparator(", ") + "]"
extension Collection where Element: Equatable {
func allEqual() -> Bool {
for element in self {
if element != self.first {
return false
}
}
return true
}
}
```
`textualDescription` 属性返回整个集合的文本描述,它将集合中的每个元素的文本描述以逗号分隔的方式连接起来,包在一对方括号中
如果集合中的所有元素都一致,`allEqual()` 方法才返回 `true`
现在我们来看看先前的 `Hamster` 结构体,它符合 `TextRepresentable` 协议,同时这里还有个装有 `Hamster` 的实例的数组
看看两个整数数组,一个数组的所有元素都是一样的,另一个不一样
```swift
let murrayTheHamster = Hamster(name: "Murray")
let morganTheHamster = Hamster(name: "Morgan")
let mauriceTheHamster = Hamster(name: "Maurice")
let hamsters = [murrayTheHamster, morganTheHamster, mauriceTheHamster]
let equalNumbers = [100, 100, 100, 100, 100]
let differentNumbers = [100, 100, 200, 100, 200]
```
因为 `Array` 符合 `CollectionType` 协议,而数组中的元素又符合 `TextRepresentable` 协议,所以数组可以使用 `textualDescription` 属性得到数组内容的文本表示:
由于数组遵循 `Collection` 而且整数遵循 `Equatable``equalNumbers``differentNumbers` 都可以使用 `allEqual()` 方法。
```swift
print(hamsters.textualDescription)
// 打印 “[A hamster named Murray, A hamster named Morgan, A hamster named Maurice]”
print(equalNumbers.allEqual())
// 打印 "true"
print(differentNumbers.allEqual())
// 打印 "false"
```
> 注意
> 如果多个协议扩展都为同一个协议要求提供了默认实现,而采纳协议的类型又同时满足这些协议扩展的限制条件,那么将会使用限制条件最多的那个协议扩展提供的默认实现。
> 注意
>
> 如果一个遵循的类型满足了为同一方法或属性提供实现的多个限制型扩展的要求, Swift 会使用最匹配限制的实现。

View File

@ -0,0 +1,693 @@
# 泛型
*泛型代码*让你能根据自定义的需求,编写出适用于任意类型的、灵活可复用的函数及类型。你可避免编写重复的代码,而是用一种清晰抽象的方式来表达代码的意图。
泛型是 Swift 最强大的特性之一,很多 Swift 标准库是基于泛型代码构建的。实际上,即使你没有意识到,你也一直在*语言指南*中使用泛型。例如Swift 的 `Array``Dictionary` 都是泛型集合。你可以创建一个 `Int` 类型数组,也可创建一个 `String` 类型数组,甚至可以是任意其他 Swift 类型的数组。同样,你也可以创建一个存储任意指定类型的字典,并对该类型没有限制。
## 泛型解决的问题 {#the-problem-that-generics-solve}
下面是一个标准的非泛型函数 `swapTwoInts(_:_:)`,用来交换两个 `Int` 值:
```swift
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
```
这个函数使用输入输出参数(`inout`)来交换 `a``b` 的值,具体请参考 [输入输出参数](./06_Functions.md#in_out_parameters)。
`swapTwoInts(_:_:)` 函数将 `b` 的原始值换成了 `a`,将 `a` 的原始值换成了 `b`,你可以调用这个函数来交换两个 `Int` 类型变量:
```swift
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// 打印“someInt is now 107, and anotherInt is now 3”
```
`swapTwoInts(_:_:)` 函数很实用,但它只能作用于 `Int` 类型。如果你想交换两个 `String` 类型值,或者 `Double` 类型值,你必须编写对应的函数,类似下面 `swapTwoStrings(_:_:)``swapTwoDoubles(_:_:)` 函数:
```swift
func swapTwoStrings(_ a: inout String, _ b: inout String) {
let temporaryA = a
a = b
b = temporaryA
}
func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
let temporaryA = a
a = b
b = temporaryA
}
```
你可能注意到了,`swapTwoInts(_:_:)``swapTwoStrings(_:_:)``swapTwoDoubles(_:_:)` 函数体是一样的,唯一的区别是它们接受的参数类型(`Int``String``Double`)。
在实际应用中,通常需要一个更实用更灵活的函数来交换两个任意类型的值,幸运的是,泛型代码帮你解决了这种问题。(这些函数的泛型版本已经在下面定义好了。)
> 注意
>
> 在上面三个函数中,`a` 和 `b` 类型必须相同。如果 `a` 和 `b` 类型不同那它们俩就不能互换值。Swift 是类型安全的语言,所以它不允许一个 `String` 类型的变量和一个 `Double` 类型的变量互换值。试图这样做将导致编译错误。
## 泛型函数 {#generic-functions}
泛型函数可适用于任意类型,下面是函数 `swapTwoInts(_:_:)` 的泛型版本,命名为 `swapTwoValues(_:_:)`
```swift
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
```
`swapTwoValues(_:_:)``swapTwoInts(_:_:)` 函数体内容相同,它们只在第一行不同,如下所示:
```swift
func swapTwoInts(_ a: inout Int, _ b: inout Int)
func swapTwoValues<T>(_ a: inout T, _ b: inout T)
```
泛型版本的函数使用`占位符`类型名(这里叫做 `T` ),而不是 *实际*类型名(例如 `Int``String``Double``占位符`类型名并不关心 `T` 具体的类型,但它要求 `a`` b` 必须是相同的类型,`T` 的实际类型由每次调用 `swapTwoValues(_:_:)` 来决定。
泛型函数和非泛型函数的另外一个不同之处在于这个泛型函数名(`swapTwoValues(_:_:)`)后面跟着占位类型名(`T`),并用尖括号括起来(`<T>`)。这个尖括号告诉 Swift 那个 `T``swapTwoValues(_:_:)` 函数定义内的一个占位类型名,因此 Swift 不会去查找名为 `T `的实际类型。
`swapTwoValues(_:_:)` 函数现在可以像 `swapTwoInts(_:_:)` 那样调用,不同的是它能接受两个任意类型的值,条件是这两个值有着相同的类型。`swapTwoValues(_:_:)` 函数被调用时,`T ` 所代表的类型都会由传入的值的类型推断出来。
在下面的两个例子中,`T` 分别代表 ` Int``String`
```swift
var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt 现在是 107anotherInt 现在是 3
var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString 现在是“world”anotherString 现在是“hello”
```
> 注意
>
> 上面定义的 `swapTwoValues(_:_:)` 函数是受 `swap(_:_:)` 函数启发而实现的。后者存在于 Swift 标准库,你可以在你的应用程序中使用它。如果你在代码中需要类似 `swapTwoValues(_:_:)` 函数的功能,你可以使用已存在的 `swap(_:_:)` 函数。
## 类型参数 {#type-parameters}
上面 `swapTwoValues(_:_:)` 例子中,占位类型 `T` 是一个类型参数的例子,类型参数指定并命名一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来(例如 `<T>`)。
一旦一个类型参数被指定,你可以用它来定义一个函数的参数类型(例如 `swapTwoValues(_:_:)` 函数中的参数 `a``b`),或者作为函数的返回类型,还可以用作函数主体中的注释类型。在这些情况下,类型参数会在函数调用时被实际类型所替换。(在上面的 `swapTwoValues(_:_:)` 例子中,当函数第一次被调用时,`T``Int` 替换,第二次调用时,被 `String` 替换。)
你可提供多个类型参数,将它们都写在尖括号中,用逗号分开。
## 命名类型参数 {#naming-type-parameters}
大多情况下,类型参数具有描述下的名称,例如字典 `Dictionary<Key, Value>` 中的 `Key``Value` 及数组 `Array<Element>` 中的 `Element`,这能告诉阅读代码的人这些参数类型与泛型类型或函数之间的关系。然而,当它们之间没有有意义的关系时,通常使用单个字符来表示,例如 `T``U``V`,例如上面演示函数 `swapTwoValues(_:_:)` 中的 `T`
> 注意
>
> 请始终使用大写字母开头的驼峰命名法(例如 `T` 和 `MyTypeParameter`)来为类型参数命名,以表明它们是占位类型,而不是一个值。
## 泛型类型 {#generic-types}
除了泛型函数Swift 还允许自定义*泛型类型*。这些自定义类、结构体和枚举可以适用于*任意类型*,类似于 `Array``Dictionary`
本节将向你展示如何编写一个名为 `Stack`(栈)的泛型集合类型。栈是值的有序集合,和数组类似,但比数组有更严格的操作限制。数组允许在其中任意位置插入或是删除元素。而栈只允许在集合的末端添加新的元素(称之为入栈)。类似的,栈也只能从末端移除元素(称之为出栈)。
> 注意
>
> 栈的概念已被 `UINavigationController` 类用来构造视图控制器的导航结构。你通过调用 `UINavigationController` 的 `pushViewController(_:animated:)` 方法来添加新的视图控制器到导航栈,通过 `popViewControllerAnimated(_:)` 方法来从导航栈中移除视图控制器。每当你需要一个严格的“后进先出”方式来管理集合,栈都是最实用的模型。
下图展示了入栈push和出栈pop的行为
![](https://docs.swift.org/swift-book/_images/stackPushPop_2x.png)
1. 现在有三个值在栈中。
2. 第四个值被压入到栈的顶部。
3. 现在栈中有四个值,最近入栈的那个值在顶部。
4. 栈中最顶部的那个值被移除出栈。
5. 一个值移除出栈后,现在栈又只有三个值了。
下面展示如何编写一个非泛型版本的栈,以 `Int` 型的栈为例:
```swift
struct IntStack {
var items = [Int]()
mutating func push(_ item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
}
```
这个结构体在栈中使用一个名为 `items` 的数组属性来存储值。栈提供了两个方法:`push(_:)``pop()`,用来向栈中压入值以及从栈中移除值。这些方法被标记为 `mutating`,因为它们需要修改结构体的 `items` 数组。
上面的 `IntStack` 结构体只能用于 `Int` 类型。不过,可以定义一个泛型 `Stack` 结构体,从而能够处理任意类型的值。
下面是相同代码的泛型版本:
```swift
struct Stack<Element> {
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
```
注意,`Stack` 基本上和 `IntStack` 相同,只是用占位类型参数 `Element` 代替了实际的 `Int` 类型。这个类型参数包裹在紧随结构体名的一对尖括号里(<`Element`>)。
`Element` 为待提供的类型定义了一个占位名。这种待提供的类型可以在结构体的定义中通过 `Element` 来引用。在这个例子中,`Element` 在如下三个地方被用作占位符:
+ 创建 `items` 属性,使用 `Element` 类型的空数组对其进行初始化。
+ 指定 `push(_:)` 方法的唯一参数 `item` 的类型必须是 `Element` 类型。
+ 指定 `pop()` 方法的返回值类型必须是 `Element` 类型。
由于 `Stack` 是泛型类型,因此可以用来创建适用于 Swift 中任意有效类型的栈,就像 `Array``Dictionary` 那样。
你可以通过在尖括号中写出栈中需要存储的数据类型来创建并初始化一个 `Stack` 实例。例如,要创建一个 `String` 类型的栈,可以写成 `Stack<String>()`
```swift
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
// 栈中现在有 4 个字符串
```
下图展示了 `stackOfStrings` 如何将这四个值压栈:
![](https://docs.swift.org/swift-book/_images/stackPushedFourStrings_2x.png)
移除并返回栈顶部的值“cuatro”即出栈
```swift
let fromTheTop = stackOfStrings.pop()
// fromTheTop 的值为“cuatro”现在栈中还有 3 个字符串
```
下图展示了如何将顶部的值出栈:
![](https://docs.swift.org/swift-book/_images/stackPoppedOneString_2x.png)
## 泛型扩展 {#extending-a-generic-type}
当对泛型类型进行扩展时,你并不需要提供类型参数列表作为定义的一部分。原始类型定义中声明的类型参数列表在扩展中可以直接使用,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。
下面的例子扩展了泛型类型 `Stack`,为其添加了一个名为 `topItem` 的只读计算型属性,它将会返回当前栈顶元素且不会将其从栈中移除:
```swift
extension Stack {
var topItem: Element? {
return items.isEmpty ? nil : items[items.count - 1]
}
}
```
`topItem` 属性会返回 `Element` 类型的可选值。当栈为空的时候,`topItem` 会返回 `nil`;当栈不为空的时候,`topItem` 会返回 `items` 数组中的最后一个元素。
注意:这个扩展并没有定义类型参数列表。相反的,`Stack` 类型已有的类型参数名称 `Element`,被用在扩展中来表示计算型属性 `topItem` 的可选类型。
计算型属性 `topItem` 现在可以用来访问任意 `Stack` 实例的顶端元素且不移除它:
```swift
if let topItem = stackOfStrings.topItem {
print("The top item on the stack is \(topItem).")
}
// 打印“The top item on the stack is tres.”
```
泛型类型的扩展,还可以包括类型扩展需要额外满足的条件,从而对类型添加新功能,这一部分将在[具有泛型 Where 子句的扩展](#extensions-with-a-generic-where-clause)中进行讨论。
## 类型约束 {#type-constraints}
`swapTwoValues(_:_:)` 函数和 `Stack` 适用于任意类型。不过,如果能对泛型函数或泛型类型中添加特定的*类型约束*,这将在某些情况下非常有用。类型约束指定类型参数必须继承自指定类、遵循特定的协议或协议组合。
例如Swift 的 `Dictionary` 类型对字典的键的类型做了些限制。在 [字典的描述](./04_Collection_Types.md#dictionaries) 中字典键的类型必须是可哈希hashable的。也就是说必须有一种方法能够唯一地表示它。字典键之所以要是可哈希的是为了便于检查字典中是否已经包含某个特定键的值。若没有这个要求字典将无法判断是否可以插入或替换某个指定键的值也不能查找到已经存储在字典中的指定键的值。
这个要求通过 `Dictionary` 键类型上的类型约束实现,它指明了键必须遵循 Swift 标准库中定义的 `Hashable` 协议。所有 Swift 的基本类型(例如 `String``Int``Double``Bool`)默认都是可哈希的。
当自定义泛型类型时,你可以定义你自己的类型约束,这些约束将提供更为强大的泛型编程能力。像 `可哈希hashable` 这种抽象概念根据它们的概念特征来描述类型,而不是它们的具体类型。
### 类型约束语法 {#type-constraint-syntax}
在一个类型参数名后面放置一个类名或者协议名,并用冒号进行分隔,来定义类型约束。下面将展示泛型函数约束的基本语法(与泛型类型的语法相同):
```swift
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// 这里是泛型函数的函数体部分
}
```
上面这个函数有两个类型参数。第一个类型参数 `T` 必须是 `SomeClass` 子类;第二个类型参数 `U` 必须符合 `SomeProtocol` 协议。
### 类型约束实践 {#type-constraints-in-action}
这里有个名为 `findIndex(ofString:in:)` 的非泛型函数,该函数的功能是在一个 `String` 数组中查找给定 `String` 值的索引。若查找到匹配的字符串,`findIndex(ofString:in:)` 函数返回该字符串在数组中的索引值,否则返回 `nil`
```swift
func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
```
`findIndex(ofString:in:)` 函数可以用于查找字符串数组中的某个字符串值:
```swift
let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
if let foundIndex = findIndex(ofString: "llama", in: strings) {
print("The index of llama is \(foundIndex)")
}
// 打印“The index of llama is 2”
```
如果只能查找字符串在数组中的索引,用处不是很大。不过,你可以用占位类型 `T` 替换 `String` 类型来写出具有相同功能的泛型函数 `findIndex(_:_:)`
下面展示了 `findIndex(ofString:in:)` 函数的泛型版本 `findIndex(of:in:)`。请注意这个函数返回值的类型仍然是 `Int?`,这是因为函数返回的是一个可选的索引数,而不是从数组中得到的一个可选值。需要提醒的是,这个函数无法通过编译,原因将在后面说明:
```swift
func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
```
上面所写的函数无法通过编译。问题出在相等性检查上,即 "`if value == valueToFind`"。不是所有的 Swift 类型都可以用等式符(`==`进行比较。例如如果你自定义类或结构体来描述复杂的数据模型对于这个类或结构体而言Swift 无法明确知道“相等”意味着什么。正因如此,这部分代码无法保证适用于任意类型 `T`,当你试图编译这部分代码时就会出现相应的错误。
不过所有的这些并不会让我们无从下手。Swift 标准库中定义了一个 `Equatable` 协议,该协议要求任何遵循该协议的类型必须实现等式符(`==`)及不等符(`!=`),从而能对该类型的任意两个值进行比较。所有的 Swift 标准类型自动支持 `Equatable` 协议。
遵循 `Equatable` 协议的类型都可以安全地用于 `findIndex(of:in:)` 函数,因为其保证支持等式操作符。为了说明这个事情,当定义一个函数时,你可以定义一个 `Equatable` 类型约束作为类型参数定义的一部分:
```swift
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
```
`findIndex(of:in:)` 类型参数写做 `T: Equatable`,也就意味着“任何符合 `Equatable` 协议的类型 `T`”。
`findIndex(of:in:)` 函数现在可以成功编译了,并且适用于任何符合 `Equatable` 的类型,如 `Double``String`
```swift
let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
// doubleIndex 类型为 Int?,其值为 nil因为 9.3 不在数组中
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
// stringIndex 类型为 Int?,其值为 2
```
## 关联类型 {#associated-types}
定义一个协议时,声明一个或多个关联类型作为协议定义的一部分将会非常有用。关联类型为协议中的某个类型提供了一个占位符名称,其代表的实际类型在协议被遵循时才会被指定。关联类型通过 `associatedtype` 关键字来指定。
### 关联类型实践 {#associated-types-in-action}
下面例子定义了一个 `Container` 协议,该协议定义了一个关联类型 `Item`
```swift
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
```
`Container` 协议定义了三个任何遵循该协议的类型(即容器)必须提供的功能:
+ 必须可以通过 `append(_:)` 方法添加一个新元素到容器里。
+ 必须可以通过 `count` 属性获取容器中元素的数量,并返回一个 Int 值。
+ 必须可以通过索引值类型为 `Int` 的下标检索到容器中的每一个元素。
该协议没有指定容器中元素该如何存储以及元素类型。该协议只指定了任何遵从 `Container` 协议的类型必须提供的三个功能。遵从协议的类型在满足这三个条件的情况下,也可以提供其他额外的功能。
任何遵从 `Container` 协议的类型必须能够指定其存储的元素的类型。具体来说,它必须确保添加到容器内的元素以及下标返回的元素类型是正确的。
为了定义这些条件,`Container` 协议需要在不知道容器中元素的具体类型的情况下引用这种类型。`Container` 协议需要指定任何通过 `append(_:)` 方法添加到容器中的元素和容器内的元素是相同类型,并且通过容器下标返回的元素的类型也是这种类型。
为此,`Container` 协议声明了一个关联类型 `Item`,写作 `associatedtype Item`。协议没有定义 `Item` 是什么,这个信息留给遵从协议的类型来提供。尽管如此,`Item` 别名提供了一种方式来引用 `Container` 中元素的类型,并将之用于 `append(_:)` 方法和下标,从而保证任何 `Container` 的行为都能如预期。
这是前面非泛型版本 `IntStack` 类型,使其遵循 `Container` 协议:
```swift
struct IntStack: Container {
// IntStack 的原始实现部分
var items = [Int]()
mutating func push(_ item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
// Container 协议的实现部分
typealias Item = Int
mutating func append(_ item: Int) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Int {
return items[i]
}
}
```
`IntStack` 结构体实现了 `Container` 协议的三个要求,其原有功能也不会和这些要求相冲突。
此外,`IntStack` 在实现 `Container` 的要求时,指定 `Item``Int` 类型,即 `typealias Item = Int`,从而将 `Container` 协议中抽象的 `Item` 类型转换为具体的 `Int` 类型。
由于 Swift 的类型推断,实际上在 `IntStack` 的定义中不需要声明 `Item``Int`。因为 `IntStack` 符合 `Container` 协议的所有要求Swift 只需通过 `append(_:)` 方法的 `item` 参数类型和下标返回值的类型,就可以推断出 `Item` 的具体类型。事实上,如果你在上面的代码中删除了 `typealias Item = Int` 这一行,一切也可正常工作,因为 Swift 清楚地知道 `Item` 应该是哪种类型。
你也可以让泛型 `Stack` 结构体遵循 `Container` 协议:
```swift
struct Stack<Element>: Container {
// Stack<Element> 的原始实现部分
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
// Container 协议的实现部分
mutating func append(_ item: Element) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Element {
return items[i]
}
}
```
这一次,占位类型参数 `Element` 被用作 `append(_:)` 方法的 `item` 参数和下标的返回类型。Swift 可以据此推断出 `Element` 的类型即是 `Item` 的类型。
### 扩展现有类型来指定关联类型 {#extending-an-existing-type-to-specify-an-associated-type}
[在扩展添加协议一致性](./21_Protocols.md#adding_protocol_conformance_with_an_extension) 中描述了如何利用扩展让一个已存在的类型遵循一个协议,这包括使用了关联类型协议。
Swift 的 `Array` 类型已经提供 `append(_:)` 方法,`count` 属性,以及带有 `Int` 索引的下标来检索其元素。这三个功能都符合 `Container` 协议的要求,也就意味着你只需声明 `Array` 遵循`Container` 协议,就可以扩展 Array使其遵从 Container 协议。你可以通过一个空扩展来实现这点,正如通过扩展采纳协议中的描述:
```swift
extension Array: Container {}
```
`Array``append(_:)` 方法和下标确保了 Swift 可以推断出 `Item` 具体类型。定义了这个扩展后,你可以将任意 `Array` 当作 Container 来使用。
### 给关联类型添加约束 {#adding-constraints-to-an-associated-type}
你可以在协议里给关联类型添加约束来要求遵循的类型满足约束。例如,下面的代码定义了 `Container` 协议, 要求关联类型 `Item` 必须遵循 `Equatable` 协议:
```swift
protocol Container {
associatedtype Item: Equatable
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
```
要遵守 `Container` 协议,`Item` 类型也必须遵守 `Equatable` 协议。
### 在关联类型约束里使用协议 {#using-a-protocol-in-its-associated-types-constraints}
协议可以作为它自身的要求出现。例如,有一个协议细化了 `Container` 协议,添加了一个` suffix(_:)` 方法。`suffix(_:)` 方法返回容器中从后往前给定数量的元素,并把它们存储在一个 `Suffix` 类型的实例里。
```swift
protocol SuffixableContainer: Container {
associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
func suffix(_ size: Int) -> Suffix
}
```
在这个协议里,`Suffix` 是一个关联类型,就像上边例子中 `Container``Item` 类型一样。`Suffix` 拥有两个约束:它必须遵循 `SuffixableContainer` 协议(就是当前定义的协议),以及它的 `Item` 类型必须是和容器里的 `Item` 类型相同。`Item` 的约束是一个 `where` 分句,它在下面[具有泛型 Where 子句的扩展](#extensions-with-a-generic-where-clause)中有讨论。
这是上面 [泛型类型](#generic-types) 中 `Stack` 类型的扩展,它遵循了 SuffixableContainer 协议:
```swift
extension Stack: SuffixableContainer {
func suffix(_ size: Int) -> Stack {
var result = Stack()
for index in (count-size)..<count {
result.append(self[index])
}
return result
}
// 推断 suffix 结果是Stack。
}
var stackOfInts = Stack<Int>()
stackOfInts.append(10)
stackOfInts.append(20)
stackOfInts.append(30)
let suffix = stackOfInts.suffix(2)
// suffix 包含 20 和 30
```
在上面的例子中,`Suffix``Stack` 的关联类型,也是 `Stack` ,所以 `Stack` 的后缀运算返回另一个 `Stack` 。另外,遵循 `SuffixableContainer` 的类型可以拥有一个与它自己不同的 `Suffix` 类型——也就是说后缀运算可以返回不同的类型。比如说,这里有一个非泛型 `IntStack` 类型的扩展,它遵循了 `SuffixableContainer` 协议,使用 `Stack<Int>` 作为它的后缀类型而不是 `IntStack`
```swift
extension IntStack: SuffixableContainer {
func suffix(_ size: Int) -> Stack<Int> {
var result = Stack<Int>()
for index in (count-size)..<count {
result.append(self[index])
}
return result
}
// 推断 suffix 结果是 Stack<Int>。
}
```
## 泛型 Where 语句 {#where-clauses}
[类型约束](#type_constraints) 让你能够为泛型函数、下标、类型的类型参数定义一些强制要求。
对关联类型添加约束通常是非常有用的。你可以通过定义一个泛型 `where` 子句来实现。通过泛型 `where` 子句让关联类型遵从某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。你可以通过将 `where` 关键字紧跟在类型参数列表后面来定义 `where` 子句,`where` 子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。你可以在函数体或者类型的大括号之前添加 `where` 子句。
下面的例子定义了一个名为 `allItemsMatch` 的泛型函数,用来检查两个 `Container` 实例是否包含相同顺序的相同元素。如果所有的元素能够匹配,那么返回 `true`,否则返回 `false`
被检查的两个 `Container` 可以不是相同类型的容器(虽然它们可以相同),但它们必须拥有相同类型的元素。这个要求通过一个类型约束以及一个 `where` 子句来表示:
```swift
func allItemsMatch<C1: Container, C2: Container>
(_ someContainer: C1, _ anotherContainer: C2) -> Bool
where C1.Item == C2.Item, C1.Item: Equatable {
// 检查两个容器含有相同数量的元素
if someContainer.count != anotherContainer.count {
return false
}
// 检查每一对元素是否相等
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}
// 所有元素都匹配,返回 true
return true
}
```
这个函数接受 `someContainer``anotherContainer` 两个参数。参数 `someContainer` 的类型为 `C1`,参数 `anotherContainer` 的类型为 `C2``C1``C2` 是容器的两个占位类型参数,函数被调用时才能确定它们的具体类型。
这个函数的类型参数列表还定义了对两个类型参数的要求:
+ `C1` 必须符合 `Container` 协议(写作 `C1: Container`)。
+ `C2` 必须符合 `Container` 协议(写作 `C2: Container`)。
+ `C1``Item` 必须和 `C2``Item` 类型相同(写作 `C1.Item == C2.Item`)。
+ `C1``Item` 必须符合 `Equatable` 协议(写作 `C1.Item: Equatable`)。
前两个要求定义在函数的类型形式参数列表里,后两个要求定义在了函数的泛型 `where` 分句中。
这些要求意味着:
+ `someContainer` 是一个 `C1` 类型的容器。
+ `anotherContainer` 是一个 `C2` 类型的容器。
+ `someContainer``anotherContainer` 包含相同类型的元素。
+ `someContainer` 中的元素可以通过不等于操作符(!=)来检查它们是否相同。
第三个和第四个要求结合起来意味着 `anotherContainer` 中的元素也可以通过 `!=` 操作符来比较,因为它们和 `someContainer` 中的元素类型相同。
这些要求让 `allItemsMatch(_:_:)` 函数能够比较两个容器,即使它们的容器类型不同。
`allItemsMatch(_:_:)` 函数首先检查两个容器元素个数是否相同,如果元素个数不同,那么一定不匹配,函数就会返回 `false`
进行这项检查之后,通过 `for-in` 循环和半闭区间操作符(`..<`)来迭代每个元素,检查 `someContainer` 中的元素是否不等于 `anotherContainer` 中的对应元素。如果两个元素不相等,那么两个容器不匹配,函数返回 false。
如果循环体结束后未发现任何不匹配的情况,表明两个容器匹配,函数返回 `true`
下面是 `allItemsMatch(_:_:)` 函数的示例:
```swift
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
var arrayOfStrings = ["uno", "dos", "tres"]
if allItemsMatch(stackOfStrings, arrayOfStrings) {
print("All items match.")
} else {
print("Not all items match.")
}
// 打印“All items match.”
```
上面的例子创建 `Stack` 实例来存储 `String` 值,然后将三个字符串压栈。这个例子还通过数组字面量创建了一个 `Array` 实例,数组中包含同栈中一样的三个字符串。即使栈和数组是不同的类型,但它们都遵从 `Container` 协议,而且它们都包含相同类型的值。因此你可以用这两个容器作为参数来调用 `allItemsMatch(_:_:)` 函数。在上面的例子中,`allItemsMatch(_:_:)` 函数正确地显示了这两个容器中的所有元素都是相互匹配的。
## 具有泛型 Where 子句的扩展 {#extensions-with-a-generic-where-clause}
你也可以使用泛型 `where` 子句作为扩展的一部分。基于以前的例子,下面的示例扩展了泛型 `Stack` 结构体,添加一个 `isTop(_:)` 方法。
```swift
extension Stack where Element: Equatable {
func isTop(_ item: Element) -> Bool {
guard let topItem = items.last else {
return false
}
return topItem == item
}
}
```
这个新的 `isTop(_:)` 方法首先检查这个栈是不是空的,然后比较给定的元素与栈顶部的元素。如果你尝试不用泛型 `where` 子句,会有一个问题:在 `isTop(_:)` 里面使用了 `==` 运算符,但是 `Stack` 的定义没有要求它的元素是符合 `Equatable` 协议的,所以使用 `==` 运算符导致编译时错误。使用泛型 `where` 子句可以为扩展添加新的条件,因此只有当栈中的元素符合 `Equatable` 协议时,扩展才会添加 `isTop(_:)` 方法。
以下是 `isTop(_:)` 方法的调用方式:
```swift
if stackOfStrings.isTop("tres") {
print("Top element is tres.")
} else {
print("Top element is something else.")
}
// 打印“Top element is tres.”
```
如果尝试在其元素不符合 `Equatable` 协议的栈上调用 `isTop(_:)` 方法,则会收到编译时错误。
```swift
struct NotEquatable { }
var notEquatableStack = Stack<NotEquatable>()
let notEquatableValue = NotEquatable()
notEquatableStack.push(notEquatableValue)
notEquatableStack.isTop(notEquatableValue) // 报错
```
你可以使用泛型 `where` 子句去扩展一个协议。基于以前的示例,下面的示例扩展了 `Container` 协议,添加一个 `startsWith(_:)` 方法。
```swift
extension Container where Item: Equatable {
func startsWith(_ item: Item) -> Bool {
return count >= 1 && self[0] == item
}
}
```
这个 `startsWith(_:)` 方法首先确保容器至少有一个元素,然后检查容器中的第一个元素是否与给定的元素相等。任何符合 `Container` 协议的类型都可以使用这个新的 `startsWith(_:)` 方法,包括上面使用的栈和数组,只要容器的元素是符合 `Equatable` 协议的。
```swift
if [9, 9, 9].startsWith(42) {
print("Starts with 42.")
} else {
print("Starts with something else.")
}
// 打印“Starts with something else.”
```
上述示例中的泛型 `where` 子句要求 `Item` 遵循协议,但也可以编写一个泛型 `where` 子句去要求 `Item` 为特定类型。例如:
```swift
extension Container where Item == Double {
func average() -> Double {
var sum = 0.0
for index in 0..<count {
sum += self[index]
}
return sum / Double(count)
}
}
print([1260.0, 1200.0, 98.6, 37.0].average())
// 打印“648.9”
```
此示例将一个 `average()` 方法添加到 `Item` 类型为 `Double` 的容器中。此方法遍历容器中的元素将其累加,并除以容器的数量计算平均值。它将数量从 `Int` 转换为 `Double` 确保能够进行浮点除法。
就像可以在其他地方写泛型 `where` 子句一样,你可以在一个泛型 `where` 子句中包含多个条件作为扩展的一部分。用逗号分隔列表中的每个条件。
## 具有泛型 Where 子句的关联类型 {#associated-types-with-a-generic-where-clause}
你可以在关联类型后面加上具有泛型 `where` 的字句。例如,建立一个包含迭代器(`Iterator`)的容器,就像是标准库中使用的 `Sequence` 协议那样。你应该这么写:
```swift
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
associatedtype Iterator: IteratorProtocol where Iterator.Element == Item
func makeIterator() -> Iterator
}
```
迭代器(`Iterator`)的泛型 `where` 子句要求:无论迭代器是什么类型,迭代器中的元素类型,必须和容器项目的类型保持一致。`makeIterator()` 则提供了容器的迭代器的访问接口。
一个协议继承了另一个协议,你通过在协议声明的时候,包含泛型 `where` 子句,来添加了一个约束到被继承协议的关联类型。例如,下面的代码声明了一个 `ComparableContainer` 协议,它要求所有的 `Item` 必须是 `Comparable` 的。
```swift
protocol ComparableContainer: Container where Item: Comparable { }
```
## 泛型下标 {#generic-subscripts}
下标可以是泛型,它们能够包含泛型 `where` 子句。你可以在 `subscript` 后用尖括号来写占位符类型,你还可以在下标代码块花括号前写 `where` 子句。例如:
```swift
extension Container {
subscript<Indices: Sequence>(indices: Indices) -> [Item]
where Indices.Iterator.Element == Int {
var result = [Item]()
for index in indices {
result.append(self[index])
}
return result
}
}
```
这个 `Container` 协议的扩展添加了一个下标方法,接收一个索引的集合,返回每一个索引所在的值的数组。这个泛型下标的约束如下:
+ 在尖括号中的泛型参数 `Indices`,必须是符合标准库中的 `Sequence` 协议的类型。
+ 下标使用的单一的参数,`indices`,必须是 `Indices` 的实例。
+ 泛型 `where` 子句要求 `SequenceIndices`的迭代器,其所有的元素都是 `Int` 类型。这样就能确保在序列(`Sequence`)中的索引和容器(`Container`)里面的索引类型是一致的。
综合一下,这些约束意味着,传入到 `indices` 下标,是一个整型的序列。

View File

@ -0,0 +1,560 @@
# 自动引用计数
Swift 使用*自动引用计数ARC*机制来跟踪和管理你的应用程序的内存。通常情况下Swift 内存管理机制会一直起作用你无须自己来考虑内存的管理。ARC 会在类的实例不再被使用时,自动释放其占用的内存。
然而在少数情况下为了能帮助你管理内存ARC 需要更多的,代码之间关系的信息。本章描述了这些情况,并且为你示范怎样才能使 ARC 来管理你的应用程序的所有内存。在 Swift 使用 ARC 与在 Obejctive-C 中使用 ARC 非常类似,具体请参考 [过渡到 ARC 的发布说明](https://developer.apple.com/library/content/releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html#//apple_ref/doc/uid/TP40011226)。
> 注意
>
> 引用计数仅仅应用于类的实例。结构体和枚举类型是值类型,不是引用类型,也不是通过引用的方式存储和传递。
## 自动引用计数的工作机制 {#how-arc-works}
当你每次创建一个类的新的实例的时候ARC 会分配一块内存来储存该实例信息。内存中会包含实例的类型信息,以及这个实例所有相关的存储型属性的值。
此外当实例不再被使用时ARC 释放实例所占用的内存,并让释放的内存能挪作他用。这确保了不再被使用的实例,不会一直占用内存空间。
然而,当 ARC 收回和释放了正在被使用中的实例,该实例的属性和方法将不能再被访问和调用。实际上,如果你试图访问这个实例,你的应用程序很可能会崩溃。
为了确保使用中的实例不会被销毁ARC 会跟踪和计算每一个实例正在被多少属性,常量和变量所引用。哪怕实例的引用数为 1ARC 都不会销毁这个实例。
为了使上述成为可能,无论你将实例赋值给属性、常量或变量,它们都会创建此实例的强引用。之所以称之为“强”引用,是因为它会将实例牢牢地保持住,只要强引用还在,实例是不允许被销毁的。
## 自动引用计数实践 {#arc-in-action}
下面的例子展示了自动引用计数的工作机制。例子以一个简单的 `Person` 类开始,并定义了一个叫 `name` 的常量属性:
```swift
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
```
`Person` 类有一个构造器,此构造器为实例的 `name` 属性赋值,并打印一条消息以表明初始化过程生效。`Person` 类也拥有一个析构器,这个析构器会在实例被销毁时打印一条消息。
接下来的代码片段定义了三个类型为 `Person?` 的变量,用来按照代码片段中的顺序,为新的 `Person` 实例建立多个引用。由于这些变量是被定义为可选类型(`Person?`,而不是 `Person`),它们的值会被自动初始化为 `nil`,目前还不会引用到 `Person` 类的实例。
```swift
var reference1: Person?
var reference2: Person?
var reference3: Person?
```
现在你可以创建 `Person` 类的新实例,并且将它赋值给三个变量中的一个:
```swift
reference1 = Person(name: "John Appleseed")
// 打印“John Appleseed is being initialized”
```
应当注意到当你调用 `Person` 类的构造器的时候,`"John Appleseed is being initialized"` 会被打印出来。由此可以确定构造器被执行。
由于 `Person` 类的新实例被赋值给了 `reference1` 变量,所以 `reference1``Person` 类的新实例之间建立了一个强引用。正是因为这一个强引用ARC 会保证 `Person` 实例被保持在内存中不被销毁。
如果你将同一个 `Person` 实例也赋值给其他两个变量,该实例又会多出两个强引用:
```swift
reference2 = reference1
reference3 = reference1
```
现在这一个 `Person` 实例已经有三个强引用了。
如果你通过给其中两个变量赋值 `nil` 的方式断开两个强引用(包括最先的那个强引用),只留下一个强引用,`Person` 实例不会被销毁:
```swift
reference1 = nil
reference2 = nil
```
在你清楚地表明不再使用这个 `Person` 实例时即第三个也就是最后一个强引用被断开时ARC 会销毁它:
```swift
reference3 = nil
// 打印“John Appleseed is being deinitialized”
```
## 类实例之间的循环强引用 {#strong-reference-cycles-between-class-instances}
在上面的例子中ARC 会跟踪你所新创建的 `Person` 实例的引用数量,并且会在 `Person` 实例不再被需要时销毁它。
然而,我们可能会写出一个类实例的强引用数*永远不能*变成 `0` 的代码。如果两个类实例互相持有对方的强引用,因而每个实例都让对方一直存在,就是这种情况。这就是所谓的*循环强引用*。
你可以通过定义类之间的关系为弱引用或无主引用,以替代强引用,从而解决循环强引用的问题。具体的过程在 [解决类实例之间的循环强引用](#resolving_strong_reference_cycles_between_class_instances) 中有描述。不管怎样,在你学习怎样解决循环强引用之前,很有必要了解一下它是怎样产生的。
下面展示了一个不经意产生循环强引用的例子。例子定义了两个类:`Person``Apartment`,用来建模公寓和它其中的居民:
```swift
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
```
每一个 `Person` 实例有一个类型为 `String`,名字为 `name` 的属性,并有一个可选的初始化为 `nil``apartment` 属性。`apartment` 属性是可选的,因为一个人并不总是拥有公寓。
类似的,每个 `Apartment` 实例有一个叫 `unit`,类型为 `String` 的属性,并有一个可选的初始化为 `nil``tenant` 属性。`tenant` 属性是可选的,因为一栋公寓并不总是有居民。
这两个类都定义了析构器,用以在类实例被析构的时候输出信息。这让你能够知晓 `Person``Apartment` 的实例是否像预期的那样被销毁。
接下来的代码片段定义了两个可选类型的变量 `john``unit4A`,并分别被设定为下面的 `Apartment``Person` 的实例。这两个变量都被初始化为 `nil`,这正是可选类型的优点:
```swift
var john: Person?
var unit4A: Apartment?
```
现在你可以创建特定的 `Person``Apartment` 实例并将赋值给 `john``unit4A` 变量:
```swift
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
```
在两个实例被创建和赋值后,下图表现了强引用的关系。变量 `john` 现在有一个指向 `Person` 实例的强引用,而变量 `unit4A` 有一个指向 `Apartment` 实例的强引用:
![](https://docs.swift.org/swift-book/_images/referenceCycle01_2x.png)
现在你能够将这两个实例关联在一起,这样人就能有公寓住了,而公寓也有了房客。注意感叹号是用来展开和访问可选变量 `john``unit4A` 中的实例,这样实例的属性才能被赋值:
```swift
john!.apartment = unit4A
unit4A!.tenant = john
```
在将两个实例联系在一起之后,强引用的关系如图所示:
![](https://docs.swift.org/swift-book/_images/referenceCycle02_2x.png)
不幸的是,这两个实例关联后会产生一个循环强引用。`Person` 实例现在有了一个指向 `Apartment` 实例的强引用,而 `Apartment` 实例也有了一个指向 `Person` 实例的强引用。因此,当你断开 `john``unit4A` 变量所持有的强引用时,引用计数并不会降为 `0`,实例也不会被 ARC 销毁:
```swift
john = nil
unit4A = nil
```
注意,当你把这两个变量设为 `nil` 时,没有任何一个析构器被调用。循环强引用会一直阻止 `Person``Apartment` 类实例的销毁,这就在你的应用程序中造成了内存泄漏。
在你将 `john``unit4A` 赋值为 `nil` 后,强引用关系如下图:
![](https://docs.swift.org/swift-book/_images/referenceCycle03_2x.png)
`Person``Apartment` 实例之间的强引用关系保留了下来并且不会被断开。
## 解决实例之间的循环强引用 {#resolving-strong-reference-cycles-between-class-instances}
Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题弱引用weak reference和无主引用unowned reference
弱引用和无主引用允许循环引用中的一个实例引用另一个实例而*不*保持强引用。这样实例能够互相引用而不产生循环强引用。
当其他的实例有更短的生命周期时,使用弱引用,也就是说,当其他实例析构在先时。在上面公寓的例子中,很显然一个公寓在它的生命周期内会在某个时间段没有它的主人,所以一个弱引用就加在公寓类里面,避免循环引用。相比之下,当其他实例有相同的或者更长生命周期时,请使用无主引用。
### 弱引用 {#weak-references}
*弱引用*不会对其引用的实例保持强引用,因而不会阻止 ARC 销毁被引用的实例。这个特性阻止了引用变为循环强引用。声明属性或者变量时,在前面加上 `weak` 关键字表明这是一个弱引用。
因为弱引用不会保持所引用的实例即使引用存在实例也有可能被销毁。因此ARC 会在引用的实例被销毁后自动将其弱引用赋值为 `nil`。并且因为弱引用需要在运行时允许被赋值为 `nil`,所以它们会被定义为可选类型变量,而不是常量。
你可以像其他可选值一样,检查弱引用的值是否存在,你将永远不会访问已销毁的实例的引用。
> 注意
>
> 当 ARC 设置弱引用为 `nil` 时,属性观察不会被触发。
下面的例子跟上面 `Person``Apartment` 的例子一致,但是有一个重要的区别。这一次,`Apartment``tenant` 属性被声明为弱引用:
```swift
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
```
然后跟之前一样,建立两个变量(`john``unit4A`)之间的强引用,并关联两个实例:
```swift
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = john
```
现在,两个关联在一起的实例的引用关系如下图所示:
![](https://docs.swift.org/swift-book/_images/weakReference01_2x.png)
`Person` 实例依然保持对 `Apartment` 实例的强引用,但是 `Apartment` 实例只持有对 `Person` 实例的弱引用。这意味着当你通过把 `john` 变量赋值为 `nil` 而断开其所保持的强引用时,再也没有指向 `Person` 实例的强引用了:
```swift
john = nil
// 打印“John Appleseed is being deinitialized”
```
由于再也没有指向 `Person` 实例的强引用,该实例会被销毁,且 `tenant` 属性会被赋值为 `nil`
![](https://docs.swift.org/swift-book/_images/weakReference02_2x.png)
唯一剩下的指向 `Apartment` 实例的强引用来自于变量 `unit4A`。如果你断开这个强引用,再也没有指向 `Apartment` 实例的强引用了:
```swift
unit4A = nil
// 打印“Apartment 4A is being deinitialized”
```
由于再也没有指向 `Person` 实例的强引用,该实例会被销毁:
![](https://docs.swift.org/swift-book/_images/weakReference03_2x.png)
> 注意
>
> 在使用垃圾收集的系统里,弱指针有时用来实现简单的缓冲机制,因为没有强引用的对象只会在内存压力触发垃圾收集时才被销毁。但是在 ARC 中,一旦值的最后一个强引用被移除,就会被立即销毁,这导致弱引用并不适合上面的用途。
### 无主引用 {#unowned-references}
和弱引用类似,*无主引用*不会牢牢保持住引用的实例。和弱引用不同的是,无主引用在其他实例有相同或者更长的生命周期时使用。你可以在声明属性或者变量时,在前面加上关键字 `unowned` 表示这是一个无主引用。
无主引用通常都被期望拥有值。不过 ARC 无法在实例被销毁后将无主引用设为 `nil`,因为非可选类型的变量不允许被赋值为 `nil`
> 重点
>
> 使用无主引用,你*必须*确保引用始终指向一个未销毁的实例。
>
> 如果你试图在实例被销毁后,访问该实例的无主引用,会触发运行时错误。
下面的例子定义了两个类,`Customer``CreditCard`,模拟了银行客户和客户的信用卡。这两个类中,每一个都将另外一个类的实例作为自身的属性。这种关系可能会造成循环强引用。
`Customer``CreditCard` 之间的关系与前面弱引用例子中 `Apartment``Person` 的关系略微不同。在这个数据模型中,一个客户可能有或者没有信用卡,但是一张信用卡总是关联着一个客户。为了表示这种关系,`Customer` 类有一个可选类型的 `card` 属性,但是 `CreditCard` 类有一个非可选类型的 `customer` 属性。
此外,只能通过将一个 `number` 值和 `customer` 实例传递给 `CreditCard` 构造器的方式来创建 `CreditCard` 实例。这样可以确保当创建 `CreditCard` 实例时总是有一个 `customer` 实例与之关联。
由于信用卡总是关联着一个客户,因此将 `customer` 属性定义为无主引用,用以避免循环强引用:
```swift
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit { print("\(name) is being deinitialized") }
}
class CreditCard {
let number: UInt64
unowned let customer: Customer
init(number: UInt64, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("Card #\(number) is being deinitialized") }
}
```
> 注意
>
> `CreditCard` 类的 `number` 属性被定义为 `UInt64` 类型而不是 `Int` 类型,以确保 `number` 属性的存储量在 32 位和 64 位系统上都能足够容纳 16 位的卡号。
下面的代码片段定义了一个叫 `john` 的可选类型 `Customer` 变量,用来保存某个特定客户的引用。由于是可选类型,所以变量被初始化为 `nil`
```swift
var john: Customer?
```
现在你可以创建 `Customer` 类的实例,用它初始化 `CreditCard` 实例,并将新创建的 `CreditCard` 实例赋值为客户的 `card` 属性:
```swift
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
```
在你关联两个实例后,它们的引用关系如下图所示:
![](https://docs.swift.org/swift-book/_images/unownedReference01_2x.png)
`Customer` 实例持有对 `CreditCard` 实例的强引用,而 `CreditCard` 实例持有对 `Customer` 实例的无主引用。
由于 `customer` 的无主引用,当你断开 `john` 变量持有的强引用时,再也没有指向 `Customer` 实例的强引用了:
![](https://docs.swift.org/swift-book/_images/unownedReference02_2x.png)
由于再也没有指向 `Customer` 实例的强引用,该实例被销毁了。其后,再也没有指向 `CreditCard` 实例的强引用,该实例也随之被销毁了:
```swift
john = nil
// 打印“John Appleseed is being deinitialized”
// 打印“Card #1234567890123456 is being deinitialized”
```
最后的代码展示了在 `john` 变量被设为 `nil``Customer` 实例和 `CreditCard` 实例的析构器都打印出了“销毁”的信息。
> 注意
>
> 上面的例子展示了如何使用安全的无主引用。对于需要禁用运行时的安全检查的情况例如出于性能方面的原因Swift 还提供了不安全的无主引用。与所有不安全的操作一样,你需要负责检查代码以确保其安全性。
> 你可以通过 `unowned(unsafe)` 来声明不安全无主引用。如果你试图在实例被销毁后,访问该实例的不安全无主引用,你的程序会尝试访问该实例之前所在的内存地址,这是一个不安全的操作。
### 无主引用和隐式解包可选值属性 {#unowned-references-and-implicitly-unwrapped-optional-properties}
上面弱引用和无主引用的例子涵盖了两种常用的需要打破循环强引用的场景。
`Person``Apartment` 的例子展示了两个属性的值都允许为 `nil`,并会潜在的产生循环强引用。这种场景最适合用弱引用来解决。
`Customer``CreditCard` 的例子展示了一个属性的值允许为 `nil`,而另一个属性的值不允许为 `nil`,这也可能会产生循环强引用。这种场景最适合通过无主引用来解决。
然而,存在着第三种场景,在这种场景中,两个属性都必须有值,并且初始化完成后永远不会为 `nil`。在这种场景中,需要一个类使用无主属性,而另外一个类使用隐式解包可选值属性。
这使两个属性在初始化完成后能被直接访问(不需要可选展开),同时避免了循环引用。这一节将为你展示如何建立这种关系。
下面的例子定义了两个类,`Country``City`,每个类将另外一个类的实例保存为属性。在这个模型中,每个国家必须有首都,每个城市必须属于一个国家。为了实现这种关系,`Country` 类拥有一个 `capitalCity` 属性,而 `City` 类有一个 `country` 属性:
```swift
class Country {
let name: String
var capitalCity: City!
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
}
```
为了建立两个类的依赖关系,`City` 的构造器接受一个 `Country` 实例作为参数,并且将实例保存到 `country` 属性。
`Country` 的构造器调用了 `City` 的构造器。然而,只有 `Country` 的实例完全初始化后,`Country` 的构造器才能把 `self` 传给 `City` 的构造器。在 [两段式构造过程](./14_Initialization.md#two_phase_initialization) 中有具体描述。
为了满足这种需求,通过在类型结尾处加上感叹号(`City!`)的方式,将 `Country``capitalCity` 属性声明为隐式解包可选值类型的属性。这意味着像其他可选类型一样,`capitalCity` 属性的默认值为 `nil`,但是不需要展开它的值就能访问它。在 [隐式解包可选值](./01_The_Basics.md#implicityly_unwrapped_optionals) 中有描述。
由于 `capitalCity` 默认值为 `nil`,一旦 `Country` 的实例在构造器中给 `name` 属性赋值后,整个初始化过程就完成了。这意味着一旦 `name` 属性被赋值后,`Country` 的构造器就能引用并传递隐式的 `self``Country` 的构造器在赋值 `capitalCity` 时,就能将 `self` 作为参数传递给 `City` 的构造器。
以上的意义在于你可以通过一条语句同时创建 `Country``City` 的实例,而不产生循环强引用,并且 `capitalCity` 的属性能被直接访问,而不需要通过感叹号来展开它的可选值:
```swift
var country = Country(name: "Canada", capitalName: "Ottawa")
print("\(country.name)'s capital city is called \(country.capitalCity.name)")
// 打印“Canada's capital city is called Ottawa”
```
在上面的例子中,使用隐式解包可选值值意味着满足了类的构造器的两个构造阶段的要求。`capitalCity` 属性在初始化完成后,能像非可选值一样使用和存取,同时还避免了循环强引用。
## 闭包的循环强引用 {#strong-reference-cycles-for-closures}
前面我们看到了循环强引用是在两个类实例属性互相保持对方的强引用时产生的,还知道了如何用弱引用和无主引用来打破这些循环强引用。
循环强引用还会发生在当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了这个类实例时。这个闭包体中可能访问了实例的某个属性,例如 `self.someProperty`,或者闭包中调用了实例的某个方法,例如 `self.someMethod()`。这两种情况都导致了闭包“捕获”`self`,从而产生了循环强引用。
循环强引用的产生,是因为闭包和类相似,都是引用类型。当你把一个闭包赋值给某个属性时,你是将这个闭包的引用赋值给了属性。实质上,这跟之前的问题是一样的——两个强引用让彼此一直有效。但是,和两个类实例不同,这次一个是类实例,另一个是闭包。
Swift 提供了一种优雅的方法来解决这个问题,称之为 `闭包捕获列表`closure capture list。同样的在学习如何用闭包捕获列表打破循环强引用之前先来了解一下这里的循环强引用是如何产生的这对我们很有帮助。
下面的例子为你展示了当一个闭包引用了 `self` 后是如何产生一个循环强引用的。例子中定义了一个叫 `HTMLElement` 的类,用一种简单的模型表示 HTML 文档中的一个单独的元素:
```swift
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = {
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
}
```
`HTMLElement` 类定义了一个 `name` 属性来表示这个元素的名称,例如代表头部元素的 `"h1"`,代表段落的 `"p"`,或者代表换行的 `"br"``HTMLElement` 还定义了一个可选属性 `text`,用来设置 HTML 元素呈现的文本。
除了上面的两个属性,`HTMLElement` 还定义了一个 `lazy` 属性 `asHTML`。这个属性引用了一个将 `name``text` 组合成 HTML 字符串片段的闭包。该属性是 `Void -> String` 类型,或者可以理解为“一个没有参数,返回 `String` 的函数”。
默认情况下,闭包赋值给了 `asHTML` 属性,这个闭包返回一个代表 HTML 标签的字符串。如果 `text` 值存在,该标签就包含可选值 `text`;如果 `text` 不存在,该标签就不包含文本。对于段落元素,根据 `text``"some text"` 还是 `nil`,闭包会返回 `"<p>some text</p>"` 或者 `"<p />"`
可以像实例方法那样去命名、使用 `asHTML` 属性。然而,由于 `asHTML` 是闭包而不是实例方法,如果你想改变特定 HTML 元素的处理方式的话,可以用自定义的闭包来取代默认值。
例如,可以将一个闭包赋值给 `asHTML` 属性,这个闭包能在 `text` 属性是 `nil` 时使用默认文本,这是为了避免返回一个空的 HTML 标签:
```swift
let heading = HTMLElement(name: "h1")
let defaultText = "some default text"
heading.asHTML = {
return "<\(heading.name)>\(heading.text ?? defaultText)</\(heading.name)>"
}
print(heading.asHTML())
// 打印“<h1>some default text</h1>”
```
> 注意
>
> `asHTML` 声明为 `lazy` 属性,因为只有当元素确实需要被处理为 HTML 输出的字符串时,才需要使用 `asHTML`。也就是说,在默认的闭包中可以使用 `self`,因为只有当初始化完成以及 `self` 确实存在后,才能访问 `lazy` 属性。
`HTMLElement` 类只提供了一个构造器,通过 `name``text`(如果有的话)参数来初始化一个新元素。该类也定义了一个析构器,当 `HTMLElement` 实例被销毁时,打印一条消息。
下面的代码展示了如何用 `HTMLElement` 类创建实例并打印消息:
```swift
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// 打印“<p>hello, world</p>”
```
> 注意
>
> 上面的 `paragraph` 变量定义为可选类型的 `HTMLElement`,因此我们可以赋值 `nil` 给它来演示循环强引用。
不幸的是,上面写的 `HTMLElement` 类产生了类实例和作为 `asHTML` 默认值的闭包之间的循环强引用。循环强引用如下图所示:
![](https://docs.swift.org/swift-book/_images/closureReferenceCycle01_2x.png)
实例的 `asHTML` 属性持有闭包的强引用。但是,闭包在其闭包体内使用了 `self`(引用了 `self.name``self.text`),因此闭包捕获了 `self`,这意味着闭包又反过来持有了 `HTMLElement` 实例的强引用。这样两个对象就产生了循环强引用。(更多关于闭包捕获值的信息,请参考 [值捕获](./07_Closures.md#capturing_values))。
> 注意
>
> 虽然闭包多次使用了 `self`,它只捕获 `HTMLElement` 实例的一个强引用。
如果设置 `paragraph` 变量为 `nil`,打破它持有的 `HTMLElement` 实例的强引用,`HTMLElement` 实例和它的闭包都不会被销毁,也是因为循环强引用:
```swift
paragraph = nil
```
注意,`HTMLElement` 的析构器中的消息并没有被打印,证明了 `HTMLElement` 实例并没有被销毁。
## 解决闭包的循环强引用 {#resolving-strong-reference-cycles-for-closures}
在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。捕获列表定义了闭包体内捕获一个或者多个引用类型的规则。跟解决两个类实例间的循环强引用一样,声明每个捕获的引用为弱引用或无主引用,而不是强引用。应当根据代码关系来决定使用弱引用还是无主引用。
> 注意
>
> Swift 有如下要求:只要在闭包内使用 `self` 的成员,就要用 `self.someProperty` 或者 `self.someMethod()`(而不只是 `someProperty` 或 `someMethod()`)。这提醒你可能会一不小心就捕获了 `self`。
### 定义捕获列表 {#defining-a-capture-list}
捕获列表中的每一项都由一对元素组成,一个元素是 `weak``unowned` 关键字,另一个元素是类实例的引用(例如 `self`)或初始化过的变量(如 `delegate = self.delegate!`)。这些项在方括号中用逗号分开。
如果闭包有参数列表和返回类型,把捕获列表放在它们前面:
```swift
lazy var someClosure: (Int, String) -> String = {
[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
// 这里是闭包的函数体
}
```
如果闭包没有指明参数列表或者返回类型,它们会通过上下文推断,那么可以把捕获列表和关键字 `in` 放在闭包最开始的地方:
```swift
lazy var someClosure: () -> String = {
[unowned self, weak delegate = self.delegate!] in
// 这里是闭包的函数体
}
```
### 弱引用和无主引用 {#weak-and-unowned-references}
在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为 `无主引用`
相反的,在被捕获的引用可能会变为 `nil` 时,将闭包内的捕获定义为 `弱引用`。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为 `nil`。这使我们可以在闭包体内检查它们是否存在。
> 注意
>
> 如果被捕获的引用绝对不会变为 `nil`,应该用无主引用,而不是弱引用。
前面的 `HTMLElement` 例子中,无主引用是正确的解决循环强引用的方法。这样编写 `HTMLElement` 类来避免循环强引用:
```swift
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = {
[unowned self] in
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
}
```
上面的 `HTMLElement` 实现和之前的实现一致,除了在 `asHTML` 闭包中多了一个捕获列表。这里,捕获列表是 `[unowned self]`,表示“将 `self` 捕获为无主引用而不是强引用”。
和之前一样,我们可以创建并打印 `HTMLElement` 实例:
```swift
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// 打印“<p>hello, world</p>”
```
使用捕获列表后引用关系如下图所示:
![](https://docs.swift.org/swift-book/_images/closureReferenceCycle02_2x.png)
这一次,闭包以无主引用的形式捕获 `self`,并不会持有 `HTMLElement` 实例的强引用。如果将 `paragraph` 赋值为 `nil``HTMLElement` 实例将会被销毁,并能看到它的析构器打印出的消息:
```swift
paragraph = nil
// 打印“p is being deinitialized”
```
你可以查看 [捕获列表](../chapter3/04_Expressions.html) 章节,获取更多关于捕获列表的信息。

View File

@ -1,536 +0,0 @@
# 泛型Generics
------
> 1.0
> 翻译:[takalard](https://github.com/takalard)
> 校对:[lifedim](https://github.com/lifedim)
> 2.0
> 翻译+校对: [SergioChan](https://github.com/SergioChan)
> 2.1
> 校对:[shanks](http://codebuild.me)2015-11-01
> 2.2:翻译+校对:[Lanford](https://github.com/LanfordCai)2016-04-08 [SketchK](https://github.com/SketchK) 2016-05-16
> 3.0:翻译+校对:[chenmingjia](https://github.com/chenmingjia)2016-09-12
本页包含内容:
- [泛型所解决的问题](#the_problem_that_generics_solve)
- [泛型函数](#generic_functions)
- [类型参数](#type_parameters)
- [命名类型参数](#naming_type_parameters)
- [泛型类型](#generic_types)
- [扩展一个泛型类型](#extending_a_generic_type)
- [类型约束](#type_constraints)
- [关联类型](#associated_types)
- [Where 子句](#where_clauses)
泛型代码让你能够根据自定义的需求,编写出适用于任意类型、灵活可重用的函数及类型。它能让你避免代码的重复,用一种清晰和抽象的方式来表达代码的意图。
泛型是 Swift 最强大的特性之一,许多 Swift 标准库是通过泛型代码构建的。事实上泛型的使用贯穿了整本语言手册只是你可能没有发现而已。例如Swift 的 `Array``Dictionary` 都是泛型集合。你可以创建一个 `Int` 数组,也可创建一个 `String` 数组,甚至可以是任意其他 Swift 类型的数组。同样的,你也可以创建存储任意指定类型的字典。
<a name="the_problem_that_generics_solve"></a>
## 泛型所解决的问题
下面是一个标准的非泛型函数 `swapTwoInts(_:_:)`,用来交换两个 `Int` 值:
```swift
func swapTwoInts(inout a: Int, inout _ b: Int) {
let temporaryA = a
a = b
b = temporaryA
}
```
这个函数使用输入输出参数(`inout`)来交换 `a``b` 的值,请参考[输入输出参数](./06_Functions.html#in_out_parameters)。
`swapTwoInts(_:_:)` 函数交换 `b` 的原始值到 `a`,并交换 `a` 的原始值到 `b`。你可以调用这个函数交换两个 `Int` 变量的值:
```swift
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// 打印 “someInt is now 107, and anotherInt is now 3”
```
诚然,`swapTwoInts(_:_:)` 函数挺有用,但是它只能交换 `Int` 值,如果你想要交换两个 `String` 值或者 `Double`值,就不得不写更多的函数,例如 `swapTwoStrings(_:_:)``swapTwoDoubles(_:_:)`,如下所示:
```swift
func swapTwoStrings(inout a: String, inout _ b: String) {
let temporaryA = a
a = b
b = temporaryA
}
func swapTwoDoubles(inout a: Double, inout _ b: Double) {
let temporaryA = a
a = b
b = temporaryA
}
```
你可能注意到 `swapTwoInts(_:_:)``swapTwoStrings(_:_:)``swapTwoDoubles(_:_:)` 的函数功能都是相同的,唯一不同之处就在于传入的变量类型不同,分别是 `Int``String``Double`
在实际应用中,通常需要一个更实用更灵活的函数来交换两个任意类型的值,幸运的是,泛型代码帮你解决了这种问题。(这些函数的泛型版本已经在下面定义好了。)
> 注意
在上面三个函数中,`a``b` 类型相同。如果 `a``b` 类型不同那它们俩就不能互换值。Swift 是类型安全的语言,所以它不允许一个 `String` 类型的变量和一个 `Double` 类型的变量互换值。试图这样做将导致编译错误。
<a name="generic_functions"></a>
## 泛型函数
泛型函数可以适用于任何类型,下面的 `swapTwoValues(_:_:)` 函数是上面三个函数的泛型版本:
```swift
func swapTwoValues<T>(inout a: T, inout _ b: T) {
let temporaryA = a
a = b
b = temporaryA
}
```
`swapTwoValues(_:_:)` 的函数主体和 `swapTwoInts(_:_:)` 函数是一样的,它们只在第一行有点不同,如下所示:
```swift
func swapTwoInts(inout a: Int, inout _ b: Int)
func swapTwoValues<T>(inout a: T, inout _ b: T)
```
这个函数的泛型版本使用了占位类型名(在这里用字母 `T` 来表示)来代替实际类型名(例如 `Int``String``Double`)。占位类型名没有指明 `T` 必须是什么类型,但是它指明了 `a``b` 必须是同一类型 `T`,无论 `T` 代表什么类型。只有 `swapTwoValues(_:_:)` 函数在调用时,才能根据所传入的实际类型决定 `T` 所代表的类型。
另外一个不同之处在于这个泛型函数名(`swapTwoValues(_:_:)`)后面跟着占位类型名(`T`),并用尖括号括起来(`<T>`)。这个尖括号告诉 Swift 那个 `T``swapTwoValues(_:_:)` 函数定义内的一个占位类型名,因此 Swift 不会去查找名为 `T` 的实际类型。
`swapTwoValues(_:_:)` 函数现在可以像 `swapTwoInts(_:_:)` 那样调用,不同的是它能接受两个任意类型的值,条件是这两个值有着相同的类型。`swapTwoValues(_:_:)` 函数被调用时,`T` 所代表的类型都会由传入的值的类型推断出来。
在下面的两个例子中,`T` 分别代表 `Int``String`
```swift
var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt is now 107, and anotherInt is now 3
var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString is now "world", and anotherString is now "hello"
```
> 注意
上面定义的 `swapTwoValues(_:_:)` 函数是受 `swap(_:_:)` 函数启发而实现的。后者存在于 Swift 标准库,你可以在你的应用程序中使用它。如果你在代码中需要类似 `swapTwoValues(_:_:)` 函数的功能,你可以使用已存在的 `swap(_:_:)` 函数。
<a name="type_parameters"></a>
## 类型参数
在上面的 `swapTwoValues(_:_:)` 例子中,占位类型 `T` 是类型参数的一个例子。类型参数指定并命名一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来(例如 `<T>`)。
一旦一个类型参数被指定,你可以用它来定义一个函数的参数类型(例如 `swapTwoValues(_:_:)` 函数中的参数 `a``b`),或者作为函数的返回类型,还可以用作函数主体中的注释类型。在这些情况下,类型参数会在函数调用时被实际类型所替换。(在上面的 `swapTwoValues(_:_:)` 例子中,当函数第一次被调用时,`T``Int` 替换,第二次调用时,被 `String` 替换。)
你可提供多个类型参数,将它们都写在尖括号中,用逗号分开。
<a name="naming_type_parameters"></a>
## 命名类型参数
在大多数情况下,类型参数具有一个描述性名字,例如 `Dictionary<Key, Value>` 中的 `Key``Value`,以及 `Array<Element>` 中的 `Element`,这可以告诉阅读代码的人这些类型参数和泛型函数之间的关系。然而,当它们之间没有有意义的关系时,通常使用单个字母来命名,例如 `T``U``V`,正如上面演示的 `swapTwoValues(_:_:)` 函数中的 `T` 一样。
> 注意
请始终使用大写字母开头的驼峰命名法(例如 `T``MyTypeParameter`)来为类型参数命名,以表明它们是占位类型,而不是一个值。
<a name="generic_types"></a>
## 泛型类型
除了泛型函数Swift 还允许你定义泛型类型。这些自定义类、结构体和枚举可以适用于任何类型,类似于 `Array``Dictionary`
这部分内容将向你展示如何编写一个名为 `Stack` (栈)的泛型集合类型。栈是一系列值的有序集合,和 `Array` 类似,但它相比 Swift 的 `Array` 类型有更多的操作限制。数组允许在数组的任意位置插入新元素或是删除其中任意位置的元素。而栈只允许在集合的末端添加新的元素(称之为入栈)。类似的,栈也只能从末端移除元素(称之为出栈)。
> 注意
栈的概念已被 `UINavigationController` 类用来构造视图控制器的导航结构。你通过调用 `UINavigationController``pushViewController(_:animated:)` 方法来添加新的视图控制器到导航栈,通过 `popViewControllerAnimated(_:)` 方法来从导航栈中移除视图控制器。每当你需要一个严格的“后进先出”方式来管理集合,栈都是最实用的模型。
下图展示了一个栈的入栈push和出栈pop的行为
![此处输入图片的描述](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPushPop_2x.png)
1. 现在有三个值在栈中。
2. 第四个值被压入到栈的顶部。
3. 现在有四个值在栈中,最近入栈的那个值在顶部。
4. 栈中最顶部的那个值被移除,或称之为出栈。
5. 移除掉一个值后,现在栈又只有三个值了。
下面展示了如何编写一个非泛型版本的栈,以 `Int` 型的栈为例:
```swift
struct IntStack {
var items = [Int]()
mutating func push(item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
}
```
这个结构体在栈中使用一个名为 `items``Array` 属性来存储值。`Stack` 提供了两个方法:`push(_:)``pop()`,用来向栈中压入值以及从栈中移除值。这些方法被标记为 `mutating`,因为它们需要修改结构体的 `items` 数组。
上面的 `IntStack` 结构体只能用于 `Int` 类型。不过,可以定义一个泛型 `Stack` 结构体,从而能够处理任意类型的值。
下面是相同代码的泛型版本:
```swift
struct Stack<Element> {
var items = [Element]()
mutating func push(item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
```
注意,`Stack` 基本上和 `IntStack` 相同,只是用占位类型参数 `Element` 代替了实际的 `Int` 类型。这个类型参数包裹在紧随结构体名的一对尖括号里(`<Element>`)。
`Element` 为待提供的类型定义了一个占位名。这种待提供的类型可以在结构体的定义中通过 `Element` 来引用。在这个例子中,`Element` 在如下三个地方被用作占位符:
- 创建 `items` 属性,使用 `Element` 类型的空数组对其进行初始化。
- 指定 `push(_:)` 方法的唯一参数 `item` 的类型必须是 `Element` 类型。
- 指定 `pop()` 方法的返回值类型必须是 `Element` 类型。
由于 `Stack` 是泛型类型,因此可以用来创建 Swift 中任意有效类型的栈,就像 `Array``Dictionary` 那样。
你可以通过在尖括号中写出栈中需要存储的数据类型来创建并初始化一个 `Stack` 实例。例如,要创建一个 `String` 类型的栈,可以写成 `Stack<String>()`
```swift
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
stackOfStrings.push("cuatro")
// 栈中现在有 4 个字符串
```
下图展示了 `stackOfStrings` 如何将这四个值入栈:
![此处输入图片的描述](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPushedFourStrings_2x.png)
移除并返回栈顶部的值 `"cuatro"`,即将其出栈:
```swift
let fromTheTop = stackOfStrings.pop()
// fromTheTop 的值为 "cuatro",现在栈中还有 3 个字符串
```
下图展示了 `stackOfStrings` 如何将顶部的值出栈:
![此处输入图片的描述](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/stackPoppedOneString_2x.png)
<a name="extending_a_generic_type"></a>
## 扩展一个泛型类型
当你扩展一个泛型类型的时候,你并不需要在扩展的定义中提供类型参数列表。原始类型定义中声明的类型参数列表在扩展中可以直接使用,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。
下面的例子扩展了泛型类型 `Stack`,为其添加了一个名为 `topItem` 的只读计算型属性,它将会返回当前栈顶端的元素而不会将其从栈中移除:
```swift
extension Stack {
var topItem: Element? {
return items.isEmpty ? nil : items[items.count - 1]
}
}
```
`topItem` 属性会返回一个 `Element` 类型的可选值。当栈为空的时候,`topItem` 会返回 `nil`;当栈不为空的时候,`topItem` 会返回 `items` 数组中的最后一个元素。
注意,这个扩展并没有定义一个类型参数列表。相反的,`Stack` 类型已有的类型参数名称 `Element`,被用在扩展中来表示计算型属性 `topItem` 的可选类型。
计算型属性 `topItem` 现在可以用来访问任意 `Stack` 实例的顶端元素且不移除它:
```swift
if let topItem = stackOfStrings.topItem {
print("The top item on the stack is \(topItem).")
}
// 打印 “The top item on the stack is tres.”
```
<a name="type_constraints"></a>
## 类型约束
`swapTwoValues(_:_:)` 函数和 `Stack` 类型可以作用于任何类型。不过,有的时候如果能将使用在泛型函数和泛型类型中的类型添加一个特定的类型约束,将会是非常有用的。类型约束可以指定一个类型参数必须继承自指定类,或者符合一个特定的协议或协议组合。
例如Swift 的 `Dictionary` 类型对字典的键的类型做了些限制。在[字典](./04_Collection_Types.html#dictionaries)的描述中,字典的键的类型必须是可哈希(`hashable`)的。也就是说,必须有一种方法能够唯一地表示它。`Dictionary` 的键之所以要是可哈希的,是为了便于检查字典是否已经包含某个特定键的值。若没有这个要求,`Dictionary` 将无法判断是否可以插入或者替换某个指定键的值,也不能查找到已经存储在字典中的指定键的值。
为了实现这个要求,一个类型约束被强制加到 `Dictionary` 的键类型上,要求其键类型必须符合 `Hashable` 协议,这是 Swift 标准库中定义的一个特定协议。所有的 Swift 基本类型(例如 `String``Int``Double``Bool`)默认都是可哈希的。
当你创建自定义泛型类型时,你可以定义你自己的类型约束,这些约束将提供更为强大的泛型编程能力。抽象概念,例如可哈希的,描述的是类型在概念上的特征,而不是它们的显式类型。
<a name="type_constraint_syntax"></a>
### 类型约束语法
你可以在一个类型参数名后面放置一个类名或者协议名,并用冒号进行分隔,来定义类型约束,它们将成为类型参数列表的一部分。对泛型函数添加类型约束的基本语法如下所示(作用于泛型类型时的语法与之相同):
```swift
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// 这里是泛型函数的函数体部分
}
```
上面这个函数有两个类型参数。第一个类型参数 `T`,有一个要求 `T` 必须是 `SomeClass` 子类的类型约束;第二个类型参数 `U`,有一个要求 `U` 必须符合 `SomeProtocol` 协议的类型约束。
<a name="type_constraints_in_action"></a>
### 类型约束实践
这里有个名为 `findStringIndex` 的非泛型函数,该函数的功能是在一个 `String` 数组中查找给定 `String` 值的索引。若查找到匹配的字符串,`findStringIndex(_:_:)` 函数返回该字符串在数组中的索引值,否则返回 `nil`
```swift
func findStringIndex(array: [String], _ valueToFind: String) -> Int? {
for (index, value) in array.enumerate() {
if value == valueToFind {
return index
}
}
return nil
}
```
`findStringIndex(_:_:)` 函数可以用于查找字符串数组中的某个字符串:
```swift
let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
if let foundIndex = findStringIndex(strings, "llama") {
print("The index of llama is \(foundIndex)")
}
// 打印 “The index of llama is 2”
```
如果只能查找字符串在数组中的索引,用处不是很大。不过,你可以用占位类型 `T` 替换 `String` 类型来写出具有相同功能的泛型函数 `findIndex(_:_:)`
下面展示了 `findStringIndex(_:_:)` 函数的泛型版本 `findIndex(_:_:)`。请注意这个函数返回值的类型仍然是 `Int?`,这是因为函数返回的是一个可选的索引数,而不是从数组中得到的一个可选值。需要提醒的是,这个函数无法通过编译,原因会在例子后面说明:
```swift
func findIndex<T>(array: [T], _ valueToFind: T) -> Int? {
for (index, value) in array.enumerate() {
if value == valueToFind {
return index
}
}
return nil
}
```
上面所写的函数无法通过编译。问题出在相等性检查上,即 "`if value == valueToFind`"。不是所有的 Swift 类型都可以用等式符(`==`)进行比较。比如说,如果你创建一个自定义的类或结构体来表示一个复杂的数据模型,那么 Swift 无法猜到对于这个类或结构体而言“相等”意味着什么。正因如此,这部分代码无法保证适用于每个可能的类型 `T`,当你试图编译这部分代码时会出现相应的错误。
不过所有的这些并不会让我们无从下手。Swift 标准库中定义了一个 `Equatable` 协议,该协议要求任何遵循该协议的类型必须实现等式符(`==`)及不等符(`!=`),从而能对该类型的任意两个值进行比较。所有的 Swift 标准类型自动支持 `Equatable` 协议。
任何 `Equatable` 类型都可以安全地使用在 `findIndex(_:_:)` 函数中,因为其保证支持等式操作符。为了说明这个事实,当你定义一个函数时,你可以定义一个 `Equatable` 类型约束作为类型参数定义的一部分:
```swift
func findIndex<T: Equatable>(array: [T], _ valueToFind: T) -> Int? {
for (index, value) in array.enumerate() {
if value == valueToFind {
return index
}
}
return nil
}
```
`findIndex(_:_:)` 唯一的类型参数写做 `T: Equatable`,也就意味着“任何符合 `Equatable` 协议的类型 `T` ”。
`findIndex(_:_:)` 函数现在可以成功编译了,并且可以作用于任何符合 `Equatable` 的类型,如 `Double``String`
```swift
let doubleIndex = findIndex([3.14159, 0.1, 0.25], 9.3)
// doubleIndex 类型为 Int?,其值为 nil因为 9.3 不在数组中
let stringIndex = findIndex(["Mike", "Malcolm", "Andrea"], "Andrea")
// stringIndex 类型为 Int?,其值为 2
```
<a name="associated_types"></a>
## 关联类型
定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分将会非常有用。关联类型为协议中的某个类型提供了一个占位名(或者说别名),其代表的实际类型在协议被采纳时才会被指定。你可以通过 `associatedtype` 关键字来指定关联类型。
<a name="associated_types_in_action"></a>
### 关联类型实践
下面例子定义了一个 `Container` 协议,该协议定义了一个关联类型 `ItemType`
```swift
protocol Container {
associatedtype ItemType
mutating func append(item: ItemType)
var count: Int { get }
subscript(i: Int) -> ItemType { get }
}
```
`Container` 协议定义了三个任何采纳了该协议的类型(即容器)必须提供的功能:
- 必须可以通过 `append(_:)` 方法添加一个新元素到容器里。
- 必须可以通过 `count` 属性获取容器中元素的数量,并返回一个 `Int` 值。
- 必须可以通过索引值类型为 `Int` 的下标检索到容器中的每一个元素。
这个协议没有指定容器中元素该如何存储,以及元素必须是何种类型。这个协议只指定了三个任何遵从 `Container` 协议的类型必须提供的功能。遵从协议的类型在满足这三个条件的情况下也可以提供其他额外的功能。
任何遵从 `Container` 协议的类型必须能够指定其存储的元素的类型,必须保证只有正确类型的元素可以加进容器中,必须明确通过其下标返回的元素的类型。
为了定义这三个条件,`Container` 协议需要在不知道容器中元素的具体类型的情况下引用这种类型。`Container` 协议需要指定任何通过 `append(_:)` 方法添加到容器中的元素和容器中的元素是相同类型,并且通过容器下标返回的元素的类型也是这种类型。
为了达到这个目的,`Container` 协议声明了一个关联类型 `ItemType`,写作 `associatedtype ItemType`。这个协议无法定义 `ItemType` 是什么类型的别名,这个信息将留给遵从协议的类型来提供。尽管如此,`ItemType` 别名提供了一种方式来引用 `Container` 中元素的类型,并将之用于 `append(_:)` 方法和下标,从而保证任何 `Container` 的行为都能够正如预期地被执行。
下面是先前的非泛型的 `IntStack` 类型,这一版本采纳并符合了 `Container` 协议:
```swift
struct IntStack: Container {
// IntStack 的原始实现部分
var items = [Int]()
mutating func push(item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
// Container 协议的实现部分
typealias ItemType = Int
mutating func append(item: Int) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Int {
return items[i]
}
}
```
`IntStack` 结构体实现了 `Container` 协议的三个要求,其原有功能也不会和这些要求相冲突。
此外,`IntStack` 在实现 `Container` 的要求时,指定 `ItemType``Int` 类型,即 `typealias ItemType = Int`,从而将 `Container` 协议中抽象的 `ItemType` 类型转换为具体的 `Int` 类型。
由于 Swift 的类型推断,你实际上不用在 `IntStack` 的定义中声明 `ItemType``Int`。因为 `IntStack` 符合 `Container` 协议的所有要求Swift 只需通过 `append(_:)` 方法的 `item` 参数类型和下标返回值的类型,就可以推断出 `ItemType` 的具体类型。事实上,如果你在上面的代码中删除了 `typealias ItemType = Int` 这一行,一切仍旧可以正常工作,因为 Swift 清楚地知道 `ItemType` 应该是哪种类型。
你也可以让泛型 `Stack` 结构体遵从 `Container` 协议:
```swift
struct Stack<Element>: Container {
// Stack<Element> 的原始实现部分
var items = [Element]()
mutating func push(item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
// Container 协议的实现部分
mutating func append(item: Element) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Element {
return items[i]
}
}
```
这一次,占位类型参数 `Element` 被用作 `append(_:)` 方法的 `item` 参数和下标的返回类型。Swift 可以据此推断出 `Element` 的类型即是 `ItemType` 的类型。
<a name="extending_an_existing_type_to_specify_an_associated_type"></a>
### 通过扩展一个存在的类型来指定关联类型
[通过扩展添加协议一致性](./22_Protocols.html#adding_protocol_conformance_with_an_extension)中描述了如何利用扩展让一个已存在的类型符合一个协议,这包括使用了关联类型的协议。
Swift 的 `Array` 类型已经提供 `append(_:)` 方法,一个 `count` 属性,以及一个接受 `Int` 类型索引值的下标用以检索其元素。这三个功能都符合 `Container` 协议的要求,也就意味着你只需简单地声明 `Array` 采纳该协议就可以扩展 `Array`,使其遵从 `Container` 协议。你可以通过一个空扩展来实现这点,正如[通过扩展采纳协议](./22_Protocols.html#declaring_protocol_adoption_with_an_extension)中的描述:
```swift
extension Array: Container {}
```
如同上面的泛型 `Stack` 结构体一样,`Array``append(_:)` 方法和下标确保了 Swift 可以推断出 `ItemType` 的类型。定义了这个扩展后,你可以将任意 `Array` 当作 `Container` 来使用。
<a name="where_clauses"></a>
## Where 子句
[类型约束](#type_constraints)让你能够为泛型函数或泛型类型的类型参数定义一些强制要求。
为关联类型定义约束也是非常有用的。你可以在参数列表中通过 `where` 子句为关联类型定义约束。你能通过 `where` 子句要求一个关联类型遵从某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。你可以通过将 `where` 关键字紧跟在类型参数列表后面来定义 `where` 子句,`where` 子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。你可以在函数体或者类型的大括号之前添加 where 子句。
下面的例子定义了一个名为 `allItemsMatch` 的泛型函数,用来检查两个 `Container` 实例是否包含相同顺序的相同元素。如果所有的元素能够匹配,那么返回 `true`,否则返回 `false`
被检查的两个 `Container` 可以不是相同类型的容器(虽然它们可以相同),但它们必须拥有相同类型的元素。这个要求通过一个类型约束以及一个 `where` 子句来表示:
```swift
func allItemsMatch<C1: Container, C2: Container>
(_ someContainer: C1, _ anotherContainer: C2) -> Bool
where C1.ItemType == C2.ItemType, C1.ItemType: Equatable {
// 检查两个容器含有相同数量的元素
if someContainer.count != anotherContainer.count {
return false
}
// 检查每一对元素是否相等
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}
// 所有元素都匹配,返回 true
return true
}
```
这个函数接受 `someContainer``anotherContainer` 两个参数。参数 `someContainer` 的类型为 `C1`,参数 `anotherContainer` 的类型为 `C2``C1``C2` 是容器的两个占位类型参数,函数被调用时才能确定它们的具体类型。
这个函数的类型参数列表还定义了对两个类型参数的要求:
- `C1` 必须符合 `Container` 协议(写作 `C1: Container`)。
- `C2` 必须符合 `Container` 协议(写作 `C2: Container`)。
- `C1``ItemType` 必须和 `C2``ItemType`类型相同(写作 `C1.ItemType == C2.ItemType`)。
- `C1``ItemType` 必须符合 `Equatable` 协议(写作 `C1.ItemType: Equatable`)。
第三个和第四个要求被定义为一个 `where` 子句,写在关键字 `where` 后面,它们也是泛型函数类型参数列表的一部分。
这些要求意味着:
- `someContainer` 是一个 `C1` 类型的容器。
- `anotherContainer` 是一个 `C2` 类型的容器。
- `someContainer``anotherContainer` 包含相同类型的元素。
- `someContainer` 中的元素可以通过不等于操作符(`!=`)来检查它们是否彼此不同。
第三个和第四个要求结合起来意味着 `anotherContainer` 中的元素也可以通过 `!=` 操作符来比较,因为它们和 `someContainer` 中的元素类型相同。
这些要求让 `allItemsMatch(_:_:)` 函数能够比较两个容器,即使它们的容器类型不同。
`allItemsMatch(_:_:)` 函数首先检查两个容器是否拥有相同数量的元素,如果它们的元素数量不同,那么一定不匹配,函数就会返回 `false`
进行这项检查之后,通过 `for-in` 循环和半闭区间操作符(`..<`)来迭代每个元素,检查 `someContainer` 中的元素是否不等于 `anotherContainer` 中的对应元素。如果两个元素不相等,那么两个容器不匹配,函数返回 `false`
如果循环体结束后未发现任何不匹配的情况,表明两个容器匹配,函数返回 `true`
下面演示了 `allItemsMatch(_:_:)` 函数的使用:
```swift
var stackOfStrings = Stack<String>()
stackOfStrings.push("uno")
stackOfStrings.push("dos")
stackOfStrings.push("tres")
var arrayOfStrings = ["uno", "dos", "tres"]
if allItemsMatch(stackOfStrings, arrayOfStrings) {
print("All items match.")
} else {
print("Not all items match.")
}
// 打印 “All items match.”
```
上面的例子创建了一个 `Stack` 实例来存储一些 `String` 值,然后将三个字符串压入栈中。这个例子还通过数组字面量创建了一个 `Array` 实例,数组中包含同栈中一样的三个字符串。即使栈和数组是不同的类型,但它们都遵从 `Container` 协议,而且它们都包含相同类型的值。因此你可以用这两个容器作为参数来调用 `allItemsMatch(_:_:)` 函数。在上面的例子中,`allItemsMatch(_:_:)` 函数正确地显示了这两个容器中的所有元素都是相互匹配的。

View File

@ -1,390 +0,0 @@
# 访问控制Access Control
------------------
> 1.0
> 翻译:[JaceFu](http://www.devtalking.com/)
> 校对:[ChildhoodAndy](http://childhood.logdown.com)
> 2.0
> 翻译+校对:[mmoaay](https://github.com/mmoaay)
> 2.1
> 翻译:[Prayer](https://github.com/futantan)
> 校对:[shanks](http://codebuild.me)2015-11-01
>
> 2.2
> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-17
本页内容包括:
- [模块和源文件](#modules_and_source_files)
- [访问级别](#access_levels)
- [访问级别基本原则](#guiding_principle_of_access_levels)
- [默认访问级别](#default_access_levels)
- [单 target 应用程序的访问级别](#access_levels_for_single-target_apps)
- [框架的访问级别](#access_levels_for_frameworks)
- [单元测试 target 的访问级别](#access_levels_for_unit_test_targets)
- [访问控制语法](#access_control_syntax)
- [自定义类型](#custom_types)
- [元组类型](#tuple_types)
- [函数类型](#function_types)
- [枚举类型](#enumeration_types)
- [嵌套类型](#nested_types)
- [子类](#subclassing)
- [常量、变量、属性、下标](#constants_variables_properties_subscripts)
- [Getter和Setter](#getters_and_setters)
- [构造器](#initializers)
- [默认构造器](#default_initializers)
- [结构体默认的成员逐一构造器](#default_memberwise_initializers_for_structure_types)
- [协议](#protocols)
- [协议继承](#protocol_inheritance)
- [协议一致性](#protocol_conformance)
- [扩展](#extensions)
- [通过扩展添加协议一致性](#adding_protocol_conformance_with_an_extension)
- [泛型](#generics)
- [类型别名](#type_aliases)
访问控制可以限定其他源文件或模块中的代码对你的代码的访问级别。这个特性可以让我们隐藏代码的一些实现细节,并且可以为其他人可以访问和使用的代码提供接口。
你可以明确地给单个类型(类、结构体、枚举)设置访问级别,也可以给这些类型的属性、方法、构造器、下标等设置访问级别。协议也可以被限定在一定的范围内使用,包括协议里的全局常量、变量和函数。
Swift 不仅提供了多种不同的访问级别,还为某些典型场景提供了默认的访问级别,这样就不需要我们在每段代码中都申明显式访问级别。其实,如果只是开发一个单一 target 的应用程序,我们完全可以不用显式申明代码的访问级别。
> 注意
为了简单起见,对于代码中可以设置访问级别的特性(属性、基本类型、函数等),在下面的章节中我们会称之为“实体”。
<a name="modules_and_source_files"></a>
## 模块和源文件
Swift 中的访问控制模型基于模块和源文件这两个概念。
模块指的是独立的代码单元,框架或应用程序会作为一个独立的模块来构建和发布。在 Swift 中,一个模块可以使用 `import` 关键字导入另外一个模块。
在 Swift 中Xcode 的每个 target例如框架或应用程序都被当作独立的模块处理。如果你是为了实现某个通用的功能或者是为了封装一些常用方法而将代码打包成独立的框架这个框架就是 Swift 中的一个模块。当它被导入到某个应用程序或者其他框架时,框架内容都将属于这个独立的模块。
源文件就是 Swift 中的源代码文件,它通常属于一个模块,即一个应用程序或者框架。尽管我们一般会将不同的类型分别定义在不同的源文件中,但是同一个源文件也可以包含多个类型、函数之类的定义。
<a name="access_levels"></a>
## 访问级别
Swift 为代码中的实体提供了三种不同的访问级别。这些访问级别不仅与源文件中定义的实体相关,同时也与源文件所属的模块相关。
- `public`:可以访问同一模块源文件中的任何实体,在模块外也可以通过导入该模块来访问源文件里的所有实体。通常情况下,框架中的某个接口可以被任何人使用时,你可以将其设置为 `public` 级别。
- `internal`:可以访问同一模块源文件中的任何实体,但是不能从模块外访问该模块源文件中的实体。通常情况下,某个接口只在应用程序或框架内部使用时,你可以将其设置为 `internal` 级别。
- `private`:限制实体只能在所在的源文件内部使用。使用 `private` 级别可以隐藏某些功能的实现细节。
`public` 为最高(限制最少)访问级别,`private` 为最低(限制最多)访问级别。
> 注意
Swift 中的 `private` 访问级别不同于其他语言,它的范围限于源文件,而不是声明范围内。这就意味着,一个类型可以访问其所在源文件中的所有 `private` 实体,但是如果它的扩展定义在其他源文件中,那么它的扩展就不能访问它在这个源文件中定义的 `private` 实体。
<a name="guiding_principle_of_access_levels"></a>
### 访问级别基本原则
Swift 中的访问级别遵循一个基本原则:不可以在某个实体中定义访问级别更高的实体。
例如:
- 一个 `public` 访问级别的变量,其类型的访问级别不能是 `internal``private`。因为无法保证变量的类型在使用变量的地方也具有访问权限。
- 函数的访问级别不能高于它的参数类型和返回类型的访问级别。因为如果函数定义为 `public` 而参数类型或者返回类型定义为 `internal``private`,就会出现函数可以在任何地方被访问,但是它的参数类型和返回类型却不可以。
<a name="default_access_levels"></a>
### 默认访问级别
如果你不为代码中的实体显式指定访问级别,那么它们默认为 `internal` 级别(有一些例外情况,稍后会进行说明)。因此,在大多数情况下,我们不需要显式指定实体的访问级别。
<a name="access_levels_for_single-target_apps"></a>
### 单 target 应用程序的访问级别
当你编写一个单 target 应用程序时,应用的所有功能都是为该应用服务,而不需要提供给其他应用或者模块使用,所以我们不需要明确设置访问级别,使用默认的访问级别 `internal` 即可。但是,你也可以使用 `private` 级别,用于隐藏一些功能的实现细节。
<a name="access_levels_for_frameworks"></a>
### 框架的访问级别
当你开发框架时,就需要把一些对外的接口定义为 `public` 级别,以便使用者导入该框架后可以正常使用其功能。这些被你定义为 `public` 的接口,就是这个框架的 API。
> 注意
框架依然会使用默认的 `internal` 级别,也可以指定为 `private` 级别。当你想把某个实体作为框架的 API 的时候,需显式为其指定 `public` 级别。
<a name="access_levels_for_unit_test_targets"></a>
### 单元测试 target 的访问级别
当你的应用程序包含单元测试 target 时,为了测试,测试模块需要访问应用程序模块中的代码。默认情况下只有 `public` 级别的实体才可以被其他模块访问。然而,如果在导入应用程序模块的语句前使用 `@testable` 特性,然后在允许测试的编译设置(`Build Options -> Enable Testability`)下编译这个应用程序模块,单元测试 target 就可以访问应用程序模块中所有 `internal` 级别的实体。
<a name="access_control_syntax"></a>
## 访问控制语法
通过修饰符 `public``internal``private` 来声明实体的访问级别:
```swift
public class SomePublicClass {}
internal class SomeInternalClass {}
private class SomePrivateClass {}
public var somePublicVariable = 0
internal let someInternalConstant = 0
private func somePrivateFunction() {}
```
除非专门指定,否则实体默认的访问级别为 `internal`,可以查阅[默认访问级别](#default_access_levels)这一节。这意味着在不使用修饰符显式声明访问级别的情况下,`SomeInternalClass``someInternalConstant` 仍然拥有隐式的访问级别 `internal`
```swift
class SomeInternalClass {} // 隐式访问级别 internal
var someInternalConstant = 0 // 隐式访问级别 internal
```
<a name="custom_types"></a>
## 自定义类型
如果想为一个自定义类型指定访问级别,在定义类型时进行指定即可。新类型只能在它的访问级别限制范围内使用。例如,你定义了一个 `private` 级别的类,那这个类就只能在定义它的源文件中使用,可以作为属性类型、函数参数类型或者返回类型,等等。
一个类型的访问级别也会影响到类型成员(属性、方法、构造器、下标)的默认访问级别。如果你将类型指定为 `private` 级别,那么该类型的所有成员的默认访问级别也会变成 `private`。如果你将类型指定为 `public` 或者 `internal` 级别(或者不明确指定访问级别,而使用默认的 `internal` 访问级别),那么该类型的所有成员的默认访问级别将是 `internal`
> 注意
上面提到,一个 `public` 类型的所有成员的访问级别默认为 `internal` 级别,而不是 `public` 级别。如果你想将某个成员指定为 `public` 级别,那么你必须显式指定。这样做的好处是,在你定义公共接口的时候,可以明确地选择哪些接口是需要公开的,哪些是内部使用的,避免不小心将内部使用的接口公开。
```swift
public class SomePublicClass { // 显式的 public 类
public var somePublicProperty = 0 // 显式的 public 类成员
var someInternalProperty = 0 // 隐式的 internal 类成员
private func somePrivateMethod() {} // 显式的 private 类成员
}
class SomeInternalClass { // 隐式的 internal 类
var someInternalProperty = 0 // 隐式的 internal 类成员
private func somePrivateMethod() {} // 显式的 private 类成员
}
private class SomePrivateClass { // 显式的 private 类
var somePrivateProperty = 0 // 隐式的 private 类成员
func somePrivateMethod() {} // 隐式的 private 类成员
}
```
<a name="tuple_types"></a>
### 元组类型
元组的访问级别将由元组中访问级别最严格的类型来决定。例如,如果你构建了一个包含两种不同类型的元组,其中一个类型为 `internal` 级别,另一个类型为 `private` 级别,那么这个元组的访问级别为 `private`
> 注意
元组不同于类、结构体、枚举、函数那样有单独的定义。元组的访问级别是在它被使用时自动推断出的,而无法明确指定。
<a name="function_types"></a>
### 函数类型
函数的访问级别根据访问级别最严格的参数类型或返回类型的访问级别来决定。但是,如果这种访问级别不符合函数定义所在环境的默认访问级别,那么就需要明确地指定该函数的访问级别。
下面的例子定义了一个名为 `someFunction` 的全局函数,并且没有明确地指定其访问级别。也许你会认为该函数应该拥有默认的访问级别 `internal`,但事实并非如此。事实上,如果按下面这种写法,代码将无法通过编译:
```swift
func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// 此处是函数实现部分
}
```
我们可以看到,这个函数的返回类型是一个元组,该元组中包含两个自定义的类(可查阅[自定义类型](#custom_types))。其中一个类的访问级别是 `internal`,另一个的访问级别是 `private`,所以根据元组访问级别的原则,该元组的访问级别是 `private`(元组的访问级别与元组中访问级别最低的类型一致)。
因为该函数返回类型的访问级别是 `private`,所以你必须使用 `private` 修饰符,明确指定该函数的访问级别:
```swift
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// 此处是函数实现部分
}
```
将该函数指定为 `public``internal`,或者使用默认的访问级别 `internal` 都是错误的,因为如果把该函数当做 `public``internal` 级别来使用的话,可能会无法访问 `private` 级别的返回值。
<a name="enumeration_types"></a>
### 枚举类型
枚举成员的访问级别和该枚举类型相同,你不能为枚举成员单独指定不同的访问级别。
比如下面的例子,枚举 `CompassPoint` 被明确指定为 `public` 级别,那么它的成员 `North``South``East``West` 的访问级别同样也是 `public`
```swift
public enum CompassPoint {
case North
case South
case East
case West
}
```
<a name="raw_values_and_associated_values"></a>
#### 原始值和关联值
枚举定义中的任何原始值或关联值的类型的访问级别至少不能低于枚举类型的访问级别。例如,你不能在一个 `internal` 访问级别的枚举中定义 `private` 级别的原始值类型。
<a name="nested_types"></a>
### 嵌套类型
如果在 `private` 级别的类型中定义嵌套类型,那么该嵌套类型就自动拥有 `private` 访问级别。如果在 `public` 或者 `internal` 级别的类型中定义嵌套类型,那么该嵌套类型自动拥有 `internal` 访问级别。如果想让嵌套类型拥有 `public` 访问级别,那么需要明确指定该嵌套类型的访问级别。
<a name="subclassing"></a>
## 子类
子类的访问级别不得高于父类的访问级别。例如,父类的访问级别是 `internal`,子类的访问级别就不能是 `public`
此外,你可以在符合当前访问级别的条件下重写任意类成员(方法、属性、构造器、下标等)。
可以通过重写为继承来的类成员提供更高的访问级别。下面的例子中,类 `A` 的访问级别是 `public`,它包含一个方法 `someMethod()`,访问级别为 `private`。类 `B` 继承自类 `A`,访问级别为 `internal`,但是在类 `B` 中重写了类 `A` 中访问级别为 `private` 的方法 `someMethod()`,并重新指定为 `internal` 级别。通过这种方式,我们就可以将某类中 `private` 级别的类成员重新指定为更高的访问级别,以便其他人使用:
```swift
public class A {
private func someMethod() {}
}
internal class B: A {
override internal func someMethod() {}
}
```
我们甚至可以在子类中,用子类成员去访问访问级别更低的父类成员,只要这一操作在相应访问级别的限制范围内(也就是说,在同一源文件中访问父类 `private` 级别的成员,在同一模块内访问父类 `internal` 级别的成员):
```swift
public class A {
private func someMethod() {}
}
internal class B: A {
override internal func someMethod() {
super.someMethod()
}
}
```
因为父类 `A` 和子类 `B` 定义在同一个源文件中,所以在子类 `B` 可以在重写的 `someMethod()` 方法中调用 `super.someMethod()`
<a name="constants_variables_properties_subscripts"></a>
## 常量、变量、属性、下标
常量、变量、属性不能拥有比它们的类型更高的访问级别。例如,你不能定义一个 `public` 级别的属性,但是它的类型却是 `private` 级别的。同样,下标也不能拥有比索引类型或返回类型更高的访问级别。
如果常量、变量、属性、下标的类型是 `private` 级别的,那么它们必须明确指定访问级别为 `private`
```swift
private var privateInstance = SomePrivateClass()
```
<a name="getters_and_setters"></a>
### Getter 和 Setter
常量、变量、属性、下标的 `Getters``Setters` 的访问级别和它们所属类型的访问级别相同。
`Setter` 的访问级别可以低于对应的 `Getter` 的访问级别,这样就可以控制变量、属性或下标的读写权限。在 `var``subscript` 关键字之前,你可以通过 `private(set)``internal(set)` 为它们的写入权限指定更低的访问级别。
> 注意
这个规则同时适用于存储型属性和计算型属性。即使你不明确指定存储型属性的 `Getter``Setter`Swift 也会隐式地为其创建 `Getter``Setter`,用于访问该属性的后备存储。使用 `private(set)``internal(set)` 可以改变 `Setter` 的访问级别,这对计算型属性也同样适用。
下面的例子中定义了一个名为 `TrackedString` 的结构体,它记录了 `value` 属性被修改的次数:
```swift
struct TrackedString {
private(set) var numberOfEdits = 0
var value: String = "" {
didSet {
numberOfEdits += 1
}
}
}
```
`TrackedString` 结构体定义了一个用于存储 `String` 值的属性 `value`,并将初始值设为 `""`(一个空字符串)。该结构体还定义了另一个用于存储 `Int` 值的属性 `numberOfEdits`,它用于记录属性 `value` 被修改的次数。这个功能通过属性 `value``didSet` 观察器实现,每当给 `value` 赋新值时就会调用 `didSet` 方法,然后将 `numberOfEdits` 的值加一。
结构体 `TrackedString` 和它的属性 `value` 均没有显式指定访问级别,所以它们都拥有默认的访问级别 `internal`。但是该结构体的 `numberOfEdits` 属性使用了 `private(set)` 修饰符,这意味着 `numberOfEdits` 属性只能在定义该结构体的源文件中赋值。`numberOfEdits` 属性的 `Getter` 依然是默认的访问级别 `internal`,但是 `Setter` 的访问级别是 `private`,这表示该属性只有在当前的源文件中是可读写的,而在当前源文件所属的模块中只是一个可读的属性。
如果你实例化 `TrackedString` 结构体,并多次对 `value` 属性的值进行修改,你就会看到 `numberOfEdits` 的值会随着修改次数而变化:
```swift
var stringToEdit = TrackedString()
stringToEdit.value = "This string will be tracked."
stringToEdit.value += " This edit will increment numberOfEdits."
stringToEdit.value += " So will this one."
print("The number of edits is \(stringToEdit.numberOfEdits)")
// 打印 “The number of edits is 3”
```
虽然你可以在其他的源文件中实例化该结构体并且获取到 `numberOfEdits` 属性的值,但是你不能对其进行赋值。这一限制保护了该记录功能的实现细节,同时还提供了方便的访问方式。
你可以在必要时为 `Getter``Setter` 显式指定访问级别。下面的例子将 `TrackedString` 结构体明确指定为了 `public` 访问级别。结构体的成员(包括 `numberOfEdits` 属性)拥有默认的访问级别 `internal`。你可以结合 `public``private(set)` 修饰符把结构体中的 `numberOfEdits` 属性的 `Getter` 的访问级别设置为 `public`,而 `Setter` 的访问级别设置为 `private`
```swift
public struct TrackedString {
public private(set) var numberOfEdits = 0
public var value: String = "" {
didSet {
numberOfEdits += 1
}
}
public init() {}
}
```
<a name="initializers"></a>
## 构造器
自定义构造器的访问级别可以低于或等于其所属类型的访问级别。唯一的例外是[必要构造器](./14_Initialization.html#required_initializers),它的访问级别必须和所属类型的访问级别相同。
如同函数或方法的参数,构造器参数的访问级别也不能低于构造器本身的访问级别。
<a name="default_initializers"></a>
### 默认构造器
如[默认构造器](./14_Initialization.html#default_initializers)所述Swift 会为结构体和类提供一个默认的无参数的构造器,只要它们为所有存储型属性设置了默认初始值,并且未提供自定义的构造器。
默认构造器的访问级别与所属类型的访问级别相同,除非类型的访问级别是 `public`。如果一个类型被指定为 `public` 级别,那么默认构造器的访问级别将为 `internal`。如果你希望一个 `public` 级别的类型也能在其他模块中使用这种无参数的默认构造器,你只能自己提供一个 `public` 访问级别的无参数构造器。
<a name="default_memberwise_initializers_for_structure_types"></a>
### 结构体默认的成员逐一构造器
如果结构体中任意存储型属性的访问级别为 `private`,那么该结构体默认的成员逐一构造器的访问级别就是 `private`。否则,这种构造器的访问级别依然是 `internal`
如同前面提到的默认构造器,如果你希望一个 `public` 级别的结构体也能在其他模块中使用其默认的成员逐一构造器,你依然只能自己提供一个 `public` 访问级别的成员逐一构造器。
<a name="protocols"></a>
## 协议
如果想为一个协议类型明确地指定访问级别,在定义协议时指定即可。这将限制该协议只能在适当的访问级别范围内被采纳。
协议中的每一个要求都具有和该协议相同的访问级别。你不能将协议中的要求设置为其他访问级别。这样才能确保该协议的所有要求对于任意采纳者都将可用。
> 注意
如果你定义了一个 `public` 访问级别的协议,那么该协议的所有实现也会是 `public` 访问级别。这一点不同于其他类型,例如,当类型是 `public` 访问级别时,其成员的访问级别却只是 `internal`
<a name="protocol_inheritance"></a>
### 协议继承
如果定义了一个继承自其他协议的新协议,那么新协议拥有的访问级别最高也只能和被继承协议的访问级别相同。例如,你不能将继承自 `internal` 协议的新协议定义为 `public` 协议。
<a name="protocol_conformance"></a>
### 协议一致性
一个类型可以采纳比自身访问级别低的协议。例如,你可以定义一个 `public` 级别的类型,它可以在其他模块中使用,同时它也可以采纳一个 `internal` 级别的协议,但是只能在该协议所在的模块中作为符合该协议的类型使用。
采纳了协议的类型的访问级别取它本身和所采纳协议两者间最低的访问级别。也就是说如果一个类型是 `public` 级别,采纳的协议是 `internal` 级别,那么采纳了这个协议后,该类型作为符合协议的类型时,其访问级别也是 `internal`
如果你采纳了协议,那么实现了协议的所有要求后,你必须确保这些实现的访问级别不能低于协议的访问级别。例如,一个 `public` 级别的类型,采纳了 `internal` 级别的协议,那么协议的实现至少也得是 `internal` 级别。
> 注意
Swift 和 Objective-C 一样,协议的一致性是全局的,也就是说,在同一程序中,一个类型不可能用两种不同的方式实现同一个协议。
<a name="extensions"></a>
## 扩展
你可以在访问级别允许的情况下对类、结构体、枚举进行扩展。扩展成员具有和原始类型成员一致的访问级别。例如,你扩展了一个 `public` 或者 `internal` 类型,扩展中的成员具有默认的 `internal` 访问级别,和原始类型中的成员一致 。如果你扩展了一个 `private` 类型,扩展成员则拥有默认的 `private` 访问级别。
或者,你可以明确指定扩展的访问级别(例如,`private extension`),从而给该扩展中的所有成员指定一个新的默认访问级别。这个新的默认访问级别仍然可以被单独指定的访问级别所覆盖。
<a name="adding_protocol_conformance_with_an_extension"></a>
### 通过扩展添加协议一致性
如果你通过扩展来采纳协议,那么你就不能显式指定该扩展的访问级别了。协议拥有相应的访问级别,并会为该扩展中所有协议要求的实现提供默认的访问级别。
<a name="generics"></a>
## 泛型
泛型类型或泛型函数的访问级别取决于泛型类型或泛型函数本身的访问级别,还需结合类型参数的类型约束的访问级别,根据这些访问级别中的最低访问级别来确定。
<a name="type_aliases"></a>
## 类型别名
你定义的任何类型别名都会被当作不同的类型,以便于进行访问控制。类型别名的访问级别不可高于其表示的类型的访问级别。例如,`private` 级别的类型别名可以作为 `public``internal``private` 类型的别名,但是 `public` 级别的类型别名只能作为 `public` 类型的别名,不能作为 `internal``private` 类型的别名。
> 注意
这条规则也适用于为满足协议一致性而将类型别名用于关联类型的情况。

View File

@ -0,0 +1,200 @@
# 内存安全
默认情况下Swift 会阻止你代码里不安全的行为。例如Swift 会保证变量在使用之前就完成初始化,在内存被回收之后就无法被访问,并且数组的索引会做越界检查。
Swift 也保证同时访问同一块内存时不会冲突,通过约束代码里对于存储地址的写操作,去获取那一块内存的访问独占权。因为 Swift 自动管理内存,所以大部分时候你完全不需要考虑内存访问的事情。然而,理解潜在的冲突也是很重要的,可以避免你写出访问冲突的代码。而如果你的代码确实存在冲突,那在编译时或者运行时就会得到错误。
## 理解内存访问冲突 {#understanding-conflicting-access-to-memory}
内存的访问,会发生在你给变量赋值,或者传递参数给函数时。例如,下面的代码就包含了读和写的访问:
```swift
// 向 one 所在的内存区域发起一次写操作
var one = 1
// 向 one 所在的内存区域发起一次读操作
print("We're number \(one)!")
```
内存访问的冲突会发生在你的代码尝试同时访问同一个存储地址的时侯。同一个存储地址的多个访问同时发生会造成不可预计或不一致的行为。在 Swift 里,有很多修改值的行为都会持续好几行代码,在修改值的过程中进行访问是有可能发生的。
你可以思考一下预算表更新的过程,会看到同样的问题。更新预算表总共有两步:首先你把预算项的名字和费用加上,然后再更新总数来反映预算表的现况。在更新之前和之后,你都可以从预算表里读取任何信息并获得正确的答案,就像下面展示的那样。
![](https://docs.swift.org/swift-book/_images/memory_shopping_2x.png)
而当你添加预算项进入表里的时候,它只是在一个临时的,错误的状态,因为总数还没有被更新。在添加数据的过程中读取总数就会读取到错误的信息。
这个例子也演示了你在修复内存访问冲突时会遇到的问题:有时修复的方式会有很多种,但哪一种是正确的就不总是那么明显了。在这个例子里,根据你是否需要更新后的总数,$5 和 $320 都可能是正确的值。在你修复访问冲突之前,你需要决定它的倾向。
> 注意
>
> 如果你写过并发和多线程的代码,内存访问冲突也许是同样的问题。然而,这里访问冲突的讨论是在单线程的情境下讨论的,并没有使用并发或者多线程。
>
> 如果你曾经在单线程代码里有访问冲突Swift 可以保证你在编译或者运行时会得到错误。对于多线程的代码,可以使用 [Thread Sanitizer](https://developer.apple.com/documentation/code_diagnostics/thread_sanitizer) 去帮助检测多线程的冲突。
### 内存访问性质 {#characteristics-of-memory-access}
内存访问冲突时,要考虑内存访问上下文中的这三个性质:访问是读还是写,访问的时长,以及被访问的存储地址。特别是,冲突会发生在当你有两个访问符合下列的情况:
* 至少有一个是写访问
* 它们访问的是同一个存储地址
* 它们的访问在时间线上部分重叠
读和写访问的区别很明显:一个写访问会改变存储地址,而读操作不会。存储地址是指向正在访问的东西(例如一个变量,常量或者属性)的位置的值 。内存访问的时长要么是瞬时的,要么是长期的。
如果一个访问不可能在其访问期间被其它代码访问,那么就是一个瞬时访问。正常来说,两个瞬时访问是不可能同时发生的。大多数内存访问都是瞬时的。例如,下面列举的所有读和写访问都是瞬时的:
```swift
func oneMore(than number: Int) -> Int {
return number + 1
}
var myNumber = 1
myNumber = oneMore(than: myNumber)
print(myNumber)
// 打印“2”
```
然而,有几种被称为长期访问的内存访问方式,会在别的代码执行时持续进行。瞬时访问和长期访问的区别在于别的代码有没有可能在访问期间同时访问,也就是在时间线上的重叠。一个长期访问可以被别的长期访问或瞬时访问重叠。
重叠的访问主要出现在使用 in-out 参数的函数和方法或者结构体的 mutating 方法里。Swift 代码里典型的长期访问会在后面进行讨论。
## In-Out 参数的访问冲突 {#conflicting-access-to-in-out-parameters}
一个函数会对它所有的 in-out 参数进行长期写访问。in-out 参数的写访问会在所有非 in-out 参数处理完之后开始,直到函数执行完毕为止。如果有多个 in-out 参数,则写访问开始的顺序与参数的顺序一致。
长期访问的存在会造成一个结果,你不能在访问以 in-out 形式传入后的原变量,即使作用域原则和访问权限允许——任何访问原变量的行为都会造成冲突。例如:
```swift
var stepSize = 1
func increment(_ number: inout Int) {
number += stepSize
}
increment(&stepSize)
// 错误stepSize 访问冲突
```
在上面的代码里,`stepSize` 是一个全局变量,并且它可以在 `increment(_:)` 里正常访问。然而,对于 `stepSize` 的读访问与 `number` 的写访问重叠了。就像下面展示的那样,`number``stepSize` 都指向了同一个存储地址。同一块内存的读和写访问重叠了,就此产生了冲突。
![](https://docs.swift.org/swift-book/_images/memory_increment_2x.png)
解决这个冲突的一种方式,是显示拷贝一份 `stepSize`
```swift
// 显式拷贝
var copyOfStepSize = stepSize
increment(&copyOfStepSize)
// 更新原来的值
stepSize = copyOfStepSize
// stepSize 现在的值是 2
```
当你在调用 `increment(_:)` 之前做一份拷贝,显然 `copyOfStepSize` 就会根据当前的 `stepSize` 增加。读访问在写操作之前就已经结束了,所以不会有冲突。
长期写访问的存在还会造成另一种结果,往同一个函数的多个 in-out 参数里传入同一个变量也会产生冲突,例如:
```swift
func balance(_ x: inout Int, _ y: inout Int) {
let sum = x + y
x = sum / 2
y = sum - x
}
var playerOneScore = 42
var playerTwoScore = 30
balance(&playerOneScore, &playerTwoScore) // 正常
balance(&playerOneScore, &playerOneScore)
// 错误playerOneScore 访问冲突
```
上面的 `balance(_:_:)` 函数会将传入的两个参数平均化。将 `playerOneScore``playerTwoScore` 作为参数传入不会产生错误 —— 有两个访问重叠了,但它们访问的是不同的内存位置。相反,将 `playerOneScore` 作为参数同时传入就会产生冲突,因为它会发起两个写访问,同时访问同一个的存储地址。
> 注意
>
> 因为操作符也是函数,它们也会对 in-out 参数进行长期访问。例如,假设 `balance(_:_:)` 是一个名为 `<^>` 的操作符函数,那么 `playerOneScore <^> playerOneScore` 也会造成像 `balance(&playerOneScore, &playerOneScore)` 一样的冲突。
## 方法里 self 的访问冲突 {#conflicting-access-to-self-in-methods}
一个结构体的 mutating 方法会在调用期间对 `self` 进行写访问。例如,想象一下这么一个游戏,每一个玩家都有血量,受攻击时血量会下降,并且有敌人的数量,使用特殊技能时会减少敌人数量。
```swift
struct Player {
var name: String
var health: Int
var energy: Int
static let maxHealth = 10
mutating func restoreHealth() {
health = Player.maxHealth
}
}
```
在上面的 `restoreHealth()` 方法里,一个对于 `self` 的写访问会从方法开始直到方法 return。在这种情况下`restoreHealth()` 里的其它代码不可以对 `Player` 实例的属性发起重叠的访问。下面的 `shareHealth(with:)` 方法接受另一个 `Player` 的实例作为 in-out 参数,产生了访问重叠的可能性。
```swift
extension Player {
mutating func shareHealth(with teammate: inout Player) {
balance(&teammate.health, &health)
}
}
var oscar = Player(name: "Oscar", health: 10, energy: 10)
var maria = Player(name: "Maria", health: 5, energy: 10)
oscar.shareHealth(with: &maria) // 正常
```
上面的例子里,调用 `shareHealth(with:)` 方法去把 `oscar` 玩家的血量分享给 `maria` 玩家并不会造成冲突。在方法调用期间会对 `oscar` 发起写访问,因为在 mutating 方法里 `self` 就是 `oscar`,同时对于 `maria` 也会发起写访问,因为 `maria` 作为 in-out 参数传入。过程如下,它们会访问内存的不同位置。即使两个写访问重叠了,它们也不会冲突。
![](https://docs.swift.org/swift-book/_images/memory_share_health_maria_2x.png)
当然,如果你将 `oscar` 作为参数传入 `shareHealth(with:)` 里,就会产生冲突:
```swift
oscar.shareHealth(with: &oscar)
// 错误oscar 访问冲突
```
mutating 方法在调用期间需要对 `self` 发起写访问,而同时 in-out 参数也需要写访问。在方法里,`self``teammate` 都指向了同一个存储地址——就像下面展示的那样。对于同一块内存同时进行两个写访问,并且它们重叠了,就此产生了冲突。
![](https://docs.swift.org/swift-book/_images/memory_share_health_oscar_2x.png)
## 属性的访问冲突 {#conflicting-access-to-properties}
如结构体,元组和枚举的类型都是由多个独立的值组成的,例如结构体的属性或元组的元素。因为它们都是值类型,修改值的任何一部分都是对于整个值的修改,意味着其中一个属性的读或写访问都需要访问整一个值。例如,元组元素的写访问重叠会产生冲突:
```swift
var playerInformation = (health: 10, energy: 20)
balance(&playerInformation.health, &playerInformation.energy)
// 错误playerInformation 的属性访问冲突
```
上面的例子里,传入同一元组的元素对 `balance(_:_:)` 进行调用,产生了冲突,因为 `playerInformation` 的访问产生了写访问重叠。`playerInformation.health``playerInformation.energy` 都被作为 in-out 参数传入,意味着 `balance(_:_:)` 需要在函数调用期间对它们发起写访问。任何情况下,对于元组元素的写访问都需要对整个元组发起写访问。这意味着对于 `playerInfomation` 发起的两个写访问重叠了,造成冲突。
下面的代码展示了一样的错误,对于一个存储在全局变量里的结构体属性的写访问重叠了。
```swift
var holly = Player(name: "Holly", health: 10, energy: 10)
balance(&holly.health, &holly.energy) // 错误
```
在实践中,大多数对于结构体属性的访问都会安全的重叠。例如,将上面例子里的变量 `holly` 改为本地变量而非全局变量,编译器就会可以保证这个重叠访问是安全的:
```swift
func someFunction() {
var oscar = Player(name: "Oscar", health: 10, energy: 10)
balance(&oscar.health, &oscar.energy) // 正常
}
```
上面的例子里,`oscar``health``energy` 都作为 in-out 参数传入了 `balance(_:_:)` 里。编译器可以保证内存安全,因为两个存储属性任何情况下都不会相互影响。
限制结构体属性的重叠访问对于保证内存安全不是必要的。保证内存安全是必要的,但因为访问独占权的要求比内存安全还要更严格——意味着即使有些代码违反了访问独占权的原则,也是内存安全的,所以如果编译器可以保证这种非专属的访问是安全的,那 Swift 就会允许这种行为的代码运行。特别是当你遵循下面的原则时,它可以保证结构体属性的重叠访问是安全的:
* 你访问的是实例的存储属性,而不是计算属性或类的属性
* 结构体是本地变量的值,而非全局变量
* 结构体要么没有被闭包捕获,要么只被非逃逸闭包捕获了
如果编译器无法保证访问的安全性,它就不会允许那次访问。

View File

@ -0,0 +1,375 @@
# 访问控制
*访问控制*可以限定其它源文件或模块中的代码对你的代码的访问级别。这个特性可以让我们隐藏代码的一些实现细节,并且可以为其他人可以访问和使用的代码提供接口。
你可以明确地给单个类型(类、结构体、枚举)设置访问级别,也可以给这些类型的属性、方法、构造器、下标等设置访问级别。协议也可以被限定在一定的范围内使用,包括协议里的全局常量、变量和函数。
Swift 不仅提供了多种不同的访问级别,还为某些典型场景提供了默认的访问级别,这样就不需要我们在每段代码中都申明显式访问级别。其实,如果只是开发一个单一 target 的应用程序,我们完全可以不用显式声明代码的访问级别。
> 注意
>
> 为了简单起见,对于代码中可以设置访问级别的特性(属性、基本类型、函数等),在下面的章节中我们会称之为“实体”。
## 模块和源文件 {#modules-and-source-files}
Swift 中的访问控制模型基于模块和源文件这两个概念。
*模块*指的是独立的代码单元,框架或应用程序会作为一个独立的模块来构建和发布。在 Swift 中,一个模块可以使用 `import` 关键字导入另外一个模块。
在 Swift 中Xcode 的每个 target例如框架或应用程序都被当作独立的模块处理。如果你是为了实现某个通用的功能或者是为了封装一些常用方法而将代码打包成独立的框架这个框架就是 Swift 中的一个模块。当它被导入到某个应用程序或者其他框架时,框架内容都将属于这个独立的模块。
*源文件*就是 Swift 中的源代码文件,它通常属于一个模块,即一个应用程序或者框架。尽管我们一般会将不同的类型分别定义在不同的源文件中,但是同一个源文件也可以包含多个类型、函数之类的定义。
## 访问级别 {#access-levels}
Swift 为代码中的实体提供了五种不同的*访问级别*。这些访问级别不仅与源文件中定义的实体相关,同时也与源文件所属的模块相关。
- *Open* 和 *Public* 级别可以让实体被同一模块源文件中的所有实体访问,在模块外也可以通过导入该模块来访问源文件里的所有实体。通常情况下,你会使用 Open 或 Public 级别来指定框架的外部接口。Open 和 Public 的区别在后面会提到。
- *Internal* 级别让实体被同一模块源文件中的任何实体访问,但是不能被模块外的实体访问。通常情况下,如果某个接口只在应用程序或框架内部使用,就可以将其设置为 Internal 级别。
- *File-private* 限制实体只能在其定义的文件内部访问。如果功能的部分细节只需要在文件内使用时,可以使用 File-private 来将其隐藏。
- *Private* 限制实体只能在其定义的作用域,以及同一文件内的 extension 访问。如果功能的部分细节只需要在当前作用域内使用时,可以使用 Private 来将其隐藏。
Open 为最高访问级别限制最少Private 为最低访问级别(限制最多)。
Open 只能作用于类和类的成员,它和 Public 的区别如下:
* Public 或者其它更严访问级别的类,只能在其定义的模块内部被继承。
* Public 或者其它更严访问级别的类成员,只能在其定义的模块内部的子类中重写。
* Open 的类,可以在其定义的模块中被继承,也可以在引用它的模块中被继承。
* Open 的类成员,可以在其定义的模块中子类中重写,也可以在引用它的模块中的子类重写。
把一个类标记为 `open`,明确的表示你已经充分考虑过外部模块使用此类作为父类的影响,并且设计好了你的类的代码了。
### 访问级别基本原则 {#guiding-principle-of-access-levels}
Swift 中的访问级别遵循一个基本原则:*实体不能定义在具有更低访问级别(更严格)的实体中*。
例如:
- 一个 Public 的变量,其类型的访问级别不能是 InternalFile-private 或是 Private。因为无法保证变量的类型在使用变量的地方也具有访问权限。
- 函数的访问级别不能高于它的参数类型和返回类型的访问级别。因为这样就会出现函数可以在任何地方被访问,但是它的参数类型和返回类型却不可以的情况。
关于此原则在各种情况下的具体表现,将在下文有所体现。
### 默认访问级别 {#default-access-levels}
如果你没有为代码中的实体显式指定访问级别,那么它们默认为 `internal` 级别(有一些例外情况,稍后会进行说明)。因此,在大多数情况下,我们不需要显式指定实体的访问级别。
### 单 target 应用程序的访问级别 {#access-levels-for-single-target-apps}
当你编写一个单目标应用程序时,应用的所有功能都是为该应用服务,而不需要提供给其他应用或者模块使用,所以我们不需要明确设置访问级别,使用默认的访问级别 Internal 即可。但是,你也可以使用 `fileprivate` 访问或 `private` 访问级别,用于隐藏一些功能的实现细节。
### 框架的访问级别 {#access-levels-for-frameworks}
当你开发框架时,就需要把一些对外的接口定义为 Open 或 Public以便使用者导入该框架后可以正常使用其功能。这些被你定义为对外的接口就是这个框架的 API。
> 注意
>
> 框架的内部实现仍然可以使用默认的访问级别 `internal`,当你需要对框架内部其它部分隐藏细节时可以使用 `private` 或 `fileprivate`。对于框架的对外 API 部分,你就需要将它们设置为 `open` 或 `public` 了。
### 单元测试 target 的访问级别 {#access-levels-for-unit-test-targets}
当你的应用程序包含单元测试 target 时,为了测试,测试模块需要访问应用程序模块中的代码。默认情况下只有 `open``public` 级别的实体才可以被其他模块访问。然而,如果在导入应用程序模块的语句前使用 `@testable` 特性,然后在允许测试的编译设置(`Build Options -> Enable Testability`)下编译这个应用程序模块,单元测试目标就可以访问应用程序模块中所有内部级别的实体。
## 访问控制语法 {#access-control-syntax}
通过修饰符 `open``public``internal``fileprivate``private` 来声明实体的访问级别:
```swift
public class SomePublicClass {}
internal class SomeInternalClass {}
fileprivate class SomeFilePrivateClass {}
private class SomePrivateClass {}
public var somePublicVariable = 0
internal let someInternalConstant = 0
fileprivate func someFilePrivateFunction() {}
private func somePrivateFunction() {}
```
除非专门指定,否则实体默认的访问级别为 `internal`,可以查阅 [默认访问级别](#default_access_levels) 这一节。这意味着在不使用修饰符显式声明访问级别的情况下,`SomeInternalClass``someInternalConstant` 仍然拥有隐式的 `internal`
```swift
class SomeInternalClass {} // 隐式 internal
var someInternalConstant = 0 // 隐式 internal
```
## 自定义类型 {#custom-types}
如果想为一个自定义类型指定访问级别,在定义类型时进行指定即可。新类型只能在它的访问级别限制范围内使用。例如,你定义了一个 `fileprivate` 级别的类,那这个类就只能在定义它的源文件中使用,可以作为属性类型、函数参数类型或者返回类型,等等。
一个类型的访问级别也会影响到类型*成员*(属性、方法、构造器、下标)的默认访问级别。如果你将类型指定为 `private` 或者 `fileprivate` 级别,那么该类型的所有成员的默认访问级别也会变成 `private` 或者 `fileprivate` 级别。如果你将类型指定为 `internal``public`(或者不明确指定访问级别,而使用默认的 `internal` ),那么该类型的所有成员的默认访问级别将是 `internal`
> 重点
>
> 上面提到,一个 `public` 类型的所有成员的访问级别默认为 `internal` 级别,而不是 `public` 级别。如果你想将某个成员指定为 `public` 级别,那么你必须显式指定。这样做的好处是,在你定义公共接口的时候,可以明确地选择哪些接口是需要公开的,哪些是内部使用的,避免不小心将内部使用的接口公开。
```swift
public class SomePublicClass { // 显式 public 类
public var somePublicProperty = 0 // 显式 public 类成员
var someInternalProperty = 0 // 隐式 internal 类成员
fileprivate func someFilePrivateMethod() {} // 显式 fileprivate 类成员
private func somePrivateMethod() {} // 显式 private 类成员
}
class SomeInternalClass { // 隐式 internal 类
var someInternalProperty = 0 // 隐式 internal 类成员
fileprivate func someFilePrivateMethod() {} // 显式 fileprivate 类成员
private func somePrivateMethod() {} // 显式 private 类成员
}
fileprivate class SomeFilePrivateClass { // 显式 fileprivate 类
func someFilePrivateMethod() {} // 隐式 fileprivate 类成员
private func somePrivateMethod() {} // 显式 private 类成员
}
private class SomePrivateClass { // 显式 private 类
func somePrivateMethod() {} // 隐式 private 类成员
}
```
### 元组类型 {#tuple-types}
元组的访问级别将由元组中访问级别最严格的类型来决定。例如,如果你构建了一个包含两种不同类型的元组,其中一个类型为 `internal`,另一个类型为 `private`,那么这个元组的访问级别为 `private`
> 注意
>
> 元组不同于类、结构体、枚举、函数那样有单独的定义。元组的访问级别是在它被使用时自动推断出的,而无法明确指定。
### 函数类型 {#function-types}
函数的访问级别根据访问级别最严格的参数类型或返回类型的访问级别来决定。但是,如果这种访问级别不符合函数定义所在环境的默认访问级别,那么就需要明确地指定该函数的访问级别。
下面的例子定义了一个名为 `someFunction()` 的全局函数,并且没有明确地指定其访问级别。也许你会认为该函数应该拥有默认的访问级别 `internal`,但事实并非如此。事实上,如果按下面这种写法,代码将无法通过编译:
```swift
func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// 此处是函数实现部分
}
```
我们可以看到,这个函数的返回类型是一个元组,该元组中包含两个自定义的类(可查阅 [自定义类型](#custom_types))。其中一个类的访问级别是 `internal`,另一个的访问级别是 `private`,所以根据元组访问级别的原则,该元组的访问级别是 `private`(元组的访问级别与元组中访问级别最低的类型一致)。
因为该函数返回类型的访问级别是 `private`,所以你必须使用 `private` 修饰符,明确指定该函数的访问级别:
```swift
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
// 此处是函数实现部分
}
```
将该函数指定为 `public``internal`,或者使用默认的访问级别 `internal` 都是错误的,因为如果把该函数当做 `public``internal` 级别来使用的话,可能会无法访问 `private` 级别的返回值。
### 枚举类型 {#enumeration-types}
枚举成员的访问级别和该枚举类型相同,你不能为枚举成员单独指定不同的访问级别。
比如下面的例子,枚举 `CompassPoint` 被明确指定为 `public`,那么它的成员 `North``South``East``West` 的访问级别同样也是 `public`
```swift
public enum CompassPoint {
case north
case south
case east
case west
}
```
#### 原始值和关联值 {#raw-values-and-associated-values}
枚举定义中的任何原始值或关联值的类型的访问级别至少不能低于枚举类型的访问级别。例如,你不能在一个 `internal` 的枚举中定义 `private` 的原始值类型。
### 嵌套类型 {#nested-types}
如果在 `private` 的类型中定义嵌套类型,那么该嵌套类型就自动拥有 `private` 访问级别。如果在 `public` 或者 `internal` 级别的类型中定义嵌套类型,那么该嵌套类型自动拥有 `internal` 访问级别。如果想让嵌套类型拥有 `public` 访问级别,那么需要明确指定该嵌套类型的访问级别。
## 子类 {#subclassing}
子类的访问级别不得高于父类的访问级别。例如,父类的访问级别是 `internal`,子类的访问级别就不能是 `public`
此外,你可以在符合当前访问级别的条件下重写任意类成员(方法、属性、构造器、下标等)。
可以通过重写为继承来的类成员提供更高的访问级别。下面的例子中,类 `A` 的访问级别是 `public`,它包含一个方法 `someMethod()`,访问级别为 `private`。类 `B` 继承自类 `A`,访问级别为 `internal`,但是在类 `B` 中重写了类 `A` 中访问级别为 `private` 的方法 `someMethod()`,并重新指定为 `internal` 级别。通过这种方式,我们就可以将某类中 `private` 级别的类成员重新指定为更高的访问级别,以便其他人使用:
```swift
public class A {
fileprivate func someMethod() {}
}
internal class B: A {
override internal func someMethod() {}
}
```
我们甚至可以在子类中,用子类成员去访问访问级别更低的父类成员,只要这一操作在相应访问级别的限制范围内(也就是说,在同一源文件中访问父类 `private` 级别的成员,在同一模块内访问父类 `internal` 级别的成员):
```swift
public class A {
fileprivate func someMethod() {}
}
internal class B: A {
override internal func someMethod() {
super.someMethod()
}
}
```
因为父类 `A` 和子类 `B` 定义在同一个源文件中,所以在子类 `B` 可以在重写的 `someMethod()` 方法中调用 `super.someMethod()`
## 常量、变量、属性、下标 {#constants-variables-properties-subscripts}
常量、变量、属性不能拥有比它们的类型更高的访问级别。例如,你不能定义一个 `public` 级别的属性,但是它的类型却是 `private` 级别的。同样,下标也不能拥有比索引类型或返回类型更高的访问级别。
如果常量、变量、属性、下标的类型是 `private` 级别的,那么它们必须明确指定访问级别为 `private`
```swift
private var privateInstance = SomePrivateClass()
```
### Getter 和 Setter {#getters-and-setters}
常量、变量、属性、下标的 `Getters``Setters` 的访问级别和它们所属类型的访问级别相同。
`Setter` 的访问级别可以低于对应的 `Getter` 的访问级别,这样就可以控制变量、属性或下标的读写权限。在 `var``subscript` 关键字之前,你可以通过 `fileprivate(set)``private(set)``internal(set)` 为它们的写入权限指定更低的访问级别。
> 注意
>
> 这个规则同时适用于存储型属性和计算型属性。即使你不明确指定存储型属性的 `Getter` 和 `Setter`Swift 也会隐式地为其创建 `Getter` 和 `Setter`,用于访问该属性的后备存储。使用 `fileprivate(set)``private(set)` 和 `internal(set)` 可以改变 `Setter` 的访问级别,这对计算型属性也同样适用。
下面的例子中定义了一个名为 `TrackedString` 的结构体,它记录了 `value` 属性被修改的次数:
```swift
struct TrackedString {
private(set) var numberOfEdits = 0
var value: String = "" {
didSet {
numberOfEdits += 1
}
}
}
```
`TrackedString` 结构体定义了一个用于存储 `String` 值的属性 `value`,并将初始值设为 `""`(一个空字符串)。该结构体还定义了另一个用于存储 `Int` 值的属性 `numberOfEdits`,它用于记录属性 `value` 被修改的次数。这个功能通过属性 `value``didSet` 观察器实现,每当给 `value` 赋新值时就会调用 `didSet` 方法,然后将 `numberOfEdits` 的值加一。
结构体 `TrackedString` 和它的属性 `value` 都没有显式地指定访问级别,所以它们都是用默认的访问级别 `internal`。但是该结构体的 `numberOfEdits` 属性使用了 `private(set)` 修饰符,这意味着 `numberOfEdits` 属性只能在结构体的定义中进行赋值。`numberOfEdits` 属性的 `Getter` 依然是默认的访问级别 `internal`,但是 `Setter` 的访问级别是 `private`,这表示该属性只能在内部修改,而在结构体的外部则表现为一个只读属性。
如果你实例化 `TrackedString` 结构体,并多次对 `value` 属性的值进行修改,你就会看到 `numberOfEdits` 的值会随着修改次数而变化:
```swift
var stringToEdit = TrackedString()
stringToEdit.value = "This string will be tracked."
stringToEdit.value += " This edit will increment numberOfEdits."
stringToEdit.value += " So will this one."
print("The number of edits is \(stringToEdit.numberOfEdits)")
// 打印“The number of edits is 3”
```
虽然你可以在其他的源文件中实例化该结构体并且获取到 `numberOfEdits` 属性的值,但是你不能对其进行赋值。这一限制保护了该记录功能的实现细节,同时还提供了方便的访问方式。
你可以在必要时为 `Getter``Setter` 显式指定访问级别。下面的例子将 `TrackedString` 结构体明确指定为了 `public` 访问级别。结构体的成员(包括 `numberOfEdits` 属性)拥有默认的访问级别 `internal`。你可以结合 `public``private(set)` 修饰符把结构体中的 `numberOfEdits` 属性的 `Getter` 的访问级别设置为 `public`,而 `Setter` 的访问级别设置为 `private`
```swift
public struct TrackedString {
public private(set) var numberOfEdits = 0
public var value: String = "" {
didSet {
numberOfEdits += 1
}
}
public init() {}
}
```
## 构造器 {#initializers}
自定义构造器的访问级别可以低于或等于其所属类型的访问级别。唯一的例外是 [必要构造器](./14_Initialization.md#required_initializers),它的访问级别必须和所属类型的访问级别相同。
如同函数或方法的参数,构造器参数的访问级别也不能低于构造器本身的访问级别。
### 默认构造器 {#default-initializers}
如 [默认构造器](./14_Initialization.md#default_initializers) 所述Swift 会为结构体和类提供一个默认的无参数的构造器,只要它们为所有存储型属性设置了默认初始值,并且未提供自定义的构造器。
默认构造器的访问级别与所属类型的访问级别相同,除非类型的访问级别是 `public`。如果一个类型被指定为 `public` 级别,那么默认构造器的访问级别将为 `internal`。如果你希望一个 `public` 级别的类型也能在其他模块中使用这种无参数的默认构造器,你只能自己提供一个 `public` 访问级别的无参数构造器。
### 结构体默认的成员逐一构造器 {#default-memberwise-initializers-for-structure-types}
如果结构体中任意存储型属性的访问级别为 `private`,那么该结构体默认的成员逐一构造器的访问级别就是 `private`。否则,这种构造器的访问级别依然是 `internal`
如同前面提到的默认构造器,如果你希望一个 `public` 级别的结构体也能在其他模块中使用其默认的成员逐一构造器,你依然只能自己提供一个 `public` 访问级别的成员逐一构造器。
## 协议 {#protocols}
如果想为一个协议类型明确地指定访问级别,在定义协议时指定即可。这将限制该协议只能在适当的访问级别范围内被遵循。
协议中的每一个要求都具有和该协议相同的访问级别。你不能将协议中的要求设置为其他访问级别。这样才能确保该协议的所有要求对于任意遵循者都将可用。
> 注意
>
> 如果你定义了一个 `public` 访问级别的协议,那么该协议的所有实现也会是 `public` 访问级别。这一点不同于其他类型,例如,当类型是 `public` 访问级别时,其成员的访问级别却只是 `internal`。
### 协议继承 {#protocol-inheritance}
如果定义了一个继承自其他协议的新协议,那么新协议拥有的访问级别最高也只能和被继承协议的访问级别相同。例如,你不能将继承自 `internal` 协议的新协议定义为 `public` 协议。
### 协议遵循 {#protocol-conformance}
一个类型可以遵循比它级别更低的协议。例如,你可以定义一个 `public` 级别类型,它能在别的模块中使用,但是如果它遵循一个 `internal` 协议,这个遵循的部分就只能在这个 `internal` 协议所在的模块中使用。
遵循协议时的上下文级别是类型和协议中级别最小的那个。如果一个类型是 `public` 级别,但它要遵循的协议是 `internal` 级别,那么这个类型对该协议的遵循上下文就是 `internal` 级别。
当你编写或扩展一个类型让它遵循一个协议时,你必须确保该类型对协议的每一个要求的实现,至少与遵循协议的上下文级别一致。例如,一个 `public` 类型遵循一个 `internal` 协议,这个类型对协议的所有实现至少都应是 `internal` 级别的。
> 注意
>
> Swift 和 Objective-C 一样,协议遵循是全局的,也就是说,在同一程序中,一个类型不可能用两种不同的方式实现同一个协议。
## Extension {#extensions}
Extension 可以在访问级别允许的情况下对类、结构体、枚举进行扩展。Extension 的成员具有和原始类型成员一致的访问级别。例如,你使用 extension 扩展了一个 `public` 或者 `internal` 类型extension 中的成员就默认使用 `internal` 访问级别,和原始类型中的成员一致。如果你使用 extension 扩展了一个 `private` 类型,则 extension 的成员默认使用 `private` 访问级别。
或者,你可以明确指定 extension 的访问级别(例如,`private extension`),从而给该 extension 中的所有成员指定一个新的默认访问级别。这个新的默认访问级别仍然可以被单独指定的访问级别所覆盖。
如果你使用 extension 来遵循协议的话,就不能显式地声明 extension 的访问级别。extension 每个 protocol 要求的实现都默认使用 protocol 的访问级别。
### Extension 的私有成员 {#Private Members in Extensions}
扩展同一文件内的类结构体或者枚举extension 里的代码会表现得跟声明在原类型里的一模一样。也就是说你可以这样:
- 在类型的声明里声明一个私有成员,在同一文件的 extension 里访问。
- 在 extension 里声明一个私有成员,在同一文件的另一个 extension 里访问。
- 在 extension 里声明一个私有成员,在同一文件的类型声明里访问。
这意味着你可以像组织的代码去使用 extension而且不受私有成员的影响。例如给定下面这样一个简单的协议
```swift
protocol SomeProtocol {
func doSomething()
}
```
你可以使用 extension 来遵循协议,就像这样:
```swift
struct SomeStruct {
private var privateVariable = 12
}
extension SomeStruct: SomeProtocol {
func doSomething() {
print(privateVariable)
}
}
```
## 泛型 {#generics}
泛型类型或泛型函数的访问级别取决于泛型类型或泛型函数本身的访问级别,还需结合类型参数的类型约束的访问级别,根据这些访问级别中的最低访问级别来确定。
## 类型别名 {#type-aliases}
你定义的任何类型别名都会被当作不同的类型,以便于进行访问控制。类型别名的访问级别不可高于其表示的类型的访问级别。例如,`private` 级别的类型别名可以作为 `private``file-private``internal``public` 或者 `open` 类型的别名,但是 `public` 级别的类型别名只能作为 `public` 类型的别名,不能作为 `internal``file-private``private` 类型的别名。
> 注意
>
> 这条规则也适用于为满足协议遵循而将类型别名用于关联类型的情况。

View File

@ -1,487 +0,0 @@
# 高级运算符Advanced Operators
-----------------
> 1.0
> 翻译:[xielingwang](https://github.com/xielingwang)
> 校对:[numbbbbb](https://github.com/numbbbbb)
> 2.0
> 翻译+校对:[buginux](https://github.com/buginux)
> 2.1
> 校对:[shanks](http://codebuild.me)2015-11-01
>
> 2.2
> 翻译+校对:[SketchK](https://github.com/SketchK) 2016-05-17
>
> 3.0
> 翻译+校对:[mmoaay](https://github.com/mmoaay) 2016-09-20
本页内容包括:
- [位运算符](#bitwise_operators)
- [溢出运算符](#overflow_operators)
- [优先级和结合性](#precedence_and_associativity)
- [运算符函数](#operator_functions)
- [自定义运算符](#custom_operators)
除了在之前介绍过的[基本运算符](./02_Basic_Operators.html)Swift 中还有许多可以对数值进行复杂运算的高级运算符。这些高级运算符包含了在 C 和 Objective-C 中已经被大家所熟知的位运算符和移位运算符。
与 C 语言中的算术运算符不同Swift 中的算术运算符默认是不会溢出的。所有溢出行为都会被捕获并报告为错误。如果想让系统允许溢出行为,可以选择使用 Swift 中另一套默认支持溢出的运算符,比如溢出加法运算符(`&+`)。所有的这些溢出运算符都是以 `&` 开头的。
自定义结构体、类和枚举时,如果也为它们提供标准 Swift 运算符的实现,将会非常有用。在 Swift 中自定义运算符非常简单,运算符也会针对不同类型使用对应实现。
我们不用被预定义的运算符所限制。在 Swift 中可以自由地定义中缀、前缀、后缀和赋值运算符,以及相应的优先级与结合性。这些运算符在代码中可以像预定义的运算符一样使用,我们甚至可以扩展已有的类型以支持自定义的运算符。
<a name="bitwise_operators"></a>
## 位运算符
位运算符可以操作数据结构中每个独立的比特位。它们通常被用在底层开发中,比如图形编程和创建设备驱动。位运算符在处理外部资源的原始数据时也十分有用,比如对自定义通信协议传输的数据进行编码和解码。
Swift 支持 C 语言中的全部位运算符,接下来会一一介绍。
<a name="bitwise_not_operator"></a>
### 按位取反运算符
按位取反运算符(`~`)可以对一个数值的全部比特位进行取反:
![Art/bitwiseNOT_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitwiseNOT_2x.png)
按位取反运算符是一个前缀运算符,需要直接放在运算的数之前,并且它们之间不能添加任何空格:
```swift
let initialBits: UInt8 = 0b00001111
let invertedBits = ~initialBits // 等于 0b11110000
```
`UInt8` 类型的整数有 8 个比特位,可以存储 `0 ~ 255` 之间的任意整数。这个例子初始化了一个 `UInt8` 类型的整数,并赋值为二进制的 `00001111`,它的前 4 位都为 `0`,后 4 位都为 `1`。这个值等价于十进制的 `15`
接着使用按位取反运算符创建了一个名为 `invertedBits` 的常量,这个常量的值与全部位取反后的 `initialBits` 相等。即所有的 `0` 都变成了 `1`,同时所有的 `1` 都变成 `0``invertedBits` 的二进制值为 `11110000`,等价于无符号十进制数的 `240`
<a name="bitwise_and_operator"></a>
### 按位与运算符
按位与运算符(`&`)可以对两个数的比特位进行合并。它返回一个新的数,只有当两个数的对应位都为 `1` 的时候,新数的对应位才为 `1`
![Art/bitwiseAND_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitwiseAND_2x.png)
在下面的示例当中,`firstSixBits``lastSixBits` 中间 4 个位的值都为 `1`。按位与运算符对它们进行了运算,得到二进制数值 `00111100`,等价于无符号十进制数的 `60`
```swift
let firstSixBits: UInt8 = 0b11111100
let lastSixBits: UInt8 = 0b00111111
let middleFourBits = firstSixBits & lastSixBits // 等于 00111100
```
<a name="bitwise_or_operator"></a>
### 按位或运算符
按位或运算符(`|`)可以对两个数的比特位进行比较。它返回一个新的数,只要两个数的对应位中有任意一个为 `1` 时,新数的对应位就为 `1`
![Art/bitwiseOR_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitwiseOR_2x.png "Art/bitwiseOR_2x.png")
在下面的示例中,`someBits``moreBits` 不同的位会被设置为 `1`。接位或运算符对它们进行了运算,得到二进制数值 `11111110`,等价于无符号十进制数的 `254`
```swift
let someBits: UInt8 = 0b10110010
let moreBits: UInt8 = 0b01011110
let combinedbits = someBits | moreBits // 等于 11111110
```
<a name="bitwise_xor_operator"></a>
### 按位异或运算符
按位异或运算符(`^`)可以对两个数的比特位进行比较。它返回一个新的数,当两个数的对应位不相同时,新数的对应位就为 `1`
![Art/bitwiseXOR_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitwiseXOR_2x.png "Art/bitwiseXOR_2x.png")
在下面的示例当中,`firstBits``otherBits` 都有一个自己的位为 `1` 而对方的对应位为 `0` 的位。 按位异或运算符将新数的这两个位都设置为 `1`,同时将其它位都设置为 `0`
```swift
let firstBits: UInt8 = 0b00010100
let otherBits: UInt8 = 0b00000101
let outputBits = firstBits ^ otherBits // 等于 00010001
```
<a name="bitwise_left_and_right_shift_operators"></a>
### 按位左移、右移运算符
按位左移运算符(`<<`)和按位右移运算符(`>>`)可以对一个数的所有位进行指定位数的左移和右移,但是需要遵守下面定义的规则。
对一个数进行按位左移或按位右移,相当于对这个数进行乘以 2 或除以 2 的运算。将一个整数左移一位,等价于将这个数乘以 2同样地将一个整数右移一位等价于将这个数除以 2。
<a name="shifting_behavior_for_unsigned_integers"></a>
#### 无符号整数的移位运算
对无符号整数进行移位的规则如下:
1. 已经存在的位按指定的位数进行左移和右移。
2. 任何因移动而超出整型存储范围的位都会被丢弃。
3.`0` 来填充移位后产生的空白位。
这种方法称为逻辑移位。
以下这张图展示了 `11111111 << 1`(即把 `11111111` 向左移动 `1` 位),和 `11111111 >> 1`(即把 `11111111` 向右移动 `1` 位)的结果。蓝色的部分是被移位的,灰色的部分是被抛弃的,橙色的部分则是被填充进来的:
![Art/bitshiftUnsigned_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitshiftUnsigned_2x.png "Art/bitshiftUnsigned_2x.png")
下面的代码演示了 Swift 中的移位运算:
```swift
let shiftBits: UInt8 = 4 // 即二进制的 00000100
shiftBits << 1 // 00001000
shiftBits << 2 // 00010000
shiftBits << 5 // 10000000
shiftBits << 6 // 00000000
shiftBits >> 2 // 00000001
```
可以使用移位运算对其他的数据类型进行编码和解码:
```swift
let pink: UInt32 = 0xCC6699
let redComponent = (pink & 0xFF0000) >> 16 // redComponent 是 0xCC即 204
let greenComponent = (pink & 0x00FF00) >> 8 // greenComponent 是 0x66 即 102
let blueComponent = pink & 0x0000FF // blueComponent 是 0x99即 153
```
这个示例使用了一个命名为 `pink``UInt32` 型常量来存储 CSS 中粉色的颜色值。该 CSS 的十六进制颜色值 `#CC6699`,在 Swift 中表示为 `0xCC6699`。然后利用按位与运算符(`&`)和按位右移运算符(`>>`)从这个颜色值中分解出红(`CC`)、绿(`66`)以及蓝(`99`)三个部分。
红色部分是通过对 `0xCC6699``0xFF0000` 进行按位与运算后得到的。`0xFF0000` 中的 `0` 部分“掩盖”了 `OxCC6699` 中的第二、第三个字节,使得数值中的 `6699` 被忽略,只留下 `0xCC0000`
然后,再将这个数按向右移动 16 位(`>> 16`)。十六进制中每两个字符表示 8 个比特位,所以移动 16 位后 `0xCC0000` 就变为 `0x0000CC`。这个数和`0xCC`是等同的,也就是十进制数值的 `204`
同样的,绿色部分通过对 `0xCC6699``0x00FF00` 进行按位与运算得到 `0x006600`。然后将这个数向右移动 8 位,得到 `0x66`,也就是十进制数值的 `102`
最后,蓝色部分通过对 `0xCC6699``0x0000FF` 进行按位与运算得到 `0x000099`。这里不需要再向右移位,所以结果为 `0x99` ,也就是十进制数值的 `153`
<a name="shifting_behavior_for_signed_integers"></a>
#### 有符号整数的移位运算
对比无符号整数,有符号整数的移位运算相对复杂得多,这种复杂性源于有符号整数的二进制表现形式。(为了简单起见,以下的示例都是基于 8 比特位的有符号整数的,但是其中的原理对任何位数的有符号整数都是通用的。)
有符号整数使用第 1 个比特位(通常被称为符号位)来表示这个数的正负。符号位为 `0` 代表正数,为 `1` 代表负数。
其余的比特位(通常被称为数值位)存储了实际的值。有符号正整数和无符号数的存储方式是一样的,都是从 `0` 开始算起。这是值为 `4``Int8` 型整数的二进制位表现形式:
![Art/bitshiftSignedFour_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitshiftSignedFour_2x.png "Art/bitshiftSignedFour_2x.png")
符号位为 `0`,说明这是一个正数,另外 7 位则代表了十进制数值 `4` 的二进制表示。
负数的存储方式略有不同。它存储的值的绝对值等于 `2``n` 次方减去它的实际值(也就是数值位表示的值),这里的 `n` 为数值位的比特位数。一个 8 比特位的数有 7 个比特位是数值位,所以是 `2``7` 次方,即 `128`
这是值为 `-4``Int8` 型整数的二进制位表现形式:
![Art/bitshiftSignedMinusFour_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitshiftSignedMinusFour_2x.png "Art/bitshiftSignedMinusFour_2x.png")
这次的符号位为 `1`,说明这是一个负数,另外 7 个位则代表了数值 `124`(即 `128 - 4`)的二进制表示:
![Art/bitshiftSignedMinusFourValue_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitshiftSignedMinusFourValue_2x.png "Art/bitshiftSignedMinusFourValue_2x.png")
负数的表示通常被称为二进制补码表示。用这种方法来表示负数乍看起来有点奇怪,但它有几个优点。
首先,如果想对 `-1``-4` 进行加法运算,我们只需要将这两个数的全部 8 个比特位进行相加,并且将计算结果中超出 8 位的数值丢弃:
![Art/bitshiftSignedAddition_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitshiftSignedAddition_2x.png "Art/bitshiftSignedAddition_2x.png")
其次,使用二进制补码可以使负数的按位左移和右移运算得到跟正数同样的效果,即每向左移一位就将自身的数值乘以 2每向右一位就将自身的数值除以 2。要达到此目的对有符号整数的右移有一个额外的规则
* 当对整数进行按位右移运算时,遵循与无符号整数相同的规则,但是对于移位产生的空白位使用符号位进行填充,而不是用 `0`
![Art/bitshiftSigned_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/bitshiftSigned_2x.png "Art/bitshiftSigned_2x.png")
这个行为可以确保有符号整数的符号位不会因为右移运算而改变,这通常被称为算术移位。
由于正数和负数的特殊存储方式,在对它们进行右移的时候,会使它们越来越接近 `0`。在移位的过程中保持符号位不变,意味着负整数在接近 `0` 的过程中会一直保持为负。
<a name="overflow_operators"></a>
## 溢出运算符
在默认情况下当向一个整数赋予超过它容量的值时Swift 默认会报错,而不是生成一个无效的数。这个行为为我们在运算过大或着过小的数的时候提供了额外的安全性。
例如,`Int16` 型整数能容纳的有符号整数范围是 `-32768``32767`,当为一个 `Int16` 型变量赋的值超过这个范围时,系统就会报错:
```swift
var potentialOverflow = Int16.max
// potentialOverflow 的值是 32767这是 Int16 能容纳的最大整数
potentialOverflow += 1
// 这里会报错
```
为过大或者过小的数值提供错误处理,能让我们在处理边界值时更加灵活。
然而,也可以选择让系统在数值溢出的时候采取截断处理,而非报错。可以使用 Swift 提供的三个溢出运算符来让系统支持整数溢出运算。这些运算符都是以 `&` 开头的:
* 溢出加法 `&+`
* 溢出减法 `&-`
* 溢出乘法 `&*`
<a name="value_overflow"></a>
### 数值溢出
数值有可能出现上溢或者下溢。
这个示例演示了当我们对一个无符号整数使用溢出加法(`&+`)进行上溢运算时会发生什么:
```swift
var unsignedOverflow = UInt8.max
// unsignedOverflow 等于 UInt8 所能容纳的最大整数 255
unsignedOverflow = unsignedOverflow &+ 1
// 此时 unsignedOverflow 等于 0
```
`unsignedOverflow` 被初始化为 `UInt8` 所能容纳的最大整数(`255`,以二进制表示即 `11111111`)。然后使用了溢出加法运算符(`&+`)对其进行加 `1` 运算。这使得它的二进制表示正好超出 `UInt8` 所能容纳的位数,也就导致了数值的溢出,如下图所示。数值溢出后,留在 `UInt8` 边界内的值是 `00000000`,也就是十进制数值的 `0`
![Art/overflowAddition_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/overflowAddition_2x.png "Art/overflowAddition_2x.png")
同样地,当我们对一个无符号整数使用溢出减法(`&-`)进行下溢运算时也会产生类似的现象:
```swift
var unsignedOverflow = UInt8.min
// unsignedOverflow 等于 UInt8 所能容纳的最小整数 0
unsignedOverflow = unsignedOverflow &- 1
// 此时 unsignedOverflow 等于 255
```
`UInt8` 型整数能容纳的最小值是 `0`,以二进制表示即 `00000000`。当使用溢出减法运算符对其进行减 `1` 运算时,数值会产生下溢并被截断为 `11111111` 也就是十进制数值的 `255`
![Art/overflowUnsignedSubtraction_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/overflowUnsignedSubtraction_2x.png "Art/overflowAddition_2x.png")
溢出也会发生在有符号整型数值上。在对有符号整型数值进行溢出加法或溢出减法运算时,符号位也需要参与计算,正如[按位左移、右移运算符](#bitwise_left_and_right_shift_operators)所描述的。
```swift
var signedOverflow = Int8.min
// signedOverflow 等于 Int8 所能容纳的最小整数 -128
signedOverflow = signedOverflow &- 1
// 此时 signedOverflow 等于 127
```
`Int8` 型整数能容纳的最小值是 `-128`,以二进制表示即 `10000000`。当使用溢出减法运算符对其进行减 `1` 运算时,符号位被翻转,得到二进制数值 `01111111`,也就是十进制数值的 `127`,这个值也是 `Int8` 型整数所能容纳的最大值。
![Art/overflowSignedSubtraction_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/overflowSignedSubtraction_2x.png "Art/overflowSignedSubtraction_2x.png")
对于无符号与有符号整型数值来说,当出现上溢时,它们会从数值所能容纳的最大数变成最小的数。同样地,当发生下溢时,它们会从所能容纳的最小数变成最大的数。
<a name="precedence_and_associativity"></a>
## 优先级和结合性
运算符的优先级使得一些运算符优先于其他运算符,高优先级的运算符会先被计算。
结合性定义了相同优先级的运算符是如何结合的,也就是说,是与左边结合为一组,还是与右边结合为一组。可以将这意思理解为“它们是与左边的表达式结合的”或者“它们是与右边的表达式结合的”。
在复合表达式的运算顺序中,运算符的优先级和结合性是非常重要的。举例来说,运算符优先级解释了为什么下面这个表达式的运算结果会是 `17`
```swift
2 + 3 % 4 * 5
// 结果是 17
```
如果完全从左到右进行运算,则运算的过程是这样的:
- 2 + 3 = 5
- 5 % 4 = 1
- 1 * 5 = 5
但是正确答案是 `17` 而不是 `5`。优先级高的运算符要先于优先级低的运算符进行计算。与 C 语言类似,在 Swift 中,乘法运算符(`*`)与取余运算符(`%`)的优先级高于加法运算符(`+`)。因此,它们的计算顺序要先于加法运算。
而乘法与取余的优先级相同。这时为了得到正确的运算顺序,还需要考虑结合性。乘法与取余运算都是左结合的。可以将这考虑成为这两部分表达式都隐式地加上了括号:
```swift
2 + ((3 % 4) * 5)
```
`(3 % 4)` 等于 `3`,所以表达式相当于:
```swift
2 + (3 * 5)
```
`3 * 5` 等于 `15`,所以表达式相当于:
```swift
2 + 15
```
因此计算结果为 `17`
如果想查看完整的 Swift 运算符优先级和结合性规则,请参考[表达式](../chapter3/04_Expressions.html)。如果想查看 Swift 标准库提供所有的运算符,请查看 [Swift Standard Library Operators Reference](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_StandardLibrary_Operators/index.html#//apple_ref/doc/uid/TP40016054)。
> 注意
> 相对 C 语言和 Objective-C 来说Swift 的运算符优先级和结合性规则更加简洁和可预测。但是,这也意味着它们相较于 C 语言及其衍生语言并不是完全一致的。在对现有的代码进行移植的时候,要注意确保运算符的行为仍然符合你的预期。
<a name="operator_functions"></a>
## 运算符函数
类和结构体可以为现有的运算符提供自定义的实现,这通常被称为运算符重载。
下面的例子展示了如何为自定义的结构体实现加法运算符(`+`)。算术加法运算符是一个双目运算符,因为它可以对两个值进行运算,同时它还是中缀运算符,因为它出现在两个值中间。
例子中定义了一个名为 `Vector2D` 的结构体用来表示二维坐标向量 `(x, y)`,紧接着定义了一个可以对两个 `Vector2D` 结构体进行相加的运算符函数:
```swift
struct Vector2D {
var x = 0.0, y = 0.0
}
extension Vector2D {
static func + (left: Vector2D, right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x, y: left.y + right.y)
}
}
```
该运算符函数被定义为 `Vector2D` 上的一个类方法,并且函数的名字与它要进行重载的 `+` 名字一致。因为加法运算并不是一个向量必需的功能,所以这个类方法被定义在 `Vector2D` 的一个扩展中,而不是 `Vector2D` 结构体声明内。而算术加法运算符是双目运算符,所以这个运算符函数接收两个类型为 `Vector2D` 的参数,同时有一个 `Vector2D` 类型的返回值。
在这个实现中,输入参数分别被命名为 `left``right`,代表在 `+` 运算符左边和右边的两个 `Vector2D` 实例。函数返回了一个新的 `Vector2D` 实例,这个实例的 `x``y` 分别等于作为参数的两个实例的 `x``y` 的值之和。
这个函数被定义成全局的,而不是 `Vector2D` 结构体的成员方法,所以任意两个 `Vector2D` 实例都可以使用这个中缀运算符:
```swift
let vector = Vector2D(x: 3.0, y: 1.0)
let anotherVector = Vector2D(x: 2.0, y: 4.0)
let combinedVector = vector + anotherVector
// combinedVector 是一个新的 Vector2D 实例,值为 (5.0, 5.0)
```
这个例子实现两个向量 `(3.01.0)``(2.04.0)` 的相加,并得到新的向量 `(5.05.0)`。这个过程如下图示:
![Art/vectorAddition_2x.png](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/vectorAddition_2x.png "Art/vectorAddition_2x.png")
<a name="prefix_and_postfix_operators"></a>
### 前缀和后缀运算符
上个例子演示了一个双目中缀运算符的自定义实现。类与结构体也能提供标准单目运算符的实现。单目运算符只运算一个值。当运算符出现在值之前时,它就是前缀的(例如 `-a`),而当它出现在值之后时,它就是后缀的(例如 `b!`)。
要实现前缀或者后缀运算符,需要在声明运算符函数的时候在 `func` 关键字之前指定 `prefix` 或者 `postfix` 修饰符:
```swift
extension Vector2D {
static prefix func - (vector: Vector2D) -> Vector2D {
return Vector2D(x: -vector.x, y: -vector.y)
}
}
```
这段代码为 `Vector2D` 类型实现了单目负号运算符。由于该运算符是前缀运算符,所以这个函数需要加上 `prefix` 修饰符。
对于简单数值,单目负号运算符可以对它们的正负性进行改变。对于 `Vector2D` 来说,该运算将其 `x``y` 属性的正负性都进行了改变:
```swift
let positive = Vector2D(x: 3.0, y: 4.0)
let negative = -positive
// negative 是一个值为 (-3.0, -4.0) 的 Vector2D 实例
let alsoPositive = -negative
// alsoPositive 是一个值为 (3.0, 4.0) 的 Vector2D 实例
```
<a name="compound_assignment_operators"></a>
### 复合赋值运算符
复合赋值运算符将赋值运算符(`=`)与其它运算符进行结合。例如,将加法与赋值结合成加法赋值运算符(`+=`)。在实现的时候,需要把运算符的左参数设置成 `inout` 类型,因为这个参数的值会在运算符函数内直接被修改。
```swift
extension Vector2D {
static func += (left: inout Vector2D, right: Vector2D) {
left = left + right
}
}
```
因为加法运算在之前已经定义过了,所以在这里无需重新定义。在这里可以直接利用现有的加法运算符函数,用它来对左值和右值进行相加,并再次赋值给左值:
```swift
var original = Vector2D(x: 1.0, y: 2.0)
let vectorToAdd = Vector2D(x: 3.0, y: 4.0)
original += vectorToAdd
// original 的值现在为 (4.0, 6.0)
```
> 注意
> 不能对默认的赋值运算符(`=`)进行重载。只有组合赋值运算符可以被重载。同样地,也无法对三目条件运算符 `a ? b : c` 进行重载。
<a name="equivalence_operators"></a>
### 等价运算符
自定义的类和结构体没有对等价运算符进行默认实现,等价运算符通常被称为“相等”运算符(`==`)与“不等”运算符(`!=`。对于自定义类型Swift 无法判断其是否“相等”,因为“相等”的含义取决于这些自定义类型在你的代码中所扮演的角色。
为了使用等价运算符能对自定义的类型进行判等运算,需要为其提供自定义实现,实现的方法与其它中缀运算符一样:
```swift
extension Vector2D {
static func == (left: Vector2D, right: Vector2D) -> Bool {
return (left.x == right.x) && (left.y == right.y)
}
static func != (left: Vector2D, right: Vector2D) -> Bool {
return !(left == right)
}
}
```
上述代码实现了“相等”运算符(`==`)来判断两个 `Vector2D` 实例是否相等。对于 `Vector2D` 类型来说,“相等”意味着“两个实例的 `x` 属性和 `y` 属性都相等”,这也是代码中用来进行判等的逻辑。示例里同时也实现了“不等”运算符(`!=`),它简单地将“相等”运算符的结果进行取反后返回。
现在我们可以使用这两个运算符来判断两个 `Vector2D` 实例是否相等:
```swift
let twoThree = Vector2D(x: 2.0, y: 3.0)
let anotherTwoThree = Vector2D(x: 2.0, y: 3.0)
if twoThree == anotherTwoThree {
print("These two vectors are equivalent.")
}
// 打印 “These two vectors are equivalent.”
```
<a name="custom_operators"></a>
## 自定义运算符
除了实现标准运算符,在 Swift 中还可以声明和实现自定义运算符。可以用来自定义运算符的字符列表请参考[运算符](../chapter3/02_Lexical_Structure.html#operators)。
新的运算符要使用 `operator` 关键字在全局作用域内进行定义,同时还要指定 `prefix``infix` 或者 `postfix` 修饰符:
```swift
prefix operator +++ {}
```
上面的代码定义了一个新的名为 `+++` 的前缀运算符。对于这个运算符,在 Swift 中并没有意义,因此我们针对 `Vector2D` 的实例来定义它的意义。对这个示例来讲,`+++` 被实现为“前缀双自增”运算符。它使用了前面定义的复合加法运算符来让矩阵对自身进行相加,从而让 `Vector2D` 实例的 `x` 属性和 `y` 属性的值翻倍。实现 `+++` 运算符的方式如下:
```swift
extension Vector2D {
static prefix func +++ (vector: inout Vector2D) -> Vector2D {
vector += vector
return vector
}
}
var toBeDoubled = Vector2D(x: 1.0, y: 4.0)
let afterDoubling = +++toBeDoubled
// toBeDoubled 现在的值为 (2.0, 8.0)
// afterDoubling 现在的值也为 (2.0, 8.0)
```
<a name="precedence_and_associativity_for_custom_infix_operators"></a>
### 自定义中缀运算符的优先级
每个自定义中缀运算符都属于某个优先级组。这个优先级组指定了这个运算符和其他中缀运算符的优先级和结合性。[优先级和结合性](#precedence_and_associativity)中详细阐述了这两个特性是如何对中缀运算符的运算产生影响的。
而没有明确放入优先级组的自定义中缀运算符会放到一个默认的优先级组内,其优先级高于三元运算符。
以下例子定义了一个新的自定义中缀运算符 `+-`,此运算符属于 `AdditionPrecedence` 优先组:
```swift
infix operator +-: AdditionPrecedence
extension Vector2D {
static func +- (left: Vector2D, right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x, y: left.y - right.y)
}
}
let firstVector = Vector2D(x: 1.0, y: 2.0)
let secondVector = Vector2D(x: 3.0, y: 4.0)
let plusMinusVector = firstVector +- secondVector
// plusMinusVector 是一个 Vector2D 实例,并且它的值为 (4.0, -2.0)
```
这个运算符把两个向量的 `x` 值相加,同时用第一个向量的 `y` 值减去第二个向量的 `y` 值。因为它本质上是属于“相加型”运算符,所以将它放置 `+``-` 等默认的中缀“相加型”运算符相同的优先级组中。关于 Swift 标准库提供的运算符,以及完整的运算符优先级组和结合性设置,请参考 [Swift Standard Library Operators Reference](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_StandardLibrary_Operators/index.html#//apple_ref/doc/uid/TP40016054)。而更多关于优先级组以及自定义操作符和优先级组的语法,请参考[运算符声明](#operator_declaration)
> 注意
> 当定义前缀与后缀运算符的时候,我们并没有指定优先级。然而,如果对同一个值同时使用前缀与后缀运算符,则后缀运算符会先参与运算。

View File

@ -0,0 +1,467 @@
# 高级运算符
除了之前介绍过的 [基本运算符](./02_Basic_Operators.md)Swift 还提供了数种可以对数值进行复杂运算的高级运算符。它们包含了在 C 和 Objective-C 中已经被大家所熟知的位运算符和移位运算符。
与 C 语言中的算术运算符不同Swift 中的算术运算符默认是不会溢出的。所有溢出行为都会被捕获并报告为错误。如果想让系统允许溢出行为,可以选择使用 Swift 中另一套默认支持溢出的运算符,比如溢出加法运算符(`&+`)。所有的这些溢出运算符都是以 `&` 开头的。
自定义结构体、类和枚举时,如果也为它们提供标准 Swift 运算符的实现,将会非常有用。在 Swift 中为这些运算符提供自定义的实现非常简单,运算符也会针对不同类型使用对应实现。
我们不用被预定义的运算符所限制。在 Swift 中可以自由地定义中缀、前缀、后缀和赋值运算符,它们具有自定义的优先级与关联值。这些运算符在代码中可以像预定义的运算符一样使用,你甚至可以扩展已有的类型以支持自定义运算符。
## 位运算符 {#bitwise-operators}
*位运算符*可以操作数据结构中每个独立的比特位。它们通常被用在底层开发中,比如图形编程和创建设备驱动。位运算符在处理外部资源的原始数据时也十分有用,比如对自定义通信协议传输的数据进行编码和解码。
Swift 支持 C 语言中的全部位运算符,接下来会一一介绍。
### Bitwise NOT Operator按位取反运算符 {#bitwise-not-operator}
*按位取反运算符(`~`*对一个数值的全部比特位进行取反:
![Art/bitwiseNOT_2x.png](https://docs.swift.org/swift-book/_images/bitwiseNOT_2x.png)
按位取反运算符是一个前缀运算符,直接放在运算数之前,并且它们之间不能添加任何空格:
```Swift
let initialBits: UInt8 = 0b00001111
let invertedBits = ~initialBits // 等于 0b11110000
```
`UInt8` 类型的整数有 8 个比特位,可以存储 `0 ~ 255` 之间的任意整数。这个例子初始化了一个 `UInt8` 类型的整数,并赋值为二进制的 `00001111`,它的前 4 位为 `0`,后 4 位为 `1`。这个值等价于十进制的 `15`
接着使用按位取反运算符创建了一个名为 `invertedBits` 的常量,这个常量的值与全部位取反后的 `initialBits` 相等。即所有的 `0` 都变成了 `1`,同时所有的 `1` 都变成 `0``invertedBits` 的二进制值为 `11110000`,等价于无符号十进制数的 `240`
### Bitwise AND Operator按位与运算符 {#bitwise-and-operator}
*按位与运算符(`&`* 对两个数的比特位进行合并。它返回一个新的数,只有当两个数的对应位*都*为 `1` 的时候,新数的对应位才为 `1`
![Art/bitwiseAND_2x.png](https://docs.swift.org/swift-book/_images/bitwiseAND_2x.png)
在下面的示例当中,`firstSixBits``lastSixBits` 中间 4 个位的值都为 `1`。使用按位与运算符之后,得到二进制数值 `00111100`,等价于无符号十进制数的 `60`
```Swift
let firstSixBits: UInt8 = 0b11111100
let lastSixBits: UInt8 = 0b00111111
let middleFourBits = firstSixBits & lastSixBits // 等于 00111100
```
### Bitwise OR Operator按位或运算符 {#bitwise-or-operator}
*按位或运算符(`|`*可以对两个数的比特位进行比较。它返回一个新的数,只要两个数的对应位中有*任意一个*为 `1` 时,新数的对应位就为 `1`
![Art/bitwiseOR_2x.png](https://docs.swift.org/swift-book/_images/bitwiseOR_2x.png)
在下面的示例中,`someBits``moreBits` 存在不同的位被设置为 `1`。使用按位或运算符之后,得到二进制数值 `11111110`,等价于无符号十进制数的 `254`
```Swift
let someBits: UInt8 = 0b10110010
let moreBits: UInt8 = 0b01011110
let combinedbits = someBits | moreBits // 等于 11111110
```
### Bitwise XOR Operator按位异或运算符 {#bitwise-xor-operator}
*按位异或运算符*,或称“排外的或运算符”(`^`),可以对两个数的比特位进行比较。它返回一个新的数,当两个数的对应位不相同时,新数的对应位就为 `1`,并且对应位相同时则为 `0`
![Art/bitwiseXOR_2x.png](https://docs.swift.org/swift-book/_images/bitwiseXOR_2x.png)
在下面的示例当中,`firstBits``otherBits` 都有一个自己为 `1`,而对方为 `0` 的位。按位异或运算符将新数的这两个位都设置为 `1`。在其余的位上 `firstBits``otherBits` 是相同的,所以设置为 `0`
```Swift
let firstBits: UInt8 = 0b00010100
let otherBits: UInt8 = 0b00000101
let outputBits = firstBits ^ otherBits // 等于 00010001
```
### Bitwise Left and Right Shift Operators按位左移、右移运算符 {#bitwise-left-and-right-shift-operators}
*按位左移运算符(`<<`* 和 *按位右移运算符(`>>`*可以对一个数的所有位进行指定位数的左移和右移,但是需要遵守下面定义的规则。
对一个数进行按位左移或按位右移,相当于对这个数进行乘以 2 或除以 2 的运算。将一个整数左移一位,等价于将这个数乘以 2同样地将一个整数右移一位等价于将这个数除以 2。
#### 无符号整数的移位运算 {#shifting-behavior-for-unsigned-integers}
对无符号整数进行移位的规则如下:
1. 已存在的位按指定的位数进行左移和右移。
2. 任何因移动而超出整型存储范围的位都会被丢弃。
3.`0` 来填充移位后产生的空白位。
这种方法称为*逻辑移位*。
以下这张图展示了 `11111111 << 1`(即把 `11111111` 向左移动 `1` 位),和 `11111111 >> 1`(即把 `11111111` 向右移动 `1` 位)的结果。蓝色的数字是被移位的,灰色的数字是被抛弃的,橙色的 `0` 则是被填充进来的:
![Art/bitshiftUnsigned_2x.png](https://docs.swift.org/swift-book/_images/bitshiftUnsigned_2x.png)
下面的代码演示了 Swift 中的移位运算:
```Swift
let shiftBits: UInt8 = 4 // 即二进制的 00000100
shiftBits << 1 // 00001000
shiftBits << 2 // 00010000
shiftBits << 5 // 10000000
shiftBits << 6 // 00000000
shiftBits >> 2 // 00000001
```
可以使用移位运算对其他的数据类型进行编码和解码:
```Swift
let pink: UInt32 = 0xCC6699
let redComponent = (pink & 0xFF0000) >> 16 // redComponent 是 0xCC即 204
let greenComponent = (pink & 0x00FF00) >> 8 // greenComponent 是 0x66 即 102
let blueComponent = pink & 0x0000FF // blueComponent 是 0x99即 153
```
这个示例使用了一个命名为 `pink``UInt32` 型常量来存储 Cascading Style SheetsCSS中粉色的颜色值。该 CSS 的颜色值 `#CC6699`,在 Swift 中表示为十六进制的 `0xCC6699`。然后利用按位与运算符(`&`)和按位右移运算符(`>>`)从这个颜色值中分解出红(`CC`)、绿(`66`)以及蓝(`99`)三个部分。
红色部分是通过对 `0xCC6699``0xFF0000` 进行按位与运算后得到的。`0xFF0000` 中的 `0` 部分“掩盖”了 `OxCC6699` 中的第二、第三个字节,使得数值中的 `6699` 被忽略,只留下 `0xCC0000`
然后,将这个数向右移动 16 位(`>> 16`)。十六进制中每两个字符占用 8 个比特位,所以移动 16 位后 `0xCC0000` 就变为 `0x0000CC`。这个数和 `0xCC` 是等同的,也就是十进制数值的 `204`
同样的,绿色部分通过对 `0xCC6699``0x00FF00` 进行按位与运算得到 `0x006600`。然后将这个数向右移动 8 位,得到 `0x66`,也就是十进制数值的 `102`
最后,蓝色部分通过对 `0xCC6699``0x0000FF` 进行按位与运算得到 `0x000099`。这里不需要再向右移位,而 `0x000099` 也就是 `0x99` ,也就是十进制数值的 `153`
#### 有符号整数的移位运算 {#shifting-behavior-for-signed-integers}
对比无符号整数,有符号整数的移位运算相对复杂得多,这种复杂性源于有符号整数的二进制表现形式。(为了简单起见,以下的示例都是基于 8 比特的有符号整数,但是其中的原理对任何位数的有符号整数都是通用的。)
有符号整数使用第 1 个比特位(通常被称为*符号位*)来表示这个数的正负。符号位为 `0` 代表正数,为 `1` 代表负数。
其余的比特位(通常被称为*数值位*)存储了实际的值。有符号正整数和无符号数的存储方式是一样的,都是从 `0` 开始算起。这是值为 `4``Int8` 型整数的二进制位表现形式:
![Art/bitshiftSignedFour_2x.png](https://docs.swift.org/swift-book/_images/bitshiftSignedFour_2x.png)
符号位为 `0`(代表这是一个“正数”),另外 7 位则代表了十进制数值 `4` 的二进制表示。
负数的存储方式略有不同。它存储 `2``n` 次方减去其实际值的绝对值,这里的 `n` 是数值位的位数。一个 8 比特位的数有 7 个比特位是数值位,所以是 `2``7` 次方,即 `128`
这是值为 `-4``Int8` 型整数的二进制表现形式:
![Art/bitshiftSignedMinusFour_2x.png](https://docs.swift.org/swift-book/_images/bitshiftSignedMinusFour_2x.png)
这次的符号位为 `1`,说明这是一个负数,另外 7 个位则代表了数值 `124`(即 `128 - 4`)的二进制表示:
![Art/bitshiftSignedMinusFourValue_2x.png](https://docs.swift.org/swift-book/_images/bitshiftSignedMinusFourValue_2x.png)
负数的表示通常被称为*二进制补码*。用这种方法来表示负数乍看起来有点奇怪,但它有几个优点。
首先,如果想对 `-1``-4` 进行加法运算,我们只需要对这两个数的全部 8 个比特位执行标准的二进制相加(包括符号位),并且将计算结果中超出 8 位的数值丢弃:
![Art/bitshiftSignedAddition_2x.png](https://docs.swift.org/swift-book/_images/bitshiftSignedAddition_2x.png)
其次,使用二进制补码可以使负数的按位左移和右移运算得到跟正数同样的效果,即每向左移一位就将自身的数值乘以 2每向右一位就将自身的数值除以 2。要达到此目的对有符号整数的右移有一个额外的规则当对有符号整数进行按位右移运算时遵循与无符号整数相同的规则但是对于移位产生的空白位使用*符号位*进行填充,而不是用 `0`
![Art/bitshiftSigned_2x.png](https://docs.swift.org/swift-book/_images/bitshiftSigned_2x.png)
这个行为可以确保有符号整数的符号位不会因为右移运算而改变,这通常被称为*算术移位*。
由于正数和负数的特殊存储方式,在对它们进行右移的时候,会使它们越来越接近 `0`。在移位的过程中保持符号位不变,意味着负整数在接近 `0` 的过程中会一直保持为负。
## 溢出运算符 {#overflow-operators}
当向一个整数类型的常量或者变量赋予超过它容量的值时Swift 默认会报错,而不是允许生成一个无效的数。这个行为为我们在运算过大或者过小的数时提供了额外的安全性。
例如,`Int16` 型整数能容纳的有符号整数范围是 `-32768``32767`。当为一个 `Int16` 类型的变量或常量赋予的值超过这个范围时,系统就会报错:
```Swift
var potentialOverflow = Int16.max
// potentialOverflow 的值是 32767这是 Int16 能容纳的最大整数
potentialOverflow += 1
// 这里会报错
```
在赋值时为过大或者过小的情况提供错误处理,能让我们在处理边界值时更加灵活。
然而当你希望的时候也可以选择让系统在数值溢出的时候采取截断处理而非报错。Swift 提供的三个*溢出运算符*来让系统支持整数溢出运算。这些运算符都是以 `&` 开头的:
* 溢出加法 `&+`
* 溢出减法 `&-`
* 溢出乘法 `&*`
### 数值溢出 {#value-overflow}
数值有可能出现上溢或者下溢。
这个示例演示了当我们对一个无符号整数使用溢出加法(`&+`)进行上溢运算时会发生什么:
```Swift
var unsignedOverflow = UInt8.max
// unsignedOverflow 等于 UInt8 所能容纳的最大整数 255
unsignedOverflow = unsignedOverflow &+ 1
// 此时 unsignedOverflow 等于 0
```
`unsignedOverflow` 被初始化为 `UInt8` 所能容纳的最大整数(`255`,以二进制表示即 `11111111`)。然后使用溢出加法运算符(`&+`)对其进行加 `1` 运算。这使得它的二进制表示正好超出 `UInt8` 所能容纳的位数,也就导致了数值的溢出,如下图所示。数值溢出后,仍然留在 `UInt8` 边界内的值是 `00000000`,也就是十进制数值的 `0`
![Art/overflowAddition_2x.png](https://docs.swift.org/swift-book/_images/overflowAddition_2x.png)
当允许对一个无符号整数进行下溢运算时也会产生类似的情况。这里有一个使用溢出减法运算符(`&-`)的例子:
```Swift
var unsignedOverflow = UInt8.min
// unsignedOverflow 等于 UInt8 所能容纳的最小整数 0
unsignedOverflow = unsignedOverflow &- 1
// 此时 unsignedOverflow 等于 255
```
`UInt8` 型整数能容纳的最小值是 `0`,以二进制表示即 `00000000`。当使用溢出减法运算符对其进行减 `1` 运算时,数值会产生下溢并被截断为 `11111111` 也就是十进制数值的 `255`
![Art/overflowUnsignedSubtraction_2x.png](https://docs.swift.org/swift-book/_images/overflowUnsignedSubtraction_2x.png)
溢出也会发生在有符号整型上。针对有符号整型的所有溢出加法或者减法运算都是按位运算的方式执行的,符号位也需要参与计算,正如 [按位左移、右移运算符](#bitwise_left_and_right_shift_operators) 所描述的。
```Swift
var signedOverflow = Int8.min
// signedOverflow 等于 Int8 所能容纳的最小整数 -128
signedOverflow = signedOverflow &- 1
// 此时 signedOverflow 等于 127
```
`Int8` 型整数能容纳的最小值是 `-128`,以二进制表示即 `10000000`。当使用溢出减法运算符对其进行减 `1` 运算时,符号位被翻转,得到二进制数值 `01111111`,也就是十进制数值的 `127`,这个值也是 `Int8` 型整所能容纳的最大值。
![Art/overflowSignedSubtraction_2x.png](https://docs.swift.org/swift-book/_images/overflowSignedSubtraction_2x.png)
对于无符号与有符号整型数值来说,当出现上溢时,它们会从数值所能容纳的最大数变成最小数。同样地,当发生下溢时,它们会从所能容纳的最小数变成最大数。
## 优先级和结合性 {#precedence-and-associativity}
运算符的*优先级*使得一些运算符优先于其他运算符;它们会先被执行。
*结合性*定义了相同优先级的运算符是如何结合的,也就是说,是与左边结合为一组,还是与右边结合为一组。可以将其理解为“它们是与左边的表达式结合的”,或者“它们是与右边的表达式结合的”。
当考虑一个复合表达式的计算顺序时,运算符的优先级和结合性是非常重要的。举例来说,运算符优先级解释了为什么下面这个表达式的运算结果会是 `17`
```Swift
2 + 3 % 4 * 5
// 结果是 17
```
如果你直接从左到右进行运算,你可能认为运算的过程是这样的:
- 2 + 3 = 5
- 5 % 4 = 1
- 1 * 5 = 5
但是正确答案是 `17` 而不是 `5`。优先级高的运算符要先于优先级低的运算符进行计算。与 C 语言类似,在 Swift 中,乘法运算符(`*`)与取余运算符(`%`)的优先级高于加法运算符(`+`)。因此,它们的计算顺序要先于加法运算。
而乘法运算与取余运算的优先级*相同*。这时为了得到正确的运算顺序,还需要考虑结合性。乘法运算与取余运算都是左结合的。可以将这考虑成,从它们的左边开始为这两部分表达式都隐式地加上括号:
```Swift
2 + ((3 % 4) * 5)
```
`(3 % 4)` 等于 `3`,所以表达式相当于:
```Swift
2 + (3 * 5)
```
`3 * 5` 等于 `15`,所以表达式相当于:
```Swift
2 + 15
```
因此计算结果为 `17`
有关 Swift 标准库提供的操作符信息,包括操作符优先级组和结核性设置的完整列表,请参见 [操作符声明](https://developer.apple.com/documentation/swift/operator_declarations)。
> 注意
>
> 相对 C 语言和 Objective-C 来说Swift 的运算符优先级和结合性规则更加简洁和可预测。但是,这也意味着它们相较于 C 语言及其衍生语言并不是完全一致。在对现有的代码进行移植的时候,要注意确保运算符的行为仍然符合你的预期。
## 运算符函数 {#operator-functions}
类和结构体可以为现有的运算符提供自定义的实现。这通常被称为运算符*重载*。
下面的例子展示了如何让自定义的结构体支持加法运算符(`+`)。算术加法运算符是一个*二元运算符*,因为它是对两个值进行运算,同时它还可以称为*中缀*运算符,因为它出现在两个值中间。
例子中定义了一个名为 `Vector2D` 的结构体用来表示二维坐标向量 `(x, y)`,紧接着定义了一个可以将两个 `Vector2D` 结构体实例进行相加的*运算符函数*
```Swift
struct Vector2D {
var x = 0.0, y = 0.0
}
extension Vector2D {
static func + (left: Vector2D, right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x, y: left.y + right.y)
}
}
```
该运算符函数被定义为 `Vector2D` 上的一个类方法,并且函数的名字与它要进行重载的 `+` 名字一致。因为加法运算并不是一个向量必需的功能,所以这个类方法被定义在 `Vector2D` 的一个扩展中,而不是 `Vector2D` 结构体声明内。而算术加法运算符是二元运算符,所以这个运算符函数接收两个类型为 `Vector2D` 的参数,同时有一个 `Vector2D` 类型的返回值。
在这个实现中,输入参数分别被命名为 `left``right`,代表在 `+` 运算符左边和右边的两个 `Vector2D` 实例。函数返回了一个新的 `Vector2D` 实例,这个实例的 `x``y` 分别等于作为参数的两个实例的 `x``y` 的值之和。
这个类方法可以在任意两个 `Vector2D` 实例中间作为中缀运算符来使用:
```Swift
let vector = Vector2D(x: 3.0, y: 1.0)
let anotherVector = Vector2D(x: 2.0, y: 4.0)
let combinedVector = vector + anotherVector
// combinedVector 是一个新的 Vector2D 实例,值为 (5.0, 5.0)
```
这个例子实现两个向量 `(3.01.0)``(2.04.0)` 的相加,并得到新的向量 `(5.05.0)`。这个过程如下图示:
![Art/vectorAddition_2x.png](https://docs.swift.org/swift-book/_images/vectorAddition_2x.png)
### 前缀和后缀运算符 {#prefix-and-postfix-operators}
上个例子演示了一个二元中缀运算符的自定义实现。类与结构体也能提供标准*一元运算符*的实现。一元运算符只运算一个值。当运算符出现在值之前时,它就是*前缀*的(例如 `-a`),而当它出现在值之后时,它就是*后缀*的(例如 `b!`)。
要实现前缀或者后缀运算符,需要在声明运算符函数的时候在 `func` 关键字之前指定 `prefix` 或者 `postfix` 修饰符:
```Swift
extension Vector2D {
static prefix func - (vector: Vector2D) -> Vector2D {
return Vector2D(x: -vector.x, y: -vector.y)
}
}
```
这段代码为 `Vector2D` 类型实现了一元运算符(`-a`)。由于该运算符是前缀运算符,所以这个函数需要加上 `prefix` 修饰符。
对于简单数值,一元负号运算符可以对它们的正负性进行改变。对于 `Vector2D` 来说,该运算将其 `x``y` 属性的正负性都进行了改变:
```Swift
let positive = Vector2D(x: 3.0, y: 4.0)
let negative = -positive
// negative 是一个值为 (-3.0, -4.0) 的 Vector2D 实例
let alsoPositive = -negative
// alsoPositive 是一个值为 (3.0, 4.0) 的 Vector2D 实例
```
### 复合赋值运算符 {#compound-assignment-operators}
*复合赋值运算符*将赋值运算符(`=`)与其它运算符进行结合。例如,将加法与赋值结合成加法赋值运算符(`+=`)。在实现的时候,需要把运算符的左参数设置成 `inout` 类型,因为这个参数的值会在运算符函数内直接被修改。
在下面的例子中,对 `Vector2D` 实例实现了一个加法赋值运算符函数:
```Swift
extension Vector2D {
static func += (left: inout Vector2D, right: Vector2D) {
left = left + right
}
}
```
因为加法运算在之前已经定义过了,所以在这里无需重新定义。在这里可以直接利用现有的加法运算符函数,用它来对左值和右值进行相加,并再次赋值给左值:
```Swift
var original = Vector2D(x: 1.0, y: 2.0)
let vectorToAdd = Vector2D(x: 3.0, y: 4.0)
original += vectorToAdd
// original 的值现在为 (4.0, 6.0)
```
> 注意
>
> 不能对默认的赋值运算符(`=`)进行重载。只有复合赋值运算符可以被重载。同样地,也无法对三元条件运算符 `a ? b : c` 进行重载。
### 等价运算符 {#equivalence-operators}
通常情况下,自定义的类和结构体没有对*等价运算符*进行默认实现,等价运算符通常被称为*相等*运算符(`==`)与*不等*运算符(`!=`)。
为了使用等价运算符对自定义的类型进行判等运算,需要为“相等”运算符提供自定义实现,实现的方法与其它中缀运算符一样, 并且增加对标准库 `Equatable` 协议的遵循:
```Swift
extension Vector2D: Equatable {
static func == (left: Vector2D, right: Vector2D) -> Bool {
return (left.x == right.x) && (left.y == right.y)
}
}
```
上述代码实现了“相等”运算符(`==`)来判断两个 `Vector2D` 实例是否相等。对于 `Vector2D` 来说,“相等”意味着“两个实例的 `x``y` 都相等”,这也是代码中用来进行判等的逻辑。如果你已经实现了“相等”运算符,通常情况下你并不需要自己再去实现“不等”运算符(`!=`)。标准库对于“不等”运算符提供了默认的实现,它简单地将“相等”运算符的结果进行取反后返回。
现在我们可以使用这两个运算符来判断两个 `Vector2D` 实例是否相等:
```Swift
let twoThree = Vector2D(x: 2.0, y: 3.0)
let anotherTwoThree = Vector2D(x: 2.0, y: 3.0)
if twoThree == anotherTwoThree {
print("These two vectors are equivalent.")
}
// 打印“These two vectors are equivalent.”
```
多数简单情况下,您可以使用 Swift 为您提供的等价运算符默认实现。Swift 为以下数种自定义类型提供等价运算符的默认实现:
- 只拥有存储属性,并且它们全都遵循 `Equatable` 协议的结构体
- 只拥有关联类型,并且它们全都遵循 `Equatable` 协议的枚举
- 没有关联类型的枚举
在类型原始的声明中声明遵循 `Equatable` 来接收这些默认实现。
下面为三维位置向量 `(x, y, z)` 定义的 `Vector3D` 结构体,与 `Vector2D` 类似。由于 `x``y``z` 属性都是 `Equatable` 类型,`Vector3D` 获得了默认的等价运算符实现。
```Swift
struct Vector3D: Equatable {
var x = 0.0, y = 0.0, z = 0.0
}
let twoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)
let anotherTwoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)
if twoThreeFour == anotherTwoThreeFour {
print("These two vectors are also equivalent.")
}
// 打印“These two vectors are also equivalent.”
```
## 自定义运算符 {#custom-operators}
除了实现标准运算符,在 Swift 中还可以声明和实现*自定义运算符*。可以用来自定义运算符的字符列表请参考 [运算符](../chapter3/02_Lexical_Structure.html#operators)。
新的运算符要使用 `operator` 关键字在全局作用域内进行定义,同时还要指定 `prefix``infix` 或者 `postfix` 修饰符:
```Swift
prefix operator +++
```
上面的代码定义了一个新的名为 `+++` 的前缀运算符。对于这个运算符,在 Swift 中并没有已知的意义,因此在针对 `Vector2D` 实例的特定上下文中,给予了它自定义的意义。对这个示例来讲,`+++` 被实现为“前缀双自增”运算符。它使用了前面定义的复合加法运算符来让矩阵与自身进行相加,从而让 `Vector2D` 实例的 `x` 属性和 `y` 属性值翻倍。你可以像下面这样通过对 `Vector2D` 添加一个 `+++` 类方法,来实现 `+++` 运算符:
```Swift
extension Vector2D {
static prefix func +++ (vector: inout Vector2D) -> Vector2D {
vector += vector
return vector
}
}
var toBeDoubled = Vector2D(x: 1.0, y: 4.0)
let afterDoubling = +++toBeDoubled
// toBeDoubled 现在的值为 (2.0, 8.0)
// afterDoubling 现在的值也为 (2.0, 8.0)
```
### 自定义中缀运算符的优先级 {#precedence-and-associativity-for-custom-infix-operators}
每个自定义中缀运算符都属于某个优先级组。优先级组指定了这个运算符相对于其他中缀运算符的优先级和结合性。[优先级和结合性](#precedence_and_associativity) 中详细阐述了这两个特性是如何对中缀运算符的运算产生影响的。
而没有明确放入某个优先级组的自定义中缀运算符将会被放到一个默认的优先级组内,其优先级高于三元运算符。
以下例子定义了一个新的自定义中缀运算符 `+-`,此运算符属于 `AdditionPrecedence` 优先组:
```Swift
infix operator +-: AdditionPrecedence
extension Vector2D {
static func +- (left: Vector2D, right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x, y: left.y - right.y)
}
}
let firstVector = Vector2D(x: 1.0, y: 2.0)
let secondVector = Vector2D(x: 3.0, y: 4.0)
let plusMinusVector = firstVector +- secondVector
// plusMinusVector 是一个 Vector2D 实例,并且它的值为 (4.0, -2.0)
```
这个运算符把两个向量的 `x` 值相加,同时从第一个向量的 `y` 中减去第二个向量的 `y` 。因为它本质上是属于“相加型”运算符,所以将它放置在 `+``-` 等默认中缀“相加型”运算符相同的优先级组中。关于 Swift 标准库提供的运算符,以及完整的运算符优先级组和结合性设置,请参考 [运算符声明](https://developer.apple.com/documentation/swift/operator_declarations)。而更多关于优先级组以及自定义操作符和优先级组的语法,请参考 [运算符声明](./06_Declarations.md#operator_declaration)。
> 注意
>
> 当定义前缀与后缀运算符的时候,我们并没有指定优先级。然而,如果对同一个值同时使用前缀与后缀运算符,则后缀运算符会先参与运算。

View File

@ -0,0 +1,249 @@
# 不透明类型
具有不透明返回类型的函数或方法会隐藏返回值的类型信息。函数不再提供具体的类型作为返回类型,而是根据它支持的协议来描述返回值。在处理模块和调用代码之间的关系时,隐藏类型信息非常有用,因为返回的底层数据类型仍然可以保持私有。而且不同于返回协议类型,不透明类型能保证类型一致性 —— 编译器能获取到类型信息,同时模块使用者却不能获取到。
## 不透明类型解决的问题 {#the-problem-that-opaque-types-solve}
举个例子,假设你正在写一个模块,用来绘制 ASCII 符号构成的几何图形。它的基本特征是有一个 `draw()` 方法,会返回一个代表最终几何图形的字符串,你可以用包含这个方法的 `Shape` 协议来描述:
```swift
protocol Shape {
func draw() -> String
}
struct Triangle: Shape {
var size: Int
func draw() -> String {
var result = [String]()
for length in 1...size {
result.append(String(repeating: "*", count: length))
}
return result.joined(separator: "\n")
}
}
let smallTriangle = Triangle(size: 3)
print(smallTriangle.draw())
// *
// **
// ***
```
你可以利用泛型来实现垂直翻转之类的操作,就像下面这样。然而,这种方式有一个很大的局限:翻转操作的结果会暴露我们用于构造结果的泛型类型:
```swift
struct FlippedShape<T: Shape>: Shape {
var shape: T
func draw() -> String {
let lines = shape.draw().split(separator: "\n")
return lines.reversed().joined(separator: "\n")
}
}
let flippedTriangle = FlippedShape(shape: smallTriangle)
print(flippedTriangle.draw())
// ***
// **
// *
```
如下方代码所示,用同样的方式定义了一个 `JoinedShape<T: Shape, U: Shape>` 结构体,能将几何图形垂直拼接起来。如果拼接一个翻转三角形和一个普通三角形,它就会得到类似于 `JoinedShape<FlippedShape<Triangle>, Triangle>` 这样的类型。
```swift
struct JoinedShape<T: Shape, U: Shape>: Shape {
var top: T
var bottom: U
func draw() -> String {
return top.draw() + "\n" + bottom.draw()
}
}
let joinedTriangles = JoinedShape(top: smallTriangle, bottom: flippedTriangle)
print(joinedTriangles.draw())
// *
// **
// ***
// ***
// **
// *
```
暴露构造所用的具体类型会造成类型信息的泄露,因为 ASCII 几何图形模块的部分公开接口必须声明完整的返回类型,而实际上这些类型信息并不应该被公开声明。输出同一种几何图形,模块内部可能有多种实现方式,而外部使用时,应该与内部各种变换顺序的实现逻辑无关。诸如 `JoinedShape``FlippedShape` 这样包装后的类型,模块使用者并不关心,它们也不应该可见。模块的公开接口应该由拼接、翻转等基础操作组成,这些操作也应该返回独立的 `Shape` 类型的值。
## 返回不透明类型 {#returning-an-opaque-type}
你可以认为不透明类型和泛型相反。泛型允许调用一个方法时,为这个方法的形参和返回值指定一个与实现无关的类型。举个例子,下面这个函数的返回值类型就由它的调用者决定:
```swift
func max<T>(_ x: T, _ y: T) -> T where T: Comparable { ... }
```
`x``y` 的值由调用 `max(_:_:)` 的代码决定,而它们的类型决定了 `T` 的具体类型。调用代码可以使用任何遵循了 `Comparable` 协议的类型,函数内部也要以一种通用的方式来写代码,才能应对调用者传入的各种类型。`max(_:_:)` 的实现就只使用了所有遵循 `Comparable` 协议的类型共有的特性。
而在返回不透明类型的函数中,上述角色发生了互换。不透明类型允许函数实现时,选择一个与调用代码无关的返回类型。比如,下面的例子返回了一个梯形,却没直接输出梯形的底层类型:
```swift
struct Square: Shape {
var size: Int
func draw() -> String {
let line = String(repeating: "*", count: size)
let result = Array<String>(repeating: line, count: size)
return result.joined(separator: "\n")
}
}
func makeTrapezoid() -> some Shape {
let top = Triangle(size: 2)
let middle = Square(size: 2)
let bottom = FlippedShape(shape: top)
let trapezoid = JoinedShape(
top: top,
bottom: JoinedShape(top: middle, bottom: bottom)
)
return trapezoid
}
let trapezoid = makeTrapezoid()
print(trapezoid.draw())
// *
// **
// **
// **
// **
// *
```
这个例子中,`makeTrapezoid()` 函数将返回值类型定义为 `some Shape`;因此,该函数返回遵循 `Shape` 协议的给定类型,而不需指定任何具体类型。这样写 `makeTrapezoid()` 函数可以表明它公共接口的基本性质 —— 返回的是一个几何图形 —— 而不是部分的公共接口生成的特殊类型。上述实现过程中使用了两个三角形和一个正方形,还可以用其他多种方式重写画梯形的函数,都不必改变返回类型。
这个例子凸显了不透明返回类型和泛型的相反之处。`makeTrapezoid()` 中代码可以返回任意它需要的类型,只要这个类型是遵循 `Shape` 协议的,就像调用泛型函数时可以使用任何需要的类型一样。这个函数的调用代码需要采用通用的方式,就像泛型函数的实现代码一样,这样才能让 `makeTrapezoid()` 返回的任何 `Shape` 类型的值都能被正常使用。
你也可以将不透明返回类型和泛型结合起来,下面的两个泛型函数也都返回了遵循 `Shape` 协议的不透明类型。
```swift
func flip<T: Shape>(_ shape: T) -> some Shape {
return FlippedShape(shape: shape)
}
func join<T: Shape, U: Shape>(_ top: T, _ bottom: U) -> some Shape {
JoinedShape(top: top, bottom: bottom)
}
let opaqueJoinedTriangles = join(smallTriangle, flip(smallTriangle))
print(opaqueJoinedTriangles.draw())
// *
// **
// ***
// ***
// **
// *
```
这个例子中 `opaqueJoinedTriangles` 的值和前文 [不透明类型解决的问题](#the-problem-that-opaque-types-solve) 中关于泛型的那个例子中的 `joinedTriangles` 完全一样。不过和前文不一样的是,`flip(_:)``join(_:_:)` 将对泛型参数的操作后的返回结果包装成了不透明类型,这样保证了在结果中泛型参数类型不可见。两个函数都是泛型函数,因为他们都依赖于泛型参数,而泛型参数又将 `FlippedShape``JoinedShape` 所需要的类型信息传递给它们。
如果函数中有多个地方返回了不透明类型,那么所有可能的返回值都必须是同一类型。即使对于泛型函数,不透明返回类型可以使用泛型参数,但仍需保证返回类型唯一。比如,下面就是一个*非法*示例 —— 包含针对 `Square` 类型进行特殊处理的翻转函数。
```swift
func invalidFlip<T: Shape>(_ shape: T) -> some Shape {
if shape is Square {
return shape // 错误:返回类型不一致
}
return FlippedShape(shape: shape) // 错误:返回类型不一致
}
```
如果你调用这个函数时传入一个 `Square` 类型,那么它会返回 `Square` 类型;否则,它会返回一个 `FlippedShape` 类型。这违反了返回值类型唯一的要求,所以 `invalidFlip(_:)` 不正确。修正 `invalidFlip(_:)` 的方法之一就是将针对 `Square` 的特殊处理移入到 `FlippedShape` 的实现中去,这样就能保证这个函数始终返回 `FlippedShape`
```swift
struct FlippedShape<T: Shape>: Shape {
var shape: T
func draw() -> String {
if shape is Square {
return shape.draw()
}
let lines = shape.draw().split(separator: "\n")
return lines.reversed().joined(separator: "\n")
}
}
```
返回类型始终唯一的要求,并不会影响在返回的不透明类型中使用泛型。比如下面的函数,就是在返回的底层类型中使用了泛型参数:
```swift
func `repeat`<T: Shape>(shape: T, count: Int) -> some Collection {
return Array<T>(repeating: shape, count: count)
}
```
这种情况下,返回的底层类型会根据 `T` 的不同而发生变化:但无论什么形状被传入,`repeat(shape:count:)` 都会创建并返回一个元素为相应形状的数组。尽管如此,返回值始终还是同样的底层类型 `[T]` 所以这符合不透明返回类型始终唯一的要求。
## 不透明类型和协议类型的区别 {#differences-between-opaque-types-and-protocol-types}
虽然使用不透明类型作为函数返回值,看起来和返回协议类型非常相似,但这两者有一个主要区别,就在于是否需要保证类型一致性。一个不透明类型只能对应一个具体的类型,即便函数调用者并不能知道是哪一种类型;协议类型可以同时对应多个类型,只要它们都遵循同一协议。总的来说,协议类型更具灵活性,底层类型可以存储更多样的值,而不透明类型对这些底层类型有更强的限定。
比如,这是 `flip(_:)` 方法不采用不透明类型,而采用返回协议类型的版本:
```swift
func protoFlip<T: Shape>(_ shape: T) -> Shape {
return FlippedShape(shape: shape)
}
```
这个版本的 `protoFlip(_:)``flip(_:)` 有相同的函数体,并且它也始终返回唯一类型。但不同于 `flip(_:)``protoFlip(_:)` 返回值其实不需要始终返回唯一类型 —— 返回类型只需要遵循 `Shape` 协议即可。换句话说,`protoFlip(_:)` 比起 `flip(_:)` 对 API 调用者的约束更加松散。它保留了返回多种不同类型的灵活性:
```swift
func protoFlip<T: Shape>(_ shape: T) -> Shape {
if shape is Square {
return shape
}
return FlippedShape(shape: shape)
}
```
修改后的代码根据代表形状的参数的不同,可能返回 `Square` 实例或者 `FlippedShape` 实例,所以同样的函数可能返回完全不同的两个类型。当翻转相同形状的多个实例时,此函数的其他有效版本也可能返回完全不同类型的结果。`protoFlip(_:)` 返回类型的不确定性,意味着很多依赖返回类型信息的操作也无法执行了。举个例子,这个函数的返回结果就不能用 == 运算符进行比较了。
```swift
let protoFlippedTriangle = protoFlip(smallTriangle)
let sameThing = protoFlip(smallTriangle)
protoFlippedTriangle == sameThing // 错误
```
上面的例子中,最后一行的错误来源于多个原因。最直接的问题在于,`Shape` 协议中并没有包含对 == 运算符的声明。如果你尝试加上这个声明,那么你会遇到新的问题,就是 == 运算符需要知道左右两侧参数的类型。这类运算符通常会使用 `Self` 类型作为参数,用来匹配符合协议的具体类型,但是由于将协议当成类型使用时会发生类型擦除,所以并不能给协议加上对 `Self` 的实现要求。
将协议类型作为函数的返回类型能更加灵活,函数只要返回遵循协议的类型即可。然而,更具灵活性导致牺牲了对返回值执行某些操作的能力。上面的例子就说明了为什么不能使用 == 运算符 —— 它依赖于具体的类型信息,而这正是使用协议类型所无法提供的。
这种方法的另一个问题在于,变换形状的操作不能嵌套。翻转三角形的结果是一个 `Shape` 类型的值,而 `protoFlip(_:)` 方法的则将遵循 `Shape` 协议的类型作为形参,然而协议类型的值并不遵循这个协议;`protoFlip(_:)` 的返回值也并不遵循 `Shape` 协议。这就是说 `protoFlip(protoFlip(smallTriange))` 这样的多重变换操作是非法的,因为经过翻转操作后的结果类型并不能作为 `protoFlip(_:)` 的形参。
相比之下不透明类型则保留了底层类型的唯一性。Swift 能够推断出关联类型,这个特点使得作为函数返回值,不透明类型比协议类型有更大的使用场景。比如,下面这个例子是 [泛型](./22_Generics.md) 中讲到的 `Container` 协议:
```swift
protocol Container {
associatedtype Item
var count: Int { get }
subscript(i: Int) -> Item { get }
}
extension Array: Container { }
```
你不能将 `Container` 作为方法的返回类型,因为此协议有一个关联类型。你也不能将它用于对泛型返回类型的约束,因为函数体之外并没有暴露足够多的信息来推断泛型类型。
```swift
// 错误:有关联类型的协议不能作为返回类型。
func makeProtocolContainer<T>(item: T) -> Container {
return [item]
}
// 错误:没有足够多的信息来推断 C 的类型。
func makeProtocolContainer<T, C: Container>(item: T) -> C {
return [item]
}
```
而使用不透明类型 `some Container` 作为返回类型,就能够明确地表达所需要的 API 契约 —— 函数会返回一个集合类型,但并不指明它的具体类型:
```swift
func makeOpaqueContainer<T>(item: T) -> some Container {
return [item]
}
let opaqueContainer = makeOpaqueContainer(item: 12)
let twelve = opaqueContainer[0]
print(type(of: twelve))
// 输出 "Int"
```
`twelve` 的类型可以被推断出为 `Int` 这说明了类型推断适用于不透明类型。在 `makeOpaqueContainer(item:)` 的实现中,底层类型是不透明集合 `[T]`。在上述这种情况下,`T` 就是 `Int` 类型,所以返回值就是整数数组,而关联类型 `Item` 也被推断出为 `Int``Container` 协议中的 `subscipt` 方法会返回 `Item`,这也意味着 `twelve` 的类型也被能推断出为 `Int`

6
source/chapter2/chapter2.md Executable file → Normal file
View File

@ -1,3 +1,3 @@
# Swift 教程
本章介绍了 Swift 的各种特性及其使用方法,是全书的核心部分。
# Swift 教程
本章介绍了 Swift 的各种特性及其使用方法,是全书的核心部分。

View File

@ -1,40 +1,32 @@
# 关于语言参考About the Language Reference
-----------------
> 1.0
> 翻译:[dabing1022](https://github.com/dabing1022)
> 校对:[numbbbbb](https://github.com/numbbbbb)
本书的这一节描述了 Swift 编程语言的形式语法。这里描述的语法是为了帮助您了解该语言的更多细节,而不是让您直接实现一个解析器或编译器。
> 2.0
> 翻译+校对:[KYawn](https://github.com/KYawn)
Swift 语言相对较小,这是由于 Swift 代码中常用的类型、函数以及运算符都已经在 Swift 标准库中定义了。虽然这些类型、函数和运算符并不是 Swift 语言自身的一部分,但是它们被广泛应用于本书的讨论和代码范例中。
本页内容包括:
- [如何阅读语法](#how_to_read_the_grammar)
本书的这一节描述了 Swift 编程语言的形式语法。这里描述的语法是为了帮助您更详细地了解该语言,而不是让您直接实现一个解析器或编译器。
Swift 语言相对较小,这是由于 Swift 代码中的几乎所有常见类型、函数以及运算符都已经在 Swift 标准库中定义了。虽然这些类型、函数和运算符并不是 Swift 语言自身的一部分,但是它们被广泛应用于本书的讨论和代码范例中。
<a name="how_to_read_the_grammar"></a>
## 如何阅读语法
## 如何阅读语法 {#how-to-read-the-grammar}
用来描述 Swift 编程语言形式语法的符号遵循下面几个约定:
- 箭头(`→`)用来标记语法产式,可以理解为“可由……构成”。
- 斜体文字用来表示句法类型,并出现在一个语法产式规则两侧。
- 关键字和标点符号由固定宽度的粗体文本表示,只出现在一个语法产式规则的右侧。
- 标记语言和标点符号由固定宽度的粗体文本表示,只出现在一个语法产式规则的右侧。
- 可供选择的语法产式由竖线(`|`)分隔。当可选用的语法产式太多时,为了阅读方便,它们将被拆分为多行语法产式规则。
- 少数情况下,语法产规则的右侧会有用于描述的常规字体文字
- 可选的句法类型和字面值用尾标 `opt` 来标记。
- 少数情况下,标准字体文本被用来描述一个语法产规则的右手侧内容
- 可选的句法类型和文本标记用尾标 `opt` 来标记。
举个例子getter-setter 的语法块的定义如下:
举个例子getter-setter 方法块的语法定义如下:
> getter-setter 方法块语法
> *getter-setter 方法块* → { [*getter 子句*](05_Declarations.html#getter-clause) [*setter 子句*](05_Declarations.html#setter-clause)<sub>可选</sub> } | { [*setter 子句*](05_Declarations.html#setter-clause) [*getter 子句*](05_Declarations.html#getter-clause) }
> getter-setter 方法块语法
>
> *getter-setter 方法块* → { [*getter 子句*](./06_Declarations.md#getter-clause) [*setter 子句*](./06_Declarations.md#setter-clause)<sub>可选</sub> } | { [*setter 子句*](./06_Declarations.md#setter-clause) [*getter 子句*](./06_Declarations.md#getter-clause) }
>
这个定义表明,一个 getter-setter 方法块可以由一个 getter 句后跟一个可选的 setter 句构成,然后用大括号括起来,或者由一个 setter 句后跟一个 getter 句构成,然后用大括号括起来。下面的两个语法产式等价于上述的语法产式,并明确指出了如何取舍
这个定义表明,一个 getter-setter 方法块可以由一个 getter 句后跟一个可选的 setter 句构成,然后用大括号括起来,或者由一个 setter 句后跟一个 getter 句构成,然后用大括号括起来。上述的语法产式等价于下面的两个语法产式,
> getter-setter 方法块语法
> getter-setter 方法块 → { [*getter 子句*](05_Declarations.html#getter-clause) [*setter 子句*](05_Declarations.html#setter-clause)<sub>可选</sub> }
> getter-setter 方法块 → { [*setter 子句*](05_Declarations.html#setter-clause) [*getter 子句*](05_Declarations.html#getter-clause) }
> getter-setter 方法块语法
>
> getter-setter 方法块 → { [*getter 子句*](./06_Declarations.md#getter-clause) [*setter 子句*](./06_Declarations.md#setter-clause)<sub>可选</sub> }
>
> getter-setter 方法块 → { [*setter 子句*](./06_Declarations.md#setter-clause) [*getter 子句*](./06_Declarations.md#getter-clause) }
>

View File

@ -1,115 +1,157 @@
# 词法结构Lexical Structure
-----------------
> 1.0
> 翻译:[superkam](https://github.com/superkam)
> 校对:[numbbbbb](https://github.com/numbbbbb)
Swift 的*“词法结构lexical structure”* 描述了能构成该语言中有效符号token的字符序列。这些合法符号组成了语言中最底层的构建基块并在之后的章节中用于描述语言的其他部分。一个合法符号由一个标识符identifier、关键字keyword、标点符号punctuation、字面量literal或运算符operator组成。
通常情况下,通过考虑输入文本当中可能的最长子串,并且在随后将介绍的语法约束之下,根据随后将介绍的语法约束生成的,根据 Swift 源文件当中的字符来生成相应的“符号”。这种方法称为*“最长匹配longest match”*,或者*“最大适合maximal munch”*。
## 空白与注释 {#whitespace}
> 2.0
> 翻译+校对:[buginux](https://github.com/buginux)
> 2.1
> 翻译:[mmoaay](https://github.com/mmoaay)
> 2.2
> 翻译+校对:[星夜暮晨](https://github.com/semperidem)2016-04-06
本页包含内容:
- [空白与注释](#whitespace_and_comments)
- [标识符](#identifiers)
- [关键字和标点符号](#keywords)
- [字面量](#literals)
- [整数字面量](#integer_literals)
- [浮点数字面量](#floating_point_literals)
- [字符串字面量](#string_literals)
- [运算符](#operators)
Swift 的*“词法结构 (lexical structure)”* 描述了能构成该语言中合法符号 (token) 的字符序列。这些合法符号组成了语言中最底层的构建基块,并在之后的章节中用于描述语言的其他部分。一个合法符号由一个标识符 (identifier)、关键字 (keyword)、标点符号 (punctuation)、字面量 (literal) 或运算符 (operator) 组成。
通常情况下,通过考虑输入文本当中可能的最长子串,并且在随后将介绍的语法约束之下,根据随后将介绍的语法约束生成的,根据 Swift 源文件当中的字符来生成相应的“符号”。这种方法称为*“最长匹配 (longest match)”*,或者*“最大适合(maximal munch)”*。
<a id="whitespace_and_comments"></a>
## 空白与注释
空白 (whitespace) 有两个用途:分隔源文件中的符号以及帮助区分运算符属于前缀还是后缀(参见 [运算符](#operators)在其他情况下空白则会被忽略。以下的字符会被当作空白空格U+0020、换行符U+000A、回车符U+000D、水平制表符U+0009、垂直制表符U+000B、换页符U+000C以及空字符U+0000
空白whitespace有两个用途分隔源文件中的符号以及帮助区分运算符属于前缀还是后缀参见 [运算符](#operators)在其他情况下空白则会被忽略。以下的字符会被当作空白空格U+0020、换行符U+000A、回车符U+000D、水平制表符U+0009、垂直制表符U+000B、换页符U+000C以及空字符U+0000
注释被编译器当作空白处理。单行注释由 `//` 开始直至遇到换行符U+000A或者回车符U+000D。多行注释由 `/*` 开始,以 `*/` 结束。注释允许嵌套,但注释标记必须匹配。
正如 [*Markup Formatting Reference*](https://developer.apple.com/library/prerelease/ios/documentation/Xcode/Reference/xcode_markup_formatting_ref/index.html#//apple_ref/doc/uid/TP40016497) 所述,注释可以包含附加的格式和标记。
> 空白语法
>
> *空白* → [*空白项*](#whitespace-item) [*空白*](#whitespace)<sub>可选</sub>
>
#### whitespace-item {#whitespace-item}
>
> *空白项* → [*断行符*](#line-break)
>
> *空白项* → [*注释*](#comment)
>
> *空白项* → [*多行注释*](#multiline-comment)
>
> *空白项* → U+0000U+0009U+000BU+000C 或者 U+0020
>
>
#### line-break {#line-break}
>
> *断行符* → U+000A
>
> *断行符* → U+000D
>
> *断行符* → U+000D 接着是 U+000A
>
>
#### comment {#comment}
>
> *注释* → // [*注释内容*](#comment-text) [断行符*](#line-break)
>
>
#### multiline-comment {#multiline-comment}
>
> *多行注释* → `/*` [*多行注释内容*](#multiline-commnet-text) `*/`
>
>
#### comment-text {#comment-text}
>
> *注释内容* → [*注释内容项*](#comment-text-item) [*注释内容*](#comment-text)<sub>可选</sub>
>
>
#### comment-text-item {#comment-text-item}
>
> *注释内容项* → 任何 Unicode 标量值, 除了 U+000A 或者 U+000D
>
>
#### multiline-commnet-text {#multiline-commnet-text}
>
> *多行注释内容* → [*多行注释内容项*](#multiline-comment-text-item) [*多行注释内容*](#multiline-comment-text)<sub>可选</sub>
>
> *多行注释内容项* → [*多行注释*](#multiline-comment).
>
> *多行注释内容项* → [*注释内容项*](#comment-text-item)
>
> *多行注释内容项* → 任何 Unicode 标量值, 除了 `/*` 或者 `*/`
<a id="identifiers"></a>
## 标识符
## 标识符 {#identifiers}
*标识符(identifier)* 可以由以下的字符开始:大写或小写的字母 `A``Z`、下划线 (`_`)、基本多文种平面 (Basic Multilingual Plane) 中非字符数字组合的 Unicode 字符以及基本多文种平面以外的非个人专用区字符。在首字符之后,允许使用数字和组合 Unicode 字符。
*标识符identifier* 可以由以下的字符开始:大写或小写的字母 `A``Z`、下划线`_`、基本多文种平面Basic Multilingual Plane中非字符数字组合的 Unicode 字符以及基本多文种平面以外的非个人专用区字符。在首字符之后,允许使用数字和组合 Unicode 字符。
使用保留字作为标识符,需要在其前后增加反引号 (`` ` ``)。例如,`class` 不是合法的标识符,但可以使用 `` `class` ``。反引号不属于标识符的一部分,`` `x` `` 和 `x` 表示同一标识符。
使用保留字作为标识符,需要在其前后增加反引号`` ` ``。例如,`class` 不是合法的标识符,但可以使用 `` `class` ``。反引号不属于标识符的一部分,`` `x` `` 和 `x` 表示同一标识符。
闭包中如果没有明确指定参数名称,参数将被隐式命名为 `$0`、`$1`、`$2` 等等。这些命名在闭包作用域范围内是合法的标识符。
> 标识符语法
<a id="identifier"></a>
> *标识符* → [*头部标识符*](#identifier-head) [*标识符字符组*](#identifier-characters)<sub>可选</sub>
> *标识符* → \`[*头部标识符*](#identifier-head) [*标识符字符组*](#identifier-characters)<sub>可选</sub>\`
> *标识符* → [*隐式参数名*](#implicit-parameter-name)
<a id="identifier-list"></a>
> *标识符列表* → [*标识符*](#identifier) | [*标识符*](#identifier) **,** [*标识符列表*](#identifier-list)
<a id="identifier-head"></a>
> *头部标识符* → 大写或小写字母 A - Z
> *头部标识符* → _
> *头部标识符* → U+00A8U+00AAU+00ADU+00AFU+00B2U+00B5或者 U+00B7U+00BA
> *头部标识符* → U+00BCU+00BEU+00C0U+00D6U+00D8U+00F6或者 U+00F8U+00FF
> *头部标识符* → U+0100U+02FFU+0370U+167FU+1681U+180D或者 U+180FU+1DBF
> *头部标识符* → U+1E00U+1FFF
> *头部标识符* → U+200BU+200DU+202AU+202EU+203FU+2040U+2054或者 U+2060U+206F
> *头部标识符* → U+2070U+20CFU+2100U+218FU+2460U+24FF或者 U+2776U+2793
> *头部标识符* → U+2C00U+2DFF 或者 U+2E80U+2FFF
> *头部标识符* → U+3004U+3007U+3021U+302FU+3031U+303F或者 U+3040U+D7FF
> *头部标识符* → U+F900U+FD3DU+FD40U+FDCFU+FDF0U+FE1F或者 U+FE30U+FE44
> *头部标识符* → U+FE47U+FFFD
> *头部标识符* → U+10000U+1FFFDU+20000U+2FFFDU+30000U+3FFFD或者 U+40000U+4FFFD
> *头部标识符* → U+50000U+5FFFDU+60000U+6FFFDU+70000U+7FFFD或者 U+80000U+8FFFD
> *头部标识符* → U+90000U+9FFFDU+A0000U+AFFFDU+B0000U+BFFFD或者 U+C0000U+CFFFD
> *头部标识符* → U+D0000U+DFFFD 或者 U+E0000U+EFFFD
<a id="identifier-character"></a>
> *标识符字符* → 数值 0 - 9
> *标识符字符* → U+0300U+036FU+1DC0U+1DFFU+20D0U+20FF或者 U+FE20U+FE2F
>
> *标识符* → [*头部标识符*](#identifier-head) [*标识符字符组*](#identifier-characters)<sub>可选</sub>
>
> *标识符* → \`[*头部标识符*](#identifier-head) [*标识符字符组*](#identifier-characters)<sub>可选</sub>\`
>
> *标识符* → [*隐式参数名*](#implicit-parameter-name)
>
> *标识符列表* → [*标识符*](#identifier) | [*标识符*](#identifier) **,** [*标识符列表*](#identifier)
>
>
#### identifier-head {#identifier-head}
>
> *头部标识符* → 大写或小写字母 A - Z
>
> *头部标识符* → _
>
> *头部标识符* → U+00A8U+00AAU+00ADU+00AFU+00B2U+00B5或者 U+00B7U+00BA
>
> *头部标识符* → U+00BCU+00BEU+00C0U+00D6U+00D8U+00F6或者 U+00F8U+00FF
>
> *头部标识符* → U+0100U+02FFU+0370U+167FU+1681U+180D或者 U+180FU+1DBF
>
> *头部标识符* → U+1E00U+1FFF
>
> *头部标识符* → U+200BU+200DU+202AU+202EU+203FU+2040U+2054或者 U+2060U+206F
>
> *头部标识符* → U+2070U+20CFU+2100U+218FU+2460U+24FF或者 U+2776U+2793
>
> *头部标识符* → U+2C00U+2DFF 或者 U+2E80U+2FFF
>
> *头部标识符* → U+3004U+3007U+3021U+302FU+3031U+303F或者 U+3040U+D7FF
>
> *头部标识符* → U+F900U+FD3DU+FD40U+FDCFU+FDF0U+FE1F或者 U+FE30U+FE44
>
> *头部标识符* → U+FE47U+FFFD
>
> *头部标识符* → U+10000U+1FFFDU+20000U+2FFFDU+30000U+3FFFD或者 U+40000U+4FFFD
>
> *头部标识符* → U+50000U+5FFFDU+60000U+6FFFDU+70000U+7FFFD或者 U+80000U+8FFFD
>
> *头部标识符* → U+90000U+9FFFDU+A0000U+AFFFDU+B0000U+BFFFD或者 U+C0000U+CFFFD
>
> *头部标识符* → U+D0000U+DFFFD 或者 U+E0000U+EFFFD
>
> *标识符字符* → 数值 0 - 9
>
>
#### identifier-character {#identifier-character}
>
> *标识符字符* → U+0300U+036FU+1DC0U+1DFFU+20D0U+20FF或者 U+FE20U+FE2F
>
> *标识符字符* → [*头部标识符*](#identifier-head)
> <a id="identifier-characters"></a>
> *标识符字符组* → [*标识符字符*](#identifier-character) [*标识符字符组*](#identifier-characters)<sub>可选</sub>
>
>
#### identifier-characters {#identifier-characters}
>
> *标识符字符组* → [*标识符字符*](#identifier-character) [*标识符字符组*](#identifier-characters)<sub>可选</sub>
>
>
#### implicit-parameter-name {#implicit-parameter-name}
>
> *隐式参数名* → **$** [*十进制数字列表*](#decimal-digit)
<a id="implicit-parameter-name"></a>
> *隐式参数名* → **$** [*十进制数字列表*](#decimal-digits)
## 关键字和标点符号 {#keywords-and-punctuation}
<a id="keywords"></a>
## 关键字和标点符号
下面这些被保留的关键字不允许用作标识符,除非使用反引号转义,具体描述请参考 [标识符](#identifiers)。除了 `inout``var` 以及 `let` 之外的关键字可以用作某个函数声明或者函数调用当中的外部参数名,无需添加反引号转义。当一个成员与一个关键字具有相同的名称时,不需要使用反引号来转义对该成员的引用,除非在引用该成员和使用该关键字之间存在歧义 - 例如,`self``Type``Protocol` 在显式的成员表达式中具有特殊的含义,因此它们必须在该上下文中使用反引号进行转义。
下面这些被保留的关键字不允许用作标识符,除非使用反引号转义,具体描述请参考 [标识符](#identifiers)。除了 `inout``var` 以及 `let` 之外的关键字可以用作某个函数声明或者函数调用当中的外部参数名,不用添加反引号转义
* 用在声明中的关键字: `associatedtype``class``deinit``enum``extension``func``import``init``inout``internal``let``operator``private``protocol``public``static``struct``subscript``typealias` 以及 `var`
* 用在声明中的关键字: `associatedtype``class``deinit``enum``extension``fileprivate ``func``import``init``inout``internal``let``open``operator``private``protocol``public``static``struct``subscript``typealias` 以及 `var`
* 用在语句中的关键字:`break``case``continue``default``defer``do``else``fallthrough``for``guard``if``in``repeat``return``switch``where` 以及 `while`
* 用在表达式和类型中的关键字:`as``catch``dynamicType``false``is``nil``rethrows``super``self``Self``throw``throws``true``try``#column``#file``#function` 以及 `#line`
* 用在表达式和类型中的关键字:`as``Any``catch``false``is``nil``rethrows``super``self``Self``throw``throws``true` 以及 `try `
* 用在模式中的关键字:`_`
* 以井字号 (`#`) 开头的关键字:`#available``#column``#else#elseif``#endif``#file``#function``#if``#line` 以及 `#selector`
* 以井字号`#`开头的关键字:`#available``#colorLiteral``#column``#else``#elseif``#endif``#error``#file``#fileLiteral``#function``#if``#imageLiteral ``#line``#selector``#sourceLocation`以及 `#warning`
* 特定上下文中被保留的关键字: `associativity``convenience``dynamic``didSet``final``get``infix``indirect``lazy``left``mutating``none``nonmutating``optional``override``postfix``precedence``prefix``Protocol``required``right``set``Type``unowned``weak` 以及 `willSet`。这些关键字在特定上下文之外可以被用做标识符。
以下符号被当作保留符号,不能用于自定义运算符: `(``)``{``}``[``]``.``,``:``;``=``@``#``&`(作为前缀运算符)、`->`、`` ` ``、`?`、`!`(作为后缀运算符)。
<a id="literals"></a>
## 字面量
## 字面量 {#literal}
*字面量 (literal)* 用来表示源码中某种特定类型的值,比如一个数字或字符串。
*字面量literal* 用来表示源码中某种特定类型的值,比如一个数字或字符串。
下面是字面量的一些示例:
@ -120,128 +162,199 @@ Swift 的*“词法结构 (lexical structure)”* 描述了能构成该语言中
true // 布尔值字面量
```
字面量本身并不包含类型信息。事实上,一个字面量会被解析为拥有无限的精度,然后 Swift 的类型推导会尝试去推导出这个字面量的类型。比如,在 `let x: Int8 = 42` 这个声明中Swift 使用了显式类型注(`: Int8`)来推导出 `42` 这个整数字面量的类型是 `Int8`。如果没有可用的类型信息, Swift 则会从标准库中定义的字面量类型中推导出一个默认的类型。整数字面量的默认类型是 `Int`,浮点数字面量的默认类型是 `Double`,字符串字面量的默认类型是 `String`,布尔值字面量的默认类型是 `Bool`。比如,在 `let str = "Hello, world"` 这个声明中,字符串 `"Hello, world"` 的默认推导类型就是 `String`。
字面量本身并不包含类型信息。事实上,一个字面量会被解析为拥有无限的精度,然后 Swift 的类型推导会尝试去推导出这个字面量的类型。比如,在 `let x: Int8 = 42` 这个声明中Swift 使用了显式类型注`: Int8`)来推导出 `42` 这个整数字面量的类型是 `Int8`。如果没有可用的类型信息, Swift 则会从标准库中定义的字面量类型中推导出一个默认的类型。整数字面量的默认类型是 `Int`,浮点数字面量的默认类型是 `Double`,字符串字面量的默认类型是 `String`,布尔值字面量的默认类型是 `Bool`。比如,在 `let str = "Hello, world"` 这个声明中,字符串 `"Hello, world"` 的默认推导类型就是 `String`。
当为一个字面量值指定了类型注的时候,这个标注的类型必须能通过这个字面量值实例化。也就是说,这个类型必须符合这些 Swift 标准库协议中的一个:整数字面量的 `IntegerLiteralConvertible` 协议、浮点数字面量的 `FloatingPointLiteralConvertible` 协议、字符串字面量的 `StringLiteralConvertible` 协议以及布尔值字面量的 `BooleanLiteralConvertible` 协议。比如,`Int8` 符合 `IntegerLiteralConvertible` 协议,因此它能在 `let x: Int8 = 42` 这个声明中作为整数字面量 `42` 的类型注。
当为一个字面量值指定了类型注的时候,这个标注的类型必须能通过这个字面量值实例化。也就是说,这个类型必须符合这些 Swift 标准库协议中的一个:整数字面量的 `IntegerLiteralConvertible` 协议、浮点数字面量的 `FloatingPointLiteralConvertible` 协议、字符串字面量的 `StringLiteralConvertible` 协议以及布尔值字面量的 `BooleanLiteralConvertible` 协议。比如,`Int8` 符合 `IntegerLiteralConvertible` 协议,因此它能在 `let x: Int8 = 42` 这个声明中作为整数字面量 `42` 的类型注
> 字面量语法
> *字面量* → [*数值字面量*](#numeric-literal) | [*字符串字面量*](#string-literal) | [*布尔值字面量*](#boolean-literal) | [*nil 字面量*](#nil-literal)
<a id="numeric-literal"></a>
> *数值字面量* → **-**<sub>可选</sub> [*整数字面量*](#integer-literal) | **-**<sub>可选</sub> [*浮点数字面量*](#floating-point-literal)
> <a id="boolean-literal"></a>
> *布尔值字面量* → **true** | **false**
> <a id="nil-literal"></a>
> 字面量语法
>
> *字面量* → [*数值字面量*](#integer-literal) | [*字符串字面量*](#string-literal) | [*布尔值字面量*](#integer-literal) | [*nil 字面量*](#integer-literal)
>
> *数值字面量* → **-**<sub>可选</sub> [*整数字面量*](#integer-literal) | **-**<sub>可选</sub> [*浮点数字面量*](#floating-point-literal)
>
> *布尔值字面量* → **true** | **false**
>
> *nil 字面量* → **nil**
<a id="integer_literals"></a>
### 整数字面量
*整数字面量 (Integer Literals)* 表示未指定精度整数的值。整数字面量默认用十进制表示,可以加前缀来指定其他的进制。二进制字面量加 `0b`,八进制字面量加 `0o`,十六进制字面量加 `0x`。
### 整数字面量{#integer-literal}
*整数字面量Integer Literals* 表示未指定精度整数的值。整数字面量默认用十进制表示,可以加前缀来指定其他的进制。二进制字面量加 `0b`,八进制字面量加 `0o`,十六进制字面量加 `0x`。
十进制字面量包含数字 `0` 至 `9`。二进制字面量只包含 `0` 或 `1`,八进制字面量包含数字 `0` 至 `7`,十六进制字面量包含数字 `0` 至 `9` 以及字母 `A` 至 `F`(大小写均可)。
负整数的字面量在整数字面量前加负号 `-`,比如 `-42`。
整型字面面可以使用下划线 (`_`) 来增加数字的可读性,下划线会被系统忽略,因此不会影响字面量的值。同样地,也可以在数字前加 `0`,这同样也会被系统所忽略,并不会影响字面量的值。
整型字面面可以使用下划线`_`来增加数字的可读性,下划线会被系统忽略,因此不会影响字面量的值。同样地,也可以在数字前加 `0`,这同样也会被系统所忽略,并不会影响字面量的值。
除非特别指定,整数字面量的默认推导类型为 Swift 标准库类型中的 `Int`。Swift 标准库还定义了其他不同长度以及是否带符号的整数类型,请参考 [整数](../chapter2/01_The_Basics.html#integers)。
除非特别指定,整数字面量的默认推导类型为 Swift 标准库类型中的 `Int`。Swift 标准库还定义了其他不同长度以及是否带符号的整数类型,请参考 [整数](../chapter2/01_The_Basics.md#integers)。
> 整数字面量语法
<a id="integer-literal"></a>
> *整数字面量* → [*二进制字面量*](#binary-literal)
> *整数字面量* → [*八进制字面量*](#octal-literal)
> *整数字面量* → [*进制字面量*](#decimal-literal)
> *整数字面量* → [*十六进制字面量*](#hexadecimal-literal)
<a id="binary-literal"></a>
> *二进制字面量* → **0b** [*二进制数字*](#binary-digit) [*二进制字面量字符组*](#binary-literal-characters)<sub>可选</sub>
> <a id="binary-digit"></a>
> *二进制数字* → 数值 0 到 1
> <a id="binary-literal-character"></a>
> *二进制字面量字符* → [*二进制数字*](#binary-digit) | _
> <a id="binary-literal-characters"></a>
> *二进制字面量字符组* → [*二进制字面量字符*](#binary-literal-character) [*二进制字面量字符组*](#binary-literal-characters)<sub>可选</sub>
<a id="octal-literal"></a>
> *八进制字面量* → **0o** [*八进字数字*](#octal-digit) [*八进制字符组*](#octal-literal-characters)<sub>可选</sub>
> <a id="octal-digit"></a>
> *八进字数字* → 数值 0 到 7
> <a id="octal-literal-character"></a>
> *八进制字符* → [*八进字数字*](#octal-digit) | _
> <a id="octal-literal-characters"></a>
> 整数字面量语法
>
>
#### integer-literal {#integer-literal}
>
> *整数字面量* → [*进制字面量*](#binary-literal)
>
> *整数字面量* → [*八进制字面量*](#octal-literal)
>
> *整数字面量* → [*十进制字面量*](#decimal-literal)
>
> *整数字面量* → [*十六进制字面量*](#hexadecimal-literal)
>
>
#### binary-literal {#binary-literal}
>
> *二进制字面量* → **0b** [*二进制数字*](#binary-digit) [*二进制字面量字符组*](#binary-literal-characters)<sub>可选</sub>
>
>
#### binary-digit {#binary-digit}
>
> *二进制数字* → 数值 0 到 1
>
> *二进制字面量字符* → [*二进制数字*](#binary-digit) | _
>
>
#### binary-literal-characters {#binary-literal-characters}
>
> *二进制字面量字符组* → [*二进制字面量字符*](#binary-literal-character) [*二进制字面量字符组*](#binary-literal-characters)<sub>可选</sub>
>
>
#### octal-literal {#octal-literal}
>
> *八进制字面量* → **0o** [*八进字数字*](#octal-digit) [*八进制字符组*](#octal-literal-characters)<sub>可选</sub>
>
>
#### octal-digit {#octal-digit}
>
> *八进字数字* → 数值 0 到 7
>
> *八进制字符* → [*八进字数字*](#octal-digit) | _
>
>
#### octal-literal-characters {#octal-literal-characters}
>
> *八进制字符组* → [*八进制字符*](#octal-literal-character) [*八进制字符组*](#octal-literal-characters)<sub>可选</sub>
>
>
#### decimal-literal {#decimal-literal}
>
> *十进制字面量* → [*十进制数字*](#decimal-digit) [*十进制字符组*](#decimal-literal-characters)<sub>可选</sub>
>
>
#### decimal-digit {#decimal-digit}
>
> *十进制数字* → 数值 0 到 9
>
>
#### decimal-literal-characters {#decimal-literal-characters}
>
> *十进制数字组* → [*十进制数字*](#decimal-digit) [*十进制数字组*](#decimal-literal-characters)<sub>可选</sub>
>
> *十进制字符* → [*十进制数字*](#decimal-digit) | _
>
> *十进制字符组* → [*十进制字符*](#decimal-literal-characters) [*十进制字符组*](#decimal-literal-characters)<sub>可选</sub>
>
>
#### hexadecimal-literal {#hexadecimal-literal}
>
> *十六进制字面量* → **0x** [*十六进制数字*](#hexadecimal-digit) [*十六进制字面量字符组*](#hexadecimal-literal-characters)<sub>可选</sub>
>
>
#### hexadecimal-digit {#hexadecimal-digit}
>
> *十六进制数字* → 数值 0 到 9, 字母 a 到 f, 或 A 到 F
>
> *十六进制字符* → [*十六进制数字*](#hexadecimal-digit) | _
>
>
#### hexadecimal-literal-characters {#hexadecimal-literal-characters}
>
> *十六进制字面量字符组* → [*十六进制字符*](#hexadecimal-literal-characters) [*十六进制字面量字符组*](#hexadecimal-literal-characters)<sub>可选</sub>
<a id="decimal-literal"></a>
> *十进制字面量* → [*十进制数字*](#decimal-digit) [*十进制字符组*](#decimal-literal-characters)<sub>可选</sub>
> <a id="decimal-digit"></a>
> *十进制数字* → 数值 0 到 9
> <a id="decimal-digits"></a>
> *十进制数字组* → [*十进制数字*](#decimal-digit) [*十进制数字组*](#decimal-digits)<sub>可选</sub>
> <a id="decimal-literal-character"></a>
> *十进制字符* → [*十进制数字*](#decimal-digit) | _
> <a id="decimal-literal-characters"></a>
> *十进制字符组* → [*十进制字符*](#decimal-literal-character) [*十进制字符组*](#decimal-literal-characters)<sub>可选</sub>
### 浮点数字面量{#floating-point-literal}
<a id="hexadecimal-literal"></a>
> *十六进制字面量* → **0x** [*十六进制数字*](#hexadecimal-digit) [*十六进制字面量字符组*](#hexadecimal-literal-characters)<sub>可选</sub>
> <a id="hexadecimal-digit"></a>
> *十六进制数字* → 数值 0 到 9, 字母 a 到 f, 或 A 到 F
> <a id="hexadecimal-literal-character"></a>
> *十六进制字符* → [*十六进制数字*](#hexadecimal-digit) | _
> <a id="hexadecimal-literal-characters"></a>
> *十六进制字面量字符组* → [*十六进制字符*](#hexadecimal-literal-character) [*十六进制字面量字符组*](#hexadecimal-literal-characters)<sub>可选</sub>
<a id="floating_point_literals"></a>
### 浮点数字面量
*浮点数字面量 (Floating-point literals)* 表示未指定精度浮点数的值。
*浮点数字面量Floating-point literals* 表示未指定精度浮点数的值。
浮点数字面量默认用十进制表示(无前缀),也可以用十六进制表示(加前缀 `0x`)。
十进制浮点数字面量由十进制数字串后跟小数部分或指数部分(或两者皆有)组成。十进制小数部分由小数点 (`.`) 后跟十进制数字串组成。指数部分由大写或小写字母 `e` 为前缀后跟十进制数字串组成,这串数字表示 `e` 之前的数量乘以 10 的几次方。例如:`1.25e2` 表示 1.25 x 10²也就是 `125.0`;同样,`1.25e2` 表示 1.25 x 10¯²也就是 `0.0125`。
十进制浮点数字面量由十进制数字串后跟小数部分或指数部分(或两者皆有)组成。十进制小数部分由小数点`.`后跟十进制数字串组成。指数部分由大写或小写字母 `e` 为前缀后跟十进制数字串组成,这串数字表示 `e` 之前的数量乘以 10 的几次方。例如:`1.25e2` 表示 1.25 x 10²也就是 `125.0`;同样,`1.25e2` 表示 1.25 x 10¯²也就是 `0.0125`。
十六进制浮点数字面量由前缀 `0x` 后跟可选的十六进制小数部分以及十六进制指数部分组成。十六进制小数部分由小数点后跟十六进制数字串组成。指数部分由大写或小写字母 `p` 为前缀后跟十进制数字串组成,这串数字表示 `p` 之前的数量乘以 2 的几次方。例如:`0xFp2` 表示 15 x 2²也就是 `60`;同样,`0xFp-2` 表示 15 x 2¯²也就是 `3.75`。
负数的浮点数字面量由负号 (`-`) 和浮点数字面量组成,例如 `-42.5`。
负数的浮点数字面量由负号`-`和浮点数字面量组成,例如 `-42.5`。
浮点数字面量允许使用下划线 (`_`) 来增强数字的可读性,下划线会被系统忽略,因此不会影响字面量的值。同样地,也可以在数字前加 `0`,并不会影响字面量的值。
浮点数字面量允许使用下划线`_`来增强数字的可读性,下划线会被系统忽略,因此不会影响字面量的值。同样地,也可以在数字前加 `0`,并不会影响字面量的值。
除非特别指定,浮点数字面量的默认推导类型为 Swift 标准库类型中的 `Double`,表示 64 位浮点数。Swift 标准库也定义了 `Float` 类型,表示 32 位浮点数。
> 浮点数字面量语法
<a id="floating-point-literal"></a>
> *浮点数字面量* → [*十进制字面量*](#decimal-literal) [*十进制分数*](#decimal-fraction)<sub>可选</sub> [*十进制指数*](#decimal-exponent)<sub>可选</sub>
> 浮点数字面量语法
>
>
#### floating-point-literal {#floating-point-literal}
>
> *浮点数字面量* → [*十进制字面量*](#decimal-literal) [*十进制分数*](#decimal-fraction)<sub>可选</sub> [*十进制指数*](#decimal-exponent)<sub>可选</sub>
>
> *浮点数字面量* → [*十六进制字面量*](#hexadecimal-literal) [*十六进制分数*](#hexadecimal-fraction)<sub>可选</sub> [*十六进制指数*](#hexadecimal-exponent)
>
>
#### decimal-fraction {#decimal-fraction}
>
> *十进制分数* → **.** [*十进制字面量*](#decimal-literal)
>
>
#### decimal-exponent {#decimal-exponent}
>
> *十进制指数* → [*十进制指数 e*](#floating-point-e) [*正负号*](#sign)<sub>可选</sub> [*十进制字面量*](#decimal-literal)
>
>
#### hexadecimal-fraction {#hexadecimal-fraction}
>
> *十六进制分数* → **.** [*十六进制数字*](#hexadecimal-digit) [*十六进制字面量字符组*](#hexadecimal-literal-characters)<sub>可选</sub>
>
>
#### hexadecimal-exponent {#hexadecimal-exponent}
>
> *十六进制指数* → [*十六进制指数 p*](#floating-point-p) [*正负号*](#sign)<sub>可选</sub> [*十进制字面量*](#decimal-literal)
>
>
#### floating-point-e {#floating-point-e}
>
> *十进制指数 e* → **e** | **E**
>
>
#### floating-point-p {#floating-point-p}
>
> *十六进制指数 p* → **p** | **P**
>
>
#### sign {#sign}
>
> *正负号* → **+** | **-**
<a id="decimal-fraction"></a>
> *十进制分数* → **.** [*十进制字面量*](#decimal-literal)
> <a id="decimal-exponent"></a>
> *十进制指数* → [*十进制指数 e*](#floating-point-e) [*正负号*](#sign)<sub>可选</sub> [*十进制字面量*](#decimal-literal)
### 字符串字面量 {#string-literal}
<a id="hexadecimal-fraction"></a>
> *十六进制分数* → **.** [*十六进制数字*](#hexadecimal-digit) [*十六进制字面量字符组*](#hexadecimal-literal-characters)<sub>可选</sub>
> <a id="hexadecimal-exponent"></a>
> *十六进制指数* → [*十六进制指数 p*](#floating-point-p) [*正负号*](#sign)<sub>可选</sub> [*十进制字面量*](#decimal-literal)
<a id="floating-point-e"></a>
> *十进制指数 e* → **e** | **E**
> <a id="floating-point-p"></a>
> *十六进制指数 p* → **p** | **P**
> <a id="sign"></a>
> *正负号* → **+** | **-**
<a id="string_literals"></a>
### 字符串字面量
字符串字面量由被包在双引号中的一串字符组成,形式如下:
字符串字面量是被引号包括的一串字符组成。 单行字符串字面量被包在双引号中的一串字符组成,形式如下:
> "`字符`"
字符串字面量中不能包含未转义的双引号(`"`)、未转义的反斜线(`\`)、回车符、换行符。
多行字符串字面量被包在三个双引号中的一串字符组成,形式如下:
> """
> `字符`
> """
与单行字符串字面量不同的是,多行字符串字面量可以包含不转义的双引号("),回车以及换行。它不能包含三个未转义的连续双引号。
""" 之后的回车或者换行开始多行字符串字面量,不是字符串的一部分。 """ 之前回车或者换行结束字面量,也不是字符串的一部分。要让多行字符串字面量的开始或结束带有换行,就在第一行或者最后一行写一个空行。
多行字符串字面量可以使用任何空格或制表符组合进行缩进;这些缩进不会包含在字符串中。 """ 的结束符号决定了缩进:字面量中的任何一个非空行必须起始于多行字符串字面量结束符号的前面;空格和制表符不会被转换。你可以包在缩进后含额外的空格和制表符;这些空格和制表符会在字符串中出现。
多行字符串字面量中的一行结束使用规范化的换行符号。尽管你的源代码混用了回车和换行符,字符串中所有的行结束都必须一样.
在多行字符串字面量里, 在行末用反斜线(`\`)可以省略字符串行间中断。 反斜线之间的空白和行间中断也可以省略。 你可以在你的代码里用这种语法硬包裹多行字符串字面量,不需要改变产生的字符串的值。
可以在字符串字面量中使用的转义特殊符号如下:
* 空字符 `\0`
@ -253,7 +366,7 @@ true // 布尔值字面量
* 单引号 `\'`
* Unicode 标量 `\u{`n`}`n 为一到八位的十六进制数字
字符串字面量允许在反斜杠 (`\`) 后的括号 `()` 中插入表达式的值。插入表达式可以包含字符串字面量,但不能包含未转义的双引号 (`"`)、未转义的反斜线 (`\`)、回车符以及换行符。
字符串字面量允许在反斜杠`\`后的括号 `()` 中插入表达式的值。插入表达式可以包含字符串字面量,但不能包含未转义的反斜线`\`、回车符以及换行符。
例如,以下所有字符串字面量的值都是相同的:
@ -265,7 +378,41 @@ true // 布尔值字面量
let x = 3; "1 2 \(x)"
```
字符串字面量的默认推导类型为 `String`。更多有关 `String` 类型的信息请参考 [字符串和字符](../chapter2/03_Strings_and_Characters.html) 以及 [*String Structure Reference*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_String_Structure/index.html#//apple_ref/doc/uid/TP40015181)。
可以使用一对或多对扩展分隔符(#)包裹字符串进行分隔,被分隔的字符串的形式如下所示:
> \#"`characters`"#
>
> \#"""
>
> `characters`
>
> """#
特殊字符在被分隔符分隔的结果字符串中会展示为普通字符,而不是特殊字符。你可以使用扩展分隔符来创建一些具有特殊效果的字符串。例如,生成字符串插值,启动或终止转义序列(字符串)。
以下所示,由字符串字面量和扩展分隔符所创建的字符串是等价的:
```swift
let string = #"\(x) \ " \u{2603}"#
let escaped = "\\(x) \\ \" \\u{2603}"
print(string)
// Prints "\(x) \ " \u{2603}"
print(string == escaped)
// Prints "true"
```
如果在一个字符串中使用多对扩展分隔符,请不要在分隔符之间使用空格。
```swift
print(###"Line 1\###nLine 2"###) // OK
print(# # #"Line 1\# # #nLine 2"# # #) // Error
```
使用扩展分隔符创建的多行字符串字面量与普通多行字符串字面量具有相同的缩进要求。
字符串字面量的默认推导类型为 `String`。更多有关 `String` 类型的信息请参考 [字符串和字符](../chapter2/03_Strings_and_Characters.md) 以及 [*字符串结构参考*](https://developer.apple.com/documentation/swift/string)。
`` 操作符连接的字符型字面量是在编译时进行连接的。比如下面的 `textA``textB` 是完全一样的,`textA` 没有任何运行时的连接操作。
@ -274,50 +421,121 @@ let textA = "Hello " + "world"
let textB = "Hello world"
```
> 字符串字面量语法
<a id="string-literal"></a>
> *字符串字面量* → [*静态字符串字面量*](#static-string-literal) | [*插值字符串字面量*](#interpolated-string-literal)
<a id="static-string-literal"></a>
> *静态字符串字面量* → **"**[*引用文本*](#quoted-text)<sub>可选</sub>**"**
> <a id="quoted-text"></a>
> *引用文本* → [*引用文本项*](#quoted-text-item) [*引用文本*](#quoted-text)<sub>可选</sub>
> <a id="quoted-text-item"></a>
> *引用文本项* → [*转义字符*](#escaped-character)
> 字符串字面量语法
>
> *字符串字面量* → [*静态字符串字面量*](#static-string-literal) | [*插值字符串字面量*](#interpolated-string-literal)
>
> *字符串开分隔定界符* → [*字符串扩展分隔符*](#extended-string-literal-delimiter) **"**
>
> *字符串闭分隔定界符* → **"** [*字符串扩展分隔符*](#extended-string-literal-delimiter)<sub>可选</sub>
>
>
#### static-string-literal {#static-string-literal}
>
> *静态字符串字面量* → [*字符串开分隔定界符*](#extended-string-literal-delimiter) [*引用文本*](#quoted-text)<sub>可选</sub> [*字符串闭分隔定界符*](#extended-string-literal-delimiter)
>
> *静态字符串字面量* → [*多行字符串开分隔定界符*](#extended-string-literal-delimiter) [*多行引用文本*](#multiline-quoted-text)<sub>可选</sub> [*多行字符串闭分隔定界符*](#extended-string-literal-delimiter)
>
> *多行字符串开分隔定界符* → [*字符串扩展分隔符*](#extended-string-literal-delimiter) **"""**
>
> *多行字符串闭分隔定界符* → **"""** [*字符串扩展分隔符*](#extended-string-literal-delimiter)
>
>
#### extended-string-literal-delimiter {#extended-string-literal-delimiter}
>
> *字符串扩展分隔符* → **#** [*字符串扩展分隔符*](#extended-string-literal-delimiter)<sub>可选</sub>
>
>
#### quoted-text {#quoted-text}
>
> *引用文本* → [*引用文本项*](#quoted-text-item) [*引用文本*](#quoted-text)<sub>可选</sub>
>
>
#### quoted-text-item {#quoted-text-item}
>
> *引用文本项* → [*转义字符*](#escaped-character)
>
> *引用文本项* → 除了 **"**、**\\**、U+000A、U+000D 以外的所有 Unicode 字符
<a id="interpolated-string-literal"></a>
> *插值字符串字面量* → **"**[*插值文本*](#interpolated-text)<sub>可选</sub>**"**
> <a id="interpolated-text"></a>
> *插值文本* → [*插值文本项*](#interpolated-text-item) [*插值文本*](#interpolated-text)<sub>可选</sub>
> <a id="interpolated-text-item"></a>
> *插值文本项* → **\\****(**[*表达式*](./04_Expressions.html)**)** | [*引用文本项*](#quoted-text-item)
<a id="escaped-character"></a>
> *转义字符* → **\\****0** | **\\****\\** | **\t** | **\n** | **\r** | **\\"** | **\\'**
> *转义字符* → **\u {** [*unicode 标量数字*](#unicode-scalar-digits) **}**
> <a id="unicode-scalar-digits"></a>
>
>
#### multiline-quoted-text {#multiline-quoted-text}
>
> *多行引用文本* → [*多行引用文本项*](#multiline-quoted-text-item) [*多行引用文本*](#multiline-quoted-text)<sub>可选</sub>
>
>
#### multiline-quoted-text-item {#multiline-quoted-text-item}
>
> *多行引用文本项* [*转义字符*](#escaped-character)<sub>可选</sub>
>
>
#### multiline-quoted-text {#multiline-quoted-text}
>
> *多行引用文本* → 除了 **\** 以外的任何Unicode标量值
>
> *多行引用文本* → [*转义换行*](#escaped-newline)
>
>
#### interpolated-string-literal {#interpolated-string-literal}
>
> *插值字符串字面量* → [*字符串开分隔定界符*](#extended-string-literal-delimiter) [*插值文本*](#interpolated-text)<sub>可选</sub> [*字符串闭分隔定界符*](#extended-string-literal-delimiter)
>
> *插值字符串字面量* → [*多行字符串开分隔定界符*](#extended-string-literal-delimiter) [*插值文本*](#interpolated-text)<sub>可选</sub> [*多行字符串闭分隔定界符*](#extended-string-literal-delimiter)
>
>
#### interpolated-text {#interpolated-text}
>
> *插值文本* → [*插值文本项*](#interpolated-text-item) [*插值文本*](#interpolated-text)<sub>可选</sub>
>
>
#### interpolated-text-item {#interpolated-text-item}
>
> *插值文本项* → **\\****(**[*表达式*](./04_Expressions.md)**)** | [*引用文本项*](#quoted-text-item)
>
> *多行插值文本* → [*多行插值文本项*](#multiline-quoted-text-item) [*多行插值文本*](#multiline-quoted-text)<sub>可选</sub>
>
> *多行插值文本项* → **\\(** [表达式](./04_Expressions.md) **)** | [多行引用文本项](#multiline-quoted-text-item)
>
>
#### escape-sequence {#escape-sequence}
>
> *转义序列* → **\\** [字符串扩展分隔符](#extended-string-literal-delimiter)
>
>
#### escaped-character {#escaped-character}
>
> *转义字符* → [*转义序列*](#escape-sequence) **0** | [*转义序列*](#escape-sequence) **\\** | [*转义序列*](#escape-sequence) **t** | [*转义序列*](#escape-sequence) **n** | [*转义序列*](#escape-sequence) **r** | [*转义序列*](#escape-sequence) **\"** | [*转义序列*](#escape-sequence) **'**
>
> *转义字符* → [*转义序列*](#escape-sequence) **u {** [*unicode 标量数字*](#unicode-scalar-digits) **}**
>
>
#### unicode-scalar-digits {#unicode-scalar-digits}
>
> *unicode 标量数字* → 一到八位的十六进制数字
>
>
#### escaped-newline {#escaped-newline}
>
> *转义换行符* → [*转义序列*](#escape-sequence) [*空白*](#whitespace)<sub>可选</sub> [*断行符*](#line-break)
<a id="operators"></a>
## 运算符
Swift 标准库定义了许多可供使用的运算符,其中大部分在 [基础运算符](../chapter2/02_Basic_Operators.html) 和 [高级运算符](../chapter2/25_Advanced_Operators.html) 中进行了阐述。这一小节将描述哪些字符能用于自定义运算符。
## 运算符 {#operator}
自定义运算符可以由以下其中之一的 ASCII 字符 `/``=``-``+``!``*``%``<``>``&``|``^``?` 以及 `~`,或者后面语法中规定的任一个 Unicode 字符(其中包含了*数学运算符*、*零散符号(Miscellaneous Symbols)* 以及印刷符号 (Dingbats) 之类的 Unicode 块)开始。在第一个字符之后,允许使用组合型 Unicode 字符。
Swift 标准库定义了许多可供使用的运算符,其中大部分在 [基础运算符](../chapter2/02_Basic_Operators.md) 和 [高级运算符](../chapter2/26_Advanced_Operators.md) 中进行了阐述。这一小节将描述哪些字符能用于自定义运算符。
您也可以以点号 (`.`) 开头来定义自定义运算符。这些运算符可以包含额外的点,例如 `.+.`。如果某个运算符不是以点号开头的,那么它就无法再包含另外的点号了。例如,`+.+` 就会被看作为一个 `+` 运算符后面跟着一个 `.+` 运算符。
自定义运算符可以由以下其中之一的 ASCII 字符 `/``=``-``+``!``*``%``<``>``&``|``^``?` 以及 `~`,或者后面语法中规定的任一个 Unicode 字符(其中包含了*数学运算符*、*零散符号Miscellaneous Symbols* 以及印刷符号Dingbats之类的 Unicode 块)开始。在第一个字符之后,允许使用组合型 Unicode 字符。
您也可以以点号(`.`)开头来定义自定义运算符。这些运算符可以包含额外的点,例如 `.+.`。如果某个运算符不是以点号开头的,那么它就无法再包含另外的点号了。例如,`+.+` 就会被看作为一个 `+` 运算符后面跟着一个 `.+` 运算符。
虽然您可以用问号 `?` 来自定义运算符,但是这个运算符不能只包含单独的一个问号。此外,虽然运算符可以包含一个惊叹号 `!`,但是前缀运算符不能够以问号或者惊叹号开头。
> 注意
> 注意
>
> 以下这些标记 `=`、`->`、`//`、`/*`、`*/`、`.`、`<`(前缀运算符)、`&`、`?`、`?`(中缀运算符)、`>`(后缀运算符)、`!` 、`?` 是被系统保留的。这些符号不能被重载,也不能用于自定义运算符。
运算符两侧的空白被用来区分该运算符是否为前缀运算符、后缀运算符或二元运算符。规则总结如下:
* 如果运算符两侧都有空白或两侧都无空白,将被看作二元运算符。例如:`a+++b``a +++ b` 当中的 `+++` 运算符会被看作二元运算符。
* 如果运算符只有左侧空白,将被看作一元前缀运算符。例如 `a +++b` 中的 `+++` 运算符会被看做是一元前缀运算符。
* 如果运算符只有左侧空白,将被看作一元前缀运算符。例如 `a +++b` 中的 `+++` 运算符会被看做是一元前缀运算符。
* 如果运算符只有右侧空白,将被看作一元后缀运算符。例如 `a+++ b` 中的 `+++` 运算符会被看作是一元后缀运算符。
* 如果运算符左侧没有空白并紧跟 `.`,将被看作一元后缀运算符。例如 `a+++.b` 中的 `+++` 运算符会被视为一元后缀运算符(即上式被视为 `a+++ .b` 而不是 `a +++ .b`)。
@ -327,52 +545,86 @@ Swift 标准库定义了许多可供使用的运算符,其中大部分在 [基
在某些特定的设计中 ,以 `<``>` 开头的运算符会被分离成两个或多个符号,剩余部分可能会以同样的方式被再次分离。因此,在 `Dictionary<String, Array<Int>>` 中没有必要添加空白来消除闭合字符 `>` 的歧义。在这个例子中, 闭合字符 `>` 不会被视为单独的符号,因而不会被错误解析为 `>>` 运算符。
要学习如何自定义运算符,请参考 [自定义运算符](../chapter2/25_Advanced_Operators.html#custom_operators) 和 [运算符声明](05_Declarations.html#operator_declaration)。要学习如何重载运算符,请参考 [运算符函数](../chapter2/25_Advanced_Operators.html#operator_functions)。
要学习如何自定义运算符,请参考 [自定义运算符](../chapter2/26_Advanced_Operators.md#custom_operators) 和 [运算符声明](./06_Declarations.md#operator_declaration)。要学习如何重载运算符,请参考 [运算符函数](../chapter2/26_Advanced_Operators.md#operator_functions)。
> 运算符语法
<a id="operator"></a>
> *运算符* → [*头部运算符*](#operator-head) [*运算符字符组*](#operator-characters)<sub>可选</sub>
> *运算符* → [*头部点运算符*](#dot-operator-head) [*点运算符字符组*](#dot-operator-characters)<sub>可选</sub>
<a id="operator-head"></a>
> *头部运算符* → **/** | **=** | **-** | **+** | **!** | __*__ | **%** | **<** | **>** | **&** | **|** | **^** | **~** | **?**
> *头部运算符* → U+00A1U+00A7
> *头部运算符* → U+00A9 或 U+00AB
> *头部运算符* → U+00AC 或 U+00AE
> *头部运算符* → U+00B0U+00B1U+00B6U+00BBU+00BFU+00D7或 U+00F7
> *头部运算符* → U+2016U+2017 或 U+2020U+2027
> *头部运算符* → U+2030U+203E
> *头部运算符* → U+2041U+2053
> *头部运算符* → U+2055U+205E
> *头部运算符* → U+2190U+23FF
> *头部运算符* → U+2500U+2775
> *头部运算符* → U+2794U+2BFF
> *头部运算符* → U+2E00U+2E7F
> *头部运算符* → U+3001U+3003
> *头部运算符* → U+3008U+3030
<a id="operator-character"></a>
> *运算符字符* → [*头部运算符*](#operator-head)
> *运算符字符* → U+0300U+036F
> *运算符字符* → U+1DC0U+1DFF
> *运算符字符* → U+20D0U+20FF
> *运算符字符* → U+FE00U+FE0F
> *运算符字符* → U+FE20U+FE2F
> *运算符字符* → U+E0100U+E01EF
> <a id="operator-characters"></a>
> *运算符字符组* → [*运算符字符*](#operator-character) [*运算符字符组*](#operator-characters)<sub>可选</sub>
<a id="dot-operator-head"></a>
> *头部运算符* → **..**
> <a id="dot-operator-character"></a>
> *点运算符字符* → **.** | [*运算符字符*](#operator-character)
> <a id="dot-operator-characters"></a>
> 运算符语法
>
> *运算符* → [*头部运算符*](#operator-head) [*运算符字符组*](#operator-characters)<sub>可选</sub>
>
> *运算符* → [*头部点运算符*](#dot-operator-head) [*点运算符字符组*](#dot-operator-characters)
>
>
#### operator-head {#operator-head}
>
> *头部运算符* → **/** | **=** | **-** | **+** | **!** | __*__ | **%** | **<** | **>** | **&** | **|** | **^** | **~** | **?**
>
> *头部运算符* → U+00A1U+00A7
>
> *头部运算符* → U+00A9 或 U+00AB
>
> *头部运算符* → U+00AC 或 U+00AE
>
> *头部运算符* → U+00B0U+00B1U+00B6U+00BBU+00BFU+00D7或 U+00F7
>
> *头部运算符* → U+2016U+2017 或 U+2020U+2027
>
> *头部运算符* → U+2030U+203E
>
> *头部运算符* → U+2041U+2053
>
> *头部运算符* → U+2055U+205E
>
> *头部运算符* → U+2190U+23FF
>
> *头部运算符* → U+2500U+2775
>
> *头部运算符* → U+2794U+2BFF
>
> *头部运算符* → U+2E00U+2E7F
>
> *头部运算符* → U+3001U+3003
>
> *头部运算符* → U+3008U+3030
>
>
#### operator-character {#operator-character}
>
> *运算符字符* → [*头部运算符*](#operator-head)
>
> *运算符字符* → U+0300U+036F
>
> *运算符字符* → U+1DC0U+1DFF
>
> *运算符字符* → U+20D0U+20FF
>
> *运算符字符* → U+FE00U+FE0F
>
> *运算符字符* → U+FE20U+FE2F
>
> *运算符字符* → U+E0100U+E01EF
>
>
#### operator-characters {#operator-characters}
>
> *运算符字符组* → [*运算符字符*](#operator-character) [*运算符字符组*](#operator-characters)<sub>可选</sub>
>
>
#### dot-operator-head {#dot-operator-head}
>
> *头部点运算符* → **..**
>
>
#### dot-operator-character {#dot-operator-character}
>
> *点运算符字符* → **.** | [*运算符字符*](#operator-character)
>
>
#### dot-operator-characters {#dot-operator-characters}
>
> *点运算符字符组* → [*点运算符字符*](#dot-operator-character) [*点运算符字符组*](#dot-operator-characters)<sub>可选</sub>
<a id="binary-operator"></a>
> *二元运算符* → [*运算符*](#operator)
> <a id="prefix-operator"></a>
> *前缀运算符* → [*运算符*](#operator)
> <a id="postfix-operator"></a>
> *后缀运算符* → [*运算符*](#operator)
>
> *二元运算符* → [*运算符*](#operator)
>
> *前缀运算符* → [*运算符*](#operator)
>
> *后缀运算符* → [*运算符*](#operator)

View File

@ -1,400 +1,526 @@
# 类型Types
-----------------
> 1.0
> 翻译:[lyuka](https://github.com/lyuka)
> 校对:[numbbbbb](https://github.com/numbbbbb), [stanzhai](https://github.com/stanzhai)
> 2.0
> 翻译+校对:[EudeMorgen](https://github.com/EudeMorgen)
> 2.1
> 翻译:[mmoaay](https://github.com/mmoaay)
本页包含内容:
- [类型注解](#type_annotation)
- [类型标识符](#type_identifier)
- [元组类型](#tuple_type)
- [函数类型](#function_type)
- [数组类型](#array_type)
- [字典类型](#dictionary_type)
- [可选类型](#optional_type)
- [隐式解析可选类型](#implicitly_unwrapped_optional_type)
- [协议合成类型](#protocol_composition_type)
- [类型](#metatype_type)
- [类型继承子句](#type_inheritance_clause)
- [类型推断](#type_inference)
Swift 语言存在两种类型:命名型类型和复合型类型。命名型类型是指定义时可以给定名字的类型。命名型类型包括类、结构体、枚举和协议。比如,一个用户定义的类 `MyClass` 的实例拥有类型 `MyClass`。除了用户定义的命名型类型Swift 标准库也定义了很多常用的命名型类型,包括那些表示数组、字典和可选值的类型。
那些通常被其它语言认为是基本或原始的数据型类型,比如表示数字、字符和字符串的类型,实际上就是命名型类型,这些类型在 Swift 标准库中是使用结构体来定义和实现的。因为它们是命名型类型,因此你可以按照 [扩展](../chapter2/21_Extensions.html) 和 [扩展声明](05_Declarations.html#extension_declaration) 中讨论的那样,声明一个扩展来增加它们的行为以满足你程序的需求。
复合型类型是没有名字的类型,它由 Swift 本身定义。Swift 存在两种复合型类型:函数类型和元组类型。一个复合型类型可以包含命名型类型和其它复合型类型。例如,元组类型 `(Int, (Int, Int))` 包含两个元素:第一个是命名型类型 `Int`,第二个是另一个复合型类型 `(Int, Int)`
本节讨论 Swift 语言本身定义的类型,并描述 Swift 中的类型推断行为。
> 类型语法
<a name="type"></a>
> *类型* → [*数组类型*](#array-type) | [*字典类型*](#dictionary-type) | [*函数类型*](#function-type) | [*类型标识*](#type-identifier) | [*元组类型*](#tuple-type) | [*可选类型*](#optional-type) | [*隐式解析可选类型*](#implicitly-unwrapped-optional-type) | [*协议合成类型*](#protocol-composition-type) | [*元型类型*](#metatype-type) | **任意类型** | **自身类型**
<a name="type_annotation"></a>
## 类型注解
类型注解显式地指定一个变量或表达式的。类型注解始于冒号 `:` 终于类型,比如下面两个例子:
```swift
let someTuple: (Double, Double) = (3.14159, 2.71828)
func someFunction(a: Int) { /* ... */ }
```
在第一个例子中,表达式 `someTuple` 的类型被指定为 `(Double, Double)`。在第二个例子中,函数 `someFunction` 的参数 `a` 的类型被指定为 `Int`
类型注解可以在类型之前包含一个类型特性的可选列表。
> 类型注解语法
<a name="type-annotation"></a>
> *类型注解* → **:** [*特性列表*](06_Attributes.html#attributes)<sub>可选</sub> **输入输出参数**<sub>可选</sub> [*类型*](#type)
<a name="type_identifier"></a>
## 类型标识符
类型标识符引用命名型类型,还可引用命名型或复合型类型的别名。
大多数情况下,类型标识符引用的是与之同名的命名型类型。例如类型标识符 `Int` 引用命名型类型 `Int`,同样,类型标识符 `Dictionary<String, Int>` 引用命名型类型 `Dictionary<String, Int>`
在两种情况下类型标识符不引用同名的类型。情况一,类型标识符引用的是命名型或复合型类型的类型别名。比如,在下面的例子中,类型标识符使用 `Point` 来引用元组 `(Int, Int)`
```swift
typealias Point = (Int, Int)
let origin: Point = (0, 0)
```
情况二,类型标识符使用点语法(`.`)来表示在其它模块或其它类型嵌套内声明的命名型类型。例如,下面例子中的类型标识符引用在 `ExampleModule` 模块中声明的命名型类型 `MyType`
```swift
var someValue: ExampleModule.MyType
```
> 类型标识符语法
<a name="type-identifier"></a>
> *类型标识符* → [*类型名称*](#type-name) [*泛型参数子句*](08_Generic_Parameters_and_Arguments.html#generic_argument_clause)<sub>可选</sub> | [*类型名称*](#type-name) [*泛型参数子句*](08_Generic_Parameters_and_Arguments.html#generic_argument_clause)<sub>可选</sub> **.** [*类型标识符*](#type-identifier)
<a name="type-name"></a>
> *类型名称* → [*标识符*](02_Lexical_Structure.html#identifier)
<a name="tuple_type"></a>
## 元组类型
元组类型是使用括号括起来的零个或多个类型,类型间用逗号隔开。
你可以使用元组类型作为一个函数的返回类型,这样就可以使函数返回多个值。你也可以命名元组类型中的元素,然后用这些名字来引用每个元素的值。元素的名字由一个标识符紧跟一个冒号 `(:)` 组成。[函数和多返回值](../chapter2/06_Functions.html#functions_with_multiple_return_values) 章节里有一个展示上述特性的例子。
# 类型Types
Swift 语言存在两种类型:命名型类型和复合型类型。*命名型类型*是指定义时可以给定名字的类型。命名型类型包括类、结构体、枚举和协议。比如,一个用户定义类 `MyClass` 的实例拥有类型 `MyClass`。除了用户定义的命名型类型Swift 标准库也定义了很多常用的命名型类型,包括那些表示数组、字典和可选值的类型。
那些通常被其它语言认为是基本或原始的数据型类型,比如表示数字、字符和字符串的类型,实际上就是命名型类型,这些类型在 Swift 标准库中是使用结构体来定义和实现的。因为它们是命名型类型,因此你可以按照 [扩展](../chapter2/20_Extensions.md) 和 [扩展声明](./06_Declarations.md#extension_declaration) 中讨论的那样,声明一个扩展来增加它们的行为以满足你程序的需求。
*复合型类型*是没有名字的类型,它由 Swift 本身定义。Swift 存在两种复合型类型:函数类型和元组类型。一个复合型类型可以包含命名型类型和其它复合型类型。例如,元组类型 `(Int, (Int, Int))` 包含两个元素:第一个是命名型类型 `Int`,第二个是另一个复合型类型 `(Int, Int)`
你可以在命名型类型和复合型类型使用小括号。但是在类型旁加小括号没有任何作用。举个例子,`(Int)` 等同于 `Int`
本节讨论 Swift 语言本身定义的类型,并描述 Swift 中的类型推断行为。
#### type {#type}
> 类型语法
>
> *类型* → [函数类型](#function-type)
>
> *类型* → [数组类型](#array-type)
>
> *类型* → [字典类型](#dictionary-type)
>
> *类型* → [类型标识](#type-identifier)
>
> *类型* → [元组类型](#tuple-type)
>
> *类型* → [可选类型](#optional-type)
>
> *类型* → [隐式解析可选类型](#implicitly-unwrapped-optional-type)
>
> *类型* → [协议合成类型](#protocol-composition-type)
>
> *类型* →[不透明类型](#opaque-type)
>
> *类型* → [元型类型](#metatype-type)
>
> *类型* → [自身类型](#self-type)
>
> *类型* → **Any**
>
> *类型* → **** [类型](#type) ****
## 类型注解 {#type-annotation-h}
*类型注解*显式地指定一个变量或表达式的类型。类型注解始于冒号 `:` 终于类型,比如下面两个例子:
```swift
let someTuple: (Double, Double) = (3.14159, 2.71828)
func someFunction(a: Int) { /* ... */ }
```
在第一个例子中,表达式 `someTuple` 的类型被指定为 `(Double, Double)`。在第二个例子中,函数 `someFunction` 的参数 `a` 的类型被指定为 `Int`
类型注解可以在类型之前包含一个类型特性的可选列表。
> 类型注解语法
>
#### type-annotation {#type-annotation}
> *类型注解* → **:** [*特性列表*](./07_Attributes.md#attributes)<sub>可选</sub> **输入输出参数**<sub>可选</sub> [*类型*](#type)
## 类型标识符 {#type-identifier-h}
*类型标识符*引用命名型类型,还可引用命名型或复合型类型的别名。
大多数情况下,类型标识符引用的是与之同名的命名型类型。例如类型标识符 `Int` 引用命名型类型 `Int`,同样,类型标识符 `Dictionary<String, Int>` 引用命名型类型 `Dictionary<String, Int>`
在两种情况下类型标识符不引用同名的类型。情况一,类型标识符引用的是命名型或复合型类型的类型别名。比如,在下面的例子中,类型标识符使用 `Point` 来引用元组 `(Int, Int)`
```swift
typealias Point = (Int, Int)
let origin: Point = (0, 0)
```
情况二,类型标识符使用点语法(`.`)来表示在其它模块或其它类型嵌套内声明的命名型类型。例如,下面例子中的类型标识符引用在 `ExampleModule` 模块中声明的命名型类型 `MyType`
```swift
var someValue: ExampleModule.MyType
```
> 类型标识符语法
>
#### type-identifier {#type-identifier}
> *类型标识符* → [*类型名称*](#type-name) [*泛型参数子句*](./09_Generic_Parameters_and_Arguments.md#generic_argument_clause)<sub>可选</sub> | [*类型名称*](#type-name) [*泛型参数子句*](./09_Generic_Parameters_and_Arguments.md#generic_argument_clause)<sub>可选</sub> **.** [*类型标识符*](#type-identifier)
>
#### type-name {#type-name}
> *类型名称* → [*标识符*](./02_Lexical_Structure.md#identifier)
## 元组类型 {#tuple-type-h}
*元组类型*是使用括号括起来的零个或多个类型,类型间用逗号隔开。
你可以使用元组类型作为一个函数的返回类型,这样就可以使函数返回多个值。你也可以命名元组类型中的元素,然后用这些名字来引用每个元素的值。元素的名字由一个标识符紧跟一个冒号 `(:)` 组成。[函数和多返回值](../chapter2/06_Functions.md#functions_with_multiple_return_values) 章节里有一个展示上述特性的例子。
当一个元组类型的元素有名字的时候,这个名字就是类型的一部分。
```swift
```swift
var someTuple = (top: 10, bottom: 12) // someTuple 的类型为 (top: Int, bottom: Int)
someTuple = (top: 4, bottom: 42) // 正确:命名类型匹配
someTuple = (9, 99) // 正确:命名类型被自动推断
someTuple = (left: 5, right: 5) // 错误:命名类型不匹配
someTuple = (left: 5, right: 5) // 错误:命名类型不匹配
```
`Void` 是空元组类型 `()` 的别名。如果括号内只有一个元素,那么该类型就是括号内元素的类型。比如,`(Int)` 的类型是 `Int` 而不是 `(Int)`。所以,只有当元组类型包含的元素个数在两个及以上时才可以命名元组元素。
> 元组类型语法
<a name="tuple-type"></a>
> *元组类型* → **(** [*元组类型元素列表*](#tuple-type-element-list) <sub>可选</sub> **)**
<a name="tuple-type-element-list"></a>
> *元组类型元素列表* → [*元组类型元素*](#tuple-type-element) | [*元组类型元素*](#tuple-type-element) **,** [*元组类型元素列表*](#tuple-type-element-list)
<a name="tuple-type-element"></a>
> *元组类型元素* → [*元素名*](#element-name) [*类型注解*](#type-annotation) | [*类型*](#type)
<a name="element-name"></a>
> *元素名* → [*标识符*](02_Lexical_Structure.html#identifier)
<a name="function_type"></a>
## 函数类型
函数类型表示一个函数、方法或闭包的类型,它由参数类型和返回值类型组成,中间用箭头(`->`)隔开:
> `参数类型` -> `返回值类型`
参数类型是由逗号间隔的类型列表。由于参数类型和返回值类型可以是元组类型,所以函数类型支持多参数与多返回值的函数与方法。
你可以对函数参数使用 `autoclosure` 特性。这会自动将参数表达式转化为闭包,表达式的结果即闭包返回值。这从语法结构上提供了一种便捷:延迟对表达式的求值,直到其值在函数体中被使用。以自动闭包做为参数的函数类型的例子详见 [自动闭包](../chapter2/07_Closures.html#autoclosures) 。
函数类型可以拥有一个可变长参数作为参数类型中的最后一个参数。从语法角度上讲,可变长参数由一个基础类型名字紧随三个点(`...`)组成,如 `Int...`。可变长参数被认为是一个包含了基础类型元素的数组。即 `Int...` 就是 `[Int]`。关于使用可变长参数的例子,请参阅 [可变参数](../chapter2/06_Functions.html#variadic_parameters)。
为了指定一个 `in-out` 参数,可以在参数类型前加 `inout` 前缀。但是你不可以对可变长参数或返回值类型使用 `inout`。关于这种参数的详细讲解请参阅 [输入输出参数](../chapter2/06_Functions.html#in_out_parameters)。
函数和方法中的参数名并不是函数类型的一部分。例如:
所有的元组类型都包含两个及以上元素, 除了 `Void``Void` 是空元组类型 `()` 的别名。
> 元组类型语法
>
#### tuple-type {#tuple-type}
> *元组类型* → **(** **)** | **(** [*元组类型元素*](#tuple-type-element) **,** [*元组类型元素列表*](#tuple-type-element-list) **)**
>
#### tuple-type-element-list {#tuple-type-element-list}
> *元组类型元素列表* → [*元组类型元素*](#tuple-type-element) | [*元组类型元素*](#tuple-type-element) **,** [*元组类型元素列表*](#tuple-type-element-list)
>
#### tuple-type-element {#tuple-type-element}
> *元组类型元素* → [*元素名*](#element-name) [*类型注解*](#type-annotation) | [*类型*](#type)
>
#### element-name {#element-name}
> *元素名* → [*标识符*](./02_Lexical_Structure.md#identifier)
>
## 函数类型 {#function-type-h}
*函数类型*表示一个函数、方法或闭包的类型,它由参数类型和返回值类型组成,中间用箭头(`->`)隔开:
> `参数类型`->`返回值类型`
*参数类型*是由逗号间隔的类型列表。由于*返回值类型*可以是元组类型,所以函数类型支持多返回值的函数与方法。
你可以对参数类型为 `() -> T`(其中 T 是任何类型)的函数使用 `autoclosure` 特性。这会自动将参数表达式转化为闭包,表达式的结果即闭包返回值。这从语法结构上提供了一种便捷:延迟对表达式的求值,直到其值在函数体中被调用。以自动闭包做为参数的函数类型的例子详见 [自动闭包](../chapter2/07_Closures.md#autoclosures)。
函数类型可以拥有一个可变长参数作为*参数类型*中的最后一个参数。从语法角度上讲,可变长参数由一个基础类型名字紧随三个点(`...`)组成,如 `Int...`。可变长参数被认为是一个包含了基础类型元素的数组。即 `Int...` 就是 `[Int]`。关于使用可变长参数的例子,请参阅 [可变参数](../chapter2/06_Functions.md#variadic_parameters)。
为了指定一个 `in-out` 参数,可以在参数类型前加 `inout` 前缀。但是你不可以对可变长参数或返回值类型使用 `inout`。关于这种参数的详细讲解请参阅 [输入输出参数](../chapter2/06_Functions.md#in_out_parameters)。
如果一个函数类型只有一个形式参数而且形式参数的类型是元组类型,那么元组类型在写函数类型的时候必须用圆括号括起来。比如说,`((Int, Int)) -> Void` 是接收一个元组 `(Int, Int)` 作为形式参数并且不返回任何值的函数类型。与此相对,不加括号的 `(Int, Int) -> Void` 是一个接收两个 `Int` 作为形式参数并且不返回任何值的函数类型。相似地,因为 `Void` 是空元组类型 `()` 的别名,函数类型 `(Void)-> Void``(()) -> ()` 是一样的 - 一个将空元组作为唯一参数的函数。但这些类型和无变量的函数类型 `() -> ()` 是不一样的。
函数和方法中的变量名并不是函数类型的一部分。例如:
```swift
func someFunction(left: Int, right: Int) {}
func anotherFunction(left: Int, right: Int) {}
func functionWithDifferentLabels(top: Int, bottom: Int) {}
var f = someFunction // 函数 f 的类型为 (Int, Int) -> Void, 而不是 (left: Int, right: Int) -> Void.
var f = someFunction // 函数f的类型为 (Int, Int) -> Void, 而不是 (left: Int, right: Int) -> Void.
f = anotherFunction // 正确
f = functionWithDifferentLabels // 正确
func functionWithDifferentArgumentTypes(left: Int, right: String) {}
func functionWithDifferentNumberOfArguments(left: Int, right: Int, top: Int) {}
f = functionWithDifferentArgumentTypes // 错误
func functionWithDifferentNumberOfArguments(left: Int, right: Int, top: Int) {}
f = functionWithDifferentNumberOfArguments // 错误
```
如果一个函数类型包涵多个箭头(->),那么函数类型将从右向左进行组合。例如,函数类型 `Int -> Int -> Int` 可以理解为 `Int -> (Int -> Int)`,也就是说,该函数类型的参数为 `Int` 类型,其返回类型是一个参数类型为 `Int`,返回类型为 `Int` 的函数类型。
函数类型若要抛出错误就必须使用 `throws` 关键字来标记,若要重抛错误则必须使用 `rethrows` 关键字来标记。`throws` 关键字是函数类型的一部分,非抛出函数是抛出函数函数的一个子类型。因此,在使用抛出函数的地方也可以使用不抛出函数。抛出和重抛函数的相关描述见章节 [抛出函数与方法](05_Declarations.html#throwing_functions_and_methods) 和 [重抛函数与方法](05_Declarations.html#rethrowing_functions_and_methods)。
> 函数类型语法
<a name="function-type"></a>
> *函数类型* → [*特性列表*](06_Attributes.html#attributes)<sub>可选</sub> [*函数类型子句*](#function-type-argument-clause) **throws**<sub>可选</sub> **->** [*类型*](#type)
> *函数类型* → [*特性列表*](06_Attributes.html#attributes)<sub>可选</sub> [*函数类型子句*](#function-type-argument-clause) **rethrows­** **->** [*类型*](#type)
<a name="function-type-argument-clause"></a>
> *函数类型子句* → (­)­
> *函数类型子句* → ([*函数类型参数列表*](#function-type-argument-list)*...*­<sub>可选</sub>)­
<a name="function-type-argument-list"></a>
> *函数类型参数列表* → [*函数类型参数*](function-type-argument) | [*函数类型参数*](function-type-argument) [*函数类型参数列表*](#function-type-argument-list)
<a name="function-type-argument"></a>
> *函数类型参数* → [*特性列表*](06_Attributes.html#attributes)<sub>可选</sub> **输入输出参数**<sub>可选</sub> [*类型*](#type) | [*参数标签*](#argument-label) [*类型注解*](#type-annotation)
<a name="argument-label"></a>
> *参数标签* → [*标识符*](02_Lexical_Structure.html#identifier)
<a name="array_type"></a>
## 数组类型
Swift 语言为标准库中定义的 `Array<Element>` 类型提供了如下语法糖:
> [`类型`]
换句话说,下面两个声明是等价的:
```swift
let someArray: Array<String> = ["Alex", "Brian", "Dave"]
let someArray: [String] = ["Alex", "Brian", "Dave"]
```
上面两种情况下,常量 `someArray` 都被声明为字符串数组。数组的元素也可以通过下标访问:`someArray[0]` 是指第 0 个元素 `"Alex"`
你也可以嵌套多对方括号来创建多维数组,最里面的方括号中指明数组元素的基本类型。比如,下面例子中使用三对方括号创建三维整数数组:
```swift
var array3D: [[[Int]]] = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
```
访问一个多维数组的元素时,最左边的下标指向最外层数组的相应位置元素。接下来往右的下标指向第一层嵌入的相应位置元素,依次类推。这就意味着,在上面的例子中,`array3D[0]``[[1, 2], [3, 4]]``array3D[0][1]``[3, 4]``array3D[0][1][1]` 则是 `4`
关于 Swift 标准库中 `Array` 类型的详细讨论,请参阅 [数组](../chapter2/04_Collection_Types.html#arrays)。
> 数组类型语法
<a name="array-type"></a>
> *数组类型* → **[** [*类型*](#type) **]**
<a name="dictionary_type"></a>
## 字典类型
Swift 语言为标准库中定义的 `Dictionary<Key, Value>` 类型提供了如下语法糖:
> [`键类型` : `值类型`]
换句话说,下面两个声明是等价的:
```swift
let someDictionary: [String: Int] = ["Alex": 31, "Paul": 39]
let someDictionary: Dictionary<String, Int> = ["Alex": 31, "Paul": 39]
```
上面两种情况,常量 `someDictionary` 被声明为一个字典,其中键为 `String` 类型,值为 `Int` 类型。
字典中的值可以通过下标来访问,这个下标在方括号中指明了具体的键:`someDictionary["Alex"]` 返回键 `Alex` 对应的值。如果键在字典中不存在的话,则这个下标返回 `nil`
字典中键的类型必须符合 Swift 标准库中的 `Hashable` 协议。
关于 Swift 标准库中 `Dictionary` 类型的详细讨论,请参阅 [字典](../chapter2/04_Collection_Types.html#dictionaries)。
> 字典类型语法
<a name="dictionary-type"></a>
> *字典类型* → **[** [*类型*](#type) **:** [*类型*](#type) **]**
<a name="optional_type"></a>
## 可选类型
Swift 定义后缀 `?` 来作为标准库中的定义的命名型类型 `Optional<Wrapped>` 的语法糖。换句话说,下面两个声明是等价的:
```swift
var optionalInteger: Int?
var optionalInteger: Optional<Int>
```
在上述两种情况下,变量 `optionalInteger` 都被声明为可选整型类型。注意在类型和 `?` 之间没有空格。
类型 `Optional<Wrapped>` 是一个枚举,有两个成员,`none``some(Wrapped)`,用来表示可能有也可能没有的值。任意类型都可以被显式地声明(或隐式地转换)为可选类型。如果你在声明或定义可选变量或属性的时候没有提供初始值,它的值则会自动赋为默认值 `nil`
如果一个可选类型的实例包含一个值,那么你就可以使用后缀运算符 `!` 来获取该值,正如下面描述的:
```swift
optionalInteger = 42
optionalInteger! // 42
```
使用 `!` 运算符解包值为 `nil` 的可选值会导致运行错误。
你也可以使用可选链式调用和可选绑定来选择性地在可选表达式上执行操作。如果值为 `nil`,不会执行任何操作,因此也就没有运行错误产生。
更多细节以及更多如何使用可选类型的例子,请参阅 [可选类型](../chapter2/01_The_Basics.html#optionals)。
> 可选类型语法
<a name="optional-type"></a>
> *可选类型* → [*类型*](#type) **?**
<a name="implicitly_unwrapped_optional_type"></a>
## 隐式解析可选类型
当可以被访问时Swift 语言定义后缀 `!` 作为标准库中命名类型 `Optional<Wrapped>` 的语法糖,来实现自动解包的功能。换句话说,下面两个声明等价:
```swift
var implicitlyUnwrappedString: String!
var explicitlyUnwrappedString: Optional<String>
```
注意类型与 `!` 之间没有空格。
由于隐式解包修改了包涵其类型的声明语义,嵌套在元组类型或泛型的可选类型(比如字典元素类型或数组元素类型),不能被标记为隐式解包。例如:
由于变量标签不是函数类型的一部分,你可以在写函数类型的时候省略它们。
```swift
var operation: (lhs: Int, rhs: Int) -> Int // 错误
var operation: (_ lhs: Int, _ rhs: Int) -> Int // 正确
var operation: (Int, Int) -> Int // 正确
```
如果一个函数类型包涵多个箭头(->),那么函数类型将从右向左进行组合。例如,函数类型 `(Int) -> (Int) -> Int` 可以理解为 `(Int) -> ((Int) -> Int)`,也就是说,该函数类型的参数为 `Int` 类型,其返回类型是一个参数类型为 `Int`,返回类型为 `Int` 的函数。
函数类型若要抛出错误就必须使用 `throws` 关键字来标记,若要重抛错误则必须使用 `rethrows` 关键字来标记。`throws` 关键字是函数类型的一部分,非抛出函数是抛出函数函数的一个子类型。因此,在使用抛出函数的地方也可以使用不抛出函数。抛出和重抛函数的相关描述见章节 [抛出函数与方法](./06_Declarations.md#throwing_functions_and_methods) 和 [重抛函数与方法](./06_Declarations.md#rethrowing_functions_and_methods)。
### 对非逃逸闭包的限制 {#Restrictions for Nonescaping Closures}
当非逃逸闭包函数是参数时,不能存储在属性、变量或任何 `Any` 类型的常量中,因为这可能导致值的逃逸。
当非逃逸闭包函数是参数时,不能作为参数传递到另一个非逃逸闭包函数中。这样的限制可以让 Swift 在编译时就完成更多的内存访问冲突检查,而不是在运行时。举个例子:
```swift
let external: (Any) -> Void = { _ in () }
func takesTwoFunctions(first: (Any) -> Void, second: (Any) -> Void) {
first(first) // 错误
second(second) // 错误
first(second) // 错误
second(first) // 错误
first(external) // 正确
external(first) // 正确
}
```
在上面代码里,`takesTwoFunctions(first:second:)` 的两个参数都是函数。它们都没有标记为 `@escaping`, 因此它们都是非逃逸的。
上述例子里的被标记为“错误”的四个函数调用会产生编译错误。因为参数 `first``second` 是非逃逸函数,它们不能够作为参数被传递到另一个非闭包函数。相对的, 标记“正确”的两个函数不会产生编译错误。这些函数调用不会违反限制,因为 `external` 不是 `takesTwoFunctions(first:second:)` 的参数之一。
如果你需要避免这个限制,标记其中之一的参数为逃逸,或者使用 `withoutActuallyEscaping(_:do:)` 函数临时地转换非逃逸函数的其中一个参数为逃逸函数。关于避免内存访问冲突,可以参阅 [内存安全](../chapter2/24_Memory_Safety.md)。
> 函数类型语法
>
#### function-type {#function-type}
> *函数类型* → [*特性列表*](./07_Attributes.md#attributes)<sub>可选</sub> [*函数类型子句*](#function-type-argument-clause) **throws**<sub>可选</sub> **->** [*类型*](#type)
>
> *函数类型* → [*特性列表*](./07_Attributes.md#attributes)<sub>可选</sub> [*函数类型子句*](#function-type-argument-clause) **rethrows­** **->** [*类型*](#type)
>
#### function-type-argument-clause {#function-type-argument-clause}
> *函数类型子句* → **(**­ **)**­
> *函数类型子句* → **(** [*函数类型参数列表*](#function-type-argument-list) *...*­ <sub>可选</sub> **)**
>
#### function-type-argument-list {#function-type-argument-list}
> *函数类型参数列表* → [*函数类型参数*](function-type-argument) | [*函数类型参数*](function-type-argument) [*函数类型参数列表*](#function-type-argument-list)
>
#### function-type-argument {#function-type-argument}
> *函数类型参数* → [*特性列表*](./07_Attributes.md#attributes)<sub>可选</sub> **输入输出参数**<sub>可选</sub> [*类型*](#type) | [*参数标签*](#argument-label) [*类型注解*](#type-annotation)
>
#### argument-label {#argument-label}
> *参数标签* → [*标识符*](./02_Lexical_Structure.md#identifier)
>
## 数组类型 {#array-type-h}
Swift 语言为标准库中定义的 `Array<Element>` 类型提供了如下语法糖:
> [`类型`]
>
换句话说,下面两个声明是等价的:
```swift
let someArray: Array<String> = ["Alex", "Brian", "Dave"]
let someArray: [String] = ["Alex", "Brian", "Dave"]
```
上面两种情况下,常量 `someArray` 都被声明为字符串数组。数组的元素也可以通过下标访问:`someArray[0]` 是指第 0 个元素 `"Alex"`
你也可以嵌套多对方括号来创建多维数组,最里面的方括号中指明数组元素的基本类型。比如,下面例子中使用三对方括号创建三维整数数组:
```swift
var array3D: [[[Int]]] = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
```
访问一个多维数组的元素时,最左边的下标指向最外层数组的相应位置元素。接下来往右的下标指向第一层嵌入的相应位置元素,依次类推。这就意味着,在上面的例子中,`array3D[0]``[[1, 2], [3, 4]]``array3D[0][1]``[3, 4]``array3D[0][1][1]` 则是 `4`
关于 Swift 标准库中 `Array` 类型的详细讨论,请参阅 [数组](../chapter2/04_Collection_Types.md#arrays)。
> 数组类型语法
>
#### array-type {#array-type}
> *数组类型* → **[** [*类型*](#type) **]**
>
## 字典类型 {#dictionary-type-h}
Swift 语言为标准库中定义的 `Dictionary<Key, Value>` 类型提供了如下语法糖:
> [`键类型` : `值类型`]
>
换句话说,下面两个声明是等价的:
```swift
let someDictionary: [String: Int] = ["Alex": 31, "Paul": 39]
let someDictionary: Dictionary<String, Int> = ["Alex": 31, "Paul": 39]
```
上面两种情况,常量 `someDictionary` 被声明为一个字典,其中键为 `String` 类型,值为 `Int` 类型。
字典中的值可以通过下标来访问,这个下标在方括号中指明了具体的键:`someDictionary["Alex"]` 返回键 `Alex` 对应的值。通过下标访问会获取对应值的可选类型。如果键在字典中不存在的话,则这个下标返回 `nil`
字典中键的类型必须符合 Swift 标准库中的 `Hashable` 协议。
关于 Swift 标准库中 `Dictionary` 类型的详细讨论,请参阅 [字典](../chapter2/04_Collection_Types.md#dictionaries)。
> 字典类型语法
>
#### dictionary-type {#dictionary-type}
> *字典类型* → **[** [*类型*](#type) **:** [*类型*](#type) **]**
>
## 可选类型 {#optional-type-h}
Swift 定义后缀 `?` 来作为标准库中定义的命名型类型 `Optional<Wrapped>` 的语法糖。换句话说,下面两个声明是等价的:
```swift
var optionalInteger: Int?
var optionalInteger: Optional<Int>
```
在上述两种情况下,变量 `optionalInteger` 都被声明为可选整型类型。注意在类型和 `?` 之间没有空格。
类型 `Optional<Wrapped>` 是一个枚举,有两个成员,`none``some(Wrapped)`,用来表示可能有也可能没有的值。任意类型都可以被显式地声明(或隐式地转换)为可选类型。如果你在声明可选变量或属性的时候没有提供初始值,它的值则会自动赋为默认值 `nil`
如果一个可选类型的实例包含一个值,那么你就可以使用后缀运算符 `!` 来获取该值,正如下面描述的:
```swift
optionalInteger = 42
optionalInteger! // 42
```
使用 `!` 运算符解包值为 `nil` 的可选值会导致运行错误。
你也可以使用可选链式调用和可选绑定来选择性地在可选表达式上执行操作。如果值为 `nil`,不会执行任何操作,因此也就没有运行错误产生。
更多细节以及更多如何使用可选类型的例子,请参阅 [可选类型](../chapter2/01_The_Basics.md#optionals)。
> 可选类型语法
>
#### optional-type {#optional-type}
> *可选类型* → [*类型*](#type) **?**
>
## 隐式解析可选类型 {#implicitly-unwrapped-optional-type-h}
当可以被访问时Swift 语言定义后缀 `!` 作为标准库中命名类型 `Optional<Wrapped>` 的语法糖,来实现自动解包的功能。如果尝试对一个值为 `nil` 的可选类型进行隐式解包,将会产生运行时错误。因为隐式解包,下面两个声明等价:
```swift
var implicitlyUnwrappedString: String!
var explicitlyUnwrappedString: Optional<String>
```
注意类型与 `!` 之间没有空格。
由于隐式解包会更改包含该类型的声明语义,嵌套在元组类型或泛型中可选类型(比如字典元素类型或数组元素类型),不能被标记为隐式解包。例如:
```swift
let tupleOfImplicitlyUnwrappedElements: (Int!, Int!) // 错误
let implicitlyUnwrappedTuple: (Int, Int)! // 正确
let arrayOfImplicitlyUnwrappedElements: [Int!] // 错误
let implicitlyUnwrappedArray: [Int]! // 正确
```
由于隐式解析可选类型和可选类型有同样的表达式`Optional<Wrapped>`,你可以在使用可选类型的地方使用隐式解析可选类型。比如,你可以将隐式解析可选类型的值赋给变量、常量和可选属性,反之亦然。
正如可选类型一样,你在声明隐式解析可选类型的变量或属性的时候也不用指定初始值,因为它有默认值 `nil`
可以使用可选链式调用来在隐式解析可选表达式选择性地执行操作。如果值为 `nil`,就不会执行任何操作,因此也不会产生运行错误。
关于隐式解析可选类型的更多细节,请参阅 [隐式解析可选类型](../chapter2/01_The_Basics.html#implicityly_unwrapped_optionals)。
> 隐式解析可选类型语法
<a name="implicitly-unwrapped-optional-type"></a>
> *隐式解析可选类型* → [*类型*](#type) **!**
<a name="protocol_composition_type"></a>
## 协议合成类型
协议合成类型是一种符合协议列表中每个指定协议的类型。协议合成类型可能会用在类型注解和泛型参数中。
协议合成类型的形式如下:
> `Protocol 1` & `Procotol 2`
协议合成类型允许你指定一个值,其类型符合多个协议的要求且不需要定义一个新的命名型协议来继承它想要符合的各个协议。比如,协议合成类型 `Protocol A & Protocol B & Protocol C` 等效于一个从 `Protocol A``Protocol B` `Protocol C` 继承而来的新协议 `Protocol D`,很显然这样做有效率的多,甚至不需引入一个新名字。
协议合成列表中的每项必须是协议名或协议合成类型的类型别名。
> 协议合成类型语法
<a name="protocol-composition-type"></a>
> *协议合成类型* → [*协议标识符*](#protocol-identifier) & [*协议合成延续*](#protocol-composition-continuation)
<a name="protocol-composition-continuation"></a>
> *协议合成延续* → [*协议标识符*](#protocol-identifier) | [*协议合成类型*](#protocol-composition-type)
<a name="protocol-identifier"></a>
> *协议标识符* → [*类型标识符*](#type-identifier)
<a name="metatype_type"></a>
## 元类型
元类型是指类型的类型,包括类类型、结构体类型、枚举类型和协议类型。
类、结构体或枚举类型的元类型是相应的类型名紧跟 `.Type`。协议类型的元类型——并不是运行时符合该协议的具体类型——而是该协议名字紧跟 `.Protocol`。比如,类 `SomeClass` 的元类型就是 `SomeClass.Type`,协议 `SomeProtocol` 的元类型就是 `SomeProtocal.Protocol`
你可以使用后缀 `self` 表达式来获取类型。比如,`SomeClass.self` 返回 `SomeClass` 本身,而不是 `SomeClass` 的一个实例。同样,`SomeProtocol.self` 返回 `SomeProtocol` 本身,而不是运行时符合 `SomeProtocol` 的某个类型的实例。还可以对类型的实例使用 `type(of:)` 表达式来获取该实例在运行阶段的类型,如下所示:
```swift
class SomeBaseClass {
class func printClassName() {
println("SomeBaseClass")
}
}
class SomeSubClass: SomeBaseClass {
override class func printClassName() {
println("SomeSubClass")
}
}
let someInstance: SomeBaseClass = SomeSubClass()
// someInstance 在编译期是 SomeBaseClass 类型,
// 但是在运行期则是 SomeSubClass 类型
type(of: someInstance).printClassName()
// 打印 “SomeSubClass”
```
可以使用恒等运算符(`===``!==`)来测试一个实例的运行时类型和它的编译时类型是否一致。
```swift
if type(of: someInstance) === someInstance.self {
print("The dynamic and static type of someInstance are the same")
} else {
print("The dynamic and static type of someInstance are different")
由于隐式解析可选类型和可选类型有同样的类型 `Optional<Wrapped>`,你可以在所有使用可选类型的地方使用隐式解析可选类型。比如,你可以将隐式解析可选类型的值赋给变量、常量和可选属性,反之亦然。
正如可选类型一样,如果你在声明隐式解析可选类型的变量或属性的时候没有指定初始值,它的值则会自动赋为默认值 `nil`
可以使用可选链式调用隐式解析可选表达式选择性地执行操作。如果值为 `nil`,就不会执行任何操作,因此也不会产生运行错误。
关于隐式解析可选类型的更多细节,请参阅 [隐式解析可选类型](../chapter2/01_The_Basics.md#implicityly_unwrapped_optionals)。
> 隐式解析可选类型语法
>
#### implicitly-unwrapped-optional-type {#implicitly-unwrapped-optional-type}
> *隐式解析可选类型* → [*类型*](#type) **!**
>
## 协议合成类型 {#protocol-composition-type-h}
*协议合成类型*定义了一种遵循协议列表中每个指定协议的类型,或者一个现有类型的子类并遵循协议列表中每个指定协议。协议合成类型只能用在类型注解、泛型参数子句和泛型 `where` 子句中指定类型。
协议合成类型的形式如下:
> `Protocol 1` & `Procotol 2`
协议合成类型允许你指定一个值,其类型遵循多个协议的要求而不需要定义一个新的命名型协议来继承它想要符合的各个协议。比如,协议合成类型 `Protocol A & Protocol B & Protocol C` 等效于一个从 `Protocol A``Protocol B``Protocol C` 继承而来的新协议。同样的,你可以使用 `SuperClass & ProtocolA` 来取代申明一个新的协议作为 `SuperClass` 的子类并遵循 `ProtocolA`
协议合成列表中的每一项都必须是下面所列情况之一,列表中最多只能包含一个类:
- 类名
- 协议名
- 一个类型别名,它的潜在类型是一个协议合成类型、一个协议或者一个类
当协议合成类型包含类型别名时,同一个协议可能多次出现在定义中 — 重复被忽略。例如,下面代码中定义的 `PQR` 等同于 `P & Q & R`
```swift
typealias PQ = P & Q
typealias PQR = PQ & Q & R
```
> 协议合成类型语法
>
#### protocol-composition-type {#protocol-composition-type}
> *协议合成类型* → [*协议标识符*](#protocol-identifier) & [*协议合成延续*](#protocol-composition-continuation)
>
#### protocol-composition-continuation {#protocol-composition-continuation}
> *协议合成延续* → [*协议标识符*](#protocol-identifier) | [*协议合成类型*](#protocol-composition-type)
## 不透明类型 {#opaque-type-h}
*不透明类型*定义了遵循某个协议或者合成协议的类型,但不需要指明底层的具体类型。
不透明类型可以作为函数或下标的返回值,亦或是属性的类型使用。
不透明类型不能作为元组类型的一部分或范型类型使用,比如数组元素类型或者可选值的包装类型。
不透明类型的形式如下:
> some `constraint`
*constraint* 可以是类类型,协议类型,协议组合类型或者 `Any`。值只有当它遵循该协议或者组合协议,或者从该类继承的时候,才能作为这个不透明类型的实例使用。和不透明值交互的代码只能使用该值定义在 *constraint* 上的接口。
协议声明里不能包括不透明类型。类不能使用不透明类型作为非 final 方法的返回值。
使用不透明类型作为返回值的函数必须返回单一公用底层类型。返回的类型可以包含函数范型类型参数的一部分。举个例子,函数 `someFunction<T>()` 可以返回类型 `T` 或者 `Dictionary<String,T>` 的值。
> 不透明类型语法
#### opaque-type {#opaque-type}
> *不透明类型* → **some** [type](#type)
## 元类型 {#metatype-type-h}
*元类型*是指任意类型的类型,包括类类型、结构体类型、枚举类型和协议类型。
类、结构体或枚举类型的元类型是相应的类型名紧跟 `.Type`。协议类型的元类型——并不是运行时遵循该协议的具体类型——是该协议名字紧跟 `.Protocol`。比如,类 `SomeClass` 的元类型就是 `SomeClass.Type`,协议 `SomeProtocol` 的元类型就是 `SomeProtocal.Protocol`
你可以使用后缀 `self` 表达式来获取类型。比如,`SomeClass.self` 返回 `SomeClass` 本身,而不是 `SomeClass` 的一个实例。同样,`SomeProtocol.self` 返回 `SomeProtocol` 本身,而不是运行时遵循 `SomeProtocol` 的某个类型的实例。还可以对类型的实例使用 `type(of:)` 表达式来获取该实例动态的、在运行阶段的类型,如下所示:
```swift
class SomeBaseClass {
class func printClassName() {
println("SomeBaseClass")
}
}
// 打印 "The dynamic and static type of someInstance are different"
```
可以使用初始化表达式从某个类型的元类型构造出一个该类型的实例。对于类实例,被调用的构造器必须使用 `required` 关键字标记,或者整个类使用 `final` 关键字标记。
```swift
class AnotherSubClass: SomeBaseClass {
let string: String
required init(string: String) {
self.string = string
}
override class func printClassName() {
print("AnotherSubClass")
}
}
let metatype: AnotherSubClass.Type = AnotherSubClass.self
let anotherInstance = metatype.init(string: "some string")
```
> 元类型语法
<a name="metatype-type"></a>
> *元类型* → [*类型*](#type) **.** **Type** | [*类型*](#type) **.** **Protocol**
<a name="type_inheritance_clause"></a>
## 类型继承子句
类型继承子句被用来指定一个命名型类型继承自哪个类、采纳哪些协议。类型继承子句也用来指定一个类类型专属协议。类型继承子句开始于冒号 `:`,其后是所需要的类、类型标识符列表或两者都有。
类可以继承单个超类,采纳任意数量的协议。当定义一个类时,超类的名字必须出现在类型标识符列表首位,然后跟上该类需要采纳的任意数量的协议。如果一个类不是从其它类继承而来,那么列表可以以协议开头。关于类继承更多的讨论和例子,请参阅 [继承](../chapter2/13_Inheritance.html)。
其它命名型类型可能只继承或采纳一系列协议。协议类型可以继承自任意数量的其他协议。当一个协议类型继承自其它协议时,其它协议中定义的要求会被整合在一起,然后从当前协议继承的任意类型必须符合所有这些条件。正如在 [协议声明](05_Declarations.html#protocol_declaration) 中所讨论的那样,可以把 `class` 关键字放到协议类型的类型继承子句的首位,这样就可以声明一个类类型专属协议。
枚举定义中的类型继承子句可以是一系列协议,或是枚举的原始值类型的命名型类型。在枚举定义中使用类型继承子句来指定原始值类型的例子,请参阅 [原始值](../chapter2/08_Enumerations.html#raw_values)。
> 类型继承子句语法
<a name="type_inheritance_clause"></a>
> *类型继承子句* → **:** [*类要求*](#class-requirement) **,** [*类型继承列表*](#type-inheritance-list)
> *类型继承子句* → **:** [*类要求*](#class-requirement)
> *类型继承子句* → **:** [*类型继承列表*](#type-inheritance-list)
<a name="type-inheritance-list"></a>
> *类型继承列表* → [*类型标识符*](#type-identifier) | [*类型标识符*](#type-identifier) **,** [*类型继承列表*](#type-inheritance-list)
<a name="class-requirement"></a>
> *类要求* → **class**
<a name="type_inference"></a>
## 类型推断
Swift 广泛使用类型推断,从而允许你省略代码中很多变量和表达式的类型或部分类型。比如,对于 `var x: Int = 0`,你可以完全省略类型而简写成 `var x = 0`,编译器会正确推断出 `x` 的类型 `Int`。类似的,当完整的类型可以从上下文推断出来时,你也可以省略类型的一部分。比如,如果你写了 `let dict: Dictionary = ["A" : 1]`,编译器能推断出 `dict` 的类型是 `Dictionary<String, Int>`
在上面的两个例子中,类型信息从表达式树的叶子节点传向根节点。也就是说,`var x: Int = 0``x` 的类型首先根据 `0` 的类型进行推断,然后将该类型信息传递到根节点(变量 `x`)。
在 Swift 中,类型信息也可以反方向流动——从根节点传向叶子节点。在下面的例子中,常量 `eFloat` 上的显式类型注解(`: Float`)将导致数字字面量 `2.71828` 的类型是 `Float` 而非 `Double`
```swift
let e = 2.71828 // e 的类型会被推断为 Double
let eFloat: Float = 2.71828 // eFloat 的类型为 Float
```
Swift 中的类型推断在单独的表达式或语句上进行。这意味着所有用于类型推断的信息必须可以从表达式或其某个子表达式的类型检查中获取到。
class SomeSubClass: SomeBaseClass {
override class func printClassName() {
println("SomeSubClass")
}
}
let someInstance: SomeBaseClass = SomeSubClass()
// someInstance 在编译期是 SomeBaseClass 类型,
// 但是在运行期则是 SomeSubClass 类型
type(of: someInstance).printClassName()
// 打印“SomeSubClass”
```
更多信息可以查看 Swift 标准库里的 [type(of:)](https://developer.apple.com/documentation/swift/2885064-type)。
可以使用初始化表达式从某个类型的元类型构造出一个该类型的实例。对于类实例,被调用的构造器必须使用 `required` 关键字标记,或者整个类使用 `final` 关键字标记。
```swift
class AnotherSubClass: SomeBaseClass {
let string: String
required init(string: String) {
self.string = string
}
override class func printClassName() {
print("AnotherSubClass")
}
}
let metatype: AnotherSubClass.Type = AnotherSubClass.self
let anotherInstance = metatype.init(string: "some string")
```
> 元类型语法
>
#### metatype-type {#metatype-type}
> *元类型* → [*类型*](#type) **.** **Type** | [*类型*](#type) **.** **Protocol**
## 自身类型 {#self-type-h}
`Self` 类型不是具体的类型,而是让你更方便的引用当前类型,不需要重复或者知道该类的名字。
在协议声明或者协议成员声明时,`Self` 类型引用的是最终遵循该协议的类型。
在结构体,类或者枚举值声明时,`Self` 类型引用的是声明的类型。在某个类型成员声明时,`Self` 类型引用的是该类型。在类成员声明时,`Self` 可以在方法的返回值和方法体中使用,但不能在其他上下文中使用。举个例子,下面的代码演示了返回值是 `Self` 的实例方法 `f`
```swift
class Superclass {
func f() -> Self { return self }
}
let x = Superclass()
print(type(of: x.f()))
// 打印 "Superclass"
class Subclass: Superclass { }
let y = Subclass()
print(type(of: y.f()))
// 打印 "Subclass"
let z: Superclass = Subclass()
print(type(of: z.f()))
// 打印 "Subclass"
```
上面例子的最后一部分表明 `Self` 引用的是值 `z` 的运行时类型 `Subclass` ,而不是变量本身的编译时类型 `Superclass`
在嵌套类型声明时,`Self` 类型引用的是最内层声明的类型。
`Self` 类型引用的类型和 Swift 标准库中 [type(of:)](https://developer.apple.com/documentation/swift/2885064-type) 函数的结果一样。使用 `Self.someStaticMember` 访问当前类型中的成员和使用 `type(of: self).someStaticMember` 是一样的。
> 自身类型语法
#### self-type{#self-type}
> *自身类型* → **Self**
## 类型继承子句 {#type-inheritance-clause-h}
*类型继承子句*被用来指定一个命名型类型继承自哪个类、采纳哪些协议。类型继承子句开始于冒号 `:`,其后是类型标识符列表。
类可以继承自单个超类,并遵循任意数量的协议。当定义一个类时,超类的名字必须出现在类型标识符列表首位,然后跟上该类需要遵循的任意数量的协议。如果一个类不是从其它类继承而来,那么列表可以以协议开头。关于类继承更多的讨论和例子,请参阅 [继承](../chapter2/13_Inheritance.md)。
其它命名型类型只能继承自或采纳一系列协议。协议类型可以继承自任意数量的其他协议。当一个协议类型继承自其它协议时,其它协议中定义的要求会被整合在一起,然后从当前协议继承的任意类型必须符合所有这些条件。
枚举定义中的类型继承子句可以是一系列协议,或者是指定单一的命名类型,此时枚举为其用例分配原始值。在枚举定义中使用类型继承子句来指定原始值类型的例子,请参阅 [原始值](../chapter2/08_Enumerations.md#raw_values)。
> 类型继承子句语法
>
#### type_inheritance_clause {#type-inheritance-clause}
> *类型继承子句* → **:** [*类型继承列表*](#type-inheritance-list)
>
#### type-inheritance-list {#type-inheritance-list}
> *类型继承列表* → [*类型标识符*](#type-identifier) | [*类型标识符*](#type-identifier) **,** [*类型继承列表*](#type-inheritance-list)
>
## 类型推断
Swift 广泛使用*类型推断*,从而允许你省略代码中很多变量和表达式的类型或部分类型。比如,对于 `var x: Int = 0`,你可以完全省略类型而简写成 `var x = 0`,编译器会正确推断出 `x` 的类型 `Int`。类似的,当完整的类型可以从上下文推断出来时,你也可以省略类型的一部分。比如,如果你写了 `let dict: Dictionary = ["A" : 1]`,编译器能推断出 `dict` 的类型是 `Dictionary<String, Int>`
在上面的两个例子中,类型信息从表达式树的叶子节点传向根节点。也就是说,`var x: Int = 0``x` 的类型首先根据 `0` 的类型进行推断,然后将该类型信息传递到根节点(变量 `x`)。
在 Swift 中,类型信息也可以反方向流动——从根节点传向叶子节点。在下面的例子中,常量 `eFloat` 上的显式类型注解(`: Float`)将导致数字字面量 `2.71828` 的类型是 `Float` 而非 `Double`
```swift
let e = 2.71828 // e 的类型会被推断为 Double
let eFloat: Float = 2.71828 // eFloat 的类型为 Float
```
Swift 中的类型推断在单独的表达式或语句上进行。这意味着所有用于类型推断的信息必须可以从表达式或其某个子表达式的类型检查中获取到。

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

908
source/chapter3/05_Statements.md Executable file
View File

@ -0,0 +1,908 @@
# 语句Statements{#statement-statements}
在 Swift 中,有三种类型的语句:简单语句、编译器控制语句和控制流语句。简单语句是最常见的,用于构造表达式或者声明。编译器控制语句允许程序改变编译器的行为,包含编译配置语句和行控制语句。
控制流语句则用于控制程序执行的流程Swift 中有多种类型的控制流语句循环语句、分支语句和控制转移语句。循环语句用于重复执行代码块分支语句用于执行满足特定条件的代码块控制转移语句则用于改变代码的执行顺序。另外Swift 提供了 `do` 语句,用于构建局部作用域,还用于错误的捕获和处理;还提供了 `defer` 语句,用于退出当前作用域之前执行清理操作。
是否将分号(`;`)添加到语句的末尾是可选的。但若要在同一行内写多条独立语句,则必须使用分号。
> 语句语法
>
> *语句* → [*表达式*](./04_Expressions.md#expression) **;**<sub>可选</sub>
>
> *语句* → [*声明*](./06_Declarations.md#declaration) **;**<sub>可选</sub>
>
> *语句* → [*循环语句*](#loop-statement) **;**<sub>可选</sub>
>
> *语句* → [*分支语句*](#branch-statement) **;**<sub>可选</sub>
>
> *语句* → [*带标签的语句*](#labeled-statement) **;**<sub>可选</sub>
>
> *语句* → [*控制转移语句*](#control-transfer-statement) **;**<sub>可选</sub>
>
> *语句* → [*defer 语句*](#defer-statement) **;**<sub>可选</sub>
>
> *语句* → [*do 语句*](#do-statement) **:**<sub>可选</sub>
>
> *语句* → [*编译器控制语句*](#compiler-control-statement)
>
> *多条语句* → [*语句*](#statement) [*多条语句*](#statements)<sub>可选</sub>
>
## 循环语句 {#loop-statements}
循环语句会根据特定的循环条件来重复执行代码块。Swift 提供三种类型的循环语句:`for-in` 语句、`while` 语句和 `repeat-while` 语句。
通过 `break` 语句和 `continue` 语句可以改变循环语句的控制流。有关这两条语句,详情参见 [Break 语句](#break_statement) 和 [Continue 语句](#continue_statement)。
> 循环语句语法
>
>
#### loop-statement {#loop-statement}
> *循环语句* → [*for-in 语句*](#for-in-statement)
>
> *循环语句* → [*while 语句*](#while-statement)
>
> *循环语句* → [*repeat-while 语句*](#repeat-while-statement)
>
### For-In 语句 {#for-in-statements}
`for-in` 语句会为集合(或实现了 [Sequence](https://developer.apple.com/documentation/swift/sequence) 协议的任意类型)中的每一项执行一次代码块。
`for-in` 语句的形式如下:
```swift
for item in collection {
statements
}
```
`for-in` 语句在循环开始前会调用集合表达式(`collection expression`)的 `makeIterator()` 方法来获取一个实现了 [IteratorProtocol](https://developer.apple.com/documentation/swift/iteratorprotocol) 协议的迭代器类型。接下来循环开始,反复调用该迭代器的 `next()` 方法。如果其返回值不是 `nil`,它将会被赋给 `item`,然后执行循环体语句,执行完毕后回到循环开始处,继续重复这一过程;否则,既不会赋值也不会执行循环体语句,`for-in` 语句至此执行完毕。
> for-in 语句语法
>
>
#### for-in-statement {#for-in-statement}
> *for-in 语句* → **for** **case**<sub>可选</sub> [*模式*](./08_Patterns.md#pattern) **in** [*表达式*](./04_Expressions.md#expression) [*where 子句*](#where-clause)<sub>可选</sub> [*代码块*](05_Declarations.md#code-block)
>
### While 语句 {#while-statements}
只要循环条件为真,`while` 语句就会重复执行代码块。
`while` 语句的形式如下:
```swift
while condition {
statements
}
```
`while` 语句的执行流程如下:
1. 判断条件(`condition`)的值。如果为 `true`,转到第 2 步;如果为 `false``while` 语句至此执行完毕。
2. 执行循环体中的语句,然后重复第 1 步。
由于会在执行循环体中的语句前判断条件的值,因此循环体中的语句可能会被执行若干次,也可能一次也不会被执行。
条件的结果必须是 Bool 类型或者 Bool 的桥接类型。另外,条件语句也可以使用可选绑定,请参阅 [可选绑定](../chapter2/01_The_Basics.md#optional_binding)。
> while 语句语法
>
>
#### while-statement {#while-statement}
> *while 语句* → **while** [*条件子句*](#condition-clause) [*代码块*](./05_Declarations.md#code-block)
>
#### condition-clause {#condition-clause}
> *条件子句* → [*表达式*](./04_Expressions.md#expression) | [*表达式*](./04_Expressions.md#expression) **,** [*条件列表*](#condition-list)
>
#### condition {#condition}
> *条件* → [*表达式*](./04_Expressions.md#expression) |[*可用性条件*](#availability-condition) | [*case 条件*](#case-condition) | [*可选绑定条件*](#optional-binding-condition)
>
>
#### case-condition {#case-condition}
> *case 条件* → **case** [*模式*](./08_Patterns.md#pattern) [*构造器*](./06_Declarations.md#initializer)
>
#### optional-binding-condition {#optional-binding-condition}
> *可选绑定条件* → **let** [*模式*](./08_Patterns.md#pattern) [*构造器*](./06_Declarations.md#initializer) | **var** [*模式*](./08_Patterns.md#pattern) [*构造器*](./06_Declarations.md#initializer)
>
### Repeat-While 语句 {#repeat-while-statements}
`repeat-while` 语句至少执行一次代码块,之后只要循环条件为真,就会重复执行代码块。
`repeat-while` 语句的形式如下:
```swift
repeat {
statements
} while condition
```
`repeat-while` 语句的执行流程如下:
1. 执行循环体中的语句,然后转到第 2 步。
2. 判断条件的值。如果为 `true`,重复第 1 步;如果为 `false``repeat-while` 语句至此执行完毕。
由于条件的值是在循环体中的语句执行后才进行判断,因此循环体中的语句至少会被执行一次。
条件的结果必须是 Bool 类型或者 Bool 的桥接类型。另外,条件语句也可以使用可选绑定,请参阅 [可选绑定](../chapter2/01_The_Basics.md#optional_binding)。
> repeat-while 语句语法
>
>
#### repeat-while-statement {#repeat-while-statement}
> *repeat-while 语句* → **repeat** [*代码块*](./06_Declarations.md#code-block) **while** [*表达式*](./04_Expressions.md#expression)
>
## 分支语句 {#branch-statements}
分支语句会根据一个或者多个条件来执行指定部分的代码。分支语句中的条件将会决定程序如何分支以及执行哪部分代码。Swift 提供三种类型的分支语句:`if` 语句、 `guard` 语句和 `switch` 语句。
`if` 语句和 `switch` 语句中的控制流可以用 `break` 语句改变,请参阅 [Break 语句](#break_statement)。
> 分支语句语法
>
>
#### branch-statement {#branch-statement}
> *分支语句* → [*if 语句*](#if-statement)
>
> *分支语句* → [*guard 语句*](#guard-statement)
>
> *分支语句* → [*switch 语句*](#switch-statement)
>
### If 语句 {#if-statements}
`if` 语句会根据一个或多个条件来决定执行哪一块代码。
`if` 语句有两种基本形式,无论哪种形式,都必须有花括号。
第一种形式是当且仅当条件为真时执行代码,像下面这样:
```swift
if condition {
statements
}
```
第二种形式是在第一种形式的基础上添加 `else` 语句(通过引入 `else` 关键字),并且用于:当条件为真时执行一部分代码,当这同一个条件为假的时候执行另一部分代码。当只有一个 `else` 语句时,`if` 语句具有以下的形式:
```swift
if condition {
statements to execute if condition is true
} else {
statements to execute if condition is false
}
```
`if` 语句的 `else` 语句也可包含另一个 `if` 语句,从而形成一条链来测试更多的条件,像下面这样:
```swift
if condition 1 {
statements to execute if condition 1 is true
} else if condition 2 {
statements to execute if condition 2 is true
} else {
statements to execute if both conditions are false
}
```
`if` 语句中条件的结果必须是 Bool 类型或者 Bool 的桥接类型。另外,条件语句也可以使用可选绑定,请参阅 [可选绑定](../chapter2/01_The_Basics.md#optional_binding)。
> if 语句语法
>
>
#### if-statement {#if-statement}
> *if 语句* → **if** [*条件子句*](#condition-clause) [*代码块*](05_Declarations.md#code-block) [*else 子句*](#else-clause)<sub>可选</sub>
>
#### else-clause {#else-clause}
> *else 子句* → **else** [*代码块*](./06_Declarations.md#code-block) | **else** [*if 语句*](#if-statement)
>
### Guard 语句 {#guard-statements}
如果一个或者多个条件不成立,可用 `guard` 语句来退出当前作用域。
`guard` 语句的格式如下:
```swift
guard condition else {
statements
}
```
`guard` 语句中条件的结果必须是 Bool 类型或者 Bool 的桥接类型。另外,条件也可以是一条可选绑定,请参阅 [可选绑定](../chapter2/01_The_Basics.md#optional_binding)。
`guard` 语句中进行可选绑定的任何常量或者变量,其可用范围从声明开始直到作用域结束。
`guard` 语句必须有 `else` 子句,而且必须在该子句中调用返回类型是 `Never` 的函数,或者使用下面的语句退出当前作用域:
* `return`
* `break`
* `continue`
* `throw`
关于控制转移语句,请参阅 [控制转移语句](#control_transfer_statements)。关于 `Never` 返回类型的函数,请参阅 [永不返回的函数](05_Declarations.md#rethrowing_functions_and_methods)。
> guard 语句语法
>
>
#### guard-statement {#guard-statement}
> *guard 语句* → **guard** [*条件子句*](#condition-clause) **else** [*代码块*] (05_Declarations.md#code-block)
>
### Switch 语句 {#switch-statements}
`switch` 语句会根据控制表达式的值来决定执行哪部分代码。
`switch` 语句的形式如下:
```swift
switch control expression {
case pattern 1:
statements
case pattern 2 where condition:
statements
case pattern 3 where condition,
pattern 4 where condition:
statements
default:
statements
}
```
`switch` 语句会先计算*控制表达式*的值,然后与每一个 `case` 的模式进行匹配。如果匹配成功,程序将会执行对应的 `case` 中的语句。另外,每一个 `case` 的作用域都不能为空,也就是说在每一个 `case` 的冒号(`:`)后面必须至少有一条语句。如果你不想在匹配到的 `case` 中执行代码,只需在该 `case` 中写一条 `break` 语句即可。
可以用作控制表达式的值是十分灵活的。除了标量类型外,如 `Int``Character`,你可以使用任何类型的值,包括浮点数、字符串、元组、自定义类型的实例和可选类型。控制表达式的值还可以用来匹配枚举类型中的成员值或是检查该值是否包含在指定的 `Range` 中。关于如何在 `switch` 语句中使用这些类型,请参阅 [控制流](../chapter2/05_Control_Flow.md) 一章中的 [Switch](../chapter2/05_Control_Flow.md#switch)。
每个 `case` 的模式后面可以有一个 `where` 子句。`where` 子句由 `where` 关键字紧跟一个提供额外条件的表达式组成。因此,当且仅当控制表达式匹配一个 `case` 的模式且 `where` 子句的表达式为真时,`case` 中的语句才会被执行。在下面的例子中,控制表达式只会匹配包含两个相等元素的元组,例如 `(1, 1)`
```swift
case let (x, y) where x == y:
```
正如上面这个例子,也可以在模式中使用 `let`(或 `var`)语句来绑定常量(或变量)。这些常量(或变量)可以在对应的 `where` 子句以及 `case` 中的代码中使用。但是,如果一个 `case` 中含有多个模式,所有的模式必须包含相同的常量(或变量)绑定,并且每一个绑定的常量(或变量)必须在所有的条件模式中都有相同的类型。
`switch` 语句也可以包含默认分支,使用 `default` 关键字表示。只有所有 `case` 都无法匹配控制表达式时,默认分支中的代码才会被执行。一个 `switch` 语句只能有一个默认分支,而且必须在 `switch` 语句的最后面。
`switch` 语句中 `case` 的匹配顺序和源代码中的书写顺序保持一致。因此,当多个模式都能匹配控制表达式时,只有第一个匹配的 `case` 中的代码会被执行。
#### Switch 语句必须是详尽的
在 Swift 中,`switch` 语句中控制表达式的每一个可能的值都必须至少有一个 `case` 与之对应。在某些无法面面俱到的情况下(例如,表达式的类型是 `Int`),你可以使用 `default` 分支满足该要求。
#### 对未来枚举的 `case` 进行 `switch` {#future-case}
非冻结枚举(`nonfronzen enumeration`)是一种特殊的枚举类型,它可能在未来会增加新的枚举 `case`,即使这时候你已经编译并且发布了你的应用,所以在 switch 非冻结枚举前需要深思熟虑。当一个库的作者们把一个枚举标记为非冻结的,这意味着他们保留了增加新的枚举 `case` 的权利,并且任何和这个枚举交互的代码都要在不需要重新编译的条件下能够处理那些未来可能新加入的 `case` 。只有那些标准库,比如用 Swift 实现的苹果的一些框架C 以及 Objective-C 代码才能够声明非冻结枚举。你在 Swift 中声明的枚举不能是非冻结的。
当你对未来枚举进行 switch 时,你总是需要有一个 `default case`,即使每种枚举类型都已经有对应的 `case` 了。你可以在 default 前标注 `@unknown`,意思是这个 `case` 应该只匹配未来加入的枚举 `case`。如果你的 `default case` 中匹配了任何在编译时就能确定的枚举 `case`Swift 会抛出一个警告。这可以很好地提醒你库的作者已经新增了一种 `case`,并且你还没有去处理。
以下就是一个例子,我们对标准库的 [Mirror.AncestorRepresentation](https://developer.apple.com/documentation/swift/mirror/ancestorrepresentation) 枚举进行 switch 操作。每当有新的 `case` 加入,我们会得到一个警告,提示我们要去处理它。
```swift
let representation: Mirror.AncestorRepresentation = .generated
switch representation {
case .customized:
print("Use the nearest ancestors implementation.")
case .generated:
print("Generate a default mirror for all ancestor classes.")
case .suppressed:
print("Suppress the representation of all ancestor classes.")
@unknown default:
print("Use a representation that was unknown when this code was compiled.")
}
// Prints "Generate a default mirror for all ancestor classes."
```
#### 不存在隐式落入
当匹配到的 `case` 中的代码执行完毕后,`switch` 语句会直接退出,而不会继续执行下一个 `case` 。这就意味着,如果你想执行下一个 `case`,需要显式地在当前 `case` 中使用 `fallthrough` 语句。关于 `fallthrough` 语句的更多信息,请参阅 [Fallthrough 语句](#fallthrough_statements)。
> switch 语句语法
>
>
#### switch-statement {#switch-statement}
> *switch 语句* → **switch** [*表达式*](./04_Expressions.md#expression) **{** [*switch-case 列表*](#switch-cases)<sub>可选</sub> **}**
>
#### switch-cases {#switch-cases}
> *switch case 列表* → [*switch-case*](#switch-case) [*switch-case 列表*](#switch-cases)<sub>可选</sub>
>
#### switch-case {#switch-case}
> *switch case* → [*case 标签*](#case-label) [*多条语句*](#statements) | [*default 标签*](#default-label) [*多条语句*](#statements) | [*conditional-switch-case*](#conditional-switch-case-label)
>
#### case-label {#case-label}
> *case 标签* → [*属性*](#switch-case-attributes-label)<sub>可选</sub> **case** [*case 项列表*](#case-item-list) **:**
>
#### case-item-list {#case-item-list}
> *case 项列表* → [*模式*](./08_Patterns.md#pattern) [*where 子句*](#where-clause)<sub>可选</sub> | [*模式*](07_Patterns.md#pattern) [*where 子句*](#where-clause)<sub>可选</sub> **,** [*case 项列表*](#case-item-list)
>
#### default-label {#default-label}
> *default 标签* → [*属性*](#switch-case-attributes-label)<sub>可选</sub> **default** **:**
>
>
#### where-clause {#where-clause}
> *where-clause* → **where** [*where 表达式*](#where-expression)
>
#### where-expression {#where-expression}
> *where-expression* → [*表达式*](./04_Expressions.md#expression)
>
>
#### grammar_conditional-switch-case {#grammar-conditional-switch-case}
> *conditional-switch-case* → [*switch-if-directive-clause*](#switch-case-attributes-label) [*switch-elseif-directive-clauses*](#switch-case-attributes-label) <sub>可选</sub> [*switch-else-directive-clause*](#switch-case-attributes-label) <sub>可选</sub> [*endif-directive*](#switch-case-attributes-label)
>
#### grammar_switch-if-directive-clause {#grammar-switch-if-directive-clause}
> *switch-if-directive 语句* → [*if-directive*](#switch-case-attributes-label) [*compilation-condition*](#switch-case-attributes-label) [*switch-cases*](#switch-case-attributes-label) <sub>可选</sub>
>
#### grammar_switch-elseif-directive-clauses {#grammar-switch-elseif-directive-clauses}
> *switch-elseif-directive 语句(复数)* → [*elseif-directive-clause*](#switch-case-attributes-label) [*switch-elseif-directive-clauses*](#switch-case-attributes-label)<sub>可选</sub>
>
#### grammar_switch-elseif-directive-clause {#grammar-switch-elseif-directive-clause}
> *switch-elseif-directive 语句* → [*elseif-directive*](#switch-case-attributes-label) [*compilation-condition*](#switch-case-attributes-label) [*switch-cases*](#switch-case-attributes-label)<sub>可选</sub>
>
#### grammar_switch-else-directive-clause {#grammar-switch-else-directive-clause}
> *switch-else-directive 语句* → [*else-directive*](#switch-case-attributes-label) [*switch-cases*](#switch-case-attributes-label) <sub>可选</sub>
>
## 带标签的语句 {#labeled-statements}
你可以在循环语句或 `switch` 语句前面加上标签,它由标签名和紧随其后的冒号(`:`)组成。在 `break``continue` 后面跟上标签名可以显式地在循环语句或 `switch` 语句中改变相应的控制流。关于这两条语句用法,请参阅 [Break 语句](#break_statement) 和 [Continue 语句](#continue_statement)。
标签的作用域在该标签所标记的语句内。可以嵌套使用带标签的语句,但标签名必须唯一。
关于使用带标签的语句的例子,请参阅 [控制流](../chapter2/05_Control_Flow.md) 一章中的 [带标签的语句](../chapter2/05_Control_Flow.md#labeled_statements)。
> 带标签的语句语法
>
>
#### labeled-statement {#labeled-statement}
> *带标签的语句* → [*语句标签*](#statement-label) [*循环语句*](#grammar_loop-statement)
>
> *带标签的语句* → [*语句标签*](#statement-label) [*if 语句*](#if-statement)
>
> *带标签的语句* → [*语句标签*](#statement-label) [*switch 语句*](#switch-statement)
>
> > *带标签的语句* → [*语句标签*](#statement-label) [*do 语句*](#sdo-statement)
>
#### statement-label {#statement-label}
> *语句标签* → [*标签名称*](#label-name) **:**
>
#### label-name {#label-name}
> *标签名称* → [*标识符*](./02_Lexical_Structure.md#identifier)
>
## 控制转移语句 {#control-transfer-statements}
控制转移语句能够无条件地把控制权从一片代码转移到另一片代码从而改变代码执行的顺序。Swift 提供五种类型的控制转移语句:`break` 语句、`continue` 语句、`fallthrough` 语句、`return` 语句和 `throw` 语句。
> 控制转移语句语法
>
>
#### control-transfer-statement {#control-transfer-statement}
> *控制转移语句* → [*break 语句*](#break-statement)
>
> *控制转移语句* → [*continue 语句*](#continue-statement)
>
> *控制转移语句* → [*fallthrough 语句*](#fallthrough-statement)
>
> *控制转移语句* → [*return 语句*](#return-statement)
>
> *控制转移语句* → [*throw 语句*](#throw-statement)
>
### Break 语句 {#break-statement}
`break` 语句用于终止循环语句、`if` 语句或 `switch` 语句的执行。使用 `break` 语句时,可以只写 `break` 这个关键词,也可以在 `break` 后面跟上标签名,像下面这样:
> break
>
> break `label name`
>
`break` 语句后面带标签名时,可用于终止由这个标签标记的循环语句、`if` 语句或 `switch` 语句的执行。
而只写 `break` 时,则会终止 `switch` 语句或 `break` 语句所属的最内层循环语句的执行。不能使用 `break` 语句来终止未使用标签的 `if` 语句。
无论哪种情况,控制权都会被转移给被终止的控制流语句后面的第一行语句。
关于使用 `break` 语句的例子,请参阅 [控制流](../chapter2/05_Control_Flow.md) 一章的 [Break](../chapter2/05_Control_Flow.md#break) 和 [带标签的语句](../chapter2/05_Control_Flow.md#labeled_statements)。
> break 语句语法
>
>
#### break-statement {#break-statement}
> *break 语句* → **break** [*标签名称*](#label-name)<sub>可选</sub>
>
### Continue 语句 {#continue-statement}
`continue` 语句用于终止循环中当前迭代的执行,但不会终止该循环的执行。使用 `continue` 语句时,可以只写 `continue` 这个关键词,也可以在 `continue` 后面跟上标签名,像下面这样:
> continue
>
> continue `label name`
>
`continue` 语句后面带标签名时,可用于终止由这个标签标记的循环中当前迭代的执行。
而当只写 `continue` 时,可用于终止 `continue` 语句所属的最内层循环中当前迭代的执行。
在这两种情况下,控制权都会被转移给循环语句的条件语句。
`for` 语句中,`continue` 语句执行后,增量表达式还是会被计算,这是因为每次循环体执行完毕后,增量表达式都会被计算。
关于使用 `continue` 语句的例子,请参阅 [控制流](../chapter2/05_Control_Flow.md) 一章的 [Continue](../chapter2/05_Control_Flow.md#continue) 和 [带标签的语句](../chapter2/05_Control_Flow.md#labeled_statements)。
> continue 语句语法
>
>
#### continue-statement {#continue-statement}
> *continue 语句* → **continue** [*标签名称*](#label-name)<sub>可选</sub>
>
### Fallthrough 语句 {#fallthrough-statements}
`fallthrough` 语句用于在 `switch` 语句中转移控制权。`fallthrough` 语句会把控制权从 `switch` 语句中的一个 `case` 转移到下一个 `case`。这种控制权转移是无条件的,即使下一个 `case` 的模式与 `switch` 语句的控制表达式的值不匹配。
`fallthrough` 语句可出现在 `switch` 语句中的任意 `case` 中,但不能出现在最后一个 `case` 中。同时,`fallthrough` 语句也不能把控制权转移到使用了值绑定的 `case`
关于在 `switch` 语句中使用 `fallthrough` 语句的例子,请参阅 [控制流](../chapter2/05_Control_Flow.md) 一章的 [控制转移语句](../chapter2/05_Control_Flow.md#control_transfer_statements)。
> fallthrough 语句语法
>
>
#### fallthrough-statement {#fallthrough-statement}
> *fallthrough 语句* → **fallthrough**
>
### Return 语句 {#return-statements}
`return` 语句用于在函数或方法的实现中将控制权转移到调用函数或方法,接着程序将会从调用位置继续向下执行。
使用 `return` 语句时,可以只写 `return` 这个关键词,也可以在 `return` 后面跟上表达式,像下面这样:
> return
>
> return `expression`
>
`return` 语句后面带表达式时表达式的值将会返回给调用函数或方法。如果表达式的值的类型与函数或者方法声明的返回类型不匹配Swift 则会在返回表达式的值之前将表达式的值的类型转换为返回类型。
> 注意
>
>
> 正如 [可失败构造器](./06_Declarations.md#failable_initializers) 中所描述的,`return nil` 在可失败构造器中用于表明构造失败。
>
而只写 `return` 时,仅仅是从该函数或方法中返回,而不返回任何值(也就是说,函数或方法的返回类型为 `Void` 或者说 `()`)。
> return 语句语法
>
>
#### return-statement {#return-statement}
> *return 语句* → **return** [*表达式*](./04_Expressions.html#expression)<sub>可选</sub>
### Throw 语句 {#throw-statements}
### Throw 语句 {#throw-statements}
`throw` 语句出现在抛出函数或者抛出方法体内,或者类型被 `throws` 关键字标记的闭包表达式体内。
`throw` 语句使程序在当前作用域结束执行,并向外围作用域传播错误。抛出的错误会一直传递,直到被 `do` 语句的 `catch` 子句处理掉。
`throw` 语句由 `throw` 关键字紧跟一个表达式组成,如下所示:
> throw `expression`
>
表达式的结果必须符合 `ErrorType` 协议。
关于如何使用 `throw` 语句的例子,请参阅 [错误处理](../chapter2/17_Error_Handling.md) 一章的 [用 throwing 函数传递错误](../chapter2/17_Error_Handling.md#propagating_errors_using_throwing_functions)。
> throw 语句语法
>
>
#### throw-statement {#throw-statement}
> *throw 语句* → **throw** [*表达式*](./04_Expressions.md#expression)
>
## Defer 语句 {#defer-statements}
`defer` 语句用于在退出当前作用域之前执行代码。
`defer` 语句形式如下:
```swift
defer {
statements
}
```
`defer` 语句中的语句无论程序控制如何转移都会被执行。在某些情况下,例如,手动管理资源时,比如关闭文件描述符,或者即使抛出了错误也需要执行一些操作时,就可以使用 `defer` 语句。
如果多个 `defer` 语句出现在同一作用域内,那么它们执行的顺序与出现的顺序相反。给定作用域中的第一个 `defer` 语句,会在最后执行,这意味着代码中最靠后的 `defer` 语句中引用的资源可以被其他 `defer` 语句清理掉。
```swift
func f() {
defer { print("First") }
defer { print("Second") }
defer { print("Third") }
}
f()
// 打印“Third”
// 打印“Second”
// 打印“First”
```
`defer` 语句中的语句无法将控制权转移到 `defer` 语句外部。
> defer 语句语法
>
>
#### defer-statement {#defer-statement}
> *延迟语句* → **defer** [*代码块*](./06_Declarations.md#code-block)
>
## Do 语句 {#do-statements}
`do` 语句用于引入一个新的作用域,该作用域中可以含有一个或多个 `catch` 子句,`catch` 子句中定义了一些匹配错误条件的模式。`do` 语句作用域内定义的常量和变量只能在 `do` 语句作用域内使用。
Swift 中的 `do` 语句与 C 中限定代码块界限的大括号(`{}`)很相似,也并不会降低程序运行时的性能。
`do` 语句的形式如下:
```swift
do {
try expression
statements
} catch pattern 1 {
statements
} catch pattern 2 where condition {
statements
}
```
如同 `switch` 语句,编译器会判断 `catch` 子句是否有遗漏。如果 `catch` 子句没有遗漏,则认为错误已被处理。否则,错误会自动传递到外围作用域,被某个 `catch` 子句处理掉或者被用 `throws` 关键字声明的抛出函数继续向外抛出。
为了确保错误已经被处理,可以让 `catch` 子句使用匹配所有错误的模式,如通配符模式(`_`)。如果一个 `catch` 子句不指定一种具体模式,`catch` 子句会匹配任何错误,并绑定到名为 `error` 的局部常量。有关在 `catch` 子句中使用模式的更多信息,请参阅 [模式](./08_Patterns.md)。
关于如何在 `do` 语句中使用一系列 `catch` 子句的例子,请参阅 [错误处理](../chapter2/17_Error_Handling.md#handling_errors)。
> do 语句语法
>
>
#### do-statement {#do-statement}
> *do 语句* → **do** [*代码块*](./06_Declarations.md#code-block) [*多条 catch 子句*](#catch-clauses)<sub>可选</sub>
>
#### catch-clauses {#catch-clauses}
> *多条 catch 子句* → [*catch 子句*](#catch-clause) [*多条 catch 子句*](#catch-clauses)<sub>可选</sub>
>
#### catch-clause {#catch-clause}
> *catch 子句* → **catch** [*模式*](./08_Patterns.md#pattern)<sub>可选</sub> [*where 子句*](#where-clause)<sub>可选</sub> [*代码块*](05_Declarations.md#code-block)
>
## 编译器控制语句 {#compiler-control-statements}
编译器控制语句允许程序改变编译器的行为。Swift 有三种编译器控制语句:条件编译语句、线路控制语句和编译时诊断语句。
> 编译器控制语句语法
>
>
#### compiler-control-statement {#compiler-control-statement}
> *编译器控制语句* → [*条件编译语句*](#grammar_conditional-compilation-block)
>
> *编译器控制语句* → [*线路控制语句*](#line-control-statement)
>
> *编译器控制语句* → [*诊断语句*](#grammar_diagnostic-statement)
>
### 条件编译代码块 {#Conditional-Compilation-Block}
条件编译代码块可以根据一个或多个配置来有条件地编译代码。
每一个条件编译代码块都以 `#if` 开始,`#endif` 结束。如下:
```swift
#if compilation condition
statements
#endif
```
`if` 语句的条件不同,编译配置的条件是在编译时进行判断的。只有编译配置在编译时判断为 `true` 的情况下,相应的语句才会被编译和执行。
编译配置可以是 `true``false` 的字面量,也可以是使用 `-D` 命令行标志的标识符,或者是下列表格中的任意一个平台检测函数。
| 函数 | 可用参数 |
| --- | --- |
| `os()` | `OSX`, `iOS`, `watchOS`, `tvOS`, `Linux` |
| `arch()` | `i386`, `x86_64`, `arm`, `arm64` |
| `swift()` | `>=``<` 后跟版本号 |
| `compiler()` | `>=``<` 后跟版本号 |
| `canImport()` | 模块名 |
| `targetEnvironment()` | 模拟器 |
`swift()``compiler()` 之后的版本号包含有主版本号,可选副版本号,可选补丁版本号类似,并且用(`.`)来分隔。在比较符和版本号之间不能有空格,版本号与前面的函数相对应,比如 `compiler()` 对应的就是这个编译器的版本号,`swift()` 对应的就是你要编译的 `Swift` 语言的版本号。举个简单的例子,如果你在使用 `Swift 5` 的编译器,想编译 `Swift 4.2` ,可以看下面的例子:
```swift
#if compiler(>=5)
print("Compiled with the Swift 5 compiler or later")
#endif
#if swift(>=4.2)
print("Compiled in Swift 4.2 mode or later")
#endif
#if compiler(>=5) && swift(<5)
print("Compiled with the Swift 5 compiler or later in a Swift mode earlier than 5")
#endif
// 打印 "Compiled with the Swift 5 compiler or later"
// 打印 "Compiled in Swift 4.2 mode or later"
// 打印 "Compiled with the Swift 5 compiler or later in a Swift mode earlier than 5"
```
`canImport()` 后面跟的变量是模块的名字,这里这个模块可能并不是每个平台上都存在的。使用它来检测是否可以导入这个模块,如果模块存在就返回 `true` 否则返回 `false`
`targetEnvironment()` 当为模拟器编译时返回 `true`,否则返回 `false`
> 注意
>
>
> `arch(arm)` 平台检测函数在 ARM 64 位设备上不会返回 `true`。如果代码在 32 位的 iOS 模拟器上编译,`arch(i386)` 平台检测函数会返回 `true`。
>
你可以使用逻辑操作符 `&&``||``!` 来组合多个编译配置,还可以使用圆括号来进行分组。
就像 `if` 语句一样,你可以使用 `#elseif` 子句来添加任意多个条件分支来测试不同的编译配置。你也可以使用 `#else` 子句来添加最终的条件分支。包含多个分支的编译配置语句例子如下:
```swift
#if compilation condition 1
statements to compile if compilation condition 1 is true
#elseif compilation condition 2
statements to compile if compilation condition 2 is true
#else
statements to compile if both compilation conditions are false
#endif
```
> 注意
>
>
> 即使没有被编译,编译配置中的语句仍然会被解析。然而,唯一的例外是编译配置语句中包含语言版本检测函数:仅当 `Swift` 编译器版本和语言版本检测函数中指定的版本号匹配时,语句才会被解析。这种设定能确保旧的编译器不会尝试去解析新 Swift 版本的语法。
>
#### build-config-statement {#build-config-statement}
> 条件编译代码块语法
>
>
#### grammar_conditional-compilation-block {#grammar-conditional-compilation-block}
> *条件编译代码块* → [*if-directive 语句*](#grammar_if-directive-clause) [*elseif-directive 语句(复数)*](#grammar_elseif-directive-clauses)<sub>可选</sub> [*else-directive 语句*](#grammar_else-directive-clause)<sub>可选</sub> [*endif-directive*](#grammar_endif-directive)
>
#### grammar_if-directive-clause {#grammar-if-directive-clause}
> *if-directive 语句* → [*if-directive*](#grammar_if-directive) [*编译条件*](#compilation-condition) [*语句(复数)*](#statements)<sub>可选</sub>
>
#### grammar_elseif-directive-clauses {#grammar-elseif-directive-clauses}
> *elseif-directive 语句(复数)* → [*elseif-directive 语句*](#grammar_elseif-directive-clause) [*elseif-directive 语句(复数)*](#grammar_elseif-directive-clauses)
>
#### grammar_elseif-directive-clauses {#grammar-elseif-directive-clauses}
> *elseif-directive 语句* → [*elseif-directive*](#grammar_elseif-directive) [*编译条件*](#compilation-condition) [*语句(复数)*](#statements)<sub>可选</sub>
>
#### grammar_else-directive-clause {#grammar-else-directive-clause}
> *else-directive 语句* → [*else-directive*](#grammar_else-directive) [*语句(复数)*](#statements)<sub>可选</sub>
>
> *if-directive* → **#if**
>
> *elseif-directive* → **#elseif**
>
> *else-directive* → **#else**
>
> *endif-directive* → **#endif**
>
#### compilation-condition {#compilation-condition}
> *编译条件* → [*平台条件*](#grammar_platform-condition)
>
> *编译条件* → [*标识符*](./02_Lexical_Structure.md#identifier)
>
> *编译条件* → [*布尔值字面量*](./02_Lexical_Structure.md#boolean-literal)
>
> *编译条件* → **(** [*编译条件*](#compilation-condition) **)**
>
> *编译条件* → **!** [*编译条件*](#compilation-condition)
>
> *编译条件* → [*编译条件*](#compilation-condition) **&&** [*编译条件*](#compilation-condition)
>
> *编译条件* → [*编译条件*](#compilation-condition) **||** [*编译条件*](#compilation-condition)
>
#### grammar_platform-condition {#grammar-platform-condition}
#### grammar_platform-condition-os {#grammar-platform-condition-os}
> *平台条件* → **os ( [*操作系统*](#operating-system) )**
>
#### grammar_platform-condition-arch {#grammar-platform-condition-arch}
> *平台条件* → **arch ( [*架构*](#architecture) )**
>
#### grammar_platform-condition-swift {#grammar-platform-condition-swift}
> *平台条件* → **swift ( >= [*swift 版本*](#swift-version) )** | **swift ( < [*swift 版本*](#swift-version) )**
>
#### grammar_platform-condition-compiler {#grammar-platform-condition-compiler}
> *平台条件* → **compiler ( >= [*swift 版本*](#swift-version) )** | **compiler ( < [*swift 版本*](#swift-version) )**
>
#### grammar_platform-condition-canImport {#grammar-platform-condition-canImport}
> *平台条件* → **canImport ( [*模块名*](#grammar_module-name) )**
>
#### grammar_platform-condition-targetEnvironment {#grammar-platform-condition-targetEnvironment}
> *平台条件* → **targetEnvironment ( [*环境*](#grammar_environment) )**
>
#### operating-system {#operating-system}
> *操作系统* → **macOS** | **iOS** | **watchOS** | **tvOS**
>
#### architecture {#architecture}
> *架构* → **i386** | **x86_64** | **arm** | **arm64**
>
#### swift-version {#swift-version}
> *swift 版本* → [*十进制数字*](./02_Lexical_Structure.md#decimal-digit) ­**.** ­[*swift 版本延续*](#grammar_swift-version-continuation) <sub>可选</sub>
>
#### grammar_swift-version-continuation {#grammar-swift-version-continuation}
> *swift 版本延续* → **.** [*十进制数字*](./02_Lexical_Structure.md#decimal-digit) [*swift 版本延续*](#grammar_swift-version-continuation) <sub>可选</sub>
>
#### grammar_module-name {#grammar-module-name}
> *模块名* → [*identifier*](./02_Lexical_Structure.md#identifier)
>
#### grammar_environment {#grammar-environment}
> *环境* → **模拟器**
>
### 行控制语句 {#line-control-statements}
行控制语句可以为被编译的源代码指定行号和文件名,从而改变源代码的定位信息,以便进行分析和调试。
行控制语句形式如下:
> \#sourceLocation(file: `filename` , line:`line number`)
>
> \#sourceLocation()
>
第一种的行控制语句会改变该语句之后的代码中的字面量表达式 `#line``#file` 所表示的值。`行号` 是一个大于 0 的整形字面量,会改变 `#line` 表达式的值。`文件名` 是一个字符串字面量,会改变 `#file` 表达式的值。
第二种的行控制语句,`#sourceLocation()`,会将源代码的定位信息重置回默认的行号和文件名。
#### line-control-statement {#line-control-statement}
> 行控制语句语法
>
>
> *行控制语句* → **#sourceLocation(file:[*文件名*](#file-name),line:[*行号*](#line-number))**
>
> *行控制语句* → **#sourceLocation()**
>
#### line-number {#line-number}
> *行号* → 大于 0 的十进制整数
>
#### file-name {#file-name}
> *文件名* → [*静态字符串字面量*](./02_Lexical_Structure.md#static-string-literal)
>
### 编译时诊断语句 {#compile-time-diagnostic-statement}
编译时诊断语句允许编译器在编译的时候可以发出错误或者警告。语句形式如下:
```swift
#error("error message")
#warning("warning message")
```
第一句会抛出错误信息并终止编译,第二句会发出警告信息但是编译会继续进行。你可以通过静态字符串字面量来书写诊断信息,静态字符串字面量不能使用字符串 `interpolation` 或者 `concatenation`,但可以使用多行的形式。
> 编译时诊断语句语法
>
>
#### grammar_compile-time-diagnostic-statement {#grammar-compile-time-diagnostic-statement}
> *诊断语句* → **#error** **(** [*diagnostic-message*](#grammar_diagnostic-message) **)**
>
> *诊断语句* → **#warning** **(** [*diagnostic-message*](#grammar_diagnostic-message) **)**
>
> *诊断语句* → [*静态字符串字面量*](./02_Lexical_Structure.md#static-string-literal)
>
## 可用性条件 {#availability-condition}
可用性条件可作为 `if``while``guard` 语句的条件,可以在运行时基于特定的平台参数来查询 API 的可用性。
可用性条件的形式如下:
```swift
if #available(platform name version, ..., *) {
statements to execute if the APIs are available
} else {
fallback statements to execute if the APIs are unavailable
}
```
使用可用性条件来执行一个代码块时,取决于使用的 API 在运行时是否可用,编译器会根据可用性条件提供的信息来决定是否执行相应的代码块。
可用性条件使用一系列逗号分隔的平台名称和版本。使用 `iOS``OSX`,以及 `watchOS` 等作为平台名称,并写上相应的版本号。`*` 参数是必须写的,用于处理未来的潜在平台。可用性条件确保了运行时的平台不低于条件中指定的平台版本时才执行代码块。
与布尔类型的条件不同,不能用逻辑运算符 `&&``||` 组合可用性条件。
> 可用性条件语法
>
>
#### availability-condition {#availability-condition}
> *可用性条件* → **#available** **(** [*可用性参数列表*](#availability-arguments) **)**
>
#### availability-arguments {#availability-arguments}
> *可用性参数列表* → [*可用性参数*](#availability-argument) | [*可用性参数*](#availability-argument) **,** [*可用性参数列表*](#availability-arguments)
>
#### availability-argument {#availability-argument}
> *可用性参数* → [平台名称](#platform-name) [平台版本](#platform-version)
>
> *可用性条件* → __*__
>
>
#### platform-name {#platform-name}
> *平台名称* → **iOS** | **iOSApplicationExtension**
>
> *平台名称* → **OSX** | **macOSApplicationExtension**
>
> *平台名称* → **watchOS**
>
> *平台名称* → **tvOS**
>
#### platform-version {#platform-version}
> *平台版本* → [十进制数字](./02_Lexical_Structure.md#decimal-digits)
>
> *平台版本* → [十进制数字](./02_Lexical_Structure.md#decimal-digits) **.** [十进制数字](./02_Lexical_Structure.md#decimal-digits)
>
> *平台版本* → [十进制数字](./02_Lexical_Structure.md#decimal-digits) **.** [十进制数字](./02_Lexical_Structure.md#decimal-digits) **.** [十进制数字](./02_Lexical_Structure.md#decimal-digits)
>

View File

@ -1,233 +0,0 @@
# 特性Attributes
-----------------
> 1.0
> 翻译:[Hawstein](https://github.com/Hawstein)
> 校对:[numbbbbb](https://github.com/numbbbbb), [stanzhai](https://github.com/stanzhai)
> 2.0
> 翻译+校对:[KYawn](https://github.com/KYawn)
> 2.1
> 翻译:[小铁匠Linus](https://github.com/kevin833752)
本页内容包括:
- [声明特性](#declaration_attributes)
- [Interface Builder 使用的声明特性](#declaration_attributes_used_by_interface_builder)
- [类型特性](#type_attributes)
特性提供了有关声明和类型的更多信息。在Swift中有两种特性分别用于修饰声明和类型。
您可以通过以下方式指定一个特性:符号`@`后跟特性的名称和特性接收的任何参数:
> @ `特性名`
> @ `特性名``特性参数`
有些声明特性通过接收参数来指定特性的更多信息以及它是如何修饰某个特定的声明的。这些特性的参数写在圆括号内,它们的格式由它们所属的特性来定义。
<a name="declaration_attributes"></a>
##声明特性
声明特性只能应用于声明。
`available`
`available` 特性用于声明时,表示该声明的生命周期与特定的平台和操作系统版本有关。
`available` 特性经常与参数列表一同出现,该参数列表至少有两个特性参数,参数之间由逗号分隔。这些参数由以下这些平台名字中的一个起头:
- iOS
- iOSApplicationExtension
- macOS
- macOSApplicationExtension
- watchOS
- watchOSApplicationExtension
- tvOS
- tvOSApplicationExtension
当然,你也可以用一个星号(*)来表示上面提到的所有平台。
其余的参数,可以按照任何顺序出现,并且可以添加关于声明生命周期的附加信息,包括重要事件。
- `unavailable`参数表示该声明在指定的平台上是无效的。
- `introduced` 参数表示指定平台从哪一版本开始引入该声明。格式如下:
`introduced`=`版本号`
*版本号*由一个或多个正整数构成,由句点分隔的。
- `deprecated`参数表示指定平台从哪一版本开始弃用该声明。格式如下:
`deprecated`=`版本号`
可选的*版本号*由一个或多个正整数构成,由句点分隔的。省略版本号表示该声明目前已弃用,当弃用出现时无需给出任何有关信息。如果你省略了版本号,冒号(:)也可省略。
- `obsoleted` 参数表示指定平台从哪一版本开始废弃该声明。当一个声明被废弃后,它就从平台中移除,不能再被使用。格式如下:
`obsoleted`=`版本号`
*版本号*由一个或多个正整数构成,由句点分隔的。
- `message` 参数用来提供文本信息。当使用被弃用或者被废弃的声明时,编译器会抛出警告或错误信息。格式如下:
`message`=`信息内容`
信息内容由一个字符串构成。
- `renamed` 参数用来提供文本信息,用以表示被重命名的声明的新名字。当使用声明的旧名字时,编译器会报错提示新名字。格式如下:
`renamed`=`新名字`
新名字由一个字符串构成。
你可以将`renamed` 参数和 `unavailable` 参数以及类型别名声明组合使用,以此向用户表示某个声明已经被重命名。当某个声明的名字在一个框架或者库的不同发布版本间发生变化时,这会相当有用。
```swift
// 首发版本
protocol MyProtocol {
// 这里是协议定义
}
```
```swift
// 后续版本重命名了 MyProtocol
protocol MyRenamedProtocol {
// 这里是协议定义
}
@available(*, unavailable, renamed:"MyRenamedProtocol")
typealias MyProtocol = MyRenamedProtocol
```
你可以在某个声明上使用多个 `available` 特性,以指定该声明在不同平台上的可用性。编译器只有在当前目标平台和 `available` 特性中指定的平台匹配时,才会使用 `available` 特性。
如果 `available` 特性除了平台名称参数外,只指定了一个 `introduced` 参数,那么可以使用以下简写语法代替:
@available(平台名称 版本号,*
`available` 特性的简写语法可以简明地表达出声明在多个平台上的可用性。尽管这两种形式在功能上是相同的,但请尽可能地使用简写语法形式。
```swift
@available(iOS 10.0, macOS 10.12, *)
class MyClass {
// 这里是类定义
}
```
`discardableResult`
该特性用于的函数或方法声明,以抑制编译器中 函数或方法的返回值被调而没有使用其结果的警告。
`GKInspectable`
应用此属性暴露一个自定义GameplayKit组件属性给SpriteKit编辑器UI。
`objc`
该特性用于修饰任何可以在 Objective-C 中表示的声明。比如,非嵌套类、协议、非泛型枚举(仅限原始值为整型的枚举)、类和协议中的属性和方法(包括存取方法)、构造器、析构器以及下标运算符。`objc` 特性告诉编译器这个声明可以在 Objective-C 代码中使用。
标有 `objc` 特性的类必须继承自 Objective-C 中定义的类。如果你将 `objc` 特性应用于一个类或协议,它也会隐式地应用于类或协议中兼容 Objective-C 的成员。对于标记了 `objc` 特性的类,编译器会隐式地为它的子类添加 `objc` 特性。标记了 `objc` 特性的协议不能继承没有标记 `objc` 的协议。
如果你将 `objc` 特性应用于枚举,每一个枚举用例都会以枚举名称和用例名称组合的方式暴露在 Objective-C 代码中。例如,在 `Planet` 枚举中有一个名为 `Venus` 的用例,该用例暴露在 Objective-C 代码中时叫做 `PlanetVenus`
`objc` 特性有一个可选的参数,由标识符构成。当你想把 objc 所修饰的实体以一个不同的名字暴露给 Objective-C 时,你就可以使用这个特性参数。你可以使用这个参数来命名类、枚举类型、枚举用例、协议、方法、存取方法以及构造器。下面的例子把 `ExampleClass` 中的 `enabled` 属性的取值方法暴露给 Objective-C名字是 `isEnabled`,而不是它原来的属性名。
```swift
@objc
class ExampleClass: NSObject {
var enabled: Bool {
@objc(isEnabled) get {
// 返回适当的值 }
}
}
```
`nonobjc`
该特性用于方法、属性、下标、或构造器的声明,这些声明本可以在 Objective-C 代码中使用,而使用 `nonobjc` 特性则告诉编译器这个声明不能在 Objective-C 代码中使用。
可以使用 `nonobjc` 特性解决标有 `objc` 的类中桥接方法的循环问题,该特性还允许对标有 `objc` 的类中的构造器和方法进行重载。
标有 `nonobjc` 特性的方法不能重写标有 `objc` 特性的方法。然而,标有 `objc` 特性的方法可以重写标有 `nonobjc` 特性的方法。同样,标有 `nonobjc` 特性的方法不能满足标有 `@objc` 特性的协议中的方法要求。
`NSApplicationMain`
在类上使用该特性表示该类是应用程序委托类,使用该特性与调用 `NSApplicationMain`(\_:_:) 函数并且把该类的名字作为委托类的名字传递给函数的效果相同。
如果你不想使用这个特性,可以提供一个 main.swift 文件,并在代码**顶层**调用`NSApplicationMain`(\_:_:) 函数,如下所示:
```swift
import AppKit
NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
```
`NSCopying`
该特性用于修饰一个类的存储型变量属性。该特性将使属性的设值方法使用传入值的副本进行赋值,这个值由传入值的 `copyWithZone`(\_:) 方法返回。该属性的类型必需符合 `NSCopying` 协议。
`NSCopying` 特性的行为与 Objective-C 中的 `copy` 特性相似。
`NSManaged`
该特性用于修饰 `NSManagedObject` 子类中的实例方法或存储型变量属性,表明它们的实现由 `Core Data` 在运行时基于相关实体描述动态提供。对于标记了 `NSManaged` 特性的属性,`Core Data` 也会在运行时为其提供存储。应用这个特性也意味着`objc`特性。
`testable`
在导入允许测试的编译模块时,该特性用于修饰 `import` 声明,这样就能访问被导入模块中的任何标有 `internal` 访问级别修饰符的实体,犹如它们被标记了 `public` 访问级别修饰符。测试也可以访问使用`internal`或者`public`访问级别修饰符标记的类和类成员,就像它们是`open`访问修饰符声明的。
`UIApplicationMain`
在类上使用该特性表示该类是应用程序委托类,使用该特性与调用 `UIApplicationMain`函数并且把该类的名字作为委托类的名字传递给函数的效果相同。
如果你不想使用这个特性,可以提供一个 main.swift 文件,并在代码顶层调用 `UIApplicationMain`(\_:\_:\_:) 函数。比如,如果你的应用程序使用一个继承于 UIApplication 的自定义子类作为主要类,你可以调用 `UIApplicationMain`(\_:\_:\_:) 函数而不是使用该特性。
<a name="declaration_attributes_used_by_interface_builder"></a>
###Interface Builder 使用的声明特性
`Interface Builder` 特性是 `Interface Builder` 用来与 Xcode 同步的声明特性。`Swift` 提供了以下的 `Interface Builder` 特性:`IBAction``IBOutlet``IBDesignable`,以及`IBInspectable` 。这些特性与 Objective-C 中对应的特性在概念上是相同的。
`IBOutlet``IBInspectable` 用于修饰一个类的属性声明,`IBAction` 特性用于修饰一个类的方法声明,`IBDesignable` 用于修饰类的声明。
`IBAction``IBOutlet` 特性都意味着`objc`特性。
<a name="type_attributes"></a>
##类型特性
类型特性只能用于修饰类型。
`autoclosure`
这个特性通过把表达式自动封装成无参数的闭包来延迟表达式的计算。它可以修饰类型为返回表达式结果类型的无参数函数类型的函数参数。关于如何使用 autoclosure 特性的例子,请参阅 [自动闭包](http://wiki.jikexueyuan.com/project/swift/chapter2/07_Closures.html/) 和 [函数类型](http://wiki.jikexueyuan.com/project/swift/chapter3/03_Types.html)。
`convention`
该特性用于修饰函数类型,它指出了函数调用的约定。
convention 特性总是与下面的参数之一一起出现。
- `swift` 参数用于表示一个 Swift 函数引用。这是 Swift 中函数值的标准调用约定。
- `block` 参数用于表示一个 Objective-C 兼容的块引用。函数值会作为一个块对象的引用,块是一种 `id` 兼容的 Objective-C 对象,其中嵌入了调用函数。调用函数使用 C 的调用约定。
- `c` 参数用于表示一个 C 函数引用。函数值没有上下文,不具备捕获功能,同样使用 C 的调用约定。
使用 C 函数调用约定的函数也可用作使用 Objective-C 块调用约定的函数,同时使用 Objective-C 块调用约定的函数也可用作使用 Swift 函数调用约定的函数。然而,只有非泛型的全局函数、局部函数以及未捕获任何局部变量的闭包,才可以被用作使用 C 函数调用约定的函数。
`escaping`
在函数或者方法声明上使用该特性,它表示参数将不会被存储以供延迟执行,这将确保参数不会超出函数调用的生命周期。在使用 `escaping` 声明特性的函数类型中访问属性和方法时不需要显式地使用 `self.`。关于如何使用 `escaping` 特性的例子,请参阅 [逃逸闭包](http://wiki.jikexueyuan.com/project/swift/chapter2/07_Closures.html)。
>特性语法
> *特性 *→ @ <font color = 0x3386c8>特性名 特性参数子句</font><sub>可选</sub>
> *特性名* → <font color = 0x3386c8>标识符
> *特性参数子句* → ( <font color = 0x3386c8>均衡令牌列表</font><sub>可选</sub> )
> *特性列表* → <font color = 0x3386c8>特性 特性列表</font><sub>可选</sub>
> *均衡令牌列表* → <font color = 0x3386c8>均衡令牌 均衡令牌列表</font><sub>可选</sub>
> *均衡令牌* → ( <font color = 0x3386c8>均衡令牌列表</font><sub>可选</sub> )
> *均衡令牌* → [ <font color = 0x3386c8>均衡令牌列表</font><sub>可选</sub> ]
> *均衡令牌* → { <font color = 0x3386c8>均衡令牌列表</font><sub>可选</sub>}
> *均衡令牌* → 任意标识符,关键字,字面量或运算符
> *均衡令牌* → 任意标点除了 ()[]{,或 }

1808
source/chapter3/06_Declarations.md Executable file

File diff suppressed because it is too large Load Diff

419
source/chapter3/07_Attributes.md Executable file
View File

@ -0,0 +1,419 @@
# 特性Attributes
在 Swift 中有两种特性,分别用于修饰声明和类型。特性提供了有关声明和类型的更多信息。例如,使用 `discardableResult` 特性声明的函数,表明该函数虽然有返回值,但如果没有使用该返回值,编译器不会产生警告。
您可以通过以下方式指定一个特性,通过符号 `@` 后跟特性的名称和特性接收的任何参数:
@`特性名`
@`特性名`(`特性参数`)
有些声明特性通过接收参数来指定特性的更多信息以及它是如何修饰某个特定的声明的。这些_特性的参数_写在圆括号内它们的格式由它们所属的特性来定义。
## 声明特性 {#declaration-attributes}
声明特性只能应用于声明。
### `available` {#available}
`available` 特性用于声明时,表示该声明的生命周期是相对于特定的平台和操作系统版本。
`available` 特性经常与参数列表一同出现,该参数列表至少有两个特性参数,参数之间由逗号分隔。这些参数由以下这些平台名字中的一个起头:
- `iOS`
- `iOSApplicationExtension`
- `macOS`
- `macOSApplicationExtension`
- `watchOS`
- `watchOSApplicationExtension`
- `tvOS`
- `tvOSApplicationExtension`
- `swift`
当然,你也可以用一个星号(`*`)来表示上面提到的所有平台。指定 Swift 版本的 `available` 特性参数,不能使用星号表示。
其余的参数,可以按照任何顺序出现,并且可以添加关于声明生命周期的附加信息,包括重要事件。
- `unavailable` 参数表示该声明在指定的平台上是无效的。当指定 Swift 版本可用性时不可使用该参数。
- `introduced` 参数表示指定平台从哪一版本开始引入该声明。格式如下:
`introduced`: `版本号`
*版本号*由一至三个正整数构成,由句点分隔的。
- `deprecated` 参数表示指定平台从哪一版本开始弃用该声明。格式如下:
`deprecated`: `版本号`
可选的*版本号*由一个或多个正整数构成,由句点分隔的。省略版本号表示该声明目前已弃用,当弃用出现时没有给出任何有关信息。如果你省略了版本号,冒号(`:`)也可省略。
- `obsoleted` 参数表示指定平台或语言从哪一版本开始废弃该声明。当一个声明被废弃后,它就从平台或语言中移除,不能再被使用。格式如下:
`obsoleted`: `版本号`
*版本号*由一至三个正整数构成,由句点分隔的。
- `message` 参数用来提供文本信息。当使用被弃用或者被废弃的声明时,编译器会抛出警告或错误信息。格式如下:
`message`: `信息内容`
_信息内容_由一个字符串构成。
- `renamed` 参数用来提供文本信息,用以表示被重命名的声明的新名字。当使用声明的旧名字时,编译器会报错提示新名字。格式如下:
`renamed`: `新名字`
_新名字_由一个字符串构成。
你可以将 `renamed` 参数和 `unavailable` 参数用于 `available` 特性,来表示声明在不同平台和 Swift 版本上的可用性。如下所示,表示声明的名字在一个框架或者库的不同发布版本间发生了变化。以此组合表示该声明被重命名的编译错误。
```swift
// 首发版本
protocol MyProtocol {
// 这里是协议定义
}
```
```swift
// 后续版本重命名了 MyProtocol
protocol MyRenamedProtocol {
// 这里是协议定义
}
@available(*, unavailable, renamed:"MyRenamedProtocol")
typealias MyProtocol = MyRenamedProtocol
```
你可以在某个声明上使用多个 `available` 特性,以指定该声明在不同平台和 Swift 版本上的可用性。编译器只有在与 `available` 特性中指定的平台或语言版本匹配时,才会使用 `available` 特性。
如果 `available` 特性除了平台名称参数外,只指定了一个 `introduced` 参数,那么可以使用以下简写语法代替:
@available(`平台名称` `版本号`, *)
@available(swift `版本号`)
`available` 特性的简写语法可以简明地表达出声明在多个平台上的可用性。尽管这两种形式在功能上是相同的,但请尽可能地使用简写语法形式。
```swift
@available(iOS 10.0, macOS 10.12, *)
class MyClass {
// 这里是类定义
}
```
`available` 特性需要同时指定 Swift 版本和平台可用性,需要使用单独的 `available` 特性来声明。
```swift
@available(swift 3.0.2)
@available(macOS 10.12, *)
struct MyStruct {
// 这里是结构体定义
}
```
### `discardableResult` {#discardableresult}
该特性用于的函数或方法声明,以抑制编译器中函数或方法的返回值被调而没有使用其结果的警告。
### `dynamicCallable` {#dynamiccallable}
该特性用于类、结构体、枚举或协议,以将该类型的实例视为可调用的函数。该类型必须实现 `dynamicallyCall(withArguments:)``dynamicallyCall(withKeywordArguments:)` 方法之一,或两者同时实现。
你可以调用 `dynamicCallable` 特性的实例,就像是调用一个任意数量参数的函数。
```swift
@dynamicCallable
struct TelephoneExchange {
func dynamicallyCall(withArguments phoneNumber: [Int]) {
if phoneNumber == [4, 1, 1] {
print("Get Swift help on forums.swift.org")
} else {
print("Unrecognized number")
}
}
}
let dial = TelephoneExchange()
// 使用动态方法调用
dial(4, 1, 1)
// 打印“Get Swift help on forums.swift.org”
dial(8, 6, 7, 5, 3, 0, 9)
// 打印“Unrecognized number”
// 直接调用底层方法
dial.dynamicallyCall(withArguments: [4, 1, 1])
```
`dynamicallyCall(withArguments:)` 方法的声明必须至少有一个参数遵循 [`ExpressibleByArrayLiteral`](https://developer.apple.com/documentation/swift/expressiblebyarrayliteral) 协议,如 `[Int]`,而返回值类型可以是任意类型。
```swift
@dynamicCallable
struct Repeater {
func dynamicallyCall(withKeywordArguments pairs: KeyValuePairs<String, Int>) -> String {
return pairs
.map { label, count in
repeatElement(label, count: count).joined(separator: " ")
}
.joined(separator: "\n")
}
}
let repeatLabels = Repeater()
print(repeatLabels(a: 1, b: 2, c: 3, b: 2, a: 1))
// a
// b b
// c c c
// b b
// a
```
`dynamicallyCall(withKeywordArguments:)` 方法声明必须至少有一个参数遵循 [`ExpressibleByDictionaryLiteral`](https://developer.apple.com/documentation/swift/expressiblebydictionaryliteral) 协议,返回值可以任意类型。参数的 [`Key`](https://developer.apple.com/documentation/swift/expressiblebydictionaryliteral/2294108-key) 必须遵循 [`ExpressibleByStringLiteral`](https://developer.apple.com/documentation/swift/expressiblebystringliteral) 协议。上述的示例使用 [`KeyValuePairs`](https://developer.apple.com/documentation/swift/keyvaluepairs) 作为参数类型,以便调用者可以传入重复的参数标签,`a``b` 在调用 `repeat`中多次使用。
如果你同时实现两种 `dynamicallyCall` 方法,则当在方法调用中包含关键字参数时,会调用 `dynamicallyCall(withKeywordArguments:)` 方法,否则调用 `dynamicallyCall(withArguments:)` 方法。
你只能调用参数和返回值与 `dynamicallyCall` 方法实现匹配的动态调用实例。在下面示例的调用无法编译,因为其 `dynamicallyCall(withArguments:)` 实现不接受 `KeyValuePairs<String, String>` 参数。
```swift
repeatLabels(a: "four") // Error
```
### `dynamicMemberLookup` {#dynamicmemberlookup}
该特性用于类、结构体、枚举或协议,让其能在运行时查找成员。该类型必须实现 `subscript(dynamicMemberLookup:)` 下标。
在显式成员表达式中,如果没有成名指定成员,则该表达式被理解为对该类型的 `subscript(dynamicMemberLookup:)` 下标的调用,传递包含成员名称字符串的参数。下标接收参数既可以是键路径,也可以是成员名称字符串;如果你同时实现这两种方式的下标调用,那么以键路径参数方式为准。
`subscript(dynamicMemberLookup:)` 实现允许接收 [`KeyPath`](https://developer.apple.com/documentation/swift/keypath)[`WritableKeyPath`](https://developer.apple.com/documentation/swift/writablekeypath) 或 [`ReferenceWritableKeyPath`](https://developer.apple.com/documentation/swift/referencewritablekeypath) 类型的键路径参数。而遵循 [`ExpressibleByStringLiteral`](https://developer.apple.com/documentation/swift/expressiblebystringliteral) 协议,下标调用接收参数为成员名称字符串 —— 在大多数情况下,下标的参数是一个 `String` 值。下标返回值类型可以为任意类型。
根据成员名称来动态地查找成员,可以帮助我们创建一个包裹数据的包装类型,但该类型无法在编译时进行类型检查,例如其他语言的数据桥接到 Swift 语言时。例如:
```swift
@dynamicMemberLookup
struct DynamicStruct {
let dictionary = ["someDynamicMember": 325,
"someOtherMember": 787]
subscript(dynamicMember member: String) -> Int {
return dictionary[member] ?? 1054
}
}
let s = DynamicStruct()
// 使用动态成员查找
let dynamic = s.someDynamicMember
print(dynamic)
// 打印“325”
// 直接调用底层下标
let equivalent = s[dynamicMember: "someDynamicMember"]
print(dynamic == equivalent)
// 打印“true”
```
根据键路径来动态地查找成员,可用于创建一个包裹数据的包装类型,该类型在编译时期进行类型检查。例如:
```swift
struct Point { var x, y: Int }
@dynamicMemberLookup
struct PassthroughWrapper<Value> {
var value: Value
subscript<T>(dynamicMember member: KeyPath<Value, T>) -> T {
get { return value[keyPath: member] }
}
}
let point = Point(x: 381, y: 431)
let wrapper = PassthroughWrapper(value: point)
print(wrapper.x)
```
### `GKInspectable` {#gkinspectable}
应用此属性,暴露一个自定义 GameplayKit 组件属性给 SpriteKit 编辑器 UI。
### `inlinable` {#inlinable}
该特性用于函数、方法、计算属性、下标、便利构造器或析构器的声明,以将该声明的实现公开为模块公开接口的一部分。编译器允许在调用处把 `inlinable` 标记的符号替换为符号实现的副本。
内联代码可以与任意模块中 `public` 访问级别的符号进行交互,同时可以与在相同模块中标记 `usableFromInline` 特性的 `internal` 访问级别的符号进行交互。内联代码不能与 `private``fileprivate` 级别的符号进行交互。
该特性不能用于嵌套在函数内的声明,也不能用于 `fileprivate``private` 访问级别的声明。在内联函数定义的函数和闭包是隐式非内联的,即使他们不能标记该特性。
### `nonobjc` {#nonobjc}
该特性用于方法、属性、下标、或构造器的声明,这些声明本可以在 Objective-C 代码中使用,而使用 `nonobjc` 特性则告诉编译器这个声明不能在 Objective-C 代码中使用。
该特性用在扩展中,与在没有明确标记为 `objc` 特性的扩展中给每个成员添加该特性具有相同效果。
可以使用 `nonobjc` 特性解决标有 `objc` 的类中桥接方法的循环问题,该特性还允许对标有 `objc` 的类中的构造器和方法进行重载。
标有 `nonobjc` 特性的方法不能重写标有 `objc` 特性的方法。然而,标有 `objc` 特性的方法可以重写标有 `nonobjc` 特性的方法。同样,标有 `nonobjc` 特性的方法不能满足标有 `@objc` 特性的协议中的方法要求。
### `NSApplicationMain` {#nsapplicationmain}
在类上使用该特性表示该类是应用程序委托类,使用该特性与调用 `NSApplicationMain(_:_:)` 函数并且把该类的名字作为委托类的名字传递给函数的效果相同。
如果你不想使用这个特性,可以提供一个 `main.swift` 文件,并在代码**顶层**调用 `NSApplicationMain(_:_:)` 函数,如下所示:
```swift
import AppKit
NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
```
### `NSCopying` {#nscopying}
该特性用于修饰一个类的存储型变量属性。该特性将使属性的设值方法使用传入值的副本进行赋值,这个值由传入值的 `copyWithZone(_:)` 方法返回。该属性的类型必需符合 `NSCopying` 协议。
`NSCopying` 特性的行为与 Objective-C 中的 `copy` 特性相似。
### `NSManaged` {#nsmanaged}
该特性用于修饰 `NSManagedObject` 子类中的实例方法或存储型变量属性,表明它们的实现由 `Core Data` 在运行时基于相关实体描述动态提供。对于标记了 `NSManaged` 特性的属性,`Core Data` 也会在运行时为其提供存储。应用这个特性也意味着 `objc` 特性。
### `objc` {#objc}
该特性用于修饰任何可以在 Objective-C 中表示的声明。比如,非嵌套类、协议、非泛型枚举(仅限原始值为整型的枚举)、类和协议中的属性和方法(包括存取方法)、构造器、析构器以及下标运算符。`objc` 特性告诉编译器这个声明可以在 Objective-C 代码中使用。
该特性用在扩展中,与在没有明确标记为 `nonobjc` 特性的扩展中给每个成员添加该特性具有相同效果。
编译器隐式地将 `objc` 特性添加到 Objective-C 中定义的任何类的子类。但是,子类不能是泛型的,并且不能继承于任何泛型类。你可以将 `objc` 特性显式添加到满足这些条件的子类,来指定其 Objective-C 名称,如下所述。添加了 `objc` 的协议不能继承于没有此特性的协议。
在以下情况中隐式添加了 `objc` 特性。
- 父类有 `objc` 特性,且重写为子类的声明。
- 遵循带有 `objc` 特性协议的声明。
- 带有 `IBAction``IBSegueAction``IBOutlet``IBDesignable``IBInspectable``NSManaged``GKInspectable` 特性的声明。
如果你将 `objc` 特性应用于枚举,每一个枚举用例都会以枚举名称和用例名称组合的方式暴露在 Objective-C 代码中。例如,在 `Planet` 枚举中有一个名为 `Venus` 的用例,该用例暴露在 Objective-C 代码中时叫做 `PlanetVenus`
`objc` 特性有一个可选的参数,由标识符构成。当你想把 `objc` 所修饰的实体以一个不同的名字暴露给 Objective-C 时,你就可以使用这个特性参数。你可以使用这个参数来命名类、枚举类型、枚举用例、协议、方法、存取方法以及构造器。如果你要指定类、协议或枚举在 Objective-C 下的名称,在名称上包含三个字母的前缀,如 [Objective-C 编程](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/Introduction/Introduction.html#//apple_ref/doc/uid/TP40011210) 中的 [约定](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/Conventions/Conventions.html#//apple_ref/doc/uid/TP40011210-CH10-SW1)。下面的例子把 `ExampleClass` 中的 `enabled` 属性的取值方法暴露给 Objective-C名字是 `isEnabled`,而不是它原来的属性名。
```swift
class ExampleClass: NSObject {
@objc var enabled: Bool {
@objc(isEnabled) get {
// 返回适当的值
}
}
}
```
### `objcMembers` {#objcmembers}
该特性用于类声明,以将 `objc` 特性应用于该类、扩展、子类以及子类的扩展的所有 Objective-C 兼容成员。
大多数代码应该使用 `objc` 特性,以暴露所需的声明。如果需要暴露多个声明,可以将其分组到添加 `objc` 特性的扩展中。`objcMembers` 特性为大量使用 Objective-C 运行时的内省工具的库提供了便利。添加不必要的 `objc` 特性会增加二进制体积并影响性能。
### `requires_stored_property_inits` {#requires-stored-property-inits}
该特性用于类声明,以要求类中所有存储属性提供默认值作为其定义的一部分。对于从中继承的任何类都推断出 `NSManagedObject` 特性。
### `testable` {#testable}
在导入允许测试的编译模块时,该特性用于修饰 `import` 声明,这样就能访问被导入模块中的任何标有 `internal` 访问级别修饰符的实体,犹如它们被标记了 `public` 访问级别修饰符。测试也可以访问使用 `internal` 或者 `public` 访问级别修饰符标记的类和类成员,就像它们是 `open` 访问修饰符声明的。
### `UIApplicationMain` {#uiapplicationmain}
在类上使用该特性表示该类是应用程序委托类,使用该特性与调用 `UIApplicationMain` 函数并且把该类的名字作为委托类的名字传递给函数的效果相同。
如果你不想使用这个特性,可以提供一个 `main.swift` 文件,并在代码顶层调用 `UIApplicationMain(_:_:_:_:)` 函数。比如,如果你的应用程序使用一个继承于 UIApplication 的自定义子类作为主要类,你可以调用 `UIApplicationMain(_:_:_:_:)` 函数而不是使用该特性。
### `usableFromInline` {#usablefrominline}
该特性用于函数、方法、计算属性、下标、构造器或析构器的声明,以在同一模块中允许该符号用于内联代码的声明。声明必须具有 `internal` 访问级别修饰符。
`public` 访问修饰符相同的是,该特性将声明公开为模块公共接口的一部分。区别于 `public`,编译器不允许在模块外部的代码通过名称引用 `usableFromInline` 标记的声明,即使导出了声明符号也是无法引用。但是,模块外的代码仍然可以通过运行时交换声明符号。
标记为 `inlinable` 特性的声明,在内联代码中可以隐式使用。虽然 `inlinable``usableFromInline` 可以用于 `internal` 声明,但这两者不能同时使用。
### `warn_unqualified_access` {#warn-unqualified-access}
该特性应用于顶级函数、实例方法、类方法或静态方法,以在没有前置限定符(例如模块名称、类型名称、实例变量或常量)的情况下使用该函数或方法时触发警告。使用该特性可以帮助减少在同一作用于访问同名函数之间的歧义。
例如Swift 标准库包含 [`min(_:_:)`](https://developer.apple.com/documentation/swift/1538339-min/) 顶级函数和用于序列比较元素的 [`min()`](https://developer.apple.com/documentation/swift/sequence/1641174-min) 方法。序列方法声明使用了 `warn_unqualified_access`,以减少在 `Sequence` 扩展中使用它们的歧义。
### Interface Builder 使用的声明特性 {#declaration-attributes-used-by-interface-builder}
`Interface Builder` 特性是 `Interface Builder` 用来与 Xcode 同步的声明特性。`Swift` 提供了以下的 `Interface Builder` 特性:`IBAction``IBSegueAction``IBOutlet``IBDesignable`,以及 `IBInspectable` 。这些特性与 Objective-C 中对应的特性在概念上是相同的。
`IBOutlet``IBInspectable` 用于修饰一个类的属性声明,`IBAction` 特性用于修饰一个类的方法声明,`IBDesignable` 用于修饰类的声明。
应用 `IBAction``IBSegueAction``IBOutlet``IBDesignable` 或者 `IBInspectable` 特性都意味着同时应用 `objc` 特性。
## 类型特性 {#type-attributes}
类型特性只能用于修饰类型。
### `autoclosure` {#autoclosure}
这个特性通过把表达式自动封装成无参数的闭包来延迟表达式的计算。它可以修饰类型为返回表达式结果类型的无参数函数类型的函数参数。关于如何使用 autoclosure 特性的例子,请参阅 [自动闭包](../chapter2/07_Closures.md#autoclosures) 和 [函数类型](./03_Types.md#function_type)。
### `convention` {#convention}
该特性用于修饰函数类型,它指出了函数调用的约定。
convention 特性总是与下面的参数之一一起出现。
- `swift` 参数用于表示一个 Swift 函数引用。这是 Swift 中函数值的标准调用约定。
- `block` 参数用于表示一个 Objective-C 兼容的块引用。函数值会作为一个块对象的引用,块是一种 `id` 兼容的 Objective-C 对象,其中嵌入了调用函数。调用函数使用 C 的调用约定。
- `c` 参数用于表示一个 C 函数引用。函数值没有上下文,不具备捕获功能,同样使用 C 的调用约定。
使用 C 函数调用约定的函数也可用作使用 Objective-C 块调用约定的函数,同时使用 Objective-C 块调用约定的函数也可用作使用 Swift 函数调用约定的函数。然而,只有非泛型的全局函数、局部函数以及未捕获任何局部变量的闭包,才可以被用作使用 C 函数调用约定的函数。
### `escaping` {#escaping}
在函数或者方法声明上使用该特性,它表示参数将不会被存储以供延迟执行,这将确保参数不会超出函数调用的生命周期。在使用 `escaping` 声明特性的函数类型中访问属性和方法时不需要显式地使用 `self.`。关于如何使用 `escaping` 特性的例子,请参阅 [逃逸闭包](../chapter2/07_Closures.md#escaping_closures)。
## Switch Case 特性 {#switch-case-attributes}
你只能在 switch cases 中使用 switch case 特性。
### `unknown` {#unknown}
次特性用于 switch case表示在编译时该地方不会匹配枚举的任何情况。有关如何使用 `unknown` 特性的示例,可参阅 [对未来枚举的 `case` 进行 `switch`](./05_Statements.md#future-case)。
> 特性语法
>
>
>
#### attribute {#attribute}
>
> *特性*→ [特性名](#attribute_name) [特性参数子句](#atribute_argument_clause)<sub>可选</sub>
>
>
#### attribute_name {#attribute-name}
>
> *特性名* → [标识符](./02_Lexical_Structure.md#identifier)
>
>
#### atribute_argument_clause {#atribute-argument-clause}
>
> *特性参数子句* → **(** [均衡令牌列表](#balanced_tokens)<sub>可选</sub> **)**
>
>
#### attributes {#attributes}
>
> *特性列表* → [特性](#attribute) [特性列表](#attributes)<sub>可选</sub>
>
>
>
#### balanced_tokens {#balanced-tokens}
>
> *均衡令牌列表* → [均衡令牌](#balanced_token) [均衡令牌列表](#balanced_tokens)<sub>可选</sub>
>
>
#### balanced_token {#balanced-token}
>
> *均衡令牌* → **(** [均衡令牌列表](#balanced_tokens)<sub>可选</sub> **)**
>
> *均衡令牌* → **\[** [均衡令牌列表](#balanced_tokens)<sub>可选</sub> **\]**
>
> *均衡令牌* → **{** [均衡令牌列表](#balanced_tokens)<sub>可选</sub> **}**
>
> *均衡令牌* → 任意标识符,关键字,字面量或运算符
>
> *均衡令牌* → 任意标点除了 **(****)****[****]****{**,或 **}**
>

View File

@ -1,241 +0,0 @@
# 模式Patterns
-----------------
> 1.0
> 翻译:[honghaoz](https://github.com/honghaoz)
> 校对:[numbbbbb](https://github.com/numbbbbb), [stanzhai](https://github.com/stanzhai)
> 2.0
> 翻译+校对:[ray16897188](https://github.com/ray16897188),
> 2.1
> 翻译:[BridgeQ](https://github.com/WXGBridgeQ)
本页内容包括:
- [通配符模式Wildcard Pattern](#wildcard_pattern)
- [标识符模式Identifier Pattern](#identifier_pattern)
- [值绑定模式Value-Binding Pattern](#value-binding_pattern)
- [元组模式Tuple Pattern](#tuple_pattern)
- [枚举用例模式Enumeration Case Pattern](#enumeration_case_pattern)
- [可选模式Optional Pattern](#optional_pattern)
- [类型转换模式Type-Casting Pattern](#type-casting_patterns)
- [表达式模式Expression Pattern](#expression_pattern)
模式代表单个值或者复合值的结构。例如,元组 `(1, 2)` 的结构是由逗号分隔的,包含两个元素的列表。因为模式代表一种值的结构,而不是特定的某个值,你可以利用模式来匹配各种各样的值。比如,`(x, y)` 可以匹配元组 `(1, 2)`,以及任何含两个元素的元组。除了利用模式匹配一个值以外,你可以从复合值中提取出部分或全部值,然后分别把各个部分的值和一个常量或变量绑定起来。
Swift 中的模式分为两类:一种能成功匹配任何类型的值,另一种在运行时匹配某个特定值时可能会失败。
第一类模式用于解构简单变量、常量和可选绑定中的值。此类模式包括通配符模式、标识符模式,以及包含前两种模式的值绑定模式和元组模式。你可以为这类模式指定一个类型标注,从而限制它们只能匹配某种特定类型的值。
第二类模式用于全模式匹配,这种情况下你试图匹配的值在运行时可能不存在。此类模式包括枚举用例模式、可选模式、表达式模式和类型转换模式。你在 `switch` 语句的 `case` 标签中,`do` 语句的 `catch` 子句中,或者在 `if``while``guard``for-in` 语句的 `case` 条件句中使用这类模式。
> 模式语法
<a name="pattern"></a>
> *模式* → [*通配符模式*](#wildcard_pattern) [*类型标注*](03_Types.md#type-annotation)<sub>可选</sub>
> *模式* → [*标识符模式*](#identifier_pattern) [*类型标注*](03_Types.md#type-annotation)<sub>可选</sub>
> *模式* → [*值绑定模式*](#value-binding-pattern)
> *模式* → [*元组模式*](#tuple-pattern) [*类型标注*](03_Types.md#type-annotation)<sub>可选</sub>
> *模式* → [*枚举用例模式*](#enum-case-pattern)
> *模式* → [*可选模式*](#optional-pattern)
> *模式* → [*类型转换模式*](#type-casting-pattern)
> *模式* → [*表达式模式*](#expression-pattern)
<a name="wildcard_pattern"></a>
## 通配符模式Wildcard Pattern
通配符模式由一个下划线(`_`)构成,用于匹配并忽略任何值。当你想忽略被匹配的值时可以使用该模式。例如,下面这段代码在闭区间 `1...3` 中迭代,每次迭代都忽略该区间的当前值:
```swift
for _ in 1...3 {
// ...
}
```
> 通配符模式语法
<a name="wildcard-pattern"></a>
> *通配符模式* → **_**
<a name="identifier_pattern"></a>
## 标识符模式Identifier Pattern
标识符模式匹配任何值,并将匹配的值和一个变量或常量绑定起来。例如,在下面的常量声明中,`someValue` 是一个标识符模式,匹配了 `Int` 类型的 `42`
```swift
let someValue = 42
```
当匹配成功时,`42` 被绑定(赋值)给常量 `someValue`
如果一个变量或常量声明的左边是一个标识符模式,那么这个标识符模式是值绑定模式的子模式。
> 标识符模式语法
<a name="identifier-pattern"></a>
> *标识符模式* → [*标识符*](02_Lexical_Structure.md#identifier)
<a name="value-binding_pattern"></a>
## 值绑定模式Value-Binding Pattern
值绑定模式把匹配到的值绑定给一个变量或常量。把匹配到的值绑定给常量时,用关键字 `let`,绑定给变量时,用关键字 `var`
在值绑定模式中的标识符模式会把新命名的变量或常量与匹配到的值做绑定。例如,你可以拆开一个元组,然后把每个元素绑定到相应的标识符模式中。
```swift
let point = (3, 2)
switch point {
// 将 point 中的元素绑定到 x 和 y
case let (x, y):
print("The point is at (\(x), \(y)).")
}
// 打印 “The point is at (3, 2).”
```
在上面这个例子中,`let` 会分配到元组模式 `(x, y)` 中的各个标识符模式。因此,`switch` 语句中 `case let (x, y):``case (let x, let y):` 的匹配效果是一样的。
> 值绑定模式语法
<a name="value-binding-pattern"></a>
> *值绑定模式* → **var** [*模式*](#pattern) | **let** [*模式*](#pattern)
<a name="tuple_pattern"></a>
## 元组模式
元组模式是由逗号分隔的,具有零个或多个模式的列表,并由一对圆括号括起来。元组模式匹配相应元组类型的值。
你可以使用类型标注去限制一个元组模式能匹配哪种元组类型。例如,在常量声明 `let (x, y): (Int, Int) = (1, 2)` 中的元组模式 `(x, y): (Int, Int)` 只匹配两个元素都是 `Int` 类型的元组。
当元组模式被用于 `for-in` 语句或者变量和常量声明时,它仅可以包含通配符模式、标识符模式、可选模式或者其他包含这些模式的元组模式。比如下面这段代码就不正确,因为 `(x, 0)` 中的元素 `0` 是一个表达式模式:
```swift
let points = [(0, 0), (1, 0), (1, 1), (2, 0), (2, 1)]
// 下面的代码是错误的
for (x, 0) in points {
/* ... */
}
```
只包含一个元素的元组模式的圆括号没有效果,模式只匹配这个单个元素的类型。举例来说,下面的语句是等效的:
```swift
let a = 2 // a: Int = 2
let (a) = 2 // a: Int = 2
let (a): Int = 2 // a: Int = 2
```
> 元组模式语法
<a name="tuple-pattern"></a>
> *元组模式* → **(** [*元组模式元素列表*](#tuple-pattern-element-list)<sub>可选</sub> **)**
<a name="tuple-pattern-element-list"></a>
> *元组模式元素列表* → [*元组模式元素*](#tuple-pattern-element) | [*元组模式元素*](#tuple-pattern-element) **,** [*元组模式元素列表*](#tuple-pattern-element-list)
<a name="tuple-pattern-element"></a>
> *元组模式元素* → [*模式*](#pattern)
<a name="enumeration_case_pattern"></a>
## 枚举用例模式Enumeration Case Pattern
枚举用例模式匹配现有的某个枚举类型的某个用例。枚举用例模式出现在 `switch` 语句中的 `case` 标签中,以及 `if``while``guard``for-in` 语句的 `case` 条件中。
如果你准备匹配的枚举用例有任何关联的值,则相应的枚举用例模式必须指定一个包含每个关联值元素的元组模式。关于使用 `switch` 语句来匹配包含关联值的枚举用例的例子,请参阅 [关联值](../chapter2/08_Enumerations.md#associated_values)。
> 枚举用例模式语法
<a name="enum-case-pattern"></a>
> *枚举用例模式* → [*类型标识*](03_Types.md#type-identifier)<sub>可选</sub> **.** [*枚举用例名*](05_Declarations.md#enum-case-name) [*元组模式*](#tuple-pattern)<sub>可选</sub>
<a name="optional_pattern"></a>
## 可选模式Optional Pattern
可选模式匹配包装在一个 `Optional(Wrapped)` 或者 `ExplicitlyUnwrappedOptional(Wrapped)` 枚举中的 `Some(Wrapped)` 用例中的值。可选模式由一个标识符模式和紧随其后的一个问号组成,可以像枚举用例模式一样使用。
由于可选模式是 `Optional``ImplicitlyUnwrappedOptional` 枚举用例模式的语法糖,下面两种写法是等效的:
```swift
let someOptional: Int? = 42
// 使用枚举用例模式匹配
if case .Some(let x) = someOptional {
print(x)
}
// 使用可选模式匹配
if case let x? = someOptional {
print(x)
}
```
可选模式为 `for-in` 语句提供了一种迭代数组的简便方式,只为数组中非 `nil` 的元素执行循环体。
```swift
let arrayOfOptionalInts: [Int?] = [nil, 2, 3, nil, 5]
// 只匹配非 nil 的元素
for case let number? in arrayOfOptinalInts {
print("Found a \(number)")
}
// Found a 2
// Found a 3
// Found a 5
```
> 可选模式语法
<a name="optional-pattern"></a>
> *可选模式* → [*标识符模式*](03_Types.md#type-identifier) **?**
<a name="type-casting_patterns"></a>
## 类型转换模式Type-Casting Patterns
有两种类型转换模式,`is` 模式和 `as` 模式。`is` 模式只出现在 `switch` 语句中的 `case` 标签中。`is` 模式和 `as` 模式形式如下:
> is `类型`
> `模式` as `类型`
`is` 模式仅当一个值的类型在运行时和 `is` 模式右边的指定类型一致,或者是其子类的情况下,才会匹配这个值。`is` 模式和 `is` 运算符有相似表现,它们都进行类型转换,但是 `is` 模式没有返回类型。
`as` 模式仅当一个值的类型在运行时和 `as` 模式右边的指定类型一致,或者是其子类的情况下,才会匹配这个值。如果匹配成功,被匹配的值的类型被转换成 `as` 模式右边指定的类型。
关于使用 `switch` 语句配合 `is` 模式和 `as` 模式来匹配值的例子,请参阅 [Any 和 AnyObject 的类型转换](../chapter2/19_Type_Casting.md#type_casting_for_any_and_anyobject)。
> 类型转换模式语法
<a name="type-casting-pattern"></a>
> *类型转换模式* → [*is模式*](#is-pattern) | [*as模式*](#as-pattern)
<a name="is-pattern"></a>
> *is模式* → **is** [*类型*](03_Types.md#type)
<a name="as-pattern"></a>
> *as模式* → [*模式*](#pattern) **as** [*类型*](03_Types.md#type)
<a name="expression_pattern"></a>
## 表达式模式Expression Pattern
表达式模式代表表达式的值。表达式模式只出现在 `switch` 语句中的 `case` 标签中。
表达式模式代表的表达式会使用 Swift 标准库中的 `~=` 运算符与输入表达式的值进行比较。如果 `~=` 运算符返回 `true`,则匹配成功。默认情况下,`~=` 运算符使用 `==` 运算符来比较两个相同类型的值。它也可以将一个整型数值与一个 `Range` 实例中的一段整数区间做匹配,正如下面这个例子所示:
```swift
let point = (1, 2)
switch point {
case (0, 0):
print("(0, 0) is at the origin.")
case (-2...2, -2...2):
print("(\(point.0), \(point.1)) is near the origin.")
default:
print("The point is at (\(point.0), \(point.1)).")
}
// 打印 “(1, 2) is near the origin.”
```
你可以重载 `~=` 运算符来提供自定义的表达式匹配行为。比如你可以重写上面的例子,将 `point` 表达式与字符串形式表示的点进行比较。
```swift
// 重载 ~= 运算符对字符串和整数进行比较
func ~=(pattern: String, value: Int) -> Bool {
return pattern == "\(value)"
}
switch point {
case ("0", "0"):
print("(0, 0) is at the origin.")
default:
print("The point is at (\(point.0), \(point.1)).")
}
// 打印 “The point is at (1, 2).”
```
> 表达式模式语法
<a name="expression-pattern"></a>
> *表达式模式* → [*表达式*](04_Expressions.md#expression)

248
source/chapter3/08_Patterns.md Executable file
View File

@ -0,0 +1,248 @@
# 模式Patterns
*模式*代表单个值或者复合值的结构。例如,元组 `(1, 2)` 的结构是由逗号分隔的,包含两个元素的列表。因为模式代表一种值的结构,而不是特定的某个值,你可以利用模式来匹配各种各样的值。比如,`(x, y)` 可以匹配元组 `(1, 2)`,以及任何含两个元素的元组。除了利用模式匹配一个值以外,你可以从复合值中提取出部分或全部值,然后分别把各个部分的值和一个常量或变量绑定起来。
Swift 中的模式分为两类:一种能成功匹配任何类型的值,另一种在运行时匹配某个特定值时可能会失败。
第一类模式用于解构简单变量、常量和可选绑定中的值。此类模式包括通配符模式、标识符模式,以及包含前两种模式的值绑定模式和元组模式。你可以为这类模式指定一个类型注解,从而限制它们只能匹配某种特定类型的值。
第二类模式用于全模式匹配,这种情况下你试图匹配的值在运行时可能不存在。此类模式包括枚举用例模式、可选模式、表达式模式和类型转换模式。你在 `switch` 语句的 `case` 标签中,`do` 语句的 `catch` 子句中,或者在 `if``while``guard``for-in` 语句的 `case` 条件句中使用这类模式。
> 模式语法
>
#### pattern {#pattern}
> *模式* → [*通配符模式*](#wildcard_pattern) [*类型注解*](03_Types.md#type-annotation)<sub>可选</sub>
>
> *模式* → [*标识符模式*](#identifier_pattern) [*类型注解*](03_Types.md#type-annotation)<sub>可选</sub>
>
> *模式* → [*值绑定模式*](#value-binding-pattern)
>
> *模式* → [*元组模式*](#tuple-pattern) [*类型注解*](03_Types.md#type-annotation)<sub>可选</sub>
>
> *模式* → [*枚举用例模式*](#enum-case-pattern)
>
> *模式* → [*可选模式*](#optional-pattern)
>
> *模式* → [*类型转换模式*](#type-casting-pattern)
>
> *模式* → [*表达式模式*](#expression-pattern)
>
## 通配符模式Wildcard Pattern {#wildcard-pattern}
*通配符模式*由一个下划线(`_`)构成,用于匹配并忽略任何值。当你想忽略被匹配的值时可以使用该模式。例如,下面这段代码在闭区间 `1...3` 中迭代,每次迭代都忽略该区间的当前值:
```swift
for _ in 1...3 {
// ...
}
```
> 通配符模式语法
>
#### wildcard-pattern {#wildcard-pattern}
> *通配符模式* → **_**
>
## 标识符模式Identifier Pattern {#identifier-pattern}
*标识符模式*匹配任何值,并将匹配的值和一个变量或常量绑定起来。例如,在下面的常量声明中,`someValue` 是一个标识符模式,匹配了 `Int` 类型的 `42`
```swift
let someValue = 42
```
当匹配成功时,`42` 被绑定(赋值)给常量 `someValue`
如果一个变量或常量声明的左边是一个标识符模式,那么这个标识符模式是值绑定模式的子模式。
> 标识符模式语法
>
#### identifier-pattern {#identifier-pattern}
> *标识符模式* → [*标识符*](./02_Lexical_Structure.md#identifier)
>
## 值绑定模式Value-Binding Pattern {#value-binding-pattern}
*值绑定模式*把匹配到的值绑定给一个变量或常量。把匹配到的值绑定给常量时,用关键字 `let`,绑定给变量时,用关键字 `var`
在值绑定模式中的标识符模式会把新命名的变量或常量与匹配到的值做绑定。例如,你可以拆开一个元组,然后把每个元素绑定到相应的标识符模式中。
```swift
let point = (3, 2)
switch point {
// 将 point 中的元素绑定到 x 和 y
case let (x, y):
print("The point is at (\(x), \(y)).")
}
// 打印“The point is at (3, 2).”
```
在上面这个例子中,`let` 会分配到元组模式 `(x, y)` 中的各个标识符模式。因此,`switch` 语句中 `case let (x, y):``case (let x, let y):` 的匹配效果是一样的。
> 值绑定模式语法
>
#### value-binding-pattern {#value-binding-pattern}
> *值绑定模式* → **var** [*模式*](#pattern) | **let** [*模式*](#pattern)
>
## 元组模式 {#tuple-pattern}
*元组模式*是由逗号分隔的,具有零个或多个模式的列表,并由一对圆括号括起来。元组模式匹配相应元组类型的值。
你可以使用类型注解去限制一个元组模式能匹配哪种元组类型。例如,在常量声明 `let (x, y): (Int, Int) = (1, 2)` 中的元组模式 `(x, y): (Int, Int)` 只匹配两个元素都是 `Int` 类型的元组。
当元组模式被用于 `for-in` 语句或者变量和常量声明时,它仅可以包含通配符模式、标识符模式、可选模式或者其他包含这些模式的元组模式。比如下面这段代码就不正确,因为 `(x, 0)` 中的元素 `0` 是一个表达式模式:
```swift
let points = [(0, 0), (1, 0), (1, 1), (2, 0), (2, 1)]
// 下面的代码是错误的
for (x, 0) in points {
/* ... */
}
```
只包含一个元素的元组模式的圆括号没有效果,模式只匹配这个单个元素的类型。举例来说,下面的语句是等效的:
```swift
let a = 2 // a: Int = 2
let (a) = 2 // a: Int = 2
let (a): Int = 2 // a: Int = 2
```
> 元组模式语法
>
#### tuple-pattern {#tuple-pattern}
> *元组模式* → **(** [*元组模式元素列表*](#tuple-pattern-element-list)<sub>可选</sub> **)**
>
#### tuple-pattern-element-list {#tuple-pattern-element-list}
> *元组模式元素列表* → [*元组模式元素*](#tuple-pattern-element) | [*元组模式元素*](#tuple-pattern-element) **,** [*元组模式元素列表*](#tuple-pattern-element-list)
>
#### tuple-pattern-element {#tuple-pattern-element}
> *元组模式元素* → [*模式*](#pattern)
>
## 枚举用例模式Enumeration Case Pattern {#enumeration-case-pattern}
*枚举用例模式*匹配现有的某个枚举类型的某个用例。枚举用例模式出现在 `switch` 语句中的 `case` 标签中,以及 `if``while``guard``for-in` 语句的 `case` 条件中。
如果你准备匹配的枚举用例有任何关联的值,则相应的枚举用例模式必须指定一个包含每个关联值元素的元组模式。关于使用 `switch` 语句来匹配包含关联值的枚举用例的例子,请参阅 [关联值](../chapter2/08_Enumerations.md#associated_values)。
> 枚举用例模式语法
>
#### enum-case-pattern {#enum-case-pattern}
> *枚举用例模式* → [*类型标识*](./03_Types.md#type-identifier)<sub>可选</sub> **.** [*枚举用例名*](./06_Declarations.md#enum-case-name) [*元组模式*](#tuple-pattern)<sub>可选</sub>
>
## 可选模式Optional Pattern {#optional-pattern}
*可选模式*匹配包装在一个 `Optional(Wrapped)` 或者 `ExplicitlyUnwrappedOptional(Wrapped)` 枚举中的 `Some(Wrapped)` 用例中的值。可选模式由一个标识符模式和紧随其后的一个问号组成,可以像枚举用例模式一样使用。
由于可选模式是 `Optional``ImplicitlyUnwrappedOptional` 枚举用例模式的语法糖,下面两种写法是等效的:
```swift
let someOptional: Int? = 42
// 使用枚举用例模式匹配
if case .Some(let x) = someOptional {
print(x)
}
// 使用可选模式匹配
if case let x? = someOptional {
print(x)
}
```
可选模式为 `for-in` 语句提供了一种迭代数组的简便方式,只为数组中非 `nil` 的元素执行循环体。
```swift
let arrayOfOptionalInts: [Int?] = [nil, 2, 3, nil, 5]
// 只匹配非 nil 的元素
for case let number? in arrayOfOptinalInts {
print("Found a \(number)")
}
// Found a 2
// Found a 3
// Found a 5
```
> 可选模式语法
>
#### optional-pattern {#optional-pattern}
> *可选模式* → [*标识符模式*](./03_Types.md#type-identifier) **?**
>
## 类型转换模式Type-Casting Patterns {#type-casting-patterns}
有两种类型转换模式,`is` 模式和 `as` 模式。`is` 模式只出现在 `switch` 语句中的 `case` 标签中。`is` 模式和 `as` 模式形式如下:
> is `类型`
>
> `模式` as `类型`
>
`is` 模式仅当一个值的类型在运行时和 `is` 模式右边的指定类型一致,或者是其子类的情况下,才会匹配这个值。`is` 模式和 `is` 运算符有相似表现,它们都进行类型转换,但是 `is` 模式没有返回类型。
`as` 模式仅当一个值的类型在运行时和 `as` 模式右边的指定类型一致,或者是其子类的情况下,才会匹配这个值。如果匹配成功,被匹配的值的类型被转换成 `as` 模式右边指定的类型。
关于使用 `switch` 语句配合 `is` 模式和 `as` 模式来匹配值的例子,请参阅 [Any 和 AnyObject 的类型转换](../chapter2/18_Type_Casting.md#type_casting_for_any_and_anyobject)。
> 类型转换模式语法
>
#### type-casting-pattern {#type-casting-pattern}
> *类型转换模式* → [*is 模式*](#is-pattern) | [*as 模式*](#as-pattern)
>
#### is-pattern {#is-pattern}
> *is 模式* → **is** [*类型*](./03_Types.md#type)
>
#### as-pattern {#as-pattern}
> *as 模式* → [*模式*](#pattern) **as** [*类型*](03_Types.md#type)
>
## 表达式模式Expression Pattern {#expression-pattern}
*表达式模式*代表表达式的值。表达式模式只出现在 `switch` 语句中的 `case` 标签中。
表达式模式代表的表达式会使用 Swift 标准库中的 `~=` 运算符与输入表达式的值进行比较。如果 `~=` 运算符返回 `true`,则匹配成功。默认情况下,`~=` 运算符使用 `==` 运算符来比较两个相同类型的值。它也可以将一个整型数值与一个 `Range` 实例中的一段整数区间做匹配,正如下面这个例子所示:
```swift
let point = (1, 2)
switch point {
case (0, 0):
print("(0, 0) is at the origin.")
case (-2...2, -2...2):
print("(\(point.0), \(point.1)) is near the origin.")
default:
print("The point is at (\(point.0), \(point.1)).")
}
// 打印“(1, 2) is near the origin.”
```
你可以重载 `~=` 运算符来提供自定义的表达式匹配行为。比如你可以重写上面的例子,将 `point` 表达式与字符串形式表示的点进行比较。
```swift
// 重载 ~= 运算符对字符串和整数进行比较
func ~=(pattern: String, value: Int) -> Bool {
return pattern == "\(value)"
}
switch point {
case ("0", "0"):
print("(0, 0) is at the origin.")
default:
print("The point is at (\(point.0), \(point.1)).")
}
// 打印“The point is at (1, 2).”
```
> 表达式模式语法
>
#### expression-pattern {#expression-pattern}
> *表达式模式* → [*表达式*](./04_Expressions.md#expression)
>

View File

@ -1,129 +1,136 @@
# 泛型参数Generic Parameters and Arguments
---------
> 1.0
> 翻译:[fd5788](https://github.com/fd5788)
> 校对:[yankuangshi](https://github.com/yankuangshi), [stanzhai](https://github.com/stanzhai)
> 2.0
> 翻译+校对:[wardenNScaiyi](https:github.com/wardenNScaiyi)
> 3.0
> 翻译+校对:[chenmingjia](https:github.com/chenmingjia)
本页包含内容:
- [泛型形参子句](#generic_parameter)
- [Where 子句](#where_clauses)
- [泛型实参子句](#generic_argument)
本节涉及泛型类型、泛型函数以及泛型构造器的参数,包括形参和实参。声明泛型类型、函数或构造器时,须指定相应的类型参数。类型参数相当于一个占位符,当实例化泛型类型、调用泛型函数或泛型构造器时,就用具体的类型实参替代之
关于 Swift 语言的泛型概述,请参阅 [泛型](../chapter2/23_Generics.md)。
<a name="generic_parameter"></a>
## 泛型形参子句
泛型形参子句指定泛型类型或函数的类型形参,以及这些参数相关的约束和要求。泛型形参子句用尖括号(`<>`)包住,形式如下:
> <`泛型形参列表`>
泛型形参列表中泛型形参用逗号分开,其中每一个采用以下形式:
> `类型形参` : `约束`
泛型形参由两部分组成:类型形参及其后的可选约束。类型形参只是占位符类型(如 `T``U``V``Key``Value` 等)的名字而已。你可以在泛型类型、函数的其余部分或者构造器声明,包括函数或构造器的签名中使用它(以及它的关联类型)。
约束用于指明该类型形参继承自某个类或者符合某个协议或协议组合。例如,在下面的泛型函数中,泛型形参 `T: Comparable` 表示任何用于替代类型形参 `T` 的类型实参必须满足 `Comparable` 协议。
```swift
func simpleMax<T: Comparable>(_ x: T, _ y: T) -> T {
if x < y {
return y
}
return x
}
```
例如,因为 `Int``Double` 均满足`Comparable`协议,所以该函数可以接受这两种类型。与泛型类型相反,调用泛型函数或构造器时不需要指定泛型实参子句。类型实参由传递给函数或构造器的实参推断而出。
```swift
simpleMax(17, 42) // T 被推断为 Int 类型
simpleMax(3.14159, 2.71828) // T 被推断为 Double 类型
```
<a name="where_clauses"></a>
### Where 子句
要想对类型形参及其关联类型指定额外要求,可以在函数体或者类型的大括号之前添加 `where` 子句。`where` 子句由关键字 `where` 及其后的用逗号分隔的一个或多个要求组成。
> `where` : `类型要求`
`where` 子句中的要求用于指明该类型形参继承自某个类或符合某个协议或协议组合。尽管 `where` 子句提供了语法糖使其有助于表达类型形参上的简单约束(如 `<T: Comparable>` 等同于 `<T> where T: Comparable`,等等),但是依然可以用来对类型形参及其关联类型提供更复杂的约束,例如你可以强制形参的关联类型遵守协议,如,` <S: Sequence> where S.Iterator.Element: Equatable` 表示泛型类型 `S` 遵守`Sequence`协议并且关联类型`S.Iterator.Element`遵守`Equatable`协议,这个约束确保队列的每一个元素都是符合 `Equatable` 协议的。
也可以用操作符 `==` 来指定两个类型必须相同。例如,泛型形参子句 ` <S1: Sequence, S2: Sequence> where S1.Iterator.Element == S2.Iterator.Element` 表示 `S1``S2` 必须都符合 `SequenceType` 协议,而且两个序列中的元素类型必须相同。
当然,替代类型形参的类型实参必须满足所有的约束和要求。
泛型函数或构造器可以重载,但在泛型形参子句中的类型形参必须有不同的约束或要求,抑或二者皆不同。当调用重载的泛型函数或构造器时,编译器会根据这些约束来决定调用哪个重载函数或构造器。
更多关于泛型where从句的信息和关于泛型函数声明的例子,可以看一看 [泛型where子句](https://github.com/numbbbbb/the-swift-programming-language-in-chinese/blob/gh-pages/source/chapter2/23_Generics.md#where_clauses)
> 泛型形参子句语法
<a name="generic-parameter-clause"></a>
> *泛型形参子句***<** [*泛型形参列表*](#generic-parameter-list) [*约束子句*](#requirement-clause)<sub>可选</sub> **>**
<a name="generic-parameter-list"></a>
> *泛型形参列表* → [*泛形形参*](#generic-parameter) | [*泛形形参*](#generic-parameter) **,** [*泛型形参列表*](#generic-parameter-list)
<a name="generic-parameter"></a>
> *泛形形参* → [*类型名称*](03_Types.html#type-name)
> *泛形形参* → [*类型名称*](03_Types.html#type-name) **:** [*类型标识符*](03_Types.html#type-identifier)
> *泛形形参* → [*类型名称*](03_Types.html#type-name) **:** [*协议合成类型*](03_Types.html#protocol-composition-type)
<a name="requirement-clause"></a>
> *约束子句***where** [*约束列表*](#requirement-list)
<a name="requirement-list"></a>
> *约束列表* → [*约束*](#requirement) | [*约束*](#requirement) **,** [*约束列表*](#requirement-list)
<a name="requirement"></a>
> *约束* → [*一致性约束*](#conformance-requirement) | [*同类型约束*](#same-type-requirement)
<a name="conformance-requirement"></a>
> *一致性约束* → [*类型标识符*](03_Types.html#type-identifier) **:** [*类型标识符*](03_Types.html#type-identifier)
> *一致性约束* → [*类型标识符*](03_Types.html#type-identifier) **:** [*协议合成类型*](03_Types.html#protocol-composition-type)
<a name="same-type-requirement"></a>
> *同类型约束* → [*类型标识符*](03_Types.html#type-identifier) **==** [*类型*](03_Types.html#type)
<a name="generic_argument"></a>
## 泛型实参子句
泛型实参子句指定泛型类型的类型实参。泛型实参子句用尖括号(`<>`)包住,形式如下:
> <`泛型实参列表`>
泛型实参列表中类型实参用逗号分开。类型实参是实际具体类型的名字用来替代泛型类型的泛型形参子句中的相应的类型形参。从而得到泛型类型的一个特化版本。例如Swift 标准库中的泛型字典类型的的简化定义如下:
```swift
struct Dictionary<Key: Hashable, Value>: CollectionType, DictionaryLiteralConvertible {
/* ... */
}
```
泛型 `Dictionary` 类型的特化版本,`Dictionary<String, Int>` 就是用具体的 `String``Int` 类型替代泛型类型 `Key: Hashable``Value` 产生的。每一个类型实参必须满足它所替代的泛型形参的所有约束,包括任何 `where` 子句所指定的额外的关联类型要求。上面的例子中,类型形参 `Key` 的类型必须符合 `Hashable` 协议,因此 `String` 也必须满足 `Hashable` 协议。
可以用本身就是泛型类型的特化版本的类型实参替代类型形参(假设已满足合适的约束和关联类型要求)。例如,为了生成一个元素类型是整型数组的数组,可以用数组的特化版本 `Array<Int>` 替代泛型类型 `Array<T>` 的类型形参 `T` 来实现。
```swift
let arrayOfArrays: Array<Array<Int>> = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
```
如 [泛型形参子句](#generic_parameter) 所述,不能用泛型实参子句来指定泛型函数或构造器的类型实参。
> 泛型实参子句语法
<a name="generic-argument-clause"></a>
> *泛型实参子句***<** [*泛型实参列表*](#generic-argument-list) **>**
<a name="generic-argument-list"></a>
> *泛型实参列表* [*泛型实参*](#generic-argument) | [*泛型实参*](#generic-argument) **,** [*泛型实参列表*](#generic-argument-list)
<a name="generic-argument"></a>
> *泛型实参* → [*类型*](03_Types.html#type)
# 泛型参数Generic Parameters and Arguments
本节涉及泛型类型、泛型函数以及泛型构造器的参数,包括形参和实参。声明泛型类型、函数或构造器时,须指定相应的类型参数。类型参数相当于一个占位符,当实例化泛型类型、调用泛型函数或泛型构造器时,就用具体的类型实参替代之。
关于 Swift 语言的泛型概述,请参阅 [泛型](../chapter2/22_Generics.md)
## 泛型形参子句 {#generic-parameter}
*泛型形参子句*指定泛型类型或函数的类型形参,以及这些参数相关的约束和要求。泛型形参子句用尖括号(`<>`)包住,形式如下:
> <`泛型形参列表`>
>
泛型形参列表中泛型形参用逗号分开,其中每一个采用以下形式:
> `类型形参` : `约束`
>
泛型形参由两部分组成:类型形参及其后的可选约束。类型形参只是占位符类型(如 `T``U``V``Key``Value` 等)的名字而已。你可以在泛型类型、函数的其余部分或者构造器声明,包括函数或构造器的签名中使用它(以及它的关联类型)。
约束用于指明该类型形参继承自某个类或者符合某个协议或协议组合。例如,在下面的泛型函数中,泛型形参 `T: Comparable` 表示任何用于替代类型形参 `T` 的类型实参必须满足 `Comparable` 协议
```swift
func simpleMax<T: Comparable>(_ x: T, _ y: T) -> T {
if x < y {
return y
}
return x
}
```
例如,因为 `Int``Double` 均满足 `Comparable` 协议,所以该函数可以接受这两种类型。与泛型类型相反,调用泛型函数或构造器时不需要指定泛型实参子句。类型实参由传递给函数或构造器的实参推断而出。
```swift
simpleMax(17, 42) // T 被推断为 Int 类型
simpleMax(3.14159, 2.71828) // T 被推断为 Double 类型
```
### Where 子句 {#where-clauses}
要想对类型形参及其关联类型指定额外要求,可以在函数体或者类型的大括号之前添加 `where` 子句。`where` 子句由关键字 `where` 及其后的用逗号分隔的一个或多个要求组成。
> `where` : `类型要求`
>
`where` 子句中的要求用于指明该类型形参继承自某个类或符合某个协议或协议组合。尽管 `where` 子句提供了语法糖使其有助于表达类型形参上的简单约束(如 `<T: Comparable>` 等同于 `<T> where T: Comparable`,等等),但是依然可以用来对类型形参及其关联类型提供更复杂的约束,例如你可以强制形参的关联类型遵守协议,如,`<S: Sequence> where S.Iterator.Element: Equatable` 表示泛型类型 `S` 遵守 `Sequence` 协议并且关联类型 `S.Iterator.Element` 遵守 `Equatable` 协议,这个约束确保队列的每一个元素都是符合 `Equatable` 协议的。
>
也可以用操作符 `==` 来指定两个类型必须相同。例如,泛型形参子句 `<S1: Sequence, S2: Sequence> where S1.Iterator.Element == S2.Iterator.Element` 表示 `S1``S2` 必须都符合 `SequenceType` 协议,而且两个序列中的元素类型必须相同。
>
当然,替代类型形参的类型实参必须满足所有的约束和要求。
泛型函数或构造器可以重载,但在泛型形参子句中的类型形参必须有不同的约束或要求,抑或二者皆不同。当调用重载的泛型函数或构造器时,编译器会根据这些约束来决定调用哪个重载函数或构造器。
更多关于泛型 where 从句的信息和关于泛型函数声明的例子,可以看一看 [泛型 where 子句](../chapter2/22_Generics.md#where_clauses)。
> 泛型形参子句语法
>
#### generic-parameter-clause {#generic-parameter-clause}
> *泛型形参子句***<** [*泛型形参列表*](#generic-parameter-list) [*约束子句*](#requirement-clause)<sub>可选</sub> **>**
>
#### generic-parameter-list {#generic-parameter-list}
> *泛型形参列表* → [*泛形形参*](#generic-parameter) | [*泛形形参*](#generic-parameter) **,** [*泛型形参列表*](#generic-parameter-list)
>
#### generic-parameter {#generic-parameter}
> *泛形形参* → [*类型名称*](./03_Types.md#type-name)
>
> *泛形形参* → [*类型名称*](./03_Types.md#type-name) **:** [*类型标识符*](./03_Types.md#type-identifier)
>
> *泛形形参* → [*类型名称*](./03_Types.md#type-name) **:** [*协议合成类型*](./03_Types.md#protocol-composition-type)
>
>
#### requirement-clause {#requirement-clause}
>
> *约束子句***where** [*约束列表*](#requirement-list)
>
#### requirement-list {#requirement-list}
> *约束列表* → [*约束*](#requirement) | [*约束*](#requirement) **,** [*约束列表*](#requirement-list)
>
#### requirement {#requirement}
> *约束*[*一致性约束*](#conformance-requirement) | [*同类型约束*](#same-type-requirement)
>
>
#### conformance-requirement {#conformance-requirement}
>
> *一致性约束* → [*类型标识符*](./03_Types.md#type-identifier) **:** [*类型标识符*](./03_Types.md#type-identifier)
>
> *一致性约束* → [*类型标识符*](./03_Types.md#type-identifier) **:** [*协议合成类型*](./03_Types.md#protocol-composition-type)
>
#### same-type-requirement {#same-type-requirement}
> *同类型约束* → [*类型标识符*](./03_Types.md#type-identifier) **==** [*类型*](./03_Types.md#type)
>
## 泛型实参子句 {#generic-argument}
*泛型实参子句*指定泛型类型的类型实参。泛型实参子句用尖括号(`<>`)包住,形式如下:
> <`泛型实参列表`>
>
泛型实参列表中类型实参用逗号分开。类型实参是实际具体类型的名字用来替代泛型类型的泛型形参子句中的相应的类型形参。从而得到泛型类型的一个特化版本。例如Swift 标准库中的泛型字典类型的的简化定义如下:
```swift
struct Dictionary<Key: Hashable, Value>: CollectionType, DictionaryLiteralConvertible {
/* ... */
}
```
泛型 `Dictionary` 类型的特化版本,`Dictionary<String, Int>` 就是用具体的 `String``Int` 类型替代泛型类型 `Key: Hashable``Value` 产生的。每一个类型实参必须满足它所替代的泛型形参的所有约束,包括任何 `where` 子句所指定的额外的关联类型要求。上面的例子中,类型形参 `Key` 的类型必须符合 `Hashable` 协议,因此 `String` 也必须满足 `Hashable` 协议。
可以用本身就是泛型类型的特化版本的类型实参替代类型形参(假设已满足合适的约束和关联类型要求)。例如,为了生成一个元素类型是整型数组的数组,可以用数组的特化版本 `Array<Int>` 替代泛型类型 `Array<T>` 的类型形参 `T` 来实现。
```swift
let arrayOfArrays: Array<Array<Int>> = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
```
如 [泛型形参子句](#generic_parameter) 所述,不能用泛型实参子句来指定泛型函数或构造器的类型实参。
> 泛型实参子句语法
>
#### generic-argument-clause {#generic-argument-clause}
> *泛型实参子句***<** [*泛型实参列表*](#generic-argument-list) **>**
>
#### generic-argument-list {#generic-argument-list}
> *泛型实参列表* → [*泛型实参*](#generic-argument) | [*泛型实参*](#generic-argument) **,** [*泛型实参列表*](#generic-argument-list)
>
#### generic-argument {#generic-argument}
> *泛型实参* → [*类型*](./03_Types.md#type)
>

View File

@ -1,946 +0,0 @@
# 语法总结Summary of the Grammar
-----
> 1.0
> 翻译:[stanzhai](https://github.com/stanzhai)
> 校对:[xielingwang](https://github.com/xielingwang)
> 2.0
> 翻译+校对:[miaosiqi](https://github.com/miaosiqi)
本页包含内容:
* [语句Statements](#statements)
* [泛型参数Generic Parameters and Arguments](#generic_parameters_and_arguments)
* [声明Declarations](#declarations)
* [模式Patterns](#patterns)
* [属性Attributes](#attributes)
* [表达式Expressions](#expressions)
* [词法结构Lexical Structure](#lexical_structure)
* [类型Types](#types)
<a name="statements"></a>
## 语句
> 语句语法
> *语句* → [*表达式*](../chapter3/04_Expressions.html#expression) **;** _可选_
> *语句* → [*声明*](../chapter3/05_Declarations.html#declaration) **;** _可选_
> *语句* → [*循环语句*](../chapter3/10_Statements.html#loop_statement) **;** _可选_
> *语句* → [*分支语句*](../chapter3/10_Statements.html#branch_statement) **;** _可选_
> *语句* → [*标记语句(Labeled Statement)*](../chapter3/10_Statements.html#labeled_statement)
> *语句* → [*控制转移语句*](../chapter3/10_Statements.html#control_transfer_statement) **;** _可选_
> *语句* → [*延迟语句*](TODO) **;** _可选_
> *语句* → [*执行语句*](TODO) **;** _可选_
> *多条语句(Statements)* → [*语句*](../chapter3/10_Statements.html#statement) [*多条语句(Statements)*](../chapter3/10_Statements.html#statements) _可选_
<!-- -->
> 循环语句语法
> *循环语句* → [*for语句*](../chapter3/10_Statements.html#for_statement)
> *循环语句* → [*for-in语句*](../chapter3/10_Statements.html#for_in_statement)
> *循环语句* → [*while语句*](../chapter3/10_Statements.html#wheetatype类型ile_statement)
> *循环语句* → [*repeat-while语句*](../chapter3/10_Statements.html#do_while_statement)
<!-- -->
> For 循环语法
> *for语句* → **for** [*for初始条件*](../chapter3/10_Statements.html#for_init) _可选_ **;** [*表达式*](../chapter3/04_Expressions.html#expression) _可选_ **;** [*表达式*](../chapter3/04_Expressions.html#expression) _可选_ [*代码块*](../chapter3/05_Declarations.html#code_block)
> *for语句* → **for** **(** [*for初始条件*](../chapter3/10_Statements.html#for_init) _可选_ **;** [*表达式*](../chapter3/04_Expressions.html#expression) _可选_ **;** [*表达式*](../chapter3/04_Expressions.html#expression) _可选_ **)** [*代码块*](../chapter3/05_Declarations.html#code_block)
> *for初始条件* → [*变量声明*](../chapter3/05_Declarations.html#variable_declaration) | [*表达式集*](../chapter3/04_Expressions.html#expression_list)
<!-- -->
> For-In 循环语法
> *for-in语句* → **for case** _可选_ [*模式*](../chapter3/07_Patterns.html#pattern) **in** [*表达式*](../chapter3/04_Expressions.html#expression) [*代码块*](../chapter3/05_Declarations.html#code_block) [*where从句*](TODO) _可选_
<!-- -->
> While 循环语法
> *while语句* → **while** [*条件从句*](../chapter3/10_Statements.html#while_condition) [*代码块*](../chapter3/05_Declarations.html#code_block)
> *条件从句* → [*表达式*](TODO)
> *条件从句* → [*表达式*](TODO) *,* [*表达式集*]()
>*条件从句* → [*表达式集*](TODO)
> *条件从句* → [*可用条件 (availability-condition*)](TODO) *|* [*表达式集*]()
> *条件集* → [*条件*](TODO) *|* [*条件*](TODO) *,* [*条件集*]()
> *条件* → [*可用条件(availability-condition)*](TODO) *|* [*个例条件(case-condition)*](TODO) *|* [*可选绑定条件(optional-binding-condition)*](TODO)
> *个例条件(case-condition)* → **case** [*模式*](TODO) [*构造器*](TODO) [*where从句*](TODO)_可选_
> *可选绑定条件(optional-binding-condition)* → [*可选绑定头(optional-binding-head)*](TODO) [*可选绑定连续集(optional-binding-continuation-list)*](TODO) _可选_ [*where从句*](TODO) _可选_
> *可选绑定头(optional-binding-head)* → **let** [*模式 构造器*](TODO) *|* **var** [*模式 构造器*](TODO)
> *可选绑定连续集(optional-binding-contiuation-list)* → [*可选绑定连续(optional-binding-contiuation)*](TODO) *|* [*可选绑定连续(optional-binding-contiuation)*](TODO) ** [*可选绑定连续集(optional-binding-contiuation-list)*](TODO)
> *可选绑定连续(optional-binding-continuation)* → [*模式 构造器*](TODO) *|* [*可选绑定头(optional-binding-head)*](TODO)
<!-- -->
> Repeat-While语句语法
*repeat-while-statement* → **repeat** [*代码块*](TODO) **while** [*表达式*](TODO)
<!-- -->
> 分支语句语法
> *分支语句* → [*if语句*](../chapter3/10_Statements.html#if_statement)
> *分支语句* → [*guard语句*](TODO)
> *分支语句* → [*switch语句*](../chapter3/10_Statements.html#switch_statement)
<!-- -->
> If语句语法
> *if语句* → **if** [*条件从句*](TODO) [*代码块*](TODO) [*else从句(Clause)*](TODO) _可选_
> *else从句(Clause)* → **else** [*代码块*](../chapter3/05_Declarations.html#code_block) | **else** [*if语句*](../chapter3/10_Statements.html#if_statement)
<!-- -->
>Guard 语句语法
>*guard语句* → **guard** [*条件从句*](TODO) **else** [*代码块*](TODO)
<!-- -->
> Switch语句语法
> *switch语句* → **switch** [*表达式*](../chapter3/04_Expressions.html#expression) **{** [*SwitchCase*](../chapter3/10_Statements.html#switch_cases) _可选_ **}**
> *SwitchCase集* → [*SwitchCase*](../chapter3/10_Statements.html#switch_case) [*SwitchCase集*](../chapter3/10_Statements.html#switch_cases) _可选_
> *SwitchCase* → [*case标签*](../chapter3/10_Statements.html#case_label) [*多条语句(Statements)*](../chapter3/10_Statements.html#statements) | [*default标签*](../chapter3/10_Statements.html#default_label) [*多条语句(Statements)*](../chapter3/10_Statements.html#statements)
> *SwitchCase* → [*case标签*](../chapter3/10_Statements.html#case_label) **;** | [*default标签*](../chapter3/10_Statements.html#default_label) **;**
> *case标签* → **case** [*case项集*](../chapter3/10_Statements.html#case_item_list) **:**
> *case项集* → [*模式*](../chapter3/07_Patterns.html#pattern) [*where-clause*](../chapter3/10_Statements.html#guard_clause) _可选_ | [*模式*](../chapter3/07_Patterns.html#pattern) [*where-clause*](../chapter3/10_Statements.html#guard_clause) _可选_ **,** [*case项集*](../chapter3/10_Statements.html#case_item_list)
> *default标签* → **default** **:**
> *where从句* → **where** [*where表达式*](TODO)
> *where表达式* → [*表达式*](TODO)
<!-- -->
> 标记语句语法
> *标记语句(Labeled Statement)* → [*语句标签*](../chapter3/10_Statements.html#statement_label) [*循环语句*](../chapter3/10_Statements.html#loop_statement) | [*语句标签*](../chapter3/10_Statements.html#statement_label) [*if语句*](../chapter3/10_Statements.html#switch_statement) | [*语句标签*](TODY) [*switch语句*](TODY)
> *语句标签* → [*标签名称*](../chapter3/10_Statements.html#label_name) **:**
> *标签名称* → [*标识符*](../chapter3/02_Lexical_Structure.html#identifier)
<!-- -->
> 控制传递语句(Control Transfer Statement) 语法
> *控制传递语句* → [*break语句*](../chapter3/10_Statements.html#break_statement)
> *控制传递语句* → [*continue语句*](../chapter3/10_Statements.html#continue_statement)
> *控制传递语句* → [*fallthrough语句*](../chapter3/10_Statements.html#fallthrough_statement)
> *控制传递语句* → [*return语句*](../chapter3/10_Statements.html#return_statement)
> *控制传递语句* → [*throw语句*](TODO)
<!-- -->
> Break 语句语法
> *break语句* → **break** [*标签名称*](../chapter3/10_Statements.html#label_name) _可选_
<!-- -->
> Continue 语句语法
> *continue语句* → **continue** [*标签名称*](../chapter3/10_Statements.html#label_name) _可选_
<!-- -->
> Fallthrough 语句语法
> *fallthrough语句* → **fallthrough**
<!-- -->
> Return 语句语法
> *return语句* → **return** [*表达式*](../chapter3/04_Expressions.html#expression) _可选_
<!-- -->
>可用条件(Availability Condition)语法
>*可用条件(availability-condition)* → **#available** **(** [*多可用参数*(availability-arguments)](TODO) **)**
>*多可用参数(availability- arguments)* → [*可用参数(availability-argument)*](TODO)|[*可用参数(availability-argument)*](TODO) , [多可用参数(availability-arguments)](TODO)
>*可用参数(availability- argument)* → [*平台名(platform-name)*](TODO) [*平台版本(platform-version)*](TODO)
>*可用参数(availability- argument)* → *
>*平台名* → **iOS** | **iOSApplicationExtension**
>*平台名* → **OSX** | **OSXApplicationExtension**
>*平台名* → **watchOS**
>*平台版本* → [*十进制数(decimal-digits)*](TODO)
>*平台版本* → [*十进制数(decimal-digits)*](TODO) . [*十进制数(decimal-digits)*](TODO)
>*平台版本* → [*十进制数(decimal-digits)*](TODO) **.** [*十进制数(decimal-digits)*](TODO) **.** [*十进制数decimal-digits)*](TODO))
<!-- -->
>抛出语句(Throw Statement)语法
>*抛出语句(throw-statement)* → **throw** [*表达式(expression)*](TODO)
<!-- -->
>延迟语句 (defer-statement)语法
>*延迟语句(defer-statement)* → **defer** [*代码块*](TODO)
<!-- -->
>执行语句(do-statement)语法
>*执行语句(do-statement)* → **do** [*代码块*](TODO) [*catch-clauses*](TODO) _可选_
>*catch-clauses* → [*catch-clause*](TODO) [*catch-clauses*](TODO) _可选_
>*catch-clauses* → **catch** [*模式(pattern)*](TODO) _可选_ [*where-clause*](TODO) _可选_ [*代码块(code-block)*](TODO) _可选_
<a name="generic_parameters_and_arguments"></a>
## 泛型参数
> 泛型形参从句(Generic Parameter Clause) 语法
> *泛型参数从句* → **<** [*泛型参数集*](GenericParametersAndArguments.html#generic_parameter_list) [*约束从句*](GenericParametersAndArguments.html#requirement_clause) _可选_ **>**
> *泛型参数集* → [*泛形参数*](GenericParametersAndArguments.html#generic_parameter) | [*泛形参数*](GenericParametersAndArguments.html#generic_parameter) **,** [*泛型参数集*](GenericParametersAndArguments.html#generic_parameter_list)
> *泛形参数* → [*类型名称*](../chapter3/03_Types.html#type_name)
> *泛形参数* → [*类型名称*](../chapter3/03_Types.html#type_name) **:** [*类型标识*](../chapter3/03_Types.html#type_identifier)
> *泛形参数* → [*类型名称*](../chapter3/03_Types.html#type_name) **:** [*协议合成类型*](../chapter3/03_Types.html#protocol_composition_type)
> *约束从句* → **where** [*约束集*](GenericParametersAndArguments.html#requirement_list)
> *约束集* → [*约束*](GenericParametersAndArguments.html#requirement) | [*约束*](GenericParametersAndArguments.html#requirement) **,** [*约束集*](GenericParametersAndArguments.html#requirement_list)
> *约束* → [*一致性约束*](GenericParametersAndArguments.html#conformance_requirement) | [*同类型约束*](GenericParametersAndArguments.html#same_type_requirement)
> *一致性约束* → [*类型标识*](../chapter3/03_Types.html#type_identifier) **:** [*类型标识*](../chapter3/03_Types.html#type_identifier)
> *一致性约束* → [*类型标识*](../chapter3/03_Types.html#type_identifier) **:** [*协议合成类型*](../chapter3/03_Types.html#protocol_composition_type)
> *同类型约束* → [*类型标识*](../chapter3/03_Types.html#type_identifier) **==** [*类型*](../chapter3/03_Types.html#type_identifier)
<!-- -->
> 泛型实参从句语法
> *(泛型参数从句Generic Argument Clause)* → **<** [*泛型参数集*](GenericParametersAndArguments.html#generic_argument_list) **>**
> *泛型参数集* → [*泛型参数*](GenericParametersAndArguments.html#generic_argument) | [*泛型参数*](GenericParametersAndArguments.html#generic_argument) **,** [*泛型参数集*](GenericParametersAndArguments.html#generic_argument_list)
> *泛型参数* → [*类型*](../chapter3/03_Types.html#type)
<a name="declarations"></a>
## 声明 (Declarations)
> 声明语法
> *声明* → [*导入声明*](../chapter3/05_Declarations.html#import_declaration)
> *声明* → [*常量声明*](../chapter3/05_Declarations.html#constant_declaration)
> *声明* → [*变量声明*](../chapter3/05_Declarations.html#variable_declaration)
> *声明* → [*类型别名声明*](../chapter3/05_Declarations.html#typealias_declaration)
> *声明* → [*函数声明*](../chapter3/05_Declarations.html#function_declaration)
> *声明* → [*枚举声明*](../chapter3/05_Declarations.html#enum_declaration)
> *声明* → [*结构体声明*](../chapter3/05_Declarations.html#struct_declaration)
> *声明* → [*类声明*](../chapter3/05_Declarations.html#class_declaration)
> *声明* → [*协议声明*](../chapter3/05_Declarations.html#protocol_declaration)
> *声明* → [*构造器声明*](../chapter3/05_Declarations.html#initializer_declaration)
> *声明* → [*析构器声明*](../chapter3/05_Declarations.html#deinitializer_declaration)
> *声明* → [*扩展声明*](../chapter3/05_Declarations.html#extension_declaration)
> *声明* → [*下标声明*](../chapter3/05_Declarations.html#subscript_declaration)
> *声明* → [*运算符声明*](../chapter3/05_Declarations.html#operator_declaration)
> *声明(Declarations)集* → [*声明*](../chapter3/05_Declarations.html#declaration) [*声明(Declarations)集*](../chapter3/05_Declarations.html#declarations) _可选_
<!-- -->
> 顶级(Top Level) 声明语法
> *顶级声明* → [*多条语句(Statements)*](../chapter3/10_Statements.html#statements) _可选_
<!-- -->
> 代码块语法
> *代码块* → **{** [*多条语句(Statements)*](../chapter3/10_Statements.html#statements) _可选_ **}**
<!-- -->
> 导入(Import)声明语法
> *导入声明* → [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ **import** [*导入类型*](../chapter3/05_Declarations.html#import_kind) _可选_ [*导入路径*](../chapter3/05_Declarations.html#import_path)
> *导入类型* → **typealias** | **struct** | **class** | **enum** | **protocol** | **var** | **func**
> *导入路径* → [*导入路径标识符*](../chapter3/05_Declarations.html#import_path_identifier) | [*导入路径标识符*](../chapter3/05_Declarations.html#import_path_identifier) **.** [*导入路径*](../chapter3/05_Declarations.html#import_path)
> *导入路径标识符* → [*标识符*](../chapter3/02_Lexical_Structure.html#identifier) | [*运算符*](../chapter3/02_Lexical_Structure.html#operator)
<!-- -->
> 常数声明语法
> *常量声明* → [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ [*声明修改符(Modifiers)集*](../chapter3/05_Declarations.html#declaration_specifiers) _可选_ **let** [*模式构造器集*](../chapter3/05_Declarations.html#pattern_initializer_list)
> *模式构造器集* → [*模式构造器*](../chapter3/05_Declarations.html#pattern_initializer) | [*模式构造器*](../chapter3/05_Declarations.html#pattern_initializer) **,** [*模式构造器集*](../chapter3/05_Declarations.html#pattern_initializer_list)
> *模式构造器* → [*模式*](../chapter3/07_Patterns.html#pattern) [*构造器*](../chapter3/05_Declarations.html#initializer) _可选_
> *构造器* → **=** [*表达式*](../chapter3/04_Expressions.html#expression)
<!-- -->
> 变量声明语法
> *变量声明* → [*变量声明头(Head)*](../chapter3/05_Declarations.html#variable_declaration_head) [*模式构造器集*](../chapter3/05_Declarations.html#pattern_initializer_list)
> *变量声明* → [*变量声明头(Head)*](../chapter3/05_Declarations.html#variable_declaration_head) [*变量名*](../chapter3/05_Declarations.html#variable_name) [*类型注解*](../chapter3/03_Types.html#type_annotation) [*代码块*](../chapter3/05_Declarations.html#code_block)
> *变量声明* → [*变量声明头(Head)*](../chapter3/05_Declarations.html#variable_declaration_head) [*变量名*](../chapter3/05_Declarations.html#variable_name) [*类型注解*](../chapter3/03_Types.html#type_annotation) [*getter-setter块*](../chapter3/05_Declarations.html#getter_setter_block)
> *变量声明* → [*变量声明头(Head)*](../chapter3/05_Declarations.html#variable_declaration_head) [*变量名*](../chapter3/05_Declarations.html#variable_name) [*类型注解*](../chapter3/03_Types.html#type_annotation) [*getter-setter关键字(Keyword)块*](../chapter3/05_Declarations.html#getter_setter_keyword_block)
> *变量声明* → [*变量声明头(Head)*](../chapter3/05_Declarations.html#variable_declaration_head) [*变量名*](../chapter3/05_Declarations.html#variable_name) [*类型注解*](../chapter3/03_Types.html#type_annotation) [*构造器*](../chapter3/05_Declarations.html#initializer) _可选_ [*willSet-didSet代码块*](../chapter3/05_Declarations.html#willSet_didSet_block)
> *变量声明头(Head)* → [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ [*声明修改符(Modifers)集*](../chapter3/05_Declarations.html#declaration_specifiers) _可选_ **var**
> *变量名称* → [*标识符*](../chapter3/02_Lexical_Structure.html#identifier)
> *getter-setter块* → **{** [*getter从句*](../chapter3/05_Declarations.html#getter_clause) [*setter从句*](../chapter3/05_Declarations.html#setter_clause) _可选_ **}**
> *getter-setter块* → **{** [*setter从句*](../chapter3/05_Declarations.html#setter_clause) [*getter从句*](../chapter3/05_Declarations.html#getter_clause) **}**
> *getter从句* → [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ **get** [*代码块*](../chapter3/05_Declarations.html#code_block)
> *setter从句* → [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ **set** [*setter名称*](../chapter3/05_Declarations.html#setter_name) _可选_ [*代码块*](../chapter3/05_Declarations.html#code_block)
> *setter名称* → **(** [*标识符*](../chapter3/02_Lexical_Structure.html#identifier) **)**
> *getter-setter关键字(Keyword)块* → **{** [*getter关键字(Keyword)从句*](../chapter3/05_Declarations.html#getter_keyword_clause) [*setter关键字(Keyword)从句*](../chapter3/05_Declarations.html#setter_keyword_clause) _可选_ **}**
> *getter-setter关键字(Keyword)块* → **{** [*setter关键字(Keyword)从句*](../chapter3/05_Declarations.html#setter_keyword_clause) [*getter关键字(Keyword)从句*](../chapter3/05_Declarations.html#getter_keyword_clause) **}**
> *getter关键字(Keyword)从句* → [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ **get**
> *setter关键字(Keyword)从句* → [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ **set**
> *willSet-didSet代码块* → **{** [*willSet从句*](../chapter3/05_Declarations.html#willSet_clause) [*didSet从句*](../chapter3/05_Declarations.html#didSet_clause) _可选_ **}**
> *willSet-didSet代码块* → **{** [*didSet从句*](../chapter3/05_Declarations.html#didSet_clause) [*willSet从句*](../chapter3/05_Declarations.html#willSet_clause) **}**
> *willSet从句* → [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ **willSet** [*setter名称*](../chapter3/05_Declarations.html#setter_name) _可选_ [*代码块*](../chapter3/05_Declarations.html#code_block)
> *didSet从句* → [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ **didSet** [*setter名称*](../chapter3/05_Declarations.html#setter_name) _可选_ [*代码块*](../chapter3/05_Declarations.html#code_block)
<!-- -->
> 类型别名声明语法
> *类型别名声明* → [*类型别名头(Head)*](../chapter3/05_Declarations.html#typealias_head) [*类型别名赋值*](../chapter3/05_Declarations.html#typealias_assignment)
> *类型别名头(Head)* → [*属性*](TODO) _可选_ [*访问级别修改符(access-level-modifier)*](TODO) **typealias** [*类型别名名称*](../chapter3/05_Declarations.html#typealias_name)
> *类型别名名称* → [*标识符*](../chapter3/02_Lexical_Structure.html#identifier)
> *类型别名赋值* → **=** [*类型*](../chapter3/03_Types.html#type)
<!-- -->
> 函数声明语法
> *函数声明* → [*函数头*](../chapter3/05_Declarations.html#function_head) [*函数名*](../chapter3/05_Declarations.html#function_name) [*泛型参数从句*](GenericParametersAndArguments.html#generic_parameter_clause) _可选_ [*函数签名(Signature)*](../chapter3/05_Declarations.html#function_signature) [*函数体*](../chapter3/05_Declarations.html#function_body)
> *函数头* → [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ [*声明描述符(Specifiers)集*](../chapter3/05_Declarations.html#declaration_specifiers) _可选_ **func**
> *函数名* → [*标识符*](../chapter3/02_Lexical_Structure.html#identifier) | [*运算符*](../chapter3/02_Lexical_Structure.html#operator)
> *函数签名(Signature)* → [*parameter-clauses*](../chapter3/05_Declarations.html#parameter_clauses) **throws** _可选_ [*函数结果*](../chapter3/05_Declarations.html#function_result) _可选_
> *函数签名(Signature)* → [*parameter-clauses*](../chapter3/05_Declarations.html#parameter_clauses) **rethrows** [*函数结果*](../chapter3/05_Declarations.html#function_result) _可选_
> *函数结果* → **->** [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ [*类型*](../chapter3/03_Types.html#type)
> *函数体* → [*代码块*](../chapter3/05_Declarations.html#code_block)
> *参数从句* → [*参数从句*](../chapter3/05_Declarations.html#parameter_clause) [*parameter-clauses*](../chapter3/05_Declarations.html#parameter_clauses) _可选_
> *参数从句* → **(** **)** | **(** [*参数集*](../chapter3/05_Declarations.html#parameter_list) **...** _可选_ **)**
> *参数集* → [*参数*](../chapter3/05_Declarations.html#parameter) | [*参数*](../chapter3/05_Declarations.html#parameter) **,** [*参数集*](../chapter3/05_Declarations.html#parameter_list)
> *参数* → **inout** _可选_ **let** _可选_ [*外部参数名*](../chapter3/05_Declarations.html#parameter_name) _可选_ [*本地参数名*](../chapter3/05_Declarations.html#local_parameter_name) _可选_ [*类型注解*](../chapter3/03_Types.html#type_annotation) [*默认参数从句*](../chapter3/05_Declarations.html#default_argument_clause) _可选_
> *参数* → **inout** _可选_ **var** [*外部参数名*](../chapter3/05_Declarations.html#parameter_name) [*本地参数名*](../chapter3/05_Declarations.html#local_parameter_name) _可选_ [*类型注解*](../chapter3/03_Types.html#type_annotation) [*默认参数从句*](../chapter3/05_Declarations.html#default_argument_clause) _可选_
> *参数* → [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ [*类型*](../chapter3/03_Types.html#type)
> *外部参数名* → [*标识符*](../chapter3/02_Lexical_Structure.html#identifier) | **_**
> *本地参数名* → [*标识符*](../chapter3/02_Lexical_Structure.html#identifier) | **_**
> *默认参数从句* → **=** [*表达式*](../chapter3/04_Expressions.html#expression)
<!-- -->
> 枚举声明语法
> *枚举声明* → [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ [*访问级别修改器(access-level-modifier)*](TODO) _可选_ [*联合式枚举*](../chapter3/05_Declarations.html#union_style_enum)
> *枚举声明* → [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ [*访问级别修改器(access-level-modifier)*](TODO) _可选_ [*原始值式枚举(raw-value-style-enum)*](TODO)
> *联合式枚举* → **enum** [*枚举名*](../chapter3/05_Declarations.html#enum_name) [*泛型参数从句*](GenericParametersAndArguments.html#generic_parameter_clause) _可选_ [*类型继承从句(type-inheritance-clause)*](TODO) _可选_ **{** [*联合样式枚举成员*](../chapter3/05_Declarations.html#union_style_enum_members) _可选_ **}**
> *联合样式枚举成员* → [*union-style-enum-member*](../chapter3/05_Declarations.html#union_style_enum_member) [*联合样式枚举成员*](../chapter3/05_Declarations.html#union_style_enum_members) _可选_
> *联合样式枚举成员* → [*声明*](../chapter3/05_Declarations.html#declaration) | [*联合式(Union Style)的枚举case从句*](../chapter3/05_Declarations.html#union_style_enum_case_clause)
> *联合式(Union Style)的枚举case从句* → [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ **case** [*联合式(Union Style)的枚举case集*](../chapter3/05_Declarations.html#union_style_enum_case_list)
> *联合式(Union Style)的枚举case集* → [*联合式(Union Style)的case*](../chapter3/05_Declarations.html#union_style_enum_case) | [*联合式(Union Style)的case*](../chapter3/05_Declarations.html#union_style_enum_case) **,** [*联合式(Union Style)的枚举case集*](../chapter3/05_Declarations.html#union_style_enum_case_list)
> *联合式(Union Style)的枚举case* → [*枚举的case名*](../chapter3/05_Declarations.html#enum_case_name) [*元组类型*](../chapter3/03_Types.html#tuple_type) _可选_
> *枚举名* → [*标识符*](../chapter3/02_Lexical_Structure.html#identifier)
> *枚举的case名* → [*标识符*](../chapter3/02_Lexical_Structure.html#identifier)
> *原始值式枚举* → **enum** [*枚举名*](../chapter3/05_Declarations.html#enum_name) [*泛型参数从句*](GenericParametersAndArguments.html#generic_parameter_clause) _可选_ **:** [*类型标识*](../chapter3/03_Types.html#type_identifier) **{** [*原始值式枚举成员集*](../chapter3/05_Declarations.html#raw_value_style_enum_members) _可选_ **}**
> *原始值式枚举成员集* → [*原始值式枚举成员*](../chapter3/05_Declarations.html#raw_value_style_enum_member) [*原始值式枚举成员集*](../chapter3/05_Declarations.html#raw_value_style_enum_members) _可选_
> *原始值式枚举成员* → [*声明*](../chapter3/05_Declarations.html#declaration) | [*原始值式枚举case从句*](../chapter3/05_Declarations.html#raw_value_style_enum_case_clause)
> *原始值式枚举case从句* → [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ **case** [*原始值式枚举case集*](../chapter3/05_Declarations.html#raw_value_style_enum_case_list)
> *原始值式枚举case集* → [*原始值式枚举case*](../chapter3/05_Declarations.html#raw_value_style_enum_case) | [*原始值式枚举case*](../chapter3/05_Declarations.html#raw_value_style_enum_case) **,** [*原始值式枚举case集*](../chapter3/05_Declarations.html#raw_value_style_enum_case_list)
> *原始值式枚举case* → [*枚举的case名*](../chapter3/05_Declarations.html#enum_case_name) [*原始值赋值*](../chapter3/05_Declarations.html#raw_value_assignment) _可选_
> *原始值赋值* → **=** [*字面量*](../chapter3/02_Lexical_Structure.html#literal)
> *原始值字面量(raw-value-literal)* → [*数值字面量*](TODO) | [*字符串字面量*](TODO) | [*布尔字面量*](TODO)
<!-- -->
> 结构体声明语法
> *结构体声明* → [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ [*访问级别修改器(access-level-modifier)*](TODO) _可选_ **struct** [*结构体名称*](../chapter3/05_Declarations.html#struct_name) [*泛型参数从句*](GenericParametersAndArguments.html#generic_parameter_clause) _可选_ [*类型继承从句*](../chapter3/03_Types.html#type_inheritance_clause) _可选_ [*结构体主体*](../chapter3/05_Declarations.html#struct_body)
> *结构体名称* → [*标识符*](../chapter3/02_Lexical_Structure.html#identifier)
> *结构体主体* → **{** [*声明(Declarations)集*](../chapter3/05_Declarations.html#declarations) _可选_ **}**
<!-- -->
> 类声明语法
> *类声明* → [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ [*访问级别修改器(access-level-modifier)*](TODO) **class** [*类名*](../chapter3/05_Declarations.html#class_name) [*泛型参数从句*](GenericParametersAndArguments.html#generic_parameter_clause) _可选_ [*类型继承从句*](../chapter3/03_Types.html#type_inheritance_clause) _可选_ [*类主体*](../chapter3/05_Declarations.html#class_body)
> *类名* → [*标识符*](../chapter3/02_Lexical_Structure.html#identifier)
> *类主体* → **{** [*声明(Declarations)集*](../chapter3/05_Declarations.html#declarations) _可选_ **}**
<!-- -->
> 协议(Protocol)声明语法
> *协议声明* → [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_[*访问级别修改器(access-level-modifier)*](TODO) **protocol** [*协议名*](../chapter3/05_Declarations.html#protocol_name) [*类型继承从句*](../chapter3/03_Types.html#type_inheritance_clause) _可选_ [*协议主体*](../chapter3/05_Declarations.html#protocol_body)
> *协议名* → [*标识符*](../chapter3/02_Lexical_Structure.html#identifier)
> *协议主体* → **{** [*协议成员声明(Declarations)集*](../chapter3/05_Declarations.html#protocol_member_declarations) _可选_ **}**
> *协议成员声明* → [*协议属性声明*](../chapter3/05_Declarations.html#protocol_property_declaration)
> *协议成员声明* → [*协议方法声明*](../chapter3/05_Declarations.html#protocol_method_declaration)
> *协议成员声明* → [*协议构造器声明*](../chapter3/05_Declarations.html#protocol_initializer_declaration)
> *协议成员声明* → [*协议下标声明*](../chapter3/05_Declarations.html#protocol_subscript_declaration)
> *协议成员声明* → [*协议关联类型声明*](../chapter3/05_Declarations.html#protocol_associated_type_declaration)
> *协议成员声明(Declarations)集* → [*协议成员声明*](../chapter3/05_Declarations.html#protocol_member_declaration) [*协议成员声明(Declarations)集*](../chapter3/05_Declarations.html#protocol_member_declarations) _可选_
<!-- -->
> 协议属性声明语法
> *协议属性声明* → [*变量声明头(Head)*](../chapter3/05_Declarations.html#variable_declaration_head) [*变量名*](../chapter3/05_Declarations.html#variable_name) [*类型注解*](../chapter3/03_Types.html#type_annotation) [*getter-setter关键字(Keyword)块*](../chapter3/05_Declarations.html#getter_setter_keyword_block)
<!-- -->
> 协议方法声明语法
> *协议方法声明* → [*函数头*](../chapter3/05_Declarations.html#function_head) [*函数名*](../chapter3/05_Declarations.html#function_name) [*泛型参数从句*](GenericParametersAndArguments.html#generic_parameter_clause) _可选_ [*函数签名(Signature)*](../chapter3/05_Declarations.html#function_signature)
<!-- -->
> 协议构造器声明语法
> *协议构造器声明* → [*构造器头(Head)*](../chapter3/05_Declarations.html#initializer_head) [*泛型参数从句*](GenericParametersAndArguments.html#generic_parameter_clause) _可选_ [*参数从句*](../chapter3/05_Declarations.html#parameter_clause)
<!-- -->
> 协议下标声明语法
> *协议下标声明* → [*下标头(Head)*](../chapter3/05_Declarations.html#subscript_head) [*下标结果(Result)*](../chapter3/05_Declarations.html#subscript_result) [*getter-setter关键字(Keyword)块*](../chapter3/05_Declarations.html#getter_setter_keyword_block)
<!-- -->
> 协议关联类型声明语法
> *协议关联类型声明* → [*类型别名头(Head)*](../chapter3/05_Declarations.html#typealias_head) [*类型继承从句*](../chapter3/03_Types.html#type_inheritance_clause) _可选_ [*类型别名赋值*](../chapter3/05_Declarations.html#typealias_assignment) _可选_
<!-- -->
> 构造器声明语法
> *构造器声明* → [*构造器头(Head)*](../chapter3/05_Declarations.html#initializer_head) [*泛型参数从句*](GenericParametersAndArguments.html#generic_parameter_clause) _可选_ [*参数从句*](../chapter3/05_Declarations.html#parameter_clause) [*构造器主体*](../chapter3/05_Declarations.html#initializer_body)
> *构造器头(Head)* → [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ [*声明修改器集(declaration-modifiers)*](TODO) _可选_ **init**
> *构造器头(Head)* → [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ [*声明修改器集(declaration-modifiers)*](TODO) _可选_ **init ?**
> *构造器头(Head)* → [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ [*声明修改器集(declaration-modifiers)*](TODO) _可选_ **init !**
> *构造器主体* → [*代码块*](../chapter3/05_Declarations.html#code_block)
<!-- -->
> 析构器声明语法
> *析构器声明* → [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ **deinit** [*代码块*](../chapter3/05_Declarations.html#code_block)
<!-- -->
> 扩展(Extension)声明语法
> *扩展声明* → [*访问级别修改器*](TODO) _可选_ **extension** [*类型标识*](../chapter3/03_Types.html#type_identifier) [*类型继承从句*](../chapter3/03_Types.html#type_inheritance_clause) _可选_ [*extension-body*](../chapter3/05_Declarations.html#extension_body)
> *extension-body* → **{** [*声明(Declarations)集*](../chapter3/05_Declarations.html#declarations) _可选_ **}**
<!-- -->
> 下标声明语法
> *下标声明* → [*下标头(Head)*](../chapter3/05_Declarations.html#subscript_head) [*下标结果(Result)*](../chapter3/05_Declarations.html#subscript_result) [*代码块*](../chapter3/05_Declarations.html#code_block)
> *下标声明* → [*下标头(Head)*](../chapter3/05_Declarations.html#subscript_head) [*下标结果(Result)*](../chapter3/05_Declarations.html#subscript_result) [*getter-setter块*](../chapter3/05_Declarations.html#getter_setter_block)
> *下标声明* → [*下标头(Head)*](../chapter3/05_Declarations.html#subscript_head) [*下标结果(Result)*](../chapter3/05_Declarations.html#subscript_result) [*getter-setter关键字(Keyword)块*](../chapter3/05_Declarations.html#getter_setter_keyword_block)
> *下标头(Head)* → [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ [*声明修改器(declaration-modifiers)*](TODO) _可选_ **subscript** [*参数从句*](../chapter3/05_Declarations.html#parameter_clause)
> *下标结果(Result)* → **->** [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ [*类型*](../chapter3/03_Types.html#type)
<!-- -->
> 运算符声明语法
> *运算符声明* → [*前置运算符声明*](../chapter3/05_Declarations.html#prefix_operator_declaration) | [*后置运算符声明*](../chapter3/05_Declarations.html#postfix_operator_declaration) | [*中置运算符声明*](../chapter3/05_Declarations.html#infix_operator_declaration)
> *前置运算符声明* → **prefix** **运算符** [*运算符*](../chapter3/02_Lexical_Structure.html#operator) **{** **}**
> *后置运算符声明* → **postfix** **运算符** [*运算符*](../chapter3/02_Lexical_Structure.html#operator) **{** **}**
> *中置运算符声明* → **infix** **运算符** [*运算符*](../chapter3/02_Lexical_Structure.html#operator) **{** [*中置运算符属性集*](../chapter3/05_Declarations.html#infix_operator_attributes) _可选_ **}**
> *中置运算符属性集* → [*优先级从句*](../chapter3/05_Declarations.html#precedence_clause) _可选_ [*结和性从句*](../chapter3/05_Declarations.html#associativity_clause) _可选_
> *优先级从句* → **precedence** [*优先级水平*](../chapter3/05_Declarations.html#precedence_level)
> *优先级水平* → 数值 0 到 255首末项包括在内
> *结和性从句* → **associativity** [*结和性*](../chapter3/05_Declarations.html#associativity)
> *结和性* → **left** | **right** | **none**
<!-- -->
声明修改器语法
> *声明修改器* → **类** | **便捷(convenience)** | **动态(dynamic)** | **final** | **中置(infix)** | **lazy** | **可变(mutating)** | **不可变(nonmutating)** | **可选(optional)** | **改写(override)** | **后置** | **前置** | **required** | **static** | **unowned** | **unowned(safe)** | **unowned(unsafe)** | **弱(weak)**
> *声明修改器* → [*访问级别声明器(access-level-modifier)*](TODO)
> *声明修改集* → [*声明修改器*](TODO) [*声明修改器集*](TODO) _可选_
> *访问级别修改器* → **内部的** | **内部的(set)**
> *访问级别修改器* → **私有的** | **私有的(set)**
> *访问级别修改器* → **公共的**
| **公共的(set)**
> *访问级别修改器集* →[*访问级别修改器*](TODO) [*访问级别修改器集*](TODO) _可选_
<a name="patterns"></a>
## 模式
> 模式(Patterns) 语法
> *模式* → [*通配符模式*](../chapter3/07_Patterns.html#wildcard_pattern) [*类型注解*](../chapter3/03_Types.html#type_annotation) _可选_
> *模式* → [*标识符模式*](../chapter3/07_Patterns.html#identifier_pattern) [*类型注解*](../chapter3/03_Types.html#type_annotati Value Bindingon ) _可选_
> *模式* → [*值绑定模式*](../chapter3/07_Patterns.html#value_binding_pattern)
> *模式* → [*元组模式*](../chapter3/07_Patterns.html#tuple_pattern) [*类型注解*](../chapter3/03_Types.html#type_annotation) _可选_
> *模式* → [*枚举个例模式*](../chapter3/07_Patterns.html#enum_case_pattern)
> *模式* → [*可选模式*](TODO)
> *模式* → [*类型转换模式*](../chapter3/07_Patterns.html#type_casting_pattern)
> *模式* → [*表达式模式*](../chapter3/07_Patterns.html#expression_pattern)
<!-- -->
> 通配符模式语法
> *通配符模式* → **_**
<!-- -->
> 标识符模式语法
> *标识符模式* → [*标识符*](../chapter3/02_Lexical_Structure.html#identifier)
<!-- -->
> 值绑定(Value Binding)模式语法
> *值绑定模式* → **var** [*模式*](../chapter3/07_Patterns.html#pattern) | **let** [*模式*](../chapter3/07_Patterns.html#pattern)
<!-- -->
> 元组模式语法
> *元组模式* → **(** [*元组模式元素集*](../chapter3/07_Patterns.html#tuple_pattern_element_list) _可选_ **)**
> *元组模式元素集* → [*元组模式元素*](../chapter3/07_Patterns.html#tuple_pattern_element) | [*元组模式元素*](../chapter3/07_Patterns.html#tuple_pattern_element) **,** [*元组模式元素集*](../chapter3/07_Patterns.html#tuple_pattern_element_list)
> *元组模式元素* → [*模式*](../chapter3/07_Patterns.html#pattern)
<!-- -->
> 枚举用例模式语法
> *enum-case-pattern* → [*类型标识*](../chapter3/03_Types.html#type_identifier) _可选_ **.** [*枚举的case名*](../chapter3/05_Declarations.html#enum_case_name) [*元组模式*](../chapter3/07_Patterns.html#tuple_pattern) _可选_
<!-- -->
> 可选模式语法
> *可选模式* → [*识别符模式*](TODO) **?**
<!-- -->
> 类型转换模式语法
> *类型转换模式(type-casting-pattern)* → [*is模式*](../chapter3/07_Patterns.html#is_pattern) | [*as模式*](../chapter3/07_Patterns.html#as_pattern)
> *is模式* → **is** [*类型*](../chapter3/03_Types.html#type)
> *as模式* → [*模式*](../chapter3/07_Patterns.html#pattern) **as** [*类型*](../chapter3/03_Types.html#type)
<!-- -->
> 表达式模式语法
> *表达式模式* → [*表达式*](../chapter3/04_Expressions.html#expression)
<a name="attributes"></a>
## 属性
> 属性语法
> *属性* → **@** [*属性名*](../chapter3/06_Attributes.html#attribute_name) [*属性参数从句*](../chapter3/06_Attributes.html#attribute_argument_clause) _可选_
> *属性名* → [*标识符*](../chapter3/02_Lexical_Structure.html#identifier)
> *属性参数从句* → **(** [*平衡令牌集*](../chapter3/06_Attributes.html#balanced_tokens) _可选_ **)**
> *属性(Attributes)集* → [*属性*](../chapter3/06_Attributes.html#attribute) [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_
> *平衡令牌集* → [*平衡令牌*](../chapter3/06_Attributes.html#balanced_token) [*平衡令牌集*](../chapter3/06_Attributes.html#balanced_tokens) _可选_
> *平衡令牌* → **(** [*平衡令牌集*](../chapter3/06_Attributes.html#balanced_tokens) _可选_ **)**
> *平衡令牌* → **[** [*平衡令牌集*](../chapter3/06_Attributes.html#balanced_tokens) _可选_ **]**
> *平衡令牌* → **{** [*平衡令牌集*](../chapter3/06_Attributes.html#balanced_tokens) _可选_ **}**
> *平衡令牌* → **任意标识符, 关键字, 字面量或运算符**
> *平衡令牌* → **任意标点除了(, ), [, ], {, 或 }**
<a name="expressions"></a>
## 表达式
> 表达式语法
> *表达式* → [*try-operator*](TODO) _可选_ [*前置表达式*](../chapter3/04_Expressions.html#prefix_expression) [*二元表达式集*](../chapter3/04_Expressions.html#binary_expressions) _可选_
> *表达式集* → [*表达式*](../chapter3/04_Expressions.html#expression) | [*表达式*](../chapter3/04_Expressions.html#expression) **,** [*表达式集*](../chapter3/04_Expressions.html#expression_list)
<!-- -->
> 前置表达式语法
> *前置表达式* → [*前置运算符*](../chapter3/02_Lexical_Structure.html#prefix_operator) _可选_ [*后置表达式*](../chapter3/04_Expressions.html#postfix_expression)
> *前置表达式* → [*写入写出(in-out)表达式*](../chapter3/04_Expressions.html#in_out_expression)
> *写入写出(in-out)表达式* → **&** [*标识符*](../chapter3/02_Lexical_Structure.html#identifier)
<!-- -->
> try表达式语法
> *try-operator* → **try** | **try !**
<!-- -->
> 二元表达式语法
> *二元表达式* → [*二元运算符*](../chapter3/02_Lexical_Structure.html#binary_operator) [*前置表达式*](../chapter3/04_Expressions.html#prefix_expression)
> *二元表达式* → [*赋值运算符*](../chapter3/04_Expressions.html#assignment_operator) [*try运算符*](TODO) _可选_ [*前置表达式*](../chapter3/04_Expressions.html#prefix_expression)
> *二元表达式* → [*条件运算符*](../chapter3/04_Expressions.html#conditional_operator) [*try运算符*](TODO) _可选_ [*前置表达式*](../chapter3/04_Expressions.html#prefix_expression)
> *二元表达式* → [*类型转换运算符*](../chapter3/04_Expressions.html#type_casting_operator)
> *二元表达式集* → [*二元表达式*](../chapter3/04_Expressions.html#binary_expression) [*二元表达式集*](../chapter3/04_Expressions.html#binary_expressions) _可选_
<!-- -->
> 赋值运算符语法
> *赋值运算符* → **=**
<!-- -->
> 三元条件运算符语法
> *三元条件运算符* → **?** [*表达式*](../chapter3/04_Expressions.html#expression) **:**
<!-- -->
> 类型转换运算符语法
> *类型转换运算符* → **is** [*类型*](../chapter3/03_Types.html#type)
> *类型转换运算符* → **as** [*类型*](../chapter3/03_Types.html#type)
> *类型转换运算符* → **as ?** [*类型*](../chapter3/03_Types.html#type)
> *类型转换运算符* → **as !** [*类型*](../chapter3/03_Types.html#type)
<!-- -->
> 主表达式语法
> *主表达式* → [*标识符*](../chapter3/02_Lexical_Structure.html#identifier) [*泛型参数从句*](GenericParametersAndArguments.html#generic_argument_clause) _可选_
> *主表达式* → [*字面量表达式*](../chapter3/04_Expressions.html#literal_expression)
> *主表达式* → [*self表达式*](../chapter3/04_Expressions.html#self_expression)
> *主表达式* → [*超类表达式*](../chapter3/04_Expressions.html#superclass_expression)
> *主表达式* → [*闭包表达式*](../chapter3/04_Expressions.html#closure_expression)
> *主表达式* → [*圆括号表达式*](../chapter3/04_Expressions.html#parenthesized_expression)
> *主表达式* → [*隐式成员表达式*](../chapter3/04_Expressions.html#implicit_member_expression)
> *主表达式* → [*通配符表达式*](../chapter3/04_Expressions.html#wildcard_expression)
<!-- -->
> 字面量表达式语法
> *字面量表达式* → [*字面量*](../chapter3/02_Lexical_Structure.html#literal)
> *字面量表达式* → [*数组字面量*](../chapter3/04_Expressions.html#array_literal) | [*字典字面量*](../chapter3/04_Expressions.html#dictionary_literal)
> *字面量表达式* → **&#95;&#95;FILE&#95;&#95;** | **&#95;&#95;LINE&#95;&#95;** | **&#95;&#95;COLUMN&#95;&#95;** | **&#95;&#95;FUNCTION&#95;&#95;**
> *数组字面量* → **[** [*数组字面量项集*](../chapter3/04_Expressions.html#array_literal_items) _可选_ **]**
> *数组字面量项集* → [*数组字面量项*](../chapter3/04_Expressions.html#array_literal_item) **,** _可选_ | [*数组字面量项*](../chapter3/04_Expressions.html#array_literal_item) **,** [*数组字面量项集*](../chapter3/04_Expressions.html#array_literal_items)
> *数组字面量项* → [*表达式*](../chapter3/04_Expressions.html#expression)
> *字典字面量* → **[** [*字典字面量项集*](../chapter3/04_Expressions.html#dictionary_literal_items) **]** | **[** **:** **]**
> *字典字面量项集* → [*字典字面量项*](../chapter3/04_Expressions.html#dictionary_literal_item) **,** _可选_ | [*字典字面量项*](../chapter3/04_Expressions.html#dictionary_literal_item) **,** [*字典字面量项集*](../chapter3/04_Expressions.html#dictionary_literal_items)
> *字典字面量项* → [*表达式*](../chapter3/04_Expressions.html#expression) **:** [*表达式*](../chapter3/04_Expressions.html#expression)
<!-- -->
> Self 表达式语法
> *self表达式* → **self**
> *self表达式* → **self** **.** [*标识符*](../chapter3/02_Lexical_Structure.html#identifier)
> *self表达式* → **self** **[** [*表达式*](../chapter3/04_Expressions.html#expression) **]**
> *self表达式* → **self** **.** **init**
<!-- -->
> 超类表达式语法
> *超类表达式* → [*超类方法表达式*](../chapter3/04_Expressions.html#superclass_method_expression) | [*超类下标表达式*](../chapter3/04_Expressions.html#超类下标表达式) | [*超类构造器表达式*](../chapter3/04_Expressions.html#superclass_initializer_expression)
> *超类方法表达式* → **super** **.** [*标识符*](../chapter3/02_Lexical_Structure.html#identifier)
> *超类下标表达式* → **super** **[** [*表达式*](../chapter3/04_Expressions.html#expression) **]**
> *超类构造器表达式* → **super** **.** **init**
<!-- -->
> 闭包表达式语法
> *闭包表达式* → **{** [*闭包签名(Signational)*](../chapter3/04_Expressions.html#closure_signature) _可选_ [*多条语句(Statements)*](../chapter3/10_Statements.html#statements) **}**
> *闭包签名(Signational)* → [*参数从句*](../chapter3/05_Declarations.html#parameter_clause) [*函数结果*](../chapter3/05_Declarations.html#function_result) _可选_ **in**
> *闭包签名(Signational)* → [*标识符集*](../chapter3/02_Lexical_Structure.html#identifier_list) [*函数结果*](../chapter3/05_Declarations.html#function_result) _可选_ **in**
> *闭包签名(Signational)* → [*捕获(Capature)集*](../chapter3/04_Expressions.html#capture_list) [*参数从句*](../chapter3/05_Declarations.html#parameter_clause) [*函数结果*](../chapter3/05_Declarations.html#function_result) _可选_ **in**
> *闭包签名(Signational)* → [*捕获(Capature)集*](../chapter3/04_Expressions.html#capture_list) [*标识符集*](../chapter3/02_Lexical_Structure.html#identifier_list) [*函数结果*](../chapter3/05_Declarations.html#function_result) _可选_ **in**
> *闭包签名(Signational)* → [*捕获(Capature)集*](../chapter3/04_Expressions.html#capture_list) **in**
> *捕获(Capature)集* → **[** [*捕获(Capature)说明符*](../chapter3/04_Expressions.html#capture_specifier) [*表达式*](../chapter3/04_Expressions.html#expression) **]**
> *捕获(Capature)说明符* → **weak** | **unowned** | **unowned(safe)** | **unowned(unsafe)**
<!-- -->
> 隐式成员表达式语法
> *隐式成员表达式* → **.** [*标识符*](../chapter3/02_Lexical_Structure.html#identifier)
<!-- -->
> 圆括号表达式(Parenthesized Expression)语法
> *圆括号表达式* → **(** [*表达式元素集*](../chapter3/04_Expressions.html#expression_element_list) _可选_ **)**
> *表达式元素集* → [*表达式元素*](../chapter3/04_Expressions.html#expression_element) | [*表达式元素*](../chapter3/04_Expressions.html#expression_element) **,** [*表达式元素集*](../chapter3/04_Expressions.html#expression_element_list)
> *表达式元素* → [*表达式*](../chapter3/04_Expressions.html#expression) | [*标识符*](../chapter3/02_Lexical_Structure.html#identifier) **:** [*表达式*](../chapter3/04_Expressions.html#expression)
<!-- -->
> 通配符表达式语法
> *通配符表达式* → **_**
<!-- -->
> 后置表达式语法
> *后置表达式* → [*主表达式*](../chapter3/04_Expressions.html#primary_expression)
> *后置表达式* → [*后置表达式*](../chapter3/04_Expressions.html#postfix_expression) [*后置运算符*](../chapter3/02_Lexical_Structure.html#postfix_operator)
> *后置表达式* → [*函数调用表达式*](../chapter3/04_Expressions.html#function_call_expression)
> *后置表达式* → [*构造器表达式*](../chapter3/04_Expressions.html#initializer_expression)
> *后置表达式* → [*显示成员表达式*](../chapter3/04_Expressions.html#explicit_member_expression)
> *后置表达式* → [*后置self表达式*](../chapter3/04_Expressions.html#postfix_self_expression)
> *后置表达式* → [*动态类型表达式*](../chapter3/04_Expressions.html#dynamic_type_expression)
> *后置表达式* → [*下标表达式*](../chapter3/04_Expressions.html#subscript_expression)
> *后置表达式* → [*强制取值(Forced Value)表达式*](../chapter3/04_Expressions.html#forced_value_expression)
> *后置表达式* → [*可选链(Optional Chaining)表达式*](../chapter3/04_Expressions.html#optional_chaining_expression)
<!-- -->
> 函数调用表达式语法
> *函数调用表达式* → [*后置表达式*](../chapter3/04_Expressions.html#postfix_expression) [*圆括号表达式*](../chapter3/04_Expressions.html#parenthesized_expression)
> *函数调用表达式* → [*后置表达式*](../chapter3/04_Expressions.html#postfix_expression) [*圆括号表达式*](../chapter3/04_Expressions.html#parenthesized_expression) _可选_ [*后置闭包(Trailing Closure)*](../chapter3/04_Expressions.html#trailing_closure)
> *后置闭包(Trailing Closure)* → [*闭包表达式*](../chapter3/04_Expressions.html#closure_expression)
<!-- -->
> 构造器表达式语法
> *构造器表达式* → [*后置表达式*](../chapter3/04_Expressions.html#postfix_expression) **.** **init**
<!-- -->
> 显式成员表达式语法
> *显示成员表达式* → [*后置表达式*](../chapter3/04_Expressions.html#postfix_expression) **.** [*十进制数字*](../chapter3/02_Lexical_Structure.html#decimal_digit)
> *显示成员表达式* → [*后置表达式*](../chapter3/04_Expressions.html#postfix_expression) **.** [*标识符*](../chapter3/02_Lexical_Structure.html#identifier) [*泛型参数从句*](GenericParametersAndArguments.html#generic_argument_clause) _可选_
<!-- -->
> 后置Self 表达式语法
> *后置self表达式* → [*后置表达式*](../chapter3/04_Expressions.html#postfix_expression) **.** **self**
<!-- -->
> 动态类型表达式语法
> *动态类型表达式* → [*后置表达式*](../chapter3/04_Expressions.html#postfix_expression) **.** **dynamicType**
<!-- -->
> 附属脚本表达式语法
> *附属脚本表达式* → [*后置表达式*](../chapter3/04_Expressions.html#postfix_expression) **[** [*表达式集*](../chapter3/04_Expressions.html#expression_list) **]**
<!-- -->
> 强制取值(Forced Value)语法
> *强制取值(Forced Value)表达式* → [*后置表达式*](../chapter3/04_Expressions.html#postfix_expression) **!**
<!-- -->
> 可选链表达式语法
> *可选链表达式* → [*后置表达式*](../chapter3/04_Expressions.html#postfix_expression) **?**
<a name="lexical_structure"></a>
## 词法结构
> 标识符语法
> *标识符* → [*标识符头(Head)*](../chapter3/02_Lexical_Structure.html#identifier_head) [*标识符字符集*](../chapter3/02_Lexical_Structure.html#identifier_characters) _可选_
> *标识符* → [*标识符头(Head)*](../chapter3/02_Lexical_Structure.html#identifier_head) [*标识符字符集*](../chapter3/02_Lexical_Structure.html#identifier_characters) _可选_
> *标识符* → [*隐式参数名*](../chapter3/02_Lexical_Structure.html#implicit_parameter_name)
> *标识符集* → [*标识符*](../chapter3/02_Lexical_Structure.html#identifier) | [*标识符*](../chapter3/02_Lexical_Structure.html#identifier) **,** [*标识符集*](../chapter3/02_Lexical_Structure.html#identifier_list)
> *标识符头(Head)* → Upper- or lowercase letter A through Z
> *标识符头(Head)* → _
> *标识符头(Head)* → U+00A8, U+00AA, U+00AD, U+00AF, U+00B2U+00B5, or U+00B7U+00BA
> *标识符头(Head)* → U+00BCU+00BE, U+00C0U+00D6, U+00D8U+00F6, or U+00F8U+00FF
> *标识符头(Head)* → U+0100U+02FF, U+0370U+167F, U+1681U+180D, or U+180FU+1DBF
> *标识符头(Head)* → U+1E00U+1FFF
> *标识符头(Head)* → U+200BU+200D, U+202AU+202E, U+203FU+2040, U+2054, or U+2060U+206F
> *标识符头(Head)* → U+2070U+20CF, U+2100U+218F, U+2460U+24FF, or U+2776U+2793
> *标识符头(Head)* → U+2C00U+2DFF or U+2E80U+2FFF
> *标识符头(Head)* → U+3004U+3007, U+3021U+302F, U+3031U+303F, or U+3040U+D7FF
> *标识符头(Head)* → U+F900U+FD3D, U+FD40U+FDCF, U+FDF0U+FE1F, or U+FE30U+FE44
> *标识符头(Head)* → U+FE47U+FFFD
> *标识符头(Head)* → U+10000U+1FFFD, U+20000U+2FFFD, U+30000U+3FFFD, or U+40000U+4FFFD
> *标识符头(Head)* → U+50000U+5FFFD, U+60000U+6FFFD, U+70000U+7FFFD, or U+80000U+8FFFD
> *标识符头(Head)* → U+90000U+9FFFD, U+A0000U+AFFFD, U+B0000U+BFFFD, or U+C0000U+CFFFD
> *标识符头(Head)* → U+D0000U+DFFFD or U+E0000U+EFFFD
> *标识符字符* → 数值 0 到 9
> *标识符字符* → U+0300U+036F, U+1DC0U+1DFF, U+20D0U+20FF, or U+FE20U+FE2F
> *标识符字符* → [*标识符头(Head)*](../chapter3/02_Lexical_Structure.html#identifier_head)
> *标识符字符集* → [*标识符字符*](../chapter3/02_Lexical_Structure.html#identifier_character) [*标识符字符集*](../chapter3/02_Lexical_Structure.html#identifier_characters) _可选_
> *隐式参数名* → **$** [*十进制数字集*](../chapter3/02_Lexical_Structure.html#decimal_digits)
<!-- -->
> 字面量语法
> *字面量* → [*数值型字面量*](../chapter3/02_Lexical_Structure.html#integer_literal) | [*字符串字面量*](../chapter3/02_Lexical_Structure.html#floating_point_literal) | [*布尔字面量*](../chapter3/02_Lexical_Structure.html#string_literal) | [*空字面量*](TODO)
> *数值型字面量* → **-** _可选_ [*整形字面量*](TODO) | **-** _可选_ [*浮点型字面量*](TODO)
> *布尔字面量* → **true** | **false**
> *空字面量* → **nil**
<!-- -->
> 整型字面量语法
> *整型字面量* → [*二进制字面量*](../chapter3/02_Lexical_Structure.html#binary_literal)
> *整型字面量* → [*八进制字面量*](../chapter3/02_Lexical_Structure.html#octal_literal)
> *整型字面量* → [*十进制字面量*](../chapter3/02_Lexical_Structure.html#decimal_literal)
> *整型字面量* → [*十六进制字面量*](../chapter3/02_Lexical_Structure.html#hexadecimal_literal)
> *二进制字面量* → **0b** [*二进制数字*](../chapter3/02_Lexical_Structure.html#binary_digit) [*二进制字面量字符集*](../chapter3/02_Lexical_Structure.html#binary_literal_characters) _可选_
> *二进制数字* → 数值 0 到 1
> *二进制字面量字符* → [*二进制数字*](../chapter3/02_Lexical_Structure.html#binary_digit) | **_**
> *二进制字面量字符集* → [*二进制字面量字符*](../chapter3/02_Lexical_Structure.html#binary_literal_character) [*二进制字面量字符集*](../chapter3/02_Lexical_Structure.html#binary_literal_characters) _可选_
> *八进制字面量* → **0o** [*八进制数字*](../chapter3/02_Lexical_Structure.html#octal_digit) [*八进制字符集*](../chapter3/02_Lexical_Structure.html#octal_literal_characters) _可选_
> *八进字数字* → 数值 0 到 7
> *八进制字符* → [*八进制数字*](../chapter3/02_Lexical_Structure.html#octal_digit) | **_**
> *八进制字符集* → [*八进制字符*](../chapter3/02_Lexical_Structure.html#octal_literal_character) [*八进制字符集*](../chapter3/02_Lexical_Structure.html#octal_literal_characters) _可选_
> *十进制字面量* → [*十进制数字*](../chapter3/02_Lexical_Structure.html#decimal_digit) [*十进制字符集*](../chapter3/02_Lexical_Structure.html#decimal_literal_characters) _可选_
> *十进制数字* → 数值 0 到 9
> *十进制数字集* → [*十进制数字*](../chapter3/02_Lexical_Structure.html#decimal_digit) [*十进制数字集*](../chapter3/02_Lexical_Structure.html#decimal_digits) _可选_
> *十进制字面量字符* → [*十进制数字*](../chapter3/02_Lexical_Structure.html#decimal_digit) | **_**
> *十进制字面量字符集* → [*十进制字面量字符*](../chapter3/02_Lexical_Structure.html#decimal_literal_character) [*十进制字面量字符集*](../chapter3/02_Lexical_Structure.html#decimal_literal_characters) _可选_
> *十六进制字面量* → **0x** [*十六进制数字*](../chapter3/02_Lexical_Structure.html#hexadecimal_digit) [*十六进制字面量字符集*](../chapter3/02_Lexical_Structure.html#hexadecimal_literal_characters) _可选_
> *十六进制数字* → 数值 0 到 9, a through f, or A through F
> *十六进制字符* → [*十六进制数字*](../chapter3/02_Lexical_Structure.html#hexadecimal_digit) | **_**
> *十六进制字面量字符集* → [*十六进制字符*](../chapter3/02_Lexical_Structure.html#hexadecimal_literal_character) [*十六进制字面量字符集*](../chapter3/02_Lexical_Structure.html#hexadecimal_literal_characters) _可选_
<!-- -->
> 浮点型字面量语法
> *浮点数字面量* → [*十进制字面量*](../chapter3/02_Lexical_Structure.html#decimal_literal) [*十进制分数*](../chapter3/02_Lexical_Structure.html#decimal_fraction) _可选_ [*十进制指数*](../chapter3/02_Lexical_Structure.html#decimal_exponent) _可选_
> *浮点数字面量* → [*十六进制字面量*](../chapter3/02_Lexical_Structure.html#hexadecimal_literal) [*十六进制分数*](../chapter3/02_Lexical_Structure.html#hexadecimal_fraction) _可选_ [*十六进制指数*](../chapter3/02_Lexical_Structure.html#hexadecimal_exponent)
> *十进制分数* → **.** [*十进制字面量*](../chapter3/02_Lexical_Structure.html#decimal_literal)
> *十进制指数* → [*浮点数e*](../chapter3/02_Lexical_Structure.html#floating_point_e) [*正负号*](../chapter3/02_Lexical_Structure.html#sign) _可选_ [*十进制字面量*](../chapter3/02_Lexical_Structure.html#decimal_literal)
> *十六进制分数* → **.** [*十六进制数*](../chapter3/02_Lexical_Structure.html#hexadecimal_literal)
[*十六进制字面量字符集*](TODO)_可选_
> *十六进制指数* → [*浮点数p*](../chapter3/02_Lexical_Structure.html#floating_point_p) [*正负号*](../chapter3/02_Lexical_Structure.html#sign) _可选_ [*十六进制字面量*](../chapter3/02_Lexical_Structure.html#hexadecimal_literal)
> *浮点数e* → **e** | **E**
> *浮点数p* → **p** | **P**
> *正负号* → **+** | **-**
<!-- -->
> 字符串型字面量语法
> *字符串字面量* → **"** [*引用文本*](../chapter3/02_Lexical_Structure.html#quoted_text) **"**
> *引用文本* → [*引用文本条目*](../chapter3/02_Lexical_Structure.html#quoted_text_item) [*引用文本*](../chapter3/02_Lexical_Structure.html#quoted_text) _可选_
> *引用文本条目* → [*转义字符*](../chapter3/02_Lexical_Structure.html#escaped_character)
> *引用文本条目* → **(** [*表达式*](../chapter3/04_Expressions.html#expression) **)**
> *引用文本条目* → 除了"­, \­, U+000A, or U+000D的所有Unicode的字符
> *转义字符* → **/0** | **\\** | **\t** | **\n** | **\r** | **\"** | **\'**
> *转义字符* → **\u** **{** [*十六进制标量数字集*](TODO) **}**
> *unicode标量数字集* → Between one and eight hexadecimal digits
<!-- -->
> 运算符语法语法
> *运算符* → [*运算符头*](../chapter3/02_Lexical_Structure.html#operator_character) [*运算符字符集*](../chapter3/02_Lexical_Structure.html#operator) _可选_
> *运算符* → [*点运算符头*](TODO) [*点运算符字符集*](TODO) _可选_
> *运算符字符* → **/** | **=** | **-** | **+** | **!** | **&#42;** | **%** | **<** | **>** | **&** | **|** | **^** | **~** | **?**
> *运算符头* → U+00A1U+00A7
> *运算符头* → U+00A9 or U+00AB
> *运算符头* → U+00AC or U+00AE
> *运算符头* → U+00B0U+00B1, U+00B6, U+00BB, U+00BF, U+00D7, or U+00F7
> *运算符头* → U+2016U+2017 or U+2020U+2027
> *运算符头* → U+2030U+203E
> *运算符头* → U+2041U+2053
> *运算符头* → U+2055U+205E
> *运算符头* → U+2190U+23FF
> *运算符头* → U+2500U+2775
> *运算符头* → U+2794U+2BFF
> *运算符头* → U+2E00U+2E7F
> *运算符头* → U+3001U+3003
> *运算符头* → U+3008U+3030
> *运算符字符* → [*运算符头*](TODO)
> *运算符字符* → U+0300U+036F
> *运算符字符* → U+1DC0U+1DFF
> *运算符字符* → U+20D0U+20FF
> *运算符字符* → U+FE00U+FE0F
> *运算符字符* → U+FE20U+FE2F
> *运算符字符* → U+E0100U+E01EF
> *运算符字符集* → [*运算符字符*](TODO) [*运算符字符集*](TODO)_可选_
> *点运算符头* → **..**
> *点运算符字符* → **.** | [*运算符字符*](TODO)
> *点运算符字符集* → [*点运算符字符*](TODO) [*点运算符字符集*](TODO) _可选_
> *二元运算符* → [*运算符*](../chapter3/02_Lexical_Structure.html#operator)
> *前置运算符* → [*运算符*](../chapter3/02_Lexical_Structure.html#operator)
> *后置运算符* → [*运算符*](../chapter3/02_Lexical_Structure.html#operator)
<a name="types"></a>
## 类型
> 类型语法
> *类型* → [*数组类型*](../chapter3/03_Types.html#array_type) | [*字典类型*](TODO) | [*函数类型*](../chapter3/03_Types.html#function_type) | [*类型标识符*](../chapter3/03_Types.html#type_identifier) | [*元组类型*](../chapter3/03_Types.html#tuple_type) | [*可选类型*](../chapter3/03_Types.html#optional_type) | [*隐式解析可选类型*](../chapter3/03_Types.html#implicitly_unwrapped_optional_type) | [*协议合成类型*](../chapter3/03_Types.html#protocol_composition_type) | [*元型类型*](../chapter3/03_Types.html#metatype_type)
<!-- -->
> 类型注解语法
> *类型注解* → **:** [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ [*类型*](../chapter3/03_Types.html#type)
<!-- -->
> 类型标识语法
> *类型标识* → [*类型名称*](../chapter3/03_Types.html#type_name) [*泛型参数从句*](GenericParametersAndArguments.html#generic_argument_clause) _可选_ | [*类型名称*](../chapter3/03_Types.html#type_name) [*泛型参数从句*](GenericParametersAndArguments.html#generic_argument_clause) _可选_ **.** [*类型标识符*](../chapter3/03_Types.html#type_identifier)
> *类型名* → [*标识符*](../chapter3/02_Lexical_Structure.html#identifier)
<!-- -->
> 元组类型语法
> *元组类型* → **(** [*元组类型主体*](../chapter3/03_Types.html#tuple_type_body) _可选_ **)**
> *元组类型主体* → [*元组类型的元素集*](../chapter3/03_Types.html#tuple_type_element_list) **...** _可选_
> *元组类型的元素集* → [*元组类型的元素*](../chapter3/03_Types.html#tuple_type_element) | [*元组类型的元素*](../chapter3/03_Types.html#tuple_type_element) **,** [*元组类型的元素集*](../chapter3/03_Types.html#tuple_type_element_list)
> *元组类型的元素* → [*属性(Attributes)集*](../chapter3/06_Attributes.html#attributes) _可选_ **inout** _可选_ [*类型*](../chapter3/03_Types.html#type) | **inout** _可选_ [*元素名*](../chapter3/03_Types.html#element_name) [*类型注解*](../chapter3/03_Types.html#type_annotation)
> *元素名* → [*标识符*](../chapter3/02_Lexical_Structure.html#identifier)
<!-- -->
> 函数类型语法
> *函数类型* → [*类型*](../chapter3/03_Types.html#type) **throws** _可选_ **->** [*类型*](../chapter3/03_Types.html#type)
> *函数类型* → [*类型*](TODO) **rethrows** **->** [*类型*](TODO)
<!-- -->
> 数组类型语法
> *数组类型* → **[** [*类型*](../chapter3/03_Types.html#array_type) **]**
<!-- -->
> 字典类型语法
> *字典类型* → **[** [*类型 **:** 类型*](TODO) **]**
<!-- -->
> 可选类型语法
> *可选类型* → [*类型*](../chapter3/03_Types.html#type) **?**
<!-- -->
> 隐式解析可选类型(Implicitly Unwrapped Optional Type)语法
> *隐式解析可选类型* → [*类型*](../chapter3/03_Types.html#type) **!**
<!-- -->
> 协议合成类型语法
> *协议合成类型* → **protocol** **<** [*协议标识符集*](../chapter3/03_Types.html#protocol_identifier_list) _可选_ **>**
> *协议标识符集* → [*协议标识符*](../chapter3/03_Types.html#protocol_identifier) | [*协议标识符*](../chapter3/03_Types.html#protocol_identifier) **,** [*协议标识符集*](../chapter3/03_Types.html#protocol_identifier_list)
> *协议标识符* → [*类型标识符*](../chapter3/03_Types.html#type_identifier)
<!-- -->
> 元(Metatype)类型语法
> *元类型* → [*类型*](../chapter3/03_Types.html#type) **.** **Type** | [*类型*](../chapter3/03_Types.html#type) **.** **Protocol**
<!-- -->
> 类型继承从句语法
> *类型继承从句* → **:** [*类条件(class-requirement))*](TODO) **,** [*类型继承集*](../chapter3/03_Types.html#type_inheritance_list)
> *类型继承从句* → **:** [*类条件(class-requirement))*](TODO)
> *类型继承从句* → **:** [*类型继承集*](TODO)
> *类型继承集* → [*类型标识符*](../chapter3/03_Types.html#type_identifier) | [*类型标识符*](../chapter3/03_Types.html#type_identifier) **,** [*类型继承集*](../chapter3/03_Types.html#type_inheritance_list)
> *类条件* → **class**

View File

@ -1,665 +0,0 @@
<a name="statement_statements"></a>
# 语句Statements
-----------------
> 1.0
> 翻译:[coverxit](https://github.com/coverxit)
> 校对:[numbbbbb](https://github.com/numbbbbb), [coverxit](https://github.com/coverxit), [stanzhai](https://github.com/stanzhai)
> 2.0
> 翻译+校对:[littledogboy](https://github.com/littledogboy)
> 2.2
> 翻译:[chenmingbiao](https://github.com/chenmingbiao)
> 3.0
> 翻译:[chenmingjia](https://github.com/chenmingjia)
本页包含内容:
- [循环语句](#loop_statements)
- [For-In 语句](#for-in_statements)
- [While 语句](#while_statements)
- [Repeat-While 语句](#repeat-while_statements)
- [分支语句](#branch_statements)
- [If 语句](#if_statements)
- [Guard 语句](#guard_statements)
- [Switch 语句](#switch_statements)
- [带标签的语句](#labeled_statements)
- [控制转移语句](#control_transfer_statements)
- [Break 语句](#break_statement)
- [Continue 语句](#continue_statement)
- [Fallthrough 语句](#fallthrough_statements)
- [Return 语句](#return_statements)
- [Throw 语句](#throw_statements)
- [Defer 语句](#defer_statements)
- [Do 语句](#do_statements)
- [编译器控制语句](#compiler_control_statements)
- [编译配置语句](#build_config_statements)
- [行控制语句](#line_control_statements)
- [可用性条件](#availability_condition)
在 Swift 中,有三种类型的语句:简单语句、编译器控制语句和控制流语句。简单语句是最常见的,用于构造表达式或者声明。编译器控制语句允许程序改变编译器的行为,包含编译配置语句和行控制语句。
控制流语句则用于控制程序执行的流程Swift 中有多种类型的控制流语句循环语句、分支语句和控制转移语句。循环语句用于重复执行代码块分支语句用于执行满足特定条件的代码块控制转移语句则用于改变代码的执行顺序。另外Swift 提供了 `do` 语句,用于构建局部作用域,还用于错误的捕获和处理;还提供了 `defer` 语句,用于退出当前作用域之前执行清理操作。
是否将分号(`;`)添加到语句的末尾是可选的。但若要在同一行内写多条独立语句,则必须使用分号。
> 语句语法
<a name="statement"></a>
> *语句* → [*表达式*](04_Expressions.md#expression) **;**<sub>可选</sub>
> *语句* → [*声明*](05_Declarations.md#declaration) **;**<sub>可选</sub>
> *语句* → [*循环语句*](#loop-statement) **;**<sub>可选</sub>
> *语句* → [*分支语句*](#branch-statement) **;**<sub>可选</sub>
> *语句* → [*带标签的语句*](#labeled-statement) **;**<sub>可选</sub>
> *语句* → [*控制转移语句*](#control-transfer-statement) **;**<sub>可选</sub>
> *语句* → [*defer 语句*](#defer-statement) **;**<sub>可选</sub>
> *语句* → [*do 语句*](#do-statement) **:**<sub>可选</sub>
> *语句* → [*编译器控制语句*](#compiler-control-statement)
<a name="statements"></a>
> *多条语句* → [*语句*](#statement) [*多条语句*](#statements)<sub>可选</sub>
<a name="loop_statements"></a>
## 循环语句
循环语句会根据特定的循环条件来重复执行代码块。Swift 提供三种类型的循环语句:`for-in` 语句、`while` 语句和 `repeat-while` 语句。
通过 `break` 语句和 `continue` 语句可以改变循环语句的控制流。有关这两条语句,详情参见 [Break 语句](#break_statement) 和 [Continue 语句](#continue_statement)。
> 循环语句语法
<a name="loop-statement"></a>
> *循环语句* → [*for-in 语句*](#for-in-statement)
> *循环语句* → [*while 语句*](#while-statement)
> *循环语句* → [*repeat-while 语句*](#repeat-while-statement)
<a name="for-in_statements"></a>
### For-In 语句
`for-in` 语句会为集合(或实现了 `SequenceType` 协议的任意类型)中的每一项执行一次代码块。
`for-in` 语句的形式如下:
```swift
for in 集合 {
循环体语句
}
```
`for-in` 语句在循环开始前会调用集合表达式的 `generate()` 方法来获取一个实现了 `GeneratorType` 协议的类型的值。接下来循环开始,反复调用该值的 `next()` 方法。如果其返回值不是 `None`,它将会被赋给“项”,然后执行循环体语句,执行完毕后回到循环开始处,继续重复这一过程;否则,既不会赋值也不会执行循环体语句,`for-in` 语句至此执行完毕。
> for-in 语句语法
<a name="for-in-statement"></a>
> *for-in 语句* → **for** **case**<sub>可选</sub> [*模式*](07_Patterns.md#pattern) **in** [*表达式*](04_Expressions.md#expression) [*where子句*](#where-clause)<sub>可选</sub> [*代码块*](05_Declarations.md#code-block)
<a name="while_statements"></a>
### While 语句
只要循环条件为真,`while` 语句就会重复执行代码块。
`while` 语句的形式如下:
```swift
while 条件 {
语句
}
```
`while` 语句的执行流程如下:
1. 判断条件的值。如果为 `true`,转到第 2 步;如果为 `false``while` 语句至此执行完毕。
2. 执行循环体中的语句,然后重复第 1 步。
由于会在执行循环体中的语句前判断条件的值,因此循环体中的语句可能会被执行若干次,也可能一次也不会被执行。
条件的结果必须是Bool类型或者Bool的桥接类型。另外条件语句也可以使用可选绑定请参阅 [可选绑定](../chapter2/01_The_Basics.md#optional_binding)。
> while 语句语法
<a name="while-statement"></a>
> *while 语句* → **while** [*条件子句*](#condition-clause) [*代码块*](05_Declarations.md#code-block)
<a name="condition-clause"></a>
> *条件子句* → [*表达式*](04_Expressions.md#expression)
> *条件子句* → [*表达式*](04_Expressions.md#expression) **,** [*条件列表*](#condition-list)
> *条件子句* → [*条件列表*](#condition-list)
> *条件子句* → [*可用性条件*](#availability-condition) **,** [*表达式*](04_Expressions.md#expression)
<a name="condition-list"></a>
> *条件列表* → [*条件*](#condition) | [*条件*](#condition) **,** [*条件列表*](#condition-list)
<a name="condition"></a>
> *条件* → [*表达式*](04_Expressions.md#expression) |[*可用性条件*](#availability-condition) | [*case条件*](#case-condition) | [*可选绑定条件*](#optional-binding-condition)
<a name="case-condition"></a>
> *case 条件* → **case** [*模式*](07_Patterns.md#pattern) [*构造器*](05_Declarations.md#initializer)
<a name="optional-binding-condition"></a>
> *可选绑定条件* → **let** [*模式*](07_Patterns.md#pattern) [*构造器*](05_Declarations.md#initializer) | **var** [*模式*](07_Patterns.md#pattern) [*构造器*](05_Declarations.md#initializer)
<a name="repeat-while_statements"></a>
### Repeat-While 语句
`repeat-while` 语句至少执行一次代码块,之后只要循环条件为真,就会重复执行代码块。
`repeat-while` 语句的形式如下:
```swift
repeat {
语句
} while 条件
```
`repeat-while` 语句的执行流程如下:
1. 执行循环体中的语句,然后转到第 2 步。
2. 判断条件的值。如果为 `true`,重复第 1 步;如果为 `false``repeat-while` 语句至此执行完毕。
由于条件的值是在循环体中的语句执行后才进行判断,因此循环体中的语句至少会被执行一次。
条件的结果必须是Bool类型或者Bool的桥接类型。另外条件语句也可以使用可选绑定请参阅 [可选绑定](../chapter2/01_The_Basics.md#optional_binding)。
> repeat-while 语句语法
<a name="repeat-while-statement"></a>
> *repeat-while 语句* → **repeat** [*代码块*](05_Declarations.md#code-block) **while** [*表达式*](04_Expressions.md#expression)
<a name="branch_statements"></a>
## 分支语句
分支语句会根据一个或者多个条件来执行指定部分的代码。分支语句中的条件将会决定程序如何分支以及执行哪部分代码。Swift 提供两种类型的分支语句:`if` 语句和 `switch` 语句。
`if` 语句和 `switch` 语句中的控制流可以用 `break` 语句改变,请参阅 [Break 语句](#break_statement)。
> 分支语句语法
<a name="branch-statement"></a>
> *分支语句* → [*if 语句*](#if-statement)
> *分支语句* → [*guard 语句*](#guard-statement)
> *分支语句* → [*switch 语句*](#switch-statement)
<a name="if_statements"></a>
### If 语句
`if` 语句会根据一个或多个条件来决定执行哪一块代码。
`if` 语句有两种基本形式,无论哪种形式,都必须有花括号。
第一种形式是当且仅当条件为真时执行代码,像下面这样:
```swift
if 条件 {
语句
}
```
第二种形式是在第一种形式的基础上添加 `else` 语句,当只有一个 `else` 语句时,像下面这样:
```swift
if 条件 {
若条件为真则执行这部分语句
} else {
若条件为假则执行这部分语句
}
```
`else` 语句也可包含 `if` 语句,从而形成一条链来测试更多的条件,像下面这样:
```swift
if 条件1 {
若条件1为真则执行这部分语句
} else if 条件2 {
若条件2为真则执行这部分语句
} else {
若前两个条件均为假则执行这部分语句
}
```
`if` 语句中条件的结果必须是Bool类型或者Bool的桥接类型。另外条件语句也可以使用可选绑定请参阅 [可选绑定](../chapter2/01_The_Basics.md#optional_binding)。
> if 语句语法
<a name="if-statement"></a>
> *if 语句* → **if** [*条件子句*](#condition-clause) [*代码块*](05_Declarations.md#code-block) [*else子句*](#else-clause)<sub>可选</sub>
<a name="else-clause"></a>
> *else 子句* → **else** [*代码块*](05_Declarations.md#code-block) | **else** [*if语句*](#if-statement)
<a name="guard_statements"></a>
### Guard 语句
如果一个或者多个条件不成立,可用 `guard` 语句用来退出当前作用域。
`guard` 语句的格式如下:
```swift
guard 条件 else {
语句
}
```
`guard` 语句中条件的结果必须是Bool类型或者Bool的桥接类型。另外条件也可以是一条可选绑定请参阅 [可选绑定](../chapter2/01_The_Basics.html#optional_binding)。
`guard` 语句中进行可选绑定的常量或者变量,其可用范围从声明开始直到作用域结束。
`guard` 语句必须有 `else` 子句,而且必须在该子句中调用 `Never` 返回类型的函数,或者使用下面的语句退出当前作用域:
* `return`
* `break`
* `continue`
* `throw`
关于控制转移语句,请参阅 [控制转移语句](#control_transfer_statements)。关于`Never`返回类型的函数,请参阅 [永不返回的函数](05_Declarations.md#rethrowing_functions_and_methods)。
> guard 语句语法
<a name="guard-statement"></a>
> *guard 语句* → **guard** [*条件子句*](#condition-clause) **else** [*代码块*](05_Declarations.html#code-block)
<a name="switch_statements"></a>
### Switch 语句
`switch` 语句会根据控制表达式的值来决定执行哪部分代码。
`switch` 语句的形式如下:
```swift
switch 控制表达式 {
case 模式1:
语句
case 模式2 where 条件:
语句
case 模式3 where 条件, 模式4 where 条件:
语句
default:
语句
}
```
`switch` 语句会先计算控制表达式的值,然后与每一个 `case` 的模式进行匹配。如果匹配成功,程序将会执行对应的 `case` 中的语句。另外,每一个 `case` 都不能为空,也就是说在每一个 `case` 中必须至少有一条语句。如果你不想在匹配到的 `case` 中执行代码,只需在该 `case` 中写一条 `break` 语句即可。
可以用作控制表达式的值是十分灵活的。除了标量类型外,如 `Int``Character`,你可以使用任何类型的值,包括浮点数、字符串、元组、自定义类型的实例和可选类型。控制表达式的值还可以用来匹配枚举类型中的成员值或是检查该值是否包含在指定的 `Range` 中。关于如何在 `switch` 语句中使用这些类型,请参阅 [控制流](../chapter2/05_Control_Flow.md) 一章中的 [Switch](../chapter2/05_Control_Flow.html#switch)。
每个 `case` 的模式后面可以有一个 `where` 子句。`where` 子句由 `where` 关键字紧跟一个提供额外条件的表达式组成。因此,当且仅当控制表达式匹配一个 `case` 的模式且 `where` 子句的表达式为真时,`case` 中的语句才会被执行。在下面的例子中,控制表达式只会匹配包含两个相等元素的元组,例如 `(1, 1)`
```swift
case let (x, y) where x == y:
```
正如上面这个例子,也可以在模式中使用 `let`(或 `var`)语句来绑定常量(或变量)。这些常量(或变量)可以在对应的 `where` 子句以及 `case` 中的代码中使用。但是,如果一个 `case` 中含有多个模式,所有的模式必须包含相同的常量(或变量)绑定,并且每一个绑定的常量(或变量)必须在所有的条件模式中都有相同的类型。
`switch` 语句也可以包含默认分支,使用 `default` 关键字表示。只有所有 `case` 都无法匹配控制表达式时,默认分支中的代码才会被执行。一个 `switch` 语句只能有一个默认分支,而且必须在 `switch` 语句的最后面。
`switch` 语句中 `case` 的匹配顺序和源代码中的书写顺序保持一致。因此,当多个模式都能匹配控制表达式时,只有第一个匹配的 `case` 中的代码会被执行。
#### Switch 语句不能有遗漏
在 Swift 中,`switch` 语句中控制表达式的每一个可能的值都必须至少有一个 `case` 与之对应。在某些无法面面俱到的情况下(例如,表达式的类型是 `Int`),你可以使用 `default` 分支满足该要求。
#### 不存在隐式落入
当匹配到的 `case` 中的代码执行完毕后,`switch` 语句会直接退出,而不会继续执行下一个 `case` 。这就意味着,如果你想执行下一个 `case`,需要显式地在当前 `case` 中使用 `fallthrough` 语句。关于 `fallthrough` 语句的更多信息,请参阅 [Fallthrough 语句](#fallthrough_statements)。
> switch 语句语法
<a name="switch-statement"></a>
> *switch 语句* → **switch** [*表达式*](04_Expressions.md#expression) **{** [*switch-case列表*](#switch-cases)<sub>可选</sub> **}**
<a name="switch-cases"></a>
> *switch case 列表* → [*switch-case*](#switch-case) [*switch-case列表*](#switch-cases)<sub>可选</sub>
<a name="switch-case"></a>
> *switch case* → [*case标签*](#case-label) [*多条语句*](#statements) | [*default标签*](#default-label) [*多条语句*](#statements)
<a name="case-label"></a>
> *case 标签* → **case** [*case项列表*](#case-item-list) **:**
<a name="case-item-list"></a>
> *case 项列表* → [*模式*](07_Patterns.md#pattern) [*where子句*](#where-clause)<sub>可选</sub> | [*模式*](07_Patterns.md#pattern) [*where子句*](#where-clause)<sub>可选</sub> **,** [*case项列表*](#case-item-list)
<a name="default-label"></a>
> *default 标签* → **default** **:**
<a name="where-clause"></a>
> *where-clause* → **where** [*where表达式*](#where-expression)
<a name="where-expression"></a>
> *where-expression* → [*表达式*](04_Expressions.md#expression)
<a name="labeled_statements"></a>
## 带标签的语句
你可以在循环语句或 `switch` 语句前面加上标签,它由标签名和紧随其后的冒号(`:`)组成。在 `break``continue` 后面跟上标签名可以显式地在循环语句或 `switch` 语句中改变相应的控制流。关于这两条语句用法,请参阅 [Break 语句](#break_statement) 和 [Continue 语句](#continue_statement)。
标签的作用域在该标签所标记的语句内。可以嵌套使用带标签的语句,但标签名必须唯一。
关于使用带标签的语句的例子,请参阅 [控制流](../chapter2/05_Control_Flow.md) 一章中的 [带标签的语句](../chapter2/05_Control_Flow.md#labeled_statements)。
> 带标签的语句语法
<a name="labeled-statement"></a>
> *带标签的语句* → [*语句标签*](#statement-label) [*循环语句*](#loop-statement) | [*语句标签*](#statement-label) [*if语句*](#if-statement) | [*语句标签*](#statement-label) [*switch语句*](#switch-statement)
<a name="statement-label"></a>
> *语句标签* → [*标签名称*](#label-name) **:**
<a name="label-name"></a>
> *标签名称* → [*标识符*](02_Lexical_Structure.md#identifier)
<a name="control_transfer_statements"></a>
## 控制转移语句
控制转移语句能够无条件地把控制权从一片代码转移到另一片代码从而改变代码执行的顺序。Swift 提供五种类型的控制转移语句:`break` 语句、`continue` 语句、`fallthrough` 语句、`return` 语句和 `throw` 语句。
> 控制转移语句语法
<a name="control-transfer-statement"></a>
> *控制转移语句* → [*break 语句*](#break-statement)
> *控制转移语句* → [*continue 语句*](#continue-statement)
> *控制转移语句* → [*fallthrough 语句*](#fallthrough-statement)
> *控制转移语句* → [*return 语句*](#return-statement)
> *控制转移语句* → [*throw 语句*](#throw-statement)
<a name="break_statement"></a>
### Break 语句
`break` 语句用于终止循环语句、`if` 语句或 `switch` 语句的执行。使用 `break` 语句时,可以只写 `break` 这个关键词,也可以在 `break` 后面跟上标签名,像下面这样:
> break
> break `标签名`
`break` 语句后面带标签名时,可用于终止由这个标签标记的循环语句、`if` 语句或 `switch` 语句的执行。
而只写 `break` 时,则会终止 `switch` 语句或 `break` 语句所属的最内层循环语句的执行。不能使用 `break` 语句来终止未使用标签的 `if` 语句。
无论哪种情况,控制权都会被转移给被终止的控制流语句后面的第一行语句。
关于使用 `break` 语句的例子,请参阅 [控制流](../chapter2/05_Control_Flow.md) 一章的 [Break](../chapter2/05_Control_Flow.md#break) 和 [带标签的语句](../chapter2/05_Control_Flow.md#labeled_statements)。
> break 语句语法
<a name="break-statement"></a>
> *break 语句* → **break** [*标签名称*](#label-name)<sub>可选</sub>
<a name="continue_statement"></a>
### Continue 语句
`continue` 语句用于终止循环中当前迭代的执行,但不会终止该循环的执行。使用 `continue` 语句时,可以只写 `continue` 这个关键词,也可以在 `continue` 后面跟上标签名,像下面这样:
> continue
> continue `标签名`
`continue` 语句后面带标签名时,可用于终止由这个标签标记的循环中当前迭代的执行。
而当只写 `continue` 时,可用于终止 `continue` 语句所属的最内层循环中当前迭代的执行。
在这两种情况下,控制权都会被转移给循环语句的条件语句。
`for` 语句中,`continue` 语句执行后,增量表达式还是会被计算,这是因为每次循环体执行完毕后,增量表达式都会被计算。
关于使用 `continue` 语句的例子,请参阅 [控制流](../chapter2/05_Control_Flow.md) 一章的 [Continue](../chapter2/05_Control_Flow.md#continue) 和 [带标签的语句](../chapter2/05_Control_Flow.md#labeled_statements)。
> continue 语句语法
<a name="continue-statement"></a>
> *continue 语句* → **continue** [*标签名称*](#label-name)<sub>可选</sub>
<a name="fallthrough_statements"></a>
### Fallthrough 语句
`fallthrough` 语句用于在 `switch` 语句中转移控制权。`fallthrough` 语句会把控制权从 `switch` 语句中的一个 `case` 转移到下一个 `case`。这种控制权转移是无条件的,即使下一个 `case` 的模式与 `switch` 语句的控制表达式的值不匹配。
`fallthrough` 语句可出现在 `switch` 语句中的任意 `case` 中,但不能出现在最后一个 `case` 中。同时,`fallthrough` 语句也不能把控制权转移到使用了值绑定的 `case`
关于在 `switch` 语句中使用 `fallthrough` 语句的例子,请参阅 [控制流](../chapter2/05_Control_Flow.md) 一章的 [控制转移语句](../chapter2/05_Control_Flow.md#control_transfer_statements)。
> fallthrough 语句语法
<a name="fallthrough-statement"></a>
> *fallthrough 语句* → **fallthrough**
<a name="return_statements"></a>
### Return 语句
`return` 语句用于在函数或方法的实现中将控制权转移到调用函数或方法,接着程序将会从调用位置继续向下执行。
使用 `return` 语句时,可以只写 `return` 这个关键词,也可以在 `return` 后面跟上表达式,像下面这样:
> return
> return `表达式`
`return` 语句后面带表达式时表达式的值将会返回给调用函数或方法。如果表达式的值的类型与函数或者方法声明的返回类型不匹配Swift 则会在返回表达式的值之前将表达式的值的类型转换为返回类型。
> 注意
> 正如 [可失败构造器](05_Declarations.md#failable_initializers) 中所描述的,`return nil` 在可失败构造器中用于表明构造失败。
而只写 `return` 时,仅仅是从该函数或方法中返回,而不返回任何值(也就是说,函数或方法的返回类型为 `Void` 或者说 `()`)。
> return 语句语法
<a name="return-statement"></a>
> *return 语句* → **return** [*表达式*](04_Expressions.html#expression)<sub>可选</sub>
<a name="throw_statements"></a>
### Throw 语句
`throw` 语句出现在抛出函数或者抛出方法体内,或者类型被 `throws` 关键字标记的闭包表达式体内。
`throw` 语句使程序在当前作用域结束执行,并向外围作用域传播错误。抛出的错误会一直传递,直到被 `do` 语句的 `catch` 子句处理掉。
`throw` 语句由 `throw` 关键字紧跟一个表达式组成,如下所示:
> throw `表达式`
表达式的结果必须符合 `ErrorType` 协议。
关于如何使用 `throw` 语句的例子,请参阅 [错误处理](../chapter2/18_Error_Handling.md) 一章的 [用 throwing 函数传递错误](../chapter2/18_Error_Handling.md#propagating_errors_using_throwing_functions)。
> throw 语句语法
<a name="throw-statement"></a>
> *throw 语句* → **throw** [*表达式*](04_Expressions.md#expression)
<a name="defer_statements"></a>
## Defer 语句
`defer` 语句用于在退出当前作用域之前执行代码。
`defer` 语句形式如下:
```swift
defer {
语句
}
```
`defer` 语句中的语句无论程序控制如何转移都会被执行。在某些情况下,例如,手动管理资源时,比如关闭文件描述符,或者即使抛出了错误也需要执行一些操作时,就可以使用 `defer` 语句。
如果多个 `defer` 语句出现在同一作用域内,那么它们执行的顺序与出现的顺序相反。给定作用域中的第一个 `defer` 语句,会在最后执行,这意味着代码中最靠后的 `defer` 语句中引用的资源可以被其他 `defer` 语句清理掉。
```swift
func f() {
defer { print("First") }
defer { print("Second") }
defer { print("Third") }
}
f()
// 打印 “Third”
// 打印 “Second”
// 打印 “First”
```
`defer` 语句中的语句无法将控制权转移到 `defer` 语句外部。
> defer 语句语法
<a name="defer-statement"></a>
> *延迟语句* → **defer** [*代码块*](05_Declarations.md#code-block)
<a name="do_statements"></a>
## Do 语句
`do` 语句用于引入一个新的作用域,该作用域中可以含有一个或多个 `catch` 子句,`catch` 子句中定义了一些匹配错误条件的模式。`do` 语句作用域内定义的常量和变量只能在 `do` 语句作用域内使用。
Swift 中的 `do` 语句与 C 中限定代码块界限的大括号(`{}`)很相似,也并不会降低程序运行时的性能。
`do` 语句的形式如下:
```swift
do {
try 表达式
语句
} catch 模式1 {
语句
} catch 模式2 where 条件 {
语句
}
```
如同 `switch` 语句,编译器会判断 `catch` 子句是否有遗漏。如果 `catch` 子句没有遗漏,则认为错误已被处理。否则,错误会自动传递到外围作用域,被某个 `catch` 子句处理掉或者被用 `throws` 关键字声明的抛出函数继续向外抛出。
为了确保错误已经被处理,可以让 `catch` 子句使用匹配所有错误的模式,如通配符模式(`_`)。如果一个 `catch` 子句不指定一种具体模式,`catch` 子句会匹配任何错误,并绑定到名为 `error` 的局部常量。有关在 `catch` 子句中使用模式的更多信息,请参阅 [模式](07_Patterns.md)。
关于如何在 `do` 语句中使用一系列 `catch` 子句的例子,请参阅 [错误处理](../chapter2/18_Error_Handling.md#handling_errors)。
> do 语句语法
<a name="do-statement"></a>
> *do 语句* → **do** [*代码块*](05_Declarations.md#code-block) [*多条 catch子句*](#catch-clauses)<sub>可选</sub>
<a name="catch-clauses"></a>
> *多条 catch 子句* → [*catch子句*](#catch-clause) [*多条 catch子句*](#catch-clauses)<sub>可选</sub>
<a name="catch-clause"></a>
> *catch 子句* → **catch** [*模式*](07_Patterns.md#pattern)<sub>可选</sub> [*where子句*](#where-clause)<sub>可选</sub> [*代码块*](05_Declarations.md#code-block)
<a name="compiler_control_statements"></a>
## 编译器控制语句
编译器控制语句允许程序改变编译器的行为。Swift 有两种编译器控制语句:编译配置语句和线路控制语句。
> 编译器控制语句语法
<a name="compiler-control-statement"></a>
> *编译器控制语句* → [*编译配置语句*](#build-config-statement)
> *编译器控制语句* → [*线路控制语句*](#line-control-statement)
<a name="build_config_statements"></a>
### 编译配置语句
编译配置语句可以根据一个或多个配置来有条件地编译代码。
每一个编译配置语句都以 `#if` 开始,`#endif` 结束。如下是一个简单的编译配置语句:
```swift
#if 编译配置项
语句
#endif
```
`if` 语句的条件不同,编译配置的条件是在编译时进行判断的。只有编译配置在编译时判断为 `true` 的情况下,相应的语句才会被编译和执行。
编译配置可以是 `true``false` 的字面量,也可以是使用 `-D` 命令行标志的标识符,或者是下列表格中的任意一个平台检测函数。
| 函数 | 可用参数 |
| --- | --- |
| `os()` | `OSX`, `iOS`, `watchOS`, `tvOS`, `Linux` |
| `arch()` | `i386`, `x86_64`, `arm`, `arm64` |
| `swift()` | `>=` 后跟版本号 |
`swift()`(语言版本检测函数)的版本号参数主要由主版本号和次版本号组成并且使用点号(`.`)分隔开,`>=` 和版本号之间不能有空格。
> 注意
> `arch(arm)` 平台检测函数在 ARM 64 位设备上不会返回 `true`。如果代码在 32 位的 iOS 模拟器上编译,`arch(i386)` 平台检测函数会返回 `true`。
你可以使用逻辑操作符 `&&``||``!` 来组合多个编译配置,还可以使用圆括号来进行分组。
就像 `if` 语句一样,你可以使用 `#elseif` 子句来添加任意多个条件分支来测试不同的编译配置。你也可以使用 `#else` 子句来添加最终的条件分支。包含多个分支的编译配置语句例子如下:
```swift
#if 编译配置1
如果编译配置1成立则执行这部分代码
#elseif 编译配置2
如果编译配置2成立则执行这部分代码
#else
如果编译配置均不成立则执行这部分代码
#endif
```
> 注意
> 即使没有被编译,编译配置中的语句仍然会被解析。然而,唯一的例外是编译配置语句中包含语言版本检测函数:仅当 `Swift` 编译器版本和语言版本检测函数中指定的版本号匹配时,语句才会被解析。这种设定能确保旧的编译器不会尝试去解析新 Swift 版本的语法。
<a name="build-config-statement"></a>
> 编译配置语句语法
<a name="build-configuration-statement"></a>
> *单个编译配置语句* → **#if** [*编译配置*](#build-configuration) [*语句*](#statements)<sub>可选</sub> [*多个编译配置elseif子句*](#build-configuration-elseif-clauses)<sub>可选</sub> **-** [*单个编译配置else子句*](#build-configuration-else-clause)<sub>可选</sub> **#endif**
<a name="build-configuration-elseif-clauses"></a>
> *多个编译配置 elseif 子句* → [*单个编译配置elseif子句*](#build-configuration-elseif-clause) [*多个编译配置elseif子句*](build-configuration-elseif-clauses)<sub>可选</sub>
<a name="build-configuration-elseif-clause"></a>
> *单个编译配置 elseif 子句* → **#elseif** [*编译配置*](#build-configuration) [*语句*](#statements)<sub>可选</sub>
<a name="build-configuration-else-clause"></a>
> *单个编译配置 else 子句* → **#else** [*语句*](#statements)<sub>可选</sub>
<a name="build-configuration"></a>
> *编译配置* → [*平台检测函数*](#platform-testing-function)
> *编译配置* → [*语言版本检测函数*](#language-version-testing-function)
> *编译配置* → [*标识符*](02_Lexical_Structure.md#identifier)
> *编译配置* → [*布尔值字面量*](02_Lexical_Structure.md#boolean-literal)
> *编译配置* → **(** [*编译配置*](#build-configuration) **)**
> *编译配置* → **!** [*编译配置*](#build-configuration)
> *编译配置* → [*编译配置*](#build-configuration) **&&** [*编译配置*](#build-configuration)
> *编译配置* → [*编译配置*](#build-configuration) **||** [*编译配置*](#build-configuration)
<a name="platform-testing-function"></a>
> *平台检测函数* → **os** **(** [*操作系统*](#operating-system) **)**
> *平台检测函数* → **arch** **(** [*架构*](#architecture) **)**
<a name="language-version-testing-function"></a>
> *语言版本检测函数* → **swift** **(** **>=** [*swift版本*](#swift-version) **)**
<a name="operating-system"></a>
> *操作系统* → **OSX** | **iOS** | **watchOS** | **tvOS**
<a name="architecture"></a>
> *架构* → **i386** | **x86_64** | **arm** | **arm64**
<a name="swift-version"></a>
> *swift 版本* → [*十进制数字*](02_Lexical_Structure.md#decimal-digit) ­**.** ­[*十进制数字*](02_Lexical_Structure.md#decimal-digit)
<a name="line_control_statements"></a>
### 行控制语句
行控制语句可以为被编译的源代码指定行号和文件名,从而改变源代码的定位信息,以便进行分析和调试。
行控制语句形式如下:
> \#sourceLocation(file: `文件名` , line:`行号`)
> \#sourceLocation()
第一种的行控制语句会改变该语句之后的代码中的字面量表达式 `#line``#file` 所表示的值。`行号` 是一个大于 0 的整形字面量,会改变 `#line` 表达式的值。`文件名` 是一个字符串字面量,会改变 `#file` 表达式的值。
第二种的行控制语句, `#sourceLocation()`,会将源代码的定位信息重置回默认的行号和文件名。
<a name="line-control-statement"></a>
> 行控制语句语法
> *行控制语句* → **#sourceLocation(file:[*文件名*](#file-name),line:[*行号*](#line-number))**
> *行控制语句* → **#sourceLocation()**
<a name="line-number"></a>
> *行号* → 大于 0 的十进制整数
<a name="file-name"></a>
> *文件名* → [*静态字符串字面量*](02_Lexical_Structure.md#static-string-literal)
<a name="availability_condition"></a>
### 可用性条件
可用性条件可作为 `if``while``guard` 语句的条件,可以在运行时基于特定的平台参数来查询 API 的可用性。
可用性条件的形式如下:
```swift
if #available(平台名称 版本, ..., *) {
如果 API 可用,则执行这部分语句
} else {
如果 API 不可用,则执行这部分语句
}
```
使用可用性条件来执行一个代码块时,取决于使用的 API 在运行时是否可用,编译器会根据可用性条件提供的信息来决定是否执行相应的代码块。
可用性条件使用一系列逗号分隔的平台名称和版本。使用 `iOS``OSX`,以及 `watchOS` 等作为平台名称,并写上相应的版本号。`*` 参数是必须写的,用于处理未来的潜在平台。可用性条件确保了运行时的平台不低于条件中指定的平台版本时才执行代码块。
与布尔类型的条件不同,不能用逻辑运算符 `&&``||` 组合可用性条件。
> 可用性条件语法
<a name="availability-condition"></a>
> *可用性条件* → **#available** **(** [*可用性参数列表*](#availability-arguments) **)**
<a name="availability-arguments"></a>
> *可用性参数列表* → [*可用性参数*](#availability-argument) | [*可用性参数*](#availability-argument) **,** [*可用性参数列表*](#availability-arguments)
<a name="availability-argument"></a>
> *可用性参数* → [平台名称](#platform-name) [平台版本](#platform-version)
> *可用性条件* → __*__
<a name="platform-name"></a>
> *平台名称* → **iOS** | **iOSApplicationExtension**
> *平台名称* → **OSX** | **OSXApplicationExtension**
> *平台名称* → **watchOS**
<a name="platform-version"></a>
> *平台版本* → [十进制数字](02_Lexical_Structure.md#decimal-digits)
> *平台版本* → [十进制数字](02_Lexical_Structure.md#decimal-digits) **.** [十进制数字](02_Lexical_Structure.md#decimal-digits)
> *平台版本* → [十进制数字](02_Lexical_Structure.md#decimal-digits) **.** [十进制数字](02_Lexical_Structure.md#decimal-digits) **.** [十进制数字](02_Lexical_Structure.md#decimal-digits)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,3 @@
# Swift 教程
本章描述了 Swift 的语言参考。

View File

@ -5,16 +5,15 @@
如果您之前没有接触过权限控制,先来听一个小故事:
> 小明是五道口工业学院的一个大一新生,最近他有点烦恼,因为同屋经常用他的热水壶,好像那是自己家的一样,可是碍于同学情面,又不好意思说。直到有一天,他和学姐小K吐槽。
> 小明是五道口工业学院的一个大一新生,最近他有点烦恼,因为同屋经常用他的热水壶,好像那是自己家的一样,可是碍于同学情面,又不好意思说。直到有一天,他和学姐小 K 吐槽。
> 学姐听了之后大学集体生活里面大部分东西都是默认室友可以共用的。如果你不想别人拿我可以帮你做封印只要打上private标记它们就看不到你的东西更加用不了你的东西了。
> 学姐听了之后,说:大学集体生活里面,大部分东西都是默认室友可以共用的。如果你不想别人拿,我可以帮你做封印,只要打上 private 标记,它们就看不到你的东西,更加用不了你的东西了。
> 小明说哇靠学姐你还会妖法......
Swift 语言从 Xcode 6 beta 5版本起加入了对权限控制Access Control的支持。其实权限控制和小明的物品一样你可以设定水壶是只有自己能用还是只有宿舍里的人能用还是全校都可以用。
Swift语言从Xcode 6 beta 5版本起加入了对权限控制Access Control的支持。其实权限控制和小明的物品一样你可以设定水壶是只有自己能用还是只有宿舍里的人能用还是全校都可以用
从此以后你可以好像神盾局局长一样完全掌控自己的代码块的”保密级别“哪些是只能在本文件引用哪些能用在整个项目里你还可以发挥大爱精神把它开源成只要导入你的框架大家都可以使用的API。
从此以后,你可以好像神盾局局长一样,完全掌控自己的代码块的”保密级别“,哪些是只能在本文件引用,哪些能用在整个项目里,你还可以发挥大爱精神,把它开源成只要导入你的框架,大家都可以使用的 API
这三种权限分别是:
@ -24,30 +23,30 @@ Swift语言从Xcode 6 beta 5版本起加入了对权限控制Access Contro
- #####internal 内部的
标记为internal的代码块在整个应用App bundle或者框架framework的范围内都是可以访问的。
标记为 internal 的代码块在整个应用App bundle或者框架framework的范围内都是可以访问的。
- #####public 公开的
标记为public的代码块一般用来建立API这是最开放的权限使得任何人只要导入这个模块都可以访问使用。
标记为 public 的代码块一般用来建立 API这是最开放的权限使得任何人只要导入这个模块都可以访问使用。
如果要把所有的爱加上一个期限,噢不,是给所有的代码块都标记上权限,不累死才怪。还好 swift 里面所有代码实体的默认权限,都是最常用的 internal。所以当你开发自己的 App 时,可能完全不用管权限控制的事情。
如果要把所有的爱加上一个期限噢不是给所有的代码块都标记上权限不累死才怪。还好swift里面所有代码实体的默认权限都是最常用的internal。所以当你开发自己的App时可能完全不用管权限控制的事情
但当你需要写一个公开 API 的时候,就必须对里面的代码块进行“隐身对其可见”的 public 标记,要么其他人是用不到的
但当你需要写一个公开API的时候就必须对里面的代码块进行“隐身对其可见”的public标记要么其他人是用不到的
Private私有级别的权限最严格它可以用来隐藏某些功能的细节实现方式。合理构筑你的代码你就可以安全地使用 extension 和高级功能,又不把它们暴露给项目内的其他文件
Private私有级别的权限最严格它可以用来隐藏某些功能的细节实现方式。合理构筑你的代码你就可以安全地使用extension和高级功能又不把它们暴露给项目内的其他文件
除了可以给整个声明设权限Swift还允许大家在需要的时候把某个属性property的取值权限比赋值权限设得更加开放。
除了可以给整个声明设权限Swift 还允许大家在需要的时候把某个属性property的取值权限比赋值权限设得更加开放
#####举个例子:
```swift
public class ListItem {
// ListItem这个类有两个公开的属性
// ListItem 这个类,有两个公开的属性
public var text: String
public var isComplete: Bool
// 下面的代码表示把变量UUID的赋值权限设为private对整个app可读但值只能在本文件里写入
// 下面的代码表示把变量 UUID 的赋值权限设为 private对整个 app 可读,但值只能在本文件里写入
private(set) var UUID: NSUUID
public init(text: String, completed: Bool, UUID: NSUUID) {
@ -56,7 +55,7 @@ Private私有级别的权限最严格它可以用来隐藏某些功能
self.UUID = UUID
}
// 这段没有特别标记权限因此属于默认的internal级别。在框架目标内可用但对于其他目标不可用
// 这段没有特别标记权限,因此属于默认的 internal 级别。在框架目标内可用,但对于其他目标不可用
func refreshIdentity() {
self.UUID = NSUUID()
}
@ -70,15 +69,15 @@ Private私有级别的权限最严格它可以用来隐藏某些功能
}
```
当我们使用Objective-CSwift混合开发时需要注意
当我们使用 Objective-CSwift 混合开发时,需要注意:
- 如果你在写的是一个应用Xcode会生成一个头文件来保证两者的可互访性而这个生成的头文件会包含publicinternal级别的声明。
- 如果你在写的是一个应用Xcode 会生成一个头文件来保证两者的可互访性,而这个生成的头文件会包含 publicinternal 级别的声明。
- 如果你的最终产品是一个Swift框架头文件里只会出现标记为public级别的声明。因为框架的头文件属于公开的Objective-C接口的一部分只有public部分对Objective-C可用。
- 如果你的最终产品是一个 Swift 框架,头文件里只会出现标记为 public 级别的声明。(因为框架的头文件,属于公开的 Objective-C 接口的一部分,只有 public 部分对 Objective-C 可用。)
虽然Swift不推荐大家传播和使用第三方的框架但对于建立和分享源文件形式的框架是支持的。对于需要写框架方便应用与多个项目的开发者来说要记得把API标记为public级别。
虽然 Swift 不推荐大家传播和使用第三方的框架,但对于建立和分享源文件形式的框架是支持的。对于需要写框架,方便应用与多个项目的开发者来说,要记得把 API 标记为 public 级别。
如果您想了解更多关于权限控制的内容可以查看苹果官方最新的《The Swift Language》和《Using Swift with Cocoa and Objective-C》指南
这两本指南在iBooks里面可以下载更新喔。
这两本指南在 iBooks 里面可以下载更新喔。
本文由翻译自Apple Swift Blog https://developer.apple.com/swift/blog/?id=5
本文由翻译自 Apple Swift Blog https://developer.apple.com/swift/blog/?id=5

View File

@ -1,4 +1,4 @@
# 造个类型不是梦-白话Swift类型创建
# 造个类型不是梦-白话 Swift 类型创建
-----------------
> 翻译:[老码团队翻译组-Tyrion](http://weibo.com/u/5241713117)
@ -9,18 +9,19 @@
- [自定义原型](#prototype)
- [实现默认值](#imp-default)
- [支持基本布尔型初始化](#init-by-bool)
- [支持Bool类型判断](#condition-by-bool)
- [支持 Bool 类型判断](#condition-by-bool)
- [支持兼容各们各派的类型](#support-all-type)
- [完善OCBool的布尔基因体系](#make-up-type)
- [完善 OCBool 的布尔基因体系](#make-up-type)
小伙伴们Swift中的Bool类型有着非常重要的语法功能并支撑起了整个Swift体系中的逻辑判断体系经过老码的研究和学习 Bool类型本身其实是对基础Boolean类型封装小伙伴们可能咬着手指头问老码怎么一会Bool类型一会Boolean类型其区别在于前者是基于枚举的组合类型而后者则是基本类型只有两种truefalse。
小伙伴们Swift 中的 Bool 类型有着非常重要的语法功能,并支撑起了整个 Swift 体系中的逻辑判断体系,经过老码的研究和学习, Bool 类型本身其实是对基础 Boolean 类型封装,小伙伴们可能咬着手指头问老码,怎么一会 Bool 类型,一会 Boolean 类型,其区别在于,前者是基于枚举的组合类型,而后者则是基本类型,只有两种 truefalse。
<a name="prefix_expressions"></a>
####自定义原型
接下老码根据Bool的思想来创建一个OCBool类型来让小伙伴们了解一下Swift中到底是怎么玩儿的。
来我们先看一下OCBool的定义。
####自定义原型 {#prefix-expressions}
接下老码根据 Bool 的思想来创建一个 OCBool 类型,来让小伙伴们了解一下 Swift 中到底是怎么玩儿的。
来我们先看一下 OCBool 的定义。
#####代码示例如下:
```swift
enum OCBool{
case ocTrue
@ -30,12 +31,13 @@ case ocFalse
#####注意:
- 代码中第2行和第3行可以合并到一行写如苹果官方Blog所写的一样
- 代码中命名需要注意OCBool是类型名所以首字母必须大写而case中的ocTrueocFalse是小类型则需要首字母小写。
- 代码中第2行和第3行可以合并到一行写如苹果官方 Blog 所写的一样
- 代码中命名需要注意OCBool 是类型名,所以首字母必须大写,而 case 中的 ocTrueocFalse 是小类型则需要首字母小写。
####实现默认值 {#imp-default}
我们给了一个漂亮的定义不过按照传统语言的经验Bool 值默认情况下是假, 所以我们的 OCBool 也应该如此,我们使用类型扩展技术增加这个默认特性:
<a name="imp-default"></a>
####实现默认值
我们给了一个漂亮的定义不过按照传统语言的经验Bool值默认情况下是假 所以我们的OCBool也应该如此我们使用类型扩展技术增加这个默认特性
```swift
extension OCBool{
init(){
@ -45,28 +47,33 @@ extension OCBool{
```
#####注意:
- 代码中第1行extension关键字非常强大小伙伴们可以通过此创造出许多好玩的东西建议各位去Github上看一个名为“Swiftz”的项目它将扩展用到了极致。
- 代码中第3行:self = .ocFalse语法刚入门的小伙伴们很迷糊为什么会有奇怪的点语法因为大牛Chris在Swift中增加了类型智能推断功能在苹果Blog中提到了“Context”概念就是这个意思因为这行语句是在枚举OCBool中的其上下文就是OCBool的定义体编译器当然知道.ocFalse就是OCBool.ocFalse了所以这里直接点语法非常整齐
现在我们可以使用如下方法使用这个Bool类型
- 代码中第1行:extension 关键字,非常强大,小伙伴们可以通过此创造出许多好玩的东西,建议各位去 Github 上看一个名为“Swiftz”的项目它将扩展用到了极致
- 代码中第3行self = .ocFalse 语法,刚入门的小伙伴们很迷糊,为什么会有奇怪的点语法,因为大牛 Chris 在 Swift 中增加了类型智能推断功能,在苹果 Blog 中提到了“Context”概念就是这个意思因为这行语句是在枚举 OCBool 中的,其上下文就是 OCBool 的定义体,编译器当然知道.ocFalse 就是 OCBool.ocFalse 了,所以这里直接点语法,非常整齐
现在我们可以使用如下方法使用这个 Bool 类型。
#####代码示例如下:
```swift
var result:OCBool = OCBool()
var result1:OCBool = .ocTrue
```
<a name="init-by-bool"></a>
####支持基本布尔型初始化
正如上述代码所述我们只能通过类型或者枚举项目赋值这是组合类型的用法但是编码的日子里我们总是希望和truefalse直接打交道也就是说我们希望这么做
####支持基本布尔型初始化 {#init-by-bool}
正如上述代码所述,我们只能通过类型或者枚举项目赋值,这是组合类型的用法,但是编码的日子里,我们总是希望和 truefalse 直接打交道,也就是说,我们希望这么做,
代码示例如下:
```swift
var isSuccess:OCBool = true
```
如果小伙伴们直接这么用,则会出现如下错误:
```
/Users/tyrion-OldCoder/Documents/Learning/BoolType/BoolType/main.swift:30:24: Type 'OCBool' does not conform to protocol 'BooleanLiteralConvertible'
```
编译器咆哮的原因是,我们的类型没有遵从“布尔字面量转换协议”,接下来修正这个问题,
#####代码示例如下:
@ -80,7 +87,6 @@ enum OCBool{
case ocFalse
}
extension OCBool: BooleanLiteralConvertible{
static func convertFromBooleanLiteral( value: Bool) ->OCBool{
return value ? ocTrue : ocFalse
@ -91,8 +97,10 @@ var isSuccess:OCBool = true
```
#####注意:
- 代码中的第11行是重点我的类型OCBool支持了BooleanLiteralConvertible协议这个协到底是干什么的呢小伙伴们在Xcode代码编辑器按住Command键然后点击第11行中的BooleanLiteralConvertible协议名则会进入它的定义
- 代码中的第11行是重点我的类型 OCBool 支持了 BooleanLiteralConvertible 协议,这个协到底是干什么的呢,小伙伴们在 Xcode 代码编辑器,按住 Command 键然后点击第11行中的 BooleanLiteralConvertible 协议名,则会进入它的定义,
#####其定义如下:
```swift
protocol BooleanLiteralConvertible {
typealias BooleanLiteralType
@ -100,27 +108,32 @@ protocol BooleanLiteralConvertible {
}
```
- 这个定义中有个类方法convertFromBooleanLiteral它的参数为BooleanLiteralType类型也就是我传入的Bool类型 且返回值为实现这个协议的类型本身在我们的OCBool类型中其返回值就是OCBool本身。经过这个定义我们可以直接对OCBool类型直接进行布尔字面量初始化了。
- 这个定义中有个类方法 convertFromBooleanLiteral它的参数为 BooleanLiteralType 类型,也就是我传入的 Bool 类型, 且返回值为实现这个协议的类型本身,在我们的 OCBool 类型中,其返回值就是 OCBool 本身。经过这个定义,我们可以直接对 OCBool 类型直接进行布尔字面量初始化了。
####支持 Bool 类型判断 {#condition-by-bool}
<a name="condition-by-bool"></a>
####支持Bool类型判断
小伙伴们不安分, 肯定想着我怎么用它实现逻辑判断,所以如果你这么写,
#####代码示例如下:
```swift
var isSuccess:OCBool = true
if isSuccess {
println( "老码请你吃火锅!")
println("老码请你吃火锅!")
}
```
你永远吃不到老码的火锅,因为这里编译器会咆哮:
```
/Users/tyrion-OldCoder/Documents/Learning/BoolType/BoolType/main.swift:27:4: Type 'OCBool' does not conform to protocol 'LogicValue'
```
OCBool现在只能用bool类型初始化而不能直接返回bool型小火把们还记得在《老码说编程之白话Swift江湖》中老码多次提到妈妈再也不担心我们 if a = 1{}的写法了, 因为等号不支持值返回了, 所以在if判断是后面的条件必须有返回值OCBool没有所以编译器哭了。我们解决这个问题。
OCBool 现在只能用 bool 类型初始化,而不能直接返回 bool 型,小火把们还记得在《老码说编程之白话 Swift 江湖》中,老码多次提到,妈妈再也不担心我们 if a = 1{}的写法了, 因为等号不支持值返回了, 所以在 if 判断是后面的条件必须有返回值OCBool 没有,所以编译器哭了。我们解决这个问题。
#####代码示例如下:
```swift
import Foundation
@ -131,7 +144,6 @@ enum OCBool{
case ocFalse
}
extension OCBool: BooleanLiteralConvertible{
static func convertFromBooleanLiteral( value: Bool) ->OCBool{
return value ? ocTrue : ocFalse
@ -152,30 +164,32 @@ extension OCBool: LogicValue{
}
}
var isSuccess:OCBool = true
if isSuccess {
println( "老码请你吃火锅!")
println("老码请你吃火锅!")
}
```
####运行结果如下:
```
Hello, World!
老码请你吃火锅!
Program ended with exit code: 0
```
#####注意:
- 如果小伙伴们现在用的是Beta版的Xcode注意苹果官方Blog中在代码第17行如果在Xcode Beta4下是错误的这里的协议是LogicValue而不是BooleanVue所以记得看错误提示才是好习惯。
- 注意代码第34行完美支持if判断且输出结果为“老码请你吃火锅”老码也是说说而已请不要当真。
<a name="support-all-type"></a>
- 如果小伙伴们现在用的是 Beta 版的 Xcode注意苹果官方 Blog 中在代码第17行如果在 Xcode Beta4下是错误的这里的协议是LogicValue 而不是 BooleanVue所以记得看错误提示才是好习惯。
- 注意代码第34行完美支持 if 判断,且输出结果为“老码请你吃火锅”,老码也是说说而已,请不要当真。
####支持兼容各们各派的类型
小伙伴们江湖风险门派众多老码有自己的OCBool类型可能嵩山少林有自己的SSBool类型甚至连郭美美都可能有自己的MMBool类型所以OCBool必须能够识别这些类型这些各门各派的类型只要支持LogicValue协议就应该可以被识别看老码怎么做
####支持兼容各们各派的类型 {#support-all-type}
小伙伴们,江湖风险,门派众多,老码有自己的 OCBool 类型,可能嵩山少林有自己的 SSBool 类型,甚至连郭美美都可能有自己的 MMBool 类型,所以 OCBool 必须能够识别这些类型,这些各门各派的类型,只要支持 LogicValue 协议,就应该可以被识别,看老码怎么做,
#####代码示例如下:
```swift
extension OCBool{
init( _ v: LogicValue )
@ -193,26 +207,28 @@ extension OCBool{
var mmResult: Bool = true
var ocResult:OCBool = OCBool(mmResult)
if ocResult {
println( "老码没钱,郭美美请你吃火锅!")
println("老码没钱,郭美美请你吃火锅!")
}
```
#####代码运行结果如下:
```
Hello, World!
老码没钱,郭美美请你吃火锅!
Program ended with exit code: 0
```
漂亮我们的OCBool类型现在支持了所有的逻辑变量初始化。
漂亮!我们的 OCBool 类型现在支持了所有的逻辑变量初始化。
#####注意:
- 代码中第2行“_”下横杠的用法这是一个功能强大的小强在此的目的是屏蔽外部参数名所以小伙伴们可以直接var ocResult:OCBool = OCBool(mmResult)而不是var ocResult:OCBool = OCBool(v: mmResult)小伙伴们惊呆了这个init函数中本来就没有外部参数名啊还记得老码在书里说过没Swift的初始化函数会默认使用内部参数名作为外部参数名。
<a name="make-up-type"></a>
####完善OCBool的布尔基因体系
小伙伴们bool类型的价值就是在于各种判断诸如==!=, &|,^,!以及各种组合逻辑运算我们OCBool也要具备这些功能否则就会基因缺陷且看老码如何实现
- 代码中第2行“_”下横杠的用法这是一个功能强大的小强在此的目的是屏蔽外部参数名所以小伙伴们可以直接var ocResult:OCBool = OCBool(mmResult)而不是var ocResult:OCBool = OCBool(v: mmResult),小伙伴们惊呆了!这个 init 函数中本来就没有外部参数名啊还记得老码在书里说过没Swift 的初始化函数会默认使用内部参数名,作为外部参数名。
####完善 OCBool 的布尔基因体系: {#make-up-type}
小伙伴们bool 类型的价值就是在于各种判断,诸如==!=, &|,^,!,以及各种组合逻辑运算,我们 OCBool 也要具备这些功能,否则就会基因缺陷,且看老码如何实现:
```swift
extension OCBool: Equatable{
@ -261,7 +277,6 @@ func &= (inout left:OCBool, right:OCBool ){
left = left & right
}
var isHasMoney:OCBool = true
var isHasWife:OCBool = true
var isHasHealty:OCBool = true
@ -273,15 +288,13 @@ isHasWife ^ isHasLover
isHasWife = !isHasLover
if (isHasMoney | isHasHealty) & isHasHealty{
println( "人生赢家,就像老码一样!")
println("人生赢家,就像老码一样!")
}else
{
println("人生最苦的事事,人死了钱没花了,人生最苦的事是,人活着,钱没了!")
}
```
好了到这里就到这里了窗外的雷声叫醒了老码现在应该去吃饭了以上老码给大家展示了如果制造一个自己的类型记得老码的示例是在Xcode6 Beta4下测试的至于Beta5的改变还没有涉及小伙伴们要好生练习以后各种自定类型都是基于这个思想。还有这个章节不是老码的原创老码认真的阅读了苹果的官方博客且自己的练习总结如果小伙伴们费了吃奶的劲还是看不懂请找度娘谷歌还是看不懂请到老码官方微博http://weibo.com/u/5241713117咆哮。
好了,到这里就到这里了,窗外的雷声叫醒了老码,现在应该去吃饭了,以上老码给大家展示了如果制造一个自己的类型,记得老码的示例是在 Xcode6 Beta4下测试的至于 Beta5的改变还没有涉及小伙伴们要好生练习以后各种自定类型都是基于这个思想。还有这个章节不是老码的原创老码认真的阅读了苹果的官方博客且自己的练习总结如果小伙伴们费了吃奶的劲还是看不懂请找度娘谷歌还是看不懂请到老码官方微博http://weibo.com/u/5241713117咆哮。
本文由翻译自Apple Swift Blog https://developer.apple.com/swift/blog/?id=8
本文由翻译自 Apple Swift Blog https://developer.apple.com/swift/blog/?id=8

View File

@ -1,18 +1,16 @@
# WWDC里面的那个“大炮打气球”
# WWDC 里面的那个“大炮打气球”
> 翻译:[老码团队翻译组-Arya](http://weibo.com/littlekok/)
> 翻译:[老码团队翻译组-Arya](http://weibo.com/littlekok/)
> 校对:[老码团队翻译组-](Jame)
![Ballon playground](https://devimages.apple.com.edgekey.net/swift/images/swift-screenshot.jpg)
很多小伙伴说,对 WWDC 上介绍 Swift 语言时,演示的那个“大炮打气球”的 Ballons 项目很感兴趣。
很多小伙伴说对WWDC上介绍Swift语言时演示的那个“大炮打气球”的Ballons项目很感兴趣
Ballons不但展现了playgrounds许多很赞的特性还让我们看到写代码的过程原来可以这么互动这么好玩。
Ballons 不但展现了 playgrounds 许多很赞的特性,还让我们看到写代码的过程,原来可以这么互动,这么好玩
现在你可以下载这个[Ballons.playground](https://developer.apple.com/swift/blog/downloads/Balloons.zip)的教学版本,学习这些有趣的效果是怎么实现的。教学版本里除了源文件,还有相关说明文档,我们还出了一些小小的实验题,你可以动手修改代码,然后在右侧马上看到效果。
这个playground文件用到了SpriteKit的新特性因此需要最新beta版本的Xcode 6和Yosemite系统来支持它运行。
这个 playground 文件用到了 SpriteKit 的新特性,因此需要最新 beta 版本的 Xcode 6和 Yosemite 系统来支持它运行。
本文由翻译自Apple Swift Blog的博文[Ballons](https://developer.apple.com/swift/blog/?id=9)
本文由翻译自 Apple Swift Blog 的博文:[Ballons](https://developer.apple.com/swift/blog/?id=9)

View File

@ -1,8 +1,8 @@
# Swift与C语言指针友好合作
# Swift 与 C 语言指针友好合作
-----------------
> 翻译:[老码团队翻译组-Relly](http://weibo.com/penguinliong/)
> 校对:[老码团队翻译组-Tyrion](http://weibo.com/u/5241713117)
> 翻译:[老码团队翻译组-Relly](http://weibo.com/penguinliong/)
> 校对:[老码团队翻译组-Tyrion](http://weibo.com/u/5241713117)
本页包含内容:
@ -11,15 +11,19 @@
- [用作字符串参数的指针](#string-as-para-pointer)
- [指针参数转换的安全性](#security-of-pointer-cast)
Objective-C和C的API常常会需要用到指针。Swift中的数据类型都原生支持基于指针的Cocoa API不仅如此Swift会自动处理部分最常用的将指针作为参数传递的情况。这篇文章中我们将着眼于在Swift中让C语言指针与变量、数组和字符串共同工作。
Objective-C 和 C 的 API 常常会需要用到指针。Swift 中的数据类型都原生支持基于指针的 Cocoa API不仅如此Swift 会自动处理部分最常用的将指针作为参数传递的情况。这篇文章中,我们将着眼于在 Swift 中让 C 语言指针与变量、数组和字符串共同工作。
####用以输入/输出的参数指针
C和Objective-C并不支持多返回值所以Cocoa API中常常将指针作为一种在方法间传递额外数据的方式。Swift允许指针被当作`inout`参数使用,所以你可以用符号`&`将对一个变量的引用作为指针参数传递。举例来说:`UIColor`中的`getRed(_:green:blue:alpha:)`方法需要四个`CGFloat*`指针来接收颜色的组成信息,我们使用`&`来将这些组成信息捕获为本地变量:
C 和 Objective-C 并不支持多返回值,所以 Cocoa API 中常常将指针作为一种在方法间传递额外数据的方式。Swift 允许指针被当作 `inout` 参数使用,所以你可以用符号 `&` 将对一个变量的引用作为指针参数传递。举例来说:`UIColor` 中的 `getRed(_:green:blue:alpha:)` 方法需要四个 `CGFloat*` 指针来接收颜色的组成信息,我们使用 `&` 来将这些组成信息捕获为本地变量:
```swift
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
color.getRed(&r, green: &g, blue: &b, alpha: &a)
```
另一种常见的情况是Cocoa中`NSError`的习惯用法。许多方法会使用一个`NSError**`参数来储存可能的错误的信息。举例来说:我们用`NSFileManager``contentOfDirectoryAtPath(_:error:)`方法来将目录下的内容列表,并将潜在的错误指向一个`NSError?`变量:
另一种常见的情况是 Cocoa 中 `NSError` 的习惯用法。许多方法会使用一个 `NSError**` 参数来储存可能的错误的信息。举例来说:我们用 `NSFileManager``contentOfDirectoryAtPath(_:error:)` 方法来将目录下的内容列表,并将潜在的错误指向一个 `NSError?` 变量:
```swift
var maybeError: NSError?
if let contents = NSFileManager.defaultManager()
@ -29,10 +33,13 @@ if let contents = NSFileManager.defaultManager()
// Handle the error
}
```
为了安全性Swift要求被使用`&`传递的变量已经初始化。因为无法确定这个方法会不会在写入数据前尝试从指针中读取数据。
为了安全性Swift 要求被使用 `&` 传递的变量已经初始化。因为无法确定这个方法会不会在写入数据前尝试从指针中读取数据。
####作为数组使用的参数指针
在C语言中数组和指针的联系十分紧密而Swift允许数组能够作为指针使用从而与基于数组的C语言API协同工作更加简单。一个固定的数组可以使用一个常量指针直接传递一个变化的数组可以用`&`运算符将一个非常量指针传递。就和输入/输出参数指针一样。举例来说我们可以用Accelerate框架中的`vDSP_vadd`方法让两个数组`a``b`相加,并将结果写入第三个数组`result`
在 C 语言中,数组和指针的联系十分紧密,而 Swift 允许数组能够作为指针使用,从而与基于数组的 C 语言 API 协同工作更加简单。一个固定的数组可以使用一个常量指针直接传递,一个变化的数组可以用 `&` 运算符将一个非常量指针传递。就和输入/输出参数指针一样。举例来说:我们可以用 Accelerate 框架中的 `vDSP_vadd` 方法让两个数组 `a``b` 相加,并将结果写入第三个数组 `result`
```swift
import Accelerate
@ -45,8 +52,10 @@ vDSP_vadd(a, 1, b, 1, &result, 1, 4)
// result now contains [1.5, 2.25, 3.125, 4.0625]
```
#用作字符串参数的指针
C语言中用`cont char*`指针来作为传递字符串的基本方式。Swift中的`String`可以被当作一个无限长度UTF-8编码的`const char*`指针来传递给方法。举例来说我们可以直接传递一个字符串给一个标准C和POSIX库方法
## 用作字符串参数的指针
C 语言中用 `cont char*` 指针来作为传递字符串的基本方式。Swift 中的 `String` 可以被当作一个无限长度 UTF-8编码的 `const char*` 指针来传递给方法。举例来说:我们可以直接传递一个字符串给一个标准 C 和 POSIX 库方法
```swift
puts("Hello from libc")
let fd = open("/tmp/scratch.txt", O_WRONLY|O_CREAT, 0o666)
@ -60,10 +69,11 @@ if fd < 0 {
}
```
#指针参数转换的安全性
Swift很努力地使与C语言指针的交互更加便利因为它们广泛地存在于Cocoa之中同时保持一定的安全性。然而相比你的其他Swift代码与C语言的指针交互具有潜在的不安全性所以务必要小心使用。其中特别要注意
- 如果被调用者为了在其返回值之后再次使用而保存了C指针的数据那么这些转换使用起来并不安全。转换后的指针仅在调用期间保证有效。甚至你将同样的变量、数组或字符串作为多指针参数再次传递你每次都会收到一个不同的指针。这个异常将全局或静态地储存为变量。你可以安全地将这段地址当作永久唯一的指针使用。例如作为一个KVO上下文参数使用的时候。
## 指针参数转换的安全性
- 当指针类型为`Array``String`时,溢出检查不是强制进行的。 基于C语言的API无法增加数组和字符串大小所以在你将其传递到基于C语言的API之前你必须确保数组或字符的大小正确。
Swift 很努力地使与 C 语言指针的交互更加便利,因为它们广泛地存在于 Cocoa 之中,同时保持一定的安全性。然而,相比你的其他 Swift 代码与 C 语言的指针交互具有潜在的不安全性,所以务必要小心使用。其中特别要注意:
- 如果被调用者为了在其返回值之后再次使用而保存了 C 指针的数据,那么这些转换使用起来并不安全。转换后的指针仅在调用期间保证有效。甚至你将同样的变量、数组或字符串作为多指针参数再次传递,你每次都会收到一个不同的指针。这个异常将全局或静态地储存为变量。你可以安全地将这段地址当作永久唯一的指针使用。例如:作为一个 KVO 上下文参数使用的时候。
如果你需要使用基于指针的API时没有遵守以上指导或是你重写了接受指针参数的Cocoa方法于是你可以在Swift中直接用不安全的指针来使用未经处理的内存。在未来的文章中我们将着眼于更加高级的情况
- 当指针类型为 `Array``String` 时,溢出检查不是强制进行的。 基于 C 语言的 API 无法增加数组和字符串大小,所以在你将其传递到基于 C 语言的 API 之前,你必须确保数组或字符的大小正确
如果你需要使用基于指针的 API 时没有遵守以上指导,或是你重写了接受指针参数的 Cocoa 方法,于是你可以在 Swift 中直接用不安全的指针来使用未经处理的内存。在未来的文章中我们将着眼于更加高级的情况。

View File

@ -1,7 +1,7 @@
# Swift里的值类型与引用类型
# Swift 里的值类型与引用类型
-----------------
> 翻译:[老码团队翻译组-Arya](http://weibo.com/littlekok/)
> 翻译:[老码团队翻译组-Arya](http://weibo.com/littlekok/)
> 校对:[老码团队翻译组-Jame](http://weibo.com/u/5241713117)
本页包含内容:
@ -10,16 +10,14 @@
- [Mutation修改在安全中扮演的角色](#act-in=mutation)
- [如何选择类型](#how-to-choose)
### Swift 里面的类型分为两种:
### Swift里面的类型分为两种
* **值类型(Value Types)**:每个实例都保留了一分独有的数据拷贝,一般以结构体 `struct``枚举enum` 或者`元组tuple`的形式出现。
* **引用类型(Reference Type)**:每个实例共享同一份数据来源,一般以`类class`的形式出现。
* **值类型Value Types**:每个实例都保留了一分独有的数据拷贝,一般以结构体 `struct`` 枚举enum` 或者 `元组tuple`的形式出现。
* **引用类型Reference Type**:每个实例共享同一份数据来源,一般以 `类class`的形式出现。
在这篇博文里面,我们会介绍两种类型各自的优点,以及应该怎么选择使用。
<a name="difference-two"></a>
#### 值类型与引用类型的区别
#### 值类型与引用类型的区别 {#difference-two}
值类型和引用类型最基本的分别在复制之后的结果。当一个值类型被复制的时候,相当于创造了一个完全独立的实例,这个实例保有属于自己的独有数据,数据不会受到其他实例的数据变化影响:
@ -27,8 +25,8 @@
// 下面是一个值类型的例子
struct S { var data: Int = -1 }
var a = S()
var b = a // b是a的拷贝
a.data = 42 // 更改a的数据b的不受影响
var b = a // b 是 a 的拷贝
a.data = 42 // 更改 a 的数据b 的不受影响
println("\(a.data), \(b.data)") // 输出结果 "42, -1"
```
@ -40,24 +38,22 @@
// 下面是一个引用类型的例子
class C { var data: Int = -1 }
var x = C()
var y = x // y是x的拷贝
x.data = 42 // 更改x的数据等于同时修改了y
var y = x // y 是 x 的拷贝
x.data = 42 // 更改 x 的数据,等于同时修改了 y
println("\(x.data), \(y.data)") // 输出结果 "42, 42"
```
<a name="act-in=mutation"></a>
#### Mutation修改在安全中扮演的角色
#### Mutation修改在安全中扮演的角色 {#act-in=mutation}
值类型较引用类型来说会让你更容易在大量代码中理清状况。如果你总是得到一个独立的拷贝出来的实例你就可以放心它不会被你app里面的其他部分代码默默地修改。这在多线程的环境里面是尤为重要的因为另外一个线程可能会在暗地里修改你的数据。因此可能会造成严重的程序错误这在调试过程中非常难以排除。
值类型较引用类型来说,会让你更容易在大量代码中理清状况。如果你总是得到一个独立的拷贝出来的实例,你就可以放心它不会被你 app 里面的其他部分代码默默地修改。这在多线程的环境里面是尤为重要的,因为另外一个线程可能会在暗地里修改你的数据。因此可能会造成严重的程序错误,这在调试过程中非常难以排除。
由于差别主要在于修改数据的后果,那么当实例的数据只读,不存在需要更改的情况下,用哪种类型都是没有分别的。
你可能在想,有的时候我可能也需要一个完全不变的类。这样使用`Cocoa NSObject`对象的时候会比较容易又可以保留值语义的好处。在今天你可以通过只使用不可变的存储属性和避开任何可以修改状态的API用Swift写出一个不可变类`immutable class`。实际上很多基本的Cocoa类例如`NSURL`都是设计成不可变类的。然而Swift语言目前只强制`struct``enum`这种值类型的不可变性,对类这种引用类型则没有。(例如还不支持强制将子类的限制为不可变类)
你可能在想,有的时候我可能也需要一个完全不变的类。这样使用 `Cocoa NSObject` 对象的时候会比较容易,又可以保留值语义的好处。在今天,你可以通过只使用不可变的存储属性,和避开任何可以修改状态的 API Swift 写出一个不可变类 `immutable class`。实际上,很多基本的 Cocoa 类,例如 `NSURL`都是设计成不可变类的。然而Swift 语言目前只强制 `struct``enum` 这种值类型的不可变性,对类这种引用类型则没有。(例如还不支持强制将子类的限制为不可变类)
<a name="how-to-choose"></a>
#### 如何选择类型?
#### 如何选择类型? {#how-to-choose}
所以当我们想要建立一个新的类型的时候怎么决定用值类型还是引用类型呢当你使用Cocoa框架的时候很多API都要通过NSObject的子类使用所以这时候必须要用到引用类型class。在其他情况下有下面几个准则
所以当我们想要建立一个新的类型的时候,怎么决定用值类型还是引用类型呢?当你使用 Cocoa 框架的时候,很多 API 都要通过 NSObject 的子类使用,所以这时候必须要用到引用类型 class。在其他情况下有下面几个准则
* **什么时候该用值类型**
* 要用==运算符来比较实例的数据时
@ -68,10 +64,9 @@
* 要用==运算符来比较实例身份的时候
* 你希望有创建一个共享的、可变对象的时候
在Swift里面数组(Array)、字符串(String)、字典(Dictionary)都属于值类型。它们就像C语言里面简单的int值是一个个独立的数据个体。你不需要花任何功夫来防范其他代码在暗地里修改它们。更重要的是你可以在线程之间安全的传递变量而不需要特地去同步。在Swift高安全性的精神下这个模式会帮助你用Swift写出更可控的代码。
Swift 里面,数组Array、字符串String、字典Dictionary都属于值类型。它们就像 C 语言里面简单的 int 值,是一个个独立的数据个体。你不需要花任何功夫来防范其他代码在暗地里修改它们。更重要的是,你可以在线程之间安全的传递变量,而不需要特地去同步。在 Swift 高安全性的精神下,这个模式会帮助你用 Swift 写出更可控的代码。
-----------------
本章节不是老码的原创,老码认真的阅读了苹果的官方博客,且自己的练习总结,如果小伙伴们费了吃奶的劲还是看不懂,请找度娘谷歌,还是看不懂请到老码[官方微博](http://weibo.com/u/5241713117)咆哮。
##### 本文由翻译自Apple Swift Blog [Value and Reference Types](https://developer.apple.com/swift/blog/?id=10)
本章节不是老码的原创,老码认真的阅读了苹果的官方博客,且自己的练习总结,如果小伙伴们费了吃奶的劲还是看不懂,请找度娘谷歌,还是看不懂请到老码[官方微博](http://weibo.com/u/5241713117)咆哮。
##### 本文由翻译自 Apple Swift Blog [Value and Reference Types](https://developer.apple.com/swift/blog/?id=10)

View File

@ -1,40 +1,33 @@
# 访问控制和protected
# 访问控制和 protected
-----------------
> 翻译:[老码团队翻译组-Arya](http://weibo.com/littlekok/)
> 翻译:[老码团队翻译组-Arya](http://weibo.com/littlekok/)
> 校对:[老码团队翻译组-Jame](http://weibo.com/u/5241713117)
原文再续,书折第一回。
很多其他编程语言都有一种”protected“设定可以限制某些类方法只能被它的子类所使用。
Swift支持了访问控制后大家给我们的反馈都很不错。而有的开发者问我们“为什么Swift没有类似protected的选项
Swift 支持了访问控制后,大家给我们的反馈都很不错。而有的开发者问我们:“为什么 Swift 没有类似 protected 的选项?”
**当我们在设计 Swift 访问控制的不同等级时,我们认为有两种主要场景:**
* 在一个 APP 里:隐藏某个类的私密细节。
* 在一个开源框架里:不让导入这个框架的 APP随便接触框架的内部实现细节。
**当我们在设计Swift访问控制的不同等级时我们认为有两种主要场景**
上面的两种常见情况,对应着 private 和 internal 这两个等级。
* 在一个APP里隐藏某个类的私密细节
* 在一个开源框架里不让导入这个框架的APP随便接触框架的内部实现细节。
而 protected 相当于把访问控制和继承特性混在一起,把访问控制的等级设定增加了一个维度,使之复杂化。即使设定了 protected子类还是可以通过新的公开方法、新的属性来接触到所谓“protected”了的 API。另一方面我们可以在各种地方重写一个方法所谓的保护却没有提供优化机制。这种设定往往在做不必要的限制 一 protected 允许了子类,但又禁止所有其他别的类(包括那些帮助子类实现某些功能的类)接触父类的成员
上面的两种常见情况对应着private和internal这两个等级
有的开发者指出apple 的框架有时候也会把给子类用的 API 分隔出来。这时候 protected 不就有用了吗?我们研究后发现,这些方法一般属于下面两种情况:一是这些方法对子类以外的类没啥用,所以不需要严格保护(例如上面说的协助实现某些功能的类)。二是这些方法就是设计出来被重写,而不是直接用的。举个例子,`drawRect(_:)` 就是在 UIKit 基础上使用的方法,但它不能在 UIKit 以外应用
而protected相当于把访问控制和继承特性混在一起把访问控制的等级设定增加了一个维度使之复杂化。即使设定了protected子类还是可以通过新的公开方法、新的属性来接触到所谓“protected”了的API。另一方面我们可以在各种地方重写一个方法所谓的保护却没有提供优化机制。这种设定往往在做不必要的限制 一 protected允许了子类但又禁止所有其他别的类包括那些帮助子类实现某些功能的类接触父类的成员。
除此之外,如果有了 protected它要怎么样和 extension 相互作用呢?一个类的 extension 能接触它的 protected 成员吗?一个子类的 extension 可以接触父类的 protected 成员吗extension 声明的位置对访问控制等级有没有影响呢?(复杂到要哭了是不是?)
有的开发者指出apple的框架有时候也会把给子类用的API分隔出来。这时候protected不就有用了吗我们研究后发现这些方法一般属于下面两种情况一是这些方法对子类以外的类没啥用所以不需要严格保护例如上面说的协助实现某些功能的类。二是这些方法就是设计出来被重写而不是直接用的。举个例子`drawRect(_:) `就是在UIKit基础上使用的方法但它不能在UIKit以外应用
对访问控制的设计,也依循了 ObjectiveC 开发者(包括 apple 内外的的常规做法。ObjectiveC 方法和属性一般在.h 头文件里声明,但也可以写在.m 实现文件里。假如有一个公开的类,想把里面某些部分设为只有框架内可以获取时,开发者一般会创建另一个头文件给内部使用。以上三种访问级别,就对应了 Swift 里面的 publicprivate 和 internal
除此之外如果有了protected它要怎么样和extension相互作用呢一个类的extension能接触它的protected成员吗一个子类的extension可以接触父类的protected成员吗extension声明的位置对访问控制等级有没有影响呢复杂到要哭了是不是
对访问控制的设计也依循了ObjectiveC开发者包括apple内外的的常规做法。ObjectiveC方法和属性一般在.h头文件里声明但也可以写在.m实现文件里。假如有一个公开的类想把里面某些部分设为只有框架内可以获取时开发者一般会创建另一个头文件给内部使用。以上三种访问级别就对应了Swift里面的publicprivate和internal。
Swift的访问控制等级和继承无关是单维度、非常清楚明了的。我们认为这样的模式更简洁同时满足了最主要的需求将一个类、或一个框架的实现细节隔离保护起来。这可能和你以前用过的不同但我们鼓励你试试看。
Swift 的访问控制等级和继承无关,是单维度、非常清楚明了的。我们认为这样的模式更简洁,同时满足了最主要的需求:将一个类、或一个框架的实现细节隔离保护起来。这可能和你以前用过的不同,但我们鼓励你试试看。
-----------------
本章节不是老码的原创,是老码认真的阅读了苹果的官方博客,自己的练习总结,如果小伙伴们费了吃奶的劲还是看不懂,请找度娘谷歌。还是看不懂?请到老码[官方微博](http://weibo.com/u/5241713117)咆哮。
##### 本文由翻译自Apple Swift Blog [Access Control and Protected](原文地址https://developer.apple.com/swift/blog/?id=11)
本章节不是老码的原创,是老码认真的阅读了苹果的官方博客,自己的练习总结,如果小伙伴们费了吃奶的劲还是看不懂,请找度娘谷歌。还是看不懂?请到老码[官方微博](http://weibo.com/u/5241713117)咆哮。
##### 本文由翻译自 Apple Swift Blog [Access Control and Protected](原文地址https://developer.apple.com/swift/blog/?id=11)

View File

@ -1,25 +1,24 @@
# 可选类型完美解决占位问题
-----------------
> 翻译:[老码团队翻译组-Tyrion](http://weibo.com/u/5241713117)
> 翻译:[老码团队翻译组-Tyrion](http://weibo.com/u/5241713117)
> 校对:[老码团队翻译组-Ayra](http://weibo.com/littlekok/)
本页包含内容:
- [为Dictionary增加objectsForKeys函数](#add-function)
- [Swift中更简便的方法](##easy-function)
- [ Dictionary 增加 objectsForKeys 函数](#add-function)
- [Swift 中更简便的方法](##easy-function)
- [内嵌可选类型](#nested-optional)
- [提供一个默认值](#provide-default)
可选类型是Swift中新引入的功能很强大。在这篇博文里讨论的是在Swift里如何通过可选类型来保证强类型的安全性。作为例子我们来创建一个Objective-C APISwift版本但实际上Swift本身并不需要这样的API。
可选类型是 Swift 中新引入的,功能很强大。在这篇博文里讨论的,是在 Swift 里,如何通过可选类型来保证强类型的安全性。作为例子,我们来创建一个 Objective-C APISwift 版本,但实际上 Swift 本身并不需要这样的 API。
#### 为 Dictionary 增加 objectsForKeys 函数 {#add-function}
<a name="#add-function"></a>
#### 为Dictionary增加objectsForKeys函数
在 Objective-C 中,`NSDictionary` 有一个方法 `-objectsForKeys:NoFoundMarker:`, 这个方法需要一个 `NSArray` 数组作为键值参数,然后返回一个包含相关值的数组。文档里写到:“返回数组中的第 N 个值,
和输入数组中的第 N 个值相对应”,那如果有某个键值在字典里不存在呢?于是就有了 `notFoundMarker` 作为返回提示。比如第三个键值没有找到,那么在返回数组中第三个值就是这个 `notFoundMarker`,而不是字典中的第三个值,但是这个值只是用来提醒原字典中没有找到对应值,但在返回数组中该元素存在,且用 `notFoundMarker` 作为占位符,因为这个对象不能直接使用,所以在 Foundation 框架中有个专门的类处理这个情况:`NSNull`
Objective-C中```NSDictionary```有一个方法```-objectsForKeys:NoFoundMarker:```, 这个方法需要一个```NSArray```数组作为键值参数,然后返回一个包含相关值的数组。文档里写到:"返回数组中的第N个值和输入数组中的第N个值相对应",那如果有某个键值在字典里不存在呢?于是就有了```notFoundMarker```作为返回提示。比如第三个键值没有找到,那么在返回数组中第三个值就是这个```notFoundMarker```,而不是字典中的第三个值,但是这个值只是用来提醒原字典中没有找到对应值,但在返回数组中该元素存在,且用```notFoundMarker```作为占位符因为这个对象不能直接使用所以在Foundation框架中有个专门的类处理这个情况```NSNull```。
在Swift中```Dictionary```类没有类似```objectsForKeys```的函数,为了说明问题,我们动手加一个,并且使其成为操作字典值的通用方法。我们可以用```extension```来实现:
Swift 中,`Dictionary` 类没有类似 `objectsForKeys` 的函数,为了说明问题,我们动手加一个,并且使其成为操作字典值的通用方法。我们可以用 `extension` 来实现:
```swift
extension Dictionary{
@ -29,8 +28,7 @@ extension Dictionary{
}
```
以上就是我们实现的Swift版本这个和Objective-C版本有很大区别。在Swift中因为其强类型的原因限制了返回的结果数组只能包含单一类型的元素所以我们不能放```NSNull```在字符串数组中但是Swift有更好的选择我们可以返回一个可选类型数据。我们所有的值都封包在可选类型中而不是```NSNull```, 我们只用```nil```就可以了。
以上就是我们实现的 Swift 版本,这个和 Objective-C 版本有很大区别。在 Swift 中,因为其强类型的原因限制了返回的结果数组只能包含单一类型的元素,所以我们不能放 `NSNull` 在字符串数组中但是Swift 有更好的选择,我们可以返回一个可选类型数据。我们所有的值都封包在可选类型中,而不是 `NSNull`, 我们只用 `nil` 就可以了。
```swift
extension Dictionary{
@ -45,10 +43,9 @@ extension Dictionary{
}
```
<a name="#easy-function"></a>
#### Swift中更简便的方法
#### Swift 中更简便的方法 {#easy-function}
小伙伴们可能会问为什么Swift中不需要实现这么一个API呢其实其有更简单的实现如下面代码所示
小伙伴们可能会问,为什么 Swift 中不需要实现这么一个 API 呢?其实其有更简单的实现,如下面代码所示:
```swift
extension Dictionary {
@ -58,14 +55,14 @@ extension Dictionary {
}
```
上述方式实现的功能和最开始的方法实现的功能相同,虽然核心的功能是封装了```map```的调用这个例子也说明了为什么Swift没有提供轻量级的API接口因为小伙伴们简单的调用```map```就可以实现。
上述方式实现的功能和最开始的方法实现的功能相同,虽然核心的功能是封装了 `map` 的调用,这个例子也说明了为什么 Swift 没有提供轻量级的 API 接口,因为小伙伴们简单的调用 `map` 就可以实现。
接下来,我们实验几个例子:
```swift
var dic: Dictionary = [ "1": 2, "3":3, "4":5 ]
var t = dic.valuesForKeys(["1", "4"])
var t = dic.valuesForKeys(["1", "4"])
//结果为:[Optional(2), Optional(5)]
var t = dict.valuesForKeys(["3", "9"])
@ -75,10 +72,9 @@ t = dic.valuesForKeys([])
//结果为:[]
```
<a name="#nested-optional"></a>
#### 内嵌可选类型
#### 内嵌可选类型 {#nested-optional}
现在,如果我们为每一个结果调用```last```方法,看下结果如何?
现在,如果我们为每一个结果调用 `last` 方法,看下结果如何?
```swift
var dic: Dictionary = [ "1": 2, "3":3, "4":5 ]
@ -91,20 +87,19 @@ var t = dict.valuesForKeys(["3", "9"]).last
var t = dict.valuesForKeys([]).last
// 结果为nil
```
小伙伴们立马迷糊了,为什么会出现两层包含的可选类型呢?,特别对第二种情况的```Optional(nil)```,这是什么节奏?
小伙伴们立马迷糊了,为什么会出现两层包含的可选类型呢?,特别对第二种情况的 `Optional(nil)`,这是什么节奏?
我们回过头看看```last```属性的定义:
我们回过头看看 `last` 属性的定义:
```swift
var last:T? { get }
```
很明显```last```属性的类型是数组元素类型的可选类型,这种情况下,因为元素类型是```(String?)```,那么再结合返回的类型,于是其结果就是```String??```了,这就是所谓的嵌套可选类型。但嵌套可选类型本质是什么意思呢?
很明显 `last` 属性的类型是数组元素类型的可选类型,这种情况下,因为元素类型是 `(String?)`,那么再结合返回的类型,于是其结果就是 `String??` 了,这就是所谓的嵌套可选类型。但嵌套可选类型本质是什么意思呢?
如果在Objective-C中重新调用上述方法我们将使用```NSNull```作为占位符Objective-C的调用语法如下所示
如果在 Objective-C 中重新调用上述方法,我们将使用 `NSNull` 作为占位符Objective-C 的调用语法如下所示:
```swift
[dict valuesForKeys:@[@"1", @"4"] notFoundMarker:[NSNull null]].lastObject
@ -115,10 +110,9 @@ var last:T? { get }
// nil
```
不管是Swift版本还是Objective-C版本返回值为```nil```都意味数组是空的,所以它就没有最后一个元素。 但是如果返回是```Optional(nil)```或者Objective-C中的```NSNull```都表示数组中的最后一个元素存在但是元素的内容是空的。在Objective-C中只能借助```NSNull```作为占位符来达到这个目的但是Swift却可以语言系统类型的角度的实现。
不管是 Swift 版本还是 Objective-C 版本,返回值为 `nil` 都意味数组是空的,所以它就没有最后一个元素。 但是如果返回是 `Optional(nil)` 或者 Objective-C 中的 `NSNull` 都表示数组中的最后一个元素存在,但是元素的内容是空的。在 Objective-C 中只能借助 `NSNull` 作为占位符来达到这个目的,但是 Swift 却可以语言系统类型的角度的实现。
<a name="#provide-default"></a>
#### 提供一个默认值
#### 提供一个默认值 {#provide-default}
进一步封装,如果我字典中的某个或某些元素不存在,我们想提供一个默认值怎么办呢?实现方法很简单:
@ -130,13 +124,13 @@ extension Dictionary {
}
```
```
```swift
dict.valuesForKeys(["1", "5"], notFoundMarker: "Anonymous")
```
和Objective-C相比其需要占位符来达到占位的目的但是Swift却已经从语言类型系统的层面原生的支持了这种用法同时提供了丰富的语法功能。这就是Swift可选类型的强大之处。同时注意上述例子中用到了空合运算符```??```
Objective-C 相比,其需要占位符来达到占位的目的,但是 Swift 却已经从语言类型系统的层面原生的支持了这种用法,同时提供了丰富的语法功能。这就是 Swift 可选类型的强大之处。同时注意上述例子中用到了空合运算符 `??`
-----------------
本章节不是老码的原创,是老码认真的阅读了苹果的官方博客,自己的练习总结,如果小伙伴们费了吃奶的劲还是看不懂,请找度娘谷歌。还是看不懂?请到老码[官方微博](http://weibo.com/u/5241713117)咆哮。
本章节不是老码的原创,是老码认真的阅读了苹果的官方博客,自己的练习总结,如果小伙伴们费了吃奶的劲还是看不懂,请找度娘谷歌。还是看不懂?请到老码[官方微博](http://weibo.com/u/5241713117)咆哮。
##### 本文由翻译自Apple Swift Blog [Optionals Case Study: valuesForKeys](https://developer.apple.com/swift/blog/?id=12)
##### 本文由翻译自 Apple Swift Blog [Optionals Case Study: valuesForKeys](https://developer.apple.com/swift/blog/?id=12)

150
source/contributors.md Executable file
View File

@ -0,0 +1,150 @@
# 文档翻译 & 校对工作记录
Swift 官方文档中文翻译由 [numbbbbb](https://github.com/numbbbbb) 发起并主导,本项目已经得到了苹果官方的 [认可](https://swift.org/documentation/)Translations 部分)。下面是各个版本官方文档翻译和校对工作的主要贡献者,排名不分先后。
## Swift 5.x 主要贡献者
- [Adolf-L](https://github.com/Adolf-L)
- [BigNerdCoding](https://github.com/bignerdcoding)
- [bqlin](https://github.com/bqlin)
- [Byelaney](https://github.com/Byelaney)
- [CMB](https://github.com/chenmingbiao)
- [DarrenChen123](https://github.com/DarrenChen123)
- [dzyding](https://github.com/dzyding)
- [Hale](https://github.com/wuqiuhao)
- [Joeytat](https://github.com/joeytat)
- [jojotov](https://github.com/jojotov)
- [Khala-wan](https://github.com/Khala-wan)
- [Nemocdz](https://github.com/Nemocdz)
- [numbbbbb](https://github.com/numbbbbb)
- [pmst](https://github.com/colourful987)
- [RickeyBoy](https://github.com/RickeyBoy)
- [SunsetWan](https://github.com/SunsetWan)
- [WAMaker](https://github.com/WAMaker)
- [YiYiZheng](https://github.com/YiYiZheng)
- [Yousanflics](https://github.com/Yousanflics)
## Swift 4.x 主要贡献者
- [Adolf-L](https://github.com/Adolf-L)
- [BigNerdCoding](https://github.com/bignerdcoding)
- [bqlin](https://github.com/bqlin)
- [Cee](https://github.com/Cee)
- [CMB](https://github.com/chenmingbiao)
- [Damonwong](https://github.com/Damonvvong)
- [Desgard](https://github.com/Desgard)
- [dzyding](https://github.com/dzyding)
- [EyreFree](https://www.eyrefree.org/)
- [Forelas](https://github.com/ForelaxX)
- [Hale](https://github.com/wuqiuhao)
- [kemchenj](https://kemchenj.github.io)
- [jojotov](https://github.com/jojotov)
- [Meler](https://github.com/pmtao)
- [mobilefellow](https://github.com/mobilefellow)
- [muhlenXi](https://github.com/muhlenxi)
- [mylittleswift](https://github.com/mylittleswift)
- [Nemocdz](https://github.com/Nemocdz)
- [numbbbbb](https://github.com/numbbbbb)
- [rain2540](https://github.com/rain2540)
- [Rsenjoyer](https://github.com/Rsenjoyer)
- [WAMaker](https://github.com/WAMaker)
- [YiYiZheng](https://github.com/YiYiZheng)
- [ZhangChi](https://github.com/zhangchi25806)
## Swift 3.x 主要贡献者
- [bqlin](https://github.com/bqlin)
- [chenmingjia](https://github.com/chenmingjia)
- [CMB](https://github.com/chenmingbiao)
- [crayygy](https://github.com/crayygy)
- [kemchenj](https://kemchenj.github.io)
- [Lanford](https://github.com/LanfordCai)
- [mmoaay](https://github.com/mmoaay)
- [mylittleswift](https://github.com/mylittleswift)
- [qhd](https://github.com/qhd)
- [shanks](https://github.com/shanksyang)
## Swift 2.x 主要贡献者
- [100mango](https://github.com/100mango)
- [175](https://github.com/Brian175)
- [BridgeQ](https://github.com/WXGBridgeQ)
- [buginux](https://github.com/buginux)
- [Cee](https://github.com/Cee)
- [Channe](https://github.com/Channe)
- [CMB](https://github.com/chenmingbiao)
- [DianQK](https://github.com/DianQK)
- [dreamkidd](https://github.com/dreamkidd)
- [EudeMorgen](https://github.com/EudeMorgen)
- [futantan](https://github.com/futantan)
- [JackAlan](https://github.com/AlanMelody)
- [KYawn](https://github.com/KYawn)
- [Lanford](https://github.com/LanfordCai)
- [Lenhoon](https://github.com/Lenhoon)
- [littledogboy](https://github.com/littledogboy)
- [LinusLing](https://github.com/linusling)
- [lyojo](https://github.com/lyojo)
- [miaosiqi](https://github.com/miaosiqi)
- [mmoaay](https://github.com/mmoaay)
- [overtrue](https://github.com/overtrue)
- [pmst](https://github.com/colourful987)
- [Prayer](https://github.com/futantan)
- [qhd](https://github.com/qhd)
- [ray16897188](https://github.com/ray16897188)
- [Realank](https://github.com/realank)
- [saitjr](https://github.com/saitjr)
- [SergioChan](https://github.com/SergioChan)
- [shanks](https://github.com/shanksyang)
- [SketchK](https://github.com/SketchK)
- [SkyJean](https://github.com/SkyJean)
- [wardenNScaiyi](https:github.com/wardenNScaiyi)
- [xtymichael](https://github.com/xtymichael)
- [yangsiy](https://github.com/yangsiy)
- [星夜暮晨](https://github.com/semperidem)
- [小铁匠 Linus](https://github.com/kevin833752)
## Swift 1.x 主要贡献者
- [bruce0505](https://github.com/bruce0505)
- [changkun](http://changkun.us/about/)
- [ChildhoodAndy](http://childhood.logdown.com)
- [coverxit](https://github.com/coverxit)
- [dabing1022](https://github.com/dabing1022)
- [EvilCome](https://github.com/Evilcome)
- [feiin](https://github.com/feiin)
- [fd5788](https://github.com/fd5788)
- [geek5nan](https://github.com/geek5nan)
- [happyming](https://github.com/happyming)
- [Hawstein](https://github.com/Hawstein)
- [honghaoz](https://github.com/honghaoz)
- [JaceFu](http://www.devtalking.com/)
- [Jasonbroker](https://github.com/Jasonbroker)
- [JaySurplus](https://github.com/JaySurplus)
- [Lenhoon](https://github.com/marsprince)
- [lifedim](https://github.com/lifedim)
- [Lin-H](https://github.com/Lin-H)
- [lslxdx](https://github.com/lslxdx)
- [LunaticM](https://github.com/LunaticM)
- [lyuka](https://github.com/lyuka)
- [marsprince](https://github.com/marsprince)
- [menlongsheng](https://github.com/menlongsheng)
- [NicePiao](https://github.com/NicePiao)
- [numbbbbb](https://github.com/numbbbbb)
- [pp-prog](https://github.com/pp-prog)
- [sg552](https://github.com/sg552)
- [stanzhai](https://github.com/stanzhai)
- [shinyzhu](https://github.com/shinyzhu)
- [superkam](https://github.com/superkam)
- [takalard](https://github.com/takalard)
- [TimothyYe](https://github.com/TimothyYe)
- [vclwei](https://github.com/vclwei)
- [wh1100717](https://github.com/wh1100717)
- [xiehurricane](https://github.com/xiehurricane)
- [XieLingWang](https://github.com/xielingwang)
- [yangsiy](https://github.com/yangsiy)
- [yankuangshi](https://github.com/yankuangshi)
- [yeahdongcn](https://github.com/yeahdongcn)
- [yangsiy](https://github.com/yangsiy)
- [zqp](https://github.com/zqp)
- [成都老码团队翻译组-Arya](http://weibo.com/littlekok/)
- [成都老码团队翻译组-Oberyn](http://weibo.com/u/5241713117)

View File

@ -2,30 +2,29 @@
> [Swift 开发者社区](http://swiftist.org)
<!-- -->
> 如果你觉得这个项目不错,请[点击Star一下](https://github.com/numbbbbb/the-swift-programming-language-in-chinese),您的支持是我们最大的动力。
> 如果你觉得这个项目不错,请[点击 Star 一下](https://github.com/numbbbbb/the-swift-programming-language-in-chinese),您的支持是我们最大的动力。
<!-- -->
> 关于文档中翻译错误,逻辑错误以及疑难问题答疑,请关注["@老码团队"](http://weibo.com/u/5241713117
)官方微博,会有技术人员统一收集答疑
# The Swift Programming Language 中文版####
# The Swift Programming Language 中文版
###这一次,让中国和世界同步
现在是6月12日凌晨4:38我用了整整一晚上的时间来进行最后的校对终于可以在12日拿出一个可以发布的版本。
9天时间1317个 Star310个 Fork超过30人参与翻译和校对工作项目最高排名GitHub总榜第4。
9天时间1317个 Star310个 Fork超过30人参与翻译和校对工作项目最高排名 GitHub 总榜第4。
设想过很多遍校对完成时的场景,仰天大笑还是泪流满面?真正到了这一刻才发现,疲倦已经不允许我有任何情绪。
说实话,刚开始发起项目的时候完全没想到会发展成今天这样,我一度计划自己一个人翻译完整本书。万万没想到,会有这么多的人愿意加入并贡献出自己的力量。
coverxit发给我最后一份文档的时候说我要去背单词了我问他周末要考六级他说是的。
coverxit 发给我最后一份文档的时候说,我要去背单词了,我问他,周末要考六级?他说是的。
pp-prog告诉我这几天太累了校对到一半睡着了醒来又继续做。2点17分发给我校对完成的文档。
pp-prog 告诉我这几天太累了校对到一半睡着了醒来又继续做。2点17分发给我校对完成的文档。
lifedim说他平时12点就会睡1点47分发给我校对后的文档。
lifedim 说他平时12点就会睡1点47分发给我校对后的文档。
团队里每个人都有自己的事情上班、上学、创业但是我们只用了9天就完成整本书的翻译。我不知道大家付出了多少牺牲了多少但是我知道他们的付出必将被这些文字记录下来即使再过10年20年依然熠熠生辉永不被人遗忘。