Swift

1. 개요

애플의 프로그래머들이 안전, 성능 및 소프트웨어 디자인 패턴에 대한 현대적인 접근 방식을 사용하여 만든 범용 프로그래밍 언어

2. 역사

2010년 Chris Lattener 를 필두로 애플의 프로그래머들이 개발 시작.

Objective-C, Ruby, Python, C#, Rust, Haskell, CLU 등 많은 프로그래밍 언어의 콘셉트를 참고하여 만들었다.

  • 2014년 6월 스위프트의 존재 발표 및 베타 버전 배포
  • 2014년 9월 정식 발표
  • 2014년 10월 1.1 버전 발표
  • 2015년 4월 1.2 버전 발표 (1.1에 비해 문법 변화가 많음)
  • 2015년 6월 2.0 버전 발표
  • 2015년 10월 2.1버전 발표
  • 2015년 12월 오픈소스로 전환
  • 2016년 3월 2.2 버전 발표
  • 2016년 6월 3.0 버전 발표
  • 2017년 3월 3.1 버전 발표
  • 2017년 6월 4.0 버전 발표

3. 언어적 특성

3.1 특징 변화

최초 스위프트 발표시에는 언어 특징을 Safe, Modern, Powerful이라고 표현 했었으나,

오픈 소스로 전환되면서 특징을 Safe, Fast, Expressive로 변경

3.2 안전성(Safe)

안전한 프로그래밍 지향. 옵셔널이라는 기능을 비롯하여 guard 구문, 오류처리, 강력한 타입 통제 등을 통해 안전한 프로그래밍 구현

3.3 신속성(Fast)

스위프트는 C언어를 기반으로한 C, C++, Objective-C 와 같은 프로그래밍 언어를 대체하려는 목적으로 만들어짐.

C언어 수준과 동등한 성능을 일정한 수준으로 유지하는데 초점을 맞춰 개발되었음.

3.4 더 나은 표현성(Expressive)

사용하기 편하고 보기 좋은 문법을 구현

3.5 다중 패러타임 프로그래밍 언어

명령형과 객체지향 프로그래밍 패러다임을 기반으로 한 함수형 프로그래밍 패러다임과 프로토콜 지향 프로그래밍 패러다임을 지향

4. 데이터 타입 기본

4.1 Int와 UInt

Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64

4.2 Bool
4.3 Float 와 Double
4.4 Character

유니코드에서 지원하는 모든 언어 및 특수기호 사용 가능

4.5

유니코드에서 지원하는 모든 언어 및 특수기호 사용 가능

4.6 Any, AnyObject와 nil

Any는 스위프트의 모든 데이터 타입을 사용할 수 있다는 뜻.

AnyObject는 Any보다 조금 한정된 의미로 클래스의 인스턴스만 할당 가능.

nil은 특정 타입이 아니라 ‘없음’을 나타내는 키워드.

5. 데이터 타입 고급

5.1 타입 추론

스위프트에서는 변수나 상수 선언시 특정 타입을 명시하지 않아도 컴파일러가 변수나 상수 타입을 결정.

5.2 타입 별칭
typealias MyInt = Int
typealias MyDouble = Double
5.3 튜플

타입의 이름이 따로 지정되어 있지 않은, 프로그래머가 만드는 타입.

지정된 데이터의 묶음 이라고 표현할 수 있음.

5.4 컬렉션 타입

Array, Dictionary, Set

5.4.1 Array

스위프트의 Array는 C 언어의 배열처럼 Buffer이다.

5.4.2 Dictionary

순서없이 키와 값의 쌍으로 구성되는 타입.

5.4.3 Set

보통 순서가 중요하지 않거나 각 요소가 유일한 값이어야 하는 경우에 사용.

세트의 요소로는 해시 가능한 값이 들어와야 함.

5.5 열거형

연관된 항목을 묶어서 표현하는 데이터 타입. Array 나 Dictionary와 다르게 프로그래머가 정의해준 항목 값 외에는 추가/수정 불가.

사용 예

  • 제한된 선택지를 주고 싶을 때
  • 정해진 값 외에는 입력받고 싶지 않을 때
  • 예상된 입력 값이 한정되어 있을 때
  1. 스위프트의 열거형은 항목별로 값을 가질 수도, 가지지 않을 수 도 있음.

  2. 각 항목이 그 자체로 고유의 값이 될 수 있음.

  3. 각 항목 자체로도 하나의 값이지만 항목의 원시 값 (Raw value)도 가질 수 있음.

  4. 스위프트의 열거형 각 항목이 연관 값을 가지게 되면, 기존 프로그래밍 언어의 공용체 형태를 띌 수도 있음.

    열거형 내의 항목(case)이 자신과 연관된 값을 가질 수 있다. 연관 값은 각 항목 옆에 소괄호로 묶어 표현.

    모든 항목이 연관 값을 가질 필요는 없다.

    enum MainDish {
        case pasta(taste: String)
        case pizza(dough: String, topping: String)
        case chicken(withSauce: Bool)
        case rice
    }
    
  5. 순환 열거형은 항목의 연관 값이 열거형 자신의 값이고자 할 때 사용. indirect 키워드를 명시하면 됨.

    // 특정 항목에 순환 열거형 명시 
    enum ArithmeticExpression {
        case number(Int)
        indirect case addition(ArithmeticExpression, ArithmeticExpression)
        indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
    }
    // 열거형 전체에 순환 열거형 명시 
    indirect enum ArithmeticExpression {
        case number(Int)
        case addition(ArithmeticExpression, ArithmeticExpression)
        case multiplication(ArithmeticExpression, ArithmeticExpression)
    }
    

    이진 탐색 트리 등의 순환 알고리즘을 구현할 때 유용

