야곰의 스위프트 프로그래밍 - 스위프트 고급
스위프트 고급
타입 중첩
타입 내부에 타입을 정의하고 구현할 수 있으며, 이렇게 구현된 타입을 중첩 타입이라고 함
- 클래스나 구조체 내부에서 자신의 역할을 구분짓고 자신의 내부에서만 사용 가능하게 할 수 있음
- 득정 데이터 타입들을 하나의 클래스나 구조체에 구현하여 외부와의 혼선을 피할 수 있음
타입의 목적성을 명확히 하는 데 도움을 줄 수 있음
패턴
이러이러한 것을 표현하고 싶다면 이러이러한 패턴을 통해 표현하면 된다.
대부분의 패턴은 switch
, if
, guard
, for
등의 키워드가 두 개 이상 합을 이뤄 동작하며, switch
구문에서 강력한 힘을 발휘함
case
를 사용하는 패턴은 case
뒤에 패턴이 위치함
- 값을 해체(추출)하거나 무시하는 패턴
- 와일드카드 / 식별자 / 값 바인딩 / 튜플 패턴
- 패턴 매칭을 위한 패턴
- 열거형 케이스 / 옵셔널 / 표현 / 타입캐스팅 패턴
와일드카드 패턴
와일드카드 식별자 _
를 사용하여 이 자리에 무엇이 오든 상관 없음을 나타냄. 와일드카드 식별자가 위치한 곳의 값은 무시됨
for _ in 0...2 { |
식별자 패턴
변수 또는 상수의 이름에 알맞는 값을 어떤 값과 매치시키는 패턴
let value: Int = 42 |
value
의 타입인 Int
와 할당하려는 값인 42
의 타입이 매치된다면 value
는 42
라는 값의 식별자가 되므로 식별자 패턴이 성립함
42
라는 값을 value
라는 상수로 식별함
값 바인딩 패턴의 일종
값 바인딩 패턴
변수 또는 상수의 이름에 매치된 값을 바인딩
식별자 패턴은 매칭되는 값을 새로운 이름의 변수 또는 상수에 바인딩하는 것
- 튜플의 요소를 해체하여 그에 대응하는 식별자 패턴에 각각의 요소를 바인딩
let info = ("presto", 24, true) |
튜플 패턴
소괄호 내에 쉼표로 분리하는 리스트. 그에 상응하는 튜플 타입과 값을 매치함
let (x, y): (Int, Int) = (2, 3) |
열거형 케이스 패턴
값을 열거형 타입의 case
와 매치시킴
switch
구문의 case
레이블 / if
, while
, guard
, for-in
구문의 case
조건에서 볼 수 있음
연관 값이 있는 열거형 케이스와 매치하려고 한다면 열거형 케이스 패턴에는 반드시 튜플 패턴이 함께해야 함
let someValue = 30 |
옵셔널 패턴
옵셔널Optional 또는 암시적 추출 옵셔널ImplicitlyUnwrappedOptional 열거형에 감싸져 있는 값을 매치시킬 때 사용
식별자 패턴 뒤에 물음표를 넣어 표기하며 열거형 케이스 패턴과 동일한 위치에 자리함
- 옵셔널은 열거형으로 구현되어 있음
public enum Optional<Wrapped>: ExpressibleByNilLiteral { |
let optional: Int? = 100 |
타입캐스팅 패턴
타입캐스팅을 하거나 타입을 매치시킴
is
패턴은 is 타입_이름
과 같이 작성하여 값의 타입이 is
우측에 작성된 타입 또는 그 타입의 자식클래스 타입이면 값과 매치시킴
as
패턴은 패턴 as 타입_이름
과 같이 작성하여 값의 타입이 as
우측에 작성된 타입 또는 그 타입의 자식클래스 타입이면 값과 매치시키고, 매치된 값의 타입은 as
패턴이 원하는 타입으로 캐스팅됨
let value: Any = 100 |
표현 패턴
표현식의 값을 평가한 결과를 이용
switch
구문의 case
레이블에서만 사용 가능
Swift 표준 라이브러리의 패턴 연산자인 ~=
연산자의 반환값이 true
이면 매치시킴
- 같은 타입의 두 값을 비교할 때
~=
연산자는==
연산자를 사용함
연산자를 중복 정의하거나 재정의, 또는 자신이 만든 타입에 ~=
연산자를 구현해주어 자신이 원하는대로 패턴을 완성시킬 수 있음
~=
연산자 재정의 또는 중복 정의하기
첫 번째 인자에
case
레이블에 작성될 패턴이 위치함두 번째 인자에
switch
문의 조건에 작성될 값이 위치함func ~=(pattern: String, value: Person) -> Bool { return pattern == value.name } <!--8-->
프로토콜 익스텐션에 where
절을 사용하여 해당 익스텐션이 특정 프로토콜을 준수하는 타입에만 적용될 수 있도록 제약을 줄 수 있음
// SelfPrintable 프로토콜을 준수하는 타입 중 Container 프로토콜도 준수하는 타입에만 해당 익스텐션이 적용 extension SelfPrintable where Self: Container
- 여러 프로토콜을 제시하려면 쉼표로 구분해줌
타입 매개변수와 연관 타입의 제약을 추가하는 데 `where`절을 사용할 수 있음
```swift
func doubled<T>(value: T) -> T where T: BinaryInteger {
return value * 2
}
// 위의 표현과 아래의 표현 같음
func doubled<T: BinaryInteger>(value: T) -> T {
return value * 2
}
func compareTwoSequences<S1, S2>(a: S1, b: S2) where S1: Sequence, S2: Sequence, S1.SubSequence: Equatable, S1.SubSequence == S2.SubSequence { ... } |
protocol Container { |
where
절을 다른 패턴과 조합하여 원하는 추가 요구사항을 자유롭게 더할 수 있음. 익스텐션과 제네릭에 사용하여 프로토콜 또는 타입에 대한 제약을 추가할 수 있음.
조건문이나 논리 연산으로 구현한 코드보다 훨씬 더 명확하고 간편하게 사용할 수 있다.
ARC
Automatic Reference Counting
Swift는 프로그램의 메모리 사용을 관리하기 위해 ARC라는 메모리 관리 기법을 사용함
참조 타입인 클래스의 인스턴스에만 적용되는 개념
ARC란
GC와의 차이
- 컴파일 시 참조 카운팅
- 컴파일 시 인스턴스의 해제 시점이 결정되어 인스턴스가 언제 메모리에서 해제될지 예측할 수 있음
- 메모리 관리를 위한 추가적인 시스템 자원이 불필요함
ARC의 작동 규칙을 모르고 사용하면 인스턴스가 메모리에서 영원히 해제되지 않을 가능성이 있음.
- 클래스의 인스턴스를 생성할 때마다 ARC는 그 인스턴스에 대한 정보를 저장하기 위한 메모리 공간을 따로 또 할당함
- 그 공간에는 인스턴스의 타입 정보와 함께 그 인스턴스와 관련된 저장 프로퍼티의 값 등을 저장함
- 인스턴스가 더이상 필요 없는 상태가 되면 인스턴스가 차지하던 메모리 공간을 다른 용도로 활용할 수 있도록 ARC가 메모리에서 인스턴스를 없앰
- 인스턴스가 지속해서 필요한 상황에서 ARC는 인스턴스가 메모리에서 해제되지 않도록 인스턴스 참조 여부를 계속 추적함
- 다른 인스턴스의 프로퍼티나 변수, 상수 등이 어느 한 곳에서 인스턴스를 참조한다면 ARC가 해당 인스턴스를 해제하지 않고 유지해야 하는 명분이 됨
강한참조
인스턴스가 계속해서 메모리에 남아있어야 하는 명분을 만들어 주는 것
인스턴스는 참조 횟수가 0이 되는 순간 메모리에서 해제됨
강한참조를 사용하여 인스턴스를 다른 인스턴스의 프로퍼티나 변수, 상수 등에 할당하면 참조 횟수가 1 증가함
강한참조를 사용하는 프로퍼티, 변수, 상수 등에 nil을 할당해주면 원래 자신에게 할당되어 있던 인스턴스의 참조 횟수가 1 감소함
기본적으로 강한참조함
강한참조 지역변수(상수)가 사용된 범위의 코드 실행이 종료되면 그 지역변수(상수)가 참조하던 인스턴스의 참조 횟수가 1 감소함
강한참조 순환 문제
인스턴스가 서로가 서로를 강한참조함. 변수가 참조하던 클래스의 인스턴스에 접근할 방법이 없으나 인스턴스는 해제되지 않은 채 메모리에 존재하여 메모리 누수가 발생함
약한참조
자신이 참조하는 인스턴스의 참조 횟수를 증가시키지 않음
weak
키워드를 참조 타입의 프로퍼티나 변수의 선언 앞에 명시하면 그 프로퍼티나 변수는 자신이 참조하는 인스턴스를 약한참조함
자신이 참조하던 인스턴스가 메모리에서 해제된다면 nil
이 할당될 수 있으므로 옵셔널 변수만 약한참조를 할 수 있음
미소유참조
약한참조와 마찬가지로 인스턴스의 참조 횟수를 증가시키지 않음
unowned
키워드를 참조 타입의 프로퍼티나 변수의 선언 앞에 명시하면 그 프로퍼티나 변수는 자신이 참조하는 인스턴스를 미소유참조함
자신이 참조하는 인스턴스가 항상 메모리에 존재할 것이라는 전제를 기반으로 동작하며, 자신이 참조하는 인스턴스가 메모리에서 해제되더라도 nil
을 할당해주지 않음. 그러므로 옵셔널 변수가 아니어도 미소유참조를 사용할 수 있음
미소유참조를 하면서 메모리에서 해제된 인스턴스에 접근하려 할 때 런타임 에러가 발생함
클로저의 강한참조 순환
클로저가 인스턴스의 프로퍼티일 때나, 클로저의 값 획득 특성 때문에 발생함
클로저를 클래스의 인스턴스 프로퍼티로 할당하면 클로저의 참조가 할당되고, 이 때 참조 타입과 참조 타입이 서로 강한참조를 하기 때문에 강한참조 순환 문제가 발생함
- 클로저, 클래스는 참조 타입
클로저의 획득 목록을 통해 해결 가능
클로저는 자신이 호출되면 언제든지 자신 내부의 참조들을 사용할 수 있도록 참조 횟수를 증가시켜 메모리에서 해제되는 것을 방지함. 이 때 자신을 프로퍼티로 갖는 인스턴스의 참조 횟수도 증가시킴. 이렇게 강한참조 순환이 발생함
획득목록
클로저 내부에서 참조 타입을 획득하는 규칙을 제시해줄 수 있는 기능
때에 따라서, 혹은 각 관계에 따라서 참조 방식을 클로저에 제안할 수 있음
클로저 내부의 매개변수 목록 이전 위치에, 참조 방식과 참조할 대상을 대괄호로 둘러싼 목록 형식으로 작성. 획득목록 뒤에는 in
키워드를 명시
획득목록에 명시한 요소가 참조 타입이 아니라면 해당 요소들은 클로저가 생성될 때 초기화됨
var a = 0 |
획득목록에 명시한 요소가 참조 타입이라면 일반적으로 기대하는 것처럼 동작함. 참조 타입이기 때문.
하지만 참조 방식 (강한획득 / 약한획득 / 미소유획득)을 명시할 수 있음
class Class { |
일반적으로 약한참조를 사용함. 미소유참조는 강제 옵셔널 래핑같은 느낌
이처럼 클로저의 획득 특성 때문에 클로저가 프로퍼티로 사용될 경우 발생할 수 있는 강한참조 문제는 클로저의 획득목록을 통해 해결할 수 있음
오류처리
오류처리란
프로그램이 오류를 일으켰을 때 이것을 감지하고 회복시키는 일련의 과정
오류의 표현
Error
프로토콜을 준수하는 타입의 값을 통해 표현됨
오류를 표현하기 위한 타입 (주로 열거형)은 이 프로토콜을 채택함
- 연관 값을 통해 오류에 관한 부가 정보를 제공할 수 있음
미리 정의한 오류가 발생하는 경우에 throw
구문을 사용
오류 포착 및 처리
오류를 던짐 / 던져진 오류를 처리하기 위한 코드 작성 필요
Swift에서 오류를 처리하기 위한 방법들
- 함수에서 발생한 오류를 해당 함수를 호출한 코드에 알리기
do-catch
구문을 이용하여 오류 처리하기- 옵셔널 값으로 오류 처리하기
- 오류가 발생하지 않을 것이라고 확신하기
함수에서 발생한 오류 알리기
함수에서 발생한 오류를 해당 함수를 호출한 코드에 알리기
try
키워드로 던져진 오류를 받을 수 있음 (try
/ try?
/ try!
)
함수, 메소드, 이니셜라이저의 매개변수 목록 뒤에 throws
키워드를 사용하여 오류를 던질 수 있음. 이러한 함수는 호출했을 때 동작 도중 오류가 발생하면 자신을 호출한 코드에 오류를 던져서 오류를 알릴 수 있음
func throwError() throws -> String { ... } |
throws
함수나 메소드는 같은 이름의throws
가 명시되지 않은 함수나 메소드와 구분됨throws
를 포함한 함수, 메소드, 이니셜라이저는 일반 함수, 메소드, 이니셜라이저로 재정의할 수 없음- 일반 함수, 메소드, 이니셜라이저는
throws
를 포험한 함수, 메소드, 이니셜라이저로 재정의 가능
- 일반 함수, 메소드, 이니셜라이저는
throws
함수를 호출하는 함수도 throws
를 명시해 주어야 함
try
키워드 이후 오류를 던질 수 있는 함수, 메소드 또는 이니셜라이저의 호출 구문을 작성
오류를 받은 코드가 적절히 오류를 처리해주지 않는다면 오류 발생 이후 코드는 작동되지 않음
do-catch 구문을 이용하여 오류처리
do { |
do
절 내부의 코드에서 오류를 던지면 catch
절에서 오류를 받아 적절하게 처리함
catch
뒤에 오류의 종류를 명시하지 않고 코드 블록을 생성하면 블록 내부에 암시적으로 error
라는 이름의 지역 상수가 오류의 내용으로 들어옴
옵셔널 값으로 오류처리
try?
표현을 통해 동작하더 코드가 오류를 던지면 그 코드의 반환값은 nil
이 됨
이를 옵셔널 바인딩과 결합하여 오류를 처리할 수도 있음
오류가 발생하지 않을 것이라고 확신하는 방법
try!
표현을 사용. 코드가 오류를 던지면 런타임 에러 발생
다시던지기
rethrows
키워드를 사용하여 함수나 메소드는 자신의 매개변수로 전달받은 함수가 오류를 던진다는 것을 나타낼 수 있음
최소 하나 이상의 오류 발생 가능한 함수를 매개변수로 전달받아야 함
enum SomeError: Error { |
다시던지기 함수 또는 메소드는 기본적으로 스스로 오류를 던지지 못함
- 자신 내부에 직접적으로
throw
구문을 사용할 수 없음 catch
절 내부에서throw
구문을 작성할 수 있음- 다시던지기 함수의 매개변수로 전달받은 오류던지기 함수만 호출하고 결과로 던져진 오류만 제어 가능
부모클래스의 다시던지기 메소드는 자식클래스에서 던지기 메소드로 재정의할 수 없음
부모클래스의 던지기 메소드는 자식클래스에서 다시던지기 메소드로 재정의할 수 있음
프로토콜 요구사항 중 다시던지기 메소드가 있다면, 던지기 메소드를 구현하는 것으로 요구사항을 충족시킬 수 없음
프로토콜 요구사항 중 던지기 메소드가 있다면, 다시던지기 메소드를 구현하는 것으로 요구사항을 충족시킬 수 있음
후처리 defer
defer
구문을 사용하여 현재 코드 블록을 나가기 전에 꼭 실행해야 하는 코드를 작성할 수 있음
- 어떠한 방식으로 코드 블록을 빠져나가든지
defer
구문은 무조건 실행됨
오류처리 상황 뿐만 아니라 모든 경우의 코드 블록 어디에서든 사용 가능
파일 입출력시 defer
구문에 파일을 닫는 코드를 작성해 두면 코드의 양을 줄이고 프로그래머의 실수를 방지할 수 있음
defer
구문 내부에 break
, return
같은 구문을 빠져나갈 수 있는 코드 또는 오류를 던지는 코드를 작성할 수 없음
여러 개의 defer
구문이 하나의 블록 내부에 속해 있다면 맨 마지막에 작성된 구문부터 역순으로 실행됨
func function() { |