Compare commits
299 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b8084200e7 | |||
| b9480f1b6c | |||
| 51e9f8133c | |||
| 52917cffb4 | |||
| 0d1124917a | |||
| 9da5252f5f | |||
| 40cb1217d7 | |||
| a5c95690f5 | |||
| c54223ad3b | |||
| 2acb695871 | |||
| 96b6cac45a | |||
| 645cbfba23 | |||
| e31ea50e8f | |||
| ed0939a579 | |||
| 9a000a2ff0 | |||
| fe2c9ede92 | |||
| 4dc673e444 | |||
| 8f3b7b5b23 | |||
| d71b74bc10 | |||
| 8c24b2e9c8 | |||
| ef29944f75 | |||
| 960616507f | |||
| 9751fa16db | |||
| a762239d2d | |||
| 77b5caff91 | |||
| df35244821 | |||
| 2128ce6f51 | |||
| 69c79b8d02 | |||
| a1b4a69db2 | |||
| 363cec309c | |||
| 739176f380 | |||
| 2f77c8dc2d | |||
| 8f21fcf5e3 | |||
| e38b314d40 | |||
| 6205b4d2f8 | |||
| 60744b4033 | |||
| 5851c6fb8a | |||
| 495cd079e1 | |||
| 382c645a22 | |||
| 9e8082c6c2 | |||
| be91198c30 | |||
| c7ab79f41c | |||
| 93212eca82 | |||
| 0825cd43e5 | |||
| aed8ee585b | |||
| a40f423705 | |||
| bf09ec6368 | |||
| 4d13d67217 | |||
| e5756d5ca7 | |||
| 32a078e84b | |||
| 49e45e154e | |||
| c0b147b67a | |||
| 1491926107 | |||
| 245f7268dc | |||
| 79076b3617 | |||
| 95e2a467fc | |||
| e97a29d09f | |||
| deff7fe8b9 | |||
| b4f1d398ea | |||
| 292399ba70 | |||
| 67c28841a7 | |||
| 42bed2bf9a | |||
| a8f729a52d | |||
| 80c2ac750d | |||
| 704d910495 | |||
| 0ab60a97c3 | |||
| f43cb3fdb9 | |||
| 872d7af8a3 | |||
| 24d88f684a | |||
| a15735f51b | |||
| aba2a5fb55 | |||
| 096f2acfc4 | |||
| 8b99054c0c | |||
| d9d2952e85 | |||
| 9b302e76cb | |||
| 9d8588726a | |||
| 59e95dc3a5 | |||
| bb4d0c7fcd | |||
| e0c37b748c | |||
| bf18871349 | |||
| 13c47f495d | |||
| 6b38f6d7a7 | |||
| 262a06affd | |||
| 558814b61d | |||
| 2408ee2066 | |||
| 7725dfb9ad | |||
| 2e82e1621a | |||
| f2999daf52 | |||
| 83500eba22 | |||
| be7dca4e18 | |||
| 99453d1d4e | |||
| 72cb5a61b8 | |||
| c9d8b0be6d | |||
| 9abdbd4e4a | |||
| a6656c4221 | |||
| b73ff1765c | |||
| 528f2109de | |||
| bf8ac090d8 | |||
| e7d525781a | |||
| fee0c4a388 | |||
| da42305134 | |||
| 9a84b62fab | |||
| 9683a0b82f | |||
| a00bd5b888 | |||
| 2fd7ccd829 | |||
| f5c85a2eed | |||
| 65f605fc00 | |||
| 6f2488e19d | |||
| 7f94b446ef | |||
| 50c356dade | |||
| aa77e5cdd6 | |||
| 31a51ccace | |||
| 4fff00bd5c | |||
| e18e9c422a | |||
| ff8a9ca1a4 | |||
| 51b864f711 | |||
| 67df77108c | |||
| f7c9433bff | |||
| 05e7dafe9e | |||
| 764753697c | |||
| 2aef26f66a | |||
| dcc5837a53 | |||
| d21f043b64 | |||
| 52c81ff7b1 | |||
| 2c5d4a51ff | |||
| f47766a015 | |||
| 08e7cf1e63 | |||
| 3d38eaa8a0 | |||
| e91cdd5d29 | |||
| ad085a8563 | |||
| 39bf300bd4 | |||
| 77b1824d6b | |||
| 93c7fada67 | |||
| 72829a29b9 | |||
| 9a2bc92a6b | |||
| f333332ee6 | |||
| 5bf3e735a5 | |||
| f82f40fd47 | |||
| b894831e80 | |||
| 4ea6ea7d7a | |||
| cc9f46c3dc | |||
| c945edb4e5 | |||
| 7d2b949236 | |||
| f416e311eb | |||
| e59b995eda | |||
| 89aa08ab57 | |||
| 670cef47ed | |||
| 55001fd340 | |||
| d54d3a6c78 | |||
| ec456eac27 | |||
| d4f1442dc9 | |||
| f6da2e83a6 | |||
| c4f7fac162 | |||
| 10ff6be465 | |||
| 028268e7a6 | |||
| b31fa0ef60 | |||
| caa9beebee | |||
| 1489d076e0 | |||
| 9a48057873 | |||
| 92c89aa4da | |||
| 7220830354 | |||
| cb11ad439e | |||
| 1f2ffa1668 | |||
| 453266a7c1 | |||
| 8e7dbad8a7 | |||
| e8ae235524 | |||
| e5e81b81d1 | |||
| f7b9eb5e1e | |||
| 663a21536b | |||
| 7a115a996a | |||
| 9988f57550 | |||
| 32fa806e77 | |||
| 273a7bdd3b | |||
| 3063f18a78 | |||
| 1f0844bb36 | |||
| 8d0a87909e | |||
| dfd3383bd0 | |||
| 7656143f00 | |||
| eeda1ed0ed | |||
| 3434b008eb | |||
| 1704931ac4 | |||
| b0a9b63e1f | |||
| 2b715ddb06 | |||
| 05be6d4ba7 | |||
| 631a2cbe92 | |||
| 5d3d15b836 | |||
| 11aa5ab0d6 | |||
| 028a6aa795 | |||
| 7ba5a56301 | |||
| 4dcdd13e74 | |||
| 8f53cc7d82 | |||
| 48373d7791 | |||
| 603a40adb0 | |||
| 06e3e12621 | |||
| 5392b093d2 | |||
| 8b743fafe5 | |||
| 30db13bc1e | |||
| d34aee47cd | |||
| 64f80c9cba | |||
| db48920fa3 | |||
| 9284579c0b | |||
| 20f0cb7f81 | |||
| ca027e9132 | |||
| 6d6118e7a3 | |||
| d1b4d709b6 | |||
| 0e784e110d | |||
| 7bd73172ed | |||
| f3a4630915 | |||
| 02e0b33fd6 | |||
| 38ba1198b7 | |||
| eedcd69d28 | |||
| b7a8566a0f | |||
| ebda86cd05 | |||
| 546db6bc47 | |||
| 3d64a8e5e6 | |||
| b95202eb6d | |||
| ed7eb9e045 | |||
| 15ee26ca6d | |||
| dca10b1aa0 | |||
| 2d10a8d6ec | |||
| 45c537a42f | |||
| a6d6e468f6 | |||
| 72708963ab | |||
| 22265fa31d | |||
| 771c5b3da0 | |||
| 0369884f87 | |||
| 75e0837fb0 | |||
| 0afcd2ebf1 | |||
| d07fdbfb98 | |||
| 21e80f09b5 | |||
| 2b140081de | |||
| 60cc810915 | |||
| 68e51d7f57 | |||
| 4381756f0f | |||
| 96f3c84b8a | |||
| cc029194be | |||
| ae117bdfbc | |||
| 5fc4252bfc | |||
| 9ec61ba488 | |||
| fc89e3ca5d | |||
| 9a629a57e3 | |||
| 787687cb1e | |||
| b699654ab8 | |||
| d902be8367 | |||
| d90c50ec74 | |||
| 4894951c3b | |||
| b5defb4c0e | |||
| b1887d01ac | |||
| 5f0da139f0 | |||
| 53eaaeb19c | |||
| c9c9bb980e | |||
| 51ceab425c | |||
| 16cae8adf6 | |||
| bab43e7db6 | |||
| 4cace74528 | |||
| b807a6edb9 | |||
| 1a6d56d228 | |||
| 004fc0906d | |||
| 850cf97f83 | |||
| d6862fa4dc | |||
| d3efb989d3 | |||
| 0f2ab87dca | |||
| 9a6f016f45 | |||
| 2164db3b8b | |||
| fb2dfa6764 | |||
| d8068f4325 | |||
| 4e4e291956 | |||
| 2f430495d4 | |||
| 7b123505db | |||
| e7def896cb | |||
| 5fbfc38fd7 | |||
| b26f8aacff | |||
| b76f8cf111 | |||
| ca87420665 | |||
| 93c390dd5a | |||
| 7020682acd | |||
| aa91ad4692 | |||
| 367cece0f5 | |||
| 3f0cc6c047 | |||
| 65dc6b133f | |||
| b58d744852 | |||
| e9aaa026c6 | |||
| 1167a215c9 | |||
| f5c9d9c5e5 | |||
| 71f0757ba0 | |||
| afc8cddb0e | |||
| a3a7770b9a | |||
| 1ffeeeedaf | |||
| b991a4eb75 | |||
| e3c9bd7992 | |||
| 425d534e7f | |||
| e0af10bc8e | |||
| 3210e30972 | |||
| e82cea3866 | |||
| 497d68be37 | |||
| cb2ebd282b | |||
| 9fd540b8d7 | |||
| 86690d4472 | |||
| de559ce2d8 |
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal 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']
|
||||
73
README.md
73
README.md
@ -5,19 +5,26 @@
|
||||
|
||||
[英文原版在线版](https://docs.swift.org/swift-book/)
|
||||
|
||||
[英文原版ePub版](https://docs.swift.org/swift-book/TheSwiftProgrammingLanguageSwift5.epub)
|
||||
|
||||
# 在线阅读
|
||||
|
||||
使用 GitBook 制作,可以直接 [在线阅读](https://swiftgg.gitbook.io/swift/)。
|
||||
使用 GitBook 制作,可以在 [GitBook](https://swiftgg.gitbook.io/swift/) 网站阅读。
|
||||
|
||||
# 当前阶段
|
||||
|
||||
- 更新到 Swift 5.7,2022-06-06
|
||||
- 更新到 Swift 5.6,2022-03-14
|
||||
- 更新到 Swift 5.5,2021-06-07
|
||||
- 更新到 Swift 5.4,2021-04-26
|
||||
- 更新到 Swift 5.3,2020-09-16
|
||||
- 更新到 Swift 5.2,2020-02-15
|
||||
- 更新到 Swift 5.1,2019-11-11
|
||||
- 更新到 Swift 5.0,2019-04-05
|
||||
- 更新到 Swift 4.5,2019-03-16
|
||||
- 更新到 Swift 4.2,2019-01-29
|
||||
- 更新到 Swift 4.1,2018-04-12,感谢 [@Mylittleswift](https://github.com/Mylittleswift)
|
||||
- 更新到 Swift 3.0,2016-09-23
|
||||
|
||||
|
||||
# 贡献力量
|
||||
|
||||
如果想做出贡献的话,你可以:
|
||||
@ -57,24 +64,29 @@ diff 操作如下:
|
||||
|
||||
| 术语 | 备选翻译 |
|
||||
| --- | --- |
|
||||
| result builder | 结果构造器 |
|
||||
| property wrapper | 属性包装器([翻译相关讨论](https://github.com/SwiftGGTeam/the-swift-programming-language-in-chinese/issues/982#issuecomment-536244784)) |
|
||||
| projected value | 被呈现值 |
|
||||
| wrapped value | 被包装值 |
|
||||
| argument | 实参 |
|
||||
| parameter | 形参 |
|
||||
| associatedtype | 关联类型 |
|
||||
| variadic parameters| 可变参数 |
|
||||
| associated type | 关联类型 |
|
||||
| range | 区间 |
|
||||
| type property | 类型属性 |
|
||||
| Unary operator | 一元运算符 |
|
||||
| Binary operator | 二元运算符 |
|
||||
| Ternary operator | 三元运算符 |
|
||||
| Labeled Statement | 具名语句 |
|
||||
| conform Protocol | 遵循协议 |
|
||||
| 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 | 复合协议类型 |
|
||||
| branch statement | 分支语句 |
|
||||
| control transfer statement | 控制传递语句 |
|
||||
| type annotation | 类型注解 |
|
||||
| type identifier | 类型标识符 |
|
||||
| metatype type | 元类型 |
|
||||
| protocol composition type | 复合协议类型 |
|
||||
| associated value | 关联值 |
|
||||
| raw value | 原始值 |
|
||||
| computed property | 计算属性 |
|
||||
@ -87,21 +99,21 @@ diff 操作如下:
|
||||
| statement | 语句 |
|
||||
| expression | 表达式 |
|
||||
| optional | 可选 |
|
||||
| Implicitly Unwrapped optional | 隐式解包可选值 |
|
||||
| Optional Binding | 可选绑定 |
|
||||
| implicitly unwrapped optional | 隐式解包可选值 |
|
||||
| optional binding | 可选绑定 |
|
||||
| optional chaining | 可选链 |
|
||||
| collection | 集合 |
|
||||
| convention | 约定 |
|
||||
| iterate | 迭代 |
|
||||
| nest | 嵌套 |
|
||||
| Inheritance | 继承 |
|
||||
| inheritance | 继承 |
|
||||
| override | 重写 |
|
||||
| base class | 基类 |
|
||||
| Designated Initializer | 指定构造器 |
|
||||
| Convenience Initializer | 便利构造器 |
|
||||
| Automatic Reference Counting | 自动引用计数 |
|
||||
| designated initializer | 指定构造器 |
|
||||
| convenience initializer | 便利构造器 |
|
||||
| automatic reference counting | 自动引用计数 |
|
||||
| type inference | 类型推断 |
|
||||
| Type Casting | 类型转换 |
|
||||
| type casting | 类型转换 |
|
||||
| unwrapped | 解包 |
|
||||
| wrapped | 包装 |
|
||||
| note | 注意 |
|
||||
@ -109,7 +121,7 @@ diff 操作如下:
|
||||
| tuple | 元组 |
|
||||
| first-class | 一等 |
|
||||
| deinitializer | 析构器 |
|
||||
| Initializer | 构造器 |
|
||||
| initializer | 构造器 |
|
||||
| initialization | 构造过程 |
|
||||
| deinitialization | 析构过程 |
|
||||
| getter | 不翻译 |
|
||||
@ -118,15 +130,18 @@ diff 操作如下:
|
||||
| property | 属性 |
|
||||
| attribute | 特性或者属性,根据上下文 |
|
||||
| method | 方法 |
|
||||
| Enumeration | 枚举 |
|
||||
| Structure | 结构体 |
|
||||
| Protocol | 协议 |
|
||||
| Extension | 扩展 |
|
||||
| Generic | 泛型 |
|
||||
| enumeration | 枚举 |
|
||||
| structure | 结构体 |
|
||||
| protocol | 协议 |
|
||||
| extension | 扩展 |
|
||||
| generic | 泛型 |
|
||||
| literal value | 字面量 |
|
||||
| alias | 别名 |
|
||||
| Assertion | 断言 |
|
||||
| assertion | 断言 |
|
||||
| conditional compilation | 条件编译 |
|
||||
| opaque type | 不透明类型 |
|
||||
| function | 函数 |
|
||||
| runtime | 运行时 |
|
||||
|
||||
# 贡献者
|
||||
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
# Summary
|
||||
|
||||
* [Introduction](README.md)
|
||||
|
||||
@ -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())
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
BIN
document/TheSwiftProgrammingLanguageSwift51.epub
Normal file
BIN
document/TheSwiftProgrammingLanguageSwift51.epub
Normal file
Binary file not shown.
BIN
document/TheSwiftProgrammingLanguageSwift52.epub
Normal file
BIN
document/TheSwiftProgrammingLanguageSwift52.epub
Normal file
Binary file not shown.
BIN
document/TheSwiftProgrammingLanguageSwift53.epub
Normal file
BIN
document/TheSwiftProgrammingLanguageSwift53.epub
Normal file
Binary file not shown.
BIN
document/TheSwiftProgrammingLanguageSwift55.epub
Normal file
BIN
document/TheSwiftProgrammingLanguageSwift55.epub
Normal file
Binary file not shown.
BIN
document/TheSwiftProgrammingLanguageSwift57.epub
Normal file
BIN
document/TheSwiftProgrammingLanguageSwift57.epub
Normal file
Binary file not shown.
@ -1 +0,0 @@
|
||||
google-site-verification: googleb0a4f5a22e9cb82f.html
|
||||
13
index.html
13
index.html
@ -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>
|
||||
|
||||
4
source/.gitignore
vendored
4
source/.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
_book
|
||||
*.pdf
|
||||
*.epub
|
||||
*.mobi
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
Swift 是一种非常好的编写软件的方式,无论是手机,台式机,服务器,还是其他运行代码的设备。它是一种安全,快速和互动的编程语言,将现代编程语言的精华和苹果工程师文化的智慧,以及来自开源社区的多样化贡献结合了起来。编译器对性能进行了优化,编程语言对开发进行了优化,两者互不干扰,鱼与熊掌兼得。
|
||||
|
||||
Swift 对于初学者来说也很友好。它是第一个既满足工业标准又像脚本语言一样充满表现力和趣味的系统编程语言。它支持代码预览(playgrounds),这个革命性的特性可以允许程序员在不编译和运行应用程序的前提下运行 Swift 代码并实时查看结果。
|
||||
Swift 对于初学者来说也很友好。它是一门满足工业标准的编程语言,但又有着脚本语言般的表达力和可玩性。它支持代码预览(playgrounds),这个革命性的特性可以允许程序员在不编译和运行应用程序的前提下运行 Swift 代码并实时查看结果。
|
||||
|
||||
Swift 通过采用现代编程模式来避免大量常见编程错误:
|
||||
|
||||
13
source/01_welcome_to_swift/02_version_compatibility.md
Executable file
13
source/01_welcome_to_swift/02_version_compatibility.md
Executable file
@ -0,0 +1,13 @@
|
||||
# 版本兼容性
|
||||
|
||||
本书描述的是在 Xcode 14 中默认包含的 Swift 5.7 版本。你可以使用 Xcode 14 来构建 Swift 5.7、Swift 4.2 或 Swift 4 写的项目。
|
||||
|
||||
使用 Xcode 14 构建 Swift 4 和 Swift 4.2 代码时,Swift 5.7 的大多数功能都适用。但以下功能仅支持 Swift 5.7 或更高版本:
|
||||
|
||||
* 返回值是不透明类型的函数依赖 Swift 5.1 运行时。
|
||||
* **try?** 表达式不会为已返回可选类型的代码引入额外的可选类型层级。
|
||||
* 大数字的整型字面量初始化代码的类型将会被正确推导,例如 **UInt64(0xffff_ffff_ffff_ffff)** 将会被推导为整型类型而非溢出。
|
||||
|
||||
并发特性需要 Swift 5.7 及以上版本,以及一个提供了并发相关类型的 Swift 标准库版本。要应用于苹果平台,请至少将部署版本设置为 iOS 15、macOS 12、tvOS 15 或 watchOS 8.0。
|
||||
|
||||
用 Swift 5.7 写的项目可以依赖用 Swift 4.2 或 Swift 4 写的项目,反之亦然。这意味着,如果你将一个大的项目分解成多个框架(framework),你可以逐个地将框架从 Swift 4 代码迁移到 Swift 5.7。
|
||||
@ -10,15 +10,9 @@ print("Hello, world!")
|
||||
|
||||
这个教程会通过一系列编程例子来让你对 Swift 有初步了解,如果你有什么不理解的地方也不用担心——任何本章介绍的内容都会在后面的章节中详细讲解到。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 最好的体验是把这一章作为 Playground 文件在 Xcode 中打开。 Playgrounds 允许你可以编辑代码并立刻看到输出结果。
|
||||
>
|
||||
> [Download Playground](https://docs.swift.org/swift-book/GuidedTour/GuidedTour.playground.zip)
|
||||
|
||||
## 简单值 {#simple-values}
|
||||
|
||||
使用 `let` 来声明常量,使用 `var` 来声明变量。一个常量的值,在编译的时候,并不需要有明确的值,但是你只能为它赋值一次。这说明你可以用一个常量来命名一个值,一次赋值就即可在多个地方使用。
|
||||
使用 `let` 来声明常量,使用 `var` 来声明变量。一个常量的值,在编译的时候,并不需要有明确的值,但是你只能为它赋值一次。这说明你可以用一个常量来命名一个值,一次赋值就可在多个地方使用。
|
||||
|
||||
```swift
|
||||
var myVariable = 42
|
||||
@ -43,7 +37,7 @@ let explicitDouble: Double = 70
|
||||
值永远不会被隐式转换为其他类型。如果你需要把一个值转换成其他类型,请显式转换。
|
||||
|
||||
```swift
|
||||
let label = "The width is"
|
||||
let label = "The width is "
|
||||
let width = 94
|
||||
let widthLabel = label + String(width)
|
||||
```
|
||||
@ -65,7 +59,7 @@ let fruitSummary = "I have \(apples + oranges) pieces of fruit."
|
||||
>
|
||||
> 使用 `\()` 来把一个浮点计算转换成字符串,并加上某人的名字,和他打个招呼。
|
||||
|
||||
使用一对三个单引号(`"""`)来包含多行字符串内容,字符串中的内容(包括引号、空格、换行符等)都会保留下来。举个例子:
|
||||
使用三个双引号(`"""`)来包含多行字符串内容。每行行首的缩进会被去除,只要和结尾引号的缩进相匹配。举个例子:
|
||||
|
||||
```swift
|
||||
let quotation = """
|
||||
@ -87,14 +81,21 @@ var occupations = [
|
||||
occupations["Jayne"] = "Public Relations"
|
||||
```
|
||||
|
||||
数组在添加元素时会自动变大。
|
||||
|
||||
```swift
|
||||
shoppingList.append("blue paint")
|
||||
print(shoppingList)
|
||||
```
|
||||
|
||||
使用初始化语法来创建一个空数组或者空字典。
|
||||
|
||||
```swift
|
||||
let emptyArray = [String]()
|
||||
let emptyDictionary = [String: Float]()
|
||||
let emptyArray: [String] = []
|
||||
let emptyDictionary: [String: Float] = [:]
|
||||
```
|
||||
|
||||
如果类型信息可以被推断出来,你可以用 `[]` 和 `[:]` 来创建空数组和空字典——就像你声明变量或者给函数传参数的时候一样。
|
||||
如果类型信息可以被推断出来,你可以用 `[]` 和 `[:]` 来创建空数组和空字典——比如,在给变量赋新值或者给函数传参数的时候。
|
||||
|
||||
```swift
|
||||
shoppingList = []
|
||||
@ -138,6 +139,7 @@ if let name = optionalName {
|
||||
> 把 `optionalName` 改成 `nil`,greeting 会是什么?添加一个 `else` 语句,当 `optionalName` 是 `nil` 时给 greeting 赋一个不同的值。
|
||||
|
||||
如果变量的可选值是 `nil`,条件会判断为 `false`,大括号中的代码会被跳过。如果不是 `nil`,会将值解包并赋给 `let` 后面的常量,这样代码块中就可以使用这个值了。
|
||||
|
||||
另一种处理可选值的方法是通过使用 `??` 操作符来提供一个默认值。如果可选值缺失的话,可以使用默认值来代替。
|
||||
|
||||
```swift
|
||||
@ -146,6 +148,14 @@ let fullName: String = "John Appleseed"
|
||||
let informalGreeting = "Hi \(nickName ?? fullName)"
|
||||
```
|
||||
|
||||
你还可以使用较短的代码解包一个值,并且对该被包装值使用相同的名称。
|
||||
|
||||
```swift
|
||||
if let nickname {
|
||||
print("Hey, \(nickName)")
|
||||
}
|
||||
```
|
||||
|
||||
`switch` 支持任意类型的数据以及各种比较操作——不仅仅是整数以及测试相等。
|
||||
|
||||
```swift
|
||||
@ -179,7 +189,7 @@ let interestingNumbers = [
|
||||
"Square": [1, 4, 9, 16, 25],
|
||||
]
|
||||
var largest = 0
|
||||
for (kind, numbers) in interestingNumbers {
|
||||
for (_, numbers) in interestingNumbers {
|
||||
for number in numbers {
|
||||
if number > largest {
|
||||
largest = number
|
||||
@ -187,11 +197,12 @@ for (kind, numbers) in interestingNumbers {
|
||||
}
|
||||
}
|
||||
print(largest)
|
||||
// 输出 "25"
|
||||
```
|
||||
|
||||
> 练习
|
||||
>
|
||||
> 添加另一个变量来记录最大数字的种类(kind),同时仍然记录这个最大数字的值。
|
||||
> 将 _ 替换成变量名,以确定哪种类型的值是最大的。
|
||||
|
||||
使用 `while` 来重复运行一段代码直到条件改变。循环条件也可以在结尾,保证能至少循环一次。
|
||||
|
||||
@ -234,7 +245,7 @@ greet(person:"Bob", day: "Tuesday")
|
||||
|
||||
> 练习
|
||||
>
|
||||
> 删除 `day` 参数,添加一个参数来表示今天吃了什么午饭。
|
||||
> 删除 `day` 参数,在这个欢迎语中添加一个参数来表示今天的特价菜。
|
||||
|
||||
默认情况下,函数使用它们的参数名称作为它们参数的标签,在参数名称前可以自定义参数标签,或者使用 `_` 表示不使用参数标签。
|
||||
|
||||
@ -314,7 +325,7 @@ var numbers = [20, 19, 7, 12]
|
||||
hasAnyMatches(list: numbers, condition: lessThanTen)
|
||||
```
|
||||
|
||||
函数实际上是一种特殊的闭包:它是一段能之后被调取的代码。闭包中的代码能访问闭包作用域中的变量和函数,即使闭包是在一个不同的作用域被执行的 - 你已经在嵌套函数的例子中看过了。你可以使用 `{}` 来创建一个匿名闭包。使用 `in` 将参数和返回值类型的声明与闭包函数体进行分离。
|
||||
函数实际上是一种特殊的闭包:它是一段能之后被调取的代码。闭包中的代码能访问闭包作用域中的变量和函数,即使闭包是在一个不同的作用域被执行的——你已经在嵌套函数的例子中看过了。你可以使用 `{}` 来创建一个匿名闭包。使用 `in` 将参数和返回值类型的声明与闭包函数体进行分离。
|
||||
|
||||
```swift
|
||||
numbers.map({
|
||||
@ -335,7 +346,7 @@ let mappedNumbers = numbers.map({ number in 3 * number })
|
||||
print(mappedNumbers)
|
||||
```
|
||||
|
||||
你可以通过参数位置而不是参数名字来引用参数——这个方法在非常短的闭包中非常有用。当一个闭包作为最后一个参数传给一个函数的时候,它可以直接跟在括号后面。当一个闭包是传给函数的唯一参数,你可以完全忽略括号。
|
||||
你可以通过参数位置而不是参数名字来引用参数——这个方法在非常短的闭包中非常有用。当一个闭包作为最后一个参数传给一个函数的时候,它可以直接跟在圆括号后面。当一个闭包是传给函数的唯一参数,你可以完全忽略圆括号。
|
||||
|
||||
```swift
|
||||
let sortedNumbers = numbers.sorted { $0 > $1 }
|
||||
@ -419,7 +430,7 @@ test.simpleDescription()
|
||||
>
|
||||
> 创建 `NamedShape` 的另一个子类 `Circle`,构造器接收两个参数,一个是半径一个是名称,在子类 `Circle` 中实现 `area()` 和 `simpleDescription()` 方法。
|
||||
|
||||
除了储存简单的属性之外,属性可以有 getter 和 setter 。
|
||||
除了简单的存储属性,还有使用 getter 和 setter 的计算属性。
|
||||
|
||||
```swift
|
||||
class EquilateralTriangle: NamedShape {
|
||||
@ -450,7 +461,7 @@ triangle.perimeter = 9.9
|
||||
print(triangle.sideLength)
|
||||
```
|
||||
|
||||
在 `perimeter` 的 setter 中,新值的名字是 `newValue`。你可以在 `set` 之后显式的设置一个名字。
|
||||
在 `perimeter` 的 setter 中,新值的名字是 `newValue`。你可以在 `set` 之后的圆括号中显式地设置一个名字。
|
||||
|
||||
注意 `EquilateralTriangle` 类的构造器执行了三步:
|
||||
|
||||
@ -484,7 +495,7 @@ triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
|
||||
print(triangleAndSquare.triangle.sideLength)
|
||||
```
|
||||
|
||||
处理变量的可选值时,你可以在操作(比如方法、属性和子脚本)之前加 `?`。如果 `?` 之前的值是 `nil`,`?` 后面的东西都会被忽略,并且整个表达式返回 `nil`。否则,`?` 之后的东西都会被运行。在这两种情况下,整个表达式的值也是一个可选值。
|
||||
处理变量的可选值时,你可以在操作(比如方法、属性和子脚本)之前加 `?`。如果 `?` 之前的值是 `nil`,`?` 后面的东西都会被忽略,并且整个表达式返回 `nil`。否则,可选值会被解包,之后的所有代码都会按照解包后的值运行。在这两种情况下,整个表达式的值也是一个可选值。
|
||||
|
||||
```swift
|
||||
let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
|
||||
@ -525,7 +536,7 @@ let aceRawValue = ace.rawValue
|
||||
|
||||
默认情况下,Swift 按照从 0 开始每次加 1 的方式为原始值进行赋值,不过你可以通过显式赋值进行改变。在上面的例子中,`Ace` 被显式赋值为 1,并且剩下的原始值会按照顺序赋值。你也可以使用字符串或者浮点数作为枚举的原始值。使用 `rawValue` 属性来访问一个枚举成员的原始值。
|
||||
|
||||
使用 `init?(rawValue:)` 初始化构造器来创建一个带有原始值的枚举成员。如果存在与原始值相应的枚举成员就返回该枚举成员,否则就返回 `nil`。
|
||||
使用 `init?(rawValue:)` 初始化构造器来从原始值创建一个枚举实例。如果存在与原始值相应的枚举成员就返回该枚举成员,否则就返回 `nil`。
|
||||
|
||||
```swift
|
||||
if let convertedRank = Rank(rawValue: 3) {
|
||||
@ -533,7 +544,7 @@ if let convertedRank = Rank(rawValue: 3) {
|
||||
}
|
||||
```
|
||||
|
||||
枚举的关联值是实际值,并不是原始值的另一种表达方法。实际上,如果没有比较有意义的原始值,你就不需要提供原始值。
|
||||
枚举值是实际值,并不是原始值的另一种表达方法。实际上,如果没有比较有意义的原始值,你就不需要提供原始值。
|
||||
|
||||
```swift
|
||||
enum Suit {
|
||||
@ -559,9 +570,9 @@ let heartsDescription = hearts.simpleDescription()
|
||||
>
|
||||
> 给 `Suit` 添加一个 `color()` 方法,对 `spades` 和 `clubs` 返回 “black” ,对 `hearts` 和 `diamonds` 返回 “red” 。
|
||||
|
||||
注意在上面的例子中用了两种方式引用 `hearts` 枚举成员:给 `hearts` 常量赋值时,枚举成员 `Suit.hearts` 需要用全名来引用,因为常量没有显式指定类型。在 `switch` 里,枚举成员使用缩写 `.hearts` 来引用,因为 `self` 的值已经是一个 `suit` 类型,在已知变量类型的情况下可以使用缩写。
|
||||
注意在上面的例子中用了两种方式引用 `hearts` 枚举成员:给 `hearts` 常量赋值时,枚举成员 `Suit.hearts` 需要用全名来引用,因为常量没有显式指定类型。在 `switch` 里,枚举成员使用缩写 `.hearts` 来引用,因为 `self` 的值已经是一个 `suit` 类型。在任何已知变量类型的情况下都可以使用缩写。
|
||||
|
||||
如果枚举成员的实例有原始值,那么这些值是在声明的时候就已经决定了,这意味着不同枚举实例的枚举成员总会有一个相同的原始值。当然我们也可以为枚举成员设定关联值,关联值是在创建实例时决定的。这意味着不同的枚举成员的关联值都可以不同。你可以把关联值想象成枚举成员的寄存属性。例如,考虑从服务器获取日出和日落的时间的情况。服务器会返回正常结果或者错误信息。
|
||||
如果枚举成员的实例有原始值,那么这些值是在声明的时候就已经决定了,这意味着不同枚举实例的枚举成员总会有一个相同的原始值。当然我们也可以为枚举成员设定关联值,关联值是在创建实例时决定的。这意味着同一枚举成员不同实例的关联值可以不相同。你可以把关联值想象成枚举成员实例的存储属性。例如,考虑从服务器获取日出和日落的时间的情况。服务器会返回正常结果或者错误信息。
|
||||
|
||||
```swift
|
||||
enum ServerResponse {
|
||||
@ -584,7 +595,7 @@ case let .failure(message):
|
||||
>
|
||||
> 给 `ServerResponse` 和 switch 添加第三种情况。
|
||||
|
||||
注意日升和日落时间是如何从 `ServerResponse` 中提取到并与 `switch` 的 `case` 相匹配的。
|
||||
注意 `ServerResponse` 的值在与 `switch` 的分支匹配时,日升和日落时间是如何从该值中提取出来的。
|
||||
|
||||
使用 `struct` 来创建一个结构体。结构体和类有很多相同的地方,包括方法和构造器。它们之间最大的一个区别就是结构体是传值,类是传引用。
|
||||
|
||||
@ -602,7 +613,53 @@ let threeOfSpadesDescription = threeOfSpades.simpleDescription()
|
||||
|
||||
> 练习
|
||||
>
|
||||
> 给 `Card` 添加一个方法,创建一副完整的扑克牌并把每张牌的 rank 和 suit 对应起来。
|
||||
> 写一个方法,创建一副完整的扑克牌,这些牌是所有 rank 和 suit 的组合。
|
||||
|
||||
## 并发性 {#concurrency}
|
||||
|
||||
使用 `async` 标记异步运行的函数
|
||||
|
||||
```swift
|
||||
func fetchUserID(from server: String) async -> Int {
|
||||
if server == "primary" {
|
||||
return 97
|
||||
}
|
||||
return 501
|
||||
}
|
||||
```
|
||||
|
||||
您还可以通过在函数名前添加 `await` 来标记对异步函数的调用
|
||||
|
||||
```swift
|
||||
func fetchUsername(from server: String) async -> String {
|
||||
let userID = await fetchUserID(from: server)
|
||||
if userID == 501 {
|
||||
return "John Appleseed"
|
||||
}
|
||||
return "Guest"
|
||||
}
|
||||
```
|
||||
|
||||
使用 `async let` 来调用异步函数,并让其与其它异步函数并行运行。
|
||||
使用 `await` 以使用该异步函数返回的值。
|
||||
|
||||
```swift
|
||||
func connectUser(to server: String) async {
|
||||
async let userID = fetchUserID(from: server)
|
||||
async let username = fetchUsername(from: server)
|
||||
let greeting = await "Hello \(username), user ID \(userID)"
|
||||
print(greeting)
|
||||
}
|
||||
```
|
||||
|
||||
使用 `Task` 从同步代码中调用异步函数且不等待它们返回结果
|
||||
|
||||
```swift
|
||||
Task {
|
||||
await connectUser(to: "primary")
|
||||
}
|
||||
//Prints "Hello Guest, user ID 97"
|
||||
```
|
||||
|
||||
## 协议和扩展 {#protocols-and-extensions}
|
||||
|
||||
@ -642,7 +699,7 @@ let bDescription = b.simpleDescription
|
||||
|
||||
> 练习
|
||||
>
|
||||
> 写一个实现这个协议的枚举。
|
||||
> 给 `ExampleProtocol` 再增加一个要求。你需要怎么改 `SimpleClass` 和 `SimpleStructure` 才能保证它们仍旧遵循这个协议?
|
||||
|
||||
注意声明 `SimpleStructure` 时候 `mutating` 关键字用来标记一个会修改结构体的方法。`SimpleClass` 的声明不需要标记任何方法,因为类中的方法通常可以修改类属性(类的性质)。
|
||||
|
||||
@ -763,7 +820,7 @@ print(fridgeIsOpen)
|
||||
|
||||
```swift
|
||||
func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {
|
||||
var result = [Item]()
|
||||
var result: [Item] = []
|
||||
for _ in 0..<numberOfTimes {
|
||||
result.append(item)
|
||||
}
|
||||
@ -788,15 +845,16 @@ possibleInteger = .some(100)
|
||||
|
||||
```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
|
||||
}
|
||||
where T.Element: Equatable, T.Element == U.Element
|
||||
{
|
||||
for lhsItem in lhs {
|
||||
for rhsItem in rhs {
|
||||
if lhsItem == rhsItem {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
anyCommonElements([1, 2, 3], [3])
|
||||
```
|
||||
@ -805,4 +863,4 @@ anyCommonElements([1, 2, 3], [3])
|
||||
>
|
||||
> 修改 `anyCommonElements(_:_:)` 函数来创建一个函数,返回一个数组,内容是两个序列的共有元素。
|
||||
|
||||
`<T: Equatable>` 和 `<T> ... where T: Equatable>` 的写法是等价的。
|
||||
`<T: Equatable>` 和 `<T> ... where T: Equatable` 的写法是等价的。
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,479 +1,479 @@
|
||||
# 基本运算符
|
||||
|
||||
*运算符*是检查、改变、合并值的特殊符号或短语。例如,加号(`+`)将两个数相加(如 `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 等于 1,y 等于 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` 中:
|
||||
|
||||

|
||||
|
||||
你可以在 `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 等于 4,dog 等于 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!”
|
||||
```
|
||||
|
||||
这括号使得前两个值被看成整个逻辑表达中独立的一个部分。虽然有括号和没括号的输出结果是一样的,但对于读代码的人来说有括号的代码更清晰。可读性比简洁性更重要,请在可以让你代码变清晰的地方加个括号吧!
|
||||
# 基本运算符
|
||||
|
||||
*运算符*是检查、改变、合并值的特殊符号或短语。例如,加号(`+`)将两个数相加(如 `let i = 1 + 2`)。更复杂的运算例子包括逻辑与运算符 `&&`(如 `if enteredDoorCode && passedRetinaScan`)。
|
||||
|
||||
Swift 所支持运算符你可能在别的语言比如 C 语言里已经认识了,同时为了减少常见编码错误对它们做了部分改进。如:赋值符(`=`)不再有返回值,这样就消除了手误将判等运算符(`==`)写成赋值符导致代码错误的缺陷。算术运算符(`+`,`-`,`*`,`/`,`%` 等)的结果会被检测并禁止值溢出,以此来避免保存变量时由于变量大于或小于其类型所能承载的范围时导致的异常结果。当然允许你使用 Swift 的溢出运算符来实现溢出。详情参见 [溢出运算符](./27_Advanced_Operators.md#overflow-operators)。
|
||||
|
||||
Swift 还提供了 C 语言没有的区间运算符,例如 `a..<b` 或 `a...b`,这方便我们表达一个区间内的数值。
|
||||
|
||||
本章节只描述了 Swift 中的基本运算符,[高级运算符](./27_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 等于 1,y 等于 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`)。详情参见 [溢出运算符](./27_Advanced_Operators.md#overflow-operators)。
|
||||
|
||||
加法运算符也可用于 `String` 的拼接:
|
||||
|
||||
```swift
|
||||
"hello, " + "world" // 等于 "hello, world"
|
||||
```
|
||||
|
||||
### 求余运算符 {#remainder-operator}
|
||||
|
||||
*求余运算符*(`a % b`)是计算 `b` 的多少倍刚刚好可以容入 `a`,返回多出来的那部分(余数)。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 求余运算符(`%`)在其他语言也叫*取模运算符*。但是严格说来,我们看该运算符对负数的操作结果,「求余」比「取模」更合适些。
|
||||
|
||||
我们来谈谈取余是怎么回事,计算 `9 % 4`,你先计算出 `4` 的多少倍会刚好可以容入 `9` 中:
|
||||
|
||||

|
||||
|
||||
你可以在 `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` 同符号时能取到的最大整数:
|
||||
|
||||
-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}
|
||||
|
||||
Swift 支持以下的比较运算符:
|
||||
|
||||
- 等于(`a == b`)
|
||||
- 不等于(`a != b`)
|
||||
- 大于(`a > b`)
|
||||
- 小于(`a < b`)
|
||||
- 大于等于(`a >= b`)
|
||||
- 小于等于(`a <= b`)
|
||||
|
||||
> 注意
|
||||
>
|
||||
> Swift 也提供恒等(`===`)和不恒等(`!==`)这两个比较符来判断两个对象是否引用同一个对象实例。更多细节在 [类与结构](./09_Structures_And_Classes.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 等于 4,dog 等于 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`,再次执行空合运算,运算结果为封包在 `userDefinedColorName` 中的值,而非默认值。
|
||||
|
||||
```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!”
|
||||
```
|
||||
|
||||
这括号使得前两个值被看成整个逻辑表达中独立的一个部分。虽然有括号和没括号的输出结果是一样的,但对于读代码的人来说有括号的代码更清晰。可读性比简洁性更重要,请在可以让你代码变清晰的地方加个括号吧!
|
||||
@ -1,8 +1,8 @@
|
||||
# 字符串和字符
|
||||
|
||||
*字符串*是是一系列字符的集合,例如 `"hello, world"`,`"albatross"`。Swift 的字符串通过 `String` 类型来表示。而 `String` 内容的访问方式有多种,例如以 `Character` 值的集合。
|
||||
*字符串*是一系列字符的集合,例如 `"hello, world"`,`"albatross"`。Swift 的字符串通过 `String` 类型来表示。而 `String` 内容的访问方式有多种,例如以 `Character` 值的集合。
|
||||
|
||||
Swift 的 `String` 和 `Character` 类型提供了一种快速且兼容 Unicode 的方式来处理代码中的文本内容。创建和操作字符串的语法与 C 语言中字符串操作相似,轻量并且易读。通过 `+` 符号就可以非常简单的实现两个字符串的拼接操作。与 Swift 中其他值一样,能否更改字符串的值,取决于其被定义为常量还是变量。你可以在已有字符串中插入常量、变量、字面量和表达式从而形成更长的字符串,这一过程也被成为字符串插值。尤其是在为显示、存储和打印创建自定义字符串值时,字符串插值操作尤其有用。
|
||||
Swift 的 `String` 和 `Character` 类型提供了一种快速且兼容 Unicode 的方式来处理代码中的文本内容。创建和操作字符串的语法与 C 语言中字符串操作相似,轻量并且易读。通过 `+` 符号就可以非常简单的实现两个字符串的拼接操作。与 Swift 中其他值一样,能否更改字符串的值,取决于其被定义为常量还是变量。你可以在已有字符串中插入常量、变量、字面量和表达式从而形成更长的字符串,这一过程也被称为字符串插值。尤其是在为显示、存储和打印创建自定义字符串值时,字符串插值操作尤其有用。
|
||||
|
||||
尽管语法简易,但 Swift 中的 `String` 类型的实现却很快速和现代化。每一个字符串都是由编码无关的 Unicode 字符组成,并支持访问字符的多种 Unicode 表示形式。
|
||||
|
||||
@ -89,7 +89,7 @@ It also ends with a line break.
|
||||
|
||||
```swift
|
||||
let wiseWords = "\"Imagination is more important than knowledge\" - Einstein"
|
||||
// "Imageination is more important than knowledge" - Enistein
|
||||
// "Imagination is more important than knowledge" - Einstein
|
||||
let dollarSign = "\u{24}" // $,Unicode 标量 U+0024
|
||||
let blackHeart = "\u{2665}" // ♥,Unicode 标量 U+2665
|
||||
let sparklingHeart = "\u{1F496}" // 💖,Unicode 标量 U+1F496
|
||||
@ -106,11 +106,11 @@ Escaping all three quotes \"\"\"
|
||||
|
||||
### 扩展字符串分隔符 {#extended-string-delimiters}
|
||||
|
||||
您可以将字符串文字放在扩展分隔符中,这样字符串中的特殊字符将会被直接包含而非转义后的效果。将字符串放在引号(**"**)中并用数字符号(**#**)括起来。例如,打印字符串文字 **#"Line 1 \ nLine 2"#** 打印换行符转义序列(**\n**)而不是进行换行打印。
|
||||
您可以将字符串文字放在扩展分隔符中,这样字符串中的特殊字符将会被直接包含而非转义后的效果。将字符串放在引号(`"`)中并用数字符号(`#`)括起来。例如,打印字符串文字 `#"Line 1 \nLine 2"#` 会打印换行符转义序列(`\n`)而不是给文字换行。
|
||||
|
||||
如果需要字符串文字中字符的特殊效果,请匹配转义字符(**\\**)后面添加与起始位置个数相匹配的 **#** 符。 例如,如果您的字符串是 **#"Line 1 \ nLine 2"#** 并且您想要换行,则可以使用 **#“Line 1 \ #nLine 2”#** 来代替。 同样,**###"Line1 \ ### nLine2"###** 也可以实现换行效果。
|
||||
如果需要字符串文字中字符的特殊效果,请匹配转义字符(`\`)后面添加与起始位置个数相匹配的 `#` 符。 例如,如果您的字符串是 `#"Line 1 \nLine 2"#` 并且您想要换行,则可以使用 `#"Line 1 \#nLine 2"#` 来代替。 同样,`###"Line1 \###nLine2"###` 也可以实现换行效果。
|
||||
|
||||
扩展分隔符创建的字符串文字也可以是多行字符串文字。 您可以使用扩展分隔符在多行字符串中包含文本 **"""**,覆盖原有的结束文字的默认行为。例如:
|
||||
扩展分隔符创建的字符串文字也可以是多行字符串文字。 您可以使用扩展分隔符在多行字符串中包含文本 `"""`,覆盖原有的结束文字的默认行为。例如:
|
||||
|
||||
```swift
|
||||
let threeMoreDoubleQuotationMarks = #"""
|
||||
@ -157,7 +157,7 @@ constantString += " and another Highlander"
|
||||
|
||||
## 字符串是值类型 {#strings-are-value-types}
|
||||
|
||||
在 Swift 中 `String` 类型是*值类型*。如果你创建了一个新的字符串,那么当其进行常量、变量赋值操作,或在函数/方法中传递时,会进行值拷贝。在前述任一情况下,都会对已有字符串值创建新副本,并对该新副本而非原始字符串进行传递或赋值操作。值类型在 [结构体和枚举是值类型](./09_Classes_and_Structures.md#structures_and_enumerations_are_value_types) 中进行了详细描述。
|
||||
在 Swift 中 `String` 类型是*值类型*。如果你创建了一个新的字符串,那么当其进行常量、变量赋值操作,或在函数/方法中传递时,会进行值拷贝。在前述任一情况下,都会对已有字符串值创建新副本,并对该新副本而非原始字符串进行传递或赋值操作。值类型在 [结构体和枚举是值类型](./09_Structures_And_Classes.md#structures-and-enumerations-are-value-types) 中进行了详细描述。
|
||||
|
||||
Swift 默认拷贝字符串的行为保证了在函数/方法向你传递的字符串所属权属于你,无论该值来自于哪里。你可以确信传递的字符串不会被修改,除非你自己去修改它。
|
||||
|
||||
@ -178,7 +178,7 @@ for character in "Dog!🐶" {
|
||||
// 🐶
|
||||
```
|
||||
|
||||
`for-in` 循环在 [For 循环](./05_Control_Flow.md#for_loops) 中进行了详细描述。
|
||||
`for-in` 循环在 [For 循环](./05_Control_Flow.md#for-loops) 中进行了详细描述。
|
||||
|
||||
另外,通过标明一个 `Character` 类型并用字符字面量进行赋值,可以建立一个独立的字符常量或变量:
|
||||
|
||||
@ -269,6 +269,20 @@ let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
|
||||
|
||||
`multiplier` 的值也作为字符串中后面表达式的一部分。该表达式计算 `Double(multiplier) * 2.5` 的值并将结果(`7.5`)插入到字符串中。在这个例子中,表达式写为 `\(Double(multiplier) * 2.5)` 并包含在字符串字面量中。
|
||||
|
||||
你可以使用扩展字符串分隔符创建字符串,来包含不想作为字符串插值处理的字符。例如:
|
||||
|
||||
```swift
|
||||
print(#"Write an interpolated string in Swift using \(multiplier)."#)
|
||||
// 打印 "Write an interpolated string in Swift using \(multiplier)."
|
||||
```
|
||||
|
||||
如果要在使用扩展字符串分隔符的字符串中使用字符串插值,需要在反斜杠后面添加与开头和结尾数量相同扩展字符串分隔符。例如:
|
||||
|
||||
```swift
|
||||
print(#"6 times 7 is \#(6 * 7)."#)
|
||||
// 打印 "6 times 7 is 42."
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 插值字符串中写在括号中的表达式不能包含非转义反斜杠(`\`),并且不能包含回车或换行符。不过,插值字符串可以包含其他字面量。
|
||||
@ -384,7 +398,7 @@ greeting[index]
|
||||
|
||||
```swift
|
||||
greeting[greeting.endIndex] // error
|
||||
greeting.index(after: endIndex) // error
|
||||
greeting.index(after: greeting.endIndex) // error
|
||||
```
|
||||
|
||||
使用 `indices` 属性会创建一个包含全部索引的范围(`Range`),用来在一个字符串中访问单个字符。
|
||||
@ -402,7 +416,7 @@ for index in greeting.indices {
|
||||
|
||||
### 插入和删除 {#inserting-and-removing}
|
||||
|
||||
调用 `insert(_:at:)` 方法可以在一个字符串的指定索引插入一个字符,调用 `insert(contentsOf:at:)` 方法可以在一个字符串的指定索引插入一个段字符串。
|
||||
调用 `insert(_:at:)` 方法可以在一个字符串的指定索引插入一个字符,调用 `insert(contentsOf:at:)` 方法可以在一个字符串的指定索引插入一段字符串。
|
||||
|
||||
```swift
|
||||
var welcome = "hello"
|
||||
@ -430,7 +444,7 @@ welcome.removeSubrange(range)
|
||||
|
||||
## 子字符串 {#substrings}
|
||||
|
||||
当你从字符串中获取一个子字符串 —— 例如,使用下标或者 `prefix(_:)` 之类的方法 —— 就可以得到一个 `SubString` 的实例,而非另外一个 `String`。Swift 里的 `SubString` 绝大部分函数都跟 `String` 一样,意味着你可以使用同样的方式去操作 `SubString` 和 `String`。然而,跟 `String` 不同的是,你只有在短时间内需要操作字符串时,才会使用 `SubString`。当你需要长时间保存结果时,就把 `SubString` 转化为 `String` 的实例:
|
||||
当你从字符串中获取一个子字符串 —— 例如,使用下标或者 `prefix(_:)` 之类的方法 —— 就可以得到一个 `Substring` 的实例,而非另外一个 `String`。Swift 里的 `Substring` 绝大部分函数都跟 `String` 一样,意味着你可以使用同样的方式去操作 `Substring` 和 `String`。然而,跟 `String` 不同的是,你只有在短时间内需要操作字符串时,才会使用 `Substring`。当你需要长时间保存结果时,就把 `Substring` 转化为 `String` 的实例:
|
||||
|
||||
```swift
|
||||
let greeting = "Hello, world!"
|
||||
@ -442,15 +456,15 @@ let beginning = greeting[..<index]
|
||||
let newString = String(beginning)
|
||||
```
|
||||
|
||||
就像 `String`,每一个 `SubString` 都会在内存里保存字符集。而 `String` 和 `SubString` 的区别在于性能优化上,`SubString` 可以重用原 `String` 的内存空间,或者另一个 `SubString` 的内存空间(`String` 也有同样的优化,但如果两个 `String` 共享内存的话,它们就会相等)。这一优化意味着你在修改 `String` 和 `SubString` 之前都不需要消耗性能去复制内存。就像前面说的那样,`SubString` 不适合长期存储 —— 因为它重用了原 `String` 的内存空间,原 `String` 的内存空间必须保留直到它的 `SubString` 不再被使用为止。
|
||||
就像 `String`,每一个 `Substring` 都会在内存里保存字符集。而 `String` 和 `Substring` 的区别在于性能优化上,`Substring` 可以重用原 `String` 的内存空间,或者另一个 `Substring` 的内存空间(`String` 也有同样的优化,但如果两个 `String` 共享内存的话,它们就会相等)。这一优化意味着你在修改 `String` 和 `Substring` 之前都不需要消耗性能去复制内存。就像前面说的那样,`Substring` 不适合长期存储 —— 因为它重用了原 `String` 的内存空间,原 `String` 的内存空间必须保留直到它的 `Substring` 不再被使用为止。
|
||||
|
||||
上面的例子,`greeting` 是一个 `String`,意味着它在内存里有一片空间保存字符集。而由于 `beginning` 是 `greeting` 的 `SubString`,它重用了 `greeting` 的内存空间。相反,`newString` 是一个 `String` —— 它是使用 `SubString` 创建的,拥有一片自己的内存空间。下面的图展示了他们之间的关系:
|
||||
上面的例子,`greeting` 是一个 `String`,意味着它在内存里有一片空间保存字符集。而由于 `beginning` 是 `greeting` 的 `Substring`,它重用了 `greeting` 的内存空间。相反,`newString` 是一个 `String` —— 它是使用 `Substring` 创建的,拥有一片自己的内存空间。下面的图展示了他们之间的关系:
|
||||
|
||||

|
||||

|
||||
|
||||
> 注意
|
||||
>
|
||||
> `String` 和 `SubString` 都遵循 `StringProtocol<//apple_ref/swift/intf/s:s14StringProtocolP>` 协议,这意味着操作字符串的函数使用 `StringProtocol` 会更加方便。你可以传入 `String` 或 `SubString` 去调用函数。
|
||||
> `String` 和 `Substring` 都遵循 [`StringProtocol`](https://developer.apple.com/documentation/swift/stringprotocol) 协议,这意味着操作字符串的函数使用 `StringProtocol` 会更加方便。你可以传入 `String` 或 `Substring` 去调用函数。
|
||||
|
||||
## 比较字符串 {#comparing-strings}
|
||||
|
||||
@ -458,7 +472,7 @@ Swift 提供了三种方式来比较文本值:字符串字符相等、前缀
|
||||
|
||||
### 字符串/字符相等 {#string-and-character-equality}
|
||||
|
||||
字符串/字符可以用等于操作符(`==`)和不等于操作符(`!=`),详细描述在[比较运算符](./02_Basic_Operators.md#comparison_operators):
|
||||
字符串/字符可以用等于操作符(`==`)和不等于操作符(`!=`),详细描述在 [比较运算符](./02_Basic_Operators.md#comparison-operators):
|
||||
|
||||
```swift
|
||||
let quotation = "We're a lot alike, you and I."
|
||||
@ -556,13 +570,13 @@ print("\(mansionCount) mansion scenes; \(cellCount) cell scenes")
|
||||
|
||||
> 注意
|
||||
>
|
||||
> `hasPrefix(_:)` 和 `hasSuffix(_:)` 方法都是在每个字符串中逐字符比较其可扩展的字符群集是否标准相等,详细描述在[字符串/字符相等](#string_and_character_equality)。
|
||||
> `hasPrefix(_:)` 和 `hasSuffix(_:)` 方法都是在每个字符串中逐字符比较其可扩展的字符群集是否标准相等,详细描述在 [字符串/字符相等](#string-and-character-equality)。
|
||||
|
||||
## 字符串的 Unicode 表示形式 {#unicode-representations-of-strings}
|
||||
|
||||
当一个 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 兼容的方式访问字符串的值:
|
||||
|
||||
@ -617,6 +631,11 @@ let dogString = "Dog‼🐶"
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
如果表格无法正确显示,请参考下图:
|
||||
|
||||

|
||||
|
||||
|
||||
```swift
|
||||
for codeUnit in dogString.utf8 {
|
||||
print("\(codeUnit) ", terminator: "")
|
||||
@ -660,6 +679,11 @@ print("")
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
如果表格无法正确显示,请参考下图:
|
||||
|
||||

|
||||
|
||||
|
||||
```swift
|
||||
for codeUnit in dogString.utf16 {
|
||||
print("\(codeUnit) ", terminator: "")
|
||||
644
source/02_language_guide/04_Collection_Types.md
Executable file
644
source/02_language_guide/04_Collection_Types.md
Executable file
@ -0,0 +1,644 @@
|
||||
# 集合类型
|
||||
|
||||
Swift 语言提供数组(Array)、集合(Set)和字典(Dictionary)三种基本的*集合类型*用来存储集合数据。数组是有序数据的集。集合是无序无重复数据的集。字典是无序的键值对的集。
|
||||
|
||||

|
||||
|
||||
Swift 中的数组、集合和字典必须明确其中保存的键和值类型,这样就可以避免插入一个错误数据类型的值。同理,对于获取到的值你也可以放心,其数据类型是确定的。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> Swift 的数组、集合和字典类型被实现为*泛型集合*。更多关于泛型类型和集合,参见 [泛型](./22_Generics.md) 章节。
|
||||
|
||||
## 集合的可变性 {#mutability-of-collections}
|
||||
|
||||
如果创建一个数组、集合或字典并且把它分配成一个变量,这个集合将会是*可变的*。这意味着可以在创建之后添加、修改或者删除数据项。如果把数组、集合或字典分配成常量,那么它就是*不可变的*,它的大小和内容都不能被改变。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 在不需要改变集合的时候创建不可变集合是很好的实践。这样做便于你理解自己的代码,也能让 Swift 编译器优化集合的性能。
|
||||
|
||||
## 数组(Arrays) {#arrays}
|
||||
|
||||
*数组*使用有序列表存储同一类型的多个值。相同的值可以多次出现在一个数组的不同位置中。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> Swift 的 `Array` 类型被桥接到 Foundation 中的 `NSArray` 类。
|
||||
>
|
||||
> 更多关于在 Foundation 和 Cocoa 中使用 `Array` 的信息,参见 [Bridging Between Array and NSArray](https://developer.apple.com/documentation/swift/array#2846730)。
|
||||
|
||||
### 数组的简单语法 {#array-type-shorthand-syntax}
|
||||
|
||||
Swift 中数组的完整写法为 `Array<Element>`,其中 `Element` 是这个数组中唯一允许存在的数据类型。也可以使用像 `[Element]` 这样的简单语法。尽管两种形式在功能上是一样的,但是推荐较短的那种,而且在本文中都会使用这种形式来使用数组。
|
||||
|
||||
### 创建一个空数组 {#creating-an-empty-array}
|
||||
|
||||
你可以使用构造语法来创建一个由特定数据类型构成的空数组:
|
||||
|
||||
```swift
|
||||
var someInts: [Int] = []
|
||||
print("someInts is of type [Int] with \(someInts.count) items.")
|
||||
// 打印“someInts is of type [Int] with 0 items.”
|
||||
```
|
||||
|
||||
注意,通过构造函数的类型,`someInts` 的值类型被推断为 `[Int]`。
|
||||
|
||||
或者,如果代码上下文中已经提供了类型信息,例如一个函数参数或者一个已经定义好类型的常量或者变量,你可以使用空数组语句创建一个空数组,它的写法很简单:`[]`(一对空方括号):
|
||||
|
||||
```swift
|
||||
someInts.append(3)
|
||||
// someInts 现在包含一个 Int 值
|
||||
someInts = []
|
||||
// someInts 现在是空数组,但是仍然是 [Int] 类型的。
|
||||
```
|
||||
|
||||
### 创建一个带有默认值的数组 {#creating-an-array-with-a-default-value}
|
||||
|
||||
Swift 中的 `Array` 类型还提供一个可以创建特定大小并且所有数据都被默认的构造方法。可以把准备加入新数组的数据项数量(`count`)和适当类型的初始值(`repeating`)传入数组构造函数:
|
||||
|
||||
```swift
|
||||
var threeDoubles = Array(repeating: 0.0, count: 3)
|
||||
// threeDoubles 是一种 [Double] 数组,等价于 [0.0, 0.0, 0.0]
|
||||
```
|
||||
|
||||
### 通过两个数组相加创建一个数组 {#creating-an-array-by-adding-two-arrays-together}
|
||||
|
||||
你可以使用加法操作符(`+`)来组合两个已存在的相同类型数组。新数组的数据类型会从两个数组的数据类型中推断出来:
|
||||
|
||||
```swift
|
||||
var anotherThreeDoubles = Array(repeating: 2.5, count: 3)
|
||||
// anotherThreeDoubles 被推断为 [Double],等价于 [2.5, 2.5, 2.5]
|
||||
|
||||
var sixDoubles = threeDoubles + anotherThreeDoubles
|
||||
// sixDoubles 被推断为 [Double],等价于 [0.0, 0.0, 0.0, 2.5, 2.5, 2.5]
|
||||
```
|
||||
|
||||
### 用数组字面量构造数组 {#creating-an-array-with-an-array-literals}
|
||||
|
||||
你可以使用*数组字面量*来进行数组构造,这是一种用一个或者多个数值构造数组的简单方法。数组字面量是一系列由逗号分割并由方括号包含的数值:
|
||||
|
||||
`[value 1, value 2, value 3]`。
|
||||
|
||||
下面这个例子创建了一个叫做 `shoppingList` 并且存储 `String` 的数组:
|
||||
|
||||
```swift
|
||||
var shoppingList: [String] = ["Eggs", "Milk"]
|
||||
// shoppingList 已经被构造并且拥有两个初始项。
|
||||
```
|
||||
|
||||
`shoppingList` 变量被声明为“字符串值类型的数组“,记作 `[String]`。因为这个数组被规定只有 `String` 一种数据结构,所以只有 `String` 类型可以在其中被存取。在这里,`shoppingList` 数组由两个 `String` 值(`"Eggs"` 和 `"Milk"`)构造,并且由数组字面量定义。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> `shoppingList` 数组被声明为变量(`var` 关键字创建)而不是常量(`let` 创建)是因为之后会有更多的数据项被插入其中。
|
||||
|
||||
在这个例子中,字面量仅仅包含两个 `String` 值。匹配了该数组的声明(只能包含 `String` 的数组),所以可以将这个字面量的赋值过程看作用两个初始项来构造 `shoppingList` 的一种方式。
|
||||
|
||||
由于 Swift 的类型推断机制,当你用字面量构造拥有相同类型值数组的时候,不必把数组的类型定义清楚。`shoppingList` 的构造也可以这样写:
|
||||
|
||||
```swift
|
||||
var shoppingList = ["Eggs", "Milk"]
|
||||
```
|
||||
|
||||
因为所有数组字面量中的值都是相同的类型,Swift 可以推断出 `[String]` 是 `shoppingList` 中变量的正确类型。
|
||||
|
||||
### 访问和修改数组 {#accessing-and-modifying-an-array}
|
||||
|
||||
你可以通过数组的方法和属性来访问和修改数组,或者使用下标语法。
|
||||
|
||||
可以使用数组的只读属性 `count` 来获取数组中的数据项数量:
|
||||
|
||||
```swift
|
||||
print("The shopping list contains \(shoppingList.count) items.")
|
||||
// 输出“The shopping list contains 2 items.”(这个数组有2个项)
|
||||
```
|
||||
|
||||
使用布尔属性 `isEmpty` 作为一个缩写形式去检查 `count` 属性是否为 `0`:
|
||||
|
||||
```swift
|
||||
if shoppingList.isEmpty {
|
||||
print("The shopping list is empty.")
|
||||
} else {
|
||||
print("The shopping list is not empty.")
|
||||
}
|
||||
// 打印“The shopping list is not empty.”(shoppinglist 不是空的)
|
||||
```
|
||||
|
||||
也可以使用 `append(_:)` 方法在数组后面添加新的数据项:
|
||||
|
||||
```swift
|
||||
shoppingList.append("Flour")
|
||||
// shoppingList 现在有3个数据项,似乎有人在摊煎饼
|
||||
```
|
||||
|
||||
除此之外,也可以使用加法赋值运算符(`+=`)直接将另一个相同类型数组中的数据添加到该数组后面:
|
||||
|
||||
```swift
|
||||
shoppingList += ["Baking Powder"]
|
||||
// shoppingList 现在有四项了
|
||||
shoppingList += ["Chocolate Spread", "Cheese", "Butter"]
|
||||
// shoppingList 现在有七项了
|
||||
```
|
||||
|
||||
可以直接使用*下标语法*来获取数组中的数据项,把所需要数据项的索引值直接放在数组名称之后的方括号中:
|
||||
|
||||
```swift
|
||||
var firstItem = shoppingList[0]
|
||||
// 第一项是“Eggs”
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 第一项在数组中的索引值是 `0` 而不是 `1`。 Swift 中的数组索引总是从零开始。
|
||||
|
||||
你也可以用下标来改变某个有效索引值对应的数据值:
|
||||
|
||||
```swift
|
||||
shoppingList[0] = "Six eggs"
|
||||
// 其中的第一项现在是“Six eggs”而不是“Eggs”
|
||||
```
|
||||
|
||||
当你使用下标语法,所使用的下标必须是有效的。例如,试图通过 `shoppingList[shoppingList.count] = "Salt"` 在数组的最后添加一项,将产生一个运行时错误。
|
||||
|
||||
还可以利用下标来一次改变一系列数据值,即使新数据和原有数据的数量是不一样的。下面的例子把 `"Chocolate Spread"`、`"Cheese"` 和 `"Butter"` 替换为 `"Bananas"` 和 `"Apples"`:
|
||||
|
||||
```swift
|
||||
shoppingList[4...6] = ["Bananas", "Apples"]
|
||||
// shoppingList 现在有6项
|
||||
```
|
||||
|
||||
通过调用数组的 `insert(_:at:)` 方法在某个指定索引值之前添加数据项:
|
||||
|
||||
```swift
|
||||
shoppingList.insert("Maple Syrup", at: 0)
|
||||
// shoppingList 现在有7项
|
||||
// 现在是这个列表中的第一项是“Maple Syrup”
|
||||
```
|
||||
|
||||
这次 `insert(_:at:)` 方法调用把值为 `"Maple Syrup"` 的新数据项插入列表的最开始位置,并且使用 `0` 作为索引值。
|
||||
|
||||
类似的可以使用 `remove(at:)` 方法来移除数组中的某一项。这个方法把数组在特定索引值中存储的数据项移除并且返回这个被移除的数据项(不需要的时候就可以无视它):
|
||||
|
||||
```swift
|
||||
let mapleSyrup = shoppingList.remove(at: 0)
|
||||
// 索引值为0的数据项被移除
|
||||
// shoppingList 现在只有6项,而且不包括 Maple Syrup
|
||||
// mapleSyrup 常量的值等于被移除数据项“Maple Syrup”
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果你试图通过越界索引来执行访问或者修改数据的操作,会引发一个运行时错误。此时可以使用索引值和数组的 `count` 属性进行比较来在使用该索引之前检验其是否有效。除了当 `count` 等于 0 时(说明这是个空数组),最大索引值一直是 `count - 1`,因为数组都是零起索引。
|
||||
|
||||
数据项被移除后数组中的空出项会被自动填补,所以现在索引值为 `0` 的数据项的值再次等于 `"Six eggs"`:
|
||||
|
||||
```swift
|
||||
firstItem = shoppingList[0]
|
||||
// firstItem 现在等于“Six eggs”
|
||||
```
|
||||
|
||||
如果你只想把数组中的最后一项移除,可以使用 `removeLast()` 方法而不是 `remove(at:)` 方法来避免需要获取数组的 `count` 属性。就像后者一样,前者也会返回被移除的数据项:
|
||||
|
||||
```swift
|
||||
let apples = shoppingList.removeLast()
|
||||
// 数组的最后一项被移除了
|
||||
// shoppingList 现在只有5项,不包括 Apples
|
||||
// apples 常量的值现在等于字符串“Apples”
|
||||
```
|
||||
|
||||
### 数组的遍历 {#iterating-over-an-array}
|
||||
|
||||
你可以使用 `for-in` 循环来遍历数组中所有的数据项:
|
||||
|
||||
```swift
|
||||
for item in shoppingList {
|
||||
print(item)
|
||||
}
|
||||
// Six eggs
|
||||
// Milk
|
||||
// Flour
|
||||
// Baking Powder
|
||||
// Bananas
|
||||
```
|
||||
|
||||
如果同时需要每个数据项的值和索引值,可以使用 `enumerated()` 方法来进行数组遍历。`enumerated()` 返回一个由索引值和数据值组成的元组数组。索引值从零开始,并且每次增加一;如果枚举一整个数组,索引值将会和数据值一一匹配。你可以把这个元组分解成临时常量或者变量来进行遍历:
|
||||
|
||||
```swift
|
||||
for (index, value) in shoppingList.enumerated() {
|
||||
print("Item \(String(index + 1)): \(value)")
|
||||
}
|
||||
// Item 1: Six eggs
|
||||
// Item 2: Milk
|
||||
// Item 3: Flour
|
||||
// Item 4: Baking Powder
|
||||
// Item 5: Bananas
|
||||
```
|
||||
|
||||
更多关于 `for-in` 循环的介绍请参见 [For 循环](./05_Control_Flow.md#for-loops)。
|
||||
|
||||
## 集合(Sets) {#sets}
|
||||
|
||||
*集合*用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以使用集合而不是数组。
|
||||
|
||||
> 注意
|
||||
> Swift 的 `Set` 类型被桥接到 Foundation 中的 `NSSet` 类。
|
||||
>
|
||||
> 关于使用 Foundation 和 Cocoa 中 `Set` 的知识,参见 [Bridging Between Set and NSSet](https://developer.apple.com/documentation/swift/set#2845530)
|
||||
|
||||
### 集合类型的哈希值 {#hash-values-for-set-types}
|
||||
|
||||
一个类型为了存储在集合中,该类型必须是*可哈希化*的——也就是说,该类型必须提供一个方法来计算它的*哈希值*。一个哈希值是 `Int` 类型的,相等的对象哈希值必须相同,比如 `a == b`,因此必须 `a.hashValue == b.hashValue`。
|
||||
|
||||
Swift 的所有基本类型(比如 `String`、`Int`、`Double` 和 `Bool`)默认都是可哈希化的,可以作为集合值的类型或者字典键的类型。没有关联值的枚举成员值(在 [枚举](./08_Enumerations.md) 有讲述)默认也是可哈希化的。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 你可以使用自定义的类型作为集合值的类型或者是字典键的类型,但需要使自定义类型遵循 Swift 标准库中的 `Hashable` 协议。遵循 `Hashable` 协议的类型需要提供一个类型为 `Int` 的可读属性 `hashValue`。由类型的 `hashValue` 属性返回的值不需要在同一程序的不同执行周期或者不同程序之间保持相同。
|
||||
>
|
||||
> 因为 `Hashable` 协议遵循 `Equatable` 协议,所以遵循该协议的类型也必须提供一个“是否相等”运算符(`==`)的实现。这个 `Equatable` 协议要求任何遵循 `==` 实现的实例间都是一种相等的关系。也就是说,对于 `a,b,c` 三个值来说,`==` 的实现必须满足下面三种情况:
|
||||
|
||||
> * `a == a`(自反性)
|
||||
> * `a == b` 意味着 `b == a`(对称性)
|
||||
> * `a == b && b == c` 意味着 `a == c`(传递性)
|
||||
>
|
||||
> 关于遵循协议的更多信息,请看 [协议](./21_Protocols.md)。
|
||||
|
||||
### 集合类型语法 {#set-type-syntax}
|
||||
|
||||
Swift 中的集合类型被写为 `Set<Element>`,这里的 `Element` 表示集合中允许存储的类型。和数组不同的是,集合没有等价的简化形式。
|
||||
|
||||
### 创建和构造一个空的集合 {#creating-and-initalizing-an-empty-set}
|
||||
|
||||
你可以通过构造器语法创建一个特定类型的空集合:
|
||||
|
||||
```swift
|
||||
var letters = Set<Character>()
|
||||
print("letters is of type Set<Character> with \(letters.count) items.")
|
||||
// 打印“letters is of type Set<Character> with 0 items.”
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 通过构造器,这里 `letters` 变量的类型被推断为 `Set<Character>`。
|
||||
|
||||
此外,如果上下文提供了类型信息,比如作为函数的参数或者已知类型的变量或常量,你可以通过一个空的数组字面量创建一个空的集合:
|
||||
|
||||
```swift
|
||||
letters.insert("a")
|
||||
// letters 现在含有1个 Character 类型的值
|
||||
letters = []
|
||||
// letters 现在是一个空的 Set,但是它依然是 Set<Character> 类型
|
||||
```
|
||||
|
||||
### 用数组字面量创建集合 {#creating-a-set-with-an-array-literal}
|
||||
|
||||
你可以使用数组字面量来构造集合,相当于一种简化的形式将一个或者多个值作为集合元素。
|
||||
|
||||
下面的例子创建一个称之为 `favoriteGenres` 的集合来存储 `String` 类型的值:
|
||||
|
||||
```swift
|
||||
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
|
||||
// favoriteGenres 被构造成含有三个初始值的集合
|
||||
```
|
||||
|
||||
这个 `favoriteGenres` 变量被声明为“一个 `String` 值的集合”,写为 `Set<String>`。由于这个特定集合指定了值为 `String` 类型,所以它*只*允许存储 `String` 类型值。这里的 `favoriteGenres` 变量有三个 `String` 类型的初始值(`"Rock"`,`"Classical"` 和 `"Hip hop"`),以数组字面量的形式书写。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> `favoriteGenres` 被声明为一个变量(拥有 `var` 标示符)而不是一个常量(拥有 `let` 标示符),因为它里面的元素将会在之后的例子中被增加或者移除。
|
||||
|
||||
一个集合类型不能从数组字面量中被直接推断出来,因此 `Set` 类型必须显式声明。然而,由于 Swift 的类型推断功能,如果你想使用一个数组字面量构造一个集合并且与该数组字面量中的所有元素类型相同,那么无须写出集合的具体类型。`favoriteGenres` 的构造形式可以采用简化的方式代替:
|
||||
|
||||
```swift
|
||||
var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]
|
||||
```
|
||||
|
||||
由于数组字面量中的所有元素类型相同,Swift 可以推断出 `Set<String>` 作为 `favoriteGenres` 变量的正确类型。
|
||||
|
||||
### 访问和修改一个集合 {#accesing-and-modifying-a-set}
|
||||
|
||||
你可以通过集合的属性和方法来对其进行访问和修改。
|
||||
|
||||
为了获取一个集合中元素的数量,可以使用其只读属性 `count`:
|
||||
|
||||
```swift
|
||||
print("I have \(favoriteGenres.count) favorite music genres.")
|
||||
// 打印“I have 3 favorite music genres.”
|
||||
```
|
||||
|
||||
使用布尔属性 `isEmpty` 作为一个缩写形式去检查 `count` 属性是否为 `0`:
|
||||
|
||||
```swift
|
||||
if favoriteGenres.isEmpty {
|
||||
print("As far as music goes, I'm not picky.")
|
||||
} else {
|
||||
print("I have particular music preferences.")
|
||||
}
|
||||
// 打印“I have particular music preferences.”
|
||||
```
|
||||
|
||||
你可以通过调用集合的 `insert(_:)` 方法来添加一个新元素:
|
||||
|
||||
```swift
|
||||
favoriteGenres.insert("Jazz")
|
||||
// favoriteGenres 现在包含4个元素
|
||||
```
|
||||
|
||||
你可以通过调用集合的 `remove(_:)` 方法去删除一个元素,如果它是该集合的一个元素则删除它并且返回它的值,若该集合不包含它,则返回 `nil`。另外,集合可以通过 `removeAll()` 方法删除所有元素。
|
||||
|
||||
```swift
|
||||
if let removedGenre = favoriteGenres.remove("Rock") {
|
||||
print("\(removedGenre)? I'm over it.")
|
||||
} else {
|
||||
print("I never much cared for that.")
|
||||
}
|
||||
// 打印“Rock? I'm over it.”
|
||||
```
|
||||
|
||||
使用 `contains(_:)` 方法去检查集合中是否包含一个特定的值:
|
||||
|
||||
```swift
|
||||
if favoriteGenres.contains("Funk") {
|
||||
print("I get up on the good foot.")
|
||||
} else {
|
||||
print("It's too funky in here.")
|
||||
}
|
||||
// 打印“It's too funky in here.”
|
||||
```
|
||||
|
||||
### 遍历一个集合 {#iterating-over-a-set}
|
||||
|
||||
你可以在一个 `for-in` 循环中遍历一个集合中的所有值。
|
||||
|
||||
```swift
|
||||
for genre in favoriteGenres {
|
||||
print("\(genre)")
|
||||
}
|
||||
// Classical
|
||||
// Jazz
|
||||
// Hip hop
|
||||
```
|
||||
|
||||
更多关于 `for-in` 循环的信息,参见 [For 循环](./05_Control_Flow.md#for-loops)。
|
||||
|
||||
Swift 的 `Set` 类型没有确定的顺序,为了按照特定顺序来遍历一个集合中的值可以使用 `sorted()` 方法,它将返回一个有序数组,这个数组的元素排列顺序由操作符 `<` 对元素进行比较的结果来确定。
|
||||
|
||||
```swift
|
||||
for genre in favoriteGenres.sorted() {
|
||||
print("\(genre)")
|
||||
}
|
||||
// Classical
|
||||
// Hip hop
|
||||
// Jazz
|
||||
```
|
||||
|
||||
## 集合操作 {#performing-set-operations}
|
||||
|
||||
你可以高效地完成集合的一些基本操作,比如把两个集合组合到一起,判断两个集合共有元素,或者判断两个集合是否全包含,部分包含或者不相交。
|
||||
|
||||
### 基本集合操作 {#fundamental-set-operations}
|
||||
|
||||
下面的插图描述了两个集合 `a` 和 `b`,以及通过阴影部分的区域显示集合各种操作的结果。
|
||||
|
||||

|
||||
|
||||
* 使用 `intersection(_:)` 方法根据两个集合的交集创建一个新的集合。
|
||||
* 使用 `symmetricDifference(_:)` 方法根据两个集合不相交的值创建一个新的集合。
|
||||
* 使用 `union(_:)` 方法根据两个集合的所有值创建一个新的集合。
|
||||
* 使用 `subtracting(_:)` 方法根据不在另一个集合中的值创建一个新的集合。
|
||||
|
||||
```swift
|
||||
let oddDigits: Set = [1, 3, 5, 7, 9]
|
||||
let evenDigits: Set = [0, 2, 4, 6, 8]
|
||||
let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]
|
||||
|
||||
oddDigits.union(evenDigits).sorted()
|
||||
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
oddDigits.intersection(evenDigits).sorted()
|
||||
// []
|
||||
oddDigits.subtracting(singleDigitPrimeNumbers).sorted()
|
||||
// [1, 9]
|
||||
oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted()
|
||||
// [1, 2, 9]
|
||||
```
|
||||
|
||||
### 集合成员关系和相等 {#set-membership-and-equality}
|
||||
|
||||
下面的插图描述了三个集合 `a`、`b` 和 `c`,以及通过重叠区域表述集合间共享的元素。集合 `a` 是集合 `b` 的*父集合*,因为 `a` 包含了 `b` 中所有的元素。相反的,集合 `b` 是集合 `a` 的*子集合*,因为属于 `b` 的元素也被 `a` 包含。集合 `b` 和集合 `c` 是*不相交*的,因为它们之间没有共同的元素。
|
||||
|
||||

|
||||
|
||||
* 使用“是否相等”运算符(`==`)来判断两个集合包含的值是否全部相同。
|
||||
* 使用 `isSubset(of:)` 方法来判断一个集合中的所有值是否也被包含在另外一个集合中。
|
||||
* 使用 `isSuperset(of:)` 方法来判断一个集合是否包含另一个集合中所有的值。
|
||||
* 使用 `isStrictSubset(of:)` 或者 `isStrictSuperset(of:)` 方法来判断一个集合是否是另外一个集合的子集合或者父集合并且两个集合并不相等。
|
||||
* 使用 `isDisjoint(with:)` 方法来判断两个集合是否不含有相同的值(是否没有交集)。
|
||||
|
||||
```swift
|
||||
let houseAnimals: Set = ["🐶", "🐱"]
|
||||
let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"]
|
||||
let cityAnimals: Set = ["🐦", "🐭"]
|
||||
|
||||
houseAnimals.isSubset(of: farmAnimals)
|
||||
// true
|
||||
farmAnimals.isSuperset(of: houseAnimals)
|
||||
// true
|
||||
farmAnimals.isDisjoint(with: cityAnimals)
|
||||
// true
|
||||
```
|
||||
|
||||
## 字典 {#dictionaries}
|
||||
|
||||
*字典*是一种无序的集合,它存储的是键值对之间的关系,其所有键的值需要是相同的类型,所有值的类型也需要相同。每个值(value)都关联唯一的*键*(key),键作为字典中这个值数据的标识符。和数组中的数据项不同,字典中的数据项并没有具体顺序。你在需要通过标识符(键)访问数据的时候使用字典,这种方法很大程度上和在现实世界中使用字典查字义的方法一样。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> Swift 的 `Dictionary` 类型被桥接到 Foundation 的 `NSDictionary` 类。
|
||||
>
|
||||
> 更多关于在 Foundation 和 Cocoa 中使用 `Dictionary` 类型的信息,参见 [Bridging Between Dictionary and NSDictionary](https://developer.apple.com/documentation/swift/dictionary#2846239)。
|
||||
|
||||
### 字典类型简化语法 {#dictionary-type-shorthand-syntax}
|
||||
|
||||
Swift 的字典使用 `Dictionary<Key, Value>` 定义,其中 `Key` 是一种可以在字典中被用作键的类型,`Value` 是字典中对应于这些键所存储值的数据类型。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 一个字典的 `Key` 类型必须遵循 `Hashable` 协议,就像 `Set` 的值类型。
|
||||
|
||||
你也可以用 `[Key: Value]` 这样简化的形式去表示字典类型。虽然这两种形式功能上相同,但是后者是首选,并且本教程中涉及到字典类型时通篇采用后者。
|
||||
|
||||
### 创建一个空字典 {#creating-an-empty-dictionary}
|
||||
|
||||
你可以像数组一样使用构造语法创建一个拥有确定类型的空字典:
|
||||
|
||||
```swift
|
||||
var namesOfIntegers: [Int: String] = [:]
|
||||
// namesOfIntegers 是一个空的 [Int: String] 字典
|
||||
```
|
||||
|
||||
这个例子创建了一个 `[Int: String]` 类型的空字典来储存整数的英语命名。它的键是 `Int` 型,值是 `String` 型。
|
||||
|
||||
如果上下文已经提供了类型信息,你可以使用空字典字面量来创建一个空字典,记作 `[:]` (一对方括号中放一个冒号):
|
||||
|
||||
```swift
|
||||
namesOfIntegers[16] = "sixteen"
|
||||
// namesOfIntegers 现在包含一个键值对
|
||||
namesOfIntegers = [:]
|
||||
// namesOfIntegers 又成为了一个 [Int: String] 类型的空字典
|
||||
```
|
||||
|
||||
### 用字典字面量创建字典 {#creating-a-dictionary-with-a-dictionary-literal}
|
||||
|
||||
你可以使用*字典字面量*来构造字典,这和刚才介绍过的数组字面量拥有相似语法。字典字面量是一种将一个或多个键值对写作 `Dictionary` 集合的快捷途径。
|
||||
|
||||
*一个键值对*是一个键和一个值的结合体。在字典字面量中,每一个键值对的键和值都由冒号分割。这些键值对构成一个列表,其中这些键值对由逗号分割、并整体被包裹在一对方括号中:
|
||||
|
||||
```swift
|
||||
[key 1: value 1, key 2: value 2, key 3: value 3]
|
||||
```
|
||||
|
||||
下面的例子创建了一个存储国际机场名称的字典。在这个字典中键是三个字母的国际航空运输相关代码,值是机场名称:
|
||||
|
||||
```swift
|
||||
var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
|
||||
```
|
||||
|
||||
`airports` 字典被声明为一种 `[String: String]` 类型,这意味着这个字典的键和值都是 `String` 类型。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> `airports` 字典被声明为变量(用 `var` 关键字)而不是常量(用 `let` 关键字)因为后面会有更多的机场信息被添加到这个字典中。
|
||||
|
||||
`airports` 字典使用字典字面量初始化,包含两个键值对。第一对的键是 `YYZ`,值是 `Toronto Pearson`。第二对的键是 `DUB`,值是 `Dublin`。
|
||||
|
||||
这个字典语句包含了两个 `String: String` 类型的键值对。它们对应 `airports` 变量声明的类型(一个只有 `String` 键和 `String` 值的字典),所以这个字典字面量的赋值是一种方式用来构造拥有两个初始数据项的 `airport` 字典。
|
||||
|
||||
和数组一样,你在用字典字面量构造字典时,如果它的键和值都有各自一致的类型,那么就不必写出字典的类型。
|
||||
`airports` 字典也可以用这种简短方式定义:
|
||||
|
||||
```swift
|
||||
var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
|
||||
```
|
||||
|
||||
因为这个语句中所有的键和值都各自拥有相同的数据类型,Swift 可以推断出 `[String: String]` 是 `airports` 字典的正确类型。
|
||||
|
||||
### 访问和修改字典 {#accessing-and-modifying-a-dictionary}
|
||||
|
||||
你可以通过字典的方法和属性来访问和修改字典,或者通过使用下标语法。
|
||||
|
||||
和数组一样,可以通过 `Dictionary` 的只读属性 `count` 来获取字典的数据项数量:
|
||||
|
||||
```swift
|
||||
print("The dictionary of airports contains \(airports.count) items.")
|
||||
// 打印“The dictionary of airports contains 2 items.”(这个字典有两个数据项)
|
||||
```
|
||||
|
||||
使用布尔属性 `isEmpty` 作为一个缩写形式去检查 `count` 属性是否为 `0`:
|
||||
|
||||
```swift
|
||||
if airports.isEmpty {
|
||||
print("The airports dictionary is empty.")
|
||||
} else {
|
||||
print("The airports dictionary is not empty.")
|
||||
}
|
||||
// 打印“The airports dictionary is not empty.”
|
||||
```
|
||||
|
||||
你可以通过下标语法来给字典添加新的数据项。可以使用一个恰当类型的键作为下标索引,并且分配恰当类型的新值:
|
||||
|
||||
```swift
|
||||
airports["LHR"] = "London"
|
||||
// airports 字典现在有三个数据项
|
||||
```
|
||||
|
||||
也可以使用下标语法来改变特定键对应的值:
|
||||
|
||||
```swift
|
||||
airports["LHR"] = "London Heathrow"
|
||||
// “LHR”对应的值被改为“London Heathrow”
|
||||
```
|
||||
|
||||
作为一种替代下标语法的方式,字典的 `updateValue(_:forKey:)` 方法可以设置或者更新特定键对应的值。就像上面所示的下标示例,`updateValue(_:forKey:)` 方法在这个键不存在对应值的时候会设置新值或者在存在时更新已存在的值。和下标的方式不同,`updateValue(_:forKey:)` 这个方法返回更新值之前的*原值*。这样使得你可以检查更新是否成功。
|
||||
|
||||
`updateValue(_:forKey:)` 方法会返回对应值类型的可选类型。举例来说:对于存储 `String` 值的字典,这个函数会返回一个 `String?` 或者“可选 `String`”类型的值。如果有值存在于更新前,则这个可选值包含了旧值,否则它将会是 `nil` :
|
||||
|
||||
```swift
|
||||
if let oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") {
|
||||
print("The old value for DUB was \(oldValue).")
|
||||
}
|
||||
// 输出“The old value for DUB was Dublin.”
|
||||
```
|
||||
|
||||
你也可以使用下标语法来在字典中检索特定键对应的值。因为有可能请求的键没有对应的值存在,字典的下标访问会返回对应值类型的可选类型。如果这个字典包含请求键所对应的值,下标会返回一个包含这个存在值的可选类型,否则将返回 `nil`:
|
||||
|
||||
```swift
|
||||
if let airportName = airports["DUB"] {
|
||||
print("The name of the airport is \(airportName).")
|
||||
} else {
|
||||
print("That airport is not in the airports dictionary.")
|
||||
}
|
||||
// 打印“The name of the airport is Dublin Airport.”
|
||||
```
|
||||
|
||||
还可以使用下标语法通过将某个键的对应值赋值为 `nil` 来从字典里移除一个键值对:
|
||||
|
||||
```swift
|
||||
airports["APL"] = "Apple Internation"
|
||||
// “Apple Internation”不是真的 APL 机场,删除它
|
||||
airports["APL"] = nil
|
||||
// APL 现在被移除了
|
||||
```
|
||||
|
||||
此外,`removeValue(forKey:)` 方法也可以用来在字典中移除键值对。这个方法在键值对存在的情况下会移除该键值对并且返回被移除的值或者在没有对应值的情况下返回 `nil`:
|
||||
|
||||
```swift
|
||||
if let removedValue = airports.removeValue(forKey: "DUB") {
|
||||
print("The removed airport's name is \(removedValue).")
|
||||
} else {
|
||||
print("The airports dictionary does not contain a value for DUB.")
|
||||
}
|
||||
// 打印“The removed airport's name is Dublin Airport.”
|
||||
```
|
||||
|
||||
### 字典遍历 {#iterating-over-a-dictionary}
|
||||
|
||||
你可以使用 `for-in` 循环来遍历某个字典中的键值对。每一个字典中的数据项都以 `(key, value)` 元组形式返回,并且可以使用临时常量或者变量来分解这些元组:
|
||||
|
||||
```swift
|
||||
for (airportCode, airportName) in airports {
|
||||
print("\(airportCode): \(airportName)")
|
||||
}
|
||||
// YYZ: Toronto Pearson
|
||||
// LHR: London Heathrow
|
||||
```
|
||||
|
||||
更多关于 `for-in` 循环的信息,参见 [For 循环](./05_Control_Flow.md#for-loops)。
|
||||
|
||||
通过访问 `keys` 或者 `values` 属性,你也可以遍历字典的键或者值:
|
||||
|
||||
```swift
|
||||
for airportCode in airports.keys {
|
||||
print("Airport code: \(airportCode)")
|
||||
}
|
||||
// Airport code: YYZ
|
||||
// Airport code: LHR
|
||||
|
||||
for airportName in airports.values {
|
||||
print("Airport name: \(airportName)")
|
||||
}
|
||||
// Airport name: Toronto Pearson
|
||||
// Airport name: London Heathrow
|
||||
```
|
||||
|
||||
如果你需要使用某个字典的键集合或者值集合来作为某个接受 `Array` 实例的 API 的参数,可以直接使用 `keys` 或者 `values` 属性构造一个新数组:
|
||||
|
||||
```swift
|
||||
let airportCodes = [String](airports.keys)
|
||||
// airportCodes 是 ["YYZ", "LHR"]
|
||||
|
||||
let airportNames = [String](airports.values)
|
||||
// airportNames 是 ["Toronto Pearson", "London Heathrow"]
|
||||
```
|
||||
|
||||
Swift 的 `Dictionary` 是无序集合类型。为了以特定的顺序遍历字典的键或值,可以对字典的 `keys` 或 `values` 属性使用 `sorted()` 方法。
|
||||
@ -23,19 +23,19 @@ for name in names {
|
||||
// Hello, Jack!
|
||||
```
|
||||
|
||||
你也可以通过遍历一个字典来访问它的键值对。遍历字典时,字典的每项元素会以 `(key, value)` 元组的形式返回,你可以在 `for-in` 循环中使用显式的常量名称来解读 `(key, value)` 元组。下面的例子中,字典的键声明会为 `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")
|
||||
}
|
||||
// cats have 4 legs
|
||||
// ants have 6 legs
|
||||
// spiders have 8 legs
|
||||
// cats have 4 legs
|
||||
```
|
||||
|
||||
字典的内容理论上是无序的,遍历元素时的顺序是无法确定的。将元素插入字典的顺序并不会决定它们被遍历的顺序。关于数组和字典的细节,参见[集合类型](./04_Collection_Types.md)。
|
||||
字典的内容理论上是无序的,遍历元素时的顺序是无法确定的。将元素插入字典的顺序并不会决定它们被遍历的顺序。关于数组和字典的细节,参见 [集合类型](./04_Collection_Types.md)。
|
||||
|
||||
`for-in` 循环还可以使用数字范围。下面的例子用来输出乘法表的一部分内容:
|
||||
|
||||
@ -69,7 +69,7 @@ print("\(base) to the power of \(power) is \(answer)")
|
||||
|
||||
这个例子计算 base 这个数的 power 次幂(本例中,是 `3` 的 `10` 次幂),从 `1`(`3` 的 `0` 次幂)开始做 `3` 的乘法, 进行 `10` 次,使用 `1` 到 `10` 的闭区间循环。这个计算并不需要知道每一次循环中计数器具体的值,只需要执行了正确的循环次数即可。下划线符号 `_` (替代循环中的变量)能够忽略当前值,并且不提供循环遍历时对值的访问。
|
||||
|
||||
在某些情况下,你可能不想使用包括两个端点的闭区间。想象一下,你在一个手表上绘制分钟的刻度线。总共 `60` 个刻度,从 `0` 分开始。使用半开区间运算符(`..<`)来表示一个左闭右开的区间。有关区间的更多信息,请参阅[区间运算符](./02_Basic_Operators.md#range_operators)。
|
||||
在某些情况下,你可能不想使用包括两个端点的闭区间。想象一下,你在一个手表上绘制分钟的刻度线。总共 `60` 个刻度,从 `0` 分开始。使用半开区间运算符(`..<`)来表示一个左闭右开的区间。有关区间的更多信息,请参阅 [区间运算符](./02_Basic_Operators.md#range-operators)。
|
||||
|
||||
```swift
|
||||
let minutes = 60
|
||||
@ -97,6 +97,8 @@ for tickMark in stride(from: 3, through: hours, by: hourInterval) {
|
||||
}
|
||||
```
|
||||
|
||||
以上示例使用 `for-in` 循环来遍历范围、数组、字典和字符串。你可以用它来遍历任何的集合,包括实现了 [Sequence](https://developer.apple.com/documentation/swift/sequence) 协议的自定义类或集合类型。
|
||||
|
||||
## While 循环 {#while-loops}
|
||||
|
||||
`while` 循环会一直运行一段语句直到条件变成 `false`。这类循环适合使用在第一次迭代前,迭代次数未知的情况下。Swift 提供两种 `while` 循环形式:
|
||||
@ -118,7 +120,7 @@ while condition {
|
||||
|
||||
下面的例子来玩一个叫做*蛇和梯子*(也叫做*滑道和梯子*)的小游戏:
|
||||
|
||||

|
||||

|
||||
|
||||
游戏的规则如下:
|
||||
|
||||
@ -324,7 +326,7 @@ default:
|
||||
// 输出“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` 外的所有值,这个分支保证了 switch 语句的完备性。
|
||||
|
||||
#### 不存在隐式的贯穿 {#no-implicit-fallthrough}
|
||||
|
||||
@ -332,7 +334,7 @@ default:
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 虽然在 Swift 中 `break` 不是必须的,但你依然可以在 case 分支中的代码执行完毕前使用 `break` 跳出,详情请参见[Switch 语句中的 break](#break_in_a_switch_statement)。
|
||||
> 虽然在 Swift 中 `break` 不是必须的,但你依然可以在 case 分支中的代码执行完毕前使用 `break` 跳出,详情请参见 [Switch 语句中的 break](#break-in-a-switch-statement)。
|
||||
|
||||
每一个 case 分支都*必须*包含至少一条语句。像下面这样书写代码是无效的,因为第一个 case 分支是空的:
|
||||
|
||||
@ -363,11 +365,11 @@ default:
|
||||
// 输出“The letter A”
|
||||
```
|
||||
|
||||
为了可读性,符合匹配可以写成多行形式,详情请参考[复合匹配](#compound_cases)
|
||||
为了可读性,符合匹配可以写成多行形式,详情请参考 [复合匹配](#compound-cases)。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果想要显式贯穿 case 分支,请使用 `fallthrough` 语句,详情请参考[贯穿](#fallthrough)。
|
||||
> 如果想要显式贯穿 case 分支,请使用 `fallthrough` 语句,详情请参考 [贯穿](#fallthrough)。
|
||||
|
||||
#### 区间匹配 {#interval-matching}
|
||||
|
||||
@ -420,11 +422,11 @@ default:
|
||||
// 输出“(1, 1) is inside the box”
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
在上面的例子中,`switch` 语句会判断某个点是否是原点 (0, 0),是否在红色的 x 轴上,是否在橘黄色的 y 轴上,是否在一个以原点为中心的4x4的蓝色矩形里,或者在这个矩形外面。
|
||||
|
||||
不像 C 语言,Swift 允许多个 case 匹配同一个值。实际上,在这个例子中,点 (0, 0)可以匹配所有_四个 case_。但是,如果存在多个匹配,那么只会执行第一个被匹配到的 case 分支。考虑点 (0, 0)会首先匹配 `case (0, 0)`,因此剩下的能够匹配的分支都会被忽视掉。
|
||||
不像 C 语言,Swift 允许多个 case 匹配同一个值。实际上,在这个例子中,四个 `case` 都可以匹配点 (0, 0) 。但是,如果存在多个匹配,那么只会执行第一个被匹配到的 case 分支。考虑点 (0, 0)会首先匹配 `case (0, 0)`,因此剩下的能够匹配的分支都会被忽视掉。
|
||||
|
||||
#### 值绑定(Value Bindings) {#value-bindings}
|
||||
|
||||
@ -445,7 +447,7 @@ case let (x, y):
|
||||
// 输出“on the x-axis with an x value of 2”
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
在上面的例子中,`switch` 语句会判断某个点是否在红色的 x 轴上,是否在橘黄色的 y 轴上,或者不在坐标轴上。
|
||||
|
||||
@ -474,7 +476,7 @@ case let (x, y):
|
||||
// 输出“(1, -1) is on the line x == -y”
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
在上面的例子中,`switch` 语句会判断某个点是否在绿色的对角线 `x == y` 上,是否在紫色的对角线 `x == -y` 上,或者不在对角线上。
|
||||
|
||||
@ -527,7 +529,7 @@ default:
|
||||
- `return`
|
||||
- `throw`
|
||||
|
||||
我们将会在下面讨论 `continue`、`break` 和 `fallthrough` 语句。`return` 语句将会在[函数](./06_Functions.md)章节讨论,`throw` 语句会在[错误抛出](./18_Error_Handling.md#throwing_errors)章节讨论。
|
||||
我们将会在下面讨论 `continue`、`break` 和 `fallthrough` 语句。`return` 语句将会在 [函数](./06_Functions.md) 章节讨论,`throw` 语句会在 [错误抛出](./17_Error_Handling.md#throwing-errors) 章节讨论。
|
||||
|
||||
### Continue {#continue}
|
||||
|
||||
@ -637,7 +639,7 @@ print(description)
|
||||
|
||||
为了实现这个目的,你可以使用标签(*statement label*)来标记一个循环体或者条件语句,对于一个条件语句,你可以使用 `break` 加标签的方式,来结束这个被标记的语句。对于一个循环语句,你可以使用 `break` 或者 `continue` 加标签,来结束或者继续这条被标记语句的执行。
|
||||
|
||||
声明一个带标签的语句是通过在该语句的关键词的同一行前面放置一个标签,作为这个语句的前导关键字(introducor keyword),并且该标签后面跟随一个冒号。下面是一个针对 `while` 循环体的标签语法,同样的规则适用于所有的循环体和条件语句。
|
||||
声明一个带标签的语句是通过在该语句的关键词的同一行前面放置一个标签,作为这个语句的前导关键字(introducer keyword),并且该标签后面跟随一个冒号。下面是一个针对 `while` 循环体的标签语法,同样的规则适用于所有的循环体和条件语句。
|
||||
|
||||
```swift
|
||||
label name: while condition {
|
||||
@ -653,7 +655,7 @@ print(description)
|
||||
|
||||
游戏的棋盘和之前一样:
|
||||
|
||||

|
||||

|
||||
|
||||
`finalSquare`、`board`、`square` 和 `diceRoll` 值被和之前一样的方式初始化:
|
||||
|
||||
@ -754,12 +756,44 @@ if #available(iOS 10, macOS 10.12, *) {
|
||||
|
||||
以上可用性条件指定,`if` 语句的代码块仅仅在 iOS 10 或 macOS 10.12 及更高版本才运行。最后一个参数,`*`,是必须的,用于指定在所有其它平台中,如果版本号高于你的设备指定的最低版本,if 语句的代码块将会运行。
|
||||
|
||||
在它一般的形式中,可用性条件使用了一个平台名字和版本的列表。平台名字可以是 `iOS`,`macOS`,`watchOS` 和 `tvOS`——请访问[声明属性](../chapter3/06_Attributes.html)来获取完整列表。除了指定像 iOS 8 或 macOS 10.10 的大版本号,也可以指定像 iOS 11.2.6 以及 macOS 10.13.3 的小版本号。
|
||||
在它一般的形式中,可用性条件使用了一个平台名字和版本的列表。平台名字可以是 `iOS`,`macOS`,`watchOS` 和 `tvOS`——请访问 [声明属性](../03_language_reference/07_Attributes.md) 来获取完整列表。除了指定像 iOS 8 或 macOS 10.10 的大版本号,也可以指定像 iOS 11.2.6 以及 macOS 10.13.3 的小版本号。
|
||||
|
||||
```swift
|
||||
if #available(平台名称 版本号, ..., *) {
|
||||
APIs 可用,语句将执行
|
||||
} else {
|
||||
APIs 不可用,语句将不执行
|
||||
APIs 不可用,使用先前版本API的语句将执行
|
||||
}
|
||||
```
|
||||
|
||||
当你在 `guard` 语句中使用可用性条件时,它将细化用于该代码块中其余代码的可用性信息。
|
||||
|
||||
```swift
|
||||
@avaliable(macOS 10.12, *)
|
||||
struct ColorPreference {
|
||||
var bestColor = "blue"
|
||||
}
|
||||
func chooseBestColor() -> String {
|
||||
guard #avaliable(macOS 10.12, *) else{
|
||||
return "gray"
|
||||
}
|
||||
let colors = ColorPreference()
|
||||
return colors.bestColor
|
||||
}
|
||||
```
|
||||
|
||||
在上面的例子中,结构体 `ColorPreference` 需要 macOS 10.12 或更高的版本。函数 `ChooseBestColor()` 先以一个可用性防护开头,若平台版本过低无法运行 `ColorPreference` 时,将执行该低版本平台可用的行为。而在 `guard` 语句后,你将能够使用 macOS 10.12 或更高版本的API。
|
||||
|
||||
除了 `#available` 以外, Swift 还支持通过不可用性条件来进行不可用性检查。举例如下,两种检查都能实现同样的效果:
|
||||
|
||||
```swift
|
||||
if #available(iOS 10, *){
|
||||
} else {
|
||||
//回滚代码
|
||||
}
|
||||
if #unavailable(iOS 10) {
|
||||
//回滚代码
|
||||
}
|
||||
```
|
||||
|
||||
若可用性检查只提供了回滚代码,改用用 `#unavailable` 能提升程序整体的可读性。
|
||||
@ -106,7 +106,7 @@ greet(person: "Dave")
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 严格地说,即使没有明确定义返回值,该 `greet(Person:)` 函数仍然返回一个值。没有明确定义返回类型的函数的返回一个 `Void` 类型特殊值,该值为一个空元组,写成 ()。
|
||||
> 严格地说,即使没有明确定义返回值,该 `greet(Person:)` 函数仍然返回一个值。没有明确定义返回类型的函数会返回一个 `Void` 类型特殊值,该值为一个空元组,写成 ()。
|
||||
|
||||
调用函数时,可以忽略该函数的返回值:
|
||||
|
||||
@ -202,6 +202,33 @@ if let bounds = minMax(array: [8, -6, 2, 109, 3, 71]) {
|
||||
// 打印“min is -6 and max is 109”
|
||||
```
|
||||
|
||||
|
||||
### 隐式返回的函数 {#functions-with-an-implicit-return}
|
||||
如果一个函数的整个函数体是一个单行表达式,这个函数可以隐式地返回这个表达式。举个例子,以下的函数有着同样的作用:
|
||||
|
||||
```swift
|
||||
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 也可以使用隐式返回的形式。
|
||||
|
||||
>注意
|
||||
|
||||
>作为隐式返回值编写的代码需要返回一些值。例如,你不能使用 `print(13)` 作为隐式返回值。然而,你可以使用不返回值的函数(如 `fatalError("Oh no!")`)作为隐式返回值,因为 Swift 知道它们并不会产生任何隐式返回。
|
||||
|
||||
## 函数参数标签和参数名称 {#Function-Argument-Labels-and-Parameter-Names}
|
||||
|
||||
每个函数参数都有一个*参数标签(argument label)*以及一个*参数名称(parameter name)*。参数标签在调用函数的时候使用;调用的时候需要将函数的参数标签写在对应的参数前面。参数名称在函数的实现中使用。默认情况下,函数参数使用参数名称来作为它们的参数标签。
|
||||
@ -213,7 +240,7 @@ func someFunction(firstParameterName: Int, secondParameterName: Int) {
|
||||
someFunction(firstParameterName: 1, secondParameterName: 2)
|
||||
```
|
||||
|
||||
所有的参数都必须有一个独一无二的名字。虽然多个参数拥有同样的参数标签是可能的,但是一个唯一的函数标签能够使你的代码更具可读性。
|
||||
所有的参数都必须有一个独一无二的名字。虽然多个参数拥有同样的参数标签是可能的,但是一个唯一的参数标签能够使你的代码更具可读性。
|
||||
|
||||
### 指定参数标签 {#specifying-argument-labels}
|
||||
|
||||
@ -286,15 +313,13 @@ arithmeticMean(3, 8.25, 18.75)
|
||||
// 返回 10.0, 是这 3 个数的平均数。
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 一个函数最多只能拥有一个可变参数。
|
||||
一个函数能拥有多个可变参数。可变参数后的第一个形参前必须加上实参标签。实参标签用于区分实参是传递给可变参数,还是后面的形参。
|
||||
|
||||
### 输入输出参数 {#in-out-parameters}
|
||||
|
||||
函数参数默认是常量。试图在函数体中更改参数值将会导致编译错误。这意味着你不能错误地更改参数值。如果你想要一个函数可以修改参数的值,并且想要在这些修改在函数调用结束后仍然存在,那么就应该把这个参数定义为*输入输出参数(In-Out Parameters)*。
|
||||
|
||||
定义一个输入输出参数时,在参数定义前加 `inout` 关键字。一个 `输入输出参数`有传入函数的值,这个值被函数修改,然后被传出函数,替换原来的值。想获取更多的关于输入输出参数的细节和相关的编译器优化,请查看[输入输出参数](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID545)一节。
|
||||
定义一个输入输出参数时,在参数定义前加 `inout` 关键字。一个 `输入输出参数`有传入函数的值,这个值被函数修改,然后被传出函数,替换原来的值。想获取更多的关于输入输出参数的细节和相关的编译器优化,请查看 [输入输出参数](../03_language_reference/06_Declarations.md#in-out-parameters) 一节。
|
||||
|
||||
你只能传递变量给输入输出参数。你不能传入常量或者字面量,因为这些量是不能被修改的。当传入的参数作为输入输出参数时,需要在参数名前加 `&` 符,表示这个值可以被函数修改。
|
||||
|
||||
@ -432,7 +457,7 @@ func stepBackward(_ input: Int) -> Int {
|
||||
}
|
||||
```
|
||||
|
||||
如下名为 `chooseStepFunction(backward:)` 的函数,它的返回类型是 `(Int) -> Int` 类型的函数。`chooseStepFunction(backward:)` 根据布尔值 `backwards` 来返回 `stepForward(_:)` 函数或 `stepBackward(_:)` 函数:
|
||||
如下名为 `chooseStepFunction(backward:)` 的函数,它的返回类型是 `(Int) -> Int` 类型的函数。`chooseStepFunction(backward:)` 根据布尔值 `backward` 来返回 `stepForward(_:)` 函数或 `stepBackward(_:)` 函数:
|
||||
|
||||
```swift
|
||||
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
|
||||
@ -6,9 +6,9 @@
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果你不熟悉捕获(capturing)这个概念也不用担心,在[值捕获](#capturing_values)章节有它更详细的介绍。
|
||||
> 如果你不熟悉捕获(capturing)这个概念也不用担心,在 [值捕获](#capturing-values) 章节有它更详细的介绍。
|
||||
|
||||
在[函数](./06_Functions.md)章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采用如下三种形式之一:
|
||||
在 [函数](./06_Functions.md) 章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采用如下三种形式之一:
|
||||
|
||||
* 全局函数是一个有名字但不会捕获任何值的闭包
|
||||
* 嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
|
||||
@ -23,7 +23,7 @@ Swift 的闭包表达式拥有简洁的风格,并鼓励在常见场景中进
|
||||
|
||||
## 闭包表达式 {#closure-expressions}
|
||||
|
||||
[嵌套函数](./06_Functions.md#Nested_Functions)作为复杂函数的一部分时,它自包含代码块式的定义和命名形式在使用上带来了方便。当然,编写未完整声明和没有函数名的类函数结构代码是很有用的,尤其是在编码中涉及到函数作为参数的那些方法时。
|
||||
[嵌套函数](./06_Functions.md#Nested-Functions) 作为复杂函数的一部分时,它自包含代码块式的定义和命名形式在使用上带来了方便。当然,编写未完整声明和没有函数名的类函数结构代码是很有用的,尤其是在编码中涉及到函数作为参数的那些方法时。
|
||||
|
||||
*闭包表达式*是一种构建内联闭包的方式,它的语法简洁。在保证不丢失它语法清晰明了的同时,闭包表达式提供了几种优化的语法简写形式。下面通过对 `sorted(by:)` 这一个案例的多次迭代改进来展示这个过程,每次迭代都使用了更加简明的方式描述了相同功能。。
|
||||
|
||||
@ -113,13 +113,13 @@ reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
|
||||
|
||||
Swift 自动为内联闭包提供了参数名称缩写功能,你可以直接通过 `$0`,`$1`,`$2` 来顺序调用闭包的参数,以此类推。
|
||||
|
||||
如果你在闭包表达式中使用参数名称缩写,你可以在闭包定义中省略参数列表,并且对应参数名称缩写的类型会通过函数类型进行推断。`in` 关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:
|
||||
如果你在闭包表达式中使用参数名称缩写,你可以在闭包定义中省略参数列表,并且对应参数名称缩写的类型会通过函数类型进行推断。闭包接受的参数的数量取决于所使用的缩写参数的最大编号。`in` 关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:
|
||||
|
||||
```swift
|
||||
reversedNames = names.sorted(by: { $0 > $1 } )
|
||||
```
|
||||
|
||||
在这个例子中,`$0` 和 `$1` 表示闭包中第一个和第二个 `String` 类型的参数。
|
||||
在这个例子中,`$0` 和 `$1` 表示闭包中第一个和第二个 `String` 类型的参数。因为 `$1` 是编号最大的缩写参数,所以可以理解为:该闭包需要两个参数。这里的 `sorted(by:)` 函数希望得到一个参数都是字符串的闭包,因此缩写参数 `$0` 和 `$1` 的类型均为 `String`。
|
||||
|
||||
### 运算符方法 {#operator-methods}
|
||||
|
||||
@ -129,7 +129,7 @@ reversedNames = names.sorted(by: { $0 > $1 } )
|
||||
reversedNames = names.sorted(by: >)
|
||||
```
|
||||
|
||||
更多关于运算符方法的内容请查看[运算符方法](./26_Advanced_Operators.md#operator_methods)。
|
||||
更多关于运算符方法的内容请查看 [运算符方法](./27_Advanced_Operators.md#operator-methods)。
|
||||
|
||||
## 尾随闭包 {#trailing-closures}
|
||||
|
||||
@ -151,7 +151,7 @@ someFunctionThatTakesAClosure() {
|
||||
}
|
||||
```
|
||||
|
||||
在[闭包表达式语法](#closure_expression_syntax)上章节中的字符串排序闭包可以作为尾随包的形式改写在 `sorted(by:)` 方法圆括号的外面:
|
||||
在 [闭包表达式语法](#closure-expression-syntax) 上章节中的字符串排序闭包可以作为尾随包的形式改写在 `sorted(by:)` 方法圆括号的外面:
|
||||
|
||||
```swift
|
||||
reversedNames = names.sorted() { $0 > $1 }
|
||||
@ -214,6 +214,35 @@ let strings = numbers.map {
|
||||
|
||||
在上面的例子中,通过尾随闭包语法,优雅地在函数后封装了闭包的具体功能,而不再需要将整个闭包包裹在 `map(_:)` 方法的括号内。
|
||||
|
||||
如果一个函数接受多个闭包,您需要省略第一个尾随闭包的参数标签,并为其余尾随闭包添加标签。例如,以下函数将为图片库加载一张图片:
|
||||
|
||||
```swift
|
||||
func loadPicture(from server: Server, completion:(Picture) -> Void,
|
||||
onFailure: () -> Void) {
|
||||
if let picture = download("photo.jpg", from: server){
|
||||
completion(picture)
|
||||
}else{
|
||||
onFailure()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
当您调用该函数以加载图片时,需要提供两个闭包。第一个闭包是一个完成处理程序,它在成功下载后加载图片;第二个闭包是一个错误处理程序,它向用户显示错误。
|
||||
|
||||
```swift
|
||||
loadPicture(from: someServer){ picture in
|
||||
someView.currentPicture = picture
|
||||
} onFailure: {
|
||||
print("Couldn't download the next picture.")
|
||||
}
|
||||
```
|
||||
|
||||
在本例中,`loadPicture(from:completion:onFailure:)` 函数将它的网络任务分配到后台,并在网络任务完成时调用两个完成处理程序中的一个。通过这种方法编写函数,您将能够把负责处理网络故障的代码和成功下载后更新用户界面的代码干净地区分开,而不是只使用一个闭包处理两种情况。
|
||||
|
||||
>注意
|
||||
>
|
||||
>完成处理程序可能很难阅读,特别是您必须嵌套多个完成处理程序时。另一种方法是使用异步代码,如章节[并发](./28_Concurrency.md#function-types-as-return-types) 中所述。
|
||||
|
||||
## 值捕获 {#capturing-values}
|
||||
|
||||
闭包可以在其被定义的上下文中*捕获*常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
|
||||
@ -233,7 +262,7 @@ func makeIncrementer(forIncrement amount: Int) -> () -> Int {
|
||||
}
|
||||
```
|
||||
|
||||
`makeIncrementer` 返回类型为 `() -> Int`。这意味着其返回的是一个*函数*,而非一个简单类型的值。该函数在每次调用时不接受参数,只返回一个 `Int` 类型的值。关于函数返回其他函数的内容,请查看[函数类型作为返回类型](./06_Functions.md#function_types_as_return_types)。
|
||||
`makeIncrementer` 返回类型为 `() -> Int`。这意味着其返回的是一个*函数*,而非一个简单类型的值。该函数在每次调用时不接受参数,只返回一个 `Int` 类型的值。关于函数返回其他函数的内容,请查看 [函数类型作为返回类型](./06_Functions.md#function-types-as-return-types)。
|
||||
|
||||
`makeIncrementer(forIncrement:)` 函数定义了一个初始值为 `0` 的整型变量 `runningTotal`,用来存储当前总计数值。该值为 `incrementer` 的返回值。
|
||||
|
||||
@ -290,7 +319,7 @@ incrementByTen()
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果你将闭包赋值给一个类实例的属性,并且该闭包通过访问该实例或其成员而捕获了该实例,你将在闭包和该实例间创建一个循环强引用。Swift 使用捕获列表来打破这种循环强引用。更多信息,请参考[闭包引起的循环强引用](./23_Automatic_Reference_Counting.md#strong_reference_cycles_for_closures)。
|
||||
> 如果你将闭包赋值给一个类实例的属性,并且该闭包通过访问该实例或其成员而捕获了该实例,你将在闭包和该实例间创建一个循环强引用。Swift 使用捕获列表来打破这种循环强引用。更多信息,请参考 [闭包引起的循环强引用](./24_Automatic_Reference_Counting.md#strong-reference-cycles-for-closures)。
|
||||
|
||||
## 闭包是引用类型 {#closures-are-reference-types}
|
||||
|
||||
@ -364,7 +393,7 @@ print(customersInLine.count)
|
||||
// 打印出“5”
|
||||
|
||||
print("Now serving \(customerProvider())!")
|
||||
// Prints "Now serving Chris!"
|
||||
// 打印出“Now serving Chris!”
|
||||
print(customersInLine.count)
|
||||
// 打印出“4”
|
||||
```
|
||||
@ -397,7 +426,7 @@ serve(customer: customersInLine.remove(at: 0))
|
||||
>
|
||||
> 过度使用 `autoclosures` 会让你的代码变得难以理解。上下文和函数名应该能够清晰地表明求值是被延迟执行的。
|
||||
|
||||
如果你想让一个自动闭包可以“逃逸”,则应该同时使用 `@autoclosure` 和 `@escaping` 属性。`@escaping` 属性的讲解见上面的[逃逸闭包](#escaping_closures)。
|
||||
如果你想让一个自动闭包可以“逃逸”,则应该同时使用 `@autoclosure` 和 `@escaping` 属性。`@escaping` 属性的讲解见上面的 [逃逸闭包](#escaping-closures)。
|
||||
|
||||
```swift
|
||||
// customersInLine i= ["Barry", "Daniella"]
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
在 Swift 中,枚举类型是一等(first-class)类型。它们采用了很多在传统上只被类(class)所支持的特性,例如计算属性(computed properties),用于提供枚举值的附加信息,实例方法(instance methods),用于提供和枚举值相关联的功能。枚举也可以定义构造函数(initializers)来提供一个初始值;可以在原始实现的基础上扩展它们的功能;还可以遵循协议(protocols)来提供标准的功能。
|
||||
|
||||
想了解更多相关信息,请参见[属性](./10_Properties.md),[方法](./11_Methods.md),[构造过程](./14_Initialization.md),[扩展](./20_Extensions.md)和[协议](./21_Protocols.md)。
|
||||
想了解更多相关信息,请参见 [属性](./10_Properties.md),[方法](./11_Methods.md),[构造过程](./14_Initialization.md),[扩展](./20_Extensions.md) 和 [协议](./21_Protocols.md)。
|
||||
|
||||
## 枚举语法 {#enumeration-syntax}
|
||||
|
||||
@ -84,7 +84,7 @@ case .west:
|
||||
|
||||
……以此类推。
|
||||
|
||||
正如在[控制流](./05_Control_Flow.md)中介绍的那样,在判断一个枚举类型的值时,`switch` 语句必须穷举所有情况。如果忽略了 `.west` 这种情况,上面那段代码将无法通过编译,因为它没有考虑到 `CompassPoint` 的全部成员。强制穷举确保了枚举成员不会被意外遗漏。
|
||||
正如在 [控制流](./05_Control_Flow.md) 中介绍的那样,在判断一个枚举类型的值时,`switch` 语句必须穷举所有情况。如果忽略了 `.west` 这种情况,上面那段代码将无法通过编译,因为它没有考虑到 `CompassPoint` 的全部成员。强制穷举确保了枚举成员不会被意外遗漏。
|
||||
|
||||
当不需要匹配每个枚举成员的时候,你可以提供一个 `default` 分支来涵盖所有未明确处理的枚举成员:
|
||||
|
||||
@ -114,7 +114,7 @@ print("\(numberOfChoices) beverages available")
|
||||
// 打印“3 beverages available”
|
||||
```
|
||||
|
||||
在前面的例子中,通过 `Beverage.allCases` 可以访问到包含 `Beverage` 枚举所有成员的集合。`allCases` 的使用方法和其它一般集合一样——集合中的元素是枚举类型的实例,所以在上面的情况中,这些元素是 `Beverage` 值。在前面的例子中,统计了总共有多少个枚举成员。而在下面的例子中,则使用 `for` 循环来遍历所有枚举成员。
|
||||
在前面的例子中,通过 `Beverage.allCases` 可以访问到包含 `Beverage` 枚举所有成员的集合。`allCases` 的使用方法和其它一般集合一样——集合中的元素是枚举类型的实例,所以在上面的情况中,这些元素是 `Beverage` 值。在前面的例子中,统计了总共有多少个枚举成员。而在下面的例子中,则使用 `for-in` 循环来遍历所有枚举成员。
|
||||
|
||||
```swift
|
||||
for beverage in Beverage.allCases {
|
||||
@ -125,7 +125,7 @@ for beverage in Beverage.allCases {
|
||||
// juice
|
||||
```
|
||||
|
||||
在前面的例子中,使用的语法表明这个枚举遵循 [CaseIterable](https://developer.apple.com/documentation/swift/caseiterable) 协议。想了解 protocols 相关信息,请参见[协议](./21_Protocols.md)。
|
||||
在前面的例子中,使用的语法表明这个枚举遵循 [CaseIterable](https://developer.apple.com/documentation/swift/caseiterable) 协议。想了解 protocols 相关信息,请参见 [协议](./21_Protocols.md)。
|
||||
|
||||
## 关联值 {#associated-values}
|
||||
|
||||
@ -135,11 +135,11 @@ for beverage in Beverage.allCases {
|
||||
|
||||
例如,假设一个库存跟踪系统需要利用两种不同类型的条形码来跟踪商品。有些商品上标有使用 `0` 到 `9` 的数字的 UPC 格式的一维条形码。每一个条形码都有一个代表数字系统的数字,该数字后接五位代表厂商代码的数字,接下来是五位代表“产品代码”的数字。最后一个数字是检查位,用来验证代码是否被正确扫描:
|
||||
|
||||
<img width="252" height="120" alt="" src="https://docs.swift.org/swift-book/_images/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://docs.swift.org/swift-book/_images/barcode_QR_2x.png">
|
||||
<img width="169" height="169" alt="" src="https://docs.swift.org/swift-book/images/barcode_QR@2x.png">
|
||||
|
||||
这便于库存跟踪系统用包含四个整型值的元组存储 UPC 码,以及用任意长度的字符串储存 QR 码。
|
||||
|
||||
@ -200,7 +200,7 @@ case let .qrCode(productCode):
|
||||
|
||||
## 原始值 {#raw-values}
|
||||
|
||||
在[关联值](#associated_values)小节的条形码例子中,演示了如何声明存储不同类型关联值的枚举成员。作为关联值的替代选择,枚举成员可以被默认值(称为*原始值*)预填充,这些原始值的类型必须相同。
|
||||
在 [关联值](#associated-values) 小节的条形码例子中,演示了如何声明存储不同类型关联值的枚举成员。作为关联值的替代选择,枚举成员可以被默认值(称为*原始值*)预填充,这些原始值的类型必须相同。
|
||||
|
||||
这是一个使用 ASCII 码作为原始值的枚举:
|
||||
|
||||
@ -212,7 +212,7 @@ enum ASCIIControlCharacter: Character {
|
||||
}
|
||||
```
|
||||
|
||||
枚举类型 `ASCIIControlCharacter` 的原始值类型被定义为 `Character`,并设置了一些比较常见的 ASCII 控制字符。`Character` 的描述详见[字符串和字符](./03_Strings_and_Characters.md)部分。
|
||||
枚举类型 `ASCIIControlCharacter` 的原始值类型被定义为 `Character`,并设置了一些比较常见的 ASCII 控制字符。`Character` 的描述详见 [字符串和字符](./03_Strings_and_Characters.md) 部分。
|
||||
|
||||
原始值可以是字符串、字符,或者任意整型值或浮点型值。每个原始值在枚举声明中必须是唯一的。
|
||||
|
||||
@ -273,7 +273,7 @@ let possiblePlanet = Planet(rawValue: 7)
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 原始值构造器是一个可失败构造器,因为并不是每一个原始值都有与之对应的枚举成员。更多信息请参见[可失败构造器](../chapter3/05_Declarations.html#failable_initializers)
|
||||
> 原始值构造器是一个可失败构造器,因为并不是每一个原始值都有与之对应的枚举成员。更多信息请参见 [可失败构造器](../03_language_reference/06_Declarations.md#failable-initializers)。
|
||||
|
||||
如果你试图寻找一个位置为 `11` 的行星,通过原始值构造器返回的可选 `Planet` 值将是 `nil`:
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 结构体和类
|
||||
|
||||
*结构体*和*类*作为一种通用而又灵活的结构,成为了人们构建代码的基础。你可以使用定义常量、变量和函数的语法,为你的结构体和类定义属性、添加方法。
|
||||
*结构体*和*类*作为一种通用而又灵活的结构,成为了人们构建代码的基础。你可以使用定义常量、变量和函数的语法,为你的结构体和类定义属性、添加方法。
|
||||
|
||||
与其他编程语言所不同的是,Swift 并不要求你为自定义的结构体和类的接口与实现代码分别创建文件。你只需在单一的文件中定义一个结构体或者类,系统将会自动生成面向其它代码的外部接口。
|
||||
|
||||
@ -28,10 +28,14 @@ Swift 中结构体和类有很多共同点。两者都可以:
|
||||
* 析构器允许一个类实例释放任何其所被分配的资源
|
||||
* 引用计数允许对一个类的多次引用
|
||||
|
||||
更多信息请参见 [继承](./13_Inheritance.md)、[类型转换](./18_Type_Casting.md)、[析构过程](./15_Deinitialization.md) 和 [自动引用计数](./23_Automatic_Reference_Counting.md)。
|
||||
更多信息请参见 [继承](./13_Inheritance.md)、[类型转换](./18_Type_Casting.md)、[析构过程](./15_Deinitialization.md) 和 [自动引用计数](./24_Automatic_Reference_Counting.md)。
|
||||
|
||||
类支持的附加功能是以增加复杂性为代价的。作为一般准则,优先使用结构体,因为它们更容易理解,仅在适当或必要时才使用类。实际上,这意味着你的大多数自定义数据类型都会是结构体和枚举。更多详细的比较参见 [在结构和类之间进行选择](https://developer.apple.com/documentation/swift/choosing_between_structures_and_classes)。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 类和 actors 共享很多特性。更多信息请参见 [并发](./28_Concurrency.md)。
|
||||
|
||||
### 类型定义的语法 {#definition-syntax}
|
||||
|
||||
结构体和类有着相似的定义方式。你通过 `struct` 关键字引入结构体,通过 `class` 关键字引入类,并将它们的具体定义放在一对大括号中:
|
||||
@ -47,7 +51,7 @@ class SomeClass {
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 每当你定义一个新的结构体或者类时,你都是定义了一个新的 Swift 类型。请使用 `UpperCamelCase` 这种方式来命名类型(如这里的 `SomeClass` 和 `SomeStructure`),以便符合标准 Swift 类型的大写命名风格(如 `String`,`Int` 和 `Bool`)。请使用 `lowerCamelCase` 这种方式来命名属性和方法(如 `framerate` 和 `incrementCount`),以便和类型名区分。
|
||||
> 每当你定义一个新的结构体或者类时,你都是定义了一个新的 Swift 类型。请使用 `UpperCamelCase` 这种方式来命名类型(如这里的 `SomeClass` 和 `SomeStructure`),以便符合标准 Swift 类型的大写命名风格(如 `String`,`Int` 和 `Bool`)。请使用 `lowerCamelCase` 这种方式来命名属性和方法(如 `frameRate` 和 `incrementCount`),以便和类型名区分。
|
||||
|
||||
以下是定义结构体和定义类的示例:
|
||||
|
||||
@ -125,7 +129,7 @@ let vga = Resolution(width: 640, height: 480)
|
||||
|
||||
Swift 中所有的结构体和枚举类型都是值类型。这意味着它们的实例,以及实例中所包含的任何值类型的属性,在代码中传递的时候都会被复制。
|
||||
|
||||
> 注意
|
||||
> 注意
|
||||
>
|
||||
> 标准库定义的集合,例如数组,字典和字符串,都对复制进行了优化以降低性能成本。新集合不会立即复制,而是跟原集合共享同一份内存,共享同样的元素。在集合的某个副本要被修改前,才会复制它的元素。而你在代码中看起来就像是立即发生了复制。
|
||||
|
||||
@ -162,7 +166,7 @@ print("hd is still \(hd.width) pixels wide")
|
||||
|
||||
将 `hd` 赋值给 `cinema` 时,`hd` 中所存储的*值*会拷贝到新的 `cinema` 实例中。结果就是两个完全独立的实例包含了相同的数值。由于两者相互独立,因此将 `cinema` 的 `width` 修改为 `2048` 并不会影响 `hd` 中的 `width` 的值,如下图所示:
|
||||
|
||||

|
||||

|
||||
|
||||
枚举也遵循相同的行为准则:
|
||||
|
||||
@ -210,7 +214,7 @@ alsoTenEighty.frameRate = 30.0
|
||||
|
||||
因为类是引用类型,所以 `tenEight` 和 `alsoTenEight` 实际上引用的是同一个 `VideoMode` 实例。换句话说,它们是同一个实例的两种叫法,如下图所示:
|
||||
|
||||

|
||||

|
||||
|
||||
通过查看 `tenEighty` 的 `frameRate` 属性,可以看到它正确地显示了底层的 `VideoMode` 实例的新帧率 `30.0`:
|
||||
|
||||
@ -221,7 +225,7 @@ print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
|
||||
|
||||
这个例子也显示了为何引用类型更加难以理解。如果 `tenEighty` 和 `alsoTenEighty` 在你代码中的位置相距很远,那么就很难找到所有修改视频模式的地方。无论在哪使用 `tenEighty`,你都要考虑使用 `alsoTenEighty` 的代码,反之亦然。相反,值类型就更容易理解了,因为你的源码中与同一个值交互的代码都很近。
|
||||
|
||||
需要注意的是 `tenEighty` 和 `alsoTenEighty` 被声明为常量而不是变量。然而你依然可以改变 `tenEighty.frameRate` 和 `alsoTenEighty.frameRate`,这是因为 `tenEighty` 和 `alsoTenEighty` 这两个常量的值并未改变。它们并不“存储”这个 `VideoMode` 实例,而仅仅是对 `VideoMode` 实例的引用。所以,改变的是底层 `VideoMode` 实例的 `frameRate` 属性,而不是指向 `VideoMode` 的常量引用的值。
|
||||
需要注意的是 `tenEighty` 和 `alsoTenEighty` 被声明为常量而不是变量。然而你依然可以改变 `tenEighty.frameRate` 和 `alsoTenEighty.frameRate`,这是因为 `tenEighty` 和 `alsoTenEighty` 这两个常量的值并未改变。它们并不“存储”这个 `VideoMode` 实例,而仅仅是对 `VideoMode` 实例的引用。所以,改变的是底层 `VideoMode` 实例的 `frameRate` 属性,而不是指向 `VideoMode` 的常量引用的值。
|
||||
|
||||
### 恒等运算符 {#identity-operators}
|
||||
|
||||
@ -243,8 +247,8 @@ if tenEighty === alsoTenEighty {
|
||||
|
||||
请注意,“相同”(用三个等号表示,`===`)与“等于”(用两个等号表示,`==`)的不同。“相同”表示两个类类型(class type)的常量或者变量引用同一个类实例。“等于”表示两个实例的值“相等”或“等价”,判定时要遵照设计者定义的评判标准。
|
||||
|
||||
当在定义你的自定义结构体和类的时候,你有义务来决定判定两个实例“相等”的标准。在章节 [等价操作符](./26_Advanced_Operators.md#equivalence_operators) 中将会详细介绍实现自定义 == 和 !== 运算符的流程。
|
||||
当在定义你的自定义结构体和类的时候,你有义务来决定判定两个实例“相等”的标准。在章节 [等价操作符](./27_Advanced_Operators.md#equivalence-operators) 中将会详细介绍实现自定义 == 和 != 运算符的流程。
|
||||
|
||||
### 指针 {#pointers}
|
||||
|
||||
如果你有 C,C++ 或者 Objective-C 语言的经验,那么你也许会知道这些语言使用*指针*来引用内存中的地址。Swift 中引用了某个引用类型实例的常量或变量,与 C 语言中的指针类似,不过它并不直接指向某个内存地址,也不要求你使用星号(`*`)来表明你在创建一个引用。相反,Swift 中引用的定义方式与其它的常量或变量的一样。如果需要直接与指针交互,你可以使用标准库提供的指针和缓冲区类型 —— 参见 [手动管理内存](https://developer.apple.com/documentation/swift/swift_standard_library/manual_memory_management)。
|
||||
如果你有 C,C++ 或者 Objective-C 语言的经验,那么你也许会知道这些语言使用*指针*来引用内存中的地址。Swift 中引用了某个引用类型实例的常量或变量,与 C 语言中的指针类似,不过它并不直接指向某个内存地址,也不要求你使用星号(`*`)来表明你在创建一个引用。相反,Swift 中引用的定义方式与其它的常量或变量的一样。如果需要直接与指针交互,你可以使用标准库提供的指针和缓冲区类型 —— 参见 [手动管理内存](https://developer.apple.com/documentation/swift/swift_standard_library/manual_memory_management)。
|
||||
@ -6,11 +6,12 @@
|
||||
|
||||
另外,还可以定义属性观察器来监控属性值的变化,以此来触发自定义的操作。属性观察器可以添加到类本身定义的存储属性上,也可以添加到从父类继承的属性上。
|
||||
|
||||
你也可以利用属性包装器来复用多个属性的 getter 和 setter 中的代码。
|
||||
## 存储属性 {#stored-properties}
|
||||
|
||||
简单来说,一个存储属性就是存储在特定类或结构体实例里的一个常量或变量。存储属性可以是*变量存储属性*(用关键字 `var` 定义),也可以是*常量存储属性*(用关键字 `let` 定义)。
|
||||
|
||||
可以在定义存储属性的时候指定默认值,请参考[默认构造器](./14_Initialization.md#default_initializers)一节。也可以在构造过程中设置或修改存储属性的值,甚至修改常量存储属性的值,请参考[构造过程中常量属性的修改](./14_Initialization.md#assigning_constant_properties_during_initialization)一节。
|
||||
可以在定义存储属性的时候指定默认值,请参考 [默认构造器](./14_Initialization.md#default-initializers) 一节。也可以在构造过程中设置或修改存储属性的值,甚至修改常量存储属性的值,请参考 [构造过程中常量属性的修改](./14_Initialization.md#assigning-constant-properties-during-initialization) 一节。
|
||||
|
||||
下面的例子定义了一个名为 `FixedLengthRange` 的结构体,该结构体用于描述整数的区间,且这个范围值在被创建后不能被修改。
|
||||
|
||||
@ -68,7 +69,7 @@ class DataImporter {
|
||||
|
||||
class DataManager {
|
||||
lazy var importer = DataImporter()
|
||||
var data = [String]()
|
||||
var data: [String] = []
|
||||
// 这里会提供数据管理功能
|
||||
}
|
||||
|
||||
@ -98,13 +99,13 @@ print(manager.importer.fileName)
|
||||
|
||||
### 存储属性和实例变量 {#stored-properties-and-instance-variables}
|
||||
|
||||
如果您有过 Objective-C 经验,应该知道 Objective-C 为类实例存储值和引用提供两种方法。除了属性之外,还可以使用实例变量作为一个备份存储将变量值赋值给属性。
|
||||
如果你有过 Objective-C 经验,应该知道 Objective-C 为类实例存储值和引用提供两种方法。除了属性之外,还可以使用实例变量作为一个备份存储将变量值赋值给属性。
|
||||
|
||||
Swift 编程语言中把这些理论统一用属性来实现。Swift 中的属性没有对应的实例变量,属性的备份存储也无法直接访问。这就避免了不同场景下访问方式的困扰,同时也将属性的定义简化成一个语句。属性的全部信息——包括命名、类型和内存管理特征——作为类型定义的一部分,都定义在一个地方。
|
||||
|
||||
## 计算属性 {#computed-properties}
|
||||
|
||||
除存储属性外,类、结构体和枚举可以定义*计算属性*。计算属性不直接存储值,而是提供一个 getter 和一个可选的 setter,来间接获取和设置其他属性或变量的值。
|
||||
除存储属性外,类、结构体和枚举还可以定义*计算属性*。计算属性不直接存储值,而是提供一个 getter 和一个可选的 setter,来间接获取和设置其他属性或变量的值。
|
||||
|
||||
```swift
|
||||
struct Point {
|
||||
@ -131,6 +132,7 @@ struct Rect {
|
||||
var square = Rect(origin: Point(x: 0.0, y: 0.0),
|
||||
size: Size(width: 10.0, height: 10.0))
|
||||
let initialSquareCenter = square.center
|
||||
// initialSquareCenter 位于(5.0, 5.0)
|
||||
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)”
|
||||
@ -146,11 +148,11 @@ print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
|
||||
|
||||
上述例子中创建了一个名为 `square` 的 `Rect` 实例,初始值原点是 `(0, 0)`,宽度高度都是 `10`。如下图中蓝色正方形所示。
|
||||
|
||||
`square` 的 `center` 属性可以通过点运算符(`square.center`)来访问,这会调用该属性的 getter 来获取它的值。跟直接返回已经存在的值不同,getter 实际上通过计算然后返回一个新的 `Point` 来表示 `square` 的中心点。如代码所示,它正确返回了中心点 `(5, 5)`。
|
||||
`square` 的 `center` 属性可以通过点运算符(`square.center`)来访问,这会调用该属性的 getter 来获取它的值。跟直接返回已经存在的值不同,getter 实际上是通过计算然后返回一个新的 `Point` 来表示 `square` 的中心点。如代码所示,它正确返回了中心点 `(5, 5)`。
|
||||
|
||||
`center` 属性之后被设置了一个新的值 `(15, 15)`,表示向右上方移动正方形到如下图橙色正方形所示的位置。设置属性 `center` 的值会调用它的 setter 来修改属性 `origin` 的 `x` 和 `y` 的值,从而实现移动正方形到新的位置。
|
||||
|
||||
<img src="https://docs.swift.org/swift-book/_images/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" />
|
||||
|
||||
### 简化 Setter 声明 {#shorthand-setter-declaration}
|
||||
|
||||
@ -174,6 +176,28 @@ struct AlternativeRect {
|
||||
}
|
||||
```
|
||||
|
||||
### 简化 Getter 声明 {#shorthand-getter-declaration}
|
||||
如果整个 getter 是单一表达式,getter 会隐式地返回这个表达式结果。下面是另一个版本的 `Rect` 结构体,用到了简化的 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 的计算属性叫*只读计算属性*。只读计算属性总是返回一个值,可以通过点运算符访问,但不能设置新的值。
|
||||
@ -202,7 +226,12 @@ print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
|
||||
|
||||
属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,即使新值和当前值相同的时候也不例外。
|
||||
|
||||
你可以为除了延时加载存储属性之外的其他存储属性添加属性观察器,你也可以在子类中通过重写属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器。你不必为非重写的计算属性添加属性观察器,因为你可以直接通过它的 setter 监控和响应值的变化。属性重写请参考[重写](./13_Inheritance.md#overriding)。
|
||||
你可以在以下位置添加属性观察器:
|
||||
* 自定义的存储属性
|
||||
* 继承的存储属性
|
||||
* 继承的计算属性
|
||||
|
||||
对于继承的属性,你可以在子类中通过重写属性的方式为它添加属性观察器。对于自定义的计算属性来说,使用它的 setter 监控和响应值的变化,而不是尝试创建观察器。属性重写请参考 [重写](./13_Inheritance.md#overriding)。
|
||||
|
||||
可以为属性添加其中一个或两个观察器:
|
||||
|
||||
@ -217,7 +246,7 @@ print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
|
||||
>
|
||||
> 在父类初始化方法调用之后,在子类构造器中给父类的属性赋值时,会调用父类属性的 `willSet` 和 `didSet` 观察器。而在父类初始化方法调用之前,给子类的属性赋值时不会调用子类属性的观察器。
|
||||
>
|
||||
> 有关构造器代理的更多信息,请参考[值类型的构造器代理](./14_Initialization.md#initializer_delegation_for_value_types)和[类的构造器代理](./14_Initialization.md#initializer_delegation_for_class_types)。
|
||||
> 有关构造器代理的更多信息,请参考 [值类型的构造器代理](./14_Initialization.md#initializer-delegation-for-value-types) 和 [类的构造器代理](./14_Initialization.md#initializer-delegation-for-class-types)。
|
||||
|
||||
下面是一个 `willSet` 和 `didSet` 实际运用的例子,其中定义了一个名为 `StepCounter` 的类,用来统计一个人步行时的总步数。这个类可以跟计步器或其他日常锻炼的统计装置的输入数据配合使用。
|
||||
|
||||
@ -256,7 +285,248 @@ stepCounter.totalSteps = 896
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果将带有观察器的属性通过 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 参数详细的介绍,请参考 [输入输出参数](../03_language_reference/06_Declarations.md#in-out-parameters)。
|
||||
|
||||
## 属性包装器 {#property-wrappers}
|
||||
属性包装器在管理属性如何存储和定义属性的代码之间添加了一个分隔层。举例来说,如果你的属性需要线程安全性检查或者需要在数据库中存储它们的基本数据,那么必须给每个属性添加同样的逻辑代码。当使用属性包装器时,你只需在定义属性包装器时编写一次管理代码,然后应用到多个属性上来进行复用。
|
||||
|
||||
定义一个属性包装器,你需要创建一个定义 `wrappedValue` 属性的结构体、枚举或者类。在下面的代码中,`TwelveOrLess` 结构体确保它包装的值始终是小于等于 12 的数字。如果要求它存储一个更大的数字,它则会存储 12 这个数字。
|
||||
|
||||
```swift
|
||||
@propertyWrapper
|
||||
struct TwelveOrLess {
|
||||
private var number = 0
|
||||
var wrappedValue: Int {
|
||||
get { return number }
|
||||
set { number = min(newValue, 12) }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这个 setter 确保新值小于或等于 12,而且返回被存储的值。
|
||||
> 注意
|
||||
>
|
||||
> 上面例子以 `private` 的方式声明 `number` 变量,这使得 `number` 仅在 `TwelveOrLess` 的实现中使用。写在其他地方的代码通过使用 `wrappedValue` 的 getter 和 setter 来获取这个值,但不能直接使用 `number`。有关 `private` 的更多信息,请参考 [访问控制](./26_Access_Control.md)
|
||||
|
||||
通过在属性之前写上包装器名称作为特性的方式,你可以把一个包装器应用到一个属性上去。这里有个存储小矩形的结构体,通过 `TwelveOrLess` 属性包装器来确保它的长宽均小于等于 12。
|
||||
|
||||
```swift
|
||||
struct SmallRectangle {
|
||||
@TwelveOrLess var height: Int
|
||||
@TwelveOrLess var width: Int
|
||||
}
|
||||
|
||||
var rectangle = SmallRectangle()
|
||||
print(rectangle.height)
|
||||
// 打印 "0"
|
||||
|
||||
rectangle.height = 10
|
||||
print(rectangle.height)
|
||||
// 打印 "10"
|
||||
|
||||
rectangle.height = 24
|
||||
print(rectangle.height)
|
||||
// 打印 "12"
|
||||
```
|
||||
|
||||
`height` 和 `width` 属性从 `TwelveOrLess` 的定义中获取它们的初始值。该定义把 `TwelveOrLess.number` 设置为 0。把数字 10 存进 `rectangle.height` 中的操作能成功,是因为数字 10 很小。尝试存储 24 的操作实际上存储的值为 12,这是因为对于这个属性的 setter 的规则来说,24 太大了。
|
||||
|
||||
当你把一个包装器应用到一个属性上时,编译器将合成提供包装器存储空间和通过包装器访问属性的代码。(属性包装器只负责存储被包装值,所以没有合成这些代码。)不利用这个特性语法的情况下,你可以写出使用属性包装器行为的代码。举例来说,这是先前代码清单中的 `SmallRectangle` 的另一个版本。这个版本将其属性明确地包装在 `TwelveOrLess` 结构体中,而不是把 `@TwelveOrLess` 作为特性写下来:
|
||||
|
||||
```swift
|
||||
struct SmallRectangle {
|
||||
private var _height = TwelveOrLess()
|
||||
private var _width = TwelveOrLess()
|
||||
var height: Int {
|
||||
get { return _height.wrappedValue }
|
||||
set { _height.wrappedValue = newValue }
|
||||
}
|
||||
var width: Int {
|
||||
get { return _width.wrappedValue }
|
||||
set { _width.wrappedValue = newValue }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`_height` 和 `_width` 属性存着这个属性包装器的一个实例,即 `TwelveOrLess`。`height` 和 `width` 的 getter 和 setter 把对 `wrappedValue` 属性的访问包装起来。
|
||||
|
||||
### 设置被包装属性的初始值 {#setting-initial-values-for-wrapped-properties}
|
||||
上面例子中的代码通过在 `TwelveOrLess` 的定义中赋予 `number` 一个初始值来设置被包装属性的初始值。使用这个属性包装器的代码没法为被 `TwelveOrLess` 包装的属性指定其他初始值。举例来说,`SmallRectangle` 的定义没法给 `height` 或者 `width` 一个初始值。为了支持设定一个初始值或者其他自定义操作,属性包装器需要添加一个构造器。这是 `TwelveOrLess` 的扩展版本,称为 `SmallNumber`。`SmallNumber` 定义了能设置被包装值和最大值的构造器:
|
||||
|
||||
|
||||
```swift
|
||||
@propertyWrapper
|
||||
struct SmallNumber {
|
||||
private var maximum: Int
|
||||
private var number: Int
|
||||
|
||||
var wrappedValue: Int {
|
||||
get { return number }
|
||||
set { number = min(newValue, maximum) }
|
||||
}
|
||||
|
||||
init() {
|
||||
maximum = 12
|
||||
number = 0
|
||||
}
|
||||
init(wrappedValue: Int) {
|
||||
maximum = 12
|
||||
number = min(wrappedValue, maximum)
|
||||
}
|
||||
init(wrappedValue: Int, maximum: Int) {
|
||||
self.maximum = maximum
|
||||
number = min(wrappedValue, maximum)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`SmallNumber` 的定义包括三个构造器——`init()`、`init(wrappedValue:)` 和 `init(wrappedValue:maximum:)`——下面的示例使用这三个构造器来设置被包装值和最大值。有关构造过程和构造器语法的更多信息,请参考 [构造过程](./14_Initialization.md)。
|
||||
|
||||
当你把包装器应用于属性且没有设定初始值时,Swift 使用 `init()` 构造器来设置包装器。举个例子:
|
||||
|
||||
```swift
|
||||
struct ZeroRectangle {
|
||||
@SmallNumber var height: Int
|
||||
@SmallNumber var width: Int
|
||||
}
|
||||
|
||||
var zeroRectangle = ZeroRectangle()
|
||||
print(zeroRectangle.height, zeroRectangle.width)
|
||||
// 打印 "0 0"
|
||||
```
|
||||
|
||||
调用 `SmallNumber()` 来创建包装 `height` 和 `width` 的 `SmallNumber` 的实例。构造器内部的代码使用默认值 0 和 12 设置初始的被包装值和初始的最大值。像之前使用在 `SmallRectangle` 中使用 `TwelveOrLess` 的例子,这个属性包装器仍然提供所有的初始值。与这个例子不同的是,`SmallNumber` 也支持把编写这些初始值作为声明属性的一部分。
|
||||
|
||||
当你为属性指定初始值时,Swift 使用 `init(wrappedValue:)` 构造器来设置包装器。举个例子:
|
||||
|
||||
```swift
|
||||
struct UnitRectangle {
|
||||
@SmallNumber var height: Int = 1
|
||||
@SmallNumber var width: Int = 1
|
||||
}
|
||||
|
||||
var unitRectangle = UnitRectangle()
|
||||
print(unitRectangle.height, unitRectangle.width)
|
||||
// 打印 "1 1"
|
||||
```
|
||||
|
||||
当你对一个被包装的属性写下 `= 1` 时,这被转换为调用 `init(wrappedValue:)` 构造器。调用 `SmallNumber(wrappedValue: 1)`来创建包装 `height` 和 `width` 的 `SmallNumber` 的实例。构造器使用此处指定的被包装值,且使用的默认最大值为 12。
|
||||
|
||||
当你在自定义特性后面把实参写在括号里时,Swift 使用接受这些实参的构造器来设置包装器。举例来说,如果你提供初始值和最大值,Swift 使用 `init(wrappedValue:maximum:)` 构造器:
|
||||
|
||||
```swift
|
||||
struct NarrowRectangle {
|
||||
@SmallNumber(wrappedValue: 2, maximum: 5) var height: Int
|
||||
@SmallNumber(wrappedValue: 3, maximum: 4) var width: Int
|
||||
}
|
||||
|
||||
var narrowRectangle = NarrowRectangle()
|
||||
print(narrowRectangle.height, narrowRectangle.width)
|
||||
// 打印 "2 3"
|
||||
|
||||
narrowRectangle.height = 100
|
||||
narrowRectangle.width = 100
|
||||
print(narrowRectangle.height, narrowRectangle.width)
|
||||
// 打印 "5 4"
|
||||
```
|
||||
|
||||
调用 `SmallNumber(wrappedValue: 2, maximum: 5)` 来创建包装 `height` 的 `SmallNumber` 的一个实例。调用 `SmallNumber(wrappedValue: 3, maximum: 4)` 来创建包装 `width` 的 `SmallNumber` 的一个实例。
|
||||
|
||||
通过将实参包含到属性包装器中,你可以设置包装器的初始状态,或者在创建包装器时传递其他的选项。这种语法是使用属性包装器最通用的方法。你可以为这个属性提供任何所需的实参,且它们将被传递给构造器。
|
||||
|
||||
当包含属性包装器实参时,你也可以使用赋值来指定初始值。Swift 将赋值视为 `wrappedValue` 参数,且使用接受被包含的实参的构造器。举个例子:
|
||||
|
||||
```swift
|
||||
struct MixedRectangle {
|
||||
@SmallNumber var height: Int = 1
|
||||
@SmallNumber(maximum: 9) var width: Int = 2
|
||||
}
|
||||
|
||||
var mixedRectangle = MixedRectangle()
|
||||
print(mixedRectangle.height)
|
||||
// 打印 "1"
|
||||
|
||||
mixedRectangle.height = 20
|
||||
print(mixedRectangle.height)
|
||||
// 打印 "12"
|
||||
```
|
||||
|
||||
调用 `SmallNumber(wrappedValue: 1)` 来创建包装 `height` 的 `SmallNumber` 的一个实例,这个实例使用默认最大值 12。调用 `SmallNumber(wrappedValue: 2, maximum: 9)` 来创建包装 `width` 的 `SmallNumber` 的一个实例。
|
||||
|
||||
### 从属性包装器中呈现一个值 {#projecting-a-value-from-a-property-wrapper}
|
||||
除了被包装值,属性包装器可以通过定义被呈现值暴露出其他功能。举个例子,管理对数据库的访问的属性包装器可以在它的被呈现值上暴露出 `flushDatabaseConnection()` 方法。除了以货币符号(\$)开头,被呈现值的名称和被包装值是一样的。因为你的代码不能够定义以 $ 开头的属性,所以被呈现值永远不会与你定义的属性有冲突。
|
||||
|
||||
在之前 `SmallNumber` 的例子中,如果你尝试把这个属性设置为一个很大的数值,属性包装器会在存储这个数值之前调整这个数值。以下的代码把被呈现值添加到 `SmallNumber` 结构体中来追踪在存储新值之前属性包装器是否为这个属性调整了新值。
|
||||
|
||||
|
||||
```swift
|
||||
@propertyWrapper
|
||||
struct SmallNumber {
|
||||
private var number: Int
|
||||
private(set) var projectedValue: Bool
|
||||
|
||||
var wrappedValue: Int {
|
||||
get { return number }
|
||||
set {
|
||||
if newValue > 12 {
|
||||
number = 12
|
||||
projectedValue = true
|
||||
} else {
|
||||
number = newValue
|
||||
projectedValue = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
self.number = 0
|
||||
self.projectedValue = false
|
||||
}
|
||||
}
|
||||
struct SomeStructure {
|
||||
@SmallNumber var someNumber: Int
|
||||
}
|
||||
var someStructure = SomeStructure()
|
||||
|
||||
someStructure.someNumber = 4
|
||||
print(someStructure.$someNumber)
|
||||
// 打印 "false"
|
||||
|
||||
someStructure.someNumber = 55
|
||||
print(someStructure.$someNumber)
|
||||
// 打印 "true"
|
||||
```
|
||||
|
||||
写下 `someStructure.$someNumber` 即可访问包装器的被呈现值。在存储一个比较小的数值时,如 4 ,`someStructure.$someNumber` 的值为 `false`。但是,在尝试存储一个较大的数值时,如 55 ,被呈现值变为 `true`。
|
||||
|
||||
属性包装器可以返回任何类型的值作为它的被呈现值。在这个例子里,属性包装器要暴露的信息是:那个数值是否被调整过,所以它暴露出布尔型值来作为它的被呈现值。需要暴露出更多信息的包装器可以返回其他数据类型的实例,或者可以返回自身来暴露出包装器的实例,并把其作为它的被呈现值。
|
||||
|
||||
当从类型的一部分代码中访问被呈现值,例如属性 getter 或实例方法,你可以在属性名称之前省略 `self.`,就像访问其他属性一样。以下示例中的代码用 `$height` 和 `$width` 引用包装器 `height` 和 `width` 的被呈现值:
|
||||
|
||||
```swift
|
||||
enum Size {
|
||||
case small, large
|
||||
}
|
||||
|
||||
struct SizedRectangle {
|
||||
@SmallNumber var height: Int
|
||||
@SmallNumber var width: Int
|
||||
|
||||
mutating func resize(to size: Size) -> Bool {
|
||||
switch size {
|
||||
case .small:
|
||||
height = 10
|
||||
width = 20
|
||||
case .large:
|
||||
height = 100
|
||||
width = 100
|
||||
}
|
||||
return $height || $width
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
因为属性包装器语法只是具有 getter 和 setter 的属性的语法糖,所以访问 `height` 和 `width` 的行为与访问任何其他属性的行为相同。举个例子,`resize(to:)` 中的代码使用它们的属性包装器来访问 `height` 和 `width`。如果调用 `resize(to: .large)`,`.large` 的 switch case 分支语句把矩形的高度和宽度设置为 100。属性包装器防止这些属性的值大于 12,且把被呈现值设置成为 `true` 来记下它调整过这些值的事实。在 `resize(to:)` 的最后,返回语句检查 `$height` 和 `$width` 来确认是否属性包装器调整过 `height` 或 `width`。
|
||||
|
||||
## 全局变量和局部变量 {#global-and-local-variables}
|
||||
|
||||
@ -268,10 +538,26 @@ stepCounter.totalSteps = 896
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 全局的常量或变量都是延迟计算的,跟[延时加载存储属性](#lazy_stored_properties)相似,不同的地方在于,全局的常量或变量不需要标记 `lazy` 修饰符。
|
||||
> 全局的常量或变量都是延迟计算的,跟 [延时加载存储属性](#lazy-stored-properties) 相似,不同的地方在于,全局的常量或变量不需要标记 `lazy` 修饰符。
|
||||
>
|
||||
> 局部范围的常量和变量从不延迟计算。
|
||||
|
||||
可以在局部存储型变量上使用属性包装器,但不能在全局变量或者计算型变量上使用。比如下面的代码,`myNumber` 使用 `SmallNumber` 作为属性包装器。
|
||||
|
||||
```swift
|
||||
func someFunction() {
|
||||
@SmallNumber var myNumber: Int = 0
|
||||
|
||||
myNumber = 10
|
||||
// 这时 myNumber 是 10
|
||||
|
||||
myNumber = 24
|
||||
// 这时 myNumber 是 12
|
||||
}
|
||||
```
|
||||
|
||||
就像将 `SmallNumber` 应用到属性上一样,将 `myNumber` 赋值为 10 是有效的。而因为这个属性包装器不允许值大于 12,将 `myNumber` 赋值为 24 时则会变成 12。
|
||||
|
||||
## 类型属性 {#type-properties}
|
||||
|
||||
实例属性属于一个特定类型的实例,每创建一个实例,实例都拥有属于自己的一套属性值,实例之间的属性相互独立。
|
||||
@ -342,7 +628,7 @@ print(SomeClass.computedTypeProperty)
|
||||
|
||||
下图展示了如何把两个声道结合来模拟立体声的音量。当声道的音量是 `0`,没有一个灯会亮;当声道的音量是 `10`,所有灯点亮。本图中,左声道的音量是 `9`,右声道的音量是 `7`:
|
||||
|
||||
<img src="https://docs.swift.org/swift-book/_images/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` 结构体的实例来表示:
|
||||
|
||||
@ -1,255 +1,255 @@
|
||||
# 方法
|
||||
|
||||
*方法*是与某些特定类型相关联的函数。类、结构体、枚举都可以定义实例方法;实例方法为给定类型的实例封装了具体的任务与功能。类、结构体、枚举也可以定义类型方法;类型方法与类型本身相关联。类型方法与 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` 结构体定义了一个可变方法 `moveBy(x:y :)` 来移动 `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` 来允许子类重写父类的方法实现。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 在 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”
|
||||
```
|
||||
# 方法
|
||||
|
||||
*方法*是与某些特定类型相关联的函数。类、结构体、枚举都可以定义实例方法;实例方法为给定类型的实例封装了具体的任务与功能。类、结构体、枚举也可以定义类型方法;类型方法与类型本身相关联。类型方法与 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` 结构体定义了一个可变方法 `moveBy(x:y:)` 来移动 `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` 来指定,从而允许子类重写父类该方法的实现。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 在 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` 属性,更多关于属性信息,请参考 [特性](../03_language_reference/07_Attributes.md) 章节。
|
||||
|
||||
下面,`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”
|
||||
```
|
||||
@ -2,11 +2,11 @@
|
||||
|
||||
*下标*可以定义在类、结构体和枚举中,是访问集合、列表或序列中元素的快捷方式。可以使用下标的索引,设置和获取值,而不需要再调用对应的存取方法。举例来说,用下标访问一个 `Array` 实例中的元素可以写作 `someArray[index]`,访问 `Dictionary` 实例中的元素可以写作 `someDictionary[key]`。
|
||||
|
||||
一个类型可以定义多个下标,通过不同索引类型进行重载。下标不限于一维,你可以定义具有多个入参的下标满足自定义类型的需求。
|
||||
一个类型可以定义多个下标,通过不同索引类型进行对应的重载。下标不限于一维,你可以定义具有多个入参的下标满足自定义类型的需求。
|
||||
|
||||
## 下标语法 {#subscript-syntax}
|
||||
|
||||
下标允许你通过在实例名称后面的方括号中传入一个或者多个索引值来对实例进行存取。语法类似于实例方法语法和计算型属性语法的混合。与定义实例方法类似,定义下标使用 `subscript` 关键字,指定一个或多个输入参数和返回类型;与实例方法不同的是,下标可以设定为读写或只读。这种行为由 getter 和 setter 实现,有点类似计算型属性:
|
||||
下标允许你通过在实例名称后面的方括号中传入一个或者多个索引值来对实例进行查询。它的语法类似于实例方法语法和计算型属性语法。定义下标使用 `subscript` 关键字,与定义实例方法类似,都是指定一个或多个输入参数和一个返回类型。与实例方法不同的是,下标可以设定为读写或只读。这种行为由 getter 和 setter 实现,类似计算型属性:
|
||||
|
||||
```swift
|
||||
subscript(index: Int) -> Int {
|
||||
@ -19,9 +19,9 @@ subscript(index: Int) -> Int {
|
||||
}
|
||||
```
|
||||
|
||||
`newValue` 的类型和下标的返回类型相同。如同计算型属性,可以不指定 setter 的参数(`newValue`)。如果不指定参数,setter 会提供一个名为 `newValue` 的默认参数。
|
||||
`newValue` 的类型和下标操作的返回类型相同。如同计算型属性,可以不指定 setter 的参数(`newValue`)。如果不指定参数,setter 会提供一个名为 `newValue` 的默认参数。
|
||||
|
||||
如同只读计算型属性,可以省略只读下标的 `get` 关键字:
|
||||
如同只读计算型属性,对于只读下标的声明,你可以通过省略 `get` 关键字和对应的大括号组来进行简写:
|
||||
|
||||
```swift
|
||||
subscript(index: Int) -> Int {
|
||||
@ -29,7 +29,7 @@ subscript(index: Int) -> Int {
|
||||
}
|
||||
```
|
||||
|
||||
下面代码演示了只读下标的实现,这里定义了一个 `TimesTable` 结构体,用来表示传入整数的乘法表:
|
||||
下面代码演示了只读下标的实现,这里定义了一个 `TimesTable` 结构体,用来表示对应整数的乘法表:
|
||||
|
||||
```swift
|
||||
struct TimesTable {
|
||||
@ -45,7 +45,7 @@ print("six times three is \(threeTimesTable[6])")
|
||||
|
||||
在上例中,创建了一个 `TimesTable` 实例,用来表示整数 `3` 的乘法表。数值 `3` 被传递给结构体的构造函数,作为实例成员 `multiplier` 的值。
|
||||
|
||||
你可以通过下标访问 `threeTimesTable` 实例,例如上面演示的 `threeTimesTable[6]`。这条语句查询了 `3` 的乘法表中的第六个元素,返回 `3` 的 `6` 倍即 `18`。
|
||||
你可以通过下标访问 `threeTimesTable` 实例,例如上面演示的 `threeTimesTable[6]`。这条语句查询了乘法表中 `3` 的第六个元素,返回 `3` 的 `6` 倍即 `18`。
|
||||
|
||||
> 注意
|
||||
>
|
||||
@ -53,9 +53,9 @@ print("six times three is \(threeTimesTable[6])")
|
||||
|
||||
## 下标用法 {#subscript-usage}
|
||||
|
||||
下标的确切含义取决于使用场景。下标通常作为访问集合,列表或序列中元素的快捷方式。你可以针对自己特定的类或结构体的功能来自由地以最恰当的方式实现下标。
|
||||
“下标”的确切含义取决于使用场景。下标通常作为访问集合,列表或序列中元素的快捷方式。你可以针对自己特定的类或结构体功能来以最恰当的方式实现下标。
|
||||
|
||||
例如,Swift 的 `Dictionary` 类型实现下标用于对其实例中储存的值进行存取操作。为字典设值时,在下标中使用和字典的键类型相同的键,并把一个和字典的值类型相同的值赋给这个下标:
|
||||
例如,Swift 的 `Dictionary` 类型实现下标用于对实例中储存的值进行存取操作。为字典设值时,在下标中使用和字典的键类型相同的键,并把一个和字典的值类型相同的值赋给这个下标:
|
||||
|
||||
```swift
|
||||
var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
|
||||
@ -64,17 +64,19 @@ numberOfLegs["bird"] = 2
|
||||
|
||||
上例定义一个名为 `numberOfLegs` 的变量,并用一个包含三对键值的字典字面量初始化它。`numberOfLegs` 字典的类型被推断为 `[String: Int]`。字典创建完成后,该例子通过下标将 `String` 类型的键 `bird` 和 `Int` 类型的值 `2` 添加到字典中。
|
||||
|
||||
更多关于 `Dictionary` 下标的信息请参考 [读取和修改字典](./04_Collection_Types.md#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` 即可。
|
||||
|
||||
## 下标选项 {#subscript-options}
|
||||
|
||||
下标可以接受任意数量的入参,并且这些入参可以是任意类型。下标的返回值也可以是任意类型。下标可以使用变量参数和可变参数,但不能使用输入输出参数,也不能给参数设置默认值。
|
||||
下标可以接受任意数量的入参,并且这些入参可以是任何类型。下标的返回值也可以是任意类型。
|
||||
|
||||
一个类或结构体可以根据自身需要提供多个下标实现,使用下标时将通过入参的数量和类型进行区分,自动匹配合适的下标,这就是*下标的重载*。
|
||||
与函数一样,下标可以接受不同数量的参数,并且为这些参数提供默认值,如在[可变参数](./06_Functions.md#variadic-parameters) 和 [默认参数值](./06_Functions.md#default-parameter-values) 中所述。但是,与函数不同的是,下标不能使用 in-out 参数。
|
||||
|
||||
一个类或结构体可以根据自身需要提供多个下标实现,使用下标时将通过入参的数量和类型进行区分,自动匹配合适的下标。它通常被称为*下标的重载*。
|
||||
|
||||
虽然接受单一入参的下标是最常见的,但也可以根据情况定义接受多个入参的下标。例如下例定义了一个 `Matrix` 结构体,用于表示一个 `Double` 类型的二维矩阵。`Matrix` 结构体的下标接受两个整型参数:
|
||||
|
||||
@ -103,17 +105,17 @@ struct Matrix {
|
||||
}
|
||||
```
|
||||
|
||||
`Matrix` 提供了一个接受两个入参的构造方法,入参分别是 `rows` 和 `columns`,创建了一个足够容纳 `rows * columns` 个 `Double` 类型的值的数组。通过传入数组长度和初始值 `0.0` 到数组的构造器,将矩阵中每个位置的值初始化为 `0.0`。关于数组的这种构造方法请参考 [创建一个带有默认值的数组](./04_Collection_Types.md#creating_an_array_with_a_default_value)。
|
||||
`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` 数组按照从左上到右下的阅读顺序将矩阵扁平化存储:
|
||||
|
||||

|
||||

|
||||
|
||||
将 `row` 和 `column` 的值传入下标来为矩阵设值,下标的入参使用逗号分隔:
|
||||
|
||||
@ -124,7 +126,7 @@ matrix[1, 0] = 3.2
|
||||
|
||||
上面两条语句分别调用下标的 setter 将矩阵右上角位置(即 `row` 为 `0`、`column` 为 `1` 的位置)的值设置为 `1.5`,将矩阵左下角位置(即 `row` 为 `1`、`column` 为 `0` 的位置)的值设置为 `3.2`:
|
||||
|
||||

|
||||

|
||||
|
||||
`Matrix` 下标的 getter 和 setter 中都含有断言,用来检查下标入参 `row` 和 `column` 的值是否有效。为了方便进行断言,`Matrix` 包含了一个名为 `indexIsValid(row:column:)` 的便利方法,用来检查入参 `row` 和 `column` 的值是否在矩阵范围内:
|
||||
|
||||
@ -140,3 +142,17 @@ func indexIsValid(row: Int, column: Int) -> Bool {
|
||||
let someValue = matrix[2, 2]
|
||||
// 断言将会触发,因为 [2, 2] 已经超过了 matrix 的范围
|
||||
```
|
||||
|
||||
## 类型下标 {#type-subscripts}
|
||||
正如上节所述,实例下标是在特定类型的一个实例上调用的下标。你也可以定义一种在这个类型自身上调用的下标。这种下标被称作_类型下标_。你可以通过在 `subscript` 关键字之前写下 `static` 关键字的方式来表示一个类型下标。类型可以使用 `class` 关键字来代替 `static`,它允许子类重写父类中对那个下标的实现。下面的例子展示了如何定义和调用一个类型下标:
|
||||
```
|
||||
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)
|
||||
```
|
||||
|
||||
@ -1,221 +1,221 @@
|
||||
# 继承
|
||||
|
||||
一个类可以*继承*另一个类的方法,属性和其它特性。当一个类继承其它类时,继承类叫*子类*,被继承类叫*超类(或父类)*。在 Swift 中,继承是区分「类」与其它类型的一个基本特征。
|
||||
|
||||
在 Swift 中,类可以调用和访问超类的方法、属性和下标,并且可以重写这些方法,属性和下标来优化或修改它们的行为。Swift 会检查你的重写定义在超类中是否有匹配的定义,以此确保你的重写行为是正确的。
|
||||
|
||||
可以为类中继承来的属性添加属性观察器,这样一来,当属性值改变时,类就会被通知到。可以为任何属性添加属性观察器,无论它原本被定义为存储型属性还是计算型属性。
|
||||
|
||||
## 定义一个基类 {#defining-a-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` 类定义了一个具有通用特性的车辆类,但实际上对于它本身来说没什么用处。为了让它变得更加有用,还需要进一步完善它,从而能够描述一个具体类型的车辆。
|
||||
|
||||
## 子类生成 {#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 。这样的类是不可被继承的,试图继承这样的类会导致编译报错。
|
||||
# 继承
|
||||
|
||||
一个类可以*继承*另一个类的方法,属性和其它特性。当一个类继承其它类时,继承类叫*子类*,被继承类叫*超类(或父类)*。在 Swift 中,继承是区分「类」与其它类型的一个基本特征。
|
||||
|
||||
在 Swift 中,类可以调用和访问超类的方法、属性和下标,并且可以重写这些方法,属性和下标来优化或修改它们的行为。Swift 会检查你的重写定义在超类中是否有匹配的定义,以此确保你的重写行为是正确的。
|
||||
|
||||
可以为类中继承来的属性添加属性观察器,这样一来,当属性值改变时,类就会被通知到。可以为任何属性添加属性观察器,无论它原本被定义为存储型属性还是计算型属性。
|
||||
|
||||
## 定义一个基类 {#defining-a-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` 类定义了一个具有通用特性的车辆类,但实际上对于它本身来说没什么用处。为了让它变得更加有用,还需要进一步完善它,从而能够描述一个具体类型的车辆。
|
||||
|
||||
## 子类生成 {#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` 属性为 `true`:
|
||||
|
||||
```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}
|
||||
|
||||
你可以通过重写属性为一个继承来的属性添加属性观察器。这样一来,无论被继承属性原本是如何实现的,当其属性值发生改变时,你就会被通知到。关于属性观察器的更多内容,请看 [属性观察器](../02_language_guide/10_Properties.md#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 。这样的类是不可被继承的,试图继承这样的类会导致编译报错。
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
你要通过定义*构造器*来实现构造过程,它就像用来创建特定类型新实例的特殊方法。与 Objective-C 中的构造器不同,Swift 的构造器没有返回值。它们的主要任务是保证某种类型的新实例在第一次使用前完成正确的初始化。
|
||||
|
||||
类的实例也可以通过实现*析构器*来执行它释放之前自定义的清理工作。想了解更多关于析构器的内容,请参考[析构过程](./15_Deinitialization.md)。
|
||||
类的实例也可以通过实现*析构器*来执行它释放之前自定义的清理工作。想了解更多关于析构器的内容,请参考 [析构过程](./15_Deinitialization.md)。
|
||||
|
||||
## 存储属性的初始赋值 {#setting-initial-values-for-stored-properties}
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 当你为存储型属性分配默认值或者在构造器中为设置初始值时,它们的值是被直接设置的,不会触发任何属性观察者。
|
||||
> 当你为存储型属性分配默认值或者在构造器中设置初始值时,它们的值是被直接设置的,不会触发任何属性观察者。
|
||||
|
||||
### 构造器 {#initializers}
|
||||
|
||||
@ -131,7 +131,7 @@ let veryGreen = Color(0.0, 1.0, 0.0)
|
||||
|
||||
如果你不希望构造器的某个形参使用实参标签,可以使用下划线(`_`)来代替显式的实参标签来重写默认行为。
|
||||
|
||||
下面是之前[形参的构造过程](#initialization_parameters)中 `Celsius` 例子的扩展,多了一个用已经的摄氏表示的 `Double` 类型值来创建新的 `Celsius` 实例的额外构造器:
|
||||
下面是之前 [形参的构造过程](#initialization-parameters) 中 `Celsius` 例子的扩展,多了一个用已经的摄氏表示的 `Double` 类型值来创建新的 `Celsius` 实例的额外构造器:
|
||||
|
||||
```swift
|
||||
struct Celsius {
|
||||
@ -225,7 +225,7 @@ var item = ShoppingListItem()
|
||||
|
||||
### 结构体的逐一成员构造器 {#memberwise-initializers-for-structure-types}
|
||||
|
||||
结构体如果没有定义任何自定义构造器,它们将自动获得一个*逐一成员构造器(memberwise initializer)*。不像默认构造器,即使存储型属性没有默认值,结构体也能会获得逐一成员构造器。
|
||||
结构体如果没有定义任何自定义构造器,它们将自动获得一个*逐一成员构造器(memberwise initializer)*。不像默认构造器,即使存储型属性没有默认值,结构体也会获得逐一成员构造器。
|
||||
|
||||
逐一成员构造器是用来初始化结构体新实例里成员属性的快捷方法。新实例的属性初始值可以通过名字传入逐一成员构造器中。
|
||||
|
||||
@ -240,11 +240,25 @@ struct Size {
|
||||
let twoByTwo = Size(width: 2.0, height: 2.0)
|
||||
```
|
||||
|
||||
|
||||
当你调用一个逐一成员构造器(memberwise initializer)时,可以省略任何一个有默认值的属性。在上面这个例子中,`Size` 结构体的 `height` 和 `width` 属性各有一个默认值。你可以省略两者或两者之一,对于被省略的属性,构造器会使用默认值。举个例子:
|
||||
|
||||
```
|
||||
let zeroByTwo = Size(height: 2.0)
|
||||
print(zeroByTwo.width, zeroByTwo.height)
|
||||
// 打印 "0.0 2.0"
|
||||
|
||||
let zeroByZero = Size()
|
||||
print(zeroByZero.width, zeroByZero.height)
|
||||
// 打印 "0.0 0.0"
|
||||
```
|
||||
|
||||
|
||||
## 值类型的构造器代理 {#initializer-delegation-for-value-types}
|
||||
|
||||
构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为*构造器代理*,它能避免多个构造器间的代码重复。
|
||||
|
||||
构造器代理的实现规则和形式在值类型和类类型中有所不同。值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给自己的其它构造器。类则不同,它可以继承自其它类(请参考[继承](./13_Inheritance.md))。这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。这些责任将在后续章节[类的继承和构造过程](#class_inheritance_and_initialization)中介绍。
|
||||
构造器代理的实现规则和形式在值类型和类类型中有所不同。值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给自己的其它构造器。类则不同,它可以继承自其它类(请参考 [继承](./13_Inheritance.md))。这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。这些责任将在后续章节 [类的继承和构造过程](#class-inheritance-and-initialization) 中介绍。
|
||||
|
||||
对于值类型,你可以使用 `self.init` 在自定义的构造器中引用相同类型中的其它构造器。并且你只能在构造器内部调用 `self.init`。
|
||||
|
||||
@ -252,7 +266,7 @@ let twoByTwo = Size(width: 2.0, height: 2.0)
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 假如你希望默认构造器、逐一成员构造器以及你自己的自定义构造器都能用来创建实例,可以将自定义的构造器写到扩展(`extension`)中,而不是写在值类型的原始定义中。想查看更多内容,请查看[扩展](./20_Extensions.md)章节。
|
||||
> 假如你希望默认构造器、逐一成员构造器以及你自己的自定义构造器都能用来创建实例,可以将自定义的构造器写到扩展(`extension`)中,而不是写在值类型的原始定义中。想查看更多内容,请查看 [扩展](./20_Extensions.md) 章节。
|
||||
|
||||
下面例子定义一个自定义结构体 `Rect`,用来代表几何矩形。这个例子需要两个辅助的结构体 `Size` 和 `Point`,它们各自为其所有的属性提供了默认初始值 `0.0`。
|
||||
|
||||
@ -287,7 +301,7 @@ struct Rect {
|
||||
}
|
||||
```
|
||||
|
||||
第一个 `Rect` 构造器 `init()`,在功能上跟没有自定义构造器时自动获得的默认构造器是一样的。这个构造器是函数体是空的,使用一对大括号 `{}` 来表示。调用这个构造器将返回一个 `Rect` 实例,它的 `origin` 和 `size` 属性都使用定义时的默认值 `Point(x: 0.0, y: 0.0)` 和 `Size(width: 0.0, height: 0.0)`:
|
||||
第一个 `Rect` 构造器 `init()`,在功能上跟没有自定义构造器时自动获得的默认构造器是一样的。这个构造器的函数体是空的,使用一对大括号 `{}` 来表示。调用这个构造器将返回一个 `Rect` 实例,它的 `origin` 和 `size` 属性都使用定义时的默认值 `Point(x: 0.0, y: 0.0)` 和 `Size(width: 0.0, height: 0.0)`:
|
||||
|
||||
```swift
|
||||
let basicRect = Rect()
|
||||
@ -314,7 +328,7 @@ let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果你想用另外一种不需要自己定义 `init()` 和 `init(origin:size:)` 的方式来实现这个例子,请参考[扩展](./21_Extensions.md)。
|
||||
> 如果你想用另外一种不需要自己定义 `init()` 和 `init(origin:size:)` 的方式来实现这个例子,请参考 [扩展](./20_Extensions.md)。
|
||||
|
||||
## 类的继承和构造过程 {#class-inheritance-and-initialization}
|
||||
|
||||
@ -328,7 +342,7 @@ Swift 为类类型提供了两种构造器来确保实例中所有存储型属
|
||||
|
||||
类倾向于拥有极少的指定构造器,普遍的是一个类只拥有一个指定构造器。指定构造器像一个个“漏斗”放在构造过程发生的地方,让构造过程沿着父类链继续往上进行。
|
||||
|
||||
每一个类都必须至少拥有一个指定构造器。在某些情况下,许多类通过继承了父类中的指定构造器而满足了这个条件。具体内容请参考后续章节[构造器的自动继承](#automatic_initializer_inheritance)。
|
||||
每一个类都必须至少拥有一个指定构造器。在某些情况下,许多类通过继承了父类中的指定构造器而满足了这个条件。具体内容请参考后续章节 [构造器的自动继承](#automatic-initializer-inheritance)。
|
||||
|
||||
*便利构造器*是类中比较次要的、辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为部分形参提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入值的实例。
|
||||
|
||||
@ -375,7 +389,7 @@ convenience init(parameters) {
|
||||
|
||||
这些规则可以通过下面图例来说明:
|
||||
|
||||

|
||||

|
||||
|
||||
如图所示,父类中包含一个指定构造器和两个便利构造器。其中一个便利构造器调用了另外一个便利构造器,而后者又调用了唯一的指定构造器。这满足了上面提到的规则 2 和 3。这个父类没有自己的父类,所以规则 1 没有用到。
|
||||
|
||||
@ -387,7 +401,7 @@ convenience init(parameters) {
|
||||
|
||||
下面图例中展示了一种涉及四个类的更复杂的类层级结构。它演示了指定构造器是如何在类层级中充当“漏斗”的作用,在类的构造器链上简化了类之间的相互关系。
|
||||
|
||||

|
||||

|
||||
|
||||
### 两段式构造过程 {#two-phase-initialization}
|
||||
|
||||
@ -403,7 +417,7 @@ Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造
|
||||
|
||||
##### 安全检查 1
|
||||
|
||||
指定构造器必须保证它所在类的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给父类中的构造器。
|
||||
指定构造器必须保证它所在类的所有属性都初始化完成,之后才能将其它构造任务向上代理给父类中的构造器。
|
||||
|
||||
如上所述,一个对象的内存只有在其所有存储型属性确定之后才能完全初始化。为了满足这一规则,指定构造器必须保证它所在类的属性在它往上代理之前先完成初始化。
|
||||
|
||||
@ -439,7 +453,7 @@ Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造
|
||||
|
||||
下图展示了在假定的子类和父类之间的构造阶段 1:
|
||||
|
||||

|
||||

|
||||
|
||||
在这个例子中,构造过程从对子类中一个便利构造器的调用开始。这个便利构造器此时还不能修改任何属性,它会代理到该类中的指定构造器。
|
||||
|
||||
@ -451,7 +465,7 @@ Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造
|
||||
|
||||
以下展示了相同构造过程的阶段 2:
|
||||
|
||||

|
||||

|
||||
|
||||
父类中的指定构造器现在有机会进一步自定义实例(尽管这不是必须的)。
|
||||
|
||||
@ -461,23 +475,23 @@ Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造
|
||||
|
||||
### 构造器的继承和重写 {#initializer-inheritance-and-overriding}
|
||||
|
||||
跟 Objective-C 中的子类不同,Swift 中的子类默认情况下不会继承父类的构造器。Swift 的这种机制可以防止一个父类的简单构造器被一个更精细的子类继承,而在用来创建子类时的新实例时没有完全或错误被初始化。
|
||||
跟 Objective-C 中的子类不同,Swift 中的子类默认情况下不会继承父类的构造器。Swift 的这种机制可以防止一个父类的简单构造器被一个更精细的子类继承,而在用来创建子类的新实例时没有完全或错误被初始化。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 父类的构造器仅会在安全和适当的某些情况下被继承。具体内容请参考后续章节[构造器的自动继承](#automatic_initializer_inheritance)。
|
||||
> 父类的构造器仅会在安全和适当的某些情况下被继承。具体内容请参考后续章节 [构造器的自动继承](#automatic-initializer-inheritance)。
|
||||
|
||||
假如你希望自定义的子类中能提供一个或多个跟父类相同的构造器,你可以在子类中提供这些构造器的自定义实现。
|
||||
|
||||
当你在编写一个和父类中指定构造器相匹配的子类构造器时,你实际上是在重写父类的这个指定构造器。因此,你必须在定义子类构造器时带上 `override` 修饰符。即使你重写的是系统自动提供的默认构造器,也需要带上 `override` 修饰符,具体内容请参考[默认构造器](#default_initializers)。
|
||||
当你在编写一个和父类中指定构造器相匹配的子类构造器时,你实际上是在重写父类的这个指定构造器。因此,你必须在定义子类构造器时带上 `override` 修饰符。即使你重写的是系统自动提供的默认构造器,也需要带上 `override` 修饰符,具体内容请参考 [默认构造器](#default-initializers)。
|
||||
|
||||
正如重写属性,方法或者是下标,`override` 修饰符会让编译器去检查父类中是否有相匹配的指定构造器,并验证构造器参数是否被按预想中被指定。
|
||||
正如重写属性,方法或者是下标,`override` 修饰符会让编译器去检查父类中是否有相匹配的指定构造器,并验证构造器参数是否按预想中被指定。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 当你重写一个父类的指定构造器时,你总是需要写 `override` 修饰符,即使是为了实现子类的便利构造器。
|
||||
|
||||
相反,如果你编写了一个和父类便利构造器相匹配的子类构造器,由于子类不能直接调用父类的便利构造器(每个规则都在上文[类的构造器代理规则](#initializer_delegation_for_class_types)有所描述),因此,严格意义上来讲,你的子类并未对一个父类构造器提供重写。最后的结果就是,你在子类中“重写”一个父类便利构造器时,不需要加 `override` 修饰符。
|
||||
相反,如果你编写了一个和父类便利构造器相匹配的子类构造器,由于子类不能直接调用父类的便利构造器(每个规则都在上文 [类的构造器代理规则](#initializer-delegation-for-class-types) 有所描述),因此,严格意义上来讲,你的子类并未对一个父类构造器提供重写。最后的结果就是,你在子类中“重写”一个父类便利构造器时,不需要加 `override` 修饰符。
|
||||
|
||||
在下面的例子中定义了一个叫 `Vehicle` 的基类。基类中声明了一个存储型属性 `numberOfWheels`,它是默认值为 `Int` 类型的 `0`。`numberOfWheels` 属性用在一个描述车辆特征 `String` 类型为 `descrpiption` 的计算型属性中:
|
||||
|
||||
@ -490,7 +504,7 @@ class Vehicle {
|
||||
}
|
||||
```
|
||||
|
||||
`Vehicle` 类只为存储型属性提供默认值,也没有提供自定义构造器。因此,它会自动获得一个默认构造器,具体内容请参考[默认构造器](#default_initializers)。默认构造器(如果有的话)总是类中的指定构造器,可以用于创建 `numberOfWheels` 为 `0` 的 `Vehicle` 实例:
|
||||
`Vehicle` 类只为存储型属性提供默认值,也没有提供自定义构造器。因此,它会自动获得一个默认构造器,具体内容请参考 [默认构造器](#default-initializers)。默认构造器(如果有的话)总是类中的指定构造器,可以用于创建 `numberOfWheels` 为 `0` 的 `Vehicle` 实例:
|
||||
|
||||
```swift
|
||||
let vehicle = Vehicle()
|
||||
@ -521,9 +535,9 @@ print("Bicycle: \(bicycle.description)")
|
||||
// 打印“Bicycle: 2 wheel(s)”
|
||||
```
|
||||
|
||||
如果父类的构造器没有在阶段 2 过程中做自定义操作,并且父类有一个无参数的自定义构造器。你可以在所有父类的存储属性赋值之后省略 `super.init()` 的调用。
|
||||
如果子类的构造器没有在阶段 2 过程中做自定义操作,并且父类有一个同步、无参数的指定构造器,你可以在所有子类的存储属性赋值之后省略 `super.init()` 的调用。若父类有一个异步的构造器,你就需要明确地写入 `await super.init()`。
|
||||
|
||||
这个例子定义了另一个 `Vehicle` 的子类 `Hoverboard` ,只设置它的 `color` 属性。这个构造器依赖隐式调用父类的构造器来完成,而不是显示调用 `super.init()`。
|
||||
这个例子定义了另一个 `Vehicle` 的子类 `Hoverboard` ,只设置它的 `color` 属性。这个构造器依赖隐式调用父类的构造器来完成,而不是显式调用 `super.init()`。
|
||||
|
||||
```swift
|
||||
class Hoverboard: Vehicle {
|
||||
@ -591,7 +605,7 @@ class Food {
|
||||
|
||||
下图中展示了 `Food` 的构造器链:
|
||||
|
||||

|
||||

|
||||
|
||||
类类型没有默认的逐一成员构造器,所以 `Food` 类提供了一个接受单一参数 `name` 的指定构造器。这个构造器可以使用一个特定的名字来创建新的 `Food` 实例:
|
||||
|
||||
@ -626,13 +640,13 @@ class RecipeIngredient: Food {
|
||||
|
||||
下图中展示了 `RecipeIngredient` 类的构造器链:
|
||||
|
||||

|
||||

|
||||
|
||||
`RecipeIngredient` 类拥有一个指定构造器 `init(name: String, quantity: Int)`,它可以用来填充 `RecipeIngredient` 实例的所有属性值。这个构造器一开始先将传入的 `quantity` 实参赋值给 `quantity` 属性,这个属性也是唯一在 `RecipeIngredient` 中新引入的属性。随后,构造器向上代理到父类 `Food` 的 `init(name: String)`。这个过程满足[两段式构造过程](#two_phase_initialization)中的安全检查 1。
|
||||
`RecipeIngredient` 类拥有一个指定构造器 `init(name: String, quantity: Int)`,它可以用来填充 `RecipeIngredient` 实例的所有属性值。这个构造器一开始先将传入的 `quantity` 实参赋值给 `quantity` 属性,这个属性也是唯一在 `RecipeIngredient` 中新引入的属性。随后,构造器向上代理到父类 `Food` 的 `init(name: String)`。这个过程满足 [两段式构造过程](#two-phase-initialization) 中的安全检查 1。
|
||||
|
||||
`RecipeIngredient` 也定义了一个便利构造器 `init(name: String)`,它只通过 `name` 来创建 `RecipeIngredient` 的实例。这个便利构造器假设任意 `RecipeIngredient` 实例的 `quantity` 为 `1`,所以不需要显式的质量即可创建出实例。这个便利构造器的定义可以更加方便和快捷地创建实例,并且避免了创建多个 `quantity` 为 `1` 的 `RecipeIngredient` 实例时的代码重复。这个便利构造器只是简单地横向代理到类中的指定构造器,并为 `quantity` 参数传递 `1`。
|
||||
|
||||
`RecipeIngredient` 的便利构造器 `init(name: String)` 使用了跟 `Food` 中指定构造器 `init(name: String)` 相同的形参。由于这个便利构造器重写了父类的指定构造器 `init(name: String)`,因此必须在前面使用 `override` 修饰符(参见[构造器的继承和重写](#initializer_inheritance_and_overriding))。
|
||||
`RecipeIngredient` 的便利构造器 `init(name: String)` 使用了跟 `Food` 中指定构造器 `init(name: String)` 相同的形参。由于这个便利构造器重写了父类的指定构造器 `init(name: String)`,因此必须在前面使用 `override` 修饰符(参见 [构造器的继承和重写](#initializer-inheritance-and-overriding))。
|
||||
|
||||
尽管 `RecipeIngredient` 将父类的指定构造器重写为了便利构造器,但是它依然提供了父类的所有指定构造器的实现。因此,`RecipeIngredient` 会自动继承父类的所有便利构造器。
|
||||
|
||||
@ -669,7 +683,7 @@ class ShoppingListItem: RecipeIngredient {
|
||||
|
||||
下图展示了这三个类的构造器链:
|
||||
|
||||

|
||||

|
||||
|
||||
你可以使用三个继承来的构造器来创建 `ShoppingListItem` 的新实例:
|
||||
|
||||
@ -1013,7 +1027,7 @@ class SomeClass {
|
||||
|
||||
下面例子中定义了一个结构体 `Chessboard`,它构建了西洋跳棋游戏的棋盘,西洋跳棋游戏在一副黑白格交替的 8 x 8 的棋盘中进行的:
|
||||
|
||||

|
||||

|
||||
|
||||
为了呈现这副游戏棋盘,`Chessboard` 结构体定义了一个属性 `boardColors`,它是一个包含 `64` 个 `Bool` 值的数组。在数组中,值为 `true` 的元素表示一个黑格,值为 `false` 的元素表示一个白格。数组中第一个元素代表棋盘上左上角的格子,最后一个元素代表棋盘上右下角的格子。
|
||||
|
||||
@ -1022,7 +1036,7 @@ class SomeClass {
|
||||
```swift
|
||||
struct Chessboard {
|
||||
let boardColors: [Bool] = {
|
||||
var temporaryBoard = [Bool]()
|
||||
var temporaryBoard: [Bool] = []
|
||||
var isBlack = false
|
||||
for i in 1...8 {
|
||||
for j in 1...8 {
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
## 析构过程原理 {#how-deinitialization-works}
|
||||
|
||||
Swift 会自动释放不再需要的实例以释放资源。如[自动引用计数](./23_Automatic_Reference_Counting.md)章节中所讲述,Swift 通过*自动引用计数(ARC)* 处理实例的内存管理。通常当你的实例被释放时不需要手动地去清理。但是,当使用自己的资源时,你可能需要进行一些额外的清理。例如,如果创建了一个自定义的类来打开一个文件,并写入一些数据,你可能需要在类实例被释放之前手动去关闭该文件。
|
||||
Swift 会自动释放不再需要的实例以释放资源。如 [自动引用计数](./24_Automatic_Reference_Counting.md) 章节中所讲述,Swift 通过*自动引用计数(ARC)* 处理实例的内存管理。通常当你的实例被释放时不需要手动地去清理。但是,当使用自己的资源时,你可能需要进行一些额外的清理。例如,如果创建了一个自定义的类来打开一个文件,并写入一些数据,你可能需要在类实例被释放之前手动去关闭该文件。
|
||||
|
||||
在类的定义中,每个类最多只能有一个析构器,而且析构器不带任何参数和圆括号,如下所示:
|
||||
|
||||
@ -16,7 +16,7 @@ deinit {
|
||||
|
||||
析构器是在实例释放发生前被自动调用的。你不能主动调用析构器。子类继承了父类的析构器,并且在子类析构器实现的最后,父类的析构器会被自动调用。即使子类没有提供自己的析构器,父类的析构器也同样会被调用。
|
||||
|
||||
因为直到实例的析构器被调用后,实例才会被释放,所以析构器可以访问实例的所有属性,并且可以根据那些属性可以修改它的行为(比如查找一个需要被关闭的文件)。
|
||||
因为直到实例的析构器被调用后,实例才会被释放,所以析构器可以访问实例的所有属性,并且可以根据那些属性修改它的行为(比如查找一个需要被关闭的文件)。
|
||||
|
||||
## 析构器实践 {#deinitializers-in-action}
|
||||
|
||||
@ -6,15 +6,15 @@
|
||||
>
|
||||
> Swift 的可选链式调用和 Objective-C 中向 `nil` 发送消息有些相像,但是 Swift 的可选链式调用可以应用于任意类型,并且能检查调用是否成功。
|
||||
|
||||
## 使用可选链式调用代替强制展开 {#optional-chaining-as-an-alternative-to-forced-unwrapping}
|
||||
## 使用可选链式调用代替强制解包 {#optional-chaining-as-an-alternative-to-forced-unwrapping}
|
||||
|
||||
通过在想调用的属性、方法,或下标的可选值后面放一个问号(`?`),可以定义一个可选链。这一点很像在可选值后面放一个叹号(`!`)来强制展开它的值。它们的主要区别在于当可选值为空时可选链式调用只会调用失败,然而强制展开将会触发运行时错误。
|
||||
通过在想调用的属性、方法,或下标的可选值后面放一个问号(`?`),可以定义一个可选链。这一点很像在可选值后面放一个叹号(`!`)来强制解包它的值。它们的主要区别在于当可选值为空时可选链式调用只会调用失败,然而强制解包将会触发运行时错误。
|
||||
|
||||
为了反映可选链式调用可以在空值(`nil`)上调用的事实,不论这个调用的属性、方法及下标返回的值是不是可选值,它的返回结果都是一个可选值。你可以利用这个返回值来判断你的可选链式调用是否调用成功,如果调用有返回值则说明调用成功,返回 `nil` 则说明调用失败。
|
||||
|
||||
这里需要特别指出,可选链式调用的返回结果与原本的返回结果具有相同的类型,但是被包装成了一个可选值。例如,使用可选链式调用访问属性,当可选链式调用成功时,如果属性原本的返回结果是 `Int` 类型,则会变为 `Int?` 类型。
|
||||
|
||||
下面几段代码将解释可选链式调用和强制展开的不同。
|
||||
下面几段代码将解释可选链式调用和强制解包的不同。
|
||||
|
||||
首先定义两个类 `Person` 和 `Residence`:
|
||||
|
||||
@ -36,7 +36,7 @@ class Residence {
|
||||
let john = Person()
|
||||
```
|
||||
|
||||
如果使用叹号(`!`)强制展开获得这个 `john` 的 `residence` 属性中的 `numberOfRooms` 值,会触发运行时错误,因为这时 `residence` 没有可以展开的值:
|
||||
如果使用叹号(`!`)强制解包获得这个 `john` 的 `residence` 属性中的 `numberOfRooms` 值,会触发运行时错误,因为这时 `residence` 没有可以解包的值:
|
||||
|
||||
```swift
|
||||
let roomCount = john.residence!.numberOfRooms
|
||||
@ -58,7 +58,7 @@ if let roomCount = john.residence?.numberOfRooms {
|
||||
|
||||
在 `residence` 后面添加问号之后,Swift 就会在 `residence` 不为 `nil` 的情况下访问 `numberOfRooms`。
|
||||
|
||||
因为访问 `numberOfRooms` 有可能失败,可选链式调用会返回 `Int?` 类型,或称为“可选的 `Int`”。如上例所示,当 `residence` 为 `nil` 的时候,可选的 `Int` 将会为 `nil`,表明无法访问 `numberOfRooms`。访问成功时,可选的 `Int` 值会通过可选绑定展开,并赋值给非可选类型的 `roomCount` 常量。
|
||||
因为访问 `numberOfRooms` 有可能失败,可选链式调用会返回 `Int?` 类型,或称为“可选的 `Int`”。如上例所示,当 `residence` 为 `nil` 的时候,可选的 `Int` 将会为 `nil`,表明无法访问 `numberOfRooms`。访问成功时,可选的 `Int` 值会通过可选绑定解包,并赋值给非可选类型的 `roomCount` 常量。
|
||||
|
||||
要注意的是,即使 `numberOfRooms` 是非可选的 `Int` 时,这一点也成立。只要使用可选链式调用就意味着 `numberOfRooms` 会返回一个 `Int?` 而不是 `Int`。
|
||||
|
||||
@ -97,7 +97,7 @@ class Person {
|
||||
|
||||
```swift
|
||||
class Residence {
|
||||
var rooms = [Room]()
|
||||
var rooms: [Room] = []
|
||||
var numberOfRooms: Int {
|
||||
return rooms.count
|
||||
}
|
||||
@ -156,7 +156,7 @@ class Address {
|
||||
|
||||
## 通过可选链式调用访问属性 {#accessing-properties-through-optional-chaining}
|
||||
|
||||
正如[使用可选链式调用代替强制展开](#optional_chaining_as_an_alternative_to_forced_unwrapping)中所述,可以通过可选链式调用在一个可选值上访问它的属性,并判断访问是否成功。
|
||||
正如 [使用可选链式调用代替强制解包](#optional-chaining-as-an-alternative-to-forced-unwrapping) 中所述,可以通过可选链式调用在一个可选值上访问它的属性,并判断访问是否成功。
|
||||
|
||||
使用前面定义过的类,创建一个 `Person` 实例,然后像之前一样,尝试访问 `numberOfRooms` 属性:
|
||||
|
||||
@ -212,7 +212,7 @@ func printNumberOfRooms() {
|
||||
}
|
||||
```
|
||||
|
||||
这个方法没有返回值。然而,没有返回值的方法具有隐式的返回类型 `Void`,如[无返回值函数](./06_Functions.md#functions_without_return_values)中所述。这意味着没有返回值的方法也会返回 `()`,或者说空的元组。
|
||||
这个方法没有返回值。然而,没有返回值的方法具有隐式的返回类型 `Void`,如 [无返回值函数](./06_Functions.md#functions-without-return-values) 中所述。这意味着没有返回值的方法也会返回 `()`,或者说空的元组。
|
||||
|
||||
如果在可选值上通过可选链式调用来调用这个方法,该方法的返回类型会是 `Void?`,而不是 `Void`,因为通过可选链式调用得到的返回值都是可选的。这样我们就可以使用 `if` 语句来判断能否成功调用 `printNumberOfRooms()` 方法,即使方法本身没有定义返回值。通过判断返回值是否为 `nil` 可以判断调用是否成功:
|
||||
|
||||
@ -225,7 +225,7 @@ if john.residence?.printNumberOfRooms() != nil {
|
||||
// 打印“It was not possible to print the number of rooms.”
|
||||
```
|
||||
|
||||
同样的,可以据此判断通过可选链式调用为属性赋值是否成功。在上面的[通过可选链式调用访问属性](#accessing_properties_through_optional_chaining)的例子中,我们尝试给 `john.residence` 中的 `address` 属性赋值,即使 `residence` 为 `nil`。通过可选链式调用给属性赋值会返回 `Void?`,通过判断返回值是否为 `nil` 就可以知道赋值是否成功:
|
||||
同样的,可以据此判断通过可选链式调用为属性赋值是否成功。在上面的 [通过可选链式调用访问属性](#accessing-properties-through-optional-chaining) 的例子中,我们尝试给 `john.residence` 中的 `address` 属性赋值,即使 `residence` 为 `nil`。通过可选链式调用给属性赋值会返回 `Void?`,通过判断返回值是否为 `nil` 就可以知道赋值是否成功:
|
||||
|
||||
```swift
|
||||
if (john.residence?.address = someAddress) != nil {
|
||||
@ -114,7 +114,7 @@ func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
|
||||
}
|
||||
```
|
||||
|
||||
上例中,`buyFavoriteSnack(person:vendingMachine:)` 函数会查找某人最喜欢的零食,并通过调用 `vend(itemNamed:)` 方法来尝试为他们购买。因为 `vend(itemNamed:)` 方法能抛出错误,所以在调用的它时候在它前面加了 `try` 关键字。
|
||||
上例中,`buyFavoriteSnack(person:vendingMachine:)` 函数会查找某人最喜欢的零食,并通过调用 `vend(itemNamed:)` 方法来尝试为他们购买。因为 `vend(itemNamed:)` 方法能抛出错误,所以在调用它的时候在它前面加了 `try` 关键字。
|
||||
|
||||
`throwing` 构造器能像 `throwing` 函数一样传递错误。例如下面代码中的 `PurchasedSnack` 构造器在构造过程中调用了 throwing 函数,并且通过传递到它的调用者来处理这些错误。
|
||||
|
||||
@ -142,12 +142,14 @@ do {
|
||||
statements
|
||||
} catch pattern 2 where condition {
|
||||
statements
|
||||
} catch pattern 3, pattern 4 where condition {
|
||||
statements
|
||||
} catch {
|
||||
statements
|
||||
}
|
||||
```
|
||||
|
||||
在 `catch` 后面写一个匹配模式来表明这个子句能处理什么样的错误。如果一条 `catch` 子句没有指定匹配模式,那么这条子句可以匹配任何错误,并且把错误绑定到一个名字为 `error` 的局部常量。关于模式匹配的更多信息请参考 [模式](../chapter3/07_Patterns.html)。
|
||||
在 `catch` 后面写一个匹配模式来表明这个子句能处理什么样的错误。如果一条 `catch` 子句没有指定匹配模式,那么这条子句可以匹配任何错误,并且把错误绑定到一个名字为 `error` 的局部常量。关于模式匹配的更多信息请参考 [模式](../03_language_reference/08_Patterns.md)。
|
||||
|
||||
举例来说,下面的代码处理了 `VendingMachineError` 枚举类型的全部三种情况:
|
||||
|
||||
@ -173,14 +175,14 @@ do {
|
||||
|
||||
`catch` 子句不必将 `do` 子句中的代码所抛出的每一个可能的错误都作处理。如果所有 `catch` 子句都未处理错误,错误就会传递到周围的作用域。然而,错误还是必须要被某个周围的作用域处理的。在不会抛出错误的函数中,必须用 `do-catch` 语句处理错误。而能够抛出错误的函数既可以使用 `do-catch` 语句处理,也可以让调用方来处理错误。如果错误传递到了顶层作用域却依然没有被处理,你会得到一个运行时错误。
|
||||
|
||||
以下面的代码为例,不是 `VendingMachineError` 中申明的错误会在调用函数的地方被捕获:
|
||||
以下面的代码为例,不是 `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.")
|
||||
print("Couldn't buy that from the vending machine.")
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,12 +191,28 @@ do {
|
||||
} catch {
|
||||
print("Unexpected non-vending-machine-related error: \(error)")
|
||||
}
|
||||
// 打印“Invalid selection, out of stock, or not enough money.”
|
||||
// 打印“Couldn't buy that from the vending machine.”
|
||||
```
|
||||
|
||||
如果 `vend(itemNamed:)` 抛出的是一个 `VendingMachineError` 类型的错误,`nourish(with:)` 会打印一条消息,否则 `nourish(with:)` 会将错误抛给它的调用方。这个错误之后会被通用的 `catch` 语句捕获。
|
||||
|
||||
### 将错误转换成可选值 {#converting_errors_to_optional_values}
|
||||
另一种捕获多个相关错误的方式是将它们放在 `catch` 后,通过逗号分隔。
|
||||
|
||||
例如:
|
||||
|
||||
```swift
|
||||
func eat(item: String) throws {
|
||||
do {
|
||||
try vendingMachine.vend(itemNamed: item)
|
||||
} catch VendingMachineError.invalidSelection, VendingMachineError.insufficientFunds, VendingMachineError.outOfStock {
|
||||
print("Invalid selection, out of stock, or not enough money.")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`eat(item:)` 函数捕获了列出来的 `VendingMachine` 错误,且它的错误文本和列表的错误相关。如果列出来的三个错误中任意一个抛出,这个 `catch` 代码块就会打印信息。其他错误会传递到外面的作用域,包括以后可能添加的其他 `VendingMachine` 错误。
|
||||
|
||||
### 将错误转换成可选值 {#converting-errors-to-optional-values}
|
||||
|
||||
可以使用 `try?` 通过将错误转换成一个可选值来处理错误。如果是在计算 `try?` 表达式时抛出错误,该表达式的结果就为 `nil`。例如,在下面的代码中,`x` 和 `y` 有着相同的数值和等价的含义:
|
||||
|
||||
@ -225,7 +243,7 @@ func fetchData() -> Data? {
|
||||
}
|
||||
```
|
||||
|
||||
### 禁用错误传递 {#disabling_error_propagation}
|
||||
### 禁用错误传递 {#disabling-error-propagation}
|
||||
|
||||
有时你知道某个 `throwing` 函数实际上在运行时是不会抛出错误的,在这种情况下,你可以在表达式前面写 `try!` 来禁用错误传递,这会把调用包装在一个不会有错误抛出的运行时断言中。如果真的抛出了错误,你会得到一个运行时错误。
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
类型转换在 Swift 中使用 `is` 和 `as` 操作符实现。这两个操作符分别提供了一种简单达意的方式去检查值的类型或者转换它的类型。
|
||||
|
||||
你也可以用它来检查一个类型是否遵循了某个协议,就像在[检验协议遵循](./21_Protocols.md#checking_for_protocol_conformance)部分讲述的一样。
|
||||
你也可以用它来检查一个类型是否遵循了某个协议,就像在 [检验协议遵循](./21_Protocols.md#checking-for-protocol-conformance) 部分讲述的一样。
|
||||
|
||||
## 为类型转换定义类层次 {#defining-a-class-hierarchy-for-type-casting}
|
||||
|
||||
@ -141,7 +141,7 @@ Swift 为不确定类型提供了两种特殊的类型别名:
|
||||
这里有个示例,使用 `Any` 类型来和混合的不同类型一起工作,包括函数类型和非类类型。它创建了一个可以存储 `Any` 类型的数组 `things`:
|
||||
|
||||
```swift
|
||||
var things = [Any]()
|
||||
var things: [Any] = []
|
||||
|
||||
things.append(0)
|
||||
things.append(0.0)
|
||||
@ -1,85 +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` 的名字尽可能的短,因为它们的名字可以由定义它们的上下文来限定。
|
||||
# 嵌套类型
|
||||
|
||||
枚举常被用于为特定类或结构体实现某些功能。类似地,枚举可以方便的定义工具类或结构体,从而为某个复杂的类型所使用。为了实现这种功能,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` 的名字尽可能的短,因为它们的名字可以由定义它们的上下文来限定。
|
||||
@ -11,7 +11,7 @@ Swift 中的扩展可以:
|
||||
- 定义和使用新的嵌套类型
|
||||
- 使已经存在的类型遵循(conform)一个协议
|
||||
|
||||
在 Swift 中,你甚至可以扩展协议以提供其需要的实现,或者添加额外功能给遵循的类型所使用。你可以从 [协议扩展](https://docs.swift.org/swift-book/LanguageGuide/Protocols.html#ID521) 获取更多细节。
|
||||
在 Swift 中,你甚至可以扩展协议以提供其需要的实现,或者添加额外功能给遵循的类型所使用。你可以从 [协议扩展](./21_Protocols.md#protocol-extensions) 获取更多细节。
|
||||
|
||||
> 注意
|
||||
>
|
||||
@ -35,9 +35,9 @@ extension SomeType: SomeProtocol, AnotherProtocol {
|
||||
}
|
||||
```
|
||||
|
||||
这种遵循协议的方式在 [使用扩展遵循协议](https://docs.swift.org/swift-book/LanguageGuide/Protocols.html#ID277) 中有描述。
|
||||
这种遵循协议的方式在 [在扩展里添加协议遵循](./21_Protocols.md#adding-protocol-conformance-with-an-extension) 中有描述。
|
||||
|
||||
扩展可以使用在现有范型类型上,就像 [扩展范型类型](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID185) 中描述的一样。你还可以使用扩展给泛型类型有条件的添加功能,就像 [扩展一个带有 Where 字句的范型](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID553) 中描述的一样。
|
||||
扩展可以使用在现有泛型类型上,就像 [泛型扩展](./22_Generics.md#extending-a-generic-type) 中描述的一样。你还可以使用扩展给泛型类型有条件的添加功能,就像 [具有泛型 Where 子句的扩展](./22_Generics.md#extensions-with-a-generic-where-clause) 中描述的一样。
|
||||
|
||||
> 注意
|
||||
>
|
||||
@ -87,7 +87,7 @@ print("A marathon is \(aMarathon) meters long")
|
||||
|
||||
扩展可以给一个类添加新的便利构造器,但是它们不能给类添加新的指定构造器或者析构器。指定构造器和析构器必须始终由类的原始实现提供。
|
||||
|
||||
如果你使用扩展给一个值类型添加构造器只是用于给所有的存储属性提供默认值,并且没有定义任何自定义构造器,那么你可以在该值类型扩展的构造器中使用默认构造器和成员构造器。如果你把构造器写到了值类型的原始实现中,就像 [值类型的构造器委托](https://docs.swift.org/swift-book/LanguageGuide/Initialization.html#ID215) 中所描述的,那么就不属于在扩展中添加构造器。
|
||||
如果你使用扩展给一个值类型添加构造器,而这个值类型已经为所有存储属性提供默认值,且没有定义任何自定义构造器,那么你可以在该值类型扩展的构造器中使用默认构造器和成员构造器。如果你已经将构造器写在值类型的原始实现中,则不适用于这种情况,如同 [值类型的构造器代理](./14_Initialization.md#initializer-delegation-for-value-types) 中所描述的那样。
|
||||
|
||||
如果你使用扩展给另一个模块中定义的结构体添加构造器,那么新的构造器直到定义模块中使用一个构造器之前,不能访问 `self`。
|
||||
|
||||
@ -106,7 +106,7 @@ struct Rect {
|
||||
}
|
||||
```
|
||||
|
||||
因为 `Rect` 结构体给所有的属性都提供了默认值,所以它自动获得了一个默认构造器和一个成员构造器,就像 [默认构造器](https://docs.swift.org/swift-book/LanguageGuide/Initialization.html#ID213) 中描述的一样。这些构造器可以用来创建新的 `Rect` 实例:
|
||||
因为 `Rect` 结构体给所有的属性都提供了默认值,所以它自动获得了一个默认构造器和一个成员构造器,就像 [默认构造器](./14_Initialization.md#default-initializers) 中描述的一样。这些构造器可以用来创建新的 `Rect` 实例:
|
||||
|
||||
```swift
|
||||
let defaultRect = Rect()
|
||||
@ -114,7 +114,7 @@ let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0),
|
||||
size: Size(width: 5.0, height: 5.0))
|
||||
```
|
||||
|
||||
你可以通过扩展 `Rect` 结构体来提供一个允许指定 point 和 size 的构造器:
|
||||
你可以通过扩展 `Rect` 结构体来提供一个允许指定 center 和 size 的构造器:
|
||||
|
||||
```swift
|
||||
extension Rect {
|
||||
@ -184,7 +184,7 @@ someInt.square()
|
||||
|
||||
## 下标 {#subscripts}
|
||||
|
||||
扩展可以给现有的类型添加新的下标。下面的例子中,对 Swift 的 `Int` 类型添加了一个整数类型的下标。下标 `[n]` 从数字右侧开始,返回小数点后的第 `n` 位:
|
||||
扩展可以给现有的类型添加新的下标。下面的例子中,对 Swift 的 `Int` 类型添加了一个整数类型的下标。下标 `[n]` 返回从数字右侧开始的第 `n` 位数字:
|
||||
|
||||
- `123456789[0]` 返回 `9`
|
||||
- `123456789[1]` 返回 `8`
|
||||
@ -210,7 +210,7 @@ extension Int {
|
||||
// 返回 7
|
||||
```
|
||||
|
||||
如果操作的 `Int` 值没有足够的位数满足所请求的下标,那么下标的现实将返回 `0`,将好像在数字的左边补上了 0:
|
||||
如果操作的 `Int` 值没有足够的位数满足所请求的下标,那么下标的实现将返回 `0`,就好像在数字的左边补上了 0:
|
||||
|
||||
```swift
|
||||
746381295[9]
|
||||
@ -268,4 +268,4 @@ printIntegerKinds([3, 19, -27, 0, -6, 0, 7])
|
||||
|
||||
> 注意
|
||||
>
|
||||
> `number.kind` 已经被认为是 `Int.Kind` 类型。所以,在 `switch` 语句中所有的 `Int.Kind` case 分支可以被缩写,就像使用 `.negative` 替代 `Int.Kind.negative.`。
|
||||
> `number.kind` 已经被认为是 `Int.Kind` 类型。所以,在 `switch` 语句中所有的 `Int.Kind` case 分支可以被缩写,例如使用 `.negative` 替代 `Int.Kind.negative.`。
|
||||
@ -22,7 +22,7 @@ struct SomeStructure: FirstProtocol, AnotherProtocol {
|
||||
}
|
||||
```
|
||||
|
||||
若一个拥有父类的类在遵循协议时,应该将父类名放在协议名之前,以逗号分隔:
|
||||
若是一个类拥有父类,应该将父类名放在遵循的协议名之前,以逗号分隔:
|
||||
|
||||
```swift
|
||||
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
|
||||
@ -75,7 +75,7 @@ let john = Person(fullName: "John Appleseed")
|
||||
|
||||
这个例子中定义了一个叫做 `Person` 的结构体,用来表示一个具有名字的人。从第一行代码可以看出,它遵循了 `FullyNamed` 协议。
|
||||
|
||||
`Person` 结构体的每一个实例都有一个 `String` 类型的存储型属性 `fullName`。这正好满足了 `FullyNamed` 协议的要求,也就意味着 `Person` 结构体正确地符合了协议。(如果协议要求未被完全满足,在编译时会报错。)
|
||||
`Person` 结构体的每一个实例都有一个 `String` 类型的存储型属性 `fullName`。这正好满足了 `FullyNamed` 协议的要求,也就意味着 `Person` 结构体正确地遵循了协议。(如果协议要求未被完全满足,在编译时会报错。)
|
||||
|
||||
下面是一个更为复杂的类,它采纳并遵循了 `FullyNamed` 协议:
|
||||
|
||||
@ -105,7 +105,7 @@ var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
|
||||
|
||||
```swift
|
||||
protocol SomeProtocol {
|
||||
static func someTypeMethod()
|
||||
static func someTypeMethod()
|
||||
}
|
||||
```
|
||||
|
||||
@ -143,7 +143,7 @@ print("And another one: \(generator.random())")
|
||||
|
||||
## 异变方法要求 {#mutating-method-requirements}
|
||||
|
||||
有时需要在方法中改变(或*异变*)方法所属的实例。例如,在值类型(即结构体和枚举)的实例方法中,将 `mutating` 关键字作为方法的前缀,写在 `func` 关键字之前,表示可以在该方法中修改它所属的实例以及实例的任意属性的值。这一过程在 [在实例方法中修改值类型](./11_Methods.md#modifying_value_types_from_within_instance_methods) 章节中有详细描述。
|
||||
有时需要在方法中改变(或*异变*)方法所属的实例。例如,在值类型(即结构体和枚举)的实例方法中,将 `mutating` 关键字作为方法的前缀,写在 `func` 关键字之前,表示可以在该方法中修改它所属的实例以及实例的任意属性的值。这一过程在 [在实例方法中修改值类型](./11_Methods.md#modifying-value-types-from-within-instance-methods) 章节中有详细描述。
|
||||
|
||||
如果你在协议中定义了一个实例方法,该方法会改变遵循该协议的类型的实例,那么在定义协议时需要在方法前加 `mutating` 关键字。这使得结构体和枚举能够遵循此协议并满足此方法要求。
|
||||
|
||||
@ -204,13 +204,13 @@ class SomeClass: SomeProtocol {
|
||||
}
|
||||
```
|
||||
|
||||
使用 `required` 修饰符可以确保所有子类也必须提供此构造器实现,从而也能符合协议。
|
||||
使用 `required` 修饰符可以确保所有子类也必须提供此构造器实现,从而也能遵循协议。
|
||||
|
||||
关于 `required` 构造器的更多内容,请参考 [必要构造器](./14_Initialization.md#required_initializers)。
|
||||
关于 `required` 构造器的更多内容,请参考 [必要构造器](./14_Initialization.md#required-initializers)。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果类已经被标记为 `final`,那么不需要在协议构造器的实现中使用 `required` 修饰符,因为 `final` 类不能有子类。关于 `final` 修饰符的更多内容,请参见 [防止重写](./13_Inheritance.md#preventing_overrides)。
|
||||
> 如果类已经被标记为 `final`,那么不需要在协议构造器的实现中使用 `required` 修饰符,因为 `final` 类不能有子类。关于 `final` 修饰符的更多内容,请参见 [防止重写](./13_Inheritance.md#preventing-overrides)。
|
||||
|
||||
如果一个子类重写了父类的指定构造器,并且该构造器满足了某个协议的要求,那么该构造器的实现需要同时标注 `required` 和 `override` 修饰符:
|
||||
|
||||
@ -236,13 +236,13 @@ class SomeSubClass: SomeSuperClass, SomeProtocol {
|
||||
|
||||
### 可失败构造器要求 {#failable-initializer-requirements}
|
||||
|
||||
协议还可以为遵循协议的类型定义可失败构造器要求,详见 [可失败构造器](./14_Initialization.md#failable_initializers)。
|
||||
协议还可以为遵循协议的类型定义可失败构造器要求,详见 [可失败构造器](./14_Initialization.md#failable-initializers)。
|
||||
|
||||
遵循协议的类型可以通过可失败构造器(`init?`)或非可失败构造器(`init`)来满足协议中定义的可失败构造器要求。协议中定义的非可失败构造器要求可以通过非可失败构造器(`init`)或隐式解包可失败构造器(`init!`)来满足。
|
||||
|
||||
## 协议作为类型 {#protocols-as-types}
|
||||
|
||||
尽管协议本身并未实现任何功能,但是协议可以被当做一个功能完备的类型来使用。
|
||||
尽管协议本身并未实现任何功能,但是协议可以被当做一个功能完备的类型来使用。协议作为类型使用,有时被称作「存在类型」,这个名词来自「存在着一个类型 T,该类型遵循协议 T」。
|
||||
|
||||
协议可以像其他普通类型一样使用,使用场景如下:
|
||||
|
||||
@ -272,7 +272,7 @@ class Dice {
|
||||
|
||||
例子中定义了一个 `Dice` 类,用来代表桌游中拥有 N 个面的骰子。`Dice` 的实例含有 `sides` 和 `generator` 两个属性,前者是整型,用来表示骰子有几个面,后者为骰子提供一个随机数生成器,从而生成随机点数。
|
||||
|
||||
`generator` 属性的类型为 `RandomNumberGenerator`,因此任何遵循了 `RandomNumberGenerator` 协议的类型的实例都可以赋值给 `generator`,除此之外并无其他要求。
|
||||
`generator` 属性的类型为 `RandomNumberGenerator`,因此任何遵循了 `RandomNumberGenerator` 协议的类型的实例都可以赋值给 `generator`,除此之外并无其他要求。并且由于其类型是 `RandomNumberGenerator`,所以在 `Dice` 类中与 `generator` 交互的代码,必须适用于所有遵循该协议的 `generator` 实例。这意味着不能使用由 `generator` 的底层类型定义的任何方法或属性。但是,你可以从协议类型转换成底层实现类型,就像从父类向下转型为子类一样。请参考 [向下转型](./18_Type_Casting.md#downcasting)。
|
||||
|
||||
`Dice` 类还有一个构造器,用来设置初始状态。构造器有一个名为 `generator`,类型为 `RandomNumberGenerator` 的形参。在调用构造方法创建 `Dice` 的实例时,可以传入任何遵循 `RandomNumberGenerator` 协议的实例给 `generator`。
|
||||
|
||||
@ -312,7 +312,7 @@ protocol DiceGameDelegate {
|
||||
|
||||
`DiceGame` 协议可以被任意涉及骰子的游戏遵循。
|
||||
|
||||
`DiceGameDelegate` 协议可以被任意类型遵循,用来追踪 `DiceGame` 的游戏过程。为了防止强引用导致的循环引用问题,可以把协议声明为弱引用,更多相关的知识请看 [类实例之间的循环强引用](./23_Automatic_Reference_Counting.md#strong_reference_cycles_between_class_instances),当协议标记为类专属可以使 `SnakesAndLadders` 类在声明协议时强制要使用弱引用。若要声明类专属的协议就必须继承于 `AnyObject` ,更多请看 [类专属的协议](#class_only_protocol)。
|
||||
`DiceGameDelegate` 协议可以被任意类型遵循,用来追踪 `DiceGame` 的游戏过程。为了防止强引用导致的循环引用问题,可以把协议声明为弱引用,更多相关的知识请看 [类实例之间的循环强引用](./24_Automatic_Reference_Counting.md#strong-reference-cycles-between-class-instances),当协议标记为类专属可以使 `SnakesAndLadders` 类在声明协议时强制要使用弱引用。若要声明类专属的协议就必须继承于 `AnyObject` ,更多请看 [类专属的协议](#class-only-protocol)。
|
||||
|
||||
如下所示,`SnakesAndLadders` 是 [控制流](./05_Control_Flow.md) 章节引入的蛇梯棋游戏的新版本。新版本使用 `Dice` 实例作为骰子,并且实现了 `DiceGame` 和 `DiceGameDelegate` 协议,后者用来记录游戏的过程:
|
||||
|
||||
@ -355,7 +355,7 @@ class SnakesAndLadders: DiceGame {
|
||||
|
||||
游戏使用 `SnakesAndLadders` 类的 `init()` 构造器来初始化游戏。所有的游戏逻辑被转移到了协议中的 `play()` 方法,`play()` 方法使用协议要求的 `dice` 属性提供骰子摇出的值。
|
||||
|
||||
注意,`delegate` 并不是游戏的必备条件,因此 `delegate` 被定义为 `DiceGameDelegate` 类型的可选属性。因为 `delegate` 是可选值,因此会被自动赋予初始值 `nil`。随后,可以在游戏中为 `delegate` 设置适当的值。
|
||||
注意,`delegate` 并不是游戏的必备条件,因此 `delegate` 被定义为 `DiceGameDelegate` 类型的可选属性。因为 `delegate` 是可选值,因此会被自动赋予初始值 `nil`。随后,可以在游戏中为 `delegate` 设置适当的值。因为 `DiceGameDelegate` 协议是类专属的,可以将 `delegate` 声明为 `weak`,从而避免循环引用。
|
||||
|
||||
`DicegameDelegate` 协议提供了三个方法用来追踪游戏过程。这三个方法被放置于游戏的逻辑中,即 `play()` 方法内。分别在游戏开始时,新一轮开始时,以及游戏结束时被调用。
|
||||
|
||||
@ -387,7 +387,7 @@ class DiceGameTracker: DiceGameDelegate {
|
||||
|
||||
`gameDidStart(_:)` 方法从 `game` 参数获取游戏信息并打印。`game` 参数是 `DiceGame` 类型而不是 `SnakeAndLadders` 类型,所以在 `gameDidStart(_:)` 方法中只能访问 `DiceGame` 协议中的内容。当然了,`SnakeAndLadders` 的方法也可以在类型转换之后调用。在上例代码中,通过 `is` 操作符检查 `game` 是否为 `SnakesAndLadders` 类型的实例,如果是,则打印出相应的消息。
|
||||
|
||||
无论当前进行的是何种游戏,由于 `game` 符合 `DiceGame` 协议,可以确保 `game` 含有 `dice` 属性。因此在 `gameDidStart(_:)` 方法中可以通过传入的 `game` 参数来访问 `dice` 属性,进而打印出 `dice` 的 `sides` 属性的值。
|
||||
无论当前进行的是何种游戏,由于 `game` 遵循 `DiceGame` 协议,可以确保 `game` 含有 `dice` 属性。因此在 `gameDidStart(_:)` 方法中可以通过传入的 `game` 参数来访问 `dice` 属性,进而打印出 `dice` 的 `sides` 属性的值。
|
||||
|
||||
`DiceGameTracker` 的运行情况如下所示:
|
||||
|
||||
@ -455,7 +455,7 @@ print(game.textualDescription)
|
||||
|
||||
## 有条件地遵循协议 {#Conditionally-Conforming-to-a-Protocol}
|
||||
|
||||
泛型类型可能只在某些情况下满足一个协议的要求,比如当类型的泛型形式参数遵循对应协议时。你可以通过在扩展类型时列出限制让泛型类型有条件地遵循某协议。在你采纳协议的名字后面写泛型 `where` 分句。更多关于泛型 `where` 分句,见 [泛型 Where 分句](./22_Generics.md##where_clauses)。
|
||||
泛型类型可能只在某些情况下满足一个协议的要求,比如当类型的泛型形式参数遵循对应协议时。你可以通过在扩展类型时列出限制让泛型类型有条件地遵循某协议。在你采纳协议的名字后面写泛型 `where` 分句。更多关于泛型 `where` 分句,见 [泛型 Where 分句](./22_Generics.md##where-clauses)。
|
||||
|
||||
下面的扩展让 `Array` 类型只要在存储遵循 `TextRepresentable` 协议的元素时就遵循 `TextRepresentable` 协议。
|
||||
|
||||
@ -473,7 +473,7 @@ print(myDice.textualDescription)
|
||||
|
||||
## 在扩展里声明采纳协议 {#declaring-protocol-adoption-with-an-extension}
|
||||
|
||||
当一个类型已经符合了某个协议中的所有要求,却还没有声明采纳该协议时,可以通过空的扩展来让它采纳该协议:
|
||||
当一个类型已经遵循了某个协议中的所有要求,却还没有声明采纳该协议时,可以通过空的扩展来让它采纳该协议:
|
||||
|
||||
```swift
|
||||
struct Hamster {
|
||||
@ -498,9 +498,65 @@ print(somethingTextRepresentable.textualDescription)
|
||||
>
|
||||
> 即使满足了协议的所有要求,类型也不会自动遵循协议,必须显式地遵循协议。
|
||||
|
||||
## 使用合成实现来采纳协议 {#adopting-a-protocol-using-a-synthesized-implementation}
|
||||
|
||||
Swift 可以自动提供一些简单场景下遵循 `Equatable`、`Hashable` 和 `Comparable` 协议的实现。在使用这些合成实现之后,无需再编写重复的代码来实现这些协议所要求的方法。
|
||||
|
||||
Swift 为以下几种自定义类型提供了 `Equatable` 协议的合成实现:
|
||||
|
||||
- 遵循 `Equatable` 协议且只有存储属性的结构体。
|
||||
- 遵循 `Equatable` 协议且只有关联类型的枚举
|
||||
- 没有任何关联类型的枚举
|
||||
|
||||
在包含类型原始声明的文件中声明对 `Equatable` 协议的遵循,可以得到 `==` 操作符的合成实现,且无需自己编写任何关于 `==` 的实现代码。`Equatable` 协议同时包含 `!=` 操作符的默认实现。
|
||||
|
||||
下面的例子中定义了一个 `Vector3D` 结构体来表示一个类似 `Vector2D` 的三维向量 `(x, y, z)`。由于 `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."
|
||||
```
|
||||
|
||||
Swift 为以下几种自定义类型提供了 `Hashable` 协议的合成实现:
|
||||
|
||||
- 遵循 `Hashable` 协议且只有存储属性的结构体。
|
||||
- 遵循 `Hashable` 协议且只有关联类型的枚举
|
||||
- 没有任何关联类型的枚举
|
||||
|
||||
在包含类型原始声明的文件中声明对 `Hashable` 协议的遵循,可以得到 `hash(into:)` 的合成实现,且无需自己编写任何关于 `hash(into:)` 的实现代码。
|
||||
|
||||
Swift 为没有原始值的枚举类型提供了 `Comparable` 协议的合成实现。如果枚举类型包含关联类型,那这些关联类型也必须同时遵循 `Comparable` 协议。在包含原始枚举类型声明的文件中声明其对 `Comparable` 协议的遵循,可以得到 `<` 操作符的合成实现,且无需自己编写任何关于 `<` 的实现代码。`Comparable` 协议同时包含 `<=`、`>` 和 `>=` 操作符的默认实现。
|
||||
|
||||
下面的例子中定义了 `SkillLevel` 枚举类型,其中定义了初学者(beginner)、中级(intermediate)和专家(expert)三种等级,专家等级会由额外的星级(stars)来进行排名。
|
||||
|
||||
```swift
|
||||
enum SkillLevel: Comparable {
|
||||
case beginner
|
||||
case intermediate
|
||||
case expert(stars: Int)
|
||||
}
|
||||
var levels = [SkillLevel.intermediate, SkillLevel.beginner,
|
||||
SkillLevel.expert(stars: 5), SkillLevel.expert(stars: 3)]
|
||||
for level in levels.sorted() {
|
||||
print(level)
|
||||
}
|
||||
// 打印 "beginner"
|
||||
// 打印 "intermediate"
|
||||
// 打印 "expert(stars: 3)"
|
||||
// 打印 "expert(stars: 5)"
|
||||
```
|
||||
|
||||
## 协议类型的集合 {#collections-of-protocol-types}
|
||||
|
||||
协议类型可以在数组或者字典这样的集合中使用,在 [协议类型](./21_Protocols.md##protocols_as_types) 提到了这样的用法。下面的例子创建了一个元素类型为 `TextRepresentable` 的数组:
|
||||
协议类型可以在数组或者字典这样的集合中使用,在 [协议类型](./21_Protocols.md##protocols-as-types) 提到了这样的用法。下面的例子创建了一个元素类型为 `TextRepresentable` 的数组:
|
||||
|
||||
```swift
|
||||
let things: [TextRepresentable] = [game, d12, simonTheHamster]
|
||||
@ -576,7 +632,7 @@ print(game.prettyTextualDescription)
|
||||
|
||||
## 类专属的协议 {#class-only-protocol}
|
||||
|
||||
你通过添加 `AnyObject` 关键字到协议的继承列表,就可以限制协议只能被类类型采纳(以及非结构体或者非枚举的类型)。
|
||||
你通过添加 `AnyObject` 关键字到协议的继承列表,就可以限制协议只能被类类型遵循(而不能是结构体类型或者枚举类型)。
|
||||
|
||||
```swift
|
||||
protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
|
||||
@ -588,7 +644,7 @@ protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 当协议定义的要求需要遵循协议的类型必须是引用语义而非值语义时,应该采用类类型专属协议。关于引用语义和值语义的更多内容,请查看 [结构体和枚举是值类型](./09_Classes_and_Structures.md#structures_and_enumerations_are_value_types) 和 [类是引用类型](./09_Classes_and_Structures.md#classes_are_reference_types)。
|
||||
> 当协议定义的要求需要遵循协议的类型必须是引用语义而非值语义时,应该采用类类型专属协议。关于引用语义和值语义的更多内容,请查看 [结构体和枚举是值类型](./09_Structures_And_Classes.md#structures-and-enumerations-are-value-types) 和 [类是引用类型](./09_Structures_And_Classes.md#classes-are-reference-types)。
|
||||
|
||||
## 协议合成 {#protocol-composition}
|
||||
|
||||
@ -619,9 +675,9 @@ wishHappyBirthday(to: birthdayPerson)
|
||||
|
||||
`Named` 协议包含 `String` 类型的 `name` 属性。`Aged` 协议包含 `Int` 类型的 `age` 属性。`Person` 结构体采纳了这两个协议。
|
||||
|
||||
`wishHappyBirthday(to:)` 函数的参数 `celebrator` 的类型为 `Named & Aged`, 这意味着“任何同时遵循 Named 和 Aged 的协议”。它不关心参数的具体类型,只要参数符合这两个协议即可。
|
||||
`wishHappyBirthday(to:)` 函数的参数 `celebrator` 的类型为 `Named & Aged`, 这意味着“任何同时遵循 Named 和 Aged 协议的类型”。它不关心参数的具体类型,只要参数遵循这两个协议即可。
|
||||
|
||||
上面的例子创建了一个名为 `birthdayPerson` 的 `Person` 的实例,作为参数传递给了 `wishHappyBirthday(to:)` 函数。因为 `Person` 同时符合这两个协议,所以这个参数合法,函数将打印生日问候语。
|
||||
上面的例子创建了一个名为 `birthdayPerson` 的 `Person` 的实例,作为参数传递给了 `wishHappyBirthday(to:)` 函数。因为 `Person` 同时遵循这两个协议,所以这个参数合法,函数将打印生日问候语。
|
||||
|
||||
这里有一个例子:将 Location 类和前面的 Named 协议进行组合:
|
||||
|
||||
@ -650,16 +706,16 @@ beginConcert(in: seattle)
|
||||
// 打印 "Hello, Seattle!"
|
||||
```
|
||||
|
||||
`beginConcert(in:)` 函数接受一个类型为 `Location & Named` 的参数,这意味着“任何 Location 的子类,并且遵循 Named 协议”。例如,City 就满足这样的条件。
|
||||
`beginConcert(in:)` 函数接受一个类型为 `Location & Named` 的参数,这意味着“任何 Location 的子类,并且遵循 Named 协议”。在这个例子中,City 就满足这样的条件。
|
||||
|
||||
将 birthdayPerson 传入 `beginConcert(in:)` 函数是不合法的,因为 Person 不是 Location 的子类。同理,如果你新建一个类继承于 Location,但是没有遵循 Named 协议,而用这个类的实例去调用 `beginConcert(in:)` 函数也是非法的。
|
||||
|
||||
## 检查协议一致性 {#checking-for-protocol-conformance}
|
||||
|
||||
你可以使用[类型转换](./18_Type_Casting.md)中描述的 `is` 和 `as` 操作符来检查协议一致性,即是否符合某协议,并且可以转换到指定的协议类型。检查和转换协议的语法与检查和转换类型是完全一样的:
|
||||
你可以使用 [类型转换](./18_Type_Casting.md) 中描述的 `is` 和 `as` 操作符来检查协议一致性,即是否遵循某协议,并且可以转换到指定的协议类型。检查和转换协议的语法与检查和转换类型是完全一样的:
|
||||
|
||||
* `is` 用来检查实例是否符合某个协议,若符合则返回 `true`,否则返回 `false`;
|
||||
* `as?` 返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回 `nil`;
|
||||
* `is` 用来检查实例是否遵循某个协议,若遵循则返回 `true`,否则返回 `false`;
|
||||
* `as?` 返回一个可选值,当实例遵循某个协议时,返回类型为协议类型的可选值,否则返回 `nil`;
|
||||
* `as!` 将实例强制向下转换到某个协议类型,如果强转失败,将触发运行时错误。
|
||||
|
||||
下面的例子定义了一个 `HasArea` 协议,该协议定义了一个 `Double` 类型的可读属性 `area`:
|
||||
@ -708,7 +764,7 @@ let objects: [AnyObject] = [
|
||||
|
||||
`objects` 数组使用字面量初始化,数组包含一个 `radius` 为 `2` 的 `Circle` 的实例,一个保存了英国国土面积的 `Country` 实例和一个 `legs` 为 `4` 的 `Animal` 实例。
|
||||
|
||||
如下所示,`objects` 数组可以被迭代,并对迭代出的每一个元素进行检查,看它是否符合 `HasArea` 协议:
|
||||
如下所示,`objects` 数组可以被迭代,并对迭代出的每一个元素进行检查,看它是否遵循 `HasArea` 协议:
|
||||
|
||||
```swift
|
||||
for object in objects {
|
||||
@ -723,7 +779,7 @@ for object in objects {
|
||||
// Something that doesn't have an area
|
||||
```
|
||||
|
||||
当迭代出的元素符合 `HasArea` 协议时,将 `as?` 操作符返回的可选值通过可选绑定,绑定到 `objectWithArea` 常量上。`objectWithArea` 是 `HasArea` 协议类型的实例,因此 `area` 属性可以被访问和打印。
|
||||
当迭代出的元素遵循 `HasArea` 协议时,将 `as?` 操作符返回的可选值通过可选绑定,绑定到 `objectWithArea` 常量上。`objectWithArea` 是 `HasArea` 协议类型的实例,因此 `area` 属性可以被访问和打印。
|
||||
|
||||
`objects` 数组中的元素的类型并不会因为强转而丢失类型信息,它们仍然是 `Circle`,`Country`,`Animal` 类型。然而,当它们被赋值给 `objectWithArea` 常量时,只被视为 `HasArea` 类型,因此只有 `area` 属性能够被访问。
|
||||
|
||||
@ -733,7 +789,7 @@ for object in objects {
|
||||
|
||||
使用可选要求时(例如,可选的方法或者属性),它们的类型会自动变成可选的。比如,一个类型为 `(Int) -> String` 的方法会变成 `((Int) -> String)?`。需要注意的是整个函数类型是可选的,而不是函数的返回值。
|
||||
|
||||
协议中的可选要求可通过可选链式调用来使用,因为遵循协议的类型可能没有实现这些可选要求。类似 `someOptionalMethod?(someArgument)` 这样,你可以在可选方法名称后加上 `?` 来调用可选方法。详细内容可在[可选链式调用](./16_Optional_Chaining.md)章节中查看。
|
||||
协议中的可选要求可通过可选链式调用来使用,因为遵循协议的类型可能没有实现这些可选要求。类似 `someOptionalMethod?(someArgument)` 这样,你可以在可选方法名称后加上 `?` 来调用可选方法。详细内容可在 [可选链式调用](./16_Optional_Chaining.md) 章节中查看。
|
||||
|
||||
下面的例子定义了一个名为 `Counter` 的用于整数计数的类,它使用外部的数据源来提供每次的增量。数据源由 `CounterDataSource` 协议定义,它包含两个可选要求:
|
||||
|
||||
@ -770,9 +826,9 @@ class Counter {
|
||||
|
||||
`increment()` 方法首先试图使用 `increment(forCount:)` 方法来得到每次的增量。`increment()` 方法使用可选链式调用来尝试调用 `increment(forCount:)`,并将当前的 `count` 值作为参数传入。
|
||||
|
||||
这里使用了两层可选链式调用。首先,由于 `dataSource` 可能为 `nil`,因此在 `dataSource` 后边加上了 `?`,以此表明只在 `dataSource` 非空时才去调用 `increment(forCount:)` 方法。其次,即使 `dataSource` 存在,也无法保证其是否实现了 `increment(forCount:)` 方法,因为这个方法是可选的。因此,`increment(forCount:)` 方法同样使用可选链式调用进行调用,只有在该方法被实现的情况下才能调用它,所以在 `increment(forCount:)` 方法后边也加上了 `?`。
|
||||
这里使用了两层可选链式调用。首先,由于 `dataSource` 可能为 `nil`,因此在 `dataSource` 后边加上了 `?`,以此表明只在 `dataSource` 非空时才去调用 `increment(forCount:)` 方法。其次,即使 `dataSource` 存在,也无法保证其是否实现了 `increment(forCount:)` 方法,因为这个方法是可选的。因此,`increment(forCount:)` 方法同样使用可选链式调用进行调用,只有在该方法被实现的情况下才能调用它,所以在 `increment(forCount:)` 方法名称后边也加上了 `?`。
|
||||
|
||||
调用 `increment(forCount:)` 方法在上述两种情形下都有可能失败,所以返回值为 `Int?` 类型。虽然在 `CounterDataSource` 协议中,`increment(forCount:)` 的返回值类型是非可选 `Int`。另外,即使这里使用了两层可选链式调用,最后的返回结果依旧是单层的可选类型。关于这一点的更多信息,请查阅 [连接多层可选链式调用](./16_Optional_Chaining)
|
||||
调用 `increment(forCount:)` 方法在上述两种情形下都有可能失败,所以返回值为 `Int?` 类型。虽然在 `CounterDataSource` 协议中,`increment(forCount:)` 的返回值类型是非可选 `Int`。另外,即使这里使用了两层可选链式调用,最后的返回结果依旧是单层的可选类型。关于这一点的更多信息,请查阅 [连接多层可选链式调用](./16_Optional_Chaining.md)。
|
||||
|
||||
在调用 `increment(forCount:)` 方法后,`Int?` 型的返回值通过可选绑定解包并赋值给常量 `amount`。如果可选值确实包含一个数值,也就是说,数据源和方法都存在,数据源方法返回了一个有效值。之后便将解包后的 `amount` 加到 `count` 上,增量操作完成。
|
||||
|
||||
@ -801,7 +857,7 @@ for _ in 1...4 {
|
||||
// 12
|
||||
```
|
||||
|
||||
上述代码新建了一个 `Counter` 实例,并将它的数据源设置为一个 `ThreeSource` 的实例,然后调用 `increment()` 方法 `4` 次。按照预期预期一样,每次调用都会将 `count` 的值增加 `3`.
|
||||
上述代码新建了一个 `Counter` 实例,并将它的数据源设置为一个 `ThreeSource` 的实例,然后调用 `increment()` 方法 `4` 次。正如预期一样,每次调用都会将 `count` 的值增加 `3`.
|
||||
|
||||
下面是一个更为复杂的数据源 `TowardsZeroSource`,它将使得最后的值变为 `0`:
|
||||
|
||||
@ -861,9 +917,11 @@ print("And here's a random Boolean: \(generator.randomBool())")
|
||||
// 打印 “And here's a random Boolean: true”
|
||||
```
|
||||
|
||||
协议扩展可以为遵循协议的类型增加实现,但不能声明该协议继承自另一个协议。协议的继承只能在协议声明处进行指定。
|
||||
|
||||
### 提供默认实现 {#providing-default-implementations}
|
||||
|
||||
可以通过协议扩展来为协议要求的属性、方法以及下标提供默认的实现。如果遵循协议的类型为这些要求提供了自己的实现,那么这些自定义实现将会替代扩展中的默认实现被使用。
|
||||
可以通过协议扩展来为协议要求的方法、计算属性提供默认的实现。如果遵循协议的类型为这些要求提供了自己的实现,那么这些自定义实现将会替代扩展中的默认实现被使用。
|
||||
|
||||
> 注意
|
||||
>
|
||||
@ -881,9 +939,9 @@ extension PrettyTextRepresentable {
|
||||
|
||||
### 为协议扩展添加限制条件 {#adding-constraints-to-protocol-extensions}
|
||||
|
||||
在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用 `where` 子句来描述,正如[泛型 Where 子句](./22_Generics.md#where_clauses)中所描述的。
|
||||
在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用 `where` 子句来描述,正如 [泛型 Where 子句](./22_Generics.md#where-clauses) 中所描述的。
|
||||
|
||||
例如,你可以扩展 `Collection` 协议,适用于集合中的元素遵循了 `Equatable` 协议的情况。通过限制集合元素遵 `Equatable` 协议, 作为标准库的一部分, 你可以使用 `==` 和 `!=` 操作符来检查两个元素的等价性和非等价性。
|
||||
例如,你可以扩展 `Collection` 协议,适用于集合中的元素遵循了 `Equatable` 协议的情况。通过限制集合元素遵循 `Equatable` 协议, 作为标准库的一部分, 你可以使用 `==` 和 `!=` 操作符来检查两个元素的等价性和非等价性。
|
||||
|
||||
```swift
|
||||
extension Collection where Element: Equatable {
|
||||
@ -1,6 +1,6 @@
|
||||
# 泛型
|
||||
|
||||
*泛型代码*让你能根据自定义的需求,编写出适用于任意类型的、灵活可复用的函数及类型。你可避免编写重复的代码,用一种清晰抽象的方式来表达代码的意图。
|
||||
*泛型代码*让你能根据自定义的需求,编写出适用于任意类型的、灵活可复用的函数及类型。你可避免编写重复的代码,而是用一种清晰抽象的方式来表达代码的意图。
|
||||
|
||||
泛型是 Swift 最强大的特性之一,很多 Swift 标准库是基于泛型代码构建的。实际上,即使你没有意识到,你也一直在*语言指南*中使用泛型。例如,Swift 的 `Array` 和 `Dictionary` 都是泛型集合。你可以创建一个 `Int` 类型数组,也可创建一个 `String` 类型数组,甚至可以是任意其他 Swift 类型的数组。同样,你也可以创建一个存储任意指定类型的字典,并对该类型没有限制。
|
||||
|
||||
@ -16,7 +16,7 @@ func swapTwoInts(_ a: inout Int, _ b: inout Int) {
|
||||
}
|
||||
```
|
||||
|
||||
这个函数使用输入输出参数(`inout`)来交换 `a` 和 `b` 的值,具体请参考[输入输出参数](./06_Functions.md#in_out_parameters)。
|
||||
这个函数使用输入输出参数(`inout`)来交换 `a` 和 `b` 的值,具体请参考 [输入输出参数](./06_Functions.md#in-out-parameters)。
|
||||
|
||||
`swapTwoInts(_:_:)` 函数将 `b` 的原始值换成了 `a`,将 `a` 的原始值换成了 `b`,你可以调用这个函数来交换两个 `Int` 类型变量:
|
||||
|
||||
@ -44,7 +44,7 @@ func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
|
||||
}
|
||||
```
|
||||
|
||||
你可能注意到了,`swapTwoInts(_:_:‘)`、`swapTwoStrings(_:_:)` 和 `swapTwoDoubles(_:_:)` 函数体是一样的,唯一的区别是它们接受的参数类型(`Int`、`String` 和 `Double`)。
|
||||
你可能注意到了,`swapTwoInts(_:_:)`、`swapTwoStrings(_:_:)` 和 `swapTwoDoubles(_:_:)` 函数体是一样的,唯一的区别是它们接受的参数类型(`Int`、`String` 和 `Double`)。
|
||||
|
||||
在实际应用中,通常需要一个更实用更灵活的函数来交换两个任意类型的值,幸运的是,泛型代码帮你解决了这种问题。(这些函数的泛型版本已经在下面定义好了。)
|
||||
|
||||
@ -123,7 +123,7 @@ swapTwoValues(&someString, &anotherString)
|
||||
|
||||
下图展示了入栈(push)和出栈(pop)的行为:
|
||||
|
||||

|
||||

|
||||
|
||||
1. 现在有三个值在栈中。
|
||||
2. 第四个值被压入到栈的顶部。
|
||||
@ -135,7 +135,7 @@ swapTwoValues(&someString, &anotherString)
|
||||
|
||||
```swift
|
||||
struct IntStack {
|
||||
var items = [Int]()
|
||||
var items: [Int] = []
|
||||
mutating func push(_ item: Int) {
|
||||
items.append(item)
|
||||
}
|
||||
@ -153,7 +153,7 @@ struct IntStack {
|
||||
|
||||
```swift
|
||||
struct Stack<Element> {
|
||||
var items = [Element]()
|
||||
var items: [Element] = []
|
||||
mutating func push(_ item: Element) {
|
||||
items.append(item)
|
||||
}
|
||||
@ -186,7 +186,7 @@ stackOfStrings.push("cuatro")
|
||||
|
||||
下图展示了 `stackOfStrings` 如何将这四个值压栈:
|
||||
|
||||

|
||||

|
||||
|
||||
移除并返回栈顶部的值“cuatro”,即出栈:
|
||||
|
||||
@ -197,7 +197,7 @@ let fromTheTop = stackOfStrings.pop()
|
||||
|
||||
下图展示了如何将顶部的值出栈:
|
||||
|
||||

|
||||

|
||||
|
||||
## 泛型扩展 {#extending-a-generic-type}
|
||||
|
||||
@ -226,13 +226,15 @@ if let topItem = stackOfStrings.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`)默认都是可哈希的。
|
||||
这个要求通过 `Dictionary` 键类型上的类型约束实现,它指明了键必须遵循 Swift 标准库中定义的 `Hashable` 协议。所有 Swift 的基本类型(例如 `String`、`Int`、`Double` 和 `Bool`)默认都是可哈希的。如何让自定义类型遵循 `Hashable` 协议,可以查看文档 [遵循 Hashable 协议](https://developer.apple.com/documentation/swift/hashable#2849490)。
|
||||
|
||||
当自定义泛型类型时,你可以定义你自己的类型约束,这些约束将提供更为强大的泛型编程能力。像 `可哈希(hashable)` 这种抽象概念根据它们的概念特征来描述类型,而不是它们的具体类型。
|
||||
|
||||
@ -352,7 +354,7 @@ protocol Container {
|
||||
```swift
|
||||
struct IntStack: Container {
|
||||
// IntStack 的原始实现部分
|
||||
var items = [Int]()
|
||||
var items: [Int] = []
|
||||
mutating func push(_ item: Int) {
|
||||
items.append(item)
|
||||
}
|
||||
@ -384,7 +386,7 @@ struct IntStack: Container {
|
||||
```swift
|
||||
struct Stack<Element>: Container {
|
||||
// Stack<Element> 的原始实现部分
|
||||
var items = [Element]()
|
||||
var items: [Element] = []
|
||||
mutating func push(_ item: Element) {
|
||||
items.append(item)
|
||||
}
|
||||
@ -408,7 +410,7 @@ struct Stack<Element>: Container {
|
||||
|
||||
### 扩展现有类型来指定关联类型 {#extending-an-existing-type-to-specify-an-associated-type}
|
||||
|
||||
[在扩展添加协议一致性](./21_Protocols.md#adding_protocol_conformance_with_an_extension)中描述了如何利用扩展让一个已存在的类型符合一个协议,这包括使用了关联类型协议。
|
||||
[在扩展添加协议一致性](./21_Protocols.md#adding-protocol-conformance-with-an-extension) 中描述了如何利用扩展让一个已存在的类型遵循一个协议,这包括使用了关联类型协议。
|
||||
|
||||
Swift 的 `Array` 类型已经提供 `append(_:)` 方法,`count` 属性,以及带有 `Int` 索引的下标来检索其元素。这三个功能都符合 `Container` 协议的要求,也就意味着你只需声明 `Array` 遵循`Container` 协议,就可以扩展 Array,使其遵从 Container 协议。你可以通过一个空扩展来实现这点,正如通过扩展采纳协议中的描述:
|
||||
|
||||
@ -433,7 +435,7 @@ protocol Container {
|
||||
|
||||
要遵守 `Container` 协议,`Item` 类型也必须遵守 `Equatable` 协议。
|
||||
|
||||
### 在关联类型约束里使用协议 {#using-a-protocol-in-its-associated-type’s-constraints}
|
||||
### 在关联类型约束里使用协议 {#using-a-protocol-in-its-associated-types-constraints}
|
||||
|
||||
协议可以作为它自身的要求出现。例如,有一个协议细化了 `Container` 协议,添加了一个` suffix(_:)` 方法。`suffix(_:)` 方法返回容器中从后往前给定数量的元素,并把它们存储在一个 `Suffix` 类型的实例里。
|
||||
|
||||
@ -444,9 +446,9 @@ protocol SuffixableContainer: Container {
|
||||
}
|
||||
```
|
||||
|
||||
在这个协议里,`Suffix` 是一个关联类型,就像上边例子中 `Container` 的 `Item` 类型一样。`Suffix` 拥有两个约束:它必须遵循 `SuffixableContainer` 协议(就是当前定义的协议),以及它的 `Item` 类型必须是和容器里的 `Item` 类型相同。`Item` 的约束是一个 `where` 分句,它在下面带有泛型 `Where` 分句的扩展中有讨论。
|
||||
在这个协议里,`Suffix` 是一个关联类型,就像上边例子中 `Container` 的 `Item` 类型一样。`Suffix` 拥有两个约束:它必须遵循 `SuffixableContainer` 协议(就是当前定义的协议),以及它的 `Item` 类型必须是和容器里的 `Item` 类型相同。`Item` 的约束是一个 `where` 分句,它在下面 [具有泛型 Where 子句的扩展](#extensions-with-a-generic-where-clause) 中有讨论。
|
||||
|
||||
这是上面 [强引用循环闭包](./23_Automatic_Reference_Counting.md#strong_reference_cycles_for_closures) 中 `Stack` 类型的扩展,它遵循了 SuffixableContainer 协议:
|
||||
这是上面 [泛型类型](#generic-types) 中 `Stack` 类型的扩展,它遵循了 SuffixableContainer 协议:
|
||||
|
||||
```swift
|
||||
extension Stack: SuffixableContainer {
|
||||
@ -484,7 +486,7 @@ extension IntStack: SuffixableContainer {
|
||||
|
||||
## 泛型 Where 语句 {#where-clauses}
|
||||
|
||||
[类型约束](#type_constraints)让你能够为泛型函数、下标、类型的类型参数定义一些强制要求。
|
||||
[类型约束](#type-constraints) 让你能够为泛型函数、下标、类型的类型参数定义一些强制要求。
|
||||
|
||||
对关联类型添加约束通常是非常有用的。你可以通过定义一个泛型 `where` 子句来实现。通过泛型 `where` 子句让关联类型遵从某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。你可以通过将 `where` 关键字紧跟在类型参数列表后面来定义 `where` 子句,`where` 子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。你可以在函数体或者类型的大括号之前添加 `where` 子句。
|
||||
|
||||
@ -641,9 +643,55 @@ print([1260.0, 1200.0, 98.6, 37.0].average())
|
||||
|
||||
就像可以在其他地方写泛型 `where` 子句一样,你可以在一个泛型 `where` 子句中包含多个条件作为扩展的一部分。用逗号分隔列表中的每个条件。
|
||||
|
||||
## 包含上下文关系的 where 分句 {#contextual-where-clauses}
|
||||
当你使用泛型时,可以为没有独立类型约束的声明添加 `where` 分句。例如,你可以使用 `where` 分句为泛型添加下标,或为扩展方法添加泛型约束。`Container` 结构体是个泛型,下面的例子通过 `where` 分句让新的方法声明其调用所需要满足的类型约束。
|
||||
|
||||
```swift
|
||||
extension Container {
|
||||
func average() -> Double where Item == Int {
|
||||
var sum = 0.0
|
||||
for index in 0..<count {
|
||||
sum += Double(self[index])
|
||||
}
|
||||
return sum / Double(count)
|
||||
}
|
||||
func endsWith(_ item: Item) -> Bool where Item: Equatable {
|
||||
return count >= 1 && self[count-1] == item
|
||||
}
|
||||
}
|
||||
let numbers = [1260, 1200, 98, 37]
|
||||
print(numbers.average())
|
||||
// 输出 "648.75"
|
||||
print(numbers.endsWith(37))
|
||||
// 输出 "true"
|
||||
```
|
||||
|
||||
例子中,当 `Item` 是整型时为 `Container` 添加 `average()` 方法,当 `Item` 遵循 `Equatable` 时添加 `endsWith(_:)` 方法。两个方法都通过 `where` 分句对 `Container` 中定义的泛型 `Item` 进行了约束。
|
||||
|
||||
如果不使用包含上下文关系的 `where` 分句,需要写两个扩展,并为每个扩展分别加上 `where` 分句。下面的例子和上面的具有相同效果。
|
||||
|
||||
```swift
|
||||
extension Container where Item == Int {
|
||||
func average() -> Double {
|
||||
var sum = 0.0
|
||||
for index in 0..<count {
|
||||
sum += Double(self[index])
|
||||
}
|
||||
return sum / Double(count)
|
||||
}
|
||||
}
|
||||
extension Container where Item: Equatable {
|
||||
func endsWith(_ item: Item) -> Bool {
|
||||
return count >= 1 && self[count-1] == item
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在包含上下文关系的 `where` 分句的例子中,由于每个方法的 `where` 分句各自声明了需要满足的条件,因此 `average()` 和 `endsWith(_:)` 的实现能放在同一个扩展里。而将 `where` 分句放在扩展进行声明也能起到同样的效果,但每一个扩展只能有一个必备条件。
|
||||
|
||||
## 具有泛型 Where 子句的关联类型 {#associated-types-with-a-generic-where-clause}
|
||||
|
||||
你可以在关联类型后面加上具有泛型 `where` 的字句。例如,建立一个包含迭代器(`Iterator`)的容器,就像是标准库中使用的 `Sequence` 协议那样。你应该这么写:
|
||||
你可以在关联类型后面加上具有泛型 `where` 的子句。例如,建立一个包含迭代器(`Iterator`)的容器,就像是标准库中使用的 `Sequence` 协议那样。你应该这么写:
|
||||
|
||||
```swift
|
||||
protocol Container {
|
||||
@ -673,7 +721,7 @@ protocol ComparableContainer: Container where Item: Comparable { }
|
||||
extension Container {
|
||||
subscript<Indices: Sequence>(indices: Indices) -> [Item]
|
||||
where Indices.Iterator.Element == Int {
|
||||
var result = [Item]()
|
||||
var result: [Item] = []
|
||||
for index in indices {
|
||||
result.append(self[index])
|
||||
}
|
||||
249
source/02_language_guide/23_Opaque_Types.md
Normal file
249
source/02_language_guide/23_Opaque_Types.md
Normal 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(smallTriangle))` 这样的多重变换操作是非法的,因为经过翻转操作后的结果类型并不能作为 `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` 协议中的 `subscript` 方法会返回 `Item`,这也意味着 `twelve` 的类型也被能推断出为 `Int`。
|
||||
File diff suppressed because it is too large
Load Diff
@ -20,7 +20,7 @@ print("We're number \(one)!")
|
||||
|
||||
你可以思考一下预算表更新的过程,会看到同样的问题。更新预算表总共有两步:首先你把预算项的名字和费用加上,然后再更新总数来反映预算表的现况。在更新之前和之后,你都可以从预算表里读取任何信息并获得正确的答案,就像下面展示的那样。
|
||||
|
||||

|
||||

|
||||
|
||||
而当你添加预算项进入表里的时候,它只是在一个临时的,错误的状态,因为总数还没有被更新。在添加数据的过程中读取总数就会读取到错误的信息。
|
||||
|
||||
@ -30,7 +30,7 @@ print("We're number \(one)!")
|
||||
>
|
||||
> 如果你写过并发和多线程的代码,内存访问冲突也许是同样的问题。然而,这里访问冲突的讨论是在单线程的情境下讨论的,并没有使用并发或者多线程。
|
||||
>
|
||||
> 如果你曾经在单线程代码里有访问冲突,Swift 可以保证你在编译或者运行时会得到错误。对于多线程的代码,可以使用 [Thread Sanitizer](https://developer.apple.com/documentation/code_diagnostics/thread_sanitizer) 去帮助检测多线程的冲突。
|
||||
> 如果你曾经在单线程代码里有访问冲突,Swift 可以保证你在编译或者运行时会得到错误。对于多线程的代码,可以使用 [Thread Sanitizer](https://developer.apple.com/documentation/xcode/diagnosing_memory_thread_and_crash_issues_early) 去帮助检测多线程的冲突。
|
||||
|
||||
### 内存访问性质 {#characteristics-of-memory-access}
|
||||
|
||||
@ -61,9 +61,9 @@ print(myNumber)
|
||||
|
||||
## In-Out 参数的访问冲突 {#conflicting-access-to-in-out-parameters}
|
||||
|
||||
一个函数会对它所有的 in-out 参数进行长期写访问。in-out 参数的写访问会在所有非 in-out 参数处理完之后开始,直到函数执行完毕为止。如果有多个 in-out 参数,则写访问开始的顺序与参数的顺序一致。
|
||||
一个函数会对它所有的 in-out 参数保持长期写访问。in-out 参数的写访问会在所有非 in-out 参数处理完之后开始,直到函数执行完毕为止。如果有多个 in-out 参数,则写访问开始的顺序与参数的顺序一致。
|
||||
|
||||
长期访问的存在会造成一个结果,你不能在访问以 in-out 形式传入后的原变量,即使作用域原则和访问权限允许——任何访问原变量的行为都会造成冲突。例如:
|
||||
这种长期保持的写访问带来的问题是,你不能再访问以 in-out 形式传入的原始变量,即使作用域原则和访问权限允许——任何访问原始变量的行为都会造成冲突。例如:
|
||||
|
||||
```swift
|
||||
var stepSize = 1
|
||||
@ -78,9 +78,9 @@ increment(&stepSize)
|
||||
|
||||
在上面的代码里,`stepSize` 是一个全局变量,并且它可以在 `increment(_:)` 里正常访问。然而,对于 `stepSize` 的读访问与 `number` 的写访问重叠了。就像下面展示的那样,`number` 和 `stepSize` 都指向了同一个存储地址。同一块内存的读和写访问重叠了,就此产生了冲突。
|
||||
|
||||

|
||||

|
||||
|
||||
解决这个冲突的一种方式,是显示拷贝一份 `stepSize` :
|
||||
解决这个冲突的一种方式,是显式拷贝一份 `stepSize` :
|
||||
|
||||
```swift
|
||||
// 显式拷贝
|
||||
@ -148,7 +148,7 @@ oscar.shareHealth(with: &maria) // 正常
|
||||
|
||||
上面的例子里,调用 `shareHealth(with:)` 方法去把 `oscar` 玩家的血量分享给 `maria` 玩家并不会造成冲突。在方法调用期间会对 `oscar` 发起写访问,因为在 mutating 方法里 `self` 就是 `oscar`,同时对于 `maria` 也会发起写访问,因为 `maria` 作为 in-out 参数传入。过程如下,它们会访问内存的不同位置。即使两个写访问重叠了,它们也不会冲突。
|
||||
|
||||

|
||||

|
||||
|
||||
当然,如果你将 `oscar` 作为参数传入 `shareHealth(with:)` 里,就会产生冲突:
|
||||
|
||||
@ -159,11 +159,11 @@ oscar.shareHealth(with: &oscar)
|
||||
|
||||
mutating 方法在调用期间需要对 `self` 发起写访问,而同时 in-out 参数也需要写访问。在方法里,`self` 和 `teammate` 都指向了同一个存储地址——就像下面展示的那样。对于同一块内存同时进行两个写访问,并且它们重叠了,就此产生了冲突。
|
||||
|
||||

|
||||

|
||||
|
||||
## 属性的访问冲突 {#conflicting-access-to-properties}
|
||||
|
||||
如结构体,元组和枚举的类型都是由多个独立的值组成的,例如结构体的属性或元组的元素。因为它们都是值类型,修改值的任何一部分都是对于整个值的修改,意味着其中一个属性的读或写访问都需要访问整一个值。例如,元组元素的写访问重叠会产生冲突:
|
||||
如结构体,元组和枚举的类型都是由多个独立的值组成的,例如结构体的属性或元组的元素。因为它们都是值类型,修改值的任何一部分都是对于整个值的修改,意味着其中一个属性的读或写访问都需要访问一整个值。例如,元组元素的写访问重叠会产生冲突:
|
||||
|
||||
```swift
|
||||
var playerInformation = (health: 10, energy: 20)
|
||||
@ -180,7 +180,7 @@ var holly = Player(name: "Holly", health: 10, energy: 10)
|
||||
balance(&holly.health, &holly.energy) // 错误
|
||||
```
|
||||
|
||||
在实践中,大多数对于结构体属性的访问都会安全的重叠。例如,将上面例子里的变量 `holly` 改为本地变量而非全局变量,编译器就会可以保证这个重叠访问是安全的:
|
||||
在实践中,大多数对于结构体属性的访问都会安全的重叠。例如,将上面例子里的变量 `holly` 改为本地变量而非全局变量,编译器就可以保证这个重叠访问是安全的:
|
||||
|
||||
```swift
|
||||
func someFunction() {
|
||||
@ -1,14 +1,14 @@
|
||||
# 访问控制
|
||||
|
||||
*访问控制*可以限定其它源文件或模块中的代码对你的代码的访问级别。这个特性可以让我们隐藏代码的一些实现细节,并且可以为其他人可以访问和使用的代码提供接口。
|
||||
*访问控制*可以限定其它源文件或模块对你的代码的访问。这个特性可以让你隐藏代码的实现细节,并且能提供一个接口来让别人访问和使用你的代码。
|
||||
|
||||
你可以明确地给单个类型(类、结构体、枚举)设置访问级别,也可以给这些类型的属性、方法、构造器、下标等设置访问级别。协议也可以被限定在一定的范围内使用,包括协议里的全局常量、变量和函数。
|
||||
你可以明确地给单个类型(类、结构体、枚举)设置访问级别,也可以给这些类型的属性、方法、构造器、下标等设置访问级别。协议也可以被限定在一定访问级别的范围内使用,包括协议里的全局常量、变量和函数。
|
||||
|
||||
Swift 不仅提供了多种不同的访问级别,还为某些典型场景提供了默认的访问级别,这样就不需要我们在每段代码中都申明显式访问级别。其实,如果只是开发一个单一 target 的应用程序,我们完全可以不用显式声明代码的访问级别。
|
||||
Swift 不仅提供了多种不同的访问级别,还为某些典型场景提供了默认的访问级别,这样就不需要我们在每段代码中都显式声明访问级别。如果你只是开发一个单 target 的应用程序,完全可以不用显式声明代码的访问级别。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 为了简单起见,对于代码中可以设置访问级别的特性(属性、基本类型、函数等),在下面的章节中我们会称之为“实体”。
|
||||
> 为了简单起见,对于代码中可以设置访问级别的特性(属性、基本类型、函数等),在下面的章节中我们会统一称之为“实体”。
|
||||
|
||||
## 模块和源文件 {#modules-and-source-files}
|
||||
|
||||
@ -16,29 +16,22 @@ Swift 中的访问控制模型基于模块和源文件这两个概念。
|
||||
|
||||
*模块*指的是独立的代码单元,框架或应用程序会作为一个独立的模块来构建和发布。在 Swift 中,一个模块可以使用 `import` 关键字导入另外一个模块。
|
||||
|
||||
在 Swift 中,Xcode 的每个 target(例如框架或应用程序)都被当作独立的模块处理。如果你是为了实现某个通用的功能,或者是为了封装一些常用方法而将代码打包成独立的框架,这个框架就是 Swift 中的一个模块。当它被导入到某个应用程序或者其他框架时,框架内容都将属于这个独立的模块。
|
||||
在 Swift 中,Xcode 的每个 target(例如框架或应用程序)都被当作独立的模块处理。如果你是为了实现某个通用的功能,或者是为了封装一些常用方法而将代码打包成独立的框架,这个框架就是 Swift 中的一个模块。当它被导入到某个应用程序或者其他框架时,框架的内容都将属于这个独立的模块。
|
||||
|
||||
*源文件*就是 Swift 中的源代码文件,它通常属于一个模块,即一个应用程序或者框架。尽管我们一般会将不同的类型分别定义在不同的源文件中,但是同一个源文件也可以包含多个类型、函数之类的定义。
|
||||
*源文件* 就是 Swift 模块中的源代码文件(实际上,源文件属于一个应用程序或框架)。尽管我们一般会将不同的类型分别定义在不同的源文件中,但是同一个源文件也可以包含多个类型、函数等的定义。
|
||||
|
||||
## 访问级别 {#access-levels}
|
||||
|
||||
Swift 为代码中的实体提供了五种不同的*访问级别*。这些访问级别不仅与源文件中定义的实体相关,同时也与源文件所属的模块相关。
|
||||
|
||||
- *Open* 和 *Public* 级别可以让实体被同一模块源文件中的所有实体访问,在模块外也可以通过导入该模块来访问源文件里的所有实体。通常情况下,你会使用 Open 或 Public 级别来指定框架的外部接口。Open 和 Public 的区别在后面会提到。
|
||||
- *Internal* 级别让实体被同一模块源文件中的任何实体访问,但是不能被模块外的实体访问。通常情况下,如果某个接口只在应用程序或框架内部使用,就可以将其设置为 Internal 级别。
|
||||
- *File-private* 限制实体只能在其定义的文件内部访问。如果功能的部分细节只需要在文件内使用时,可以使用 File-private 来将其隐藏。
|
||||
- *Private* 限制实体只能在其定义的作用域,以及同一文件内的 extension 访问。如果功能的部分细节只需要在当前作用域内使用时,可以使用 Private 来将其隐藏。
|
||||
- *open* 和 *public* 级别可以让实体被同一模块源文件中的所有实体访问,在模块外也可以通过导入该模块来访问源文件里的所有实体。通常情况下,你会使用 open 或 public 级别来指定框架的外部接口。open 和 public 的区别在后面会提到。
|
||||
- *internal* 级别让实体被同一模块源文件中的任何实体访问,但是不能被模块外的实体访问。通常情况下,如果某个接口只在应用程序或框架内部使用,就可以将其设置为 internal 级别。
|
||||
- *fileprivate* 限制实体只能在其定义的文件内部访问。如果功能的部分实现细节只需要在文件内使用时,可以使用 fileprivate 来将其隐藏。
|
||||
- *private* 限制实体只能在其定义的作用域,以及同一文件内的 extension 访问。如果功能的部分细节只需要在当前作用域内使用时,可以使用 private 来将其隐藏。
|
||||
|
||||
Open 为最高访问级别(限制最少),Private 为最低访问级别(限制最多)。
|
||||
open 为最高访问级别(限制最少),private 为最低访问级别(限制最多)。
|
||||
|
||||
Open 只能作用于类和类的成员,它和 Public 的区别如下:
|
||||
|
||||
* Public 或者其它更严访问级别的类,只能在其定义的模块内部被继承。
|
||||
* Public 或者其它更严访问级别的类成员,只能在其定义的模块内部的子类中重写。
|
||||
* Open 的类,可以在其定义的模块中被继承,也可以在引用它的模块中被继承。
|
||||
* Open 的类成员,可以在其定义的模块中子类中重写,也可以在引用它的模块中的子类重写。
|
||||
|
||||
把一个类标记为 `open`,明确的表示你已经充分考虑过外部模块使用此类作为父类的影响,并且设计好了你的类的代码了。
|
||||
open 只能作用于类和类的成员,它和 public 的区别主要在于 open 限定的类和成员能够在模块外被继承和重写,在下面的 [子类](#subclassing) 这一节中有详解。将类的访问级别显式指定为 `open` 表明你已经设计好了类的代码,并且充分考虑过这个类在其他模块中用作父类时的影响。
|
||||
|
||||
### 访问级别基本原则 {#guiding-principle-of-access-levels}
|
||||
|
||||
@ -46,22 +39,22 @@ Swift 中的访问级别遵循一个基本原则:*实体不能定义在具有
|
||||
|
||||
例如:
|
||||
|
||||
- 一个 Public 的变量,其类型的访问级别不能是 Internal,File-private 或是 Private。因为无法保证变量的类型在使用变量的地方也具有访问权限。
|
||||
- 一个 public 的变量,其类型的访问级别不能是 internal,fileprivate 或是 private。因为无法保证变量的类型在使用变量的地方也具有访问权限。
|
||||
- 函数的访问级别不能高于它的参数类型和返回类型的访问级别。因为这样就会出现函数可以在任何地方被访问,但是它的参数类型和返回类型却不可以的情况。
|
||||
|
||||
关于此原则在各种情况下的具体表现,将在下文有所体现。
|
||||
|
||||
### 默认访问级别 {#default-access-levels}
|
||||
|
||||
如果你没有为代码中的实体显式指定访问级别,那么它们默认为 `internal` 级别(有一些例外情况,稍后会进行说明)。因此,在大多数情况下,我们不需要显式指定实体的访问级别。
|
||||
你代码中所有的实体,如果你不显式的指定它们的访问级别,那么它们将都有一个 `internal` 的默认访问级别,(有一些例外情况,本文稍后会有说明)。因此,多数情况下你不需要显式指定实体的访问级别。
|
||||
|
||||
### 单 target 应用程序的访问级别 {#access-levels-for-single-target-apps}
|
||||
|
||||
当你编写一个单目标应用程序时,应用的所有功能都是为该应用服务,而不需要提供给其他应用或者模块使用,所以我们不需要明确设置访问级别,使用默认的访问级别 Internal 即可。但是,你也可以使用 `fileprivate` 访问或 `private` 访问级别,用于隐藏一些功能的实现细节。
|
||||
当你编写一个单 target 应用程序时,应用的所有功能都是为该应用服务,而不需要提供给其他应用或者模块使用,所以你不需要明确设置访问级别,使用默认的访问级别 internal 即可。但是,你也可以使用 `fileprivate` 或 `private` 访问级别,用于隐藏一些功能的实现细节。
|
||||
|
||||
### 框架的访问级别 {#access-levels-for-frameworks}
|
||||
|
||||
当你开发框架时,就需要把一些对外的接口定义为 Open 或 Public,以便使用者导入该框架后可以正常使用其功能。这些被你定义为对外的接口,就是这个框架的 API。
|
||||
当你开发框架时,就需要把一些对外的接口定义为 open 或 public 访问级别,以便使用者导入该框架后可以正常使用其功能。这些被你定义为对外的接口,就是这个框架的 API。
|
||||
|
||||
> 注意
|
||||
>
|
||||
@ -87,7 +80,7 @@ fileprivate func someFilePrivateFunction() {}
|
||||
private func somePrivateFunction() {}
|
||||
```
|
||||
|
||||
除非专门指定,否则实体默认的访问级别为 `internal`,可以查阅[默认访问级别](#default_access_levels)这一节。这意味着在不使用修饰符显式声明访问级别的情况下,`SomeInternalClass` 和 `someInternalConstant` 仍然拥有隐式的 `internal`:
|
||||
除非专门指定,否则实体默认的访问级别为 `internal`,可以查阅 [默认访问级别](#default-access-levels) 这一节。这意味着在不使用修饰符显式声明访问级别的情况下,`SomeInternalClass` 和 `someInternalConstant` 的访问级别是 `internal`:
|
||||
|
||||
```swift
|
||||
class SomeInternalClass {} // 隐式 internal
|
||||
@ -96,7 +89,7 @@ var someInternalConstant = 0 // 隐式 internal
|
||||
|
||||
## 自定义类型 {#custom-types}
|
||||
|
||||
如果想为一个自定义类型指定访问级别,在定义类型时进行指定即可。新类型只能在它的访问级别限制范围内使用。例如,你定义了一个 `fileprivate` 级别的类,那这个类就只能在定义它的源文件中使用,可以作为属性类型、函数参数类型或者返回类型,等等。
|
||||
如果想为一个自定义类型指定访问级别,在定义类型时进行指定即可。新类型只能在它的访问级别限制范围内使用。例如,你定义了一个 `fileprivate` 级别的类,那这个类就只能在定义它的源文件中使用,可以作为属性类型、函数参数类型或者返回类型等等。
|
||||
|
||||
一个类型的访问级别也会影响到类型*成员*(属性、方法、构造器、下标)的默认访问级别。如果你将类型指定为 `private` 或者 `fileprivate` 级别,那么该类型的所有成员的默认访问级别也会变成 `private` 或者 `fileprivate` 级别。如果你将类型指定为 `internal` 或 `public`(或者不明确指定访问级别,而使用默认的 `internal` ),那么该类型的所有成员的默认访问级别将是 `internal`。
|
||||
|
||||
@ -127,13 +120,15 @@ private class SomePrivateClass { // 显式 private 类
|
||||
func somePrivateMethod() {} // 隐式 private 类成员
|
||||
}
|
||||
```
|
||||
|
||||
### 元组类型 {#tuple-types}
|
||||
|
||||
元组的访问级别将由元组中访问级别最严格的类型来决定。例如,如果你构建了一个包含两种不同类型的元组,其中一个类型为 `internal`,另一个类型为 `private`,那么这个元组的访问级别为 `private`。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 元组不同于类、结构体、枚举、函数那样有单独的定义。元组的访问级别是在它被使用时自动推断出的,而无法明确指定。
|
||||
> 元组不同于类、结构体、枚举、函数那样有单独的定义。一个元组的访问级别是由元组中元素的访问级别来决定的,不能被显式指定。
|
||||
|
||||
|
||||
### 函数类型 {#function-types}
|
||||
|
||||
@ -147,9 +142,9 @@ func someFunction() -> (SomeInternalClass, SomePrivateClass) {
|
||||
}
|
||||
```
|
||||
|
||||
我们可以看到,这个函数的返回类型是一个元组,该元组中包含两个自定义的类(可查阅[自定义类型](#custom_types))。其中一个类的访问级别是 `internal`,另一个的访问级别是 `private`,所以根据元组访问级别的原则,该元组的访问级别是 `private`(元组的访问级别与元组中访问级别最低的类型一致)。
|
||||
我们可以看到,这个函数的返回类型是一个元组,该元组中包含两个自定义的类(可查阅 [自定义类型](#custom-types))。其中一个类的访问级别是 `internal`,另一个的访问级别是 `private`,所以根据元组访问级别的原则,该元组的访问级别是 `private`(元组的访问级别与元组中访问级别最低的类型一致)。
|
||||
|
||||
因为该函数返回类型的访问级别是 `private`,所以你必须使用 `private` 修饰符,明确指定该函数的访问级别:
|
||||
因为该函数返回类型的访问级别是 `private`,所以你必须使用 `private` 修饰符来明确指定该函数的访问级别:
|
||||
|
||||
```swift
|
||||
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
|
||||
@ -163,7 +158,7 @@ private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
|
||||
|
||||
枚举成员的访问级别和该枚举类型相同,你不能为枚举成员单独指定不同的访问级别。
|
||||
|
||||
比如下面的例子,枚举 `CompassPoint` 被明确指定为 `public`,那么它的成员 `North`、`South`、`East`、`West` 的访问级别同样也是 `public`:
|
||||
比如下面的例子,枚举 `CompassPoint` 被明确指定为 `public`,那么它的成员 `north`、`south`、`east`、`west` 的访问级别同样也是 `public`:
|
||||
|
||||
```swift
|
||||
public enum CompassPoint {
|
||||
@ -180,15 +175,15 @@ public enum CompassPoint {
|
||||
|
||||
### 嵌套类型 {#nested-types}
|
||||
|
||||
如果在 `private` 的类型中定义嵌套类型,那么该嵌套类型就自动拥有 `private` 访问级别。如果在 `public` 或者 `internal` 级别的类型中定义嵌套类型,那么该嵌套类型自动拥有 `internal` 访问级别。如果想让嵌套类型拥有 `public` 访问级别,那么需要明确指定该嵌套类型的访问级别。
|
||||
嵌套类型的访问级别和包含它的类型的访问级别相同,嵌套类型是 public 的情况除外。在一个 public 的类型中定义嵌套类型,那么嵌套类型自动拥有 `internal` 的访问级别。如果你想让嵌套类型拥有 `public` 访问级别,那么必须显式指定该嵌套类型的访问级别为 public。
|
||||
|
||||
## 子类 {#subclassing}
|
||||
|
||||
子类的访问级别不得高于父类的访问级别。例如,父类的访问级别是 `internal`,子类的访问级别就不能是 `public`。
|
||||
你可以继承同一模块中的所有有访问权限的类,也可以继承不同模块中被 open 修饰的类。一个子类的访问级别不得高于父类的访问级别。例如,父类的访问级别是 `internal`,子类的访问级别就不能是 `public`。
|
||||
|
||||
此外,你可以在符合当前访问级别的条件下重写任意类成员(方法、属性、构造器、下标等)。
|
||||
此外,在同一模块中,你可以在符合当前访问级别的条件下重写任意类成员(方法、属性、构造器、下标等)。在不同模块中,你可以重写类中被 open 修饰的成员。
|
||||
|
||||
可以通过重写为继承来的类成员提供更高的访问级别。下面的例子中,类 `A` 的访问级别是 `public`,它包含一个方法 `someMethod()`,访问级别为 `private`。类 `B` 继承自类 `A`,访问级别为 `internal`,但是在类 `B` 中重写了类 `A` 中访问级别为 `private` 的方法 `someMethod()`,并重新指定为 `internal` 级别。通过这种方式,我们就可以将某类中 `private` 级别的类成员重新指定为更高的访问级别,以便其他人使用:
|
||||
可以通过重写给所继承类的成员提供更高的访问级别。下面的例子中,类 `A` 的访问级别是 `public`,它包含一个方法 `someMethod()`,访问级别为 `fileprivate`。类 `B` 继承自类 `A`,访问级别为 `internal`,但是在类 `B` 中重写了类 `A` 中访问级别为 `fileprivate` 的方法 `someMethod()`,并重新指定为 `internal` 级别。通过这种方式,我们就可以将某类中 `fileprivate` 级别的类成员重新指定为更高的访问级别,以便其他人使用:
|
||||
|
||||
```swift
|
||||
public class A {
|
||||
@ -200,7 +195,7 @@ internal class B: A {
|
||||
}
|
||||
```
|
||||
|
||||
我们甚至可以在子类中,用子类成员去访问访问级别更低的父类成员,只要这一操作在相应访问级别的限制范围内(也就是说,在同一源文件中访问父类 `private` 级别的成员,在同一模块内访问父类 `internal` 级别的成员):
|
||||
我们甚至可以在子类中,用子类成员去访问访问级别更低的父类成员,只要这一操作在相应访问级别的限制范围内(也就是说,在同一源文件中访问父类 `fileprivate` 级别的成员,在同一模块内访问父类 `internal` 级别的成员):
|
||||
|
||||
```swift
|
||||
public class A {
|
||||
@ -234,7 +229,7 @@ private var privateInstance = SomePrivateClass()
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 这个规则同时适用于存储型属性和计算型属性。即使你不明确指定存储型属性的 `Getter` 和 `Setter`,Swift 也会隐式地为其创建 `Getter` 和 `Setter`,用于访问该属性的后备存储。使用 `fileprivate(set)`,`private(set)` 和 `internal(set)` 可以改变 `Setter` 的访问级别,这对计算型属性也同样适用。
|
||||
> 这个规则同时适用于存储型属性和计算型属性。即使你不明确指定存储型属性的 `Getter` 和 `Setter`,Swift 也会隐式地为其创建 `Getter` 和 `Setter`,用于访问该属性的存储内容。使用 `fileprivate(set)`,`private(set)` 和 `internal(set)` 可以改变 `Setter` 的访问级别,这对计算型属性也同样适用。
|
||||
|
||||
下面的例子中定义了一个名为 `TrackedString` 的结构体,它记录了 `value` 属性被修改的次数:
|
||||
|
||||
@ -282,13 +277,13 @@ public struct TrackedString {
|
||||
|
||||
## 构造器 {#initializers}
|
||||
|
||||
自定义构造器的访问级别可以低于或等于其所属类型的访问级别。唯一的例外是[必要构造器](./14_Initialization.md#required_initializers),它的访问级别必须和所属类型的访问级别相同。
|
||||
自定义构造器的访问级别可以低于或等于其所属类型的访问级别。唯一的例外是 [必要构造器](./14_Initialization.md#required-initializers),它的访问级别必须和所属类型的访问级别相同。
|
||||
|
||||
如同函数或方法的参数,构造器参数的访问级别也不能低于构造器本身的访问级别。
|
||||
|
||||
### 默认构造器 {#default-initializers}
|
||||
|
||||
如[默认构造器](./14_Initialization.md#default_initializers)所述,Swift 会为结构体和类提供一个默认的无参数的构造器,只要它们为所有存储型属性设置了默认初始值,并且未提供自定义的构造器。
|
||||
如 [默认构造器](./14_Initialization.md#default-initializers) 所述,Swift 会为结构体和类提供一个默认的无参数的构造器,只要它们为所有存储型属性设置了默认初始值,并且未提供自定义的构造器。
|
||||
|
||||
默认构造器的访问级别与所属类型的访问级别相同,除非类型的访问级别是 `public`。如果一个类型被指定为 `public` 级别,那么默认构造器的访问级别将为 `internal`。如果你希望一个 `public` 级别的类型也能在其他模块中使用这种无参数的默认构造器,你只能自己提供一个 `public` 访问级别的无参数构造器。
|
||||
|
||||
@ -300,17 +295,17 @@ public struct TrackedString {
|
||||
|
||||
## 协议 {#protocols}
|
||||
|
||||
如果想为一个协议类型明确地指定访问级别,在定义协议时指定即可。这将限制该协议只能在适当的访问级别范围内被遵循。
|
||||
如果想为一个协议类型明确地指定访问级别,在声明协议时指定即可。这将限制该协议只能在适当的访问级别范围内被遵循。
|
||||
|
||||
协议中的每一个要求都具有和该协议相同的访问级别。你不能将协议中的要求设置为其他访问级别。这样才能确保该协议的所有要求对于任意遵循者都将可用。
|
||||
协议中的每个方法或属性都必须具有和该协议相同的访问级别。你不能将协议中的方法或属性设置为其他访问级别。这样才能确保该协议的所有方法或属性对于任意遵循者都可用。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果你定义了一个 `public` 访问级别的协议,那么该协议的所有实现也会是 `public` 访问级别。这一点不同于其他类型,例如,当类型是 `public` 访问级别时,其成员的访问级别却只是 `internal`。
|
||||
> 如果你定义了一个 `public` 访问级别的协议,那么该协议的所有实现也会是 `public` 访问级别。这一点不同于其他类型,例如,类型是 `public` 访问级别时,其成员的访问级别却只是 `internal`。
|
||||
|
||||
### 协议继承 {#protocol-inheritance}
|
||||
|
||||
如果定义了一个继承自其他协议的新协议,那么新协议拥有的访问级别最高也只能和被继承协议的访问级别相同。例如,你不能将继承自 `internal` 协议的新协议定义为 `public` 协议。
|
||||
如果定义了一个继承自其他协议的新协议,那么新协议拥有的访问级别最高也只能和被继承协议的访问级别相同。例如,你不能将继承自 `internal` 协议的新协议访问级别指定为 `public` 。
|
||||
|
||||
### 协议遵循 {#protocol-conformance}
|
||||
|
||||
@ -326,9 +321,9 @@ public struct TrackedString {
|
||||
|
||||
## Extension {#extensions}
|
||||
|
||||
Extension 可以在访问级别允许的情况下对类、结构体、枚举进行扩展。Extension 的成员具有和原始类型成员一致的访问级别。例如,你使用 extension 扩展了一个 `public` 或者 `internal` 类型,extension 中的成员就默认使用 `internal` 访问级别,和原始类型中的成员一致。如果你使用 extension 扩展了一个 `private` 类型,则 extension 的成员默认使用 `private` 访问级别。
|
||||
Extension 可以在访问级别允许的情况下对类、结构体、枚举进行扩展。Extension 的新增成员具有和原始类型成员一致的访问级别。例如,你使用 extension 扩展了一个 `public` 或者 `internal` 类型,则 extension 中的成员就默认使用 `internal` 访问级别。如果你使用 extension 扩展一个 `fileprivate` 类型,则 extension 中的成员默认使用 `fileprivate` 访问级别。如果你使用 extension 扩展了一个 `private` 类型,则 extension 的成员默认使用 `private` 访问级别。
|
||||
|
||||
或者,你可以明确指定 extension 的访问级别(例如,`private extension`),从而给该 extension 中的所有成员指定一个新的默认访问级别。这个新的默认访问级别仍然可以被单独指定的访问级别所覆盖。
|
||||
或者,你可以通过修饰语重新指定 extension 的默认访问级别(例如,`private`),从而给该 extension 中的所有成员指定一个新的默认访问级别。这个新的默认访问级别仍然可以被单独成员指定的访问级别所覆盖。
|
||||
|
||||
如果你使用 extension 来遵循协议的话,就不能显式地声明 extension 的访问级别。extension 每个 protocol 要求的实现都默认使用 protocol 的访问级别。
|
||||
|
||||
@ -340,7 +335,7 @@ Extension 可以在访问级别允许的情况下对类、结构体、枚举进
|
||||
- 在 extension 里声明一个私有成员,在同一文件的另一个 extension 里访问。
|
||||
- 在 extension 里声明一个私有成员,在同一文件的类型声明里访问。
|
||||
|
||||
这意味着你可以像组织的代码去使用 extension,而且不受私有成员的影响。例如,给定下面这样一个简单的协议:
|
||||
这意味着你可以使用 extension 来组织你的代码,而且不受私有成员的影响。例如,给定下面这样一个简单的协议:
|
||||
|
||||
```swift
|
||||
protocol SomeProtocol {
|
||||
@ -368,7 +363,7 @@ extension SomeStruct: SomeProtocol {
|
||||
|
||||
## 类型别名 {#type-aliases}
|
||||
|
||||
你定义的任何类型别名都会被当作不同的类型,以便于进行访问控制。类型别名的访问级别不可高于其表示的类型的访问级别。例如,`private` 级别的类型别名可以作为 `private`、`file-private`、`internal`、`public` 或者 `open` 类型的别名,但是 `public` 级别的类型别名只能作为 `public` 类型的别名,不能作为 `internal`、`file-private` 或 `private` 类型的别名。
|
||||
你定义的任何类型别名都会被当作不同的类型,以便于进行访问控制。类型别名的访问级别不可高于其表示的类型的访问级别。例如,`private` 级别的类型别名可以作为 `private`、`fileprivate`、`internal`、`public` 或者 `open` 类型的别名,但是 `public` 级别的类型别名只能作为 `public` 类型的别名,不能作为 `internal`、`fileprivate` 或 `private` 类型的别名。
|
||||
|
||||
> 注意
|
||||
>
|
||||
@ -1,6 +1,6 @@
|
||||
# 高级运算符
|
||||
|
||||
除了之前介绍过的[基本运算符](./02_Basic_Operators.md),Swift 还提供了数种可以对数值进行复杂运算的高级运算符。它们包含了在 C 和 Objective-C 中已经被大家所熟知的位运算符和移位运算符。
|
||||
除了之前介绍过的 [基本运算符](./02_Basic_Operators.md),Swift 还提供了数种可以对数值进行复杂运算的高级运算符。它们包含了在 C 和 Objective-C 中已经被大家所熟知的位运算符和移位运算符。
|
||||
|
||||
与 C 语言中的算术运算符不同,Swift 中的算术运算符默认是不会溢出的。所有溢出行为都会被捕获并报告为错误。如果想让系统允许溢出行为,可以选择使用 Swift 中另一套默认支持溢出的运算符,比如溢出加法运算符(`&+`)。所有的这些溢出运算符都是以 `&` 开头的。
|
||||
|
||||
@ -16,9 +16,9 @@ Swift 支持 C 语言中的全部位运算符,接下来会一一介绍。
|
||||
|
||||
### Bitwise NOT Operator(按位取反运算符) {#bitwise-not-operator}
|
||||
|
||||
*按位取反运算符(`~`)*对一个数值的全部比特位进行取反:
|
||||
*按位取反运算符(`~`)* 对一个数值的全部比特位进行取反:
|
||||
|
||||

|
||||

|
||||
|
||||
按位取反运算符是一个前缀运算符,直接放在运算数之前,并且它们之间不能添加任何空格:
|
||||
|
||||
@ -35,7 +35,7 @@ let invertedBits = ~initialBits // 等于 0b11110000
|
||||
|
||||
*按位与运算符(`&`)* 对两个数的比特位进行合并。它返回一个新的数,只有当两个数的对应位*都*为 `1` 的时候,新数的对应位才为 `1`:
|
||||
|
||||

|
||||

|
||||
|
||||
在下面的示例当中,`firstSixBits` 和 `lastSixBits` 中间 4 个位的值都为 `1`。使用按位与运算符之后,得到二进制数值 `00111100`,等价于无符号十进制数的 `60`:
|
||||
|
||||
@ -49,7 +49,7 @@ let middleFourBits = firstSixBits & lastSixBits // 等于 00111100
|
||||
|
||||
*按位或运算符(`|`)*可以对两个数的比特位进行比较。它返回一个新的数,只要两个数的对应位中有*任意一个*为 `1` 时,新数的对应位就为 `1`:
|
||||
|
||||

|
||||

|
||||
|
||||
在下面的示例中,`someBits` 和 `moreBits` 存在不同的位被设置为 `1`。使用按位或运算符之后,得到二进制数值 `11111110`,等价于无符号十进制数的 `254`:
|
||||
|
||||
@ -63,7 +63,7 @@ let combinedbits = someBits | moreBits // 等于 11111110
|
||||
|
||||
*按位异或运算符*,或称“排外的或运算符”(`^`),可以对两个数的比特位进行比较。它返回一个新的数,当两个数的对应位不相同时,新数的对应位就为 `1`,并且对应位相同时则为 `0`:
|
||||
|
||||

|
||||

|
||||
|
||||
在下面的示例当中,`firstBits` 和 `otherBits` 都有一个自己为 `1`,而对方为 `0` 的位。按位异或运算符将新数的这两个位都设置为 `1`。在其余的位上 `firstBits` 和 `otherBits` 是相同的,所以设置为 `0`:
|
||||
|
||||
@ -75,7 +75,7 @@ let outputBits = firstBits ^ otherBits // 等于 00010001
|
||||
|
||||
### Bitwise Left and Right Shift Operators(按位左移、右移运算符) {#bitwise-left-and-right-shift-operators}
|
||||
|
||||
*按位左移运算符(`<<`)* 和 *按位右移运算符(`>>`)*可以对一个数的所有位进行指定位数的左移和右移,但是需要遵守下面定义的规则。
|
||||
*按位左移运算符(`<<`)* 和 *按位右移运算符(`>>`)* 可以对一个数的所有位进行指定位数的左移和右移,但是需要遵守下面定义的规则。
|
||||
|
||||
对一个数进行按位左移或按位右移,相当于对这个数进行乘以 2 或除以 2 的运算。将一个整数左移一位,等价于将这个数乘以 2,同样地,将一个整数右移一位,等价于将这个数除以 2。
|
||||
|
||||
@ -91,7 +91,7 @@ let outputBits = firstBits ^ otherBits // 等于 00010001
|
||||
|
||||
以下这张图展示了 `11111111 << 1`(即把 `11111111` 向左移动 `1` 位),和 `11111111 >> 1`(即把 `11111111` 向右移动 `1` 位)的结果。蓝色的数字是被移位的,灰色的数字是被抛弃的,橙色的 `0` 则是被填充进来的:
|
||||
|
||||

|
||||

|
||||
|
||||
下面的代码演示了 Swift 中的移位运算:
|
||||
|
||||
@ -121,7 +121,7 @@ let blueComponent = pink & 0x0000FF // blueComponent 是 0x99,即 153
|
||||
|
||||
同样的,绿色部分通过对 `0xCC6699` 和 `0x00FF00` 进行按位与运算得到 `0x006600`。然后将这个数向右移动 8 位,得到 `0x66`,也就是十进制数值的 `102`。
|
||||
|
||||
最后,蓝色部分通过对 `0xCC6699` 和 `0x0000FF` 进行按位与运算得到 `0x000099`。这里不需要再向右移位,而 `0x000099` 也就是 `0x99` ,也就是十进制数值的 `153`。
|
||||
最后,蓝色部分通过对 `0xCC6699` 和 `0x0000FF` 进行按位与运算得到 `0x000099`。因为 `0x000099` 已经等于 `0x99` ,也就是十进制数值的 `153`,所以这个值就不需要再向右移位了。
|
||||
|
||||
#### 有符号整数的移位运算 {#shifting-behavior-for-signed-integers}
|
||||
|
||||
@ -131,7 +131,7 @@ let blueComponent = pink & 0x0000FF // blueComponent 是 0x99,即 153
|
||||
|
||||
其余的比特位(通常被称为*数值位*)存储了实际的值。有符号正整数和无符号数的存储方式是一样的,都是从 `0` 开始算起。这是值为 `4` 的 `Int8` 型整数的二进制位表现形式:
|
||||
|
||||

|
||||

|
||||
|
||||
符号位为 `0`(代表这是一个“正数”),另外 7 位则代表了十进制数值 `4` 的二进制表示。
|
||||
|
||||
@ -139,21 +139,21 @@ let blueComponent = pink & 0x0000FF // blueComponent 是 0x99,即 153
|
||||
|
||||
这是值为 `-4` 的 `Int8` 型整数的二进制表现形式:
|
||||
|
||||

|
||||

|
||||
|
||||
这次的符号位为 `1`,说明这是一个负数,另外 7 个位则代表了数值 `124`(即 `128 - 4`)的二进制表示:
|
||||
|
||||

|
||||

|
||||
|
||||
负数的表示通常被称为*二进制补码*。用这种方法来表示负数乍看起来有点奇怪,但它有几个优点。
|
||||
|
||||
首先,如果想对 `-1` 和 `-4` 进行加法运算,我们只需要对这两个数的全部 8 个比特位执行标准的二进制相加(包括符号位),并且将计算结果中超出 8 位的数值丢弃:
|
||||
|
||||

|
||||

|
||||
|
||||
其次,使用二进制补码可以使负数的按位左移和右移运算得到跟正数同样的效果,即每向左移一位就将自身的数值乘以 2,每向右一位就将自身的数值除以 2。要达到此目的,对有符号整数的右移有一个额外的规则:当对有符号整数进行按位右移运算时,遵循与无符号整数相同的规则,但是对于移位产生的空白位使用*符号位*进行填充,而不是用 `0`。
|
||||
其次,使用二进制补码可以使负数的按位左移和右移运算得到跟正数同样的效果,即每向左移一位就将自身的数值乘以 2,每向右移一位就将自身的数值除以 2。要达到此目的,对有符号整数的右移有一个额外的规则:当对有符号整数进行按位右移运算时,遵循与无符号整数相同的规则,但是对于移位产生的空白位使用*符号位*进行填充,而不是用 `0`。
|
||||
|
||||

|
||||

|
||||
|
||||
这个行为可以确保有符号整数的符号位不会因为右移运算而改变,这通常被称为*算术移位*。
|
||||
|
||||
@ -195,7 +195,7 @@ unsignedOverflow = unsignedOverflow &+ 1
|
||||
|
||||
`unsignedOverflow` 被初始化为 `UInt8` 所能容纳的最大整数(`255`,以二进制表示即 `11111111`)。然后使用溢出加法运算符(`&+`)对其进行加 `1` 运算。这使得它的二进制表示正好超出 `UInt8` 所能容纳的位数,也就导致了数值的溢出,如下图所示。数值溢出后,仍然留在 `UInt8` 边界内的值是 `00000000`,也就是十进制数值的 `0`。
|
||||
|
||||

|
||||

|
||||
|
||||
当允许对一个无符号整数进行下溢运算时也会产生类似的情况。这里有一个使用溢出减法运算符(`&-`)的例子:
|
||||
|
||||
@ -208,9 +208,9 @@ unsignedOverflow = unsignedOverflow &- 1
|
||||
|
||||
`UInt8` 型整数能容纳的最小值是 `0`,以二进制表示即 `00000000`。当使用溢出减法运算符对其进行减 `1` 运算时,数值会产生下溢并被截断为 `11111111`, 也就是十进制数值的 `255`。
|
||||
|
||||

|
||||

|
||||
|
||||
溢出也会发生在有符号整型上。针对有符号整型的所有溢出加法或者减法运算都是按位运算的方式执行的,符号位也需要参与计算,正如[按位左移、右移运算符](#bitwise_left_and_right_shift_operators)所描述的。
|
||||
溢出也会发生在有符号整型上。针对有符号整型的所有溢出加法或者减法运算都是按位运算的方式执行的,符号位也需要参与计算,正如 [按位左移、右移运算符](#bitwise-left-and-right-shift-operators) 所描述的。
|
||||
|
||||
```Swift
|
||||
var signedOverflow = Int8.min
|
||||
@ -219,9 +219,9 @@ signedOverflow = signedOverflow &- 1
|
||||
// 此时 signedOverflow 等于 127
|
||||
```
|
||||
|
||||
`Int8` 型整数能容纳的最小值是 `-128`,以二进制表示即 `10000000`。当使用溢出减法运算符对其进行减 `1` 运算时,符号位被翻转,得到二进制数值 `01111111`,也就是十进制数值的 `127`,这个值也是 `Int8` 型整所能容纳的最大值。
|
||||
`Int8` 型整数能容纳的最小值是 `-128`,以二进制表示即 `10000000`。当使用溢出减法运算符对其进行减 `1` 运算时,符号位被翻转,得到二进制数值 `01111111`,也就是十进制数值的 `127`,这个值也是 `Int8` 型整数所能容纳的最大值。
|
||||
|
||||

|
||||

|
||||
|
||||
对于无符号与有符号整型数值来说,当出现上溢时,它们会从数值所能容纳的最大数变成最小数。同样地,当发生下溢时,它们会从所能容纳的最小数变成最大数。
|
||||
|
||||
@ -266,7 +266,7 @@ signedOverflow = signedOverflow &- 1
|
||||
|
||||
因此计算结果为 `17`。
|
||||
|
||||
有关 Swift 标准库提供的操作符信息,包括操作符优先级组和结核性设置的完整列表,请参见[操作符声明](https://developer.apple.com/documentation/swift/operator_declarations)。
|
||||
有关 Swift 标准库提供的操作符信息,包括操作符优先级组和结合性设置的完整列表,请参见 [操作符声明](https://developer.apple.com/documentation/swift/operator_declarations)。
|
||||
|
||||
> 注意
|
||||
>
|
||||
@ -276,7 +276,7 @@ signedOverflow = signedOverflow &- 1
|
||||
|
||||
类和结构体可以为现有的运算符提供自定义的实现。这通常被称为运算符*重载*。
|
||||
|
||||
下面的例子展示了如何让自定义的结构体支持加法运算符(`+`)。算术加法运算符是一个*二元运算符*,因为它是对两个值进行运算,同时它还可以称为*中缀*运算符,因为它出现在两个值中间。
|
||||
下面的例子展示了如何让自定义的结构体支持加法运算符(`+`)。算术加法运算符是一个二元运算符,因为它是对两个值进行运算,同时它还可以称为中缀运算符,因为它出现在两个值中间。
|
||||
|
||||
例子中定义了一个名为 `Vector2D` 的结构体用来表示二维坐标向量 `(x, y)`,紧接着定义了一个可以将两个 `Vector2D` 结构体实例进行相加的*运算符函数*:
|
||||
|
||||
@ -307,7 +307,7 @@ let combinedVector = vector + anotherVector
|
||||
|
||||
这个例子实现两个向量 `(3.0,1.0)` 和 `(2.0,4.0)` 的相加,并得到新的向量 `(5.0,5.0)`。这个过程如下图示:
|
||||
|
||||

|
||||

|
||||
|
||||
### 前缀和后缀运算符 {#prefix-and-postfix-operators}
|
||||
|
||||
@ -389,32 +389,11 @@ if twoThree == anotherTwoThree {
|
||||
// 打印“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.”
|
||||
```
|
||||
多数简单情况下,你可以让 Swift 合成等价运算符的实现,详见 [使用合成实现来采纳协议](./21_Protocols.md#adopting-a-protocol-using-a-synthesized-implementation)。
|
||||
|
||||
## 自定义运算符 {#custom-operators}
|
||||
|
||||
除了实现标准运算符,在 Swift 中还可以声明和实现*自定义运算符*。可以用来自定义运算符的字符列表请参考[运算符](../chapter3/02_Lexical_Structure.html#operators)。
|
||||
除了实现标准运算符,在 Swift 中还可以声明和实现*自定义运算符*。可以用来自定义运算符的字符列表请参考 [运算符](../03_language_reference/02_Lexical_Structure.md#operators)。
|
||||
|
||||
新的运算符要使用 `operator` 关键字在全局作用域内进行定义,同时还要指定 `prefix`、`infix` 或者 `postfix` 修饰符:
|
||||
|
||||
@ -440,7 +419,7 @@ let afterDoubling = +++toBeDoubled
|
||||
|
||||
### 自定义中缀运算符的优先级 {#precedence-and-associativity-for-custom-infix-operators}
|
||||
|
||||
每个自定义中缀运算符都属于某个优先级组。优先级组指定了这个运算符相对于其他中缀运算符的优先级和结合性。[优先级和结合性](#precedence_and_associativity)中详细阐述了这两个特性是如何对中缀运算符的运算产生影响的。
|
||||
每个自定义中缀运算符都属于某个优先级组。优先级组指定了这个运算符相对于其他中缀运算符的优先级和结合性。[优先级和结合性](#precedence-and-associativity) 中详细阐述了这两个特性是如何对中缀运算符的运算产生影响的。
|
||||
|
||||
而没有明确放入某个优先级组的自定义中缀运算符将会被放到一个默认的优先级组内,其优先级高于三元运算符。
|
||||
|
||||
@ -459,9 +438,155 @@ 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)。
|
||||
这个运算符把两个向量的 `x` 值相加,同时从第一个向量的 `y` 中减去第二个向量的 `y` 。因为它本质上是属于“相加型”运算符,所以将它放置在 `+` 和 `-` 等默认中缀“相加型”运算符相同的优先级组中。关于 Swift 标准库提供的运算符,以及完整的运算符优先级组和结合性设置,请参考 [运算符声明](https://developer.apple.com/documentation/swift/operator_declarations)。而更多关于优先级组以及自定义操作符和优先级组的语法,请参考 [运算符声明](../03_language_reference/06_Declarations.md#operator-declaration)。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 当定义前缀与后缀运算符的时候,我们并没有指定优先级。然而,如果对同一个值同时使用前缀与后缀运算符,则后缀运算符会先参与运算。
|
||||
|
||||
## 结果构造器 {#result-builder}
|
||||
|
||||
*结果构造器*是一种自定义类型,支持添加自然的声明式语法来创建类似列表或者树这样的嵌套数据。使用结果构造器的代码可以包含普通的 Swift 语法,例如用来处理判断条件的 `if`,或者处理重复数据的 `for`。
|
||||
|
||||
下面的代码定义了一些类型用于绘制星星线段和文字线段。
|
||||
|
||||
```swift
|
||||
protocol Drawable {
|
||||
func draw() -> String
|
||||
}
|
||||
struct Line: Drawable {
|
||||
var elements: [Drawable]
|
||||
func draw() -> String {
|
||||
return elements.map { $0.draw() }.joined(separator: "")
|
||||
}
|
||||
}
|
||||
struct Text: Drawable {
|
||||
var content: String
|
||||
init(_ content: String) { self.content = content }
|
||||
func draw() -> String { return content }
|
||||
}
|
||||
struct Space: Drawable {
|
||||
func draw() -> String { return " " }
|
||||
}
|
||||
struct Stars: Drawable {
|
||||
var length: Int
|
||||
func draw() -> String { return String(repeating: "*", count: length) }
|
||||
}
|
||||
struct AllCaps: Drawable {
|
||||
var content: Drawable
|
||||
func draw() -> String { return content.draw().uppercased() }
|
||||
}
|
||||
```
|
||||
|
||||
`Drawable` 协议定义了绘制所需要遵循的方法,例如线或者形状都需要实现 `draw()` 方法。`Line` 结构体用来表示单行线段绘制,给大多数可绘制的元素提供了顶层容器。绘制 `Line` 时,调用了线段中每个元素的 `draw()`,然后将所有结果字符串连成单个字符串。`Text` 结构体包装了一个字符串作为绘制的一部分。`AllCaps` 结构体包装另一个可绘制元素,并将元素中所有文本转换为大写。
|
||||
|
||||
可以组合这些类型的构造器来创建一个可绘制元素。
|
||||
|
||||
```swift
|
||||
let name: String? = "Ravi Patel"
|
||||
let manualDrawing = Line(elements: [
|
||||
Stars(length: 3),
|
||||
Text("Hello"),
|
||||
Space(),
|
||||
AllCaps(content: Text((name ?? "World") + "!")),
|
||||
Stars(length: 2),
|
||||
])
|
||||
print(manualDrawing.draw())
|
||||
// 打印 "***Hello RAVI PATEL!**"
|
||||
```
|
||||
|
||||
代码没问题,但是不够优雅。`AllCaps` 后面的括号嵌套太深,可读性不佳。`name` 为 `nil` 时使用 “World” 的兜底逻辑必须要依赖 `??` 操作符,这在逻辑复杂的时候会更难以阅读。如果还需要 `switch` 或者 `for` 循环来构建绘制的一部分,就更难以编写了。使用结果构造器可以将这样的代码重构得更像普通的 Swift 代码。
|
||||
|
||||
在类型的定义上加上 `@resultBuilder` 特性来定义一个结果构造器。比如下面的代码定义了允许使用声明式语法来描述绘制的结果构造器 `DrawingBuilder`:
|
||||
|
||||
```swift
|
||||
@resultBuilder
|
||||
struct DrawingBuilder {
|
||||
static func buildBlock(_ components: Drawable...) -> Drawable {
|
||||
return Line(elements: components)
|
||||
}
|
||||
static func buildEither(first: Drawable) -> Drawable {
|
||||
return first
|
||||
}
|
||||
static func buildEither(second: Drawable) -> Drawable {
|
||||
return second
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`DrawingBuilder` 结构体定义了三个方法来实现部分结果构造器语法。`buildBlock(_:)` 方法添加了在方法块中写多行代码的支持。它将方法块中的多个元素组合成 `Line`。`buildEither(first:)` 和 `buildEither(second:)` 方法添加了对 `if`-`else` 的支持。
|
||||
|
||||
可以在函数形参上应用 `@DrawingBuilder` 特性,它会将传递给函数的闭包转换为用结果构造器创建的值。例如:
|
||||
|
||||
```swift
|
||||
func draw(@DrawingBuilder content: () -> Drawable) -> Drawable {
|
||||
return content()
|
||||
}
|
||||
func caps(@DrawingBuilder content: () -> Drawable) -> Drawable {
|
||||
return AllCaps(content: content())
|
||||
}
|
||||
|
||||
func makeGreeting(for name: String? = nil) -> Drawable {
|
||||
let greeting = draw {
|
||||
Stars(length: 3)
|
||||
Text("Hello")
|
||||
Space()
|
||||
caps {
|
||||
if let name = name {
|
||||
Text(name + "!")
|
||||
} else {
|
||||
Text("World!")
|
||||
}
|
||||
}
|
||||
Stars(length: 2)
|
||||
}
|
||||
return greeting
|
||||
}
|
||||
let genericGreeting = makeGreeting()
|
||||
print(genericGreeting.draw())
|
||||
// 打印 "***Hello WORLD!**"
|
||||
|
||||
let personalGreeting = makeGreeting(for: "Ravi Patel")
|
||||
print(personalGreeting.draw())
|
||||
// 打印 "***Hello RAVI PATEL!**"
|
||||
```
|
||||
|
||||
`makeGreeting(for:)` 函数将传入的 `name` 形参用于绘制个性化问候。`draw(_:)` 和 `caps(_:)` 函数都传入应用 `@DrawingBuilder` 特性的单一闭包实参。当调用这些函数时,要使用 `DrawingBuilder` 定义的特殊语法。Swift 将绘制的声明式描述转换为一系列 `DrawingBuilder` 的方法调用,构造成最终传递进函数的实参值。例如,Swift 将例子中的 `caps(_:)` 的调用转换为下面的代码:
|
||||
|
||||
```swift
|
||||
let capsDrawing = caps {
|
||||
let partialDrawing: Drawable
|
||||
if let name = name {
|
||||
let text = Text(name + "!")
|
||||
partialDrawing = DrawingBuilder.buildEither(first: text)
|
||||
} else {
|
||||
let text = Text("World!")
|
||||
partialDrawing = DrawingBuilder.buildEither(second: text)
|
||||
}
|
||||
return partialDrawing
|
||||
}
|
||||
```
|
||||
|
||||
Swift 将 `if-else` 方法块转换成调用 `buildEither(first:)` 和 `buildEither(second:)` 方法。虽然不会在自己的代码中调用这些方法,但是转换后的结果可以更清晰的理解在使用 `DrawingBuilder` 语法时 Swift 是如何进行转换的。
|
||||
|
||||
为了支持 `for` 循环来满足某些特殊的绘制语法,需要添加 `buildArray(_:)` 方法。
|
||||
|
||||
```swift
|
||||
extension DrawingBuilder {
|
||||
static func buildArray(_ components: [Drawable]) -> Drawable {
|
||||
return Line(elements: components)
|
||||
}
|
||||
}
|
||||
let manyStars = draw {
|
||||
Text("Stars:")
|
||||
for length in 1...3 {
|
||||
Space()
|
||||
Stars(length: length)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上面的代码中,使用 `for` 循环创建了一个绘制数组,`buildArray(_:)` 方法将该数组构建成 `Line`。
|
||||
|
||||
有关 Swift 如何将构建器语法转换为构建器类型方法的完整信息,查看 [结果构造器](../03_language_reference/07_Attributes.md#resultbuilder)。
|
||||
|
||||
298
source/02_language_guide/28_Concurrency.md
Normal file
298
source/02_language_guide/28_Concurrency.md
Normal file
@ -0,0 +1,298 @@
|
||||
# 并发
|
||||
Swift 对于结构化的编写异步和并行代码有着原生的支持。异步代码可以被挂起并在之后继续执行,同一时间只能有一段代码被执行。代码支持挂起和继续执行,就可以在执行耗时很长的任务时抽空执行一些快速的操作,比如在下载文件、解析文件的过程中更新 UI。*并行代码*指的是多段代码同时执行;比如一个拥有四核处理器的电脑可以同时运行四段代码,每个核心执行其中一项任务。一个使用并行和异步代码的程序可以同时执行多个运算;它可以在某个运算等待外部系统的时候挂起这个运算,从而让编写内存安全的代码更加容易。
|
||||
|
||||
并发和异步代码在带来时序灵活性的同时不免会增加复杂度。一些异步代码会自动包含编译时检查——比如,你可以使用 actor 来安全的访问可变的状态。然而,给一段运行缓慢并且有错误的代码添加并发能力并不能让它更快或者更正确的运行。事实上,给代码增加并发能力还有可能导致代码问题更难排查。但如果在需要并发的代码中使用 Swift 原生支持的并发能力会让你在编译阶段就发现问题。
|
||||
|
||||
本章剩余的部分将使用*并发*指代异步和并行。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果你曾经写过并发的代码的话,那可能使用过线程。Swift 中的并发模型是基于线程的,但你不会直接和线程打交道。在 Swift 中,一个异步函数可以交出它在某个线程上的运行权,这样另一个异步函数在这个函数被阻塞时就能获得此线程的运行权。但是,Swift并不能确定当异步函数恢复运行时其将在哪条线程上运行。
|
||||
|
||||
你当然也可以不用 Swift 原生支持去写并发的代码,只不过代码的可读性会下降。比如,下面的这段代码会拉取一系列图片名称的列表,下载列表中的图片然后展示给用户:
|
||||
|
||||
```Swift
|
||||
listPhotos(inGallery: "Summer Vacation") { photoNames in
|
||||
let sortedNames = photoNames.sorted()
|
||||
let name = sortedNames[0]
|
||||
downloadPhoto(named: name) { photo in
|
||||
show(photo)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在这个简单的案例中,由于代码中有一系列的 completion handler,最终你必须得使用嵌套闭包。更加复杂的代码会产生更深的嵌套,从而使代码迅速变得臃肿起来。
|
||||
|
||||
## 定义和调用异步函数 {#Defining-and-Calling-Asynchronous-Functions}
|
||||
|
||||
*异步函数*或*异步方法*是一种能在运行中被挂起的特殊函数或方法。对于普通的同步函数或方法来说,它们只能运行到完成闭包、抛出错误或者永远不返回。异步函数或方法也能做到这三件事,但同时也可以在等待其他资源的时候挂起。在异步函数或者方法的函数体中,你可以标记其中的任意位置是可以被挂起的。
|
||||
|
||||
为了标记某个函数或者方法是异步的,你可以在它的声明中的参数列表后边加上 `async` 关键字,和使用 `throws` 关键字来标记 throwing 函数是类似的。如果一个函数或方法有返回值,可以在返回箭头(->)前添加 `async` 关键字。 比如,下面是从图库中拉取图片名称的方法:
|
||||
|
||||
```Swift
|
||||
func listPhotos(inGallery name: String) async -> [String] {
|
||||
let result = // 省略一些异步网络请求代码
|
||||
return result
|
||||
}
|
||||
```
|
||||
对于那些既是异步又是 throwing 的函数,需要把 `async` 写在`throws` 关键字前边。
|
||||
|
||||
调用一个异步方法时,执行会被挂起直到这个异步方法返回。你需要在调用前增加 `await` 关键字来标记此处为可能的悬点(Suspension point)。这就像调用 throwing 函数需要添加 `try` 关键字来标记在发生错误的时候会改变程序流程一样。在一个异步方法中,执行只会在调用另一个异步方法的时候会被挂起;挂起永远都不会是隐式或者优先的,这也意味着所有的悬点都需要被标记为 `await`。
|
||||
|
||||
比如,下面的这段代码可以拉取图库中所有图片的名称,然后展示第一张图片:
|
||||
|
||||
```Swift
|
||||
let photoNames = await listPhotos(inGallery: "Summer Vacation")
|
||||
let sortedNames = photoNames.sorted()
|
||||
let name = sortedNames[0]
|
||||
let photo = await downloadPhoto(named: name)
|
||||
show(photo)
|
||||
```
|
||||
|
||||
因为 `listPhotos(inGallery:)` 和 `downloadPhoto(named:)` 都需要发起网络请求,需要花费较长的时间完成。给这两个函数在返回箭头前加上 `async` 可以将它们定义为异步函数,从而使得这部分代码在等待图片的时候让程序的其他部分继续运行。
|
||||
|
||||
为了更好理解上面这段代码的并发本质,下面列举出这段程序可能的一个执行顺序:
|
||||
|
||||
1. 代码从第一行开始执行到第一个 `await`,调用 `listPhotos(inGallery:)` 函数并且挂起这段代码的执行,等待这个函数的返回。
|
||||
2. 当这段代码的执行被挂起时,程序的其他并行代码会继续执行。比如,后台有一个耗时长的任务更新其他一些图库。那段代码会执行到被 `await` 的标记的悬点,或者执行完成。
|
||||
3. 当 `listPhotos(inGallery:)` 函数返回之后,上面这段代码会从上次的悬点开始继续执行。它会把函数的返回赋值给 `photoNames` 变量。
|
||||
4. 定义 `sortedNames` 和 `name` 的那行代码是普通的同步代码,因为并没有被 `await` 标记,也不会有任何可能的悬点。
|
||||
5. 接下来的 `await` 标记是在调用 `downloadPhoto(named:)` 的地方。这里会再次暂停这段代码的执行直到函数返回,从而给了其他并行代码执行的机会。
|
||||
6. 在 `downloadPhoto(named:)` 返回后,它的返回值会被赋值到 `photo` 变量中,然后被作为参数传递给 `show(_:)`。
|
||||
|
||||
代码中被 `await` 标记的悬点表明当前这段代码可能会暂停等待异步方法或函数的返回。这也被称为*让出线程(yielding the thread)*,因为在幕后 Swift 会挂起你这段代码在当前线程的执行,转而让其他代码在当前线程执行。因为有 `await` 标记的代码可以被挂起,所以在程序中只有特定的地方才能调用异步方法或函数:
|
||||
|
||||
* 异步函数,方法或变量内部的代码
|
||||
* 静态函数 `main()` 中被打上 `@main` 标记的结构体、类或者枚举中的代码
|
||||
* 非结构化的子任务中的代码,之后会在 [非结构化并行](#Unstructured-Concurrency) 中说明
|
||||
|
||||
在可能的悬点之间的代码将按顺序运行,并不可能被其它并发代码中断。例如,以下代码将一张图片从一个图库移动到另一个图库:
|
||||
|
||||
```swift
|
||||
let firstPhoto = await listPhotos(inGallery: "Summer Vacation")[0]
|
||||
add(firstPhoto, toGallery: "Road Trip")
|
||||
//此时,firstPhoto暂时地同时存在于两个画廊中
|
||||
remove(firstPhoto, fromGallery: "Summer Vacation")
|
||||
```
|
||||
|
||||
其它代码不能在 `add(_:toGallery:)` 和 `remove(_:fromGallery:)` 两个方法之间运行。在此期间,第一张图片同时存在于两个图库,暂时打破了应用程序的一个不变量。为了更明确地表示这段代码不能加入 `await` 标记,你可以将这段代码重构为一个同步函数:
|
||||
|
||||
```swift
|
||||
func move(_photoName: String, from source: String, to destination: String) {
|
||||
add(photoName, to: destination)
|
||||
remove(photoName, from: source)
|
||||
}
|
||||
//...
|
||||
let firstPhoto = await listPhotos(inGallery: "Summer Vacation")[0]
|
||||
move(firstPhoto, from: "Summer Vacation", to: "Road Trip")
|
||||
```
|
||||
|
||||
在上例中,由于 `move(_:from:to:)` 函数为同步函数,你将能够保证它将不会包含潜在的悬点。在未来,试图在该函数中写入并发代码将引发编译错误而非产生bug。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 学习并行的过程中,[Task.sleep(_:)](https://developer.apple.com/documentation/swift/task/3814836-sleep) 方法非常有用。这个方法什么都没有做,只是等待不少于指定的时间(单位纳秒)后返回。下面是使用 `sleep(until:clock:)` 方法模拟网络请求实现 `listPhotos(inGallery:)` 的一个版本:
|
||||
>
|
||||
|
||||
```swift
|
||||
func listPhotos(inGallery name: String) async throws -> [String] {
|
||||
try await Task.sleep(until: .now + .seconds(2), clock: .continuous)
|
||||
return ["IMG001", "IMG99", "IMG0404"]
|
||||
}
|
||||
```
|
||||
|
||||
## 异步序列 {#Asynchronous-Sequences}
|
||||
|
||||
上一节中的 `listPhotos(inGallery:)` 方法会在拿到数组中的所有元素后,以异步的方式一次性返回整个数组。另一种方式是使用*异步序列(asynchronous sequence)*,每次收到一个元素后对其进行处理。下面这段代码展示了如何遍历一个异步序列:
|
||||
|
||||
```Swift
|
||||
import Foundation
|
||||
|
||||
let handle = FileHandle.standardInput
|
||||
for try await line in handle.bytes.lines {
|
||||
print(line)
|
||||
}
|
||||
```
|
||||
|
||||
与普通的 `for-in` 循环相比,上面的例子在 `for` 之后添加了 `await` 关键字。就像在调用异步函数或方法时一样,`await` 表明代码中有一个可能的悬点。`for-await-in` 循环会在每次循环开始的时候因为有可能需要等待下一个元素而挂起当前代码的执行。
|
||||
|
||||
想让自己创建的类型使用 `for-in` 循环需要遵循 [Sequence](https://developer.apple.com/documentation/swift/sequence) 协议,这里也同理,如果想让自己创建的类型使用 `for-await-in` 循环,就需要遵循 [AsyncSequence](https://developer.apple.com/documentation/swift/asyncsequence) 协议。
|
||||
|
||||
## 并行的调用异步方法 {#Calling-Asynchronous-Functions-in-Parallel}
|
||||
|
||||
调用有 `await` 标记的异步函数在同一时间只能执行一段代码。在异步代码执行的过程中,调用方需要等待异步代码执行完后才能继续执行下一行代码。比如,当你想从图库中拉取前三张图片,可以像下面这样,等三次调用完后再执行 `downloadPhoto(named:)` 函数:
|
||||
|
||||
```Swift
|
||||
let firstPhoto = await downloadPhoto(named: photoNames[0])
|
||||
let secondPhoto = await downloadPhoto(named: photoNames[1])
|
||||
let thirdPhoto = await downloadPhoto(named: photoNames[2])
|
||||
|
||||
let photos = [firstPhoto, secondPhoto, thirdPhoto]
|
||||
show(photos)
|
||||
```
|
||||
|
||||
这种方式有一个非常严重的缺陷:虽然下载过程是异步的,并且在等待过程中可以执行其他任务,但每次只能执行一个 `downloadPhoto(named:)`。每一张图片只能在上一张图片下载结束了才开始下载。然而,并没有必要让这些操作等待,每张图片可以独立甚至同时下载。
|
||||
|
||||
为了在调用异步函数的时候让它附近的代码并发执行,定义一个常量时,在 `let` 前添加 `async` 关键字,然后在每次使用这个常量时添加 `await` 标记。
|
||||
|
||||
```Swift
|
||||
async let firstPhoto = downloadPhoto(named: photoNames[0])
|
||||
async let secondPhoto = downloadPhoto(named: photoNames[1])
|
||||
async let thirdPhoto = downloadPhoto(named: photoNames[2])
|
||||
|
||||
let photos = await [firstPhoto, secondPhoto, thirdPhoto]
|
||||
show(photos)
|
||||
```
|
||||
|
||||
在上面的例子中,三次调用 `downloadPhoto(named:)` 都不需要等待前一次调用结束。如果系统有足够的资源,这三次调用甚至都可以同时执行。这三次调用都没有没标记为 `await`,因为代码不需要被挂起等待函数的结果。程序会继续执行直到 `photos` 被定义,与上面不同的是,在这个时间点由于程序需要上面几次异步调用的结果,所以你需要添加 `await` 关键字来挂起当前代码的执行直到所有图片下载完成。
|
||||
|
||||
下面是关于两种不同方法的一些说法:
|
||||
|
||||
* 代码中接下来的几行需要依赖异步函数的结果时,需要使用 `await` 来调用异步函数。这样产生的结果是有序的。
|
||||
* 短时间内并不需要异步函数的结果时,需要使用 `async-let` 来调用异步函数。这样产生的任务是并发的。
|
||||
* `await` 和 `async-let` 都允许其他任务在他们被挂起的时候执行。
|
||||
* 在两种情况下,都需要用 `await` 标记可能的悬点,以表明代码在这些点在需要的情况下会被挂起,直到异步函数执行结束。
|
||||
|
||||
你也可以在同一段代码中混用两种方式。
|
||||
|
||||
## 任务和任务组 {#Tasks-and-Task-Groups}
|
||||
|
||||
*任务(task)* 是一项工作,可以作为程序的一部分并发执行。所有的异步代码都属于某个任务。上一部分介绍的 `async-let` 语法就会产生一个子任务。你也可以创建一个任务组并且给其中添加子任务,这可以让你对优先级和任务取消有了更多的掌控力,并且可以控制任务的数量。
|
||||
|
||||
任务是按层级结构排列的。同一个任务组中的任务拥有相同的父任务,并且每个任务都可以添加子任务。由于任务和任务组之间明确的关系,这种方式又被称为*结构化并发(structured concurrency)*。虽然你需要确保代码的正确性,但任务间明确的父子关系让 Swift 能替你处理一些如扩散取消(propagating cancellation)之类的行为,并且能让 Swift 在编译阶段发现一些错误。
|
||||
|
||||
```Swift
|
||||
await withTaskGroup(of: Data.self) { taskGroup in
|
||||
let photoNames = await listPhotos(inGallery: "Summer Vacation")
|
||||
for name in photoNames {
|
||||
taskGroup.addTask { await downloadPhoto(named: name) }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
如果想更多的了解任务组,可以参考 [TaskGroup](https://developer.apple.com/documentation/swift/taskgroup)。
|
||||
|
||||
### 非结构化并发 {#Unstructured-Concurrency}
|
||||
|
||||
对于并发来说,除了上一部分讲到的结构化的方式,Swift 还支持非结构化并发。与任务组中的任务不同的是,*非结构化任务(unstructured task)*并没有父任务。你能以任何方式来处理非结构化任务以满足你程序的需要,但与此同时,你需要对于他们的正确性付全责。如果想创建一个在当前 actor 上运行的非结构化任务,需要调用构造器 [Task.init(priority:operation:)](https://developer.apple.com/documentation/swift/task/3856790-init)。如果想要创建一个不在当前 actor 上运行的非结构化任务(更具体地说就是*游离任务(detached task)*),需要调用类方法 [Task.detached(priority:operation:)](https://developer.apple.com/documentation/swift/task/3856786-detached)。以上两种方法都能返回一个能让你与任务交互(继续等待结果或取消任务)的任务句柄,如下:
|
||||
|
||||
```swift
|
||||
let newPhoto = // ... 图片数据 ...
|
||||
let handle = Task {
|
||||
return await add(newPhoto, toGalleryNamed: "Spring Adventures")
|
||||
}
|
||||
let result = await handle.value
|
||||
```
|
||||
|
||||
如果你想更多的了解游离任务,可以参考 [Task](https://developer.apple.com/documentation/swift/task)。
|
||||
|
||||
### 任务取消 {#Task-Cancellation}
|
||||
|
||||
Swift 中的并发使用合作取消模型。每个任务都会在执行中合适的时间点检查自己是否被取消了,并且会用任何合适的方式来响应取消操作。这些方式会根据你所执行的工作分为以下几种:
|
||||
|
||||
* 抛出如 `CancellationError` 这样的错误
|
||||
* 返回 nil 或者空的集合
|
||||
* 返回完成一半的工作
|
||||
|
||||
如果想检查任务是否被取消,既可以使用 [Task.checkCancellation()](https://developer.apple.com/documentation/swift/task/3814826-checkcancellation)(如果任务取消会返回 `CancellationError`),也可以使用 [Task.isCancelled](https://developer.apple.com/documentation/swift/task/3814832-iscancelled) 来判断,继而在代码中对取消进行相应的处理。比如,一个从图库中下载图片的任务需要删除下载到一半的文件并且关闭连接。
|
||||
|
||||
如果想手动执行扩散取消,调用 [Task.cancel()](https://developer.apple.com/documentation/swift/task/3851218-cancel)。
|
||||
|
||||
## Actors {#Actors}
|
||||
|
||||
你可以使用任务来将自己的程序分割为孤立、并发的部分。任务间相互孤立,这也使得它们能够安全地同时运行。但有时你需要在任务间共享信息。Actors便能够帮助你安全地在并发代码间分享信息。
|
||||
|
||||
跟类一样,actor 也是一个引用类型,所以 [类是引用类型](https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html#ID89) 中关于值类型和引用类型的比较同样适用于 actor 和类。不同于类的是,actor 在同一时间只允许一个任务访问它的可变状态,这使得多个任务中的代码与一个 actor 交互时更加安全。比如,下面是一个记录温度的 actor:
|
||||
|
||||
```Swift
|
||||
actor TemperatureLogger {
|
||||
let label: String
|
||||
var measurements: [Int]
|
||||
private(set) var max: Int
|
||||
|
||||
init(label: String, measurement: Int) {
|
||||
self.label = label
|
||||
self.measurements = [measurement]
|
||||
self.max = measurement
|
||||
}
|
||||
}
|
||||
```
|
||||
你可以用 `actor` 关键字引入一个 actor,后边的花括号中是它的定义。`TemperatureLogger` 中有外部能访问到的属性,并且限制 `max` 变量,所以只能在 actor 内部修改最大值。
|
||||
|
||||
你可以使用与结构体和类初始化相同的语法创建一个 actor。当你访问 actor 中的属性或方法时,需要使用 `await` 来标记潜在的悬点,比如:
|
||||
|
||||
```Swift
|
||||
let logger = TemperatureLogger(label: "Outdoors", measurement: 25)
|
||||
print(await logger.max)
|
||||
// 输出 "25"
|
||||
```
|
||||
|
||||
在这个例子中,访问 `logger.max` 是一个可能的悬点。因为 actor 在同一时间只允许一个任务访问它的可变状态,如果别的任务正在与 logger 交互,上面这段代码将会在等待访问属性的时候被挂起。
|
||||
|
||||
相比之下,actor 内部的代码在访问其属性的时候不需要添加 `await` 关键字。比如,下面的方法是更新 `TemperatureLogger` 中的温度:
|
||||
|
||||
```Swift
|
||||
extension TemperatureLogger {
|
||||
func update(with measurement: Int) {
|
||||
measurements.append(measurement)
|
||||
if measurement > max {
|
||||
max = measurement
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`update(with:)` 方法本来就在 actor 中运行,所以没必要在访问如 `max` 等属性的时候加 `await` 关键字。这个方法也展示了为什么要在同一时间只允许一个任务访问其可变状态的其中一个理由:一些对于 actor 状态的改变暂时打破了不可变性。 `TemperatureLogger` 记录了一个温度的列表和最高温度,并且会在你更新了一个新测量值之后更新最大温度。在更新的过程中,在增加了新测量值但没有更新 `max` 前,`TemperatureLogger` 正处于一个暂时不一致的状态。阻止不同的任务和同一个 actor 实例交互可以防止以下事件序列的发生:
|
||||
|
||||
1. 你的代码调用 `update(with:)` 方法,并且先更新了 `measurements` 数组。
|
||||
2. 在你的代码更新 `max` 前,其他地方的代码读取了最大值和温度列表的值。
|
||||
3. 你的代码更新了 `max` 完成调用。
|
||||
|
||||
在这种情况下,其他的代码读取到了错误的值,因为 actor 的读取操作被夹在 `update(with:)` 方法中间,而此时数据暂时是无效的。你可以用 Swift 中的 actor 以防止这种问题的发生,因为 actor 在同一时刻只允许有一个任务能访问它的状态,而且只有在被 `await` 标记为悬点的地方代码才会被打断。因为 `update(with:)` 方法没有任何悬点,没有其他任何代码可以在更新的过程中访问到数据。
|
||||
|
||||
如果你想在 actor 外部像访问类属性一样访问 actor 的属性,会得到一个编译时错误;比如:
|
||||
|
||||
```Swift
|
||||
print(logger.max) // 报错
|
||||
```
|
||||
|
||||
不添加 `await` 关键字的情况下访问 `logger.max` 会失败,因为 actor 的属性是它隔离的本地状态的一部分。Swift 可以保证只有 actor 内部的代码可以访问 actor 的内部状态。这个保证也被称为 *actor isolation*。
|
||||
|
||||
## 可发送类型 {#Sendable-Types}
|
||||
|
||||
任务和Actor能够帮助你将程序分割为能够安全地并发运行的小块。在一个任务中,或是在一个Actor实例中,程序包含可变状态的部分(如变量和属性)被称为*并发域(Concurrency domain)*。部分类型的数据不能在并发域间共享,因为它们包含了可变状态,但它不能阻止重叠访问。
|
||||
|
||||
能够在并发域间共享的类型被称为*可发送类型(Sendable Type)*。例如在调用Actor方法时被作为实参传递,或是作为任务的结果返回。本章之前的例子并未讨论可发送性,因为这些例子均使用了简单值类型,对于在并发域间传递的数据而言,简单值类型总是安全的。而与之相反,另一些类型并不能安全地在并发域间传递。例如,当你在不同的任务间传递该类的实例时,包含可变属性且并未序列化对这些属性的访问的类可能产生不可预测和不正确的结果。
|
||||
|
||||
你可以通过声明其符合 `Sendable` 协议来将某个类型标记为可发送类型。该协议并不包含任何代码要求,但Swift对其做出了强制的语义要求。总之,有三种方法将一个类型声明为可发送类型:
|
||||
|
||||
- 该类型为值类型,且其可变状态由其它可发送数据构成——例如具有存储属性的结构体或是具有关联值的枚举。
|
||||
|
||||
- 该类型不包含任何可变状态,且其不可变状态由其它可发送数据构成——例如只包含只读属性的结构体或类
|
||||
|
||||
- 该类型包含能确保其可变状态安全的代码——例如标记了 `@MainActor` 的类或序列化了对特定线程/队列上其属性的访问的类。
|
||||
|
||||
如需了解Swift对Sendable协议的语义要求的详细信息,请访问 [Sendable](https://developer.apple.com/documentation/swift/sendable) 协议参考。
|
||||
|
||||
部分类型总是可发送类型,如只有可发送属性的结构体和只有可发送关联值的枚举。例如:
|
||||
|
||||
```swift
|
||||
struct TemperatureReading: Sendable {
|
||||
var measurement: Int
|
||||
}
|
||||
extension TemperatureLogger {
|
||||
func addReading(from reading: TemperatureReading) {
|
||||
measurements.append(reading.measurement)
|
||||
}
|
||||
}
|
||||
let logger = TemperatureLogger(label: "Tea kettle", measurement: 85)
|
||||
let reading = TemperatureReading(measurement: 45)
|
||||
await logger.addReading(from: reading)
|
||||
```
|
||||
由于 `TemperatureReading` 是只有可发送属性的结构体,且该结构体并未被标记为 `public` 或 `@usableFromInline`,因此它是隐式可发送的。下文给出了该结构体的一个符合 `Sendable` 协议的版本:
|
||||
|
||||
```swift
|
||||
struct TemperatureReading {
|
||||
var measurement: Int
|
||||
}
|
||||
```
|
||||
@ -1,3 +1,3 @@
|
||||
# Swift 教程
|
||||
# Swift 语言教程
|
||||
|
||||
本章介绍了 Swift 的各种特性及其使用方法,是全书的核心部分。
|
||||
@ -19,14 +19,14 @@ Swift 语言相对较小,这是由于 Swift 代码中常用的类型、函数
|
||||
|
||||
> 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 子句](./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 方法块 → { [*getter 子句*](./06_Declarations.md#getter-clause) [*setter 子句*](./06_Declarations.md#setter-clause)<sub>可选</sub> }
|
||||
> 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) }
|
||||
> getter-setter 方法块 → { [setter 子句](./06_Declarations.md#setter-clause) [getter 子句](./06_Declarations.md#getter-clause) }
|
||||
>
|
||||
|
||||
714
source/03_language_reference/02_Lexical_Structure.md
Executable file
714
source/03_language_reference/02_Lexical_Structure.md
Executable file
@ -0,0 +1,714 @@
|
||||
# 词法结构(Lexical Structure)
|
||||
|
||||
Swift 的*“词法结构(lexical structure)”* 描述了能构成该语言中有效符号(token)的字符序列。这些合法符号组成了语言中最底层的构建基块,并在之后的章节中用于描述语言的其他部分。一个合法符号由一个标识符(identifier)、关键字(keyword)、标点符号(punctuation)、字面量(literal)或运算符(operator)组成。
|
||||
|
||||
通常情况下,符号是考虑了输入文本中最长可能的子字符串,并被随后将介绍的语法约束,根据 Swift 源文件的字符生成的。这种方法称为*“最长匹配(longest match)”*,或者*“最大适合(maximal munch)”*。
|
||||
|
||||
## 空白与注释 {#whitespace}
|
||||
|
||||
空白(whitespace)有两个用途:分隔源文件中的符号和区分前缀、后缀和中缀运算符(参见 [运算符](#operators)),在其他情况下空白则会被忽略。以下的字符会被当作空白:空格(U+0020)、换行符(U+000A)、回车符(U+000D)、水平制表符(U+0009)、垂直制表符(U+000B)、换页符(U+000C)以及空字符(U+0000)。
|
||||
|
||||
注释被编译器当作空白处理。单行注释由 `//` 开始直至遇到换行符(U+000A)或者回车符(U+000D)。多行注释由 `/*` 开始,以 `*/` 结束。多行注释允许嵌套,但注释标记必须成对出现。
|
||||
|
||||
注释可以包含其他的格式和标记,如 [标记格式参考](https://developer.apple.com/library/archive/documentation/Xcode/Reference/xcode_markup_formatting_ref/index.html)中所述。
|
||||
|
||||
> 空白语法
|
||||
>
|
||||
> *空白* → [空白项](#whitespace-item) [空白](#whitespace)<sub>可选</sub>
|
||||
#### whitespace-item {#whitespace-item}
|
||||
>
|
||||
> *空白项* → [断行符](#line-break)
|
||||
>
|
||||
> *空白项* → [行内空白](#inline-space)
|
||||
>
|
||||
> *空白项* → [注释](#comment)
|
||||
>
|
||||
> *空白项* → [多行注释](#multiline-comment)
|
||||
>
|
||||
> *空白项* → U+0000,U+0009,U+000B,U+000C 或者 U+0020
|
||||
>
|
||||
>
|
||||
#### line-break {#line-break}
|
||||
>
|
||||
> *断行符* → U+000A
|
||||
>
|
||||
> *断行符* → U+000D
|
||||
>
|
||||
> *断行符* → U+000D 接着是 U+000A
|
||||
>
|
||||
|
||||
#### inline-spaces {#inline-spaces}
|
||||
>
|
||||
> *行内空白组* → [行内空白](#inline-space) [行内空白组](#inline-spaces)<sub>可选</sub>
|
||||
|
||||
#### inline-space {#inline-space}
|
||||
> *行内空白* → U+0009 或 U+0020
|
||||
|
||||
#### 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 标量值,除了 `/*` 或者 `*/`
|
||||
|
||||
## 标识符 {#identifiers}
|
||||
|
||||
*标识符(identifier)* 可以由以下的字符开始:大写或小写的字母 `A` 到 `Z`、下划线(`_`)、基本多文种平面(Basic Multilingual Plane)中非字符数字组合的 Unicode 字符以及基本多文种平面以外的非个人专用区字符。在首字符之后,允许使用数字和组合 Unicode 字符。
|
||||
|
||||
以下划线开头的标识视为内部标识符,即使声明了 `public` 访问级别。这个约定允许框架作者标记一部分不能被使用方调用或依赖的 API,即便因为某些限制原因导致它的声明必须是公开的。另外,双下划线开头的标识符是为 Swift 编译器和标准库预留的。
|
||||
|
||||
使用保留字作为标识符,需要在其前后增加反引号(`` ` ``)。例如,`class` 不是合法的标识符,但可以使用 `` `class` ``。反引号不属于标识符的一部分,`` `x` `` 和 `x` 表示同一标识符。
|
||||
|
||||
闭包中如果没有明确指定参数名称,参数将被隐式命名为 `$0`、`$1`、`$2` 等等。这些命名在闭包作用域范围内是合法的标识符。
|
||||
|
||||
编译器给含有属性包装器呈现值的属性自动合成以美元符号(*$*)开头的标识符。你的代码可以与这些标识符进行交互,,但是不能使用该前缀声明标识符。更详细的介绍,请查看 [特性](./07_Attributes.md) 章节中的 [属性包装器](./07_Attributes.md#propertywrapper) 部分。
|
||||
|
||||
> 标识符语法
|
||||
>
|
||||
> #### identifier {#identifier}
|
||||
>
|
||||
> *标识符* → [头部标识符](#identifier-head) [标识符字符组](#identifier-characters)<sub>可选</sub>
|
||||
>
|
||||
> *标识符* → \`[头部标识符](#identifier-head) [标识符字符组](#identifier-characters)<sub>可选</sub>\`
|
||||
>
|
||||
> *标识符* → [隐式参数名](#implicit-parameter-name)
|
||||
>
|
||||
> *标识符* → [属性包装器呈现值](#property-wrapper-projection)
|
||||
>
|
||||
> #### identifier-list {#identifier-list}
|
||||
>
|
||||
> *标识符列表* → [标识符](#identifier) | [标识符](#identifier) **,** [标识符列表](#identifier)
|
||||
>
|
||||
>
|
||||
#### identifier-head {#identifier-head}
|
||||
>
|
||||
> *头部标识符* → 大写或小写字母 A - Z
|
||||
>
|
||||
> *头部标识符* → **_**
|
||||
>
|
||||
> *头部标识符* → U+00A8,U+00AA,U+00AD,U+00AF,U+00B2–U+00B5,或者 U+00B7–U+00BA
|
||||
>
|
||||
> *头部标识符* → U+00BC–U+00BE,U+00C0–U+00D6,U+00D8–U+00F6,或者 U+00F8–U+00FF
|
||||
>
|
||||
> *头部标识符* → U+0100–U+02FF,U+0370–U+167F,U+1681–U+180D,或者 U+180F–U+1DBF
|
||||
>
|
||||
> *头部标识符* → U+1E00–U+1FFF
|
||||
>
|
||||
> *头部标识符* → U+200B–U+200D,U+202A–U+202E,U+203F–U+2040,U+2054,或者 U+2060–U+206F
|
||||
>
|
||||
> *头部标识符* → U+2070–U+20CF,U+2100–U+218F,U+2460–U+24FF,或者 U+2776–U+2793
|
||||
>
|
||||
> *头部标识符* → U+2C00–U+2DFF 或者 U+2E80–U+2FFF
|
||||
>
|
||||
> *头部标识符* → U+3004–U+3007,U+3021–U+302F,U+3031–U+303F,或者 U+3040–U+D7FF
|
||||
>
|
||||
> *头部标识符* → U+F900–U+FD3D,U+FD40–U+FDCF,U+FDF0–U+FE1F,或者 U+FE30–U+FE44
|
||||
>
|
||||
> *头部标识符* → U+FE47–U+FFFD
|
||||
>
|
||||
> *头部标识符* → U+10000–U+1FFFD,U+20000–U+2FFFD,U+30000–U+3FFFD,或者 U+40000–U+4FFFD
|
||||
>
|
||||
> *头部标识符* → U+50000–U+5FFFD,U+60000–U+6FFFD,U+70000–U+7FFFD,或者 U+80000–U+8FFFD
|
||||
>
|
||||
> *头部标识符* → U+90000–U+9FFFD,U+A0000–U+AFFFD,U+B0000–U+BFFFD,或者 U+C0000–U+CFFFD
|
||||
>
|
||||
> *头部标识符* → U+D0000–U+DFFFD 或者 U+E0000–U+EFFFD
|
||||
#### identifier-character {#identifier-character}
|
||||
>
|
||||
> *标识符字符* → 数值 0 - 9
|
||||
>
|
||||
> *标识符字符* → U+0300–U+036F,U+1DC0–U+1DFF,U+20D0–U+20FF,或者 U+FE20–U+FE2F
|
||||
>
|
||||
> *标识符字符* → [头部标识符](#identifier-head)
|
||||
>
|
||||
>
|
||||
#### identifier-characters {#identifier-characters}
|
||||
>
|
||||
> *标识符字符组* → [标识符字符](#identifier-character) [标识符字符组](#identifier-characters)<sub>可选</sub>
|
||||
>
|
||||
>
|
||||
#### implicit-parameter-name {#implicit-parameter-name}
|
||||
>
|
||||
> *隐式参数名* → **$** [十进制数字列表](#decimal-digit)
|
||||
#### property-wrapper-projection {#property-wrapper-projection}
|
||||
>
|
||||
> *属性包装器呈现值* → **$** [标识符字符组](#identifier-characters)
|
||||
|
||||
## 关键字和标点符号 {#keywords-and-punctuation}
|
||||
|
||||
下面这些被保留的关键字不允许用作标识符,除非使用反引号转义,具体描述请参考 [标识符](#identifiers)。除了 `inout`、`var` 以及 `let` 之外的关键字可以用作某个函数声明或者函数调用当中的外部参数名,无需添加反引号转义。当一个成员与一个关键字具有相同的名称时,不需要使用反引号来转义对该成员的引用,除非在引用该成员和使用该关键字之间存在歧义 - 例如,`self`,`Type` 和 `Protocol` 在显式的成员表达式中具有特殊的含义,因此它们必须在该上下文中使用反引号进行转义。
|
||||
|
||||
* 用在声明中的关键字:`associatedtype`、`class`、`deinit`、`enum`、`extension`、`fileprivate `、`func`、`import`、`init`、`inout`、`internal`、`let`、`open`、`operator`、`private`、`precedencegroup`、`protocol`、`public`、`rethrows`、`static`、`struct`、`subscript`、`typealias` 以及 `var`。
|
||||
* 用在语句中的关键字:`break`、`case`、`catch`、`continue`、`default`、`defer`、`do`、`else`、`fallthrough`、`for`、`guard`、`if`、`in`、`repeat`、`return`、`throw`、`switch`、`where` 以及 `while`。
|
||||
* 用在表达式和类型中的关键字:`Any`、`as`、`catch`、`false`、`is`、`nil`、`rethrows`、`self`、`Self`、`super`、`throw`、`throws`、`true` 以及 `try `。
|
||||
* 用在模式中的关键字:`_`。
|
||||
* 以井字号(`#`)开头的关键字:`#available`、`#colorLiteral`、`#column`、`#dsohandle`、`#elseif`、`#else`、`#endif`、`#error`、`#fileID`、`#fileLiteral`、`#filePath`、`#file`、`#function`、`#if`、`#imageLiteral`、`#keyPath`、`#line`、`#selector`、`#sourceLocation` 以及 `#warning`。
|
||||
* 特定上下文中被保留的关键字:`associativity`、`convenience`、`didSet`、`dynamic`、`final`、`get`、`indirect`、`infix`、`lazy`、`left`、`mutating`、`none`、`nonmutating`、`optional`、`override`、`postfix`、`precedence`、`prefix`、`Protocol`、`required`、`right`、`set`、`some`、`Type`、`unowned`、`weak` 以及 `willSet`。这些关键字在特定上下文之外可以被用做标识符。
|
||||
|
||||
以下符号被保留为标点符号,不能用于自定义运算符:`(`、`)`、`{`、`}`、`[`、`]`、`.`、`,`、`:`、`;`、`=`、`@`、`#`、`&`(作为前缀运算符)、`->`、`` ` ``、`?`、以及 `!`(作为后缀运算符)。
|
||||
|
||||
## 字面量 {#literal}
|
||||
|
||||
*字面量(literal)* 用来表示源码中某种特定类型的值,比如一个数字或字符串。
|
||||
|
||||
下面是字面量的一些示例:
|
||||
|
||||
```swift
|
||||
42 // 整数字面量
|
||||
3.14159 // 浮点数字面量
|
||||
"Hello, world!" // 字符串字面量
|
||||
/Hello, .*/ // 正则表达式字面量
|
||||
true // 布尔值字面量
|
||||
```
|
||||
|
||||
字面量本身并不包含类型信息。事实上,一个字面量会被解析为拥有无限的精度,然后 Swift 的类型推导会尝试去推导出这个字面量的类型。比如,在 `let x: Int8 = 42` 这个声明中,Swift 使用了显式类型注解(`: Int8`)来推导出 `42` 这个整数字面量的类型是 `Int8`。如果没有可用的类型信息,Swift 则会从标准库中定义的字面量类型中推导出一个默认的类型。整数字面量的默认类型是 `Int`,浮点数字面量的默认类型是 `Double`,字符串字面量的默认类型是 `String`,布尔值字面量的默认类型是 `Bool`。比如,在 `let str = "Hello, world"` 这个声明中,字符串 `"Hello, world"` 的默认推导类型就是 `String`。
|
||||
|
||||
当为一个字面量值指定了类型注解的时候,这个注解类型必须能通过这个字面量值实例化。也就是说,这个类型必须符合这些 Swift 标准库协议中的一个:整数字面量的 `ExpressibleByIntegerLiteral` 协议、浮点数字面量的 `ExpressibleByFloatLiteral` 协议、字符串字面量的 `ExpressibleByStringLiteral` 协议、布尔值字面量的 `ExpressibleByBooleanLiteral` 协议、只包含单个 Unicode 标量字符串文本的 `ExpressibleByUnicodeScalarLiteral` 协议以及只包含单个扩展字形簇(grapheme cluster)字符串文字的 `ExpressibleByExtendedGraphemeClusterLiteral` 协议。比如,`Int8` 符合 `ExpressibleByIntegerLiteral` 协议,因此它能在 `let x: Int8 = 42` 这个声明中作为整数字面量 `42` 的类型注解。
|
||||
|
||||
|
||||
> 字面量语法
|
||||
>
|
||||
> *字面量* → [数值字面量](#integer-literal) | [字符串字面量](#string-literal) | [布尔值字面量](#integer-literal) | [nil 字面量](#integer-literal)
|
||||
>
|
||||
> *数值字面量* → **-**<sub>可选</sub> [整数字面量](#integer-literal) | **-**<sub>可选</sub> [浮点数字面量](#floating-point-literal)
|
||||
>
|
||||
> *布尔值字面量* → **true** | **false**
|
||||
>
|
||||
> *nil 字面量* → **nil**
|
||||
|
||||
|
||||
### 整数字面量{#integer-literal}
|
||||
|
||||
*整数字面量(Integer Literals)* 表示未指定精度的整数值。整数字面量默认用十进制表示;可以加前缀来指定其他的进制。二进制字面量加 `0b`,八进制字面量加 `0o`,十六进制字面量加 `0x`。
|
||||
|
||||
十进制字面量包含数字 `0` 至 `9`。二进制字面量包含 `0` 和 `1`,八进制字面量包含数字 `0` 至 `7`,十六进制字面量包含数字 `0` 至 `9` 以及字母 `A` 至 `F`(大小写均可)。
|
||||
|
||||
负整数字面量的表示方式为在整数字面量前加负号 `-`,比如 `-42`。
|
||||
|
||||
整型字面面可以使用下划线(`_`)来增加数字的可读性,下划线会被系统忽略,因此不会影响字面量的值。同样地,也可以在数字前加 `0`,这同样也会被系统所忽略,并不会影响字面量的值。
|
||||
|
||||
除非特别指定,整数字面量的默认推导类型为 Swift 标准库类型中的 `Int`。Swift 标准库还定义了其他不同长度以及是否带符号的整数类型,请参考 [整数](../02_language_guide/01_The_Basics.md#integers)。
|
||||
|
||||
> 整数字面量语法
|
||||
>
|
||||
>
|
||||
#### 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>
|
||||
|
||||
### 浮点数字面量{#floating-point-literal}
|
||||
|
||||
*浮点数字面量(Floating-point literals)*表示未指定精度浮点数的值。
|
||||
|
||||
浮点数字面量默认用十进制表示(无前缀),也可以用十六进制表示(加前缀 `0x`)。
|
||||
|
||||
十进制浮点数字面量由十进制数字串后跟十进制小数部分或十进制指数部分(或两者皆有)组成。十进制小数部分由小数点(`.`)后跟十进制数字串组成。指数部分由大写或小写字母 `e` 为前缀后跟十进制数字串组成,这串数字表示 `e` 之前的数值乘以 10 的几次方。例如:`1.25e2` 表示 1.25 x 10²,也就是 `125.0`。同样,`1.25e-2` 表示 1.25 x 10¯²,也就是 `0.0125`。
|
||||
|
||||
十六进制浮点数字面量由前缀 `0x` 后跟可选的十六进制小数部分以及十六进制指数部分组成。十六进制小数部分由小数点后跟十六进制数字串组成。指数部分由大写或小写字母 `p` 为前缀后跟十进制数字串组成,这串数字表示 `p` 之前的数量乘以 2 的几次方。例如:`0xFp2` 表示 15 x 2²,也就是 `60`。同样,`0xFp-2` 表示 15 x 2¯²,也就是 `3.75`。
|
||||
|
||||
负数的浮点数字面量由负号(`-`)和浮点数字面量组成,例如 `-42.5`。
|
||||
|
||||
浮点数字面量允许使用下划线(`_`)来增强数字的可读性,下划线会被系统忽略,因此不会影响字面量的值。同样地,也可以在数字前加 `0`,并不会影响字面量的值。
|
||||
|
||||
除非特别指定,浮点数字面量的默认推导类型为 Swift 标准库类型中的 `Double`,表示 64 位浮点数。Swift 标准库也定义了 `Float` 类型,表示 32 位浮点数。
|
||||
|
||||
> 浮点数字面量语法
|
||||
>
|
||||
>
|
||||
#### 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}
|
||||
>
|
||||
> *正负号* → **+** | **-**
|
||||
|
||||
### 字符串字面量 {#string-literal}
|
||||
|
||||
字符串字面量是被引号包括的一串字符组成。单行字符串字面量是被包在双引号中的一串字符组成,形式如下:
|
||||
|
||||
> "`字符`"
|
||||
|
||||
字符串字面量中不能包含未转义的双引号(`"`)、未转义的反斜线(`\`)、回车符、换行符。
|
||||
|
||||
多行字符串字面量被包在三个双引号中的一串字符组成,形式如下:
|
||||
> """
|
||||
> `字符`
|
||||
> """
|
||||
|
||||
与单行字符串字面量不同的是,多行字符串字面量可以包含不转义的双引号("),回车以及换行。它不能包含三个未转义的连续双引号。
|
||||
|
||||
`"""` 之后的回车或者换行开始多行字符串字面量,它们不是字符串的一部分。结束部分的 `"""` 以及它之前的回车或者换行,也不是字符串的一部分。要让多行字符串字面量的开始或结束带有换行,就在第一行或者最后一行写一个空行。
|
||||
|
||||
多行字符串字面量可以使用任何空格或制表符组合进行缩进;这些缩进不会包含在字符串中。`"""` 的结束符号决定了缩进:字面量中的每一个非空行开头都必须与结束符 `"""` 之前出现的缩进完全一致;空格和制表符不会被转换。你可以在缩进后包含额外的空格和制表符;这些空格和制表符会在字符串中出现。
|
||||
|
||||
多行字符串字面量中的一行结束使用规范化的换行符号。尽管你的源代码混用了回车和换行符,字符串中所有的行结束都必须一样.
|
||||
|
||||
在多行字符串字面量里,在行末用反斜线(`\`)可以省略字符串行间中断。反斜线和换行符之间的空白也将被忽略。你可以在你的代码里用这种语法硬包裹多行字符串字面量,而不改变结果字符串的值。
|
||||
|
||||
可以在字符串字面量中使用的转义特殊符号如下:
|
||||
|
||||
* 空字符 (`\0`)
|
||||
* 反斜线 (`\\`)
|
||||
* 水平制表符 (`\t`)
|
||||
* 换行符 (`\n`)
|
||||
* 回车符 (`\r`)
|
||||
* 双引号 (`\"`)
|
||||
* 单引号 (`\'`)
|
||||
* Unicode 标量 (`\u{`n`}`),n 为一到八位的十六进制数字
|
||||
|
||||
字符串字面量允许在反斜杠(`\`)后的括号 `()` 中插入表达式的值。插入表达式可以包含字符串字面量,但不能包含未转义的反斜线(`\`)、回车符以及换行符。
|
||||
|
||||
例如,以下所有字符串字面量的值都是相同的:
|
||||
|
||||
```swift
|
||||
"1 2 3"
|
||||
"1 2 \("3")"
|
||||
"1 2 \(3)"
|
||||
"1 2 \(1 + 2)"
|
||||
let x = 3; "1 2 \(x)"
|
||||
```
|
||||
|
||||
由扩展分隔符包裹的字符串,它是由引号以及成对出现的数字符号(`#`)包裹的字符串序列。由扩展分隔符包裹的字符串形式如下所示:
|
||||
|
||||
> \#"`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` 类型的信息请参考 [字符串和字符](../02_language_guide/03_Strings_and_Characters.md) 以及 [*字符串结构参考*](https://developer.apple.com/documentation/swift/string)。
|
||||
|
||||
用 `+` 操作符连接的字符型字面量是在编译时进行连接的。比如下面的 `textA` 和 `textB` 是完全一样的,`textA` 没有任何运行时的连接操作。
|
||||
|
||||
```swift
|
||||
let textA = "Hello " + "world"
|
||||
let textB = "Hello world"
|
||||
```
|
||||
|
||||
> 字符串字面量语法
|
||||
>
|
||||
> *字符串字面量* → [静态字符串字面量](#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 字符
|
||||
>
|
||||
>
|
||||
#### 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)
|
||||
|
||||
### 正则表达式字面量 {#regular-expression-literals}
|
||||
|
||||
正则表达式字面量是被符号 `/` 以如下形式包围的一串字符:
|
||||
|
||||
```swift
|
||||
/ 正则表达式 /
|
||||
```
|
||||
|
||||
正则表达式字面量不能以未转义的制表符或空格开头,且正则表达式字面量内不能包含未转义的 `/` 符号、回车或是换行符。
|
||||
|
||||
在正则表达式字面量内部,反斜杠符号 (`/`) 被视作正则表达式的一部分,而非如字符串表达式中一样被视为一个转义符。反斜杠符号提示其后的特殊字符应当按照其字面义进行解读,其后的非特殊字符则应按照特殊方式进行解读。例如,`/\(/` 匹配了一个左括号 (`(`), 而 `/\d/` 匹配了一个单个数字。
|
||||
|
||||
被一对反斜杠 `/` 和一对称数字符 `#` 包围的一串字符被称为由扩展分隔符分割的正则表达式字面量,它有以下两种形式:
|
||||
|
||||
```swift
|
||||
`#/ 正则表达式 /#`
|
||||
```
|
||||
```swift
|
||||
`#/
|
||||
正则表达式
|
||||
/#`
|
||||
```
|
||||
|
||||
由扩展分隔符分割的正则表达式字面量可以以未转义的制表符或是空格开头,也可以包含未转义的反斜杠符号 (`/`) 并占据多行代码。若需要占用多行,正则表达式字面量开头的扩展分隔符 `#` 应位于行末,末尾的扩展分隔符 `#` 应独占一行。在多行正则表达式字面量内默认启用了扩展正则表达式的语法规则——尤其是关于忽略空格并允许注释的部分。
|
||||
|
||||
如果你使用多个数字符号(`#`)来形成扩展分隔符分隔的正则表达式,数字符号不应使用空格分隔。
|
||||
|
||||
```swift
|
||||
let regex1 = ##/abc/## //正常运行
|
||||
let regex2 = # #/abc/# # //报错
|
||||
```
|
||||
|
||||
若需要创建一个空白的正则表达式字面量,必须使用扩展分隔符语法。
|
||||
|
||||
> 正则表达式字面量语法
|
||||
>
|
||||
> 正则表达式字面量 → [正则表达式字面量开分隔定界符](#regular-expression-literal-opening-delimiter) [正则表达式字面量](#regular-expression) [正则表达式字面量闭分隔定界符](#regular-expression-literal-closing-delimiter)
|
||||
>
|
||||
#### 正则表达式字面量 {#regular-expression}
|
||||
>
|
||||
> 正则表达式字面量 → 任何正则表达式
|
||||
>
|
||||
#### 正则表达式字面量开分隔定界符 {#regular-expression-literal-opening-delimiter}
|
||||
>
|
||||
> 正则表达式字面量开分隔定界符 → [扩展正则表达式分隔符](#extended-regular-expression-literal-delimiter)<sub>可选</sub> **/**
|
||||
>
|
||||
#### 正则表达式字面量闭分隔定界符 {#regular-expression-literal-closing-delimiter}
|
||||
>
|
||||
> 正则表达式字面量闭分隔定界符 → **/** [扩展正则表达式分隔符](#extended-regular-expression-literal-delimiter)<sub>可选</sub>
|
||||
>
|
||||
#### 扩展正则表达式分隔符 {#extended-regular-expression-literal-delimiter}
|
||||
>
|
||||
> 扩展正则表达式分隔符 → **#** [扩展正则表达式分隔定界符](#extended-regular-expression-literal-delimiter)<sub>可选</sub>
|
||||
>
|
||||
|
||||
## 运算符 {#operator}
|
||||
|
||||
Swift 标准库定义了许多可供使用的运算符,其中大部分在 [基础运算符](../02_language_guide/02_Basic_Operators.md) 和 [高级运算符](../02_language_guide/27_Advanced_Operators.md) 中进行了阐述。这一小节将描述哪些字符能用于自定义运算符。
|
||||
|
||||
自定义运算符可以由以下其中之一的 ASCII 字符 `/`、`=`、`-`、`+`、`!`、`*`、`%`、`<`、`>`、`&`、`|`、`^`、`?` 以及 `~`,或者后面语法中规定的任一个 Unicode 字符(其中包含了*数学运算符*、*零散符号(Miscellaneous Symbols)* 以及*印刷符号(Dingbats)*之类的 Unicode 块)开始。在第一个字符之后,允许使用组合型 Unicode 字符。
|
||||
|
||||
您也可以以点号(`.`)开头来定义自定义运算符。这些运算符可以包含额外的点。例如 `.+.` 会被看作一个单独的运算符。如果某个运算符不是以点号开头的,那么它就无法再包含另外的点号了。例如,`+.+` 就会被看作为一个 `+` 运算符后面跟着一个 `.+` 运算符。
|
||||
|
||||
虽然您可以用问号 `(?)` 来自定义运算符,但是这个运算符不能只包含单独的一个问号。此外,虽然运算符可以包含一个惊叹号 `(!)`,但是前缀运算符不能够以问号或者惊叹号开头。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 以下这些标记 `=`、`->`、`//`、`/*`、`*/`、`.`,前缀运算符 `<`、`&` 和 `?`,中缀运算符 `?`,后缀运算符 `>`、`!` 和 `?` 是被系统保留的。这些符号不能被重载,也不能用作自定义运算符。
|
||||
|
||||
运算符两侧的空白被用来区分该运算符是否为前缀运算符、后缀运算符或二元运算符。规则总结如下:
|
||||
|
||||
* 如果运算符两侧都有空白或两侧都无空白,将被看作二元运算符。例如:`a+++b` 和 `a +++ b` 当中的 `+++` 运算符会被看作二元运算符。
|
||||
* 如果运算符只有左侧空白,将被看作一元前缀运算符。例如 `a +++b` 中的 `+++` 运算符会被看做是一元前缀运算符。
|
||||
* 如果运算符只有右侧空白,将被看作一元后缀运算符。例如 `a+++ b` 中的 `+++` 运算符会被看作是一元后缀运算符。
|
||||
* 如果运算符左侧没有空白并紧跟 `(.)`,将被看作一元后缀运算符。例如 `a+++.b` 中的 `+++` 运算符会被视为一元后缀运算符(即上式被视为 `a+++ .b` 而不是 `a +++ .b`)。
|
||||
|
||||
鉴于这些规则,`(`、`[` 和 `{` 是在运算符前面,`)`、`]` 和 `}` 是在运算符后面,以及字符 `,`、`;` 和 `:` 都被视为空白。
|
||||
|
||||
以上规则需注意一点。如果预定义运算符 `!` 或 `?` 左侧没有空白,则不管右侧是否有空白都将被看作后缀运算符。如果将 `?` 用作可选链式调用运算符,左侧必须无空白。如果用于条件运算符 `(? :)`,必须两侧都有空白。
|
||||
|
||||
如果中缀操作符的参数之一是正则表达式字面量,则操作符两侧都必须有空格。
|
||||
|
||||
在某些特定的设计中,以 `<` 或 `>` 开头的运算符会被分离成两个或多个符号。剩余部分可能会以同样的方式被再次分离。因此,在 `Dictionary<String, Array<Int>>` 中没有必要添加空白来消除闭合字符 `>` 的歧义。在这个例子中,闭合字符 `>` 不会被视为单独的符号,因而不会被错误解析为 `>>` 运算符。
|
||||
|
||||
要学习如何自定义运算符,请参考 [自定义运算符](../02_language_guide/27_Advanced_Operators.md#custom-operators) 和 [运算符声明](./06_Declarations.md#operator-declaration)。要学习如何重载运算符,请参考 [运算符函数](../02_language_guide/27_Advanced_Operators.md#operator-functions)。
|
||||
|
||||
> 运算符语法
|
||||
>
|
||||
> *运算符* → [头部运算符](#operator-head) [运算符字符组](#operator-characters)<sub>可选</sub>
|
||||
>
|
||||
> *运算符* → [头部点运算符](#dot-operator-head) [点运算符字符组](#dot-operator-characters)
|
||||
>
|
||||
>
|
||||
#### operator-head {#operator-head}
|
||||
>
|
||||
> *头部运算符* → **/** | **=** | **-** | **+** | **!** | __*__ | **%** | **<** | **>** | **&** | **|** | **^** | **~** | **?**
|
||||
>
|
||||
> *头部运算符* → U+00A1–U+00A7
|
||||
>
|
||||
> *头部运算符* → U+00A9 或 U+00AB
|
||||
>
|
||||
> *头部运算符* → U+00AC 或 U+00AE
|
||||
>
|
||||
> *头部运算符* → U+00B0–U+00B1
|
||||
>
|
||||
> *头部运算符* → U+00B6,U+00BB,U+00BF,U+00D7,或 U+00F7
|
||||
>
|
||||
> *头部运算符* → U+2016–U+2017
|
||||
>
|
||||
> *头部运算符* → U+2020–U+2027
|
||||
>
|
||||
> *头部运算符* → U+2030–U+203E
|
||||
>
|
||||
> *头部运算符* → U+2041–U+2053
|
||||
>
|
||||
> *头部运算符* → U+2055–U+205E
|
||||
>
|
||||
> *头部运算符* → U+2190–U+23FF
|
||||
>
|
||||
> *头部运算符* → U+2500–U+2775
|
||||
>
|
||||
> *头部运算符* → U+2794–U+2BFF
|
||||
>
|
||||
> *头部运算符* → U+2E00–U+2E7F
|
||||
>
|
||||
> *头部运算符* → U+3001–U+3003
|
||||
>
|
||||
> *头部运算符* → U+3008–U+3020
|
||||
>
|
||||
> *头部运算符* → U+3030
|
||||
>
|
||||
>
|
||||
#### operator-character {#operator-character}
|
||||
>
|
||||
> *运算符字符* → [头部运算符](#operator-head)
|
||||
>
|
||||
> *运算符字符* → U+0300–U+036F
|
||||
>
|
||||
> *运算符字符* → U+1DC0–U+1DFF
|
||||
>
|
||||
> *运算符字符* → U+20D0–U+20FF
|
||||
>
|
||||
> *运算符字符* → U+FE00–U+FE0F
|
||||
>
|
||||
> *运算符字符* → U+FE20–U+FE2F
|
||||
>
|
||||
> *运算符字符* → U+E0100–U+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>
|
||||
>
|
||||
> *二元运算符* → [运算符](#operator)
|
||||
>
|
||||
> *前缀运算符* → [运算符](#operator)
|
||||
>
|
||||
> *后缀运算符* → [运算符](#operator)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
799
source/03_language_reference/07_Attributes.md
Executable file
799
source/03_language_reference/07_Attributes.md
Executable file
@ -0,0 +1,799 @@
|
||||
# 特性(Attributes)
|
||||
|
||||
在 Swift 中有两种特性,分别用于修饰声明和类型。特性提供了有关声明和类型的更多信息。例如,使用 `discardableResult` 特性声明的函数,表明该函数虽然有返回值,但如果没有使用该返回值,编译器不会产生警告。
|
||||
|
||||
您可以通过以下方式指定一个特性,通过符号 `@` 后跟特性的名称和特性接收的任何参数:
|
||||
|
||||
@`特性名`
|
||||
|
||||
@`特性名`(`特性参数`)
|
||||
|
||||
有些声明特性通过接收参数来指定特性的更多信息以及它是如何修饰某个特定的声明的。这些_特性的参数_写在圆括号内,它们的格式由它们所属的特性来定义。
|
||||
|
||||
## 声明特性 {#declaration-attributes}
|
||||
|
||||
声明特性只能应用于声明。
|
||||
|
||||
### `available` {#available}
|
||||
|
||||
将 `available` 特性用于声明时,表示该声明的生命周期是相对于特定的平台和操作系统版本。
|
||||
|
||||
`available` 特性经常与参数列表一同出现,该参数列表至少有两个特性参数,参数之间由逗号分隔。这些参数由以下这些平台名字中的一个起头:
|
||||
|
||||
- `iOS`
|
||||
- `iOSApplicationExtension`
|
||||
- `macOS`
|
||||
- `macOSApplicationExtension`
|
||||
- `macCatalyst`
|
||||
- `macCatalystApplicationExtension`
|
||||
- `watchOS`
|
||||
- `watchOSApplicationExtension`
|
||||
- `tvOS`
|
||||
- `tvOSApplicationExtension`
|
||||
- `swift`
|
||||
|
||||
当然,你也可以用一个星号(`*`)来表示上面提到的所有平台。指定 Swift 版本的 `available` 特性参数,不能使用星号表示。
|
||||
|
||||
其余的参数,可以按照任何顺序出现,并且可以添加关于声明生命周期的附加信息,包括重要事件。
|
||||
|
||||
- `unavailable` 参数表示该声明在指定的平台上是无效的。当指定 Swift 版本可用性时不可使用该参数。
|
||||
- `introduced` 参数表示指定平台从哪一版本开始引入该声明。格式如下:
|
||||
|
||||
`introduced`: `版本号`
|
||||
|
||||
*版本号*由一至三个正整数构成,由句点分隔的。
|
||||
|
||||
- `deprecated` 参数表示指定平台从哪一版本开始弃用该声明。格式如下:
|
||||
|
||||
`deprecated`: `版本号`
|
||||
|
||||
可选的*版本号*由一个或多个正整数构成,由句点分隔的。省略版本号表示该声明目前已弃用,而不提供有关弃用发生时间的任何信息。如果你省略了版本号,冒号(`:`)也可省略。
|
||||
|
||||
- `obsoleted` 参数表示指定平台或语言从哪一版本开始废弃该声明。当一个声明被废弃后,它就从平台或语言中移除,不能再被使用。格式如下:
|
||||
|
||||
`obsoleted`: `版本号`
|
||||
|
||||
*版本号*由一至三个正整数构成,由句点分隔的。
|
||||
|
||||
- `message` 参数用来提供文本信息。当使用被弃用或者被废弃的声明时,编译器会抛出该信息作为警告或错误。格式如下:
|
||||
|
||||
`message`: `信息内容`
|
||||
|
||||
_信息内容_由一个字符串构成。
|
||||
|
||||
- `renamed` 参数用来提供文本信息,用以表示被重命名声明的新名字。当使用声明的旧名字时,编译器会报错并提示新名字。格式如下:
|
||||
|
||||
`renamed`: `新名字`
|
||||
|
||||
_新名字_由一个字符串构成。
|
||||
|
||||
你可以将带有 `renamed` 和 `unavailable` 参数的 `available` 特性应用于类型别名声明,如下所示,来表明框架和库发行版本之间的声明名称已经被更改。这个组合会导致声明已重命名的编译时错误。
|
||||
|
||||
```swift
|
||||
// 首发版本
|
||||
protocol MyProtocol {
|
||||
// 这里是协议定义
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
// 后续版本重命名了 MyProtocol
|
||||
protocol MyRenamedProtocol {
|
||||
// 这里是协议定义
|
||||
}
|
||||
@available(*, unavailable, renamed:"MyRenamedProtocol")
|
||||
typealias MyProtocol = MyRenamedProtocol
|
||||
```
|
||||
|
||||
你可以在某个声明上使用多个 `available` 特性,以指定该声明在不同平台和 Swift 版本上的可用性。如果当前目标与 `available` 特性中指定的平台或语言版本不匹配时,该声明将会被忽略。如果你使用了多个 `available` 特性,则最终效果是平台和 Swift 可用性的组合。
|
||||
|
||||
如果 `available` 特性除了平台名称或者语言版本参数之外,只指定了一个 `introduced` 参数,那么可以使用以下简写语法代替:
|
||||
|
||||
@available(`平台名称` `版本号`, *)
|
||||
|
||||
@available(swift `版本号`)
|
||||
|
||||
`available` 特性的简写语法简洁的表示了多个平台的可用性。尽管这两种形式在功能上是相同的,但请尽可能地使用简写语法形式。
|
||||
|
||||
```swift
|
||||
@available(iOS 10.0, macOS 10.12, *)
|
||||
class MyClass {
|
||||
// 这里是类定义
|
||||
}
|
||||
```
|
||||
|
||||
当需要同时指定 Swift 版本和平台可用性,需要使用一个单独的 `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]`,而返回值类型可以是任意类型。
|
||||
|
||||
如果实现 `dynamicallyCall(withKeywordArguments:)` 方法,则可以在动态方法调用中包含标签。
|
||||
|
||||
```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(dynamicMember:)` 下标。
|
||||
|
||||
在显式成员表达式中,如果指定成员没有相应的声明,则该表达式被理解为对该类型的 `subscript(dynamicMember:)` 下标调用,将有关该成员的信息作为参数传递。下标接收参数既可以是键路径,也可以是成员名称字符串;如果你同时实现这两种方式的下标调用,那么以键路径参数方式为准。
|
||||
|
||||
`subscript(dynamicMember:)` 实现允许接收 [`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)
|
||||
```
|
||||
|
||||
### `frozen` {#frozen}
|
||||
|
||||
针对枚举或者结构体的声明使用该特性,可以限制你对该类型的修改。它只有在编译迭代库时被允许使用。未来版本的库不能通过添加、删除或重新排序枚举的 case 或结构的存储实例属性来更改声明。在未冻结的类型上,这些操作都是允许的,但是他们破坏了冻结类型的 ABI 兼容性。
|
||||
|
||||
> 注意
|
||||
> 当编译器不处于迭代库的模式,所有的结构体和枚举都是隐性冻结,并且该特性会被忽视。
|
||||
|
||||
在迭代库的模式中,与未冻结结构体和枚举的成员进行交互的代码在被编译时,允许它在不重新编译的情况下继续工作,即使在新版本的库中添加、删除或重新排序该类型的成员。编译器用类似运行时查找信息和添加间接层的技术使之可能。将一个枚举或者结构体标记为冻结将以放弃这种灵活性为代价来获取性能上的提升:未来版本的库只能对类型进行有限的更改,但编译器可以对与类型成员交互的代码进行额外的优化。
|
||||
|
||||
使用冻结类型,结构体存储属性的类型以及枚举 case 的关联值必须是 `public` 或使用 `usablefrominline` 特性标记。冻结结构体的属性不能有属性观察器,为存储实例属性提供初始值的表达式必须遵循与 `inlinable` 函数相同的限制,如 [`inlinable`](#inlinable) 中所述。
|
||||
|
||||
要在命令行上启用迭代库模式,请将 `-enable-library-evolution` 选项传递给 Swift 编译器。要在 Xcode 中支持它,则将生成设置 “Build Libraries for Distribution”(`BUILD_LIBRARY_FOR_DISTRIBUTION`)设置为 Yes,详情查看 [`Xcode 帮助文档`](https://help.apple.com/xcode/mac/current/#/dev04b3a04ba)。
|
||||
|
||||
针对冻结枚举的 switch 语法,不需要 `default` case,就像 [`对未来枚举的 case 进行 switch`](./05_Statements.md#future-case)。在针对冻结枚举使用 switch 语法时包含 `default` 或 `@unknown default` case 将生成警告,因为该代码永远不会执行。
|
||||
|
||||
### `GKInspectable` {#gkinspectable}
|
||||
|
||||
应用此特性可将自定义 GameplayKit 组件属性公开到 SpriteKit 编辑器 UI。应用此特性同时表示应用了 `objc` 特性。
|
||||
|
||||
### `inlinable` {#inlinable}
|
||||
|
||||
该特性用于函数、方法、计算属性、下标、便利构造器或析构器的声明,以将该声明的实现公开为模块公开接口的一部分。编译器允许在调用处把 `inlinable` 标记的符号替换为符号实现的副本。
|
||||
|
||||
内联代码可以与任意模块中 `public` 访问级别的符号进行交互,同时可以与在相同模块中标记 `usableFromInline` 特性的 `internal` 访问级别的符号进行交互。内联代码不能与 `private` 或 `fileprivate` 级别的符号进行交互。
|
||||
|
||||
该特性不能用于嵌套在函数内的声明,也不能用于 `fileprivate` 或 `private` 访问级别的声明。在内联函数内定义的函数和闭包都是隐式内联的,即使他们不能标记该特性。
|
||||
|
||||
### `main` {#main}
|
||||
|
||||
将该特性用于结构体、类,或枚举的声明,表示它包含了程序流的顶级入口。类型必须提供一个不接收任何参数,且返回值为 `Void` 的 `main` 类型函数。如下所示:
|
||||
|
||||
```swift
|
||||
@main
|
||||
struct MyTopLevel {
|
||||
static func main() {
|
||||
// 顶级代码从这里开始
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
换一个说法,应用了 `main` 特性的类型所必须满足的条件,与遵循下面这个假想的协议一样:
|
||||
|
||||
```swift
|
||||
protocol ProvidesMain {
|
||||
static func main() throws
|
||||
}
|
||||
```
|
||||
|
||||
编译生成可执行程序的 Swift 代码最多只能拥有一个顶级代码入口,请参阅[顶级代码](../03_language_reference/06_Declarations.md#top-level-code)。
|
||||
|
||||
### `nonobjc` {#nonobjc}
|
||||
|
||||
针对方法、属性、下标、或构造器的声明使用该特性将覆盖隐式的 `objc` 特性。`nonobjc` 特性告诉编译器该声明不能在 Objective-C 代码中使用,即便它能在 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)
|
||||
```
|
||||
|
||||
编译生成可执行程序的 Swift 代码最多只能拥有一个顶级代码入口,请参阅[顶级代码](../03_language_reference/06_Declarations.md#top-level-code)。
|
||||
|
||||
### `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` 特性应用于枚举,每一个枚举 case 都会以枚举名称和 case 名称组合的方式暴露在 Objective-C 代码中。实例名称的首字母大写。例如,在 Swift 枚举类型 `Planet` 中有一个名为 `Venus` 的 case,该 case 暴露在 Objective-C 代码中时叫做 `PlanetVenus`。
|
||||
|
||||
`objc` 特性可以接受一个可选的特性参数,由标识符构成。当你想把 `objc` 所修饰的实体以一个不同的名字暴露给 Objective-C 时,你就可以使用这个特性参数。你可以使用这个参数来命名类、枚举类型、枚举 case、协议、方法、存取方法以及构造器。如果你要指定类、协议或枚举在 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 {
|
||||
// 返回适当的值
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
更多相关信息,请参考 [把 Swift 导入 Objective-C](https://developer.apple.com/documentation/swift/imported_c_and_objective-c_apis/importing_swift_into_objective-c)。
|
||||
|
||||
> 注意
|
||||
> 具有 `objc` 特性的实参也会改变那个声明的运行时名称。在调用与 Objective-C 运行时交互的函数时,比如 [NSClassFromString](https://developer.apple.com/documentation/foundation/1395135-nsclassfromstring),以及在应用程序的 info.plist 文件中指定类名时,你会用到运行时名称。如果你通过传递实参的方式来指定名称,这个名称会作为 Objective-C 代码中的名称和运行时名称。如果你不使用这个实参,在 Objective-C 代码中使用的名称会与 Swift 代码中的名称匹配,并且运行时名称会遵循标准 Swift 编译器名称管理的惯例。
|
||||
|
||||
### `objcMembers` {#objcmembers}
|
||||
|
||||
该特性用于类声明,以将 `objc` 特性应用于该类、扩展、子类以及子类的扩展的所有 Objective-C 兼容成员。
|
||||
|
||||
大多数代码应该使用 `objc` 特性,以暴露所需的声明。如果需要暴露多个声明,可以将其分组到添加 `objc` 特性的扩展中。`objcMembers` 特性为大量使用 Objective-C 运行时的内省工具的库提供了便利。添加不必要的 `objc` 特性会增加二进制体积并影响性能。
|
||||
|
||||
### `propertyWrapper` {#propertywrapper}
|
||||
|
||||
在类、结构体或者枚举的声明时使用该特性,可以让其成为一个属性包装器。如果将该特性应用在一个类型上,将会创建一个与该类型同名的自定义特性。将这个新的特性用于类、结构体、枚举的属性,则可以通过包装器的实例封装对该属性的访问;本地存储变量声明也能利用这个特性完成对它的访问封装。局部和全局变量不能使用属性包装器。
|
||||
|
||||
包装器必须定义一个 `wrappedValue` 实例属性。该属性 _wrapped value_ 是该属性存取方法暴露的值。大多数时候,`wrappedValue` 是一个计算属性,但它可以是一个存储属性。包装器负责定义和管理其包装值所需的任何底层存储。编译器通过在包装属性的名称前加下划线(`_`)来为包装器的实例提供同步存储。例如,`someProperty` 的包装器存储为 `_someProperty`。包装器的同步存储具有 `private` 的访问控制级别。
|
||||
|
||||
拥有属性包装器的属性可以包含 `willSet` 和 `didSet` 闭包,但是不能重写编译器合成的 `get` 和 `set` 闭包。
|
||||
|
||||
Swift 为属性包装器的构造函数提供了两种形式的语法糖。可以在包装值的定义中使用赋值语法,将赋值语句右侧的表达式作为值传递给属性包装器构造函数中的 `wrappedValue` 参数。同样的,你也可以为包装器提供一些参数,这些参数将会传递给包装器的构造函数。就像下面的例子,`SomeStruct` 中,定义 `SomeWrapper` 的地方各自调用了对应的构造函数。
|
||||
|
||||
```swift
|
||||
@propertyWrapper
|
||||
struct SomeWrapper {
|
||||
var wrappedValue: Int
|
||||
var someValue: Double
|
||||
init() {
|
||||
self.wrappedValue = 100
|
||||
self.someValue = 12.3
|
||||
}
|
||||
init(wrappedValue: Int) {
|
||||
self.wrappedValue = wrappedValue
|
||||
self.someValue = 45.6
|
||||
}
|
||||
init(wrappedValue value: Int, custom: Double) {
|
||||
self.wrappedValue = value
|
||||
self.someValue = custom
|
||||
}
|
||||
}
|
||||
|
||||
struct SomeStruct {
|
||||
// 使用 init()
|
||||
@SomeWrapper var a: Int
|
||||
|
||||
// 使用 init(wrappedValue:)
|
||||
@SomeWrapper var b = 10
|
||||
|
||||
// 两个都是使用 init(wrappedValue:custom:)
|
||||
@SomeWrapper(custom: 98.7) var c = 30
|
||||
@SomeWrapper(wrappedValue: 30, custom: 98.7) var d
|
||||
}
|
||||
```
|
||||
|
||||
属性包装器中 _projected value_ 是它可以用来暴露额外功能的第二个值。属性包装器的作者负责确认其映射值的含义并定义公开映射值的接口。若要通过属性包装器来映射值,请在包装器的类型上定义 `projectedValue` 实例属性。编译器通过在包装属性的名称前面加上美元符号(`$`)来合成映射值的标识符。例如,`someProperty` 的映射值是 `$someProperty`。映射值具有与原始包装属性相同的访问控制级别。
|
||||
|
||||
```swift
|
||||
@propertyWrapper
|
||||
struct WrapperWithProjection {
|
||||
var wrappedValue: Int
|
||||
var projectedValue: SomeProjection {
|
||||
return SomeProjection(wrapper: self)
|
||||
}
|
||||
}
|
||||
struct SomeProjection {
|
||||
var wrapper: WrapperWithProjection
|
||||
}
|
||||
|
||||
struct SomeStruct {
|
||||
@WrapperWithProjection var x = 123
|
||||
}
|
||||
let s = SomeStruct()
|
||||
s.x // Int value
|
||||
s.$x // SomeProjection value
|
||||
s.$x.wrapper // WrapperWithProjection value
|
||||
```
|
||||
|
||||
### `resultBuilder` {#result-builder}
|
||||
|
||||
将该特性应用于类、结构体或者枚举,可以把它作为结果构造器使用。结果构造器能按顺序构造一组嵌套的数据结构。利用它,可以以一种自然的声明式语法为嵌套数据结构实现一套领域专门语言(DSL)。想了解如何使用 `resultBuild`,请参阅 [结果构造器](../02_language_guide/27_Advanced_Operators.md#result-builders)。
|
||||
|
||||
#### 结果构造方法 {#result-building-methods}
|
||||
|
||||
结果构造器实现了下面的静态方法。它所有的功能已经通过静态方法暴露了,因此不需要初始化一个实例。`buildBlock(_:)` 方法是必须实现的,而那些能让领域专门语言(DSL)拥有额外能力的方法则是可选的。事实上,结果构造器类型的声明不需要遵循任何协议。
|
||||
|
||||
这些静态方法的描述中用到了三种类型。`Expression` 为构造器的输入类型,`Component` 为中间结果类型,`FinalResult` 为构造器最终生成结果的类型。将它们替换成你构造器所使用的类型。如果结果构造方法中没有明确 `Expression` 或 `FinalResult`,默认会使用 `Component` 的类型。
|
||||
|
||||
结果构造方法如下:
|
||||
|
||||
- `static func buildBlock(_ components: Component...) -> Component`
|
||||
将可变数量的中间结果合并成一个中间结果。必须实现这个方法。
|
||||
- `static func buildOptional(_ component: Component?) -> Component`
|
||||
将可选的中间结果构造成新的中间结果。用于支持不包含 `else` 闭包的 `if` 表达式。
|
||||
- `static func buildEither(first: Component) -> Component`
|
||||
构造一个由条件约束的中间结果。同时实现它与 `buildEither(second:)` 来支持 `switch` 与包含 `else` 闭包的 `if` 表达式。
|
||||
- `static func buildEither(second: Component) -> Component`
|
||||
构造一个由条件约束的中间结果。同时实现它与 `buildEither(first:)` 来支持 `switch` 与包含 `else` 闭包的 `if` 表达式。
|
||||
- `static func buildArray(_ components: [Component]) -> Component`
|
||||
将中间结果数组构造成中间结果。用于支持 `for` 循环。
|
||||
- `static func buildExpression(_ expression: Expression) -> Component`
|
||||
将表达式构造成中间结果。利用它来执行预处理,比如,将入参转换为内部类型,或为使用方提供额外的类型推断信息。
|
||||
- `static func buildFinalResult(_ component: Component) -> FinalResult`
|
||||
将中间结果构造成最终结果。可以在中间结果与最终结果类型不一致的结果构造器中实现这个方法,或是在最终结果返回前对它做处理。
|
||||
- `static func buildLimitedAvailability(_ component: Component) -> Component`
|
||||
构建中间结果,用于在编译器控制(compiler-control)语句进行可用性检查之外,传递或擦除类型信息。可以在不同的条件分支上擦除类型信息。(译者注:这里的编译器控制语句主要指 `if #available`,参考 https://github.com/apple/swift-evolution/blob/main/proposals/0289-result-builders.md#result-building-methods)
|
||||
|
||||
下面的代码定义了一个简易的结果构造器,用于构造整型数组。类型别名明确了 `Compontent` 与 `Expression`,以一种简单的方式让下面的例子满足上面的方法。
|
||||
|
||||
```swift
|
||||
@resultBuilder
|
||||
struct ArrayBuilder {
|
||||
typealias Component = [Int]
|
||||
typealias Expression = Int
|
||||
static func buildExpression(_ element: Expression) -> Component {
|
||||
return [element]
|
||||
}
|
||||
static func buildOptional(_ component: Component?) -> Component {
|
||||
guard let component = component else { return [] }
|
||||
return component
|
||||
}
|
||||
static func buildEither(first component: Component) -> Component {
|
||||
return component
|
||||
}
|
||||
static func buildEither(second component: Component) -> Component {
|
||||
return component
|
||||
}
|
||||
static func buildArray(_ components: [Component]) -> Component {
|
||||
return Array(components.joined())
|
||||
}
|
||||
static func buildBlock(_ components: Component...) -> Component {
|
||||
return Array(components.joined())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 结果转换 {#result-transformations}
|
||||
|
||||
使用了结果构造器语法的代码会被递归地转换,成为对结果构造器类型静态方法的调用:
|
||||
|
||||
- 如果结果构造器实现了 `buildExpression(_:)` 方法,每个表达式都会转换成一次方法调用。这是转换的第一步。下面的声明方式是等价的:
|
||||
|
||||
```swift
|
||||
@ArrayBuilder var builderNumber: [Int] { 10 }
|
||||
var manualNumber = ArrayBuilder.buildExpression(10)
|
||||
```
|
||||
|
||||
- 赋值语句也会像表达式一样被转换,但它的类型会被当作 `()`。重载 `buildExpression(_:)` 方法,接收实参类型为 `()` 来特殊处理赋值。(译者注:重载方法参考 https://github.com/apple/swift-evolution/blob/main/proposals/0289-result-builders.md#assignments)
|
||||
|
||||
- 分支语句中对可用性的检查会转换成 `buildLimitedAvailability(_:)` 调用。这个转换早于 `buildEither(first:)`、`buildEither(second:)` 或 `buildOptional(_:)`。类型信息会因进入的条件分支不同发生改变,使用 `buildLimitedAvailability(_:)` 方法可以将它擦除。下面的实例中 `buildEither(first:)` 和 `buildEither(second:)` 使用泛型捕获了不同条件分支的具体类型信息。
|
||||
|
||||
```swift
|
||||
protocol Drawable {
|
||||
func draw() -> String
|
||||
}
|
||||
struct Text: Drawable {
|
||||
var content: String
|
||||
init(_ content: String) { self.content = content }
|
||||
func draw() -> String { return content }
|
||||
}
|
||||
struct Line<D: Drawable>: Drawable {
|
||||
var elements: [D]
|
||||
func draw() -> String {
|
||||
return elements.map { $0.draw() }.joined(separator: "")
|
||||
}
|
||||
}
|
||||
struct DrawEither<First: Drawable, Second: Drawable>: Drawable {
|
||||
var content: Drawable
|
||||
func draw() -> String { return content.draw() }
|
||||
}
|
||||
|
||||
@resultBuilder
|
||||
struct DrawingBuilder {
|
||||
static func buildBlock<D: Drawable>(_ components: D...) -> Line<D> {
|
||||
return Line(elements: components)
|
||||
}
|
||||
static func buildEither<First, Second>(first: First)
|
||||
-> DrawEither<First, Second> {
|
||||
return DrawEither(content: first)
|
||||
}
|
||||
static func buildEither<First, Second>(second: Second)
|
||||
-> DrawEither<First, Second> {
|
||||
return DrawEither(content: second)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
然而,在面对有可用性检测的代码时会出现问题:
|
||||
|
||||
```swift
|
||||
@available(macOS 99, *)
|
||||
struct FutureText: Drawable {
|
||||
var content: String
|
||||
init(_ content: String) { self.content = content }
|
||||
func draw() -> String { return content }
|
||||
}
|
||||
@DrawingBuilder var brokenDrawing: Drawable {
|
||||
if #available(macOS 99, *) {
|
||||
FutureText("Inside.future") // 问题所在
|
||||
} else {
|
||||
Text("Inside.present")
|
||||
}
|
||||
}
|
||||
// brokenDrawing 的类型是 Line<DrawEither<Line<FutureText>, Line<Text>>>
|
||||
```
|
||||
|
||||
上面的代码中,由于 `FutureText` 是 `DrawEither` 泛型的一个类型,它成为了 `brokenDrawing` 类型的组成部分。`FutureText` 如果在运行时不可用会引起程序的崩溃,即使它没有被显式使用。
|
||||
|
||||
为了解决这个问题,实现 `buildLimitedAvailability(_:)` 方法擦除类型信息。举例来说,下面的代码构造了一个 `AnyDrawable` 用于可用性检查。
|
||||
|
||||
```swift
|
||||
struct AnyDrawable: Drawable {
|
||||
var content: Drawable
|
||||
func draw() -> String { return content.draw() }
|
||||
}
|
||||
extension DrawingBuilder {
|
||||
static func buildLimitedAvailability(_ content: Drawable) -> AnyDrawable {
|
||||
return AnyDrawable(content: content)
|
||||
}
|
||||
}
|
||||
|
||||
@DrawingBuilder var typeErasedDrawing: Drawable {
|
||||
if #available(macOS 99, *) {
|
||||
FutureText("Inside.future")
|
||||
} else {
|
||||
Text("Inside.present")
|
||||
}
|
||||
}
|
||||
// typeErasedDrawing 的类型是 Line<DrawEither<AnyDrawable, Line<Text>>>
|
||||
```
|
||||
|
||||
- 分支语句会被转换成一连串 `buildEither(first:)` 与 `buildEither(second:)` 的方法调用。语句的不同条件分支会被映射到一颗二叉树的叶子结点上,语句则变成了从根节点到叶子结点路径的嵌套 `buildEither` 方法调用。
|
||||
|
||||
举例来说,如果你的 `switch` 语句有三个条件分支,编译器会生成一颗拥有 3 个叶子结点的二叉树。同样地,从根节点到第二个分支相当于走了“第二个子节点”再到“第一个子节点”这样的路径,这种情况会转化成 `buildEither(first: buildEither(second: ... ))` 这样的嵌套调用。下面的声明方式是等价的:
|
||||
|
||||
```swift
|
||||
let someNumber = 19
|
||||
@ArrayBuilder var builderConditional: [Int] {
|
||||
if someNumber < 12 {
|
||||
31
|
||||
} else if someNumber == 19 {
|
||||
32
|
||||
} else {
|
||||
33
|
||||
}
|
||||
}
|
||||
|
||||
var manualConditional: [Int]
|
||||
if someNumber < 12 {
|
||||
let partialResult = ArrayBuilder.buildExpression(31)
|
||||
let outerPartialResult = ArrayBuilder.buildEither(first: partialResult)
|
||||
manualConditional = ArrayBuilder.buildEither(first: outerPartialResult)
|
||||
} else if someNumber == 19 {
|
||||
let partialResult = ArrayBuilder.buildExpression(32)
|
||||
let outerPartialResult = ArrayBuilder.buildEither(second: partialResult)
|
||||
manualConditional = ArrayBuilder.buildEither(first: outerPartialResult)
|
||||
} else {
|
||||
let partialResult = ArrayBuilder.buildExpression(33)
|
||||
manualConditional = ArrayBuilder.buildEither(second: partialResult)
|
||||
}
|
||||
```
|
||||
|
||||
- 分支语句不一定会产生值,就像没有 `else` 闭包的 `if` 语句,会转换成 `buildOptional(_:)` 方法调用。如果 `if` 语句满足条件,它的代码块会被转换,作为实参进行传递;否则,`buildOptional(_:)` 会被调用,并用 `nil` 作为它的实参。下面的声明方式是等价的:
|
||||
|
||||
```swift
|
||||
@ArrayBuilder var builderOptional: [Int] {
|
||||
if (someNumber % 2) == 1 { 20 }
|
||||
}
|
||||
|
||||
var partialResult: [Int]? = nil
|
||||
if (someNumber % 2) == 1 {
|
||||
partialResult = ArrayBuilder.buildExpression(20)
|
||||
}
|
||||
var manualOptional = ArrayBuilder.buildOptional(partialResult)
|
||||
```
|
||||
|
||||
- 代码块或 `do` 语句会转换成 `buildBlock(_:)` 调用。闭包中的每一条语句都会被转换,完成后作为实参传入 `buildBlock(_:)`。下面的声明方式是等价的:
|
||||
|
||||
```swift
|
||||
@ArrayBuilder var builderBlock: [Int] {
|
||||
100
|
||||
200
|
||||
300
|
||||
}
|
||||
|
||||
var manualBlock = ArrayBuilder.buildBlock(
|
||||
ArrayBuilder.buildExpression(100),
|
||||
ArrayBuilder.buildExpression(200),
|
||||
ArrayBuilder.buildExpression(300)
|
||||
)
|
||||
```
|
||||
|
||||
- `for` 循环的转换分为三个部分:一个临时数组,一个 `for` 循环,以及一次 `buildArray(_:)` 方法调用。新的 `for` 循环会遍历整个序列,并把每一个中间结果存入临时数组。临时数组会作为实参传递给 `buildArray(_:)` 调用。下面的声明方式是等价的:
|
||||
|
||||
```swift
|
||||
@ArrayBuilder var builderArray: [Int] {
|
||||
for i in 5...7 {
|
||||
100 + i
|
||||
}
|
||||
}
|
||||
|
||||
var temporary: [[Int]] = []
|
||||
for i in 5...7 {
|
||||
let partialResult = ArrayBuilder.buildExpression(100 + i)
|
||||
temporary.append(partialResult)
|
||||
}
|
||||
let manualArray = ArrayBuilder.buildArray(temporary)
|
||||
```
|
||||
|
||||
- 如果结果构造器实现了 `buildFinalResult(_:)` 方法,最终结果会转换成对于这个方法的调用。它永远最后执行。
|
||||
|
||||
虽然转换行为描述中会出现临时变量,但结果构造器不会创建任何对代码可见的临时变量。
|
||||
|
||||
不能在结果构造器的转换中使用 `break`、`continue`、`defer`、`guard`,或是 `return` 语句、`while` 语句、`do-catch` 语句。
|
||||
|
||||
转换过程不会改变代码中的声明,这样就可以使用临时常量或变量一步一步构造表达式。它也不会改变 `throw` 语句、编译时诊断语句,或包含 `return` 语句的闭包。
|
||||
|
||||
在可能的情况下转换会被合并。例如,表达式 `4 + 5 * 6` 会被直接转换成 `buildExpression(4 + 5 * 6)` 而不是多个函数调用。同样地,嵌套分支语句也只会变成一颗调用 `buildEither` 方法的二叉树。
|
||||
|
||||
#### 自定义结果构造器特性 {#custom-result-builder-attributes}
|
||||
|
||||
创建一个结果构造器类型的同时也创建了一个同名的自定义特性。你可以将它应用于如下场景:
|
||||
|
||||
- 用于函数声明时,会构造函数体
|
||||
- 用于变量或包含 getter 方法的下标声明时,会构造 getter 的函数体
|
||||
- 用于函数声明中的形参时,会构造传递相应实参的闭包
|
||||
|
||||
应用结果构造器特性并不会影响 ABI 稳定。在形参上应用结果构造器,会把这个特性变成函数接口的一部分,从而影响源码稳定性。
|
||||
|
||||
### `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(_:_:_:_:)` 函数而不是使用该特性。
|
||||
|
||||
编译生成可执行程序的 Swift 代码最多只能拥有一个顶级代码入口,请参阅[顶级代码](../03_language_reference/06_Declarations.md#top-level-code)。
|
||||
|
||||
### `unchecked` {#unchecked}
|
||||
|
||||
该特性用于协议类型,将其作为已采用协议的类型声明列表的一部分,以关闭该协议要求的强制执行。
|
||||
|
||||
仅支持 [Sendable](https://developer.apple.com/documentation/swift/sendable) 协议。
|
||||
|
||||
### `usableFromInline` {#usablefrominline}
|
||||
|
||||
该特性用于函数、方法、计算属性、下标、构造器或析构器的声明,以在同一模块中允许该符号用于内联代码的声明。声明必须具有 `internal` 访问级别修饰符。被标记为 `usableFromInline` 的结构体或类它们属性的类型只能是被标记为 public 或者 `usableFromInline` 的类型。被标记为 `usableFromInline` 的枚举,它 case 的真实值或者关联类型只能是被标记为 public 或者 `usableFromInline` 的类型。
|
||||
|
||||
与 `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` 和 `IBSegueAction` 特性用于修饰一个类的方法声明,`IBDesignable` 用于修饰类的声明。
|
||||
|
||||
应用 `IBAction`、`IBSegueAction`、`IBOutlet`、`IBDesignable` 或者 `IBInspectable` 特性都意味着同时应用 `objc` 特性。
|
||||
|
||||
## 类型特性 {#type-attributes}
|
||||
|
||||
类型特性只能用于修饰类型。
|
||||
|
||||
### `autoclosure` {#autoclosure}
|
||||
|
||||
这个特性通过把表达式自动封装成无参数的闭包来延迟表达式的计算。它可以修饰类型为返回表达式结果类型的无参数函数类型的函数参数。关于如何使用 `autoclosure` 特性的例子,请参阅 [自动闭包](../02_language_guide/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 调用约定。其余的 Swift 函数不能转换成 C 调用约定。一个 Objective-C 块调用约定的函数不能转换成 C 调用约定。
|
||||
|
||||
### `escaping` {#escaping}
|
||||
|
||||
在函数或者方法声明上使用该特性,它表示参数将不会被存储以供延迟执行。这将确保参数不会超出函数调用的生命周期。在使用 `escaping` 特性声明的函数类型中访问属性和方法时需要显式地使用 `self.`。关于如何使用 `escaping` 特性的例子,请参阅 [逃逸闭包](../02_language_guide/07_Closures.md#escaping-closures)。
|
||||
|
||||
### `Sendable` {#Sendable}
|
||||
|
||||
在函数类型上使用该特性以指示该函数是闭包或可发送的。在函数类型上使用该特性与使非函数类型符合 `Sendable` 协议具有相同的意义。
|
||||
|
||||
当一个函数或闭包被用在需要可发送值的情况,且该函数或闭包满足可发送的要求,则可以在该函数或闭包上推断此特性。
|
||||
|
||||
可发送函数类型是对应的不可发送函数类型的一个子类型。
|
||||
|
||||
## Switch Case 特性 {#switch-case-attributes}
|
||||
|
||||
你只能在 switch cases 语句中使用 switch case 特性。
|
||||
|
||||
### `unknown` {#unknown}
|
||||
|
||||
该特性用于 switch case,用于没有匹配上代码编译时已知 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> **}**
|
||||
>
|
||||
> *均衡令牌* → 任意标识符,关键字,字面量或运算符
|
||||
>
|
||||
> *均衡令牌* → 任意标点除了 **(**,**)**,**[**,**]**,**{**,或 **}**
|
||||
>
|
||||
@ -4,7 +4,7 @@
|
||||
|
||||
Swift 中的模式分为两类:一种能成功匹配任何类型的值,另一种在运行时匹配某个特定值时可能会失败。
|
||||
|
||||
第一类模式用于解构简单变量、常量和可选绑定中的值。此类模式包括通配符模式、标识符模式,以及包含前两种模式的值绑定模式和元组模式。你可以为这类模式指定一个类型标注,从而限制它们只能匹配某种特定类型的值。
|
||||
第一类模式用于解构简单变量、常量和可选绑定中的值。此类模式包括通配符模式、标识符模式,以及包含前两种模式的值绑定模式和元组模式。你可以为这类模式指定一个类型注解,从而限制它们只能匹配某种特定类型的值。
|
||||
|
||||
第二类模式用于全模式匹配,这种情况下你试图匹配的值在运行时可能不存在。此类模式包括枚举用例模式、可选模式、表达式模式和类型转换模式。你在 `switch` 语句的 `case` 标签中,`do` 语句的 `catch` 子句中,或者在 `if`、`while`、`guard` 和 `for-in` 语句的 `case` 条件句中使用这类模式。
|
||||
|
||||
@ -12,21 +12,21 @@ Swift 中的模式分为两类:一种能成功匹配任何类型的值,另
|
||||
>
|
||||
|
||||
#### pattern {#pattern}
|
||||
> *模式* → [*通配符模式*](#wildcard_pattern) [*类型标注*](03_Types.md#type-annotation)<sub>可选</sub>
|
||||
> *模式* → [通配符模式](#wildcard-pattern) [类型注解](./03_Types.md#type-annotation)<sub>可选</sub>
|
||||
>
|
||||
> *模式* → [*标识符模式*](#identifier_pattern) [*类型标注*](03_Types.md#type-annotation)<sub>可选</sub>
|
||||
> *模式* → [标识符模式](#identifier-pattern) [类型注解](./03_Types.md#type-annotation)<sub>可选</sub>
|
||||
>
|
||||
> *模式* → [*值绑定模式*](#value-binding-pattern)
|
||||
> *模式* → [值绑定模式](#value-binding-pattern)
|
||||
>
|
||||
> *模式* → [*元组模式*](#tuple-pattern) [*类型标注*](03_Types.md#type-annotation)<sub>可选</sub>
|
||||
> *模式* → [元组模式](#tuple-pattern) [类型注解](./03_Types.md#type-annotation)<sub>可选</sub>
|
||||
>
|
||||
> *模式* → [*枚举用例模式*](#enum-case-pattern)
|
||||
> *模式* → [枚举用例模式](#enum-case-pattern)
|
||||
>
|
||||
> *模式* → [*可选模式*](#optional-pattern)
|
||||
> *模式* → [可选模式](#optional-pattern)
|
||||
>
|
||||
> *模式* → [*类型转换模式*](#type-casting-pattern)
|
||||
> *模式* → [类型转换模式](#type-casting-pattern)
|
||||
>
|
||||
> *模式* → [*表达式模式*](#expression-pattern)
|
||||
> *模式* → [表达式模式](#expression-pattern)
|
||||
>
|
||||
|
||||
## 通配符模式(Wildcard Pattern) {#wildcard-pattern}
|
||||
@ -41,8 +41,7 @@ for _ in 1...3 {
|
||||
|
||||
> 通配符模式语法
|
||||
>
|
||||
|
||||
#### wildcard-pattern {#wildcard-pattern}
|
||||
> #### wildcard-pattern {#wildcard-pattern}
|
||||
> *通配符模式* → **_**
|
||||
>
|
||||
|
||||
@ -59,9 +58,8 @@ let someValue = 42
|
||||
|
||||
> 标识符模式语法
|
||||
>
|
||||
|
||||
#### identifier-pattern {#identifier-pattern}
|
||||
> *标识符模式* → [*标识符*](./02_Lexical_Structure.md#identifier)
|
||||
> #### identifier-pattern {#identifier-pattern}
|
||||
> *标识符模式* → [标识符](./02_Lexical_Structure.md#identifier)
|
||||
>
|
||||
|
||||
## 值绑定模式(Value-Binding Pattern) {#value-binding-pattern}
|
||||
@ -83,15 +81,14 @@ case let (x, y):
|
||||
|
||||
> 值绑定模式语法
|
||||
>
|
||||
|
||||
#### value-binding-pattern {#value-binding-pattern}
|
||||
> *值绑定模式* → **var** [*模式*](#pattern) | **let** [*模式*](#pattern)
|
||||
> #### value-binding-pattern {#value-binding-pattern}
|
||||
> *值绑定模式* → **var** [模式](#pattern) | **let** [模式](#pattern)
|
||||
>
|
||||
|
||||
## 元组模式 {#tuple-pattern}
|
||||
*元组模式*是由逗号分隔的,具有零个或多个模式的列表,并由一对圆括号括起来。元组模式匹配相应元组类型的值。
|
||||
|
||||
你可以使用类型标注去限制一个元组模式能匹配哪种元组类型。例如,在常量声明 `let (x, y): (Int, Int) = (1, 2)` 中的元组模式 `(x, y): (Int, Int)` 只匹配两个元素都是 `Int` 类型的元组。
|
||||
你可以使用类型注解去限制一个元组模式能匹配哪种元组类型。例如,在常量声明 `let (x, y): (Int, Int) = (1, 2)` 中的元组模式 `(x, y): (Int, Int)` 只匹配两个元素都是 `Int` 类型的元组。
|
||||
|
||||
当元组模式被用于 `for-in` 语句或者变量和常量声明时,它仅可以包含通配符模式、标识符模式、可选模式或者其他包含这些模式的元组模式。比如下面这段代码就不正确,因为 `(x, 0)` 中的元素 `0` 是一个表达式模式:
|
||||
|
||||
@ -113,29 +110,42 @@ let (a): Int = 2 // a: Int = 2
|
||||
|
||||
> 元组模式语法
|
||||
>
|
||||
|
||||
#### tuple-pattern {#tuple-pattern}
|
||||
> *元组模式* → **(** [*元组模式元素列表*](#tuple-pattern-element-list)<sub>可选</sub> **)**
|
||||
> #### 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) **,** [元组模式元素列表](#tuple-pattern-element-list)
|
||||
>
|
||||
|
||||
#### tuple-pattern-element {#tuple-pattern-element}
|
||||
> *元组模式元素* → [*模式*](#pattern)
|
||||
> #### 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)。
|
||||
如果你准备匹配的枚举用例有任何关联的值,则相应的枚举用例模式必须指定一个包含每个关联值元素的元组模式。关于使用 `switch` 语句来匹配包含关联值的枚举用例的例子,请参阅 [关联值](../02_language_guide/08_Enumerations.md#associated-values)。
|
||||
|
||||
枚举用例模式同样会匹配那些被包装成可选值的用例。简化的语法能将可选模式过滤掉。注意,由于 `Optional` 是枚举实现的,`.none` 和 `.some` 都会作为枚举类型的用例出现在 switch 中。
|
||||
|
||||
```swift
|
||||
enum SomeEnum { case left, right }
|
||||
let x: SomeEnum? = .left
|
||||
switch x {
|
||||
case .left:
|
||||
print("Turn left")
|
||||
case .right:
|
||||
print("Turn right")
|
||||
case nil:
|
||||
print("Keep going straight")
|
||||
}
|
||||
// 打印 "Turn left"
|
||||
```
|
||||
|
||||
> 枚举用例模式语法
|
||||
>
|
||||
|
||||
#### enum-case-pattern {#enum-case-pattern}
|
||||
> *枚举用例模式* → [*类型标识*](./03_Types.md#type-identifier)<sub>可选</sub> **.** [*枚举用例名*](./06_Declarations.md#enum-case-name) [*元组模式*](#tuple-pattern)<sub>可选</sub>
|
||||
> #### 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}
|
||||
@ -171,9 +181,8 @@ for case let number? in arrayOfOptinalInts {
|
||||
|
||||
> 可选模式语法
|
||||
>
|
||||
|
||||
#### optional-pattern {#optional-pattern}
|
||||
> *可选模式* → [*标识符模式*](./03_Types.md#type-identifier) **?**
|
||||
> #### optional-pattern {#optional-pattern}
|
||||
> *可选模式* → [标识符模式](./03_Types.md#type-identifier) **?**
|
||||
>
|
||||
|
||||
## 类型转换模式(Type-Casting Patterns) {#type-casting-patterns}
|
||||
@ -188,21 +197,18 @@ for case let number? in arrayOfOptinalInts {
|
||||
|
||||
`as` 模式仅当一个值的类型在运行时和 `as` 模式右边的指定类型一致,或者是其子类的情况下,才会匹配这个值。如果匹配成功,被匹配的值的类型被转换成 `as` 模式右边指定的类型。
|
||||
|
||||
关于使用 `switch` 语句配合 `is` 模式和 `as` 模式来匹配值的例子,请参阅 [Any 和 AnyObject 的类型转换](../chapter2/18_Type_Casting.md#type_casting_for_any_and_anyobject)。
|
||||
关于使用 `switch` 语句配合 `is` 模式和 `as` 模式来匹配值的例子,请参阅 [Any 和 AnyObject 的类型转换](../02_language_guide/18_Type_Casting.md#type-casting-for-any-and-anyobject)。
|
||||
|
||||
> 类型转换模式语法
|
||||
>
|
||||
|
||||
#### type-casting-pattern {#type-casting-pattern}
|
||||
> *类型转换模式* → [*is 模式*](#is-pattern) | [*as 模式*](#as-pattern)
|
||||
> #### type-casting-pattern {#type-casting-pattern}
|
||||
> *类型转换模式* → [is 模式](#is-pattern) | [as 模式](#as-pattern)
|
||||
>
|
||||
|
||||
#### is-pattern {#is-pattern}
|
||||
> *is 模式* → **is** [*类型*](./03_Types.md#type)
|
||||
> #### is-pattern {#is-pattern}
|
||||
> *is 模式* → **is** [类型](./03_Types.md#type)
|
||||
>
|
||||
|
||||
#### as-pattern {#as-pattern}
|
||||
> *as 模式* → [*模式*](#pattern) **as** [*类型*](03_Types.md#type)
|
||||
> #### as-pattern {#as-pattern}
|
||||
> *as 模式* → [模式](#pattern) **as** [类型](./03_Types.md#type)
|
||||
>
|
||||
|
||||
## 表达式模式(Expression Pattern) {#expression-pattern}
|
||||
@ -228,7 +234,6 @@ default:
|
||||
```swift
|
||||
// 重载 ~= 运算符对字符串和整数进行比较
|
||||
func ~=(pattern: String, value: Int) -> Bool {
|
||||
>
|
||||
return pattern == "\(value)"
|
||||
}
|
||||
|
||||
@ -243,7 +248,6 @@ default:
|
||||
|
||||
> 表达式模式语法
|
||||
>
|
||||
|
||||
#### expression-pattern {#expression-pattern}
|
||||
> *表达式模式* → [*表达式*](./04_Expressions.md#expression)
|
||||
> #### expression-pattern {#expression-pattern}
|
||||
> *表达式模式* → [表达式](./04_Expressions.md#expression)
|
||||
>
|
||||
@ -1,138 +1,148 @@
|
||||
# 泛型参数(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)
|
||||
>
|
||||
# 泛型参数(Generic Parameters and Arguments)
|
||||
|
||||
本节涉及泛型类型、泛型函数以及泛型构造器的参数,包括形参和实参。声明泛型类型、函数或构造器时,须指定相应的类型参数。类型参数相当于一个占位符,当实例化泛型类型、调用泛型函数或泛型构造器时,就用具体的类型实参替代之。
|
||||
|
||||
关于 Swift 语言的泛型概述,请参阅 [泛型](../02_language_guide/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` 子句依然可以指向包围它的声明中的类型参数,此时 `where` 子句需要满足的条件仅用于它被声明的地方。
|
||||
|
||||
如果外层的声明也有一个 `where` 子句,两个子句的条件都需要满足。下面的例子中,`startsWithZero()` 只有在 `Element` 同时满足 `SomeProtocol` 和 `Numeric` 才有效。
|
||||
|
||||
```swift
|
||||
extension Collection where Element: SomeProtocol {
|
||||
func startsWithZero() -> Bool where Element: Numeric {
|
||||
return first == .zero
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
泛型函数或构造器可以重载,但在泛型形参子句中的类型形参必须有不同的约束或要求,抑或二者皆不同。当调用重载的泛型函数或构造器时,编译器会根据这些约束来决定调用哪个重载函数或构造器。
|
||||
|
||||
更多关于泛型 where 从句的信息和关于泛型函数声明的例子,可以看一看 [泛型 where 子句](../02_language_guide/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)
|
||||
>
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,3 @@
|
||||
# Swift 教程
|
||||
# Swift 语言参考
|
||||
|
||||
本章描述了 Swift 的语言参考。
|
||||
327
source/04_revision_history/04_revision_history.md
Normal file
327
source/04_revision_history/04_revision_history.md
Normal file
@ -0,0 +1,327 @@
|
||||
# Swift 文档修订历史
|
||||
|
||||
### 2022-06-06
|
||||
|
||||
* 更新至 Swift 5.7。
|
||||
* 新增 [可发送类型](../02_language_guide/28_Concurrency.md#Sendable-Types) 章节,其中包含在 actors 和任务间发送数据的内容。在 [可发送](03_language_reference/07_Attributes.md#Sendable) 章节和 [unchecked](03_language_reference/07_Attributes.md#unchecked) 章节新增了有关特性 `@Sendable` 和 `@unchecked` 的内容。
|
||||
* 新增了 [正则表达式字面量](03_language_reference/02_Lexical_Structure.md#regular-expression-literals) 章节,其中包含新建正则表达式的内容。
|
||||
* 在 [可选绑定](../02_language_guide/01_The_Basics.md#optional-binding) 章节新增了 `if-let` 结构更简短的一种形式。
|
||||
* 在 [检查 API 可用性](../02_language_guide/05_Control_Flow.md#checking-api-availability) 章节新增了与不可用性检查 `#unavaliable` 有关的内容。
|
||||
|
||||
### 2022-03-14
|
||||
|
||||
* 更新至 Swift 5.6
|
||||
* 更新了 [显式成员表达式](../03_language_reference/04_Expressions.md#explicit-member-expression) 章节,现在可以在链式调用方法和其它后缀表达式周围使用 `#if` 了。
|
||||
|
||||
### 2021-07-14
|
||||
|
||||
* 更新 [并发](../02_language_guide/28_Concurrency.md) 篇章里的示例,使用当前的并发 API。
|
||||
|
||||
### 2021-06-07
|
||||
|
||||
* 更新至 Swift 5.5。
|
||||
* 在 [并发](../02_language_guide/28_Concurrency.md) 篇章、[Actor 声明](03_language_reference/06_Declarations.md#actor-declaration)、[异步函数和方法](03_language_reference/06_Declarations.md#asynchronous-functions-and-methods) 和 [Await 运算符](03_language_reference/04_Expressions.md#await-operators) 中新增了有关异步函数、任务和 actor 的内容。
|
||||
|
||||
### 2021-04-26
|
||||
|
||||
* 更新至 Swift 5.4。
|
||||
* 新增 [结果构造器](../02_language_guide/27_Advanced_Operators.md#result-builders) 和 [resultBuilder](03_language_reference/07_Attributes.md#resultbuilder) 章节,其中包含结果构造器的内容。
|
||||
* 新增 [指针类型的隐式转换](03_language_reference/04_Expressions.md#implicit-conversion-to-a-pointer) 章节,其中包含在函数调用时输入输出形参可以隐式转换到不安全指针的内容。
|
||||
* 更新 [可变参数](../02_language_guide/06_Functions.md#variadic-parameters) 和 [函数声明](03_language_reference/06_Declarations.md#function-declaration) 章节,现在函数可以有多个可变参数了。
|
||||
* 更新 [隐式成员表达式](03_language_reference/04_Expressions.md#implicit-member-expression) 章节,现在可以链式调用隐式成员表达式了。
|
||||
|
||||
### 2020-09-16
|
||||
|
||||
* 更新至 Swift 5.3。
|
||||
* 在 [尾随闭包](../02_language_guide/07_Closures.md#trailing-closures) 章节中新增了有关多个尾随闭包的内容,在 [函数调用表达式](../03_language_reference/04_Expressions.md#function-call-expression) 章节中新增了有关尾随闭包如何匹配形参的内容。
|
||||
* 在 [使用合成实现来采纳协议](../02_language_guide/21_Protocols.md#adopting-a-protocol-using-a-synthesized-implementation) 章节中新增了枚举合成实现 `Comparable` 的内容。
|
||||
* 新增 [包含上下文关系的 where 分句](../02_language_guide/22_Generics.md#contextual-where-clauses) 章节,现在可以在更多位置编写范型 `where` 分句。
|
||||
* 新增 [无主可选引用](../02_language_guide/24_Automatic_Reference_Counting.md#unowned-optional-references) 章节,其中包含可选值使用无主引用的内容。
|
||||
* 在 [main](../03_language_reference/07_Attributes.md#main) 章节中新增有关 `@main` 特性的内容。
|
||||
* 在 [字面量表达式](../03_language_reference/04_Expressions.md#literal-expression) 章节中新增 `#filePath`,并更新了 `#file` 的讨论。
|
||||
* 更新 [逃逸闭包](../02_language_guide/07_Closures.md#escaping-closures) 章节,现在闭包可以在更多场景隐式引用 `self`。
|
||||
* 更新 [用 Do-Catch 处理错误](../02_language_guide/17_Error_Handling.md#handling-errors-using-do-Catch) 和 [Do 语句](../03_language_reference/05_Statements.md#do-statements) 章节,现在 `catch` 子句可以匹配多个错误。
|
||||
* 新增更多有关 `Any` 的内容并移动到新增的 [Any 类型](../03_language_reference/03_Types.md#any-type) 章节。
|
||||
* 更新 [属性观察器](../02_language_guide/10_Properties.md#property-observers) 章节,现在 lazy 属性可以有观察器。
|
||||
* 更新 [协议声明](../03_language_reference/06_Declarations.md#protocol-declaration) 章节,现在枚举的成员可以用于遵循协议的要求。
|
||||
* 更新 [存储型变量和属性的观察器](../03_language_reference/06_Declarations.md#stored-variable-observers-and-property-observers) 章节,描述观察器之前 getter 是何时被调用的。
|
||||
* 更新 [内存安全](../02_language_guide/25_Memory_Safety.md) 篇章并提及原子操作。
|
||||
|
||||
### 2020-03-24
|
||||
|
||||
* 更新至 Swift 5.2。
|
||||
* 在 [Key-Path 表达式](../03_language_reference/04_Expressions.md#key-path-expression) 章节中新增了有关传递 key path 代替闭包的内容。
|
||||
* 在 [特殊名称方法](../03_language_reference/06_Declarations.md#methods-with-special-names) 章节中新增了有关让类、结构体和枚举的实例作为函数调用语法糖的内容。
|
||||
* 更新 [下标选项](../02_language_guide/12_Subscripts.md#subscript-options) 章节,现在下标支持形参默认值。
|
||||
* 更新 [自身类型](../03_language_reference/03_Types.md#self-type-h) 章节,现在 `Self` 可以在更多上下文中使用。
|
||||
* 更新 [隐式解析可选类型](../02_language_guide/01_The_Basics.md#implicityly-unwrapped-optionals) 章节,明确了隐式解析可选值可以作为可选值或者非可选值使用。
|
||||
|
||||
### 2019-09-10
|
||||
|
||||
* 更新至 Swift 5.1。
|
||||
* 在 [不透明类型](../02_language_guide/23_Opaque_Types.md) 篇章中新增了有关函数返回值遵循指定协议,而不需要提供指定返回类型的内容。
|
||||
* 在 [属性包装器](../02_language_guide/10_Properties.md#property-wrappers) 章节中新增了有关属性包装器的内容。
|
||||
* 在 [冻结](../03_language_reference/07_Attributes.md#frozen) 章节中新增了有关因库演变而需要的枚举和结构体冻结。
|
||||
* 新增 [隐式返回的函数](../02_language_guide/06_Functions.md#functions-with-an-implicit-return) 和 [简化 Getter 声明](../02_language_guide/10_Properties.md#shorthand-getter-declaration) 章节,其中包含函数省略 `return` 的内容。
|
||||
* 在 [类型下标](../02_language_guide/12_Subscripts.md#type-subscripts) 章节中新增了有关在类型中使用下标的内容。
|
||||
* 更新 [枚举 Case 模式匹配](../03_language_reference/08_Patterns.md#enumeration-case-pattern) 章节,现在枚举 case 模式匹配支持匹配可选值。
|
||||
* 更新 [结构体的逐一成员构造器](../02_language_guide/14_Initialization.md#memberwise-initializers-for-structure-types) 章节,现在逐一成员构造器支持在属性有默认值时省略形参。
|
||||
* 在 [动态查找成员](../03_language_reference/07_Attributes.md#dynamicmemberlookup) 章节中新增了有关在运行时用 key path 查找动态成员的内容。
|
||||
* 在 [条件编译代码块](../03_language_reference/05_Statements.md#Conditional-Compilation-Block) 中的目标环境里添加了 `macCatalyst`。
|
||||
* 更新 [自身类型](../03_language_reference/03_Types.md#self-type-h) 章节,现在 `Self` 可以指向当前类,结构体或者枚举声明时的类型。
|
||||
|
||||
### 2019-03-25
|
||||
|
||||
* 更新至 Swift 5。
|
||||
* 新增 [拓展字符串分隔符](../02_language_guide/03_Strings_and_Characters.md#extended-string-delimiters) 章节。更新 [字符串字面量](../03_language_reference/02_Lexical_Structure.md#string-literal) 章节,拓展有关字符串分隔符的内容。
|
||||
* 新增 [动态调用](../03_language_reference/07_Attributes.md#dynamiccallable) 章节,其中包含使用 `dynamicCallable` 属性动态调用实例作为函数的内容。
|
||||
* 新增 [unknown](../03_language_reference/07_Attributes.md#unknown) 和 [未来枚举匹配](../03_language_reference/05_Statements.md#future-case) 章节,其中包含了使用 `unknown` 来处理未来枚举可能发生改变的情形。
|
||||
* 在 [Key-Path 表达式](../03_language_reference/04_Expressions.md#key-path-expression) 章节新增了有关标示 key path (\\.self) 的内容。
|
||||
* 在 [可选编译块](../03_language_reference/05_Statements.md#Conditional-Compilation-Block) 章节新增了有关小于比较符 `<` 的内容。
|
||||
|
||||
### 2018-09-17
|
||||
|
||||
* 更新至 Swift 4.2。
|
||||
* 在 [遍历枚举情形](../02_language_guide/08_Enumerations.md#iterating-over-enumeration-cases) 章节新增了有关访问所有枚举情形的内容。
|
||||
* 在 [编译诊断](../03_language_reference/05_Statements.md#compile-time-diagnostic-statement) 章节新增了有关 `#error` 和 `#warning` 的内容。
|
||||
* 在 [属性声明](../03_language_reference/07_Attributes.md#Ideclaration-attributes) 章节中新增了有关 `inlinable` 和 `usableFromInline` 属性的内容。
|
||||
* 在 [属性声明](../03_language_reference/07_Attributes.md#Ideclaration-attributes) 章节中新增了有关 `requires-stored-property-inits` 和 `warn-unqualified-access` 属性的内容。
|
||||
* 在 [可选编译块](../03_language_reference/05_Statements.md#Conditional-Compilation-Block) 章节新增了有关如何根据 Swift 编译器版本对代码进行对应编译处理的内容。
|
||||
* 在 [字面量语法](../03_language_reference/04_Expressions.md#literal-expression) 章节新增了有关 `#dsohandle` 的内容。
|
||||
|
||||
### 2018-03-29
|
||||
|
||||
* 更新至 Swift 4.1。
|
||||
* 在 [等价运算符](../02_language_guide/27_Advanced_Operators.md#equivalence-operators) 章节新增了有关等价运算符的合成实现的内容。
|
||||
* 在 [声明](../03_language_reference/06_Declarations.md) 篇章中 [声明拓展](../03_language_reference/06_Declarations.md#extension-declaration) 章节和 [协议](../02_language_guide/21_Protocols.md) 篇章中 [有条件地遵循协议](../02_language_guide/21_Protocols.md#Conditionally-Conforming-to-a-Protocol) 章节新增了有关协议有条件遵循的内容。
|
||||
* 在 [关联类型约束中使用协议](../02_language_guide/22_Generics.md#using-a-protocol-in-its-associated-types-constraints) 章节中新增了有关递归协议约束的内容。
|
||||
* 在 [条件编译块](../03_language_reference/05_Statements.md#Conditional-Compilation-Block) 章节中新增了有关 `canImport()` 和 `targetEnvironment()` 平台条件的内容。
|
||||
|
||||
### 2017-12-04
|
||||
|
||||
* 更新至 Swift 4.0.3。
|
||||
* 更新 [Key-Path 表达式](../03_language_reference/04_Expressions.md#key-path-expression) 章节,现在 key path 支持下标子路径。
|
||||
|
||||
### 2017-09-19
|
||||
|
||||
* 更新至 Swift 4.0。
|
||||
* 在 [内存安全](../02_language_guide/25_Memory_Safety.md) 章节新增了有关内存互斥访问的内容。
|
||||
* 新增 [带有泛型 Where 子句联类型](../02_language_guide/22_Generics.md#associated-types-with-a-generic-where-clause) 章节,现在可以使用泛型 `where` 子句约束关联类型。
|
||||
* 在 [字符串和字符](../02_language_guide/03_Strings_and_Characters.md) 篇章中 [字面量](../02_language_guide/03_Strings_and_Characters.md#string-literals) 章节以及 [词法结构](../03_language_reference/02_Lexical_Structure.md) 篇章的 [字符串字面量](../03_language_reference/02_Lexical_Structure.md#string-literal) 章节中新增了有关多行字符串字面量的内容。
|
||||
* 更新 [声明属性](../03_language_reference/07_Attributes.md#Ideclaration-attributes) 中 `objc` 属性的讨论,现在该属性会在更少的位置被推断出来。
|
||||
* 新增 [范型下标](../02_language_guide/22_Generics.md#generic-subscripts) 章节,现在下标也支持范型特性了。
|
||||
* 更新 [协议](../02_language_guide/21_Protocols.md) 篇章中 [协议组合](../02_language_guide/21_Protocols.md#protocol-composition) 章节和 [类型](../03_language_reference/03_Types.md) 篇章中 [协议组合类型](../03_language_reference/03_Types.md#protocol-composition-type-h) 章节的讨论,现在协议组合类型支持进行父类约束了。
|
||||
* 更新 [拓展声明](../03_language_reference/06_Declarations.md#extension-declaration) 中有关协议扩展的讨论,现在它们不支持 `final` 特性了。
|
||||
* 在 [断言和前置条件](../02_language_guide/01_The_Basics.md#assertions-and-preconditions) 章节中新增了部分前置条件和致命错误的内容。
|
||||
|
||||
### 2017-03-27
|
||||
|
||||
* 更新至 Swift 3.1。
|
||||
* 新增 [范型 Where 子句扩展](../02_language_guide/22_Generics.md#extensions-with-a-generic-where-clause) 章节,包含需要的扩展内容。
|
||||
* 在 [For-In 循环](../02_language_guide/05_Control_Flow.md#for-in-loops) 章节中新增了区间迭代的例子。
|
||||
* 在 [可失败构造器](../02_language_guide/14_Initialization.md#failable-initializers) 章节中新增了可失败数值转换的例子。
|
||||
* 在 [声明特性](../03_language_reference/07_Attributes.md#Ideclaration-attributes) 章节中新增了有关使用 Swift 语言版本的 `available` 特性的内容 。
|
||||
* 更新 [函数类型](../03_language_reference/03_Types.md#function-type-h) 章节中的讨论,注意在写函数类型时不允许使用参数标签。
|
||||
* 更新 [条件编译块](../03_language_reference/05_Statements.md#Conditional-Compilation-Block) 章节中的 Swift 语言版本号的讨论,现在可以使用可选的补丁版本号。
|
||||
* 更新 [函数类型](../03_language_reference/03_Types.md#function-type-h) 章节的讨论,现在 Swift 区分了采用多参数的函数和采用元组类型的单个参数的函数。
|
||||
* 在 [表达式](../03_language_reference/04_Expressions.md) 篇章中删除了动态表达式的章节,现在 `type(of:)` 是 Swift 标准库函数。
|
||||
|
||||
### 2016-10-27
|
||||
|
||||
* 更新至 Swift 3.0.1。
|
||||
* 更新 [自动引用计数](../02_language_guide/24_Automatic_Reference_Counting.md) 章节中有关 weak 和 unowned 引用的讨论。
|
||||
* 在 [声明标识符](../03_language_reference/06_Declarations.md#declaration-modifiers) 章节中新增了有关新的标识符 `unowned`,`unowend(safe)` 和 `unowned(unsafe)` 的内容。
|
||||
* 在 [Any 和 AnyObject 的类型转换](../02_language_guide/18_Type_Casting.md#type-casting-for-any-and-anyobject) 章节中新增了一处说明,有关使用类型 `Any` 作为可选值。
|
||||
* 更新 [表达式](../03_language_reference/04_Expressions.md) 章节,把括号表达式和元组表达式的描述分开。
|
||||
|
||||
### 2016-09-13
|
||||
|
||||
* 更新至 Swift 3.0。
|
||||
* 更新 [函数](../02_language_guide/06_Functions.md) 篇章和 [函数声明](../03_language_reference/06_Declarations.md#function-declaration) 章节中有关函数的讨论,所有函数参数默认都有函数标签。
|
||||
* 更新 [高级操作符](../02_language_guide/27_Advanced_Operators.md) 篇章中有关操作符的讨论,现在你可以作为类型函数来实现,替代之前的全局函数实现方式。
|
||||
* 在 [访问控制](../02_language_guide/26_Access_Control.md) 章节中新增有关对新的访问级别描述符 `open` 和 `fileprivate` 的内容。
|
||||
* 更新 [函数声明](../03_language_reference/06_Declarations.md#function-declaration) 章节中有关 `inout` 的讨论,注意它现在出现在参数类型的前面,而不是在参数名称的前面。
|
||||
* 更新 [逃逸闭包](../02_language_guide/07_Closures.md#escaping-closures) 和 [自动闭包](../02_language_guide/07_Closures.md#autoclosures) 章节还有 [属性](../03_language_reference/07_Attributes.md) 篇章中有关 `@noescape` 和 `@autoclosure` 的讨论,现在他们是类型属性,而不是定义属性。
|
||||
* 在 [高级操作符](../02_language_guide/27_Advanced_Operators.md) 篇章中 [自定义中缀操作符的优先级](./02_language_guide/27_Advanced_Operators.md#precedence-and-associativity-for-custom-infix-operators) 章节和 [定义](../03_language_reference/06_Declarations.md) 篇章中 [优先级组声明](../03_language_reference/06_Declarations.md#precedence-group-declaration-modifiers) 章节中新增了有关操作符优先级组的内容。
|
||||
* 更新一些讨论,使用 macOS 替换掉 OS X, Error 替换掉 ErrorProtocol。更新一些协议名称,比如使用 ExpressibleByStringLiteral 替换掉 StringLiteralConvertible。
|
||||
* 更新 [泛型](../02_language_guide/22_Generics.md) 篇章中 [泛型 Where 语句](../02_language_guide/22_Generics.md#extensions-with-a-generic-where-clause) 章节和 [泛型形参和实参](../03_language_reference/09_Generic_Parameters_and_Arguments.md) 篇章的讨论,现在泛型的 where 语句写在一个声明的最后。
|
||||
* 更新 [逃逸闭包](../02_language_guide/07_Closures.md#escaping-closures) 章节中的讨论,现在闭包默认为非逃逸的。
|
||||
* 更新 [基础部分](../02_language_guide/01_The_Basics.md) 篇章中 [可选绑定](../02_language_guide/01_The_Basics.md#optional-binding) 章节和 [语句](../03_language_reference/05_Statements.md) 篇章中 [While 语句](../03_language_reference/05_Statements.md#while-statement) 章节中的讨论,现在 if,`while` 和 `guard` 语句使用逗号分隔条件列表,不需要使用 `where` 语句。
|
||||
* 在 [控制流](../02_language_guide/05_Control_Flow.md) 篇章中 [Switch](../02_language_guide/05_Control_Flow.md#switch) 章节和 [语句](../03_language_reference/05_Statements.md) 篇章中 [Switch 语句](../03_language_reference/05_Statements.md#switch-statement) 章节中新增了 switch cases 可以使用多模式的内容。
|
||||
* 更新 [函数类型](../03_language_reference/03_Types.md#function-type-h) 章节有关现在函数参数标签不包含在函数类型中的讨论。
|
||||
* 更新 [协议](../02_language_guide/21_Protocols.md) 篇章中 [协议组合](../02_language_guide/21_Protocols.md#protocol-composition) 章节和 [类型](../03_language_reference/03_Types.md) 篇章中 [协议组合类型](../03_language_reference/03_Types.md#protocol-composition-type-h) 章节中有关使用新的 Protocol1 & Protocol2 语法的内容。
|
||||
* 更新动态类型表达式章节中使用新的 `type(of:)` 表达式的讨论。
|
||||
* 更新 [行控制表达式](../03_language_reference/05_Statements.md#line-control-statement) 章节中使用 `#sourceLocation(file:line:)` 表达式的讨论。
|
||||
* 更新 [永不返回函数](../03_language_reference/06_Declarations.md#functions-that-never-return) 章节中使用 新的 `Never` 类型的讨论。
|
||||
* 在 [字面量表达式](../03_language_reference/04_Expressions.md#literal-expression) 章节中新增了有关 `playground` 字面量的内容。
|
||||
* 更新 [In-Out 参数](../03_language_reference/06_Declarations.md#in-out-parameters) 章节,标明只有非逃逸闭包能捕获 `in-out` 参数。
|
||||
* 更新 [默认参数值](../02_language_guide/06_Functions.md#default-parameter-values) 章节,现在默认参数不能在调用时候重新排序。
|
||||
* 更新 [属性](../03_language_reference/07_Attributes.md) 篇章中有关属性参数使用分号的说明。
|
||||
* 在 [重新抛出函数和方法](../03_language_reference/06_Declarations.md#rethrowing-functions-and-methods) 章节中新增了有关在 catch 代码块中抛出错误的重新抛出函数的内容。
|
||||
* 在 [Selector 表达式](../03_language_reference/04_Expressions.md#selector-expression7) 章节中新增了中有关访问 Objective-C 中 Selector 的 getter 和 setter 的内容。
|
||||
* 在 [类型别名声明](../03_language_reference/06_Declarations.md#type-alias-declaration) 章节中中新增了有关泛型类型别名和在协议内使用类型别名的内容。
|
||||
* 更新 [函数类型](../03_language_reference/03_Types.md#function-type-h) 章节中有关函数类型的讨论,标明函数类型作为参数类型必须使用括号包裹。
|
||||
* 更新 [属性](../03_language_reference/07_Attributes.md) 篇章,标明 `@IBAction`,`@IBOutlet` 和 `@NSManaged` 隐式含有 `@objc` 属性。
|
||||
* 在 [声明属性](../03_language_reference/07_Attributes.md#Ideclaration-attributes) 章节中新增了有关 `@GKInspectable` 的内容。
|
||||
* 更新 [可选协议要求](../02_language_guide/21_Protocols.md#optional-protocol-requirements) 章节中有关只能在与 `Objective-C` 交互的代码中才能使用可选协议要求的内容。
|
||||
* 删除 [函数声明](../03_language_reference/06_Declarations.md#function-declaration) 章节中有关显式使用 `let` 关键字作为函数参数的内容。
|
||||
* 删除 [语句](../03_language_reference/05_Statements.md) 章节中有关 `Boolean` 协议的内容, 现在这个协议已经被 Swift 标准库删除。
|
||||
* 更正 [声明属性](../03_language_reference/07_Attributes.md#Ideclaration-attributes) 章节中有关 `@NSApplicationMain` 协议的内容。
|
||||
|
||||
### 2016-03-21
|
||||
|
||||
* 更新至 Swift 2.2。
|
||||
* 在 [编译配置语句](../03_language_reference/05_Statements.md#Conditional-Compilation-Block) 章节新增了中有关如何根据 Swift 版本进行条件编译。
|
||||
* 在 [显示成员表达式](../03_language_reference/04_Expressions.md#explicit-member-expression) 章节中新增了有关如何区分只有参数名不同的方法和构造器的内容。
|
||||
* 在 [选择器表达式](../03_language_reference/04_Expressions.md#selector-expression7) 章节中新增了了针对 Objective-C 选择器的 `#selector` 语法。
|
||||
* 更新 [关联类型](../02_language_guide/22_Generics.md#associated-types) 和 [协议关联类型声明](../03_language_reference/06_Declarations.md#protocol-associated-type-declaration) 章节中有关使用 `associatedtype` 关键词修饰关联类型的讨论。
|
||||
* 更新 [可失败构造器](../02_language_guide/14_Initialization.md#failable-initializers) 章节中有关当构造器在实例完全初始化之前返回 `nil` 的相关内容。
|
||||
* 在 [比较运算符](../02_language_guide/02_Basic_Operators.md#comparison-operators) 章节中新增了比较元组的内容。
|
||||
* 在 [关键字和标点符号](../03_language_reference/02_Lexical_Structure.md#keywords-and-punctuation) 章节中新增了使用关键字作为外部参数名的内容。
|
||||
* 更新 [声明特性](../03_language_reference/07_Attributes.md#Ideclaration-attributes) 章节中有关 `@objc` 特性的讨论,并指出枚举和枚举用例。
|
||||
* 更新 [操作符](../03_language_reference/02_Lexical_Structure.md#operator) 章节中对于自定义运算符的包含了 `.` 的讨论。
|
||||
* 在 [重新抛出错误的函数和方法](../03_language_reference/06_Declarations.md#rethrowing-functions-and-methods) 章节中新增了一处说明,重新抛出错误函数不能直接抛出错误。
|
||||
* 在 [属性观察器](../02_language_guide/10_Properties.md#property-observers) 章节中新增了一处说明,当作为 in-out 参数传递属性时,属性观察器的调用行为。
|
||||
* 在 [Swift 初见](../01_welcome_to_swift/03_a_swift_tour.md) 篇章中新增了错误处理的章节。
|
||||
* 更新 [弱引用](../02_language_guide/24_Automatic_Reference_Counting.md#weak-references) 章节中的图片用以更清楚的展示重新分配过程。
|
||||
* 删除 C 语言风格的 `for` 循环,`++` 前缀和后缀运算符,以及 `--` 前缀和后缀运算符。
|
||||
* 删除对变量函数参数和柯里化函数的特殊语法的讨论。
|
||||
|
||||
### 2015-10-20
|
||||
|
||||
* 更新至 Swift 2.1。
|
||||
* 更新 [字符串插值](../02_language_guide/03_Strings_and_Characters.md#string-interpolation) 和 [字符串字面量](../03_language_reference/02_Lexical_Structure.md#string-literal) 章节,现在字符串插值可包含字符串字面量。
|
||||
* 在 [逃逸闭包](../02_language_guide/07_Closures.md#escaping-closures) 章节中新增了有关 `@noescape` 属性的相关内容。
|
||||
* 更新 [声明特性](../03_language_reference/07_Attributes.md#Ideclaration-attributes) 和 [编译配置语句](../03_language_reference/05_Statements.md#Conditional-Compilation-Block) 章节中与 tvOS 相关的内容。
|
||||
* 在 [In-Out 参数](../03_language_reference/06_Declarations.md#in-out-parameters) 章节中新增了与 in-out 参数行为相关的内容。
|
||||
* 在 [捕获列表](../03_language_reference/04_Expressions.md#capture-lists) 章节新增了有关指定闭包捕获列表被捕获时捕获值的相关内容。
|
||||
* 更新 [可选链式调用访问属性](../02_language_guide/16_Optional_Chaining.md#accessing-properties-through-optional-chaining) 章节,阐明了如何通过可选链式调用进行赋值。
|
||||
* 改进 [自动闭包](../02_language_guide/07_Closures.md#autoclosures) 章节中对自闭包的讨论。
|
||||
* 在 [Swift 初见](../01_welcome_to_swift/03_a_swift_tour.md) 篇章中新增了一个使用 `??` 操作符的例子。
|
||||
|
||||
### 2015-09-16
|
||||
|
||||
* 更新至 Swift 2.0。
|
||||
* 在 [错误处理](../02_language_guide/17_Error_Handling.md) 篇章中新增了有关错误处理的相关内容,包括 [Do 语句](../03_language_reference/05_Statements.md#do-statement)、 [Throw 语句](../03_language_reference/05_Statements.md#throw-statement)、 [Defer 语句](../03_language_reference/05_Statements.md##defer-statements) 以及 [try 运算符](../03_language_reference/04_Expressions.md#try-operator)。
|
||||
* 更新 [错误表示和抛出](../02_language_guide/17_Error_Handling.md#representing-and-throwing-errors) 章节,现在所有类型都可以遵循 `ErrorType` 协议了。
|
||||
* 在 [将错误装换成可选值](../02_language_guide/17_Error_Handling.md#converting-errors-to-optional-values) 篇章增加了 `try?` 关键字相关内容。
|
||||
* 在 [枚举](../02_language_guide/08_Enumerations.md) 篇章的 [递归枚举](../02_language_guide/08_Enumerations.md#recursive-enumerations) 章节以及以及 [声明](../03_language_reference/06_Declarations.md) 篇章的 [任意类型用例的枚举](../03_language_reference/06_Declarations.md#enumerations-with-cases-of-any-type) 章节中新增了递归枚举相关内容。
|
||||
* 在 [控制流](../02_language_guide/05_Control_Flow.md) 篇章的 [API 可用性检查](../02_language_guide/05_Control_Flow.md#checking-api-availability) 章节和 [语句](../03_language_reference/05_Statements.md) 篇章的 [可用性条件](../03_language_reference/05_Statements.md#availability-condition) 章节中新增了有关 API 可用性检查相关的内容。
|
||||
* 在 [控制流](../02_language_guide/05_Control_Flow.md) 篇章的 [尽早退出](../02_language_guide/05_Control_Flow.md#early-exit) 章节和 [语句](../03_language_reference/05_Statements.md) 篇章的 [Guard 语句](../03_language_reference/05_Statements.md#guard-statement) 章节新增了与 `guard` 语句相关的内容。
|
||||
* 在 [协议](../02_language_guide/21_Protocols.md) 篇章中 [协议扩展](../02_language_guide/21_Protocols.md#protocol-extensions) 章节中新增了有关协议扩展的内容。
|
||||
* 在 [访问控制](../02_language_guide/26_Access_Control.md) 篇章的 [单元测试 target 的访问级别](../02_language_guide/26_Access_Control.md#access-levels-for-unit-test-targets) 章节中新增了有关单元测试访问控制相关的内容。
|
||||
* 在 [模式](../03_language_reference/08_Patterns.md) 篇章的 [可选模式](../03_language_reference/08_Patterns.md#optional-pattern) 章节中新增了可选模式相关内容。
|
||||
* 更新 [Repeat-While](../02_language_guide/05_Control_Flow.md#repeat-while) 章节中有关 `repeat-while` 循环相关的内容。
|
||||
* 更新 [字符串和字符](../02_language_guide/03_Strings_and_Characters.md) 章节,现在 `String` 类型在 Swift 标准库中不再遵循 `CollectionType` 协议。
|
||||
* 在 [常量与变量打印](../02_language_guide/01_The_Basics.md#printing) 章节中新增了新 Swift 标准库中有关 `print(-:separator:terminator) ` 相关内容。
|
||||
* 在 [枚举](../02_language_guide/08_Enumerations.md) 篇章的 [原始值的隐式赋值](../02_language_guide/08_Enumerations.md#implicitly-assigned-raw-values) 章节和 [声明](../03_language_reference/06_Declarations.md) 篇章的 [包含原始值类型的枚举](../03_language_reference/06_Declarations.md#enumerations-with-cases-of-a-raw-value-type) 章节中新增了有关包含 `String` 原始值的枚举用例的行为相关内容。
|
||||
* 在 [自动闭包](../02_language_guide/07_Closures.md#autoclosures) 章节中新增了有关 `@autoclosure` 特性的相关内容,包括它的 `@autoclosure(escaping)` 形式。
|
||||
* 更新 [声明特性](../03_language_reference/07_Attributes.md#Ideclaration-attributes) 章节中有关 `@avaliable` 和 `warn-unused-result` 特性的相关内容。
|
||||
* 更新 [类型特性](../03_language_reference/07_Attributes.md#type-attributes) 章节中有关 `@convention` 特性的相关内容。
|
||||
* 在 [可选绑定](../02_language_guide/01_The_Basics.md#optional-binding) 章节中新增了有关使用 `where` 子句进行多可选绑定的相关内容。
|
||||
* 在 [字符串字面量](../03_language_reference/02_Lexical_Structure.md#string-literal) 章节中新增了有关在编译时使用 `+` 运算符拼接字符串字面量的相关内容。
|
||||
* 在 [元类型](../03_language_reference/03_Types.md#metatype-type-h) 章节中新增了有关元类型值的比较和使用它们通过构造器表达式构造实例相关内容。
|
||||
* 在 [断言调试](../02_language_guide/01_The_Basics.md#debugging-with-assertions) 章节中新增了一处说明,有关用户定义断言何时会失效。
|
||||
* 更新 [声明特性](../03_language_reference/07_Attributes.md#Ideclaration-attributes) 章节中对 `@NSManaged` 特性的讨论,现在这个特性可以被应用到一个确定实例方法。
|
||||
* 更新 [可变参数](../02_language_guide/06_Functions.md#variadic-parameters) 章节,现在可变参数可以声明在函数参数列表的任意位置中。
|
||||
* 在 [重写可失败构造器](../02_language_guide/14_Initialization.md#overriding-a-failable-initializer) 章节中新增了有关非可失败构造器相当于一个可失败构造器通过父类构造器的结果进行强制拆包的相关内容。
|
||||
* 在 [任意类型用例的枚举](../03_language_reference/06_Declarations.md#enumerations-with-cases-of-any-type) 章节中新增了有关枚举用例作为函数的内容。
|
||||
* 在 [构造器表达式](../03_language_reference/04_Expressions.md#initializer-expression) 章节中新增了有关显式引用一个构造器相关内容。
|
||||
* 在 [编译控制语句](../03_language_reference/05_Statements.md#compiler-control-statements) 章节中新增了有关编译内容以及行控制语句相关内容。
|
||||
* 在 [元类型](../03_language_reference/03_Types.md#metatype-type-h) 章节新增了一处说明,有关如何从元类型值中构造类实例相关内容。
|
||||
* 在 [弱引用](../02_language_guide/24_Automatic_Reference_Counting.md#weak-references) 章节新增了一处说明,有关弱引用作为缓存所存在的不足。
|
||||
* 更新 [类型特性](../02_language_guide/10_Properties.md#type-properties) 章节,提到了存储型特性其实是懒加载。
|
||||
* 更新 [捕获类型](../02_language_guide/07_Closures.md#capturing-values) 章节,阐明了变量和常量在闭包中如何被捕获。
|
||||
* 更新 [声明特性](../03_language_reference/07_Attributes.md#Ideclaration-attributes) 章节,用以描述何时在类中使用 `@objc` 关键字。
|
||||
* 在 [错误处理](../02_language_guide/17_Error_Handling.md#handling-errors) 章节中新增了一处说明,有关执行 `throw` 语句的性能。在 [Do 语句](../03_language_reference/05_Statements.md#do-statement) 章节的 do 语句部分也新增了类似内容。
|
||||
* 更新 [类型特性](../02_language_guide/10_Properties.md#type-properties) 章节中有关类、结构体和枚举的存储型和计算型特性相关的内容。
|
||||
* 更新 [Break 语句](../03_language_reference/05_Statements.md#break-statement) 章节中有关带标签的 break 语句相关内容。
|
||||
* 在 [属性观察器](../02_language_guide/10_Properties.md#property-observers) 章节更新了一处说明,用来明确 `willSet` 和 `didSet` 观察器的行为。
|
||||
* 在 [访问级别](../02_language_guide/26_Access_Control.md#access-levels) 章节新增了有关 `private` 作用域的相关内容说明。
|
||||
* 在 [弱引用](../02_language_guide/24_Automatic_Reference_Counting.md#weak-references) 章节新增了有关弱应用在垃圾回收系统和 ARC 之间的区别的说明。
|
||||
* 更新 [字符串字面量中特殊字符](../02_language_guide/03_Strings_and_Characters.md#special-characters-in-string-literals) 章节,对 Unicode 标量更精确定义。
|
||||
|
||||
|
||||
### 2015-04-08
|
||||
|
||||
* 更新至 Swift 1.2。
|
||||
* Swift 现在自身提供了一个 `Set` 集合类型,更多内容,请看 [Sets](../02_language_guide/04_Collection_Types.md#sets) 。
|
||||
* `@autoclosure` 现在是一个参数声明的属性,而不是参数类型的属性。这里还有一个新的参数声明属性 `@noescape`。更多内容,请看 [属性声明](../03_language_reference/07_Attributes.md#Ideclaration-attributes) 。
|
||||
* 对于类型属性和方法现在可以使用 `static` 关键字作为声明描述符,更多内容,请看 [类型变量属性](../03_language_reference/06_Declarations.md#type-variable-properties)。
|
||||
* Swift 现在包含一个 `as?` 和 `as!` 的向下可失败类型转换运算符。更多内容,请看 [协议遵循性检查](../02_language_guide/21_Protocols.md#checking-for-protocol-conformance)。
|
||||
* 新增 [字符串索引](../02_language_guide/03_Strings_and_Characters.md#string-indices) 的新指导章节。
|
||||
* 在 [溢出运算符](../02_language_guide/27_Advanced_Operators.md#overflow-operators) 一节中删除了溢出除运算符(`&/`)和求余溢出运算符(`&%`)。
|
||||
* 更新常量和常量属性在声明和构造时的规则,更多内容,请看 [常量声明](../03_language_reference/06_Declarations.md#constant-declaration) 。
|
||||
* 更新字符串字面量中 Unicode 标量集的定义,请看 [字符串字面量中的特殊字符](../02_language_guide/03_Strings_and_Characters.md#special-characters-in-string-literals) 。
|
||||
* 更新 [区间运算符](../02_language_guide/02_Basic_Operators.md#range-operators) 章节,注意当半开区间运算符含有相同的起止索引时,其区间为空。
|
||||
* 更新 [闭包引用类型](../02_language_guide/07_Closures.md#closures-are-reference-types) 章节,对于变量的捕获规则进行了阐明。
|
||||
* 更新 [值溢出](../02_language_guide/27_Advanced_Operators.md#value-overflow) 章节,对有符号整数和无符号整数的溢出行为进行了阐明。
|
||||
* 更新 [协议声明](../03_language_reference/06_Declarations.md#protocol-declaration) 章节,对协议声明时的作用域和成员等内容进行了阐明。
|
||||
* 更新 [捕获列表](../02_language_guide/24_Automatic_Reference_Counting.md#defining-a-capture-list) 章节,对于闭包捕获列表中的弱引用和无主引用的使用语法进行了阐明。
|
||||
* 更新 [运算符](../03_language_reference/02_Lexical_Structure.md#operator) 章节,明确指明一些例子来说明自定义运算符所支持的特性,如数学运算符,各种符号,Unicode 符号块等。
|
||||
* 在函数作用域中的常量声明时可以不被初始化,它必须在第一次使用前被赋值。更多的内容,请看 [常量声明](../03_language_reference/06_Declarations.md#constant-declaration)。
|
||||
* 在构造器中,常量属性有且仅能被赋值一次。更多内容,请看 [在构造过程中给常量属性赋值](../02_language_guide/14_Initialization.md#assigning-constant-properties-during-initialization)。
|
||||
* 多个可选绑定现在可以在`if`语句后面以逗号分隔的赋值列表的方式出现,更多内容,请看 [可选绑定](../02_language_guide/01_The_Basics.md#optional-binding)。
|
||||
* 一个 [可选链表达式](../03_language_reference/04_Expressions.md#optional-chaining-expression) 必须出现在后缀表达式中。
|
||||
* 协议类型转换不再局限于 `@obj` 修饰的协议了。
|
||||
* 在运行时可能会失败的类型转换可以使用 `as?` 和 `as!` 运算符,而确保不会失败的类型转换现在使用 `as` 运算符。更多内容,请看 [类型转换运算符](../03_language_reference/04_Expressions.md#type-casting-operator)。
|
||||
|
||||
### 2014-10-16
|
||||
|
||||
* 更新至 Swift 1.1。
|
||||
* 新增 [失败构造器](../02_language_guide/14_Initialization.md#failable-initializers) 的完整指引。
|
||||
* 在协议中新增了 [失败构造器要求](../02_language_guide/21_Protocols.md#failable-initializer-requirements) 的描述。
|
||||
* 常量和变量的 `Any` 类型现可以包含函数实例。更新了有关 `Any` 相关的示例来展示如何在 `switch` 语句中如何检查并转换到一个函数类型。
|
||||
* 带有原始值的枚举类型增加了一个 `rawValue` 属性替代 `toRaw()` 方法,同时使用了一个以 `rawValue` 为参数的失败构造器来替代 `fromRaw()` 方法。更多的内容,请看 [原始值](../02_language_guide/08_Enumerations.md#raw-values) 和 [带原始值的枚举类型](../03_language_reference/06_Declarations.md#enumerations-with-cases-of-a-raw-value-type)。
|
||||
* 新增 [Failable Initializer](../03_language_reference/06_Declarations.md#failable-initializers) 的参考章节,它可以触发初始化失败。
|
||||
* 自定义运算符现在可以包含 `?` 字符,更新了 [运算符](../03_language_reference/02_Lexical_Structure.md#operator) 涉及改进后的规则的部分,并且在 [自定义运算符](../02_language_guide/27-Advanced-Operators.md#custom-operators) 章节中删除了重复的运算符有效字符集合。
|
||||
|
||||
### 2014-08-18
|
||||
|
||||
* 描述 Swift 1.0 的新文档。Swift 是苹果公司发布的全新编程语言,用于 iOS 和 OS X 应用开发。
|
||||
* 在协议中新增了 [对构造器的规定](../02_language_guide/21_Protocols.md#initializer-requirements) 章节。
|
||||
* 新增 [类专属协议](../02_language_guide/21_Protocols.md#class-only-protocol) 章节。
|
||||
* [断言](../02_language_guide/01_The_Basics.md#assertions-and-preconditions) 现在可以使用字符串内插语法,并删除了文档中有冲突的注释。
|
||||
* 更新 [连接字符串和字符](../02_language_guide/03_Strings_and_Characters.md#concatenating-strings-and-characters) 章节来说明字符串和字符不能再用 `+` 号运算符或者复合加法运算符 `+=` 相互连接,这两种运算符现在只能用于字符串之间相连。请使用 `String` 类型的 `append` 方法在一个字符串的尾部增加单个字符。
|
||||
* 在 [属性声明](../03_language_reference/07_Attributes.md#Ideclaration-attributes) 章节增加了有关 `availability` 特性的一些内容。
|
||||
* [可选类型](../02_language_guide/01_The_Basics.md#optionals) 若有值时,不再隐式的转换为 `true`,同样,若无值时,也不再隐式的转换为 `false`,这是为了避免在判别 optional `Bool` 的值时产生困惑。 替代的方案是,用`==` 或 `!=` 运算符显式地去判断 Optinal 是否是 `nil`,以确认其是否包含值。
|
||||
* Swift 新增了一个 [Nil 合并运算符](../02_language_guide/02_Basic_Operators.md#nil-coalescing-operator) (`a ?? b`) , 该表达式中,如果 Optional `a` 的值存在,则取得它并返回,若 Optional `a` 为 `nil`,则返回默认值 `b`
|
||||
* 更新和扩展 [字符串的比较](../02_language_guide/03_Strings_and_Characters.md#comparing-strings) ,用以反映和展示'字符串和字符的比较',以及'前缀(prefix)/后缀(postfix)比较'都开始基于扩展字符集(extended grapheme clusters)规范的等价比较。
|
||||
* 现在,你可以通过下标赋值或者 [可选调用链](../02_language_guide/16_Optional_Chaining.md) 中的可变方法和操作符来给属性设值。相应地更新了有关 [通过可选链接访问属性](../02_language_guide/16_Optional_Chaining.md#accessing-properties-through-optional-chaining) 的内容,并扩展了 [通过可选链接调用方法](../02_language_guide/16_Optional_Chaining.md#calling-methods-through-optional-chaining) 时检查方法调用成功的示例,以显示如何检查属性设置是否成功。
|
||||
* 在可选链中新增了 [访问可选类型的下标脚注](../02_language_guide/16_Optional_Chaining.md#accessing-subscripts-through-optional-chaining) 章节。
|
||||
* 更新 [访问和修改数组](../02_language_guide/04_Collection_Types.md#accessing-and-modifying-a-dictionary) 章节以标示,从该版本起,不能再通过 `+=` 运算符给一个数组新增一个新的项。对应的替代方案是,使 `append` 方法,或者通过 `+=` 运算符来新增一个只有一个项的数组。
|
||||
* 新增一处说明,在 [范围运算符](../02_language_guide/02_Basic_Operators.md#range-operators) 中,比如, `a..b` 和 `a..<b` ,起始值 `a` 不能大于结束值 `b`。
|
||||
* 重写 [继承](../02_language_guide/13_Inheritance.md) 篇章:删除了本章中有关构造器重写的介绍性报道;转而将更多的注意力放到新增的部分——子类的新功能,以及如何通过重写(overrides)修改已有的功能。另外, [重写属性的 Getters 和 Setters](../02_language_guide/13_Inheritance.md#overriding-property-etters-and-setters) 中的例子已经被替换为展示如何重写一个 `description` 属性。 (而有关如何在子类的构造器中修改继承属性的默认值的例子,已经被移到 [构造过程](../02_language_guide/14_Initialization.md) 篇章。)
|
||||
* 更新 [构造器的继承与重写](../02_language_guide/14_Initialization.md#initializer-inheritance-and-overriding) 章节以标示: 重写一个特定的构造器必须使用 `override` 修饰符。
|
||||
* 更新 [Required 构造器](../02_language_guide/14_Initialization.md#required-initializers) 章节以标示:`required` 修饰符现在需要出现在所有子类的 required 构造器的声明中,而 required 构造器的实现,现在可以仅从父类自动继承。
|
||||
* 中置(Infix)的 [运算符函数](../02_language_guide/27_Advanced_Operators.md#operator-functions) 不再需要 `@infix` 属性。
|
||||
* [前置和后置运算符](../02_language_guide/27_Advanced_Operators.md#prefix-and-postfix-operators) 的 `@prefix` 和 `@postfix` 属性,已变更为 `prefix` 和 `postfix` 声明修饰符。
|
||||
* 新增一处说明,在 Prefix 和 postfix 运算符被作用于同一个操作数时 [前置和后置运算符](../02_language_guide/27_Advanced_Operators.md#prefix-and-postfix-operators) 的执行顺序。
|
||||
* [组合赋值运算符](../02_language_guide/27_Advanced_Operators.md#compound-assignment-operators) 的运算符函数不再使用 `@assignment` 属性来定义函数。
|
||||
* 在定义 [自定义操作符](../02_language_guide/27_Advanced_Operators.md#custom-operators) 时,`修饰符(Modifiers)的出现顺序发生变化`。比如现在,你该编写 `prefix operator`, 而不是 `operator prefix`。
|
||||
* 在 [声明修饰符](../03_language_reference/06_Declarations.md#declaration-modifiers) 章节新增了有关 `dynamic` 声明修饰符的内容。
|
||||
* 新增有关 [字面量](../03_language_reference/02_Lexical_Structure.md#literal) 类型推导内容的内容。
|
||||
* 新增更多有关柯里化函数的内容。
|
||||
* 新增 [权限控制](../02_language_guide/26_Access_Control.md) 篇章。
|
||||
* 更新 [字符串和字符](../02_language_guide/03_Strings_and_Characters.md) 章节,在 Swift 中现在 `Character` 类型代表的是扩展字符集(extended grapheme cluster)中的一个 Unicode,为此,新增了 [Extended Grapheme Clusters](../02_language_guide/03_Strings_and_Characters.md#extended-grapheme-clusters) 章节。同时,[Unicode 标量](../02_language_guide/03-Strings-And-Characters.md#unicode-scalars-representation) 和 [字符串比较](../02_language_guide/03-Strings-And-Characters.md#comparing-strings) 章节新增了更多内容。
|
||||
* 更新 [字符串字面量](../02_language_guide/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` 属性。在 [字符串和字符](../02_language_guide/03_Strings_and_Characters.md) 章节中删除了对应部分,并更新了各种对应的代码用例。
|
||||
* 新增 [没有外部名的构造器参数](../02_language_guide/14_Initialization.md#initializer-parameters-without-external-names) 章节。
|
||||
* 新增 [Required 构造器](../02_language_guide/14_Initialization.md#required-initializers) 章节。
|
||||
* 新增 [可选元组返回类型](../02_language_guide/06_Functions.md#optional-tuple-return-types) 章节。
|
||||
* 更新 [类型注解](../02_language_guide/01_The_Basics.md#type-annotations) 章节,多个相关变量可以用"类型注解”在同一行中声明为同一类型。
|
||||
* `@optional`, `@lazy`, `@final`, `@required` 等关键字被更新为 `optional`, `lazy`, `final`, `required` 参见 [声明修饰符](../03_language_reference/06_Declarations.md#declaration-modifiers)。
|
||||
* 更新了整本书中有关 `..<` 的引用,从半闭区间改为了 [半开区间](../02_language_guide/02_Basic_Operators.md#half-open-range-operator)。
|
||||
* 更新 [读取和修改字典](../02_language_guide/04_Collection_Types.md#accessing-and-modifying-a-dictionary) 章节, `Dictionary` 现在增加了一个 Boolean 型的属性:`isEmpty`。
|
||||
* 解释了哪些字符(集)可被用来定义 [自定义操作符](../02_language_guide/27_Advanced_Operators.md#custom-operators)。
|
||||
* `nil` 和布尔运算中的 `true` 和 `false` 现在被定义为 [字面量](../03_language_reference/02_Lexical_Structure.md#literal)。
|
||||
* Swift 中的数组 (`Array`) 类型从现在起具备了完整的值语义。具体内容被更新到 [集合的可变性](../02_language_guide/04_Collection_Types.md#mutability-of-collections) 和 [数组](../02_language_guide/04_Collection_Types.md#arrays) 两小节,以反映这个新的变化。 此外,还解释了如何给 Strings, Arrays 和 Dictionaries 进行赋值和拷贝。
|
||||
* [数组类型速记语法](../02_language_guide/04_Collection_Types.md#array-type-shorthand-syntax) 从 `SomeType []` 更新为 ` [SomeType]`。
|
||||
* 新增 [字典类型的速记语法](../02_language_guide/04_Collection_Types.md#dictionary-type-shorthand-syntax) 章节,现在书写格式为: ` [KeyType: ValueType]`。
|
||||
* 新增 [字典键类型的哈希值](../02_language_guide/04_Collection_Types.md#hash-values-for-set-types) 章节。
|
||||
* [闭包表达式](../02_language_guide/07_Closures.md#closure-expressions) 示例中使用新的全局函数 `sorted` 取代原先的全局函数 `sort` 去展示如何返回一个全新的数组。
|
||||
* 更新 [结构体逐一成员构造器](../02_language_guide/14_Initialization.md#memberwise-initializers-for-structure-types) 章节,即使结构体的成员 `没有默认值`,逐一成员构造器也可以自动获得。
|
||||
* [半开区间运算符](../02_language_guide/02_Basic_Operators.md#half-open-range-operator) 中`..` 更新为 `..<`。
|
||||
* 新增 [泛型拓展](../02_language_guide/22_Generics.md#extending-a-generic-type) 的示例。
|
||||
|
||||
3
source/04_revision_history/chapter4.md
Normal file
3
source/04_revision_history/chapter4.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Swift 文档修订历史
|
||||
|
||||
本章描述了 Swift 文档修订历史。
|
||||
457
source/README.md
457
source/README.md
@ -1,149 +1,314 @@
|
||||
# 文档翻译 & 校对工作记录
|
||||
|
||||
Swift 官方文档中文翻译工作由[numbbbbb](https://github.com/numbbbbb)发起并主导,该工作已经得到了苹果官方的认可。下面是各个版本官方文档翻译和校对工作的主要贡献者,排名不分先后。
|
||||
|
||||
## 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)
|
||||
- [jojotov](https://github.com/jojotov)
|
||||
- [Khala-wan](https://github.com/Khala-wan)
|
||||
- [Nemocdz](https://github.com/Nemocdz)
|
||||
- [numbbbbb](https://github.com/numbbbbb)
|
||||
- [WAMaker](https://github.com/WAMaker)
|
||||
- [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)
|
||||
|
||||
# Swift 文档修订历史
|
||||
|
||||
### 2021-07-14
|
||||
|
||||
* 更新 [并发](../02_language_guide/28_Concurrency.md) 篇章里的示例,使用当前的并发 API。
|
||||
|
||||
### 2021-06-07
|
||||
|
||||
* 更新至 Swift 5.5。
|
||||
* 在 [并发](../02_language_guide/28_Concurrency.md) 篇章、[Actor 声明](03_language_reference/06_Declarations.md#actor-declaration)、[异步函数和方法](03_language_reference/06_Declarations.md#asynchronous-functions-and-methods) 和 [Await 运算符](03_language_reference/04_Expressions.md#await-operators) 中新增了有关异步函数、任务和 actor 的内容。
|
||||
|
||||
### 2021-04-26
|
||||
|
||||
* 更新至 Swift 5.4。
|
||||
* 新增 [结果构造器](../02_language_guide/27_Advanced_Operators.md#result-builders) 和 [resultBuilder](03_language_reference/07_Attributes.md#resultbuilder) 章节,其中包含结果构造器的内容。
|
||||
* 新增 [指针类型的隐式转换](03_language_reference/04_Expressions.md#implicit-conversion-to-a-pointer) 章节,其中包含在函数调用时输入输出形参可以隐式转换到不安全指针的内容。
|
||||
* 更新 [可变参数](../02_language_guide/06_Functions.md#variadic-parameters) 和 [函数声明](03_language_reference/06_Declarations.md#function-declaration) 章节,现在函数可以有多个可变参数了。
|
||||
* 更新 [隐式成员表达式](03_language_reference/04_Expressions.md#implicit-member-expression) 章节,现在可以链式调用隐式成员表达式了。
|
||||
|
||||
### 2020-09-16
|
||||
|
||||
* 更新至 Swift 5.3。
|
||||
* 在 [尾随闭包](../02_language_guide/07_Closures.md#trailing-closures) 章节中新增了有关多个尾随闭包的内容,在 [函数调用表达式](../03_language_reference/04_Expressions.md#function-call-expression) 章节中新增了有关尾随闭包如何匹配形参的内容。
|
||||
* 在 [使用合成实现来采纳协议](../02_language_guide/21_Protocols.md#adopting-a-protocol-using-a-synthesized-implementation) 章节中新增了枚举合成实现 `Comparable` 的内容。
|
||||
* 新增 [包含上下文关系的 where 分句](../02_language_guide/22_Generics.md#contextual-where-clauses) 章节,现在可以在更多位置编写范型 `where` 分句。
|
||||
* 新增 [无主可选引用](../02_language_guide/24_Automatic_Reference_Counting.md#unowned-optional-references) 章节,其中包含可选值使用无主引用的内容。
|
||||
* 在 [main](../03_language_reference/07_Attributes.md#main) 章节中新增有关 `@main` 特性的内容。
|
||||
* 在 [字面量表达式](../03_language_reference/04_Expressions.md#literal-expression) 章节中新增 `#filePath`,并更新了 `#file` 的讨论。
|
||||
* 更新 [逃逸闭包](../02_language_guide/07_Closures.md#escaping-closures) 章节,现在闭包可以在更多场景隐式引用 `self`。
|
||||
* 更新 [用 Do-Catch 处理错误](../02_language_guide/17_Error_Handling.md#handling-errors-using-do-Catch) 和 [Do 语句](../03_language_reference/05_Statements.md#do-statements) 章节,现在 `catch` 子句可以匹配多个错误。
|
||||
* 新增更多有关 `Any` 的内容并移动到新增的 [Any 类型](../03_language_reference/03_Types.md#any-type) 章节。
|
||||
* 更新 [属性观察器](../02_language_guide/10_Properties.md#property-observers) 章节,现在 lazy 属性可以有观察器。
|
||||
* 更新 [协议声明](../03_language_reference/06_Declarations.md#protocol-declaration) 章节,现在枚举的成员可以用于遵循协议的要求。
|
||||
* 更新 [存储型变量和属性的观察器](../03_language_reference/06_Declarations.md#stored-variable-observers-and-property-observers) 章节,描述观察器之前 getter 是何时被调用的。
|
||||
* 更新 [内存安全](../02_language_guide/25_Memory_Safety.md) 篇章并提及原子操作。
|
||||
|
||||
### 2020-03-24
|
||||
|
||||
* 更新至 Swift 5.2。
|
||||
* 在 [Key-Path 表达式](../03_language_reference/04_Expressions.md#key-path-expression) 章节中新增了有关传递 key path 代替闭包的内容。
|
||||
* 在 [特殊名称方法](../03_language_reference/06_Declarations.md#methods-with-special-names) 章节中新增了有关让类、结构体和枚举的实例作为函数调用语法糖的内容。
|
||||
* 更新 [下标选项](../02_language_guide/12_Subscripts.md#subscript-options) 章节,现在下标支持形参默认值。
|
||||
* 更新 [自身类型](../03_language_reference/03_Types.md#self-type-h) 章节,现在 `Self` 可以在更多上下文中使用。
|
||||
* 更新 [隐式解析可选类型](../02_language_guide/01_The_Basics.md#implicityly-unwrapped-optionals) 章节,明确了隐式解析可选值可以作为可选值或者非可选值使用。
|
||||
|
||||
### 2019-09-10
|
||||
|
||||
* 更新至 Swift 5.1。
|
||||
* 在 [不透明类型](../02_language_guide/23_Opaque_Types.md) 篇章中新增了有关函数返回值遵循指定协议,而不需要提供指定返回类型的内容。
|
||||
* 在 [属性包装器](../02_language_guide/10_Properties.md#property-wrappers) 章节中新增了有关属性包装器的内容。
|
||||
* 在 [冻结](../03_language_reference/07_Attributes.md#frozen) 章节中新增了有关因库演变而需要的枚举和结构体冻结。
|
||||
* 新增 [隐式返回的函数](../02_language_guide/06_Functions.md#functions-with-an-implicit-return) 和 [简化 Getter 声明](../02_language_guide/10_Properties.md#shorthand-getter-declaration) 章节,其中包含函数省略 `return` 的内容。
|
||||
* 在 [类型下标](../02_language_guide/12_Subscripts.md#type-subscripts) 章节中新增了有关在类型中使用下标的内容。
|
||||
* 更新 [枚举 Case 模式匹配](../03_language_reference/08_Patterns.md#enumeration-case-pattern) 章节,现在枚举 case 模式匹配支持匹配可选值。
|
||||
* 更新 [结构体的逐一成员构造器](../02_language_guide/14_Initialization.md#memberwise-initializers-for-structure-types) 章节,现在逐一成员构造器支持在属性有默认值时省略形参。
|
||||
* 在 [动态查找成员](../03_language_reference/07_Attributes.md#dynamicmemberlookup) 章节中新增了有关在运行时用 key path 查找动态成员的内容。
|
||||
* 在 [条件编译代码块](../03_language_reference/05_Statements.md#Conditional-Compilation-Block) 中的目标环境里添加了 `macCatalyst`。
|
||||
* 更新 [自身类型](../03_language_reference/03_Types.md#self-type-h) 章节,现在 `Self` 可以指向当前类,结构体或者枚举声明时的类型。
|
||||
|
||||
### 2019-03-25
|
||||
|
||||
* 更新至 Swift 5。
|
||||
* 新增 [拓展字符串分隔符](../02_language_guide/03_Strings_and_Characters.md#extended-string-delimiters) 章节。更新 [字符串字面量](../03_language_reference/02_Lexical_Structure.md#string-literal) 章节,拓展有关字符串分隔符的内容。
|
||||
* 新增 [动态调用](../03_language_reference/07_Attributes.md#dynamiccallable) 章节,其中包含使用 `dynamicCallable` 属性动态调用实例作为函数的内容。
|
||||
* 新增 [unknown](../03_language_reference/07_Attributes.md#unknown) 和 [未来枚举匹配](../03_language_reference/05_Statements.md#future-case) 章节,其中包含了使用 `unknown` 来处理未来枚举可能发生改变的情形。
|
||||
* 在 [Key-Path 表达式](../03_language_reference/04_Expressions.md#key-path-expression) 章节新增了有关标示 key path (\\.self) 的内容。
|
||||
* 在 [可选编译块](../03_language_reference/05_Statements.md#Conditional-Compilation-Block) 章节新增了有关小于比较符 `<` 的内容。
|
||||
|
||||
### 2018-09-17
|
||||
|
||||
* 更新至 Swift 4.2。
|
||||
* 在 [遍历枚举情形](../02_language_guide/08_Enumerations.md#iterating-over-enumeration-cases) 章节新增了有关访问所有枚举情形的内容。
|
||||
* 在 [编译诊断](../03_language_reference/05_Statements.md#compile-time-diagnostic-statement) 章节新增了有关 `#error` 和 `#warning` 的内容。
|
||||
* 在 [属性声明](../03_language_reference/07_Attributes.md#Ideclaration-attributes) 章节中新增了有关 `inlinable` 和 `usableFromInline` 属性的内容。
|
||||
* 在 [属性声明](../03_language_reference/07_Attributes.md#Ideclaration-attributes) 章节中新增了有关 `requires-stored-property-inits` 和 `warn-unqualified-access` 属性的内容。
|
||||
* 在 [可选编译块](../03_language_reference/05_Statements.md#Conditional-Compilation-Block) 章节新增了有关如何根据 Swift 编译器版本对代码进行对应编译处理的内容。
|
||||
* 在 [字面量语法](../03_language_reference/04_Expressions.md#literal-expression) 章节新增了有关 `#dsohandle` 的内容。
|
||||
|
||||
### 2018-03-29
|
||||
|
||||
* 更新至 Swift 4.1。
|
||||
* 在 [等价运算符](../02_language_guide/27_Advanced_Operators.md#equivalence-operators) 章节新增了有关等价运算符的合成实现的内容。
|
||||
* 在 [声明](../03_language_reference/06_Declarations.md) 篇章中 [声明拓展](../03_language_reference/06_Declarations.md#extension-declaration) 章节和 [协议](../02_language_guide/21_Protocols.md) 篇章中 [有条件地遵循协议](../02_language_guide/21_Protocols.md#Conditionally-Conforming-to-a-Protocol) 章节新增了有关协议有条件遵循的内容。
|
||||
* 在 [关联类型约束中使用协议](../02_language_guide/22_Generics.md#using-a-protocol-in-its-associated-types-constraints) 章节中新增了有关递归协议约束的内容。
|
||||
* 在 [条件编译块](../03_language_reference/05_Statements.md#Conditional-Compilation-Block) 章节中新增了有关 `canImport()` 和 `targetEnvironment()` 平台条件的内容。
|
||||
|
||||
### 2017-12-04
|
||||
|
||||
* 更新至 Swift 4.0.3。
|
||||
* 更新 [Key-Path 表达式](../03_language_reference/04_Expressions.md#key-path-expression) 章节,现在 key path 支持下标子路径。
|
||||
|
||||
### 2017-09-19
|
||||
|
||||
* 更新至 Swift 4.0。
|
||||
* 在 [内存安全](../02_language_guide/25_Memory_Safety.md) 章节新增了有关内存互斥访问的内容。
|
||||
* 新增 [带有泛型 Where 子句联类型](../02_language_guide/22_Generics.md#associated-types-with-a-generic-where-clause) 章节,现在可以使用泛型 `where` 子句约束关联类型。
|
||||
* 在 [字符串和字符](../02_language_guide/03_Strings_and_Characters.md) 篇章中 [字面量](../02_language_guide/03_Strings_and_Characters.md#string-literals) 章节以及 [词法结构](../03_language_reference/02_Lexical_Structure.md) 篇章的 [字符串字面量](../03_language_reference/02_Lexical_Structure.md#string-literal) 章节中新增了有关多行字符串字面量的内容。
|
||||
* 更新 [声明属性](../03_language_reference/07_Attributes.md#Ideclaration-attributes) 中 `objc` 属性的讨论,现在该属性会在更少的位置被推断出来。
|
||||
* 新增 [范型下标](../02_language_guide/22_Generics.md#generic-subscripts) 章节,现在下标也支持范型特性了。
|
||||
* 更新 [协议](../02_language_guide/21_Protocols.md) 篇章中 [协议组合](../02_language_guide/21_Protocols.md#protocol-composition) 章节和 [类型](../03_language_reference/03_Types.md) 篇章中 [协议组合类型](../03_language_reference/03_Types.md#protocol-composition-type-h) 章节的讨论,现在协议组合类型支持进行父类约束了。
|
||||
* 更新 [拓展声明](../03_language_reference/06_Declarations.md#extension-declaration) 中有关协议扩展的讨论,现在它们不支持 `final` 特性了。
|
||||
* 在 [断言和前置条件](../02_language_guide/01_The_Basics.md#assertions-and-preconditions) 章节中新增了部分前置条件和致命错误的内容。
|
||||
|
||||
### 2017-03-27
|
||||
|
||||
* 更新至 Swift 3.1。
|
||||
* 新增 [范型 Where 子句扩展](../02_language_guide/22_Generics.md#extensions-with-a-generic-where-clause) 章节,包含需要的扩展内容。
|
||||
* 在 [For-In 循环](../02_language_guide/05_Control_Flow.md#for-in-loops) 章节中新增了区间迭代的例子。
|
||||
* 在 [可失败构造器](../02_language_guide/14_Initialization.md#failable-initializers) 章节中新增了可失败数值转换的例子。
|
||||
* 在 [声明特性](../03_language_reference/07_Attributes.md#Ideclaration-attributes) 章节中新增了有关使用 Swift 语言版本的 `available` 特性的内容 。
|
||||
* 更新 [函数类型](../03_language_reference/03_Types.md#function-type-h) 章节中的讨论,注意在写函数类型时不允许使用参数标签。
|
||||
* 更新 [条件编译块](../03_language_reference/05_Statements.md#Conditional-Compilation-Block) 章节中的 Swift 语言版本号的讨论,现在可以使用可选的补丁版本号。
|
||||
* 更新 [函数类型](../03_language_reference/03_Types.md#function-type-h) 章节的讨论,现在 Swift 区分了采用多参数的函数和采用元组类型的单个参数的函数。
|
||||
* 在 [表达式](../03_language_reference/04_Expressions.md) 篇章中删除了动态表达式的章节,现在 `type(of:)` 是 Swift 标准库函数。
|
||||
|
||||
### 2016-10-27
|
||||
|
||||
* 更新至 Swift 3.0.1。
|
||||
* 更新 [自动引用计数](../02_language_guide/24_Automatic_Reference_Counting.md) 章节中有关 weak 和 unowned 引用的讨论。
|
||||
* 在 [声明标识符](../03_language_reference/06_Declarations.md#declaration-modifiers) 章节中新增了有关新的标识符 `unowned`,`unowend(safe)` 和 `unowned(unsafe)` 的内容。
|
||||
* 在 [Any 和 AnyObject 的类型转换](../02_language_guide/18_Type_Casting.md#type-casting-for-any-and-anyobject) 章节中新增了一处说明,有关使用类型 `Any` 作为可选值。
|
||||
* 更新 [表达式](../03_language_reference/04_Expressions.md) 章节,把括号表达式和元组表达式的描述分开。
|
||||
|
||||
### 2016-09-13
|
||||
|
||||
* 更新至 Swift 3.0。
|
||||
* 更新 [函数](../02_language_guide/06_Functions.md) 篇章和 [函数声明](../03_language_reference/06_Declarations.md#function-declaration) 章节中有关函数的讨论,所有函数参数默认都有函数标签。
|
||||
* 更新 [高级操作符](../02_language_guide/27_Advanced_Operators.md) 篇章中有关操作符的讨论,现在你可以作为类型函数来实现,替代之前的全局函数实现方式。
|
||||
* 在 [访问控制](../02_language_guide/26_Access_Control.md) 章节中新增有关对新的访问级别描述符 `open` 和 `fileprivate` 的内容。
|
||||
* 更新 [函数声明](../03_language_reference/06_Declarations.md#function-declaration) 章节中有关 `inout` 的讨论,注意它现在出现在参数类型的前面,而不是在参数名称的前面。
|
||||
* 更新 [逃逸闭包](../02_language_guide/07_Closures.md#escaping-closures) 和 [自动闭包](../02_language_guide/07_Closures.md#autoclosures) 章节还有 [属性](../03_language_reference/07_Attributes.md) 篇章中有关 `@noescape` 和 `@autoclosure` 的讨论,现在他们是类型属性,而不是定义属性。
|
||||
* 在 [高级操作符](../02_language_guide/27_Advanced_Operators.md) 篇章中 [自定义中缀操作符的优先级](./02_language_guide/27_Advanced_Operators.md#precedence-and-associativity-for-custom-infix-operators) 章节和 [定义](../03_language_reference/06_Declarations.md) 篇章中 [优先级组声明](../03_language_reference/06_Declarations.md#precedence-group-declaration-modifiers) 章节中新增了有关操作符优先级组的内容。
|
||||
* 更新一些讨论,使用 macOS 替换掉 OS X, Error 替换掉 ErrorProtocol。更新一些协议名称,比如使用 ExpressibleByStringLiteral 替换掉 StringLiteralConvertible。
|
||||
* 更新 [泛型](../02_language_guide/22_Generics.md) 篇章中 [泛型 Where 语句](../02_language_guide/22_Generics.md#extensions-with-a-generic-where-clause) 章节和 [泛型形参和实参](../03_language_reference/09_Generic_Parameters_and_Arguments.md) 篇章的讨论,现在泛型的 where 语句写在一个声明的最后。
|
||||
* 更新 [逃逸闭包](../02_language_guide/07_Closures.md#escaping-closures) 章节中的讨论,现在闭包默认为非逃逸的。
|
||||
* 更新 [基础部分](../02_language_guide/01_The_Basics.md) 篇章中 [可选绑定](../02_language_guide/01_The_Basics.md#optional-binding) 章节和 [语句](../03_language_reference/05_Statements.md) 篇章中 [While 语句](../03_language_reference/05_Statements.md#while-statement) 章节中的讨论,现在 if,`while` 和 `guard` 语句使用逗号分隔条件列表,不需要使用 `where` 语句。
|
||||
* 在 [控制流](../02_language_guide/05_Control_Flow.md) 篇章中 [Switch](../02_language_guide/05_Control_Flow.md#switch) 章节和 [语句](../03_language_reference/05_Statements.md) 篇章中 [Switch 语句](../03_language_reference/05_Statements.md#switch-statement) 章节中新增了 switch cases 可以使用多模式的内容。
|
||||
* 更新 [函数类型](../03_language_reference/03_Types.md#function-type-h) 章节有关现在函数参数标签不包含在函数类型中的讨论。
|
||||
* 更新 [协议](../02_language_guide/21_Protocols.md) 篇章中 [协议组合](../02_language_guide/21_Protocols.md#protocol-composition) 章节和 [类型](../03_language_reference/03_Types.md) 篇章中 [协议组合类型](../03_language_reference/03_Types.md#protocol-composition-type-h) 章节中有关使用新的 Protocol1 & Protocol2 语法的内容。
|
||||
* 更新动态类型表达式章节中使用新的 `type(of:)` 表达式的讨论。
|
||||
* 更新 [行控制表达式](../03_language_reference/05_Statements.md#line-control-statement) 章节中使用 `#sourceLocation(file:line:)` 表达式的讨论。
|
||||
* 更新 [永不返回函数](../03_language_reference/06_Declarations.md#functions-that-never-return) 章节中使用 新的 `Never` 类型的讨论。
|
||||
* 在 [字面量表达式](../03_language_reference/04_Expressions.md#literal-expression) 章节中新增了有关 `playground` 字面量的内容。
|
||||
* 更新 [In-Out 参数](../03_language_reference/06_Declarations.md#in-out-parameters) 章节,标明只有非逃逸闭包能捕获 `in-out` 参数。
|
||||
* 更新 [默认参数值](../02_language_guide/06_Functions.md#default-parameter-values) 章节,现在默认参数不能在调用时候重新排序。
|
||||
* 更新 [属性](../03_language_reference/07_Attributes.md) 篇章中有关属性参数使用分号的说明。
|
||||
* 在 [重新抛出函数和方法](../03_language_reference/06_Declarations.md#rethrowing-functions-and-methods) 章节中新增了有关在 catch 代码块中抛出错误的重新抛出函数的内容。
|
||||
* 在 [Selector 表达式](../03_language_reference/04_Expressions.md#selector-expression7) 章节中新增了中有关访问 Objective-C 中 Selector 的 getter 和 setter 的内容。
|
||||
* 在 [类型别名声明](../03_language_reference/06_Declarations.md#type-alias-declaration) 章节中中新增了有关泛型类型别名和在协议内使用类型别名的内容。
|
||||
* 更新 [函数类型](../03_language_reference/03_Types.md#function-type-h) 章节中有关函数类型的讨论,标明函数类型作为参数类型必须使用括号包裹。
|
||||
* 更新 [属性](../03_language_reference/07_Attributes.md) 篇章,标明 `@IBAction`,`@IBOutlet` 和 `@NSManaged` 隐式含有 `@objc` 属性。
|
||||
* 在 [声明属性](../03_language_reference/07_Attributes.md#Ideclaration-attributes) 章节中新增了有关 `@GKInspectable` 的内容。
|
||||
* 更新 [可选协议要求](../02_language_guide/21_Protocols.md#optional-protocol-requirements) 章节中有关只能在与 `Objective-C` 交互的代码中才能使用可选协议要求的内容。
|
||||
* 删除 [函数声明](../03_language_reference/06_Declarations.md#function-declaration) 章节中有关显式使用 `let` 关键字作为函数参数的内容。
|
||||
* 删除 [语句](../03_language_reference/05_Statements.md) 章节中有关 `Boolean` 协议的内容, 现在这个协议已经被 Swift 标准库删除。
|
||||
* 更正 [声明属性](../03_language_reference/07_Attributes.md#Ideclaration-attributes) 章节中有关 `@NSApplicationMain` 协议的内容。
|
||||
|
||||
### 2016-03-21
|
||||
|
||||
* 更新至 Swift 2.2。
|
||||
* 在 [编译配置语句](../03_language_reference/05_Statements.md#Conditional-Compilation-Block) 章节新增了中有关如何根据 Swift 版本进行条件编译。
|
||||
* 在 [显示成员表达式](../03_language_reference/04_Expressions.md#explicit-member-expression) 章节中新增了有关如何区分只有参数名不同的方法和构造器的内容。
|
||||
* 在 [选择器表达式](../03_language_reference/04_Expressions.md#selector-expression7) 章节中新增了了针对 Objective-C 选择器的 `#selector` 语法。
|
||||
* 更新 [关联类型](../02_language_guide/22_Generics.md#associated-types) 和 [协议关联类型声明](../03_language_reference/06_Declarations.md#protocol-associated-type-declaration) 章节中有关使用 `associatedtype` 关键词修饰关联类型的讨论。
|
||||
* 更新 [可失败构造器](../02_language_guide/14_Initialization.md#failable-initializers) 章节中有关当构造器在实例完全初始化之前返回 `nil` 的相关内容。
|
||||
* 在 [比较运算符](../02_language_guide/02_Basic_Operators.md#comparison-operators) 章节中新增了比较元组的内容。
|
||||
* 在 [关键字和标点符号](../03_language_reference/02_Lexical_Structure.md#keywords-and-punctuation) 章节中新增了使用关键字作为外部参数名的内容。
|
||||
* 更新 [声明特性](../03_language_reference/07_Attributes.md#Ideclaration-attributes) 章节中有关 `@objc` 特性的讨论,并指出枚举和枚举用例。
|
||||
* 更新 [操作符](../03_language_reference/02_Lexical_Structure.md#operator) 章节中对于自定义运算符的包含了 `.` 的讨论。
|
||||
* 在 [重新抛出错误的函数和方法](../03_language_reference/06_Declarations.md#rethrowing-functions-and-methods) 章节中新增了一处说明,重新抛出错误函数不能直接抛出错误。
|
||||
* 在 [属性观察器](../02_language_guide/10_Properties.md#property-observers) 章节中新增了一处说明,当作为 in-out 参数传递属性时,属性观察器的调用行为。
|
||||
* 在 [Swift 初见](../01_welcome_to_swift/03_a_swift_tour.md) 篇章中新增了错误处理的章节。
|
||||
* 更新 [弱引用](../02_language_guide/24_Automatic_Reference_Counting.md#weak-references) 章节中的图片用以更清楚的展示重新分配过程。
|
||||
* 删除 C 语言风格的 `for` 循环,`++` 前缀和后缀运算符,以及 `--` 前缀和后缀运算符。
|
||||
* 删除对变量函数参数和柯里化函数的特殊语法的讨论。
|
||||
|
||||
### 2015-10-20
|
||||
|
||||
* 更新至 Swift 2.1。
|
||||
* 更新 [字符串插值](../02_language_guide/03_Strings_and_Characters.md#string-interpolation) 和 [字符串字面量](../03_language_reference/02_Lexical_Structure.md#string-literal) 章节,现在字符串插值可包含字符串字面量。
|
||||
* 在 [逃逸闭包](../02_language_guide/07_Closures.md#escaping-closures) 章节中新增了有关 `@noescape` 属性的相关内容。
|
||||
* 更新 [声明特性](../03_language_reference/07_Attributes.md#Ideclaration-attributes) 和 [编译配置语句](../03_language_reference/05_Statements.md#Conditional-Compilation-Block) 章节中与 tvOS 相关的内容。
|
||||
* 在 [In-Out 参数](../03_language_reference/06_Declarations.md#in-out-parameters) 章节中新增了与 in-out 参数行为相关的内容。
|
||||
* 在 [捕获列表](../03_language_reference/04_Expressions.md#capture-lists) 章节新增了有关指定闭包捕获列表被捕获时捕获值的相关内容。
|
||||
* 更新 [可选链式调用访问属性](../02_language_guide/16_Optional_Chaining.md#accessing-properties-through-optional-chaining) 章节,阐明了如何通过可选链式调用进行赋值。
|
||||
* 改进 [自动闭包](../02_language_guide/07_Closures.md#autoclosures) 章节中对自闭包的讨论。
|
||||
* 在 [Swift 初见](../01_welcome_to_swift/03_a_swift_tour.md) 篇章中新增了一个使用 `??` 操作符的例子。
|
||||
|
||||
### 2015-09-16
|
||||
|
||||
* 更新至 Swift 2.0。
|
||||
* 在 [错误处理](../02_language_guide/17_Error_Handling.md) 篇章中新增了有关错误处理的相关内容,包括 [Do 语句](../03_language_reference/05_Statements.md#do-statement)、 [Throw 语句](../03_language_reference/05_Statements.md#throw-statement)、 [Defer 语句](../03_language_reference/05_Statements.md##defer-statements) 以及 [try 运算符](../03_language_reference/04_Expressions.md#try-operator)。
|
||||
* 更新 [错误表示和抛出](../02_language_guide/17_Error_Handling.md#representing-and-throwing-errors) 章节,现在所有类型都可以遵循 `ErrorType` 协议了。
|
||||
* 在 [将错误装换成可选值](../02_language_guide/17_Error_Handling.md#converting-errors-to-optional-values) 篇章增加了 `try?` 关键字相关内容。
|
||||
* 在 [枚举](../02_language_guide/08_Enumerations.md) 篇章的 [递归枚举](../02_language_guide/08_Enumerations.md#recursive-enumerations) 章节以及以及 [声明](../03_language_reference/06_Declarations.md) 篇章的 [任意类型用例的枚举](../03_language_reference/06_Declarations.md#enumerations-with-cases-of-any-type) 章节中新增了递归枚举相关内容。
|
||||
* 在 [控制流](../02_language_guide/05_Control_Flow.md) 篇章的 [API 可用性检查](../02_language_guide/05_Control_Flow.md#checking-api-availability) 章节和 [语句](../03_language_reference/05_Statements.md) 篇章的 [可用性条件](../03_language_reference/05_Statements.md#availability-condition) 章节中新增了有关 API 可用性检查相关的内容。
|
||||
* 在 [控制流](../02_language_guide/05_Control_Flow.md) 篇章的 [尽早退出](../02_language_guide/05_Control_Flow.md#early-exit) 章节和 [语句](../03_language_reference/05_Statements.md) 篇章的 [Guard 语句](../03_language_reference/05_Statements.md#guard-statement) 章节新增了与 `guard` 语句相关的内容。
|
||||
* 在 [协议](../02_language_guide/21_Protocols.md) 篇章中 [协议扩展](../02_language_guide/21_Protocols.md#protocol-extensions) 章节中新增了有关协议扩展的内容。
|
||||
* 在 [访问控制](../02_language_guide/26_Access_Control.md) 篇章的 [单元测试 target 的访问级别](../02_language_guide/26_Access_Control.md#access-levels-for-unit-test-targets) 章节中新增了有关单元测试访问控制相关的内容。
|
||||
* 在 [模式](../03_language_reference/08_Patterns.md) 篇章的 [可选模式](../03_language_reference/08_Patterns.md#optional-pattern) 章节中新增了可选模式相关内容。
|
||||
* 更新 [Repeat-While](../02_language_guide/05_Control_Flow.md#repeat-while) 章节中有关 `repeat-while` 循环相关的内容。
|
||||
* 更新 [字符串和字符](../02_language_guide/03_Strings_and_Characters.md) 章节,现在 `String` 类型在 Swift 标准库中不再遵循 `CollectionType` 协议。
|
||||
* 在 [常量与变量打印](../02_language_guide/01_The_Basics.md#printing) 章节中新增了新 Swift 标准库中有关 `print(-:separator:terminator) ` 相关内容。
|
||||
* 在 [枚举](../02_language_guide/08_Enumerations.md) 篇章的 [原始值的隐式赋值](../02_language_guide/08_Enumerations.md#implicitly-assigned-raw-values) 章节和 [声明](../03_language_reference/06_Declarations.md) 篇章的 [包含原始值类型的枚举](../03_language_reference/06_Declarations.md#enumerations-with-cases-of-a-raw-value-type) 章节中新增了有关包含 `String` 原始值的枚举用例的行为相关内容。
|
||||
* 在 [自动闭包](../02_language_guide/07_Closures.md#autoclosures) 章节中新增了有关 `@autoclosure` 特性的相关内容,包括它的 `@autoclosure(escaping)` 形式。
|
||||
* 更新 [声明特性](../03_language_reference/07_Attributes.md#Ideclaration-attributes) 章节中有关 `@avaliable` 和 `warn-unused-result` 特性的相关内容。
|
||||
* 更新 [类型特性](../03_language_reference/07_Attributes.md#type-attributes) 章节中有关 `@convention` 特性的相关内容。
|
||||
* 在 [可选绑定](../02_language_guide/01_The_Basics.md#optional-binding) 章节中新增了有关使用 `where` 子句进行多可选绑定的相关内容。
|
||||
* 在 [字符串字面量](../03_language_reference/02_Lexical_Structure.md#string-literal) 章节中新增了有关在编译时使用 `+` 运算符拼接字符串字面量的相关内容。
|
||||
* 在 [元类型](../03_language_reference/03_Types.md#metatype-type-h) 章节中新增了有关元类型值的比较和使用它们通过构造器表达式构造实例相关内容。
|
||||
* 在 [断言调试](../02_language_guide/01_The_Basics.md#debugging-with-assertions) 章节中新增了一处说明,有关用户定义断言何时会失效。
|
||||
* 更新 [声明特性](../03_language_reference/07_Attributes.md#Ideclaration-attributes) 章节中对 `@NSManaged` 特性的讨论,现在这个特性可以被应用到一个确定实例方法。
|
||||
* 更新 [可变参数](../02_language_guide/06_Functions.md#variadic-parameters) 章节,现在可变参数可以声明在函数参数列表的任意位置中。
|
||||
* 在 [重写可失败构造器](../02_language_guide/14_Initialization.md#overriding-a-failable-initializer) 章节中新增了有关非可失败构造器相当于一个可失败构造器通过父类构造器的结果进行强制拆包的相关内容。
|
||||
* 在 [任意类型用例的枚举](../03_language_reference/06_Declarations.md#enumerations-with-cases-of-any-type) 章节中新增了有关枚举用例作为函数的内容。
|
||||
* 在 [构造器表达式](../03_language_reference/04_Expressions.md#initializer-expression) 章节中新增了有关显式引用一个构造器相关内容。
|
||||
* 在 [编译控制语句](../03_language_reference/05_Statements.md#compiler-control-statements) 章节中新增了有关编译内容以及行控制语句相关内容。
|
||||
* 在 [元类型](../03_language_reference/03_Types.md#metatype-type-h) 章节新增了一处说明,有关如何从元类型值中构造类实例相关内容。
|
||||
* 在 [弱引用](../02_language_guide/24_Automatic_Reference_Counting.md#weak-references) 章节新增了一处说明,有关弱引用作为缓存所存在的不足。
|
||||
* 更新 [类型特性](../02_language_guide/10_Properties.md#type-properties) 章节,提到了存储型特性其实是懒加载。
|
||||
* 更新 [捕获类型](../02_language_guide/07_Closures.md#capturing-values) 章节,阐明了变量和常量在闭包中如何被捕获。
|
||||
* 更新 [声明特性](../03_language_reference/07_Attributes.md#Ideclaration-attributes) 章节,用以描述何时在类中使用 `@objc` 关键字。
|
||||
* 在 [错误处理](../02_language_guide/17_Error_Handling.md#handling-errors) 章节中新增了一处说明,有关执行 `throw` 语句的性能。在 [Do 语句](../03_language_reference/05_Statements.md#do-statement) 章节的 do 语句部分也新增了类似内容。
|
||||
* 更新 [类型特性](../02_language_guide/10_Properties.md#type-properties) 章节中有关类、结构体和枚举的存储型和计算型特性相关的内容。
|
||||
* 更新 [Break 语句](../03_language_reference/05_Statements.md#break-statement) 章节中有关带标签的 break 语句相关内容。
|
||||
* 在 [属性观察器](../02_language_guide/10_Properties.md#property-observers) 章节更新了一处说明,用来明确 `willSet` 和 `didSet` 观察器的行为。
|
||||
* 在 [访问级别](../02_language_guide/26_Access_Control.md#access-levels) 章节新增了有关 `private` 作用域的相关内容说明。
|
||||
* 在 [弱引用](../02_language_guide/24_Automatic_Reference_Counting.md#weak-references) 章节新增了有关弱应用在垃圾回收系统和 ARC 之间的区别的说明。
|
||||
* 更新 [字符串字面量中特殊字符](../02_language_guide/03_Strings_and_Characters.md#special-characters-in-string-literals) 章节,对 Unicode 标量更精确定义。
|
||||
|
||||
|
||||
### 2015-04-08
|
||||
|
||||
* 更新至 Swift 1.2。
|
||||
* Swift 现在自身提供了一个 `Set` 集合类型,更多内容,请看 [Sets](../02_language_guide/04_Collection_Types.md#sets) 。
|
||||
* `@autoclosure` 现在是一个参数声明的属性,而不是参数类型的属性。这里还有一个新的参数声明属性 `@noescape`。更多内容,请看 [属性声明](../03_language_reference/07_Attributes.md#Ideclaration-attributes) 。
|
||||
* 对于类型属性和方法现在可以使用 `static` 关键字作为声明描述符,更多内容,请看 [类型变量属性](../03_language_reference/06_Declarations.md#type-variable-properties)。
|
||||
* Swift 现在包含一个 `as?` 和 `as!` 的向下可失败类型转换运算符。更多内容,请看 [协议遵循性检查](../02_language_guide/21_Protocols.md#checking-for-protocol-conformance)。
|
||||
* 新增 [字符串索引](../02_language_guide/03_Strings_and_Characters.md#string-indices) 的新指导章节。
|
||||
* 在 [溢出运算符](../02_language_guide/27_Advanced_Operators.md#overflow-operators) 一节中删除了溢出除运算符(`&/`)和求余溢出运算符(`&%`)。
|
||||
* 更新常量和常量属性在声明和构造时的规则,更多内容,请看 [常量声明](../03_language_reference/06_Declarations.md#constant-declaration) 。
|
||||
* 更新字符串字面量中 Unicode 标量集的定义,请看 [字符串字面量中的特殊字符](../02_language_guide/03_Strings_and_Characters.md#special-characters-in-string-literals) 。
|
||||
* 更新 [区间运算符](../02_language_guide/02_Basic_Operators.md#range-operators) 章节,注意当半开区间运算符含有相同的起止索引时,其区间为空。
|
||||
* 更新 [闭包引用类型](../02_language_guide/07_Closures.md#closures-are-reference-types) 章节,对于变量的捕获规则进行了阐明。
|
||||
* 更新 [值溢出](../02_language_guide/27_Advanced_Operators.md#value-overflow) 章节,对有符号整数和无符号整数的溢出行为进行了阐明。
|
||||
* 更新 [协议声明](../03_language_reference/06_Declarations.md#protocol-declaration) 章节,对协议声明时的作用域和成员等内容进行了阐明。
|
||||
* 更新 [捕获列表](../02_language_guide/24_Automatic_Reference_Counting.md#defining-a-capture-list) 章节,对于闭包捕获列表中的弱引用和无主引用的使用语法进行了阐明。
|
||||
* 更新 [运算符](../03_language_reference/02_Lexical_Structure.md#operator) 章节,明确指明一些例子来说明自定义运算符所支持的特性,如数学运算符,各种符号,Unicode 符号块等。
|
||||
* 在函数作用域中的常量声明时可以不被初始化,它必须在第一次使用前被赋值。更多的内容,请看 [常量声明](../03_language_reference/06_Declarations.md#constant-declaration)。
|
||||
* 在构造器中,常量属性有且仅能被赋值一次。更多内容,请看 [在构造过程中给常量属性赋值](../02_language_guide/14_Initialization.md#assigning-constant-properties-during-initialization)。
|
||||
* 多个可选绑定现在可以在`if`语句后面以逗号分隔的赋值列表的方式出现,更多内容,请看 [可选绑定](../02_language_guide/01_The_Basics.md#optional-binding)。
|
||||
* 一个 [可选链表达式](../03_language_reference/04_Expressions.md#optional-chaining-expression) 必须出现在后缀表达式中。
|
||||
* 协议类型转换不再局限于 `@obj` 修饰的协议了。
|
||||
* 在运行时可能会失败的类型转换可以使用 `as?` 和 `as!` 运算符,而确保不会失败的类型转换现在使用 `as` 运算符。更多内容,请看 [类型转换运算符](../03_language_reference/04_Expressions.md#type-casting-operator)。
|
||||
|
||||
### 2014-10-16
|
||||
|
||||
* 更新至 Swift 1.1。
|
||||
* 新增 [失败构造器](../02_language_guide/14_Initialization.md#failable-initializers) 的完整指引。
|
||||
* 在协议中新增了 [失败构造器要求](../02_language_guide/21_Protocols.md#failable-initializer-requirements) 的描述。
|
||||
* 常量和变量的 `Any` 类型现可以包含函数实例。更新了有关 `Any` 相关的示例来展示如何在 `switch` 语句中如何检查并转换到一个函数类型。
|
||||
* 带有原始值的枚举类型增加了一个 `rawValue` 属性替代 `toRaw()` 方法,同时使用了一个以 `rawValue` 为参数的失败构造器来替代 `fromRaw()` 方法。更多的内容,请看 [原始值](../02_language_guide/08_Enumerations.md#raw-values) 和 [带原始值的枚举类型](../03_language_reference/06_Declarations.md#enumerations-with-cases-of-a-raw-value-type)。
|
||||
* 新增 [Failable Initializer](../03_language_reference/06_Declarations.md#failable-initializers) 的参考章节,它可以触发初始化失败。
|
||||
* 自定义运算符现在可以包含 `?` 字符,更新了 [运算符](../03_language_reference/02_Lexical_Structure.md#operator) 涉及改进后的规则的部分,并且在 [自定义运算符](../02_language_guide/27-Advanced-Operators.md#custom-operators) 章节中删除了重复的运算符有效字符集合。
|
||||
|
||||
### 2014-08-18
|
||||
|
||||
* 描述 Swift 1.0 的新文档。Swift 是苹果公司发布的全新编程语言,用于 iOS 和 OS X 应用开发。
|
||||
* 在协议中新增了 [对构造器的规定](../02_language_guide/21_Protocols.md#initializer-requirements) 章节。
|
||||
* 新增 [类专属协议](../02_language_guide/21_Protocols.md#class-only-protocol) 章节。
|
||||
* [断言](../02_language_guide/01_The_Basics.md#assertions-and-preconditions) 现在可以使用字符串内插语法,并删除了文档中有冲突的注释。
|
||||
* 更新 [连接字符串和字符](../02_language_guide/03_Strings_and_Characters.md#concatenating-strings-and-characters) 章节来说明字符串和字符不能再用 `+` 号运算符或者复合加法运算符 `+=` 相互连接,这两种运算符现在只能用于字符串之间相连。请使用 `String` 类型的 `append` 方法在一个字符串的尾部增加单个字符。
|
||||
* 在 [属性声明](../03_language_reference/07_Attributes.md#Ideclaration-attributes) 章节增加了有关 `availability` 特性的一些内容。
|
||||
* [可选类型](../02_language_guide/01_The_Basics.md#optionals) 若有值时,不再隐式的转换为 `true`,同样,若无值时,也不再隐式的转换为 `false`,这是为了避免在判别 optional `Bool` 的值时产生困惑。 替代的方案是,用`==` 或 `!=` 运算符显式地去判断 Optinal 是否是 `nil`,以确认其是否包含值。
|
||||
* Swift 新增了一个 [Nil 合并运算符](../02_language_guide/02_Basic_Operators.md#nil-coalescing-operator) (`a ?? b`) , 该表达式中,如果 Optional `a` 的值存在,则取得它并返回,若 Optional `a` 为 `nil`,则返回默认值 `b`
|
||||
* 更新和扩展 [字符串的比较](../02_language_guide/03_Strings_and_Characters.md#comparing-strings) ,用以反映和展示'字符串和字符的比较',以及'前缀(prefix)/后缀(postfix)比较'都开始基于扩展字符集(extended grapheme clusters)规范的等价比较。
|
||||
* 现在,你可以通过下标赋值或者 [可选调用链](../02_language_guide/16_Optional_Chaining.md) 中的可变方法和操作符来给属性设值。相应地更新了有关 [通过可选链接访问属性](../02_language_guide/16_Optional_Chaining.md#accessing-properties-through-optional-chaining) 的内容,并扩展了 [通过可选链接调用方法](../02_language_guide/16_Optional_Chaining.md#calling-methods-through-optional-chaining) 时检查方法调用成功的示例,以显示如何检查属性设置是否成功。
|
||||
* 在可选链中新增了 [访问可选类型的下标脚注](../02_language_guide/16_Optional_Chaining.md#accessing-subscripts-through-optional-chaining) 章节。
|
||||
* 更新 [访问和修改数组](../02_language_guide/04_Collection_Types.md#accessing-and-modifying-a-dictionary) 章节以标示,从该版本起,不能再通过 `+=` 运算符给一个数组新增一个新的项。对应的替代方案是,使 `append` 方法,或者通过 `+=` 运算符来新增一个只有一个项的数组。
|
||||
* 新增一处说明,在 [范围运算符](../02_language_guide/02_Basic_Operators.md#range-operators) 中,比如, `a..b` 和 `a..<b` ,起始值 `a` 不能大于结束值 `b`。
|
||||
* 重写 [继承](../02_language_guide/13_Inheritance.md) 篇章:删除了本章中有关构造器重写的介绍性报道;转而将更多的注意力放到新增的部分——子类的新功能,以及如何通过重写(overrides)修改已有的功能。另外, [重写属性的 Getters 和 Setters](../02_language_guide/13_Inheritance.md#overriding-property-etters-and-setters) 中的例子已经被替换为展示如何重写一个 `description` 属性。 (而有关如何在子类的构造器中修改继承属性的默认值的例子,已经被移到 [构造过程](../02_language_guide/14_Initialization.md) 篇章。)
|
||||
* 更新 [构造器的继承与重写](../02_language_guide/14_Initialization.md#initializer-inheritance-and-overriding) 章节以标示: 重写一个特定的构造器必须使用 `override` 修饰符。
|
||||
* 更新 [Required 构造器](../02_language_guide/14_Initialization.md#required-initializers) 章节以标示:`required` 修饰符现在需要出现在所有子类的 required 构造器的声明中,而 required 构造器的实现,现在可以仅从父类自动继承。
|
||||
* 中置(Infix)的 [运算符函数](../02_language_guide/27_Advanced_Operators.md#operator-functions) 不再需要 `@infix` 属性。
|
||||
* [前置和后置运算符](../02_language_guide/27_Advanced_Operators.md#prefix-and-postfix-operators) 的 `@prefix` 和 `@postfix` 属性,已变更为 `prefix` 和 `postfix` 声明修饰符。
|
||||
* 新增一处说明,在 Prefix 和 postfix 运算符被作用于同一个操作数时 [前置和后置运算符](../02_language_guide/27_Advanced_Operators.md#prefix-and-postfix-operators) 的执行顺序。
|
||||
* [组合赋值运算符](../02_language_guide/27_Advanced_Operators.md#compound-assignment-operators) 的运算符函数不再使用 `@assignment` 属性来定义函数。
|
||||
* 在定义 [自定义操作符](../02_language_guide/27_Advanced_Operators.md#custom-operators) 时,`修饰符(Modifiers)的出现顺序发生变化`。比如现在,你该编写 `prefix operator`, 而不是 `operator prefix`。
|
||||
* 在 [声明修饰符](../03_language_reference/06_Declarations.md#declaration-modifiers) 章节新增了有关 `dynamic` 声明修饰符的内容。
|
||||
* 新增有关 [字面量](../03_language_reference/02_Lexical_Structure.md#literal) 类型推导内容的内容。
|
||||
* 新增更多有关柯里化函数的内容。
|
||||
* 新增 [权限控制](../02_language_guide/26_Access_Control.md) 篇章。
|
||||
* 更新 [字符串和字符](../02_language_guide/03_Strings_and_Characters.md) 章节,在 Swift 中现在 `Character` 类型代表的是扩展字符集(extended grapheme cluster)中的一个 Unicode,为此,新增了 [Extended Grapheme Clusters](../02_language_guide/03_Strings_and_Characters.md#extended-grapheme-clusters) 章节。同时,[Unicode 标量](../02_language_guide/03-Strings-And-Characters.md#unicode-scalars-representation) 和 [字符串比较](../02_language_guide/03-Strings-And-Characters.md#comparing-strings) 章节新增了更多内容。
|
||||
* 更新 [字符串字面量](../02_language_guide/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` 属性。在 [字符串和字符](../02_language_guide/03_Strings_and_Characters.md) 章节中删除了对应部分,并更新了各种对应的代码用例。
|
||||
* 新增 [没有外部名的构造器参数](../02_language_guide/14_Initialization.md#initializer-parameters-without-external-names) 章节。
|
||||
* 新增 [Required 构造器](../02_language_guide/14_Initialization.md#required-initializers) 章节。
|
||||
* 新增 [可选元组返回类型](../02_language_guide/06_Functions.md#optional-tuple-return-types) 章节。
|
||||
* 更新 [类型注解](../02_language_guide/01_The_Basics.md#type-annotations) 章节,多个相关变量可以用"类型注解”在同一行中声明为同一类型。
|
||||
* `@optional`, `@lazy`, `@final`, `@required` 等关键字被更新为 `optional`, `lazy`, `final`, `required` 参见 [声明修饰符](../03_language_reference/06_Declarations.md#declaration-modifiers)。
|
||||
* 更新了整本书中有关 `..<` 的引用,从半闭区间改为了 [半开区间](../02_language_guide/02_Basic_Operators.md#half-open-range-operator)。
|
||||
* 更新 [读取和修改字典](../02_language_guide/04_Collection_Types.md#accessing-and-modifying-a-dictionary) 章节, `Dictionary` 现在增加了一个 Boolean 型的属性:`isEmpty`。
|
||||
* 解释了哪些字符(集)可被用来定义 [自定义操作符](../02_language_guide/27_Advanced_Operators.md#custom-operators)。
|
||||
* `nil` 和布尔运算中的 `true` 和 `false` 现在被定义为 [字面量](../03_language_reference/02_Lexical_Structure.md#literal)。
|
||||
* Swift 中的数组 (`Array`) 类型从现在起具备了完整的值语义。具体内容被更新到 [集合的可变性](../02_language_guide/04_Collection_Types.md#mutability-of-collections) 和 [数组](../02_language_guide/04_Collection_Types.md#arrays) 两小节,以反映这个新的变化。 此外,还解释了如何给 Strings, Arrays 和 Dictionaries 进行赋值和拷贝。
|
||||
* [数组类型速记语法](../02_language_guide/04_Collection_Types.md#array-type-shorthand-syntax) 从 `SomeType []` 更新为 ` [SomeType]`。
|
||||
* 新增 [字典类型的速记语法](../02_language_guide/04_Collection_Types.md#dictionary-type-shorthand-syntax) 章节,现在书写格式为: ` [KeyType: ValueType]`。
|
||||
* 新增 [字典键类型的哈希值](../02_language_guide/04_Collection_Types.md#hash-values-for-set-types) 章节。
|
||||
* [闭包表达式](../02_language_guide/07_Closures.md#closure-expressions) 示例中使用新的全局函数 `sorted` 取代原先的全局函数 `sort` 去展示如何返回一个全新的数组。
|
||||
* 更新 [结构体逐一成员构造器](../02_language_guide/14_Initialization.md#memberwise-initializers-for-structure-types) 章节,即使结构体的成员 `没有默认值`,逐一成员构造器也可以自动获得。
|
||||
* [半开区间运算符](../02_language_guide/02_Basic_Operators.md#half-open-range-operator) 中`..` 更新为 `..<`。
|
||||
* 新增 [泛型拓展](../02_language_guide/22_Generics.md#extending-a-generic-type) 的示例。
|
||||
|
||||
|
||||
@ -1,49 +1,49 @@
|
||||
# Summary
|
||||
|
||||
* [Introduction](README.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](01_welcome_to_swift/01_about_swift.md)
|
||||
* [版本兼容性](01_welcome_to_swift/02_version_compatibility.md)
|
||||
* [Swift 初见](01_welcome_to_swift/03_a_swift_tour.md)
|
||||
* [Swift 版本历史记录](04_revision_history/04_revision_history.md)
|
||||
* Swift 教程
|
||||
* [基础部分](chapter2/01_The_Basics.md)
|
||||
* [基本运算符](chapter2/02_Basic_Operators.md)
|
||||
* [字符串和字符](chapter2/03_Strings_and_Characters.md)
|
||||
* [集合类型](chapter2/04_Collection_Types.md)
|
||||
* [控制流](chapter2/05_Control_Flow.md)
|
||||
* [函数](chapter2/06_Functions.md)
|
||||
* [闭包](chapter2/07_Closures.md)
|
||||
* [枚举](chapter2/08_Enumerations.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_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/23_Automatic_Reference_Counting.md)
|
||||
* [内存安全](chapter2/24_Memory_Safety.md)
|
||||
* [访问控制](chapter2/25_Access_Control.md)
|
||||
* [高级运算符](chapter2/26_Advanced_Operators.md)
|
||||
* [基础部分](02_language_guide/01_The_Basics.md)
|
||||
* [基本运算符](02_language_guide/02_Basic_Operators.md)
|
||||
* [字符串和字符](02_language_guide/03_Strings_and_Characters.md)
|
||||
* [集合类型](02_language_guide/04_Collection_Types.md)
|
||||
* [控制流](02_language_guide/05_Control_Flow.md)
|
||||
* [函数](02_language_guide/06_Functions.md)
|
||||
* [闭包](02_language_guide/07_Closures.md)
|
||||
* [枚举](02_language_guide/08_Enumerations.md)
|
||||
* [类和结构体](02_language_guide/09_Structures_And_Classes.md)
|
||||
* [属性](02_language_guide/10_Properties.md)
|
||||
* [方法](02_language_guide/11_Methods.md)
|
||||
* [下标](02_language_guide/12_Subscripts.md)
|
||||
* [继承](02_language_guide/13_Inheritance.md)
|
||||
* [构造过程](02_language_guide/14_Initialization.md)
|
||||
* [析构过程](02_language_guide/15_Deinitialization.md)
|
||||
* [可选链](02_language_guide/16_Optional_Chaining.md)
|
||||
* [错误处理](02_language_guide/17_Error_Handling.md)
|
||||
* [并发](02_language_guide/28_Concurrency.md)
|
||||
* [类型转换](02_language_guide/18_Type_Casting.md)
|
||||
* [嵌套类型](02_language_guide/19_Nested_Types.md)
|
||||
* [扩展](02_language_guide/20_Extensions.md)
|
||||
* [协议](02_language_guide/21_Protocols.md)
|
||||
* [泛型](02_language_guide/22_Generics.md)
|
||||
* [不透明类型](02_language_guide/23_Opaque_Types.md)
|
||||
* [自动引用计数](02_language_guide/24_Automatic_Reference_Counting.md)
|
||||
* [内存安全](02_language_guide/25_Memory_Safety.md)
|
||||
* [访问控制](02_language_guide/26_Access_Control.md)
|
||||
* [高级运算符](02_language_guide/27_Advanced_Operators.md)
|
||||
* 语言参考
|
||||
* [关于语言参考](chapter3/01_About_the_Language_Reference.md)
|
||||
* [词法结构](chapter3/02_Lexical_Structure.md)
|
||||
* [类型](chapter3/03_Types.md)
|
||||
* [表达式](chapter3/04_Expressions.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)
|
||||
* [关于语言参考](03_language_reference/01_About_the_Language_Reference.md)
|
||||
* [词法结构](03_language_reference/02_Lexical_Structure.md)
|
||||
* [类型](03_language_reference/03_Types.md)
|
||||
* [表达式](03_language_reference/04_Expressions.md)
|
||||
* [语句](03_language_reference/05_Statements.md)
|
||||
* [声明](03_language_reference/06_Declarations.md)
|
||||
* [特性](03_language_reference/07_Attributes.md)
|
||||
* [模式](03_language_reference/08_Patterns.md)
|
||||
* [泛型参数](03_language_reference/09_Generic_Parameters_and_Arguments.md)
|
||||
* [语法总结](03_language_reference/10_Summary_of_the_Grammar.md)
|
||||
* 翻译贡献者
|
||||
* [翻译贡献者](contributors.md)
|
||||
|
||||
* [翻译贡献者](contributors.md)
|
||||
|
||||
@ -1,10 +0,0 @@
|
||||
# 版本兼容性
|
||||
|
||||
本书描述的是在 Xcode 10.2 中的默认 Swift 版本 Swift 5。你可以使用 Xcode10.2 来构建 Swift 5、Swift 4.2 或 Swift 4 写的项目
|
||||
|
||||
当您使用 Xcode 10.2 构建 Swift 4 和 Swift 4.2 代码时,除了下面的功能仅支持 Swift 5,其他大多数功能都依然可用。
|
||||
|
||||
* **try?** 表达式不会为已返回可选类型的代码引入额外的可选类型层级。
|
||||
* 大数字的整型字面量初始化代码的类型将会被正确推导,例如 **UInt64(0xffff_ffff_ffff_ffff)** 将会被推导为整型类型而非溢出。
|
||||
|
||||
用 Swift 5 写的项目可以依赖用 Swift 4.2 或 Swift 4 写的项目,反之亦然。这意味着,如果你将一个大的项目分解成多个框架(framework),你可以每次一个框架地迁移 Swift 4 代码到 Swift 5。
|
||||
@ -1,256 +0,0 @@
|
||||
# Swift 文档修订历史
|
||||
|
||||
### 2019-01-24
|
||||
|
||||
* 更新到 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-type’s-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)部分的讨论,现在协议组合类型支持进行父类约束了。
|
||||
* 更新了[拓展声明](../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)。
|
||||
* 增加一个可失败数值转换的例子[到可失败构造器](../chapter2/14_Initialization.md#failable-initializers)章节。
|
||||
* 增加关于使用 Swift 语言版本的 `available` 特性内容到[声明特性](../chapter3/07_Attributes.md#Ideclaration-attributes)章节。
|
||||
* 更新了[函数类型](../chapter3/03_Types.md#function_type)章节中的描述,注意在写函数类型时不允许使用参数标签。
|
||||
* 更新了[条件编译块](../chapter3/05_Statements.md#Conditional-Compilation-Block)章节中的 Swift 语言版本号的描述,现在可以使用可选的补丁版本号。
|
||||
* 更新了[函数类型](../chapter3/03_Types.md#function_type)>章节的描述,现在 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)和[泛型形参和实参](../chapter3/09_Generic_Parameters_And_Arguments.md)章节中[泛型 Where 语句](../chapter2/22_Generics.md#extensions-with-a-generic-where-clause)部分,现在泛型的 where 语句写在一个声明的最后。
|
||||
* 更新[逃逸闭包](../chapter2/07_Closures.md#escaping-closures)一节,现在闭包默认为非逃逸的(noescaping)。
|
||||
* 更新[基础部分](../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)一节,现在函数参数标签不包含在函数类型中。
|
||||
* 更新[协议](../chapter2/21_Protocols.md)一章[协议组合](../chapter2/21_Protocols.md#protocol-composition)部分和[类型](../chapter3/03_Types.md)一章[协议组合类型](../chapter3/03_Types.md#protocol-composition)部分关于使用新的 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)一节中关于泛型类型别名和在协议内使用类型别名的信息。
|
||||
* 更新[属性](../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)一节中新增了关于元类型值的比较和使用它们通过构造器表达式构造实例相关内容。
|
||||
* 在[断言调试](../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)一节中新增了关于如何从元类型值中构造类实例相关内容。
|
||||
* 在[弱引用](../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-4-8
|
||||
|
||||
* 更新至 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,苹果公司针对 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)的类型推导内容。
|
||||
* 为章节 Curried Functions 添加了更多的信息。
|
||||
* 加入新的章节[权限控制(../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)的示例。
|
||||
Binary file not shown.
Binary file not shown.
@ -1,646 +0,0 @@
|
||||
# 集合类型
|
||||
|
||||
Swift 语言提供 `Arrays`、`Sets` 和 `Dictionaries` 三种基本的*集合类型*用来存储集合数据。数组(Arrays)是有序数据的集。集合(Sets)是无序无重复数据的集。字典(Dictionaries)是无序的键值对的集。
|
||||
|
||||

|
||||
|
||||
Swift 语言中的 `Arrays`、`Sets` 和 `Dictionaries` 中存储的数据值类型必须明确。这意味着我们不能把错误的数据类型插入其中。同时这也说明你完全可以对取回值的类型非常放心。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> Swift 的 `Arrays`、`Sets` 和 `Dictionaries` 类型被实现为*泛型集合*。更多关于泛型类型和集合,参见 [泛型](./23_Generics.md)章节。
|
||||
|
||||
## 集合的可变性 {#mutability-of-collections}
|
||||
|
||||
如果创建一个 `Arrays`、`Sets` 或 `Dictionaries` 并且把它分配成一个变量,这个集合将会是*可变的*。这意味着你可以在创建之后添加更多或移除已存在的数据项,或者改变集合中的数据项。如果我们把 `Arrays`、`Sets` 或 `Dictionaries` 分配成常量,那么它就是*不可变的*,它的大小和内容都不能被改变。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 在我们不需要改变集合的时候创建不可变集合是很好的实践。如此 Swift 编译器可以优化我们创建的集合。
|
||||
|
||||
## 数组(Arrays) {#arrays}
|
||||
|
||||
*数组*使用有序列表存储同一类型的多个值。相同的值可以多次出现在一个数组的不同位置中。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> Swift 的 `Array` 类型被桥接到 `Foundation` 中的 `NSArray` 类。更多关于在 `Foundation` 和 `Cocoa` 中使用 `Array` 的信息,参见 [*Using Swift with Cocoa and Obejective-C(Swift 4.1)*](https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 中[使用 Cocoa 数据类型](https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html#//apple_ref/doc/uid/TP40014216-CH6)部分。
|
||||
|
||||
### 数组的简单语法 {#array-type-shorthand-syntax}
|
||||
|
||||
写 Swift 数组应该遵循像 `Array<Element>` 这样的形式,其中 `Element` 是这个数组中唯一允许存在的数据类型。我们也可以使用像 `[Element]` 这样的简单语法。尽管两种形式在功能上是一样的,但是推荐较短的那种,而且在本文中都会使用这种形式来使用数组。
|
||||
|
||||
### 创建一个空数组 {#creating-an-empty-array}
|
||||
|
||||
我们可以使用构造语法来创建一个由特定数据类型构成的空数组:
|
||||
|
||||
```swift
|
||||
var someInts = [Int]()
|
||||
print("someInts is of type [Int] with \(someInts.count) items.")
|
||||
// 打印“someInts is of type [Int] with 0 items.”
|
||||
```
|
||||
|
||||
注意,通过构造函数的类型,`someInts` 的值类型被推断为 `[Int]`。
|
||||
|
||||
或者,如果代码上下文中已经提供了类型信息,例如一个函数参数或者一个已经定义好类型的常量或者变量,我们可以使用空数组语句创建一个空数组,它的写法很简单:`[]`(一对空方括号):
|
||||
|
||||
```swift
|
||||
someInts.append(3)
|
||||
// someInts 现在包含一个 Int 值
|
||||
someInts = []
|
||||
// someInts 现在是空数组,但是仍然是 [Int] 类型的。
|
||||
```
|
||||
|
||||
### 创建一个带有默认值的数组 {#creating-an-array-with-a-default-value}
|
||||
|
||||
Swift 中的 `Array` 类型还提供一个可以创建特定大小并且所有数据都被默认的构造方法。我们可以把准备加入新数组的数据项数量(`count`)和适当类型的初始值(`repeating`)传入数组构造函数:
|
||||
|
||||
```swift
|
||||
var threeDoubles = Array(repeating: 0.0, count: 3)
|
||||
// threeDoubles 是一种 [Double] 数组,等价于 [0.0, 0.0, 0.0]
|
||||
```
|
||||
|
||||
### 通过两个数组相加创建一个数组 {#creating-an-array-by-adding-two-arrays-together}
|
||||
|
||||
我们可以使用加法操作符(`+`)来组合两种已存在的相同类型数组。新数组的数据类型会被从两个数组的数据类型中推断出来:
|
||||
|
||||
```swift
|
||||
var anotherThreeDoubles = Array(repeating: 2.5, count: 3)
|
||||
// anotherThreeDoubles 被推断为 [Double],等价于 [2.5, 2.5, 2.5]
|
||||
|
||||
var sixDoubles = threeDoubles + anotherThreeDoubles
|
||||
// sixDoubles 被推断为 [Double],等价于 [0.0, 0.0, 0.0, 2.5, 2.5, 2.5]
|
||||
```
|
||||
|
||||
### 用数组字面量构造数组 {#creating-an-array-with-an-array-literals}
|
||||
|
||||
我们可以使用*数组字面量*来进行数组构造,这是一种用一个或者多个数值构造数组的简单方法。数组字面量是一系列由逗号分割并由方括号包含的数值:
|
||||
|
||||
`[value 1, value 2, value 3]`。
|
||||
|
||||
下面这个例子创建了一个叫做 `shoppingList` 并且存储 `String` 的数组:
|
||||
|
||||
```swift
|
||||
var shoppingList: [String] = ["Eggs", "Milk"]
|
||||
// shoppingList 已经被构造并且拥有两个初始项。
|
||||
```
|
||||
|
||||
`shoppingList` 变量被声明为“字符串值类型的数组“,记作 `[String]`。 因为这个数组被规定只有 `String` 一种数据结构,所以只有 `String` 类型可以在其中被存取。 在这里,`shoppingList` 数组由两个 `String` 值(`"Eggs"` 和 `"Milk"`)构造,并且由数组字面量定义。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> `shoppingList` 数组被声明为变量(`var` 关键字创建)而不是常量(`let` 创建)是因为以后可能会有更多的数据项被插入其中。
|
||||
|
||||
在这个例子中,字面量仅仅包含两个 `String` 值。匹配了该数组的变量声明(只能包含 `String` 的数组),所以这个字面量的分配过程可以作为用两个初始项来构造 `shoppingList` 的一种方式。
|
||||
|
||||
由于 Swift 的类型推断机制,当我们用字面量构造只拥有相同类型值数组的时候,我们不必把数组的类型定义清楚。`shoppingList` 的构造也可以这样写:
|
||||
|
||||
```swift
|
||||
var shoppingList = ["Eggs", "Milk"]
|
||||
```
|
||||
|
||||
因为所有数组字面量中的值都是相同的类型,Swift 可以推断出 `[String]` 是 `shoppingList` 中变量的正确类型。
|
||||
|
||||
### 访问和修改数组 {#accessing-and-modifying-an-array}
|
||||
|
||||
我们可以通过数组的方法和属性来访问和修改数组,或者使用下标语法。
|
||||
|
||||
可以使用数组的只读属性 `count` 来获取数组中的数据项数量:
|
||||
|
||||
```swift
|
||||
print("The shopping list contains \(shoppingList.count) items.")
|
||||
// 输出“The shopping list contains 2 items.”(这个数组有2个项)
|
||||
```
|
||||
|
||||
使用布尔属性 `isEmpty` 作为一个缩写形式去检查 `count` 属性是否为 `0`:
|
||||
|
||||
```swift
|
||||
if shoppingList.isEmpty {
|
||||
print("The shopping list is empty.")
|
||||
} else {
|
||||
print("The shopping list is not empty.")
|
||||
}
|
||||
// 打印“The shopping list is not empty.”(shoppinglist 不是空的)
|
||||
```
|
||||
|
||||
也可以使用 `append(_:)` 方法在数组后面添加新的数据项:
|
||||
|
||||
```swift
|
||||
shoppingList.append("Flour")
|
||||
// shoppingList 现在有3个数据项,有人在摊煎饼
|
||||
```
|
||||
|
||||
除此之外,使用加法赋值运算符(`+=`)也可以直接在数组后面添加一个或多个拥有相同类型的数据项:
|
||||
|
||||
```swift
|
||||
shoppingList += ["Baking Powder"]
|
||||
// shoppingList 现在有四项了
|
||||
shoppingList += ["Chocolate Spread", "Cheese", "Butter"]
|
||||
// shoppingList 现在有七项了
|
||||
```
|
||||
|
||||
可以直接使用下标语法来获取数组中的数据项,把我们需要的数据项的索引值放在直接放在数组名称的方括号中:
|
||||
|
||||
```swift
|
||||
var firstItem = shoppingList[0]
|
||||
// 第一项是“Eggs”
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 第一项在数组中的索引值是 `0` 而不是 `1`。 Swift 中的数组索引总是从零开始。
|
||||
|
||||
我们也可以用下标来改变某个已有索引值对应的数据值:
|
||||
|
||||
```swift
|
||||
shoppingList[0] = "Six eggs"
|
||||
// 其中的第一项现在是“Six eggs”而不是“Eggs”
|
||||
```
|
||||
|
||||
还可以利用下标来一次改变一系列数据值,即使新数据和原有数据的数量是不一样的。下面的例子把 `"Chocolate Spread"`、`"Cheese"` 和 `"Butter"` 替换为 `"Bananas"` 和 `"Apples"`:
|
||||
|
||||
```swift
|
||||
shoppingList[4...6] = ["Bananas", "Apples"]
|
||||
// shoppingList 现在有6项
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 不可以用下标访问的形式去在数组尾部添加新项。
|
||||
|
||||
调用数组的 `insert(_:at:)` 方法来在某个具体索引值之前添加数据项:
|
||||
|
||||
```swift
|
||||
shoppingList.insert("Maple Syrup", at: 0)
|
||||
// shoppingList 现在有7项
|
||||
// 现在是这个列表中的第一项是“Maple Syrup”
|
||||
```
|
||||
|
||||
这次 `insert(_:at:)` 方法调用把值为 `"Maple Syrup"` 的新数据项插入列表的最开始位置,并且使用 `0` 作为索引值。
|
||||
|
||||
类似的我们可以使用 `remove(at:)` 方法来移除数组中的某一项。这个方法把数组在特定索引值中存储的数据项移除并且返回这个被移除的数据项(我们不需要的时候就可以无视它):
|
||||
|
||||
```swift
|
||||
let mapleSyrup = shoppingList.remove(at: 0)
|
||||
// 索引值为0的数据项被移除
|
||||
// shoppingList 现在只有6项,而且不包括 Maple Syrup
|
||||
// mapleSyrup 常量的值等于被移除数据项“Maple Syrup”的值
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果我们试着对索引越界的数据进行检索或者设置新值的操作,会引发一个运行期错误。我们可以使用索引值和数组的 `count` 属性进行比较来在使用某个索引之前先检验是否有效。除了当 `count` 等于 0 时(说明这是个空数组),最大索引值一直是 `count - 1`,因为数组都是零起索引。
|
||||
|
||||
数据项被移除后数组中的空出项会被自动填补,所以现在索引值为 `0` 的数据项的值再次等于 `"Six eggs"`:
|
||||
|
||||
```swift
|
||||
firstItem = shoppingList[0]
|
||||
// firstItem 现在等于“Six eggs”
|
||||
```
|
||||
|
||||
如果我们只想把数组中的最后一项移除,可以使用 `removeLast()` 方法而不是 `remove(at:)` 方法来避免我们需要获取数组的 `count` 属性。就像后者一样,前者也会返回被移除的数据项:
|
||||
|
||||
```swift
|
||||
let apples = shoppingList.removeLast()
|
||||
// 数组的最后一项被移除了
|
||||
// shoppingList 现在只有5项,不包括 Apples
|
||||
// apples 常量的值现在等于“Apples”字符串
|
||||
```
|
||||
|
||||
### 数组的遍历 {#iterating-over-an-array}
|
||||
|
||||
我们可以使用 `for-in` 循环来遍历所有数组中的数据项:
|
||||
|
||||
```swift
|
||||
for item in shoppingList {
|
||||
print(item)
|
||||
}
|
||||
// Six eggs
|
||||
// Milk
|
||||
// Flour
|
||||
// Baking Powder
|
||||
// Bananas
|
||||
```
|
||||
|
||||
如果我们同时需要每个数据项的值和索引值,可以使用 `enumerated()` 方法来进行数组遍历。`enumerated()` 返回一个由每一个数据项索引值和数据值组成的元组。我们可以把这个元组分解成临时常量或者变量来进行遍历:
|
||||
|
||||
```swift
|
||||
for (index, value) in shoppingList.enumerated() {
|
||||
print("Item \(String(index + 1)): \(value)")
|
||||
}
|
||||
// Item 1: Six eggs
|
||||
// Item 2: Milk
|
||||
// Item 3: Flour
|
||||
// Item 4: Baking Powder
|
||||
// Item 5: Bananas
|
||||
```
|
||||
|
||||
更多关于 `for-in` 循环的介绍请参见[for 循环](05_Control_Flow.html#for_loops)。
|
||||
|
||||
## 集合(Sets) {#sets}
|
||||
|
||||
*集合(Set)*用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以使用集合而不是数组。
|
||||
|
||||
> 注意
|
||||
> Swift 的 `Set` 类型被桥接到 `Foundation` 中的 `NSSet` 类。
|
||||
>
|
||||
> 关于使用 `Foundation` 和 `Cocoa` 中 `Set` 的知识,参见 [*Using Swift with Cocoa and Obejective-C(Swift 4.1)*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 中[使用 Cocoa 数据类型](https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html#//apple_ref/doc/uid/TP40014216-CH6)部分。
|
||||
|
||||
### 集合类型的哈希值 {#hash-values-for-set-types}
|
||||
|
||||
一个类型为了存储在集合中,该类型必须是*可哈希化*的——也就是说,该类型必须提供一个方法来计算它的*哈希值*。一个哈希值是 `Int` 类型的,相等的对象哈希值必须相同,比如 `a==b`,因此必须 `a.hashValue == b.hashValue`。
|
||||
|
||||
Swift 的所有基本类型(比如 `String`、`Int`、`Double` 和 `Bool`)默认都是可哈希化的,可以作为集合的值的类型或者字典的键的类型。没有关联值的枚举成员值(在[枚举](./08_Enumerations.md)有讲述)默认也是可哈希化的。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 你可以使用你自定义的类型作为集合的值的类型或者是字典的键的类型,但你需要使你的自定义类型遵循 Swift 标准库中的 `Hashable` 协议。遵循 `Hashable` 协议的类型需要提供一个类型为 `Int` 的可读属性 `hashValue`。由类型的 `hashValue` 属性返回的值不需要在同一程序的不同执行周期或者不同程序之间保持相同。
|
||||
>
|
||||
> 因为 `Hashable` 协议遵循 `Equatable` 协议,所以遵循该协议的类型也必须提供一个“是否相等”运算符(`==`)的实现。这个 `Equatable` 协议要求任何遵循 `==` 实现的实例间都是一种相等的关系。也就是说,对于 `a,b,c` 三个值来说,`==` 的实现必须满足下面三种情况:
|
||||
|
||||
> * `a == a`(自反性)
|
||||
> * `a == b` 意味着 `b == a`(对称性)
|
||||
> * `a == b && b == c` 意味着 `a == c`(传递性)
|
||||
|
||||
关于遵循协议的更多信息,请看[协议](./22_Protocols.md)。
|
||||
|
||||
### 集合类型语法 {#set-type-syntax}
|
||||
|
||||
Swift 中的 `Set` 类型被写为 `Set<Element>`,这里的 `Element` 表示 `Set` 中允许存储的类型,和数组不同的是,集合没有等价的简化形式。
|
||||
|
||||
### 创建和构造一个空的集合 {#creating-and-initalizing-an-empty-set}
|
||||
|
||||
你可以通过构造器语法创建一个特定类型的空集合:
|
||||
|
||||
```swift
|
||||
var letters = Set<Character>()
|
||||
print("letters is of type Set<Character> with \(letters.count) items.")
|
||||
// 打印“letters is of type Set<Character> with 0 items.”
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 通过构造器,这里的 `letters` 变量的类型被推断为 `Set<Character>`。
|
||||
|
||||
此外,如果上下文提供了类型信息,比如作为函数的参数或者已知类型的变量或常量,我们可以通过一个空的数组字面量创建一个空的 `Set`:
|
||||
|
||||
```swift
|
||||
letters.insert("a")
|
||||
// letters 现在含有1个 Character 类型的值
|
||||
letters = []
|
||||
// letters 现在是一个空的 Set,但是它依然是 Set<Character> 类型
|
||||
```
|
||||
|
||||
### 用数组字面量创建集合 {#creating-a-set-with-an-array-literal}
|
||||
|
||||
你可以使用数组字面量来构造集合,并且可以使用简化形式写一个或者多个值作为集合元素。
|
||||
|
||||
下面的例子创建一个称之为 `favoriteGenres` 的集合来存储 `String` 类型的值:
|
||||
|
||||
```swift
|
||||
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
|
||||
// favoriteGenres 被构造成含有三个初始值的集合
|
||||
```
|
||||
|
||||
这个 `favoriteGenres` 变量被声明为“一个 `String` 值的集合”,写为 `Set<String>`。由于这个特定的集合含有指定 `String` 类型的值,所以它只允许存储 `String` 类型值。这里的 `favoriteGenres` 变量有三个 `String` 类型的初始值(`"Rock"`,`"Classical"` 和 `"Hip hop"`),并以数组字面量的方式出现。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> `favoriteGenres` 被声明为一个变量(拥有 `var` 标示符)而不是一个常量(拥有 `let` 标示符),因为它里面的元素将会在下面的例子中被增加或者移除。
|
||||
|
||||
一个 `Set` 类型不能从数组字面量中被单独推断出来,因此 `Set` 类型必须显式声明。然而,由于 Swift 的类型推断功能,如果你想使用一个数组字面量构造一个 `Set` 并且该数组字面量中的所有元素类型相同,那么你无须写出 `Set` 的具体类型。`favoriteGenres` 的构造形式可以采用简化的方式代替:
|
||||
|
||||
```swift
|
||||
var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]
|
||||
```
|
||||
|
||||
由于数组字面量中的所有元素类型相同,Swift 可以推断出 `Set<String>` 作为 `favoriteGenres` 变量的正确类型。
|
||||
|
||||
### 访问和修改一个集合 {#accesing-and-modifying-a-set}
|
||||
|
||||
你可以通过 `Set` 的属性和方法来访问和修改一个 `Set`。
|
||||
|
||||
为了找出一个 `Set` 中元素的数量,可以使用其只读属性 `count`:
|
||||
|
||||
```swift
|
||||
print("I have \(favoriteGenres.count) favorite music genres.")
|
||||
// 打印“I have 3 favorite music genres.”
|
||||
```
|
||||
|
||||
使用布尔属性 `isEmpty` 作为一个缩写形式去检查 `count` 属性是否为 `0`:
|
||||
|
||||
```swift
|
||||
if favoriteGenres.isEmpty {
|
||||
print("As far as music goes, I'm not picky.")
|
||||
} else {
|
||||
print("I have particular music preferences.")
|
||||
}
|
||||
// 打印“I have particular music preferences.”
|
||||
```
|
||||
|
||||
你可以通过调用 `Set` 的 `insert(_:)` 方法来添加一个新元素:
|
||||
|
||||
```swift
|
||||
favoriteGenres.insert("Jazz")
|
||||
// favoriteGenres 现在包含4个元素
|
||||
```
|
||||
|
||||
你可以通过调用 `Set` 的 `remove(_:)` 方法去删除一个元素,如果该值是该 `Set` 的一个元素则删除该元素并且返回被删除的元素值,否则如果该 `Set` 不包含该值,则返回 `nil`。另外,`Set` 中的所有元素可以通过它的 `removeAll()` 方法删除。
|
||||
|
||||
```swift
|
||||
if let removedGenre = favoriteGenres.remove("Rock") {
|
||||
print("\(removedGenre)? I'm over it.")
|
||||
} else {
|
||||
print("I never much cared for that.")
|
||||
}
|
||||
// 打印“Rock? I'm over it.”
|
||||
```
|
||||
|
||||
使用 `contains(_:)` 方法去检查 `Set` 中是否包含一个特定的值:
|
||||
|
||||
```swift
|
||||
if favoriteGenres.contains("Funk") {
|
||||
print("I get up on the good foot.")
|
||||
} else {
|
||||
print("It's too funky in here.")
|
||||
}
|
||||
// 打印“It's too funky in here.”
|
||||
```
|
||||
|
||||
### 遍历一个集合 {#iterating-over-a-set}
|
||||
|
||||
你可以在一个 `for-in` 循环中遍历一个 `Set` 中的所有值。
|
||||
|
||||
```swift
|
||||
for genre in favoriteGenres {
|
||||
print("\(genre)")
|
||||
}
|
||||
// Classical
|
||||
// Jazz
|
||||
// Hip hop
|
||||
```
|
||||
|
||||
更多关于 `for-in` 循环的信息,参见[For 循环](./05_Control_Flow.md#for_loops)。
|
||||
|
||||
Swift 的 `Set` 类型没有确定的顺序,为了按照特定顺序来遍历一个 `Set` 中的值可以使用 `sorted()` 方法,它将返回一个有序数组,这个数组的元素排列顺序由操作符'<'对元素进行比较的结果来确定。
|
||||
|
||||
```swift
|
||||
for genre in favoriteGenres.sorted() {
|
||||
print("\(genre)")
|
||||
}
|
||||
// Classical
|
||||
// Hip hop
|
||||
// Jazz
|
||||
```
|
||||
|
||||
## 集合操作 {#performing-set-operations}
|
||||
|
||||
你可以高效地完成 `Set` 的一些基本操作,比如把两个集合组合到一起,判断两个集合共有元素,或者判断两个集合是否全包含,部分包含或者不相交。
|
||||
|
||||
### 基本集合操作 {#fundamental-set-operations}
|
||||
|
||||
下面的插图描述了两个集合 `a` 和 `b`,以及通过阴影部分的区域显示集合各种操作的结果。
|
||||
|
||||

|
||||
|
||||
* 使用 `intersection(_:)` 方法根据两个集合中都包含的值创建的一个新的集合。
|
||||
* 使用 `symmetricDifference(_:)` 方法根据在一个集合中但不在两个集合中的值创建一个新的集合。
|
||||
* 使用 `union(_:)` 方法根据两个集合的值创建一个新的集合。
|
||||
* 使用 `subtracting(_:)` 方法根据不在该集合中的值创建一个新的集合。
|
||||
|
||||
```swift
|
||||
let oddDigits: Set = [1, 3, 5, 7, 9]
|
||||
let evenDigits: Set = [0, 2, 4, 6, 8]
|
||||
let singleDigitPrimeNumbers: Set = [2, 3, 5, 7]
|
||||
|
||||
oddDigits.union(evenDigits).sorted()
|
||||
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
oddDigits.intersection(evenDigits).sorted()
|
||||
// []
|
||||
oddDigits.subtracting(singleDigitPrimeNumbers).sorted()
|
||||
// [1, 9]
|
||||
oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted()
|
||||
// [1, 2, 9]
|
||||
```
|
||||
|
||||
### 集合成员关系和相等 {#set-membership-and-equality}
|
||||
|
||||
下面的插图描述了三个集合 `a`、`b` 和 `c`,以及通过重叠区域表述集合间共享的元素。集合 `a` 是集合 `b` 的父集合,因为 `a` 包含了 `b` 中所有的元素,相反的,集合 `b` 是集合 `a` 的子集合,因为属于 `b` 的元素也被 `a` 包含。集合 `b` 和集合 `c` 彼此不关联,因为它们之间没有共同的元素。
|
||||
|
||||

|
||||
|
||||
* 使用“是否相等”运算符(`==`)来判断两个集合是否包含全部相同的值。
|
||||
* 使用 `isSubset(of:)` 方法来判断一个集合中的值是否也被包含在另外一个集合中。
|
||||
* 使用 `isSuperset(of:)` 方法来判断一个集合中包含另一个集合中所有的值。
|
||||
* 使用 `isStrictSubset(of:)` 或者 `isStrictSuperset(of:)` 方法来判断一个集合是否是另外一个集合的子集合或者父集合并且两个集合并不相等。
|
||||
* 使用 `isDisjoint(with:)` 方法来判断两个集合是否不含有相同的值(是否没有交集)。
|
||||
|
||||
```swift
|
||||
let houseAnimals: Set = ["🐶", "🐱"]
|
||||
let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"]
|
||||
let cityAnimals: Set = ["🐦", "🐭"]
|
||||
|
||||
houseAnimals.isSubset(of: farmAnimals)
|
||||
// true
|
||||
farmAnimals.isSuperset(of: houseAnimals)
|
||||
// true
|
||||
farmAnimals.isDisjoint(with: cityAnimals)
|
||||
// true
|
||||
```
|
||||
|
||||
## 字典 {#dictionaries}
|
||||
|
||||
*字典*是一种存储多个相同类型的值的容器。每个值(value)都关联唯一的键(key),键作为字典中的这个值数据的标识符。和数组中的数据项不同,字典中的数据项并没有具体顺序。我们在需要通过标识符(键)访问数据的时候使用字典,这种方法很大程度上和我们在现实世界中使用字典查字义的方法一样。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> Swift 的 `Dictionary` 类型被桥接到 `Foundation` 的 `NSDictionary` 类。
|
||||
>
|
||||
> 更多关于在 `Foundation` 和 `Cocoa` 中使用 `Dictionary` 类型的信息,参见 [*Using Swift with Cocoa and Obejective-C(Swift 4.1)*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 中[使用 Cocoa 数据类型](https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html#//apple_ref/doc/uid/TP40014216-CH6)部分。
|
||||
|
||||
### 字典类型简化语法 {#dictionary-type-shorthand-syntax}
|
||||
|
||||
Swift 的字典使用 `Dictionary<Key, Value>` 定义,其中 `Key` 是字典中键的数据类型,`Value` 是字典中对应于这些键所存储值的数据类型。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 一个字典的 `Key` 类型必须遵循 `Hashable` 协议,就像 `Set` 的值类型。
|
||||
|
||||
我们也可以用 `[Key: Value]` 这样简化的形式去创建一个字典类型。虽然这两种形式功能上相同,但是后者是首选,并且这本指导书涉及到字典类型时通篇采用后者。
|
||||
|
||||
### 创建一个空字典 {#creating-an-empty-dictionary}
|
||||
|
||||
我们可以像数组一样使用构造语法创建一个拥有确定类型的空字典:
|
||||
|
||||
```swift
|
||||
var namesOfIntegers = [Int: String]()
|
||||
// namesOfIntegers 是一个空的 [Int: String] 字典
|
||||
```
|
||||
|
||||
这个例子创建了一个 `[Int: String]` 类型的空字典来储存整数的英语命名。它的键是 `Int` 型,值是 `String` 型。
|
||||
|
||||
如果上下文已经提供了类型信息,我们可以使用空字典字面量来创建一个空字典,记作 `[:]`(中括号中放一个冒号):
|
||||
|
||||
```swift
|
||||
namesOfIntegers[16] = "sixteen"
|
||||
// namesOfIntegers 现在包含一个键值对
|
||||
namesOfIntegers = [:]
|
||||
// namesOfIntegers 又成为了一个 [Int: String] 类型的空字典
|
||||
```
|
||||
|
||||
### 用字典字面量创建字典 {#creating-a-dictionary-with-a-dictionary-literal}
|
||||
|
||||
我们可以使用*字典字面量*来构造字典,这和我们刚才介绍过的数组字面量拥有相似语法。字典字面量是一种将一个或多个键值对写作 `Dictionary` 集合的快捷途径。
|
||||
|
||||
一个键值对是一个 `key` 和一个 `value` 的结合体。在字典字面量中,每一个键值对的键和值都由冒号分割。这些键值对构成一个列表,其中这些键值对由方括号包含、由逗号分割:
|
||||
|
||||
```swift
|
||||
[key 1: value 1, key 2: value 2, key 3: value 3]
|
||||
```
|
||||
|
||||
下面的例子创建了一个存储国际机场名称的字典。在这个字典中键是三个字母的国际航空运输相关代码,值是机场名称:
|
||||
|
||||
```swift
|
||||
var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
|
||||
```
|
||||
|
||||
`airports` 字典被声明为一种 `[String: String]` 类型,这意味着这个字典的键和值都是 `String` 类型。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> `airports` 字典被声明为变量(用 `var` 关键字)而不是常量(`let` 关键字)因为后来更多的机场信息会被添加到这个示例字典中。
|
||||
|
||||
`airports` 字典使用字典字面量初始化,包含两个键值对。第一对的键是 `YYZ`,值是 `Toronto Pearson`。第二对的键是 `DUB`,值是 `Dublin`。
|
||||
|
||||
这个字典语句包含了两个 `String: String` 类型的键值对。它们对应 `airports` 变量声明的类型(一个只有 `String` 键和 `String` 值的字典)所以这个字典字面量的任务是构造拥有两个初始数据项的 `airport` 字典。
|
||||
|
||||
和数组一样,我们在用字典字面量构造字典时,如果它的键和值都有各自一致的类型,那么就不必写出字典的类型。
|
||||
`airports` 字典也可以用这种简短方式定义:
|
||||
|
||||
```swift
|
||||
var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
|
||||
```
|
||||
|
||||
因为这个语句中所有的键和值都各自拥有相同的数据类型,Swift 可以推断出 `Dictionary<String, String>` 是 `airports` 字典的正确类型。
|
||||
|
||||
### 访问和修改字典 {#accessing-and-modifying-a-dictionary}
|
||||
|
||||
我们可以通过字典的方法和属性来访问和修改字典,或者通过使用下标语法。
|
||||
|
||||
和数组一样,我们可以通过字典的只读属性 `count` 来获取某个字典的数据项数量:
|
||||
|
||||
```swift
|
||||
print("The dictionary of airports contains \(airports.count) items.")
|
||||
// 打印“The dictionary of airports contains 2 items.”(这个字典有两个数据项)
|
||||
```
|
||||
|
||||
使用布尔属性 `isEmpty` 作为一个缩写形式去检查 `count` 属性是否为 `0`:
|
||||
|
||||
```swift
|
||||
if airports.isEmpty {
|
||||
print("The airports dictionary is empty.")
|
||||
} else {
|
||||
print("The airports dictionary is not empty.")
|
||||
}
|
||||
// 打印“The airports dictionary is not empty.”
|
||||
```
|
||||
|
||||
我们也可以在字典中使用下标语法来添加新的数据项。可以使用一个恰当类型的键作为下标索引,并且分配恰当类型的新值:
|
||||
|
||||
```swift
|
||||
airports["LHR"] = "London"
|
||||
// airports 字典现在有三个数据项
|
||||
```
|
||||
|
||||
我们也可以使用下标语法来改变特定键对应的值:
|
||||
|
||||
```swift
|
||||
airports["LHR"] = "London Heathrow"
|
||||
// “LHR”对应的值被改为“London Heathrow”
|
||||
```
|
||||
|
||||
作为另一种下标方法,字典的 `updateValue(_:forKey:)` 方法可以设置或者更新特定键对应的值。就像上面所示的下标示例,`updateValue(_:forKey:)` 方法在这个键不存在对应值的时候会设置新值或者在存在时更新已存在的值。和上面的下标方法不同的,`updateValue(_:forKey:)` 这个方法返回更新值之前的原值。这样使得我们可以检查更新是否成功。
|
||||
|
||||
`updateValue(_:forKey:)` 方法会返回对应值的类型的可选值。举例来说:对于存储 `String` 值的字典,这个函数会返回一个 `String?` 或者“可选 `String`”类型的值。
|
||||
|
||||
如果有值存在于更新前,则这个可选值包含了旧值,否则它将会是 `nil`。
|
||||
|
||||
```swift
|
||||
if let oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") {
|
||||
print("The old value for DUB was \(oldValue).")
|
||||
}
|
||||
// 输出“The old value for DUB was Dublin.”
|
||||
```
|
||||
|
||||
我们也可以使用下标语法来在字典中检索特定键对应的值。因为有可能请求的键没有对应的值存在,字典的下标访问会返回对应值的类型的可选值。如果这个字典包含请求键所对应的值,下标会返回一个包含这个存在值的可选值,否则将返回 `nil`:
|
||||
|
||||
```swift
|
||||
if let airportName = airports["DUB"] {
|
||||
print("The name of the airport is \(airportName).")
|
||||
} else {
|
||||
print("That airport is not in the airports dictionary.")
|
||||
}
|
||||
// 打印“The name of the airport is Dublin Airport.”
|
||||
```
|
||||
|
||||
我们还可以使用下标语法来通过给某个键的对应值赋值为 `nil` 来从字典里移除一个键值对:
|
||||
|
||||
```swift
|
||||
airports["APL"] = "Apple Internation"
|
||||
// “Apple Internation”不是真的 APL 机场,删除它
|
||||
airports["APL"] = nil
|
||||
// APL 现在被移除了
|
||||
```
|
||||
|
||||
此外,`removeValue(forKey:)` 方法也可以用来在字典中移除键值对。这个方法在键值对存在的情况下会移除该键值对并且返回被移除的值或者在没有值的情况下返回 `nil`:
|
||||
|
||||
```swift
|
||||
if let removedValue = airports.removeValue(forKey: "DUB") {
|
||||
print("The removed airport's name is \(removedValue).")
|
||||
} else {
|
||||
print("The airports dictionary does not contain a value for DUB.")
|
||||
}
|
||||
// 打印“The removed airport's name is Dublin Airport.”
|
||||
```
|
||||
|
||||
### 字典遍历 {#iterating-over-a-dictionary}
|
||||
|
||||
我们可以使用 `for-in` 循环来遍历某个字典中的键值对。每一个字典中的数据项都以 `(key, value)` 元组形式返回,并且我们可以使用临时常量或者变量来分解这些元组:
|
||||
|
||||
```swift
|
||||
for (airportCode, airportName) in airports {
|
||||
print("\(airportCode): \(airportName)")
|
||||
}
|
||||
// YYZ: Toronto Pearson
|
||||
// LHR: London Heathrow
|
||||
```
|
||||
|
||||
更多关于 `for-in` 循环的信息,参见[For 循环](./05_Control_Flow.md#for_loops)。
|
||||
|
||||
通过访问 `keys` 或者 `values` 属性,我们也可以遍历字典的键或者值:
|
||||
|
||||
```swift
|
||||
for airportCode in airports.keys {
|
||||
print("Airport code: \(airportCode)")
|
||||
}
|
||||
// Airport code: YYZ
|
||||
// Airport code: LHR
|
||||
|
||||
for airportName in airports.values {
|
||||
print("Airport name: \(airportName)")
|
||||
}
|
||||
// Airport name: Toronto Pearson
|
||||
// Airport name: London Heathrow
|
||||
```
|
||||
|
||||
如果我们只是需要使用某个字典的键集合或者值集合来作为某个接受 `Array` 实例的 API 的参数,可以直接使用 `keys` 或者 `values` 属性构造一个新数组:
|
||||
|
||||
```swift
|
||||
let airportCodes = [String](airports.keys)
|
||||
// airportCodes 是 ["YYZ", "LHR"]
|
||||
|
||||
let airportNames = [String](airports.values)
|
||||
// airportNames 是 ["Toronto Pearson", "London Heathrow"]
|
||||
```
|
||||
|
||||
Swift 的字典类型是无序集合类型。为了以特定的顺序遍历字典的键或值,可以对字典的 `keys` 或 `values` 属性使用 `sorted()` 方法。
|
||||
@ -1,630 +0,0 @@
|
||||
# 词法结构(Lexical Structure)
|
||||
|
||||
Swift 的*“词法结构(lexical structure)”* 描述了能构成该语言中有效符号(token)的字符序列。这些合法符号组成了语言中最底层的构建基块,并在之后的章节中用于描述语言的其他部分。一个合法符号由一个标识符(identifier)、关键字(keyword)、标点符号(punctuation)、字面量(literal)或运算符(operator)组成。
|
||||
|
||||
通常情况下,通过考虑输入文本当中可能的最长子串,并且在随后将介绍的语法约束之下,根据随后将介绍的语法约束生成的,根据 Swift 源文件当中的字符来生成相应的“符号”。这种方法称为*“最长匹配(longest match)”*,或者*“最大适合(maximal munch)”*。
|
||||
|
||||
## 空白与注释 {#whitespace}
|
||||
|
||||
空白(whitespace)有两个用途:分隔源文件中的符号以及帮助区分运算符属于前缀还是后缀(参见 [运算符](#operators)),在其他情况下空白则会被忽略。以下的字符会被当作空白:空格(U+0020)、换行符(U+000A)、回车符(U+000D)、水平制表符(U+0009)、垂直制表符(U+000B)、换页符(U+000C)以及空字符(U+0000)。
|
||||
|
||||
注释被编译器当作空白处理。单行注释由 `//` 开始直至遇到换行符(U+000A)或者回车符(U+000D)。多行注释由 `/*` 开始,以 `*/` 结束。注释允许嵌套,但注释标记必须匹配。
|
||||
|
||||
> 空白语法
|
||||
>
|
||||
> *空白* → [*空白项*](#whitespace-item) [*空白*](#whitespace)<sub>可选</sub>
|
||||
>
|
||||
#### whitespace-item {#whitespace-item}
|
||||
>
|
||||
> *空白项* → [*断行符*](#line-break)
|
||||
>
|
||||
> *空白项* → [*注释*](#comment)
|
||||
>
|
||||
> *空白项* → [*多行注释*](#multiline-comment)
|
||||
>
|
||||
> *空白项* → U+0000,U+0009,U+000B,U+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 标量值, 除了 `/*` 或者 `*/`
|
||||
|
||||
## 标识符 {#identifiers}
|
||||
|
||||
*标识符(identifier)* 可以由以下的字符开始:大写或小写的字母 `A` 到 `Z`、下划线(`_`)、基本多文种平面(Basic Multilingual Plane)中非字符数字组合的 Unicode 字符以及基本多文种平面以外的非个人专用区字符。在首字符之后,允许使用数字和组合 Unicode 字符。
|
||||
|
||||
使用保留字作为标识符,需要在其前后增加反引号(`` ` ``)。例如,`class` 不是合法的标识符,但可以使用 `` `class` ``。反引号不属于标识符的一部分,`` `x` `` 和 `x` 表示同一标识符。
|
||||
|
||||
闭包中如果没有明确指定参数名称,参数将被隐式命名为 `$0`、`$1`、`$2` 等等。这些命名在闭包作用域范围内是合法的标识符。
|
||||
|
||||
> 标识符语法
|
||||
>
|
||||
> *标识符* → [*头部标识符*](#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+00A8,U+00AA,U+00AD,U+00AF,U+00B2–U+00B5,或者 U+00B7–U+00BA
|
||||
>
|
||||
> *头部标识符* → U+00BC–U+00BE,U+00C0–U+00D6,U+00D8–U+00F6,或者 U+00F8–U+00FF
|
||||
>
|
||||
> *头部标识符* → U+0100–U+02FF,U+0370–U+167F,U+1681–U+180D,或者 U+180F–U+1DBF
|
||||
>
|
||||
> *头部标识符* → U+1E00–U+1FFF
|
||||
>
|
||||
> *头部标识符* → U+200B–U+200D,U+202A–U+202E,U+203F–U+2040,U+2054,或者 U+2060–U+206F
|
||||
>
|
||||
> *头部标识符* → U+2070–U+20CF,U+2100–U+218F,U+2460–U+24FF,或者 U+2776–U+2793
|
||||
>
|
||||
> *头部标识符* → U+2C00–U+2DFF 或者 U+2E80–U+2FFF
|
||||
>
|
||||
> *头部标识符* → U+3004–U+3007,U+3021–U+302F,U+3031–U+303F,或者 U+3040–U+D7FF
|
||||
>
|
||||
> *头部标识符* → U+F900–U+FD3D,U+FD40–U+FDCF,U+FDF0–U+FE1F,或者 U+FE30–U+FE44
|
||||
>
|
||||
> *头部标识符* → U+FE47–U+FFFD
|
||||
>
|
||||
> *头部标识符* → U+10000–U+1FFFD,U+20000–U+2FFFD,U+30000–U+3FFFD,或者 U+40000–U+4FFFD
|
||||
>
|
||||
> *头部标识符* → U+50000–U+5FFFD,U+60000–U+6FFFD,U+70000–U+7FFFD,或者 U+80000–U+8FFFD
|
||||
>
|
||||
> *头部标识符* → U+90000–U+9FFFD,U+A0000–U+AFFFD,U+B0000–U+BFFFD,或者 U+C0000–U+CFFFD
|
||||
>
|
||||
> *头部标识符* → U+D0000–U+DFFFD 或者 U+E0000–U+EFFFD
|
||||
>
|
||||
> *标识符字符* → 数值 0 - 9
|
||||
>
|
||||
>
|
||||
#### identifier-character {#identifier-character}
|
||||
>
|
||||
> *标识符字符* → U+0300–U+036F,U+1DC0–U+1DFF,U+20D0–U+20FF,或者 U+FE20–U+FE2F
|
||||
>
|
||||
> *标识符字符* → [*头部标识符*](#identifier-head)
|
||||
>
|
||||
>
|
||||
#### identifier-characters {#identifier-characters}
|
||||
>
|
||||
> *标识符字符组* → [*标识符字符*](#identifier-character) [*标识符字符组*](#identifier-characters)<sub>可选</sub>
|
||||
>
|
||||
>
|
||||
#### implicit-parameter-name {#implicit-parameter-name}
|
||||
>
|
||||
> *隐式参数名* → **$** [*十进制数字列表*](#decimal-digit)
|
||||
|
||||
## 关键字和标点符号 {#keywords-and-punctuation}
|
||||
|
||||
下面这些被保留的关键字不允许用作标识符,除非使用反引号转义,具体描述请参考 [标识符](#identifiers)。除了 `inout`、`var` 以及 `let` 之外的关键字可以用作某个函数声明或者函数调用当中的外部参数名,无需添加反引号转义。当一个成员与一个关键字具有相同的名称时,不需要使用反引号来转义对该成员的引用,除非在引用该成员和使用该关键字之间存在歧义 - 例如,`self`,`Type` 和 `Protocol` 在显式的成员表达式中具有特殊的含义,因此它们必须在该上下文中使用反引号进行转义。
|
||||
|
||||
* 用在声明中的关键字: `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`、`Any`、`catch`、`false`、`is`、`nil`、`rethrows`、`super`、`self`、`Self`、`throw`、`throws`、`true` 以及 `try `。
|
||||
* 用在模式中的关键字:`_`。
|
||||
* 以井字号(`#`)开头的关键字:`#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`。这些关键字在特定上下文之外可以被用做标识符。
|
||||
|
||||
以下符号被当作保留符号,不能用于自定义运算符: `(`、`)`、`{`、`}`、`[`、`]`、`.`、`,`、`:`、`;`、`=`、`@`、`#`、`&`(作为前缀运算符)、`->`、`` ` ``、`?`、`!`(作为后缀运算符)。
|
||||
|
||||
## 字面量 {#literal}
|
||||
|
||||
*字面量(literal)* 用来表示源码中某种特定类型的值,比如一个数字或字符串。
|
||||
|
||||
下面是字面量的一些示例:
|
||||
|
||||
```swift
|
||||
42 // 整数字面量
|
||||
3.14159 // 浮点数字面量
|
||||
"Hello, world!" // 字符串字面量
|
||||
true // 布尔值字面量
|
||||
```
|
||||
|
||||
字面量本身并不包含类型信息。事实上,一个字面量会被解析为拥有无限的精度,然后 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` 的类型标注。
|
||||
|
||||
> 字面量语法
|
||||
>
|
||||
> *字面量* → [*数值字面量*](#integer-literal) | [*字符串字面量*](#string-literal) | [*布尔值字面量*](#integer-literal) | [*nil 字面量*](#integer-literal)
|
||||
>
|
||||
> *数值字面量* → **-**<sub>可选</sub> [*整数字面量*](#integer-literal) | **-**<sub>可选</sub> [*浮点数字面量*](#floating-point-literal)
|
||||
>
|
||||
> *布尔值字面量* → **true** | **false**
|
||||
>
|
||||
> *nil 字面量* → **nil**
|
||||
|
||||
|
||||
### 整数字面量{#integer-literal}
|
||||
|
||||
*整数字面量(Integer Literals)* 表示未指定精度整数的值。整数字面量默认用十进制表示,可以加前缀来指定其他的进制。二进制字面量加 `0b`,八进制字面量加 `0o`,十六进制字面量加 `0x`。
|
||||
|
||||
十进制字面量包含数字 `0` 至 `9`。二进制字面量只包含 `0` 或 `1`,八进制字面量包含数字 `0` 至 `7`,十六进制字面量包含数字 `0` 至 `9` 以及字母 `A` 至 `F`(大小写均可)。
|
||||
|
||||
负整数的字面量在整数字面量前加负号 `-`,比如 `-42`。
|
||||
|
||||
整型字面面可以使用下划线(`_`)来增加数字的可读性,下划线会被系统忽略,因此不会影响字面量的值。同样地,也可以在数字前加 `0`,这同样也会被系统所忽略,并不会影响字面量的值。
|
||||
|
||||
除非特别指定,整数字面量的默认推导类型为 Swift 标准库类型中的 `Int`。Swift 标准库还定义了其他不同长度以及是否带符号的整数类型,请参考 [整数](../chapter2/01_The_Basics.md#integers)。
|
||||
|
||||
> 整数字面量语法
|
||||
>
|
||||
>
|
||||
#### 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>
|
||||
|
||||
### 浮点数字面量{#floating-point-literal}
|
||||
|
||||
*浮点数字面量(Floating-point literals)* 表示未指定精度浮点数的值。
|
||||
|
||||
浮点数字面量默认用十进制表示(无前缀),也可以用十六进制表示(加前缀 `0x`)。
|
||||
|
||||
十进制浮点数字面量由十进制数字串后跟小数部分或指数部分(或两者皆有)组成。十进制小数部分由小数点(`.`)后跟十进制数字串组成。指数部分由大写或小写字母 `e` 为前缀后跟十进制数字串组成,这串数字表示 `e` 之前的数量乘以 10 的几次方。例如:`1.25e2` 表示 1.25 x 10²,也就是 `125.0`;同样,`1.25e-2` 表示 1.25 x 10¯²,也就是 `0.0125`。
|
||||
|
||||
十六进制浮点数字面量由前缀 `0x` 后跟可选的十六进制小数部分以及十六进制指数部分组成。十六进制小数部分由小数点后跟十六进制数字串组成。指数部分由大写或小写字母 `p` 为前缀后跟十进制数字串组成,这串数字表示 `p` 之前的数量乘以 2 的几次方。例如:`0xFp2` 表示 15 x 2²,也就是 `60`;同样,`0xFp-2` 表示 15 x 2¯²,也就是 `3.75`。
|
||||
|
||||
负数的浮点数字面量由负号(`-`)和浮点数字面量组成,例如 `-42.5`。
|
||||
|
||||
浮点数字面量允许使用下划线(`_`)来增强数字的可读性,下划线会被系统忽略,因此不会影响字面量的值。同样地,也可以在数字前加 `0`,并不会影响字面量的值。
|
||||
|
||||
除非特别指定,浮点数字面量的默认推导类型为 Swift 标准库类型中的 `Double`,表示 64 位浮点数。Swift 标准库也定义了 `Float` 类型,表示 32 位浮点数。
|
||||
|
||||
> 浮点数字面量语法
|
||||
>
|
||||
>
|
||||
#### 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}
|
||||
>
|
||||
> *正负号* → **+** | **-**
|
||||
|
||||
### 字符串字面量 {#string-literal}
|
||||
|
||||
字符串字面量是被引号包括的一串字符组成。 单行字符串字面量被包在双引号中的一串字符组成,形式如下:
|
||||
|
||||
> "`字符`"
|
||||
|
||||
字符串字面量中不能包含未转义的双引号(`"`)、未转义的反斜线(`\`)、回车符、换行符。
|
||||
|
||||
多行字符串字面量被包在三个双引号中的一串字符组成,形式如下:
|
||||
> """
|
||||
> `字符`
|
||||
> """
|
||||
|
||||
与单行字符串字面量不同的是,多行字符串字面量可以包含不转义的双引号("),回车以及换行。它不能包含三个未转义的连续双引号。
|
||||
|
||||
""" 之后的回车或者换行开始多行字符串字面量,不是字符串的一部分。 """ 之前回车或者换行结束字面量,也不是字符串的一部分。要让多行字符串字面量的开始或结束带有换行,就在第一行或者最后一行写一个空行。
|
||||
|
||||
多行字符串字面量可以使用任何空格或制表符组合进行缩进;这些缩进不会包含在字符串中。 """ 的结束符号决定了缩进:字面量中的任何一个非空行必须起始于多行字符串字面量结束符号的前面;空格和制表符不会被转换。你可以包在缩进后含额外的空格和制表符;这些空格和制表符会在字符串中出现。
|
||||
|
||||
多行字符串字面量中的一行结束使用规范化的换行符号。尽管你的源代码混用了回车和换行符,字符串中所有的行结束都必须一样.
|
||||
|
||||
在多行字符串字面量里, 在行末用反斜线(`\`)可以省略字符串行间中断。 反斜线之间的空白和行间中断也可以省略。 你可以在你的代码里用这种语法硬包裹多行字符串字面量,不需要改变产生的字符串的值。
|
||||
|
||||
可以在字符串字面量中使用的转义特殊符号如下:
|
||||
|
||||
* 空字符 `\0`
|
||||
* 反斜线 `\\`
|
||||
* 水平制表符 `\t`
|
||||
* 换行符 `\n`
|
||||
* 回车符 `\r`
|
||||
* 双引号 `\"`
|
||||
* 单引号 `\'`
|
||||
* Unicode 标量 `\u{`n`}`,n 为一到八位的十六进制数字
|
||||
|
||||
字符串字面量允许在反斜杠(`\`)后的括号 `()` 中插入表达式的值。插入表达式可以包含字符串字面量,但不能包含未转义的反斜线(`\`)、回车符以及换行符。
|
||||
|
||||
例如,以下所有字符串字面量的值都是相同的:
|
||||
|
||||
```swift
|
||||
"1 2 3"
|
||||
"1 2 \("3")"
|
||||
"1 2 \(3)"
|
||||
"1 2 \(1 + 2)"
|
||||
let x = 3; "1 2 \(x)"
|
||||
```
|
||||
|
||||
可以使用一对或多对扩展分隔符(#)包裹字符串进行分隔,被分隔的字符串的形式如下所示:
|
||||
|
||||
> \#"`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` 没有任何运行时的连接操作。
|
||||
|
||||
```swift
|
||||
let textA = "Hello " + "world"
|
||||
let textB = "Hello world"
|
||||
```
|
||||
|
||||
> 字符串字面量语法
|
||||
>
|
||||
> *字符串字面量* → [*静态字符串字面量*](#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 字符
|
||||
>
|
||||
>
|
||||
#### 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)
|
||||
|
||||
|
||||
## 运算符 {#operator}
|
||||
|
||||
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`)。
|
||||
|
||||
鉴于这些规则,运算符前的字符 `(`、`[` 和 `{`,运算符后的字符 `)`、`]` 和 `}`,以及字符 `,`、`;` 和 `:` 都被视为空白。
|
||||
|
||||
以上规则需注意一点,如果预定义运算符 `!` 或 `?` 左侧没有空白,则不管右侧是否有空白都将被看作后缀运算符。如果将 `?` 用作可选链式调用运算符,左侧必须无空白。如果用于条件运算符 `? :`,必须两侧都有空白。
|
||||
|
||||
在某些特定的设计中 ,以 `<` 或 `>` 开头的运算符会被分离成两个或多个符号,剩余部分可能会以同样的方式被再次分离。因此,在 `Dictionary<String, Array<Int>>` 中没有必要添加空白来消除闭合字符 `>` 的歧义。在这个例子中, 闭合字符 `>` 不会被视为单独的符号,因而不会被错误解析为 `>>` 运算符。
|
||||
|
||||
要学习如何自定义运算符,请参考 [自定义运算符](../chapter2/26_Advanced_Operators.md#custom_operators) 和 [运算符声明](./06_Declarations.md#operator_declaration)。要学习如何重载运算符,请参考 [运算符函数](../chapter2/26_Advanced_Operators.md#operator_functions)。
|
||||
|
||||
> 运算符语法
|
||||
>
|
||||
> *运算符* → [*头部运算符*](#operator-head) [*运算符字符组*](#operator-characters)<sub>可选</sub>
|
||||
>
|
||||
> *运算符* → [*头部点运算符*](#dot-operator-head) [*点运算符字符组*](#dot-operator-characters)
|
||||
>
|
||||
>
|
||||
#### operator-head {#operator-head}
|
||||
>
|
||||
> *头部运算符* → **/** | **=** | **-** | **+** | **!** | __*__ | **%** | **<** | **>** | **&** | **|** | **^** | **~** | **?**
|
||||
>
|
||||
> *头部运算符* → U+00A1–U+00A7
|
||||
>
|
||||
> *头部运算符* → U+00A9 或 U+00AB
|
||||
>
|
||||
> *头部运算符* → U+00AC 或 U+00AE
|
||||
>
|
||||
> *头部运算符* → U+00B0–U+00B1,U+00B6,U+00BB,U+00BF,U+00D7,或 U+00F7
|
||||
>
|
||||
> *头部运算符* → U+2016–U+2017 或 U+2020–U+2027
|
||||
>
|
||||
> *头部运算符* → U+2030–U+203E
|
||||
>
|
||||
> *头部运算符* → U+2041–U+2053
|
||||
>
|
||||
> *头部运算符* → U+2055–U+205E
|
||||
>
|
||||
> *头部运算符* → U+2190–U+23FF
|
||||
>
|
||||
> *头部运算符* → U+2500–U+2775
|
||||
>
|
||||
> *头部运算符* → U+2794–U+2BFF
|
||||
>
|
||||
> *头部运算符* → U+2E00–U+2E7F
|
||||
>
|
||||
> *头部运算符* → U+3001–U+3003
|
||||
>
|
||||
> *头部运算符* → U+3008–U+3030
|
||||
>
|
||||
>
|
||||
#### operator-character {#operator-character}
|
||||
>
|
||||
> *运算符字符* → [*头部运算符*](#operator-head)
|
||||
>
|
||||
> *运算符字符* → U+0300–U+036F
|
||||
>
|
||||
> *运算符字符* → U+1DC0–U+1DFF
|
||||
>
|
||||
> *运算符字符* → U+20D0–U+20FF
|
||||
>
|
||||
> *运算符字符* → U+FE00–U+FE0F
|
||||
>
|
||||
> *运算符字符* → U+FE20–U+FE2F
|
||||
>
|
||||
> *运算符字符* → U+E0100–U+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>
|
||||
>
|
||||
> *二元运算符* → [*运算符*](#operator)
|
||||
>
|
||||
> *前缀运算符* → [*运算符*](#operator)
|
||||
>
|
||||
> *后缀运算符* → [*运算符*](#operator)
|
||||
@ -1,399 +0,0 @@
|
||||
# 特性(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:)` 下标的调用,传递包含成员名称字符串的参数。下标的参数只需遵循 `ExpressibleByStringLiteral` 协议,返回值类型可以为任意类型。在大多数情况下,下标的参数是一个 `String` 值。例如:
|
||||
|
||||
```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”
|
||||
```
|
||||
|
||||
### `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`、`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`,`IBOutlet`,`IBDesignable`,以及 `IBInspectable` 。这些特性与 Objective-C 中对应的特性在概念上是相同的。
|
||||
|
||||
`IBOutlet` 和 `IBInspectable` 用于修饰一个类的属性声明,`IBAction` 特性用于修饰一个类的方法声明,`IBDesignable` 用于修饰类的声明。
|
||||
|
||||
应用 `IBAction`、`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> **}**
|
||||
>
|
||||
> *均衡令牌* → 任意标识符,关键字,字面量或运算符
|
||||
>
|
||||
> *均衡令牌* → 任意标点除了 **(**,**)**,**[**,**]**,**{**,或 **}**
|
||||
>
|
||||
@ -12,12 +12,20 @@ Swift 官方文档中文翻译由 [numbbbbb](https://github.com/numbbbbb) 发起
|
||||
- [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)
|
||||
- [Licardo](https://github.com/L1cardo)
|
||||
- [Khala-wan](https://github.com/Khala-wan)
|
||||
- [Nemocdz](https://github.com/Nemocdz)
|
||||
- [numbbbbb](https://github.com/numbbbbb)
|
||||
- [pmst](https://github.com/colourful987)
|
||||
- [Phenmod](https://github.com/Phenmod)
|
||||
- [RickeyBoy](https://github.com/RickeyBoy)
|
||||
- [SunsetWan](https://github.com/SunsetWan)
|
||||
- [WAMaker](https://github.com/WAMaker)
|
||||
- [Yousanflics](https://github.com/Yousanflics)
|
||||
- [YiYiZheng](https://github.com/YiYiZheng)
|
||||
- [Yousanflics](https://github.com/Yousanflics)
|
||||
- [CyberHex](https://cyberhex.me/)
|
||||
|
||||
## Swift 4.x 主要贡献者
|
||||
|
||||
@ -143,4 +151,3 @@ Swift 官方文档中文翻译由 [numbbbbb](https://github.com/numbbbbb) 发起
|
||||
- [zqp](https://github.com/zqp)
|
||||
- [成都老码团队翻译组-Arya](http://weibo.com/littlekok/)
|
||||
- [成都老码团队翻译组-Oberyn](http://weibo.com/u/5241713117)
|
||||
|
||||
|
||||
BIN
source/cover.jpg
BIN
source/cover.jpg
Binary file not shown.
|
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 576 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 20 KiB |
@ -11,7 +11,7 @@
|
||||
|
||||
> 小明说哇靠学姐你还会妖法......
|
||||
|
||||
Swift 语言从 Xcode 6 beta 5版本起,加入了对权限控制(Access Control)的支持。其实权限控制和小明的物品一样,你可以设定水壶是只有自己能用,还是只有宿舍里的人能用,还是全校都可以用。
|
||||
Swift 语言从 Xcode 6 beta 5 版本起,加入了对权限控制(Access Control)的支持。其实权限控制和小明的物品一样,你可以设定水壶是只有自己能用,还是只有宿舍里的人能用,还是全校都可以用。
|
||||
|
||||
从此以后,你可以好像神盾局局长一样,完全掌控自己的代码块的”保密级别“,哪些是只能在本文件引用,哪些能用在整个项目里,你还可以发挥大爱精神,把它开源成只要导入你的框架,大家都可以使用的 API。
|
||||
|
||||
Reference in New Issue
Block a user