6. 연산자

6.1 종류
  • 할당

  • 산술

  • 비교

  • 삼항 조건 : Question ? A : B

  • 범위

    연산자 부호 설명
    폐쇄 범위 연산자 A…B A 부터 B까지의 수를 묶어 표현. A와 B를 포함.
    반 폐쇄 범위 연산자 A ..< B A 부터 B 미만까지의 수
    단방향 범위 연산자 A… A 이상의 수
      …A A 이하의 수
      ..< A A 미만의 수
  • 부울

  • 비트

  • 복합 할당

  • 오버플로

    연산자 부호 설명
    오버프로 더하기 연산 &+ 오버플로에 대비한 덧셈 연산
    오버플로 빼기 연산 &- 오버플로에 대비한 뺄셈 연산
    오버플로 곱하기 연산 &* 오버플로에 대비한 곱셈 연산
  • 기타

    연산자 부호 설명
    nil 병합 연산자 A ?? B A 가 nil이 아니면 A를 반환하고, A가 nil이면 B를 반환
    옵셔널 강제 추출 연산자 O! O의 값을 강제로 추출
    옵셔널 연산자 V? V를 안전하게 추출하거나 V가 옵셔널임을 표현
6.2 사용자 정의 연산자

스위프트에서는 사용자의 입맛에 맞게 연산자 역할을 부여할 수 있다. 또, 기존에 존재하지 않던 연산자 기호를 만들어 추가 가능

7. 함수

7.1 종료되지 않는 함수

스위프트에는 종료(return)되지 않는 함수가 있다.

종료되지 않는다는 의미는 정상적으로 끝나지 않는 함수라는 뜻. 이를 비반환 함수 Nonreturning fuction 또는 Nonreturning method 비반환 메서드라고 함.

비반환 함수 안에서는 오류를 던진다든가, 중대한 시스템 오류를 보고하는 등의 일을 하고 프로세스를 종료해 버림.

비반환 함수는 반환 타입을 Never라고 명시해 주면 됨.

func crashAndBurn() -> Never {
    fatalError("Something very, very bad happend")
}

8. 옵셔널

옵셔널은 스위프트의 특징 중 하나인 안정성을 문법으로 담보하는 기능.

######값 추출 방법

8.1 강제 추출

런타임 오류가 일어날 가능성이 높아 위험한 방법

8.2 옵셔널 바인딩

조금 더 안전하고 세련된 방법

8.3 암시적 추출 옵셔널 (Implicitly Unwrapped Optionals)

nil을 할당하고 싶지만, 옵셔널 바인딩으로 매번 값을 추출하기 귀찮거나 런타임 오류가 발생하지 않을 것 같다는 확신이 들때 사용

암시적 추출 옵셔널로 지정된 타입은 일반 값처럼 사용가능하나, 여전히 옵셔널이기 때문에 nil도 할당 가능.

9. 구조체와 클래스

9.1 구조체 정의
struct 구조체 이름 {
    프로퍼티와 메서드들
}

기본적으로 멤버 와이즈 이니셜라이저 제공.

사용자 정의 이니셜라이저도 구현 가능.

9.2 클래스 정의
class 클래스 이름 {
    프로퍼티와 메서드들
}
9.2.1 클래스 인스턴스의 소멸

클래스의 인스턴스는 참조 타입이므로 더는 참조할 필요가 없을 때 메모리에서 해제 된다.

이 과정을 소멸이라고 하는데 소멸되기 직전에 deinit 라는 메소드가 호출된다.

9.3 구조체와 클래스의 차이

같은점

  • 값을 저장하기 위해 프로퍼티를 정의할 수 있다.
  • 기능 실행을 위해 메서드를 정의 할 수 있다.
  • 서브스크립트 문법을 통해 프로퍼티에 접근하도록 정의할 수 있다.
  • 초기화 상태를 지정하기 의해 이니셜라이저를 지정할 수 있다.
  • extension 기능으로 확장 할 수 있다.
  • 특정 프로토콜을 준수 할 수 있다.

다른 점

  • 구조체는 상속할 수 없다.
  • 타입캐스팅은 클래스의 인스턴스에만 허용.
  • 디이니셜라이저는 클래스의 인스턴스에만 활용 가능
  • 참조 횟수 계산 (Reference counting)은 클래스의 인스턴스에만 적용
9.3.1 스위프트의 기본 데이터 타입

스위프트의 기본 데이터타입은 모두 구조체이다.

