You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
스위프트에는 두 가지 종류의 특성(attributes)이 있는데
선언에 적용하는 것과 타입에 적용하는 것이 있다
특성은 선언이나 타입에 대한 추가 정보를 제공한다
예를 들어, 함수 선언이 discardableResult 특성이면,
함수는 값을 반환하지만 반환 값을 사용하지 않는다고 컴파일러가 경고를 생성하지는 않는게 좋다는 걸 지시한다
특성을 지정하려면 @ 기호 뒤에 특성 이름과 특성이 받아들이는 어떤 인자를 작성하면 된다
@attribute name @attribute name(attribute arguments)
일부 선언 특성은 특성에 대한 자세한 정보와 특성이 특정 선언에 적용되는 방법을 지정하는 인수를 허용한다
이러한 특성 인수(attribute arguments)는 괄호 안에 포함되며,
해당 형식은 해당 인수가 속한 특성에 의해 정의된다
선언 특성
선언 특성은 선언에만 적용할 수 있다
1) available
해당 특성을 적용하면 특정 스위프트 언어 버전이나 특정 플랫폼 및 운영 체제 버전과 관련된 선언의 생명 주기를 지시한다
available 특성은 항상 쉼표로 구분한 둘 이상의 특성 인수 목록과 함께 나타난다
이러한 인수는 다음의 플랫폼이나 언어 이름 중 하나로 시작한다
iOS
iOSApplicationExtension
macOS
macOSApplicationExtension
macCatalyst
macCatalystApplicationExtension
watchOS
watchOSApplicationExtension
tvOS
tvOSApplicationExtension
swift
별표(*)를 사용하여 위에 나열한 모든 플랫폼 이름에 대한 선언의 사용 가능성(availability)을 나타낼 수도 있다
스위프트 버전 번호로 사용 가능성을 나타낸 available 특성엔 별표를 사용할 수 없다
나머지 인수는 어떤 순서로든 나타날 수 있으며
선언 생명 주기에 대한 추가 정보를 지정할 수 있는데, 이는 중요한 이정표(milestones)를 포함한다
unavailable 인수는 지정한 플랫폼에선 선언의 사용이 불가능하다는 걸 나타낸다
스위프트 버전 사용 가능성을 지정할 땐 해당 인수를 사용할 수 없다
introduced 인수는 지정한 플랫폼이나 언어 버전이 선언을 도입한 첫번째 버전이라는 걸 지시한다
버전 번호(version number)는 1개에서 3개까지의 양수를 마침표로 구분하여 구성한다
형식은 다음과 같다
introduced: version number
deprecated 인자는 지정한 플랫폼이나 언어 버전이 선언을 폐기할 예정인 첫 번째 버전이라는 걸 나타낸다
옵션인 버전 번호(version number)는 1개에서 3개까지의 양수를 마침표로 구분하여 구성한다
버전 번호를 생략한다면 언제 폐기가 일어나는지에 대해선 어떤 정보도 주지 않고, 현재부터 선언이 폐기 예정이라는 걸 나타내게 된다
버전 번호를 생략한다면 콜론(:) 마저 생략한다
형식은 다음과 같다
deprecated: version number
obsoleted 인자는 지정한 플랫폼이나 언어 버전이 선언을 폐기한 첫 번째 버전이라는 걸 나타낸다
폐기한 선언일 땐 지정한 플랫폼이나 언어에서 제거하여 더 이상 사용할 수 없다
버전 번호(version number)는 1개에서 3개까지의 양수를 마침표로 구분하여 구성한다
형식은 다음과 같다
obsoleted: version number
message 인자는 폐기 예정이거나 폐기한 선언의 사용에 대한 경고 또는 에러를 내보낼 때 컴파일러가 보여줄 메시지 글을 제공한다
메시지(message)는 문자열 글자값으로 구성된다
형식은 다음과 같다
message: message
renamed 인자는 이름을 바꾼 선언의 새 이름을 지시하는 메시지 글을 제공한다
컴파일러는 이름 바꾼 선언의 사용에 대한 에러를 내보낼 때 새 이름을 보여주게 된다
새로운 이름(new name)은 문자열 글자값으로 구성된다
형식은 다음과 같다
renamed: new name
밑에 예시에서 보는 것처럼 rename 과 unavailable 인수가 있는 available 특성을 타입 별명 선언에 적용하면,
프레임워크이나 라이브러리 릴리스 사이에 바뀐 선언의 이름을 나타낼 수 있다
해당 조합으로 인해 선언의 이름이 바뀌었다는 컴파일 에러를 발생시킨다
단일 선언에 여러 개의 available 특성을 적용하여
서로 다른 플랫폼과 서로 다른 스위프트 버전에 대한 선언의 사용 가능 여부를 지정할 수 있다
특성이 지정한 플랫폼이나 언어 버전이 현재 대상과 일치하지 않으면 available 특성을 적용한 선언이 무시된다
여러 개의 available 특성을 사용할 경우, 효과적인 사용 가능성은 플랫폼과 스위프트 사용 가능성을 조합하여 사용할 수 있다
여러 개의 available 특성을 사용할 경우,
플랫폼 사용 가능성만 여러 개 적용하거나 스위프트 버전 사용 가능성만 여러 개 적용한 건 별 의미가 없다
available 특성이 플랫폼이나 언어 이름 인자 외엔 introduced 인자만 지정한다면, 다음의 짧게 줄인 구문을 대신 사용할 수 있다
@available(platform name version number, *) @available(swift version number)
available 특성의 짧게 줄인 구문은 여러 플랫폼의 사용 가능성을 간결하게 표현한다
두 형식의 기능이 같긴 하지만, 가능할 때마다 짧게 줄인 형식을 쓰는게 더 좋다
@available(iOS 10.0, macOS 10.12,*)classMyClass{
// class definition
}
스위프트 버전 번호를 써서 사용 가능성을 지정한 available 특성은
선언의 플랫폼 사용 가능성을 추가로 지정할 수 없다
대신, 별도의 available 특성으로 스위프트 버전 사용 가능성과 하나 이상의 플랫폼 사용 가능성을 지정하여 사용할 수 있다
해당 특성을 함수나 메서드 선언에 적용하면
값을 반환하는 함수나 메서드를 호출하고 결과를 사용하지 않을 때의 컴파일러 경고를 억제할 수 있다
3) dynamicCallable
해당 특성을 클래스나, 구조체, 열거체, 프로토콜에 적용하면
해당 타입의 인스턴스를 호출 가능한 함수로 취급한다
타입은 반드시 dynamicallyCall(withArguments:) 메서드나 dynamicallyCall(withKeywordArguments:) 메서드, 또는 둘 다를 구현해야 한다
동적으로 호출 가능한 타입의 인스턴스는 마치 원하는 수 인수를 사용하는 함수처럼 호출할 수 있다
@dynamicCallablestructTelephoneExchange{func dynamicallyCall(withArguments phoneNumber:[Int]){
if phoneNumber ==[4,1,1]{print("Get Swift help on forums.swift.org")}else{print("Unrecognized number")}}}letdial=TelephoneExchange()
// Use a dynamic method call.
dial(4,1,1)
// Prints "Get Swift help on forums.swift.org"
dial(8,6,7,5,3,0,9)
// Prints "Unrecognized number"
// Call the underlying method directly.
dial.dynamicallyCall(withArguments:[4,1,1])
dynamicallyCall(withArguments:) 메서드 선언은
반드시 ExpressibleByArrayLiteral 프로토콜을 준수한 단일 매개 변수(예제의 [Int])를 가져야 한다
반환 타입은 어떤 타입이든 가능하다
dynamicallyCall(withKeywordArguments:) 메서드를 구현한다면 동적 메서드 호출에 라벨을 포함시킬 수 있다
@dynamicCallablestructRepeater{func dynamicallyCall(withKeywordArguments pairs:KeyValuePairs<String,Int>)->String{return pairs
.map{ label, count inrepeatElement(label, count: count).joined(separator:"")}.joined(separator:"\n")}}letrepeatLabels=Repeater()print(repeatLabels(a:1, b:2, c:3, b:2, a:1))
// a
// b b
// c c c
// b b
// a
dynamicallyCall 메서드를 둘 다 구현하면,
메서드 호출에 키워드 인수를 포함할 때 dynamicallyCall(withKeywordArguments:)를 호출한다
다른 모든 경우엔, dynamicallyCall(withArguments:)를 호출하게 된다
인수와 반환 값 타입이 dynamicallyCall 메서드 구현에서 지정한 것과 일치한 동적 호출 가능 인스턴스만 호출할 수 있다
다음 예제 호출은 컴파일이 안되는데 KeyValuePairs<String, String> 을 취하는 dynamicallyCall(withArguments:) 구현이 없기 때문이다
repeatLabels(a:"four")
// Error
4) dynamicMemberLookup
해당 특성을 클래스, 구조체, 열거체, 프로토콜에 적용하면 멤버를 런타임에 이름으로 찾아볼 수 있게 한다
타입은 반드시 subscript(dynamicMemberLookup:)를 구현해야 한다
명시적 멤버 표현식에서 이름 있는 멤버에 해당하는 선언이 없는 경우,
표현식이 타입의 subscript(dynamicMemberLookup:) 서브 스크립트에 대한 호출이라고 이해되며,
멤버에 대한 정보를 인수로 전달한다
서브 스크립트는 키 경로나 멤버 이름인 매개 변수를 받아들일 수 있으며
두 서브 스크립트를 모두 구현하면, 키 경로 인수를 가진 서브 스크립트를 사용하게 된다
해당 특성을 구조체나 열거체 선언에 적용하면 타입에 가할 수 있는 변화의 종류를 제약한다
특성은 library evolution mode로 컴파일할 때만 허용된다
미래 버전의 라이브러리가 열거체 case나 구조체의 저장 인스턴스 프로퍼티를 추가, 삭제, 또는 재배치하는 걸로 선언을 바꿀 수 없다
동결 아닌(nonfrozen) 타입은 이러한 변화를 허용하지만,
동결 (frozen) 타입이 이러면 ABI 호환성을 깨뜨리게 된다
‘라이브러리 진화 모드(library evolution mode)’ 는 스위프트 바이너리 프레임워크을 생성할 때 사용할 수 있는 옵션이다
앞으로 라이브러리가 지속적으로 바뀔 가능성이 있을 때 선택하는 옵션이다
컴파일러가 라이브러리 진화 모드가 아닐 땐, 모든 구조체 및 열거체가 암시적 동결이라 해당 특성을 무시하게 된다
‘ABI 호환성 (Application Binary Interface Compatibility)’ 이란 앱과 앱에서 사용한 라이브러리가
바이너리 수준에서 호환성을 가지는 걸 말한다
ABI 호환성이 있으면 이미 컴파일되어 있는 라이브러리로 앱에서 그대로 사용할 수 있다
이렇게 ABI 호환성을 가질려면 기존에 컴파일해둔 라이브러리가 미래에 바뀌어선 안된다
라이브러리 진화 모드에서 동결 아닌 구조체 및 열거체 멤버와 상호 작용하는 코드는
미래 버전의 라이브러리가 해당 타입의 일부 멤버를
추가, 삭제, 또는 재배치하더라도 재-컴파일 없이 작업을 계속 허용하는 방식으로 컴파일한다
컴파일러는 런타임에 정보 찾아보기 및 간접 계층 추가하기 같은 기술을 써서 이를 가능하게 한다
구조체나 열거체를 동결로 만드는 건 이런 유연함을 포기하면서 성능을 얻는 것인데
미래 버전의 라이브러리가 타입을 바꾸는 데는 제한이 있지만,
타입 멤버와 상호 작용하는 코드를 컴파일러가 추가로 최적화할 수 있다
동결 타입과 동결 구조체의 저장 프로퍼티 타입, 및 동결 열거체 case의 연관 값은
반드시 공용(public)이거나 ‘usableFromInline 특성을 표시해야 한다
동결 구조체의 프로퍼티엔 프로퍼티 옵저버가 있을 수 없으며,
저장 인스턴스 프로퍼티에 초기 값을 제공하는 표현식은 뒤에서 알아볼 inlinable에서 논의할 것처럼
반드시 인라인 가능 함수와 똑같은 제약 사항을 따라야 한다
명령 줄에서 라이브러리 진화 모드를 켜려면, -enable-library-evolution 옵션을 스위프트 컴파일러로 전달한다
엑스코드에서 켜려면, Xcode Help에서 설명한, “배포용 라이브러리 제작(BUILD_LIBRARY_FOR_DISTRIBUTION)” 배포 설정을 Yes로 설정한다
Switching Over Future Enumeration Cases에서 논의한 것처럼,
동결 열거체에 대한 switch 문은 default case 를 요구하지 않는다
동결 열거체 전환 때 default 나 @unknown default case를 포함하면 경고를 만들어 내는데
해당 코드는 절대 실행되지 않기 때문이다
6) 점검 가능한 GameplayKit(GKInspectable)
해당 특성을 적용하면 사용자가 정의한 GameplayKit 컴포넌트 프로퍼티를 SpriteKit 편집기 UI 로 드러낸다
해당 특성을 적용하면 암시적으로 objc 특성도 적용한다
7) inlinable
해당 특성을 함수, 메서드, 계산 프로퍼티, 서브 스크립트, 편리한 이니셜라이저, 디이니셜라이저 선언에 적용하면
그 선언 구현을 모듈의 공용 인터페이스 일부분으로 표시한다
컴파일러는 인라인 가능한 기호의 호출을 기호 구현부의 복사본으로 호출한 쪽에서 대체할 수 있다
인라인 가능 코드는 어떤 모듈에서 선언한 public 기호와도 상호 작용할 수 있으며,
동일 모듈에서 usableFromInline 특성을 표시하여 선언한 internal 기호와도 상호 작용할 수 있다
인라인 가능 코드는 private 이나 fileprivate 기호와는 상호 작용할 수 없다
해당 특성은 함수 안에 중첩한 선언이나 fileprivate 및 private 선언에 적용할 순 없다
인라인 가능 함수 안에서 정의한 함수와 클로저는 해당 특성을 표시할 수 없을지라도 암시적으로 인라인 가능하다
8) main
해당 특성을 구조체, 클래스, 열거체 선언에 적용하면
이게 프로그램 흐름의 최상단 진입점(top-level entry point)을 포함한다는 걸 나타낸다
타입은 반드시 어떤 인수도 취하지 않고 Void 를 반환하는 main 타입 함수를 제공해야 한다
예를 들면 다음과 같다
@mainstructMyTopLevel{staticfunc main(){
// Top-level code goes here
}}
main 특성의 요구 사항을 설명하는 또 다른 방법은
해당 특성을 쓴 타입은 반드시 다음의 가상(hypothetical) 프로토콜을 준수한 타입과 동일한 요구 사항을 충족해야 한다는 것이다
protocolProvidesMain{staticfunc main()throws}
Top-Level Code에서 논한 것처럼 실행 파일을 만들려고 컴파일하는 스위프트 코드는
최대 한 개의 최상단 진입점을 담을 수 있다
9) nonobjc
해당 특성을 메서드, 프로퍼티, 서브 스크립트, 이니셜라이저 선언에 적용하면 암시적 objc 특성을 억제한다
nonobjc 특성은 컴파일러에게 오브젝티브-C 로 나타내는게 가능한 선언일지라도,
오브젝티브-C 코드에서 사용 불가능하게 하라고 말하는 것과 같다
익스텐션에 해당 속성을 적용하면 objc 속성으로 명시적으로 표시되지 않은 해당 익스텐션의 모든 구성원에 적용하는 것과 같은 효과가 있다
nonobjc 특성을 사용하여 클래스에서 objc 특성을 표시한 연동 메서드(bridging methods)의 순환성을 해결하고,
클래스에서 objc 특성을 표시한 메서드와 이니셜라이저의 중복 정의(overload)도 가능하다
nonobjc 특성을 표시한 메서드는 objc 특성을 표시한 메서드를 재정의(override)할 수 없다
하지만, objc 특성을 표시한 메서드는 nonobjc 특성을 표시한 메서드를 재정의(override)할 수 있다
이와 비슷하게, nonobjc 특성을 표시한 메서드는 objc 특성을 표시한 메서드에 대한 프로토콜 요구 사항을 만족할 수 없다
10) NSApplicationMain
해당 특성을 클래스에 적용하면
자신이 앱의 응용 프로그램 위임자(the application delegate)라는 걸 나타낸다
해당 특성을 사용하는 건 NSApplicationMain(::) 함수를 호출하는 것과 같다
해당 특성을 사용하지 않는다면
다음 처럼 최상단에서 NSApplicationMain(::) 함수를 호출하는 코드가 있는 main.swift 파일을 제공한다
Top-Level Code에서 이야기한 것처럼, 실행 파일을 만들려고 컴파일하는 스위프트 코드는
최대 한 개의 최상단 진입점을 가질 수 있다
11) NSCopying
해당 특성은 클래스의 저장 변수 프로퍼티에 적용한다
해당 특성은 프로퍼티의 세터가 프로퍼티 그 자체의 값 대신
copyWithZone(_:) 메서드로 반환한 프로퍼티 값의 복사본(copy)과 합성되도록 한다
프로퍼티 타입은 반드시 NSCopying 프로토콜을 준수해야 한다
NSCopying 특성은 오브젝티브-C 의 copy 프로퍼티 특성과 비슷하게 동작한다
12) NSManaged
해당 특성을 NSManagedObject를 상속한 클래스의 인스턴스 메서드 또는 저장 변수 프로퍼티에 적용하면,
결합 개체 설명을 기초로 코어 데이터(Core Data)가 자신의 구현을 런타임에 동적으로 제공한다는 걸 나타낸다
NSManaged 특성을 표시한 프로퍼티면 코어 데이터가 런타임에 저장 공간도 제공한다
해당 특성을 적용하면 암시적으로 objc 특성도 적용된다
‘결합 개체 설명(associated entity description)’은 엑스코드 안의 *.xcdatamodeld 파일에서 만드는
데이터베이스 스키마(database schema)를 말한다
코어 데이터의 개체(entity)는 다른 데이터베이스의 테이블(table)을 의미한다
13) objc
해당 특성은 오브젝티브-C로 표현할 수 있는 어떤 선언에든 적용할 수 있다
예를 들어, 중첩 아닌 클래스, 프로토콜,
정수 원시-값 타입으로 제약한 비-제네릭 열거체,
게터와 세터를 포함한 클래스 프로퍼티 및 메서드,
프로토콜과 프로토콜의 옵셔널 멤버, 이니셜라이저, 서브 스크립트가 있다
objc 특성은 컴파일러에게 오브젝티브-C 코드에서 선언이 사용 가능하다고 말하는 것과 같다
해당 특성을 익스텐션에 적용하는 건
그 익스텐션에서 nonobjc 특성을 명시하지 않은 모든 멤버에 이를 적용한 것과 똑같은 효과가 있다
컴파일러는 오브젝티브-C로 정의한 어떤 클래스의 하위 클래스에든 objc 특성을 암시적으로 추가한다
하지만, 하위 클래스는 반드시 제네릭이 아니어야 하며,
반드시 어떤 제네릭 클래스도 상속하지 않아야 한다
이러한 기준에 부합하는 하위 클래스에 objc 특성을 명시적으로 추가하면,
아래에서 설명한 대로 자신의 오브젝티브-C 이름을 지정할 수 있다
objc 특성을 표시한 프로토콜은 해당 특성을 표시하지 않은 프로토콜을 상속할 수 없다
objc 속성은 다음과 같은 경우에도 암시적으로 추가된다
하위 클래스에서 재정의한 선언이 상위 클래스에서 objc 특성을 가진 경우
objc 특성을 가진 프로토콜의 요구 사항을 만족하는 선언인 경우
IBAction, IBSegueAction, IBOutlet, IBDesignable, IBInspectable, NSManaged, GKInspectable 특성을 가진 선언인 경우
열거체에 objc 특성을 적용하면 각 열거체 case를 열거체 이름과 case 이름을 이어붙인 형태로 오브젝티드-C 코드에 노출된다
case 이름의 첫 글자는 대문자로 표시한다
ex. 스위프트 Planet 열거체 안의 venus 라는 이름의 case는
PlanetVenus라는 이름의 case로 오브젝티브-C에 노출된다
objc 특성은 식별자로 구성한 단일 특성 인수를 옵셔널하게 받는다
식별자는 objc 특성을 적용한 개체가 오브젝티브-C 로 노출될 이름을 지정한다
해당 인수를 사용하여 클래스, 열거체, 열거체 case, 프로토콜, 메서드, 게터, 세터, 이니셜라이저의 이름을 지을 수 있다
클래스, 프로토콜, 열거체의 오브젝티브-C 이름을 지정한다면, Programming with Objective-C에 있는 Conventions에서 설명한 것처럼,
이름에 세-글자짜리 접두사를 포함시킨다
아래 예제는 ExampleClass에 있는 enabled 프로퍼티의 게터를 프로퍼티 자체의 이름이 아닌 isEnabled로 오브젝티브-C 코드에 그대로 노출한다
classExampleClass:NSObject{@objcvarenabled:Bool{@objc(isEnabled)get{
// Return the appropriate value
}}}
objc 특성의 인수는 해당 선언의 런타임 라이브러리의 이름을 바꿀 수도 있다
런타임 이름은 NSClassFromString 같이 오브젝티브-C 런타임과 상호 작용하는 함수를 호출할 때와
앱의 Info.plist 파일에서 클래스 이름을 지정할 때 사용한다
인수를 전달해서 이름을 정하면, 그 이름을 오브젝티브-C 코드 안의 이름과 런타임 이름으로 사용한다
인수를 생략하면 오브젝티브-C 코드 안에서 사용할 이름이 스위프트 코드 안의 이름과 일치하며,
런타임 이름은 보통의 스위프트 컴파일러 이름 뭉개기 규칙을 따른다
‘이름 뭉개기 (name mangling)’ 는 현대 프로그래밍에서 함수와 같은 각각의 프로그램 개체에 유일한 이름을 주기 위한 기법이다
타입 자체의 이름에 추가 정보를 덧대어서 유일한 이름을 만드는데, 해당 과정에서 이름이 뭉개지기 때문에 이렇게 부르는 것으로 추측된다
14) objcMembers
해당 특성을 클래스 선언에 적용하면,
클래스, 익스텐션, 하위 클래스, 하위 클래스의 모든 익스텐션 안에서
오브젝티브-C 와 호환 가능한 모든 멤버에 암시적으로 objc 특성을 적용한다
대부분의 코드는 objc 특성을 대신 사용하여 필요한 선언만 드러내는게 좋다
수 많은 선언을 노출해야 하는 경우, objc 속성이 있는 익스텐션에서 선언을 그룹화할 수 있다
objcMembers 특성은 오브젝티브-C 런타임의 내부 검사 기능(introspection facilities)을 아주 많이 쓰는 라이브러리에 편리하다
오브젝티브-C 기능을 아주 많이 쓰면 호환성을 위해 objc를 남발하게 될텐데,
이 때의 비효율성을 줄이기 위해 objcMembers 특성을 사용한다고 이해할 수 있다
불필요할 때 objc 특성을 적용하면 바이너리 크기가 증가하고 성능에 불리한 영향을 줄 수 있다
15) propertyWrapper
해당 특성을 클래스, 구조체, 열거체 선언에 적용하면 해당 타입을 propertyWrapper로 사용한다
해당 특성을 타입에 적용할 땐, 타입과 똑같은 이름을 가진 커스텀 특성을 생성한다
새로운 특성을 클래스, 구조체, 열거체 프로퍼티에 적용하면, 래퍼 타입(wrapper type)의 인스턴스를 통해 프로퍼티로 액세스를 래핑한다
변수에 대한 액세스를 동일한 방식으로 래핑하려면, 로컬 저장 변수 선언에 특성을 적용한다
계산 변수, 전역 변수, 상수는 propertyWrapper을 사용할 수 없다
래퍼(wrapper)는 반드시 wrappedValue 인스턴스 프로퍼티를 정의해야 한다
프로퍼티의 래핑된 값(wrapped value)은 해당 프로퍼티의 게터(getter)와 세터(setter)가 표시하는 값이다
wrappedValue는 대부분의 경우 계산된 값(computed value)이지만, 대신 저장된 값(stored value)일 수도 있다
래퍼은 자신의 래퍼된 값에 필요한 기본 저장 공간를 정의하고 관리한다
컴파일러는 래핑된 프로퍼티 이름 앞에 접두사로 밑줄(_)을 붙여서
래퍼 타입 인스턴스에 대한 저장 공간을 만들어 합성한다
예를 들어, someProperty의 래퍼는 _someProperty라고 저장된다
래퍼를 위해 합성된 저장 공간은 private 접근 제어 수준을 가진다
propertyWrapper을 가진 프로퍼티은 willSet 과 didSet 블럭을 포함할 수 있지만,
컴파일러에 의해 합성된 get 이나 set 블럭을 재정의할 순 없다
스위프트는 propertyWrapper의 초기화를 위해 두 가지 형식의 수월한 구문을 제공한다
래핑된 값의 정의에서 할당 구문을 사용하면
할당 오른쪽 표현식을 propertyWrapper 이니셜라이저의 wrappedValue 매개 변수에 인수로 전달할 수 있다
프로퍼티에 특성을 적용할 때 인수를 제공할 수도 있는데,
해당 인수들은 propertyWrapper 이니셜라이저로 전달된다
예를 들어, 아래 코드에서, SomeStruct 는 SomeWrapper 에서 정의한 각각의 이니셜라이저를 호출한다
래핑된 프로퍼티의 투영된 값(projected value)은 두 번째 값으로
이를 사용하면 propertyWrapper가 추가 기능을 표시할 수 있다
propertyWrapper 타입의 작성자는 투영된 값의 의미를 결정하고
투영된 값이 노출하는 인터페이스의 정의를 책임진다
propertyWrapper에서 값을 투영하려면, 래퍼 타입에 projectedValue 인스턴스 프로퍼티를 정의한다
컴파일러는 래핑된 프로퍼티 이름 앞에 접두사로 달러 기호($)를 둠으로써
투영된 값의 식별자를 만들어 통합한다
예를 들어, someProperty의 투영된 값은 $someProperty다
투영된 값의 접근 제어 수준은 원본 래핑된 프로퍼티와 똑같다
@propertyWrapperstructWrapperWithProjection{varwrappedValue:IntvarprojectedValue:SomeProjection{returnSomeProjection(wrapper:self)}}structSomeProjection{varwrapper:WrapperWithProjection}structSomeStruct{@WrapperWithProjectionvarx=123}lets=SomeStruct()
s.x // Int value
s.$x // SomeProjection value
s.$x.wrapper // WrapperWithProjection value
16) resultBuilder
해당 특성을 클래스, 구조체, 열거체에 적용하면 그 타입을 resultBuilder로 사용한다
결과 제작자(result builder)는 중첩 자료 구조를 한 걸음씩 단계별로 제작하는 타입이다
resultBuilder를 사용하여 자연스러운, 선언형 방식으로, 중첩 자료 구조를 생성하기 위한 특정-분야 언어(DSL)을 구현할 수 있다
resultBuilder 특성의 사용법에 대한 예제는, Result Builders 페이지 부분을 보도록 하자
17) Result-Building Methods
resultBuilder는 밑에서 설명할 static 메서드를 구현한다
resultBuilder의 모든 기능은 static 메서드를 통해 드러나기 때문에,
절대 해당 타입의 인스턴스를 초기화하지 않는다
buildBlock(_:) 메서드는 필수이며 DSL에 추가 기능을 부여하는 다른 메서드는 옵션이다
resultBuilder 타입의 선언은 어떤 프로토콜 준수도 실제로 포함할 필요가 없다
이 역시 인스턴스를 만들어서 쓰지 않기에 프로토콜의 요구 사항의 구현이라는 개념이 의미가 없다
static 메서드 설명엔 세 개의 타입을 플레이스 홀더로 사용한다
Expression은 resultBuilder의 input 타입에 대한 플레이스 홀더이고,
Component는 부분적인 result 타입에 대한 플레이스 홀더이며,
FinalResult는 resultBuilder가 최종적으로 만들어 내는 result 타입에 대한 플레이스 홀더다
이러한 타입들을 자신의 resultBuilder에서 사용하는 실제 타입으로 바꿀 수 있다
Result-Building 메서드에서 Expression 이나 FinalResult 타입을 지정하지 않으면 기본적으로 Component와 동일하게 구성된다
Result-Building 메서드는 다음과 같다
static func buildBlock(_ components: Compnent...) -> Component
부분 결과 배열을 단일 부분 결과로 조합한다
resultBuilder는 반드시 이 메서드를 구현해야 한다
static func buildOptional(_ component: Compnent?) -> Component
nil 일 수 있는 부분 결과를 기반으로 부분 결과를 제작한다
이 메서드를 구현하면 else 절을 포함하지 않는 if 문을 지원한다
static func buildEither(first: Compnent) -> Component
어떠한 조건에 따라 값이 변하는 부분 결과를 제작한다
이 메서드와 buildEither(second:) 둘 다를 구현하면 switch 문과 else 절을 포함한 if 문을 지원한다
static func buildEither(second: Compnent) -> Component
어떠한 조건에 따라 값이 변하는 부분 결과를 제작한다
이 메서드와 buildEither(first:) 둘 다를 구현하면 switch 문과 else 절을 포함한 if 문을 지원한다
static func buildArray(_ components: [Compnent]) -> Component
부분 결과들의 배열로 부분 결과를 제작한다
이 메서드를 구현하면 for 반복분을 지원한다
static func buildExpression(_ expression: Expression) -> Component
표현식으로 부분 결과를 제작한다
이 메서드를 구현하면 표현식을 내부 타입으로 변환하는 등의 전처리 과정을 수행하거나
사용하는 쪽에 타입 추론을 위한 추가 정보를 제공한다
static func buildFinalResult(_ component: Compnent) -> FinalResult
부분 결과로 최종 결과를 제작한다
이 메서드는 부분 및 최종 결과에서 사용할 타입이 서로 다른 resultBuilder를 구현하거나,
결과 반환 전에 다른 후처리 과정을 수행하기 위해 구현할 수 있다
static func buildLimitedAvailablility(_ component: Compnent) -> Component
사용 가능성을 검사하는 컴파일러 제어문 밖으로 타입 정보를 전파하거나 지우는 부분 결과를 제작한다
이를 사용하면 조건 분기마다 다양하게 변하는 타입 정보를 지울 수 있다
예를 들어, 아래 코드는 정수 배열을 제작하는 단순한 resultBuilder를 정의한다
이 코드는 Component 와 Expression 을 타입 별명으로 정의하여, 아래 예제와 위의 메서드 목록을 더 쉽게 맞춰보게 한다
할당문은 표현식처럼 변환되지만, ()로 평가되는 것으로 이해된다
할당문의 경우 buildExpression(_:) 의 평가 결과를 사용한다
() 타입 인수를 사용하는 buildExpression(_:)를 중복 정의(overload)하면 지정된 할당을 특수하게 처리할 수 있다
사용 가능성 조건을 검사하는 분기문은 buildLimitedAvailablility(_:) 메서드에 대한 호출이 된다
이 변환은 buildEither(first:), buildEither(second:), buildOptional(_:) 호출로 변환하기 전에 발생한다
buildLimitedAvailablility(_:) 메서드를 사용하면 어떤 분기를 취할 지에 따라 변경되는 타입 정보를 지운다
ex. 아래의 buildEither(first:) 와 buildEither(second:) 메서드는 제네릭 타입을 사용하여 두 분기 모두에 대한 타입 정보를 캡쳐한다
@available(macOS 99,*)structFutureText:Drawable{varcontent:Stringinit(_ content:String){self.content = content }func draw()->String{return content }}@DrawingBuildervarbrokenDrawing:Drawable{
if #available(macOS 99,*){FutureText("Inside.future") // Problem
}else{Text("Inside.present")}}
// The type of brokenDrawing is Line<DrawEither<Line<FutureText>, Line<Text>>>
위의 코드에서 FutureText는 DrawOne 제네릭 타입 중 하나이기 때문에 brokenDrawing 타입의 일부로 나타난다
이로 인해 FutureText 타입이 명시적으로 사용되지 않는 경우에도
런타임에 해당 FutureText를 사용할 수 없으면 프로그램이 중단될 수 있다
이 문제를 해결하려면, buildLimitedAvailability(_:) 메서드를 구현하여 타입 정보를 지우면 된다
예를 들어, 아래 코드는 자신의 사용 가능성 검사에서 AnyDrawable 값을 작성한다
structAnyDrawable:Drawable{varcontent:Drawablefunc draw()->String{return content.draw()}}extensionDrawingBuilder{staticfunc buildLimitedAvailability(_ content:Drawable)->AnyDrawable{returnAnyDrawable(content: content)}}@DrawingBuildervartypeErasedDrawing:Drawable{
if #available(macOS 99,*){FutureText("Inside.future")}else{Text("Inside.present")}}
// The type of typeErasedDrawing is Line<DrawEither<AnyDrawable, Line<Text>>>
분기문은 buildEither(first:)와 buildEither(second:) 일련의 중첩 호출이 된다
구문의 조건과 case는 이진 트리의 잎 노드 (leaf nodes)에 대응하며,
구문은 뿌리 노드(root node)에서 해당 잎 노드로의 경로를 따라가는 중첩된 buildEither 메서드 호출이 된다
예를 들어, 3개의 case가 있는 스위치 문을 작성하는 경우, 컴파일러는 3개의 잎 노드가 있는 이진트리를 사용한다
마찬가지로 뿌리 노드에서 두 번째 case로 경로는 "second child" 다음에 "first child" 이기 때문에,
해당 case는 buildEither(first: buildEither(second: ...))와 같은 중첩 호출이 된다
다음 선언들은 서로 동일하다
letsomeNumber=19@ArrayBuildervarbuilderConditional:[Int]{
if someNumber <12{31}else if someNumber ==19{32}else{33}}varmanualConditional:[Int]
if someNumber <12{letpartialResult=ArrayBuilder.buildExpression(31)letouterPartialResult=ArrayBuilder.buildEither(first: partialResult)
manualConditional =ArrayBuilder.buildEither(first: outerPartialResult)}else if someNumber ==19{letpartialResult=ArrayBuilder.buildExpression(32)letouterPartialResult=ArrayBuilder.buildEither(second: partialResult)
manualConditional =ArrayBuilder.buildEither(first: outerPartialResult)}else{letpartialResult=ArrayBuilder.buildExpression(33)
manualConditional =ArrayBuilder.buildEither(second: partialResult)}
값을 만들지 않을 지도 모르는, else 절 없는 if 문 같은 분기문은 buildOptional(_:) 메서드 호출이 된다
if 문의 조건을 만족하면 자신의 코드 블럭을 변형하여 인자로 전달하며
그 외 경우, nil 을 인자로 가진 buildOptional(_:)을 호출한다
예를 들어, 다음 선언들은 서로 동일하다
@ArrayBuildervarbuilderOptional:[Int]{
if (someNumber %2)==1{20}}varpartialResult:[Int]?=nil
if (someNumber %2)==1{
partialResult =ArrayBuilder.buildExpression(20)}varmanualOptional=ArrayBuilder.buildOptional(partialResult)
코드 블럭이나 do 문은 buildBlock(_:) 메서드 호출이 된다
블럭 안에 있는 각각의 구문은 한번에 하나씩, 변형하여 buildBlock(_:) 메소드의 인수가 된다
예를 들어, 다음 선언들은 서로 동일하다
for 반복문은 임시 변수(배열)와 for 반복문 및 buildArray(_:) 메서드 호출이 된다
새로운 for 반복문은 같은 타입의 값들이 순차적으로 붙어서 나열된 시퀀스를 반복하여 각 부분 결과를 해당 배열에 덧붙인다
임시 배열은 buildArray(_:) 호출의 인수로 전달된다
예를 들어, 다음 선언들은 서로 동일하다
@ArrayBuildervarbuilderArray:[Int]{
for i in 5...7{100+ i
}}vartemporary:[[Int]]=[]
for i in 5...7{letpartialResult=ArrayBuilder.buildExpression(100+ i)
temporary.append(partialResult)}letmanualArray=ArrayBuilder.buildArray(temporary)
resultBuilder에 buildFinalResult(_:) 메서드가 있으면, 최종 결과는 이 메서드 호출이 된다
이 변환이 항상 마지막이다
변환 동작을 임시 변수로 설명하긴 했지만,
resultBuilder를 사용한다고 실제로 어떤 새로운 선언을 코드에 보이게 생성하는 건 아니다
resultBuilder가 변환할 코드에는 break, continue, defer, guard, return, while, do-catch 문을 사용할 수 없다
변환 과정은 코드 안의 선언을 바꾸지 않으며,
이는 임시 상수와 변수를 사용하여 표현식을 한 조각씩 제작하게 해준다
이는 throw 문, 컴파일-시간 진단문, return 문을 담은 클로저도 바꾸지 않는다
가능할 때마다 변환을 합체한다
예를 들어, 표현식 4 + 5 * 6 는 그 함수를 여러 번 호출하기 보단 buildExpression(4 + 5 * 6) 가 된다
마찬가지로 중첩한 분기문은 단일 이진 트리 형태로 buildEither 메소드를 호출한다
19) Custom Result-Builder Attributes
resultBuilder 유형을 생성하면 동일한 이름의 사용자 지정 특성이 생성된다
해당 특성은 다음 위치에 적용할 수 있다
함수 선언에선 resultBuilder가 함수 본문을 작성한다
게터를 포함한 변수나 서브 스크립트 연산 선언에선, resultBuilder가 게테 본문을 작성한다
함수 선언의 매개 변수에선, resultBuilder가 해당 인수로 전달된 클로저의 본문을 제작한다
결과 제작자 특성을 적용해도 ABI 호환성엔 영향을 주지 않는다
결과 제작자 특성을 매개 변수에 적용하면 해당 특성이 함수 인터페이스의 일부가 되어, 소스 호환성에 효과를 줄 수 있다
20) requires_stored_property_inits
해당 특성을 클래스 선언에 적용하면
클래스 정의 부분에서 자신의 모든 저장 프로퍼티에 기본 값을 제공하길 요구한다
NSManagedObject를 상속한 어떤 클래스든 이 특성을 가진다고 추론한다
21) testable
해당 특성을 import 선언에 적용하면 그 모듈의 접근 제어를 바꾼 채로 불러와서 모듈 코드 테스트를 단순하게 해준다
불러온 모듈 안의 개체가 internal 접근-수준 지정자로 표시한 경우 , 마치 public 접근-수준 지정자로 선언한 것처럼 불러온다
internal 이나 public 접근-수준 지정자로 표시한 클래스 및 클래스 멤버는
마치 open 접근-수준 지정자로 선언한 것처럼 불러온다
불러온 모듈은 반드시 테스트 할 수 있는 상태인
엑스코드의 스킴(Scheme) 화면 안의 테스트(Test) 옵션에서 Debug executable이 켜진 상태로 컴파일해야 한다
22) UIApplicationMain
해당 특성을 클래스에 적용하면 응용 프로그램 위임 상태(the application delegate) 라는 걸 나타낸다
이 특성을 사용하는 건 UIApplicationMain 함수를 호출하고 해당 클래스의 이름을 위임 클래스의 이름으로 전달하는 것과 같다
해당 특성을 사용하지 않는다면,
최상단에서 UIApplicationMain(_:_:_:_:) 함수를 호출하는 코드가 있는 main.swift 파일을 제공 한다
예를 들어, 자신의 앱이 자신만의 UIApplication 하위 클래스를 주된 클래스(principal class)로 사용한다면,
이 특성을 사용하는 대신 UIApplicationMain(_:_:_:_:) 함수를 호출한다
실행 파일을 만들기 위해 컴파일하는 Swift 코드는 Top-Level Code 페이지에서 설명한 것처럼 최상위 진입점을 하나만 포함할 수 있다
23) unchecked
해당 특성을 채택한 프로토콜의 타입 선언 목록 부분의 프로토콜 타입에 적용하면 그 프로토콜 요구 사항을 강제하는 걸 끈다
지원하는 단 하나의 프로토콜은 Sendable이다
한 동시성 영역에서 다른 곳으로 안전하게 값을 보낼 수 있는 sendable 타입으로
Actor를 사용할 때 필수적으로 고려되는 특성이며, Concurrent 관련 코드에서 data race를 방지하기 위해 사용된다
24) usableFromInline
해당 특성을 함수, 메서드, 계산 속성, 서브 스크립트, 이니셜라이저, 디이니셜라이저 선언에 적용하면
선언과 동일한 모듈 안에서 정의한 인라인 가능 코드에서 해당 기호를 사용하는 걸 허용한다
선언엔 반드시 internal 접근 수준 자정자가 있어야 한다
usableFromInline 을 표시한 구조체나 클래스는 자신의 속성이 public 또는 usableFromInline 인 타입만 사용할 수 있다
usableFromInline 을 표시한 열거체는 자신의 case 원시값과 연관 값이 public 또는 usableFromInline인 타입만 사용할 수 있다
public 접근 수준 지정자와 마찬가지로 이 특성은 선언을 모듈의 공용 인터페이스의 일부로 표시한다
public과는 달리 컴파일러는 선언 기호를 내보낼지라도,
usableFromInline을 표시한 선언이 모듈 밖 코드에서 이름으로 참조되는 걸 컴파일러가 허용하진 않는다
하지만, 모듈 밖 코드는 런타임 동작을 사용하여 여전히 선언 기호와 상호 작용할 수 있다
inlinable 특성을 표시한 선언은 암시적으로 inlinable 코드에서 사용 가능하다
inlinable 특성을 부여하면 따로 usableFromInline 특성을 부여할 필요가 없다
inlinable이든 usableFromInline이든 internal 선언에 적용할 수 있긴 하지만, 두 특성을 모두 적용하면 에러가 발생한다
25) 규명 안된 접근 경고하기(warn_unqualified_access)
해당 특성을 최상단 함수, 인스턴스 메서드, 또는 class나 static 메서드에 적용하면
모듈 이름, 타입 이름, 또는 인스턴스 변수 및 상수 같은 앞선 규명자 없이
해당 함수나 메서드를 사용할 때 경고를 발생시킨다
‘앞선 규명자(preceding qualifier)’는 Result Transformations 챕터에서 예제에 있던 temporary.append(partialResult)에서의 temporary 같이 그 기호에 앞에서 그 기호가 어디에 소속되어 있는지를 규명하는 지시자를 말한다
해당 특성을 사용하면 동일한 scope에서 접근 가능한 똑같은 이름의 함수 사이에 모호함이 없도록 도와준다
ex. 스위프트 표준 라이브러리는 최상단 min(_:_:) 함수 및 비교 가능 원소의 시퀀스(sequence)를 위한 min() 메서드를 모두 포함한다
시퀀스 메서드는 warn_unqualified_access 특성으로 선언되어
Sequence 익스텐션 안에서 둘 중 하나를 사용하려할 때의 혼동을 줄이도록 해준다
26) 인터페이스 빌더에서 쓰는 선언 특성(Declaration Attributes Used by Interface Builder)
인터페이스 빌더 특성은 인터페이스 빌더에서 엑스코드와 동기화하기 위해 쓰는 선언 특성이다
스위프트는 다음의 인터페이스 빌더 특성을 제공하는데
IBAction, IBSegueAction, IBOutlet, IBDesignable, IBInspectable 이 있다
이러한 특성은 오브젝티브-C 에 있는 것과 개념이 똑같다
IBOutlet과 IBInspectable 특성은 클래스의 프로퍼티 선언에 적용한다
IBAction 과 IBSegueAction 특성은 클래스의 메서드 선언에 IBDesignable 특성은 클래스 선언에 적용한다
IBAction, IBSegueAction, IBOutlet, IBDesignable, IBInspectable 특성을 적용하는 건 objc 특성이기도 함을 암시한다
타입 특성
타입 특성은 타입에만 적용할 수 있다
1) autoclosure
해당 특성을 적용하면 인수 없는 클로저로 표현식을 자동 포장함으로써 해당 표현식의 평가를 늦춘다
함수나 메서드 선언에서 매개 변수 타입이 아무런 인자도 취하지 않으며 표현식 타입의 값을 반환하는 함수 타입이면,
그 매개 변수 타입에 이를 적용한다
autoclosure 특성의 사용법에 대한 예제는 Autoclosures와 Function Type 페이지 부분을 보도록하자
2) 협약(convention)
해당 특성을 함수의 타입에 적용하면 호출 협약을 나타낸다
‘The Swift Calling Convention’ 은 호출한 쪽의 매개 변수 전달 방법과 하위 루틴의 결과 반환 방법을 협의해서 정한 약속이다
호출 규약이라고도 하는데 규약은 프로토콜 (protocol) 을 의미하기 때문에 Convention은 협약이라고 해석하면 된다
convention 특성은 항상 다음의 인수 중 하나와 함께 나타난다
swift 인수는 스위프트 함수 참조를 나타낸다
이는 스위프트 함수 값에 대한 표준 호출 협약이다
block 인수는 Objective-C 호환 블록 참조를 나타낸다
함수 값은 블록 객체에 대한 참조로 표현되며, 블록 객체는 객체 내에 호출 함수를 포함하는 id 호환 가능한 Objective-C 객체이다
호출 기능은 C 호출 규약을 사용한다
c 인자는 C 함수 참조를 나타낸다
함수 값은 아무런 의미도 없으며 C 호출 협약을 사용한다
몇 가지를 제외하면 어떤 호출 협약의 함수든 다른 어떤 호출 협약인 함수가 필요할 때 사용할 수 있다
제네릭이 아닌 전역 함수나, 어떤 지역 변수도 캡쳐하지 않는 지역 함수, 또는 어떤 지역 변수도 캡쳐하지 않는 클로저는
C 호출 협약으로 변환할 수 있다
다른 스위프트 함수는 C 호출 협약으로 변환할 수 없다
오브젝티브-C 블럭 호출 협약인 함수는 C 호출 협약으로 변환 할 수 없다
3) escaping
해당 특성을 함수나 메서드 선언 안의 매개 변수 타입에 적용하면 나중에 실행하기 위해 매개 변수 값을 저장할 수 있다는 걸 나타낸다
이는 그 값이 호출 수명보다 오래 사는 걸 허용한다는 의미다
escaping 타입 특성인 함수 타입 매개 변수는 프로퍼티나 메서드에 self. 를 명시하여 사용할 걸 요구한다
escaping 특성의 사용법에 대한 예제는, Escaping Closures 페이지 부분을 보도록하자
4) Sendable
해당 특성을 함수의 타입에 적용하면 함수나 클로저가 보내기 가능하다는 걸 지시한다
주어진 타입의 값이 concurrent code에서 안전하게 사용될 수 있음을 의미한다
해당 특성을 함수 타입에 적용하는 건 비-함수 타입이 Sendable 프로토콜을 준수하는 것과 똑같은 의미다
함수나 클로저가 Sendable 값을 예상한 상황에서 사용되는데,
그 함수나 클로저가 Sendable에 필요한 요구 사항을 만족한다면, 함수나 클로저가 해당 특성이라고 추론한다
Sendable 함수 타입은 보내기 불가능한 함수 타입에 해당하는 것의 하위 타입이다
switch 문 case 절 특성(Switch Case Attributes)
switch 의 case 특성은 switch 문의 case 에만 적용할 수 있다
1) unknown
해당 특성을 switch문의 case에 적용하면
코드를 컴파일하는 시점에 알고 있는 어떤 열거체 case 와도 일치하지 않을 것으로 예상한다고 나타낸다
unknown 특성의 사용법에 대한 예제는, Switching Over Future Enumeration Cases 페이지 부분을 보도록 하자
The text was updated successfully, but these errors were encountered:
스위프트에는 두 가지 종류의 특성(attributes)이 있는데
선언에 적용하는 것과 타입에 적용하는 것이 있다
특성은 선언이나 타입에 대한 추가 정보를 제공한다
예를 들어, 함수 선언이 discardableResult 특성이면,
함수는 값을 반환하지만 반환 값을 사용하지 않는다고 컴파일러가 경고를 생성하지는 않는게 좋다는 걸 지시한다
특성을 지정하려면 @ 기호 뒤에 특성 이름과 특성이 받아들이는 어떤 인자를 작성하면 된다
일부 선언 특성은 특성에 대한 자세한 정보와 특성이 특정 선언에 적용되는 방법을 지정하는 인수를 허용한다
이러한 특성 인수(attribute arguments)는 괄호 안에 포함되며,
해당 형식은 해당 인수가 속한 특성에 의해 정의된다
선언 특성
선언 특성은 선언에만 적용할 수 있다
1) available
해당 특성을 적용하면 특정 스위프트 언어 버전이나 특정 플랫폼 및 운영 체제 버전과 관련된 선언의 생명 주기를 지시한다
available 특성은 항상 쉼표로 구분한 둘 이상의 특성 인수 목록과 함께 나타난다
이러한 인수는 다음의 플랫폼이나 언어 이름 중 하나로 시작한다
별표(*)를 사용하여 위에 나열한 모든 플랫폼 이름에 대한 선언의 사용 가능성(availability)을 나타낼 수도 있다
스위프트 버전 번호로 사용 가능성을 나타낸 available 특성엔 별표를 사용할 수 없다
나머지 인수는 어떤 순서로든 나타날 수 있으며
선언 생명 주기에 대한 추가 정보를 지정할 수 있는데, 이는 중요한 이정표(milestones)를 포함한다
unavailable 인수는 지정한 플랫폼에선 선언의 사용이 불가능하다는 걸 나타낸다
스위프트 버전 사용 가능성을 지정할 땐 해당 인수를 사용할 수 없다
introduced 인수는 지정한 플랫폼이나 언어 버전이 선언을 도입한 첫번째 버전이라는 걸 지시한다
버전 번호(version number)는 1개에서 3개까지의 양수를 마침표로 구분하여 구성한다
형식은 다음과 같다
deprecated 인자는 지정한 플랫폼이나 언어 버전이 선언을 폐기할 예정인 첫 번째 버전이라는 걸 나타낸다
옵션인 버전 번호(version number)는 1개에서 3개까지의 양수를 마침표로 구분하여 구성한다
버전 번호를 생략한다면 언제 폐기가 일어나는지에 대해선 어떤 정보도 주지 않고, 현재부터 선언이 폐기 예정이라는 걸 나타내게 된다
버전 번호를 생략한다면 콜론(:) 마저 생략한다
형식은 다음과 같다
obsoleted 인자는 지정한 플랫폼이나 언어 버전이 선언을 폐기한 첫 번째 버전이라는 걸 나타낸다
폐기한 선언일 땐 지정한 플랫폼이나 언어에서 제거하여 더 이상 사용할 수 없다
버전 번호(version number)는 1개에서 3개까지의 양수를 마침표로 구분하여 구성한다
형식은 다음과 같다
message 인자는 폐기 예정이거나 폐기한 선언의 사용에 대한 경고 또는 에러를 내보낼 때 컴파일러가 보여줄 메시지 글을 제공한다
메시지(message)는 문자열 글자값으로 구성된다
형식은 다음과 같다
renamed 인자는 이름을 바꾼 선언의 새 이름을 지시하는 메시지 글을 제공한다
컴파일러는 이름 바꾼 선언의 사용에 대한 에러를 내보낼 때 새 이름을 보여주게 된다
새로운 이름(new name)은 문자열 글자값으로 구성된다
형식은 다음과 같다
밑에 예시에서 보는 것처럼 rename 과 unavailable 인수가 있는 available 특성을 타입 별명 선언에 적용하면,
프레임워크이나 라이브러리 릴리스 사이에 바뀐 선언의 이름을 나타낼 수 있다
해당 조합으로 인해 선언의 이름이 바뀌었다는 컴파일 에러를 발생시킨다
단일 선언에 여러 개의 available 특성을 적용하여
서로 다른 플랫폼과 서로 다른 스위프트 버전에 대한 선언의 사용 가능 여부를 지정할 수 있다
특성이 지정한 플랫폼이나 언어 버전이 현재 대상과 일치하지 않으면 available 특성을 적용한 선언이 무시된다
여러 개의 available 특성을 사용할 경우, 효과적인 사용 가능성은 플랫폼과 스위프트 사용 가능성을 조합하여 사용할 수 있다
여러 개의 available 특성을 사용할 경우,
플랫폼 사용 가능성만 여러 개 적용하거나 스위프트 버전 사용 가능성만 여러 개 적용한 건 별 의미가 없다
available 특성이 플랫폼이나 언어 이름 인자 외엔 introduced 인자만 지정한다면, 다음의 짧게 줄인 구문을 대신 사용할 수 있다
available 특성의 짧게 줄인 구문은 여러 플랫폼의 사용 가능성을 간결하게 표현한다
두 형식의 기능이 같긴 하지만, 가능할 때마다 짧게 줄인 형식을 쓰는게 더 좋다
스위프트 버전 번호를 써서 사용 가능성을 지정한 available 특성은
선언의 플랫폼 사용 가능성을 추가로 지정할 수 없다
대신, 별도의 available 특성으로 스위프트 버전 사용 가능성과 하나 이상의 플랫폼 사용 가능성을 지정하여 사용할 수 있다
2) discardableResult
해당 특성을 함수나 메서드 선언에 적용하면
값을 반환하는 함수나 메서드를 호출하고 결과를 사용하지 않을 때의 컴파일러 경고를 억제할 수 있다
3) dynamicCallable
해당 특성을 클래스나, 구조체, 열거체, 프로토콜에 적용하면
해당 타입의 인스턴스를 호출 가능한 함수로 취급한다
타입은 반드시 dynamicallyCall(withArguments:) 메서드나 dynamicallyCall(withKeywordArguments:) 메서드, 또는 둘 다를 구현해야 한다
동적으로 호출 가능한 타입의 인스턴스는 마치 원하는 수 인수를 사용하는 함수처럼 호출할 수 있다
dynamicallyCall(withArguments:) 메서드 선언은
반드시 ExpressibleByArrayLiteral 프로토콜을 준수한 단일 매개 변수(예제의 [Int])를 가져야 한다
반환 타입은 어떤 타입이든 가능하다
dynamicallyCall(withKeywordArguments:) 메서드를 구현한다면 동적 메서드 호출에 라벨을 포함시킬 수 있다
dynamicallyCall(withKeywordArguments:) 메서드 선언엔
반드시 ExpressibleByDictionaryLiteral 프로토콜을 준수한 단일 매개 변수가 있어야 하며,
반환 타입은 어떤 타입이든 가능하다
매개 변수의 Key는 반드시 ExpressibleByStringLiteral이어야 한다
이전 예제에서는 호출자가 중복된 매개 변수 라벨을 포함할 수 있도록 KeyValuePairs를 매개 변수 유형으로 사용한다
a와 b는 반복적인 호출에 의해 여러 번 나타나게 된다
dynamicallyCall 메서드를 둘 다 구현하면,
메서드 호출에 키워드 인수를 포함할 때 dynamicallyCall(withKeywordArguments:)를 호출한다
다른 모든 경우엔, dynamicallyCall(withArguments:)를 호출하게 된다
인수와 반환 값 타입이 dynamicallyCall 메서드 구현에서 지정한 것과 일치한 동적 호출 가능 인스턴스만 호출할 수 있다
다음 예제 호출은 컴파일이 안되는데 KeyValuePairs<String, String> 을 취하는 dynamicallyCall(withArguments:) 구현이 없기 때문이다
4) dynamicMemberLookup
해당 특성을 클래스, 구조체, 열거체, 프로토콜에 적용하면 멤버를 런타임에 이름으로 찾아볼 수 있게 한다
타입은 반드시 subscript(dynamicMemberLookup:)를 구현해야 한다
명시적 멤버 표현식에서 이름 있는 멤버에 해당하는 선언이 없는 경우,
표현식이 타입의 subscript(dynamicMemberLookup:) 서브 스크립트에 대한 호출이라고 이해되며,
멤버에 대한 정보를 인수로 전달한다
서브 스크립트는 키 경로나 멤버 이름인 매개 변수를 받아들일 수 있으며
두 서브 스크립트를 모두 구현하면, 키 경로 인수를 가진 서브 스크립트를 사용하게 된다
subscript(dynamicMemberLookup:) 구현하면
KeyPath나 WritableKeyPath, 또는 ReferenceWritableKeyPath 타입의 인수를 사용하여
키 경로를 받아들일 수 있다
ExpressibleByStringLiteral 프로토콜을 준수하는 타입의 인수(대부분의 경우, String)를 사용한 멤버 이름도 받아들일 수 있다
서브 스크립트의 반환 타입은 어떤 타입이든 가능하다
멤버 이름에 의한 dynamicMemberLookup은
다른 언어로 된 데이터를 스위프트로 연동할 때와 같이
컴파일 시간에 타입 검사를 할 수 없는 데이터의 래퍼 타입을 생성하는데 사용할 수 있다
예를 들면 다음과 같다
키 경로에 의한 dynamicMemberLookup은 컴파일-시간 타입 검사를 지원하는 방식으로
래퍼 타입을 구현하는데 사용할 수 있다
예를 들면 다음과 같다
5) frozen
해당 특성을 구조체나 열거체 선언에 적용하면 타입에 가할 수 있는 변화의 종류를 제약한다
특성은 library evolution mode로 컴파일할 때만 허용된다
미래 버전의 라이브러리가 열거체 case나 구조체의 저장 인스턴스 프로퍼티를 추가, 삭제, 또는 재배치하는 걸로 선언을 바꿀 수 없다
동결 아닌(nonfrozen) 타입은 이러한 변화를 허용하지만,
동결 (frozen) 타입이 이러면 ABI 호환성을 깨뜨리게 된다
라이브러리 진화 모드에서 동결 아닌 구조체 및 열거체 멤버와 상호 작용하는 코드는
미래 버전의 라이브러리가 해당 타입의 일부 멤버를
추가, 삭제, 또는 재배치하더라도 재-컴파일 없이 작업을 계속 허용하는 방식으로 컴파일한다
컴파일러는 런타임에 정보 찾아보기 및 간접 계층 추가하기 같은 기술을 써서 이를 가능하게 한다
구조체나 열거체를 동결로 만드는 건 이런 유연함을 포기하면서 성능을 얻는 것인데
미래 버전의 라이브러리가 타입을 바꾸는 데는 제한이 있지만,
타입 멤버와 상호 작용하는 코드를 컴파일러가 추가로 최적화할 수 있다
동결 타입과 동결 구조체의 저장 프로퍼티 타입, 및 동결 열거체 case의 연관 값은
반드시 공용(public)이거나 ‘usableFromInline 특성을 표시해야 한다
동결 구조체의 프로퍼티엔 프로퍼티 옵저버가 있을 수 없으며,
저장 인스턴스 프로퍼티에 초기 값을 제공하는 표현식은 뒤에서 알아볼 inlinable에서 논의할 것처럼
반드시 인라인 가능 함수와 똑같은 제약 사항을 따라야 한다
명령 줄에서 라이브러리 진화 모드를 켜려면, -enable-library-evolution 옵션을 스위프트 컴파일러로 전달한다
엑스코드에서 켜려면, Xcode Help에서 설명한, “배포용 라이브러리 제작(BUILD_LIBRARY_FOR_DISTRIBUTION)” 배포 설정을 Yes로 설정한다
Switching Over Future Enumeration Cases에서 논의한 것처럼,
동결 열거체에 대한 switch 문은 default case 를 요구하지 않는다
동결 열거체 전환 때 default 나
@
unknown default case를 포함하면 경고를 만들어 내는데해당 코드는 절대 실행되지 않기 때문이다
6) 점검 가능한 GameplayKit(GKInspectable)
해당 특성을 적용하면 사용자가 정의한 GameplayKit 컴포넌트 프로퍼티를 SpriteKit 편집기 UI 로 드러낸다
해당 특성을 적용하면 암시적으로 objc 특성도 적용한다
7) inlinable
해당 특성을 함수, 메서드, 계산 프로퍼티, 서브 스크립트, 편리한 이니셜라이저, 디이니셜라이저 선언에 적용하면
그 선언 구현을 모듈의 공용 인터페이스 일부분으로 표시한다
컴파일러는 인라인 가능한 기호의 호출을 기호 구현부의 복사본으로 호출한 쪽에서 대체할 수 있다
인라인 가능 코드는 어떤 모듈에서 선언한 public 기호와도 상호 작용할 수 있으며,
동일 모듈에서 usableFromInline 특성을 표시하여 선언한 internal 기호와도 상호 작용할 수 있다
인라인 가능 코드는 private 이나 fileprivate 기호와는 상호 작용할 수 없다
해당 특성은 함수 안에 중첩한 선언이나 fileprivate 및 private 선언에 적용할 순 없다
인라인 가능 함수 안에서 정의한 함수와 클로저는 해당 특성을 표시할 수 없을지라도 암시적으로 인라인 가능하다
8) main
해당 특성을 구조체, 클래스, 열거체 선언에 적용하면
이게 프로그램 흐름의 최상단 진입점(top-level entry point)을 포함한다는 걸 나타낸다
타입은 반드시 어떤 인수도 취하지 않고 Void 를 반환하는 main 타입 함수를 제공해야 한다
예를 들면 다음과 같다
main 특성의 요구 사항을 설명하는 또 다른 방법은
해당 특성을 쓴 타입은 반드시 다음의 가상(hypothetical) 프로토콜을 준수한 타입과 동일한 요구 사항을 충족해야 한다는 것이다
Top-Level Code에서 논한 것처럼 실행 파일을 만들려고 컴파일하는 스위프트 코드는
최대 한 개의 최상단 진입점을 담을 수 있다
9) nonobjc
해당 특성을 메서드, 프로퍼티, 서브 스크립트, 이니셜라이저 선언에 적용하면 암시적 objc 특성을 억제한다
nonobjc 특성은 컴파일러에게 오브젝티브-C 로 나타내는게 가능한 선언일지라도,
오브젝티브-C 코드에서 사용 불가능하게 하라고 말하는 것과 같다
익스텐션에 해당 속성을 적용하면 objc 속성으로 명시적으로 표시되지 않은 해당 익스텐션의 모든 구성원에 적용하는 것과 같은 효과가 있다
nonobjc 특성을 사용하여 클래스에서 objc 특성을 표시한 연동 메서드(bridging methods)의 순환성을 해결하고,
클래스에서 objc 특성을 표시한 메서드와 이니셜라이저의 중복 정의(overload)도 가능하다
nonobjc 특성을 표시한 메서드는 objc 특성을 표시한 메서드를 재정의(override)할 수 없다
하지만, objc 특성을 표시한 메서드는 nonobjc 특성을 표시한 메서드를 재정의(override)할 수 있다
이와 비슷하게, nonobjc 특성을 표시한 메서드는 objc 특성을 표시한 메서드에 대한 프로토콜 요구 사항을 만족할 수 없다
10) NSApplicationMain
해당 특성을 클래스에 적용하면
자신이 앱의 응용 프로그램 위임자(the application delegate)라는 걸 나타낸다
해당 특성을 사용하는 건 NSApplicationMain(::) 함수를 호출하는 것과 같다
해당 특성을 사용하지 않는다면
다음 처럼 최상단에서 NSApplicationMain(::) 함수를 호출하는 코드가 있는 main.swift 파일을 제공한다
Top-Level Code에서 이야기한 것처럼, 실행 파일을 만들려고 컴파일하는 스위프트 코드는
최대 한 개의 최상단 진입점을 가질 수 있다
11) NSCopying
해당 특성은 클래스의 저장 변수 프로퍼티에 적용한다
해당 특성은 프로퍼티의 세터가 프로퍼티 그 자체의 값 대신
copyWithZone(_:) 메서드로 반환한 프로퍼티 값의 복사본(copy)과 합성되도록 한다
프로퍼티 타입은 반드시 NSCopying 프로토콜을 준수해야 한다
NSCopying 특성은 오브젝티브-C 의 copy 프로퍼티 특성과 비슷하게 동작한다
12) NSManaged
해당 특성을 NSManagedObject를 상속한 클래스의 인스턴스 메서드 또는 저장 변수 프로퍼티에 적용하면,
결합 개체 설명을 기초로 코어 데이터(Core Data)가 자신의 구현을 런타임에 동적으로 제공한다는 걸 나타낸다
NSManaged 특성을 표시한 프로퍼티면 코어 데이터가 런타임에 저장 공간도 제공한다
해당 특성을 적용하면 암시적으로 objc 특성도 적용된다
13) objc
해당 특성은 오브젝티브-C로 표현할 수 있는 어떤 선언에든 적용할 수 있다
예를 들어, 중첩 아닌 클래스, 프로토콜,
정수 원시-값 타입으로 제약한 비-제네릭 열거체,
게터와 세터를 포함한 클래스 프로퍼티 및 메서드,
프로토콜과 프로토콜의 옵셔널 멤버, 이니셜라이저, 서브 스크립트가 있다
objc 특성은 컴파일러에게 오브젝티브-C 코드에서 선언이 사용 가능하다고 말하는 것과 같다
해당 특성을 익스텐션에 적용하는 건
그 익스텐션에서 nonobjc 특성을 명시하지 않은 모든 멤버에 이를 적용한 것과 똑같은 효과가 있다
컴파일러는 오브젝티브-C로 정의한 어떤 클래스의 하위 클래스에든 objc 특성을 암시적으로 추가한다
하지만, 하위 클래스는 반드시 제네릭이 아니어야 하며,
반드시 어떤 제네릭 클래스도 상속하지 않아야 한다
이러한 기준에 부합하는 하위 클래스에 objc 특성을 명시적으로 추가하면,
아래에서 설명한 대로 자신의 오브젝티브-C 이름을 지정할 수 있다
objc 특성을 표시한 프로토콜은 해당 특성을 표시하지 않은 프로토콜을 상속할 수 없다
objc 속성은 다음과 같은 경우에도 암시적으로 추가된다
열거체에 objc 특성을 적용하면 각 열거체 case를 열거체 이름과 case 이름을 이어붙인 형태로 오브젝티드-C 코드에 노출된다
case 이름의 첫 글자는 대문자로 표시한다
ex. 스위프트 Planet 열거체 안의 venus 라는 이름의 case는
PlanetVenus라는 이름의 case로 오브젝티브-C에 노출된다
objc 특성은 식별자로 구성한 단일 특성 인수를 옵셔널하게 받는다
식별자는 objc 특성을 적용한 개체가 오브젝티브-C 로 노출될 이름을 지정한다
해당 인수를 사용하여 클래스, 열거체, 열거체 case, 프로토콜, 메서드, 게터, 세터, 이니셜라이저의 이름을 지을 수 있다
클래스, 프로토콜, 열거체의 오브젝티브-C 이름을 지정한다면,
Programming with Objective-C에 있는 Conventions에서 설명한 것처럼,
이름에 세-글자짜리 접두사를 포함시킨다
아래 예제는 ExampleClass에 있는 enabled 프로퍼티의 게터를 프로퍼티 자체의 이름이 아닌 isEnabled로 오브젝티브-C 코드에 그대로 노출한다
더 많은 정보는, Importing Swift into Objective-C 항목 페이지를 보도록 하자
14) objcMembers
해당 특성을 클래스 선언에 적용하면,
클래스, 익스텐션, 하위 클래스, 하위 클래스의 모든 익스텐션 안에서
오브젝티브-C 와 호환 가능한 모든 멤버에 암시적으로 objc 특성을 적용한다
대부분의 코드는 objc 특성을 대신 사용하여 필요한 선언만 드러내는게 좋다
수 많은 선언을 노출해야 하는 경우, objc 속성이 있는 익스텐션에서 선언을 그룹화할 수 있다
objcMembers 특성은 오브젝티브-C 런타임의 내부 검사 기능(introspection facilities)을 아주 많이 쓰는 라이브러리에 편리하다
오브젝티브-C 기능을 아주 많이 쓰면 호환성을 위해 objc를 남발하게 될텐데,
이 때의 비효율성을 줄이기 위해 objcMembers 특성을 사용한다고 이해할 수 있다
불필요할 때 objc 특성을 적용하면 바이너리 크기가 증가하고 성능에 불리한 영향을 줄 수 있다
15) propertyWrapper
해당 특성을 클래스, 구조체, 열거체 선언에 적용하면 해당 타입을 propertyWrapper로 사용한다
해당 특성을 타입에 적용할 땐, 타입과 똑같은 이름을 가진 커스텀 특성을 생성한다
새로운 특성을 클래스, 구조체, 열거체 프로퍼티에 적용하면, 래퍼 타입(wrapper type)의 인스턴스를 통해 프로퍼티로 액세스를 래핑한다
변수에 대한 액세스를 동일한 방식으로 래핑하려면, 로컬 저장 변수 선언에 특성을 적용한다
계산 변수, 전역 변수, 상수는 propertyWrapper을 사용할 수 없다
래퍼(wrapper)는 반드시 wrappedValue 인스턴스 프로퍼티를 정의해야 한다
프로퍼티의 래핑된 값(wrapped value)은 해당 프로퍼티의 게터(getter)와 세터(setter)가 표시하는 값이다
wrappedValue는 대부분의 경우 계산된 값(computed value)이지만, 대신 저장된 값(stored value)일 수도 있다
래퍼은 자신의 래퍼된 값에 필요한 기본 저장 공간를 정의하고 관리한다
컴파일러는 래핑된 프로퍼티 이름 앞에 접두사로 밑줄(_)을 붙여서
래퍼 타입 인스턴스에 대한 저장 공간을 만들어 합성한다
예를 들어, someProperty의 래퍼는 _someProperty라고 저장된다
래퍼를 위해 합성된 저장 공간은 private 접근 제어 수준을 가진다
propertyWrapper을 가진 프로퍼티은 willSet 과 didSet 블럭을 포함할 수 있지만,
컴파일러에 의해 합성된 get 이나 set 블럭을 재정의할 순 없다
스위프트는 propertyWrapper의 초기화를 위해 두 가지 형식의 수월한 구문을 제공한다
래핑된 값의 정의에서 할당 구문을 사용하면
할당 오른쪽 표현식을 propertyWrapper 이니셜라이저의 wrappedValue 매개 변수에 인수로 전달할 수 있다
프로퍼티에 특성을 적용할 때 인수를 제공할 수도 있는데,
해당 인수들은 propertyWrapper 이니셜라이저로 전달된다
예를 들어, 아래 코드에서, SomeStruct 는 SomeWrapper 에서 정의한 각각의 이니셜라이저를 호출한다
래핑된 프로퍼티의 투영된 값(projected value)은 두 번째 값으로
이를 사용하면 propertyWrapper가 추가 기능을 표시할 수 있다
propertyWrapper 타입의 작성자는 투영된 값의 의미를 결정하고
투영된 값이 노출하는 인터페이스의 정의를 책임진다
propertyWrapper에서 값을 투영하려면, 래퍼 타입에 projectedValue 인스턴스 프로퍼티를 정의한다
컴파일러는 래핑된 프로퍼티 이름 앞에 접두사로 달러 기호($)를 둠으로써
투영된 값의 식별자를 만들어 통합한다
예를 들어, someProperty의 투영된 값은 $someProperty다
투영된 값의 접근 제어 수준은 원본 래핑된 프로퍼티와 똑같다
16) resultBuilder
해당 특성을 클래스, 구조체, 열거체에 적용하면 그 타입을 resultBuilder로 사용한다
결과 제작자(result builder)는 중첩 자료 구조를 한 걸음씩 단계별로 제작하는 타입이다
resultBuilder를 사용하여 자연스러운, 선언형 방식으로, 중첩 자료 구조를 생성하기 위한 특정-분야 언어(DSL)을 구현할 수 있다
resultBuilder 특성의 사용법에 대한 예제는, Result Builders 페이지 부분을 보도록 하자
17) Result-Building Methods
resultBuilder는 밑에서 설명할 static 메서드를 구현한다
resultBuilder의 모든 기능은 static 메서드를 통해 드러나기 때문에,
절대 해당 타입의 인스턴스를 초기화하지 않는다
buildBlock(_:) 메서드는 필수이며 DSL에 추가 기능을 부여하는 다른 메서드는 옵션이다
resultBuilder 타입의 선언은 어떤 프로토콜 준수도 실제로 포함할 필요가 없다
이 역시 인스턴스를 만들어서 쓰지 않기에 프로토콜의 요구 사항의 구현이라는 개념이 의미가 없다
static 메서드 설명엔 세 개의 타입을 플레이스 홀더로 사용한다
Expression은 resultBuilder의 input 타입에 대한 플레이스 홀더이고,
Component는 부분적인 result 타입에 대한 플레이스 홀더이며,
FinalResult는 resultBuilder가 최종적으로 만들어 내는 result 타입에 대한 플레이스 홀더다
이러한 타입들을 자신의 resultBuilder에서 사용하는 실제 타입으로 바꿀 수 있다
Result-Building 메서드에서 Expression 이나 FinalResult 타입을 지정하지 않으면 기본적으로 Component와 동일하게 구성된다
Result-Building 메서드는 다음과 같다
static func buildBlock(_ components: Compnent...) -> Component
부분 결과 배열을 단일 부분 결과로 조합한다
resultBuilder는 반드시 이 메서드를 구현해야 한다
static func buildOptional(_ component: Compnent?) -> Component
nil 일 수 있는 부분 결과를 기반으로 부분 결과를 제작한다
이 메서드를 구현하면 else 절을 포함하지 않는 if 문을 지원한다
static func buildEither(first: Compnent) -> Component
어떠한 조건에 따라 값이 변하는 부분 결과를 제작한다
이 메서드와 buildEither(second:) 둘 다를 구현하면 switch 문과 else 절을 포함한 if 문을 지원한다
static func buildEither(second: Compnent) -> Component
어떠한 조건에 따라 값이 변하는 부분 결과를 제작한다
이 메서드와 buildEither(first:) 둘 다를 구현하면 switch 문과 else 절을 포함한 if 문을 지원한다
static func buildArray(_ components: [Compnent]) -> Component
부분 결과들의 배열로 부분 결과를 제작한다
이 메서드를 구현하면 for 반복분을 지원한다
static func buildExpression(_ expression: Expression) -> Component
표현식으로 부분 결과를 제작한다
이 메서드를 구현하면 표현식을 내부 타입으로 변환하는 등의 전처리 과정을 수행하거나
사용하는 쪽에 타입 추론을 위한 추가 정보를 제공한다
static func buildFinalResult(_ component: Compnent) -> FinalResult
부분 결과로 최종 결과를 제작한다
이 메서드는 부분 및 최종 결과에서 사용할 타입이 서로 다른 resultBuilder를 구현하거나,
결과 반환 전에 다른 후처리 과정을 수행하기 위해 구현할 수 있다
static func buildLimitedAvailablility(_ component: Compnent) -> Component
사용 가능성을 검사하는 컴파일러 제어문 밖으로 타입 정보를 전파하거나 지우는 부분 결과를 제작한다
이를 사용하면 조건 분기마다 다양하게 변하는 타입 정보를 지울 수 있다
예를 들어, 아래 코드는 정수 배열을 제작하는 단순한 resultBuilder를 정의한다
이 코드는 Component 와 Expression 을 타입 별명으로 정의하여, 아래 예제와 위의 메서드 목록을 더 쉽게 맞춰보게 한다
18) Result Transformations
다음 구문 변환은 resultBuilder 구문을 사용하는 코드를
result-builder 타입의 static 메서드를 호출하는 코드로 바꾸기 위해 재귀적으로 적용된다
resultBuilder에 buildExpression(_:) 메서드가 있으면, 각각의 표현식은 해당 메서드에 대한 호출이 된다
이 변환이 항상 첫 번째다. 예를 들어, 다음 선언들은 서로 동일하다
할당문은 표현식처럼 변환되지만, ()로 평가되는 것으로 이해된다
할당문의 경우 buildExpression(_:) 의 평가 결과를 사용한다
() 타입 인수를 사용하는 buildExpression(_:)를 중복 정의(overload)하면 지정된 할당을 특수하게 처리할 수 있다
사용 가능성 조건을 검사하는 분기문은 buildLimitedAvailablility(_:) 메서드에 대한 호출이 된다
이 변환은 buildEither(first:), buildEither(second:), buildOptional(_:) 호출로 변환하기 전에 발생한다
buildLimitedAvailablility(_:) 메서드를 사용하면 어떤 분기를 취할 지에 따라 변경되는 타입 정보를 지운다
ex. 아래의 buildEither(first:) 와 buildEither(second:) 메서드는 제네릭 타입을 사용하여 두 분기 모두에 대한 타입 정보를 캡쳐한다
하지만, 이런 접근법은 사용 가능성을 검사하는 코드에서 문제를 일으킨다
위의 코드에서 FutureText는 DrawOne 제네릭 타입 중 하나이기 때문에 brokenDrawing 타입의 일부로 나타난다
이로 인해 FutureText 타입이 명시적으로 사용되지 않는 경우에도
런타임에 해당 FutureText를 사용할 수 없으면 프로그램이 중단될 수 있다
이 문제를 해결하려면, buildLimitedAvailability(_:) 메서드를 구현하여 타입 정보를 지우면 된다
예를 들어, 아래 코드는 자신의 사용 가능성 검사에서 AnyDrawable 값을 작성한다
분기문은 buildEither(first:)와 buildEither(second:) 일련의 중첩 호출이 된다
구문의 조건과 case는 이진 트리의 잎 노드 (leaf nodes)에 대응하며,
구문은 뿌리 노드(root node)에서 해당 잎 노드로의 경로를 따라가는 중첩된 buildEither 메서드 호출이 된다
예를 들어, 3개의 case가 있는 스위치 문을 작성하는 경우, 컴파일러는 3개의 잎 노드가 있는 이진트리를 사용한다
마찬가지로 뿌리 노드에서 두 번째 case로 경로는 "second child" 다음에 "first child" 이기 때문에,
해당 case는 buildEither(first: buildEither(second: ...))와 같은 중첩 호출이 된다
다음 선언들은 서로 동일하다
값을 만들지 않을 지도 모르는, else 절 없는 if 문 같은 분기문은 buildOptional(_:) 메서드 호출이 된다
if 문의 조건을 만족하면 자신의 코드 블럭을 변형하여 인자로 전달하며
그 외 경우, nil 을 인자로 가진 buildOptional(_:)을 호출한다
예를 들어, 다음 선언들은 서로 동일하다
코드 블럭이나 do 문은 buildBlock(_:) 메서드 호출이 된다
블럭 안에 있는 각각의 구문은 한번에 하나씩, 변형하여 buildBlock(_:) 메소드의 인수가 된다
예를 들어, 다음 선언들은 서로 동일하다
for 반복문은 임시 변수(배열)와 for 반복문 및 buildArray(_:) 메서드 호출이 된다
새로운 for 반복문은 같은 타입의 값들이 순차적으로 붙어서 나열된 시퀀스를 반복하여 각 부분 결과를 해당 배열에 덧붙인다
임시 배열은 buildArray(_:) 호출의 인수로 전달된다
예를 들어, 다음 선언들은 서로 동일하다
resultBuilder에 buildFinalResult(_:) 메서드가 있으면, 최종 결과는 이 메서드 호출이 된다
이 변환이 항상 마지막이다
변환 동작을 임시 변수로 설명하긴 했지만,
resultBuilder를 사용한다고 실제로 어떤 새로운 선언을 코드에 보이게 생성하는 건 아니다
resultBuilder가 변환할 코드에는 break, continue, defer, guard, return, while, do-catch 문을 사용할 수 없다
변환 과정은 코드 안의 선언을 바꾸지 않으며,
이는 임시 상수와 변수를 사용하여 표현식을 한 조각씩 제작하게 해준다
이는 throw 문, 컴파일-시간 진단문, return 문을 담은 클로저도 바꾸지 않는다
가능할 때마다 변환을 합체한다
예를 들어, 표현식 4 + 5 * 6 는 그 함수를 여러 번 호출하기 보단 buildExpression(4 + 5 * 6) 가 된다
마찬가지로 중첩한 분기문은 단일 이진 트리 형태로 buildEither 메소드를 호출한다
19) Custom Result-Builder Attributes
resultBuilder 유형을 생성하면 동일한 이름의 사용자 지정 특성이 생성된다
해당 특성은 다음 위치에 적용할 수 있다
결과 제작자 특성을 적용해도 ABI 호환성엔 영향을 주지 않는다
결과 제작자 특성을 매개 변수에 적용하면 해당 특성이 함수 인터페이스의 일부가 되어, 소스 호환성에 효과를 줄 수 있다
20) requires_stored_property_inits
해당 특성을 클래스 선언에 적용하면
클래스 정의 부분에서 자신의 모든 저장 프로퍼티에 기본 값을 제공하길 요구한다
NSManagedObject를 상속한 어떤 클래스든 이 특성을 가진다고 추론한다
21) testable
해당 특성을 import 선언에 적용하면 그 모듈의 접근 제어를 바꾼 채로 불러와서 모듈 코드 테스트를 단순하게 해준다
불러온 모듈 안의 개체가 internal 접근-수준 지정자로 표시한 경우 , 마치 public 접근-수준 지정자로 선언한 것처럼 불러온다
internal 이나 public 접근-수준 지정자로 표시한 클래스 및 클래스 멤버는
마치 open 접근-수준 지정자로 선언한 것처럼 불러온다
불러온 모듈은 반드시 테스트 할 수 있는 상태인
엑스코드의 스킴(Scheme) 화면 안의 테스트(Test) 옵션에서 Debug executable이 켜진 상태로 컴파일해야 한다
22) UIApplicationMain
해당 특성을 클래스에 적용하면 응용 프로그램 위임 상태(the application delegate) 라는 걸 나타낸다
이 특성을 사용하는 건 UIApplicationMain 함수를 호출하고 해당 클래스의 이름을 위임 클래스의 이름으로 전달하는 것과 같다
해당 특성을 사용하지 않는다면,
최상단에서 UIApplicationMain(_:_:_:_:) 함수를 호출하는 코드가 있는 main.swift 파일을 제공 한다
예를 들어, 자신의 앱이 자신만의 UIApplication 하위 클래스를 주된 클래스(principal class)로 사용한다면,
이 특성을 사용하는 대신 UIApplicationMain(_:_:_:_:) 함수를 호출한다
실행 파일을 만들기 위해 컴파일하는 Swift 코드는 Top-Level Code 페이지에서 설명한 것처럼 최상위 진입점을 하나만 포함할 수 있다
23) unchecked
해당 특성을 채택한 프로토콜의 타입 선언 목록 부분의 프로토콜 타입에 적용하면 그 프로토콜 요구 사항을 강제하는 걸 끈다
지원하는 단 하나의 프로토콜은 Sendable이다
한 동시성 영역에서 다른 곳으로 안전하게 값을 보낼 수 있는 sendable 타입으로
Actor를 사용할 때 필수적으로 고려되는 특성이며, Concurrent 관련 코드에서 data race를 방지하기 위해 사용된다
24) usableFromInline
해당 특성을 함수, 메서드, 계산 속성, 서브 스크립트, 이니셜라이저, 디이니셜라이저 선언에 적용하면
선언과 동일한 모듈 안에서 정의한 인라인 가능 코드에서 해당 기호를 사용하는 걸 허용한다
선언엔 반드시 internal 접근 수준 자정자가 있어야 한다
usableFromInline 을 표시한 구조체나 클래스는 자신의 속성이 public 또는 usableFromInline 인 타입만 사용할 수 있다
usableFromInline 을 표시한 열거체는 자신의 case 원시값과 연관 값이 public 또는 usableFromInline인 타입만 사용할 수 있다
public 접근 수준 지정자와 마찬가지로 이 특성은 선언을 모듈의 공용 인터페이스의 일부로 표시한다
public과는 달리 컴파일러는 선언 기호를 내보낼지라도,
usableFromInline을 표시한 선언이 모듈 밖 코드에서 이름으로 참조되는 걸 컴파일러가 허용하진 않는다
하지만, 모듈 밖 코드는 런타임 동작을 사용하여 여전히 선언 기호와 상호 작용할 수 있다
inlinable 특성을 표시한 선언은 암시적으로 inlinable 코드에서 사용 가능하다
inlinable 특성을 부여하면 따로 usableFromInline 특성을 부여할 필요가 없다
inlinable이든 usableFromInline이든 internal 선언에 적용할 수 있긴 하지만, 두 특성을 모두 적용하면 에러가 발생한다
25) 규명 안된 접근 경고하기(warn_unqualified_access)
해당 특성을 최상단 함수, 인스턴스 메서드, 또는 class나 static 메서드에 적용하면
모듈 이름, 타입 이름, 또는 인스턴스 변수 및 상수 같은 앞선 규명자 없이
해당 함수나 메서드를 사용할 때 경고를 발생시킨다
‘앞선 규명자(preceding qualifier)’는 Result Transformations 챕터에서 예제에 있던 temporary.append(partialResult)에서의 temporary 같이 그 기호에 앞에서 그 기호가 어디에 소속되어 있는지를 규명하는 지시자를 말한다
해당 특성을 사용하면 동일한 scope에서 접근 가능한 똑같은 이름의 함수 사이에 모호함이 없도록 도와준다
ex. 스위프트 표준 라이브러리는 최상단 min(_:_:) 함수 및 비교 가능 원소의 시퀀스(sequence)를 위한 min() 메서드를 모두 포함한다
시퀀스 메서드는 warn_unqualified_access 특성으로 선언되어
Sequence 익스텐션 안에서 둘 중 하나를 사용하려할 때의 혼동을 줄이도록 해준다
26) 인터페이스 빌더에서 쓰는 선언 특성(Declaration Attributes Used by Interface Builder)
인터페이스 빌더 특성은 인터페이스 빌더에서 엑스코드와 동기화하기 위해 쓰는 선언 특성이다
스위프트는 다음의 인터페이스 빌더 특성을 제공하는데
IBAction, IBSegueAction, IBOutlet, IBDesignable, IBInspectable 이 있다
이러한 특성은 오브젝티브-C 에 있는 것과 개념이 똑같다
IBOutlet과 IBInspectable 특성은 클래스의 프로퍼티 선언에 적용한다
IBAction 과 IBSegueAction 특성은 클래스의 메서드 선언에 IBDesignable 특성은 클래스 선언에 적용한다
IBAction, IBSegueAction, IBOutlet, IBDesignable, IBInspectable 특성을 적용하는 건 objc 특성이기도 함을 암시한다
타입 특성
타입 특성은 타입에만 적용할 수 있다
1) autoclosure
해당 특성을 적용하면 인수 없는 클로저로 표현식을 자동 포장함으로써 해당 표현식의 평가를 늦춘다
함수나 메서드 선언에서 매개 변수 타입이 아무런 인자도 취하지 않으며 표현식 타입의 값을 반환하는 함수 타입이면,
그 매개 변수 타입에 이를 적용한다
autoclosure 특성의 사용법에 대한 예제는 Autoclosures와 Function Type 페이지 부분을 보도록하자
2) 협약(convention)
해당 특성을 함수의 타입에 적용하면 호출 협약을 나타낸다
‘The Swift Calling Convention’ 은 호출한 쪽의 매개 변수 전달 방법과 하위 루틴의 결과 반환 방법을 협의해서 정한 약속이다
호출 규약이라고도 하는데 규약은 프로토콜 (protocol) 을 의미하기 때문에 Convention은 협약이라고 해석하면 된다
convention 특성은 항상 다음의 인수 중 하나와 함께 나타난다
이는 스위프트 함수 값에 대한 표준 호출 협약이다
함수 값은 블록 객체에 대한 참조로 표현되며, 블록 객체는 객체 내에 호출 함수를 포함하는 id 호환 가능한 Objective-C 객체이다
호출 기능은 C 호출 규약을 사용한다
함수 값은 아무런 의미도 없으며 C 호출 협약을 사용한다
몇 가지를 제외하면 어떤 호출 협약의 함수든 다른 어떤 호출 협약인 함수가 필요할 때 사용할 수 있다
제네릭이 아닌 전역 함수나, 어떤 지역 변수도 캡쳐하지 않는 지역 함수, 또는 어떤 지역 변수도 캡쳐하지 않는 클로저는
C 호출 협약으로 변환할 수 있다
다른 스위프트 함수는 C 호출 협약으로 변환할 수 없다
오브젝티브-C 블럭 호출 협약인 함수는 C 호출 협약으로 변환 할 수 없다
3) escaping
해당 특성을 함수나 메서드 선언 안의 매개 변수 타입에 적용하면 나중에 실행하기 위해 매개 변수 값을 저장할 수 있다는 걸 나타낸다
이는 그 값이 호출 수명보다 오래 사는 걸 허용한다는 의미다
escaping 타입 특성인 함수 타입 매개 변수는 프로퍼티나 메서드에 self. 를 명시하여 사용할 걸 요구한다
escaping 특성의 사용법에 대한 예제는, Escaping Closures 페이지 부분을 보도록하자
4) Sendable
해당 특성을 함수의 타입에 적용하면 함수나 클로저가 보내기 가능하다는 걸 지시한다
주어진 타입의 값이 concurrent code에서 안전하게 사용될 수 있음을 의미한다
해당 특성을 함수 타입에 적용하는 건 비-함수 타입이 Sendable 프로토콜을 준수하는 것과 똑같은 의미다
함수나 클로저가 Sendable 값을 예상한 상황에서 사용되는데,
그 함수나 클로저가 Sendable에 필요한 요구 사항을 만족한다면, 함수나 클로저가 해당 특성이라고 추론한다
Sendable 함수 타입은 보내기 불가능한 함수 타입에 해당하는 것의 하위 타입이다
switch 문 case 절 특성(Switch Case Attributes)
switch 의 case 특성은 switch 문의 case 에만 적용할 수 있다
1) unknown
해당 특성을 switch문의 case에 적용하면
코드를 컴파일하는 시점에 알고 있는 어떤 열거체 case 와도 일치하지 않을 것으로 예상한다고 나타낸다
unknown 특성의 사용법에 대한 예제는, Switching Over Future Enumeration Cases 페이지 부분을 보도록 하자
The text was updated successfully, but these errors were encountered: