Compare commits
620 Commits
v2.2
...
Ch-Lexical
| Author | SHA1 | Date | |
|---|---|---|---|
| e82d019942 | |||
| 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 | |||
| 7fcb93a763 | |||
| d885f9e1fd | |||
| 7fcd04ec5b | |||
| 09eb20007b | |||
| 72c51e42bb | |||
| bc806e9b20 | |||
| 5c2873d295 | |||
| 2760b1993e | |||
| f6e0bfa622 | |||
| 3201f23a17 | |||
| 5d3bf7bbcf | |||
| 61e3a3243f | |||
| 099837dbc6 | |||
| 8c866e4435 | |||
| 75e3d9d98d | |||
| 37bcb2969a | |||
| 307ddedd43 | |||
| 4d6ef27d54 | |||
| 34990cf782 | |||
| 2b4fddc049 | |||
| cd435f5a28 | |||
| 10bc54af00 | |||
| 4ef5e74cbb | |||
| 212b685b24 | |||
| 3e2520a893 | |||
| 44c325d3ce | |||
| 9ee2363352 | |||
| 835959d62b | |||
| c23b0f796d | |||
| aaaf3abb2f | |||
| c22767f242 | |||
| 55acc0b7f7 | |||
| 90d6f1012b | |||
| 4005616424 | |||
| 453ac5e365 | |||
| f1151f62aa | |||
| 8e2641a0bf | |||
| c02ecdcc09 | |||
| 93cffd5ff1 | |||
| c634f574a1 | |||
| 7ced6d2dd7 | |||
| bf38ce27c6 | |||
| fc716d45e8 | |||
| 5968f31d1f | |||
| 91afca8367 | |||
| 7b603a307f | |||
| d29d719469 | |||
| 4cb21c38f0 | |||
| 3acdfbca8c | |||
| 964478ed72 | |||
| fc86ccb932 | |||
| cd06ac6f52 | |||
| 5fca0fa704 | |||
| f65cd95306 | |||
| 1d01a9900b | |||
| 810eba1150 | |||
| 7eb0b94d85 | |||
| 6f99dca741 | |||
| adc4897eff | |||
| c27d3a44cd | |||
| b9d9e62555 | |||
| 7b26ec50c3 | |||
| 7032b5c6fa | |||
| b46d6e1337 | |||
| 6a881a91db | |||
| 55bd47a9b1 | |||
| 33bddddebb | |||
| 64050823a2 | |||
| 899f708be2 | |||
| 582d450a75 | |||
| 125fdaf25f | |||
| afc5131f59 | |||
| a4c5b36ff1 | |||
| a93c26c7a4 | |||
| 0d9f9f64c2 | |||
| b6a56e5ebc | |||
| 0a30944f53 | |||
| 096d239c83 | |||
| 3857433e94 | |||
| 4a965cdb05 | |||
| f44291ad8e | |||
| 1abd1157fd | |||
| de559ce2d8 | |||
| 4ac5ae6485 | |||
| 8b9f8ba4cb | |||
| daeda4aab1 | |||
| 9b43c08e47 | |||
| 719d83d33b | |||
| 6ccc8227ef | |||
| fead955824 | |||
| 10395aaee1 | |||
| 3224ccac8f | |||
| 8c267a3de8 | |||
| 4c3ad3d2f8 | |||
| 1343749921 | |||
| 1493185ba4 | |||
| 85b457b8ca | |||
| 3529a8c94b | |||
| 52a95688ad | |||
| 60e700582a | |||
| 4081d8d286 | |||
| ff3880f81c | |||
| 740196487e | |||
| 7dee72fe3d | |||
| 1ec921bb88 | |||
| 3acf9a1bc4 | |||
| a6a14ebcbe | |||
| a8d4536826 | |||
| a49587cd09 | |||
| ddda95cc6b | |||
| 75495c45c8 | |||
| a76cbc957b | |||
| d068d8f1df | |||
| 5adeb1c9cc | |||
| 18d2cc447c | |||
| 9f027f7614 | |||
| a175a2cae8 | |||
| 72e24680a0 | |||
| 9f412c8526 | |||
| 2c2097e940 | |||
| 2f66d833d7 | |||
| 6c3269d13f | |||
| d1ddc1a8ec | |||
| 51f79344ee | |||
| 73d1b558f3 | |||
| 18d24826d2 | |||
| 884f50e5dc | |||
| 150626ec6b | |||
| 0cdb353cfc | |||
| d8dc27c992 | |||
| c1be0a2755 | |||
| 378e36152f | |||
| f27276223c | |||
| 1b7b7ed1ab | |||
| 8f612f188f | |||
| f0ef29ef58 | |||
| 611f78f28c | |||
| 76b78304bd | |||
| 8417b49bcd | |||
| dc10749c04 | |||
| f86e8daa25 | |||
| be8dae5181 | |||
| 143d28f759 | |||
| d2bbb6d6b7 | |||
| ba99f9d349 | |||
| 90c7949aa0 | |||
| 1b0dafc062 | |||
| 84da873846 | |||
| a078759330 | |||
| c08703f216 | |||
| ad1835712f | |||
| 0f428e93f6 | |||
| 0a734e697a | |||
| 3a73b1d92b | |||
| 6f199069a5 | |||
| 517a9fdb05 | |||
| ae89afc25d | |||
| d7655dbc29 | |||
| adfa42decf | |||
| f22f44cef7 | |||
| 06e5383739 | |||
| 514e7be186 | |||
| 4a8365d78c | |||
| 08a76e561f | |||
| a3f0cae500 | |||
| da6d08cd6e | |||
| c4e5f11769 | |||
| 78e2e93ae0 | |||
| ec82cbd6fe | |||
| 53ac4e8a87 | |||
| 73ea47e3eb | |||
| 067bc9634d | |||
| d59123919f | |||
| 34f8d0c6b9 | |||
| ad1d3944bc | |||
| 90f8d2cbbd | |||
| ceb6979c82 | |||
| 0cbec45c9f | |||
| d9617c415f | |||
| fd1aecdb89 | |||
| 210d43551f | |||
| 025d16c3ba | |||
| c78fa92988 | |||
| 495214b832 | |||
| 68ea29968a | |||
| 98805bb7c4 | |||
| 6db217d2c7 | |||
| 8a628beaa8 | |||
| 1342d15a16 | |||
| 7d5e310e86 | |||
| cd90e8a170 | |||
| 048631e6b8 | |||
| aef5000b0e | |||
| 6f4ff95741 | |||
| 12b4818fdb | |||
| 24a0c355ee | |||
| e5b7c926f3 | |||
| d222669668 | |||
| 00b6cfcb2c | |||
| f5bc40e272 | |||
| 0d8dc74a94 | |||
| f728ffeacd | |||
| bfd072a14d | |||
| da73d25416 | |||
| bc2c9aaef0 | |||
| f869dc842c | |||
| 70ebf4fe50 | |||
| 2a33ed0b68 | |||
| f8aaac1837 | |||
| 2ae623f0e0 | |||
| ba4905fc7c | |||
| 2b2a898640 | |||
| 01bf831dbb | |||
| 2dd31dffae | |||
| 7ae7164290 | |||
| a387c8de17 | |||
| a1b00d6121 | |||
| bc4f5b5c44 | |||
| 7861f9901a | |||
| a49e63768b | |||
| b33fd51696 | |||
| 1d2564dbd0 | |||
| 0c18c51c8a | |||
| 9c0a744c47 | |||
| 2db98b316e | |||
| b79439d416 | |||
| a104f980b4 | |||
| dde3afbd07 | |||
| 5aef50b6a1 | |||
| b91f75b529 | |||
| c04b28ba60 | |||
| fd88deac6e | |||
| 0c87be8409 | |||
| c74f8bc7ee | |||
| 544a1c697a | |||
| ee858e3bb0 | |||
| b2a0c3aa64 | |||
| db8f2d65a9 | |||
| d18f3d02e1 | |||
| eda2310aab | |||
| 3290b3460c | |||
| 5e14a60046 | |||
| 80e357c01c | |||
| ef7dc20a57 | |||
| 141ca2ee6e | |||
| d03d2dfc75 | |||
| e86602b95c | |||
| c678d198e4 | |||
| 59e630f19e | |||
| 33e4f02ee2 | |||
| 1fb769e4df | |||
| ea125ddb18 | |||
| de4a848d6f | |||
| ef577dbad4 | |||
| fc6dc8c973 | |||
| 85ee2bc5cf | |||
| 9b3a98b473 | |||
| 7d3aa4650d | |||
| 0b2daf4c77 | |||
| e130059ba0 | |||
| 882d38c792 | |||
| fe606ddc93 | |||
| 3112212509 | |||
| 91da2d6ebf | |||
| dc3b19aa6f | |||
| 4f24490fe4 | |||
| db9afa4d0b | |||
| 03d6026823 | |||
| 203e04a136 | |||
| e5799258ad | |||
| 0ef78137fe | |||
| 0d0d772723 | |||
| 5ac4199715 | |||
| 4c01c83f95 | |||
| 9869c0448b | |||
| f8201be713 | |||
| eedc29b425 | |||
| a296757ea8 | |||
| ce5501a4e8 | |||
| 786108db04 | |||
| 70d5c57c96 | |||
| 2da452f6bf | |||
| 98de761133 | |||
| 1d0c833c35 | |||
| c122ef5d7d | |||
| 869b339375 | |||
| e59b912d69 | |||
| 41a2f8b71f | |||
| fe0c1fc4aa | |||
| 2f77ef47f9 | |||
| b38540f643 | |||
| aef3d29f2c | |||
| 0a792fa7a6 | |||
| 990803d796 | |||
| 833c6078da | |||
| f472b7d84a | |||
| 2d50b83a34 | |||
| 31a43a71b8 | |||
| 9c27e01947 | |||
| fa3f0bc8ac | |||
| 50f692c277 | |||
| 1d00ea6156 | |||
| 9250e9132e | |||
| 1cd9134cbe | |||
| dcbc857436 | |||
| d5629a91e6 | |||
| 1ad8b08ec6 | |||
| ca165654c9 | |||
| 17757b41b4 | |||
| df86057648 | |||
| 24b39e0328 | |||
| a9d2f42e60 | |||
| 0a36aaf9c1 | |||
| 0ff5800a0f | |||
| c708b0e592 | |||
| 2cc398e0c0 | |||
| 2f2488877b | |||
| c4a033bbba | |||
| 2dd84524a7 | |||
| c966b595d9 | |||
| 94bdafd565 | |||
| 9c03037e3a | |||
| e71013d713 | |||
| ed9a67ba9d | |||
| 0d462b58e0 | |||
| aaf2e886e5 | |||
| 64dd00b6f6 | |||
| dc08ebf499 | |||
| 6a9b24881e | |||
| 498143e4f1 | |||
| 9573e34aa2 | |||
| 64530ef5d0 | |||
| e24c2eb213 | |||
| 5feabef88f | |||
| 4f4cc171e7 | |||
| 64a2be3b3a | |||
| 5739f5d957 | |||
| abe1c5d76e | |||
| 00688ac1e5 | |||
| 845075d5ba | |||
| e1e67c8efa | |||
| 66cc38231a | |||
| df2ea5ddc9 | |||
| 38a64d9f64 | |||
| efd6a1d9c3 | |||
| 1b772fb16e | |||
| 4edf9501b4 | |||
| 7c36900e89 | |||
| 3aa5844ff8 | |||
| 49cdfb7347 | |||
| f1b76b38ed | |||
| d46f2c2314 | |||
| 0e160b98c5 | |||
| f928b61d09 | |||
| 289d8aadbf | |||
| 9d4403eada | |||
| 20b60b4224 | |||
| 52151afb7f | |||
| e2a6b722d2 | |||
| 083aa8b78b | |||
| 1d46a3ec76 | |||
| 2baad57b65 | |||
| 26ad26472a | |||
| 990352db8e | |||
| 82d9cd0156 | |||
| b39b8a5222 | |||
| 443976cbbf | |||
| 63de595251 | |||
| c848062fa2 | |||
| 988c3e0368 | |||
| 728ae92a90 | |||
| f5a6be2ca3 | |||
| 3a1a54cce9 | |||
| ad5f1f66b4 | |||
| 7da13b9823 | |||
| 7cb0005c65 | |||
| a5f99e7160 | |||
| cdb3267dff | |||
| ba7e7d5b03 | |||
| dadae616fa | |||
| 70ff24980d | |||
| 019cb42f13 | |||
| 4976c25347 | |||
| 24488696b5 | |||
| b9d3f5ead6 | |||
| fc6f33567d | |||
| bb316a341f | |||
| 0bdade3c66 |
5
.gitbook.yaml
Normal file
5
.gitbook.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
root: ./source/
|
||||
|
||||
structure:
|
||||
readme: ../README.md
|
||||
summary: SUMMARY.md
|
||||
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']
|
||||
279
README.md
279
README.md
@ -3,209 +3,144 @@
|
||||
|
||||
中文版 Apple 官方 Swift 教程《The Swift Programming Language》
|
||||
|
||||
[英文原版](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/index.html#//apple_ref/doc/uid/TP40014097-CH3-ID0)
|
||||
|
||||
**如果想帮忙翻译或者校对,请加QQ群:480616051,谢谢!**
|
||||
[英文原版在线版](https://docs.swift.org/swift-book/)
|
||||
|
||||
# 在线阅读
|
||||
|
||||
使用 Gitbook 制作,可以直接[在线阅读](http://swiftguide.cn/)。
|
||||
使用 GitBook 制作,可以在 [GitBook](https://swiftgg.gitbook.io/swift/) 网站阅读。
|
||||
|
||||
# 当前阶段
|
||||
|
||||
已经更新到 Swift 2.2。
|
||||
- 更新到 Swift 5.2,2020-02-15
|
||||
- 更新到 Swift 5.1,2019-11-11
|
||||
- 更新到 Swift 5.0,2019-04-05
|
||||
- 更新到 Swift 4.2,2019-01-29
|
||||
- 更新到 Swift 4.1,2018-04-12,感谢 [@Mylittleswift](https://github.com/Mylittleswift)
|
||||
- 更新到 Swift 3.0,2016-09-23
|
||||
|
||||
# 2.1 & 2.2 译者记录
|
||||
|
||||
详见各章节开头位置。
|
||||
|
||||
# 2.0 译者记录
|
||||
|
||||
- About Swift ([xtymichael](https://github.com/xtymichael))
|
||||
- A Swift Tour([xtymichael](https://github.com/xtymichael))
|
||||
- The Basics([xtymichael](https://github.com/xtymichael))
|
||||
- Basic Operators([AlanMelody](https://github.com/AlanMelody))
|
||||
- Strings and Characters([DianQK](https://github.com/DianQK))
|
||||
- Collection Types([AlanMelody](https://github.com/AlanMelody))
|
||||
- Control Flow([AlanMelody](https://github.com/AlanMelody))
|
||||
- Functions([dreamkidd](https://github.com/dreamkidd))
|
||||
- Closures([100mango](https://github.com/100mango))
|
||||
- Enumerations([futantan](https://github.com/futantan))
|
||||
- Classes and Structures([SkyJean](https://github.com/SkyJean))
|
||||
- Properties([yangsiy](https://github.com/yangsiy))
|
||||
- Methods([DianQK](https://github.com/DianQK))
|
||||
- Subscripts([shanksyang](https://github.com/shanksyang))
|
||||
- Inheritance([shanksyang](https://github.com/shanksyang))
|
||||
- Initialization([chenmingbiao](https://github.com/chenmingbiao))
|
||||
- Deinitialization([chenmingbiao](https://github.com/chenmingbiao))
|
||||
- Automatic Reference Counting([Channe](https://github.com/Channe))
|
||||
- Optional Chaining([lyojo](https://github.com/lyojo))
|
||||
- Error Handling([lyojo](https://github.com/lyojo))
|
||||
- Type Casting([yangsiy](https://github.com/yangsiy))
|
||||
- Nested Types([SergioChan](https://github.com/SergioChan))
|
||||
- Extensions([shanksyang](https://github.com/shanksyang))
|
||||
- Protocols([futantan](https://github.com/futantan))
|
||||
- Generics([SergioChan](https://github.com/SergioChan))
|
||||
- Access Control([mmoaay](https://github.com/mmoaay))
|
||||
- Advanced Operators([buginux](https://github.com/buginux))
|
||||
- About the Language Reference([KYawn](https://github.com/KYawn))
|
||||
- Lexical Structure([buginux](https://github.com/buginux))
|
||||
- Types([EudeMorgen](https://github.com/EudeMorgen))
|
||||
- Expressions([EudeMorgen](https://github.com/EudeMorgen))
|
||||
- Statements([littledogboy](https://github.com/littledogboy))
|
||||
- Declarations([Lenhoon](https://github.com/Lenhoon))
|
||||
- Attributes([KYawn](https://github.com/KYawn))
|
||||
- Patterns([ray16897188](https://github.com/ray16897188))
|
||||
- Generic Parameters and Arguments([wardenNScaiyi](https://github.com/wardenNScaiyi))
|
||||
- Summary of the Grammar([miaosiqi](https://github.com/miaosiqi))
|
||||
|
||||
# 1.0 译者记录
|
||||
|
||||
* 欢迎使用 Swift
|
||||
* 关于 Swift ([numbbbbb])
|
||||
* Swift 初见 ([numbbbbb])
|
||||
* Swift 教程
|
||||
* 基础部分 ([numbbbbb], [lyuka], [JaySurplus])
|
||||
* 基本操作符 ([xielingwang])
|
||||
* 字符串和字符 ([wh1100717])
|
||||
* 集合类型 ([zqp])
|
||||
* 控制流 ([vclwei], [coverxit], [NicePiao])
|
||||
* 函数 ([honghaoz])
|
||||
* 闭包 ([wh1100717])
|
||||
* 枚举 ([yankuangshi])
|
||||
* 类和结构体 ([JaySurplus])
|
||||
* 属性 ([shinyzhu])
|
||||
* 方法 ([pp-prog])
|
||||
* 下标 ([siemenliu])
|
||||
* 继承 ([Hawstein])
|
||||
* 构造过程 ([lifedim])
|
||||
* 析构过程 ([bruce0505])
|
||||
* 自动引用计数 ([TimothyYe])
|
||||
* 可选链 ([Jasonbroker])
|
||||
* 类型检查 ([xiehurricane])
|
||||
* 嵌套类型 ([Lin-H])
|
||||
* 扩展 ([lyuka])
|
||||
* 协议 ([geek5nan])
|
||||
* 泛型 ([takalard])
|
||||
* 高级操作符 ([xielingwang])
|
||||
* 语言参考
|
||||
* 关于语言参考 ([dabing1022])
|
||||
* 词法结构 ([superkam])
|
||||
* 类型 ([lyuka])
|
||||
* 表达式 ([sg552] )
|
||||
* 语句 ([coverxit])
|
||||
* 声明 ([marsprince])
|
||||
* 特性 ([Hawstein])
|
||||
* 模式 ([honghaoz])
|
||||
* 泛型参数 ([fd5788])
|
||||
* 语法总结 ([StanZhai])
|
||||
|
||||
# 贡献力量
|
||||
|
||||
如果想做出贡献的话,你可以:
|
||||
|
||||
- 参与翻译
|
||||
- 帮忙校对,挑错别字、病句等等
|
||||
- 提出修改建议
|
||||
- 提出术语翻译建议
|
||||
|
||||
# 翻译建议
|
||||
|
||||
如果你愿意一起校对的话,请仔细阅读:
|
||||
如果你有兴趣参与项目,请仔细阅读说明:
|
||||
|
||||
- 使用markdown进行翻译,文件名必须使用英文,因为中文的话gitbook编译的时候会出问题
|
||||
- 翻译后的文档请放到source文件夹下的对应章节中,然后pull request即可,我会用gitbook编译成网页
|
||||
- 工作分支为gh-pages,用于GitHub的pages服务
|
||||
- fork过去之后新建一个分支进行翻译,完成后pull request这个分支,没问题的话我会合并到gh-pages分支中
|
||||
- 有其他任何问题都欢迎发issue,我看到了会尽快回复
|
||||
排版格式和流程说明:
|
||||
|
||||
谢谢!
|
||||
- 翻译排版格式要求参考 SwiftGG [排版指南](https://github.com/SwiftGGTeam/translation/blob/master/SwiftGG%20排版指南.md)
|
||||
- Pull Request 发起方式参考 SwiftGG [Pull Request 说明](https://github.com/SwiftGGTeam/translation/blob/master/%E7%BF%BB%E8%AF%91%E6%B5%81%E7%A8%8B%E6%A6%82%E8%BF%B0%E5%8F%8APR%E8%AF%B4%E6%98%8E.md#%E5%A6%82%E4%BD%95%E5%8F%91%E8%B5%B7-pull-request)
|
||||
|
||||
# 关于术语
|
||||
原版文档差异比较:
|
||||
|
||||
翻译术语的时候请参考这个流程:
|
||||
在翻译时可以通过 Calibre 软件对 [document 目录下](https://github.com/SwiftGGTeam/the-swift-programming-language-in-chinese/tree/gh-pages/document) 不同版本的文档进行 diff,检查待更新部分。
|
||||
|
||||
- 尽量保证和已翻译的内容一致
|
||||
- 尽量先搜索,一般来说编程语言的大部分术语是一样的,可以参考[微软官方术语搜索](http://www.microsoft.com/Language/zh-cn/Search.aspx)
|
||||
- 如果以上两条都没有找到合适的结果,请自己决定一个合适的翻译或者直接使用英文原文,后期校对的时候会进行统一
|
||||
diff 操作如下:
|
||||
|
||||
# 参考流程
|
||||
将最新文档加入到 Calibre 中,点击 **Edit Book**,然后在编辑界面选择 **File** -> **Compare to other book** 检查各模块的更新内容,详见 [链接](https://manual.calibre-ebook.com/diff.html)。
|
||||
|
||||
有些朋友可能不太清楚如何帮忙翻译,我这里写一个简单的流程,大家可以参考一下:
|
||||
其他说明:
|
||||
|
||||
1. 首先fork我的项目
|
||||
2. 把fork过去的项目也就是你的项目clone到你的本地
|
||||
3. 在命令行运行 `git branch develop` 来创建一个新分支
|
||||
4. 运行 `git checkout develop` 来切换到新分支
|
||||
5. 运行 `git remote add upstream https://github.com/numbbbbb/the-swift-programming-language-in-chinese.git` 把我的库添加为远端库
|
||||
6. 运行 `git remote update`更新
|
||||
7. 运行 `git fetch upstream gh-pages` 拉取我的库的更新到本地
|
||||
8. 运行 `git rebase upstream/gh-pages` 将我的更新合并到你的分支
|
||||
- 相关术语请严格按照术语表来翻译,如果有问题可以发 Issue 大家一起讨论
|
||||
- 使用 Markdown 进行翻译,文件名必须使用英文
|
||||
- 翻译后的文档请放到 source 文件夹下的对应章节中,然后 Pull Request 即可,我们会用 GitBook 编译成网页
|
||||
- 有其他任何问题都欢迎发 Issue
|
||||
|
||||
这是一个初始化流程,只需要做一遍就行,之后请一直在develop分支进行修改。
|
||||
# 术语表
|
||||
|
||||
如果修改过程中我的库有了更新,请重复6、7、8步。
|
||||
翻译术语的时候请参考这个对照表:
|
||||
|
||||
修改之后,首先push到你的库,然后登录GitHub,在你的库的首页可以看到一个 `pull request` 按钮,点击它,填写一些说明信息,然后提交即可。
|
||||
| 术语 | 备选翻译 |
|
||||
| --- | --- |
|
||||
| property wrapper | 属性包装器([翻译相关讨论](https://github.com/SwiftGGTeam/the-swift-programming-language-in-chinese/issues/982#issuecomment-536244784)) |
|
||||
| projected value | 被呈现值 |
|
||||
| wrapped value | 被包装值 |
|
||||
| argument | 实参 |
|
||||
| parameter | 形参 |
|
||||
| variadic parameters| 可变参数 |
|
||||
| associated type | 关联类型 |
|
||||
| range | 区间 |
|
||||
| type property | 类型属性 |
|
||||
| unary operator | 一元运算符 |
|
||||
| binary operator | 二元运算符 |
|
||||
| ternary operator | 三元运算符 |
|
||||
| labeled statement | 具名语句 |
|
||||
| conform protocol | 遵循协议 |
|
||||
| availability-condition | 可用性条件 |
|
||||
| fallthrough | 贯穿 |
|
||||
| branch statement | 分支语句 |
|
||||
| control transfer statement | 控制传递语句 |
|
||||
| type annotation | 类型注解 |
|
||||
| type identifier | 类型标识符 |
|
||||
| metatype type | 元类型 |
|
||||
| protocol composition type | 复合协议类型 |
|
||||
| associated value | 关联值 |
|
||||
| raw value | 原始值 |
|
||||
| computed property | 计算属性 |
|
||||
| stored property | 存储属性 |
|
||||
| operator | 运算符 |
|
||||
| playground | 不翻译 |
|
||||
| array | 数组 |
|
||||
| dictionary | 字典 |
|
||||
| list | 列表 |
|
||||
| statement | 语句 |
|
||||
| expression | 表达式 |
|
||||
| optional | 可选 |
|
||||
| implicitly unwrapped optional | 隐式解包可选值 |
|
||||
| optional binding | 可选绑定 |
|
||||
| optional chaining | 可选链 |
|
||||
| collection | 集合 |
|
||||
| convention | 约定 |
|
||||
| iterate | 迭代 |
|
||||
| nest | 嵌套 |
|
||||
| inheritance | 继承 |
|
||||
| override | 重写 |
|
||||
| base class | 基类 |
|
||||
| designated initializer | 指定构造器 |
|
||||
| convenience initializer | 便利构造器 |
|
||||
| automatic reference counting | 自动引用计数 |
|
||||
| type inference | 类型推断 |
|
||||
| type casting | 类型转换 |
|
||||
| unwrapped | 解包 |
|
||||
| wrapped | 包装 |
|
||||
| note | 注意 |
|
||||
| closure | 闭包 |
|
||||
| tuple | 元组 |
|
||||
| first-class | 一等 |
|
||||
| deinitializer | 析构器 |
|
||||
| initializer | 构造器 |
|
||||
| initialization | 构造过程 |
|
||||
| deinitialization | 析构过程 |
|
||||
| getter | 不翻译 |
|
||||
| setter | 不翻译 |
|
||||
| subscript | 下标 |
|
||||
| property | 属性 |
|
||||
| attribute | 特性或者属性,根据上下文 |
|
||||
| method | 方法 |
|
||||
| enumeration | 枚举 |
|
||||
| structure | 结构体 |
|
||||
| protocol | 协议 |
|
||||
| extension | 扩展 |
|
||||
| generic | 泛型 |
|
||||
| literal value | 字面量 |
|
||||
| alias | 别名 |
|
||||
| assertion | 断言 |
|
||||
| conditional compilation | 条件编译 |
|
||||
| opaque type | 不透明类型 |
|
||||
| function | 函数 |
|
||||
| runtime | 运行时 |
|
||||
|
||||
# 贡献者
|
||||
|
||||
# 开源协议
|
||||
基于[WTFPL](http://en.wikipedia.org/wiki/WTFPL)协议开源。
|
||||
[贡献者列表](https://github.com/SwiftGGTeam/the-swift-programming-language-in-chinese/blob/gh-pages/source/contributors.md),感谢大家!
|
||||
|
||||
|
||||
|
||||
[numbbbbb]:https://github.com/numbbbbb
|
||||
[stanzhai]:https://github.com/stanzhai
|
||||
[coverxit]:https://github.com/coverxit
|
||||
[wh1100717]:https://github.com/wh1100717
|
||||
[TimothyYe]:https://github.com/TimothyYe
|
||||
[honghaoz]:https://github.com/honghaoz
|
||||
[lyuka]:https://github.com/lyuka
|
||||
[JaySurplus]:https://github.com/JaySurplus
|
||||
[Hawstein]:https://github.com/Hawstein
|
||||
[geek5nan]:https://github.com/geek5nan
|
||||
[yankuangshi]:https://github.com/yankuangshi
|
||||
[xielingwang]:https://github.com/xielingwang
|
||||
[yulingtianxia]:https://github.com/yulingtianxia
|
||||
[twlkyao]:https://github.com/twlkyao
|
||||
[dabing1022]:https://github.com/dabing1022
|
||||
[vclwei]:https://github.com/vclwei
|
||||
[fd5788]:https://github.com/fd5788
|
||||
[siemenliu]:https://github.com/siemenliu
|
||||
[youkugems]:https://github.com/youkugems
|
||||
[haolloyin]:https://github.com/haolloyin
|
||||
[wxstars]:https://github.com/wxstars
|
||||
[IceskYsl]:https://github.com/IceskYsl
|
||||
[sg552]:https://github.com/sg552
|
||||
[superkam]:https://github.com/superkam
|
||||
[zac1st1k]:https://github.com/zac1st1k
|
||||
[bzsy]:https://github.com/bzsy
|
||||
[pyanfield]:https://github.com/pyanfield
|
||||
[ericzyh]:https://github.com/ericzyh
|
||||
[peiyucn]:https://github.com/peiyucn
|
||||
[sunfiled]:https://github.com/sunfiled
|
||||
[lzw120]:https://github.com/lzw120
|
||||
[viztor]:https://github.com/viztor
|
||||
[wongzigii]:https://github.com/wongzigii
|
||||
[umcsdon]:https://github.com/umcsdon
|
||||
[zq54zquan]:https://github.com/zq54zquan
|
||||
[xiehurricane]:https://github.com/xiehurricane
|
||||
[Jasonbroker]:https://github.com/Jasonbroker
|
||||
[tualatrix]:https://github.com/tualatrix
|
||||
[pp-prog]:https://github.com/pp-prog
|
||||
[088haizi]:https://github.com/088haizi
|
||||
[baocaixiong]:https://github.com/baocaixiong
|
||||
[yeahdongcn]:https://github.com/yeahdongcn
|
||||
[shinyzhu]:https://github.com/shinyzhu
|
||||
[lslxdx]:https://github.com/lslxdx
|
||||
[Evilcome]:https://github.com/Evilcome
|
||||
[zqp]:https://github.com/zqp
|
||||
[NicePiao]:https://github.com/NicePiao
|
||||
[LunaticM]:https://github.com/LunaticM
|
||||
[menlongsheng]:https://github.com/menlongsheng
|
||||
[lifedim]:https://github.com/lifedim
|
||||
[happyming]:https://github.com/happyming
|
||||
[bruce0505]:https://github.com/bruce0505
|
||||
[Lin-H]:https://github.com/Lin-H
|
||||
[takalard]:https://github.com/takalard
|
||||
[dabing1022]:https://github.com/dabing1022
|
||||
[marsprince]:https://github.com/marsprince
|
||||
# 协议
|
||||
和 [苹果官方文档](https://swift.org/documentation/) 协议一致:[Creative Commons Attribution 4.0 International (CC BY 4.0) License](https://creativecommons.org/licenses/by/4.0/)。
|
||||
|
||||
@ -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
cover/cover.jpg
BIN
cover/cover.jpg
Binary file not shown.
|
Before Width: | Height: | Size: 838 KiB After Width: | Height: | Size: 882 KiB |
BIN
cover/cover_3.0.jpg
Normal file
BIN
cover/cover_3.0.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 882 KiB |
BIN
document/TheSwiftProgrammingLanguageSwift4.2.epub
Normal file
BIN
document/TheSwiftProgrammingLanguageSwift4.2.epub
Normal file
Binary file not shown.
BIN
document/TheSwiftProgrammingLanguageSwift5.epub
Normal file
BIN
document/TheSwiftProgrammingLanguageSwift5.epub
Normal file
Binary file not shown.
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.
@ -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>
|
||||
|
||||
@ -154,14 +154,14 @@ let yenSign: Character = "¥"
|
||||
通過調用全局`countElements`函數,並將字符串作為參數進行傳遞,可以獲取該字符串的字符數量。
|
||||
|
||||
```swift
|
||||
let unusualMenagerie = "Koala □謠Snail □□Penguin □笠Dromedary □□
|
||||
let unusualMenagerie = "Koala □謠Snail □□Penguin □笠Dromedary □□
|
||||
println("unusualMenagerie has \(countElements(unusualMenagerie)) characters")
|
||||
// 打印輸出:"unusualMenagerie has 40 characters"
|
||||
```
|
||||
|
||||
> 注意:
|
||||
不同的 Unicode 字符以及相同 Unicode 字符的不同表示方式可能需要不同數量的內存空間來存儲。所以 Swift 中的字符在一個字符串中並不一定佔用相同的內存空間。因此字符串的長度不得不通過迭代字符串中每一個字符的長度來進行計算。如果您正在處理一個長字符串,需要注意`countElements`函數必須遍歷字符串中的字符以精準計算字符串的長度。
|
||||
> 另外需要注意的是通過`countElements`返回的字符數量並不總是與包含相同字符的`NSString`的`length`屬性相同。`NSString`的`length`屬性是基於利用 UTF-16 表示的十六位代碼單元數字,而不是基於 Unicode 字符。為了解決這個問題,`NSString`的`length`屬性在被 Swift 的`String`訪問時會成為`utf16count`。
|
||||
> 另外需要注意的是通過`countElements`返回的字符數量並不總是與包含相同字符的`NSString`的`length`屬性相同。`NSString`的`length`屬性是基於利用 UTF-16 表示的十六位代碼單元數字,而不是基於 Unicode 字符。
|
||||
|
||||
<a name="concatenating_strings_and_characters"></a>
|
||||
## 連接字符串和字符 (Concatenating Strings and Characters)
|
||||
@ -336,7 +336,7 @@ Swift 提供了幾種不同的方式來訪問字符串的 Unicode 表示。
|
||||
下面由`D``o``g``!`和`□栨`DOG FACE`,Unicode 標量為`U+1F436`)組成的字符串中的每一個字符代表著一種不同的表示:
|
||||
|
||||
```swift
|
||||
let dogString = "Dog!□皂
|
||||
let dogString = "Dog!□皂
|
||||
```
|
||||
|
||||
<a name="UTF-8"></a>
|
||||
|
||||
@ -193,14 +193,9 @@ struct AlternativeRect {
|
||||
|
||||
只有 getter 沒有 setter 的計算屬性就是*只讀計算屬性*。只讀計算屬性總是返回一個值,可以通過點運算符訪問,但不能設置新的值。
|
||||
|
||||
<<<<<<< HEAD
|
||||
> 注意:
|
||||
> 必須使用`var`關鍵字定義計算屬性,包括只讀計算屬性,因為他們的值不是固定的。`let`關鍵字只用來聲明常量屬性,表示初始化後再也無法修改的值。
|
||||
=======
|
||||
> 注意:
|
||||
>
|
||||
> 必須使用`var`關鍵字定義計算屬性,包括只讀計算屬性,因為它們的值不是固定的。`let`關鍵字只用來聲明常量屬性,表示初始化後再也無法修改的值。
|
||||
>>>>>>> a516af6a531a104ec88da0d236ecf389a5ec72af
|
||||
|
||||
只讀計算屬性的聲明可以去掉`get`關鍵字和花括號:
|
||||
|
||||
@ -237,14 +232,9 @@ println("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
|
||||
|
||||
類似地,`didSet`觀察器會將舊的屬性值作為參數傳入,可以為該參數命名或者使用默認參數名`oldValue`。
|
||||
|
||||
<<<<<<< HEAD
|
||||
> 注意:
|
||||
> `willSet`和`didSet`觀察器在屬性初始化過程中不會被調用,他們只會當屬性的值在初始化之外的地方被設置時被調用。
|
||||
=======
|
||||
> 注意:
|
||||
>
|
||||
> `willSet`和`didSet`觀察器在屬性初始化過程中不會被調用,它們只會當屬性的值在初始化之外的地方被設置時被調用。
|
||||
>>>>>>> a516af6a531a104ec88da0d236ecf389a5ec72af
|
||||
|
||||
這裡是一個`willSet`和`didSet`的實際例子,其中定義了一個名為`StepCounter`的類,用來統計當人步行時的總步數,可以跟計步器或其他日常鍛煉的統計裝置的輸入數據配合使用。
|
||||
|
||||
|
||||
@ -100,7 +100,7 @@ struct Matrix {
|
||||
init(rows: Int, columns: Int) {
|
||||
self.rows = rows
|
||||
self.columns = columns
|
||||
grid = Array(count: rows * columns, repeatedValue: 0.0)
|
||||
grid = Array(repeating: 0.0, count: rows * columns)
|
||||
}
|
||||
func indexIsValidForRow(row: Int, column: Int) -> Bool {
|
||||
return row >= 0 && row < rows && column >= 0 && column < columns
|
||||
|
||||
20
source/01_welcome_to_swift/01_about_swift.md
Executable file
20
source/01_welcome_to_swift/01_about_swift.md
Executable file
@ -0,0 +1,20 @@
|
||||
# 关于 Swift
|
||||
|
||||
Swift 是一种非常好的编写软件的方式,无论是手机,台式机,服务器,还是其他运行代码的设备。它是一种安全,快速和互动的编程语言,将现代编程语言的精华和苹果工程师文化的智慧,以及来自开源社区的多样化贡献结合了起来。编译器对性能进行了优化,编程语言对开发进行了优化,两者互不干扰,鱼与熊掌兼得。
|
||||
|
||||
Swift 对于初学者来说也很友好。它是一门满足工业标准的编程语言,但又有着脚本语言般的表达力和可玩性。它支持代码预览(playgrounds),这个革命性的特性可以允许程序员在不编译和运行应用程序的前提下运行 Swift 代码并实时查看结果。
|
||||
|
||||
Swift 通过采用现代编程模式来避免大量常见编程错误:
|
||||
|
||||
* 变量始终在使用前初始化。
|
||||
* 检查数组索引超出范围的错误。
|
||||
* 检查整数是否溢出。
|
||||
* 可选值确保明确处理 `nil` 值。
|
||||
* 内存被自动管理。
|
||||
* 错误处理允许从意外故障控制恢复。
|
||||
|
||||
Swift 代码被编译和优化,以充分利用现代硬件。语法和标准库是基于指导原则设计的,编写代码的明显方式也应该是最好的。安全性和速度的结合使得 Swift 成为从 “Hello,world!” 到整个操作系统的绝佳选择。
|
||||
|
||||
Swift 将强大的类型推理和模式匹配与现代轻巧的语法相结合,使复杂的想法能够以清晰简洁的方式表达。因此,代码不仅更容易编写,而且易于阅读和维护。
|
||||
|
||||
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 13 中默认包含的 Swift 5.5 版本。你可以使用 Xcode 13 来构建 Swift 5.5、Swift 4.2 或 Swift 4 写的项目。
|
||||
|
||||
使用 Xcode 13 构建 Swift 4 和 Swift 4.2 代码时,Swift 5.5 的大多数功能都适用。但以下功能仅支持 Swift 5.5 或更高版本:
|
||||
|
||||
* 返回值是不透明类型的函数依赖 Swift 5.1 运行时。
|
||||
* **try?** 表达式不会为已返回可选类型的代码引入额外的可选类型层级。
|
||||
* 大数字的整型字面量初始化代码的类型将会被正确推导,例如 **UInt64(0xffff_ffff_ffff_ffff)** 将会被推导为整型类型而非溢出。
|
||||
|
||||
并发特性需要 Swift 5.5 及以上版本,以及一个提供了并发相关类型的 Swift 标准库版本。要应用于苹果平台,请至少将部署版本设置为 iOS 15、macOS 12、tvOS 15 或 watchOS 8.0。
|
||||
|
||||
用 Swift 5.5 写的项目可以依赖用 Swift 4.2 或 Swift 4 写的项目,反之亦然。这意味着,如果你将一个大的项目分解成多个框架(framework),你可以逐个地将框架从 Swift 4 代码迁移到 Swift 5.5。
|
||||
818
source/01_welcome_to_swift/03_a_swift_tour.md
Executable file
818
source/01_welcome_to_swift/03_a_swift_tour.md
Executable file
@ -0,0 +1,818 @@
|
||||
# Swift 初见
|
||||
|
||||
通常来说,编程语言教程中的第一个程序应该在屏幕上打印“Hello, world”。在 Swift 中,可以用一行代码实现:
|
||||
|
||||
```swift
|
||||
print("Hello, world!")
|
||||
```
|
||||
|
||||
如果你写过 C 或者 Objective-C 代码,那你应该很熟悉这种形式——在 Swift 中,这行代码就是一个完整的程序。你不需要为了输入输出或者字符串处理导入一个单独的库。全局作用域中的代码会被自动当做程序的入口点,所以你也不需要 `main()` 函数。你同样不需要在每个语句结尾写上分号。
|
||||
|
||||
这个教程会通过一系列编程例子来让你对 Swift 有初步了解,如果你有什么不理解的地方也不用担心——任何本章介绍的内容都会在后面的章节中详细讲解到。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 最好的体验是把这一章作为 Playground 文件在 Xcode 中打开。 Playgrounds 允许你可以编辑代码并立刻看到输出结果。
|
||||
>
|
||||
> [下载 Playground](https://docs.swift.org/swift-book/GuidedTour/GuidedTour.playground.zip)
|
||||
|
||||
## 简单值 {#simple-values}
|
||||
|
||||
使用 `let` 来声明常量,使用 `var` 来声明变量。一个常量的值,在编译的时候,并不需要有明确的值,但是你只能为它赋值一次。这说明你可以用一个常量来命名一个值,一次赋值就可在多个地方使用。
|
||||
|
||||
```swift
|
||||
var myVariable = 42
|
||||
myVariable = 50
|
||||
let myConstant = 42
|
||||
```
|
||||
|
||||
常量或者变量的类型必须和你赋给它们的值一样。然而,你不用明确地声明类型。当你通过一个值来声明变量和常量时,编译器会自动推断其类型。在上面的例子中,编译器推断出 `myVariable` 是一个整数类型,因为它的初始值是整数。
|
||||
|
||||
如果初始值没有提供足够的信息(或者没有初始值),那你需要在变量后面声明类型,用冒号分割。
|
||||
|
||||
```swift
|
||||
let implicitInteger = 70
|
||||
let implicitDouble = 70.0
|
||||
let explicitDouble: Double = 70
|
||||
```
|
||||
|
||||
> 练习
|
||||
>
|
||||
> 创建一个常量,显式指定类型为 `Float` 并指定初始值为 4。
|
||||
|
||||
值永远不会被隐式转换为其他类型。如果你需要把一个值转换成其他类型,请显式转换。
|
||||
|
||||
```swift
|
||||
let label = "The width is"
|
||||
let width = 94
|
||||
let widthLabel = label + String(width)
|
||||
```
|
||||
|
||||
> 练习
|
||||
>
|
||||
> 删除最后一行中的 `String`,错误提示是什么?
|
||||
|
||||
有一种更简单的把值转换成字符串的方法:把值写到括号中,并且在括号之前写一个反斜杠(\)。例如:
|
||||
|
||||
```swift
|
||||
let apples = 3
|
||||
let oranges = 5
|
||||
let appleSummary = "I have \(apples) apples."
|
||||
let fruitSummary = "I have \(apples + oranges) pieces of fruit."
|
||||
```
|
||||
|
||||
> 练习
|
||||
>
|
||||
> 使用 `\()` 来把一个浮点计算转换成字符串,并加上某人的名字,和他打个招呼。
|
||||
|
||||
使用三个双引号(`"""`)来包含多行字符串内容。每行行首的缩进会被去除,直到和结尾引号的缩进相匹配。举个例子:
|
||||
|
||||
```swift
|
||||
let quotation = """
|
||||
I said "I have \(apples) apples."
|
||||
And then I said "I have \(apples + oranges) pieces of fruit."
|
||||
"""
|
||||
```
|
||||
|
||||
使用方括号 `[]` 来创建数组和字典,并使用下标或者键(key)来访问元素。最后一个元素后面允许有个逗号。
|
||||
|
||||
```swift
|
||||
var shoppingList = ["catfish", "water", "tulips", "blue paint"]
|
||||
shoppingList[1] = "bottle of water"
|
||||
|
||||
var occupations = [
|
||||
"Malcolm": "Captain",
|
||||
"Kaylee": "Mechanic",
|
||||
]
|
||||
occupations["Jayne"] = "Public Relations"
|
||||
```
|
||||
|
||||
数组在添加元素时会自动变大。
|
||||
|
||||
```swift
|
||||
shoppingList.append("blue paint")
|
||||
print(shoppingList)
|
||||
```
|
||||
|
||||
使用初始化语法来创建一个空数组或者空字典。
|
||||
|
||||
```swift
|
||||
let emptyArray: [String] = []
|
||||
let emptyDictionary: [String: Float] = [:]
|
||||
```
|
||||
|
||||
如果类型信息可以被推断出来,你可以用 `[]` 和 `[:]` 来创建空数组和空字典——比如,在给变量赋新值或者给函数传参数的时候。
|
||||
|
||||
```swift
|
||||
shoppingList = []
|
||||
occupations = [:]
|
||||
```
|
||||
|
||||
## 控制流 {#control-flow}
|
||||
|
||||
使用 `if` 和 `switch` 来进行条件操作,使用 `for-in`、`while` 和 `repeat-while` 来进行循环。包裹条件和循环变量的括号可以省略,但是语句体的大括号是必须的。
|
||||
|
||||
```swift
|
||||
let individualScores = [75, 43, 103, 87, 12]
|
||||
var teamScore = 0
|
||||
for score in individualScores {
|
||||
if score > 50 {
|
||||
teamScore += 3
|
||||
} else {
|
||||
teamScore += 1
|
||||
}
|
||||
}
|
||||
print(teamScore)
|
||||
```
|
||||
|
||||
在 `if` 语句中,条件必须是一个布尔表达式——这意味着像 `if score { ... }` 这样的代码将报错,而不会隐形地与 0 做对比。
|
||||
|
||||
你可以一起使用 `if` 和 `let` 一起来处理值缺失的情况。这些值可由可选值来代表。一个可选的值是一个具体的值或者是 `nil` 以表示值缺失。在类型后面加一个问号(`?`)来标记这个变量的值是可选的。
|
||||
|
||||
```swift
|
||||
var optionalString: String? = "Hello"
|
||||
print(optionalString == nil)
|
||||
|
||||
var optionalName: String? = "John Appleseed"
|
||||
var greeting = "Hello!"
|
||||
if let name = optionalName {
|
||||
greeting = "Hello, \(name)"
|
||||
}
|
||||
```
|
||||
|
||||
> 练习
|
||||
>
|
||||
> 把 `optionalName` 改成 `nil`,greeting 会是什么?添加一个 `else` 语句,当 `optionalName` 是 `nil` 时给 greeting 赋一个不同的值。
|
||||
|
||||
如果变量的可选值是 `nil`,条件会判断为 `false`,大括号中的代码会被跳过。如果不是 `nil`,会将值解包并赋给 `let` 后面的常量,这样代码块中就可以使用这个值了。
|
||||
|
||||
另一种处理可选值的方法是通过使用 `??` 操作符来提供一个默认值。如果可选值缺失的话,可以使用默认值来代替。
|
||||
|
||||
```swift
|
||||
let nickName: String? = nil
|
||||
let fullName: String = "John Appleseed"
|
||||
let informalGreeting = "Hi \(nickName ?? fullName)"
|
||||
```
|
||||
|
||||
`switch` 支持任意类型的数据以及各种比较操作——不仅仅是整数以及测试相等。
|
||||
|
||||
```swift
|
||||
let vegetable = "red pepper"
|
||||
switch vegetable {
|
||||
case "celery":
|
||||
print("Add some raisins and make ants on a log.")
|
||||
case "cucumber", "watercress":
|
||||
print("That would make a good tea sandwich.")
|
||||
case let x where x.hasSuffix("pepper"):
|
||||
print("Is it a spicy \(x)?")
|
||||
default:
|
||||
print("Everything tastes good in soup.")
|
||||
}
|
||||
```
|
||||
|
||||
> 练习
|
||||
>
|
||||
> 删除 `default` 语句,看看会有什么错误?
|
||||
|
||||
注意 `let` 在上述例子的等式中是如何使用的,它将匹配等式的值赋给常量 `x`。
|
||||
|
||||
运行 `switch` 中匹配到的 `case` 语句之后,程序会退出 `switch` 语句,并不会继续向下运行,所以不需要在每个子句结尾写 `break`。
|
||||
|
||||
你可以使用 `for-in` 来遍历字典,需要一对儿变量来表示每个键值对。字典是一个无序的集合,所以他们的键和值以任意顺序迭代结束。
|
||||
|
||||
```swift
|
||||
let interestingNumbers = [
|
||||
"Prime": [2, 3, 5, 7, 11, 13],
|
||||
"Fibonacci": [1, 1, 2, 3, 5, 8],
|
||||
"Square": [1, 4, 9, 16, 25],
|
||||
]
|
||||
var largest = 0
|
||||
for (_, numbers) in interestingNumbers {
|
||||
for number in numbers {
|
||||
if number > largest {
|
||||
largest = number
|
||||
}
|
||||
}
|
||||
}
|
||||
print(largest)
|
||||
// 输出 "25"
|
||||
```
|
||||
|
||||
> 练习
|
||||
>
|
||||
> 将 _ 替换成变量名,以确定哪种类型的值是最大的。
|
||||
|
||||
使用 `while` 来重复运行一段代码直到条件改变。循环条件也可以在结尾,保证能至少循环一次。
|
||||
|
||||
```swift
|
||||
var n = 2
|
||||
while n < 100 {
|
||||
n *= 2
|
||||
}
|
||||
print(n)
|
||||
|
||||
var m = 2
|
||||
repeat {
|
||||
m *= 2
|
||||
} while m < 100
|
||||
print(m)
|
||||
```
|
||||
|
||||
你可以在循环中使用 `..<` 来表示下标范围。
|
||||
|
||||
```swift
|
||||
var total = 0
|
||||
for i in 0..<4 {
|
||||
total += i
|
||||
}
|
||||
print(total)
|
||||
```
|
||||
|
||||
使用 `..<` 创建的范围不包含上界,如果想包含的话需要使用 `...`。
|
||||
|
||||
## 函数和闭包 {#functions-and-closures}
|
||||
|
||||
使用 `func` 来声明一个函数,使用名字和参数来调用函数。使用 `->` 来指定函数返回值的类型。
|
||||
|
||||
```swift
|
||||
func greet(person: String, day: String) -> String {
|
||||
return "Hello \(person), today is \(day)."
|
||||
}
|
||||
greet(person:"Bob", day: "Tuesday")
|
||||
```
|
||||
|
||||
> 练习
|
||||
>
|
||||
> 删除 `day` 参数,在这个欢迎语中添加一个参数来表示今天的特价菜。
|
||||
|
||||
默认情况下,函数使用它们的参数名称作为它们参数的标签,在参数名称前可以自定义参数标签,或者使用 `_` 表示不使用参数标签。
|
||||
|
||||
```swift
|
||||
func greet(_ person: String, on day: String) -> String {
|
||||
return "Hello \(person), today is \(day)."
|
||||
}
|
||||
greet("John", on: "Wednesday")
|
||||
```
|
||||
|
||||
使用元组来生成复合值,比如让一个函数返回多个值。该元组的元素可以用名称或数字来获取。
|
||||
|
||||
```swift
|
||||
func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
|
||||
var min = scores[0]
|
||||
var max = scores[0]
|
||||
var sum = 0
|
||||
|
||||
for score in scores {
|
||||
if score > max {
|
||||
max = score
|
||||
} else if score < min {
|
||||
min = score
|
||||
}
|
||||
sum += score
|
||||
}
|
||||
|
||||
return (min, max, sum)
|
||||
}
|
||||
let statistics = calculateStatistics(scores:[5, 3, 100, 3, 9])
|
||||
print(statistics.sum)
|
||||
print(statistics.2)
|
||||
```
|
||||
|
||||
函数可以嵌套。被嵌套的函数可以访问外侧函数的变量,你可以使用嵌套函数来重构一个太长或者太复杂的函数。
|
||||
|
||||
```swift
|
||||
func returnFifteen() -> Int {
|
||||
var y = 10
|
||||
func add() {
|
||||
y += 5
|
||||
}
|
||||
add()
|
||||
return y
|
||||
}
|
||||
returnFifteen()
|
||||
```
|
||||
|
||||
函数是第一等类型,这意味着函数可以作为另一个函数的返回值。
|
||||
|
||||
```swift
|
||||
func makeIncrementer() -> ((Int) -> Int) {
|
||||
func addOne(number: Int) -> Int {
|
||||
return 1 + number
|
||||
}
|
||||
return addOne
|
||||
}
|
||||
var increment = makeIncrementer()
|
||||
increment(7)
|
||||
```
|
||||
|
||||
函数也可以当做参数传入另一个函数。
|
||||
|
||||
```swift
|
||||
func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
|
||||
for item in list {
|
||||
if condition(item) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
func lessThanTen(number: Int) -> Bool {
|
||||
return number < 10
|
||||
}
|
||||
var numbers = [20, 19, 7, 12]
|
||||
hasAnyMatches(list: numbers, condition: lessThanTen)
|
||||
```
|
||||
|
||||
函数实际上是一种特殊的闭包:它是一段能之后被调取的代码。闭包中的代码能访问闭包作用域中的变量和函数,即使闭包是在一个不同的作用域被执行的——你已经在嵌套函数的例子中看过了。你可以使用 `{}` 来创建一个匿名闭包。使用 `in` 将参数和返回值类型的声明与闭包函数体进行分离。
|
||||
|
||||
```swift
|
||||
numbers.map({
|
||||
(number: Int) -> Int in
|
||||
let result = 3 * number
|
||||
return result
|
||||
})
|
||||
```
|
||||
|
||||
> 练习
|
||||
>
|
||||
> 重写闭包,对所有奇数返回 0。
|
||||
|
||||
有很多种创建更简洁的闭包的方法。如果一个闭包的类型已知,比如作为一个代理的回调,你可以忽略参数,返回值,甚至两个都忽略。单个语句闭包会把它语句的值当做结果返回。
|
||||
|
||||
```swift
|
||||
let mappedNumbers = numbers.map({ number in 3 * number })
|
||||
print(mappedNumbers)
|
||||
```
|
||||
|
||||
你可以通过参数位置而不是参数名字来引用参数——这个方法在非常短的闭包中非常有用。当一个闭包作为最后一个参数传给一个函数的时候,它可以直接跟在圆括号后面。当一个闭包是传给函数的唯一参数,你可以完全忽略圆括号。
|
||||
|
||||
```swift
|
||||
let sortedNumbers = numbers.sorted { $0 > $1 }
|
||||
print(sortedNumbers)
|
||||
```
|
||||
|
||||
## 对象和类 {#objects-and-classes}
|
||||
|
||||
使用 `class` 和类名来创建一个类。类中属性的声明和常量、变量声明一样,唯一的区别就是它们的上下文是类。同样,方法和函数声明也一样。
|
||||
|
||||
```swift
|
||||
class Shape {
|
||||
var numberOfSides = 0
|
||||
func simpleDescription() -> String {
|
||||
return "A shape with \(numberOfSides) sides."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 练习
|
||||
>
|
||||
> 使用 `let` 添加一个常量属性,再添加一个接收一个参数的方法。
|
||||
|
||||
要创建一个类的实例,在类名后面加上括号。使用点语法来访问实例的属性和方法。
|
||||
|
||||
```swift
|
||||
var shape = Shape()
|
||||
shape.numberOfSides = 7
|
||||
var shapeDescription = shape.simpleDescription()
|
||||
```
|
||||
|
||||
这个版本的 `Shape` 类缺少了一些重要的东西:一个构造函数来初始化类实例。使用 `init` 来创建一个构造器。
|
||||
|
||||
```swift
|
||||
class NamedShape {
|
||||
var numberOfSides: Int = 0
|
||||
var name: String
|
||||
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
}
|
||||
|
||||
func simpleDescription() -> String {
|
||||
return "A shape with \(numberOfSides) sides."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
注意 `self` 被用来区别实例变量 `name` 和构造器的参数 `name`。当你创建实例的时候,像传入函数参数一样给类传入构造器的参数。每个属性都需要赋值——无论是通过声明(就像 `numberOfSides`)还是通过构造器(就像 `name`)。
|
||||
|
||||
如果你需要在对象释放之前进行一些清理工作,使用 `deinit` 创建一个析构函数。
|
||||
|
||||
子类的定义方法是在它们的类名后面加上父类的名字,用冒号分割。创建类的时候并不需要一个标准的根类,所以你可以根据需要添加或者忽略父类。
|
||||
|
||||
子类如果要重写父类的方法的话,需要用 `override` 标记——如果没有添加 `override` 就重写父类方法的话编译器会报错。编译器同样会检测 `override` 标记的方法是否确实在父类中。
|
||||
|
||||
```swift
|
||||
class Square: NamedShape {
|
||||
var sideLength: Double
|
||||
|
||||
init(sideLength: Double, name: String) {
|
||||
self.sideLength = sideLength
|
||||
super.init(name: name)
|
||||
numberOfSides = 4
|
||||
}
|
||||
|
||||
func area() -> Double {
|
||||
return sideLength * sideLength
|
||||
}
|
||||
|
||||
override func simpleDescription() -> String {
|
||||
return "A square with sides of length \(sideLength)."
|
||||
}
|
||||
}
|
||||
let test = Square(sideLength: 5.2, name: "my test square")
|
||||
test.area()
|
||||
test.simpleDescription()
|
||||
```
|
||||
|
||||
> 练习
|
||||
>
|
||||
> 创建 `NamedShape` 的另一个子类 `Circle`,构造器接收两个参数,一个是半径一个是名称,在子类 `Circle` 中实现 `area()` 和 `simpleDescription()` 方法。
|
||||
|
||||
除了简单的存储属性,还有使用 getter 和 setter 的计算属性。
|
||||
|
||||
```swift
|
||||
class EquilateralTriangle: NamedShape {
|
||||
var sideLength: Double = 0.0
|
||||
|
||||
init(sideLength: Double, name: String) {
|
||||
self.sideLength = sideLength
|
||||
super.init(name: name)
|
||||
numberOfSides = 3
|
||||
}
|
||||
|
||||
var perimeter: Double {
|
||||
get {
|
||||
return 3.0 * sideLength
|
||||
}
|
||||
set {
|
||||
sideLength = newValue / 3.0
|
||||
}
|
||||
}
|
||||
|
||||
override func simpleDescription() -> String {
|
||||
return "An equilateral triangle with sides of length \(sideLength)."
|
||||
}
|
||||
}
|
||||
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
|
||||
print(triangle.perimeter)
|
||||
triangle.perimeter = 9.9
|
||||
print(triangle.sideLength)
|
||||
```
|
||||
|
||||
在 `perimeter` 的 setter 中,新值的名字是 `newValue`。你可以在 `set` 之后的圆括号中显式地设置一个名字。
|
||||
|
||||
注意 `EquilateralTriangle` 类的构造器执行了三步:
|
||||
|
||||
1. 设置子类声明的属性值
|
||||
2. 调用父类的构造器
|
||||
3. 改变父类定义的属性值。其他的工作比如调用方法、getters 和 setters 也可以在这个阶段完成。
|
||||
|
||||
如果你不需要计算属性,但是仍然需要在设置一个新值之前或者之后运行代码,使用 `willSet` 和 `didSet`。写入的代码会在属性值发生改变时调用,但不包含构造器中发生值改变的情况。比如,下面的类确保三角形的边长总是和正方形的边长相同。
|
||||
|
||||
```swift
|
||||
class TriangleAndSquare {
|
||||
var triangle: EquilateralTriangle {
|
||||
willSet {
|
||||
square.sideLength = newValue.sideLength
|
||||
}
|
||||
}
|
||||
var square: Square {
|
||||
willSet {
|
||||
triangle.sideLength = newValue.sideLength
|
||||
}
|
||||
}
|
||||
init(size: Double, name: String) {
|
||||
square = Square(sideLength: size, name: name)
|
||||
triangle = EquilateralTriangle(sideLength: size, name: name)
|
||||
}
|
||||
}
|
||||
var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
|
||||
print(triangleAndSquare.square.sideLength)
|
||||
print(triangleAndSquare.triangle.sideLength)
|
||||
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
|
||||
print(triangleAndSquare.triangle.sideLength)
|
||||
```
|
||||
|
||||
处理变量的可选值时,你可以在操作(比如方法、属性和子脚本)之前加 `?`。如果 `?` 之前的值是 `nil`,`?` 后面的东西都会被忽略,并且整个表达式返回 `nil`。否则,可选值会被解包,之后的所有代码都会按照解包后的值运行。在这两种情况下,整个表达式的值也是一个可选值。
|
||||
|
||||
```swift
|
||||
let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
|
||||
let sideLength = optionalSquare?.sideLength
|
||||
```
|
||||
|
||||
## 枚举和结构体 {#enumerations-and-structure}
|
||||
|
||||
使用 `enum` 来创建一个枚举。就像类和其他所有命名类型一样,枚举可以包含方法。
|
||||
|
||||
```swift
|
||||
enum Rank: Int {
|
||||
case ace = 1
|
||||
case two, three, four, five, six, seven, eight, nine, ten
|
||||
case jack, queen, king
|
||||
func simpleDescription() -> String {
|
||||
switch self {
|
||||
case .ace:
|
||||
return "ace"
|
||||
case .jack:
|
||||
return "jack"
|
||||
case .queen:
|
||||
return "queen"
|
||||
case .king:
|
||||
return "king"
|
||||
default:
|
||||
return String(self.rawValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
let ace = Rank.ace
|
||||
let aceRawValue = ace.rawValue
|
||||
```
|
||||
|
||||
> 练习
|
||||
>
|
||||
> 写一个函数,通过比较它们的原始值来比较两个 `Rank` 值。
|
||||
|
||||
默认情况下,Swift 按照从 0 开始每次加 1 的方式为原始值进行赋值,不过你可以通过显式赋值进行改变。在上面的例子中,`Ace` 被显式赋值为 1,并且剩下的原始值会按照顺序赋值。你也可以使用字符串或者浮点数作为枚举的原始值。使用 `rawValue` 属性来访问一个枚举成员的原始值。
|
||||
|
||||
使用 `init?(rawValue:)` 初始化构造器来从原始值创建一个枚举实例。如果存在与原始值相应的枚举成员就返回该枚举成员,否则就返回 `nil`。
|
||||
|
||||
```swift
|
||||
if let convertedRank = Rank(rawValue: 3) {
|
||||
let threeDescription = convertedRank.simpleDescription()
|
||||
}
|
||||
```
|
||||
|
||||
枚举的关联值是实际值,并不是原始值的另一种表达方法。实际上,如果没有比较有意义的原始值,你就不需要提供原始值。
|
||||
|
||||
```swift
|
||||
enum Suit {
|
||||
case spades, hearts, diamonds, clubs
|
||||
func simpleDescription() -> String {
|
||||
switch self {
|
||||
case .spades:
|
||||
return "spades"
|
||||
case .hearts:
|
||||
return "hearts"
|
||||
case .diamonds:
|
||||
return "diamonds"
|
||||
case .clubs:
|
||||
return "clubs"
|
||||
}
|
||||
}
|
||||
}
|
||||
let hearts = Suit.hearts
|
||||
let heartsDescription = hearts.simpleDescription()
|
||||
```
|
||||
|
||||
> 练习
|
||||
>
|
||||
> 给 `Suit` 添加一个 `color()` 方法,对 `spades` 和 `clubs` 返回 “black” ,对 `hearts` 和 `diamonds` 返回 “red” 。
|
||||
|
||||
注意在上面的例子中用了两种方式引用 `hearts` 枚举成员:给 `hearts` 常量赋值时,枚举成员 `Suit.hearts` 需要用全名来引用,因为常量没有显式指定类型。在 `switch` 里,枚举成员使用缩写 `.hearts` 来引用,因为 `self` 的值已经是一个 `suit` 类型。在任何已知变量类型的情况下都可以使用缩写。
|
||||
|
||||
如果枚举成员的实例有原始值,那么这些值是在声明的时候就已经决定了,这意味着不同枚举实例的枚举成员总会有一个相同的原始值。当然我们也可以为枚举成员设定关联值,关联值是在创建实例时决定的。这意味着同一枚举成员不同实例的关联值可以不相同。你可以把关联值想象成枚举成员实例的存储属性。例如,考虑从服务器获取日出和日落的时间的情况。服务器会返回正常结果或者错误信息。
|
||||
|
||||
```swift
|
||||
enum ServerResponse {
|
||||
case result(String, String)
|
||||
case failure(String)
|
||||
}
|
||||
|
||||
let success = ServerResponse.result("6:00 am", "8:09 pm")
|
||||
let failure = ServerResponse.failure("Out of cheese.")
|
||||
|
||||
switch success {
|
||||
case let .result(sunrise, sunset):
|
||||
print("Sunrise is at \(sunrise) and sunset is at \(sunset)")
|
||||
case let .failure(message):
|
||||
print("Failure... \(message)")
|
||||
}
|
||||
```
|
||||
|
||||
> 练习
|
||||
>
|
||||
> 给 `ServerResponse` 和 switch 添加第三种情况。
|
||||
|
||||
注意 `ServerResponse` 的值在与 `switch` 的分支匹配时,日升和日落时间是如何从该值中提取出来的。
|
||||
|
||||
使用 `struct` 来创建一个结构体。结构体和类有很多相同的地方,包括方法和构造器。它们之间最大的一个区别就是结构体是传值,类是传引用。
|
||||
|
||||
```swift
|
||||
struct Card {
|
||||
var rank: Rank
|
||||
var suit: Suit
|
||||
func simpleDescription() -> String {
|
||||
return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
|
||||
}
|
||||
}
|
||||
let threeOfSpades = Card(rank: .three, suit: .spades)
|
||||
let threeOfSpadesDescription = threeOfSpades.simpleDescription()
|
||||
```
|
||||
|
||||
> 练习
|
||||
>
|
||||
> 写一个方法,创建一副完整的扑克牌,这些牌是所有 rank 和 suit 的组合。
|
||||
|
||||
## 协议和扩展 {#protocols-and-extensions}
|
||||
|
||||
使用 `protocol` 来声明一个协议。
|
||||
|
||||
```swift
|
||||
protocol ExampleProtocol {
|
||||
var simpleDescription: String { get }
|
||||
mutating func adjust()
|
||||
}
|
||||
```
|
||||
|
||||
类、枚举和结构体都可以遵循协议。
|
||||
|
||||
```swift
|
||||
class SimpleClass: ExampleProtocol {
|
||||
var simpleDescription: String = "A very simple class."
|
||||
var anotherProperty: Int = 69105
|
||||
func adjust() {
|
||||
simpleDescription += " Now 100% adjusted."
|
||||
}
|
||||
}
|
||||
var a = SimpleClass()
|
||||
a.adjust()
|
||||
let aDescription = a.simpleDescription
|
||||
|
||||
struct SimpleStructure: ExampleProtocol {
|
||||
var simpleDescription: String = "A simple structure"
|
||||
mutating func adjust() {
|
||||
simpleDescription += " (adjusted)"
|
||||
}
|
||||
}
|
||||
var b = SimpleStructure()
|
||||
b.adjust()
|
||||
let bDescription = b.simpleDescription
|
||||
```
|
||||
|
||||
> 练习
|
||||
>
|
||||
> 给 `ExampleProtocol` 再增加一个要求。你需要怎么改 `SimpleClass` 和 `SimpleStructure` 才能保证它们仍旧遵循这个协议?
|
||||
|
||||
注意声明 `SimpleStructure` 时候 `mutating` 关键字用来标记一个会修改结构体的方法。`SimpleClass` 的声明不需要标记任何方法,因为类中的方法通常可以修改类属性(类的性质)。
|
||||
|
||||
使用 `extension` 来为现有的类型添加功能,比如新的方法和计算属性。你可以使用扩展让某个在别处声明的类型来遵守某个协议,这同样适用于从外部库或者框架引入的类型。
|
||||
|
||||
```swift
|
||||
extension Int: ExampleProtocol {
|
||||
var simpleDescription: String {
|
||||
return "The number \(self)"
|
||||
}
|
||||
mutating func adjust() {
|
||||
self += 42
|
||||
}
|
||||
}
|
||||
print(7.simpleDescription)
|
||||
```
|
||||
|
||||
> 练习
|
||||
>
|
||||
> 给 `Double` 类型写一个扩展,添加 `absoluteValue` 属性。
|
||||
|
||||
你可以像使用其他命名类型一样使用协议名——例如,创建一个有不同类型但是都实现一个协议的对象集合。当你处理类型是协议的值时,协议外定义的方法不可用。
|
||||
|
||||
```swift
|
||||
let protocolValue: ExampleProtocol = a
|
||||
print(protocolValue.simpleDescription)
|
||||
// print(protocolValue.anotherProperty) // 去掉注释可以看到错误
|
||||
```
|
||||
|
||||
即使 `protocolValue` 变量运行时的类型是 `simpleClass` ,编译器还是会把它的类型当做 `ExampleProtocol`。这表示你不能调用在协议之外的方法或者属性。
|
||||
|
||||
## 错误处理 {#error-handling}
|
||||
|
||||
使用采用 `Error` 协议的类型来表示错误。
|
||||
|
||||
```swift
|
||||
enum PrinterError: Error {
|
||||
case outOfPaper
|
||||
case noToner
|
||||
case onFire
|
||||
}
|
||||
```
|
||||
|
||||
使用 `throw` 来抛出一个错误和使用 `throws` 来表示一个可以抛出错误的函数。如果在函数中抛出一个错误,这个函数会立刻返回并且调用该函数的代码会进行错误处理。
|
||||
|
||||
```swift
|
||||
func send(job: Int, toPrinter printerName: String) throws -> String {
|
||||
if printerName == "Never Has Toner" {
|
||||
throw PrinterError.noToner
|
||||
}
|
||||
return "Job sent"
|
||||
}
|
||||
```
|
||||
|
||||
有多种方式可以用来进行错误处理。一种方式是使用 `do-catch` 。在 `do` 代码块中,使用 `try` 来标记可以抛出错误的代码。在 `catch` 代码块中,除非你另外命名,否则错误会自动命名为 `error` 。
|
||||
|
||||
```swift
|
||||
do {
|
||||
let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
|
||||
print(printerResponse)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
```
|
||||
|
||||
> 练习
|
||||
>
|
||||
> 将 printer name 改为 `"Never Has Toner"` 使 `send(job:toPrinter:)` 函数抛出错误。
|
||||
|
||||
可以使用多个 `catch` 块来处理特定的错误。参照 switch 中的 `case` 风格来写 `catch`。
|
||||
|
||||
```swift
|
||||
do {
|
||||
let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
|
||||
print(printerResponse)
|
||||
} catch PrinterError.onFire {
|
||||
print("I'll just put this over here, with the rest of the fire.")
|
||||
} catch let printerError as PrinterError {
|
||||
print("Printer error: \(printerError).")
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
```
|
||||
|
||||
> 练习
|
||||
>
|
||||
> 在 `do` 代码块中添加抛出错误的代码。你需要抛出哪种错误来使第一个 `catch` 块进行接收?怎么使第二个和第三个 `catch` 进行接收呢?
|
||||
|
||||
另一种处理错误的方式使用 `try?` 将结果转换为可选的。如果函数抛出错误,该错误会被抛弃并且结果为 `nil`。否则,结果会是一个包含函数返回值的可选值。
|
||||
|
||||
```swift
|
||||
let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
|
||||
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")
|
||||
```
|
||||
|
||||
使用 `defer` 代码块来表示在函数返回前,函数中最后执行的代码。无论函数是否会抛出错误,这段代码都将执行。使用 `defer`,可以把函数调用之初就要执行的代码和函数调用结束时的扫尾代码写在一起,虽然这两者的执行时机截然不同。
|
||||
|
||||
```swift
|
||||
var fridgeIsOpen = false
|
||||
let fridgeContent = ["milk", "eggs", "leftovers"]
|
||||
|
||||
func fridgeContains(_ food: String) -> Bool {
|
||||
fridgeIsOpen = true
|
||||
defer {
|
||||
fridgeIsOpen = false
|
||||
}
|
||||
|
||||
let result = fridgeContent.contains(food)
|
||||
return result
|
||||
}
|
||||
fridgeContains("banana")
|
||||
print(fridgeIsOpen)
|
||||
```
|
||||
|
||||
## 泛型 {#generics}
|
||||
|
||||
在尖括号里写一个名字来创建一个泛型函数或者类型。
|
||||
|
||||
```swift
|
||||
func makeArray<Item>(repeating item: Item, numberOfTimes: Int) -> [Item] {
|
||||
var result: [Item] = []
|
||||
for _ in 0..<numberOfTimes {
|
||||
result.append(item)
|
||||
}
|
||||
return result
|
||||
}
|
||||
makeArray(repeating: "knock", numberOfTimes: 4)
|
||||
```
|
||||
|
||||
你也可以创建泛型函数、方法、类、枚举和结构体。
|
||||
|
||||
```swift
|
||||
// 重新实现 Swift 标准库中的可选类型
|
||||
enum OptionalValue<Wrapped> {
|
||||
case none
|
||||
case some(Wrapped)
|
||||
}
|
||||
var possibleInteger: OptionalValue<Int> = .none
|
||||
possibleInteger = .some(100)
|
||||
```
|
||||
|
||||
在类型名后面使用 `where` 来指定对类型的一系列需求,比如,限定类型实现某一个协议,限定两个类型是相同的,或者限定某个类必须有一个特定的父类。
|
||||
|
||||
```swift
|
||||
func anyCommonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> Bool
|
||||
where T.Element: Equatable, T.Element == U.Element
|
||||
{
|
||||
for lhsItem in lhs {
|
||||
for rhsItem in rhs {
|
||||
if lhsItem == rhsItem {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
anyCommonElements([1, 2, 3], [3])
|
||||
```
|
||||
|
||||
> 练习
|
||||
>
|
||||
> 修改 `anyCommonElements(_:_:)` 函数来创建一个函数,返回一个数组,内容是两个序列的共有元素。
|
||||
|
||||
`<T: Equatable>` 和 `<T> ... where T: Equatable>` 的写法是等价的。
|
||||
@ -1,4 +1,3 @@
|
||||
# 欢迎使用 Swift
|
||||
|
||||
在本章中您将了解 Swift 的特性和开发历史,并对 Swift 有一个初步的了解。
|
||||
|
||||
773
source/02_language_guide/01_The_Basics.md
Executable file
773
source/02_language_guide/01_The_Basics.md
Executable file
@ -0,0 +1,773 @@
|
||||
# 基础部分
|
||||
|
||||
Swift 是一门开发 iOS, macOS, watchOS 和 tvOS 应用的新语言。然而,如果你有 C 或者 Objective-C 开发经验的话,你会发现 Swift 的很多内容都是你熟悉的。
|
||||
|
||||
Swift 包含了 C 和 Objective-C 上所有基础数据类型,`Int` 表示整型值; `Double` 和 `Float` 表示浮点型值; `Bool` 是布尔型值;`String` 是文本型数据。 Swift 还提供了三个基本的集合类型,`Array`、`Set` 和 `Dictionary` ,详见 [集合类型](./04_Collection_Types.md)。
|
||||
|
||||
就像 C 语言一样,Swift 使用变量来进行存储并通过变量名来关联值。在 Swift 中,广泛的使用着值不可变的变量,它们就是常量,而且比 C 语言的常量更强大。在 Swift 中,如果你要处理的值不需要改变,那使用常量可以让你的代码更加安全并且更清晰地表达你的意图。
|
||||
|
||||
除了我们熟悉的类型,Swift 还增加了 Objective-C 中没有的高阶数据类型比如元组(Tuple)。元组可以让你创建或者传递一组数据,比如作为函数的返回值时,你可以用一个元组可以返回多个值。
|
||||
|
||||
Swift 还增加了可选(Optional)类型,用于处理值缺失的情况。可选表示 “那儿有一个值,并且它等于 *x* ” 或者 “那儿没有值” 。可选有点像在 Objective-C 中使用 `nil` ,但是它可以用在任何类型上,不仅仅是类。可选类型比 Objective-C 中的 `nil` 指针更加安全也更具表现力,它是 Swift 许多强大特性的重要组成部分。
|
||||
|
||||
Swift 是一门*类型安全*的语言,这意味着 Swift 可以让你清楚地知道值的类型。如果你的代码需要一个 `String` ,类型安全会阻止你不小心传入一个 `Int` 。同样的,如果你的代码需要一个 `String`,类型安全会阻止你意外传入一个可选的 `String` 。类型安全可以帮助你在开发阶段尽早发现并修正错误。
|
||||
|
||||
## 常量和变量 {#constants-and-variables}
|
||||
|
||||
常量和变量把一个名字(比如 `maximumNumberOfLoginAttempts` 或者 `welcomeMessage` )和一个指定类型的值(比如数字 `10` 或者字符串 `"Hello"` )关联起来。*常量*的值一旦设定就不能改变,而*变量*的值可以随意更改。
|
||||
|
||||
### 声明常量和变量 {#declaring}
|
||||
|
||||
常量和变量必须在使用前声明,用 `let` 来声明常量,用 `var` 来声明变量。下面的例子展示了如何用常量和变量来记录用户尝试登录的次数:
|
||||
|
||||
```swift
|
||||
let maximumNumberOfLoginAttempts = 10
|
||||
var currentLoginAttempt = 0
|
||||
```
|
||||
|
||||
这两行代码可以被理解为:
|
||||
|
||||
“声明一个名字是 `maximumNumberOfLoginAttempts` 的新常量,并给它一个值 `10` 。然后,声明一个名字是 `currentLoginAttempt` 的变量并将它的值初始化为 `0` 。”
|
||||
|
||||
在这个例子中,允许的最大尝试登录次数被声明为一个常量,因为这个值不会改变。当前尝试登录次数被声明为一个变量,因为每次尝试登录失败的时候都需要增加这个值。
|
||||
|
||||
你可以在一行中声明多个常量或者多个变量,用逗号隔开:
|
||||
|
||||
```swift
|
||||
var x = 0.0, y = 0.0, z = 0.0
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果你的代码中有不需要改变的值,请使用 `let` 关键字将它声明为常量。只将需要改变的值声明为变量。
|
||||
|
||||
### 类型注解 {#type-annotations}
|
||||
|
||||
当你声明常量或者变量的时候可以加上*类型注解(type annotation)*,说明常量或者变量中要存储的值的类型。如果要添加类型注解,需要在常量或者变量名后面加上一个冒号和空格,然后加上类型名称。
|
||||
|
||||
这个例子给 `welcomeMessage` 变量添加了类型注解,表示这个变量可以存储 `String` 类型的值:
|
||||
|
||||
```swift
|
||||
var welcomeMessage: String
|
||||
```
|
||||
|
||||
声明中的冒号代表着*“是...类型”*,所以这行代码可以被理解为:
|
||||
|
||||
“声明一个类型为 `String` ,名字为 `welcomeMessage` 的变量。”
|
||||
|
||||
“类型为 `String` ”的意思是“可以存储任意 `String` 类型的值。”
|
||||
|
||||
`welcomeMessage` 变量现在可以被设置成任意字符串:
|
||||
|
||||
```swift
|
||||
welcomeMessage = "Hello"
|
||||
```
|
||||
|
||||
你可以在一行中定义多个同样类型的变量,用逗号分割,并在最后一个变量名之后添加类型注解:
|
||||
|
||||
```swift
|
||||
var red, green, blue: Double
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 一般来说你很少需要写类型注解。如果你在声明常量或者变量的时候赋了一个初始值,Swift 可以推断出这个常量或者变量的类型,请参考 [类型安全和类型推断](#type-safety-and-type-inference)。在上面的例子中,没有给 `welcomeMessage` 赋初始值,所以变量 `welcomeMessage` 的类型是通过一个类型注解指定的,而不是通过初始值推断的。
|
||||
|
||||
### 常量和变量的命名 {#naming}
|
||||
|
||||
常量和变量名可以包含几乎所有的字符,包括 Unicode 字符:
|
||||
|
||||
```swift
|
||||
let π = 3.14159
|
||||
let 你好 = "你好世界"
|
||||
let 🐶🐮 = "dogcow"
|
||||
```
|
||||
|
||||
常量与变量名不能包含数学符号,箭头,保留的(或者非法的)Unicode 码位,连线与制表符。也不能以数字开头,但是可以在常量与变量名的其他地方包含数字。
|
||||
|
||||
一旦你将常量或者变量声明为确定的类型,你就不能使用相同的名字再次进行声明,或者改变其存储的值的类型。同时,你也不能将常量与变量进行互转。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果你需要使用与 Swift 保留关键字相同的名称作为常量或者变量名,你可以使用反引号(`)将关键字包围的方式将其作为名字使用。无论如何,你应当避免使用关键字作为常量或变量名,除非你别无选择。
|
||||
|
||||
你可以更改现有的变量值为其他同类型的值,在下面的例子中,`friendlyWelcome` 的值从 `"Hello!"` 改为了 `"Bonjour!"`:
|
||||
|
||||
```swift
|
||||
var friendlyWelcome = "Hello!"
|
||||
friendlyWelcome = "Bonjour!"
|
||||
// friendlyWelcome 现在是 "Bonjour!"
|
||||
```
|
||||
|
||||
与变量不同,常量的值一旦被确定就不能更改了。尝试这样做会导致编译时报错:
|
||||
|
||||
```swift
|
||||
let languageName = "Swift"
|
||||
languageName = "Swift++"
|
||||
// 这会报编译时错误 - languageName 不可改变
|
||||
```
|
||||
|
||||
### 输出常量和变量 {#printing}
|
||||
|
||||
你可以用 `print(_:separator:terminator:)` 函数来输出当前常量或变量的值:
|
||||
|
||||
```swift
|
||||
print(friendlyWelcome)
|
||||
// 输出“Bonjour!”
|
||||
```
|
||||
|
||||
`print(_:separator:terminator:)` 是一个用来输出一个或多个值到适当输出区的全局函数。如果你用 Xcode,`print(_:separator:terminator:)` 将会输出内容到“console”面板上。`separator` 和 `terminator` 参数具有默认值,因此你调用这个函数的时候可以忽略它们。默认情况下,该函数通过添加换行符来结束当前行。如果不想换行,可以传递一个空字符串给 `terminator` 参数--例如,`print(someValue, terminator:"")` 。关于参数默认值的更多信息,请参考 [默认参数值](./06_Functions.md#default-parameter-values)。
|
||||
|
||||
Swift 用*字符串插值(string interpolation)*的方式把常量名或者变量名当做占位符加入到长字符串中,Swift 会用当前常量或变量的值替换这些占位符。将常量或变量名放入圆括号中,并在开括号前使用反斜杠将其转义:
|
||||
|
||||
```swift
|
||||
print("The current value of friendlyWelcome is \(friendlyWelcome)")
|
||||
// 输出“The current value of friendlyWelcome is Bonjour!”
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 字符串插值所有可用的选项,请参考 [字符串插值](./03_Strings_and_Characters.md#string-interpolation)。
|
||||
|
||||
## 注释 {#comments}
|
||||
|
||||
请将你的代码中的非执行文本注释成提示或者笔记以方便你将来阅读。Swift 的编译器将会在编译代码时自动忽略掉注释部分。
|
||||
|
||||
Swift 中的注释与 C 语言的注释非常相似。单行注释以双正斜杠(`//`)作为起始标记:
|
||||
|
||||
```swift
|
||||
// 这是一个注释
|
||||
```
|
||||
|
||||
你也可以进行多行注释,其起始标记为单个正斜杠后跟随一个星号(`/*`),终止标记为一个星号后跟随单个正斜杠(`*/`):
|
||||
|
||||
```swift
|
||||
/* 这也是一个注释,
|
||||
但是是多行的 */
|
||||
```
|
||||
|
||||
与 C 语言多行注释不同,Swift 的多行注释可以嵌套在其它的多行注释之中。你可以先生成一个多行注释块,然后在这个注释块之中再嵌套成第二个多行注释。终止注释时先插入第二个注释块的终止标记,然后再插入第一个注释块的终止标记:
|
||||
|
||||
```swift
|
||||
/* 这是第一个多行注释的开头
|
||||
/* 这是第二个被嵌套的多行注释 */
|
||||
这是第一个多行注释的结尾 */
|
||||
```
|
||||
|
||||
通过运用嵌套多行注释,你可以快速方便的注释掉一大段代码,即使这段代码之中已经含有了多行注释块。
|
||||
|
||||
## 分号 {#semicolons}
|
||||
|
||||
与其他大部分编程语言不同,Swift 并不强制要求你在每条语句的结尾处使用分号(`;`),当然,你也可以按照你自己的习惯添加分号。有一种情况下必须要用分号,即你打算在同一行内写多条独立的语句:
|
||||
|
||||
```swift
|
||||
let cat = "🐱"; print(cat)
|
||||
// 输出“🐱”
|
||||
```
|
||||
|
||||
## 整数 {#integers}
|
||||
|
||||
整数就是没有小数部分的数字,比如 `42` 和 `-23` 。整数可以是 `有符号`(正、负、零)或者 `无符号`(正、零)。
|
||||
|
||||
Swift 提供了8、16、32和64位的有符号和无符号整数类型。这些整数类型和 C 语言的命名方式很像,比如8位无符号整数类型是 `UInt8`,32位有符号整数类型是 `Int32` 。就像 Swift 的其他类型一样,整数类型采用大写命名法。
|
||||
|
||||
### 整数范围 {#integer-bounds}
|
||||
|
||||
你可以访问不同整数类型的 `min` 和 `max` 属性来获取对应类型的最小值和最大值:
|
||||
|
||||
```swift
|
||||
let minValue = UInt8.min // minValue 为 0,是 UInt8 类型
|
||||
let maxValue = UInt8.max // maxValue 为 255,是 UInt8 类型
|
||||
```
|
||||
|
||||
`min` 和 `max` 所传回值的类型,正是其所对的整数类型(如上例 UInt8, 所传回的类型是 UInt8),可用在表达式中相同类型值旁。
|
||||
|
||||
### Int {#Int}
|
||||
|
||||
一般来说,你不需要专门指定整数的长度。Swift 提供了一个特殊的整数类型 `Int`,长度与当前平台的原生字长相同:
|
||||
|
||||
* 在32位平台上,`Int` 和 `Int32` 长度相同。
|
||||
* 在64位平台上,`Int` 和 `Int64` 长度相同。
|
||||
|
||||
除非你需要特定长度的整数,一般来说使用 `Int` 就够了。这可以提高代码一致性和可复用性。即使是在32位平台上,`Int` 可以存储的整数范围也可以达到 `-2,147,483,648` ~ `2,147,483,647`,大多数时候这已经足够大了。
|
||||
|
||||
### UInt {#UInt}
|
||||
|
||||
Swift 也提供了一个特殊的无符号类型 `UInt`,长度与当前平台的原生字长相同:
|
||||
|
||||
* 在32位平台上,`UInt` 和 `UInt32` 长度相同。
|
||||
* 在64位平台上,`UInt` 和 `UInt64` 长度相同。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 尽量不要使用 `UInt`,除非你真的需要存储一个和当前平台原生字长相同的无符号整数。除了这种情况,最好使用 `Int`,即使你要存储的值已知是非负的。统一使用 `Int` 可以提高代码的可复用性,避免不同类型数字之间的转换,并且匹配数字的类型推断,请参考 [类型安全和类型推断](#type-safety-and-type-inference)。
|
||||
|
||||
## 浮点数 {#floating-point-numbers}
|
||||
|
||||
浮点数是有小数部分的数字,比如 `3.14159`、`0.1` 和 `-273.15`。
|
||||
|
||||
浮点类型比整数类型表示的范围更大,可以存储比 `Int` 类型更大或者更小的数字。Swift 提供了两种有符号浮点数类型:
|
||||
|
||||
* `Double` 表示64位浮点数。当你需要存储很大或者很高精度的浮点数时请使用此类型。
|
||||
* `Float` 表示32位浮点数。精度要求不高的话可以使用此类型。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> `Double` 精确度很高,至少有 15 位小数,而 `Float` 只有 6 位小数。选择哪个类型取决于你的代码需要处理的值的范围,在两种类型都匹配的情况下,将优先选择 `Double`。
|
||||
|
||||
## 类型安全和类型推断 {#type-safety-and-type-inference}
|
||||
|
||||
Swift 是一个*类型安全(type safe)*的语言。类型安全的语言可以让你清楚地知道代码要处理的值的类型。如果你的代码需要一个 `String`,你绝对不可能不小心传进去一个 `Int`。
|
||||
|
||||
由于 Swift 是类型安全的,所以它会在编译你的代码时进行*类型检查(type checks)*,并把不匹配的类型标记为错误。这可以让你在开发的时候尽早发现并修复错误。
|
||||
|
||||
当你要处理不同类型的值时,类型检查可以帮你避免错误。然而,这并不是说你每次声明常量和变量的时候都需要显式指定类型。如果你没有显式指定类型,Swift 会使用*类型推断(type inference)*来选择合适的类型。有了类型推断,编译器可以在编译代码的时候自动推断出表达式的类型。原理很简单,只要检查你赋的值即可。
|
||||
|
||||
因为有类型推断,和 C 或者 Objective-C 比起来 Swift 很少需要声明类型。常量和变量虽然需要明确类型,但是大部分工作并不需要你自己来完成。
|
||||
|
||||
当你声明常量或者变量并赋初值的时候类型推断非常有用。当你在声明常量或者变量的时候赋给它们一个字面量(literal value 或 literal)即可触发类型推断。(字面量就是会直接出现在你代码中的值,比如 `42` 和 `3.14159` 。)
|
||||
|
||||
例如,如果你给一个新常量赋值 `42` 并且没有标明类型,Swift 可以推断出常量类型是 `Int` ,因为你给它赋的初始值看起来像一个整数:
|
||||
|
||||
```swift
|
||||
let meaningOfLife = 42
|
||||
// meaningOfLife 会被推测为 Int 类型
|
||||
```
|
||||
|
||||
同理,如果你没有给浮点字面量标明类型,Swift 会推断你想要的是 `Double`:
|
||||
|
||||
```swift
|
||||
let pi = 3.14159
|
||||
// pi 会被推测为 Double 类型
|
||||
```
|
||||
|
||||
当推断浮点数的类型时,Swift 总是会选择 `Double` 而不是 `Float`。
|
||||
|
||||
如果表达式中同时出现了整数和浮点数,会被推断为 `Double` 类型:
|
||||
|
||||
```swift
|
||||
let anotherPi = 3 + 0.14159
|
||||
// anotherPi 会被推测为 Double 类型
|
||||
```
|
||||
|
||||
原始值 `3` 没有显式声明类型,而表达式中出现了一个浮点字面量,所以表达式会被推断为 `Double` 类型。
|
||||
|
||||
## 数值型字面量 {#numeric-literals}
|
||||
|
||||
整数字面量可以被写作:
|
||||
|
||||
* 一个*十进制*数,没有前缀
|
||||
* 一个*二进制*数,前缀是 `0b`
|
||||
* 一个*八进制*数,前缀是 `0o`
|
||||
* 一个*十六进制*数,前缀是 `0x`
|
||||
|
||||
下面的所有整数字面量的十进制值都是 `17`:
|
||||
|
||||
```swift
|
||||
let decimalInteger = 17
|
||||
let binaryInteger = 0b10001 // 二进制的17
|
||||
let octalInteger = 0o21 // 八进制的17
|
||||
let hexadecimalInteger = 0x11 // 十六进制的17
|
||||
```
|
||||
|
||||
浮点字面量可以是十进制(没有前缀)或者是十六进制(前缀是 `0x` )。小数点两边必须有至少一个十进制数字(或者是十六进制的数字)。十进制浮点数也可以有一个可选的指数(exponent),通过大写或者小写的 `e` 来指定;十六进制浮点数必须有一个指数,通过大写或者小写的 `p` 来指定。
|
||||
|
||||
如果一个十进制数的指数为 `exp`,那这个数相当于基数和10^exp 的乘积:
|
||||
|
||||
* `1.25e2` 表示 1.25 × 10^2,等于 `125.0`。
|
||||
* `1.25e-2` 表示 1.25 × 10^-2,等于 `0.0125`。
|
||||
|
||||
如果一个十六进制数的指数为 `exp`,那这个数相当于基数和2^exp 的乘积:
|
||||
|
||||
* `0xFp2` 表示 15 × 2^2,等于 `60.0`。
|
||||
* `0xFp-2` 表示 15 × 2^-2,等于 `3.75`。
|
||||
|
||||
下面的这些浮点字面量都等于十进制的 `12.1875`:
|
||||
|
||||
```swift
|
||||
let decimalDouble = 12.1875
|
||||
let exponentDouble = 1.21875e1
|
||||
let hexadecimalDouble = 0xC.3p0
|
||||
```
|
||||
|
||||
数值类字面量可以包括额外的格式来增强可读性。整数和浮点数都可以添加额外的零并且包含下划线,并不会影响字面量:
|
||||
|
||||
```swift
|
||||
let paddedDouble = 000123.456
|
||||
let oneMillion = 1_000_000
|
||||
let justOverOneMillion = 1_000_000.000_000_1
|
||||
```
|
||||
|
||||
## 数值型类型转换 {#numeric-type-conversion}
|
||||
|
||||
通常来讲,即使代码中的整数常量和变量已知非负,也请使用 `Int` 类型。总是使用默认的整数类型可以保证你的整数常量和变量可以直接被复用并且可以匹配整数类字面量的类型推断。
|
||||
|
||||
只有在必要的时候才使用其他整数类型,比如要处理外部的长度明确的数据或者为了优化性能、内存占用等等。使用显式指定长度的类型可以及时发现值溢出并且可以暗示正在处理特殊数据。
|
||||
|
||||
### 整数转换 {#integer-conversion}
|
||||
|
||||
不同整数类型的变量和常量可以存储不同范围的数字。`Int8` 类型的常量或者变量可以存储的数字范围是 `-128`~`127`,而 `UInt8` 类型的常量或者变量能存储的数字范围是 `0`~`255`。如果数字超出了常量或者变量可存储的范围,编译的时候会报错:
|
||||
|
||||
```swift
|
||||
let cannotBeNegative: UInt8 = -1
|
||||
// UInt8 类型不能存储负数,所以会报错
|
||||
let tooBig: Int8 = Int8.max + 1
|
||||
// Int8 类型不能存储超过最大值的数,所以会报错
|
||||
```
|
||||
|
||||
由于每种整数类型都可以存储不同范围的值,所以你必须根据不同情况选择性使用数值型类型转换。这种选择性使用的方式,可以预防隐式转换的错误并让你的代码中的类型转换意图变得清晰。
|
||||
|
||||
要将一种数字类型转换成另一种,你要用当前值来初始化一个期望类型的新数字,这个数字的类型就是你的目标类型。在下面的例子中,常量 `twoThousand` 是 `UInt16` 类型,然而常量 `one` 是 `UInt8` 类型。它们不能直接相加,因为它们类型不同。所以要调用 `UInt16(one)` 来创建一个新的 `UInt16` 数字并用 `one` 的值来初始化,然后使用这个新数字来计算:
|
||||
|
||||
```swift
|
||||
let twoThousand: UInt16 = 2_000
|
||||
let one: UInt8 = 1
|
||||
let twoThousandAndOne = twoThousand + UInt16(one)
|
||||
```
|
||||
|
||||
现在两个数字的类型都是 `UInt16`,可以进行相加。目标常量 `twoThousandAndOne` 的类型被推断为 `UInt16`,因为它是两个 `UInt16` 值的和。
|
||||
|
||||
`SomeType(ofInitialValue)` 是调用 Swift 构造器并传入一个初始值的默认方法。在语言内部,`UInt16` 有一个构造器,可以接受一个 `UInt8` 类型的值,所以这个构造器可以用现有的 `UInt8` 来创建一个新的 `UInt16`。注意,你并不能传入任意类型的值,只能传入 `UInt16` 内部有对应构造器的值。不过你可以扩展现有的类型来让它可以接收其他类型的值(包括自定义类型),请参考 [扩展](./20_Extensions.md)。
|
||||
|
||||
### 整数和浮点数转换 {#integer-and-floating-point-conversion}
|
||||
|
||||
整数和浮点数的转换必须显式指定类型:
|
||||
|
||||
```swift
|
||||
let three = 3
|
||||
let pointOneFourOneFiveNine = 0.14159
|
||||
let pi = Double(three) + pointOneFourOneFiveNine
|
||||
// pi 等于 3.14159,所以被推测为 Double 类型
|
||||
```
|
||||
|
||||
这个例子中,常量 `three` 的值被用来创建一个 `Double` 类型的值,所以加号两边的数类型须相同。如果不进行转换,两者无法相加。
|
||||
|
||||
浮点数到整数的反向转换同样行,整数类型可以用 `Double` 或者 `Float` 类型来初始化:
|
||||
|
||||
```swift
|
||||
let integerPi = Int(pi)
|
||||
// integerPi 等于 3,所以被推测为 Int 类型
|
||||
```
|
||||
|
||||
当用这种方式来初始化一个新的整数值时,浮点值会被截断。也就是说 `4.75` 会变成 `4`,`-3.9` 会变成 `-3`。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 结合数字类常量和变量不同于结合数字类字面量。字面量 `3` 可以直接和字面量 `0.14159` 相加,因为数字字面量本身没有明确的类型。它们的类型只在编译器需要求值的时候被推测。
|
||||
|
||||
## 类型别名 {#type-aliases}
|
||||
|
||||
*类型别名(type aliases)*就是给现有类型定义另一个名字。你可以使用 `typealias` 关键字来定义类型别名。
|
||||
|
||||
当你想要给现有类型起一个更有意义的名字时,类型别名非常有用。假设你正在处理特定长度的外部资源的数据:
|
||||
|
||||
```swift
|
||||
typealias AudioSample = UInt16
|
||||
```
|
||||
|
||||
定义了一个类型别名之后,你可以在任何使用原始名的地方使用别名:
|
||||
|
||||
```swift
|
||||
var maxAmplitudeFound = AudioSample.min
|
||||
// maxAmplitudeFound 现在是 0
|
||||
```
|
||||
|
||||
本例中,`AudioSample` 被定义为 `UInt16` 的一个别名。因为它是别名,`AudioSample.min` 实际上是 `UInt16.min`,所以会给 `maxAmplitudeFound` 赋一个初值 `0`。
|
||||
|
||||
## 布尔值 {#booleans}
|
||||
|
||||
Swift 有一个基本的*布尔(Boolean)类型*,叫做 `Bool`。布尔值指*逻辑*上的值,因为它们只能是真或者假。Swift 有两个布尔常量,`true` 和 `false`:
|
||||
|
||||
```swift
|
||||
let orangesAreOrange = true
|
||||
let turnipsAreDelicious = false
|
||||
```
|
||||
|
||||
`orangesAreOrange` 和 `turnipsAreDelicious` 的类型会被推断为 `Bool`,因为它们的初值是布尔字面量。就像之前提到的 `Int` 和 `Double` 一样,如果你创建变量的时候给它们赋值 `true` 或者 `false`,那你不需要将常量或者变量声明为 `Bool` 类型。初始化常量或者变量的时候如果所赋的值类型已知,就可以触发类型推断,这让 Swift 代码更加简洁并且可读性更高。
|
||||
|
||||
当你编写条件语句比如 `if` 语句的时候,布尔值非常有用:
|
||||
|
||||
```swift
|
||||
if turnipsAreDelicious {
|
||||
print("Mmm, tasty turnips!")
|
||||
} else {
|
||||
print("Eww, turnips are horrible.")
|
||||
}
|
||||
// 输出“Eww, turnips are horrible.”
|
||||
```
|
||||
|
||||
条件语句,例如 `if`,请参考 [控制流](./05_Control_Flow.md)。
|
||||
|
||||
如果你在需要使用 `Bool` 类型的地方使用了非布尔值,Swift 的类型安全机制会报错。下面的例子会报告一个编译时错误:
|
||||
|
||||
```swift
|
||||
let i = 1
|
||||
if i {
|
||||
// 这个例子不会通过编译,会报错
|
||||
}
|
||||
```
|
||||
|
||||
然而,下面的例子是合法的:
|
||||
|
||||
```swift
|
||||
let i = 1
|
||||
if i == 1 {
|
||||
// 这个例子会编译成功
|
||||
}
|
||||
```
|
||||
|
||||
`i == 1` 的比较结果是 `Bool` 类型,所以第二个例子可以通过类型检查。类似 `i == 1` 这样的比较,请参考 [基本操作符](./05_Control_Flow.md)。
|
||||
|
||||
和 Swift 中的其他类型安全的例子一样,这个方法可以避免错误并保证这块代码的意图总是清晰的。
|
||||
|
||||
## 元组 {#tuples}
|
||||
|
||||
*元组(tuples)*把多个值组合成一个复合值。元组内的值可以是任意类型,并不要求是相同类型。
|
||||
|
||||
下面这个例子中,`(404, "Not Found")` 是一个描述 *HTTP 状态码(HTTP status code)*的元组。HTTP 状态码是当你请求网页的时候 web 服务器返回的一个特殊值。如果你请求的网页不存在就会返回一个 `404 Not Found` 状态码。
|
||||
|
||||
```swift
|
||||
let http404Error = (404, "Not Found")
|
||||
// http404Error 的类型是 (Int, String),值是 (404, "Not Found")
|
||||
```
|
||||
|
||||
`(404, "Not Found")` 元组把一个 `Int` 值和一个 `String` 值组合起来表示 HTTP 状态码的两个部分:一个数字和一个人类可读的描述。这个元组可以被描述为“一个类型为 `(Int, String)` 的元组”。
|
||||
|
||||
你可以把任意顺序的类型组合成一个元组,这个元组可以包含所有类型。只要你想,你可以创建一个类型为 `(Int, Int, Int)` 或者 `(String, Bool)` 或者其他任何你想要的组合的元组。
|
||||
|
||||
你可以将一个元组的内容分解(decompose)成单独的常量和变量,然后你就可以正常使用它们了:
|
||||
|
||||
```swift
|
||||
let (statusCode, statusMessage) = http404Error
|
||||
print("The status code is \(statusCode)")
|
||||
// 输出“The status code is 404”
|
||||
print("The status message is \(statusMessage)")
|
||||
// 输出“The status message is Not Found”
|
||||
```
|
||||
|
||||
如果你只需要一部分元组值,分解的时候可以把要忽略的部分用下划线(`_`)标记:
|
||||
|
||||
```swift
|
||||
let (justTheStatusCode, _) = http404Error
|
||||
print("The status code is \(justTheStatusCode)")
|
||||
// 输出“The status code is 404”
|
||||
```
|
||||
|
||||
此外,你还可以通过下标来访问元组中的单个元素,下标从零开始:
|
||||
|
||||
```swift
|
||||
print("The status code is \(http404Error.0)")
|
||||
// 输出“The status code is 404”
|
||||
print("The status message is \(http404Error.1)")
|
||||
// 输出“The status message is Not Found”
|
||||
```
|
||||
|
||||
你可以在定义元组的时候给单个元素命名:
|
||||
|
||||
```swift
|
||||
let http200Status = (statusCode: 200, description: "OK")
|
||||
```
|
||||
|
||||
给元组中的元素命名后,你可以通过名字来获取这些元素的值:
|
||||
|
||||
```swift
|
||||
print("The status code is \(http200Status.statusCode)")
|
||||
// 输出“The status code is 200”
|
||||
print("The status message is \(http200Status.description)")
|
||||
// 输出“The status message is OK”
|
||||
```
|
||||
|
||||
作为函数返回值时,元组非常有用。一个用来获取网页的函数可能会返回一个 `(Int, String)` 元组来描述是否获取成功。和只能返回一个类型的值比较起来,一个包含两个不同类型值的元组可以让函数的返回信息更有用。请参考 [函数参数与返回值](./06_Functions.md#Function-Parameters-and-Return-Values)。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 当遇到一些相关值的简单分组时,元组是很有用的。元组不适合用来创建复杂的数据结构。如果你的数据结构比较复杂,不要使用元组,用类或结构体去建模。欲获得更多信息,请参考 [结构体和类](./09_Structures_And_Classes.md)。
|
||||
|
||||
## 可选类型 {#optionals}
|
||||
|
||||
使用*可选类型(optionals)*来处理值可能缺失的情况。可选类型表示两种可能:
|
||||
或者有值, 你可以解析可选类型访问这个值, 或者根本没有值。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> C 和 Objective-C 中并没有可选类型这个概念。最接近的是 Objective-C 中的一个特性,一个方法要不返回一个对象要不返回 `nil`,`nil` 表示“缺少一个合法的对象”。然而,这只对对象起作用——对于结构体,基本的 C 类型或者枚举类型不起作用。对于这些类型,Objective-C 方法一般会返回一个特殊值(比如 `NSNotFound`)来暗示值缺失。这种方法假设方法的调用者知道并记得对特殊值进行判断。然而,Swift 的可选类型可以让你暗示*任意类型*的值缺失,并不需要一个特殊值。
|
||||
|
||||
来看一个例子。Swift 的 `Int` 类型有一种构造器,作用是将一个 `String` 值转换成一个 `Int` 值。然而,并不是所有的字符串都可以转换成一个整数。字符串 `"123"` 可以被转换成数字 `123` ,但是字符串 `"hello, world"` 不行。
|
||||
|
||||
下面的例子使用这种构造器来尝试将一个 `String` 转换成 `Int`:
|
||||
|
||||
```swift
|
||||
let possibleNumber = "123"
|
||||
let convertedNumber = Int(possibleNumber)
|
||||
// convertedNumber 被推测为类型 "Int?", 或者类型 "optional Int"
|
||||
```
|
||||
|
||||
因为该构造器可能会失败,所以它返回一个*可选类型*(optional)`Int`,而不是一个 `Int`。一个可选的 `Int` 被写作 `Int?` 而不是 `Int`。问号暗示包含的值是可选类型,也就是说可能包含 `Int` 值也可能*不包含值*。(不能包含其他任何值比如 `Bool` 值或者 `String` 值。只能是 `Int` 或者什么都没有。)
|
||||
|
||||
### nil {#nil}
|
||||
|
||||
你可以给可选变量赋值为 `nil` 来表示它没有值:
|
||||
|
||||
```swift
|
||||
var serverResponseCode: Int? = 404
|
||||
// serverResponseCode 包含一个可选的 Int 值 404
|
||||
serverResponseCode = nil
|
||||
// serverResponseCode 现在不包含值
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> `nil` 不能用于非可选的常量和变量。如果你的代码中有常量或者变量需要处理值缺失的情况,请把它们声明成对应的可选类型。
|
||||
|
||||
如果你声明一个可选常量或者变量但是没有赋值,它们会自动被设置为 `nil`:
|
||||
|
||||
```swift
|
||||
var surveyAnswer: String?
|
||||
// surveyAnswer 被自动设置为 nil
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> Swift 的 `nil` 和 Objective-C 中的 `nil` 并不一样。在 Objective-C 中,`nil` 是一个指向不存在对象的指针。在 Swift 中,`nil` 不是指针——它是一个确定的值,用来表示值缺失。任何类型的可选状态都可以被设置为 `nil`,不只是对象类型。
|
||||
|
||||
### if 语句以及强制解析 {#if}
|
||||
|
||||
你可以使用 `if` 语句和 `nil` 比较来判断一个可选值是否包含值。你可以使用“相等”(`==`)或“不等”(`!=`)来执行比较。
|
||||
|
||||
如果可选类型有值,它将不等于 `nil`:
|
||||
|
||||
```swift
|
||||
if convertedNumber != nil {
|
||||
print("convertedNumber contains some integer value.")
|
||||
}
|
||||
// 输出“convertedNumber contains some integer value.”
|
||||
```
|
||||
|
||||
当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(`!`)来获取值。这个惊叹号表示“我知道这个可选有值,请使用它。”这被称为可选值的*强制解析(forced unwrapping)*:
|
||||
|
||||
```swift
|
||||
if convertedNumber != nil {
|
||||
print("convertedNumber has an integer value of \(convertedNumber!).")
|
||||
}
|
||||
// 输出“convertedNumber has an integer value of 123.”
|
||||
```
|
||||
|
||||
更多关于 `if` 语句的内容,请参考 [控制流](./05_Control_Flow.md)。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 使用 `!` 来获取一个不存在的可选值会导致运行时错误。使用 `!` 来强制解析值之前,一定要确定可选包含一个非 `nil` 的值。
|
||||
|
||||
### 可选绑定 {#optional-binding}
|
||||
|
||||
使用*可选绑定(optional binding)*来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定可以用在 `if` 和 `while` 语句中,这条语句不仅可以用来判断可选类型中是否有值,同时可以将可选类型中的值赋给一个常量或者变量。`if` 和 `while` 语句,请参考 [控制流](./05_Control_Flow.md)。
|
||||
|
||||
像下面这样在 `if` 语句中写一个可选绑定:
|
||||
|
||||
```swift
|
||||
if let constantName = someOptional {
|
||||
statements
|
||||
}
|
||||
```
|
||||
|
||||
你可以像上面这样使用可选绑定来重写 在 [可选类型](./01_The_Basics.md#optionals) 举出的 `possibleNumber` 例子:
|
||||
|
||||
```swift
|
||||
if let actualNumber = Int(possibleNumber) {
|
||||
print("\'\(possibleNumber)\' has an integer value of \(actualNumber)")
|
||||
} else {
|
||||
print("\'\(possibleNumber)\' could not be converted to an integer")
|
||||
}
|
||||
// 输出“'123' has an integer value of 123”
|
||||
```
|
||||
|
||||
这段代码可以被理解为:
|
||||
|
||||
“如果 `Int(possibleNumber)` 返回的可选 `Int` 包含一个值,创建一个叫做 `actualNumber` 的新常量并将可选包含的值赋给它。”
|
||||
|
||||
如果转换成功,`actualNumber` 常量可以在 `if` 语句的第一个分支中使用。它已经被可选类型 *包含的* 值初始化过,所以不需要再使用 `!` 后缀来获取它的值。在这个例子中,`actualNumber` 只被用来输出转换结果。
|
||||
|
||||
你可以在可选绑定中使用常量和变量。如果你想在 `if` 语句的第一个分支中操作 `actualNumber` 的值,你可以改成 `if var actualNumber`,这样可选类型包含的值就会被赋给一个变量而非常量。
|
||||
|
||||
你可以包含多个可选绑定或多个布尔条件在一个 `if` 语句中,只要使用逗号分开就行。只要有任意一个可选绑定的值为 `nil`,或者任意一个布尔条件为 `false`,则整个 `if` 条件判断为 `false`。下面的两个 `if` 语句是等价的:
|
||||
|
||||
```swift
|
||||
if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 {
|
||||
print("\(firstNumber) < \(secondNumber) < 100")
|
||||
}
|
||||
// 输出“4 < 42 < 100”
|
||||
|
||||
if let firstNumber = Int("4") {
|
||||
if let secondNumber = Int("42") {
|
||||
if firstNumber < secondNumber && secondNumber < 100 {
|
||||
print("\(firstNumber) < \(secondNumber) < 100")
|
||||
}
|
||||
}
|
||||
}
|
||||
// 输出“4 < 42 < 100”
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 在 `if` 条件语句中使用常量和变量来创建一个可选绑定,仅在 `if` 语句的句中(`body`)中才能获取到值。相反,在 `guard` 语句中使用常量和变量来创建一个可选绑定,仅在 `guard` 语句外且在语句后才能获取到值,请参考 [提前退出](./05_Control_Flow.md#early-exit)。
|
||||
|
||||
### 隐式解析可选类型 {#implicityly-unwrapped-optionals}
|
||||
|
||||
如上所述,可选类型暗示了常量或者变量可以“没有值”。可选可以通过 `if` 语句来判断是否有值,如果有值的话可以通过可选绑定来解析值。
|
||||
|
||||
有时候在程序架构中,第一次被赋值之后,可以确定一个可选类型_总会_有值。在这种情况下,每次都要判断和解析可选值是非常低效的,因为可以确定它总会有值。
|
||||
|
||||
这种类型的可选状态被定义为隐式解析可选类型(implicitly unwrapped optionals)。把想要用作可选的类型的后面的问号(`String?`)改成感叹号(`String!`)来声明一个隐式解析可选类型。与其在使用时把感叹号放在可选类型的名称的后面,你可以在定义它时,直接把感叹号放在可选类型的后面。
|
||||
|
||||
当可选类型被第一次赋值之后就可以确定之后一直有值的时候,隐式解析可选类型非常有用。隐式解析可选类型主要被用在 Swift 中类的构造过程中,请参考 [无主引用以及隐式解析可选属性](./24_Automatic_Reference_Counting.md#unowned-references-and-implicitly-unwrapped-optional-properties)。
|
||||
|
||||
一个隐式解析可选类型其实就是一个普通的可选类型,但是可以被当做非可选类型来使用,并不需要每次都使用解析来获取可选值。下面的例子展示了可选类型 `String` 和隐式解析可选类型 `String` 之间的区别:
|
||||
|
||||
```swift
|
||||
let possibleString: String? = "An optional string."
|
||||
let forcedString: String = possibleString! // 需要感叹号来获取值
|
||||
|
||||
let assumedString: String! = "An implicitly unwrapped optional string."
|
||||
let implicitString: String = assumedString // 不需要感叹号
|
||||
```
|
||||
|
||||
你可以把隐式解析可选类型当做一个可以自动解析的可选类型。当你使用一个隐式解析可选值时,Swift 首先会把它当作普通的可选值;如果它不能被当成可选类型使用,Swift 会强制解析可选值。在以上的代码中,可选值 `assumedString` 在把自己的值赋给 `implicitString` 之前会被强制解析,原因是 `implicitString` 本身的类型是非可选类型的 `String`。在下面的代码中,`optionalString` 并没有显式的数据类型。那么根据类型推断,它就是一个普通的可选类型。
|
||||
```swift
|
||||
let optionalString = assumedString
|
||||
// optionalString 的类型是 "String?",assumedString 也没有被强制解析。
|
||||
```
|
||||
|
||||
如果你在隐式解析可选类型没有值的时候尝试取值,会触发运行时错误。和你在没有值的普通可选类型后面加一个感叹号一样。
|
||||
|
||||
你可以把隐式解析可选类型当做普通可选类型来判断它是否包含值:
|
||||
|
||||
```swift
|
||||
if assumedString != nil {
|
||||
print(assumedString!)
|
||||
}
|
||||
// 输出“An implicitly unwrapped optional string.”
|
||||
```
|
||||
|
||||
你也可以在可选绑定中使用隐式解析可选类型来检查并解析它的值:
|
||||
|
||||
```swift
|
||||
if let definiteString = assumedString {
|
||||
print(definiteString)
|
||||
}
|
||||
// 输出“An implicitly unwrapped optional string.”
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果一个变量之后可能变成 `nil` 的话请不要使用隐式解析可选类型。如果你需要在变量的生命周期中判断是否是 `nil` 的话,请使用普通可选类型。
|
||||
|
||||
## 错误处理 {#error-handling}
|
||||
|
||||
你可以使用 *错误处理(error handling)* 来应对程序执行中可能会遇到的错误条件。
|
||||
|
||||
相对于可选中运用值的存在与缺失来表达函数的成功与失败,错误处理可以推断失败的原因,并传播至程序的其他部分。
|
||||
|
||||
当一个函数遇到错误条件,它能报错。调用函数的地方能抛出错误消息并合理处理。
|
||||
|
||||
```swift
|
||||
func canThrowAnError() throws {
|
||||
// 这个函数有可能抛出错误
|
||||
}
|
||||
```
|
||||
|
||||
一个函数可以通过在声明中添加 `throws` 关键词来抛出错误消息。当你的函数能抛出错误消息时,你应该在表达式中前置 `try` 关键词。
|
||||
|
||||
```swift
|
||||
do {
|
||||
try canThrowAnError()
|
||||
// 没有错误消息抛出
|
||||
} catch {
|
||||
// 有一个错误消息抛出
|
||||
}
|
||||
```
|
||||
|
||||
一个 `do` 语句创建了一个新的包含作用域,使得错误能被传播到一个或多个 `catch` 从句。
|
||||
|
||||
这里有一个错误处理如何用来应对不同错误条件的例子。
|
||||
|
||||
```swift
|
||||
func makeASandwich() throws {
|
||||
// ...
|
||||
}
|
||||
|
||||
do {
|
||||
try makeASandwich()
|
||||
eatASandwich()
|
||||
} catch SandwichError.outOfCleanDishes {
|
||||
washDishes()
|
||||
} catch SandwichError.missingIngredients(let ingredients) {
|
||||
buyGroceries(ingredients)
|
||||
}
|
||||
```
|
||||
|
||||
在此例中,`makeASandwich()`(做一个三明治)函数会抛出一个错误消息如果没有干净的盘子或者某个原料缺失。因为 `makeASandwich()` 抛出错误,函数调用被包裹在 `try` 表达式中。将函数包裹在一个 `do` 语句中,任何被抛出的错误会被传播到提供的 `catch` 从句中。
|
||||
|
||||
如果没有错误被抛出,`eatASandwich()` 函数会被调用。如果一个匹配 `SandwichError.outOfCleanDishes` 的错误被抛出,`washDishes()` 函数会被调用。如果一个匹配 `SandwichError.missingIngredients` 的错误被抛出,`buyGroceries(_:)` 函数会被调用,并且使用 `catch` 所捕捉到的关联值 `[String]` 作为参数。
|
||||
|
||||
抛出,捕捉,以及传播错误会在 [错误处理](./17_Error_Handling.md) 章节详细说明。
|
||||
|
||||
## 断言和先决条件 {#assertions-and-preconditions}
|
||||
|
||||
断言和先决条件是在运行时所做的检查。你可以用他们来检查在执行后续代码之前是否一个必要的条件已经被满足了。如果断言或者先决条件中的布尔条件评估的结果为 true(真),则代码像往常一样继续执行。如果布尔条件评估结果为 false(假),程序的当前状态是无效的,则代码执行结束,应用程序中止。
|
||||
|
||||
你使用断言和先决条件来表达你所做的假设和你在编码时候的期望。你可以将这些包含在你的代码中。断言帮助你在开发阶段找到错误和不正确的假设,先决条件帮助你在生产环境中探测到存在的问题。
|
||||
|
||||
除了在运行时验证你的期望值,断言和先决条件也变成了一个在你的代码中的有用的文档形式。和在上面讨论过的 [错误处理](./17_Error_Handling.md) 不同,断言和先决条件并不是用来处理可以恢复的或者可预期的错误。因为一个断言失败表明了程序正处于一个无效的状态,没有办法去捕获一个失败的断言。
|
||||
|
||||
使用断言和先决条件不是一个能够避免出现程序出现无效状态的编码方法。然而,如果一个无效状态程序产生了,断言和先决条件可以强制检查你的数据和程序状态,使得你的程序可预测的中止(译者:不是系统强制的,被动的中止),并帮助使这个问题更容易调试。一旦探测到无效的状态,执行则被中止,防止无效的状态导致的进一步对于系统的伤害。
|
||||
|
||||
断言和先决条件的不同点是,他们什么时候进行状态检测:断言仅在调试环境运行,而先决条件则在调试环境和生产环境中运行。在生产环境中,断言的条件将不会进行评估。这个意味着你可以使用很多断言在你的开发阶段,但是这些断言在生产环境中不会产生任何影响。
|
||||
|
||||
### 使用断言进行调试 {#debugging-with-assertions}
|
||||
|
||||
你可以调用 Swift 标准库的 `assert(_:_:file:line:)` 函数来写一个断言。向这个函数传入一个结果为 `true` 或者 `false` 的表达式以及一条信息,当表达式的结果为 `false` 的时候这条信息会被显示:
|
||||
|
||||
```swift
|
||||
let age = -3
|
||||
assert(age >= 0, "A person's age cannot be less than zero")
|
||||
// 因为 age < 0,所以断言会触发
|
||||
```
|
||||
|
||||
在这个例子中,只有 `age >= 0` 为 `true` 时,即 `age` 的值非负的时候,代码才会继续执行。如果 `age` 的值是负数,就像代码中那样,`age >= 0` 为 `false`,断言被触发,终止应用。
|
||||
|
||||
如果不需要断言信息,可以就像这样忽略掉:
|
||||
|
||||
```swift
|
||||
assert(age >= 0)
|
||||
```
|
||||
|
||||
如果代码已经检查了条件,你可以使用 `assertionFailure(_:file:line:)` 函数来表明断言失败了,例如:
|
||||
|
||||
```swift
|
||||
if age > 10 {
|
||||
print("You can ride the roller-coaster or the ferris wheel.")
|
||||
} else if age > 0 {
|
||||
print("You can ride the ferris wheel.")
|
||||
} else {
|
||||
assertionFailure("A person's age can't be less than zero.")
|
||||
}
|
||||
```
|
||||
|
||||
### 强制执行先决条件 {#enforcing-preconditions}
|
||||
|
||||
当一个条件可能为假,但是继续执行代码要求条件必须为真的时候,需要使用先决条件。例如使用先决条件来检查是否下标越界,或者来检查是否将一个正确的参数传给函数。
|
||||
|
||||
你可以使用全局 `precondition(_:_:file:line:)` 函数来写一个先决条件。向这个函数传入一个结果为 `true` 或者 `false` 的表达式以及一条信息,当表达式的结果为 `false` 的时候这条信息会被显示:
|
||||
|
||||
```swift
|
||||
// 在一个下标的实现里...
|
||||
precondition(index > 0, "Index must be greater than zero.")
|
||||
```
|
||||
|
||||
你可以调用 `preconditionFailure(_:file:line:)` 方法来表明出现了一个错误,例如,switch 进入了 default 分支,但是所有的有效值应该被任意一个其他分支(非 default 分支)处理。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果你使用 unchecked 模式(-Ounchecked)编译代码,先决条件将不会进行检查。编译器假设所有的先决条件总是为 true(真),他将优化你的代码。然而,`fatalError(_:file:line:)` 函数总是中断执行,无论你怎么进行优化设定。
|
||||
>
|
||||
> 你能使用 `fatalError(_:file:line:)` 函数在设计原型和早期开发阶段,这个阶段只有方法的声明,但是没有具体实现,你可以在方法体中写上 fatalError("Unimplemented")作为具体实现。因为 fatalError 不会像断言和先决条件那样被优化掉,所以你可以确保当代码执行到一个没有被实现的方法时,程序会被中断。
|
||||
479
source/02_language_guide/02_Basic_Operators.md
Executable file
479
source/02_language_guide/02_Basic_Operators.md
Executable file
@ -0,0 +1,479 @@
|
||||
# 基本运算符
|
||||
|
||||
*运算符*是检查、改变、合并值的特殊符号或短语。例如,加号(`+`)将两个数相加(如 `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 = (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!”
|
||||
```
|
||||
|
||||
这括号使得前两个值被看成整个逻辑表达中独立的一个部分。虽然有括号和没括号的输出结果是一样的,但对于读代码的人来说有括号的代码更清晰。可读性比简洁性更重要,请在可以让你代码变清晰的地方加个括号吧!
|
||||
749
source/02_language_guide/03_Strings_and_Characters.md
Executable file
749
source/02_language_guide/03_Strings_and_Characters.md
Executable file
@ -0,0 +1,749 @@
|
||||
# 字符串和字符
|
||||
|
||||
*字符串*是一系列字符的集合,例如 `"hello, world"`,`"albatross"`。Swift 的字符串通过 `String` 类型来表示。而 `String` 内容的访问方式有多种,例如以 `Character` 值的集合。
|
||||
|
||||
Swift 的 `String` 和 `Character` 类型提供了一种快速且兼容 Unicode 的方式来处理代码中的文本内容。创建和操作字符串的语法与 C 语言中字符串操作相似,轻量并且易读。通过 `+` 符号就可以非常简单的实现两个字符串的拼接操作。与 Swift 中其他值一样,能否更改字符串的值,取决于其被定义为常量还是变量。你可以在已有字符串中插入常量、变量、字面量和表达式从而形成更长的字符串,这一过程也被称为字符串插值。尤其是在为显示、存储和打印创建自定义字符串值时,字符串插值操作尤其有用。
|
||||
|
||||
尽管语法简易,但 Swift 中的 `String` 类型的实现却很快速和现代化。每一个字符串都是由编码无关的 Unicode 字符组成,并支持访问字符的多种 Unicode 表示形式。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> Swift 的 `String` 类型与 Foundation `NSString` 类进行了无缝桥接。Foundation 还对 `String` 进行扩展使其可以访问 `NSString` 类型中定义的方法。这意味着调用那些 `NSString` 的方法,你无需进行任何类型转换。
|
||||
>
|
||||
> 更多关于在 Foundation 和 Cocoa 中使用 `String` 的信息请查看 *[Bridging Between String and NSString](https://developer.apple.com/documentation/swift/string#2919514)*。
|
||||
|
||||
## 字符串字面量 {#string-literals}
|
||||
|
||||
你可以在代码里使用一段预定义的字符串值作为字符串字面量。字符串字面量是由一对双引号包裹着的具有固定顺序的字符集。
|
||||
|
||||
字符串字面量可以用于为常量和变量提供初始值:
|
||||
|
||||
```swift
|
||||
let someString = "Some string literal value"
|
||||
```
|
||||
|
||||
注意,Swift 之所以推断 `someString` 常量为字符串类型,是因为它使用了字面量方式进行初始化。
|
||||
|
||||
### 多行字符串字面量 {#multiline-string-literals}
|
||||
|
||||
如果你需要一个字符串是跨越多行的,那就使用多行字符串字面量 — 由一对三个双引号包裹着的具有固定顺序的文本字符集:
|
||||
|
||||
```swift
|
||||
let quotation = """
|
||||
The White Rabbit put on his spectacles. "Where shall I begin,
|
||||
please your Majesty?" he asked.
|
||||
|
||||
"Begin at the beginning," the King said gravely, "and go on
|
||||
till you come to the end; then stop."
|
||||
"""
|
||||
```
|
||||
|
||||
一个多行字符串字面量包含了所有的在开启和关闭引号(`"""`)中的行。这个字符从开启引号(`"""`)之后的第一行开始,到关闭引号(`"""`)之前为止。这就意味着字符串开启引号之后(`"""`)或者结束引号(`"""`)之前都没有换行符号。(译者:下面两个字符串其实是一样的,虽然第二个使用了多行字符串的形式)
|
||||
|
||||
```swift
|
||||
let singleLineString = "These are the same."
|
||||
let multilineString = """
|
||||
These are the same.
|
||||
"""
|
||||
```
|
||||
|
||||
如果你的代码中,多行字符串字面量包含换行符的话,则多行字符串字面量中也会包含换行符。如果你想换行,以便加强代码的可读性,但是你又不想在你的多行字符串字面量中出现换行符的话,你可以用在行尾写一个反斜杠(`\`)作为续行符。
|
||||
|
||||
```swift
|
||||
let softWrappedQuotation = """
|
||||
The White Rabbit put on his spectacles. "Where shall I begin, \
|
||||
please your Majesty?" he asked.
|
||||
|
||||
"Begin at the beginning," the King said gravely, "and go on \
|
||||
till you come to the end; then stop."
|
||||
"""
|
||||
```
|
||||
|
||||
为了让一个多行字符串字面量开始和结束于换行符,请将换行写在第一行和最后一行,例如:
|
||||
|
||||
```swift
|
||||
let lineBreaks = """
|
||||
|
||||
This string starts with a line break.
|
||||
It also ends with a line break.
|
||||
|
||||
"""
|
||||
```
|
||||
|
||||
一个多行字符串字面量能够缩进来匹配周围的代码。关闭引号(`"""`)之前的空白字符串告诉 Swift 编译器其他各行多少空白字符串需要忽略。然而,如果你在某行的前面写的空白字符串超出了关闭引号(`"""`)之前的空白字符串,则超出部分将被包含在多行字符串字面量中。
|
||||
|
||||

|
||||
|
||||
在上面的例子中,尽管整个多行字符串字面量都是缩进的(源代码缩进),第一行和最后一行没有以空白字符串开始(实际的变量值)。中间一行的缩进用空白字符串(源代码缩进)比关闭引号(`"""`)之前的空白字符串多,所以,它的行首将有4个空格。
|
||||
|
||||
### 字符串字面量的特殊字符 {#special-characters-in-string-literals}
|
||||
|
||||
字符串字面量可以包含以下特殊字符:
|
||||
|
||||
* 转义字符 `\0`(空字符)、`\\`(反斜线)、`\t`(水平制表符)、`\n`(换行符)、`\r`(回车符)、`\"`(双引号)、`\'`(单引号)。
|
||||
* Unicode 标量,写成 `\u{n}`(u 为小写),其中 `n` 为任意一到八位十六进制数且可用的 Unicode 位码。
|
||||
|
||||
下面的代码为各种特殊字符的使用示例。
|
||||
`wiseWords` 常量包含了两个双引号。
|
||||
`dollarSign`、`blackHeart` 和 `sparklingHeart` 常量演示了三种不同格式的 Unicode 标量:
|
||||
|
||||
```swift
|
||||
let wiseWords = "\"Imagination is more important than knowledge\" - Einstein"
|
||||
// "Imageination is more important than knowledge" - Enistein
|
||||
let dollarSign = "\u{24}" // $,Unicode 标量 U+0024
|
||||
let blackHeart = "\u{2665}" // ♥,Unicode 标量 U+2665
|
||||
let sparklingHeart = "\u{1F496}" // 💖,Unicode 标量 U+1F496
|
||||
```
|
||||
|
||||
由于多行字符串字面量使用了三个双引号,而不是一个,所以你可以在多行字符串字面量里直接使用双引号(`"`)而不必加上转义符(`\`)。要在多行字符串字面量中使用 `"""` 的话,就需要使用至少一个转义符(`\`):
|
||||
|
||||
```swift
|
||||
let threeDoubleQuotes = """
|
||||
Escaping the first quote \"""
|
||||
Escaping all three quotes \"\"\"
|
||||
"""
|
||||
```
|
||||
|
||||
### 扩展字符串分隔符 {#extended-string-delimiters}
|
||||
|
||||
您可以将字符串文字放在扩展分隔符中,这样字符串中的特殊字符将会被直接包含而非转义后的效果。将字符串放在引号(`"`)中并用数字符号(`#`)括起来。例如,打印字符串文字 `#"Line 1 \nLine 2"#` 会打印换行符转义序列(`\n`)而不是给文字换行。
|
||||
|
||||
如果需要字符串文字中字符的特殊效果,请匹配转义字符(`\`)后面添加与起始位置个数相匹配的 `#` 符。 例如,如果您的字符串是 `#"Line 1 \nLine 2"#` 并且您想要换行,则可以使用 `#"Line 1 \#nLine 2"#` 来代替。 同样,`###"Line1 \###nLine2"###` 也可以实现换行效果。
|
||||
|
||||
扩展分隔符创建的字符串文字也可以是多行字符串文字。 您可以使用扩展分隔符在多行字符串中包含文本 `"""`,覆盖原有的结束文字的默认行为。例如:
|
||||
|
||||
```swift
|
||||
let threeMoreDoubleQuotationMarks = #"""
|
||||
Here are three more double quotes: """
|
||||
"""#
|
||||
```
|
||||
|
||||
## 初始化空字符串 {#initializing-an-empty-string}
|
||||
|
||||
要创建一个空字符串作为初始值,可以将空的字符串字面量赋值给变量,也可以初始化一个新的 `String` 实例:
|
||||
|
||||
```swift
|
||||
var emptyString = "" // 空字符串字面量
|
||||
var anotherEmptyString = String() // 初始化方法
|
||||
// 两个字符串均为空并等价。
|
||||
```
|
||||
|
||||
你可以通过检查 `Bool` 类型的 `isEmpty` 属性来判断该字符串是否为空:
|
||||
|
||||
```swift
|
||||
if emptyString.isEmpty {
|
||||
print("Nothing to see here")
|
||||
}
|
||||
// 打印输出:“Nothing to see here”
|
||||
```
|
||||
|
||||
## 字符串可变性 {#string-mutability}
|
||||
|
||||
你可以通过将一个特定字符串分配给一个变量来对其进行修改,或者分配给一个常量来保证其不会被修改:
|
||||
|
||||
```swift
|
||||
var variableString = "Horse"
|
||||
variableString += " and carriage"
|
||||
// variableString 现在为 "Horse and carriage"
|
||||
|
||||
let constantString = "Highlander"
|
||||
constantString += " and another Highlander"
|
||||
// 这会报告一个编译错误(compile-time error) - 常量字符串不可以被修改。
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 在 Objective-C 和 Cocoa 中,需要通过选择两个不同的类(`NSString` 和 `NSMutableString`)来指定字符串是否可以被修改。
|
||||
|
||||
## 字符串是值类型 {#strings-are-value-types}
|
||||
|
||||
在 Swift 中 `String` 类型是*值类型*。如果你创建了一个新的字符串,那么当其进行常量、变量赋值操作,或在函数/方法中传递时,会进行值拷贝。在前述任一情况下,都会对已有字符串值创建新副本,并对该新副本而非原始字符串进行传递或赋值操作。值类型在 [结构体和枚举是值类型](./09_Structures_And_Classes.md#structures-and-enumerations-are-value-types) 中进行了详细描述。
|
||||
|
||||
Swift 默认拷贝字符串的行为保证了在函数/方法向你传递的字符串所属权属于你,无论该值来自于哪里。你可以确信传递的字符串不会被修改,除非你自己去修改它。
|
||||
|
||||
在实际编译时,Swift 编译器会优化字符串的使用,使实际的复制只发生在绝对必要的情况下,这意味着你将字符串作为值类型的同时可以获得极高的性能。
|
||||
|
||||
## 使用字符 {#working-with-characters}
|
||||
|
||||
你可通过 `for-in` 循环来遍历字符串,获取字符串中每一个字符的值:
|
||||
|
||||
```swift
|
||||
for character in "Dog!🐶" {
|
||||
print(character)
|
||||
}
|
||||
// D
|
||||
// o
|
||||
// g
|
||||
// !
|
||||
// 🐶
|
||||
```
|
||||
|
||||
`for-in` 循环在 [For 循环](./05_Control_Flow.md#for-loops) 中进行了详细描述。
|
||||
|
||||
另外,通过标明一个 `Character` 类型并用字符字面量进行赋值,可以建立一个独立的字符常量或变量:
|
||||
|
||||
```swift
|
||||
let exclamationMark: Character = "!"
|
||||
```
|
||||
|
||||
字符串可以通过传递一个值类型为 `Character` 的数组作为自变量来初始化:
|
||||
|
||||
```swift
|
||||
let catCharacters: [Character] = ["C", "a", "t", "!", "🐱"]
|
||||
let catString = String(catCharacters)
|
||||
print(catString)
|
||||
// 打印输出:“Cat!🐱”
|
||||
```
|
||||
|
||||
## 连接字符串和字符 {#concatenating-strings-and-characters}
|
||||
|
||||
字符串可以通过加法运算符(`+`)相加在一起(或称“连接”)创建一个新的字符串:
|
||||
|
||||
```swift
|
||||
let string1 = "hello"
|
||||
let string2 = " there"
|
||||
var welcome = string1 + string2
|
||||
// welcome 现在等于 "hello there"
|
||||
```
|
||||
|
||||
你也可以通过加法赋值运算符(`+=`)将一个字符串添加到一个已经存在字符串变量上:
|
||||
|
||||
```swift
|
||||
var instruction = "look over"
|
||||
instruction += string2
|
||||
// instruction 现在等于 "look over there"
|
||||
```
|
||||
|
||||
你可以用 `append()` 方法将一个字符附加到一个字符串变量的尾部:
|
||||
|
||||
```swift
|
||||
let exclamationMark: Character = "!"
|
||||
welcome.append(exclamationMark)
|
||||
// welcome 现在等于 "hello there!"
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 你不能将一个字符串或者字符添加到一个已经存在的字符变量上,因为字符变量只能包含一个字符。
|
||||
|
||||
如果你需要使用多行字符串字面量来拼接字符串,并且你需要字符串每一行都以换行符结尾,包括最后一行:
|
||||
|
||||
```swift
|
||||
let badStart = """
|
||||
one
|
||||
two
|
||||
"""
|
||||
let end = """
|
||||
three
|
||||
"""
|
||||
print(badStart + end)
|
||||
// 打印两行:
|
||||
// one
|
||||
// twothree
|
||||
|
||||
let goodStart = """
|
||||
one
|
||||
two
|
||||
|
||||
"""
|
||||
print(goodStart + end)
|
||||
// 打印三行:
|
||||
// one
|
||||
// two
|
||||
// three
|
||||
```
|
||||
|
||||
上面的代码,把 `badStart` 和 `end` 拼接起来的字符串非我们想要的结果。因为 `badStart` 最后一行没有换行符,它与 `end` 的第一行结合到了一起。相反的,`goodStart` 的每一行都以换行符结尾,所以它与 `end` 拼接的字符串总共有三行,正如我们期望的那样。
|
||||
|
||||
## 字符串插值 {#string-interpolation}
|
||||
|
||||
*字符串插值*是一种构建新字符串的方式,可以在其中包含常量、变量、字面量和表达式。**字符串字面量**和**多行字符串字面量**都可以使用字符串插值。你插入的字符串字面量的每一项都在以反斜线为前缀的圆括号中:
|
||||
|
||||
```swift
|
||||
let multiplier = 3
|
||||
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
|
||||
// message 是 "3 times 2.5 is 7.5"
|
||||
```
|
||||
|
||||
在上面的例子中,`multiplier` 作为 `\(multiplier)` 被插入到一个字符串常量量中。当创建字符串执行插值计算时此占位符会被替换为 `multiplier` 实际的值。
|
||||
|
||||
`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."
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 插值字符串中写在括号中的表达式不能包含非转义反斜杠(`\`),并且不能包含回车或换行符。不过,插值字符串可以包含其他字面量。
|
||||
|
||||
## Unicode {#unicode}
|
||||
|
||||
*Unicode*是一个用于在不同书写系统中对文本进行编码、表示和处理的国际标准。它使你可以用标准格式表示来自任意语言几乎所有的字符,并能够对文本文件或网页这样的外部资源中的字符进行读写操作。Swift 的 `String` 和 `Character` 类型是完全兼容 Unicode 标准的。
|
||||
|
||||
### Unicode 标量 {#unicode-scalars}
|
||||
|
||||
Swift 的 `String` 类型是基于 *Unicode 标量* 建立的。Unicode 标量是对应字符或者修饰符的唯一的 21 位数字,例如 `U+0061` 表示小写的拉丁字母(`LATIN SMALL LETTER A`)("`a`"),`U+1F425` 表示小鸡表情(`FRONT-FACING BABY CHICK`)("`🐥`")。
|
||||
|
||||
请注意,并非所有 21 位 Unicode 标量值都分配给字符,某些标量被保留用于将来分配或用于 UTF-16 编码。已分配的标量值通常也有一个名称,例如上面示例中的 LATIN SMALL LETTER A 和 FRONT-FACING BABY CHICK。
|
||||
|
||||
### 可扩展的字形群集 {#extended-grapheme-clusters}
|
||||
|
||||
每一个 Swift 的 `Character` 类型代表一个*可扩展的字形群*。而一个可扩展的字形群构成了人类可读的单个字符,它由一个或多个(当组合时) Unicode 标量的序列组成。
|
||||
|
||||
举个例子,字母 `é` 可以用单一的 Unicode 标量 `é`(`LATIN SMALL LETTER E WITH ACUTE`, 或者 `U+00E9`)来表示。然而一个标准的字母 `e`(`LATIN SMALL LETTER E` 或者 `U+0065`) 加上一个急促重音(`COMBINING ACTUE ACCENT`)的标量(`U+0301`),这样一对标量就表示了同样的字母 `é`。
|
||||
这个急促重音的标量形象的将 `e` 转换成了 `é`。
|
||||
|
||||
在这两种情况中,字母 `é` 代表了一个单一的 Swift 的 `Character` 值,同时代表了一个可扩展的字形群。在第一种情况,这个字形群包含一个单一标量;而在第二种情况,它是包含两个标量的字形群:
|
||||
|
||||
```swift
|
||||
let eAcute: Character = "\u{E9}" // é
|
||||
let combinedEAcute: Character = "\u{65}\u{301}" // e 后面加上 ́
|
||||
// eAcute 是 é, combinedEAcute 是 é
|
||||
```
|
||||
|
||||
可扩展的字形集是一个将许多复杂的脚本字符表示为单个字符值的灵活方式。例如,来自朝鲜语字母表的韩语音节能表示为组合或分解的有序排列。在 Swift 都会表示为同一个单一的 `Character` 值:
|
||||
|
||||
```swift
|
||||
let precomposed: Character = "\u{D55C}" // 한
|
||||
let decomposed: Character = "\u{1112}\u{1161}\u{11AB}" // ᄒ, ᅡ, ᆫ
|
||||
// precomposed 是 한, decomposed 是 한
|
||||
```
|
||||
|
||||
可拓展的字符群集可以使包围记号(例如 `COMBINING ENCLOSING CIRCLE` 或者 `U+20DD`)的标量包围其他 Unicode 标量,作为一个单一的 `Character` 值:
|
||||
|
||||
```swift
|
||||
let enclosedEAcute: Character = "\u{E9}\u{20DD}"
|
||||
// enclosedEAcute 是 é⃝
|
||||
```
|
||||
|
||||
地域性指示符号的 Unicode 标量可以组合成一个单一的 `Character` 值,例如 `REGIONAL INDICATOR SYMBOL LETTER U`(`U+1F1FA`)和 `REGIONAL INDICATOR SYMBOL LETTER S`(`U+1F1F8`):
|
||||
|
||||
```swift
|
||||
let regionalIndicatorForUS: Character = "\u{1F1FA}\u{1F1F8}"
|
||||
// regionalIndicatorForUS 是 🇺🇸
|
||||
```
|
||||
|
||||
## 计算字符数量 {#counting-characters}
|
||||
|
||||
如果想要获得一个字符串中 `Character` 值的数量,可以使用 `count` 属性:
|
||||
|
||||
```swift
|
||||
let unusualMenagerie = "Koala 🐨, Snail 🐌, Penguin 🐧, Dromedary 🐪"
|
||||
print("unusualMenagerie has \(unusualMenagerie.count) characters")
|
||||
// 打印输出“unusualMenagerie has 40 characters”
|
||||
```
|
||||
|
||||
注意在 Swift 中,使用可拓展的字符群集作为 `Character` 值来连接或改变字符串时,并不一定会更改字符串的字符数量。
|
||||
|
||||
例如,如果你用四个字符的单词 `cafe` 初始化一个新的字符串,然后添加一个 `COMBINING ACTUE ACCENT`(`U+0301`)作为字符串的结尾。最终这个字符串的字符数量仍然是 `4`,因为第四个字符是 `é`,而不是 `e`:
|
||||
|
||||
```swift
|
||||
var word = "cafe"
|
||||
print("the number of characters in \(word) is \(word.count)")
|
||||
// 打印输出“the number of characters in cafe is 4”
|
||||
|
||||
word += "\u{301}" // 拼接一个重音,U+0301
|
||||
|
||||
print("the number of characters in \(word) is \(word.count)")
|
||||
// 打印输出“the number of characters in café is 4”
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 可扩展的字形群可以由多个 Unicode 标量组成。这意味着不同的字符以及相同字符的不同表示方式可能需要不同数量的内存空间来存储。所以 Swift 中的字符在一个字符串中并不一定占用相同的内存空间数量。因此在没有获取字符串的可扩展的字符群的范围时候,就不能计算出字符串的字符数量。如果你正在处理一个长字符串,需要注意 `count` 属性必须遍历全部的 Unicode 标量,来确定字符串的字符数量。
|
||||
>
|
||||
> 另外需要注意的是通过 `count` 属性返回的字符数量并不总是与包含相同字符的 `NSString` 的 `length` 属性相同。`NSString` 的 `length` 属性是利用 UTF-16 表示的十六位代码单元数字,而不是 Unicode 可扩展的字符群集。
|
||||
|
||||
## 访问和修改字符串 {#accessing-and-modifying-a-string}
|
||||
|
||||
你可以通过字符串的属性和方法来访问和修改它,当然也可以用下标语法完成。
|
||||
|
||||
### 字符串索引 {#string-indices}
|
||||
|
||||
每一个 `String` 值都有一个关联的索引(*index*)类型,`String.Index`,它对应着字符串中的每一个 `Character` 的位置。
|
||||
|
||||
前面提到,不同的字符可能会占用不同数量的内存空间,所以要知道 `Character` 的确定位置,就必须从 `String` 开头遍历每一个 Unicode 标量直到结尾。因此,Swift 的字符串不能用整数(integer)做索引。
|
||||
|
||||
使用 `startIndex` 属性可以获取一个 `String` 的第一个 `Character` 的索引。使用 `endIndex` 属性可以获取最后一个 `Character` 的后一个位置的索引。因此,`endIndex` 属性不能作为一个字符串的有效下标。如果 `String` 是空串,`startIndex` 和 `endIndex` 是相等的。
|
||||
|
||||
通过调用 `String` 的 `index(before:)` 或 `index(after:)` 方法,可以立即得到前面或后面的一个索引。你还可以通过调用 `index(_:offsetBy:)` 方法来获取对应偏移量的索引,这种方式可以避免多次调用 `index(before:)` 或 `index(after:)` 方法。
|
||||
|
||||
你可以使用下标语法来访问 `String` 特定索引的 `Character`。
|
||||
|
||||
```swift
|
||||
let greeting = "Guten Tag!"
|
||||
greeting[greeting.startIndex]
|
||||
// G
|
||||
greeting[greeting.index(before: greeting.endIndex)]
|
||||
// !
|
||||
greeting[greeting.index(after: greeting.startIndex)]
|
||||
// u
|
||||
let index = greeting.index(greeting.startIndex, offsetBy: 7)
|
||||
greeting[index]
|
||||
// a
|
||||
```
|
||||
|
||||
试图获取越界索引对应的 `Character`,将引发一个运行时错误。
|
||||
|
||||
```swift
|
||||
greeting[greeting.endIndex] // error
|
||||
greeting.index(after: greeting.endIndex) // error
|
||||
```
|
||||
|
||||
使用 `indices` 属性会创建一个包含全部索引的范围(`Range`),用来在一个字符串中访问单个字符。
|
||||
|
||||
```swift
|
||||
for index in greeting.indices {
|
||||
print("\(greeting[index]) ", terminator: "")
|
||||
}
|
||||
// 打印输出“G u t e n T a g ! ”
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 你可以使用 `startIndex` 和 `endIndex` 属性或者 `index(before:)` 、`index(after:)` 和 `index(_:offsetBy:)` 方法在任意一个确认的并遵循 `Collection` 协议的类型里面,如上文所示是使用在 `String` 中,你也可以使用在 `Array`、`Dictionary` 和 `Set` 中。
|
||||
|
||||
### 插入和删除 {#inserting-and-removing}
|
||||
|
||||
调用 `insert(_:at:)` 方法可以在一个字符串的指定索引插入一个字符,调用 `insert(contentsOf:at:)` 方法可以在一个字符串的指定索引插入一个段字符串。
|
||||
|
||||
```swift
|
||||
var welcome = "hello"
|
||||
welcome.insert("!", at: welcome.endIndex)
|
||||
// welcome 变量现在等于 "hello!"
|
||||
|
||||
welcome.insert(contentsOf:" there", at: welcome.index(before: welcome.endIndex))
|
||||
// welcome 变量现在等于 "hello there!"
|
||||
```
|
||||
|
||||
调用 `remove(at:)` 方法可以在一个字符串的指定索引删除一个字符,调用 `removeSubrange(_:)` 方法可以在一个字符串的指定索引删除一个子字符串。
|
||||
|
||||
```swift
|
||||
welcome.remove(at: welcome.index(before: welcome.endIndex))
|
||||
// welcome 现在等于 "hello there"
|
||||
|
||||
let range = welcome.index(welcome.endIndex, offsetBy: -6)..<welcome.endIndex
|
||||
welcome.removeSubrange(range)
|
||||
// welcome 现在等于 "hello"
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 你可以使用 `insert(_:at:)`、`insert(contentsOf:at:)`、`remove(at:)` 和 `removeSubrange(_:)` 方法在任意一个确认的并遵循 `RangeReplaceableCollection` 协议的类型里面,如上文所示是使用在 `String` 中,你也可以使用在 `Array`、`Dictionary` 和 `Set` 中。
|
||||
|
||||
## 子字符串 {#substrings}
|
||||
|
||||
当你从字符串中获取一个子字符串 —— 例如,使用下标或者 `prefix(_:)` 之类的方法 —— 就可以得到一个 `Substring` 的实例,而非另外一个 `String`。Swift 里的 `Substring` 绝大部分函数都跟 `String` 一样,意味着你可以使用同样的方式去操作 `Substring` 和 `String`。然而,跟 `String` 不同的是,你只有在短时间内需要操作字符串时,才会使用 `Substring`。当你需要长时间保存结果时,就把 `Substring` 转化为 `String` 的实例:
|
||||
|
||||
```swift
|
||||
let greeting = "Hello, world!"
|
||||
let index = greeting.firstIndex(of: ",") ?? greeting.endIndex
|
||||
let beginning = greeting[..<index]
|
||||
// beginning 的值为 "Hello"
|
||||
|
||||
// 把结果转化为 String 以便长期存储。
|
||||
let newString = String(beginning)
|
||||
```
|
||||
|
||||
就像 `String`,每一个 `Substring` 都会在内存里保存字符集。而 `String` 和 `Substring` 的区别在于性能优化上,`Substring` 可以重用原 `String` 的内存空间,或者另一个 `Substring` 的内存空间(`String` 也有同样的优化,但如果两个 `String` 共享内存的话,它们就会相等)。这一优化意味着你在修改 `String` 和 `Substring` 之前都不需要消耗性能去复制内存。就像前面说的那样,`Substring` 不适合长期存储 —— 因为它重用了原 `String` 的内存空间,原 `String` 的内存空间必须保留直到它的 `Substring` 不再被使用为止。
|
||||
|
||||
上面的例子,`greeting` 是一个 `String`,意味着它在内存里有一片空间保存字符集。而由于 `beginning` 是 `greeting` 的 `Substring`,它重用了 `greeting` 的内存空间。相反,`newString` 是一个 `String` —— 它是使用 `Substring` 创建的,拥有一片自己的内存空间。下面的图展示了他们之间的关系:
|
||||
|
||||

|
||||
|
||||
> 注意
|
||||
>
|
||||
> `String` 和 `Substring` 都遵循 [`StringProtocol`](https://developer.apple.com/documentation/swift/stringprotocol) 协议,这意味着操作字符串的函数使用 `StringProtocol` 会更加方便。你可以传入 `String` 或 `Substring` 去调用函数。
|
||||
|
||||
## 比较字符串 {#comparing-strings}
|
||||
|
||||
Swift 提供了三种方式来比较文本值:字符串字符相等、前缀相等和后缀相等。
|
||||
|
||||
### 字符串/字符相等 {#string-and-character-equality}
|
||||
|
||||
字符串/字符可以用等于操作符(`==`)和不等于操作符(`!=`),详细描述在 [比较运算符](./02_Basic_Operators.md#comparison-operators):
|
||||
|
||||
```swift
|
||||
let quotation = "We're a lot alike, you and I."
|
||||
let sameQuotation = "We're a lot alike, you and I."
|
||||
if quotation == sameQuotation {
|
||||
print("These two strings are considered equal")
|
||||
}
|
||||
// 打印输出“These two strings are considered equal”
|
||||
```
|
||||
|
||||
如果两个字符串(或者两个字符)的可扩展的字形群集是标准相等,那就认为它们是相等的。只要可扩展的字形群集有同样的语言意义和外观则认为它们标准相等,即使它们是由不同的 Unicode 标量构成。
|
||||
|
||||
例如,`LATIN SMALL LETTER E WITH ACUTE`(`U+00E9`)就是标准相等于 `LATIN SMALL LETTER E`(`U+0065`)后面加上 `COMBINING ACUTE ACCENT`(`U+0301`)。这两个字符群集都是表示字符 `é` 的有效方式,所以它们被认为是标准相等的:
|
||||
|
||||
```swift
|
||||
// "Voulez-vous un café?" 使用 LATIN SMALL LETTER E WITH ACUTE
|
||||
let eAcuteQuestion = "Voulez-vous un caf\u{E9}?"
|
||||
|
||||
// "Voulez-vous un café?" 使用 LATIN SMALL LETTER E and COMBINING ACUTE ACCENT
|
||||
let combinedEAcuteQuestion = "Voulez-vous un caf\u{65}\u{301}?"
|
||||
|
||||
if eAcuteQuestion == combinedEAcuteQuestion {
|
||||
print("These two strings are considered equal")
|
||||
}
|
||||
// 打印输出“These two strings are considered equal”
|
||||
```
|
||||
|
||||
相反,英语中的 `LATIN CAPITAL LETTER A`(`U+0041`,或者 `A`)不等于俄语中的 `CYRILLIC CAPITAL LETTER A`(`U+0410`,或者 `A`)。两个字符看着是一样的,但却有不同的语言意义:
|
||||
|
||||
```swift
|
||||
let latinCapitalLetterA: Character = "\u{41}"
|
||||
|
||||
let cyrillicCapitalLetterA: Character = "\u{0410}"
|
||||
|
||||
if latinCapitalLetterA != cyrillicCapitalLetterA {
|
||||
print("These two characters are not equivalent")
|
||||
}
|
||||
// 打印“These two characters are not equivalent”
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 在 Swift 中,字符串和字符并不区分地域(not locale-sensitive)。
|
||||
|
||||
### 前缀/后缀相等 {#prefix-and-suffix-equality}
|
||||
|
||||
通过调用字符串的 `hasPrefix(_:)`/`hasSuffix(_:)` 方法来检查字符串是否拥有特定前缀/后缀,两个方法均接收一个 `String` 类型的参数,并返回一个布尔值。
|
||||
|
||||
下面的例子以一个字符串数组表示莎士比亚话剧《罗密欧与朱丽叶》中前两场的场景位置:
|
||||
|
||||
```swift
|
||||
let romeoAndJuliet = [
|
||||
"Act 1 Scene 1: Verona, A public place",
|
||||
"Act 1 Scene 2: Capulet's mansion",
|
||||
"Act 1 Scene 3: A room in Capulet's mansion",
|
||||
"Act 1 Scene 4: A street outside Capulet's mansion",
|
||||
"Act 1 Scene 5: The Great Hall in Capulet's mansion",
|
||||
"Act 2 Scene 1: Outside Capulet's mansion",
|
||||
"Act 2 Scene 2: Capulet's orchard",
|
||||
"Act 2 Scene 3: Outside Friar Lawrence's cell",
|
||||
"Act 2 Scene 4: A street in Verona",
|
||||
"Act 2 Scene 5: Capulet's mansion",
|
||||
"Act 2 Scene 6: Friar Lawrence's cell"
|
||||
]
|
||||
```
|
||||
|
||||
你可以调用 `hasPrefix(_:)` 方法来计算话剧中第一幕的场景数:
|
||||
|
||||
```swift
|
||||
var act1SceneCount = 0
|
||||
for scene in romeoAndJuliet {
|
||||
if scene.hasPrefix("Act 1 ") {
|
||||
act1SceneCount += 1
|
||||
}
|
||||
}
|
||||
print("There are \(act1SceneCount) scenes in Act 1")
|
||||
// 打印输出“There are 5 scenes in Act 1”
|
||||
```
|
||||
|
||||
相似地,你可以用 `hasSuffix(_:)` 方法来计算发生在不同地方的场景数:
|
||||
|
||||
```swift
|
||||
var mansionCount = 0
|
||||
var cellCount = 0
|
||||
for scene in romeoAndJuliet {
|
||||
if scene.hasSuffix("Capulet's mansion") {
|
||||
mansionCount += 1
|
||||
} else if scene.hasSuffix("Friar Lawrence's cell") {
|
||||
cellCount += 1
|
||||
}
|
||||
}
|
||||
print("\(mansionCount) mansion scenes; \(cellCount) cell scenes")
|
||||
// 打印输出“6 mansion scenes; 2 cell scenes”
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> `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) 中进行了描述。
|
||||
|
||||
另外,能够以其他三种 Unicode 兼容的方式访问字符串的值:
|
||||
|
||||
* UTF-8 代码单元集合(利用字符串的 `utf8` 属性进行访问)
|
||||
* UTF-16 代码单元集合(利用字符串的 `utf16` 属性进行访问)
|
||||
* 21 位的 Unicode 标量值集合,也就是字符串的 UTF-32 编码格式(利用字符串的 `unicodeScalars` 属性进行访问)
|
||||
|
||||
下面由 `D`,`o`,`g`,`‼`(`DOUBLE EXCLAMATION MARK`, Unicode 标量 `U+203C`)和 `🐶`(`DOG FACE`,Unicode 标量为 `U+1F436`)组成的字符串中的每一个字符代表着一种不同的表示:
|
||||
|
||||
```swift
|
||||
let dogString = "Dog‼🐶"
|
||||
```
|
||||
|
||||
### UTF-8 表示 {#UTF-8-representation}
|
||||
|
||||
你可以通过遍历 `String` 的 `utf8` 属性来访问它的 `UTF-8` 表示。其为 `String.UTF8View` 类型的属性,`UTF8View` 是无符号 8 位(`UInt8`)值的集合,每一个 `UInt8` 值都是一个字符的 UTF-8 表示:
|
||||
|
||||
<table style='text-align:center'>
|
||||
<tr height="77">
|
||||
<td>Character</td>
|
||||
<td>D<br>U+0044</td>
|
||||
<td>o<br>U+006F</td>
|
||||
<td>g<br>U+0067</td>
|
||||
<td colspan="3">‼<br>U+203C</td>
|
||||
<td colspan="4">🐶<br>U+1F436</td>
|
||||
</tr>
|
||||
<tr height="77">
|
||||
<td height="77">UTF-8<br>Code Unit</td>
|
||||
<td>68</td>
|
||||
<td>111</td>
|
||||
<td>103</td>
|
||||
<td>226</td>
|
||||
<td>128</td>
|
||||
<td>188</td>
|
||||
<td>240</td>
|
||||
<td>159</td>
|
||||
<td>144</td>
|
||||
<td>182</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td height="77">Position</td>
|
||||
<td>0</td>
|
||||
<td>1</td>
|
||||
<td>2</td>
|
||||
<td>3</td>
|
||||
<td>4</td>
|
||||
<td>5</td>
|
||||
<td>6</td>
|
||||
<td>7</td>
|
||||
<td>8</td>
|
||||
<td>9</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
```swift
|
||||
for codeUnit in dogString.utf8 {
|
||||
print("\(codeUnit) ", terminator: "")
|
||||
}
|
||||
print("")
|
||||
// 68 111 103 226 128 188 240 159 144 182
|
||||
```
|
||||
|
||||
上面的例子中,前三个 10 进制 `codeUnit` 值(`68`、`111`、`103`)代表了字符 `D`、`o` 和 `g`,它们的 UTF-8 表示与 ASCII 表示相同。接下来的三个 10 进制 `codeUnit` 值(`226`、`128`、`188`)是 `DOUBLE EXCLAMATION MARK` 的3字节 UTF-8 表示。最后的四个 `codeUnit` 值(`240`、`159`、`144`、`182`)是 `DOG FACE` 的4字节 UTF-8 表示。
|
||||
|
||||
### UTF-16 表示 {#UTF-16-representation}
|
||||
|
||||
你可以通过遍历 `String` 的 `utf16` 属性来访问它的 `UTF-16` 表示。其为 `String.UTF16View` 类型的属性,`UTF16View` 是无符号16位(`UInt16`)值的集合,每一个 `UInt16` 都是一个字符的 UTF-16 表示:
|
||||
|
||||
<table style='text-align:center'>
|
||||
<tr height="77">
|
||||
<td>Character</td>
|
||||
<td>D<br>U+0044</td>
|
||||
<td>o<br>U+006F</td>
|
||||
<td>g<br>U+0067</td>
|
||||
<td>‼<br>U+203C</td>
|
||||
<td colspan="2">🐶<br>U+1F436</td>
|
||||
</tr>
|
||||
<tr height="77">
|
||||
<td height="77">UTF-16<br>Code Unit</td>
|
||||
<td>68</td>
|
||||
<td>111</td>
|
||||
<td>103</td>
|
||||
<td>8252</td>
|
||||
<td>55357</td>
|
||||
<td>56374</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td height="77">Position</td>
|
||||
<td>0</td>
|
||||
<td>1</td>
|
||||
<td>2</td>
|
||||
<td>3</td>
|
||||
<td>4</td>
|
||||
<td>5</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
```swift
|
||||
for codeUnit in dogString.utf16 {
|
||||
print("\(codeUnit) ", terminator: "")
|
||||
}
|
||||
print("")
|
||||
// 68 111 103 8252 55357 56374
|
||||
```
|
||||
|
||||
同样,前三个 `codeUnit` 值(`68`、`111`、`103`)代表了字符 `D`、`o` 和 `g`,它们的 UTF-16 代码单元和 UTF-8 完全相同(因为这些 Unicode 标量表示 ASCII 字符)。
|
||||
|
||||
第四个 `codeUnit` 值(`8252`)是一个等于十六进制 `203C` 的的十进制值。这个代表了 `DOUBLE EXCLAMATION MARK` 字符的 Unicode 标量值 `U+203C`。这个字符在 UTF-16 中可以用一个代码单元表示。
|
||||
|
||||
第五和第六个 `codeUnit` 值(`55357` 和 `56374`)是 `DOG FACE` 字符的 UTF-16 表示。第一个值为 `U+D83D`(十进制值为 `55357`),第二个值为 `U+DC36`(十进制值为 `56374`)。
|
||||
|
||||
### Unicode 标量表示 {#unicode-scalars-representation}
|
||||
|
||||
你可以通过遍历 `String` 值的 `unicodeScalars` 属性来访问它的 Unicode 标量表示。其为 `UnicodeScalarView` 类型的属性,`UnicodeScalarView` 是 `UnicodeScalar` 类型的值的集合。
|
||||
|
||||
每一个 `UnicodeScalar` 拥有一个 `value` 属性,可以返回对应的 21 位数值,用 `UInt32` 来表示:
|
||||
|
||||
<table style='text-align:center'>
|
||||
<tr height="77">
|
||||
<td>Character</td>
|
||||
<td>D<br>U+0044</td>
|
||||
<td>o<br>U+006F</td>
|
||||
<td>g<br>U+0067</td>
|
||||
<td>‼<br>U+203C</td>
|
||||
<td>🐶<br>U+1F436</td>
|
||||
</tr>
|
||||
<tr height="77">
|
||||
<td height="77">Unicode Scalar<br>Code Unit</td>
|
||||
<td>68</td>
|
||||
<td>111</td>
|
||||
<td>103</td>
|
||||
<td>8252</td>
|
||||
<td>128054</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td height="77">Position</td>
|
||||
<td>0</td>
|
||||
<td>1</td>
|
||||
<td>2</td>
|
||||
<td>3</td>
|
||||
<td>4</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
```swift
|
||||
for scalar in dogString.unicodeScalars {
|
||||
print("\(scalar.value) ", terminator: "")
|
||||
}
|
||||
print("")
|
||||
// 68 111 103 8252 128054
|
||||
```
|
||||
|
||||
前三个 `UnicodeScalar` 值(`68`、`111`、`103`)的 `value` 属性仍然代表字符 `D`、`o` 和 `g`。
|
||||
|
||||
第四个 `codeUnit` 值(`8252`)仍然是一个等于十六进制 `203C` 的十进制值。这个代表了 `DOUBLE EXCLAMATION MARK` 字符的 Unicode 标量 `U+203C`。
|
||||
|
||||
第五个 `UnicodeScalar` 值的 `value` 属性,`128054`,是一个十六进制 `1F436` 的十进制表示。其等同于 `DOG FACE` 的 Unicode 标量 `U+1F436`。
|
||||
|
||||
作为查询它们的 `value` 属性的一种替代方法,每个 `UnicodeScalar` 值也可以用来构建一个新的 `String` 值,比如在字符串插值中使用:
|
||||
|
||||
```swift
|
||||
for scalar in dogString.unicodeScalars {
|
||||
print("\(scalar) ")
|
||||
}
|
||||
// D
|
||||
// o
|
||||
// g
|
||||
// ‼
|
||||
// 🐶
|
||||
```
|
||||
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()` 方法。
|
||||
767
source/02_language_guide/05_Control_Flow.md
Executable file
767
source/02_language_guide/05_Control_Flow.md
Executable file
@ -0,0 +1,767 @@
|
||||
# 控制流
|
||||
|
||||
Swift 提供了多种流程控制结构,包括可以多次执行任务的 `while` 循环,基于特定条件选择执行不同代码分支的 `if`、`guard` 和 `switch` 语句,还有控制流程跳转到其他代码位置的 `break` 和 `continue` 语句。
|
||||
|
||||
Swift 还提供了 `for-in` 循环,用来更简单地遍历数组(Array),字典(Dictionary),区间(Range),字符串(String)和其他序列类型。
|
||||
|
||||
Swift 的 `switch` 语句比许多类 C 语言要更加强大。case 还可以匹配很多不同的模式,包括范围匹配,元组(tuple)和特定类型匹配。`switch` 语句的 case 中匹配的值可以声明为临时常量或变量,在 case 作用域内使用,也可以配合 `where` 来描述更复杂的匹配条件。
|
||||
|
||||
## For-In 循环 {#for-in-loops}
|
||||
|
||||
你可以使用 `for-in` 循环来遍历一个集合中的所有元素,例如数组中的元素、范围内的数字或者字符串中的字符。
|
||||
|
||||
以下例子使用 `for-in` 遍历一个数组所有元素:
|
||||
|
||||
```swift
|
||||
let names = ["Anna", "Alex", "Brian", "Jack"]
|
||||
for name in names {
|
||||
print("Hello, \(name)!")
|
||||
}
|
||||
// Hello, Anna!
|
||||
// Hello, Alex!
|
||||
// Hello, Brian!
|
||||
// Hello, Jack!
|
||||
```
|
||||
|
||||
你也可以通过遍历一个字典来访问它的键值对。遍历字典时,字典的每项元素会以 `(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
|
||||
```
|
||||
|
||||
字典的内容理论上是无序的,遍历元素时的顺序是无法确定的。将元素插入字典的顺序并不会决定它们被遍历的顺序。关于数组和字典的细节,参见 [集合类型](./04_Collection_Types.md)。
|
||||
|
||||
`for-in` 循环还可以使用数字范围。下面的例子用来输出乘法表的一部分内容:
|
||||
|
||||
```swift
|
||||
for index in 1...5 {
|
||||
print("\(index) times 5 is \(index * 5)")
|
||||
}
|
||||
// 1 times 5 is 5
|
||||
// 2 times 5 is 10
|
||||
// 3 times 5 is 15
|
||||
// 4 times 5 is 20
|
||||
// 5 times 5 is 25
|
||||
```
|
||||
|
||||
例子中用来进行遍历的元素是使用闭区间操作符(`...`)表示的从 `1` 到 `5` 的数字区间。`index` 被赋值为闭区间中的第一个数字(`1`),然后循环中的语句被执行一次。在本例中,这个循环只包含一个语句,用来输出当前 `index` 值所对应的乘 5 乘法表的结果。该语句执行后,`index` 的值被更新为闭区间中的第二个数字(`2`),之后 `print(_:separator:terminator:)` 函数会再执行一次。整个过程会进行到闭区间结尾为止。
|
||||
|
||||
上面的例子中,`index` 是一个每次循环遍历开始时被自动赋值的常量。这种情况下,`index` 在使用前不需要声明,只需要将它包含在循环的声明中,就可以对其进行隐式声明,而无需使用 `let` 关键字声明。
|
||||
|
||||
如果你不需要区间序列内每一项的值,你可以使用下划线(`_`)替代变量名来忽略这个值:
|
||||
|
||||
```swift
|
||||
let base = 3
|
||||
let power = 10
|
||||
var answer = 1
|
||||
for _ in 1...power {
|
||||
answer *= base
|
||||
}
|
||||
print("\(base) to the power of \(power) is \(answer)")
|
||||
// 输出“3 to the power of 10 is 59049”
|
||||
```
|
||||
|
||||
这个例子计算 base 这个数的 power 次幂(本例中,是 `3` 的 `10` 次幂),从 `1`(`3` 的 `0` 次幂)开始做 `3` 的乘法, 进行 `10` 次,使用 `1` 到 `10` 的闭区间循环。这个计算并不需要知道每一次循环中计数器具体的值,只需要执行了正确的循环次数即可。下划线符号 `_` (替代循环中的变量)能够忽略当前值,并且不提供循环遍历时对值的访问。
|
||||
|
||||
在某些情况下,你可能不想使用包括两个端点的闭区间。想象一下,你在一个手表上绘制分钟的刻度线。总共 `60` 个刻度,从 `0` 分开始。使用半开区间运算符(`..<`)来表示一个左闭右开的区间。有关区间的更多信息,请参阅 [区间运算符](./02_Basic_Operators.md#range-operators)。
|
||||
|
||||
```swift
|
||||
let minutes = 60
|
||||
for tickMark in 0..<minutes {
|
||||
// 每一分钟都渲染一个刻度线(60次)
|
||||
}
|
||||
```
|
||||
|
||||
一些用户可能在其 UI 中可能需要较少的刻度。他们可以每 5 分钟作为一个刻度。使用 `stride(from:to:by:)` 函数跳过不需要的标记。
|
||||
|
||||
```swift
|
||||
let minuteInterval = 5
|
||||
for tickMark in stride(from: 0, to: minutes, by: minuteInterval) {
|
||||
// 每5分钟渲染一个刻度线(0, 5, 10, 15 ... 45, 50, 55)
|
||||
}
|
||||
```
|
||||
|
||||
可以在闭区间使用 `stride(from:through:by:)` 起到同样作用:
|
||||
|
||||
```swift
|
||||
let hours = 12
|
||||
let hourInterval = 3
|
||||
for tickMark in stride(from: 3, through: hours, by: hourInterval) {
|
||||
// 每3小时渲染一个刻度线(3, 6, 9, 12)
|
||||
}
|
||||
```
|
||||
|
||||
以上示例使用 `for-in` 循环来遍历范围、数组、字典和字符串。你可以用它来遍历任何的集合,包括实现了 [Sequence](https://developer.apple.com/documentation/swift/sequence) 协议的自定义类或集合类型。
|
||||
|
||||
## While 循环 {#while-loops}
|
||||
|
||||
`while` 循环会一直运行一段语句直到条件变成 `false`。这类循环适合使用在第一次迭代前,迭代次数未知的情况下。Swift 提供两种 `while` 循环形式:
|
||||
|
||||
* `while` 循环,每次在循环开始时计算条件是否符合;
|
||||
* `repeat-while` 循环,每次在循环结束时计算条件是否符合。
|
||||
|
||||
### While {#while}
|
||||
|
||||
`while` 循环从计算一个条件开始。如果条件为 `true`,会重复运行一段语句,直到条件变为 `false`。
|
||||
|
||||
下面是 `while` 循环的一般格式:
|
||||
|
||||
```swift
|
||||
while condition {
|
||||
statements
|
||||
}
|
||||
```
|
||||
|
||||
下面的例子来玩一个叫做*蛇和梯子*(也叫做*滑道和梯子*)的小游戏:
|
||||
|
||||

|
||||
|
||||
游戏的规则如下:
|
||||
|
||||
* 游戏盘面包括 25 个方格,游戏目标是达到或者超过第 25 个方格;
|
||||
* 每一轮,你通过掷一个六面体骰子来确定你移动方块的步数,移动的路线由上图中横向的虚线所示;
|
||||
* 如果在某轮结束,你移动到了梯子的底部,可以顺着梯子爬上去;
|
||||
* 如果在某轮结束,你移动到了蛇的头部,你会顺着蛇的身体滑下去。
|
||||
|
||||
游戏盘面可以使用一个 `Int` 数组来表达。数组的长度由一个 `finalSquare` 常量储存,用来初始化数组和检测最终胜利条件。游戏盘面由 26 个 `Int` 0 值初始化,而不是 25 个(由 `0` 到 `25`,一共 26 个):
|
||||
|
||||
```swift
|
||||
let finalSquare = 25
|
||||
var board = [Int](repeating: 0, count: finalSquare + 1)
|
||||
```
|
||||
|
||||
一些方格被设置成特定的值来表示有蛇或者梯子。梯子底部的方格是一个正值,使你可以向上移动,蛇头处的方格是一个负值,会让你向下移动:
|
||||
|
||||
```swift
|
||||
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
|
||||
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
|
||||
```
|
||||
|
||||
3 号方格是梯子的底部,会让你向上移动到 11 号方格,我们使用 `board[03]` 等于 `+08`(来表示 `11` 和 `3` 之间的差值)。为了对齐语句,这里使用了一元正运算符(`+i`)和一元负运算符(`-i`),并且小于 10 的数字都使用 0 补齐(这些语法的技巧不是必要的,只是为了让代码看起来更加整洁)。
|
||||
|
||||
玩家由左下角空白处编号为 0 的方格开始游戏。玩家第一次掷骰子后才会进入游戏盘面:
|
||||
|
||||
```swift
|
||||
var square = 0
|
||||
var diceRoll = 0
|
||||
while square < finalSquare {
|
||||
// 掷骰子
|
||||
diceRoll += 1
|
||||
if diceRoll == 7 { diceRoll = 1 }
|
||||
// 根据点数移动
|
||||
square += diceRoll
|
||||
if square < board.count {
|
||||
// 如果玩家还在棋盘上,顺着梯子爬上去或者顺着蛇滑下去
|
||||
square += board[square]
|
||||
}
|
||||
}
|
||||
print("Game over!")
|
||||
```
|
||||
|
||||
本例中使用了最简单的方法来模拟掷骰子。`diceRoll` 的值并不是一个随机数,而是以 `0` 为初始值,之后每一次 `while` 循环,`diceRoll` 的值增加 1 ,然后检测是否超出了最大值。当 `diceRoll` 的值等于 7 时,就超过了骰子的最大值,会被重置为 `1`。所以 `diceRoll` 的取值顺序会一直是 `1`,`2`,`3`,`4`,`5`,`6`,`1`,`2` 等。
|
||||
|
||||
掷完骰子后,玩家向前移动 `diceRoll` 个方格,如果玩家移动超过了第 25 个方格,这个时候游戏将会结束,为了应对这种情况,代码会首先判断 `square` 的值是否小于 `board` 的 `count` 属性,只有小于才会在 `board[square]` 上增加 `square`,来向前或向后移动(遇到了梯子或者蛇)。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果没有这个检测(`square < board.count`),`board[square]` 可能会越界访问 `board` 数组,导致运行时错误。
|
||||
|
||||
当本轮 `while` 循环运行完毕,会再检测循环条件是否需要再运行一次循环。如果玩家移动到或者超过第 25 个方格,循环条件结果为 `false`,此时游戏结束。
|
||||
|
||||
`while` 循环比较适合本例中的这种情况,因为在 `while` 循环开始时,我们并不知道游戏要跑多久,只有在达成指定条件时循环才会结束。
|
||||
|
||||
### Repeat-While {#repeat-while}
|
||||
|
||||
`while` 循环的另外一种形式是 `repeat-while`,它和 `while` 的区别是在判断循环条件之前,先执行一次循环的代码块。然后重复循环直到条件为 `false`。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> Swift 语言的 `repeat-while` 循环和其他语言中的 `do-while` 循环是类似的。
|
||||
|
||||
下面是 `repeat-while` 循环的一般格式:
|
||||
|
||||
```swift
|
||||
repeat {
|
||||
statements
|
||||
} while condition
|
||||
```
|
||||
|
||||
还是*蛇和梯子*的游戏,使用 `repeat-while` 循环来替代 `while` 循环。`finalSquare`、`board`、`square` 和 `diceRoll` 的值初始化同 `while` 循环时一样:
|
||||
|
||||
```swift
|
||||
let finalSquare = 25
|
||||
var board = [Int](repeating: 0, count: finalSquare + 1)
|
||||
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
|
||||
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
|
||||
var square = 0
|
||||
var diceRoll = 0
|
||||
```
|
||||
|
||||
`repeat-while` 的循环版本,循环中*第一步*就需要去检测是否在梯子或者蛇的方块上。没有梯子会让玩家直接上到第 25 个方格,所以玩家不会通过梯子直接赢得游戏。这样在循环开始时先检测是否踩在梯子或者蛇上是安全的。
|
||||
|
||||
游戏开始时,玩家在第 0 个方格上,`board[0]` 一直等于 0, 不会有什么影响:
|
||||
|
||||
```swift
|
||||
repeat {
|
||||
// 顺着梯子爬上去或者顺着蛇滑下去
|
||||
square += board[square]
|
||||
// 掷骰子
|
||||
diceRoll += 1
|
||||
if diceRoll == 7 { diceRoll = 1 }
|
||||
// 根据点数移动
|
||||
square += diceRoll
|
||||
} while square < finalSquare
|
||||
print("Game over!")
|
||||
```
|
||||
|
||||
检测完玩家是否踩在梯子或者蛇上之后,开始掷骰子,然后玩家向前移动 `diceRoll` 个方格,本轮循环结束。
|
||||
|
||||
循环条件(`while square < finalSquare`)和 `while` 方式相同,但是只会在循环结束后进行计算。在这个游戏中,`repeat-while` 表现得比 `while` 循环更好。`repeat-while` 方式会在条件判断 `square` 没有超出后直接运行 `square += board[square]`,这种方式可以比起前面 `while` 循环的版本,可以省去数组越界的检查。
|
||||
|
||||
## 条件语句 {#conditional-statement}
|
||||
|
||||
根据特定的条件执行特定的代码通常是十分有用的。当错误发生时,你可能想运行额外的代码;或者,当值太大或太小时,向用户显示一条消息。要实现这些功能,你就需要使用*条件语句*。
|
||||
|
||||
Swift 提供两种类型的条件语句:`if` 语句和 `switch` 语句。通常,当条件较为简单且可能的情况很少时,使用 `if` 语句。而 `switch` 语句更适用于条件较复杂、有更多排列组合的时候。并且 `switch` 在需要用到模式匹配(pattern-matching)的情况下会更有用。
|
||||
|
||||
### If {#if}
|
||||
|
||||
`if` 语句最简单的形式就是只包含一个条件,只有该条件为 `true` 时,才执行相关代码:
|
||||
|
||||
```swift
|
||||
var temperatureInFahrenheit = 30
|
||||
if temperatureInFahrenheit <= 32 {
|
||||
print("It's very cold. Consider wearing a scarf.")
|
||||
}
|
||||
// 输出“It's very cold. Consider wearing a scarf.”
|
||||
```
|
||||
|
||||
上面的例子会判断温度是否小于等于 32 华氏度(水的冰点)。如果是,则打印一条消息;否则,不打印任何消息,继续执行 `if` 块后面的代码。
|
||||
|
||||
当然,`if` 语句允许二选一执行,叫做 `else` 从句。也就是当条件为 `false` 时,执行 *else 语句*:
|
||||
|
||||
```swift
|
||||
temperatureInFahrenheit = 40
|
||||
if temperatureInFahrenheit <= 32 {
|
||||
print("It's very cold. Consider wearing a scarf.")
|
||||
} else {
|
||||
print("It's not that cold. Wear a t-shirt.")
|
||||
}
|
||||
// 输出“It's not that cold. Wear a t-shirt.”
|
||||
```
|
||||
|
||||
显然,这两条分支中总有一条会被执行。由于温度已升至 40 华氏度,不算太冷,没必要再围围巾。因此,`else` 分支就被触发了。
|
||||
|
||||
你可以把多个 `if` 语句链接在一起,来实现更多分支:
|
||||
|
||||
```swift
|
||||
temperatureInFahrenheit = 90
|
||||
if temperatureInFahrenheit <= 32 {
|
||||
print("It's very cold. Consider wearing a scarf.")
|
||||
} else if temperatureInFahrenheit >= 86 {
|
||||
print("It's really warm. Don't forget to wear sunscreen.")
|
||||
} else {
|
||||
print("It's not that cold. Wear a t-shirt.")
|
||||
}
|
||||
// 输出“It's really warm. Don't forget to wear sunscreen.”
|
||||
```
|
||||
|
||||
在上面的例子中,额外的 `if` 语句用于判断是不是特别热。而最后的 `else` 语句被保留了下来,用于打印既不冷也不热时的消息。
|
||||
|
||||
实际上,当不需要完整判断情况的时候,最后的 `else` 语句是可选的:
|
||||
|
||||
```swift
|
||||
temperatureInFahrenheit = 72
|
||||
if temperatureInFahrenheit <= 32 {
|
||||
print("It's very cold. Consider wearing a scarf.")
|
||||
} else if temperatureInFahrenheit >= 86 {
|
||||
print("It's really warm. Don't forget to wear sunscreen.")
|
||||
}
|
||||
```
|
||||
|
||||
在这个例子中,由于既不冷也不热,所以不会触发 `if` 或 `else if` 分支,也就不会打印任何消息。
|
||||
|
||||
### Switch {#switch}
|
||||
|
||||
`switch` 语句会尝试把某个值与若干个模式(pattern)进行匹配。根据第一个匹配成功的模式,`switch` 语句会执行对应的代码。当有可能的情况较多时,通常用 `switch` 语句替换 `if` 语句。
|
||||
|
||||
`switch` 语句最简单的形式就是把某个值与一个或若干个相同类型的值作比较:
|
||||
|
||||
```swift
|
||||
switch some value to consider {
|
||||
case value 1:
|
||||
respond to value 1
|
||||
case value 2,
|
||||
value 3:
|
||||
respond to value 2 or 3
|
||||
default:
|
||||
otherwise, do something else
|
||||
}
|
||||
```
|
||||
|
||||
`switch` 语句由*多个 case* 构成,每个由 `case` 关键字开始。为了匹配某些更特定的值,Swift 提供了几种方法来进行更复杂的模式匹配,这些模式将在本节的稍后部分提到。
|
||||
|
||||
与 `if` 语句类似,每一个 case 都是代码执行的一条分支。`switch` 语句会决定哪一条分支应该被执行,这个流程被称作根据给定的值*切换(switching)*。
|
||||
|
||||
`switch` 语句必须是完备的。这就是说,每一个可能的值都必须至少有一个 case 分支与之对应。在某些不可能涵盖所有值的情况下,你可以使用默认(`default`)分支来涵盖其它所有没有对应的值,这个默认分支必须在 `switch` 语句的最后面。
|
||||
|
||||
下面的例子使用 `switch` 语句来匹配一个名为 `someCharacter` 的小写字符:
|
||||
|
||||
```swift
|
||||
let someCharacter: Character = "z"
|
||||
switch someCharacter {
|
||||
case "a":
|
||||
print("The first letter of the alphabet")
|
||||
case "z":
|
||||
print("The last letter of the alphabet")
|
||||
default:
|
||||
print("Some other character")
|
||||
}
|
||||
// 输出“The last letter of the alphabet”
|
||||
```
|
||||
|
||||
在这个例子中,第一个 case 分支用于匹配第一个英文字母 `a`,第二个 case 分支用于匹配最后一个字母 `z`。因为 `switch` 语句必须有一个 case 分支用于覆盖所有可能的字符,而不仅仅是所有的英文字母,所以 switch 语句使用 `default` 分支来匹配除了 `a` 和 `z` 外的所有值,这个分支保证了 switch 语句的完备性。
|
||||
|
||||
#### 不存在隐式的贯穿 {#no-implicit-fallthrough}
|
||||
|
||||
与 C 和 Objective-C 中的 `switch` 语句不同,在 Swift 中,当匹配的 case 分支中的代码执行完毕后,程序会终止 `switch` 语句,而不会继续执行下一个 case 分支。这也就是说,不需要在 case 分支中显式地使用 `break` 语句。这使得 `switch` 语句更安全、更易用,也避免了漏写 `break` 语句导致多个语言被执行的错误。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 虽然在 Swift 中 `break` 不是必须的,但你依然可以在 case 分支中的代码执行完毕前使用 `break` 跳出,详情请参见 [Switch 语句中的 break](#break-in-a-switch-statement)。
|
||||
|
||||
每一个 case 分支都*必须*包含至少一条语句。像下面这样书写代码是无效的,因为第一个 case 分支是空的:
|
||||
|
||||
```swift
|
||||
let anotherCharacter: Character = "a"
|
||||
switch anotherCharacter {
|
||||
case "a": // 无效,这个分支下面没有语句
|
||||
case "A":
|
||||
print("The letter A")
|
||||
default:
|
||||
print("Not the letter A")
|
||||
}
|
||||
// 这段代码会报编译错误
|
||||
```
|
||||
|
||||
不像 C 语言里的 `switch` 语句,在 Swift 中,`switch` 语句不会一起匹配 `"a"` 和 `"A"`。相反的,上面的代码会引起编译期错误:`case "a": 不包含任何可执行语句 `——这就避免了意外地从一个 case 分支贯穿到另外一个,使得代码更安全、也更直观。
|
||||
|
||||
为了让单个 case 同时匹配 `a` 和 `A`,可以将这个两个值组合成一个复合匹配,并且用逗号分开:
|
||||
|
||||
```swift
|
||||
let anotherCharacter: Character = "a"
|
||||
switch anotherCharacter {
|
||||
case "a", "A":
|
||||
print("The letter A")
|
||||
default:
|
||||
print("Not the letter A")
|
||||
}
|
||||
// 输出“The letter A”
|
||||
```
|
||||
|
||||
为了可读性,符合匹配可以写成多行形式,详情请参考 [复合匹配](#compound-cases)。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果想要显式贯穿 case 分支,请使用 `fallthrough` 语句,详情请参考 [贯穿](#fallthrough)。
|
||||
|
||||
#### 区间匹配 {#interval-matching}
|
||||
|
||||
case 分支的模式也可以是一个值的区间。下面的例子展示了如何使用区间匹配来输出任意数字对应的自然语言格式:
|
||||
|
||||
```swift
|
||||
let approximateCount = 62
|
||||
let countedThings = "moons orbiting Saturn"
|
||||
let naturalCount: String
|
||||
switch approximateCount {
|
||||
case 0:
|
||||
naturalCount = "no"
|
||||
case 1..<5:
|
||||
naturalCount = "a few"
|
||||
case 5..<12:
|
||||
naturalCount = "several"
|
||||
case 12..<100:
|
||||
naturalCount = "dozens of"
|
||||
case 100..<1000:
|
||||
naturalCount = "hundreds of"
|
||||
default:
|
||||
naturalCount = "many"
|
||||
}
|
||||
print("There are \(naturalCount) \(countedThings).")
|
||||
// 输出“There are dozens of moons orbiting Saturn.”
|
||||
```
|
||||
|
||||
在上例中,`approximateCount` 在一个 `switch` 声明中被评估。每一个 `case` 都与之进行比较。因为 `approximateCount` 落在了 12 到 100 的区间,所以 `naturalCount` 等于 `"dozens of"` 值,并且此后的执行跳出了 `switch` 语句。
|
||||
|
||||
#### 元组 {#tuples}
|
||||
|
||||
我们可以使用元组在同一个 `switch` 语句中测试多个值。元组中的元素可以是值,也可以是区间。另外,使用下划线(`_`)来匹配所有可能的值。
|
||||
|
||||
下面的例子展示了如何使用一个 `(Int, Int)` 类型的元组来分类下图中的点 (x, y):
|
||||
|
||||
```swift
|
||||
let somePoint = (1, 1)
|
||||
switch somePoint {
|
||||
case (0, 0):
|
||||
print("\(somePoint) is at the origin")
|
||||
case (_, 0):
|
||||
print("\(somePoint) is on the x-axis")
|
||||
case (0, _):
|
||||
print("\(somePoint) is on the y-axis")
|
||||
case (-2...2, -2...2):
|
||||
print("\(somePoint) is inside the box")
|
||||
default:
|
||||
print("\(somePoint) is outside of the box")
|
||||
}
|
||||
// 输出“(1, 1) is inside the box”
|
||||
```
|
||||
|
||||

|
||||
|
||||
在上面的例子中,`switch` 语句会判断某个点是否是原点 (0, 0),是否在红色的 x 轴上,是否在橘黄色的 y 轴上,是否在一个以原点为中心的4x4的蓝色矩形里,或者在这个矩形外面。
|
||||
|
||||
不像 C 语言,Swift 允许多个 case 匹配同一个值。实际上,在这个例子中,点 (0, 0)可以匹配所有_四个 case_。但是,如果存在多个匹配,那么只会执行第一个被匹配到的 case 分支。考虑点 (0, 0)会首先匹配 `case (0, 0)`,因此剩下的能够匹配的分支都会被忽视掉。
|
||||
|
||||
#### 值绑定(Value Bindings) {#value-bindings}
|
||||
|
||||
case 分支允许将匹配的值声明为临时常量或变量,并且在 case 分支体内使用 —— 这种行为被称为*值绑定*(value binding),因为匹配的值在 case 分支体内,与临时的常量或变量绑定。
|
||||
|
||||
下面的例子将下图中的点 (x, y),使用 `(Int, Int)` 类型的元组表示,然后分类表示:
|
||||
|
||||
```swift
|
||||
let anotherPoint = (2, 0)
|
||||
switch anotherPoint {
|
||||
case (let x, 0):
|
||||
print("on the x-axis with an x value of \(x)")
|
||||
case (0, let y):
|
||||
print("on the y-axis with a y value of \(y)")
|
||||
case let (x, y):
|
||||
print("somewhere else at (\(x), \(y))")
|
||||
}
|
||||
// 输出“on the x-axis with an x value of 2”
|
||||
```
|
||||
|
||||

|
||||
|
||||
在上面的例子中,`switch` 语句会判断某个点是否在红色的 x 轴上,是否在橘黄色的 y 轴上,或者不在坐标轴上。
|
||||
|
||||
这三个 case 都声明了常量 `x` 和 `y` 的占位符,用于临时获取元组 `anotherPoint` 的一个或两个值。第一个 case ——`case (let x, 0)` 将匹配一个纵坐标为 `0` 的点,并把这个点的横坐标赋给临时的常量 `x`。类似的,第二个 case ——`case (0, let y)` 将匹配一个横坐标为 `0` 的点,并把这个点的纵坐标赋给临时的常量 `y`。
|
||||
|
||||
一旦声明了这些临时的常量,它们就可以在其对应的 case 分支里使用。在这个例子中,它们用于打印给定点的类型。
|
||||
|
||||
请注意,这个 `switch` 语句不包含默认分支。这是因为最后一个 case ——`case let(x, y)` 声明了一个可以匹配余下所有值的元组。这使得 `switch` 语句已经完备了,因此不需要再书写默认分支。
|
||||
|
||||
#### Where {#where}
|
||||
|
||||
case 分支的模式可以使用 `where` 语句来判断额外的条件。
|
||||
|
||||
下面的例子把下图中的点 (x, y)进行了分类:
|
||||
|
||||
```swift
|
||||
let yetAnotherPoint = (1, -1)
|
||||
switch yetAnotherPoint {
|
||||
case let (x, y) where x == y:
|
||||
print("(\(x), \(y)) is on the line x == y")
|
||||
case let (x, y) where x == -y:
|
||||
print("(\(x), \(y)) is on the line x == -y")
|
||||
case let (x, y):
|
||||
print("(\(x), \(y)) is just some arbitrary point")
|
||||
}
|
||||
// 输出“(1, -1) is on the line x == -y”
|
||||
```
|
||||
|
||||

|
||||
|
||||
在上面的例子中,`switch` 语句会判断某个点是否在绿色的对角线 `x == y` 上,是否在紫色的对角线 `x == -y` 上,或者不在对角线上。
|
||||
|
||||
这三个 case 都声明了常量 `x` 和 `y` 的占位符,用于临时获取元组 `yetAnotherPoint` 的两个值。这两个常量被用作 `where` 语句的一部分,从而创建一个动态的过滤器(filter)。当且仅当 `where` 语句的条件为 `true` 时,匹配到的 case 分支才会被执行。
|
||||
|
||||
就像是值绑定中的例子,由于最后一个 case 分支匹配了余下所有可能的值,`switch` 语句就已经完备了,因此不需要再书写默认分支。
|
||||
|
||||
#### 复合型 Cases {#compound-cases}
|
||||
|
||||
当多个条件可以使用同一种方法来处理时,可以将这几种可能放在同一个 `case` 后面,并且用逗号隔开。当 case 后面的任意一种模式匹配的时候,这条分支就会被匹配。并且,如果匹配列表过长,还可以分行书写:
|
||||
|
||||
```swift
|
||||
let someCharacter: Character = "e"
|
||||
switch someCharacter {
|
||||
case "a", "e", "i", "o", "u":
|
||||
print("\(someCharacter) is a vowel")
|
||||
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
|
||||
"n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
|
||||
print("\(someCharacter) is a consonant")
|
||||
default:
|
||||
print("\(someCharacter) is not a vowel or a consonant")
|
||||
}
|
||||
// 输出“e is a vowel”
|
||||
```
|
||||
|
||||
这个 `switch` 语句中的第一个 case,匹配了英语中的五个小写元音字母。相似的,第二个 case 匹配了英语中所有的小写辅音字母。最终,`default` 分支匹配了其它所有字符。
|
||||
|
||||
复合匹配同样可以包含值绑定。复合匹配里所有的匹配模式,都必须包含相同的值绑定。并且每一个绑定都必须获取到相同类型的值。这保证了,无论复合匹配中的哪个模式发生了匹配,分支体内的代码,都能获取到绑定的值,并且绑定的值都有一样的类型。
|
||||
|
||||
```swift
|
||||
let stillAnotherPoint = (9, 0)
|
||||
switch stillAnotherPoint {
|
||||
case (let distance, 0), (0, let distance):
|
||||
print("On an axis, \(distance) from the origin")
|
||||
default:
|
||||
print("Not on an axis")
|
||||
}
|
||||
// 输出“On an axis, 9 from the origin”
|
||||
```
|
||||
|
||||
上面的 case 有两个模式:`(let distance, 0)` 匹配了在 x 轴上的值,`(0, let distance)` 匹配了在 y 轴上的值。两个模式都绑定了 `distance`,并且 `distance` 在两种模式下,都是整型——这意味着分支体内的代码,只要 case 匹配,都可以获取到 `distance` 值。
|
||||
|
||||
## 控制转移语句 {#control-transfer-statements}
|
||||
|
||||
控制转移语句改变你代码的执行顺序,通过它可以实现代码的跳转。Swift 有五种控制转移语句:
|
||||
|
||||
- `continue`
|
||||
- `break`
|
||||
- `fallthrough`
|
||||
- `return`
|
||||
- `throw`
|
||||
|
||||
我们将会在下面讨论 `continue`、`break` 和 `fallthrough` 语句。`return` 语句将会在 [函数](./06_Functions.md) 章节讨论,`throw` 语句会在 [错误抛出](./17_Error_Handling.md#throwing-errors) 章节讨论。
|
||||
|
||||
### Continue {#continue}
|
||||
|
||||
`continue` 语句告诉一个循环体立刻停止本次循环,重新开始下次循环。就好像在说“本次循环我已经执行完了”,但是并不会离开整个循环体。
|
||||
|
||||
下面的例子把一个小写字符串中的元音字母和空格字符移除,生成了一个含义模糊的短句:
|
||||
|
||||
```swift
|
||||
let puzzleInput = "great minds think alike"
|
||||
var puzzleOutput = ""
|
||||
for character in puzzleInput {
|
||||
switch character {
|
||||
case "a", "e", "i", "o", "u", " ":
|
||||
continue
|
||||
default:
|
||||
puzzleOutput.append(character)
|
||||
}
|
||||
}
|
||||
print(puzzleOutput)
|
||||
// 输出“grtmndsthnklk”
|
||||
```
|
||||
|
||||
在上面的代码中,只要匹配到元音字母或者空格字符,就调用 `continue` 语句,使本次循环结束,重新开始下次循环。这种行为使 `switch` 匹配到元音字母和空格字符时不做处理,而不是让每一个匹配到的字符都被打印。
|
||||
|
||||
### Break {#break}
|
||||
|
||||
`break` 语句会立刻结束整个控制流的执行。`break` 可以在 `switch` 或循环语句中使用,用来提前结束 `switch` 或循环语句。
|
||||
|
||||
#### 循环语句中的 break {#break-in-a-loop-statement}
|
||||
|
||||
当在一个循环体中使用 `break` 时,会立刻中断该循环体的执行,然后跳转到表示循环体结束的大括号(`}`)后的第一行代码。不会再有本次循环的代码被执行,也不会再有下次的循环产生。
|
||||
|
||||
#### Switch 语句中的 break {#break-in-a-switch-statement}
|
||||
|
||||
当在一个 `switch` 代码块中使用 `break` 时,会立即中断该 `switch` 代码块的执行,并且跳转到表示 `switch` 代码块结束的大括号(`}`)后的第一行代码。
|
||||
|
||||
这种特性可以被用来匹配或者忽略一个或多个分支。因为 Swift 的 `switch` 需要包含所有的分支而且不允许有为空的分支,有时为了使你的意图更明显,需要特意匹配或者忽略某个分支。那么当你想忽略某个分支时,可以在该分支内写上 `break` 语句。当那个分支被匹配到时,分支内的 `break` 语句立即结束 `switch` 代码块。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 当一个 `switch` 分支仅仅包含注释时,会被报编译时错误。注释不是代码语句而且也不能让 `switch` 分支达到被忽略的效果。你应该使用 `break` 来忽略某个分支。
|
||||
|
||||
下面的例子通过 `switch` 来判断一个 `Character` 值是否代表下面四种语言之一。为了简洁,多个值被包含在了同一个分支情况中。
|
||||
|
||||
```swift
|
||||
let numberSymbol: Character = "三" // 简体中文里的数字 3
|
||||
var possibleIntegerValue: Int?
|
||||
switch numberSymbol {
|
||||
case "1", "١", "一", "๑":
|
||||
possibleIntegerValue = 1
|
||||
case "2", "٢", "二", "๒":
|
||||
possibleIntegerValue = 2
|
||||
case "3", "٣", "三", "๓":
|
||||
possibleIntegerValue = 3
|
||||
case "4", "٤", "四", "๔":
|
||||
possibleIntegerValue = 4
|
||||
default:
|
||||
break
|
||||
}
|
||||
if let integerValue = possibleIntegerValue {
|
||||
print("The integer value of \(numberSymbol) is \(integerValue).")
|
||||
} else {
|
||||
print("An integer value could not be found for \(numberSymbol).")
|
||||
}
|
||||
// 输出“The integer value of 三 is 3.”
|
||||
```
|
||||
|
||||
这个例子检查 `numberSymbol` 是否是拉丁,阿拉伯,中文或者泰语中的 `1` 到 `4` 之一。如果被匹配到,该 `switch` 分支语句给 `Int?` 类型变量 `possibleIntegerValue` 设置一个整数值。
|
||||
|
||||
当 `switch` 代码块执行完后,接下来的代码通过使用可选绑定来判断 `possibleIntegerValue` 是否曾经被设置过值。因为是可选类型的缘故,`possibleIntegerValue` 有一个隐式的初始值 `nil`,所以仅仅当 `possibleIntegerValue` 曾被 `switch` 代码块的前四个分支中的某个设置过一个值时,可选的绑定才会被判定为成功。
|
||||
|
||||
在上面的例子中,想要把 `Character` 所有的的可能性都枚举出来是不现实的,所以使用 `default` 分支来包含所有上面没有匹配到字符的情况。由于这个 `default` 分支不需要执行任何动作,所以它只写了一条 `break` 语句。一旦落入到 `default` 分支中后,`break` 语句就完成了该分支的所有代码操作,代码继续向下,开始执行 `if let` 语句。
|
||||
|
||||
### 贯穿(Fallthrough) {#fallthrough}
|
||||
|
||||
在 Swift 里,`switch` 语句不会从上一个 case 分支跳转到下一个 case 分支中。相反,只要第一个匹配到的 case 分支完成了它需要执行的语句,整个 `switch` 代码块完成了它的执行。相比之下,C 语言要求你显式地插入 `break` 语句到每个 case 分支的末尾来阻止自动落入到下一个 case 分支中。Swift 的这种避免默认落入到下一个分支中的特性意味着它的 `switch` 功能要比 C 语言的更加清晰和可预测,可以避免无意识地执行多个 case 分支从而引发的错误。
|
||||
|
||||
如果你确实需要 C 风格的贯穿的特性,你可以在每个需要该特性的 case 分支中使用 `fallthrough` 关键字。下面的例子使用 `fallthrough` 来创建一个数字的描述语句。
|
||||
|
||||
```swift
|
||||
let integerToDescribe = 5
|
||||
var description = "The number \(integerToDescribe) is"
|
||||
switch integerToDescribe {
|
||||
case 2, 3, 5, 7, 11, 13, 17, 19:
|
||||
description += " a prime number, and also"
|
||||
fallthrough
|
||||
default:
|
||||
description += " an integer."
|
||||
}
|
||||
print(description)
|
||||
// 输出“The number 5 is a prime number, and also an integer.”
|
||||
```
|
||||
|
||||
这个例子定义了一个 `String` 类型的变量 `description` 并且给它设置了一个初始值。函数使用 `switch` 逻辑来判断 `integerToDescribe` 变量的值。当 `integerToDescribe` 的值属于列表中的质数之一时,该函数在 `description` 后添加一段文字,来表明这个数字是一个质数。然后它使用 `fallthrough` 关键字来“贯穿”到 `default` 分支中。`default` 分支在 `description` 的最后添加一段额外的文字,至此 `switch` 代码块执行完了。
|
||||
|
||||
如果 `integerToDescribe` 的值不属于列表中的任何质数,那么它不会匹配到第一个 `switch` 分支。而这里没有其他特别的分支情况,所以 `integerToDescribe` 匹配到 `default` 分支中。
|
||||
|
||||
当 `switch` 代码块执行完后,使用 `print(_:separator:terminator:)` 函数打印该数字的描述。在这个例子中,数字 `5` 被准确的识别为了一个质数。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> `fallthrough` 关键字不会检查它下一个将会落入执行的 case 中的匹配条件。`fallthrough` 简单地使代码继续连接到下一个 case 中的代码,这和 C 语言标准中的 `switch` 语句特性是一样的。
|
||||
|
||||
### 带标签的语句 {#labeled-statements}
|
||||
|
||||
在 Swift 中,你可以在循环体和条件语句中嵌套循环体和条件语句来创造复杂的控制流结构。并且,循环体和条件语句都可以使用 `break` 语句来提前结束整个代码块。因此,显式地指明 `break` 语句想要终止的是哪个循环体或者条件语句,会很有用。类似地,如果你有许多嵌套的循环体,显式指明 `continue` 语句想要影响哪一个循环体也会非常有用。
|
||||
|
||||
为了实现这个目的,你可以使用标签(*statement label*)来标记一个循环体或者条件语句,对于一个条件语句,你可以使用 `break` 加标签的方式,来结束这个被标记的语句。对于一个循环语句,你可以使用 `break` 或者 `continue` 加标签,来结束或者继续这条被标记语句的执行。
|
||||
|
||||
声明一个带标签的语句是通过在该语句的关键词的同一行前面放置一个标签,作为这个语句的前导关键字(introducor keyword),并且该标签后面跟随一个冒号。下面是一个针对 `while` 循环体的标签语法,同样的规则适用于所有的循环体和条件语句。
|
||||
|
||||
```swift
|
||||
label name: while condition {
|
||||
statements
|
||||
}
|
||||
```
|
||||
|
||||
下面的例子是前面章节中*蛇和梯子*的适配版本,在此版本中,我们将使用一个带有标签的 `while` 循环体中调用 `break` 和 `continue` 语句。这次,游戏增加了一条额外的规则:
|
||||
|
||||
- 为了获胜,你必须*刚好*落在第 25 个方块中。
|
||||
|
||||
如果某次掷骰子使你的移动超出第 25 个方块,你必须重新掷骰子,直到你掷出的骰子数刚好使你能落在第 25 个方块中。
|
||||
|
||||
游戏的棋盘和之前一样:
|
||||
|
||||

|
||||
|
||||
`finalSquare`、`board`、`square` 和 `diceRoll` 值被和之前一样的方式初始化:
|
||||
|
||||
```swift
|
||||
let finalSquare = 25
|
||||
var board = [Int](repeating: 0, count: finalSquare + 1)
|
||||
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
|
||||
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
|
||||
var square = 0
|
||||
var diceRoll = 0
|
||||
```
|
||||
|
||||
这个版本的游戏使用 `while` 循环和 `switch` 语句来实现游戏的逻辑。`while` 循环有一个标签名 `gameLoop`,来表明它是游戏的主循环。
|
||||
|
||||
该 `while` 循环体的条件判断语句是 `while square !=finalSquare`,这表明你必须刚好落在方格25中。
|
||||
|
||||
```swift
|
||||
gameLoop: while square != finalSquare {
|
||||
diceRoll += 1
|
||||
if diceRoll == 7 { diceRoll = 1 }
|
||||
switch square + diceRoll {
|
||||
case finalSquare:
|
||||
// 骰子数刚好使玩家移动到最终的方格里,游戏结束。
|
||||
break gameLoop
|
||||
case let newSquare where newSquare > finalSquare:
|
||||
// 骰子数将会使玩家的移动超出最后的方格,那么这种移动是不合法的,玩家需要重新掷骰子
|
||||
continue gameLoop
|
||||
default:
|
||||
// 合法移动,做正常的处理
|
||||
square += diceRoll
|
||||
square += board[square]
|
||||
}
|
||||
}
|
||||
print("Game over!")
|
||||
```
|
||||
|
||||
每次循环迭代开始时掷骰子。与之前玩家掷完骰子就立即移动不同,这里使用了 `switch` 语句来考虑每次移动可能产生的结果,从而决定玩家本次是否能够移动。
|
||||
|
||||
- 如果骰子数刚好使玩家移动到最终的方格里,游戏结束。`break gameLoop` 语句跳转控制去执行 `while` 循环体后的第一行代码,意味着游戏结束。
|
||||
- 如果骰子数将会使玩家的移动超出最后的方格,那么这种移动是不合法的,玩家需要重新掷骰子。`continue gameLoop` 语句结束本次 `while` 循环,开始下一次循环。
|
||||
- 在剩余的所有情况中,骰子数产生的都是合法的移动。玩家向前移动 `diceRoll` 个方格,然后游戏逻辑再处理玩家当前是否处于蛇头或者梯子的底部。接着本次循环结束,控制跳转到 `while` 循环体的条件判断语句处,再决定是否需要继续执行下次循环。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果上述的 `break` 语句没有使用 `gameLoop` 标签,那么它将会中断 `switch` 语句而不是 `while` 循环。使用 `gameLoop` 标签清晰的表明了 `break` 想要中断的是哪个代码块。
|
||||
>
|
||||
> 同时请注意,当调用 `continue gameLoop` 去跳转到下一次循环迭代时,这里使用 `gameLoop` 标签并不是严格必须的。因为在这个游戏中,只有一个循环体,所以 `continue` 语句会影响到哪个循环体是没有歧义的。然而,`continue` 语句使用 `gameLoop` 标签也是没有危害的。这样做符合标签的使用规则,同时参照旁边的 `break gameLoop`,能够使游戏的逻辑更加清晰和易于理解。
|
||||
|
||||
## 提前退出 {#early-exit}
|
||||
|
||||
像 `if` 语句一样,`guard` 的执行取决于一个表达式的布尔值。我们可以使用 `guard` 语句来要求条件必须为真时,以执行 `guard` 语句后的代码。不同于 `if` 语句,一个 `guard` 语句总是有一个 `else` 从句,如果条件不为真则执行 `else` 从句中的代码。
|
||||
|
||||
```swift
|
||||
func greet(person: [String: String]) {
|
||||
guard let name = person["name"] else {
|
||||
return
|
||||
}
|
||||
|
||||
print("Hello \(name)!")
|
||||
|
||||
guard let location = person["location"] else {
|
||||
print("I hope the weather is nice near you.")
|
||||
return
|
||||
}
|
||||
|
||||
print("I hope the weather is nice in \(location).")
|
||||
}
|
||||
|
||||
greet(person: ["name": "John"])
|
||||
// 输出“Hello John!”
|
||||
// 输出“I hope the weather is nice near you.”
|
||||
greet(person: ["name": "Jane", "location": "Cupertino"])
|
||||
// 输出“Hello Jane!”
|
||||
// 输出“I hope the weather is nice in Cupertino.”
|
||||
```
|
||||
|
||||
如果 `guard` 语句的条件被满足,则继续执行 `guard` 语句大括号后的代码。将变量或者常量的可选绑定作为 `guard` 语句的条件,都可以保护 `guard` 语句后面的代码。
|
||||
|
||||
如果条件不被满足,在 `else` 分支上的代码就会被执行。这个分支必须转移控制以退出 `guard` 语句出现的代码段。它可以用控制转移语句如 `return`、`break`、`continue` 或者 `throw` 做这件事,或者调用一个不返回的方法或函数,例如 `fatalError()`。
|
||||
|
||||
相比于可以实现同样功能的 `if` 语句,按需使用 `guard` 语句会提升我们代码的可读性。它可以使你的代码连贯的被执行而不需要将它包在 `else` 块中,它可以使你在紧邻条件判断的地方,处理违规的情况。
|
||||
|
||||
## 检测 API 可用性 {#checking-api-availability}
|
||||
|
||||
Swift 内置支持检查 API 可用性,这可以确保我们不会在当前部署机器上,不小心地使用了不可用的 API。
|
||||
|
||||
编译器使用 SDK 中的可用信息来验证我们的代码中使用的所有 API 在项目指定的部署目标上是否可用。如果我们尝试使用一个不可用的 API,Swift 会在编译时报错。
|
||||
|
||||
我们在 `if` 或 `guard` 语句中使用 `可用性条件(availability condition)`去有条件的执行一段代码,来在运行时判断调用的 API 是否可用。编译器使用从可用性条件语句中获取的信息去验证,在这个代码块中调用的 API 是否可用。
|
||||
|
||||
```swift
|
||||
if #available(iOS 10, macOS 10.12, *) {
|
||||
// 在 iOS 使用 iOS 10 的 API, 在 macOS 使用 macOS 10.12 的 API
|
||||
} else {
|
||||
// 使用先前版本的 iOS 和 macOS 的 API
|
||||
}
|
||||
```
|
||||
|
||||
以上可用性条件指定,`if` 语句的代码块仅仅在 iOS 10 或 macOS 10.12 及更高版本才运行。最后一个参数,`*`,是必须的,用于指定在所有其它平台中,如果版本号高于你的设备指定的最低版本,if 语句的代码块将会运行。
|
||||
|
||||
在它一般的形式中,可用性条件使用了一个平台名字和版本的列表。平台名字可以是 `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 不可用,语句将不执行
|
||||
}
|
||||
```
|
||||
520
source/02_language_guide/06_Functions.md
Executable file
520
source/02_language_guide/06_Functions.md
Executable file
@ -0,0 +1,520 @@
|
||||
# 函数
|
||||
|
||||
*函数*是一段完成特定任务的独立代码片段。你可以通过给函数命名来标识某个函数的功能,这个名字可以被用来在需要的时候“调用”这个函数来完成它的任务。
|
||||
|
||||
Swift 统一的函数语法非常的灵活,可以用来表示任何函数,包括从最简单的没有参数名字的 C 风格函数,到复杂的带局部和外部参数名的 Objective-C 风格函数。参数可以提供默认值,以简化函数调用。参数也可以既当做传入参数,也当做传出参数,也就是说,一旦函数执行结束,传入的参数值将被修改。
|
||||
|
||||
在 Swift 中,每个函数都有一个由函数的参数值类型和返回值类型组成的类型。你可以把函数类型当做任何其他普通变量类型一样处理,这样就可以更简单地把函数当做别的函数的参数,也可以从其他函数中返回函数。函数的定义可以写在其他函数定义中,这样可以在嵌套函数范围内实现功能封装。
|
||||
|
||||
## 函数的定义与调用 {#Defining-and-Calling-Functions}
|
||||
|
||||
当你定义一个函数时,你可以定义一个或多个有名字和类型的值,作为函数的输入,称为*参数*,也可以定义某种类型的值作为函数执行结束时的输出,称为*返回类型*。
|
||||
|
||||
每个函数有个*函数名*,用来描述函数执行的任务。要使用一个函数时,用函数名来“调用”这个函数,并传给它匹配的输入值(称作*实参*)。函数的实参必须与函数参数表里参数的顺序一致。
|
||||
|
||||
下面例子中的函数的名字是 `greet(person:)`,之所以叫这个名字,是因为这个函数用一个人的名字当做输入,并返回向这个人问候的语句。为了完成这个任务,你需要定义一个输入参数——一个叫做 `person` 的 `String` 值,和一个包含给这个人问候语的 `String` 类型的返回值:
|
||||
|
||||
```swift
|
||||
func greet(person: String) -> String {
|
||||
let greeting = "Hello, " + person + "!"
|
||||
return greeting
|
||||
}
|
||||
```
|
||||
|
||||
所有的这些信息汇总起来成为函数的*定义*,并以 `func` 作为前缀。指定函数返回类型时,用返回箭头 `->`(一个连字符后跟一个右尖括号)后跟返回类型的名称的方式来表示。
|
||||
|
||||
该定义描述了函数的功能,它期望接收什么作为参数和执行结束时它返回的结果是什么类型。这样的定义使得函数可以在别的地方以一种清晰的方式被调用:
|
||||
|
||||
```swift
|
||||
print(greet(person: "Anna"))
|
||||
// 打印“Hello, Anna!”
|
||||
print(greet(person: "Brian"))
|
||||
// 打印“Hello, Brian!”
|
||||
```
|
||||
|
||||
调用 `greet(person:)` 函数时,在圆括号中传给它一个 `String` 类型的实参,例如 `greet(person: "Anna")`。正如上面所示,因为这个函数返回一个 `String` 类型的值,所以 `greet` 可以被包含在 `print(_:separator:terminator:)` 的调用中,用来输出这个函数的返回值。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> `print(_:separator:terminator:)` 函数的第一个参数并没有设置一个标签,而其他的参数因为已经有了默认值,因此是可选的。关于这些函数语法上的变化详见下方关于 函数参数标签和参数名以及默认参数值。
|
||||
|
||||
在 `greet(person:)` 的函数体中,先定义了一个新的名为 `greeting` 的 `String` 常量,同时,把对 `personName` 的问候消息赋值给了 `greeting` 。然后用 `return` 关键字把这个问候返回出去。一旦 `return greeting` 被调用,该函数结束它的执行并返回 `greeting` 的当前值。
|
||||
|
||||
你可以用不同的输入值多次调用 `greet(person:)`。上面的例子展示的是用 `"Anna"` 和 `"Brian"` 调用的结果,该函数分别返回了不同的结果。
|
||||
|
||||
为了简化这个函数的定义,可以将问候消息的创建和返回写成一句:
|
||||
|
||||
```swift
|
||||
func greetAgain(person: String) -> String {
|
||||
return "Hello again, " + person + "!"
|
||||
}
|
||||
print(greetAgain(person: "Anna"))
|
||||
// 打印“Hello again, Anna!”
|
||||
```
|
||||
|
||||
## 函数参数与返回值 {#Function-Parameters-and-Return-Values}
|
||||
|
||||
函数参数与返回值在 Swift 中非常的灵活。你可以定义任何类型的函数,包括从只带一个未名参数的简单函数到复杂的带有表达性参数名和不同参数选项的复杂函数。
|
||||
|
||||
### 无参数函数 {#functions-without-parameters}
|
||||
|
||||
函数可以没有参数。下面这个函数就是一个无参数函数,当被调用时,它返回固定的 `String` 消息:
|
||||
|
||||
```swift
|
||||
func sayHelloWorld() -> String {
|
||||
return "hello, world"
|
||||
}
|
||||
print(sayHelloWorld())
|
||||
// 打印“hello, world”
|
||||
```
|
||||
|
||||
尽管这个函数没有参数,但是定义中在函数名后还是需要一对圆括号。当被调用时,也需要在函数名后写一对圆括号。
|
||||
|
||||
### 多参数函数 {#functions-with-multiple-parameters}
|
||||
|
||||
函数可以有多种输入参数,这些参数被包含在函数的括号之中,以逗号分隔。
|
||||
|
||||
下面这个函数用一个人名和是否已经打过招呼作为输入,并返回对这个人的适当问候语:
|
||||
|
||||
```swift
|
||||
func greet(person: String, alreadyGreeted: Bool) -> String {
|
||||
if alreadyGreeted {
|
||||
return greetAgain(person: person)
|
||||
} else {
|
||||
return greet(person: person)
|
||||
}
|
||||
}
|
||||
print(greet(person: "Tim", alreadyGreeted: true))
|
||||
// 打印“Hello again, Tim!”
|
||||
```
|
||||
|
||||
你可以通过在括号内使用逗号分隔来传递一个 `String` 参数值和一个标识为 `alreadyGreeted` 的 `Bool` 值,来调用 `greet(person:alreadyGreeted:)` 函数。注意这个函数和上面 `greet(person:)` 是不同的。虽然它们都有着同样的名字 `greet`,但是 `greet(person:alreadyGreeted:)` 函数需要两个参数,而 `greet(person:)` 只需要一个参数。
|
||||
|
||||
### 无返回值函数 {#functions-without-return-values}
|
||||
|
||||
函数可以没有返回值。下面是 `greet(person:)` 函数的另一个版本,这个函数直接打印一个 `String` 值,而不是返回它:
|
||||
|
||||
```swift
|
||||
func greet(person: String) {
|
||||
print("Hello, \(person)!")
|
||||
}
|
||||
greet(person: "Dave")
|
||||
// 打印“Hello, Dave!”
|
||||
```
|
||||
|
||||
因为这个函数不需要返回值,所以这个函数的定义中没有返回箭头(->)和返回类型。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 严格地说,即使没有明确定义返回值,该 `greet(Person:)` 函数仍然返回一个值。没有明确定义返回类型的函数的返回一个 `Void` 类型特殊值,该值为一个空元组,写成 ()。
|
||||
|
||||
调用函数时,可以忽略该函数的返回值:
|
||||
|
||||
```swift
|
||||
func printAndCount(string: String) -> Int {
|
||||
print(string)
|
||||
return string.count
|
||||
}
|
||||
func printWithoutCounting(string: String) {
|
||||
let _ = printAndCount(string: string)
|
||||
}
|
||||
printAndCount(string: "hello, world")
|
||||
// 打印“hello, world”,并且返回值 12
|
||||
printWithoutCounting(string: "hello, world")
|
||||
// 打印“hello, world”,但是没有返回任何值
|
||||
```
|
||||
|
||||
第一个函数 `printAndCount(string:)`,输出一个字符串并返回 `Int` 类型的字符数。第二个函数 `printWithoutCounting(string:)` 调用了第一个函数,但是忽略了它的返回值。当第二个函数被调用时,消息依然会由第一个函数输出,但是返回值不会被用到。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 返回值可以被忽略,但定义了有返回值的函数必须返回一个值,如果在函数定义底部没有返回任何值,将导致编译时错误。
|
||||
|
||||
### 多重返回值函数 {#functions-with-multiple-return-values}
|
||||
|
||||
你可以用元组(tuple)类型让多个值作为一个复合值从函数中返回。
|
||||
|
||||
下例中定义了一个名为 `minMax(array:)` 的函数,作用是在一个 `Int` 类型的数组中找出最小值与最大值。
|
||||
|
||||
```swift
|
||||
func minMax(array: [Int]) -> (min: Int, max: Int) {
|
||||
var currentMin = array[0]
|
||||
var currentMax = array[0]
|
||||
for value in array[1..<array.count] {
|
||||
if value < currentMin {
|
||||
currentMin = value
|
||||
} else if value > currentMax {
|
||||
currentMax = value
|
||||
}
|
||||
}
|
||||
return (currentMin, currentMax)
|
||||
}
|
||||
```
|
||||
|
||||
`minMax(array:)` 函数返回一个包含两个 `Int` 值的元组,这些值被标记为 `min` 和 `max` ,以便查询函数的返回值时可以通过名字访问它们。
|
||||
|
||||
在 `minMax(array:)` 的函数体中,在开始的时候设置两个工作变量 `currentMin` 和 `currentMax` 的值为数组中的第一个数。然后函数会遍历数组中剩余的值并检查该值是否比 `currentMin` 和 `currentMax` 更小或更大。最后数组中的最小值与最大值作为一个包含两个 `Int` 值的元组返回。
|
||||
|
||||
因为元组的成员值已被命名,因此可以通过 `.` 语法来检索找到的最小值与最大值:
|
||||
|
||||
```swift
|
||||
let bounds = minMax(array: [8, -6, 2, 109, 3, 71])
|
||||
print("min is \(bounds.min) and max is \(bounds.max)")
|
||||
// 打印“min is -6 and max is 109”
|
||||
```
|
||||
|
||||
需要注意的是,元组的成员不需要在元组从函数中返回时命名,因为它们的名字已经在函数返回类型中指定了。
|
||||
|
||||
### 可选元组返回类型 {#optional-tuple-return-types}
|
||||
|
||||
如果函数返回的元组类型有可能整个元组都“没有值”,你可以使用*可选的* 元组返回类型反映整个元组可以是 `nil` 的事实。你可以通过在元组类型的右括号后放置一个问号来定义一个可选元组,例如 `(Int, Int)?` 或 `(String, Int, Bool)?`
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 可选元组类型如 `(Int, Int)?` 与元组包含可选类型如 `(Int?, Int?)` 是不同的。可选的元组类型,整个元组是可选的,而不只是元组中的每个元素值。
|
||||
|
||||
前面的 `minMax(array:)` 函数返回了一个包含两个 `Int` 值的元组。但是函数不会对传入的数组执行任何安全检查,如果 `array` 参数是一个空数组,如上定义的 `minMax(array:)` 在试图访问 `array[0]` 时会触发一个运行时错误。
|
||||
|
||||
为了安全地处理这个“空数组”问题,将 `minMax(array:)` 函数改写为使用可选元组返回类型,并且当数组为空时返回 `nil`:
|
||||
|
||||
```swift
|
||||
func minMax(array: [Int]) -> (min: Int, max: Int)? {
|
||||
if array.isEmpty { return nil }
|
||||
var currentMin = array[0]
|
||||
var currentMax = array[0]
|
||||
for value in array[1..<array.count] {
|
||||
if value < currentMin {
|
||||
currentMin = value
|
||||
} else if value > currentMax {
|
||||
currentMax = value
|
||||
}
|
||||
}
|
||||
return (currentMin, currentMax)
|
||||
}
|
||||
```
|
||||
|
||||
你可以使用可选绑定来检查 `minMax(array:)` 函数返回的是一个存在的元组值还是 `nil`:
|
||||
|
||||
```swift
|
||||
if let bounds = minMax(array: [8, -6, 2, 109, 3, 71]) {
|
||||
print("min is \(bounds.min) and max is \(bounds.max)")
|
||||
}
|
||||
// 打印“min is -6 and max is 109”
|
||||
```
|
||||
|
||||
|
||||
### 隐式返回的函数 {#functions-with-an-implicit-return}
|
||||
如果一个函数的整个函数体是一个单行表达式,这个函数可以隐式地返回这个表达式。举个例子,以下的函数有着同样的作用:
|
||||
|
||||
```
|
||||
func greeting(for person: String) -> String {
|
||||
"Hello, " + person + "!"
|
||||
}
|
||||
print(greeting(for: "Dave"))
|
||||
// 打印 "Hello, Dave!"
|
||||
|
||||
func anotherGreeting(for person: String) -> String {
|
||||
return "Hello, " + person + "!"
|
||||
}
|
||||
print(anotherGreeting(for: "Dave"))
|
||||
// 打印 "Hello, Dave!"
|
||||
```
|
||||
|
||||
|
||||
`greeting(for:)` 函数的完整定义是打招呼内容的返回,这就意味着它能使用隐式返回这样更简短的形式。`anothergreeting(for:)` 函数返回同样的内容,却因为 `return` 关键字显得函数更长。任何一个可以被写成一行 `return` 语句的函数都可以忽略 `return`。
|
||||
|
||||
正如你将会在 [简略的 Getter 声明](./10_Properties.md) 里看到的, 一个属性的 getter 也可以使用隐式返回的形式。
|
||||
|
||||
|
||||
## 函数参数标签和参数名称 {#Function-Argument-Labels-and-Parameter-Names}
|
||||
|
||||
每个函数参数都有一个*参数标签(argument label)*以及一个*参数名称(parameter name)*。参数标签在调用函数的时候使用;调用的时候需要将函数的参数标签写在对应的参数前面。参数名称在函数的实现中使用。默认情况下,函数参数使用参数名称来作为它们的参数标签。
|
||||
|
||||
```swift
|
||||
func someFunction(firstParameterName: Int, secondParameterName: Int) {
|
||||
// 在函数体内,firstParameterName 和 secondParameterName 代表参数中的第一个和第二个参数值
|
||||
}
|
||||
someFunction(firstParameterName: 1, secondParameterName: 2)
|
||||
```
|
||||
|
||||
所有的参数都必须有一个独一无二的名字。虽然多个参数拥有同样的参数标签是可能的,但是一个唯一的参数标签能够使你的代码更具可读性。
|
||||
|
||||
### 指定参数标签 {#specifying-argument-labels}
|
||||
|
||||
你可以在参数名称前指定它的参数标签,中间以空格分隔:
|
||||
|
||||
```swift
|
||||
func someFunction(argumentLabel parameterName: Int) {
|
||||
// 在函数体内,parameterName 代表参数值
|
||||
}
|
||||
```
|
||||
|
||||
这个版本的 `greet(person:)` 函数,接收一个人的名字和他的家乡,并且返回一句问候:
|
||||
|
||||
```swift
|
||||
func greet(person: String, from hometown: String) -> String {
|
||||
return "Hello \(person)! Glad you could visit from \(hometown)."
|
||||
}
|
||||
print(greet(person: "Bill", from: "Cupertino"))
|
||||
// 打印“Hello Bill! Glad you could visit from Cupertino.”
|
||||
```
|
||||
|
||||
参数标签的使用能够让一个函数在调用时更有表达力,更类似自然语言,并且仍保持了函数内部的可读性以及清晰的意图。
|
||||
|
||||
### 忽略参数标签 {#omitting-argument-labels}
|
||||
|
||||
如果你不希望为某个参数添加一个标签,可以使用一个下划线(`_`)来代替一个明确的参数标签。
|
||||
|
||||
```swift
|
||||
func someFunction(_ firstParameterName: Int, secondParameterName: Int) {
|
||||
// 在函数体内,firstParameterName 和 secondParameterName 代表参数中的第一个和第二个参数值
|
||||
}
|
||||
someFunction(1, secondParameterName: 2)
|
||||
```
|
||||
|
||||
如果一个参数有一个标签,那么在调用的时候必须使用标签来标记这个参数。
|
||||
|
||||
### 默认参数值 {#default-parameter-values}
|
||||
|
||||
你可以在函数体中通过给参数赋值来为任意一个参数定义*默认值(Deafult Value)*。当默认值被定义后,调用这个函数时可以忽略这个参数。
|
||||
|
||||
```swift
|
||||
func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) {
|
||||
// 如果你在调用时候不传第二个参数,parameterWithDefault 会值为 12 传入到函数体中。
|
||||
}
|
||||
someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6) // parameterWithDefault = 6
|
||||
someFunction(parameterWithoutDefault: 4) // parameterWithDefault = 12
|
||||
```
|
||||
|
||||
将不带有默认值的参数放在函数参数列表的最前。一般来说,没有默认值的参数更加的重要,将不带默认值的参数放在最前保证在函数调用时,非默认参数的顺序是一致的,同时也使得相同的函数在不同情况下调用时显得更为清晰。
|
||||
|
||||
### 可变参数 {#variadic-parameters}
|
||||
|
||||
一个*可变参数(variadic parameter)*可以接受零个或多个值。函数调用时,你可以用可变参数来指定函数参数可以被传入不确定数量的输入值。通过在变量类型名后面加入(`...`)的方式来定义可变参数。
|
||||
|
||||
可变参数的传入值在函数体中变为此类型的一个数组。例如,一个叫做 `numbers` 的 `Double...` 型可变参数,在函数体内可以当做一个叫 `numbers` 的 `[Double]` 型的数组常量。
|
||||
|
||||
下面的这个函数用来计算一组任意长度数字的 *算术平均数(arithmetic mean)*:
|
||||
|
||||
```swift
|
||||
func arithmeticMean(_ numbers: Double...) -> Double {
|
||||
var total: Double = 0
|
||||
for number in numbers {
|
||||
total += number
|
||||
}
|
||||
return total / Double(numbers.count)
|
||||
}
|
||||
arithmeticMean(1, 2, 3, 4, 5)
|
||||
// 返回 3.0, 是这 5 个数的平均数。
|
||||
arithmeticMean(3, 8.25, 18.75)
|
||||
// 返回 10.0, 是这 3 个数的平均数。
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 一个函数最多只能拥有一个可变参数。
|
||||
|
||||
### 输入输出参数 {#in-out-parameters}
|
||||
|
||||
函数参数默认是常量。试图在函数体中更改参数值将会导致编译错误。这意味着你不能错误地更改参数值。如果你想要一个函数可以修改参数的值,并且想要在这些修改在函数调用结束后仍然存在,那么就应该把这个参数定义为*输入输出参数(In-Out Parameters)*。
|
||||
|
||||
定义一个输入输出参数时,在参数定义前加 `inout` 关键字。一个 `输入输出参数`有传入函数的值,这个值被函数修改,然后被传出函数,替换原来的值。想获取更多的关于输入输出参数的细节和相关的编译器优化,请查看 [输入输出参数](../03_language_reference/06_Declarations.md#in-out-parameters) 一节。
|
||||
|
||||
你只能传递变量给输入输出参数。你不能传入常量或者字面量,因为这些量是不能被修改的。当传入的参数作为输入输出参数时,需要在参数名前加 `&` 符,表示这个值可以被函数修改。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 输入输出参数不能有默认值,而且可变参数不能用 `inout` 标记。
|
||||
|
||||
下例中,`swapTwoInts(_:_:)` 函数有两个分别叫做 `a` 和 `b` 的输入输出参数:
|
||||
|
||||
```swift
|
||||
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
|
||||
let temporaryA = a
|
||||
a = b
|
||||
b = temporaryA
|
||||
}
|
||||
```
|
||||
|
||||
`swapTwoInts(_:_:)` 函数简单地交换 `a` 与 `b` 的值。该函数先将 `a` 的值存到一个临时常量 `temporaryA` 中,然后将 `b` 的值赋给 `a`,最后将 `temporaryA` 赋值给 `b`。
|
||||
|
||||
你可以用两个 `Int` 型的变量来调用 `swapTwoInts(_:_:)`。需要注意的是,`someInt` 和 `anotherInt` 在传入 `swapTwoInts(_:_:)` 函数前,都加了 `&` 的前缀:
|
||||
|
||||
```swift
|
||||
var someInt = 3
|
||||
var anotherInt = 107
|
||||
swapTwoInts(&someInt, &anotherInt)
|
||||
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
|
||||
// 打印“someInt is now 107, and anotherInt is now 3”
|
||||
```
|
||||
|
||||
从上面这个例子中,我们可以看到 `someInt` 和 `anotherInt` 的原始值在 `swapTwoInts(_:_:)` 函数中被修改,尽管它们的定义在函数体外。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 输入输出参数和返回值是不一样的。上面的 `swapTwoInts` 函数并没有定义任何返回值,但仍然修改了 `someInt` 和 `anotherInt` 的值。输入输出参数是函数对函数体外产生影响的另一种方式。
|
||||
|
||||
## 函数类型 {#Function-Types}
|
||||
|
||||
每个函数都有种特定的*函数类型*,函数的类型由函数的参数类型和返回类型组成。
|
||||
|
||||
例如:
|
||||
|
||||
```swift
|
||||
func addTwoInts(_ a: Int, _ b: Int) -> Int {
|
||||
return a + b
|
||||
}
|
||||
func multiplyTwoInts(_ a: Int, _ b: Int) -> Int {
|
||||
return a * b
|
||||
}
|
||||
```
|
||||
|
||||
这个例子中定义了两个简单的数学函数:`addTwoInts` 和 `multiplyTwoInts`。这两个函数都接受两个 `Int` 值, 返回一个 `Int` 值。
|
||||
|
||||
这两个函数的类型是 `(Int, Int) -> Int`,可以解读为:
|
||||
|
||||
“这个函数类型有两个 `Int` 型的参数并返回一个 `Int` 型的值”。
|
||||
|
||||
下面是另一个例子,一个没有参数,也没有返回值的函数:
|
||||
|
||||
```swift
|
||||
func printHelloWorld() {
|
||||
print("hello, world")
|
||||
}
|
||||
```
|
||||
|
||||
这个函数的类型是:`() -> Void`,或者叫“没有参数,并返回 `Void` 类型的函数”。
|
||||
|
||||
### 使用函数类型 {#using-function-types}
|
||||
|
||||
在 Swift 中,使用函数类型就像使用其他类型一样。例如,你可以定义一个类型为函数的常量或变量,并将适当的函数赋值给它:
|
||||
|
||||
```swift
|
||||
var mathFunction: (Int, Int) -> Int = addTwoInts
|
||||
```
|
||||
|
||||
这段代码可以被解读为:
|
||||
|
||||
”定义一个叫做 `mathFunction` 的变量,类型是‘一个有两个 `Int` 型的参数并返回一个 `Int` 型的值的函数’,并让这个新变量指向 `addTwoInts` 函数”。
|
||||
|
||||
`addTwoInts` 和 `mathFunction` 有同样的类型,所以这个赋值过程在 Swift 类型检查(type-check)中是允许的。
|
||||
|
||||
现在,你可以用 `mathFunction` 来调用被赋值的函数了:
|
||||
|
||||
```swift
|
||||
print("Result: \(mathFunction(2, 3))")
|
||||
// Prints "Result: 5"
|
||||
```
|
||||
|
||||
有相同匹配类型的不同函数可以被赋值给同一个变量,就像非函数类型的变量一样:
|
||||
|
||||
```swift
|
||||
mathFunction = multiplyTwoInts
|
||||
print("Result: \(mathFunction(2, 3))")
|
||||
// Prints "Result: 6"
|
||||
```
|
||||
|
||||
就像其他类型一样,当赋值一个函数给常量或变量时,你可以让 Swift 来推断其函数类型:
|
||||
|
||||
```swift
|
||||
let anotherMathFunction = addTwoInts
|
||||
// anotherMathFunction 被推断为 (Int, Int) -> Int 类型
|
||||
```
|
||||
|
||||
### 函数类型作为参数类型 {#function-types-as-parameter-types}
|
||||
|
||||
你可以用 `(Int, Int) -> Int` 这样的函数类型作为另一个函数的参数类型。这样你可以将函数的一部分实现留给函数的调用者来提供。
|
||||
|
||||
下面是另一个例子,正如上面的函数一样,同样是输出某种数学运算结果:
|
||||
|
||||
```swift
|
||||
func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
|
||||
print("Result: \(mathFunction(a, b))")
|
||||
}
|
||||
printMathResult(addTwoInts, 3, 5)
|
||||
// 打印“Result: 8”
|
||||
```
|
||||
|
||||
这个例子定义了 `printMathResult(_:_:_:)` 函数,它有三个参数:第一个参数叫 `mathFunction`,类型是 `(Int, Int) -> Int`,你可以传入任何这种类型的函数;第二个和第三个参数叫 `a` 和 `b`,它们的类型都是 `Int`,这两个值作为已给出的函数的输入值。
|
||||
|
||||
当 `printMathResult(_:_:_:)` 被调用时,它被传入 `addTwoInts` 函数和整数 `3` 和 `5`。它用传入 `3` 和 `5` 调用 `addTwoInts`,并输出结果:`8`。
|
||||
|
||||
`printMathResult(_:_:_:)` 函数的作用就是输出另一个适当类型的数学函数的调用结果。它不关心传入函数是如何实现的,只关心传入的函数是不是一个正确的类型。这使得 `printMathResult(_:_:_:)` 能以一种类型安全(type-safe)的方式将一部分功能转给调用者实现。
|
||||
|
||||
### 函数类型作为返回类型 {#function-types-as-return-types}
|
||||
|
||||
你可以用函数类型作为另一个函数的返回类型。你需要做的是在返回箭头(->)后写一个完整的函数类型。
|
||||
|
||||
下面的这个例子中定义了两个简单函数,分别是 `stepForward(_:)` 和 `stepBackward(_:)`。`stepForward(_:)` 函数返回一个比输入值大 `1` 的值。`stepBackward(_:)` 函数返回一个比输入值小 `1` 的值。这两个函数的类型都是 `(Int) -> Int`:
|
||||
|
||||
```swift
|
||||
func stepForward(_ input: Int) -> Int {
|
||||
return input + 1
|
||||
}
|
||||
func stepBackward(_ input: Int) -> Int {
|
||||
return input - 1
|
||||
}
|
||||
```
|
||||
|
||||
如下名为 `chooseStepFunction(backward:)` 的函数,它的返回类型是 `(Int) -> Int` 类型的函数。`chooseStepFunction(backward:)` 根据布尔值 `backwards` 来返回 `stepForward(_:)` 函数或 `stepBackward(_:)` 函数:
|
||||
|
||||
```swift
|
||||
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
|
||||
return backward ? stepBackward : stepForward
|
||||
}
|
||||
```
|
||||
|
||||
你现在可以用 `chooseStepFunction(backward:)` 来获得两个函数其中的一个:
|
||||
|
||||
```swift
|
||||
var currentValue = 3
|
||||
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
|
||||
// moveNearerToZero 现在指向 stepBackward() 函数。
|
||||
```
|
||||
|
||||
上面这个例子中计算出从 `currentValue` 逐渐接近到0是需要向正数走还是向负数走。`currentValue` 的初始值是 `3`,这意味着 `currentValue > 0` 为真(true),这将使得 `chooseStepFunction(_:)` 返回 `stepBackward(_:)` 函数。一个指向返回的函数的引用保存在了 `moveNearerToZero` 常量中。
|
||||
|
||||
现在,`moveNearerToZero` 指向了正确的函数,它可以被用来数到零:
|
||||
|
||||
```swift
|
||||
print("Counting to zero:")
|
||||
// Counting to zero:
|
||||
while currentValue != 0 {
|
||||
print("\(currentValue)... ")
|
||||
currentValue = moveNearerToZero(currentValue)
|
||||
}
|
||||
print("zero!")
|
||||
// 3...
|
||||
// 2...
|
||||
// 1...
|
||||
// zero!
|
||||
```
|
||||
|
||||
## 嵌套函数 {#Nested-Functions}
|
||||
|
||||
到目前为止本章中你所见到的所有函数都叫*全局函数(global functions)*,它们定义在全局域中。你也可以把函数定义在别的函数体中,称作 *嵌套函数(nested functions)*。
|
||||
|
||||
默认情况下,嵌套函数是对外界不可见的,但是可以被它们的外围函数(enclosing function)调用。一个外围函数也可以返回它的某一个嵌套函数,使得这个函数可以在其他域中被使用。
|
||||
|
||||
你可以用返回嵌套函数的方式重写 `chooseStepFunction(backward:)` 函数:
|
||||
|
||||
```swift
|
||||
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
|
||||
func stepForward(input: Int) -> Int { return input + 1 }
|
||||
func stepBackward(input: Int) -> Int { return input - 1 }
|
||||
return backward ? stepBackward : stepForward
|
||||
}
|
||||
var currentValue = -4
|
||||
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
|
||||
// moveNearerToZero now refers to the nested stepForward() function
|
||||
while currentValue != 0 {
|
||||
print("\(currentValue)... ")
|
||||
currentValue = moveNearerToZero(currentValue)
|
||||
}
|
||||
print("zero!")
|
||||
// -4...
|
||||
// -3...
|
||||
// -2...
|
||||
// -1...
|
||||
// zero!
|
||||
```
|
||||
420
source/02_language_guide/07_Closures.md
Executable file
420
source/02_language_guide/07_Closures.md
Executable file
@ -0,0 +1,420 @@
|
||||
# 闭包
|
||||
|
||||
*闭包*是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数(Lambdas)比较相似。
|
||||
|
||||
闭包可以捕获和存储其所在上下文中任意常量和变量的引用。被称为*包裹*常量和变量。 Swift 会为你管理在捕获过程中涉及到的所有内存操作。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果你不熟悉捕获(capturing)这个概念也不用担心,在 [值捕获](#capturing-values) 章节有它更详细的介绍。
|
||||
|
||||
在 [函数](./06_Functions.md) 章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采用如下三种形式之一:
|
||||
|
||||
* 全局函数是一个有名字但不会捕获任何值的闭包
|
||||
* 嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
|
||||
* 闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包
|
||||
|
||||
Swift 的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下:
|
||||
|
||||
* 利用上下文推断参数和返回值类型
|
||||
* 隐式返回单表达式闭包,即单表达式闭包可以省略 `return` 关键字
|
||||
* 参数名称缩写
|
||||
* 尾随闭包语法
|
||||
|
||||
## 闭包表达式 {#closure-expressions}
|
||||
|
||||
[嵌套函数](./06_Functions.md#Nested-Functions) 作为复杂函数的一部分时,它自包含代码块式的定义和命名形式在使用上带来了方便。当然,编写未完整声明和没有函数名的类函数结构代码是很有用的,尤其是在编码中涉及到函数作为参数的那些方法时。
|
||||
|
||||
*闭包表达式*是一种构建内联闭包的方式,它的语法简洁。在保证不丢失它语法清晰明了的同时,闭包表达式提供了几种优化的语法简写形式。下面通过对 `sorted(by:)` 这一个案例的多次迭代改进来展示这个过程,每次迭代都使用了更加简明的方式描述了相同功能。。
|
||||
|
||||
### 排序方法 {#the-sorted-function}
|
||||
|
||||
Swift 标准库提供了名为 `sorted(by:)` 的方法,它会基于你提供的排序闭包表达式的判断结果对数组中的值(类型确定)进行排序。一旦它完成排序过程,`sorted(by:)` 方法会返回一个与旧数组类型大小相同类型的新数组,该数组的元素有着正确的排序顺序。原数组不会被 `sorted(by:)` 方法修改。
|
||||
|
||||
下面的闭包表达式示例使用 `sorted(by:)` 方法对一个 `String` 类型的数组进行字母逆序排序。以下是初始数组:
|
||||
|
||||
```swift
|
||||
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
|
||||
```
|
||||
|
||||
`sorted(by:)` 方法接受一个闭包,该闭包函数需要传入与数组元素类型相同的两个值,并返回一个布尔类型值来表明当排序结束后传入的第一个参数排在第二个参数前面还是后面。如果第一个参数值出现在第二个参数值*前面*,排序闭包函数需要返回 `true`,反之返回 `false`。
|
||||
|
||||
该例子对一个 `String` 类型的数组进行排序,因此排序闭包函数类型需为 `(String, String) -> Bool`。
|
||||
|
||||
提供排序闭包函数的一种方式是撰写一个符合其类型要求的普通函数,并将其作为 `sorted(by:)` 方法的参数传入:
|
||||
|
||||
```swift
|
||||
func backward(_ s1: String, _ s2: String) -> Bool {
|
||||
return s1 > s2
|
||||
}
|
||||
var reversedNames = names.sorted(by: backward)
|
||||
// reversedNames 为 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
|
||||
```
|
||||
|
||||
如果第一个字符串(`s1`)大于第二个字符串(`s2`),`backward(_:_:)` 函数会返回 `true`,表示在新的数组中 `s1` 应该出现在 `s2` 前。对于字符串中的字符来说,“大于”表示“按照字母顺序较晚出现”。这意味着字母 `"B"` 大于字母 `"A"` ,字符串 `"Tom"` 大于字符串 `"Tim"`。该闭包将进行字母逆序排序,`"Barry"` 将会排在 `"Alex"` 之前。
|
||||
|
||||
然而,以这种方式来编写一个实际上很简单的表达式(`a > b`),确实太过繁琐了。对于这个例子来说,利用闭包表达式语法可以更好地构造一个内联排序闭包。
|
||||
|
||||
### 闭包表达式语法 {#closure-expression-syntax}
|
||||
|
||||
闭包表达式语法有如下的一般形式:
|
||||
|
||||
```swift
|
||||
{ (parameters) -> return type in
|
||||
statements
|
||||
}
|
||||
```
|
||||
|
||||
*闭包表达式参数* 可以是 in-out 参数,但不能设定默认值。如果你命名了可变参数,也可以使用此可变参数。元组也可以作为参数和返回值。
|
||||
|
||||
下面的例子展示了之前 `backward(_:_:)` 函数对应的闭包表达式版本的代码:
|
||||
|
||||
```swift
|
||||
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
|
||||
return s1 > s2
|
||||
})
|
||||
```
|
||||
|
||||
需要注意的是内联闭包参数和返回值类型声明与 `backward(_:_:)` 函数类型声明相同。在这两种方式中,都写成了 `(s1: String, s2: String) -> Bool`。然而在内联闭包表达式中,函数和返回值类型都写在*大括号内*,而不是大括号外。
|
||||
|
||||
闭包的函数体部分由关键字 `in` 引入。该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。
|
||||
|
||||
由于这个闭包的函数体部分如此短,以至于可以将其改写成一行代码:
|
||||
|
||||
```swift
|
||||
reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )
|
||||
```
|
||||
|
||||
该例中 `sorted(by:)` 方法的整体调用保持不变,一对圆括号仍然包裹住了方法的整个参数。然而,参数现在变成了内联闭包。
|
||||
|
||||
### 根据上下文推断类型 {#inferring-type-from-context}
|
||||
|
||||
因为排序闭包函数是作为 `sorted(by:)` 方法的参数传入的,Swift 可以推断其参数和返回值的类型。`sorted(by:)` 方法被一个字符串数组调用,因此其参数必须是 `(String, String) -> Bool` 类型的函数。这意味着 `(String, String)` 和 `Bool` 类型并不需要作为闭包表达式定义的一部分。因为所有的类型都可以被正确推断,返回箭头(`->`)和围绕在参数周围的括号也可以被省略:
|
||||
|
||||
```swift
|
||||
reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )
|
||||
```
|
||||
|
||||
实际上,通过内联闭包表达式构造的闭包作为参数传递给函数或方法时,总是能够推断出闭包的参数和返回值类型。这意味着闭包作为函数或者方法的参数时,你几乎不需要利用完整格式构造内联闭包。
|
||||
|
||||
尽管如此,你仍然可以明确写出有着完整格式的闭包。如果完整格式的闭包能够提高代码的可读性,则我们更鼓励采用完整格式的闭包。而在 `sorted(by:)` 方法这个例子里,显然闭包的目的就是排序。由于这个闭包是为了处理字符串数组的排序,因此读者能够推测出这个闭包是用于字符串处理的。
|
||||
|
||||
### 单表达式闭包的隐式返回 {#implicit-returns-from-single-expression-closures}
|
||||
|
||||
单行表达式闭包可以通过省略 `return` 关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为:
|
||||
|
||||
```swift
|
||||
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
|
||||
```
|
||||
|
||||
在这个例子中,`sorted(by:)` 方法的参数类型明确了闭包必须返回一个 `Bool` 类型值。因为闭包函数体只包含了一个单一表达式(`s1 > s2`),该表达式返回 `Bool` 类型值,因此这里没有歧义,`return` 关键字可以省略。
|
||||
|
||||
### 参数名称缩写 {#shorthand-argument-names}
|
||||
|
||||
Swift 自动为内联闭包提供了参数名称缩写功能,你可以直接通过 `$0`,`$1`,`$2` 来顺序调用闭包的参数,以此类推。
|
||||
|
||||
如果你在闭包表达式中使用参数名称缩写,你可以在闭包定义中省略参数列表,并且对应参数名称缩写的类型会通过函数类型进行推断。闭包接受的参数的数量取决于所使用的缩写参数的最大编号。`in` 关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:
|
||||
|
||||
```swift
|
||||
reversedNames = names.sorted(by: { $0 > $1 } )
|
||||
```
|
||||
|
||||
在这个例子中,`$0` 和 `$1` 表示闭包中第一个和第二个 `String` 类型的参数。因为 `$1` 是编号最大的缩写参数,所以可以理解为:该闭包需要两个参数。这里的 `sorted(by:)` 函数希望得到一个参数都是字符串的闭包,因此缩写参数 `$0` 和 `$1` 的类型均为 `String`。
|
||||
|
||||
### 运算符方法 {#operator-methods}
|
||||
|
||||
实际上还有一种更*简短的*方式来编写上面例子中的闭包表达式。Swift 的 `String` 类型定义了关于大于号(`>`)的字符串实现,其作为一个函数接受两个 `String` 类型的参数并返回 `Bool` 类型的值。而这正好与 `sorted(by:)` 方法的参数需要的函数类型相符合。因此,你可以简单地传递一个大于号,Swift 可以自动推断找到系统自带的那个字符串函数的实现:
|
||||
|
||||
```swift
|
||||
reversedNames = names.sorted(by: >)
|
||||
```
|
||||
|
||||
更多关于运算符方法的内容请查看 [运算符方法](./27_Advanced_Operators.md#operator-methods)。
|
||||
|
||||
## 尾随闭包 {#trailing-closures}
|
||||
|
||||
如果你需要将一个很长的闭包表达式作为最后一个参数传递给函数,将这个闭包替换成为尾随闭包的形式很有用。尾随闭包是一个书写在函数圆括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,你不用写出它的参数标签:
|
||||
|
||||
```swift
|
||||
func someFunctionThatTakesAClosure(closure: () -> Void) {
|
||||
// 函数体部分
|
||||
}
|
||||
|
||||
// 以下是不使用尾随闭包进行函数调用
|
||||
someFunctionThatTakesAClosure(closure: {
|
||||
// 闭包主体部分
|
||||
})
|
||||
|
||||
// 以下是使用尾随闭包进行函数调用
|
||||
someFunctionThatTakesAClosure() {
|
||||
// 闭包主体部分
|
||||
}
|
||||
```
|
||||
|
||||
在 [闭包表达式语法](#closure-expression-syntax) 上章节中的字符串排序闭包可以作为尾随包的形式改写在 `sorted(by:)` 方法圆括号的外面:
|
||||
|
||||
```swift
|
||||
reversedNames = names.sorted() { $0 > $1 }
|
||||
```
|
||||
|
||||
如果闭包表达式是函数或方法的唯一参数,则当你使用尾随闭包时,你甚至可以把 `()` 省略掉:
|
||||
|
||||
```swift
|
||||
reversedNames = names.sorted { $0 > $1 }
|
||||
```
|
||||
|
||||
当闭包非常长以至于不能在一行中进行书写时,尾随闭包变得非常有用。举例来说,Swift 的 `Array` 类型有一个 `map(_:)` 方法,这个方法获取一个闭包表达式作为其唯一参数。该闭包函数会为数组中的每一个元素调用一次,并返回该元素所映射的值。具体的映射方式和返回值类型由闭包来指定。
|
||||
|
||||
当提供给数组的闭包应用于每个数组元素后,`map(_:)` 方法将返回一个新的数组,数组中包含了与原数组中的元素一一对应的映射后的值。
|
||||
|
||||
下例介绍了如何在 `map(_:)` 方法中使用尾随闭包将 `Int` 类型数组 `[16, 58, 510]` 转换为包含对应 `String` 类型的值的数组 `["OneSix", "FiveEight", "FiveOneZero"]`:
|
||||
|
||||
```swift
|
||||
let digitNames = [
|
||||
0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four",
|
||||
5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
|
||||
]
|
||||
let numbers = [16, 58, 510]
|
||||
```
|
||||
|
||||
如上代码创建了一个整型数位和它们英文版本名字相映射的字典。同时还定义了一个准备转换为字符串数组的整型数组。
|
||||
|
||||
你现在可以通过传递一个尾随闭包给 `numbers` 数组的 `map(_:)` 方法来创建对应的字符串版本数组:
|
||||
|
||||
```swift
|
||||
let strings = numbers.map {
|
||||
(number) -> String in
|
||||
var number = number
|
||||
var output = ""
|
||||
repeat {
|
||||
output = digitNames[number % 10]! + output
|
||||
number /= 10
|
||||
} while number > 0
|
||||
return output
|
||||
}
|
||||
// strings 常量被推断为字符串类型数组,即 [String]
|
||||
// 其值为 ["OneSix", "FiveEight", "FiveOneZero"]
|
||||
```
|
||||
|
||||
`map(_:)` 为数组中每一个元素调用了一次闭包表达式。你不需要指定闭包的输入参数 `number` 的类型,因为可以通过要映射的数组类型进行推断。
|
||||
|
||||
在该例中,局部变量 `number` 的值由闭包中的 `number` 参数获得,因此可以在闭包函数体内对其进行修改,(闭包或者函数的参数总是常量),闭包表达式指定了返回类型为 `String`,以表明存储映射值的新数组类型为 `String`。
|
||||
|
||||
闭包表达式在每次被调用的时候创建了一个叫做 `output` 的字符串并返回。其使用求余运算符(`number % 10`)计算最后一位数字并利用 `digitNames` 字典获取所映射的字符串。这个闭包能够用于创建任意正整数的字符串表示。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 字典 `digitNames` 下标后跟着一个叹号(`!`),因为字典下标返回一个可选值(optional value),表明该键不存在时会查找失败。在上例中,由于可以确定 `number % 10` 总是 `digitNames` 字典的有效下标,因此叹号可以用于强制解包(force-unwrap)存储在下标的可选类型的返回值中的 `String` 类型的值。
|
||||
|
||||
从 `digitNames` 字典中获取的字符串被添加到 `output` 的*前部*,逆序建立了一个字符串版本的数字。(在表达式 `number % 10` 中,如果 `number` 为 `16`,则返回 `6`,`58` 返回 `8`,`510` 返回 `0`。)
|
||||
|
||||
`number` 变量之后除以 `10`。因为其是整数,在计算过程中未除尽部分被忽略。因此 `16` 变成了 `1`,`58` 变成了 `5`,`510` 变成了 `51`。
|
||||
|
||||
整个过程重复进行,直到 `number /= 10` 为 `0`,这时闭包会将字符串 `output` 返回,而 `map(_:)` 方法则会将字符串添加到映射数组中。
|
||||
|
||||
在上面的例子中,通过尾随闭包语法,优雅地在函数后封装了闭包的具体功能,而不再需要将整个闭包包裹在 `map(_:)` 方法的括号内。
|
||||
|
||||
## 值捕获 {#capturing-values}
|
||||
|
||||
闭包可以在其被定义的上下文中*捕获*常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
|
||||
|
||||
Swift 中,可以捕获值的闭包的最简单形式是嵌套函数,也就是定义在其他函数的函数体内的函数。嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。
|
||||
|
||||
举个例子,这有一个叫做 `makeIncrementer` 的函数,其包含了一个叫做 `incrementer` 的嵌套函数。嵌套函数 `incrementer()` 从上下文中捕获了两个值,`runningTotal` 和 `amount`。捕获这些值之后,`makeIncrementer` 将 `incrementer` 作为闭包返回。每次调用 `incrementer` 时,其会以 `amount` 作为增量增加 `runningTotal` 的值。
|
||||
|
||||
```swift
|
||||
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
|
||||
var runningTotal = 0
|
||||
func incrementer() -> Int {
|
||||
runningTotal += amount
|
||||
return runningTotal
|
||||
}
|
||||
return incrementer
|
||||
}
|
||||
```
|
||||
|
||||
`makeIncrementer` 返回类型为 `() -> Int`。这意味着其返回的是一个*函数*,而非一个简单类型的值。该函数在每次调用时不接受参数,只返回一个 `Int` 类型的值。关于函数返回其他函数的内容,请查看 [函数类型作为返回类型](./06_Functions.md#function-types-as-return-types)。
|
||||
|
||||
`makeIncrementer(forIncrement:)` 函数定义了一个初始值为 `0` 的整型变量 `runningTotal`,用来存储当前总计数值。该值为 `incrementer` 的返回值。
|
||||
|
||||
`makeIncrementer(forIncrement:)` 有一个 `Int` 类型的参数,其外部参数名为 `forIncrement`,内部参数名为 `amount`,该参数表示每次 `incrementer` 被调用时 `runningTotal` 将要增加的量。`makeIncrementer` 函数还定义了一个嵌套函数 `incrementer`,用来执行实际的增加操作。该函数简单地使 `runningTotal` 增加 `amount`,并将其返回。
|
||||
|
||||
如果我们单独考虑嵌套函数 `incrementer()`,会发现它有些不同寻常:
|
||||
|
||||
```swift
|
||||
func incrementer() -> Int {
|
||||
runningTotal += amount
|
||||
return runningTotal
|
||||
}
|
||||
```
|
||||
|
||||
`incrementer()` 函数并没有任何参数,但是在函数体内访问了 `runningTotal` 和 `amount` 变量。这是因为它从外围函数捕获了 `runningTotal` 和 `amount` 变量的*引用*。捕获引用保证了 `runningTotal` 和 `amount` 变量在调用完 `makeIncrementer` 后不会消失,并且保证了在下一次执行 `incrementer` 函数时,`runningTotal` 依旧存在。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 为了优化,如果一个值不会被闭包改变,或者在闭包创建后不会改变,Swift 可能会改为捕获并保存一份对值的拷贝。
|
||||
>
|
||||
> Swift 也会负责被捕获变量的所有内存管理工作,包括释放不再需要的变量。
|
||||
|
||||
下面是一个使用 `makeIncrementer` 的例子:
|
||||
|
||||
```swift
|
||||
let incrementByTen = makeIncrementer(forIncrement: 10)
|
||||
```
|
||||
|
||||
该例子定义了一个叫做 `incrementByTen` 的常量,该常量指向一个每次调用会将其 `runningTotal` 变量增加 `10` 的 `incrementer` 函数。调用这个函数多次可以得到以下结果:
|
||||
|
||||
```swift
|
||||
incrementByTen()
|
||||
// 返回的值为10
|
||||
incrementByTen()
|
||||
// 返回的值为20
|
||||
incrementByTen()
|
||||
// 返回的值为30
|
||||
```
|
||||
|
||||
如果你创建了另一个 `incrementer`,它会有属于自己的引用,指向一个全新、独立的 `runningTotal` 变量:
|
||||
|
||||
```swift
|
||||
let incrementBySeven = makeIncrementer(forIncrement: 7)
|
||||
incrementBySeven()
|
||||
// 返回的值为7
|
||||
```
|
||||
|
||||
再次调用原来的 `incrementByTen` 会继续增加它自己的 `runningTotal` 变量,该变量和 `incrementBySeven` 中捕获的变量没有任何联系:
|
||||
|
||||
```swift
|
||||
incrementByTen()
|
||||
// 返回的值为40
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果你将闭包赋值给一个类实例的属性,并且该闭包通过访问该实例或其成员而捕获了该实例,你将在闭包和该实例间创建一个循环强引用。Swift 使用捕获列表来打破这种循环强引用。更多信息,请参考 [闭包引起的循环强引用](./24_Automatic_Reference_Counting.md#strong-reference-cycles-for-closures)。
|
||||
|
||||
## 闭包是引用类型 {#closures-are-reference-types}
|
||||
|
||||
上面的例子中,`incrementBySeven` 和 `incrementByTen` 都是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量的值。这是因为函数和闭包都是*引用类型*。
|
||||
|
||||
无论你将函数或闭包赋值给一个常量还是变量,你实际上都是将常量或变量的值设置为对应函数或闭包的*引用*。上面的例子中,指向闭包的引用 `incrementByTen` 是一个常量,而并非闭包内容本身。
|
||||
|
||||
这也意味着如果你将闭包赋值给了两个不同的常量或变量,两个值都会指向同一个闭包:
|
||||
|
||||
```swift
|
||||
let alsoIncrementByTen = incrementByTen
|
||||
alsoIncrementByTen()
|
||||
// 返回的值为50
|
||||
```
|
||||
|
||||
## 逃逸闭包 {#escaping-closures}
|
||||
|
||||
当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中*逃逸*。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 `@escaping`,用来指明这个闭包是允许“逃逸”出这个函数的。
|
||||
|
||||
一种能使闭包“逃逸”出函数的方法是,将这个闭包保存在一个函数外部定义的变量中。举个例子,很多启动异步操作的函数接受一个闭包参数作为 completion handler。这类函数会在异步操作开始之后立刻返回,但是闭包直到异步操作结束后才会被调用。在这种情况下,闭包需要“逃逸”出函数,因为闭包需要在函数返回之后被调用。例如:
|
||||
|
||||
```swift
|
||||
var completionHandlers: [() -> Void] = []
|
||||
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
|
||||
completionHandlers.append(completionHandler)
|
||||
}
|
||||
```
|
||||
|
||||
`someFunctionWithEscapingClosure(_:)` 函数接受一个闭包作为参数,该闭包被添加到一个函数外定义的数组中。如果你不将这个参数标记为 `@escaping`,就会得到一个编译错误。
|
||||
|
||||
将一个闭包标记为 `@escaping` 意味着你必须在闭包中显式地引用 `self`。比如说,在下面的代码中,传递到 `someFunctionWithEscapingClosure(_:)` 中的闭包是一个逃逸闭包,这意味着它需要显式地引用 `self`。相对的,传递到 `someFunctionWithNonescapingClosure(_:)` 中的闭包是一个非逃逸闭包,这意味着它可以隐式引用 `self`。
|
||||
|
||||
```swift
|
||||
func someFunctionWithNonescapingClosure(closure: () -> Void) {
|
||||
closure()
|
||||
}
|
||||
|
||||
class SomeClass {
|
||||
var x = 10
|
||||
func doSomething() {
|
||||
someFunctionWithEscapingClosure { self.x = 100 }
|
||||
someFunctionWithNonescapingClosure { x = 200 }
|
||||
}
|
||||
}
|
||||
|
||||
let instance = SomeClass()
|
||||
instance.doSomething()
|
||||
print(instance.x)
|
||||
// 打印出“200”
|
||||
|
||||
completionHandlers.first?()
|
||||
print(instance.x)
|
||||
// 打印出“100”
|
||||
```
|
||||
|
||||
## 自动闭包 {#autoclosures}
|
||||
|
||||
*自动闭包*是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包。
|
||||
|
||||
我们经常会*调用*采用自动闭包的函数,但是很少去*实现*这样的函数。举个例子来说,`assert(condition:message:file:line:)` 函数接受自动闭包作为它的 `condition` 参数和 `message` 参数;它的 `condition` 参数仅会在 debug 模式下被求值,它的 `message` 参数仅当 `condition` 参数为 `false` 时被计算求值。
|
||||
|
||||
自动闭包让你能够延迟求值,因为直到你调用这个闭包,代码段才会被执行。延迟求值对于那些有副作用(Side Effect)和高计算成本的代码来说是很有益处的,因为它使得你能控制代码的执行时机。下面的代码展示了闭包如何延时求值。
|
||||
|
||||
```swift
|
||||
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
|
||||
print(customersInLine.count)
|
||||
// 打印出“5”
|
||||
|
||||
let customerProvider = { customersInLine.remove(at: 0) }
|
||||
print(customersInLine.count)
|
||||
// 打印出“5”
|
||||
|
||||
print("Now serving \(customerProvider())!")
|
||||
// 打印出“Now serving Chris!”
|
||||
print(customersInLine.count)
|
||||
// 打印出“4”
|
||||
```
|
||||
|
||||
尽管在闭包的代码中,`customersInLine` 的第一个元素被移除了,不过在闭包被调用之前,这个元素是不会被移除的。如果这个闭包永远不被调用,那么在闭包里面的表达式将永远不会执行,那意味着列表中的元素永远不会被移除。请注意,`customerProvider` 的类型不是 `String`,而是 `() -> String`,一个没有参数且返回值为 `String` 的函数。
|
||||
|
||||
将闭包作为参数传递给函数时,你能获得同样的延时求值行为。
|
||||
|
||||
```swift
|
||||
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
|
||||
func serve(customer customerProvider: () -> String) {
|
||||
print("Now serving \(customerProvider())!")
|
||||
}
|
||||
serve(customer: { customersInLine.remove(at: 0) } )
|
||||
// 打印出“Now serving Alex!”
|
||||
```
|
||||
|
||||
上面的 `serve(customer:)` 函数接受一个返回顾客名字的显式的闭包。下面这个版本的 `serve(customer:)` 完成了相同的操作,不过它并没有接受一个显式的闭包,而是通过将参数标记为 `@autoclosure` 来接收一个自动闭包。现在你可以将该函数当作接受 `String` 类型参数(而非闭包)的函数来调用。`customerProvider` 参数将自动转化为一个闭包,因为该参数被标记了 `@autoclosure` 特性。
|
||||
|
||||
```swift
|
||||
// customersInLine is ["Ewa", "Barry", "Daniella"]
|
||||
func serve(customer customerProvider: @autoclosure () -> String) {
|
||||
print("Now serving \(customerProvider())!")
|
||||
}
|
||||
serve(customer: customersInLine.remove(at: 0))
|
||||
// 打印“Now serving Ewa!”
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 过度使用 `autoclosures` 会让你的代码变得难以理解。上下文和函数名应该能够清晰地表明求值是被延迟执行的。
|
||||
|
||||
如果你想让一个自动闭包可以“逃逸”,则应该同时使用 `@autoclosure` 和 `@escaping` 属性。`@escaping` 属性的讲解见上面的 [逃逸闭包](#escaping-closures)。
|
||||
|
||||
```swift
|
||||
// customersInLine i= ["Barry", "Daniella"]
|
||||
var customerProviders: [() -> String] = []
|
||||
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
|
||||
customerProviders.append(customerProvider)
|
||||
}
|
||||
collectCustomerProviders(customersInLine.remove(at: 0))
|
||||
collectCustomerProviders(customersInLine.remove(at: 0))
|
||||
|
||||
print("Collected \(customerProviders.count) closures.")
|
||||
// 打印“Collected 2 closures.”
|
||||
for customerProvider in customerProviders {
|
||||
print("Now serving \(customerProvider())!")
|
||||
}
|
||||
// 打印“Now serving Barry!”
|
||||
// 打印“Now serving Daniella!”
|
||||
```
|
||||
|
||||
在上面的代码中,`collectCustomerProviders(_:)` 函数并没有调用传入的 `customerProvider` 闭包,而是将闭包追加到了 `customerProviders` 数组中。这个数组定义在函数作用域范围外,这意味着数组内的闭包能够在函数返回之后被调用。因此,`customerProvider` 参数必须允许“逃逸”出函数作用域。
|
||||
348
source/02_language_guide/08_Enumerations.md
Executable file
348
source/02_language_guide/08_Enumerations.md
Executable file
@ -0,0 +1,348 @@
|
||||
# 枚举
|
||||
|
||||
*枚举*为一组相关的值定义了一个共同的类型,使你可以在你的代码中以类型安全的方式来使用这些值。
|
||||
|
||||
如果你熟悉 C 语言,你会知道在 C 语言中,枚举会为一组整型值分配相关联的名称。Swift 中的枚举更加灵活,不必给每一个枚举成员提供一个值。如果给枚举成员提供一个值(称为原始值),则该值的类型可以是字符串、字符,或是一个整型值或浮点数。
|
||||
|
||||
此外,枚举成员可以指定*任意*类型的关联值存储到枚举成员中,就像其他语言中的联合体(unions)和变体(variants)。你可以在一个枚举中定义一组相关的枚举成员,每一个枚举成员都可以有适当类型的关联值。
|
||||
|
||||
在 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)。
|
||||
|
||||
## 枚举语法 {#enumeration-syntax}
|
||||
|
||||
使用 `enum` 关键词来创建枚举并且把它们的整个定义放在一对大括号内:
|
||||
|
||||
```swift
|
||||
enum SomeEnumeration {
|
||||
// 枚举定义放在这里
|
||||
}
|
||||
```
|
||||
|
||||
下面是用枚举表示指南针四个方向的例子:
|
||||
|
||||
```swift
|
||||
enum CompassPoint {
|
||||
case north
|
||||
case south
|
||||
case east
|
||||
case west
|
||||
}
|
||||
```
|
||||
|
||||
枚举中定义的值(如 `north`,`south`,`east` 和 `west`)是这个枚举的*成员值*(或*成员*)。你可以使用 `case` 关键字来定义一个新的枚举成员值。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 与 C 和 Objective-C 不同,Swift 的枚举成员在被创建时不会被赋予一个默认的整型值。在上面的 `CompassPoint` 例子中,`north`,`south`,`east` 和 `west` 不会被隐式地赋值为 `0`,`1`,`2` 和 `3`。相反,这些枚举成员本身就是完备的值,这些值的类型是已经明确定义好的 `CompassPoint` 类型。
|
||||
|
||||
多个成员值可以出现在同一行上,用逗号隔开:
|
||||
|
||||
```swift
|
||||
enum Planet {
|
||||
case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
|
||||
}
|
||||
```
|
||||
|
||||
每个枚举定义了一个全新的类型。像 Swift 中其他类型一样,它们的名字(例如 `CompassPoint` 和 `Planet`)以一个大写字母开头。给枚举类型起一个单数名字而不是复数名字,以便于:
|
||||
|
||||
```swift
|
||||
var directionToHead = CompassPoint.west
|
||||
```
|
||||
|
||||
`directionToHead` 的类型可以在它被 `CompassPoint` 的某个值初始化时推断出来。一旦 `directionToHead` 被声明为 `CompassPoint` 类型,你可以使用更简短的点语法将其设置为另一个 `CompassPoint` 的值:
|
||||
|
||||
```swift
|
||||
directionToHead = .east
|
||||
```
|
||||
|
||||
当 `directionToHead` 的类型已知时,再次为其赋值可以省略枚举类型名。在使用具有显式类型的枚举值时,这种写法让代码具有更好的可读性。
|
||||
|
||||
## 使用 Switch 语句匹配枚举值 {#matching-enumeration-values-with-a-switch-statement}
|
||||
|
||||
你可以使用 `switch` 语句匹配单个枚举值:
|
||||
|
||||
```swift
|
||||
directionToHead = .south
|
||||
switch directionToHead {
|
||||
case .north:
|
||||
print("Lots of planets have a north")
|
||||
case .south:
|
||||
print("Watch out for penguins")
|
||||
case .east:
|
||||
print("Where the sun rises")
|
||||
case .west:
|
||||
print("Where the skies are blue")
|
||||
}
|
||||
// 打印“Watch out for penguins”
|
||||
```
|
||||
|
||||
你可以这样理解这段代码:
|
||||
|
||||
“判断 `directionToHead` 的值。当它等于 `.north`,打印 `“Lots of planets have a north”`。当它等于 `.south`,打印 `“Watch out for penguins”`。”
|
||||
|
||||
……以此类推。
|
||||
|
||||
正如在 [控制流](./05_Control_Flow.md) 中介绍的那样,在判断一个枚举类型的值时,`switch` 语句必须穷举所有情况。如果忽略了 `.west` 这种情况,上面那段代码将无法通过编译,因为它没有考虑到 `CompassPoint` 的全部成员。强制穷举确保了枚举成员不会被意外遗漏。
|
||||
|
||||
当不需要匹配每个枚举成员的时候,你可以提供一个 `default` 分支来涵盖所有未明确处理的枚举成员:
|
||||
|
||||
```swift
|
||||
let somePlanet = Planet.earth
|
||||
switch somePlanet {
|
||||
case .earth:
|
||||
print("Mostly harmless")
|
||||
default:
|
||||
print("Not a safe place for humans")
|
||||
}
|
||||
// 打印“Mostly harmless”
|
||||
```
|
||||
|
||||
## 枚举成员的遍历 {#iterating-over-enumeration-cases}
|
||||
|
||||
在一些情况下,你会需要得到一个包含枚举所有成员的集合。可以通过如下代码实现:
|
||||
|
||||
令枚举遵循 `CaseIterable` 协议。Swift 会生成一个 `allCases` 属性,用于表示一个包含枚举所有成员的集合。下面是一个例子:
|
||||
|
||||
```swift
|
||||
enum Beverage: CaseIterable {
|
||||
case coffee, tea, juice
|
||||
}
|
||||
let numberOfChoices = Beverage.allCases.count
|
||||
print("\(numberOfChoices) beverages available")
|
||||
// 打印“3 beverages available”
|
||||
```
|
||||
|
||||
在前面的例子中,通过 `Beverage.allCases` 可以访问到包含 `Beverage` 枚举所有成员的集合。`allCases` 的使用方法和其它一般集合一样——集合中的元素是枚举类型的实例,所以在上面的情况中,这些元素是 `Beverage` 值。在前面的例子中,统计了总共有多少个枚举成员。而在下面的例子中,则使用 `for-in` 循环来遍历所有枚举成员。
|
||||
|
||||
```swift
|
||||
for beverage in Beverage.allCases {
|
||||
print(beverage)
|
||||
}
|
||||
// coffee
|
||||
// tea
|
||||
// juice
|
||||
```
|
||||
|
||||
在前面的例子中,使用的语法表明这个枚举遵循 [CaseIterable](https://developer.apple.com/documentation/swift/caseiterable) 协议。想了解 protocols 相关信息,请参见 [协议](./21_Protocols.md)。
|
||||
|
||||
## 关联值 {#associated-values}
|
||||
|
||||
枚举语法那一小节的例子演示了如何定义和分类枚举的成员。你可以为 `Planet.earth` 设置一个常量或者变量,并在赋值之后查看这个值。然而,有时候把其他类型的值和成员值一起存储起来会很有用。这额外的信息称为*关联值*,并且你每次在代码中使用该枚举成员时,还可以修改这个关联值。
|
||||
|
||||
你可以定义 Swift 枚举来存储任意类型的关联值,如果需要的话,每个枚举成员的关联值类型可以各不相同。枚举的这种特性跟其他语言中的可识别联合(discriminated unions),标签联合(tagged unions),或者变体(variants)相似。
|
||||
|
||||
例如,假设一个库存跟踪系统需要利用两种不同类型的条形码来跟踪商品。有些商品上标有使用 `0` 到 `9` 的数字的 UPC 格式的一维条形码。每一个条形码都有一个代表数字系统的数字,该数字后接五位代表厂商代码的数字,接下来是五位代表“产品代码”的数字。最后一个数字是检查位,用来验证代码是否被正确扫描:
|
||||
|
||||
<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">
|
||||
|
||||
这便于库存跟踪系统用包含四个整型值的元组存储 UPC 码,以及用任意长度的字符串储存 QR 码。
|
||||
|
||||
在 Swift 中,使用如下方式定义表示两种商品条形码的枚举:
|
||||
|
||||
```swift
|
||||
enum Barcode {
|
||||
case upc(Int, Int, Int, Int)
|
||||
case qrCode(String)
|
||||
}
|
||||
```
|
||||
|
||||
以上代码可以这么理解:
|
||||
|
||||
“定义一个名为 `Barcode` 的枚举类型,它的一个成员值是具有 `(Int,Int,Int,Int)` 类型关联值的 `upc`,另一个成员值是具有 `String` 类型关联值的 `qrCode`。”
|
||||
|
||||
这个定义不提供任何 `Int` 或 `String` 类型的关联值,它只是定义了,当 `Barcode` 常量和变量等于 `Barcode.upc` 或 `Barcode.qrCode` 时,可以存储的关联值的类型。
|
||||
|
||||
然后你可以使用任意一种条形码类型创建新的条形码,例如:
|
||||
|
||||
```swift
|
||||
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
|
||||
```
|
||||
|
||||
上面的例子创建了一个名为 `productBarcode` 的变量,并将 `Barcode.upc` 赋值给它,关联的元组值为 `(8, 85909, 51226, 3)`。
|
||||
|
||||
同一个商品可以被分配一个不同类型的条形码,例如:
|
||||
|
||||
```swift
|
||||
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
|
||||
```
|
||||
|
||||
这时,原始的 `Barcode.upc` 和其整数关联值被新的 `Barcode.qrCode` 和其字符串关联值所替代。`Barcode` 类型的常量和变量可以存储一个 `.upc` 或者一个 `.qrCode`(连同它们的关联值),但是在同一时间只能存储这两个值中的一个。
|
||||
|
||||
你可以使用一个 switch 语句来检查不同的条形码类型,和之前使用 Switch 语句来匹配枚举值的例子一样。然而,这一次,关联值可以被提取出来作为 switch 语句的一部分。你可以在 `switch` 的 case 分支代码中提取每个关联值作为一个常量(用 `let` 前缀)或者作为一个变量(用 `var` 前缀)来使用:
|
||||
|
||||
```swift
|
||||
switch productBarcode {
|
||||
case .upc(let numberSystem, let manufacturer, let product, let check):
|
||||
print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
|
||||
case .qrCode(let productCode):
|
||||
print("QR code: \(productCode).")
|
||||
}
|
||||
// 打印“QR code: ABCDEFGHIJKLMNOP.”
|
||||
```
|
||||
|
||||
如果一个枚举成员的所有关联值都被提取为常量,或者都被提取为变量,为了简洁,你可以只在成员名称前标注一个 `let` 或者 `var`:
|
||||
|
||||
```swift
|
||||
switch productBarcode {
|
||||
case let .upc(numberSystem, manufacturer, product, check):
|
||||
print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
|
||||
case let .qrCode(productCode):
|
||||
print("QR code: \(productCode).")
|
||||
}
|
||||
// 打印“QR code: ABCDEFGHIJKLMNOP.”
|
||||
```
|
||||
|
||||
## 原始值 {#raw-values}
|
||||
|
||||
在 [关联值](#associated-values) 小节的条形码例子中,演示了如何声明存储不同类型关联值的枚举成员。作为关联值的替代选择,枚举成员可以被默认值(称为*原始值*)预填充,这些原始值的类型必须相同。
|
||||
|
||||
这是一个使用 ASCII 码作为原始值的枚举:
|
||||
|
||||
```swift
|
||||
enum ASCIIControlCharacter: Character {
|
||||
case tab = "\t"
|
||||
case lineFeed = "\n"
|
||||
case carriageReturn = "\r"
|
||||
}
|
||||
```
|
||||
|
||||
枚举类型 `ASCIIControlCharacter` 的原始值类型被定义为 `Character`,并设置了一些比较常见的 ASCII 控制字符。`Character` 的描述详见 [字符串和字符](./03_Strings_and_Characters.md) 部分。
|
||||
|
||||
原始值可以是字符串、字符,或者任意整型值或浮点型值。每个原始值在枚举声明中必须是唯一的。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 原始值和关联值是不同的。原始值是在定义枚举时被预先填充的值,像上述三个 ASCII 码。对于一个特定的枚举成员,它的原始值始终不变。关联值是创建一个基于枚举成员的常量或变量时才设置的值,枚举成员的关联值可以变化。
|
||||
|
||||
### 原始值的隐式赋值 {#implicitly-assigned-raw-values}
|
||||
|
||||
在使用原始值为整数或者字符串类型的枚举时,不需要显式地为每一个枚举成员设置原始值,Swift 将会自动为你赋值。
|
||||
|
||||
例如,当使用整数作为原始值时,隐式赋值的值依次递增 `1`。如果第一个枚举成员没有设置原始值,其原始值将为 `0`。
|
||||
|
||||
下面的枚举是对之前 `Planet` 这个枚举的一个细化,利用整型的原始值来表示每个行星在太阳系中的顺序:
|
||||
|
||||
```swift
|
||||
enum Planet: Int {
|
||||
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
|
||||
}
|
||||
```
|
||||
|
||||
在上面的例子中,`Plant.mercury` 的显式原始值为 `1`,`Planet.venus` 的隐式原始值为 `2`,依次类推。
|
||||
|
||||
当使用字符串作为枚举类型的原始值时,每个枚举成员的隐式原始值为该枚举成员的名称。
|
||||
|
||||
下面的例子是 `CompassPoint` 枚举的细化,使用字符串类型的原始值来表示各个方向的名称:
|
||||
|
||||
```swift
|
||||
enum CompassPoint: String {
|
||||
case north, south, east, west
|
||||
}
|
||||
```
|
||||
|
||||
上面例子中,`CompassPoint.south` 拥有隐式原始值 `south`,依次类推。
|
||||
|
||||
使用枚举成员的 `rawValue` 属性可以访问该枚举成员的原始值:
|
||||
|
||||
```swift
|
||||
let earthsOrder = Planet.earth.rawValue
|
||||
// earthsOrder 值为 3
|
||||
|
||||
let sunsetDirection = CompassPoint.west.rawValue
|
||||
// sunsetDirection 值为 "west"
|
||||
```
|
||||
|
||||
### 使用原始值初始化枚举实例 {#initializing-from-a-raw-value}
|
||||
|
||||
如果在定义枚举类型的时候使用了原始值,那么将会自动获得一个初始化方法,这个方法接收一个叫做 `rawValue` 的参数,参数类型即为原始值类型,返回值则是枚举成员或 `nil`。你可以使用这个初始化方法来创建一个新的枚举实例。
|
||||
|
||||
这个例子利用原始值 `7` 创建了枚举成员 `Uranus`:
|
||||
|
||||
```swift
|
||||
let possiblePlanet = Planet(rawValue: 7)
|
||||
// possiblePlanet 类型为 Planet? 值为 Planet.uranus
|
||||
```
|
||||
|
||||
然而,并非所有 `Int` 值都可以找到一个匹配的行星。因此,原始值构造器总是返回一个*可选*的枚举成员。在上面的例子中,`possiblePlanet` 是 `Planet?` 类型,或者说“可选的 `Planet`”。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 原始值构造器是一个可失败构造器,因为并不是每一个原始值都有与之对应的枚举成员。更多信息请参见 [可失败构造器](../03_language_reference/06_Declarations.md#failable-initializers)。
|
||||
|
||||
如果你试图寻找一个位置为 `11` 的行星,通过原始值构造器返回的可选 `Planet` 值将是 `nil`:
|
||||
|
||||
```swift
|
||||
let positionToFind = 11
|
||||
if let somePlanet = Planet(rawValue: positionToFind) {
|
||||
switch somePlanet {
|
||||
case .earth:
|
||||
print("Mostly harmless")
|
||||
default:
|
||||
print("Not a safe place for humans")
|
||||
}
|
||||
} else {
|
||||
print("There isn't a planet at position \(positionToFind)")
|
||||
}
|
||||
// 打印“There isn't a planet at position 11”
|
||||
```
|
||||
|
||||
这个例子使用了可选绑定(optional binding),试图通过原始值 `11` 来访问一个行星。`if let somePlanet = Planet(rawValue: 11)` 语句创建了一个可选 `Planet`,如果可选 `Planet` 的值存在,就会赋值给 `somePlanet`。在这个例子中,无法检索到位置为 `11` 的行星,所以 `else` 分支被执行。
|
||||
|
||||
## 递归枚举 {#recursive-enumerations}
|
||||
|
||||
*递归枚举*是一种枚举类型,它有一个或多个枚举成员使用该枚举类型的实例作为关联值。使用递归枚举时,编译器会插入一个间接层。你可以在枚举成员前加上 `indirect` 来表示该成员可递归。
|
||||
|
||||
例如,下面的例子中,枚举类型存储了简单的算术表达式:
|
||||
|
||||
```swift
|
||||
enum ArithmeticExpression {
|
||||
case number(Int)
|
||||
indirect case addition(ArithmeticExpression, ArithmeticExpression)
|
||||
indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
|
||||
}
|
||||
```
|
||||
|
||||
你也可以在枚举类型开头加上 `indirect` 关键字来表明它的所有成员都是可递归的:
|
||||
|
||||
```swift
|
||||
indirect enum ArithmeticExpression {
|
||||
case number(Int)
|
||||
case addition(ArithmeticExpression, ArithmeticExpression)
|
||||
case multiplication(ArithmeticExpression, ArithmeticExpression)
|
||||
}
|
||||
```
|
||||
|
||||
上面定义的枚举类型可以存储三种算术表达式:纯数字、两个表达式相加、两个表达式相乘。枚举成员 `addition` 和 `multiplication` 的关联值也是算术表达式——这些关联值使得嵌套表达式成为可能。例如,表达式 `(5 + 4) * 2`,乘号右边是一个数字,左边则是另一个表达式。因为数据是嵌套的,因而用来存储数据的枚举类型也需要支持这种嵌套——这意味着枚举类型需要支持递归。下面的代码展示了使用 `ArithmeticExpression` 这个递归枚举创建表达式 `(5 + 4) * 2`
|
||||
|
||||
```swift
|
||||
let five = ArithmeticExpression.number(5)
|
||||
let four = ArithmeticExpression.number(4)
|
||||
let sum = ArithmeticExpression.addition(five, four)
|
||||
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
|
||||
```
|
||||
|
||||
要操作具有递归性质的数据结构,使用递归函数是一种直截了当的方式。例如,下面是一个对算术表达式求值的函数:
|
||||
|
||||
```swift
|
||||
func evaluate(_ expression: ArithmeticExpression) -> Int {
|
||||
switch expression {
|
||||
case let .number(value):
|
||||
return value
|
||||
case let .addition(left, right):
|
||||
return evaluate(left) + evaluate(right)
|
||||
case let .multiplication(left, right):
|
||||
return evaluate(left) * evaluate(right)
|
||||
}
|
||||
}
|
||||
|
||||
print(evaluate(product))
|
||||
// 打印“18”
|
||||
```
|
||||
|
||||
该函数如果遇到纯数字,就直接返回该数字的值。如果遇到的是加法或乘法运算,则分别计算左边表达式和右边表达式的值,然后相加或相乘。
|
||||
254
source/02_language_guide/09_Structures_And_Classes.md
Executable file
254
source/02_language_guide/09_Structures_And_Classes.md
Executable file
@ -0,0 +1,254 @@
|
||||
# 结构体和类
|
||||
|
||||
*结构体*和*类*作为一种通用而又灵活的结构,成为了人们构建代码的基础。你可以使用定义常量、变量和函数的语法,为你的结构体和类定义属性、添加方法。
|
||||
|
||||
与其他编程语言所不同的是,Swift 并不要求你为自定义的结构体和类的接口与实现代码分别创建文件。你只需在单一的文件中定义一个结构体或者类,系统将会自动生成面向其它代码的外部接口。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 通常一个*类*的实例被称为*对象*。然而相比其他语言,Swift 中结构体和类的功能更加相近,本章中所讨论的大部分功能都可以用在结构体或者类上。因此,这里会使用*实例*这个更通用的术语。
|
||||
|
||||
## 结构体和类对比 {#comparing-structures-and-classes}
|
||||
|
||||
Swift 中结构体和类有很多共同点。两者都可以:
|
||||
|
||||
* 定义属性用于存储值
|
||||
* 定义方法用于提供功能
|
||||
* 定义下标操作用于通过下标语法访问它们的值
|
||||
* 定义构造器用于设置初始值
|
||||
* 通过扩展以增加默认实现之外的功能
|
||||
* 遵循协议以提供某种标准功能
|
||||
|
||||
更多信息请参见 [属性](./10_Properties.md)、[方法](./11_Methods.md)、[下标](./12_Subscripts.md)、[构造过程](./14_Initialization.md)、[扩展](./20_Extensions.md) 和 [协议](./21_Protocols.md)。
|
||||
|
||||
与结构体相比,类还有如下的附加功能:
|
||||
|
||||
* 继承允许一个类继承另一个类的特征
|
||||
* 类型转换允许在运行时检查和解释一个类实例的类型
|
||||
* 析构器允许一个类实例释放任何其所被分配的资源
|
||||
* 引用计数允许对一个类的多次引用
|
||||
|
||||
更多信息请参见 [继承](./13_Inheritance.md)、[类型转换](./18_Type_Casting.md)、[析构过程](./15_Deinitialization.md) 和 [自动引用计数](./24_Automatic_Reference_Counting.md)。
|
||||
|
||||
类支持的附加功能是以增加复杂性为代价的。作为一般准则,优先使用结构体,因为它们更容易理解,仅在适当或必要时才使用类。实际上,这意味着你的大多数自定义数据类型都会是结构体和枚举。更多详细的比较参见 [在结构和类之间进行选择](https://developer.apple.com/documentation/swift/choosing_between_structures_and_classes)。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 类和 actors 共享很多特性。更多信息请参见 [并发](./28_Concurrency.md)。
|
||||
|
||||
### 类型定义的语法 {#definition-syntax}
|
||||
|
||||
结构体和类有着相似的定义方式。你通过 `struct` 关键字引入结构体,通过 `class` 关键字引入类,并将它们的具体定义放在一对大括号中:
|
||||
|
||||
```swift
|
||||
struct SomeStructure {
|
||||
// 在这里定义结构体
|
||||
}
|
||||
class SomeClass {
|
||||
// 在这里定义类
|
||||
}
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 每当你定义一个新的结构体或者类时,你都是定义了一个新的 Swift 类型。请使用 `UpperCamelCase` 这种方式来命名类型(如这里的 `SomeClass` 和 `SomeStructure`),以便符合标准 Swift 类型的大写命名风格(如 `String`,`Int` 和 `Bool`)。请使用 `lowerCamelCase` 这种方式来命名属性和方法(如 `frameRate` 和 `incrementCount`),以便和类型名区分。
|
||||
|
||||
以下是定义结构体和定义类的示例:
|
||||
|
||||
```swift
|
||||
struct Resolution {
|
||||
var width = 0
|
||||
var height = 0
|
||||
}
|
||||
class VideoMode {
|
||||
var resolution = Resolution()
|
||||
var interlaced = false
|
||||
var frameRate = 0.0
|
||||
var name: String?
|
||||
}
|
||||
```
|
||||
|
||||
在上面的示例中定义了一个名为 `Resolution` 的结构体,用来描述基于像素的分辨率。这个结构体包含了名为 `width` 和 `height` 的两个存储属性。存储属性是与结构体或者类绑定的,并存储在其中的常量或变量。当这两个属性被初始化为整数 `0` 的时候,它们会被推断为 `Int` 类型。
|
||||
|
||||
在上面的示例还定义了一个名为 `VideoMode` 的类,用来描述视频显示器的某个特定视频模式。这个类包含了四个可变的存储属性。第一个, `resolution`,被初始化为一个新的 `Resolution` 结构体的实例,属性类型被推断为 `Resolution`。新 `VideoMode` 实例同时还会初始化其它三个属性,它们分别是初始值为 `false` 的 `interlaced`(意为“非隔行视频”),初始值为 `0.0` 的 `frameRate`,以及值为可选 `String` 的 `name`。因为 `name` 是一个可选类型,它会被自动赋予一个默认值 `nil`,意为“没有 `name` 值”。
|
||||
|
||||
### 结构体和类的实例 {#class-and-structure-instances}
|
||||
|
||||
`Resolution` 结构体和 `VideoMode` 类的定义仅描述了什么是 `Resolution` 和 `VideoMode`。它们并没有描述一个特定的分辨率(resolution)或者视频模式(video mode)。为此,你需要创建结构体或者类的一个实例。
|
||||
|
||||
创建结构体和类实例的语法非常相似:
|
||||
|
||||
```swift
|
||||
let someResolution = Resolution()
|
||||
let someVideoMode = VideoMode()
|
||||
```
|
||||
|
||||
结构体和类都使用构造器语法来创建新的实例。构造器语法的最简单形式是在结构体或者类的类型名称后跟随一对空括号,如 `Resolution()` 或 `VideoMode()`。通过这种方式所创建的类或者结构体实例,其属性均会被初始化为默认值。[构造过程](./14_Initialization.md) 章节会对类和结构体的初始化进行更详细的讨论。
|
||||
|
||||
### 属性访问 {#accessing-properties}
|
||||
|
||||
你可以通过使用*点语法*访问实例的属性。其语法规则是,实例名后面紧跟属性名,两者以点号(`.`)分隔,不带空格:
|
||||
|
||||
```swift
|
||||
print("The width of someResolution is \(someResolution.width)")
|
||||
// 打印 "The width of someResolution is 0"
|
||||
```
|
||||
|
||||
在上面的例子中,`someResolution.width` 引用 `someResolution` 的 `width` 属性,返回 `width` 的初始值 `0`。
|
||||
|
||||
你也可以访问子属性,如 `VideoMode` 中 `resolution` 属性的 `width` 属性:
|
||||
|
||||
```swift
|
||||
print("The width of someVideoMode is \(someVideoMode.resolution.width)")
|
||||
// 打印 "The width of someVideoMode is 0"
|
||||
```
|
||||
|
||||
你也可以使用点语法为可变属性赋值:
|
||||
|
||||
```swift
|
||||
someVideoMode.resolution.width = 1280
|
||||
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
|
||||
// 打印 "The width of someVideoMode is now 1280"
|
||||
```
|
||||
|
||||
### 结构体类型的成员逐一构造器 {#memberwise-initializers-for-structure-types}
|
||||
|
||||
所有结构体都有一个自动生成的*成员逐一构造器*,用于初始化新结构体实例中成员的属性。新实例中各个属性的初始值可以通过属性的名称传递到成员逐一构造器之中:
|
||||
|
||||
```swift
|
||||
let vga = Resolution(width: 640, height: 480)
|
||||
```
|
||||
|
||||
与结构体不同,类实例没有默认的成员逐一构造器。[构造过程](./14_Initialization.md) 章节会对构造器进行更详细的讨论。
|
||||
|
||||
## 结构体和枚举是值类型 {#structures-and-enumerations-are-value-types}
|
||||
|
||||
*值类型*是这样一种类型,当它被赋值给一个变量、常量或者被传递给一个函数的时候,其值会被*拷贝*。
|
||||
|
||||
在之前的章节中,你已经大量使用了值类型。实际上,Swift 中所有的基本类型:整数(integer)、浮点数(floating-point number)、布尔值(boolean)、字符串(string)、数组(array)和字典(dictionary),都是值类型,其底层也是使用结构体实现的。
|
||||
|
||||
Swift 中所有的结构体和枚举类型都是值类型。这意味着它们的实例,以及实例中所包含的任何值类型的属性,在代码中传递的时候都会被复制。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 标准库定义的集合,例如数组,字典和字符串,都对复制进行了优化以降低性能成本。新集合不会立即复制,而是跟原集合共享同一份内存,共享同样的元素。在集合的某个副本要被修改前,才会复制它的元素。而你在代码中看起来就像是立即发生了复制。
|
||||
|
||||
请看下面这个示例,其使用了上一个示例中的 `Resolution` 结构体:
|
||||
|
||||
```swift
|
||||
let hd = Resolution(width: 1920, height: 1080)
|
||||
var cinema = hd
|
||||
```
|
||||
|
||||
在以上示例中,声明了一个名为 `hd` 的常量,其值为一个初始化为全高清视频分辨率(`1920` 像素宽,`1080` 像素高)的 `Resolution` 实例。
|
||||
|
||||
然后示例中又声明了一个名为 `cinema` 的变量,并将 `hd` 赋值给它。因为 `Resolution` 是一个结构体,所以会先创建一个现有实例的副本,然后将副本赋值给 `cinema` 。尽管 `hd` 和 `cinema` 有着相同的宽(width)和高(height),但是在幕后它们是两个完全不同的实例。
|
||||
|
||||
下面,为了符合数码影院放映的需求(`2048` 像素宽,`1080` 像素高),`cinema` 的 `width` 属性被修改为稍微宽一点的 2K 标准:
|
||||
|
||||
```swift
|
||||
cinema.width = 2048
|
||||
```
|
||||
|
||||
查看 `cinema` 的 `width` 属性,它的值确实改为了 `2048`:
|
||||
|
||||
```swift
|
||||
print("cinema is now \(cinema.width) pixels wide")
|
||||
// 打印 "cinema is now 2048 pixels wide"
|
||||
```
|
||||
|
||||
然而,初始的 `hd` 实例中 `width` 属性还是 `1920`:
|
||||
|
||||
```swift
|
||||
print("hd is still \(hd.width) pixels wide")
|
||||
// 打印 "hd is still 1920 pixels wide"
|
||||
```
|
||||
|
||||
将 `hd` 赋值给 `cinema` 时,`hd` 中所存储的*值*会拷贝到新的 `cinema` 实例中。结果就是两个完全独立的实例包含了相同的数值。由于两者相互独立,因此将 `cinema` 的 `width` 修改为 `2048` 并不会影响 `hd` 中的 `width` 的值,如下图所示:
|
||||
|
||||

|
||||
|
||||
枚举也遵循相同的行为准则:
|
||||
|
||||
```swift
|
||||
enum CompassPoint {
|
||||
case north, south, east, west
|
||||
mutating func turnNorth() {
|
||||
self = .north
|
||||
}
|
||||
}
|
||||
var currentDirection = CompassPoint.west
|
||||
let rememberedDirection = currentDirection
|
||||
currentDirection.turnNorth()
|
||||
|
||||
print("The current direction is \(currentDirection)")
|
||||
print("The remembered direction is \(rememberedDirection)")
|
||||
// 打印 "The current direction is north"
|
||||
// 打印 "The remembered direction is west"
|
||||
```
|
||||
|
||||
当 `rememberedDirection` 被赋予了 `currentDirection` 的值,实际上它被赋予的是值的一个拷贝。赋值过程结束后再修改 `currentDirection` 的值并不影响 `rememberedDirection` 所储存的原始值的拷贝。
|
||||
|
||||
## 类是引用类型 {#classes-are-reference-types}
|
||||
|
||||
与值类型不同,*引用类型*在被赋予到一个变量、常量或者被传递到一个函数时,其值不会被拷贝。因此,使用的是已存在实例的引用,而不是其拷贝。
|
||||
|
||||
请看下面这个示例,其使用了之前定义的 `VideoMode` 类:
|
||||
|
||||
```swift
|
||||
let tenEighty = VideoMode()
|
||||
tenEighty.resolution = hd
|
||||
tenEighty.interlaced = true
|
||||
tenEighty.name = "1080i"
|
||||
tenEighty.frameRate = 25.0
|
||||
```
|
||||
|
||||
以上示例中,声明了一个名为 `tenEighty` 的常量,并让其引用一个 `VideoMode` 类的新实例。它的视频模式(video mode)被赋值为之前创建的 HD 分辨率(`1920`\*`1080`)的一个拷贝。然后将它设置为隔行视频,名字设为 `“1080i”`,并将帧率设置为 `25.0` 帧每秒。
|
||||
|
||||
接下来,将 `tenEighty` 赋值给一个名为 `alsoTenEighty` 的新常量,并修改 `alsoTenEighty` 的帧率:
|
||||
|
||||
```swift
|
||||
let alsoTenEighty = tenEighty
|
||||
alsoTenEighty.frameRate = 30.0
|
||||
```
|
||||
|
||||
因为类是引用类型,所以 `tenEight` 和 `alsoTenEight` 实际上引用的是同一个 `VideoMode` 实例。换句话说,它们是同一个实例的两种叫法,如下图所示:
|
||||
|
||||

|
||||
|
||||
通过查看 `tenEighty` 的 `frameRate` 属性,可以看到它正确地显示了底层的 `VideoMode` 实例的新帧率 `30.0`:
|
||||
|
||||
```swift
|
||||
print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
|
||||
// 打印 "The frameRate property of theEighty is now 30.0"
|
||||
```
|
||||
|
||||
这个例子也显示了为何引用类型更加难以理解。如果 `tenEighty` 和 `alsoTenEighty` 在你代码中的位置相距很远,那么就很难找到所有修改视频模式的地方。无论在哪使用 `tenEighty`,你都要考虑使用 `alsoTenEighty` 的代码,反之亦然。相反,值类型就更容易理解了,因为你的源码中与同一个值交互的代码都很近。
|
||||
|
||||
需要注意的是 `tenEighty` 和 `alsoTenEighty` 被声明为常量而不是变量。然而你依然可以改变 `tenEighty.frameRate` 和 `alsoTenEighty.frameRate`,这是因为 `tenEighty` 和 `alsoTenEighty` 这两个常量的值并未改变。它们并不“存储”这个 `VideoMode` 实例,而仅仅是对 `VideoMode` 实例的引用。所以,改变的是底层 `VideoMode` 实例的 `frameRate` 属性,而不是指向 `VideoMode` 的常量引用的值。
|
||||
|
||||
### 恒等运算符 {#identity-operators}
|
||||
|
||||
因为类是引用类型,所以多个常量和变量可能在幕后同时引用同一个类实例。(对于结构体和枚举来说,这并不成立。因为它们作为值类型,在被赋予到常量、变量或者传递到函数时,其值总是会被拷贝。)
|
||||
|
||||
判定两个常量或者变量是否引用同一个类实例有时很有用。为了达到这个目的,Swift 提供了两个恒等运算符:
|
||||
|
||||
* 相同(`===`)
|
||||
* 不相同(`!==`)
|
||||
|
||||
使用这两个运算符检测两个常量或者变量是否引用了同一个实例:
|
||||
|
||||
```swift
|
||||
if tenEighty === alsoTenEighty {
|
||||
print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
|
||||
}
|
||||
// 打印 "tenEighty and alsoTenEighty refer to the same VideoMode instance."
|
||||
```
|
||||
|
||||
请注意,“相同”(用三个等号表示,`===`)与“等于”(用两个等号表示,`==`)的不同。“相同”表示两个类类型(class type)的常量或者变量引用同一个类实例。“等于”表示两个实例的值“相等”或“等价”,判定时要遵照设计者定义的评判标准。
|
||||
|
||||
当在定义你的自定义结构体和类的时候,你有义务来决定判定两个实例“相等”的标准。在章节 [等价操作符](./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)。
|
||||
676
source/02_language_guide/10_Properties.md
Executable file
676
source/02_language_guide/10_Properties.md
Executable file
@ -0,0 +1,676 @@
|
||||
# 属性
|
||||
|
||||
*属性*将值与特定的类、结构体或枚举关联。存储属性会将常量和变量存储为实例的一部分,而计算属性则是直接计算(而不是存储)值。计算属性可以用于类、结构体和枚举,而存储属性只能用于类和结构体。
|
||||
|
||||
存储属性和计算属性通常与特定类型的实例关联。但是,属性也可以直接与类型本身关联,这种属性称为类型属性。
|
||||
|
||||
另外,还可以定义属性观察器来监控属性值的变化,以此来触发自定义的操作。属性观察器可以添加到类本身定义的存储属性上,也可以添加到从父类继承的属性上。
|
||||
|
||||
你也可以利用属性包装器来复用多个属性的 getter 和 setter 中的代码。
|
||||
## 存储属性 {#stored-properties}
|
||||
|
||||
简单来说,一个存储属性就是存储在特定类或结构体实例里的一个常量或变量。存储属性可以是*变量存储属性*(用关键字 `var` 定义),也可以是*常量存储属性*(用关键字 `let` 定义)。
|
||||
|
||||
可以在定义存储属性的时候指定默认值,请参考 [默认构造器](./14_Initialization.md#default-initializers) 一节。也可以在构造过程中设置或修改存储属性的值,甚至修改常量存储属性的值,请参考 [构造过程中常量属性的修改](./14_Initialization.md#assigning-constant-properties-during-initialization) 一节。
|
||||
|
||||
下面的例子定义了一个名为 `FixedLengthRange` 的结构体,该结构体用于描述整数的区间,且这个范围值在被创建后不能被修改。
|
||||
|
||||
```swift
|
||||
struct FixedLengthRange {
|
||||
var firstValue: Int
|
||||
let length: Int
|
||||
}
|
||||
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
|
||||
// 该区间表示整数 0,1,2
|
||||
rangeOfThreeItems.firstValue = 6
|
||||
// 该区间现在表示整数 6,7,8
|
||||
```
|
||||
|
||||
`FixedLengthRange` 的实例包含一个名为 `firstValue` 的变量存储属性和一个名为 `length` 的常量存储属性。在上面的例子中,`length` 在创建实例的时候被初始化,且之后无法修改它的值,因为它是一个常量存储属性。
|
||||
|
||||
### 常量结构体实例的存储属性 {#stored-properties-of-constant-structure-instances}
|
||||
|
||||
如果创建了一个结构体实例并将其赋值给一个常量,则无法修改该实例的任何属性,即使被声明为可变属性也不行:
|
||||
|
||||
```swift
|
||||
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
|
||||
// 该区间表示整数 0,1,2,3
|
||||
rangeOfFourItems.firstValue = 6
|
||||
// 尽管 firstValue 是个可变属性,但这里还是会报错
|
||||
```
|
||||
|
||||
因为 `rangeOfFourItems` 被声明成了常量(用 `let` 关键字),所以即使 `firstValue` 是一个可变属性,也无法再修改它了。
|
||||
|
||||
这种行为是由于结构体属于*值类型*。当值类型的实例被声明为常量的时候,它的所有属性也就成了常量。
|
||||
|
||||
属于*引用类型*的类则不一样。把一个引用类型的实例赋给一个常量后,依然可以修改该实例的可变属性。
|
||||
|
||||
### 延时加载存储属性 {#lazy-stored-properties}
|
||||
|
||||
*延时加载存储属性*是指当第一次被调用的时候才会计算其初始值的属性。在属性声明前使用 `lazy` 来标示一个延时加载存储属性。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 必须将延时加载属性声明成变量(使用 `var` 关键字),因为属性的初始值可能在实例构造完成之后才会得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延时加载。
|
||||
|
||||
当属性的值依赖于一些外部因素且这些外部因素只有在构造过程结束之后才会知道的时候,延时加载属性就会很有用。或者当获得属性的值因为需要复杂或者大量的计算,而需要采用需要的时候再计算的方式,延时加载属性也会很有用。
|
||||
|
||||
下面的例子使用了延时加载存储属性来避免复杂类中不必要的初始化工作。例子中定义了 `DataImporter` 和 `DataManager` 两个类,下面是部分代码:
|
||||
|
||||
```swift
|
||||
class DataImporter {
|
||||
/*
|
||||
DataImporter 是一个负责将外部文件中的数据导入的类。
|
||||
这个类的初始化会消耗不少时间。
|
||||
*/
|
||||
var fileName = "data.txt"
|
||||
// 这里会提供数据导入功能
|
||||
}
|
||||
|
||||
class DataManager {
|
||||
lazy var importer = DataImporter()
|
||||
var data = [String]()
|
||||
// 这里会提供数据管理功能
|
||||
}
|
||||
|
||||
let manager = DataManager()
|
||||
manager.data.append("Some data")
|
||||
manager.data.append("Some more data")
|
||||
// DataImporter 实例的 importer 属性还没有被创建
|
||||
```
|
||||
|
||||
`DataManager` 类包含一个名为 `data` 的存储属性,初始值是一个空的字符串数组。这里没有给出全部代码,只需知道 `DataManager` 类的目的是管理和提供对这个字符串数组的访问即可。
|
||||
|
||||
`DataManager` 的一个功能是从文件中导入数据。这个功能由 `DataImporter` 类提供,`DataImporter` 完成初始化需要消耗不少时间:因为它的实例在初始化时可能需要打开文件并读取文件中的内容到内存中。
|
||||
|
||||
`DataManager` 管理数据时也可能不从文件中导入数据。所以当 `DataManager` 的实例被创建时,没必要创建一个 `DataImporter` 的实例,更明智的做法是第一次用到 `DataImporter` 的时候才去创建它。
|
||||
|
||||
由于使用了 `lazy`,`DataImporter` 的实例 `importer` 属性只有在第一次被访问的时候才被创建。比如访问它的属性 `fileName` 时:
|
||||
|
||||
```swift
|
||||
print(manager.importer.fileName)
|
||||
// DataImporter 实例的 importer 属性现在被创建了
|
||||
// 输出“data.txt”
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果一个被标记为 `lazy` 的属性在没有初始化时就同时被多个线程访问,则无法保证该属性只会被初始化一次。
|
||||
|
||||
### 存储属性和实例变量 {#stored-properties-and-instance-variables}
|
||||
|
||||
如果你有过 Objective-C 经验,应该知道 Objective-C 为类实例存储值和引用提供两种方法。除了属性之外,还可以使用实例变量作为一个备份存储将变量值赋值给属性。
|
||||
|
||||
Swift 编程语言中把这些理论统一用属性来实现。Swift 中的属性没有对应的实例变量,属性的备份存储也无法直接访问。这就避免了不同场景下访问方式的困扰,同时也将属性的定义简化成一个语句。属性的全部信息——包括命名、类型和内存管理特征——作为类型定义的一部分,都定义在一个地方。
|
||||
|
||||
## 计算属性 {#computed-properties}
|
||||
|
||||
除存储属性外,类、结构体和枚举可以定义*计算属性*。计算属性不直接存储值,而是提供一个 getter 和一个可选的 setter,来间接获取和设置其他属性或变量的值。
|
||||
|
||||
```swift
|
||||
struct Point {
|
||||
var x = 0.0, y = 0.0
|
||||
}
|
||||
struct Size {
|
||||
var width = 0.0, height = 0.0
|
||||
}
|
||||
struct Rect {
|
||||
var origin = Point()
|
||||
var size = Size()
|
||||
var center: Point {
|
||||
get {
|
||||
let centerX = origin.x + (size.width / 2)
|
||||
let centerY = origin.y + (size.height / 2)
|
||||
return Point(x: centerX, y: centerY)
|
||||
}
|
||||
set(newCenter) {
|
||||
origin.x = newCenter.x - (size.width / 2)
|
||||
origin.y = newCenter.y - (size.height / 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
var square = Rect(origin: Point(x: 0.0, y: 0.0),
|
||||
size: Size(width: 10.0, height: 10.0))
|
||||
let initialSquareCenter = square.center
|
||||
square.center = Point(x: 15.0, y: 15.0)
|
||||
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
|
||||
// 打印“square.origin is now at (10.0, 10.0)”
|
||||
```
|
||||
|
||||
这个例子定义了 3 个结构体来描述几何形状:
|
||||
|
||||
- `Point` 封装了一个 `(x, y)` 的坐标
|
||||
- `Size` 封装了一个 `width` 和一个 `height`
|
||||
- `Rect` 表示一个有原点和尺寸的矩形
|
||||
|
||||
`Rect` 也提供了一个名为 `center` 的计算属性。一个 `Rect` 的中心点可以从 `origin`(原点)和 `size`(大小)算出,所以不需要将中心点以 `Point` 类型的值来保存。`Rect` 的计算属性 `center` 提供了自定义的 getter 和 setter 来获取和设置矩形的中心点,就像它有一个存储属性一样。
|
||||
|
||||
上述例子中创建了一个名为 `square` 的 `Rect` 实例,初始值原点是 `(0, 0)`,宽度高度都是 `10`。如下图中蓝色正方形所示。
|
||||
|
||||
`square` 的 `center` 属性可以通过点运算符(`square.center`)来访问,这会调用该属性的 getter 来获取它的值。跟直接返回已经存在的值不同,getter 实际上通过计算然后返回一个新的 `Point` 来表示 `square` 的中心点。如代码所示,它正确返回了中心点 `(5, 5)`。
|
||||
|
||||
`center` 属性之后被设置了一个新的值 `(15, 15)`,表示向右上方移动正方形到如下图橙色正方形所示的位置。设置属性 `center` 的值会调用它的 setter 来修改属性 `origin` 的 `x` 和 `y` 的值,从而实现移动正方形到新的位置。
|
||||
|
||||
<img src="https://docs.swift.org/swift-book/_images/computedProperties_2x.png" alt="Computed Properties sample" width="388" height="387" />
|
||||
|
||||
### 简化 Setter 声明 {#shorthand-setter-declaration}
|
||||
|
||||
如果计算属性的 setter 没有定义表示新值的参数名,则可以使用默认名称 `newValue`。下面是使用了简化 setter 声明的 `Rect` 结构体代码:
|
||||
|
||||
```swift
|
||||
struct AlternativeRect {
|
||||
var origin = Point()
|
||||
var size = Size()
|
||||
var center: Point {
|
||||
get {
|
||||
let centerX = origin.x + (size.width / 2)
|
||||
let centerY = origin.y + (size.height / 2)
|
||||
return Point(x: centerX, y: centerY)
|
||||
}
|
||||
set {
|
||||
origin.x = newValue.x - (size.width / 2)
|
||||
origin.y = newValue.y - (size.height / 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 简化 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 的计算属性叫*只读计算属性*。只读计算属性总是返回一个值,可以通过点运算符访问,但不能设置新的值。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 必须使用 `var` 关键字定义计算属性,包括只读计算属性,因为它们的值不是固定的。`let` 关键字只用来声明常量属性,表示初始化后再也无法修改的值。
|
||||
|
||||
只读计算属性的声明可以去掉 `get` 关键字和花括号:
|
||||
|
||||
```swift
|
||||
struct Cuboid {
|
||||
var width = 0.0, height = 0.0, depth = 0.0
|
||||
var volume: Double {
|
||||
return width * height * depth
|
||||
}
|
||||
}
|
||||
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
|
||||
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
|
||||
// 打印“the volume of fourByFiveByTwo is 40.0”
|
||||
```
|
||||
|
||||
这个例子定义了一个名为 `Cuboid` 的结构体,表示三维空间的立方体,包含 `width`、`height` 和 `depth` 属性。结构体还有一个名为 `volume` 的只读计算属性用来返回立方体的体积。为 `volume` 提供 setter 毫无意义,因为无法确定如何修改 `width`、`height` 和 `depth` 三者的值来匹配新的 `volume`。然而,`Cuboid` 提供一个只读计算属性来让外部用户直接获取体积是很有用的。
|
||||
|
||||
## 属性观察器 {#property-observers}
|
||||
|
||||
属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,即使新值和当前值相同的时候也不例外。
|
||||
|
||||
你可以在以下位置添加属性观察器:
|
||||
* 自定义的存储属性
|
||||
* 继承的存储属性
|
||||
* 继承的计算属性
|
||||
|
||||
对于继承的属性,你可以在子类中通过重写属性的方式为它添加属性观察器。对于自定义的计算属性来说,使用它的 setter 监控和响应值的变化,而不是尝试创建观察器。属性重写请参考 [重写](./13_Inheritance.md#overriding)。
|
||||
|
||||
可以为属性添加其中一个或两个观察器:
|
||||
|
||||
- `willSet` 在新的值被设置之前调用
|
||||
- `didSet` 在新的值被设置之后调用
|
||||
|
||||
`willSet` 观察器会将新的属性值作为常量参数传入,在 `willSet` 的实现代码中可以为这个参数指定一个名称,如果不指定则参数仍然可用,这时使用默认名称 `newValue` 表示。
|
||||
|
||||
同样,`didSet` 观察器会将旧的属性值作为参数传入,可以为该参数指定一个名称或者使用默认参数名 `oldValue`。如果在 `didSet` 方法中再次对该属性赋值,那么新值会覆盖旧的值。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 在父类初始化方法调用之后,在子类构造器中给父类的属性赋值时,会调用父类属性的 `willSet` 和 `didSet` 观察器。而在父类初始化方法调用之前,给子类的属性赋值时不会调用子类属性的观察器。
|
||||
>
|
||||
> 有关构造器代理的更多信息,请参考 [值类型的构造器代理](./14_Initialization.md#initializer-delegation-for-value-types) 和 [类的构造器代理](./14_Initialization.md#initializer-delegation-for-class-types)。
|
||||
|
||||
下面是一个 `willSet` 和 `didSet` 实际运用的例子,其中定义了一个名为 `StepCounter` 的类,用来统计一个人步行时的总步数。这个类可以跟计步器或其他日常锻炼的统计装置的输入数据配合使用。
|
||||
|
||||
```swift
|
||||
class StepCounter {
|
||||
var totalSteps: Int = 0 {
|
||||
willSet(newTotalSteps) {
|
||||
print("将 totalSteps 的值设置为 \(newTotalSteps)")
|
||||
}
|
||||
didSet {
|
||||
if totalSteps > oldValue {
|
||||
print("增加了 \(totalSteps - oldValue) 步")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let stepCounter = StepCounter()
|
||||
stepCounter.totalSteps = 200
|
||||
// 将 totalSteps 的值设置为 200
|
||||
// 增加了 200 步
|
||||
stepCounter.totalSteps = 360
|
||||
// 将 totalSteps 的值设置为 360
|
||||
// 增加了 160 步
|
||||
stepCounter.totalSteps = 896
|
||||
// 将 totalSteps 的值设置为 896
|
||||
// 增加了 536 步
|
||||
```
|
||||
|
||||
`StepCounter` 类定义了一个叫 `totalSteps` 的 `Int` 类型的属性。它是一个存储属性,包含 `willSet` 和 `didSet` 观察器。
|
||||
|
||||
当 `totalSteps` 被设置新值的时候,它的 `willSet` 和 `didSet` 观察器都会被调用,即使新值和当前值完全相同时也会被调用。
|
||||
|
||||
例子中的 `willSet` 观察器将表示新值的参数自定义为 `newTotalSteps`,这个观察器只是简单的将新的值输出。
|
||||
|
||||
`didSet` 观察器在 `totalSteps` 的值改变后被调用,它把新值和旧值进行对比,如果总步数增加了,就输出一个消息表示增加了多少步。`didSet` 没有为旧值提供自定义名称,所以默认值 `oldValue` 表示旧值的参数名。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果将带有观察器的属性通过 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: Int
|
||||
init() { self.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` 属性包装器实现类似(挺随意的)对“小”的定义。
|
||||
|
||||
```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
|
||||
var projectedValue: Bool
|
||||
init() {
|
||||
self.number = 0
|
||||
self.projectedValue = false
|
||||
}
|
||||
var wrappedValue: Int {
|
||||
get { return number }
|
||||
set {
|
||||
if newValue > 12 {
|
||||
number = 12
|
||||
projectedValue = true
|
||||
} else {
|
||||
number = newValue
|
||||
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}
|
||||
|
||||
计算属性和观察属性所描述的功能也可以用于*全局变量*和*局部变量*。全局变量是在函数、方法、闭包或任何类型之外定义的变量。局部变量是在函数、方法或闭包内部定义的变量。
|
||||
|
||||
前面章节提到的全局或局部变量都属于*存储型变量*,跟存储属性类似,它为特定类型的值提供存储空间,并允许读取和写入。
|
||||
|
||||
另外,在全局或局部范围都可以定义*计算型变量*和为存储型变量定义观察器。计算型变量跟计算属性一样,返回一个计算结果而不是存储值,声明格式也完全一样。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 全局的常量或变量都是延迟计算的,跟 [延时加载存储属性](#lazy-stored-properties) 相似,不同的地方在于,全局的常量或变量不需要标记 `lazy` 修饰符。
|
||||
>
|
||||
> 局部范围的常量和变量从不延迟计算。
|
||||
|
||||
## 类型属性 {#type-properties}
|
||||
|
||||
实例属性属于一个特定类型的实例,每创建一个实例,实例都拥有属于自己的一套属性值,实例之间的属性相互独立。
|
||||
|
||||
你也可以为类型本身定义属性,无论创建了多少个该类型的实例,这些属性都只有唯一一份。这种属性就是*类型属性*。
|
||||
|
||||
类型属性用于定义某个类型所有实例共享的数据,比如*所有*实例都能用的一个常量(就像 C 语言中的静态常量),或者所有实例都能访问的一个变量(就像 C 语言中的静态变量)。
|
||||
|
||||
存储型类型属性可以是变量或常量,计算型类型属性跟实例的计算型属性一样只能定义成变量属性。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 跟实例的存储型属性不同,必须给存储型类型属性指定默认值,因为类型本身没有构造器,也就无法在初始化过程中使用构造器给类型属性赋值。
|
||||
>
|
||||
> 存储型类型属性是延迟初始化的,它们只有在第一次被访问的时候才会被初始化。即使它们被多个线程同时访问,系统也保证只会对其进行一次初始化,并且不需要对其使用 `lazy` 修饰符。
|
||||
|
||||
### 类型属性语法 {#type-property-syntax}
|
||||
|
||||
在 C 或 Objective-C 中,与某个类型关联的静态常量和静态变量,是作为 *global*(全局)静态变量定义的。但是在 Swift 中,类型属性是作为类型定义的一部分写在类型最外层的花括号内,因此它的作用范围也就在类型支持的范围内。
|
||||
|
||||
使用关键字 `static` 来定义类型属性。在为类定义计算型类型属性时,可以改用关键字 `class` 来支持子类对父类的实现进行重写。下面的例子演示了存储型和计算型类型属性的语法:
|
||||
|
||||
```swift
|
||||
struct SomeStructure {
|
||||
static var storedTypeProperty = "Some value."
|
||||
static var computedTypeProperty: Int {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
enum SomeEnumeration {
|
||||
static var storedTypeProperty = "Some value."
|
||||
static var computedTypeProperty: Int {
|
||||
return 6
|
||||
}
|
||||
}
|
||||
class SomeClass {
|
||||
static var storedTypeProperty = "Some value."
|
||||
static var computedTypeProperty: Int {
|
||||
return 27
|
||||
}
|
||||
class var overrideableComputedTypeProperty: Int {
|
||||
return 107
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 例子中的计算型类型属性是只读的,但也可以定义可读可写的计算型类型属性,跟计算型实例属性的语法相同。
|
||||
|
||||
### 获取和设置类型属性的值 {#querying-and-setting-type-properties}
|
||||
|
||||
跟实例属性一样,类型属性也是通过点运算符来访问。但是,类型属性是通过*类型*本身来访问,而不是通过实例。比如:
|
||||
|
||||
```swift
|
||||
print(SomeStructure.storedTypeProperty)
|
||||
// 打印“Some value.”
|
||||
SomeStructure.storedTypeProperty = "Another value."
|
||||
print(SomeStructure.storedTypeProperty)
|
||||
// 打印“Another value.”
|
||||
print(SomeEnumeration.computedTypeProperty)
|
||||
// 打印“6”
|
||||
print(SomeClass.computedTypeProperty)
|
||||
// 打印“27”
|
||||
```
|
||||
|
||||
下面的例子定义了一个结构体,使用两个存储型类型属性来表示两个声道的音量,每个声道具有 `0` 到 `10` 之间的整数音量。
|
||||
|
||||
下图展示了如何把两个声道结合来模拟立体声的音量。当声道的音量是 `0`,没有一个灯会亮;当声道的音量是 `10`,所有灯点亮。本图中,左声道的音量是 `9`,右声道的音量是 `7`:
|
||||
|
||||
<img src="https://docs.swift.org/swift-book/_images/staticPropertiesVUMeter_2x.png" alt="Static Properties VUMeter" width="243" height="357" />
|
||||
|
||||
上面所描述的声道模型使用 `AudioChannel` 结构体的实例来表示:
|
||||
|
||||
```swift
|
||||
struct AudioChannel {
|
||||
static let thresholdLevel = 10
|
||||
static var maxInputLevelForAllChannels = 0
|
||||
var currentLevel: Int = 0 {
|
||||
didSet {
|
||||
if currentLevel > AudioChannel.thresholdLevel {
|
||||
// 将当前音量限制在阈值之内
|
||||
currentLevel = AudioChannel.thresholdLevel
|
||||
}
|
||||
if currentLevel > AudioChannel.maxInputLevelForAllChannels {
|
||||
// 存储当前音量作为新的最大输入音量
|
||||
AudioChannel.maxInputLevelForAllChannels = currentLevel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`AudioChannel` 结构定义了 2 个存储型类型属性来实现上述功能。第一个是 `thresholdLevel`,表示音量的最大上限阈值,它是一个值为 `10` 的常量,对所有实例都可见,如果音量高于 `10`,则取最大上限值 `10`(见后面描述)。
|
||||
|
||||
第二个类型属性是变量存储型属性 `maxInputLevelForAllChannels`,它用来表示所有 `AudioChannel` 实例的最大输入音量,初始值是 `0`。
|
||||
|
||||
`AudioChannel` 也定义了一个名为 `currentLevel` 的存储型实例属性,表示当前声道现在的音量,取值为 `0` 到 `10`。
|
||||
|
||||
属性 `currentLevel` 包含 `didSet` 属性观察器来检查每次设置后的属性值,它做如下两个检查:
|
||||
|
||||
- 如果 `currentLevel` 的新值大于允许的阈值 `thresholdLevel`,属性观察器将 `currentLevel` 的值限定为阈值 `thresholdLevel`。
|
||||
- 如果修正后的 `currentLevel` 值大于静态类型属性 `maxInputLevelForAllChannels` 的值,属性观察器就将新值保存在 `maxInputLevelForAllChannels` 中。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 在第一个检查过程中,`didSet` 属性观察器将 `currentLevel` 设置成了不同的值,但这不会造成属性观察器被再次调用。
|
||||
|
||||
可以使用结构体 `AudioChannel` 创建两个声道 `leftChannel` 和 `rightChannel`,用以表示立体声系统的音量:
|
||||
|
||||
```swift
|
||||
var leftChannel = AudioChannel()
|
||||
var rightChannel = AudioChannel()
|
||||
```
|
||||
|
||||
如果将左声道的 `currentLevel` 设置成 `7`,类型属性 `maxInputLevelForAllChannels` 也会更新成 `7`:
|
||||
|
||||
```swift
|
||||
leftChannel.currentLevel = 7
|
||||
print(leftChannel.currentLevel)
|
||||
// 输出“7”
|
||||
print(AudioChannel.maxInputLevelForAllChannels)
|
||||
// 输出“7”
|
||||
```
|
||||
|
||||
如果试图将右声道的 `currentLevel` 设置成 `11`,它会被修正到最大值 `10`,同时 `maxInputLevelForAllChannels` 的值也会更新到 `10`:
|
||||
|
||||
```swift
|
||||
rightChannel.currentLevel = 11
|
||||
print(rightChannel.currentLevel)
|
||||
// 输出“10”
|
||||
print(AudioChannel.maxInputLevelForAllChannels)
|
||||
// 输出“10”
|
||||
```
|
||||
255
source/02_language_guide/11_Methods.md
Executable file
255
source/02_language_guide/11_Methods.md
Executable file
@ -0,0 +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` 属性,更多关于属性信息,请参考 [特性](../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”
|
||||
```
|
||||
158
source/02_language_guide/12_Subscripts.md
Executable file
158
source/02_language_guide/12_Subscripts.md
Executable file
@ -0,0 +1,158 @@
|
||||
# 下标
|
||||
|
||||
*下标*可以定义在类、结构体和枚举中,是访问集合、列表或序列中元素的快捷方式。可以使用下标的索引,设置和获取值,而不需要再调用对应的存取方法。举例来说,用下标访问一个 `Array` 实例中的元素可以写作 `someArray[index]`,访问 `Dictionary` 实例中的元素可以写作 `someDictionary[key]`。
|
||||
|
||||
一个类型可以定义多个下标,通过不同索引类型进行对应的重载。下标不限于一维,你可以定义具有多个入参的下标满足自定义类型的需求。
|
||||
|
||||
## 下标语法 {#subscript-syntax}
|
||||
|
||||
下标允许你通过在实例名称后面的方括号中传入一个或者多个索引值来对实例进行查询。它的语法类似于实例方法语法和计算型属性语法。定义下标使用 `subscript` 关键字,与定义实例方法类似,都是指定一个或多个输入参数和一个返回类型。与实例方法不同的是,下标可以设定为读写或只读。这种行为由 getter 和 setter 实现,类似计算型属性:
|
||||
|
||||
```swift
|
||||
subscript(index: Int) -> Int {
|
||||
get {
|
||||
// 返回一个适当的 Int 类型的值
|
||||
}
|
||||
set(newValue) {
|
||||
// 执行适当的赋值操作
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`newValue` 的类型和下标操作的返回类型相同。如同计算型属性,可以不指定 setter 的参数(`newValue`)。如果不指定参数,setter 会提供一个名为 `newValue` 的默认参数。
|
||||
|
||||
如同只读计算型属性,对于只读下标的声明,你可以通过省略 `get` 关键字和对应的大括号组来进行简写:
|
||||
|
||||
```swift
|
||||
subscript(index: Int) -> Int {
|
||||
// 返回一个适当的 Int 类型的值
|
||||
}
|
||||
```
|
||||
|
||||
下面代码演示了只读下标的实现,这里定义了一个 `TimesTable` 结构体,用来表示对应整数的乘法表:
|
||||
|
||||
```swift
|
||||
struct TimesTable {
|
||||
let multiplier: Int
|
||||
subscript(index: Int) -> Int {
|
||||
return multiplier * index
|
||||
}
|
||||
}
|
||||
let threeTimesTable = TimesTable(multiplier: 3)
|
||||
print("six times three is \(threeTimesTable[6])")
|
||||
// 打印“six times three is 18”
|
||||
```
|
||||
|
||||
在上例中,创建了一个 `TimesTable` 实例,用来表示整数 `3` 的乘法表。数值 `3` 被传递给结构体的构造函数,作为实例成员 `multiplier` 的值。
|
||||
|
||||
你可以通过下标访问 `threeTimesTable` 实例,例如上面演示的 `threeTimesTable[6]`。这条语句查询了乘法表中 `3` 的第六个元素,返回 `3` 的 `6` 倍即 `18`。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> `TimesTable` 例子基于一个固定的数学公式,对 `threeTimesTable[someIndex]` 进行赋值操作并不合适,因此下标定义为只读的。
|
||||
|
||||
## 下标用法 {#subscript-usage}
|
||||
|
||||
“下标”的确切含义取决于使用场景。下标通常作为访问集合,列表或序列中元素的快捷方式。你可以针对自己特定的类或结构体功能来以最恰当的方式实现下标。
|
||||
|
||||
例如,Swift 的 `Dictionary` 类型实现下标用于对实例中储存的值进行存取操作。为字典设值时,在下标中使用和字典的键类型相同的键,并把一个和字典的值类型相同的值赋给这个下标:
|
||||
|
||||
```swift
|
||||
var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
|
||||
numberOfLegs["bird"] = 2
|
||||
```
|
||||
|
||||
上例定义一个名为 `numberOfLegs` 的变量,并用一个包含三对键值的字典字面量初始化它。`numberOfLegs` 字典的类型被推断为 `[String: Int]`。字典创建完成后,该例子通过下标将 `String` 类型的键 `bird` 和 `Int` 类型的值 `2` 添加到字典中。
|
||||
|
||||
更多关于 `Dictionary` 下标的信息请参考 [读取和修改字典](./04_Collection_Types.md#accessing-and-modifying-a-dictionary)。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 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` 结构体的下标接受两个整型参数:
|
||||
|
||||
```swift
|
||||
struct Matrix {
|
||||
let rows: Int, columns: Int
|
||||
var grid: [Double]
|
||||
init(rows: Int, columns: Int) {
|
||||
self.rows = rows
|
||||
self.columns = columns
|
||||
grid = Array(repeating: 0.0, count: rows * columns)
|
||||
}
|
||||
func indexIsValid(row: Int, column: Int) -> Bool {
|
||||
return row >= 0 && row < rows && column >= 0 && column < columns
|
||||
}
|
||||
subscript(row: Int, column: Int) -> Double {
|
||||
get {
|
||||
assert(indexIsValid(row: row, column: column), "Index out of range")
|
||||
return grid[(row * columns) + column]
|
||||
}
|
||||
set {
|
||||
assert(indexIsValid(row: row, column: column), "Index out of range")
|
||||
grid[(row * columns) + column] = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Matrix` 提供了一个接受两个入参的构造方法,入参分别是 `rows` 和 `columns`,创建了一个足够容纳 `rows * columns` 个 `Double` 类型的值的数组。通过传入数组长度和初始值 `0.0` 到数组的构造器,将矩阵中每个位置的值初始化为 `0.0`。关于数组的这种构造方法请参考 [创建一个带有默认值的数组](./04_Collection_Types.md#creating-an-array-with-a-default-value)。
|
||||
|
||||
你可以通过传入合适的 `row` 和 `column` 数值来构造一个新的 `Matrix` 实例:
|
||||
|
||||
```swift
|
||||
var matrix = Matrix(rows: 2, columns: 2)
|
||||
```
|
||||
|
||||
上例中创建了一个两行两列的 `Matrix` 实例。该 `Matrix` 实例的 `grid` 数组按照从左上到右下的阅读顺序将矩阵扁平化存储:
|
||||
|
||||

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

|
||||
|
||||
`Matrix` 下标的 getter 和 setter 中都含有断言,用来检查下标入参 `row` 和 `column` 的值是否有效。为了方便进行断言,`Matrix` 包含了一个名为 `indexIsValid(row:column:)` 的便利方法,用来检查入参 `row` 和 `column` 的值是否在矩阵范围内:
|
||||
|
||||
```swift
|
||||
func indexIsValid(row: Int, column: Int) -> Bool {
|
||||
return row >= 0 && row < rows && column >= 0 && column < columns
|
||||
}
|
||||
```
|
||||
|
||||
断言在下标越界时触发:
|
||||
|
||||
```swift
|
||||
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)
|
||||
```
|
||||
|
||||
221
source/02_language_guide/13_Inheritance.md
Executable file
221
source/02_language_guide/13_Inheritance.md
Executable file
@ -0,0 +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}
|
||||
|
||||
你可以通过重写属性为一个继承来的属性添加属性观察器。这样一来,无论被继承属性原本是如何实现的,当其属性值发生改变时,你就会被通知到。关于属性观察器的更多内容,请看 [属性观察器](../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 。这样的类是不可被继承的,试图继承这样的类会导致编译报错。
|
||||
1064
source/02_language_guide/14_Initialization.md
Executable file
1064
source/02_language_guide/14_Initialization.md
Executable file
File diff suppressed because it is too large
Load Diff
96
source/02_language_guide/15_Deinitialization.md
Executable file
96
source/02_language_guide/15_Deinitialization.md
Executable file
@ -0,0 +1,96 @@
|
||||
# 析构过程
|
||||
|
||||
*析构器*只适用于类类型,当一个类的实例被释放之前,析构器会被立即调用。析构器用关键字 `deinit` 来标示,类似于构造器要用 `init` 来标示。
|
||||
|
||||
## 析构过程原理 {#how-deinitialization-works}
|
||||
|
||||
Swift 会自动释放不再需要的实例以释放资源。如 [自动引用计数](./24_Automatic_Reference_Counting.md) 章节中所讲述,Swift 通过*自动引用计数(ARC)* 处理实例的内存管理。通常当你的实例被释放时不需要手动地去清理。但是,当使用自己的资源时,你可能需要进行一些额外的清理。例如,如果创建了一个自定义的类来打开一个文件,并写入一些数据,你可能需要在类实例被释放之前手动去关闭该文件。
|
||||
|
||||
在类的定义中,每个类最多只能有一个析构器,而且析构器不带任何参数和圆括号,如下所示:
|
||||
|
||||
```swift
|
||||
deinit {
|
||||
// 执行析构过程
|
||||
}
|
||||
```
|
||||
|
||||
析构器是在实例释放发生前被自动调用的。你不能主动调用析构器。子类继承了父类的析构器,并且在子类析构器实现的最后,父类的析构器会被自动调用。即使子类没有提供自己的析构器,父类的析构器也同样会被调用。
|
||||
|
||||
因为直到实例的析构器被调用后,实例才会被释放,所以析构器可以访问实例的所有属性,并且可以根据那些属性可以修改它的行为(比如查找一个需要被关闭的文件)。
|
||||
|
||||
## 析构器实践 {#deinitializers-in-action}
|
||||
|
||||
这是一个析构器实践的例子。这个例子描述了一个简单的游戏,这里定义了两种新类型,分别是 `Bank` 和 `Player`。`Bank` 类管理一种虚拟硬币,确保流通的硬币数量永远不可能超过 10,000。在游戏中有且只能有一个 `Bank` 存在,因此 `Bank` 用类来实现,并使用类型属性和类型方法来存储和管理其当前状态。
|
||||
|
||||
```swift
|
||||
class Bank {
|
||||
static var coinsInBank = 10_000
|
||||
static func distribute(coins numberOfCoinsRequested: Int) -> Int {
|
||||
let numberOfCoinsToVend = min(numberOfCoinsRequested, coinsInBank)
|
||||
coinsInBank -= numberOfCoinsToVend
|
||||
return numberOfCoinsToVend
|
||||
}
|
||||
static func receive(coins: Int) {
|
||||
coinsInBank += coins
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Bank` 使用 `coinsInBank` 属性来跟踪它当前拥有的硬币数量。`Bank` 还提供了两个方法,`distribute(coins:)` 和 `receive(coins:)`,分别用来处理硬币的分发和收集。
|
||||
|
||||
`distribute(coins:)` 方法在 `Bank` 对象分发硬币之前检查是否有足够的硬币。如果硬币不足,`Bank` 对象会返回一个比请求时小的数字(如果 `Bank` 对象中没有硬币了就返回 `0`)。此方法返回一个整型值,表示提供的硬币的实际数量。
|
||||
|
||||
`receive(coins:)` 方法只是将 `Bank` 实例接收到的硬币数目加回硬币存储中。
|
||||
|
||||
`Player` 类描述了游戏中的一个玩家。每一个玩家在任意时间都有一定数量的硬币存储在他们的钱包中。这通过玩家的 `coinsInPurse` 属性来表示:
|
||||
|
||||
```swift
|
||||
class Player {
|
||||
var coinsInPurse: Int
|
||||
init(coins: Int) {
|
||||
coinsInPurse = Bank.distribute(coins: coins)
|
||||
}
|
||||
func win(coins: Int) {
|
||||
coinsInPurse += Bank.distribute(coins: coins)
|
||||
}
|
||||
deinit {
|
||||
Bank.receive(coins: coinsInPurse)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
每个 `Player` 实例在初始化的过程中,都从 `Bank` 对象获取指定数量的硬币。如果没有足够的硬币可用,`Player` 实例可能会收到比指定数量少的硬币。
|
||||
|
||||
`Player` 类定义了一个 `win(coins:)` 方法,该方法从 `Bank` 对象获取一定数量的硬币,并把它们添加到玩家的钱包。`Player` 类还实现了一个析构器,这个析构器在 `Player` 实例释放前被调用。在这里,析构器的作用只是将玩家的所有硬币都返还给 `Bank` 对象:
|
||||
|
||||
```swift
|
||||
var playerOne: Player? = Player(coins: 100)
|
||||
print("A new player has joined the game with \(playerOne!.coinsInPurse) coins")
|
||||
// 打印“A new player has joined the game with 100 coins”
|
||||
print("There are now \(Bank.coinsInBank) coins left in the bank")
|
||||
// 打印“There are now 9900 coins left in the bank”
|
||||
```
|
||||
|
||||
创建一个 `Player` 实例的时候,会向 `Bank` 对象申请得到 100 个硬币,前提是有足够的硬币可用。这个 `Player` 实例存储在一个名为 `playerOne` 的可选类型的变量中。这里使用了一个可选类型的变量,是因为玩家可以随时离开游戏,设置为可选使你可以追踪玩家当前是否在游戏中。
|
||||
|
||||
因为 `playerOne` 是可选的,所以在访问其 `coinsInPurse` 属性来打印钱包中的硬币数量和调用 `win(coins:)` 方法时,使用感叹号(`!`)强制解包:
|
||||
|
||||
```swift
|
||||
playerOne!.win(coins: 2_000)
|
||||
print("PlayerOne won 2000 coins & now has \(playerOne!.coinsInPurse) coins")
|
||||
// 打印“PlayerOne won 2000 coins & now has 2100 coins”
|
||||
print("The bank now only has \(Bank.coinsInBank) coins left")
|
||||
// 打印“The bank now only has 7900 coins left”
|
||||
```
|
||||
|
||||
在这里,玩家已经赢得了 2,000 枚硬币,所以玩家的钱包中现在有 2,100 枚硬币,而 `Bank` 对象只剩余 7,900 枚硬币。
|
||||
|
||||
```swift
|
||||
playerOne = nil
|
||||
print("PlayerOne has left the game")
|
||||
// 打印“PlayerOne has left the game”
|
||||
print("The bank now has \(Bank.coinsInBank) coins")
|
||||
// 打印“The bank now has 10000 coins”
|
||||
```
|
||||
|
||||
玩家现在已经离开了游戏。这通过将可选类型的 `playerOne` 变量设置为 `nil` 来表示,意味着“没有 `Player` 实例”。当这一切发生时,`playerOne` 变量对 `Player` 实例的引用被破坏了。没有其它属性或者变量引用 `Player` 实例,因此该实例会被释放,以便回收内存。在这之前,该实例的析构器被自动调用,玩家的硬币被返还给银行。
|
||||
374
source/02_language_guide/16_Optional_Chaining.md
Executable file
374
source/02_language_guide/16_Optional_Chaining.md
Executable file
@ -0,0 +1,374 @@
|
||||
# 可选链式调用
|
||||
|
||||
*可选链式调用*是一种可以在当前值可能为 `nil` 的可选值上请求和调用属性、方法及下标的方法。如果可选值有值,那么调用就会成功;如果可选值是 `nil`,那么调用将返回 `nil`。多个调用可以连接在一起形成一个调用链,如果其中任何一个节点为 `nil`,整个调用链都会失败,即返回 `nil`。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> Swift 的可选链式调用和 Objective-C 中向 `nil` 发送消息有些相像,但是 Swift 的可选链式调用可以应用于任意类型,并且能检查调用是否成功。
|
||||
|
||||
## 使用可选链式调用代替强制解包 {#optional-chaining-as-an-alternative-to-forced-unwrapping}
|
||||
|
||||
通过在想调用的属性、方法,或下标的可选值后面放一个问号(`?`),可以定义一个可选链。这一点很像在可选值后面放一个叹号(`!`)来强制解包它的值。它们的主要区别在于当可选值为空时可选链式调用只会调用失败,然而强制解包将会触发运行时错误。
|
||||
|
||||
为了反映可选链式调用可以在空值(`nil`)上调用的事实,不论这个调用的属性、方法及下标返回的值是不是可选值,它的返回结果都是一个可选值。你可以利用这个返回值来判断你的可选链式调用是否调用成功,如果调用有返回值则说明调用成功,返回 `nil` 则说明调用失败。
|
||||
|
||||
这里需要特别指出,可选链式调用的返回结果与原本的返回结果具有相同的类型,但是被包装成了一个可选值。例如,使用可选链式调用访问属性,当可选链式调用成功时,如果属性原本的返回结果是 `Int` 类型,则会变为 `Int?` 类型。
|
||||
|
||||
下面几段代码将解释可选链式调用和强制解包的不同。
|
||||
|
||||
首先定义两个类 `Person` 和 `Residence`:
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
var residence: Residence?
|
||||
}
|
||||
|
||||
class Residence {
|
||||
var numberOfRooms = 1
|
||||
}
|
||||
```
|
||||
|
||||
`Residence` 有一个 `Int` 类型的属性 `numberOfRooms`,其默认值为 `1`。`Person` 具有一个可选的 `residence` 属性,其类型为 `Residence?`。
|
||||
|
||||
假如你创建了一个新的 `Person` 实例,它的 `residence` 属性由于是可选类型而将被初始化为 `nil`,在下面的代码中,`john` 有一个值为 `nil` 的 `residence` 属性:
|
||||
|
||||
```swift
|
||||
let john = Person()
|
||||
```
|
||||
|
||||
如果使用叹号(`!`)强制解包获得这个 `john` 的 `residence` 属性中的 `numberOfRooms` 值,会触发运行时错误,因为这时 `residence` 没有可以解包的值:
|
||||
|
||||
```swift
|
||||
let roomCount = john.residence!.numberOfRooms
|
||||
// 这会引发运行时错误
|
||||
```
|
||||
|
||||
`john.residence` 为非 `nil` 值的时候,上面的调用会成功,并且把 `roomCount` 设置为 `Int` 类型的房间数量。正如上面提到的,当 `residence` 为 `nil` 的时候,上面这段代码会触发运行时错误。
|
||||
|
||||
可选链式调用提供了另一种访问 `numberOfRooms` 的方式,使用问号(`?`)来替代原来的叹号(`!`):
|
||||
|
||||
```swift
|
||||
if let roomCount = john.residence?.numberOfRooms {
|
||||
print("John's residence has \(roomCount) room(s).")
|
||||
} else {
|
||||
print("Unable to retrieve the number of rooms.")
|
||||
}
|
||||
// 打印“Unable to retrieve the number of rooms.”
|
||||
```
|
||||
|
||||
在 `residence` 后面添加问号之后,Swift 就会在 `residence` 不为 `nil` 的情况下访问 `numberOfRooms`。
|
||||
|
||||
因为访问 `numberOfRooms` 有可能失败,可选链式调用会返回 `Int?` 类型,或称为“可选的 `Int`”。如上例所示,当 `residence` 为 `nil` 的时候,可选的 `Int` 将会为 `nil`,表明无法访问 `numberOfRooms`。访问成功时,可选的 `Int` 值会通过可选绑定解包,并赋值给非可选类型的 `roomCount` 常量。
|
||||
|
||||
要注意的是,即使 `numberOfRooms` 是非可选的 `Int` 时,这一点也成立。只要使用可选链式调用就意味着 `numberOfRooms` 会返回一个 `Int?` 而不是 `Int`。
|
||||
|
||||
可以将一个 `Residence` 的实例赋给 `john.residence`,这样它就不再是 `nil` 了:
|
||||
|
||||
```swift
|
||||
john.residence = Residence()
|
||||
```
|
||||
|
||||
`john.residence` 现在包含一个实际的 `Residence` 实例,而不再是 `nil`。如果你试图使用先前的可选链式调用访问 `numberOfRooms`,它现在将返回值为 `1` 的 `Int?` 类型的值:
|
||||
|
||||
```swift
|
||||
if let roomCount = john.residence?.numberOfRooms {
|
||||
print("John's residence has \(roomCount) room(s).")
|
||||
} else {
|
||||
print("Unable to retrieve the number of rooms.")
|
||||
}
|
||||
// 打印“John's residence has 1 room(s).”
|
||||
```
|
||||
|
||||
## 为可选链式调用定义模型类 {#defining-model-classes-for-optional-chaining}
|
||||
|
||||
通过使用可选链式调用可以调用多层属性、方法和下标。这样可以在复杂的模型中向下访问各种子属性,并且判断能否访问子属性的属性、方法和下标。
|
||||
|
||||
下面这段代码定义了四个模型类,这些例子包括多层可选链式调用。为了方便说明,在 `Person` 和 `Residence` 的基础上增加了 `Room` 类和 `Address` 类,以及相关的属性、方法以及下标。
|
||||
|
||||
`Person` 类的定义基本保持不变:
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
var residence: Residence?
|
||||
}
|
||||
```
|
||||
|
||||
`Residence` 类比之前复杂些,增加了一个名为 `rooms` 的变量属性,该属性被初始化为 `[Room]` 类型的空数组:
|
||||
|
||||
```swift
|
||||
class Residence {
|
||||
var rooms: [Room] = []
|
||||
var numberOfRooms: Int {
|
||||
return rooms.count
|
||||
}
|
||||
subscript(i: Int) -> Room {
|
||||
get {
|
||||
return rooms[i]
|
||||
}
|
||||
set {
|
||||
rooms[i] = newValue
|
||||
}
|
||||
}
|
||||
func printNumberOfRooms() {
|
||||
print("The number of rooms is \(numberOfRooms)")
|
||||
}
|
||||
var address: Address?
|
||||
}
|
||||
```
|
||||
|
||||
现在 `Residence` 有了一个存储 `Room` 实例的数组,`numberOfRooms` 属性被实现为计算型属性,而不是存储型属性。`numberOfRooms` 属性简单地返回 `rooms` 数组的 `count` 属性的值。
|
||||
|
||||
`Residence` 还提供了访问 `rooms` 数组的快捷方式,即提供可读写的下标来访问 `rooms` 数组中指定位置的元素。
|
||||
|
||||
此外,`Residence` 还提供了 `printNumberOfRooms` 方法,这个方法的作用是打印 `numberOfRooms` 的值。
|
||||
|
||||
最后,`Residence` 还定义了一个可选属性 `address`,其类型为 `Address?`。`Address` 类的定义在下面会说明。
|
||||
|
||||
`Room` 类是一个简单类,其实例被存储在 `rooms` 数组中。该类只包含一个属性 `name`,以及一个用于将该属性设置为适当的房间名的初始化函数:
|
||||
|
||||
```swift
|
||||
class Room {
|
||||
let name: String
|
||||
init(name: String) { self.name = name }
|
||||
}
|
||||
```
|
||||
|
||||
最后一个类是 `Address`,这个类有三个 `String?` 类型的可选属性。`buildingName` 以及 `buildingNumber` 属性分别表示大厦的名称和号码,第三个属性 `street` 表示大厦所在街道的名称:
|
||||
|
||||
```swift
|
||||
class Address {
|
||||
var buildingName: String?
|
||||
var buildingNumber: String?
|
||||
var street: String?
|
||||
func buildingIdentifier() -> String? {
|
||||
if buildingName != nil {
|
||||
return buildingName
|
||||
} else if let buildingNumber = buildingNumber, let street = street {
|
||||
return "\(buildingNumber) \(street)"
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Address` 类提供了 `buildingIdentifier()` 方法,返回值为 `String?`。 如果 `buildingName` 有值则返回 `buildingName`。或者,如果 `buildingNumber` 和 `street` 均有值,则返回两者拼接得到的字符串。否则,返回 `nil`。
|
||||
|
||||
## 通过可选链式调用访问属性 {#accessing-properties-through-optional-chaining}
|
||||
|
||||
正如 [使用可选链式调用代替强制解包](#optional-chaining-as-an-alternative-to-forced-unwrapping) 中所述,可以通过可选链式调用在一个可选值上访问它的属性,并判断访问是否成功。
|
||||
|
||||
使用前面定义过的类,创建一个 `Person` 实例,然后像之前一样,尝试访问 `numberOfRooms` 属性:
|
||||
|
||||
```swift
|
||||
let john = Person()
|
||||
if let roomCount = john.residence?.numberOfRooms {
|
||||
print("John's residence has \(roomCount) room(s).")
|
||||
} else {
|
||||
print("Unable to retrieve the number of rooms.")
|
||||
}
|
||||
// 打印“Unable to retrieve the number of rooms.”
|
||||
```
|
||||
|
||||
因为 `john.residence` 为 `nil`,所以这个可选链式调用依旧会像先前一样失败。
|
||||
|
||||
还可以通过可选链式调用来设置属性值:
|
||||
|
||||
```swift
|
||||
let someAddress = Address()
|
||||
someAddress.buildingNumber = "29"
|
||||
someAddress.street = "Acacia Road"
|
||||
john.residence?.address = someAddress
|
||||
```
|
||||
|
||||
在这个例子中,通过 `john.residence` 来设定 `address` 属性也会失败,因为 `john.residence` 当前为 `nil`。
|
||||
|
||||
上面代码中的赋值过程是可选链式调用的一部分,这意味着可选链式调用失败时,等号右侧的代码不会被执行。对于上面的代码来说,很难验证这一点,因为像这样赋值一个常量没有任何副作用。下面的代码完成了同样的事情,但是它使用一个函数来创建 `Address` 实例,然后将该实例返回用于赋值。该函数会在返回前打印“Function was called”,这使你能验证等号右侧的代码是否被执行。
|
||||
|
||||
```swift
|
||||
func createAddress() -> Address {
|
||||
print("Function was called.")
|
||||
|
||||
let someAddress = Address()
|
||||
someAddress.buildingNumber = "29"
|
||||
someAddress.street = "Acacia Road"
|
||||
|
||||
return someAddress
|
||||
}
|
||||
john.residence?.address = createAddress()
|
||||
```
|
||||
|
||||
没有任何打印消息,可以看出 `createAddress()` 函数并未被执行。
|
||||
|
||||
## 通过可选链式调用来调用方法 {#calling-methods-through-optional-chaining}
|
||||
|
||||
可以通过可选链式调用来调用方法,并判断是否调用成功,即使这个方法没有返回值。
|
||||
|
||||
`Residence` 类中的 `printNumberOfRooms()` 方法打印当前的 `numberOfRooms` 值,如下所示:
|
||||
|
||||
```swift
|
||||
func printNumberOfRooms() {
|
||||
print("The number of rooms is \(numberOfRooms)")
|
||||
}
|
||||
```
|
||||
|
||||
这个方法没有返回值。然而,没有返回值的方法具有隐式的返回类型 `Void`,如 [无返回值函数](./06_Functions.md#functions-without-return-values) 中所述。这意味着没有返回值的方法也会返回 `()`,或者说空的元组。
|
||||
|
||||
如果在可选值上通过可选链式调用来调用这个方法,该方法的返回类型会是 `Void?`,而不是 `Void`,因为通过可选链式调用得到的返回值都是可选的。这样我们就可以使用 `if` 语句来判断能否成功调用 `printNumberOfRooms()` 方法,即使方法本身没有定义返回值。通过判断返回值是否为 `nil` 可以判断调用是否成功:
|
||||
|
||||
```swift
|
||||
if john.residence?.printNumberOfRooms() != nil {
|
||||
print("It was possible to print the number of rooms.")
|
||||
} else {
|
||||
print("It was not possible to print the number of rooms.")
|
||||
}
|
||||
// 打印“It was not possible to print the number of rooms.”
|
||||
```
|
||||
|
||||
同样的,可以据此判断通过可选链式调用为属性赋值是否成功。在上面的 [通过可选链式调用访问属性](#accessing-properties-through-optional-chaining) 的例子中,我们尝试给 `john.residence` 中的 `address` 属性赋值,即使 `residence` 为 `nil`。通过可选链式调用给属性赋值会返回 `Void?`,通过判断返回值是否为 `nil` 就可以知道赋值是否成功:
|
||||
|
||||
```swift
|
||||
if (john.residence?.address = someAddress) != nil {
|
||||
print("It was possible to set the address.")
|
||||
} else {
|
||||
print("It was not possible to set the address.")
|
||||
}
|
||||
// 打印“It was not possible to set the address.”
|
||||
```
|
||||
|
||||
## 通过可选链式调用访问下标 {#accessing-subscripts-through-optional-chaining}
|
||||
|
||||
通过可选链式调用,我们可以在一个可选值上访问下标,并且判断下标调用是否成功。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 通过可选链式调用访问可选值的下标时,应该将问号放在下标方括号的前面而不是后面。可选链式调用的问号一般直接跟在可选表达式的后面。
|
||||
|
||||
下面这个例子用下标访问 `john.residence` 属性存储的 `Residence` 实例的 `rooms` 数组中的第一个房间的名称,因为 `john.residence` 为 `nil`,所以下标调用失败了:
|
||||
|
||||
```swift
|
||||
if let firstRoomName = john.residence?[0].name {
|
||||
print("The first room name is \(firstRoomName).")
|
||||
} else {
|
||||
print("Unable to retrieve the first room name.")
|
||||
}
|
||||
// 打印“Unable to retrieve the first room name.”
|
||||
```
|
||||
|
||||
在这个例子中,问号直接放在 `john.residence` 的后面,并且在方括号的前面,因为 `john.residence` 是可选值。
|
||||
|
||||
类似的,可以通过下标,用可选链式调用来赋值:
|
||||
|
||||
```swift
|
||||
john.residence?[0] = Room(name: "Bathroom")
|
||||
```
|
||||
|
||||
这次赋值同样会失败,因为 `residence` 目前是 `nil`。
|
||||
|
||||
如果你创建一个 `Residence` 实例,并为其 `rooms` 数组添加一些 `Room` 实例,然后将 `Residence` 实例赋值给 `john.residence`,那就可以通过可选链和下标来访问数组中的元素:
|
||||
|
||||
```swift
|
||||
let johnsHouse = Residence()
|
||||
johnsHouse.rooms.append(Room(name: "Living Room"))
|
||||
johnsHouse.rooms.append(Room(name: "Kitchen"))
|
||||
john.residence = johnsHouse
|
||||
|
||||
if let firstRoomName = john.residence?[0].name {
|
||||
print("The first room name is \(firstRoomName).")
|
||||
} else {
|
||||
print("Unable to retrieve the first room name.")
|
||||
}
|
||||
// 打印“The first room name is Living Room.”
|
||||
```
|
||||
|
||||
### 访问可选类型的下标 {#accessing-subscripts-of-optional-type}
|
||||
|
||||
如果下标返回可选类型值,比如 Swift 中 `Dictionary` 类型的键的下标,可以在下标的结尾括号后面放一个问号来在其可选返回值上进行可选链式调用:
|
||||
|
||||
```swift
|
||||
var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
|
||||
testScores["Dave"]?[0] = 91
|
||||
testScores["Bev"]?[0] += 1
|
||||
testScores["Brian"]?[0] = 72
|
||||
// "Dave" 数组现在是 [91, 82, 84],"Bev" 数组现在是 [80, 94, 81]
|
||||
```
|
||||
|
||||
上面的例子中定义了一个 `testScores` 数组,包含了两个键值对,分别把 `String` 类型的键映射到一个 `Int` 值的数组。这个例子用可选链式调用把 `"Dave"` 数组中第一个元素设为 `91`,把 `"Bev"` 数组的第一个元素 `+1`,然后尝试把 `"Brian"` 数组中的第一个元素设为 `72`。前两个调用成功,因为 `testScores` 字典中包含 `"Dave"` 和 `"Bev"` 这两个键。但是 `testScores` 字典中没有 `"Brian"` 这个键,所以第三个调用失败。
|
||||
|
||||
## 连接多层可选链式调用 {#linking-multiple-levels-of-chaining}
|
||||
|
||||
可以通过连接多个可选链式调用在更深的模型层级中访问属性、方法以及下标。然而,多层可选链式调用不会增加返回值的可选层级。
|
||||
|
||||
也就是说:
|
||||
|
||||
+ 如果你访问的值不是可选的,可选链式调用将会返回可选值。
|
||||
+ 如果你访问的值就是可选的,可选链式调用不会让可选返回值变得“更可选”。
|
||||
|
||||
因此:
|
||||
|
||||
+ 通过可选链式调用访问一个 `Int` 值,将会返回 `Int?`,无论使用了多少层可选链式调用。
|
||||
+ 类似的,通过可选链式调用访问 `Int?` 值,依旧会返回 `Int?` 值,并不会返回 `Int??`。
|
||||
|
||||
下面的例子尝试访问 `john` 中的 `residence` 属性中的 `address` 属性中的 `street` 属性。这里使用了两层可选链式调用,`residence` 以及 `address` 都是可选值:
|
||||
|
||||
```swift
|
||||
if let johnsStreet = john.residence?.address?.street {
|
||||
print("John's street name is \(johnsStreet).")
|
||||
} else {
|
||||
print("Unable to retrieve the address.")
|
||||
}
|
||||
// 打印“Unable to retrieve the address.”
|
||||
```
|
||||
|
||||
`john.residence` 现在包含一个有效的 `Residence` 实例。然而,`john.residence.address` 的值当前为 `nil`。因此,调用 `john.residence?.address?.street` 会失败。
|
||||
|
||||
需要注意的是,上面的例子中,`street` 的属性为 `String?`。`john.residence?.address?.street` 的返回值也依然是 `String?`,即使已经使用了两层可选链式调用。
|
||||
|
||||
如果为 `john.residence.address` 赋值一个 `Address` 实例,并且为 `address` 中的 `street` 属性设置一个有效值,我们就能过通过可选链式调用来访问 `street` 属性:
|
||||
|
||||
```swift
|
||||
let johnsAddress = Address()
|
||||
johnsAddress.buildingName = "The Larches"
|
||||
johnsAddress.street = "Laurel Street"
|
||||
john.residence?.address = johnsAddress
|
||||
|
||||
if let johnsStreet = john.residence?.address?.street {
|
||||
print("John's street name is \(johnsStreet).")
|
||||
} else {
|
||||
print("Unable to retrieve the address.")
|
||||
}
|
||||
// 打印“John's street name is Laurel Street.”
|
||||
```
|
||||
|
||||
在上面的例子中,因为 `john.residence` 包含一个有效的 `Address` 实例,所以对 `john.residence` 的 `address` 属性赋值将会成功。
|
||||
|
||||
## 在方法的可选返回值上进行可选链式调用 {#chaining-on-methods-with-optional-return-values}
|
||||
|
||||
上面的例子展示了如何在一个可选值上通过可选链式调用来获取它的属性值。我们还可以在一个可选值上通过可选链式调用来调用方法,并且可以根据需要继续在方法的可选返回值上进行可选链式调用。
|
||||
|
||||
在下面的例子中,通过可选链式调用来调用 `Address` 的 `buildingIdentifier()` 方法。这个方法返回 `String?` 类型的值。如上所述,通过可选链式调用来调用该方法,最终的返回值依旧会是 `String?` 类型:
|
||||
|
||||
```swift
|
||||
if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
|
||||
print("John's building identifier is \(buildingIdentifier).")
|
||||
}
|
||||
// 打印“John's building identifier is The Larches.”
|
||||
```
|
||||
|
||||
如果要在该方法的返回值上进行可选链式调用,在方法的圆括号后面加上问号即可:
|
||||
|
||||
```swift
|
||||
if let beginsWithThe =
|
||||
john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
|
||||
if beginsWithThe {
|
||||
print("John's building identifier begins with \"The\".")
|
||||
} else {
|
||||
print("John's building identifier does not begin with \"The\".")
|
||||
}
|
||||
}
|
||||
// 打印“John's building identifier begins with "The".”
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 在上面的例子中,在方法的圆括号后面加上问号是因为你要在 `buildingIdentifier()` 方法的可选返回值上进行可选链式调用,而不是 `buildingIdentifier()` 方法本身。
|
||||
281
source/02_language_guide/17_Error_Handling.md
Executable file
281
source/02_language_guide/17_Error_Handling.md
Executable file
@ -0,0 +1,281 @@
|
||||
# 错误处理
|
||||
|
||||
*错误处理(Error handling)* 是响应错误以及从错误中恢复的过程。Swift 在运行时提供了抛出、捕获、传递和操作可恢复错误(recoverable errors)的一等支持(first-class support)。
|
||||
|
||||
某些操作无法保证总是执行完所有代码或生成有用的结果。可选类型用来表示值缺失,但是当某个操作失败时,理解造成失败的原因有助于你的代码作出相应的应对。
|
||||
|
||||
举个例子,假如有个从磁盘上的某个文件读取数据并进行处理的任务,该任务会有多种可能失败的情况,包括指定路径下文件并不存在,文件不具有可读权限,或者文件编码格式不兼容。区分这些不同的失败情况可以让程序处理并解决某些错误,然后把它解决不了的错误报告给用户。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> Swift 中的错误处理涉及到错误处理模式,这会用到 Cocoa 和 Objective-C 中的 `NSError`。更多详情参见 [用 Swift 解决 Cocoa 错误](https://developer.apple.com/documentation/swift/cocoa_design_patterns/handling_cocoa_errors_in_swift)。
|
||||
|
||||
## 表示与抛出错误 {#representing-and-throwing-errors}
|
||||
|
||||
在 Swift 中,错误用遵循 `Error` 协议的类型的值来表示。这个空协议表明该类型可以用于错误处理。
|
||||
|
||||
Swift 的枚举类型尤为适合构建一组相关的错误状态,枚举的关联值还可以提供错误状态的额外信息。例如,在游戏中操作自动贩卖机时,你可以这样表示可能会出现的错误状态:
|
||||
|
||||
```swift
|
||||
enum VendingMachineError: Error {
|
||||
case invalidSelection //选择无效
|
||||
case insufficientFunds(coinsNeeded: Int) //金额不足
|
||||
case outOfStock //缺货
|
||||
}
|
||||
```
|
||||
|
||||
抛出一个错误可以让你表明有意外情况发生,导致正常的执行流程无法继续执行。抛出错误使用 `throw` 语句。例如,下面的代码抛出一个错误,提示贩卖机还需要 `5` 个硬币:
|
||||
|
||||
```swift
|
||||
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
|
||||
```
|
||||
|
||||
## 处理错误 {#handling-errors}
|
||||
|
||||
某个错误被抛出时,附近的某部分代码必须负责处理这个错误,例如纠正这个问题、尝试另外一种方式、或是向用户报告错误。
|
||||
|
||||
Swift 中有 `4` 种处理错误的方式。你可以把函数抛出的错误传递给调用此函数的代码、用 `do-catch` 语句处理错误、将错误作为可选类型处理、或者断言此错误根本不会发生。每种方式在下面的小节中都有描述。
|
||||
|
||||
当一个函数抛出一个错误时,你的程序流程会发生改变,所以重要的是你能迅速识别代码中会抛出错误的地方。为了标识出这些地方,在调用一个能抛出错误的函数、方法或者构造器之前,加上 `try` 关键字,或者 `try?` 或 `try!` 这种变体。这些关键字在下面的小节中有具体讲解。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> Swift 中的错误处理和其他语言中用 `try`,`catch` 和 `throw` 进行异常处理很像。和其他语言中(包括 Objective-C )的异常处理不同的是,Swift 中的错误处理并不涉及解除调用栈,这是一个计算代价高昂的过程。就此而言,`throw` 语句的性能特性是可以和 `return` 语句相媲美的。
|
||||
|
||||
### 用 throwing 函数传递错误 {#propagating-errors-using-throwing-functions}
|
||||
|
||||
为了表示一个函数、方法或构造器可以抛出错误,在函数声明的参数之后加上 `throws` 关键字。一个标有 `throws` 关键字的函数被称作 *throwing 函数*。如果这个函数指明了返回值类型,`throws` 关键词需要写在返回箭头(`->`)的前面。
|
||||
|
||||
```swift
|
||||
func canThrowErrors() throws -> String
|
||||
|
||||
func cannotThrowErrors() -> String
|
||||
```
|
||||
|
||||
一个 throwing 函数可以在其内部抛出错误,并将错误传递到函数被调用时的作用域。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 只有 throwing 函数可以传递错误。任何在某个非 throwing 函数内部抛出的错误只能在函数内部处理。
|
||||
|
||||
下面的例子中,`VendingMachine` 类有一个 `vend(itemNamed:)` 方法,如果请求的物品不存在、缺货或者投入金额小于物品价格,该方法就会抛出一个相应的 `VendingMachineError`:
|
||||
|
||||
```swift
|
||||
struct Item {
|
||||
var price: Int
|
||||
var count: Int
|
||||
}
|
||||
|
||||
class VendingMachine {
|
||||
var inventory = [
|
||||
"Candy Bar": Item(price: 12, count: 7),
|
||||
"Chips": Item(price: 10, count: 4),
|
||||
"Pretzels": Item(price: 7, count: 11)
|
||||
]
|
||||
var coinsDeposited = 0
|
||||
|
||||
func vend(itemNamed name: String) throws {
|
||||
guard let item = inventory[name] else {
|
||||
throw VendingMachineError.invalidSelection
|
||||
}
|
||||
|
||||
guard item.count > 0 else {
|
||||
throw VendingMachineError.outOfStock
|
||||
}
|
||||
|
||||
guard item.price <= coinsDeposited else {
|
||||
throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited)
|
||||
}
|
||||
|
||||
coinsDeposited -= item.price
|
||||
|
||||
var newItem = item
|
||||
newItem.count -= 1
|
||||
inventory[name] = newItem
|
||||
|
||||
print("Dispensing \(name)")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在 `vend(itemNamed:)` 方法的实现中使用了 `guard` 语句来确保在购买某个物品所需的条件中有任一条件不满足时,能提前退出方法并抛出相应的错误。由于 `throw` 语句会立即退出方法,所以物品只有在所有条件都满足时才会被售出。
|
||||
|
||||
因为 `vend(itemNamed:)` 方法会传递出它抛出的任何错误,在你的代码中调用此方法的地方,必须要么直接处理这些错误——使用 `do-catch` 语句,`try?` 或 `try!`;要么继续将这些错误传递下去。例如下面例子中,`buyFavoriteSnack(person:vendingMachine:)` 同样是一个 throwing 函数,任何由 `vend(itemNamed:)` 方法抛出的错误会一直被传递到 `buyFavoriteSnack(person:vendingMachine:)` 函数被调用的地方。
|
||||
|
||||
```swift
|
||||
let favoriteSnacks = [
|
||||
"Alice": "Chips",
|
||||
"Bob": "Licorice",
|
||||
"Eve": "Pretzels",
|
||||
]
|
||||
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
|
||||
let snackName = favoriteSnacks[person] ?? "Candy Bar"
|
||||
try vendingMachine.vend(itemNamed: snackName)
|
||||
}
|
||||
```
|
||||
|
||||
上例中,`buyFavoriteSnack(person:vendingMachine:)` 函数会查找某人最喜欢的零食,并通过调用 `vend(itemNamed:)` 方法来尝试为他们购买。因为 `vend(itemNamed:)` 方法能抛出错误,所以在调用它的时候在它前面加了 `try` 关键字。
|
||||
|
||||
`throwing` 构造器能像 `throwing` 函数一样传递错误。例如下面代码中的 `PurchasedSnack` 构造器在构造过程中调用了 throwing 函数,并且通过传递到它的调用者来处理这些错误。
|
||||
|
||||
```swift
|
||||
struct PurchasedSnack {
|
||||
let name: String
|
||||
init(name: String, vendingMachine: VendingMachine) throws {
|
||||
try vendingMachine.vend(itemNamed: name)
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 用 Do-Catch 处理错误 {#handling-errors-using-do-Catch}
|
||||
|
||||
你可以使用一个 `do-catch` 语句运行一段闭包代码来处理错误。如果在 `do` 子句中的代码抛出了一个错误,这个错误会与 `catch` 子句做匹配,从而决定哪条子句能处理它。
|
||||
|
||||
下面是 `do-catch` 语句的一般形式:
|
||||
|
||||
```swift
|
||||
do {
|
||||
try expression
|
||||
statements
|
||||
} catch pattern 1 {
|
||||
statements
|
||||
} catch pattern 2 where condition {
|
||||
statements
|
||||
} catch pattern 3, pattern 4 where condition {
|
||||
statements
|
||||
} catch {
|
||||
statements
|
||||
}
|
||||
```
|
||||
|
||||
在 `catch` 后面写一个匹配模式来表明这个子句能处理什么样的错误。如果一条 `catch` 子句没有指定匹配模式,那么这条子句可以匹配任何错误,并且把错误绑定到一个名字为 `error` 的局部常量。关于模式匹配的更多信息请参考 [模式](../03_language_reference/08_Patterns.md)。
|
||||
|
||||
举例来说,下面的代码处理了 `VendingMachineError` 枚举类型的全部三种情况:
|
||||
|
||||
```swift
|
||||
var vendingMachine = VendingMachine()
|
||||
vendingMachine.coinsDeposited = 8
|
||||
do {
|
||||
try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
|
||||
print("Success! Yum.")
|
||||
} catch VendingMachineError.invalidSelection {
|
||||
print("Invalid Selection.")
|
||||
} catch VendingMachineError.outOfStock {
|
||||
print("Out of Stock.")
|
||||
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
|
||||
print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
|
||||
} catch {
|
||||
print("Unexpected error: \(error).")
|
||||
}
|
||||
// 打印“Insufficient funds. Please insert an additional 2 coins.”
|
||||
```
|
||||
|
||||
上面的例子中,`buyFavoriteSnack(person:vendingMachine:)` 函数在一个 `try` 表达式中被调用,是因为它能抛出错误。如果错误被抛出,相应的执行会马上转移到 `catch` 子句中,并判断这个错误是否要被继续传递下去。如果错误没有被匹配,它会被最后一个 `catch` 语句捕获,并赋值给一个 `error` 常量。如果没有错误被抛出,`do` 子句中余下的语句就会被执行。
|
||||
|
||||
`catch` 子句不必将 `do` 子句中的代码所抛出的每一个可能的错误都作处理。如果所有 `catch` 子句都未处理错误,错误就会传递到周围的作用域。然而,错误还是必须要被某个周围的作用域处理的。在不会抛出错误的函数中,必须用 `do-catch` 语句处理错误。而能够抛出错误的函数既可以使用 `do-catch` 语句处理,也可以让调用方来处理错误。如果错误传递到了顶层作用域却依然没有被处理,你会得到一个运行时错误。
|
||||
|
||||
以下面的代码为例,不是 `VendingMachineError` 中声明的错误会在调用函数的地方被捕获:
|
||||
|
||||
```swift
|
||||
func nourish(with item: String) throws {
|
||||
do {
|
||||
try vendingMachine.vend(itemNamed: item)
|
||||
} catch is VendingMachineError {
|
||||
print("Couldn't buy that from the vending machine.")
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
try nourish(with: "Beet-Flavored Chips")
|
||||
} catch {
|
||||
print("Unexpected non-vending-machine-related error: \(error)")
|
||||
}
|
||||
// 打印“Couldn't buy that from the vending machine.”
|
||||
```
|
||||
|
||||
如果 `vend(itemNamed:)` 抛出的是一个 `VendingMachineError` 类型的错误,`nourish(with:)` 会打印一条消息,否则 `nourish(with:)` 会将错误抛给它的调用方。这个错误之后会被通用的 `catch` 语句捕获。
|
||||
|
||||
另一种捕获多个相关错误的方式是将它们放在 `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` 有着相同的数值和等价的含义:
|
||||
|
||||
```swift
|
||||
func someThrowingFunction() throws -> Int {
|
||||
// ...
|
||||
}
|
||||
|
||||
let x = try? someThrowingFunction()
|
||||
|
||||
let y: Int?
|
||||
do {
|
||||
y = try someThrowingFunction()
|
||||
} catch {
|
||||
y = nil
|
||||
}
|
||||
```
|
||||
|
||||
如果 `someThrowingFunction()` 抛出一个错误,`x` 和 `y` 的值是 `nil`。否则 `x` 和 `y` 的值就是该函数的返回值。注意,无论 `someThrowingFunction()` 的返回值类型是什么类型,`x` 和 `y` 都是这个类型的可选类型。例子中此函数返回一个整型,所以 `x` 和 `y` 是可选整型。
|
||||
|
||||
如果你想对所有的错误都采用同样的方式来处理,用 `try?` 就可以让你写出简洁的错误处理代码。例如,下面的代码用几种方式来获取数据,如果所有方式都失败了则返回 `nil`。
|
||||
|
||||
```swift
|
||||
func fetchData() -> Data? {
|
||||
if let data = try? fetchDataFromDisk() { return data }
|
||||
if let data = try? fetchDataFromServer() { return data }
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### 禁用错误传递 {#disabling-error-propagation}
|
||||
|
||||
有时你知道某个 `throwing` 函数实际上在运行时是不会抛出错误的,在这种情况下,你可以在表达式前面写 `try!` 来禁用错误传递,这会把调用包装在一个不会有错误抛出的运行时断言中。如果真的抛出了错误,你会得到一个运行时错误。
|
||||
|
||||
例如,下面的代码使用了 `loadImage(atPath:)` 函数,该函数从给定的路径加载图片资源,如果图片无法载入则抛出一个错误。在这种情况下,因为图片是和应用绑定的,运行时不会有错误抛出,所以适合禁用错误传递。
|
||||
|
||||
```swift
|
||||
let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")
|
||||
```
|
||||
|
||||
## 指定清理操作 {#specifying-cleanup-actions}
|
||||
|
||||
你可以使用 `defer` 语句在即将离开当前代码块时执行一系列语句。该语句让你能执行一些必要的清理工作,不管是以何种方式离开当前代码块的——无论是由于抛出错误而离开,或是由于诸如 `return`、`break` 的语句。例如,你可以用 `defer` 语句来确保文件描述符得以关闭,以及手动分配的内存得以释放。
|
||||
|
||||
`defer` 语句将代码的执行延迟到当前的作用域退出之前。该语句由 `defer` 关键字和要被延迟执行的语句组成。延迟执行的语句不能包含任何控制转移语句,例如 `break`、`return` 语句,或是抛出一个错误。延迟执行的操作会按照它们声明的顺序从后往前执行——也就是说,第一条 `defer` 语句中的代码最后才执行,第二条 `defer` 语句中的代码倒数第二个执行,以此类推。最后一条语句会第一个执行。
|
||||
|
||||
```swift
|
||||
func processFile(filename: String) throws {
|
||||
if exists(filename) {
|
||||
let file = open(filename)
|
||||
defer {
|
||||
close(file)
|
||||
}
|
||||
while let line = try file.readline() {
|
||||
// 处理文件。
|
||||
}
|
||||
// close(file) 会在这里被调用,即作用域的最后。
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上面的代码使用一条 `defer` 语句来确保 `open(_:)` 函数有一个相应的对 `close(_:)` 函数的调用。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 即使没有涉及到错误处理的代码,你也可以使用 `defer` 语句。
|
||||
@ -1,32 +1,12 @@
|
||||
# 类型转换(Type Casting)
|
||||
-----------------
|
||||
# 类型转换
|
||||
|
||||
> 1.0
|
||||
> 翻译:[xiehurricane](https://github.com/xiehurricane)
|
||||
> 校对:[happyming](https://github.com/happyming)
|
||||
*类型转换*可以判断实例的类型,也可以将实例看做是其父类或者子类的实例。
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[yangsiy](https://github.com/yangsiy)
|
||||
类型转换在 Swift 中使用 `is` 和 `as` 操作符实现。这两个操作符分别提供了一种简单达意的方式去检查值的类型或者转换它的类型。
|
||||
|
||||
> 2.1
|
||||
> 校对:[shanks](http://codebuild.me),2015-11-01
|
||||
你也可以用它来检查一个类型是否遵循了某个协议,就像在 [检验协议遵循](./21_Protocols.md#checking-for-protocol-conformance) 部分讲述的一样。
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [定义一个类层次作为例子](#defining_a_class_hierarchy_for_type_casting)
|
||||
- [检查类型](#checking_type)
|
||||
- [向下转型(Downcasting)](#downcasting)
|
||||
- [`Any` 和 `AnyObject` 的类型转换](#type_casting_for_any_and_anyobject)
|
||||
|
||||
|
||||
_类型转换_ 可以判断实例的类型,也可以将实例看做是其父类或者子类的实例。
|
||||
|
||||
类型转换在 Swift 中使用 `is` 和 `as` 操作符实现。这两个操作符提供了一种简单达意的方式去检查值的类型或者转换它的类型。
|
||||
|
||||
你也可以用它来检查一个类型是否实现了某个协议,就像在[检验协议的一致性](./22_Protocols.html#checking_for_protocol_conformance)部分讲述的一样。
|
||||
|
||||
<a name="defining_a_class_hierarchy_for_type_casting"></a>
|
||||
## 定义一个类层次作为例子
|
||||
## 为类型转换定义类层次 {#defining-a-class-hierarchy-for-type-casting}
|
||||
|
||||
你可以将类型转换用在类和子类的层次结构上,检查特定类实例的类型并且转换这个类实例的类型成为这个层次结构中的其他类型。下面的三个代码段定义了一个类层次和一个包含了这些类实例的数组,作为类型转换的例子。
|
||||
|
||||
@ -76,10 +56,9 @@ let library = [
|
||||
|
||||
在幕后 `library` 里存储的媒体项依然是 `Movie` 和 `Song` 类型的。但是,若你迭代它,依次取出的实例会是 `MediaItem` 类型的,而不是 `Movie` 和 `Song` 类型。为了让它们作为原本的类型工作,你需要检查它们的类型或者向下转换它们到其它类型,就像下面描述的一样。
|
||||
|
||||
<a name="checking_type"></a>
|
||||
## 检查类型(Checking Type)
|
||||
## 检查类型 {#checking-type}
|
||||
|
||||
用类型检查操作符(`is`)来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回 `true`,否则返回 `false`。
|
||||
用*类型检查操作符*(`is`)来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回 `true`,否则返回 `false`。
|
||||
|
||||
下面的例子定义了两个变量,`movieCount` 和 `songCount`,用来计算数组 `library` 中 `Movie` 和 `Song` 类型的实例数量:
|
||||
|
||||
@ -89,30 +68,29 @@ var songCount = 0
|
||||
|
||||
for item in library {
|
||||
if item is Movie {
|
||||
++movieCount
|
||||
movieCount += 1
|
||||
} else if item is Song {
|
||||
++songCount
|
||||
songCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
print("Media library contains \(movieCount) movies and \(songCount) songs")
|
||||
// 打印 “Media library contains 2 movies and 3 songs”
|
||||
// 打印“Media library contains 2 movies and 3 songs”
|
||||
```
|
||||
|
||||
示例迭代了数组 `library` 中的所有项。每一次,`for-in` 循环设置
|
||||
`item` 为数组中的下一个 `MediaItem`。
|
||||
`item` 常量为数组中的下一个 `MediaItem` 实例。
|
||||
|
||||
若当前 `MediaItem` 是一个 `Movie` 类型的实例,`item is Movie` 返回
|
||||
`true`,否则返回 `false`。同样的,`item is Song` 检查 `item` 是否为 `Song` 类型的实例。在循环结束后,`movieCount` 和 `songCount` 的值就是被找到的属于各自类型的实例的数量。
|
||||
|
||||
<a name="downcasting"></a>
|
||||
## 向下转型(Downcasting)
|
||||
## 向下转型 {#downcasting}
|
||||
|
||||
某类型的一个常量或变量可能在幕后实际上属于一个子类。当确定是这种情况时,你可以尝试向下转到它的子类型,用类型转换操作符(`as?` 或 `as!`)。
|
||||
某类型的一个常量或变量可能在幕后实际上属于一个子类。当确定是这种情况时,你可以尝试用*类型转换操作符*(`as?` 或 `as!`)向下转到它的子类型。
|
||||
|
||||
因为向下转型可能会失败,类型转型操作符带有两种不同形式。条件形式(conditional form)`as?` 返回一个你试图向下转成的类型的可选值(optional value)。强制形式 `as!` 把试图向下转型和强制解包(force-unwraps)转换结果结合为一个操作。
|
||||
因为向下转型可能会失败,类型转型操作符带有两种不同形式。条件形式 `as?` 返回一个你试图向下转成的类型的可选值。强制形式 `as!` 把试图向下转型和强制解包转换结果结合为一个操作。
|
||||
|
||||
当你不确定向下转型可以成功时,用类型转换的条件形式(`as?`)。条件形式的类型转换总是返回一个可选值(optional value),并且若下转是不可能的,可选值将是 `nil`。这使你能够检查向下转型是否成功。
|
||||
当你不确定向下转型可以成功时,用类型转换的条件形式(`as?`)。条件形式的类型转换总是返回一个可选值,并且若下转是不可能的,可选值将是 `nil`。这使你能够检查向下转型是否成功。
|
||||
|
||||
只有你可以确定向下转型一定会成功时,才使用强制形式(`as!`)。当你试图向下转型为一个不正确的类型时,强制形式的类型转换会触发一个运行时错误。
|
||||
|
||||
@ -123,17 +101,17 @@ print("Media library contains \(movieCount) movies and \(songCount) songs")
|
||||
```swift
|
||||
for item in library {
|
||||
if let movie = item as? Movie {
|
||||
print("Movie: '\(movie.name)', dir. \(movie.director)")
|
||||
print("Movie: \(movie.name), dir. \(movie.director)")
|
||||
} else if let song = item as? Song {
|
||||
print("Song: '\(song.name)', by \(song.artist)")
|
||||
print("Song: \(song.name), by \(song.artist)")
|
||||
}
|
||||
}
|
||||
|
||||
// Movie: 'Casablanca', dir. Michael Curtiz
|
||||
// Song: 'Blue Suede Shoes', by Elvis Presley
|
||||
// Movie: 'Citizen Kane', dir. Orson Welles
|
||||
// Song: 'The One And Only', by Chesney Hawkes
|
||||
// Song: 'Never Gonna Give You Up', by Rick Astley
|
||||
// Movie: Casablanca, dir. Michael Curtiz
|
||||
// Song: Blue Suede Shoes, by Elvis Presley
|
||||
// Movie: Citizen Kane, dir. Orson Welles
|
||||
// Song: The One And Only, by Chesney Hawkes
|
||||
// Song: Never Gonna Give You Up, by Rick Astley
|
||||
```
|
||||
|
||||
示例首先试图将 `item` 下转为 `Movie`。因为 `item` 是一个 `MediaItem`
|
||||
@ -147,70 +125,23 @@ for item in library {
|
||||
|
||||
若向下转型成功,然后 `movie` 的属性将用于打印一个 `Movie` 实例的描述,包括它的导演的名字 `director`。相似的原理被用来检测 `Song` 实例,当 `Song` 被找到时则打印它的描述(包含 `artist` 的名字)。
|
||||
|
||||
> 注意
|
||||
> 注意
|
||||
>
|
||||
> 转换没有真的改变实例或它的值。根本的实例保持不变;只是简单地把它作为它被转换成的类型来使用。
|
||||
|
||||
<a name="type_casting_for_any_and_anyobject"></a>
|
||||
## `Any` 和 `AnyObject` 的类型转换
|
||||
## `Any` 和 `AnyObject` 的类型转换 {#type-casting-for-any-and-anyobject}
|
||||
|
||||
Swift 为不确定类型提供了两种特殊的类型别名:
|
||||
|
||||
* `AnyObject` 可以表示任何类类型的实例。
|
||||
* `Any` 可以表示任何类型,包括函数类型。
|
||||
* `AnyObject` 可以表示任何类类型的实例。
|
||||
|
||||
> 注意
|
||||
> 只有当你确实需要它们的行为和功能时才使用 `Any` 和 `AnyObject`。在你的代码里使用你期望的明确类型总是更好的。
|
||||
|
||||
<a name="anyobject"></a>
|
||||
### `AnyObject` 类型
|
||||
|
||||
当在工作中使用 Cocoa APIs 时,我们经常会接收到一个 `[AnyObject]` 类型的数组,或者说“一个任意类型对象的数组”。这是因为 Objective-C 没有明确的类型化数组。但是,你常常可以从 API 提供的信息来确定数组中对象的类型。
|
||||
|
||||
> 译者注
|
||||
> 这段文档似乎没有及时更新,从 Xcode 7 和 Swift 2.0 开始,由于 Objective-C 引入了轻量泛型,集合类型已经可以类型化了,在 Swift 中使用 Cocoa API 也越来越少遇到 `AnyObject` 类型了。详情请参阅 [Lightweight Generics](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithObjective-CAPIs.html#//apple_ref/doc/uid/TP40014216-CH4-ID173) 和 [Collection Classes](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html#//apple_ref/doc/uid/TP40014216-CH6-ID69)。
|
||||
|
||||
在这些情况下,你可以使用强制形式的类型转换(`as`)来下转数组中的每一项到比 `AnyObject` 更明确的类型,不需要可选解包(optional unwrapping)。
|
||||
|
||||
下面的示例定义了一个 `[AnyObject]` 类型的数组并填入三个 `Movie` 类型的实例:
|
||||
|
||||
```swift
|
||||
let someObjects: [AnyObject] = [
|
||||
Movie(name: "2001: A Space Odyssey", director: "Stanley Kubrick"),
|
||||
Movie(name: "Moon", director: "Duncan Jones"),
|
||||
Movie(name: "Alien", director: "Ridley Scott")
|
||||
]
|
||||
```
|
||||
|
||||
因为知道这个数组只包含 `Movie` 实例,你可以直接用(`as!`)下转并解包到非可选的 `Movie` 类型:
|
||||
|
||||
```swift
|
||||
for object in someObjects {
|
||||
let movie = object as! Movie
|
||||
print("Movie: '\(movie.name)', dir. \(movie.director)")
|
||||
}
|
||||
// Movie: '2001: A Space Odyssey', dir. Stanley Kubrick
|
||||
// Movie: 'Moon', dir. Duncan Jones
|
||||
// Movie: 'Alien', dir. Ridley Scott
|
||||
```
|
||||
|
||||
为了变为一个更简短的形式,下转 `someObjects` 数组为 `[Movie]` 类型而不是下转数组中的每一项:
|
||||
|
||||
```swift
|
||||
for movie in someObjects as! [Movie] {
|
||||
print("Movie: '\(movie.name)', dir. \(movie.director)")
|
||||
}
|
||||
// Movie: '2001: A Space Odyssey', dir. Stanley Kubrick
|
||||
// Movie: 'Moon', dir. Duncan Jones
|
||||
// Movie: 'Alien', dir. Ridley Scott
|
||||
```
|
||||
|
||||
<a name="any"></a>
|
||||
### `Any` 类型
|
||||
只有当你确实需要它们的行为和功能时才使用 `Any` 和 `AnyObject`。最好还是在代码中指明需要使用的类型。
|
||||
|
||||
这里有个示例,使用 `Any` 类型来和混合的不同类型一起工作,包括函数类型和非类类型。它创建了一个可以存储 `Any` 类型的数组 `things`:
|
||||
|
||||
```swift
|
||||
var things = [Any]()
|
||||
var things: [Any] = []
|
||||
|
||||
things.append(0)
|
||||
things.append(0.0)
|
||||
@ -222,7 +153,7 @@ things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
|
||||
things.append({ (name: String) -> String in "Hello, \(name)" })
|
||||
```
|
||||
|
||||
`things` 数组包含两个 `Int` 值,两个 `Double` 值,一个 `String` 值,一个元组 `(Double, Double)`,一个`Movie`实例“Ghostbusters”,以及一个接受 `String` 值并返回另一个 `String` 值的闭包表达式。
|
||||
`things` 数组包含两个 `Int` 值,两个 `Double` 值,一个 `String` 值,一个元组 `(Double, Double)`,一个 `Movie` 实例“Ghostbusters”,以及一个接受 `String` 值并返回另一个 `String` 值的闭包表达式。
|
||||
|
||||
你可以在 `switch` 表达式的 `case` 中使用 `is` 和 `as` 操作符来找出只知道是 `Any` 或 `AnyObject` 类型的常量或变量的具体类型。下面的示例迭代 `things` 数组中的每一项,并用 `switch` 语句查找每一项的类型。有几个 `switch` 语句的 `case` 绑定它们匹配到的值到一个指定类型的常量,从而可以打印这些值:
|
||||
|
||||
@ -244,8 +175,8 @@ for thing in things {
|
||||
case let (x, y) as (Double, Double):
|
||||
print("an (x, y) point at \(x), \(y)")
|
||||
case let movie as Movie:
|
||||
print("a movie called '\(movie.name)', dir. \(movie.director)")
|
||||
case let stringConverter as String -> String:
|
||||
print("a movie called \(movie.name), dir. \(movie.director)")
|
||||
case let stringConverter as (String) -> String:
|
||||
print(stringConverter("Michael"))
|
||||
default:
|
||||
print("something else")
|
||||
@ -258,6 +189,17 @@ for thing in things {
|
||||
// a positive double value of 3.14159
|
||||
// a string value of "hello"
|
||||
// an (x, y) point at 3.0, 5.0
|
||||
// a movie called 'Ghostbusters', dir. Ivan Reitman
|
||||
// a movie called Ghostbusters, dir. Ivan Reitman
|
||||
// Hello, Michael
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> `Any` 类型可以表示所有类型的值,包括可选类型。Swift 会在你用 `Any` 类型来表示一个可选值的时候,给你一个警告。如果你确实想使用 `Any` 类型来承载可选值,你可以使用 `as` 操作符显式转换为 `Any`,如下所示:
|
||||
|
||||
```swift
|
||||
let optionalNumber: Int? = 3
|
||||
things.append(optionalNumber) // 警告
|
||||
things.append(optionalNumber as Any) // 没有警告
|
||||
```
|
||||
|
||||
85
source/02_language_guide/19_Nested_Types.md
Executable file
85
source/02_language_guide/19_Nested_Types.md
Executable file
@ -0,0 +1,85 @@
|
||||
# 嵌套类型
|
||||
|
||||
枚举常被用于为特定类或结构体实现某些功能。类似地,枚举可以方便的定义工具类或结构体,从而为某个复杂的类型所使用。为了实现这种功能,Swift 允许你定义*嵌套类型*,可以在支持的类型中定义嵌套的枚举、类和结构体。
|
||||
|
||||
要在一个类型中嵌套另一个类型,将嵌套类型的定义写在其外部类型的 `{}` 内,而且可以根据需要定义多级嵌套。
|
||||
|
||||
## 嵌套类型实践 {#nested-types-in-action}
|
||||
|
||||
下面这个例子定义了一个结构体 `BlackjackCard`(二十一点),用来模拟 `BlackjackCard` 中的扑克牌点数。`BlackjackCard` 结构体包含两个嵌套定义的枚举类型 `Suit` 和 `Rank`。
|
||||
|
||||
在 `BlackjackCard` 中,`Ace` 牌可以表示 `1` 或者 `11`,`Ace` 牌的这一特征通过一个嵌套在 `Rank` 枚举中的结构体 `Values` 来表示:
|
||||
|
||||
```swift
|
||||
struct BlackjackCard {
|
||||
|
||||
// 嵌套的 Suit 枚举
|
||||
enum Suit: Character {
|
||||
case spades = "♠", hearts = "♡", diamonds = "♢", clubs = "♣"
|
||||
}
|
||||
|
||||
// 嵌套的 Rank 枚举
|
||||
enum Rank: Int {
|
||||
case two = 2, three, four, five, six, seven, eight, nine, ten
|
||||
case jack, queen, king, ace
|
||||
struct Values {
|
||||
let first: Int, second: Int?
|
||||
}
|
||||
var values: Values {
|
||||
switch self {
|
||||
case .ace:
|
||||
return Values(first: 1, second: 11)
|
||||
case .jack, .queen, .king:
|
||||
return Values(first: 10, second: nil)
|
||||
default:
|
||||
return Values(first: self.rawValue, second: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BlackjackCard 的属性和方法
|
||||
let rank: Rank, suit: Suit
|
||||
var description: String {
|
||||
var output = "suit is \(suit.rawValue),"
|
||||
output += " value is \(rank.values.first)"
|
||||
if let second = rank.values.second {
|
||||
output += " or \(second)"
|
||||
}
|
||||
return output
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Suit` 枚举用来描述扑克牌的四种花色,并用一个 `Character` 类型的原始值表示花色符号。
|
||||
|
||||
`Rank` 枚举用来描述扑克牌从 `Ace`~`10`,以及 `J`、`Q`、`K`,这 `13` 种牌,并用一个 `Int` 类型的原始值表示牌的面值。(这个 `Int` 类型的原始值未用于 `Ace`、`J`、`Q`、`K` 这 `4` 种牌。)
|
||||
|
||||
如上所述,`Rank` 枚举在内部定义了一个嵌套结构体 `Values`。结构体 `Values` 中定义了两个属性,用于反映只有 `Ace` 有两个数值,其余牌都只有一个数值:
|
||||
|
||||
- `first` 的类型为 `Int`
|
||||
- `second` 的类型为 `Int?`,或者说“可选 `Int`”
|
||||
|
||||
`Rank` 还定义了一个计算型属性 `values`,它将会返回一个 `Values` 结构体的实例。这个计算型属性会根据牌的面值,用适当的数值去初始化 `Values` 实例。对于 `J`、`Q`、`K`、`Ace` 这四种牌,会使用特殊数值。对于数字面值的牌,使用枚举实例的 `Int` 类型的原始值。
|
||||
|
||||
`BlackjackCard` 结构体拥有两个属性——`rank` 与 `suit`。它也同样定义了一个计算型属性 `description`,`description` 属性用 `rank` 和 `suit` 中的内容来构建对扑克牌名字和数值的描述。该属性使用可选绑定来检查可选类型 `second` 是否有值,若有值,则在原有的描述中增加对 `second` 的描述。
|
||||
|
||||
因为 `BlackjackCard` 是一个没有自定义构造器的结构体,在 [结构体的逐一成员构造器](./14_Initialization.md#memberwise-initializers-for-structure-types) 中可知,结构体有默认的成员构造器,所以你可以用默认的构造器去初始化新常量 `theAceOfSpades`:
|
||||
|
||||
```swift
|
||||
let theAceOfSpades = BlackjackCard(rank: .ace, suit: .spades)
|
||||
print("theAceOfSpades: \(theAceOfSpades.description)")
|
||||
// 打印“theAceOfSpades: suit is ♠, value is 1 or 11”
|
||||
```
|
||||
|
||||
尽管 `Rank` 和 `Suit` 嵌套在 `BlackjackCard` 中,但它们的类型仍可从上下文中推断出来,所以在初始化实例时能够单独通过成员名称(`.ace` 和 `.spades`)引用枚举实例。在上面的例子中,`description` 属性正确地反映了黑桃 A 牌具有 `1` 和 `11` 两个值。
|
||||
|
||||
## 引用嵌套类型 {#referring-to-nested-types}
|
||||
|
||||
在外部引用嵌套类型时,在嵌套类型的类型名前加上其外部类型的类型名作为前缀:
|
||||
|
||||
```swift
|
||||
let heartsSymbol = BlackjackCard.Suit.hearts.rawValue
|
||||
// 红心符号为“♡”
|
||||
```
|
||||
|
||||
对于上面这个例子,这样可以使 `Suit`、`Rank` 和 `Values` 的名字尽可能的短,因为它们的名字可以由定义它们的上下文来限定。
|
||||
271
source/02_language_guide/20_Extensions.md
Normal file
271
source/02_language_guide/20_Extensions.md
Normal file
@ -0,0 +1,271 @@
|
||||
# 扩展
|
||||
|
||||
*扩展*可以给一个现有的类,结构体,枚举,还有协议添加新的功能。它还拥有不需要访问被扩展类型源代码就能完成扩展的能力(即*逆向建模*)。扩展和 Objective-C 的分类很相似。(与 Objective-C 分类不同的是,Swift 扩展是没有名字的。)
|
||||
|
||||
Swift 中的扩展可以:
|
||||
|
||||
- 添加计算型实例属性和计算型类属性
|
||||
- 定义实例方法和类方法
|
||||
- 提供新的构造器
|
||||
- 定义下标
|
||||
- 定义和使用新的嵌套类型
|
||||
- 使已经存在的类型遵循(conform)一个协议
|
||||
|
||||
在 Swift 中,你甚至可以扩展协议以提供其需要的实现,或者添加额外功能给遵循的类型所使用。你可以从 [协议扩展](https://docs.swift.org/swift-book/LanguageGuide/Protocols.html#ID521) 获取更多细节。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 扩展可以给一个类型添加新的功能,但是不能重写已经存在的功能。
|
||||
|
||||
## 扩展的语法 {#extension-syntax}
|
||||
|
||||
使用 `extension` 关键字声明扩展:
|
||||
|
||||
```swift
|
||||
extension SomeType {
|
||||
// 在这里给 SomeType 添加新的功能
|
||||
}
|
||||
```
|
||||
|
||||
扩展可以扩充一个现有的类型,给它添加一个或多个协议。协议名称的写法和类或者结构体一样:
|
||||
|
||||
```swift
|
||||
extension SomeType: SomeProtocol, AnotherProtocol {
|
||||
// 协议所需要的实现写在这里
|
||||
}
|
||||
```
|
||||
|
||||
这种遵循协议的方式在 [使用扩展遵循协议](https://docs.swift.org/swift-book/LanguageGuide/Protocols.html#ID277) 中有描述。
|
||||
|
||||
扩展可以使用在现有范型类型上,就像 [扩展范型类型](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID185) 中描述的一样。你还可以使用扩展给泛型类型有条件的添加功能,就像 [扩展一个带有 Where 字句的范型](https://docs.swift.org/swift-book/LanguageGuide/Generics.html#ID553) 中描述的一样。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 对一个现有的类型,如果你定义了一个扩展来添加新的功能,那么这个类型的所有实例都可以使用这个新功能,包括那些在扩展定义之前就存在的实例。
|
||||
|
||||
## 计算型属性 {#computed-properties}
|
||||
|
||||
扩展可以给现有类型添加计算型实例属性和计算型类属性。这个例子给 Swift 内建的 `Double` 类型添加了五个计算型实例属性,从而提供与距离单位相关工作的基本支持:
|
||||
|
||||
```swift
|
||||
extension Double {
|
||||
var km: Double { return self * 1_000.0 }
|
||||
var m: Double { return self }
|
||||
var cm: Double { return self / 100.0 }
|
||||
var mm: Double { return self / 1_000.0 }
|
||||
var ft: Double { return self / 3.28084 }
|
||||
}
|
||||
let oneInch = 25.4.mm
|
||||
print("One inch is \(oneInch) meters")
|
||||
// 打印“One inch is 0.0254 meters”
|
||||
let threeFeet = 3.ft
|
||||
print("Three feet is \(threeFeet) meters")
|
||||
// 打印“Three feet is 0.914399970739201 meters”
|
||||
```
|
||||
|
||||
这些计算型属性表示的含义是把一个 `Double` 值看作是某单位下的长度值。即使它们被实现为计算型属性,但这些属性的名字仍可紧接一个浮点型字面值,从而通过点语法来使用,并以此实现距离转换。
|
||||
|
||||
在上述例子中,`Double` 类型的 `1.0` 代表的是“一米”。这就是为什么计算型属性 `m` 返回的是 `self`——表达式 `1.m` 被认为是计算一个 `Double` 类型的 `1.0`。
|
||||
|
||||
其它单位则需要一些单位换算。一千米等于 1,000 米,所以计算型属性 `km` 要把值乘以 `1_000.00` 来实现千米到米的单位换算。类似地,一米有 3.28084 英尺,所以计算型属性 `ft` 要把对应的 `Double` 值除以 `3.28084`,来实现英尺到米的单位换算。
|
||||
|
||||
这些属性都是只读的计算型属性,所以为了简便,它们的表达式里面都不包含 `get` 关键字。它们使用 `Double` 作为返回值类型,并可用于所有接受 `Double` 类型的数学计算中:
|
||||
|
||||
```swift
|
||||
let aMarathon = 42.km + 195.m
|
||||
print("A marathon is \(aMarathon) meters long")
|
||||
// 打印“A marathon is 42195.0 meters long”
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 扩展可以添加新的计算属性,但是它们不能添加存储属性,或向现有的属性添加属性观察者。
|
||||
|
||||
## 构造器 {#initializers}
|
||||
|
||||
扩展可以给现有的类型添加新的构造器。它使你可以把自定义类型作为参数来供其他类型的构造器使用,或者在类型的原始实现上添加额外的构造选项。
|
||||
|
||||
扩展可以给一个类添加新的便利构造器,但是它们不能给类添加新的指定构造器或者析构器。指定构造器和析构器必须始终由类的原始实现提供。
|
||||
|
||||
如果你使用扩展给一个值类型添加构造器,而这个值类型已经为所有存储属性提供默认值,且没有定义任何自定义构造器,那么你可以在该值类型扩展的构造器中使用默认构造器和成员构造器。如果你已经将构造器写在值类型的原始实现中,则不适用于这种情况,如同 [值类型的构造器委托](https://docs.swift.org/swift-book/LanguageGuide/Initialization.html#ID215) 中所描述的那样。
|
||||
|
||||
如果你使用扩展给另一个模块中定义的结构体添加构造器,那么新的构造器直到定义模块中使用一个构造器之前,不能访问 `self`。
|
||||
|
||||
在下面的例子中,自定义了一个的 `Rect` 结构体用来表示一个几何矩形。这个例子中还定义了两个给予支持的结构体 `Size` 和 `Point`,它们都把属性的默认值设置为 `0.0`:
|
||||
|
||||
```swift
|
||||
struct Size {
|
||||
var width = 0.0, height = 0.0
|
||||
}
|
||||
struct Point {
|
||||
var x = 0.0, y = 0.0
|
||||
}
|
||||
struct Rect {
|
||||
var origin = Point()
|
||||
var size = Size()
|
||||
}
|
||||
```
|
||||
|
||||
因为 `Rect` 结构体给所有的属性都提供了默认值,所以它自动获得了一个默认构造器和一个成员构造器,就像 [默认构造器](https://docs.swift.org/swift-book/LanguageGuide/Initialization.html#ID213) 中描述的一样。这些构造器可以用来创建新的 `Rect` 实例:
|
||||
|
||||
```swift
|
||||
let defaultRect = Rect()
|
||||
let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0),
|
||||
size: Size(width: 5.0, height: 5.0))
|
||||
```
|
||||
|
||||
你可以通过扩展 `Rect` 结构体来提供一个允许指定 point 和 size 的构造器:
|
||||
|
||||
```swift
|
||||
extension Rect {
|
||||
init(center: Point, size: Size) {
|
||||
let originX = center.x - (size.width / 2)
|
||||
let originY = center.y - (size.height / 2)
|
||||
self.init(origin: Point(x: originX, y: originY), size: size)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这个新的构造器首先根据提供的 `center` 和 `size` 计算一个适当的原点。然后这个构造器调用结构体自带的成员构造器 `init(origin:size:)`,它会将新的 origin 和 size 值储存在适当的属性中:
|
||||
|
||||
```swift
|
||||
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
|
||||
size: Size(width: 3.0, height: 3.0))
|
||||
// centerRect 的 origin 是 (2.5, 2.5) 并且它的 size 是 (3.0, 3.0)
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果你通过扩展提供一个新的构造器,你有责任确保每个通过该构造器创建的实例都是初始化完整的。
|
||||
|
||||
## 方法 {#methods}
|
||||
|
||||
扩展可以给现有类型添加新的实例方法和类方法。在下面的例子中,给 `Int` 类型添加了一个新的实例方法叫做 `repetitions`:
|
||||
|
||||
```swift
|
||||
extension Int {
|
||||
func repetitions(task: () -> Void) {
|
||||
for _ in 0..<self {
|
||||
task()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`repetitions(task:)` 方法仅接收一个 `() -> Void` 类型的参数,它表示一个没有参数没有返回值的方法。
|
||||
|
||||
定义了这个扩展之后,你可以对任意整形数值调用 `repetitions(task:)` 方法,来执行对应次数的任务:
|
||||
|
||||
```swift
|
||||
3.repetitions {
|
||||
print("Hello!")
|
||||
}
|
||||
// Hello!
|
||||
// Hello!
|
||||
// Hello!
|
||||
```
|
||||
|
||||
### 可变实例方法 {#mutating-instance-methods}
|
||||
|
||||
通过扩展添加的实例方法同样也可以修改(或 *mutating(改变)*)实例本身。结构体和枚举的方法,若是可以修改 `self` 或者它自己的属性,则必须将这个实例方法标记为 `mutating`,就像是改变了方法的原始实现。
|
||||
|
||||
在下面的例子中,对 Swift 的 `Int` 类型添加了一个新的 mutating 方法,叫做 `square`,它将原始值求平方:
|
||||
|
||||
```swift
|
||||
extension Int {
|
||||
mutating func square() {
|
||||
self = self * self
|
||||
}
|
||||
}
|
||||
var someInt = 3
|
||||
someInt.square()
|
||||
// someInt 现在是 9
|
||||
```
|
||||
|
||||
## 下标 {#subscripts}
|
||||
|
||||
扩展可以给现有的类型添加新的下标。下面的例子中,对 Swift 的 `Int` 类型添加了一个整数类型的下标。下标 `[n]` 从数字右侧开始,返回小数点后的第 `n` 位:
|
||||
|
||||
- `123456789[0]` 返回 `9`
|
||||
- `123456789[1]` 返回 `8`
|
||||
|
||||
……以此类推:
|
||||
```swift
|
||||
extension Int {
|
||||
subscript(digitIndex: Int) -> Int {
|
||||
var decimalBase = 1
|
||||
for _ in 0..<digitIndex {
|
||||
decimalBase *= 10
|
||||
}
|
||||
return (self / decimalBase) % 10
|
||||
}
|
||||
}
|
||||
746381295[0]
|
||||
// 返回 5
|
||||
746381295[1]
|
||||
// 返回 9
|
||||
746381295[2]
|
||||
// 返回 2
|
||||
746381295[8]
|
||||
// 返回 7
|
||||
```
|
||||
|
||||
如果操作的 `Int` 值没有足够的位数满足所请求的下标,那么下标的现实将返回 `0`,将好像在数字的左边补上了 0:
|
||||
|
||||
```swift
|
||||
746381295[9]
|
||||
// 返回 0,就好像你进行了这个请求:
|
||||
0746381295[9]
|
||||
```
|
||||
|
||||
## 嵌套类型 {#nested-yypes}
|
||||
|
||||
扩展可以给现有的类,结构体,还有枚举添加新的嵌套类型:
|
||||
|
||||
```swift
|
||||
extension Int {
|
||||
enum Kind {
|
||||
case negative, zero, positive
|
||||
}
|
||||
var kind: Kind {
|
||||
switch self {
|
||||
case 0:
|
||||
return .zero
|
||||
case let x where x > 0:
|
||||
return .positive
|
||||
default:
|
||||
return .negative
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这个例子给 `Int` 添加了一个新的嵌套枚举。这个枚举叫做 `Kind`,表示特定整数所代表的数字类型。具体来说,它表示数字是负的、零的还是正的。
|
||||
|
||||
这个例子同样给 `Int` 添加了一个新的计算型实例属性,叫做 `kind`,它返回被操作整数所对应的 `Kind` 枚举 case 分支。
|
||||
|
||||
现在,任意 `Int` 的值都可以使用这个嵌套类型:
|
||||
|
||||
```swift
|
||||
func printIntegerKinds(_ numbers: [Int]) {
|
||||
for number in numbers {
|
||||
switch number.kind {
|
||||
case .negative:
|
||||
print("- ", terminator: "")
|
||||
case .zero:
|
||||
print("0 ", terminator: "")
|
||||
case .positive:
|
||||
print("+ ", terminator: "")
|
||||
}
|
||||
}
|
||||
print("")
|
||||
}
|
||||
printIntegerKinds([3, 19, -27, 0, -6, 0, 7])
|
||||
// 打印“+ + - 0 - 0 + ”
|
||||
```
|
||||
|
||||
方法 `printIntegerKinds(_:)`,使用一个 `Int` 类型的数组作为输入,然后依次迭代这些值。对于数组中的每一个整数,方法会检查它的 `kind` 计算型属性,然后打印适当的描述。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> `number.kind` 已经被认为是 `Int.Kind` 类型。所以,在 `switch` 语句中所有的 `Int.Kind` case 分支可以被缩写,就像使用 `.negative` 替代 `Int.Kind.negative.`。
|
||||
979
source/02_language_guide/21_Protocols.md
Normal file
979
source/02_language_guide/21_Protocols.md
Normal file
@ -0,0 +1,979 @@
|
||||
# 协议
|
||||
|
||||
*协议* 定义了一个蓝图,规定了用来实现某一特定任务或者功能的方法、属性,以及其他需要的东西。类、结构体或枚举都可以遵循协议,并为协议定义的这些要求提供具体实现。某个类型能够满足某个协议的要求,就可以说该类型*遵循*这个协议。
|
||||
|
||||
除了遵循协议的类型必须实现的要求外,还可以对协议进行扩展,通过扩展来实现一部分要求或者实现一些附加功能,这样遵循协议的类型就能够使用这些功能。
|
||||
|
||||
## 协议语法 {#protocol-syntax}
|
||||
|
||||
协议的定义方式与类、结构体和枚举的定义非常相似:
|
||||
|
||||
```swift
|
||||
protocol SomeProtocol {
|
||||
// 这里是协议的定义部分
|
||||
}
|
||||
```
|
||||
|
||||
要让自定义类型遵循某个协议,在定义类型时,需要在类型名称后加上协议名称,中间以冒号(`:`)分隔。遵循多个协议时,各协议之间用逗号(`,`)分隔:
|
||||
|
||||
```swift
|
||||
struct SomeStructure: FirstProtocol, AnotherProtocol {
|
||||
// 这里是结构体的定义部分
|
||||
}
|
||||
```
|
||||
|
||||
若是一个类拥有父类,应该将父类名放在遵循的协议名之前,以逗号分隔:
|
||||
|
||||
```swift
|
||||
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
|
||||
// 这里是类的定义部分
|
||||
}
|
||||
```
|
||||
|
||||
## 属性要求 {#property-requirements}
|
||||
|
||||
协议可以要求遵循协议的类型提供特定名称和类型的实例属性或类型属性。协议不指定属性是存储属性还是计算属性,它只指定属性的名称和类型。此外,协议还指定属性是*可读*的还是*可读可写的*。
|
||||
|
||||
如果协议要求属性是可读可写的,那么该属性不能是常量属性或只读的计算型属性。如果协议只要求属性是可读的,那么该属性不仅可以是可读的,如果代码需要的话,还可以是可写的。
|
||||
|
||||
协议总是用 `var` 关键字来声明变量属性,在类型声明后加上 `{ set get }` 来表示属性是*可读可写*的,*可读*属性则用 `{ get }` 来表示:
|
||||
|
||||
```swift
|
||||
protocol SomeProtocol {
|
||||
var mustBeSettable: Int { get set }
|
||||
var doesNotNeedToBeSettable: Int { get }
|
||||
}
|
||||
```
|
||||
|
||||
在协议中定义类型属性时,总是使用 `static` 关键字作为前缀。当类类型遵循协议时,除了 `static` 关键字,还可以使用 `class` 关键字来声明类型属性:
|
||||
|
||||
```swift
|
||||
protocol AnotherProtocol {
|
||||
static var someTypeProperty: Int { get set }
|
||||
}
|
||||
```
|
||||
|
||||
如下所示,这是一个只含有一个实例属性要求的协议:
|
||||
|
||||
```swift
|
||||
protocol FullyNamed {
|
||||
var fullName: String { get }
|
||||
}
|
||||
```
|
||||
|
||||
`FullyNamed` 协议除了要求遵循协议的类型提供 `fullName` 属性外,并没有其他特别的要求。这个协议表示,任何遵循 `FullyNamed` 的类型,都必须有一个可读的 `String` 类型的实例属性 `fullName`。
|
||||
|
||||
下面是一个遵循 `FullyNamed` 协议的简单结构体:
|
||||
|
||||
```swift
|
||||
struct Person: FullyNamed {
|
||||
var fullName: String
|
||||
}
|
||||
let john = Person(fullName: "John Appleseed")
|
||||
// john.fullName 为 "John Appleseed"
|
||||
```
|
||||
|
||||
这个例子中定义了一个叫做 `Person` 的结构体,用来表示一个具有名字的人。从第一行代码可以看出,它遵循了 `FullyNamed` 协议。
|
||||
|
||||
`Person` 结构体的每一个实例都有一个 `String` 类型的存储型属性 `fullName`。这正好满足了 `FullyNamed` 协议的要求,也就意味着 `Person` 结构体正确地遵循了协议。(如果协议要求未被完全满足,在编译时会报错。)
|
||||
|
||||
下面是一个更为复杂的类,它采纳并遵循了 `FullyNamed` 协议:
|
||||
|
||||
```swift
|
||||
class Starship: FullyNamed {
|
||||
var prefix: String?
|
||||
var name: String
|
||||
init(name: String, prefix: String? = nil) {
|
||||
self.name = name
|
||||
self.prefix = prefix
|
||||
}
|
||||
var fullName: String {
|
||||
return (prefix != nil ? prefix! + " " : "") + name
|
||||
}
|
||||
}
|
||||
var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
|
||||
// ncc1701.fullName 为 "USS Enterprise"
|
||||
```
|
||||
|
||||
`Starship` 类把 `fullName` 作为只读的计算属性来实现。每一个 `Starship` 类的实例都有一个名为 `name` 的非可选属性和一个名为 `prefix` 的可选属性。 当 `prefix` 存在时,计算属性 `fullName` 会将 `prefix` 插入到 `name` 之前,从而得到一个带有 `prefix` 的 `fullName`。
|
||||
|
||||
## 方法要求 {#method-requirements}
|
||||
|
||||
协议可以要求遵循协议的类型实现某些指定的实例方法或类方法。这些方法作为协议的一部分,像普通方法一样放在协议的定义中,但是不需要大括号和方法体。可以在协议中定义具有可变参数的方法,和普通方法的定义方式相同。但是,不支持为协议中的方法提供默认参数。
|
||||
|
||||
正如属性要求中所述,在协议中定义类方法的时候,总是使用 `static` 关键字作为前缀。即使在类实现时,类方法要求使用 `class` 或 `static` 作为关键字前缀,前面的规则仍然适用:
|
||||
|
||||
```swift
|
||||
protocol SomeProtocol {
|
||||
static func someTypeMethod()
|
||||
}
|
||||
```
|
||||
|
||||
下面的例子定义了一个只含有一个实例方法的协议:
|
||||
|
||||
```swift
|
||||
protocol RandomNumberGenerator {
|
||||
func random() -> Double
|
||||
}
|
||||
```
|
||||
|
||||
`RandomNumberGenerator` 协议要求遵循协议的类型必须拥有一个名为 `random`, 返回值类型为 `Double` 的实例方法。尽管这里并未指明,但是我们假设返回值是从 `0.0` 到(但不包括)`1.0`。
|
||||
|
||||
`RandomNumberGenerator` 协议并不关心每一个随机数是怎样生成的,它只要求必须提供一个随机数生成器。
|
||||
|
||||
如下所示,下边是一个遵循并符合 `RandomNumberGenerator` 协议的类。该类实现了一个叫做 *线性同余生成器(linear congruential generator)* 的伪随机数算法。
|
||||
|
||||
```swift
|
||||
class LinearCongruentialGenerator: RandomNumberGenerator {
|
||||
var lastRandom = 42.0
|
||||
let m = 139968.0
|
||||
let a = 3877.0
|
||||
let c = 29573.0
|
||||
func random() -> Double {
|
||||
lastRandom = ((lastRandom * a + c).truncatingRemainder(dividingBy:m))
|
||||
return lastRandom / m
|
||||
}
|
||||
}
|
||||
let generator = LinearCongruentialGenerator()
|
||||
print("Here's a random number: \(generator.random())")
|
||||
// 打印 “Here's a random number: 0.37464991998171”
|
||||
print("And another one: \(generator.random())")
|
||||
// 打印 “And another one: 0.729023776863283”
|
||||
```
|
||||
|
||||
## 异变方法要求 {#mutating-method-requirements}
|
||||
|
||||
有时需要在方法中改变(或*异变*)方法所属的实例。例如,在值类型(即结构体和枚举)的实例方法中,将 `mutating` 关键字作为方法的前缀,写在 `func` 关键字之前,表示可以在该方法中修改它所属的实例以及实例的任意属性的值。这一过程在 [在实例方法中修改值类型](./11_Methods.md#modifying-value-types-from-within-instance-methods) 章节中有详细描述。
|
||||
|
||||
如果你在协议中定义了一个实例方法,该方法会改变遵循该协议的类型的实例,那么在定义协议时需要在方法前加 `mutating` 关键字。这使得结构体和枚举能够遵循此协议并满足此方法要求。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 实现协议中的 `mutating` 方法时,若是类类型,则不用写 `mutating` 关键字。而对于结构体和枚举,则必须写 `mutating` 关键字。
|
||||
|
||||
如下所示,`Togglable` 协议只定义了一个名为 `toggle` 的实例方法。顾名思义,`toggle()` 方法将改变实例属性,从而切换遵循该协议类型的实例的状态。
|
||||
|
||||
`toggle()` 方法在定义的时候,使用 `mutating` 关键字标记,这表明当它被调用时,该方法将会改变遵循协议的类型的实例:
|
||||
|
||||
```swift
|
||||
protocol Togglable {
|
||||
mutating func toggle()
|
||||
}
|
||||
```
|
||||
|
||||
当使用枚举或结构体来实现 `Togglable` 协议时,需要提供一个带有 `mutating` 前缀的 `toggle()` 方法。
|
||||
|
||||
下面定义了一个名为 `OnOffSwitch` 的枚举。这个枚举在两种状态之间进行切换,用枚举成员 `On` 和 `Off` 表示。枚举的 `toggle()` 方法被标记为 `mutating`,以满足 `Togglable` 协议的要求:
|
||||
|
||||
```swift
|
||||
enum OnOffSwitch: Togglable {
|
||||
case off, on
|
||||
mutating func toggle() {
|
||||
switch self {
|
||||
case .off:
|
||||
self = .on
|
||||
case .on:
|
||||
self = .off
|
||||
}
|
||||
}
|
||||
}
|
||||
var lightSwitch = OnOffSwitch.off
|
||||
lightSwitch.toggle()
|
||||
// lightSwitch 现在的值为 .on
|
||||
```
|
||||
|
||||
## 构造器要求 {#initializer-requirements}
|
||||
|
||||
协议可以要求遵循协议的类型实现指定的构造器。你可以像编写普通构造器那样,在协议的定义里写下构造器的声明,但不需要写花括号和构造器的实体:
|
||||
|
||||
```swift
|
||||
protocol SomeProtocol {
|
||||
init(someParameter: Int)
|
||||
}
|
||||
```
|
||||
|
||||
### 协议构造器要求的类实现 {#class-implementations-of-protocol-initializer-requirements}
|
||||
|
||||
你可以在遵循协议的类中实现构造器,无论是作为指定构造器,还是作为便利构造器。无论哪种情况,你都必须为构造器实现标上 `required` 修饰符:
|
||||
|
||||
```swift
|
||||
class SomeClass: SomeProtocol {
|
||||
required init(someParameter: Int) {
|
||||
// 这里是构造器的实现部分
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
使用 `required` 修饰符可以确保所有子类也必须提供此构造器实现,从而也能遵循协议。
|
||||
|
||||
关于 `required` 构造器的更多内容,请参考 [必要构造器](./14_Initialization.md#required-initializers)。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果类已经被标记为 `final`,那么不需要在协议构造器的实现中使用 `required` 修饰符,因为 `final` 类不能有子类。关于 `final` 修饰符的更多内容,请参见 [防止重写](./13_Inheritance.md#preventing-overrides)。
|
||||
|
||||
如果一个子类重写了父类的指定构造器,并且该构造器满足了某个协议的要求,那么该构造器的实现需要同时标注 `required` 和 `override` 修饰符:
|
||||
|
||||
```swift
|
||||
protocol SomeProtocol {
|
||||
init()
|
||||
}
|
||||
|
||||
class SomeSuperClass {
|
||||
init() {
|
||||
// 这里是构造器的实现部分
|
||||
}
|
||||
}
|
||||
|
||||
class SomeSubClass: SomeSuperClass, SomeProtocol {
|
||||
// 因为遵循协议,需要加上 required
|
||||
// 因为继承自父类,需要加上 override
|
||||
required override init() {
|
||||
// 这里是构造器的实现部分
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 可失败构造器要求 {#failable-initializer-requirements}
|
||||
|
||||
协议还可以为遵循协议的类型定义可失败构造器要求,详见 [可失败构造器](./14_Initialization.md#failable-initializers)。
|
||||
|
||||
遵循协议的类型可以通过可失败构造器(`init?`)或非可失败构造器(`init`)来满足协议中定义的可失败构造器要求。协议中定义的非可失败构造器要求可以通过非可失败构造器(`init`)或隐式解包可失败构造器(`init!`)来满足。
|
||||
|
||||
## 协议作为类型 {#protocols-as-types}
|
||||
|
||||
尽管协议本身并未实现任何功能,但是协议可以被当做一个功能完备的类型来使用。协议作为类型使用,有时被称作「存在类型」,这个名词来自「存在着一个类型 T,该类型遵循协议 T」。
|
||||
|
||||
协议可以像其他普通类型一样使用,使用场景如下:
|
||||
|
||||
* 作为函数、方法或构造器中的参数类型或返回值类型
|
||||
* 作为常量、变量或属性的类型
|
||||
* 作为数组、字典或其他容器中的元素类型
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 协议是一种类型,因此协议类型的名称应与其他类型(例如 `Int`,`Double`,`String`)的写法相同,使用大写字母开头的驼峰式写法,例如(`FullyNamed` 和 `RandomNumberGenerator`)。
|
||||
|
||||
下面是将协议作为类型使用的例子:
|
||||
|
||||
```swift
|
||||
class Dice {
|
||||
let sides: Int
|
||||
let generator: RandomNumberGenerator
|
||||
init(sides: Int, generator: RandomNumberGenerator) {
|
||||
self.sides = sides
|
||||
self.generator = generator
|
||||
}
|
||||
func roll() -> Int {
|
||||
return Int(generator.random() * Double(sides)) + 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
例子中定义了一个 `Dice` 类,用来代表桌游中拥有 N 个面的骰子。`Dice` 的实例含有 `sides` 和 `generator` 两个属性,前者是整型,用来表示骰子有几个面,后者为骰子提供一个随机数生成器,从而生成随机点数。
|
||||
|
||||
`generator` 属性的类型为 `RandomNumberGenerator`,因此任何遵循了 `RandomNumberGenerator` 协议的类型的实例都可以赋值给 `generator`,除此之外并无其他要求。并且由于其类型是 `RandomNumberGenerator`,在 `Dice` 类中与 `generator` 交互的代码,必须适用于所有 `generator` 实例都遵循的方法。这句话的意思是不能使用由 `generator` 底层类型提供的任何方法或属性。但是你可以通过向下转型,从协议类型转换成底层实现类型,比如从父类向下转型为子类。请参考 [向下转型](./18_Type_Casting.md#downcasting)。
|
||||
|
||||
`Dice` 类还有一个构造器,用来设置初始状态。构造器有一个名为 `generator`,类型为 `RandomNumberGenerator` 的形参。在调用构造方法创建 `Dice` 的实例时,可以传入任何遵循 `RandomNumberGenerator` 协议的实例给 `generator`。
|
||||
|
||||
`Dice` 类提供了一个名为 `roll` 的实例方法,用来模拟骰子的面值。它先调用 `generator` 的 `random()` 方法来生成一个 `[0.0,1.0)` 区间内的随机数,然后使用这个随机数生成正确的骰子面值。因为 `generator` 遵循了 `RandomNumberGenerator` 协议,可以确保它有个 `random()` 方法可供调用。
|
||||
|
||||
下面的例子展示了如何使用 `LinearCongruentialGenerator` 的实例作为随机数生成器来创建一个六面骰子:
|
||||
|
||||
```swift
|
||||
var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
|
||||
for _ in 1...5 {
|
||||
print("Random dice roll is \(d6.roll())")
|
||||
}
|
||||
// Random dice roll is 3
|
||||
// Random dice roll is 5
|
||||
// Random dice roll is 4
|
||||
// Random dice roll is 5
|
||||
// Random dice roll is 4
|
||||
```
|
||||
|
||||
## 委托 {#delegation}
|
||||
|
||||
*委托*是一种设计模式,它允许类或结构体将一些需要它们负责的功能委托给其他类型的实例。委托模式的实现很简单:定义协议来封装那些需要被委托的功能,这样就能确保遵循协议的类型能提供这些功能。委托模式可以用来响应特定的动作,或者接收外部数据源提供的数据,而无需关心外部数据源的类型。
|
||||
|
||||
下面的例子定义了两个基于骰子游戏的协议:
|
||||
|
||||
```swift
|
||||
protocol DiceGame {
|
||||
var dice: Dice { get }
|
||||
func play()
|
||||
}
|
||||
protocol DiceGameDelegate {
|
||||
func gameDidStart(_ game: DiceGame)
|
||||
func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
|
||||
func gameDidEnd(_ game: DiceGame)
|
||||
}
|
||||
```
|
||||
|
||||
`DiceGame` 协议可以被任意涉及骰子的游戏遵循。
|
||||
|
||||
`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` 协议,后者用来记录游戏的过程:
|
||||
|
||||
```swift
|
||||
class SnakesAndLadders: DiceGame {
|
||||
let finalSquare = 25
|
||||
let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
|
||||
var square = 0
|
||||
var board: [Int]
|
||||
init() {
|
||||
board = Array(repeating: 0, count: finalSquare + 1)
|
||||
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
|
||||
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
|
||||
}
|
||||
var delegate: DiceGameDelegate?
|
||||
func play() {
|
||||
square = 0
|
||||
delegate?.gameDidStart(self)
|
||||
gameLoop: while square != finalSquare {
|
||||
let diceRoll = dice.roll()
|
||||
delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)
|
||||
switch square + diceRoll {
|
||||
case finalSquare:
|
||||
break gameLoop
|
||||
case let newSquare where newSquare > finalSquare:
|
||||
continue gameLoop
|
||||
default:
|
||||
square += diceRoll
|
||||
square += board[square]
|
||||
}
|
||||
}
|
||||
delegate?.gameDidEnd(self)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
关于这个*蛇梯棋*游戏的详细描述请参阅 [中断(Break)](./05_Control_Flow.md#break)。
|
||||
|
||||
这个版本的游戏封装到了 `SnakesAndLadders` 类中,该类遵循了 `DiceGame` 协议,并且提供了相应的可读的 `dice` 属性和 `play()` 方法。( `dice` 属性在构造之后就不再改变,且协议只要求 `dice` 为可读的,因此将 `dice` 声明为常量属性。)
|
||||
|
||||
游戏使用 `SnakesAndLadders` 类的 `init()` 构造器来初始化游戏。所有的游戏逻辑被转移到了协议中的 `play()` 方法,`play()` 方法使用协议要求的 `dice` 属性提供骰子摇出的值。
|
||||
|
||||
注意,`delegate` 并不是游戏的必备条件,因此 `delegate` 被定义为 `DiceGameDelegate` 类型的可选属性。因为 `delegate` 是可选值,因此会被自动赋予初始值 `nil`。随后,可以在游戏中为 `delegate` 设置适当的值。因为 `DiceGameDelegate` 协议是类专属的,可以将 `delegate` 声明为 `weak`,从而避免循环引用。
|
||||
|
||||
`DicegameDelegate` 协议提供了三个方法用来追踪游戏过程。这三个方法被放置于游戏的逻辑中,即 `play()` 方法内。分别在游戏开始时,新一轮开始时,以及游戏结束时被调用。
|
||||
|
||||
因为 `delegate` 是一个 `DiceGameDelegate` 类型的可选属性,因此在 `play()` 方法中通过可选链式调用来调用它的方法。若 `delegate` 属性为 `nil`,则调用方法会优雅地失败,并不会产生错误。若 `delegate` 不为 `nil`,则方法能够被调用,并传递 `SnakesAndLadders` 实例作为参数。
|
||||
|
||||
如下示例定义了 `DiceGameTracker` 类,它遵循了 `DiceGameDelegate` 协议:
|
||||
|
||||
```swift
|
||||
class DiceGameTracker: DiceGameDelegate {
|
||||
var numberOfTurns = 0
|
||||
func gameDidStart(_ game: DiceGame) {
|
||||
numberOfTurns = 0
|
||||
if game is SnakesAndLadders {
|
||||
print("Started a new game of Snakes and Ladders")
|
||||
}
|
||||
print("The game is using a \(game.dice.sides)-sided dice")
|
||||
}
|
||||
func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
|
||||
numberOfTurns += 1
|
||||
print("Rolled a \(diceRoll)")
|
||||
}
|
||||
func gameDidEnd(_ game: DiceGame) {
|
||||
print("The game lasted for \(numberOfTurns) turns")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`DiceGameTracker` 实现了 `DiceGameDelegate` 协议要求的三个方法,用来记录游戏已经进行的轮数。当游戏开始时,`numberOfTurns` 属性被赋值为 `0`,然后在每新一轮中递增,游戏结束后,打印游戏的总轮数。
|
||||
|
||||
`gameDidStart(_:)` 方法从 `game` 参数获取游戏信息并打印。`game` 参数是 `DiceGame` 类型而不是 `SnakeAndLadders` 类型,所以在 `gameDidStart(_:)` 方法中只能访问 `DiceGame` 协议中的内容。当然了,`SnakeAndLadders` 的方法也可以在类型转换之后调用。在上例代码中,通过 `is` 操作符检查 `game` 是否为 `SnakesAndLadders` 类型的实例,如果是,则打印出相应的消息。
|
||||
|
||||
无论当前进行的是何种游戏,由于 `game` 遵循 `DiceGame` 协议,可以确保 `game` 含有 `dice` 属性。因此在 `gameDidStart(_:)` 方法中可以通过传入的 `game` 参数来访问 `dice` 属性,进而打印出 `dice` 的 `sides` 属性的值。
|
||||
|
||||
`DiceGameTracker` 的运行情况如下所示:
|
||||
|
||||
```swift
|
||||
let tracker = DiceGameTracker()
|
||||
let game = SnakesAndLadders()
|
||||
game.delegate = tracker
|
||||
game.play()
|
||||
// Started a new game of Snakes and Ladders
|
||||
// The game is using a 6-sided dice
|
||||
// Rolled a 3
|
||||
// Rolled a 5
|
||||
// Rolled a 4
|
||||
// Rolled a 5
|
||||
// The game lasted for 4 turns
|
||||
```
|
||||
|
||||
## 在扩展里添加协议遵循 {#adding-protocol-conformance-with-an-extension}
|
||||
|
||||
即便无法修改源代码,依然可以通过扩展令已有类型遵循并符合协议。扩展可以为已有类型添加属性、方法、下标以及构造器,因此可以符合协议中的相应要求。详情请在 [扩展](./20_Extensions.md) 章节中查看。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 通过扩展令已有类型遵循并符合协议时,该类型的所有实例也会随之获得协议中定义的各项功能。
|
||||
|
||||
例如下面这个 `TextRepresentable` 协议,任何想要通过文本表示一些内容的类型都可以实现该协议。这些想要表示的内容可以是实例本身的描述,也可以是实例当前状态的文本描述:
|
||||
|
||||
```swift
|
||||
protocol TextRepresentable {
|
||||
var textualDescription: String { get }
|
||||
}
|
||||
```
|
||||
|
||||
可以通过扩展,令先前提到的 `Dice` 类可以扩展来采纳和遵循 `TextRepresentable` 协议:
|
||||
|
||||
```swift
|
||||
extension Dice: TextRepresentable {
|
||||
var textualDescription: String {
|
||||
return "A \(sides)-sided dice"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
通过扩展遵循并采纳协议,和在原始定义中遵循并符合协议的效果完全相同。协议名称写在类型名之后,以冒号隔开,然后在扩展的大括号内实现协议要求的内容。
|
||||
|
||||
现在所有 `Dice` 的实例都可以看做 `TextRepresentable` 类型:
|
||||
|
||||
```swift
|
||||
let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
|
||||
print(d12.textualDescription)
|
||||
// 打印 “A 12-sided dice”
|
||||
```
|
||||
|
||||
同样,`SnakesAndLadders` 类也可以通过扩展来采纳和遵循 `TextRepresentable` 协议:
|
||||
|
||||
```swift
|
||||
extension SnakesAndLadders: TextRepresentable {
|
||||
var textualDescription: String {
|
||||
return "A game of Snakes and Ladders with \(finalSquare) squares"
|
||||
}
|
||||
}
|
||||
print(game.textualDescription)
|
||||
// 打印 “A game of Snakes and Ladders with 25 squares”
|
||||
```
|
||||
|
||||
## 有条件地遵循协议 {#Conditionally-Conforming-to-a-Protocol}
|
||||
|
||||
泛型类型可能只在某些情况下满足一个协议的要求,比如当类型的泛型形式参数遵循对应协议时。你可以通过在扩展类型时列出限制让泛型类型有条件地遵循某协议。在你采纳协议的名字后面写泛型 `where` 分句。更多关于泛型 `where` 分句,见 [泛型 Where 分句](./22_Generics.md##where-clauses)。
|
||||
|
||||
下面的扩展让 `Array` 类型只要在存储遵循 `TextRepresentable` 协议的元素时就遵循 `TextRepresentable` 协议。
|
||||
|
||||
```swift
|
||||
extension Array: TextRepresentable where Element: TextRepresentable {
|
||||
var textualDescription: String {
|
||||
let itemsAsText = self.map { $0.textualDescription }
|
||||
return "[" + itemsAsText.joined(separator: ", ") + "]"
|
||||
}
|
||||
}
|
||||
let myDice = [d6, d12]
|
||||
print(myDice.textualDescription)
|
||||
// 打印 "[A 6-sided dice, A 12-sided dice]"
|
||||
```
|
||||
|
||||
## 在扩展里声明采纳协议 {#declaring-protocol-adoption-with-an-extension}
|
||||
|
||||
当一个类型已经遵循了某个协议中的所有要求,却还没有声明采纳该协议时,可以通过空的扩展来让它采纳该协议:
|
||||
|
||||
```swift
|
||||
struct Hamster {
|
||||
var name: String
|
||||
var textualDescription: String {
|
||||
return "A hamster named \(name)"
|
||||
}
|
||||
}
|
||||
extension Hamster: TextRepresentable {}
|
||||
```
|
||||
|
||||
从现在起,`Hamster` 的实例可以作为 `TextRepresentable` 类型使用:
|
||||
|
||||
```swift
|
||||
let simonTheHamster = Hamster(name: "Simon")
|
||||
let somethingTextRepresentable: TextRepresentable = simonTheHamster
|
||||
print(somethingTextRepresentable.textualDescription)
|
||||
// 打印 “A hamster named Simon”
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 即使满足了协议的所有要求,类型也不会自动遵循协议,必须显式地遵循协议。
|
||||
|
||||
## 使用合成实现来采纳协议 {#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` 的数组:
|
||||
|
||||
```swift
|
||||
let things: [TextRepresentable] = [game, d12, simonTheHamster]
|
||||
```
|
||||
|
||||
如下所示,可以遍历 `things` 数组,并打印每个元素的文本表示:
|
||||
|
||||
```swift
|
||||
for thing in things {
|
||||
print(thing.textualDescription)
|
||||
}
|
||||
// A game of Snakes and Ladders with 25 squares
|
||||
// A 12-sided dice
|
||||
// A hamster named Simon
|
||||
```
|
||||
|
||||
注意 `thing` 常量是 `TextRepresentable` 类型而不是 `Dice`,`DiceGame`,`Hamster` 等类型,即使实例在幕后确实是这些类型中的一种。由于 `thing` 是 `TextRepresentable` 类型,任何 `TextRepresentable` 的实例都有一个 `textualDescription` 属性,所以在每次循环中可以安全地访问 `thing.textualDescription`。
|
||||
|
||||
## 协议的继承 {#protocol-inheritance}
|
||||
|
||||
协议能够*继承*一个或多个其他协议,可以在继承的协议的基础上增加新的要求。协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔:
|
||||
|
||||
```swift
|
||||
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
|
||||
// 这里是协议的定义部分
|
||||
}
|
||||
```
|
||||
|
||||
如下所示,`PrettyTextRepresentable` 协议继承了 `TextRepresentable` 协议:
|
||||
|
||||
```swift
|
||||
protocol PrettyTextRepresentable: TextRepresentable {
|
||||
var prettyTextualDescription: String { get }
|
||||
}
|
||||
```
|
||||
|
||||
例子中定义了一个新的协议 `PrettyTextRepresentable`,它继承自 `TextRepresentable` 协议。任何遵循 `PrettyTextRepresentable` 协议的类型在满足该协议的要求时,也必须满足 `TextRepresentable` 协议的要求。在这个例子中,`PrettyTextRepresentable` 协议额外要求遵循协议的类型提供一个返回值为 `String` 类型的 `prettyTextualDescription` 属性。
|
||||
|
||||
如下所示,扩展 `SnakesAndLadders`,使其遵循并符合 `PrettyTextRepresentable` 协议:
|
||||
|
||||
```swift
|
||||
extension SnakesAndLadders: PrettyTextRepresentable {
|
||||
var prettyTextualDescription: String {
|
||||
var output = textualDescription + ":\n"
|
||||
for index in 1...finalSquare {
|
||||
switch board[index] {
|
||||
case let ladder where ladder > 0:
|
||||
output += "▲ "
|
||||
case let snake where snake < 0:
|
||||
output += "▼ "
|
||||
default:
|
||||
output += "○ "
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上述扩展令 `SnakesAndLadders` 遵循了 `PrettyTextRepresentable` 协议,并提供了协议要求的 `prettyTextualDescription` 属性。每个 `PrettyTextRepresentable` 类型同时也是 `TextRepresentable` 类型,所以在 `prettyTextualDescription` 的实现中,可以访问 `textualDescription` 属性。然后,拼接上了冒号和换行符。接着,遍历数组中的元素,拼接一个几何图形来表示每个棋盘方格的内容:
|
||||
|
||||
* 当从数组中取出的元素的值大于 `0` 时,用 `▲` 表示。
|
||||
* 当从数组中取出的元素的值小于 `0` 时,用 `▼` 表示。
|
||||
* 当从数组中取出的元素的值等于 `0` 时,用 `○` 表示。
|
||||
|
||||
任意 `SankesAndLadders` 的实例都可以使用 `prettyTextualDescription` 属性来打印一个漂亮的文本描述:
|
||||
|
||||
```swift
|
||||
print(game.prettyTextualDescription)
|
||||
// A game of Snakes and Ladders with 25 squares:
|
||||
// ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○
|
||||
```
|
||||
|
||||
## 类专属的协议 {#class-only-protocol}
|
||||
|
||||
你通过添加 `AnyObject` 关键字到协议的继承列表,就可以限制协议只能被类类型采纳(以及非结构体或者非枚举的类型)。
|
||||
|
||||
```swift
|
||||
protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
|
||||
// 这里是类专属协议的定义部分
|
||||
}
|
||||
```
|
||||
|
||||
在以上例子中,协议 `SomeClassOnlyProtocol` 只能被类类型采纳。如果尝试让结构体或枚举类型采纳 `SomeClassOnlyProtocol`,则会导致编译时错误。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 当协议定义的要求需要遵循协议的类型必须是引用语义而非值语义时,应该采用类类型专属协议。关于引用语义和值语义的更多内容,请查看 [结构体和枚举是值类型](./09_Structures_And_Classes.md#structures-and-enumerations-are-value-types) 和 [类是引用类型](./09_Structures_And_Classes.md#classes-are-reference-types)。
|
||||
|
||||
## 协议合成 {#protocol-composition}
|
||||
|
||||
要求一个类型同时遵循多个协议是很有用的。你可以使用*协议组合*来复合多个协议到一个要求里。协议组合行为就和你定义的临时局部协议一样拥有构成中所有协议的需求。协议组合不定义任何新的协议类型。
|
||||
|
||||
协议组合使用 `SomeProtocol & AnotherProtocol` 的形式。你可以列举任意数量的协议,用和符号(`&`)分开。除了协议列表,协议组合也能包含类类型,这允许你标明一个需要的父类。
|
||||
|
||||
下面的例子中,将 `Named` 和 `Aged` 两个协议按照上述语法组合成一个协议,作为函数参数的类型:
|
||||
|
||||
```swift
|
||||
protocol Named {
|
||||
var name: String { get }
|
||||
}
|
||||
protocol Aged {
|
||||
var age: Int { get }
|
||||
}
|
||||
struct Person: Named, Aged {
|
||||
var name: String
|
||||
var age: Int
|
||||
}
|
||||
func wishHappyBirthday(to celebrator: Named & Aged) {
|
||||
print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
|
||||
}
|
||||
let birthdayPerson = Person(name: "Malcolm", age: 21)
|
||||
wishHappyBirthday(to: birthdayPerson)
|
||||
// 打印 “Happy birthday Malcolm - you're 21!”
|
||||
```
|
||||
|
||||
`Named` 协议包含 `String` 类型的 `name` 属性。`Aged` 协议包含 `Int` 类型的 `age` 属性。`Person` 结构体采纳了这两个协议。
|
||||
|
||||
`wishHappyBirthday(to:)` 函数的参数 `celebrator` 的类型为 `Named & Aged`, 这意味着“任何同时遵循 Named 和 Aged 的协议”。它不关心参数的具体类型,只要参数遵循这两个协议即可。
|
||||
|
||||
上面的例子创建了一个名为 `birthdayPerson` 的 `Person` 的实例,作为参数传递给了 `wishHappyBirthday(to:)` 函数。因为 `Person` 同时遵循这两个协议,所以这个参数合法,函数将打印生日问候语。
|
||||
|
||||
这里有一个例子:将 Location 类和前面的 Named 协议进行组合:
|
||||
|
||||
```swift
|
||||
class Location {
|
||||
var latitude: Double
|
||||
var longitude: Double
|
||||
init(latitude: Double, longitude: Double) {
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
}
|
||||
}
|
||||
class City: Location, Named {
|
||||
var name: String
|
||||
init(name: String, latitude: Double, longitude: Double) {
|
||||
self.name = name
|
||||
super.init(latitude: latitude, longitude: longitude)
|
||||
}
|
||||
}
|
||||
func beginConcert(in location: Location & Named) {
|
||||
print("Hello, \(location.name)!")
|
||||
}
|
||||
|
||||
let seattle = City(name: "Seattle", latitude: 47.6, longitude: -122.3)
|
||||
beginConcert(in: seattle)
|
||||
// 打印 "Hello, Seattle!"
|
||||
```
|
||||
|
||||
`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` 操作符来检查协议一致性,即是否遵循某协议,并且可以转换到指定的协议类型。检查和转换协议的语法与检查和转换类型是完全一样的:
|
||||
|
||||
* `is` 用来检查实例是否遵循某个协议,若遵循则返回 `true`,否则返回 `false`;
|
||||
* `as?` 返回一个可选值,当实例遵循某个协议时,返回类型为协议类型的可选值,否则返回 `nil`;
|
||||
* `as!` 将实例强制向下转换到某个协议类型,如果强转失败,将触发运行时错误。
|
||||
|
||||
下面的例子定义了一个 `HasArea` 协议,该协议定义了一个 `Double` 类型的可读属性 `area`:
|
||||
|
||||
```swift
|
||||
protocol HasArea {
|
||||
var area: Double { get }
|
||||
}
|
||||
```
|
||||
|
||||
如下所示,`Circle` 类和 `Country` 类都遵循了 `HasArea` 协议:
|
||||
|
||||
```swift
|
||||
class Circle: HasArea {
|
||||
let pi = 3.1415927
|
||||
var radius: Double
|
||||
var area: Double { return pi * radius * radius }
|
||||
init(radius: Double) { self.radius = radius }
|
||||
}
|
||||
class Country: HasArea {
|
||||
var area: Double
|
||||
init(area: Double) { self.area = area }
|
||||
}
|
||||
```
|
||||
|
||||
`Circle` 类把 `area` 属性实现为基于存储型属性 `radius` 的计算型属性。`Country` 类则把 `area` 属性实现为存储型属性。这两个类都正确地遵循了 `HasArea` 协议。
|
||||
|
||||
如下所示,`Animal` 是一个未遵循 `HasArea` 协议的类:
|
||||
|
||||
```swift
|
||||
class Animal {
|
||||
var legs: Int
|
||||
init(legs: Int) { self.legs = legs }
|
||||
}
|
||||
```
|
||||
|
||||
`Circle`,`Country`,`Animal` 并没有一个共同的基类,尽管如此,它们都是类,它们的实例都可以作为 `AnyObject` 类型的值,存储在同一个数组中:
|
||||
|
||||
```swift
|
||||
let objects: [AnyObject] = [
|
||||
Circle(radius: 2.0),
|
||||
Country(area: 243_610),
|
||||
Animal(legs: 4)
|
||||
]
|
||||
```
|
||||
|
||||
`objects` 数组使用字面量初始化,数组包含一个 `radius` 为 `2` 的 `Circle` 的实例,一个保存了英国国土面积的 `Country` 实例和一个 `legs` 为 `4` 的 `Animal` 实例。
|
||||
|
||||
如下所示,`objects` 数组可以被迭代,并对迭代出的每一个元素进行检查,看它是否遵循 `HasArea` 协议:
|
||||
|
||||
```swift
|
||||
for object in objects {
|
||||
if let objectWithArea = object as? HasArea {
|
||||
print("Area is \(objectWithArea.area)")
|
||||
} else {
|
||||
print("Something that doesn't have an area")
|
||||
}
|
||||
}
|
||||
// Area is 12.5663708
|
||||
// Area is 243610.0
|
||||
// Something that doesn't have an area
|
||||
```
|
||||
|
||||
当迭代出的元素遵循 `HasArea` 协议时,将 `as?` 操作符返回的可选值通过可选绑定,绑定到 `objectWithArea` 常量上。`objectWithArea` 是 `HasArea` 协议类型的实例,因此 `area` 属性可以被访问和打印。
|
||||
|
||||
`objects` 数组中的元素的类型并不会因为强转而丢失类型信息,它们仍然是 `Circle`,`Country`,`Animal` 类型。然而,当它们被赋值给 `objectWithArea` 常量时,只被视为 `HasArea` 类型,因此只有 `area` 属性能够被访问。
|
||||
|
||||
## 可选的协议要求 {#optional-protocol-requirements}
|
||||
|
||||
协议可以定义*可选要求*,遵循协议的类型可以选择是否实现这些要求。在协议中使用 `optional` 关键字作为前缀来定义可选要求。可选要求用在你需要和 Objective-C 打交道的代码中。协议和可选要求都必须带上 `@objc` 属性。标记 `@objc` 特性的协议只能被继承自 Objective-C 类的类或者 `@objc` 类遵循,其他类以及结构体和枚举均不能遵循这种协议。
|
||||
|
||||
使用可选要求时(例如,可选的方法或者属性),它们的类型会自动变成可选的。比如,一个类型为 `(Int) -> String` 的方法会变成 `((Int) -> String)?`。需要注意的是整个函数类型是可选的,而不是函数的返回值。
|
||||
|
||||
协议中的可选要求可通过可选链式调用来使用,因为遵循协议的类型可能没有实现这些可选要求。类似 `someOptionalMethod?(someArgument)` 这样,你可以在可选方法名称后加上 `?` 来调用可选方法。详细内容可在 [可选链式调用](./16_Optional_Chaining.md) 章节中查看。
|
||||
|
||||
下面的例子定义了一个名为 `Counter` 的用于整数计数的类,它使用外部的数据源来提供每次的增量。数据源由 `CounterDataSource` 协议定义,它包含两个可选要求:
|
||||
|
||||
```swift
|
||||
@objc protocol CounterDataSource {
|
||||
@objc optional func increment(forCount count: Int) -> Int
|
||||
@objc optional var fixedIncrement: Int { get }
|
||||
}
|
||||
```
|
||||
|
||||
`CounterDataSource` 协议定义了一个可选方法 `increment(forCount:)` 和一个可选属性 `fiexdIncrement`,它们使用了不同的方法来从数据源中获取适当的增量值。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 严格来讲,`CounterDataSource` 协议中的方法和属性都是可选的,因此遵循协议的类可以不实现这些要求,尽管技术上允许这样做,不过最好不要这样写。
|
||||
|
||||
`Counter` 类含有 `CounterDataSource?` 类型的可选属性 `dataSource`,如下所示:
|
||||
|
||||
```swift
|
||||
class Counter {
|
||||
var count = 0
|
||||
var dataSource: CounterDataSource?
|
||||
func increment() {
|
||||
if let amount = dataSource?.increment?(forCount: count) {
|
||||
count += amount
|
||||
} else if let amount = dataSource?.fixedIncrement {
|
||||
count += amount
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Counter` 类使用变量属性 `count` 来存储当前值。该类还定义了一个 `increment` 方法,每次调用该方法的时候,将会增加 `count` 的值。
|
||||
|
||||
`increment()` 方法首先试图使用 `increment(forCount:)` 方法来得到每次的增量。`increment()` 方法使用可选链式调用来尝试调用 `increment(forCount:)`,并将当前的 `count` 值作为参数传入。
|
||||
|
||||
这里使用了两层可选链式调用。首先,由于 `dataSource` 可能为 `nil`,因此在 `dataSource` 后边加上了 `?`,以此表明只在 `dataSource` 非空时才去调用 `increment(forCount:)` 方法。其次,即使 `dataSource` 存在,也无法保证其是否实现了 `increment(forCount:)` 方法,因为这个方法是可选的。因此,`increment(forCount:)` 方法同样使用可选链式调用进行调用,只有在该方法被实现的情况下才能调用它,所以在 `increment(forCount:)` 方法后边也加上了 `?`。
|
||||
|
||||
调用 `increment(forCount:)` 方法在上述两种情形下都有可能失败,所以返回值为 `Int?` 类型。虽然在 `CounterDataSource` 协议中,`increment(forCount:)` 的返回值类型是非可选 `Int`。另外,即使这里使用了两层可选链式调用,最后的返回结果依旧是单层的可选类型。关于这一点的更多信息,请查阅 [连接多层可选链式调用](./16_Optional_Chaining.md)。
|
||||
|
||||
在调用 `increment(forCount:)` 方法后,`Int?` 型的返回值通过可选绑定解包并赋值给常量 `amount`。如果可选值确实包含一个数值,也就是说,数据源和方法都存在,数据源方法返回了一个有效值。之后便将解包后的 `amount` 加到 `count` 上,增量操作完成。
|
||||
|
||||
如果没有从 `increment(forCount:)` 方法获取到值,可能由于 `dataSource` 为 `nil`,或者它并没有实现 `increment(forCount:)` 方法,那么 `increment()` 方法将试图从数据源的 `fixedIncrement` 属性中获取增量。`fixedIncrement` 是一个可选属性,因此属性值是一个 `Int?` 值,即使该属性在 `CounterDataSource` 协议中的类型是非可选的 `Int`。
|
||||
|
||||
下面的例子展示了 `CounterDataSource` 的简单实现。`ThreeSource` 类遵循了 `CounterDataSource` 协议,它实现了可选属性 `fixedIncrement`,每次会返回 `3`:
|
||||
|
||||
```swift
|
||||
class ThreeSource: NSObject, CounterDataSource {
|
||||
let fixedIncrement = 3
|
||||
}
|
||||
```
|
||||
|
||||
可以使用 `ThreeSource` 的实例作为 `Counter` 实例的数据源:
|
||||
|
||||
```swift
|
||||
var counter = Counter()
|
||||
counter.dataSource = ThreeSource()
|
||||
for _ in 1...4 {
|
||||
counter.increment()
|
||||
print(counter.count)
|
||||
}
|
||||
// 3
|
||||
// 6
|
||||
// 9
|
||||
// 12
|
||||
```
|
||||
|
||||
上述代码新建了一个 `Counter` 实例,并将它的数据源设置为一个 `ThreeSource` 的实例,然后调用 `increment()` 方法 `4` 次。按照预期预期一样,每次调用都会将 `count` 的值增加 `3`.
|
||||
|
||||
下面是一个更为复杂的数据源 `TowardsZeroSource`,它将使得最后的值变为 `0`:
|
||||
|
||||
```swift
|
||||
class TowardsZeroSource: NSObject, CounterDataSource {
|
||||
func increment(forCount count: Int) -> Int {
|
||||
if count == 0 {
|
||||
return 0
|
||||
} else if count < 0 {
|
||||
return 1
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`TowardsZeroSource` 实现了 `CounterDataSource` 协议中的 `increment(forCount:)` 方法,以 `count` 参数为依据,计算出每次的增量。如果 `count` 已经为 `0`,此方法将返回 `0`,以此表明之后不应再有增量操作发生。
|
||||
|
||||
你可以使用 `TowardsZeroSource` 实例将 `Counter` 实例来从 `-4` 增加到 `0`。一旦增加到 `0`,数值便不会再有变动:
|
||||
|
||||
```swift
|
||||
counter.count = -4
|
||||
counter.dataSource = TowardsZeroSource()
|
||||
for _ in 1...5 {
|
||||
counter.increment()
|
||||
print(counter.count)
|
||||
}
|
||||
// -3
|
||||
// -2
|
||||
// -1
|
||||
// 0
|
||||
// 0
|
||||
```
|
||||
|
||||
## 协议扩展 {#protocol-extensions}
|
||||
|
||||
协议可以通过扩展来为遵循协议的类型提供属性、方法以及下标的实现。通过这种方式,你可以基于协议本身来实现这些功能,而无需在每个遵循协议的类型中都重复同样的实现,也无需使用全局函数。
|
||||
|
||||
例如,可以扩展 `RandomNumberGenerator` 协议来提供 `randomBool()` 方法。该方法使用协议中定义的 `random()` 方法来返回一个随机的 `Bool` 值:
|
||||
|
||||
```swift
|
||||
extension RandomNumberGenerator {
|
||||
func randomBool() -> Bool {
|
||||
return random() > 0.5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
通过协议扩展,所有遵循协议的类型,都能自动获得这个扩展所增加的方法实现而无需任何额外修改:
|
||||
|
||||
```swift
|
||||
let generator = LinearCongruentialGenerator()
|
||||
print("Here's a random number: \(generator.random())")
|
||||
// 打印 “Here's a random number: 0.37464991998171”
|
||||
print("And here's a random Boolean: \(generator.randomBool())")
|
||||
// 打印 “And here's a random Boolean: true”
|
||||
```
|
||||
|
||||
协议扩展可以为遵循协议的类型增加实现,但不能声明该协议继承自另一个协议。协议的继承只能在协议声明处进行指定。
|
||||
|
||||
### 提供默认实现 {#providing-default-implementations}
|
||||
|
||||
可以通过协议扩展来为协议要求的方法、计算属性提供默认的实现。如果遵循协议的类型为这些要求提供了自己的实现,那么这些自定义实现将会替代扩展中的默认实现被使用。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 通过协议扩展为协议要求提供的默认实现和可选的协议要求不同。虽然在这两种情况下,遵循协议的类型都无需自己实现这些要求,但是通过扩展提供的默认实现可以直接调用,而无需使用可选链式调用。
|
||||
|
||||
例如,`PrettyTextRepresentable` 协议继承自 `TextRepresentable` 协议,可以为其提供一个默认的 `prettyTextualDescription` 属性来简单地返回 `textualDescription` 属性的值:
|
||||
|
||||
```swift
|
||||
extension PrettyTextRepresentable {
|
||||
var prettyTextualDescription: String {
|
||||
return textualDescription
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 为协议扩展添加限制条件 {#adding-constraints-to-protocol-extensions}
|
||||
|
||||
在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用 `where` 子句来描述,正如 [泛型 Where 子句](./22_Generics.md#where-clauses) 中所描述的。
|
||||
|
||||
例如,你可以扩展 `Collection` 协议,适用于集合中的元素遵循了 `Equatable` 协议的情况。通过限制集合元素遵循 `Equatable` 协议, 作为标准库的一部分, 你可以使用 `==` 和 `!=` 操作符来检查两个元素的等价性和非等价性。
|
||||
|
||||
```swift
|
||||
extension Collection where Element: Equatable {
|
||||
func allEqual() -> Bool {
|
||||
for element in self {
|
||||
if element != self.first {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
如果集合中的所有元素都一致,`allEqual()` 方法才返回 `true`。
|
||||
|
||||
看看两个整数数组,一个数组的所有元素都是一样的,另一个不一样:
|
||||
|
||||
```swift
|
||||
let equalNumbers = [100, 100, 100, 100, 100]
|
||||
let differentNumbers = [100, 100, 200, 100, 200]
|
||||
```
|
||||
|
||||
由于数组遵循 `Collection` 而且整数遵循 `Equatable`,`equalNumbers` 和 `differentNumbers` 都可以使用 `allEqual()` 方法。
|
||||
|
||||
```swift
|
||||
print(equalNumbers.allEqual())
|
||||
// 打印 "true"
|
||||
print(differentNumbers.allEqual())
|
||||
// 打印 "false"
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果一个遵循的类型满足了为同一方法或属性提供实现的多个限制型扩展的要求, Swift 会使用最匹配限制的实现。
|
||||
739
source/02_language_guide/22_Generics.md
Normal file
739
source/02_language_guide/22_Generics.md
Normal file
@ -0,0 +1,739 @@
|
||||
# 泛型
|
||||
|
||||
*泛型代码*让你能根据自定义的需求,编写出适用于任意类型的、灵活可复用的函数及类型。你可避免编写重复的代码,而是用一种清晰抽象的方式来表达代码的意图。
|
||||
|
||||
泛型是 Swift 最强大的特性之一,很多 Swift 标准库是基于泛型代码构建的。实际上,即使你没有意识到,你也一直在*语言指南*中使用泛型。例如,Swift 的 `Array` 和 `Dictionary` 都是泛型集合。你可以创建一个 `Int` 类型数组,也可创建一个 `String` 类型数组,甚至可以是任意其他 Swift 类型的数组。同样,你也可以创建一个存储任意指定类型的字典,并对该类型没有限制。
|
||||
|
||||
## 泛型解决的问题 {#the-problem-that-generics-solve}
|
||||
|
||||
下面是一个标准的非泛型函数 `swapTwoInts(_:_:)`,用来交换两个 `Int` 值:
|
||||
|
||||
```swift
|
||||
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
|
||||
let temporaryA = a
|
||||
a = b
|
||||
b = temporaryA
|
||||
}
|
||||
```
|
||||
|
||||
这个函数使用输入输出参数(`inout`)来交换 `a` 和 `b` 的值,具体请参考 [输入输出参数](./06_Functions.md#in-out-parameters)。
|
||||
|
||||
`swapTwoInts(_:_:)` 函数将 `b` 的原始值换成了 `a`,将 `a` 的原始值换成了 `b`,你可以调用这个函数来交换两个 `Int` 类型变量:
|
||||
|
||||
```swift
|
||||
var someInt = 3
|
||||
var anotherInt = 107
|
||||
swapTwoInts(&someInt, &anotherInt)
|
||||
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
|
||||
// 打印“someInt is now 107, and anotherInt is now 3”
|
||||
```
|
||||
|
||||
`swapTwoInts(_:_:)` 函数很实用,但它只能作用于 `Int` 类型。如果你想交换两个 `String` 类型值,或者 `Double` 类型值,你必须编写对应的函数,类似下面 `swapTwoStrings(_:_:)` 和 `swapTwoDoubles(_:_:)` 函数:
|
||||
|
||||
```swift
|
||||
func swapTwoStrings(_ a: inout String, _ b: inout String) {
|
||||
let temporaryA = a
|
||||
a = b
|
||||
b = temporaryA
|
||||
}
|
||||
|
||||
func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
|
||||
let temporaryA = a
|
||||
a = b
|
||||
b = temporaryA
|
||||
}
|
||||
```
|
||||
|
||||
你可能注意到了,`swapTwoInts(_:_:)`、`swapTwoStrings(_:_:)` 和 `swapTwoDoubles(_:_:)` 函数体是一样的,唯一的区别是它们接受的参数类型(`Int`、`String` 和 `Double`)。
|
||||
|
||||
在实际应用中,通常需要一个更实用更灵活的函数来交换两个任意类型的值,幸运的是,泛型代码帮你解决了这种问题。(这些函数的泛型版本已经在下面定义好了。)
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 在上面三个函数中,`a` 和 `b` 类型必须相同。如果 `a` 和 `b` 类型不同,那它们俩就不能互换值。Swift 是类型安全的语言,所以它不允许一个 `String` 类型的变量和一个 `Double` 类型的变量互换值。试图这样做将导致编译错误。
|
||||
|
||||
## 泛型函数 {#generic-functions}
|
||||
|
||||
泛型函数可适用于任意类型,下面是函数 `swapTwoInts(_:_:)` 的泛型版本,命名为 `swapTwoValues(_:_:)`:
|
||||
|
||||
```swift
|
||||
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
|
||||
let temporaryA = a
|
||||
a = b
|
||||
b = temporaryA
|
||||
}
|
||||
```
|
||||
|
||||
`swapTwoValues(_:_:)` 和 `swapTwoInts(_:_:)` 函数体内容相同,它们只在第一行不同,如下所示:
|
||||
|
||||
```swift
|
||||
func swapTwoInts(_ a: inout Int, _ b: inout Int)
|
||||
func swapTwoValues<T>(_ a: inout T, _ b: inout T)
|
||||
```
|
||||
|
||||
泛型版本的函数使用`占位符`类型名(这里叫做 `T` ),而不是 *实际*类型名(例如 `Int`、`String` 或 `Double`),`占位符`类型名并不关心 `T` 具体的类型,但它要求 `a` 和` b` 必须是相同的类型,`T` 的实际类型由每次调用 `swapTwoValues(_:_:)` 来决定。
|
||||
|
||||
泛型函数和非泛型函数的另外一个不同之处在于这个泛型函数名(`swapTwoValues(_:_:)`)后面跟着占位类型名(`T`),并用尖括号括起来(`<T>`)。这个尖括号告诉 Swift 那个 `T` 是 `swapTwoValues(_:_:)` 函数定义内的一个占位类型名,因此 Swift 不会去查找名为 `T `的实际类型。
|
||||
|
||||
`swapTwoValues(_:_:)` 函数现在可以像 `swapTwoInts(_:_:)` 那样调用,不同的是它能接受两个任意类型的值,条件是这两个值有着相同的类型。`swapTwoValues(_:_:)` 函数被调用时,`T ` 所代表的类型都会由传入的值的类型推断出来。
|
||||
|
||||
在下面的两个例子中,`T` 分别代表 ` Int` 和 `String`:
|
||||
|
||||
```swift
|
||||
var someInt = 3
|
||||
var anotherInt = 107
|
||||
swapTwoValues(&someInt, &anotherInt)
|
||||
// someInt 现在是 107,anotherInt 现在是 3
|
||||
|
||||
var someString = "hello"
|
||||
var anotherString = "world"
|
||||
swapTwoValues(&someString, &anotherString)
|
||||
// someString 现在是“world”,anotherString 现在是“hello”
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 上面定义的 `swapTwoValues(_:_:)` 函数是受 `swap(_:_:)` 函数启发而实现的。后者存在于 Swift 标准库,你可以在你的应用程序中使用它。如果你在代码中需要类似 `swapTwoValues(_:_:)` 函数的功能,你可以使用已存在的 `swap(_:_:)` 函数。
|
||||
|
||||
## 类型参数 {#type-parameters}
|
||||
|
||||
上面 `swapTwoValues(_:_:)` 例子中,占位类型 `T` 是一个类型参数的例子,类型参数指定并命名一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来(例如 `<T>`)。
|
||||
|
||||
一旦一个类型参数被指定,你可以用它来定义一个函数的参数类型(例如 `swapTwoValues(_:_:)` 函数中的参数 `a` 和 `b`),或者作为函数的返回类型,还可以用作函数主体中的注释类型。在这些情况下,类型参数会在函数调用时被实际类型所替换。(在上面的 `swapTwoValues(_:_:)` 例子中,当函数第一次被调用时,`T` 被 `Int` 替换,第二次调用时,被 `String` 替换。)
|
||||
|
||||
你可提供多个类型参数,将它们都写在尖括号中,用逗号分开。
|
||||
|
||||
## 命名类型参数 {#naming-type-parameters}
|
||||
|
||||
大多情况下,类型参数具有描述下的名称,例如字典 `Dictionary<Key, Value>` 中的 `Key` 和 `Value` 及数组 `Array<Element>` 中的 `Element`,这能告诉阅读代码的人这些参数类型与泛型类型或函数之间的关系。然而,当它们之间没有有意义的关系时,通常使用单个字符来表示,例如 `T`、`U`、`V`,例如上面演示函数 `swapTwoValues(_:_:)` 中的 `T`。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 请始终使用大写字母开头的驼峰命名法(例如 `T` 和 `MyTypeParameter`)来为类型参数命名,以表明它们是占位类型,而不是一个值。
|
||||
|
||||
## 泛型类型 {#generic-types}
|
||||
|
||||
除了泛型函数,Swift 还允许自定义*泛型类型*。这些自定义类、结构体和枚举可以适用于*任意类型*,类似于 `Array` 和 `Dictionary`。
|
||||
|
||||
本节将向你展示如何编写一个名为 `Stack`(栈)的泛型集合类型。栈是值的有序集合,和数组类似,但比数组有更严格的操作限制。数组允许在其中任意位置插入或是删除元素。而栈只允许在集合的末端添加新的元素(称之为入栈)。类似的,栈也只能从末端移除元素(称之为出栈)。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 栈的概念已被 `UINavigationController` 类用来构造视图控制器的导航结构。你通过调用 `UINavigationController` 的 `pushViewController(_:animated:)` 方法来添加新的视图控制器到导航栈,通过 `popViewControllerAnimated(_:)` 方法来从导航栈中移除视图控制器。每当你需要一个严格的“后进先出”方式来管理集合,栈都是最实用的模型。
|
||||
|
||||
下图展示了入栈(push)和出栈(pop)的行为:
|
||||
|
||||

|
||||
|
||||
1. 现在有三个值在栈中。
|
||||
2. 第四个值被压入到栈的顶部。
|
||||
3. 现在栈中有四个值,最近入栈的那个值在顶部。
|
||||
4. 栈中最顶部的那个值被移除出栈。
|
||||
5. 一个值移除出栈后,现在栈又只有三个值了。
|
||||
|
||||
下面展示如何编写一个非泛型版本的栈,以 `Int` 型的栈为例:
|
||||
|
||||
```swift
|
||||
struct IntStack {
|
||||
var items = [Int]()
|
||||
mutating func push(_ item: Int) {
|
||||
items.append(item)
|
||||
}
|
||||
mutating func pop() -> Int {
|
||||
return items.removeLast()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这个结构体在栈中使用一个名为 `items` 的数组属性来存储值。栈提供了两个方法:`push(_:)` 和 `pop()`,用来向栈中压入值以及从栈中移除值。这些方法被标记为 `mutating`,因为它们需要修改结构体的 `items` 数组。
|
||||
|
||||
上面的 `IntStack` 结构体只能用于 `Int` 类型。不过,可以定义一个泛型 `Stack` 结构体,从而能够处理任意类型的值。
|
||||
|
||||
下面是相同代码的泛型版本:
|
||||
|
||||
```swift
|
||||
struct Stack<Element> {
|
||||
var items = [Element]()
|
||||
mutating func push(_ item: Element) {
|
||||
items.append(item)
|
||||
}
|
||||
mutating func pop() -> Element {
|
||||
return items.removeLast()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
注意,`Stack` 基本上和 `IntStack` 相同,只是用占位类型参数 `Element` 代替了实际的 `Int` 类型。这个类型参数包裹在紧随结构体名的一对尖括号里(<`Element`>)。
|
||||
|
||||
`Element` 为待提供的类型定义了一个占位名。这种待提供的类型可以在结构体的定义中通过 `Element` 来引用。在这个例子中,`Element` 在如下三个地方被用作占位符:
|
||||
|
||||
+ 创建 `items` 属性,使用 `Element` 类型的空数组对其进行初始化。
|
||||
+ 指定 `push(_:)` 方法的唯一参数 `item` 的类型必须是 `Element` 类型。
|
||||
+ 指定 `pop()` 方法的返回值类型必须是 `Element` 类型。
|
||||
|
||||
由于 `Stack` 是泛型类型,因此可以用来创建适用于 Swift 中任意有效类型的栈,就像 `Array` 和 `Dictionary` 那样。
|
||||
|
||||
你可以通过在尖括号中写出栈中需要存储的数据类型来创建并初始化一个 `Stack` 实例。例如,要创建一个 `String` 类型的栈,可以写成 `Stack<String>()`:
|
||||
|
||||
```swift
|
||||
var stackOfStrings = Stack<String>()
|
||||
stackOfStrings.push("uno")
|
||||
stackOfStrings.push("dos")
|
||||
stackOfStrings.push("tres")
|
||||
stackOfStrings.push("cuatro")
|
||||
// 栈中现在有 4 个字符串
|
||||
```
|
||||
|
||||
下图展示了 `stackOfStrings` 如何将这四个值压栈:
|
||||
|
||||

|
||||
|
||||
移除并返回栈顶部的值“cuatro”,即出栈:
|
||||
|
||||
```swift
|
||||
let fromTheTop = stackOfStrings.pop()
|
||||
// fromTheTop 的值为“cuatro”,现在栈中还有 3 个字符串
|
||||
```
|
||||
|
||||
下图展示了如何将顶部的值出栈:
|
||||
|
||||

|
||||
|
||||
## 泛型扩展 {#extending-a-generic-type}
|
||||
|
||||
当对泛型类型进行扩展时,你并不需要提供类型参数列表作为定义的一部分。原始类型定义中声明的类型参数列表在扩展中可以直接使用,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。
|
||||
|
||||
下面的例子扩展了泛型类型 `Stack`,为其添加了一个名为 `topItem` 的只读计算型属性,它将会返回当前栈顶元素且不会将其从栈中移除:
|
||||
|
||||
```swift
|
||||
extension Stack {
|
||||
var topItem: Element? {
|
||||
return items.isEmpty ? nil : items[items.count - 1]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`topItem` 属性会返回 `Element` 类型的可选值。当栈为空的时候,`topItem` 会返回 `nil`;当栈不为空的时候,`topItem` 会返回 `items` 数组中的最后一个元素。
|
||||
|
||||
注意:这个扩展并没有定义类型参数列表。相反的,`Stack` 类型已有的类型参数名称 `Element`,被用在扩展中来表示计算型属性 `topItem` 的可选类型。
|
||||
|
||||
计算型属性 `topItem` 现在可以用来访问任意 `Stack` 实例的顶端元素且不移除它:
|
||||
|
||||
```swift
|
||||
if let topItem = stackOfStrings.topItem {
|
||||
print("The top item on the stack is \(topItem).")
|
||||
}
|
||||
// 打印“The top item on the stack is tres.”
|
||||
```
|
||||
|
||||
泛型类型的扩展,还可以包括类型扩展需要额外满足的条件,从而对类型添加新功能,这一部分将在 [具有泛型 Where 子句的扩展](#extensions-with-a-generic-where-clause) 中进行讨论。
|
||||
|
||||
## 类型约束 {#type-constraints}
|
||||
|
||||
`swapTwoValues(_:_:)` 函数和 `Stack` 适用于任意类型。不过,如果能对泛型函数或泛型类型中添加特定的*类型约束*,这将在某些情况下非常有用。类型约束指定类型参数必须继承自指定类、遵循特定的协议或协议组合。
|
||||
|
||||
例如,Swift 的 `Dictionary` 类型对字典的键的类型做了些限制。在 [字典的描述](./04_Collection_Types.md#dictionaries) 中,字典键的类型必须是可哈希(hashable)的。也就是说,必须有一种方法能够唯一地表示它。字典键之所以要是可哈希的,是为了便于检查字典中是否已经包含某个特定键的值。若没有这个要求,字典将无法判断是否可以插入或替换某个指定键的值,也不能查找到已经存储在字典中的指定键的值。
|
||||
|
||||
这个要求通过 `Dictionary` 键类型上的类型约束实现,它指明了键必须遵循 Swift 标准库中定义的 `Hashable` 协议。所有 Swift 的基本类型(例如 `String`、`Int`、`Double` 和 `Bool`)默认都是可哈希的。如何让自定义类型遵循 `Hashable` 协议,可以查看文档 [遵循 Hashable 协议](https://developer.apple.com/documentation/swift/hashable#2849490)。
|
||||
|
||||
当自定义泛型类型时,你可以定义你自己的类型约束,这些约束将提供更为强大的泛型编程能力。像 `可哈希(hashable)` 这种抽象概念根据它们的概念特征来描述类型,而不是它们的具体类型。
|
||||
|
||||
### 类型约束语法 {#type-constraint-syntax}
|
||||
|
||||
在一个类型参数名后面放置一个类名或者协议名,并用冒号进行分隔,来定义类型约束。下面将展示泛型函数约束的基本语法(与泛型类型的语法相同):
|
||||
|
||||
```swift
|
||||
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
|
||||
// 这里是泛型函数的函数体部分
|
||||
}
|
||||
```
|
||||
|
||||
上面这个函数有两个类型参数。第一个类型参数 `T` 必须是 `SomeClass` 子类;第二个类型参数 `U` 必须符合 `SomeProtocol` 协议。
|
||||
|
||||
### 类型约束实践 {#type-constraints-in-action}
|
||||
|
||||
这里有个名为 `findIndex(ofString:in:)` 的非泛型函数,该函数的功能是在一个 `String` 数组中查找给定 `String` 值的索引。若查找到匹配的字符串,`findIndex(ofString:in:)` 函数返回该字符串在数组中的索引值,否则返回 `nil`:
|
||||
|
||||
```swift
|
||||
func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
|
||||
for (index, value) in array.enumerated() {
|
||||
if value == valueToFind {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
`findIndex(ofString:in:)` 函数可以用于查找字符串数组中的某个字符串值:
|
||||
|
||||
```swift
|
||||
let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
|
||||
if let foundIndex = findIndex(ofString: "llama", in: strings) {
|
||||
print("The index of llama is \(foundIndex)")
|
||||
}
|
||||
// 打印“The index of llama is 2”
|
||||
```
|
||||
|
||||
如果只能查找字符串在数组中的索引,用处不是很大。不过,你可以用占位类型 `T` 替换 `String` 类型来写出具有相同功能的泛型函数 `findIndex(_:_:)`。
|
||||
|
||||
下面展示了 `findIndex(ofString:in:)` 函数的泛型版本 `findIndex(of:in:)`。请注意这个函数返回值的类型仍然是 `Int?`,这是因为函数返回的是一个可选的索引数,而不是从数组中得到的一个可选值。需要提醒的是,这个函数无法通过编译,原因将在后面说明:
|
||||
|
||||
```swift
|
||||
func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
|
||||
for (index, value) in array.enumerated() {
|
||||
if value == valueToFind {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
上面所写的函数无法通过编译。问题出在相等性检查上,即 "`if value == valueToFind`"。不是所有的 Swift 类型都可以用等式符(`==`)进行比较。例如,如果你自定义类或结构体来描述复杂的数据模型,对于这个类或结构体而言,Swift 无法明确知道“相等”意味着什么。正因如此,这部分代码无法保证适用于任意类型 `T`,当你试图编译这部分代码时就会出现相应的错误。
|
||||
|
||||
不过,所有的这些并不会让我们无从下手。Swift 标准库中定义了一个 `Equatable` 协议,该协议要求任何遵循该协议的类型必须实现等式符(`==`)及不等符(`!=`),从而能对该类型的任意两个值进行比较。所有的 Swift 标准类型自动支持 `Equatable` 协议。
|
||||
|
||||
遵循 `Equatable` 协议的类型都可以安全地用于 `findIndex(of:in:)` 函数,因为其保证支持等式操作符。为了说明这个事情,当定义一个函数时,你可以定义一个 `Equatable` 类型约束作为类型参数定义的一部分:
|
||||
|
||||
```swift
|
||||
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
|
||||
for (index, value) in array.enumerated() {
|
||||
if value == valueToFind {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
`findIndex(of:in:)` 类型参数写做 `T: Equatable`,也就意味着“任何符合 `Equatable` 协议的类型 `T`”。
|
||||
|
||||
`findIndex(of:in:)` 函数现在可以成功编译了,并且适用于任何符合 `Equatable` 的类型,如 `Double` 或 `String`:
|
||||
|
||||
```swift
|
||||
let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])
|
||||
// doubleIndex 类型为 Int?,其值为 nil,因为 9.3 不在数组中
|
||||
let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])
|
||||
// stringIndex 类型为 Int?,其值为 2
|
||||
```
|
||||
|
||||
## 关联类型 {#associated-types}
|
||||
|
||||
定义一个协议时,声明一个或多个关联类型作为协议定义的一部分将会非常有用。关联类型为协议中的某个类型提供了一个占位符名称,其代表的实际类型在协议被遵循时才会被指定。关联类型通过 `associatedtype` 关键字来指定。
|
||||
|
||||
### 关联类型实践 {#associated-types-in-action}
|
||||
|
||||
下面例子定义了一个 `Container` 协议,该协议定义了一个关联类型 `Item`:
|
||||
|
||||
```swift
|
||||
protocol Container {
|
||||
associatedtype Item
|
||||
mutating func append(_ item: Item)
|
||||
var count: Int { get }
|
||||
subscript(i: Int) -> Item { get }
|
||||
}
|
||||
```
|
||||
|
||||
`Container` 协议定义了三个任何遵循该协议的类型(即容器)必须提供的功能:
|
||||
|
||||
+ 必须可以通过 `append(_:)` 方法添加一个新元素到容器里。
|
||||
+ 必须可以通过 `count` 属性获取容器中元素的数量,并返回一个 Int 值。
|
||||
+ 必须可以通过索引值类型为 `Int` 的下标检索到容器中的每一个元素。
|
||||
|
||||
该协议没有指定容器中元素该如何存储以及元素类型。该协议只指定了任何遵从 `Container` 协议的类型必须提供的三个功能。遵从协议的类型在满足这三个条件的情况下,也可以提供其他额外的功能。
|
||||
|
||||
任何遵从 `Container` 协议的类型必须能够指定其存储的元素的类型。具体来说,它必须确保添加到容器内的元素以及下标返回的元素类型是正确的。
|
||||
|
||||
为了定义这些条件,`Container` 协议需要在不知道容器中元素的具体类型的情况下引用这种类型。`Container` 协议需要指定任何通过 `append(_:)` 方法添加到容器中的元素和容器内的元素是相同类型,并且通过容器下标返回的元素的类型也是这种类型。
|
||||
|
||||
为此,`Container` 协议声明了一个关联类型 `Item`,写作 `associatedtype Item`。协议没有定义 `Item` 是什么,这个信息留给遵从协议的类型来提供。尽管如此,`Item` 别名提供了一种方式来引用 `Container` 中元素的类型,并将之用于 `append(_:)` 方法和下标,从而保证任何 `Container` 的行为都能如预期。
|
||||
|
||||
这是前面非泛型版本 `IntStack` 类型,使其遵循 `Container` 协议:
|
||||
|
||||
```swift
|
||||
struct IntStack: Container {
|
||||
// IntStack 的原始实现部分
|
||||
var items = [Int]()
|
||||
mutating func push(_ item: Int) {
|
||||
items.append(item)
|
||||
}
|
||||
mutating func pop() -> Int {
|
||||
return items.removeLast()
|
||||
}
|
||||
// Container 协议的实现部分
|
||||
typealias Item = Int
|
||||
mutating func append(_ item: Int) {
|
||||
self.push(item)
|
||||
}
|
||||
var count: Int {
|
||||
return items.count
|
||||
}
|
||||
subscript(i: Int) -> Int {
|
||||
return items[i]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`IntStack` 结构体实现了 `Container` 协议的三个要求,其原有功能也不会和这些要求相冲突。
|
||||
|
||||
此外,`IntStack` 在实现 `Container` 的要求时,指定 `Item` 为 `Int` 类型,即 `typealias Item = Int`,从而将 `Container` 协议中抽象的 `Item` 类型转换为具体的 `Int` 类型。
|
||||
|
||||
由于 Swift 的类型推断,实际上在 `IntStack` 的定义中不需要声明 `Item` 为 `Int`。因为 `IntStack` 符合 `Container` 协议的所有要求,Swift 只需通过 `append(_:)` 方法的 `item` 参数类型和下标返回值的类型,就可以推断出 `Item` 的具体类型。事实上,如果你在上面的代码中删除了 `typealias Item = Int` 这一行,一切也可正常工作,因为 Swift 清楚地知道 `Item` 应该是哪种类型。
|
||||
|
||||
你也可以让泛型 `Stack` 结构体遵循 `Container` 协议:
|
||||
|
||||
```swift
|
||||
struct Stack<Element>: Container {
|
||||
// Stack<Element> 的原始实现部分
|
||||
var items = [Element]()
|
||||
mutating func push(_ item: Element) {
|
||||
items.append(item)
|
||||
}
|
||||
mutating func pop() -> Element {
|
||||
return items.removeLast()
|
||||
}
|
||||
// Container 协议的实现部分
|
||||
mutating func append(_ item: Element) {
|
||||
self.push(item)
|
||||
}
|
||||
var count: Int {
|
||||
return items.count
|
||||
}
|
||||
subscript(i: Int) -> Element {
|
||||
return items[i]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这一次,占位类型参数 `Element` 被用作 `append(_:)` 方法的 `item` 参数和下标的返回类型。Swift 可以据此推断出 `Element` 的类型即是 `Item` 的类型。
|
||||
|
||||
### 扩展现有类型来指定关联类型 {#extending-an-existing-type-to-specify-an-associated-type}
|
||||
|
||||
[在扩展添加协议一致性](./21_Protocols.md#adding-protocol-conformance-with-an-extension) 中描述了如何利用扩展让一个已存在的类型遵循一个协议,这包括使用了关联类型协议。
|
||||
|
||||
Swift 的 `Array` 类型已经提供 `append(_:)` 方法,`count` 属性,以及带有 `Int` 索引的下标来检索其元素。这三个功能都符合 `Container` 协议的要求,也就意味着你只需声明 `Array` 遵循`Container` 协议,就可以扩展 Array,使其遵从 Container 协议。你可以通过一个空扩展来实现这点,正如通过扩展采纳协议中的描述:
|
||||
|
||||
```swift
|
||||
extension Array: Container {}
|
||||
```
|
||||
|
||||
`Array` 的 `append(_:)` 方法和下标确保了 Swift 可以推断出 `Item` 具体类型。定义了这个扩展后,你可以将任意 `Array` 当作 Container 来使用。
|
||||
|
||||
### 给关联类型添加约束 {#adding-constraints-to-an-associated-type}
|
||||
|
||||
你可以在协议里给关联类型添加约束来要求遵循的类型满足约束。例如,下面的代码定义了 `Container` 协议, 要求关联类型 `Item` 必须遵循 `Equatable` 协议:
|
||||
|
||||
```swift
|
||||
protocol Container {
|
||||
associatedtype Item: Equatable
|
||||
mutating func append(_ item: Item)
|
||||
var count: Int { get }
|
||||
subscript(i: Int) -> Item { get }
|
||||
}
|
||||
```
|
||||
|
||||
要遵守 `Container` 协议,`Item` 类型也必须遵守 `Equatable` 协议。
|
||||
|
||||
### 在关联类型约束里使用协议 {#using-a-protocol-in-its-associated-types-constraints}
|
||||
|
||||
协议可以作为它自身的要求出现。例如,有一个协议细化了 `Container` 协议,添加了一个` suffix(_:)` 方法。`suffix(_:)` 方法返回容器中从后往前给定数量的元素,并把它们存储在一个 `Suffix` 类型的实例里。
|
||||
|
||||
```swift
|
||||
protocol SuffixableContainer: Container {
|
||||
associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
|
||||
func suffix(_ size: Int) -> Suffix
|
||||
}
|
||||
```
|
||||
|
||||
在这个协议里,`Suffix` 是一个关联类型,就像上边例子中 `Container` 的 `Item` 类型一样。`Suffix` 拥有两个约束:它必须遵循 `SuffixableContainer` 协议(就是当前定义的协议),以及它的 `Item` 类型必须是和容器里的 `Item` 类型相同。`Item` 的约束是一个 `where` 分句,它在下面 [具有泛型 Where 子句的扩展](#extensions-with-a-generic-where-clause) 中有讨论。
|
||||
|
||||
这是上面 [泛型类型](#generic-types) 中 `Stack` 类型的扩展,它遵循了 SuffixableContainer 协议:
|
||||
|
||||
```swift
|
||||
extension Stack: SuffixableContainer {
|
||||
func suffix(_ size: Int) -> Stack {
|
||||
var result = Stack()
|
||||
for index in (count-size)..<count {
|
||||
result.append(self[index])
|
||||
}
|
||||
return result
|
||||
}
|
||||
// 推断 suffix 结果是Stack。
|
||||
}
|
||||
var stackOfInts = Stack<Int>()
|
||||
stackOfInts.append(10)
|
||||
stackOfInts.append(20)
|
||||
stackOfInts.append(30)
|
||||
let suffix = stackOfInts.suffix(2)
|
||||
// suffix 包含 20 和 30
|
||||
```
|
||||
|
||||
在上面的例子中,`Suffix` 是 `Stack` 的关联类型,也是 `Stack` ,所以 `Stack` 的后缀运算返回另一个 `Stack` 。另外,遵循 `SuffixableContainer` 的类型可以拥有一个与它自己不同的 `Suffix` 类型——也就是说后缀运算可以返回不同的类型。比如说,这里有一个非泛型 `IntStack` 类型的扩展,它遵循了 `SuffixableContainer` 协议,使用 `Stack<Int>` 作为它的后缀类型而不是 `IntStack`:
|
||||
|
||||
```swift
|
||||
extension IntStack: SuffixableContainer {
|
||||
func suffix(_ size: Int) -> Stack<Int> {
|
||||
var result = Stack<Int>()
|
||||
for index in (count-size)..<count {
|
||||
result.append(self[index])
|
||||
}
|
||||
return result
|
||||
}
|
||||
// 推断 suffix 结果是 Stack<Int>。
|
||||
}
|
||||
```
|
||||
|
||||
## 泛型 Where 语句 {#where-clauses}
|
||||
|
||||
[类型约束](#type-constraints) 让你能够为泛型函数、下标、类型的类型参数定义一些强制要求。
|
||||
|
||||
对关联类型添加约束通常是非常有用的。你可以通过定义一个泛型 `where` 子句来实现。通过泛型 `where` 子句让关联类型遵从某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。你可以通过将 `where` 关键字紧跟在类型参数列表后面来定义 `where` 子句,`where` 子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。你可以在函数体或者类型的大括号之前添加 `where` 子句。
|
||||
|
||||
下面的例子定义了一个名为 `allItemsMatch` 的泛型函数,用来检查两个 `Container` 实例是否包含相同顺序的相同元素。如果所有的元素能够匹配,那么返回 `true`,否则返回 `false`。
|
||||
|
||||
被检查的两个 `Container` 可以不是相同类型的容器(虽然它们可以相同),但它们必须拥有相同类型的元素。这个要求通过一个类型约束以及一个 `where` 子句来表示:
|
||||
|
||||
```swift
|
||||
func allItemsMatch<C1: Container, C2: Container>
|
||||
(_ someContainer: C1, _ anotherContainer: C2) -> Bool
|
||||
where C1.Item == C2.Item, C1.Item: Equatable {
|
||||
|
||||
// 检查两个容器含有相同数量的元素
|
||||
if someContainer.count != anotherContainer.count {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查每一对元素是否相等
|
||||
for i in 0..<someContainer.count {
|
||||
if someContainer[i] != anotherContainer[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 所有元素都匹配,返回 true
|
||||
return true
|
||||
}
|
||||
```
|
||||
|
||||
这个函数接受 `someContainer` 和 `anotherContainer` 两个参数。参数 `someContainer` 的类型为 `C1`,参数 `anotherContainer` 的类型为 `C2`。`C1` 和 `C2` 是容器的两个占位类型参数,函数被调用时才能确定它们的具体类型。
|
||||
|
||||
这个函数的类型参数列表还定义了对两个类型参数的要求:
|
||||
|
||||
+ `C1` 必须符合 `Container` 协议(写作 `C1: Container`)。
|
||||
+ `C2` 必须符合 `Container` 协议(写作 `C2: Container`)。
|
||||
+ `C1` 的 `Item` 必须和 `C2` 的 `Item` 类型相同(写作 `C1.Item == C2.Item`)。
|
||||
+ `C1` 的 `Item` 必须符合 `Equatable` 协议(写作 `C1.Item: Equatable`)。
|
||||
|
||||
前两个要求定义在函数的类型形式参数列表里,后两个要求定义在了函数的泛型 `where` 分句中。
|
||||
|
||||
这些要求意味着:
|
||||
|
||||
+ `someContainer` 是一个 `C1` 类型的容器。
|
||||
+ `anotherContainer` 是一个 `C2` 类型的容器。
|
||||
+ `someContainer` 和 `anotherContainer` 包含相同类型的元素。
|
||||
+ `someContainer` 中的元素可以通过不等于操作符(!=)来检查它们是否相同。
|
||||
|
||||
第三个和第四个要求结合起来意味着 `anotherContainer` 中的元素也可以通过 `!=` 操作符来比较,因为它们和 `someContainer` 中的元素类型相同。
|
||||
|
||||
这些要求让 `allItemsMatch(_:_:)` 函数能够比较两个容器,即使它们的容器类型不同。
|
||||
|
||||
`allItemsMatch(_:_:)` 函数首先检查两个容器元素个数是否相同,如果元素个数不同,那么一定不匹配,函数就会返回 `false`。
|
||||
|
||||
进行这项检查之后,通过 `for-in` 循环和半闭区间操作符(`..<`)来迭代每个元素,检查 `someContainer` 中的元素是否不等于 `anotherContainer` 中的对应元素。如果两个元素不相等,那么两个容器不匹配,函数返回 false。
|
||||
|
||||
如果循环体结束后未发现任何不匹配的情况,表明两个容器匹配,函数返回 `true`。
|
||||
|
||||
下面是 `allItemsMatch(_:_:)` 函数的示例:
|
||||
|
||||
```swift
|
||||
var stackOfStrings = Stack<String>()
|
||||
stackOfStrings.push("uno")
|
||||
stackOfStrings.push("dos")
|
||||
stackOfStrings.push("tres")
|
||||
|
||||
var arrayOfStrings = ["uno", "dos", "tres"]
|
||||
|
||||
if allItemsMatch(stackOfStrings, arrayOfStrings) {
|
||||
print("All items match.")
|
||||
} else {
|
||||
print("Not all items match.")
|
||||
}
|
||||
// 打印“All items match.”
|
||||
```
|
||||
|
||||
上面的例子创建 `Stack` 实例来存储 `String` 值,然后将三个字符串压栈。这个例子还通过数组字面量创建了一个 `Array` 实例,数组中包含同栈中一样的三个字符串。即使栈和数组是不同的类型,但它们都遵从 `Container` 协议,而且它们都包含相同类型的值。因此你可以用这两个容器作为参数来调用 `allItemsMatch(_:_:)` 函数。在上面的例子中,`allItemsMatch(_:_:)` 函数正确地显示了这两个容器中的所有元素都是相互匹配的。
|
||||
|
||||
## 具有泛型 Where 子句的扩展 {#extensions-with-a-generic-where-clause}
|
||||
|
||||
你也可以使用泛型 `where` 子句作为扩展的一部分。基于以前的例子,下面的示例扩展了泛型 `Stack` 结构体,添加一个 `isTop(_:)` 方法。
|
||||
|
||||
```swift
|
||||
extension Stack where Element: Equatable {
|
||||
func isTop(_ item: Element) -> Bool {
|
||||
guard let topItem = items.last else {
|
||||
return false
|
||||
}
|
||||
return topItem == item
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这个新的 `isTop(_:)` 方法首先检查这个栈是不是空的,然后比较给定的元素与栈顶部的元素。如果你尝试不用泛型 `where` 子句,会有一个问题:在 `isTop(_:)` 里面使用了 `==` 运算符,但是 `Stack` 的定义没有要求它的元素是符合 `Equatable` 协议的,所以使用 `==` 运算符导致编译时错误。使用泛型 `where` 子句可以为扩展添加新的条件,因此只有当栈中的元素符合 `Equatable` 协议时,扩展才会添加 `isTop(_:)` 方法。
|
||||
|
||||
以下是 `isTop(_:)` 方法的调用方式:
|
||||
|
||||
```swift
|
||||
if stackOfStrings.isTop("tres") {
|
||||
print("Top element is tres.")
|
||||
} else {
|
||||
print("Top element is something else.")
|
||||
}
|
||||
// 打印“Top element is tres.”
|
||||
```
|
||||
|
||||
如果尝试在其元素不符合 `Equatable` 协议的栈上调用 `isTop(_:)` 方法,则会收到编译时错误。
|
||||
|
||||
```swift
|
||||
struct NotEquatable { }
|
||||
var notEquatableStack = Stack<NotEquatable>()
|
||||
let notEquatableValue = NotEquatable()
|
||||
notEquatableStack.push(notEquatableValue)
|
||||
notEquatableStack.isTop(notEquatableValue) // 报错
|
||||
```
|
||||
|
||||
你可以使用泛型 `where` 子句去扩展一个协议。基于以前的示例,下面的示例扩展了 `Container` 协议,添加一个 `startsWith(_:)` 方法。
|
||||
|
||||
```swift
|
||||
extension Container where Item: Equatable {
|
||||
func startsWith(_ item: Item) -> Bool {
|
||||
return count >= 1 && self[0] == item
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这个 `startsWith(_:)` 方法首先确保容器至少有一个元素,然后检查容器中的第一个元素是否与给定的元素相等。任何符合 `Container` 协议的类型都可以使用这个新的 `startsWith(_:)` 方法,包括上面使用的栈和数组,只要容器的元素是符合 `Equatable` 协议的。
|
||||
|
||||
```swift
|
||||
if [9, 9, 9].startsWith(42) {
|
||||
print("Starts with 42.")
|
||||
} else {
|
||||
print("Starts with something else.")
|
||||
}
|
||||
// 打印“Starts with something else.”
|
||||
```
|
||||
|
||||
上述示例中的泛型 `where` 子句要求 `Item` 遵循协议,但也可以编写一个泛型 `where` 子句去要求 `Item` 为特定类型。例如:
|
||||
|
||||
```swift
|
||||
extension Container where Item == Double {
|
||||
func average() -> Double {
|
||||
var sum = 0.0
|
||||
for index in 0..<count {
|
||||
sum += self[index]
|
||||
}
|
||||
return sum / Double(count)
|
||||
}
|
||||
}
|
||||
print([1260.0, 1200.0, 98.6, 37.0].average())
|
||||
// 打印“648.9”
|
||||
```
|
||||
|
||||
此示例将一个 `average()` 方法添加到 `Item` 类型为 `Double` 的容器中。此方法遍历容器中的元素将其累加,并除以容器的数量计算平均值。它将数量从 `Int` 转换为 `Double` 确保能够进行浮点除法。
|
||||
|
||||
就像可以在其他地方写泛型 `where` 子句一样,你可以在一个泛型 `where` 子句中包含多个条件作为扩展的一部分。用逗号分隔列表中的每个条件。
|
||||
|
||||
## 包含上下文关系的 where 分句 {#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` 协议那样。你应该这么写:
|
||||
|
||||
```swift
|
||||
protocol Container {
|
||||
associatedtype Item
|
||||
mutating func append(_ item: Item)
|
||||
var count: Int { get }
|
||||
subscript(i: Int) -> Item { get }
|
||||
|
||||
associatedtype Iterator: IteratorProtocol where Iterator.Element == Item
|
||||
func makeIterator() -> Iterator
|
||||
}
|
||||
```
|
||||
|
||||
迭代器(`Iterator`)的泛型 `where` 子句要求:无论迭代器是什么类型,迭代器中的元素类型,必须和容器项目的类型保持一致。`makeIterator()` 则提供了容器的迭代器的访问接口。
|
||||
|
||||
一个协议继承了另一个协议,你通过在协议声明的时候,包含泛型 `where` 子句,来添加了一个约束到被继承协议的关联类型。例如,下面的代码声明了一个 `ComparableContainer` 协议,它要求所有的 `Item` 必须是 `Comparable` 的。
|
||||
|
||||
```swift
|
||||
protocol ComparableContainer: Container where Item: Comparable { }
|
||||
```
|
||||
|
||||
## 泛型下标 {#generic-subscripts}
|
||||
|
||||
下标可以是泛型,它们能够包含泛型 `where` 子句。你可以在 `subscript` 后用尖括号来写占位符类型,你还可以在下标代码块花括号前写 `where` 子句。例如:
|
||||
|
||||
```swift
|
||||
extension Container {
|
||||
subscript<Indices: Sequence>(indices: Indices) -> [Item]
|
||||
where Indices.Iterator.Element == Int {
|
||||
var result = [Item]()
|
||||
for index in indices {
|
||||
result.append(self[index])
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这个 `Container` 协议的扩展添加了一个下标方法,接收一个索引的集合,返回每一个索引所在的值的数组。这个泛型下标的约束如下:
|
||||
|
||||
+ 在尖括号中的泛型参数 `Indices`,必须是符合标准库中的 `Sequence` 协议的类型。
|
||||
+ 下标使用的单一的参数,`indices`,必须是 `Indices` 的实例。
|
||||
+ 泛型 `where` 子句要求 `Sequence(Indices)`的迭代器,其所有的元素都是 `Int` 类型。这样就能确保在序列(`Sequence`)中的索引和容器(`Container`)里面的索引类型是一致的。
|
||||
|
||||
综合一下,这些约束意味着,传入到 `indices` 下标,是一个整型的序列。
|
||||
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(smallTriange))` 这样的多重变换操作是非法的,因为经过翻转操作后的结果类型并不能作为 `protoFlip(_:)` 的形参。
|
||||
|
||||
相比之下,不透明类型则保留了底层类型的唯一性。Swift 能够推断出关联类型,这个特点使得作为函数返回值,不透明类型比协议类型有更大的使用场景。比如,下面这个例子是 [泛型](./22_Generics.md) 中讲到的 `Container` 协议:
|
||||
|
||||
```swift
|
||||
protocol Container {
|
||||
associatedtype Item
|
||||
var count: Int { get }
|
||||
subscript(i: Int) -> Item { get }
|
||||
}
|
||||
extension Array: Container { }
|
||||
```
|
||||
|
||||
你不能将 `Container` 作为方法的返回类型,因为此协议有一个关联类型。你也不能将它用于对泛型返回类型的约束,因为函数体之外并没有暴露足够多的信息来推断泛型类型。
|
||||
|
||||
```swift
|
||||
// 错误:有关联类型的协议不能作为返回类型。
|
||||
func makeProtocolContainer<T>(item: T) -> Container {
|
||||
return [item]
|
||||
}
|
||||
|
||||
// 错误:没有足够多的信息来推断 C 的类型。
|
||||
func makeProtocolContainer<T, C: Container>(item: T) -> C {
|
||||
return [item]
|
||||
}
|
||||
```
|
||||
|
||||
而使用不透明类型 `some Container` 作为返回类型,就能够明确地表达所需要的 API 契约 —— 函数会返回一个集合类型,但并不指明它的具体类型:
|
||||
|
||||
```swift
|
||||
func makeOpaqueContainer<T>(item: T) -> some Container {
|
||||
return [item]
|
||||
}
|
||||
let opaqueContainer = makeOpaqueContainer(item: 12)
|
||||
let twelve = opaqueContainer[0]
|
||||
print(type(of: twelve))
|
||||
// 输出 "Int"
|
||||
```
|
||||
|
||||
`twelve` 的类型可以被推断出为 `Int`, 这说明了类型推断适用于不透明类型。在 `makeOpaqueContainer(item:)` 的实现中,底层类型是不透明集合 `[T]`。在上述这种情况下,`T` 就是 `Int` 类型,所以返回值就是整数数组,而关联类型 `Item` 也被推断出为 `Int`。`Container` 协议中的 `subscipt` 方法会返回 `Item`,这也意味着 `twelve` 的类型也被能推断出为 `Int`。
|
||||
561
source/02_language_guide/24_Automatic_Reference_Counting.md
Executable file
561
source/02_language_guide/24_Automatic_Reference_Counting.md
Executable file
@ -0,0 +1,561 @@
|
||||
# 自动引用计数
|
||||
|
||||
Swift 使用*自动引用计数(ARC)*机制来跟踪和管理你的应用程序的内存。通常情况下,Swift 内存管理机制会一直起作用,你无须自己来考虑内存的管理。ARC 会在类的实例不再被使用时,自动释放其占用的内存。
|
||||
|
||||
然而在少数情况下,为了能帮助你管理内存,ARC 需要更多的,代码之间关系的信息。本章描述了这些情况,并且为你示范怎样才能使 ARC 来管理你的应用程序的所有内存。在 Swift 使用 ARC 与在 Obejctive-C 中使用 ARC 非常类似,具体请参考 [过渡到 ARC 的发布说明](https://developer.apple.com/library/content/releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html#//apple-ref/doc/uid/TP40011226)。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 引用计数仅仅应用于类的实例。结构体和枚举类型是值类型,不是引用类型,也不是通过引用的方式存储和传递。
|
||||
|
||||
## 自动引用计数的工作机制 {#how-arc-works}
|
||||
|
||||
每当你创建一个新的类实例时,ARC 会分配一块内存来储存该实例的信息。内存中会包含实例的类型信息,以及这个实例所关联的任何存储属性的值。
|
||||
|
||||
此外,当实例不再被使用时,ARC 释放实例所占用的内存,并让释放的内存能挪作他用。这确保了不再被使用的实例,不会一直占用内存空间。
|
||||
|
||||
然而,当 ARC 回收并释放了正在被使用中的实例后,该实例的属性和方法将不能再被访问和调用。实际上,如果你试图访问这个实例,你的应用程序很可能会崩溃。
|
||||
|
||||
为了确保使用中的实例不会被销毁,ARC 会跟踪和计算每一个实例正在被多少属性,常量和变量所引用。哪怕实例的引用数为 1,ARC 都不会销毁这个实例。
|
||||
|
||||
为了使上述成为可能,无论你将实例赋值给属性、常量或变量,它们都会创建此实例的强引用。之所以称之为“强”引用,是因为它会将实例牢牢地保持住,只要强引用还在,实例是不允许被销毁的。
|
||||
|
||||
## 自动引用计数实践 {#arc-in-action}
|
||||
|
||||
下面的例子展示了自动引用计数的工作机制。例子以一个简单的 `Person` 类开始,并定义了一个叫 `name` 的常量属性:
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
let name: String
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
print("\(name) is being initialized")
|
||||
}
|
||||
deinit {
|
||||
print("\(name) is being deinitialized")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Person` 类有一个构造器,此构造器给实例的 `name` 属性赋值,并打印一条消息以表明初始化过程生效。`Person` 类也拥有一个析构器,这个析构器会在实例被销毁时打印一条消息。
|
||||
|
||||
接下来的代码片段定义了三个类型为 `Person?` 的变量,按照代码片段中的顺序,为新的 `Person` 实例建立多个引用。由于这些变量是被定义为可选类型(`Person?`,而不是 `Person`),它们的值会被自动初始化为 `nil`,目前还不会引用到 `Person` 类的实例。
|
||||
|
||||
```swift
|
||||
var reference1: Person?
|
||||
var reference2: Person?
|
||||
var reference3: Person?
|
||||
```
|
||||
|
||||
现在你可以创建 `Person` 类的新实例,并且将它赋值给三个变量中的一个:
|
||||
|
||||
```swift
|
||||
reference1 = Person(name: "John Appleseed")
|
||||
// 打印“John Appleseed is being initialized”
|
||||
```
|
||||
|
||||
应当注意到当你调用 `Person` 类的构造器的时候,`"John Appleseed is being initialized"` 会被打印出来。由此可以确定构造器被执行。
|
||||
|
||||
由于 `Person` 类的新实例被赋值给了 `reference1` 变量,所以 `reference1` 到 `Person` 类的新实例之间建立了一个强引用。正是因为这一个强引用,ARC 会保证 `Person` 实例被保持在内存中不被销毁。
|
||||
|
||||
如果你将同一个 `Person` 实例也赋值给其他两个变量,该实例又会多出两个强引用:
|
||||
|
||||
```swift
|
||||
reference2 = reference1
|
||||
reference3 = reference1
|
||||
```
|
||||
|
||||
现在这一个 `Person` 实例已经有三个强引用了。
|
||||
|
||||
如果你通过给其中两个变量赋值 `nil` 的方式断开两个强引用(包括最先的那个强引用),只留下一个强引用,`Person` 实例不会被销毁:
|
||||
|
||||
```swift
|
||||
reference1 = nil
|
||||
reference2 = nil
|
||||
```
|
||||
|
||||
在你清楚地表明不再使用这个 `Person` 实例时,即第三个也就是最后一个强引用被断开时,ARC 会销毁它:
|
||||
|
||||
```swift
|
||||
reference3 = nil
|
||||
// 打印“John Appleseed is being deinitialized”
|
||||
```
|
||||
|
||||
## 类实例之间的循环强引用 {#strong-reference-cycles-between-class-instances}
|
||||
|
||||
在上面的例子中,ARC 会跟踪你所新创建的 `Person` 实例的引用数量,并且会在 `Person` 实例不再被需要时销毁它。
|
||||
|
||||
然而,我们可能会写出一个类实例的强引用数*永远不能*变成 `0` 的代码。如果两个类实例互相持有对方的强引用,因而每个实例都让对方一直存在,就是这种情况。这就是所谓的*循环强引用*。
|
||||
|
||||
你可以通过定义类之间的关系为弱引用或无主引用,来替代强引用,从而解决循环强引用的问题。具体的过程在 [解决类实例之间的循环强引用](#resolving-strong-reference-cycles-between-class-instances) 中有描述。不管怎样,在你学习怎样解决循环强引用之前,很有必要了解一下它是怎样产生的。
|
||||
|
||||
下面展示了一个不经意产生循环强引用的例子。例子定义了两个类:`Person` 和 `Apartment`,用来建模公寓和它其中的居民:
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
let name: String
|
||||
init(name: String) { self.name = name }
|
||||
var apartment: Apartment?
|
||||
deinit { print("\(name) is being deinitialized") }
|
||||
}
|
||||
|
||||
class Apartment {
|
||||
let unit: String
|
||||
init(unit: String) { self.unit = unit }
|
||||
var tenant: Person?
|
||||
deinit { print("Apartment \(unit) is being deinitialized") }
|
||||
}
|
||||
```
|
||||
|
||||
每一个 `Person` 实例有一个类型为 `String`,名字为 `name` 的属性,并有一个可选的初始化为 `nil` 的 `apartment` 属性。`apartment` 属性是可选的,因为一个人并不总是拥有公寓。
|
||||
|
||||
类似的,每个 `Apartment` 实例有一个叫 `unit`,类型为 `String` 的属性,并有一个可选的初始化为 `nil` 的 `tenant` 属性。`tenant` 属性是可选的,因为一栋公寓并不总是有居民。
|
||||
|
||||
这两个类都定义了析构器,在类实例被析构的时候输出信息。这让你能够知晓 `Person` 和 `Apartment` 的实例是否像预期的那样被销毁。
|
||||
|
||||
接下来的代码片段定义了两个可选类型的变量 `john` 和 `unit4A`,并分别被设为下面的 `Apartment` 和 `Person` 的实例。这两个变量都被初始化为 `nil`,这正是可选类型的优点:
|
||||
|
||||
```swift
|
||||
var john: Person?
|
||||
var unit4A: Apartment?
|
||||
```
|
||||
|
||||
现在你可以创建特定的 `Person` 和 `Apartment` 实例并将赋值给 `john` 和 `unit4A` 变量:
|
||||
|
||||
```swift
|
||||
john = Person(name: "John Appleseed")
|
||||
unit4A = Apartment(unit: "4A")
|
||||
```
|
||||
|
||||
在两个实例被创建和赋值后,下图表现了强引用的关系。变量 `john` 现在有一个指向 `Person` 实例的强引用,而变量 `unit4A` 有一个指向 `Apartment` 实例的强引用:
|
||||
|
||||

|
||||
|
||||
现在你能够将这两个实例关联在一起,这样人就能有公寓住了,而公寓也有了房客。注意感叹号是用来解包和访问可选变量 `john` 和 `unit4A` 中的实例,这样实例的属性才能被赋值:
|
||||
|
||||
```swift
|
||||
john!.apartment = unit4A
|
||||
unit4A!.tenant = john
|
||||
```
|
||||
|
||||
在将两个实例联系在一起之后,强引用的关系如图所示:
|
||||
|
||||

|
||||
|
||||
不幸的是,这两个实例关联后会产生一个循环强引用。`Person` 实例现在有了一个指向 `Apartment` 实例的强引用,而 `Apartment` 实例也有了一个指向 `Person` 实例的强引用。因此,当你断开 `john` 和 `unit4A` 变量所持有的强引用时,引用计数并不会降为 `0`,实例也不会被 ARC 销毁:
|
||||
|
||||
```swift
|
||||
john = nil
|
||||
unit4A = nil
|
||||
```
|
||||
|
||||
注意,当你把这两个变量设为 `nil` 时,没有任何一个析构器被调用。循环强引用会一直阻止 `Person` 和 `Apartment` 类实例的销毁,这就在你的应用程序中造成了内存泄漏。
|
||||
|
||||
在你将 `john` 和 `unit4A` 赋值为 `nil` 后,强引用关系如下图:
|
||||
|
||||

|
||||
|
||||
`Person` 和 `Apartment` 实例之间的强引用关系保留了下来并且不会被断开。
|
||||
|
||||
## 解决实例之间的循环强引用 {#resolving-strong-reference-cycles-between-class-instances}
|
||||
|
||||
Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题:弱引用(weak reference)和无主引用(unowned reference)。
|
||||
|
||||
弱引用和无主引用允许循环引用中的一个实例引用另一个实例而*不*保持强引用。这样实例能够互相引用而不产生循环强引用。
|
||||
|
||||
当其他的实例有更短的生命周期时,使用弱引用,也就是说,当其他实例析构在先时。在上面公寓的例子中,很显然一个公寓在它的生命周期内会在某个时间段没有它的主人,所以一个弱引用就加在公寓类里面,避免循环引用。相比之下,当其他实例有相同的或者更长生命周期时,请使用无主引用。
|
||||
|
||||
### 弱引用 {#weak-references}
|
||||
|
||||
*弱引用*不会对其引用的实例保持强引用,因而不会阻止 ARC 销毁被引用的实例。这个特性阻止了引用变为循环强引用。声明属性或者变量时,在前面加上 `weak` 关键字表明这是一个弱引用。
|
||||
|
||||
因为弱引用不会保持所引用的实例,即使引用存在,实例也有可能被销毁。因此,ARC 会在引用的实例被销毁后自动将其弱引用赋值为 `nil`。并且因为弱引用需要在运行时允许被赋值为 `nil`,所以它们会被定义为可选类型变量,而不是常量。
|
||||
|
||||
你可以像其他可选值一样,检查弱引用的值是否存在,这样可以避免访问已销毁的实例的引用。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 当 ARC 设置弱引用为 `nil` 时,属性观察不会被触发。
|
||||
|
||||
下面的例子跟上面 `Person` 和 `Apartment` 的例子一致,但是有一个重要的区别。这一次,`Apartment` 的 `tenant` 属性被声明为弱引用:
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
let name: String
|
||||
init(name: String) { self.name = name }
|
||||
var apartment: Apartment?
|
||||
deinit { print("\(name) is being deinitialized") }
|
||||
}
|
||||
|
||||
class Apartment {
|
||||
let unit: String
|
||||
init(unit: String) { self.unit = unit }
|
||||
weak var tenant: Person?
|
||||
deinit { print("Apartment \(unit) is being deinitialized") }
|
||||
}
|
||||
```
|
||||
|
||||
然后跟之前一样,建立两个变量(`john` 和 `unit4A`)之间的强引用,并关联两个实例:
|
||||
|
||||
```swift
|
||||
var john: Person?
|
||||
var unit4A: Apartment?
|
||||
|
||||
john = Person(name: "John Appleseed")
|
||||
unit4A = Apartment(unit: "4A")
|
||||
|
||||
john!.apartment = unit4A
|
||||
unit4A!.tenant = john
|
||||
```
|
||||
|
||||
现在,两个关联在一起的实例的引用关系如下图所示:
|
||||
|
||||

|
||||
|
||||
`Person` 实例依然保持对 `Apartment` 实例的强引用,但是 `Apartment` 实例只持有对 `Person` 实例的弱引用。这意味着当你通过把 `john` 变量赋值为 `nil` 而断开其所保持的强引用时,再也没有指向 `Person` 实例的强引用了:
|
||||
|
||||
```swift
|
||||
john = nil
|
||||
// 打印“John Appleseed is being deinitialized”
|
||||
```
|
||||
|
||||
由于再也没有指向 `Person` 实例的强引用,该实例会被销毁,且 `tenant` 属性会被赋值为 `nil`:
|
||||
|
||||

|
||||
|
||||
唯一剩下的指向 `Apartment` 实例的强引用来自于变量 `unit4A`。如果你断开这个强引用,再也没有指向 `Apartment` 实例的强引用了:
|
||||
|
||||
```swift
|
||||
unit4A = nil
|
||||
// 打印“Apartment 4A is being deinitialized”
|
||||
```
|
||||
|
||||
由于再也没有指向 `Apartment` 实例的强引用,该实例会被销毁:
|
||||
|
||||

|
||||
|
||||
> 注意
|
||||
>
|
||||
> 在使用垃圾收集的系统里,弱指针有时用来实现简单的缓冲机制,因为没有强引用的对象只会在内存压力触发垃圾收集时才被销毁。但是在 ARC 中,一旦值的最后一个强引用被移除,就会被立即销毁,这导致弱引用并不适合上面的用途。
|
||||
|
||||
### 无主引用 {#unowned-references}
|
||||
|
||||
和弱引用类似,*无主引用*不会牢牢保持住引用的实例。和弱引用不同的是,无主引用在其他实例有相同或者更长的生命周期时使用。你可以在声明属性或者变量时,在前面加上关键字 `unowned` 表示这是一个无主引用。
|
||||
|
||||
无主引用通常都被期望拥有值。不过 ARC 无法在实例被销毁后将无主引用设为 `nil`,因为非可选类型的变量不允许被赋值为 `nil`。
|
||||
|
||||
> 重点
|
||||
>
|
||||
> 使用无主引用,你*必须*确保引用始终指向一个未销毁的实例。
|
||||
>
|
||||
> 如果你试图在实例被销毁后,访问该实例的无主引用,会触发运行时错误。
|
||||
|
||||
下面的例子定义了两个类,`Customer` 和 `CreditCard`,模拟了银行客户和客户的信用卡。这两个类中,每一个都将另外一个类的实例作为自身的属性。这种关系可能会造成循环强引用。
|
||||
|
||||
`Customer` 和 `CreditCard` 之间的关系与前面弱引用例子中 `Apartment` 和 `Person` 的关系略微不同。在这个数据模型中,一个客户可能有或者没有信用卡,但是一张信用卡总是关联着一个客户。为了表示这种关系,`Customer` 类有一个可选类型的 `card` 属性,但是 `CreditCard` 类有一个非可选类型的 `customer` 属性。
|
||||
|
||||
此外,只能通过将一个 `number` 值和 `customer` 实例传递给 `CreditCard` 构造器的方式来创建 `CreditCard` 实例。这样可以确保当创建 `CreditCard` 实例时总是有一个 `customer` 实例与之关联。
|
||||
|
||||
由于信用卡总是关联着一个客户,因此将 `customer` 属性定义为无主引用,用以避免循环强引用:
|
||||
|
||||
```swift
|
||||
class Customer {
|
||||
let name: String
|
||||
var card: CreditCard?
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
}
|
||||
deinit { print("\(name) is being deinitialized") }
|
||||
}
|
||||
|
||||
class CreditCard {
|
||||
let number: UInt64
|
||||
unowned let customer: Customer
|
||||
init(number: UInt64, customer: Customer) {
|
||||
self.number = number
|
||||
self.customer = customer
|
||||
}
|
||||
deinit { print("Card #\(number) is being deinitialized") }
|
||||
}
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> `CreditCard` 类的 `number` 属性被定义为 `UInt64` 类型而不是 `Int` 类型,以确保 `number` 属性的存储量在 32 位和 64 位系统上都能足够容纳 16 位的卡号。
|
||||
|
||||
下面的代码片段定义了一个叫 `john` 的可选类型 `Customer` 变量,用来保存某个特定客户的引用。由于是可选类型,所以变量被初始化为 `nil`:
|
||||
|
||||
```swift
|
||||
var john: Customer?
|
||||
```
|
||||
|
||||
现在你可以创建 `Customer` 类的实例,用它初始化 `CreditCard` 实例,并将新创建的 `CreditCard` 实例赋值为客户的 `card` 属性:
|
||||
|
||||
```swift
|
||||
john = Customer(name: "John Appleseed")
|
||||
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
|
||||
```
|
||||
|
||||
在你关联两个实例后,它们的引用关系如下图所示:
|
||||
|
||||

|
||||
|
||||
`Customer` 实例持有对 `CreditCard` 实例的强引用,而 `CreditCard` 实例持有对 `Customer` 实例的无主引用。
|
||||
|
||||
由于 `customer` 的无主引用,当你断开 `john` 变量持有的强引用时,再也没有指向 `Customer` 实例的强引用了:
|
||||
|
||||

|
||||
|
||||
由于再也没有指向 `Customer` 实例的强引用,该实例被销毁了。其后,再也没有指向 `CreditCard` 实例的强引用,该实例也随之被销毁了:
|
||||
|
||||
```swift
|
||||
john = nil
|
||||
// 打印“John Appleseed is being deinitialized”
|
||||
// 打印“Card #1234567890123456 is being deinitialized”
|
||||
```
|
||||
|
||||
最后的代码展示了在 `john` 变量被设为 `nil` 后 `Customer` 实例和 `CreditCard` 实例的析构器都打印出了“销毁”的信息。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 上面的例子展示了如何使用安全的无主引用。对于需要禁用运行时的安全检查的情况(例如,出于性能方面的原因),Swift 还提供了不安全的无主引用。与所有不安全的操作一样,你需要负责检查代码以确保其安全性。
|
||||
> 你可以通过 `unowned(unsafe)` 来声明不安全无主引用。如果你试图在实例被销毁后,访问该实例的不安全无主引用,你的程序会尝试访问该实例之前所在的内存地址,这是一个不安全的操作。
|
||||
|
||||
### 无主引用和隐式解包可选值属性 {#unowned-references-and-implicitly-unwrapped-optional-properties}
|
||||
|
||||
上面弱引用和无主引用的例子涵盖了两种常用的需要打破循环强引用的场景。
|
||||
|
||||
`Person` 和 `Apartment` 的例子展示了两个属性的值都允许为 `nil`,并会潜在的产生循环强引用。这种场景最适合用弱引用来解决。
|
||||
|
||||
`Customer` 和 `CreditCard` 的例子展示了一个属性的值允许为 `nil`,而另一个属性的值不允许为 `nil`,这也可能会产生循环强引用。这种场景最适合通过无主引用来解决。
|
||||
|
||||
然而,存在着第三种场景,在这种场景中,两个属性都必须有值,并且初始化完成后永远不会为 `nil`。在这种场景中,需要一个类使用无主属性,而另外一个类使用隐式解包可选值属性。
|
||||
|
||||
这使两个属性在初始化完成后能被直接访问(不需要可选解包),同时避免了循环引用。这一节将为你展示如何建立这种关系。
|
||||
|
||||
下面的例子定义了两个类,`Country` 和 `City`,每个类将另外一个类的实例保存为属性。在这个模型中,每个国家必须有首都,每个城市必须属于一个国家。为了实现这种关系,`Country` 类拥有一个 `capitalCity` 属性,而 `City` 类有一个 `country` 属性:
|
||||
|
||||
```swift
|
||||
class Country {
|
||||
let name: String
|
||||
var capitalCity: City!
|
||||
init(name: String, capitalName: String) {
|
||||
self.name = name
|
||||
self.capitalCity = City(name: capitalName, country: self)
|
||||
}
|
||||
}
|
||||
|
||||
class City {
|
||||
let name: String
|
||||
unowned let country: Country
|
||||
init(name: String, country: Country) {
|
||||
self.name = name
|
||||
self.country = country
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
为了建立两个类的依赖关系,`City` 的构造器接受一个 `Country` 实例作为参数,并且将实例保存到 `country` 属性。
|
||||
|
||||
`Country` 的构造器调用了 `City` 的构造器。然而,只有 `Country` 的实例完全初始化后,`Country` 的构造器才能把 `self` 传给 `City` 的构造器。在 [两段式构造过程](./14_Initialization.md#two-phase-initialization) 中有具体描述。
|
||||
|
||||
为了满足这种需求,通过在类型结尾处加上感叹号(`City!`)的方式,将 `Country` 的 `capitalCity` 属性声明为隐式解包可选值类型的属性。这意味着像其他可选类型一样,`capitalCity` 属性的默认值为 `nil`,但是不需要解包它的值就能访问它。在 [隐式解包可选值](./01_The_Basics.md#implicityly-unwrapped-optionals) 中有描述。
|
||||
|
||||
由于 `capitalCity` 默认值为 `nil`,一旦 `Country` 的实例在构造器中给 `name` 属性赋值后,整个初始化过程就完成了。这意味着一旦 `name` 属性被赋值后,`Country` 的构造器就能引用并传递隐式的 `self`。`Country` 的构造器在赋值 `capitalCity` 时,就能将 `self` 作为参数传递给 `City` 的构造器。
|
||||
|
||||
上述的意义在于你可以通过一条语句同时创建 `Country` 和 `City` 的实例,而不产生循环强引用,并且 `capitalCity` 的属性能被直接访问,而不需要通过感叹号来解包它的可选值:
|
||||
|
||||
```swift
|
||||
var country = Country(name: "Canada", capitalName: "Ottawa")
|
||||
print("\(country.name)'s capital city is called \(country.capitalCity.name)")
|
||||
// 打印“Canada's capital city is called Ottawa”
|
||||
```
|
||||
|
||||
在上面的例子中,使用隐式解包可选值值意味着满足了类的构造器的两个构造阶段的要求。`capitalCity` 属性在初始化完成后,能像非可选值一样使用和存取,同时还避免了循环强引用。
|
||||
|
||||
## 闭包的循环强引用 {#strong-reference-cycles-for-closures}
|
||||
|
||||
前面我们看到了循环强引用是在两个类实例属性互相保持对方的强引用时产生的,还知道了如何用弱引用和无主引用来打破这些循环强引用。
|
||||
|
||||
循环强引用还会发生在当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了这个类实例时。这个闭包体中可能访问了实例的某个属性,例如 `self.someProperty`,或者闭包中调用了实例的某个方法,例如 `self.someMethod()`。这两种情况都导致了闭包“捕获”`self`,从而产生了循环强引用。
|
||||
|
||||
循环强引用的产生,是因为闭包和类相似,都是引用类型。当你把一个闭包赋值给某个属性时,你是将这个闭包的引用赋值给了属性。实质上,这跟之前的问题是一样的——两个强引用让彼此一直有效。但是,和两个类实例不同,这次一个是类实例,另一个是闭包。
|
||||
|
||||
Swift 提供了一种优雅的方法来解决这个问题,称之为 `闭包捕获列表`(closure capture list)。同样的,在学习如何用闭包捕获列表打破循环强引用之前,先来了解一下这里的循环强引用是如何产生的,这对我们很有帮助。
|
||||
|
||||
下面的例子为你展示了当一个闭包引用了 `self` 后是如何产生一个循环强引用的。例子中定义了一个叫 `HTMLElement` 的类,用一种简单的模型表示 HTML 文档中的一个单独的元素:
|
||||
|
||||
```swift
|
||||
class HTMLElement {
|
||||
|
||||
let name: String
|
||||
let text: String?
|
||||
|
||||
lazy var asHTML: () -> String = {
|
||||
if let text = self.text {
|
||||
return "<\(self.name)>\(text)</\(self.name)>"
|
||||
} else {
|
||||
return "<\(self.name) />"
|
||||
}
|
||||
}
|
||||
|
||||
init(name: String, text: String? = nil) {
|
||||
self.name = name
|
||||
self.text = text
|
||||
}
|
||||
|
||||
deinit {
|
||||
print("\(name) is being deinitialized")
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
`HTMLElement` 类定义了一个 `name` 属性来表示这个元素的名称,例如代表头部元素的 `"h1"`,代表段落的 `"p"`,或者代表换行的 `"br"`。`HTMLElement` 还定义了一个可选属性 `text`,用来设置 HTML 元素呈现的文本。
|
||||
|
||||
除了上面的两个属性,`HTMLElement` 还定义了一个 `lazy` 属性 `asHTML`。这个属性引用了一个将 `name` 和 `text` 组合成 HTML 字符串片段的闭包。该属性是 `Void -> String` 类型,或者可以理解为“一个没有参数,返回 `String` 的函数”。
|
||||
|
||||
默认情况下,闭包赋值给了 `asHTML` 属性,这个闭包返回一个代表 HTML 标签的字符串。如果 `text` 值存在,该标签就包含可选值 `text`;如果 `text` 不存在,该标签就不包含文本。对于段落元素,根据 `text` 是 `"some text"` 还是 `nil`,闭包会返回 `"<p>some text</p>"` 或者 `"<p />"`。
|
||||
|
||||
可以像实例方法那样去命名、使用 `asHTML` 属性。然而,由于 `asHTML` 是闭包而不是实例方法,如果你想改变特定 HTML 元素的处理方式的话,可以用自定义的闭包来取代默认值。
|
||||
|
||||
例如,可以将一个闭包赋值给 `asHTML` 属性,这个闭包能在 `text` 属性是 `nil` 时使用默认文本,这是为了避免返回一个空的 HTML 标签:
|
||||
|
||||
```swift
|
||||
let heading = HTMLElement(name: "h1")
|
||||
let defaultText = "some default text"
|
||||
heading.asHTML = {
|
||||
return "<\(heading.name)>\(heading.text ?? defaultText)</\(heading.name)>"
|
||||
}
|
||||
print(heading.asHTML())
|
||||
// 打印“<h1>some default text</h1>”
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> `asHTML` 声明为 `lazy` 属性,因为只有当元素确实需要被处理为 HTML 输出的字符串时,才需要使用 `asHTML`。也就是说,在默认的闭包中可以使用 `self`,因为只有当初始化完成以及 `self` 确实存在后,才能访问 `lazy` 属性。
|
||||
|
||||
`HTMLElement` 类只提供了一个构造器,通过 `name` 和 `text`(如果有的话)参数来初始化一个新元素。该类也定义了一个析构器,当 `HTMLElement` 实例被销毁时,打印一条消息。
|
||||
|
||||
下面的代码展示了如何用 `HTMLElement` 类创建实例并打印消息:
|
||||
|
||||
```swift
|
||||
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
|
||||
print(paragraph!.asHTML())
|
||||
// 打印“<p>hello, world</p>”
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 上面的 `paragraph` 变量定义为可选类型的 `HTMLElement`,因此我们可以赋值 `nil` 给它来演示循环强引用。
|
||||
|
||||
不幸的是,上面写的 `HTMLElement` 类产生了类实例和作为 `asHTML` 默认值的闭包之间的循环强引用。循环强引用如下图所示:
|
||||
|
||||

|
||||
|
||||
实例的 `asHTML` 属性持有闭包的强引用。但是,闭包在其闭包体内使用了 `self`(引用了 `self.name` 和 `self.text`),因此闭包捕获了 `self`,这意味着闭包又反过来持有了 `HTMLElement` 实例的强引用。这样两个对象就产生了循环强引用。(更多关于闭包捕获值的信息,请参考 [值捕获](./07_Closures.md#capturing-values))。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 虽然闭包多次使用了 `self`,它只捕获 `HTMLElement` 实例的一个强引用。
|
||||
|
||||
如果设置 `paragraph` 变量为 `nil`,打破它持有的 `HTMLElement` 实例的强引用,`HTMLElement` 实例和它的闭包都不会被销毁,也是因为循环强引用:
|
||||
|
||||
```swift
|
||||
paragraph = nil
|
||||
```
|
||||
|
||||
注意,`HTMLElement` 的析构器中的消息并没有被打印,证明了 `HTMLElement` 实例并没有被销毁。
|
||||
|
||||
## 解决闭包的循环强引用 {#resolving-strong-reference-cycles-for-closures}
|
||||
|
||||
在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。捕获列表定义了闭包体内捕获一个或者多个引用类型的规则。跟解决两个类实例间的循环强引用一样,声明每个捕获的引用为弱引用或无主引用,而不是强引用。应当根据代码关系来决定使用弱引用还是无主引用。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> Swift 有如下要求:只要在闭包内使用 `self` 的成员,就要用 `self.someProperty` 或者 `self.someMethod()`(而不只是 `someProperty` 或 `someMethod()`)。这提醒你可能会一不小心就捕获了 `self`。
|
||||
|
||||
### 定义捕获列表 {#defining-a-capture-list}
|
||||
|
||||
捕获列表中的每一项都由一对元素组成,一个元素是 `weak` 或 `unowned` 关键字,另一个元素是类实例的引用(例如 `self`)或初始化过的变量(如 `delegate = self.delegate`)。这些项在方括号中用逗号分开。
|
||||
|
||||
如果闭包有参数列表和返回类型,把捕获列表放在它们前面:
|
||||
|
||||
```swift
|
||||
lazy var someClosure = {
|
||||
[unowned self, weak delegate = self.delegate]
|
||||
(index: Int, stringToProcess: String) -> String in
|
||||
// 这里是闭包的函数体
|
||||
}
|
||||
```
|
||||
|
||||
如果闭包没有指明参数列表或者返回类型,它们会通过上下文推断,那么可以把捕获列表和关键字 `in` 放在闭包最开始的地方:
|
||||
|
||||
```swift
|
||||
lazy var someClosure = {
|
||||
[unowned self, weak delegate = self.delegate] in
|
||||
// 这里是闭包的函数体
|
||||
}
|
||||
```
|
||||
|
||||
### 弱引用和无主引用 {#weak-and-unowned-references}
|
||||
|
||||
在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为 `无主引用`。
|
||||
|
||||
相反的,在被捕获的引用可能会变为 `nil` 时,将闭包内的捕获定义为 `弱引用`。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为 `nil`。这使我们可以在闭包体内检查它们是否存在。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果被捕获的引用绝对不会变为 `nil`,应该用无主引用,而不是弱引用。
|
||||
|
||||
前面的 `HTMLElement` 例子中,无主引用是正确的解决循环强引用的方法。这样编写 `HTMLElement` 类来避免循环强引用:
|
||||
|
||||
```swift
|
||||
class HTMLElement {
|
||||
|
||||
let name: String
|
||||
let text: String?
|
||||
|
||||
lazy var asHTML: () -> String = {
|
||||
[unowned self] in
|
||||
if let text = self.text {
|
||||
return "<\(self.name)>\(text)</\(self.name)>"
|
||||
} else {
|
||||
return "<\(self.name) />"
|
||||
}
|
||||
}
|
||||
|
||||
init(name: String, text: String? = nil) {
|
||||
self.name = name
|
||||
self.text = text
|
||||
}
|
||||
|
||||
deinit {
|
||||
print("\(name) is being deinitialized")
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
上面的 `HTMLElement` 实现和之前的实现一致,除了在 `asHTML` 闭包中多了一个捕获列表。这里,捕获列表是 `[unowned self]`,表示“将 `self` 捕获为无主引用而不是强引用”。
|
||||
|
||||
和之前一样,我们可以创建并打印 `HTMLElement` 实例:
|
||||
|
||||
```swift
|
||||
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
|
||||
print(paragraph!.asHTML())
|
||||
// 打印“<p>hello, world</p>”
|
||||
```
|
||||
|
||||
使用捕获列表后引用关系如下图所示:
|
||||
|
||||

|
||||
|
||||
这一次,闭包以无主引用的形式捕获 `self`,并不会持有 `HTMLElement` 实例的强引用。如果将 `paragraph` 赋值为 `nil`,`HTMLElement` 实例将会被销毁,并能看到它的析构器打印出的消息:
|
||||
|
||||
```swift
|
||||
paragraph = nil
|
||||
// 打印“p is being deinitialized”
|
||||
```
|
||||
|
||||
你可以查看 [捕获列表](../03_language_reference/04_Expressions.md) 章节,获取更多关于捕获列表的信息。
|
||||
200
source/02_language_guide/25_Memory_Safety.md
Normal file
200
source/02_language_guide/25_Memory_Safety.md
Normal file
@ -0,0 +1,200 @@
|
||||
# 内存安全
|
||||
|
||||
默认情况下,Swift 会阻止你代码里不安全的行为。例如,Swift 会保证变量在使用之前就完成初始化,在内存被回收之后就无法被访问,并且数组的索引会做越界检查。
|
||||
|
||||
Swift 也保证同时访问同一块内存时不会冲突,通过约束代码里对于存储地址的写操作,去获取那一块内存的访问独占权。因为 Swift 自动管理内存,所以大部分时候你完全不需要考虑内存访问的事情。然而,理解潜在的冲突也是很重要的,可以避免你写出访问冲突的代码。而如果你的代码确实存在冲突,那在编译时或者运行时就会得到错误。
|
||||
|
||||
## 理解内存访问冲突 {#understanding-conflicting-access-to-memory}
|
||||
|
||||
内存的访问,会发生在你给变量赋值,或者传递参数给函数时。例如,下面的代码就包含了读和写的访问:
|
||||
|
||||
```swift
|
||||
// 向 one 所在的内存区域发起一次写操作
|
||||
var one = 1
|
||||
|
||||
// 向 one 所在的内存区域发起一次读操作
|
||||
print("We're number \(one)!")
|
||||
```
|
||||
|
||||
内存访问的冲突会发生在你的代码尝试同时访问同一个存储地址的时侯。同一个存储地址的多个访问同时发生会造成不可预计或不一致的行为。在 Swift 里,有很多修改值的行为都会持续好几行代码,在修改值的过程中进行访问是有可能发生的。
|
||||
|
||||
你可以思考一下预算表更新的过程,会看到同样的问题。更新预算表总共有两步:首先你把预算项的名字和费用加上,然后再更新总数来反映预算表的现况。在更新之前和之后,你都可以从预算表里读取任何信息并获得正确的答案,就像下面展示的那样。
|
||||
|
||||

|
||||
|
||||
而当你添加预算项进入表里的时候,它只是在一个临时的,错误的状态,因为总数还没有被更新。在添加数据的过程中读取总数就会读取到错误的信息。
|
||||
|
||||
这个例子也演示了你在修复内存访问冲突时会遇到的问题:有时修复的方式会有很多种,但哪一种是正确的就不总是那么明显了。在这个例子里,根据你是否需要更新后的总数,$5 和 $320 都可能是正确的值。在你修复访问冲突之前,你需要决定它的倾向。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 如果你写过并发和多线程的代码,内存访问冲突也许是同样的问题。然而,这里访问冲突的讨论是在单线程的情境下讨论的,并没有使用并发或者多线程。
|
||||
>
|
||||
> 如果你曾经在单线程代码里有访问冲突,Swift 可以保证你在编译或者运行时会得到错误。对于多线程的代码,可以使用 [Thread Sanitizer](https://developer.apple.com/documentation/xcode/diagnosing_memory_thread_and_crash_issues_early) 去帮助检测多线程的冲突。
|
||||
|
||||
### 内存访问性质 {#characteristics-of-memory-access}
|
||||
|
||||
内存访问冲突时,要考虑内存访问上下文中的这三个性质:访问是读还是写,访问的时长,以及被访问的存储地址。特别是,冲突会发生在当你有两个访问符合下列的情况:
|
||||
|
||||
* 至少有一个是写访问
|
||||
* 它们访问的是同一个存储地址
|
||||
* 它们的访问在时间线上部分重叠
|
||||
|
||||
读和写访问的区别很明显:一个写访问会改变存储地址,而读操作不会。存储地址是指向正在访问的东西(例如一个变量,常量或者属性)的位置的值 。内存访问的时长要么是瞬时的,要么是长期的。
|
||||
|
||||
如果一个访问不可能在其访问期间被其它代码访问,那么就是一个瞬时访问。正常来说,两个瞬时访问是不可能同时发生的。大多数内存访问都是瞬时的。例如,下面列举的所有读和写访问都是瞬时的:
|
||||
|
||||
```swift
|
||||
func oneMore(than number: Int) -> Int {
|
||||
return number + 1
|
||||
}
|
||||
|
||||
var myNumber = 1
|
||||
myNumber = oneMore(than: myNumber)
|
||||
print(myNumber)
|
||||
// 打印“2”
|
||||
```
|
||||
|
||||
然而,有几种被称为长期访问的内存访问方式,会在别的代码执行时持续进行。瞬时访问和长期访问的区别在于别的代码有没有可能在访问期间同时访问,也就是在时间线上的重叠。一个长期访问可以被别的长期访问或瞬时访问重叠。
|
||||
|
||||
重叠的访问主要出现在使用 in-out 参数的函数和方法或者结构体的 mutating 方法里。Swift 代码里典型的长期访问会在后面进行讨论。
|
||||
|
||||
## In-Out 参数的访问冲突 {#conflicting-access-to-in-out-parameters}
|
||||
|
||||
一个函数会对它所有的 in-out 参数进行长期写访问。in-out 参数的写访问会在所有非 in-out 参数处理完之后开始,直到函数执行完毕为止。如果有多个 in-out 参数,则写访问开始的顺序与参数的顺序一致。
|
||||
|
||||
长期访问的存在会造成一个结果,你不能在访问以 in-out 形式传入后的原变量,即使作用域原则和访问权限允许——任何访问原变量的行为都会造成冲突。例如:
|
||||
|
||||
```swift
|
||||
var stepSize = 1
|
||||
|
||||
func increment(_ number: inout Int) {
|
||||
number += stepSize
|
||||
}
|
||||
|
||||
increment(&stepSize)
|
||||
// 错误:stepSize 访问冲突
|
||||
```
|
||||
|
||||
在上面的代码里,`stepSize` 是一个全局变量,并且它可以在 `increment(_:)` 里正常访问。然而,对于 `stepSize` 的读访问与 `number` 的写访问重叠了。就像下面展示的那样,`number` 和 `stepSize` 都指向了同一个存储地址。同一块内存的读和写访问重叠了,就此产生了冲突。
|
||||
|
||||

|
||||
|
||||
解决这个冲突的一种方式,是显示拷贝一份 `stepSize` :
|
||||
|
||||
```swift
|
||||
// 显式拷贝
|
||||
var copyOfStepSize = stepSize
|
||||
increment(©OfStepSize)
|
||||
|
||||
// 更新原来的值
|
||||
stepSize = copyOfStepSize
|
||||
// stepSize 现在的值是 2
|
||||
```
|
||||
|
||||
当你在调用 `increment(_:)` 之前做一份拷贝,显然 `copyOfStepSize` 就会根据当前的 `stepSize` 增加。读访问在写操作之前就已经结束了,所以不会有冲突。
|
||||
|
||||
长期写访问的存在还会造成另一种结果,往同一个函数的多个 in-out 参数里传入同一个变量也会产生冲突,例如:
|
||||
|
||||
```swift
|
||||
func balance(_ x: inout Int, _ y: inout Int) {
|
||||
let sum = x + y
|
||||
x = sum / 2
|
||||
y = sum - x
|
||||
}
|
||||
var playerOneScore = 42
|
||||
var playerTwoScore = 30
|
||||
balance(&playerOneScore, &playerTwoScore) // 正常
|
||||
balance(&playerOneScore, &playerOneScore)
|
||||
// 错误:playerOneScore 访问冲突
|
||||
```
|
||||
|
||||
上面的 `balance(_:_:)` 函数会将传入的两个参数平均化。将 `playerOneScore` 和 `playerTwoScore` 作为参数传入不会产生错误 —— 有两个访问重叠了,但它们访问的是不同的内存位置。相反,将 `playerOneScore` 作为参数同时传入就会产生冲突,因为它会发起两个写访问,同时访问同一个的存储地址。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 因为操作符也是函数,它们也会对 in-out 参数进行长期访问。例如,假设 `balance(_:_:)` 是一个名为 `<^>` 的操作符函数,那么 `playerOneScore <^> playerOneScore` 也会造成像 `balance(&playerOneScore, &playerOneScore)` 一样的冲突。
|
||||
|
||||
## 方法里 self 的访问冲突 {#conflicting-access-to-self-in-methods}
|
||||
|
||||
一个结构体的 mutating 方法会在调用期间对 `self` 进行写访问。例如,想象一下这么一个游戏,每一个玩家都有血量,受攻击时血量会下降,并且有敌人的数量,使用特殊技能时会减少敌人数量。
|
||||
|
||||
```swift
|
||||
struct Player {
|
||||
var name: String
|
||||
var health: Int
|
||||
var energy: Int
|
||||
|
||||
static let maxHealth = 10
|
||||
mutating func restoreHealth() {
|
||||
health = Player.maxHealth
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在上面的 `restoreHealth()` 方法里,一个对于 `self` 的写访问会从方法开始直到方法 return。在这种情况下,`restoreHealth()` 里的其它代码不可以对 `Player` 实例的属性发起重叠的访问。下面的 `shareHealth(with:)` 方法接受另一个 `Player` 的实例作为 in-out 参数,产生了访问重叠的可能性。
|
||||
|
||||
```swift
|
||||
extension Player {
|
||||
mutating func shareHealth(with teammate: inout Player) {
|
||||
balance(&teammate.health, &health)
|
||||
}
|
||||
}
|
||||
|
||||
var oscar = Player(name: "Oscar", health: 10, energy: 10)
|
||||
var maria = Player(name: "Maria", health: 5, energy: 10)
|
||||
oscar.shareHealth(with: &maria) // 正常
|
||||
```
|
||||
|
||||
上面的例子里,调用 `shareHealth(with:)` 方法去把 `oscar` 玩家的血量分享给 `maria` 玩家并不会造成冲突。在方法调用期间会对 `oscar` 发起写访问,因为在 mutating 方法里 `self` 就是 `oscar`,同时对于 `maria` 也会发起写访问,因为 `maria` 作为 in-out 参数传入。过程如下,它们会访问内存的不同位置。即使两个写访问重叠了,它们也不会冲突。
|
||||
|
||||

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

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

|
||||
|
||||
按位取反运算符是一个前缀运算符,直接放在运算数之前,并且它们之间不能添加任何空格:
|
||||
|
||||
```Swift
|
||||
let initialBits: UInt8 = 0b00001111
|
||||
let invertedBits = ~initialBits // 等于 0b11110000
|
||||
```
|
||||
|
||||
`UInt8` 类型的整数有 8 个比特位,可以存储 `0 ~ 255` 之间的任意整数。这个例子初始化了一个 `UInt8` 类型的整数,并赋值为二进制的 `00001111`,它的前 4 位为 `0`,后 4 位为 `1`。这个值等价于十进制的 `15`。
|
||||
|
||||
接着使用按位取反运算符创建了一个名为 `invertedBits` 的常量,这个常量的值与全部位取反后的 `initialBits` 相等。即所有的 `0` 都变成了 `1`,同时所有的 `1` 都变成 `0`。`invertedBits` 的二进制值为 `11110000`,等价于无符号十进制数的 `240`。
|
||||
|
||||
### Bitwise AND Operator(按位与运算符) {#bitwise-and-operator}
|
||||
|
||||
*按位与运算符(`&`)* 对两个数的比特位进行合并。它返回一个新的数,只有当两个数的对应位*都*为 `1` 的时候,新数的对应位才为 `1`:
|
||||
|
||||

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

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

|
||||
|
||||
在下面的示例当中,`firstBits` 和 `otherBits` 都有一个自己为 `1`,而对方为 `0` 的位。按位异或运算符将新数的这两个位都设置为 `1`。在其余的位上 `firstBits` 和 `otherBits` 是相同的,所以设置为 `0`:
|
||||
|
||||
```Swift
|
||||
let firstBits: UInt8 = 0b00010100
|
||||
let otherBits: UInt8 = 0b00000101
|
||||
let outputBits = firstBits ^ otherBits // 等于 00010001
|
||||
```
|
||||
|
||||
### Bitwise Left and Right Shift Operators(按位左移、右移运算符) {#bitwise-left-and-right-shift-operators}
|
||||
|
||||
*按位左移运算符(`<<`)* 和 *按位右移运算符(`>>`)*可以对一个数的所有位进行指定位数的左移和右移,但是需要遵守下面定义的规则。
|
||||
|
||||
对一个数进行按位左移或按位右移,相当于对这个数进行乘以 2 或除以 2 的运算。将一个整数左移一位,等价于将这个数乘以 2,同样地,将一个整数右移一位,等价于将这个数除以 2。
|
||||
|
||||
#### 无符号整数的移位运算 {#shifting-behavior-for-unsigned-integers}
|
||||
|
||||
对无符号整数进行移位的规则如下:
|
||||
|
||||
1. 已存在的位按指定的位数进行左移和右移。
|
||||
2. 任何因移动而超出整型存储范围的位都会被丢弃。
|
||||
3. 用 `0` 来填充移位后产生的空白位。
|
||||
|
||||
这种方法称为*逻辑移位*。
|
||||
|
||||
以下这张图展示了 `11111111 << 1`(即把 `11111111` 向左移动 `1` 位),和 `11111111 >> 1`(即把 `11111111` 向右移动 `1` 位)的结果。蓝色的数字是被移位的,灰色的数字是被抛弃的,橙色的 `0` 则是被填充进来的:
|
||||
|
||||

|
||||
|
||||
下面的代码演示了 Swift 中的移位运算:
|
||||
|
||||
```Swift
|
||||
let shiftBits: UInt8 = 4 // 即二进制的 00000100
|
||||
shiftBits << 1 // 00001000
|
||||
shiftBits << 2 // 00010000
|
||||
shiftBits << 5 // 10000000
|
||||
shiftBits << 6 // 00000000
|
||||
shiftBits >> 2 // 00000001
|
||||
```
|
||||
|
||||
可以使用移位运算对其他的数据类型进行编码和解码:
|
||||
|
||||
```Swift
|
||||
let pink: UInt32 = 0xCC6699
|
||||
let redComponent = (pink & 0xFF0000) >> 16 // redComponent 是 0xCC,即 204
|
||||
let greenComponent = (pink & 0x00FF00) >> 8 // greenComponent 是 0x66, 即 102
|
||||
let blueComponent = pink & 0x0000FF // blueComponent 是 0x99,即 153
|
||||
```
|
||||
|
||||
这个示例使用了一个命名为 `pink` 的 `UInt32` 型常量来存储 Cascading Style Sheets(CSS)中粉色的颜色值。该 CSS 的颜色值 `#CC6699`,在 Swift 中表示为十六进制的 `0xCC6699`。然后利用按位与运算符(`&`)和按位右移运算符(`>>`)从这个颜色值中分解出红(`CC`)、绿(`66`)以及蓝(`99`)三个部分。
|
||||
|
||||
红色部分是通过对 `0xCC6699` 和 `0xFF0000` 进行按位与运算后得到的。`0xFF0000` 中的 `0` 部分“掩盖”了 `OxCC6699` 中的第二、第三个字节,使得数值中的 `6699` 被忽略,只留下 `0xCC0000`。
|
||||
|
||||
然后,将这个数向右移动 16 位(`>> 16`)。十六进制中每两个字符占用 8 个比特位,所以移动 16 位后 `0xCC0000` 就变为 `0x0000CC`。这个数和 `0xCC` 是等同的,也就是十进制数值的 `204`。
|
||||
|
||||
同样的,绿色部分通过对 `0xCC6699` 和 `0x00FF00` 进行按位与运算得到 `0x006600`。然后将这个数向右移动 8 位,得到 `0x66`,也就是十进制数值的 `102`。
|
||||
|
||||
最后,蓝色部分通过对 `0xCC6699` 和 `0x0000FF` 进行按位与运算得到 `0x000099`。这里不需要再向右移位,而 `0x000099` 也就是 `0x99` ,也就是十进制数值的 `153`。
|
||||
|
||||
#### 有符号整数的移位运算 {#shifting-behavior-for-signed-integers}
|
||||
|
||||
对比无符号整数,有符号整数的移位运算相对复杂得多,这种复杂性源于有符号整数的二进制表现形式。(为了简单起见,以下的示例都是基于 8 比特的有符号整数,但是其中的原理对任何位数的有符号整数都是通用的。)
|
||||
|
||||
有符号整数使用第 1 个比特位(通常被称为*符号位*)来表示这个数的正负。符号位为 `0` 代表正数,为 `1` 代表负数。
|
||||
|
||||
其余的比特位(通常被称为*数值位*)存储了实际的值。有符号正整数和无符号数的存储方式是一样的,都是从 `0` 开始算起。这是值为 `4` 的 `Int8` 型整数的二进制位表现形式:
|
||||
|
||||

|
||||
|
||||
符号位为 `0`(代表这是一个“正数”),另外 7 位则代表了十进制数值 `4` 的二进制表示。
|
||||
|
||||
负数的存储方式略有不同。它存储 `2` 的 `n` 次方减去其实际值的绝对值,这里的 `n` 是数值位的位数。一个 8 比特位的数有 7 个比特位是数值位,所以是 `2` 的 `7` 次方,即 `128`。
|
||||
|
||||
这是值为 `-4` 的 `Int8` 型整数的二进制表现形式:
|
||||
|
||||

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

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

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

|
||||
|
||||
这个行为可以确保有符号整数的符号位不会因为右移运算而改变,这通常被称为*算术移位*。
|
||||
|
||||
由于正数和负数的特殊存储方式,在对它们进行右移的时候,会使它们越来越接近 `0`。在移位的过程中保持符号位不变,意味着负整数在接近 `0` 的过程中会一直保持为负。
|
||||
|
||||
## 溢出运算符 {#overflow-operators}
|
||||
|
||||
当向一个整数类型的常量或者变量赋予超过它容量的值时,Swift 默认会报错,而不是允许生成一个无效的数。这个行为为我们在运算过大或者过小的数时提供了额外的安全性。
|
||||
|
||||
例如,`Int16` 型整数能容纳的有符号整数范围是 `-32768` 到 `32767`。当为一个 `Int16` 类型的变量或常量赋予的值超过这个范围时,系统就会报错:
|
||||
|
||||
```Swift
|
||||
var potentialOverflow = Int16.max
|
||||
// potentialOverflow 的值是 32767,这是 Int16 能容纳的最大整数
|
||||
potentialOverflow += 1
|
||||
// 这里会报错
|
||||
```
|
||||
|
||||
在赋值时为过大或者过小的情况提供错误处理,能让我们在处理边界值时更加灵活。
|
||||
|
||||
然而,当你希望的时候也可以选择让系统在数值溢出的时候采取截断处理,而非报错。Swift 提供的三个*溢出运算符*来让系统支持整数溢出运算。这些运算符都是以 `&` 开头的:
|
||||
|
||||
* 溢出加法 `&+`
|
||||
* 溢出减法 `&-`
|
||||
* 溢出乘法 `&*`
|
||||
|
||||
### 数值溢出 {#value-overflow}
|
||||
|
||||
数值有可能出现上溢或者下溢。
|
||||
|
||||
这个示例演示了当我们对一个无符号整数使用溢出加法(`&+`)进行上溢运算时会发生什么:
|
||||
|
||||
```Swift
|
||||
var unsignedOverflow = UInt8.max
|
||||
// unsignedOverflow 等于 UInt8 所能容纳的最大整数 255
|
||||
unsignedOverflow = unsignedOverflow &+ 1
|
||||
// 此时 unsignedOverflow 等于 0
|
||||
```
|
||||
|
||||
`unsignedOverflow` 被初始化为 `UInt8` 所能容纳的最大整数(`255`,以二进制表示即 `11111111`)。然后使用溢出加法运算符(`&+`)对其进行加 `1` 运算。这使得它的二进制表示正好超出 `UInt8` 所能容纳的位数,也就导致了数值的溢出,如下图所示。数值溢出后,仍然留在 `UInt8` 边界内的值是 `00000000`,也就是十进制数值的 `0`。
|
||||
|
||||

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

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

|
||||
|
||||
对于无符号与有符号整型数值来说,当出现上溢时,它们会从数值所能容纳的最大数变成最小数。同样地,当发生下溢时,它们会从所能容纳的最小数变成最大数。
|
||||
|
||||
## 优先级和结合性 {#precedence-and-associativity}
|
||||
|
||||
运算符的*优先级*使得一些运算符优先于其他运算符;它们会先被执行。
|
||||
|
||||
*结合性*定义了相同优先级的运算符是如何结合的,也就是说,是与左边结合为一组,还是与右边结合为一组。可以将其理解为“它们是与左边的表达式结合的”,或者“它们是与右边的表达式结合的”。
|
||||
|
||||
当考虑一个复合表达式的计算顺序时,运算符的优先级和结合性是非常重要的。举例来说,运算符优先级解释了为什么下面这个表达式的运算结果会是 `17`。
|
||||
|
||||
```Swift
|
||||
2 + 3 % 4 * 5
|
||||
// 结果是 17
|
||||
```
|
||||
|
||||
如果你直接从左到右进行运算,你可能认为运算的过程是这样的:
|
||||
|
||||
- 2 + 3 = 5
|
||||
- 5 % 4 = 1
|
||||
- 1 * 5 = 5
|
||||
|
||||
但是正确答案是 `17` 而不是 `5`。优先级高的运算符要先于优先级低的运算符进行计算。与 C 语言类似,在 Swift 中,乘法运算符(`*`)与取余运算符(`%`)的优先级高于加法运算符(`+`)。因此,它们的计算顺序要先于加法运算。
|
||||
|
||||
而乘法运算与取余运算的优先级*相同*。这时为了得到正确的运算顺序,还需要考虑结合性。乘法运算与取余运算都是左结合的。可以将这考虑成,从它们的左边开始为这两部分表达式都隐式地加上括号:
|
||||
|
||||
```Swift
|
||||
2 + ((3 % 4) * 5)
|
||||
```
|
||||
|
||||
`(3 % 4)` 等于 `3`,所以表达式相当于:
|
||||
|
||||
```Swift
|
||||
2 + (3 * 5)
|
||||
```
|
||||
|
||||
`3 * 5` 等于 `15`,所以表达式相当于:
|
||||
|
||||
```Swift
|
||||
2 + 15
|
||||
```
|
||||
|
||||
因此计算结果为 `17`。
|
||||
|
||||
有关 Swift 标准库提供的操作符信息,包括操作符优先级组和结合性设置的完整列表,请参见 [操作符声明](https://developer.apple.com/documentation/swift/operator_declarations)。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 相对 C 语言和 Objective-C 来说,Swift 的运算符优先级和结合性规则更加简洁和可预测。但是,这也意味着它们相较于 C 语言及其衍生语言并不是完全一致。在对现有的代码进行移植的时候,要注意确保运算符的行为仍然符合你的预期。
|
||||
|
||||
## 运算符函数 {#operator-functions}
|
||||
|
||||
类和结构体可以为现有的运算符提供自定义的实现。这通常被称为运算符*重载*。
|
||||
|
||||
下面的例子展示了如何让自定义的结构体支持加法运算符(`+`)。算术加法运算符是一个*二元运算符*,因为它是对两个值进行运算,同时它还可以称为*中缀*运算符,因为它出现在两个值中间。
|
||||
|
||||
例子中定义了一个名为 `Vector2D` 的结构体用来表示二维坐标向量 `(x, y)`,紧接着定义了一个可以将两个 `Vector2D` 结构体实例进行相加的*运算符函数*:
|
||||
|
||||
```Swift
|
||||
struct Vector2D {
|
||||
var x = 0.0, y = 0.0
|
||||
}
|
||||
|
||||
extension Vector2D {
|
||||
static func + (left: Vector2D, right: Vector2D) -> Vector2D {
|
||||
return Vector2D(x: left.x + right.x, y: left.y + right.y)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
该运算符函数被定义为 `Vector2D` 上的一个类方法,并且函数的名字与它要进行重载的 `+` 名字一致。因为加法运算并不是一个向量必需的功能,所以这个类方法被定义在 `Vector2D` 的一个扩展中,而不是 `Vector2D` 结构体声明内。而算术加法运算符是二元运算符,所以这个运算符函数接收两个类型为 `Vector2D` 的参数,同时有一个 `Vector2D` 类型的返回值。
|
||||
|
||||
在这个实现中,输入参数分别被命名为 `left` 和 `right`,代表在 `+` 运算符左边和右边的两个 `Vector2D` 实例。函数返回了一个新的 `Vector2D` 实例,这个实例的 `x` 和 `y` 分别等于作为参数的两个实例的 `x` 和 `y` 的值之和。
|
||||
|
||||
这个类方法可以在任意两个 `Vector2D` 实例中间作为中缀运算符来使用:
|
||||
|
||||
```Swift
|
||||
let vector = Vector2D(x: 3.0, y: 1.0)
|
||||
let anotherVector = Vector2D(x: 2.0, y: 4.0)
|
||||
let combinedVector = vector + anotherVector
|
||||
// combinedVector 是一个新的 Vector2D 实例,值为 (5.0, 5.0)
|
||||
```
|
||||
|
||||
这个例子实现两个向量 `(3.0,1.0)` 和 `(2.0,4.0)` 的相加,并得到新的向量 `(5.0,5.0)`。这个过程如下图示:
|
||||
|
||||

|
||||
|
||||
### 前缀和后缀运算符 {#prefix-and-postfix-operators}
|
||||
|
||||
上个例子演示了一个二元中缀运算符的自定义实现。类与结构体也能提供标准*一元运算符*的实现。一元运算符只运算一个值。当运算符出现在值之前时,它就是*前缀*的(例如 `-a`),而当它出现在值之后时,它就是*后缀*的(例如 `b!`)。
|
||||
|
||||
要实现前缀或者后缀运算符,需要在声明运算符函数的时候在 `func` 关键字之前指定 `prefix` 或者 `postfix` 修饰符:
|
||||
|
||||
```Swift
|
||||
extension Vector2D {
|
||||
static prefix func - (vector: Vector2D) -> Vector2D {
|
||||
return Vector2D(x: -vector.x, y: -vector.y)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这段代码为 `Vector2D` 类型实现了一元运算符(`-a`)。由于该运算符是前缀运算符,所以这个函数需要加上 `prefix` 修饰符。
|
||||
|
||||
对于简单数值,一元负号运算符可以对它们的正负性进行改变。对于 `Vector2D` 来说,该运算将其 `x` 和 `y` 属性的正负性都进行了改变:
|
||||
|
||||
```Swift
|
||||
let positive = Vector2D(x: 3.0, y: 4.0)
|
||||
let negative = -positive
|
||||
// negative 是一个值为 (-3.0, -4.0) 的 Vector2D 实例
|
||||
let alsoPositive = -negative
|
||||
// alsoPositive 是一个值为 (3.0, 4.0) 的 Vector2D 实例
|
||||
```
|
||||
|
||||
### 复合赋值运算符 {#compound-assignment-operators}
|
||||
|
||||
*复合赋值运算符*将赋值运算符(`=`)与其它运算符进行结合。例如,将加法与赋值结合成加法赋值运算符(`+=`)。在实现的时候,需要把运算符的左参数设置成 `inout` 类型,因为这个参数的值会在运算符函数内直接被修改。
|
||||
|
||||
在下面的例子中,对 `Vector2D` 实例实现了一个加法赋值运算符函数:
|
||||
|
||||
```Swift
|
||||
extension Vector2D {
|
||||
static func += (left: inout Vector2D, right: Vector2D) {
|
||||
left = left + right
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
因为加法运算在之前已经定义过了,所以在这里无需重新定义。在这里可以直接利用现有的加法运算符函数,用它来对左值和右值进行相加,并再次赋值给左值:
|
||||
|
||||
```Swift
|
||||
var original = Vector2D(x: 1.0, y: 2.0)
|
||||
let vectorToAdd = Vector2D(x: 3.0, y: 4.0)
|
||||
original += vectorToAdd
|
||||
// original 的值现在为 (4.0, 6.0)
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 不能对默认的赋值运算符(`=`)进行重载。只有复合赋值运算符可以被重载。同样地,也无法对三元条件运算符 (`a ? b : c`) 进行重载。
|
||||
|
||||
### 等价运算符 {#equivalence-operators}
|
||||
|
||||
通常情况下,自定义的类和结构体没有对*等价运算符*进行默认实现,等价运算符通常被称为*相等*运算符(`==`)与*不等*运算符(`!=`)。
|
||||
|
||||
为了使用等价运算符对自定义的类型进行判等运算,需要为“相等”运算符提供自定义实现,实现的方法与其它中缀运算符一样, 并且增加对标准库 `Equatable` 协议的遵循:
|
||||
|
||||
```Swift
|
||||
extension Vector2D: Equatable {
|
||||
static func == (left: Vector2D, right: Vector2D) -> Bool {
|
||||
return (left.x == right.x) && (left.y == right.y)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上述代码实现了“相等”运算符(`==`)来判断两个 `Vector2D` 实例是否相等。对于 `Vector2D` 来说,“相等”意味着“两个实例的 `x` 和 `y` 都相等”,这也是代码中用来进行判等的逻辑。如果你已经实现了“相等”运算符,通常情况下你并不需要自己再去实现“不等”运算符(`!=`)。标准库对于“不等”运算符提供了默认的实现,它简单地将“相等”运算符的结果进行取反后返回。
|
||||
|
||||
现在我们可以使用这两个运算符来判断两个 `Vector2D` 实例是否相等:
|
||||
|
||||
```Swift
|
||||
let twoThree = Vector2D(x: 2.0, y: 3.0)
|
||||
let anotherTwoThree = Vector2D(x: 2.0, y: 3.0)
|
||||
if twoThree == anotherTwoThree {
|
||||
print("These two vectors are equivalent.")
|
||||
}
|
||||
// 打印“These two vectors are equivalent.”
|
||||
```
|
||||
|
||||
多数简单情况下,你可以让 Swift 合成等价运算符的实现,详见 [使用合成实现来采纳协议](./21_Protocols.md#adopting-a-protocol-using-a-synthesized-implementation)。
|
||||
|
||||
## 自定义运算符 {#custom-operators}
|
||||
|
||||
除了实现标准运算符,在 Swift 中还可以声明和实现*自定义运算符*。可以用来自定义运算符的字符列表请参考 [运算符](../03_language_reference/02_Lexical_Structure.md#operators)。
|
||||
|
||||
新的运算符要使用 `operator` 关键字在全局作用域内进行定义,同时还要指定 `prefix`、`infix` 或者 `postfix` 修饰符:
|
||||
|
||||
```Swift
|
||||
prefix operator +++
|
||||
```
|
||||
|
||||
上面的代码定义了一个新的名为 `+++` 的前缀运算符。对于这个运算符,在 Swift 中并没有已知的意义,因此在针对 `Vector2D` 实例的特定上下文中,给予了它自定义的意义。对这个示例来讲,`+++` 被实现为“前缀双自增”运算符。它使用了前面定义的复合加法运算符来让矩阵与自身进行相加,从而让 `Vector2D` 实例的 `x` 属性和 `y` 属性值翻倍。你可以像下面这样通过对 `Vector2D` 添加一个 `+++` 类方法,来实现 `+++` 运算符:
|
||||
|
||||
```Swift
|
||||
extension Vector2D {
|
||||
static prefix func +++ (vector: inout Vector2D) -> Vector2D {
|
||||
vector += vector
|
||||
return vector
|
||||
}
|
||||
}
|
||||
|
||||
var toBeDoubled = Vector2D(x: 1.0, y: 4.0)
|
||||
let afterDoubling = +++toBeDoubled
|
||||
// toBeDoubled 现在的值为 (2.0, 8.0)
|
||||
// afterDoubling 现在的值也为 (2.0, 8.0)
|
||||
```
|
||||
|
||||
### 自定义中缀运算符的优先级 {#precedence-and-associativity-for-custom-infix-operators}
|
||||
|
||||
每个自定义中缀运算符都属于某个优先级组。优先级组指定了这个运算符相对于其他中缀运算符的优先级和结合性。[优先级和结合性](#precedence-and-associativity) 中详细阐述了这两个特性是如何对中缀运算符的运算产生影响的。
|
||||
|
||||
而没有明确放入某个优先级组的自定义中缀运算符将会被放到一个默认的优先级组内,其优先级高于三元运算符。
|
||||
|
||||
以下例子定义了一个新的自定义中缀运算符 `+-`,此运算符属于 `AdditionPrecedence` 优先组:
|
||||
|
||||
```Swift
|
||||
infix operator +-: AdditionPrecedence
|
||||
extension Vector2D {
|
||||
static func +- (left: Vector2D, right: Vector2D) -> Vector2D {
|
||||
return Vector2D(x: left.x + right.x, y: left.y - right.y)
|
||||
}
|
||||
}
|
||||
let firstVector = Vector2D(x: 1.0, y: 2.0)
|
||||
let secondVector = Vector2D(x: 3.0, y: 4.0)
|
||||
let plusMinusVector = firstVector +- secondVector
|
||||
// plusMinusVector 是一个 Vector2D 实例,并且它的值为 (4.0, -2.0)
|
||||
```
|
||||
|
||||
这个运算符把两个向量的 `x` 值相加,同时从第一个向量的 `y` 中减去第二个向量的 `y` 。因为它本质上是属于“相加型”运算符,所以将它放置在 `+` 和 `-` 等默认中缀“相加型”运算符相同的优先级组中。关于 Swift 标准库提供的运算符,以及完整的运算符优先级组和结合性设置,请参考 [运算符声明](https://developer.apple.com/documentation/swift/operator_declarations)。而更多关于优先级组以及自定义操作符和优先级组的语法,请参考 [运算符声明](../03_language_reference/06_Declarations.md#operator-declaration)。
|
||||
|
||||
> 注意
|
||||
>
|
||||
> 当定义前缀与后缀运算符的时候,我们并没有指定优先级。然而,如果对同一个值同时使用前缀与后缀运算符,则后缀运算符会先参与运算。
|
||||
|
||||
1
source/02_language_guide/28_Concurrency.md
Normal file
1
source/02_language_guide/28_Concurrency.md
Normal file
@ -0,0 +1 @@
|
||||
# 并发
|
||||
6
source/chapter2/chapter2.md → source/02_language_guide/chapter2.md
Executable file → Normal file
6
source/chapter2/chapter2.md → source/02_language_guide/chapter2.md
Executable file → Normal file
@ -1,3 +1,3 @@
|
||||
# Swift 教程
|
||||
|
||||
本章介绍了 Swift 的各种特性及其使用方法,是全书的核心部分。
|
||||
# Swift 语言教程
|
||||
|
||||
本章介绍了 Swift 的各种特性及其使用方法,是全书的核心部分。
|
||||
32
source/03_language_reference/01_About_the_Language_Reference.md
Executable file
32
source/03_language_reference/01_About_the_Language_Reference.md
Executable file
@ -0,0 +1,32 @@
|
||||
# 关于语言参考(About the Language Reference)
|
||||
|
||||
本书的这一节描述了 Swift 编程语言的形式语法。这里描述的语法是为了帮助您了解该语言的更多细节,而不是让您直接实现一个解析器或编译器。
|
||||
|
||||
Swift 语言相对较小,这是由于 Swift 代码中常用的类型、函数以及运算符都已经在 Swift 标准库中定义了。虽然这些类型、函数和运算符并不是 Swift 语言自身的一部分,但是它们被广泛应用于本书的讨论和代码范例中。
|
||||
|
||||
## 如何阅读语法 {#how-to-read-the-grammar}
|
||||
|
||||
用来描述 Swift 编程语言形式语法的符号遵循下面几个约定:
|
||||
|
||||
- 箭头(`→`)用来标记语法产式,可以理解为“可由……构成”。
|
||||
- 斜体文字用来表示句法类型,并出现在一个语法产式规则两侧。
|
||||
- 标记语言和标点符号由固定宽度的粗体文本表示,只出现在一个语法产式规则的右侧。
|
||||
- 可供选择的语法产式由竖线(`|`)分隔。当可选用的语法产式太多时,为了阅读方便,它们将被拆分为多行语法产式规则。
|
||||
- 少数情况下,标准字体文本被用来描述一个语法产生规则的右手侧内容。
|
||||
- 可选的句法类型和文本标记用尾标 `opt` 来标记。
|
||||
|
||||
举个例子,getter-setter 方法块的语法定义如下:
|
||||
|
||||
> getter-setter 方法块语法
|
||||
>
|
||||
> *getter-setter 方法块* → { [getter 子句](./06_Declarations.md#getter-clause) [setter 子句](./06_Declarations.md#setter-clause)<sub>可选</sub> } | { [setter 子句](./06_Declarations.md#setter-clause) [getter 子句](./06_Declarations.md#getter-clause) }
|
||||
|
||||
这个定义表明,一个 getter-setter 方法块可以由一个 getter 分句后跟一个可选的 setter 分句构成,然后用大括号括起来,或者由一个 setter 分句后跟一个 getter 分句构成,然后用大括号括起来。上述的语法产式等价于下面的两个语法产式, :
|
||||
|
||||
> getter-setter 方法块语法
|
||||
>
|
||||
> getter-setter 方法块 → { [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) }
|
||||
>
|
||||
|
||||
645
source/03_language_reference/02_Lexical_Structure.md
Executable file
645
source/03_language_reference/02_Lexical_Structure.md
Executable file
@ -0,0 +1,645 @@
|
||||
# 词法结构(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)
|
||||
>
|
||||
> *空白项* → [注释](#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` 等等。这些命名在闭包作用域范围内是合法的标识符。
|
||||
|
||||
编译器给含有属性包装器呈现值的属性自动合成以美元符号(*$*)开头的标识符。你的代码可以与这些标识符进行交互,,但是不能使用该前缀声明标识符。更详细的介绍,请查看 [特性](./07_Attributes.md) 章节中的 [属性包装器](./07_Attributes.md#propertywrapper) 部分。
|
||||
|
||||
> 标识符语法
|
||||
>
|
||||
> *标识符* → [头部标识符](#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
|
||||
>
|
||||
#### 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`、`protocol`、`public`、`rethrows`、`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`、`super`、`self`、`Self`、`throw`、`throws`、`true` 以及 `try `。
|
||||
* 用在模式中的关键字:`_`。
|
||||
* 以井字号(`#`)开头的关键字:`#available`、`#colorLiteral`、`#column`、`#else`、`#elseif`、`#endif`、`#error`、`#file`、`#filePath`、`#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 标准库协议中的一个:整数字面量的 `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)
|
||||
|
||||
|
||||
## 运算符 {#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)
|
||||
525
source/03_language_reference/03_Types.md
Normal file
525
source/03_language_reference/03_Types.md
Normal file
@ -0,0 +1,525 @@
|
||||
# 类型(Types)
|
||||
|
||||
Swift 语言存在两种类型:命名型类型和复合型类型。*命名型类型*是指定义时可以给定名字的类型。命名型类型包括类、结构体、枚举和协议。比如,一个用户定义类 `MyClass` 的实例拥有类型 `MyClass`。除了用户定义的命名型类型,Swift 标准库也定义了很多常用的命名型类型,包括那些表示数组、字典和可选值的类型。
|
||||
|
||||
那些通常被其它语言认为是基本或原始的数据型类型,比如表示数字、字符和字符串的类型,实际上就是命名型类型,这些类型在 Swift 标准库中是使用结构体来定义和实现的。因为它们是命名型类型,因此你可以按照 [扩展](../02_language_guide/20_Extensions.md) 和 [扩展声明](./06_Declarations.md#extension-declaration) 中讨论的那样,声明一个扩展来增加它们的行为以满足你程序的需求。
|
||||
|
||||
*复合型类型*是没有名字的类型,它由 Swift 本身定义。Swift 存在两种复合型类型:函数类型和元组类型。一个复合型类型可以包含命名型类型和其它复合型类型。例如,元组类型 `(Int, (Int, Int))` 包含两个元素:第一个是命名型类型 `Int`,第二个是另一个复合型类型 `(Int, Int)`。
|
||||
|
||||
你可以在命名型类型和复合型类型使用小括号。但是在类型旁加小括号没有任何作用。举个例子,`(Int)` 等同于 `Int`。
|
||||
|
||||
本节讨论 Swift 语言本身定义的类型,并描述 Swift 中的类型推断行为。
|
||||
|
||||
#### type {#type}
|
||||
|
||||
> 类型语法
|
||||
>
|
||||
> *类型* → [函数类型](#function-type)
|
||||
>
|
||||
> *类型* → [数组类型](#array-type)
|
||||
>
|
||||
> *类型* → [字典类型](#dictionary-type)
|
||||
>
|
||||
> *类型* → [类型标识](#type-identifier)
|
||||
>
|
||||
> *类型* → [元组类型](#tuple-type)
|
||||
>
|
||||
> *类型* → [可选类型](#optional-type)
|
||||
>
|
||||
> *类型* → [隐式解析可选类型](#implicitly-unwrapped-optional-type)
|
||||
>
|
||||
> *类型* → [协议合成类型](#protocol-composition-type)
|
||||
>
|
||||
> *类型* →[不透明类型](#opaque-type)
|
||||
>
|
||||
> *类型* → [元型类型](#metatype-type)
|
||||
>
|
||||
> *类型* → [自身类型](#self-type)
|
||||
>
|
||||
> *类型* → **Any**
|
||||
>
|
||||
> *类型* → **(** [类型](#type) **)**
|
||||
|
||||
## 类型注解 {#type-annotation-h}
|
||||
*类型注解*显式地指定一个变量或表达式的类型。类型注解从冒号 (`:`)开始, 以类型结尾,比如下面两个例子:
|
||||
|
||||
```swift
|
||||
let someTuple: (Double, Double) = (3.14159, 2.71828)
|
||||
func someFunction(a: Int) { /* ... */ }
|
||||
```
|
||||
|
||||
在第一个例子中,表达式 `someTuple` 的类型被指定为 `(Double, Double)`。在第二个例子中,函数 `someFunction` 的形参 `a` 的类型被指定为 `Int`。
|
||||
|
||||
类型注解可以在类型之前包含一个类型特性的可选列表。
|
||||
|
||||
> 类型注解语法
|
||||
>
|
||||
|
||||
#### type-annotation {#type-annotation}
|
||||
> *类型注解* → **:** [特性列表](./07_Attributes.md#attributes)<sub>可选</sub> **输入输出参数**<sub>可选</sub> [类型](#type)
|
||||
|
||||
## 类型标识符 {#type-identifier-h}
|
||||
*类型标识符*可以引用命名型类型,还可引用命名型或复合型类型的别名。
|
||||
|
||||
大多数情况下,类型标识符引用的是与之同名的命名型类型。例如类型标识符 `Int` 引用命名型类型 `Int`,同样,类型标识符 `Dictionary<String, Int>` 引用命名型类型 `Dictionary<String, Int>`。
|
||||
|
||||
在两种情况下类型标识符不引用同名的类型。情况一,类型标识符引用的是命名型或复合型类型的类型别名。比如,在下面的例子中,类型标识符使用 `Point` 来引用元组 `(Int, Int)`:
|
||||
|
||||
```swift
|
||||
typealias Point = (Int, Int)
|
||||
let origin: Point = (0, 0)
|
||||
```
|
||||
|
||||
情况二,类型标识符使用点语法(`.`)来表示在其它模块或其它类型嵌套内声明的命名型类型。例如,下面例子中的类型标识符引用在 `ExampleModule` 模块中声明的命名型类型 `MyType`:
|
||||
|
||||
```swift
|
||||
var someValue: ExampleModule.MyType
|
||||
```
|
||||
|
||||
> 类型标识符语法
|
||||
>
|
||||
|
||||
#### type-identifier {#type-identifier}
|
||||
> *类型标识符* → [类型名称](#type-name) [泛型实参子句](./09_Generic_Parameters_and_Arguments.md#generic-argument-clause)<sub>可选</sub> | [类型名称](#type-name) [泛型实参子句](./09_Generic_Parameters_and_Arguments.md#generic-argument-clause)<sub>可选</sub> **.** [类型标识符](#type-identifier)
|
||||
|
||||
#### type-name {#type-name}
|
||||
> *类型名称* → [标识符](./02_Lexical_Structure.md#identifier)
|
||||
|
||||
## 元组类型 {#tuple-type-h}
|
||||
*元组类型*是使用括号括起来的零个或多个类型,类型间用逗号隔开。
|
||||
|
||||
你可以使用元组类型作为一个函数的返回类型,这样就可以使函数返回多个值。你也可以命名元组类型中的元素,然后用这些名字来引用每个元素的值。元素的名字由一个标识符紧跟一个冒号 `(:)` 组成。[函数和多返回值](../02_language_guide/06_Functions.md#functions-with-multiple-return-values) 章节里有一个展示上述特性的例子。
|
||||
|
||||
当一个元组类型的元素有名字的时候,这个名字就是类型的一部分。
|
||||
|
||||
```swift
|
||||
var someTuple = (top: 10, bottom: 12) // someTuple 的类型为 (top: Int, bottom: Int)
|
||||
someTuple = (top: 4, bottom: 42) // 正确:命名类型匹配
|
||||
someTuple = (9, 99) // 正确:命名类型被自动推断
|
||||
someTuple = (left: 5, right: 5) // 错误:命名类型不匹配
|
||||
```
|
||||
|
||||
所有的元组类型都包含两个及以上元素, 除了 `Void`。`Void` 是空元组类型 `()` 的别名。
|
||||
|
||||
> 元组类型语法
|
||||
>
|
||||
|
||||
#### tuple-type {#tuple-type}
|
||||
> *元组类型* → **(** **)** | **(** [元组类型元素](#tuple-type-element) **,** [元组类型元素列表](#tuple-type-element-list) **)**
|
||||
>
|
||||
|
||||
#### tuple-type-element-list {#tuple-type-element-list}
|
||||
> *元组类型元素列表* → [元组类型元素](#tuple-type-element) | [元组类型元素](#tuple-type-element) **,** [元组类型元素列表](#tuple-type-element-list)
|
||||
>
|
||||
|
||||
#### tuple-type-element {#tuple-type-element}
|
||||
> *元组类型元素* → [元素名](#element-name) [类型注解](#type-annotation) | [类型](#type)
|
||||
>
|
||||
|
||||
#### element-name {#element-name}
|
||||
> *元素名* → [标识符](./02_Lexical_Structure.md#identifier)
|
||||
>
|
||||
|
||||
## 函数类型 {#function-type-h}
|
||||
*函数类型*表示一个函数、方法或闭包的类型,它由形参类型和返回值类型组成,中间用箭头(`->`)隔开:
|
||||
|
||||
> (`形参类型`)->(`返回值类型`)
|
||||
|
||||
*形参类型*是由逗号间隔的类型列表。由于*返回值类型*可以是元组类型,所以函数类型支持多返回值的函数与方法。
|
||||
|
||||
你可以对形参类型为 `() -> T`(其中 T 是任何类型)的函数使用 `autoclosure` 特性,这会在调用侧隐式创建一个闭包。这从语法结构上提供了一种便捷:延迟对表达式的求值,直到其值在函数体中被调用。以自动闭包做为形参的函数类型的例子详见 [自动闭包](../02_language_guide/07_Closures.md#autoclosures)。
|
||||
|
||||
函数类型可以拥有一个可变参数在*形参类型*中。从语法角度上讲,可变参数由一个基础类型名字紧随三个点(`...`)组成,如 `Int...`。可变参数被认为是一个包含了基础类型元素的数组。即 `Int...` 就是 `[Int]`。关于使用可变参数的例子,请参阅 [可变参数](../02_language_guide/06_Functions.md#variadic-parameters)。
|
||||
|
||||
为了指定一个 `in-out` 参数,可以在形参类型前加 `inout` 前缀。但是你不可以对可变参数或返回值类型使用 `inout`。关于这种形参的详细讲解请参阅 [输入输出参数](../02_language_guide/06_Functions.md#in-out-parameters)。
|
||||
|
||||
如果函数类型只有一个类型是元组类型的一个形参,那么元组类型在写函数类型的时候必须用圆括号括起来。比如说,`((Int, Int)) -> Void` 是接收一个元组 `(Int, Int)` 作为形参并且不返回任何值的函数类型。与此相对,不加括号的 `(Int, Int) -> Void` 是一个接收两个 `Int` 作为形参并且不返回任何值的函数类型。相似地,因为 `Void` 是空元组类型 `()` 的别名,函数类型 `(Void)-> Void` 与 `(()) -> ()` 是一样的 - 一个将空元组作为唯一实参的函数。但这些类型和 `() -> ()` 是不一样的 - 一个无实参的函数。
|
||||
|
||||
函数和方法中的实参名并不是函数类型的一部分。例如:
|
||||
|
||||
```swift
|
||||
func someFunction(left: Int, right: Int) {}
|
||||
func anotherFunction(left: Int, right: Int) {}
|
||||
func functionWithDifferentLabels(top: Int, bottom: Int) {}
|
||||
|
||||
var f = someFunction // 函数 f 的类型为 (Int, Int) -> Void, 而不是 (left: Int, right: Int) -> Void.
|
||||
|
||||
f = anotherFunction // 正确
|
||||
f = functionWithDifferentLabels // 正确
|
||||
|
||||
func functionWithDifferentArgumentTypes(left: Int, right: String) {}
|
||||
f = functionWithDifferentArgumentTypes // 错误
|
||||
|
||||
func functionWithDifferentNumberOfArguments(left: Int, right: Int, top: Int) {}
|
||||
f = functionWithDifferentNumberOfArguments // 错误
|
||||
```
|
||||
|
||||
由于实参标签不是函数类型的一部分,你可以在写函数类型的时候省略它们。
|
||||
|
||||
```swift
|
||||
var operation: (lhs: Int, rhs: Int) -> Int // 错误
|
||||
var operation: (_ lhs: Int, _ rhs: Int) -> Int // 正确
|
||||
var operation: (Int, Int) -> Int // 正确
|
||||
```
|
||||
|
||||
如果一个函数类型包涵多个箭头(->),那么函数类型将从右向左进行组合。例如,函数类型 `(Int) -> (Int) -> Int` 可以理解为 `(Int) -> ((Int) -> Int)`,也就是说,该函数传入 `Int`,并返回另一个传入并返回 `Int` 的函数。
|
||||
|
||||
函数类型若要抛出或重抛错误就必须使用 `throws` 关键字来标记。`throws` 关键字是函数类型的一部分,非抛出函数是抛出函数的子类型。因此,在使用抛出函数的地方也可以使用不抛出函数。抛出和重抛函数的相关描述见章节 [抛出函数与方法](./06_Declarations.md#throwing-functions-and-methods) 和 [重抛函数与方法](./06_Declarations.md#rethrowing-functions-and-methods)。
|
||||
|
||||
### 对非逃逸闭包的限制 {#Restrictions for Nonescaping Closures}
|
||||
当非逃逸闭包函数是形参时,不能存储在属性、变量或任何 `Any` 类型的常量中,因为这可能导致值的逃逸。
|
||||
|
||||
当非逃逸闭包函数是形参时,不能作为实参传递到另一个非逃逸闭包函数中。这样的限制可以让 Swift 在编译时就完成更好的内存访问冲突检查,而不是在运行时。举个例子:
|
||||
|
||||
```swift
|
||||
let external: (Any) -> Void = { _ in () }
|
||||
func takesTwoFunctions(first: (Any) -> Void, second: (Any) -> Void) {
|
||||
first(first) // 错误
|
||||
second(second) // 错误
|
||||
|
||||
first(second) // 错误
|
||||
second(first) // 错误
|
||||
|
||||
first(external) // 正确
|
||||
external(first) // 正确
|
||||
}
|
||||
```
|
||||
|
||||
在上面代码里,`takesTwoFunctions(first:second:)` 的两个形参都是函数。它们都没有标记为 `@escaping`, 因此它们都是非逃逸的。
|
||||
|
||||
上述例子里的被标记为“错误”的四个函数调用会产生编译错误。因为形参 `first` 和 `second` 是非逃逸函数,它们不能够作为实参被传递到另一个非闭包函数。相对的, 标记“正确”的两个函数不会产生编译错误。这些函数调用不会违反限制,因为 `external` 不是 `takesTwoFunctions(first:second:)` 的形参之一。
|
||||
|
||||
如果你需要避免这个限制,标记其中一个形参为逃逸,或者使用 `withoutActuallyEscaping(_:do:)` 函数临时转换其中一个非逃逸函数形参为逃逸函数。关于避免内存访问冲突,可以参阅 [内存安全](../02_language_guide/25_Memory_Safety.md)。
|
||||
|
||||
> 函数类型语法
|
||||
>
|
||||
|
||||
#### function-type {#function-type}
|
||||
> *函数类型* → [特性列表](./07_Attributes.md#attributes)<sub>可选</sub> [函数类型子句](#function-type-argument-clause) **throws**<sub>可选</sub> **->** [类型](#type)
|
||||
|
||||
#### function-type-argument-clause {#function-type-argument-clause}
|
||||
> *函数类型子句* → **(** **)**
|
||||
> *函数类型子句* → **(** [函数类型实参列表](#function-type-argument-list) *...* <sub>可选</sub> **)**
|
||||
|
||||
#### function-type-argument-list {#function-type-argument-list}
|
||||
> *函数类型实参列表* → [函数类型实参](#function-type-argument) | [函数类型实参](#function-type-argument), [函数类型实参列表](#function-type-argument-list)
|
||||
|
||||
#### function-type-argument {#function-type-argument}
|
||||
|
||||
> *函数类型实参* → [特性列表](./07_Attributes.md#attributes)<sub>可选</sub> **输入输出参数**<sub>可选</sub> [类型](#type) | [实参标签](#argument-label) [类型注解](#type-annotation)
|
||||
|
||||
#### argument-label {#argument-label}
|
||||
> *形参标签* → [标识符](./02_Lexical_Structure.md#identifier)
|
||||
|
||||
## 数组类型 {#array-type-h}
|
||||
Swift 语言为标准库中定义的 `Array<Element>` 类型提供了如下语法糖:
|
||||
|
||||
> [`类型`]
|
||||
>
|
||||
|
||||
换句话说,下面两个声明是等价的:
|
||||
|
||||
```swift
|
||||
let someArray: Array<String> = ["Alex", "Brian", "Dave"]
|
||||
let someArray: [String] = ["Alex", "Brian", "Dave"]
|
||||
```
|
||||
|
||||
上面两种情况下,常量 `someArray` 都被声明为字符串数组。数组的元素也可以通过下标访问:`someArray[0]` 是指第 0 个元素 `"Alex"`。
|
||||
|
||||
你也可以嵌套多对方括号来创建多维数组,最里面的方括号中指明数组元素的基本类型。比如,下面例子中使用三对方括号创建三维整数数组:
|
||||
|
||||
```swift
|
||||
var array3D: [[[Int]]] = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
|
||||
```
|
||||
|
||||
访问一个多维数组的元素时,最左边的下标指向最外层数组的相应位置元素。接下来往右的下标指向第一层嵌入的相应位置元素,依次类推。这就意味着,在上面的例子中,`array3D[0]` 是 `[[1, 2], [3, 4]]`,`array3D[0][1]` 是 `[3, 4]`,`array3D[0][1][1]` 则是 `4`。
|
||||
|
||||
关于 Swift 标准库中 `Array` 类型的详细讨论,请参阅 [数组](../02_language_guide/04_Collection_Types.md#arrays)。
|
||||
|
||||
> 数组类型语法
|
||||
>
|
||||
|
||||
#### array-type {#array-type}
|
||||
> *数组类型* → **[** [类型](#type) **]**
|
||||
>
|
||||
|
||||
## 字典类型 {#dictionary-type-h}
|
||||
Swift 语言为标准库中定义的 `Dictionary<Key, Value>` 类型提供了如下语法糖:
|
||||
|
||||
> [`键类型` : `值类型`]
|
||||
>
|
||||
|
||||
换句话说,下面两个声明是等价的:
|
||||
|
||||
```swift
|
||||
let someDictionary: [String: Int] = ["Alex": 31, "Paul": 39]
|
||||
let someDictionary: Dictionary<String, Int> = ["Alex": 31, "Paul": 39]
|
||||
```
|
||||
|
||||
上面两种情况,常量 `someDictionary` 被声明为一个字典,其中键为 `String` 类型,值为 `Int` 类型。
|
||||
|
||||
字典中的值可以通过下标来访问,这个下标在方括号中指明了具体的键:`someDictionary["Alex"]` 返回键 `Alex` 对应的值。通过下标访问会获取对应值的可选类型。如果键在字典中不存在的话,则这个下标返回 `nil`。
|
||||
|
||||
字典中键的类型必须符合 Swift 标准库中的 `Hashable` 协议。
|
||||
|
||||
关于 Swift 标准库中 `Dictionary` 类型的详细讨论,请参阅 [字典](../02_language_guide/04_Collection_Types.md#dictionaries)。
|
||||
|
||||
> 字典类型语法
|
||||
>
|
||||
|
||||
#### dictionary-type {#dictionary-type}
|
||||
> *字典类型* → **[** [类型](#type) **:** [类型](#type) **]**
|
||||
>
|
||||
|
||||
## 可选类型 {#optional-type-h}
|
||||
Swift 定义后缀 `?` 来作为标准库中定义的命名型类型 `Optional<Wrapped>` 的语法糖。换句话说,下面两个声明是等价的:
|
||||
|
||||
```swift
|
||||
var optionalInteger: Int?
|
||||
var optionalInteger: Optional<Int>
|
||||
```
|
||||
|
||||
在上述两种情况下,变量 `optionalInteger` 都被声明为可选整型类型。注意在类型和 `?` 之间没有空格。
|
||||
|
||||
类型 `Optional<Wrapped>` 是一个枚举,有两个成员,`none` 和 `some(Wrapped)`,用来表示可能有也可能没有的值。任意类型都可以被显式地声明(或隐式地转换)为可选类型。如果你在声明可选变量或属性的时候没有提供初始值,它的值则会自动赋为默认值 `nil`。
|
||||
|
||||
如果一个可选类型的实例包含一个值,那么你就可以使用后缀运算符 `!` 来获取该值,正如下面描述的:
|
||||
|
||||
```swift
|
||||
optionalInteger = 42
|
||||
optionalInteger! // 42
|
||||
```
|
||||
|
||||
使用 `!` 运算符解包值为 `nil` 的可选值会导致运行错误。
|
||||
|
||||
你也可以使用可选链式调用和可选绑定来选择性在可选表达式上执行操作。如果值为 `nil`,不会执行任何操作,因此也就没有运行错误产生。
|
||||
|
||||
更多细节以及更多如何使用可选类型的例子,请参阅 [可选类型](../02_language_guide/01_The_Basics.md#optionals)。
|
||||
|
||||
> 可选类型语法
|
||||
>
|
||||
|
||||
#### optional-type {#optional-type}
|
||||
> *可选类型* → [类型](#type) **?**
|
||||
>
|
||||
|
||||
## 隐式解析可选类型 {#implicitly-unwrapped-optional-type-h}
|
||||
当可以被访问时,Swift 语言定义后缀 `!` 作为标准库中命名类型 `Optional<Wrapped>` 的语法糖,来实现自动解包的功能。如果尝试对一个值为 `nil` 的可选类型进行隐式解包,将会产生运行时错误。因为隐式解包,下面两个声明等价:
|
||||
|
||||
```swift
|
||||
var implicitlyUnwrappedString: String!
|
||||
var explicitlyUnwrappedString: Optional<String>
|
||||
```
|
||||
|
||||
注意类型与 `!` 之间没有空格。
|
||||
|
||||
由于隐式解包会更改包含该类型的声明语义,嵌套在元组类型或泛型中可选类型(比如字典元素类型或数组元素类型),不能被标记为隐式解包。例如:
|
||||
|
||||
```swift
|
||||
let tupleOfImplicitlyUnwrappedElements: (Int!, Int!) // 错误
|
||||
let implicitlyUnwrappedTuple: (Int, Int)! // 正确
|
||||
|
||||
let arrayOfImplicitlyUnwrappedElements: [Int!] // 错误
|
||||
let implicitlyUnwrappedArray: [Int]! // 正确
|
||||
```
|
||||
|
||||
由于隐式解析可选类型和可选类型有同样的类型 `Optional<Wrapped>`,你可以在所有使用可选类型的地方使用隐式解析可选类型。比如,你可以将隐式解析可选类型的值赋给变量、常量和可选属性,反之亦然。
|
||||
|
||||
正如可选类型一样,如果你在声明隐式解析可选类型的变量或属性的时候没有指定初始值,它的值则会自动赋为默认值 `nil`。
|
||||
|
||||
可以使用可选链式调用对隐式解析可选表达式选择性地执行操作。如果值为 `nil`,就不会执行任何操作,因此也不会产生运行错误。
|
||||
|
||||
关于隐式解析可选类型的更多细节,请参阅 [隐式解析可选类型](../02_language_guide/01_The_Basics.md#implicityly-unwrapped-optionals)。
|
||||
|
||||
> 隐式解析可选类型语法
|
||||
>
|
||||
|
||||
#### implicitly-unwrapped-optional-type {#implicitly-unwrapped-optional-type}
|
||||
> *隐式解析可选类型* → [类型](#type) **!**
|
||||
>
|
||||
|
||||
## 协议合成类型 {#protocol-composition-type-h}
|
||||
*协议合成类型*定义了一种遵循协议列表中每个指定协议的类型,或者一个现有类型的子类并遵循协议列表中每个指定协议。协议合成类型只能用在类型注解、泛型形参子句和泛型 `where` 子句中指定类型。
|
||||
|
||||
协议合成类型的形式如下:
|
||||
|
||||
> `Protocol 1` & `Procotol 2`
|
||||
|
||||
协议合成类型允许你指定一个值,其类型遵循多个协议的要求而不需要定义一个新的命名型协议来继承它想要符合的各个协议。比如,协议合成类型 `Protocol A & Protocol B & Protocol C` 等效于一个从 `Protocol A`,`Protocol B`,`Protocol C` 继承而来的新协议。同样的,你可以使用 `SuperClass & ProtocolA` 来取代声明一个新的协议作为 `SuperClass` 的子类并遵循 `ProtocolA`。
|
||||
|
||||
协议合成列表中的每一项都必须是下面所列情况之一,列表中最多只能包含一个类:
|
||||
|
||||
- 类名
|
||||
- 协议名
|
||||
- 一个类型别名,它的潜在类型是一个协议合成类型、一个协议或者一个类
|
||||
|
||||
当协议合成类型包含类型别名时,同一个协议可能多次出现在定义中 — 重复被忽略。例如,下面代码中定义的 `PQR` 等同于 `P & Q & R`。
|
||||
|
||||
```swift
|
||||
typealias PQ = P & Q
|
||||
typealias PQR = PQ & Q & R
|
||||
```
|
||||
|
||||
> 协议合成类型语法
|
||||
>
|
||||
|
||||
#### protocol-composition-type {#protocol-composition-type}
|
||||
> *协议合成类型* → [协议标识符](#protocol-identifier) & [协议合成延续](#protocol-composition-continuation)
|
||||
>
|
||||
|
||||
#### protocol-composition-continuation {#protocol-composition-continuation}
|
||||
> *协议合成延续* → [协议标识符](#protocol-identifier) | [协议合成类型](#protocol-composition-type)
|
||||
|
||||
## 不透明类型 {#opaque-type-h}
|
||||
|
||||
*不透明类型*定义了遵循某个协议或者合成协议的类型,但不需要指明底层的具体类型。
|
||||
|
||||
不透明类型可以作为函数或下标的返回值,亦或是属性的类型使用。
|
||||
|
||||
不透明类型不能作为元组类型的一部分或范型类型使用,比如数组元素类型或者可选值的包装类型。
|
||||
|
||||
不透明类型的形式如下:
|
||||
|
||||
> some `constraint`
|
||||
|
||||
*constraint* 可以是类类型,协议类型,协议组合类型或者 `Any`。值只有当它遵循该协议或者组合协议,或者从该类继承的时候,才能作为这个不透明类型的实例使用。和不透明值交互的代码只能使用该值定义在 *constraint* 上的接口。
|
||||
|
||||
协议声明里不能包括不透明类型。类不能使用不透明类型作为非 final 方法的返回值。
|
||||
|
||||
使用不透明类型作为返回值的函数必须返回单一公用底层类型。返回的类型可以包含函数范型类型形参的一部分。举个例子,函数 `someFunction<T>()` 可以返回类型 `T` 或者 `Dictionary<String,T>` 的值。
|
||||
|
||||
> 不透明类型语法
|
||||
|
||||
#### opaque-type {#opaque-type}
|
||||
|
||||
> *不透明类型* → **some** [type](#type)
|
||||
|
||||
## 元类型 {#metatype-type-h}
|
||||
|
||||
*元类型*是指任意类型的类型,包括类类型、结构体类型、枚举类型和协议类型。
|
||||
|
||||
类、结构体或枚举类型的元类型是相应的类型名紧跟 `.Type`。协议类型的元类型——并不是运行时遵循该协议的具体类型——是该协议名字紧跟 `.Protocol`。比如,类 `SomeClass` 的元类型就是 `SomeClass.Type`,协议 `SomeProtocol` 的元类型就是 `SomeProtocal.Protocol`。
|
||||
|
||||
你可以使用后缀 `self` 表达式来获取类型。比如,`SomeClass.self` 返回 `SomeClass` 本身,而不是 `SomeClass` 的一个实例。同样,`SomeProtocol.self` 返回 `SomeProtocol` 本身,而不是运行时遵循 `SomeProtocol` 的某个类型的实例。还可以对类型的实例使用 `type(of:)` 表达式来获取该实例动态的、在运行阶段的类型,如下所示:
|
||||
|
||||
```swift
|
||||
class SomeBaseClass {
|
||||
class func printClassName() {
|
||||
println("SomeBaseClass")
|
||||
}
|
||||
}
|
||||
class SomeSubClass: SomeBaseClass {
|
||||
override class func printClassName() {
|
||||
println("SomeSubClass")
|
||||
}
|
||||
}
|
||||
let someInstance: SomeBaseClass = SomeSubClass()
|
||||
// someInstance 在编译期是 SomeBaseClass 类型,
|
||||
// 但是在运行期则是 SomeSubClass 类型
|
||||
type(of: someInstance).printClassName()
|
||||
// 打印“SomeSubClass”
|
||||
```
|
||||
|
||||
更多信息可以查看 Swift 标准库里的 [type(of:)](https://developer.apple.com/documentation/swift/2885064-type)。
|
||||
|
||||
可以使用初始化表达式从某个类型的元类型构造出一个该类型的实例。对于类实例,被调用的构造器必须使用 `required` 关键字标记,或者整个类使用 `final` 关键字标记。
|
||||
|
||||
```swift
|
||||
class AnotherSubClass: SomeBaseClass {
|
||||
let string: String
|
||||
required init(string: String) {
|
||||
self.string = string
|
||||
}
|
||||
override class func printClassName() {
|
||||
print("AnotherSubClass")
|
||||
}
|
||||
}
|
||||
let metatype: AnotherSubClass.Type = AnotherSubClass.self
|
||||
let anotherInstance = metatype.init(string: "some string")
|
||||
```
|
||||
|
||||
> 元类型语法
|
||||
>
|
||||
|
||||
#### metatype-type {#metatype-type}
|
||||
> *元类型* → [类型](#type) **.** **Type** | [类型](#type) **.** **Protocol**
|
||||
|
||||
## 自身类型 {#self-type-h}
|
||||
|
||||
`Self` 类型不是具体的类型,而是让你更方便的引用当前类型,不需要重复或者知道该类的名字。
|
||||
|
||||
在协议声明或者协议成员声明时,`Self` 类型引用的是最终遵循该协议的类型。
|
||||
|
||||
在结构体,类或者枚举值声明时,Self 类型引用的是声明的类型。在某个类型成员声明时,Self 类型引用的是该类型。在类成员声明时,`Self` 只能在以下几种情况中出现:
|
||||
|
||||
* 作为方法的返回类型
|
||||
* 作为只读下标的返回类型
|
||||
* 作为只读计算属性的类型
|
||||
* 在方法体中
|
||||
|
||||
举个例子,下面的代码演示了返回值是 `Self` 的实例方法 `f` 。
|
||||
|
||||
```swift
|
||||
class Superclass {
|
||||
func f() -> Self { return self }
|
||||
}
|
||||
let x = Superclass()
|
||||
print(type(of: x.f()))
|
||||
// 打印 "Superclass"
|
||||
|
||||
class Subclass: Superclass { }
|
||||
let y = Subclass()
|
||||
print(type(of: y.f()))
|
||||
// 打印 "Subclass"
|
||||
|
||||
let z: Superclass = Subclass()
|
||||
print(type(of: z.f()))
|
||||
// 打印 "Subclass"
|
||||
```
|
||||
|
||||
上面例子的最后一部分表明 `Self` 引用的是值 `z` 的运行时类型 `Subclass` ,而不是变量本身的编译时类型 `Superclass` 。
|
||||
|
||||
在嵌套类型声明时,`Self` 类型引用的是最内层声明的类型。
|
||||
|
||||
`Self` 类型引用的类型和 Swift 标准库中 [type(of:)](https://developer.apple.com/documentation/swift/2885064-type) 函数的结果一样。使用 `Self.someStaticMember` 访问当前类型中的成员和使用 `type(of: self).someStaticMember` 是一样的。
|
||||
|
||||
> 自身类型语法
|
||||
|
||||
#### self-type{#self-type}
|
||||
|
||||
> *自身类型* → **Self**
|
||||
|
||||
## 类型继承子句 {#type-inheritance-clause-h}
|
||||
|
||||
*类型继承子句*被用来指定一个命名型类型继承自哪个类、采纳哪些协议。类型继承子句开始于冒号 `:`,其后是类型标识符列表。
|
||||
|
||||
类可以继承自单个超类,并遵循任意数量的协议。当定义一个类时,超类的名字必须出现在类型标识符列表首位,然后跟上该类需要遵循的任意数量的协议。如果一个类不是从其它类继承而来,那么列表可以以协议开头。关于类继承更多的讨论和例子,请参阅 [继承](../02_language_guide/13_Inheritance.md)。
|
||||
|
||||
其它命名型类型只能继承自或采纳一系列协议。协议类型可以继承自任意数量的其他协议。当一个协议类型继承自其它协议时,其它协议中定义的要求会被整合在一起,然后从当前协议继承的任意类型必须符合所有这些条件。
|
||||
|
||||
枚举定义中的类型继承子句可以是一系列协议,或者是指定单一的命名类型,此时枚举为其用例分配原始值。在枚举定义中使用类型继承子句来指定原始值类型的例子,请参阅 [原始值](../02_language_guide/08_Enumerations.md#raw-values)。
|
||||
|
||||
> 类型继承子句语法
|
||||
>
|
||||
|
||||
#### type-inheritance-clause {#type-inheritance-clause}
|
||||
> *类型继承子句* → **:** [类型继承列表](#type-inheritance-list)
|
||||
>
|
||||
|
||||
#### type-inheritance-list {#type-inheritance-list}
|
||||
> *类型继承列表* → [类型标识符](#type-identifier) | [类型标识符](#type-identifier) **,** [类型继承列表](#type-inheritance-list)
|
||||
|
||||
## 类型推断
|
||||
|
||||
Swift 广泛使用*类型推断*,从而允许你省略代码中很多变量和表达式的类型或部分类型。比如,对于 `var x: Int = 0`,你可以完全省略类型而简写成 `var x = 0`,编译器会正确推断出 `x` 的类型 `Int`。类似的,当完整的类型可以从上下文推断出来时,你也可以省略类型的一部分。比如,如果你写了 `let dict: Dictionary = ["A" : 1]`,编译器能推断出 `dict` 的类型是 `Dictionary<String, Int>`。
|
||||
|
||||
在上面的两个例子中,类型信息从表达式树的叶子节点传向根节点。也就是说,`var x: Int = 0` 中 `x` 的类型首先根据 `0` 的类型进行推断,然后将该类型信息传递到根节点(变量 `x`)。
|
||||
|
||||
在 Swift 中,类型信息也可以反方向流动——从根节点传向叶子节点。在下面的例子中,常量 `eFloat` 上的显式类型注解(`: Float`)将导致数字字面量 `2.71828` 的类型是 `Float` 而非 `Double`。
|
||||
|
||||
```swift
|
||||
let e = 2.71828 // e 的类型会被推断为 Double
|
||||
let eFloat: Float = 2.71828 // eFloat 的类型为 Float
|
||||
```
|
||||
|
||||
Swift 中的类型推断在单独的表达式或语句上进行。这意味着所有用于类型推断的信息必须可以从表达式或其某个子表达式的类型检查中获取到。
|
||||
1292
source/03_language_reference/04_Expressions.md
Normal file
1292
source/03_language_reference/04_Expressions.md
Normal file
File diff suppressed because it is too large
Load Diff
903
source/03_language_reference/05_Statements.md
Executable file
903
source/03_language_reference/05_Statements.md
Executable file
@ -0,0 +1,903 @@
|
||||
# 语句(Statements){#statement-statements}
|
||||
|
||||
在 Swift 中,有三种类型的语句:简单语句、编译器控制语句和控制流语句。简单语句是最常见的,用于构造表达式或者声明。编译器控制语句允许程序改变编译器的行为,包含编译配置语句和行控制语句。
|
||||
|
||||
控制流语句则用于控制程序执行的流程,Swift 中有多种类型的控制流语句:循环语句、分支语句和控制转移语句。循环语句用于重复执行代码块;分支语句用于执行满足特定条件的代码块;控制转移语句则用于改变代码的执行顺序。另外,Swift 提供了 `do` 语句,用于构建局部作用域,还用于错误的捕获和处理;还提供了 `defer` 语句,用于退出当前作用域之前执行清理操作。
|
||||
|
||||
是否将分号(`;`)添加到语句的末尾是可选的。但若要在同一行内写多条独立语句,则必须使用分号。
|
||||
|
||||
> 语句语法
|
||||
>
|
||||
> *语句* → [表达式](./04_Expressions.md#expression) **;**<sub>可选</sub>
|
||||
>
|
||||
> *语句* → [声明](./06_Declarations.md#declaration) **;**<sub>可选</sub>
|
||||
>
|
||||
> *语句* → [循环语句](#loop-statement) **;**<sub>可选</sub>
|
||||
>
|
||||
> *语句* → [分支语句](#branch-statement) **;**<sub>可选</sub>
|
||||
>
|
||||
> *语句* → [带标签的语句](#labeled-statement) **;**<sub>可选</sub>
|
||||
>
|
||||
> *语句* → [控制转移语句](#control-transfer-statement) **;**<sub>可选</sub>
|
||||
>
|
||||
> *语句* → [defer 语句](#defer-statement) **;**<sub>可选</sub>
|
||||
>
|
||||
> *语句* → [do 语句](#do-statement) **:**<sub>可选</sub>
|
||||
>
|
||||
> *语句* → [编译器控制语句](#compiler-control-statement)
|
||||
>
|
||||
> *多条语句* → [语句](#statement) [多条语句](#statements)<sub>可选</sub>
|
||||
|
||||
## 循环语句 {#loop-statements}
|
||||
循环语句会根据特定的循环条件来重复执行代码块。Swift 提供三种类型的循环语句:`for-in` 语句、`while` 语句和 `repeat-while` 语句。
|
||||
|
||||
通过 `break` 语句和 `continue` 语句可以改变循环语句的控制流。有关这两条语句,详情参阅 [Break 语句](#break-statement) 和 [Continue 语句](#continue-statement)。
|
||||
|
||||
> 循环语句语法
|
||||
>
|
||||
>
|
||||
|
||||
#### loop-statement {#loop-statement}
|
||||
> *循环语句* → [for-in 语句](#for-in-statement)
|
||||
>
|
||||
> *循环语句* → [while 语句](#while-statement)
|
||||
>
|
||||
> *循环语句* → [repeat-while 语句](#repeat-while-statement)
|
||||
>
|
||||
|
||||
### For-In 语句 {#for-in-statements}
|
||||
|
||||
`for-in` 语句会为集合(或实现了 [Sequence](https://developer.apple.com/documentation/swift/sequence) 协议的任意类型)中的每一项执行一次代码块。
|
||||
|
||||
`for-in` 语句的形式如下:
|
||||
|
||||
```swift
|
||||
for item in collection {
|
||||
statements
|
||||
}
|
||||
```
|
||||
|
||||
`for-in` 语句在循环开始前会调用集合表达式(`collection expression`)的 `makeIterator()` 方法来获取一个实现了 [IteratorProtocol](https://developer.apple.com/documentation/swift/iteratorprotocol) 协议的迭代器类型。接下来循环开始,反复调用该迭代器的 `next()` 方法。如果其返回值不是 `nil`,它将会被赋给 `item`,然后执行循环体语句,执行完毕后回到循环开始处,继续重复这一过程;否则,既不会赋值也不会执行循环体语句,`for-in` 语句至此执行完毕。
|
||||
|
||||
> for-in 语句语法
|
||||
>
|
||||
>
|
||||
|
||||
#### for-in-statement {#for-in-statement}
|
||||
> *for-in 语句* → **for** **case**<sub>可选</sub> [模式](./08_Patterns.md#pattern) **in** [表达式](./04_Expressions.md#expression) [where 子句](#where-clause)<sub>可选</sub> [代码块](./06_Declarations.md#code-block)
|
||||
>
|
||||
|
||||
### While 语句 {#while-statements}
|
||||
只要循环条件为真,`while` 语句就会重复执行代码块。
|
||||
|
||||
`while` 语句的形式如下:
|
||||
|
||||
```swift
|
||||
while condition {
|
||||
statements
|
||||
}
|
||||
```
|
||||
|
||||
`while` 语句的执行流程如下:
|
||||
|
||||
1. 判断条件(`condition`)的值。如果为 `true`,转到第 2 步;如果为 `false`,`while` 语句至此执行完毕。
|
||||
2. 执行循环体中的语句,然后重复第 1 步。
|
||||
|
||||
由于会在执行循环体中的语句前判断条件的值,因此循环体中的语句可能会被执行若干次,也可能一次也不会被执行。
|
||||
|
||||
条件的结果必须是 Bool 类型或者 Bool 的桥接类型。另外,条件语句也可以使用可选绑定,请参阅 [可选绑定](../02_language_guide/01_The_Basics.md#optional-binding)。
|
||||
|
||||
> while 语句语法
|
||||
>
|
||||
>
|
||||
|
||||
#### while-statement {#while-statement}
|
||||
> *while 语句* → **while** [条件子句](#condition-clause) [代码块](./06_Declarations.md#code-block)
|
||||
>
|
||||
|
||||
|
||||
#### condition-clause {#condition-clause}
|
||||
> *条件子句* → [表达式](./04_Expressions.md#expression) | [表达式](./04_Expressions.md#expression) **,** [条件列表](#condition-list)
|
||||
>
|
||||
|
||||
#### condition {#condition}
|
||||
> *条件* → [表达式](./04_Expressions.md#expression) |[可用性条件](#availability-condition) | [case 条件](#case-condition) | [可选绑定条件](#optional-binding-condition)
|
||||
>
|
||||
>
|
||||
|
||||
#### case-condition {#case-condition}
|
||||
> *case 条件* → **case** [模式](./08_Patterns.md#pattern) [构造器](./06_Declarations.md#initializer)
|
||||
>
|
||||
|
||||
#### optional-binding-condition {#optional-binding-condition}
|
||||
> *可选绑定条件* → **let** [模式](./08_Patterns.md#pattern) [构造器](./06_Declarations.md#initializer) | **var** [模式](./08_Patterns.md#pattern) [构造器](./06_Declarations.md#initializer)
|
||||
>
|
||||
|
||||
### Repeat-While 语句 {#repeat-while-statements}
|
||||
`repeat-while` 语句至少执行一次代码块,之后只要循环条件为真,就会重复执行代码块。
|
||||
|
||||
`repeat-while` 语句的形式如下:
|
||||
|
||||
```swift
|
||||
repeat {
|
||||
statements
|
||||
} while condition
|
||||
```
|
||||
|
||||
`repeat-while` 语句的执行流程如下:
|
||||
|
||||
1. 执行循环体中的语句,然后转到第 2 步。
|
||||
2. 判断条件的值。如果为 `true`,重复第 1 步;如果为 `false`,`repeat-while` 语句至此执行完毕。
|
||||
|
||||
由于条件的值是在循环体中的语句执行后才进行判断,因此循环体中的语句至少会被执行一次。
|
||||
|
||||
条件的结果必须是 Bool 类型或者 Bool 的桥接类型。另外,条件语句也可以使用可选绑定,请参阅 [可选绑定](../02_language_guide/01_The_Basics.md#optional-binding)。
|
||||
|
||||
> repeat-while 语句语法
|
||||
>
|
||||
>
|
||||
|
||||
#### repeat-while-statement {#repeat-while-statement}
|
||||
> *repeat-while 语句* → **repeat** [代码块](./06_Declarations.md#code-block) **while** [表达式](./04_Expressions.md#expression)
|
||||
>
|
||||
|
||||
## 分支语句 {#branch-statements}
|
||||
分支语句会根据一个或者多个条件来执行指定部分的代码。分支语句中的条件将会决定程序如何分支以及执行哪部分代码。Swift 提供三种类型的分支语句:`if` 语句、 `guard` 语句和 `switch` 语句。
|
||||
|
||||
`if` 语句和 `switch` 语句中的控制流可以用 `break` 语句改变,请参阅 [Break 语句](#break-statement)。
|
||||
|
||||
> 分支语句语法
|
||||
>
|
||||
>
|
||||
|
||||
#### branch-statement {#branch-statement}
|
||||
> *分支语句* → [if 语句](#if-statement)
|
||||
>
|
||||
> *分支语句* → [guard 语句](#guard-statement)
|
||||
>
|
||||
> *分支语句* → [switch 语句](#switch-statement)
|
||||
>
|
||||
|
||||
### If 语句 {#if-statements}
|
||||
`if` 语句会根据一个或多个条件来决定执行哪一块代码。
|
||||
|
||||
`if` 语句有两种基本形式,无论哪种形式,都必须有花括号。
|
||||
|
||||
第一种形式是当且仅当条件为真时执行代码,像下面这样:
|
||||
|
||||
```swift
|
||||
if condition {
|
||||
statements
|
||||
}
|
||||
```
|
||||
|
||||
第二种形式是在第一种形式的基础上添加 `else` 语句(通过引入 `else` 关键字),并且用于:当条件为真时执行一部分代码,当这同一个条件为假的时候执行另一部分代码。当只有一个 `else` 语句时,`if` 语句具有以下的形式:
|
||||
|
||||
```swift
|
||||
if condition {
|
||||
statements to execute if condition is true
|
||||
} else {
|
||||
statements to execute if condition is false
|
||||
}
|
||||
```
|
||||
|
||||
`if` 语句的 `else` 语句也可包含另一个 `if` 语句,从而形成一条链来测试更多的条件,像下面这样:
|
||||
|
||||
```swift
|
||||
if condition 1 {
|
||||
statements to execute if condition 1 is true
|
||||
} else if condition 2 {
|
||||
statements to execute if condition 2 is true
|
||||
} else {
|
||||
statements to execute if both conditions are false
|
||||
}
|
||||
```
|
||||
|
||||
`if` 语句中条件的结果必须是 Bool 类型或者 Bool 的桥接类型。另外,条件语句也可以使用可选绑定,请参阅 [可选绑定](../02_language_guide/01_The_Basics.md#optional-binding)。
|
||||
|
||||
> if 语句语法
|
||||
>
|
||||
>
|
||||
|
||||
#### if-statement {#if-statement}
|
||||
> *if 语句* → **if** [条件子句](#condition-clause) [代码块](./06_Declarations.md#code-block) [else 子句](#else-clause)<sub>可选</sub>
|
||||
>
|
||||
|
||||
#### else-clause {#else-clause}
|
||||
> *else 子句* → **else** [代码块](./06_Declarations.md#code-block) | **else** [if 语句](#if-statement)
|
||||
|
||||
### Guard 语句 {#guard-statements}
|
||||
如果一个或者多个条件不成立,可用 `guard` 语句来退出当前作用域。
|
||||
|
||||
`guard` 语句的格式如下:
|
||||
|
||||
```swift
|
||||
guard condition else {
|
||||
statements
|
||||
}
|
||||
```
|
||||
|
||||
`guard` 语句中条件的结果必须是 Bool 类型或者 Bool 的桥接类型。另外,条件也可以是一条可选绑定,请参阅 [可选绑定](../02_language_guide/01_The_Basics.md#optional-binding)。
|
||||
|
||||
在 `guard` 语句中进行可选绑定的任何常量或者变量,其可用范围从声明开始直到作用域结束。
|
||||
|
||||
`guard` 语句必须有 `else` 子句,而且必须在该子句中调用返回类型是 `Never` 的函数,或者使用下面的语句退出当前作用域:
|
||||
|
||||
* `return`
|
||||
* `break`
|
||||
* `continue`
|
||||
* `throw`
|
||||
|
||||
关于控制转移语句,请参阅 [控制转移语句](#control-transfer-statements)。关于 `Never` 返回类型的函数,请参阅 [永不返回的函数](./06_Declarations.md#rethrowing-functions-and-methods)。
|
||||
|
||||
> guard 语句语法
|
||||
>
|
||||
>
|
||||
|
||||
#### guard-statement {#guard-statement}
|
||||
> *guard 语句* → **guard** [条件子句](#condition-clause) **else** [代码块](./06_Declarations.md#code-block)
|
||||
|
||||
### Switch 语句 {#switch-statements}
|
||||
|
||||
`switch` 语句会根据控制表达式的值来决定执行哪部分代码。
|
||||
|
||||
`switch` 语句的形式如下:
|
||||
|
||||
```swift
|
||||
switch control expression {
|
||||
case pattern 1:
|
||||
statements
|
||||
case pattern 2 where condition:
|
||||
statements
|
||||
case pattern 3 where condition,
|
||||
pattern 4 where condition:
|
||||
statements
|
||||
default:
|
||||
statements
|
||||
}
|
||||
```
|
||||
|
||||
`switch` 语句会先计算*控制表达式*的值,然后与每一个 `case` 的模式进行匹配。如果匹配成功,程序将会执行对应的 `case` 中的语句。另外,每一个 `case` 的作用域都不能为空,也就是说在每一个 `case` 的冒号(`:`)后面必须至少有一条语句。如果你不想在匹配到的 `case` 中执行代码,只需在该 `case` 中写一条 `break` 语句即可。
|
||||
|
||||
可以用作控制表达式的值是十分灵活的。除了标量类型外,如 `Int`、`Character`,你可以使用任何类型的值,包括浮点数、字符串、元组、自定义类型的实例和可选类型。控制表达式的值还可以用来匹配枚举类型中的成员值或是检查该值是否包含在指定的 `Range` 中。关于如何在 `switch` 语句中使用这些类型,请参阅 [控制流](../02_language_guide/05_Control_Flow.md) 一章中的 [Switch](../02_language_guide/05_Control_Flow.md#switch)。
|
||||
|
||||
每个 `case` 的模式后面可以有一个 `where` 子句。`where` 子句由 `where` 关键字紧跟一个提供额外条件的表达式组成。因此,当且仅当控制表达式匹配一个 `case` 的模式且 `where` 子句的表达式为真时,`case` 中的语句才会被执行。在下面的例子中,控制表达式只会匹配包含两个相等元素的元组,例如 `(1, 1)`:
|
||||
|
||||
```swift
|
||||
case let (x, y) where x == y:
|
||||
```
|
||||
|
||||
正如上面这个例子,也可以在模式中使用 `let`(或 `var`)语句来绑定常量(或变量)。这些常量(或变量)可以在对应的 `where` 子句以及 `case` 中的代码中使用。但是,如果一个 `case` 中含有多个模式,所有的模式必须包含相同的常量(或变量)绑定,并且每一个绑定的常量(或变量)必须在所有的条件模式中都有相同的类型。
|
||||
|
||||
`switch` 语句也可以包含默认分支,使用 `default` 关键字表示。只有所有 `case` 都无法匹配控制表达式时,默认分支中的代码才会被执行。一个 `switch` 语句只能有一个默认分支,而且必须在 `switch` 语句的最后面。
|
||||
|
||||
`switch` 语句中 `case` 的匹配顺序和源代码中的书写顺序保持一致。因此,当多个模式都能匹配控制表达式时,只有第一个匹配的 `case` 中的代码会被执行。
|
||||
|
||||
#### Switch 语句必须是详尽的
|
||||
|
||||
在 Swift 中,`switch` 语句中控制表达式的每一个可能的值都必须至少有一个 `case` 与之对应。在某些无法面面俱到的情况下(例如,表达式的类型是 `Int`),你可以使用 `default` 分支满足该要求。
|
||||
|
||||
#### 对未来枚举的 `case` 进行 `switch` {#future-case}
|
||||
非冻结枚举(`nonfronzen enumeration`)是一种特殊的枚举类型,它可能在未来会增加新的枚举 `case`,即使这时候你已经编译并且发布了你的应用,所以在 switch 非冻结枚举前需要深思熟虑。当一个库的作者们把一个枚举标记为非冻结的,这意味着他们保留了增加新的枚举 `case` 的权利,并且任何和这个枚举交互的代码都*必须*在无需重新编译的条件下能够处理那些未来可能新加入的 `case` 。只有演进模式的库代码、标准库代码、用 Swift 实现的 Apple 框架、C 以及 Objective-C 代码才能够声明非冻结枚举。更多关于冻结和非冻结枚举的内容,请参阅 [冻结](./07_Attributes.md#frozen)。
|
||||
|
||||
当你对未来枚举进行 switch 时,你总是需要有一个 `default case`,即使每种枚举类型都已经有对应的 `case` 了。你可以在 default 前标注 `@unknown`,意思是这个 `case` 应该只匹配未来加入的枚举 `case`。如果你的 `default case` 中匹配了任何在编译时就能确定的枚举 `case`,Swift 会抛出一个警告。这可以很好地提醒你库的作者已经新增了一种 `case`,并且你还没有去处理。
|
||||
|
||||
以下就是一个例子,我们对标准库的 [Mirror.AncestorRepresentation](https://developer.apple.com/documentation/swift/mirror/ancestorrepresentation) 枚举进行 switch 操作。每当有新的 `case` 加入,我们会得到一个警告,提示我们要去处理它。
|
||||
|
||||
```swift
|
||||
let representation: Mirror.AncestorRepresentation = .generated
|
||||
switch representation {
|
||||
case .customized:
|
||||
print("Use the nearest ancestor’s implementation.")
|
||||
case .generated:
|
||||
print("Generate a default mirror for all ancestor classes.")
|
||||
case .suppressed:
|
||||
print("Suppress the representation of all ancestor classes.")
|
||||
@unknown default:
|
||||
print("Use a representation that was unknown when this code was compiled.")
|
||||
}
|
||||
// Prints "Generate a default mirror for all ancestor classes."
|
||||
```
|
||||
|
||||
#### 不存在隐式落入
|
||||
|
||||
当匹配到的 `case` 中的代码执行完毕后,`switch` 语句会直接退出,而不会继续执行下一个 `case` 。这就意味着,如果你想执行下一个 `case`,需要显式地在当前 `case` 中使用 `fallthrough` 语句。关于 `fallthrough` 语句的更多信息,请参阅 [Fallthrough 语句](#fallthrough-statements)。
|
||||
|
||||
> switch 语句语法
|
||||
>
|
||||
>
|
||||
|
||||
#### switch-statement {#switch-statement}
|
||||
> *switch 语句* → **switch** [表达式](./04_Expressions.md#expression) **{** [switch-case 列表](#switch-cases)<sub>可选</sub> **}**
|
||||
>
|
||||
|
||||
#### switch-cases {#switch-cases}
|
||||
> *switch case 列表* → [switch-case](#switch-case) [switch-case 列表](#switch-cases)<sub>可选</sub>
|
||||
>
|
||||
|
||||
#### switch-case {#switch-case}
|
||||
> *switch case* → [case 标签](#case-label) [多条语句](#statements) | [default 标签](#default-label) [多条语句](#statements) | [conditional-switch-case](#conditional-switch-case-label)
|
||||
>
|
||||
|
||||
|
||||
#### case-label {#case-label}
|
||||
> *case 标签* → [属性](#switch-case-attributes-label)<sub>可选</sub> **case** [case 项列表](#case-item-list) **:**
|
||||
>
|
||||
|
||||
#### case-item-list {#case-item-list}
|
||||
> *case 项列表* → [模式](./08_Patterns.md#pattern) [where 子句](#where-clause)<sub>可选</sub> | [模式](./08_Patterns.md#pattern) [where 子句](#where-clause)<sub>可选</sub> **,** [case 项列表](#case-item-list)
|
||||
>
|
||||
|
||||
#### default-label {#default-label}
|
||||
> *default 标签* → [属性](#switch-case-attributes-label)<sub>可选</sub> **default** **:**
|
||||
>
|
||||
>
|
||||
|
||||
#### where-clause {#where-clause}
|
||||
> *where-clause* → **where** [where 表达式](#where-expression)
|
||||
>
|
||||
|
||||
#### where-expression {#where-expression}
|
||||
> *where-expression* → [表达式](./04_Expressions.md#expression)
|
||||
>
|
||||
>
|
||||
|
||||
#### grammar-conditional-switch-case {#grammar-conditional-switch-case}
|
||||
> *conditional-switch-case* → [switch-if-directive-clause](#switch-case-attributes-label) [switch-elseif-directive-clauses](#switch-case-attributes-label) <sub>可选</sub> [switch-else-directive-clause](#switch-case-attributes-label) <sub>可选</sub> [endif-directive](#switch-case-attributes-label)
|
||||
>
|
||||
|
||||
#### grammar-switch-if-directive-clause {#grammar-switch-if-directive-clause}
|
||||
> *switch-if-directive 语句* → [if-directive](#switch-case-attributes-label) [compilation-condition](#switch-case-attributes-label) [switch-cases](#switch-case-attributes-label) <sub>可选</sub>
|
||||
>
|
||||
|
||||
#### grammar-switch-elseif-directive-clauses {#grammar-switch-elseif-directive-clauses}
|
||||
> *switch-elseif-directive 语句(复数)* → [elseif-directive-clause](#switch-case-attributes-label) [switch-elseif-directive-clauses](#switch-case-attributes-label)<sub>可选</sub>
|
||||
>
|
||||
|
||||
#### grammar-switch-elseif-directive-clause {#grammar-switch-elseif-directive-clause}
|
||||
> *switch-elseif-directive 语句* → [elseif-directive](#switch-case-attributes-label) [compilation-condition](#switch-case-attributes-label) [switch-cases](#switch-case-attributes-label)<sub>可选</sub>
|
||||
>
|
||||
|
||||
#### grammar-switch-else-directive-clause {#grammar-switch-else-directive-clause}
|
||||
> *switch-else-directive 语句* → [else-directive](#switch-case-attributes-label) [switch-cases](#switch-case-attributes-label) <sub>可选</sub>
|
||||
>
|
||||
|
||||
## 带标签的语句 {#labeled-statements}
|
||||
你可以在循环语句或 `switch` 语句前面加上标签,它由标签名和紧随其后的冒号(`:`)组成。在 `break` 和 `continue` 后面跟上标签名可以显式地在循环语句或 `switch` 语句中改变相应的控制流。关于这两条语句用法,请参阅 [Break 语句](#break-statement) 和 [Continue 语句](#continue-statement)。
|
||||
|
||||
标签的作用域在该标签所标记的语句内。可以嵌套使用带标签的语句,但标签名必须唯一。
|
||||
|
||||
关于使用带标签的语句的例子,请参阅 [控制流](../02_language_guide/05_Control_Flow.md) 一章中的 [带标签的语句](../02_language_guide/05_Control_Flow.md#labeled-statements)。
|
||||
|
||||
> 带标签的语句语法
|
||||
>
|
||||
>
|
||||
|
||||
#### labeled-statement {#labeled-statement}
|
||||
> *带标签的语句* → [语句标签](#statement-label) [循环语句](#grammar-loop-statement)
|
||||
>
|
||||
> *带标签的语句* → [语句标签](#statement-label) [if 语句](#if-statement)
|
||||
>
|
||||
> *带标签的语句* → [语句标签](#statement-label) [switch 语句](#switch-statement)
|
||||
>
|
||||
> > *带标签的语句* → [语句标签](#statement-label) [do 语句](#sdo-statement)
|
||||
>
|
||||
|
||||
#### statement-label {#statement-label}
|
||||
> *语句标签* → [标签名称](#label-name) **:**
|
||||
>
|
||||
|
||||
#### label-name {#label-name}
|
||||
> *标签名称* → [标识符](./02_Lexical_Structure.md#identifier)
|
||||
>
|
||||
|
||||
## 控制转移语句 {#control-transfer-statements}
|
||||
控制转移语句能够无条件地把控制权从一片代码转移到另一片代码,从而改变代码执行的顺序。Swift 提供五种类型的控制转移语句:`break` 语句、`continue` 语句、`fallthrough` 语句、`return` 语句和 `throw` 语句。
|
||||
|
||||
> 控制转移语句语法
|
||||
>
|
||||
>
|
||||
|
||||
#### control-transfer-statement {#control-transfer-statement}
|
||||
> *控制转移语句* → [break 语句](#break-statement)
|
||||
>
|
||||
> *控制转移语句* → [continue 语句](#continue-statement)
|
||||
>
|
||||
> *控制转移语句* → [fallthrough 语句](#fallthrough-statement)
|
||||
>
|
||||
> *控制转移语句* → [return 语句](#return-statement)
|
||||
>
|
||||
> *控制转移语句* → [throw 语句](#throw-statement)
|
||||
>
|
||||
|
||||
### Break 语句 {#break-statement}
|
||||
`break` 语句用于终止循环语句、`if` 语句或 `switch` 语句的执行。使用 `break` 语句时,可以只写 `break` 这个关键词,也可以在 `break` 后面跟上标签名,像下面这样:
|
||||
|
||||
> break
|
||||
>
|
||||
> break `label name`
|
||||
>
|
||||
|
||||
当 `break` 语句后面带标签名时,可用于终止由这个标签标记的循环语句、`if` 语句或 `switch` 语句的执行。
|
||||
|
||||
而只写 `break` 时,则会终止 `switch` 语句或 `break` 语句所属的最内层循环语句的执行。不能使用 `break` 语句来终止未使用标签的 `if` 语句。
|
||||
|
||||
无论哪种情况,控制权都会被转移给被终止的控制流语句后面的第一行语句。
|
||||
|
||||
关于使用 `break` 语句的例子,请参阅 [控制流](../02_language_guide/05_Control_Flow.md) 一章的 [Break](../02_language_guide/05_Control_Flow.md#break) 和 [带标签的语句](../02_language_guide/05_Control_Flow.md#labeled-statements)。
|
||||
|
||||
> break 语句语法
|
||||
>
|
||||
>
|
||||
|
||||
#### break-statement {#break-statement}
|
||||
> *break 语句* → **break** [标签名称](#label-name)<sub>可选</sub>
|
||||
>
|
||||
|
||||
### Continue 语句 {#continue-statement}
|
||||
`continue` 语句用于终止循环中当前迭代的执行,但不会终止该循环的执行。使用 `continue` 语句时,可以只写 `continue` 这个关键词,也可以在 `continue` 后面跟上标签名,像下面这样:
|
||||
|
||||
> continue
|
||||
>
|
||||
> continue `label name`
|
||||
>
|
||||
|
||||
当 `continue` 语句后面带标签名时,可用于终止由这个标签标记的循环中当前迭代的执行。
|
||||
|
||||
而当只写 `continue` 时,可用于终止 `continue` 语句所属的最内层循环中当前迭代的执行。
|
||||
|
||||
在这两种情况下,控制权都会被转移给循环语句的条件语句。
|
||||
|
||||
在 `for` 语句中,`continue` 语句执行后,增量表达式还是会被计算,这是因为每次循环体执行完毕后,增量表达式都会被计算。
|
||||
|
||||
关于使用 `continue` 语句的例子,请参阅 [控制流](../02_language_guide/05_Control_Flow.md) 一章的 [Continue](../02_language_guide/05_Control_Flow.md#continue) 和 [带标签的语句](../02_language_guide/05_Control_Flow.md#labeled-statements)。
|
||||
|
||||
> continue 语句语法
|
||||
>
|
||||
>
|
||||
|
||||
#### continue-statement {#continue-statement}
|
||||
> *continue 语句* → **continue** [标签名称](#label-name)<sub>可选</sub>
|
||||
>
|
||||
|
||||
### Fallthrough 语句 {#fallthrough-statements}
|
||||
`fallthrough` 语句用于在 `switch` 语句中转移控制权。`fallthrough` 语句会把控制权从 `switch` 语句中的一个 `case` 转移到下一个 `case`。这种控制权转移是无条件的,即使下一个 `case` 的模式与 `switch` 语句的控制表达式的值不匹配。
|
||||
|
||||
`fallthrough` 语句可出现在 `switch` 语句中的任意 `case` 中,但不能出现在最后一个 `case` 中。同时,`fallthrough` 语句也不能把控制权转移到使用了值绑定的 `case`。
|
||||
|
||||
关于在 `switch` 语句中使用 `fallthrough` 语句的例子,请参阅 [控制流](../02_language_guide/05_Control_Flow.md) 一章的 [控制转移语句](../02_language_guide/05_Control_Flow.md#control-transfer-statements)。
|
||||
|
||||
> fallthrough 语句语法
|
||||
>
|
||||
>
|
||||
|
||||
#### fallthrough-statement {#fallthrough-statement}
|
||||
> *fallthrough 语句* → **fallthrough**
|
||||
>
|
||||
|
||||
### Return 语句 {#return-statements}
|
||||
`return` 语句用于在函数或方法的实现中将控制权转移到调用函数或方法,接着程序将会从调用位置继续向下执行。
|
||||
|
||||
使用 `return` 语句时,可以只写 `return` 这个关键词,也可以在 `return` 后面跟上表达式,像下面这样:
|
||||
|
||||
> return
|
||||
>
|
||||
> return `expression`
|
||||
>
|
||||
|
||||
当 `return` 语句后面带表达式时,表达式的值将会返回给调用函数或方法。如果表达式的值的类型与函数或者方法声明的返回类型不匹配,Swift 则会在返回表达式的值之前将表达式的值的类型转换为返回类型。
|
||||
|
||||
> 注意
|
||||
>
|
||||
>
|
||||
> 正如 [可失败构造器](./06_Declarations.md#failable-initializers) 中所描述的,`return nil` 在可失败构造器中用于表明构造失败。
|
||||
>
|
||||
|
||||
而只写 `return` 时,仅仅是从该函数或方法中返回,而不返回任何值(也就是说,函数或方法的返回类型为 `Void` 或者说 `()`)。
|
||||
|
||||
> return 语句语法
|
||||
>
|
||||
>
|
||||
|
||||
#### return-statement {#return-statement}
|
||||
> *return 语句* → **return** [表达式](./04_Expressions.md#expression)<sub>可选</sub>
|
||||
|
||||
### Throw 语句 {#throw-statements}
|
||||
|
||||
`throw` 语句出现在抛出函数或者抛出方法体内,或者类型被 `throws` 关键字标记的闭包表达式体内。
|
||||
|
||||
`throw` 语句使程序在当前作用域结束执行,并向外围作用域传播错误。抛出的错误会一直传递,直到被 `do` 语句的 `catch` 子句处理掉。
|
||||
|
||||
`throw` 语句由 `throw` 关键字紧跟一个表达式组成,如下所示:
|
||||
|
||||
> throw `expression`
|
||||
>
|
||||
|
||||
表达式的结果必须符合 `ErrorType` 协议。
|
||||
|
||||
关于如何使用 `throw` 语句的例子,请参阅 [错误处理](../02_language_guide/17_Error_Handling.md) 一章的 [用 throwing 函数传递错误](../02_language_guide/17_Error_Handling.md#propagating-errors-using-throwing-functions)。
|
||||
|
||||
> throw 语句语法
|
||||
>
|
||||
>
|
||||
|
||||
#### throw-statement {#throw-statement}
|
||||
> *throw 语句* → **throw** [表达式](./04_Expressions.md#expression)
|
||||
>
|
||||
|
||||
## Defer 语句 {#defer-statements}
|
||||
`defer` 语句用于在退出当前作用域之前执行代码。
|
||||
|
||||
`defer` 语句形式如下:
|
||||
|
||||
```swift
|
||||
defer {
|
||||
statements
|
||||
}
|
||||
```
|
||||
|
||||
在 `defer` 语句中的语句无论程序控制如何转移都会被执行。在某些情况下,例如,手动管理资源时,比如关闭文件描述符,或者即使抛出了错误也需要执行一些操作时,就可以使用 `defer` 语句。
|
||||
|
||||
如果多个 `defer` 语句出现在同一作用域内,那么它们执行的顺序与出现的顺序相反。给定作用域中的第一个 `defer` 语句,会在最后执行,这意味着代码中最靠后的 `defer` 语句中引用的资源可以被其他 `defer` 语句清理掉。
|
||||
|
||||
```swift
|
||||
func f() {
|
||||
defer { print("First") }
|
||||
defer { print("Second") }
|
||||
defer { print("Third") }
|
||||
}
|
||||
f()
|
||||
// 打印“Third”
|
||||
// 打印“Second”
|
||||
// 打印“First”
|
||||
```
|
||||
|
||||
`defer` 语句中的语句无法将控制权转移到 `defer` 语句外部。
|
||||
|
||||
> defer 语句语法
|
||||
>
|
||||
>
|
||||
|
||||
#### defer-statement {#defer-statement}
|
||||
> *延迟语句* → **defer** [代码块](./06_Declarations.md#code-block)
|
||||
>
|
||||
|
||||
## Do 语句 {#do-statements}
|
||||
`do` 语句用于引入一个新的作用域,该作用域中可以含有一个或多个 `catch` 子句,`catch` 子句中定义了一些匹配错误条件的模式。`do` 语句作用域内定义的常量和变量只能在 `do` 语句作用域内使用。
|
||||
|
||||
Swift 中的 `do` 语句与 C 中限定代码块界限的大括号(`{}`)很相似,也并不会降低程序运行时的性能。
|
||||
|
||||
`do` 语句的形式如下:
|
||||
|
||||
```swift
|
||||
do {
|
||||
try expression
|
||||
statements
|
||||
} catch pattern 1 {
|
||||
statements
|
||||
} catch pattern 2 where condition {
|
||||
statements
|
||||
} catch pattern 3, pattern 4 where condition {
|
||||
statements
|
||||
} catch {
|
||||
statements
|
||||
}
|
||||
```
|
||||
|
||||
如果 `do` 代码块中的任何语句抛出了错误,程序会跳转到第一个能模式匹配该错误的 `catch` 子句。如果没有任何子句匹配,错误会传递到外层作作用域。如果错误在最顶层依旧没有被处理,程序执行会因为运行时错误而停止。
|
||||
|
||||
如同 `switch` 语句,编译器会判断 `catch` 子句是否有遗漏。如果 `catch` 子句没有遗漏,则认为错误已被处理。否则,错误会自动传递到外层作用域,被某个 `catch` 子句处理掉或者被用 `throws` 关键字声明的抛出函数继续向外抛出。
|
||||
|
||||
拥有多个模式匹配的 `catch` 子句只需其中一个匹配到错误即可。如果 `catch` 子句拥有多个模式匹配,所有的模式必须包含相同的绑定常量或变量,并且每个 `catch` 子句里所有绑定的变量或常量的类型必须相同。
|
||||
|
||||
为了确保错误已经被处理,可以让 `catch` 子句使用匹配所有错误的模式,如通配符模式(`_`)。如果一个 `catch` 子句不指定一种具体模式,`catch` 子句会匹配任何错误,并绑定到名为 `error` 的局部常量。有关在 `catch` 子句中使用模式的更多信息,请参阅 [模式](./08_Patterns.md)。
|
||||
|
||||
关于如何在 `do` 语句中使用一系列 `catch` 子句的例子,请参阅 [错误处理](../02_language_guide/17_Error_Handling.md#handling-errors)。
|
||||
|
||||
> do 语句语法
|
||||
>
|
||||
>
|
||||
|
||||
#### do-statement {#do-statement}
|
||||
> *do 语句* → **do** [代码块](./06_Declarations.md#code-block) [多条 catch 子句](#catch-clauses)<sub>可选</sub>
|
||||
>
|
||||
|
||||
#### catch-clauses {#catch-clauses}
|
||||
> *多条 catch 子句* → [catch 子句](#catch-clause) [多条 catch 子句](#catch-clauses)<sub>可选</sub>
|
||||
>
|
||||
|
||||
#### catch-clause {#catch-clause}
|
||||
> *catch 子句* → **catch** [模式](./08_Patterns.md#pattern)<sub>可选</sub> [where 子句](#where-clause)<sub>可选</sub> [代码块](./06_Declarations.md#code-block)
|
||||
|
||||
#### catch-pattern-list{#catch-pattern-list}
|
||||
> *catch 模式列表* → [catch 模式](#catch-pattern) | [catch 模式](#catch-pattern) ,[catch 模式列表](#catch-pattern-list)
|
||||
|
||||
#### catch-pattern{#catch-pattern}
|
||||
> *catch 模式* → [模式](./08_Patterns.md#pattern) [where 子句](./05_Statements.md#where-clause)<sub>可选</sub>
|
||||
|
||||
## 编译器控制语句 {#compiler-control-statements}
|
||||
编译器控制语句允许程序改变编译器的行为。Swift 有三种编译器控制语句:条件编译语句、线路控制语句和编译时诊断语句。
|
||||
|
||||
> 编译器控制语句语法
|
||||
>
|
||||
>
|
||||
|
||||
#### compiler-control-statement {#compiler-control-statement}
|
||||
> *编译器控制语句* → [条件编译语句](#grammar-conditional-compilation-block)
|
||||
>
|
||||
> *编译器控制语句* → [线路控制语句](#line-control-statement)
|
||||
>
|
||||
> *编译器控制语句* → [诊断语句](#grammar-diagnostic-statement)
|
||||
>
|
||||
|
||||
### 条件编译代码块 {#Conditional-Compilation-Block}
|
||||
|
||||
条件编译代码块可以根据一个或多个配置来有条件地编译代码。
|
||||
|
||||
每一个条件编译代码块都以 `#if` 开始,`#endif` 结束。如下:
|
||||
|
||||
```swift
|
||||
#if compilation condition
|
||||
statements
|
||||
#endif
|
||||
```
|
||||
|
||||
和 `if` 语句的条件不同,编译配置的条件是在编译时进行判断的。只有编译配置在编译时判断为 `true` 的情况下,相应的语句才会被编译和执行。
|
||||
|
||||
编译配置可以是 `true` 和 `false` 的字面量,也可以是使用 `-D` 命令行标志的标识符,或者是下列表格中的任意一个平台检测函数。
|
||||
|
||||
| 函数 | 可用参数 |
|
||||
| --- | --- |
|
||||
| `os()` | `OSX`, `iOS`, `watchOS`, `tvOS`, `Linux` |
|
||||
| `arch()` | `i386`, `x86_64`, `arm`, `arm64` |
|
||||
| `swift()` | `>=` 或 `<` 后跟版本号 |
|
||||
| `compiler()` | `>=` 或 `<` 后跟版本号 |
|
||||
| `canImport()` | 模块名 |
|
||||
| `targetEnvironment()` | `simulator`,`macCatalyst` |
|
||||
|
||||
在 `swift()` 和 `compiler()` 之后的版本号包含有主版本号,可选副版本号,可选补丁版本号类似,并且用(`.`)来分隔。在比较符和版本号之间不能有空格,版本号与前面的函数相对应,比如 `compiler()` 对应的就是这个编译器的版本号,`swift()` 对应的就是你要编译的 `Swift` 语言的版本号。举个简单的例子,如果你在使用 `Swift 5` 的编译器,想编译 `Swift 4.2` ,可以看下面的例子:
|
||||
|
||||
```swift
|
||||
#if compiler(>=5)
|
||||
print("Compiled with the Swift 5 compiler or later")
|
||||
#endif
|
||||
#if swift(>=4.2)
|
||||
print("Compiled in Swift 4.2 mode or later")
|
||||
#endif
|
||||
#if compiler(>=5) && swift(<5)
|
||||
print("Compiled with the Swift 5 compiler or later in a Swift mode earlier than 5")
|
||||
#endif
|
||||
// 打印 "Compiled with the Swift 5 compiler or later"
|
||||
// 打印 "Compiled in Swift 4.2 mode or later"
|
||||
// 打印 "Compiled with the Swift 5 compiler or later in a Swift mode earlier than 5"
|
||||
```
|
||||
|
||||
`canImport()` 后面跟的变量是模块的名字,这里这个模块可能并不是每个平台上都存在的。使用它来检测是否可以导入这个模块,如果模块存在就返回 `true` 否则返回 `false` 。
|
||||
|
||||
`targetEnvironment()` 当为模拟器编译时返回 `true`,否则返回 `false` 。
|
||||
|
||||
> 注意
|
||||
>
|
||||
>
|
||||
> `arch(arm)` 平台检测函数在 ARM 64 位设备上不会返回 `true`。如果代码在 32 位的 iOS 模拟器上编译,`arch(i386)` 平台检测函数会返回 `true`。
|
||||
>
|
||||
|
||||
你可以使用逻辑操作符 `&&`、`||` 和 `!` 来组合多个编译配置,还可以使用圆括号来进行分组。
|
||||
|
||||
就像 `if` 语句一样,你可以使用 `#elseif` 子句来添加任意多个条件分支来测试不同的编译配置。你也可以使用 `#else` 子句来添加最终的条件分支。包含多个分支的编译配置语句例子如下:
|
||||
|
||||
```swift
|
||||
#if compilation condition 1
|
||||
statements to compile if compilation condition 1 is true
|
||||
#elseif compilation condition 2
|
||||
statements to compile if compilation condition 2 is true
|
||||
#else
|
||||
statements to compile if both compilation conditions are false
|
||||
#endif
|
||||
```
|
||||
|
||||
> 注意
|
||||
>
|
||||
>
|
||||
> 即使没有被编译,编译配置中的语句仍然会被解析。然而,唯一的例外是编译配置语句中包含语言版本检测函数:仅当 `Swift` 编译器版本和语言版本检测函数中指定的版本号匹配时,语句才会被解析。这种设定能确保旧的编译器不会尝试去解析新 Swift 版本的语法。
|
||||
>
|
||||
|
||||
|
||||
#### build-config-statement {#build-config-statement}
|
||||
> 条件编译代码块语法
|
||||
>
|
||||
>
|
||||
|
||||
#### grammar-conditional-compilation-block {#grammar-conditional-compilation-block}
|
||||
> *条件编译代码块* → [if-directive 语句](#grammar-if-directive-clause) [elseif-directive 语句(复数)](#grammar-elseif-directive-clauses)<sub>可选</sub> [else-directive 语句](#grammar-else-directive-clause)<sub>可选</sub> [endif-directive](#grammar-endif-directive)
|
||||
>
|
||||
|
||||
#### grammar-if-directive-clause {#grammar-if-directive-clause}
|
||||
> *if-directive 语句* → [if-directive](#grammar-if-directive) [编译条件](#compilation-condition) [语句(复数)](#statements)<sub>可选</sub>
|
||||
>
|
||||
|
||||
#### grammar-elseif-directive-clauses {#grammar-elseif-directive-clauses}
|
||||
> *elseif-directive 语句(复数)* → [elseif-directive 语句](#grammar-elseif-directive-clause) [elseif-directive 语句(复数)](#grammar-elseif-directive-clauses)
|
||||
>
|
||||
|
||||
#### grammar-elseif-directive-clauses {#grammar-elseif-directive-clauses}
|
||||
> *elseif-directive 语句* → [elseif-directive](#grammar-elseif-directive) [编译条件](#compilation-condition) [语句(复数)](#statements)<sub>可选</sub>
|
||||
>
|
||||
|
||||
#### grammar-else-directive-clause {#grammar-else-directive-clause}
|
||||
> *else-directive 语句* → [else-directive](#grammar-else-directive) [语句(复数)](#statements)<sub>可选</sub>
|
||||
>
|
||||
|
||||
|
||||
> *if-directive* → **#if**
|
||||
>
|
||||
> *elseif-directive* → **#elseif**
|
||||
>
|
||||
> *else-directive* → **#else**
|
||||
>
|
||||
> *endif-directive* → **#endif**
|
||||
>
|
||||
|
||||
|
||||
#### compilation-condition {#compilation-condition}
|
||||
> *编译条件* → [平台条件](#grammar-platform-condition)
|
||||
>
|
||||
> *编译条件* → [标识符](./02_Lexical_Structure.md#identifier)
|
||||
>
|
||||
> *编译条件* → [布尔值字面量](./02_Lexical_Structure.md#boolean-literal)
|
||||
>
|
||||
> *编译条件* → **(** [编译条件](#compilation-condition) **)**
|
||||
>
|
||||
> *编译条件* → **!** [编译条件](#compilation-condition)
|
||||
>
|
||||
> *编译条件* → [编译条件](#compilation-condition) **&&** [编译条件](#compilation-condition)
|
||||
>
|
||||
> *编译条件* → [编译条件](#compilation-condition) **||** [编译条件](#compilation-condition)
|
||||
>
|
||||
|
||||
|
||||
#### grammar-platform-condition {#grammar-platform-condition}
|
||||
|
||||
#### grammar-platform-condition-os {#grammar-platform-condition-os}
|
||||
> *平台条件* → **os ( [操作系统](#operating-system) )**
|
||||
|
||||
#### grammar-platform-condition-arch {#grammar-platform-condition-arch}
|
||||
> *平台条件* → **arch ( [架构](#architecture) )**
|
||||
|
||||
#### grammar-platform-condition-swift {#grammar-platform-condition-swift}
|
||||
> *平台条件* → **swift ( >= [swift 版本](#swift-version) )** | **swift ( < [swift 版本](#swift-version) )**
|
||||
|
||||
#### grammar-platform-condition-compiler {#grammar-platform-condition-compiler}
|
||||
> *平台条件* → **compiler ( >= [swift 版本](#swift-version) )** | **compiler ( < [swift 版本](#swift-version) )**
|
||||
|
||||
#### grammar-platform-condition-canImport {#grammar-platform-condition-canImport}
|
||||
> *平台条件* → **canImport ( [模块名](#grammar-module-name) )**
|
||||
|
||||
#### grammar-platform-condition-targetEnvironment {#grammar-platform-condition-targetEnvironment}
|
||||
> *平台条件* → **targetEnvironment ( [环境](#grammar-environment) )**
|
||||
|
||||
#### operating-system {#operating-system}
|
||||
> *操作系统* → **macOS** | **iOS** | **watchOS** | **tvOS**
|
||||
>
|
||||
|
||||
#### architecture {#architecture}
|
||||
> *架构* → **i386** | **x86_64** | **arm** | **arm64**
|
||||
>
|
||||
|
||||
#### swift-version {#swift-version}
|
||||
> *swift 版本* → [十进制数字](./02_Lexical_Structure.md#decimal-digit) **.** [swift 版本延续](#grammar-swift-version-continuation) <sub>可选</sub>
|
||||
|
||||
#### grammar-swift-version-continuation {#grammar-swift-version-continuation}
|
||||
> *swift 版本延续* → **.** [十进制数字](./02_Lexical_Structure.md#decimal-digit) [swift 版本延续](#grammar-swift-version-continuation) <sub>可选</sub>
|
||||
|
||||
#### grammar-module-name {#grammar-module-name}
|
||||
> *模块名* → [identifier](./02_Lexical_Structure.md#identifier)
|
||||
|
||||
#### grammar-environment {#grammar-environment}
|
||||
|
||||
> *环境* → **模拟器** | **macCatalyst**
|
||||
|
||||
### 行控制语句 {#line-control-statements}
|
||||
行控制语句可以为被编译的源代码指定行号和文件名,从而改变源代码的定位信息,以便进行分析和调试。
|
||||
|
||||
行控制语句形式如下:
|
||||
|
||||
> #sourceLocation(file: file path, line: line number)
|
||||
>
|
||||
> #sourceLocation()
|
||||
|
||||
第一种的行控制语句会改变该语句之后的代码中的字面量表达式 `#line`、 `#file` 和 `#filePath` 所表示的值,从行控制语句里行号的代码开始。`行号` 是一个大于 0 的整形字面量,会改变 `#line` 表达式的值。`文件名` 是一个字符串字面量,会改变 `#file` 和 `#filePath` 表达式的值。指定的字符串会变成 `#filePath` 的值,且字符串最后的路径部分会变成 `#file` 的值。
|
||||
|
||||
第二种的行控制语句,`#sourceLocation()`,会将源代码的定位信息重置回默认的行号和文件名。
|
||||
|
||||
|
||||
#### line-control-statement {#line-control-statement}
|
||||
> 行控制语句语法
|
||||
>
|
||||
>*行控制语句* → **#sourceLocation(file:[文件名](#file-name),line:[行号](#line-number))**
|
||||
>
|
||||
> *行控制语句* → **#sourceLocation( )**
|
||||
|
||||
#### line-number {#line-number}
|
||||
|
||||
> *行号* → 大于 0 的十进制整数
|
||||
>
|
||||
|
||||
#### file-name {#file-name}
|
||||
> *文件名* → [静态字符串字面量](./02_Lexical_Structure.md#static-string-literal)
|
||||
|
||||
### 编译时诊断语句 {#compile-time-diagnostic-statement}
|
||||
|
||||
编译时诊断语句允许编译器在编译的时候可以发出错误或者警告。语句形式如下:
|
||||
|
||||
```swift
|
||||
#error("error message")
|
||||
#warning("warning message")
|
||||
```
|
||||
|
||||
第一句会抛出错误信息并终止编译,第二句会发出警告信息但是编译会继续进行。你可以通过静态字符串字面量来书写诊断信息,静态字符串字面量不能使用字符串 `interpolation` 或者 `concatenation`,但可以使用多行的形式。
|
||||
|
||||
> 编译时诊断语句语法
|
||||
>
|
||||
>
|
||||
|
||||
#### grammar-compile-time-diagnostic-statement {#grammar-compile-time-diagnostic-statement}
|
||||
> *诊断语句* → **#error** **(** [诊断消息](#grammar-diagnostic-message) **)**
|
||||
>
|
||||
> *诊断语句* → **#warning** **(** [诊断消息](#grammar-diagnostic-message) **)**
|
||||
>
|
||||
> *诊断语句* → [静态字符串字面量](./02_Lexical_Structure.md#static-string-literal)
|
||||
|
||||
## 可用性条件 {#availability-condition}
|
||||
|
||||
可用性条件可作为 `if`,`while`,`guard` 语句的条件,可以在运行时基于特定的平台参数来查询 API 的可用性。
|
||||
|
||||
可用性条件的形式如下:
|
||||
|
||||
```swift
|
||||
if #available(platform name version, ..., *) {
|
||||
statements to execute if the APIs are available
|
||||
} else {
|
||||
fallback statements to execute if the APIs are unavailable
|
||||
}
|
||||
```
|
||||
|
||||
使用可用性条件来执行一个代码块时,取决于使用的 API 在运行时是否可用,编译器会根据可用性条件提供的信息来决定是否执行相应的代码块。
|
||||
|
||||
可用性条件使用一系列逗号分隔的平台名称和版本。使用 `iOS`,`OSX`,以及 `watchOS` 等作为平台名称,并写上相应的版本号。`*` 参数是必须写的,用于处理未来的潜在平台。可用性条件确保了运行时的平台不低于条件中指定的平台版本时才执行代码块。
|
||||
|
||||
与布尔类型的条件不同,不能用逻辑运算符 `&&` 和 `||` 组合可用性条件。
|
||||
|
||||
> 可用性条件语法
|
||||
>
|
||||
>
|
||||
|
||||
#### availability-condition {#availability-condition}
|
||||
> *可用性条件* → **#available** **(** [可用性参数列表](#availability-arguments) **)**
|
||||
|
||||
#### availability-arguments {#availability-arguments}
|
||||
> *可用性参数列表* → [可用性参数](#availability-argument) | [可用性参数](#availability-argument) **,** [可用性参数列表](#availability-arguments)
|
||||
|
||||
#### availability-argument {#availability-argument}
|
||||
> *可用性参数* → [平台名称](#platform-name) [平台版本](#platform-version)
|
||||
>
|
||||
> *可用性条件* → __*__
|
||||
>
|
||||
>
|
||||
|
||||
#### platform-name {#platform-name}
|
||||
> *平台名称* → **iOS** | **iOSApplicationExtension**
|
||||
>
|
||||
> *平台名称* → **OSX** | **macOSApplicationExtension**
|
||||
>
|
||||
> *平台名称* → **watchOS**
|
||||
>
|
||||
> *平台名称* → **tvOS**
|
||||
>
|
||||
|
||||
#### platform-version {#platform-version}
|
||||
> *平台版本* → [十进制数字](./02_Lexical_Structure.md#decimal-digits)
|
||||
>
|
||||
> *平台版本* → [十进制数字](./02_Lexical_Structure.md#decimal-digits) **.** [十进制数字](./02_Lexical_Structure.md#decimal-digits)
|
||||
>
|
||||
> *平台版本* → [十进制数字](./02_Lexical_Structure.md#decimal-digits) **.** [十进制数字](./02_Lexical_Structure.md#decimal-digits) **.** [十进制数字](./02_Lexical_Structure.md#decimal-digits)
|
||||
1842
source/03_language_reference/06_Declarations.md
Executable file
1842
source/03_language_reference/06_Declarations.md
Executable file
File diff suppressed because it is too large
Load Diff
505
source/03_language_reference/07_Attributes.md
Executable file
505
source/03_language_reference/07_Attributes.md
Executable file
@ -0,0 +1,505 @@
|
||||
# 特性(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
|
||||
// 首发版本
|
||||
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(dynamicMemberLookup:)` 下标。
|
||||
|
||||
在显式成员表达式中,如果指定成员没有相应的声明,则该表达式被理解为对该类型的 `subscript(dynamicMemberLookup:)` 下标调用,将有关该成员的信息作为参数传递。下标接收参数既可以是键路径,也可以是成员名称字符串;如果你同时实现这两种方式的下标调用,那么以键路径参数方式为准。
|
||||
|
||||
`subscript(dynamicMemberLookup:)` 实现允许接收 [`KeyPath`](https://developer.apple.com/documentation/swift/keypath),[`WritableKeyPath`](https://developer.apple.com/documentation/swift/writablekeypath) 或 [`ReferenceWritableKeyPath`](https://developer.apple.com/documentation/swift/referencewritablekeypath) 类型的键路径参数。它可以使用遵循 [`ExpressibleByStringLiteral`](https://developer.apple.com/documentation/swift/expressiblebystringliteral) 协议的类型作为参数来接受成员名 -- 通常情况下是 `String`。下标返回值类型可以为任意类型。
|
||||
|
||||
按成员名进行的动态成员查找可用于围绕编译时无法进行类型检查的数据创建包装类型,例如在将其他语言的数据桥接到 `Swift` 时。例如:
|
||||
|
||||
```swift
|
||||
@dynamicMemberLookup
|
||||
struct DynamicStruct {
|
||||
let dictionary = ["someDynamicMember": 325,
|
||||
"someOtherMember": 787]
|
||||
subscript(dynamicMember member: String) -> Int {
|
||||
return dictionary[member] ?? 1054
|
||||
}
|
||||
}
|
||||
let s = DynamicStruct()
|
||||
|
||||
// 使用动态成员查找
|
||||
let dynamic = s.someDynamicMember
|
||||
print(dynamic)
|
||||
// 打印“325”
|
||||
|
||||
// 直接调用底层下标
|
||||
let equivalent = s[dynamicMember: "someDynamicMember"]
|
||||
print(dynamic == equivalent)
|
||||
// 打印“true”
|
||||
```
|
||||
|
||||
根据键路径来动态地查找成员,可用于创建一个包裹数据的包装类型,该类型支持在编译时期进行类型检查。例如:
|
||||
|
||||
```swift
|
||||
struct Point { var x, y: Int }
|
||||
|
||||
@dynamicMemberLookup
|
||||
struct PassthroughWrapper<Value> {
|
||||
var value: Value
|
||||
subscript<T>(dynamicMember member: KeyPath<Value, T>) -> T {
|
||||
get { return value[keyPath: member] }
|
||||
}
|
||||
}
|
||||
|
||||
let point = Point(x: 381, y: 431)
|
||||
let wrapper = PassthroughWrapper(value: point)
|
||||
print(wrapper.x)
|
||||
```
|
||||
|
||||
### `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` 访问级别的声明。在内联函数内定义的函数和闭包都是隐式内联的,即使他们不能标记该特性。
|
||||
|
||||
### `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)
|
||||
```
|
||||
|
||||
### `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
|
||||
```
|
||||
|
||||
### `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` 访问级别修饰符。被标记为 `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)。
|
||||
|
||||
## 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> **}**
|
||||
>
|
||||
> *均衡令牌* → 任意标识符,关键字,字面量或运算符
|
||||
>
|
||||
> *均衡令牌* → 任意标点除了 **(**,**)**,**[**,**]**,**{**,或 **}**
|
||||
>
|
||||
253
source/03_language_reference/08_Patterns.md
Executable file
253
source/03_language_reference/08_Patterns.md
Executable file
@ -0,0 +1,253 @@
|
||||
# 模式(Patterns)
|
||||
|
||||
*模式*代表单个值或者复合值的结构。例如,元组 `(1, 2)` 的结构是由逗号分隔的,包含两个元素的列表。因为模式代表一种值的结构,而不是特定的某个值,你可以利用模式来匹配各种各样的值。比如,`(x, y)` 可以匹配元组 `(1, 2)`,以及任何含两个元素的元组。除了利用模式匹配一个值以外,你可以从复合值中提取出部分或全部值,然后分别把各个部分的值和一个常量或变量绑定起来。
|
||||
|
||||
Swift 中的模式分为两类:一种能成功匹配任何类型的值,另一种在运行时匹配某个特定值时可能会失败。
|
||||
|
||||
第一类模式用于解构简单变量、常量和可选绑定中的值。此类模式包括通配符模式、标识符模式,以及包含前两种模式的值绑定模式和元组模式。你可以为这类模式指定一个类型注解,从而限制它们只能匹配某种特定类型的值。
|
||||
|
||||
第二类模式用于全模式匹配,这种情况下你试图匹配的值在运行时可能不存在。此类模式包括枚举用例模式、可选模式、表达式模式和类型转换模式。你在 `switch` 语句的 `case` 标签中,`do` 语句的 `catch` 子句中,或者在 `if`、`while`、`guard` 和 `for-in` 语句的 `case` 条件句中使用这类模式。
|
||||
|
||||
> 模式语法
|
||||
>
|
||||
|
||||
#### pattern {#pattern}
|
||||
> *模式* → [通配符模式](#wildcard-pattern) [类型注解](./03_Types.md#type-annotation)<sub>可选</sub>
|
||||
>
|
||||
> *模式* → [标识符模式](#identifier-pattern) [类型注解](./03_Types.md#type-annotation)<sub>可选</sub>
|
||||
>
|
||||
> *模式* → [值绑定模式](#value-binding-pattern)
|
||||
>
|
||||
> *模式* → [元组模式](#tuple-pattern) [类型注解](./03_Types.md#type-annotation)<sub>可选</sub>
|
||||
>
|
||||
> *模式* → [枚举用例模式](#enum-case-pattern)
|
||||
>
|
||||
> *模式* → [可选模式](#optional-pattern)
|
||||
>
|
||||
> *模式* → [类型转换模式](#type-casting-pattern)
|
||||
>
|
||||
> *模式* → [表达式模式](#expression-pattern)
|
||||
>
|
||||
|
||||
## 通配符模式(Wildcard Pattern) {#wildcard-pattern}
|
||||
|
||||
*通配符模式*由一个下划线(`_`)构成,用于匹配并忽略任何值。当你想忽略被匹配的值时可以使用该模式。例如,下面这段代码在闭区间 `1...3` 中迭代,每次迭代都忽略该区间的当前值:
|
||||
|
||||
```swift
|
||||
for _ in 1...3 {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
> 通配符模式语法
|
||||
>
|
||||
> #### wildcard-pattern {#wildcard-pattern}
|
||||
> *通配符模式* → **_**
|
||||
>
|
||||
|
||||
## 标识符模式(Identifier Pattern) {#identifier-pattern}
|
||||
*标识符模式*匹配任何值,并将匹配的值和一个变量或常量绑定起来。例如,在下面的常量声明中,`someValue` 是一个标识符模式,匹配了 `Int` 类型的 `42`:
|
||||
|
||||
```swift
|
||||
let someValue = 42
|
||||
```
|
||||
|
||||
当匹配成功时,`42` 被绑定(赋值)给常量 `someValue`。
|
||||
|
||||
如果一个变量或常量声明的左边是一个标识符模式,那么这个标识符模式是值绑定模式的子模式。
|
||||
|
||||
> 标识符模式语法
|
||||
>
|
||||
> #### identifier-pattern {#identifier-pattern}
|
||||
> *标识符模式* → [标识符](./02_Lexical_Structure.md#identifier)
|
||||
>
|
||||
|
||||
## 值绑定模式(Value-Binding Pattern) {#value-binding-pattern}
|
||||
*值绑定模式*把匹配到的值绑定给一个变量或常量。把匹配到的值绑定给常量时,用关键字 `let`,绑定给变量时,用关键字 `var`。
|
||||
|
||||
在值绑定模式中的标识符模式会把新命名的变量或常量与匹配到的值做绑定。例如,你可以拆开一个元组,然后把每个元素绑定到相应的标识符模式中。
|
||||
|
||||
```swift
|
||||
let point = (3, 2)
|
||||
switch point {
|
||||
// 将 point 中的元素绑定到 x 和 y
|
||||
case let (x, y):
|
||||
print("The point is at (\(x), \(y)).")
|
||||
}
|
||||
// 打印“The point is at (3, 2).”
|
||||
```
|
||||
|
||||
在上面这个例子中,`let` 会分配到元组模式 `(x, y)` 中的各个标识符模式。因此,`switch` 语句中 `case let (x, y):` 和 `case (let x, let y):` 的匹配效果是一样的。
|
||||
|
||||
> 值绑定模式语法
|
||||
>
|
||||
> #### value-binding-pattern {#value-binding-pattern}
|
||||
> *值绑定模式* → **var** [模式](#pattern) | **let** [模式](#pattern)
|
||||
>
|
||||
|
||||
## 元组模式 {#tuple-pattern}
|
||||
*元组模式*是由逗号分隔的,具有零个或多个模式的列表,并由一对圆括号括起来。元组模式匹配相应元组类型的值。
|
||||
|
||||
你可以使用类型注解去限制一个元组模式能匹配哪种元组类型。例如,在常量声明 `let (x, y): (Int, Int) = (1, 2)` 中的元组模式 `(x, y): (Int, Int)` 只匹配两个元素都是 `Int` 类型的元组。
|
||||
|
||||
当元组模式被用于 `for-in` 语句或者变量和常量声明时,它仅可以包含通配符模式、标识符模式、可选模式或者其他包含这些模式的元组模式。比如下面这段代码就不正确,因为 `(x, 0)` 中的元素 `0` 是一个表达式模式:
|
||||
|
||||
```swift
|
||||
let points = [(0, 0), (1, 0), (1, 1), (2, 0), (2, 1)]
|
||||
// 下面的代码是错误的
|
||||
for (x, 0) in points {
|
||||
/* ... */
|
||||
}
|
||||
```
|
||||
|
||||
只包含一个元素的元组模式的圆括号没有效果,模式只匹配这个单个元素的类型。举例来说,下面的语句是等效的:
|
||||
|
||||
```swift
|
||||
let a = 2 // a: Int = 2
|
||||
let (a) = 2 // a: Int = 2
|
||||
let (a): Int = 2 // a: Int = 2
|
||||
```
|
||||
|
||||
> 元组模式语法
|
||||
>
|
||||
> #### tuple-pattern {#tuple-pattern}
|
||||
> *元组模式* → **(** [元组模式元素列表](#tuple-pattern-element-list)<sub>可选</sub> **)**
|
||||
>
|
||||
|
||||
#### tuple-pattern-element-list {#tuple-pattern-element-list}
|
||||
> *元组模式元素列表* → [元组模式元素](#tuple-pattern-element) | [元组模式元素](#tuple-pattern-element) **,** [元组模式元素列表](#tuple-pattern-element-list)
|
||||
>
|
||||
> #### tuple-pattern-element {#tuple-pattern-element}
|
||||
> *元组模式元素* → [模式](#pattern)
|
||||
>
|
||||
|
||||
## 枚举用例模式(Enumeration Case Pattern) {#enumeration-case-pattern}
|
||||
*枚举用例模式*匹配现有的某个枚举类型的某个用例。枚举用例模式出现在 `switch` 语句中的 `case` 标签中,以及 `if`、`while`、`guard` 和 `for-in` 语句的 `case` 条件中。
|
||||
|
||||
如果你准备匹配的枚举用例有任何关联的值,则相应的枚举用例模式必须指定一个包含每个关联值元素的元组模式。关于使用 `switch` 语句来匹配包含关联值的枚举用例的例子,请参阅 [关联值](../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>
|
||||
>
|
||||
|
||||
## 可选模式(Optional Pattern) {#optional-pattern}
|
||||
*可选模式*匹配包装在一个 `Optional(Wrapped)` 或者 `ExplicitlyUnwrappedOptional(Wrapped)` 枚举中的 `Some(Wrapped)` 用例中的值。可选模式由一个标识符模式和紧随其后的一个问号组成,可以像枚举用例模式一样使用。
|
||||
|
||||
由于可选模式是 `Optional` 和 `ImplicitlyUnwrappedOptional` 枚举用例模式的语法糖,下面两种写法是等效的:
|
||||
|
||||
```swift
|
||||
let someOptional: Int? = 42
|
||||
// 使用枚举用例模式匹配
|
||||
if case .Some(let x) = someOptional {
|
||||
print(x)
|
||||
}
|
||||
|
||||
// 使用可选模式匹配
|
||||
if case let x? = someOptional {
|
||||
print(x)
|
||||
}
|
||||
```
|
||||
|
||||
可选模式为 `for-in` 语句提供了一种迭代数组的简便方式,只为数组中非 `nil` 的元素执行循环体。
|
||||
|
||||
```swift
|
||||
let arrayOfOptionalInts: [Int?] = [nil, 2, 3, nil, 5]
|
||||
// 只匹配非 nil 的元素
|
||||
for case let number? in arrayOfOptinalInts {
|
||||
print("Found a \(number)")
|
||||
}
|
||||
// Found a 2
|
||||
// Found a 3
|
||||
// Found a 5
|
||||
```
|
||||
|
||||
> 可选模式语法
|
||||
>
|
||||
> #### optional-pattern {#optional-pattern}
|
||||
> *可选模式* → [标识符模式](./03_Types.md#type-identifier) **?**
|
||||
>
|
||||
|
||||
## 类型转换模式(Type-Casting Patterns) {#type-casting-patterns}
|
||||
有两种类型转换模式,`is` 模式和 `as` 模式。`is` 模式只出现在 `switch` 语句中的 `case` 标签中。`is` 模式和 `as` 模式形式如下:
|
||||
|
||||
> is `类型`
|
||||
>
|
||||
> `模式` as `类型`
|
||||
>
|
||||
|
||||
`is` 模式仅当一个值的类型在运行时和 `is` 模式右边的指定类型一致,或者是其子类的情况下,才会匹配这个值。`is` 模式和 `is` 运算符有相似表现,它们都进行类型转换,但是 `is` 模式没有返回类型。
|
||||
|
||||
`as` 模式仅当一个值的类型在运行时和 `as` 模式右边的指定类型一致,或者是其子类的情况下,才会匹配这个值。如果匹配成功,被匹配的值的类型被转换成 `as` 模式右边指定的类型。
|
||||
|
||||
关于使用 `switch` 语句配合 `is` 模式和 `as` 模式来匹配值的例子,请参阅 [Any 和 AnyObject 的类型转换](../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)
|
||||
>
|
||||
> #### is-pattern {#is-pattern}
|
||||
> *is 模式* → **is** [类型](./03_Types.md#type)
|
||||
>
|
||||
> #### as-pattern {#as-pattern}
|
||||
> *as 模式* → [模式](#pattern) **as** [类型](./03_Types.md#type)
|
||||
>
|
||||
|
||||
## 表达式模式(Expression Pattern) {#expression-pattern}
|
||||
*表达式模式*代表表达式的值。表达式模式只出现在 `switch` 语句中的 `case` 标签中。
|
||||
|
||||
表达式模式代表的表达式会使用 Swift 标准库中的 `~=` 运算符与输入表达式的值进行比较。如果 `~=` 运算符返回 `true`,则匹配成功。默认情况下,`~=` 运算符使用 `==` 运算符来比较两个相同类型的值。它也可以将一个整型数值与一个 `Range` 实例中的一段整数区间做匹配,正如下面这个例子所示:
|
||||
|
||||
```swift
|
||||
let point = (1, 2)
|
||||
switch point {
|
||||
case (0, 0):
|
||||
print("(0, 0) is at the origin.")
|
||||
case (-2...2, -2...2):
|
||||
print("(\(point.0), \(point.1)) is near the origin.")
|
||||
default:
|
||||
print("The point is at (\(point.0), \(point.1)).")
|
||||
}
|
||||
// 打印“(1, 2) is near the origin.”
|
||||
```
|
||||
|
||||
你可以重载 `~=` 运算符来提供自定义的表达式匹配行为。比如你可以重写上面的例子,将 `point` 表达式与字符串形式表示的点进行比较。
|
||||
|
||||
```swift
|
||||
// 重载 ~= 运算符对字符串和整数进行比较
|
||||
func ~=(pattern: String, value: Int) -> Bool {
|
||||
return pattern == "\(value)"
|
||||
}
|
||||
|
||||
switch point {
|
||||
case ("0", "0"):
|
||||
print("(0, 0) is at the origin.")
|
||||
default:
|
||||
print("The point is at (\(point.0), \(point.1)).")
|
||||
}
|
||||
// 打印“The point is at (1, 2).”
|
||||
```
|
||||
|
||||
> 表达式模式语法
|
||||
>
|
||||
> #### expression-pattern {#expression-pattern}
|
||||
> *表达式模式* → [表达式](./04_Expressions.md#expression)
|
||||
>
|
||||
148
source/03_language_reference/09_Generic_Parameters_and_Arguments.md
Executable file
148
source/03_language_reference/09_Generic_Parameters_and_Arguments.md
Executable file
@ -0,0 +1,148 @@
|
||||
# 泛型参数(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)
|
||||
>
|
||||
1681
source/03_language_reference/10_Summary_of_the_Grammar.md
Executable file
1681
source/03_language_reference/10_Summary_of_the_Grammar.md
Executable file
File diff suppressed because it is too large
Load Diff
3
source/03_language_reference/chapter3.md
Executable file
3
source/03_language_reference/chapter3.md
Executable file
@ -0,0 +1,3 @@
|
||||
# Swift 语言参考
|
||||
|
||||
本章描述了 Swift 的语言参考。
|
||||
278
source/04_revision_history/04_revision_history.md
Normal file
278
source/04_revision_history/04_revision_history.md
Normal file
@ -0,0 +1,278 @@
|
||||
# Swift 文档修订历史
|
||||
|
||||
### 2020-02-05
|
||||
|
||||
* 更新至 Swift 5.2。
|
||||
* 在 [特殊名称方法](../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` 可以在更多上下文中使用。
|
||||
|
||||
### 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 文档修订历史。
|
||||
330
source/README.md
330
source/README.md
@ -1,76 +1,278 @@
|
||||
> 已同步更新到 Swift 2.2
|
||||
# Swift 文档修订历史
|
||||
|
||||
# 2.0 新的开始
|
||||
### 2020-02-05
|
||||
|
||||
> Swift 兴趣交流群:`131595168`, `146932759`, `151336833`, `153549217`. **加入一个群即可,请勿重复添加**
|
||||
* 更新至 Swift 5.2。
|
||||
* 在 [特殊名称方法](../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` 可以在更多上下文中使用。
|
||||
|
||||
> <a target='_blank' href="http://swiftweekly.cn">订阅 Swift 开发者周报,每周获取最新 Swift 资源</a>
|
||||
### 2019-09-10
|
||||
|
||||
<!-- -->
|
||||
> 如果您觉得这个项目不错,请<a target='_blank' href="https://github.com/numbbbbb/the-swift-programming-language-in-chinese">点击Star一下</a>,您的支持是我们最大的动力。
|
||||
* 更新至 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 标量更精确定义。
|
||||
|
||||
|
||||
## 1
|
||||
### 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
|
||||
|
||||
1.0发布之后,我们就再也没能和苹果的文档同步。语法错误、编译不通过、语言不通顺,阅读量直线下降,最低时每天只有不到1000人访问。
|
||||
* 更新至 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) 章节中删除了重复的运算符有效字符集合。
|
||||
|
||||
6月9日,calvingit发了一个issue“准备翻译2.0版本吗”,我没有回复,应该已经没人关注这个项目了吧,我想。
|
||||
### 2014-08-18
|
||||
|
||||
## 2
|
||||
* 描述 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) 的示例。
|
||||
|
||||
我错了。
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
在我没有任何回复的情况下,不到一天时间,有五位朋友报名。看到这些回复的时候我真的很惊讶,也很感动,无论这个项目存在多少问题,只要有人关注,有人愿意为它付出,那我还有什么理由放弃呢?
|
||||
|
||||
6月28日8点55分,Swift 2.0翻译正式启动。按下发送按钮后,我不停的刷新页面,半个小时过去了,一个回复都没有。“还是不行啊”“如果再过一个小时没人回复我就把issue删掉”,类似的念头不断出现,又不断消失。
|
||||
|
||||
9:35,xtymichael第一个回复,而且一下就认领了三篇!接下来就是不断的回复认领,到中午已经有超过一半章节被认领。
|
||||
|
||||
第二天早晨,37个章节全部认领完毕。
|
||||
|
||||
## 3
|
||||
|
||||
经过一个多月的努力,我们终于完成了文档的更新。听起来似乎没什么,确实,从1到n总是没有从0到1那么振奋人心。不过真正参与了才知道,修改往往比创造更麻烦,一个需要耐心,一个需要激情,前者往往得不到应有的重视。
|
||||
|
||||
但是我还是想尽最大可能去感谢他们,这个项目能走到今天,靠的不是我,是那个issue,是那些回复,是这几十个兄弟在工作学习的空闲敲下的每一个字符。而我能做的,只是在每篇文章的开头,那个所有人都会忽略的地方,加上他们的ID。
|
||||
|
||||
下次你再打开这篇文档,可以多看看那些列在最上方的ID,哪怕不去follow和star,只是看一眼就好。他们的所有努力和付出,就存在于这短暂的一瞥中。
|
||||
|
||||
Swift 2.0 参与者名单(按照章节顺序):
|
||||
- [xtymichael](https://github.com/xtymichael)
|
||||
- [AlanMelody](https://github.com/AlanMelody)
|
||||
- [DianQK](https://github.com/DianQK)
|
||||
- [dreamkidd](https://github.com/dreamkidd)
|
||||
- [100mango](https://github.com/100mango)
|
||||
- [futantan](https://github.com/futantan)
|
||||
- [SkyJean](https://github.com/SkyJean)
|
||||
- [yangsiy](https://github.com/yangsiy)
|
||||
- [shanksyang](https://github.com/shanksyang)
|
||||
- [chenmingbiao](https://github.com/chenmingbiao)
|
||||
- [Channe](https://github.com/Channe)
|
||||
- [lyojo](https://github.com/lyojo)
|
||||
- [SergioChan](https://github.com/SergioChan)
|
||||
- [mmoaay](https://github.com/mmoaay)
|
||||
- [buginux](https://github.com/buginux)
|
||||
- [KYawn](https://github.com/KYawn)
|
||||
- [EudeMorgen](https://github.com/EudeMorgen)
|
||||
- [littledogboy](https://github.com/littledogboy)
|
||||
- [Lenhoon](https://github.com/Lenhoon)
|
||||
- [ray16897188](https://github.com/ray16897188)
|
||||
- [wardenNScaiyi](https://github.com/wardenNScaiyi)
|
||||
- [miaosiqi](https://github.com/miaosiqi)
|
||||
- [Realank](https://github.com/Realank)
|
||||
|
||||
最后,感谢[极客学院](http://wiki.jikexueyuan.com)提供的wiki系统,在国内访问起来速度很快,优化后的样式看起来也更舒服。
|
||||
|
||||
@ -1,52 +1,49 @@
|
||||
# Summary
|
||||
|
||||
* [欢迎使用 Swift](chapter1/chapter1.md)
|
||||
* [关于 Swift](chapter1/01_swift.md)
|
||||
* [Swift 初见](chapter1/02_a_swift_tour.md)
|
||||
* [Swift 版本历史记录](chapter1/03_revision_history.md)
|
||||
* [Swift 1.0 发布内容](v1.0.md)
|
||||
* [Swift 教程](chapter2/chapter2.md)
|
||||
* [基础部分](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_Classes_and_Structures.md)
|
||||
* [属性](chapter2/10_Properties.md)
|
||||
* [方法](chapter2/11_Methods.md)
|
||||
* [下标](chapter2/12_Subscripts.md)
|
||||
* [继承](chapter2/13_Inheritance.md)
|
||||
* [构造过程](chapter2/14_Initialization.md)
|
||||
* [析构过程](chapter2/15_Deinitialization.md)
|
||||
* [自动引用计数](chapter2/16_Automatic_Reference_Counting.md)
|
||||
* [可选链](chapter2/17_Optional_Chaining.md)
|
||||
* [错误处理](chapter2/18_Error_Handling.md)
|
||||
* [类型转换](chapter2/19_Type_Casting.md)
|
||||
* [嵌套类型](chapter2/20_Nested_Types.md)
|
||||
* [扩展](chapter2/21_Extensions.md)
|
||||
* [协议](chapter2/22_Protocols.md)
|
||||
* [泛型](chapter2/23_Generics.md)
|
||||
* [访问控制](chapter2/24_Access_Control.md)
|
||||
* [高级运算符](chapter2/25_Advanced_Operators.md)
|
||||
* 欢迎使用 Swift
|
||||
* [关于 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 教程
|
||||
* [基础部分](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/10_Statements.md)
|
||||
* [声明](chapter3/05_Declarations.md)
|
||||
* [特性](chapter3/06_Attributes.md)
|
||||
* [模式](chapter3/07_Patterns.md)
|
||||
* [泛型参数](chapter3/08_Generic_Parameters_and_Arguments.md)
|
||||
* [语法总结](chapter3/09_Summary_of_the_Grammar.md)
|
||||
* 苹果官方Blog官方翻译
|
||||
* [Access Control 权限控制的黑与白](chapter4/01_Access_Control.md)
|
||||
* [造个类型不是梦-白话Swift类型创建](chapter4/02_Type_Custom.md)
|
||||
* [WWDC里面的那个“大炮打气球”](chapter4/03_Ballons.md)
|
||||
* [Swift与C语言指针友好合作](chapter4/04_Interacting_with_C_Pointers.md)
|
||||
* [引用类型和值类型的恩怨](chapter4/05_Value_and_Reference_Types.md)
|
||||
* [访问控制和Protected](chapter4/06_Access_Control_and_Protected.md)
|
||||
* [可选类型完美解决占位问题](chapter4/07_Optional_Case_Study.md)
|
||||
* [关于语言参考](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)
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
# 关于 Swift(About Swift)
|
||||
-----------------
|
||||
|
||||
> 1.0
|
||||
> 翻译:[numbbbbb](https://github.com/numbbbbb)
|
||||
> 校对:[yeahdongcn](https://github.com/yeahdongcn)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[xtymichael](https://github.com/xtymichael)
|
||||
|
||||
Swift 是一种新的编程语言,用于编写 iOS,OS X 和 watchOS应用程序。Swift 结合了 C 和 Objective-C 的优点并且不受 C 兼容性的限制。Swift 采用安全的编程模式并添加了很多新特性,这将使编程更简单,更灵活,也更有趣。Swift 是基于成熟而且倍受喜爱的 Cocoa 和 Cocoa Touch 框架,它的降临将重新定义软件开发。
|
||||
|
||||
Swift 的开发从很久之前就开始了。为了给 Swift 打好基础,苹果公司改进了编译器,调试器和框架结构。我们使用自动引用计数(Automatic Reference Counting, ARC)来简化内存管理。我们在 Foundation 和 Cocoa 的基础上构建框架栈使其完全现代化和标准化。
|
||||
Objective-C 本身支持块、集合语法和模块,所以框架可以轻松支持现代编程语言技术。正是得益于这些基础工作,我们现在才能发布这样一个用于未来苹果软件开发的新语言。
|
||||
|
||||
Objective-C 开发者对 Swift 并不会感到陌生。它采用了 Objective-C 的命名参数以及动态对象模型,可以无缝对接到现有的 Cocoa 框架,并且可以兼容 Objective-C 代码。在此基础之上,Swift 还有许多新特性并且支持过程式编程和面向对象编程。
|
||||
|
||||
Swift 对于初学者来说也很友好。它是第一个既满足工业标准又像脚本语言一样充满表现力和趣味的脚本语言。它支持代码预览,这个革命性的特性可以允许程序员在不编译和运行应用程序的前提下运行 Swift 代码并实时查看结果。
|
||||
|
||||
Swift 将现代编程语言的精华和苹果工程师文化的智慧结合了起来。编译器对性能进行了优化,编程语言对开发进行了优化,两者互不干扰,鱼与熊掌兼得。Swift 既可以用于开发 “hello, world” 这样的小程序,也可以用于开发一套完整的操作系统。所有的这些特性让 Swift 对于开发者和苹果来说都是一项值得的投资。
|
||||
|
||||
Swift 是编写 iOS,OS X 和 watchOS应用的极佳手段,并将伴随着新的特性和功能持续演进。我们对 Swift 充满信心,你还在等什么!
|
||||
@ -1,818 +0,0 @@
|
||||
# Swift 初见(A Swift Tour)
|
||||
|
||||
---
|
||||
|
||||
> 1.0
|
||||
> 翻译:[numbbbbb](https://github.com/numbbbbb)
|
||||
> 校对:[shinyzhu](https://github.com/shinyzhu), [stanzhai](https://github.com/stanzhai)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[xtymichael](https://github.com/xtymichael)
|
||||
|
||||
> 2.2
|
||||
> 翻译:[175](https://github.com/Brian175),2016-04-09
|
||||
|
||||
本页内容包括:
|
||||
|
||||
- [简单值(Simple Values)](#simple_values)
|
||||
- [控制流(Control Flow)](#control_flow)
|
||||
- [函数和闭包(Functions and Closures)](#functions_and_closures)
|
||||
- [对象和类(Objects and Classes)](#objects_and_classes)
|
||||
- [枚举和结构体(Enumerations and Structures)](#enumerations_and_structures)
|
||||
- [协议和扩展(Protocols and Extensions)](#protocols_and_extensions)
|
||||
- [错误处理(Error Handling)](#error_handling)
|
||||
- [泛型(Generics)](#generics)
|
||||
|
||||
通常来说,编程语言教程中的第一个程序应该在屏幕上打印“Hello, world”。在 Swift 中,可以用一行代码实现:
|
||||
|
||||
```swift
|
||||
print("Hello, world!")
|
||||
```
|
||||
|
||||
如果你写过 C 或者 Objective-C 代码,那你应该很熟悉这种形式——在 Swift 中,这行代码就是一个完整的程序。你不需要为了输入输出或者字符串处理导入一个单独的库。全局作用域中的代码会被自动当做程序的入口点,所以你也不需要`main()`函数。你同样不需要在每个语句结尾写上分号。
|
||||
|
||||
这个教程会通过一系列编程例子来让你对 Swift 有初步了解,如果你有什么不理解的地方也不用担心——任何本章介绍的内容都会在后面的章节中详细讲解。
|
||||
|
||||
> 注意:
|
||||
> 在 Mac 上,下载 Playground 并双击文件在 Xcode 里打开:[https://developer.apple.com/go/?id=swift-tour](https://developer.apple.com/go/?id=swift-tour)
|
||||
|
||||
<a name="simple_values"></a>
|
||||
## 简单值
|
||||
|
||||
使用`let`来声明常量,使用`var`来声明变量。一个常量的值,在编译的时候,并不需要有明确的值,但是你只能为它赋值一次。也就是说你可以用常量来表示这样一个值:你只需要决定一次,但是需要使用很多次。
|
||||
|
||||
```swift
|
||||
var myVariable = 42
|
||||
myVariable = 50
|
||||
let myConstant = 42
|
||||
```
|
||||
|
||||
常量或者变量的类型必须和你赋给它们的值一样。然而,你不用明确地声明类型,声明的同时赋值的话,编译器会自动推断类型。在上面的例子中,编译器推断出`myVariable`是一个整数(integer)因为它的初始值是整数。
|
||||
|
||||
如果初始值没有提供足够的信息(或者没有初始值),那你需要在变量后面声明类型,用冒号分割。
|
||||
|
||||
```swift
|
||||
let implicitInteger = 70
|
||||
let implicitDouble = 70.0
|
||||
let explicitDouble: Double = 70
|
||||
```
|
||||
|
||||
> 练习:
|
||||
> 创建一个常量,显式指定类型为`Float`并指定初始值为4。
|
||||
|
||||
值永远不会被隐式转换为其他类型。如果你需要把一个值转换成其他类型,请显式转换。
|
||||
|
||||
```swift
|
||||
let label = "The width is"
|
||||
let width = 94
|
||||
let widthLabel = label + String(width)
|
||||
```
|
||||
> 练习:
|
||||
> 删除最后一行中的`String`,错误提示是什么?
|
||||
|
||||
有一种更简单的把值转换成字符串的方法:把值写到括号中,并且在括号之前写一个反斜杠。例如:
|
||||
|
||||
```swift
|
||||
let apples = 3
|
||||
let oranges = 5
|
||||
let appleSummary = "I have \(apples) apples."
|
||||
let fruitSummary = "I have \(apples + oranges) pieces of fruit."
|
||||
```
|
||||
|
||||
> 练习:
|
||||
> 使用`\()`来把一个浮点计算转换成字符串,并加上某人的名字,和他打个招呼。
|
||||
|
||||
使用方括号`[]`来创建数组和字典,并使用下标或者键(key)来访问元素。最后一个元素后面允许有个逗号。
|
||||
|
||||
```swift
|
||||
var shoppingList = ["catfish", "water", "tulips", "blue paint"]
|
||||
shoppingList[1] = "bottle of water"
|
||||
|
||||
var occupations = [
|
||||
"Malcolm": "Captain",
|
||||
"Kaylee": "Mechanic",
|
||||
]
|
||||
occupations["Jayne"] = "Public Relations"
|
||||
```
|
||||
|
||||
要创建一个空数组或者字典,使用初始化语法。
|
||||
|
||||
```swift
|
||||
let emptyArray = [String]()
|
||||
let emptyDictionary = [String: Float]()
|
||||
```
|
||||
|
||||
如果类型信息可以被推断出来,你可以用`[]`和`[:]`来创建空数组和空字典——就像你声明变量或者给函数传参数的时候一样。
|
||||
|
||||
```swift
|
||||
shoppingList = []
|
||||
occupations = [:]
|
||||
```
|
||||
|
||||
<a name="control_flow"></a>
|
||||
## 控制流
|
||||
|
||||
使用`if`和`switch`来进行条件操作,使用`for-in`、`for`、`while`和`repeat-while`来进行循环。包裹条件和循环变量括号可以省略,但是语句体的大括号是必须的。
|
||||
|
||||
```swift
|
||||
let individualScores = [75, 43, 103, 87, 12]
|
||||
var teamScore = 0
|
||||
for score in individualScores {
|
||||
if score > 50 {
|
||||
teamScore += 3
|
||||
} else {
|
||||
teamScore += 1
|
||||
}
|
||||
}
|
||||
print(teamScore)
|
||||
```
|
||||
|
||||
在`if`语句中,条件必须是一个布尔表达式——这意味着像`if score { ... }`这样的代码将报错,而不会隐形地与 0 做对比。
|
||||
|
||||
你可以一起使用`if`和`let`来处理值缺失的情况。这些值可由可选值来代表。一个可选的值是一个具体的值或者是`nil`以表示值缺失。在类型后面加一个问号来标记这个变量的值是可选的。
|
||||
|
||||
```swift
|
||||
var optionalString: String? = "Hello"
|
||||
print(optionalString == nil)
|
||||
|
||||
var optionalName: String? = "John Appleseed"
|
||||
var greeting = "Hello!"
|
||||
if let name = optionalName {
|
||||
greeting = "Hello, \(name)"
|
||||
}
|
||||
```
|
||||
|
||||
> 练习:
|
||||
> 把`optionalName`改成`nil`,greeting会是什么?添加一个`else`语句,当`optionalName`是`nil`时给greeting赋一个不同的值。
|
||||
|
||||
如果变量的可选值是`nil`,条件会判断为`false`,大括号中的代码会被跳过。如果不是`nil`,会将值赋给`let`后面的常量,这样代码块中就可以使用这个值了。
|
||||
另一种处理可选值的方法是通过使用 ?? 操作符来提供一个默认值。如果可选值缺失的话,可以使用默认值来代替。
|
||||
```swift
|
||||
let nickName: String? = nil
|
||||
let fullName: String = "John Appleseed"
|
||||
let informalGreeting = "Hi \(nickName ?? fullName)"
|
||||
```
|
||||
|
||||
`switch`支持任意类型的数据以及各种比较操作——不仅仅是整数以及测试相等。
|
||||
|
||||
```swift
|
||||
let vegetable = "red pepper"
|
||||
switch vegetable {
|
||||
case "celery":
|
||||
print("Add some raisins and make ants on a log.")
|
||||
case "cucumber", "watercress":
|
||||
print("That would make a good tea sandwich.")
|
||||
case let x where x.hasSuffix("pepper"):
|
||||
print("Is it a spicy \(x)?")
|
||||
default:
|
||||
print("Everything tastes good in soup.")
|
||||
}
|
||||
```
|
||||
|
||||
> 练习:
|
||||
> 删除`default`语句,看看会有什么错误?
|
||||
|
||||
|
||||
注意`let`在上述例子的等式中是如何使用的,它将匹配等式的值赋给常量`x`。
|
||||
|
||||
运行`switch`中匹配到的子句之后,程序会退出`switch`语句,并不会继续向下运行,所以不需要在每个子句结尾写`break`。
|
||||
|
||||
你可以使用`for-in`来遍历字典,需要两个变量来表示每个键值对。字典是一个无序的集合,所以他们的键和值以任意顺序迭代结束。
|
||||
|
||||
```swift
|
||||
let interestingNumbers = [
|
||||
"Prime": [2, 3, 5, 7, 11, 13],
|
||||
"Fibonacci": [1, 1, 2, 3, 5, 8],
|
||||
"Square": [1, 4, 9, 16, 25],
|
||||
]
|
||||
var largest = 0
|
||||
for (kind, numbers) in interestingNumbers {
|
||||
for number in numbers {
|
||||
if number > largest {
|
||||
largest = number
|
||||
}
|
||||
}
|
||||
}
|
||||
print(largest)
|
||||
```
|
||||
|
||||
> 练习:
|
||||
> 添加另一个变量来记录现在和之前最大数字的类型。
|
||||
|
||||
使用`while`来重复运行一段代码直到不满足条件。循环条件也可以在结尾,保证能至少循环一次。
|
||||
|
||||
```swift
|
||||
var n = 2
|
||||
while n < 100 {
|
||||
n = n * 2
|
||||
}
|
||||
print(n)
|
||||
|
||||
var m = 2
|
||||
repeat {
|
||||
m = m * 2
|
||||
} while m < 100
|
||||
print(m)
|
||||
```
|
||||
|
||||
你可以在循环中使用`..<`来表示范围。
|
||||
|
||||
```swift
|
||||
var total = 0
|
||||
for i in 0..<4 {
|
||||
total += i
|
||||
}
|
||||
print(total)
|
||||
```
|
||||
|
||||
使用`..<`创建的范围不包含上界,如果想包含的话需要使用`...`。
|
||||
|
||||
<a name="functions_and_closures"></a>
|
||||
## 函数和闭包
|
||||
|
||||
使用`func`来声明一个函数,使用名字和参数来调用函数。使用`->`来指定函数返回值的类型。
|
||||
|
||||
```swift
|
||||
func greet(name: String, day: String) -> String {
|
||||
return "Hello \(name), today is \(day)."
|
||||
}
|
||||
greet("Bob", day: "Tuesday")
|
||||
```
|
||||
|
||||
> 练习:
|
||||
> 删除`day`参数,添加一个参数来表示今天吃了什么午饭。
|
||||
|
||||
使用元组来让一个函数返回多个值。该元组的元素可以用名称或数字来表示。
|
||||
|
||||
```swift
|
||||
func calculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
|
||||
var min = scores[0]
|
||||
var max = scores[0]
|
||||
var sum = 0
|
||||
|
||||
for score in scores {
|
||||
if score > max {
|
||||
max = score
|
||||
} else if score < min {
|
||||
min = score
|
||||
}
|
||||
sum += score
|
||||
}
|
||||
|
||||
return (min, max, sum)
|
||||
}
|
||||
let statistics = calculateStatistics([5, 3, 100, 3, 9])
|
||||
print(statistics.sum)
|
||||
print(statistics.2)
|
||||
```
|
||||
|
||||
函数可以带有可变个数的参数,这些参数在函数内表现为数组的形式:
|
||||
|
||||
```swift
|
||||
func sumOf(numbers: Int...) -> Int {
|
||||
var sum = 0
|
||||
for number in numbers {
|
||||
sum += number
|
||||
}
|
||||
return sum
|
||||
}
|
||||
sumOf()
|
||||
sumOf(42, 597, 12)
|
||||
```
|
||||
|
||||
> 练习:
|
||||
> 写一个计算参数平均值的函数。
|
||||
|
||||
函数可以嵌套。被嵌套的函数可以访问外侧函数的变量,你可以使用嵌套函数来重构一个太长或者太复杂的函数。
|
||||
|
||||
```swift
|
||||
func returnFifteen() -> Int {
|
||||
var y = 10
|
||||
func add() {
|
||||
y += 5
|
||||
}
|
||||
add()
|
||||
return y
|
||||
}
|
||||
returnFifteen()
|
||||
```
|
||||
|
||||
函数是第一等类型,这意味着函数可以作为另一个函数的返回值。
|
||||
|
||||
```swift
|
||||
func makeIncrementer() -> (Int -> Int) {
|
||||
func addOne(number: Int) -> Int {
|
||||
return 1 + number
|
||||
}
|
||||
return addOne
|
||||
}
|
||||
var increment = makeIncrementer()
|
||||
increment(7)
|
||||
```
|
||||
|
||||
函数也可以当做参数传入另一个函数。
|
||||
|
||||
```swift
|
||||
func hasAnyMatches(list: [Int], condition: Int -> Bool) -> Bool {
|
||||
for item in list {
|
||||
if condition(item) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
func lessThanTen(number: Int) -> Bool {
|
||||
return number < 10
|
||||
}
|
||||
var numbers = [20, 19, 7, 12]
|
||||
hasAnyMatches(numbers, condition: lessThanTen)
|
||||
```
|
||||
|
||||
函数实际上是一种特殊的闭包:它是一段能之后被调取的代码。闭包中的代码能访问闭包所建作用域中能得到的变量和函数,即使闭包是在一个不同的作用域被执行的 - 你已经在嵌套函数例子中所看到。你可以使用`{}`来创建一个匿名闭包。使用`in`将参数和返回值类型声明与闭包函数体进行分离。
|
||||
|
||||
```swift
|
||||
numbers.map({
|
||||
(number: Int) -> Int in
|
||||
let result = 3 * number
|
||||
return result
|
||||
})
|
||||
```
|
||||
|
||||
> 练习:
|
||||
> 重写闭包,对所有奇数返回0。
|
||||
|
||||
有很多种创建更简洁的闭包的方法。如果一个闭包的类型已知,比如作为一个回调函数,你可以忽略参数的类型和返回值。单个语句闭包会把它语句的值当做结果返回。
|
||||
|
||||
```swift
|
||||
let mappedNumbers = numbers.map({ number in 3 * number })
|
||||
print(mappedNumbers)
|
||||
```
|
||||
|
||||
你可以通过参数位置而不是参数名字来引用参数——这个方法在非常短的闭包中非常有用。当一个闭包作为最后一个参数传给一个函数的时候,它可以直接跟在括号后面。当一个闭包是传给函数的唯一参数,你可以完全忽略括号。
|
||||
|
||||
```swift
|
||||
let sortedNumbers = numbers.sort { $0 > $1 }
|
||||
print(sortedNumbers)
|
||||
```
|
||||
|
||||
<a name="objects_and_classes"></a>
|
||||
## 对象和类
|
||||
|
||||
使用`class`和类名来创建一个类。类中属性的声明和常量、变量声明一样,唯一的区别就是它们的上下文是类。同样,方法和函数声明也一样。
|
||||
|
||||
```swift
|
||||
class Shape {
|
||||
var numberOfSides = 0
|
||||
func simpleDescription() -> String {
|
||||
return "A shape with \(numberOfSides) sides."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 练习:
|
||||
> 使用`let`添加一个常量属性,再添加一个接收一个参数的方法。
|
||||
|
||||
要创建一个类的实例,在类名后面加上括号。使用点语法来访问实例的属性和方法。
|
||||
|
||||
```swift
|
||||
var shape = Shape()
|
||||
shape.numberOfSides = 7
|
||||
var shapeDescription = shape.simpleDescription()
|
||||
```
|
||||
|
||||
这个版本的`Shape`类缺少了一些重要的东西:一个构造函数来初始化类实例。使用`init`来创建一个构造器。
|
||||
|
||||
```swift
|
||||
class NamedShape {
|
||||
var numberOfSides: Int = 0
|
||||
var name: String
|
||||
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
}
|
||||
|
||||
func simpleDescription() -> String {
|
||||
return "A shape with \(numberOfSides) sides."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
注意`self`被用来区别实例变量。当你创建实例的时候,像传入函数参数一样给类传入构造器的参数。每个属性都需要赋值——无论是通过声明(就像`numberOfSides`)还是通过构造器(就像`name`)。
|
||||
|
||||
如果你需要在删除对象之前进行一些清理工作,使用`deinit`创建一个析构函数。
|
||||
|
||||
子类的定义方法是在它们的类名后面加上父类的名字,用冒号分割。创建类的时候并不需要一个标准的根类,所以你可以忽略父类。
|
||||
|
||||
子类如果要重写父类的方法的话,需要用`override`标记——如果没有添加`override`就重写父类方法的话编译器会报错。编译器同样会检测`override`标记的方法是否确实在父类中。
|
||||
|
||||
```swift
|
||||
class Square: NamedShape {
|
||||
var sideLength: Double
|
||||
|
||||
init(sideLength: Double, name: String) {
|
||||
self.sideLength = sideLength
|
||||
super.init(name: name)
|
||||
numberOfSides = 4
|
||||
}
|
||||
|
||||
func area() -> Double {
|
||||
return sideLength * sideLength
|
||||
}
|
||||
|
||||
override func simpleDescription() -> String {
|
||||
return "A square with sides of length \(sideLength)."
|
||||
}
|
||||
}
|
||||
let test = Square(sideLength: 5.2, name: "my test square")
|
||||
test.area()
|
||||
test.simpleDescription()
|
||||
```
|
||||
|
||||
> 练习:
|
||||
> 创建`NamedShape`的另一个子类`Circle`,构造器接收两个参数,一个是半径一个是名称,在子类`Circle`中实现`area()`和`simpleDescription()`方法。
|
||||
|
||||
除了储存简单的属性之外,属性可以有 getter 和 setter 。
|
||||
|
||||
```swift
|
||||
class EquilateralTriangle: NamedShape {
|
||||
var sideLength: Double = 0.0
|
||||
|
||||
init(sideLength: Double, name: String) {
|
||||
self.sideLength = sideLength
|
||||
super.init(name: name)
|
||||
numberOfSides = 3
|
||||
}
|
||||
|
||||
var perimeter: Double {
|
||||
get {
|
||||
return 3.0 * sideLength
|
||||
}
|
||||
set {
|
||||
sideLength = newValue / 3.0
|
||||
}
|
||||
}
|
||||
|
||||
override func simpleDescription() -> String {
|
||||
return "An equilateral triagle with sides of length \(sideLength)."
|
||||
}
|
||||
}
|
||||
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
|
||||
print(triangle.perimeter)
|
||||
triangle.perimeter = 9.9
|
||||
print(triangle.sideLength)
|
||||
```
|
||||
|
||||
在`perimeter`的 setter 中,新值的名字是`newValue`。你可以在`set`之后显式的设置一个名字。
|
||||
|
||||
注意`EquilateralTriangle`类的构造器执行了三步:
|
||||
|
||||
1. 设置子类声明的属性值
|
||||
2. 调用父类的构造器
|
||||
3. 改变父类定义的属性值。其他的工作比如调用方法、getters和setters也可以在这个阶段完成。
|
||||
|
||||
如果你不需要计算属性,但是仍然需要在设置一个新值之前或者之后运行代码,使用`willSet`和`didSet`。
|
||||
|
||||
比如,下面的类确保三角形的边长总是和正方形的边长相同。
|
||||
|
||||
```swift
|
||||
class TriangleAndSquare {
|
||||
var triangle: EquilateralTriangle {
|
||||
willSet {
|
||||
square.sideLength = newValue.sideLength
|
||||
}
|
||||
}
|
||||
var square: Square {
|
||||
willSet {
|
||||
triangle.sideLength = newValue.sideLength
|
||||
}
|
||||
}
|
||||
init(size: Double, name: String) {
|
||||
square = Square(sideLength: size, name: name)
|
||||
triangle = EquilateralTriangle(sideLength: size, name: name)
|
||||
}
|
||||
}
|
||||
var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
|
||||
print(triangleAndSquare.square.sideLength)
|
||||
print(triangleAndSquare.triangle.sideLength)
|
||||
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
|
||||
print(triangleAndSquare.triangle.sideLength)
|
||||
```
|
||||
|
||||
处理变量的可选值时,你可以在操作(比如方法、属性和子脚本)之前加`?`。如果`?`之前的值是`nil`,`?`后面的东西都会被忽略,并且整个表达式返回`nil`。否则,`?`之后的东西都会被运行。在这两种情况下,整个表达式的值也是一个可选值。
|
||||
|
||||
```swift
|
||||
let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
|
||||
let sideLength = optionalSquare?.sideLength
|
||||
```
|
||||
|
||||
<a name="enumerations_and_structure"></a>
|
||||
## 枚举和结构体
|
||||
|
||||
使用`enum`来创建一个枚举。就像类和其他所有命名类型一样,枚举可以包含方法。
|
||||
|
||||
```swift
|
||||
enum Rank: Int {
|
||||
case Ace = 1
|
||||
case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten
|
||||
case Jack, Queen, King
|
||||
func simpleDescription() -> String {
|
||||
switch self {
|
||||
case .Ace:
|
||||
return "ace"
|
||||
case .Jack:
|
||||
return "jack"
|
||||
case .Queen:
|
||||
return "queen"
|
||||
case .King:
|
||||
return "king"
|
||||
default:
|
||||
return String(self.rawValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
let ace = Rank.Ace
|
||||
let aceRawValue = ace.rawValue
|
||||
```
|
||||
|
||||
> 练习:
|
||||
> 写一个函数,通过比较它们的原始值来比较两个`Rank`值。
|
||||
|
||||
默认情况下,Swift 按照从 0 开始每次加 1 的方式为原始值进行赋值,不过你可以通过显式赋值进行改变。在上面的例子中,`Ace`被显式赋值为 1,并且剩下的原始值会按照顺序赋值。你也可以使用字符串或者浮点数作为枚举的原始值。使用`rawValue`属性来访问一个枚举成员的原始值。
|
||||
|
||||
使用`init?(rawValue:)`初始化构造器在原始值和枚举值之间进行转换。
|
||||
|
||||
```swift
|
||||
if let convertedRank = Rank(rawValue: 3) {
|
||||
let threeDescription = convertedRank.simpleDescription()
|
||||
}
|
||||
```
|
||||
|
||||
枚举的成员值是实际值,并不是原始值的另一种表达方法。实际上,以防原始值没有意义,你不需要设置。
|
||||
|
||||
```swift
|
||||
enum Suit {
|
||||
case Spades, Hearts, Diamonds, Clubs
|
||||
func simpleDescription() -> String {
|
||||
switch self {
|
||||
case .Spades:
|
||||
return "spades"
|
||||
case .Hearts:
|
||||
return "hearts"
|
||||
case .Diamonds:
|
||||
return "diamonds"
|
||||
case .Clubs:
|
||||
return "clubs"
|
||||
}
|
||||
}
|
||||
}
|
||||
let hearts = Suit.Hearts
|
||||
let heartsDescription = hearts.simpleDescription()
|
||||
```
|
||||
|
||||
> 练习:
|
||||
> 给`Suit`添加一个`color()`方法,对`spades`和`clubs`返回“black”,对`hearts`和`diamonds`返回“red”。
|
||||
|
||||
注意,有两种方式可以引用`Hearts`成员:给`hearts`常量赋值时,枚举成员`Suit.Hearts`需要用全名来引用,因为常量没有显式指定类型。在`switch`里,枚举成员使用缩写`.Hearts`来引用,因为`self`的值已经知道是一个`suit`。已知变量类型的情况下你可以使用缩写。
|
||||
|
||||
使用`struct`来创建一个结构体。结构体和类有很多相同的地方,比如方法和构造器。它们之间最大的一个区别就是结构体是传值,类是传引用。
|
||||
|
||||
```swift
|
||||
struct Card {
|
||||
var rank: Rank
|
||||
var suit: Suit
|
||||
func simpleDescription() -> String {
|
||||
return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
|
||||
}
|
||||
}
|
||||
let threeOfSpades = Card(rank: .Three, suit: .Spades)
|
||||
let threeOfSpadesDescription = threeOfSpades.simpleDescription()
|
||||
```
|
||||
|
||||
> 练习:
|
||||
> 给`Card`添加一个方法,创建一副完整的扑克牌并把每张牌的 rank 和 suit 对应起来。
|
||||
|
||||
一个枚举成员的实例可以有实例值。相同枚举成员的实例可以有不同的值。创建实例的时候传入值即可。实例值和原始值是不同的:枚举成员的原始值对于所有实例都是相同的,而且你是在定义枚举的时候设置原始值。
|
||||
|
||||
例如,考虑从服务器获取日出和日落的时间。服务器会返回正常结果或者错误信息。
|
||||
|
||||
```swift
|
||||
enum ServerResponse {
|
||||
case Result(String, String)
|
||||
case Failure(String)
|
||||
}
|
||||
|
||||
let success = ServerResponse.Result("6:00 am", "8:09 pm")
|
||||
let failure = ServerResponse.Failure("Out of cheese.")
|
||||
|
||||
switch success {
|
||||
case let .Result(sunrise, sunset):
|
||||
let serverResponse = "Sunrise is at \(sunrise) and sunset is at \(sunset)."
|
||||
case let .Failure(message):
|
||||
print("Failure... \(message)")
|
||||
}
|
||||
```
|
||||
|
||||
> 练习:
|
||||
> 给`ServerResponse`和`switch`添加第三种情况。
|
||||
|
||||
注意如何从`ServerResponse`中提取日升和日落时间并用得到的值用来和`switch`的情况作比较。
|
||||
|
||||
<a name="protocols_and_extensions"></a>
|
||||
## 协议和扩展
|
||||
|
||||
使用`protocol`来声明一个协议。
|
||||
|
||||
```swift
|
||||
protocol ExampleProtocol {
|
||||
var simpleDescription: String { get }
|
||||
mutating func adjust()
|
||||
}
|
||||
```
|
||||
|
||||
类、枚举和结构体都可以实现协议。
|
||||
|
||||
```swift
|
||||
class SimpleClass: ExampleProtocol {
|
||||
var simpleDescription: String = "A very simple class."
|
||||
var anotherProperty: Int = 69105
|
||||
func adjust() {
|
||||
simpleDescription += " Now 100% adjusted."
|
||||
}
|
||||
}
|
||||
var a = SimpleClass()
|
||||
a.adjust()
|
||||
let aDescription = a.simpleDescription
|
||||
|
||||
struct SimpleStructure: ExampleProtocol {
|
||||
var simpleDescription: String = "A simple structure"
|
||||
mutating func adjust() {
|
||||
simpleDescription += " (adjusted)"
|
||||
}
|
||||
}
|
||||
var b = SimpleStructure()
|
||||
b.adjust()
|
||||
let bDescription = b.simpleDescription
|
||||
```
|
||||
|
||||
> 练习:
|
||||
> 写一个实现这个协议的枚举。
|
||||
|
||||
注意声明`SimpleStructure`时候`mutating`关键字用来标记一个会修改结构体的方法。`SimpleClass`的声明不需要标记任何方法,因为类中的方法通常可以修改类属性(类的性质)。
|
||||
|
||||
使用`extension`来为现有的类型添加功能,比如新的方法和计算属性。你可以使用扩展在别处修改定义,甚至是从外部库或者框架引入的一个类型,使得这个类型遵循某个协议。
|
||||
|
||||
```swift
|
||||
extension Int: ExampleProtocol {
|
||||
var simpleDescription: String {
|
||||
return "The number \(self)"
|
||||
}
|
||||
mutating func adjust() {
|
||||
self += 42
|
||||
}
|
||||
}
|
||||
print(7.simpleDescription)
|
||||
```
|
||||
|
||||
> 练习:
|
||||
> 给`Double`类型写一个扩展,添加`absoluteValue`功能。
|
||||
|
||||
你可以像使用其他命名类型一样使用协议名——例如,创建一个有不同类型但是都实现一个协议的对象集合。当你处理类型是协议的值时,协议外定义的方法不可用。
|
||||
|
||||
```swift
|
||||
let protocolValue: ExampleProtocol = a
|
||||
print(protocolValue.simpleDescription)
|
||||
// print(protocolValue.anotherProperty) // Uncomment to see the error
|
||||
```
|
||||
|
||||
即使`protocolValue`变量运行时的类型是`simpleClass`,编译器会把它的类型当做`ExampleProtocol`。这表示你不能调用类在它实现的协议之外实现的方法或者属性。
|
||||
|
||||
<a name="error_handling"></a>
|
||||
## 错误处理
|
||||
|
||||
使用采用`ErrorType`协议的类型来表示错误。
|
||||
|
||||
```swift
|
||||
enum PrinterError: ErrorType {
|
||||
case OutOfPaper
|
||||
case NoToner
|
||||
case OnFire
|
||||
}
|
||||
```
|
||||
|
||||
使用`throw`来抛出一个错误并使用`throws`来表示一个可以抛出错误的函数。如果在函数中抛出一个错误,这个函数会立刻返回并且调用该函数的代码会进行错误处理。
|
||||
|
||||
```swift
|
||||
func sendToPrinter(printerName: String) throws -> String {
|
||||
if printerName == "Never Has Toner" {
|
||||
throw PrinterError.NoToner
|
||||
}
|
||||
return "Job sent"
|
||||
}
|
||||
```
|
||||
|
||||
有多种方式可以用来进行错误处理。一种方式是使用`do-catch`。在`do`代码块中,使用`try`来标记可以抛出错误的代码。在`catch`代码块中,除非你另外命名,否则错误会自动命名为`error`。
|
||||
|
||||
```swift
|
||||
do{
|
||||
let printerResponse = try sendToPrinter("Bi Sheng")
|
||||
print(printerResponse)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
```
|
||||
|
||||
> 练习:
|
||||
> 将 printer name 改为`"Never Has Toner"`使`sendToPrinter(_:)`函数抛出错误。
|
||||
|
||||
可以使用多个`catch`块来处理特定的错误。参照 switch 中的`case`风格来写`catch`。
|
||||
|
||||
```swift
|
||||
do {
|
||||
let printerResponse = try sendToPrinter("Gutenberg")
|
||||
print(printerResponse)
|
||||
} catch PrinterError.OnFire {
|
||||
print("I'll just put this over here, with the rest of the fire.")
|
||||
} catch let printerError as PrinterError {
|
||||
print("Printer error: \(printerError).")
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
```
|
||||
|
||||
> 练习:
|
||||
> 在`do`代码块中添加抛出错误的代码。你需要抛出哪种错误来使第一个`catch`块进行接收?怎么使第二个和第三个`catch`进行接收呢?
|
||||
|
||||
另一种处理错误的方式使用`try?`将结果转换为可选的。如果函数抛出错误,该错误会被抛弃并且结果为`nil`。否则的话,结果会是一个包含函数返回值的可选值。
|
||||
|
||||
```swift
|
||||
let printerSuccess = try? sendToPrinter("Mergenthaler")
|
||||
let printerFailure = try? sendToPrinter("Never Has Toner")
|
||||
```
|
||||
|
||||
使用`defer`代码块来表示在函数返回前,函数中最后执行的代码。无论函数是否会抛出错误,这段代码都将执行。使用`defer`,可以把函数调用之初就要执行的代码和函数调用结束时的扫尾代码写在一起,虽然这两者的执行时机截然不同。
|
||||
|
||||
```swift
|
||||
var fridgeIsOpen = false
|
||||
let fridgeContent = ["milk", "eggs", "leftovers"]
|
||||
|
||||
func fridgeContains(itemName: String) -> Bool {
|
||||
fridgeIsOpen = true
|
||||
defer {
|
||||
fridgeIsOpen = false
|
||||
}
|
||||
|
||||
let result = fridgeContent.contains(itemName)
|
||||
return result
|
||||
}
|
||||
fridgeContains("banana")
|
||||
print(fridgeIsOpen)
|
||||
```
|
||||
|
||||
<a name="generics"></a>
|
||||
## 泛型
|
||||
|
||||
在尖括号里写一个名字来创建一个泛型函数或者类型。
|
||||
|
||||
```swift
|
||||
func repeatItem<Item>(item: Item, numberOfTimes: Int) -> [Item] {
|
||||
var result = [Item]()
|
||||
for _ in 0..<numberOfTimes {
|
||||
result.append(item)
|
||||
}
|
||||
return result
|
||||
}
|
||||
repeatItem("knock", numberOfTimes:4)
|
||||
```
|
||||
|
||||
你也可以创建泛型函数、方法、类、枚举和结构体。
|
||||
|
||||
```swift
|
||||
// Reimplement the Swift standard library's optional type
|
||||
enum OptionalValue<Wrapped> {
|
||||
case None
|
||||
case Some(Wrapped)
|
||||
}
|
||||
var possibleInteger: OptionalValue<Int> = .None
|
||||
possibleInteger = .Some(100)
|
||||
```
|
||||
|
||||
在类型名后面使用`where`来指定对类型的需求,比如,限定类型实现某一个协议,限定两个类型是相同的,或者限定某个类必须有一个特定的父类。
|
||||
|
||||
```swift
|
||||
func anyCommonElements <T: SequenceType, U: SequenceType where T.Generator.Element: Equatable, T.Generator.Element == U.Generator.Element> (lhs: T, _ rhs: U) -> Bool {
|
||||
for lhsItem in lhs {
|
||||
for rhsItem in rhs {
|
||||
if lhsItem == rhsItem {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
anyCommonElements([1, 2, 3], [3])
|
||||
```
|
||||
|
||||
> 练习:
|
||||
> 修改`anyCommonElements(_:_:)`函数来创建一个函数,返回一个数组,内容是两个序列的共有元素。
|
||||
|
||||
`<T: Equatable>`和`<T where T: Equatable>`是等价的。
|
||||
@ -1,575 +0,0 @@
|
||||
# Swift 文档修订历史
|
||||
|
||||
---
|
||||
|
||||
> 1.0
|
||||
> 翻译:[成都老码团队翻译组-Arya](http://weibo.com/littlekok/)
|
||||
> 校对:[成都老码团队翻译组-Oberyn](http://weibo.com/u/5241713117)
|
||||
[changkun](http://changkun.us/about/)
|
||||
>
|
||||
> 1.1
|
||||
> 翻译:[成都老码团队翻译组-Arya](http://weibo.com/littlekok/)
|
||||
> 校对:[成都老码团队翻译组-Oberyn](http://weibo.com/u/5241713117)
|
||||
[changkun](http://changkun.us/about/)
|
||||
>
|
||||
> 1.2
|
||||
> 翻译:[成都老码团队翻译组-Arya](http://weibo.com/littlekok/)
|
||||
> 校对:[成都老码团队翻译组-Oberyn](http://weibo.com/u/5241713117)
|
||||
[changkun](http://changkun.us/about/)
|
||||
>
|
||||
> 2.0
|
||||
> 翻译+校对:[changkun](http://changkun.us/about/)
|
||||
>
|
||||
> 2.1
|
||||
> 翻译+校对:[changkun](http://changkun.us/about/)
|
||||
>
|
||||
> 2.2
|
||||
> 翻译+校对:[changkun](http://changkun.us/about/)
|
||||
|
||||
本页面根据 [Document Revision History](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/RevisionHistory.html) 进行适配更新。
|
||||
|
||||
本页内容包括:
|
||||
|
||||
- [Swift 2.2 更新](#swift_2_2)
|
||||
- [Swift 2.1 更新](#swift_2_1)
|
||||
- [Swift 2.0 更新](#swift_2_0)
|
||||
- [Swift 1.2 更新](#swift_1_2)
|
||||
- [Swift 1.1 更新](#swift_1_1)
|
||||
- [Swift 1.0 更新](#swift_1_0)
|
||||
|
||||
<a name="swift_2_2"></a>
|
||||
### Swift 2.2 更新
|
||||
|
||||
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" width="100">发布日期</th>
|
||||
<th scope="col">语法变更记录</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td scope="row">2016-03-21</td>
|
||||
<td><ul class="list-bullet">
|
||||
<li>
|
||||
更新至 Swift 2.2。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID539">编译配置语句</a>一节中关于如何根据 Swift 版本进行条件编译。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID400">显示成员表达式</a>一节中关于如何区分只有参数名不同的方法和构造器的信息。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID547">选择器表达式</a>一节中针对 Objective-C 选择器的 <code>#selector</code> 语法。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Generics.html#//apple_ref/doc/uid/TP40014097-CH26-ID189">关联类型</a>和<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID374">协议关联类型</a>声明,使用 <code>associatedtype</code> 关键词修改了对于关联类型的讨论。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-ID224">可失败构造器</a>一节中关于当构造器在实例完全初始化之前返回 <code>nil</code>的相关信息。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/BasicOperators.html#//apple_ref/doc/uid/TP40014097-CH6-ID70">比较运算符</a>一节中关于比较元组的信息。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-ID413">关键字和标点符号</a>一节中关于使用关键字作为外部参数名的信息。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-ID413">声明特性</a>一节中关于<code>@objc</code>特性的讨论,并指出枚举(Enumeration)和枚举用例(Enumaration Case)。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-ID418">操作符</a>一节中对于自定义运算符的讨论包含了<code>.</code>。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID531">重新抛出错误的函数和方法</a>一节中关于重新抛出错误函数不能直接抛出错误的笔记。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html#//apple_ref/doc/uid/TP40014097-CH14-ID262">属性观察器</a>一节中关于当作为 in-out 参数传递属性时,属性观察器的调用行为。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/GuidedTour.html#//apple_ref/doc/uid/TP40014097-CH2-ID1">Swift 初见</a>一节中关于错误处理的内容。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html#//apple_ref/doc/uid/TP40014097-CH20-ID53">弱引用</a>一节中的图片用以更清楚的展示重新分配过程。
|
||||
</li>
|
||||
<li>
|
||||
删除了 C 语言风格的 <code>for</code> 循环,<code>++</code> 前缀和后缀运算符,以及<code>--</code> 前缀和后缀运算符。
|
||||
</li>
|
||||
<li>
|
||||
删除了对变量函数参数和柯里化函数的特殊语法的讨论。
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a name="swift_2_1"></a>
|
||||
### Swift 2.1 更新
|
||||
|
||||
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" width="100">发布日期</th>
|
||||
<th scope="col">语法变更记录</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td scope="row">2015-10-20</td>
|
||||
<td><ul class="list-bullet">
|
||||
<li>
|
||||
更新至 Swift 2.1。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-ID292">字符串插值(String Interprolation)</a>和<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-ID417">字符串字面量(String Literals)</a>小节,现在字符串插值可包含字符串字面量。
|
||||
</li>
|
||||
<li>
|
||||
增加了在<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-ID546">非逃逸闭包(Nonescaping Closures)</a>一节中关于 <code>@noescape</code> 属性的相关内容。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Attributes.html#//apple_ref/doc/uid/TP40014097-CH35-ID348">声明特性(Declaration Attributes)</a>和<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID539">编译配置语句(Build Configuration Statement)</a>小节中与 tvOS 相关的信息。
|
||||
</li>
|
||||
<li>
|
||||
增加了 <a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID545">In-Out 参数(In-Out Parameters)</a>小节中与 in-out 参数行为相关的信息。
|
||||
</li>
|
||||
<li>
|
||||
增加了在<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID544">捕获列表(Capture Lists)</a>一节中关于指定闭包捕获列表被捕获时捕获值的相关内容。
|
||||
</li>
|
||||
<li>
|
||||
更新了通过<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/OptionalChaining.html#//apple_ref/doc/uid/TP40014097-CH21-ID248">可选链式调用访问属性(Accessing Properties Through Optional Chaining)</a>一节,阐明了如何通过可选链式调用进行赋值。
|
||||
</li>
|
||||
<li>
|
||||
改进了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-ID543">自闭包(Autoclosure)</a>一节中对自闭包的讨论。
|
||||
</li>
|
||||
<li>
|
||||
在<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/GuidedTour.html#//apple_ref/doc/uid/TP40014097-CH2-ID1">Swift 初见(A Swift Tour)</a>一节中更新了一个使用<code>??</code>操作符的例子。
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a name="swift_2_0"></a>
|
||||
### Swift 2.0 更新
|
||||
|
||||
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" width="100">发布日期</th>
|
||||
<th scope="col">语法变更记录</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td scope="row">2015-09-16</td>
|
||||
<td><ul class="list-bullet">
|
||||
<li>
|
||||
更新至 Swift 2.0。
|
||||
</li>
|
||||
<li>
|
||||
增加了对于错误处理相关内容,包括 <a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html#//apple_ref/doc/uid/TP40014097-CH42-ID508">错误处理</a>一章、<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID533">Do 语句</a>、<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID518">Throw 语句</a>、<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID532">Defer 语句</a>以及<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID516">try 运算符</a> 的多个小节。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html#//apple_ref/doc/uid/TP40014097-CH42-ID509">表示并抛出错误</a>一节,现在所有类型均可遵循 <code>ErrorType</code> 协议。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html#//apple_ref/doc/uid/TP40014097-CH42-ID542">将错误转换成可选值</a>一节 <code>try?</code> 关键字的相关信息。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html#//apple_ref/doc/uid/TP40014097-CH12-ID145">枚举</a>一章的<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html#//apple_ref/doc/uid/TP40014097-CH12-ID536">递归枚举</a>一节和<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID351">声明</a>一章的<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID365">任意类型用例的枚举</a>一节中关于递归枚举的内容。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html#//apple_ref/doc/uid/TP40014097-CH9-ID120">控制流</a>一章中a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html#//apple_ref/doc/uid/TP40014097-CH9-ID523">检查 API 可用性</a>一节和<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID428">语句</a>一章中<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID522">可用性条件</a>一节中关于 API 可用性检查的内容。
|
||||
</li>
|
||||
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html#//apple_ref/doc/uid/TP40014097-CH9-ID120">控制流</a>一章的<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html#//apple_ref/doc/uid/TP40014097-CH9-ID525">早期退出</a>一节和<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID428">语句</a>一章的<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID524">guard语句</a>中关于新 <code>guard</code> 语句的内容。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID267">协议</a>一章中<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID521">协议扩展</a>一节中关于协议扩展的内容。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AccessControl.html#//apple_ref/doc/uid/TP40014097-CH41-ID3">访问控制</a>一章中<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AccessControl.html#//apple_ref/doc/uid/TP40014097-CH41-ID519">单元测试 target 的访问级别</a>一节中关于单元测试的访问控制相关的内容。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Patterns.html#//apple_ref/doc/uid/TP40014097-CH36-ID419">模式</a>一章中<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Patterns.html#//apple_ref/doc/uid/TP40014097-CH36-ID520">可选模式</a>一节中的新可选模式。
|
||||
</li>
|
||||
<li>
|
||||
更新了 <a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html#//apple_ref/doc/uid/TP40014097-CH9-ID126">Repeat-While</a> 一节中关于<code>repeat-while</code>循环的信息。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-ID285">字符串和字符</a>一章,现在<code>String</code>在 Swift 标准库中不再遵循<code>CollectionType</code>协议。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-ID314">打印常量和变量</a>一节中关于新 Swift 标准库中关于 <code>print(_:separator:terminator)</code> 的信息。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html#//apple_ref/doc/uid/TP40014097-CH12-ID145">枚举</a>一章中<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html#//apple_ref/doc/uid/TP40014097-CH12-ID535">原始值的隐式赋值</a>一节和<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID351">声明</a>一章的<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID366">包含原始值类型的枚举</a>一节中关于包含<code>String</code>原始值的枚举用例的行为。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-ID543">自闭包</a>一节中关于<code>@autoclosure</code>特性的相关信息,包括它的<code>@autoclosure(escaping)</code>形式。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Attributes.html#//apple_ref/doc/uid/TP40014097-CH35-ID348">声明特性</a>一节中关于<code>@avaliable</code>和<code>warn_unused_result</code>特性的相关内容。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Attributes.html#//apple_ref/doc/uid/TP40014097-CH35-ID350">类型特性</a>一节中关于<code>@convention</code>特性的相关信息。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-ID333">可选绑定</a>一节中关于使用<code>where</code>子句进行多可选绑定的内容。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-ID292">字符串字面量</a>一节中关于在编译时使用 <code>+</code> 运算符凭借字符串字面量的相关信息。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Types.html#//apple_ref/doc/uid/TP40014097-CH31-ID455">元类型</a>一节中关于元类型值的比较和使用它们通过构造器表达式构造实例。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-ID336">断言调试</a>一节中关于用户定义断言是被警用的相关内容。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Attributes.html#//apple_ref/doc/uid/TP40014097-CH35-ID348">声明特性</a>一节中,对<code>@NSManaged</code>特性的讨论,现在这个特性可以被应用到一个确定实例方法。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Functions.html#//apple_ref/doc/uid/TP40014097-CH10-ID171">可变参数</a>一节,现在可变参数可以声明在函数参数列表的任意位置中。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-ID229">重写可失败构造器</a>一节中,关于非可失败构造器相当于一个可失败构造器通过父类构造器的结果进行强制拆包的相关内容。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID365">任意类型用例的枚举</a>一节中关于枚举用例作为函数的内容。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID399">构造器表达式</a>一节中关于显式引用一个构造器的内容。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID538">编译控制语句</a>一节中关于编译信息以及行控制语句的相关信息。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Types.html#//apple_ref/doc/uid/TP40014097-CH31-ID455">元类型</a>一节中关于如何从元类型值中构造类实例。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html#//apple_ref/doc/uid/TP40014097-CH20-ID53">弱引用</a>一节中关于弱引用作为缓存的显存的不足。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-ID292">类型特性</a>一节,提到了存储型特性其实是懒加载。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html#//apple_ref/doc/uid/TP40014097-CH14-ID264">捕获类型</a>一节,阐明了变量和常量在闭包中如何被捕获。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Attributes.html#//apple_ref/doc/uid/TP40014097-CH35-ID348">声明特性</a>一节用以描述如何在类中使用<code>@objc</code>关键字。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html#//apple_ref/doc/uid/TP40014097-CH42-ID512">错误处理</a>一节中关于执行<code>throw</code>语句的性能的讨论。增加了 Do 语句一节中相似的信息。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID533">类型特性</a>一节中关于类、结构体和枚举的存储型和计算型特性的信息。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Statements.html#//apple_ref/doc/uid/TP40014097-CH33-ID441">Break 语句</a>一节中关于带标签的 break 语句。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html#//apple_ref/doc/uid/TP40014097-CH14-ID262">属性观察器</a>一节,阐明了<code>willSet</code>和<code>didSet</code>观察器的行为。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AccessControl.html#//apple_ref/doc/uid/TP40014097-CH41-ID5">访问级</a>一节中关于<code>private</code>作用域访问的相关信息。
|
||||
</li>
|
||||
<li>
|
||||
增加了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html#//apple_ref/doc/uid/TP40014097-CH20-ID53">弱引用</a>一节中关于若应用在垃圾回收系统和 ARC 之间的区别。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-ID295">字符串字面量中特殊字符</a>一节中对 Unicode 标量更精确的定义。
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<a name="swift_1_2"></a>
|
||||
### Swift 1.2 更新
|
||||
<a name="xcode6_3"></a>
|
||||
|
||||
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" width="100">发布日期</th>
|
||||
<th scope="col">语法变更记录</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td scope="row">2015-4-8</td>
|
||||
<td><ul class="list-bullet">
|
||||
<li>
|
||||
更新至 Swift 1.2。
|
||||
</li>
|
||||
<li>
|
||||
Swift现在自身提供了一个<code>Set</code>集合类型,更多信息请看<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html#//apple_ref/doc/uid/TP40014097-CH8-ID484">集合</a>
|
||||
|
||||
</li>
|
||||
<li>
|
||||
<code>@autoclosure</code>现在是一个参数声明的属性,而不是参数类型的属性。这里还有一个新的参数声明属性<code>@noescape</code>。更多信息,请看<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Attributes.html#//apple_ref/doc/uid/TP40014097-CH35-ID348">属性声明</a>
|
||||
</li>
|
||||
<li>
|
||||
对于类型属性和方法现在可以使用<code>static</code>关键字作为声明描述符,更多信息,请看<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID483">类型变量属性</a>
|
||||
</li>
|
||||
<li>
|
||||
Swift现在包含一个<code>as?</code>和<code>as!</code>的向下可失败类型转换运算符。更多信息,请看<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID283">协议遵循性检查</a>
|
||||
</li>
|
||||
<li>
|
||||
增加了一个新的指导章节,它是关于<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-ID495">字符串索引</a>的
|
||||
</li>
|
||||
<li>
|
||||
从<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-ID37">溢出运算符</a>中移除了溢出除运算符(&/)和求余溢出运算符(&%)。
|
||||
</li>
|
||||
<li>
|
||||
更新了常量和常量属性在声明和构造时的规则,更多信息,请看<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID355">常量声明</a>
|
||||
</li>
|
||||
<li>
|
||||
更新了字符串字面量中Unicode标量集的定义,请看<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-ID295">字符串字面量中的特殊字符</a>
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/BasicOperators.html#//apple_ref/doc/uid/TP40014097-CH6-ID73">区间运算符</a>章节来提示当半开区间运算符含有相同的起止索引时,其区间为空。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-ID104">闭包引用类型</a>章节来澄清对于变量的捕获规则
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-ID38">值溢出</a>章节来澄清有符号整数和无符号整数的溢出行为
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID369">协议声明</a>章节来澄清协议声明时的作用域和成员
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html#//apple_ref/doc/uid/TP40014097-CH20-ID58">捕获列表</a>章节来澄清对于闭包捕获列表中的弱引用和无主引用的使用语法。
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-ID418">运算符</a>章节来明确指明一些例子来说明自定义运算符所支持的特性,如数学运算符,各种符号,Unicode符号块等
|
||||
</li>
|
||||
<li>
|
||||
在函数作用域中的常量声明时可以不被初始化,它必须在第一次使用前被赋值。更多的信息,请看<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-ID355">常量声明</a>
|
||||
</li>
|
||||
<li>
|
||||
在构造器中,常量属性有且仅能被赋值一次。更多信息,请看<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-ID212">在构造过程中给常量属性赋值</a>
|
||||
</li>
|
||||
<li>
|
||||
多个可选绑定现在可以在<code>if</code>语句后面以逗号分隔的赋值列表的方式出现,更多信息,请看<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-ID333">可选绑定</a>
|
||||
</li>
|
||||
<li>
|
||||
一个<a link="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID405">可选链表达式</a>必须出现在后缀表达式中
|
||||
</li>
|
||||
<li>
|
||||
协议类型转换不再局限于<code>@obj</code>修饰的协议了
|
||||
</li>
|
||||
<li>
|
||||
在运行时可能会失败的类型转换可以使用<code>as?</code>和<code>as!</code>运算符,而确保不会失败的类型转换现在使用<code>as</code>运算符。更多信息,请看<a link="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Expressions.html#//apple_ref/doc/uid/TP40014097-CH32-ID388">类型转换运算符</a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
<a name="swift_1_1"></a>
|
||||
### Swift 1.1 更新
|
||||
|
||||
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" width="100">发布日期</th>
|
||||
<th scope="col">语法变更记录</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td scope="row">2014-10-16</td>
|
||||
<td><ul class="list-bullet">
|
||||
<li>
|
||||
更新至 Swift 1.1。
|
||||
</li>
|
||||
<li>
|
||||
增加了关于<a href="http://developer.apple.com/library/etc/redirect/xcode/devtools/419f35/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html">失败构造器(Failable Initializers)</a>的完整章节。
|
||||
</li>
|
||||
<li>
|
||||
增加了协议中关于失败构造器要求的描述。
|
||||
</li>
|
||||
<li>
|
||||
常量和变量的 <code>Any</code> 类型现可以包含函数实例。更新了关于 <code>Any</code> 相关的示例来展示如何在 <code>switch</code> 语句中如何检查并转换到一个函数类型。
|
||||
</li>
|
||||
<li>
|
||||
带有原始值的枚举类型增加了一个<code>rawValue</code>属性替代<code>toRaw()</code>方法,同时使用了一个以<code>rawValue</code>为参数的失败构造器来替代<code>fromRaw()</code>方法。更多的信息,请看<a href="http://developer.apple.com/library/etc/redirect/xcode/devtools/419f35/documentation/Swift/Conceptual/Swift_Programming_Language/Enumerations.html">原始值(Raw Values)</a>和<a href="http://developer.apple.com/library/etc/redirect/xcode/devtools/419f35/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html">带原始值的枚举类型(Enumerations with Cases of a Raw-Value Type)</a>部分。
|
||||
</li>
|
||||
<li>
|
||||
自定义运算符现在可以包含`?`字符,更新的<a href="http://developer.apple.com/library/etc/redirect/xcode/devtools/419f35/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html">运算符(Operators)</a>章节描述了改进后的规则,并且从<a href="http://developer.apple.com/library/etc/redirect/xcode/devtools/419f35/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html">自定义运算符(Custom Operators)</a>章节删除了重复的运算符有效字符集合
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a name="swift_1_0"></a>
|
||||
### Swift 1.0 更新
|
||||
|
||||
<table class="graybox" border="0" cellspacing="0" cellpadding="5">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" width="100">发布日期</th>
|
||||
<th scope="col">语法变更记录</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td scope="row">2014-08-18</td>
|
||||
<td><ul class="list-bullet">
|
||||
<li>
|
||||
发布新的文档用以详述 Swift 1.0,苹果公司针对iOS和OS X应用的全新开发语言。
|
||||
</li>
|
||||
<li>
|
||||
在章节协议中,增加新的小节:<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-XID_397">对构造器的规定(Initializer Requirements)</a>
|
||||
</li>
|
||||
<li>
|
||||
在章节协议中,增加新的小节:<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-XID_409">类专属协议(class-only protocols)</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-XID_494">断言(assertions)</a>现在可以使用字符串内插语法,并删除了文档中有冲突的注释
|
||||
</li>
|
||||
<li>
|
||||
更新了<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-XID_428">连接字符串和字符(Concatenating Strings and Characters)</a>小节来说明一个事实,那就是字符串和字符不能再用<code>+</code>号运算符或者复合加法运算符<code>+=</code>相互连接,这两种运算符现在只能用于字符串之间相连。请使用<code>String</code>类型的<code>append</code>方法在一个字符串的尾部增加单个字符
|
||||
</li>
|
||||
<li>
|
||||
在<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Attributes.html#//apple_ref/doc/uid/TP40014097-CH35-XID_516">声明特性(Declaration Attributes)</a>章节增加了关于<code>availability</code>特性的一些信息
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-XID_478">可选类型(Optionals)</a> 若有值时,不再隐式的转换为 <code>true</code>,同样,若无值时,也不再隐式的转换为 <code>false</code>, 这是为了避免在判别 optional <code>Bool</code> 的值时产生困惑。 替代的方案是,用<code>==</code> 或 <code>!=</code> 运算符显式地去判断Optinal是否是 <code>nil</code>,以确认其是否包含值。
|
||||
</li>
|
||||
<li>
|
||||
Swift新增了一个 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/BasicOperators.html#//apple_ref/doc/uid/TP40014097-CH6-XID_124" data-id="//apple_ref/doc/uid/TP40014097-CH6-XID_124">Nil合并运算符(Nil Coalescing Operator)</a> (<code>a ?? b</code>), 该表达式中,如果Optional <code>a</code>的值存在,则取得它并返回,若Optional <code>a</code>为<code>nil</code>,则返回默认值 <code>b</code>
|
||||
</li>
|
||||
<li>
|
||||
更新和扩展 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-XID_434">字符串的比较(Comparing Strings)</a> 章节,用以反映和展示'字符串和字符的比较',以及'前缀(prefix)/后缀(postfix)比较'都开始基于扩展字符集(extended grapheme clusters)规范的等价比较.
|
||||
</li>
|
||||
<li>
|
||||
现在,你可以通过 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/OptionalChaining.html#//apple_ref/doc/uid/TP40014097-CH21-XID_356">可选链(Optional Chaining)</a>来:给属性设值,将其赋给一个下标脚注(subscript); 或调用一个变异(mutating)方法或运算符。对此,章节——<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/OptionalChaining.html#//apple_ref/doc/uid/TP40014097-CH21-XID_364">通过可选链访问属性(Accessing Properties Through Optional Chaining)</a>的内容已经被相应的更新。而章节——<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/OptionalChaining.html#//apple_ref/doc/uid/TP40014097-CH21-XID_361">通过可选链调用方法(Calling Methods Through Optional Chaining</a>中,关于检查方法调用是否成功的例子,已被扩展为展示如何检查一个属性是否被设值成功。
|
||||
|
||||
</li>
|
||||
<li>
|
||||
在章节可选链中,增加一个新的小节 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/OptionalChaining.html#//apple_ref/doc/uid/TP40014097-CH21-XID_364">访问可选类型的下标脚注(Accessing Subscripts of Optional Type)</a>
|
||||
</li>
|
||||
<li>
|
||||
更新章节 <a href="../chapter2/04_Collection_Types.md#访问和修改数组" data-id="访问和修改数组">访问和修改数组(Accessing and Modifying an Array)</a> 以标示:从该版本起,不能再通过<code>+=</code> 运算符给一个数组添加一个新的项。. 对应的替代方案是, 使<code>append</code> 方法, 或者通过<code>+=</code>运算符来添加一个<b>只有一个项的数组</b>(single-item Array).</li>
|
||||
<li>
|
||||
添加了一个提示:在 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/BasicOperators.html#//apple_ref/doc/uid/TP40014097-CH6-XID_126">范围运算符(Range Operators)</a>中,比如, <code>a...b</code> 和 <code>a..<b</code> ,起始值<code>a</code>不能大于结束值<code>b</code>.
|
||||
</li>
|
||||
<li>
|
||||
重写了<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Inheritance.html#//apple_ref/doc/uid/TP40014097-CH17-XID_293">继承(Inheritance)</a> 这一章:删除了本章中关于构造器重写的介绍性报道;转而将更多的注意力放到新增的部分——子类的新功能,以及如何通过重写(overrides)修改已有的功能。另外,小节 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Inheritance.html#//apple_ref/doc/uid/TP40014097-CH17-XID_301">重写属性的Getters和Setters(Overriding Property Getters and Setters)</a> 中的例子已经被替换为展示如何重写一个 <code>description</code> 属性. (而关于如何在子类的构造器中修改继承属性的默认值的例子,已经被移到 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Inheritance.html#//apple_ref/doc/uid/TP40014097-CH17-XID_293">构造过程(Initialization)</a> 这一章.)
|
||||
</li>
|
||||
<li>
|
||||
更新了 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-XID_331">构造器的继承与重写(Initializer Inheritance and Overriding)</a> 小节以标示: 重写一个特定的构造器必须使用 <code>override</code> 修饰符.
|
||||
</li>
|
||||
<li>
|
||||
更新 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-XID_339"> Required构造器(Required Initializers)</a> 小节以标示:<code>required</code> 修饰符现在需要出现在所有子类的required构造器的声明中, 而required构造器的实现,现在可以仅从父类自动继承。
|
||||
</li>
|
||||
<li>
|
||||
中置(Infix)的 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-XID_80">运算符函数(Operator Functions)</a> 不再需要<code>@infix</code> 属性.
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/RevisionHistory.html#//apple_ref/doc/uid/TP40014097-CH40-XID_1631">前置和后置运算符(Prefix and Postfix Operators)</a>的<code>@prefix</code> 和 <code>@postfix</code> 属性,已变更为 <code>prefix</code> 和 <code>postfix</code> 声明修饰符(declaration modifiers).
|
||||
</li>
|
||||
<li>
|
||||
增加一条注解:当Prefix和postfix运算符被作用于同一个操作数时,关于<a href="AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-XID_81" data-id="//apple_ref/doc/uid/TP40014097-CH27-XID_81">前置和后置运算符(Prefix and Postfix Operators)</a>的顺序(postfix运算符会先被执行)
|
||||
</li>
|
||||
<li>
|
||||
在运算符函数(Operator functions)中, <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-XID_82" data-id="//apple_ref/doc/uid/TP40014097-CH27-XID_82">组合赋值运算符(Compound Assignment Operators)</a> 不再使用 <code>@assignment</code> 属性来定义函数.
|
||||
</li>
|
||||
<li>
|
||||
在这个版本中,在定义<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-XID_85">自定义操作符(Custom Operators)</a> 时,<b>修饰符(Modifiers)的出现顺序发生变化</b>。比如, 现在,你该编写 <code>prefix operator</code>, 而不是 <code>operator prefix</code>.
|
||||
</li>
|
||||
<li>
|
||||
增加信息:关于<code>dynamic</code> 声明修饰符(declaration modifier),于章节 <a href="Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-XID_705" data-id="//apple_ref/doc/uid/TP40014097-CH34-XID_705">声明修饰符(Declaration Modifiers)</a>.
|
||||
</li>
|
||||
<li>
|
||||
增加信息:<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-XID_886">字面量Literals</a> 的类型推导(type inference)
|
||||
</li>
|
||||
<li>
|
||||
为章节<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-XID_597">Curried Functions</a>添加了更多的信息.
|
||||
</li>
|
||||
<li>
|
||||
加入新的章节 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AccessControl.html#//apple_ref/doc/uid/TP40014097-CH41-XID_29">权限控制(Access Control)</a>.
|
||||
</li>
|
||||
<li>
|
||||
更新了章节 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-XID_413">字符串和字符(Strings and Characters)</a> 用以表明,在Swift中,<code>Character</code> 类型现在代表的是扩展字符集(extended grapheme cluster)中的一个Unicode,为此,新增了小节 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-XID_431">Extended Grapheme Clusters</a> 。同时,为小节 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-XID_428">Unicode标量(Unicode Scalars)</a> 和 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-XID_434">字符串比较(Comparing Strings)</a>增加了更多内容.
|
||||
</li>
|
||||
<li>
|
||||
更新章节<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-XID_856">字符串字面量(String Literals)</a>:在一个字符串中,Unicode标量(Unicode scalars) 以 <code>\u{n}</code>的形式来表示, <code>n</code> 是一个最大可以有8位的16进制数(hexadecimal digits)
|
||||
</li>
|
||||
<li>
|
||||
<code>NSString</code> <code>length</code> 属性已被映射到Swift的内建 <code>String</code>类型。(注意,这两属性的类型是<code>utf16Count</code>,而非 <code>utf16count</code>).
|
||||
</li>
|
||||
<li>
|
||||
Swift的内建 <code>String</code> 类型不再拥有 <code>uppercaseString</code> 和 <code>lowercaseString</code> 属性.其对应部分在章节 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-XID_413">字符串和字符(Strings and Characters)</a>已经被删除, 并且各种对应的代码用例也已被更新.
|
||||
</li>
|
||||
<li>
|
||||
加入新的章节 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-XID_315">没有外部名的构造器参数(Initializer Parameters Without External Names)</a>.
|
||||
</li>
|
||||
<li>
|
||||
加入新的章节 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-XID_339"> Required构造器(Required Initializers)</a>.
|
||||
</li>
|
||||
<li>
|
||||
加入新的章节 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Functions.html#//apple_ref/doc/uid/TP40014097-CH10-XID_252">可选元祖(函数)返回类型 (Optional Tuple Return Types)</a>.
|
||||
</li>
|
||||
<li>
|
||||
更新章节 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html#//apple_ref/doc/uid/TP40014097-CH5-XID_453">类型标注(Type Annotations)</a> :多个相关变量可以用“类型标注”(type annotaion)在同一行中声明为同一类型。
|
||||
</li>
|
||||
<li>
|
||||
<code>@optional</code>, <code>@lazy</code>, <code>@final</code>, <code>@required</code> 等关键字被更新为 <code>optional</code>, <code>lazy</code>, <code>final</code>, <code>required</code> <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-XID_705">参见声明修饰符(Declaration Modifiers)</a>.
|
||||
</li>
|
||||
<li>
|
||||
更新整本书 —— 引用 <code>..<</code> 作为<a href="BasicOperators.html#//apple_ref/doc/uid/TP40014097-CH6-XID_128" data-id="//apple_ref/doc/uid/TP40014097-CH6-XID_128">区间运算符(Half-Open Range Operator)</a> (取代原先的<code>..</code> ).
|
||||
</li>
|
||||
<li>
|
||||
更新了小节 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html#//apple_ref/doc/uid/TP40014097-CH8-XID_185">读取和修改字典(Accessing and Modifying a Dictionary)</a>: <code>Dictionary</code> 现在早呢更加了一个 Boolean型的属性: <code>isEmpty</code>
|
||||
</li>
|
||||
<li>
|
||||
解释了哪些字符(集)可被用来定义<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-XID_85">自定义操作符 (Custom Operators)</a>
|
||||
</li>
|
||||
<li>
|
||||
<code>nil</code> 和布尔运算中的 <code>true</code> 和 <code>false</code> 现在被定义为字面量<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/LexicalStructure.html#//apple_ref/doc/uid/TP40014097-CH30-XID_886">Literals</a>.
|
||||
</li>
|
||||
<li>
|
||||
Swift 中的数组 (<code>Array</code>) 类型从现在起具备了完整的值语义。具体信息被更新到 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html#//apple_ref/doc/uid/TP40014097-CH8-XID_170">集合的可变性(Mutability of Collections)</a> 和 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html#//apple_ref/doc/uid/TP40014097-CH8-XID_172">数组(Arrays)</a> 两小节,以反映这个新的变化. 此外,还解释了如何 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ClassesAndStructures.html#//apple_ref/doc/uid/TP40014097-CH13-XID_150">给Strings, Arrays和Dictionaries进行赋值和拷贝 (Assignment and Copy Behavior for Strings, Arrays, and Dictionaries)</a>.
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html#//apple_ref/doc/uid/TP40014097-CH8-XID_173">数组类型速记语法(Array Type Shorthand Syntax)</a> 从 <code>SomeType[]</code>.更新为<code>[SomeType]</code>
|
||||
</li>
|
||||
<li>
|
||||
加入新的小节:<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html#//apple_ref/doc/uid/TP40014097-CH8-XID_182">字典类型的速记语法(Dictionary Type Shorthand Syntax)</a>.: <code>[KeyType: ValueType]</code>.
|
||||
</li>
|
||||
<li>
|
||||
加入新的小节:<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html#//apple_ref/doc/uid/TP40014097-CH8-XID_189">字典键类型的哈希值(Hash Values for Dictionary Key Types)</a>.
|
||||
</li>
|
||||
<li>
|
||||
例子 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-XID_154">闭包表达式 (Closure Expressions)</a> 中使用新的全局函数 <code>sorted</code> 取代原先的全局函数 <code>sort</code> 去展示如何返回一个全新的数组.
|
||||
</li>
|
||||
<li>
|
||||
更新关于 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-XID_320">结构体逐一成员构造器 (Memberwise Initializers for Structure Types)</a> 的描述:即使结构体的成员<b>没有默认值</b>,逐一成员构造器也可以自动获得。
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/BasicOperators.html#//apple_ref/doc/uid/TP40014097-CH6-XID_128">区间运算符(Half-Open Range Operator)</a>由<code>..</code>更新到<code>..<</code>
|
||||
</li>
|
||||
<li>
|
||||
添加一个例子 <a href="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Generics.html#//apple_ref/doc/uid/TP40014097-CH26-XID_285">扩展一个泛型(Extending a Generic Type)</a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
Binary file not shown.
Binary file not shown.
@ -1,794 +0,0 @@
|
||||
# 基础部分(The Basics)
|
||||
-----------------
|
||||
|
||||
> 1.0
|
||||
> 翻译:[numbbbbb](https://github.com/numbbbbb), [lyuka](https://github.com/lyuka), [JaySurplus](https://github.com/JaySurplus)
|
||||
> 校对:[lslxdx](https://github.com/lslxdx)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[xtymichael](https://github.com/xtymichael)
|
||||
|
||||
> 2.1
|
||||
> 翻译:[Prayer](https://github.com/futantan)
|
||||
> 校对:[shanks](http://codebuild.me),[overtrue](https://github.com/overtrue)
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [常量和变量](#constants_and_variables)
|
||||
- [声明常量和变量](#declaring)
|
||||
- [类型标注](#type_annotations)
|
||||
- [常量和变量的命名](#naming)
|
||||
- [输出常量和变量](#printing)
|
||||
- [注释](#comments)
|
||||
- [分号](#semicolons)
|
||||
- [整数](#integers)
|
||||
- [整数范围](#integer_bounds)
|
||||
- [Int](#Int)
|
||||
- [UInt](#UInt)
|
||||
- [浮点数](#floating-point_numbers)
|
||||
- [类型安全和类型推断](#type_safety_and_type_inference)
|
||||
- [数值型字面量](#numeric_literals)
|
||||
- [数值型类型转换](#numeric_type_conversion)
|
||||
- [整数转换](#integer_conversion)
|
||||
- [数整数和浮点数转换](#integer_and_floating_point_conversion)
|
||||
- [类型别名](#type_aliases)
|
||||
- [布尔值](#booleans)
|
||||
- [元组](#tuples)
|
||||
- [可选](#optionals)
|
||||
- [nil](#nil)
|
||||
- [if 语句以及强制解析](#if)
|
||||
- [可选绑定](#optional_binding)
|
||||
- [隐式解析可选类型](#implicityly_unwrapped_optionals)
|
||||
- [错误处理](#error_handling)
|
||||
- [断言](#assertions)
|
||||
|
||||
Swift 是一门开发 iOS, OS X 和 watchOS 应用的新语言。然而,如果你有 C 或者 Objective-C 开发经验的话,你会发现 Swift 的很多内容都是你熟悉的。
|
||||
|
||||
Swift 包含了 C 和 Objective-C 上所有基础数据类型,`Int`表示整型值;`Double`和`Float`表示浮点型值;`Bool`是布尔型值;`String`是文本型数据。Swift 还提供了三个基本的集合类型,`Array`,`Set`和`Dictionary`,详见[集合类型](04_Collection_Types.html)。
|
||||
|
||||
就像 C 语言一样,Swift 使用变量来进行存储并通过变量名来关联值。在 Swift 中,广泛的使用着值不可变的变量,它们就是常量,而且比 C 语言的常量更强大。在 Swift 中,如果你要处理的值不需要改变,那使用常量可以让你的代码更加安全并且更清晰地表达你的意图。
|
||||
|
||||
除了我们熟悉的类型,Swift 还增加了 Objective-C 中没有的高阶数据类型比如元组(Tuple)。元组可以让你创建或者传递一组数据,比如作为函数的返回值时,你可以用一个元组可以返回多个值。
|
||||
|
||||
Swift 还增加了可选(Optional)类型,用于处理值缺失的情况。可选表示“那儿有一个值,并且它等于 x ”或者“那儿没有值”。可选有点像在 Objective-C 中使用`nil`,但是它可以用在任何类型上,不仅仅是类。可选类型比 Objective-C 中的`nil`指针更加安全也更具表现力,它是 Swift 许多强大特性的重要组成部分。
|
||||
|
||||
Swift 是一门类型安全的语言,可选类型就是一个很好的例子。Swift 可以让你清楚地知道值的类型。如果你的代码期望得到一个`String`,类型安全会阻止你不小心传入一个`Int`。你可以在开发阶段尽早发现并修正错误。
|
||||
|
||||
<a name="constants_and_variables"></a>
|
||||
## 常量和变量
|
||||
|
||||
常量和变量把一个名字(比如`maximumNumberOfLoginAttempts`或者`welcomeMessage`)和一个指定类型的值(比如数字`10`或者字符串`"Hello"`)关联起来。常量的值一旦设定就不能改变,而变量的值可以随意更改。
|
||||
|
||||
<a name="declaring"></a>
|
||||
### 声明常量和变量
|
||||
|
||||
常量和变量必须在使用前声明,用`let`来声明常量,用`var`来声明变量。下面的例子展示了如何用常量和变量来记录用户尝试登录的次数:
|
||||
|
||||
```swift
|
||||
let maximumNumberOfLoginAttempts = 10
|
||||
var currentLoginAttempt = 0
|
||||
```
|
||||
|
||||
这两行代码可以被理解为:
|
||||
|
||||
“声明一个名字是`maximumNumberOfLoginAttempts`的新常量,并给它一个值`10`。然后,声明一个名字是`currentLoginAttempt`的变量并将它的值初始化为`0`。”
|
||||
|
||||
在这个例子中,允许的最大尝试登录次数被声明为一个常量,因为这个值不会改变。当前尝试登录次数被声明为一个变量,因为每次尝试登录失败的时候都需要增加这个值。
|
||||
|
||||
你可以在一行中声明多个常量或者多个变量,用逗号隔开:
|
||||
|
||||
```swift
|
||||
var x = 0.0, y = 0.0, z = 0.0
|
||||
```
|
||||
|
||||
>注意:
|
||||
如果你的代码中有不需要改变的值,请使用`let`关键字将它声明为常量。只将需要改变的值声明为变量。
|
||||
|
||||
<a name="type_annotations"></a>
|
||||
### 类型标注
|
||||
|
||||
当你声明常量或者变量的时候可以加上类型标注(type annotation),说明常量或者变量中要存储的值的类型。如果要添加类型标注,需要在常量或者变量名后面加上一个冒号和空格,然后加上类型名称。
|
||||
|
||||
这个例子给`welcomeMessage`变量添加了类型标注,表示这个变量可以存储`String`类型的值:
|
||||
|
||||
```swift
|
||||
var welcomeMessage: String
|
||||
```
|
||||
|
||||
声明中的冒号代表着“是...类型”,所以这行代码可以被理解为:
|
||||
|
||||
“声明一个类型为`String`,名字为`welcomeMessage`的变量。”
|
||||
|
||||
“类型为`String`”的意思是“可以存储任意`String`类型的值。”
|
||||
|
||||
`welcomeMessage`变量现在可以被设置成任意字符串:
|
||||
|
||||
```swift
|
||||
welcomeMessage = "Hello"
|
||||
```
|
||||
|
||||
你可以在一行中定义多个同样类型的变量,用逗号分割,并在最后一个变量名之后添加类型标注:
|
||||
|
||||
```swift
|
||||
var red, green, blue: Double
|
||||
```
|
||||
|
||||
> 注意:
|
||||
一般来说你很少需要写类型标注。如果你在声明常量或者变量的时候赋了一个初始值,Swift可以推断出这个常量或者变量的类型,请参考[类型安全和类型推断](#type_safety_and_type_inference)。在上面的例子中,没有给`welcomeMessage`赋初始值,所以变量`welcomeMessage`的类型是通过一个类型标注指定的,而不是通过初始值推断的。
|
||||
|
||||
<a name="naming"></a>
|
||||
### 常量和变量的命名
|
||||
|
||||
你可以用任何你喜欢的字符作为常量和变量名,包括 Unicode 字符:
|
||||
|
||||
```swift
|
||||
let π = 3.14159
|
||||
let 你好 = "你好世界"
|
||||
let 🐶🐮 = "dogcow"
|
||||
```
|
||||
|
||||
常量与变量名不能包含数学符号,箭头,保留的(或者非法的)Unicode 码位,连线与制表符。也不能以数字开头,但是可以在常量与变量名的其他地方包含数字。
|
||||
|
||||
一旦你将常量或者变量声明为确定的类型,你就不能使用相同的名字再次进行声明,或者改变其存储的值的类型。同时,你也不能将常量与变量进行互转。
|
||||
|
||||
> 注意:
|
||||
如果你需要使用与Swift保留关键字相同的名称作为常量或者变量名,你可以使用反引号(`)将关键字包围的方式将其作为名字使用。无论如何,你应当避免使用关键字作为常量或变量名,除非你别无选择。
|
||||
|
||||
你可以更改现有的变量值为其他同类型的值,在下面的例子中,`friendlyWelcome`的值从`"Hello!"`改为了`"Bonjour!"`:
|
||||
|
||||
```swift
|
||||
var friendlyWelcome = "Hello!"
|
||||
friendlyWelcome = "Bonjour!"
|
||||
// friendlyWelcome 现在是 "Bonjour!"
|
||||
```
|
||||
|
||||
与变量不同,常量的值一旦被确定就不能更改了。尝试这样做会导致编译时报错:
|
||||
|
||||
```swift
|
||||
let languageName = "Swift"
|
||||
languageName = "Swift++"
|
||||
// 这会报编译时错误 - languageName 不可改变
|
||||
```
|
||||
|
||||
<a name="printing"></a>
|
||||
### 输出常量和变量
|
||||
|
||||
你可以用`print(_:separator:terminator:)`函数来输出当前常量或变量的值:
|
||||
|
||||
```swift
|
||||
print(friendlyWelcome)
|
||||
// 输出 "Bonjour!"
|
||||
```
|
||||
|
||||
`print(_:separator:terminator:)`是一个用来输出一个或多个值到适当输出区的全局函数。如果你用 Xcode,`print(_:separator:terminator:)`将会输出内容到“console”面板上。`separator`和`terminator`参数具有默认值,因此你调用这个函数的时候可以忽略它们。默认情况下,该函数通过添加换行符来结束当前行。如果不想换行,可以传递一个空字符串给`terminator`参数--例如,`print(someValue, terminator:"")`。关于参数默认值的更多信息,请参考[默认参数值](./06_Functions.html#default_parameter_values)。
|
||||
|
||||
Swift 用字符串插值(string interpolation)的方式把常量名或者变量名当做占位符加入到长字符串中,Swift 会用当前常量或变量的值替换这些占位符。将常量或变量名放入圆括号中,并在开括号前使用反斜杠将其转义:
|
||||
|
||||
```swift
|
||||
print("The current value of friendlyWelcome is \(friendlyWelcome)")
|
||||
// 输出 "The current value of friendlyWelcome is Bonjour!
|
||||
```
|
||||
|
||||
> 注意:
|
||||
字符串插值所有可用的选项,请参考[字符串插值](./03_Strings_and_Characters.html#string_interpolation)。
|
||||
|
||||
<a name="comments"></a>
|
||||
## 注释
|
||||
请将你的代码中的非执行文本注释成提示或者笔记以方便你将来阅读。Swift 的编译器将会在编译代码时自动忽略掉注释部分。
|
||||
|
||||
Swift 中的注释与 C 语言的注释非常相似。单行注释以双正斜杠(`//`)作为起始标记:
|
||||
|
||||
```swift
|
||||
// 这是一个注释
|
||||
```
|
||||
|
||||
你也可以进行多行注释,其起始标记为单个正斜杠后跟随一个星号(`/*`),终止标记为一个星号后跟随单个正斜杠(`*/`):
|
||||
|
||||
```swift
|
||||
/* 这是一个,
|
||||
多行注释 */
|
||||
```
|
||||
|
||||
与 C 语言多行注释不同,Swift 的多行注释可以嵌套在其它的多行注释之中。你可以先生成一个多行注释块,然后在这个注释块之中再嵌套成第二个多行注释。终止注释时先插入第二个注释块的终止标记,然后再插入第一个注释块的终止标记:
|
||||
|
||||
```swift
|
||||
/* 这是第一个多行注释的开头
|
||||
/* 这是第二个被嵌套的多行注释 */
|
||||
这是第一个多行注释的结尾 */
|
||||
```
|
||||
|
||||
通过运用嵌套多行注释,你可以快速方便的注释掉一大段代码,即使这段代码之中已经含有了多行注释块。
|
||||
|
||||
<a name="semicolons"></a>
|
||||
## 分号
|
||||
与其他大部分编程语言不同,Swift 并不强制要求你在每条语句的结尾处使用分号(`;`),当然,你也可以按照你自己的习惯添加分号。有一种情况下必须要用分号,即你打算在同一行内写多条独立的语句:
|
||||
|
||||
```swift
|
||||
let cat = "🐱"; print(cat)
|
||||
// 输出 "🐱"
|
||||
```
|
||||
|
||||
<a name="integers"></a>
|
||||
## 整数
|
||||
|
||||
整数就是没有小数部分的数字,比如`42`和`-23`。整数可以是`有符号`(正、负、零)或者`无符号`(正、零)。
|
||||
|
||||
Swift 提供了8,16,32和64位的有符号和无符号整数类型。这些整数类型和 C 语言的命名方式很像,比如8位无符号整数类型是`UInt8`,32位有符号整数类型是`Int32`。就像 Swift 的其他类型一样,整数类型采用大写命名法。
|
||||
|
||||
<a name="integer_bounds"></a>
|
||||
### 整数范围
|
||||
|
||||
你可以访问不同整数类型的`min`和`max`属性来获取对应类型的最小值和最大值:
|
||||
|
||||
```swift
|
||||
let minValue = UInt8.min // minValue 为 0,是 UInt8 类型
|
||||
let maxValue = UInt8.max // maxValue 为 255,是 UInt8 类型
|
||||
```
|
||||
`min`和`max`所传回值的类型,正是其所对的整数类型(如上例UInt8, 所传回的类型是UInt8),可用在表达式中相同类型值旁。
|
||||
|
||||
<a name="Int"></a>
|
||||
### Int
|
||||
|
||||
一般来说,你不需要专门指定整数的长度。Swift 提供了一个特殊的整数类型`Int`,长度与当前平台的原生字长相同:
|
||||
|
||||
* 在32位平台上,`Int`和`Int32`长度相同。
|
||||
* 在64位平台上,`Int`和`Int64`长度相同。
|
||||
|
||||
除非你需要特定长度的整数,一般来说使用`Int`就够了。这可以提高代码一致性和可复用性。即使是在32位平台上,`Int`可以存储的整数范围也可以达到`-2,147,483,648`~`2,147,483,647`,大多数时候这已经足够大了。
|
||||
|
||||
<a name="UInt"></a>
|
||||
### UInt
|
||||
|
||||
Swift 也提供了一个特殊的无符号类型`UInt`,长度与当前平台的原生字长相同:
|
||||
|
||||
* 在32位平台上,`UInt`和`UInt32`长度相同。
|
||||
* 在64位平台上,`UInt`和`UInt64`长度相同。
|
||||
|
||||
> 注意:
|
||||
尽量不要使用`UInt`,除非你真的需要存储一个和当前平台原生字长相同的无符号整数。除了这种情况,最好使用`Int`,即使你要存储的值已知是非负的。统一使用`Int`可以提高代码的可复用性,避免不同类型数字之间的转换,并且匹配数字的类型推断,请参考[类型安全和类型推断](#type_safety_and_type_inference)。
|
||||
|
||||
<a name="floating-point_numbers"></a>
|
||||
## 浮点数
|
||||
|
||||
浮点数是有小数部分的数字,比如`3.14159`,`0.1`和`-273.15`。
|
||||
|
||||
浮点类型比整数类型表示的范围更大,可以存储比`Int`类型更大或者更小的数字。Swift 提供了两种有符号浮点数类型:
|
||||
|
||||
* `Double`表示64位浮点数。当你需要存储很大或者很高精度的浮点数时请使用此类型。
|
||||
* `Float`表示32位浮点数。精度要求不高的话可以使用此类型。
|
||||
|
||||
> 注意:
|
||||
`Double`精确度很高,至少有15位数字,而`Float`只有6位数字。选择哪个类型取决于你的代码需要处理的值的范围。
|
||||
|
||||
<a name="type_safety_and_type_inference"></a>
|
||||
## 类型安全和类型推断
|
||||
|
||||
Swift 是一个*类型安全(type safe)*的语言。类型安全的语言可以让你清楚地知道代码要处理的值的类型。如果你的代码需要一个`String`,你绝对不可能不小心传进去一个`Int`。
|
||||
|
||||
由于 Swift 是类型安全的,所以它会在编译你的代码时进行*类型检查(type checks)*,并把不匹配的类型标记为错误。这可以让你在开发的时候尽早发现并修复错误。
|
||||
|
||||
当你要处理不同类型的值时,类型检查可以帮你避免错误。然而,这并不是说你每次声明常量和变量的时候都需要显式指定类型。如果你没有显式指定类型,Swift 会使用*类型推断(type inference)*来选择合适的类型。有了类型推断,编译器可以在编译代码的时候自动推断出表达式的类型。原理很简单,只要检查你赋的值即可。
|
||||
|
||||
因为有类型推断,和 C 或者 Objective-C 比起来 Swift 很少需要声明类型。常量和变量虽然需要明确类型,但是大部分工作并不需要你自己来完成。
|
||||
|
||||
当你声明常量或者变量并赋初值的时候类型推断非常有用。当你在声明常量或者变量的时候赋给它们一个字面量(literal value 或 literal)即可触发类型推断。(字面量就是会直接出现在你代码中的值,比如`42`和`3.14159`。)
|
||||
|
||||
例如,如果你给一个新常量赋值`42`并且没有标明类型,Swift 可以推断出常量类型是`Int`,因为你给它赋的初始值看起来像一个整数:
|
||||
|
||||
```swift
|
||||
let meaningOfLife = 42
|
||||
// meaningOfLife 会被推测为 Int 类型
|
||||
```
|
||||
|
||||
同理,如果你没有给浮点字面量标明类型,Swift 会推断你想要的是`Double`:
|
||||
|
||||
```swift
|
||||
let pi = 3.14159
|
||||
// pi 会被推测为 Double 类型
|
||||
```
|
||||
|
||||
当推断浮点数的类型时,Swift 总是会选择`Double`而不是`Float`。
|
||||
|
||||
如果表达式中同时出现了整数和浮点数,会被推断为`Double`类型:
|
||||
|
||||
```swift
|
||||
let anotherPi = 3 + 0.14159
|
||||
// anotherPi 会被推测为 Double 类型
|
||||
```
|
||||
|
||||
原始值`3`没有显式声明类型,而表达式中出现了一个浮点字面量,所以表达式会被推断为`Double`类型。
|
||||
|
||||
<a name="numeric_literals"></a>
|
||||
## 数值型字面量
|
||||
|
||||
整数字面量可以被写作:
|
||||
|
||||
* 一个*十进制*数,没有前缀
|
||||
* 一个*二进制*数,前缀是`0b`
|
||||
* 一个*八进制*数,前缀是`0o`
|
||||
* 一个*十六进制*数,前缀是`0x`
|
||||
|
||||
下面的所有整数字面量的十进制值都是`17`:
|
||||
|
||||
```swift
|
||||
let decimalInteger = 17
|
||||
let binaryInteger = 0b10001 // 二进制的17
|
||||
let octalInteger = 0o21 // 八进制的17
|
||||
let hexadecimalInteger = 0x11 // 十六进制的17
|
||||
```
|
||||
|
||||
浮点字面量可以是十进制(没有前缀)或者是十六进制(前缀是`0x`)。小数点两边必须有至少一个十进制数字(或者是十六进制的数字)。十进制浮点数也可以有一个可选的指数(exponent),通过大写或者小写的 `e` 来指定;十六进制浮点数必须有一个指数,通过大写或者小写的 `p` 来指定。
|
||||
|
||||
如果一个十进制数的指数为`exp`,那这个数相当于基数和10^exp的乘积:
|
||||
* `1.25e2` 表示 1.25 × 10^2,等于 `125.0`。
|
||||
* `1.25e-2` 表示 1.25 × 10^-2,等于 `0.0125`。
|
||||
|
||||
如果一个十六进制数的指数为`exp`,那这个数相当于基数和2^exp的乘积:
|
||||
* `0xFp2` 表示 15 × 2^2,等于 `60.0`。
|
||||
* `0xFp-2` 表示 15 × 2^-2,等于 `3.75`。
|
||||
|
||||
下面的这些浮点字面量都等于十进制的`12.1875`:
|
||||
|
||||
```swift
|
||||
let decimalDouble = 12.1875
|
||||
let exponentDouble = 1.21875e1
|
||||
let hexadecimalDouble = 0xC.3p0
|
||||
```
|
||||
|
||||
数值类字面量可以包括额外的格式来增强可读性。整数和浮点数都可以添加额外的零并且包含下划线,并不会影响字面量:
|
||||
|
||||
```swift
|
||||
let paddedDouble = 000123.456
|
||||
let oneMillion = 1_000_000
|
||||
let justOverOneMillion = 1_000_000.000_000_1
|
||||
```
|
||||
|
||||
<a name="numeric_type_conversion"></a>
|
||||
## 数值型类型转换
|
||||
|
||||
通常来讲,即使代码中的整数常量和变量已知非负,也请使用`Int`类型。总是使用默认的整数类型可以保证你的整数常量和变量可以直接被复用并且可以匹配整数类字面量的类型推断。
|
||||
|
||||
只有在必要的时候才使用其他整数类型,比如要处理外部的长度明确的数据或者为了优化性能、内存占用等等。使用显式指定长度的类型可以及时发现值溢出并且可以暗示正在处理特殊数据。
|
||||
|
||||
<a name="integer_conversion"></a>
|
||||
### 整数转换
|
||||
|
||||
不同整数类型的变量和常量可以存储不同范围的数字。`Int8`类型的常量或者变量可以存储的数字范围是`-128`~`127`,而`UInt8`类型的常量或者变量能存储的数字范围是`0`~`255`。如果数字超出了常量或者变量可存储的范围,编译的时候会报错:
|
||||
|
||||
```swift
|
||||
let cannotBeNegative: UInt8 = -1
|
||||
// UInt8 类型不能存储负数,所以会报错
|
||||
let tooBig: Int8 = Int8.max + 1
|
||||
// Int8 类型不能存储超过最大值的数,所以会报错
|
||||
```
|
||||
|
||||
由于每种整数类型都可以存储不同范围的值,所以你必须根据不同情况选择性使用数值型类型转换。这种选择性使用的方式,可以预防隐式转换的错误并让你的代码中的类型转换意图变得清晰。
|
||||
|
||||
要将一种数字类型转换成另一种,你要用当前值来初始化一个期望类型的新数字,这个数字的类型就是你的目标类型。在下面的例子中,常量`twoThousand`是`UInt16`类型,然而常量`one`是`UInt8`类型。它们不能直接相加,因为它们类型不同。所以要调用`UInt16(one)`来创建一个新的`UInt16`数字并用`one`的值来初始化,然后使用这个新数字来计算:
|
||||
|
||||
```swift
|
||||
let twoThousand: UInt16 = 2_000
|
||||
let one: UInt8 = 1
|
||||
let twoThousandAndOne = twoThousand + UInt16(one)
|
||||
```
|
||||
|
||||
现在两个数字的类型都是`UInt16`,可以进行相加。目标常量`twoThousandAndOne`的类型被推断为`UInt16`,因为它是两个`UInt16`值的和。
|
||||
|
||||
`SomeType(ofInitialValue)`是调用 Swift 构造器并传入一个初始值的默认方法。在语言内部,`UInt16`有一个构造器,可以接受一个`UInt8`类型的值,所以这个构造器可以用现有的`UInt8`来创建一个新的`UInt16`。注意,你并不能传入任意类型的值,只能传入`UInt16`内部有对应构造器的值。不过你可以扩展现有的类型来让它可以接收其他类型的值(包括自定义类型),请参考[扩展](./20_Extensions.html)。
|
||||
|
||||
<a name="integer_and_floating_point_conversion"></a>
|
||||
### 整数和浮点数转换
|
||||
|
||||
整数和浮点数的转换必须显式指定类型:
|
||||
|
||||
```swift
|
||||
let three = 3
|
||||
let pointOneFourOneFiveNine = 0.14159
|
||||
let pi = Double(three) + pointOneFourOneFiveNine
|
||||
// pi 等于 3.14159,所以被推测为 Double 类型
|
||||
```
|
||||
|
||||
这个例子中,常量`three`的值被用来创建一个`Double`类型的值,所以加号两边的数类型须相同。如果不进行转换,两者无法相加。
|
||||
|
||||
浮点数到整数的反向转换同样行,整数类型可以用`Double`或者`Float`类型来初始化:
|
||||
|
||||
```swift
|
||||
let integerPi = Int(pi)
|
||||
// integerPi 等于 3,所以被推测为 Int 类型
|
||||
```
|
||||
|
||||
当用这种方式来初始化一个新的整数值时,浮点值会被截断。也就是说`4.75`会变成`4`,`-3.9`会变成`-3`。
|
||||
|
||||
> 注意:
|
||||
结合数字类常量和变量不同于结合数字类字面量。字面量`3`可以直接和字面量`0.14159`相加,因为数字字面量本身没有明确的类型。它们的类型只在编译器需要求值的时候被推测。
|
||||
|
||||
<a name="type_aliases"></a>
|
||||
## 类型别名
|
||||
|
||||
类型别名(type aliases)就是给现有类型定义另一个名字。你可以使用`typealias`关键字来定义类型别名。
|
||||
|
||||
当你想要给现有类型起一个更有意义的名字时,类型别名非常有用。假设你正在处理特定长度的外部资源的数据:
|
||||
|
||||
```swift
|
||||
typealias AudioSample = UInt16
|
||||
```
|
||||
|
||||
定义了一个类型别名之后,你可以在任何使用原始名的地方使用别名:
|
||||
|
||||
```swift
|
||||
var maxAmplitudeFound = AudioSample.min
|
||||
// maxAmplitudeFound 现在是 0
|
||||
```
|
||||
|
||||
本例中,`AudioSample`被定义为`UInt16`的一个别名。因为它是别名,`AudioSample.min`实际上是`UInt16.min`,所以会给`maxAmplitudeFound`赋一个初值`0`。
|
||||
|
||||
<a name="booleans"></a>
|
||||
## 布尔值
|
||||
|
||||
Swift 有一个基本的布尔(Boolean)类型,叫做`Bool`。布尔值指逻辑上的值,因为它们只能是真或者假。Swift 有两个布尔常量,`true`和`false`:
|
||||
|
||||
```swift
|
||||
let orangesAreOrange = true
|
||||
let turnipsAreDelicious = false
|
||||
```
|
||||
|
||||
`orangesAreOrange`和`turnipsAreDelicious`的类型会被推断为`Bool`,因为它们的初值是布尔字面量。就像之前提到的`Int`和`Double`一样,如果你创建变量的时候给它们赋值`true`或者`false`,那你不需要将常量或者变量声明为`Bool`类型。初始化常量或者变量的时候如果所赋的值类型已知,就可以触发类型推断,这让 Swift 代码更加简洁并且可读性更高。
|
||||
|
||||
当你编写条件语句比如`if`语句的时候,布尔值非常有用:
|
||||
|
||||
```swift
|
||||
if turnipsAreDelicious {
|
||||
print("Mmm, tasty turnips!")
|
||||
} else {
|
||||
print("Eww, turnips are horrible.")
|
||||
}
|
||||
// 输出 "Eww, turnips are horrible."
|
||||
```
|
||||
|
||||
条件语句,例如`if`,请参考[控制流](./05_Control_Flow.html)。
|
||||
|
||||
如果你在需要使用`Bool`类型的地方使用了非布尔值,Swift 的类型安全机制会报错。下面的例子会报告一个编译时错误:
|
||||
|
||||
```swift
|
||||
let i = 1
|
||||
if i {
|
||||
// 这个例子不会通过编译,会报错
|
||||
}
|
||||
```
|
||||
|
||||
然而,下面的例子是合法的:
|
||||
|
||||
```swift
|
||||
let i = 1
|
||||
if i == 1 {
|
||||
// 这个例子会编译成功
|
||||
}
|
||||
```
|
||||
|
||||
`i == 1`的比较结果是`Bool`类型,所以第二个例子可以通过类型检查。类似`i == 1`这样的比较,请参考[基本操作符](./05_Control_Flow.html)。
|
||||
|
||||
和 Swift 中的其他类型安全的例子一样,这个方法可以避免错误并保证这块代码的意图总是清晰的。
|
||||
|
||||
<a name="tuples"></a>
|
||||
## 元组
|
||||
|
||||
*元组(tuples)*把多个值组合成一个复合值。元组内的值可以是任意类型,并不要求是相同类型。
|
||||
|
||||
下面这个例子中,`(404, "Not Found")`是一个描述 *HTTP 状态码(HTTP status code)*的元组。HTTP 状态码是当你请求网页的时候 web 服务器返回的一个特殊值。如果你请求的网页不存在就会返回一个`404 Not Found`状态码。
|
||||
|
||||
```swift
|
||||
let http404Error = (404, "Not Found")
|
||||
// http404Error 的类型是 (Int, String),值是 (404, "Not Found")
|
||||
```
|
||||
|
||||
`(404, "Not Found")`元组把一个`Int`值和一个`String`值组合起来表示 HTTP 状态码的两个部分:一个数字和一个人类可读的描述。这个元组可以被描述为“一个类型为`(Int, String)`的元组”。
|
||||
|
||||
你可以把任意顺序的类型组合成一个元组,这个元组可以包含所有类型。只要你想,你可以创建一个类型为`(Int, Int, Int)`或者`(String, Bool)`或者其他任何你想要的组合的元组。
|
||||
|
||||
你可以将一个元组的内容分解(decompose)成单独的常量和变量,然后你就可以正常使用它们了:
|
||||
|
||||
```swift
|
||||
let (statusCode, statusMessage) = http404Error
|
||||
print("The status code is \(statusCode)")
|
||||
// 输出 "The status code is 404"
|
||||
print("The status message is \(statusMessage)")
|
||||
// 输出 "The status message is Not Found"
|
||||
```
|
||||
|
||||
如果你只需要一部分元组值,分解的时候可以把要忽略的部分用下划线(`_`)标记:
|
||||
|
||||
```swift
|
||||
let (justTheStatusCode, _) = http404Error
|
||||
print("The status code is \(justTheStatusCode)")
|
||||
// 输出 "The status code is 404"
|
||||
```
|
||||
|
||||
此外,你还可以通过下标来访问元组中的单个元素,下标从零开始:
|
||||
|
||||
```swift
|
||||
print("The status code is \(http404Error.0)")
|
||||
// 输出 "The status code is 404"
|
||||
print("The status message is \(http404Error.1)")
|
||||
// 输出 "The status message is Not Found"
|
||||
```
|
||||
|
||||
你可以在定义元组的时候给单个元素命名:
|
||||
|
||||
```swift
|
||||
let http200Status = (statusCode: 200, description: "OK")
|
||||
```
|
||||
|
||||
给元组中的元素命名后,你可以通过名字来获取这些元素的值:
|
||||
|
||||
```swift
|
||||
print("The status code is \(http200Status.statusCode)")
|
||||
// 输出 "The status code is 200"
|
||||
print("The status message is \(http200Status.description)")
|
||||
// 输出 "The status message is OK"
|
||||
```
|
||||
|
||||
作为函数返回值时,元组非常有用。一个用来获取网页的函数可能会返回一个`(Int, String)`元组来描述是否获取成功。和只能返回一个类型的值比较起来,一个包含两个不同类型值的元组可以让函数的返回信息更有用。请参考[函数参数与返回值](./06_Functions.html#Function_Parameters_and_Return_Values)。
|
||||
|
||||
> 注意:
|
||||
元组在临时组织值的时候很有用,但是并不适合创建复杂的数据结构。如果你的数据结构并不是临时使用,请使用类或者结构体而不是元组。请参考[类和结构体](./09_Classes_and_Structures.html)。
|
||||
|
||||
<a name="optionals"></a>
|
||||
## 可选类型
|
||||
|
||||
使用*可选类型(optionals)*来处理值可能缺失的情况。可选类型表示:
|
||||
|
||||
* 有值,等于 x
|
||||
|
||||
或者
|
||||
|
||||
* 没有值
|
||||
|
||||
> 注意:
|
||||
C 和 Objective-C 中并没有可选类型这个概念。最接近的是 Objective-C 中的一个特性,一个方法要不返回一个对象要不返回`nil`,`nil`表示“缺少一个合法的对象”。然而,这只对对象起作用——对于结构体,基本的 C 类型或者枚举类型不起作用。对于这些类型,Objective-C 方法一般会返回一个特殊值(比如`NSNotFound`)来暗示值缺失。这种方法假设方法的调用者知道并记得对特殊值进行判断。然而,Swift 的可选类型可以让你暗示_任意类型_的值缺失,并不需要一个特殊值。
|
||||
|
||||
来看一个例子。Swift 的`Int`类型有一种构造器,作用是将一个`String`值转换成一个`Int`值。然而,并不是所有的字符串都可以转换成一个整数。字符串`"123"`可以被转换成数字`123`,但是字符串`"hello, world"`不行。
|
||||
|
||||
下面的例子使用这种构造器来尝试将一个`String`转换成`Int`:
|
||||
|
||||
```swift
|
||||
let possibleNumber = "123"
|
||||
let convertedNumber = Int(possibleNumber)
|
||||
// convertedNumber 被推测为类型 "Int?", 或者类型 "optional Int"
|
||||
```
|
||||
|
||||
因为该构造器可能会失败,所以它返回一个_可选类型_(optional)`Int`,而不是一个`Int`。一个可选的`Int`被写作`Int?`而不是`Int`。问号暗示包含的值是可选类型,也就是说可能包含`Int`值也可能*不包含值*。(不能包含其他任何值比如`Bool`值或者`String`值。只能是`Int`或者什么都没有。)
|
||||
|
||||
<a name="nil"></a>
|
||||
### nil
|
||||
|
||||
你可以给可选变量赋值为`nil`来表示它没有值:
|
||||
|
||||
```swift
|
||||
var serverResponseCode: Int? = 404
|
||||
// serverResponseCode 包含一个可选的 Int 值 404
|
||||
serverResponseCode = nil
|
||||
// serverResponseCode 现在不包含值
|
||||
```
|
||||
|
||||
> 注意:
|
||||
`nil`不能用于非可选的常量和变量。如果你的代码中有常量或者变量需要处理值缺失的情况,请把它们声明成对应的可选类型。
|
||||
|
||||
如果你声明一个可选常量或者变量但是没有赋值,它们会自动被设置为`nil`:
|
||||
|
||||
```swift
|
||||
var surveyAnswer: String?
|
||||
// surveyAnswer 被自动设置为 nil
|
||||
```
|
||||
|
||||
> 注意:
|
||||
Swift 的`nil`和 Objective-C 中的`nil`并不一样。在 Objective-C 中,`nil`是一个指向不存在对象的指针。在 Swift 中,`nil`不是指针——它是一个确定的值,用来表示值缺失。任何类型的可选状态都可以被设置为`nil`,不只是对象类型。
|
||||
|
||||
<a name="if"></a>
|
||||
### if 语句以及强制解析
|
||||
|
||||
你可以使用`if`语句和`nil`比较来判断一个可选值是否包含值。你可以使用“相等”(`==`)或“不等”(`!=`)来执行比较。
|
||||
|
||||
如果可选类型有值,它将不等于`nil`:
|
||||
|
||||
```swift
|
||||
if convertedNumber != nil {
|
||||
print("convertedNumber contains some integer value.")
|
||||
}
|
||||
// 输出 "convertedNumber contains some integer value."
|
||||
```
|
||||
当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号(`!`)来获取值。这个惊叹号表示“我知道这个可选有值,请使用它。”这被称为可选值的_强制解析(forced unwrapping)_:
|
||||
|
||||
```swift
|
||||
if convertedNumber != nil {
|
||||
print("convertedNumber has an integer value of \(convertedNumber!).")
|
||||
}
|
||||
// 输出 "convertedNumber has an integer value of 123."
|
||||
```
|
||||
|
||||
更多关于`if`语句的内容,请参考[控制流](05_Control_Flow.html)。
|
||||
|
||||
> 注意:
|
||||
使用`!`来获取一个不存在的可选值会导致运行时错误。使用`!`来强制解析值之前,一定要确定可选包含一个非`nil`的值。
|
||||
|
||||
<a name="optional_binding"></a>
|
||||
### 可选绑定
|
||||
|
||||
使用*可选绑定(optional binding)*来判断可选类型是否包含值,如果包含就把值赋给一个临时常量或者变量。可选绑定可以用在`if`和`while`语句中,这条语句不仅可以用来判断可选类型中是否有值,同时可以将可选类型中的值赋给一个常量或者变量。`if`和`while`语句,请参考[控制流](./05_Control_Flow.html)。
|
||||
|
||||
像下面这样在`if`语句中写一个可选绑定:
|
||||
|
||||
```swift
|
||||
if let constantName = someOptional {
|
||||
statements
|
||||
}
|
||||
```
|
||||
|
||||
你可以像上面这样使用可选绑定来重写`possibleNumber`这个[例子](./01_The_Basics.html#optionals):
|
||||
|
||||
```swift
|
||||
if let actualNumber = Int(possibleNumber) {
|
||||
print("\'\(possibleNumber)\' has an integer value of \(actualNumber)")
|
||||
} else {
|
||||
print("\'\(possibleNumber)\' could not be converted to an integer")
|
||||
}
|
||||
// 输出 "'123' has an integer value of 123"
|
||||
```
|
||||
|
||||
这段代码可以被理解为:
|
||||
|
||||
“如果`Int(possibleNumber)`返回的可选`Int`包含一个值,创建一个叫做`actualNumber`的新常量并将可选包含的值赋给它。”
|
||||
|
||||
如果转换成功,`actualNumber`常量可以在`if`语句的第一个分支中使用。它已经被可选类型 _包含的_ 值初始化过,所以不需要再使用`!`后缀来获取它的值。在这个例子中,`actualNumber`只被用来输出转换结果。
|
||||
|
||||
你可以在可选绑定中使用常量和变量。如果你想在`if`语句的第一个分支中操作`actualNumber`的值,你可以改成`if var actualNumber`,这样可选类型包含的值就会被赋给一个变量而非常量。
|
||||
|
||||
你可以包含多个可选绑定在`if`语句中,并使用`where`子句做布尔值判断。
|
||||
|
||||
```swift
|
||||
if let firstNumber = Int("4"), secondNumber = Int("42") where firstNumber < secondNumber {
|
||||
print("\(firstNumber) < \(secondNumber)")
|
||||
}
|
||||
// prints "4 < 42"
|
||||
```
|
||||
|
||||
<a name="implicityly_unwrapped_optionals"></a>
|
||||
### 隐式解析可选类型
|
||||
|
||||
如上所述,可选类型暗示了常量或者变量可以“没有值”。可选可以通过`if`语句来判断是否有值,如果有值的话可以通过可选绑定来解析值。
|
||||
|
||||
有时候在程序架构中,第一次被赋值之后,可以确定一个可选类型_总会_有值。在这种情况下,每次都要判断和解析可选值是非常低效的,因为可以确定它总会有值。
|
||||
|
||||
这种类型的可选状态被定义为隐式解析可选类型(implicitly unwrapped optionals)。把想要用作可选的类型的后面的问号(`String?`)改成感叹号(`String!`)来声明一个隐式解析可选类型。
|
||||
|
||||
当可选类型被第一次赋值之后就可以确定之后一直有值的时候,隐式解析可选类型非常有用。隐式解析可选类型主要被用在 Swift 中类的构造过程中,请参考[无主引用以及隐式解析可选属性](./16_Automatic_Reference_Counting.html#unowned_references_and_implicitly_unwrapped_optional_properties)。
|
||||
|
||||
一个隐式解析可选类型其实就是一个普通的可选类型,但是可以被当做非可选类型来使用,并不需要每次都使用解析来获取可选值。下面的例子展示了可选类型`String`和隐式解析可选类型`String`之间的区别:
|
||||
|
||||
```swift
|
||||
let possibleString: String? = "An optional string."
|
||||
let forcedString: String = possibleString! // 需要惊叹号来获取值
|
||||
|
||||
let assumedString: String! = "An implicitly unwrapped optional string."
|
||||
let implicitString: String = assumedString // 不需要感叹号
|
||||
```
|
||||
|
||||
你可以把隐式解析可选类型当做一个可以自动解析的可选类型。你要做的只是声明的时候把感叹号放到类型的结尾,而不是每次取值的可选名字的结尾。
|
||||
|
||||
> 注意:
|
||||
如果你在隐式解析可选类型没有值的时候尝试取值,会触发运行时错误。和你在没有值的普通可选类型后面加一个惊叹号一样。
|
||||
|
||||
你仍然可以把隐式解析可选类型当做普通可选类型来判断它是否包含值:
|
||||
|
||||
```swift
|
||||
if assumedString != nil {
|
||||
print(assumedString)
|
||||
}
|
||||
// 输出 "An implicitly unwrapped optional string."
|
||||
```
|
||||
|
||||
你也可以在可选绑定中使用隐式解析可选类型来检查并解析它的值:
|
||||
|
||||
```swift
|
||||
if let definiteString = assumedString {
|
||||
print(definiteString)
|
||||
}
|
||||
// 输出 "An implicitly unwrapped optional string."
|
||||
```
|
||||
|
||||
> 注意:
|
||||
如果一个变量之后可能变成`nil`的话请不要使用隐式解析可选类型。如果你需要在变量的生命周期中判断是否是`nil`的话,请使用普通可选类型。
|
||||
|
||||
<a name="error_handling"></a>
|
||||
## 错误处理
|
||||
你可以使用*错误处理(error handling)*来应对程序执行中可能会遇到的错误条件。
|
||||
|
||||
相对于可选中运用值的存在与缺失来表达函数的成功与失败,错误处理可以推断失败的原因,并传播至程序的其他部分。
|
||||
|
||||
当一个函数遇到错误条件,它能报错。调用函数的地方能抛出错误消息并合理处理。
|
||||
|
||||
```swift
|
||||
func canThrowAnError() throws {
|
||||
// 这个函数有可能抛出错误
|
||||
}
|
||||
```
|
||||
|
||||
一个函数可以通过在声明中添加`throws`关键词来抛出错误消息。当你的函数能抛出错误消息时, 你应该在表达式中前置`try`关键词。
|
||||
|
||||
```swift
|
||||
do {
|
||||
try canThrowAnError()
|
||||
// 没有错误消息抛出
|
||||
} catch {
|
||||
// 有一个错误消息抛出
|
||||
}
|
||||
```
|
||||
|
||||
一个`do`语句创建了一个新的包含作用域,使得错误能被传播到一个或多个`catch`从句。
|
||||
|
||||
这里有一个错误处理如何用来应对不同错误条件的例子。
|
||||
|
||||
```swift
|
||||
func makeASandwich() throws {
|
||||
// ...
|
||||
}
|
||||
|
||||
do {
|
||||
try makeASandwich()
|
||||
eatASandwich()
|
||||
} catch Error.OutOfCleanDishes {
|
||||
washDishes()
|
||||
} catch Error.MissingIngredients(let ingredients) {
|
||||
buyGroceries(ingredients)
|
||||
}
|
||||
```
|
||||
|
||||
在此例中,`makeASandwich()`(做一个三明治)函数会抛出一个错误消息如果没有干净的盘子或者某个原料缺失。因为`makeASandwich()`抛出错误,函数调用被包裹在`try`表达式中。将函数包裹在一个`do`语句中,任何被抛出的错误会被传播到提供的`catch`从句中。
|
||||
|
||||
如果没有错误被抛出, `eatASandwich()`函数会被调用。如果一个匹配`Error.OutOfCleanDishes`的错误被抛出,`washDishes`函数会被调用。如果一个匹配`Error.MissingIngredients`的错误被抛出,`buyGroceries(_:)`函数会随着被`catch`所捕捉到的关联值`[String]`被调用。
|
||||
|
||||
抛出,捕捉,以及传播错误会在[错误处理](./18_Error_Handling.html)章节详细说明。
|
||||
|
||||
<a name="assertions"></a>
|
||||
## 断言
|
||||
|
||||
可选类型可以让你判断值是否存在,你可以在代码中优雅地处理值缺失的情况。然而,在某些情况下,如果值缺失或者值并不满足特定的条件,你的代码可能没办法继续执行。这时,你可以在你的代码中触发一个*断言(assertion)*来结束代码运行并通过调试来找到值缺失的原因。
|
||||
|
||||
### 使用断言进行调试
|
||||
|
||||
断言会在运行时判断一个逻辑条件是否为`true`。从字面意思来说,断言“断言”一个条件是否为真。你可以使用断言来保证在运行其他代码之前,某些重要的条件已经被满足。如果条件判断为`true`,代码运行会继续进行;如果条件判断为`false`,代码执行结束,你的应用被终止。
|
||||
|
||||
如果你的代码在调试环境下触发了一个断言,比如你在 Xcode 中构建并运行一个应用,你可以清楚地看到不合法的状态发生在哪里并检查断言被触发时你的应用的状态。此外,断言允许你附加一条调试信息。
|
||||
|
||||
你可以使用全局`assert(_:_file:line:)`函数来写一个断言。向这个函数传入一个结果为`true`或者`false`的表达式以及一条信息,当表达式的结果为`false`的时候这条信息会被显示:
|
||||
|
||||
```swift
|
||||
let age = -3
|
||||
assert(age >= 0, "A person's age cannot be less than zero")
|
||||
// 因为 age < 0,所以断言会触发
|
||||
```
|
||||
|
||||
在这个例子中,只有`age >= 0`为`true`的时候,即`age`的值非负的时候,代码才会继续执行。如果`age`的值是负数,就像代码中那样,`age >= 0`为`false`,断言被触发,终止应用。
|
||||
|
||||
如果不需要断言信息,可以省略,就像这样:
|
||||
|
||||
```swift
|
||||
assert(age >= 0)
|
||||
```
|
||||
|
||||
> 注意:
|
||||
当代码使用优化编译的时候,断言将会被禁用,例如在 Xcode 中,使用默认的 target Release 配置选项来 build 时,断言会被禁用。
|
||||
|
||||
### 何时使用断言
|
||||
|
||||
当条件可能为假时使用断言,但是最终一定要_保证_条件为真,这样你的代码才能继续运行。断言的适用情景:
|
||||
|
||||
* 整数类型的下标索引被传入一个自定义下标实现,但是下标索引值可能太小或者太大。
|
||||
* 需要给函数传入一个值,但是非法的值可能导致函数不能正常执行。
|
||||
* 一个可选值现在是`nil`,但是后面的代码运行需要一个非`nil`值。
|
||||
|
||||
请参考[下标](./12_Subscripts.html)和[函数](./06_Functions.html)。
|
||||
|
||||
> 注意:
|
||||
断言可能导致你的应用终止运行,所以你应当仔细设计你的代码来让非法条件不会出现。然而,在你的应用发布之前,有时候非法条件可能出现,这时使用断言可以快速发现问题。
|
||||
|
||||
|
||||
@ -1,508 +0,0 @@
|
||||
# 基本运算符(Basic Operators)
|
||||
-----------------
|
||||
|
||||
> 1.0
|
||||
> 翻译:[XieLingWang](https://github.com/xielingwang)
|
||||
> 校对:[EvilCome](https://github.com/Evilcome)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[JackAlan](https://github.com/AlanMelody)
|
||||
|
||||
> 2.1
|
||||
> 校对:[shanks](http://codebuild.me)
|
||||
|
||||
> 2.2
|
||||
> 翻译+校对:[Cee](https://github.com/Cee)
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [术语](#terminology)
|
||||
- [赋值运算符](#assignment_operator)
|
||||
- [算术运算符](#arithmetic_operators)
|
||||
- [组合赋值运算符](#compound_assignment_operators)
|
||||
- [比较运算符](#comparison_operators)
|
||||
- [三目运算符](#ternary_conditional_operator)
|
||||
- [空合运算符](#nil_coalescing_operator)
|
||||
- [区间运算符](#range_operators)
|
||||
- [逻辑运算符](#logical_operators)
|
||||
|
||||
运算符是检查、改变、合并值的特殊符号或短语。例如,加号(`+`)将两个数相加(如 `let i = 1 + 2`)。更复杂的运算例子包括逻辑与运算符 `&&`(如 `if enteredDoorCode && passedRetinaScan`),或让 i 值加 1 的便捷自增运算符 `++i` 等。
|
||||
|
||||
Swift 支持大部分标准 C 语言的运算符,且改进许多特性来减少常规编码错误。如:赋值符(`=`)不返回值,以防止把想要判断相等运算符(`==`)的地方写成赋值符导致的错误。算术运算符(`+`,`-`,`*`,`/`,`%`等)会检测并不允许值溢出,以此来避免保存变量时由于变量大于或小于其类型所能承载的范围时导致的异常结果。当然允许你使用 Swift 的溢出运算符来实现溢出。详情参见[溢出运算符](../chapter2/25_Advanced_Operators.html#overflow_operators)。
|
||||
|
||||
区别于 C 语言,在 Swift 中你可以对浮点数进行取余运算(`%`),Swift 还提供了 C 语言没有的表达两数之间的值的区间运算符(`a..<b` 和 `a...b`),这方便我们表达一个区间内的数值。
|
||||
|
||||
本章节只描述了 Swift 中的基本运算符,[高级运算符](../chapter2/25_Advanced_Operators.html)这章会包含 Swift 中的高级运算符,及如何自定义运算符,及如何进行自定义类型的运算符重载。
|
||||
|
||||
<a name="terminology"></a>
|
||||
## 术语
|
||||
|
||||
运算符分为一元、二元和三元运算符。
|
||||
|
||||
- 一元运算符对单一操作对象操作(如 `-a`)。一元运算符分前置运算符和后置运算符,前置运算符需紧跟在操作对象之前(如 `!b`),后置运算符需紧跟在操作对象之后(如 `i++`)。
|
||||
- 二元运算符操作两个操作对象(如 `2 + 3`),是中置的,因为它们出现在两个操作对象之间。
|
||||
- 三元运算符操作三个操作对象,和 C 语言一样,Swift 只有一个三元运算符,就是三目运算符(`a ? b : c`)。
|
||||
|
||||
受运算符影响的值叫操作数,在表达式 `1 + 2` 中,加号 `+` 是二元运算符,它的两个操作数是值 `1` 和 `2`。
|
||||
|
||||
<a name="assignment_operator"></a>
|
||||
## 赋值运算符
|
||||
|
||||
赋值运算(`a = b`),表示用 `b` 的值来初始化或更新 `a` 的值:
|
||||
|
||||
```swift
|
||||
let b = 10
|
||||
var a = 5
|
||||
a = b
|
||||
// a 现在等于 10
|
||||
```
|
||||
|
||||
如果赋值的右边是一个多元组,它的元素可以马上被分解成多个常量或变量:
|
||||
|
||||
```swift
|
||||
let (x, y) = (1, 2)
|
||||
// 现在 x 等于 1,y 等于 2
|
||||
```
|
||||
|
||||
与 C 语言和 Objective-C 不同,Swift 的赋值操作并不返回任何值。所以以下代码是错误的:
|
||||
|
||||
```swift
|
||||
if x = y {
|
||||
// 此句错误, 因为 x = y 并不返回任何值
|
||||
}
|
||||
```
|
||||
|
||||
这个特性使你无法把(`==`)错写成(`=`),由于 `if x = y` 是错误代码,Swift 能帮你避免此类错误发生。
|
||||
|
||||
<a name="arithmetic_operators"></a>
|
||||
## 算术运算符
|
||||
|
||||
Swift 中所有数值类型都支持了基本的四则算术运算:
|
||||
|
||||
- 加法(`+`)
|
||||
- 减法(`-`)
|
||||
- 乘法(`*`)
|
||||
- 除法(`/`)
|
||||
|
||||
```swift
|
||||
1 + 2 // 等于 3
|
||||
5 - 3 // 等于 2
|
||||
2 * 3 // 等于 6
|
||||
10.0 / 2.5 // 等于 4.0
|
||||
```
|
||||
|
||||
与 C 语言和 Objective-C 不同的是,Swift 默认情况下不允许在数值运算中出现溢出情况。但是你可以使用 Swift 的溢出运算符来实现溢出运算(如 `a &+ b`)。详情参见[溢出运算符](../chapter2/25_Advanced_Operators.html#overflow_operators)。
|
||||
|
||||
加法运算符也可用于 `String` 的拼接:
|
||||
|
||||
```swift
|
||||
"hello, " + "world" // 等于 "hello, world"
|
||||
```
|
||||
|
||||
### 求余运算符
|
||||
|
||||
求余运算(`a % b`)是计算 `b` 的多少倍刚刚好可以容入`a`,返回多出来的那部分(余数)。
|
||||
|
||||
> 注意:
|
||||
求余运算(`%`)在其他语言也叫取模运算。然而严格说来,我们看该运算符对负数的操作结果,「求余」比「取模」更合适些。
|
||||
|
||||
我们来谈谈取余是怎么回事,计算 `9 % 4`,你先计算出 `4` 的多少倍会刚好可以容入 `9` 中:
|
||||
|
||||

|
||||
|
||||
你可以在 `9` 中放入两个 `4`,那余数是 1(用橙色标出)。
|
||||
|
||||
在 Swift 中可以表达为:
|
||||
|
||||
```swift
|
||||
9 % 4 // 等于 1
|
||||
```
|
||||
|
||||
为了得到 `a % b` 的结果,`%` 计算了以下等式,并输出`余数`作为结果:
|
||||
|
||||
a = (b × 倍数) + 余数
|
||||
|
||||
当`倍数`取最大值的时候,就会刚好可以容入 `a` 中。
|
||||
|
||||
把 `9` 和 `4` 代入等式中,我们得 `1`:
|
||||
|
||||
9 = (4 × 2) + 1
|
||||
|
||||
同样的方法,我们来计算 `-9 % 4`:
|
||||
|
||||
```swift
|
||||
-9 % 4 // 等于 -1
|
||||
```
|
||||
|
||||
把 `-9` 和 `4` 代入等式,`-2` 是取到的最大整数:
|
||||
|
||||
-9 = (4 × -2) + -1
|
||||
|
||||
余数是 `-1`。
|
||||
|
||||
在对负数 `b` 求余时,`b` 的符号会被忽略。这意味着 `a % b` 和 `a % -b` 的结果是相同的。
|
||||
|
||||
### 浮点数求余计算
|
||||
|
||||
不同于 C 语言和 Objective-C,Swift 中是可以对浮点数进行求余的。
|
||||
|
||||
```swift
|
||||
8 % 2.5 // 等于 0.5
|
||||
```
|
||||
|
||||
这个例子中,`8` 除以 `2.5` 等于 `3` 余 `0.5`,所以结果是一个 `Double` 型的值为 `0.5`。
|
||||
|
||||

|
||||
|
||||
### 自增和自减运算
|
||||
|
||||
和 C 语言一样,Swift 也提供了对变量本身加 1 或减 1 的自增(`++`)和自减(`--`)的缩略算符。其操作对象可以是整形和浮点型。
|
||||
|
||||
```swift
|
||||
var i = 0
|
||||
++i // 现在 i = 1
|
||||
```
|
||||
|
||||
每调用一次 `++i`,`i` 的值就会加 1。实际上,`++i` 是 `i = i + 1` 的简写,而 `--i` 是 `i = i - 1` 的简写。
|
||||
|
||||
`++` 和 `--` 既可以用作前置运算又可以用作后置运算。`++i`、`i++` 都是有效的自增的写法;相类似对应自减的写法则是 `--i` 和 `i--`。
|
||||
|
||||
我们需要注意的是这些运算符即可修改了 `i` 的值也可以返回 `i` 的值。如果你只想修改 `i` 的值,那你就可以忽略这个返回值。但如果你想使用返回值,你就需要留意前置和后置操作的返回值是不同的,它们遵循以下原则:
|
||||
|
||||
- 当 `++` 前置的时候,先自増再返回。
|
||||
- 当 `++` 后置的时候,先返回再自增。
|
||||
|
||||
例如:
|
||||
|
||||
```swift
|
||||
var a = 0
|
||||
let b = ++a // a 和 b 现在都是 1
|
||||
let c = a++ // a 现在 2, 但 c 是 a 自增前的值 1
|
||||
```
|
||||
|
||||
上述例子,`let b = ++a` 先把 `a` 加 1 了再返回 `a` 的值。所以 `a` 和 `b` 都是新值 `1`。
|
||||
|
||||
而 `let c = a++`,是先返回了 `a` 的值,然后 `a` 才加 1。所以 `c` 得到了 `a` 的旧值 1,而 `a` 加 1 后变成 2。
|
||||
|
||||
除非你需要使用 `i++` 的特性,不然推荐你使用 `++i` 和 `--i`,因为先修改后返回这样的行为更符合我们的逻辑。
|
||||
|
||||
|
||||
### 一元负号运算符
|
||||
|
||||
数值的正负号可以使用前缀 `-`(即一元负号)来切换:
|
||||
|
||||
```swift
|
||||
let three = 3
|
||||
let minusThree = -three // minusThree 等于 -3
|
||||
let plusThree = -minusThree // plusThree 等于 3, 或 "负负3"
|
||||
```
|
||||
|
||||
一元负号(`-`)写在操作数之前,中间没有空格。
|
||||
|
||||
### 一元正号运算符
|
||||
|
||||
一元正号(`+`)不做任何改变地返回操作数的值:
|
||||
|
||||
```swift
|
||||
let minusSix = -6
|
||||
let alsoMinusSix = +minusSix // alsoMinusSix 等于 -6
|
||||
```
|
||||
|
||||
虽然一元 `+` 什么都不会改变,但当你在使用一元负号来表达负数时,你可以使用一元正号来表达正数,如此你的代码会具有对称美。
|
||||
|
||||
|
||||
<a name="compound_assignment_operators"></a>
|
||||
## 组合赋值运算符
|
||||
|
||||
如同 C 语言,Swift 也提供把其他运算符和赋值运算(`=`)组合的组合赋值运算符,组合加运算(`+=`)是其中一个例子:
|
||||
|
||||
```swift
|
||||
var a = 1
|
||||
a += 2
|
||||
// a 现在是 3
|
||||
```
|
||||
|
||||
表达式 `a += 2` 是 `a = a + 2` 的简写,一个组合加运算就是把加法运算和赋值运算组合成进一个运算符里,同时完成两个运算任务。
|
||||
|
||||
> 注意:
|
||||
复合赋值运算没有返回值,`let b = a += 2`这类代码是错误。这不同于上面提到的自增和自减运算符。
|
||||
|
||||
在[表达式](../chapter3/04_Expressions.html)章节里有复合运算符的完整列表。
|
||||
|
||||
<a name="comparison_operators"></a>
|
||||
## 比较运算符(Comparison Operators)
|
||||
|
||||
所有标准 C 语言中的比较运算都可以在 Swift 中使用:
|
||||
|
||||
- 等于(`a == b`)
|
||||
- 不等于(`a != b`)
|
||||
- 大于(`a > b`)
|
||||
- 小于(`a < b`)
|
||||
- 大于等于(`a >= b`)
|
||||
- 小于等于(`a <= b`)
|
||||
|
||||
> 注意:
|
||||
Swift 也提供恒等(`===`)和不恒等(`!==`)这两个比较符来判断两个对象是否引用同一个对象实例。更多细节在[类与结构](../chapter2/09_Classes_and_Structures.html)。
|
||||
|
||||
每个比较运算都返回了一个标识表达式是否成立的布尔值:
|
||||
|
||||
```swift
|
||||
1 == 1 // true, 因为 1 等于 1
|
||||
2 != 1 // true, 因为 2 不等于 1
|
||||
2 > 1 // true, 因为 2 大于 1
|
||||
1 < 2 // true, 因为 1 小于2
|
||||
1 >= 1 // true, 因为 1 大于等于 1
|
||||
2 <= 1 // false, 因为 2 并不小于等于 1
|
||||
```
|
||||
|
||||
比较运算多用于条件语句,如`if`条件:
|
||||
|
||||
```swift
|
||||
let name = "world"
|
||||
if name == "world" {
|
||||
print("hello, world")
|
||||
} else {
|
||||
print("I'm sorry \(name), but I don't recognize you")
|
||||
}
|
||||
// 输出 "hello, world", 因为 `name` 就是等于 "world"
|
||||
```
|
||||
|
||||
关于 `if` 语句,请看[控制流](../chapter2/05_Control_Flow.html)。
|
||||
|
||||
当元组中的值可以比较时,你也可以使用这些运算符来比较它们的大小。例如,因为 `Int` 和 `String` 类型的值可以比较,所以类型为 `(Int, String)` 的元组也可以被比较。相反,`Bool` 不能被比较,也意味着存有布尔类型的元组不能被比较。
|
||||
|
||||
比较元组大小会按照从左到右、逐值比较的方式,直到发现有两个值不等时停止。如果所有的值都相等,那么这一对元组我们就称它们是相等的。例如:
|
||||
|
||||
```swift
|
||||
(1, "zebra") < (2, "apple") // true,因为 1 小于 2
|
||||
(3, "apple") < (3, "bird") // true,因为 3 等于 3,但是 apple 小于 bird
|
||||
(4, "dog") == (4, "dog") // true,因为 4 等于 4,dog 等于 dog
|
||||
```
|
||||
|
||||
> 注意:
|
||||
Swift 标准库只能比较七个以内元素的元组比较函数。如果你的元组元素超过七个时,你需要自己实现比较运算符。
|
||||
|
||||
<a name="ternary_conditional_operator"></a>
|
||||
## 三目运算符(Ternary Conditional Operator)
|
||||
|
||||
三目运算符的特殊在于它是有三个操作数的运算符,它的形式是 `问题 ? 答案 1 : 答案 2`。它简洁地表达根据 `问题`成立与否作出二选一的操作。如果 `问题` 成立,返回 `答案 1` 的结果;反之返回 `答案 2` 的结果。
|
||||
|
||||
三目运算符是以下代码的缩写形式:
|
||||
|
||||
```swift
|
||||
if question {
|
||||
answer1
|
||||
} else {
|
||||
answer2
|
||||
}
|
||||
```
|
||||
|
||||
这里有个计算表格行高的例子。如果有表头,那行高应比内容高度要高出 50 点;如果没有表头,只需高出 20 点:
|
||||
|
||||
```swift
|
||||
let contentHeight = 40
|
||||
let hasHeader = true
|
||||
let rowHeight = contentHeight + (hasHeader ? 50 : 20)
|
||||
// rowHeight 现在是 90
|
||||
```
|
||||
|
||||
上面的写法比下面的代码更简洁:
|
||||
|
||||
```swift
|
||||
let contentHeight = 40
|
||||
let hasHeader = true
|
||||
var rowHeight = contentHeight
|
||||
if hasHeader {
|
||||
rowHeight = rowHeight + 50
|
||||
} else {
|
||||
rowHeight = rowHeight + 20
|
||||
}
|
||||
// rowHeight 现在是 90
|
||||
```
|
||||
|
||||
第一段代码例子使用了三目运算,所以一行代码就能让我们得到正确答案。这比第二段代码简洁得多,无需将 `rowHeight` 定义成变量,因为它的值无需在 `if` 语句中改变。
|
||||
|
||||
三目运算提供有效率且便捷的方式来表达二选一的选择。需要注意的事,过度使用三目运算符会使简洁的代码变的难懂。我们应避免在一个组合语句中使用多个三目运算符。
|
||||
|
||||
<a name="nil_coalescing_operator"></a>
|
||||
## 空合运算符(Nil Coalescing Operator)
|
||||
|
||||
空合运算符(`a ?? b`)将对可选类型 `a` 进行空判断,如果 `a` 包含一个值就进行解封,否则就返回一个默认值 `b`。表达式 `a` 必须是 Optional 类型。默认值 `b` 的类型必须要和 `a` 存储值的类型保持一致。
|
||||
|
||||
空合运算符是对以下代码的简短表达方法:
|
||||
|
||||
```swift
|
||||
a != nil ? a! : b
|
||||
```
|
||||
|
||||
上述代码使用了三目运算符。当可选类型 `a` 的值不为空时,进行强制解封(`a!`),访问 `a` 中的值;反之返回默认值 `b`。无疑空合运算符(`??`)提供了一种更为优雅的方式去封装条件判断和解封两种行为,显得简洁以及更具可读性。
|
||||
|
||||
> 注意:
|
||||
如果 `a` 为非空值(`non-nil`),那么值 `b` 将不会被计算。这也就是所谓的短路求值。
|
||||
|
||||
下文例子采用空合运算符,实现了在默认颜色名和可选自定义颜色名之间抉择:
|
||||
|
||||
```swift
|
||||
let defaultColorName = "red"
|
||||
var userDefinedColorName: String? //默认值为 nil
|
||||
|
||||
var colorNameToUse = userDefinedColorName ?? defaultColorName
|
||||
// userDefinedColorName 的值为空,所以 colorNameToUse 的值为 "red"
|
||||
```
|
||||
|
||||
`userDefinedColorName` 变量被定义为一个可选的 `String` 类型,默认值为 `nil`。由于 `userDefinedColorName` 是一个可选类型,我们可以使用空合运算符去判断其值。在上一个例子中,通过空合运算符为一个名为 `colorNameToUse` 的变量赋予一个字符串类型初始值。
|
||||
由于 `userDefinedColorName` 值为空,因此表达式 `userDefinedColorName ?? defaultColorName` 返回 `defaultColorName` 的值,即 `red`。
|
||||
|
||||
另一种情况,分配一个非空值(`non-nil`)给 `userDefinedColorName`,再次执行空合运算,运算结果为封包在 `userDefaultColorName` 中的值,而非默认值。
|
||||
|
||||
```swift
|
||||
userDefinedColorName = "green"
|
||||
colorNameToUse = userDefinedColorName ?? defaultColorName
|
||||
// userDefinedColorName 非空,因此 colorNameToUse 的值为 "green"
|
||||
```
|
||||
|
||||
<a name="range_operators"></a>
|
||||
## 区间运算符(Range Operators)
|
||||
|
||||
Swift 提供了两个方便表达一个区间的值的运算符。
|
||||
|
||||
### 闭区间运算符
|
||||
闭区间运算符(`a...b`)定义一个包含从 `a` 到 `b`(包括 `a` 和 `b`)的所有值的区间。`a` 的值不能超过 `b`。
|
||||
|
||||
闭区间运算符在迭代一个区间的所有值时是非常有用的,如在 `for-in` 循环中:
|
||||
|
||||
```swift
|
||||
for index in 1...5 {
|
||||
print("\(index) * 5 = \(index * 5)")
|
||||
}
|
||||
// 1 * 5 = 5
|
||||
// 2 * 5 = 10
|
||||
// 3 * 5 = 15
|
||||
// 4 * 5 = 20
|
||||
// 5 * 5 = 25
|
||||
```
|
||||
|
||||
关于 `for-in`,请看[控制流](../chapter2/05_Control_Flow.html)。
|
||||
|
||||
### 半开区间运算符
|
||||
|
||||
半开区间(`a..<b`)定义一个从 `a` 到 `b` 但不包括 `b` 的区间。
|
||||
之所以称为半开区间,是因为该区间包含第一个值而不包括最后的值。
|
||||
|
||||
半开区间的实用性在于当你使用一个从 0 开始的列表(如数组)时,非常方便地从0数到列表的长度。
|
||||
|
||||
```swift
|
||||
let names = ["Anna", "Alex", "Brian", "Jack"]
|
||||
let count = names.count
|
||||
for i in 0..<count {
|
||||
print("第 \(i + 1) 个人叫 \(names[i])")
|
||||
}
|
||||
// 第 1 个人叫 Anna
|
||||
// 第 2 个人叫 Alex
|
||||
// 第 3 个人叫 Brian
|
||||
// 第 4 个人叫 Jack
|
||||
```
|
||||
|
||||
数组有 4 个元素,但 `0..<count` 只数到3(最后一个元素的下标),因为它是半开区间。关于数组,请查阅[数组](../chapter2/04_Collection_Types.html#arrays)。
|
||||
|
||||
<a name="logical_operators"></a>
|
||||
## 逻辑运算(Logical Operators)
|
||||
|
||||
逻辑运算的操作对象是逻辑布尔值。Swift 支持基于 C 语言的三个标准逻辑运算。
|
||||
|
||||
- 逻辑非(`!a`)
|
||||
- 逻辑与(`a && b`)
|
||||
- 逻辑或(`a || b`)
|
||||
|
||||
### 逻辑非
|
||||
|
||||
逻辑非运算(`!a`)对一个布尔值取反,使得 `true` 变 `false`,`false` 变 `true`。
|
||||
|
||||
它是一个前置运算符,需紧跟在操作数之前,且不加空格。读作 `非 a` ,例子如下:
|
||||
|
||||
```swift
|
||||
let allowedEntry = false
|
||||
if !allowedEntry {
|
||||
print("ACCESS DENIED")
|
||||
}
|
||||
// 输出 "ACCESS DENIED"
|
||||
```
|
||||
|
||||
`if !allowedEntry` 语句可以读作「如果非 allowedEntry」,接下一行代码只有在「非 allowedEntry」为 `true`,即 `allowEntry` 为 `false` 时被执行。
|
||||
|
||||
在示例代码中,小心地选择布尔常量或变量有助于代码的可读性,并且避免使用双重逻辑非运算,或混乱的逻辑语句。
|
||||
|
||||
### 逻辑与
|
||||
|
||||
逻辑与(`a && b`)表达了只有 `a` 和 `b` 的值都为 `true` 时,整个表达式的值才会是 `true`。
|
||||
|
||||
只要任意一个值为 `false`,整个表达式的值就为 `false`。事实上,如果第一个值为 `false`,那么是不去计算第二个值的,因为它已经不可能影响整个表达式的结果了。这被称做「短路计算(short-circuit evaluation)」。
|
||||
|
||||
以下例子,只有两个 `Bool` 值都为 `true` 的时候才允许进入 if:
|
||||
|
||||
```swift
|
||||
let enteredDoorCode = true
|
||||
let passedRetinaScan = false
|
||||
if enteredDoorCode && passedRetinaScan {
|
||||
print("Welcome!")
|
||||
} else {
|
||||
print("ACCESS DENIED")
|
||||
}
|
||||
// 输出 "ACCESS DENIED"
|
||||
```
|
||||
|
||||
### 逻辑或
|
||||
|
||||
逻辑或(`a || b`)是一个由两个连续的 `|` 组成的中置运算符。它表示了两个逻辑表达式的其中一个为 `true`,整个表达式就为 `true`。
|
||||
|
||||
同逻辑与运算类似,逻辑或也是「短路计算」的,当左端的表达式为 `true` 时,将不计算右边的表达式了,因为它不可能改变整个表达式的值了。
|
||||
|
||||
以下示例代码中,第一个布尔值(`hasDoorKey`)为 `false`,但第二个值(`knowsOverridePassword`)为 `true`,所以整个表达是 `true`,于是允许进入:
|
||||
|
||||
```swift
|
||||
let hasDoorKey = false
|
||||
let knowsOverridePassword = true
|
||||
if hasDoorKey || knowsOverridePassword {
|
||||
print("Welcome!")
|
||||
} else {
|
||||
print("ACCESS DENIED")
|
||||
}
|
||||
// 输出 "Welcome!"
|
||||
```
|
||||
|
||||
### 逻辑运算符组合计算
|
||||
|
||||
我们可以组合多个逻辑运算来表达一个复合逻辑:
|
||||
|
||||
```swift
|
||||
if enteredDoorCode && passedRetinaScan || hasDoorKey || knowsOverridePassword {
|
||||
print("Welcome!")
|
||||
} else {
|
||||
print("ACCESS DENIED")
|
||||
}
|
||||
// 输出 "Welcome!"
|
||||
```
|
||||
|
||||
这个例子使用了含多个 `&&` 和 `||` 的复合逻辑。但无论怎样,`&&` 和 `||` 始终只能操作两个值。所以这实际是三个简单逻辑连续操作的结果。我们来解读一下:
|
||||
|
||||
如果我们输入了正确的密码并通过了视网膜扫描,或者我们有一把有效的钥匙,又或者我们知道紧急情况下重置的密码,我们就能把门打开进入。
|
||||
|
||||
前两种情况,我们都不满足,所以前两个简单逻辑的结果是 `false`,但是我们是知道紧急情况下重置的密码的,所以整个复杂表达式的值还是 `true`。
|
||||
|
||||
> 注意:
|
||||
Swift 逻辑操作符 `&&` 和 `||` 是左结合的,这意味着拥有多元逻辑操作符的复合表达式优先计算最左边的子表达式。
|
||||
|
||||
### 使用括号来明确优先级
|
||||
|
||||
为了一个复杂表达式更容易读懂,在合适的地方使用括号来明确优先级是很有效的,虽然它并非必要的。在上个关于门的权限的例子中,我们给第一个部分加个括号,使它看起来逻辑更明确:
|
||||
|
||||
```swift
|
||||
if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword {
|
||||
print("Welcome!")
|
||||
} else {
|
||||
print("ACCESS DENIED")
|
||||
}
|
||||
// 输出 "Welcome!"
|
||||
```
|
||||
|
||||
这括号使得前两个值被看成整个逻辑表达中独立的一个部分。虽然有括号和没括号的输出结果是一样的,但对于读代码的人来说有括号的代码更清晰。可读性比简洁性更重要,请在可以让你代码变清晰的地方加个括号吧!
|
||||
@ -1,694 +0,0 @@
|
||||
# 字符串和字符(Strings and Characters)
|
||||
---
|
||||
|
||||
> 1.0
|
||||
> 翻译:[wh1100717](https://github.com/wh1100717)
|
||||
> 校对:[Hawstein](https://github.com/Hawstein)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[DianQK](https://github.com/DianQK)
|
||||
|
||||
> 2.1
|
||||
> 翻译:[DianQK](https://github.com/DianQK)
|
||||
> 校对:[shanks](http://codebuild.me), [Realank](https://github.com/Realank)
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [字符串字面量](#string_literals)
|
||||
- [初始化空字符串](#initializing_an_empty_string)
|
||||
- [字符串可变性](#string_mutability)
|
||||
- [字符串是值类型](#strings_are_value_types)
|
||||
- [使用字符](#working_with_characters)
|
||||
- [连接字符串和字符](#concatenating_strings_and_characters)
|
||||
- [字符串插值](#string_interpolation)
|
||||
- [Unicode](#unicode)
|
||||
- [计算字符数量](#counting_characters)
|
||||
- [访问和修改字符串](#accessing_and_modifying_a_string)
|
||||
- [比较字符串](#comparing_strings)
|
||||
- [字符串的 Unicode 表示形式](#unicode_representations_of_strings)
|
||||
|
||||
|
||||
`String`是例如"hello, world","albatross"这样的有序的`Character`(字符)类型的值的集合。通过`String`类型来表示。
|
||||
一个`String`的内容可以用许多方式读取,它包括一个`Character`值的集合。
|
||||
创建和操作字符串的语法与 C 语言中字符串操作相似,轻量并且易读。
|
||||
字符串连接操作只需要简单地通过`+`符号将两个字符串相连即可。与 Swift 中其他值一样,能否更改字符串的值,取决于其被定义为常量还是变量。你也可以在字符串内插过程中使用字符串插入常量、变量、字面量表达成更长的字符串,这样可以很容易的创建自定义的字符串值,进行展示、存储以及打印。
|
||||
尽管语法简易,但`String`类型是一种快速、现代化的字符串实现。
|
||||
每一个字符串都是由编码无关的 Unicode 字符组成,并支持访问字符的多种 Unicode 表示形式(representations)。
|
||||
|
||||
> 注意:
|
||||
> Swift 的`String`类型与 Foundation `NSString`类进行了无缝桥接。就像 [`AnyObject`类型](./19_Type_Casting.html#anyobject) 中提到的一样,在使用 Cocoa 中的 Foundation 框架时,您可以将创建的任何字符串的值转换成`NSString`,并调用任意的`NSString` API。您也可以在任意要求传入`NSString`实例作为参数的 API 中用`String`类型的值代替。
|
||||
> 更多关于在 Foundation 和 Cocoa 中使用`String`的信息请查看 *[Using Swift with Cocoa and Objective-C (Swift 2.1)](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216)*。
|
||||
|
||||
|
||||
|
||||
<a name="string_literals"></a>
|
||||
## 字符串字面量(String Literals)
|
||||
|
||||
您可以在您的代码中包含一段预定义的字符串值作为字符串字面量。字符串字面量是由双引号 (`""`) 包裹着的具有固定顺序的文本字符集。
|
||||
字符串字面量可以用于为常量和变量提供初始值:
|
||||
|
||||
```swift
|
||||
let someString = "Some string literal value"
|
||||
```
|
||||
|
||||
注意`someString`常量通过字符串字面量进行初始化,Swift 会推断该常量为`String`类型。
|
||||
|
||||
> 注意:
|
||||
更多关于在字符串字面量中使用特殊字符的信息,请查看 [字符串字面量的特殊字符](#special_characters_in_string_literals) 。
|
||||
|
||||
|
||||
<a name="initializing_an_empty_string"></a>
|
||||
## 初始化空字符串 (Initializing an Empty String)
|
||||
|
||||
要创建一个空字符串作为初始值,可以将空的字符串字面量赋值给变量,也可以初始化一个新的`String`实例:
|
||||
|
||||
```swift
|
||||
var emptyString = "" // 空字符串字面量
|
||||
var anotherEmptyString = String() // 初始化方法
|
||||
// 两个字符串均为空并等价。
|
||||
```
|
||||
|
||||
您可以通过检查其`Bool`类型的`isEmpty`属性来判断该字符串是否为空:
|
||||
|
||||
```swift
|
||||
if emptyString.isEmpty {
|
||||
print("Nothing to see here")
|
||||
}
|
||||
// 打印输出:"Nothing to see here"
|
||||
```
|
||||
|
||||
<a name="string_mutability"></a>
|
||||
## 字符串可变性 (String Mutability)
|
||||
|
||||
您可以通过将一个特定字符串分配给一个变量来对其进行修改,或者分配给一个常量来保证其不会被修改:
|
||||
|
||||
```swift
|
||||
var variableString = "Horse"
|
||||
variableString += " and carriage"
|
||||
// variableString 现在为 "Horse and carriage"
|
||||
|
||||
let constantString = "Highlander"
|
||||
constantString += " and another Highlander"
|
||||
// 这会报告一个编译错误 (compile-time error) - 常量字符串不可以被修改。
|
||||
```
|
||||
|
||||
> 注意:
|
||||
在 Objective-C 和 Cocoa 中,您需要通过选择两个不同的类(`NSString`和`NSMutableString`)来指定字符串是否可以被修改。
|
||||
|
||||
<a name="strings_are_value_types"></a>
|
||||
## 字符串是值类型(Strings Are Value Types)
|
||||
|
||||
Swift 的`String`类型是值类型。
|
||||
如果您创建了一个新的字符串,那么当其进行常量、变量赋值操作,或在函数/方法中传递时,会进行值拷贝。
|
||||
任何情况下,都会对已有字符串值创建新副本,并对该新副本进行传递或赋值操作。
|
||||
值类型在 [结构体和枚举是值类型](./09_Classes_and_Structures.html#structures_and_enumerations_are_value_types) 中进行了详细描述。
|
||||
|
||||
Swift 默认字符串拷贝的方式保证了在函数/方法中传递的是字符串的值。
|
||||
很明显无论该值来自于哪里,都是您独自拥有的。
|
||||
您可以确信传递的字符串不会被修改,除非你自己去修改它。
|
||||
|
||||
在实际编译时,Swift 编译器会优化字符串的使用,使实际的复制只发生在绝对必要的情况下,这意味着您将字符串作为值类型的同时可以获得极高的性能。
|
||||
|
||||
<a name="working_with_characters"></a>
|
||||
## 使用字符(Working with Characters)
|
||||
|
||||
您可通过`for-in`循环来遍历字符串中的`characters`属性来获取每一个字符的值:
|
||||
|
||||
```swift
|
||||
for character in "Dog!🐶".characters {
|
||||
print(character)
|
||||
}
|
||||
// D
|
||||
// o
|
||||
// g
|
||||
// !
|
||||
// 🐶
|
||||
```
|
||||
|
||||
`for-in`循环在 [For 循环](./05_Control_Flow.html#for_loops) 中进行了详细描述。
|
||||
|
||||
另外,通过标明一个`Character`类型并用字符字面量进行赋值,可以建立一个独立的字符常量或变量:
|
||||
|
||||
```swift
|
||||
let exclamationMark: Character = "!"
|
||||
```
|
||||
字符串可以通过传递一个值类型为`Character`的数组作为自变量来初始化:
|
||||
|
||||
```swift
|
||||
let catCharacters: [Character] = ["C", "a", "t", "!", "🐱"]
|
||||
let catString = String(catCharacters)
|
||||
print(catString)
|
||||
// 打印输出:"Cat!🐱"
|
||||
```
|
||||
|
||||
<a name="concatenating_strings_and_characters"></a>
|
||||
## 连接字符串和字符 (Concatenating Strings and Characters)
|
||||
|
||||
字符串可以通过加法运算符(`+`)相加在一起(或称“连接”)创建一个新的字符串:
|
||||
|
||||
```swift
|
||||
let string1 = "hello"
|
||||
let string2 = " there"
|
||||
var welcome = string1 + string2
|
||||
// welcome 现在等于 "hello there"
|
||||
```
|
||||
|
||||
您也可以通过加法赋值运算符 (`+=`) 将一个字符串添加到一个已经存在字符串变量上:
|
||||
|
||||
```swift
|
||||
var instruction = "look over"
|
||||
instruction += string2
|
||||
// instruction 现在等于 "look over there"
|
||||
```
|
||||
|
||||
您可以用`append()`方法将一个字符附加到一个字符串变量的尾部:
|
||||
|
||||
```swift
|
||||
let exclamationMark: Character = "!"
|
||||
welcome.append(exclamationMark)
|
||||
// welcome 现在等于 "hello there!"
|
||||
```
|
||||
|
||||
> 注意:
|
||||
您不能将一个字符串或者字符添加到一个已经存在的字符变量上,因为字符变量只能包含一个字符。
|
||||
|
||||
|
||||
<a name="string_interpolation"></a>
|
||||
## 字符串插值 (String Interpolation)
|
||||
|
||||
字符串插值是一种构建新字符串的方式,可以在其中包含常量、变量、字面量和表达式。
|
||||
您插入的字符串字面量的每一项都在以反斜线为前缀的圆括号中:
|
||||
|
||||
```swift
|
||||
let multiplier = 3
|
||||
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
|
||||
// message is "3 times 2.5 is 7.5"
|
||||
```
|
||||
|
||||
在上面的例子中,`multiplier`作为`\(multiplier)`被插入到一个字符串常量量中。
|
||||
当创建字符串执行插值计算时此占位符会被替换为`multiplier`实际的值。
|
||||
|
||||
`multiplier`的值也作为字符串中后面表达式的一部分。
|
||||
该表达式计算`Double(multiplier) * 2.5`的值并将结果 (`7.5`) 插入到字符串中。
|
||||
在这个例子中,表达式写为`\(Double(multiplier) * 2.5)`并包含在字符串字面量中。
|
||||
|
||||
> 注意:
|
||||
> 插值字符串中写在括号中的表达式不能包含非转义反斜杠 (`\`),并且不能包含回车或换行符。不过,插值字符串可以包含其他字面量。
|
||||
|
||||
|
||||
<a name="unicode"></a>
|
||||
## Unicode
|
||||
|
||||
Unicode 是一个国际标准,用于文本的编码和表示。
|
||||
它使您可以用标准格式表示来自任意语言几乎所有的字符,并能够对文本文件或网页这样的外部资源中的字符进行读写操作。
|
||||
Swift 的`String`和`Character`类型是完全兼容 Unicode 标准的。
|
||||
|
||||
<a name="unicode_scalars"></a>
|
||||
### Unicode 标量(Unicode Scalars)
|
||||
|
||||
Swift 的`String`类型是基于 *Unicode 标量* 建立的。
|
||||
Unicode 标量是对应字符或者修饰符的唯一的21位数字,例如`U+0061`表示小写的拉丁字母(`LATIN SMALL LETTER A`)("`a`"),`U+1F425`表示小鸡表情(`FRONT-FACING BABY CHICK`) ("`🐥`")。
|
||||
|
||||
> 注意:
|
||||
> Unicode *码位(code poing)* 的范围是`U+0000`到`U+D7FF`或者`U+E000`到`U+10FFFF`。Unicode 标量不包括 Unicode *代理项(surrogate pair)* 码位,其码位范围是`U+D800`到`U+DFFF`。
|
||||
|
||||
注意不是所有的21位 Unicode 标量都代表一个字符,因为有一些标量是留作未来分配的。已经代表一个典型字符的标量都有自己的名字,例如上面例子中的`LATIN SMALL LETTER A`和`FRONT-FACING BABY CHICK`。
|
||||
|
||||
<a name="special_characters_in_string_literals"></a>
|
||||
### 字符串字面量的特殊字符 (Special Characters in String Literals)
|
||||
|
||||
字符串字面量可以包含以下特殊字符:
|
||||
|
||||
* 转义字符`\0`(空字符)、`\\`(反斜线)、`\t`(水平制表符)、`\n`(换行符)、`\r`(回车符)、`\"`(双引号)、`\'`(单引号)。
|
||||
* Unicode 标量,写成`\u{n}`(u为小写),其中`n`为任意一到八位十六进制数且可用的 Unicode 位码。
|
||||
|
||||
下面的代码为各种特殊字符的使用示例。
|
||||
`wiseWords`常量包含了两个双引号。
|
||||
`dollarSign`、`blackHeart`和`sparklingHeart`常量演示了三种不同格式的 Unicode 标量:
|
||||
|
||||
```swift
|
||||
let wiseWords = "\"Imagination is more important than knowledge\" - Einstein"
|
||||
// "Imageination is more important than knowledge" - Enistein
|
||||
let dollarSign = "\u{24}" // $, Unicode 标量 U+0024
|
||||
let blackHeart = "\u{2665}" // ♥, Unicode 标量 U+2665
|
||||
let sparklingHeart = "\u{1F496}" // 💖, Unicode 标量 U+1F496
|
||||
```
|
||||
|
||||
<a name="extended_grapheme_clusters"></a>
|
||||
### 可扩展的字形群集(Extended Grapheme Clusters)
|
||||
|
||||
每一个 Swift 的`Character`类型代表一个可扩展的字形群。
|
||||
一个可扩展的字形群是一个或多个可生成人类可读的字符 Unicode 标量的有序排列。
|
||||
举个例子,字母`é`可以用单一的 Unicode 标量`é`(`LATIN SMALL LETTER E WITH ACUTE`, 或者`U+00E9`)来表示。然而一个标准的字母`e`(`LATIN SMALL LETTER E`或者`U+0065`) 加上一个急促重音(`COMBINING ACTUE ACCENT`)的标量(`U+0301`),这样一对标量就表示了同样的字母`é`。
|
||||
这个急促重音的标量形象的将`e`转换成了`é`。
|
||||
|
||||
在这两种情况中,字母`é`代表了一个单一的 Swift 的`Character`值,同时代表了一个可扩展的字形群。
|
||||
在第一种情况,这个字形群包含一个单一标量;而在第二种情况,它是包含两个标量的字形群:
|
||||
|
||||
```swift
|
||||
let eAcute: Character = "\u{E9}" // é
|
||||
let combinedEAcute: Character = "\u{65}\u{301}" // e 后面加上 ́
|
||||
// eAcute 是 é, combinedEAcute 是 é
|
||||
```
|
||||
|
||||
可扩展的字符群集是一个灵活的方法,用许多复杂的脚本字符表示单一的`Character`值。
|
||||
例如,来自朝鲜语字母表的韩语音节能表示为组合或分解的有序排列。
|
||||
在 Swift 都会表示为同一个单一的`Character`值:
|
||||
|
||||
|
||||
```swift
|
||||
let precomposed: Character = "\u{D55C}" // 한
|
||||
let decomposed: Character = "\u{1112}\u{1161}\u{11AB}" // ᄒ, ᅡ, ᆫ
|
||||
// precomposed 是 한, decomposed 是 한
|
||||
```
|
||||
|
||||
可拓展的字符群集可以使包围记号(例如`COMBINING ENCLOSING CIRCLE`或者`U+20DD`)的标量包围其他 Unicode 标量,作为一个单一的`Character`值:
|
||||
|
||||
```swift
|
||||
let enclosedEAcute: Character = "\u{E9}\u{20DD}"
|
||||
// enclosedEAcute 是 é⃝
|
||||
```
|
||||
|
||||
地域性指示符号的 Unicode 标量可以组合成一个单一的`Character`值,例如`REGIONAL INDICATOR SYMBOL LETTER U`(`U+1F1FA`)和`REGIONAL INDICATOR SYMBOL LETTER S`(`U+1F1F8`):
|
||||
|
||||
|
||||
```swift
|
||||
let regionalIndicatorForUS: Character = "\u{1F1FA}\u{1F1F8}"
|
||||
// regionalIndicatorForUS 是 🇺🇸
|
||||
```
|
||||
|
||||
<a name="counting_characters"></a>
|
||||
## 计算字符数量 (Counting Characters)
|
||||
|
||||
如果想要获得一个字符串中`Character`值的数量,可以使用字符串的`characters`属性的`count`属性:
|
||||
|
||||
```swift
|
||||
let unusualMenagerie = "Koala 🐨, Snail 🐌, Penguin 🐧, Dromedary 🐪"
|
||||
print("unusualMenagerie has \(unusualMenagerie.characters.count) characters")
|
||||
// 打印输出 "unusualMenagerie has 40 characters"
|
||||
```
|
||||
|
||||
注意在 Swift 中,使用可拓展的字符群集作为`Character`值来连接或改变字符串时,并不一定会更改字符串的字符数量。
|
||||
|
||||
例如,如果你用四个字符的单词`cafe`初始化一个新的字符串,然后添加一个`COMBINING ACTUE ACCENT`(`U+0301`)作为字符串的结尾。最终这个字符串的字符数量仍然是`4`,因为第四个字符是`é`,而不是`e`:
|
||||
|
||||
```swift
|
||||
var word = "cafe"
|
||||
print("the number of characters in \(word) is \(word.characters.count)")
|
||||
// 打印输出 "the number of characters in cafe is 4"
|
||||
|
||||
word += "\u{301}" // COMBINING ACUTE ACCENT, U+0301
|
||||
|
||||
print("the number of characters in \(word) is \(word.characters.count)")
|
||||
// 打印输出 "the number of characters in café is 4"
|
||||
```
|
||||
|
||||
> 注意:
|
||||
> 可扩展的字符群集可以组成一个或者多个 Unicode 标量。这意味着不同的字符以及相同字符的不同表示方式可能需要不同数量的内存空间来存储。所以 Swift 中的字符在一个字符串中并不一定占用相同的内存空间数量。因此在没有获取字符串的可扩展的字符群的范围时候,就不能计算出字符串的字符数量。如果您正在处理一个长字符串,需要注意`characters`属性必须遍历全部的 Unicode 标量,来确定字符串的字符数量。
|
||||
>
|
||||
> 另外需要注意的是通过`characters`属性返回的字符数量并不总是与包含相同字符的`NSString`的`length`属性相同。`NSString`的`length`属性是利用 UTF-16 表示的十六位代码单元数字,而不是 Unicode 可扩展的字符群集。作为佐证,当一个`NSString`的`length`属性被一个Swift的`String`值访问时,实际上是调用了`utf16Count`。
|
||||
|
||||
|
||||
<a name="accessing_and_modifying_a_string"></a>
|
||||
## 访问和修改字符串 (Accessing and Modifying a String)
|
||||
|
||||
你可以通过字符串的属性和方法来访问和修改它,当然也可以用下标语法完成。
|
||||
|
||||
<a name="string_indices"></a>
|
||||
### 字符串索引 (String Indices)
|
||||
|
||||
每一个`String`值都有一个关联的索引(*index*)类型,`String.Index`,它对应着字符串中的每一个`Character`的位置。
|
||||
|
||||
前面提到,不同的字符可能会占用不同数量的内存空间,所以要知道`Character`的确定位置,就必须从`String`开头遍历每一个 Unicode 标量直到结尾。因此,Swift 的字符串不能用整数(integer)做索引。
|
||||
|
||||
使用`startIndex`属性可以获取一个`String`的第一个`Character`的索引。使用`endIndex`属性可以获取最后一个`Character`的后一个位置的索引。因此,`endIndex`属性不能作为一个字符串的有效下标。如果`String`是空串,`startIndex`和`endIndex`是相等的。
|
||||
|
||||
通过调用`String.Index`的`predecessor()`方法,可以立即得到前面一个索引,调用`successor()`方法可以立即得到后面一个索引。任何一个`String`的索引都可以通过锁链作用的这些方法来获取另一个索引,也可以调用`advancedBy(_:)`方法来获取。但如果尝试获取出界的字符串索引,就会抛出一个运行时错误。
|
||||
|
||||
你可以使用下标语法来访问`String`特定索引的`Character`。
|
||||
|
||||
```swift
|
||||
let greeting = "Guten Tag!"
|
||||
greeting[greeting.startIndex]
|
||||
// G
|
||||
greeting[greeting.endIndex.predecessor()]
|
||||
// !
|
||||
greeting[greeting.startIndex.successor()]
|
||||
// u
|
||||
let index = greeting.startIndex.advancedBy(7)
|
||||
greeting[index]
|
||||
// a
|
||||
```
|
||||
|
||||
试图获取越界索引对应的`Character`,将引发一个运行时错误。
|
||||
|
||||
```swift
|
||||
greeting[greeting.endIndex] // error
|
||||
greeting.endIndex.successor() // error
|
||||
```
|
||||
|
||||
使用`characters`属性的`indices`属性会创建一个包含全部索引的范围(`Range`),用来在一个字符串中访问单个字符。
|
||||
|
||||
```swift
|
||||
for index in greeting.characters.indices {
|
||||
print("\(greeting[index]) ", terminator: "")
|
||||
}
|
||||
// 打印输出 "G u t e n T a g ! "
|
||||
```
|
||||
|
||||
<a name="inserting_and_removing"></a>
|
||||
### 插入和删除 (Inserting and Removing)
|
||||
|
||||
调用`insert(_:atIndex:)`方法可以在一个字符串的指定索引插入一个字符。
|
||||
|
||||
```swift
|
||||
var welcome = "hello"
|
||||
welcome.insert("!", atIndex: welcome.endIndex)
|
||||
// welcome now 现在等于 "hello!"
|
||||
```
|
||||
|
||||
调用`insertContentsOf(_:at:)`方法可以在一个字符串的指定索引插入一个字符串。
|
||||
|
||||
```swift
|
||||
welcome.insertContentsOf(" there".characters, at: welcome.endIndex.predecessor())
|
||||
// welcome 现在等于 "hello there!"
|
||||
```
|
||||
|
||||
调用`removeAtIndex(_:)`方法可以在一个字符串的指定索引删除一个字符。
|
||||
|
||||
```swift
|
||||
welcome.removeAtIndex(welcome.endIndex.predecessor())
|
||||
// welcome 现在等于 "hello there"
|
||||
```
|
||||
|
||||
调用`removeRange(_:)`方法可以在一个字符串的指定索引删除一个子字符串。
|
||||
|
||||
```swift
|
||||
let range = welcome.endIndex.advancedBy(-6)..<welcome.endIndex
|
||||
welcome.removeRange(range)
|
||||
// welcome 现在等于 "hello"
|
||||
```
|
||||
|
||||
|
||||
<a name="comparing_strings"></a>
|
||||
## 比较字符串 (Comparing Strings)
|
||||
|
||||
Swift 提供了三种方式来比较文本值:字符串字符相等、前缀相等和后缀相等。
|
||||
|
||||
<a name="string_and_character_equality"></a>
|
||||
### 字符串/字符相等 (String and Character Equality)
|
||||
|
||||
字符串/字符可以用等于操作符(`==`)和不等于操作符(`!=`),详细描述在[比较运算符](./02_Basic_Operators.html#comparison_operators):
|
||||
|
||||
```swift
|
||||
let quotation = "We're a lot alike, you and I."
|
||||
let sameQuotation = "We're a lot alike, you and I."
|
||||
if quotation == sameQuotation {
|
||||
print("These two strings are considered equal")
|
||||
}
|
||||
// 打印输出 "These two strings are considered equal"
|
||||
```
|
||||
|
||||
如果两个字符串(或者两个字符)的可扩展的字形群集是标准相等的,那就认为它们是相等的。在这个情况下,即使可扩展的字形群集是有不同的 Unicode 标量构成的,只要它们有同样的语言意义和外观,就认为它们标准相等。
|
||||
|
||||
例如,`LATIN SMALL LETTER E WITH ACUTE`(`U+00E9`)就是标准相等于`LATIN SMALL LETTER E`(`U+0065`)后面加上`COMBINING ACUTE ACCENT`(`U+0301`)。这两个字符群集都是表示字符`é`的有效方式,所以它们被认为是标准相等的:
|
||||
|
||||
```swift
|
||||
// "Voulez-vous un café?" 使用 LATIN SMALL LETTER E WITH ACUTE
|
||||
let eAcuteQuestion = "Voulez-vous un caf\u{E9}?"
|
||||
|
||||
// "Voulez-vous un café?" 使用 LATIN SMALL LETTER E and COMBINING ACUTE ACCENT
|
||||
let combinedEAcuteQuestion = "Voulez-vous un caf\u{65}\u{301}?"
|
||||
|
||||
if eAcuteQuestion == combinedEAcuteQuestion {
|
||||
print("These two strings are considered equal")
|
||||
}
|
||||
// 打印输出 "These two strings are considered equal"
|
||||
```
|
||||
|
||||
相反,英语中的`LATIN CAPITAL LETTER A`(`U+0041`,或者`A`)不等于俄语中的`CYRILLIC CAPITAL LETTER A`(`U+0410`,或者`A`)。两个字符看着是一样的,但却有不同的语言意义:
|
||||
|
||||
```swift
|
||||
let latinCapitalLetterA: Character = "\u{41}"
|
||||
|
||||
let cyrillicCapitalLetterA: Character = "\u{0410}"
|
||||
|
||||
if latinCapitalLetterA != cyrillicCapitalLetterA {
|
||||
print("These two characters are not equivalent")
|
||||
}
|
||||
// 打印 "These two characters are not equivalent"
|
||||
```
|
||||
|
||||
> 注意:
|
||||
> 在 Swift 中,字符串和字符并不区分地域(not locale-sensitive)。
|
||||
|
||||
|
||||
<a name="prefix_and_suffix_equality"></a>
|
||||
### 前缀/后缀相等 (Prefix and Suffix Equality)
|
||||
|
||||
通过调用字符串的`hasPrefix(_:)`/`hasSuffix(_:)`方法来检查字符串是否拥有特定前缀/后缀,两个方法均接收一个`String`类型的参数,并返回一个布尔值。
|
||||
|
||||
下面的例子以一个字符串数组表示莎士比亚话剧《罗密欧与朱丽叶》中前两场的场景位置:
|
||||
|
||||
```swift
|
||||
let romeoAndJuliet = [
|
||||
"Act 1 Scene 1: Verona, A public place",
|
||||
"Act 1 Scene 2: Capulet's mansion",
|
||||
"Act 1 Scene 3: A room in Capulet's mansion",
|
||||
"Act 1 Scene 4: A street outside Capulet's mansion",
|
||||
"Act 1 Scene 5: The Great Hall in Capulet's mansion",
|
||||
"Act 2 Scene 1: Outside Capulet's mansion",
|
||||
"Act 2 Scene 2: Capulet's orchard",
|
||||
"Act 2 Scene 3: Outside Friar Lawrence's cell",
|
||||
"Act 2 Scene 4: A street in Verona",
|
||||
"Act 2 Scene 5: Capulet's mansion",
|
||||
"Act 2 Scene 6: Friar Lawrence's cell"
|
||||
]
|
||||
```
|
||||
|
||||
您可以调用`hasPrefix(_:)`方法来计算话剧中第一幕的场景数:
|
||||
|
||||
```swift
|
||||
var act1SceneCount = 0
|
||||
for scene in romeoAndJuliet {
|
||||
if scene.hasPrefix("Act 1 ") {
|
||||
++act1SceneCount
|
||||
}
|
||||
}
|
||||
print("There are \(act1SceneCount) scenes in Act 1")
|
||||
// 打印输出 "There are 5 scenes in Act 1"
|
||||
```
|
||||
|
||||
相似地,您可以用`hasSuffix(_:)`方法来计算发生在不同地方的场景数:
|
||||
|
||||
```swift
|
||||
var mansionCount = 0
|
||||
var cellCount = 0
|
||||
for scene in romeoAndJuliet {
|
||||
if scene.hasSuffix("Capulet's mansion") {
|
||||
++mansionCount
|
||||
} else if scene.hasSuffix("Friar Lawrence's cell") {
|
||||
++cellCount
|
||||
}
|
||||
}
|
||||
print("\(mansionCount) mansion scenes; \(cellCount) cell scenes")
|
||||
// 打印输出 "6 mansion scenes; 2 cell scenes"
|
||||
```
|
||||
|
||||
> 注意:
|
||||
> `hasPrefix(_:)`和`hasSuffix(_:)`方法都是在每个字符串中逐字符比较其可扩展的字符群集是否标准相等,详细描述在[字符串/字符相等](#string_and_character_equality)。
|
||||
|
||||
|
||||
<a name="unicode_representations_of_strings"></a>
|
||||
## 字符串的 Unicode 表示形式(Unicode Representations of Strings)
|
||||
|
||||
当一个 Unicode 字符串被写进文本文件或者其他储存时,字符串中的 Unicode 标量会用 Unicode 定义的几种`编码格式`(encoding forms)编码。每一个字符串中的小块编码都被称`代码单元`(code units)。这些包括 UTF-8 编码格式(编码字符串为8位的代码单元), UTF-16 编码格式(编码字符串位16位的代码单元),以及 UTF-32 编码格式(编码字符串32位的代码单元)。
|
||||
|
||||
Swift 提供了几种不同的方式来访问字符串的 Unicode 表示形式。
|
||||
您可以利用`for-in`来对字符串进行遍历,从而以 Unicode 可扩展的字符群集的方式访问每一个`Character`值。
|
||||
该过程在 [使用字符](#working_with_characters) 中进行了描述。
|
||||
|
||||
另外,能够以其他三种 Unicode 兼容的方式访问字符串的值:
|
||||
|
||||
* UTF-8 代码单元集合 (利用字符串的`utf8`属性进行访问)
|
||||
* UTF-16 代码单元集合 (利用字符串的`utf16`属性进行访问)
|
||||
* 21位的 Unicode 标量值集合,也就是字符串的 UTF-32 编码格式 (利用字符串的`unicodeScalars`属性进行访问)
|
||||
|
||||
下面由`D`,`o`,`g`,`‼`(`DOUBLE EXCLAMATION MARK`, Unicode 标量 `U+203C`)和`🐶`(`DOG FACE`,Unicode 标量为`U+1F436`)组成的字符串中的每一个字符代表着一种不同的表示:
|
||||
|
||||
```swift
|
||||
let dogString = "Dog‼🐶"
|
||||
```
|
||||
|
||||
|
||||
<a name="UTF-8_representation"></a>
|
||||
### UTF-8 表示
|
||||
|
||||
您可以通过遍历`String`的`utf8`属性来访问它的`UTF-8`表示。
|
||||
其为`String.UTF8View`类型的属性,`UTF8View`是无符号8位 (`UInt8`) 值的集合,每一个`UInt8`值都是一个字符的 UTF-8 表示:
|
||||
|
||||
<table style='text-align:center'>
|
||||
<tr height="77">
|
||||
<td>Character</td>
|
||||
<td>D<br>U+0044</td>
|
||||
<td>o<br>U+006F</td>
|
||||
<td>g<br>U+0067</td>
|
||||
<td colspan="3">‼<br>U+203C</td>
|
||||
<td colspan="4">🐶<br>U+1F436</td>
|
||||
</tr>
|
||||
<tr height="77">
|
||||
<td height="77">UTF-8<br>Code Unit</td>
|
||||
<td>68</td>
|
||||
<td>111</td>
|
||||
<td>103</td>
|
||||
<td>226</td>
|
||||
<td>128</td>
|
||||
<td>188</td>
|
||||
<td>240</td>
|
||||
<td>159</td>
|
||||
<td>144</td>
|
||||
<td>182</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td height="77">Position</td>
|
||||
<td>0</td>
|
||||
<td>1</td>
|
||||
<td>2</td>
|
||||
<td>3</td>
|
||||
<td>4</td>
|
||||
<td>5</td>
|
||||
<td>6</td>
|
||||
<td>7</td>
|
||||
<td>8</td>
|
||||
<td>9</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
```swift
|
||||
for codeUnit in dogString.utf8 {
|
||||
print("\(codeUnit) ", terminator: "")
|
||||
}
|
||||
print("")
|
||||
// 68 111 103 226 128 188 240 159 144 182
|
||||
```
|
||||
|
||||
上面的例子中,前三个10进制`codeUnit`值 (`68`, `111`, `103`) 代表了字符`D`、`o`和 `g`,它们的 UTF-8 表示与 ASCII 表示相同。
|
||||
接下来的三个10进制`codeUnit`值 (`226`, `128`, `188`) 是`DOUBLE EXCLAMATION MARK`的3字节 UTF-8 表示。
|
||||
最后的四个`codeUnit`值 (`240`, `159`, `144`, `182`) 是`DOG FACE`的4字节 UTF-8 表示。
|
||||
|
||||
|
||||
<a name="UTF-16_representation"></a>
|
||||
### UTF-16 表示
|
||||
|
||||
您可以通过遍历`String`的`utf16`属性来访问它的`UTF-16`表示。
|
||||
其为`String.UTF16View`类型的属性,`UTF16View`是无符号16位 (`UInt16`) 值的集合,每一个`UInt16`都是一个字符的 UTF-16 表示:
|
||||
|
||||
<table style='text-align:center'>
|
||||
<tr height="77">
|
||||
<td>Character</td>
|
||||
<td>D<br>U+0044</td>
|
||||
<td>o<br>U+006F</td>
|
||||
<td>g<br>U+0067</td>
|
||||
<td>‼<br>U+203C</td>
|
||||
<td colspan="2">🐶<br>U+1F436</td>
|
||||
</tr>
|
||||
<tr height="77">
|
||||
<td height="77">UTF-16<br>Code Unit</td>
|
||||
<td>68</td>
|
||||
<td>111</td>
|
||||
<td>103</td>
|
||||
<td>8252</td>
|
||||
<td>55357</td>
|
||||
<td>56374</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td height="77">Position</td>
|
||||
<td>0</td>
|
||||
<td>1</td>
|
||||
<td>2</td>
|
||||
<td>3</td>
|
||||
<td>4</td>
|
||||
<td>5</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
```swift
|
||||
for codeUnit in dogString.utf16 {
|
||||
print("\(codeUnit) ", terminator: "")
|
||||
}
|
||||
print("")
|
||||
// 68 111 103 8252 55357 56374
|
||||
```
|
||||
|
||||
同样,前三个`codeUnit`值 (`68`, `111`, `103`) 代表了字符`D`、`o`和`g`,它们的 UTF-16 代码单元和 UTF-8 完全相同(因为这些 Unicode 标量表示 ASCII 字符)。
|
||||
|
||||
第四个`codeUnit`值 (`8252`) 是一个等于十六进制`203C`的的十进制值。这个代表了`DOUBLE EXCLAMATION MARK`字符的 Unicode 标量值`U+203C`。这个字符在 UTF-16 中可以用一个代码单元表示。
|
||||
|
||||
第五和第六个`codeUnit`值 (`55357`和`56374`) 是`DOG FACE`字符的 UTF-16 表示。
|
||||
第一个值为`U+D83D`(十进制值为`55357`),第二个值为`U+DC36`(十进制值为`56374`)。
|
||||
|
||||
<a name="unicode_scalars_representation"></a>
|
||||
### Unicode 标量表示 (Unicode Scalars Representation)
|
||||
|
||||
您可以通过遍历`String`值的`unicodeScalars`属性来访问它的 Unicode 标量表示。
|
||||
其为`UnicodeScalarView`类型的属性,`UnicodeScalarView`是`UnicodeScalar`类型的值的集合。
|
||||
`UnicodeScalar`是21位的 Unicode 代码点。
|
||||
|
||||
每一个`UnicodeScalar`拥有一个`value`属性,可以返回对应的21位数值,用`UInt32`来表示:
|
||||
|
||||
|
||||
<table style='text-align:center'>
|
||||
<tr height="77">
|
||||
<td>Character</td>
|
||||
<td>D<br>U+0044</td>
|
||||
<td>o<br>U+006F</td>
|
||||
<td>g<br>U+0067</td>
|
||||
<td>‼<br>U+203C</td>
|
||||
<td>🐶<br>U+1F436</td>
|
||||
</tr>
|
||||
<tr height="77">
|
||||
<td height="77">Unicode Scalar<br>Code Unit</td>
|
||||
<td>68</td>
|
||||
<td>111</td>
|
||||
<td>103</td>
|
||||
<td>8252</td>
|
||||
<td>128054</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td height="77">Position</td>
|
||||
<td>0</td>
|
||||
<td>1</td>
|
||||
<td>2</td>
|
||||
<td>3</td>
|
||||
<td>4</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
```swift
|
||||
for scalar in dogString.unicodeScalars {
|
||||
print("\(scalar.value) ", terminator: "")
|
||||
}
|
||||
print("")
|
||||
// 68 111 103 8252 128054
|
||||
```
|
||||
|
||||
前三个`UnicodeScalar`值(`68`, `111`, `103`)的`value`属性仍然代表字符`D`、`o`和`g`。
|
||||
第四个`codeUnit`值(`8252`)仍然是一个等于十六进制`203C`的十进制值。这个代表了`DOUBLE EXCLAMATION MARK`字符的 Unicode 标量`U+203C`。
|
||||
|
||||
第五个`UnicodeScalar`值的`value`属性,`128054`,是一个十六进制`1F436`的十进制表示。其等同于`DOG FACE`的 Unicode 标量`U+1F436`。
|
||||
|
||||
作为查询它们的`value`属性的一种替代方法,每个`UnicodeScalar`值也可以用来构建一个新的`String`值,比如在字符串插值中使用:
|
||||
|
||||
```swift
|
||||
for scalar in dogString.unicodeScalars {
|
||||
print("\(scalar) ")
|
||||
}
|
||||
// D
|
||||
// o
|
||||
// g
|
||||
// ‼
|
||||
// 🐶
|
||||
```
|
||||
@ -1,675 +0,0 @@
|
||||
# 集合类型 (Collection Types)
|
||||
-----------------
|
||||
|
||||
> 1.0
|
||||
> 翻译:[zqp](https://github.com/zqp)
|
||||
> 校对:[shinyzhu](https://github.com/shinyzhu), [stanzhai](https://github.com/stanzhai), [feiin](https://github.com/feiin)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[JackAlan](https://github.com/AlanMelody)
|
||||
|
||||
> 2.1
|
||||
> 校对:[shanks](http://codebuild.me)
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [集合的可变性(Mutability of Collections)](#mutability_of_collections)
|
||||
- [数组(Arrays)](#arrays)
|
||||
- [集合(Sets)](#sets)
|
||||
- [字典(Dictionaries)](#dictionaries)
|
||||
|
||||
Swift 语言提供`Arrays`、`Sets`和`Dictionaries`三种基本的集合类型用来存储集合数据。数组(Arrays)是有序数据的集。集合(Sets)是无序无重复数据的集。字典(Dictionaries)是无序的键值对的集。
|
||||
|
||||

|
||||
|
||||
Swift 语言中的`Arrays`、`Sets`和`Dictionaries`中存储的数据值类型必须明确。这意味着我们不能把不正确的数据类型插入其中。同时这也说明我们完全可以对取回值的类型非常自信。
|
||||
|
||||
> 注意:
|
||||
Swift 的`Arrays`、`Sets`和`Dictionaries`类型被实现为*泛型集合*。更多关于泛型类型和集合,参见 [泛型](./23_Generics.html)章节。
|
||||
|
||||
<a name="mutability_of_collections"></a>
|
||||
## 集合的可变性
|
||||
|
||||
如果创建一个`Arrays`、`Sets`或`Dictionaries`并且把它分配成一个变量,这个集合将会是*可变的*。这意味着我们可以在创建之后添加更多或移除已存在的数据项,或者改变集合中的数据项。如果我们把`Arrays`、`Sets`或`Dictionaries`分配成常量,那么它就是*不可变的*,它的大小和内容都不能被改变。
|
||||
|
||||
> 注意:
|
||||
在我们不需要改变集合的时候创建不可变集合是很好的实践。如此 Swift 编译器可以优化我们创建的集合。
|
||||
|
||||
<a name="arrays"></a>
|
||||
## 数组(Arrays)
|
||||
|
||||
数组使用有序列表存储同一类型的多个值。相同的值可以多次出现在一个数组的不同位置中。
|
||||
|
||||
> 注意:
|
||||
Swift 的`Array`类型被桥接到`Foundation`中的`NSArray`类。
|
||||
更多关于在`Foundation`和`Cocoa`中使用`Array`的信息,参见 [*Using Swift with Cocoa and Obejective-C*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 一书。
|
||||
|
||||
<a name="array_type_shorthand_syntax"></a>
|
||||
### 数组的简单语法
|
||||
|
||||
写 Swift 数组应该遵循像`Array<Element>`这样的形式,其中`Element`是这个数组中唯一允许存在的数据类型。我们也可以使用像`[Element]`这样的简单语法。尽管两种形式在功能上是一样的,但是推荐较短的那种,而且在本文中都会使用这种形式来使用数组。
|
||||
|
||||
<a name="creating_an_empty_array"></a>
|
||||
### 创建一个空数组
|
||||
|
||||
我们可以使用构造语法来创建一个由特定数据类型构成的空数组:
|
||||
|
||||
```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] 类型的。
|
||||
```
|
||||
|
||||
<a name="creating_an_array_with_a_default_value"></a>
|
||||
### 创建一个带有默认值的数组
|
||||
|
||||
Swift 中的`Array`类型还提供一个可以创建特定大小并且所有数据都被默认的构造方法。我们可以把准备加入新数组的数据项数量(`count`)和适当类型的初始值(`repeatedValue`)传入数组构造函数:
|
||||
|
||||
```swift
|
||||
var threeDoubles = [Double](count: 3, repeatedValue:0.0)
|
||||
// threeDoubles 是一种 [Double] 数组,等价于 [0.0, 0.0, 0.0]
|
||||
```
|
||||
|
||||
<a name="creating_an_array_by_adding_two_arrays_together"></a>
|
||||
### 通过两个数组相加创建一个数组
|
||||
|
||||
我们可以使用加法操作符(`+`)来组合两种已存在的相同类型数组。新数组的数据类型会被从两个数组的数据类型中推断出来:
|
||||
|
||||
```swift
|
||||
var anotherThreeDoubles = Array(count: 3, repeatedValue: 2.5)
|
||||
// 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]
|
||||
```
|
||||
|
||||
<a name="creating_an_array_with_an_array_literals"></a>
|
||||
### 用字面量构造数组
|
||||
|
||||
我们可以使用字面量来进行数组构造,这是一种用一个或者多个数值构造数组的简单方法。字面量是一系列由逗号分割并由方括号包含的数值:
|
||||
|
||||
`[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`中变量的正确类型。
|
||||
|
||||
<a name="accessing_and_modifying_an_array"></a>
|
||||
### 访问和修改数组
|
||||
|
||||
我们可以通过数组的方法和属性来访问和修改数组,或者使用下标语法。
|
||||
|
||||
可以使用数组的只读属性`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(_:atIndex:)`方法来在某个具体索引值之前添加数据项:
|
||||
|
||||
```swift
|
||||
shoppingList.insert("Maple Syrup", atIndex: 0)
|
||||
// shoppingList 现在有7项
|
||||
// "Maple Syrup" 现在是这个列表中的第一项
|
||||
```
|
||||
|
||||
这次`insert(_:atIndex:)`方法调用把值为`"Maple Syrup"`的新数据项插入列表的最开始位置,并且使用`0`作为索引值。
|
||||
|
||||
类似的我们可以使用`removeAtIndex(_:)`方法来移除数组中的某一项。这个方法把数组在特定索引值中存储的数据项移除并且返回这个被移除的数据项(我们不需要的时候就可以无视它):
|
||||
|
||||
```swift
|
||||
let mapleSyrup = shoppingList.removeAtIndex(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()`方法而不是`removeAtIndex(_:)`方法来避免我们需要获取数组的`count`属性。就像后者一样,前者也会返回被移除的数据项:
|
||||
|
||||
```swift
|
||||
let apples = shoppingList.removeLast()
|
||||
// 数组的最后一项被移除了
|
||||
// shoppingList 现在只有5项,不包括 Apples
|
||||
// apples 常量的值现在等于 "Apples" 字符串
|
||||
```
|
||||
|
||||
<a name="iterating_over_an_array"></a>
|
||||
### 数组的遍历
|
||||
|
||||
我们可以使用`for-in`循环来遍历所有数组中的数据项:
|
||||
|
||||
```swift
|
||||
for item in shoppingList {
|
||||
print(item)
|
||||
}
|
||||
// Six eggs
|
||||
// Milk
|
||||
// Flour
|
||||
// Baking Powder
|
||||
// Bananas
|
||||
```
|
||||
|
||||
如果我们同时需要每个数据项的值和索引值,可以使用`enumerate()`方法来进行数组遍历。`enumerate()`返回一个由每一个数据项索引值和数据值组成的元组。我们可以把这个元组分解成临时常量或者变量来进行遍历:
|
||||
|
||||
```swift
|
||||
for (index, value) in shoppingList.enumerate() {
|
||||
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)。
|
||||
|
||||
<a name="sets"></a>
|
||||
## 集合(Sets)
|
||||
|
||||
*集合(Set)*用来存储相同类型并且没有确定顺序的值。当集合元素顺序不重要时或者希望确保每个元素只出现一次时可以使用集合而不是数组。
|
||||
|
||||
> 注意:
|
||||
> Swift的`Set`类型被桥接到`Foundation`中的`NSSet`类。
|
||||
> 关于使用`Foundation`和`Cocoa`中`Set`的知识,请看 [*Using Swift with Cocoa and Objective-C*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216)。
|
||||
|
||||
<a name="hash_values_for_set_types"></a>
|
||||
#### 集合类型的哈希值
|
||||
|
||||
一个类型为了存储在集合中,该类型必须是可哈希化的--也就是说,该类型必须提供一个方法来计算它的哈希值。一个哈希值是`Int`类型的,相等的对象哈希值必须相同,比如`a==b`,因此必须`a.hashValue == b.hashValue`。
|
||||
|
||||
Swift 的所有基本类型(比如`String`,`Int`,`Double`和`Bool`)默认都是可哈希化的,可以作为集合的值的类型或者字典的键的类型。没有关联值的枚举成员值(在[枚举](./08_Enumerations.html)有讲述)默认也是可哈希化的。
|
||||
|
||||
> 注意:
|
||||
> 你可以使用你自定义的类型作为集合的值的类型或者是字典的键的类型,但你需要使你的自定义类型符合 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.html)。
|
||||
|
||||
<a name="set_type_syntax"></a>
|
||||
### 集合类型语法
|
||||
|
||||
Swift 中的`Set`类型被写为`Set<Element>`,这里的`Element`表示`Set`中允许存储的类型,和数组不同的是,集合没有等价的简化形式。
|
||||
|
||||
<a name="creating_and_initalizing_an_empty_set"></a>
|
||||
### 创建和构造一个空的集合
|
||||
|
||||
你可以通过构造器语法创建一个特定类型的空集合:
|
||||
|
||||
```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> 类型
|
||||
```
|
||||
|
||||
<a name="creating_a_set_with_an_array_literal"></a>
|
||||
### 用数组字面量创建集合
|
||||
|
||||
你可以使用数组字面量来构造集合,并且可以使用简化形式写一个或者多个值作为集合元素。
|
||||
|
||||
下面的例子创建一个称之为`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`变量的正确类型。
|
||||
|
||||
<a name="accesing_and_modifying_a_set"></a>
|
||||
### 访问和修改一个集合
|
||||
|
||||
你可以通过`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."
|
||||
```
|
||||
|
||||
<a name="iterating_over_a_set"></a>
|
||||
### 遍历一个集合
|
||||
|
||||
你可以在一个`for-in`循环中遍历一个`Set`中的所有值。
|
||||
|
||||
```swift
|
||||
for genre in favoriteGenres {
|
||||
print("\(genre)")
|
||||
}
|
||||
// Classical
|
||||
// Jazz
|
||||
// Hip hop
|
||||
```
|
||||
|
||||
更多关于`for-in`循环的信息,参见[For 循环](./05_Control_Flow.html#for_loops)。
|
||||
|
||||
Swift 的`Set`类型没有确定的顺序,为了按照特定顺序来遍历一个`Set`中的值可以使用`sort()`方法,它将根据提供的序列返回一个有序集合.
|
||||
|
||||
```swift
|
||||
for genre in favoriteGenres.sort() {
|
||||
print("\(genre)")
|
||||
}
|
||||
// prints "Classical"
|
||||
// prints "Hip hop"
|
||||
// prints "Jazz
|
||||
```
|
||||
|
||||
<a name="performing_set_operations"></a>
|
||||
### 集合操作
|
||||
|
||||
你可以高效地完成`Set`的一些基本操作,比如把两个集合组合到一起,判断两个集合共有元素,或者判断两个集合是否全包含,部分包含或者不相交。
|
||||
|
||||
<a name="fundamental_set_operations"></a>
|
||||
#### 基本集合操作
|
||||
|
||||
下面的插图描述了两个集合-`a`和`b`-以及通过阴影部分的区域显示集合各种操作的结果。
|
||||
|
||||

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

|
||||
|
||||
* 使用“是否相等”运算符(`==`)来判断两个集合是否包含全部相同的值。
|
||||
* 使用`isSubsetOf(_:)`方法来判断一个集合中的值是否也被包含在另外一个集合中。
|
||||
* 使用`isSupersetOf(_:)`方法来判断一个集合中包含另一个集合中所有的值。
|
||||
* 使用`isStrictSubsetOf(_:)`或者`isStrictSupersetOf(_:)`方法来判断一个集合是否是另外一个集合的子集合或者父集合并且两个集合并不相等。
|
||||
* 使用`isDisjointWith(_:)`方法来判断两个集合是否不含有相同的值(是否没有交集)。
|
||||
|
||||
```swift
|
||||
let houseAnimals: Set = ["🐶", "🐱"]
|
||||
let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"]
|
||||
let cityAnimals: Set = ["🐦", "🐭"]
|
||||
|
||||
houseAnimals.isSubsetOf(farmAnimals)
|
||||
// true
|
||||
farmAnimals.isSupersetOf(houseAnimals)
|
||||
// true
|
||||
farmAnimals.isDisjointWith(cityAnimals)
|
||||
// true
|
||||
```
|
||||
|
||||
<a name="dictionaries"></a>
|
||||
## 字典
|
||||
|
||||
*字典*是一种存储多个相同类型的值的容器。每个值(value)都关联唯一的键(key),键作为字典中的这个值数据的标识符。和数组中的数据项不同,字典中的数据项并没有具体顺序。我们在需要通过标识符(键)访问数据的时候使用字典,这种方法很大程度上和我们在现实世界中使用字典查字义的方法一样。
|
||||
|
||||
> 注意:
|
||||
> Swift 的`Dictionary`类型被桥接到`Foundation`的`NSDictionary`类。
|
||||
> 更多关于在`Foundation`和`Cocoa`中使用`Dictionary`类型的信息,参见 [*Using Swift with Cocoa and Objective-C (Swift 2.1)*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 一书。
|
||||
|
||||
<a name="dictionary_type_shorthand_syntax"></a>
|
||||
## 字典类型快捷语法
|
||||
|
||||
Swift 的字典使用`Dictionary<Key, Value>`定义,其中`Key`是字典中键的数据类型,`Value`是字典中对应于这些键所存储值的数据类型。
|
||||
|
||||
> 注意:
|
||||
> 一个字典的`Key`类型必须遵循`Hashable`协议,就像`Set`的值类型。
|
||||
|
||||
我们也可以用`[Key: Value]`这样快捷的形式去创建一个字典类型。虽然这两种形式功能上相同,但是后者是首选,并且这本指导书涉及到字典类型时通篇采用后者。
|
||||
|
||||
<a name="creating_an_empty_dictionary"></a>
|
||||
### 创建一个空字典
|
||||
|
||||
我们可以像数组一样使用构造语法创建一个拥有确定类型的空字典:
|
||||
|
||||
```swift
|
||||
var namesOfIntegers = [Int: String]()
|
||||
// namesOfIntegers 是一个空的 [Int: String] 字典
|
||||
```
|
||||
|
||||
这个例子创建了一个`[Int: String]`类型的空字典来储存整数的英语命名。它的键是`Int`型,值是`String`型。
|
||||
|
||||
如果上下文已经提供了类型信息,我们可以使用空字典字面量来创建一个空字典,记作`[:]`(中括号中放一个冒号):
|
||||
|
||||
```swift
|
||||
namesOfIntegers[16] = "sixteen"
|
||||
// namesOfIntegers 现在包含一个键值对
|
||||
namesOfIntegers = [:]
|
||||
// namesOfIntegers 又成为了一个 [Int: String] 类型的空字典
|
||||
```
|
||||
|
||||
<a name="creating_a_dictionary_with_a_dictionary_literal"></a>
|
||||
## 用字典字面量创建字典
|
||||
|
||||
我们可以使用字典字面量来构造字典,这和我们刚才介绍过的数组字面量拥有相似语法。字典字面量是一种将一个或多个键值对写作`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`字典的正确类型。
|
||||
|
||||
<a name="accessing_and_modifying_a_dictionary"></a>
|
||||
### 访问和修改字典
|
||||
|
||||
我们可以通过字典的方法和属性来访问和修改字典,或者通过使用下标语法。
|
||||
|
||||
和数组一样,我们可以通过字典的只读属性`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 现在被移除了
|
||||
```
|
||||
|
||||
此外,`removeValueForKey(_:)`方法也可以用来在字典中移除键值对。这个方法在键值对存在的情况下会移除该键值对并且返回被移除的值或者在没有值的情况下返回`nil`:
|
||||
|
||||
```swift
|
||||
if let removedValue = airports.removeValueForKey("DUB") {
|
||||
print("The removed airport's name is \(removedValue).")
|
||||
} else {
|
||||
print("The airports dictionary does not contain a value for DUB.")
|
||||
}
|
||||
// prints "The removed airport's name is Dublin Airport."
|
||||
```
|
||||
|
||||
<a name="iterating_over_a_dictionary"></a>
|
||||
### 字典遍历
|
||||
|
||||
我们可以使用`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.html#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`属性使用`sort()`方法。
|
||||
@ -1,738 +0,0 @@
|
||||
# 控制流(Control Flow)
|
||||
-----------------
|
||||
|
||||
> 1.0
|
||||
> 翻译:[vclwei](https://github.com/vclwei), [coverxit](https://github.com/coverxit), [NicePiao](https://github.com/NicePiao)
|
||||
> 校对:[coverxit](https://github.com/coverxit), [stanzhai](https://github.com/stanzhai)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[JackAlan](https://github.com/AlanMelody)
|
||||
|
||||
> 2.1
|
||||
> 翻译:[Prayer](https://github.com/futantan)
|
||||
> 校对:[shanks](http://codebuild.me)
|
||||
|
||||
> 2.2
|
||||
> 翻译:[LinusLing](https://github.com/linusling)
|
||||
> 校对:[]()
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [For-In 循环](#for_in_loops)
|
||||
- [While 循环](#while_loops)
|
||||
- [条件语句](#conditional_statement)
|
||||
- [控制转移语句(Control Transfer Statements)](#control_transfer_statements)
|
||||
- [提前退出](#early_exit)
|
||||
- [检测 API 可用性](#checking_api_availability)
|
||||
|
||||
Swift提供了多种流程控制结构,包括可以多次执行任务的`while`循环,基于特定条件选择执行不同代码分支的`if`、`guard`和`switch`语句,还有控制流程跳转到其他代码的`break`和`continue`语句。
|
||||
|
||||
Swift 还增加了`for-in`循环,用来更简单地遍历数组(array),字典(dictionary),区间(range),字符串(string)和其他序列类型。
|
||||
|
||||
Swift 的`switch`语句比 C 语言中更加强大。在 C 语言中,如果某个 case 不小心漏写了`break`,这个 case 就会贯穿至下一个 case,Swift 无需写`break`,所以不会发生这种贯穿的情况。case 还可以匹配更多的类型模式,包括区间匹配(range matching),元组(tuple)和特定类型的描述。`switch`的 case 语句中匹配的值可以是由 case 体内部临时的常量或者变量决定,也可以由`where`分句描述更复杂的匹配条件。
|
||||
|
||||
<a name="for_in_loops"></a>
|
||||
## For-In 循环
|
||||
|
||||
你可以使用`for-in`循环来遍历一个集合里面的所有元素,例如由数字表示的区间、数组中的元素、字符串中的字符。
|
||||
|
||||
下面的例子用来输出乘 5 乘法表前面一部分内容:
|
||||
|
||||
```swift
|
||||
for index in 1...5 {
|
||||
print("\(index) times 5 is \(index * 5)")
|
||||
}
|
||||
// 1 times 5 is 5
|
||||
// 2 times 5 is 10
|
||||
// 3 times 5 is 15
|
||||
// 4 times 5 is 20
|
||||
// 5 times 5 is 25
|
||||
```
|
||||
|
||||
例子中用来进行遍历的元素是一组使用闭区间操作符(`...`)表示的从`1`到`5`的数字。`index`被赋值为闭区间中的第一个数字(`1`),然后循环中的语句被执行一次。在本例中,这个循环只包含一个语句,用来输出当前`index`值所对应的乘 5 乘法表结果。该语句执行后,`index`的值被更新为闭区间中的第二个数字(`2`),之后`print(_:separator:terminator:)`函数会再执行一次。整个过程会进行到闭区间结尾为止。
|
||||
|
||||
上面的例子中,`index`是一个每次循环遍历开始时被自动赋值的常量。这种情况下,`index`在使用前不需要声明,只需要将它包含在循环的声明中,就可以对其进行隐式声明,而无需使用`let`关键字声明。
|
||||
|
||||
如果你不需要知道区间序列内每一项的值,你可以使用下划线(`_`)替代变量名来忽略对值的访问:
|
||||
|
||||
```swift
|
||||
let base = 3
|
||||
let power = 10
|
||||
var answer = 1
|
||||
for _ in 1...power {
|
||||
answer *= base
|
||||
}
|
||||
print("\(base) to the power of \(power) is \(answer)")
|
||||
// 输出 "3 to the power of 10 is 59049"
|
||||
```
|
||||
|
||||
这个例子计算 base 这个数的 power 次幂(本例中,是`3`的`10`次幂),从`1`(`3`的`0`次幂)开始做`3`的乘法, 进行`10`次,使用`1`到`10`的闭区间循环。这个计算并不需要知道每一次循环中计数器具体的值,只需要执行了正确的循环次数即可。下划线符号`_`(替代循环中的变量)能够忽略具体的值,并且不提供循环遍历时对值的访问。
|
||||
|
||||
使用`for-in`遍历一个数组所有元素:
|
||||
|
||||
```swift
|
||||
let names = ["Anna", "Alex", "Brian", "Jack"]
|
||||
for name in names {
|
||||
print("Hello, \(name)!")
|
||||
}
|
||||
// Hello, Anna!
|
||||
// Hello, Alex!
|
||||
// Hello, Brian!
|
||||
// Hello, Jack!
|
||||
```
|
||||
|
||||
你也可以通过遍历一个字典来访问它的键值对。遍历字典时,字典的每项元素会以`(key, value)`元组的形式返回,你可以在`for-in`循环中使用显式的常量名称来解读`(key, value)`元组。下面的例子中,字典的键(key)解读为常量`animalName`,字典的值会被解读为常量`legCount`:
|
||||
|
||||
```swift
|
||||
let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
|
||||
for (animalName, legCount) in numberOfLegs {
|
||||
print("\(animalName)s have \(legCount) legs")
|
||||
}
|
||||
// ants have 6 legs
|
||||
// cats have 4 legs
|
||||
// spiders have 8 legs
|
||||
```
|
||||
|
||||
字典元素的遍历顺序和插入顺序可能不同,字典的内容在内部是无序的,所以遍历元素时不能保证顺序。关于数组和字典,详情参见[集合类型](./04_Collection_Types.html)。
|
||||
|
||||
<a name="while_loops"></a>
|
||||
## While 循环
|
||||
|
||||
`while`循环运行一系列语句直到条件变成`false`。这类循环适合使用在第一次迭代前迭代次数未知的情况下。Swift 提供两种`while`循环形式:
|
||||
|
||||
* `while`循环,每次在循环开始时计算条件是否符合;
|
||||
* `repeat-while`循环,每次在循环结束时计算条件是否符合。
|
||||
|
||||
<a name="while"></a>
|
||||
###While
|
||||
|
||||
`while`循环从计算单一条件开始。如果条件为`true`,会重复运行一系列语句,直到条件变为`false`。
|
||||
|
||||
下面是一般情况下 `while` 循环格式:
|
||||
|
||||
```swift
|
||||
while condition {
|
||||
statements
|
||||
}
|
||||
```
|
||||
|
||||
下面的例子来玩一个叫做蛇和梯子的小游戏,也叫做滑道和梯子:
|
||||
|
||||

|
||||
|
||||
游戏的规则如下:
|
||||
|
||||
* 游戏盘面包括 25 个方格,游戏目标是达到或者超过第 25 个方格;
|
||||
* 每一轮,你通过掷一个 6 边的骰子来确定你移动方块的步数,移动的路线由上图中横向的虚线所示;
|
||||
* 如果在某轮结束,你移动到了梯子的底部,可以顺着梯子爬上去;
|
||||
* 如果在某轮结束,你移动到了蛇的头部,你会顺着蛇的身体滑下去。
|
||||
|
||||
游戏盘面可以使用一个`Int`数组来表达。数组的长度由一个`finalSquare`常量储存,用来初始化数组和检测最终胜利条件。游戏盘面由 26 个 `Int` 0 值初始化,而不是 25 个(由`0`到`25`,一共 26 个):
|
||||
|
||||
```swift
|
||||
let finalSquare = 25
|
||||
var board = [Int](count: finalSquare + 1, repeatedValue: 0)
|
||||
```
|
||||
|
||||
一些方块被设置成有蛇或者梯子的指定值。梯子底部的方块是一个正值,使你可以向上移动,蛇头处的方块是一个负值,会让你向下移动:
|
||||
|
||||
```swift
|
||||
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
|
||||
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
|
||||
```
|
||||
|
||||
3 号方块是梯子的底部,会让你向上移动到 11 号方格,我们使用`board[03]`等于`+08`(来表示`11`和`3`之间的差值)。使用一元加运算符(`+i`)是为了和一元减运算符(`-i`)对称,为了让盘面代码整齐,小于 10 的数字都使用 0 补齐(这些风格上的调整都不是必须的,只是为了让代码看起来更加整洁)。
|
||||
|
||||
玩家由左下角编号为 0 的方格开始游戏。一般来说玩家第一次掷骰子后才会进入游戏盘面:
|
||||
|
||||
```swift
|
||||
var square = 0
|
||||
var diceRoll = 0
|
||||
while square < finalSquare {
|
||||
// 掷骰子
|
||||
if ++diceRoll == 7 { diceRoll = 1 }
|
||||
// 根据点数移动
|
||||
square += diceRoll
|
||||
if square < board.count {
|
||||
// 如果玩家还在棋盘上,顺着梯子爬上去或者顺着蛇滑下去
|
||||
square += board[square]
|
||||
}
|
||||
}
|
||||
print("Game over!")
|
||||
```
|
||||
|
||||
本例中使用了最简单的方法来模拟掷骰子。 `diceRoll`的值并不是一个随机数,而是以`0`为初始值,之后每一次`while`循环,`diceRoll`的值使用前置自增操作符(`++i`)来自增 1 ,然后检测是否超出了最大值。任何时候如果`diceRoll`的值等于 7 时,就超过了骰子的最大值,会被重置为`1`。所以`diceRoll`的取值顺序会一直是`1`。因此,`diceRoll` 所有的值只可能是 `1` ,`2`,`3`,`4`,`5`,`6`,`1`,`2` 等。
|
||||
|
||||
掷完骰子后,玩家向前移动`diceRoll`个方格,如果玩家移动超过了第 25 个方格,这个时候游戏结束,相应地,代码会在`square`增加`board[square]`的值向前或向后移动(遇到了梯子或者蛇)之前,检测`square`的值是否小于`board`的`count`属性。
|
||||
|
||||
> 注意:
|
||||
> 如果没有这个检测(`square < board.count`),`board[square]`可能会越界访问`board`数组,导致错误。例如如果`square`等于`26`, 代码会去尝试访问`board[26]`,超过数组的长度。
|
||||
|
||||
当本轮`while`循环运行完毕,会再检测循环条件是否需要再运行一次循环。如果玩家移动到或者超过第 25 个方格,循环条件结果为`false`,此时游戏结束。
|
||||
|
||||
`while` 循环比较适合本例中的这种情况,因为在 `while` 循环开始时,我们并不知道游戏的长度或者循环的次数,只有在达成指定条件时循环才会结束。
|
||||
|
||||
|
||||
<a name="repeat_while"></a>
|
||||
###Repeat-While
|
||||
|
||||
`while`循环的另外一种形式是`repeat-while`,它和`while`的区别是在判断循环条件之前,先执行一次循环的代码块,然后重复循环直到条件为`false`。
|
||||
|
||||
> 注意:
|
||||
> Swift语言的`repeat-while`循环合其他语言中的`do-while`循环是类似的。
|
||||
|
||||
下面是一般情况下 `repeat-while`循环的格式:
|
||||
|
||||
```swift
|
||||
repeat {
|
||||
statements
|
||||
} while condition
|
||||
```
|
||||
|
||||
还是蛇和梯子的游戏,使用`repeat-while`循环来替代`while`循环。`finalSquare`、`board`、`square`和`diceRoll`的值初始化同`while`循环一样:
|
||||
|
||||
``` swift
|
||||
let finalSquare = 25
|
||||
var board = [Int](count: finalSquare + 1, repeatedValue: 0)
|
||||
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
|
||||
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
|
||||
var square = 0
|
||||
var diceRoll = 0
|
||||
```
|
||||
|
||||
`repeat-while`的循环版本,循环中_第一步_就需要去检测是否在梯子或者蛇的方块上。没有梯子会让玩家直接上到第 25 个方格,所以玩家不会通过梯子直接赢得游戏。这样在循环开始时先检测是否踩在梯子或者蛇上是安全的。
|
||||
|
||||
游戏开始时,玩家在第 0 个方格上,`board[0]`一直等于 0, 不会有什么影响:
|
||||
|
||||
```swift
|
||||
repeat {
|
||||
// 顺着梯子爬上去或者顺着蛇滑下去
|
||||
square += board[square]
|
||||
// 掷骰子
|
||||
if ++diceRoll == 7 { diceRoll = 1 }
|
||||
// 根据点数移动
|
||||
square += diceRoll
|
||||
} while square < finalSquare
|
||||
print("Game over!")
|
||||
```
|
||||
|
||||
检测完玩家是否踩在梯子或者蛇上之后,开始掷骰子,然后玩家向前移动`diceRoll`个方格,本轮循环结束。
|
||||
|
||||
循环条件(`while square < finalSquare`)和`while`方式相同,但是只会在循环结束后进行计算。在这个游戏中,`repeat-while`表现得比`while`循环更好。`repeat-while`方式会在条件判断`square`没有超出后直接运行`square += board[square]`,这种方式可以去掉`while`版本中的数组越界判断。
|
||||
|
||||
<a name="conditional_statement"></a>
|
||||
## 条件语句
|
||||
|
||||
根据特定的条件执行特定的代码通常是十分有用的,例如:当错误发生时,你可能想运行额外的代码;或者,当输入的值太大或太小时,向用户显示一条消息等。要实现这些功能,你就需要使用*条件语句*。
|
||||
|
||||
Swift 提供两种类型的条件语句:`if`语句和`switch`语句。通常,当条件较为简单且可能的情况很少时,使用`if`语句。而`switch`语句更适用于条件较复杂、可能情况较多且需要用到模式匹配(pattern-matching)的情境。
|
||||
|
||||
<a name="if"></a>
|
||||
### If
|
||||
|
||||
`if`语句最简单的形式就是只包含一个条件,当且仅当该条件为`true`时,才执行相关代码:
|
||||
|
||||
```swift
|
||||
var temperatureInFahrenheit = 30
|
||||
if temperatureInFahrenheit <= 32 {
|
||||
print("It's very cold. Consider wearing a scarf.")
|
||||
}
|
||||
// 输出 "It's very cold. Consider wearing a scarf."
|
||||
```
|
||||
|
||||
上面的例子会判断温度是否小于等于 32 华氏度(水的冰点)。如果是,则打印一条消息;否则,不打印任何消息,继续执行`if`块后面的代码。
|
||||
|
||||
当然,`if`语句允许二选一,也就是当条件为`false`时,执行 *else 语句*:
|
||||
|
||||
```swift
|
||||
temperatureInFahrenheit = 40
|
||||
if temperatureInFahrenheit <= 32 {
|
||||
print("It's very cold. Consider wearing a scarf.")
|
||||
} else {
|
||||
print("It's not that cold. Wear a t-shirt.")
|
||||
}
|
||||
// 输出 "It's not that cold. Wear a t-shirt."
|
||||
```
|
||||
|
||||
显然,这两条分支中总有一条会被执行。由于温度已升至 40 华氏度,不算太冷,没必要再围围巾——因此,`else`分支就被触发了。
|
||||
|
||||
你可以把多个`if`语句链接在一起,像下面这样:
|
||||
|
||||
```swift
|
||||
temperatureInFahrenheit = 90
|
||||
if temperatureInFahrenheit <= 32 {
|
||||
print("It's very cold. Consider wearing a scarf.")
|
||||
} else if temperatureInFahrenheit >= 86 {
|
||||
print("It's really warm. Don't forget to wear sunscreen.")
|
||||
} else {
|
||||
print("It's not that cold. Wear a t-shirt.")
|
||||
}
|
||||
// 输出 "It's really warm. Don't forget to wear sunscreen."
|
||||
```
|
||||
|
||||
在上面的例子中,额外的`if`语句用于判断是不是特别热。而最后的`else`语句被保留了下来,用于打印既不冷也不热时的消息。
|
||||
|
||||
实际上,最后的`else`语句是可选的:
|
||||
|
||||
```swift
|
||||
temperatureInFahrenheit = 90
|
||||
if temperatureInFahrenheit <= 32 {
|
||||
print("It's very cold. Consider wearing a scarf.")
|
||||
} else if temperatureInFahrenheit >= 86 {
|
||||
print("It's really warm. Don't forget to wear sunscreen.")
|
||||
}
|
||||
```
|
||||
|
||||
在这个例子中,由于既不冷也不热,所以不会触发`if`或`else if`分支,也就不会打印任何消息。
|
||||
|
||||
<a name="switch"></a>
|
||||
### Switch
|
||||
|
||||
`switch`语句会尝试把某个值与若干个模式(pattern)进行匹配。根据第一个匹配成功的模式,`switch`语句会执行对应的代码。当有可能的情况较多时,通常用`switch`语句替换`if`语句。
|
||||
|
||||
`switch`语句最简单的形式就是把某个值与一个或若干个相同类型的值作比较:
|
||||
|
||||
```swift
|
||||
switch some value to consider {
|
||||
case value 1:
|
||||
respond to value 1
|
||||
case value 2,
|
||||
value 3:
|
||||
respond to value 2 or 3
|
||||
default:
|
||||
otherwise, do something else
|
||||
}
|
||||
```
|
||||
|
||||
`switch`语句都由*多个 case* 构成。为了匹配某些更特定的值,Swift 提供了几种更复杂的匹配模式,这些模式将在本节的稍后部分提到。
|
||||
|
||||
每一个 case 都是代码执行的一条分支,这与`if`语句类似。与之不同的是,`switch`语句会决定哪一条分支应该被执行。
|
||||
|
||||
`switch`语句必须是完备的。这就是说,每一个可能的值都必须至少有一个 case 分支与之对应。在某些不可能涵盖所有值的情况下,你可以使用默认(`default`)分支满足该要求,这个默认分支必须在`switch`语句的最后面。
|
||||
|
||||
下面的例子使用`switch`语句来匹配一个名为`someCharacter`的小写字符:
|
||||
|
||||
```swift
|
||||
let someCharacter: Character = "e"
|
||||
switch someCharacter {
|
||||
case "a", "e", "i", "o", "u":
|
||||
print("\(someCharacter) is a vowel")
|
||||
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
|
||||
"n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
|
||||
print("\(someCharacter) is a consonant")
|
||||
default:
|
||||
print("\(someCharacter) is not a vowel or a consonant")
|
||||
}
|
||||
// 输出 "e is a vowel"
|
||||
```
|
||||
|
||||
在这个例子中,第一个 case 分支用于匹配五个元音,第二个 case 分支用于匹配所有的辅音。
|
||||
|
||||
由于为其它可能的字符写 case 分支没有实际的意义,因此在这个例子中使用了默认分支来处理剩下的既不是元音也不是辅音的字符——这就保证了`switch`语句的完备性。
|
||||
|
||||
<a name="no_implicit_fallthrough"></a>
|
||||
#### 不存在隐式的贯穿(No Implicit Fallthrough)
|
||||
|
||||
与 C 语言和 Objective-C 中的`switch`语句不同,在 Swift 中,当匹配的 case 分支中的代码执行完毕后,程序会终止`switch`语句,而不会继续执行下一个 case 分支。这也就是说,不需要在 case 分支中显式地使用`break`语句。这使得`switch`语句更安全、更易用,也避免了因忘记写`break`语句而产生的错误。
|
||||
|
||||
> 注意:
|
||||
虽然在Swift中`break`不是必须的,但你依然可以在 case 分支中的代码执行完毕前使用`break`跳出,详情请参见[Switch 语句中的 break](#break_in_a_switch_statement)。
|
||||
|
||||
每一个 case 分支都*必须*包含至少一条语句。像下面这样书写代码是无效的,因为第一个 case 分支是空的:
|
||||
|
||||
```swift
|
||||
let anotherCharacter: Character = "a"
|
||||
switch anotherCharacter {
|
||||
case "a":
|
||||
case "A":
|
||||
print("The letter A")
|
||||
default:
|
||||
print("Not the letter A")
|
||||
}
|
||||
// this will report a compile-time error
|
||||
```
|
||||
|
||||
不像 C 语言里的`switch`语句,在 Swift 中,`switch`语句不会同时匹配`"a"`和`"A"`。相反的,上面的代码会引起编译期错误:`case "a": does not contain any executable statements`——这就避免了意外地从一个 case 分支贯穿到另外一个,使得代码更安全、也更直观。
|
||||
|
||||
一个 case 也可以包含多个模式,用逗号把它们分开(如果太长了也可以分行写):
|
||||
|
||||
```swift
|
||||
switch some value to consider {
|
||||
case value 1,
|
||||
value 2:
|
||||
statements
|
||||
}
|
||||
```
|
||||
|
||||
> 注意:
|
||||
如果想要贯穿至特定的 case 分支中,请使用`fallthrough`语句,详情请参考[贯穿(Fallthrough)](#fallthrough)。
|
||||
|
||||
<a name="interval_matching"></a>
|
||||
#### 区间匹配
|
||||
|
||||
case 分支的模式也可以是一个值的区间。下面的例子展示了如何使用区间匹配来输出任意数字对应的自然语言格式:
|
||||
|
||||
```swift
|
||||
let approximateCount = 62
|
||||
let countedThings = "moons orbiting Saturn"
|
||||
var naturalCount: String
|
||||
switch approximateCount {
|
||||
case 0:
|
||||
naturalCount = "no"
|
||||
case 1..<5:
|
||||
naturalCount = "a few"
|
||||
case 5..<12:
|
||||
naturalCount = "several"
|
||||
case 12..<100:
|
||||
naturalCount = "dozens of"
|
||||
case 100..<1000:
|
||||
naturalCount = "hundreds of"
|
||||
default:
|
||||
naturalCount = "many"
|
||||
}
|
||||
print("There are \(naturalCount) \(countedThings).")
|
||||
// 输出 "There are dozens of moons orbiting Saturn."
|
||||
```
|
||||
|
||||
在上例中,`approximateCount`在一个`switch`声明中被估值。每一个`case`都与之进行比较。因为`approximateCount`落在了 12 到 100 的区间,所以`naturalCount`等于`"dozens of"`值,并且此后这段执行跳出了`switch`声明。
|
||||
|
||||
> 注意:
|
||||
> 闭区间操作符(`...`)以及半开区间操作符(`..<`)功能被重载去返回`IntervalType`或`Range`。一个区间可以决定他是否包含特定的元素,就像当匹配一个`switch`声明的`case`一样。区间是一个连续值的集合,可以用`for-in`语句遍历它。
|
||||
|
||||
<a name="tuples"></a>
|
||||
#### 元组(Tuple)
|
||||
|
||||
我们可以使用元组在同一个`switch`语句中测试多个值。元组中的元素可以是值,也可以是区间。另外,使用下划线(`_`)来匹配所有可能的值。
|
||||
|
||||
下面的例子展示了如何使用一个`(Int, Int)`类型的元组来分类下图中的点(x, y):
|
||||
|
||||
```swift
|
||||
let somePoint = (1, 1)
|
||||
switch somePoint {
|
||||
case (0, 0):
|
||||
print("(0, 0) is at the origin")
|
||||
case (_, 0):
|
||||
print("(\(somePoint.0), 0) is on the x-axis")
|
||||
case (0, _):
|
||||
print("(0, \(somePoint.1)) is on the y-axis")
|
||||
case (-2...2, -2...2):
|
||||
print("(\(somePoint.0), \(somePoint.1)) is inside the box")
|
||||
default:
|
||||
print("(\(somePoint.0), \(somePoint.1)) is outside of the box")
|
||||
}
|
||||
// 输出 "(1, 1) is inside the box"
|
||||
```
|
||||
|
||||

|
||||
|
||||
在上面的例子中,`switch`语句会判断某个点是否是原点(0, 0),是否在红色的x轴上,是否在黄色y轴上,是否在一个以原点为中心的4x4的矩形里,或者在这个矩形外面。
|
||||
|
||||
不像 C 语言,Swift 允许多个 case 匹配同一个值。实际上,在这个例子中,点(0, 0)可以匹配所有_四个 case_。但是,如果存在多个匹配,那么只会执行第一个被匹配到的 case 分支。考虑点(0, 0)会首先匹配`case (0, 0)`,因此剩下的能够匹配(0, 0)的 case 分支都会被忽视掉。
|
||||
|
||||
|
||||
<a name="value_bindings"></a>
|
||||
#### 值绑定(Value Bindings)
|
||||
|
||||
case 分支的模式允许将匹配的值绑定到一个临时的常量或变量,这些常量或变量在该 case 分支里就可以被引用了——这种行为被称为*值绑定*(value binding)。
|
||||
|
||||
下面的例子展示了如何在一个`(Int, Int)`类型的元组中使用值绑定来分类下图中的点(x, y):
|
||||
|
||||
```swift
|
||||
let anotherPoint = (2, 0)
|
||||
switch anotherPoint {
|
||||
case (let x, 0):
|
||||
print("on the x-axis with an x value of \(x)")
|
||||
case (0, let y):
|
||||
print("on the y-axis with a y value of \(y)")
|
||||
case let (x, y):
|
||||
print("somewhere else at (\(x), \(y))")
|
||||
}
|
||||
// 输出 "on the x-axis with an x value of 2"
|
||||
```
|
||||
|
||||

|
||||
|
||||
在上面的例子中,`switch`语句会判断某个点是否在红色的x轴上,是否在黄色y轴上,或者不在坐标轴上。
|
||||
|
||||
这三个 case 都声明了常量`x`和`y`的占位符,用于临时获取元组`anotherPoint`的一个或两个值。第一个 case ——`case (let x, 0)`将匹配一个纵坐标为`0`的点,并把这个点的横坐标赋给临时的常量`x`。类似的,第二个 case ——`case (0, let y)`将匹配一个横坐标为`0`的点,并把这个点的纵坐标赋给临时的常量`y`。
|
||||
|
||||
一旦声明了这些临时的常量,它们就可以在其对应的 case 分支里引用。在这个例子中,它们用于简化`print(_:separator:terminator:)`的书写。
|
||||
|
||||
请注意,这个`switch`语句不包含默认分支。这是因为最后一个 case ——`case let(x, y)`声明了一个可以匹配余下所有值的元组。这使得`switch`语句已经完备了,因此不需要再书写默认分支。
|
||||
|
||||
<a name="where"></a>
|
||||
#### Where
|
||||
|
||||
case 分支的模式可以使用`where`语句来判断额外的条件。
|
||||
|
||||
下面的例子把下图中的点(x, y)进行了分类:
|
||||
|
||||
```swift
|
||||
let yetAnotherPoint = (1, -1)
|
||||
switch yetAnotherPoint {
|
||||
case let (x, y) where x == y:
|
||||
print("(\(x), \(y)) is on the line x == y")
|
||||
case let (x, y) where x == -y:
|
||||
print("(\(x), \(y)) is on the line x == -y")
|
||||
case let (x, y):
|
||||
print("(\(x), \(y)) is just some arbitrary point")
|
||||
}
|
||||
// 输出 "(1, -1) is on the line x == -y"
|
||||
```
|
||||
|
||||

|
||||
|
||||
在上面的例子中,`switch`语句会判断某个点是否在绿色的对角线`x == y`上,是否在紫色的对角线`x == -y`上,或者不在对角线上。
|
||||
|
||||
这三个 case 都声明了常量`x`和`y`的占位符,用于临时获取元组`yetAnotherPoint`的两个值。这些常量被用作`where`语句的一部分,从而创建一个动态的过滤器(filter)。当且仅当`where`语句的条件为`true`时,匹配到的 case 分支才会被执行。
|
||||
|
||||
就像是值绑定中的例子,由于最后一个 case 分支匹配了余下所有可能的值,`switch`语句就已经完备了,因此不需要再书写默认分支。
|
||||
|
||||
<a name="control_transfer_statements"></a>
|
||||
## 控制转移语句(Control Transfer Statements)
|
||||
|
||||
控制转移语句改变你代码的执行顺序,通过它你可以实现代码的跳转。Swift 有五种控制转移语句:
|
||||
|
||||
- `continue`
|
||||
- `break`
|
||||
- `fallthrough`
|
||||
- `return`
|
||||
- `throw`
|
||||
|
||||
我们将会在下面讨论`continue`、`break`和`fallthrough`语句。`return`语句将会在[函数](./06_Functions.html)章节讨论,`throw`语句会在[错误抛出](./18_Error_Handling.html#throwing_errors)章节讨论。
|
||||
|
||||
<a name="continue"></a>
|
||||
### Continue
|
||||
|
||||
`continue`语句告诉一个循环体立刻停止本次循环迭代,重新开始下次循环迭代。就好像在说“本次循环迭代我已经执行完了”,但是并不会离开整个循环体。
|
||||
|
||||
> 注意:
|
||||
> 在一个带有条件和递增的 for 循环体中,调用`continue`语句后,迭代增量仍然会被计算求值。循环体继续像往常一样工作,仅仅只是循环体中的执行代码会被跳过。
|
||||
|
||||
下面的例子把一个小写字符串中的元音字母和空格字符移除,生成了一个含义模糊的短句:
|
||||
|
||||
```swift
|
||||
let puzzleInput = "great minds think alike"
|
||||
var puzzleOutput = ""
|
||||
for character in puzzleInput.characters {
|
||||
switch character {
|
||||
case "a", "e", "i", "o", "u", " ":
|
||||
continue
|
||||
default:
|
||||
puzzleOutput.append(character)
|
||||
}
|
||||
}
|
||||
print(puzzleOutput)
|
||||
// 输出 "grtmndsthnklk"
|
||||
```
|
||||
|
||||
在上面的代码中,只要匹配到元音字母或者空格字符,就调用`continue`语句,使本次循环迭代结束,从新开始下次循环迭代。这种行为使`switch`匹配到元音字母和空格字符时不做处理,而不是让每一个匹配到的字符都被打印。
|
||||
|
||||
<a name="break"></a>
|
||||
### Break
|
||||
|
||||
`break`语句会立刻结束整个控制流的执行。当你想要更早的结束一个`switch`代码块或者一个循环体时,你都可以使用`break`语句。
|
||||
|
||||
<a name="break_in_a_loop_statement"></a>
|
||||
#### 循环语句中的 break
|
||||
|
||||
当在一个循环体中使用`break`时,会立刻中断该循环体的执行,然后跳转到表示循环体结束的大括号(`}`)后的第一行代码。不会再有本次循环迭代的代码被执行,也不会再有下次的循环迭代产生。
|
||||
|
||||
<a name="break_in_a_switch_statement"></a>
|
||||
#### Switch 语句中的 break
|
||||
|
||||
当在一个`switch`代码块中使用`break`时,会立即中断该`switch`代码块的执行,并且跳转到表示`switch`代码块结束的大括号(`}`)后的第一行代码。
|
||||
|
||||
这种特性可以被用来匹配或者忽略一个或多个分支。因为 Swift 的`switch`需要包含所有的分支而且不允许有为空的分支,有时为了使你的意图更明显,需要特意匹配或者忽略某个分支。那么当你想忽略某个分支时,可以在该分支内写上`break`语句。当那个分支被匹配到时,分支内的`break`语句立即结束`switch`代码块。
|
||||
|
||||
>注意:
|
||||
当一个`switch`分支仅仅包含注释时,会被报编译时错误。注释不是代码语句而且也不能让`switch`分支达到被忽略的效果。你总是可以使用`break`来忽略某个分支。
|
||||
|
||||
下面的例子通过`switch`来判断一个`Character`值是否代表下面四种语言之一。为了简洁,多个值被包含在了同一个分支情况中。
|
||||
|
||||
```swift
|
||||
let numberSymbol: Character = "三" // 简体中文里的数字 3
|
||||
var possibleIntegerValue: Int?
|
||||
switch numberSymbol {
|
||||
case "1", "١", "一", "๑":
|
||||
possibleIntegerValue = 1
|
||||
case "2", "٢", "二", "๒":
|
||||
possibleIntegerValue = 2
|
||||
case "3", "٣", "三", "๓":
|
||||
possibleIntegerValue = 3
|
||||
case "4", "٤", "四", "๔":
|
||||
possibleIntegerValue = 4
|
||||
default:
|
||||
break
|
||||
}
|
||||
if let integerValue = possibleIntegerValue {
|
||||
print("The integer value of \(numberSymbol) is \(integerValue).")
|
||||
} else {
|
||||
print("An integer value could not be found for \(numberSymbol).")
|
||||
}
|
||||
// 输出 "The integer value of 三 is 3."
|
||||
```
|
||||
|
||||
这个例子检查`numberSymbol`是否是拉丁,阿拉伯,中文或者泰语中的`1`到`4`之一。如果被匹配到,该`switch`分支语句给`Int?`类型变量`possibleIntegerValue`设置一个整数值。
|
||||
|
||||
当`switch`代码块执行完后,接下来的代码通过使用可选绑定来判断`possibleIntegerValue`是否曾经被设置过值。因为是可选类型的缘故,`possibleIntegerValue`有一个隐式的初始值`nil`,所以仅仅当`possibleIntegerValue`曾被`switch`代码块的前四个分支中的某个设置过一个值时,可选的绑定将会被判定为成功。
|
||||
|
||||
在上面的例子中,想要把`Character`所有的的可能性都枚举出来是不现实的,所以使用`default`分支来包含所有上面没有匹配到字符的情况。由于这个`default`分支不需要执行任何动作,所以它只写了一条`break`语句。一旦落入到`default`分支中后,`break`语句就完成了该分支的所有代码操作,代码继续向下,开始执行`if let`语句。
|
||||
|
||||
<a name="fallthrough"></a>
|
||||
### 贯穿(Fallthrough)
|
||||
|
||||
Swift 中的`switch`不会从上一个 case 分支落入到下一个 case 分支中。相反,只要第一个匹配到的 case 分支完成了它需要执行的语句,整个`switch`代码块完成了它的执行。相比之下,C 语言要求你显式地插入`break`语句到每个`switch`分支的末尾来阻止自动落入到下一个 case 分支中。Swift 的这种避免默认落入到下一个分支中的特性意味着它的`switch` 功能要比 C 语言的更加清晰和可预测,可以避免无意识地执行多个 case 分支从而引发的错误。
|
||||
|
||||
如果你确实需要 C 风格的贯穿的特性,你可以在每个需要该特性的 case 分支中使用`fallthrough`关键字。下面的例子使用`fallthrough`来创建一个数字的描述语句。
|
||||
|
||||
```swift
|
||||
let integerToDescribe = 5
|
||||
var description = "The number \(integerToDescribe) is"
|
||||
switch integerToDescribe {
|
||||
case 2, 3, 5, 7, 11, 13, 17, 19:
|
||||
description += " a prime number, and also"
|
||||
fallthrough
|
||||
default:
|
||||
description += " an integer."
|
||||
}
|
||||
print(description)
|
||||
// 输出 "The number 5 is a prime number, and also an integer."
|
||||
```
|
||||
|
||||
这个例子定义了一个`String`类型的变量`description`并且给它设置了一个初始值。函数使用`switch`逻辑来判断`integerToDescribe`变量的值。当`integerToDescribe`的值属于列表中的质数之一时,该函数添加一段文字在`description`后,来表明这个是数字是一个质数。然后它使用`fallthrough`关键字来“贯穿”到`default`分支中。`default`分支添加一段额外的文字在`description`的最后,至此`switch`代码块执行完了。
|
||||
|
||||
如果`integerToDescribe`的值不属于列表中的任何质数,那么它不会匹配到第一个`switch`分支。而这里没有其他特别的分支情况,所以`integerToDescribe`匹配到包含所有的`default`分支中。
|
||||
|
||||
当`switch`代码块执行完后,使用`print(_:separator:terminator:)`函数打印该数字的描述。在这个例子中,数字`5`被准确的识别为了一个质数。
|
||||
|
||||
> 注意:
|
||||
> `fallthrough`关键字不会检查它下一个将会落入执行的 case 中的匹配条件。`fallthrough`简单地使代码执行继续连接到下一个 case 中的执行代码,这和 C 语言标准中的`switch`语句特性是一样的。
|
||||
|
||||
<a name="labeled_statements"></a>
|
||||
### 带标签的语句
|
||||
|
||||
在 Swift 中,你可以在循环体和`switch`代码块中嵌套循环体和`switch`代码块来创造复杂的控制流结构。然而,循环体和`switch`代码块两者都可以使用`break`语句来提前结束整个方法体。因此,显式地指明`break`语句想要终止的是哪个循环体或者`switch`代码块,会很有用。类似地,如果你有许多嵌套的循环体,显式指明`continue`语句想要影响哪一个循环体也会非常有用。
|
||||
|
||||
为了实现这个目的,你可以使用标签来标记一个循环体或者`switch`代码块,当使用`break`或者`continue`时,带上这个标签,可以控制该标签代表对象的中断或者执行。
|
||||
|
||||
产生一个带标签的语句是通过在该语句的关键词的同一行前面放置一个标签,并且该标签后面还需带着一个冒号。下面是一个`while`循环体的语法,同样的规则适用于所有的循环体和`switch`代码块。
|
||||
|
||||
> `label name`: while `condition` {
|
||||
> `statements`
|
||||
> }
|
||||
|
||||
下面的例子是在一个带有标签的`while`循环体中调用`break`和`continue`语句,该循环体是前面章节中*蛇和梯子*的改编版本。这次,游戏增加了一条额外的规则:
|
||||
|
||||
- 为了获胜,你必须*刚好*落在第 25 个方块中。
|
||||
|
||||
如果某次掷骰子使你的移动超出第 25 个方块,你必须重新掷骰子,直到你掷出的骰子数刚好使你能落在第 25 个方块中。
|
||||
|
||||
游戏的棋盘和之前一样:
|
||||
|
||||

|
||||
|
||||
`finalSquare`、`board`、`square`和`diceRoll`值被和之前一样的方式初始化:
|
||||
|
||||
```swift
|
||||
let finalSquare = 25
|
||||
var board = [Int](count: finalSquare + 1, repeatedValue: 0)
|
||||
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
|
||||
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
|
||||
var square = 0
|
||||
var diceRoll = 0
|
||||
```
|
||||
|
||||
这个版本的游戏使用`while`循环体和`switch`方法块来实现游戏的逻辑。`while`循环体有一个标签名`gameLoop`,来表明它是蛇与梯子的主循环。
|
||||
|
||||
该`while`循环体的条件判断语句是`while square !=finalSquare`,这表明你必须刚好落在方格25中。
|
||||
|
||||
```swift
|
||||
gameLoop: while square != finalSquare {
|
||||
if ++diceRoll == 7 { diceRoll = 1 }
|
||||
switch square + diceRoll {
|
||||
case finalSquare:
|
||||
// 到达最后一个方块,游戏结束
|
||||
break gameLoop
|
||||
case let newSquare where newSquare > finalSquare:
|
||||
// 超出最后一个方块,再掷一次骰子
|
||||
continue gameLoop
|
||||
default:
|
||||
// 本次移动有效
|
||||
square += diceRoll
|
||||
square += board[square]
|
||||
}
|
||||
}
|
||||
print("Game over!")
|
||||
```
|
||||
|
||||
每次循环迭代开始时掷骰子。与之前玩家掷完骰子就立即移动不同,这里使用了`switch`来考虑每次移动可能产生的结果,从而决定玩家本次是否能够移动。
|
||||
|
||||
- 如果骰子数刚好使玩家移动到最终的方格里,游戏结束。`break gameLoop`语句跳转控制去执行`while`循环体后的第一行代码,游戏结束。
|
||||
- 如果骰子数将会使玩家的移动超出最后的方格,那么这种移动是不合法的,玩家需要重新掷骰子。`continue gameLoop`语句结束本次`while`循环的迭代,开始下一次循环迭代。
|
||||
- 在剩余的所有情况中,骰子数产生的都是合法的移动。玩家向前移动骰子数个方格,然后游戏逻辑再处理玩家当前是否处于蛇头或者梯子的底部。本次循环迭代结束,控制跳转到`while`循环体的条件判断语句处,再决定是否能够继续执行下次循环迭代。
|
||||
|
||||
>注意:
|
||||
如果上述的`break`语句没有使用`gameLoop`标签,那么它将会中断`switch`代码块而不是`while`循环体。使用`gameLoop`标签清晰的表明了`break`想要中断的是哪个代码块。
|
||||
同时请注意,当调用`continue gameLoop`去跳转到下一次循环迭代时,这里使用`gameLoop`标签并不是严格必须的。因为在这个游戏中,只有一个循环体,所以`continue`语句会影响到哪个循环体是没有歧义的。然而,`continue`语句使用`gameLoop`标签也是没有危害的。这样做符合标签的使用规则,同时参照旁边的`break gameLoop`,能够使游戏的逻辑更加清晰和易于理解。
|
||||
|
||||
<a name="early_exit"></a>
|
||||
## 提前退出
|
||||
|
||||
像`if`语句一样,`guard`的执行取决于一个表达式的布尔值。我们可以使用`guard`语句来要求条件必须为真时,以执行`guard`语句后的代码。不同于`if`语句,一个`guard`语句总是有一个`else`分句,如果条件不为真则执行`else`分句中的代码。
|
||||
|
||||
```swift
|
||||
func greet(person: [String: String]) {
|
||||
guard let name = person["name"] else {
|
||||
return
|
||||
}
|
||||
print("Hello \(name)")
|
||||
|
||||
guard let location = person["location"] else {
|
||||
print("I hope the weather is nice near you.")
|
||||
return
|
||||
}
|
||||
print("I hope the weather is nice in \(location).")
|
||||
}
|
||||
greet(["name": "John"])
|
||||
// 输出 "Hello John!"
|
||||
// 输出 "I hope the weather is nice near you."
|
||||
greet(["name": "Jane", "location": "Cupertino"])
|
||||
// 输出 "Hello Jane!"
|
||||
// 输出 "I hope the weather is nice in Cupertino."
|
||||
```
|
||||
|
||||
如果`guard`语句的条件被满足,则在保护语句的封闭大括号结束后继续执行代码。任何使用了可选绑定作为条件的一部分并被分配了值的变量或常量对于剩下的保护语句出现的代码段是可用的。
|
||||
|
||||
如果条件不被满足,在`else`分支上的代码就会被执行。这个分支必须转移控制以退出`guard`语句出现的代码段。它可以用控制转移语句如`return`,`break`,`continue`或者`throw`做这件事,或者调用一个不返回的方法或函数,例如`fatalError()`。
|
||||
|
||||
相比于可以实现同样功能的`if`语句,按需使用`guard`语句会提升我们代码的可靠性。它可以使你的代码连贯的被执行而不需要将它包在`else`块中,它可以使你处理违反要求的代码接近要求。
|
||||
|
||||
<a name="checking_api_availability"></a>
|
||||
## 检测 API 可用性
|
||||
|
||||
Swift 有检查 API 可用性的内置支持,这可以确保我们不会不小心地使用对于当前部署目标不可用的 API。
|
||||
|
||||
编译器使用 SDK 中的可用信息来验证我们的代码中使用的所有 API 在项目指定的部署目标上是否可用。如果我们尝试使用一个不可用的 API,Swift 会在编译时报错。
|
||||
|
||||
我们使用一个可用性条件在一个`if`或`guard`语句中去有条件的执行一段代码,这取决于我们想要使用的 API 是否在运行时是可用的。编译器使用从可用性条件语句中获取的信息去验证在代码块中调用的 API 是否都可用。
|
||||
|
||||
```swift
|
||||
if #available(iOS 9, OSX 10.10, *) {
|
||||
// 在 iOS 使用 iOS 9 的 API, 在 OS X 使用 OS X v10.10 的 API
|
||||
} else {
|
||||
// 使用先前版本的 iOS 和 OS X 的 API
|
||||
}
|
||||
```
|
||||
|
||||
以上可用性条件指定在 iOS,`if`段的代码仅仅在 iOS 9 及更高可运行;在 OS X,仅在 OS X v10.10 及更高可运行。最后一个参数,`*`,是必须的并且指定在任何其他平台上,`if`段的代码在最小可用部署目标指定项目中执行。
|
||||
|
||||
在它普遍的形式中,可用性条件获取了平台名字和版本的清单。平台名字可以是`iOS`,`OSX`或`watchOS`。除了特定的主板本号像 iOS 8,我们可以指定较小的版本号像iOS 8.3 以及 OS X v10.10.3。
|
||||
|
||||
```swift
|
||||
if #available(`platform name` `version`, `...`, *) {
|
||||
`statements to execute if the APIs are available`
|
||||
} else {
|
||||
`fallback statements to execute if the APIs are unavailable`
|
||||
}
|
||||
```
|
||||
@ -1,578 +0,0 @@
|
||||
# 函数(Functions)
|
||||
-----------------
|
||||
|
||||
> 1.0
|
||||
> 翻译:[honghaoz](https://github.com/honghaoz)
|
||||
> 校对:[LunaticM](https://github.com/LunaticM)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[dreamkidd](https://github.com/dreamkidd)
|
||||
|
||||
> 2.1
|
||||
> 翻译:[DianQK](https://github.com/DianQK)
|
||||
> 定稿:[shanks](http://codebuild.me)
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [函数定义与调用(Defining and Calling Functions)](#Defining_and_Calling_Functions)
|
||||
- [函数参数与返回值(Function Parameters and Return Values)](#Function_Parameters_and_Return_Values)
|
||||
- [函数参数名称(Function Parameter Names)](#Function_Parameter_Names)
|
||||
- [函数类型(Function Types)](#Function_Types)
|
||||
- [嵌套函数(Nested Functions)](#Nested_Functions)
|
||||
|
||||
*函数*是用来完成特定任务的独立的代码块。你给一个函数起一个合适的名字,用来标识函数做什么,并且当函数需要执行的时候,这个名字会被用于“调用”函数。
|
||||
|
||||
Swift 统一的函数语法足够灵活,可以用来表示任何函数,包括从最简单的没有参数名字的 C 风格函数,到复杂的带局部和外部参数名的 Objective-C 风格函数。参数可以提供默认值,以简化函数调用。参数也可以既当做传入参数,也当做传出参数,也就是说,一旦函数执行结束,传入的参数值可以被修改。
|
||||
|
||||
在 Swift 中,每个函数都有一种类型,包括函数的参数值类型和返回值类型。你可以把函数类型当做任何其他普通变量类型一样处理,这样就可以更简单地把函数当做别的函数的参数,也可以从其他函数中返回函数。函数的定义可以写在其他函数定义中,这样可以在嵌套函数范围内实现功能封装。
|
||||
|
||||
<a name="Defining_and_Calling_Functions"></a>
|
||||
## 函数的定义与调用(Defining and Calling Functions)
|
||||
|
||||
当你定义一个函数时,你可以定义一个或多个有名字和类型的值,作为函数的输入(称为*参数,parameters*),也可以定义某种类型的值作为函数执行结束的输出(称为*返回类型,return type*)。
|
||||
|
||||
每个函数有个*函数名*,用来描述函数执行的任务。要使用一个函数时,你用函数名“调用”,并传给它匹配的输入值(称作*实参,arguments*)。一个函数的实参必须与函数参数表里参数的顺序一致。
|
||||
|
||||
在下面例子中的函数叫做`"sayHello(_:)"`,之所以叫这个名字,是因为这个函数用一个人的名字当做输入,并返回给这个人的问候语。为了完成这个任务,你定义一个输入参数-一个叫做 `personName` 的 `String` 值,和一个包含给这个人问候语的 `String` 类型的返回值:
|
||||
|
||||
```swift
|
||||
func sayHello(personName: String) -> String {
|
||||
let greeting = "Hello, " + personName + "!"
|
||||
return greeting
|
||||
}
|
||||
```
|
||||
|
||||
所有的这些信息汇总起来成为函数的*定义*,并以 `func` 作为前缀。指定函数返回类型时,用返回箭头 `->`(一个连字符后跟一个右尖括号)后跟返回类型的名称的方式来表示。
|
||||
|
||||
该定义描述了函数做什么,它期望接收什么和执行结束时它返回的结果是什么类型。这样的定义使得函数可以在别的地方以一种清晰的方式被调用:
|
||||
|
||||
```swift
|
||||
print(sayHello("Anna"))
|
||||
// prints "Hello, Anna!"
|
||||
print(sayHello("Brian"))
|
||||
// prints "Hello, Brian!"
|
||||
```
|
||||
|
||||
调用 `sayHello(_:)` 函数时,在圆括号中传给它一个 `String` 类型的实参,例如 `sayHello("Anna")`。因为这个函数返回一个 `String` 类型的值,`sayHello` 可以被包含在 `print(_:separator:terminator:)` 的调用中,用来输出这个函数的返回值,正如上面所示。
|
||||
|
||||
在 `sayHello(_:)` 的函数体中,先定义了一个新的名为 `greeting` 的 `String` 常量,同时,把对 `personName` 的问候消息赋值给了 `greeting` 。然后用 `return` 关键字把这个问候返回出去。一旦 `return greeting` 被调用,该函数结束它的执行并返回 `greeting` 的当前值。
|
||||
|
||||
你可以用不同的输入值多次调用 `sayHello(_:)`。上面的例子展示的是用`"Anna"`和`"Brian"`调用的结果,该函数分别返回了不同的结果。
|
||||
|
||||
为了简化这个函数的定义,可以将问候消息的创建和返回写成一句:
|
||||
|
||||
```swift
|
||||
func sayHelloAgain(personName: String) -> String {
|
||||
return "Hello again, " + personName + "!"
|
||||
}
|
||||
print(sayHelloAgain("Anna"))
|
||||
// prints "Hello again, Anna!"
|
||||
```
|
||||
|
||||
<a name="Function_Parameters_and_Return_Values"></a>
|
||||
## 函数参数与返回值(Function Parameters and Return Values)
|
||||
|
||||
函数参数与返回值在 Swift 中极为灵活。你可以定义任何类型的函数,包括从只带一个未名参数的简单函数到复杂的带有表达性参数名和不同参数选项的复杂函数。
|
||||
|
||||
### 无参函数(Functions Without Parameters)
|
||||
|
||||
函数可以没有参数。下面这个函数就是一个无参函数,当被调用时,它返回固定的 `String` 消息:
|
||||
|
||||
```swift
|
||||
func sayHelloWorld() -> String {
|
||||
return "hello, world"
|
||||
}
|
||||
print(sayHelloWorld())
|
||||
// prints "hello, world"
|
||||
```
|
||||
|
||||
尽管这个函数没有参数,但是定义中在函数名后还是需要一对圆括号。当被调用时,也需要在函数名后写一对圆括号。
|
||||
|
||||
### 多参数函数 (Functions With Multiple Parameters)
|
||||
|
||||
函数可以有多种输入参数,这些参数被包含在函数的括号之中,以逗号分隔。
|
||||
|
||||
这个函数用一个人名和是否已经打过招呼作为输入,并返回对这个人的适当问候语:
|
||||
|
||||
```swift
|
||||
func sayHello(personName: String, alreadyGreeted: Bool) -> String {
|
||||
if alreadyGreeted {
|
||||
return sayHelloAgain(personName)
|
||||
} else {
|
||||
return sayHello(personName)
|
||||
}
|
||||
}
|
||||
print(sayHello("Tim", alreadyGreeted: true))
|
||||
// prints "Hello again, Tim!"
|
||||
```
|
||||
|
||||
你通过在括号内传递一个`String`参数值和一个标识为`alreadyGreeted`的`Bool`值,使用逗号分隔来调用`sayHello(_:alreadyGreeted:)`函数。
|
||||
|
||||
当调用超过一个参数的函数时,第一个参数后的参数根据其对应的参数名称标记,函数参数命名在[函数参数名称(Function Parameter Names)](#Function_Parameter_Names)有更详细的描述。
|
||||
|
||||
<a name="functions_without_return_values"></a>
|
||||
### 无返回值函数(Functions Without Return Values)
|
||||
|
||||
函数可以没有返回值。下面是 `sayHello(_:)` 函数的另一个版本,叫 `sayGoodbye(_:)`,这个函数直接输出 `String` 值,而不是返回它:
|
||||
|
||||
```swift
|
||||
func sayGoodbye(personName: String) {
|
||||
print("Goodbye, \(personName)!")
|
||||
}
|
||||
sayGoodbye("Dave")
|
||||
// prints "Goodbye, Dave!"
|
||||
```
|
||||
|
||||
因为这个函数不需要返回值,所以这个函数的定义中没有返回箭头(->)和返回类型。
|
||||
|
||||
> 注意
|
||||
> 严格上来说,虽然没有返回值被定义,`sayGoodbye(_:)` 函数依然返回了值。没有定义返回类型的函数会返回特殊的值,叫 `Void`。它其实是一个空的元组(tuple),没有任何元素,可以写成`()`。
|
||||
|
||||
被调用时,一个函数的返回值可以被忽略:
|
||||
|
||||
```swift
|
||||
func printAndCount(stringToPrint: String) -> Int {
|
||||
print(stringToPrint)
|
||||
return stringToPrint.characters.count
|
||||
}
|
||||
func printWithoutCounting(stringToPrint: String) {
|
||||
printAndCount(stringToPrint)
|
||||
}
|
||||
printAndCount("hello, world")
|
||||
// prints "hello, world" and returns a value of 12
|
||||
printWithoutCounting("hello, world")
|
||||
// prints "hello, world" but does not return a value
|
||||
|
||||
```
|
||||
|
||||
第一个函数 `printAndCount(_:)`,输出一个字符串并返回 `Int` 类型的字符数。第二个函数 `printWithoutCounting`调用了第一个函数,但是忽略了它的返回值。当第二个函数被调用时,消息依然会由第一个函数输出,但是返回值不会被用到。
|
||||
|
||||
> 注意
|
||||
> 返回值可以被忽略,但定义了有返回值的函数必须返回一个值,如果在函数定义底部没有返回任何值,将导致编译错误(compile-time error)。
|
||||
|
||||
<a name="functions_with_multiple_return_values"></a>
|
||||
### 多重返回值函数(Functions with Multiple Return Values)
|
||||
|
||||
你可以用元组(tuple)类型让多个值作为一个复合值从函数中返回。
|
||||
|
||||
下面的这个例子中,定义了一个名为`minMax(_:)`的函数,作用是在一个`Int`数组中找出最小值与最大值。
|
||||
|
||||
```swift
|
||||
func minMax(array: [Int]) -> (min: Int, max: Int) {
|
||||
var currentMin = array[0]
|
||||
var currentMax = array[0]
|
||||
for value in array[1..<array.count] {
|
||||
if value < currentMin {
|
||||
currentMin = value
|
||||
} else if value > currentMax {
|
||||
currentMax = value
|
||||
}
|
||||
}
|
||||
return (currentMin, currentMax)
|
||||
}
|
||||
```
|
||||
|
||||
`minMax(_:)`函数返回一个包含两个`Int`值的元组,这些值被标记为`min`和`max`,以便查询函数的返回值时可以通过名字访问它们。
|
||||
|
||||
`minMax(_:)`的函数体中,在开始的时候设置两个工作变量`currentMin`和`currentMax`的值为数组中的第一个数。然后函数会遍历数组中剩余的值并检查该值是否比`currentMin`和`currentMax`更小或更大。最后数组中的最小值与最大值作为一个包含两个`Int`值的元组返回。
|
||||
|
||||
因为元组的成员值已被命名,因此可以通过点语法来检索找到的最小值与最大值:
|
||||
|
||||
```swift
|
||||
let bounds = minMax([8, -6, 2, 109, 3, 71])
|
||||
print("min is \(bounds.min) and max is \(bounds.max)")
|
||||
// prints "min is -6 and max is 109"
|
||||
```
|
||||
|
||||
需要注意的是,元组的成员不需要在元组从函数中返回时命名,因为它们的名字已经在函数返回类型中指定了。
|
||||
|
||||
<a name="optional_tuple_return_types"></a>
|
||||
###可选元组返回类型(Optional Tuple Return Types)
|
||||
|
||||
如果函数返回的元组类型有可能整个元组都“没有值”,你可以使用*可选的(Optional)* 元组返回类型反映整个元组可以是`nil`的事实。你可以通过在元组类型的右括号后放置一个问号来定义一个可选元组,例如`(Int, Int)?`或`(String, Int, Bool)?`
|
||||
|
||||
> 注意
|
||||
> 可选元组类型如`(Int, Int)?`与元组包含可选类型如`(Int?, Int?)`是不同的.可选的元组类型,整个元组是可选的,而不只是元组中的每个元素值。
|
||||
|
||||
前面的`minMax(_:)`函数返回了一个包含两个`Int`值的元组。但是函数不会对传入的数组执行任何安全检查,如果`array`参数是一个空数组,如上定义的`minMax(_:)`在试图访问`array[0]`时会触发一个运行时错误。
|
||||
|
||||
为了安全地处理这个“空数组”问题,将`minMax(_:)`函数改写为使用可选元组返回类型,并且当数组为空时返回`nil`:
|
||||
|
||||
```swift
|
||||
func minMax(array: [Int]) -> (min: Int, max: Int)? {
|
||||
if array.isEmpty { return nil }
|
||||
var currentMin = array[0]
|
||||
var currentMax = array[0]
|
||||
for value in array[1..<array.count] {
|
||||
if value < currentMin {
|
||||
currentMin = value
|
||||
} else if value > currentMax {
|
||||
currentMax = value
|
||||
}
|
||||
}
|
||||
return (currentMin, currentMax)
|
||||
}
|
||||
```
|
||||
|
||||
你可以使用可选绑定来检查`minMax(_:)`函数返回的是一个实际的元组值还是`nil`:
|
||||
|
||||
```swift
|
||||
if let bounds = minMax([8, -6, 2, 109, 3, 71]) {
|
||||
print("min is \(bounds.min) and max is \(bounds.max)")
|
||||
}
|
||||
// prints "min is -6 and max is 109"
|
||||
```
|
||||
|
||||
<a name="Function_Parameter_Names"></a>
|
||||
## 函数参数名称(Function Parameter Names)
|
||||
|
||||
函数参数都有一个*外部参数名(external parameter name)*和一个*局部参数名(local parameter name)*。外部参数名用于在函数调用时标注传递给函数的参数,局部参数名在函数的实现内部使用。
|
||||
|
||||
```swift
|
||||
func someFunction(firstParameterName: Int, secondParameterName: Int) {
|
||||
// function body goes here
|
||||
// firstParameterName and secondParameterName refer to
|
||||
// the argument values for the first and second parameters
|
||||
}
|
||||
someFunction(1, secondParameterName: 2)
|
||||
```
|
||||
|
||||
一般情况下,第一个参数省略其外部参数名,第二个以及随后的参数使用其局部参数名作为外部参数名。所有参数必须有独一无二的局部参数名。尽管多个参数可以有相同的外部参数名,但不同的外部参数名能让你的代码更有可读性。
|
||||
|
||||
<a name="specifying_external_parameter_names"></a>
|
||||
### 指定外部参数名(Specifying External Parameter Names)
|
||||
|
||||
你可以在局部参数名前指定外部参数名,中间以空格分隔:
|
||||
|
||||
```swift
|
||||
func someFunction(externalParameterName localParameterName: Int) {
|
||||
// function body goes here, and can use localParameterName
|
||||
// to refer to the argument value for that parameter
|
||||
}
|
||||
```
|
||||
|
||||
> 注意
|
||||
> 如果你提供了外部参数名,那么函数在被调用时,必须使用外部参数名。
|
||||
|
||||
这个版本的`sayHello(_:)`函数,接收两个人的名字,会同时返回对他俩的问候:
|
||||
|
||||
```swift
|
||||
func sayHello(to person: String, and anotherPerson: String) -> String {
|
||||
return "Hello \(person) and \(anotherPerson)!"
|
||||
}
|
||||
print(sayHello(to: "Bill", and: "Ted"))
|
||||
// prints "Hello Bill and Ted!"
|
||||
```
|
||||
|
||||
为每个参数指定外部参数名后,在你调用`sayHello(to:and:)`函数时两个外部参数名都必须写出来。
|
||||
|
||||
使用外部函数名可以使函数以一种更富有表达性的类似句子的方式调用,并使函数体意图清晰,更具可读性。
|
||||
|
||||
### 忽略外部参数名(Omitting External Parameter Names)
|
||||
|
||||
如果你不想为第二个及后续的参数设置外部参数名,用一个下划线(`_`)代替一个明确的参数名。
|
||||
|
||||
```swift
|
||||
func someFunction(firstParameterName: Int, _ secondParameterName: Int) {
|
||||
// function body goes here
|
||||
// firstParameterName and secondParameterName refer to
|
||||
// the argument values for the first and second parameters
|
||||
}
|
||||
someFunction(1, 2)
|
||||
```
|
||||
|
||||
> 注意
|
||||
> 因为第一个参数默认忽略其外部参数名称,显式地写下划线是多余的。
|
||||
|
||||
<a name="default_parameter_values"></a>
|
||||
### 默认参数值(Default Parameter Values)
|
||||
|
||||
你可以在函数体中为每个参数定义`默认值(Deafult Values)`。当默认值被定义后,调用这个函数时可以忽略这个参数。
|
||||
|
||||
```swift
|
||||
func someFunction(parameterWithDefault: Int = 12) {
|
||||
// function body goes here
|
||||
// if no arguments are passed to the function call,
|
||||
// value of parameterWithDefault is 12
|
||||
}
|
||||
someFunction(6) // parameterWithDefault is 6
|
||||
someFunction() // parameterWithDefault is 12
|
||||
```
|
||||
|
||||
> 注意
|
||||
> 将带有默认值的参数放在函数参数列表的最后。这样可以保证在函数调用时,非默认参数的顺序是一致的,同时使得相同的函数在不同情况下调用时显得更为清晰。
|
||||
|
||||
<a name="variadic_parameters"></a>
|
||||
### 可变参数(Variadic Parameters)
|
||||
|
||||
一个`可变参数(variadic parameter)`可以接受零个或多个值。函数调用时,你可以用可变参数来指定函数参数可以被传入不确定数量的输入值。通过在变量类型名后面加入`(...)`的方式来定义可变参数。
|
||||
|
||||
可变参数的传入值在函数体中变为此类型的一个数组。例如,一个叫做 `numbers` 的 `Double...` 型可变参数,在函数体内可以当做一个叫 `numbers` 的 `[Double]` 型的数组常量。
|
||||
|
||||
下面的这个函数用来计算一组任意长度数字的`算术平均数(arithmetic mean)`:
|
||||
|
||||
```swift
|
||||
func arithmeticMean(numbers: Double...) -> Double {
|
||||
var total: Double = 0
|
||||
for number in numbers {
|
||||
total += number
|
||||
}
|
||||
return total / Double(numbers.count)
|
||||
}
|
||||
arithmeticMean(1, 2, 3, 4, 5)
|
||||
// returns 3.0, which is the arithmetic mean of these five numbers
|
||||
arithmeticMean(3, 8.25, 18.75)
|
||||
// returns 10.0, which is the arithmetic mean of these three numbers
|
||||
```
|
||||
|
||||
> 注意
|
||||
> 一个函数最多只能有一个可变参数。
|
||||
|
||||
如果函数有一个或多个带默认值的参数,而且还有一个可变参数,那么把可变参数放在参数表的最后。
|
||||
|
||||
<a name="constant_and_variable_parameters"></a>
|
||||
### 常量参数和变量参数(Constant and Variable Parameters)
|
||||
|
||||
函数参数默认是常量。试图在函数体中更改参数值将会导致编译错误。这意味着你不能错误地更改参数值。
|
||||
|
||||
但是,有时候,如果函数中有传入参数的变量值副本将是很有用的。你可以通过指定一个或多个参数为变量参数,从而避免自己在函数中定义新的变量。变量参数不是常量,你可以在函数中把它当做新的可修改副本来使用。
|
||||
|
||||
通过在参数名前加关键字 `var` 来定义变量参数:
|
||||
|
||||
```swift
|
||||
func alignRight(var string: String, totalLength: Int, pad: Character) -> String {
|
||||
let amountToPad = totalLength - string.characters.count
|
||||
if amountToPad < 1 {
|
||||
return string
|
||||
}
|
||||
let padString = String(pad)
|
||||
for _ in 1...amountToPad {
|
||||
string = padString + string
|
||||
}
|
||||
return string
|
||||
}
|
||||
let originalString = "hello"
|
||||
let paddedString = alignRight(originalString, totalLength: 10, pad: "-")
|
||||
// paddedString is equal to "-----hello"
|
||||
// originalString is still equal to "hello"
|
||||
```
|
||||
|
||||
这个例子中定义了一个叫做 `alignRight(_:totalLength:pad:)` 的新函数,用来将输入的字符串对齐到更长的输出字符串的右边缘。左侧空余的地方用指定的填充字符填充。这个例子中,字符串`"hello"`被转换成了`"-----hello"`。
|
||||
|
||||
`alignRight(_:totalLength:pad:)` 函数将输入参数 `string` 定义为变量参数。这意味着 `string` 现在可以作为一个局部变量,被传入的字符串值初始化,并且可以在函数体中进行操作。
|
||||
|
||||
函数首先计算出有多少字符需要被添加到`string`的左边,从而将其在整个字符串中右对齐。这个值存储在一个称为`amountToPad`的本地常量。如果不需要填充(也就是说,如果`amountToPad`小于1),该函数简单地返回没有任何填充的输入值`string`。
|
||||
|
||||
否则,该函数用`pad`字符创建一个叫做`padString`的临时`String`常量,并将`amountToPad`个 `padString`添加到现有字符串的左边。(一个`String`值不能被添加到一个`Character`值上,所以`padString`常量用于确保`+`操作符两侧都是`String`值)。
|
||||
|
||||
> 注意
|
||||
> 对变量参数所进行的修改在函数调用结束后便消失了,并且对于函数体外是不可见的。变量参数仅仅存在于函数调用的生命周期中。
|
||||
|
||||
<a name="in_out_parameters"></a>
|
||||
### 输入输出参数(In-Out Parameters)
|
||||
|
||||
变量参数,正如上面所述,仅仅能在函数体内被更改。如果你想要一个函数可以修改参数的值,并且想要在这些修改在函数调用结束后仍然存在,那么就应该把这个参数定义为输入输出参数(In-Out Parameters)。
|
||||
|
||||
定义一个输入输出参数时,在参数定义前加 `inout` 关键字。一个输入输出参数有传入函数的值,这个值被函数修改,然后被传出函数,替换原来的值。想获取更多的关于输入输出参数的细节和相关的编译器优化,请查看[输入输出参数](../chapter3/05_Declarations.html#function_declaration)一节。
|
||||
|
||||
<!--上面的链接对应的内容没有更新翻译-->
|
||||
|
||||
你只能传递变量给输入输出参数。你不能传入常量或者字面量(literal value),因为这些量是不能被修改的。当传入的参数作为输入输出参数时,需要在参数名前加`&`符,表示这个值可以被函数修改。
|
||||
|
||||
> 注意
|
||||
> 输入输出参数不能有默认值,而且可变参数不能用 `inout` 标记。如果你用 `inout` 标记一个参数,这个参数不能被 `var` 或者 `let` 标记。
|
||||
|
||||
下面是例子,`swapTwoInts(_:_:)` 函数,有两个分别叫做 `a` 和 `b` 的输入输出参数:
|
||||
|
||||
```swift
|
||||
func swapTwoInts(inout a: Int, inout _ b: Int) {
|
||||
let temporaryA = a
|
||||
a = b
|
||||
b = temporaryA
|
||||
}
|
||||
```
|
||||
|
||||
这个 `swapTwoInts(_:_:)` 函数简单地交换 `a` 与 `b` 的值。该函数先将 `a` 的值存到一个临时常量 `temporaryA` 中,然后将 `b` 的值赋给 `a`,最后将 `temporaryA` 赋值给 `b`。
|
||||
|
||||
你可以用两个 `Int` 型的变量来调用 `swapTwoInts(_:_:)`。需要注意的是,`someInt` 和 `anotherInt` 在传入 `swapTwoInts(_:_:)` 函数前,都加了 `&` 的前缀:
|
||||
|
||||
```swift
|
||||
var someInt = 3
|
||||
var anotherInt = 107
|
||||
swapTwoInts(&someInt, &anotherInt)
|
||||
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
|
||||
// prints "someInt is now 107, and anotherInt is now 3"
|
||||
```
|
||||
|
||||
从上面这个例子中,我们可以看到 `someInt` 和 `anotherInt` 的原始值在 `swapTwoInts(_:_:)` 函数中被修改,尽管它们的定义在函数体外。
|
||||
|
||||
> 注意
|
||||
> 输入输出参数和返回值是不一样的。上面的 `swapTwoInts` 函数并没有定义任何返回值,但仍然修改了 `someInt` 和 `anotherInt` 的值。输入输出参数是函数对函数体外产生影响的另一种方式。
|
||||
|
||||
<a name="Function_Types"></a>
|
||||
## 函数类型(Function Types)
|
||||
|
||||
每个函数都有种特定的函数类型,由函数的参数类型和返回类型组成。
|
||||
|
||||
例如:
|
||||
|
||||
```swift
|
||||
func addTwoInts(a: Int, _ b: Int) -> Int {
|
||||
return a + b
|
||||
}
|
||||
func multiplyTwoInts(a: Int, _ b: Int) -> Int {
|
||||
return a * b
|
||||
}
|
||||
```
|
||||
|
||||
这个例子中定义了两个简单的数学函数:`addTwoInts` 和 `multiplyTwoInts`。这两个函数都接受两个 `Int` 值, 返回一个`Int`值。
|
||||
|
||||
这两个函数的类型是 `(Int, Int) -> Int`,可以解读为“这个函数类型有两个 `Int` 型的参数并返回一个 `Int` 型的值。”。
|
||||
|
||||
下面是另一个例子,一个没有参数,也没有返回值的函数:
|
||||
|
||||
```swift
|
||||
func printHelloWorld() {
|
||||
print("hello, world")
|
||||
}
|
||||
```
|
||||
|
||||
这个函数的类型是:`() -> Void`,或者叫“没有参数,并返回 `Void` 类型的函数”。
|
||||
|
||||
<a name="using_function_types"></a>
|
||||
### 使用函数类型(Using Function Types)
|
||||
|
||||
在 Swift 中,使用函数类型就像使用其他类型一样。例如,你可以定义一个类型为函数的常量或变量,并将适当的函数赋值给它:
|
||||
|
||||
```swift
|
||||
var mathFunction: (Int, Int) -> Int = addTwoInts
|
||||
```
|
||||
|
||||
这个可以解读为:
|
||||
|
||||
“定义一个叫做 `mathFunction` 的变量,类型是‘一个有两个 `Int` 型的参数并返回一个 `Int` 型的值的函数’,并让这个新变量指向 `addTwoInts` 函数”。
|
||||
|
||||
`addTwoInts` 和 `mathFunction` 有同样的类型,所以这个赋值过程在 Swift 类型检查中是允许的。
|
||||
|
||||
现在,你可以用 `mathFunction` 来调用被赋值的函数了:
|
||||
|
||||
```swift
|
||||
print("Result: \(mathFunction(2, 3))")
|
||||
// prints "Result: 5"
|
||||
```
|
||||
|
||||
有相同匹配类型的不同函数可以被赋值给同一个变量,就像非函数类型的变量一样:
|
||||
|
||||
```swift
|
||||
mathFunction = multiplyTwoInts
|
||||
print("Result: \(mathFunction(2, 3))")
|
||||
// prints "Result: 6"
|
||||
```
|
||||
|
||||
就像其他类型一样,当赋值一个函数给常量或变量时,你可以让 Swift 来推断其函数类型:
|
||||
|
||||
```swift
|
||||
let anotherMathFunction = addTwoInts
|
||||
// anotherMathFunction is inferred to be of type (Int, Int) -> Int
|
||||
```
|
||||
|
||||
<a name="function_types_as_parameter_types"></a>
|
||||
### 函数类型作为参数类型(Function Types as Parameter Types)
|
||||
|
||||
你可以用`(Int, Int) -> Int`这样的函数类型作为另一个函数的参数类型。这样你可以将函数的一部分实现留给函数的调用者来提供。
|
||||
|
||||
下面是另一个例子,正如上面的函数一样,同样是输出某种数学运算结果:
|
||||
|
||||
```swift
|
||||
func printMathResult(mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
|
||||
print("Result: \(mathFunction(a, b))")
|
||||
}
|
||||
printMathResult(addTwoInts, 3, 5)
|
||||
// prints "Result: 8"
|
||||
```
|
||||
|
||||
这个例子定义了 `printMathResult(_:_:_:)` 函数,它有三个参数:第一个参数叫 `mathFunction`,类型是`(Int, Int) -> Int`,你可以传入任何这种类型的函数;第二个和第三个参数叫 `a` 和 `b`,它们的类型都是 `Int`,这两个值作为已给出的函数的输入值。
|
||||
|
||||
当 `printMathResult(_:_:_:)` 被调用时,它被传入 `addTwoInts` 函数和整数`3`和`5`。它用传入`3`和`5`调用 `addTwoInts`,并输出结果:`8`。
|
||||
|
||||
`printMathResult(_:_:_:)` 函数的作用就是输出另一个适当类型的数学函数的调用结果。它不关心传入函数是如何实现的,它只关心这个传入的函数类型是正确的。这使得 `printMathResult(_:_:_:)` 能以一种类型安全(type-safe)的方式将一部分功能转给调用者实现。
|
||||
|
||||
<a name="function_types_as_return_types"></a>
|
||||
### 函数类型作为返回类型(Function Types as Return Types)
|
||||
|
||||
你可以用函数类型作为另一个函数的返回类型。你需要做的是在返回箭头(`->`)后写一个完整的函数类型。
|
||||
|
||||
下面的这个例子中定义了两个简单函数,分别是 `stepForward` 和`stepBackward`。`stepForward` 函数返回一个比输入值大一的值。`stepBackward` 函数返回一个比输入值小一的值。这两个函数的类型都是 `(Int) -> Int`:
|
||||
|
||||
```swift
|
||||
func stepForward(input: Int) -> Int {
|
||||
return input + 1
|
||||
}
|
||||
func stepBackward(input: Int) -> Int {
|
||||
return input - 1
|
||||
}
|
||||
```
|
||||
|
||||
下面这个叫做 `chooseStepFunction(_:)` 的函数,它的返回类型是 `(Int) -> Int` 类型的函数。`chooseStepFunction(_:)` 根据布尔值 `backwards` 来返回 `stepForward(_:)` 函数或 `stepBackward(_:)` 函数:
|
||||
|
||||
```swift
|
||||
func chooseStepFunction(backwards: Bool) -> (Int) -> Int {
|
||||
return backwards ? stepBackward : stepForward
|
||||
}
|
||||
```
|
||||
|
||||
你现在可以用 `chooseStepFunction(_:)` 来获得两个函数其中的一个:
|
||||
|
||||
```swift
|
||||
var currentValue = 3
|
||||
let moveNearerToZero = chooseStepFunction(currentValue > 0)
|
||||
// moveNearerToZero now refers to the stepBackward() function
|
||||
```
|
||||
|
||||
上面这个例子中计算出从 `currentValue` 逐渐接近到`0`是需要向正数走还是向负数走。`currentValue` 的初始值是`3`,这意味着 `currentValue > 0` 是真的(`true`),这将使得 `chooseStepFunction(_:)` 返回 `stepBackward(_:)` 函数。一个指向返回的函数的引用保存在了 `moveNearerToZero` 常量中。
|
||||
|
||||
现在,`moveNearerToZero` 指向了正确的函数,它可以被用来数到`0`:
|
||||
|
||||
```swift
|
||||
print("Counting to zero:")
|
||||
// Counting to zero:
|
||||
while currentValue != 0 {
|
||||
print("\(currentValue)... ")
|
||||
currentValue = moveNearerToZero(currentValue)
|
||||
}
|
||||
print("zero!")
|
||||
// 3...
|
||||
// 2...
|
||||
// 1...
|
||||
// zero!
|
||||
```
|
||||
|
||||
<a name="Nested_Functions"></a>
|
||||
## 嵌套函数(Nested Functions)
|
||||
|
||||
这章中你所见到的所有函数都叫全局函数(global functions),它们定义在全局域中。你也可以把函数定义在别的函数体中,称作嵌套函数(nested functions)。
|
||||
|
||||
默认情况下,嵌套函数是对外界不可见的,但是可以被它们的外围函数(enclosing function)调用。一个外围函数也可以返回它的某一个嵌套函数,使得这个函数可以在其他域中被使用。
|
||||
|
||||
你可以用返回嵌套函数的方式重写 `chooseStepFunction(_:)` 函数:
|
||||
|
||||
```swift
|
||||
func chooseStepFunction(backwards: Bool) -> (Int) -> Int {
|
||||
func stepForward(input: Int) -> Int { return input + 1 }
|
||||
func stepBackward(input: Int) -> Int { return input - 1 }
|
||||
return backwards ? stepBackward : stepForward
|
||||
}
|
||||
var currentValue = -4
|
||||
let moveNearerToZero = chooseStepFunction(currentValue > 0)
|
||||
// moveNearerToZero now refers to the nested stepForward() function
|
||||
while currentValue != 0 {
|
||||
print("\(currentValue)... ")
|
||||
currentValue = moveNearerToZero(currentValue)
|
||||
}
|
||||
print("zero!")
|
||||
// -4...
|
||||
// -3...
|
||||
// -2...
|
||||
// -1...
|
||||
// zero!
|
||||
```
|
||||
@ -1,455 +0,0 @@
|
||||
# 闭包(Closures)
|
||||
-----------------
|
||||
|
||||
> 1.0
|
||||
> 翻译:[wh1100717](https://github.com/wh1100717)
|
||||
> 校对:[lyuka](https://github.com/lyuka)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[100mango](https://github.com/100mango)
|
||||
|
||||
> 2.1
|
||||
> 翻译:[100mango](https://github.com/100mango), [magicdict](https://github.com/magicdict)
|
||||
> 校对:[shanks](http://codebuild.me)
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [闭包表达式(Closure Expressions)](#closure_expressions)
|
||||
- [尾随闭包(Trailing Closures)](#trailing_closures)
|
||||
- [值捕获(Capturing Values)](#capturing_values)
|
||||
- [闭包是引用类型(Closures Are Reference Types)](#closures_are_reference_types)
|
||||
- [非逃逸闭包(Nonescaping Closures) ](#nonescaping_closures)
|
||||
- [自动闭包(Autoclosures)](#autoclosures)
|
||||
|
||||
闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似。
|
||||
|
||||
闭包可以捕获和存储其所在上下文中任意常量和变量的引用。这就是所谓的闭合并包裹着这些常量和变量,俗称闭包。Swift 会为您管理在捕获过程中涉及到的所有内存操作。
|
||||
|
||||
> 注意
|
||||
> 如果您不熟悉捕获(capturing)这个概念也不用担心,您可以在[值捕获](#capturing_values)章节对其进行详细了解。
|
||||
|
||||
在[函数](./06_Functions.html)章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采取如下三种形式之一:
|
||||
|
||||
* 全局函数是一个有名字但不会捕获任何值的闭包
|
||||
* 嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包
|
||||
* 闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包
|
||||
|
||||
Swift 的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下:
|
||||
|
||||
* 利用上下文推断参数和返回值类型
|
||||
* 隐式返回单表达式闭包,即单表达式闭包可以省略`return`关键字
|
||||
* 参数名称缩写
|
||||
* 尾随(Trailing)闭包语法
|
||||
|
||||
<a name="closure_expressions"></a>
|
||||
## 闭包表达式(Closure Expressions)
|
||||
|
||||
|
||||
[嵌套函数](./06_Functions.html#nested_function)是一个在较复杂函数中方便进行命名和定义自包含代码模块的方式。当然,有时候撰写小巧的没有完整定义和命名的类函数结构也是很有用处的,尤其是在您处理一些函数并需要将另外一些函数作为该函数的参数时。
|
||||
|
||||
闭包表达式是一种利用简洁语法构建内联闭包的方式。闭包表达式提供了一些语法优化,使得撰写闭包变得简单明了。下面闭包表达式的例子通过使用几次迭代展示了`sort(_:)`方法定义和语法优化的方式。每一次迭代都用更简洁的方式描述了相同的功能。
|
||||
|
||||
<a name="the_sorted_function"></a>
|
||||
### sort 方法(The Sort Method)
|
||||
|
||||
Swift 标准库提供了名为`sort`的方法,会根据您提供的用于排序的闭包函数将已知类型数组中的值进行排序。一旦排序完成,`sort(_:)`方法会返回一个与原数组大小相同,包含同类型元素且元素已正确排序的新数组。原数组不会被`sort(_:)`方法修改。
|
||||
|
||||
下面的闭包表达式示例使用`sort(_:)`方法对一个`String`类型的数组进行字母逆序排序.以下是初始数组值:
|
||||
|
||||
```swift
|
||||
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
|
||||
```
|
||||
|
||||
`sort(_:)`方法接受一个闭包,该闭包函数需要传入与数组元素类型相同的两个值,并返回一个布尔类型值来表明当排序结束后传入的第一个参数排在第二个参数前面还是后面。如果第一个参数值出现在第二个参数值前面,排序闭包函数需要返回`true`,反之返回`false`。
|
||||
|
||||
该例子对一个`String`类型的数组进行排序,因此排序闭包函数类型需为`(String, String) -> Bool`。
|
||||
|
||||
提供排序闭包函数的一种方式是撰写一个符合其类型要求的普通函数,并将其作为`sort(_:)`方法的参数传入:
|
||||
|
||||
```swift
|
||||
func backwards(s1: String, s2: String) -> Bool {
|
||||
return s1 > s2
|
||||
}
|
||||
var reversed = names.sort(backwards)
|
||||
// reversed 为 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]
|
||||
```
|
||||
|
||||
如果第一个字符串(`s1`)大于第二个字符串(`s2`),`backwards(_:_:)`函数返回`true`,表示在新的数组中`s1`应该出现在`s2`前。对于字符串中的字符来说,“大于”表示“按照字母顺序较晚出现”。这意味着字母`"B"`大于字母`"A"`,字符串`"Tom"`大于字符串`"Tim"`。该闭包将进行字母逆序排序,`"Barry"`将会排在`"Alex"`之前。
|
||||
|
||||
然而,这是一个相当冗长的方式,本质上只是写了一个单表达式函数 (`a > b`)。在下面的例子中,利用闭包表达式语法可以更好地构造一个内联排序闭包。
|
||||
|
||||
<a name="closure_expression_syntax"></a>
|
||||
### 闭包表达式语法(Closure Expression Syntax)
|
||||
|
||||
闭包表达式语法有如下一般形式:
|
||||
|
||||
```swift
|
||||
{ (parameters) -> returnType in
|
||||
statements
|
||||
}
|
||||
```
|
||||
|
||||
闭包表达式语法可以使用常量、变量和`inout`类型作为参数,不能提供默认值。也可以在参数列表的最后使用可变参数。元组也可以作为参数和返回值。
|
||||
|
||||
下面的例子展示了之前`backwards(_:_:)`函数对应的闭包表达式版本的代码:
|
||||
|
||||
```swift
|
||||
reversed = names.sort({ (s1: String, s2: String) -> Bool in
|
||||
return s1 > s2
|
||||
})
|
||||
```
|
||||
|
||||
需要注意的是内联闭包参数和返回值类型声明与`backwards(_:_:)`函数类型声明相同。在这两种方式中,都写成了`(s1: String, s2: String) -> Bool`。然而在内联闭包表达式中,函数和返回值类型都写在大括号内,而不是大括号外。
|
||||
|
||||
闭包的函数体部分由关键字`in`引入。该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。
|
||||
|
||||
由于这个闭包的函数体部分如此短,以至于可以将其改写成一行代码:
|
||||
|
||||
```swift
|
||||
reversed = names.sort( { (s1: String, s2: String) -> Bool in return s1 > s2 } )
|
||||
```
|
||||
|
||||
该例中`sort(_:)`方法的整体调用保持不变,一对圆括号仍然包裹住了方法的整个参数。然而,参数现在变成了内联闭包。
|
||||
|
||||
<a name="inferring_type_from_context"></a>
|
||||
### 根据上下文推断类型(Inferring Type From Context)
|
||||
|
||||
因为排序闭包函数是作为`sort(_:)`方法的参数传入的,Swift 可以推断其参数和返回值的类型。`sort(_:)`方法被一个字符串数组调用,因此其参数必须是`(String, String) -> Bool`类型的函数。这意味着`(String, String)`和`Bool`类型并不需要作为闭包表达式定义的一部分。因为所有的类型都可以被正确推断,返回箭头(`->`)和围绕在参数周围的括号也可以被省略:
|
||||
|
||||
```swift
|
||||
reversed = names.sort( { s1, s2 in return s1 > s2 } )
|
||||
```
|
||||
|
||||
实际上任何情况下,通过内联闭包表达式构造的闭包作为参数传递给函数或方法时,都可以推断出闭包的参数和返回值类型。
|
||||
这意味着闭包作为函数或者方法的参数时,您几乎不需要利用完整格式构造内联闭包。
|
||||
|
||||
尽管如此,您仍然可以明确写出有着完整格式的闭包。如果完整格式的闭包能够提高代码的可读性,则可以采用完整格式的闭包。而在`sort(_:)`方法这个例子里,闭包的目的就是排序。由于这个闭包是为了处理字符串数组的排序,因此读者能够推测出这个闭包是用于字符串处理的。
|
||||
|
||||
<a name="implicit_returns_from_single_expression_closures"></a>
|
||||
### 单表达式闭包隐式返回(Implicit Return From Single-Expression Clossures)
|
||||
|
||||
单行表达式闭包可以通过省略`return`关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为:
|
||||
|
||||
```swift
|
||||
reversed = names.sort( { s1, s2 in s1 > s2 } )
|
||||
```
|
||||
|
||||
在这个例子中,`sort(_:)`方法的第二个参数函数类型明确了闭包必须返回一个`Bool`类型值。因为闭包函数体只包含了一个单一表达式(`s1 > s2`),该表达式返回`Bool`类型值,因此这里没有歧义,`return`关键字可以省略。
|
||||
|
||||
<a name="shorthand_argument_names"></a>
|
||||
### 参数名称缩写(Shorthand Argument Names)
|
||||
|
||||
Swift 自动为内联闭包提供了参数名称缩写功能,您可以直接通过`$0`,`$1`,`$2`来顺序调用闭包的参数,以此类推。
|
||||
|
||||
如果您在闭包表达式中使用参数名称缩写,您可以在闭包参数列表中省略对其的定义,并且对应参数名称缩写的类型会通过函数类型进行推断。`in`关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:
|
||||
|
||||
```swift
|
||||
reversed = names.sort( { $0 > $1 } )
|
||||
```
|
||||
|
||||
在这个例子中,`$0`和`$1`表示闭包中第一个和第二个`String`类型的参数。
|
||||
|
||||
<a name="operator_functions"></a>
|
||||
### 运算符函数(Operator Functions)
|
||||
|
||||
实际上还有一种更简短的方式来撰写上面例子中的闭包表达式。Swift 的`String`类型定义了关于大于号(`>`)的字符串实现,其作为一个函数接受两个`String`类型的参数并返回`Bool`类型的值。而这正好与`sort(_:)`方法的第二个参数需要的函数类型相符合。因此,您可以简单地传递一个大于号,Swift 可以自动推断出您想使用大于号的字符串函数实现:
|
||||
|
||||
```swift
|
||||
reversed = names.sort(>)
|
||||
```
|
||||
|
||||
更多关于运算符表达式的内容请查看[运算符函数](./25_Advanced_Operators.html#operator_functions)。
|
||||
|
||||
<a name="trailing_closures"></a>
|
||||
## 尾随闭包(Trailing Closures)
|
||||
|
||||
如果您需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用*尾随闭包*来增强函数的可读性。尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用:
|
||||
|
||||
```swift
|
||||
func someFunctionThatTakesAClosure(closure: () -> Void) {
|
||||
// 函数体部分
|
||||
}
|
||||
|
||||
// 以下是不使用尾随闭包进行函数调用
|
||||
someFunctionThatTakesAClosure({
|
||||
// 闭包主体部分
|
||||
})
|
||||
|
||||
// 以下是使用尾随闭包进行函数调用
|
||||
someFunctionThatTakesAClosure() {
|
||||
// 闭包主体部分
|
||||
}
|
||||
```
|
||||
|
||||
在[闭包表达式语法](#closure_expression_syntax)一节中作为`sort(_:)`方法参数的字符串排序闭包可以改写为:
|
||||
|
||||
```swift
|
||||
reversed = names.sort() { $0 > $1 }
|
||||
```
|
||||
|
||||
如果函数只需要闭包表达式一个参数,当您使用尾随闭包时,您甚至可以把`()`省略掉:
|
||||
|
||||
```swift
|
||||
reversed = names.sort { $0 > $1 }
|
||||
```
|
||||
|
||||
当闭包非常长以至于不能在一行中进行书写时,尾随闭包变得非常有用。举例来说,Swift 的`Array`类型有一个`map(_:)`方法,其获取一个闭包表达式作为其唯一参数。该闭包函数会为数组中的每一个元素调用一次,并返回该元素所映射的值。具体的映射方式和返回值类型由闭包来指定。
|
||||
|
||||
当提供给数组的闭包应用于每个数组元素后,`map(_:)`方法将返回一个新的数组,数组中包含了与原数组中的元素一一对应的映射后的值。
|
||||
|
||||
下例介绍了如何在`map(_:)`方法中使用尾随闭包将`Int`类型数组`[16, 58, 510]`转换为包含对应`String`类型的值的数组`["OneSix", "FiveEight", "FiveOneZero"]`:
|
||||
|
||||
```swift
|
||||
let digitNames = [
|
||||
0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four",
|
||||
5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
|
||||
]
|
||||
let numbers = [16, 58, 510]
|
||||
```
|
||||
|
||||
如上代码创建了一个数字位和它们英文版本名字相映射的字典。同时还定义了一个准备转换为字符串数组的整型数组。
|
||||
|
||||
您现在可以通过传递一个尾随闭包给`numbers`的`map(_:)`方法来创建对应的字符串版本数组:
|
||||
|
||||
```swift
|
||||
let strings = numbers.map {
|
||||
(var number) -> String in
|
||||
var output = ""
|
||||
while number > 0 {
|
||||
output = digitNames[number % 10]! + output
|
||||
number /= 10
|
||||
}
|
||||
return output
|
||||
}
|
||||
// strings 常量被推断为字符串类型数组,即 [String]
|
||||
// 其值为 ["OneSix", "FiveEight", "FiveOneZero"]
|
||||
```
|
||||
|
||||
`map(_:)`为数组中每一个元素调用了闭包表达式。您不需要指定闭包的输入参数`number`的类型,因为可以通过要映射的数组类型进行推断。
|
||||
|
||||
在该例中,闭包`number`参数被声明为一个变量参数(变量的具体描述请参看[常量参数和变量参数](./06_Functions.html#constant_and_variable_parameters)),因此可以在闭包函数体内对其进行修改,而不用再定义一个新的局部变量并将`number`的值赋值给它。闭包表达式指定了返回类型为`String`,以表明存储映射值的新数组类型为`String`。
|
||||
|
||||
闭包表达式在每次被调用的时候创建了一个叫做`output`的字符串并返回。其使用求余运算符(`number % 10`)计算最后一位数字并利用`digitNames`字典获取所映射的字符串。
|
||||
|
||||
> 注意
|
||||
> 字典`digitNames`下标后跟着一个叹号(`!`),因为字典下标返回一个可选值(optional value),表明该键不存在时会查找失败。在上例中,由于可以确定`number % 10`总是`digitNames`字典的有效下标,因此叹号可以用于强制解包 (force-unwrap) 存储在下标的可选类型的返回值中的`String`类型的值。
|
||||
|
||||
从`digitNames`字典中获取的字符串被添加到`output`的前部,逆序建立了一个字符串版本的数字。(在表达式`number % 10`中,如果`number`为`16`,则返回`6`,`58`返回`8`,`510`返回`0`。)
|
||||
|
||||
`number`变量之后除以`10`。因为其是整数,在计算过程中未除尽部分被忽略。因此`16`变成了`1`,`58`变成了`5`,`510`变成了`51`。
|
||||
|
||||
整个过程重复进行,直到`number /= 10`为`0`,这时闭包会将字符串`output`返回,而`map(_:)`方法则会将字符串添加到所映射的数组中。
|
||||
|
||||
在上面的例子中,通过尾随闭包语法,优雅地在函数后封装了闭包的具体功能,而不再需要将整个闭包包裹在`map(_:)`方法的括号内。
|
||||
|
||||
<a name="capturing_values"></a>
|
||||
## 捕获值(Capturing Values)
|
||||
|
||||
闭包可以在其被定义的上下文中*捕获*常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
|
||||
|
||||
Swift 中,可以捕获值的闭包的最简单形式是嵌套函数,也就是定义在其他函数的函数体内的函数。嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。
|
||||
|
||||
举个例子,这有一个叫做`makeIncrementor`的函数,其包含了一个叫做`incrementor`的嵌套函数。嵌套函数`incrementor()`从上下文中捕获了两个值,`runningTotal`和`amount`。捕获这些值之后,`makeIncrementor`将`incrementor`作为闭包返回。每次调用`incrementor`时,其会以`amount`作为增量增加`runningTotal`的值。
|
||||
|
||||
```swift
|
||||
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
|
||||
var runningTotal = 0
|
||||
func incrementor() -> Int {
|
||||
runningTotal += amount
|
||||
return runningTotal
|
||||
}
|
||||
return incrementor
|
||||
}
|
||||
```
|
||||
|
||||
`makeIncrementor`返回类型为`() -> Int`。这意味着其返回的是一个函数,而不是一个简单类型的值。该函数在每次调用时不接受参数,只返回一个`Int`类型的值。关于函数返回其他函数的内容,请查看[函数类型作为返回类型](./06_Functions.html#function_types_as_return_types)。
|
||||
|
||||
`makeIncrementer(forIncrement:)`函数定义了一个初始值为`0`的整型变量`runningTotal`,用来存储当前跑步总数。该值通过`incrementor`返回。
|
||||
|
||||
`makeIncrementer(forIncrement:)`有一个`Int`类型的参数,其外部参数名为`forIncrement`,内部参数名为`amount`,该参数表示每次`incrementor`被调用时`runningTotal`将要增加的量。
|
||||
|
||||
嵌套函数`incrementor`用来执行实际的增加操作。该函数简单地使`runningTotal`增加`amount`,并将其返回。
|
||||
|
||||
如果我们单独看这个函数,会发现看上去不同寻常:
|
||||
|
||||
```swift
|
||||
func incrementor() -> Int {
|
||||
runningTotal += amount
|
||||
return runningTotal
|
||||
}
|
||||
```
|
||||
|
||||
`incrementer()`函数并没有任何参数,但是在函数体内访问了`runningTotal`和`amount`变量。这是因为它从外围函数捕获了`runningTotal`和`amount`变量的引用。捕获引用保证了`runningTotal`和`amount`变量在调用完`makeIncrementer`后不会消失,并且保证了在下一次执行`incrementer`函数时,`runningTotal`依旧存在。
|
||||
|
||||
> 注意
|
||||
> 为了优化,如果一个值是不可变的,Swift 可能会改为捕获并保存一份对值的拷贝。
|
||||
> Swift 也会负责被捕获变量的所有内存管理工作,包括释放不再需要的变量。
|
||||
|
||||
下面是一个使用`makeIncrementor`的例子:
|
||||
|
||||
```swift
|
||||
let incrementByTen = makeIncrementor(forIncrement: 10)
|
||||
```
|
||||
|
||||
该例子定义了一个叫做`incrementByTen`的常量,该常量指向一个每次调用会将`runningTotal`变量增加`10`的`incrementor`函数。调用这个函数多次可以得到以下结果:
|
||||
|
||||
```swift
|
||||
incrementByTen()
|
||||
// 返回的值为10
|
||||
incrementByTen()
|
||||
// 返回的值为20
|
||||
incrementByTen()
|
||||
// 返回的值为30
|
||||
```
|
||||
|
||||
如果您创建了另一个`incrementor`,它会有属于它自己的一个全新、独立的`runningTotal`变量的引用:
|
||||
|
||||
```swift
|
||||
let incrementBySeven = makeIncrementor(forIncrement: 7)
|
||||
incrementBySeven()
|
||||
// 返回的值为7
|
||||
```
|
||||
|
||||
再次调用原来的`incrementByTen`会在原来的变量`runningTotal`上继续增加值,该变量和`incrementBySeven`中捕获的变量没有任何联系:
|
||||
|
||||
```swift
|
||||
incrementByTen()
|
||||
// 返回的值为40
|
||||
```
|
||||
|
||||
> 注意
|
||||
> 如果您将闭包赋值给一个类实例的属性,并且该闭包通过访问该实例或其成员而捕获了该实例,您将创建一个在闭包和该实例间的循环强引用。Swift 使用捕获列表来打破这种循环强引用。更多信息,请参考[闭包引起的循环强引用](./16_Automatic_Reference_Counting.html#strong_reference_cycles_for_closures)。
|
||||
|
||||
<a name="closures_are_reference_types"></a>
|
||||
## 闭包是引用类型(Closures Are Reference Types)
|
||||
|
||||
上面的例子中,`incrementBySeven`和`incrementByTen`是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量的值。这是因为函数和闭包都是*引用类型*。
|
||||
|
||||
无论您将函数或闭包赋值给一个常量还是变量,您实际上都是将常量或变量的值设置为对应函数或闭包的引用。上面的例子中,指向闭包的引用`incrementByTen`是一个常量,而并非闭包内容本身。
|
||||
|
||||
这也意味着如果您将闭包赋值给了两个不同的常量或变量,两个值都会指向同一个闭包:
|
||||
|
||||
```swift
|
||||
let alsoIncrementByTen = incrementByTen
|
||||
alsoIncrementByTen()
|
||||
// 返回的值为50
|
||||
```
|
||||
|
||||
<a name="nonescaping_closures"></a>
|
||||
## 非逃逸闭包(Nonescaping Closures)
|
||||
|
||||
当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中*逃逸*。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注`@noescape`,用来指明这个闭包是不允许“逃逸”出这个函数的。将闭包标注`@noescape`能使编译器知道这个闭包的生命周期(译者注:闭包只能在函数体中被执行,不能脱离函数体执行,所以编译器明确知道运行时的上下文),从而可以进行一些比较激进的优化。
|
||||
|
||||
```swift
|
||||
func someFunctionWithNoescapeClosure(@noescape closure: () -> Void) {
|
||||
closure()
|
||||
}
|
||||
```
|
||||
|
||||
举个例子,`sort(_:)`方法接受一个用来进行元素比较的闭包作为参数。这个参数被标注了`@noescape`,因为它确保自己在排序结束之后就没用了。
|
||||
|
||||
一种能使闭包“逃逸”出函数的方法是,将这个闭包保存在一个函数外部定义的变量中。举个例子,很多启动异步操作的函数接受一个闭包参数作为 completion handler。这类函数会在异步操作开始之后立刻返回,但是闭包直到异步操作结束后才会被调用。在这种情况下,闭包需要“逃逸”出函数,因为闭包需要在函数返回之后被调用。例如:
|
||||
|
||||
```swift
|
||||
var completionHandlers: [() -> Void] = []
|
||||
func someFunctionWithEscapingClosure(completionHandler: () -> Void) {
|
||||
completionHandlers.append(completionHandler)
|
||||
}
|
||||
```
|
||||
|
||||
`someFunctionWithEscapingClosure(_:)`函数接受一个闭包作为参数,该闭包被添加到一个函数外定义的数组中。如果你试图将这个参数标注为`@noescape`,你将会获得一个编译错误。
|
||||
|
||||
将闭包标注为`@noescape`使你能在闭包中隐式地引用`self`。
|
||||
|
||||
```swift
|
||||
class SomeClass {
|
||||
var x = 10
|
||||
func doSomething() {
|
||||
someFunctionWithEscapingClosure { self.x = 100 }
|
||||
someFunctionWithNoescapeClosure { x = 200 }
|
||||
}
|
||||
}
|
||||
|
||||
let instance = SomeClass()
|
||||
instance.doSomething()
|
||||
print(instance.x)
|
||||
// prints "200"
|
||||
|
||||
completionHandlers.first?()
|
||||
print(instance.x)
|
||||
// prints "100"
|
||||
```
|
||||
|
||||
<a name="autoclosures"></a>
|
||||
## 自动闭包(Autoclosures)
|
||||
|
||||
*自动闭包*是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。这种便利语法让你能够用一个普通的表达式来代替显式的闭包,从而省略闭包的花括号。
|
||||
|
||||
我们经常会调用一个接受闭包作为参数的函数,但是很少实现那样的函数。举个例子来说,`assert(condition:message:file:line:)`函数接受闭包作为它的`condition`参数和`message`参数;它的`condition`参数仅会在 debug 模式下被求值,它的`message`参数仅当`condition`参数为`false`时被计算求值。
|
||||
|
||||
自动闭包让你能够延迟求值,因为代码段不会被执行直到你调用这个闭包。延迟求值对于那些有副作用(Side Effect)和代价昂贵的代码来说是很有益处的,因为你能控制代码什么时候执行。下面的代码展示了闭包如何延时求值。
|
||||
|
||||
```swift
|
||||
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
|
||||
print(customersInLine.count)
|
||||
// prints "5"
|
||||
|
||||
let customerProvider = { customersInLine.removeAtIndex(0) }
|
||||
print(customersInLine.count)
|
||||
// prints "5"
|
||||
|
||||
print("Now serving \(customerProvider())!")
|
||||
// prints "Now serving Chris!"
|
||||
print(customersInLine.count)
|
||||
// prints "4"
|
||||
```
|
||||
|
||||
尽管在闭包的代码中,`customersInLine`的第一个元素被移除了,不过在闭包被调用之前,这个元素是不会被移除的。如果这个闭包永远不被调用,那么在闭包里面的表达式将永远不会执行,那意味着列表中的元素永远不会被移除。请注意,`customerProvider`的类型不是`String`,而是`() -> String`,一个没有参数且返回值为`String`的函数。
|
||||
|
||||
将闭包作为参数传递给函数时,你能获得同样的延时求值行为。
|
||||
|
||||
```swift
|
||||
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
|
||||
func serveCustomer(customerProvider: () -> String) {
|
||||
print("Now serving \(customerProvider())!")
|
||||
}
|
||||
serveCustomer( { customersInLine.removeAtIndex(0) } )
|
||||
// prints "Now serving Alex!"
|
||||
```
|
||||
|
||||
`serveCustomer(_:)`接受一个返回顾客名字的显式的闭包。下面这个版本的`serveCustomer(_:)`完成了相同的操作,不过它并没有接受一个显式的闭包,而是通过将参数标记为`@autoclosure`来接收一个自动闭包。现在你可以将该函数当做接受`String`类型参数的函数来调用。`customerProvider`参数将自动转化为一个闭包,因为该参数被标记了`@autoclosure`特性。
|
||||
|
||||
```swift
|
||||
// customersInLine is ["Ewa", "Barry", "Daniella"]
|
||||
func serveCustomer(@autoclosure customerProvider: () -> String) {
|
||||
print("Now serving \(customerProvider())!")
|
||||
}
|
||||
serveCustomer(customersInLine.removeAtIndex(0))
|
||||
// prints "Now serving Ewa!"
|
||||
```
|
||||
|
||||
> 注意
|
||||
> 过度使用`autoclosures`会让你的代码变得难以理解。上下文和函数名应该能够清晰地表明求值是被延迟执行的。
|
||||
|
||||
`@autoclosure`特性暗含了`@noescape`特性,这个特性在[非逃逸闭包](#nonescaping_closures)一节中有描述。如果你想让这个闭包可以“逃逸”,则应该使用`@autoclosure(escaping)`特性.
|
||||
|
||||
```swift
|
||||
// customersInLine is ["Barry", "Daniella"]
|
||||
var customerProviders: [() -> String] = []
|
||||
func collectCustomerProviders(@autoclosure(escaping) customerProvider: () -> String) {
|
||||
customerProviders.append(customerProvider)
|
||||
}
|
||||
collectCustomerProviders(customersInLine.removeAtIndex(0))
|
||||
collectCustomerProviders(customersInLine.removeAtIndex(0))
|
||||
|
||||
print("Collected \(customerProviders.count) closures.")
|
||||
// prints "Collected 2 closures."
|
||||
for customerProvider in customerProviders {
|
||||
print("Now serving \(customerProvider())!")
|
||||
}
|
||||
// prints "Now serving Barry!"
|
||||
// prints "Now serving Daniella!"
|
||||
```
|
||||
|
||||
在上面的代码中,`collectCustomerProviders(_:)`函数并没有调用传入的`customerProvider`闭包,而是将闭包追加到了`customerProviders`数组中。这个数组定义在函数作用域范围外,这意味着数组内的闭包将会在函数返回之后被调用。因此,`customerProvider`参数必须允许“逃逸”出函数作用域。
|
||||
|
||||
@ -1,347 +0,0 @@
|
||||
# 枚举(Enumerations)
|
||||
---
|
||||
|
||||
> 1.0
|
||||
> 翻译:[yankuangshi](https://github.com/yankuangshi)
|
||||
> 校对:[shinyzhu](https://github.com/shinyzhu)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[futantan](https://github.com/futantan)
|
||||
|
||||
> 2.1
|
||||
> 翻译:[Channe](https://github.com/Channe)
|
||||
> 校对:[shanks](http://codebuild.me)
|
||||
|
||||
本页内容包含:
|
||||
|
||||
- [枚举语法(Enumeration Syntax)](#enumeration_syntax)
|
||||
- [使用 Switch 语句匹配枚举值(Matching Enumeration Values with a Switch Statement)](#matching_enumeration_values_with_a_switch_statement)
|
||||
- [关联值(Associated Values)](#associated_values)
|
||||
- [原始值(Raw Values)](#raw_values)
|
||||
- [递归枚举(Recursive Enumerations)](#recursive_enumerations)
|
||||
|
||||
*枚举*为一组相关的值定义了一个共同的类型,使你可以在你的代码中以类型安全的方式来使用这些值。
|
||||
|
||||
如果你熟悉 C 语言,你会知道在 C 语言中,枚举会为一组整型值分配相关联的名称。Swift 中的枚举更加灵活,不必给每一个枚举成员提供一个值。如果给枚举成员提供一个值(称为“原始”值),则该值的类型可以是字符串,字符,或是一个整型值或浮点数。
|
||||
|
||||
此外,枚举成员可以指定任意类型的关联值存储到枚举成员中,就像其他语言中的联合体(unions)和变体(variants)。每一个枚举成员都可以有适当类型的关联值。
|
||||
|
||||
在 Swift 中,枚举类型是一等(first-class)类型。它们采用了很多在传统上只被类(class)所支持的特性,例如计算型属性(computed properties),用于提供枚举值的附加信息,实例方法(instance methods),用于提供和枚举值相关联的功能。枚举也可以定义构造函数(initializers)来提供一个初始值;可以在原始实现的基础上扩展它们的功能;还可以遵守协议(protocols)来提供标准的功能。
|
||||
|
||||
欲了解更多相关信息,请参见[属性(Properties)](./10_Properties.html),[方法(Methods)](./11_Methods.html),[构造过程(Initialization)](./14_Initialization.html),[扩展(Extensions)](./21_Extensions.html)和[协议(Protocols)](./22_Protocols.html)。
|
||||
|
||||
<a name="enumeration_syntax"></a>
|
||||
## 枚举语法
|
||||
|
||||
使用`enum`关键词来创建枚举并且把它们的整个定义放在一对大括号内:
|
||||
|
||||
```swift
|
||||
enum SomeEnumeration {
|
||||
// 枚举定义放在这里
|
||||
}
|
||||
```
|
||||
|
||||
下面是用枚举表示指南针四个方向的例子:
|
||||
|
||||
```swift
|
||||
enum CompassPoint {
|
||||
case North
|
||||
case South
|
||||
case East
|
||||
case West
|
||||
}
|
||||
```
|
||||
|
||||
枚举中定义的值(如 `North`,`South`,`East`和`West`)是这个枚举的*成员值*(或*成员*)。你使用`case`关键字来定义一个新的枚举成员值。
|
||||
|
||||
> 注意
|
||||
> 与 C 和 Objective-C 不同,Swift 的枚举成员在被创建时不会被赋予一个默认的整型值。在上面的`CompassPoint`例子中,`North`,`South`,`East`和`West`不会被隐式地赋值为`0`,`1`,`2`和`3`。相反,这些枚举成员本身就是完备的值,这些值的类型是已经明确定义好的`CompassPoint`类型。
|
||||
|
||||
多个成员值可以出现在同一行上,用逗号隔开:
|
||||
|
||||
```swift
|
||||
enum Planet {
|
||||
case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
|
||||
}
|
||||
```
|
||||
|
||||
每个枚举定义了一个全新的类型。像 Swift 中其他类型一样,它们的名字(例如`CompassPoint`和`Planet`)应该以一个大写字母开头。给枚举类型起一个单数名字而不是复数名字,以便于读起来更加容易理解:
|
||||
|
||||
```swift
|
||||
var directionToHead = CompassPoint.West
|
||||
```
|
||||
|
||||
`directionToHead`的类型可以在它被`CompassPoint`的某个值初始化时推断出来。一旦`directionToHead`被声明为`CompassPoint`类型,你可以使用更简短的点语法将其设置为另一个`CompassPoint`的值:
|
||||
|
||||
```swift
|
||||
directionToHead = .East
|
||||
```
|
||||
|
||||
当`directionToHead`的类型已知时,再次为其赋值可以省略枚举类型名。在使用具有显式类型的枚举值时,这种写法让代码具有更好的可读性。
|
||||
|
||||
<a name="matching_enumeration_values_with_a_switch_statement"></a>
|
||||
## 使用 Switch 语句匹配枚举值
|
||||
|
||||
你可以使用`switch`语句匹配单个枚举值:
|
||||
|
||||
```swift
|
||||
directionToHead = .South
|
||||
switch directionToHead {
|
||||
case .North:
|
||||
print("Lots of planets have a north")
|
||||
case .South:
|
||||
print("Watch out for penguins")
|
||||
case .East:
|
||||
print("Where the sun rises")
|
||||
case .West:
|
||||
print("Where the skies are blue")
|
||||
}
|
||||
// 输出 "Watch out for penguins”
|
||||
```
|
||||
|
||||
你可以这样理解这段代码:
|
||||
|
||||
“判断`directionToHead`的值。当它等于`.North`,打印`“Lots of planets have a north”`。当它等于`.South`,打印`“Watch out for penguins”`。”
|
||||
|
||||
……以此类推。
|
||||
|
||||
正如在[控制流(Control Flow)](./05_Control_Flow.html)中介绍的那样,在判断一个枚举类型的值时,`switch`语句必须穷举所有情况。如果忽略了`.West`这种情况,上面那段代码将无法通过编译,因为它没有考虑到`CompassPoint`的全部成员。强制穷举确保了枚举成员不会被意外遗漏。
|
||||
|
||||
当不需要匹配每个枚举成员的时候,你可以提供一个`default`分支来涵盖所有未明确处理的枚举成员:
|
||||
|
||||
```swift
|
||||
let somePlanet = Planet.Earth
|
||||
switch somePlanet {
|
||||
case .Earth:
|
||||
print("Mostly harmless")
|
||||
default:
|
||||
print("Not a safe place for humans")
|
||||
}
|
||||
// 输出 "Mostly harmless”
|
||||
```
|
||||
|
||||
<a name="associated_values"></a>
|
||||
## 关联值(Associated Values)
|
||||
|
||||
上一小节的例子演示了如何定义和分类枚举的成员。你可以为`Planet.Earth`设置一个常量或者变量,并在赋值之后查看这个值。然而,有时候能够把其他类型的*关联值*和成员值一起存储起来会很有用。这能让你连同成员值一起存储额外的自定义信息,并且你每次在代码中使用该枚举成员时,还可以修改这个关联值。
|
||||
|
||||
你可以定义 Swift 枚举来存储任意类型的关联值,如果需要的话,每个枚举成员的关联值类型可以各不相同。枚举的这种特性跟其他语言中的可识别联合(discriminated unions),标签联合(tagged unions),或者变体(variants)相似。
|
||||
|
||||
例如,假设一个库存跟踪系统需要利用两种不同类型的条形码来跟踪商品。有些商品上标有使用`0`到`9`的数字的 UPC-A 格式的一维条形码。每一个条形码都有一个代表“数字系统”的数字,该数字后接五位代表“厂商代码”的数字,接下来是五位代表“产品代码”的数字。最后一个数字是“检查”位,用来验证代码是否被正确扫描:
|
||||
|
||||
<img width="252" height="120" alt="" src="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/barcode_UPC_2x.png">
|
||||
|
||||
其他商品上标有 QR 码格式的二维码,它可以使用任何 ISO 8859-1 字符,并且可以编码一个最多拥有 2,953 个字符的字符串:
|
||||
|
||||
<img width="169" height="169" alt="" src="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/barcode_QR_2x.png">
|
||||
|
||||
这便于库存跟踪系统用包含四个整型值的元组存储 UPC-A 码,以及用任意长度的字符串储存 QR 码。
|
||||
|
||||
在 Swift 中,使用如下方式定义表示两种商品条形码的枚举:
|
||||
|
||||
```swift
|
||||
enum Barcode {
|
||||
case UPCA(Int, Int, Int, Int)
|
||||
case QRCode(String)
|
||||
}
|
||||
```
|
||||
|
||||
以上代码可以这么理解:
|
||||
|
||||
“定义一个名为`Barcode`的枚举类型,它的一个成员值是具有`(Int,Int,Int,Int)`类型关联值的`UPCA`,另一个成员值是具有`String`类型关联值的`QRCode`。”
|
||||
|
||||
这个定义不提供任何`Int`或`String`类型的关联值,它只是定义了,当`Barcode`常量和变量等于`Barcode.UPCA`或`Barcode.QRCode`时,可以存储的关联值的类型。
|
||||
|
||||
然后可以使用任意一种条形码类型创建新的条形码,例如:
|
||||
|
||||
```swift
|
||||
var productBarcode = Barcode.UPCA(8, 85909, 51226, 3)
|
||||
```
|
||||
|
||||
上面的例子创建了一个名为`productBarcode`的变量,并将`Barcode.UPCA`赋值给它,关联的元组值为`(8, 85909, 51226, 3)`。
|
||||
|
||||
同一个商品可以被分配一个不同类型的条形码,例如:
|
||||
|
||||
```swift
|
||||
productBarcode = .QRCode("ABCDEFGHIJKLMNOP")
|
||||
```
|
||||
|
||||
这时,原始的`Barcode.UPCA`和其整数关联值被新的`Barcode.QRCode`和其字符串关联值所替代。`Barcode`类型的常量和变量可以存储一个`.UPCA`或者一个`.QRCode`(连同它们的关联值),但是在同一时间只能存储这两个值中的一个。
|
||||
|
||||
像先前那样,可以使用一个 switch 语句来检查不同的条形码类型。然而,这一次,关联值可以被提取出来作为 switch 语句的一部分。你可以在`switch`的 case 分支代码中提取每个关联值作为一个常量(用`let`前缀)或者作为一个变量(用`var`前缀)来使用:
|
||||
|
||||
```swift
|
||||
switch productBarcode {
|
||||
case .UPCA(let numberSystem, let manufacturer, let product, let check):
|
||||
print("UPC-A: \(numberSystem), \(manufacturer), \(product), \(check).")
|
||||
case .QRCode(let productCode):
|
||||
print("QR code: \(productCode).")
|
||||
}
|
||||
// 输出 "QR code: ABCDEFGHIJKLMNOP."
|
||||
```
|
||||
|
||||
如果一个枚举成员的所有关联值都被提取为常量,或者都被提取为变量,为了简洁,你可以只在成员名称前标注一个`let`或者`var`:
|
||||
|
||||
```swift
|
||||
switch productBarcode {
|
||||
case let .UPCA(numberSystem, manufacturer, product, check):
|
||||
print("UPC-A: \(numberSystem), \(manufacturer), \(product), \(check).")
|
||||
case let .QRCode(productCode):
|
||||
print("QR code: \(productCode).")
|
||||
}
|
||||
// 输出 "QR code: ABCDEFGHIJKLMNOP."
|
||||
```
|
||||
|
||||
<a name="raw_values"></a>
|
||||
## 原始值(Raw Values)
|
||||
|
||||
在[关联值](#associated_values)小节的条形码例子中,演示了如何声明存储不同类型关联值的枚举成员。作为关联值的替代选择,枚举成员可以被默认值(称为*原始值*)预填充,这些原始值的类型必须相同。
|
||||
|
||||
这是一个使用 ASCII 码作为原始值的枚举:
|
||||
|
||||
```swift
|
||||
enum ASCIIControlCharacter: Character {
|
||||
case Tab = "\t"
|
||||
case LineFeed = "\n"
|
||||
case CarriageReturn = "\r"
|
||||
}
|
||||
```
|
||||
|
||||
枚举类型`ASCIIControlCharacter`的原始值类型被定义为`Character`,并设置了一些比较常见的 ASCII 控制字符。`Character`的描述详见[字符串和字符](./03_Strings_and_Characters.html)部分。
|
||||
|
||||
|
||||
原始值可以是字符串,字符,或者任意整型值或浮点型值。每个原始值在枚举声明中必须是唯一的。
|
||||
|
||||
> 注意
|
||||
> 原始值和关联值是不同的。原始值是在定义枚举时被预先填充的值,像上述三个 ASCII 码。对于一个特定的枚举成员,它的原始值始终不变。关联值是创建一个基于枚举成员的常量或变量时才设置的值,枚举成员的关联值可以变化。
|
||||
|
||||
<a name="implicitly_assigned_raw_values"></a>
|
||||
### 原始值的隐式赋值(Implicitly Assigned Raw Values)
|
||||
|
||||
在使用原始值为整数或者字符串类型的枚举时,不需要显式地为每一个枚举成员设置原始值,Swift 将会自动为你赋值。
|
||||
|
||||
例如,当使用整数作为原始值时,隐式赋值的值依次递增`1`。如果第一个枚举成员没有设置原始值,其原始值将为`0`。
|
||||
|
||||
下面的枚举是对之前`Planet`这个枚举的一个细化,利用整型的原始值来表示每个行星在太阳系中的顺序:
|
||||
|
||||
```swift
|
||||
enum Planet: Int {
|
||||
case Mercury = 1, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
|
||||
}
|
||||
```
|
||||
|
||||
在上面的例子中,`Plant.Mercury`的显式原始值为`1`,`Planet.Venus`的隐式原始值为`2`,依次类推。
|
||||
|
||||
当使用字符串作为枚举类型的原始值时,每个枚举成员的隐式原始值为该枚举成员的名称。
|
||||
|
||||
下面的例子是`CompassPoint`枚举的细化,使用字符串类型的原始值来表示各个方向的名称:
|
||||
|
||||
```swift
|
||||
enum CompassPoint: String {
|
||||
case North, South, East, West
|
||||
}
|
||||
```
|
||||
|
||||
上面例子中,`CompassPoint.South`拥有隐式原始值`South`,依次类推。
|
||||
|
||||
使用枚举成员的`rawValue`属性可以访问该枚举成员的原始值:
|
||||
|
||||
```swift
|
||||
let earthsOrder = Planet.Earth.rawValue
|
||||
// earthsOrder 值为 3
|
||||
|
||||
let sunsetDirection = CompassPoint.West.rawValue
|
||||
// sunsetDirection 值为 "West"
|
||||
```
|
||||
|
||||
<a name="initializing_from_a_raw_value"></a>
|
||||
### 使用原始值初始化枚举实例(Initializing from a Raw Value)
|
||||
|
||||
如果在定义枚举类型的时候使用了原始值,那么将会自动获得一个初始化方法,这个方法接收一个叫做`rawValue`的参数,参数类型即为原始值类型,返回值则是枚举成员或`nil`。你可以使用这个初始化方法来创建一个新的枚举实例。
|
||||
|
||||
这个例子利用原始值`7`创建了枚举成员`Uranus`:
|
||||
|
||||
```swift
|
||||
let possiblePlanet = Planet(rawValue: 7)
|
||||
// possiblePlanet 类型为 Planet? 值为 Planet.Uranus
|
||||
```
|
||||
|
||||
然而,并非所有`Int`值都可以找到一个匹配的行星。因此,原始值构造器总是返回一个*可选*的枚举成员。在上面的例子中,`possiblePlanet`是`Planet?`类型,或者说“可选的`Planet`”。
|
||||
|
||||
> 注意
|
||||
> 原始值构造器是一个可失败构造器,因为并不是每一个原始值都有与之对应的枚举成员。更多信息请参见[可失败构造器](../chapter3/05_Declarations.html#failable_initializers)
|
||||
|
||||
如果你试图寻找一个位置为`9`的行星,通过原始值构造器返回的可选`Planet`值将是`nil`:
|
||||
|
||||
```swift
|
||||
let positionToFind = 9
|
||||
if let somePlanet = Planet(rawValue: positionToFind) {
|
||||
switch somePlanet {
|
||||
case .Earth:
|
||||
print("Mostly harmless")
|
||||
default:
|
||||
print("Not a safe place for humans")
|
||||
}
|
||||
} else {
|
||||
print("There isn't a planet at position \(positionToFind)")
|
||||
}
|
||||
// 输出 "There isn't a planet at position 9
|
||||
```
|
||||
|
||||
这个例子使用了可选绑定(optional binding),试图通过原始值`9`来访问一个行星。`if let somePlanet = Planet(rawValue: 9)`语句创建了一个可选`Planet`,如果可选`Planet`的值存在,就会赋值给`somePlanet`。在这个例子中,无法检索到位置为`9`的行星,所以`else`分支被执行。
|
||||
|
||||
<a name="recursive_enumerations"></a>
|
||||
## 递归枚举(Recursive Enumerations)
|
||||
|
||||
当各种可能的情况可以被穷举时,非常适合使用枚举进行数据建模,例如可以用枚举来表示用于简单整数运算的操作符。这些操作符让你可以将简单的算术表达式,例如整数`5`,结合为更为复杂的表达式,例如`5 + 4`。
|
||||
|
||||
算术表达式的一个重要特性是,表达式可以嵌套使用。例如,表达式`(5 + 4) * 2`,乘号右边是一个数字,左边则是另一个表达式。因为数据是嵌套的,因而用来存储数据的枚举类型也需要支持这种嵌套——这意味着枚举类型需要支持递归。
|
||||
|
||||
*递归枚举(recursive enumeration)*是一种枚举类型,它有一个或多个枚举成员使用该枚举类型的实例作为关联值。使用递归枚举时,编译器会插入一个间接层。你可以在枚举成员前加上`indirect`来表示该成员可递归。
|
||||
|
||||
例如,下面的例子中,枚举类型存储了简单的算术表达式:
|
||||
|
||||
```swift
|
||||
enum ArithmeticExpression {
|
||||
case Number(Int)
|
||||
indirect case Addition(ArithmeticExpression, ArithmeticExpression)
|
||||
indirect case Multiplication(ArithmeticExpression, ArithmeticExpression)
|
||||
}
|
||||
```
|
||||
|
||||
你也可以在枚举类型开头加上`indirect`关键字来表明它的所有成员都是可递归的:
|
||||
|
||||
```swift
|
||||
indirect enum ArithmeticExpression {
|
||||
case Number(Int)
|
||||
case Addition(ArithmeticExpression, ArithmeticExpression)
|
||||
case Multiplication(ArithmeticExpression, ArithmeticExpression)
|
||||
}
|
||||
```
|
||||
|
||||
上面定义的枚举类型可以存储三种算术表达式:纯数字、两个表达式相加、两个表达式相乘。枚举成员`Addition`和`Multiplication`的关联值也是算术表达式——这些关联值使得嵌套表达式成为可能。
|
||||
|
||||
要操作具有递归性质的数据结构,使用递归函数是一种直截了当的方式。例如,下面是一个对算术表达式求值的函数:
|
||||
|
||||
```swift
|
||||
func evaluate(expression: ArithmeticExpression) -> Int {
|
||||
switch expression {
|
||||
case .Number(let value):
|
||||
return value
|
||||
case .Addition(let left, let right):
|
||||
return evaluate(left) + evaluate(right)
|
||||
case .Multiplication(let left, let right):
|
||||
return evaluate(left) * evaluate(right)
|
||||
}
|
||||
}
|
||||
|
||||
// 计算 (5 + 4) * 2
|
||||
let five = ArithmeticExpression.Number(5)
|
||||
let four = ArithmeticExpression.Number(4)
|
||||
let sum = ArithmeticExpression.Addition(five, four)
|
||||
let product = ArithmeticExpression.Multiplication(sum, ArithmeticExpression.Number(2))
|
||||
print(evaluate(product))
|
||||
// 输出 "18"
|
||||
```
|
||||
|
||||
该函数如果遇到纯数字,就直接返回该数字的值。如果遇到的是加法或乘法运算,则分别计算左边表达式和右边表达式的值,然后相加或相乘。
|
||||
@ -1,301 +0,0 @@
|
||||
# 类和结构体(Classes and Structures)
|
||||
|
||||
> 1.0
|
||||
> 翻译:[JaySurplus](https://github.com/JaySurplus)
|
||||
> 校对:[sg552](https://github.com/sg552)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[SkyJean](https://github.com/SkyJean)
|
||||
|
||||
> 2.1
|
||||
> 校对:[shanks](http://codebuild.me),2015-10-29
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [类和结构体对比](#comparing_classes_and_structures)
|
||||
- [结构体和枚举是值类型](#structures_and_enumerations_are_value_types)
|
||||
- [类是引用类型](#classes_are_reference_types)
|
||||
- [类和结构体的选择](#choosing_between_classes_and_structures)
|
||||
- [字符串(String)、数组(Array)、和字典(Dictionary)类型的赋值与复制行为](#assignment_and_copy_behavior_for_strings_arrays_and_dictionaries)
|
||||
|
||||
|
||||
*类*和*结构体*是人们构建代码所用的一种通用且灵活的构造体。我们可以使用完全相同的语法规则来为类和结构体定义属性(常量、变量)和添加方法,从而扩展类和结构体的功能。
|
||||
|
||||
与其他编程语言所不同的是,Swift 并不要求你为自定义类和结构去创建独立的接口和实现文件。你所要做的是在一个单一文件中定义一个类或者结构体,系统将会自动生成面向其它代码的外部接口。
|
||||
|
||||
> 注意
|
||||
> 通常一个`类`的实例被称为`对象`。然而在 Swift 中,类和结构体的关系要比在其他语言中更加的密切,本章中所讨论的大部分功能都可以用在类和结构体上。因此,我们会主要使用`实例`而不是`对象`。
|
||||
|
||||
<a name="comparing_classes_and_structures"></a>
|
||||
###类和结构体对比
|
||||
|
||||
Swift 中类和结构体有很多共同点。共同处在于:
|
||||
|
||||
* 定义属性用于存储值
|
||||
* 定义方法用于提供功能
|
||||
* 定义附属脚本用于访问值
|
||||
* 定义构造器用于生成初始化值
|
||||
* 通过扩展以增加默认实现的功能
|
||||
* 实现协议以提供某种标准功能
|
||||
|
||||
更多信息请参见[属性](./10_Properties.html),[方法](./11_Methods.html),[下标](./12_Subscripts.html),[构造过程](./14_Initialization.html),[扩展](./21_Extensions.html),和[协议](./22_Protocols.html)。
|
||||
|
||||
与结构体相比,类还有如下的附加功能:
|
||||
|
||||
* 继承允许一个类继承另一个类的特征
|
||||
* 类型转换允许在运行时检查和解释一个类实例的类型
|
||||
* 析构器允许一个类实例释放任何其所被分配的资源
|
||||
* 引用计数允许对一个类的多次引用
|
||||
|
||||
更多信息请参见[继承](./13_Inheritance.html),[类型转换](./19_Type_Casting.html),[析构过程](./15_Deinitialization.html),和[自动引用计数](./16_Automatic_Reference_Counting.html)。
|
||||
|
||||
> 注意
|
||||
> 结构体总是通过被复制的方式在代码中传递,不使用引用计数。
|
||||
|
||||
<a name="definition_syntax"></a>
|
||||
### 定义语法
|
||||
|
||||
类和结构体有着类似的定义方式。我们通过关键字`class`和`struct`来分别表示类和结构体,并在一对大括号中定义它们的具体内容:
|
||||
|
||||
```swift
|
||||
class SomeClass {
|
||||
// class definition goes here
|
||||
}
|
||||
struct SomeStructure {
|
||||
// structure definition goes here
|
||||
}
|
||||
```
|
||||
|
||||
> 注意
|
||||
> 在你每次定义一个新类或者结构体的时候,实际上你是定义了一个新的 Swift 类型。因此请使用`UpperCamelCase`这种方式来命名(如`SomeClass`和`SomeStructure`等),以便符合标准 Swift 类型的大写命名风格(如`String`,`Int`和`Bool`)。相反的,请使用`lowerCamelCase`这种方式为属性和方法命名(如`framerate`和`incrementCount`),以便和类型名区分。
|
||||
|
||||
以下是定义结构体和定义类的示例:
|
||||
|
||||
```swift
|
||||
struct Resolution {
|
||||
var width = 0
|
||||
var height = 0
|
||||
}
|
||||
class VideoMode {
|
||||
var resolution = Resolution()
|
||||
var interlaced = false
|
||||
var frameRate = 0.0
|
||||
var name: String?
|
||||
}
|
||||
```
|
||||
|
||||
在上面的示例中我们定义了一个名为`Resolution`的结构体,用来描述一个显示器的像素分辨率。这个结构体包含了两个名为`width`和`height`的存储属性。存储属性是被捆绑和存储在类或结构体中的常量或变量。当这两个属性被初始化为整数`0`的时候,它们会被推断为`Int`类型。
|
||||
|
||||
在上面的示例中我们还定义了一个名为`VideoMode`的类,用来描述一个视频显示器的特定模式。这个类包含了四个变量存储属性。第一个是`分辨率`,它被初始化为一个新的`Resolution`结构体的实例,属性类型被推断为`Resolution`。新`VideoMode`实例同时还会初始化其它三个属性,它们分别是,初始值为`false`的`interlaced`,初始值为`0.0`的`frameRate`,以及值为可选`String`的`name`。`name`属性会被自动赋予一个默认值`nil`,意为“没有`name`值”,因为它是一个可选类型。
|
||||
|
||||
<a name="class_and_structure_instances"></a>
|
||||
### 类和结构体实例
|
||||
|
||||
`Resolution`结构体和`VideoMode`类的定义仅描述了什么是`Resolution`和`VideoMode`。它们并没有描述一个特定的分辨率(resolution)或者视频模式(video mode)。为了描述一个特定的分辨率或者视频模式,我们需要生成一个它们的实例。
|
||||
|
||||
生成结构体和类实例的语法非常相似:
|
||||
|
||||
```swift
|
||||
let someResolution = Resolution()
|
||||
let someVideoMode = VideoMode()
|
||||
```
|
||||
|
||||
结构体和类都使用构造器语法来生成新的实例。构造器语法的最简单形式是在结构体或者类的类型名称后跟随一对空括号,如`Resolution()`或`VideoMode()`。通过这种方式所创建的类或者结构体实例,其属性均会被初始化为默认值。[构造过程](./14_Initialization.html)章节会对类和结构体的初始化进行更详细的讨论。
|
||||
|
||||
<a name="accessing_properties"></a>
|
||||
### 属性访问
|
||||
|
||||
通过使用*点语法*(*dot syntax*),你可以访问实例的属性。其语法规则是,实例名后面紧跟属性名,两者通过点号(`.`)连接:
|
||||
|
||||
```swift
|
||||
print("The width of someResolution is \(someResolution.width)")
|
||||
// 输出 "The width of someResolution is 0"
|
||||
```
|
||||
|
||||
在上面的例子中,`someResolution.width`引用`someResolution`的`width`属性,返回`width`的初始值`0`。
|
||||
|
||||
你也可以访问子属性,如`VideoMode`中`Resolution`属性的`width`属性:
|
||||
|
||||
```swift
|
||||
print("The width of someVideoMode is \(someVideoMode.resolution.width)")
|
||||
// 输出 "The width of someVideoMode is 0"
|
||||
```
|
||||
|
||||
你也可以使用点语法为变量属性赋值:
|
||||
|
||||
```swift
|
||||
someVideoMode.resolution.width = 1280
|
||||
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
|
||||
// 输出 "The width of someVideoMode is now 1280"
|
||||
```
|
||||
|
||||
> 注意
|
||||
> 与 Objective-C 语言不同的是,Swift 允许直接设置结构体属性的子属性。上面的最后一个例子,就是直接设置了`someVideoMode`中`resolution`属性的`width`这个子属性,以上操作并不需要重新为整个`resolution`属性设置新值。
|
||||
|
||||
<a name="memberwise_initializers_for_structure_types"></a>
|
||||
### 结构体类型的成员逐一构造器(Memberwise Initializers for Structure Types)
|
||||
|
||||
所有结构体都有一个自动生成的*成员逐一构造器*,用于初始化新结构体实例中成员的属性。新实例中各个属性的初始值可以通过属性的名称传递到成员逐一构造器之中:
|
||||
|
||||
```swift
|
||||
let vga = Resolution(width:640, height: 480)
|
||||
```
|
||||
|
||||
与结构体不同,类实例没有默认的成员逐一构造器。[构造过程](./14_Initialization.html)章节会对构造器进行更详细的讨论。
|
||||
|
||||
<a name="structures_and_enumerations_are_value_types"></a>
|
||||
## 结构体和枚举是值类型
|
||||
|
||||
*值类型*被赋予给一个变量、常量或者被传递给一个函数的时候,其值会被*拷贝*。
|
||||
|
||||
在之前的章节中,我们已经大量使用了值类型。实际上,在 Swift 中,所有的基本类型:整数(Integer)、浮点数(floating-point)、布尔值(Boolean)、字符串(string)、数组(array)和字典(dictionary),都是值类型,并且在底层都是以结构体的形式所实现。
|
||||
|
||||
在 Swift 中,所有的结构体和枚举类型都是值类型。这意味着它们的实例,以及实例中所包含的任何值类型属性,在代码中传递的时候都会被复制。
|
||||
|
||||
请看下面这个示例,其使用了前一个示例中的`Resolution`结构体:
|
||||
|
||||
```swift
|
||||
let hd = Resolution(width: 1920, height: 1080)
|
||||
var cinema = hd
|
||||
```
|
||||
|
||||
在以上示例中,声明了一个名为`hd`的常量,其值为一个初始化为全高清视频分辨率(`1920` 像素宽,`1080` 像素高)的`Resolution`实例。
|
||||
|
||||
然后示例中又声明了一个名为`cinema`的变量,并将`hd`赋值给它。因为`Resolution`是一个结构体,所以`cinema`的值其实是`hd`的一个拷贝副本,而不是`hd`本身。尽管`hd`和`cinema`有着相同的宽(width)和高(height),但是在幕后它们是两个完全不同的实例。
|
||||
|
||||
下面,为了符合数码影院放映的需求(`2048` 像素宽,`1080` 像素高),`cinema`的`width`属性需要作如下修改:
|
||||
|
||||
```swift
|
||||
cinema.width = 2048
|
||||
```
|
||||
|
||||
这里,将会显示`cinema`的`width`属性确已改为了`2048`:
|
||||
|
||||
```swift
|
||||
print("cinema is now \(cinema.width) pixels wide")
|
||||
// 输出 "cinema is now 2048 pixels wide"
|
||||
```
|
||||
|
||||
然而,初始的`hd`实例中`width`属性还是`1920`:
|
||||
|
||||
```swift
|
||||
print("hd is still \(hd.width) pixels wide")
|
||||
// 输出 "hd is still 1920 pixels wide"
|
||||
```
|
||||
|
||||
在将`hd`赋予给`cinema`的时候,实际上是将`hd`中所存储的值进行拷贝,然后将拷贝的数据存储到新的`cinema`实例中。结果就是两个完全独立的实例碰巧包含有相同的数值。由于两者相互独立,因此将`cinema`的`width`修改为`2048`并不会影响`hd`中的`width`的值。
|
||||
|
||||
枚举也遵循相同的行为准则:
|
||||
|
||||
```swift
|
||||
enum CompassPoint {
|
||||
case North, South, East, West
|
||||
}
|
||||
var currentDirection = CompassPoint.West
|
||||
let rememberedDirection = currentDirection
|
||||
currentDirection = .East
|
||||
if rememberedDirection == .West {
|
||||
print("The remembered direction is still .West")
|
||||
}
|
||||
// 输出 "The remembered direction is still .West"
|
||||
```
|
||||
|
||||
上例中`rememberedDirection`被赋予了`currentDirection`的值,实际上它被赋予的是值的一个拷贝。赋值过程结束后再修改`currentDirection`的值并不影响`rememberedDirection`所储存的原始值的拷贝。
|
||||
|
||||
<a name="classes_are_reference_types"></a>
|
||||
## 类是引用类型
|
||||
|
||||
与值类型不同,引用类型在被赋予到一个变量、常量或者被传递到一个函数时,其值不会被拷贝。因此,引用的是已存在的实例本身而不是其拷贝。
|
||||
|
||||
请看下面这个示例,其使用了之前定义的`VideoMode`类:
|
||||
|
||||
```swift
|
||||
let tenEighty = VideoMode()
|
||||
tenEighty.resolution = hd
|
||||
tenEighty.interlaced = true
|
||||
tenEighty.name = "1080i"
|
||||
tenEighty.frameRate = 25.0
|
||||
```
|
||||
|
||||
以上示例中,声明了一个名为`tenEighty`的常量,其引用了一个`VideoMode`类的新实例。在之前的示例中,这个视频模式(video mode)被赋予了HD分辨率(`1920`*`1080`)的一个拷贝(即`hd`实例)。同时设置为`interlaced`,命名为`“1080i”`。最后,其帧率是`25.0`帧每秒。
|
||||
|
||||
然后,`tenEighty`被赋予名为`alsoTenEighty`的新常量,同时对`alsoTenEighty`的帧率进行修改:
|
||||
|
||||
```swift
|
||||
let alsoTenEighty = tenEighty
|
||||
alsoTenEighty.frameRate = 30.0
|
||||
```
|
||||
|
||||
因为类是引用类型,所以`tenEight`和`alsoTenEight`实际上引用的是相同的`VideoMode`实例。换句话说,它们是同一个实例的两种叫法。
|
||||
|
||||
下面,通过查看`tenEighty`的`frameRate`属性,我们会发现它正确的显示了所引用的`VideoMode`实例的新帧率,其值为`30.0`:
|
||||
|
||||
```swift
|
||||
print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
|
||||
// 输出 "The frameRate property of theEighty is now 30.0"
|
||||
```
|
||||
|
||||
需要注意的是`tenEighty`和`alsoTenEighty`被声明为常量而不是变量。然而你依然可以改变`tenEighty.frameRate`和`alsoTenEighty.frameRate`,因为`tenEighty`和`alsoTenEighty`这两个常量的值并未改变。它们并不“存储”这个`VideoMode`实例,而仅仅是对`VideoMode`实例的引用。所以,改变的是被引用的`VideoMode`的`frameRate`属性,而不是引用`VideoMode`的常量的值。
|
||||
|
||||
<a name="identity_operators"></a>
|
||||
### 恒等运算符
|
||||
|
||||
因为类是引用类型,有可能有多个常量和变量在幕后同时引用同一个类实例。(对于结构体和枚举来说,这并不成立。因为它们作为值类型,在被赋予到常量、变量或者传递到函数时,其值总是会被拷贝。)
|
||||
|
||||
如果能够判定两个常量或者变量是否引用同一个类实例将会很有帮助。为了达到这个目的,Swift 内建了两个恒等运算符:
|
||||
|
||||
* 等价于(`===`)
|
||||
* 不等价于(`!==`)
|
||||
|
||||
运用这两个运算符检测两个常量或者变量是否引用同一个实例:
|
||||
|
||||
```swift
|
||||
if tenEighty === alsoTenEighty {
|
||||
print("tenEighty and alsoTenEighty refer to the same Resolution instance.")
|
||||
}
|
||||
//输出 "tenEighty and alsoTenEighty refer to the same Resolution instance."
|
||||
```
|
||||
|
||||
请注意,“等价于”(用三个等号表示,`===`)与“等于”(用两个等号表示,`==`)的不同:
|
||||
|
||||
* “等价于”表示两个类类型(class type)的常量或者变量引用同一个类实例。
|
||||
* “等于”表示两个实例的值“相等”或“相同”,判定时要遵照设计者定义的评判标准,因此相对于“相等”来说,这是一种更加合适的叫法。
|
||||
|
||||
当你在定义你的自定义类和结构体的时候,你有义务来决定判定两个实例“相等”的标准。在章节[等价操作符](./25_Advanced_Operators.html#equivalence_operators)中将会详细介绍实现自定义“等于”和“不等于”运算符的流程。
|
||||
|
||||
<a name="pointers"></a>
|
||||
### 指针
|
||||
|
||||
如果你有 C,C++ 或者 Objective-C 语言的经验,那么你也许会知道这些语言使用*指针*来引用内存中的地址。一个引用某个引用类型实例的 Swift 常量或者变量,与 C 语言中的指针类似,但是并不直接指向某个内存地址,也不要求你使用星号(`*`)来表明你在创建一个引用。Swift 中的这些引用与其它的常量或变量的定义方式相同。
|
||||
|
||||
<a name="choosing_between_classes_and_structures"></a>
|
||||
## 类和结构体的选择
|
||||
|
||||
在你的代码中,你可以使用类和结构体来定义你的自定义数据类型。
|
||||
|
||||
然而,结构体实例总是通过值传递,类实例总是通过引用传递。这意味两者适用不同的任务。当你在考虑一个工程项目的数据结构和功能的时候,你需要决定每个数据结构是定义成类还是结构体。
|
||||
|
||||
按照通用的准则,当符合一条或多条以下条件时,请考虑构建结构体:
|
||||
|
||||
* 该数据结构的主要目的是用来封装少量相关简单数据值。
|
||||
* 有理由预计该数据结构的实例在被赋值或传递时,封装的数据将会被拷贝而不是被引用。
|
||||
* 该数据结构中储存的值类型属性,也应该被拷贝,而不是被引用。
|
||||
* 该数据结构不需要去继承另一个既有类型的属性或者行为。
|
||||
|
||||
举例来说,以下情境中适合使用结构体:
|
||||
|
||||
* 几何形状的大小,封装一个`width`属性和`height`属性,两者均为`Double`类型。
|
||||
* 一定范围内的路径,封装一个`start`属性和`length`属性,两者均为`Int`类型。
|
||||
* 三维坐标系内一点,封装`x`,`y`和`z`属性,三者均为`Double`类型。
|
||||
|
||||
在所有其它案例中,定义一个类,生成一个它的实例,并通过引用来管理和传递。实际中,这意味着绝大部分的自定义数据构造都应该是类,而非结构体。
|
||||
|
||||
<a name="assignment_and_copy_behavior_for_strings_arrays_and_dictionaries"></a>
|
||||
## 字符串(String)、数组(Array)、和字典(Dictionary)类型的赋值与复制行为
|
||||
|
||||
Swift 中,许多基本类型,诸如`String`,`Array`和`Dictionary`类型均以结构体的形式实现。这意味着被赋值给新的常量或变量,或者被传入函数或方法中时,它们的值会被拷贝。
|
||||
|
||||
Objective-C 中`NSString`,`NSArray`和`NSDictionary`类型均以类的形式实现,而并非结构体。它们在被赋值或者被传入函数或方法时,不会发生值拷贝,而是传递现有实例的引用。
|
||||
|
||||
> 注意
|
||||
> 以上是对字符串、数组、字典的“拷贝”行为的描述。在你的代码中,拷贝行为看起来似乎总会发生。然而,Swift 在幕后只在绝对必要时才执行实际的拷贝。Swift 管理所有的值拷贝以确保性能最优化,所以你没必要去回避赋值来保证性能最优化。
|
||||
@ -1,438 +0,0 @@
|
||||
# 属性 (Properties)
|
||||
---
|
||||
|
||||
> 1.0
|
||||
> 翻译:[shinyzhu](https://github.com/shinyzhu)
|
||||
> 校对:[pp-prog](https://github.com/pp-prog) [yangsiy](https://github.com/yangsiy)
|
||||
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[yangsiy](https://github.com/yangsiy)
|
||||
|
||||
|
||||
> 2.1
|
||||
> 翻译:[buginux](https://github.com/buginux)
|
||||
> 校对:[shanks](http://codebuild.me),2015-10-29
|
||||
|
||||
|
||||
> 2.2
|
||||
> 翻译:[saitjr](https://github.com/saitjr),2016-04-11
|
||||
|
||||
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [存储属性(Stored Properties)](#stored_properties)
|
||||
- [计算属性(Computed Properties)](#computed_properties)
|
||||
- [属性观察器(Property Observers)](#property_observers)
|
||||
- [全局变量和局部变量(Global and Local Variables)](#global_and_local_variables)
|
||||
- [类型属性(Type Properties)](#type_properties)
|
||||
|
||||
*属性*将值跟特定的类、结构或枚举关联。存储属性存储常量或变量作为实例的一部分,而计算属性计算(不是存储)一个值。计算属性可以用于类、结构体和枚举,存储属性只能用于类和结构体。
|
||||
|
||||
存储属性和计算属性通常与特定类型的实例关联。但是,属性也可以直接作用于类型本身,这种属性称为类型属性。
|
||||
|
||||
另外,还可以定义属性观察器来监控属性值的变化,以此来触发一个自定义的操作。属性观察器可以添加到自己定义的存储属性上,也可以添加到从父类继承的属性上。
|
||||
|
||||
<a name="stored_properties"></a>
|
||||
## 存储属性
|
||||
|
||||
简单来说,一个存储属性就是存储在特定类或结构体实例里的一个常量或变量。存储属性可以是*变量存储属性*(用关键字 `var` 定义),也可以是*常量存储属性*(用关键字 `let` 定义)。
|
||||
|
||||
可以在定义存储属性的时候指定默认值,请参考[默认构造器](./14_Initialization.html#default_initializers)一节。也可以在构造过程中设置或修改存储属性的值,甚至修改常量存储属性的值,请参考[构造过程中常量属性的修改](./14_Initialization.html#assigning_constant_properties_during_initialization)一节。
|
||||
|
||||
下面的例子定义了一个名为 `FixedLengthRange` 的结构体,它描述了一个用于表示整型范围的常量,在创建后就不能进行修改:
|
||||
|
||||
```swift
|
||||
struct FixedLengthRange {
|
||||
var firstValue: Int
|
||||
let length: Int
|
||||
}
|
||||
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
|
||||
// 该区间表示整数0,1,2
|
||||
rangeOfThreeItems.firstValue = 6
|
||||
// 该区间现在表示整数6,7,8
|
||||
```
|
||||
|
||||
`FixedLengthRange` 的实例包含一个名为 `firstValue` 的变量存储属性和一个名为 `length` 的常量存储属性。在上面的例子中,`length` 在创建实例的时候被初始化,因为它是一个常量存储属性,所以之后无法修改它的值。
|
||||
|
||||
<a name="stored_properties_of_constant_structure_instances"></a>
|
||||
### 常量结构体的存储属性
|
||||
|
||||
如果创建了一个结构体的实例并将其赋值给一个常量,则无法修改该实例的任何属性,即使有属性被声明为变量也不行:
|
||||
|
||||
```swift
|
||||
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
|
||||
// 该区间表示整数0,1,2,3
|
||||
rangeOfFourItems.firstValue = 6
|
||||
// 尽管 firstValue 是个变量属性,这里还是会报错
|
||||
```
|
||||
|
||||
因为 `rangeOfFourItems` 被声明成了常量(用 `let` 关键字),即使 `firstValue` 是一个变量属性,也无法再修改它了。
|
||||
|
||||
这种行为是由于结构体(struct)属于*值类型*。当值类型的实例被声明为常量的时候,它的所有属性也就成了常量。
|
||||
|
||||
属于*引用类型*的类(class)则不一样。把一个引用类型的实例赋给一个常量后,仍然可以修改该实例的变量属性。
|
||||
|
||||
<a name="lazy_stored_properties"></a>
|
||||
### 延迟存储属性
|
||||
|
||||
延迟存储属性是指当第一次被调用的时候才会计算其初始值的属性。在属性声明前使用 `lazy` 来标示一个延迟存储属性。
|
||||
|
||||
> 注意
|
||||
> 必须将延迟存储属性声明成变量(使用 `var` 关键字),因为属性的初始值可能在实例构造完成之后才会得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。
|
||||
|
||||
延迟属性很有用,当属性的值依赖于在实例的构造过程结束后才会知道影响值的外部因素时,或者当获得属性的初始值需要复杂或大量计算时,可以只在需要的时候计算它。
|
||||
|
||||
下面的例子使用了延迟存储属性来避免复杂类中不必要的初始化。例子中定义了 `DataImporter` 和 `DataManager` 两个类,下面是部分代码:
|
||||
|
||||
```swift
|
||||
class DataImporter {
|
||||
/*
|
||||
DataImporter 是一个负责将外部文件中的数据导入的类。
|
||||
这个类的初始化会消耗不少时间。
|
||||
*/
|
||||
var fileName = "data.txt"
|
||||
// 这里会提供数据导入功能
|
||||
}
|
||||
|
||||
class DataManager {
|
||||
lazy var importer = DataImporter()
|
||||
var data = [String]()
|
||||
// 这里会提供数据管理功能
|
||||
}
|
||||
|
||||
let manager = DataManager()
|
||||
manager.data.append("Some data")
|
||||
manager.data.append("Some more data")
|
||||
// DataImporter 实例的 importer 属性还没有被创建
|
||||
```
|
||||
|
||||
`DataManager` 类包含一个名为 `data` 的存储属性,初始值是一个空的字符串(`String`)数组。这里没有给出全部代码,只需知道 `DataManager` 类的目的是管理和提供对这个字符串数组的访问即可。
|
||||
|
||||
`DataManager` 的一个功能是从文件导入数据。该功能由 `DataImporter` 类提供,`DataImporter` 完成初始化需要消耗不少时间:因为它的实例在初始化时可能要打开文件,还要读取文件内容到内存。
|
||||
|
||||
`DataManager` 管理数据时也可能不从文件中导入数据。所以当 `DataManager` 的实例被创建时,没必要创建一个 `DataImporter` 的实例,更明智的做法是第一次用到 `DataImporter` 的时候才去创建它。
|
||||
|
||||
由于使用了 `lazy` ,`importer` 属性只有在第一次被访问的时候才被创建。比如访问它的属性 `fileName` 时:
|
||||
|
||||
```swift
|
||||
print(manager.importer.fileName)
|
||||
// DataImporter 实例的 importer 属性现在被创建了
|
||||
// 输出 "data.txt”
|
||||
```
|
||||
|
||||
> 注意
|
||||
> 如果一个被标记为 `lazy` 的属性在没有初始化时就同时被多个线程访问,则无法保证该属性只会被初始化一次。
|
||||
|
||||
<a name="stored_properties_and_instance_variables"></a>
|
||||
### 存储属性和实例变量
|
||||
|
||||
如果您有过 Objective-C 经验,应该知道 Objective-C 为类实例存储值和引用提供两种方法。除了属性之外,还可以使用实例变量作为属性值的后端存储。
|
||||
|
||||
Swift 编程语言中把这些理论统一用属性来实现。Swift 中的属性没有对应的实例变量,属性的后端存储也无法直接访问。这就避免了不同场景下访问方式的困扰,同时也将属性的定义简化成一个语句。属性的全部信息——包括命名、类型和内存管理特征——都在唯一一个地方(类型定义中)定义。
|
||||
|
||||
<a name="computed_properties"></a>
|
||||
## 计算属性
|
||||
|
||||
除存储属性外,类、结构体和枚举可以定义*计算属性*。计算属性不直接存储值,而是提供一个 getter 和一个可选的 setter,来间接获取和设置其他属性或变量的值。
|
||||
|
||||
```swift
|
||||
struct Point {
|
||||
var x = 0.0, y = 0.0
|
||||
}
|
||||
struct Size {
|
||||
var width = 0.0, height = 0.0
|
||||
}
|
||||
struct Rect {
|
||||
var origin = Point()
|
||||
var size = Size()
|
||||
var center: Point {
|
||||
get {
|
||||
let centerX = origin.x + (size.width / 2)
|
||||
let centerY = origin.y + (size.height / 2)
|
||||
return Point(x: centerX, y: centerY)
|
||||
}
|
||||
set(newCenter) {
|
||||
origin.x = newCenter.x - (size.width / 2)
|
||||
origin.y = newCenter.y - (size.height / 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
var square = Rect(origin: Point(x: 0.0, y: 0.0),
|
||||
size: Size(width: 10.0, height: 10.0))
|
||||
let initialSquareCenter = square.center
|
||||
square.center = Point(x: 15.0, y: 15.0)
|
||||
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
|
||||
// 输出 "square.origin is now at (10.0, 10.0)”
|
||||
```
|
||||
|
||||
这个例子定义了 3 个结构体来描述几何形状:
|
||||
|
||||
- `Point` 封装了一个 `(x, y)` 的坐标
|
||||
- `Size` 封装了一个 `width` 和一个 `height`
|
||||
- `Rect` 表示一个有原点和尺寸的矩形
|
||||
|
||||
`Rect`也提供了一个名为`center` 的计算属性。一个矩形的中心点可以从原点(`origin`)和大小(`size`)算出,所以不需要将它以显式声明的 `Point` 来保存。`Rect` 的计算属性 `center` 提供了自定义的 getter 和 setter 来获取和设置矩形的中心点,就像它有一个存储属性一样。
|
||||
|
||||
上述例子中创建了一个名为 `square` 的 `Rect` 实例,初始值原点是 `(0, 0)`,宽度高度都是 `10`。如下图中蓝色正方形所示。
|
||||
|
||||
`square` 的 `center` 属性可以通过点运算符(`square.center`)来访问,这会调用该属性的 getter 来获取它的值。跟直接返回已经存在的值不同,getter 实际上通过计算然后返回一个新的 `Point` 来表示 `square` 的中心点。如代码所示,它正确返回了中心点 `(5, 5)`。
|
||||
|
||||
`center` 属性之后被设置了一个新的值 `(15, 15)`,表示向右上方移动正方形到如下图橙色正方形所示的位置。设置属性`center`的值会调用它的 setter 来修改属性 `origin` 的 `x` 和 `y` 的值,从而实现移动正方形到新的位置。
|
||||
|
||||
<img src="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/computedProperties_2x.png" alt="Computed Properties sample" width="388" height="387" />
|
||||
|
||||
<a name="shorthand_setter_declaration"></a>
|
||||
### 便捷 setter 声明
|
||||
|
||||
如果计算属性的 setter 没有定义表示新值的参数名,则可以使用默认名称 `newValue`。下面是使用了便捷 setter 声明的 `Rect` 结构体代码:
|
||||
|
||||
```swift
|
||||
struct AlternativeRect {
|
||||
var origin = Point()
|
||||
var size = Size()
|
||||
var center: Point {
|
||||
get {
|
||||
let centerX = origin.x + (size.width / 2)
|
||||
let centerY = origin.y + (size.height / 2)
|
||||
return Point(x: centerX, y: centerY)
|
||||
}
|
||||
set {
|
||||
origin.x = newValue.x - (size.width / 2)
|
||||
origin.y = newValue.y - (size.height / 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<a name="readonly_computed_properties"></a>
|
||||
### 只读计算属性
|
||||
|
||||
只有 getter 没有 setter 的计算属性就是*只读计算属性*。只读计算属性总是返回一个值,可以通过点运算符访问,但不能设置新的值。
|
||||
|
||||
> 注意
|
||||
> 必须使用 `var` 关键字定义计算属性,包括只读计算属性,因为它们的值不是固定的。`let` 关键字只用来声明常量属性,表示初始化后再也无法修改的值。
|
||||
|
||||
只读计算属性的声明可以去掉 `get` 关键字和花括号:
|
||||
|
||||
```swift
|
||||
struct Cuboid {
|
||||
var width = 0.0, height = 0.0, depth = 0.0
|
||||
var volume: Double {
|
||||
return width * height * depth
|
||||
}
|
||||
}
|
||||
let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
|
||||
print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
|
||||
// 输出 "the volume of fourByFiveByTwo is 40.0"
|
||||
```
|
||||
|
||||
这个例子定义了一个名为 `Cuboid` 的结构体,表示三维空间的立方体,包含 `width`、`height` 和 `depth` 属性。结构体还有一个名为 `volume` 的只读计算属性用来返回立方体的体积。为 `volume` 提供 setter 毫无意义,因为无法确定如何修改 `width`、`height` 和 `depth` 三者的值来匹配新的 `volume`。然而,`Cuboid` 提供一个只读计算属性来让外部用户直接获取体积是很有用的。
|
||||
|
||||
<a name="property_observers"></a>
|
||||
## 属性观察器
|
||||
|
||||
*属性观察器*监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,即使新值和当前值相同的时候也不例外。
|
||||
|
||||
可以为除了延迟存储属性之外的其他存储属性添加属性观察器,也可以通过重写属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器。你不必为非重写的计算属性添加属性观察器,因为可以通过它的 setter 直接监控和响应值的变化。 属性重写请参考[重写](./13_Inheritance.html#overriding)。
|
||||
|
||||
|
||||
可以为属性添加如下的一个或全部观察器:
|
||||
|
||||
- `willSet` 在新的值被设置之前调用
|
||||
- `didSet` 在新的值被设置之后立即调用
|
||||
|
||||
`willSet` 观察器会将新的属性值作为常量参数传入,在 `willSet` 的实现代码中可以为这个参数指定一个名称,如果不指定则参数仍然可用,这时使用默认名称 `newValue` 表示。
|
||||
|
||||
同样,`didSet` 观察器会将旧的属性值作为参数传入,可以为该参数命名或者使用默认参数名 `oldValue`。如果在 `didSet` 方法中再次对该属性赋值,那么新值会覆盖旧的值。
|
||||
|
||||
> 注意
|
||||
> 父类的属性在子类的构造器中被赋值时,它在父类中的 `willSet` 和 `didSet` 观察器会被调用,随后才会调用子类的观察器。在父类书初始化方法调用之前,子类给属性赋值时,观察器不会被调用。
|
||||
> 有关构造器代理的更多信息,请参考[值类型的构造器代理](./14_Initialization.html#initializer_delegation_for_value_types)和[类的构造器代理规则](./14_Initialization.html#initializer_delegation_for_class_types)。
|
||||
|
||||
下面是一个 `willSet` 和 `didSet` 实际运用的例子,其中定义了一个名为 `StepCounter` 的类,用来统计一个人步行时的总步数。这个类可以跟计步器或其他日常锻炼的统计装置的输入数据配合使用。
|
||||
|
||||
```swift
|
||||
class StepCounter {
|
||||
var totalSteps: Int = 0 {
|
||||
willSet(newTotalSteps) {
|
||||
print("About to set totalSteps to \(newTotalSteps)")
|
||||
}
|
||||
didSet {
|
||||
if totalSteps > oldValue {
|
||||
print("Added \(totalSteps - oldValue) steps")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let stepCounter = StepCounter()
|
||||
stepCounter.totalSteps = 200
|
||||
// About to set totalSteps to 200
|
||||
// Added 200 steps
|
||||
stepCounter.totalSteps = 360
|
||||
// About to set totalSteps to 360
|
||||
// Added 160 steps
|
||||
stepCounter.totalSteps = 896
|
||||
// About to set totalSteps to 896
|
||||
// Added 536 steps
|
||||
```
|
||||
|
||||
`StepCounter` 类定义了一个 `Int` 类型的属性 `totalSteps`,它是一个存储属性,包含 `willSet` 和 `didSet` 观察器。
|
||||
|
||||
当 `totalSteps` 被设置新值的时候,它的 `willSet` 和 `didSet` 观察器都会被调用,即使新值和当前值完全相同时也会被调用。
|
||||
|
||||
例子中的 `willSet` 观察器将表示新值的参数自定义为 `newTotalSteps`,这个观察器只是简单的将新的值输出。
|
||||
|
||||
`didSet` 观察器在 `totalSteps` 的值改变后被调用,它把新值和旧值进行对比,如果总步数增加了,就输出一个消息表示增加了多少步。`didSet` 没有为旧值提供自定义名称,所以默认值 `oldValue` 表示旧值的参数名。
|
||||
|
||||
>注意
|
||||
>
|
||||
>如果将属性通过 in-out 方式传入函数,`willSet` 和 `didSet` 也会调用。这是因为 in-out 参数采用了拷入拷出模式:即在函数内部使用的是参数的 copy,函数结束后,又对参数重新赋值。关于 in-out 参数详细的介绍,请参考[输入输出参数](../chapter3/05_Declarations.html#in-out_parameters)
|
||||
|
||||
<a name="global_and_local_variables"></a>
|
||||
##全局变量和局部变量
|
||||
|
||||
计算属性和属性观察器所描述的功能也可以用于*全局变量*和*局部变量*。全局变量是在函数、方法、闭包或任何类型之外定义的变量。局部变量是在函数、方法或闭包内部定义的变量。
|
||||
|
||||
前面章节提到的全局或局部变量都属于存储型变量,跟存储属性类似,它为特定类型的值提供存储空间,并允许读取和写入。
|
||||
|
||||
另外,在全局或局部范围都可以定义计算型变量和为存储型变量定义观察器。计算型变量跟计算属性一样,返回一个计算结果而不是存储值,声明格式也完全一样。
|
||||
|
||||
> 注意
|
||||
> 全局的常量或变量都是延迟计算的,跟[延迟存储属性](#lazy_stored_properties)相似,不同的地方在于,全局的常量或变量不需要标记`lazy`修饰符。
|
||||
> 局部范围的常量或变量从不延迟计算。
|
||||
|
||||
<a name="type_properties"></a>
|
||||
##类型属性
|
||||
|
||||
实例属性属于一个特定类型的实例,每创建一个实例,实例都拥有属于自己的一套属性值,实例之间的属性相互独立。
|
||||
|
||||
也可以为类型本身定义属性,无论创建了多少个该类型的实例,这些属性都只有唯一一份。这种属性就是*类型属性*。
|
||||
|
||||
类型属性用于定义某个类型所有实例共享的数据,比如所有实例都能用的一个常量(就像 C 语言中的静态常量),或者所有实例都能访问的一个变量(就像 C 语言中的静态变量)。
|
||||
|
||||
存储型类型属性可以是变量或常量,计算型类型属性跟实例的计算型属性一样只能定义成变量属性。
|
||||
|
||||
> 注意
|
||||
> 跟实例的存储型属性不同,必须给存储型类型属性指定默认值,因为类型本身没有构造器,也就无法在初始化过程中使用构造器给类型属性赋值。
|
||||
> 存储型类型属性是延迟初始化的,它们只有在第一次被访问的时候才会被初始化。即使它们被多个线程同时访问,系统也保证只会对其进行一次初始化,并且不需要对其使用 `lazy` 修饰符。
|
||||
|
||||
<a name="type_property_syntax"></a>
|
||||
###类型属性语法
|
||||
|
||||
在 C 或 Objective-C 中,与某个类型关联的静态常量和静态变量,是作为全局(*global*)静态变量定义的。但是在 Swift 中,类型属性是作为类型定义的一部分写在类型最外层的花括号内,因此它的作用范围也就在类型支持的范围内。
|
||||
|
||||
使用关键字 `static` 来定义类型属性。在为类定义计算型类型属性时,可以改用关键字 `class` 来支持子类对父类的实现进行重写。下面的例子演示了存储型和计算型类型属性的语法:
|
||||
|
||||
```swift
|
||||
struct SomeStructure {
|
||||
static var storedTypeProperty = "Some value."
|
||||
static var computedTypeProperty: Int {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
enum SomeEnumeration {
|
||||
static var storedTypeProperty = "Some value."
|
||||
static var computedTypeProperty: Int {
|
||||
return 6
|
||||
}
|
||||
}
|
||||
class SomeClass {
|
||||
static var storedTypeProperty = "Some value."
|
||||
static var computedTypeProperty: Int {
|
||||
return 27
|
||||
}
|
||||
class var overrideableComputedTypeProperty: Int {
|
||||
return 107
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 注意
|
||||
> 例子中的计算型类型属性是只读的,但也可以定义可读可写的计算型类型属性,跟计算型实例属性的语法相同。
|
||||
|
||||
<a name="querying_and_setting_type_properties"></a>
|
||||
###获取和设置类型属性的值
|
||||
|
||||
跟实例属性一样,类型属性也是通过点运算符来访问。但是,类型属性是通过类型本身来访问,而不是通过实例。比如:
|
||||
|
||||
```swift
|
||||
print(SomeStructure.storedTypeProperty)
|
||||
// 输出 "Some value."
|
||||
SomeStructure.storedTypeProperty = "Another value."
|
||||
print(SomeStructure.storedTypeProperty)
|
||||
// 输出 "Another value.”
|
||||
print(SomeEnumeration.computedTypeProperty)
|
||||
// 输出 "6"
|
||||
print(SomeClass.computedTypeProperty)
|
||||
// 输出 "27"
|
||||
```
|
||||
|
||||
下面的例子定义了一个结构体,使用两个存储型类型属性来表示两个声道的音量,每个声道具有 `0` 到 `10` 之间的整数音量。
|
||||
|
||||
下图展示了如何把两个声道结合来模拟立体声的音量。当声道的音量是 `0`,没有一个灯会亮;当声道的音量是 `10`,所有灯点亮。本图中,左声道的音量是 `9`,右声道的音量是 `7`:
|
||||
|
||||
<img src="https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Art/staticPropertiesVUMeter_2x.png" alt="Static Properties VUMeter" width="243" height="357" />
|
||||
|
||||
上面所描述的声道模型使用 `AudioChannel` 结构体的实例来表示:
|
||||
|
||||
```swift
|
||||
struct AudioChannel {
|
||||
static let thresholdLevel = 10
|
||||
static var maxInputLevelForAllChannels = 0
|
||||
var currentLevel: Int = 0 {
|
||||
didSet {
|
||||
if currentLevel > AudioChannel.thresholdLevel {
|
||||
// 将当前音量限制在阀值之内
|
||||
currentLevel = AudioChannel.thresholdLevel
|
||||
}
|
||||
if currentLevel > AudioChannel.maxInputLevelForAllChannels {
|
||||
// 存储当前音量作为新的最大输入音量
|
||||
AudioChannel.maxInputLevelForAllChannels = currentLevel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
结构 `AudioChannel` 定义了 2 个存储型类型属性来实现上述功能。第一个是 `thresholdLevel`,表示音量的最大上限阈值,它是一个值为 `10` 的常量,对所有实例都可见,如果音量高于 `10`,则取最大上限值 `10`(见后面描述)。
|
||||
|
||||
第二个类型属性是变量存储型属性 `maxInputLevelForAllChannels`,它用来表示所有 `AudioChannel` 实例的最大音量,初始值是`0`。
|
||||
|
||||
`AudioChannel` 也定义了一个名为 `currentLevel` 的存储型实例属性,表示当前声道现在的音量,取值为 `0` 到 `10`。
|
||||
|
||||
属性 `currentLevel` 包含 `didSet` 属性观察器来检查每次设置后的属性值,它做如下两个检查:
|
||||
|
||||
- 如果 `currentLevel` 的新值大于允许的阈值 `thresholdLevel`,属性观察器将 `currentLevel` 的值限定为阈值 `thresholdLevel`。
|
||||
- 如果修正后的 `currentLevel` 值大于静态类型属性 `maxInputLevelForAllChannels` 的值,属性观察器就将新值保存在 `maxInputLevelForAllChannels` 中。
|
||||
|
||||
> 注意
|
||||
> 在第一个检查过程中,`didSet` 属性观察器将 `currentLevel` 设置成了不同的值,但这不会造成属性观察器被再次调用。
|
||||
|
||||
可以使用结构体 `AudioChannel` 创建两个声道 `leftChannel` 和 `rightChannel`,用以表示立体声系统的音量:
|
||||
|
||||
```swift
|
||||
var leftChannel = AudioChannel()
|
||||
var rightChannel = AudioChannel()
|
||||
```
|
||||
|
||||
如果将左声道的 `currentLevel` 设置成 `7`,类型属性 `maxInputLevelForAllChannels` 也会更新成 `7`:
|
||||
|
||||
```swift
|
||||
leftChannel.currentLevel = 7
|
||||
print(leftChannel.currentLevel)
|
||||
// 输出 "7"
|
||||
print(AudioChannel.maxInputLevelForAllChannels)
|
||||
// 输出 "7"
|
||||
```
|
||||
|
||||
如果试图将右声道的 `currentLevel` 设置成 `11`,它会被修正到最大值 `10`,同时 `maxInputLevelForAllChannels` 的值也会更新到 `10`:
|
||||
|
||||
```swift
|
||||
rightChannel.currentLevel = 11
|
||||
print(rightChannel.currentLevel)
|
||||
// 输出 "10"
|
||||
print(AudioChannel.maxInputLevelForAllChannels)
|
||||
// 输出 "10"
|
||||
```
|
||||
@ -1,308 +0,0 @@
|
||||
# 方法(Methods)
|
||||
-----------------
|
||||
|
||||
> 1.0
|
||||
> 翻译:[pp-prog](https://github.com/pp-prog)
|
||||
> 校对:[zqp](https://github.com/zqp)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[DianQK](https://github.com/DianQK)
|
||||
|
||||
> 2.1
|
||||
> 翻译:[DianQK](https://github.com/DianQK),[Realank](https://github.com/Realank) 校对:[shanks](http://codebuild.me),2016-01-18
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [实例方法(Instance Methods)](#instance_methods)
|
||||
- [类型方法(Type Methods)](#type_methods)
|
||||
|
||||
**方法**是与某些特定类型相关联的函数。类、结构体、枚举都可以定义实例方法;实例方法为给定类型的实例封装了具体的任务与功能。类、结构体、枚举也可以定义类型方法;类型方法与类型本身相关联。类型方法与 Objective-C 中的类方法(class methods)相似。
|
||||
|
||||
结构体和枚举能够定义方法是 Swift 与 C/Objective-C 的主要区别之一。在 Objective-C 中,类是唯一能定义方法的类型。但在 Swift 中,你不仅能选择是否要定义一个类/结构体/枚举,还能灵活地在你创建的类型(类/结构体/枚举)上定义方法。
|
||||
|
||||
<a name="instance_methods"></a>
|
||||
## 实例方法 (Instance Methods)
|
||||
|
||||
**实例方法**是属于某个特定类、结构体或者枚举类型实例的方法。实例方法提供访问和修改实例属性的方法或提供与实例目的相关的功能,并以此来支撑实例的功能。实例方法的语法与函数完全一致,详情参见[函数](./06_Functions.md)。
|
||||
|
||||
实例方法要写在它所属的类型的前后大括号之间。实例方法能够隐式访问它所属类型的所有的其他实例方法和属性。实例方法只能被它所属的类的某个特定实例调用。实例方法不能脱离于现存的实例而被调用。
|
||||
|
||||
下面的例子,定义一个很简单的`Counter`类,`Counter`能被用来对一个动作发生的次数进行计数:
|
||||
|
||||
```swift
|
||||
class Counter {
|
||||
var count = 0
|
||||
func increment() {
|
||||
++count
|
||||
}
|
||||
func incrementBy(amount: Int) {
|
||||
count += amount
|
||||
}
|
||||
func reset() {
|
||||
count = 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Counter`类定义了三个实例方法:
|
||||
- `increment`让计数器按一递增;
|
||||
- `incrementBy(amount: Int)`让计数器按一个指定的整数值递增;
|
||||
- `reset`将计数器重置为0。
|
||||
|
||||
`Counter`这个类还声明了一个可变属性`count`,用它来保持对当前计数器值的追踪。
|
||||
|
||||
和调用属性一样,用点语法(dot syntax)调用实例方法:
|
||||
|
||||
```swift
|
||||
let counter = Counter()
|
||||
// 初始计数值是0
|
||||
counter.increment()
|
||||
// 计数值现在是1
|
||||
counter.incrementBy(5)
|
||||
// 计数值现在是6
|
||||
counter.reset()
|
||||
// 计数值现在是0
|
||||
```
|
||||
|
||||
<a name="local_and_external_parameter"></a>
|
||||
### 方法的局部参数名称和外部参数名称 (Local and External Parameter Names for Methods)
|
||||
|
||||
函数参数可以同时有一个局部名称(在函数体内部使用)和一个外部名称(在调用函数时使用),详情参见[指定外部参数名](./06_Functions.html#specifying_external_parameter_names)。方法参数也一样(因为方法就是函数,只是这个函数与某个类型相关联了)。
|
||||
|
||||
Swift 中的方法和 Objective-C 中的方法极其相似。像在 Objective-C 中一样,Swift 中方法的名称通常用一个介词指向方法的第一个参数,比如:`with`,`for`,`by`等等。前面的`Counter`类的例子中`incrementBy(_:)`方法就是这样的。介词的使用让方法在被调用时能像一个句子一样被解读。
|
||||
|
||||
具体来说,Swift 默认仅给方法的第一个参数名称一个局部参数名称;默认同时给第二个和后续的参数名称局部参数名称和外部参数名称。这个约定与典型的命名和调用约定相适应,与你在写 Objective-C 的方法时很相似。这个约定还让富于表达性的方法在调用时不需要再限定参数名称。
|
||||
|
||||
看看下面这个`Counter`的另一个版本(它定义了一个更复杂的`incrementBy(_:)`方法):
|
||||
|
||||
```swift
|
||||
class Counter {
|
||||
var count: Int = 0
|
||||
func incrementBy(amount: Int, numberOfTimes: Int) {
|
||||
count += amount * numberOfTimes
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`incrementBy(_:numberOfTimes:)`方法有两个参数: `amount`和`numberOfTimes`。默认情况下,Swift 只把`amount`当作一个局部名称,但是把`numberOfTimes`即看作局部名称又看作外部名称。下面调用这个方法:
|
||||
|
||||
```swift
|
||||
let counter = Counter()
|
||||
counter.incrementBy(5, numberOfTimes: 3)
|
||||
// counter 的值现在是 15
|
||||
```
|
||||
|
||||
你不必为第一个参数值再定义一个外部变量名:因为从函数名`incrementBy(_numberOfTimes:)`已经能很清楚地看出它的作用。但是第二个参数,就要被一个外部参数名称所限定,以便在方法被调用时明确它的作用。
|
||||
|
||||
上面描述的这种默认行为意味着在 Swift 中,定义方法使用了与 Objective-C 同样的语法风格,并且方法将以自然且富于表达性的方式被调用。
|
||||
|
||||
<a name="modifying_external_parameter_name_behavior_for_methods"></a>
|
||||
### 修改方法的外部参数名称(Modifying External Parameter Name Behavior for Methods)
|
||||
|
||||
有时为方法的第一个参数提供一个外部参数名称是非常有用的,尽管这不是默认的行为。你自己可以为第一个参数添加一个显式的外部名称。
|
||||
|
||||
相反,如果你不想为方法的第二个及后续的参数提供一个外部名称,可以通过使用下划线(`_`)作为该参数的显式外部名称,这样做将覆盖默认行为。
|
||||
|
||||
<a name="the_self_property"></a>
|
||||
### self 属性(The self Property)
|
||||
|
||||
类型的每一个实例都有一个隐含属性叫做`self`,`self`完全等同于该实例本身。你可以在一个实例的实例方法中使用这个隐含的`self`属性来引用当前实例。
|
||||
|
||||
上面例子中的`increment`方法还可以这样写:
|
||||
|
||||
```swift
|
||||
func increment() {
|
||||
self.count++
|
||||
}
|
||||
```
|
||||
|
||||
实际上,你不必在你的代码里面经常写`self`。不论何时,只要在一个方法中使用一个已知的属性或者方法名称,如果你没有明确地写`self`,Swift 假定你是指当前实例的属性或者方法。这种假定在上面的`Counter`中已经示范了:`Counter`中的三个实例方法中都使用的是`count`(而不是`self.count`)。
|
||||
|
||||
使用这条规则的主要场景是实例方法的某个参数名称与实例的某个属性名称相同的时候。在这种情况下,参数名称享有优先权,并且在引用属性时必须使用一种更严格的方式。这时你可以使用`self`属性来区分参数名称和属性名称。
|
||||
|
||||
下面的例子中,`self`消除方法参数`x`和实例属性`x`之间的歧义:
|
||||
|
||||
```swift
|
||||
struct Point {
|
||||
var x = 0.0, y = 0.0
|
||||
func isToTheRightOfX(x: Double) -> Bool {
|
||||
return self.x > x
|
||||
}
|
||||
}
|
||||
let somePoint = Point(x: 4.0, y: 5.0)
|
||||
if somePoint.isToTheRightOfX(1.0) {
|
||||
print("This point is to the right of the line where x == 1.0")
|
||||
}
|
||||
// 打印输出: This point is to the right of the line where x == 1.0
|
||||
```
|
||||
|
||||
如果不使用`self`前缀,Swift 就认为两次使用的`x`都指的是名称为`x`的函数参数。
|
||||
|
||||
<a name="modifying_value_types_from_within_instance_methods"></a>
|
||||
### 在实例方法中修改值类型(Modifying Value Types from Within Instance Methods)
|
||||
|
||||
结构体和枚举是**值类型**。默认情况下,值类型的属性不能在它的实例方法中被修改。
|
||||
|
||||
但是,如果你确实需要在某个特定的方法中修改结构体或者枚举的属性,你可以为这个方法选择`可变(mutating)`行为,然后就可以从其方法内部改变它的属性;并且这个方法做的任何改变都会在方法执行结束时写回到原始结构中。方法还可以给它隐含的`self`属性赋予一个全新的实例,这个新实例在方法结束时会替换现存实例。
|
||||
|
||||
要使用`可变`方法,将关键字`mutating` 放到方法的`func`关键字之前就可以了:
|
||||
|
||||
```swift
|
||||
struct Point {
|
||||
var x = 0.0, y = 0.0
|
||||
mutating func moveByX(deltaX: Double, y deltaY: Double) {
|
||||
x += deltaX
|
||||
y += deltaY
|
||||
}
|
||||
}
|
||||
var somePoint = Point(x: 1.0, y: 1.0)
|
||||
somePoint.moveByX(2.0, y: 3.0)
|
||||
print("The point is now at (\(somePoint.x), \(somePoint.y))")
|
||||
// 打印输出: "The point is now at (3.0, 4.0)"
|
||||
```
|
||||
|
||||
上面的`Point`结构体定义了一个可变方法 `moveByX(_:y:)` 来移动`Point`实例到给定的位置。该方法被调用时修改了这个点,而不是返回一个新的点。方法定义时加上了`mutating`关键字,从而允许修改属性。
|
||||
|
||||
注意,不能在结构体类型的常量(a constant of structure type)上调用可变方法,因为其属性不能被改变,即使属性是变量属性,详情参见[常量结构体的存储属性](./10_Properties.html#stored_properties_of_constant_structure_instances):
|
||||
|
||||
```swift
|
||||
let fixedPoint = Point(x: 3.0, y: 3.0)
|
||||
fixedPoint.moveByX(2.0, y: 3.0)
|
||||
// 这里将会报告一个错误
|
||||
```
|
||||
|
||||
<a name="assigning_to_self_within_a_mutating_method"></a>
|
||||
### 在可变方法中给 self 赋值(Assigning to self Within a Mutating Method)
|
||||
|
||||
可变方法能够赋给隐含属性`self`一个全新的实例。上面`Point`的例子可以用下面的方式改写:
|
||||
|
||||
```swift
|
||||
struct Point {
|
||||
var x = 0.0, y = 0.0
|
||||
mutating func moveByX(deltaX: Double, y deltaY: Double) {
|
||||
self = Point(x: x + deltaX, y: y + deltaY)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
新版的可变方法`moveByX(_:y:)`创建了一个新的结构体实例,它的 x 和 y 的值都被设定为目标值。调用这个版本的方法和调用上个版本的最终结果是一样的。
|
||||
|
||||
枚举的可变方法可以把`self`设置为同一枚举类型中不同的成员:
|
||||
|
||||
```swift
|
||||
enum TriStateSwitch {
|
||||
case Off, Low, High
|
||||
mutating func next() {
|
||||
switch self {
|
||||
case Off:
|
||||
self = Low
|
||||
case Low:
|
||||
self = High
|
||||
case High:
|
||||
self = Off
|
||||
}
|
||||
}
|
||||
}
|
||||
var ovenLight = TriStateSwitch.Low
|
||||
ovenLight.next()
|
||||
// ovenLight 现在等于 .High
|
||||
ovenLight.next()
|
||||
// ovenLight 现在等于 .Off
|
||||
```
|
||||
|
||||
上面的例子中定义了一个三态开关的枚举。每次调用`next()`方法时,开关在不同的电源状态(`Off`,`Low`,`High`)之间循环切换。
|
||||
|
||||
<a name="type_methods"></a>
|
||||
## 类型方法 (Type Methods)
|
||||
|
||||
实例方法是被某个类型的实例调用的方法。你也可以定义在类型本身上调用的方法,这种方法就叫做**类型方法**(Type Methods)。在方法的`func`关键字之前加上关键字`static`,来指定类型方法。类还可以用关键字`class`来允许子类重写父类的方法实现。
|
||||
|
||||
> 注意
|
||||
> 在 Objective-C 中,你只能为 Objective-C 的类类型(classes)定义类型方法(type-level methods)。在 Swift 中,你可以为所有的类、结构体和枚举定义类型方法。每一个类型方法都被它所支持的类型显式包含。
|
||||
|
||||
类型方法和实例方法一样用点语法调用。但是,你是在类型上调用这个方法,而不是在实例上调用。下面是如何在`SomeClass`类上调用类型方法的例子:
|
||||
|
||||
```swift
|
||||
class SomeClass {
|
||||
class func someTypeMethod() {
|
||||
// type method implementation goes here
|
||||
}
|
||||
}
|
||||
SomeClass.someTypeMethod()
|
||||
```
|
||||
|
||||
在类型方法的方法体(body)中,`self`指向这个类型本身,而不是类型的某个实例。这意味着你可以用`self`来消除类型属性和类型方法参数之间的歧义(类似于我们在前面处理实例属性和实例方法参数时做的那样)。
|
||||
|
||||
一般来说,在类型方法的方法体中,任何未限定的方法和属性名称,可以被本类中其他的类型方法和类型属性引用。一个类型方法可以直接通过类型方法的名称调用本类中的其它类型方法,而无需在方法名称前面加上类型名称。类似地,在结构体和枚举中,也能够直接通过类型属性的名称访问本类中的类型属性,而不需要前面加上类型名称。
|
||||
|
||||
下面的例子定义了一个名为`LevelTracker`结构体。它监测玩家的游戏发展情况(游戏的不同层次或阶段)。这是一个单人游戏,但也可以存储多个玩家在同一设备上的游戏信息。
|
||||
|
||||
游戏初始时,所有的游戏等级(除了等级 1)都被锁定。每次有玩家完成一个等级,这个等级就对这个设备上的所有玩家解锁。`LevelTracker`结构体用类型属性和方法监测游戏的哪个等级已经被解锁。它还监测每个玩家的当前等级。
|
||||
|
||||
```swift
|
||||
struct LevelTracker {
|
||||
static var highestUnlockedLevel = 1
|
||||
static func unlockLevel(level: Int) {
|
||||
if level > highestUnlockedLevel { highestUnlockedLevel = level }
|
||||
}
|
||||
static func levelIsUnlocked(level: Int) -> Bool {
|
||||
return level <= highestUnlockedLevel
|
||||
}
|
||||
var currentLevel = 1
|
||||
mutating func advanceToLevel(level: Int) -> Bool {
|
||||
if LevelTracker.levelIsUnlocked(level) {
|
||||
currentLevel = level
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`LevelTracker`监测玩家已解锁的最高等级。这个值被存储在类型属性`highestUnlockedLevel`中。
|
||||
|
||||
`LevelTracker`还定义了两个类型方法与`highestUnlockedLevel`配合工作。第一个类型方法是`unlockLevel`,一旦新等级被解锁,它会更新`highestUnlockedLevel`的值。第二个类型方法是`levelIsUnlocked`,如果某个给定的等级已经被解锁,它将返回`true`。(注意,尽管我们没有使用类似`LevelTracker.highestUnlockedLevel`的写法,这个类型方法还是能够访问类型属性`highestUnlockedLevel`)
|
||||
|
||||
除了类型属性和类型方法,`LevelTracker`还监测每个玩家的进度。它用实例属性`currentLevel`来监测每个玩家当前的等级。
|
||||
|
||||
为了便于管理`currentLevel`属性,`LevelTracker`定义了实例方法`advanceToLevel`。这个方法会在更新`currentLevel`之前检查所请求的新等级是否已经解锁。`advanceToLevel`方法返回布尔值以指示是否能够设置`currentLevel`。
|
||||
|
||||
下面,`Player`类使用`LevelTracker`来监测和更新每个玩家的发展进度:
|
||||
|
||||
```swift
|
||||
class Player {
|
||||
var tracker = LevelTracker()
|
||||
let playerName: String
|
||||
func completedLevel(level: Int) {
|
||||
LevelTracker.unlockLevel(level + 1)
|
||||
tracker.advanceToLevel(level + 1)
|
||||
}
|
||||
init(name: String) {
|
||||
playerName = name
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Player`类创建一个新的`LevelTracker`实例来监测这个用户的进度。它提供了`completedLevel`方法,一旦玩家完成某个指定等级就调用它。这个方法为所有玩家解锁下一等级,并且将当前玩家的进度更新为下一等级。(我们忽略了`advanceToLevel`返回的布尔值,因为之前调用`LevelTracker.unlockLevel`时就知道了这个等级已经被解锁了)。
|
||||
|
||||
你还可以为一个新的玩家创建一个`Player`的实例,然后看这个玩家完成等级一时发生了什么:
|
||||
|
||||
```swift
|
||||
var player = Player(name: "Argyrios")
|
||||
player.completedLevel(1)
|
||||
print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)")
|
||||
// 打印输出:highest unlocked level is now 2
|
||||
```
|
||||
|
||||
如果你创建了第二个玩家,并尝试让他开始一个没有被任何玩家解锁的等级,那么试图设置玩家当前等级将会失败:
|
||||
|
||||
```swift
|
||||
player = Player(name: "Beto")
|
||||
if player.tracker.advanceToLevel(6) {
|
||||
print("player is now on level 6")
|
||||
} else {
|
||||
print("level 6 has not yet been unlocked")
|
||||
}
|
||||
// 打印输出:level 6 has not yet been unlocked
|
||||
```
|
||||
@ -1,162 +0,0 @@
|
||||
# 下标(Subscripts)
|
||||
-----------------
|
||||
|
||||
> 1.0
|
||||
> 翻译:[siemenliu](https://github.com/siemenliu)
|
||||
> 校对:[zq54zquan](https://github.com/zq54zquan)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[shanks](http://codebuild.me)
|
||||
|
||||
> 2.1
|
||||
> 翻译+校对:[shanks](http://codebuild.me),[Realank](https://github.com/Realank)
|
||||
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [下标语法](#subscript_syntax)
|
||||
- [下标用法](#subscript_usage)
|
||||
- [下标选项](#subscript_options)
|
||||
|
||||
*下标* (subscripts)可以定义在类(class)、结构体(structure)和枚举(enumeration)中,是访问集合(collection),列表(list)或序列(sequence)中元素的快捷方式。可以使用下标的索引,设置和获取值,而不需要再调用对应的存取方法。举例来说,用下标访问一个`Array`实例中的元素可以写作`someArray[index]`,访问`Dictionary`实例中的元素可以写作`someDictionary[key]`。
|
||||
|
||||
一个类型可以定义多个下标,通过不同索引类型进行重载。下标不限于一维,你可以定义具有多个入参的下标满足自定义类型的需求。
|
||||
|
||||
<a name="subscript_syntax"></a>
|
||||
## 下标语法
|
||||
|
||||
下标允许你通过在实例名称后面的方括号中传入一个或者多个索引值来对实例进行存取。语法类似于实例方法语法和计算型属性语法的混合。与定义实例方法类似,定义下标使用`subscript`关键字,指定一个或多个输入参数和返回类型;与实例方法不同的是,下标可以设定为读写或只读。这种行为由 getter 和 setter 实现,有点类似计算型属性:
|
||||
|
||||
```swift
|
||||
subscript(index: Int) -> Int {
|
||||
get {
|
||||
// 返回一个适当的 Int 类型的值
|
||||
}
|
||||
|
||||
set(newValue) {
|
||||
// 执行适当的赋值操作
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`newValue`的类型和下标的返回类型相同。如同计算型属性,可以不指定 setter 的参数(`newValue`)。如果不指定参数,setter 会提供一个名为`newValue`的默认参数。
|
||||
|
||||
如同只读计算型属性,可以省略只读下标的`get`关键字:
|
||||
|
||||
```swift
|
||||
subscript(index: Int) -> Int {
|
||||
// 返回一个适当的 Int 类型的值
|
||||
}
|
||||
```
|
||||
|
||||
下面代码演示了只读下标的实现,这里定义了一个`TimesTable`结构体,用来表示传入整数的乘法表:
|
||||
|
||||
```swift
|
||||
struct TimesTable {
|
||||
let multiplier: Int
|
||||
subscript(index: Int) -> Int {
|
||||
return multiplier * index
|
||||
}
|
||||
}
|
||||
let threeTimesTable = TimesTable(multiplier: 3)
|
||||
print("six times three is \(threeTimesTable[6])")
|
||||
// 输出 "six times three is 18"
|
||||
```
|
||||
|
||||
在上例中,创建了一个`TimesTable`实例,用来表示整数`3`的乘法表。数值`3`被传递给结构体的构造函数,作为实例成员`multiplier`的值。
|
||||
|
||||
你可以通过下标访问`threeTimesTable`实例,例如上面演示的`threeTimesTable[6]`。这条语句查询了`3`的乘法表中的第六个元素,返回`3`的`6`倍即`18`。
|
||||
|
||||
> 注意
|
||||
> `TimesTable`例子基于一个固定的数学公式,对`threeTimesTable[someIndex]`进行赋值操作并不合适,因此下标定义为只读的。
|
||||
|
||||
<a name="subscript_usage"></a>
|
||||
## 下标用法
|
||||
|
||||
下标的确切含义取决于使用场景。下标通常作为访问集合(collection),列表(list)或序列(sequence)中元素的快捷方式。你可以针对自己特定的类或结构体的功能来自由地以最恰当的方式实现下标。
|
||||
|
||||
例如,Swift 的`Dictionary`类型实现下标用于对其实例中储存的值进行存取操作。为字典设值时,在下标中使用和字典的键类型相同的键,并把一个和字典的值类型相同的值赋给这个下标:
|
||||
|
||||
```swift
|
||||
var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4]
|
||||
numberOfLegs["bird"] = 2
|
||||
```
|
||||
|
||||
上例定义一个名为`numberOfLegs`的变量,并用一个包含三对键值的字典字面量初始化它。`numberOfLegs`字典的类型被推断为`[String: Int]`。字典创建完成后,该例子通过下标将`String`类型的键`bird`和`Int`类型的值`2`添加到字典中。
|
||||
|
||||
更多关于`Dictionary`下标的信息请参考[读取和修改字典](./04_Collection_Types.html#accessing_and_modifying_a_dictionary)
|
||||
|
||||
> 注意
|
||||
> Swift 的`Dictionary`类型的下标接受并返回可选类型的值。上例中的`numberOfLegs`字典通过下标返回的是一个`Int?`或者说“可选的int”。`Dictionary`类型之所以如此实现下标,是因为不是每个键都有个对应的值,同时这也提供了一种通过键删除对应值的方式,只需将键对应的值赋值为`nil`即可。
|
||||
|
||||
<a name="subscript_options"></a>
|
||||
## 下标选项
|
||||
|
||||
下标可以接受任意数量的入参,并且这些入参可以是任意类型。下标的返回值也可以是任意类型。下标可以使用变量参数和可变参数,但不能使用输入输出参数,也不能给参数设置默认值。
|
||||
|
||||
一个类或结构体可以根据自身需要提供多个下标实现,使用下标时将通过入参的数量和类型进行区分,自动匹配合适的下标,这就是*下标的重载*。
|
||||
|
||||
虽然接受单一入参的下标是最常见的,但也可以根据情况定义接受多个入参的下标。例如下例定义了一个`Matrix`结构体,用于表示一个`Double`类型的二维矩阵。`Matrix`结构体的下标接受两个整型参数:
|
||||
|
||||
```swift
|
||||
struct Matrix {
|
||||
let rows: Int, columns: Int
|
||||
var grid: [Double]
|
||||
init(rows: Int, columns: Int) {
|
||||
self.rows = rows
|
||||
self.columns = columns
|
||||
grid = Array(count: rows * columns, repeatedValue: 0.0)
|
||||
}
|
||||
func indexIsValidForRow(row: Int, column: Int) -> Bool {
|
||||
return row >= 0 && row < rows && column >= 0 && column < columns
|
||||
}
|
||||
subscript(row: Int, column: Int) -> Double {
|
||||
get {
|
||||
assert(indexIsValidForRow(row, column: column), "Index out of range")
|
||||
return grid[(row * columns) + column]
|
||||
}
|
||||
set {
|
||||
assert(indexIsValidForRow(row, column: column), "Index out of range")
|
||||
grid[(row * columns) + column] = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Matrix`提供了一个接受两个入参的构造方法,入参分别是`rows`和`columns`,创建了一个足够容纳`rows * columns`个`Double`类型的值的数组。通过传入数组长度和初始值`0.0`到数组的构造器,将矩阵中每个位置的值初始化为`0.0`。关于数组的这种构造方法请参考[创建一个空数组](./04_Collection_Types.html#creating_an_empty_array)。
|
||||
|
||||
你可以通过传入合适的`row`和`column`的数量来构造一个新的`Matrix`实例:
|
||||
|
||||
```swift
|
||||
var matrix = Matrix(rows: 2, columns: 2)
|
||||
```
|
||||
|
||||
上例中创建了一个`Matrix`实例来表示两行两列的矩阵。该`Matrix`实例的`grid`数组按照从左上到右下的阅读顺序将矩阵扁平化存储:
|
||||
|
||||

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

|
||||
|
||||
`Matrix`下标的 getter 和 setter 中都含有断言,用来检查下标入参`row`和`column`的值是否有效。为了方便进行断言,`Matrix`包含了一个名为`indexIsValidForRow(_:column:)`的便利方法,用来检查入参`row`和`column`的值是否在矩阵范围内:
|
||||
|
||||
```swift
|
||||
func indexIsValidForRow(row: Int, column: Int) -> Bool {
|
||||
return row >= 0 && row < rows && column >= 0 && column < columns
|
||||
}
|
||||
```
|
||||
|
||||
断言在下标越界时触发:
|
||||
|
||||
```swift
|
||||
let someValue = matrix[2, 2]
|
||||
// 断言将会触发,因为 [2, 2] 已经超过了 matrix 的范围
|
||||
```
|
||||
@ -1,238 +0,0 @@
|
||||
# 继承(Inheritance)
|
||||
-------------------
|
||||
|
||||
> 1.0
|
||||
> 翻译:[Hawstein](https://github.com/Hawstein)
|
||||
> 校对:[menlongsheng](https://github.com/menlongsheng)
|
||||
|
||||
> 2.0,2.1
|
||||
> 翻译+校对:[shanks](http://codebuild.me)
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [定义一个基类(Base class)](#defining_a_base_class)
|
||||
- [子类生成(Subclassing)](#subclassing)
|
||||
- [重写(Overriding)](#overriding)
|
||||
- [防止重写(Preventing Overrides)](#preventing_overrides)
|
||||
|
||||
一个类可以*继承(inherit)*另一个类的方法(methods),属性(properties)和其它特性。当一个类继承其它类时,继承类叫*子类(subclass)*,被继承类叫*超类(或父类,superclass)*。在 Swift 中,继承是区分「类」与其它类型的一个基本特征。
|
||||
|
||||
在 Swift 中,类可以调用和访问超类的方法,属性和下标(subscripts),并且可以重写(override)这些方法,属性和下标来优化或修改它们的行为。Swift 会检查你的重写定义在超类中是否有匹配的定义,以此确保你的重写行为是正确的。
|
||||
|
||||
可以为类中继承来的属性添加属性观察器(property observers),这样一来,当属性值改变时,类就会被通知到。可以为任何属性添加属性观察器,无论它原本被定义为存储型属性(stored property)还是计算型属性(computed property)。
|
||||
|
||||
<a name="defining_a_base_class"></a>
|
||||
## 定义一个基类(Base class)
|
||||
|
||||
不继承于其它类的类,称之为*基类(base class)*。
|
||||
|
||||
> 注意
|
||||
Swift 中的类并不是从一个通用的基类继承而来。如果你不为你定义的类指定一个超类的话,这个类就自动成为基类。
|
||||
|
||||
下面的例子定义了一个叫`Vehicle`的基类。这个基类声明了一个名为`currentSpeed `,默认值是`0.0`的存储属性(属性类型推断为`Double`)。`currentSpeed`属性的值被一个`String`类型的只读计算型属性`description`使用,用来创建车辆的描述。
|
||||
|
||||
`Vehicle`基类也定义了一个名为`makeNoise`的方法。这个方法实际上不为`Vehicle`实例做任何事,但之后将会被`Vehicle`的子类定制:
|
||||
|
||||
```swift
|
||||
class Vehicle {
|
||||
var currentSpeed = 0.0
|
||||
var description: String {
|
||||
return "traveling at \(currentSpeed) miles per hour"
|
||||
}
|
||||
func makeNoise() {
|
||||
// 什么也不做-因为车辆不一定会有噪音
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
您可以用初始化语法创建一个`Vehicle`的新实例,即类名后面跟一个空括号:
|
||||
|
||||
```swift
|
||||
let someVehicle = Vehicle()
|
||||
```
|
||||
|
||||
现在已经创建了一个`Vehicle`的新实例,你可以访问它的`description`属性来打印车辆的当前速度:
|
||||
|
||||
```swift
|
||||
print("Vehicle: \(someVehicle.description)")
|
||||
// Vehicle: traveling at 0.0 miles per hour
|
||||
```
|
||||
|
||||
`Vehicle`类定义了一个通用特性的车辆类,实际上没什么用处。为了让它变得更加有用,需要完善它从而能够描述一个更加具体类型的车辆。
|
||||
|
||||
<a name="subclassing"></a>
|
||||
## 子类生成(Subclassing)
|
||||
|
||||
*子类生成(Subclassing)*指的是在一个已有类的基础上创建一个新的类。子类继承超类的特性,并且可以进一步完善。你还可以为子类添加新的特性。
|
||||
|
||||
为了指明某个类的超类,将超类名写在子类名的后面,用冒号分隔:
|
||||
|
||||
```swift
|
||||
class SomeClass: SomeSuperclass {
|
||||
// 这里是子类的定义
|
||||
}
|
||||
```
|
||||
|
||||
下一个例子,定义一个叫`Bicycle`的子类,继承成父类`Vehicle`:
|
||||
|
||||
```swift
|
||||
class Bicycle: Vehicle {
|
||||
var hasBasket = false
|
||||
}
|
||||
```
|
||||
|
||||
新的`Bicycle`类自动获得`Vehicle`类的所有特性,比如`currentSpeed`和`description`属性,还有它的`makeNoise()`方法。
|
||||
|
||||
除了它所继承的特性,`Bicycle`类还定义了一个默认值为`false`的存储型属性`hasBasket`(属性推断为`Bool`)。
|
||||
|
||||
默认情况下,你创建任何新的`Bicycle`实例将不会有一个篮子(即`hasBasket`属性默认为`false`),创建该实例之后,你可以为特定的`Bicycle`实例设置`hasBasket`属性为`ture`:
|
||||
|
||||
```swift
|
||||
let bicycle = Bicycle()
|
||||
bicycle.hasBasket = true
|
||||
```
|
||||
|
||||
你还可以修改`Bicycle`实例所继承的`currentSpeed`属性,和查询实例所继承的`description`属性:
|
||||
|
||||
```swift
|
||||
bicycle.currentSpeed = 15.0
|
||||
print("Bicycle: \(bicycle.description)")
|
||||
// Bicycle: traveling at 15.0 miles per hour
|
||||
```
|
||||
|
||||
子类还可以继续被其它类继承,下面的示例为`Bicycle`创建了一个名为`Tandem`(双人自行车)的子类:
|
||||
|
||||
```swift
|
||||
class Tandem: Bicycle {
|
||||
var currentNumberOfPassengers = 0
|
||||
}
|
||||
```
|
||||
|
||||
`Tandem`从`Bicycle`继承了所有的属性与方法,这又使它同时继承了`Vehicle`的所有属性与方法。`Tandem`也增加了一个新的叫做`currentNumberOfPassengers`的存储型属性,默认值为`0`。
|
||||
|
||||
如果你创建了一个`Tandem`的实例,你可以使用它所有的新属性和继承的属性,还能查询从`Vehicle`继承来的只读属性`description`:
|
||||
|
||||
```swift
|
||||
let tandem = Tandem()
|
||||
tandem.hasBasket = true
|
||||
tandem.currentNumberOfPassengers = 2
|
||||
tandem.currentSpeed = 22.0
|
||||
print("Tandem: \(tandem.description)")
|
||||
// Tandem: traveling at 22.0 miles per hour
|
||||
```
|
||||
|
||||
<a name="overriding"></a>
|
||||
## 重写(Overriding)
|
||||
|
||||
子类可以为继承来的实例方法(instance method),类方法(class method),实例属性(instance property),或下标(subscript)提供自己定制的实现(implementation)。我们把这种行为叫*重写(overriding)*。
|
||||
|
||||
如果要重写某个特性,你需要在重写定义的前面加上`override`关键字。这么做,你就表明了你是想提供一个重写版本,而非错误地提供了一个相同的定义。意外的重写行为可能会导致不可预知的错误,任何缺少`override`关键字的重写都会在编译时被诊断为错误。
|
||||
|
||||
`override`关键字会提醒 Swift 编译器去检查该类的超类(或其中一个父类)是否有匹配重写版本的声明。这个检查可以确保你的重写定义是正确的。
|
||||
|
||||
### 访问超类的方法,属性及下标
|
||||
|
||||
当你在子类中重写超类的方法,属性或下标时,有时在你的重写版本中使用已经存在的超类实现会大有裨益。比如,你可以完善已有实现的行为,或在一个继承来的变量中存储一个修改过的值。
|
||||
|
||||
在合适的地方,你可以通过使用`super`前缀来访问超类版本的方法,属性或下标:
|
||||
|
||||
* 在方法`someMethod()`的重写实现中,可以通过`super.someMethod()`来调用超类版本的`someMethod()`方法。
|
||||
* 在属性`someProperty`的 getter 或 setter 的重写实现中,可以通过`super.someProperty`来访问超类版本的`someProperty`属性。
|
||||
* 在下标的重写实现中,可以通过`super[someIndex]`来访问超类版本中的相同下标。
|
||||
|
||||
### 重写方法
|
||||
|
||||
在子类中,你可以重写继承来的实例方法或类方法,提供一个定制或替代的方法实现。
|
||||
|
||||
下面的例子定义了`Vehicle`的一个新的子类,叫`Train`,它重写了从`Vehicle`类继承来的`makeNoise()`方法:
|
||||
|
||||
```swift
|
||||
class Train: Vehicle {
|
||||
override func makeNoise() {
|
||||
print("Choo Choo")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
如果你创建一个`Train`的新实例,并调用了它的`makeNoise()`方法,你就会发现`Train`版本的方法被调用:
|
||||
|
||||
```swift
|
||||
let train = Train()
|
||||
train.makeNoise()
|
||||
// 打印 "Choo Choo"
|
||||
```
|
||||
|
||||
### 重写属性
|
||||
|
||||
你可以重写继承来的实例属性或类型属性,提供自己定制的 getter 和 setter,或添加属性观察器使重写的属性可以观察属性值什么时候发生改变。
|
||||
|
||||
#### 重写属性的 Getters 和 Setters
|
||||
|
||||
你可以提供定制的 getter(或 setter)来重写任意继承来的属性,无论继承来的属性是存储型的还是计算型的属性。子类并不知道继承来的属性是存储型的还是计算型的,它只知道继承来的属性会有一个名字和类型。你在重写一个属性时,必需将它的名字和类型都写出来。这样才能使编译器去检查你重写的属性是与超类中同名同类型的属性相匹配的。
|
||||
|
||||
你可以将一个继承来的只读属性重写为一个读写属性,只需要在重写版本的属性里提供 getter 和 setter 即可。但是,你不可以将一个继承来的读写属性重写为一个只读属性。
|
||||
|
||||
> 注意
|
||||
如果你在重写属性中提供了 setter,那么你也一定要提供 getter。如果你不想在重写版本中的 getter 里修改继承来的属性值,你可以直接通过`super.someProperty`来返回继承来的值,其中`someProperty`是你要重写的属性的名字。
|
||||
|
||||
以下的例子定义了一个新类,叫`Car`,它是`Vehicle`的子类。这个类引入了一个新的存储型属性叫做`gear`,默认值为整数`1`。`Car`类重写了继承自`Vehicle`的`description`属性,提供包含当前档位的自定义描述:
|
||||
|
||||
```swift
|
||||
class Car: Vehicle {
|
||||
var gear = 1
|
||||
override var description: String {
|
||||
return super.description + " in gear \(gear)"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
重写的`description`属性首先要调用`super.description`返回`Vehicle`类的`description`属性。之后,`Car`类版本的`description`在末尾增加了一些额外的文本来提供关于当前档位的信息。
|
||||
|
||||
如果你创建了`Car`的实例并且设置了它的`gear`和`currentSpeed`属性,你可以看到它的`description`返回了`Car`中的自定义描述:
|
||||
|
||||
```swift
|
||||
let car = Car()
|
||||
car.currentSpeed = 25.0
|
||||
car.gear = 3
|
||||
print("Car: \(car.description)")
|
||||
// Car: traveling at 25.0 miles per hour in gear 3
|
||||
```
|
||||
|
||||
<a name="overriding_property_observers"></a>
|
||||
#### 重写属性观察器(Property Observer)
|
||||
|
||||
你可以通过重写属性为一个继承来的属性添加属性观察器。这样一来,当继承来的属性值发生改变时,你就会被通知到,无论那个属性原本是如何实现的。关于属性观察器的更多内容,请看[属性观察器](../chapter2/10_Properties.html#property_observers)。
|
||||
|
||||
> 注意
|
||||
你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。这些属性的值是不可以被设置的,所以,为它们提供`willSet`或`didSet`实现是不恰当。
|
||||
此外还要注意,你不可以同时提供重写的 setter 和重写的属性观察器。如果你想观察属性值的变化,并且你已经为那个属性提供了定制的 setter,那么你在 setter 中就可以观察到任何值变化了。
|
||||
|
||||
下面的例子定义了一个新类叫`AutomaticCar`,它是`Car`的子类。`AutomaticCar`表示自动挡汽车,它可以根据当前的速度自动选择合适的挡位:
|
||||
|
||||
```swift
|
||||
class AutomaticCar: Car {
|
||||
override var currentSpeed: Double {
|
||||
didSet {
|
||||
gear = Int(currentSpeed / 10.0) + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
当你设置`AutomaticCar`的`currentSpeed`属性,属性的`didSet`观察器就会自动地设置`gear`属性,为新的速度选择一个合适的挡位。具体来说就是,属性观察器将新的速度值除以`10`,然后向下取得最接近的整数值,最后加`1`来得到档位`gear`的值。例如,速度为`35.0`时,挡位为`4`:
|
||||
|
||||
```swift
|
||||
let automatic = AutomaticCar()
|
||||
automatic.currentSpeed = 35.0
|
||||
print("AutomaticCar: \(automatic.description)")
|
||||
// AutomaticCar: traveling at 35.0 miles per hour in gear 4
|
||||
```
|
||||
|
||||
<a name="preventing_overrides"></a>
|
||||
## 防止重写
|
||||
|
||||
你可以通过把方法,属性或下标标记为*`final`*来防止它们被重写,只需要在声明关键字前加上`final`修饰符即可(例如:`final var`,`final func`,`final class func`,以及`final subscript`)。
|
||||
|
||||
如果你重写了`final`方法,属性或下标,在编译时会报错。在类扩展中的方法,属性或下标也可以在扩展的定义里标记为 final 的。
|
||||
|
||||
你可以通过在关键字`class`前添加`final`修饰符(`final class`)来将整个类标记为 final 的。这样的类是不可被继承的,试图继承这样的类会导致编译报错。
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,114 +0,0 @@
|
||||
# 析构过程(Deinitialization)
|
||||
---------------------------
|
||||
|
||||
> 1.0
|
||||
> 翻译:[bruce0505](https://github.com/bruce0505)
|
||||
> 校对:[fd5788](https://github.com/fd5788)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[chenmingbiao](https://github.com/chenmingbiao)
|
||||
|
||||
> 2.1
|
||||
> 校对:[shanks](http://codebuild.me),2015-10-31
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [析构过程原理](#how_deinitialization_works)
|
||||
- [析构器实践](#deinitializers_in_action)
|
||||
|
||||
*析构器*只适用于类类型,当一个类的实例被释放之前,析构器会被立即调用。析构器用关键字`deinit`来标示,类似于构造器要用`init`来标示。
|
||||
|
||||
<a name="how_deinitialization_works"></a>
|
||||
##析构过程原理
|
||||
|
||||
Swift 会自动释放不再需要的实例以释放资源。如[自动引用计数](./16_Automatic_Reference_Counting.html)章节中所讲述,Swift 通过`自动引用计数(ARC)`处理实例的内存管理。通常当你的实例被释放时不需要手动地去清理。但是,当使用自己的资源时,你可能需要进行一些额外的清理。例如,如果创建了一个自定义的类来打开一个文件,并写入一些数据,你可能需要在类实例被释放之前手动去关闭该文件。
|
||||
|
||||
在类的定义中,每个类最多只能有一个析构器,而且析构器不带任何参数,如下所示:
|
||||
|
||||
```swift
|
||||
deinit {
|
||||
// 执行析构过程
|
||||
}
|
||||
```
|
||||
|
||||
析构器是在实例释放发生前被自动调用。你不能主动调用析构器。子类继承了父类的析构器,并且在子类析构器实现的最后,父类的析构器会被自动调用。即使子类没有提供自己的析构器,父类的析构器也同样会被调用。
|
||||
|
||||
因为直到实例的析构器被调用后,实例才会被释放,所以析构器可以访问实例的所有属性,并且可以根据那些属性可以修改它的行为(比如查找一个需要被关闭的文件)。
|
||||
|
||||
<a name="deinitializers_in_action"></a>
|
||||
##析构器实践
|
||||
|
||||
这是一个析构器实践的例子。这个例子描述了一个简单的游戏,这里定义了两种新类型,分别是`Bank`和`Player`。`Bank`类管理一种虚拟硬币,确保流通的硬币数量永远不可能超过 10,000。在游戏中有且只能有一个`Bank`存在,因此`Bank`用类来实现,并使用静态属性和静态方法来存储和管理其当前状态。
|
||||
|
||||
```swift
|
||||
class Bank {
|
||||
static var coinsInBank = 10_000
|
||||
static func vendCoins(var numberOfCoinsToVend: Int) -> Int {
|
||||
numberOfCoinsToVend = min(numberOfCoinsToVend, coinsInBank)
|
||||
coinsInBank -= numberOfCoinsToVend
|
||||
return numberOfCoinsToVend
|
||||
}
|
||||
static func receiveCoins(coins: Int) {
|
||||
coinsInBank += coins
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Bank`使用`coinsInBank`属性来跟踪它当前拥有的硬币数量。`Bank`还提供了两个方法,`vendCoins(_:)`和`receiveCoins(_:)`,分别用来处理硬币的分发和收集。
|
||||
|
||||
`vendCoins(_:)`方法在`Bank`对象分发硬币之前检查是否有足够的硬币。如果硬币不足,`Bank`对象会返回一个比请求时小的数字(如果`Bank`对象中没有硬币了就返回`0`)。`vendCoins`方法声明`numberOfCoinsToVend`为一个变量参数,这样就可以在方法体内部修改分发的硬币数量,而不需要定义一个新的变量。`vendCoins`方法返回一个整型值,表示提供的硬币的实际数量。
|
||||
|
||||
`receiveCoins(_:)`方法只是将`Bank`对象接收到的硬币数目加回硬币存储中。
|
||||
|
||||
`Player`类描述了游戏中的一个玩家。每一个玩家在任意时间都有一定数量的硬币存储在他们的钱包中。这通过玩家的`coinsInPurse`属性来表示:
|
||||
|
||||
```swift
|
||||
class Player {
|
||||
var coinsInPurse: Int
|
||||
init(coins: Int) {
|
||||
coinsInPurse = Bank.vendCoins(coins)
|
||||
}
|
||||
func winCoins(coins: Int) {
|
||||
coinsInPurse += Bank.vendCoins(coins)
|
||||
}
|
||||
deinit {
|
||||
Bank.receiveCoins(coinsInPurse)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
每个`Player`实例在初始化的过程中,都从`Bank`对象获取指定数量的硬币。如果没有足够的硬币可用,`Player`实例可能会收到比指定数量少的硬币.
|
||||
|
||||
`Player`类定义了一个`winCoins(_:)`方法,该方法从`Bank`对象获取一定数量的硬币,并把它们添加到玩家的钱包。`Player`类还实现了一个析构器,这个析构器在`Player`实例释放前被调用。在这里,析构器的作用只是将玩家的所有硬币都返还给`Bank`对象:
|
||||
|
||||
```swift
|
||||
var playerOne: Player? = Player(coins: 100)
|
||||
print("A new player has joined the game with \(playerOne!.coinsInPurse) coins")
|
||||
// 打印 "A new player has joined the game with 100 coins"
|
||||
print("There are now \(Bank.coinsInBank) coins left in the bank")
|
||||
// 打印 "There are now 9900 coins left in the bank"
|
||||
```
|
||||
|
||||
创建一个`Player`实例的时候,会向`Bank`对象请求 100 个硬币,如果有足够的硬币可用的话。这个`Player`实例存储在一个名为`playerOne`的可选类型的变量中。这里使用了一个可选类型的变量,因为玩家可以随时离开游戏,设置为可选使你可以追踪玩家当前是否在游戏中。
|
||||
|
||||
因为`playerOne`是可选的,所以访问其`coinsInPurse`属性来打印钱包中的硬币数量时,使用感叹号(`!`)来解包:
|
||||
|
||||
```swift
|
||||
playerOne!.winCoins(2_000)
|
||||
print("PlayerOne won 2000 coins & now has \(playerOne!.coinsInPurse) coins")
|
||||
// 输出 "PlayerOne won 2000 coins & now has 2100 coins"
|
||||
print("The bank now only has \(Bank.coinsInBank) coins left")
|
||||
// 输出 "The bank now only has 7900 coins left"
|
||||
```
|
||||
|
||||
这里,玩家已经赢得了 2,000 枚硬币,所以玩家的钱包中现在有 2,100 枚硬币,而`Bank`对象只剩余 7,900 枚硬币。
|
||||
|
||||
```swift
|
||||
playerOne = nil
|
||||
print("PlayerOne has left the game")
|
||||
// 打印 "PlayerOne has left the game"
|
||||
print("The bank now has \(Bank.coinsInBank) coins")
|
||||
// 打印 "The bank now has 10000 coins"
|
||||
```
|
||||
|
||||
玩家现在已经离开了游戏。这通过将可选类型的`playerOne`变量设置为`nil`来表示,意味着“没有`Player`实例”。当这一切发生时,`playerOne`变量对`Player`实例的引用被破坏了。没有其它属性或者变量引用`Player`实例,因此该实例会被释放,以便回收内存。在这之前,该实例的析构器被自动调用,玩家的硬币被返还给银行。
|
||||
@ -1,588 +0,0 @@
|
||||
# 自动引用计数(Automatic Reference Counting)
|
||||
-----------------
|
||||
|
||||
> 1.0
|
||||
> 翻译:[TimothyYe](https://github.com/TimothyYe)
|
||||
> 校对:[Hawstein](https://github.com/Hawstein)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[Channe](https://github.com/Channe)
|
||||
|
||||
> 2.1
|
||||
> 翻译:[Channe](https://github.com/Channe)
|
||||
> 校对:[shanks](http://codebuild.me),[Realank](https://github.com/Realank) ,2016-01-23
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [自动引用计数的工作机制](#how_arc_works)
|
||||
- [自动引用计数实践](#arc_in_action)
|
||||
- [类实例之间的循环强引用](#strong_reference_cycles_between_class_instances)
|
||||
- [解决实例之间的循环强引用](#resolving_strong_reference_cycles_between_class_instances)
|
||||
- [闭包引起的循环强引用](#strong_reference_cycles_for_closures)
|
||||
- [解决闭包引起的循环强引用](#resolving_strong_reference_cycles_for_closures)
|
||||
|
||||
Swift 使用自动引用计数(ARC)机制来跟踪和管理你的应用程序的内存。通常情况下,Swift 内存管理机制会一直起作用,你无须自己来考虑内存的管理。ARC 会在类的实例不再被使用时,自动释放其占用的内存。
|
||||
|
||||
然而在少数情况下,为了能帮助你管理内存,ARC 需要更多的,代码之间关系的信息。本章描述了这些情况,并且为你示范怎样才能使 ARC 来管理你的应用程序的所有内存。
|
||||
|
||||
> 注意
|
||||
引用计数仅仅应用于类的实例。结构体和枚举类型是值类型,不是引用类型,也不是通过引用的方式存储和传递。
|
||||
|
||||
<a name="how_arc_works"></a>
|
||||
## 自动引用计数的工作机制
|
||||
|
||||
当你每次创建一个类的新的实例的时候,ARC 会分配一块内存来储存该实例信息。内存中会包含实例的类型信息,以及这个实例所有相关的存储型属性的值。
|
||||
|
||||
此外,当实例不再被使用时,ARC 释放实例所占用的内存,并让释放的内存能挪作他用。这确保了不再被使用的实例,不会一直占用内存空间。
|
||||
|
||||
然而,当 ARC 收回和释放了正在被使用中的实例,该实例的属性和方法将不能再被访问和调用。实际上,如果你试图访问这个实例,你的应用程序很可能会崩溃。
|
||||
|
||||
为了确保使用中的实例不会被销毁,ARC 会跟踪和计算每一个实例正在被多少属性,常量和变量所引用。哪怕实例的引用数为1,ARC都不会销毁这个实例。
|
||||
|
||||
为了使上述成为可能,无论你将实例赋值给属性、常量或变量,它们都会创建此实例的强引用。之所以称之为“强”引用,是因为它会将实例牢牢地保持住,只要强引用还在,实例是不允许被销毁的。
|
||||
|
||||
<a name="arc_in_action"></a>
|
||||
## 自动引用计数实践
|
||||
|
||||
下面的例子展示了自动引用计数的工作机制。例子以一个简单的`Person`类开始,并定义了一个叫`name`的常量属性:
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
let name: String
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
print("\(name) is being initialized")
|
||||
}
|
||||
deinit {
|
||||
print("\(name) is being deinitialized")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Person`类有一个构造函数,此构造函数为实例的`name`属性赋值,并打印一条消息以表明初始化过程生效。`Person`类也拥有一个析构函数,这个析构函数会在实例被销毁时打印一条消息。
|
||||
|
||||
接下来的代码片段定义了三个类型为`Person?`的变量,用来按照代码片段中的顺序,为新的`Person`实例建立多个引用。由于这些变量是被定义为可选类型(`Person?`,而不是`Person`),它们的值会被自动初始化为`nil`,目前还不会引用到`Person`类的实例。
|
||||
|
||||
```swift
|
||||
var reference1: Person?
|
||||
var reference2: Person?
|
||||
var reference3: Person?
|
||||
```
|
||||
|
||||
现在你可以创建`Person`类的新实例,并且将它赋值给三个变量中的一个:
|
||||
|
||||
```swift
|
||||
reference1 = Person(name: "John Appleseed")
|
||||
// prints "John Appleseed is being initialized”
|
||||
```
|
||||
|
||||
应当注意到当你调用`Person`类的构造函数的时候,`“John Appleseed is being initialized”`会被打印出来。由此可以确定构造函数被执行。
|
||||
|
||||
由于`Person`类的新实例被赋值给了`reference1`变量,所以`reference1`到`Person`类的新实例之间建立了一个强引用。正是因为这一个强引用,ARC 会保证`Person`实例被保持在内存中不被销毁。
|
||||
|
||||
如果你将同一个`Person`实例也赋值给其他两个变量,该实例又会多出两个强引用:
|
||||
|
||||
```swift
|
||||
reference2 = reference1
|
||||
reference3 = reference1
|
||||
```
|
||||
|
||||
现在这一个`Person`实例已经有三个强引用了。
|
||||
|
||||
如果你通过给其中两个变量赋值`nil`的方式断开两个强引用(包括最先的那个强引用),只留下一个强引用,`Person`实例不会被销毁:
|
||||
|
||||
```swift
|
||||
reference1 = nil
|
||||
reference2 = nil
|
||||
```
|
||||
|
||||
在你清楚地表明不再使用这个`Person`实例时,即第三个也就是最后一个强引用被断开时,ARC 会销毁它:
|
||||
|
||||
```swift
|
||||
reference3 = nil
|
||||
// 打印 “John Appleseed is being deinitialized”
|
||||
```
|
||||
|
||||
<a name="strong_reference_cycles_between_class_instances"></a>
|
||||
## 类实例之间的循环强引用
|
||||
|
||||
在上面的例子中,ARC 会跟踪你所新创建的`Person`实例的引用数量,并且会在`Person`实例不再被需要时销毁它。
|
||||
|
||||
然而,我们可能会写出一个类实例的强引用数永远不能变成`0`的代码。如果两个类实例互相持有对方的强引用,因而每个实例都让对方一直存在,就是这种情况。这就是所谓的循环强引用。
|
||||
|
||||
你可以通过定义类之间的关系为弱引用或无主引用,以替代强引用,从而解决循环强引用的问题。具体的过程在[解决类实例之间的循环强引用](#resolving_strong_reference_cycles_between_class_instances)中有描述。不管怎样,在你学习怎样解决循环强引用之前,很有必要了解一下它是怎样产生的。
|
||||
|
||||
下面展示了一个不经意产生循环强引用的例子。例子定义了两个类:`Person`和`Apartment`,用来建模公寓和它其中的居民:
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
let name: String
|
||||
init(name: String) { self.name = name }
|
||||
var apartment: Apartment?
|
||||
deinit { print("\(name) is being deinitialized") }
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
class Apartment {
|
||||
let unit: String
|
||||
init(unit: String) { self.unit = unit }
|
||||
var tenant: Person?
|
||||
deinit { print("Apartment \(unit) is being deinitialized") }
|
||||
}
|
||||
```
|
||||
|
||||
每一个`Person`实例有一个类型为`String`,名字为`name`的属性,并有一个可选的初始化为`nil`的`apartment`属性。`apartment`属性是可选的,因为一个人并不总是拥有公寓。
|
||||
|
||||
类似的,每个`Apartment`实例有一个叫`unit`,类型为`String`的属性,并有一个可选的初始化为`nil`的`tenant`属性。`tenant`属性是可选的,因为一栋公寓并不总是有居民。
|
||||
|
||||
这两个类都定义了析构函数,用以在类实例被析构的时候输出信息。这让你能够知晓`Person`和`Apartment`的实例是否像预期的那样被销毁。
|
||||
|
||||
接下来的代码片段定义了两个可选类型的变量`john`和`unit4A`,并分别被设定为下面的`Apartment`和`Person`的实例。这两个变量都被初始化为`nil`,这正是可选的优点:
|
||||
|
||||
```swift
|
||||
var john: Person?
|
||||
var unit4A: Apartment?
|
||||
```
|
||||
|
||||
现在你可以创建特定的`Person`和`Apartment`实例并将赋值给`john`和`unit4A`变量:
|
||||
|
||||
```swift
|
||||
john = Person(name: "John Appleseed")
|
||||
unit4A = Apartment(unit: "4A")
|
||||
```
|
||||
|
||||
在两个实例被创建和赋值后,下图表现了强引用的关系。变量`john`现在有一个指向`Person`实例的强引用,而变量`unit4A`有一个指向`Apartment`实例的强引用:
|
||||
|
||||

|
||||
|
||||
现在你能够将这两个实例关联在一起,这样人就能有公寓住了,而公寓也有了房客。注意感叹号是用来展开和访问可选变量`john`和`unit4A`中的实例,这样实例的属性才能被赋值:
|
||||
|
||||
```swift
|
||||
john!.apartment = unit4A
|
||||
unit4A!.tenant = john
|
||||
```
|
||||
|
||||
在将两个实例联系在一起之后,强引用的关系如图所示:
|
||||
|
||||

|
||||
|
||||
不幸的是,这两个实例关联后会产生一个循环强引用。`Person`实例现在有了一个指向`Apartment`实例的强引用,而`Apartment`实例也有了一个指向`Person`实例的强引用。因此,当你断开`john`和`unit4A`变量所持有的强引用时,引用计数并不会降为`0`,实例也不会被 ARC 销毁:
|
||||
|
||||
```swift
|
||||
john = nil
|
||||
unit4A = nil
|
||||
```
|
||||
|
||||
注意,当你把这两个变量设为`nil`时,没有任何一个析构函数被调用。循环强引用会一直阻止`Person`和`Apartment`类实例的销毁,这就在你的应用程序中造成了内存泄漏。
|
||||
|
||||
在你将`john`和`unit4A`赋值为`nil`后,强引用关系如下图:
|
||||
|
||||

|
||||
|
||||
`Person`和`Apartment`实例之间的强引用关系保留了下来并且不会被断开。
|
||||
|
||||
<a name="resolving_strong_reference_cycles_between_class_instances"></a>
|
||||
## 解决实例之间的循环强引用
|
||||
|
||||
Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题:弱引用(weak reference)和无主引用(unowned reference)。
|
||||
|
||||
弱引用和无主引用允许循环引用中的一个实例引用另外一个实例而不保持强引用。这样实例能够互相引用而不产生循环强引用。
|
||||
|
||||
对于生命周期中会变为`nil`的实例使用弱引用。相反地,对于初始化赋值后再也不会被赋值为`nil`的实例,使用无主引用。
|
||||
|
||||
<a name="weak_references"></a>
|
||||
### 弱引用
|
||||
|
||||
弱引用不会对其引用的实例保持强引用,因而不会阻止 ARC 销毁被引用的实例。这个特性阻止了引用变为循环强引用。声明属性或者变量时,在前面加上`weak`关键字表明这是一个弱引用。
|
||||
|
||||
在实例的生命周期中,如果某些时候引用没有值,那么弱引用可以避免循环强引用。如果引用总是有值,则可以使用无主引用,在[无主引用](#unowned_references)中有描述。在上面`Apartment`的例子中,一个公寓的生命周期中,有时是没有“居民”的,因此适合使用弱引用来解决循环强引用。
|
||||
|
||||
> 注意
|
||||
> 弱引用必须被声明为变量,表明其值能在运行时被修改。弱引用不能被声明为常量。
|
||||
|
||||
因为弱引用可以没有值,你必须将每一个弱引用声明为可选类型。在 Swift 中,推荐使用可选类型描述可能没有值的类型。
|
||||
|
||||
因为弱引用不会保持所引用的实例,即使引用存在,实例也有可能被销毁。因此,ARC 会在引用的实例被销毁后自动将其赋值为`nil`。你可以像其他可选值一样,检查弱引用的值是否存在,你将永远不会访问已销毁的实例的引用。
|
||||
|
||||
下面的例子跟上面`Person`和`Apartment`的例子一致,但是有一个重要的区别。这一次,`Apartment`的`tenant`属性被声明为弱引用:
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
let name: String
|
||||
init(name: String) { self.name = name }
|
||||
var apartment: Apartment?
|
||||
deinit { print("\(name) is being deinitialized") }
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
class Apartment {
|
||||
let unit: String
|
||||
init(unit: String) { self.unit = unit }
|
||||
weak var tenant: Person?
|
||||
deinit { print("Apartment \(unit) is being deinitialized") }
|
||||
}
|
||||
```
|
||||
|
||||
然后跟之前一样,建立两个变量(`john`和`unit4A`)之间的强引用,并关联两个实例:
|
||||
|
||||
```swift
|
||||
var john: Person?
|
||||
var unit4A: Apartment?
|
||||
|
||||
john = Person(name: "John Appleseed")
|
||||
unit4A = Apartment(unit: "4A")
|
||||
|
||||
john!.apartment = unit4A
|
||||
unit4A!.tenant = john
|
||||
```
|
||||
|
||||
现在,两个关联在一起的实例的引用关系如下图所示:
|
||||
|
||||

|
||||
|
||||
`Person`实例依然保持对`Apartment`实例的强引用,但是`Apartment`实例只持有对`Person`实例的弱引用。这意味着当你断开`john`变量所保持的强引用时,再也没有指向`Person`实例的强引用了:
|
||||
|
||||

|
||||
|
||||
由于再也没有指向`Person`实例的强引用,该实例会被销毁:
|
||||
|
||||
```swift
|
||||
john = nil
|
||||
// 打印 “John Appleseed is being deinitialized”
|
||||
```
|
||||
|
||||
唯一剩下的指向`Apartment`实例的强引用来自于变量`unit4A`。如果你断开这个强引用,再也没有指向`Apartment`实例的强引用了:
|
||||
|
||||

|
||||
|
||||
由于再也没有指向`Apartment`实例的强引用,该实例也会被销毁:
|
||||
|
||||
```swift
|
||||
unit4A = nil
|
||||
// 打印 “Apartment 4A is being deinitialized”
|
||||
```
|
||||
|
||||
上面的两段代码展示了变量`john`和`unit4A`在被赋值为`nil`后,`Person`实例和`Apartment`实例的析构函数都打印出“销毁”的信息。这证明了引用循环被打破了。
|
||||
|
||||
> 注意
|
||||
在使用垃圾收集的系统里,弱指针有时用来实现简单的缓冲机制,因为没有强引用的对象只会在内存压力触发垃圾收集时才被销毁。但是在 ARC 中,一旦值的最后一个强引用被移除,就会被立即销毁,这导致弱引用并不适合上面的用途。
|
||||
|
||||
<a name="unowned_references"></a>
|
||||
### 无主引用
|
||||
|
||||
和弱引用类似,无主引用不会牢牢保持住引用的实例。和弱引用不同的是,无主引用是永远有值的。因此,无主引用总是被定义为非可选类型(non-optional type)。你可以在声明属性或者变量时,在前面加上关键字`unowned`表示这是一个无主引用。
|
||||
|
||||
由于无主引用是非可选类型,你不需要在使用它的时候将它展开。无主引用总是可以被直接访问。不过 ARC 无法在实例被销毁后将无主引用设为`nil`,因为非可选类型的变量不允许被赋值为`nil`。
|
||||
|
||||
> 注意
|
||||
> 如果你试图在实例被销毁后,访问该实例的无主引用,会触发运行时错误。使用无主引用,你必须确保引用始终指向一个未销毁的实例。
|
||||
> 还需要注意的是如果你试图访问实例已经被销毁的无主引用,Swift 确保程序会直接崩溃,而不会发生无法预期的行为。所以你应当避免这样的事情发生。
|
||||
|
||||
下面的例子定义了两个类,`Customer`和`CreditCard`,模拟了银行客户和客户的信用卡。这两个类中,每一个都将另外一个类的实例作为自身的属性。这种关系可能会造成循环强引用。
|
||||
|
||||
`Customer`和`CreditCard`之间的关系与前面弱引用例子中`Apartment`和`Person`的关系略微不同。在这个数据模型中,一个客户可能有或者没有信用卡,但是一张信用卡总是关联着一个客户。为了表示这种关系,`Customer`类有一个可选类型的`card`属性,但是`CreditCard`类有一个非可选类型的`customer`属性。
|
||||
|
||||
此外,只能通过将一个`number`值和`customer`实例传递给`CreditCard`构造函数的方式来创建`CreditCard`实例。这样可以确保当创建`CreditCard`实例时总是有一个`customer`实例与之关联。
|
||||
|
||||
由于信用卡总是关联着一个客户,因此将`customer`属性定义为无主引用,用以避免循环强引用:
|
||||
|
||||
```swift
|
||||
class Customer {
|
||||
let name: String
|
||||
var card: CreditCard?
|
||||
init(name: String) {
|
||||
self.name = name
|
||||
}
|
||||
deinit { print("\(name) is being deinitialized") }
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
class CreditCard {
|
||||
let number: UInt64
|
||||
unowned let customer: Customer
|
||||
init(number: UInt64, customer: Customer) {
|
||||
self.number = number
|
||||
self.customer = customer
|
||||
}
|
||||
deinit { print("Card #\(number) is being deinitialized") }
|
||||
}
|
||||
```
|
||||
|
||||
> 注意
|
||||
> `CreditCard`类的`number`属性被定义为`UInt64`类型而不是`Int`类型,以确保`number`属性的存储量在 32 位和 64 位系统上都能足够容纳 16 位的卡号。
|
||||
|
||||
下面的代码片段定义了一个叫`john`的可选类型`Customer`变量,用来保存某个特定客户的引用。由于是可选类型,所以变量被初始化为`nil`:
|
||||
|
||||
```swift
|
||||
var john: Customer?
|
||||
```
|
||||
|
||||
现在你可以创建`Customer`类的实例,用它初始化`CreditCard`实例,并将新创建的`CreditCard`实例赋值为客户的`card`属性:
|
||||
|
||||
```swift
|
||||
john = Customer(name: "John Appleseed")
|
||||
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
|
||||
```
|
||||
|
||||
在你关联两个实例后,它们的引用关系如下图所示:
|
||||
|
||||

|
||||
|
||||
`Customer`实例持有对`CreditCard`实例的强引用,而`CreditCard`实例持有对`Customer`实例的无主引用。
|
||||
|
||||
由于`customer`的无主引用,当你断开`john`变量持有的强引用时,再也没有指向`Customer`实例的强引用了:
|
||||
|
||||

|
||||
|
||||
由于再也没有指向`Customer`实例的强引用,该实例被销毁了。其后,再也没有指向`CreditCard`实例的强引用,该实例也随之被销毁了:
|
||||
|
||||
```swift
|
||||
john = nil
|
||||
// 打印 “John Appleseed is being deinitialized”
|
||||
// 打印 ”Card #1234567890123456 is being deinitialized”
|
||||
```
|
||||
|
||||
最后的代码展示了在`john`变量被设为`nil`后`Customer`实例和`CreditCard`实例的构造函数都打印出了“销毁”的信息。
|
||||
|
||||
<a name="unowned_references_and_implicitly_unwrapped_optional_properties"></a>
|
||||
###无主引用以及隐式解析可选属性
|
||||
|
||||
上面弱引用和无主引用的例子涵盖了两种常用的需要打破循环强引用的场景。
|
||||
|
||||
`Person`和`Apartment`的例子展示了两个属性的值都允许为`nil`,并会潜在的产生循环强引用。这种场景最适合用弱引用来解决。
|
||||
|
||||
`Customer`和`CreditCard`的例子展示了一个属性的值允许为`nil`,而另一个属性的值不允许为`nil`,这也可能会产生循环强引用。这种场景最适合通过无主引用来解决。
|
||||
|
||||
然而,存在着第三种场景,在这种场景中,两个属性都必须有值,并且初始化完成后永远不会为`nil`。在这种场景中,需要一个类使用无主属性,而另外一个类使用隐式解析可选属性。
|
||||
|
||||
这使两个属性在初始化完成后能被直接访问(不需要可选展开),同时避免了循环引用。这一节将为你展示如何建立这种关系。
|
||||
|
||||
下面的例子定义了两个类,`Country`和`City`,每个类将另外一个类的实例保存为属性。在这个模型中,每个国家必须有首都,每个城市必须属于一个国家。为了实现这种关系,`Country`类拥有一个`capitalCity`属性,而`City`类有一个`country`属性:
|
||||
|
||||
```swift
|
||||
class Country {
|
||||
let name: String
|
||||
var capitalCity: City!
|
||||
init(name: String, capitalName: String) {
|
||||
self.name = name
|
||||
self.capitalCity = City(name: capitalName, country: self)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```swift
|
||||
class City {
|
||||
let name: String
|
||||
unowned let country: Country
|
||||
init(name: String, country: Country) {
|
||||
self.name = name
|
||||
self.country = country
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
为了建立两个类的依赖关系,`City`的构造函数接受一个`Country`实例作为参数,并且将实例保存到`country`属性。
|
||||
|
||||
`Country`的构造函数调用了`City`的构造函数。然而,只有`Country`的实例完全初始化后,`Country`的构造函数才能把`self`传给`City`的构造函数。(在[两段式构造过程](./14_Initialization.html#two_phase_initialization)中有具体描述)
|
||||
|
||||
为了满足这种需求,通过在类型结尾处加上感叹号(`City!`)的方式,将`Country`的`capitalCity`属性声明为隐式解析可选类型的属性。这意味着像其他可选类型一样,`capitalCity`属性的默认值为`nil`,但是不需要展开它的值就能访问它。(在[隐式解析可选类型](./01_The_Basics.html#implicityly_unwrapped_optionals)中有描述)
|
||||
|
||||
由于`capitalCity`默认值为`nil`,一旦`Country`的实例在构造函数中给`name`属性赋值后,整个初始化过程就完成了。这意味着一旦`name`属性被赋值后,`Country`的构造函数就能引用并传递隐式的`self`。`Country`的构造函数在赋值`capitalCity`时,就能将`self`作为参数传递给`City`的构造函数。
|
||||
|
||||
以上的意义在于你可以通过一条语句同时创建`Country`和`City`的实例,而不产生循环强引用,并且`capitalCity`的属性能被直接访问,而不需要通过感叹号来展开它的可选值:
|
||||
|
||||
```swift
|
||||
var country = Country(name: "Canada", capitalName: "Ottawa")
|
||||
print("\(country.name)'s capital city is called \(country.capitalCity.name)")
|
||||
// 打印 “Canada's capital city is called Ottawa”
|
||||
```
|
||||
|
||||
在上面的例子中,使用隐式解析可选值意味着满足了类的构造函数的两个构造阶段的要求。`capitalCity`属性在初始化完成后,能像非可选值一样使用和存取,同时还避免了循环强引用。
|
||||
|
||||
<a name="strong_reference_cycles_for_closures"></a>
|
||||
##闭包引起的循环强引用
|
||||
|
||||
前面我们看到了循环强引用是在两个类实例属性互相保持对方的强引用时产生的,还知道了如何用弱引用和无主引用来打破这些循环强引用。
|
||||
|
||||
循环强引用还会发生在当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了这个类实例时。这个闭包体中可能访问了实例的某个属性,例如`self.someProperty`,或者闭包中调用了实例的某个方法,例如`self.someMethod()`。这两种情况都导致了闭包“捕获”`self`,从而产生了循环强引用。
|
||||
|
||||
循环强引用的产生,是因为闭包和类相似,都是引用类型。当你把一个闭包赋值给某个属性时,你是将这个闭包的引用赋值给了属性。实质上,这跟之前的问题是一样的——两个强引用让彼此一直有效。但是,和两个类实例不同,这次一个是类实例,另一个是闭包。
|
||||
|
||||
Swift 提供了一种优雅的方法来解决这个问题,称之为`闭包捕获列表`(closure capture list)。同样的,在学习如何用闭包捕获列表打破循环强引用之前,先来了解一下这里的循环强引用是如何产生的,这对我们很有帮助。
|
||||
|
||||
下面的例子为你展示了当一个闭包引用了`self`后是如何产生一个循环强引用的。例子中定义了一个叫`HTMLElement`的类,用一种简单的模型表示 HTML 文档中的一个单独的元素:
|
||||
|
||||
```swift
|
||||
class HTMLElement {
|
||||
|
||||
let name: String
|
||||
let text: String?
|
||||
|
||||
lazy var asHTML: Void -> String = {
|
||||
if let text = self.text {
|
||||
return "<\(self.name)>\(text)</\(self.name)>"
|
||||
} else {
|
||||
return "<\(self.name) />"
|
||||
}
|
||||
}
|
||||
|
||||
init(name: String, text: String? = nil) {
|
||||
self.name = name
|
||||
self.text = text
|
||||
}
|
||||
|
||||
deinit {
|
||||
print("\(name) is being deinitialized")
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
`HTMLElement`类定义了一个`name`属性来表示这个元素的名称,例如代表段落的`“p”`,或者代表换行的`“br”`。`HTMLElement`还定义了一个可选属性`text`,用来设置 HTML 元素呈现的文本。
|
||||
|
||||
除了上面的两个属性,`HTMLElement`还定义了一个`lazy`属性`asHTML`。这个属性引用了一个将`name`和`text`组合成 HTML 字符串片段的闭包。该属性是`Void -> String`类型,或者可以理解为“一个没有参数,返回`String`的函数”。
|
||||
|
||||
默认情况下,闭包赋值给了`asHTML`属性,这个闭包返回一个代表 HTML 标签的字符串。如果`text`值存在,该标签就包含可选值`text`;如果`text`不存在,该标签就不包含文本。对于段落元素,根据`text`是`“some text”`还是`nil`,闭包会返回`"<p>some text</p>"`或者`"<p />"`。
|
||||
|
||||
可以像实例方法那样去命名、使用`asHTML`属性。然而,由于`asHTML`是闭包而不是实例方法,如果你想改变特定 HTML 元素的处理方式的话,可以用自定义的闭包来取代默认值。
|
||||
|
||||
例如,可以将一个闭包赋值给`asHTML`属性,这个闭包能在`text`属性是`nil`时使用默认文本,这是为了避免返回一个空的 HTML 标签:
|
||||
|
||||
```swift
|
||||
let heading = HTMLElement(name: "h1")
|
||||
let defaultText = "some default text"
|
||||
heading.asHTML = {
|
||||
return "<\(heading.name)>\(heading.text ?? defaultText)</\(heading.name)>"
|
||||
}
|
||||
print(heading.asHTML())
|
||||
// 打印 “<h1>some default text</h1>”
|
||||
```
|
||||
|
||||
> 注意
|
||||
`asHTML`声明为`lazy`属性,因为只有当元素确实需要被处理为 HTML 输出的字符串时,才需要使用`asHTML`。也就是说,在默认的闭包中可以使用`self`,因为只有当初始化完成以及`self`确实存在后,才能访问`lazy`属性。
|
||||
|
||||
`HTMLElement`类只提供了一个构造函数,通过`name`和`text`(如果有的话)参数来初始化一个新元素。该类也定义了一个析构函数,当`HTMLElement`实例被销毁时,打印一条消息。
|
||||
|
||||
下面的代码展示了如何用`HTMLElement`类创建实例并打印消息:
|
||||
|
||||
```swift
|
||||
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
|
||||
print(paragraph!.asHTML())
|
||||
// 打印 “<p>hello, world</p>”
|
||||
```
|
||||
|
||||
> 注意
|
||||
上面的`paragraph`变量定义为可选类型的`HTMLElement`,因此我们可以赋值`nil`给它来演示循环强引用。
|
||||
|
||||
不幸的是,上面写的`HTMLElement`类产生了类实例和作为`asHTML`默认值的闭包之间的循环强引用。循环强引用如下图所示:
|
||||
|
||||

|
||||
|
||||
实例的`asHTML`属性持有闭包的强引用。但是,闭包在其闭包体内使用了`self`(引用了`self.name`和`self.text`),因此闭包捕获了`self`,这意味着闭包又反过来持有了`HTMLElement`实例的强引用。这样两个对象就产生了循环强引用。(更多关于闭包捕获值的信息,请参考[值捕获](./07_Closures.html#capturing_values))。
|
||||
|
||||
> 注意
|
||||
虽然闭包多次使用了`self`,它只捕获`HTMLElement`实例的一个强引用。
|
||||
|
||||
如果设置`paragraph`变量为`nil`,打破它持有的`HTMLElement`实例的强引用,`HTMLElement`实例和它的闭包都不会被销毁,也是因为循环强引用:
|
||||
|
||||
```swift
|
||||
paragraph = nil
|
||||
```
|
||||
|
||||
注意,`HTMLElement`的析构函数中的消息并没有被打印,证明了`HTMLElement`实例并没有被销毁。
|
||||
|
||||
<a name="resolving_strong_reference_cycles_for_closures"></a>
|
||||
##解决闭包引起的循环强引用
|
||||
|
||||
在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。捕获列表定义了闭包体内捕获一个或者多个引用类型的规则。跟解决两个类实例间的循环强引用一样,声明每个捕获的引用为弱引用或无主引用,而不是强引用。应当根据代码关系来决定使用弱引用还是无主引用。
|
||||
|
||||
> 注意
|
||||
Swift 有如下要求:只要在闭包内使用`self`的成员,就要用`self.someProperty`或者`self.someMethod()`(而不只是`someProperty`或`someMethod()`)。这提醒你可能会一不小心就捕获了`self`。
|
||||
|
||||
<a name="defining_a_capture_list"></a>
|
||||
###定义捕获列表
|
||||
|
||||
捕获列表中的每一项都由一对元素组成,一个元素是`weak`或`unowned`关键字,另一个元素是类实例的引用(例如`self`)或初始化过的变量(如`delegate = self.delegate!`)。这些项在方括号中用逗号分开。
|
||||
|
||||
如果闭包有参数列表和返回类型,把捕获列表放在它们前面:
|
||||
|
||||
```swift
|
||||
lazy var someClosure: (Int, String) -> String = {
|
||||
[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
|
||||
// 这里是闭包的函数体
|
||||
}
|
||||
```
|
||||
|
||||
如果闭包没有指明参数列表或者返回类型,即它们会通过上下文推断,那么可以把捕获列表和关键字`in`放在闭包最开始的地方:
|
||||
|
||||
```swift
|
||||
lazy var someClosure: Void -> String = {
|
||||
[unowned self, weak delegate = self.delegate!] in
|
||||
// 这里是闭包的函数体
|
||||
}
|
||||
```
|
||||
|
||||
<a name="weak_and_unowned_references"></a>
|
||||
###弱引用和无主引用
|
||||
|
||||
在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为`无主引用`。
|
||||
|
||||
相反的,在被捕获的引用可能会变为`nil`时,将闭包内的捕获定义为`弱引用`。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为`nil`。这使我们可以在闭包体内检查它们是否存在。
|
||||
|
||||
> 注意
|
||||
如果被捕获的引用绝对不会变为`nil`,应该用无主引用,而不是弱引用。
|
||||
|
||||
前面的`HTMLElement`例子中,无主引用是正确的解决循环强引用的方法。这样编写`HTMLElement`类来避免循环强引用:
|
||||
|
||||
```swift
|
||||
class HTMLElement {
|
||||
|
||||
let name: String
|
||||
let text: String?
|
||||
|
||||
lazy var asHTML: Void -> String = {
|
||||
[unowned self] in
|
||||
if let text = self.text {
|
||||
return "<\(self.name)>\(text)</\(self.name)>"
|
||||
} else {
|
||||
return "<\(self.name) />"
|
||||
}
|
||||
}
|
||||
|
||||
init(name: String, text: String? = nil) {
|
||||
self.name = name
|
||||
self.text = text
|
||||
}
|
||||
|
||||
deinit {
|
||||
print("\(name) is being deinitialized")
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
上面的`HTMLElement`实现和之前的实现一致,除了在`asHTML`闭包中多了一个捕获列表。这里,捕获列表是`[unowned self]`,表示“将`self`捕获为无主引用而不是强引用”。
|
||||
|
||||
和之前一样,我们可以创建并打印`HTMLElement`实例:
|
||||
|
||||
```swift
|
||||
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
|
||||
print(paragraph!.asHTML())
|
||||
// 打印 “<p>hello, world</p>”
|
||||
```
|
||||
|
||||
使用捕获列表后引用关系如下图所示:
|
||||
|
||||

|
||||
|
||||
这一次,闭包以无主引用的形式捕获`self`,并不会持有`HTMLElement`实例的强引用。如果将`paragraph`赋值为`nil`,`HTMLElement`实例将会被销毁,并能看到它的析构函数打印出的消息:
|
||||
|
||||
```swift
|
||||
paragraph = nil
|
||||
// 打印 “p is being deinitialized”
|
||||
```
|
||||
|
||||
你可以查看[捕获列表](../chapter3/04_Expressions.html)章节,获取更多关于捕获列表的信息。
|
||||
@ -1,401 +0,0 @@
|
||||
# 可选链式调用(Optional Chaining)
|
||||
|
||||
-----------------
|
||||
|
||||
> 1.0
|
||||
> 翻译:[Jasonbroker](https://github.com/Jasonbroker)
|
||||
> 校对:[numbbbbb](https://github.com/numbbbbb), [stanzhai](https://github.com/stanzhai)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[lyojo](https://github.com/lyojo)
|
||||
|
||||
> 2.1
|
||||
> 校对:[shanks](http://codebuild.me),2015-10-31
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [使用可选链式调用代替强制展开](#optional_chaining_as_an_alternative_to_forced_unwrapping)
|
||||
- [为可选链式调用定义模型类](#defining_model_classes_for_optional_chaining)
|
||||
- [通过可选链式调用访问属性](#accessing_properties_through_optional_chaining)
|
||||
- [通过可选链式调用调用方法](#calling_methods_through_optional_chaining)
|
||||
- [通过可选链式调用访问下标](#accessing_subscripts_through_optional_chaining)
|
||||
- [连接多层可选链式调用](#linking_multiple_levels_of_chaining)
|
||||
- [在方法的可选返回值上进行可选链式调用](#chaining_on_methods_with_optional_return_values)
|
||||
|
||||
可选链式调用(Optional Chaining)是一种可以在当前值可能为`nil`的可选值上请求和调用属性、方法及下标的方法。如果可选值有值,那么调用就会成功;如果可选值是`nil`,那么调用将返回`nil`。多个调用可以连接在一起形成一个调用链,如果其中任何一个节点为`nil`,整个调用链都会失败,即返回`nil`。
|
||||
|
||||
> 注意
|
||||
Swift 的可选链式调用和 Objective-C 中向`nil`发送消息有些相像,但是 Swift 的可选链式调用可以应用于任意类型,并且能检查调用是否成功。
|
||||
|
||||
<a name="optional_chaining_as_an_alternative_to_forced_unwrapping"></a>
|
||||
## 使用可选链式调用代替强制展开
|
||||
|
||||
通过在想调用的属性、方法、或下标的可选值(optional value)后面放一个问号(`?`),可以定义一个可选链。这一点很像在可选值后面放一个叹号(`!`)来强制展开它的值。它们的主要区别在于当可选值为空时可选链式调用只会调用失败,然而强制展开将会触发运行时错误。
|
||||
|
||||
为了反映可选链式调用可以在空值(`nil`)上调用的事实,不论这个调用的属性、方法及下标返回的值是不是可选值,它的返回结果都是一个可选值。你可以利用这个返回值来判断你的可选链式调用是否调用成功,如果调用有返回值则说明调用成功,返回`nil`则说明调用失败。
|
||||
|
||||
特别地,可选链式调用的返回结果与原本的返回结果具有相同的类型,但是被包装成了一个可选值。例如,使用可选链式调用访问属性,当可选链式调用成功时,如果属性原本的返回结果是`Int`类型,则会变为`Int?`类型。
|
||||
|
||||
下面几段代码将解释可选链式调用和强制展开的不同。
|
||||
|
||||
首先定义两个类`Person`和`Residence`:
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
var residence: Residence?
|
||||
}
|
||||
|
||||
class Residence {
|
||||
var numberOfRooms = 1
|
||||
}
|
||||
```
|
||||
|
||||
`Residence`有一个`Int`类型的属性`numberOfRooms`,其默认值为`1`。`Person`具有一个可选的`residence`属性,其类型为`Residence?`。
|
||||
|
||||
如果创建一个新的`Person`实例,因为它的`residence`属性是可选的,`john`属性将初始化为`nil`:
|
||||
|
||||
```swift
|
||||
let john = Person()
|
||||
```
|
||||
|
||||
如果使用叹号(`!`)强制展开获得这个`john`的`residence`属性中的`numberOfRooms`值,会触发运行时错误,因为这时`residence`没有可以展开的值:
|
||||
|
||||
```swift
|
||||
let roomCount = john.residence!.numberOfRooms
|
||||
// 这会引发运行时错误
|
||||
```
|
||||
|
||||
`john.residence`为非`nil`值的时候,上面的调用会成功,并且把`roomCount`设置为`Int`类型的房间数量。正如上面提到的,当`residence`为`nil`的时候上面这段代码会触发运行时错误。
|
||||
|
||||
可选链式调用提供了另一种访问`numberOfRooms`的方式,使用问号(`?`)来替代原来的叹号(`!`):
|
||||
|
||||
```swift
|
||||
if let roomCount = john.residence?.numberOfRooms {
|
||||
print("John's residence has \(roomCount) room(s).")
|
||||
} else {
|
||||
print("Unable to retrieve the number of rooms.")
|
||||
}
|
||||
// 打印 “Unable to retrieve the number of rooms.”
|
||||
```
|
||||
|
||||
在`residence`后面添加问号之后,Swift 就会在`residence`不为`nil`的情况下访问`numberOfRooms`。
|
||||
|
||||
因为访问`numberOfRooms`有可能失败,可选链式调用会返回`Int?`类型,或称为“可选的 `Int`”。如上例所示,当`residence`为`nil`的时候,可选的`Int`将会为`nil`,表明无法访问`numberOfRooms`。访问成功时,可选的`Int`值会通过可选绑定展开,并赋值给非可选类型的`roomCount`常量。
|
||||
|
||||
要注意的是,即使`numberOfRooms`是非可选的`Int`时,这一点也成立。只要使用可选链式调用就意味着`numberOfRooms`会返回一个`Int?`而不是`Int`。
|
||||
|
||||
可以将一个`Residence`的实例赋给`john.residence`,这样它就不再是`nil`了:
|
||||
|
||||
```swift
|
||||
john.residence = Residence()
|
||||
```
|
||||
|
||||
`john.residence`现在包含一个实际的`Residence`实例,而不再是`nil`。如果你试图使用先前的可选链式调用访问`numberOfRooms`,它现在将返回值为`1`的`Int?`类型的值:
|
||||
|
||||
```swift
|
||||
if let roomCount = john.residence?.numberOfRooms {
|
||||
print("John's residence has \(roomCount) room(s).")
|
||||
} else {
|
||||
print("Unable to retrieve the number of rooms.")
|
||||
}
|
||||
// 打印 “John's residence has 1 room(s).”
|
||||
```
|
||||
|
||||
<a name="defining_model_classes_for_optional_chaining"></a>
|
||||
## 为可选链式调用定义模型类
|
||||
|
||||
通过使用可选链式调用可以调用多层属性、方法和下标。这样可以在复杂的模型中向下访问各种子属性,并且判断能否访问子属性的属性、方法或下标。
|
||||
|
||||
下面这段代码定义了四个模型类,这些例子包括多层可选链式调用。为了方便说明,在`Person`和`Residence`的基础上增加了`Room`类和`Address`类,以及相关的属性、方法以及下标。
|
||||
|
||||
`Person`类的定义基本保持不变:
|
||||
|
||||
```swift
|
||||
class Person {
|
||||
var residence: Residence?
|
||||
}
|
||||
```
|
||||
|
||||
`Residence`类比之前复杂些,增加了一个名为`rooms`的变量属性,该属性被初始化为`[Room]`类型的空数组:
|
||||
|
||||
```swift
|
||||
class Residence {
|
||||
var rooms = [Room]()
|
||||
var numberOfRooms: Int {
|
||||
return rooms.count
|
||||
}
|
||||
subscript(i: Int) -> Room {
|
||||
get {
|
||||
return rooms[i]
|
||||
}
|
||||
set {
|
||||
rooms[i] = newValue
|
||||
}
|
||||
}
|
||||
func printNumberOfRooms() {
|
||||
print("The number of rooms is \(numberOfRooms)")
|
||||
}
|
||||
var address: Address?
|
||||
}
|
||||
```
|
||||
|
||||
现在`Residence`有了一个存储`Room`实例的数组,`numberOfRooms`属性被实现为计算型属性,而不是存储型属性。`numberOfRooms`属性简单地返回`rooms`数组的`count`属性的值。
|
||||
|
||||
`Residence`还提供了访问`rooms`数组的快捷方式,即提供可读写的下标来访问`rooms`数组中指定位置的元素。
|
||||
|
||||
此外,`Residence`还提供了`printNumberOfRooms()`方法,这个方法的作用是打印`numberOfRooms`的值。
|
||||
|
||||
最后,`Residence`还定义了一个可选属性`address`,其类型为`Address?`。`Address`类的定义在下面会说明。
|
||||
|
||||
`Room`类是一个简单类,其实例被存储在`rooms`数组中。该类只包含一个属性`name`,以及一个用于将该属性设置为适当的房间名的初始化函数:
|
||||
|
||||
```swift
|
||||
class Room {
|
||||
let name: String
|
||||
init(name: String) { self.name = name }
|
||||
}
|
||||
```
|
||||
|
||||
最后一个类是`Address`,这个类有三个`String?`类型的可选属性。`buildingName`以及`buildingNumber`属性分别表示某个大厦的名称和号码,第三个属性`street`表示大厦所在街道的名称:
|
||||
|
||||
```swift
|
||||
class Address {
|
||||
var buildingName: String?
|
||||
var buildingNumber: String?
|
||||
var street: String?
|
||||
func buildingIdentifier() -> String? {
|
||||
if buildingName != nil {
|
||||
return buildingName
|
||||
} else if buildingNumber != nil && street != nil {
|
||||
return "\(buildingNumber) \(street)"
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Address`类提供了`buildingIdentifier()`方法,返回值为`String?`。 如果`buildingName`有值则返回`buildingName`。或者,如果`buildingNumber`和`street`均有值则返回`buildingNumber`。否则,返回`nil`。
|
||||
|
||||
<a name="accessing_properties_through_optional_chaining"></a>
|
||||
## 通过可选链式调用访问属性
|
||||
|
||||
正如[使用可选链式调用代替强制展开](#optional_chaining_as_an_alternative_to_forced_unwrapping)中所述,可以通过可选链式调用在一个可选值上访问它的属性,并判断访问是否成功。
|
||||
|
||||
下面的代码创建了一个`Person`实例,然后像之前一样,尝试访问`numberOfRooms`属性:
|
||||
|
||||
```swift
|
||||
let john = Person()
|
||||
if let roomCount = john.residence?.numberOfRooms {
|
||||
print("John's residence has \(roomCount) room(s).")
|
||||
} else {
|
||||
print("Unable to retrieve the number of rooms.")
|
||||
}
|
||||
// 打印 “Unable to retrieve the number of rooms.”
|
||||
```
|
||||
|
||||
因为`john.residence`为`nil`,所以这个可选链式调用依旧会像先前一样失败。
|
||||
|
||||
还可以通过可选链式调用来设置属性值:
|
||||
|
||||
```swift
|
||||
let someAddress = Address()
|
||||
someAddress.buildingNumber = "29"
|
||||
someAddress.street = "Acacia Road"
|
||||
john.residence?.address = someAddress
|
||||
```
|
||||
|
||||
在这个例子中,通过`john.residence`来设定`address`属性也会失败,因为`john.residence`当前为`nil`。
|
||||
|
||||
上面代码中的赋值过程是可选链式调用的一部分,这意味着可选链式调用失败时,等号右侧的代码不会被执行。对于上面的代码来说,很难验证这一点,因为像这样赋值一个常量没有任何副作用。下面的代码完成了同样的事情,但是它使用一个函数来创建`Address`实例,然后将该实例返回用于赋值。该函数会在返回前打印“Function was called”,这使你能验证等号右侧的代码是否被执行。
|
||||
|
||||
```swift
|
||||
func createAddress() -> Address {
|
||||
print("Function was called.")
|
||||
|
||||
let someAddress = Address()
|
||||
someAddress.buildingNumber = "29"
|
||||
someAddress.street = "Acacia Road"
|
||||
|
||||
return someAddress
|
||||
}
|
||||
john.residence?.address = createAddress()
|
||||
```
|
||||
|
||||
没有任何打印消息,可以看出`createAddress()`函数并未被执行。
|
||||
|
||||
<a name="calling_methods_through_optional_chaining"></a>
|
||||
## 通过可选链式调用调用方法
|
||||
|
||||
可以通过可选链式调用来调用方法,并判断是否调用成功,即使这个方法没有返回值。
|
||||
|
||||
`Residence`类中的`printNumberOfRooms()`方法打印当前的`numberOfRooms`值,如下所示:
|
||||
|
||||
```swift
|
||||
func printNumberOfRooms() {
|
||||
print("The number of rooms is \(numberOfRooms)")
|
||||
}
|
||||
```
|
||||
|
||||
这个方法没有返回值。然而,没有返回值的方法具有隐式的返回类型`Void`,如[无返回值函数](./06_Functions.html#functions_without_return_values)中所述。这意味着没有返回值的方法也会返回`()`,或者说空的元组。
|
||||
|
||||
如果在可选值上通过可选链式调用来调用这个方法,该方法的返回类型会是`Void?`,而不是`Void`,因为通过可选链式调用得到的返回值都是可选的。这样我们就可以使用`if`语句来判断能否成功调用`printNumberOfRooms()`方法,即使方法本身没有定义返回值。通过判断返回值是否为`nil`可以判断调用是否成功:
|
||||
|
||||
```swift
|
||||
if john.residence?.printNumberOfRooms() != nil {
|
||||
print("It was possible to print the number of rooms.")
|
||||
} else {
|
||||
print("It was not possible to print the number of rooms.")
|
||||
}
|
||||
// 打印 “It was not possible to print the number of rooms.”
|
||||
```
|
||||
|
||||
同样的,可以据此判断通过可选链式调用为属性赋值是否成功。在上面的[通过可选链式调用访问属性](#accessing_properties_through_optional_chaining)的例子中,我们尝试给`john.residence`中的`address`属性赋值,即使`residence`为`nil`。通过可选链式调用给属性赋值会返回`Void?`,通过判断返回值是否为`nil`就可以知道赋值是否成功:
|
||||
|
||||
```swift
|
||||
if (john.residence?.address = someAddress) != nil {
|
||||
print("It was possible to set the address.")
|
||||
} else {
|
||||
print("It was not possible to set the address.")
|
||||
}
|
||||
// 打印 “It was not possible to set the address.”
|
||||
```
|
||||
|
||||
<a name="accessing_subscripts_through_optional_chaining"></a>
|
||||
## 通过可选链式调用访问下标
|
||||
|
||||
通过可选链式调用,我们可以在一个可选值上访问下标,并且判断下标调用是否成功。
|
||||
|
||||
> 注意
|
||||
通过可选链式调用访问可选值的下标时,应该将问号放在下标方括号的前面而不是后面。可选链式调用的问号一般直接跟在可选表达式的后面。
|
||||
|
||||
下面这个例子用下标访问`john.residence`属性存储的`Residence`实例的`rooms`数组中的第一个房间的名称,因为`john.residence`为`nil`,所以下标调用失败了:
|
||||
|
||||
```swift
|
||||
if let firstRoomName = john.residence?[0].name {
|
||||
print("The first room name is \(firstRoomName).")
|
||||
} else {
|
||||
print("Unable to retrieve the first room name.")
|
||||
}
|
||||
// 打印 “Unable to retrieve the first room name.”
|
||||
```
|
||||
|
||||
在这个例子中,问号直接放在`john.residence`的后面,并且在方括号的前面,因为`john.residence`是可选值。
|
||||
|
||||
类似的,可以通过下标,用可选链式调用来赋值:
|
||||
|
||||
```swift
|
||||
john.residence?[0] = Room(name: "Bathroom")
|
||||
```
|
||||
|
||||
这次赋值同样会失败,因为`residence`目前是`nil`。
|
||||
|
||||
如果你创建一个`Residence`实例,并为其`rooms`数组添加一些`Room`实例,然后将`Residence`实例赋值给`john.residence`,那就可以通过可选链和下标来访问数组中的元素:
|
||||
|
||||
```swift
|
||||
let johnsHouse = Residence()
|
||||
johnsHouse.rooms.append(Room(name: "Living Room"))
|
||||
johnsHouse.rooms.append(Room(name: "Kitchen"))
|
||||
john.residence = johnsHouse
|
||||
|
||||
if let firstRoomName = john.residence?[0].name {
|
||||
print("The first room name is \(firstRoomName).")
|
||||
} else {
|
||||
print("Unable to retrieve the first room name.")
|
||||
}
|
||||
// 打印 “The first room name is Living Room.”
|
||||
```
|
||||
|
||||
<a name="accessing_subscripts_of_optional_type"></a>
|
||||
### 访问可选类型的下标
|
||||
|
||||
如果下标返回可选类型值,比如 Swift 中`Dictionary`类型的键的下标,可以在下标的结尾括号后面放一个问号来在其可选返回值上进行可选链式调用:
|
||||
|
||||
```swift
|
||||
var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
|
||||
testScores["Dave"]?[0] = 91
|
||||
testScores["Bev"]?[0]++
|
||||
testScores["Brian"]?[0] = 72
|
||||
// "Dave" 数组现在是 [91, 82, 84],"Bev" 数组现在是 [80, 94, 81]
|
||||
```
|
||||
|
||||
上面的例子中定义了一个`testScores`数组,包含了两个键值对,把`String`类型的键映射到一个`Int`值的数组。这个例子用可选链式调用把`"Dave"`数组中第一个元素设为`91`,把`"Bev"`数组的第一个元素`+1`,然后尝试把`"Brian"`数组中的第一个元素设为`72`。前两个调用成功,因为`testScores`字典中包含`"Dave"`和`"Bev"`这两个键。但是`testScores`字典中没有`"Brian"`这个键,所以第三个调用失败。
|
||||
|
||||
<a name="linking_multiple_levels_of_chaining"></a>
|
||||
## 连接多层可选链式调用
|
||||
|
||||
可以通过连接多个可选链式调用在更深的模型层级中访问属性、方法以及下标。然而,多层可选链式调用不会增加返回值的可选层级。
|
||||
|
||||
也就是说:
|
||||
|
||||
+ 如果你访问的值不是可选的,可选链式调用将会返回可选值。
|
||||
+ 如果你访问的值就是可选的,可选链式调用不会让可选返回值变得“更可选”。
|
||||
|
||||
因此:
|
||||
|
||||
+ 通过可选链式调用访问一个`Int`值,将会返回`Int?`,无论使用了多少层可选链式调用。
|
||||
+ 类似的,通过可选链式调用访问`Int?`值,依旧会返回`Int?`值,并不会返回`Int??`。
|
||||
|
||||
下面的例子尝试访问`john`中的`residence`属性中的`address`属性中的`street`属性。这里使用了两层可选链式调用,`residence`以及`address`都是可选值:
|
||||
|
||||
```swift
|
||||
if let johnsStreet = john.residence?.address?.street {
|
||||
print("John's street name is \(johnsStreet).")
|
||||
} else {
|
||||
print("Unable to retrieve the address.")
|
||||
}
|
||||
// 打印 “Unable to retrieve the address.”
|
||||
```
|
||||
|
||||
`john.residence`现在包含一个有效的`Residence`实例。然而,`john.residence.address`的值当前为`nil`。因此,调用`john.residence?.address?.street`会失败。
|
||||
|
||||
需要注意的是,上面的例子中,`street`的属性为`String?`。`john.residence?.address?.street`的返回值也依然是`String?`,即使已经使用了两层可选链式调用。
|
||||
|
||||
如果为`john.residence.address`赋值一个`Address`实例,并且为`address`中的`street`属性设置一个有效值,我们就能过通过可选链式调用来访问`street`属性:
|
||||
|
||||
```swift
|
||||
let johnsAddress = Address()
|
||||
johnsAddress.buildingName = "The Larches"
|
||||
johnsAddress.street = "Laurel Street"
|
||||
john.residence?.address = johnsAddress
|
||||
|
||||
if let johnsStreet = john.residence?.address?.street {
|
||||
print("John's street name is \(johnsStreet).")
|
||||
} else {
|
||||
print("Unable to retrieve the address.")
|
||||
}
|
||||
// 打印 “John's street name is Laurel Street.”
|
||||
```
|
||||
|
||||
在上面的例子中,因为`john.residence`包含一个有效的`Residence`实例,所以对`john.residence`的`address`属性赋值将会成功。
|
||||
|
||||
<a name="chaining_on_methods_with_optional_return_values"></a>
|
||||
## 在方法的可选返回值上进行可选链式调用
|
||||
|
||||
上面的例子展示了如何在一个可选值上通过可选链式调用来获取它的属性值。我们还可以在一个可选值上通过可选链式调用来调用方法,并且可以根据需要继续在方法的可选返回值上进行可选链式调用。
|
||||
|
||||
在下面的例子中,通过可选链式调用来调用`Address`的`buildingIdentifier()`方法。这个方法返回`String?`类型的值。如上所述,通过可选链式调用来调用该方法,最终的返回值依旧会是`String?`类型:
|
||||
|
||||
```swift
|
||||
if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
|
||||
print("John's building identifier is \(buildingIdentifier).")
|
||||
}
|
||||
// 打印 “John's building identifier is The Larches.”
|
||||
```
|
||||
|
||||
如果要在该方法的返回值上进行可选链式调用,在方法的圆括号后面加上问号即可:
|
||||
|
||||
```swift
|
||||
if let beginsWithThe =
|
||||
john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
|
||||
if beginsWithThe {
|
||||
print("John's building identifier begins with \"The\".")
|
||||
} else {
|
||||
print("John's building identifier does not begin with \"The\".")
|
||||
}
|
||||
}
|
||||
// 打印 “John's building identifier begins with "The".”
|
||||
```
|
||||
|
||||
> 注意
|
||||
在上面的例子中,在方法的圆括号后面加上问号是因为你要在`buildingIdentifier()`方法的可选返回值上进行可选链式调用,而不是方法本身。
|
||||
@ -1,233 +0,0 @@
|
||||
# 错误处理(Error Handling)
|
||||
-----------------
|
||||
|
||||
> 2.1
|
||||
> 翻译+校对:[lyojo](https://github.com/lyojo) [ray16897188](https://github.com/ray16897188) 2015-10-23
|
||||
> 校对:[shanks](http://codebuild.me) 2015-10-24
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [表示并抛出错误](#representing_and_throwing_errors)
|
||||
- [处理错误](#handling_errors)
|
||||
- [指定清理操作](#specifying_cleanup_actions)
|
||||
|
||||
*错误处理(Error handling)*是响应错误以及从错误中恢复的过程。Swift 提供了在运行时对可恢复错误的抛出、捕获、传递和操作的一流支持。
|
||||
|
||||
某些操作无法保证总是执行完所有代码或总是生成有用的结果。可选类型可用来表示值缺失,但是当某个操作失败时,最好能得知失败的原因,从而可以作出相应的应对。
|
||||
|
||||
举个例子,假如有个从磁盘上的某个文件读取数据并进行处理的任务,该任务会有多种可能失败的情况,包括指定路径下文件并不存在,文件不具有可读权限,或者文件编码格式不兼容。区分这些不同的失败情况可以让程序解决并处理某些错误,然后把它解决不了的错误报告给用户。
|
||||
|
||||
> 注意
|
||||
Swift 中的错误处理涉及到错误处理模式,这会用到 Cocoa 和 Objective-C 中的`NSError`。关于这个类的更多信息请参见 [Using Swift with Cocoa and Objective-C (Swift 2.1)](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 中的[错误处理](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html#//apple_ref/doc/uid/TP40014216-CH7-ID10)。
|
||||
|
||||
<a name="representing_and_throwing_errors"></a>
|
||||
##表示并抛出错误
|
||||
|
||||
在 Swift 中,错误用符合`ErrorType`协议的类型的值来表示。这个空协议表明该类型可以用于错误处理。
|
||||
|
||||
Swift 的枚举类型尤为适合构建一组相关的错误状态,枚举的关联值还可以提供错误状态的额外信息。例如,你可以这样表示在一个游戏中操作自动贩卖机时可能会出现的错误状态:
|
||||
|
||||
```swift
|
||||
enum VendingMachineError: ErrorType {
|
||||
case InvalidSelection //选择无效
|
||||
case InsufficientFunds(coinsNeeded: Int) //金额不足
|
||||
case OutOfStock //缺货
|
||||
}
|
||||
```
|
||||
|
||||
抛出一个错误可以让你表明有意外情况发生,导致正常的执行流程无法继续执行。抛出错误使用`throws`关键字。例如,下面的代码抛出一个错误,提示贩卖机还需要`5`个硬币:
|
||||
|
||||
```swift
|
||||
throw VendingMachineError.InsufficientFunds(coinsNeeded: 5)
|
||||
```
|
||||
|
||||
<a name="handling_errors"></a>
|
||||
##处理错误
|
||||
|
||||
某个错误被抛出时,附近的某部分代码必须负责处理这个错误,例如纠正这个问题、尝试另外一种方式、或是向用户报告错误。
|
||||
|
||||
Swift 中有`4`种处理错误的方式。你可以把函数抛出的错误传递给调用此函数的代码、用`do-catch`语句处理错误、将错误作为可选类型处理、或者断言此错误根本不会发生。每种方式在下面的小节中都有描述。
|
||||
|
||||
当一个函数抛出一个错误时,你的程序流程会发生改变,所以重要的是你能迅速识别代码中会抛出错误的地方。为了标识出这些地方,在调用一个能抛出错误的函数、方法或者构造器之前,加上`try`关键字,或者`try?`或`try!`这种变体。这些关键字在下面的小节中有具体讲解。
|
||||
|
||||
> 注意
|
||||
> Swift 中的错误处理和其他语言中用`try`,`catch`和`throw`进行异常处理很像。和其他语言中(包括 Objective-C )的异常处理不同的是,Swift 中的错误处理并不涉及解除调用栈,这是一个计算代价高昂的过程。就此而言,`throw`语句的性能特性是可以和`return`语句相媲美的。
|
||||
|
||||
<a name="propagating_errors_using_throwing_functions"></a>
|
||||
### 用 throwing 函数传递错误
|
||||
|
||||
为了表示一个函数、方法或构造器可以抛出错误,在函数声明的参数列表之后加上`throws`关键字。一个标有`throws`关键字的函数被称作*throwing 函数*。如果这个函数指明了返回值类型,`throws`关键词需要写在箭头(`->`)的前面。
|
||||
|
||||
```swift
|
||||
func canThrowErrors() throws -> String
|
||||
func cannotThrowErrors() -> String
|
||||
```
|
||||
|
||||
一个 throwing 函数可以在其内部抛出错误,并将错误传递到函数被调用时的作用域。
|
||||
|
||||
> 注意
|
||||
只有 throwing 函数可以传递错误。任何在某个非 throwing 函数内部抛出的错误只能在函数内部处理。
|
||||
|
||||
下面的例子中,`VendingMechine`类有一个`vend(itemNamed:)`方法,如果请求的物品不存在、缺货或者花费超过了投入金额,该方法就会抛出一个相应的`VendingMachineError`:
|
||||
|
||||
```swift
|
||||
struct Item {
|
||||
var price: Int
|
||||
var count: Int
|
||||
}
|
||||
|
||||
class VendingMachine {
|
||||
var inventory = [
|
||||
"Candy Bar": Item(price: 12, count: 7),
|
||||
"Chips": Item(price: 10, count: 4),
|
||||
"Pretzels": Item(price: 7, count: 11)
|
||||
]
|
||||
var coinsDeposited = 0
|
||||
func dispenseSnack(snack: String) {
|
||||
print("Dispensing \(snack)")
|
||||
}
|
||||
|
||||
func vend(itemNamed name: String) throws {
|
||||
guard var item = inventory[name] else {
|
||||
throw VendingMachineError.InvalidSelection
|
||||
}
|
||||
|
||||
guard item.count > 0 else {
|
||||
throw VendingMachineError.OutOfStock
|
||||
}
|
||||
|
||||
guard item.price <= coinsDeposited else {
|
||||
throw VendingMachineError.InsufficientFunds(coinsNeeded: item.price - coinsDeposited)
|
||||
}
|
||||
|
||||
coinsDeposited -= item.price
|
||||
--item.count
|
||||
inventory[name] = item
|
||||
dispenseSnack(name)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在`vend(itemNamed:)`方法的实现中使用了`guard`语句来提前退出方法,确保在购买某个物品所需的条件中,有任一条件不满足时,能提前退出方法并抛出相应的错误。由于`throw`语句会立即退出方法,所以物品只有在所有条件都满足时才会被售出。
|
||||
|
||||
因为`vend(itemNamed:)`方法会传递出它抛出的任何错误,在你的代码中调用此方法的地方,必须要么直接处理这些错误——使用`do-catch`语句,`try?`或`try!`;要么继续将这些错误传递下去。例如下面例子中,`buyFavoriteSnack(_:vendingMachine:)`同样是一个 throwing 函数,任何由`vend(itemNamed:)`方法抛出的错误会一直被传递到`buyFavoriteSnack(_:vendingMachine:)`函数被调用的地方。
|
||||
|
||||
```swift
|
||||
let favoriteSnacks = [
|
||||
"Alice": "Chips",
|
||||
"Bob": "Licorice",
|
||||
"Eve": "Pretzels",
|
||||
]
|
||||
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
|
||||
let snackName = favoriteSnacks[person] ?? "Candy Bar"
|
||||
try vendingMachine.vend(itemNamed: snackName)
|
||||
}
|
||||
```
|
||||
|
||||
上例中,`buyFavoriteSnack(_:vendingMachine:)`函数会查找某人最喜欢的零食,并通过调用`vend(itemNamed:)`方法来尝试为他们购买。因为`vend(itemNamed:)`方法能抛出错误,所以在调用的它时候在它前面加了`try`关键字。
|
||||
|
||||
###用 Do-Catch 处理错误
|
||||
|
||||
可以使用一个`do-catch`语句运行一段闭包代码来处理错误。如果在`do`子句中的代码抛出了一个错误,这个错误会与`catch`子句做匹配,从而决定哪条子句能处理它。
|
||||
|
||||
下面是`do-catch`语句的一般形式:
|
||||
|
||||
```swift
|
||||
do {
|
||||
try expression
|
||||
statements
|
||||
} catch pattern 1 {
|
||||
statements
|
||||
} catch pattern 2 where condition {
|
||||
statements
|
||||
}
|
||||
```
|
||||
|
||||
在`catch`后面写一个匹配模式来表明这个子句能处理什么样的错误。如果一条`catch`子句没有指定匹配模式,那么这条子句可以匹配任何错误,并且把错误绑定到一个名字为`error`的局部常量。关于模式匹配的更多信息请参考 [模式](../chapter3/07_Patterns.html)。
|
||||
|
||||
`catch`子句不必将`do`子句中的代码所抛出的每一个可能的错误都作处理。如果所有`catch`子句都未处理错误,错误就会传递到周围的作用域。然而,错误还是必须要被某个周围的作用域处理的——要么是一个外围的`do-catch`错误处理语句,要么是一个 throwing 函数的内部。举例来说,下面的代码处理了`VendingMachineError`枚举类型的全部枚举值,但是所有其它的错误就必须由它周围的作用域处理:
|
||||
|
||||
```swift
|
||||
var vendingMachine = VendingMachine()
|
||||
vendingMachine.coinsDeposited = 8
|
||||
do {
|
||||
try buyFavoriteSnack("Alice", vendingMachine: vendingMachine)
|
||||
} catch VendingMachineError.InvalidSelection {
|
||||
print("Invalid Selection.")
|
||||
} catch VendingMachineError.OutOfStock {
|
||||
print("Out of Stock.")
|
||||
} catch VendingMachineError.InsufficientFunds(let coinsNeeded) {
|
||||
print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
|
||||
}
|
||||
// 打印 “Insufficient funds. Please insert an additional 2 coins.”
|
||||
```
|
||||
|
||||
上面的例子中,`buyFavoriteSnack(_:vendingMachine:)`函数在一个`try`表达式中调用,因为它能抛出错误。如果错误被抛出,相应的执行会马上转移到`catch`子句中,并判断这个错误是否要被继续传递下去。如果没有错误抛出,`do`子句中余下的语句就会被执行。
|
||||
|
||||
###将错误转换成可选值
|
||||
|
||||
可以使用`try?`通过将错误转换成一个可选值来处理错误。如果在评估`try?`表达式时一个错误被抛出,那么表达式的值就是`nil`。例如下面代码中的`x`和`y`具有相同的值:
|
||||
|
||||
```swift
|
||||
func someThrowingFunction() throws -> Int {
|
||||
// ...
|
||||
}
|
||||
|
||||
let x = try? someThrowingFunction()
|
||||
|
||||
let y: Int?
|
||||
do {
|
||||
y = try someThrowingFunction()
|
||||
} catch {
|
||||
y = nil
|
||||
}
|
||||
```
|
||||
|
||||
如果`someThrowingFunction()`抛出一个错误,`x`和`y`的值是`nil`。否则`x`和`y`的值就是该函数的返回值。注意,无论`someThrowingFunction()`的返回值类型是什么类型,`x`和`y`都是这个类型的可选类型。例子中此函数返回一个整型,所以`x`和`y`是可选整型。
|
||||
|
||||
如果你想对所有的错误都采用同样的方式来处理,用`try?`就可以让你写出简洁的错误处理代码。例如,下面的代码用几种方式来获取数据,如果所有方式都失败了则返回`nil`:
|
||||
|
||||
```swift
|
||||
func fetchData() -> Data? {
|
||||
if let data = try? fetchDataFromDisk() { return data }
|
||||
if let data = try? fetchDataFromServer() { return data }
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### 禁用错误传递
|
||||
|
||||
有时你知道某个 throwing 函数实际上在运行时是不会抛出错误的,在这种情况下,你可以在表达式前面写`try!`来禁用错误传递,这会把调用包装在一个断言不会有错误抛出的运行时断言中。如果实际上抛出了错误,你会得到一个运行时错误。
|
||||
|
||||
例如,下面的代码使用了`loadImage(_:)`函数,该函数从给定的路径加载图片资源,如果图片无法载入则抛出一个错误。在这种情况下,因为图片是和应用绑定的,运行时不会有错误抛出,所以适合禁用错误传递:
|
||||
|
||||
```swift
|
||||
let photo = try! loadImage("./Resources/John Appleseed.jpg")
|
||||
```
|
||||
|
||||
<a name="specifying_cleanup_actions"></a>
|
||||
##指定清理操作
|
||||
|
||||
可以使用`defer`语句在即将离开当前代码块时执行一系列语句。该语句让你能执行一些必要的清理工作,不管是以何种方式离开当前代码块的——无论是由于抛出错误而离开,还是由于诸如`return`或者`break`的语句。例如,你可以用`defer`语句来确保文件描述符得以关闭,以及手动分配的内存得以释放。
|
||||
|
||||
`defer`语句将代码的执行延迟到当前的作用域退出之前。该语句由`defer`关键字和要被延迟执行的语句组成。延迟执行的语句不能包含任何控制转移语句,例如`break`或是`return`语句,或是抛出一个错误。延迟执行的操作会按照它们被指定时的顺序的相反顺序执行——也就是说,第一条`defer`语句中的代码会在第二条`defer`语句中的代码被执行之后才执行,以此类推。
|
||||
|
||||
```swift
|
||||
func processFile(filename: String) throws {
|
||||
if exists(filename) {
|
||||
let file = open(filename)
|
||||
defer {
|
||||
close(file)
|
||||
}
|
||||
while let line = try file.readline() {
|
||||
// 处理文件。
|
||||
}
|
||||
// close(file) 会在这里被调用,即作用域的最后。
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上面的代码使用一条`defer`语句来确保`open(_:)`函数有一个相应的对`close(_:)`函数的调用。
|
||||
|
||||
> 注意
|
||||
> 即使没有涉及到错误处理,你也可以使用`defer`语句。
|
||||
@ -1,102 +0,0 @@
|
||||
# 嵌套类型(Nested Types)
|
||||
-----------------
|
||||
|
||||
> 1.0
|
||||
> 翻译:[Lin-H](https://github.com/Lin-H)
|
||||
> 校对:[shinyzhu](https://github.com/shinyzhu)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[SergioChan](https://github.com/SergioChan)
|
||||
|
||||
> 2.1
|
||||
> 校对:[shanks](http://codebuild.me),2015-11-01
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [嵌套类型实践](#nested_types_in_action)
|
||||
- [引用嵌套类型](#referring_to_nested_types)
|
||||
|
||||
枚举常被用于为特定类或结构体实现某些功能。类似的,也能够在某个复杂的类型中,方便地定义工具类或结构体来使用。为了实现这种功能,Swift 允许你定义嵌套类型,可以在支持的类型中定义嵌套的枚举、类和结构体。
|
||||
|
||||
要在一个类型中嵌套另一个类型,将嵌套类型的定义写在其外部类型的`{}`内,而且可以根据需要定义多级嵌套。
|
||||
|
||||
<a name="nested_types_in_action"></a>
|
||||
## 嵌套类型实践
|
||||
|
||||
下面这个例子定义了一个结构体`BlackjackCard`(二十一点),用来模拟`BlackjackCard`中的扑克牌点数。`BlackjackCard`结构体包含两个嵌套定义的枚举类型`Suit`和`Rank`。
|
||||
|
||||
在`BlackjackCard`中,`Ace`牌可以表示`1`或者`11`,`Ace`牌的这一特征通过一个嵌套在`Rank`枚举中的结构体`Values`来表示:
|
||||
|
||||
```swift
|
||||
struct BlackjackCard {
|
||||
// 嵌套的 Suit 枚举
|
||||
enum Suit: Character {
|
||||
case Spades = "♠", Hearts = "♡", Diamonds = "♢", Clubs = "♣"
|
||||
}
|
||||
|
||||
// 嵌套的 Rank 枚举
|
||||
enum Rank: Int {
|
||||
case Two = 2, Three, Four, Five, Six, Seven, Eight, Nine, Ten
|
||||
case Jack, Queen, King, Ace
|
||||
struct Values {
|
||||
let first: Int, second: Int?
|
||||
}
|
||||
var values: Values {
|
||||
switch self {
|
||||
case .Ace:
|
||||
return Values(first: 1, second: 11)
|
||||
case .Jack, .Queen, .King:
|
||||
return Values(first: 10, second: nil)
|
||||
default:
|
||||
return Values(first: self.rawValue, second: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BlackjackCard 的属性和方法
|
||||
let rank: Rank, suit: Suit
|
||||
var description: String {
|
||||
var output = "suit is \(suit.rawValue),"
|
||||
output += " value is \(rank.values.first)"
|
||||
if let second = rank.values.second {
|
||||
output += " or \(second)"
|
||||
}
|
||||
return output
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Suit`枚举用来描述扑克牌的四种花色,并用一个`Character`类型的原始值表示花色符号。
|
||||
|
||||
`Rank`枚举用来描述扑克牌从`Ace`~`10`,以及`J`、`Q`、`K`,这`13`种牌,并用一个`Int`类型的原始值表示牌的面值。(这个`Int`类型的原始值未用于`Ace`、`J`、`Q`、`K`这`4`种牌。)
|
||||
|
||||
如上所述,`Rank`枚举在内部定义了一个嵌套结构体`Values`。结构体`Values`中定义了两个属性,用于反映只有`Ace`有两个数值,其余牌都只有一个数值:
|
||||
|
||||
- `first`的类型为`Int`
|
||||
- `second`的类型为`Int?`,或者说“optional `Int`”
|
||||
|
||||
`Rank`还定义了一个计算型属性`values`,它将会返回一个`Values`结构体的实例。这个计算型属性会根据牌的面值,用适当的数值去初始化`Values`实例。对于`J`、`Q`、`K`、`Ace`这四种牌,会使用特殊数值。对于数字面值的牌,使用枚举实例的原始值。
|
||||
|
||||
`BlackjackCard`结构体拥有两个属性——`rank`与`suit`。它也同样定义了一个计算型属性`description`,`description`属性用`rank`和`suit`中的内容来构建对扑克牌名字和数值的描述。该属性使用可选绑定来检查可选类型`second`是否有值,若有值,则在原有的描述中增加对`second`的描述。
|
||||
|
||||
因为`BlackjackCard`是一个没有自定义构造器的结构体,在[结构体的逐一成员构造器](./14_Initialization.html#memberwise_initializers_for_structure_types)中可知,结构体有默认的成员构造器,所以你可以用默认的构造器去初始化新常量`theAceOfSpades`:
|
||||
|
||||
```swift
|
||||
let theAceOfSpades = BlackjackCard(rank: .Ace, suit: .Spades)
|
||||
print("theAceOfSpades: \(theAceOfSpades.description)")
|
||||
// 打印 “theAceOfSpades: suit is ♠, value is 1 or 11”
|
||||
```
|
||||
|
||||
尽管`Rank`和`Suit`嵌套在`BlackjackCard`中,但它们的类型仍可从上下文中推断出来,所以在初始化实例时能够单独通过成员名称(`.Ace`和`.Spades`)引用枚举实例。在上面的例子中,`description`属性正确地反映了黑桃A牌具有`1`和`11`两个值。
|
||||
|
||||
<a name="referring_to_nested_types"></a>
|
||||
## 引用嵌套类型
|
||||
|
||||
在外部引用嵌套类型时,在嵌套类型的类型名前加上其外部类型的类型名作为前缀:
|
||||
|
||||
```swift
|
||||
let heartsSymbol = BlackjackCard.Suit.Hearts.rawValue
|
||||
// 红心符号为 “♡”
|
||||
```
|
||||
|
||||
对于上面这个例子,这样可以使`Suit`、`Rank`和`Values`的名字尽可能的短,因为它们的名字可以由定义它们的上下文来限定。
|
||||
@ -1,304 +0,0 @@
|
||||
# 扩展(Extensions)
|
||||
----
|
||||
|
||||
> 1.0
|
||||
> 翻译:[lyuka](https://github.com/lyuka)
|
||||
> 校对:[Hawstein](https://github.com/Hawstein)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[shanks](http://codebuild.me)
|
||||
|
||||
> 2.1
|
||||
> 校对:[shanks](http://codebuild.me)
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [扩展语法](#extension_syntax)
|
||||
- [计算型属性](#computed_properties)
|
||||
- [构造器](#initializers)
|
||||
- [方法](#methods)
|
||||
- [下标](#subscripts)
|
||||
- [嵌套类型](#nested_types)
|
||||
|
||||
*扩展* 就是为一个已有的类、结构体、枚举类型或者协议类型添加新功能。这包括在没有权限获取原始源代码的情况下扩展类型的能力(即 *逆向建模* )。扩展和 Objective-C 中的分类类似。(与 Objective-C 不同的是,Swift 的扩展没有名字。)
|
||||
|
||||
Swift 中的扩展可以:
|
||||
|
||||
- 添加计算型属性和计算型类型属性
|
||||
- 定义实例方法和类型方法
|
||||
- 提供新的构造器
|
||||
- 定义下标
|
||||
- 定义和使用新的嵌套类型
|
||||
- 使一个已有类型符合某个协议
|
||||
|
||||
在 Swift 中,你甚至可以对协议进行扩展,提供协议要求的实现,或者添加额外的功能,从而可以让符合协议的类型拥有这些功能。你可以从[协议扩展](./22_Protocols.html#protocol_extensions)获取更多的细节。
|
||||
|
||||
> 注意
|
||||
扩展可以为一个类型添加新的功能,但是不能重写已有的功能。
|
||||
|
||||
<a name="extension_syntax"></a>
|
||||
## 扩展语法(Extension Syntax)
|
||||
|
||||
使用关键字 `extension` 来声明扩展:
|
||||
|
||||
```swift
|
||||
extension SomeType {
|
||||
// 为 SomeType 添加的新功能写到这里
|
||||
}
|
||||
```
|
||||
|
||||
可以通过扩展来扩展一个已有类型,使其采纳一个或多个协议。在这种情况下,无论是类还是结构体,协议名字的书写方式完全一样:
|
||||
|
||||
```swift
|
||||
extension SomeType: SomeProtocol, AnotherProctocol {
|
||||
// 协议实现写到这里
|
||||
}
|
||||
```
|
||||
|
||||
通过这种方式添加协议一致性的详细描述请参阅[利用扩展添加协议一致性](./22_Protocols.html#adding_protocol_conformance_with_an_extension)。
|
||||
|
||||
> 注意
|
||||
如果你通过扩展为一个已有类型添加新功能,那么新功能对该类型的所有已有实例都是可用的,即使它们是在这个扩展定义之前创建的。
|
||||
|
||||
<a name="computed_properties"></a>
|
||||
## 计算型属性(Computed Properties)
|
||||
|
||||
扩展可以为已有类型添加计算型实例属性和计算型类型属性。下面的例子为 Swift 的内建 `Double` 类型添加了五个计算型实例属性,从而提供与距离单位协作的基本支持:
|
||||
|
||||
```swift
|
||||
extension Double {
|
||||
var km: Double { return self * 1_000.0 }
|
||||
var m : Double { return self }
|
||||
var cm: Double { return self / 100.0 }
|
||||
var mm: Double { return self / 1_000.0 }
|
||||
var ft: Double { return self / 3.28084 }
|
||||
}
|
||||
let oneInch = 25.4.mm
|
||||
print("One inch is \(oneInch) meters")
|
||||
// 打印 “One inch is 0.0254 meters”
|
||||
let threeFeet = 3.ft
|
||||
print("Three feet is \(threeFeet) meters")
|
||||
// 打印 “Three feet is 0.914399970739201 meters”
|
||||
```
|
||||
|
||||
这些计算型属性表达的含义是把一个 `Double` 值看作是某单位下的长度值。即使它们被实现为计算型属性,但这些属性的名字仍可紧接一个浮点型字面值,从而通过点语法来使用,并以此实现距离转换。
|
||||
|
||||
在上述例子中,`Double` 值 `1.0` 用来表示“1米”。这就是为什么计算型属性 `m` 返回 `self`,即表达式 `1.m` 被认为是计算 `Double` 值 `1.0`。
|
||||
|
||||
其它单位则需要一些单位换算。一千米等于 1,000 米,所以计算型属性 `km` 要把值乘以 `1_000.00` 来实现千米到米的单位换算。类似地,一米有 3.28024 英尺,所以计算型属性 `ft` 要把对应的 `Double` 值除以 `3.28024` 来实现英尺到米的单位换算。
|
||||
|
||||
这些属性是只读的计算型属性,为了更简洁,省略了 `get` 关键字。它们的返回值是 `Double`,而且可以用于所有接受 `Double` 值的数学计算中:
|
||||
|
||||
```swift
|
||||
let aMarathon = 42.km + 195.m
|
||||
print("A marathon is \(aMarathon) meters long")
|
||||
// 打印 “A marathon is 42195.0 meters long”
|
||||
```
|
||||
|
||||
> 注意
|
||||
扩展可以添加新的计算型属性,但是不可以添加存储型属性,也不可以为已有属性添加属性观察器。
|
||||
|
||||
<a name="initializers"></a>
|
||||
## 构造器(Initializers)
|
||||
|
||||
扩展可以为已有类型添加新的构造器。这可以让你扩展其它类型,将你自己的定制类型作为其构造器参数,或者提供该类型的原始实现中未提供的额外初始化选项。
|
||||
|
||||
扩展能为类添加新的便利构造器,但是它们不能为类添加新的指定构造器或析构器。指定构造器和析构器必须总是由原始的类实现来提供。
|
||||
|
||||
> 注意
|
||||
如果你使用扩展为一个值类型添加构造器,且该值类型的原始实现中未定义任何定制的构造器时,你可以在扩展中的构造器里调用逐一成员构造器。如果该值类型为所有存储型属性提供了默认值,你还可以在扩展中的构造器里调用默认构造器。
|
||||
正如在[值类型的构造器代理](./14_Initialization.html#initializer_delegation_for_value_types)中描述的,如果你把定制的构造器写在值类型的原始实现中,上述规则将不再适用。
|
||||
|
||||
下面的例子定义了一个用于描述几何矩形的结构体 `Rect`。这个例子同时定义了两个辅助结构体 `Size` 和 `Point`,它们都把 `0.0` 作为所有属性的默认值:
|
||||
|
||||
```swift
|
||||
struct Size {
|
||||
var width = 0.0, height = 0.0
|
||||
}
|
||||
struct Point {
|
||||
var x = 0.0, y = 0.0
|
||||
}
|
||||
struct Rect {
|
||||
var origin = Point()
|
||||
var size = Size()
|
||||
}
|
||||
```
|
||||
因为结构体 `Rect` 未提供定制的构造器,因此它会获得一个逐一成员构造器。又因为它为所有存储型属性提供了默认值,它又会获得一个默认构造器。详情请参阅[默认构造器](./14_Initialization.html#default_initializers)。这些构造器可以用于构造新的 `Rect` 实例:
|
||||
|
||||
```swift
|
||||
let defaultRect = Rect()
|
||||
let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0),
|
||||
size: Size(width: 5.0, height: 5.0))
|
||||
```
|
||||
|
||||
你可以提供一个额外的接受指定中心点和大小的构造器来扩展 `Rect` 结构体:
|
||||
|
||||
```swift
|
||||
extension Rect {
|
||||
init(center: Point, size: Size) {
|
||||
let originX = center.x - (size.width / 2)
|
||||
let originY = center.y - (size.height / 2)
|
||||
self.init(origin: Point(x: originX, y: originY), size: size)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这个新的构造器首先根据提供的 `center` 和 `size` 的值计算一个合适的原点。然后调用该结构体的逐一成员构造器 `init(origin:size:)`,该构造器将新的原点和大小的值保存到了相应的属性中:
|
||||
|
||||
```swift
|
||||
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
|
||||
size: Size(width: 3.0, height: 3.0))
|
||||
// centerRect 的原点是 (2.5, 2.5),大小是 (3.0, 3.0)
|
||||
```
|
||||
|
||||
> 注意
|
||||
如果你使用扩展提供了一个新的构造器,你依旧有责任确保构造过程能够让实例完全初始化。
|
||||
|
||||
<a name="methods"></a>
|
||||
## 方法(Methods)
|
||||
|
||||
扩展可以为已有类型添加新的实例方法和类型方法。下面的例子为 `Int` 类型添加了一个名为 `repetitions` 的实例方法:
|
||||
|
||||
```swift
|
||||
extension Int {
|
||||
func repetitions(task: () -> Void) {
|
||||
for _ in 0..<self {
|
||||
task()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这个 `repetitions(:_)` 方法接受一个 `() -> Void` 类型的单参数,表示没有参数且没有返回值的函数。
|
||||
|
||||
定义该扩展之后,你就可以对任意整数调用 `repetitions(_:)` 方法,将闭包中的任务执行整数对应的次数:
|
||||
|
||||
```swift
|
||||
3.repetitions({
|
||||
print("Hello!")
|
||||
})
|
||||
// Hello!
|
||||
// Hello!
|
||||
// Hello!
|
||||
```
|
||||
|
||||
可以使用尾随闭包让调用更加简洁:
|
||||
|
||||
```swift
|
||||
3.repetitions {
|
||||
print("Goodbye!")
|
||||
}
|
||||
// Goodbye!
|
||||
// Goodbye!
|
||||
// Goodbye!
|
||||
```
|
||||
|
||||
<a name="mutating_instance_methods"></a>
|
||||
### 可变实例方法(Mutating Instance Methods)
|
||||
|
||||
通过扩展添加的实例方法也可以修改该实例本身。结构体和枚举类型中修改 `self` 或其属性的方法必须将该实例方法标注为 `mutating`,正如来自原始实现的可变方法一样。
|
||||
|
||||
下面的例子为 Swift 的 `Int` 类型添加了一个名为 `square` 的可变方法,用于计算原始值的平方值:
|
||||
|
||||
```swift
|
||||
extension Int {
|
||||
mutating func square() {
|
||||
self = self * self
|
||||
}
|
||||
}
|
||||
var someInt = 3
|
||||
someInt.square()
|
||||
// someInt 的值现在是 9
|
||||
```
|
||||
|
||||
<a name="subscripts"></a>
|
||||
## 下标(Subscripts)
|
||||
|
||||
扩展可以为已有类型添加新下标。这个例子为 Swift 内建类型 `Int` 添加了一个整型下标。该下标 `[n]` 返回十进制数字从右向左数的第 `n` 个数字:
|
||||
|
||||
- `123456789[0]` 返回 `9`
|
||||
- `123456789[1]` 返回 `8`
|
||||
|
||||
……以此类推。
|
||||
|
||||
```swift
|
||||
extension Int {
|
||||
subscript(var digitIndex: Int) -> Int {
|
||||
var decimalBase = 1
|
||||
while digitIndex > 0 {
|
||||
decimalBase *= 10
|
||||
--digitIndex
|
||||
}
|
||||
return (self / decimalBase) % 10
|
||||
}
|
||||
}
|
||||
746381295[0]
|
||||
// 返回 5
|
||||
746381295[1]
|
||||
// 返回 9
|
||||
746381295[2]
|
||||
// 返回 2
|
||||
746381295[8]
|
||||
// 返回 7
|
||||
```
|
||||
|
||||
如果该 `Int` 值没有足够的位数,即下标越界,那么上述下标实现会返回 `0`,犹如在数字左边自动补 `0`:
|
||||
|
||||
```swift
|
||||
746381295[9]
|
||||
// 返回 0,即等同于:
|
||||
0746381295[9]
|
||||
```
|
||||
|
||||
<a name="nested_types"></a>
|
||||
## 嵌套类型(Nested Types)
|
||||
|
||||
扩展可以为已有的类、结构体和枚举添加新的嵌套类型:
|
||||
|
||||
```swift
|
||||
extension Int {
|
||||
enum Kind {
|
||||
case Negative, Zero, Positive
|
||||
}
|
||||
var kind: Kind {
|
||||
switch self {
|
||||
case 0:
|
||||
return .Zero
|
||||
case let x where x > 0:
|
||||
return .Positive
|
||||
default:
|
||||
return .Negative
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
该例子为 `Int` 添加了嵌套枚举。这个名为 `Kind` 的枚举表示特定整数的类型。具体来说,就是表示整数是正数、零或者负数。
|
||||
|
||||
这个例子还为 `Int` 添加了一个计算型实例属性,即 `kind`,用来根据整数返回适当的 `Kind` 枚举成员。
|
||||
|
||||
现在,这个嵌套枚举可以和任意 `Int` 值一起使用了:
|
||||
|
||||
|
||||
```swift
|
||||
func printIntegerKinds(numbers: [Int]) {
|
||||
for number in numbers {
|
||||
switch number.kind {
|
||||
case .Negative:
|
||||
print("- ", terminator: "")
|
||||
case .Zero:
|
||||
print("0 ", terminator: "")
|
||||
case .Positive:
|
||||
print("+ ", terminator: "")
|
||||
}
|
||||
}
|
||||
print("")
|
||||
}
|
||||
printIntegerKinds([3, 19, -27, 0, -6, 0, 7])
|
||||
// 打印 “+ + - 0 - 0 +”
|
||||
```
|
||||
|
||||
函数 `printIntegerKinds(_:)` 接受一个 `Int` 数组,然后对该数组进行迭代。在每次迭代过程中,对当前整数的计算型属性 `kind` 的值进行评估,并打印出适当的描述。
|
||||
|
||||
> 注意
|
||||
由于已知 `number.kind` 是 `Int.Kind` 类型,因此在 `switch` 语句中,`Int.Kind` 中的所有成员值都可以使用简写形式,例如使用 `. Negative` 而不是 `Int.Kind.Negative`。
|
||||
@ -1,912 +0,0 @@
|
||||
# 协议(Protocols)
|
||||
-----------------
|
||||
|
||||
> 1.0
|
||||
> 翻译:[geek5nan](https://github.com/geek5nan)
|
||||
> 校对:[dabing1022](https://github.com/dabing1022)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[futantan](https://github.com/futantan)
|
||||
|
||||
> 2.1
|
||||
> 翻译:[小铁匠Linus](https://github.com/kevin833752)
|
||||
> 校对:[shanks](http://codebuild.me),2015-11-01
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [协议语法(Protocol Syntax)](#protocol_syntax)
|
||||
- [属性要求(Property Requirements)](#property_requirements)
|
||||
- [方法要求(Method Requirements)](#method_requirements)
|
||||
- [Mutating 方法要求(Mutating Method Requirements)](#mutating_method_requirements)
|
||||
- [构造器要求(Initializer Requirements)](#initializer_requirements)
|
||||
- [协议作为类型(Protocols as Types)](#protocols_as_types)
|
||||
- [委托(代理)模式(Delegation)](#delegation)
|
||||
- [通过扩展添加协议一致性(Adding Protocol Conformance with an Extension)](#adding_protocol_conformance_with_an_extension)
|
||||
- [通过扩展采纳协议(Declaring Protocol Adoption with an Extension)](#declaring_protocol_adoption_with_an_extension)
|
||||
- [协议类型的集合(Collections of Protocol Types)](#collections_of_protocol_types)
|
||||
- [协议的继承(Protocol Inheritance)](#protocol_inheritance)
|
||||
- [类类型专属协议(Class-Only Protocol)](#class_only_protocol)
|
||||
- [协议合成(Protocol Composition)](#protocol_composition)
|
||||
- [检查协议一致性(Checking for Protocol Conformance)](#checking_for_protocol_conformance)
|
||||
- [可选的协议要求(Optional Protocol Requirements)](#optional_protocol_requirements)
|
||||
- [协议扩展(Protocol Extensions)](#protocol_extensions)
|
||||
|
||||
协议定义了一个蓝图,规定了用来实现某一特定任务或者功能的方法、属性,以及其他需要的东西。类、结构体或枚举都可以采纳协议,并为协议定义的这些要求提供具体实现。某个类型能够满足某个协议的要求,就可以说该类型“符合”这个协议。
|
||||
|
||||
除了采纳协议的类型必须实现的要求外,还可以对协议进行扩展,通过扩展来实现一部分要求或者实现一些附加功能,这样采纳协议的类型就能够使用这些功能。
|
||||
|
||||
<a name="protocol_syntax"></a>
|
||||
## 协议语法
|
||||
|
||||
协议的定义方式与类、结构体和枚举的定义非常相似:
|
||||
|
||||
```swift
|
||||
protocol SomeProtocol {
|
||||
// 这里是协议的定义部分
|
||||
}
|
||||
```
|
||||
|
||||
要让自定义类型采纳某个协议,在定义类型时,需要在类型名称后加上协议名称,中间以冒号(`:`)分隔。采纳多个协议时,各协议之间用逗号(`,`)分隔:
|
||||
|
||||
```swift
|
||||
struct SomeStructure: FirstProtocol, AnotherProtocol {
|
||||
// 这里是结构体的定义部分
|
||||
}
|
||||
```
|
||||
|
||||
拥有父类的类在采纳协议时,应该将父类名放在协议名之前,以逗号分隔:
|
||||
|
||||
```swift
|
||||
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
|
||||
// 这里是类的定义部分
|
||||
}
|
||||
```
|
||||
|
||||
<a name="property_requirements"></a>
|
||||
## 属性要求
|
||||
|
||||
协议可以要求采纳协议的类型提供特定名称和类型的实例属性或类型属性。协议不指定属性是存储型属性还是计算型属性,它只指定属性的名称和类型。此外,协议还指定属性是可读的还是可读可写的。
|
||||
|
||||
如果协议要求属性是可读可写的,那么该属性不能是常量属性或只读的计算型属性。如果协议只要求属性是可读的,那么该属性不仅可以是可读的,如果代码需要的话,还可以是可写的。
|
||||
|
||||
协议总是用 `var` 关键字来声明变量属性,在类型声明后加上 `{ set get }` 来表示属性是可读可写的,可读属性则用 `{ get }` 来表示:
|
||||
|
||||
```swift
|
||||
protocol SomeProtocol {
|
||||
var mustBeSettable: Int { get set }
|
||||
var doesNotNeedToBeSettable: Int { get }
|
||||
}
|
||||
```
|
||||
|
||||
在协议中定义类型属性时,总是使用 `static` 关键字作为前缀。当类类型采纳协议时,除了 `static` 关键字,还可以使用 `class` 关键字来声明类型属性:
|
||||
|
||||
```swift
|
||||
protocol AnotherProtocol {
|
||||
static var someTypeProperty: Int { get set }
|
||||
}
|
||||
```
|
||||
|
||||
如下所示,这是一个只含有一个实例属性要求的协议:
|
||||
|
||||
```swift
|
||||
protocol FullyNamed {
|
||||
var fullName: String { get }
|
||||
}
|
||||
```
|
||||
|
||||
`FullyNamed` 协议除了要求采纳协议的类型提供 `fullName` 属性外,并没有其他特别的要求。这个协议表示,任何采纳 `FullyNamed` 的类型,都必须有一个可读的 `String` 类型的实例属性 `fullName`。
|
||||
|
||||
下面是一个采纳 `FullyNamed` 协议的简单结构体:
|
||||
|
||||
```swift
|
||||
struct Person: FullyNamed {
|
||||
var fullName: String
|
||||
}
|
||||
let john = Person(fullName: "John Appleseed")
|
||||
// john.fullName 为 "John Appleseed"
|
||||
```
|
||||
|
||||
这个例子中定义了一个叫做 `Person` 的结构体,用来表示一个具有名字的人。从第一行代码可以看出,它采纳了 `FullyNamed` 协议。
|
||||
|
||||
`Person` 结构体的每一个实例都有一个 `String` 类型的存储型属性 `fullName`。这正好满足了 `FullyNamed` 协议的要求,也就意味着 `Person` 结构体正确地符合了协议。(如果协议要求未被完全满足,在编译时会报错。)
|
||||
|
||||
下面是一个更为复杂的类,它采纳并符合了 `FullyNamed` 协议:
|
||||
|
||||
```swift
|
||||
class Starship: FullyNamed {
|
||||
var prefix: String?
|
||||
var name: String
|
||||
init(name: String, prefix: String? = nil) {
|
||||
self.name = name
|
||||
self.prefix = prefix
|
||||
}
|
||||
var fullName: String {
|
||||
return (prefix != nil ? prefix! + " " : "") + name
|
||||
}
|
||||
}
|
||||
var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
|
||||
// ncc1701.fullName 是 "USS Enterprise"
|
||||
```
|
||||
|
||||
`Starship` 类把 `fullName` 属性实现为只读的计算型属性。每一个 `Starship` 类的实例都有一个名为 `name` 的非可选属性和一个名为 `prefix` 的可选属性。 当 `prefix` 存在时,计算型属性 `fullName` 会将 `prefix` 插入到 `name` 之前,从而为星际飞船构建一个全名。
|
||||
|
||||
<a name="method_requirements"></a>
|
||||
## 方法要求
|
||||
|
||||
协议可以要求采纳协议的类型实现某些指定的实例方法或类方法。这些方法作为协议的一部分,像普通方法一样放在协议的定义中,但是不需要大括号和方法体。可以在协议中定义具有可变参数的方法,和普通方法的定义方式相同。但是,不支持为协议中的方法的参数提供默认值。
|
||||
|
||||
正如属性要求中所述,在协议中定义类方法的时候,总是使用 `static` 关键字作为前缀。当类类型采纳协议时,除了 `static` 关键字,还可以使用 `class` 关键字作为前缀:
|
||||
|
||||
```swift
|
||||
protocol SomeProtocol {
|
||||
static func someTypeMethod()
|
||||
}
|
||||
```
|
||||
|
||||
下面的例子定义了一个只含有一个实例方法的协议:
|
||||
|
||||
```swift
|
||||
protocol RandomNumberGenerator {
|
||||
func random() -> Double
|
||||
}
|
||||
```
|
||||
|
||||
`RandomNumberGenerator` 协议要求采纳协议的类型必须拥有一个名为 `random`, 返回值类型为 `Double` 的实例方法。尽管这里并未指明,但是我们假设返回值在 `[0.0,1.0)` 区间内。
|
||||
|
||||
`RandomNumberGenerator` 协议并不关心每一个随机数是怎样生成的,它只要求必须提供一个随机数生成器。
|
||||
|
||||
如下所示,下边是一个采纳并符合 `RandomNumberGenerator` 协议的类。该类实现了一个叫做 *线性同余生成器(linear congruential generator)* 的伪随机数算法。
|
||||
|
||||
```swift
|
||||
class LinearCongruentialGenerator: RandomNumberGenerator {
|
||||
var lastRandom = 42.0
|
||||
let m = 139968.0
|
||||
let a = 3877.0
|
||||
let c = 29573.0
|
||||
func random() -> Double {
|
||||
lastRandom = ((lastRandom * a + c) % m)
|
||||
return lastRandom / m
|
||||
}
|
||||
}
|
||||
let generator = LinearCongruentialGenerator()
|
||||
print("Here's a random number: \(generator.random())")
|
||||
// 打印 “Here's a random number: 0.37464991998171”
|
||||
print("And another one: \(generator.random())")
|
||||
// 打印 “And another one: 0.729023776863283”
|
||||
```
|
||||
|
||||
<a name="mutating_method_requirements"></a>
|
||||
## Mutating 方法要求
|
||||
|
||||
有时需要在方法中改变方法所属的实例。例如,在值类型(即结构体和枚举)的实例方法中,将 `mutating` 关键字作为方法的前缀,写在 `func` 关键字之前,表示可以在该方法中修改它所属的实例以及实例的任意属性的值。这一过程在[在实例方法中修改值类型](./11_Methods.html#modifying_value_types_from_within_instance_methods)章节中有详细描述。
|
||||
|
||||
如果你在协议中定义了一个实例方法,该方法会改变采纳该协议的类型的实例,那么在定义协议时需要在方法前加 `mutating` 关键字。这使得结构体和枚举能够采纳此协议并满足此方法要求。
|
||||
|
||||
> 注意
|
||||
> 实现协议中的 `mutating` 方法时,若是类类型,则不用写 `mutating` 关键字。而对于结构体和枚举,则必须写 `mutating` 关键字。
|
||||
|
||||
如下所示,`Togglable` 协议只要求实现一个名为 `toggle` 的实例方法。根据名称的暗示,`toggle()` 方法将改变实例属性,从而切换采纳该协议类型的实例的状态。
|
||||
|
||||
`toggle()` 方法在定义的时候,使用 `mutating` 关键字标记,这表明当它被调用时,该方法将会改变采纳协议的类型的实例:
|
||||
|
||||
```swift
|
||||
protocol Togglable {
|
||||
mutating func toggle()
|
||||
}
|
||||
```
|
||||
|
||||
当使用枚举或结构体来实现 `Togglable` 协议时,需要提供一个带有 `mutating` 前缀的 `toggle()` 方法。
|
||||
|
||||
下面定义了一个名为 `OnOffSwitch` 的枚举。这个枚举在两种状态之间进行切换,用枚举成员 `On` 和 `Off` 表示。枚举的 `toggle()` 方法被标记为 `mutating`,以满足 `Togglable` 协议的要求:
|
||||
|
||||
```swift
|
||||
enum OnOffSwitch: Togglable {
|
||||
case Off, On
|
||||
mutating func toggle() {
|
||||
switch self {
|
||||
case Off:
|
||||
self = On
|
||||
case On:
|
||||
self = Off
|
||||
}
|
||||
}
|
||||
}
|
||||
var lightSwitch = OnOffSwitch.Off
|
||||
lightSwitch.toggle()
|
||||
// lightSwitch 现在的值为 .On
|
||||
```
|
||||
|
||||
<a name="initializer_requirements"></a>
|
||||
## 构造器要求
|
||||
|
||||
协议可以要求采纳协议的类型实现指定的构造器。你可以像编写普通构造器那样,在协议的定义里写下构造器的声明,但不需要写花括号和构造器的实体:
|
||||
|
||||
```swift
|
||||
protocol SomeProtocol {
|
||||
init(someParameter: Int)
|
||||
}
|
||||
```
|
||||
|
||||
### 构造器要求在类中的实现
|
||||
|
||||
你可以在采纳协议的类中实现构造器,无论是作为指定构造器,还是作为便利构造器。无论哪种情况,你都必须为构造器实现标上 `required` 修饰符:
|
||||
|
||||
```swift
|
||||
class SomeClass: SomeProtocol {
|
||||
required init(someParameter: Int) {
|
||||
// 这里是构造器的实现部分
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
使用 `required` 修饰符可以确保所有子类也必须提供此构造器实现,从而也能符合协议。
|
||||
|
||||
关于 `required` 构造器的更多内容,请参考[必要构造器](./14_Initialization.html#required_initializers)。
|
||||
|
||||
> 注意
|
||||
> 如果类已经被标记为 `final`,那么不需要在协议构造器的实现中使用 `required` 修饰符,因为 `final` 类不能有子类。关于 `final` 修饰符的更多内容,请参见[防止重写](./13_Inheritance.html#preventing_overrides)。
|
||||
|
||||
如果一个子类重写了父类的指定构造器,并且该构造器满足了某个协议的要求,那么该构造器的实现需要同时标注 `required` 和 `override` 修饰符:
|
||||
|
||||
```swift
|
||||
protocol SomeProtocol {
|
||||
init()
|
||||
}
|
||||
|
||||
class SomeSuperClass {
|
||||
init() {
|
||||
// 这里是构造器的实现部分
|
||||
}
|
||||
}
|
||||
|
||||
class SomeSubClass: SomeSuperClass, SomeProtocol {
|
||||
// 因为采纳协议,需要加上 required
|
||||
// 因为继承自父类,需要加上 override
|
||||
required override init() {
|
||||
// 这里是构造器的实现部分
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 可失败构造器要求
|
||||
|
||||
协议还可以为采纳协议的类型定义可失败构造器要求,详见[可失败构造器](./14_Initialization.html#failable_initializers)。
|
||||
|
||||
采纳协议的类型可以通过可失败构造器(`init?`)或非可失败构造器(`init`)来满足协议中定义的可失败构造器要求。协议中定义的非可失败构造器要求可以通过非可失败构造器(`init`)或隐式解包可失败构造器(`init!`)来满足。
|
||||
|
||||
<a name="protocols_as_types"></a>
|
||||
## 协议作为类型
|
||||
|
||||
尽管协议本身并未实现任何功能,但是协议可以被当做一个成熟的类型来使用。
|
||||
|
||||
协议可以像其他普通类型一样使用,使用场景如下:
|
||||
|
||||
* 作为函数、方法或构造器中的参数类型或返回值类型
|
||||
* 作为常量、变量或属性的类型
|
||||
* 作为数组、字典或其他容器中的元素类型
|
||||
|
||||
> 注意
|
||||
> 协议是一种类型,因此协议类型的名称应与其他类型(例如 `Int`,`Double`,`String`)的写法相同,使用大写字母开头的驼峰式写法,例如(`FullyNamed` 和 `RandomNumberGenerator`)。
|
||||
|
||||
下面是将协议作为类型使用的例子:
|
||||
|
||||
```swift
|
||||
class Dice {
|
||||
let sides: Int
|
||||
let generator: RandomNumberGenerator
|
||||
init(sides: Int, generator: RandomNumberGenerator) {
|
||||
self.sides = sides
|
||||
self.generator = generator
|
||||
}
|
||||
func roll() -> Int {
|
||||
return Int(generator.random() * Double(sides)) + 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
例子中定义了一个 `Dice` 类,用来代表桌游中拥有 N 个面的骰子。`Dice` 的实例含有 `sides` 和 `generator` 两个属性,前者是整型,用来表示骰子有几个面,后者为骰子提供一个随机数生成器,从而生成随机点数。
|
||||
|
||||
`generator` 属性的类型为 `RandomNumberGenerator`,因此任何采纳了 `RandomNumberGenerator` 协议的类型的实例都可以赋值给 `generator`,除此之外并无其他要求。
|
||||
|
||||
`Dice` 类还有一个构造器,用来设置初始状态。构造器有一个名为 `generator`,类型为 `RandomNumberGenerator` 的形参。在调用构造方法创建 `Dice` 的实例时,可以传入任何采纳 `RandomNumberGenerator` 协议的实例给 `generator`。
|
||||
|
||||
`Dice` 类提供了一个名为 `roll` 的实例方法,用来模拟骰子的面值。它先调用 `generator` 的 `random()` 方法来生成一个 `[0.0,1.0)` 区间内的随机数,然后使用这个随机数生成正确的骰子面值。因为 `generator` 采纳了 `RandomNumberGenerator` 协议,可以确保它有个 `random()` 方法可供调用。
|
||||
|
||||
下面的例子展示了如何使用 `LinearCongruentialGenerator` 的实例作为随机数生成器来创建一个六面骰子:
|
||||
|
||||
```swift
|
||||
var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
|
||||
for _ in 1...5 {
|
||||
print("Random dice roll is \(d6.roll())")
|
||||
}
|
||||
// Random dice roll is 3
|
||||
// Random dice roll is 5
|
||||
// Random dice roll is 4
|
||||
// Random dice roll is 5
|
||||
// Random dice roll is 4
|
||||
```
|
||||
|
||||
<a name="delegation"></a>
|
||||
## 委托(代理)模式
|
||||
|
||||
委托是一种设计模式,它允许类或结构体将一些需要它们负责的功能委托给其他类型的实例。委托模式的实现很简单:定义协议来封装那些需要被委托的功能,这样就能确保采纳协议的类型能提供这些功能。委托模式可以用来响应特定的动作,或者接收外部数据源提供的数据,而无需关心外部数据源的类型。
|
||||
|
||||
下面的例子定义了两个基于骰子游戏的协议:
|
||||
|
||||
```swift
|
||||
protocol DiceGame {
|
||||
var dice: Dice { get }
|
||||
func play()
|
||||
}
|
||||
|
||||
protocol DiceGameDelegate {
|
||||
func gameDidStart(game: DiceGame)
|
||||
func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll:Int)
|
||||
func gameDidEnd(game: DiceGame)
|
||||
}
|
||||
```
|
||||
|
||||
`DiceGame` 协议可以被任意涉及骰子的游戏采纳。`DiceGameDelegate` 协议可以被任意类型采纳,用来追踪 `DiceGame` 的游戏过程。
|
||||
|
||||
如下所示,`SnakesAndLadders` 是 [Control Flow](./05_Control_Flow.html) 章节引入的蛇梯棋游戏的新版本。新版本使用 `Dice` 实例作为骰子,并且实现了 `DiceGame` 和 `DiceGameDelegate` 协议,后者用来记录游戏的过程:
|
||||
|
||||
```swift
|
||||
class SnakesAndLadders: DiceGame {
|
||||
let finalSquare = 25
|
||||
let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
|
||||
var square = 0
|
||||
var board: [Int]
|
||||
init() {
|
||||
board = [Int](count: finalSquare + 1, repeatedValue: 0)
|
||||
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
|
||||
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
|
||||
}
|
||||
var delegate: DiceGameDelegate?
|
||||
func play() {
|
||||
square = 0
|
||||
delegate?.gameDidStart(self)
|
||||
gameLoop: while square != finalSquare {
|
||||
let diceRoll = dice.roll()
|
||||
delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)
|
||||
switch square + diceRoll {
|
||||
case finalSquare:
|
||||
break gameLoop
|
||||
case let newSquare where newSquare > finalSquare:
|
||||
continue gameLoop
|
||||
default:
|
||||
square += diceRoll
|
||||
square += board[square]
|
||||
}
|
||||
}
|
||||
delegate?.gameDidEnd(self)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
关于这个蛇梯棋游戏的详细描述请参阅 [Control Flow](./05_Control_Flow.html) 章节中的 [Break](./05_Control_Flow.html#break) 部分。
|
||||
|
||||
这个版本的游戏封装到了 `SnakesAndLadders` 类中,该类采纳了 `DiceGame` 协议,并且提供了相应的可读的 `dice` 属性和 `play()` 方法。( `dice` 属性在构造之后就不再改变,且协议只要求 `dice` 为可读的,因此将 `dice` 声明为常量属性。)
|
||||
|
||||
游戏使用 `SnakesAndLadders` 类的 `init()` 构造器来初始化游戏。所有的游戏逻辑被转移到了协议中的 `play()` 方法,`play()` 方法使用协议要求的 `dice` 属性提供骰子摇出的值。
|
||||
|
||||
注意,`delegate` 并不是游戏的必备条件,因此 `delegate` 被定义为 `DiceGameDelegate` 类型的可选属性。因为 `delegate` 是可选值,因此会被自动赋予初始值 `nil`。随后,可以在游戏中为 `delegate` 设置适当的值。
|
||||
|
||||
`DicegameDelegate` 协议提供了三个方法用来追踪游戏过程。这三个方法被放置于游戏的逻辑中,即 `play()` 方法内。分别在游戏开始时,新一轮开始时,以及游戏结束时被调用。
|
||||
|
||||
因为 `delegate` 是一个 `DiceGameDelegate` 类型的可选属性,因此在 `play()` 方法中通过可选链式调用来调用它的方法。若 `delegate` 属性为 `nil`,则调用方法会优雅地失败,并不会产生错误。若 `delegate` 不为 `nil`,则方法能够被调用,并传递 `SnakesAndLadders` 实例作为参数。
|
||||
|
||||
如下示例定义了 `DiceGameTracker` 类,它采纳了 `DiceGameDelegate` 协议:
|
||||
|
||||
```swift
|
||||
class DiceGameTracker: DiceGameDelegate {
|
||||
var numberOfTurns = 0
|
||||
func gameDidStart(game: DiceGame) {
|
||||
numberOfTurns = 0
|
||||
if game is SnakesAndLadders {
|
||||
print("Started a new game of Snakes and Ladders")
|
||||
}
|
||||
print("The game is using a \(game.dice.sides)-sided dice")
|
||||
}
|
||||
func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
|
||||
++numberOfTurns
|
||||
print("Rolled a \(diceRoll)")
|
||||
}
|
||||
func gameDidEnd(game: DiceGame) {
|
||||
print("The game lasted for \(numberOfTurns) turns")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`DiceGameTracker` 实现了 `DiceGameDelegate` 协议要求的三个方法,用来记录游戏已经进行的轮数。当游戏开始时,`numberOfTurns` 属性被赋值为 `0`,然后在每新一轮中递增,游戏结束后,打印游戏的总轮数。
|
||||
|
||||
`gameDidStart(_:)` 方法从 `game` 参数获取游戏信息并打印。`game` 参数是 `DiceGame` 类型而不是 `SnakeAndLadders` 类型,所以在方法中只能访问 `DiceGame` 协议中的内容。当然了,`SnakeAndLadders` 的方法也可以在类型转换之后调用。在上例代码中,通过 `is` 操作符检查 `game` 是否为 `SnakesAndLadders` 类型的实例,如果是,则打印出相应的消息。
|
||||
|
||||
无论当前进行的是何种游戏,由于 `game` 符合 `DiceGame` 协议,可以确保 `game` 含有 `dice` 属性。因此在 `gameDidStart(_:)` 方法中可以通过传入的 `game` 参数来访问 `dice` 属性,进而打印出 `dice` 的 `sides` 属性的值。
|
||||
|
||||
`DiceGameTracker` 的运行情况如下所示:
|
||||
|
||||
```swift
|
||||
let tracker = DiceGameTracker()
|
||||
let game = SnakesAndLadders()
|
||||
game.delegate = tracker
|
||||
game.play()
|
||||
// Started a new game of Snakes and Ladders
|
||||
// The game is using a 6-sided dice
|
||||
// Rolled a 3
|
||||
// Rolled a 5
|
||||
// Rolled a 4
|
||||
// Rolled a 5
|
||||
// The game lasted for 4 turns
|
||||
```
|
||||
|
||||
<a name="adding_protocol_conformance_with_an_extension"></a>
|
||||
## 通过扩展添加协议一致性
|
||||
|
||||
即便无法修改源代码,依然可以通过扩展令已有类型采纳并符合协议。扩展可以为已有类型添加属性、方法、下标以及构造器,因此可以符合协议中的相应要求。详情请在[扩展](./21_Extensions.html)章节中查看。
|
||||
|
||||
> 注意
|
||||
> 通过扩展令已有类型采纳并符合协议时,该类型的所有实例也会随之获得协议中定义的各项功能。
|
||||
|
||||
例如下面这个 `TextRepresentable` 协议,任何想要通过文本表示一些内容的类型都可以实现该协议。这些想要表示的内容可以是实例本身的描述,也可以是实例当前状态的文本描述:
|
||||
|
||||
```swift
|
||||
protocol TextRepresentable {
|
||||
var textualDescription: String { get }
|
||||
}
|
||||
```
|
||||
|
||||
可以通过扩展,令先前提到的 `Dice` 类采纳并符合 `TextRepresentable` 协议:
|
||||
|
||||
```swift
|
||||
extension Dice: TextRepresentable {
|
||||
var textualDescription: String {
|
||||
return "A \(sides)-sided dice"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
通过扩展采纳并符合协议,和在原始定义中采纳并符合协议的效果完全相同。协议名称写在类型名之后,以冒号隔开,然后在扩展的大括号内实现协议要求的内容。
|
||||
|
||||
现在所有 `Dice` 的实例都可以看做 `TextRepresentable` 类型:
|
||||
|
||||
```swift
|
||||
let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
|
||||
print(d12.textualDescription)
|
||||
// 打印 “A 12-sided dice”
|
||||
```
|
||||
|
||||
同样,`SnakesAndLadders` 类也可以通过扩展采纳并符合 `TextRepresentable` 协议:
|
||||
|
||||
```swift
|
||||
extension SnakesAndLadders: TextRepresentable {
|
||||
var textualDescription: String {
|
||||
return "A game of Snakes and Ladders with \(finalSquare) squares"
|
||||
}
|
||||
}
|
||||
print(game.textualDescription)
|
||||
// 打印 “A game of Snakes and Ladders with 25 squares”
|
||||
```
|
||||
|
||||
<a name="declaring_protocol_adoption_with_an_extension"></a>
|
||||
## 通过扩展采纳协议
|
||||
|
||||
当一个类型已经符合了某个协议中的所有要求,却还没有声明采纳该协议时,可以通过空扩展体的扩展来采纳该协议:
|
||||
|
||||
```swift
|
||||
struct Hamster {
|
||||
var name: String
|
||||
var textualDescription: String {
|
||||
return "A hamster named \(name)"
|
||||
}
|
||||
}
|
||||
extension Hamster: TextRepresentable {}
|
||||
```
|
||||
|
||||
从现在起,`Hamster` 的实例可以作为 `TextRepresentable` 类型使用:
|
||||
|
||||
```swift
|
||||
let simonTheHamster = Hamster(name: "Simon")
|
||||
let somethingTextRepresentable: TextRepresentable = simonTheHamster
|
||||
print(somethingTextRepresentable.textualDescription)
|
||||
// 打印 “A hamster named Simon”
|
||||
```
|
||||
|
||||
> 注意
|
||||
> 即使满足了协议的所有要求,类型也不会自动采纳协议,必须显式地采纳协议。
|
||||
|
||||
<a name="collections_of_protocol_types"></a>
|
||||
## 协议类型的集合
|
||||
|
||||
协议类型可以在数组或者字典这样的集合中使用,在[协议类型](./22_Protocols.html##protocols_as_types)提到了这样的用法。下面的例子创建了一个元素类型为 `TextRepresentable` 的数组:
|
||||
|
||||
```swift
|
||||
let things: [TextRepresentable] = [game, d12, simonTheHamster]
|
||||
```
|
||||
|
||||
如下所示,可以遍历 `things` 数组,并打印每个元素的文本表示:
|
||||
|
||||
```swift
|
||||
for thing in things {
|
||||
print(thing.textualDescription)
|
||||
}
|
||||
// A game of Snakes and Ladders with 25 squares
|
||||
// A 12-sided dice
|
||||
// A hamster named Simon
|
||||
```
|
||||
|
||||
`thing` 是 `TextRepresentable` 类型而不是 `Dice`,`DiceGame`,`Hamster` 等类型,即使实例在幕后确实是这些类型中的一种。由于 `thing` 是 `TextRepresentable` 类型,任何 `TextRepresentable` 的实例都有一个 `textualDescription` 属性,所以在每次循环中可以安全地访问 `thing.textualDescription`。
|
||||
|
||||
<a name="protocol_inheritance"></a>
|
||||
## 协议的继承
|
||||
|
||||
协议能够继承一个或多个其他协议,可以在继承的协议的基础上增加新的要求。协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔:
|
||||
|
||||
```swift
|
||||
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
|
||||
// 这里是协议的定义部分
|
||||
}
|
||||
```
|
||||
|
||||
如下所示,`PrettyTextRepresentable` 协议继承了 `TextRepresentable` 协议:
|
||||
|
||||
```swift
|
||||
protocol PrettyTextRepresentable: TextRepresentable {
|
||||
var prettyTextualDescription: String { get }
|
||||
}
|
||||
```
|
||||
|
||||
例子中定义了一个新的协议 `PrettyTextRepresentable`,它继承自 `TextRepresentable` 协议。任何采纳 `PrettyTextRepresentable` 协议的类型在满足该协议的要求时,也必须满足 `TextRepresentable` 协议的要求。在这个例子中,`PrettyTextRepresentable` 协议额外要求采纳协议的类型提供一个返回值为 `String` 类型的 `prettyTextualDescription` 属性。
|
||||
|
||||
如下所示,扩展 `SnakesAndLadders`,使其采纳并符合 `PrettyTextRepresentable` 协议:
|
||||
|
||||
```swift
|
||||
extension SnakesAndLadders: PrettyTextRepresentable {
|
||||
var prettyTextualDescription: String {
|
||||
var output = textualDescription + ":\n"
|
||||
for index in 1...finalSquare {
|
||||
switch board[index] {
|
||||
case let ladder where ladder > 0:
|
||||
output += "▲ "
|
||||
case let snake where snake < 0:
|
||||
output += "▼ "
|
||||
default:
|
||||
output += "○ "
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上述扩展令 `SnakesAndLadders` 采纳了 `PrettyTextRepresentable` 协议,并提供了协议要求的 `prettyTextualDescription` 属性。每个 `PrettyTextRepresentable` 类型同时也是 `TextRepresentable` 类型,所以在 `prettyTextualDescription` 的实现中,可以访问 `textualDescription` 属性。然后,拼接上了冒号和换行符。接着,遍历数组中的元素,拼接一个几何图形来表示每个棋盘方格的内容:
|
||||
|
||||
* 当从数组中取出的元素的值大于 `0` 时,用 `▲` 表示。
|
||||
* 当从数组中取出的元素的值小于 `0` 时,用 `▼` 表示。
|
||||
* 当从数组中取出的元素的值等于 `0` 时,用 `○` 表示。
|
||||
|
||||
任意 `SankesAndLadders` 的实例都可以使用 `prettyTextualDescription` 属性来打印一个漂亮的文本描述:
|
||||
|
||||
```swift
|
||||
print(game.prettyTextualDescription)
|
||||
// A game of Snakes and Ladders with 25 squares:
|
||||
// ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○
|
||||
```
|
||||
|
||||
<a name="class_only_protocol"></a>
|
||||
## 类类型专属协议
|
||||
|
||||
你可以在协议的继承列表中,通过添加 `class` 关键字来限制协议只能被类类型采纳,而结构体或枚举不能采纳该协议。`class` 关键字必须第一个出现在协议的继承列表中,在其他继承的协议之前:
|
||||
|
||||
```swift
|
||||
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
|
||||
// 这里是类类型专属协议的定义部分
|
||||
}
|
||||
```
|
||||
|
||||
在以上例子中,协议 `SomeClassOnlyProtocol` 只能被类类型采纳。如果尝试让结构体或枚举类型采纳该协议,则会导致编译错误。
|
||||
|
||||
> 注意
|
||||
> 当协议定义的要求需要采纳协议的类型必须是引用语义而非值语义时,应该采用类类型专属协议。关于引用语义和值语义的更多内容,请查看[结构体和枚举是值类型](./09_Classes_and_Structures.html#structures_and_enumerations_are_value_types)和[类是引用类型](./09_Classes_and_Structures.html#classes_are_reference_types)。
|
||||
|
||||
<a name="protocol_composition"></a>
|
||||
## 协议合成
|
||||
|
||||
有时候需要同时采纳多个协议,你可以将多个协议采用 `protocol<SomeProtocol, AnotherProtocol>` 这样的格式进行组合,称为 *协议合成(protocol composition)*。你可以在 `<>` 中罗列任意多个你想要采纳的协议,以逗号分隔。
|
||||
|
||||
下面的例子中,将 `Named` 和 `Aged` 两个协议按照上述语法组合成一个协议,作为函数参数的类型:
|
||||
|
||||
```swift
|
||||
protocol Named {
|
||||
var name: String { get }
|
||||
}
|
||||
protocol Aged {
|
||||
var age: Int { get }
|
||||
}
|
||||
struct Person: Named, Aged {
|
||||
var name: String
|
||||
var age: Int
|
||||
}
|
||||
func wishHappyBirthday(celebrator: protocol<Named, Aged>) {
|
||||
print("Happy birthday \(celebrator.name) - you're \(celebrator.age)!")
|
||||
}
|
||||
let birthdayPerson = Person(name: "Malcolm", age: 21)
|
||||
wishHappyBirthday(birthdayPerson)
|
||||
// 打印 “Happy birthday Malcolm - you're 21!”
|
||||
```
|
||||
|
||||
`Named` 协议包含 `String` 类型的 `name` 属性。`Aged` 协议包含 `Int` 类型的 `age` 属性。`Person` 结构体采纳了这两个协议。
|
||||
|
||||
`wishHappyBirthday(_:)` 函数的参数 `celebrator` 的类型为 `protocol<Named,Aged>`。这意味着它不关心参数的具体类型,只要参数符合这两个协议即可。
|
||||
|
||||
上面的例子创建了一个名为 `birthdayPerson` 的 `Person` 的实例,作为参数传递给了 `wishHappyBirthday(_:)` 函数。因为 `Person` 同时符合这两个协议,所以这个参数合法,函数将打印生日问候语。
|
||||
|
||||
> 注意
|
||||
> 协议合成并不会生成新的、永久的协议类型,而是将多个协议中的要求合成到一个只在局部作用域有效的临时协议中。
|
||||
|
||||
<a name="checking_for_protocol_conformance"></a>
|
||||
## 检查协议一致性
|
||||
|
||||
你可以使用[类型转换](./20_Type_Casting.html)中描述的 `is` 和 `as` 操作符来检查协议一致性,即是否符合某协议,并且可以转换到指定的协议类型。检查和转换到某个协议类型在语法上和类型的检查和转换完全相同:
|
||||
|
||||
* `is` 用来检查实例是否符合某个协议,若符合则返回 `true`,否则返回 `false`。
|
||||
* `as?` 返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回 `nil`。
|
||||
* `as!` 将实例强制向下转换到某个协议类型,如果强转失败,会引发运行时错误。
|
||||
|
||||
下面的例子定义了一个 `HasArea` 协议,该协议定义了一个 `Double` 类型的可读属性 `area`:
|
||||
|
||||
```swift
|
||||
protocol HasArea {
|
||||
var area: Double { get }
|
||||
}
|
||||
```
|
||||
|
||||
如下所示,`Circle` 类和 `Country` 类都采纳了 `HasArea` 协议:
|
||||
|
||||
```swift
|
||||
class Circle: HasArea {
|
||||
let pi = 3.1415927
|
||||
var radius: Double
|
||||
var area: Double { return pi * radius * radius }
|
||||
init(radius: Double) { self.radius = radius }
|
||||
}
|
||||
class Country: HasArea {
|
||||
var area: Double
|
||||
init(area: Double) { self.area = area }
|
||||
}
|
||||
```
|
||||
|
||||
`Circle` 类把 `area` 属性实现为基于存储型属性 `radius` 的计算型属性。`Country` 类则把 `area` 属性实现为存储型属性。这两个类都正确地符合了 `HasArea` 协议。
|
||||
|
||||
如下所示,`Animal` 是一个未采纳 `HasArea` 协议的类:
|
||||
|
||||
```swift
|
||||
class Animal {
|
||||
var legs: Int
|
||||
init(legs: Int) { self.legs = legs }
|
||||
}
|
||||
```
|
||||
|
||||
`Circle`,`Country`,`Animal` 并没有一个共同的基类,尽管如此,它们都是类,它们的实例都可以作为 `AnyObject` 类型的值,存储在同一个数组中:
|
||||
|
||||
```swift
|
||||
let objects: [AnyObject] = [
|
||||
Circle(radius: 2.0),
|
||||
Country(area: 243_610),
|
||||
Animal(legs: 4)
|
||||
]
|
||||
```
|
||||
|
||||
`objects` 数组使用字面量初始化,数组包含一个 `radius` 为 `2` 的 `Circle` 的实例,一个保存了英国国土面积的 `Country` 实例和一个 `legs` 为 `4` 的 `Animal` 实例。
|
||||
|
||||
如下所示,`objects` 数组可以被迭代,并对迭代出的每一个元素进行检查,看它是否符合 `HasArea` 协议:
|
||||
|
||||
```swift
|
||||
for object in objects {
|
||||
if let objectWithArea = object as? HasArea {
|
||||
print("Area is \(objectWithArea.area)")
|
||||
} else {
|
||||
print("Something that doesn't have an area")
|
||||
}
|
||||
}
|
||||
// Area is 12.5663708
|
||||
// Area is 243610.0
|
||||
// Something that doesn't have an area
|
||||
```
|
||||
|
||||
当迭代出的元素符合 `HasArea` 协议时,将 `as?` 操作符返回的可选值通过可选绑定,绑定到 `objectWithArea` 常量上。`objectWithArea` 是 `HasArea` 协议类型的实例,因此 `area` 属性可以被访问和打印。
|
||||
|
||||
`objects` 数组中的元素的类型并不会因为强转而丢失类型信息,它们仍然是 `Circle`,`Country`,`Animal` 类型。然而,当它们被赋值给 `objectWithArea` 常量时,只被视为 `HasArea` 类型,因此只有 `area` 属性能够被访问。
|
||||
|
||||
<a name="optional_protocol_requirements"></a>
|
||||
## 可选的协议要求
|
||||
|
||||
协议可以定义可选要求,采纳协议的类型可以选择是否实现这些要求。在协议中使用 `optional` 关键字作为前缀来定义可选要求。使用可选要求时(例如,可选的方法或者属性),它们的类型会自动变成可选的。比如,一个类型为 `(Int) -> String` 的方法会变成 `((Int) -> String)?`。需要注意的是整个函数类型是可选的,而不是函数的返回值。
|
||||
|
||||
协议中的可选要求可通过可选链式调用来使用,因为采纳协议的类型可能没有实现这些可选要求。类似 `someOptionalMethod?(someArgument)` 这样,你可以在可选方法名称后加上 `?` 来调用可选方法。详细内容可在[可选链式调用](./17_Optional_Chaining.html)章节中查看。
|
||||
|
||||
> 注意
|
||||
> 可选的协议要求只能用在标记 `@objc` 特性的协议中。
|
||||
> 该特性表示协议将暴露给 Objective-C 代码,详情参见[`Using Swift with Cocoa and Objective-C(Swift 2.1)`](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216)。即使你不打算和 Objective-C 有什么交互,如果你想要指定可选的协议要求,那么还是要为协议加上 `@obj` 特性。
|
||||
> 还需要注意的是,标记 `@objc` 特性的协议只能被继承自 Objective-C 类的类或者 `@objc` 类采纳,其他类以及结构体和枚举均不能采纳这种协议。
|
||||
|
||||
下面的例子定义了一个名为 `Counter` 的用于整数计数的类,它使用外部的数据源来提供每次的增量。数据源由 `CounterDataSource` 协议定义,包含两个可选要求:
|
||||
|
||||
```swift
|
||||
@objc protocol CounterDataSource {
|
||||
optional func incrementForCount(count: Int) -> Int
|
||||
optional var fixedIncrement: Int { get }
|
||||
}
|
||||
```
|
||||
|
||||
`CounterDataSource` 协议定义了一个可选方法 `incrementForCount(_:)` 和一个可选属性 `fiexdIncrement`,它们使用了不同的方法来从数据源中获取适当的增量值。
|
||||
|
||||
> 注意
|
||||
> 严格来讲,`CounterDataSource` 协议中的方法和属性都是可选的,因此采纳协议的类可以不实现这些要求,尽管技术上允许这样做,不过最好不要这样写。
|
||||
|
||||
`Counter` 类含有 `CounterDataSource?` 类型的可选属性 `dataSource`,如下所示:
|
||||
|
||||
```swift
|
||||
class Counter {
|
||||
var count = 0
|
||||
var dataSource: CounterDataSource?
|
||||
func increment() {
|
||||
if let amount = dataSource?.incrementForCount?(count) {
|
||||
count += amount
|
||||
} else if let amount = dataSource?.fixedIncrement {
|
||||
count += amount
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Counter` 类使用变量属性 `count` 来存储当前值。该类还定义了一个 `increment()` 方法,每次调用该方法的时候,将会增加 `count` 的值。
|
||||
|
||||
`increment()` 方法首先试图使用 `incrementForCount(_:)` 方法来得到每次的增量。`increment()` 方法使用可选链式调用来尝试调用 `incrementForCount(_:)`,并将当前的 `count` 值作为参数传入。
|
||||
|
||||
这里使用了两层可选链式调用。首先,由于 `dataSource` 可能为 `nil`,因此在 `dataSource` 后边加上了 `?`,以此表明只在 `dataSource` 非空时才去调用 `incrementForCount(_:)` 方法。其次,即使 `dataSource` 存在,也无法保证其是否实现了 `incrementForCount(_:)` 方法,因为这个方法是可选的。因此,`incrementForCount(_:)` 方法同样使用可选链式调用进行调用,只有在该方法被实现的情况下才能调用它,所以在 `incrementForCount(_:)` 方法后边也加上了 `?`。
|
||||
|
||||
|
||||
调用 `incrementForCount(_:)` 方法在上述两种情形下都有可能失败,所以返回值为 `Int?` 类型。虽然在 `CounterDataSource` 协议中,`incrementForCount(_:)` 的返回值类型是非可选 `Int`。另外,即使这里使用了两层可选链式调用,最后的返回结果依旧是单层的可选类型,即 `Int?` 而不是 `Int??`。关于这一点的更多信息,请查阅[连接多层可选链式调用](./17_Optional_Chaining)
|
||||
|
||||
在调用 `incrementForCount(_:)` 方法后,`Int?` 型的返回值通过可选绑定解包并赋值给常量 `amount`。如果可选值确实包含一个数值,也就是说,数据源和方法都存在,数据源方法返回了一个有效值。之后便将解包后的 `amount` 加到 `count` 上,增量操作完成。
|
||||
|
||||
如果没有从 `incrementForCount(_:)` 方法获取到值,可能由于 `dataSource` 为 `nil`,或者它并没有实现 `incrementForCount(_:)` 方法,那么 `increment()` 方法将试图从数据源的 `fixedIncrement` 属性中获取增量。`fixedIncrement` 是一个可选属性,因此属性值是一个 `Int?` 值,即使该属性在 `CounterDataSource` 协议中的类型是非可选的 `Int`。
|
||||
|
||||
下面的例子展示了 `CounterDataSource` 的简单实现。`ThreeSource` 类采纳了 `CounterDataSource` 协议,它实现了可选属性 `fixedIncrement`,每次会返回 `3`:
|
||||
|
||||
```swift
|
||||
class ThreeSource: NSObject, CounterDataSource {
|
||||
let fixedIncrement = 3
|
||||
}
|
||||
```
|
||||
|
||||
可以使用 `ThreeSource` 的实例作为 `Counter` 实例的数据源:
|
||||
|
||||
```swift
|
||||
var counter = Counter()
|
||||
counter.dataSource = ThreeSource()
|
||||
for _ in 1...4 {
|
||||
counter.increment()
|
||||
print(counter.count)
|
||||
}
|
||||
// 3
|
||||
// 6
|
||||
// 9
|
||||
// 12
|
||||
```
|
||||
|
||||
上述代码新建了一个 `Counter` 实例,并将它的数据源设置为一个 `TreeSource` 的实例,然后调用 `increment()` 方法四次。和预期一样,每次调用都会将 `count` 的值增加 `3`.
|
||||
|
||||
下面是一个更为复杂的数据源 `TowardsZeroSource`,它将使得最后的值变为 `0`:
|
||||
|
||||
```swift
|
||||
@objc class TowardsZeroSource: NSObject, CounterDataSource {
|
||||
func incrementForCount(count: Int) -> Int {
|
||||
if count == 0 {
|
||||
return 0
|
||||
} else if count < 0 {
|
||||
return 1
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`TowardsZeroSource` 实现了 `CounterDataSource` 协议中的 `incrementForCount(_:)` 方法,以 `count` 参数为依据,计算出每次的增量。如果 `count` 已经为 `0`,此方法返回 `0`,以此表明之后不应再有增量操作发生。
|
||||
|
||||
你可以使用 `TowardsZeroSource` 实例将 `Counter` 实例来从 `-4` 增加到 `0`。一旦增加到 `0`,数值便不会再有变动:
|
||||
|
||||
```swift
|
||||
counter.count = -4
|
||||
counter.dataSource = TowardsZeroSource()
|
||||
for _ in 1...5 {
|
||||
counter.increment()
|
||||
print(counter.count)
|
||||
}
|
||||
// -3
|
||||
// -2
|
||||
// -1
|
||||
// 0
|
||||
// 0
|
||||
```
|
||||
|
||||
<a name="protocol_extensions"></a>
|
||||
## 协议扩展
|
||||
|
||||
协议可以通过扩展来为采纳协议的类型提供属性、方法以及下标的实现。通过这种方式,你可以基于协议本身来实现这些功能,而无需在每个采纳协议的类型中都重复同样的实现,也无需使用全局函数。
|
||||
|
||||
例如,可以扩展 `RandomNumberGenerator` 协议来提供 `randomBool()` 方法。该方法使用协议中定义的 `random()` 方法来返回一个随机的 `Bool` 值:
|
||||
|
||||
```swift
|
||||
extension RandomNumberGenerator {
|
||||
func randomBool() -> Bool {
|
||||
return random() > 0.5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
通过协议扩展,所有采纳协议的类型,都能自动获得这个扩展所增加的方法实现,无需任何额外修改:
|
||||
|
||||
```swift
|
||||
let generator = LinearCongruentialGenerator()
|
||||
print("Here's a random number: \(generator.random())")
|
||||
// 打印 “Here's a random number: 0.37464991998171”
|
||||
print("And here's a random Boolean: \(generator.randomBool())")
|
||||
// 打印 “And here's a random Boolean: true”
|
||||
```
|
||||
|
||||
<a name="providing_default_implementations"></a>
|
||||
### 提供默认实现
|
||||
|
||||
可以通过协议扩展来为协议要求的属性、方法以及下标提供默认的实现。如果采纳协议的类型为这些要求提供了自己的实现,那么这些自定义实现将会替代扩展中的默认实现被使用。
|
||||
|
||||
> 注意
|
||||
> 通过协议扩展为协议要求提供的默认实现和可选的协议要求不同。虽然在这两种情况下,采纳协议的类型都无需自己实现这些要求,但是通过扩展提供的默认实现可以直接调用,而无需使用可选链式调用。
|
||||
|
||||
例如,`PrettyTextRepresentable` 协议继承自 `TextRepresentable` 协议,可以为其提供一个默认的 `prettyTextualDescription` 属性,只是简单地返回 `textualDescription` 属性的值:
|
||||
|
||||
```swift
|
||||
extension PrettyTextRepresentable {
|
||||
var prettyTextualDescription: String {
|
||||
return textualDescription
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<a name="adding_constraints_to_protocol_extensions"></a>
|
||||
### 为协议扩展添加限制条件
|
||||
|
||||
在扩展协议的时候,可以指定一些限制条件,只有采纳协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用 `where` 子句来描述,正如[Where子句](./23_Generics.html#where_clauses)中所描述的。
|
||||
|
||||
例如,你可以扩展 `CollectionType` 协议,但是只适用于集合中的元素采纳了 `TextRepresentable` 协议的情况:
|
||||
|
||||
```swift
|
||||
extension CollectionType where Generator.Element: TextRepresentable {
|
||||
var textualDescription: String {
|
||||
let itemsAsText = self.map { $0.textualDescription }
|
||||
return "[" + itemsAsText.joinWithSeparator(", ") + "]"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`textualDescription` 属性返回整个集合的文本描述,它将集合中的每个元素的文本描述以逗号分隔的方式连接起来,包在一对方括号中。
|
||||
|
||||
现在我们来看看先前的 `Hamster` 结构体,它符合 `TextRepresentable` 协议,同时这里还有个装有 `Hamster` 的实例的数组:
|
||||
|
||||
```swift
|
||||
let murrayTheHamster = Hamster(name: "Murray")
|
||||
let morganTheHamster = Hamster(name: "Morgan")
|
||||
let mauriceTheHamster = Hamster(name: "Maurice")
|
||||
let hamsters = [murrayTheHamster, morganTheHamster, mauriceTheHamster]
|
||||
```
|
||||
|
||||
因为 `Array` 符合 `CollectionType` 协议,而数组中的元素又符合 `TextRepresentable` 协议,所以数组可以使用 `textualDescription` 属性得到数组内容的文本表示:
|
||||
|
||||
```swift
|
||||
print(hamsters.textualDescription)
|
||||
// 打印 “[A hamster named Murray, A hamster named Morgan, A hamster named Maurice]”
|
||||
```
|
||||
|
||||
> 注意
|
||||
> 如果多个协议扩展都为同一个协议要求提供了默认实现,而采纳协议的类型又同时满足这些协议扩展的限制条件,那么将会使用限制条件最多的那个协议扩展提供的默认实现。
|
||||
@ -1,535 +0,0 @@
|
||||
# 泛型(Generics)
|
||||
|
||||
------
|
||||
|
||||
> 1.0
|
||||
> 翻译:[takalard](https://github.com/takalard)
|
||||
> 校对:[lifedim](https://github.com/lifedim)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对: [SergioChan](https://github.com/SergioChan)
|
||||
|
||||
> 2.1
|
||||
> 校对:[shanks](http://codebuild.me),2015-11-01
|
||||
|
||||
> 2.2:翻译+校对:[Lanford](https://github.com/LanfordCai),2016-04-08
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [泛型所解决的问题](#the_problem_that_generics_solve)
|
||||
- [泛型函数](#generic_functions)
|
||||
- [类型参数](#type_parameters)
|
||||
- [命名类型参数](#naming_type_parameters)
|
||||
- [泛型类型](#generic_types)
|
||||
- [扩展一个泛型类型](#extending_a_generic_type)
|
||||
- [类型约束](#type_constraints)
|
||||
- [关联类型](#associated_types)
|
||||
- [Where 子句](#where_clauses)
|
||||
|
||||
泛型代码让你能够根据自定义的需求,编写出适用于任意类型、灵活可重用的函数及类型。它能让你避免代码的重复,用一种清晰和抽象的方式来表达代码的意图。
|
||||
|
||||
泛型是 Swift 最强大的特性之一,许多 Swift 标准库是通过泛型代码构建的。事实上,泛型的使用贯穿了整本语言手册,只是你可能没有发现而已。例如,Swift 的 `Array` 和 `Dictionary` 都是泛型集合。你可以创建一个 `Int` 数组,也可创建一个 `String` 数组,甚至可以是任意其他 Swift 类型的数组。同样的,你也可以创建存储任意指定类型的字典。
|
||||
|
||||
<a name="the_problem_that_generics_solve"></a>
|
||||
## 泛型所解决的问题
|
||||
|
||||
下面是一个标准的非泛型函数 `swapTwoInts(_:_:)`,用来交换两个 `Int` 值:
|
||||
|
||||
```swift
|
||||
func swapTwoInts(inout a: Int, inout _ b: Int) {
|
||||
let temporaryA = a
|
||||
a = b
|
||||
b = temporaryA
|
||||
}
|
||||
```
|
||||
|
||||
这个函数使用输入输出参数(`inout`)来交换 `a` 和 `b` 的值,请参考[输入输出参数](./06_Functions.html#in_out_parameters)。
|
||||
|
||||
`swapTwoInts(_:_:)` 函数交换 `b` 的原始值到 `a`,并交换 `a` 的原始值到 `b`。你可以调用这个函数交换两个 `Int` 变量的值:
|
||||
|
||||
```swift
|
||||
var someInt = 3
|
||||
var anotherInt = 107
|
||||
swapTwoInts(&someInt, &anotherInt)
|
||||
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
|
||||
// 打印 “someInt is now 107, and anotherInt is now 3”
|
||||
```
|
||||
|
||||
诚然,`swapTwoInts(_:_:)` 函数挺有用,但是它只能交换 `Int` 值,如果你想要交换两个 `String` 值或者 `Double`值,就不得不写更多的函数,例如 `swapTwoStrings(_:_:)` 和 `swapTwoDoubles(_:_:)`,如下所示:
|
||||
|
||||
```swift
|
||||
func swapTwoStrings(inout a: String, inout _ b: String) {
|
||||
let temporaryA = a
|
||||
a = b
|
||||
b = temporaryA
|
||||
}
|
||||
|
||||
func swapTwoDoubles(inout a: Double, inout _ b: Double) {
|
||||
let temporaryA = a
|
||||
a = b
|
||||
b = temporaryA
|
||||
}
|
||||
```
|
||||
|
||||
你可能注意到 `swapTwoInts(_:_:)`、`swapTwoStrings(_:_:)` 和 `swapTwoDoubles(_:_:)` 的函数功能都是相同的,唯一不同之处就在于传入的变量类型不同,分别是 `Int`、`String` 和 `Double`。
|
||||
|
||||
在实际应用中,通常需要一个更实用更灵活的函数来交换两个任意类型的值,幸运的是,泛型代码帮你解决了这种问题。(这些函数的泛型版本已经在下面定义好了。)
|
||||
|
||||
> 注意
|
||||
在上面三个函数中,`a` 和 `b` 类型相同。如果 `a` 和 `b` 类型不同,那它们俩就不能互换值。Swift 是类型安全的语言,所以它不允许一个 `String` 类型的变量和一个 `Double` 类型的变量互换值。试图这样做将导致编译错误。
|
||||
|
||||
<a name="generic_functions"></a>
|
||||
## 泛型函数
|
||||
|
||||
泛型函数可以适用于任何类型,下面的 `swapTwoValues(_:_:)` 函数是上面三个函数的泛型版本:
|
||||
|
||||
```swift
|
||||
func swapTwoValues<T>(inout a: T, inout _ b: T) {
|
||||
let temporaryA = a
|
||||
a = b
|
||||
b = temporaryA
|
||||
}
|
||||
```
|
||||
|
||||
`swapTwoValues(_:_:)` 的函数主体和 `swapTwoInts(_:_:)` 函数是一样的,它们只在第一行有点不同,如下所示:
|
||||
|
||||
```swift
|
||||
func swapTwoInts(inout a: Int, inout _ b: Int)
|
||||
func swapTwoValues<T>(inout a: T, inout _ b: T)
|
||||
```
|
||||
|
||||
这个函数的泛型版本使用了占位类型名(在这里用字母 `T` 来表示)来代替实际类型名(例如 `Int`、`String` 或 `Double`)。占位类型名没有指明 `T` 必须是什么类型,但是它指明了 `a` 和 `b` 必须是同一类型 `T`,无论 `T` 代表什么类型。只有 `swapTwoValues(_:_:)` 函数在调用时,才能根据所传入的实际类型决定 `T` 所代表的类型。
|
||||
|
||||
另外一个不同之处在于这个泛型函数名(`swapTwoValues(_:_:)`)后面跟着占位类型名(`T`),并用尖括号括起来(`<T>`)。这个尖括号告诉 Swift 那个 `T` 是 `swapTwoValues(_:_:)` 函数定义内的一个占位类型名,因此 Swift 不会去查找名为 `T` 的实际类型。
|
||||
|
||||
`swapTwoValues(_:_:)` 函数现在可以像 `swapTwoInts(_:_:)` 那样调用,不同的是它能接受两个任意类型的值,条件是这两个值有着相同的类型。`swapTwoValues(_:_:)` 函数被调用时,`T` 所代表的类型都会由传入的值的类型推断出来。
|
||||
|
||||
在下面的两个例子中,`T` 分别代表 `Int` 和 `String`:
|
||||
|
||||
```swift
|
||||
var someInt = 3
|
||||
var anotherInt = 107
|
||||
swapTwoValues(&someInt, &anotherInt)
|
||||
// someInt is now 107, and anotherInt is now 3
|
||||
|
||||
var someString = "hello"
|
||||
var anotherString = "world"
|
||||
swapTwoValues(&someString, &anotherString)
|
||||
// someString is now "world", and anotherString is now "hello"
|
||||
```
|
||||
|
||||
> 注意
|
||||
上面定义的 `swapTwoValues(_:_:)` 函数是受 `swap(_:_:)` 函数启发而实现的。后者存在于 Swift 标准库,你可以在你的应用程序中使用它。如果你在代码中需要类似 `swapTwoValues(_:_:)` 函数的功能,你可以使用已存在的 `swap(_:_:)` 函数。
|
||||
|
||||
<a name="type_parameters"></a>
|
||||
## 类型参数
|
||||
|
||||
在上面的 `swapTwoValues(_:_:)` 例子中,占位类型 `T` 是类型参数的一个例子。类型参数指定并命名一个占位类型,并且紧随在函数名后面,使用一对尖括号括起来(例如 `<T>`)。
|
||||
|
||||
一旦一个类型参数被指定,你可以用它来定义一个函数的参数类型(例如 `swapTwoValues(_:_:)` 函数中的参数 `a` 和 `b`),或者作为函数的返回类型,还可以用作函数主体中的注释类型。在这些情况下,类型参数会在函数调用时被实际类型所替换。(在上面的 `swapTwoValues(_:_:)` 例子中,当函数第一次被调用时,`T` 被 `Int` 替换,第二次调用时,被 `String` 替换。)
|
||||
|
||||
你可提供多个类型参数,将它们都写在尖括号中,用逗号分开。
|
||||
|
||||
<a name="naming_type_parameters"></a>
|
||||
## 命名类型参数
|
||||
|
||||
在大多数情况下,类型参数具有一个描述性名字,例如 `Dictionary<Key, Value>` 中的 `Key` 和 `Value`,以及 `Array<Element>` 中的 `Element`,这可以告诉阅读代码的人这些类型参数和泛型函数之间的关系。然而,当它们之间没有有意义的关系时,通常使用单个字母来命名,例如 `T`、`U`、`V`,正如上面演示的 `swapTwoValues(_:_:)` 函数中的 `T` 一样。
|
||||
|
||||
> 注意
|
||||
请始终使用大写字母开头的驼峰命名法(例如 `T` 和 `MyTypeParameter`)来为类型参数命名,以表明它们是占位类型,而不是一个值。
|
||||
|
||||
<a name="generic_types"></a>
|
||||
## 泛型类型
|
||||
|
||||
除了泛型函数,Swift 还允许你定义泛型类型。这些自定义类、结构体和枚举可以适用于任何类型,类似于 `Array` 和 `Dictionary`。
|
||||
|
||||
这部分内容将向你展示如何编写一个名为 `Stack` (栈)的泛型集合类型。栈是一系列值的有序集合,和 `Array` 类似,但它相比 Swift 的 `Array` 类型有更多的操作限制。数组允许在数组的任意位置插入新元素或是删除其中任意位置的元素。而栈只允许在集合的末端添加新的元素(称之为入栈)。类似的,栈也只能从末端移除元素(称之为出栈)。
|
||||
|
||||
> 注意
|
||||
栈的概念已被 `UINavigationController` 类用来构造视图控制器的导航结构。你通过调用 `UINavigationController` 的 `pushViewController(_:animated:)` 方法来添加新的视图控制器到导航栈,通过 `popViewControllerAnimated(_:)` 方法来从导航栈中移除视图控制器。每当你需要一个严格的“后进先出”方式来管理集合,栈都是最实用的模型。
|
||||
|
||||
下图展示了一个栈的入栈(push)和出栈(pop)的行为:
|
||||
|
||||

|
||||
|
||||
1. 现在有三个值在栈中。
|
||||
2. 第四个值被压入到栈的顶部。
|
||||
3. 现在有四个值在栈中,最近入栈的那个值在顶部。
|
||||
4. 栈中最顶部的那个值被移除,或称之为出栈。
|
||||
5. 移除掉一个值后,现在栈又只有三个值了。
|
||||
|
||||
下面展示了如何编写一个非泛型版本的栈,以 `Int` 型的栈为例:
|
||||
|
||||
```swift
|
||||
struct IntStack {
|
||||
var items = [Int]()
|
||||
mutating func push(item: Int) {
|
||||
items.append(item)
|
||||
}
|
||||
mutating func pop() -> Int {
|
||||
return items.removeLast()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这个结构体在栈中使用一个名为 `items` 的 `Array` 属性来存储值。`Stack` 提供了两个方法:`push(_:)` 和 `pop()`,用来向栈中压入值以及从栈中移除值。这些方法被标记为 `mutating`,因为它们需要修改结构体的 `items` 数组。
|
||||
|
||||
上面的 `IntStack` 结构体只能用于 `Int` 类型。不过,可以定义一个泛型 `Stack` 结构体,从而能够处理任意类型的值。
|
||||
|
||||
下面是相同代码的泛型版本:
|
||||
|
||||
```swift
|
||||
struct Stack<Element> {
|
||||
var items = [Element]()
|
||||
mutating func push(item: Element) {
|
||||
items.append(item)
|
||||
}
|
||||
mutating func pop() -> Element {
|
||||
return items.removeLast()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
注意,`Stack` 基本上和 `IntStack` 相同,只是用占位类型参数 `Element` 代替了实际的 `Int` 类型。这个类型参数包裹在紧随结构体名的一对尖括号里(`<Element>`)。
|
||||
|
||||
`Element` 为待提供的类型定义了一个占位名。这种待提供的类型可以在结构体的定义中通过 `Element` 来引用。在这个例子中,`Element` 在如下三个地方被用作占位符:
|
||||
|
||||
- 创建 `items` 属性,使用 `Element` 类型的空数组对其进行初始化。
|
||||
- 指定 `push(_:)` 方法的唯一参数 `item` 的类型必须是 `Element` 类型。
|
||||
- 指定 `pop()` 方法的返回值类型必须是 `Element` 类型。
|
||||
|
||||
由于 `Stack` 是泛型类型,因此可以用来创建 Swift 中任意有效类型的栈,就像 `Array` 和 `Dictionary` 那样。
|
||||
|
||||
你可以通过在尖括号中写出栈中需要存储的数据类型来创建并初始化一个 `Stack` 实例。例如,要创建一个 `String` 类型的栈,可以写成 `Stack<String>()`:
|
||||
|
||||
```swift
|
||||
var stackOfStrings = Stack<String>()
|
||||
stackOfStrings.push("uno")
|
||||
stackOfStrings.push("dos")
|
||||
stackOfStrings.push("tres")
|
||||
stackOfStrings.push("cuatro")
|
||||
// 栈中现在有 4 个字符串
|
||||
```
|
||||
|
||||
下图展示了 `stackOfStrings` 如何将这四个值入栈:
|
||||
|
||||

|
||||
|
||||
移除并返回栈顶部的值 `"cuatro"`,即将其出栈:
|
||||
|
||||
```swift
|
||||
let fromTheTop = stackOfStrings.pop()
|
||||
// fromTheTop 的值为 "cuatro",现在栈中还有 3 个字符串
|
||||
```
|
||||
|
||||
下图展示了 `stackOfStrings` 如何将顶部的值出栈:
|
||||
|
||||

|
||||
|
||||
<a name="extending_a_generic_type"></a>
|
||||
## 扩展一个泛型类型
|
||||
|
||||
当你扩展一个泛型类型的时候,你并不需要在扩展的定义中提供类型参数列表。原始类型定义中声明的类型参数列表在扩展中可以直接使用,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。
|
||||
|
||||
下面的例子扩展了泛型类型 `Stack`,为其添加了一个名为 `topItem` 的只读计算型属性,它将会返回当前栈顶端的元素而不会将其从栈中移除:
|
||||
|
||||
```swift
|
||||
extension Stack {
|
||||
var topItem: Element? {
|
||||
return items.isEmpty ? nil : items[items.count - 1]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`topItem` 属性会返回一个 `Element` 类型的可选值。当栈为空的时候,`topItem` 会返回 `nil`;当栈不为空的时候,`topItem` 会返回 `items` 数组中的最后一个元素。
|
||||
|
||||
注意,这个扩展并没有定义一个类型参数列表。相反的,`Stack` 类型已有的类型参数名称 `Element`,被用在扩展中来表示计算型属性 `topItem` 的可选类型。
|
||||
|
||||
计算型属性 `topItem` 现在可以用来访问任意 `Stack` 实例的顶端元素且不移除它:
|
||||
|
||||
```swift
|
||||
if let topItem = stackOfStrings.topItem {
|
||||
print("The top item on the stack is \(topItem).")
|
||||
}
|
||||
// 打印 “The top item on the stack is tres.”
|
||||
```
|
||||
|
||||
<a name="type_constraints"></a>
|
||||
## 类型约束
|
||||
|
||||
`swapTwoValues(_:_:)` 函数和 `Stack` 类型可以作用于任何类型。不过,有的时候如果能将使用在泛型函数和泛型类型中的类型添加一个特定的类型约束,将会是非常有用的。类型约束可以指定一个类型参数必须继承自指定类,或者符合一个特定的协议或协议组合。
|
||||
|
||||
例如,Swift 的 `Dictionary` 类型对字典的键的类型做了些限制。在[字典](./04_Collection_Types.html#dictionaries)的描述中,字典的键的类型必须是可哈希(`hashable`)的。也就是说,必须有一种方法能够唯一地表示它。`Dictionary` 的键之所以要是可哈希的,是为了便于检查字典是否已经包含某个特定键的值。若没有这个要求,`Dictionary` 将无法判断是否可以插入或者替换某个指定键的值,也不能查找到已经存储在字典中的指定键的值。
|
||||
|
||||
为了实现这个要求,一个类型约束被强制加到 `Dictionary` 的键类型上,要求其键类型必须符合 `Hashable` 协议,这是 Swift 标准库中定义的一个特定协议。所有的 Swift 基本类型(例如 `String`、`Int`、`Double` 和 `Bool`)默认都是可哈希的。
|
||||
|
||||
当你创建自定义泛型类型时,你可以定义你自己的类型约束,这些约束将提供更为强大的泛型编程能力。抽象概念,例如可哈希的,描述的是类型在概念上的特征,而不是它们的显式类型。
|
||||
|
||||
<a name="type_constraint_syntax"></a>
|
||||
### 类型约束语法
|
||||
|
||||
你可以在一个类型参数名后面放置一个类名或者协议名,并用冒号进行分隔,来定义类型约束,它们将成为类型参数列表的一部分。对泛型函数添加类型约束的基本语法如下所示(作用于泛型类型时的语法与之相同):
|
||||
|
||||
```swift
|
||||
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
|
||||
// 这里是泛型函数的函数体部分
|
||||
}
|
||||
```
|
||||
|
||||
上面这个函数有两个类型参数。第一个类型参数 `T`,有一个要求 `T` 必须是 `SomeClass` 子类的类型约束;第二个类型参数 `U`,有一个要求 `U` 必须符合 `SomeProtocol` 协议的类型约束。
|
||||
|
||||
<a name="type_constraints_in_action"></a>
|
||||
### 类型约束实践
|
||||
|
||||
这里有个名为 `findStringIndex` 的非泛型函数,该函数的功能是在一个 `String` 数组中查找给定 `String` 值的索引。若查找到匹配的字符串,`findStringIndex(_:_:)` 函数返回该字符串在数组中的索引值,否则返回 `nil`:
|
||||
|
||||
```swift
|
||||
func findStringIndex(array: [String], _ valueToFind: String) -> Int? {
|
||||
for (index, value) in array.enumerate() {
|
||||
if value == valueToFind {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
`findStringIndex(_:_:)` 函数可以用于查找字符串数组中的某个字符串:
|
||||
|
||||
```swift
|
||||
let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
|
||||
if let foundIndex = findStringIndex(strings, "llama") {
|
||||
print("The index of llama is \(foundIndex)")
|
||||
}
|
||||
// 打印 “The index of llama is 2”
|
||||
```
|
||||
|
||||
如果只能查找字符串在数组中的索引,用处不是很大。不过,你可以用占位类型 `T` 替换 `String` 类型来写出具有相同功能的泛型函数 `findIndex(_:_:)`。
|
||||
|
||||
下面展示了 `findStringIndex(_:_:)` 函数的泛型版本 `findIndex(_:_:)`。请注意这个函数返回值的类型仍然是 `Int?`,这是因为函数返回的是一个可选的索引数,而不是从数组中得到的一个可选值。需要提醒的是,这个函数无法通过编译,原因会在例子后面说明:
|
||||
|
||||
```swift
|
||||
func findIndex<T>(array: [T], _ valueToFind: T) -> Int? {
|
||||
for (index, value) in array.enumerate() {
|
||||
if value == valueToFind {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
上面所写的函数无法通过编译。问题出在相等性检查上,即 "`if value == valueToFind`"。不是所有的 Swift 类型都可以用等式符(`==`)进行比较。比如说,如果你创建一个自定义的类或结构体来表示一个复杂的数据模型,那么 Swift 无法猜到对于这个类或结构体而言“相等”意味着什么。正因如此,这部分代码无法保证适用于每个可能的类型 `T`,当你试图编译这部分代码时会出现相应的错误。
|
||||
|
||||
不过,所有的这些并不会让我们无从下手。Swift 标准库中定义了一个 `Equatable` 协议,该协议要求任何遵循该协议的类型必须实现等式符(`==`)及不等符(`!=`),从而能对该类型的任意两个值进行比较。所有的 Swift 标准类型自动支持 `Equatable` 协议。
|
||||
|
||||
任何 `Equatable` 类型都可以安全地使用在 `findIndex(_:_:)` 函数中,因为其保证支持等式操作符。为了说明这个事实,当你定义一个函数时,你可以定义一个 `Equatable` 类型约束作为类型参数定义的一部分:
|
||||
|
||||
```swift
|
||||
func findIndex<T: Equatable>(array: [T], _ valueToFind: T) -> Int? {
|
||||
for (index, value) in array.enumerate() {
|
||||
if value == valueToFind {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
`findIndex(_:_:)` 唯一的类型参数写做 `T: Equatable`,也就意味着“任何符合 `Equatable` 协议的类型 `T` ”。
|
||||
|
||||
`findIndex(_:_:)` 函数现在可以成功编译了,并且可以作用于任何符合 `Equatable` 的类型,如 `Double` 或 `String`:
|
||||
|
||||
```swift
|
||||
let doubleIndex = findIndex([3.14159, 0.1, 0.25], 9.3)
|
||||
// doubleIndex 类型为 Int?,其值为 nil,因为 9.3 不在数组中
|
||||
let stringIndex = findIndex(["Mike", "Malcolm", "Andrea"], "Andrea")
|
||||
// stringIndex 类型为 Int?,其值为 2
|
||||
```
|
||||
|
||||
<a name="associated_types"></a>
|
||||
## 关联类型
|
||||
|
||||
定义一个协议时,有的时候声明一个或多个关联类型作为协议定义的一部分将会非常有用。关联类型为协议中的某个类型提供了一个占位名(或者说别名),其代表的实际类型在协议被采纳时才会被指定。你可以通过 `associatedtype` 关键字来指定关联类型。
|
||||
|
||||
<a name="associated_types_in_action"></a>
|
||||
### 关联类型实践
|
||||
|
||||
下面例子定义了一个 `Container` 协议,该协议定义了一个关联类型 `ItemType`:
|
||||
|
||||
```swift
|
||||
protocol Container {
|
||||
associatedtype ItemType
|
||||
mutating func append(item: ItemType)
|
||||
var count: Int { get }
|
||||
subscript(i: Int) -> ItemType { get }
|
||||
}
|
||||
```
|
||||
|
||||
`Container` 协议定义了三个任何采纳了该协议的类型(即容器)必须提供的功能:
|
||||
|
||||
- 必须可以通过 `append(_:)` 方法添加一个新元素到容器里。
|
||||
- 必须可以通过 `count` 属性获取容器中元素的数量,并返回一个 `Int` 值。
|
||||
- 必须可以通过索引值类型为 `Int` 的下标检索到容器中的每一个元素。
|
||||
|
||||
这个协议没有指定容器中元素该如何存储,以及元素必须是何种类型。这个协议只指定了三个任何遵从 `Container` 协议的类型必须提供的功能。遵从协议的类型在满足这三个条件的情况下也可以提供其他额外的功能。
|
||||
|
||||
任何遵从 `Container` 协议的类型必须能够指定其存储的元素的类型,必须保证只有正确类型的元素可以加进容器中,必须明确通过其下标返回的元素的类型。
|
||||
|
||||
为了定义这三个条件,`Container` 协议需要在不知道容器中元素的具体类型的情况下引用这种类型。`Container` 协议需要指定任何通过 `append(_:)` 方法添加到容器中的元素和容器中的元素是相同类型,并且通过容器下标返回的元素的类型也是这种类型。
|
||||
|
||||
为了达到这个目的,`Container` 协议声明了一个关联类型 `ItemType`,写作 `associatedtype ItemType`。这个协议无法定义 `ItemType` 是什么类型的别名,这个信息将留给遵从协议的类型来提供。尽管如此,`ItemType` 别名提供了一种方式来引用 `Container` 中元素的类型,并将之用于 `append(_:)` 方法和下标,从而保证任何 `Container` 的行为都能够正如预期地被执行。
|
||||
|
||||
下面是先前的非泛型的 `IntStack` 类型,这一版本采纳并符合了 `Container` 协议:
|
||||
|
||||
```swift
|
||||
struct IntStack: Container {
|
||||
// IntStack 的原始实现部分
|
||||
var items = [Int]()
|
||||
mutating func push(item: Int) {
|
||||
items.append(item)
|
||||
}
|
||||
mutating func pop() -> Int {
|
||||
return items.removeLast()
|
||||
}
|
||||
// Container 协议的实现部分
|
||||
typealias ItemType = Int
|
||||
mutating func append(item: Int) {
|
||||
self.push(item)
|
||||
}
|
||||
var count: Int {
|
||||
return items.count
|
||||
}
|
||||
subscript(i: Int) -> Int {
|
||||
return items[i]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`IntStack` 结构体实现了 `Container` 协议的三个要求,其原有功能也不会和这些要求相冲突。
|
||||
|
||||
此外,`IntStack` 在实现 `Container` 的要求时,指定 `ItemType` 为 `Int` 类型,即 `typealias ItemType = Int`,从而将 `Container` 协议中抽象的 `ItemType` 类型转换为具体的 `Int` 类型。
|
||||
|
||||
由于 Swift 的类型推断,你实际上不用在 `IntStack` 的定义中声明 `ItemType` 为 `Int`。因为 `IntStack` 符合 `Container` 协议的所有要求,Swift 只需通过 `append(_:)` 方法的 `item` 参数类型和下标返回值的类型,就可以推断出 `ItemType` 的具体类型。事实上,如果你在上面的代码中删除了 `typealias ItemType = Int` 这一行,一切仍旧可以正常工作,因为 Swift 清楚地知道 `ItemType` 应该是哪种类型。
|
||||
|
||||
你也可以让泛型 `Stack` 结构体遵从 `Container` 协议:
|
||||
|
||||
```swift
|
||||
struct Stack<Element>: Container {
|
||||
// Stack<Element> 的原始实现部分
|
||||
var items = [Element]()
|
||||
mutating func push(item: Element) {
|
||||
items.append(item)
|
||||
}
|
||||
mutating func pop() -> Element {
|
||||
return items.removeLast()
|
||||
}
|
||||
// Container 协议的实现部分
|
||||
mutating func append(item: Element) {
|
||||
self.push(item)
|
||||
}
|
||||
var count: Int {
|
||||
return items.count
|
||||
}
|
||||
subscript(i: Int) -> Element {
|
||||
return items[i]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这一次,占位类型参数 `Element` 被用作 `append(_:)` 方法的 `item` 参数和下标的返回类型。Swift 可以据此推断出 `Element` 的类型即是 `ItemType` 的类型。
|
||||
|
||||
<a name="extending_an_existing_type_to_specify_an_associated_type"></a>
|
||||
### 通过扩展一个存在的类型来指定关联类型
|
||||
|
||||
[通过扩展添加协议一致性](./22_Protocols.html#adding_protocol_conformance_with_an_extension)中描述了如何利用扩展让一个已存在的类型符合一个协议,这包括使用了关联类型的协议。
|
||||
|
||||
Swift 的 `Array` 类型已经提供 `append(_:)` 方法,一个 `count` 属性,以及一个接受 `Int` 类型索引值的下标用以检索其元素。这三个功能都符合 `Container` 协议的要求,也就意味着你只需简单地声明 `Array` 采纳该协议就可以扩展 `Array`,使其遵从 `Container` 协议。你可以通过一个空扩展来实现这点,正如[通过扩展采纳协议](./22_Protocols.html#declaring_protocol_adoption_with_an_extension)中的描述:
|
||||
|
||||
```swift
|
||||
extension Array: Container {}
|
||||
```
|
||||
|
||||
如同上面的泛型 `Stack` 结构体一样,`Array` 的 `append(_:)` 方法和下标确保了 Swift 可以推断出 `ItemType` 的类型。定义了这个扩展后,你可以将任意 `Array` 当作 `Container` 来使用。
|
||||
|
||||
<a name="where_clauses"></a>
|
||||
## Where 子句
|
||||
|
||||
[类型约束](#type_constraints)让你能够为泛型函数或泛型类型的类型参数定义一些强制要求。
|
||||
|
||||
为关联类型定义约束也是非常有用的。你可以在参数列表中通过 `where` 子句为关联类型定义约束。你能通过 `where` 子句要求一个关联类型遵从某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。你可以通过将 `where` 关键字紧跟在类型参数列表后面来定义 `where` 子句,`where` 子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。
|
||||
|
||||
下面的例子定义了一个名为 `allItemsMatch` 的泛型函数,用来检查两个 `Container` 实例是否包含相同顺序的相同元素。如果所有的元素能够匹配,那么返回 `true`,否则返回 `false`。
|
||||
|
||||
被检查的两个 `Container` 可以不是相同类型的容器(虽然它们可以相同),但它们必须拥有相同类型的元素。这个要求通过一个类型约束以及一个 `where` 子句来表示:
|
||||
|
||||
```swift
|
||||
func allItemsMatch<
|
||||
C1: Container, C2: Container
|
||||
where C1.ItemType == C2.ItemType, C1.ItemType: Equatable>
|
||||
(someContainer: C1, _ anotherContainer: C2) -> Bool {
|
||||
|
||||
// 检查两个容器含有相同数量的元素
|
||||
if someContainer.count != anotherContainer.count {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查每一对元素是否相等
|
||||
for i in 0..<someContainer.count {
|
||||
if someContainer[i] != anotherContainer[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 所有元素都匹配,返回 true
|
||||
return true
|
||||
}
|
||||
```
|
||||
|
||||
这个函数接受 `someContainer` 和 `anotherContainer` 两个参数。参数 `someContainer` 的类型为 `C1`,参数 `anotherContainer` 的类型为 `C2`。`C1` 和 `C2` 是容器的两个占位类型参数,函数被调用时才能确定它们的具体类型。
|
||||
|
||||
这个函数的类型参数列表还定义了对两个类型参数的要求:
|
||||
|
||||
- `C1` 必须符合 `Container` 协议(写作 `C1: Container`)。
|
||||
- `C2` 必须符合 `Container` 协议(写作 `C2: Container`)。
|
||||
- `C1` 的 `ItemType` 必须和 `C2` 的 `ItemType`类型相同(写作 `C1.ItemType == C2.ItemType`)。
|
||||
- `C1` 的 `ItemType` 必须符合 `Equatable` 协议(写作 `C1.ItemType: Equatable`)。
|
||||
|
||||
第三个和第四个要求被定义为一个 `where` 子句,写在关键字 `where` 后面,它们也是泛型函数类型参数列表的一部分。
|
||||
|
||||
这些要求意味着:
|
||||
|
||||
- `someContainer` 是一个 `C1` 类型的容器。
|
||||
- `anotherContainer` 是一个 `C2` 类型的容器。
|
||||
- `someContainer` 和 `anotherContainer` 包含相同类型的元素。
|
||||
- `someContainer` 中的元素可以通过不等于操作符(`!=`)来检查它们是否彼此不同。
|
||||
|
||||
第三个和第四个要求结合起来意味着 `anotherContainer` 中的元素也可以通过 `!=` 操作符来比较,因为它们和 `someContainer` 中的元素类型相同。
|
||||
|
||||
这些要求让 `allItemsMatch(_:_:)` 函数能够比较两个容器,即使它们的容器类型不同。
|
||||
|
||||
`allItemsMatch(_:_:)` 函数首先检查两个容器是否拥有相同数量的元素,如果它们的元素数量不同,那么一定不匹配,函数就会返回 `false`。
|
||||
|
||||
进行这项检查之后,通过 `for-in` 循环和半闭区间操作符(`..<`)来迭代每个元素,检查 `someContainer` 中的元素是否不等于 `anotherContainer` 中的对应元素。如果两个元素不相等,那么两个容器不匹配,函数返回 `false`。
|
||||
|
||||
如果循环体结束后未发现任何不匹配的情况,表明两个容器匹配,函数返回 `true`。
|
||||
|
||||
下面演示了 `allItemsMatch(_:_:)` 函数的使用:
|
||||
|
||||
```swift
|
||||
var stackOfStrings = Stack<String>()
|
||||
stackOfStrings.push("uno")
|
||||
stackOfStrings.push("dos")
|
||||
stackOfStrings.push("tres")
|
||||
|
||||
var arrayOfStrings = ["uno", "dos", "tres"]
|
||||
|
||||
if allItemsMatch(stackOfStrings, arrayOfStrings) {
|
||||
print("All items match.")
|
||||
} else {
|
||||
print("Not all items match.")
|
||||
}
|
||||
// 打印 “All items match.”
|
||||
```
|
||||
|
||||
上面的例子创建了一个 `Stack` 实例来存储一些 `String` 值,然后将三个字符串压入栈中。这个例子还通过数组字面量创建了一个 `Array` 实例,数组中包含同栈中一样的三个字符串。即使栈和数组是不同的类型,但它们都遵从 `Container` 协议,而且它们都包含相同类型的值。因此你可以用这两个容器作为参数来调用 `allItemsMatch(_:_:)` 函数。在上面的例子中,`allItemsMatch(_:_:)` 函数正确地显示了这两个容器中的所有元素都是相互匹配的。
|
||||
@ -1,387 +0,0 @@
|
||||
# 访问控制(Access Control)
|
||||
------------------
|
||||
|
||||
> 1.0
|
||||
> 翻译:[JaceFu](http://www.devtalking.com/)
|
||||
> 校对:[ChildhoodAndy](http://childhood.logdown.com)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[mmoaay](https://github.com/mmoaay)
|
||||
|
||||
> 2.1
|
||||
> 翻译:[Prayer](https://github.com/futantan)
|
||||
> 校对:[shanks](http://codebuild.me),2015-11-01
|
||||
|
||||
本页内容包括:
|
||||
|
||||
- [模块和源文件](#modules_and_source_files)
|
||||
- [访问级别](#access_levels)
|
||||
- [访问级别基本原则](#guiding_principle_of_access_levels)
|
||||
- [默认访问级别](#default_access_levels)
|
||||
- [单 target 应用程序的访问级别](#access_levels_for_single-target_apps)
|
||||
- [框架的访问级别](#access_levels_for_frameworks)
|
||||
- [单元测试 target 的访问级别](#access_levels_for_unit_test_targets)
|
||||
- [访问控制语法](#access_control_syntax)
|
||||
- [自定义类型](#custom_types)
|
||||
- [元组类型](#tuple_types)
|
||||
- [函数类型](#function_types)
|
||||
- [枚举类型](#enumeration_types)
|
||||
- [嵌套类型](#nested_types)
|
||||
- [子类](#subclassing)
|
||||
- [常量、变量、属性、下标](#constants_variables_properties_subscripts)
|
||||
- [Getter和Setter](#getters_and_setters)
|
||||
- [构造器](#initializers)
|
||||
- [默认构造器](#default_initializers)
|
||||
- [结构体默认的成员逐一构造器](#default_memberwise_initializers_for_structure_types)
|
||||
- [协议](#protocols)
|
||||
- [协议继承](#protocol_inheritance)
|
||||
- [协议一致性](#protocol_conformance)
|
||||
- [扩展](#extensions)
|
||||
- [通过扩展添加协议一致性](#adding_protocol_conformance_with_an_extension)
|
||||
- [泛型](#generics)
|
||||
- [类型别名](#type_aliases)
|
||||
|
||||
访问控制可以限定其他源文件或模块中的代码对你的代码的访问级别。这个特性可以让我们隐藏代码的一些实现细节,并且可以为其他人可以访问和使用的代码提供接口。
|
||||
|
||||
你可以明确地给单个类型(类、结构体、枚举)设置访问级别,也可以给这些类型的属性、方法、构造器、下标等设置访问级别。协议也可以被限定在一定的范围内使用,包括协议里的全局常量、变量和函数。
|
||||
|
||||
Swift 不仅提供了多种不同的访问级别,还为某些典型场景提供了默认的访问级别,这样就不需要我们在每段代码中都申明显式访问级别。其实,如果只是开发一个单一 target 的应用程序,我们完全可以不用显式申明代码的访问级别。
|
||||
|
||||
> 注意
|
||||
为了简单起见,对于代码中可以设置访问级别的特性(属性、基本类型、函数等),在下面的章节中我们会称之为“实体”。
|
||||
|
||||
<a name="modules_and_source_files"></a>
|
||||
## 模块和源文件
|
||||
Swift 中的访问控制模型基于模块和源文件这两个概念。
|
||||
|
||||
模块指的是独立的代码单元,框架或应用程序会作为一个独立的模块来构建和发布。在 Swift 中,一个模块可以使用 `import` 关键字导入另外一个模块。
|
||||
|
||||
在 Swift 中,Xcode 的每个 target(例如框架或应用程序)都被当作独立的模块处理。如果你是为了实现某个通用的功能,或者是为了封装一些常用方法而将代码打包成独立的框架,这个框架就是 Swift 中的一个模块。当它被导入到某个应用程序或者其他框架时,框架内容都将属于这个独立的模块。
|
||||
|
||||
源文件就是 Swift 中的源代码文件,它通常属于一个模块,即一个应用程序或者框架。尽管我们一般会将不同的类型分别定义在不同的源文件中,但是同一个源文件也可以包含多个类型、函数之类的定义。
|
||||
|
||||
<a name="access_levels"></a>
|
||||
## 访问级别
|
||||
Swift 为代码中的实体提供了三种不同的访问级别。这些访问级别不仅与源文件中定义的实体相关,同时也与源文件所属的模块相关。
|
||||
|
||||
- `public`:可以访问同一模块源文件中的任何实体,在模块外也可以通过导入该模块来访问源文件里的所有实体。通常情况下,框架中的某个接口可以被任何人使用时,你可以将其设置为 `public` 级别。
|
||||
- `internal`:可以访问同一模块源文件中的任何实体,但是不能从模块外访问该模块源文件中的实体。通常情况下,某个接口只在应用程序或框架内部使用时,你可以将其设置为 `internal` 级别。
|
||||
- `private`:限制实体只能在所在的源文件内部使用。使用 `private` 级别可以隐藏某些功能的实现细节。
|
||||
|
||||
`public` 为最高(限制最少)访问级别,`private` 为最低(限制最多)访问级别。
|
||||
|
||||
> 注意
|
||||
Swift 中的 `private` 访问级别不同于其他语言,它的范围限于源文件,而不是声明范围内。这就意味着,一个类型可以访问其所在源文件中的所有 `private` 实体,但是如果它的扩展定义在其他源文件中,那么它的扩展就不能访问它在这个源文件中定义的 `private` 实体。
|
||||
|
||||
<a name="guiding_principle_of_access_levels"></a>
|
||||
### 访问级别基本原则
|
||||
|
||||
Swift 中的访问级别遵循一个基本原则:不可以在某个实体中定义访问级别更高的实体。
|
||||
|
||||
例如:
|
||||
|
||||
- 一个 `public` 访问级别的变量,其类型的访问级别不能是 `internal` 或 `private`。因为无法保证变量的类型在使用变量的地方也具有访问权限。
|
||||
- 函数的访问级别不能高于它的参数类型和返回类型的访问级别。因为如果函数定义为 `public` 而参数类型或者返回类型定义为 `internal` 或 `private`,就会出现函数可以在任何地方被访问,但是它的参数类型和返回类型却不可以。
|
||||
|
||||
<a name="default_access_levels"></a>
|
||||
### 默认访问级别
|
||||
|
||||
如果你不为代码中的实体显式指定访问级别,那么它们默认为 `internal` 级别(有一些例外情况,稍后会进行说明)。因此,在大多数情况下,我们不需要显式指定实体的访问级别。
|
||||
|
||||
<a name="access_levels_for_single-target_apps"></a>
|
||||
### 单 target 应用程序的访问级别
|
||||
|
||||
当你编写一个单 target 应用程序时,应用的所有功能都是为该应用服务,而不需要提供给其他应用或者模块使用,所以我们不需要明确设置访问级别,使用默认的访问级别 `internal` 即可。但是,你也可以使用 `private` 级别,用于隐藏一些功能的实现细节。
|
||||
|
||||
<a name="access_levels_for_frameworks"></a>
|
||||
### 框架的访问级别
|
||||
|
||||
当你开发框架时,就需要把一些对外的接口定义为 `public` 级别,以便使用者导入该框架后可以正常使用其功能。这些被你定义为 `public` 的接口,就是这个框架的 API。
|
||||
|
||||
> 注意
|
||||
框架依然会使用默认的 `internal` 级别,也可以指定为 `private` 级别。当你想把某个实体作为框架的 API 的时候,需显式为其指定 `public` 级别。
|
||||
|
||||
<a name="access_levels_for_unit_test_targets"></a>
|
||||
### 单元测试 target 的访问级别
|
||||
|
||||
当你的应用程序包含单元测试 target 时,为了测试,测试模块需要访问应用程序模块中的代码。默认情况下只有 `public` 级别的实体才可以被其他模块访问。然而,如果在导入应用程序模块的语句前使用 `@testable` 特性,然后在允许测试的编译设置(`Build Options -> Enable Testability`)下编译这个应用程序模块,单元测试 target 就可以访问应用程序模块中所有 `internal` 级别的实体。
|
||||
|
||||
<a name="access_control_syntax"></a>
|
||||
## 访问控制语法
|
||||
|
||||
通过修饰符 `public`、`internal`、`private` 来声明实体的访问级别:
|
||||
|
||||
```swift
|
||||
public class SomePublicClass {}
|
||||
internal class SomeInternalClass {}
|
||||
private class SomePrivateClass {}
|
||||
|
||||
public var somePublicVariable = 0
|
||||
internal let someInternalConstant = 0
|
||||
private func somePrivateFunction() {}
|
||||
```
|
||||
|
||||
除非专门指定,否则实体默认的访问级别为 `internal`,可以查阅[默认访问级别](#default_access_levels)这一节。这意味着在不使用修饰符显式声明访问级别的情况下,`SomeInternalClass` 和 `someInternalConstant` 仍然拥有隐式的访问级别 `internal`:
|
||||
|
||||
```swift
|
||||
class SomeInternalClass {} // 隐式访问级别 internal
|
||||
var someInternalConstant = 0 // 隐式访问级别 internal
|
||||
```
|
||||
<a name="custom_types"></a>
|
||||
## 自定义类型
|
||||
|
||||
如果想为一个自定义类型指定访问级别,在定义类型时进行指定即可。新类型只能在它的访问级别限制范围内使用。例如,你定义了一个 `private` 级别的类,那这个类就只能在定义它的源文件中使用,可以作为属性类型、函数参数类型或者返回类型,等等。
|
||||
|
||||
一个类型的访问级别也会影响到类型成员(属性、方法、构造器、下标)的默认访问级别。如果你将类型指定为 `private` 级别,那么该类型的所有成员的默认访问级别也会变成 `private`。如果你将类型指定为 `public` 或者 `internal` 级别(或者不明确指定访问级别,而使用默认的 `internal` 访问级别),那么该类型的所有成员的默认访问级别将是 `internal`。
|
||||
|
||||
> 注意
|
||||
上面提到,一个 `public` 类型的所有成员的访问级别默认为 `internal` 级别,而不是 `public` 级别。如果你想将某个成员指定为 `public` 级别,那么你必须显式指定。这样做的好处是,在你定义公共接口的时候,可以明确地选择哪些接口是需要公开的,哪些是内部使用的,避免不小心将内部使用的接口公开。
|
||||
|
||||
```swift
|
||||
public class SomePublicClass { // 显式的 public 类
|
||||
public var somePublicProperty = 0 // 显式的 public 类成员
|
||||
var someInternalProperty = 0 // 隐式的 internal 类成员
|
||||
private func somePrivateMethod() {} // 显式的 private 类成员
|
||||
}
|
||||
|
||||
class SomeInternalClass { // 隐式的 internal 类
|
||||
var someInternalProperty = 0 // 隐式的 internal 类成员
|
||||
private func somePrivateMethod() {} // 显式的 private 类成员
|
||||
}
|
||||
|
||||
private class SomePrivateClass { // 显式的 private 类
|
||||
var somePrivateProperty = 0 // 隐式的 private 类成员
|
||||
func somePrivateMethod() {} // 隐式的 private 类成员
|
||||
}
|
||||
```
|
||||
<a name="tuple_types"></a>
|
||||
### 元组类型
|
||||
|
||||
元组的访问级别将由元组中访问级别最严格的类型来决定。例如,如果你构建了一个包含两种不同类型的元组,其中一个类型为 `internal` 级别,另一个类型为 `private` 级别,那么这个元组的访问级别为 `private`。
|
||||
|
||||
> 注意
|
||||
元组不同于类、结构体、枚举、函数那样有单独的定义。元组的访问级别是在它被使用时自动推断出的,而无法明确指定。
|
||||
|
||||
<a name="function_types"></a>
|
||||
### 函数类型
|
||||
|
||||
函数的访问级别根据访问级别最严格的参数类型或返回类型的访问级别来决定。但是,如果这种访问级别不符合函数定义所在环境的默认访问级别,那么就需要明确地指定该函数的访问级别。
|
||||
|
||||
下面的例子定义了一个名为 `someFunction` 的全局函数,并且没有明确地指定其访问级别。也许你会认为该函数应该拥有默认的访问级别 `internal`,但事实并非如此。事实上,如果按下面这种写法,代码将无法通过编译:
|
||||
|
||||
```swift
|
||||
func someFunction() -> (SomeInternalClass, SomePrivateClass) {
|
||||
// 此处是函数实现部分
|
||||
}
|
||||
```
|
||||
|
||||
我们可以看到,这个函数的返回类型是一个元组,该元组中包含两个自定义的类(可查阅[自定义类型](#custom_types))。其中一个类的访问级别是 `internal`,另一个的访问级别是 `private`,所以根据元组访问级别的原则,该元组的访问级别是 `private`(元组的访问级别与元组中访问级别最低的类型一致)。
|
||||
|
||||
因为该函数返回类型的访问级别是 `private`,所以你必须使用 `private` 修饰符,明确指定该函数的访问级别:
|
||||
|
||||
```swift
|
||||
private func someFunction() -> (SomeInternalClass, SomePrivateClass) {
|
||||
// 此处是函数实现部分
|
||||
}
|
||||
```
|
||||
|
||||
将该函数指定为 `public` 或 `internal`,或者使用默认的访问级别 `internal` 都是错误的,因为如果把该函数当做 `public` 或 `internal` 级别来使用的话,可能会无法访问 `private` 级别的返回值。
|
||||
|
||||
<a name="enumeration_types"></a>
|
||||
### 枚举类型
|
||||
|
||||
枚举成员的访问级别和该枚举类型相同,你不能为枚举成员单独指定不同的访问级别。
|
||||
|
||||
比如下面的例子,枚举 `CompassPoint` 被明确指定为 `public` 级别,那么它的成员 `North`、`South`、`East`、`West` 的访问级别同样也是 `public`:
|
||||
|
||||
```swift
|
||||
public enum CompassPoint {
|
||||
case North
|
||||
case South
|
||||
case East
|
||||
case West
|
||||
}
|
||||
```
|
||||
|
||||
<a name="raw_values_and_associated_values"></a>
|
||||
#### 原始值和关联值
|
||||
|
||||
枚举定义中的任何原始值或关联值的类型的访问级别至少不能低于枚举类型的访问级别。例如,你不能在一个 `internal` 访问级别的枚举中定义 `private` 级别的原始值类型。
|
||||
|
||||
<a name="nested_types"></a>
|
||||
### 嵌套类型
|
||||
|
||||
如果在 `private` 级别的类型中定义嵌套类型,那么该嵌套类型就自动拥有 `private` 访问级别。如果在 `public` 或者 `internal` 级别的类型中定义嵌套类型,那么该嵌套类型自动拥有 `internal` 访问级别。如果想让嵌套类型拥有 `public` 访问级别,那么需要明确指定该嵌套类型的访问级别。
|
||||
|
||||
<a name="subclassing"></a>
|
||||
## 子类
|
||||
|
||||
子类的访问级别不得高于父类的访问级别。例如,父类的访问级别是 `internal`,子类的访问级别就不能是 `public`。
|
||||
|
||||
此外,你可以在符合当前访问级别的条件下重写任意类成员(方法、属性、构造器、下标等)。
|
||||
|
||||
可以通过重写为继承来的类成员提供更高的访问级别。下面的例子中,类 `A` 的访问级别是 `public`,它包含一个方法 `someMethod()`,访问级别为 `private`。类 `B` 继承自类 `A`,访问级别为 `internal`,但是在类 `B` 中重写了类 `A` 中访问级别为 `private` 的方法 `someMethod()`,并重新指定为 `internal` 级别。通过这种方式,我们就可以将某类中 `private` 级别的类成员重新指定为更高的访问级别,以便其他人使用:
|
||||
|
||||
```swift
|
||||
public class A {
|
||||
private func someMethod() {}
|
||||
}
|
||||
|
||||
internal class B: A {
|
||||
override internal func someMethod() {}
|
||||
}
|
||||
```
|
||||
|
||||
我们甚至可以在子类中,用子类成员去访问访问级别更低的父类成员,只要这一操作在相应访问级别的限制范围内(也就是说,在同一源文件中访问父类 `private` 级别的成员,在同一模块内访问父类 `internal` 级别的成员):
|
||||
|
||||
```swift
|
||||
public class A {
|
||||
private func someMethod() {}
|
||||
}
|
||||
|
||||
internal class B: A {
|
||||
override internal func someMethod() {
|
||||
super.someMethod()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
因为父类 `A` 和子类 `B` 定义在同一个源文件中,所以在子类 `B` 可以在重写的 `someMethod()` 方法中调用 `super.someMethod()`。
|
||||
|
||||
<a name="constants_variables_properties_subscripts"></a>
|
||||
## 常量、变量、属性、下标
|
||||
|
||||
常量、变量、属性不能拥有比它们的类型更高的访问级别。例如,你不能定义一个 `public` 级别的属性,但是它的类型却是 `private` 级别的。同样,下标也不能拥有比索引类型或返回类型更高的访问级别。
|
||||
|
||||
如果常量、变量、属性、下标的类型是 `private` 级别的,那么它们必须明确指定访问级别为 `private`:
|
||||
|
||||
```swift
|
||||
private var privateInstance = SomePrivateClass()
|
||||
```
|
||||
|
||||
<a name="getters_and_setters"></a>
|
||||
### Getter 和 Setter
|
||||
|
||||
常量、变量、属性、下标的 `Getters` 和 `Setters` 的访问级别和它们所属类型的访问级别相同。
|
||||
|
||||
`Setter` 的访问级别可以低于对应的 `Getter` 的访问级别,这样就可以控制变量、属性或下标的读写权限。在 `var` 或 `subscript` 关键字之前,你可以通过 `private(set)` 或 `internal(set)` 为它们的写入权限指定更低的访问级别。
|
||||
|
||||
> 注意
|
||||
这个规则同时适用于存储型属性和计算型属性。即使你不明确指定存储型属性的 `Getter` 和 `Setter`,Swift 也会隐式地为其创建 `Getter` 和 `Setter`,用于访问该属性的后备存储。使用 `private(set)` 和 `internal(set)` 可以改变 `Setter` 的访问级别,这对计算型属性也同样适用。
|
||||
|
||||
下面的例子中定义了一个名为 `TrackedString` 的结构体,它记录了 `value` 属性被修改的次数:
|
||||
|
||||
```swift
|
||||
struct TrackedString {
|
||||
private(set) var numberOfEdits = 0
|
||||
var value: String = "" {
|
||||
didSet {
|
||||
numberOfEdits++
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`TrackedString` 结构体定义了一个用于存储 `String` 值的属性 `value`,并将初始值设为 `""`(一个空字符串)。该结构体还定义了另一个用于存储 `Int` 值的属性 `numberOfEdits`,它用于记录属性 `value` 被修改的次数。这个功能通过属性 `value` 的 `didSet` 观察器实现,每当给 `value` 赋新值时就会调用 `didSet` 方法,然后将 `numberOfEdits` 的值加一。
|
||||
|
||||
结构体 `TrackedString` 和它的属性 `value` 均没有显式指定访问级别,所以它们都拥有默认的访问级别 `internal`。但是该结构体的 `numberOfEdits` 属性使用了 `private(set)` 修饰符,这意味着 `numberOfEdits` 属性只能在定义该结构体的源文件中赋值。`numberOfEdits` 属性的 `Getter` 依然是默认的访问级别 `internal`,但是 `Setter` 的访问级别是 `private`,这表示该属性只有在当前的源文件中是可读写的,而在当前源文件所属的模块中只是一个可读的属性。
|
||||
|
||||
如果你实例化 `TrackedString` 结构体,并多次对 `value` 属性的值进行修改,你就会看到 `numberOfEdits` 的值会随着修改次数而变化:
|
||||
|
||||
```swift
|
||||
var stringToEdit = TrackedString()
|
||||
stringToEdit.value = "This string will be tracked."
|
||||
stringToEdit.value += " This edit will increment numberOfEdits."
|
||||
stringToEdit.value += " So will this one."
|
||||
print("The number of edits is \(stringToEdit.numberOfEdits)")
|
||||
// 打印 “The number of edits is 3”
|
||||
```
|
||||
|
||||
虽然你可以在其他的源文件中实例化该结构体并且获取到 `numberOfEdits` 属性的值,但是你不能对其进行赋值。这一限制保护了该记录功能的实现细节,同时还提供了方便的访问方式。
|
||||
|
||||
你可以在必要时为 `Getter` 和 `Setter` 显式指定访问级别。下面的例子将 `TrackedString` 结构体明确指定为了 `public` 访问级别。结构体的成员(包括 `numberOfEdits` 属性)拥有默认的访问级别 `internal`。你可以结合 `public` 和 `private(set)` 修饰符把结构体中的 `numberOfEdits` 属性的 `Getter` 的访问级别设置为 `public`,而 `Setter` 的访问级别设置为 `private`:
|
||||
|
||||
```swift
|
||||
public struct TrackedString {
|
||||
public private(set) var numberOfEdits = 0
|
||||
public var value: String = "" {
|
||||
didSet {
|
||||
numberOfEdits++
|
||||
}
|
||||
}
|
||||
public init() {}
|
||||
}
|
||||
```
|
||||
|
||||
<a name="initializers"></a>
|
||||
## 构造器
|
||||
|
||||
自定义构造器的访问级别可以低于或等于其所属类型的访问级别。唯一的例外是[必要构造器](./14_Initialization.html#required_initializers),它的访问级别必须和所属类型的访问级别相同。
|
||||
|
||||
如同函数或方法的参数,构造器参数的访问级别也不能低于构造器本身的访问级别。
|
||||
|
||||
<a name="default_initializers"></a>
|
||||
### 默认构造器
|
||||
|
||||
如[默认构造器](./14_Initialization.html#default_initializers)所述,Swift 会为结构体和类提供一个默认的无参数的构造器,只要它们为所有存储型属性设置了默认初始值,并且未提供自定义的构造器。
|
||||
|
||||
默认构造器的访问级别与所属类型的访问级别相同,除非类型的访问级别是 `public`。如果一个类型被指定为 `public` 级别,那么默认构造器的访问级别将为 `internal`。如果你希望一个 `public` 级别的类型也能在其他模块中使用这种无参数的默认构造器,你只能自己提供一个 `public` 访问级别的无参数构造器。
|
||||
|
||||
<a name="default_memberwise_initializers_for_structure_types"></a>
|
||||
### 结构体默认的成员逐一构造器
|
||||
|
||||
如果结构体中任意存储型属性的访问级别为 `private`,那么该结构体默认的成员逐一构造器的访问级别就是 `private`。否则,这种构造器的访问级别依然是 `internal`。
|
||||
|
||||
如同前面提到的默认构造器,如果你希望一个 `public` 级别的结构体也能在其他模块中使用其默认的成员逐一构造器,你依然只能自己提供一个 `public` 访问级别的成员逐一构造器。
|
||||
|
||||
<a name="protocols"></a>
|
||||
## 协议
|
||||
|
||||
如果想为一个协议类型明确地指定访问级别,在定义协议时指定即可。这将限制该协议只能在适当的访问级别范围内被采纳。
|
||||
|
||||
协议中的每一个要求都具有和该协议相同的访问级别。你不能将协议中的要求设置为其他访问级别。这样才能确保该协议的所有要求对于任意采纳者都将可用。
|
||||
|
||||
> 注意
|
||||
如果你定义了一个 `public` 访问级别的协议,那么该协议的所有实现也会是 `public` 访问级别。这一点不同于其他类型,例如,当类型是 `public` 访问级别时,其成员的访问级别却只是 `internal`。
|
||||
|
||||
<a name="protocol_inheritance"></a>
|
||||
### 协议继承
|
||||
|
||||
如果定义了一个继承自其他协议的新协议,那么新协议拥有的访问级别最高也只能和被继承协议的访问级别相同。例如,你不能将继承自 `internal` 协议的新协议定义为 `public` 协议。
|
||||
|
||||
<a name="protocol_conformance"></a>
|
||||
### 协议一致性
|
||||
|
||||
一个类型可以采纳比自身访问级别低的协议。例如,你可以定义一个 `public` 级别的类型,它可以在其他模块中使用,同时它也可以采纳一个 `internal` 级别的协议,但是只能在该协议所在的模块中作为符合该协议的类型使用。
|
||||
|
||||
采纳了协议的类型的访问级别取它本身和所采纳协议两者间最低的访问级别。也就是说如果一个类型是 `public` 级别,采纳的协议是 `internal` 级别,那么采纳了这个协议后,该类型作为符合协议的类型时,其访问级别也是 `internal`。
|
||||
|
||||
如果你采纳了协议,那么实现了协议的所有要求后,你必须确保这些实现的访问级别不能低于协议的访问级别。例如,一个 `public` 级别的类型,采纳了 `internal` 级别的协议,那么协议的实现至少也得是 `internal` 级别。
|
||||
|
||||
> 注意
|
||||
Swift 和 Objective-C 一样,协议的一致性是全局的,也就是说,在同一程序中,一个类型不可能用两种不同的方式实现同一个协议。
|
||||
|
||||
<a name="extensions"></a>
|
||||
## 扩展
|
||||
|
||||
你可以在访问级别允许的情况下对类、结构体、枚举进行扩展。扩展成员具有和原始类型成员一致的访问级别。例如,你扩展了一个 `public` 或者 `internal` 类型,扩展中的成员具有默认的 `internal` 访问级别,和原始类型中的成员一致 。如果你扩展了一个 `private` 类型,扩展成员则拥有默认的 `private` 访问级别。
|
||||
|
||||
或者,你可以明确指定扩展的访问级别(例如,`private extension`),从而给该扩展中的所有成员指定一个新的默认访问级别。这个新的默认访问级别仍然可以被单独指定的访问级别所覆盖。
|
||||
|
||||
<a name="adding_protocol_conformance_with_an_extension"></a>
|
||||
### 通过扩展添加协议一致性
|
||||
|
||||
如果你通过扩展来采纳协议,那么你就不能显式指定该扩展的访问级别了。协议拥有相应的访问级别,并会为该扩展中所有协议要求的实现提供默认的访问级别。
|
||||
|
||||
<a name="generics"></a>
|
||||
## 泛型
|
||||
|
||||
泛型类型或泛型函数的访问级别取决于泛型类型或泛型函数本身的访问级别,还需结合类型参数的类型约束的访问级别,根据这些访问级别中的最低访问级别来确定。
|
||||
|
||||
<a name="type_aliases"></a>
|
||||
## 类型别名
|
||||
|
||||
你定义的任何类型别名都会被当作不同的类型,以便于进行访问控制。类型别名的访问级别不可高于其表示的类型的访问级别。例如,`private` 级别的类型别名可以作为 `public`、`internal`、`private` 类型的别名,但是 `public` 级别的类型别名只能作为 `public` 类型的别名,不能作为 `internal` 或 `private` 类型的别名。
|
||||
|
||||
> 注意
|
||||
这条规则也适用于为满足协议一致性而将类型别名用于关联类型的情况。
|
||||
@ -1,490 +0,0 @@
|
||||
# 高级运算符(Advanced Operators)
|
||||
-----------------
|
||||
|
||||
> 1.0
|
||||
> 翻译:[xielingwang](https://github.com/xielingwang)
|
||||
> 校对:[numbbbbb](https://github.com/numbbbbb)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[buginux](https://github.com/buginux)
|
||||
|
||||
> 2.1
|
||||
> 校对:[shanks](http://codebuild.me),2015-11-01
|
||||
|
||||
本页内容包括:
|
||||
|
||||
- [位运算符](#bitwise_operators)
|
||||
- [溢出运算符](#overflow_operators)
|
||||
- [优先级和结合性](#precedence_and_associativity)
|
||||
- [运算符函数](#operator_functions)
|
||||
- [自定义运算符](#custom_operators)
|
||||
|
||||
除了在之前介绍过的[基本运算符](./02_Basic_Operators.html),Swift 中还有许多可以对数值进行复杂运算的高级运算符。这些高级运算符包含了在 C 和 Objective-C 中已经被大家所熟知的位运算符和移位运算符。
|
||||
|
||||
与 C 语言中的算术运算符不同,Swift 中的算术运算符默认是不会溢出的。所有溢出行为都会被捕获并报告为错误。如果想让系统允许溢出行为,可以选择使用 Swift 中另一套默认支持溢出的运算符,比如溢出加法运算符(`&+`)。所有的这些溢出运算符都是以 `&` 开头的。
|
||||
|
||||
自定义结构体、类和枚举时,如果也为它们提供标准 Swift 运算符的实现,将会非常有用。在 Swift 中自定义运算符非常简单,运算符也会针对不同类型使用对应实现。
|
||||
|
||||
我们不用被预定义的运算符所限制。在 Swift 中可以自由地定义中缀、前缀、后缀和赋值运算符,以及相应的优先级与结合性。这些运算符在代码中可以像预定义的运算符一样使用,我们甚至可以扩展已有的类型以支持自定义的运算符。
|
||||
|
||||
<a name="bitwise_operators"></a>
|
||||
## 位运算符
|
||||
|
||||
位运算符可以操作数据结构中每个独立的比特位。它们通常被用在底层开发中,比如图形编程和创建设备驱动。位运算符在处理外部资源的原始数据时也十分有用,比如对自定义通信协议传输的数据进行编码和解码。
|
||||
|
||||
Swift 支持 C 语言中的全部位运算符,接下来会一一介绍。
|
||||
|
||||
<a name="bitwise_not_operator"></a>
|
||||
### 按位取反运算符
|
||||
|
||||
按位取反运算符(`~`)可以对一个数值的全部比特位进行取反:
|
||||
|
||||
|
||||

|
||||
|
||||
按位取反运算符是一个前缀运算符,需要直接放在运算的数之前,并且它们之间不能添加任何空格:
|
||||
|
||||
```swift
|
||||
let initialBits: UInt8 = 0b00001111
|
||||
let invertedBits = ~initialBits // 等于 0b11110000
|
||||
```
|
||||
|
||||
`UInt8` 类型的整数有 8 个比特位,可以存储 `0 ~ 255` 之间的任意整数。这个例子初始化了一个 `UInt8` 类型的整数,并赋值为二进制的 `00001111`,它的前 4 位都为 `0`,后 4 位都为 `1`。这个值等价于十进制的 `15`。
|
||||
|
||||
接着使用按位取反运算符创建了一个名为 `invertedBits` 的常量,这个常量的值与全部位取反后的 `initialBits` 相等。即所有的 `0` 都变成了 `1`,同时所有的 `1` 都变成 `0`。`invertedBits` 的二进制值为 `11110000`,等价于无符号十进制数的 `240`。
|
||||
|
||||
<a name="bitwise_and_operator"></a>
|
||||
### 按位与运算符
|
||||
|
||||
按位与运算符(`&`)可以对两个数的比特位进行合并。它返回一个新的数,只有当两个数的对应位都为 `1` 的时候,新数的对应位才为 `1`:
|
||||
|
||||

|
||||
|
||||
在下面的示例当中,`firstSixBits` 和 `lastSixBits` 中间 4 个位的值都为 `1`。按位与运算符对它们进行了运算,得到二进制数值 `00111100`,等价于无符号十进制数的 `60`:
|
||||
|
||||
```swift
|
||||
let firstSixBits: UInt8 = 0b11111100
|
||||
let lastSixBits: UInt8 = 0b00111111
|
||||
let middleFourBits = firstSixBits & lastSixBits // 等于 00111100
|
||||
```
|
||||
|
||||
<a name="bitwise_or_operator"></a>
|
||||
### 按位或运算符
|
||||
|
||||
按位或运算符(`|`)可以对两个数的比特位进行比较。它返回一个新的数,只要两个数的对应位中有任意一个为 `1` 时,新数的对应位就为 `1`:
|
||||
|
||||

|
||||
|
||||
在下面的示例中,`someBits` 和 `moreBits` 不同的位会被设置为 `1`。接位或运算符对它们进行了运算,得到二进制数值 `11111110`,等价于无符号十进制数的 `254`:
|
||||
|
||||
```swift
|
||||
let someBits: UInt8 = 0b10110010
|
||||
let moreBits: UInt8 = 0b01011110
|
||||
let combinedbits = someBits | moreBits // 等于 11111110
|
||||
```
|
||||
|
||||
<a name="bitwise_xor_operator"></a>
|
||||
### 按位异或运算符
|
||||
|
||||
按位异或运算符(`^`)可以对两个数的比特位进行比较。它返回一个新的数,当两个数的对应位不相同时,新数的对应位就为 `1`:
|
||||
|
||||

|
||||
|
||||
在下面的示例当中,`firstBits` 和 `otherBits` 都有一个自己的位为 `1` 而对方的对应位为 `0` 的位。 按位异或运算符将新数的这两个位都设置为 `1`,同时将其它位都设置为 `0`:
|
||||
|
||||
```swift
|
||||
let firstBits: UInt8 = 0b00010100
|
||||
let otherBits: UInt8 = 0b00000101
|
||||
let outputBits = firstBits ^ otherBits // 等于 00010001
|
||||
```
|
||||
|
||||
<a name="bitwise_left_and_right_shift_operators"></a>
|
||||
### 按位左移、右移运算符
|
||||
|
||||
按位左移运算符(`<<`)和按位右移运算符(`>>`)可以对一个数的所有位进行指定位数的左移和右移,但是需要遵守下面定义的规则。
|
||||
|
||||
对一个数进行按位左移或按位右移,相当于对这个数进行乘以 2 或除以 2 的运算。将一个整数左移一位,等价于将这个数乘以 2,同样地,将一个整数右移一位,等价于将这个数除以 2。
|
||||
|
||||
<a name="shifting_behavior_for_unsigned_integers"></a>
|
||||
#### 无符号整数的移位运算
|
||||
|
||||
对无符号整数进行移位的规则如下:
|
||||
|
||||
1. 已经存在的位按指定的位数进行左移和右移。
|
||||
2. 任何因移动而超出整型存储范围的位都会被丢弃。
|
||||
3. 用 `0` 来填充移位后产生的空白位。
|
||||
|
||||
这种方法称为逻辑移位。
|
||||
|
||||
以下这张图展示了 `11111111 << 1`(即把 `11111111` 向左移动 `1` 位),和 `11111111 >> 1`(即把 `11111111` 向右移动 `1` 位)的结果。蓝色的部分是被移位的,灰色的部分是被抛弃的,橙色的部分则是被填充进来的:
|
||||
|
||||

|
||||
|
||||
下面的代码演示了 Swift 中的移位运算:
|
||||
|
||||
```swift
|
||||
let shiftBits: UInt8 = 4 // 即二进制的 00000100
|
||||
shiftBits << 1 // 00001000
|
||||
shiftBits << 2 // 00010000
|
||||
shiftBits << 5 // 10000000
|
||||
shiftBits << 6 // 00000000
|
||||
shiftBits >> 2 // 00000001
|
||||
```
|
||||
|
||||
可以使用移位运算对其他的数据类型进行编码和解码:
|
||||
|
||||
```swift
|
||||
let pink: UInt32 = 0xCC6699
|
||||
let redComponent = (pink & 0xFF0000) >> 16 // redComponent 是 0xCC,即 204
|
||||
let greenComponent = (pink & 0x00FF00) >> 8 // greenComponent 是 0x66, 即 102
|
||||
let blueComponent = pink & 0x0000FF // blueComponent 是 0x99,即 153
|
||||
```
|
||||
|
||||
这个示例使用了一个命名为 `pink` 的 `UInt32` 型常量来存储 CSS 中粉色的颜色值。该 CSS 的十六进制颜色值 `#CC6699`,在 Swift 中表示为 `0xCC6699`。然后利用按位与运算符(`&`)和按位右移运算符(`>>`)从这个颜色值中分解出红(`CC`)、绿(`66`)以及蓝(`99`)三个部分。
|
||||
|
||||
红色部分是通过对 `0xCC6699` 和 `0xFF0000` 进行按位与运算后得到的。`0xFF0000` 中的 `0` 部分“掩盖”了 `OxCC6699` 中的第二、第三个字节,使得数值中的 `6699` 被忽略,只留下 `0xCC0000`。
|
||||
|
||||
然后,再将这个数按向右移动 16 位(`>> 16`)。十六进制中每两个字符表示 8 个比特位,所以移动 16 位后 `0xCC0000` 就变为 `0x0000CC`。这个数和`0xCC`是等同的,也就是十进制数值的 `204`。
|
||||
|
||||
同样的,绿色部分通过对 `0xCC6699` 和 `0x00FF00` 进行按位与运算得到 `0x006600`。然后将这个数向右移动 8 位,得到 `0x66`,也就是十进制数值的 `102`。
|
||||
|
||||
最后,蓝色部分通过对 `0xCC6699` 和 `0x0000FF` 进行按位与运算得到 `0x000099`。这里不需要再向右移位,所以结果为 `0x99` ,也就是十进制数值的 `153`。
|
||||
|
||||
<a name="shifting_behavior_for_signed_integers"></a>
|
||||
#### 有符号整数的移位运算
|
||||
|
||||
对比无符号整数,有符号整数的移位运算相对复杂得多,这种复杂性源于有符号整数的二进制表现形式。(为了简单起见,以下的示例都是基于 8 比特位的有符号整数的,但是其中的原理对任何位数的有符号整数都是通用的。)
|
||||
|
||||
有符号整数使用第 1 个比特位(通常被称为符号位)来表示这个数的正负。符号位为 `0` 代表正数,为 `1` 代表负数。
|
||||
|
||||
其余的比特位(通常被称为数值位)存储了实际的值。有符号正整数和无符号数的存储方式是一样的,都是从 `0` 开始算起。这是值为 `4` 的 `Int8` 型整数的二进制位表现形式:
|
||||
|
||||

|
||||
|
||||
符号位为 `0`,说明这是一个正数,另外 7 位则代表了十进制数值 `4` 的二进制表示。
|
||||
|
||||
负数的存储方式略有不同。它存储的值的绝对值等于 `2` 的 `n` 次方减去它的实际值(也就是数值位表示的值),这里的 `n` 为数值位的比特位数。一个 8 比特位的数有 7 个比特位是数值位,所以是 `2` 的 `7` 次方,即 `128`。
|
||||
|
||||
这是值为 `-4` 的 `Int8` 型整数的二进制位表现形式:
|
||||
|
||||

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

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

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

|
||||
|
||||
这个行为可以确保有符号整数的符号位不会因为右移运算而改变,这通常被称为算术移位。
|
||||
|
||||
由于正数和负数的特殊存储方式,在对它们进行右移的时候,会使它们越来越接近 `0`。在移位的过程中保持符号位不变,意味着负整数在接近 `0` 的过程中会一直保持为负。
|
||||
|
||||
<a name="overflow_operators"></a>
|
||||
## 溢出运算符
|
||||
|
||||
在默认情况下,当向一个整数赋予超过它容量的值时,Swift 默认会报错,而不是生成一个无效的数。这个行为为我们在运算过大或着过小的数的时候提供了额外的安全性。
|
||||
|
||||
例如,`Int16` 型整数能容纳的有符号整数范围是 `-32768` 到 `32767`,当为一个 `Int16` 型变量赋的值超过这个范围时,系统就会报错:
|
||||
|
||||
```swift
|
||||
var potentialOverflow = Int16.max
|
||||
// potentialOverflow 的值是 32767,这是 Int16 能容纳的最大整数
|
||||
potentialOverflow += 1
|
||||
// 这里会报错
|
||||
```
|
||||
|
||||
为过大或者过小的数值提供错误处理,能让我们在处理边界值时更加灵活。
|
||||
|
||||
然而,也可以选择让系统在数值溢出的时候采取截断处理,而非报错。可以使用 Swift 提供的三个溢出运算符来让系统支持整数溢出运算。这些运算符都是以 `&` 开头的:
|
||||
|
||||
* 溢出加法 `&+`
|
||||
* 溢出减法 `&-`
|
||||
* 溢出乘法 `&*`
|
||||
|
||||
<a name="value_overflow"></a>
|
||||
### 数值溢出
|
||||
|
||||
数值有可能出现上溢或者下溢。
|
||||
|
||||
这个示例演示了当我们对一个无符号整数使用溢出加法(`&+`)进行上溢运算时会发生什么:
|
||||
|
||||
```swift
|
||||
var unsignedOverflow = UInt8.max
|
||||
// unsignedOverflow 等于 UInt8 所能容纳的最大整数 255
|
||||
unsignedOverflow = unsignedOverflow &+ 1
|
||||
// 此时 unsignedOverflow 等于 0
|
||||
```
|
||||
|
||||
`unsignedOverflow` 被初始化为 `UInt8` 所能容纳的最大整数(`255`,以二进制表示即 `11111111`)。然后使用了溢出加法运算符(`&+`)对其进行加 `1` 运算。这使得它的二进制表示正好超出 `UInt8` 所能容纳的位数,也就导致了数值的溢出,如下图所示。数值溢出后,留在 `UInt8` 边界内的值是 `00000000`,也就是十进制数值的 `0`。
|
||||
|
||||

|
||||
|
||||
同样地,当我们对一个无符号整数使用溢出减法(`&-`)进行下溢运算时也会产生类似的现象:
|
||||
|
||||
```swift
|
||||
var unsignedOverflow = UInt8.min
|
||||
// unsignedOverflow 等于 UInt8 所能容纳的最小整数 0
|
||||
unsignedOverflow = unsignedOverflow &- 1
|
||||
// 此时 unsignedOverflow 等于 255
|
||||
```
|
||||
|
||||
`UInt8` 型整数能容纳的最小值是 `0`,以二进制表示即 `00000000`。当使用溢出减法运算符对其进行减 `1` 运算时,数值会产生下溢并被截断为 `11111111`, 也就是十进制数值的 `255`。
|
||||
|
||||

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

|
||||
|
||||
对于无符号与有符号整型数值来说,当出现上溢时,它们会从数值所能容纳的最大数变成最小的数。同样地,当发生下溢时,它们会从所能容纳的最小数变成最大的数。
|
||||
|
||||
<a name="precedence_and_associativity"></a>
|
||||
## 优先级和结合性
|
||||
|
||||
运算符的优先级使得一些运算符优先于其他运算符,高优先级的运算符会先被计算。
|
||||
|
||||
结合性定义了相同优先级的运算符是如何结合的,也就是说,是与左边结合为一组,还是与右边结合为一组。可以将这意思理解为“它们是与左边的表达式结合的”或者“它们是与右边的表达式结合的”。
|
||||
|
||||
在复合表达式的运算顺序中,运算符的优先级和结合性是非常重要的。举例来说,运算符优先级解释了为什么下面这个表达式的运算结果会是 `17`。
|
||||
|
||||
```swift
|
||||
2 + 3 % 4 * 5
|
||||
// 结果是 17
|
||||
```
|
||||
|
||||
如果完全从左到右进行运算,则运算的过程是这样的:
|
||||
|
||||
- 2 + 3 = 5
|
||||
- 5 % 4 = 1
|
||||
- 1 * 5 = 5
|
||||
|
||||
但是正确答案是 `17` 而不是 `5`。优先级高的运算符要先于优先级低的运算符进行计算。与 C 语言类似,在 Swift 中,乘法运算符(`*`)与取余运算符(`%`)的优先级高于加法运算符(`+`)。因此,它们的计算顺序要先于加法运算。
|
||||
|
||||
而乘法与取余的优先级相同。这时为了得到正确的运算顺序,还需要考虑结合性。乘法与取余运算都是左结合的。可以将这考虑成为这两部分表达式都隐式地加上了括号:
|
||||
|
||||
```swift
|
||||
2 + ((3 % 4) * 5)
|
||||
```
|
||||
|
||||
`(3 % 4)` 等于 `3`,所以表达式相当于:
|
||||
|
||||
```swift
|
||||
2 + (3 * 5)
|
||||
```
|
||||
|
||||
`3 * 5` 等于 `15`,所以表达式相当于:
|
||||
|
||||
```swift
|
||||
2 + 15
|
||||
```
|
||||
|
||||
因此计算结果为 `17`。
|
||||
|
||||
如果想查看完整的 Swift 运算符优先级和结合性规则,请参考[表达式](../chapter3/04_Expressions.html)。如果想查看 Swift 标准库提供所有的运算符,请查看 [Swift Standard Library Operators Reference](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_StandardLibrary_Operators/index.html#//apple_ref/doc/uid/TP40016054)。
|
||||
|
||||
> 注意
|
||||
> 相对 C 语言和 Objective-C 来说,Swift 的运算符优先级和结合性规则更加简洁和可预测。但是,这也意味着它们相较于 C 语言及其衍生语言并不是完全一致的。在对现有的代码进行移植的时候,要注意确保运算符的行为仍然符合你的预期。
|
||||
|
||||
<a name="operator_functions"></a>
|
||||
## 运算符函数
|
||||
|
||||
类和结构体可以为现有的运算符提供自定义的实现,这通常被称为运算符重载。
|
||||
|
||||
下面的例子展示了如何为自定义的结构体实现加法运算符(`+`)。算术加法运算符是一个双目运算符,因为它可以对两个值进行运算,同时它还是中缀运算符,因为它出现在两个值中间。
|
||||
|
||||
例子中定义了一个名为 `Vector2D` 的结构体用来表示二维坐标向量 `(x, y)`,紧接着定义了一个可以对两个 `Vector2D` 结构体进行相加的运算符函数:
|
||||
|
||||
```swift
|
||||
struct Vector2D {
|
||||
var x = 0.0, y = 0.0
|
||||
}
|
||||
func + (left: Vector2D, right: Vector2D) -> Vector2D {
|
||||
return Vector2D(x: left.x + right.x, y: left.y + right.y)
|
||||
}
|
||||
```
|
||||
|
||||
该运算符函数被定义为一个全局函数,并且函数的名字与它要进行重载的 `+` 名字一致。因为算术加法运算符是双目运算符,所以这个运算符函数接收两个类型为 `Vector2D` 的参数,同时有一个 `Vector2D` 类型的返回值。
|
||||
|
||||
在这个实现中,输入参数分别被命名为 `left` 和 `right`,代表在 `+` 运算符左边和右边的两个 `Vector2D` 实例。函数返回了一个新的 `Vector2D` 实例,这个实例的 `x` 和 `y` 分别等于作为参数的两个实例的 `x` 和 `y` 的值之和。
|
||||
|
||||
这个函数被定义成全局的,而不是 `Vector2D` 结构体的成员方法,所以任意两个 `Vector2D` 实例都可以使用这个中缀运算符:
|
||||
|
||||
```swift
|
||||
let vector = Vector2D(x: 3.0, y: 1.0)
|
||||
let anotherVector = Vector2D(x: 2.0, y: 4.0)
|
||||
let combinedVector = vector + anotherVector
|
||||
// combinedVector 是一个新的 Vector2D 实例,值为 (5.0, 5.0)
|
||||
```
|
||||
|
||||
这个例子实现两个向量 `(3.0,1.0)` 和 `(2.0,4.0)` 的相加,并得到新的向量 `(5.0,5.0)`。这个过程如下图示:
|
||||
|
||||

|
||||
|
||||
<a name="prefix_and_postfix_operators"></a>
|
||||
### 前缀和后缀运算符
|
||||
|
||||
上个例子演示了一个双目中缀运算符的自定义实现。类与结构体也能提供标准单目运算符的实现。单目运算符只运算一个值。当运算符出现在值之前时,它就是前缀的(例如 `-a`),而当它出现在值之后时,它就是后缀的(例如 `i++`)。
|
||||
|
||||
要实现前缀或者后缀运算符,需要在声明运算符函数的时候在 `func` 关键字之前指定 `prefix` 或者 `postfix` 修饰符:
|
||||
|
||||
```swift
|
||||
prefix func - (vector: Vector2D) -> Vector2D {
|
||||
return Vector2D(x: -vector.x, y: -vector.y)
|
||||
}
|
||||
```
|
||||
|
||||
这段代码为 `Vector2D` 类型实现了单目负号运算符。由于该运算符是前缀运算符,所以这个函数需要加上 `prefix` 修饰符。
|
||||
|
||||
对于简单数值,单目负号运算符可以对它们的正负性进行改变。对于 `Vector2D` 来说,该运算将其 `x` 和 `y` 属性的正负性都进行了改变:
|
||||
|
||||
```swift
|
||||
let positive = Vector2D(x: 3.0, y: 4.0)
|
||||
let negative = -positive
|
||||
// negative 是一个值为 (-3.0, -4.0) 的 Vector2D 实例
|
||||
let alsoPositive = -negative
|
||||
// alsoPositive 是一个值为 (3.0, 4.0) 的 Vector2D 实例
|
||||
```
|
||||
<a name="compound_assignment_operators"></a>
|
||||
### 复合赋值运算符
|
||||
|
||||
复合赋值运算符将赋值运算符(`=`)与其它运算符进行结合。例如,将加法与赋值结合成加法赋值运算符(`+=`)。在实现的时候,需要把运算符的左参数设置成 `inout` 类型,因为这个参数的值会在运算符函数内直接被修改。
|
||||
|
||||
```swift
|
||||
func += (inout left: Vector2D, right: Vector2D) {
|
||||
left = left + right
|
||||
}
|
||||
```
|
||||
|
||||
因为加法运算在之前已经定义过了,所以在这里无需重新定义。在这里可以直接利用现有的加法运算符函数,用它来对左值和右值进行相加,并再次赋值给左值:
|
||||
|
||||
```swift
|
||||
var original = Vector2D(x: 1.0, y: 2.0)
|
||||
let vectorToAdd = Vector2D(x: 3.0, y: 4.0)
|
||||
original += vectorToAdd
|
||||
// original 的值现在为 (4.0, 6.0)
|
||||
```
|
||||
|
||||
还可以将赋值与 `prefix` 或 `postfix` 修饰符结合起来,下面的代码为 `Vector2D` 实例实现了前缀自增运算符:
|
||||
|
||||
```swift
|
||||
prefix func ++ (inout vector: Vector2D) -> Vector2D {
|
||||
vector += Vector2D(x: 1.0, y: 1.0)
|
||||
return vector
|
||||
}
|
||||
```
|
||||
|
||||
这个前缀自增运算符使用了前面定义的加法赋值运算。它对 `Vector2D` 的 `x` 和 `y` 属性都进行了加 `1` 运算,再将结果返回:
|
||||
|
||||
```swift
|
||||
var toIncrement = Vector2D(x: 3.0, y: 4.0)
|
||||
let afterIncrement = ++toIncrement
|
||||
// toIncrement 的值现在为 (4.0, 5.0)
|
||||
// afterIncrement 的值同样为 (4.0, 5.0)
|
||||
```
|
||||
|
||||
> 注意
|
||||
> 不能对默认的赋值运算符(`=`)进行重载。只有组合赋值运算符可以被重载。同样地,也无法对三目条件运算符 (`a ? b : c`) 进行重载。
|
||||
|
||||
<a name="equivalence_operators"></a>
|
||||
### 等价运算符
|
||||
|
||||
自定义的类和结构体没有对等价运算符进行默认实现,等价运算符通常被称为“相等”运算符(`==`)与“不等”运算符(`!=`)。对于自定义类型,Swift 无法判断其是否“相等”,因为“相等”的含义取决于这些自定义类型在你的代码中所扮演的角色。
|
||||
|
||||
为了使用等价运算符能对自定义的类型进行判等运算,需要为其提供自定义实现,实现的方法与其它中缀运算符一样:
|
||||
|
||||
```swift
|
||||
func == (left: Vector2D, right: Vector2D) -> Bool {
|
||||
return (left.x == right.x) && (left.y == right.y)
|
||||
}
|
||||
func != (left: Vector2D, right: Vector2D) -> Bool {
|
||||
return !(left == right)
|
||||
}
|
||||
```
|
||||
|
||||
上述代码实现了“相等”运算符(`==`)来判断两个 `Vector2D` 实例是否相等。对于 `Vector2D` 类型来说,“相等”意味着“两个实例的 `x` 属性和 `y` 属性都相等”,这也是代码中用来进行判等的逻辑。示例里同时也实现了“不等”运算符(`!=`),它简单地将“相等”运算符的结果进行取反后返回。
|
||||
|
||||
现在我们可以使用这两个运算符来判断两个 `Vector2D` 实例是否相等:
|
||||
|
||||
```swift
|
||||
let twoThree = Vector2D(x: 2.0, y: 3.0)
|
||||
let anotherTwoThree = Vector2D(x: 2.0, y: 3.0)
|
||||
if twoThree == anotherTwoThree {
|
||||
print("These two vectors are equivalent.")
|
||||
}
|
||||
// 打印 “These two vectors are equivalent.”
|
||||
```
|
||||
|
||||
<a name="custom_operators"></a>
|
||||
## 自定义运算符
|
||||
|
||||
除了实现标准运算符,在 Swift 中还可以声明和实现自定义运算符。可以用来自定义运算符的字符列表请参考[运算符](../chapter3/02_Lexical_Structure.html#operators)。
|
||||
|
||||
新的运算符要使用 `operator` 关键字在全局作用域内进行定义,同时还要指定 `prefix`、`infix` 或者 `postfix` 修饰符:
|
||||
|
||||
```swift
|
||||
prefix operator +++ {}
|
||||
```
|
||||
|
||||
上面的代码定义了一个新的名为 `+++` 的前缀运算符。对于这个运算符,在 Swift 中并没有意义,因此我们针对 `Vector2D` 的实例来定义它的意义。对这个示例来讲,`+++` 被实现为“前缀双自增”运算符。它使用了前面定义的复合加法运算符来让矩阵对自身进行相加,从而让 `Vector2D` 实例的 `x` 属性和 `y` 属性的值翻倍:
|
||||
|
||||
```swift
|
||||
prefix func +++ (inout vector: Vector2D) -> Vector2D {
|
||||
vector += vector
|
||||
return vector
|
||||
}
|
||||
```
|
||||
|
||||
`Vector2D` 的 `+++` 的实现和 `++` 的实现很相似,唯一不同的是前者对自身进行相加,而后者是与另一个值为 `(1.0, 1.0)` 的向量相加。
|
||||
|
||||
```swift
|
||||
var toBeDoubled = Vector2D(x: 1.0, y: 4.0)
|
||||
let afterDoubling = +++toBeDoubled
|
||||
// toBeDoubled 现在的值为 (2.0, 8.0)
|
||||
// afterDoubling 现在的值也为 (2.0, 8.0)
|
||||
```
|
||||
|
||||
<a name="precedence_and_associativity_for_custom_infix_operators"></a>
|
||||
### 自定义中缀运算符的优先级和结合性
|
||||
|
||||
自定义的中缀运算符也可以指定优先级和结合性。[优先级和结合性](#precedence_and_associativity)中详细阐述了这两个特性是如何对中缀运算符的运算产生影响的。
|
||||
|
||||
结合性可取的值有` left`,`right` 和 `none`。当左结合运算符跟其他相同优先级的左结合运算符写在一起时,会跟左边的值进行结合。同理,当右结合运算符跟其他相同优先级的右结合运算符写在一起时,会跟右边的值进行结合。而非结合运算符不能跟其他相同优先级的运算符写在一起。
|
||||
|
||||
结合性的默认值是 `none`,优先级的默认值 `100`。
|
||||
|
||||
以下例子定义了一个新的中缀运算符 `+-`,此运算符的结合性为 `left`,并且它的优先级为 `140`:
|
||||
|
||||
```swift
|
||||
infix operator +- { associativity left precedence 140 }
|
||||
func +- (left: Vector2D, right: Vector2D) -> Vector2D {
|
||||
return Vector2D(x: left.x + right.x, y: left.y - right.y)
|
||||
}
|
||||
let firstVector = Vector2D(x: 1.0, y: 2.0)
|
||||
let secondVector = Vector2D(x: 3.0, y: 4.0)
|
||||
let plusMinusVector = firstVector +- secondVector
|
||||
// plusMinusVector 是一个 Vector2D 实例,并且它的值为 (4.0, -2.0)
|
||||
```
|
||||
|
||||
这个运算符把两个向量的 `x` 值相加,同时用第一个向量的 `y` 值减去第二个向量的 `y` 值。因为它本质上是属于“相加型”运算符,所以将它的结合性和优先级被分别设置为 `left` 和 `140`,这与 `+` 和 `-` 等默认的中缀“相加型”运算符是相同的。关于 Swift 标准库提供的运算符的结合性与优先级,请参考 [Swift Standard Library Operators Reference](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_StandardLibrary_Operators/index.html#//apple_ref/doc/uid/TP40016054)。
|
||||
|
||||
> 注意
|
||||
> 当定义前缀与后缀运算符的时候,我们并没有指定优先级。然而,如果对同一个值同时使用前缀与后缀运算符,则后缀运算符会先参与运算。
|
||||
@ -1,40 +0,0 @@
|
||||
# 关于语言参考(About the Language Reference)
|
||||
-----------------
|
||||
|
||||
> 1.0
|
||||
> 翻译:[dabing1022](https://github.com/dabing1022)
|
||||
> 校对:[numbbbbb](https://github.com/numbbbbb)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[KYawn](https://github.com/KYawn)
|
||||
|
||||
本页内容包括:
|
||||
|
||||
- [如何阅读语法](#how_to_read_the_grammar)
|
||||
|
||||
本书的这一节描述了 Swift 编程语言的形式语法。这里描述的语法是为了帮助您更详细地了解该语言,而不是让您直接实现一个解析器或编译器。
|
||||
|
||||
Swift 语言相对较小,这是由于 Swift 代码中的几乎所有常见类型、函数以及运算符都已经在 Swift 标准库中定义了。虽然这些类型、函数和运算符并不是 Swift 语言自身的一部分,但是它们被广泛应用于本书的讨论和代码范例中。
|
||||
|
||||
<a name="how_to_read_the_grammar"></a>
|
||||
## 如何阅读语法
|
||||
|
||||
用来描述 Swift 编程语言形式语法的符号遵循下面几个约定:
|
||||
|
||||
- 箭头(`→`)用来标记语法产式,可以理解为“可由……构成”。
|
||||
- 斜体文字用来表示句法类型,并出现在一个语法产式规则两侧。
|
||||
- 关键字和标点符号由固定宽度的粗体文本表示,只出现在一个语法产式规则的右侧。
|
||||
- 可供选择的语法产式由竖线(`|`)分隔。当可选用的语法产式太多时,为了阅读方便,它们将被拆分为多行语法产式规则。
|
||||
- 少数情况下,语法产式规则的右侧会有用于描述的常规字体文字。
|
||||
- 可选的句法类型和字面值用尾标 `opt` 来标记。
|
||||
|
||||
举个例子,getter-setter 的语法块的定义如下:
|
||||
|
||||
> getter-setter 方法块语法
|
||||
> *getter-setter 方法块* → { [*getter 子句*](05_Declarations.html#getter-clause) [*setter 子句*](05_Declarations.html#setter-clause)<sub>可选</sub> } | { [*setter 子句*](05_Declarations.html#setter-clause) [*getter 子句*](05_Declarations.html#getter-clause) }
|
||||
|
||||
这个定义表明,一个 getter-setter 方法块可以由一个 getter 子句后跟一个可选的 setter 子句构成,然后用大括号括起来,或者由一个 setter 子句后跟一个 getter 子句构成,然后用大括号括起来。下面的两个语法产式等价于上述的语法产式,并明确指出了如何取舍:
|
||||
|
||||
> getter-setter 方法块语法
|
||||
> getter-setter 方法块 → { [*getter 子句*](05_Declarations.html#getter-clause) [*setter 子句*](05_Declarations.html#setter-clause)<sub>可选</sub> }
|
||||
> getter-setter 方法块 → { [*setter 子句*](05_Declarations.html#setter-clause) [*getter 子句*](05_Declarations.html#getter-clause) }
|
||||
@ -1,378 +0,0 @@
|
||||
# 词法结构(Lexical Structure)
|
||||
-----------------
|
||||
|
||||
> 1.0
|
||||
> 翻译:[superkam](https://github.com/superkam)
|
||||
> 校对:[numbbbbb](https://github.com/numbbbbb)
|
||||
|
||||
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[buginux](https://github.com/buginux)
|
||||
|
||||
|
||||
|
||||
> 2.1
|
||||
> 翻译:[mmoaay](https://github.com/mmoaay)
|
||||
|
||||
|
||||
|
||||
> 2.2
|
||||
> 翻译+校对:[星夜暮晨](https://github.com/semperidem),2016-04-06
|
||||
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [空白与注释](#whitespace_and_comments)
|
||||
- [标识符](#identifiers)
|
||||
- [关键字和标点符号](#keywords)
|
||||
- [字面量](#literals)
|
||||
- [整数字面量](#integer_literals)
|
||||
- [浮点数字面量](#floating_point_literals)
|
||||
- [字符串字面量](#string_literals)
|
||||
- [运算符](#operators)
|
||||
|
||||
Swift 的*“词法结构 (lexical structure)”* 描述了能构成该语言中合法符号 (token) 的字符序列。这些合法符号组成了语言中最底层的构建基块,并在之后的章节中用于描述语言的其他部分。一个合法符号由一个标识符 (identifier)、关键字 (keyword)、标点符号 (punctuation)、字面量 (literal) 或运算符 (operator) 组成。
|
||||
|
||||
通常情况下,通过考虑输入文本当中可能的最长子串,并且在随后将介绍的语法约束之下,根据随后将介绍的语法约束生成的,根据 Swift 源文件当中的字符来生成相应的“符号”。这种方法称为*“最长匹配 (longest match)”*,或者*“最大适合(maximal munch)”*。
|
||||
|
||||
<a id="whitespace_and_comments"></a>
|
||||
## 空白与注释
|
||||
|
||||
空白 (whitespace) 有两个用途:分隔源文件中的符号以及帮助区分运算符属于前缀还是后缀(参见 [运算符](#operators)),在其他情况下空白则会被忽略。以下的字符会被当作空白:空格(U+0020)、换行符(U+000A)、回车符(U+000D)、水平制表符(U+0009)、垂直制表符(U+000B)、换页符(U+000C)以及空字符(U+0000)。
|
||||
|
||||
注释被编译器当作空白处理。单行注释由 `//` 开始直至遇到换行符(U+000A)或者回车符(U+000D)。多行注释由 `/*` 开始,以 `*/` 结束。注释允许嵌套,但注释标记必须匹配。
|
||||
|
||||
正如 [*Markup Formatting Reference*](https://developer.apple.com/library/prerelease/ios/documentation/Xcode/Reference/xcode_markup_formatting_ref/index.html#//apple_ref/doc/uid/TP40016497) 所述,注释可以包含附加的格式和标记。
|
||||
|
||||
<a id="identifiers"></a>
|
||||
## 标识符
|
||||
|
||||
*标识符(identifier)* 可以由以下的字符开始:大写或小写的字母 `A` 到 `Z`、下划线 (`_`)、基本多文种平面 (Basic Multilingual Plane) 中非字符数字组合的 Unicode 字符以及基本多文种平面以外的非个人专用区字符。在首字符之后,允许使用数字和组合 Unicode 字符。
|
||||
|
||||
使用保留字作为标识符,需要在其前后增加反引号 (`` ` ``)。例如,`class` 不是合法的标识符,但可以使用 `` `class` ``。反引号不属于标识符的一部分,`` `x` `` 和 `x` 表示同一标识符。
|
||||
|
||||
闭包中如果没有明确指定参数名称,参数将被隐式命名为 `$0`、`$1`、`$2` 等等。这些命名在闭包作用域范围内是合法的标识符。
|
||||
|
||||
> 标识符语法
|
||||
|
||||
<a id="identifier"></a>
|
||||
> *标识符* → [*头部标识符*](#identifier-head) [*标识符字符组*](#identifier-characters)<sub>可选</sub>
|
||||
> *标识符* → \`[*头部标识符*](#identifier-head) [*标识符字符组*](#identifier-characters)<sub>可选</sub>\`
|
||||
> *标识符* → [*隐式参数名*](#implicit-parameter-name)
|
||||
|
||||
<a id="identifier-list"></a>
|
||||
> *标识符列表* → [*标识符*](#identifier) | [*标识符*](#identifier) **,** [*标识符列表*](#identifier-list)
|
||||
|
||||
<a id="identifier-head"></a>
|
||||
> *头部标识符* → 大写或小写字母 A - Z
|
||||
> *头部标识符* → _
|
||||
> *头部标识符* → U+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
|
||||
|
||||
<a id="identifier-character"></a>
|
||||
> *标识符字符* → 数值 0 - 9
|
||||
> *标识符字符* → U+0300–U+036F,U+1DC0–U+1DFF,U+20D0–U+20FF,或者 U+FE20–U+FE2F
|
||||
> *标识符字符* → [*头部标识符*](#identifier-head)
|
||||
> <a id="identifier-characters"></a>
|
||||
> *标识符字符组* → [*标识符字符*](#identifier-character) [*标识符字符组*](#identifier-characters)<sub>可选</sub>
|
||||
|
||||
<a id="implicit-parameter-name"></a>
|
||||
> *隐式参数名* → **$** [*十进制数字列表*](#decimal-digits)
|
||||
|
||||
<a id="keywords"></a>
|
||||
## 关键字和标点符号
|
||||
|
||||
下面这些被保留的关键字不允许用作标识符,除非使用反引号转义,具体描述请参考 [标识符](#identifiers)。除了 `inout`、`var` 以及 `let` 之外的关键字可以用作某个函数声明或者函数调用当中的外部参数名,不用添加反引号转义。
|
||||
|
||||
* 用在声明中的关键字: `associatedtype`、`class`、`deinit`、`enum`、`extension`、`func`、`import`、`init`、`inout`、`internal`、`let`、`operator`、`private`、`protocol`、`public`、`static`、`struct`、`subscript`、`typealias` 以及 `var`。
|
||||
* 用在语句中的关键字:`break`、`case`、`continue`、`default`、`defer`、`do`、`else`、`fallthrough`、`for`、`guard`、`if`、`in`、`repeat`、`return`、`switch`、`where` 以及 `while`。
|
||||
* 用在表达式和类型中的关键字:`as`、`catch`、`dynamicType`、`false`、`is`、`nil`、`rethrows`、`super`、`self`、`Self`、`throw`、`throws`、`true`、`try`、`#column`、`#file`、`#function` 以及 `#line`。
|
||||
* 用在模式中的关键字:`_`。
|
||||
* 以井字号 (`#`) 开头的关键字:`#available`、`#column`、`#else#elseif`、`#endif`、`#file`、`#function`、`#if`、`#line` 以及 `#selector`。
|
||||
* 特定上下文中被保留的关键字: `associativity`、`convenience`、`dynamic`、`didSet`、`final`、`get`、`infix`、`indirect`、`lazy`、`left`、`mutating`、`none`、`nonmutating`、`optional`、`override`、`postfix`、`precedence`、`prefix`、`Protocol`、`required`、`right`、`set`、`Type`、`unowned`、`weak` 以及 `willSet`。这些关键字在特定上下文之外可以被用做标识符。
|
||||
|
||||
以下符号被当作保留符号,不能用于自定义运算符: `(`、`)`、`{`、`}`、`[`、`]`、`.`、`,`、`:`、`;`、`=`、`@`、`#`、`&`(作为前缀运算符)、`->`、`` ` ``、`?`、`!`(作为后缀运算符)。
|
||||
|
||||
<a id="literals"></a>
|
||||
## 字面量
|
||||
|
||||
*字面量 (literal)* 用来表示源码中某种特定类型的值,比如一个数字或字符串。
|
||||
|
||||
下面是字面量的一些示例:
|
||||
|
||||
```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` 的类型标注。
|
||||
|
||||
> 字面量语法
|
||||
> *字面量* → [*数值字面量*](#numeric-literal) | [*字符串字面量*](#string-literal) | [*布尔值字面量*](#boolean-literal) | [*nil 字面量*](#nil-literal)
|
||||
|
||||
<a id="numeric-literal"></a>
|
||||
> *数值字面量* → **-**<sub>可选</sub> [*整数字面量*](#integer-literal) | **-**<sub>可选</sub> [*浮点数字面量*](#floating-point-literal)
|
||||
> <a id="boolean-literal"></a>
|
||||
> *布尔值字面量* → **true** | **false**
|
||||
> <a id="nil-literal"></a>
|
||||
> *nil 字面量* → **nil**
|
||||
|
||||
<a id="integer_literals"></a>
|
||||
### 整数字面量
|
||||
|
||||
*整数字面量 (Integer Literals)* 表示未指定精度整数的值。整数字面量默认用十进制表示,可以加前缀来指定其他的进制。二进制字面量加 `0b`,八进制字面量加 `0o`,十六进制字面量加 `0x`。
|
||||
|
||||
十进制字面量包含数字 `0` 至 `9`。二进制字面量只包含 `0` 或 `1`,八进制字面量包含数字 `0` 至 `7`,十六进制字面量包含数字 `0` 至 `9` 以及字母 `A` 至 `F`(大小写均可)。
|
||||
|
||||
负整数的字面量在整数字面量前加负号 `-`,比如 `-42`。
|
||||
|
||||
整型字面面可以使用下划线 (`_`) 来增加数字的可读性,下划线会被系统忽略,因此不会影响字面量的值。同样地,也可以在数字前加 `0`,这同样也会被系统所忽略,并不会影响字面量的值。
|
||||
|
||||
除非特别指定,整数字面量的默认推导类型为 Swift 标准库类型中的 `Int`。Swift 标准库还定义了其他不同长度以及是否带符号的整数类型,请参考 [整数](../chapter2/01_The_Basics.html#integers)。
|
||||
|
||||
> 整数字面量语法
|
||||
|
||||
<a id="integer-literal"></a>
|
||||
> *整数字面量* → [*二进制字面量*](#binary-literal)
|
||||
> *整数字面量* → [*八进制字面量*](#octal-literal)
|
||||
> *整数字面量* → [*十进制字面量*](#decimal-literal)
|
||||
> *整数字面量* → [*十六进制字面量*](#hexadecimal-literal)
|
||||
|
||||
<a id="binary-literal"></a>
|
||||
> *二进制字面量* → **0b** [*二进制数字*](#binary-digit) [*二进制字面量字符组*](#binary-literal-characters)<sub>可选</sub>
|
||||
> <a id="binary-digit"></a>
|
||||
> *二进制数字* → 数值 0 到 1
|
||||
> <a id="binary-literal-character"></a>
|
||||
> *二进制字面量字符* → [*二进制数字*](#binary-digit) | _
|
||||
> <a id="binary-literal-characters"></a>
|
||||
> *二进制字面量字符组* → [*二进制字面量字符*](#binary-literal-character) [*二进制字面量字符组*](#binary-literal-characters)<sub>可选</sub>
|
||||
|
||||
<a id="octal-literal"></a>
|
||||
> *八进制字面量* → **0o** [*八进字数字*](#octal-digit) [*八进制字符组*](#octal-literal-characters)<sub>可选</sub>
|
||||
> <a id="octal-digit"></a>
|
||||
> *八进字数字* → 数值 0 到 7
|
||||
> <a id="octal-literal-character"></a>
|
||||
> *八进制字符* → [*八进字数字*](#octal-digit) | _
|
||||
> <a id="octal-literal-characters"></a>
|
||||
> *八进制字符组* → [*八进制字符*](#octal-literal-character) [*八进制字符组*](#octal-literal-characters)<sub>可选</sub>
|
||||
|
||||
<a id="decimal-literal"></a>
|
||||
> *十进制字面量* → [*十进制数字*](#decimal-digit) [*十进制字符组*](#decimal-literal-characters)<sub>可选</sub>
|
||||
> <a id="decimal-digit"></a>
|
||||
> *十进制数字* → 数值 0 到 9
|
||||
> <a id="decimal-digits"></a>
|
||||
> *十进制数字组* → [*十进制数字*](#decimal-digit) [*十进制数字组*](#decimal-digits)<sub>可选</sub>
|
||||
> <a id="decimal-literal-character"></a>
|
||||
> *十进制字符* → [*十进制数字*](#decimal-digit) | _
|
||||
> <a id="decimal-literal-characters"></a>
|
||||
> *十进制字符组* → [*十进制字符*](#decimal-literal-character) [*十进制字符组*](#decimal-literal-characters)<sub>可选</sub>
|
||||
|
||||
<a id="hexadecimal-literal"></a>
|
||||
> *十六进制字面量* → **0x** [*十六进制数字*](#hexadecimal-digit) [*十六进制字面量字符组*](#hexadecimal-literal-characters)<sub>可选</sub>
|
||||
> <a id="hexadecimal-digit"></a>
|
||||
> *十六进制数字* → 数值 0 到 9, 字母 a 到 f, 或 A 到 F
|
||||
> <a id="hexadecimal-literal-character"></a>
|
||||
> *十六进制字符* → [*十六进制数字*](#hexadecimal-digit) | _
|
||||
> <a id="hexadecimal-literal-characters"></a>
|
||||
> *十六进制字面量字符组* → [*十六进制字符*](#hexadecimal-literal-character) [*十六进制字面量字符组*](#hexadecimal-literal-characters)<sub>可选</sub>
|
||||
|
||||
<a id="floating_point_literals"></a>
|
||||
### 浮点数字面量
|
||||
|
||||
*浮点数字面量 (Floating-point literals)* 表示未指定精度浮点数的值。
|
||||
|
||||
浮点数字面量默认用十进制表示(无前缀),也可以用十六进制表示(加前缀 `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 位浮点数。
|
||||
|
||||
> 浮点数字面量语法
|
||||
|
||||
<a id="floating-point-literal"></a>
|
||||
> *浮点数字面量* → [*十进制字面量*](#decimal-literal) [*十进制分数*](#decimal-fraction)<sub>可选</sub> [*十进制指数*](#decimal-exponent)<sub>可选</sub>
|
||||
> *浮点数字面量* → [*十六进制字面量*](#hexadecimal-literal) [*十六进制分数*](#hexadecimal-fraction)<sub>可选</sub> [*十六进制指数*](#hexadecimal-exponent)
|
||||
|
||||
<a id="decimal-fraction"></a>
|
||||
> *十进制分数* → **.** [*十进制字面量*](#decimal-literal)
|
||||
> <a id="decimal-exponent"></a>
|
||||
> *十进制指数* → [*十进制指数 e*](#floating-point-e) [*正负号*](#sign)<sub>可选</sub> [*十进制字面量*](#decimal-literal)
|
||||
|
||||
<a id="hexadecimal-fraction"></a>
|
||||
> *十六进制分数* → **.** [*十六进制数字*](#hexadecimal-digit) [*十六进制字面量字符组*](#hexadecimal-literal-characters)<sub>可选</sub>
|
||||
> <a id="hexadecimal-exponent"></a>
|
||||
> *十六进制指数* → [*十六进制指数 p*](#floating-point-p) [*正负号*](#sign)<sub>可选</sub> [*十进制字面量*](#decimal-literal)
|
||||
|
||||
<a id="floating-point-e"></a>
|
||||
> *十进制指数 e* → **e** | **E**
|
||||
> <a id="floating-point-p"></a>
|
||||
> *十六进制指数 p* → **p** | **P**
|
||||
> <a id="sign"></a>
|
||||
> *正负号* → **+** | **-**
|
||||
|
||||
<a id="string_literals"></a>
|
||||
### 字符串字面量
|
||||
|
||||
字符串字面量由被包在双引号中的一串字符组成,形式如下:
|
||||
|
||||
> "`字符`"
|
||||
|
||||
字符串字面量中不能包含未转义的双引号(`"`)、未转义的反斜线(`\`)、回车符、换行符。
|
||||
|
||||
可以在字符串字面量中使用的转义特殊符号如下:
|
||||
|
||||
* 空字符 `\0`
|
||||
* 反斜线 `\\`
|
||||
* 水平制表符 `\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)"
|
||||
```
|
||||
|
||||
字符串字面量的默认推导类型为 `String`。更多有关 `String` 类型的信息请参考 [字符串和字符](../chapter2/03_Strings_and_Characters.html) 以及 [*String Structure Reference*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_String_Structure/index.html#//apple_ref/doc/uid/TP40015181)。
|
||||
|
||||
用 `+` 操作符连接的字符型字面量是在编译时进行连接的。比如下面的 `textA` 和 `textB` 是完全一样的,`textA` 没有任何运行时的连接操作。
|
||||
|
||||
```swift
|
||||
let textA = "Hello " + "world"
|
||||
let textB = "Hello world"
|
||||
```
|
||||
|
||||
> 字符串字面量语法
|
||||
|
||||
<a id="string-literal"></a>
|
||||
> *字符串字面量* → [*静态字符串字面量*](#static-string-literal) | [*插值字符串字面量*](#interpolated-string-literal)
|
||||
|
||||
<a id="static-string-literal"></a>
|
||||
> *静态字符串字面量* → **"**[*引用文本*](#quoted-text)<sub>可选</sub>**"**
|
||||
> <a id="quoted-text"></a>
|
||||
> *引用文本* → [*引用文本项*](#quoted-text-item) [*引用文本*](#quoted-text)<sub>可选</sub>
|
||||
> <a id="quoted-text-item"></a>
|
||||
> *引用文本项* → [*转义字符*](#escaped-character)
|
||||
> *引用文本项* → 除了 **"**、**\\**、U+000A、U+000D 以外的所有 Unicode 字符
|
||||
|
||||
<a id="interpolated-string-literal"></a>
|
||||
> *插值字符串字面量* → **"**[*插值文本*](#interpolated-text)<sub>可选</sub>**"**
|
||||
> <a id="interpolated-text"></a>
|
||||
> *插值文本* → [*插值文本项*](#interpolated-text-item) [*插值文本*](#interpolated-text)<sub>可选</sub>
|
||||
> <a id="interpolated-text-item"></a>
|
||||
> *插值文本项* → **\\****(**[*表达式*](./04_Expressions.html)**)** | [*引用文本项*](#quoted-text-item)
|
||||
|
||||
<a id="escaped-character"></a>
|
||||
> *转义字符* → **\\****0** | **\\****\\** | **\t** | **\n** | **\r** | **\\"** | **\\'**
|
||||
> *转义字符* → **\u {** [*unicode 标量数字*](#unicode-scalar-digits) **}**
|
||||
> <a id="unicode-scalar-digits"></a>
|
||||
> *unicode 标量数字* → 一到八位的十六进制数字
|
||||
|
||||
<a id="operators"></a>
|
||||
## 运算符
|
||||
|
||||
Swift 标准库定义了许多可供使用的运算符,其中大部分在 [基础运算符](../chapter2/02_Basic_Operators.html) 和 [高级运算符](../chapter2/25_Advanced_Operators.html) 中进行了阐述。这一小节将描述哪些字符能用于自定义运算符。
|
||||
|
||||
自定义运算符可以由以下其中之一的 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/25_Advanced_Operators.html#custom_operators) 和 [运算符声明](05_Declarations.html#operator_declaration)。要学习如何重载运算符,请参考 [运算符函数](../chapter2/25_Advanced_Operators.html#operator_functions)。
|
||||
|
||||
> 运算符语法
|
||||
|
||||
<a id="operator"></a>
|
||||
> *运算符* → [*头部运算符*](#operator-head) [*运算符字符组*](#operator-characters)<sub>可选</sub>
|
||||
> *运算符* → [*头部点运算符*](#dot-operator-head) [*点运算符字符组*](#dot-operator-characters)<sub>可选</sub>
|
||||
|
||||
<a id="operator-head"></a>
|
||||
> *头部运算符* → **/** | **=** | **-** | **+** | **!** | __*__ | **%** | **<** | **>** | **&** | **|** | **^** | **~** | **?**
|
||||
> *头部运算符* → U+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
|
||||
|
||||
<a id="operator-character"></a>
|
||||
> *运算符字符* → [*头部运算符*](#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
|
||||
> <a id="operator-characters"></a>
|
||||
> *运算符字符组* → [*运算符字符*](#operator-character) [*运算符字符组*](#operator-characters)<sub>可选</sub>
|
||||
|
||||
<a id="dot-operator-head"></a>
|
||||
> *头部点运算符* → **..**
|
||||
> <a id="dot-operator-character"></a>
|
||||
> *点运算符字符* → **.** | [*运算符字符*](#operator-character)
|
||||
> <a id="dot-operator-characters"></a>
|
||||
> *点运算符字符组* → [*点运算符字符*](#dot-operator-character) [*点运算符字符组*](#dot-operator-characters)<sub>可选</sub>
|
||||
|
||||
<a id="binary-operator"></a>
|
||||
> *二元运算符* → [*运算符*](#operator)
|
||||
> <a id="prefix-operator"></a>
|
||||
> *前缀运算符* → [*运算符*](#operator)
|
||||
> <a id="postfix-operator"></a>
|
||||
> *后缀运算符* → [*运算符*](#operator)
|
||||
@ -1,356 +0,0 @@
|
||||
# 类型(Types)
|
||||
-----------------
|
||||
|
||||
> 1.0
|
||||
> 翻译:[lyuka](https://github.com/lyuka)
|
||||
> 校对:[numbbbbb](https://github.com/numbbbbb), [stanzhai](https://github.com/stanzhai)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[EudeMorgen](https://github.com/EudeMorgen)
|
||||
|
||||
> 2.1
|
||||
> 翻译:[mmoaay](https://github.com/mmoaay)
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [类型注解](#type_annotation)
|
||||
- [类型标识符](#type_identifier)
|
||||
- [元组类型](#tuple_type)
|
||||
- [函数类型](#function_type)
|
||||
- [数组类型](#array_type)
|
||||
- [字典类型](#dictionary_type)
|
||||
- [可选类型](#optional_type)
|
||||
- [隐式解析可选类型](#implicitly_unwrapped_optional_type)
|
||||
- [协议合成类型](#protocol_composition_type)
|
||||
- [元类型](#metatype_type)
|
||||
- [类型继承子句](#type_inheritance_clause)
|
||||
- [类型推断](#type_inference)
|
||||
|
||||
Swift 语言存在两种类型:命名型类型和复合型类型。命名型类型是指定义时可以给定名字的类型。命名型类型包括类、结构体、枚举和协议。比如,一个用户定义的类 MyClass 的实例拥有类型 MyClass。除了用户定义的命名型类型,Swift 标准库也定义了很多常用的命名型类型,包括那些表示数组、字典和可选值的类型。
|
||||
|
||||
那些通常被其它语言认为是基本或原始的数据型类型,比如表示数字、字符和字符串的类型,实际上就是命名型类型,这些类型在 Swift 标准库中是使用结构体来定义和实现的。因为它们是命名型类型,因此你可以按照 [扩展](../chapter2/21_Extensions.html) 和 [扩展声明](05_Declarations.html#extension_declaration) 中讨论的那样,声明一个扩展来增加它们的行为以满足你程序的需求。
|
||||
|
||||
复合型类型是没有名字的类型,它由 Swift 本身定义。Swift 存在两种复合型类型:函数类型和元组类型。一个复合型类型可以包含命名型类型和其它复合型类型。例如,元组类型 `(Int, (Int, Int))` 包含两个元素:第一个是命名型类型 `Int`,第二个是另一个复合型类型 `(Int, Int)`。
|
||||
|
||||
本节讨论 Swift 语言本身定义的类型,并描述 Swift 中的类型推断行为。
|
||||
|
||||
> 类型语法
|
||||
<a name="type"></a>
|
||||
> *类型* → [*数组类型*](#array-type) | [*字典类型*](#dictionary-type) | [*函数类型*](#function-type) | [*类型标识*](#type-identifier) | [*元组类型*](#tuple-type) | [*可选类型*](#optional-type) | [*隐式解析可选类型*](#implicitly-unwrapped-optional-type) | [*协议合成类型*](#protocol-composition-type) | [*元型类型*](#metatype-type)
|
||||
|
||||
<a name="type_annotation"></a>
|
||||
## 类型注解
|
||||
|
||||
类型注解显式地指定一个变量或表达式的值。类型注解始于冒号 `:` 终于类型,比如下面两个例子:
|
||||
|
||||
```swift
|
||||
let someTuple: (Double, Double) = (3.14159, 2.71828)
|
||||
func someFunction(a: Int) { /* ... */ }
|
||||
```
|
||||
在第一个例子中,表达式 `someTuple` 的类型被指定为 `(Double, Double)`。在第二个例子中,函数 `someFunction` 的参数 `a` 的类型被指定为 `Int`。
|
||||
|
||||
类型注解可以在类型之前包含一个类型特性的可选列表。
|
||||
|
||||
> 类型注解语法
|
||||
<a name="type-annotation"></a>
|
||||
> *类型注解* → **:** [*特性列表*](06_Attributes.html#attributes)<sub>可选</sub> [*类型*](#type)
|
||||
|
||||
<a name="type_identifier"></a>
|
||||
## 类型标识符
|
||||
|
||||
类型标识符引用命名型类型,还可引用命名型或复合型类型的别名。
|
||||
|
||||
大多数情况下,类型标识符引用的是与之同名的命名型类型。例如类型标识符 `Int` 引用命名型类型 `Int`,同样,类型标识符 `Dictionary<String, Int>` 引用命名型类型 `Dictionary<String, Int>`。
|
||||
|
||||
在两种情况下类型标识符不引用同名的类型。情况一,类型标识符引用的是命名型或复合型类型的类型别名。比如,在下面的例子中,类型标识符使用 `Point` 来引用元组 `(Int, Int)`:
|
||||
|
||||
```swift
|
||||
typealias Point = (Int, Int)
|
||||
let origin: Point = (0, 0)
|
||||
```
|
||||
|
||||
情况二,类型标识符使用点语法(`.`)来表示在其它模块或其它类型嵌套内声明的命名型类型。例如,下面例子中的类型标识符引用在 `ExampleModule` 模块中声明的命名型类型 `MyType`:
|
||||
|
||||
```swift
|
||||
var someValue: ExampleModule.MyType
|
||||
```
|
||||
|
||||
> 类型标识符语法
|
||||
<a name="type-identifier"></a>
|
||||
> *类型标识符* → [*类型名称*](#type-name) [*泛型参数子句*](08_Generic_Parameters_and_Arguments.html#generic_argument_clause)<sub>可选</sub> | [*类型名称*](#type-name) [*泛型参数子句*](08_Generic_Parameters_and_Arguments.html#generic_argument_clause)<sub>可选</sub> **.** [*类型标识符*](#type-identifier)
|
||||
<a name="type-name"></a>
|
||||
> *类型名称* → [*标识符*](02_Lexical_Structure.html#identifier)
|
||||
|
||||
<a name="tuple_type"></a>
|
||||
## 元组类型
|
||||
|
||||
元组类型是使用括号括起来的零个或多个类型,类型间用逗号隔开。
|
||||
|
||||
你可以使用元组类型作为一个函数的返回类型,这样就可以使函数返回多个值。你也可以命名元组类型中的元素,然后用这些名字来引用每个元素的值。元素的名字由一个标识符紧跟一个冒号 `(:)` 组成。[函数和多返回值](../chapter2/06_Functions.html#functions_with_multiple_return_values) 章节里有一个展示上述特性的例子。
|
||||
|
||||
`Void` 是空元组类型 `()` 的别名。如果括号内只有一个元素,那么该类型就是括号内元素的类型。比如,`(Int)` 的类型是 `Int` 而不是 `(Int)`。所以,只有当元组类型包含的元素个数在两个及以上时才可以命名元组元素。
|
||||
|
||||
> 元组类型语法
|
||||
<a name="tuple-type"></a>
|
||||
> *元组类型* → **(** [*元组类型主体*](#tuple-type-body)<sub>可选</sub> **)**
|
||||
<a name="tuple-type-body"></a>
|
||||
> *元组类型主体* → [*元组类型元素列表*](#tuple-type-element-list) **...**<sub>可选</sub>
|
||||
<a name="tuple-type-element-list"></a>
|
||||
> *元组类型元素列表* → [*元组类型元素*](#tuple-type-element) | [*元组类型元素*](#tuple-type-element) **,** [*元组类型元素列表*](#tuple-type-element-list)
|
||||
<a name="tuple-type-element"></a>
|
||||
> *元组类型元素* → [*特性列表*](06_Attributes.html#attributes)<sub>可选</sub> **inout**<sub>可选</sub> [*类型*](#type) | **inout**<sub>可选</sub> [*元素名*](#element-name) [*类型注解*](#type-annotation)
|
||||
<a name="element-name"></a>
|
||||
> *元素名* → [*标识符*](02_Lexical_Structure.html#identifier)
|
||||
|
||||
<a name="function_type"></a>
|
||||
## 函数类型
|
||||
|
||||
函数类型表示一个函数、方法或闭包的类型,它由参数类型和返回值类型组成,中间用箭头(`->`)隔开:
|
||||
|
||||
> `参数类型` -> `返回值类型`
|
||||
|
||||
由于参数类型和返回值类型可以是元组类型,所以函数类型支持多参数与多返回值的函数与方法。
|
||||
|
||||
你可以对函数参数使用 `autoclosure` 特性。这会自动将参数表达式转化为闭包,表达式的结果即闭包返回值。这从语法结构上提供了一种便捷:延迟对表达式的求值,直到其值在函数体中被使用。以自动闭包做为参数的函数类型的例子详见 [自动闭包](../chapter2/07_Closures.html#autoclosures) 。
|
||||
|
||||
函数类型可以拥有一个可变长参数作为参数类型中的最后一个参数。从语法角度上讲,可变长参数由一个基础类型名字紧随三个点(`...`)组成,如 `Int...`。可变长参数被认为是一个包含了基础类型元素的数组。即 `Int...` 就是 `[Int]`。关于使用可变长参数的例子,请参阅 [可变参数](../chapter2/06_Functions.html#variadic_parameters)。
|
||||
|
||||
为了指定一个 `in-out` 参数,可以在参数类型前加 `inout` 前缀。但是你不可以对可变长参数或返回值类型使用 `inout`。关于这种参数的详细讲解请参阅 [输入输出参数](../chapter2/06_Functions.html#in_out_parameters)。
|
||||
|
||||
柯里化函数的函数类型从右向左进行组合。例如,函数类型 `Int -> Int -> Int` 可以理解为 `Int -> (Int -> Int)`,也就是说,该函数类型的参数为 `Int` 类型,其返回类型是一个参数类型为 `Int`,返回类型为 `Int` 的函数类型。关于柯里化函数的讨论见章节 [柯里化函数](05_Declarations.html#curried_functions)。
|
||||
|
||||
函数类型若要抛出错误就必须使用 `throws` 关键字来标记,若要重抛错误则必须使用 `rethrows` 关键字来标记。`throws` 关键字是函数类型的一部分,非抛出函数是抛出函数函数的一个子类型。因此,在使用抛出函数的地方也可以使用不抛出函数。对于柯里化函数,`throws` 关键字只应用于最里层的函数。抛出和重抛函数的相关描述见章节 [抛出函数与方法](05_Declarations.html#throwing_functions_and_methods) 和 [重抛函数与方法](05_Declarations.html#rethrowing_functions_and_methods)。
|
||||
|
||||
> 函数类型语法
|
||||
<a name="function-type"></a>
|
||||
> *函数类型* → [*类型*](#type) **throws**<sub>可选</sub> **->** [*类型*](#type)
|
||||
> *函数类型* → [*类型*](#type) **rethrows**<sub>可选</sub> **->** [*类型*](#type)
|
||||
|
||||
<a name="array_type"></a>
|
||||
## 数组类型
|
||||
|
||||
Swift 语言为标准库中定义的 `Array<Element>` 类型提供了如下语法糖:
|
||||
|
||||
> [`类型`]
|
||||
|
||||
换句话说,下面两个声明是等价的:
|
||||
|
||||
```swift
|
||||
let someArray: Array<String> = ["Alex", "Brian", "Dave"]
|
||||
let someArray: [String] = ["Alex", "Brian", "Dave"]
|
||||
```
|
||||
|
||||
上面两种情况下,常量 `someArray` 都被声明为字符串数组。数组的元素也可以通过下标访问:`someArray[0]` 是指第 0 个元素 `"Alex"`。
|
||||
|
||||
你也可以嵌套多对方括号来创建多维数组,最里面的方括号中指明数组元素的基本类型。比如,下面例子中使用三对方括号创建三维整数数组:
|
||||
|
||||
```swift
|
||||
var array3D: [[[Int]]] = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
|
||||
```
|
||||
|
||||
访问一个多维数组的元素时,最左边的下标指向最外层数组的相应位置元素。接下来往右的下标指向第一层嵌入的相应位置元素,依次类推。这就意味着,在上面的例子中,`array3D[0]` 是 `[[1, 2], [3, 4]]`,`array3D[0][1]` 是 `[3, 4]`,`array3D[0][1][1]` 则是 `4`。
|
||||
|
||||
关于 Swift 标准库中 `Array` 类型的详细讨论,请参阅 [数组](../chapter2/04_Collection_Types.html#arrays)。
|
||||
|
||||
> 数组类型语法
|
||||
<a name="array-type"></a>
|
||||
> *数组类型* → **[** [*类型*](#type) **]**
|
||||
|
||||
<a name="dictionary_type"></a>
|
||||
## 字典类型
|
||||
|
||||
Swift 语言为标准库中定义的 `Dictionary<Key, Value>` 类型提供了如下语法糖:
|
||||
|
||||
> [`键类型` : `值类型`]
|
||||
|
||||
换句话说,下面两个声明是等价的:
|
||||
|
||||
```swift
|
||||
let someDictionary: [String: Int] = ["Alex": 31, "Paul": 39]
|
||||
let someDictionary: Dictionary<String, Int> = ["Alex": 31, "Paul": 39]
|
||||
```
|
||||
|
||||
上面两种情况,常量 `someDictionary` 被声明为一个字典,其中键为 `String` 类型,值为 `Int` 类型。
|
||||
|
||||
字典中的值可以通过下标来访问,这个下标在方括号中指明了具体的键:`someDictionary["Alex"]` 返回键 `Alex` 对应的值。如果键在字典中不存在的话,则这个下标返回 `nil`。
|
||||
|
||||
字典中键的类型必须符合 Swift 标准库中的 `Hashable` 协议。
|
||||
|
||||
关于 Swift 标准库中 `Dictionary` 类型的详细讨论,请参阅 [字典](../chapter2/04_Collection_Types.html#dictionaries)。
|
||||
|
||||
> 字典类型语法
|
||||
<a name="dictionary-type"></a>
|
||||
> *字典类型* → **[** [*类型*](#type) **:** [*类型*](#type) **]**
|
||||
|
||||
<a name="optional_type"></a>
|
||||
## 可选类型
|
||||
|
||||
Swift 定义后缀 `?` 来作为标准库中的定义的命名型类型 `Optional<Wrapped>` 的语法糖。换句话说,下面两个声明是等价的:
|
||||
|
||||
```swift
|
||||
var optionalInteger: Int?
|
||||
var optionalInteger: Optional<Int>
|
||||
```
|
||||
|
||||
在上述两种情况下,变量 `optionalInteger` 都被声明为可选整型类型。注意在类型和 `?` 之间没有空格。
|
||||
|
||||
类型 `Optional<Wrapped>` 是一个枚举,有两个成员,`None` 和 `Some(Wrapped)`,用来表示可能有也可能没有的值。任意类型都可以被显式地声明(或隐式地转换)为可选类型。如果你在声明或定义可选变量或属性的时候没有提供初始值,它的值则会自动赋为默认值 `nil`。
|
||||
|
||||
如果一个可选类型的实例包含一个值,那么你就可以使用后缀运算符 `!` 来获取该值,正如下面描述的:
|
||||
|
||||
```swift
|
||||
optionalInteger = 42
|
||||
optionalInteger! // 42
|
||||
```
|
||||
|
||||
使用 `!` 运算符解包值为 `nil` 的可选值会导致运行错误。
|
||||
|
||||
你也可以使用可选链式调用和可选绑定来选择性地在可选表达式上执行操作。如果值为 `nil`,不会执行任何操作,因此也就没有运行错误产生。
|
||||
|
||||
更多细节以及更多如何使用可选类型的例子,请参阅 [可选类型](../chapter2/01_The_Basics.html#optionals)。
|
||||
|
||||
> 可选类型语法
|
||||
<a name="optional-type"></a>
|
||||
> *可选类型* → [*类型*](#type) **?**
|
||||
|
||||
<a name="implicitly_unwrapped_optional_type"></a>
|
||||
## 隐式解析可选类型
|
||||
|
||||
Swift 语言定义后缀 `!` 作为标准库中命名类型 `ImplicitlyUnwrappedOptional<Wrapped>` 的语法糖。换句话说,下面两个声明等价:
|
||||
|
||||
```swift
|
||||
var implicitlyUnwrappedString: String!
|
||||
var implicitlyUnwrappedString: ImplicitlyUnwrappedOptional<String>
|
||||
```
|
||||
|
||||
上述两种情况下,变量 `implicitlyUnwrappedString` 被声明为一个隐式解析可选类型的字符串。注意类型与 `!` 之间没有空格。
|
||||
|
||||
你可以在使用可选类型的地方使用隐式解析可选类型。比如,你可以将隐式解析可选类型的值赋给变量、常量和可选属性,反之亦然。
|
||||
|
||||
正如可选类型一样,你在声明隐式解析可选类型的变量或属性的时候也不用指定初始值,因为它有默认值 `nil`。
|
||||
|
||||
由于隐式解析可选类型的值会在使用时自动解析,所以没必要使用操作符 `!` 来解析它。也就是说,如果你使用值为 `nil` 的隐式解析可选类型,就会导致运行错误。
|
||||
|
||||
可以使用可选链式调用来在隐式解析可选表达式上选择性地执行操作。如果值为 `nil`,就不会执行任何操作,因此也不会产生运行错误。
|
||||
|
||||
关于隐式解析可选类型的更多细节,请参阅 [隐式解析可选类型](../chapter2/01_The_Basics.html#implicityly_unwrapped_optionals)。
|
||||
|
||||
> 隐式解析可选类型语法
|
||||
<a name="implicitly-unwrapped-optional-type"></a>
|
||||
> *隐式解析可选类型* → [*类型*](#type) **!**
|
||||
|
||||
<a name="protocol_composition_type"></a>
|
||||
## 协议合成类型
|
||||
|
||||
协议合成类型是一种符合协议列表中每个指定协议的类型。协议合成类型可能会用在类型注解和泛型参数中。
|
||||
|
||||
协议合成类型的形式如下:
|
||||
|
||||
> protocol<`Protocol 1`, `Procotol 2`>
|
||||
|
||||
协议合成类型允许你指定一个值,其类型符合多个协议的要求且不需要定义一个新的命名型协议来继承它想要符合的各个协议。比如,协议合成类型 `protocol<Protocol A, Protocol B, Protocol C>` 等效于一个从 `Protocol A`,`Protocol B`, `Protocol C` 继承而来的新协议 `Protocol D`,很显然这样做有效率的多,甚至不需引入一个新名字。
|
||||
|
||||
协议合成列表中的每项必须是协议名或协议合成类型的类型别名。如果列表为空,它就会指定一个空协议合成列表,每个类型都符合它。
|
||||
|
||||
> 协议合成类型语法
|
||||
<a name="protocol-composition-type"></a>
|
||||
> *协议合成类型* → **protocol** **<** [*协议标识符列表*](#protocol-identifier-list)<sub>可选</sub> **>**
|
||||
<a name="protocol-identifier-list"></a>
|
||||
> *协议标识符列表* → [*协议标识符*](#protocol-identifier) | [*协议标识符*](#protocol-identifier) **,** [*协议标识符列表*](#protocol-identifier-list)
|
||||
<a name="protocol-identifier"></a>
|
||||
> *协议标识符* → [*类型标识符*](#type-identifier)
|
||||
|
||||
<a name="metatype_type"></a>
|
||||
## 元类型
|
||||
|
||||
元类型是指类型的类型,包括类类型、结构体类型、枚举类型和协议类型。
|
||||
|
||||
类、结构体或枚举类型的元类型是相应的类型名紧跟 `.Type`。协议类型的元类型——并不是运行时符合该协议的具体类型——而是该协议名字紧跟 `.Protocol`。比如,类 `SomeClass` 的元类型就是 `SomeClass.Type`,协议 `SomeProtocol` 的元类型就是 `SomeProtocal.Protocol`。
|
||||
|
||||
你可以使用后缀 `self` 表达式来获取类型。比如,`SomeClass.self` 返回 `SomeClass` 本身,而不是 `SomeClass` 的一个实例。同样,`SomeProtocol.self` 返回 `SomeProtocol` 本身,而不是运行时符合 `SomeProtocol` 的某个类型的实例。还可以对类型的实例使用 `dynamicType` 表达式来获取该实例在运行阶段的类型,如下所示:
|
||||
|
||||
```swift
|
||||
class SomeBaseClass {
|
||||
class func printClassName() {
|
||||
println("SomeBaseClass")
|
||||
}
|
||||
}
|
||||
class SomeSubClass: SomeBaseClass {
|
||||
override class func printClassName() {
|
||||
println("SomeSubClass")
|
||||
}
|
||||
}
|
||||
let someInstance: SomeBaseClass = SomeSubClass()
|
||||
// someInstance 在编译期是 SomeBaseClass 类型,
|
||||
// 但是在运行期则是 SomeSubClass 类型
|
||||
someInstance.dynamicType.printClassName()
|
||||
// 打印 “SomeSubClass”
|
||||
```
|
||||
|
||||
可以使用恒等运算符(`===` 和 `!==`)来测试一个实例的运行时类型和它的编译时类型是否一致。
|
||||
|
||||
```swift
|
||||
if someInstance.dynamicType === SomeBaseClass.self {
|
||||
print("The dynamic type of someInstance is SomeBaseCass")
|
||||
} else {
|
||||
print("The dynamic type of someInstance isn't SomeBaseClass")
|
||||
}
|
||||
// 打印 “The dynamic type of someInstance isn't SomeBaseClass”
|
||||
```
|
||||
|
||||
可以使用初始化表达式从某个类型的元类型构造出一个该类型的实例。对于类实例,被调用的构造器必须使用 `required` 关键字标记,或者整个类使用 `final` 关键字标记。
|
||||
|
||||
```swift
|
||||
class AnotherSubClass: SomeBaseClass {
|
||||
let string: String
|
||||
required init(string: String) {
|
||||
self.string = string
|
||||
}
|
||||
override class func printClassName() {
|
||||
print("AnotherSubClass")
|
||||
}
|
||||
}
|
||||
let metatype: AnotherSubClass.Type = AnotherSubClass.self
|
||||
let anotherInstance = metatype.init(string: "some string")
|
||||
```
|
||||
|
||||
> 元类型语法
|
||||
<a name="metatype-type"></a>
|
||||
> *元类型* → [*类型*](#type) **.** **Type** | [*类型*](#type) **.** **Protocol**
|
||||
|
||||
<a name="type_inheritance_clause"></a>
|
||||
## 类型继承子句
|
||||
|
||||
类型继承子句被用来指定一个命名型类型继承自哪个类、采纳哪些协议。类型继承子句也用来指定一个类类型专属协议。类型继承子句开始于冒号 `:`,其后是类的超类或者一系列类型标识符。
|
||||
|
||||
类可以继承单个超类,采纳任意数量的协议。当定义一个类时,超类的名字必须出现在类型标识符列表首位,然后跟上该类需要采纳的任意数量的协议。如果一个类不是从其它类继承而来,那么列表可以以协议开头。关于类继承更多的讨论和例子,请参阅 [继承](../chapter2/13_Inheritance.html)。
|
||||
|
||||
其它命名型类型可能只继承或采纳一系列协议。协议类型可以继承自任意数量的其他协议。当一个协议类型继承自其它协议时,其它协议中定义的要求会被整合在一起,然后从当前协议继承的任意类型必须符合所有这些条件。正如在 [协议声明](05_Declarations.html#protocol_declaration) 中所讨论的那样,可以把 `class` 关键字放到协议类型的类型继承子句的首位,这样就可以声明一个类类型专属协议。
|
||||
|
||||
枚举定义中的类型继承子句可以是一系列协议,或是枚举的原始值类型的命名型类型。在枚举定义中使用类型继承子句来指定原始值类型的例子,请参阅 [原始值](../chapter2/08_Enumerations.html#raw_values)。
|
||||
|
||||
> 类型继承子句语法
|
||||
<a name="type_inheritance_clause"></a>
|
||||
> *类型继承子句* → **:** [*类要求*](#class-requirement) **,** [*类型继承列表*](#type-inheritance-list)
|
||||
> *类型继承子句* → **:** [*类要求*](#class-requirement)
|
||||
> *类型继承子句* → **:** [*类型继承列表*](#type-inheritance-list)
|
||||
<a name="type-inheritance-list"></a>
|
||||
> *类型继承列表* → [*类型标识符*](#type-identifier) | [*类型标识符*](#type-identifier) **,** [*类型继承列表*](#type-inheritance-list)
|
||||
<a name="class-requirement"></a>
|
||||
> *类要求* → **class**
|
||||
|
||||
<a name="type_inference"></a>
|
||||
## 类型推断
|
||||
|
||||
Swift 广泛使用类型推断,从而允许你省略代码中很多变量和表达式的类型或部分类型。比如,对于 `var x: Int = 0`,你可以完全省略类型而简写成 `var x = 0`,编译器会正确推断出 `x` 的类型 `Int`。类似的,当完整的类型可以从上下文推断出来时,你也可以省略类型的一部分。比如,如果你写了 `let dict: Dictionary = ["A" : 1]`,编译器能推断出 `dict` 的类型是 `Dictionary<String, Int>`。
|
||||
|
||||
在上面的两个例子中,类型信息从表达式树的叶子节点传向根节点。也就是说,`var x: Int = 0` 中 `x` 的类型首先根据 `0` 的类型进行推断,然后将该类型信息传递到根节点(变量 `x`)。
|
||||
|
||||
在 Swift 中,类型信息也可以反方向流动——从根节点传向叶子节点。在下面的例子中,常量 `eFloat` 上的显式类型注解(`: Float`)将导致数字字面量 `2.71828` 的类型是 `Float` 而非 `Double`。
|
||||
|
||||
```swift
|
||||
let e = 2.71828 // e 的类型会被推断为 Double
|
||||
let eFloat: Float = 2.71828 // eFloat 的类型为 Float
|
||||
```
|
||||
|
||||
Swift 中的类型推断在单独的表达式或语句上进行。这意味着所有用于类型推断的信息必须可以从表达式或其某个子表达式的类型检查中获取到。
|
||||
@ -1,871 +0,0 @@
|
||||
# 表达式(Expressions)
|
||||
-----------------
|
||||
|
||||
> 1.0
|
||||
> 翻译:[sg552](https://github.com/sg552)
|
||||
> 校对:[numbbbbb](https://github.com/numbbbbb), [stanzhai](https://github.com/stanzhai)
|
||||
|
||||
> 2.0
|
||||
> 翻译+校对:[EudeMorgen](https://github.com/EudeMorgen)
|
||||
|
||||
> 2.1
|
||||
> 翻译:[mmoaay](https://github.com/mmoaay)
|
||||
|
||||
> 2.2
|
||||
> 校对:[175](https://github.com/Brian175)
|
||||
|
||||
本页包含内容:
|
||||
|
||||
- [前缀表达式](#prefix_expressions)
|
||||
- [try 运算符](#try_operator)
|
||||
- [二元表达式](#binary_expressions)
|
||||
- [赋值表达式](#assignment_operator)
|
||||
- [三元条件运算符](#ternary_conditional_operator)
|
||||
- [类型转换运算符](#type-casting_operators)
|
||||
- [基本表达式](#primary_expressions)
|
||||
- [字面量表达式](#literal_expression)
|
||||
- [self 表达式](#self_expression)
|
||||
- [超类表达式](#superclass_expression)
|
||||
- [闭包表达式](#closure_expression)
|
||||
- [隐式成员表达式](#implicit_member_expression)
|
||||
- [圆括号表达式](#parenthesized_expression)
|
||||
- [通配符表达式](#wildcard_expression)
|
||||
- [选择器表达式](#selector_expression)
|
||||
- [后缀表达式](#postfix_expressions)
|
||||
- [函数调用表达式](#function_call_expression)
|
||||
- [构造器表达式](#initializer_expression)
|
||||
- [显式成员表达式](#explicit_member_expression)
|
||||
- [后缀 self 表达式](#postfix_self_expression)
|
||||
- [dynamicType 表达式](#dynamic_type_expression)
|
||||
- [下标表达式](#subscript_expression)
|
||||
- [强制取值表达式](#forced-Value_expression)
|
||||
- [可选链表达式](#optional-chaining_expression)
|
||||
|
||||
Swift 中存在四种表达式:前缀表达式,二元表达式,基本表达式和后缀表达式。表达式在返回一个值的同时还可以引发副作用。
|
||||
|
||||
通过前缀表达式和二元表达式可以对简单表达式使用各种运算符。基本表达式从概念上讲是最简单的一种表达式,它是一种访问值的方式。后缀表达式则允许你建立复杂的表达式,例如函数调用和成员访问。每种表达式都在下面有详细论述。
|
||||
|
||||
> 表达式语法
|
||||
<a name="expression"></a>
|
||||
> *表达式* → [*try运算符*](#try-operator)<sub>可选</sub> [*前缀表达式*](#prefix-expression) [*二元表达式列表*](#binary-expressions)<sub>可选</sub>
|
||||
<a name="expression-list"></a>
|
||||
> *表达式列表* → [*表达式*](#expression) | [*表达式*](#expression) **,** [*表达式列表*](#expression-list)
|
||||
|
||||
<a name="prefix_expressions"></a>
|
||||
## 前缀表达式
|
||||
|
||||
前缀表达式由可选的前缀运算符和表达式组成。前缀运算符只接收一个参数,表达式则紧随其后。
|
||||
|
||||
关于这些运算符的更多信息,请参阅 [基本运算符](../chapter2/02_Basic_Operators.md) 和 [高级运算符](../chapter2/25_Advanced_Operators.md)。
|
||||
|
||||
关于 Swift 标准库提供的运算符的更多信息,请参阅 [*Swift Standard Library Operators Reference*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_StandardLibrary_Operators/index.html#//apple_ref/doc/uid/TP40016054)。
|
||||
|
||||
除了标准库运算符,你也可以对某个变量使用 `&` 运算符,从而将其传递给函数的输入输出参数。更多信息,请参阅 [输入输出参数](../chapter2/06_Functions.html#in_out_parameters)。
|
||||
|
||||
> 前缀表达式语法
|
||||
<a name="prefix-expression"></a>
|
||||
> *前缀表达式* → [*前缀运算符*](02_Lexical_Structure.md#prefix-operator)<sub>可选</sub> [*后缀表达式*](#postfix-expression)
|
||||
> *前缀表达式* → [*输入输出表达式*](#in-out-expression)
|
||||
<a name="in-out-expression"></a>
|
||||
> *输入输出表达式* → **&** [*标识符*](02_Lexical_Structure.md#identifier)
|
||||
|
||||
<a name="try_operator"></a>
|
||||
### try 运算符
|
||||
|
||||
try 表达式由 `try` 运算符加上紧随其后的可抛出错误的表达式组成,形式如下:
|
||||
|
||||
> try `可抛出错误的表达式`
|
||||
|
||||
可选的 try 表达式由 `try?` 运算符加上紧随其后的可抛出错误的表达式组成,形式如下:
|
||||
|
||||
> try? `可抛出错误的表达式`
|
||||
|
||||
如果可抛出错误的表达式没有抛出错误,整个表达式返回的可选值将包含可抛出错误的表达式的返回值,否则,该可选值为 `nil`。
|
||||
|
||||
强制的 try 表达式由 `try!` 运算符加上紧随其后的可抛出错误的表达式组成,形式如下:
|
||||
|
||||
> try! `可抛出错误的表达式`
|
||||
|
||||
如果可抛出错误的表达式抛出了错误,将会引发运行时错误。
|
||||
|
||||
在二进制运算符左侧的表达式被标记上 `try`、`try?` 或者 `try!` 时,这个运算符对整个二进制表达式都产生作用。也就是说,你可以使用括号来明确运算符的作用范围。
|
||||
|
||||
```swift
|
||||
sum = try someThrowingFunction() + anotherThrowingFunction() // try 对两个函数调用都产生作用
|
||||
sum = try (someThrowingFunction() + anotherThrowingFunction()) // try 对两个函数调用都产生作用
|
||||
sum = (try someThrowingFunction()) + anotherThrowingFunction() // 错误:try 只对第一个函数调用产生作用
|
||||
```
|
||||
|
||||
`try` 表达式不能出现在二进制运算符的的右侧,除非二进制运算符是赋值运算符或者 `try` 表达式是被圆括号括起来的。
|
||||
|
||||
关于 `try`、`try?` 和 `try!` 的更多信息,以及该如何使用的例子,请参阅 [错误处理](../chapter2/18_Error_Handling.html)。
|
||||
> try 表达式语法
|
||||
<a name="try-operator"></a>
|
||||
> *try 运算符* → **try** | **try?** | **try!**
|
||||
|
||||
<a name="binary_expressions"></a>
|
||||
## 二元表达式
|
||||
|
||||
二元表达式由中缀运算符和左右参数表达式组成。形式如下:
|
||||
|
||||
> `左侧参数` `二元运算符` `右侧参数`
|
||||
|
||||
关于这些运算符的更多信息,请参阅 [基本运算符](../chapter2/02_Basic_Operators.md) 和 [高级运算符](../chapter2/25_Advanced_Operators.md)。
|
||||
|
||||
关于 Swift 标准库提供的运算符的更多信息,请参阅 [*Swift Standard Library Operators Reference*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_StandardLibrary_Operators/index.html#//apple_ref/doc/uid/TP40016054)。
|
||||
|
||||
> 注意
|
||||
> 在解析时,一个二元表达式将作为一个扁平列表表示,然后根据运算符的优先级,再进一步进行组合。例如,`2 + 3 * 5` 首先被看作具有五个元素的列表,即 `2`、`+`、`3`、`*`、`5`,随后根据运算符优先级组合为 `(2 + (3 * 5))`。
|
||||
|
||||
<a name="binary-expression"></a>
|
||||
> 二元表达式语法
|
||||
> *二元表达式* → [*二元运算符*](02_Lexical_Structure.md#binary-operator) [*前缀表达式*](#prefix-expression)
|
||||
> *二元表达式* → [*赋值运算符*](#assignment-operator) [*try运算符*](#try-operator)<sub>可选</sub> [*前缀表达式*](#prefix-expression)
|
||||
> *二元表达式* → [*条件运算符*](#conditional-operator) [*try运算符*](#try-operator)<sub>可选</sub> [*前缀表达式*](#prefix-expression)
|
||||
> *二元表达式* → [*类型转换运算符*](#type-casting-operator)
|
||||
<a name="binary-expressions"></a>
|
||||
> *二元表达式列表* → [*二元表达式*](#binary-expression) [*二元表达式列表*](#binary-expressions)<sub>可选</sub>
|
||||
|
||||
<a name="assignment_operator"></a>
|
||||
### 赋值表达式
|
||||
|
||||
赋值表达式会为某个给定的表达式赋值,形式如下;
|
||||
|
||||
> `表达式` = `值`
|
||||
|
||||
右边的值会被赋值给左边的表达式。如果左边表达式是一个元组,那么右边必须是一个具有同样元素个数的元组。(嵌套元组也是允许的。)右边的值中的每一部分都会被赋值给左边的表达式中的相应部分。例如:
|
||||
|
||||
```swift
|
||||
(a, _, (b, c)) = ("test", 9.45, (12, 3))
|
||||
// a 为 "test",b 为 12,c 为 3,9.45 会被忽略
|
||||
```
|
||||
|
||||
赋值运算符不返回任何值。
|
||||
|
||||
> 赋值运算符语法
|
||||
<a name="assignment-operator"></a>
|
||||
> *赋值运算符* → **=**
|
||||
|
||||
<a name="ternary_conditional_operator"></a>
|
||||
### 三元条件运算符
|
||||
|
||||
三元条件运算符会根据条件来对两个给定表达式中的一个进行求值,形式如下:
|
||||
|
||||
> `条件` ? `表达式(条件为真则使用)` : `表达式(条件为假则使用)`
|
||||
|
||||
如果条件为真,那么对第一个表达式进行求值并返回结果。否则,对第二个表达式进行求值并返回结果。未使用的表达式不会进行求值。
|
||||
|
||||
关于使用三元条件运算符的例子,请参阅 [三元条件运算符](../chapter2/02_Basic_Operators.md#ternary_conditional_operator)。
|
||||
|
||||
> 三元条件运算符语法
|
||||
<a name="conditional-operator"></a>
|
||||
> *三元条件运算符* → **?** [try运算符](#try-operator)<sub>可选</sub> [*表达式*](#expression) **:**
|
||||
|
||||
<a name="type-casting_operators"></a>
|
||||
### 类型转换运算符
|
||||
|
||||
有 4 种类型转换运算符:`is`、`as`、`as? `和`as!`。它们有如下的形式:
|
||||
|
||||
> `表达式` is `类型`
|
||||
`表达式` as `类型`
|
||||
`表达式` as? `类型`
|
||||
`表达式` as! `类型`
|
||||
|
||||
`is` 运算符在运行时检查表达式能否向下转化为指定的类型,如果可以则返回 `ture`,否则返回 `false`。
|
||||
|
||||
`as` 运算符在编译时执行向上转换和桥接。向上转换可将表达式转换成超类的实例而无需使用任何中间变量。以下表达式是等价的:
|
||||
|
||||
```swift
|
||||
func f(any: Any) { print("Function for Any") }
|
||||
func f(int: Int) { print("Function for Int") }
|
||||
let x = 10
|
||||
f(x)
|
||||
// 打印 “Function for Int”
|
||||
|
||||
let y: Any = x
|
||||
f(y)
|
||||
// 打印 “Function for Any”
|
||||
|
||||
f(x as Any)
|
||||
// 打印 “Function for Any”
|
||||
```
|
||||
|
||||
桥接可将 Swift 标准库中的类型(例如 `String`)作为一个与之相关的 Foundation 类型(例如 `NSString`)来使用,而不需要新建一个实例。关于桥接的更多信息,请参阅 [*Using Swift with Cocoa and Objective-C (Swift2.2)*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 中的 [Working with Cocoa Data Types](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html#//apple_ref/doc/uid/TP40014216-CH6)。
|
||||
|
||||
`as?` 运算符有条件地执行类型转换,返回目标类型的可选值。在运行时,如果转换成功,返回的可选值将包含转换后的值,否则返回 `nil`。如果在编译时就能确定转换一定会成功或是失败,则会导致编译报错。
|
||||
|
||||
`as!` 运算符执行强制类型转换,返回目标类型的非可选值。如果转换失败,则会导致运行时错误。表达式 `x as! T` 效果等同于 `(x as? T)!`。
|
||||
|
||||
关于类型转换的更多内容和例子,请参阅 [类型转换](../chapter2/19_Type_Casting.md)。
|
||||
|
||||
<a name="type-casting-operator"></a>
|
||||
> 类型转换运算符语法
|
||||
> *类型转换运算符* → **is** [*类型*](03_Types.md#type)
|
||||
> *类型转换运算符* → **as** [*类型*](03_Types.md#type)
|
||||
> *类型转换运算符* → **as** **?** [*类型*](03_Types.md#type)
|
||||
> *类型转换运算符* → **as** **!** [*类型*](03_Types.md#type)
|
||||
|
||||
<a name="primary_expressions"></a>
|
||||
## 基本表达式
|
||||
|
||||
基本表达式是最基本的表达式。它们可以单独使用,也可以跟前缀表达式、二元表达式、后缀表达式组合使用。
|
||||
|
||||
> 基本表达式语法
|
||||
<a name="primary-expression"></a>
|
||||
> *基本表达式* → [*标识符*](02_Lexical_Structure.md#identifier) [*泛型实参子句*](08_Generic_Parameters_and_Arguments.md#generic-argument-clause)<sub>可选</sub>
|
||||
> *基本表达式* → [*字面量表达式*](#literal-expression)
|
||||
> *基本表达式* → [*self表达式*](#self-expression)
|
||||
> *基本表达式* → [*超类表达式*](#superclass-expression)
|
||||
> *基本表达式* → [*闭包表达式*](#closure-expression)
|
||||
> *基本表达式* → [*圆括号表达式*](#parenthesized-expression)
|
||||
> *基本表达式* → [*隐式成员表达式*](#implicit-member-expression)
|
||||
> *基本表达式* → [*通配符表达式*](#wildcard-expression)
|
||||
> *基本表达式* → [*选择器表达式*](#selector-expression)
|
||||
|
||||
<a name="literal_expression"></a>
|
||||
### 字面量表达式
|
||||
|
||||
字面量表达式可由普通字面量(例如字符串或者数字),字典或者数组字面量,或者下面列表中的特殊字面量组成:
|
||||
|
||||
字面量 | 类型 | 值
|
||||
:------------- | :---------- | :----------
|
||||
`#file` | `String` | 所在的文件名
|
||||
`#line` | `Int` | 所在的行数
|
||||
`#column` | `Int` | 所在的列数
|
||||
`#function` | `String` | 所在的声明的名字
|
||||
|
||||
`#line`除了上述含义外,还有另一种含义。当它出现在单独一行时,会被理解成行控制语句,请参阅[线路控制语句](../chapter3/10_Statements.md#线路控制语句)。
|
||||
|
||||
对于 `#function`,在函数中会返回当前函数的名字,在方法中会返回当前方法的名字,在属性的存取器中会返回属性的名字,在特殊的成员如 `init` 或 `subscript` 中会返回这个关键字的名字,在某个文件中会返回当前模块的名字。
|
||||
|
||||
`#function` 作为函数或者方法的默认参数值时,该字面量的值取决于函数或方法的调用环境。
|
||||
|
||||
```swift
|
||||
func logFunctionName(string: String = #function) {
|
||||
print(string)
|
||||
}
|
||||
func myFunction() {
|
||||
logFunctionName()
|
||||
}
|
||||
myFunction() // 打印 “myFunction()”
|
||||
```
|
||||
|
||||
数组字面量是值的有序集合,形式如下:
|
||||
|
||||
> [`值 1`, `值 2`, `...`]
|
||||
|
||||
数组中的最后一个表达式可以紧跟一个逗号。数组字面量的类型是 `[T]`,这个 `T` 就是数组中元素的类型。如果数组中包含多种类型,`T` 则是跟这些类型最近的的公共父类型。空数组字面量由一组方括号定义,可用来创建特定类型的空数组。
|
||||
|
||||
```swift
|
||||
var emptyArray: [Double] = []
|
||||
```
|
||||
|
||||
字典字面量是一个包含无序键值对的集合,形式如下:
|
||||
|
||||
> [`键 1` : `值 1`, `键 2` : `值 2`, `...`]
|
||||
|
||||
字典中的最后一个表达式可以紧跟一个逗号。字典字面量的类型是 `[Key : Value]`,`Key` 表示键的类型,`Value` 表示值的类型。如果字典中包含多种类型,那么 `Key` 表示的类型则为所有键最接近的公共父类型,`Value` 与之相似。一个空的字典字面量由方括号中加一个冒号组成(`[:]`),从而与空数组字面量区分开,可以使用空字典字面量来创建特定类型的字典。
|
||||
|
||||
```swift
|
||||
var emptyDictionary: [String : Double] = [:]
|
||||
```
|
||||
|
||||
> 字面量表达式语法
|
||||
|
||||
<a name="literal-expression"></a>
|
||||
> *字面量表达式* → [*字面量*](02_Lexical_Structure.md#literal)
|
||||
> *字面量表达式* → [*数组字面量*](#array-literal) | [*字典字面量*](#dictionary-literal)
|
||||
> *字面量表达式* → **#file** | **#line** | **#column** | **#function**
|
||||
|
||||
<a name="array-literal"></a>
|
||||
> *数组字面量* → **[** [*数组字面量项列表*](#array-literal-items)<sub>可选</sub> **]**
|
||||
<a name="array-literal-items"></a>
|
||||
> *数组字面量项列表* → [*数组字面量项*](#array-literal-item) **,**<sub>可选</sub> | [*数组字面量项*](#array-literal-item) **,** [*数组字面量项列表*](#array-literal-items)
|
||||
<a name="array-literal-item"></a>
|
||||
> *数组字面量项* → [*表达式*](#expression)
|
||||
|
||||
<a name="dictionary-literal"></a>
|
||||
> *字典字面量* → **[** [*字典字面量项列表*](#dictionary-literal-items) **]** | **[** **:** **]**
|
||||
<a name="dictionary-literal-items"></a>
|
||||
> *字典字面量项列表* → [*字典字面量项*](#dictionary-literal-item) **,**<sub>可选</sub> | [*字典字面量项*](#dictionary-literal-item) **,** [*字典字面量项列表*](#dictionary-literal-items)
|
||||
<a name="dictionary-literal-item"></a>
|
||||
> *字典字面量项* → [*表达式*](#expression) **:** [*表达式*](#expression)
|
||||
|
||||
<a name="self_expression"></a>
|
||||
### self 表达式
|
||||
|
||||
`self` 表达式是对当前类型或者当前实例的显式引用,它有如下形式:
|
||||
|
||||
> self
|
||||
> self.`成员名称`
|
||||
> self[`下标索引`]
|
||||
> self(`构造器参数`)
|
||||
> self.init(`构造器参数`)
|
||||
|
||||
如果在构造器、下标、实例方法中,`self` 引用的是当前类型的实例。在一个类型方法中,`self` 引用的是当前的类型。
|
||||
|
||||
当访问成员时,`self` 可用来区分重名变量,例如函数的参数:
|
||||
|
||||
```swift
|
||||
class SomeClass {
|
||||
var greeting: String
|
||||
init(greeting: String) {
|
||||
self.greeting = greeting
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在 `mutating` 方法中,你可以对 `self` 重新赋值:
|
||||
|
||||
```swift
|
||||
struct Point {
|
||||
var x = 0.0, y = 0.0
|
||||
mutating func moveByX(deltaX: Double, y deltaY: Double) {
|
||||
self = Point(x: x + deltaX, y: y + deltaY)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> self 表达式语法
|
||||
<a name="self-expression"></a>
|
||||
> *self 表达式* → **self** | [*self 方法表达式*](#self-method-expression) | [*self 下标表达式*](#self-subscript-expression) | [*self 构造器表达式*](#self-initializer-expression)
|
||||
>
|
||||
<a name="self-method-expression"></a>
|
||||
> *self 方法表达式* → **self** **.** [*标识符*](02_Lexical_Structure.md#identifier)
|
||||
<a name="self-subscript-expression"></a>
|
||||
> *self 下标表达式* → **self** **[** [*表达式*](#expression) **]**
|
||||
<a name="self-initializer-expression"></a>
|
||||
> *self 构造器表达式* → **self** **.** **init**
|
||||
|
||||
<a name="superclass_expression"></a>
|
||||
### 超类表达式
|
||||
|
||||
超类表达式可以使我们在某个类中访问它的超类,它有如下形式:
|
||||
|
||||
> super.`成员名称`
|
||||
> super[`下标索引`]
|
||||
> super.init(`构造器参数`)
|
||||
|
||||
第一种形式用来访问超类的某个成员,第二种形式用来访问超类的下标,第三种形式用来访问超类的构造器。
|
||||
|
||||
子类可以通过超类表达式在它们的成员、下标和构造器中使用超类中的实现。
|
||||
|
||||
> 超类表达式语法
|
||||
<a name="superclass-expression"></a>
|
||||
> *超类表达式* → [*超类方法表达式*](#superclass-method-expression) | [*超类下标表达式*](#superclass-subscript-expression) | [*超类构造器表达式*](#superclass-initializer-expression)
|
||||
<a name="superclass-method-expression"></a>
|
||||
> *超类方法表达式* → **super** **.** [*标识符*](02_Lexical_Structure.md#identifier)
|
||||
<a name="superclass-subscript-expression"></a>
|
||||
> *超类下标表达式* → **super** **[** [*表达式*](#expression) **]**
|
||||
<a name="superclass-initializer-expression"></a>
|
||||
> *超类构造器表达式* → **super** **.** **init**
|
||||
|
||||
<a name="closure_expression"></a>
|
||||
### 闭包表达式
|
||||
|
||||
闭包表达式会创建一个闭包,在其他语言中也叫 *lambda* 或匿名函数。跟函数一样,闭包包含了待执行的代码,不同的是闭包还会捕获所在环境中的常量和变量。它的形式如下:
|
||||
|
||||
```swift
|
||||
{ (parameters) -> return type in
|
||||
statements
|
||||
}
|
||||
```
|
||||
|
||||
闭包的参数声明形式跟函数一样,请参阅 [函数声明](05_Declarations.md#function_declaration)。
|
||||
|
||||
闭包还有几种特殊的形式,能让闭包使用起来更加简洁:
|
||||
|
||||
- 闭包可以省略它的参数和返回值的类型。如果省略了参数名和所有的类型,也要省略 `in` 关键字。如果被省略的类型无法被编译器推断,那么就会导致编译错误。
|
||||
- 闭包可以省略参数名,参数会被隐式命名为 `$` 加上其索引位置,例如 `$0`、`$1`、`$2` 分别表示第一个、第二个、第三个参数,以此类推。
|
||||
- 如果闭包中只包含一个表达式,那么该表达式的结果就会被视为闭包的返回值。表达式结果的类型也会被推断为闭包的返回类型。
|
||||
|
||||
下面几个闭包表达式是等价的:
|
||||
|
||||
```swift
|
||||
myFunction {
|
||||
(x: Int, y: Int) -> Int in
|
||||
return x + y
|
||||
}
|
||||
|
||||
myFunction {
|
||||
(x, y) in
|
||||
return x + y
|
||||
}
|
||||
|
||||
myFunction { return $0 + $1 }
|
||||
|
||||
myFunction { $0 + $1 }
|
||||
```
|
||||
|
||||
关于如何将闭包作为参数来传递的内容,请参阅 [函数调用表达式](#function_call_expression)。
|
||||
|
||||
#### 捕获列表
|
||||
|
||||
默认情况下,闭包会捕获附近作用域中的常量和变量,并使用强引用指向它们。你可以通过一个捕获列表来显式指定它的捕获行为。
|
||||
|
||||
捕获列表在参数列表之前,由中括号括起来,里面是由逗号分隔的一系列表达式。一旦使用了捕获列表,就必须使用 `in` 关键字,即使省略了参数名、参数类型和返回类型。
|
||||
|
||||
捕获列表中的项会在闭包创建时被初始化。每一项都会用闭包附近作用域中的同名常量或者变量的值初始化。例如下面的代码示例中,捕获列表包含 `a` 而不包含 `b`,这将导致这两个变量具有不同的行为。
|
||||
|
||||
```swift
|
||||
var a = 0
|
||||
var b = 0
|
||||
let closure = { [a] in
|
||||
print(a, b)
|
||||
}
|
||||
|
||||
a = 10
|
||||
b = 10
|
||||
closure()
|
||||
// 打印 “0 10”
|
||||
```
|
||||
|
||||
在示例中,变量 `b` 只有一个,然而,变量 `a` 有两个,一个在闭包外,一个在闭包内。闭包内的变量 `a` 会在闭包创建时用闭包外的变量 `a` 的值来初始化,除此之外它们并无其他联系。这意味着在闭包创建后,改变某个 `a` 的值都不会对另一个 `a` 的值造成任何影响。与此相反,闭包内外都是同一个变量 `b`,因此在闭包外改变其值,闭包内的值也会受影响。
|
||||
|
||||
如果闭包捕获的值具有引用语义则有所不同。例如,下面示例中,有两个变量 `x`,一个在闭包外,一个在闭包内,由于它们的值是引用语义,虽然这是两个不同的变量,它们却都引用着同一实例。
|
||||
|
||||
```swift
|
||||
class SimpleClass {
|
||||
var value: Int = 0
|
||||
}
|
||||
var x = SimpleClass()
|
||||
var y = SimpleClass()
|
||||
let closure = { [x] in
|
||||
print(x.value, y.value)
|
||||
}
|
||||
|
||||
x.value = 10
|
||||
y.value = 10
|
||||
closure()
|
||||
// 打印 “10 10”
|
||||
```
|
||||
|
||||
如果捕获列表中的值是类类型,你可以使用 `weak` 或者 `unowned` 来修饰它,闭包会分别用弱引用和无主引用来捕获该值。
|
||||
|
||||
```swift
|
||||
myFunction { print(self.title) } // 以强引用捕获
|
||||
myFunction { [weak self] in print(self!.title) } // 以弱引用捕获
|
||||
myFunction { [unowned self] in print(self.title) } // 以无主引用捕获
|
||||
```
|
||||
|
||||
在捕获列表中,也可以将任意表达式的值绑定到一个常量上。该表达式会在闭包被创建时进行求值,闭包会按照指定的引用类型来捕获表达式的值。例如:
|
||||
|
||||
```swift
|
||||
// 以弱引用捕获 self.parent 并赋值给 parent
|
||||
myFunction { [weak parent = self.parent] in print(parent!.title) }
|
||||
```
|
||||
|
||||
关于闭包表达式的更多信息和例子,请参阅 [闭包表达式](../chapter2/07_Closures.md#closure_expressions)。关于捕获列表的更多信息和例子,请参阅 [解决闭包引起的循环强引用](../chapter2/16_Automatic_Reference_Counting.md#resolving_strong_reference_cycles_for_closures)。
|
||||
|
||||
> 闭包表达式语法
|
||||
|
||||
<a name="closure-expression"></a>
|
||||
> *闭包表达式* → **{** [*闭包签名*](#closure-signature)<sub>可选</sub> [*语句*](10_Statements.md#statements) **}**
|
||||
|
||||
<a name="closure-signature"></a>
|
||||
> *闭包签名* → [*参数子句*](05_Declarations.md#parameter-clause) [*函数结果*](05_Declarations.md#function-result)<sub>可选</sub> **in**
|
||||
> *闭包签名* → [*标识符列表*](02_Lexical_Structure.md#identifier-list) [*函数结果*](05_Declarations.md#function-result)<sub>可选</sub> **in**
|
||||
> *闭包签名* → [*捕获列表*](#capture-list) [*参数子句*](05_Declarations.md#parameter-clause) [*函数结果*](05_Declarations.md#function-result)<sub>可选</sub> **in**
|
||||
> *闭包签名* → [*捕获列表*](#capture-list) [*标识符列表*](02_Lexical_Structure.md#identifier-list) [*函数结果*](05_Declarations.md#function-result)<sub>可选</sub> **in**
|
||||
> *闭包签名* → [*捕获列表*](#capture-list) **in**
|
||||
|
||||
<a name="capture-list"></a>
|
||||
> *捕获列表* → **[** [*捕获列表项列表*](#capture-list-items) **]**
|
||||
<a name="capture-list-items"></a>
|
||||
> *捕获列表项列表* → [*捕获列表项*](#capture-list-item) | [*捕获列表项*](#capture-list-item) **,** [*捕获列表项列表*](#capture-list-items)
|
||||
<a name="capture-list-item"></a>
|
||||
> *捕获列表项* → [*捕获说明符*](#capture-specifier)<sub>可选</sub> [*表达式*](#expression)
|
||||
<a name="capture-specifier"></a>
|
||||
> *捕获说明符* → **weak** | **unowned** | **unowned(safe)** | **unowned(unsafe)**
|
||||
|
||||
<a name="implicit_member_expression"></a>
|
||||
### 隐式成员表达式
|
||||
|
||||
若类型可被推断出来,可以使用隐式成员表达式来访问某个类型的成员(例如某个枚举成员或某个类型方法),形式如下:
|
||||
|
||||
> .`成员名称`
|
||||
|
||||
例如:
|
||||
|
||||
```swift
|
||||
var x = MyEnumeration.SomeValue
|
||||
x = .AnotherValue
|
||||
```
|
||||
|
||||
> 隐式成员表达式语法
|
||||
<a name="implicit-member-expression"></a>
|
||||
> *隐式成员表达式* → **.** [*标识符*](02_Lexical_Structure.md#identifier)
|
||||
|
||||
<a name="parenthesized_expression"></a>
|
||||
### 圆括号表达式
|
||||
|
||||
圆括号表达式由圆括号和其中多个逗号分隔的子表达式组成。每个子表达式前面可以有一个标识符,用冒号隔开。圆括号表达式形式如下:
|
||||
|
||||
> (`标识符 1` : `表达式 1`, `标识符 2` : `表达式 2`, `...`)
|
||||
|
||||
使用圆括号表达式来创建元组,然后将其作为参数传递给函数。如果某个圆括号表达式中只有一个子表达式,那么它的类型就是子表达式的类型。例如,表达式 `(1)` 的类型是 `Int`,而不是 `(Int)`。
|
||||
|
||||
> 圆括号表达式语法
|
||||
<a name="parenthesized-expression"></a>
|
||||
> *圆括号表达式* → **(** [*表达式元素列表*](#expression-element-list)<sub>可选</sub> **)**
|
||||
<a name="expression-element-list"></a>
|
||||
> *表达式元素列表* → [*表达式元素*](#expression-element) | [*表达式元素*](#expression-element) **,** [*表达式元素列表*](#expression-element-list)
|
||||
<a name="expression-element"></a>
|
||||
> *表达式元素* → [*表达式*](#expression) | [*标识符*](02_Lexical_Structure.md#identifier) **:** [*表达式*](#expression)
|
||||
|
||||
<a name="wildcard_expression"></a>
|
||||
### 通配符表达式
|
||||
|
||||
通配符表达式可以在赋值过程中显式忽略某个值。例如下面的代码中,`10` 被赋值给 `x`,而 `20` 则被忽略:
|
||||
|
||||
```swift
|
||||
(x, _) = (10, 20)
|
||||
// x 为 10,20 被忽略
|
||||
```
|
||||
|
||||
> 通配符表达式语法
|
||||
<a name="wildcard-expression"></a>
|
||||
> *通配符表达式* → **_**
|
||||
|
||||
<a name="selector_expression"></a>
|
||||
### 选择器表达式
|
||||
|
||||
可通过选择器表达式来获取引用 Objective-C 方法的选择器。
|
||||
|
||||
> \#selector(方法名)
|
||||
|
||||
方法名必须是存在于 Objective-C 运行时中的方法的引用。选择器表达式的返回值是一个 `Selector` 类型的实例。例如:
|
||||
|
||||
```swift
|
||||
class SomeClass: NSObject {
|
||||
@objc(doSomethingWithInt:)
|
||||
func doSomething(x: Int) { }
|
||||
}
|
||||
let x = SomeClass()
|
||||
let selector = #selector(x.doSomething(_:))
|
||||
```
|
||||
|
||||
具有相同方法名但类型不同的方法可以使用 `as` 操作符来区分:
|
||||
|
||||
```swift
|
||||
extension SomeClass {
|
||||
@objc(doSomethingWithString:)
|
||||
func doSomething(x: String) { }
|
||||
}
|
||||
let anotherSelector = #selector(x.doSomething(_:) as (String) -> Void)
|
||||
```
|
||||
|
||||
由于选择器是在编译时创建的,因此编译器可以检查方法是否存在,以及方法是否在运行时暴露给了 Objective-C 。
|
||||
|
||||
> 注意
|
||||
> 虽然方法名是个表达式,但是它不会被求值。
|
||||
|
||||
更多关于如何在 Swift 代码中使用选择器来与 Objective-C API 进行交互的信息,请参阅 [*Using Swift with Cocoa and Objective-C*](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216) 中的 [*Objective-C Selectors*](https://developer.apple.com/library/ios/documentation/Swift/Conceptual/BuildingCocoaApps/InteractingWithObjective-CAPIs.html#//apple_ref/doc/uid/TP40014216-CH4-ID59) 部分。
|
||||
|
||||
> 选择器表达式语法
|
||||
<a name="selector-expression"></a>
|
||||
> *选择器表达式* → __#selector__ **(** [*表达式*](#expression) **)**
|
||||
|
||||
<a name="postfix_expressions"></a>
|
||||
## 后缀表达式
|
||||
|
||||
后缀表达式就是在某个表达式的后面运用后缀运算符或其他后缀语法。从语法构成上来看,基本表达式也是后缀表达式。
|
||||
|
||||
关于这些运算符的更多信息,请参阅 [基本运算符](../chapter2/02_Basic_Operators.md) 和 [高级运算符](../chapter2/25_Advanced_Operators.md)。
|
||||
|
||||
关于 Swift 标准库提供的运算符的更多信息,请参阅 [*Swift Standard Library Operators Reference*](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Reference/Swift_StandardLibrary_Operators/index.html#//apple_ref/doc/uid/TP40016054)。
|
||||
|
||||
> 后缀表达式语法
|
||||
<a name="postfix-expression"></a>
|
||||
> *后缀表达式* → [*基本表达式*](#primary-expression)
|
||||
> *后缀表达式* → [*后缀表达式*](#postfix-expression) [*后缀运算符*](02_Lexical_Structure.md#postfix-operator)
|
||||
> *后缀表达式* → [*函数调用表达式*](#function-call-expression)
|
||||
> *后缀表达式* → [*构造器表达式*](#initializer-expression)
|
||||
> *后缀表达式* → [*显式成员表达式*](#explicit-member-expression)
|
||||
> *后缀表达式* → [*后缀 self 表达式*](#postfix-self-expression)
|
||||
> *后缀表达式* → [*dynamicType 表达式*](#dynamic-type-expression)
|
||||
> *后缀表达式* → [*下标表达式*](#subscript-expression)
|
||||
> *后缀表达式* → [*强制取值表达式*](#forced-value-expression)
|
||||
> *后缀表达式* → [*可选链表达式*](#optional-chaining-expression)
|
||||
|
||||
<a name="function_call_expression"></a>
|
||||
### 函数调用表达式
|
||||
|
||||
函数调用表达式由函数名和参数列表组成,形式如下:
|
||||
|
||||
> `函数名`(`参数 1`, `参数 2`)
|
||||
|
||||
函数名可以是值为函数类型的任意表达式。
|
||||
|
||||
如果函数声明中指定了参数的名字,那么在调用的时候也必须得写出来。这种函数调用表达式具有以下形式:
|
||||
|
||||
> `函数名`(`参数名 1`: `参数 1`, `参数名 2`: `参数 2`)
|
||||
|
||||
如果函数的最后一个参数是函数类型,可以在函数调用表达式的尾部(右圆括号之后)加上一个闭包,该闭包会作为函数的最后一个参数。如下两种写法是等价的:
|
||||
|
||||
```swift
|
||||
// someFunction 接受整数和闭包参数
|
||||
someFunction(x, f: {$0 == 13})
|
||||
someFunction(x) {$0 == 13}
|
||||
```
|
||||
|
||||
如果闭包是该函数的唯一参数,那么圆括号可以省略。
|
||||
|
||||
```swift
|
||||
// someFunction 只接受一个闭包参数
|
||||
myData.someMethod() {$0 == 13}
|
||||
myData.someMethod {$0 == 13}
|
||||
```
|
||||
|
||||
> 函数调用表达式语法
|
||||
<a name="function-call-expression"></a>
|
||||
> *函数调用表达式* → [*后缀表达式*](#postfix-expression) [*圆括号表达式*](#parenthesized-expression)
|
||||
> *函数调用表达式* → [*后缀表达式*](#postfix-expression) [*圆括号表达式*](#parenthesized-expression)<sub>可选</sub> [*尾随闭包*](#trailing-closure)
|
||||
<a name="trailing-closure"></a>
|
||||
> *尾随闭包* → [*闭包表达式*](#closure-expression)
|
||||
|
||||
<a name="initializer_expression"></a>
|
||||
### 构造器表达式
|
||||
|
||||
构造器表达式用于访问某个类型的构造器,形式如下:
|
||||
|
||||
> `表达式`.init(`构造器参数`)
|
||||
|
||||
你可以在函数调用表达式中使用构造器表达式来初始化某个类型的新实例。也可以使用构造器表达式来代理给超类构造器。
|
||||
|
||||
```swift
|
||||
class SomeSubClass: SomeSuperClass {
|
||||
override init() {
|
||||
// 此处为子类构造过程
|
||||
super.init()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
和函数类似,构造器表达式可以作为一个值。 例如:
|
||||
|
||||
```swift
|
||||
// 类型注解是必须的,因为 String 类型有多种构造器
|
||||
let initializer: Int -> String = String.init
|
||||
let oneTwoThree = [1, 2, 3].map(initializer).reduce("", combine: +)
|
||||
print(oneTwoThree)
|
||||
// 打印 “123”
|
||||
```
|
||||
|
||||
如果通过名字来指定某个类型,可以不用构造器表达式而直接使用类型的构造器。在其他情况下,你必须使用构造器表达式。
|
||||
|
||||
```swift
|
||||
let s1 = SomeType.init(data: 3) // 有效
|
||||
let s2 = SomeType(data: 1) // 有效
|
||||
|
||||
let s4 = someValue.dynamicType(data: 5) // 错误
|
||||
let s3 = someValue.dynamicType.init(data: 7) // 有效
|
||||
```
|
||||
|
||||
> 构造器表达式语法
|
||||
<a name="initializer-expression"></a>
|
||||
> *构造器表达式* → [*后缀表达式*](#postfix-expression) **.** **init**
|
||||
> *构造器表达式* → [*后缀表达式*](#postfix-expression) **.** **init** **(** [*参数名称*](#argument-names) **)**
|
||||
|
||||
<a name="explicit_member_expression"></a>
|
||||
### 显式成员表达式
|
||||
|
||||
显式成员表达式允许我们访问命名类型、元组或者模块的成员,其形式如下:
|
||||
|
||||
> `表达式`.`成员名`
|
||||
|
||||
命名类型的某个成员在原始实现或者扩展中定义,例如:
|
||||
|
||||
```swift
|
||||
class SomeClass {
|
||||
var someProperty = 42
|
||||
}
|
||||
let c = SomeClass()
|
||||
let y = c.someProperty // 访问成员
|
||||
```
|
||||
|
||||
元组的成员会隐式地根据表示它们出现顺序的整数来命名,以 0 开始,例如:
|
||||
|
||||
```swift
|
||||
var t = (10, 20, 30)
|
||||
t.0 = t.1
|
||||
// 现在元组 t 为 (20, 20, 30)
|
||||
```
|
||||
|
||||
对于模块的成员来说,只能直接访问顶级声明中的成员。
|
||||
|
||||
为了区分只有参数名有所不同的方法或构造器,在圆括号中写出参数名,参数名后紧跟一个冒号,对于没有参数名的参数,使用下划线代替参数名。而对于重载方法,则需使用类型标注进行区分。例如:
|
||||
|
||||
```swift
|
||||
class SomeClass {
|
||||
func someMethod(x: Int, y: Int) {}
|
||||
func someMethod(x: Int, z: Int) {}
|
||||
func overloadedMethod(x: Int, y: Int) {}
|
||||
func overloadedMethod(x: Int, y: Bool) {}
|
||||
}
|
||||
let instance = SomeClass()
|
||||
|
||||
let a = instance.someMethod // 有歧义
|
||||
let b = instance.someMethod(_:y:) // 无歧义
|
||||
|
||||
let d = instance.overloadedMethod // 有歧义
|
||||
let d = instance.overloadedMethod(_:y:) // 有歧义
|
||||
let d: (Int, Bool) -> Void = instance.overloadedMethod(_:y:) // 无歧义
|
||||
```
|
||||
|
||||
如果点号(`.`)出现在行首,它会被视为显式成员表达式的一部分,而不是隐式成员表达式的一部分。例如如下代码所展示的被分为多行的链式方法调用:
|
||||
|
||||
```swift
|
||||
let x = [10, 3, 20, 15, 4]
|
||||
.sort()
|
||||
.filter { $0 > 5 }
|
||||
.map { $0 * 100 }
|
||||
```
|
||||
|
||||
> 显式成员表达式语法
|
||||
<a name="explicit-member-expression"></a>
|
||||
> *显式成员表达式* → [*后缀表达式*](#postfix-expression) **.** [*十进制数字*](02_Lexical_Structure.md#decimal-digit)
|
||||
> *显式成员表达式* → [*后缀表达式*](#postfix-expression) **.** [*标识符*](02_Lexical_Structure.md#identifier) [*泛型实参子句*](08_Generic_Parameters_and_Arguments.md#generic-argument-clause)<sub>可选</sub><br/>
|
||||
> *显式成员表达式* → [*后缀表达式*](#postfix-expression) **.** [*标识符*](02_Lexical_Structure.md#identifier) **(** [*参数名称*](#argument-names) **)**
|
||||
>
|
||||
<a name="argument-names"></a>
|
||||
> *参数名称* → [*参数名*](#argument-name) [*参数名称*](#argument-names)<sub>可选</sub><br/>
|
||||
<a name="argument-name"></a>
|
||||
> *参数名* → [*标识符*](02_Lexical_Structure.md#identifier) **:**
|
||||
|
||||
<a name="postfix_self_expression"></a>
|
||||
### 后缀 self 表达式
|
||||
|
||||
后缀 `self` 表达式由某个表达式或类型名紧跟 `.self` 组成,其形式如下:
|
||||
|
||||
> `表达式`.self
|
||||
> `类型`.self
|
||||
|
||||
第一种形式返回表达式的值。例如:`x.self` 返回 `x`。
|
||||
|
||||
第二种形式返回相应的类型。我们可以用它来获取某个实例的类型作为一个值来使用。例如,`SomeClass.self` 会返回 `SomeClass` 类型本身,你可以将其传递给相应函数或者方法作为参数。
|
||||
|
||||
> 后缀 self 表达式语法
|
||||
<a name="postfix-self-expression"></a>
|
||||
> *后缀 self 表达式* → [*后缀表达式*](#postfix-expression) **.** **self**
|
||||
|
||||
<a name="dynamic_type_expression"></a>
|
||||
### dynamicType 表达式
|
||||
|
||||
`dynamicType` 表达式由某个表达式紧跟 `.dynamicType` 组成,其形式如下:
|
||||
|
||||
> `表达式`.dynamicType
|
||||
|
||||
上述形式中的表达式不能是类型名。`dynamicType` 表达式会返回某个实例在运行时的类型,具体请看下面的例子:
|
||||
|
||||
```swift
|
||||
class SomeBaseClass {
|
||||
class func printClassName() {
|
||||
print("SomeBaseClass")
|
||||
}
|
||||
}
|
||||
class SomeSubClass: SomeBaseClass {
|
||||
override class func printClassName() {
|
||||
print("SomeSubClass")
|
||||
}
|
||||
}
|
||||
let someInstance: SomeBaseClass = SomeSubClass()
|
||||
// someInstance 在编译时的静态类型为 SomeBaseClass,
|
||||
// 在运行时的动态类型为 SomeSubClass
|
||||
someInstance.dynamicType.printClassName()
|
||||
// 打印 “SomeSubClass”
|
||||
```
|
||||
|
||||
> 动态类型表达式语法
|
||||
<a name="dynamic-type-expression"></a>
|
||||
> *动态类型表达式* → [*后缀表达式*](#postfix-expression) **.** **dynamicType**
|
||||
|
||||
<a name="subscript_expression"></a>
|
||||
### 下标表达式
|
||||
|
||||
可通过下标表达式访问相应的下标,形式如下:
|
||||
|
||||
> `表达式`[`索引表达式`]
|
||||
|
||||
要获取下标表达式的值,可将索引表达式作为下标表达式的参数来调用下标 getter。下标 setter 的调用方式与之一样。
|
||||
|
||||
关于下标的声明,请参阅 [协议下标声明](05_Declarations.md#protocol_subscript_declaration)。
|
||||
|
||||
> 下标表达式语法
|
||||
<a name="subscript-expression"></a>
|
||||
> *下标表达式* → [*后缀表达式*](#postfix-expression) **[** [*表达式列表*](#expression-list) **]**
|
||||
|
||||
<a name="forced-Value_expression"></a>
|
||||
### 强制取值表达式
|
||||
|
||||
当你确定可选值不是 `nil` 时,可以使用强制取值表达式来强制解包,形式如下:
|
||||
|
||||
> `表达式`!
|
||||
|
||||
如果该表达式的值不是 `nil`,则返回解包后的值。否则,抛出运行时错误。
|
||||
|
||||
返回的值可以被修改,无论是修改值本身,还是修改值的成员。例如:
|
||||
|
||||
```swift
|
||||
var x: Int? = 0
|
||||
x!++
|
||||
// x 现在是 1
|
||||
|
||||
var someDictionary = ["a": [1, 2, 3], "b": [10, 20]]
|
||||
someDictionary["a"]![0] = 100
|
||||
// someDictionary 现在是 [b: [10, 20], a: [100, 2, 3]]
|
||||
```
|
||||
|
||||
> 强制取值语法
|
||||
<a name="forced-value-expression"></a>
|
||||
> *强制取值表达式* → [*后缀表达式*](#postfix-expression) **!**
|
||||
|
||||
<a name="optional-chaining_expression"></a>
|
||||
### 可选链表达式
|
||||
|
||||
可选链表达式提供了一种使用可选值的便捷方法,形式如下:
|
||||
|
||||
> `表达式`?
|
||||
|
||||
后缀 `?` 运算符会根据表达式生成可选链表达式而不会改变表达式的值。
|
||||
|
||||
如果某个后缀表达式包含可选链表达式,那么它的执行过程会比较特殊。如果该可选链表达式的值是 `nil`,整个后缀表达式会直接返回 `nil`。如果该可选链表达式的值不是 `nil`,则返回可选链表达式解包后的值,并将该值用于后缀表达式中剩余的表达式。在这两种情况下,整个后缀表达式的值都会是可选类型。
|
||||
|
||||
如果某个后缀表达式中包含了可选链表达式,那么只有最外层的表达式会返回一个可选类型。例如,在下面的例子中,如果 `c` 不是 `nil`,那么它的值会被解包,然后通过 `.property` 访问它的属性,接着进一步通过 `.performAction()` 调用相应方法。整个 `c?.property.performAction()` 表达式返回一个可选类型的值,而不是多重可选类型。
|
||||
|
||||
```swift
|
||||
var c: SomeClass?
|
||||
var result: Bool? = c?.property.performAction()
|
||||
```
|
||||
|
||||
上面的例子跟下面的不使用可选链表达式的例子等价:
|
||||
|
||||
```swift
|
||||
var result: Bool? = nil
|
||||
if let unwrappedC = c {
|
||||
result = unwrappedC.property.performAction()
|
||||
}
|
||||
```
|
||||
|
||||
可选链表达式解包后的值可以被修改,无论是修改值本身,还是修改值的成员。如果可选链表达式的值为 `nil`,则表达式右侧的赋值操作不会被执行。例如:
|
||||
|
||||
```swift
|
||||
func someFunctionWithSideEffects() -> Int {
|
||||
// 译者注:为了能看出此函数是否被执行,加上了一句打印
|
||||
print("someFunctionWithSideEffects")
|
||||
return 42
|
||||
}
|
||||
var someDictionary = ["a": [1, 2, 3], "b": [10, 20]]
|
||||
|
||||
someDictionary["not here"]?[0] = someFunctionWithSideEffects()
|
||||
// someFunctionWithSideEffects 不会被执行
|
||||
// someDictionary 依然是 ["b": [10, 20], "a": [1, 2, 3]]
|
||||
|
||||
someDictionary["a"]?[0] = someFunctionWithSideEffects()
|
||||
// someFunctionWithSideEffects 被执行并返回 42
|
||||
// someDictionary 现在是 ["b": [10, 20], "a": [42, 2, 3]]
|
||||
```
|
||||
|
||||
> 可选链表达式语法
|
||||
<a name="optional-chaining-expression"></a>
|
||||
> *可选链表达式* → [*后缀表达式*](#postfix-expression) **?**
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user