9.4 구조체와 클래스 선택해서 사용하기

다음 조건 중 하나이상에 해당한다면 구조체 사용 권장

  • 연관된 간단한 값의 집합을 캡슐화하는 것만이 목적인 경우
  • 캡슐화한 값을 참조하는 것보다 복사하는 것이 합당할 때
  • 구조체에 저장된 프로퍼티가 값 타입이며 참조하는 것보다 복사하는 것이 합당할 때
  • 다른 타입으로부터 상속받거나 자신을 상속할 필요가 없을 때

10. 프로퍼티와 메서드

10.1 프로퍼티

클래스, 구조체 또는 열거형 등에 관련된 값

10.1.1 저장 프로퍼티

클래스 또는 구조체의 인스턴스와 연관된 값을 저장하는 가장 단순한 개념의 프로퍼티

10.1.2 지연 저장 프로퍼티

호출이 있어야 값을 초기화 하며, lazy 키워드 사용

10.1.3 연산 프로퍼티

실제 값을 저장하는 프로퍼티가 아니라, 특정 상태에 따른 값을 연산하는 프로퍼티.

인스턴스 내/외부의 값을 연산하여 돌려주는 접근자getter의 역할이나 은닉화된 내부의 프로퍼티 값을 간접적으로 설정하는 설정자setter의 역할 가능

10.1.4 프로퍼티 감시자

프로퍼티 값이 변경 되기 직전 호출하는 willSet 메서드와 변경된 직후 호출하는 didSet메서드가 있다.

10.1.5 전역변수와 지역변수

전역변수 또는 전역상수는 지연 저장 프로퍼티처럼 처음 접근할 때 최초로 연산이 이루어 지므로 lazy키워드를 사용하여 연산을 늦출 필요가 없다. 반대로 지역변수 및 지역상수는 절대로 지연 연산 되지 않는다.

10.1.6 타입 프로퍼티

각각의 인스턴스가 아닌 타입 자체에 속하는 프로퍼티. 모든 인스턴스에서 공용으로 접근하고 값을 변경할 수 있는 변수 등을 정의할때 유용.

저장 타입 프로퍼티는 변수 또는 상수로만 선언할 수 있으며, 연산 타입 프로퍼티는 변수로만 선언 가능.

인스턴스를 생성하지 않고도 사용할 수 있다.

10.2 메서드

기존 프로그래밍 언어에서의 클래스 메서드와 유사한 개념.

구조체와 열겨형이 메서드를 가질 수 있다는 점은 기존 프로그래밍 언어와 스위프트의 큰 차이점 중 하나.

10.2.1 인스턴스 메서드

인스턴스가 존재할 때만 사용 가능

10.2.2 타입 메서드

메서드 앞에 static 키워드를 사용하여 타입 메서드임을 나타낸다. 클래스의 타입 메서드는 static 키워드와 class 키워드를 사용할 수 있는데 static으로 정의하면 상속 후 메서드 재정의가 불가능하고 class로 정의하면 상속 후 재정의 가능.

11. 인스턴스 생성

11.1 옵셔널 프로퍼티 타입

초기화 과정에서 값을 지정해주기 어려운 경우 저장 프로퍼티를 옵셔널로 선언 가능.

옵셔널로 선언한 저장 프로퍼티는 초기화 과정에서 값을 할당해주지 않으면 자동으로 nil 할당.

11.2 실패 가능한 이니셜라이저

클래스, 구조체, 열거형 모두 정의 가능.

실패했을 때 nil을 반환해주므로 반환 타입이 옵셔널로 지정됨.

11.3 함수를 사용한 프로퍼티 기본값 설정

클로저나 함수의 반환 타입은 프로퍼티의 타입과 일치해야 함.

만약 클로저를 사용한다면 클로저가 실행되는 시점은 초기화할 때 다른 프로퍼티 값이 설정되기 전이기 때문에 클로저 내부에서 인스턴스의 다른 프로퍼티를 사용해서 연산할수 없음.

self 사용 안됨.

인스턴스 메서드 호출 안됨.

12. 접근제어

접근 수준

접근수준 키워드 범위
개방 접근 수준 open 모듈 외부까지(클래스 에서만 사용)
공개 접근 수준 public 모듈 외부까지
내부 접근 수준 internal 모듈 내부
파일외부비공개 접근 수준 fileprivate 파일 내부
비공개 접근 수준 private 기능 정의 내부

13. 클로저

형태

  • 이름이 있으면서 어떤 값도 획득하지 않는 전역함수의 형태
  • 이름이 있으면서 다른 함수 내부의 값을 획득할 수 있는 중첩된 함수의 형태
  • 이름이 없고 주변 문맥에 따라 값을 획득할 수 있는 축약 문법으로 작성한 형태

표현

  • 클로저는 매개변수와 반환 값의 타입을 문맥으로 유추할수 있기 때문에 매개변수와 반환 값의 타입 생략 가능
  • 클로저에 단 한줄의 표현만 들어있다면 암시적으로 리턴값으로 취급
  • 축약된 전달인자 이름 사용 가능
  • 후행 클로저 문법 사용 가능
13.1 값 획득

클로저는 자신이 정의된 위치의 주변 문맥을 통해 상수나 변수를 capture 할수 있다.

값 획득을 통해 클로저는 주변에 정의한 상수나 변수가 더 이상 존재하지 않더라도 해당 상수나 변수의 값을 자신의 내부에서 참조하거나 수정 할 수 있다.

13.2 탈출 클로저

함수의 전달인자로 전달한 클로저가 함수 종료 후에 호출될 때 클로저가 함수를 탈출 Escape 한다고 표현.

클로저를 매개변수로 갖는 함수 선언시 매개변수 이름의 콜론(:) 뒤에 @escaping 키워드를 사용하여 클로저가 탈출하는 것용 허용한다고 명시 해줄수 있음

비동기 작업으로 함수가 종료되고 난 후 호출할 필요가 있는 클로저를 사용해야할 때 탈출 클로저가 필요.

13.3 withoutActuallyEscaping

비탈출 클로저로 잔달한 클로저가 탈출 클로저인 척 해야하는 경우. 실제로는 탈출하지 않는데 다른 함수애서 탈출 클로저를 요구하는 상황

오류가 발생하는 hasElements 함수
func hasElements(in array: [Int], match predicate: (Int)->Bool) -> Bool {
    return (array.lazy.filter { predicate($0)}.isEmpty == false)
}
withoutActuallyEscaping(_:do:)함수의 활용
func hasElements(in array: [Int], match predicate: (Int)->Bool) -> Bool {
    return withoutActuallyEscaping(predicate, do: { escapablePredicate in
    	return (array.lazy.filter { predicate($0)}.isEmpty == false) 
    })
}
13.4 자동 클로저

함수의 전달인자로 전달하는 표현을 자동으로 변환해주는 클로저.

자동클로저는 전달인자를 갖지 않음. 호출되기 전까지 내부의 코드가 동작하지 않음

// 내부 코드를 미리 실행하지 않음
let customerProvider: () -> String = {
    return customersInLine.removeFirst()
}

// 실제 실행
print("Now serving \(customerProvider()!)")

14. 옵셔널 체이닝

옵셔널에 속해있는 프로퍼티, 메서드, 서브스크립션 등을 가져오거나 호출할 때 사용할 수 있는 일련의 과정.

let optionalChaining: Int? = object?.property?.someValue

15. guard 구문

  • if 와 유사하게 Bool 타입의 값으로 동작.

  • guard뒤에 따라 붙는 코드의 실행 결과가 true 일때 코드가 계속 실행.

  • else 구문이 항상 뒤에 따라와야 함.
  • 특정 조건에 부합하지 않는다면 바로 코드 블록의 실행을 종료 할 수 있음
  • if와 다르게 예외 상황만 처리하고 싶은 경우 사용
guard Bool 타입  else {
    예외사항 실행문
    제어문 전환 명령어
}

제어문 전환 명령어

return, break, continue, throw, fatalError() 등

16. map, filter, reduce

16.1 map
  • 자신을 호출할 때 매개변수로 전달된 함수를 실행하여 그 결과를 다시 반환해주는 함수
  • Sequence, Collection 프로토콜을 따르는 타입과 옵셔널은 모두 맵 사용 가능
  • 기존 컨테이너의 값은 변경되지 않고 새로운 컨테이너가 생성되어 반환
  • 기존 데이터를 변형하는데 많이 사용

map 메서드의 사용법은 for-in 구문과 별반 차이가 없지만 코드의 재사용 측면이나 컴파일러 최적화 측면에서 성능의 차이가 있다. 또, 다중 스레드 환경일 때 컨테이너의 값이 스레드에서 변경되는 시점에 다른 스레드에서도 동시에 값이 변경되려고 할 때 예측하지 못한 결과가 발생하는 부작용을 방지할 수도 있다.

16.2 filter
  • 컨테이너 내부의 값을 걸러서 추출
  • map과 마찬가지로 새로운 컨테이너에 값을 담아 반환
  • 특정 조건에 맞게 걸러내는 역할을 할 수 있음

filter 함수의 매개변수로 전달되는 함수의 반환 타입은 Bool이다. 해당 콘텐츠의 값을 갖고 새로운 컨테이너에 포함될 항목이면 true, 포함하지 않으려면 false를 반환하게 하면 된다.

16.3 reduce
  • 컨테이너 내부의 콘텐츠를 하나로 합하는 기능

swift의 reduce는 두 가지 형태로 구현 되어있다.

첫 번째는 클로저가 각 요소를 전달받아 연산한 후 값을 다음 클로저 실행을 위해 반환하며 컨테이너를 순환하는 형태이다.

public func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result

두 번째는 컨테이너를 순환하면서 클로저가 실행되지만 클로저가 따로 결과값을 반환하지 않는 형태이다. 대신 inout 매개변수를 사용하여 초깃값에 직접 연산을 실행하게 된다.

public func reduce<Result>(into initialResult: Result, _ updateAccmulatingResult: (inout Result, Element) throws-> ()) rethrows -> Result

사용 예)

