├── .gitbook └── assets │ ├── 0e2c8fe1-94f3-4b1d-8059-e31b0e0670ac.png │ ├── 191df9fc-ea47-457a-8849-d2729ea4457c.png │ ├── 45ce334f-7bb5-42ce-bde5-998c708e567a.png │ ├── 56c640e4-4550-4977-87c3-4c242f03d678.png │ ├── 59ff831a-ab4f-4db7-a56f-aae51d677173.png │ ├── 5a40911d-d7ab-489f-9cb3-ccb600be7be8.png │ ├── 5f6b5aa5-57d6-41e3-bc95-89e1f396ea1a.png │ ├── 89c45f54-9bef-4e9b-b164-0776eb90b991.png │ ├── 8b9e80a3-7fb1-4e96-bffd-f9e6f2505b75.png │ ├── 99d5e388-2e8f-4b46-8ff4-86d558154224.png │ ├── b59598cb-4e0f-4016-b84e-4a4b9fc868fb.png │ ├── be27d61e-5730-4155-b132-ed1a14038787.png │ ├── bitshiftsigned_2x.png │ ├── bitshiftsignedaddition_2x.png │ ├── bitshiftsignedfour_2x.png │ ├── bitshiftsignedminusfour_2x.png │ ├── bitshiftsignedminusfourvalue_2x.png │ ├── bitshiftunsigned_2x.png │ ├── bitwiseand_2x.png │ ├── bitwisenot_2x.png │ ├── bitwiseor_2x.png │ ├── bitwisexor_2x.png │ ├── c7eea172-39ef-49ba-876d-cd2a72d5e2dc.png │ ├── chessboard_2x.png │ ├── closurereferencecycle01_2x.png │ ├── closurereferencecycle02_2x.png │ ├── ff0945a1-9577-4526-b668-d4b27904e037.png │ ├── initializerdelegation01_2x.png │ ├── initializerdelegation02_2x.png │ ├── initializersexample01_2x.png │ ├── initializersexample02_2x.png │ ├── initializersexample03_2x (1).png │ ├── initializersexample03_2x.png │ ├── memory_increment_2x.png │ ├── memory_share_health_maria_2x.png │ ├── memory_share_health_oscar_2x.png │ ├── memory_shopping_2x.png │ ├── overflowaddition_2x.png │ ├── overflowsignedsubtraction_2x.png │ ├── overflowunsignedsubtraction_2x.png │ ├── referencecycle01_2x.png │ ├── referencecycle02_2x (1).png │ ├── referencecycle02_2x.png │ ├── referencecycle03_2x.png │ ├── remainderinteger_2x.png │ ├── stackpoppedonestring_2x.png │ ├── stackpushedfourstrings_2x.png │ ├── stackpushpop_2x.png │ ├── twophaseinitialization01_2x.png │ ├── twophaseinitialization02_2x.png │ ├── unownedreference01_2x.png │ ├── unownedreference02_2x.png │ ├── vectoraddition_2x.png │ ├── weakreference01_2x.png │ ├── weakreference02_2x.png │ └── weakreference03_2x.png ├── LICENSE ├── README.md ├── SUMMARY.md ├── language-guide ├── 02-basic-operators.md ├── 03-strings-and-characters.md ├── 04-collection-types.md ├── 05-control-flow.md ├── 06-functions.md ├── 07-closures.md ├── 08-enumerations.md ├── 09-classes-and-structures.md ├── 10-properties.md ├── 11-methods.md ├── 12-subscripts.md ├── 13-inheritance.md ├── 14-initialization.md ├── 15-deinitialization.md ├── 16-optional-chaining.md ├── 17-error-handling.md ├── 18-type-casting.md ├── 19-nested-types.md ├── 20-extensions.md ├── 21-protocols.md ├── 22-generics.md ├── 23-automatic-reference-counting.md ├── 24-memory-safety.md ├── 25-access-control.md └── 26-advanced-operators.md ├── revision-history-1 └── document-revision-history.md └── revision-history ├── contacts.md └── contributors.md /.gitbook/assets/0e2c8fe1-94f3-4b1d-8059-e31b0e0670ac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/0e2c8fe1-94f3-4b1d-8059-e31b0e0670ac.png -------------------------------------------------------------------------------- /.gitbook/assets/191df9fc-ea47-457a-8849-d2729ea4457c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/191df9fc-ea47-457a-8849-d2729ea4457c.png -------------------------------------------------------------------------------- /.gitbook/assets/45ce334f-7bb5-42ce-bde5-998c708e567a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/45ce334f-7bb5-42ce-bde5-998c708e567a.png -------------------------------------------------------------------------------- /.gitbook/assets/56c640e4-4550-4977-87c3-4c242f03d678.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/56c640e4-4550-4977-87c3-4c242f03d678.png -------------------------------------------------------------------------------- /.gitbook/assets/59ff831a-ab4f-4db7-a56f-aae51d677173.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/59ff831a-ab4f-4db7-a56f-aae51d677173.png -------------------------------------------------------------------------------- /.gitbook/assets/5a40911d-d7ab-489f-9cb3-ccb600be7be8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/5a40911d-d7ab-489f-9cb3-ccb600be7be8.png -------------------------------------------------------------------------------- /.gitbook/assets/5f6b5aa5-57d6-41e3-bc95-89e1f396ea1a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/5f6b5aa5-57d6-41e3-bc95-89e1f396ea1a.png -------------------------------------------------------------------------------- /.gitbook/assets/89c45f54-9bef-4e9b-b164-0776eb90b991.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/89c45f54-9bef-4e9b-b164-0776eb90b991.png -------------------------------------------------------------------------------- /.gitbook/assets/8b9e80a3-7fb1-4e96-bffd-f9e6f2505b75.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/8b9e80a3-7fb1-4e96-bffd-f9e6f2505b75.png -------------------------------------------------------------------------------- /.gitbook/assets/99d5e388-2e8f-4b46-8ff4-86d558154224.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/99d5e388-2e8f-4b46-8ff4-86d558154224.png -------------------------------------------------------------------------------- /.gitbook/assets/b59598cb-4e0f-4016-b84e-4a4b9fc868fb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/b59598cb-4e0f-4016-b84e-4a4b9fc868fb.png -------------------------------------------------------------------------------- /.gitbook/assets/be27d61e-5730-4155-b132-ed1a14038787.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/be27d61e-5730-4155-b132-ed1a14038787.png -------------------------------------------------------------------------------- /.gitbook/assets/bitshiftsigned_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/bitshiftsigned_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/bitshiftsignedaddition_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/bitshiftsignedaddition_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/bitshiftsignedfour_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/bitshiftsignedfour_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/bitshiftsignedminusfour_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/bitshiftsignedminusfour_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/bitshiftsignedminusfourvalue_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/bitshiftsignedminusfourvalue_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/bitshiftunsigned_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/bitshiftunsigned_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/bitwiseand_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/bitwiseand_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/bitwisenot_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/bitwisenot_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/bitwiseor_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/bitwiseor_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/bitwisexor_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/bitwisexor_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/c7eea172-39ef-49ba-876d-cd2a72d5e2dc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/c7eea172-39ef-49ba-876d-cd2a72d5e2dc.png -------------------------------------------------------------------------------- /.gitbook/assets/chessboard_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/chessboard_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/closurereferencecycle01_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/closurereferencecycle01_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/closurereferencecycle02_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/closurereferencecycle02_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/ff0945a1-9577-4526-b668-d4b27904e037.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/ff0945a1-9577-4526-b668-d4b27904e037.png -------------------------------------------------------------------------------- /.gitbook/assets/initializerdelegation01_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/initializerdelegation01_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/initializerdelegation02_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/initializerdelegation02_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/initializersexample01_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/initializersexample01_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/initializersexample02_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/initializersexample02_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/initializersexample03_2x (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/initializersexample03_2x (1).png -------------------------------------------------------------------------------- /.gitbook/assets/initializersexample03_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/initializersexample03_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/memory_increment_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/memory_increment_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/memory_share_health_maria_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/memory_share_health_maria_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/memory_share_health_oscar_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/memory_share_health_oscar_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/memory_shopping_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/memory_shopping_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/overflowaddition_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/overflowaddition_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/overflowsignedsubtraction_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/overflowsignedsubtraction_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/overflowunsignedsubtraction_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/overflowunsignedsubtraction_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/referencecycle01_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/referencecycle01_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/referencecycle02_2x (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/referencecycle02_2x (1).png -------------------------------------------------------------------------------- /.gitbook/assets/referencecycle02_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/referencecycle02_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/referencecycle03_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/referencecycle03_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/remainderinteger_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/remainderinteger_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/stackpoppedonestring_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/stackpoppedonestring_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/stackpushedfourstrings_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/stackpushedfourstrings_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/stackpushpop_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/stackpushpop_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/twophaseinitialization01_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/twophaseinitialization01_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/twophaseinitialization02_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/twophaseinitialization02_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/unownedreference01_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/unownedreference01_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/unownedreference02_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/unownedreference02_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/vectoraddition_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/vectoraddition_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/weakreference01_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/weakreference01_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/weakreference02_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/weakreference02_2x.png -------------------------------------------------------------------------------- /.gitbook/assets/weakreference03_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jusung/the-swift-programming-language-kr/338a8b19e475baba5119a77bf1ac99ee462d2948/.gitbook/assets/weakreference03_2x.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 jusung Kye 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Swift Language Guide \(한국어\) 2 | 3 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [The Swift Language Guide \(한국어\)](README.md) 4 | 5 | ## Language Guide 6 | 7 | * [기본 연산자 \(Basic Operators\)](language-guide/02-basic-operators.md) 8 | * [문자열과 문자 \(Strings and Characters\)](language-guide/03-strings-and-characters.md) 9 | * [콜렉션 타입 \(Collection Types\)](language-guide/04-collection-types.md) 10 | * [제어문 \(Control Flow\)](language-guide/05-control-flow.md) 11 | * [함수 \(Functions\)](language-guide/06-functions.md) 12 | * [클로저 \(Closures\)](language-guide/07-closures.md) 13 | * [열거형 \(Enumerations\)](language-guide/08-enumerations.md) 14 | * [클래스과 구조체 \(Classes and Structures\)](language-guide/09-classes-and-structures.md) 15 | * [프로퍼티 \(Properties\)](language-guide/10-properties.md) 16 | * [매소드 \(Methods\)](language-guide/11-methods.md) 17 | * [서브스크립트 \(Subscripts\)](language-guide/12-subscripts.md) 18 | * [상속 \(Inheritance\)](language-guide/13-inheritance.md) 19 | * [초기화 \(Initialization\)](language-guide/14-initialization.md) 20 | * [초기화 해지 \(Deinitialization\)](language-guide/15-deinitialization.md) 21 | * [옵셔널 체이닝 \(Optional Chaining\)](language-guide/16-optional-chaining.md) 22 | * [에러 처리\(Error Handling\)](language-guide/17-error-handling.md) 23 | * [타입캐스팅 \(Type Casting\)](language-guide/18-type-casting.md) 24 | * [중첩 타입 \(Nested Types\)](language-guide/19-nested-types.md) 25 | * [익스텐션 \(Extensions\)](language-guide/20-extensions.md) 26 | * [프로토콜 \(Protocols\)](language-guide/21-protocols.md) 27 | * [지네릭 \(Generics\)](language-guide/22-generics.md) 28 | * [자동 참조 카운트 \(Automatic Reference Counting\)](language-guide/23-automatic-reference-counting.md) 29 | * [메모리 안정성 \(Memory Safety\)](language-guide/24-memory-safety.md) 30 | * [접근제어 \(Access Control\)](language-guide/25-access-control.md) 31 | * [고급 연산자 \(Advanced Operators\)](language-guide/26-advanced-operators.md) 32 | 33 | ## 연락처 및 도움을 주신 분들 34 | 35 | * [연락처](revision-history/contacts.md) 36 | * [번역에 도움을 주신 분들](revision-history/contributors.md) 37 | 38 | ## REVISION HISTORY 39 | 40 | * [Document Revision History](revision-history-1/document-revision-history.md) 41 | 42 | -------------------------------------------------------------------------------- /language-guide/02-basic-operators.md: -------------------------------------------------------------------------------- 1 | # 기본 연산자 \(Basic Operators\) 2 | 3 | Swift에서는 통상적으로 이용하는 `+`, `-`, `/`, `%` 같은 산술연산자와 `&&`, `||` 같은 논리 연산자, 그리고 C에서 지원하지 않는 `a.. _주의_ 104 | > 합성 할당 연산자는 값을 반환하지 않습니다. 즉, `let b = a+=2` 와 같은 문법은 사용할 수 없습니다. 105 | 106 | 더 많은 정보는 [연산자 선언\(Operator Declarations\)](https://developer.apple.com/documentation/swift/swift_standard_library/operator_declarations) 에서 확인할 수 있습니다. 107 | 108 | ## 비교 연산자\(Comparison Operators\) 109 | 110 | Swift에서는 표준 C에서 제공하는 비교 연산자를 모두 지원합니다. 111 | 112 | * 같다 \(a == b\) 113 | * 같지않다 \(a != b\) 114 | * 크다 \(a > b\) 115 | * 작다 \(a < b\) 116 | * 크거나 같다 \(a >= b\) 117 | * 작거나 같다 \(a <= b\) 118 | 119 | > _주의_ 120 | > Swift는 객체 비교를 위해 식별 연산자 `===` 과 `!==`를 제공합니다. 121 | 122 | 각 비교연산은 true 혹은 false 값을 반환합니다. 123 | 124 | ```swift 125 | 1 == 1 // true 126 | 2 != 1 // true 127 | 2 > 1 // true 128 | 1 < 2 // true 129 | 1 >= 1 // true 130 | 2 <= 1 // false 131 | ``` 132 | 133 | 비교 연산은 if-else와 같은 조건 구문에서 자주 사용 됩니다. 134 | 135 | ```swift 136 | let name = "world" 137 | if name == "world" { 138 | print("hello, world") 139 | } else { 140 | print("I'm sorry \(name), but I don't recognize you") 141 | } 142 | // Prints "hello, world", because name is indeed equal to "world". 143 | ``` 144 | 145 | if문과 관련한 더 많은 정보는 [제어문 ****\(Control Flow\)](https://jusung.gitbook.io/the-swift-language-guide/language-guide/05-control-flow) ****에서 확인할 수 있습니다. 146 | 147 | 같은 타입의 값을 갖는 두 개의 튜플을 비교할 수 있습니다. 튜플의 비교는 왼쪽에서 오른쪽 방향으로 이뤄지고 한번에 한개의 값만 비교합니다. 이 비교를 다른 두 값을 비교하게 될 때까지 수행합니다. 148 | 149 | ```swift 150 | (1, "zebra") < (2, "apple") // true, 1이 2보다 작고; zebra가 apple은 비교하지 않기 때문 151 | (3, "apple") < (3, "bird") // true 왼쪽 3이 오른쪽 3과 같고; apple은 bird보다 작기 때문 152 | (4, "dog") == (4, "dog") // true 왼쪽 4는 오른쪽 4와 같고 왼쪽 dog는 오른쪽 dog와 같기 때문 153 | ``` 154 | 155 | 첫 번째 줄의 튜플의 비교는 1이 2와 같지 않기 때문에 이 시점에 튜플의 비교는 종료됩니다. 반면 두 번째와 세 번째 줄의 튜플의 비교는 3과 4 숫자가 비교하는 튜플이 각각 같기 때문에 문자도 비교해봅니다. 156 | 157 | 튜플은 비교 연산자가 해당 값을 비교할 수 있는 경우에만 비교를 수행할 수 있습니다. 158 | 159 | ```swift 160 | ("blue", -1) < ("purple", 1) // 비교가능, 결과 : true 161 | ("blue", false) < ("purple", true) // 에러, Boolean 값은 < 연산자로 비교할 수 없기 때문에 162 | ``` 163 | 164 | > Swift 표준 라이브러리에서는 7개 요소 미만을 갖는 튜플만 비교할 수 있습니다. 만약 7개 혹은 그 이상의 요소를 갖는 튜플을 비교하고 싶으면 직접 비교 연산자를 구현해야 합니다. 165 | 166 | ## 삼항 조건 연산자\(Ternary Conditional Operator\) 167 | 168 | 삼항 조건 연산자는 `question ? answer1 : answer2`의 구조를 갖습니다. 그래서 question 조건이 참인경우 answer1이 거짓인 경우 answer2가 실행됩니다. 삼항 조건 연산자는 아래 코드의 축약입니다. 169 | 170 | ```swift 171 | if question { 172 | answer1 173 | } else { 174 | answer2 175 | } 176 | ``` 177 | 178 | 삼항 조건 연산의 실제 예 입니다. 179 | 180 | ```swift 181 | let contentHeight = 40 182 | let hasHeader = true 183 | let rowHeight = contentHeight + (hasHeader ? 50 : 20) 184 | // rowHeight는 90 (40 + 50) 185 | ``` 186 | 187 | 위에서 삼항 조건 연산을 다음과 같이 풀어 쓸 수 있습니다. 188 | 189 | ```swift 190 | let contentHeight = 40 191 | let hasHeader = true 192 | let rowHeight: Int 193 | if hasHeader { 194 | rowHeight = contentHeight + 50 195 | } else { 196 | rowHeight = contentHeight + 20 197 | } 198 | // rowHeight는 90입니다. 199 | ``` 200 | 201 | 삼항 조건 연산자는 코드를 짧게 만들어 가독성을 높여줍니다. 202 | 203 | ## Nil 병합 연산자\(Nil-Coalescing Operator\) 204 | 205 | nil 병합 연산자는 `a ?? b` 형태를 갖는 연산자 입니다. 옵셔널 `a`를 벗겨서\(unwraps\) 만약 `a`가 `nil` 인 경우 `b`를 반환합니다. 이 `nil` 병합 연산자는 다음 코드의 축약형입니다. 206 | 207 | ```swift 208 | a != nil ? a! : b 209 | ``` 210 | 211 | 삼항 조건 연산자를 사용했는데요. 옵셔널 `a`가 `nil`이 아니면 `a`를 unwrap하고`nil`이면 `b`를 반환하라는 의미입니다. 212 | 213 | 예제를 보시겠습니다. 214 | 215 | ```swift 216 | let defaultColorName = "red" 217 | var userDefinedColorName: String? // 이 값은 defaults 값 nil입니다. 218 | 219 | var colorNameToUse = userDefinedColorName ?? defaultColorName 220 | // userDefinedColorNam이 nil이므로 colorNameToUse 값은 defaultColorName인 "red"가 설정 됩니다. 221 | ``` 222 | 223 | ```swift 224 | userDefinedColorName = "green" 225 | colorNameToUse = userDefinedColorName ?? defaultColorName 226 | // userDefinedColorName가 nil이 아니므로 colorNameToUse 는 "green"이 됩니다. 227 | ``` 228 | 229 | ## 범위 연산자\(Range Operators\) 230 | 231 | ### 닫힌 범위 연산자\(Closed Range Operator\) 232 | 233 | `(a...b)`의 형태로 범위의 시작과 끝이 있는 연산자 입니다. for-in loop에 자주 사용됩니다. 234 | 235 | ```swift 236 | for index in 1...5 { 237 | print("\(index) times 5 is \(index * 5)") 238 | } 239 | // 1 times 5 is 5 240 | // 2 times 5 is 10 241 | // 3 times 5 is 15 242 | // 4 times 5 is 20 243 | // 5 times 5 is 25 244 | ``` 245 | 246 | for-in loop에 관한 더 많은 정보는 [제어문 \(Control Flow\)](https://jusung.gitbook.io/the-swift-language-guide/language-guide/05-control-flow)에서 보실 수 있습니다. 247 | 248 | ### 반 닫힌 범위 연산자\(Half-Open Range Operator\) 249 | 250 | `(a.. Swift의 논리 연산자 && 와 \|\| 는 왼쪽의 표현을 우선해서 논리 계산을 합니다. 360 | 361 | ### 명시적 괄호\(Explicit Parentheses\) 362 | 363 | 논리 연산자의 적용 우선 순위를 연산자에 맡지기 않고 명시적으로 괄호를 사용해 계산 순서를 지정할 수 있습니다. 364 | 365 | ```swift 366 | if (enteredDoorCode && passedRetinaScan) || hasDoorKey || knowsOverridePassword { 367 | print("Welcome!") 368 | } else { 369 | print("ACCESS DENIED") 370 | } 371 | // Prints "Welcome!" 372 | ``` 373 | 374 | 이렇게 괄호를 사용하면 가독성이 높아져 코드의 의도를 더 명확하게 하는데 도움이 됩니다. 375 | -------------------------------------------------------------------------------- /language-guide/03-strings-and-characters.md: -------------------------------------------------------------------------------- 1 | # 문자열과 문자 \(Strings and Characters\) 2 | 3 | Swift의 `String`은 `Foundation` 프레임워크의 `NSString`이 bridge된 타입이기 때문에 `NSString`의 메소드를 `String`에서 캐스팅 없이 사용 가능합니다. 4 | 5 | ## 문자열 리터럴 6 | 7 | 문자열은 큰 따옴표\(`“`\)로 묶어 표현 합니다. 8 | 9 | ```swift 10 | let something = "Some string literal value" 11 | ``` 12 | 13 | ### 여러줄 문자열 리터럴 14 | 15 | 여러줄의 문자열을 사용하고 싶은 경우 큰 따옴표 3개\(“””\)로 묶어서 사용할 수 있습니다. 16 | 17 | ```text 18 | let quotation = """ 19 | The White Rabbit put on his spectacles. "Where shall I begin, 20 | please your Majesty?" he asked. 21 | 22 | "Begin at the beginning," the King said gravely, "and go on 23 | till you come to the end; then stop." 24 | """ 25 | ``` 26 | 27 | 여러줄 문자열을 사용할 때는 첫 시작의 `"""` 다음 줄부터 마지막 `"""`의 직전까지를 문자열로 봅니다. 그래서 아래 `singleLineString`과 `multilineString`은 같은 값을 갖게 됩니다. 28 | 29 | ```swift 30 | let singleLineString = "These are the same." 31 | let multilineString = """ 32 | These are the same. 33 | """ 34 | ``` 35 | 36 | 여러줄 문자열을 사용하며 줄바꿈을 하고 싶으면 백슬래쉬\(`\`\)를 사용합니다. 37 | 38 | ```swift 39 | let softWrappedQuotation = """ 40 | The White Rabbit put on his spectacles. "Where shall I begin, \ 41 | please your Majesty?" he asked. 42 | 43 | "Begin at the beginning," the King said gravely, "and go on \ 44 | till you come to the end; then stop." 45 | """ 46 | ``` 47 | 48 | 문자열의 시작과 끝에 각각 빈줄을 넣고 싶다면 한 줄을 띄어서 문자열을 입력하면 됩니다. 49 | 50 | ```swift 51 | let lineBreaks = """ 52 | 53 | This string starts with a line break. 54 | It also ends with a line break. 55 | 56 | """ 57 | ``` 58 | 59 | 들여쓰기도 가능합니다. 들여쓰기 기준은 끝나는 지점의 `"""`의 위치입니다. 아래의 경우에는 닫는 `"""`위치 앞에 있는 문자들은 전부 무시되고 그 이후의 공백은 문자열에 반영됩니다. 60 | 61 | ![](../.gitbook/assets/45ce334f-7bb5-42ce-bde5-998c708e567a.png) 62 | 63 | ### 문자열 리터럴의 특수 문자 64 | 65 | 문자열 리터럴은 다음과 같은 특수 문자를 포함할 수 있습니다. 66 | 67 | * `\0`, `\`, `\t`, `\n`, `\r`, `\”`, `\’` 68 | * `\u{n}`, n은 1-8자리 16진수 형태로 구성된 유니코드 69 | 70 | ```swift 71 | let wiseWords = "\"Imagination is more important than knowledge\" - Einstein" 72 | // "Imagination is more important than knowlege" - Einstein 73 | let dollaSign = "\u{24}" // $, 유니코트 U+0024 74 | let blackHeart = "\u{2665}" // ♥, 유니코드 U+2665 75 | let sparklingHeart = "\u{1F496}" // 💖,유니코드 U+1F496 76 | ``` 77 | 78 | ## 빈 문자열 초기화 79 | 80 | 아래 예의 두 변수의 문자열 값은 같습니다. 81 | 82 | ```swift 83 | var emtpryString = "" 84 | var anotherEmptyString = String() 85 | ``` 86 | 87 | 문자열이 비어있는지 여부를 확인하기 위해서는 `isEmpty` 프로퍼티를 이용합니다. 88 | 89 | ```swift 90 | if emptyString.isEmpty { 91 | print("Nothing to see here") 92 | } 93 | // Prints "Nothing to see here" 94 | ``` 95 | 96 | ### 문자열 수정 97 | 98 | ```swift 99 | var variableString = "Horse" 100 | variableString += " and carriage" 101 | // variableString = Horse and carriage 102 | 103 | let constantString = "Highlander" 104 | constantString += " and another Highlander" 105 | // 문자열 상수(let)로 선언돼 있어 에러발생! 106 | ``` 107 | 108 | ## 값 타입 문자열 109 | 110 | Swift의 `String`은 값 타입\(value type\)입니다. 그래서 `String`이 다른 함수 혹은 메소드로 부터 생성되면 `String`값이 할당 될때, 이전 `String`의 레퍼런스를 할당하는 것이 아니라 값을 복사해서 생성합니다. 반대로 이야기 하면 다른 메소드에서 할당 받은 문자열은 그 문자열을 수정해도 원본 문자열이 변하지 않기 때문에 편하게 사용하셔도 됩니다. 111 | 112 | ## 문자 113 | 114 | 문자열의 개별 문자를 `for-in` loop을 사용해 접근할 수 있습니다. 115 | 116 | ```swift 117 | for character in "Dog!🐶" { 118 | print(character) 119 | } 120 | // D 121 | // o 122 | // g 123 | // ! 124 | // 🐶 125 | ``` 126 | 127 | 다음과 같이 문자 상수를 선언할 수 있습니다. 128 | 129 | ```swift 130 | let exclamationMark: Character = "!" 131 | ``` 132 | 133 | 문자 배열을 이용해 문자열의 초기화 메소드에 인자로 넣어 문자열을 생성할 수 있습니다. 134 | 135 | ```swift 136 | let catCharacters: [Character] = ["C", "a", "t", "!", "🐱"] 137 | let catString = String(catCharacters) 138 | print(catString) 139 | // Prints "Cat!🐱" 140 | ``` 141 | 142 | ## 문자열과 문자의 결합 143 | 144 | ```swift 145 | let string1 = "hello" 146 | let string2 = " there" 147 | var welcome = string1 + string2 148 | // welcome : "hello there" 149 | ``` 150 | 151 | ```swift 152 | var instruction = "look over" 153 | instruction += string2 154 | // instruction : "look over there" 155 | ``` 156 | 157 | ```swift 158 | let exclamationMark: Character = "!" 159 | welcome.append(exclamationMark) 160 | // welcome : "hello there!" 161 | ``` 162 | 163 | ## 문자열 삽입 164 | 165 | 백슬래쉬 괄호를 이용해 상수, 변수, 리터럴 값을 문자열에 추가할 수 있습니다. 166 | 167 | ```swift 168 | let mutiplier = 3 169 | let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)" 170 | // message : "3 times 2.5 is 7.5" 171 | ``` 172 | 173 | ## 유니코드 174 | 175 | * 유니코드는 전 세계의 모든 문자를 컴퓨터에서 일관되게 표현하고 다룰 수 있도록 설계된 국제 표준입니다. Swift의 문자열과 문자 타입은 유니코드에 순응\(compliant\)합니다. 176 | 177 | **유니코드 스칼라** 178 | 179 | Swift의 네이티브 문자열 타입은 유니코드 스칼라 값으로 만들어 졌습니다. 하나의 유니코드는 21비트의 숫자로 구성돼 있습니다. 예를 들면 `U+0061`는 라틴어의 소문자 `a`를 나타내고 `U+1F425`는 정면의 병아리 🐥 를 나타냅니다. 180 | 181 | **자모 그룹의 확장** 182 | 183 | 유니코드를 결합해서 사용할 수 있습니다. 184 | 185 | ```swift 186 | let eAcute: Character = "\u{E9}" // é 187 | let combinedEAcute: Character = "\u{65}\u{301}" // e + ́ 188 | // eAcute : é, combinedEAcute : é 189 | ``` 190 | 191 | 아래는 한글의 `“한”`자를 단독으로 사용했을 때와 `ㅎ`,`ㅏ`,`ㄴ`의 자모를 따로 결합해서 사용한 예 입니다. 192 | 193 | ```swift 194 | let precomposed: Character = "\u{D55C}" // 한 195 | let decomposed: Character = "\u{1112}\u{u1161}\u{11AB}" // ㅎ, ㅏ,ㄴ 196 | // precomposed : 한, decomposed 한 197 | ``` 198 | 199 | 아래는 `é`\(E9\)와 원심볼\(20DD\)을 결합한 결과입니다. 200 | 201 | ```swift 202 | let enclosedEAcute: Character = "\u{E9}\u{20DD}" 203 | // enclosedEAcute : é⃝ 204 | ``` 205 | 206 | 아래는 지역심볼문자인 `U`\(1F1FA\)와 `S`\(1F1F8\)를 결합한 결과입니다. 207 | 208 | ```swift 209 | let regionalIndicatorForUS: Character = "\u{1F1FA}\u{1F1F8}" 210 | // regionalIndicatorForUS : 🇺🇸 211 | ``` 212 | 213 | **문자 세기** 214 | 215 | 문자열의 문자의 순자를 세기 위해서는 문자열의 `count` 프로퍼티를 이용합니다. 216 | 217 | ```swift 218 | let unusualMenagerie = "Koala 🐨, Snail 🐌, Penguin 🐧, Dromedary 🐪" 219 | print("unusualMenagerie has \(unusualMenagerie.count) characters") 220 | // "unusualMenagerie의 문자는 40개" 221 | ``` 222 | 223 | **문자열의 접근과 수정** 224 | 225 | 문자열의 수정과 접근은 문자열 메소드 혹은 프로퍼티를 이용하거나 서브스크립트\(subscript\) 문법을 이용해 할 수 있습니다. 226 | 227 | **문자열 인덱스** 228 | 229 | 아래와 같이 `startIndex`, `endIndex`, `index(before:)`, `index(after:)`, `index(_:offsetBy:)` 메소드 등을 이용해 문자열에서 특정 문자에 접근할 수 있습니다. 230 | 231 | > _주의_ 232 | > 위 메소드들은 Collection 프로토콜을 따르는 Array, Dictionary, Set 등에서도 동일하게 사용할 수 있습니다. 233 | 234 | ```swift 235 | let greeting = "Guten Tag!" 236 | greeting[greeting.startIndex] 237 | // G 238 | greeting[greeting.index(before: greeting.endIndex)] 239 | // ! 240 | greeting[greeting.index(after: greeting.startIndex)] 241 | // u 242 | let index = greeting.index(greeting.startIndex, offsetBy: 7) 243 | greeting[index] 244 | // a 245 | ``` 246 | 247 | 문자열의 인덱스를 벗어나는 문자를 가져오려고 하면 런타임 에러가 발생합니다. 248 | 249 | ```swift 250 | greeting[greeting.endIndex] // 에러! 251 | greeting.index(after: greeting.endIndex) // 에러! 252 | ``` 253 | 254 | 문자열의 개별 문자를 접근하기 위해서는 indices 프로퍼티를 사용합니다. 255 | 256 | ```swift 257 | for index in greeting.indices { 258 | print("\(greeting[index]) ", terminator: "") 259 | // G u t e n T a g ! 260 | ``` 261 | 262 | ### 문자의 삽입과 삭제 263 | 264 | 문자의 삽입과 삭제에는 `insert(:at:)`_,_ `insert(contentsOf:at:)`_,_ `remove(at:)`_,_ `removeSubrange(:)` 메소드를 사용할 수 있습니다. 265 | 266 | > _주의_ 267 | > 위 메소드들은 RangeReplaceableCollection 프로토콜을 따르는 Array, Dictionary, Set 등에서도 동일하게 사용할 수 있습니다. 268 | 269 | ```swift 270 | var welcome = "hello" 271 | welcome.insert("!", at: welcome.endIndex) 272 | // welcome : hello! 273 | 274 | welcome.insert(contentsOf: " there", at: welcome.index(before: welcome.endIndex)) 275 | // welcome : hello there! 276 | ``` 277 | 278 | ```swift 279 | welcome.remove(at: welcome.index(before: welcome.endIndex)) 280 | // welcome : hello there 281 | 282 | let range = welcome.index(welcome.endIndex, offsetBy: -6).. _주의_ 308 | > String과 Substring 모두 StringProtocol을 따릅니다. 그래서 문자 조작에 필요한 편리한 매소스들을 공통으로 사용할 수 있습니다. 309 | 310 | 문자열 비교 Swift에서는 문자열과 문자 , 접두사, 접미사를 비교하는 방법을 제공합니다. 311 | 312 | ### 문자열과 문자 비교 313 | 314 | 문자열과 문자 비교에는 `==` 혹은 `!=` 연산자를 사용합니다. 315 | 316 | ```swift 317 | let quotation = "We're a lot alike, you and I." 318 | let sameQuotation = "We're a lost alike, you and I." 319 | if quotation == sameQuotation { 320 | print("These two strings are considered equal") 321 | } 322 | // These two strings are considered equal 출력 323 | ``` 324 | 325 | 유니코드는 결합된 문자열을 갖고 비교하게 됩니다. 326 | 327 | ```swift 328 | // "Voulez-vous un café?" using LATIN SMALL LETTER E WITH ACUTE 329 | let eAcuteQuestion = "Voulez-vous un caf\u{E9}?" 330 | 331 | // "Voulez-vous un café?" using LATIN SMALL LETTER E and COMBINING ACUTE ACCENT 332 | let combinedEAcuteQuestion = "Voulez-vous un caf\u{65}\u{301}?" 333 | 334 | if eAcuteQuestion == combinedEAcuteQuestion { 335 | print("These two strings are considered equal") 336 | } 337 | // These two strings are considered equal 출력 338 | ``` 339 | 340 | 같은 유니코드 문자여도 유니코드가 다르면 다른 문자로 판별합니다. 아래 예제는 영어의 알파벳 대문자 `A(U+0041)`와 러시아어에서 사용되는 대문자 `A(U+0410)`를 비교한 것입니다. 341 | 342 | ```swift 343 | let latinCapitalLetterA: Character = "\u{41}" 344 | 345 | let cyrillicCapitalLetterA: Character = "\u{0410}" 346 | 347 | if latinCapitalLetterA != cyrillicCapitalLetterA { 348 | print("These two characters are not equivalent.") 349 | } 350 | // Prints "These two characters are not equivalent." 351 | ``` 352 | 353 | > _주의_ 354 | > Swift에서 문자열과 문자의 비교는 언어를 고려하지 않습니다. 다시말해, 언어와 상관없이 같은 문자면 같은 문자로 취급합니다. 355 | 356 | ### 접두사와 접미사 비교 357 | 358 | 접두사와 접미사의 비교를 위해 `hasPrefix(:)`_,_ `hasSuffix(:)` 메소드를 사용할 수 있습니다. 359 | 360 | ```swift 361 | let romeoAndJuliet = [ 362 | "Act 1 Scene 1: Verona, A public place", 363 | "Act 1 Scene 2: Capulet's mansion", 364 | "Act 1 Scene 3: A room in Capulet's mansion", 365 | "Act 1 Scene 4: A street outside Capulet's mansion", 366 | "Act 1 Scene 5: The Great Hall in Capulet's mansion", 367 | "Act 2 Scene 1: Outside Capulet's mansion", 368 | "Act 2 Scene 2: Capulet's orchard", 369 | "Act 2 Scene 3: Outside Friar Lawrence's cell", 370 | "Act 2 Scene 4: A street in Verona", 371 | "Act 2 Scene 5: Capulet's mansion", 372 | "Act 2 Scene 6: Friar Lawrence's cell" 373 | ] 374 | ``` 375 | 376 | 다음 코드는 문자열 배열에서 접두어 `Act 1`가 몇개 들어있는지 확인하는 코드 입니다. 377 | 378 | ```swift 379 | var act1SceneCount = 0 380 | for scene in remeoAndJuliet { 381 | if scene.hasPrefix("Act 1 ") { 382 | act1SceneCount += 1 383 | } 384 | } 385 | print("There are \(act1SceneCount) scenes in Act 1") 386 | // There are 5 scenes in Act 1 387 | ``` 388 | 389 | 다음 코드는 문자열 배열에서 접미어 `Capulet's mansion` 과 `Friar Lawrences' cell` 이 각각 몇개 들어있는지 확인하는 코드 입니다. 390 | 391 | ```swift 392 | var mansionCount = 0 393 | var cellCount = 0 394 | for scene in remeoAndJuliet { 395 | if scene.hasSuffix("Capulet's mansion") { 396 | mansionCount += 1 397 | } else if scene.hasSuffix("Friar Lawrence's cell") { 398 | cellCount += 1 399 | } 400 | } 401 | print("\(mansionCount) mansion scenes; \(cellCount) cell scenes") 402 | // 6 mansion scenes; 2 cell scenes 403 | ``` 404 | 405 | ## 문자열의 유니코드 표현 406 | 407 | 유니코드 문자가 텍스트 파일이나 다른 저장소에 쓰여질 때 유니코드 스칼라는 UTF-8, UTF-16, UTF-32 등 다양한 유니코드 인코딩 방식이 사용됩니다. 408 | 409 | ```text 410 | let dogString = "Dog!!🐶" 411 | ``` 412 | 413 | ### UTF-8 표현 414 | 415 | ![](../.gitbook/assets/ff0945a1-9577-4526-b668-d4b27904e037.png) 416 | 417 | ```swift 418 | for codeUnit in dogString.utf8 { 419 | print("\(codeUnit) ", terminator: "") 420 | } 421 | print("") 422 | // 68 111 103 226 128 188 240 159 144 182 423 | ``` 424 | 425 | UTF-16 표현 426 | 427 | ![](../.gitbook/assets/0e2c8fe1-94f3-4b1d-8059-e31b0e0670ac.png) 428 | 429 | ```swift 430 | for codeUnit in dogString.utf16 { 431 | print("\(codeUnit) ", terminator: "") 432 | } 433 | print("") 434 | // 68 111 103 8252 55357 56374 435 | ``` 436 | 437 | ### 유니코드 스칼라 표현 438 | 439 | ![](../.gitbook/assets/5f6b5aa5-57d6-41e3-bc95-89e1f396ea1a.png) 440 | 441 | ```swift 442 | for scalar in dogString.unicodeScalars { 443 | print("\(scalar.value) ", terminator: "") 444 | } 445 | print("") 446 | // 68 111 103 8252 128054 447 | ``` 448 | 449 | ```swift 450 | for scalar in dogString.unicodeScalars { 451 | print("\(scalar) ") 452 | } 453 | // D 454 | // o 455 | // g 456 | // !! 457 | // 🐶 458 | ``` 459 | 460 | 461 | 462 | 463 | -------------------------------------------------------------------------------- /language-guide/04-collection-types.md: -------------------------------------------------------------------------------- 1 | # 콜렉션 타입 \(Collection Types\) 2 | 3 | Swift에서는 콜렉션 타입으로 배열, 셋, 사전 세 가지를 지원합니다. 4 | 5 | ![](../.gitbook/assets/89c45f54-9bef-4e9b-b164-0776eb90b991.png) 6 | 7 | ## 콜렉션의 변경 8 | 9 | 배열, 셋, 사전을 변수\(var\)에 할당하면 이 콜렉션은 변경가능하고 상수\(let\)에 할당하면 변경 불가능 합니다. 10 | 11 | ## 배열\(Array\) 12 | 13 | ### 배열의 축약형 문법 14 | 15 | 배열 타입은 Array로 적을 수 있는데 축약형으로 \[Element\] 형태로 사용할 수도 있습니다. 16 | 17 | ### 빈 배열의 생성 18 | 19 | 아래와 같이 Int형 빈 배열을 생성할 수 있습니다. 20 | 21 | ```swift 22 | var someInts = [Int]() 23 | print("someInts is of type [Int] with \(someInts.count) items.") 24 | // someInts is of type [Int] with 0 items. 25 | ``` 26 | 27 | ```swift 28 | someInts.append(3) 29 | // 배열에 3을 추가 했습니다. 30 | someInts = [] 31 | // 배열을 비웠습니다. 배열의 아이템 타입은 그대로 Int로 유지됩니다. 32 | ``` 33 | 34 | ### 기본 값으로 빈 배열 생성 35 | 36 | repeating 메소드와 count 메소드를 이용해 기본 값으로 빈 배열을 생성할 수 있습니다. 37 | 38 | ```swift 39 | var threeDoubles = Array(repeating: 0.0, count: 3) 40 | // threeDoubles : Double 타입의 [0.0, 0.0, 0.0] 41 | ``` 42 | 43 | ### 다른 배열을 추가한 배열의 생성 44 | 45 | `+` 연산자를 이용해 배열을 합칠 수 있습니다. 46 | 47 | ```swift 48 | var anotherThreeDoubles = Array(repeating: 2.5, count: 3) 49 | // anotherThreeDoubles : [2.5, 2.5, 2.5] 50 | 51 | var sixDoubles = threeDoubles + anotherThreeDoubles 52 | // sixDoubles : [0.0, 0.0, 0.0, 2.5, 2.5, 2.5] 53 | ``` 54 | 55 | ### 리터럴을 이용한 배열의 생성 56 | 57 | `[value 1, value 2, value 3]` 형태를 이용해 배열을 생성할 수 있습니다. 58 | 59 | ```swift 60 | var shoppingList: [String] = ["Eggs", "Milk"] 61 | ``` 62 | 63 | 더 간단하게 선언할 수도 있습니다. 64 | 65 | ```swift 66 | var shoppingList = ["Eggs", "Milk"] 67 | ``` 68 | 69 | ### 배열의 접근 및 변환 70 | 71 | 배열의 원소 개수 확인 72 | 73 | ```swift 74 | print("The shopping list contains \(shoppingList.count) items.") 75 | // The shopping list contains 2 items. 76 | ``` 77 | 78 | 배열이 비었는지 확인 79 | 80 | ```swift 81 | if shoppingList.isEmpty { 82 | print("The shopping list is empty.") 83 | } else { 84 | print("The shopping list is not empty.") 85 | } 86 | // The shopping list is not empty. 87 | ``` 88 | 89 | 배열에 원소 추가 90 | 91 | ```swift 92 | shoppingList.append("Four") 93 | // shoppingList.count = 3 94 | ``` 95 | 96 | ```swift 97 | shoppingList += ["Baking Powder"] 98 | // shoppingList.count = 4 99 | shoppingList += ["Chocolate Spread", "Cheese", "Butter"] 100 | // shoppingList.count = 7 101 | ``` 102 | 103 | 배열의 특정 위치의 원소 접근 104 | 105 | ```swift 106 | var firstItem = shoppingList[0] 107 | // firstItem : "Eggs" 108 | ``` 109 | 110 | ```swift 111 | shoppingList[4...6] = ["Bananas", "Apples"] 112 | // 4, 5, 6번째 인덱스 아이템을 Banana, Apples로 변환 113 | // 즉, 아이템 3개가 2개로 줄었다. 114 | ``` 115 | 116 | 특정 위치에 원소 추가/삭제/접근 117 | 118 | ```swift 119 | shoppingList.insert("Maple Syrup", at:0) 120 | ``` 121 | 122 | ```swift 123 | let mapleSyrup = shoppingList.remove(at: 0) 124 | ``` 125 | 126 | ```swift 127 | firstItem = shoppingList[0] 128 | // firstItem : "Six eggs" 129 | ``` 130 | 131 | ```swift 132 | let apples = shoppingList.removeLast() 133 | ``` 134 | 135 | ### 배열의 순회 136 | 137 | `for-in` loop을 이용해 배열을 순회할 수 있습니다. 138 | 139 | ```swift 140 | for item in shoppingList { 141 | print(item) 142 | } 143 | // Six eggs 144 | // Milk 145 | // Flour 146 | // Baking Powder 147 | // Bananas 148 | ``` 149 | 150 | 배열의 값과 인덱스가 필요할 때는 `enumerated()` 메소드를 사용합니다. 151 | 152 | ```swift 153 | for (index, value) in shoppingList.enumerated() { 154 | print("Item \(index + 1): \(value)") 155 | } 156 | // Item 1: Six eggs 157 | // Item 2: Milk 158 | // Item 3: Flour 159 | // Item 4: Baking Powder 160 | // Item 5: Bananas 161 | ``` 162 | 163 | ## 셋\(Sets\) 164 | 165 | `Set` 형태로 저장되기 위해서는 반드시 타입이 `hashable`이어야만 합니다. Swift에서 `String`, `Int`, `Double`, `Bool` 같은 기본 타입은 기본적으로 `hashable`입니다. Swift에서 `Set` 타입은 `Set`으로 선언합니다. 166 | 167 | ### 빈 Set 생성 168 | 169 | ```swift 170 | var letters = Set() 171 | print("letters is of type Set with \(letters.count) items.") 172 | // letters is of type Set with 0 items. 173 | ``` 174 | 175 | ```swift 176 | letters.insert("a") 177 | letters = [] 178 | ``` 179 | 180 | ### 배열 리터럴을 이용한 Set 생성 181 | 182 | ```swift 183 | var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"] 184 | ``` 185 | 186 | Swift의 타입추론으로 아래와 같이 선언도 가능합니다. 187 | 188 | ```swift 189 | var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"] 190 | ``` 191 | 192 | ### Set의 접근과 변경 193 | 194 | ```swift 195 | print("I have \(favoriteGenres.count) favorite music genres.") 196 | // I have 3 favorite music genres. 197 | ``` 198 | 199 | 비었는지 확인 200 | 201 | ```swift 202 | if favoriteGenres.isEmpty { 203 | print("As far as music goes, I'm not picky.") 204 | } else { 205 | print("I have particular music preferences.") 206 | } 207 | // I have particular preferences. 208 | ``` 209 | 210 | 추가 211 | 212 | ```swift 213 | favoriteGenres.insert("Jazz") 214 | ``` 215 | 216 | 삭제 217 | 218 | ```swift 219 | if let removedGenre = favoriteGenres.remove("Rock") { 220 | print("\(removedGenre)? I'm over it.") 221 | } else { 222 | print("I never much cared for that.") 223 | } 224 | // Rock? I'm over it. 225 | ``` 226 | 227 | 값 확인 228 | 229 | ```swift 230 | if favoriteGenres.contains("Funk") { 231 | print("I get up on the good foot.") 232 | } else { 233 | print("It's too funky in here.") 234 | } 235 | // It's too funky in here. 236 | ``` 237 | 238 | ### Set의 순회 239 | 240 | for-in loop을 이용해 set을 순회할 수 있습니다. 241 | 242 | ```swift 243 | for genre in favoriteGenres { 244 | print("\(genre)") 245 | } 246 | // Classical 247 | // Hip hop 248 | // Jazz 249 | ``` 250 | 251 | ### Set 명령 252 | 253 | ![](../.gitbook/assets/c7eea172-39ef-49ba-876d-cd2a72d5e2dc.png) 254 | 255 | ```swift 256 | let oddDigits: Set = [1, 3, 5, 7, 9] 257 | let evenDigits: Set = [0, 2, 4, 6, 8] 258 | let singleDigitPrimeNumbers: Set = [2, 3, 5, 7] 259 | 260 | oddDigits.union(evenDigits).sorted() 261 | // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 262 | oddDigits.intersection(evenDigits).sorted() 263 | // [] 264 | oddDigits.subtracting(singleDigitPrimeNumbers).sorted() 265 | // [1, 9] 266 | oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted() 267 | // [1, 2, 9] 268 | ``` 269 | 270 | ### Set의 맴버십과 동등 비교 271 | 272 | Set의 동등비교와 맴버 여부를 확인하기 위해 각각 `==` 연산자와 `isSuperset(of:)`, `isStrictSubset(of:)`, `isStrictSuperset(of:)`, `isDisjoint(with:)` 메소드를 사용합니다. 273 | 274 | ![](../.gitbook/assets/56c640e4-4550-4977-87c3-4c242f03d678.png) 275 | 276 | `isDisjoint(with:)`는 둘간의 공통값이 없는 경우에 `true`를 반환합니다. 277 | 278 | ```swift 279 | let houseAnimals: Set = ["🐶", "🐱"] 280 | let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"] 281 | let cityAnimals: Set = ["🐦", "🐭"] 282 | 283 | houseAnimals.isSubset(of: farmAnimals) 284 | // 참 285 | farmAnimals.isSuperset(of: houseAnimals) 286 | // 참 287 | farmAnimals.isDisjoint(with: cityAnimals) 288 | // 참 289 | ``` 290 | 291 | ## 사전\(Dictionaries\) 292 | 293 | > _주의_ 294 | > Swift의 `Dictionary`타입은 `Foundation` 클래스의 `NSDictionary`를 bridge한 타입입니다. 295 | 296 | ### 축약형 Dictionary 297 | 298 | \[Key: Value\] 형태로 Dictionary를 선언해 사용할 수 있습니다. 299 | 300 | ### 빈 Dictionary의 생성 301 | 302 | ```swift 303 | var namesOfIntegers = [Int: String]() 304 | ``` 305 | 306 | ```swift 307 | namesOfIntegers[16] = "sixteen" 308 | namesOfIntegers = [:] 309 | // 빈 사전 310 | ``` 311 | 312 | ### 리터럴를 이용한 Dictionary의 생성 313 | 314 | `[key 1: value 1, key 2: value 2, key 3: value 3]` 형태로 사전을 선언할 수 있습니다. 315 | 316 | ```swift 317 | var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"] 318 | ``` 319 | 320 | ### Dictionary의 접근과 변경 321 | 322 | ```swift 323 | print("The airports dictionary contains \(airports.count) items.") 324 | // The airports dictionary contains 2 items. 325 | ``` 326 | 327 | 빈 Dictionary 확인 328 | 329 | ```swift 330 | if airports.isEmpty { 331 | print("The airports dictionary is empty.") 332 | } else { 333 | print("The airports dictionary is not empty.") 334 | } 335 | // The airports dictionary is not empty. 336 | ``` 337 | 338 | 값 할당 339 | 340 | ```swift 341 | airports["LHR"] = "London" 342 | // the airports dictionary now contains 3 items 343 | ``` 344 | 345 | -------------------------------------------------------------------------------- /language-guide/05-control-flow.md: -------------------------------------------------------------------------------- 1 | # 제어문 \(Control Flow\) 2 | 3 | Swift에서는 `while loop,` `if guard`, `switch`, `for-in` 문 등 많은 제어문을 제공합니다. 4 | 5 | ## For-In 문 \(For-In Loops\) 6 | 7 | `for-in`문는 배열, 숫자, 문자열을 순서대로 순회\(iterate\)하기 위해 사용합니다. 8 | 9 | ```swift 10 | let names = ["Anna", "Alex", "Brian", "Jack"] 11 | for name in names { 12 | print("Hello, \(name)!") 13 | } 14 | // Hello, Anna! 15 | // Hello, Alex! 16 | // Hello, Brian! 17 | // Hello, Jack! 18 | ``` 19 | 20 | 사전\(dictionary\)에서 반환된 키\(key\)-값\(value\) 쌍으로 구성된 튜플을 순회하며 제어할 수도 있습니다. 21 | 22 | ```swift 23 | let numberOfLegs = ["spider": 8, "ant": 6, "cat": 4] 24 | for (animalName, legCount) in numberOfLegs { 25 | print("\(animalName)s have \(legCount) legs") 26 | } 27 | // ants have 6 legs 28 | // spiders have 8 legs 29 | // cats have 4 legs 30 | ``` 31 | 32 | 사전\(dictionary\)에 담긴 콘텐츠는 정렬이 되지 않은 상태입니다. 사전에 넣었던 순서대로 순회되지 않습니다. 아래와 같이 숫자 범위를 지정해 순회할 수 있습니다. 33 | 34 | ```swift 35 | for index in 1...5 { 36 | print("\(index) times 5 is \(index * 5)") 37 | } 38 | // 1 times 5 is 5 39 | // 2 times 5 is 10 40 | // 3 times 5 is 15 41 | // 4 times 5 is 20 42 | // 5 times 5 is 25 43 | ``` 44 | 45 | `for-in` 문을 순서대로 제어할 필요가 없다면, 변수자리에 `_`키워드를 사용하면 성능을 높일 수 있습니다. 46 | 47 | ```swift 48 | let base = 3 49 | let power = 10 50 | var answer = 1 51 | for _ in 1...power { 52 | answer *= base 53 | } 54 | print("\(base) to the power of \(power) is \(answer)") 55 | // Prints "3 to the power of 10 is 59049" 56 | ``` 57 | 58 | 범위 연산자와 함께 사용할 수 있습니다. 59 | 60 | ```swift 61 | let minutes = 60 62 | for tickMark in 0.. `reapeat-while`문은 다른 언어의 `do-while`문과 유사한 `while`문입니다. 122 | 123 | 구문\(statements\)을 최소 한번 이상 실행하고 `while` 조건이 거짓일 때까지 반복합니다. 124 | 125 | ```swift 126 | repeat { 127 | statements 128 | } while condition 129 | ``` 130 | 131 | `repeat-while` 문의 \(예\) 132 | 133 | ```swift 134 | repeat { 135 | // move up or down for a snake or ladder 136 | square += board[square] 137 | // roll the dice 138 | diceRoll += 1 139 | if diceRoll == 7 { diceRoll = 1 } 140 | // move by the rolled amount 141 | square += diceRoll 142 | } while square < finalSquare 143 | print("Game over!") 144 | ``` 145 | 146 | ## 조건적 구문 \(Conditional Statements\) 147 | 148 | Swift에서는 `if`와 `switch`문 두 가지의 조건 구문을 제공합니다. 149 | 150 | ### If 문 151 | 152 | \(예1\) `If`만 사용 153 | 154 | ```swift 155 | var temperatureInFahrenheit = 30 156 | if temperatureInFahrenheit <= 32 { 157 | print("It's very cold. Consider wearing a scarf.") 158 | } 159 | // Prints "It's very cold. Consider wearing a scarf." 160 | ``` 161 | 162 | \(예2\) `else`를 사용 163 | 164 | ```swift 165 | temperatureInFahrenheit = 40 166 | if temperatureInFahrenheit <= 32 { 167 | print("It's very cold. Consider wearing a scarf.") 168 | } else { 169 | print("It's not that cold. Wear a t-shirt.") 170 | } 171 | // Prints "It's not that cold. Wear a t-shirt." 172 | ``` 173 | 174 | \(예3\) `else`, `else-if`를 사용 175 | 176 | ```swift 177 | temperatureInFahrenheit = 90 178 | if temperatureInFahrenheit <= 32 { 179 | print("It's very cold. Consider wearing a scarf.") 180 | } else if temperatureInFahrenheit >= 86 { 181 | print("It's really warm. Don't forget to wear sunscreen.") 182 | } else { 183 | print("It's not that cold. Wear a t-shirt.") 184 | } 185 | // Prints "It's really warm. Don't forget to wear sunscreen." 186 | ``` 187 | 188 | \(예4\) `else-if` 만 사용 189 | 190 | ```swift 191 | temperatureInFahrenheit = 72 192 | if temperatureInFahrenheit <= 32 { 193 | print("It's very cold. Consider wearing a scarf.") 194 | } else if temperatureInFahrenheit >= 86 { 195 | print("It's really warm. Don't forget to wear sunscreen.") 196 | } 197 | ``` 198 | 199 | ### Switch 200 | 201 | Switch문의 기본 형태는 다음과 같습니다. 202 | 203 | ```swift 204 | switch some value to consider { 205 | case value 1: 206 | respond to value 1 207 | case value 2, 208 | value 3: 209 | respond to value 2 or 3 210 | default: 211 | otherwise, do something else 212 | } 213 | ``` 214 | 215 | 문자를 비교해 처리하는 경우 아래와 같이 사용할 수 있습니다. 216 | 217 | ```swift 218 | let someCharacter: Character = "z" 219 | switch someCharacter { 220 | case "a": 221 | print("The first letter of the alphabet") 222 | case "z": 223 | print("The last letter of the alphabet") 224 | default: 225 | print("Some other character") 226 | } 227 | // Prints "The last letter of the alphabet" 228 | ``` 229 | 230 | ### 암시적인 진행을 사용하지 않음 \(No Implicit Fallthrough\) 231 | 232 | C와 Objective-C의 `switch` 구문과는 달리 Swift의 `switch`구문은 암시적인 진행을 하지 않습니다. C나 Objective-C에서는 `switch` 구문이 기본적으로 모든 `case`를 순회하여 `default`를 만날 때까지 진행됩니다. 그래서 그것을 진행하지 않기 위해 `break`라는 문구를 명시적으로 적어야 했습니다. Swift에서는 `break`를 적지 않아도 특정 `case`가 완료되면 자동으로 `switch`구문을 빠져 나오게 됩니다. 이런 사용법으로 인해 실수로 `break`를 적지않아 의도하지 않은 `case`문이 실행되는 것을 방지해 줍니다. 233 | 234 | > _주의_ 235 | > `break`가 Swift에서 필수적이지는 않지만 `case`안에 특정 지점에서 멈추도록 하기 위해 `break`를 사용할 수 있습니다. 자세한 내용은 `Break`문을 참조해주세요. 236 | 237 | `case` 안 에 최소 하나의 실행 구문이 반드시 있어야 합니다. 238 | 239 | ```swift 240 | let anotherCharacter: Character = "a" 241 | switch anotherCharacter { 242 | case "a": // Invalid, case문에 body가 없으므로 에러가 발생합니다. 243 | case "A": 244 | print("The letter A") 245 | default: 246 | print("Not the letter A") 247 | } 248 | // 컴파일 에러 발생! 249 | ``` 250 | 251 | case 안에 콤마\(,\)로 구분해서 복수의 case 조건을 혼합\(compound\)해 사용할 수 있습니다. 252 | 253 | ```swift 254 | let anotherCharacter: Character = "a" 255 | switch anotherCharacter { 256 | case "a", "A": 257 | print("The letter A") 258 | default: 259 | print("Not the letter A") 260 | } 261 | // Prints "The letter A" 262 | ``` 263 | 264 | 가독성 때문에 혼합해 사용하는 경우를 여러 코드라인에 나눠서 적을 수 있습니다. 더 많은 정보는 혼합 케이스 \(Compound Cases\)에서 확인할 수 있습니다. 265 | 266 | > _주의_ 267 | > 명시적으로 switch-case 문의 특정 지점의 끝까지 실행하고 싶다면 `fallthrough` 키워드를 사용할 수 있습니다. 자세한 정보는 Fallthrough에서 확인할 수 있습니다. 268 | 269 | ### 인터벌 매칭 \(Interval Matching\) 270 | 271 | 숫자의 특정 범위를 조건으로 사용할 수 있습니다. 272 | 273 | ```swift 274 | let approximateCount = 62 275 | let countedThings = "moons orbiting Saturn" 276 | let naturalCount: String 277 | switch approximateCount { 278 | case 0: 279 | naturalCount = "no" 280 | case 1..<5: 281 | naturalCount = "a few" 282 | case 5..<12: 283 | naturalCount = "several" 284 | case 12..<100: 285 | naturalCount = "dozens of" 286 | case 100..<1000: 287 | naturalCount = "hundreds of" 288 | default: 289 | naturalCount = "many" 290 | } 291 | print("There are \(naturalCount) \(countedThings).") 292 | // Prints "There are dozens of moons orbiting Saturn." 293 | ``` 294 | 295 | ### 튜플 \(Tuple\) 296 | 297 | 튜플을 조건으로 사용할 수 있습니다. 298 | 299 | ```swift 300 | let somePoint = (1, 1) 301 | switch somePoint { 302 | case (0, 0): 303 | print("\(somePoint) is at the origin") 304 | case (_, 0): 305 | print("\(somePoint) is on the x-axis") 306 | case (0, _): 307 | print("\(somePoint) is on the y-axis") 308 | case (-2...2, -2...2): 309 | print("\(somePoint) is inside the box") 310 | default: 311 | print("\(somePoint) is outside of the box") 312 | } 313 | // Prints "(1, 1) is inside the box" 314 | ``` 315 | 316 | ### 값 바인딩 \(Value Bindings\) 317 | 318 | 특정 x, y 값을 각각 다른 case에 정의하고 그 정의된 상수를 또 다른 case에서 사용할 수 있습니다. 이런 기법을 값-바인딩\(value bindings\)라 부릅니다. 319 | 320 | ```swift 321 | let anotherPoint = (2, 0) 322 | switch anotherPoint { 323 | case (let x, 0): 324 | print("on the x-axis with an x value of \(x)") 325 | case (0, let y): 326 | print("on the y-axis with a y value of \(y)") 327 | case let (x, y): 328 | print("somewhere else at (\(x), \(y))") 329 | } 330 | // Prints "on the x-axis with an x value of 2" 331 | ``` 332 | 333 | ### Where 문 334 | 335 | `case`에 `where` 조건을 사용할 수 있습니다. 336 | 337 | ```swift 338 | let yetAnotherPoint = (1, -1) 339 | switch yetAnotherPoint { 340 | case let (x, y) where x == y: 341 | print("(\(x), \(y)) is on the line x == y") 342 | case let (x, y) where x == -y: 343 | print("(\(x), \(y)) is on the line x == -y") 344 | case let (x, y): 345 | print("(\(x), \(y)) is just some arbitrary point") 346 | } 347 | // Prints "(1, -1) is on the line x == -y" 348 | ``` 349 | 350 | ### 혼합 케이스 \(Compound Cases\) 351 | 352 | `case`에 콤마\(,\)로 구분해 여러 조건을 혼합해 사용할 수 있습니다. 353 | 354 | ```swift 355 | let someCharacter: Character = "e" 356 | switch someCharacter { 357 | case "a", "e", "i", "o", "u": 358 | print("\(someCharacter) is a vowel") 359 | case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", 360 | "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z": 361 | print("\(someCharacter) is a consonant") 362 | default: 363 | print("\(someCharacter) is not a vowel or a consonant") 364 | } 365 | // Prints "e is a vowel" 366 | ``` 367 | 368 | 혼합 케이스에서도 값-바인딩을 사용할 수 있습니다. 369 | 370 | ```swift 371 | let stillAnotherPoint = (9, 0) 372 | switch stillAnotherPoint { 373 | case (let distance, 0), (0, let distance): 374 | print("On an axis, \(distance) from the origin") 375 | default: 376 | print("Not on an axis") 377 | } 378 | // Prints "On an axis, 9 from the origin" 379 | ``` 380 | 381 | ## 제어 전송 구문 \(Control Transfer Statements\) 382 | 383 | 제어 전송 구문은 코드의 진행을 계속 할지 말지를 결정하거나, 실행되는 코드의 흐름을 바꾸기 위해 사용합니다. Swift에서는 다음 다섯 가지의 제어 전송 구문을 제공합니다. 384 | 385 | * continue 386 | * break 387 | * fallthrough 388 | * return 389 | * throw 390 | 391 | ### continue 문 392 | 393 | `continue`문은 현재 `loop`를 중지하고 다음 `loop`를 수행하도록 합니다. 394 | 395 | ```swift 396 | let puzzleInput = "great minds think alike" 397 | var puzzleOutput = "" 398 | let charactersToRemove: [Character] = ["a", "e", "i", "o", "u", " "] 399 | for character in puzzleInput { 400 | if charactersToRemove.contains(character) { 401 | continue 402 | } else { 403 | puzzleOutput.append(character) 404 | } 405 | } 406 | print(puzzleOutput) 407 | // Prints "grtmndsthnklk" 408 | ``` 409 | 410 | ### Break 문 411 | 412 | `break`문은 전체 제어문의 실행을 즉각 중지 시킵니다. `break`문은 `loop`나 `switch`문에서 사용할 수 있습니다. 413 | 414 | ```swift 415 | let numberSymbol: Character = "三" // 중국어로 3을 의미하는 문자입니다. 416 | var possibleIntegerValue: Int? 417 | switch numberSymbol { 418 | case "1", "١", "一", "๑": 419 | possibleIntegerValue = 1 420 | case "2", "٢", "二", "๒": 421 | possibleIntegerValue = 2 422 | case "3", "٣", "三", "๓": 423 | possibleIntegerValue = 3 424 | case "4", "٤", "四", "๔": 425 | possibleIntegerValue = 4 426 | default: 427 | break 428 | } 429 | if let integerValue = possibleIntegerValue { 430 | print("The integer value of \(numberSymbol) is \(integerValue).") 431 | } else { 432 | print("An integer value could not be found for \(numberSymbol).") 433 | } 434 | ``` 435 | 436 | ### fallthrough 문 437 | 438 | `fallthrough` 키워드는 이후의 `case`에 대해서도 실행하게 합니다. 앞에서 언급했던 것 처럼 Swift에서는 한번 특정 `case`를 타면 바로 그 switch 문은 종료됩니다. 마치 `case` 안에 `break`를 자동으로 넣은 것과 같은 기능을 하는 것이죠. 하지만 이 `fallthrough` 를 사용하면 이 자동으로 `break`가 사용되는 것을 막는 효과를 가져옵니다. 439 | 440 | ```swift 441 | let integerToDescribe = 5 442 | var description = "The number \(integerToDescribe) is" 443 | switch integerToDescribe { 444 | case 2, 3, 5, 7, 11, 13, 17, 19: 445 | description += " a prime number, and also" 446 | fallthrough 447 | default: 448 | description += " an integer." 449 | } 450 | print(description) 451 | // Prints "The number 5 is a prime number, and also an integer." 452 | ``` 453 | 454 | > _주의_ 455 | > fallthrough 는 case 조건을 확인하지 않고 그냥 다음 case를 실행하게 만듭니다. 456 | 457 | ### 레이블 구문 \(Labeled Statements\) 458 | 459 | 아래와 같은 형태로 `label` 이름과 `while` 조건을 넣어 특정 구문을 실행하는 구문으로 사용할 수 있습니다. 460 | 461 | ```swift 462 | label name: while condition { 463 | statements 464 | } 465 | ``` 466 | 467 | switch 문과 함께 사용할 수 있습니다. 468 | 469 | ```swift 470 | gameLoop: while square != finalSquare { 471 | diceRoll += 1 472 | if diceRoll == 7 { diceRoll = 1 } 473 | switch square + diceRoll { 474 | case finalSquare: 475 | // diceRoll will move us to the final square, so the game is over 476 | break gameLoop 477 | case let newSquare where newSquare > finalSquare: 478 | // diceRoll will move us beyond the final square, so roll again 479 | continue gameLoop 480 | default: 481 | // this is a valid move, so find out its effect 482 | square += diceRoll 483 | square += board[square] 484 | } 485 | } 486 | print("Game over!") 487 | ``` 488 | 489 | ### 이른 탈출 \(Early Exit\) 490 | 491 | `guard`문을 이용해 특정 조건을 만족하지 않으면 이 후 코드를 실행하지 않도록 방어코드를 작성할 수 있습니다. 492 | 493 | ```swift 494 | func greet(person: [String: String]) { 495 | guard let name = person["name"] else { 496 | return 497 | } 498 | 499 | print("Hello \(name)!") 500 | 501 | guard let location = person["location"] else { 502 | print("I hope the weather is nice near you.") 503 | return 504 | } 505 | 506 | print("I hope the weather is nice in \(location).") 507 | } 508 | 509 | greet(person: ["name": "John"]) 510 | // Prints "Hello John!" 511 | // Prints "I hope the weather is nice near you." 512 | greet(person: ["name": "Jane", "location": "Cupertino"]) 513 | // Prints "Hello Jane!" 514 | // Prints "I hope the weather is nice in Cupertino." 515 | ``` 516 | 517 | ## 이용가능한 API 버전 확인 \(Checking API Availability\) 518 | 519 | Swift에서는 기본으로 특정 플랫폼 \(iOS, macOS, tvOS, watchOS\)과 특정 버전을 확인하는 구문을 제공해 줍니다. 이 구문을 활용해 특정 플랫폼과 버전을 사용하는 기기에 대한 처리를 따로 할 수 있습니다. 구문의 기본 형태는 다음과 같습니다. 520 | 521 | ```swift 522 | if #available(platform name version, ..., *) { 523 | statements to execute if the APIs are available 524 | } else { 525 | fallback statements to execute if the APIs are unavailable 526 | } 527 | ``` 528 | 529 | 실제 사용 \(예\) 530 | 531 | ```swift 532 | if #available(iOS 10, macOS 10.12, *) { 533 | // Use iOS 10 APIs on iOS, and use macOS 10.12 APIs on macOS 534 | } else { 535 | // Fall back to earlier iOS and macOS APIs 536 | } 537 | ``` 538 | 539 | -------------------------------------------------------------------------------- /language-guide/06-functions.md: -------------------------------------------------------------------------------- 1 | # 함수 \(Functions\) 2 | 3 | ## **정의와 호출 \(Defining and Calling Functions\)** 4 | 5 | 함수를 선언할 때는 가장 앞에 `func` 키워드를 붙이고 `(person: String)` 파라미터와 형 그리고 `-> String` 형태로 반환형을 정의합니다. 6 | 7 | ```swift 8 | func greet(person: String) -> String { 9 | let greeting = "Hello, " + person + "!" 10 | return greeting 11 | } 12 | ``` 13 | 14 | 정의한 함수에 인자 값을 넣어 호출한 \(예\) 15 | 16 | ```swift 17 | print(greet(person: "Anna")) 18 | // Prints "Hello, Anna!" 19 | print(greet(person: "Brian")) 20 | // Prints "Hello, Brian!" 21 | ``` 22 | 23 | 위 함수에서 메시지를 결합하는 부분과 반환하는 부분을 합쳐서 더 짧게 만들 수 있습니다. 24 | 25 | ```swift 26 | func greetAgain(person: String) -> String { 27 | return "Hello again, " + person + "!" 28 | } 29 | print(greetAgain(person: "Anna")) 30 | // Prints "Hello again, Anna!" 31 | ``` 32 | 33 | ## 함수 파라미터와 반환 값 \(Function Parameters and Return Values\) 34 | 35 | ### 파라미터가 없는 함수 \(Functions Without Parameters\) 36 | 37 | ```swift 38 | func sayHelloWorld() -> String { 39 | return "hello, world" 40 | } 41 | print(sayHelloWorld()) 42 | // Prints "hello, world" 43 | ``` 44 | 45 | ### 복수의 파라미터를 사용하는 함수 \(Functions With Multiple Parameters\) 46 | 47 | ```swift 48 | func greet(person: String, alreadyGreeted: Bool) -> String { 49 | if alreadyGreeted { 50 | return greetAgain(person: person) 51 | } else { 52 | return greet(person: person) 53 | } 54 | } 55 | print(greet(person: "Tim", alreadyGreeted: true)) 56 | // Prints "Hello again, Tim!" 57 | ``` 58 | 59 | ### 반환 값이 없는 함수 \(Functions Without Return Values\) 60 | 61 | ```swift 62 | func greet(person: String) { 63 | print("Hello, \(person)!") 64 | } 65 | greet(person: "Dave") 66 | // Prints "Hello, Dave!" 67 | ``` 68 | 69 | > _주의_ 70 | > 엄밀히 말하면 위 함수는 반환 값을 선언하지 않았지만 반환 값이 있습니다. 반환 값이 정의 되지 않은 함수는 Void라는 특별한 형을 반환합니다. Void는 간단히 \(\)를 사용한 빈 튜플입니다. 71 | 72 | 함수의 반환 값은 아래와 같이 호출 될때 무시될 수 있습니다. 73 | 74 | ```swift 75 | func printAndCount(string: String) -> Int { 76 | print(string) 77 | return string.count 78 | } 79 | func printWithoutCounting(string: String) { 80 | let _ = printAndCount(string: string) 81 | } 82 | printAndCount(string: "hello, world") 83 | // prints "hello, world" and returns a value of 12 84 | printWithoutCounting(string: "hello, world") 85 | // prints "hello, world" but does not return a value 86 | ``` 87 | 88 | ### 복수의 값을 반환하는 함수 \(Functions with Multiple Return Values\) 89 | 90 | 튜플을 함수의 반환 값으로 사용할 수 있습니다. 91 | 92 | ```swift 93 | func minMax(array: [Int]) -> (min: Int, max: Int) { 94 | var currentMin = array[0] 95 | var currentMax = array[0] 96 | for value in array[1.. currentMax { 100 | currentMax = value 101 | } 102 | } 103 | return (currentMin, currentMax) 104 | } 105 | ``` 106 | 107 | 반환 값의 인자를 반환 값을 접근하는 접근자로 사용할 수 있습니다. 108 | 109 | ```swift 110 | let bounds = minMax(array: [8, -6, 2, 109, 3, 71]) 111 | print("min is \(bounds.min) and max is \(bounds.max)") 112 | // Prints "min is -6 and max is 109" 113 | ``` 114 | 115 | ### 옵셔널 튜플 반환형 \(Optional Tuple Return Types\) 116 | 117 | 위의 반환 값과 달리 반환 값에 `?` 물음표가 붙었습니다. `(min: Int, max: Int)?` 118 | 119 | ```swift 120 | func minMax(array: [Int]) -> (min: Int, max: Int)? { 121 | if array.isEmpty { return nil } 122 | var currentMin = array[0] 123 | var currentMax = array[0] 124 | for value in array[1.. currentMax { 128 | currentMax = value 129 | } 130 | } 131 | return (currentMin, currentMax) 132 | } 133 | ``` 134 | 135 | 실제 반환 값에 접근하기 위해서는 `if let`과 같은 옵셔널 체인을 사용하거나 강제 unwrapping을 해야 합니다. 아래는 옵셔널 체인을 사용한 \(예\)입니다. 136 | 137 | ```swift 138 | if let bounds = minMax(array: [8, -6, 2, 109, 3, 71]) { 139 | print("min is \(bounds.min) and max is \(bounds.max)") 140 | } 141 | // Prints "min is -6 and max is 109" 142 | ``` 143 | 144 | ### 함수 인자 라벨과 파라미터 이름 \(Function Argument Labels and Parameter Names\) 145 | 146 | 함수 호출시 적절한 파라미터 이름을 지정해 함수 내부와 함수 호출시 사용할 수 있습니다. 147 | 148 | ```swift 149 | func someFunction(firstParameterName: Int, secondParameterName: Int) { 150 | // 함수 내부에서 firstParameterName와 secondParameterName의 인자를 사용합니다. 151 | } 152 | someFunction(firstParameterName: 1, secondParameterName: 2) 153 | ``` 154 | 155 | ### 인자 라벨 지정 \(Specifying Argument Labels\) 156 | 157 | 파라미터 앞에 인자 라벨을 지정해 실제 함수 내부에서 해당 인자를 식별하기 위한 이름과 함수 호출시 사용하는 이름을 다르게 해서 사용할 수 있습니다. 158 | 159 | ```swift 160 | func someFunction(argumentLabel parameterName: Int) { 161 | // 함수 안애서 parameterName로 argumentLabel의 인자값을 참조할 수 있습니다. 162 | } 163 | ``` 164 | 165 | 인자 라벨을 지정해서 함수 내부에서는 `hometown`으로 값을 제어하고 함수 호출시에는 인자 값으로 `from`을 사용한 \(예\) 입니다. 166 | 167 | ```swift 168 | func greet(person: String, from hometown: String) -> String { 169 | return "Hello \(person)! Glad you could visit from \(hometown)." 170 | } 171 | print(greet(person: "Bill", from: "Cupertino")) 172 | // Prints "Hello Bill! Glad you could visit from Cupertino." 173 | ``` 174 | 175 | ### 인자 생략 \(Omitting Argument Labels\) 176 | 177 | 파라미터 앞에 `_`를 붙여 함수 호출시 인자값을 생략할 수 있습니다. 178 | 179 | ```swift 180 | func someFunction(_ firstParameterName: Int, secondParameterName: Int) { 181 | // 함수 안에서 firstParameterName, secondParameterName 182 | // 인자로 입력받은 첫번째, 두번째 값을 참조합니다. 183 | } 184 | someFunction(1, secondParameterName: 2) 185 | ``` 186 | 187 | ### 기본 파라미터 값 \(Default Parameter Values\) 188 | 189 | 함수의 파라미터 값에 기본 값\(`: Int = 12`\)을 설정할 수 있습니다. 기본 값이 설정 되어 있는 파라미터는 함수 호출시 생략할 수 있습니다. 기본 값을 사용하지 않는 파라미터를 앞에 위치 시켜야 함수를 의미있게 사용하기 쉽습니다. 190 | 191 | ```swift 192 | func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) { 193 | // 함수 호출시 두번째 인자를 생략하면 함수안에서 194 | // parameterWithDefault값은 12가 기본 값으로 사용됩니다. 195 | } 196 | someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6) // parameterWithDefault는 6 197 | someFunction(parameterWithoutDefault: 4) // parameterWithDefault는 12 198 | ``` 199 | 200 | ### 집합 파라미터 \(Variadic Parameters\) 201 | 202 | 인자 값으로 특정 형\(type\)의 집합 값을 사용할 수 있습니다. 203 | 204 | ```swift 205 | func arithmeticMean(_ numbers: Double...) -> Double { 206 | var total: Double = 0 207 | for number in numbers { 208 | total += number 209 | } 210 | return total / Double(numbers.count) 211 | } 212 | arithmeticMean(1, 2, 3, 4, 5) 213 | // returns 3.0, which is the arithmetic mean of these five numbers 214 | arithmeticMean(3, 8.25, 18.75) 215 | // returns 10.0, which is the arithmetic mean of these three numbers 216 | ``` 217 | 218 | ### 인-아웃 파라미터 \(In-Out Parameters\) 219 | 220 | 인자 값을 직접 변경하는 파라미터 입니다. 선언을 위해 파라미터 앞에 `inout` 이라는 키워드를 사용합니다. 아래는 인자 두 값을 변경하는 함수입니다. 221 | 222 | ```swift 223 | func swapTwoInts(_ a: inout Int, _ b: inout Int) { 224 | let temporaryA = a 225 | a = b 226 | b = temporaryA 227 | } 228 | ``` 229 | 230 | 아래는 실제로 사용하는 \(예\) 입니다. 함수의 인자에 변수를 넣을때 `&` 키워드를 넣었습니다. C언어를 아시는 분은 `inout`파라미터는 포인터를 넣는다고 생각하시면 이해하기 편하실 것입니다. 231 | 232 | ```swift 233 | var someInt = 3 234 | var anotherInt = 107 235 | swapTwoInts(&someInt, &anotherInt) 236 | print("someInt is now \(someInt), and anotherInt is now \(anotherInt)") 237 | // Prints "someInt is now 107, and anotherInt is now 3" 238 | ``` 239 | 240 | 두 변수의 실제 값이 변경 되었습니다. 241 | 242 | > _주의_ 243 | > 인-아웃 파라미터는 기본 값을 갖을 수 없고, 집합 파라미터는 `inout`으로 선언될 수 없습니다. 인-아웃 파라미터를 사용하는 것은 함수의 반환 값을 사용하지 않고 함수 scope 밖에 영향을 줄 수 있는 또 하나의 방법입니다. 244 | 245 | ## 함수 형 \(Function Types\) 246 | 247 | 함수의 형은 파라미터 형과\(parameter types\) 반환 형\(return type\)으로 구성 돼 있습니다. 아래 두 함수는 `Int`값 두 개를 입력받고 `Int`를 반환하는 함수입니다. 248 | 249 | ```swift 250 | func addTwoInts(_ a: Int, _ b: Int) -> Int { 251 | return a + b 252 | } 253 | func multiplyTwoInts(_ a: Int, _ b: Int) -> Int { 254 | return a * b 255 | } 256 | ``` 257 | 258 | 아래는 입력 받는 파라미터와 반환 값이 없는 함수입니다. 259 | 260 | ```swift 261 | func printHelloWorld() { 262 | print("hello, world") 263 | } 264 | ``` 265 | 266 | ### 함수 형의 사용 \(Using Function Types\) 267 | 268 | 아래와 같이 함수를 변수처럼 정의해서 사용할 수 있습니다. 269 | 270 | ```swift 271 | var mathFunction: (Int, Int) -> Int = addTwoInts 272 | ``` 273 | 274 | 변수 `mathFunction`는 `addTwoInts` 함수의 인자 값과 반환 값이 같으므로 이 함수가 변수로 할당 될 수 있습니다. 아래는 이렇게 변수에 함수를 할당해 사용한 \(예\) 입니다. 275 | 276 | ```swift 277 | print("Result: \(mathFunction(2, 3))") 278 | // Prints "Result: 5" 279 | ``` 280 | 281 | `multiplyTwoInts` 함수도 mathFunction과 함수 형이 같으므로 할당해 사용할 수 있습니다. 282 | 283 | ```swift 284 | mathFunction = multiplyTwoInts 285 | print("Result: \(mathFunction(2, 3))") 286 | // Prints "Result: 6" 287 | ``` 288 | 289 | 변수나 상수에 함수를 할당할 때 직접 함수 형을 선언하지 않아도 Swift가 형을 추론해\(Type Inferred\) 자동으로 함수를 할당할 수 있습니다. 290 | 291 | ```swift 292 | let anotherMathFunction = addTwoInts 293 | // anotherMathFunction is inferred to be of type (Int, Int) -> Int 294 | ``` 295 | 296 | ### 파라미터 형으로써의 함수 형 \(Function Types as Parameter Types\) 297 | 298 | 파라미터에 함수 형을 사용할 수 있습니다. 299 | 300 | ```swift 301 | func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) { 302 | print("Result: \(mathFunction(a, b))") 303 | } 304 | printMathResult(addTwoInts, 3, 5) 305 | // Prints "Result: 8" 306 | ``` 307 | 308 | ### 반환 형으로써의 함수 형 \(Function Types as Return Types\) 309 | 310 | 함수를 반환하는 함수를 만들수도 있습니다. 311 | 312 | ```swift 313 | func stepForward(_ input: Int) -> Int { 314 | return input + 1 315 | } 316 | func stepBackward(_ input: Int) -> Int { 317 | return input - 1 318 | } 319 | ``` 320 | 321 | 입력한 step에 하나를 더하거나 빼는 함수를 선언했습니다. 이 함수를 리턴값으로 사용할 수 있습니다. 아래 코드는 `backward`함수가 `true`냐 `false`냐에 따라 위에서 선언한 적절한 함수를 반환하는 함수입니다. 322 | 323 | ```swift 324 | func chooseStepFunction(backward: Bool) -> (Int) -> Int { 325 | return backward ? stepBackward : stepForward 326 | } 327 | ``` 328 | 329 | 위 함수를 사용한 \(예\)는 아래와 같습니다. 330 | 331 | ```swift 332 | var currentValue = 3 333 | let moveNearerToZero = chooseStepFunction(backward: currentValue > 0) 334 | // moveNearerToZero는 이제 stepBackward() 함수를 가르키고 있습니다. 335 | ``` 336 | 337 | `moveNearerToZero`를 호출할 때마다 `stepBackward()` 함수가 호출돼 입력 값이 1씩 줄어들어 결국 0이 됩니다. 338 | 339 | ```swift 340 | print("Counting to zero:") 341 | // Counting to zero: 342 | while currentValue != 0 { 343 | print("\(currentValue)... ") 344 | currentValue = moveNearerToZero(currentValue) 345 | } 346 | print("zero!") 347 | // 3... 348 | // 2... 349 | // 1... 350 | // zero! 351 | ``` 352 | 353 | ## 중첩 함수 \(Nested Functions\) 354 | 355 | 지금까지 함수는 전역적으로 동작하도록 선언했습니다. 함수 중에는 다른 함수 안의 `body`에서 동작하는 함수가 있는데 이 함수를 중첩 함수\(Nested Function\) 이라 합니다. 중첩함수는 함수 밖에서는 감춰져 있고 함수의 `body`내에서 접근 가능합니다. 위의 `chooseStepFunction`을 중첩 함수를 이용해 아래처럼 다시 작성할 수 있습니다. 356 | 357 | ```swift 358 | func chooseStepFunction(backward: Bool) -> (Int) -> Int { 359 | func stepForward(input: Int) -> Int { return input + 1 } 360 | func stepBackward(input: Int) -> Int { return input - 1 } 361 | return backward ? stepBackward : stepForward 362 | } 363 | var currentValue = -4 364 | let moveNearerToZero = chooseStepFunction(backward: currentValue > 0) 365 | // moveNearerToZero는 이제 중첩 돼 있는 stepForward() 함수를 가르킵니다. 366 | while currentValue != 0 { 367 | print("\(currentValue)... ") 368 | currentValue = moveNearerToZero(currentValue) 369 | } 370 | print("zero!") 371 | // -4... 372 | // -3... 373 | // -2... 374 | // -1... 375 | // zero! 376 | ``` 377 | 378 | -------------------------------------------------------------------------------- /language-guide/07-closures.md: -------------------------------------------------------------------------------- 1 | # 클로저 \(Closures\) 2 | 3 | 클로저 \(Closure\)는 코드블럭으로 C와 Objective-C의 블럭\(blocks\)과 다른 언어의 람다\(lambdas\)와 비슷 합니다. 클로저는 어떤 상수나 변수의 참조를 캡쳐\(capture\)해 저장할 수 있습니다. Swift는 이 캡쳐와 관련한 모든 메모리를 알아서 처리합니다. 4 | 5 | > 캡쳐의 개념에 대해 익숙하지 않다고 걱정하지 않으셔도 됩니다. 값 캡쳐는 아래에서 자세히 설명해 두었습니다. 6 | 7 | 전역 함수\(global functions\)와 중첩 함수\(nested function\)은 실제 클로저의 특별한 경우입니다. 클로저는 다음 세 가지 형태 중 하나를 갖습니다. 8 | 9 | * 전역 함수 : 이름이 있고 어떤 값도 캡쳐하지 않는 클로저 10 | * 중첩 함수 : 이름이 있고 관련한 함수로 부터 값을 캡쳐 할 수 있는 클로저 11 | * 클로저 표현 : 경량화 된 문법으로 쓰여지고 관련된 문맥\(context\)으로부터 값을 캡쳐할 수 있는 이름이 없는 클로저 12 | 13 | Swift에서 클로저 표현은 최적화 되어서 간결하고 명확합니다. 이 최적화에는 다음과 같은 내용을 포함합니다. 14 | 15 | * 문맥\(context\)에서 인자 타입\(parameter type\)과 반환 타입\(return type\)의 추론 16 | * 단일 표현 클로저에서의 암시적 반환 17 | * 축약된 인자 이름 18 | * 후위 클로저 문법 19 | 20 | 용어가 생소해 어렵게 느끼실 수 있지만, 하나하나 뜯어보면 어렵지 않고 쉽습니다. 그러니 쫄지마세요. 21 | 22 | ## 클로저 표현 \(Closure Expressions\) 23 | 24 | 클로저 표현은 인라인 클로저를 명확하게 표현하는 방법으로 문법에 초첨이 맞춰져 있습니다. 클로저 표현은 코드의 명확성과 의도를 잃지 않으면서도 문법을 축약해 사용할 수 있는 다양한 문법의 최적화 방법을 제공합니다. 25 | 26 | ### 정렬 메소드 \(The Sorted Method\) 27 | 28 | Swift의 표준 라이브러리에 `sorted(by:)`라는 알려진 타입의 배열 값을 정렬하는 메소드를 제공합니다. 여기 `by`에 어떤 방법으로 정렬을 수행할 것인지에 대해 기술한 클로저를 넣으면 그 방법대로 정렬된 배열을 얻을 수 있습니다. `sorted(by:)`메소드는 원본 배열은 변경하지 않습니다. 아래와 같이 이름으로 구성된 `names` 배열을 `sorted(by:)`메소드와 클로저를 이용해 정렬해 보겠습니다. 29 | 30 | ```swift 31 | let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] 32 | ``` 33 | 34 | `sorted(by:)` 메소드는 배열의 콘텐츠와 같은 타입을 갖고 두개의 인자를 갖는 클로저를 인자로 사용합니다. `name`의 콘텐츠는 String 타입이므로 `(String, String) -> Bool` 의 타입의 클로저를 사용해야 합니다. 35 | 36 | 클로저를 제공하는 일반적인 방법은 함수를 하나 만드는 것입니다. 위 타입을 만족 시키는 함수를 하나 만들면 정렬에 인자로 넣을 수 있는 클로저를 만들 수 있습니다. 37 | 38 | ```swift 39 | func backward(_ s1: String, _ s2: String) -> Bool { 40 | return s1 > s2 41 | } 42 | var reversedNames = names.sorted(by: backward) 43 | // reversedNames is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"] 44 | ``` 45 | 46 | `backward`클로저를 만들고 그것을 `names.sorted(by: backward)`에 넣으면 원본 배열의 순서가 바뀐 배열을 정렬 결과로 얻을 수 있습니다. 비교하는 클로저를 사용하는데 제법 긴 코드를 사용했는데, 앞으로 클로저의 다양한 문법 및 사용에 대해 알아 보겠습니다. 47 | 48 | ### 클로저 표현 문법 \(Closure Expression Syntax\) 49 | 50 | 클로저 표현 문법은 일반적으로 아래의 형태를 띱니다. 51 | 52 | ```swift 53 | { (parameters) -> return type in 54 | statements 55 | } 56 | ``` 57 | 58 | 인자로 넣을 `parameters`, 인자 값으로 처리할 내용을 기술하는 `statements` 그리고 `return type`입니다. 앞의 `backward`클로저를 이용해 배열을 정렬하는 코드는 클로저 표현을 이용해 다음과 같이 바꿀 수 있습니다. 59 | 60 | ```swift 61 | reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in 62 | return s1 > s2 63 | }) 64 | ``` 65 | 66 | 이렇게 함수로 따로 정의된 형태가 아닌 인자로 들어가 있는 형태의 클로저를 `인라인 클로저`라 부릅니다. 클로저의 몸통\(body\)은 `in` 키워드 다음에 시작합니다. 사용할 인자 값과\(parameters\) 반환 타입\(return type\)을 알았으니 이제 그것들을 적절히 처리해 넘겨 줄 수 있다는 뜻이죠. 클로저의 `body`가 짧으니 아래와 같이 한줄에 적을 수도 있습니다. 67 | 68 | ```swift 69 | reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } ) 70 | ``` 71 | 72 | ### 문맥에서 타입 추론 \(Inferring Type From Context\) 73 | 74 | 위 예제에서 정렬 클로저는 String 배열에서 `sorted(by:)` 메소드의 인자로 사용됩니다. `sorted(by:)`의 메소드에서 이미 `(String, String) -> Bool` 타입의 인자가 들어와야 하는지 알기 때문에 클로저에서 이 타입들은 생략 될 수 있습니다. 그래서 위 함수는 더 생략한 형태로 아래와 같이 기술 할 수 있습니다. 75 | 76 | ```swift 77 | reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } ) 78 | ``` 79 | 80 | 위와 같이 클로저의 인자 값과 반환 타입을 생략할 수 있지만, 가독성과 코드의 모호성을 피하기 위해 타입을 명시할 수도 있습니다. 81 | 82 | ### 단일 표현 클로저에서의 암시적 반환 \(Implicit Returns from Single-Express Closures\) 83 | 84 | 단일 표현 클로저에서는 반환 키워드를 생략할 수 있습니다. 85 | 86 | ```swift 87 | reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } ) 88 | ``` 89 | 90 | 이렇게 표현해도 어떠한 모호성도 없습니다. s1과 s2를 인자로 받아 그 두 값을 비교한 결과를 반환합니다. 91 | 92 | ### 인자 이름 축약 \(Shorthand Arguments Names\) 93 | 94 | Swift는 인라인 클로저에 자동으로 축약 인자 이름을 제공합니다. 이 인 자를 사용하면 인자 값을 순서대로 $0, $1, $2 등으로 사용할 수 있습니다. 축약 인자 이름을 사용하면 인자 값과 그 인자로 처리할 때 사용하는 인자가 같다는 것을 알기 때문에 인자를 입력 받는 부분과 `in` 키워드 부분을 생략 할 수 있습니다. 그러면 이런 형태로 축약 가능합니다. 95 | 96 | ```swift 97 | reversedNames = names.sorted(by: { $0 > $1 } ) 98 | ``` 99 | 100 | 축약은 되었지만 논리를 표현하는데는 지장이 없습니다. 인라인 클로저에 생략된 내용을 포함해 설명하면 1. $0과 $1 인자를 두개 받아서 2. $0이 $1 보다 큰지를 비교하고 3. 그 결과\(Bool\)를 반환해라. 입니다. 101 | 102 | ### 연산자 메소드 \(Operator Methods\) 103 | 104 | 축약이 이게 끝인줄 아셨겠지만 아닙니다. 😉 여기서 더 줄일 수 있습니다. Swift의 `String` 타입 연산자에는 `String`끼리 비교할 수 있는 비교 연산자\(>\) 를 구현해 두었습니다. 이 때문에 그냥 이 연산자를 사용하면 됩니다. 105 | 106 | ```swift 107 | reversedNames = names.sorted(by: >) 108 | ``` 109 | 110 | 더 많은 정보는 **연산자 메소드**를 참조하시기 바랍니다. 111 | 112 | ## 후위 클로저 \(Trailing Closures\) 113 | 114 | 만약 함수의 마지막 인자로 클로저를 넣고 그 클로저가 길다면 후위 클로저를 사용할 수 있습니다. 이런 형태의 함수와 클로저가 있다면 115 | 116 | ```swift 117 | func someFunctionThatTakesAClosure(closure: () -> Void) { 118 | // function body goes here 119 | } 120 | ``` 121 | 122 | 위 클로저의 인자 값 입력 부분과 반환 형 부분을 생략해 다음과 같이 표현할 수 있고 123 | 124 | ```swift 125 | someFunctionThatTakesAClosure(closure: { 126 | // closure's body goes here 127 | }) 128 | ``` 129 | 130 | 이것을 후위 클로저로 표현하면 아래와 같이 표현할 수 있습니다. 함수를 대괄호 \( {, } \)로 묶어 그 안에 처리할 내용을 적으면 됩니다. 모르고 사용하셨다면 이런 일반적인 전역함수 형태가 사실 클로저를 사용하고 있던 것이었습니다. 😉 131 | 132 | ```swift 133 | someFunctionThatTakesAClosure() { 134 | // trailing closure's body goes here 135 | } 136 | ``` 137 | 138 | 앞의 정렬 예제를 후위 클로저를 이용해 표현하면 이렇게 표현할 수 있습니다. 139 | 140 | ```swift 141 | reversedNames = names.sorted() { $0 > $1 } 142 | ``` 143 | 144 | 만약 함수의 마지막 인자가 클로저이고 후위 클로저를 사용하면 괄호`()`를 생략할 수 있습니다. 145 | 146 | ```swift 147 | reversedNames = names.sorted { $0 > $1 } 148 | ``` 149 | 150 | 이번에는 후위 클로저를 이용해 숫자\(Int\)를 문자\(String\)로 매핑\(Mapping\)하는 예제를 살펴 보겠습니다. 다음과 같은 문자와 숫자가 있습니다. 151 | 152 | ```swift 153 | let digitNames = [ 154 | 0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four", 155 | 5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine" 156 | ] 157 | let numbers = [16, 58, 510] 158 | ``` 159 | 160 | 이 값을 배열의 `map(_:)`메소드를 이용해 특정 값을 다른 특정 값으로 매핑하는 할 수 있는 클로저를 구현합니다. 161 | 162 | ```swift 163 | let strings = numbers.map { (number) -> String in 164 | var number = number 165 | var output = "" 166 | repeat { 167 | output = digitNames[number % 10]! + output 168 | number /= 10 169 | } while number > 0 170 | return output 171 | } 172 | // let strings는 타입 추론에 의해 문자 배열([String])타입을 갖습니다. 173 | // 결과는 숫자가 문자로 바뀐 ["OneSix", "FiveEight", "FiveOneZero"]가 됩니다. 174 | ``` 175 | 176 | 위 코드는 각 자리수를 구해서 그 자리수를 문자로 변환하고, 10으로 나눠서 자리수를 바꾸며 문자로 변환하는 것을 반복합니다. 이 과정을 통해 숫자 배열을, 문자 배열로 바꿀 수 있습니다. `number`값은 상수인데, 이 상수 값을 클로저 안에서 변수 `var`로 재정의 했기 때문에 `number`값의 변환이 가능합니다. 기본적으로 함수와 클로저에 넘겨지는 인자 값은 상수입니다. 177 | 178 | > `digitNames[number % 10]!`에 뒤에 느낌표\(!\)가 붙어있는 것은 사전\(dictionary\)의 `subscript`는 옵셔널이기 때문입니다. 즉, 사전에서 특정 `key`에 대한 값은 있을 수도 있고 없을 수도 있기 때문에 논리적으로 당연한 일입니다. 179 | 180 | ## 값 캡쳐 \(Capturing Values\) 181 | 182 | 클로저는 특정 문맥의 상수나 변수의 값을 캡쳐할 수 있습니다. 다시말해 원본 값이 사라져도 클로져의 `body`안에서 그 값을 활용할 수 있습니다. Swift에서 값을 캡쳐 하는 가장 단순한 형태는 `중첩 함수(nested function)` 입니다. 중첩 함수는 함수의 body에서 다른 함수를 다시 호출하는 형태로 된 함수 입니다. 예제를 보겠습니다. 183 | 184 | ```swift 185 | func makeIncrementer(forIncrement amount: Int) -> () -> Int { 186 | var runningTotal = 0 187 | func incrementer() -> Int { 188 | runningTotal += amount 189 | return runningTotal 190 | } 191 | return incrementer 192 | } 193 | ``` 194 | 195 | 이 함수는 `makeIncrementer` 함수 안에서 `incrementer`함수를 호출하는 형태로 중첩 함수입니다. 클로저의 인자와 반환 값이 보통의 경우와 달라 어렵게 보일 수 있는데, 이것도 역시 쪼개 보면 어렵지 않습니다.인자와 반환 값 `(forIncrement amount: Int) -> () -> Int` 중에 처음 `->` 를 기준으로 앞의 `(forIncrement amount: Int)` 부분이 인자 값이고 뒤 `() -> Int`는 반환 값입니다. 그렇습니다. 이것은 반환 값이 클로저인 형태입니다. 반환 값을 인자가 없고 `Int`형의 클로저를 반환한다는 의미입니다. 함수 안의 `incrementer`함수만 따로 보겠습니다. 196 | 197 | ```swift 198 | func incrementer() -> Int { 199 | runningTotal += amount 200 | return runningTotal 201 | } 202 | ``` 203 | 204 | `runningTotal`과 `amount`도 없습니다. 하지만 이 함수는 돌아갑니다. 그것은 `runningTotal`과 `amount`가 캡쳐링 되서 그런 것 입니다. 205 | 206 | > 최적화 이유로 Swift는 만약 더 이상 클로저에 의해 값이 사용되지 않으면 그 값을 복사해 저장하거나 캡쳐링 하지 않습니다. Swift는 또 특정 변수가 더 이상 필요하지 않을 때 제거하는 것과 관련한 모든 메모리 관리를 알아서 처리합니다. 207 | 208 | 이제 위 중첩 함수를 실행해 보겠습니다. 209 | 210 | ```swift 211 | let incrementByTen = makeIncrementer(forIncrement: 10) 212 | ``` 213 | 214 | `makeIncrementer`함수는 클로저를 반환합니다. 여기서는 `makeIncrementer` 내부의 `incrementer` 함수를 실행하는 메소드를 반환합니다. 215 | 216 | ```swift 217 | incrementByTen() 218 | // 값으로 10을 반환합니다. 219 | incrementByTen() 220 | // 값으로 20을 반환합니다. 221 | incrementByTen() 222 | // 값으로 30을 반환합니다. 223 | ``` 224 | 225 | 함수가 각기 실행 되지만 실제로는 변수 `runningTotal`과 `amount`가 캡쳐링 되서 그 변수를 공유하기 때문에 계산이 누적된 결과를 갖습니다. 만약 아래와 같이 새로운 클로저를 생성하면 어떻까요? 226 | 227 | ```swift 228 | let incrementBySeven = makeIncrementer(forIncrement: 7) 229 | incrementBySeven() 230 | // returns a value of 7 231 | ``` 232 | 233 | 네, 다른 클로저이기 때문에 고유의 저장소에 `runningTotal`과 `amount`를 캡쳐링 해서 사용합니다. 그래서 다른 값이 나옵니다. 그렇다면 여기서 이전의 클로저를 실행하면 어떻게 될까요? 234 | 235 | ```swift 236 | incrementByTen() 237 | // 값으로 40을 반환합니다. 238 | ``` 239 | 240 | 네, 다른 클로저이기 때문에 연산에 전혀 영향이 없습니다. 그냥 다른 저장소의 변수를 사용해 계산합니다. 241 | 242 | > 만약 클로저를 어떤 클래스 인스턴스의 프로퍼티로 할당하고 그 클로저가 그 인스턴스를 캡쳐링하면 강한 순환참조에 빠지게 됩니다. 즉, 인스턴스의 사용이 끝나도 메모리를 해제하지 못하는 것이죠. 그래서 Swift는 이 문제를 다루기 위해 캡쳐 리스트\(capture list\)를 사용합니다. 더 많은 정보는 **클로저의 강한 참조 순환**을 참조하세요. 243 | 244 | ## 클로저는 참조 타입 \(Closures Are Reference Types\) 245 | 246 | 앞의 예제에서 `incrementBySeven`과 `incrementByTen`은 상수입니다. 그런데 어떻게 `runningTotal`변수를 계속 증가 시킬 수 있는 걸까요? 답은 함수와 클로저는 참조 타입이기 때문입니다. 함수와 클로저를 상수나 변수에 할당할 때 실제로는 상수와 변수에 해당 함수나 클로저의 참조\(reference\)가 할당 됩니다. 그래서 만약 한 클로저를 두 상수나 변수에 할당하면 그 두 상수나 변수는 같은 클로저를 참조하고 있습니다. C나 C++에 익숙하신 분은 함수 포인터를 저장한다고 생각하시면 이해하기 쉬우실 것입니다. 247 | 248 | ```swift 249 | let alsoIncrementByTen = incrementByTen 250 | alsoIncrementByTen() 251 | // 50을 반환합니다. 252 | ``` 253 | 254 | 앞에서 사용했던 클로저를 상수에 할당하고 실행시키면 사용한 클로저의 마지막 상태에서 10을 증가시켜 결과 값으로 50을 반환하게 됩니다. 255 | 256 | ## 이스케이핑 클로저 \(Escaping Closures\) 257 | 258 | 클로저를 함수의 파라미터로 넣을 수 있는데, 함수 밖\(함수가 끝나고\)에서 실행되는 클로저 예를들어, 비동기로 실행되거나 `completionHandler`로 사용되는 클로저는 파라미터 타입 앞에 `@escaping`이라는 키워드를 명시해야 합니다. 259 | 260 | ```swift 261 | var completionHandlers: [() -> Void] = [] 262 | func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) { 263 | completionHandlers.append(completionHandler) 264 | } 265 | ``` 266 | 267 | 위 함수에서 인자로 전달된 `completionHandler`는 `someFunctionWithEscapingClosure` 함수가 끝나고 나중에 처리 됩니다. 만약 함수가 끝나고 실행되는 클로저에 `@escaping` 키워드를 붙이지 않으면 컴파일시 오류가 발생합니다. 268 | 269 | `@escaping` 를 사용하는 클로저에서는 `self`를 명시적으로 언급해야 합니다. 270 | 271 | ```swift 272 | func someFunctionWithNonescapingClosure(closure: () -> Void) { 273 | closure() // 함수 안에서 끝나는 클로저 274 | } 275 | 276 | class SomeClass { 277 | var x = 10 278 | func doSomething() { 279 | someFunctionWithEscapingClosure { self.x = 100 } // 명시적으로 self를 적어줘야 합니다. 280 | someFunctionWithNonescapingClosure { x = 200 } 281 | } 282 | } 283 | 284 | let instance = SomeClass() 285 | instance.doSomething() 286 | print(instance.x) 287 | // Prints "200" 288 | 289 | completionHandlers.first?() 290 | print(instance.x) 291 | // Prints "100" 292 | ``` 293 | 294 | ## 자동클로저 \(Autoclosures\) 295 | 296 | 자동클로저는 인자 값이 없으며 특정 표현을 감싸서 다른 함수에 전달 인자로 사용할 수 있는 클로저입니다. 자동클로저는 클로저를 실행하기 전까지 실제 실행이 되지 않습니다. 그래서 계산이 복잡한 연산을 하는데 유용합니다. 왜냐면 실제 계산이 필요할 때 호출되기 때문입니다. 예제를 보면서 무슨 뜻인지 알아 보겠습니다. 297 | 298 | ```swift 299 | var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"] 300 | print(customersInLine.count) 301 | // Prints "5" 302 | 303 | let customerProvider = { customersInLine.remove(at: 0) } 304 | print(customersInLine.count) 305 | // Prints "5" 306 | 307 | print("Now serving \(customerProvider())!") 308 | // Prints "Now serving Chris!" 309 | print(customersInLine.count) 310 | // Prints "4" 311 | ``` 312 | 313 | 위 예제 코드를 보면 `let customerProvider = { customersInLine.remove(at: 0) }` 이 클로저 코드를 지났음에도 불구하고 `customersInLine.count` 는 변함없이 5인 것을 볼 수 있습니다. 그리고 그 클로저를 실행시킨 `print("Now serving \(customerProvider())!")` 이후에야 배열에서 값이 하나 제거되어 배열의 원소 개수가 4로 줄어든 것을 확인할 수 있습니다. 이렇듯 자동 클로저는 적혀진 라인 순서대로 바로 실행되지 않고, 실제 사용될 때 지연 호출 됩니다. 314 | 315 | 자동클로저를 함수의 인자 값으로 넣는 예제는 아래와 같습니다. 316 | 317 | ```swift 318 | // customersInLine is ["Alex", "Ewa", "Barry", "Daniella"] 319 | func serve(customer customerProvider: () -> String) { 320 | print("Now serving \(customerProvider())!") 321 | } 322 | serve(customer: { customersInLine.remove(at: 0) } ) 323 | // Prints "Now serving Alex!" 324 | ``` 325 | 326 | `serve`함수는 인자로 `() -> String)` 형, 즉 인자가 없고, `String`을 반환하는 클로저를 받는 함수 입니다. 그리고 이 함수를 실행할 때는 `serve(customer: { customersInLine.remove(at: 0) } )`이와 같이 클로저`{ customersInLine.remove(at: 0) }`를 명시적으로 직접 넣을 수 있습니다. 327 | 328 | 위 예제에서는 함수의 인자로 클로저를 넣을 때 명시적으로 넣는 경우에 대해 알아 보았습니다. 위 예제를 `@autoclosure`키워드를 이용해서 보다 간결하게 사용할 수 있습니다. 예제를 보시겠습니다. 329 | 330 | ```swift 331 | // customersInLine is ["Ewa", "Barry", "Daniella"] 332 | func serve(customer customerProvider: @autoclosure () -> String) { 333 | print("Now serving \(customerProvider())!") 334 | } 335 | serve(customer: customersInLine.remove(at: 0)) 336 | // Prints "Now serving Ewa!" 337 | ``` 338 | 339 | `serve`함수의 인자를 받는 부분 `customerProvider: @autoclosure ()` 에서 클로저의 인자`()`앞에 `@autoclosure`라는 키워드를 붙였습니다. 이 키워드를 붙임으로써 인자 값은 자동으로 클로저로 변환됩니다. 그래서 함수의 인자 값을 넣을 때 클로저가 아니라 클로저가 반환하는 반환 값과 일치하는 형의 함수를 인자로 넣을 수 있습니다. 그래서 `serve(customer: { customersInLine.remove(at: 0) } )` 이런 코드를 `@autoclosure`키워드를 사용했기 때문에 `serve(customer: customersInLine.remove(at: 0))` 이렇게 `{}` 없이 사용할 수 있습니다. 정리하면 클로저 인자에 `@autoclosure`를 선언하면 함수가 이미 클로저 인것을 알기 때문에 리턴값 타입과 같은 값을 넣어줄 수 있습니다. 340 | 341 | > NOTE 자동클로저를 너무 남용하면 코드를 이해하기 어려워 질 수 있습니다. 그래서 문맥과 함수 이름이 `autoclosure`를 사용하기에 분명해야 합니다. 342 | 343 | 자동클로저`@autoclosure`는 이스케이프`@escaping`와 같이 사용할 수 있습니다. 동작에 대한 설명은 코드에 직접 주석을 달았습니다. 344 | 345 | ```swift 346 | // customersInLine is ["Barry", "Daniella"] 347 | var customerProviders: [() -> String] = [] // 클로저를 저장하는 배열을 선언 348 | func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) { 349 | customerProviders.append(customerProvider) 350 | } // 클로저를 인자로 받아 그 클로저를 customerProviders 배열에 추가하는 함수를 선언 351 | collectCustomerProviders(customersInLine.remove(at: 0)) // 클로저를 customerProviders 배열에 추가 352 | collectCustomerProviders(customersInLine.remove(at: 0)) 353 | 354 | print("Collected \(customerProviders.count) closures.") 355 | // Prints "Collected 2 closures." // 2개의 클로저가 추가 됨 356 | for customerProvider in customerProviders { 357 | print("Now serving \(customerProvider())!") // 클로저를 실행하면 배열의 0번째 원소를 제거하며 그 값을 출력 358 | } 359 | // Prints "Now serving Barry!" 360 | // Prints "Now serving Daniella!" 361 | ``` 362 | 363 | `collectCustomerProviders`함수의 인자 `customerProvider`는 `@autoclosure`이면서 `@escaping`로 선언되었습니다. `@autoclosure`로 선언됐기 때문에 함수의 인자로 리턴값 `String`만 만족하는 `customersInLine.remove(at: 0)`형태로 함수 인자에 넣을 수 있고, 이 클로저는 `collectCustomerProviders`함수가 종료된 후에 실행되는 클로저 이기 때문에 인자 앞에 `@escaping` 키워드를 붙여주었습니다. 364 | 365 | -------------------------------------------------------------------------------- /language-guide/08-enumerations.md: -------------------------------------------------------------------------------- 1 | # 열거형 \(Enumerations\) 2 | 3 | 열거형은 관련된 값으로 이루어진 그룹을 공통의 형으로\(type\) 선언해 형 안전성\(type-safety\)을 보장하는 방법으로 코드를 다룰 수 있게 해줍니다. C나 Objective-C가 Integer값들로 열거형을 구성한 것에 반해 Swift에서는 case값이 string, character, integer, floting 값들을 사용할 수 있습니다. 열거형은 1급 클래스 형\(first-class types\)이어서 계산된 프로퍼티\(computed properties\)를 제공하거나 초기화를 지정하거나, 초기 선언을 확장해 사용할 수 있습니다. 4 | 5 | ## 열거형 문법 \(Enumeration Syntax\) 6 | 7 | `enum`키워드를 사용해 열거형을 정의합니다. 8 | 9 | ```swift 10 | enum SomeEnumeration { 11 | // enumeration definition goes here 12 | } 13 | ``` 14 | 15 | 다음은 네 가지 방향을 갖는 `CompassPoint` 열거형 선언의 \(예\)입니다. 16 | 17 | ```swift 18 | enum CompassPoint { 19 | case north 20 | case south 21 | case east 22 | case west 23 | } 24 | ``` 25 | 26 | > C나 Objective-C 와는 다르게 Swift에서 열거형은 생성될 때 각 case 별로 기본 integer값을 할당하지 않습니다. 위 `CompassPoint`를 예로 들면, north, south, east, west는 각각 암시적으로 0, 1, 2, 3값을 갖지 않습니다. 대신 Swift에서 열거형의 각 case는 CompassPoint으로 선언된 온전한 값입니다. 27 | 28 | 여러 case를 콤마\(,\)로 구분해서 한줄에 적을 수 있습니다. 29 | 30 | ```swift 31 | enum Planet { 32 | case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune 33 | } 34 | ``` 35 | 36 | 각 열거형 정의는 완전 새로운 형\(type\)을 정의합니다. Swift의 다른 형\(types\)과 마찬가지로 형의 이름은 대문자로 \(CompassPoint나 Planet 같이\) 시작해야 합니다. 37 | 38 | ```swift 39 | var directionToHead = CompassPoint.west 40 | ``` 41 | 42 | `directionToHead`의 형은 초기화 될 때 타입추론이 돼서 `CompassPoint`형을 갖게 됩니다. `directionToHead`의 형이 `CompassPoint`로 한번 정의되면 다음에 값을 할당할 때 형을 생략한 점 문법\(dot syntax\)을 이용해 값을 할당하는 축약형 문법을 사용할 수 있습니다. 43 | 44 | ```swift 45 | directionToHead = .east 46 | ``` 47 | 48 | ## Switch 구문에서 열거형 값 매칭하기 \(Matching Enumeration Values with a Switch Statement\) 49 | 50 | 각 열거형 값을 `Switch` 문에서 매칭할 수 있습니다. 51 | 52 | ```swift 53 | directionToHead = .south 54 | switch directionToHead { 55 | case .north: 56 | print("Lots of planets have a north") 57 | case .south: 58 | print("Watch out for penguins") 59 | case .east: 60 | print("Where the sun rises") 61 | case .west: 62 | print("Where the skies are blue") 63 | } 64 | // Prints "Watch out for penguins" 65 | ``` 66 | 67 | `switch`문은 반드시 열거형의 모든 경우\(cases\)를 완전히 포함해야 합니다. 만약 위에서 `case .west`가 생략되었다면 코드는 컴파일 되지 않습니다. 만약 열거형의 모든 cases의 처리를 기술하는게 적당하지 않다면 기본\(default\) case를 제공함으로써 처리되지 않는 case를 피할 수 있습니다. 68 | 69 | ```swift 70 | let somePlanet = Planet.earth 71 | switch somePlanet { 72 | case .earth: 73 | print("Mostly harmless") 74 | default: 75 | print("Not a safe place for humans") 76 | } 77 | ``` 78 | 79 | ## 관련 값 \(Associated Values\) 80 | 81 | 열거형의 각 case에 custom type의 추가적인 정보를 저장할 수 있습니다. 82 | 83 | ![](../.gitbook/assets/5a40911d-d7ab-489f-9cb3-ccb600be7be8.png) 84 | 85 | ![](../.gitbook/assets/8b9e80a3-7fb1-4e96-bffd-f9e6f2505b75.png) 86 | 87 | 예를 들어 바코드가 위와 같이 4가지 구분으로 이루어진 숫자로 이루어진 종류가 있거나, 2,953개의 문자로 구성된 QR코드 형태로 이루어진 두 가지 종류가 있다면 이 바코드를 아래와 같은 열거형으로 정의할 수 있습니다. 88 | 89 | ```swift 90 | enum Barcode { 91 | case upc(Int, Int, Int, Int) 92 | case qrCode(String) 93 | } 94 | ``` 95 | 96 | 관련 값을 이용하면 위와 같이 같은 형이지만, 다른 형태의 값을 갖는 case를 만들 수 있습니다. 바코드는 아래와 같이 선언할 수 있고 97 | 98 | ```swift 99 | var productBarcode = Barcode.upc(8, 85909, 51226, 3) 100 | ``` 101 | 102 | 이렇게 선언하는 것도 가능합니다. 103 | 104 | ```swift 105 | productBarcode = .qrCode("ABCDEFGHIJKLMNOP") 106 | ``` 107 | 108 | 관련 값은 `switch case`문에서 사용할 때 상수 혹은 변수로 선언할 수 있습니다. 109 | 110 | ```swift 111 | switch productBarcode { 112 | case .upc(let numberSystem, let manufacturer, let product, let check): 113 | print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).") 114 | case .qrCode(let productCode): 115 | print("QR code: \(productCode).") 116 | } 117 | // Prints "QR code: ABCDEFGHIJKLMNOP." 118 | ``` 119 | 120 | case 안의 관련 값이 전부 상수이거나 변수이면 공통된 값을 case 뒤에 선언해서 보다 간결하게 기술할 수 있습니다. \(공통된 `let`을 앞으로 빼내 간결하게 기술한 코드\) 121 | 122 | ```swift 123 | switch productBarcode { 124 | case let .upc(numberSystem, manufacturer, product, check): 125 | print("UPC : \(numberSystem), \(manufacturer), \(product), \(check).") 126 | case let .qrCode(productCode): 127 | print("QR code: \(productCode).") 128 | } 129 | // Prints "QR code: ABCDEFGHIJKLMNOP." 130 | ``` 131 | 132 | ## Raw 값 \(Raw Values\) 133 | 134 | C와 Objective-C 같이 case에 raw 값을 지정할 수 있습니다. 135 | 136 | ```swift 137 | enum ASCIIControlCharacter: Character { 138 | case tab = "\t" 139 | case lineFeed = "\n" 140 | case carriageReturn = "\r" 141 | } 142 | ``` 143 | 144 | 위 예제에서는 `Character`형의 raw값으로 정의했지만, `String`, `Character`, `Integer`,`Float`등의 형을 사용할 수도 있습니다. 단, 각 raw값은 열거형 선언에서 유일한 값으로 중복되어서는 안됩니다. 145 | 146 | > Raw값은 관계 값\(associated value\)과는 다릅니다. Raw값은 코드에서 열거형을 처음 선언할 때 정의되서 특정 열거형의 raw값은 항상 같은 값을 갖습니다. 하지만 관계 값은 같은 case라도 생성될 때 달라질 수 있습니다. 147 | 148 | ### 암시적으로 할당된 Raw 값 \(Implicitly Assigned Raw Values\) 149 | 150 | 열거형을 다루면서 raw값으로 Integer나 String 값을 사용할 수 있는데, 각 case별로 명시적으로 raw값을 할당할 필요는 없습니다. 만약 raw값을 할당하지 않으면 Swift에서 자동으로 값을 할당해 줍니다. 151 | 152 | ```swift 153 | enum Planet: Int { 154 | case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune 155 | } 156 | ``` 157 | 158 | 위 경우는 mercury에 1을 raw 값으로 명시적으로 할당했고 venus는 암시적으로 2 그리고 이후 값은 1증가 된 값을 자동으로 raw값으로 갖게 됩니다. 만약 `String`을 raw값으로 사용한다면 case텍스트가 raw값으로 자동으로 raw값으로 할당됩니다. 159 | 160 | ```swift 161 | enum CompassPoint: String { 162 | case north, south, east, west 163 | } 164 | ``` 165 | 166 | 위 경우 `CompassPoint.south`는 암시적으로 `"south"`를 raw값으로 갖습니다. 167 | 168 | raw값은 `rawValue` 프로퍼티를 사용해 접근할 수 있습니다. 169 | 170 | ```swift 171 | let earthsOrder = Planet.earth.rawValue 172 | // earthsOrder is 3 173 | 174 | let sunsetDirection = CompassPoint.west.rawValue 175 | // sunsetDirection is "west" 176 | ``` 177 | 178 | ### Raw 값을 이용한 초기화 \(Initializing from a Raw Value\) 179 | 180 | raw값을 이용해 열거형 변수를 초기화 할 수 있습니다. 아래 예제는 raw값 7을 갖는 값을 열거형 변수의 초기 값으로 지정합니다. 그 값은 Uranus입니다. 181 | 182 | ```swift 183 | let possiblePlanet = Planet(rawValue: 7) 184 | // possiblePlanet is of type Planet? and equals Planet.uranus 185 | ``` 186 | 187 | > raw 값 초기자는 모든 raw값에 대해 열거형 case를 반환이 보장되지 않으므로 실패할 수 있는 초기자\(failable initializer\)입니다. 188 | 189 | 만약 열거형에 지정된 raw값이 없는 값으로 초기자를 지정하면 그 값은 `nil`이 됩니다. 190 | 191 | ```swift 192 | let positionToFind = 11 193 | if let somePlanet = Planet(rawValue: positionToFind) { 194 | switch somePlanet { 195 | case .earth: 196 | print("Mostly harmless") 197 | default: 198 | print("Not a safe place for humans") 199 | } 200 | } else { 201 | print("There isn't a planet at position \(positionToFind)") 202 | } 203 | // Prints "There isn't a planet at position 11" 204 | ``` 205 | 206 | ### 재귀 열거자 \(Recursive Enumerations\) 207 | 208 | 재귀 열거자는 다른 열거 인스턴스를 관계 값으로 갖는 열거형입니다. 재귀 열거자 case는 앞에 `indirect`키워드를 붙여 표시합니다. 209 | 210 | ```swift 211 | enum ArithmeticExpression { 212 | case number(Int) 213 | indirect case addition(ArithmeticExpression, ArithmeticExpression) 214 | indirect case multiplication(ArithmeticExpression, ArithmeticExpression) 215 | } 216 | ``` 217 | 218 | 만약 관계 값을 갖는 모든 열거형 case에 `indirect`표시를 하고 싶으면 `enum`키워드 앞에 `indirect`표시를 하면 됩니다. 219 | 220 | ```swift 221 | indirect enum ArithmeticExpression { 222 | case number(Int) 223 | case addition(ArithmeticExpression, ArithmeticExpression) 224 | case multiplication(ArithmeticExpression, ArithmeticExpression) 225 | } 226 | ``` 227 | 228 | 아래 예제는 \( 5 + 4 \) \* 2를 재귀 열거자로 표현한 것입니다. 229 | 230 | ```swift 231 | let five = ArithmeticExpression.number(5) 232 | let four = ArithmeticExpression.number(4) 233 | let sum = ArithmeticExpression.addition(five, four) 234 | let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2)) 235 | ``` 236 | 237 | 다음은 위 재귀 열거자를 처리하는 함수입니다. 238 | 239 | ```swift 240 | func evaluate(_ expression: ArithmeticExpression) -> Int { 241 | switch expression { 242 | case let .number(value): 243 | return value 244 | case let .addition(left, right): 245 | return evaluate(left) + evaluate(right) 246 | case let .multiplication(left, right): 247 | return evaluate(left) * evaluate(right) 248 | } 249 | } 250 | 251 | print(evaluate(product)) 252 | // Prints "18" 253 | ``` 254 | 255 | -------------------------------------------------------------------------------- /language-guide/09-classes-and-structures.md: -------------------------------------------------------------------------------- 1 | # 클래스과 구조체 \(Classes and Structures\) 2 | 3 | 클래스와 구조체는 프로그램의 코드를 조직화 하기 위해 일반적으로 사용합니다. OOP를 위한 필요 요소이기도 합니다. Swift는 다른 프로그래밍 언어와 다르게 `interface`파일과 `implementation`파일을 분리해서 만들지 않아도 됩니다. 하나의 파일에 구조체나 클래스를 정의하면, Swift가 자동으로 알아서 해당 클래스와 구조체를 사용할 수 있는 인터페이스를 생성해 줍니다. 4 | 5 | ## 클래스와 구조체의 비교 \(Comparing Classes and Structures\) 6 | 7 | Swift에서 클래스와 구조체는 많은 공통점이 있습니다. 클래스와 구조체 둘다 다음과 같은 기능이 가능합니다. 8 | 9 | * 값을 저장하기 위한 프로퍼티 정의 10 | * 기능을 제공하기 위한 메소드 정의 11 | * `subscript` 문법을 이용해 특정 값을 접근할 수 있는 `subscript` 정의 12 | * 초기 상태를 설정할 수 있는 `initializer` 정의 13 | * 기본 구현에서 기능 확장 14 | * 특정한 종류의 표준 기능을 제공하기 위한 프로토콜 순응\(conform\) 15 | 16 | 더 많은 정보는 프로퍼티, 메소드, 서브스크립트, 초기화, 확장 그리고 프로토콜을 참조 17 | 18 | 구조체로는 가능하지 않고 클래스만 가능한 기능은 아래와 같습니다. 19 | 20 | * 상속 \(Inheritance\) : 클래스의 여러 속성을 다른 클래스에 물려 줌 21 | * 타입 캐스팅 \(Type casting\) : 런타임에 클래스 인스턴스의 타입을 확인 22 | * 소멸자 \(Deinitializers\) : 할당된 자원을 해제\(free up\) 시킴 23 | * 참조 카운트 \(Reference counting\) : 클래스 인스턴스에 하나 이상의 참조가 가능 24 | 25 | 더 많은 정보는 상속, 타입캐스팅, 소멸자 그리고 자동 참조 카운트를 참조 26 | 27 | > NOTE 28 | > 구조체는 다른 코드로 전달될 때 항상 복사되서 전달되고, 참조 카운트\(Reference counting\)를 사용하지 않습니다. 29 | 30 | ### 선언 문법 \(Definition Syntax\) 31 | 32 | 클래스와 구조체 둘다 비슷한 선언 문법을 갖고 있습니다. 클래스는 `class` 키워드를 구조체는 `struct` 키워드를 이름 앞에 적어서 선언할 수 있습니다. 33 | 34 | ```swift 35 | class SomeClass { 36 | // 클래스 내용은 여기에 37 | } 38 | struct SomeStructure { 39 | // 구조체 내용은 여기에 40 | } 41 | ``` 42 | 43 | > NOTE 44 | > 새로운 클래스나 구조체를 선언할 때마다 Swift 에서 완전 새로운 타입을 선언하는 것입니다. 그래서 이름을 다른 표준 Swift 타입\(String, Int, Bool\)과 같이 UpperCamelCase 이름\(SomeClass, SomeStructure 등\)으로 선언합니다. 반대로 프로퍼티나 메소드는 lowerCamelCase\(frameRate, incrementCount 등\)으로 선언합니다. 45 | 46 | 아래는 각각 구조체 선언과 클래스 선언의 예입니다. 47 | 48 | ```swift 49 | struct Resolution { 50 | var width = 0 51 | var height = 0 52 | } 53 | class VideoMode { 54 | var resolution = Resolution() // 위 Resolution 구조체를 값으로 사용 55 | var interlaced = false 56 | var frameRate = 0.0 57 | var name: String? 58 | } 59 | ``` 60 | 61 | 구조체 `Resolution`의 프로퍼티 `width` 와 `height`는 초기 값으로 0을 할당 했기 때문에 Swift의 타입추론에 의해 자동으로 `Int`형을 갖게 됩니다. 62 | 63 | ### 클래스와 구조체 인스턴스 \(Class and Structure Instances\) 64 | 65 | 클래스와 구조체 이름 뒤에 빈 괄호를 적으면 각각의 인스턴스를 생성할 수 있습니다. 66 | 67 | ```swift 68 | let someResolution = Resolution() // 구조체 인스턴스 생성 69 | let someVideoMode = VideoMode() // 클래스 인스턴스 생성 70 | ``` 71 | 72 | ### 프로퍼티 접근 \(Accessing Properties\) 73 | 74 | 점\(dot\) 문법을 통해 클래스/구조체 인스턴스의 프로퍼티에 접근할 수 있습니다. 75 | 76 | ```swift 77 | print("The width of someResolution is \(someResolution.width)") 78 | // "The width of someResolution is 0" 이 출력 79 | ``` 80 | 81 | 하위레벨 프로퍼티도 점\(.\)문법을 이용해 접근할 수 있습니다. 82 | 83 | ```swift 84 | print("The width of someVideoMode is \(someVideoMode.resolution.width)") 85 | // Prints "The width of someVideoMode is 0" 이 출력 86 | ``` 87 | 88 | 점문법을 이용해 값을 할당할 수 있습니다. 89 | 90 | ```swift 91 | someVideoMode.resolution.width = 1280 92 | print("The width of someVideoMode is now \(someVideoMode.resolution.width)") 93 | // "The width of someVideoMode is now 1280" 이 출력 94 | ``` 95 | 96 | > NOTE 97 | > Objective-C와 다르게 Swift에서는 하위레벨의 구조체 프로퍼티도 직접 설정할 수 있습니다. 위 예를 들면 `someVideoMode.resolution.width = 1280` 처럼 resolution 전체의 값을 설정하지 않고 width 프로퍼티만 직접 설정할 수 있었습니다. 98 | 99 | ### 구조체형의 맴버 초기화 \(Memberwise Initializers for Structure Types\) 100 | 101 | 모든 구조체는 초기화시 프로퍼티를 선언할 수 있는 초기자를 자동으로 생성해 제공합니다. 아래와 같은 맴버의 초기화는 구조체 안에 `width`와 `height`프로퍼티만 정의했다면 자동으로 사용 가능하다는 의미입니다. 102 | 103 | ```swift 104 | let vga = Resolution(width: 640, height: 480) 105 | ``` 106 | 107 | ## 구조체와 열거형은 값 타입 \(Structures and Enumerations Are Value Types\) 108 | 109 | 값 타입이라는 뜻은, 이것이 함수에서 상수나 변수에 전달될 때 그 값이 **복사되서 전달** 된다는 의미입니다. Swift에서 모든 구조체와 열거형 타입은 값 타입입니다. 예제를 보시겠습니다. 110 | 111 | ```swift 112 | let hd = Resolution(width: 1920, height: 1080) 113 | var cinema = hd 114 | ``` 115 | 116 | 위 코드의 첫 줄에서 `Resolution`구조체의 인스턴스 `hd`를 선언합니다. 그리고 `hd`를 cinema라는 변수에 할당 했습니다. 그러면 이 `cinema`와 `hd`는 같을까요? 위에서 설명드린 것과 같이 할당하는 순간 복사\(copy\)되기 때문에 `cinema`와 `hd`는 같지 않고 완전히 다른 인스턴스입니다. 예제를 보시겠습니다. 117 | 118 | `cinema`인스턴스의 `width`프로퍼티에 2048을 할당합니다. 119 | 120 | ```swift 121 | cinema.width = 2048 122 | ``` 123 | 124 | `cinema`의 `width` 값을 보니 2048입니다. 125 | 126 | ```swift 127 | print("cinema is now \(cinema.width) pixels wide") 128 | // "cinema is now 2048 pixels wide" 출력 129 | ``` 130 | 131 | 하지만 원본이었던 `hd`의 `width`값은 여전히 원래 값인 1920을 갖고 있는 것을 볼 수 있습니다. 이것은 두 인스턴스가 완전히 다른 개체로 다른 주소 공간에 저장되어 사용된다는 것을 보여줍니다. 132 | 133 | ```swift 134 | print("hd is still \(hd.width) pixels wide") 135 | // "hd is still 1920 pixels wide" 출력 136 | ``` 137 | 138 | 열거형에서의 동작도 마찬가지 입니다. 139 | 140 | ```swift 141 | enum CompassPoint { 142 | case north, south, east, west 143 | } 144 | var currentDirection = CompassPoint.west 145 | let rememberedDirection = currentDirection 146 | currentDirection = .east 147 | if rememberedDirection == .west { 148 | print("The remembered direction is still .west") 149 | } 150 | // "The remembered direction is still .west" 출력 151 | ``` 152 | 153 | 현재 방향 `currentDirection`에 `west`를 할당하고 기억한 방양 `rememberedDirection`에 현재 방향 `currentDirection`을 저장합니다. 그리고 나서 현재 방향 `currentDirection`을 `east`로 변경합니다. 그리고 나서 기억하고 있는 인스턴스 `rememberedDirection`를 살펴보면 여전히 원본을 복사할 때의 값 `west`를 갖고 있는 것을 확인할 수 있습니다. 즉, 다른 인스턴스의 변화는 그 인스턴스에만 영향을 끼치고 그것과 가른 인스턴스에는 아무런 영향도 없다는 것을 알 수 있습니다. 154 | 155 | ## 클래스는 참조 타입 \(Classes Are Reference Types\) 156 | 157 | 값 타입과 달리 참조 타입은 변수나 상수에 값을 할당을 하거나 함수에 인자로 전달할 때 그 값이 복사되지 않고 참조 됩니다. 참조된다는 의미는 그 값을 갖고 있는 메모리를 바라보고 있다는 뜻 입니다. 예제를 보겠습니다. 158 | 159 | ```swift 160 | let tenEighty = VideoMode() 161 | tenEighty.resolution = hd 162 | tenEighty.interlaced = true 163 | tenEighty.name = "1080i" 164 | tenEighty.frameRate = 25.0 165 | ``` 166 | 167 | `tenEighty`라는 `VideoMode` 클래스 인스턴스를 생성하고 각 프로퍼티에 값을 할당합니다. 168 | 169 | ```swift 170 | let alsoTenEighty = tenEighty 171 | alsoTenEighty.frameRate = 30.0 172 | ``` 173 | 174 | `alsoTenEighty`라는 상수를 만들고 그것에 위에서 선언한 `tenEighty` 클래스 인스턴스를 할당합니다. 그리고 나서 `alsoTenEighty`의 `frameRate`값을 30으로 변경합니다. 위에서 이 값은 25로 선언 되었었습니다. 175 | 176 | 이제 최초에 할당한 `tenEight.frameRate`의 값을 한번 볼까요? 177 | 178 | ```swift 179 | print("The frameRate property of tenEighty is now \(tenEighty.frameRate)") 180 | // "The frameRate property of tenEighty is now 30.0" 출력 181 | ``` 182 | 183 | 가장 처음에 `tenEighty.frameRate = 25.0` 에서 할당한 25.0이 아니라 나중에 `alsoTenEighty.frameRate = 30.0`에서 변경한 30이 결과 값으로 출력되었습니다. 그 이유는 `let alsoTenEighty = tenEighty`코드에서 `alsoTenEighty`상수가 `tenEighty`인스턴스를 복사한 것이 아니라 참조한 것이기 때문입니다. 그래서 실제로 `alsoTenEighty`는 `tenEighty`가 바라보고 있는 메모리 주소를 동일하게 바라보고 참조하고 있는 것입니다. 여기서 한가지 더 의아하게 생각하실 수 있는 부분은 `let alsoTenEighty = tenEighty`로 `alsoTenEighty`를 상수로 선언했는데 어떻게 `alsoTenEighty.frameRate = 30.0` 같이 값을 변경할 수 있을까? 입니다. 이것은 `alsoTenEighty`자체를 변경하는 것이 아니라 그것이 바라보는 값을 변경하는 것이기 때문에 가능합니다. 184 | 185 | ### 식별 연산자 \(Identity Operators\) 186 | 187 | 클래스는 참조 타입이기 때문에 여러 상수와 변수에서 같은 인스턴스를 참조할 수 있습니다. 상수와 변수가 같은 인스턴스를 참조하고 있는지 비교하기 위해 식별 연산자를 사용합니다. 188 | 189 | * === : 두 상수나 변수가 같은 인스턴스를 참조하고 있는 경우 참 190 | * !== : 두 상수나 변수가 다른 인스턴스를 참조하고 있는 경우 참 191 | 192 | ```swift 193 | if tenEighty === alsoTenEighty { 194 | print("tenEighty and alsoTenEighty refer to the same VideoMode instance.") 195 | } 196 | // "tenEighty and alsoTenEighty refer to the same VideoMode instance." 출력 197 | ``` 198 | 199 | 위 예제의 경우 `tenEighty`와 `alsoTenEighty`가 같은 인스턴스를 참조하고 있어서 print가 실행됩니다. 200 | 201 | 식별 연산자\(===\)는 비교 연산자\(==\)와 같지 않습니다. 식별 연산자는 참조를 비교하는 것이고, 비교 연산자는 값을 비교합니다. 202 | 203 | ### 포인터 \(Pointers\) 204 | 205 | C, C++ 혹은 Objective-C를 사용해 보신 분이라면 이 참조라는 것이 포인터라고 생각하실 수 있습니다. Swift에서 상수나 변수가 특정 타입의 인스턴스를 참조하고 있다는 것은 위 포인터와 유사합니다. 하지만 표현에는 다른 점이 있는데요 C, C++, Objective-C에서 포인터는 실제 메모리를 직접 가르키고 있고 _키워드로 표시하지만 Swift는 참조를 가르키기 위해_ 를 사용하지 않고 대신 다른 상수와 변수처럼 정의해 사용합니다. 206 | 207 | ## 클래스와 구조체의 선택 \(Choosing Between Classes and Structures\) 208 | 209 | 클래스와 구조체 모두 프로그램의 코드를 조직화 하고 특정 타입을 선언하는데 사용됩니다. 그리고 앞서 설명했던 것처럼 클래스 인스턴스가 인자로 사용될 때는 참조가 넘어가고 구조체는 값이 넘어간다고 했습니다. 그럼 언제 클래스를 사용하고 언제 구조체를 사용해야 할까요? 210 | 211 | 일반적으로 다음의 조건 중 1개 이상을 만족하면 구조체를 사용하는 것을 고려해 볼 수 있습니다. 212 | 213 | * 구조체의 주 목적이 관계된 간단한 값을 캡슐화\(encapsulate\) 하기 위한 것인 경우 214 | * 구조체의 인스턴스가 참조되기 보다 복사되기를 기대하는 경우 215 | * 구조체에 의해 저장된 어떠한 프로퍼티가 참조되기 보다 복사되기를 기대하는 경우 216 | * 구조체가 프로퍼티나 메소드 등을 상속할 필요가 없는 경우 217 | 218 | 실 “예”를 들면 다음과 같습니다. `double`형을 갖는 `width` 와 `height`를 캡슐화해 특정 지형의 크기로 사용하는 경우 `Int`형을 갖는 `start`와 `length`를 캡슐화해 특정 값의 범위를 나타내는 경우 `Double`형으로 구성된 x, y, z를 캡슐화 해 3D 좌표 시스템의 point로 사용하는 경우 219 | 220 | 위에 기술된 경우를 제외한 다른 모든 경우에는 구조체가 아니라 클래스를 사용하면 됩니다. 221 | 222 | ## String, Array, Dictionary의 할당과 복사 동작 \(Assignment and Copy Behavior for Strings, Arrays, and Dictionaries\) 223 | 224 | Swift에서는 `String`, `Array`, `Dictionary` 같은 기본 데이터 타입이 구조체로 구현 돼 있습니다. 그렇다는 의미는 이 값을 다른 상수나 변수에 할당하거나 함수나 메소드에 인자로 넘길 때 이 값이 복사 된다는 것입니다. 반면 `Foundation`의 `NSString`, `NSArray`, `NSDictionary`는 클래스로 구현 돼 있습니다. 그래서 이 데이터들은 항상 할당 되거나 전달될 때 복사 되지 않고 참조가 사용됩니다. 225 | 226 | > NOTE 227 | > 위에서 `String`, `Array`, `Dictionary` 는 할당되거나 전달될 때 복사된다고 설명했습니다. 하지만 실제로 Swift에서는 최적의 성능을 위해 실제 필요할 때만 데이터가 복사됩니다. 228 | 229 | -------------------------------------------------------------------------------- /language-guide/10-properties.md: -------------------------------------------------------------------------------- 1 | # 프로퍼티 \(Properties\) 2 | 3 | 프로퍼티는 클래스, 구조체, 열거형과 관련한 값입니다. 프로퍼티의 종류에는 저장 프로퍼티\(Stored Properties\)와 계산된 프로퍼티\(Computed Properties\)가 있습니다. 저장 프로퍼티는 말 그대로 값을 저장하고 있는 프로퍼티이고, 계산된 프로퍼티는 값을 저장하고 있지 않고 특정하게 계산한 값을 반환해 주는 프로퍼티입니다. 계산된 프로퍼티는 클래스, 구조체, 열거형 모두에서 사용가능하지만, 저장 프로퍼티는 클래스와 구조체에서만 사용 가능합니다. 추가로 프로퍼티 옵저버를 정의해서 값이 변할 때마다 모니터링할 수 있습니다. 4 | 5 | ## 저장 프로퍼티 \(Stored Properties\) 6 | 7 | 저장 프로퍼티는 위에서 설명한 대로 단순히 값을 저장하고 있는 프로퍼티 입니다. 이 프로퍼티는 `let`키워드를 이용해서 상수 혹은 `var`키워드를 이용해서 변수로 선언해 사용할 수 있습니다. 8 | 9 | ```swift 10 | struct FixedLengthRange { 11 | var firstValue: Int 12 | let length: Int 13 | } 14 | var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3) 15 | // 범위 값은 0, 1, 2 입니다. 16 | rangeOfThreeItems.firstValue = 6 17 | // 범위 값은 6, 7, 8 입니다. 18 | ``` 19 | 20 | 위 예제를 보면 `firstValue`와 `length` 에 첫 값과 그 길이를 각각의 프로퍼티에 저장해 범위 값을 표현합니다. 21 | 22 | ### 상수 구조체 인스턴스의 저장 프로퍼티 \(Stored Properties of Constant Structure Instances\) 23 | 24 | 구조체를 상수로 선언하면\(`let`\) 그 구조체 인스턴스의 프로퍼티를 변경할 수 없습니다. 25 | 26 | ```swift 27 | let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4) 28 | // 범위 값은 0, 1, 2, 3 입니다. 29 | rangeOfFourItems.firstValue = 6 30 | // 에러 발생! 31 | ``` 32 | 33 | 위 예제에서 `rangeOfFourItems`는 상수\(`let`\)로 선언되었기 때문에 프로퍼티를 변경할 수 없습니다. 반면 구조체가 아니라 클래스는 `let`으로 선언하더라도 프로퍼티가 변경 가능합니다. 이유는 클래스 인스턴스는 참조 타입 이기 때문입니다. 34 | 35 | ### 지연 저장 프로퍼티 \(Lazy Stored Properties\) 36 | 37 | 지연 저장 프로퍼티는 값이 처음으로 사용 되기 전에는 계산되지 않는 프로퍼티입니다. 지연 저장 프로퍼티로 선언하기 위해서는 프로퍼티의 선언 앞에 `lazy` 키워드를 붙이면 됩니다. 38 | 39 | > NOTE 40 | > 지연 프로퍼티는 반드시 변수\(`var`\)로 선언해야 합니다. 왜냐하면 상수는 초기화가 되기전에 항상 값을 같는 프로퍼티인데, 지연 프로퍼티는 처음 사용되기 전에는 값을 갖지 않는 프로퍼티이기 때문입니다. 41 | 42 | 지연 프로퍼티는 프로퍼티가 특정 요소에 의존적이어서 그 요소가 끝나기 전에 적절한 값을 알지 못하는 경우에 유용합니다. 또 복잡한 계산이나 부하가 많이 걸리는 작업을 지연 프로퍼티로 선언해 사용하면 실제 사용되기 전에는 실행되지 않아서 인스턴스의 초기화 시점에 복잡한 계산을 피할 수 있습니다. 43 | 44 | 예제를 보시겠습니다. 45 | 46 | ```swift 47 | class DataImporter { 48 | /* 49 | DataImporter는 외부 파일에서 데이터를 가져오는 클래스입니다. 50 | 이 클래스는 초기화 하는데 매우 많은 시간이 소요된다고 가정하겠습니다. 51 | */ 52 | var filename = "data.txt" 53 | // 데이터를 가져오는 기능의 구현이 이 부분에 구현돼 있다고 가정 54 | } 55 | 56 | class DataManager { 57 | lazy var importer = DataImporter() 58 | var data = [String]() 59 | // 데이터를 관리하는 기능이 이 부분에 구현돼 있다고 가정 60 | } 61 | 62 | let manager = DataManager() 63 | manager.data.append("Some data") 64 | manager.data.append("Some more data") 65 | // DataImporter 인스턴스는 이 시점에 생성돼 있지 않습니다. 66 | ``` 67 | 68 | `DataManager`라는 클래스를 선언하고 이 클래스는 데이터를 가져오는 `DataImporter`클래스를 갖고 있습니다. 그리고 이 `DataImporter`는 실제 디스크 파일에서 데이터를 가져오기 때문에 초기화시 많은 시간이 소요됩니다. 그래서 이 클래스를 지연 프로퍼티\(`lazy var importer = DataImporter()`\) 로 선언합니다. 이 프로퍼티는 코드에서 볼 수 있듯 `DataManager` 인스턴스 `manager`를 생성하고 거기에 data 를 넣어도 그 시점에 `DataImporter`인스턴스는 생성돼 있지 않습니다. 다시 말하면 지연 프로퍼티로 선언해 놓았기 때문에 실제 그 프로퍼티를 사용하기 전에는 복잡하고 시간일 오래 소요되는 연산을 할 필요가 없다는 것입니다. 69 | 70 | ```swift 71 | print(manager.importer.filename) 72 | // the DataImporter 인스턴스가 생성되었습니다. 73 | // "data.txt" 파일을 출력합니다. 74 | ``` 75 | 76 | `manager.importer.filename`가 실행돼 실제 `importer` 프로퍼티에 처음 접근할 때 비로소 `importer`인스턴스는 생성됩니다. 77 | 78 | > NOTE 79 | > 만약 지연 프로퍼티가 여러 스레드에서 사용되면 지연 프로퍼티가 한번만 실행되는 걸 보장하지 않습니다. 만약 지연 프로퍼티가 단일 스레드에서 사용되면 초기화는 한번만 하게 됩니다. 80 | 81 | ### 저장 프로퍼티와 인스턴스 변수 \(Stored Properties and Instance Variables\) 82 | 83 | Objective-C에 경험이 있으신 분은 Objective-C 언어에서는 값을 저장하기 위해 점 연산자\(`instance.property = value`\)나 set 연산\(`instance.setProperty(value)`\)으로 값을 저장한다는 것을 아실 겁니다. 뿐만 아니라 메모리 관리와 관련한 개념도 프로퍼티에 함께 명시합니다. 바로 이런식으로 말이죠. `@property (nonatomic, retain) NSString *propertyName;` Swift에서는 이런 컨셉을 하나의 프로퍼티애 녹여 프로퍼티의 선언과 사용의 혼란을 피했습니다. 프로퍼티의 이름, 타입, 메모리 관리 등의 모든 정보를 프로퍼티를 선언하는 한곳에서 정의하게 됩니다. 84 | 85 | ## 계산된 프로퍼티 \(Computed Properties\) 86 | 87 | 저장 프로퍼티를 뿐 아니라 추가적으로 클래스, 구조체, 열거형은 계산된 프로퍼티를 선언할 수 있습니다. 이 계산된 프로퍼티는 실제 값을 저장하고 있는 것이 아니라 `getter`와 `optional한 setter`를 제공해 값을 탐색하고 간접적으로 다른 프로퍼티 값을 설정할 수 있는 방법을 제공합니다. 88 | 89 | ```swift 90 | struct Point { 91 | var x = 0.0, y = 0.0 92 | } 93 | struct Size { 94 | var width = 0.0, height = 0.0 95 | } 96 | struct Rect { 97 | var origin = Point() 98 | var size = Size() 99 | var center: Point { 100 | get { 101 | let centerX = origin.x + (size.width / 2) 102 | let centerY = origin.y + (size.height / 2) 103 | return Point(x: centerX, y: centerY) 104 | } 105 | set(newCenter) { 106 | origin.x = newCenter.x - (size.width / 2) 107 | origin.y = newCenter.y - (size.height / 2) 108 | } 109 | } 110 | } 111 | var square = Rect(origin: Point(x: 0.0, y: 0.0), 112 | size: Size(width: 10.0, height: 10.0)) 113 | let initialSquareCenter = square.center 114 | square.center = Point(x: 15.0, y: 15.0) 115 | print("square.origin is now at (\(square.origin.x), \(square.origin.y))") 116 | // "square.origin is now at (10.0, 10.0)" 출력 117 | ``` 118 | 119 | 위 코드는 좌표와 크기를 갖는 사각형을 표현하는 구조체에 관한 코드입니다. 여기서 `Rect`구조체는 사각형의 중점을 표현하는 `center`라는 계산된 프로퍼티를 제공합니다. 이 프로퍼티는 계산된 프로퍼티의 정의대로 값을 직접 갖고 있는 것이 아니라 다른 좌표와 크기 프로퍼티들을 적절히 연산해서 구할 수 있습니다.\(`get`\). 또 `set` 으로 사각형의 중점을 직접 설정할 수 있는데, 이 값을 설정할 때 `x, y`좌표가 어떤 값을 가져야 하는지 계산해서 `x, y`에 적절한 좌표값을 넣어 줍니다. 120 | 121 | ![](../.gitbook/assets/191df9fc-ea47-457a-8849-d2729ea4457c.png) 122 | 123 | ### Setter 선언의 간략한 표현 \(Shorthand Setter Declaration\) 124 | 125 | 앞의 코드에서는 `Setter`의 인자 이름을 아래와 같이 `set(newCenter)`라고 명시했지만, 만약 이렇게 `(newCenter)`라고 인자 이름을 지정하지 않으면 인자 기본 이름인 `newValue`를 사용할 수 있습니다. 126 | 127 | ```swift 128 | struct AlternativeRect { 129 | var origin = Point() 130 | var size = Size() 131 | var center: Point { 132 | get { 133 | let centerX = origin.x + (size.width / 2) 134 | let centerY = origin.y + (size.height / 2) 135 | return Point(x: centerX, y: centerY) 136 | } 137 | set { 138 | origin.x = newValue.x - (size.width / 2) 139 | origin.y = newValue.y - (size.height / 2) 140 | } 141 | } 142 | } 143 | ``` 144 | 145 | 위 코드에서는 `set` 메소드 안에서 인자 이름을 지정하지 않았는데도 `newValue.x`, `newValue.y`를 사용할 수 있는 것을 보실 수 있습니다. 146 | 147 | ### 읽기전용 계산된 프로퍼티 \(Read-Only Computed Properties\) 148 | 149 | `getter`만 있고 `setter`를 제공하지 않는 계산된 프로퍼티를 읽기전용 계산된 프로퍼티라고 합니다. 즉, 읽기전용 계산된 프로퍼티는 반드시 반환 값을 제공하고 다른 값을 지정할 수는 없는 프로퍼티 입니다. 150 | 151 | > NOTE 152 | > 읽기전용 계산된 프로퍼티를 포함해 계산된 프로퍼티를 선언시에는 반드시 `let`이 아니라 `var`로 선언해야합니다. 보통 읽기전용\(read-only\)이라 함은 한번 값이 정해지면 변하지 않기 때문에 `let`으로 선언하는 것이 맞으나 계산된 프로퍼티는 읽기전용\(read-only\)이라 하더라도 계산 값에 따라 값이 변할 수 있기 때문에 `var`로 선언합니다. 153 | 154 | 다음 코드는 `volume`이라는 읽기전용 계산된 프로퍼티를 사용한 \(예\)입니다. 155 | 156 | ```swift 157 | struct Cuboid { 158 | var width = 0.0, height = 0.0, depth = 0.0 159 | var volume: Double { 160 | return width * height * depth 161 | } 162 | } 163 | let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0) 164 | print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)") 165 | // "the volume of fourByFiveByTwo is 40.0" 출력 166 | ``` 167 | 168 | ## 프로퍼티 옵저버 \(Property Observers\) 169 | 170 | 프로퍼티에는 새 값이 설정\(set\) 될 때마다 이 이벤트를 감지할 수 있는 옵저버를 제공합니다. 이 옵저버를 프로퍼티 옵저버라 하는데 프로퍼티 옵저버는 새 값이 이전 값과 같더라도 항상 호출 됩니다. 이 프로퍼티 옵저버는 지연 저장 프로퍼티\(`lazy stored properties` 에서는 사용할 수 없습니다. 서브클래스의 프로퍼티에 옵저버를 정의하는 것도 가능합니다. 계산된 프로퍼티는 `setter`에서 값의 변화를 감지 할 수 있기 때문에 따로 옵저버를 정의할 필요가 없습니다. 프로퍼티에서는 다음 두가지 옵저버를 제공합니다. 171 | 172 | * `willSet` : 값이 저장되기 바로 직전에 호출 됨 173 | * `didSet` : 새 값이 저장되고 난 직후에 호출 됨 174 | 175 | `willSet`에서는 새 값의 파라미터명을 지정할 수 있는데, 지정하지 않으면 기본 값으로 `newValue`를 사용합니다. 176 | 177 | `didSet`에서는 바뀌기 전의 값의 파라미터명을 지정할 수 있는데, 지정하지 않으면 기본 값으로 `oldValue`를 사용합니다. 178 | 179 | > NOTE 180 | > 서브클래스에서 특정 프로퍼티의 값을 설정했을 때, 수퍼클래스의 초기자\(initializer\)가 호출 된 후 `willSet`, `didSet` 프로퍼티 옵저버가 실행됩니다. 수퍼클래스에서 프로퍼티를 변경하는 것도 마찬가지로 수퍼클래스의 초기자가 호출된 후 옵저버가 실행됩니다. 181 | 182 | 프로퍼티 옵저버 관련한 예제를 보시겠습니다. 183 | 184 | ```swift 185 | class StepCounter { 186 | var totalSteps: Int = 0 { 187 | willSet(newTotalSteps) { 188 | print("About to set totalSteps to \(newTotalSteps)") 189 | } 190 | didSet { 191 | if totalSteps > oldValue { 192 | print("Added \(totalSteps - oldValue) steps") 193 | } 194 | } 195 | } 196 | } 197 | let stepCounter = StepCounter() 198 | stepCounter.totalSteps = 200 199 | // About to set totalSteps to 200 200 | // Added 200 steps 201 | stepCounter.totalSteps = 360 202 | // About to set totalSteps to 360 203 | // Added 160 steps 204 | stepCounter.totalSteps = 896 205 | // About to set totalSteps to 896 206 | // Added 536 steps 207 | ``` 208 | 209 | 프로퍼티 옵저버 `willSet`과 `didSet`을 사용한 것을 보실 수 있습니다. `willSet`에서는 새로운 값의 파라미터명으로 `newTotalSteps`를 지정해서 사용했고, `didSet`에서는 변하기 전의 값을 의미하는 파라미터명을 지정하지 않고 `oldValue`라는 기본 파라미터명을 이용한 것을 할 수 있습니다. 실행 결과 로그를 보시면 값이 변하기 전과 변하고 나서 호출된 로그를 보며 프로퍼티 값 설정시 `willSet`과 `didSet`이 호출되는 순서를 확인하실 수 있습니다. 210 | 211 | > NOTE 212 | > 만약 in-out 파라미터로 선언된 함수의 인자에 프로퍼티를 넘기면 `willSet`과 `didSet`이 항상 실행됩니다. 이유는 in-out 파라미터이기 때문에 프로퍼티가 항상 복사\(copy\)되기 때문입니다. 이 in-out 파라미터의 프로퍼티는 항상 원래 값에 새 값을 다시 덮어쓰게 됩니다. 213 | 214 | ## 전역변수와 지역변수 \(Global and Local Variables\) 215 | 216 | 앞서 소개한 계산된 프로퍼티와 프로퍼티 옵저버 기능은 전역변수와 지역변수 모두에서 이용 가능합니다. 전역 변수란 함수, 메소드, 클로저 혹은 타입 컨텍스트 밖에 정의된 변수이고 지역 변수는 그 안에 선언된 변수를 말합니다. 217 | 218 | > NOTE 219 | > 전역 상수와 변수는 지연 저장 프로퍼티\(Lazy Stored Properties\)와 같이 지연 계산\(lazy computed\) 됩니다. 하지만 지연 저장 프로퍼티와 다르게 `lazy`키워드를 붙일 필요 없습니다. 반면 지역 상수와 변수는 지연 계산될 수 없습니다. 220 | 221 | ## 타입 프로퍼티 \(Type Properties\) 222 | 223 | 인스턴스 프로퍼티는 특정 인스턴스에 속한 프로퍼티를 말합니다. 이 프로퍼티는 새로운 인스턴스가 생성될 때마다 새로운 프로퍼티도 같이 생성됩니다. 타입 프로퍼티는 특정 타입에 속한 프로퍼티로 그 타입에 해당하는 단 하나의 프로퍼티만 생성됩니다. 이 타입 프로퍼티는 특정 타입의 모든 인스턴스에 공통으로 사용되는 값을 정의할때 유용합니다. 224 | 225 | > NOTE 226 | > 인스턴스 프로퍼티와는 다르게 타입 프로퍼티는 항상 초기값을 지정해서 사용해야 합니다. 왜냐하면 타입 자체에는 초기자\(Initializer\)가 없어 초기화 할 곳이 없기 때문입니다. 227 | 228 | ### 타입 프로퍼티 구문 \(Type Property Syntax\) 229 | 230 | 타입 프로퍼티를 선언을 위해서는 `static` 키워드를 사용합니다. 클래스에서는 `static`과 `class` 이렇게 2가지 형태로 타입 프로퍼티를 선언할 수 있는데 두 가지 경우의 차이는서브클래스에서 `overriding`가능 여부입니다. `class`로 선언된 프로퍼티는 서브클래스에서 오버라이드 가능합니다. 구조체, 열거형, 클래스에서의 타입 프로퍼티 선언의 \(예\)는 다음과 같습니다. 231 | 232 | ```swift 233 | struct SomeStructure { 234 | static var storedTypeProperty = "Some value." 235 | static var computedTypeProperty: Int { 236 | return 1 237 | } 238 | } 239 | enum SomeEnumeration { 240 | static var storedTypeProperty = "Some value." 241 | static var computedTypeProperty: Int { 242 | return 6 243 | } 244 | } 245 | class SomeClass { 246 | static var storedTypeProperty = "Some value." 247 | static var computedTypeProperty: Int { 248 | return 27 249 | } 250 | class var overrideableComputedTypeProperty: Int { 251 | return 107 252 | } 253 | } 254 | ``` 255 | 256 | > NOTE 257 | > 위의 계산된 타입 프로퍼티의 \(예\)는 읽기전용이지만, 같은 문법으로 계산된 인스턴스 타입 프로퍼티에서는 읽고 쓸 수 있는 프로퍼티로 사용할 수 있습니다. 258 | 259 | ### 타입 프로퍼티의 접근과 설정 \(Querying and Setting Type Properties\) 260 | 261 | 인스턴스 프로퍼티와 마찬가지로 타입 프로퍼티도 점연산자\(dot operator\)로 프로퍼티의 값을 가져오고 할당할 수 있습니다. 관련 \(예\)는 다음과 같습니다. 262 | 263 | ```swift 264 | print(SomeStructure.storedTypeProperty) 265 | // Prints "Some value." 266 | SomeStructure.storedTypeProperty = "Another value." 267 | print(SomeStructure.storedTypeProperty) 268 | // Prints "Another value." 269 | print(SomeEnumeration.computedTypeProperty) 270 | // Prints "6" 271 | print(SomeClass.computedTypeProperty) 272 | // Prints "27" 273 | ``` 274 | 275 | ![](../.gitbook/assets/59ff831a-ab4f-4db7-a56f-aae51d677173.png) 276 | 277 | 다음 예제 코드는 오디오 채널의 볼륨을 조절하고 관리하는 구조체 입니다. 278 | 279 | ```swift 280 | struct AudioChannel { 281 | static let thresholdLevel = 10 282 | static var maxInputLevelForAllChannels = 0 283 | var currentLevel: Int = 0 { 284 | didSet { 285 | if currentLevel > AudioChannel.thresholdLevel { 286 | // cap the new audio level to the threshold level 287 | currentLevel = AudioChannel.thresholdLevel 288 | } 289 | if currentLevel > AudioChannel.maxInputLevelForAllChannels { 290 | // store this as the new overall maximum input level 291 | AudioChannel.maxInputLevelForAllChannels = currentLevel 292 | } 293 | } 294 | } 295 | } 296 | ``` 297 | 298 | 현재 오디오의 최대 볼륨의 크기는 타입 프로퍼티 `maxInputLevelForAllChannels`로 관리하고 그 값은 `thresholdLevel`로 상수로 정의돼 있어서 현재 정도\(currentLevel\)을 설정할 때마다\(`didSet`\) 채널별 볼륨이 최대 그 값을 넘지 못하도록 조정합니다. 299 | 300 | > NOTE 301 | > `currentLevel`의 `didSet`안에서 `currentLevel`에 값을 할당하는 것은 `didSet`을 반복호출하지 않습니다. 302 | 303 | 이렇게 만든 오디오 채널 구조체를 이용해 좌우 두개의 오디오 채널을 생성할 수 있습니다. 304 | 305 | ```swift 306 | var leftChannel = AudioChannel() 307 | var rightChannel = AudioChannel() 308 | ``` 309 | 310 | 왼쪽 채널에 7 값을 할당 합니다. 311 | 312 | ```swift 313 | leftChannel.currentLevel = 7 314 | print(leftChannel.currentLevel) 315 | // Prints "7" 316 | print(AudioChannel.maxInputLevelForAllChannels) 317 | // Prints "7" 318 | ``` 319 | 320 | 오른쪽 채널에서 최대 값으로 설정한 값을 넘는 11을 할당해 보겠습니다. 321 | 322 | ```swift 323 | rightChannel.currentLevel = 11 324 | print(rightChannel.currentLevel) 325 | // Prints "10" 326 | print(AudioChannel.maxInputLevelForAllChannels) 327 | // Prints "10" 328 | ``` 329 | 330 | `thresholdLevel`이 적용돼서 11을 입력했지만 10으로 설정된 것을 확인할 수 있습니다. 331 | 332 | -------------------------------------------------------------------------------- /language-guide/11-methods.md: -------------------------------------------------------------------------------- 1 | # 메소드 \(Methods\) 2 | 3 | 특정 타입의 클래스, 구조체, 열거형과 관련된 함수를 메소드라 합니다. 특정 타입의 인스턴스에서 실행할 수 있는 메소드를 인스턴스 메소드, 특정 형과 관련된 메소드를 타입 메소드라 합니다. 타입 메소드는 Objective-C에서 클래스 메소드와 유사합니다. Swift에서 메소드가 C나 Objective-C 메소드 간의 가장 큰 차이는 바로 Objective-C에서는 클래스 타입에서만 메소드를 선언할 수 있다는 것 입니다. 반면 Swift에서는 클래스 타입 뿐만아니라 구조체, 열거형에서도 메소드를 선언해 사용할 수 있습니다. 4 | 5 | ## 인스턴스 메소드 \(Instance Methods\) 6 | 7 | 인스턴스 메소드는 특정 클래스, 구조체, 열거형의 인스턴스에 속한 메소드입니다. 이 메소드를 통해 인스턴스 내의 값을 제어하거나 변경할 수 있습니다. 인스턴스 메소드는 이름 그대로 그 인스턴스가 속한 특정 타입의 인스턴스에서만 실행 가능합니다. 예제를 보시겠습니다. 8 | 9 | ```swift 10 | class Counter { 11 | var count = 0 12 | func increment() { 13 | count += 1 14 | } 15 | func increment(by amount: Int) { 16 | count += amount 17 | } 18 | func reset() { 19 | count = 0 20 | } 21 | } 22 | ``` 23 | 24 | `Counter`클래스를 선언하고 인스턴스 메소드로 각각 `increment()`, `increment(by amount: Int)`,`reset()` 를 정의해 인스턴스 내의 `count`property를 변경하는 기능을 수행합니다. 다음은 이 클래스를 사용한 \(예\)입니다. 25 | 26 | ```swift 27 | let counter = Counter() 28 | // 초기 count 값은 0입니다. 29 | counter.increment() 30 | // count 값이 1로 변경 됐습니다. 31 | counter.increment(by: 5) 32 | // count 값은 현재 6입니다. 33 | counter.reset() 34 | // count 값은 0이 됩니다. 35 | ``` 36 | 37 | ### self 프로퍼티 \(The self Property\) 38 | 39 | 모든 프로퍼티는 암시적으로 인스턴스 자체를 의미하는 `self`라는 프로퍼티를 갖습니다. 인스턴스 메소드 안에서 `self`프로퍼티를 이용해 인스턴스 자체를 참조하는데 사용할 수 있습니다. 40 | 41 | ```swift 42 | func increment() { 43 | self.count += 1 44 | } 45 | ``` 46 | 47 | 앞의 예제에서는 `increment()`메소드에서는 `count += 1`이라 표현했지만 사실은 `self.count += 1`의 의미 입니다. 이것이 가능한 이유는 Swift에서 특정 메소드에서 해당 인스턴스에 등록된 메소드나 프로퍼티를 호출하면 현재 인스턴스의 메소드나 프로퍼티를 사용하는 것으로 자동으로 가정하기 때문입니다. 위 규칙이 적용되지 않는 예외적인 상황이 있습니다. 바로 인자 이름이 프로퍼티 이름과 같은 경우 입니다. 이 경우에는 프로퍼티에 접근하기 위해 명시적으로 `self`키워드를 사용해야 합니다. 다음은 `self`키워드를 이용해 파라미터와 프로퍼티의 모호함을 명확하게 한 \(예\)입니다. 48 | 49 | ```swift 50 | struct Point { 51 | var x = 0.0, y = 0.0 52 | func isToTheRightOf(x: Double) -> Bool { 53 | return self.x > x // self.x를 이용해 프로퍼티 x와 인자 x를 구분 54 | } 55 | } 56 | let somePoint = Point(x: 4.0, y: 5.0) 57 | if somePoint.isToTheRightOf(x: 1.0) { 58 | print("This point is to the right of the line where x == 1.0") 59 | } 60 | // "This point is to the right of the line where x == 1.0" 출력 61 | ``` 62 | 63 | 위 `isToTheRightOf(x: Double)`메소드에서 `self.x`를 사용해 프로퍼티 `x`와 인자`x`를 구분했습니다. 만약 프로퍼티와 인자 이름이 같을 때 `self`키워드를 사용하지 않으면 Swift는 자동으로 인자 이름으로 가정합니다. 64 | 65 | ### 인스턴스 메소드 내에서 값 타입 변경 \(Modifying Value Types from Within Instance Methods\) 66 | 67 | 구조체와 열거형은 값 타입입니다. 그래서 기본적으로 인스턴스 메소드 내에서 값 타입의 프로퍼티를 변경할 수 없습니다. 하지만 값 타입 형의 메소드에서 프로퍼티를 변경하고 싶을 때가 있을 텐데요. 어떻게 해야할까요? 바로 메소드에 `mutating`붙여 주면 가능합니다. `mutating`이라는 키워드가 붙은 메소드에서는 메소드의 계산이 끝난 후 원본 구조체에 그 결과를 덮어 써서 그 값을 변경합니다. 68 | 69 | ```swift 70 | struct Point { 71 | var x = 0.0, y = 0.0 72 | mutating func moveBy(x deltaX: Double, y deltaY: Double) { 73 | x += deltaX 74 | y += deltaY 75 | } 76 | } 77 | var somePoint = Point(x: 1.0, y: 1.0) 78 | somePoint.moveBy(x: 2.0, y: 3.0) 79 | print("The point is now at (\(somePoint.x), \(somePoint.y))") 80 | // "The point is now at (3.0, 4.0)" 출력 81 | ``` 82 | 83 | 위 `Point`는 구조체여서 메소드로 인스턴스의 값을 변경할 수 없지만 `mutating func moveBy()`같이 메소드 앞에 `mutating`을 붙여서 값을 변경할 수 있습니다. 한 가지 기억하실 부분은 여기서는 `Point` 인스턴스를 `var somePoint`로 선언해서 사용해서 가능했지만 만약 `let somePoint`으로 선언하면 구조체 단에서 변경이 불가능하다는 선언이기 때문에 mutating 선언과 상관없이 메소드로 값을 변경할 수 없습니다. 84 | 85 | ```swift 86 | let fixedPoint = Point(x: 3.0, y: 3.0) 87 | fixedPoint.moveBy(x: 2.0, y: 3.0) 88 | // let으로 선언해서 값 변경 시도시 에러 발생! 89 | ``` 90 | 91 | ### Mutating 메소드 내에서 self 할당 \(Assigning to self Within a Mutating Method\) 92 | 93 | `Mutating`메소드에서 `self`프로퍼티를 이용해 완전 새로운 인스턴스를 생성할 수 있습니다. 위 메소드를 아래와 같이 작성할 수도 있습니다. 94 | 95 | ```swift 96 | struct Point { 97 | var x = 0.0, y = 0.0 98 | mutating func moveBy(x deltaX: Double, y deltaY: Double) { 99 | self = Point(x: x + deltaX, y: y + deltaY) 100 | } 101 | } 102 | ``` 103 | 104 | 이 버전의 메소드의 동작은 앞의 동작과 완전히 동일합니다. 같은 열거형에서 `Mutating`메소드를 사용해 다른 상태를 표현할 수 있습니다. 105 | 106 | ```swift 107 | enum TriStateSwitch { 108 | case off, low, high 109 | mutating func next() { 110 | switch self { 111 | case .off: 112 | self = .low 113 | case .low: 114 | self = .high 115 | case .high: 116 | self = .off 117 | } 118 | } 119 | } 120 | var ovenLight = TriStateSwitch.low 121 | ovenLight.next() 122 | // ovenLight 값은 .high 123 | ovenLight.next() 124 | // ovenLight 값은 .off 125 | ``` 126 | 127 | `ovenLight`인스턴스에서 `next()`메소드를 호출해 상태를 변경할 수 있습니다. 128 | 129 | ## 타입 메소드 \(Type Methods\) 130 | 131 | 인스턴스 메소드는 특정 타입의 인스턴스에서 호출되고, 타입 메소드는 특정 타입 자체에서 호출해 사용합니다. 타입 메소드의 선언은 메소드 키워드 `func`앞에 `static`혹은 `class`키워드를 추가하면 됩니다. `static`메소드와 `class`메소드의 차이점은 `static`메소드는 서브클래스에서 오버라이드 할 수 없는 타입 메소드 이고, `class`메소드는 서브클래스에서 오버라이드 할 수 있는 타입 메소드 라는 것입니다. 132 | 133 | > Note 134 | > Objective-C에서는 클래스 타입에서만 타입 메소드를 선언할 수 있던 것에 반해 Swift에서는 클래스, 구조체, 열거형에서 모두 타입 메소드를 사용할 수 있습니다. 135 | 136 | 타입 메소드도 인스턴스 메소드와 같이 점\(.\) 문법으로 호출할 수 있습니다. 하지만 인스턴스에서 호출하는 것이 아니라 타입 이름에서 메소드를 호출합니다. 예제를 보시겠습니다. 137 | 138 | ```swift 139 | class SomeClass { 140 | class func someTypeMethod() { 141 | // 타입 메소드 구현 142 | } 143 | } 144 | SomeClass.someTypeMethod() // 타입 메소드 호출! 145 | ``` 146 | 147 | 타입 메소드 안에서도 `self`키워드를 사용할 수 있습니다. 하지만 타입 메소드에서의 `self`는 인스턴스가 아니라 타입 자신을 의미합니다. 또 타입 메소드 안에서 다른 타입 메소드를 사용하는 것이 가능합니다. 코드를 보시겠습니다. 148 | 149 | ```swift 150 | struct LevelTracker { 151 | static var highestUnlockedLevel = 1 152 | var currentLevel = 1 153 | 154 | static func unlock(_ level: Int) { 155 | if level > highestUnlockedLevel { highestUnlockedLevel = level } 156 | } 157 | 158 | static func isUnlocked(_ level: Int) -> Bool { 159 | return level <= highestUnlockedLevel 160 | } 161 | 162 | @discardableResult 163 | mutating func advance(to level: Int) -> Bool { 164 | if LevelTracker.isUnlocked(level) { 165 | currentLevel = level 166 | return true 167 | } else { 168 | return false 169 | } 170 | } 171 | } 172 | ``` 173 | 174 | 위 코드는 타입 메소드 2개와 `mutating`메소드 1개를 선언해 현재 레벨과, 최고 레벨, 다음 레벨 열기, 다음 레벨로 넘어가기 기능을 구현 했습니다. 그래서 게임에서 최고 레벨이 어디인지 추적하고 만약 그 레벨이 열려 있으면 그쪽으로 다른 사용자가 바로 넘어갈 수 있는\(advance\) 기능을 제공합니다. `advance`메소드 앞에 붙은 `@discardableResult`키워드는 리턴 값이 있는 메소드에서 리턴 값을 사용하지 않으면 컴파일러 경고가 발생하는데, 그 경고를 발생하지 않도록 해줍니다. 다시말해 `xx.advance`라고 하면 이 메소드를 실행하고나서 `Bool`값의 리턴 값을 어떤 변수에도 할당하지 않아 컴파일러 경고가 발생하지만 `@discardableResult`이라는 키워드를 붙여줘서 말 그대로 이 결과 값은 버릴 수 있는 결과 값이야 라고 표시를 해서 경고가 발생하지 않도록 합니다. 경고를 발생하지 않게하는 다른 방법으로는 `_ = xx.advance`형태로 호출하는 것도 가능합니다. 175 | 176 | 아래 \(예\)는 `Player`클래스의 `complete`메소드에서 `LevelTracker` 인스턴스 `tracker`와 타입메소드 `LevelTracker.unlock`를 사용해 특정 사용자의 현재 레벨을 추적하도록 구현 했습니다. 177 | 178 | ```swift 179 | class Player { 180 | var tracker = LevelTracker() 181 | let playerName: String 182 | func complete(level: Int) { 183 | LevelTracker.unlock(level + 1) 184 | tracker.advance(to: level + 1) 185 | } 186 | init(name: String) { 187 | playerName = name 188 | } 189 | } 190 | ``` 191 | 192 | ```swift 193 | var player = Player(name: "Argyrios") 194 | player.complete(level: 1) 195 | print("highest unlocked level is now \(LevelTracker.highestUnlockedLevel)") 196 | // "highest unlocked level is now 2" 출력 197 | ``` 198 | 199 | 레벨 1을 완료해서 레벨 2가 열렸습니다. 200 | 201 | ```swift 202 | player = Player(name: "Beto") 203 | if player.tracker.advance(to: 6) { 204 | print("player is now on level 6") 205 | } else { 206 | print("level 6 has not yet been unlocked") 207 | } 208 | // "level 6 has not yet been unlocked" 출력 209 | ``` 210 | 211 | 레벨 6으로 바로 건너뛰려 하지만 아직 열리지 않아 관련 메시지를 출력합니다. 212 | 213 | -------------------------------------------------------------------------------- /language-guide/12-subscripts.md: -------------------------------------------------------------------------------- 1 | # 서브스크립트 \(Subscripts\) 2 | 3 | 클래스, 구조체 그리고 열거형에서 스크립트를 정의해 사용할 수 있습니다. 서브스크립트란 콜렉션, 리스트, 시퀀스 등 집합의 특정 멤버 엘리먼트에 간단하게 접근할 수 있는 문법입니다. 서브스크립트를 이용하면 추가적인 메소드 없이 특정 값을 할당\(assign\)하거나 가져올 수\(retrieve\) 있습니다. 예를들면, 배열\(Array\) 인스턴스의 특정 엘리먼트는 `someArray[index]`문법으로, 사전\(Dictionary\) 인스턴스의 특정 엘리먼트는 `someDictionary[key]`로 접근할 수 있습니다. 하나의 타입에 여러 서브스크립트를 정의할 수 있고 오버로드\(Overload\)도 가능합니다. 뿐만아니라 단일 인자 값을 넘어, 필요 따라 복수 인자 값을 사용할 수 있습니다. 4 | 5 | ## 서브스크립트 문법 \(Subscript Syntax\) 6 | 7 | 서브스크립트 선언 문법은 인스턴스 메소드와 계산된 프로퍼티를 선언하는 것과 비슷합니다. 인스턴스 메소드와 다른 점은, 서브스크립트는 읽고-쓰기\(read-write\) 혹은 읽기 전용\(read only\)만 가능하다는 것입니다. 정의는 계산된 프로퍼티 방식과 같이 `setter`, `getter` 방식을 따릅니다. 8 | 9 | ```swift 10 | subscript(index: Int) -> Int { 11 | get { 12 | // 적절한 반환 값 13 | } 14 | set(newValue) { 15 | // 적절한 set 액션 16 | } 17 | } 18 | ``` 19 | 20 | 서브스크립트의 `set`에 대한 인자 값을 따로 지정하지 않으면 기본 값\(default value\)로 `newValue`를 사용합니다. 읽기 전용으로 선언하려면 `get`, `set`을 지우고 따로 지정하지 않으면 `get`으로 동작하게 되서 읽기 전용으로 선언됩니다. 21 | 22 | ```swift 23 | subscript(index: Int) -> Int { 24 | // 적절한 반환 값 25 | } 26 | ``` 27 | 28 | 다음은 읽기 전용으로 선언한 서브스크립트의 \(예\)입니다. 29 | 30 | ```swift 31 | struct TimesTable { 32 | let multiplier: Int 33 | subscript(index: Int) -> Int { 34 | return multiplier * index 35 | } 36 | } 37 | let threeTimesTable = TimesTable(multiplier: 3) 38 | print("six times three is \(threeTimesTable[6])") 39 | // "six times three is 18" 출력 40 | ``` 41 | 42 | `TimesTable`구조체의 `multiplier`를 3으로 설정하고 `threeTimesTable[6]`에서 6번째 값, 여기서는 3에 6을 곱한 값을 출력합니다. 43 | 44 | ## 서브스크립트 사용 \(Subscript Usage\) 45 | 46 | 다음은 사전에서 서브스크립트의 사용 \(예\) 입니다. 47 | 48 | ```swift 49 | var numberOfLegs = ["spider": 8, "ant": 6, "cat": 4] 50 | numberOfLegs["bird"] = 2 51 | ``` 52 | 53 | `numberOfLegs`값은 타입 추론에 의해 `[String: Int]`형을 갖습니다. `numberOfLegs["bird"] = 2`는 사전형 변수 `numberOfLegs`에 key로 bird를 그 값은 2를 넣으라는 서브스크립트 문법입니다. 54 | 55 | > NOTE 56 | > 사전의 반환 값은 옵셔널입니다. 그 이유는 사전에 특정 키 값이 없는 경우가 있을 수 있고, 특정 키 값을 nil로 설정할 수 있기 때문입니다. 57 | 58 | ### 서브스크립트 옵션 \(Subscript Options\) 59 | 60 | 서브스크립트는 입력 인자의 숫자에 제한이 없고, 입력 인자의 타입과 반환 타입의 제한도 없습니다. 다만 in-out 인자\(in-out parameter\)나 기본 인자 값\(default parameter value\)을 제공할 수는 없습니다. 서브스크립트는 오버로딩도 허용합니다. 그래서 인자형, 반환형에 따라 원하는 수 만큼의 서브스크립트를 선언할 수 있습니다. 다음은 서브스크립트를 이용해 다차원 행열을 선언하고 접근하는 \(예\) 입니다. 61 | 62 | ```swift 63 | struct Matrix { 64 | let rows: Int, columns: Int 65 | var grid: [Double] 66 | init(rows: Int, columns: Int) { 67 | self.rows = rows 68 | self.columns = columns 69 | grid = Array(repeating: 0.0, count: rows * columns) 70 | } 71 | func indexIsValid(row: Int, column: Int) -> Bool { 72 | return row >= 0 && row < rows && column >= 0 && column < columns 73 | } 74 | subscript(row: Int, column: Int) -> Double { 75 | get { 76 | assert(indexIsValid(row: row, column: column), "Index out of range") 77 | return grid[(row * columns) + column] 78 | } 79 | set { 80 | assert(indexIsValid(row: row, column: column), "Index out of range") 81 | grid[(row * columns) + column] = newValue 82 | } 83 | } 84 | } 85 | ``` 86 | 87 | 위 코드에서는 `subscript(row: Int, column: Int) -> Double`코드와 같이 row, column 2개의 인자를 받고, Double를 반환하는 서브스크립트를 선언했습니다. get, set 각각에 `indexIsValid`메소드를 사용해서 유효한 인덱스가 아닌경우 프로그램이 바로 종료 되도록 `assert`를 호출했습니다. 선언한 서브스크립트 문법을 이용해 `var matrix = Matrix(rows: 2, columns: 2)` 2 x 2 행렬을 선언합니다. 88 | 89 | ![](../.gitbook/assets/b59598cb-4e0f-4016-b84e-4a4b9fc868fb.png) 90 | 91 | grid 배열은 서브스크립트에 의해 위와 같이 row와 column을 갖는 행렬도 동작합니다. 행렬에 서브스크립트를 이용해 특정 row/column에 값을 넣을 수 있습니다. 92 | 93 | ```swift 94 | matrix[0, 1] = 1.5 95 | matrix[1, 0] = 3.2 96 | ``` 97 | 98 | 값을 넣은 결과 행렬은 다음과 같은 모습이 됩니다. 99 | 100 | ![](../.gitbook/assets/be27d61e-5730-4155-b132-ed1a14038787.png) 101 | 102 | 행렬의 입출력시 row/column의 범위가 적절한지 아래의 코드로 확인합니다. 103 | 104 | ```swift 105 | func indexIsValid(row: Int, column: Int) -> Bool { 106 | return row >= 0 && row < rows && column >= 0 && column < columns 107 | } 108 | ``` 109 | 110 | 만약 적절한 범위를 벗어나면 assert가 실행됩니다. 111 | 112 | ```swift 113 | let someValue = matrix[2, 2] 114 | // [2, 2]가 사용할 수 있는 행렬의 범위를 벗어나므로 assert가 실행됩니다. 115 | ``` 116 | 117 | -------------------------------------------------------------------------------- /language-guide/13-inheritance.md: -------------------------------------------------------------------------------- 1 | # 상속 \(Inheritance\) 2 | 3 | 클래스는 메소드, 프로퍼티와 다른 특징\(characteristics\)을 다른 클래스로 부터 상속할 수 있습니다. 이것이 Swift에서 클래스가 다른 타입과 구분되는 근본적인 요소입니다. 클래스에서는 저장된 프로퍼티와 계산된 프로퍼티와 상관없이 상속받은 프로퍼티에 프로퍼티 옵저버를 설정해서 값 설정에 반응할 수 있습니다. 4 | 5 | ## 기반 클래스 정의 \(Defining a Base Class\) 6 | 7 | 다른 어떤 클래스로부터도 상속받지 않은 클래스를 기반 클래스라 합니다. 8 | 9 | > NOTE 10 | > Objective-C에서는 모든 클래스가 `NSObject`클래스로부터 파생된 클래스로 생성되는 것과 달리 Swift에서는 SuperClass 지정없이 클래스 선언이 가능하고 그 클래스가 SuperClass가 됩니다. 11 | 12 | 예제를 통해 실제 사용에 대해 알아 보겠습니다. 13 | 14 | ```swift 15 | class Vehicle { 16 | var currentSpeed = 0.0 17 | var description: String { 18 | return "traveling at \(currentSpeed) miles per hour" 19 | } 20 | func makeNoise() { 21 | // do nothing - an arbitrary vehicle doesn't necessarily make a noise 22 | } 23 | } 24 | ``` 25 | 26 | 위 코드는 탈것\(Vehicle\)이라는 기반 클래스를 하나 정의한 것입니다. 이 탈것은 현재속도\(currentSpeed\)를 프로퍼티로 갖고 있고 `description`을 계산된 프로퍼티로 선언했습니다. `makeNoise()`라는 메소드도 갖고 있습니다. 이 클래스를 이용해 탈것 객체를 하나 생성합니다. `let someVehicle = Vehicle()` `description`프로퍼티를 이용해 탈 것의 현재 상태를 확인합니다. 27 | 28 | ```swift 29 | print("Vehicle: \(someVehicle.description)") 30 | // Vehicle: traveling at 0.0 miles per hour 31 | ``` 32 | 33 | ## 서브클래싱 \(Subclassing\) 34 | 35 | 상속, 다시말해 서브클래싱을 하면 부모로 부터 성격을 상속받고 자기 자신 고유의 특성도 추가할 수 있습니다. 서브클래싱해 새 클래스를 선언하는 코드는 다음과 같습니다. 36 | 37 | ```swift 38 | class SomeSubclass: SomeSuperclass { 39 | // subclass definition goes here 40 | } 41 | ``` 42 | 43 | 위 탈것을 서브클래싱해 자전거라는 클래스를 선언하면 코드는 다음과 같습니다. 44 | 45 | ```swift 46 | class Bicycle: Vehicle { 47 | var hasBasket = false 48 | } 49 | ``` 50 | 51 | `Bicycle`은 `Vehicle` 서브클래싱하고 자기 자신의 속성 `hasBasket`도 추가합니다. 52 | 53 | ```swift 54 | let bicycle = Bicycle() 55 | bicycle.hasBasket = true 56 | ``` 57 | 58 | `Bicycle`객체를 생성해 고유 속성을 사용할 수 있는 것을 확인할 수 있습니다. 59 | 60 | ```swift 61 | bicycle.currentSpeed = 15.0 62 | print("Bicycle: \(bicycle.description)") 63 | // Bicycle: traveling at 15.0 miles per hour 64 | ``` 65 | 66 | 물론 부모로부터 물려받은 `currentSpeed`, `description`속성도 사용 가능합니다. 서브클래스로 생성된 클래스를 다시 서브클래싱 하는 것도 가능합니다. 67 | 68 | ```swift 69 | class Tandem: Bicycle { 70 | var currentNumberOfPassengers = 0 71 | } 72 | ``` 73 | 74 | `Bicycle`을 서브클래싱해서 `Tandem`이라는 클래스를 생성하고 속성도 추가합니다. 75 | 76 | ```swift 77 | let tandem = Tandem() 78 | tandem.hasBasket = true 79 | tandem.currentNumberOfPassengers = 2 80 | tandem.currentSpeed = 22.0 81 | print("Tandem: \(tandem.description)") 82 | // Tandem: traveling at 22.0 miles per hour 83 | ``` 84 | 85 | 위 코드에서 `Tandem`객체 에서는 `Tandem`객체 고유 속성인 `currentNumberOfPassengers`와 자신의 부모 `Bicycle`의 속성 그리고 조부모인 `Vehicle`의 속성도 모두 사용 가능한 것을 확인할 수 있습니다. 86 | 87 | ## 오버라이딩 \(Overriding\) 88 | 89 | 서브클래스에서는 부모클래스에서 상속받은 것을 재정의 할 수 있습니다. 이것을 `overriding` 이라 부르는데, 오버라이딩은 인스턴스 메소드, 타입 메소드, 인스턴스 프로퍼티, 타입 프로퍼티, 서브스크립트 모두에 대해 가능합니다. 오버라이드를 위해서는 다른 선언 앞에 `override`키워드를 붙여줍니다. Swift에서는 이 `override`키워드를 보면 부모에 그 정의가 있는지 확인합니다. 90 | 91 | ## 부모클래스의 메소드, 프로퍼티, 서브스크립트의 접근 \(Accessing Superclass Methods, Properties, and Subscripts\) 92 | 93 | `super`키워드와 점문법 혹은 인덱스 구문으로 부모 클래스의 메소드, 프로퍼티, 서브스크립트에 접근할 수 있습니다. `super.someMethod`, `super.someProperty`, `super[someIndex]` 94 | 95 | ### 메소드 오버라이드 \(Overriding Methods\) 96 | 97 | 상속받은 메소드를 오버라이드 하기 위해서는 메소드 선언 앞에 `override`키워드를 붙이고 내용을 적어주면 됩니다. 98 | 99 | ```swift 100 | class Train: Vehicle { 101 | override func makeNoise() { 102 | print("Choo Choo") 103 | } 104 | } 105 | ``` 106 | 107 | ```swift 108 | let train = Train() 109 | train.makeNoise() 110 | // Prints "Choo Choo" : 새로 정의한 내용이 출력됩니다. 111 | ``` 112 | 113 | ### 프로퍼티 오버라이드 \(Overriding Properties\) 114 | 115 | 서브클래스에서는 상속받은 저장된 프로퍼티, 계산된 프로퍼티 모두 오버라이드 가능합니다. 오버라이드시에는 프로퍼티의 이름과 타입을 명시해야합니다. 왜냐하면 서브클래스에서는 단순히 상속받은 특정 형의 프로퍼티가 있다는 정도만 알고 있기 때문입니다. 상속받은 읽기전용\(read only\) 프로퍼티도 `getter/setter`를 정의해서 읽고/쓰기가 가능한\(read-write\) 프로퍼티로 변경해서 제공 가능합니다. 하지만 반대의 읽고/쓰기가 가능한 프로퍼티를 읽기전용 프로퍼티로 선언하는 것은 할 수 없습니다. 116 | 117 | > NOTE 118 | > 만약 `setter`를 오버라이드 해서 제공한다면 반드시 `getter`도 제공해야 합니다. 119 | 120 | 계산된 프로퍼티를 오버라이딩한 \(예\) 입니다. 121 | 122 | ```swift 123 | class Car: Vehicle { 124 | var gear = 1 125 | override var description: String { 126 | return super.description + " in gear \(gear)" 127 | } 128 | } 129 | ``` 130 | 131 | 오버라이딩한 프로퍼티가 재정의 한대로 출력되는 것을 확인할 수 있습니다. 132 | 133 | ```swift 134 | let car = Car() 135 | car.currentSpeed = 25.0 136 | car.gear = 3 137 | print("Car: \(car.description)") 138 | // Car: traveling at 25.0 miles per hour in gear 3 139 | ``` 140 | 141 | ### 프로퍼티 옵저버 오버라이드 \(Overriding Property Observers\) 142 | 143 | 이미 부모클래스에 선언된 프로퍼티 옵저버도 서브클래스에서 재정의해 사용할 수 있습니다. 144 | 145 | > NOTE 146 | > 상수 프로퍼티와 읽기전용 프로퍼티에는 옵저버를 붙일 수 없습니다. 이 프로퍼티는 정의 그대로 `set`을 할 수 없는 프로퍼티이기 때문입니다. 또 같은 프로퍼티에 옵저버를 추가하고 `setter`를 추가해 둘을 동시에 사용할 수 없습니다. 이미 `setter`를 설정했다면 옵저버를 붙인 것과 같은 동작을 하기 때문입니다. 147 | 148 | 다음은 옵저버 오버라이드의 예제 코드입니다. 부모의 `currentSpeed` 계산된 프로퍼티를 오버라이드 했습니다. 149 | 150 | ```swift 151 | class AutomaticCar: Car { 152 | override var currentSpeed: Double { 153 | didSet { 154 | gear = Int(currentSpeed / 10.0) + 1 155 | } 156 | } 157 | } 158 | ``` 159 | 160 | 코드 실행결과 오버라이드한 내용이 출력되는 것을 확인할 수 있습니다. 161 | 162 | ```swift 163 | let automatic = AutomaticCar() 164 | automatic.currentSpeed = 35.0 165 | print("AutomaticCar: \(automatic.description)") 166 | // AutomaticCar: traveling at 35.0 miles per hour in gear 4 167 | ``` 168 | 169 | ## 오버라이드 방지 \(Preventing Overrides\) 170 | 171 | 서브클래스에서 특정 메소드, 프로퍼티, 서브스크립트가 오버라이드 되는 것을 방지하려면 `final`키워드를 사용합니다. 다시말해, `final`로 선언되면 `override`되는 것을 막을 수 있습니다. \(final func, final class func, final subscript\) 만일 `final`로 선언된 메소드, 프로퍼티, 서브스크립트를 오버라이드 하려고 하면 컴파일 시간\(compile-time\)에 에러가 발생합니다. 클래스 전체를 `final`로 선언해서 클래스 안의 모든 메소드, 프로퍼티 등이 `override`가 되는 것을 막을 수 있습니다. \(final class\) 172 | 173 | -------------------------------------------------------------------------------- /language-guide/15-deinitialization.md: -------------------------------------------------------------------------------- 1 | # 초기화 해지 \(Deinitialization\) 2 | 3 | 디이니셜라이저는 초기자\(Initializer\)와 반대로 클래스 인스턴스가 소멸되기 직전에 호출됩니다. 초기자는 선언 키워드로 `init`를 사용하는데 디이니셜라이저는 선언을 위해 `deinit`키워드를 사용합니다. 디이니셜라이저는 오직 클래스 타입에서만 사용 가능합니다. 4 | 5 | ## 초기화 해지의 동작 \(How Deinitilization Works\) 6 | 7 | 일반적으로 Swift가 자원의 해제를 자동으로 알아서 해주는데, 열었던 파일을 사용이 끝나고 닫는 것 같이 사용자가 자원 해지를 위해 수동으로 작업 해야하는 경우도 있습니다. 이럴 때 사용하는 것이 디이니셜라이저입니다.디이니셜라이저는 클래스당 오직 하나만 선언할 수 있고 파라미터를 받을 수 없습니다. 디이니셜라이저의 기본적인 선언 형태는 다음과 같습니다. 8 | 9 | ```swift 10 | deinit { 11 | // perform the deinitialization 12 | } 13 | ``` 14 | 15 | 디이니셜라이저는 자동으로 호출되고 수동으로 호출할 수 없습니다. Superclass의 디이니셜라이저는 Subclass에서 디이니셜라이저를 선언하지 않아도 자동으로 호출 됩니다. 16 | 17 | ## 디이니셜라이저의 사용 \(Deinitializers in Action\) 18 | 19 | 디이니셜라이저의 동작확인을 위해 예제코드를 하나 만들어 보겠습니다. 20 | 21 | ```swift 22 | class Bank { 23 | static var coinsInBank = 10_000 24 | static func distribute(coins numberOfCoinsRequested: Int) -> Int { 25 | let numberOfCoinsToVend = min(numberOfCoinsRequested, coinsInBank) 26 | coinsInBank -= numberOfCoinsToVend 27 | return numberOfCoinsToVend 28 | } 29 | static func receive(coins: Int) { 30 | coinsInBank += coins 31 | } 32 | } 33 | ``` 34 | 35 | 은행 클래스를 하나 선언하고 은행이 소유한 코인수 `coinsInBank` 코인을 배포하는 `distribute`메소드와 코인을 받는 `receive` 메소드를 선언했습니다. `distribute`메소드에서는 현재 은행에 남은 코인수를 확인해 요청한 코인수와 비교해 더 작은 것을 반환합니다. 만약 은행에 남은 코인이 0이라면 요청한 코인 대신 0을 반환합니다. `receive` 메소드는 코인을 받아 은행에 추가하는 메소드입니다. 이 코인을 활용해 게임은 한다고 가정해 봅시다. 사용자는 게임을 하는데 처음에 일정 코인을 은행으로 부터 받고 시작하고, 게임에서 이길 때마다 은행에서 코인을 받아 사용자의 지갑에 저장합니다. 36 | 37 | ```swift 38 | class Player { 39 | var coinsInPurse: Int 40 | init(coins: Int) { 41 | coinsInPurse = Bank.distribute(coins: coins) 42 | } 43 | func win(coins: Int) { 44 | coinsInPurse += Bank.distribute(coins: coins) 45 | } 46 | deinit { 47 | Bank.receive(coins: coinsInPurse) 48 | } 49 | } 50 | ``` 51 | 52 | 게임이 끝나면 은행에서 받았던 돈을 다시 은행에 돌여주기 위해 위 코드에서 `deinit`안에 `Bank.receive(coins: coinsInPurse)` 코드를 넣어 사용했던 돈은 모두 은행에 다시 반환하도록 했습니다. 자 게임을 시작해보죠. 53 | 54 | ```swift 55 | var playerOne: Player? = Player(coins: 100) 56 | print("A new player has joined the game with \(playerOne!.coinsInPurse) coins") 57 | // Prints "A new player has joined the game with 100 coins" 58 | // 사용자는 100 코인을 갖고 시작합니다. 59 | print("There are now \(Bank.coinsInBank) coins left in the bank") 60 | // Prints "There are now 9900 coins left in the bank" 61 | // 현 시점에 은행은 9900의 코인을 소유하고 있습니다. 62 | ``` 63 | 64 | 사용자가 게임에 이겨 2000 코인을 받게 됩니다. 65 | 66 | ```swift 67 | playerOne!.win(coins: 2_000) 68 | print("PlayerOne won 2000 coins & now has \(playerOne!.coinsInPurse) coins") 69 | // Prints "PlayerOne won 2000 coins & now has 2100 coins" 70 | // 사용자가 게임에 이겨 2000코인을 받아 처음에 갖고 있던 100 코인과 더불어 현재 총 2100 코인을 소유하게 됩니다. 71 | print("The bank now only has \(Bank.coinsInBank) coins left") 72 | // Prints "The bank now only has 7900 coins left" 73 | // 사용자에게 2100 코인을 나눠준 은행에는 현재 7900 코인이 남았습니다. 74 | ``` 75 | 76 | 게임이 끝났습니다. 77 | 78 | ```swift 79 | playerOne = nil 80 | print("PlayerOne has left the game") 81 | // Prints "PlayerOne has left the game" 82 | // 사용자가 게임에서 나갔습니다. 83 | print("The bank now has \(Bank.coinsInBank) coins") 84 | // Prints "The bank now has 10000 coins" 85 | ``` 86 | 87 | `playerOne = nil`이라는 것은 더 이상 이 인스턴스를 사용하지 않는다는 것입니다. 그래서 앞의 디이니셜라이저\(deinit\)이 실행돼 그곳에 선언한 지갑에 있는 돈을 은행에 반납하는 코드가 실행돼 은행은 다시 처음에 갖고 있던 10000 코인을 갖게 됩니다. 88 | 89 | -------------------------------------------------------------------------------- /language-guide/16-optional-chaining.md: -------------------------------------------------------------------------------- 1 | # 옵셔널 체이닝 \(Optional Chaining\) 2 | 3 | 옵셔널 체이닝은 nil일 수도 있는 프로퍼티나, 메소드 그리고 서브스크립트에 질의\(query\)를하는 과정을 말합니다. 만약 옵셔널이 프로퍼티나 메소드 혹은 서브스크립트에 대한 값을 갖고 있다면 그 값을 반환하고 만약 값이 nil이면 nil을 반환 합니다. 여러 질의를 연결해서 할 수도 있는데, 연결된 질의에서 어느 하나라도 nil이면 전체 결과는 nil이 됩니다. 4 | 5 | > NOTE 6 | > Swift에서 옵셔널 체이닝은 Objective-C에서의 nil 메시징과 유사합니다. 차이점은 Swift에서는 옵셔널 체이닝은 reference type뿐만 아니라 primitive타입에서 사용도 가능하고 값을 가져오는데 성공했는지 실패했는지 확인할 수 있습니다. 7 | 8 | ## 강제 언래핑의 대체로써의 옵셔널 체이닝 \(Optional Chaining as an Alternative to Forced Unwrapping\) 9 | 10 | 옵셔널 체이닝은 옵셔널 값 뒤에 물음표\(?\)를 붙여서 표현 가능합니다. 옵셔널을 사용할 수 있는 값에는 프로퍼티, 메소드 그리고 서브스크립트가 포함 됩니다. 옵셔널 값을 강제 언래핑 하기위해서 뒤에 느낌표\(!\)를 붙이는 것과 문법이 비슷한데, 가장 큰 차이는 강제 언레핑을 했는데 만약 그 값이 없으면 런타임 에러가 발생하지만, 옵셔널 체이닝을 사용하면 런타임 에러 대신 nil이 반환 된다는 것입니다. 11 | 12 | 옵셔널 체이닝에 의해 nil 값이 호출 될 수 있기 때문에 옵셔널 체이닝의 값은 항상 옵셔널 값이 됩니다. 옵셔널 값을 반환하지 않는 프로퍼티, 메소드 혹은 서브스크립트를 호출하더라도 옵셔널 체이닝에 의해 옵셔널 값이 반환 됩니다. 이 옵셔널 리턴 값을 이용해 옵셔널 체이닝이 성공적으로 실행 됐는지 아니면 nil을 반환 했는지 확인할 수 있습니다. 13 | 14 | 보다 구체적으로는 옵셔널 체이닝에 의해 호출되면 반환 값과 같은 타입에 옵셔널이 붙어 반환 됩니다. 예를 들어, Int를 반환하는 메소드 경우 옵셔널 체이닝이 성공적으로 실행되면 Int?를 반환합니다. 15 | 16 | 다음 코드는 옵셔널 체이닝이 강제 언래핑과 어떻게 다른지 설명해 줍니다. 17 | 18 | ```swift 19 | class Person { 20 | var residence: Residence? 21 | } 22 | 23 | class Residence { 24 | var numberOfRooms = 1 25 | } 26 | ``` 27 | 28 | Residence 인스턴스는 numberOfRooms이라는 Int 프로퍼티 하나를 소유하고 있고 Person 인스턴스는 옵셔널 residence 프로퍼티를 Residence?로 소유합니다. 만약 Person이라는 인스턴스를 생성하면 residence 프로퍼티는 옵셔널의 기본값인 nil로 초기화 됩니다. 아래 코드에서 john은 residence 프로퍼티가 nil인 값을 소유합니다. 29 | 30 | `let john = Person()` 31 | 32 | 만약 이 person의 residence에 numberOfRooms프로퍼티에 접근하기 위해 느낌표를 이용해 강제 언래핑을 한다면 residence가 언래핑 할 값이 없기 때문에 런타임 에러가 발생합니다. 33 | 34 | ```swift 35 | let roomCount = john.residence!.numberOfRooms 36 | // this triggers a runtime error 37 | ``` 38 | 39 | numberOfRooms의 값에 접근하기 위해 강제 언래핑 대신 옵셔널 체이닝을 사용할 수 있습니다. 옵셔널 체이닝을 사용하기 위해 느낌표 대신 물음표를 사용합니다. 40 | 41 | ```swift 42 | if let roomCount = john.residence?.numberOfRooms { 43 | print("John's residence has \(roomCount) room(s).") 44 | } else { 45 | print("Unable to retrieve the number of rooms.") 46 | } 47 | // Prints "Unable to retrieve the number of rooms." 48 | ``` 49 | 50 | 옵셔널 체이닝의 체인 이라는 단어처럼 이 residence 프로퍼티들은 묶여 있습니다. 그래서 위 코드에서 numberOfRooms 프로퍼티는 옵셔널이 아니지만 이 값에 접근하기 위해 사용했던 인스턴스의 프로퍼티 residence가 옵셔널이기 때문에 최종 값은 옵셔널 값이 됩니다. 51 | 52 | nil이었던 residence 값에 Residence 인스턴스를 생성해 추가할 수 있습니다. `john.residence = Residence()` 53 | 54 | john.residence는 이제 nil이 아니라 Residence 인스턴스를 갖습니다. 그래서 옵셔널 체이닝으로 numberOfRooms 값에 접근 했을 때 nil 대신 Int? 값인 1을 리턴합니다. 55 | 56 | ```swift 57 | if let roomCount = john.residence?.numberOfRooms { 58 | print("John's residence has \(roomCount) room(s).") 59 | } else { 60 | print("Unable to retrieve the number of rooms.") 61 | } 62 | // Prints "John's residence has 1 room(s)." 63 | ``` 64 | 65 | ## 옵셔널 체이닝을 위한 모델 클래스 정의 \(Defining Model Classes for Optional Chaining\) 66 | 67 | 옵셔널 체이닝을 프로퍼티, 메소드 그리고 서브스크립트에서 사용할 수 있는데, 한 단계가 아닌 여러 level로\(multilevel optional chaining\) 사용할 수 있습니다. 68 | 69 | 아래 코드는 위의 Person, Residence 모델을 확장해 Room과 Address 클래스를 추가한 4가지 모델을 정의합니다. Person 클래스는 전과 같습니다. 70 | 71 | ```swift 72 | class Person { 73 | var residence: Residence? 74 | } 75 | ``` 76 | 77 | Residence는 전보다 더 복잡해 졌습니다. 이번에 Residence는 rooms라는 프로퍼티를 소유합니다. 이 프로퍼티는 \[Room\]타입의 빈 배열로 초기화 됩니다. 78 | 79 | ```swift 80 | class Residence { 81 | var rooms = [Room]() 82 | var numberOfRooms: Int { 83 | return rooms.count 84 | } 85 | subscript(i: Int) -> Room { 86 | get { 87 | return rooms[i] 88 | } 89 | set { 90 | rooms[i] = newValue 91 | } 92 | } 93 | func printNumberOfRooms() { 94 | print("The number of rooms is \(numberOfRooms)") 95 | } 96 | var address: Address? 97 | } 98 | ``` 99 | 100 | 이번 버전의 Residence 가 Room인스턴스의 배열을 소유하고 있기 때문에 numberOfRooms 프로퍼티는 계산된 프로퍼티로 선언됩니다. rooms 배열을 접근하기 위한 단축\(shortcut\)으로 서브스크립트\(subscript\)를 선언했습니다. 101 | 102 | rooms 배열의 Room 클래스는 이름 하나를 초기화 때 인자로 받는 간단한 클래스 입니다. 103 | 104 | ```swift 105 | class Room { 106 | let name: String 107 | init(name: String) { self.name = name } 108 | } 109 | ``` 110 | 111 | 마지막 클래스는 Address입니다. 이 클래스는 3개의 String? 옵셔널 프로퍼티를 갖습니다. 처음 두개는 buildingName, buildingNumber이고, 빌딩 주소를 구분하는 대체 수단\(alternative\)입니다. 마지막 street프로퍼티는 거리의 주소를 나타내는데 사용됩니다. 112 | 113 | ```swift 114 | class Address { 115 | var buildingName: String? 116 | var buildingNumber: String? 117 | var street: String? 118 | func buildingIdentifier() -> String? { 119 | if let buildingNumber = buildingNumber, let street = street { 120 | return "\(buildingNumber) \(street)" 121 | } else if buildingName != nil { 122 | return buildingName 123 | } else { 124 | return nil 125 | } 126 | } 127 | } 128 | ``` 129 | 130 | Address 클래스는 buildingIdentifier\(\)라는 String?타입의 메소드를 지원합니다. 이 메소드는 buildingNumber와 street을 확인해 값이 있으면 buildingNumber와 결합된 street 값을 반환하고 값이 없는 경우 nil을 반환 합니다. 131 | 132 | ## 옵셔널 체이닝을 통한 프로퍼티의 접근 \(Accessing Properties Through Optional Chaining\) 133 | 134 | 옵셔널 체이닝을 이용해 프로퍼티에 접근 할 수 있습니다. 135 | 136 | ```swift 137 | let john = Person() 138 | if let roomCount = john.residence?.numberOfRooms { 139 | print("John's residence has \(roomCount) room(s).") 140 | } else { 141 | print("Unable to retrieve the number of rooms.") 142 | } 143 | // Prints "Unable to retrieve the number of rooms." 144 | ``` 145 | 146 | 위 코드의 경우 residence?가 nil이기 때문에 옵셔널 체이닝 결과 nil을 호출하게 됩니다. 옵셔널 체이닝을 값을 할당하는데 사용할 수도 있습니다. 147 | 148 | ```swift 149 | let someAddress = Address() 150 | someAddress.buildingNumber = "29" 151 | someAddress.street = "Acacia Road" 152 | john.residence?.address = someAddress 153 | ``` 154 | 155 | 위 예제에서는 Address\(\) 인스턴스를 생성해 john.residence의 address로 할당하는 코드입니다. john.residence?가 nil이기 때문에 address 할당은 실패합니다. 할당도 할당받는 왼쪽 항이 nil이면 아예 오른쪽 항이 실행되지 않습니다. 확인을 위해 createAddress\(\) 메소드를 만들고 메소드 실행 첫줄에 "Function was called.”를 출력하도록 했습니다. 156 | 157 | ```swift 158 | func createAddress() -> Address { 159 | print("Function was called.") 160 | let someAddress = Address() 161 | someAddress.buildingNumber = "29" 162 | someAddress.street = "Acacia Road" 163 | 164 | return someAddress 165 | } 166 | john.residence?.address = createAddress() 167 | ``` 168 | 169 | 이 코드를 `john.residence?.address = createAddress()`에서 호출하면 "Function was called.”가 출력되지 않는 것을 확인함으로써 이 메소드가 아예 실행되지 않았음을 확인할 수 있습니다. 170 | 171 | ## 옵셔널 체이닝을 통한 메소드 호출 \(Calling Methods Through Optional Chaining\) 172 | 173 | 옵셔널 체이닝을 이용해 메소드를 호출 할 수 있습니다. 174 | 175 | ```swift 176 | func printNumberOfRooms() { 177 | print("The number of rooms is \(numberOfRooms)") 178 | } 179 | ``` 180 | 181 | 위 메소드는 리턴 값이 명시되지 않았습니다. 하지만 함수나 메소드는 리턴 값이 없는 경우암시적으로 Void라는 값을 갖습니다. \(참조 [Functions Without Return Values](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Functions.html#//apple_ref/doc/uid/TP40014097-CH10-ID163) . \) 그래서 이 메소드가 옵셔널 체이닝에서 호출되면 반환 값은 Void가 아니라 Void? 가 타입이 반환됩니다. 182 | 183 | 관련된 예제 코드를 살펴 보겠습니다. 184 | 185 | ```swift 186 | if john.residence?.printNumberOfRooms() != nil { 187 | print("It was possible to print the number of rooms.") 188 | } else { 189 | print("It was not possible to print the number of rooms.") 190 | } 191 | // Prints "It was not possible to print the number of rooms." 192 | ``` 193 | 194 | 위 코드는 `john.residence?.printNumberOfRooms()` 메소드 호출의 결과가 nil인지 아닌지 비교를 하고 그 결과에 대한 처리를 합니다. 위에서 언급했던 것처럼 printNumberOfRooms\(\) 에는 직접적인 반환 값이 명시돼 있지 않지만 암시적으로 Void를 반환하고 이 메소드 호출이 옵셔널 체이닝에서 이루어 지기 때문에 Void? 가 반환되서 nil비교를 할 수 있습니다. 195 | 196 | 옵셔널 체이팅을 통해 값을 할당하면 값을 Void? 값을 반환합니다. 그래서 이 값이 nil인지 아닌지 비교할 수 있습니다. 아래 코드는 이와 관련한 예제 입니다. 197 | 198 | ```swift 199 | if (john.residence?.address = someAddress) != nil { 200 | print("It was possible to set the address.") 201 | } else { 202 | print("It was not possible to set the address.") 203 | } 204 | // Prints "It was not possible to set the address." 205 | ``` 206 | 207 | ## 옵셔널 체이닝을 통한 서브스크립트 접근 \(Accessing Subscripts Through Optional Chaining\) 208 | 209 | 옵셔널 체이닝을 이용해 옵셔널 값을 서브스크립트로 접근할 수 있습니다. 210 | 211 | > NOTE 212 | > 옵셔널 값을 서브스크립트로 접근 하기 위해서는 \[\] 괄호 전에 물음표\(?\) 기호를 붙여서 사용합니다. 213 | 214 | 아래 예제는 서브스크립트를 이용해 rooms에서 첫 rooms의 name을 요청하는 코드입니다. 현재 john.residence가 nil이기 때문에 서브스크립트 접근은 실패합니다. 215 | 216 | ```swift 217 | if let firstRoomName = john.residence?[0].name { 218 | print("The first room name is \(firstRoomName).") 219 | } else { 220 | print("Unable to retrieve the first room name.") 221 | } 222 | // Prints "Unable to retrieve the first room name." 223 | ``` 224 | 225 | 옵셔널 체이닝에서 서브스크립트로 값을 가져오는 것과 유사한 형태로 값을 할당할 수 있습니다. `john.residence?[0] = Room(name: "Bathroom")` 이 코드는 첫 room 값을 할당하는 코드인데 room을 할당 하지 못하고 fail이 발생합니다. 이유는 residence가 현재 nil이기 때문입니다. 226 | 227 | 아래 코드와 같이 Residence 인스턴스를 할당하면 residence 서브스크립트를 사용할 수 있습니다. 228 | 229 | ```swift 230 | let johnsHouse = Residence() 231 | johnsHouse.rooms.append(Room(name: "Living Room")) 232 | johnsHouse.rooms.append(Room(name: "Kitchen")) 233 | john.residence = johnsHouse 234 | 235 | if let firstRoomName = john.residence?[0].name { 236 | print("The first room name is \(firstRoomName).") 237 | } else { 238 | print("Unable to retrieve the first room name.") 239 | } 240 | // Prints "The first room name is Living Room." 241 | ``` 242 | 243 | ### 옵셔널 타입의 서브스크립트 접근 \(Accessing Subscripts of Optional Type\) 244 | 245 | 만약 Swift의 사전 타입\(Dictionary Type\)같이 서브스크립트의 결과로 옵셔널을 반환 한다면 그 뒤에 물음표\(?\)를 붙여 줍니다. 사전 타입은 key-value로 동작하기 때문에 항상 그 사전에 key가 존재한다는 보장이 없기 때문에 옵셔널 값을 반환합니다. 246 | 247 | ```swift 248 | var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]] 249 | testScores["Dave"]?[0] = 91 250 | testScores["Bev"]?[0] += 1 251 | testScores["Brian"]?[0] = 72 252 | // the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81] 253 | ``` 254 | 255 | 위 예제는 testScores라는 사전에서 특정 key에 대한 값을 get 혹은 set하는 코드입니다. Dave, Bev와 관련한 값은 처리가 되고 Brian은 등록된 key가 없기 때문에 관련한 아무런 일도 처리도 일어나지 않습니다. 256 | 257 | ## 체이닝의 다중 레벨 연결 \(Linking Multiple Levels of Chaining\) 258 | 259 | 옵셔널 체이닝이 여러 단계에 걸쳐 연결 될 수 있습니다. 상위 값이 옵셔널 값인 경우 하위도 옵셔널 값이라고 해서 더 옵셔널 해지진 않습니다. 옵셔널은 옵셔널입니다. 260 | 261 | * 옵셔널 체이닝의 상위 레벨 값이 옵셔널인 경우 현재 값이 옵셔널이 아니더라도 그 값은 옵셔널값이 됩니다. 262 | * 옵셔널 체이닝의 상위 레벨 값이 옵셔널이고 현재 값이 옵셔널 이라고 해서 더 옵셔널하게 되진 않습니다. 263 | * 옵셔널 체이닝을 통해 값을 검색하거나 메소드를 호출하면 몇 단계를 거치는지 상관없이 옵셔널을 반환합니다. 264 | 265 | 예제를 보시겠습니다. 266 | 267 | ```swift 268 | if let johnsStreet = john.residence?.address?.street { 269 | print("John's street name is \(johnsStreet).") 270 | } else { 271 | print("Unable to retrieve the address.") 272 | } 273 | // Prints "Unable to retrieve the address." 274 | ``` 275 | 276 | john.residence는 valid한 Residence 인스턴스를 갖고 있지만 address가 nil이어서 이 옵셔널 체이닝은 fail이 됩니다. 비록 상위의 옵셔널 값인 john.residence가 true여도 하위 값이 nil 이면 체이닝은 fail이 됩니다. 277 | 278 | Address 인스턴스를 생성에서 할당하고나서 다시 옵셔널 체이닝을 해보겠습니다. 279 | 280 | ```swift 281 | let johnsAddress = Address() 282 | johnsAddress.buildingName = "The Larches" 283 | johnsAddress.street = "Laurel Street" 284 | john.residence?.address = johnsAddress 285 | 286 | if let johnsStreet = john.residence?.address?.street { 287 | print("John's street name is \(johnsStreet).") 288 | } else { 289 | print("Unable to retrieve the address.") 290 | } 291 | // Prints "John's street name is Laurel Street." 292 | ``` 293 | 294 | Residence인스턴스와 Address인스턴스가 모두 존재하기 때문에 옵셔널 체이닝은 success가 됩니다. 295 | 296 | ## 체이닝에서 옵셔널 값을 반환하는 메소드 \(Chaining on Methods with Optional Return Values\) 297 | 298 | 아래 예제와 같이 옵셔널 체이닝에서 반환 값이 있는 메소드를 호출할 수 있습니다. 299 | 300 | ```swift 301 | if let buildingIdentifier = john.residence?.address?.buildingIdentifier() { 302 | print("John's building identifier is \(buildingIdentifier).") 303 | } 304 | // Prints "John's building identifier is The Larches." 305 | ``` 306 | 307 | 만약 메소드의 반환 값을 갖고 추가적인 행동을 더 하고 싶다면 메소드 호출 표현 뒤에 물음표\(?\)를 붙이고 그 행동을 적어주시면 됩니다. 앞에서 언급 했던 것 처럼 옵셔널 체이닝에 물려 있기 때문에 메소드의 반환 값도 옵셔널이 돼서 그 표시를 해줘야 합니다. 308 | 309 | ```swift 310 | if let beginsWithThe = 311 | john.residence?.address?.buildingIdentifier()?.hasPrefix("The") { 312 | if beginsWithThe { 313 | print("John's building identifier begins with \"The\".") 314 | } else { 315 | print("John's building identifier does not begin with \"The\".") 316 | } 317 | } 318 | // Prints "John's building identifier begins with "The"." 319 | ``` 320 | 321 | > NOTE 322 | > 옵셔널이 반환 값에 걸려있고 메소드 자체에 걸려 있는 것이 아니기 때문에 메소드 괄호 뒤에 “?”를 붙여 줍니다. 323 | 324 | -------------------------------------------------------------------------------- /language-guide/17-error-handling.md: -------------------------------------------------------------------------------- 1 | # 에러 처리\(Error Handling\) 2 | 3 | 프로그램 실행시 에러가 발생하면 그 상황에 대해 적절한 처리가 필요합니다. 이 과정을 에러 처리라고 부릅니다. Swift에서는 런타임에 에러가 발생한 경우 그것의 처리를 위해 에러의 발생\(throwing\), 감지\(catching\), 증식\(propagating\), 조작\(manipulating\)을 지원하는 일급 클래스\(first-class\)를 제공합니다. 4 | 5 | 어떤 명령은 항상 완전히 실행되는 것이 보장되지 않는 경우가 있습니다. 그런 경우에 옵셔널을 사용해 에러가 발생해 값이 없다는 것을 표시할 수 있지만, 어떤 종류의 에러가 발생했는지 확인할 수는 없습니다. 이럴 때는 구제적으로 발생한 에러를 확인할 수 있어야 코드를 작성하는 사람이 각 에러의 경우에 따른 적절한 처리를 할 수 있습니다. 6 | 7 | 예를들어, 디스크에서 파일을 읽어 데이터를 처리하는 일을 한다고 할 때 이 작업이 실패할 경우의 수는 여러가지가 존재합니다. 파일이 특정 경로에 존재하지 않거나, 읽기 권한이 없거나 혹은 파일의 데이터가 식별할 수 있는 포맷으로 적절히 인코딩 되지 않은 경우 등 말이죠. 이런 종류별 에러 상황을 식별해 사용자에게 제공해 주면 프로그램 실행중 발생할 각 에러별로 사용자가 적절히 대응할 수 있도록 도울 수 있습니다. 8 | 9 | > NOTE 10 | > Swift에서 에러 처리는 Cocoa의 NSError 클래스와 상호 호환되는 에러 핸들링 패턴을 사용합니다. 11 | 12 | ## 에러의 표시와 발생\(Representing and Throwing Errors\) 13 | 14 | Swift에서 에러는 Error 프로토콜을 따르는 타입의 값으로 표현됩니다. 비어있는\(Empty\) 이 프로토콜은 프로토콜을 따르는 타입이 에러 처리를 위해 사용 될 수 있다는 것을 가르킵니다. 15 | 16 | Swift의 열거형은 특별히 이런 관련된 에러를 그룹화\(Grouping\)하고 추가적인 정보를 제공하기에 적합합니다. 예를들어, 게임 안에서 판매기기 동작의에러 상황을 다음과 같이 표현할 수 있습니다. 17 | 18 | ```swift 19 | enum VendingMachineError: Error { 20 | case invalidSelection 21 | case insufficientFunds(coinsNeeded: Int) 22 | case outOfStock 23 | } 24 | ``` 25 | 26 | 에러를 발생시킴으로써 무언가 기대하지 않았던 동작이 발생했고 작업을 계속 수행할 수 없다는 것을 알려줄 수 있습니다. 에러를 발생시키기 위해 throw 구문을 사용할 수 있습니다. 예를들어, 다음 코드는 판매 기기에서 5개의 코인이 더 필요하다는 에러를 발생시킵니다. 27 | 28 | ```swift 29 | throw VendingMachineError.insufficientFunds(coinsNeeded: 5) 30 | ``` 31 | 32 | ## 에러 처리\(Handling Errors\) 33 | 34 | 에러가 발생하면 특정 코드영역이 해당 에러를 처리하도록 해야합니다. 예를들어, 문제를 해결하거나, 혹은 우회할 수 있는 방법을 시도하거나 아니면 사용자에게 실패 상황을 알리는 것이 에러 처리의 방법이 될 수 있습니다. 35 | 36 | Swift에서 4가지 에러를 처리 방법이 있습니다. 첫째로 에러가 발생한 함수에서 리턴값으로 에러를 반환해 해당 함수를 호출한 코드에서 에러를 처리하도록 하는 방법, 두번째로 do-catch 구문을 사용하는 방법, 세째로 옵셔널 값을 반환하는 방법, 마지막으로 assert를 사용해 강제로 크래쉬를 발생시키는 방법입니다. 각각의 방법에 대한 설명은 이 후 섹션에 설명돼 있습니다. 37 | 38 | > Note 39 | > Swift에서의 에러 처리는 다른 언어의 exception 처리와 닮았습니다. try-catch와 throw 키워드를 사용합니다. Objective-C를 포함해 다른 언어의 exception 처리와 다른 점은 Swift에서 에러 처리는 많은 계산이 필요할 수 있는 콜스택\(call stack\) 되돌리기\(unwinding\)와 관련이 없다는 것 입니다. 그렇기 때문에 에러를 반환하는 throw 구문은 일반적인 반환 구문인 return 구문과 비슷한 성능을 보여줍니다. 40 | 41 | ### 에러를 발생시키는 함수 사용하기\(Propagating Errors Using Throwing Fuctions\) 42 | 43 | 어떤 함수, 메소드 혹은 초기자가 에러를 발생 시킬 수 있다는 것을 알리기 위해서 throw 키워드를 함수 선언부의 파라미터 뒤에 붙일 수 있습니다. throw 키워드로 표시된 함수를 throwing function이라고 부릅니다. 만약 함수가 리턴 값을 명시했다면 throw 키워드는 리턴 값 표시 기호인 -> 전에 적습니다. 44 | 45 | ```swift 46 | func canThrowErrors() throws -> String 47 | 48 | func cannotThrowErrors() -> String 49 | ``` 50 | 51 | throwing function은 함수 내부에서 에러를\(throw\)를 만들어 함수가 호출된 곳에 전달합니다. 52 | 53 | > Note 54 | > 오직 throwing function만이 에러를 발생시킬 수 있습니다. 만약 throwing function이 아닌 함수에서 throw가 발생한다면 반드시 그 함수내에서 throw에 대해 처리돼야 합니다. 아래 예제에서 VendingMachine 클래스는 요청한 아이템이 이용가능하지 않거나, 재고가 없거나, 현재 계좌를 초과하는 비용이 발생했을 때를 구분해 적절한 에러를 발생시키는 VendingMachineError throw를 발생시키는 vend\(itemNamed:\) 메소드를 갖고 있습니다. 55 | 56 | ```swift 57 | struct Item { 58 | var price: Int 59 | var count: Int 60 | } 61 | 62 | class VendingMachine { 63 | var inventory = [ 64 | "Candy Bar": Item(price: 12, count: 7), 65 | "Chips": Item(price: 10, count: 4), 66 | "Pretzels": Item(price: 7, count: 11) 67 | ] 68 | var coinsDeposited = 0 69 | 70 | func vend(itemNamed name: String) throws { 71 | guard let item = inventory[name] else { 72 | throw VendingMachineError.invalidSelection 73 | } 74 | 75 | guard item.count > 0 else { 76 | throw VendingMachineError.outOfStock 77 | } 78 | 79 | guard item.price <= coinsDeposited else { 80 | throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited) 81 | } 82 | 83 | coinsDeposited -= item.price 84 | 85 | var newItem = item 86 | newItem.count -= 1 87 | inventory[name] = newItem 88 | 89 | print("Dispensing \(name)") 90 | } 91 | } 92 | ``` 93 | 94 | vend\(itemNamed:\) 메소드의 구현에서 guard 구문을 사용해 snack을 구매하는 과정에서 에러가 발생하면 함수에서 에러를 발생시키고 빠르게 함수를 탈출할 수 있도록 합니다\(early exit\). vend\(itemNamed:\) 메소드는 에러를 발생시키기 때문에 이 메소드를 호출하는 메소드는 반드시 do-catch, try?, try! 등의 구문을 사용해 에러를 처리해야 합니다. 예를 들어, 아래 예제의 buyFavoriteSnack\(person:vendingMachine:\) 또한 에러를 발생시키는 함수인데 vend\(itemNamed:\) 메소드에서 발생한 에러는 buyFavoriteSnack\(person:vendingMachine:\) 함수가 실행되는 곳에까지 전해집니다. 95 | 96 | ```swift 97 | let favoriteSnacks = [ 98 | "Alice": "Chips", 99 | "Bob": "Licorice", 100 | "Eve": "Pretzels", 101 | ] 102 | func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws { 103 | let snackName = favoriteSnacks[person] ?? "Candy Bar" 104 | try vendingMachine.vend(itemNamed: snackName) 105 | } 106 | ``` 107 | 108 | 이 예제에서는 buyFavoriteSnack\(person:vendingMachine:\) 함수가 주어진 사람의 가장 좋아하는 스낵이 뭔지 확인하고 그것을 vend\(itemNamed:\) 메소드를 호출해 구매 시도를 합니다. 왜냐하면 vend\(itemNamed:\) 메소드는 에러를 발생 시킬 수 있기 때문에 메소드 호출 앞에 try 키워드를 사용합니다. 에러 발생 초기자는 throwing function과 같은 방법으로 에러를 발생시킬 수 있습니다. 예를 들어, 아래 예제의 PurchasedSnack 구조체의 초기자는 초기화 단계의 일부분으로써 에러를 발생시킬 수 있는 함수입니다. 그리고 초기자가 실행될 때 발생한 에러는 이 초기자를 호출한 곳에 전달 됩니다. 109 | 110 | ```swift 111 | struct PurchasedSnack { 112 | let name: String 113 | init(name: String, vendingMachine: VendingMachine) throws { 114 | try vendingMachine.vend(itemNamed: name) 115 | self.name = name 116 | } 117 | } 118 | ``` 119 | 120 | ### Do-Catch를 이용해 에러를 처리하기\(Handling Error Using Do-Catch\) 121 | 122 | do-catch를 이용해 에러를 처리하는 코드 블럭을 작성할 수 있 수 있습니다. 만약 에러가 do 구문 안에서 발생한다면 발생하는 에러의 종류를 catch 구문으로 구분해 처리할 수 있습니다. 다음은 do-catch 구문의 일반적인 형태입니다. 123 | 124 | ```swift 125 | do { 126 | try expression 127 | statements 128 | } catch pattern 1 { 129 | statements 130 | } catch pattern 2 where condition { 131 | statements 132 | } catch { 133 | statements 134 | } 135 | ``` 136 | 137 | catch 구문 뒤에 어떤 에러인지 적고 그것을 어떻게 처리할지 명시할 수 있고 만약 catch 구문 뒤에 에러 종류를 명시하지 않으면 발생하는 모든 에러를 지역 상수인 error로 바인딩 합니다. 보다 자세한 정보는 패턴 매칭을 참조하세요. 예를 들어, 다음 코드는 VendingMachineError 열거형의 모든 세 가지 에러 종류에 대해 처리하는 코드입니다. 138 | 139 | ```swift 140 | var vendingMachine = VendingMachine() 141 | vendingMachine.coinsDeposited = 8 142 | do { 143 | try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine) 144 | print("Success! Yum.") 145 | } catch VendingMachineError.invalidSelection { 146 | print("Invalid Selection.") 147 | } catch VendingMachineError.outOfStock { 148 | print("Out of Stock.") 149 | } catch VendingMachineError.insufficientFunds(let coinsNeeded) { 150 | print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.") 151 | } catch { 152 | print("Unexpected error: \(error).") 153 | } 154 | // Prints "Insufficient funds. Please insert an additional 2 coins." 155 | ``` 156 | 157 | 위 예제에서 buyFavoriteSnack\(person:vendingMachine:\) 함수는 try 표현 안에서 호출됩니다. 왜냐하면 이 함수가 에러를 발생시킬 수 있기 때문에 에러가 발생하자마자 catch 구문에 전달해 적절한 처리를 할 수 있게 하기위해서 입니다. 만약 발생한 에러 종류를 처리하는 catch 구문이 없다면 에러는 마지막 catch 구문에 걸리게 되서 지역 에러 상수인 error로 처리할 수 있습니다. 만약 아무런 에러도 발생하지 않는다면 do 구문이 실행됩니다. catch 구문에서 발생 가능한 모든 에러에 대해 반드시 종류 별로 처리할 필요는 없습니다. 만약 에러를 처리하는 적절한 catch 구문이 없다면 그 코드에 둘러 쌓인 곳에 에러가 발생합니다. 하지만 발생 되는 에러는 반드시 관련된 특정 코드 영역에서 처리돼야 합니다. 에러를 발생 시키지 않는 함수에서는 관련된 do-catch 구문에서 그 에러를 반드시 처리해야 하고, 에러를 발생 시키는 함수에서는 에러를 do-catch 구문에서 처리하거나 함수를 호출한 곳에서 반드시 에러를 처리해야 합니다. 만약 에러가 발생한 곳에서 에러에 대해 아무런 처리도 하지 않으면 런타임 에러가 발생하게 됩니다. 예를 들어, 위 코드는 모든 VendingMachineError에 대해 기술하는 것 대신 다음과 같이 처리 할 수 있습니다. 158 | 159 | ```swift 160 | func nourish(with item: String) throws { 161 | do { 162 | try vendingMachine.vend(itemNamed: item) 163 | } catch is VendingMachineError { // 모든 VendingMachineError 구분을 위해 is를 사용 164 | print("Invalid selection, out of stock, or not enough money.") 165 | } 166 | } 167 | 168 | do { 169 | try nourish(with: "Beet-Flavored Chips") 170 | } catch { 171 | print("Unexpected non-vending-machine-related error: \(error)") 172 | // 여기에서 처럼 catch를 그냥 if-else에서 else 같이 사용 가능 173 | } 174 | // Prints "Invalid selection, out of stock, or not enough money." 175 | ``` 176 | 177 | nourish\(with:\) 함수에서 만약 vend\(itemNamed:\) 초기자에서 VendingMachineError 열거형 중 한가지의 에러가 발생한 경우, nourish\(with:\) 함수가 에러를 처리해 메시지를 출력합니다. 반면 nourish\(with:\) 함수는 그것을 호출한 곳에 에러를 발생 시킵니다. 발생한 에러는 그리고 나서 일반적인 catch 구문에 의해 처리 됩니다. 178 | 179 | ### 에러를 옵셔널 값으로 변환하기 \(Converting Errors to Optional Values\) 180 | 181 | try? 구문을 사용해 에러를 옵셔널 값으로 변환할 수 있습니다. 만약 에러가 try? 표현 내에서 발생한다면, 그 표현의 값은 nil이 됩니다. 예를들어 다음 코드의 x와 y는 같은 값을 갖습니다. 182 | 183 | ```swift 184 | func someThrowingFunction() throws -> Int { 185 | // ... 186 | } 187 | 188 | let x = try? someThrowingFunction() 189 | 190 | let y: Int? 191 | do { 192 | y = try someThrowingFunction() 193 | } catch { 194 | y = nil 195 | } 196 | ``` 197 | 198 | 만약 someThrowingFunction\(\) 이 에러를 발생시키면 x와 y는 nil이 됩니다. 그렇지 않으면 x와 y는 함수의 리턴 값을 갖습니다. x와 y는 someThrowingFunction\(\)의 타입이 어떤 것이든 상관없이 옵셔널이 됩니다. 이 함수에서는 integer를 리턴하기 때문에 x와 y는 옵셔널 integer입니다. try?는 만약 발생하는 모든 에러를 같은 방법으로 처리하고 싶을 때 사용합니다. 예를 들어, 다음 코드는 데이터를 가져오는 여러 접근 방법을 시도하는데 접근 방법이 모두 실패하면 nil을 반환 합니다. 199 | 200 | ```swift 201 | func fetchData() -> Data? { 202 | if let data = try? fetchDataFromDisk() { return data } 203 | if let data = try? fetchDataFromServer() { return data } 204 | return nil 205 | } 206 | ``` 207 | 208 | ### 에러 발생을 중지하기 \(Disabling Error Propagation\) 209 | 210 | 함수나 메소드에서 에러가 발생되지 않을 것이라고 확신하는 경우 try!를 사용할 수 있습니다. 혹은 runtime assertion을 사용해 에러가 발생하지 않도록 할 수 있습니다. 하지만 만약 에러가 발생하면 런타임 에러가 발생하게 됩니다. 예를 들어, 다음 코드는 loadImage\(atPath:\) 함수를 사용해 주어진 경로에서 이미지 리소스를 불러오거나 이미지를 불러오는데 실패한 경우 에러를 발생 시킵니다. 이 경우에는 앱이 배포될때 이미지가 포함되 배포되기 때문에 런타임에는 아무 에러도 발생되지 않을 것이라 확신할 수 있어 try!를 사용하는 것이 적절합니다. `let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")` 211 | 212 | ## 정리 액션 기술 \(Specifying Cleanup Actions\) 213 | 214 | defer 구문을 이용해 함수가 종료 된 후 파일 스트림을 닫거나, 사용했던 자원을 해지 하는 등의 일을 할 수 있습니다. defer가 여러개가 있는 경우 가장 마지막 줄부터 실행 됩니다. 즉 bottom-up 순으로 실행 됩니다. 215 | 216 | ```swift 217 | func processFile(filename: String) throws { 218 | if exists(filename) { 219 | let file = open(filename) 220 | defer { 221 | close(file) // block이 끝나기 직전에 실행, 주로 자원 해제나 정지에 사용 222 | } 223 | while let line = try file.readline() { 224 | // Work with the file. 225 | } 226 | // close(file) is called here, at the end of the scope. 227 | } 228 | } 229 | ``` 230 | 231 | 위 예제는 defer 구문을 이용해 open\(_:\) 함수와 짝을 이루는 close\(_:\) 함수를 실행한다. 232 | 233 | > Note 234 | > defer 구문을 에러 처리 이외의 경우에도 사용할 수 있습니다. 235 | 236 | -------------------------------------------------------------------------------- /language-guide/18-type-casting.md: -------------------------------------------------------------------------------- 1 | # 타입캐스팅 \(Type Casting\) 2 | 3 | 타입캐스팅은 인스턴스의 타입을 확인하거나 인스턴스를 같은 계층\(hierachy\)에 있는 다른 `superclass`나 `subclass`로 취급하는 방법입니다. 타입캐스팅에는 `is`와 `as` 두 연산자를 사용합니다. 타입캐스팅을 이용하면 특정 프로토콜을 따르는지\(conforms\) 확인할 수도 있습니다. 4 | 5 | ## 타입캐스팅을 위한 클래스 계층구조 선언 \(Defining a Class Hierarchy for Type Casting\) 6 | 7 | 타입캐스팅의 동작을 확인하기 위해 클래스를 하나 만들어 보겠습니다. 8 | 9 | ```swift 10 | class MediaItem { 11 | var name: String 12 | init(name: String) { 13 | self.name = name 14 | } 15 | } 16 | ``` 17 | 18 | `MediaItem`이라는 클래스를 선언했는데 이 클래스를 서브클래싱 해서 두개의 다른 서브클래스를 만들어 보겠습니다. 19 | 20 | ```swift 21 | class Movie: MediaItem { 22 | var director: String 23 | init(name: String, director: String) { 24 | self.director = director 25 | super.init(name: name) 26 | } 27 | } 28 | 29 | class Song: MediaItem { 30 | var artist: String 31 | init(name: String, artist: String) { 32 | self.artist = artist 33 | super.init(name: name) 34 | } 35 | } 36 | ``` 37 | 38 | `MediaItem`클래스의 서브클래스 `Movie`와 `Song` 두개의 클래스를 선언했습니다. 마지막으로 이 `Movie`와 `Song` 두개의 클래스를 아이템으로 갖는 `library`배열을 선언합니다. 39 | 40 | ```swift 41 | let library = [ 42 | Movie(name: "Casablanca", director: "Michael Curtiz"), 43 | Song(name: "Blue Suede Shoes", artist: "Elvis Presley"), 44 | Movie(name: "Citizen Kane", director: "Orson Welles"), 45 | Song(name: "The One And Only", artist: "Chesney Hawkes"), 46 | Song(name: "Never Gonna Give You Up", artist: "Rick Astley") 47 | ] 48 | // the type of "library" is inferred to be [MediaItem] 49 | ``` 50 | 51 | `library`가 갖고 있는 `Movie`,`Song`인스턴스의 공통 부모는 `MediaItem`이기 때문에 `library`는 타입 추론에 의해 `[MediaItem]` 배열의 형을 갖게 됩니다. `library`를 순회\(iterate\)하면 배열의 아이템은 `Movie`, `Song` 타입이 아니라 `MediaItem`타입이라는 것을 확인할 수 있습니다. 타입 지정을 위해서는 `downcasting`을 이용해야 합니다. 52 | 53 | ## 형 확인 \(Checking Type\) 54 | 55 | `is`연산자를 이용해 특정 인스턴스의 타입을 확인할 수 있습니다. 아래 코드는 `library`배열을 순회하고 아이템이 특정 타입일때마다 그 숫자를 증가하는 예제 코드입니다. 56 | 57 | ```swift 58 | var movieCount = 0 59 | var songCount = 0 60 | 61 | for item in library { 62 | if item is Movie { 63 | movieCount += 1 64 | } else if item is Song { 65 | songCount += 1 66 | } 67 | } 68 | 69 | print("Media library contains \(movieCount) movies and \(songCount) songs") 70 | // "Media library contains 2 movies and 3 songs" 출력 71 | ``` 72 | 73 | ## 다운캐스팅 \(Downcasting\) 74 | 75 | 특정 클래스 타입의 상수나 변수는 특정 서브클래스의 인스턴스를 참조하고 있을 수 있습니다. `as?`와 `as!`연산자를 이용해 어떤 타입의 인스턴스인지 확인할 수 있습니다. `as?`는 특정 타입이 맞는지 확신할 수 없을때 사용하고 `as!`는 특정 타입이라는 것이 확실한 경우에 사용합니다. 단 `as!`으로 다운캐스팅을 했는데 지정한 타입이 아니라면 런타임 에러가 발생합니다. 다음 예제는 `library`배열의 `mediaItem`이 `Movie` 인스턴스 일수도 있고 `Song`인스턴스일 수도 있기 때문에 다운캐스팅을 위해 `as?`연산자를 사용했습니다. 76 | 77 | ```swift 78 | for item in library { 79 | if let movie = item as? Movie { 80 | print("Movie: \(movie.name), dir. \(movie.director)") 81 | } else if let song = item as? Song { 82 | print("Song: \(song.name), by \(song.artist)") 83 | } 84 | } 85 | 86 | // Movie: Casablanca, dir. Michael Curtiz 87 | // Song: Blue Suede Shoes, by Elvis Presley 88 | // Movie: Citizen Kane, dir. Orson Welles 89 | // Song: The One And Only, by Chesney Hawkes 90 | // Song: Never Gonna Give You Up, by Rick Astley 91 | ``` 92 | 93 | > NOTE 94 | > 캐스팅은 실제 인스턴스나 값을 바꾸는 것이 아니라 지정한 타입으로 취급하는 것 뿐입니다. 95 | 96 | ## Any, AnyObject의 타입 캐스팅 \(Type Casting for Any and AnyObject\) 97 | 98 | Swift에서는 두가지 특별한 타입을 제공합니다. 99 | 100 | * Any : 함수 타입을 포함해 모든 타입을 나타냅니다. 101 | * AnyObject : 모든 클래스 타임의 인스턴스를 나타냅니다. 102 | 103 | `Any`타입의 예제입니다. `things`라는 `Any`타입 배열을 선언해 여러 타입의 값을 저장합니다. 여기에는 `Int`, `String`, 함수, 클로저까지 포함됩니다. 104 | 105 | ```swift 106 | var things = [Any]() 107 | 108 | things.append(0) 109 | things.append(0.0) 110 | things.append(42) 111 | things.append(3.14159) 112 | things.append("hello") 113 | things.append((3.0, 5.0)) 114 | things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman")) 115 | things.append({ (name: String) -> String in "Hello, \(name)" }) 116 | ``` 117 | 118 | `things`를 순회하며 타입캐스팅이 되는지 `switch case`문에 `as` 연산자로 확인해 타입캐스팅 되는 배열의 원소의 값을 적절히 출력합니다. 119 | 120 | ```swift 121 | for thing in things { 122 | switch thing { 123 | case 0 as Int: 124 | print("zero as an Int") 125 | case 0 as Double: 126 | print("zero as a Double") 127 | case let someInt as Int: 128 | print("an integer value of \(someInt)") 129 | case let someDouble as Double where someDouble > 0: 130 | print("a positive double value of \(someDouble)") 131 | case is Double: 132 | print("some other double value that I don't want to print") 133 | case let someString as String: 134 | print("a string value of \"\(someString)\"") 135 | case let (x, y) as (Double, Double): 136 | print("an (x, y) point at \(x), \(y)") 137 | case let movie as Movie: 138 | print("a movie called \(movie.name), dir. \(movie.director)") 139 | case let stringConverter as (String) -> String: 140 | print(stringConverter("Michael")) 141 | default: 142 | print("something else") 143 | } 144 | } 145 | ``` 146 | 147 | `Int`, `Double`뿐만 아니라 튜플, 함수도 `Any`타입에 포함될 수 있다는 것을 확인할 수 있습니다. 148 | 149 | > NOTE 150 | > `Any` 타입은 옵셔널 타입을 포함합니다. 하지만 Swift에서는 `Any`타입을 사용해야 하는 곳에 옵셔널을 사용하면 경고를 발생 시킵니다. let optionalNumber: Int? = 3 things.append\(optionalNumber\) // 경고 things.append\(optionalNumber as Any\) // 경고 없음 151 | 152 | -------------------------------------------------------------------------------- /language-guide/19-nested-types.md: -------------------------------------------------------------------------------- 1 | # 중첩 타입 \(Nested Types\) 2 | 3 | 열거형은 특정 구조체나 클래스의 기능을 처리하기 위해 자주 사용됩니다. 이와 비슷하게 특정 문맥에서 좀 더 복잡한 타입을 위해 사용할 수 있는 유틸리티 클래스나 구조체를 정의할 수 있습니다. Swift에서는 이 기능을 위해 중첩 타입\(nested types\)을 지원합니다. 열거형, 클래스, 구조체를 그 타입 안에서 다시 정의할 수 있습니다. 4 | 5 | ## 중첩 타입의 사용 6 | 7 | 다음 코드는 블랙잭 게임에서 사용되는 카드를 모델링한 BlackjackCard라는 구조체를 정의한 \(예\)입니다. BlackjackCard 구조체는 Suit과 Rank라고 부르는 두개의 중첩 열거 타입을 포함 합니다. 8 | 9 | 블랙잭에서는 Ace 카드는 1이나 11로 사용될 수 있습니다. 이 기능은 Rank 열겨형의 중첩 타입인 구조체의 Values라고 하는 프로퍼티로 표현돼 있습니다. 10 | 11 | ```swift 12 | struct BlackjackCard { 13 | 14 | // nested Suit enumeration 15 | // struct 안에 enum이 들어갈 수 있습니다. 16 | enum Suit: Character { 17 | case spades = "♠", hearts = "♡", diamonds = "♢", clubs = "♣" 18 | } 19 | 20 | // nested Rank enumeration 21 | enum Rank: Int { 22 | case two = 2, three, four, five, six, seven, eight, nine, ten 23 | case jack, queen, king, ace 24 | struct Values { // enum안에 struct가 들어가는 것도 가능합니다. 25 | let first: Int, second: Int? 26 | } 27 | var values: Values { 28 | switch self { 29 | case .ace: 30 | return Values(first: 1, second: 11) 31 | case .jack, .queen, .king: 32 | return Values(first: 10, second: nil) 33 | default: 34 | return Values(first: self.rawValue, second: nil) 35 | } 36 | } 37 | } 38 | 39 | // BlackjackCard properties and methods 40 | let rank: Rank, suit: Suit 41 | var description: String { 42 | var output = "suit is \(suit.rawValue)," 43 | output += " value is \(rank.values.first)" 44 | if let second = rank.values.second { 45 | output += " or \(second)" 46 | } 47 | return output 48 | } 49 | } 50 | ``` 51 | 52 | Suit 열거 값은 카드에서 사용하는 4가지 모양을 표현 합니다. raw값은 그 모양의 기호를 나타냅니다. Rank 열거 값은 카드에서 사용 가능한 13가지 카드 등급을 표현 합니다. raw값은 그 등급의 값을 나타냅니다. \(이 raw Int값은 Jack, Queen, King 그리고 Ace 카드에서는 사용되지 않습니다.\) 53 | 54 | 위에서 언급했던 것 같이, Rank 값은 그 값 자체의 중첩 구조체인 Values라는 값을 정의합니다. 이 구조는 대부분의 카드가 하나의 값만 갖지만 Ace 카드는 두개를 갖는데 이것을 캡슐화 해줍니다. Values 구조체는 이것을 표현하기 위해 55 | 56 | * first : Int 타입 57 | * second : Int? \(옵셔널 Int\) 58 | 59 | 를 정의합니다. 60 | 61 | Rank는 또 계산된 프로퍼티\(computed property\)를 정의해 사용합니다. ace, jack, queen, king등 first와 second 값을 모두 갖는 카드는 그것에 맞게 값을 반환하고 나머지 보통 숫자에 대해서는 first값만 존재하고 second값은 nil인 값을 반환합니다. 62 | 63 | BlackjackCard는 rank와 suit 두개의 프로퍼티를 소유하고 description이라고 부르는 계산된 프로퍼티도 정의합니다. BlackjackCard는 커스텀 초기자\(custom initializer\)가 없는 구조체이기 때문에 암시적인 맴버쪽 초기자\(implicit member wise initializer\)를 갖습니다. 그래서 아래 코드와 같이 rank와 suit를 인자로 받아 BlackjackCard를 초기화 할 수 있습니다. 64 | 65 | ```swift 66 | let theAceOfSpades = BlackjackCard(rank: .ace, suit: .spades) 67 | print("theAceOfSpades: \(theAceOfSpades.description)") 68 | // Prints "theAceOfSpades: suit is ♠, value is 1 or 11" 69 | ``` 70 | 71 | .ace, .spades가 BlackjackCard안에 중첩 타입으로 선언돼 있기 때문에 타입 추론이 가능 하기 때문에 BlackjackCard 초기화시 타입형의 전체 명시 없이 .ace, .spades 로 사용할 수 있습니다. 72 | 73 | ## 중첩 타입의 언급 \(Referring to Nested Types\) 74 | 75 | 중첩 타입을 선언 밖에서 사용하려면 선언된 곳의 시작부터 끝까지 적어줘야 합니다. 76 | 77 | ```swift 78 | let heartsSymbol = BlackjackCard.Suit.hearts.rawValue 79 | // heartsSymbol is "♡" 80 | ``` 81 | 82 | -------------------------------------------------------------------------------- /language-guide/20-extensions.md: -------------------------------------------------------------------------------- 1 | # 익스텐션 \(Extensions\) 2 | 3 | 익스텐션을 이용해 클래스, 구조체, 열거형 혹은 프로토콜 타입에 기능을 추가할 수 있습니다. retroactive modeling으로 알려진 것과 같이 원본 코드를 몰라도 그 타입에 대한 기능을 확장할 수 있습니다. 익스텐션은 Objective-C 의 카테고리와 유사합니다. Swift에서 익스텐션을 이용해 다음을 할 수 있습니다. 4 | 5 | * 계산된 인스턴스 프로퍼티와 계산된 타입 프로퍼티의 추가 6 | * 인스턴스 메소드와 타입 메소드의 추가 7 | * 새로운 이니셜라이저 제공 8 | * 서브스크립트 정의 9 | * 중첩 타입의 선언과 사용 10 | * 특정 프로토콜을 따르는 타입 만들기 11 | 12 | > NOTE 13 | > 익스텐션은 타입에 새 기능을 추가할 수 있지만 오버라이드는\(override\)는 할 수 없습니다. 14 | 15 | ## 익스텐션 문법 \(Extension Syntax\) 16 | 17 | 익스텐션은 extension 키워드를 사용해 선언합니다. 18 | 19 | ```swift 20 | extension SomeType { 21 | // new functionality to add to SomeType goes here 22 | } 23 | ``` 24 | 25 | 하나의 익스텐션에서 현재 존재하는 타입에 한개 이상의 프로토콜을 따르도록 확장할 수 있습니다. 26 | 27 | ```swift 28 | extension SomeType: SomeProtocol, AnotherProtocol { 29 | // implementation of protocol requirements goes here 30 | } 31 | ``` 32 | 33 | 이런 방식으로 프로토콜을 구현하는 것은 Addind Protocol Conformance with an Extension에 설명돼 있습니다. 하나의 익스텐션은 Extending a Generic Type에 설명돼 있는 것처럼 generic 타입으로 확장하는데 사용할 수 있습니다. 또 generic 타입에 조건적으로 기능을 추가 할 수 있는 것은 Extensions with a Generic Where clause에 설명돼 있습니다. 34 | 35 | > NOTE 36 | > 익스텐션을 정의하여 존재하는 타입에 새 기능을 추가하면, 그 기능은 익스텐션을 정의하기 이전에 생성한 인스턴스를 포함한 존재하는 모든 해당 타입의 인스턴스에서 사용 가능합니다. 37 | 38 | ## 계산된 프로퍼티 \(Computed Properties\) 39 | 40 | 익스텐션을 이용해 존재하는 타입에 계산된 인스턴스 프로퍼티와 타입 프로퍼티를 추가할 수 있습니다. 다음 예제는 Swift의 built-in 타입인 Double에 5개의 계산된 인스턴스 프로퍼티를 추가하는 예제 입니다. 41 | 42 | ```swift 43 | extension Double { 44 | var km: Double { return self 1_000.0 } 45 | var m: Double { return self } 46 | var cm: Double { return self / 100.0 } 47 | var mm: Double { return self / 1_000.0 } 48 | var ft: Double { return self / 3.28084 } 49 | } 50 | let oneInch = 25.4.mm 51 | print("One inch is \(oneInch) meters") 52 | // Prints "One inch is 0.0254 meters" 53 | let threeFeet = 3.ft 54 | print("Three feet is \(threeFeet) meters") 55 | // Prints "Three feet is 0.914399970739201 meters" 56 | ``` 57 | 58 | 주어진 Double값에 km, m, cm 등 단위를 붙여 미터로 변경하는 계산된 프로퍼티 입니다. 단위 변환은 m\(미터\)를 기준으로 합니다. 이 프로퍼티들은 읽기 전용 계산된 프로퍼티이기 때문에 간결함을 위해 get을 붙이지 않았습니다. 59 | 60 | ```swift 61 | let aMarathon = 42.km + 195.m 62 | print("A marathon is \(aMarathon) meters long") 63 | // Prints "A marathon is 42195.0 meters long" 64 | ``` 65 | 66 | 단위 변환 값이 Double 타입이기 때문에 각각의 변환 값의 연산도 가능합니다. 67 | 68 | > NOTE 69 | > 익스텐션은 새 계산된 값을 추가할 수 있지만 새로운 저장된 프로퍼티나 프로퍼티 옵저버를 추가할 수는 없습니다. 70 | 71 | ## 이니셜라이저 \(Initializers\) 72 | 73 | 익스텐션을 이용해 존재하는 타입에 새로운 이니셜라이저를 추가할 수 있습니다. 이 방법으로 커스텀 타입의 이니셜라이저 파라미터를 넣을 수 있도록 변경하거나 원래 구현에서 포함하지 않는 초기화 정보를 추가할 수 있습니다. 74 | 75 | 익스텐션은 클래스에 새로운 편리한 이니셜라이저\(convenience initializer\)를 추가할 수는 있지만 지정된 이니셜라이저\(designated initializers\)나 디이니셜라이저\(deinitializers\)를 추가할 수는 없습니다. 지정된 이니셜라이저는 항상 반드시 오리지널 클래스의 구현에서 작성돼야합니다. 76 | 77 | 만약 익스텐션을 값타입에 이니셜라이저를 추가하는데 사용하고, 그 값타입이 모든 프로퍼티에 대해 기본 값을 제공하고 커스텀 이니셜라이저를 정의하지 않았다면, 익스텐션에서 기본 이니셜라이저와 멤버쪽 이니셜라이저를 익스텐션에서 호출할 수 있습니다. 만약 값 타입의 오리지널 구현의 부분으로 이니셜라이저 코드를 작성했다면 그것은 이 경우에 해당하지 않습니다. 78 | 79 | 만약 다른 모듈에 선언돼 있는 구조체에 이니셜라이저를 추가하는 익스텐션을 사용한다면 새로운 이니셜 라이져는 모듈에 정의된 이니셜라이저를 호출하기 전까지 self에 접근할 수 없습니다. 80 | 81 | ```swift 82 | struct Size { 83 | var width = 0.0, height = 0.0 84 | } 85 | struct Point { 86 | var x = 0.0, y = 0.0 87 | } 88 | struct Rect { 89 | var origin = Point() 90 | var size = Size() 91 | } 92 | ``` 93 | 94 | 위 예제에서는 Size와 Point구조체를 정의하고 그것을 사용하는 Rect 구조체를 정의했습니다. Rect 구조체에서 모든 프로퍼티의 기본 값을 제공하기 때문에 Rect구조체는 기본 이니셜라이저와 멤버쪽 이니셜라이저를 자동으로 제공 받아 사용할 수 있습니다. 95 | 96 | 기본적으로 제공되는 이니셜라이저를 사용해 초기화를 한 예제입니다. 97 | 98 | ```swift 99 | let defaultRect = Rect() 100 | let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0), 101 | size: Size(width: 5.0, height: 5.0)) 102 | ``` 103 | 104 | Rect 구조체를 추가적인 이니셜라이저를 제공하기 위해 확장 할 수 있습니다. 105 | 106 | ```swift 107 | extension Rect { 108 | init(center: Point, size: Size) { 109 | let originX = center.x - (size.width / 2) 110 | let originY = center.y - (size.height / 2) 111 | self.init(origin: Point(x: originX, y: originY), size: size) 112 | } 113 | } 114 | ``` 115 | 116 | Rect에서 확장한 이니셜라이저를 사용한 코드는 다음과 같이 사용할 수 있습니다. 117 | 118 | ```swift 119 | let centerRect = Rect(center: Point(x: 4.0, y: 4.0), 120 | size: Size(width: 3.0, height: 3.0)) 121 | // centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0) 122 | ``` 123 | 124 | > NOTE 125 | > 익스텐션에서 이니셜라이저를 제공할 때 각 인스턴스가 이니셜라이저가 한번 완료되면 완전히 초기화 되도록 확실히 해야 합니다. 126 | 127 | ## 메소드 \(Methods\) 128 | 129 | 익스텐션을 이용해 존재하는 타입에 인스턴스 메소드나 타입 메소드를 추가할 수 있습니다. 다음 예제는 Int 타입에 repetitions라는 인스턴스 메소드를 추가한 예제 입니다. 130 | 131 | ```swift 132 | extension Int { 133 | func repetitions(task: () -> Void) { 134 | for _ in 0.. Int { 181 | var decimalBase = 1 182 | for _ in 0.. 5가 나머지 188 | // 2인 경우 746381295 % 10 -> 9가 나머지 189 | } 190 | } 191 | 746381295[0] 192 | // returns 5 193 | 746381295[1] 194 | // returns 9 195 | 746381295[2] 196 | // returns 2 197 | 746381295[8] 198 | // returns 7 199 | ``` 200 | 201 | 만약 Int값에서 요청한 값이 처리할 수 있는 자릿 수를 넘어가면 서브스크립트 구현에서 0을 반환합니다. 202 | 203 | ```swift 204 | 746381295[9] 205 | // 9로 처리할 수 있는 자릿 수를 넘어가면 0을 반환 206 | 0746381295[9] 207 | ``` 208 | 209 | ## 중첩 타입 \(Nested Types\) 210 | 211 | 익스텐션을 이용해 존재하는 클래스, 구조체, 열거형에 중첩 타입을 추가할 수 있습니다. 212 | 213 | ```swift 214 | extension Int { 215 | enum Kind { 216 | case negative, zero, positive 217 | } 218 | var kind: Kind { 219 | switch self { 220 | case 0: 221 | return .zero 222 | case let x where x > 0: 223 | return .positive 224 | default: 225 | return .negative 226 | } 227 | } 228 | } 229 | ``` 230 | 231 | 위 예제는 Int에 중첩형 enum을 추가한 예제입니다. Kind라고 불리는 열거형은 Int를 음수, 0, 양수로 표현합니다. 232 | 233 | 아래 예제는 새로운 계산된 프로퍼티 kind를 이용해 특정 수가 음수, 0, 양수 중 어떤 것인지를 나타내는 예제입니다. 234 | 235 | ```swift 236 | func printIntegerKinds(_ numbers: [Int]) { 237 | for number in numbers { 238 | switch number.kind { 239 | case .negative: 240 | print("- ", terminator: "") 241 | case .zero: 242 | print("0 ", terminator: "") 243 | case .positive: 244 | print("+ ", terminator: "") 245 | } 246 | } 247 | print("") 248 | } 249 | printIntegerKinds([3, 19, -27, 0, -6, 0, 7]) 250 | // Prints "+ + - 0 - 0 + " 251 | ``` 252 | 253 | printIntegerKinds\(\_:\) 함수를 Int 배열을 입력으로 받아 각 Int가 음수, 0, 양수 어디에 속하는지 계산해서 그에 맞는 기호를 반환하는 함수 입니다. 254 | 255 | > NOTE 256 | > number.kind가 이미 switch에서 Int.Kind 타입이라는 것을 알고 있기 때문에 안의 case에서 kind의 축약형인 .negative, .zeo, .positive로 사용할 수 있습니다. 257 | 258 | -------------------------------------------------------------------------------- /language-guide/23-automatic-reference-counting.md: -------------------------------------------------------------------------------- 1 | # 자동 참조 카운트 \(Automatic Reference Counting\) 2 | 3 | Swift에서는 앱의 메모리 사용을 관리하기 위해 ARC\(Automatic Reference Counting\)을 사용합니다. 자동으로 참조 횟수를 관리하기 때문에 대부분의 경우에 개발자는 메모리 관리에 신경쓸 필요가 없고 ARC가 알아서 더이상 사용하지 않는 인스턴스를 메모리에서 해지합니다. 하지만 몇몇의 경우 ARC에서 메모리 관리를 위해 코드의 특정 부분에 대한 관계에 대한 정보를 필요로 합니다. 참조 횟수는 클래스 타입의 인스턴스에만 적용되고 값 타입인 구조체 열거형 등에는 적용되지 않습니다. 4 | 5 | ## ARC의 동작 \(How ARC Works\) 6 | 7 | 클래스의 새 인스턴스를 만들 때마자 ARC는 인스턴스 정보를 담는데 필요한 적정한 크기의 메모리를 할당합니다. 이 메모리는 그 인스턴스에 대한 정보와 관련된 저장 프로퍼티 값도 갖고 있습니다. 추가적으로 인스턴스가 더이상 사용되지 않을 때 ARC는 그 인스턴스가 차지하고 있는 메모리를 해지해서 다른 용도로 사용할 수 있도록 공간을 확보해 둡니다. 하지만 만약 ARC가 아직 사용중인 인스턴스를 메모리에서 내렸는데 인스턴스의 프로퍼티에 접근한다면 앱은 아마 크래시가 발생하게됩니다. ARC에서는 아직 사용중인 인스턴스를 해지하지 않기 위해 얼마나 많은 프로퍼티, 상수 혹은 변수가 그 인스턴스에 대한 참조를 갖고 있는지 추적합니다. 그래서 ARC는 최소 하나라도 그 인스턴스에 대한 참조가 있는 경우 그 인스턴스를 메모리에서 해지 하지 않습니다. 8 | 9 | ## ARC의 사용 \(ARC in Action\) 10 | 11 | ARC가 실제 어떻게 동작하는지 예제 코드를 통해 확인해 보도록 하겠습니다. 아래 코드는 하나의 클래스를 선언하고 클래스의 인스턴스가 생성될 때와 해지될때 print로 로그를 찍게 구현한 클래스 입니다. 12 | 13 | ```swift 14 | class Person { 15 | let name: String 16 | init(name: String) { 17 | self.name = name 18 | print("\(name) is being initialized") 19 | } 20 | deinit { 21 | print("\(name) is being deinitialized") 22 | } 23 | } 24 | ``` 25 | 26 | 위에서 선언한 `Person`클래스 타입을 갖는 reference 변수 3개를 선언합니다. 이 변수는 모두 옵셔널 변수입니다. 그래서 초기값으로 모두 `nil`을 갖고 있습니다. 27 | 28 | ```swift 29 | var reference1: Person? 30 | var reference2: Person? 31 | var reference3: Person? 32 | ``` 33 | 34 | 하나의 변수에 Person 인스턴스를 생성에 참조하도록 합니다. 35 | 36 | ```swift 37 | reference1 = Person(name: "John Appleseed") 38 | // Prints "John Appleseed is being initialized" 39 | ``` 40 | 41 | 나머지 두 변수를 첫 번째 변수를 참조하도록 합니다. 42 | 43 | ```swift 44 | reference2 = reference1 45 | reference3 = reference1 46 | ``` 47 | 48 | 이 경우 `reference2`, `reference3`모두 처음에 `reference1`이 참조하고 있는 같은 `Person`인스턴스를 참조하게 됩니다. 이 시점에 `Person`인스턴스에 대한 참조 횟수는 3이 됩니다. 그리고 나서 `reference1`, `reference2`두 변수의 참조를 해지합니다. 그렇게 되면 `Person` 인스턴스에 대한 참조 횟수는 아직 1이어서 `Person`인스턴스는 해지되지는 않습니다. 49 | 50 | ```swift 51 | reference1 = nil 52 | reference2 = nil 53 | ``` 54 | 55 | `Person`인스턴스를 참조하고있는 나머지 변수 `reference3`의 참조를 해지하면 더이상 `Person`인스턴스를 참조하고있는 것이 없으므로 ARC가 `Person`인스턴스를 메모리에서 해지하게 됩니다. 56 | 57 | ```swift 58 | reference3 = nil 59 | // Prints "John Appleseed is being deinitialized" 60 | ``` 61 | 62 | 위에 해지 로그가 찍히는 것으로 `Person`인스턴스가 메모리에서 내려 갔음을 확인할 수 있습니다. 63 | 64 | ## 클래스 인스턴스간 강한 참조 순환 \(Strong Reference Cycles Between Class Instances\) 65 | 66 | 앞선 예제에서 보았다시피 ARC에서 기본적으로 참조 횟수에 대해 추적하고 있기 때문에 더이상 사용하지 인스턴스는 자동으로 메모리에서 해제되게 됩니다. 하지만 절대로 메모리에서 해제 되지 않는 경우도 있습니다. 예를들어, 클래스의 인스턴스간 강하게 상호 참조를 하고 있는 경우가 바로 그경우 입니다. 이 경우는 강한 참조 순환이라 알려져 있습니다. 예를 통해 어떻게 강한 참조 순환이 발생하는지 알아 보겠습니다. 67 | 68 | ```swift 69 | class Person { 70 | let name: String 71 | init(name: String) { self.name = name } 72 | var apartment: Apartment? 73 | deinit { print("\(name) is being deinitialized") } 74 | } 75 | 76 | class Apartment { 77 | let unit: String 78 | init(unit: String) { self.unit = unit } 79 | var tenant: Person? 80 | deinit { print("Apartment \(unit) is being deinitialized") } 81 | } 82 | ``` 83 | 84 | 위 예제 코드를 보시면 `Person`이라는 클래스는 변수로 `Apartment`클래스의 인스턴스를 소유하고 있고 그 `Apartment`클래스에서는 변수로 `Person`형의 인스턴스를 소유하고 있습니다. 만약 다음과 같이 변수를 선언하고 인스턴스를 생성하면 어떤 일이 발생할까요? `Person`과 `Apartment`형의 변수를 각각 선언합니다. 85 | 86 | ```swift 87 | var john: Person? 88 | var unit4A: Apartment? 89 | ``` 90 | 91 | 선언한 변수에 각각에 맞는 타입의 인스턴스를 생성합니다. 지금 여기까지는 아무 문제가 없습니다. 92 | 93 | ```swift 94 | john = Person(name: "John Appleseed") 95 | unit4A = Apartment(unit: "4A") 96 | ``` 97 | 98 | 현재까지의 변수와 인스턴스 상태를 그림으로 보면 다음과 같습니다. 변수 john은 Person 인스턴스를 참조하고 있고 변수 unit4A는 Apartment 인스턴스를 참조하고 있습니다. 99 | 100 | ![](../.gitbook/assets/referencecycle01_2x.png) 101 | 102 | 이 상황에서 john의 apartment 변수에 unit4A를 unit4A.tenant \(세입자\)에 john을 할당해 보겠습니다. 103 | 104 | ```swift 105 | john!.apartment = unit4A 106 | unit4A!.tenant = john 107 | ``` 108 | 109 | 인스턴스 안의 apartment와 tenant가 각각 Apartment, Person 인스턴스를 참조하고 있는 상황이 됩니다 즉 Person 인스턴스의 참조 횟수는 2, Apartment의 인스턴스 참조 횟수도 마찬가지로 2가 됩니다. 110 | 111 | ![](../.gitbook/assets/referencecycle02_2x.png) 112 | 113 | 이 시점에서 각 변수에 nil을 할당해 참조를 해지해 보겠습니다. 원래 의도한 것은 각 변수가 참조하고 있던 Person과, Apartment인스턴스가 해지되는 것이었을 것입니다. 그러나 이 두 인스턴스는 해지되지 않습니다. 114 | 115 | ```swift 116 | john = nil 117 | unit4A = nil 118 | ``` 119 | 120 | 각 변수에 nil을 할당한 시점에서의 참조 상황은 다음 그림과 같습니다. 변수 john과 unit4A는 각 인스턴스에 대한 참조를 하고 있지 않지만 Person인스턴스와 Apartment인스턴스의 변수가 각각 상호 참조를 하고 있어 참조 횟수가 1이기 때문에 이 두 인스턴스는 해지되지 않고 메모리 누수가 발생합니다. 121 | 122 | ![](../.gitbook/assets/referencecycle03_2x.png) 123 | 124 | ## 클래스 인스턴스간 강한 참조 순환 문제의 해결 \(Resolving Strong Reference Cycles Between Class Instances\) 125 | 126 | 앞에서 살펴본 강한 참조 순환 문제를 해결하기 위해서는 두가지 방법이 있습니다. 하나는 weak 참조, 다른 하나는 unowned 참조를 사용하는 것입니다. weak 참조, unowned 참조 모두 ARC에서 참조 횟수를 증가시키지 않고 인스턴스를 참조합니다. 그래서 강한 참조 순환 문제를 해결할 수 있습니다. 약한 참조는 참조하고 있는 인스턴스가 먼저 메모리에서 해제될때 사용하고 미소유 참조는 반대로 참조하고 있는 인스턴스가 같은 시점 혹은 더 뒤에 해제될때 사용합니다. 위 Apartment 예제에서 Apartment의 tenant는 없는 상태가 될 수 있기 때문에 \(먼저 해지되기 때문에\) 약한 참조를 사용하는 것이 적절합니다. 127 | 128 | ### 약한 참조 \(Weak References\) 129 | 130 | 약한 참조로 선언하면 참조하고 있는 것이 먼저 메모리에서 해제되기 때문에 ARC는 약한 참조로 선언된 참조 대상이 해지 되면 런타임에 자동으로 참조하고 있는 변수에 nil을 할당합니다. 131 | 132 | > NOTE 133 | > ARC에서 약한 참조에 nil을 할당하면 프로퍼티 옵저버는 실행되지 않습니다. 134 | 135 | 예제를 살펴 보겠습니다. 아래 예제에서 Apartment의 tenant변수는 weak으로 선언됐습니다. 136 | 137 | ```swift 138 | class Person { 139 | let name: String 140 | init(name: String) { self.name = name } 141 | var apartment: Apartment? 142 | deinit { print("\(name) is being deinitialized") } 143 | } 144 | 145 | class Apartment { 146 | let unit: String 147 | init(unit: String) { self.unit = unit } 148 | weak var tenant: Person? 149 | deinit { print("Apartment \(unit) is being deinitialized") } 150 | } 151 | ``` 152 | 153 | 그리고 앞선 예제처럼 Person 인스턴스와 Apartment 인스턴스의 변수에서 각각 인스턴스를 상호 참조하도록 할당합니다. 154 | 155 | ```swift 156 | var john: Person? 157 | var unit4A: Apartment? 158 | 159 | john = Person(name: "John Appleseed") 160 | unit4A = Apartment(unit: "4A") 161 | 162 | john!.apartment = unit4A 163 | unit4A!.tenant = john 164 | ``` 165 | 166 | 그러면 아래와 같은 참조상황이 됩니다. 앞선 예제와 다른 점은 Apartment의 tenant변수가 Person 인스턴스를 약한 참조\(weak\)로 참조하고 있다는 것입니다. 그래서 이 시점에서 Person 인스턴스에 대한 참조 회수는 변수 john이 참조하고 있는 1회 뿐입니다. 167 | 168 | ![](../.gitbook/assets/weakreference01_2x.png) 169 | 170 | 그래서 john의 참조 대상을 nil로 할당하면 더 이상 Person 인스턴스를 참조하는 것이 없게 됩니다. 171 | 172 | ```swift 173 | john = nil 174 | // Prints "John Appleseed is being deinitialized" 175 | ``` 176 | 177 | 그결과 ARC에서 아래 그림과 같이 Person 인스턴스를 메모리에서 해지합니다. 178 | 179 | ![](../.gitbook/assets/weakreference02_2x.png) 180 | 181 | 이 시점에서 변수 unit4A에 nil을 할당하면 182 | 183 | ```swift 184 | unit4A = nil 185 | // Prints "Apartment 4A is being deinitialized" 186 | ``` 187 | 188 | Apartment 인스턴스를 참조하는 개체도 사라지게 되서 Apartment 인스턴스도 메모리에서 해지됩니다. 189 | 190 | ![](../.gitbook/assets/weakreference03_2x.png) 191 | 192 | > NOTE 193 | > 가비지 콜렉션을 사용하는 시스템에서 weak pointer를 단순한 시스템 캐싱 목적으로 사용하기도 합니다. 왜냐하면 메모리가 소모가 많아지면 가비지 콜렉터를 실행해서 강한 참조가 없는 객체를 메모리에서 해제하는 식으로 동작하기 때문입니다. 하지만 ARC는 이 경우와 다르게 참조 횟수가 0이 되는 즉시 해당 인스턴스를 제거하기 때문에 약한 참조를 이런 목적으로 사용할 수 없습니다. 194 | 195 | ### 미소유 참조 \(Unowned References\) 196 | 197 | 미소유 참조는 약한 참조와 다르게 참조 대상이 되는 인스턴스가 현재 참조하고 있는 것과 같은 생애주기\(lifetime\)를 갖거나 더 긴 생애 주기\(longer lifetime\)를 갖기 때문에 항상 참조에 그 값이 있다고 기대됩니다. 그래서 ARC는 미소유 참조에는 절대 nil을 할당하지 않습니다. 다시말하면 미소유 참조는 옵셔널 타입을 사용하지 않습니다. 198 | 199 | > IMPORTANT 200 | > 미소유 참조는 참조 대상 인스턴스가 항상 존재한다고 생각하기 때문에 만약 미소유 참조로 선언된 인스턴스가 해제됐는데 접근하게 되면 런타임 에러가 발생합니다. 201 | 202 | 예제를 통해 미소유 참조의 동작을 확인해 보도록 하겠습니다. 우선 Customer와 CreditCard 두개의 클래스를 선언합니다. 203 | 204 | ```swift 205 | class Customer { 206 | let name: String 207 | var card: CreditCard? 208 | init(name: String) { 209 | self.name = name 210 | } 211 | deinit { print("\(name) is being deinitialized") } 212 | } 213 | 214 | class CreditCard { 215 | let number: UInt64 216 | unowned let customer: Customer 217 | init(number: UInt64, customer: Customer) { 218 | self.number = number 219 | self.customer = customer 220 | } 221 | deinit { print("Card #\(number) is being deinitialized") } 222 | } 223 | ``` 224 | 225 | 여기서 Customer는 card 변수로 `CreditCard` 인스턴스를 참조하고 있고 CreditCard는 customer로 `Custome`인스턴스를 참조하고 있습니다. customer는 미소유 참조 unowned로 선언합니다. 이유는 고객과 신용카드를 비교해 봤을때 신용카드는 없더라도 사용자는 남아있을 것이기 때문입니다. 다시말하면 사용자는 항상 존재합니다. 그래서 CreditCard에 customer를 unowned로 선언합니다. 226 | 227 | > NOTE 228 | > CreditCard에서 카드 번호인 number는 충분히 긴 숫자를 저장할 수 있도록 하기 위해 Int가 아닌 UInt로 사용합니다. UInt64형은 32비트, 64비트 시스템 모두에서 16자리 숫자를 저장할 수 있습니다. 229 | 230 | 이제 고객 변수 john을 옵셔널 타입으로 선언합니다. 231 | 232 | ```swift 233 | var john: Customer? 234 | ``` 235 | 236 | 선언한 고객에 인스턴스를 생성하고 고객의 카드변수에도 카드 인스턴스를 생성해 할당합니다. 237 | 238 | ```swift 239 | john = Customer(name: "John Appleseed") 240 | john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!) 241 | ``` 242 | 243 | 이 시점에서의 참조 상황을 그림으로 표현하면 다음과 같습니다. John이 Customer 인스턴스를 참조하고 있고 CreditCard 인스턴스도 Customer Instance를 참조하고 있지만 미소유\(unowned\) 참조를 하고 있기 때문에 Customer 인스턴스에 대한 참조 횟수는 1회가 됩니다. 244 | 245 | ![](../.gitbook/assets/unownedreference01_2x.png) 246 | 247 | 이 상황에서 john 변수의 Customer 인스턴스 참조를 끊으면 다음 그림과 같이 됩니다. 248 | 249 | ![](../.gitbook/assets/unownedreference02_2x.png) 250 | 251 | 그러면 더이상 Customer 인스턴스를 강하게 참조하고 있는 인스턴스가 없으므로 Customer 인스턴가 해제되고 인스턴스가 해제됨에 따라 CreditCard 인스턴스를 참조하고 있는 개체도 사라지므로 CreditCard 인스턴스도 메모리에서 해제됩니다. 252 | 253 | ```swift 254 | john = nil 255 | // Prints "John Appleseed is being deinitialized" 256 | // Prints "Card #1234567890123456 is being deinitialized" 257 | ``` 258 | 259 | > NOTE 260 | > 위 예제는 안전하게 미소유 참조를 사용하는 방법의 예였습니다. 반면 Swift에서는 런타임에 안전성 확인을 하지 않고 사용하는 unsafe 미소유 참조도 제공합니다. 이것을 제공해 주는 이유는 성능 때문입니다. 261 | 262 | ### 미소유 참조와 암시적 옵셔널 프로퍼티 언래핑 \(Unowned References and Implicitly Unwrapped Optional Properties\) 263 | 264 | 약한 참조, 미소유 참조의 구분을 해당 참조가 nil이 될 수 있느냐 없느냐로 구분할 수 있습니다. 하지만 이 두경우를 제외한 제 3의 경우도 발생할 수 있습니다. 두 프로퍼티가 항상 값을 갖지만 한번 초기화 되면 절대 nil이 되지 않는 경우 입니다. 이 경우에는 미소유 프로퍼티를 암시적 옵셔널 프로퍼티 언래핑을 사용해 참조 문제를 해결할 수 있습니다. 예제를 보겠습니다. 265 | 266 | ```swift 267 | class Country { 268 | let name: String 269 | var capitalCity: City! 270 | init(name: String, capitalName: String) { 271 | self.name = name 272 | self.capitalCity = City(name: capitalName, country: self) 273 | } 274 | } 275 | 276 | class City { 277 | let name: String 278 | unowned let country: Country 279 | init(name: String, country: Country) { 280 | self.name = name 281 | self.country = country 282 | } 283 | } 284 | ``` 285 | 286 | Country의 capitalCity는 초기화 단계에서 City 클래스에 초기화 된 후 사용되게 됩니다. 즉 실제로 Country의 capitalCity는 옵셔널이 돼야 맞습니다. 하지만 여기서는 느낌표 연산자\(!\)를 이용해 명시적으로 강제 언래핑을 시켰습니다. 그래서 암시적 언래핑이 돼서 Country에서 name이 초기화 되는 시점에 self를 사용할 수 있게 됩니다. 그리고 City에서는 강한 참조 순환을 피하기 위해 미소유 참조로 country를 선언해서 두 인스턴스를 문제없이 사용할 수 있습니다. 287 | 288 | ```swift 289 | var country = Country(name: "Canada", capitalName: "Ottawa") 290 | print("\(country.name)'s capital city is called \(country.capitalCity.name)") 291 | // Prints "Canada's capital city is called Ottawa" 292 | ``` 293 | 294 | ## 클로저에서의 강한 참조 순환 \(Strong Reference Cycles for Closures\) 295 | 296 | 강한 참조 순환은 변수 뿐만아니라 클로저와 관계돼서 발생할수도 있습니다. 왜냐하면 클로저에서는 self를 캡쳐하기 때문입니다. 이 문제를 해결 하기 위해서는 클로저 캡쳐 리스트를 사용합니다. 예제를 보겠습니다. 아래 HTMLElement 클래스의 클로저 asHTML는 입력 값을 받지 않고 반환값이 String인 `() -> String` 클로저를 사용합니다. 그리고 이 클로저 안에서 self.text와 self.name과 같이 self를 캡쳐하게 됩니다. 297 | 298 | ```swift 299 | class HTMLElement { 300 | let name: String 301 | let text: String? 302 | lazy var asHTML: () -> String = { 303 | if let text = self.text { 304 | return "<\(self.name)>\(text)" 305 | } else { 306 | return "<\(self.name) />" 307 | } 308 | } 309 | init(name: String, text: String? = nil) { 310 | self.name = name 311 | self.text = text 312 | } 313 | deinit { 314 | print("\(name) is being deinitialized") 315 | } 316 | } 317 | ``` 318 | 319 | asHTML 클로저는 아래와 같이 다른 클로저로 변경 될 수도 있습니다. 320 | 321 | ```swift 322 | let heading = HTMLElement(name: "h1") 323 | let defaultText = "some default text" 324 | heading.asHTML = { 325 | return "<\(heading.name)>\(heading.text ?? defaultText)" 326 | } 327 | print(heading.asHTML()) 328 | // Prints "

some default text

" 329 | ``` 330 | 331 | > NOTE 332 | > asHTML 클로저는 지연 프로퍼티로 선언됐습니다. 왜냐하면 HTML를 렌더링 하기 위해 필요한 태그와 텍스트가 준비되고 나서야 그것의 HTML이 필요하기 때문입니다. 또 지연 프로퍼티이기 때문에 프로퍼티 안에서 self를 참조할 수 있습니다. 333 | 334 | 이 코드를 실행하면 결과는 다음과 같습니다. 335 | 336 | ```swift 337 | var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world") 338 | print(paragraph!.asHTML()) 339 | // Prints "

hello, world

" 340 | ``` 341 | 342 | > NOTE 343 | > 위에서 paragraph 변수는 HTMLElement?로 선언됐습니다. 그래서 변수에 nil을 할당할 수 있지만 아래 그림과 같이 강한 참조 순환에 빠지게 됩니다. 344 | 345 | 아래와 같이 인스턴스와 클로저 간에 강한 참조를 하게돼서 강한 순한 참조에 빠지게 됩니다. 346 | 347 | ![](../.gitbook/assets/closurereferencecycle01_2x.png) 348 | 349 | > NOTE 350 | > 클로저 안에서 self를 여러번 참조하더라도 실제로는 단 한번의 강한 참조만 캡쳐합니다. 351 | 352 | 아래와 같이 paragraph의 참조를 nil로 할당하더라도 HTMLElement인스턴스는 해제되지 않습니다. 353 | 354 | ```swift 355 | paragraph = nil 356 | ``` 357 | 358 | ## 클로저에서 강한 참조 순환 문제의 해결 \(Resolving Strong Reference Cycles for Closures\) 359 | 360 | 클로저에서 강한 참조 순환 문제의 해결하기 위해 캡쳐 참조에 강한 참조 대신 약한 참조\(weak\) 혹은 미소유\(unowend\) 참조를 지정할 수 있습니다. 약한 참조인지 미소유 참조를 사용할지는 코드에서 상호 관계에 달려있습니다. 361 | 362 | > NOTE 363 | > Swift에서는 클로저에서 특정 self의 메소드를 사용할 때 캡쳐를 실수하는 것을 막기위해 someProperty 혹은 someMethod 대신 self.someProperty 혹은 self.someMethod와 같이 self를 명시하는 것을 필요로 합니다. 364 | 365 | ### 캡쳐리스트 정의 \(Defining a Capture List\) 366 | 367 | 캡처리스트를 정의하기 위해서는 클로저의 파라미터 앞에 소괄호\(\[\]\)를 넣고 그 안에 각 갭쳐 대상에 대한 참조 타입을 적어 줍니다. 368 | 369 | ```swift 370 | lazy var someClosure: (Int, String) -> String = { 371 | [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in 372 | // closure body goes here 373 | } 374 | ``` 375 | 376 | 클로저의 파라미터가 없고 반환 값이 추론에 의해 생략 가능한 경우에는 캡처리스트 정의를 in앞에 적어 줍니다. 377 | 378 | ```swift 379 | lazy var someClosure: () -> String = { 380 | [unowned self, weak delegate = self.delegate!] in 381 | // closure body goes here 382 | } 383 | ``` 384 | 385 | ### 약한 참조와 미소유 참조 \(Weak and Unowned References\) 386 | 387 | 앞서 인스턴스 참조와 마찬가지로 참조가 먼저 해제되는 경우는 약한 참조를 같은 시점이나 나중 시점에 해제되는 경우에는 미소유 참조를 사용합니다 388 | 389 | > NOTE 390 | > 만약 캡쳐리스트가 절대 nil이 될 수 없다면 그것은 반드시 약한 참조 리스트가 아니라 미소유 참조 리스트로 캡쳐돼야 합니다. 391 | 392 | 자 이제 클로저에 적절한 캡쳐 리스트를 적어 코드를 실행해 보도록 하겠습니다. asHTML 클로저의 self에 \[unowned self\]라고 캡쳐리스트를 아래 코드와 같이 적어줍니다. 393 | 394 | ```swift 395 | class HTMLElement { 396 | let name: String 397 | let text: String? 398 | lazy var asHTML: () -> String = { 399 | [unowned self] in 400 | if let text = self.text { 401 | return "<\(self.name)>\(text)" 402 | } else { 403 | return "<\(self.name) />" 404 | } 405 | } 406 | init(name: String, text: String? = nil) { 407 | self.name = name 408 | self.text = text 409 | } 410 | deinit { 411 | print("\(name) is being deinitialized") 412 | } 413 | } 414 | ``` 415 | 416 | 앞서와 같이 인스턴스를 생성해 실행합니다. 417 | 418 | ```swift 419 | var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world") 420 | print(paragraph!.asHTML()) 421 | // Prints "

hello, world

" 422 | ``` 423 | 424 | 참조 상황을 그림으로 살펴보면 앞서와는 다르게 클로저에서 HTMLElement 인스턴스를 미소유 참조로 참조하고 있습니다. 425 | 426 | ![](../.gitbook/assets/closurereferencecycle02_2x.png) 427 | 428 | 그래서 paragraph의 참조를 제거 하면 HTMLElement 인스턴스가 바로 메모리에서 해제되는 것을 확인할 수 있습니다. 429 | 430 | ```swift 431 | paragraph = nil 432 | // Prints "p is being deinitialized" 433 | ``` 434 | 435 | -------------------------------------------------------------------------------- /language-guide/24-memory-safety.md: -------------------------------------------------------------------------------- 1 | # 메모리 안정성 \(Memory Safety\) 2 | 3 | 기본적으로 Swift는 코드가 비정상 적으로 동작하는 것을 막는 행위를 합니다. 예를 들면, 변수가 초기화 되기 전에 사용된다던가, 메모리에서 해제된 값을 접근하는 것을 막는 다던가, 인덱스의 한계를 넘는지 확인한다는 것 들이 그것 입니다. Swift에서는 또 메모리의 같은 영역을 동시에 접근해서 충돌이 나지 않도록 해줍니다. 이렇듯 Swift에서 메모리 관련해 자동으로 관리해 주기때문에 대부분의 경우에는 Swift언어를 사용하는 사용자는 메모리의 접근에 대해 전혀 생각하지 않고 사용해도 됩니다. 하지만 메모리 접근 충돌이 발생할 수 있는 잠재적인 상황을 이애하고 메모리 접근 충돌을 피하는 코드를 어떻게 작성할 수 있는지 이해하는 것은 중요합니다. 만약 메모리 접근 충돌이 일어나면 런타임 에러나, 컴파일 에러가 발생합니다. 4 | 5 | ## 메모리 접근 충돌의 이해 \(Understanding Conflicting Access to Memory\) 6 | 7 | 코드에서 메모리 접근은 아래 예와 같이 변수에 값을 할당하거나 접근할 때 발생합니다. 8 | 9 | ```swift 10 | // A write access to the memory where one is stored. 11 | var one = 1 12 | 13 | // A read access from the memory where one is stored. 14 | print("We're number \(one)!") 15 | ``` 16 | 17 | 위 코드는 one이라는 변수에 1을 할당하고 print하기 위해 one을 접근해 사용합니다. 메모리 충돌은 메모리에 값을 할당하고 메모리 값을 접근하는 것을 동시에 수행할때 발생합니다. 예를 들어보겠습니다. 만약 아래와 같이 특정 물건을 구매하고 구매 총 금액을 확인하는 경우 Before의 기본 상태에서 Total은 5$ 입니다. 만약 TV와 T-shirt 를 구매하는 동안\(During\) Total에 접근해 값을 가져 왔다면 Total은 5$가 됩니다. 하지만 실제 제대로 된 값을 After의 320$ 이어야 할 것 입니다. 18 | 19 | ![](../.gitbook/assets/memory_shopping_2x.png) 20 | 21 | > NOTE 22 | > 만약 동시성\(cuncurrent\) 코드나, 멀티쓰레드 코드를 작성한적이 있다면 이 메모리 접근 충돌 문제는 익숙한 문제일 것 입니다. 하지만 이 접근 충돌 문제는 싱글 쓰레드에서 발생할 수 있는 문제이고 동시성과 멀티쓰레드와 관련이 없습니다. 23 | 24 | ### Characteristics of Memory Access 25 | 26 | 메모리 접근이 충돌 할 수 있는 상황의 성격은 3가지 경우가 있습니다. 메모리를 읽거나 쓰는 경우, 접근 지속시간 그리고 메모리가 접근되는 위치입니다. 구체적으로 메모리 충돌은 다음 3가지 조건 중 2가지를 만족하면 발생합니다. 27 | 28 | * 최소 하나의 쓰기 접근 상황 29 | * 메모리의 같은 위치를 접근할 때 30 | * 접근 지속시간이 겹칠 때 31 | 32 | 읽기 접근과 쓰기 접근의 차이는 분명합니다. 쓰기 접근은 메모리의 위치를 변경하고 읽기는 그렇지 않습니다. 메모리의 위치는 무엇을 참조하고 있는지 나타냅니다. 33 | 34 | 메모리 접근의 지속시간은 즉각적인\(instantaneous\) 접근과 장기\(long-term\) 접근으로 구분할 수 있습니다. 35 | 36 | 즉각적인 접근은 코드에서 메모리 접근이 시작되고 끝나기 전에 그 메모리에 대한 다른 접근이 시작될 수 없을 때를 의미 합니다. 아래 예제는 즉각적인 접근의 예입니다. 37 | 38 | ```swift 39 | func oneMore(than number: Int) -> Int { 40 | return number + 1 41 | } 42 | 43 | var myNumber = 1 44 | myNumber = oneMore(than: myNumber) 45 | print(myNumber) 46 | // Prints "2" 47 | ``` 48 | 49 | 위 코드에서는 메모리 접근의 충돌이 발생하지 않습니다. 50 | 51 | ## Conflicting Access to In-Out Parameters 52 | 53 | 메모리 충돌은 in-out 파라미터를 잘못 사용할 때 발생할 수 있습니다. 아래 예제 코드를 보시겠습니다. 54 | 55 | ```swift 56 | var stepSize = 1 57 | 58 | func increment(_ number: inout Int) { 59 | number += stepSize 60 | } 61 | 62 | increment(&stepSize) 63 | // Error: conflicting accesses to stepSize 64 | ``` 65 | 66 | increment의 파라미터로 inout Int의 number를 사용합니다. 그리고 함수 내부에서 인자로 사용한 number를 변경합니다. 이 경우 인자로 number를 넣고, 또 number를 읽어 그 number에 stepSize를 추가해 다시 할당하는 쓰기와 읽기가 아래 그림과 같이 동시에 발생해 접근 충돌이 일어 납니다. 67 | 68 | ![](../.gitbook/assets/memory_increment_2x.png) 69 | 70 | 이 문제를 해결하기 위한 한가지 방법은 stepSize의 복사본을 명시적으로 사용하는 것입니다. 아래 코드와 같이 stepSize를 복사한 copyOfStepSize를 사용하면 하나의 메모리를 읽고 쓰는 행위를 동시에 하지 않게돼 접근 충돌을 피할 수 있습니다. 71 | 72 | ```swift 73 | // Make an explicit copy. 74 | var copyOfStepSize = stepSize 75 | increment(©OfStepSize) 76 | 77 | // Update the original. 78 | stepSize = copyOfStepSize 79 | // stepSize is now 2 80 | ``` 81 | 82 | 또 다른 in-out 파라미터의 장기 접근\(long-term\)으로 인한 충돌은 다음과 같은 상황에서 일어날 수 있습니다. balance라는 함수에 inout 파라미터 2개를 입력하는데 그 입력한 파라미터를 갖고 읽고 쓰는 것과 관련한 연산을 합니다. 83 | 84 | ```swift 85 | func balance(_ x: inout Int, _ y: inout Int) { 86 | let sum = x + y 87 | x = sum / 2 88 | y = sum - x 89 | } 90 | var playerOneScore = 42 91 | var playerTwoScore = 30 92 | balance(&playerOneScore, &playerTwoScore) // OK 93 | balance(&playerOneScore, &playerOneScore) 94 | // Error: conflicting accesses to playerOneScore 95 | ``` 96 | 97 | 이 경우 `balance(&playerOneScore, &playerOneScore)`와 같이 인자를 넣으면 읽기와 쓰기를 동시에 하게 돼서 접근 충돌이 발생합니다. 98 | 99 | > NOTE 100 | > 연산자도 함수이기 때문에 in-out파라미터 장기 접근 문제가 발생할 수 있습니다. 101 | 102 | ## Conflicting Access to self in Methods 103 | 104 | 메소드 안에서 self에 접근할때 충돌이 발생할 수도 있습니다. 예를 들기 위해 아래와 같이 Player구조체를 선언합니다. 105 | 106 | ```swift 107 | struct Player { 108 | var name: String 109 | var health: Int 110 | var energy: Int 111 | 112 | static let maxHealth = 10 113 | mutating func restoreHealth() { 114 | health = Player.maxHealth 115 | } 116 | } 117 | ``` 118 | 119 | 이 구조체를 확장해 Player간에 체력을 공유하는 `shareHealth`함수를 익스텐션에서 선언합니다. Player타입의 teammate는 inout파라미터로 지정하고 동작은 현재 Player와 입력한 teammate Player간에 balance함수를 실행합니다. 여기서 사용하는 인자는 in-out 파라미터입니다. 120 | 121 | ```swift 122 | extension Player { 123 | mutating func shareHealth(with teammate: inout Player) { 124 | balance(&teammate.health, &health) 125 | } 126 | } 127 | 128 | var oscar = Player(name: "Oscar", health: 10, energy: 10) 129 | var maria = Player(name: "Maria", health: 5, energy: 10) 130 | oscar.shareHealth(with: &maria) // OK 131 | ``` 132 | 133 | 이 경우는 oscar와 maria둘다 다른 구조체 인스턴스 이기 때문에 체력을 공유해도 아래와 같이 아무 문제가 없습니다. 134 | 135 | ![](../.gitbook/assets/memory_share_health_maria_2x.png) 136 | 137 | 하지만 만약 oscar를 자신과 같은 인스턴스인 oscar와 체력을 공유한다고 실행하면 어떻게 될까요? 138 | 139 | ```swift 140 | oscar.shareHealth(with: &oscar) 141 | // Error: conflicting accesses to oscar 142 | ``` 143 | 144 | 체력을 읽어오고 읽어온 체력을 변경하는 동작을 한 메모리 위치에서 동시에 수행하게 돼서 충돌이 발생합니다. 145 | 146 | ![](../.gitbook/assets/memory_share_health_oscar_2x.png) 147 | 148 | ## Conflicting Access to Properties 149 | 150 | 프로퍼티 접근시에도 충돌이 발생할 수 있습니다. 아래와 같은 playerInformation은 전역변수인데 이 전역변수를 가지고 balance를 수행하면 어떻게 될까요? 151 | 152 | ```swift 153 | var playerInformation = (health: 10, energy: 20) 154 | balance(&playerInformation.health, &playerInformation.energy) 155 | // Error: conflicting access to properties of playerInformation 156 | ``` 157 | 158 | 읽기 쓰기를 동시에 수행하는 balance를 실행할때 충돌 에러가 발생합니다. 아래와 같이 Player의 경우에도 전역변수로 선언돼 있어서 blance 수행시 충돌 에러가 발생합니다. 159 | 160 | ```swift 161 | var holly = Player(name: "Holly", health: 10, energy: 10) 162 | balance(&holly.health, &holly.energy) // Error 163 | ``` 164 | 165 | 그렇다면 지역변수는 어떨까요? 아래와 같이 지역변수에서 balance를 수행하는 경우 충돌 에러가 발생하지 않습니다. 166 | 167 | ```swift 168 | func someFunction() { 169 | var oscar = Player(name: "Oscar", health: 10, energy: 10) 170 | balance(&oscar.health, &oscar.energy) // OK 171 | } 172 | ``` 173 | 174 | 구조체에서 프로퍼티를 접근하는데 오버레핑 접근으로부터 안전한 상황은 다음과 같습니다. 175 | 176 | * 구조체 인스턴스에서 저장프로퍼티에만 접근하고 계산된 프로퍼티 혹은 클래스 프로퍼티를 접근하지 않을 때 177 | * 구조체가 전역변수가 아니라 지역변수 일때 178 | * 구조체가 어떤 클로저로부터도 캡쳐\(capturing\)하지 않거나 nonescaping 클로저에서만 획득된 경우 179 | 180 | 만약 컴파일러가 접근이 안전하다고 판단하지 못하면 접근이 불가능 합니다. 181 | 182 | -------------------------------------------------------------------------------- /language-guide/25-access-control.md: -------------------------------------------------------------------------------- 1 | # 접근제어 \(Access Control\) 2 | 3 | 접근제어는 특정 코드의 접근을 다른 소스파일이나 모듈에서 제한하는 것입니다. 이렇게 접근제어를 함으로써 특정 코드의 세부적인 구현을 감추고 딱 필요한 만큼 공개해 다른 곳에서 사용할 수 있도록 합니다. 접근제어는 클래스, 구조체, 열거형 등 개별 타입에도 적용할 수 있고 그 타입에 속한 프로퍼티, 메소드, 초기자, 서브스크립트에도 적용할 수 있습니다. 프로토콜은 그래서 전역상수, 변수, 함수같이 특정 문맥에 종속됩니다. Swift에서는 기본 접근레벨을 제공해 접근레벨의 처리를 쉽게할 수 있도록 돕습니다. 그래서 사실 단일타겟의 앱에서는 특별히 접근레벨을 전혀 명시하지 않아도 됩니다. 4 | 5 | > NOTE 6 | > 프로퍼티, 타입, 함수 등에 적용가능한 각 접근제어와 관련한 내용은 이후 섹션에서 자세하고 명확하게 기술해 두었습니다. 7 | 8 | ## 모듈과 소스파일 \(Modules and Source Files\) 9 | 10 | Swift의 접근제어는 모듈과 소스파일에 기반을 두고 있습니다. 모듈은 코드를 배포하는 단일 단위로 하나의 프레임워크나 앱이 이 단위로 배포되고 다른 모듈에서 Swift의 `import`키워드를 사용해 import될 수 있습니다. Xcode의 각 빌드 타겟은 Swift에서 분리된 단일 모듈로 취급됩니다. 소스파일은 모듈안에 있는 소스파일을 의미합니다. 각 소스파일에 여러 특정 타입을 선언해 사용할 수 있습니다. 11 | 12 | ## 접근레벨 \(Access Levels\) 13 | 14 | Swift에서는 5개의 접근레벨을 제공합니다. 15 | 16 | * `Open` & `Public` : Open과 Public 접근자 모두 선언한 모듈이 아닌 다른 모듈에서 사용가능합니다. 두 접근자의 차이점은 Open은 다른 모듈에서 오버라이드와 서브클래싱이 가능하지만 Public 접근자로 선언된 것은 다른 모듈에서는 오버라이드와 서브클래싱이 불가능 합니다. 17 | * `Internal` : 기본 접근레벨로 아무 접근레벨을 선언하지 않으면 `Internal`로 간주됩니다. `Internal`레벨로 선언되면 해당 모듈 전체에서 사용 가능합니다. 18 | * `File-private` : 특정 엔티티를 선언한 파일 안에서만 사용 가능합니다. 19 | * `Private` : 특정 엔티티가 선언된 괄호\({}\) 안에서만 사용 가능합니다. 20 | 21 | **접근레벨 가이드 원칙 \(Guiding Principle of Access Levels\)** 22 | 23 | Swift에서 접근 레벨은 더 낮은 레벨을 갖고 있는 다른 엔티티를 특정 엔티티에 선언해 사용할 수 없다는 일반 가이드 원칙을 따릅니다. 예를 들어, 24 | 25 | * public 변수는 다른 internal, file-private 혹은 private 타입에서 정의될 수 없습니다. 왜냐하면 그 타입은 public 변수가 사용되는 모든 곳에서 사용될 수 없을 것이기 때문입니다. 26 | * 함수는 그 함수의 파라미터 타입이나 리턴 값 타입보다 더 높은 접근 레벨을 갖을 수 없습니다. 왜냐하면 함수에는 접근 가능하지만 파라미터에 접근이 불가능 하거나 혹은 반환 값 타입보다 접근 레벨이 낮아 함수를 사용하는 관련 코드에서 이용할 수 없을 수 있기 때문입니다. 27 | 28 | **기본 접근레벨 \(Default Access Levels\)** 29 | 30 | 위에서 설명한 것과 같이 아무런 접근 레벨을 명시하지 않은 경우 `internal`을 갖게 됩니다. 31 | 32 | **단일 타겟 앱을 위한 접근레벨 \(Access Levels for Single-Target Apps\)** 33 | 34 | 단일 타겟 앱에서는 특별히 접근레벨을 명시할 필요가 없지만 필요에 따라 `file-private`, `private`등을 사용해 앱내에서 구현 세부사항을 숨길 수 있습니다. 35 | 36 | **프레임워크를 위한 접근레벨 \(Access Levels for Frameworks\)** 37 | 38 | 프레임워크를 개발한다면 `public`혹은 `open`으로 지정해서 다른 모듈에서 볼 수 있고 접근 가능하도록 만들어야 합니다. 39 | 40 | > NOTE 41 | > 만약 프레임워크의 구체적 구현을 감추고 싶은 부분이 있다면 그 부분은 `internal`로 선언하면 됩니다. 그리고 노출 시키고 싶은 API만 `public`혹은 `open`으로 지정합니다. 42 | 43 | ### 유닛테스트 타겟을 위한 접근레벨 \(Access Levels for Unit Test Targets\) 44 | 45 | 기본적으로 `open`이나 `public`으로 지정된 엔티티만 다른 모듈에서 접근 가능합니다. 하지만 유닛테스트를 하는 경우 모듈을 import할때 import앞에 `@testable`이라는 에트리뷰트를 붙여주면 해달 모듈을 테스트가 가능한 모듈로 컴파일해 사용합니다. 46 | 47 | ## 접근제어 문법 \(Access Control Syntax\) 48 | 49 | 각 접근자를 사용해 클래스와 변수, 상수를 선언한 예는 다음과 같습니다. 50 | 51 | ```swift 52 | public class SomePublicClass {} 53 | internal class SomeInternalClass {} 54 | fileprivate class SomeFilePrivateClass {} 55 | private class SomePrivateClass {} 56 | 57 | public var somePublicVariable = 0 58 | internal let someInternalConstant = 0 59 | fileprivate func someFilePrivateFunction() {} 60 | private func somePrivateFunction() {} 61 | ``` 62 | 63 | `internal`접근레벨은 생략할 수 있습니다. 64 | 65 | ```swift 66 | class SomeInternalClass {} // implicitly internal 67 | let someInternalConstant = 0 // implicitly internal 68 | ``` 69 | 70 | ## 커스텀 타입 \(Custom Types\) 71 | 72 | 커스텀 클래스에 특정 접근레벨을 지정할 수 있습니다. 그 레벨을 지정하면 그 클래스 안의 프로퍼티와 리턴 타입의 접근레벨은 클래스 레벨의 접근 권한만 사용할 수 있습니다. 예를 들어, 클래스를 `file-private` 클래스로 선언하면 그안의 프로퍼티와 함수 파라미터 그리고 반환 타입도 `file-private` 접근 권한을 갖습니다. 다시말해 특정 타입의 접근레벨의 지정은 그 타입의 멤버\(프로퍼티, 메소드 초기자 와 서브스크립트\)에 기본 접근레벨에 영향을 미칩니다. 73 | 74 | > IMPORTANT `public`타입은 기본 적으로 `internal` 멤버를 갖고 `public`을 갖지 않습니다. 이렇게 동작하는 이유는 public API를 만들시 노출되지 말아야 할 API가 실수로 노출되는 것을 막기 위함입니다. 그래서 노출시키고 싶은 멤버는 명시적으로 `public` 접근제어자를 붙여줘야 합니다. 75 | 76 | ```swift 77 | public class SomePublicClass { // explicitly public class 78 | public var somePublicProperty = 0 // explicitly public class member 79 | var someInternalProperty = 0 // implicitly internal class member 80 | fileprivate func someFilePrivateMethod() {} // explicitly file-private class member 81 | private func somePrivateMethod() {} // explicitly private class member 82 | } 83 | 84 | class SomeInternalClass { // implicitly internal class 85 | var someInternalProperty = 0 // implicitly internal class member 86 | fileprivate func someFilePrivateMethod() {} // explicitly file-private class member 87 | private func somePrivateMethod() {} // explicitly private class member 88 | } 89 | 90 | fileprivate class SomeFilePrivateClass { // explicitly file-private class 91 | func someFilePrivateMethod() {} // implicitly file-private class member 92 | private func somePrivateMethod() {} // explicitly private class member 93 | } 94 | 95 | private class SomePrivateClass { // explicitly private class 96 | func somePrivateMethod() {} // implicitly private class member 97 | } 98 | ``` 99 | 100 | ### 튜플타입 \(Tuple Types\) 101 | 102 | 튜플타입의 접근레벨은 튜플에서 사용되는 모든 타입의 접근레벨 중 가장 제한적인 접근레벨을 갖습니다. 예를 들어, 만약 하나는 `internal` 다른 하나는 `private`접근 권한을 갖는 2개의 타입으로 구성된 튜플은 더 낮은 레벨인 `private` 접근 레벨을 갖습니다. 103 | 104 | > NOTE 105 | > 튜플은 자체적으로 접근레벨을 선언하지 않고 사용하는 클래스, 구조체, 열거형 그리고 함수 등에 따라 자동으로 최소 접근레벨을 부여 받습니다. 즉, 튜플은 명시적으로 접근권한을 명시하지 않습니다. 106 | 107 | ### 함수타입 \(Function Types\) 108 | 109 | 함수 타입의 접근레벨은 함수의 파라미터 타입과 리턴타입의 접근레벨 중 최소의 접근레벨로 계산돼 사용됩니다. 그래서 그것에 맞는 접근 레벨을 함수 앞에 명시해 줘야 합니다. 다음 코드는 명시적인 접근 레벨을 명시하지 않은 함수인데, 컴파일시 에러가 발생합니다. 이유는 반환값 중에 접근레벨이 `private`인 `SomePrivateClass` 가 존재하기 때문에 `someFunction()`은 `internal`접근레벨로 선언될 수 없기 때문입니다. 110 | 111 | ```swift 112 | func someFunction() -> (SomeInternalClass, SomePrivateClass) { 113 | // function implementation goes here 114 | } 115 | ``` 116 | 117 | 이 함수를 적절히 선언하기 위해서는 아래와 같이 함수 앞에 `private`접근레벨을 지정해 줘야 합니다. 118 | 119 | ```swift 120 | private func someFunction() -> (SomeInternalClass, SomePrivateClass) { 121 | // function implementation goes here 122 | } 123 | ``` 124 | 125 | ### 열거형 타입 \(Enumeration Types\) 126 | 127 | 열거형에서 각 case는 enum의 접근레벨을 따르고 개별적으로 다른 접근레벨을 지정할 수 없습니다. 다음 코드에서 각 case는 enum의 접근레벨 `public`을 따라 모두 `public`접근레벨을 갖습니다. 128 | 129 | ```swift 130 | public enum CompassPoint { 131 | case north 132 | case south 133 | case east 134 | case west 135 | } 136 | ``` 137 | 138 | ## 고유값과 연관값\(Raw Values and Associated Values\) 139 | 140 | 고유값과 연관값을 사용하는 타입의 경우 반드시 그 타입보다 높은 접근 레벨을 가져야 합니다. 즉, `internal`접근 레벨을 갖고 있는 열거형 타입에서 `private`접근레벨을 갖는 고유값을 사용할 수 없습니다. 141 | 142 | ### 중첩타입 \(Nested Types\) 143 | 144 | `private`로 선언된 타입의 중첩 타입은 자동으로 `private`접근레벨을 갖습니다. `file-private`으로 선언된 경우 중첩타입은 `file-private`을 갖습니다. `public` 혹은 `internal`로 선언된 타입에서 중첩타입은 자동으로 `internal`접근레벨을 갖습니다. `public`으로 선언된 타입에서 `public`으로 선언된 중첩타입을 사용하고 싶으면 명시적으로 `public`접근자를 중첩타입에 적어줘야 합니다. 145 | 146 | ## 서브클래싱 \(Subclassing\) 147 | 148 | 서브클래스는 수퍼클래스보다 더 높은 접근레벨을 갖을 수 없습니다. 예를들어, 수퍼클래스가 `internal` 를 갖는데 그것을 서브클래싱해서 `pubic` 서브클래스를 만들 수 없습니다. 하지만 메소드는 서브클래스에서 더 높은 접은 레벨을 갖는 메소드로 오버라이드 할 수 있습니다. 아래 예제에서는 class B의 someMethod\(\)는 수퍼클래스의 `fileprivate`보다 더 높은 접근레벨인 `internal`을 갖도록 오버라이드 됐습니다. 149 | 150 | ```swift 151 | public class A { 152 | fileprivate func someMethod() {} 153 | } 154 | 155 | internal class B: A { 156 | override internal func someMethod() {} 157 | } 158 | ``` 159 | 160 | 이게 가능한 이유는 class A와 class B가 같은 소스파일에 선언돼 있어서 class B의 someMethod구현에서 `super.someMethod()`를 호출하는 것이 유효하기 때문입니다. 161 | 162 | ```swift 163 | public class A { 164 | fileprivate func someMethod() {} 165 | } 166 | 167 | internal class B: A { 168 | override internal func someMethod() { 169 | super.someMethod() 170 | } 171 | } 172 | ``` 173 | 174 | ## 상수, 변수, 프로퍼티 그리고 서브스크립트 \(Constants, Variables, Properties, and Subscripts\) 175 | 176 | 상수, 변수, 프로퍼티 등은 그 타입보다 더 높은 접근레벨을 갖을 수 없습니다. 즉, `private`타입에서 `public`프로퍼티를 선언할 수 없습니다. 아래와 같이 `private`클래스 변수의 접근레벨은 `private`이 돼야 합니다. 177 | 178 | ```swift 179 | private var privateInstance = SomePrivateClass() 180 | ``` 181 | 182 | ### 게터와 세터 \(Getters and Setters\) 183 | 184 | 상수, 변수, 프로퍼티 그리고 서브스크립트의 게터와 세터는 자동으로 해당 상수, 변수, 프로퍼티 그리고 서브스크립트가 갖는 접근레벨을 동일하게 갖습니다. 필요에 따라 세터의 접근레벨을 게터보다 낮게 정할 수 있습니다. 이를 위해 다음 키워드를 var혹은 subscript앞에 붙여 사용합니다. `fileprivate(set)`, `private(set)`, `internal(set)` 185 | 186 | > NOTE 187 | > 이 규칙은 저장프로퍼티 뿐아니라 계산된 프로퍼티에도 동일하게 적용됩니다. 아래 예제는 numberOfEdits변수를 `private(set)`로 설정해 TrackedString 내부에서만 변경되도록 지정한 코드입니다. TrackedString에 아무런 접근레벨이 지정되지 않았기 때문에 `internal`로 취급돼 numberOfEdits변수에 `private(set)`이 지정되지 않았다면 TrackedString 밖에서도 접근이 가능했을 것입니다. 188 | 189 | ```swift 190 | struct TrackedString { 191 | private(set) var numberOfEdits = 0 192 | var value: String = "" { 193 | didSet { 194 | numberOfEdits += 1 195 | } 196 | } 197 | } 198 | ``` 199 | 200 | 이 코드를 실행하면 다음과 같이 게터만을 이용해 변경된 숫자를 확인할 수 있습니다. 201 | 202 | ```swift 203 | var stringToEdit = TrackedString() 204 | stringToEdit.value = "This string will be tracked." 205 | stringToEdit.value += " This edit will increment numberOfEdits." 206 | stringToEdit.value += " So will this one." 207 | print("The number of edits is \(stringToEdit.numberOfEdits)") 208 | // Prints "The number of edits is 3" 209 | ``` 210 | 211 | 세터뿐 아니라 게터에도 접근레벨을 지정하고 싶다면 아래 코드와 같이 `public private(set) var numberOfEdits = 0` 게터 레벨도 지정할 수 있습니다. 212 | 213 | ```swift 214 | public struct TrackedString { 215 | public private(set) var numberOfEdits = 0 216 | public var value: String = "" { 217 | didSet { 218 | numberOfEdits += 1 219 | } 220 | } 221 | public init() {} 222 | } 223 | ``` 224 | 225 | ## 초기자 \(Initializers\) 226 | 227 | 초기자의 접근레벨은 타입의 레벨과 같거나 낮습니다. 한가지 예외상황은 지정초기자\(required Initializer\)인데, 이 지정초기자는 반드시 이 초기자가 속한 타입과 접근레벨이 같아야 합니다. 228 | 229 | ### 기본 초기자 \(Default Initializers\) 230 | 231 | 기본 초기자는 타입의 접근레벨이 `public`으로 지정돼있지 않은 이상 타입과 같은 접근레벨을 갖습니다. 만약 타입의 접근 레벨이 `public`으로 지정돼 있으면 기본 초기자는 `internal` 접근레벨을 갖습니다. 232 | 233 | ### 구조체 타입을 위한 기본 멤버쪽 초기자 \(Default Memberwise Initializers for Structure Types\) 234 | 235 | 만약 모든 저장 프로퍼티가 `private`로 지정된 경우 멤버쪽 초기자의 접근레벨은 `private`을 갖고 `file private`인 경우는 `file private`를 갖습니다. 그밖에는 `internal`접근레벨을 갖습니다. 만약 `public` 구조체 타입이 다른 모듈에서 사용될 때는 반드시 멤버쪽 초기자의 선언에 `public`으로 지정해야 다른 모듈에서 그 멤버쪽 초기자를 사용할 수 있습니다. 236 | 237 | ## 프로토콜 \(Protocols\) 238 | 239 | 프로토콜의 접근레벨과 그 안의 요구사항의 접근레벨은 항상 동일합니다. 240 | 241 | > NOTE 242 | > 프로토콜의 접근레벨과 그 안의 요구사항의 접근레벨은 항상 동일해야하는 것은 다른 타입과는 다르다. 다른 타입은 타입의 선언이 `public`이어도 안의 구현은 `internal`일 수 있습니다. 243 | 244 | ### 프로토콜 상속 \(Protocol Inheritance\) 245 | 246 | 이미 존재하는 프로토콜을 상속받아 새로운 프로토콜을 선언하는 경우 새 프로토콜은 상속을 한 프로토콜과 같은 접근레벨을 갖습니다. 247 | 248 | ### 프로토콜 순응 \(Protocol Conformance\) 249 | 250 | 특정 타입은 그 타입보다 낮은 접근레벨을 갖는 프로토콜을 따를 수 있습니다. 예를들어 `public`타입을 다른 모듈에서 사용하지만 `internal`프로토콜은 오직 `internal`프로토콜이 선언된 곳에서만 사용가능 합니다. 만약 타입이 `public`이고 프로토콜이 `internal`이라면 그 프로토콜을 따르는 타입도 `internal`입니다. 251 | 252 | > NOTE 253 | > Objective-C 와 같이 Swift에서도 프로토콜 순응은 전역적으로 같은 프로그램에서 다른 방법으로 프로토콜을 따르는 것은 불가능 합니다. 254 | 255 | ## 익스텐션 \(Extensions\) 256 | 257 | 클래스, 구조체, 열거형 등에 익스텐션에서 새로운 맴버를 추가하면 새로 추가된 것은 기존에 타입이 선언된 접근레벨과 같은 레벨을 갖습니다. 익스텐션에 명시적으로 접근레벨을 지정할수도 있습니다. 익스텐션을 프로토콜로 사용하는 경우 명시적인 접근레벨 변경이 불가능합니다. 대신 프로토콜의 접근레벨이 익스텐션의 프로토콜 구현에서 사용 됩니다. 258 | 259 | ### 프라이빗 멤버 \(Private Members in Extensions\) 260 | 261 | 익스텐션이 클래스, 구조체 혹은 열거형과 같은 파일에 선언된 경우 다음이 가능합니다. 262 | 263 | * 원본 선언에서 `private` 멤버로 선언한것을 익스텐션에서 멤버로 접근할 수 있습니다. 264 | * 하나의 익스텐션에서 `private`멤버로 선언한 것을 같은 파일의 다른 익스텐션에서 접근할 수 있습니다. 265 | * 하나의 익스텐션에서 `private`멤버로 선언한 것을 원본 선언에서 멤버로 접근할 수 있습니다. 266 | 267 | 예제 입니다. 268 | 269 | ```swift 270 | protocol SomeProtocol { 271 | func doSomething() 272 | } 273 | ``` 274 | 275 | 위와 같이 프로토콜을 선언한고 아래와 같이 구조체를 하나 선언하고 `private`변수를 하나 선언합니다. 276 | 277 | ```swift 278 | struct SomeStruct { 279 | private var privateVariable = 12 280 | } 281 | 282 | extension SomeStruct: SomeProtocol { 283 | func doSomething() { 284 | print(privateVariable) 285 | } 286 | } 287 | ``` 288 | 289 | 프로토콜을 따르는 익스텐션에서 그 타입의 `private`변수에 접근할 수 있습니다. 290 | 291 | ## 지네릭 \(Generics\) 292 | 293 | 지네릭 타입 혹은 함수는 지네릭 타입 혹은 함수 자체 그리고 타입 파라미터의 접근레벨의 최소 접근레벨을 갖습니다. 294 | 295 | ## 타입 별칭 \(Type Aliases\) 296 | 297 | 타입 별칭은 별칭을 붙인 타입보다 같거나 낮은 접근레벨을 갖습니다. 예를들어, `private`타입의 별칭은 `private`, `file-private`, `internal`, `public`, `open` 타입의 접근 권한을 갖을 수 있습니다. 298 | 299 | > NOTE 300 | > 이 규칙은 프로토콜의 순응을 충족시키는데 사용하는 연관 타입을 위한 별칭에도 동일하게 적용됩니다. 301 | 302 | -------------------------------------------------------------------------------- /language-guide/26-advanced-operators.md: -------------------------------------------------------------------------------- 1 | # 고급 연산자 \(Advanced Operators\) 2 | 3 | ## 비트 연산자 \(Bitwise Operators\) 4 | 5 | Swift에서도 여러 비트 연산자를 지원합니다. 6 | 7 | ### 비트 연산자 NOT \(Bitwise NOT Operator\) 8 | 9 | 비트 연산자 NOT은 비트를 뒤집습니다. 10 | 11 | ![](../.gitbook/assets/bitwisenot_2x.png) 12 | 13 | ```swift 14 | let initialBits: UInt8 = 0b00001111 15 | let invertedBits = ~initialBits // equals 11110000 16 | ``` 17 | 18 | ### 비트 연산자 AND \(Bitwise AND Operator\) 19 | 20 | 비트 연산자 AND는 두 비트가 같은 값인 경우 1 다른경우 0으로 변환합니다. 21 | 22 | ![](../.gitbook/assets/bitwiseand_2x.png) 23 | 24 | ```swift 25 | let firstSixBits: UInt8 = 0b11111100 26 | let lastSixBits: UInt8 = 0b00111111 27 | let middleFourBits = firstSixBits & lastSixBits // equals 00111100 28 | ``` 29 | 30 | ### 비트 연산자 OR \(Bitwise OR Operator\) 31 | 32 | 비트 연산자 OR는 두 비트중 하나라도 1이면 1 값을 갖습니다. 33 | 34 | ![](../.gitbook/assets/bitwiseor_2x.png) 35 | 36 | ```swift 37 | let someBits: UInt8 = 0b10110010 38 | let moreBits: UInt8 = 0b01011110 39 | let combinedbits = someBits | moreBits // equals 11111110 40 | ``` 41 | 42 | ### 비트 연산자 XOR \(Bitwise XOR Operator\) 43 | 44 | 비트 연산자 XOR은 두 비트가 다른 경우에 1, 같은 경우에 0을 갖습니다. 45 | 46 | ![](../.gitbook/assets/bitwisexor_2x.png) 47 | 48 | ```swift 49 | let firstBits: UInt8 = 0b00010100 50 | let otherBits: UInt8 = 0b00000101 51 | let outputBits = firstBits ^ otherBits // equals 00010001 52 | ``` 53 | 54 | ### 비트 시프트 연산자 \(Bitwise Left and Right Shift Operators\) 55 | 56 | 비트를 왼쪽 혹은 오른쪽으로 미는 시프트 연산자도 지원합니다. 57 | 58 | ## 부호없는 Integer의 시프팅 \(Shifting Behavior for Unsigned Integers\) 59 | 60 | ![](../.gitbook/assets/bitshiftunsigned_2x.png) 61 | 62 | ```swift 63 | let shiftBits: UInt8 = 4 // 00000100 in binary 64 | shiftBits << 1 // 00001000 65 | shiftBits << 2 // 00010000 66 | shiftBits << 5 // 10000000 67 | shiftBits << 6 // 00000000 68 | shiftBits >> 2 // 00000001 69 | ``` 70 | 71 | 아래 예제에서 pink와 16진수와 AND 연산을 하게 되는데 여기서 FF는 윈도우 역할을 해서 AND연산에서 FF의 위치에 있는 값만 취해 시프트 연산을 하게 됩니다. 72 | 73 | ```swift 74 | let pink: UInt32 = 0xCC6699 75 | let redComponent = (pink & 0xFF0000) >> 16 // redComponent is 0xCC, or 204 76 | let greenComponent = (pink & 0x00FF00) >> 8 // greenComponent is 0x66, or 102 77 | let blueComponent = pink & 0x0000FF // blueComponent is 0x99, or 153 78 | ``` 79 | 80 | ## 부호있는 Integer의 시프팅 \(Shifting Behavior for Signed Integers\) 81 | 82 | 부호가 있는 정수형에서는 최상단의 비트가 부호를 표현합니다. 0일때는 양수 1일때는 음수를 의미합니다. 연산의 결과는 각각 다음 그림과 같습니다. 83 | 84 | ![](../.gitbook/assets/bitshiftsignedfour_2x.png) 85 | 86 | ![](../.gitbook/assets/bitshiftsignedminusfour_2x.png) 87 | 88 | ![](../.gitbook/assets/bitshiftsignedminusfourvalue_2x.png) 89 | 90 | ![](../.gitbook/assets/bitshiftsignedaddition_2x.png) 91 | 92 | ![](../.gitbook/assets/bitshiftsigned_2x.png) 93 | 94 | ## 오버플로우 연산자 \(Overflow Operators\) 95 | 96 | Int16의 값은 범위는 -32768~32767 값을 갖습니다. Int16의 최대값인 Int16.max에 값을 하나 더하면 Int16이 저장할 수 있는 값의 범위를 벗어나므로 에러가 발생합니다. 97 | 98 | ```swift 99 | var potentialOverflow = Int16.max 100 | // potentialOverflow equals 32767, which is the maximum value an Int16 can hold 101 | potentialOverflow += 1 102 | // this causes an error 103 | ``` 104 | 105 | 만약 값이 넘치는 오버플로우가 발생하더라도 그 뒷 값을 자르고 연산을 수행하려면 다음과 같이 각 연산자 앞에 &를 붙여 수행하면 됩니다. 106 | 107 | * 오버플로우 덧셈 \(&+\) 108 | * 오버플로우 뺄셈 \(&-\) 109 | * 오버플로우 곱셈 \(&\*\) 110 | 111 | **값 오버플로우 \(Value Overflow\)** 112 | 113 | 값은 양수쪽 혹은 음수쪽 양쪽으로 넘칠 수 있습니다. 114 | 115 | ```swift 116 | var unsignedOverflow = UInt8.max 117 | // unsignedOverflow equals 255, which is the maximum value a UInt8 can hold 118 | unsignedOverflow = unsignedOverflow &+ 1 119 | // unsignedOverflow is now equal to 0 120 | ``` 121 | 122 | 여기서 오버플로우 덧셈을 하면 그결과는 다음과 같이 값이 초기화 됩니다. 123 | 124 | ![](../.gitbook/assets/overflowaddition_2x.png) 125 | 126 | * 만약 오버플로우 뺄셈을 하면 최대 값을 같습니다. 127 | 128 | ```swift 129 | var unsignedOverflow = UInt8.min 130 | // unsignedOverflow equals 0, which is the minimum value a UInt8 can hold 131 | unsignedOverflow = unsignedOverflow &- 1 132 | // unsignedOverflow is now equal to 255 133 | ``` 134 | 135 | ![](../.gitbook/assets/overflowunsignedsubtraction_2x.png) 136 | 137 | 만약 부호를 갖는 Int형의 경우 오버플로우 뺄셈 연산을 하면 결과는 다음과 같습니다. 138 | 139 | ```swift 140 | var signedOverflow = Int8.min 141 | // signedOverflow equals -128, which is the minimum value an Int8 can hold 142 | signedOverflow = signedOverflow &- 1 143 | // signedOverflow is now equal to 127 144 | ``` 145 | 146 | ![](../.gitbook/assets/overflowsignedsubtraction_2x.png) 147 | 148 | **연산자 수행의 우선순위 \(Precedence and Associativity\)** 149 | 150 | 연산자 별로 우선순위가 있어서 어떤 등식의 순서대로 연산이 수행되는 것이 아니라 연산자의 우선순위 별로 연산을 수행합니다. 아래와 같은 경우 결과 값은 17입니다. 151 | 152 | ```swift 153 | 2 + 3 % 4 * 5 154 | // this equals 17 155 | ``` 156 | 157 | 이유는 몫 연산자인 3%4가 우선 수행되고 그다음 곰셈 연산다 \* 5 그리고 마지막으로 덧셈 연산자가 수행돼 결과는 17이 됩니다. 158 | 159 | ```swift 160 | 2 + ((3 % 4) * 5) 161 | (3 % 4) is 3, so this is equivalent to: 162 | 2 + (3 * 5) 163 | (3 * 5) is 15, so this is equivalent to: 164 | 2 + 15 165 | ``` 166 | 167 | **연산자 메소드 \(Operator Methods\)** 168 | 169 | +,++같은 연산자는 메소드로 수행되고 이 메소드를 오버라이딩 할 수 있습니다. 아래 예제는 2D Vector 구조체를 더하는 + 메소드를 선언한 예입니다. 170 | 171 | ```swift 172 | struct Vector2D { 173 | var x = 0.0, y = 0.0 174 | } 175 | 176 | extension Vector2D { 177 | static func + (left: Vector2D, right: Vector2D) -> Vector2D { 178 | return Vector2D(x: left.x + right.x, y: left.y + right.y) 179 | } 180 | } 181 | ``` 182 | 183 | ```swift 184 | let vector = Vector2D(x: 3.0, y: 1.0) 185 | let anotherVector = Vector2D(x: 2.0, y: 4.0) 186 | let combinedVector = vector + anotherVector 187 | // combinedVector is a Vector2D instance with values of (5.0, 5.0) 188 | ``` 189 | 190 | vector + anotherVector를 수행하면 +메소드가 수행되 두 2차원 좌표의 x, y 값을 각각 더합니다. 191 | 192 | ![](../.gitbook/assets/vectoraddition_2x.png) 193 | 194 | ### 접두, 접미 연산자 \(Prefix and Postfix Operators\) 195 | 196 | 어떤 피 연산자 앞에 붙이는 접두 연산자를 정의 할 수 있습니다. 아래와 같이 -를 붙이면 이후 값을 음수로 변환하도록 함수를 정의합니다. 197 | 198 | ```swift 199 | extension Vector2D { 200 | static prefix func - (vector: Vector2D) -> Vector2D { 201 | return Vector2D(x: -vector.x, y: -vector.y) 202 | } 203 | } 204 | ``` 205 | 206 | 아래 코드를 수행하면 2차원 좌표가 음수로 변한 것을 확인할 수 있습니다. 207 | 208 | ```swift 209 | let positive = Vector2D(x: 3.0, y: 4.0) 210 | let negative = -positive 211 | // negative is a Vector2D instance with values of (-3.0, -4.0) 212 | let alsoPositive = -negative 213 | // alsoPositive is a Vector2D instance with values of (3.0, 4.0) 214 | ``` 215 | 216 | ### 할당 연산자의 합성 \(Compound Assignment Operators\) 217 | 218 | 할당 연산자를 오버라이딩 할 수 있습니다. 아래는 `+=`연산자를 재정의 한 예 입니다. 좌변은 in-out 파라미터로 좌변에 좌변과 우변을 더한 값을 할당합니다. 219 | 220 | ```swift 221 | extension Vector2D { 222 | static func += (left: inout Vector2D, right: Vector2D) { 223 | left = left + right 224 | } 225 | } 226 | ``` 227 | 228 | 위 할당 연산자를 실행하면 아래와 같이 원래 값에 더한 값이 추가된 것을 확인할 수 있습니다. 229 | 230 | ```swift 231 | var original = Vector2D(x: 1.0, y: 2.0) 232 | let vectorToAdd = Vector2D(x: 3.0, y: 4.0) 233 | original += vectorToAdd 234 | // original now has values of (4.0, 6.0) 235 | ``` 236 | 237 | > NOTE 238 | > 할당 연산자중 기본 할당연산자 `=` 는 오버로딩 할 수 없습니다. 삼중 조건 연산자인 `a ? b : c` 또한 오버로딩 할 수 없습니다. 239 | 240 | ### 등위 연산자 \(Equivalence Operators\) 241 | 242 | 두 값 혹은 객체를 비교하는 등위 연산자 `==`를 오버라이딩 할 수 있습니다. 다음은 2차원 좌표의 등위를 비교하는 `==` 연산자를 오버라이딩 한 예입니다. 243 | 244 | ```swift 245 | extension Vector2D: Equatable { 246 | static func == (left: Vector2D, right: Vector2D) -> Bool { 247 | return (left.x == right.x) && (left.y == right.y) 248 | } 249 | } 250 | ``` 251 | 252 | 두 좌표의 x, y가 각각 같은 경우에만 같다고 판별 합니다. 253 | 254 | ```swift 255 | let twoThree = Vector2D(x: 2.0, y: 3.0) 256 | let anotherTwoThree = Vector2D(x: 2.0, y: 3.0) 257 | if twoThree == anotherTwoThree { 258 | print("These two vectors are equivalent.") 259 | } 260 | // Prints "These two vectors are equivalent." 261 | ``` 262 | 263 | Swift에서는 등위 연산자의 구현을 자동으로 합성해 주기도 합니다. 다음의 경우 Swift에서 등위 연산에 대해 자동으로 제공해 줍니다. 264 | 265 | * 구조체가 저장 프로퍼티만 갖는 경우 Equatable 프로토콜을 따르게 됩니다. 266 | * 열거형이 연관 타입만 갖는 경우 Equatable 프로토콜을 따르게 됩니다. 267 | * 열거형이 연관 타입이 없는 경우 268 | 269 | Swift가 자동으로 제공하는 등위연산을 이용해 3차원 좌표의 등위도 아래와 같이 비교할 수 있습니다. 3차원 좌표 구조체는 저장 프로퍼티만 있기 때문에 Swift에서 자동으로 등위연산을 제공해 줍니다. 270 | 271 | ```swift 272 | struct Vector3D: Equatable { 273 | var x = 0.0, y = 0.0, z = 0.0 274 | } 275 | 276 | let twoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0) 277 | let anotherTwoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0) 278 | if twoThreeFour == anotherTwoThreeFour { 279 | print("These two vectors are also equivalent.") 280 | } 281 | // Prints "These two vectors are also equivalent." 282 | ``` 283 | 284 | ## 커스텀 연산자 \(Custom Operators\) 285 | 286 | 기본 연산자에는 커스텀 전위 연산자 +++를 정의해 사용할 수 있습니다. 287 | 288 | ```swift 289 | prefix operator +++ 290 | ``` 291 | 292 | 이 연산은 자신의 x, y값을 각각 2배하는 연산을 수행하도록 정의합니다. 293 | 294 | ```swift 295 | extension Vector2D { 296 | static prefix func +++ (vector: inout Vector2D) -> Vector2D { 297 | vector += vector 298 | return vector 299 | } 300 | } 301 | 302 | var toBeDoubled = Vector2D(x: 1.0, y: 4.0) 303 | let afterDoubling = +++toBeDoubled 304 | // toBeDoubled now has values of (2.0, 8.0) 305 | // afterDoubling also has values of (2.0, 8.0) 306 | ``` 307 | 308 | 정의 한대로 `+++`연산자가 동작하는 것을 확인할 수 있습니다. 309 | 310 | ### 중위 연산자의 수행 \(Precedence for Custom Infix Operators\) 311 | 312 | 다음과 같이 두 피연산자 사이에 존재하는 중위 연산자를 정의해 사용할 수 있습니다. x 값은 더하고 y 값은 서로 빼는 연산으로 정의한 예는 다음과 같습니다. 313 | 314 | ```swift 315 | infix operator +-: AdditionPrecedence 316 | extension Vector2D { 317 | static func +- (left: Vector2D, right: Vector2D) -> Vector2D { 318 | return Vector2D(x: left.x + right.x, y: left.y - right.y) 319 | } 320 | } 321 | let firstVector = Vector2D(x: 1.0, y: 2.0) 322 | let secondVector = Vector2D(x: 3.0, y: 4.0) 323 | let plusMinusVector = firstVector +- secondVector 324 | // plusMinusVector is a Vector2D instance with values of (4.0, -2.0) 325 | ``` 326 | 327 | `+-` 중위 연산이 정의한 대로 동작하는 것을 확인할 수 있습니다. 328 | 329 | -------------------------------------------------------------------------------- /revision-history-1/document-revision-history.md: -------------------------------------------------------------------------------- 1 | # Document Revision History 2 | 3 | **2019-08-11** 4 | 5 | * 오탈자 / 모호한 의미의 내용 수정 reported by 진형욱님 6 | 7 | **2018-08-21** 8 | 9 | * Swift 4.1기반의 한국어 번역 문서 첫 공개 10 | 11 | \*\*\*\* 12 | 13 | -------------------------------------------------------------------------------- /revision-history/contacts.md: -------------------------------------------------------------------------------- 1 | # 연락처 2 | 3 | 번역과 관련된 문의나 건의는 아래 연락처로 해주세요. 4 | 5 | Twitter : [@kyejusung](http://twitter.com/kyejusung) 6 | 7 | Email : kyejusung{at}gmail.com 8 | 9 | -------------------------------------------------------------------------------- /revision-history/contributors.md: -------------------------------------------------------------------------------- 1 | # 번역에 도움을 주신 분들 2 | 3 | 다음 분들이 Swift 공식 문서의 번역을 도와주셨습니다. 4 | 5 | * 진형국님 \(이메일 : hkjin81@gmail.com\) 6 | * 권영호님 \(트위터: [@y0ung1019](https://twitter.com/y0ung1019)\) 7 | 8 | --------------------------------------------------------------------------------