9.1 타입 시스템
스위프트 타입 시스템은 자바스크립트나 파이썬처럼 자유로운 덕 타입 시스템이 아니라 명시적인 타입 시스템이다. 오브젝티브-C 처럼 모든 객체가 다이내믹 타입은 아니지만 프로토콜 타입을 활용해서 다이내믹하게 확장하면서도 오브젝티브-C보다 안전하게 쓸 수 있다.
9.1.1 스위프트 타입
스위프트에는 크게 두 종류의 타입이 있다. 이름 있는 타입(named type)과 이름 없이 합쳐진 타입(compound type)이다. 이름 있는 타입은 클래스, 구조체, 열거, 프로토콜 같은 타입의 이름이 미리 정해진 형태를 말한다. 기존 타입을 상속, 확장해서 나만의 이름을 주고 새로운 타입으로 지정할 수도 있다. 라이브러리에 포함하고 있는 배열, 사전, 옵셔널 타입도 모두 이름 있는 타입이다. 오브젝티브-C에서 사용하는 int, char 같은 기본 타입도 스위프트에서는 구조체 타입으로 만들어진 이름 있는 타입이다.
합쳐진 타입은 튜플이나 클로저/함수 타입으로 이름이 따로 정해지지 않고, 다른 타입들을 합쳐서 사용하는 타입이다. …중략…
내부 타입 유형
스위프트 컴파일러가 내부적으로 구분하는 세부 타입들은 평소에 사용하는 타입과 사뭇 다르다.
…중략…
9.1.2 타입 검사
스위프트는 안전한 타입 언어를 표방한다. 안전한 타입 언어라는 것은 값에 대한 타입을 명확하게 구분해서 사용할 수 있는 언어라는 의미다. 컴파일러가 다른 타입으로 선언한 변수에 값을 전달하는 것을 미리 방지할 수 있도록 해준다는 것이다. 컴파일하는 동안 안전한 타입 사용을 위해서 타입 검사를 진행한다. 타입 검사는 타입이 일치하지 않아서 개발하는 동안 발생할 수 있는 문제들을 미리 발견할 수 있도록 도와준다.
타입 추론(Type Inference)
…중략…
스위프트 타입 검사는 기존의 오브젝티브-C 처럼 명시적으로 타입을 선언한 정보를 근거로 타입 정보를 만드는 것도 가능하다. 앞의 예시처럼 타입 추론이 필요하면 ML 언어 계열에서 많이 사용하는 HM(Hindley-Milder) 타입 추론 알고리즘(삼단 논법으로 타입을 유추하기 위한 계산식을 만드는 알고리즘)을 구현하는 타입 제약을 이용한다. 컴파일러는 문맥에 따라서 제약 사항을 수식으로 만들고 HM 타입 추론 알고리즘으로 특정 변수나 표현식 타입에 적합한지 계산을 한다. 타입 제약은 타입 비교를 통한 등가성을 판단하고(Equality), 서브 타입에 대한 조건(Subtyping)을 비교한다. 그리고 타입 사이에 전환이 가능한지 판단하거나(Conversion), 다른 타입을 생성하는 값으로 활용할 수 있는지 (Construction) 판단한다. 프로토콜 타입은 프로토콜 규칙을 따르는지(Conformance) 판단한다. 타입 변환에 사용하는 as 연산자 (Checked Cast)도 제약 사항 중에 하나로 계산한다. 고전적인 HM 타입 시스템에 없는 다형성을 위한 제약사항이나 함수 오버로딩에 대한 제약사항도 있다.
스위프트 타입 추론은 3단계로 진행된다.
1단계는 제약 만들기 단계로, 컴파일 요소로 분석해야 하는 표현식과 문맥 정보에서 유추해야 하는 숨겨진 표현식을 분석해서 각 요소들 타입 관계를 제약 사항 집합으로 만든다. 이렇게 만든 제약 사항들을 계산해서 여러 가지 가능성 중에서 가장 적합한 타입을 찾기 위한 준비 단계가 2단계 ‘제약 계산하기’다. 마지막 3단계 ‘제약 판단하기’에서는 앞 단계에서 만든 제약 사항, 표현식과 확정적인 타입 정보까지 포함해서 종합적으로 정확한 타입 정보를 포함하는 표현식을 재생산한다.
스위프트 컴파일러는 제약 사항을 계산할 때, 제약 사항들 조합에 대해 조건에 부합하는 경우에만 점수를 주고 최종적으로 점수가 가장 높은 타입 제약을 선택한다. 같은 점수일 경우는 좀 더 세부적인 타입을 선택한다.
타입 변환
타입 변환은 종류가 전혀 다른 타입끼리 타입을 바꾸는 것이 아니라, 비슷한 종류의 타입끼리만 타입을 바꾸는 것을 의미한다. …중략… 수학에서 구조 동일성을 가지는 벡터와 좌표 시스템처럼, 데이터 타입의 메모리 구조가 동일하고 다루는 소재가 다른 타입끼리만 타입을 바꿀 수 있다. …중략…
다만 값에 대한 손실이 발생할 수 있는 경우에는 반드시 명시적으로 타입을 지정해야만 한다.
9.1.3 의미 있는 값(value samantic) vs 의미 있는 레퍼런스(reference semantic)
의미 있는 레퍼런스는 레퍼런스 방식으로 참조하는 대상이 중요하다는 것이고, 의미 있는 값은 값 자체가 중요하다는 것이다. 스위프트는 값과 레퍼런스에 대한 동작을 모두 지원하지만, 무게 중심은 ‘의미 있는 값’으로 쏠려 있다. …중략… 함수 중심 프로그래밍에서는 함수에서 다루는 변수가 레퍼런스가 아니고 불변 변수여야만 부작용(side effect)이 없다. 따라서 값 자체를 다루는 것이 더 의미 있다. 값 방식은 참조 계산을 하지 않기 때문에 그만큼 병렬 처리나 성능 최적화 측면에서 유리하다.
…중략…
타입별 성능 비교
…중략…
정적 디스패치는 컴파일 시점에 함수의 메모리 주소를 찾아두기 때문에 런타입에는 해당 주소로 바로 이동한다. 특정 조건에서는 컴파일러가 속도 향상을 위해서 인라인에 코드를 그대로 복사하기도 한다. 반면에 동적 디스패치는 런타임에 구현 함수 목록에서 함수 메모리 주소를 찾아서 이동해야 한다. 동적 디스패치는 인라인 처리나 최적화가 불가능하다. (구조체 타입과 파이널 클래스 타입은 정적 디스패치, 클래스 타입과 프로토콜 타입은 동적 디스패치 사용)
9.2 열거 타입
…중략… 스위프트에서 열거 타입은 열거하는 경우에 따라 문자열 타입도 지정 가능하고 실수 타입도 지정할 수 있다. 뿐만 아니라 모든 값이 있을 필요도 없고, 모두 다 같은 타입이 아니어도 된다. 클래스처럼 함수를 만들 수도 있고 확장도 가능하다.
9.2.1 열거 타입과 프로토콜
열거 타입에 정의한 값은 기본적으로 Hashable 프로토콜을 지원해야 한다. <현재는 Hashable 프로토콜이 변경되어서 hashValue 가 아닌, hash(into:) 메서드를 구현해야 한다.> Hashable 프로토콜은 Equatable 프로토콜을 상속받아 만들어져서, 추가적으로 Equatable 프로토콜에 있는 == 비교 함수까지 구현해야 한다.
…중략…
프로토콜 타입과 증거 테이블
클래스 타입에 대한 상속과 다형성은 가상 함수들을 런타임에 찾는 다이내믹 디스패치 방식을 사용한다. 하지만 다른 타입들은 프로토콜 중심 프로그래밍 방식에 맞춰서 프로토콜 증거 테이블을 사용해서 다형성을 구현한다. 어느 모듈의 특정 타입에 대한 프로토콜 구현 함수 이름을 프로토콜 증거 테이블에서 바로 찾아 호출할 수 있다.
변수를 포함하는 프로토콜을 컴파일하면 PWT(Protocol Withness Table)와 함께 VWT(Value Witness Table) 도 함께 만들어진다. VWT는 의미 있는 값을 가지는 타입에 대한 기본적인 동작을 다루는 생성(allocate), 복사(copy), 파괴(destruct), 해제(deallocate) 함수들에 대한 참조 테이블이다. VWT와 PWT 증거 테이블은 다음 그림처럼 값을 저장하는 저장소 데이터 구조를 참조한다. 값 크기가 버퍼 크기보다 작으면 좌측 첫 번째 구조처럼 스택 공간을 그대로 저장한다. 만약 값 크기가 버퍼 크기보다 크면 좌측 두 번째 구조처럼 힙에 큰 데이터 구조를 생성하고 버퍼에는 힙 공간의 주소를 저장한다. 따라서 프로토콜 타입에서 스택만 사용하는 의미 있는 값을 사용하려면 버퍼보다 작은 데이터 구조를 사용해야 한다.
…중략…
9.3 구조체 타입
스위프트 표준 라이브러리는 대부분 구조체 타입을 기반으로 만들어졌다. …중략… 스위프트로 프로그래밍을 한다면 클래스보다는 구조체 타입을 사용하는 것이 더 효율적이다.
9.3.1 구조체 타입
구조체 타입은 C언어의 구조체에 가까울까 오브젝티브-C언어의 클래스에 가까울까? …중략…
스위프트 구조체 타입은 클래스와 비슷하게 생명주기를 가지는 타입이다. 생성자에 해당하는 init() 초기화 함수가 만들어진다. …중략…
9.3.2 구조체 타입 기반의 스위프트 타입
스위프트 표준 라이브러리는 구조체 타입을 기반으로 작성된 타입이 대부분이다. Int나 Bool 타입과 Set, Array, Dictionary 컬렉션 타입도 구조체 타입으로 구현했다. 따라서 오브젝티브-C와 다르게 스위프트 기본 데이터 타입은 의미 있는 값 방식(Value Semantic)으로 동작한다. …중략…
9.3.3 요약
스위프트 구조체 타입은 의미 있는 값 방식으로 동작하는 매우 중요한 타입이다. 구조체 타입은 성능 향상을 위해서 대부분의 경우는 스택에 값을 할당하고 사용한다. 구조체 구조가 동적으로 변하거나 크기가 너무 크다면 힙 공간을 예외적으로 사용하기도 한다. 힙 공간에 있는 구조체이거나 글로벌 구조체의 경우 함수 볌위가 벗어나도 해당 구조체를 참조할 수 있다. 이런 경우 구조체는 객체에 대한 레퍼런스 방식과 비슷하게 동작하지만 참조 계산을 사용하지 않아 순환 참조 문제가 발생하지 않는다.
9.4 문자열 타입
스위프트 문자열 타입은 유니코드를 다루기 적합하도록 구현되었다. 유니코드를 처리하기 위해서 타입뿐만 아니라 문자열을 다루는 API도 유니코드를 고려해서 만들어졌다.
문자열 타입 내부를 살펴보자. 다음 코드처럼 문자열 타입 내부 형태는 문자열 코어(StringCore) 타입을 포함하고 있는 구조체 타입일 뿐이다. 의미 있는 값을 가지는 문자열 타입은 복사할 경우 문자열 코어도 복사한다. 하지만 문자열 값을 바꾸기 전까지는 동일한 메모리를 그대로 갖고 있는 copy-on-write 방식으로 동작하기 때문에, 단지 문자열을 복사하는 것 자체가 메모리를 낭비하지는 않는다. 다만 복사한 문자열을 처음 변경할 때는 문자열 길이에 따라서 O(N) 성능 특성을 가진다는 점을 기억하자.
public struct String {
public init() {
_core = _StringCore()
}
public init(_ _core: _StringCore) {
self._core = _core
}
public var _core: _StringCore
}
코코아 프레임워크에서 NSString 타입은 내부에 여러 문자열 요소를 처리하는 클래스들을 연결해주는 껍데기 타입이었다. 스위프트 문자열 타입도 그 자체로 완성된 타입이라기보다는, 내부 타입을 포함하고 여러 프로토콜로 확장된 타입이다. _core 변수는 public으로 선언되어 있고 완전히 감춰져 있지 않아 접근할 수 있다.
스위프트 문자열에 대한 Equatable 확장을 위한 ==함수의 코드는 다음과 같다. == 함수는 캐노니컬 동등비교 유니코드 표준 방식을 지원한다. 따라서 비교하는 유니코드 문자끼리 다른 코드 값을 갖더라도, 같은 언어적인 의미와 외형을 가지면(canonically) 동등하다고 판단한다. 예를 들어 코드 9-14처럼 초성 ‘ㄱ’과 중성 ‘ㅏ’와 종성 ‘ㄱ’ 코드를 각각 넣더라도 합쳐진 ‘각’ 글자와 동일하다고 판단한다.
extension String: Equatable {
public func ==(lhs: String, rhs: String) -> Bool {
if lhs._core.isASCII && rhs._core.isASCII {
if lhs._core.amount != rhs._core.amount {
return false
}
return _swift_stdlib_memcmp(
lhs._core.startASCII, rhs._core.startASCII,
rhs._core.count) == 0
}
return lhs._compareString(rhs) == 0
}
}
문자열 비교를 위한 다음 코드를 살펴보면, core 변수 문자열이 ASCII 문자열인지 UTF8인지 UTF16인지 NSString 객체인지에 따라서 각기 다른 비교 함수를 사용한다는 것을 알 수 있다. …중략…
9.4.1 문자열 코어
문자열 코어는 ASCII 형태부터 UTF16 형태까지 모두 저장할 수 있는 최적화된 내부 문자열 타입이다.
다음 코드는 StringCore 내부 구조를 보여준다.
public struct _StringCore {
public var _baseAddress: OpaquePointer
var _countAndFlags: UInt
public var _owner: AnyObject?
}
위 코드를 살펴보면 문자열 코어 타입이 얼마나 복잡한지 알 수 있다. 문자열 코어 타입의 기본 형태는 문자열 저장소에 대한 포인터 baseAddress와 문자열 길이나 옵션을 포함하는 countAndFlags, 문자열을 소유하는 AnyObject 타입 객체 owner를 가진다. owner 변수를 갖고 있다는 것에서 알 수 있듯이, 문자열 코어는 NSString 객체에 대한 호환성까지 고려해서 만들어졌다. 문자열 저장소는 스위프트 StringBuffer 타입과 함께 메모리 공간을 효율적으로 함께 쓰기 위해 만들어졌다.
…중략…
문자열 코어 타입은 문자열 저장소를 다루는 기본 코드를 포함하고 있다. 문자열은 기본적으로 연속해서 이어진 메모리 공간에 저장된다. 만약 문자열 뒤에 새로운 글자를 추가하거나 문자열을 변경해서 기존에 저장하던 공간이 가득차면, 기존 저장소보다 더 큰 저장소를 할당하고 문자열을 옮기는 작업을 한다. claimCapacity() 함수에서 큰 저장소를 만드는데, 의도적으로 기존 저장소보다 2배 이상 크게 만든다.
…중략…
9.4.3 문자열 뷰
문자열 타입은 문자를 넣어놓은 배열도, 문자들을 모아놓은 컬렉션 타입도 아니다. 문자열 타입을 마치 컬렉션 타입처럼 다룰 수 있도록 도와주는 뷰 속성들이 있을 뿐이다.
문자열 뷰 (CharacterView)
문자열 뷰는 표준 라이브러리에서 StringCharacterView.swift 파일에 구현되어 있는 문자열 컬렉션 형태로 extened graphme cluter 라는 유니코드 표준 방식을 지원한다. 한글의 경우 초,중,종성으로 각각 분리된 코드도 시각적인 조합 (graphme cluster) 단위로 묶으면 완성형 글자가 된다. …중략… 문자열 뷰로 문자열에 접근하려면 characters 속성을 사용하면 된다. 문자열 뷰에 접근하면 내부적으로는 characterView라는 새로운 구조체를 만드록 문자열 코어를 복사해서 문자 단위로 접근할 수 있도록 확장한 함수들을 제공한다.
유니코드 스칼라 뷰(UnicodeScalarView)
표준 라이브러리에는 StringUnicodeScalarView.swift 파일에 구현되어 있고, 유니코드 표준에서 사용하는 21비트 코드로 구성된 UnicodeScalar 값(CodePoint 값)에 접근할 수 있도록 도와준다. 마찬가지로 내부적으로 UnicodeScalarView 구조체 내부에 새로운 문자열 코어를 복사해서 접근가능하도록 도와준다. 문자열에서 접근할 때는 unicodeScalars 속성을 사용하면 된다.
UTF8View와 UTF16View
표준 라이브러리에는 각각 StringUTF8.swift 파일과 StringUTF16.swift 파일에 구현되어 있다. 유니코드 표준에서 8비트 코드 유닛을 지원하는 UTF8 인코딩과 16비트 코드 유닛을 지원하는 UTF16 문자에 대한 컬렉션 뷰를 제공한다.
9.4.4 요약
스위프트 문자열은 NSString과 호환성을 유지하지만 동일한 구현체는 아니다. 유니코드 기반으로 의미 있는 값을 유지하기 위한 최적화된 코드가 제공된다. 문자열 뷰 형태로 컬렉션 함수들을 제공하는 것은 메모리 효율성과 편리함 성능 특성의 사이에서 적절한 타협점이다.
…중략…
9.5.1 불안전한 포인터
UnsafePointer와 UnsafeMutablePointer는 메모리 주소를 다루기 위해 구조체 타입으로 포장한 제네릭 포인터 타입이다. 자동으로 메모리를 관리해주지 않기 때문에 불안전한 포인터 (UnsafePointer) 타입으로 메모리 관리를 해줘야만 한다. alloc() 함수로 메모리를 할당하고 dealloc() 함수로 할당한 메모리를 해제할 수 있다. memory 변수는 alloc() 함수 호출 전에 할당되지 않은 상태가 있고, alloc() 함수 호출 이후 initialize() 함수를 호출하지 않은 초기화 상태가 있다. initialize() 함수를 호출해야만 초기화 상태가 된다.
UnsafePointer와 비슷하게 포인터를 다루는 타입에 OpaquePointer가 있다. OpaquePointer는 스위프트 포인터 타입으로 다룰 수 없는 C 언어 포인터 호환성을 유지할 때 사용한다. 어떤 타입은 OpaquePointer를 해시 값이라고 가정하고 비교하기도 한다. 내부에서 메모리 주소를 값으로 사용하기 때문에 동일한 메모리 주소일 경우만 같다고 판단한다.
9.5.2 옵셔널
옵셔널 타입은 다음 코드에서 볼 수 있듯이 열거 타입으로 none값과 some(wrapped) 값을 가진다. 옵셔널로 선언한 타입에 값이 없으면 none 값이 돼서, 옵셔널 변수의 값이 없다는 것을 읨한다. 반면에 값이 있으면 none 값이 돼서, 옵셔널 변수의 값이 없다는 것을 의미한다. 반면에 값이 있으면 some 상태가 되면서 열거 타입 내부에 값을 저장한다. 이때 저장되는 Wrapped 타입은 제네릭 타입으로 어떤 타입이라도 들어갈 수 있다.
9.5.3 옵셔널 강제 제거
강제로 옵셔널 상태를 제거하고, nil 값이라더도 상관없이 값을 그대로 사용하기 위한 타입이다. 아래 두 표현식은 동일하게 옵셔널 강제 제거(Implicitly UnwrappedOptional) 타입으로 선언하는 방식이다.
var unknownString1: String!
var unknownString2: ImplicitlyUnwrappedOptional<String>
다음 처럼 스위프트 내부에서 옵셔널 강제 제거 타입은 옵셔널 타입과 동일하게 처리한다. 다만 강제 제거 타입이라는 것을 알고 있으면서 옵셔널을 제거하고 값에 접근할 수 있도록 도와준다.
public enum ImplicitlyUnwrappedOptional<Wrapped>: _Reflectable, NilLiteralConvertible {
case none
case some(Wrapped)
public init(_ some: Wrapped) { self = .some(some) }
public init(_ v: Wrapped?) { self = .some(v!) }
public init(nilLiteral: ()) {
self = .none
}
}
// 이하 생략
9.5.4 슬라이스
슬라이스 타입은 컬렉션 타입의 내부 요소들 일부에 접근하는 뷰를 제공하는 구조체 타입이다. 슬라이스 타입은 다음 처럼 컬렉션 저장소에서 참조하는 시작 지점과 끝 지점을 제공할 뿐이다. 컬렉션에 슬라이스 참조 범위를 지정하면 슬라이스로 나눠서 접근이 가능하다.
…중략…
슬라이스는 컬렉션 데이터 요소를 복사해서 별도로 저장하지 않는다. 그래서 슬라이스를 생성하는 것 자체는 O(1) 성능 특성을 가진다.
9.5.5 시퀀스
시퀀스 타입은 for..in 반복문에서 내부 요소에 순차적으로 접근하기 위한 프로토콜 타입이다. 함수 중심 언어에서 제공하는 map, filter 함수들을 내부 반복자 라고 부르고, 반복문에서 탐색하는 방식을 외부 반복자라고 부르기도 한다. 시퀀스 타입은 반복문에서 컨테이너를 탐색하는 반복 패턴(iteractor pattern)을 구현하는 타입이다. 다시 말해서 시퀀스 프로토콜을 구현하면 for…in 반복문에서 탐색할 수 있는 타입이 된다. 시퀀스 프로토콜은 저장된 값들을 순서대로 접근하기 위한 일반적인 여러 가지 동작들을 제공하기 때문에 모든 요소를 탐색하지 않아도 원하는 요소를 찾기가 수월하다.
시퀀스 타입으로 내부 요소를 탐색할 때 순서대로 접근한다고 해서 항상 같은 순서가 보장되는 것은 아니다. 시퀀스를 마치 배열 색인 처럼 사용해서는 안 된다.
이터레이터프로토콜
시퀀스 타입은 반복자를 구현하기 위해서 반복 전용 프로토콜을 사용한다. 스위프트 3 이전까지는 제네레이터라고 불렀지만, 이후부터는 이름을 이터레이터로 바꿔서 사용한다. 시퀀스 타입에 있는 makeIterator() 함수로 이터레이터를 생성하고 nil 값이 될때까지 next() 함수를 반복 호출해서 탐색하는 방식이다. …중략…
시퀀스 타입 성능 특성
시퀀스 타입은 이터레이터를 제공하는데, 복잡도가 O(1)이어야만 한다. 그 외에는 성능 측면에서 다른 제약 사항이 없다. 대부분 공간 탐색과 관련해서는 O(N) 특성을 가지는 경우가 많다. 예를 들어 시퀀스 내부 전체 개수를 예측하는 underestimatedCount() 함수는 O(N) 특성을 갖고 있다.
9.5.6 AnyObject
모든 클래스 타입을 지칭하는 프로토콜 타입이다. 애플 플랫폼에서 오브젝티브-C 런타임과 연결하느냐 아니냐에 따라서 @objc 속성을 갖기도 하고 갖지 않기도 한다.
#if _runtime(_Objc)
@objc
public protocol AnyObject: class {}
#else
public protocol AnyObject: class {}
#endif
AnyObject와 관련이 많은 타입은 Any 타입이다. Any 타입은 스위프트 타입 시스템에서 의미 있는 타입이 아니라서 타입 검사를 하지 않기 때문에 주의해서 사용해야 한다. 표준 라이브러리에서 Any 타입은
public typealias Any = protocol<>
로 선언되어 있다. 선언한 코드를 그대로 보면 이름 없는 프로토콜의 확장일 뿐이다. 컴파일러는 Any 타입으로 선언하면, 특정 타입과 비교하는 것이 아니라 모든 타입을 확장해버리는 용도로 사용할 뿐이다. 그래서 Where절과 함께 타입 비교에서 사용하더라도 타입 시스템에서 비교되지 않는다.
AnyObject 타입은 스위프트3 이전까지 오브젝티브-C 객체를 담기 위한 의미 있는 타입으로 사용했다. 이후부터는 오브젝티브-C 객체의 일반적인 id 타입을 스위프트로 가져올 때 AnyObject 대신 Any 타입을 사용한다. 제네릭 타입에 대한 연결은 AnyObject를 그대로 사용하며, 그 외 경우 제네릭 타입을 선언하지 않은 NSArray 배열 객체는 스위프트에서 [Any] 타입으로 연결한다.
9.6 스위프트 런타임
일반적으로 런타임은 말그대로 실행 중에 판단하거나 처리하기 위한 관련 동작들을 구현해놓은 함수나 코드들을 의미한다. …중략… 스위프트 런타임API들은 public인 경우는 C함수를 제공하며, 내부에서는 C++로 만든 swift 클래스 멤버 함수로 만들어진 것도 있다.
…중략…
9.7 스위프트 파운데이션
스위프트 2에서 제공하던 파운데이션 프레임워크는 오브젝티브-C로 만들어진 파운데이션을 연결해서 사용하도록 그대로 제공했었다. 스위프트 3부터는 열거 타입이나 구조체 타입 같은 스위프트 표준 라이브러리 타입을 활용해서 스위프트 다운 파운데이션 API를 제공한다. NSCharacterSet, NSDate, NSNotification, NSURL 같은 클래스는 구조체 타입으로 새로 만들어졌다. 안전한 타입 시스템을 위해서 글로벌 상수도 타입 내부 상수로 선언해서 사용하기 편리하다.
…중략…
Uploaded by N2T