// 초기 값이 3이고 정수 배열의 모든 값을 더함
let sumFromThree : Int = numbers.reduce(3) {
    return $0 + $1
}

// 첫 번째 reduce 형태와 달리 클로저의 값을 반환하지 않고 내부에서
// 직접 이전값을 변경
let subtractFromThree = numbers.reduce(into: 3, {
    $0 -= $1
})

17. Monad

monad 는 특정한 상태로 값을 포장하는 것에서 출발한다. swift에서는 이를 옵셔널이라는 형태로 구현했는데 값이 있을지 없을지 모르는 상태속에 포장하는 것이다.

함수 객체와 모나드는 특정 기능이 아닌 디자인 패턴 혹은 자료구조라고 할 수 있다.

17.1 Context

context 의 사전적 정의를 보면 ‘맥락’, ‘전후 사정’ 등이다. 여기서 context 는 ‘contents 를 담은 그 무엇인가’를 뜻한다.

옵셔널은 열거형으로 구현되어 있어서 열거형 case 의 연관 값을 통해 인스턴스 안에 연관 값을 갖는 형태이다. 옵셔널에 값이 없다면 열거형의 .none case로, 값이 있다면 .some(Val) case 로 값을 지니게 된다.

2라는 숫자를 옵셔널로 둘러싸면, 컨텍스트 안에 2라는 콘텐츠가 들어가는 모양새이다. 그리고 ‘컨텍스트는 2라는 값을 가지고 있다’라고 말할 수 있다. 만약 값이 없는 옵셔널 상태라면 ‘컨텍스트는 존재하지만 내부에 값이 없다’고 말할 수 있다.

17.2 함수 객체

map 은 컨테이너의 값을 변형시킬 수 있는 고차함수 이다. 그리고 옵셔널은 컨테이너와 값을 가지기 때문에 map 함수를 사용할 수 있다.

Optional(2).map(addThree) // Optional(5)

‘함수 객체란 map을 적용할 수 있는 컨테이너 타입’ 이라고 말할 수 있다.

Array, Dictionary, Set 등등 스위프트의 많은 컬렉션 타입이 함수객체이다.

옵셔널의 map(_:) 메서드를 호출하면 옵셔널 스스로 값이 있는지 없는지 switch 구문으로 판단하고 값이 있다면 전달받은 함수에 자신의 값을 적용한 결과값을 다시 컨텍스트에 넣어 반환하고, 그렇지 않다면 함수를 실행하지 않고 빈 컨텍스트를 반환한다.

17.3 모나드

monad는 함수객체의 일종으로 맵 함수를 적용할 수 있는, 즉 맵 함수를 지원하는 컨테이너 타입이다. 모나드는 거기에 더 나아가 값이 있을지 업을지 모르는 상태를 추가한다. 즉, 모나드는 값이 있을 수도 있고 없을 수도 있는 컨텍스트를 갖는 함수객체 타입이다.

17.3.1 flatMap

flatMap 은 포장된 값을 받아서 값이 있으면 포장을 풀어서 값을 처리한 후 포장된 값을 반환하고, 값이 없으면 없는대로 다시 포장하여 반환한다. flatMap과 map의 차이점이라면 내부의 값을 알아서 더 추출해준다는 것이다.

차이점 1

let optionalArr: [Int?] = [1, 2, nil, 5]

let mappedArr: [Int?] = optionalArr.map{ $0 }
let flatmappedArr: [Int] = optionalArr.flatMap{ $0 }

print(mappedArr)	// [Optional(1), Optional(2), nil, Optional(5)]
print(flatmappedArr)	// [1, 2, 5]

차이점 2

let multipleContainer = [[1, 2, Optional.none], [3, Optional.none], [4, 5, Optional.none]]
let mappedMultipleContainer = multipleContainer.map{ $0.map{ $0 } }
let flatmappedMultipleContainer = multipleContainer.flatMap{ $0.flatMap{ $0 } }

print(mappedMultipleContainer)
// [[Optional(1), Optional(2), nil], [Optional(3), nil], [Optional(4), Optional(5), nil]]
print(flatmappedMultipleContainer) // [1, 2, 3, 4, 5]

18. 서브스크립트

클래스, 구조체, 열거형에는 컬렉션, 리스트, 시퀀스 등 타입의 요소에 접근하는 단축 문법인 서브스크립트 정의 가능.

18.1 문법

인스턴스 이름 뒤에 대괄호로 감싼 값을 써줌으로써 인턴스 내부의 특정 값에 접근 가능.

subscript 키워드를 사용하여 정의.

subscript(index: Int) -> Int {
    get {
        // 결과 값 반환
    }
    set(newValue){
        // 설정자 역할
    }
}
18.2 복수 서브스크립트

하나의 타입이 여러개의 서브스크립트를 가질 수 도 있다.

19. 상속

19.1 프로퍼티 재정의

프로퍼티를 재정의 할때 저장 프로퍼티는 재정의 할수 없다. 프로퍼티를 재정의 한다는 것은 프로퍼티 자체가 아니라 프로퍼티의 접근자, 설정자, 프로퍼티 감시자등을 재정의하는것을 의미한다.

부모 클래스에서 읽기 전용 프로퍼티였더라도 자식클래스에서 읽고 쓰기가 가능한 프로퍼티로 재정의 가능하지만 읽기, 쓰기 모두 가능했던 프로퍼티를 읽기 전용으로 재정의 불가능.

19.2 서브스크립트 재정의

서브스크립트도 재정의 가능.

19.3 재정의 방지

재정의할 수 없도록 제한하고 싶다면 앞에 final 키워드를 명시.

19.4 클래스의 이니셜라이저
19.4.1 Designated Initializer 와 Convenience Initializer

Designated Initializer 는 클래스의 주요 이니셜라이져이다. 정의된 클래스의 모든 프로퍼티를 초기화 해야한다.

Convenience Initializer는 초기화를 쉽게 도와주는 역할을 한다. 지정 이니셜라이저를 자신 내부에서 호출하한다.

편의 이니셜라이저는 필수 요소는 아니다. 클래스 설계자의 의도대로 외부에서 사용하길 원하거나 인스턴스 생성 코드를 작석하느느 수고를 덜 목적으로 사용할 수 있다.

19.4.2 클래스 초기화 위임

지정 이니셜라이저와 편의 이니셜라이저의 관계 규칙

  1. 자식 클래스의 지정 이니셜라이저는 부모클래스의 지정 이니셜라이저를 반드시 호출 해야한다.
  2. 편의 이니셜라이저는 자신을 정의한 클래스의 다른 이니셜라이저를 반드시 호출해야 한다.
  3. 편의 이니셜라이저는 궁극적으로는 지정 이니셜라이저를 반드시 호출해야 한다.
19.4.3 2단계 초기화

스위프트의 클래스 초기화는 2단계를 거친다. 1단계는 클래스에 정의한 각각의 저장 프로퍼티에 초깃값이 할당된다. 모든 저장 프로퍼티의 초기 상태가 결정되면 2단계로 들어가 저장 프로퍼티들을 사용자 정의할 기회를 얻는다. 그 후 비로소 새로운 인스턴스를 사용할 준비가 끝난다.

2단계 초기화는 프로퍼티를 초기화하기 전에 프로퍼티 값에 접근하는 것을 막아 초기화를 안전하게 해준다.

스위프트 컴파일러는 2단계 초기화를 오류없이 처리하기 위해 네가지 안전확인을 실행한다.

  1. 자식클래스의 지정 이니셜라이저가 부모클래스의 이니셜라이저를 호출하기 전에 자신의 프로퍼티를 모두 초기화 했는지 확인.
  2. 자식클래스의 지정 이니셜라이저는 상속받은 프로퍼티에 값을 할당하기 전에 반드시 부모클래스의 이니셜라이저를 호출해야 한다.
  3. 편의 이니셜라이저는 자신의 클래스에 정의한 프로퍼티를 포함하면 그 어떤 프로퍼티라도 값을 할당하기 전에 다른 아니셜라이저를 호출해야한다.
  4. 초기화 1단계를 마치기 전까지는 이니셜라이저는 인스턴스 메서드를 호출할 수 없다. 또, 인스턴스 프로퍼티의 값을 읽어들일 수도 없다. self 프로퍼티를 자신의 인스턴스를 나타내는 값으로도 활용할수 없다.

네 가지 안전 확인에 근거하여 2단계 초기화가 이루어지는 과정

1단계
  1. 클래스가 지정 또는 편의 이니셜라이저 호출
  2. 그 클래스의 새로운 인스턴스를 위한 메모리가 할당. 메모리는 아직 초기화되지 않음
  3. 지정 이니셜라이저는 클래스에 정의된 모든 저장 프로퍼티에 값이 있는지 확인. 현재 클래스 부분까지의 저장 프로퍼티를 위한 메모르는 이제 초기화 됨
  4. 지정 이니셜라이저는 부모클래스의 이니셜라이저가 같은 동작을 행할 수 있도록 초기화 양도
  5. 부모클래스는 상속 체인을 따라 최상위 클래스에 도달할 때까지 이 작업을 반복
2단계
  1. 최상위 클래스로부터 최하위 클래스까지 상속 체인을 따라 내려오면서 지정 이니셜라이저들이 인스턴스를 제 각각 사용자 정의하게 된다. 이 단계에서는 self를 통해 프로퍼티 값을 수정할 수 있고, 인스턴스 메서드를 호출하는 등의 작업 진행가능
  2. 마지막으로 각각의 편의 이니셜라이저를 통해 self를 통한 사용자정의 작업을 진행할 수 있다.
19.4.4 이니셜라이저 상속 및 재정의

기본적으로 스위프트의 이니셜라이저는 부모 클래스의 이니셜라이저를 상속받지 않는다. 안전하고 적절하다고 판단되는 상황에서는 상속되기도 한다.

부모클래스와 동일한 지정 이니셜라이저를 자식클래스에서 구현하려면 재정의하면 된다.

그러러면 override 수식어를 붙여아한다. 클래스에 주어지는 기본 이니셜라이저를 재정의 할 때도 마찬가지이다.

자식클래스에서 부모클래스의 편의 이니셜라이저는 절대 호출할 수 없다.

부모클래스에서 실패 가능한 이니셜라이저였더라도 자식클래스에서느느 필요에 따라 실패하지 않는 이니셜라이저로 재정의 가능.

19.4.5 이니셜라이저 자동 상속

자식클래스에서 프로퍼티 기본값을 모두 제공한다고 가정할때, 두 가지 규칙에 따라 자동 상속된다.

  1. 자식클래스에서 별도의 저징 이니셜라이저를 구현하지 않을 때
  2. 부모클래스의 지정 이니셜라이저를 모두 재정의하여 부모클래스와 동일한 지정 이니셜라이저를 모두 사용할 수 있는 상황 일때.
19.4.6 요구 이니셜라이저

required 수식어를 클래스의 이니셜라이저 앞에 명시해주면 이 클래스를 상속받은 자식클래스에서는 반드시 해당 이니셜라이저를 구현해야한다.

20. 타입캐스팅

스위프트는 데이터 타입 안전을 위하여 각기 다른 타입끼리의 값 교환을 엄격히 제한한다.

다른 프로그래밍 언어에서 대부부누 지원하는 암시적 데이터 타입 변환은 지원하지 않는다.

20.1 기존 언어의 타입 변환과 스위프트의 타입 변환

c 언어

double value = 3.3;
int convertedValue = (int)value;
convertedValue = 5.5	// double -> int 암시적 데이터 타입 반환

스위프트

var value: Double = 3.3
var convertedValue: Int = Int(value)
convertedValue = 5.5	// 오류!

스위프트 코드를 보면 Int(value) 형태로 데이터 타입의 형태를 변경해주는데, 이것은 이니셜라이저이다. 즉, 기존 값을 전달인자로 받는 이니셜라이저를 통해 새로운 Int 구조체의 인스턴스를 생성한다. Int 구조체에 실패가능한 이니셜라이저도 포함되어있다.

적절치 못한 매개변수가 전달되면 새로운 인스턴스가 생성되지 않을 수 있다.

20.2 스위프트의 타입 캐스팅

스위프트의 타입캐스팅은 인스턴스의 타입을 확인하거나 자신을 다른 타입의 인스턴스인양 행세할 수 있는 방법으로 사용할 수 있다. 스위프트의 타입 캐스팅은 is 와 as 연산자로 구현했다.

20.3 데이터 타입 확인

타입 확인 연산자인 is를 사용하여 인스턴스가 어떤 클래스의 인스턴스인지 타입을 확인할 수 있다.

is 연산자 이외에도 Meta Type을 이용해서 타입을 확인할 수 있다.

메타 타입은 타입의 타입을 뜻한다. 즉, 타입 자체가 하나의 타입으로 또 표현할 수 있다.

SomeClass라는 클래스의 메타 타입 SomeClass.Type,

SomeProtocol의 메타 타입은 SomeProtocol.Protocol로 표현.

self 를 사용해서 타입을 값처럼 표현 가능.

20.4 다운 캐스팅

타입 캐스트 연산자(as)를 사용하여 자식클래스 타입으로 다운 캐스팅.

타입캐스팅의 의미

캐스팅은 실제 인스턴스를 수정하거나 값을 변경하는 작업이 아닌 어떤 타입으로 다루고 접근해야 할지 판단할 수 있도록 컴퓨터에 힌트를 주는 것 뿐이다.

20.5 Any, AnyObject의 타입 캐스팅

특정 타입을 지정하지 않고 여러 타입의 값을 할당할 수있는 Any와 AnyObject라는 특별한 타입이 있다. Any는 함수의 타입을 포함한 모든 타입을 뜻하고, AnyObject는 클래스 타입만 가능하다.

21. 프로토콜

프로토콜은 특정 역할을 하기 위한 메서드, 프로퍼티, 기타 요구사항 등의 청사진을 정의.

어떤 프로토콜의 요구사항을 모두 따르는 타입은 ‘해당 프로토콜을 준수한다고 표현’.

프로토콜은 정의를 하고 제시할 뿐이지 스스로 기능을 구현하지 않음.

21.1 정의
protocol 프로토콜 이름 {
    프로토콜 정의
}
21.2 프로토콜 요구사항
21.2.1 프로퍼티 요구

프로토콜은 자신을 채택한 타입이 어떤 프로퍼티를 구현해야하는지 요구할 수 있다. 그렇지만 그 프로퍼티의 종류 (연산 프로퍼티, 저장 프로퍼티)는 따로 신경쓰지 않는다. 프로토콜을 채택한 타입은 프로토콜이 요구하는 프로퍼티의 이름과 타입만 맞도록 구현하면 된다. 다만 읽기 전용으로 할지 혹은 읽기 쓰기 모두 가능하게 할지는 정해야 한다.

protocol SomeProtocol {
    var settableProperty: String { get set }
    var notNeedToBeSettableProperty: String { get }
}
21.2.2 메서드 요구

프로토콜은 특정 인스턴스 메서드나 타입 메서드를 요구할 수도 있다. 다만 메서드의 실제 구현부인 중괄호 부분은 제외하고 메서드의 이름, 매개변수 , 반환 타입 등만 작성하며 가변 매개변수도 허용한다.

프로토콜의 메서드 요구에서는 매개변수 기본값 지정이 불가능하다.

타입 메서드를 요구할 때는 프로퍼티 요구와 마찬가지로 앞에 static키워드를 명시하며, 클래스에서 실제 구현할 때는 static 키워드나 class 키워드 어느 쪽을 사용해도 무방하다.

21.2.3 가변 메서드 요구

가끔은 메서드가 인스턴스 내부의 값을 변경할 필요가 있다. 값 타입 (구조체와 열거형)의 인스턴스 메서드에서 자신 내부의 값을 변경하고자 할 때는 func 키워드 앞에 mutating 키워드를 적어 메서드에서 인스턴스 내부의 값을 변경한다는 것을 확실히 해준다.

참조 타입인 클래스의 메서드 앞에는 mutating 키워드를 명시하지 않아도 인스턴스 내부의 값을 바꾸는데 문제가 없지만, 값 타입인 구조체와 열겨헝의 메서드 앞에는 mutating 키워드를 붙인 가변 메서드 요구가 필요하다.

protocol Resettable {
    mutating func set()
}

struct Point: Resettable {
    var x: Int = 0
    var y: Int = 0
    
    mutating func reset() {
        self.x = 0
        self.y = 0
    }
}

enum Direction: Resettable {
    case east, west, south, north, unknown
    
    mutating func reset() {
        self = Direction.unknown
    }
}
21.2.4 이니셜라이저 요구

프로토콜에서 이니셜라이저를 요구하려면 메서드 요구와 마찬가지로 정의만 하지 구현하지 않는다.

  • 구조체의 이니셜라이저 요구 구현
protocol Named {
    var name: String { get }
    
    init(name: String)
}

struct Pet: Named {
    var name: String
    
    init(name: String) {
        self.name = name
    }
}

구조체는 상속할 수 없기 때문에 이니셜라이저 요구에 대해 크게 신경쓸 필요가 없다.

  • 클래스의 이니셜라이저 구현
class Person: Named {
    var name: String
    
    required init(name: String) {
        self.name = name
    }
}

이니셜라이저 요구에 부합하는 이니셜라이저를 구현할 때는 required 식별자를 붙인 요구 이니셜라이저로 구현해야 한다.

만약 클래스 자체가 상속받을 수 없는 final 클래스라면 required 식별자를 붙여줄 필요가 없다.

특정 프로토콜이 요구하는 이니셜라이저가 이미 구현되어 있는 상황에서 그 클래스를 상속받은 클래스가 있다면, requiredoverride 식별자를 모두 명시하여 프로토콜에서 요구하는 이니셜라이저를 구현해주어야 한다. 두 식별자 중 어떤 것이 먼저 위치해도 상관 없다. 즉, override required로 표기해도 무방하다.

class School {
    var name: String
    
    init(name: String) {
        self.name = name
    }
}

class MiddleSchool: School, Named {
    required override init(name: String) {
        super.init(name: name)
    }
}
21.3 프로토콜의 상속과 클래스 전용 프로토콜

프로토콜은 하나 이상의 프로토콜을 상속받아 기존보다 더 많은 요구사항을 추가할 수 있다.

프로토콜의 상속 리스트에 class 키워들 추가해 프로토콜이 클래스 타입에만 채택될 수 있도록 제한할 수 있다.

21.4 프로토콜의 선택적 요구

요구사항 중 일부를 선택적 요구사항으로 지정할 수 있다. 다만 선택적 요구사항을 정의하고 싶은 프로토콜은 @objc 속성이 부여된 프로토콜이어야 한다. @objc 속성이 부여되는 프로토콜은 Objective-C 클래스를 상속받은 클래스에서만 채택할 수 있다. 즉, 열거형이나 구조체 등에서는 @objc 속성이 부여된 프로토콜은 아예 채택할 수 없다.

선택적 요구사항은 optional 식별자를 요구사항의 정의 앞에 붙여주면 된다. 만약 메서드나 프로퍼티를 선택적 요구사항으로 요구하게 되면 그 타입은 자동적으로 옵셔널이 된다.

@objc protocol Moveable {
    func walk()
    @objc optional func fly()
}
21.5 프로토콜 변수와 상수

프로토콜 이름을 타입으로 갖는 변수 또는 상수에는 그 프로토콜을 준수하는 타입의 어떤 인스턴스라도 할당 할수 있다.

21.6 위임을 위한 프로토콜

위임은 클래스나 구조체가 자신의 책임이나 임무를 다른 타입의 인스턴스에게 위임하는 디자인 패턴이다. 책무를 위임하기 위해 정의한 프로토콜을 준수하는 타입은 자신에게 위임될 일정 책무를 할 수 있다는 것을 보장한다. 위임은 사용자의 특정 행동에 반응하기 위해 사용되기도 하며, 비동기 처리에도 많이 사용한다.


Improve this page