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
선언(declaration)은 프로그램에 새로운 이름이나 구조물(construct)을 구성한다
예를 들어, 선언을 사용하여 함수와 메서드를 도입하고, 변수와 상수를 도입하며,
열거체와, 구조체, 클래스, 및 프로토콜 타입을 정의한다
선언을 사용하면 기존 이름 붙인 타입의 동작을 확장하고
다른 곳에서 선언한 기호(symbols)를 자신의 프로그램 안으로 불러 올 수도 있다
스위프트에선 선언과 동시에 구현 또는 초기화한다는 점에서 대부분의 선언이 정의이기도 하다
그렇더라도, 프로토콜은 자신의 멤버를 구현하지 않기 때문에
대부분의 프로토콜 멤버는 선언일 뿐이다
편의상 그리고 스위프트에선 이런 구분이 그다지 중요하지 않기 때문에,
선언(declarations)이라는 용어가 선언과 정의 둘 다를 아우르게 된다
Top-Level Code(최상단 코드)
스위프트 소스 파일 안의 최상단 코드는 0개 이상의 구문과, 선언, 및 표현식으로 구성된다
기본적으로 소스 파일 최상단에 선언한 변수와 상수 및 그 외 이름 붙인 선언은
동일 모듈 안의 모든 소스 파일 코드에 접근 가능하다 Access Control Levels 페이지에서 설명한 접근 지정자로 선언을 표시함으로써
이런 기본 동작을 재정의할 수 있다
두 종류의 최상단 코드가 있는데,
최상단 선언 (top-level declarations)과 실행 가능한 최상단 코드(excutable top-level code)가 있다
최상단 선언은 선언으로만 구성하며, 모든 스위프트 소스 파일에서 허용한다
실행 가능한 최상단 코드는 선언만이 아니라 구문과 표현식도 담으며
프로그램의 최상단 진입점(top-level entry point)으로만 허용하게 된다
실행 파일(executable)을 만들려고 컴파일하는 스위프트 코드는
파일과 모듈로 어떻게 코드를 정돈하는지에 상관없이
다음 접근법 중 최대 하나만을 담아 최상단 진입점을 표시할 수 있는데
이는 main 특성, NSApplicationMain 특성, UIApplicationMain 특성,
main.swift 파일 또는 최상단(에서) 실행 가능한 코드를 담은 파일로 이를 명시할 수 있다
Code Blocks(코드 블럭)
코드 블럭(code block)은 구문을 서로 그룹묶기 위해 다양한 선언 및 제어 구조에서 사용한다
형식은 다음과 같다
{
statements
}
코드 블럭 안의 구문(statements)은 선언과, 표현식 및 다른 종류의 구문을 포함하며
소스 코드에 나타난 순서대로 실행한다
Import 선언
import 선언은 현재 파일 밖에서 선언한 기호에 접근하게 해준다
기본 형식은 전체 모듈을 불러오며; import 키워드와 그 뒤의 모듈 이름으로 구성합니다
import module
어느 기호를 불러올지 더 자세하게 제한하면
모듈이나 하위 모듈 안의 특정 하위 모듈 또는 특정 선언을 지정할 수 있다
이런 자세한 형식을 사용할 땐, 선언한 모듈이 아닌 불러온 기호만 현재 영역에서 사용 가능하다
import import kind module.symbol name
import module.submodule
상수 선언
상수 선언(constant declaration)은 프로그램에 상수 이름 값을 도입한다
상수 선언은 let 키워드로 선언하며 형식은 다음과 같다
let constant name: type = expression
상수 선언은 상수 이름(constant name)과 이니셜라이저 표현식(expression) 값 사이에 변경 불가한 연결을 정의하여
상수 값을 설정한 후엔 바꿀 수 없다
그렇더라도, 상수를 클래스 객체로 초기화하면
상수 이름과 그게 참조할 수 객체 사이의 연결은 바꿀 수 없지만
객체 그 자체는 바꿀 수 있다
즉, 상수는 값을 바꿀 수 없는데
클래스 같은 참조 타입은 주소가 값이기 때문에 바꿀 수 없는 것은 주소이며
해당 주소가 가리키는 곳의 내용은 바뀌어질 수 있다
전역, 프로그램의 최상단 진입점에서 상수를 선언할 땐
반드시 값으로 초기화해야 한다
함수나 메서드에서 상수 선언을 할 때
값을 최초로 읽기 전에 설정한다고 보증하는 한 나중에 초기화할 수 있다(lazy)
만약 컴파일러가 상수의 값이 절대 읽히지 않는다는 것을 증명할 수 있다면,
상수는 값이 설정될 필요가 전혀 없다
클래스나 구조체 선언에서 상수 선언을 할 땐, 상수 프로퍼티라고 고려한다
상수 선언은 계산 프로퍼티가 아니며 따라서 게터나 세터가 없다
상수 선언의 상수 이름(constant name)이 튜플 패턴이면,
튜플 안의 각 항목 이름을 이니셜라이저 표현식(expression) 안의 해당 값과 연결한다
let(firstNumber, secondNumber)=(10,42)
예제에서, firstNumber는 10 이라는 값에 ‘이름 붙인 상수’ 이며,
secondNumber는 42 라는 값에 ‘이름 붙인 상수’ 다
두 상수 모두 이제 독립적으로 사용할 수 있다
print("The first number is \(firstNumber).")// Prints "The first number is 10."print("The second number is \(secondNumber).")// Prints "The second number is 42."
Type Inference에서 설명한 것처럼,
상수 이름(constant name)의 타입을 추론할 수 있을 땐 : type 이라는 타입 명시 주석은 옵션이다
상수 타입 프로퍼티를 선언하려면, 선언에 static 선언 수정자를 표시한다
클래스의 상수 타입 프로퍼티는 항상 암시적으로 이미 final이며
class 나 final 선언 수정자를 표시하여 하위 클래스 재정의를 허용하거나 불허할 수 없다
타입 프로퍼티에 대한 더 많은 정보는 Type Properties 페이지에서 알 수 있다
변수 선언(variable declaration)은 프로그램에 변수 이름 값을 도입하며 var 키워드로 선언한다
변수 선언은 여러 가지 형식으로 서로 다른 종류의 이름을 붙인 변경 가능한 값을 선언하는데,
이는 저장 및 계산 변수 / 프로퍼티, 저장 변수와 프로퍼치 옵저버 및 static 변수 프로퍼티을 포함한다
사용할 적절한 형식은 변수가 선언되는 scope와 선언하려는 변수의 종류에 따라 달라진다
Overriding에서 설명한 것처럼
하위 클래스의 프로퍼티 선언을 override 선언 수정자로 표시함으로써
하위 클래스에서 프로퍼티를 재정의할 수 있다
1) 저장 변수와 저장 변수 프로퍼티(Stored Variables and Stored Variable Properties)
다음 형식은 저장 변수 또는 저장 변수 프로퍼티를 선언한다
var variable name: type = expression
해당 형식의 변수 선언은 전역이나 함수 지역, 또는 클래스나 구조체 선언에서 정의한다
전역이나 함수 지역에서 할 때, 저장 변수(stored variable)라고 한다
클래스나 구조체 선언에서 선언할 때, 저장 변수 프로퍼티(stored variable property)라고 한다
이니셜라이저 표현식(expression)은 프로토콜 선언에 있을 수 없지만,
다른 모든 상황에서 이니셜라이저 표현식(expression)은 옵션이다
그렇더라도, 이니셜라이저 표현식 (expression)이 아무 것도 없으면,
반드시 : type 이라는 타입 명시 주석을 변수 선언에 포함해야 한다
상수 선언 처럼 변수 이름(variable name)이 튜플 패턴이면,
튜플 안의 각 항목 이름을 이니셜라이저 표현식(expression) 안의 해당 값과 연결한다
이름이 알 수 있듯이 저장 변수 또는 저장 변수 프로퍼티 값은 메모리에 저장한다
2) 계산 변수와 계산 프로퍼티(Computed Variables and Computed Properties)
다음 형식은 계산 변수 또는 계산 프로퍼티를 선언한다
var variable name: type {
get {
statements
}
set(setter name) {
statements
}
}
이 형식의 변수 선언은 전역이나 함수 지역 또는 클래스, 구조체, 열거체 및 익스텐션 선언에서 정의한다
전역이나 함수 지역에서 할 때, 계산 변수 (computed variable)라고 한다
클래스, 구조체 또는 익스텐션 선언에서 할 때, 계산 프로퍼티(computed property)라고 한다
게터(getter)로 값을 읽고, 세터(setter)로 값을 쓴다 Read-Only Computed Properties에서 설명한 것 처럼 세터 구성은 옵션이라
게터만 필요할 땐, 두 절 모두 생략하고 단순히 요청 값만 직접 반환할 수 있다
세터 절을 제공하면, 게터 절도 반드시 제공해야 한다
setter name과 테두리 괄호는 옵션이다
setter name을 제공하면 설정자의 매개 변수 이름으로 이걸 사용한다
setter name을 제공하지 않으면, Shorthand Setter Declaration에서 설명한 것처럼, newValue 가 설정자의 기본 매개 변수 이름이 된다
저장 변수 및 저장 변수 프로퍼티와는 달리,
계산 변수와 계산 프로퍼티를 메모리에 저장하지 않는다
3) 저장 변수 옵저버와 프로퍼티 옵저버(Stored Variable Observers and Property Observers)
willSet 과 didSet 옵저버가 있는 저장 변수나 프로퍼티를 선언할 수도 있다
옵저버가 있는 저장 변수나 프로퍼티의 선언 형식은 다음과 같다
var variable name: type = expression {
willSet(setter name) {
statements
}
didSet(setter name) {
statements
}
}
이 형식의 변수 선언은 전역, 함수 지역, 또는 클래스, 구조체 선언에서 정의한다
전역이나 함수 지역에서 할 때, 옵저버를 저장 변수 옵저버(stored variable observers)라고 한다
클래스나 구조체 선언에서 선언할 때, 옵저버를 프로퍼티 옵저버(property observers) 라고 한다
프로퍼티 옵저버는 어떤 저장 프로퍼티에든 추가할 수 있다 Overriding Property Observers에서 설명한 것 처럼
하위 클래스 안에서 프로퍼티를 재정의함으로써
저장이든 계산이든 상관없이 어떤 상속 프로퍼티에도 프로퍼티 옵저버를 추가할 수 있다
이니셜라이저 표현식(expression)은 클래스나 구조체 선언에서는 옵션이지만 다른 곳에선 필수다
이니셜라이저 표현식 (expression)으로 타입을 추론할 수 있을 땐
타입 명시 주석이 옵션이다
프로퍼티 값을 최초로 읽을 때 해당 표현식을 평가한다
프로퍼티 초기 값을 읽지 않고 덮어 쓰면, 프로퍼티에 최초로 쓰기 전 해당 표현식을 평가한다
willSet과 didSet 옵저버는 변수나 프로퍼티 값을 설정할 때
이를 관찰하고 적절히 응답할 방법을 제공한다
변수나 프로퍼티를 최초로 초기화할 땐 옵저버를 호출하지 않는다
대신, 초기화 컨텍스트 외부에서 값을 설정할 때만 호출한다
willSet 옵저버는 변수나 프로퍼티 값 설정 직전에 호출된다
새 값은 willSet 옵저버에 상수로 전달되어, 따라서 willSet 절 구현부에선 바꿀 수 없다
didSet 옵저버는 새 값 설정 직후에 호출된다
willSet 옵저버와는 대조적으로, 여전히 접근이 필요한 경우를 위해
didSet 옵저버에 변수나 프로퍼티의 예전 값을 전달한다
즉, 변수나 프로퍼티 자신의 didSet 옵저버 절에서 값을 할당하면,
할당된 새 값이 방금 설정되어 willSet 옵저버에게 전달된 값을 대체한다
willSet 과 didSet 절의 setter name 과 테두리 괄호는 옵션이다
setter name을 제공하면, willSet 과 didSet 옵저버의 매개 변수 이름으로 이걸 사용한다
설정자 이름을 제공하지 않으면,
willSet 옵저버의 기본 매개 변수 이름은 newValue이고
didSet 옵저버의 기본 매개 변수 이름은 oldValue다
willSet 절을 제공할 뗀 didSet 절이 옵션이며,
didSet 절을 제공할 땐 willSet 절이 옵션이다
didSet 옵저버 본문에서 예전 값을 참조하면, 예전 값이 사용 가능하도록 옵저버 전에 게터를 호출한다
그 외 경우, 상위 클래스의 게터를 호출하지 않고 새 값을 저장한다
아래 예제는 상위 클래스에서 정의한 계산 프로퍼티에 옵저버를 추가하도록 하위 클래스에서 재정의하는 걸 보여준다
classSuperclass{privatevarxValue=12varx:Int{get{print("Getter was called"); return xValue }set{print("Setter was called"); xValue = newValue }}}// This subclass doesn't refer to oldValue in its observer, so the// superclass's getter is called only once to print the value.classNew:Superclass{overridevarx:Int{
didSet {print("New value \(x)")}}}letnew=New()
new.x =100// Prints "Setter was called"// Prints "Getter was called"// Prints "New value 100"// This subclass refers to oldValue in its observer, so the superclass's// getter is called once before the setter, and again to print the value.classNewAndOld:Superclass{overridevarx:Int{
didSet {print("Old value \(oldValue) - new value \(x)")}}}letnewAndOld=NewAndOld()
newAndOld.x =200// Prints "Getter was called"// Prints "Setter was called"// Prints "Getter was called"// Prints "Old value 12 - new value 200"
타입 변수 프로퍼티를 선언하려면, 선언에 static 선언 수정자를 표시한다
클래스에선 그 대신 타입 계산 프로퍼티에 class 선언 수정자를 표시하여
하위 클래스에서 상위 클래스 구현을 재정의하게 할 수 있다
타입 프로퍼티는 Type Properties 페이지에서 더 알아 볼 수 있다
타입 별명 선언(Type Alias Declaration)
타입 별명 선언(type alias declaration)은 프로그램에 기존 타입의 별명을 도입할 수 있다
타입 별명 선언은 typealias 키워드로 하며 형식은 다음과 같다
typealias name = existing type
타입 별명의 선언 후엔 프로그램 어디서나 기존 타입(existing type) 대신 별명 이름(name)을 사용할 수 있다
기존 타입(existing type)은 이름 붙인 타입이나 복합 타입일 수 있다
타입 별명은 새로운 타입을 생성하지 않으며
단순히 한 이름이 기존 타입을 가리키도록 한다
타입 별명 선언은 제네릭 매개 변수를 사용하여 기존 제네릭 타입에 이름을 줄 수 있다
타입 별명은 기존 타입의 제네릭 매개 변수 중 일부 또는 모두에 고정 타입을 제공할 수 있다
예를 들면 다음과 같다
typealiasStringDictionary<Value>=Dictionary<String,Value>// The following dictionaries have the same type.vardictionary1:StringDictionary<Int>=[:]vardictionary2:Dictionary<String,Int>=[:]
제네릭 매개 변수가 있는 타입 별명을 선언할 땐,
해당 매개 변수에 대한 제약 조건과 기존 타입의 제네릭 매개 변수에 대한 제약 조건이 반드시 정확히 일치해야 한다
예를 들면 다음과 같다
해당 타입 별명이 없다면 sum 함수가 associatedtype을
T.Element 대신 T.Iterator.Element 라고 참조해야 작동하게 된다 Protocol Associated Type Declaration 페이지 부분을 통해 해당 메커니즘을 확인할 수 있다
함수 선언
함수 선언(function declaration)은 프로그램에 함수나 메서드를 도입하게 된다
클래스, 구조체, 열거체 또는 프로토콜 안에서 선언한 함수를 메서드(method)라고 하며
함수 선언은 func 키워드로 하며 형식은 다음과 같다
func function name(parameters) -> return type {
statements
}
함수 반환 타입이 Void면 다음 처럼 반환 타입을 생략할 수 있다
func function name(parameters) {
statements
}
각각의 매개 변수 타입은 반드시 포함해야 하며 이는 추론할 수 있는게 아니다
매개 변수 타입 앞에 inout 을 쓰면, 함수 영역 안에서 매개 변수를 수정할 수 있다
In-Out 매개 변수는, 밑의, In-Out Parameters에서 자세히 논의해보자
함수 선언 구문(statements)이 단일 표현식만 포함하고 있으면
해당 표현식 값을 반환하는 걸로 이해한다
표현식 타입과 함수 반환 타입이 Void도 아니고
Never 처럼 어떤 case 값도 없는 열거체가 아닐 경우에만 이런 암시적 반환 구문을 고려한다
튜플 타입을 함수의 반환 타입으로 사용하면 함수가 여러 개의 값을 반환할 수 있다
함수 정의를 또 다른 함수 선언 안의 본문에서 나타낼 수 있는데
이런 종류의 함수를 중첩 함수(nested function)라고 한다
In-Out 매개 변수처럼 절대 escape 하지 않는다는 걸 보증한 값
또는 nonescaping 함수 인수로 전달한 값을 붙잡으면
중첩 함수는 벗어나지 않는다
함수 매개 변수는 여러 가지 형식으로 된 각각의 매개 변수들을 쉼표로 구분한 목록이다
함수 호출의 인수 순서는 함수 선언의 매개 변수 순서와 반드시 일치해야 한다
매개 변수 목록에서 가장 단순한 요소의 형식은 다음과 같다
parameter name: parameter type
매개 변수에는 함수 본문 안에서 사용하는 이름뿐 아니라,
함수나 메서드 호출 때 사용하는 인수 라벨도 있다
기본적으로 매개 변수 이름을 인수 라벨로도 사용한다
예를 들면 다음과 같다
func f(x:Int, y:Int)->Int{return x + y }f(x:1, y:2)// both x and y are labeled
인수 라벨의 기본 동작을 다음 형식 중 하나로 재정의할 수도 있다
argument label parameter name: parameter type
_ parameter name: parameter type
매개 변수 이름 앞의 이름은 매개 변수에 명시적 인수 라벨을 주는데,
이는 매개 변수 이름과 다를 수 있다
해당 인수는 함수나 메서드 호출에서 반드시 주어진 인수 라벨을 사용해야 한다
매개 변수 이름 앞의 밑줄(_)이 있으면 인수 라벨이 표시되지 않는다
해당 인수는 함수나 메서드 호출 쪽에서도 반드시 아무런 라벨이 없어야 한다
func repeatGreeting(_ greeting:String, count n:Int){/* Greet n times */}repeatGreeting("Hello, world!", count:2)// count is labeled, greeting is not
2) In-Out 매개변수
In-Out 매개 변수는 다음 처럼 전달한다
함수 호출 때, 인수 값을 복사한다
함수 본문에서, 복사본을 수정한다
함수 반환 때, 복사본 값을 원본 인자에 다시 할당한다
해당 동작을 복사-입력 복사-출력 (copy-in copy-out) 또는 값-결과에 의한 호출 (call by value result)이라고 한다
‘값-결과에 의한 호출 (call by value result)’ 은
함수 안에서는 ‘값에 의한 호출 (call by value)’ 처럼 동작하고,
함수 반환 시에는 ‘참조에 의한 호출 (call by reference)’ 처럼 동작한다
swift는 상황에 따라 참조에 의한 호출과 값-결과에 의한 호출을 적절하게 선택하여 인자를 전달한다
예를 들어, 계산 프로퍼티 또는 옵저버가 있는 프로퍼티를
In-Out 매개 변수로 전달할 땐, 함수 호출 시엔 게터를 호출하고 함수 반환 시엔 세터를 호출하게 된다
최적화로써 인수가 메모리의 물리 주소에 저장된 reference 값일 땐,
함수 본문 안과 밖 모두에서 동일한 메모리 위치를 사용한다
이런 최적화 동작을 참조에 의한 호출(call by reference)이라고 하며
복사-입력 복사-출력 모델의 모든 필수 조건을 만족하면서도 복사의 오버헤드를 제거한다
즉, 스위프트는 인수가 value인지 reference인지에 따라,
값에 의한 호출과 참조에 의한 호출이라는 두 가지 방식으로 In-Out 매개 변수 전달을 최적화한다
참조에 의한 호출 최적화에 의존하지 않고
주어진 복사-입력 복사-출력 모델로 코드를 작성하는 경우에는
최적화 여부와 관계없이 올바르게 작동하게 된다
In-Out 매개 변수의 최적화는 컴파일러가 컴파일 단게에서 알아서 신경쓰는 문제이므로,
개발자는 값에 의한 호출(복사-입력 복사 출력) 방식을 사용하기만 하면 된다
함수 안에선 현재 영역에서 원본 값이 사용 가능하더라도,
In-Out 매개변수에 인수로 전달한 값엔 접근하지 않아야 한다
원본에 접근하면 값에 대한 동시 접근이라, 스위프트의 메모리 독점권 보장을 위반한다
똑같은 이유로 여러 개의 In-Out 매개 변수에 동일한 값을 전달할 순 없다
어길 경우 메모리 접근 충돌이 발생하게 된다
메모리 안전성과 메모리 독점권에 대한 더 많은 정보는 Memory Safety 페이지를 참고해보자
In-Out 매개 변수를 붙잡는 클로저나 중첩 함수는 반드시 nonescaping 해야한다
In-Out 매개 변수를 변경(mutating) 없이 캡쳐해야 하는 경우가 있다면,
캡쳐 리스트를 써서 매개 변수가 변경 불가능하다는 걸 명시하고 캡쳐해야 한다
func someFunction(a:inoutInt)->()->Int{return{[a]inreturn a +1}}
In-Out 매개 변수를 캡쳐하고 변경할 필요가 있다면,
함수 반환 전에 모든 변경의 종료를 보장하는 다중 쓰레드 코드처럼,
명시적 지역 복사본(local copy)을 사용하면 된다
func multithreadedFunction(queue:DispatchQueue, x:inoutInt){// Make a local copy and manually copy it back.varlocalX= x
defer{ x = localX }// Operate on localX asynchronously, then wait before returning.
queue.async{someMutatingOperation(&localX)}
queue.sync{}}
다음 형식을 사용하면 매개 변수를 무시하고 여러 개의 값을 취하며 기본 값을 제공할 수 있다
_ : parameter type
parameter name: parameter type...
parameter name: parameter type = default argument value
밑줄(_) 매개 변수는 명시적으로 무시하며 함수 본문 안에서 접근할 수 없다
기초 타입 이름 바로 뒤에 세 점(...)이 있는 매개 변수는 가변 매개 변수라고 이해한다
가변 매개 변수 바로 뒤에 있는 매개 변수엔 반드시 인수 라벨이 있어야 한다
인수 라벨이 없으면 새로운 매개 변수가 아닌 가변 매개 변수의 한 원소라고 인식하기 때문이다
함수의 가변 매개 변수는 여러 개일 수 있다
가변 매개 변수는 기초 타입 이름의 원소를 담은 배열로 취급하는데
예를 들어, Int... 라는 가변 매개 변수는 [Int] 로 취급한다
가변 매개 변수의 사용 예는, Variadic Parameters 부분을 보도록하자
자신의 타입 뒤에 같음 기호(=)와 표현식이 있는 매개 변수는
주어진 표현식에 해당하는 기본 값을 가진다고 이해한다
주어진 표현식은 함수 호출 때 평가하며
함수 호출 때 매개 변수를 생략하면 기본 값을 대신 사용하게 된다
func f(x:Int=42)->Int{return x }f()// Valid, uses default valuef(x:7)// Valid, uses the value providedf(7)// Invalid, missing argument label
4) 특별한 종류의 메서드
self를 수정하는 열거체나 구조체의 메서드엔 반드시 mutating 선언 수정자를 표시해야 한다
상위 클래스 메서드를 재정의한 메서드엔 반드시 override 선언 수정자를 표시해야 한다
override 수정자 없이 메서드를 재정의하거나
상위 클래스 메서드를 재정의하지 않는 메서드에 override 수정자를 사용하면 컴파일 에러가 발생한다
타입의 인스턴스라기 보단 타입 자체에 결합된 메서드는
반드시 열거체와 구조체라면 static 선언 수정자를,
클래스라면 static 이나 class 선언 수정자를 표시해야 한다
class 선언 수정자로 표시한 클래스 타입 메사드는 하위 클래스 구현에서 재정의할 수 있으며
class final 이나 static 으로 표시한 클래스 타입 메서드는 재정의할 수 없다
5) 특별한 이름의 메서드
특수한 이름의 여러가지 메서드로 함수 호출 구문을 수월하게 할 수 있다
타입에서 해당 메서드 중 하나를 정의하면, 타입의 인스턴스를 함수 호출 구문에 사용할 수 있다
함수 호출은 인스턴스의 특수 이름 메서드 중 하나를 호출하는 걸로 이해한다
클래스, 구조체, 열거체 타입은 dynamicCallable(동적으로 호출 가능)에서 설명한 것처럼
dynamicallyCall(withArguments:) 메서드나 dynamicallyCall(withKeywordArguments:) 메서드를 정의하거나
또는, 아래 설명 처럼 함수-처럼-호출하는 메서드를 정의함으로써 함수 호출 구문을 지원할 수 있다
타입에서 함수-처럼-호출하는 메서드와 dynamicCallable 특성의 메서드를 둘 다 정의하면,
어느 메서드든 사용할 수 있는 상황에선 컴파일러가 함수-처럼-호출하는 메서드에 우선권을 주게된다
함수-처럼-호출하는 메서드의 이름은 callAsFunction()이거나,
아니면 callAsFunction( 으로 시작하고 라벨이 있거나 없는 인수를 추가한 이름이다
예를 들어, callAsFunction(::) 과 callAsFunction(something:) 모두 함수-처럼-호출하는 메서드 이름으로 유효하다
다음의 함수 호출들은 서로 같다
structCallableStruct{varvalue:Intfunc callAsFunction(_ number:Int, scale:Int){print(scale *(number + value))}}letcallable=CallableStruct(value:100)callable(4, scale:2)
callable.callAsFunction(4, scale:2)// Both function calls print 208.
함수-처럼-호출하는 메서드와 dynamicCallable 특성인 메서드 사이에는
얼마나 많은 정보를 타입 시스템에 부호화(encode)해서 넣을 것인지
그리고 런타임에 얼마나 많은 동적 동작이 가능한지에 대해서 서로 다르게 절충(trade-offs) 한다
함수-처럼-호출하는 메서드를 선언할 땐, 인수의 개수와, 각각의 인수 타입과 라벨을 지정한다
dynamicCallable 특성인 메서드는 인수 배열을 들고 있는데 쓸 타입만을 지정한다
함수-처럼-호출하는 메서드나, dynamicCallable 특성인 메서드를 정의한다고,
함수 호출 표현식이 아닌 곳에서 해당 타입의 인스턴스를 마치 함수인 것처럼 사용할 수 있는 건 아니다
예를 들면 다음과 같다
dynamicMemberLookup에서 설명한 것처럼,
subscript(dynamicMemberLookup:) 서브 스크립트는 멤버 찾아보기 구문을 수월하게 할 수 있다
6) Throwing 함수와 메서드
에러를 던질 수 있는 함수와 메서드엔 반드시 throws 키워드를 표시해야 한다
이러한 함수와 메서드를 throwing 함수와 throwing 메서드라고 한다
형식은 다음과 같다
func function name(parameters) throws -> return type {
statements
}
throwing 함수나 메서드 호출문은 반드시 try 나 try! 표현식 즉, try 나 try! 연산자 영역으로 감싸야 한다
throws 키워드는 함수 타입의 일부이며,
던지지 않는 함수는 throwing 함수의 하위 타입이다
그 결과, throwing 함수를 예상한 곳에서 던지지 않는 함수를 사용할 수도 있다
함수가 에러를 던질 수 있는지 여부만 가지고선 함수를 중복 정의할 순 없다
에러를 던지는 것과 던지지 않는 것만 차이가 나고 나머지는 똑같은 함수를 둘 수는 없다
그렇더라도 함수의 매개 변수(parameter)가 에러를 던질 수 있는지를 기초로는 함수를 중복 정의할 수 있다
throwing 메서드는 던지지 않는 메서드를 재정의할 수도 없고,
던지지 않는 메서드에 대한 프로토콜 요구 사항을 throwing 메서드로 만족할 수도 없다
그렇더라도, 던지지 않는 메서드는 throwing 메서드를 재정의할 수 있고,
throwing 메서드에 대한 프로토콜 요구 사항을 던지지 않는 메서드로 만족할 수도 있다
throwing 함수는 에러를 던질 수도 있고 아닐 수도 있다
그러므로 throwing 함수를 던지지 않는 함수로 재정의하는 건 자연스럽다
하지만, 던지지 않는 함수를 throwing 함수로 재정의하면 원래 정의한 성질에 맞지 않게 된다
7) Rethrowing 함수와 메서드
함수나 메서드를 rethrows 키워드로 선언하면
함수 매개 변수 중 하나가 에러를 던지는 경우에만
에러를 발생시킨다는 것을 나타내기 위해 rethrows 키워드를 사용하여 선언할 수 있다
이러한 함수와 메서드를 rethrowing 함수와 rethrowing 메서드라고 한다
rethrowing 함수와 메서드에는 반드시 적어도 하나의 throwing 함수 매개 변수가 있어야 한다
rethrowing 함수나 메서드는 catch 절 안에서만 throw 문을 담을 수 있다
이는 throwing 함수 호출을 do-catch 문 안에서 하게 하고
catch 절의 에러 처리를 또 다른 에러를 던지는 걸로 하게 해준다
또한, catch 절은 반드시 rethrowing 함수의 throwing 매개 변수가 던진 에러만 처리해야 한다
예를 들어, 다음은 catch절이 alwaysThrows()가 던진 에러도 처리할 것이기 때문에 무효한 구분이다
throwing 메서드는 rethrowing 메서드를 재정의할 수도 없고,
rethrowing 메서드에 대한 프로토콜 요구 사항을 throwing 메서드로 만족할 수도 없다
그렇더라도, rethrowing 메서드는 throwing 메서드를 재정의 할 수 있고,
throwing 메서드에 대한 프로토콜 필수 조건을 rethrowing 메서드로 만족할 수도 있다
rethrowing 함수는 함수 매개 변수가 던진 에러를 다시 던진다는 걸 빼면,
함수 자체로는 에러를 던지지 않는 함수라고 할 수 있다
8) 비동기 함수와 메서드
비동기로 실행하는 함수와 메서드엔 반드시 async 키워드를 표시해야 한다
이러한 함수와 메서드를 비동기 함수(asynchronous functions)와 비동기 메서드(asynchronous methods)라고 한다
형식은 다음과 같다
func function name(parameters) async -> return type {
statements
}
비동기 함수나 메서드 호출문은 반드시 await 표현식으로 감싸야 한다
즉, 반드시 await 연산자 영역 안에 있어야 한다
async 키워드는 함수 타입의 일부이며, 동기 함수는 비동기 함수의 하위 타입이다
그 결과, 비동기 함수를 예상한 곳에서 동기 함수를 사용할 수도 있다
예를 들어, 비동기 메서드를 동기 메서드로 재정의할 수도 있고,
비동기 메서드를 요구한 프로토콜 요구 사항을 동기 메서드로 만족시킬 수 있다
함수가 비동기인지 아닌지 여부로 함수를 중복 정의할 수 있다
호출한 쪽에서 어느 중복 정의를 사용할지는 상황으로 결정하는데
비동기 상황이면 비동기 함수를 사용하고, 동기 상황이면 동기 함수를 사용한다
비동기 메서드는 동기 메서드를 재정의할 수도 없고,
동기 메서드에 대한 프로토콜 요구 사항을 비동기 메서드로 만족할 수도 없다
그렇더라도, 동기 메서드는 비동기 메서드를 재정의할 수 있고
비동기 메서드에 대한 프로토콜 요구 사항을 동기 메서드로 만족시킬 수 있다
9) 절대 반환하지 않는 함수
스위프트는 함수 또는 메서드가 호출자에게 반환되지 않음을 나타내는 Never 타입을 정의 한다
Never 반환 타입인 함수와 메서드를 nonreturning이라고 한다
nonreturning 함수와 메서드는 복구할 수 없는 에러를 일으키거나
무한정 계속하는 일련의 작업을 시작한다
그렇지 않으면 호출 바로 뒤에 실행될 코드를 절대 실행하지 않는다는 걸 의미한다
throwing 및 rethrowing 함수는 자신이 반환하지 않더라도,
적절한 catch 절로 프로그램 제어를 옮길 순 있다
Guard Statement에서 논의한 것처럼,
nonreturning 함수나 메서드를 호출하여 ‘guard’ 문의 else 절로 결론지어 끝낼 수 있다
nonreturning 메소드를 재정의할 순 있지만,
새 메소드는 자신의 반환 타입과 반환하지 않는다는 동작을 반드시 유지해야 한다
열거체 선언
열거체 선언(enumeration declaration)은 프로그램에 이름지은 열거체 타입을 도입한다
열거체 선언에는 두 개의 기초 형식이 있으며 enum 키워드로 선언한다
어느 형식의 열거체 선언 본문이든 0개 이상의 열거체 case(enumeration cases)라는-값을 담고 있는데,
여기엔 계산 프로퍼티, 인스턴스 메소드, 타입 메소드, 이니셜라이저, type aliases 및
심지어 다른 열거체, 구조체, 클래스, Actor 선언도 포함한다
열거체 선언에 디이니셜라이저나 프로토콜 선언을 담을 순 없다
열거체 타입은 어떤 개수의 프로토콜이든 채택할 수 있지만
클래스, 구조체, 다른 열거체를 상속할 순 없다
클래스 및 구조체와는 달리, 열거체 타입에는 암시적으로 제공하는 기본 이니셜라이저가 없으며
모든 이니셜라이저는 반드시 명시적으로 선언해야 한다
이니셜라이저는 열거체 안의 다른 이니셜라이저에 작업을 위임할 수 있지만,
이니셜라이저가 self에 열거체 case 중 하나를 할당한 후에만 초기화 과정이 완료된다
1) 어떤 타입의 case 든 가지는 열거체(Enumerations with Cases of Any Type)
다음 형식으로 선언한 열거체 타입은 어떤 타입의 열거체 case든 담는다
enum enumeration name: adopted protocols {
case enumeration case 1
case enumeration case 2(associated value types)
}
이런 형식으로 선언한 열거체를 다른 프로그래밍 언어에서는 discriminated unions(차별화된 공용체)라고 한다
이런 형식에선 각각의 case 블럭을 case 키워드와
그 뒤에 쉼표로 구분한, 하나 이상의 열거체 case 로 구성한다
각각의 case 이름은 반드시 유일해야 한다
각각의 case 가 주어진 타입의 값을 저장한다고 지정할 수도 있다
case 이름 바로 뒤의 결합 값 타입(associated value types) 튜플 안에 이러한 타입을 지정하면 된다
결합 값을 저장한 열거체 case는 지정한 결합 값으로 열거체 인스턴스를 생성하는 함수처럼 사용할 수 있다
그리고 함수처럼 열거체 case로의 참조를 가질 수도 나중에 이를 코드에 적용할 수도 있다
enumNumber{case integer(Int)case real(Double)}letf=Number.integer
// f is a function of type (Int) -> Number// Apply f to create an array of Number instances with integer valuesletevenInts:[Number]=[0,2,4,6].map(f)
열거체는 재귀 구조, 즉, 결합 값이 열거체 타입 그 자체의 인스턴스인 case를 가질 수 있다
‘재귀 (recursion)’ 란 하나의 컴퓨터 문제를 더 작은 동일 문제를 풀어서 해결해 나가는 방법으로
하나의 함수 안에서 자기 자신을 호출하여 이를 구현한다
하지만, 열거체 타입 인스턴스는 값 의미 구조를 가지며, 이는 메모리 안에 고정 구획을 가진다
여기서 말하는 메모리 안의 고정 구획(fixed layout)을 보통 스택(stack)이라고 한다
열거체 인스턴스는 값 의미 구조라 스택에 저장하므로,
한 열거체 인스턴스가 다른 인스턴스를 호출하려면
해당 인스턴스로의 참조도 따로 저장해야 한다
해당 인스턴스로의 참조를 추가하여 저장하는 걸 ‘간접 계층 (layer-of-indirection)을 집어 넣는다’ 고 한다
재귀를 지원하려면 컴파일러가 반드시 간접 계층을 집어 넣어야 한다
특별한 한 열거체 case가 간접 사용을 할 수 있게 하려면,
거기에 indirect 선언 수정자를 표시하면 된다
간접 case 엔 반드시 결합 값이 있어야 한다
3) Raw-Value 타입의 case 를 가지는 열거체(Enumerations with Cases of a Raw-Value Type)
다음 형식으로 선언한 열거체 타입은 동일한 기초 타입의 열거체 case를 담는다
enum enumeration name: raw-value type, adopted protocols {
case enumeration case 1 = raw value 1
case enumeration case 2 = raw value 2
}
해당 형식에선 각각의 case 블럭을 case 키워드와 뒤에 쉼표로 구분한 하나 이상의 열거체 case로 구성한다
첫 번째 형식의 case와는 달리, 각각의 case는, 원시 값(raw value)이라는 동일한 기초 타입의 실제 값을 가진다
이러한 값의 타입은 원시 값 타입(raw-value type)에서 정하며
반드시 정수, 부동 소수점 수, 문자열, 단일 문자를 나타내야 한다
특히, 원시 값 타입(raw-value type)은 Equatable 프로토콜과
다음의 프로토콜 중 하나를 반드시 준수해야 하는데
정수 글자 값: ExpressibleByIntegerLiteral,
부동-소수점 글자 값: ExpressibleByFloatLiteral,
어떤 개수의 문자든 담는 문자열 글자 값: ExpressibleByStringLiteral,
단일 문자만 담는 문자열 글자 값: ExpressibleByUnicodeScalarLiteral / ExpressibleByExtendedGraphemeClusterLiteral
각각의 case는 반드시 유일한 이름을 가지고 유일한 원시 값을 할당해야한다
원시 값 타입을 Int로 정했는데 case 값을 명시하지 않으면,
0, 1, 2, 등의 값을 암시적으로 할당한다
할당 안한 각각의 Int 타입 case엔 이전 case 원시 값에서 자동 증가한 원시 값을 암시적으로 할당하게 된다
enumExampleEnum:Int{case a, b, c =5, d
}
위 예제에서,
ExampleEnum.a 의 원시 값은 0 이고 ExampleEnum.b 값은 1이다
ExampleEnum.c 값은 5로 명시하여 설정했기 때문에,
ExampleEnum.d 값은 따라서 5 에서 자동 증가한 6이 된다
원시 값 타입을 String으로 정했는데 case 값을 명시하지 않으면,
할당 안한 각각의 case에는 해당 case 이름과 똑같은 글자의 문자열을 암시적으로 할당한다
위 예제에서,
GamePlayMode.cooperative 의 원시 값은 "cooperative" 이고,
GamePlayMode.individual 의 원시 값은 "individual" 이며,
GamePlayMode.competitive 의 원시 값은 "competitive" 가 된다
원시 값 타입의 case를 가진 열거체는 암시적으로,
스위프트 표준 라이브러리에서 정의한 RawRepresentable 프로토콜을 준수한다
그 결과, rawValue 프로퍼티와 init?(rawValue: RawValue)라는 서명의 실패 가능한 이니셜라이저를 가진다
함수나 메소드에서 ‘서명(signature)과 이름(name)’ 의 차이점은 매개 변수의 포함 여부다
이 예제의 init(rawValue: RawValue)는 이니셜라이저 서명(signature)이라고 하고,
매개 변수 부분을 뺀 init? 은 이니셜라이저 이름이라고 한다
ExampleEnum.b.rawValue처럼 rawValue 프로퍼티를 사용하여 열거체 case의 원시 값에 접근할 수 있다
원시 값을 사용하여 해당하는 case 를 찾을 수도 있다
예를 들면, ExampleEnum(rawValue: 5)처럼 열거체의 실패 가능 이니셜라이저를 호출하면 되는데 이는 옵셔널 case를 반환한다
원시 값 타입을 가지는 case에 대한 더 많은 정보와 예제를 보려면 Raw Values 페이지를 참고하자
4) 열거체 case 에 접근하기(Accessing Enumeration Cases)
열거체 타입의 case를 참조하려면, EnumerationType.enumerationCase 처럼 점(.)구문을 사용한다 Enumeration Syntax와 Implicit Member Expression 에서 설명한 것처럼,
열거체 타입을 추론할 수 있는 상황일 땐 이를 생략할 수 있으며, 이 때 점은 여전히 필요하다
구조체 본문은 0개 이상의 선언(declarations)을 담는다
이러한 선언(declarations)은 저장 및 계산 프로퍼티, 타입 프로퍼티,
인스턴스 메소드, 타입 메소드, 이니셜라이저,
서브 스크립트, type aliases 및 심지어 다른 구조체와, 클래스, Actor, 열거체 선언을 포함할 수 있다
구조체 선언에 디이니셜라이저나 프로토콜 선언을 담을 순 없다
다양한 종류의 선언을 포함한 구조체에 대한 논의 및 여러 가지 예제는, Structures and Classes 페이지를 통해 확인할 수 있다
구조체 타입은 어떤 개수의 프로토콜도 채택할 수 있지만, 클래스나, 열거체, 또는 다른 구조체를 상속할 순 없다
이전에 선언한 구조체로 인스턴스를 생성하는 데는 세 가지 방법이 있다
Initializers 에서 설명한 처럼, 구조체 안에 선언한 이니셜라이저 중 하나를 호출한다
클래스 선언(class declaration)은 프로그램에 이름지은 클래스 타입을 도입한다
클래스 선언은 class 키워드로 하며 형식은 다음과 같다
class class name: superclass, adopted protocols {
declarations
}
클래스 본문은 0개 이상의 선언(declaration)을 담는다
이러한 선언(declarations)은 저장 및 계산 프로퍼티,
인스턴스 메소드, 타입 메소드, 이니셜라이저, 단일한 디이니셜라이저,
서브 스크립트, type aliases 및 심지어 다른 클래스, 구조체, Actor 및 열거체 선언을 포함할 수 있다
클래스 선언에 프로토콜 선언을 담을 순 없다
다양한 종류의 선언을 포함한 클래스에 대한 논의 및 여러 가지 예제는, Structures and Classes 페이지를 통해 확인할 수 있다
클래스 타입은 자신의 상위 클래스(superclass)라는 단 하나의 부모 클래스만 상속할 수 있지만
프로토콜은 어떤 개수든 채택할 수 있다
클래스 이름(class anme)과 콜론 뒤에 상위 클래스(superclass)를 첫 번째로 나타내고,
그 뒤에 채택한 프로토콜(adopted protocols)을 작성한다
제네릭 클래스는 다른 제네릭 및 제네릭 하지 않은 클래스를 상속할 수 있지만,
제네릭 하지 않은 클래스는 다른 제네릭 하지 않은 클래스만 상속할 수 있다
콜론 뒤에 제네릭 상위 클래스 이름을 쓸 때는,
반드시 해당 제네릭 클래스의 전체 이름을 포함해야 해서, 제네릭 매개 변수 절도 포함해야 한다
Initializer Declaration에서 논의한 것처럼,
클래스엔 지정 이니셜라이저와 convenience 이니셜라이저가 있을 수 있다
클래스의 지정 이니셜라이저는 반드시 클래스가 선언한 모든 프로퍼티를 초기화해야 하며
그걸 반드시 상위 클래스의 지정 이니셜라이저 호출 전에 해야 한다
클래스는 상위 클래스의 프로퍼티, 메소드, 서브 스크립트 및 이니셜라이저를 재정의할 수 있다
재정의한 프로퍼티, 메소드, 서브 스크립트, 지정 이니셜라이저엔 반드시 override 선언 수정자를 표시해야 한다
여기서 그냥 이니셜라이저가 아니라 지정 이니셜라이저라고 한 건,
재정의할 이니셜라이저가 상위 클래스에서 지정 이니셜라이저든 convenience 이니셜라이저든,
재정의하고 나면 무조건 지정 이니셜라이저가 되기 때문이다
하위 클래스가 상위 클래스 이니셜라이저를 구현하길 요구하려면,
상위 클래스 이니셜라이저에 required 선언 수정자를 표시한다
해당 이니셜라이저의 하위 클래스 구현에도 반드시 required 선언 수정자를 표시해야 한다
상위 클래스(superclass)에서 선언한 프로퍼티와 메소드를 현재 클래스가 상속하긴 하지만,
상위 클래스(superclass)에서 선언한 지정 이니셜라이저는 Automatic Initializer Inheritance의 설명 조건과 만날 때만 하위 클래스가 상속한다
스위프트 클래스는 범용 기초 클래스를 상속하지 않는다
‘범용 기초 클래스 (universal base class)’ 는 오브젝티브-C 언어의 NSObject 같은 클래스를 말하며
대부분의 OOP 클래스들은 이러한 범용 기초 클래스를 상속한다
이전에 선언한 클래스로 인스턴스를 생성하는 데는 두 가지 방법이 있다
Initializers에서 설명한 것처럼, 클래스 안에 선언한 이니셜라이저 중 하나를 호출한다
아무런 이니셜라이저도 선언하지 않았으나, 클래스 선언의 모든 프로퍼티에 초기 값을 준 경우, Default Initializers에서 설명한 것처럼, 클래스의 기본 이니셜라이저를 호출한다
Actor 선언은 프로그램에 이름지은 액터 타입을 도입한다
Actor 선언은 actor 키워드로 하며 형식은 다음과 같다
actor actor name: adopted protocols {
declarations
}
액터 본문은 0개 이상의 선언(declarations)을 담는다
이러한 선언(declarations)은 저장 및 계산 프로퍼티,
인스턴스 메소드, 타입 메소드, 이니셜라이저, 단일한 디이니셜라이저,
서브 스크립트, type aliases,
심지어 다른 클래스와, 구조체, 및 열거체 선언을 포함할 수 있다
다양한 종류의 선언을 포함한 행위자에 대한 논의 및 여러 가지 예제는, Actors 페이지에서 확인할 수 있다
액터 타입은 어떤 개수의 프로토콜이든 채택할 수 있지만,
클래스, 열거체, 구조체, 또는 다른 액터를 상속할 수 없다
하지만, @objc 특성을 표시한 액터는 암시적으로 NSObjectProtocol 프로토콜을 준수하며
오브젝티브-C 런타임에서 NSObject의 하위 타입으로 노출된다
이전에 선언한 액터로 인스턴스를 생성하는 데는 두 가지 방법이 있다
Initializers에서 설명한 것처럼, 액터 안에 선언한 이니셜라이저 중 하나를 호출한다
아무런 이니셜라이저도 선언하지 않았으나, 액터 선언의 모든 프로퍼티에 초기 값을 준 경우, Default Initializers에서 설명한 것처럼, 액터의 기본 이니셜라이저를 호출한다
기본적으로 액터의 멤버는 해당 액터로 격리(actor isolation)된다
메소드 본문이나 프로퍼티 게터 같은 코드는 해당 액터 위에서 실행한다
액터 안의 코드는 이미 동일한 액터 위에서 실행하고 있기 때문에 서로 동기로 상호 작용할 수 있지만,
액터 밖의 코드엔 반드시 await 를 표시해서 해당 코드가 다른 액터 위에서 비동기로 실행 중이라는 걸 지시해야 한다
키 경로(key paths)는 액터의 격리 멤버를 참조할 수 없다
액터로 격리한 저장 프로퍼티를 동기 함수의 In-Out 매개 변수로 전달할 순 있지만 비동기 함수로는 불가능하다
액터는 격리 안한 멤버도 가질 수 있는데, 이들의 선언엔 nonisolated 키워드를 표시한다
격리 안한 멤버는 액터 밖의 코드 처럼 실행하는데, 액터의 어떤 격리 상태와도 상호 작용할 수 없으며
호출하는 쪽에서 사용할 때 await 키워드를 표시하지 않는다
프로토콜 선언(protocol declaration)은 프로그램에 이름지은 프로토콜 타입을 도입한다
프로토콜 선언은 protocol 키워드로 전역에서 선언하며 형식은 다음과 같다
protocol protocol name: inherited protocols {
protocol member declarations
}
프로토콜 본문은 0개 이상의 프로토콜 멤버 선언(protocol member declarations)을 담는데,
어떤 프로토콜 채택 타입이든 반드시 충족해야 할 준수 요구 사항을 설명한다
특히, 프로토콜은 준수 타입이 반드시 구현해야 할 특정한 프로퍼티, 메소드, 이니셜라이저, 서브 스크립트를 선언할 수 있다
프로토콜은 결합 타입(associated types)이라는 특수한 종류의 타입 별명도 선언할 수 있는데
이것으로 프로토콜의 다양한 선언들 사이의 관계를 지정할 수 있다
프로토콜 선언엔 클래스, 구조체, 열거체 및 다른 프로토콜 선언을 담을 수 없다
프로토콜 멤버 선언(protocol member declarations)은 밑에서 자세히 논의한다
프로토콜 타입은 다른 프로토콜을 어떤 개수든 상속할 수 있다
프로토콜 타입이 다른 프로토콜을 상속할 땐, 다른 프로토콜의 필수 조건 집합을 한 군데로 모으므로(aggregated),
현재 프로토콜을 상속한 어떤 타입이든 반드시 모든 요구 사항을 준수해야 한다
프로토콜 상속 사용법에 대한 예제는, Protocol Inheritance 부분을 보도록 하자
이전에 선언한 타입에 프로토콜 준수성을 추가하려면
해당 타입의 익스텐션 선언에 프로토콜을 채택하면 된다
익스텐션에선 채택한 프로토콜의 모든 요구 사항들을 반드시 구현해야 한다
타입이 모든 요구 사항을 이미 구현하고 있다면, 익스텐션 선언의 본문을 비워둬도 된다
기본적으로 프로토콜을 준수하는 타입은 반드시 프로토콜 안에 선언한 모든 프로퍼티, 메소드, 및 서브 스크립트를 구현해야 한다
그렇더라도, 해당 프로토콜 멤버 선언에 optional 선언 수정자를 표시하면
준수 타입의 구현부가 옵셔널임을 지정할 수 있다
프로토콜에서 선언한 요구 사항의 구현 그 자체가 옵셔널이라는 의미며
프로토콜의 준수 타입이 구현을 하면 구현부가 있는 것이고 아니면 nil이게 된다
optional 수정자는 objc 특성을 표시한 멤버와 objc 특성을 표시한 프로토콜의 멤버에만 적용할 수 있다
그 결과, 옵셔널 멤버 요구 사항을 담은 프로토콜은 클래스 타입만이 채택하고 준수할 수 있다
optional 선언 수정자를 사용하는 방법과 선택적 프로토콜 멤버에 접근하는 방법에 대한 자세한 내용은 Optional Protocol Requirements을 참조하면 된다
ex. 준수 타입이 구현했는지 확신할 수 없을 경우
열거체 case도 타입 멤버의 프로토콜 요구 사항을 만족할 수 있다
어떤 결합 값도 없는 열거체 case는 Self 타입인 읽기 전용 타입 변수의 프로토콜 요구 사항을 만족하며,
결합 값이 있는 열거체 case는 매개 변수와 인수 라벨이 case의 결합 값과 일치하고 Self를 반환하는 함수의 프로토콜 요구 사항을 만족한다
예를 들면 다음과 같다
protocolSomeProtocol{staticvarsomeValue:Self{get}staticfunc someFunction(x:Int)->Self}enumMyEnum:SomeProtocol{case someValue
case someFunction(x:Int)}
클래스만 프로토콜을 채택하게 제약하려면,
콜론 뒤의 상속 프로토콜(inherited protocols) 목록에 AnyObject 프로토콜을 포함시키면 된다
예를 들어, 다음 프로토콜은 클래스 타입만 채택할 수 있다
protocolSomeProtocol:AnyObject{/* Protocol members go here */}
AnyObject 요구 사항을 표시한 어떤 프로토콜을 상속한 프로토콜도 마찬가지로 클래스 타입만 채택할 수 있다
프로토콜에 objc 특성을 표시하면 해당 프로토콜에 AnyObject 요구 사항을 암시적으로 적용하므로
AnyObject 요구 사항을 프로토콜에 명시할 필요가 없다
프로토콜은 이름 있는 타입이며
따라서, Protocols as Types에서 논의한 것처럼
다른 이름 있는 타입과 동일한 위치에 모두 나타날 수 있다
하지만, 프로토콜로 인스턴스를 생성할 순 없는데
프로토콜이 자신이 지정한 요구 사항의 구현을 실제로 제공하진 않기 때문이다
Delegation에서 설명한 것처럼, 프로토콜을 사용하여 클래스 또는 구조체의 delegate가 구현해야 하는 메서드를 선언할 수 있다
1) 프로토콜 프로퍼티 선언
프로토콜 요구 사항이 반드시 프로퍼티를 구현해야 한다고 선언하려면
프로토콜 선언 본문 안에 프로토콜 프로퍼티 선언을 포함하면 된다
프로토콜 프로퍼티 선언은 특수한 형식의 변수 선언이다
var property name: type { get set }
다른 프로토콜 멤버 선언 처럼 해당 프로퍼티 선언은
프로토콜을 준수할 타입의 게터 및 세터 요구 사항만 선언한다
그 결과, 이를 선언한 프로토콜이 게터와 세터를 직접 구현하진 않는다
준수 타입은 다양한 방식으로 게터 및 세터 요구 사항을 만족할 수 있다
프로퍼티 선언이 get 과 set 키워드를 둘 다 포함하면,
준수 타입이 읽기 쓰기가 둘 다 가능한
즉, 게터와 세터를 둘 다 구현한 저장 변수 프로퍼티 또는 계산 프로퍼티로 이를 구현할 수 있다
하지만, 해당 프로퍼티 선언을 상수 프로퍼티 또는 읽기 전용 계산 프로퍼티로 구현할 순 없다
프로퍼티 선언이 get 키워드만 포함하면, 어떤 종류의 프로퍼티로도 이를 구현할 수 있다
준수 타입이 프로토콜의 프로퍼티 요구 사항을 구현하는 예제는, Property Requirements 페이지 부분을 보도록 하자
프로토콜 선언 안에서 타입 프로퍼티 요구 사항을 선언하려면,
프로퍼티 선언에 static 키워드를 표시한다
프로토콜을 준수하는게 구조체와 열거체면 프로퍼티를 static 키워드로 선언하고,
프로토콜을 준수하는게 클래스면 프로퍼티를 static 이나 class 키워드로 선언한다
구조체나, 열거체 또는 클래스에 프로토콜 준수성을 추가하는 익스텐션은
자신이 확장할 타입과 똑같은 키워드를 사용해야한다.
타입 프로퍼티 요구 사항에 기본 구현을 제공하는 익스텐션은 static 키워드를 사용한다
프로토콜 요구 사항이 반드시 메소드를 구현해야 한다고 선언하려면
프로토콜 선언 본문 안에 프로토콜 메소드 선언을 포함하면 된다
프로토콜 메소드 선언의 형식은 함수 선언과 똑같으나, 두 가지 예외가 있는데
함수 본문을 포함하지 않고 함수 선언 부분에서 어떤 기본 매개 변수 값도 제공할 수 없다
프로토콜의 메소드 요구 사항을 구현한 준수 타입 예제는, Method Requirements 부분을 보도록 하자
프로토콜 선언 안에서 클래스 및 정적 메소드 요구 사항을 선언하려면,
메소드 선언에 static 선언 수정자를 표시한다
프로토콜을 준수하는게 구조체와 열거체면 메소드를 static 키워드로 선언하고,
프로토콜을 준수하는게 클래스면 메소드를 static 이나 class 키워드로 선언한다
구조체나, 열거체, 또는 클래스에 프로토콜 준수성을 추가하는 익스텐션은
자신이 확장할 타입과 똑같은 키워드를 사용해야 한다
타입 메소드 요구 사항에 기본 구현을 제공하는 익스텐션은 static 키워드를 사용한다
프로토콜 준수 타입이 반드시 서브 스크립트를 구현해야 한다고 선언하려면
프로토콜 선언 본문 안에 프로토콜 서브 스크립트 선언을 포함하면 된다
프로토콜 서브 스크립트 선언은 특수한 형식의 서브 스크립트 선언이다
subscript (parameters) -> return type { get set }
서브 스크립트 선언은 프로토콜을 준수하는 타입에 대한 최소한의 게터 및 세터 구현 요구 사항만 선언한다
서브 스크립트 선언이 get 과 set 키워드를 둘 다 포함하면,
준수 타입은 반드시 게터와 세터 절을 둘 다 구현해야 한다
서브 스크립트 선언이 get 키워드만 포함하면,
준수 타입은 적어도 게터 절은 반드시 구현해야 하며 세터 절 구현은 옵션으로 할 수 있다
프로토콜 선언 안에서 정적 서브 스크립트 요구 사항을 선언하려면,
서브 스크립트 선언에 static 선언 수정자를 표시하면 된다
프로토콜을 준수하는게 구조체와 열거체면 서브 스크립트를 static 키워드로 선언하고,
프로토콜을 준수하는게 클래스면 서브 스크립트를 static 이나 class 키워드로 선언한다
구조체, 열거체, 또는 클래스에 프로토콜 준수성을 추가하는 익스텐션은
자신이 확장할 타입과 똑같은 키워드를 사용한다
정적 서브 스크립트 요구 사항에 기본 구현을 제공하는 익스텐션은 static 키워드를 사용한다
프로토콜은 associatedtype 키워드를 사용하여 연관 타입을 선언한다
연관 타입은 프로토콜 선언의 일부로 사용할 타입에 별명을 제공한다
연관 타입은 제네릭 매개 변수 절에 있는 타입 매개 변수와 비슷하지만,
선언된 프로토콜에서 Self와 결합되어 있다
이러한 context 안에서 Self는 프로토콜을 준수한 최종 결과 타입(eventual type)을 의미한다
더 많은 정보와 예제는 Associated Types 페이지 부분을 보도록 하자
프로토콜 선언에서 제네릭 where 절을 사용하여 연관 타입을 다시 선언하지 않고
다른 프로토콜에서 상속된 연관 타입에 요구 사항을 추가할 수 있다
예를 들어, 아래의 하위 프로토콜 선언은 다음과 같다
protocolSomeProtocol{associatedtypeSomeType}protocolSubProtocolA:SomeProtocol{// This syntax produces a warning.associatedtypeSomeType:Equatable}// This syntax is preferred.protocolSubProtocolB:SomeProtocolwhere SomeType:Equatable{}
이니셜라이저 선언은 프로그램에 클래스, 구조체, 열거체의 이니셜라이저를 도입한다
이니셜라이저 선언은 init 키워드로 하며 두 가지 기초 형식이 있다
구조체, 열거체, 클래스 타입엔 이니셜라이저가 어떤 개수든 있을 수 있지만,
클래스 이니셜라이저들에선 규칙과 결합 동작이 다르다
구조체 및 열거체와는 달리, 클래스엔 두 종류의 이니셜라이저가 있는데 Initialization에서 설명한 지정(designated) 이니셜라이저와 편리한(convenience) 이니셜라이저가 있다
다음 형식으론 구조체, 열거체의 이니셜라이저 및 클래스의 지정 이니셜라이저를 선언한다
init(parameters) {
statements
}
클래스의 지정 이니셜라이저는 모든 클래스 프로퍼티를 직접 초기화한다
이는 같은 클래스에 있는 어떤 다른 이니셜라이저도 호출할 수 없으며,
클래스에 상위 클래스가 있으면, 반드시 상위 클래스 지정 이니셜라이저를 호출해야 한다
클래스가 상위 클래스에서 어떤 프로퍼티를 상속했으면,
반드시 상위 클래스 지정 이니셜라이저를 호출하고 나야 현재 클래스에서 해당 프로퍼티를 설정할 수 있다
지정 이니셜라이저는 클래스 선언 상황에서만 선언할 수 있으므로 익스텐션 선언으로 클래스에 추가할 순 없다
구조체와 열거체 안의 이니셜라이저는 선언한 다른 이니셜라이저를 호출하여 일부 또는 전체 초기화 과정을 위임할 수 있다
편라한 이니셜라이저는 또 다른 편라한 이니셜라이저나 클래스의 지정 이니셜라이저 로 초기화 과정을 위임할 수 있다
그렇더라도, 초기화 과정은 반드시 지정 이니셜라이저 호출로 끝나야 궁극적으로 클래스 프로퍼티를 초기화하게 된다
편라한 이니셜라이저는 상위 클래스 이니셜라이저를 호출할 수 없다
지정 및 편리한 이니셜라이저에 required 선언 수정자를 표시하면
모든 하위 클래스가 이니셜라이저를 필수로 구현하길 요구할 수 있다
해당 이니셜라이저의 하위 클래스 구현부에도 반드시 required 선언 수정자를 표시해야 한다
기본적으로, 상위 클래스 안에 선언한 이니셜라이저를 하위 클래스가 상속하진 않는다
그렇더라도, 하위 클래스가 자신의 모든 저장 프로퍼티를 기본 값으로 초기화하면서
자기 자신은 어떤 이니셜라이저도 직접 정의하지 않으면, 상위 클래스의 모든 이니셜라이저를 상속하게 된다
하위 클래스가 상위 클래스의 모든 지정 이니셜라이저를 재정의하면, 상위 클래스의 편리한 이니셜라이저를 상속하게 된다
메소드, 프로퍼티, 서브 스크립트에서 처럼
재정의한 지정 이니셜라이저엔 override 선언 수정자를 표시할 필요가 있다
이니셜라이저에 required 선언 수정자를 표시하면,
하위 클래스에서 필수 구현해야하는 이니셜라이저를 재정의할 때
이니셜라이저에 override 수정자도 표시하지 않아도 된다
함수와 메소드처럼 이니셜라이저도 에러를 던지거나 다시 던질 수 있다
그리고 함수와 메소드처럼 이니셜라이저의 매개 변수 뒤에 throws 나 rethrows 키워드를 써서 적절한 동작을 지시한다
마찬가지로, 이니셜라이저는 비동기일 수 있으며 async 키워드로 이를 명시한다
실패 가능한 이니셜라이저(failable initializer)는 이니셜라이저를 선언한 타입의
옵셔널 인스턴스나 암시적으로 포장 푸는 옵셔널 인스턴스를 만들어 내는 타입의 이니셜라이저다
그 결과, 실패 가능한 이니셜라이저는 nil 을 반환하여 초기화 실패를 지시할 수 있다
옵셔널 인스턴스를 만드는 실패 가능한 이니셜라이저를 선언하려면,
이니셜라이저 선언의 init 키워드에 물음표를 덧붙이면 된다 - init?
암시적으로 포장 푸는 옵셔널 인스턴스를 만드는 실패 가능한 이니셜라이저를 선언하려면,
이니셜라이저 선언의 init 키워드에 느낌표를 덧붙이면 된다 - init!
아래 예제는 init? 실패 가능한 이니셜라이저로 구조체의 옵셔널 인스턴스를 만드는 걸 보여준다
structSomeStruct{letproperty:String// produces an optional instance of 'SomeStruct'init?(input:String){
if input.isEmpty {// discard 'self' and return 'nil'returnnil}
property = input
}}
init? 실패 가능한 이니셜라이저는 반드시 결과를 옵셔널로 다룬다는 것만 제외하면,
실패하지 않는 이니셜라이저 호출과 똑같은 방식으로 호출한다
if let actualInstance =SomeStruct(input:"Hello"){// do something with the instance of 'SomeStruct'}else{// initialization of 'SomeStruct' failed and the initializer returned 'nil'}
실패 가능한 이니셜라이저는 이니셜라이저 본문 구현부의 어떤 시점에서든 nil 을 반환할 수 있다
실패 가능한 이니셜라이저는 어떤 종류의 이니셜라이저로도 일을 위임할 수 있다
실패하지 않는 이니셜라이저는 또 다른 실패하지 않는 이니셜라이저 또는 init! 실패 가능한 이니셜라이저로 일을 위임할 수 있다
실패하지 않는 이니셜라이저가 init? 실패 가능한 이니셜라이저로 일을 위임하려면
상위 클래스 이니셜라이저의 결과를 강제 언래핑하여 작성하면 된다
ex. super.init()!
초기화 실패는 이니셜라이저의 위임을 통하여 전파된다
특히, 실패 가능한 이니셜라이저가 위임한 이니셜라이저가 실패하여 nil 을 반환하면,
일을 위임 했던 이니셜라이저도 실패하여 암시적으로 nil 을 반환하게 된다
실패하지 않는 이니셜라이저가 일을 위임한 init! 실패 가능한 이니셜라이저가 실패하여 nil 을 반환하면,
마치 ! 연산자로 nil 값인 옵셔널의 포장을 풀려고 한 것처럼 런타임 에러가 발생하게 된다
실패 가능한 지정 이니셜라이저를 하위 클래스에서 재정의하는 건
어떤 종류의 지정 이니셜라이저로도 할 수 있다
실패하지 않는 지정 이니셜라이저를 하위 클래스에서 재정의하는 건
실패하지 않는 지정 이니셜라이저만 할 수 있다
익스텐션 선언은 기존 타입의 동작을 확장하도록 한다
익스텐션 선언은 extension 키워드로 하며 형식은 다음과 같다
extension type name where requirements {
declarations
}
익스텐션 선언 본문은 0개 이상의 선언(declarations)을 담는다
이러한 선언은 계산 프로퍼티, 계산 타입 프로퍼티,
인스턴스 메소드, 타입 메소드, 이니셜라이저, 서브 스크립트 선언,
심지어 클래스, 구조체 및 열거체 선언을 포함할 수 있다
익스텐션 선언엔 디이니셜라이저 및 프로토콜 선언이나,
저장 프로퍼티, 프로퍼티 옵저버, 또는 다른 익스텐션 선언을 담을 수 없다
프로토콜 익스텐션 안의 선언엔 final 을 표시할 수 없다
다양한 종류의 선언을 포함한 익스텐션에 대한 논의 및 여러 가지 예제는, Extensions 페이지를 보도록 하자
타입 이름(type name)이 클래스, 구조체, 열거체 타입이면 익스텐션은 해당 타입을 확장한다
타입 이름(type name)이 프로토콜 타입이면, 익스텐션은 해당 프로토콜을 준수한 모든 타입을 확장한다
제네릭 타입 또는 연관 타입을 가진 프로토콜을 확장하는 익스텐션 선언은 요구 사항을 포함할 수 있다
확장한 타입 또는 확장한 프로토콜을 준수한 타입의 인스턴스가 요구 사항을 만족하면,
선언 안에서 지정한 동작을 인스턴스가 얻게 된다
익스텐션 선언에는 이니셜라이저 선언이 포함될 수 있습니다
즉, 확장하려는 타입이 다른 모듈에 정의되어 있는 경우,
이니셜라이저 선언은 해당 타입의 멤버가 적절하게 초기화되도록 보장하기 위해
해당 모듈에 이미 정의된 이니셜라이저에게 업무를 위임해야 한다
기존 타입의 프로퍼티, 메소드, 이니셜라이저를 해당 타입의 익스텐션에서 재정의할 순 없다
익스텐션 선언으로 기존 클래스, 구조체, 열거체 타입에 프로토콜 준수성을 추가하려면
채택한 프로토콜(adopted protocols)을 지정하면 된다
extension type name: adopted protocols where requirements {
declarations
}
익스텐션 선언으로 기존 클래스에 클래스 상속성을 추가할 순 없으므로,
타입 이름(type name)과 콜론 뒤엔 프로토콜 목록만 지정할 수 있다
1) 조건부 준수(Conditional Conformance)
제네릭 타입이 프로토콜을 조건부로 준수하도록 확장해서,
타입의 인스턴스가 특정한 요구 사항과 만날 때만 프로토콜을 준수하게 할 수 있다
프로토콜에 조건부 준수를 추가하려면 익스텐션 선언이 요구 사항을 포함하면 된다
재정의된 요구 사항이 일부 제네릭 컨텍스트에서 사용되지 않는 경우(Overridden Requirements Aren’t Used in Some Generic Contexts)
일부 제네릭 상황에선 프로토콜 조건부 준수로 동작을 획득한 타입이
해당 프로토콜 요구 사항의 특수화 구현을 항상 사용하는 건 아니다
‘특수화 구현(specialized implementations)’은 제네릭 타입의 타입 매개 변수를 특수한 타입으로 고정하여, 적용 범위를 좁힌 구현을 의미한다
해당 동작을 묘사하기 위해, 다음 예제는 두 프로토콜과 그 두 프로토콜을 모두 조건부로 준수하는 제네릭 타입을 하나 정의한다
protocolLoggable{func log()}extensionLoggable{func log(){print(self)}}protocolTitledLoggable:Loggable{staticvarlogTitle:String{get}}extensionTitledLoggable{func log(){print("\(Self.logTitle): \(self)")}}structPair<T>:CustomStringConvertible{letfirst:Tletsecond:Tvardescription:String{return"(\(first), \(second))"}}extensionPair:Loggablewhere T:Loggable{}extensionPair:TitledLoggablewhere T:TitledLoggable{staticvarlogTitle:String{return"Pair of '\(T.logTitle)'"}}extensionString:TitledLoggable{staticvarlogTitle:String{return"String"}}
Pair 구조체는 자신의 제네릭 타입이 각 Loggable이나 TitledLoggable을 준수할 때마다 Loggable과 TitledLoggable을 준수한다
아래 예제에서, oneAndTwo 는 Pair 의 인스턴스로,
TitledLoggable을 준수하는데 이는 String이 TitleLoggable을 준수하기 때문이다
oneAndTwo의 log() 메소드를 직접 호출할 땐, 제목 문자열을 담은 특수화 버전을 사용하게 된다
letoneAndTwo=Pair(first:"one", second:"two")
oneAndTwo.log()// Prints "Pair of 'String': (one, two)"
하지만, 제네릭 상황 안에서나 Loggable 프로토콜의 인스턴스로써 oneAndTwo 를 사용할 땐, 특수화 버전을 사용하지 않게 된다
스위프트는 Pair가 Loggable을 준수하는 데 필요한 최소 요구 사항만을 참고하여 호출할 log() 구현을 고른다
이런 이유로, 그 대신 Loggable 프로토콜이 제공하는 기본 구현을 사용하게 된다
func doSomething<T:Loggable>(with x:T){
x.log()}doSomething(with: oneAndTwo)// Prints "(one, two)"
doSomething(_:) 으로 전달한 인스턴스의 log() 를 호출할 땐, 기록 문자열에서 커스텀된 제목을 생략한다
2) Protocol Conformance Must Not Be Redundant(프로토콜 준수는 중복되어서는 안 된다)
구체적인 타입(concrete type)은 특정 프로토콜을 단 한 번만 준수할 수 있다
스위프트는 중복 프로토콜 준수를 에러로 표시한다
이런 종류의 에러는 두 가지 종류의 상황에서 마주칠 수 있다
첫 번째 상황은 동일한 프로토콜을 서로 다른 요구 사항으로 명시적으로 여러 번 준수할 때다
두 번째 상황은 동일한 프로토콜을 암시적으로 여러 번 상속할 때다
아래에서 이러한 상황을 논의해보자
명시적 중복 해결(Resolving Explicit Redundancy)
구체적인 타입에 대한 여러 개의 익스텐션은 익스텐션의 요구 사항이 상호 배타적(mutually exclusive)이더라도,
동일한 프로토콜로의 준수를 추가할 수 없다
아래 예제에서 이러한 제약을 실제로 보여준다
두 개의 익스텐션 선언이 Serializable 프로토콜로의 조건부 준수를 추가하려고 하는데,
하나는 원소가 Int 인 배열을 위해서, 다른 하나는 원소가 String 인 배열을 위한 것이다
protocolSerializable{func serialize()->Any}extensionArray:Serializablewhere Element ==Int{func serialize()->Any{// implementation}}extensionArray:Serializablewhere Element ==String{func serialize()->Any{// implementation}}// Error: redundant conformance of 'Array<Element>' to protocol 'Serializable'
여러 개의 구체적인 타입을 기준으로 조건부 적합성을 추가해야 하는 경우에는
각 타입이 준수할 수 있는 새 프로토콜을 만들고 조건부 적합성을 선언할 때 해당 프로토콜을 요구 사항으로 사용해야 한다
구체적인 타입이 조건부로 프로토콜을 준수하는 경우,
해당 타입은 동일한 요구 사항을 가진 모든 상위 프로토콜을 암시적으로 준수한다
단일 상위 프로토콜에서 상속되는 두 개의 프로토콜을 조건부로 준수하는 유형이 필요한 경우
상위 프로토콜에 대한 적합성을 명시적으로 선언한다
이는 서로 다른 요구 사항으로 부모 프로토콜을 두 번 암묵적으로 준수하는 것을 방지한다
다음 예제에서는 TitledLoggable 프로토콜과 새로운 MarkedLoggable 프로토콜 모두에 대한 조건부 적합성을 선언할 때
충돌을 피하기 위해 Array의 Loggable의 조건부 적합성을 명시적으로 선언한다
protocolMarkedLoggable:Loggable{func markAndLog()}extensionMarkedLoggable{func markAndLog(){print("----------")log()}}extensionArray:Loggablewhere Element:Loggable{}extensionArray:TitledLoggablewhere Element:TitledLoggable{staticvarlogTitle:String{return"Array of '\(Element.logTitle)'"}}extensionArray:MarkedLoggablewhere Element:MarkedLoggable{}
Loggable에 대한 조건부 적합성을 명시적으로 선언하는 익스텐션이 없으면
다른 Array 확장은 암시적으로 다음과 같은 선언을 생성하여 에러를 발생시킨다
부모 프로토콜로의 준수성을 명시하면 자식 프로토콜로의 준수성으로 인하여
부모 프로토콜로의 준수성이 암시적으로 생기는 것을 막아준다
extensionArray:Loggablewhere Element:TitledLoggable{}extensionArray:Loggablewhere Element:MarkedLoggable{}// Error: redundant conformance of 'Array<Element>' to protocol 'Loggable'
서브 스크립트 선언
서브 스크립트 선언은 특별한 한 타입의 객체에 서브 스크립트 지원 기능을 추가하도록 하며
전형적으로 이를 사용하여 집합체(collection), 리스트(list), 시퀀스(sequence) 원소 접근의 편의 구문을 제공한다
첨자 선언은 subscript 키워드로 하며 형식은 다음과 같다
subscript (parameters) -> return type {
get {
statements
}
set(setter name) {
statements
}
}
서브 스크립트 선언은 클래스, 구조체, 열거체, 익스텐션, 프로토콜 선언 안에서만 있을 수 있다
매개 변수(paramter)는 서브 스크립트 표현식에서 해당 타입의 원소 접근에 사용하는 하나 이상의 인덱스를 지정한다
ex. object[i] 표현식의 i
원소 접근에 사용할 인덱스는 어떤 타입이어도 되긴 하지만,
각 매개 변수는 반드시 타입 명시 주석을 포함하여 각각의 인덱스 타입을 지정해야 한다
반환 타입(return type)은 접근할 원소의 타입을 지정한다
계산 프로퍼티처럼 서브 스크립트 선언은 접근한 원소 값의 읽기와 쓰기를 지원한다
게터를 써서 값을 읽고, 세터를 써서 값을 쓴다
세터 절은 옵션이며, 게터만 필요할 땐 두 절 모두 생략하고 단순히 요청 값을 직접 반환 할 수도 있다
세터 절을 제공한다면 반드시 게터 절도 제공해야 한다
세터 이름(setter name)과 테두리 괄호는 옵션이다
세터 이름을 제공하면, 이를 세터의 매개 변수 이름으로 사용한다
세터 이름을 제공하지 않으면, 세터의 기본 매개 변수 이름은 value가 된다
세터의 매개 변수 타입은 반환 타입(return type)과 똑같다
자신을 선언한 타입 안에서 서브 스크립트 선언을 중복 정의하려면,
중복 정의할 것의 매개 변수(paramter)나 반환 타입(return type)이 다르기만 하면 된다
상위 클래스에서 상속한 서브 스크립트 선언을 재정의할 수도 있다
그럴 땐, 반드시 재정의한 서브 스크립트 선언에 override 선언 수정자를 표시해야 한다
서브 스크립트 매개 변수는 함수 매개 변수와 동일한 규칙을 따르지만, 두 가지 예외가 있다
기본적으로, 서브 스크립트 연산에서 쓰는 매개 변수엔, 함수, 메소드, 이니셜라이저와 달리, 인수 라벨이 없다
하지만, 함수와 메소드 및 이니셜라이저에서 쓰는 것과 똑같은 구문의 명시적 인수 라벨을 제공할 순 있다
또한, 서브 스크립트 연산엔 In-Out 매개 변수가 있을 수 없다
서브 스크립트 연산 매개 변수는 Special Kinds of Parameters에서 설명한대로 구문의 기본 값을 가질 수 있다
서브 스크립트에 대한 더 많은 정보 및 첨자 선언 예제를 보려면, Subscripts 페이지를 보도록 하자
1) 타입 서브 스크립트 선언
서브 스크립트를 타입의 인스턴스가 아닌 타입이 드러내도록 선언하려면,
서브 스크립트 선언에 static 선언 수정자를 표시한다
클래스의 타입 계산 프로퍼티는 class 선언 수정자를 대신 표시하여 상위 클래스 구현을 하위 클래스가 재정의하는 걸 허용할 수 있다
클래스 선언에서 static 키워드는 선언에 class 와 final 선언 수정자를 둘 다 표시한 것과 똑같은 효과를 가진다
연산자 선언
연산자 선언(operator declaration)은 프로그램에 새로운 중위(infix), 접두사(prefix), 접미사(postfix) 연산자를 도입하며
operator 키워드로 선언한다
연산자는 서로 다른 세 개의 고정 위치(fixity)로 선언할 수 있는데
중위, 접두사, 접미사가 있다
연산자의 고정 위치(fixity)가 지정하는 건 피연산자에 대한 연산자의 상대 위치가 된다
연산자 선언은 각각의 고정 위치마다 하나씩, 총 세 개의 기초 형식이 있다
연산자 고정 위치는 연산자 선언에서 operator 키워드 앞에 infix, prefix, postfix 선언 수정자를 표시하여 지정한다
각 형식에서 연산자 이름엔 Operators에서 정의한 연산자 문자만 담을 수 있다
다음 형식은 새로운 중위 연산자를 선언한다
infix operator operator name: precedence group
중위 연산자(infix operator)는 두 개의 피연산자 사이에 작성하는 이항(binary) 연산자로,
익숙한 1 + 2 표현식 안의 덧셈 연산자(+)가 있다
중위 연산자는 옵션으로 우선 순위 그룹을 지정할 수 있다
연산자의 우선 순위 그룹을 생략하면,
DefaultPrecedence라는 기본 우선권 그룹을 스위프트가 사용하며,
이는 TernaryPrecedence 바로 위의 우선 순위를 가진다
더 많은 정보는 Precedence Group Declaration 페이지 부분을 보도록 하자
다음 형식은 새로운 접두사 연산자를 선언한다
prefix operator operator name
접두사 연산자(prefix operator)는 피연산자 바로 앞에 작성하는 단항(unary) 연산자로,
!a 표현식 안의 접두사 논리 부정(NOT) 연산자(!)가 있다
접두사 연산자 선언은 우선 순위를 지정하지 않는다
접두사 연산자는 비-결합적(nonassociative)이다
다음 형식은 새로운 접미사 연산자를 선언한다
postfix operator operator name
접미사 연산자(postfix operator)는 피연산자 바로 뒤에 작성하는 단항(unary) 연산자로,
a! 표현식 안의 강제-언래핑 연산자(!)가 있다
접두사 연산자처럼 접미사 연산자 선언도 우선 순위를 지정하지 않는다
접미사 연산자는 비-결합적이다
새로운 연산자를 선언한 후, 연산자와 똑같은 이름의 정적 메소드를 선언하여 이를 구현한다
정적 메소드는 연산자가 인수로 취하는 값 타입 안의 멤버다
예를 들어, Double 과 Int 를 곱하는 연산자는 Double 이나 Int 구조체의 정적 메소드로 구현한다
접두사나 접미사 연산자를 구현할 거면, 반드시 해당 메소드 선언에 해당하는 prefix 나 postfix 선언 수정자도 표시해야 한다
새로운 연산자의 생성 및 구현 방법에 대한 예제를 보려면, Custom Operators 페이지 부분을 보도록 하자
우선 순위 그룹 선언
우선 순위 그룹 선언(precedence group declaration)은 프로그램에 새로운 중위 연산자 우선 순위 그룹을 도입한다
연산자 우선 순위는 괄호 그룹이 없을 때, 연산자와 피-연산자를 얼마나 밀접하게 연결할지 지정한다
우선 순위 그룹 선언의 형식은 다음과 같다
precedencegroup precedence group name {
higherThan: lower group names
lowerThan: higher group names
associativity: associativity
assignment: assignment
}
더 낮은 그룹 이름들(lower group names) 및 더 높은 그룹 이름들(higher group names) 목록은
새로운 우선 순위 그룹과 기존 우선 순위 그룹 사이의 관계를 지정한다
lowerThan 우선 순위 그룹 특성은 현재 모듈 밖에서 선언한 우선 순위 그룹의 참조에만 사용할 수도 있다
2 + 3 * 5 표현식처럼 두 연산자가 피-연산자를 두고 경쟁할 때는,
상대적으로 더 높은 우선 순위의 연산자가 피-연산자와 더 밀접하게 연결된다
더 낮은 그룹 이름들(lower group names) 및 더 높은 그룹 이름들(higher group names)로 서로 관련된 우선 순위 그룹은
반드시 단일 관계 계층(single relational hierarchy)이어야 하지만, 선형 계층(linear hierarchy) 을 형성하진 않아도 된다
이는 상대적인 우선 순위를 정의하지 않은 우선 순위 그룹도 가능하다는 의미가 된다
이러한 우선 순위 그룹의 연산자들은 괄호 그룹 없이 서로 나란히 사용할 수 없다
스위프트는 수 없이 많은 우선 순위 그룹을 정의하여
표준 라이브러리에서 제공한 연산자와 나란히 사용한다
ex. 덧셈 (+) 및 뺄셈 (-) 연산자는 AdditionPrecedence 그룹에 속하고,
곱셈 (*) 및 나눗셈 (/) 연산자는 MultiplicationPrecedence 그룹에 속한다
스위프트 표준 라이브러리가 제공하는 우선 순위 그룹의 완전한 목록은,Operator Declarations 페이지 항목을 보도록 하자
연산자 결합성(associativity)은 괄호 그룹이 없을 때
동일한 우선 순위를 가진 일렬로 나열된 연산자들을 어떻게 함께 그룹지을지 지정한다
연산자 결합성은 left나 right, 또는 none 이라는 상황에-민감한(context-sensitive) 키워드 중 하나를 작성하여 지정한다
결합성을 생략하면 기본 값은 none이 된다
왼쪽 결합(left-associative) 연산자는 왼쪽에서-오른쪽으로 그룹 짓는다
ex. 뺄셈 연산자 (-) 는 왼쪽-결합이라서, 표현식 4 - 5 - 6 은 (4 - 5) - 6 으로 그룹지어 -7 이라고 평가된다
오른쪽-결합 연산자는 오른쪽에서-왼쪽으로 그룹짓고,
none 이라는 결합성을 지정한 연산자는 아예 결합을 하지 않는다
동일한 우선 순위인 비-결합 연산자는 서로 인접하여 나타날 수 없다
ex. < 연산자의 결합성은 none 이며, 이는 1 < 2 < 3 같은 건 유효한 표현식이 아니다
우선 순위 그룹의 할당(assignment)은 옵셔널 체이닝을 포함한 연산에 사용할 때의 연산자 우선 순위를 지정한다
true 로 설정할 땐, 옵셔널 체이닝 중에 해당 우선 순위 그룹 안의 연산자가 표준 라이브러리 할당 연산자와 동일한 그룹 규칙을 사용한다
false 로 설정하거나 생략할 땐, 우선 순위 그룹 안의 연산자가 할당을 하지 않는 연산자와 동일한 옵셔널 체이닝 규칙을 따른다
선언 수정자(Declaration Modifiers)
선언 수정자 (declaration modifiers) 는 선언의 동작이나 의미를 수정하는 키워드 또는 상황에-민감한 키워드다
선언 수정자는 있다면 선언의 특성과 선언을 도입한 키워드 사이에 적절한 키워드 또는 상황에-민감한 키워드를 작성하여 지정한다
class
해당 수정자를 클래스 멤버에 적용하면 멤버가 클래스 인스턴스의 멤버가 아닌 클래스 그 자체의 멤버라는 걸 지시한다
해당 수정자는 있고 final 수정자는 없는 상위 클래스 멤버는 하위 클래스에서 재정의할 수 있다
dynamic
해당 수정자를 적용한 어떤 클래스 멤버든 오브젝티브-C 로 나타낼 수 있다
멤버 선언에 dynamic 수정자를 표시할 땐, 그 멤버로의 접근은 항상 오브젝티브-C 런타임을 써서 동적 급파(dispatched)한다
C++ 등에서 사용하는 가상 함수 테이블 (virtual function table)을 사용한다는 의미로
해당 멤버로의 접근은 컴파일러가 절대 인라인(inline; 코드 줄에 넣기)하거나 탈-가상화(devirtualized)하지 않습니다
dynamic 수정자로 표시한 선언은 오브젝티브-C 런타임을 써서 급파하기 때문에, 반드시 objc 특성을 표시해야 한다
final
해당 수정자는 클래스 또는 클래스의 프로퍼티, 메소드, 서브 스크립트 멤버에 적용한다
클래스에 적용하면 클래스로 하위 클래스를 만들 수 없다는 걸 지시한다
클래스의 프로퍼티, 메소드, 서브 스크립트에 적용하면 클래스 멤버를 어떤 하위 클래스에서도 재정의할 수 없다는 걸 지시한다
final 사용법의 예제는 Preventing Overrides 페이지 부분을 보도록 하자
lazy
해당 수정자를 클래스나 구조체의 저장 변수 프로퍼티에 적용하면,
프로퍼티에 최초로 접근할 때, 최대 한 번만 프로퍼티의 초기 값을 계산하고 저장한다는 걸 지시한다
lazy 수정자의 사용법에 대한 예제는, Lazy Stored Properties 페이지 부분을 보도록 하자
optional
해당 수정자를 프로토콜의 프로퍼티, 메소드, 서브 스크립트 멤버에 적용하면
준수 타입이 이러한 멤버를 구현하는 게 필수는 아니라는 걸 지시한다
optional 수정자는 objc 특성을 표시한 프로토콜에만 적용할 수 있다
그 결과, 클래스 타입만 옵셔널 멤버 요구 사항을 담은 프로토콜을 채택하고 준수할 수 있다
optional 수정자 사용법에 대한 더 많은 정보와 옵셔널 프로토콜 멤버의 접근법에 대한 길잡이는, Optional Protocol Requirements 페이지 부분을 보도록 하자
ex. 준수 타입이 구현했는지 확실하지 않을 경우
required
해당 수정자를 클래스의 지정 또는 편리한 이니셜라이저에 적용하면
반드시 모든 하위 클래스가 그 이니셜라이저를 구현해야 한다는 걸 지시한다
해당 이니셜라이저의 하위 클래스 구현에도 반드시 required 수정자를 표시해야 한다
static
해당 수정자를 구조체, 클래스, 열거체, 프로토콜 멤버에 적용하면
그 타입의 인스턴스에 대한 멤버라기 보단, 타입 자체의 멤버라는 걸 지시한다
클래스 선언 영역 안에서 멤버 선언에 static 수정자를 작성하면
해당 멤버 선언에 class 와 final 수정자를 모두 작성하는 것과 똑같은 효과를 가진다
하지만, 클래스의 상수 타입 프로퍼티는 예외인데
class 또는 final 선언을 쓸 수 없기 때문에 static은 클래스 선언 영역에서의 의미가 아닌 일반적인 의미를 가진다
unowned
해당 수정자를 저장 변수, 상수, 저장 프로퍼티에 적용하면
변수나 프로퍼티의 값이 객체로의 unowned 참조라는 걸 지시한다
객체를 메모리에서 해제한 후에 변수나 프로퍼티에 접근하려 하면 런타임 에러가 발생하게 된다
weak 참조와 마찬가지로 프로퍼티나 값의 타입은 반드시 클래스 타입이어야 하며
weak 참조와 달리 옵셔널이 아닌 타입이다
unowned 수정자에 대한 예제 및 더 많은 정보는, Unowned References 페이지 부분을 보도록 하자
unowned(safe)
unowned의 전체 철자를 명시한 것이다
unowned(unsafe)
해당 수정자를 저장 변수, 상수, 저장 프로퍼티에 적용하면
변수나 프로퍼티의 값이 객체로의 소유하지 않은 참조라는 걸 지시한다
객체를 메모리에서 해제한 후에 변수나 프로퍼티에 접근하려 하면,
객체였던 메모리 장소에 접근할 건데, 이는 안전하지 않은-메모리 연산이다
weak 참조와 마찬가지로 프로퍼티나 값의 타입은 반드시 클래스 타입이어야 하며
weak 참조와 달리 옵셔널이 아닌 타입이다
unowned 수정자에 대한 예제 및 더 많은 정보는, Unowned References 페이지 부분을 보도록 하자
weak
해당 수정자를 저장 변수나 저장 변수 프로퍼티에 적용하면
변수나 프로퍼티의 값이 객체로의 weak 참조라는 걸 지시한다
변수나 프로퍼티의 타입은 반드시 옵셔널 클래스 타입이어야 한다
객체를 해제한 후에 변수나 프로퍼티에 접근하면, 값이 nil이다
weak 수정자에 대한 예제 및 더 많은 정보는, Unowned References 페이지 부분을 보도록 하자
1) 접근 지정자
스위프트는 다섯 가지 수준의 접근 제어를 제공하는데
공개(open), 공용(public), 내부(internal), 파일 전용(file private), 개인 전용(private)이 있다
선언에 아래의 접근 수준 중 하나를 표시하여 선언의 접근 수준을 지정한다 Access Control 페이지에서 자세히 알아보자
open
해당 지정자를 선언에 적용하면
선언과 동일한 모듈 안의 코드가 선언에 접근하고 하위 클래스를 만들 수 있다는 걸 지시한다
open 접근-수준 지정자를 표시한 선언은 해당 선언이 담긴 모듈을 불러온 모듈 코드도 접근하고 하위 클래스를 만들 수 있다
public
해당 지정자를 선언에 적용하면
선언과 동일한 모듈 안의 코드가 선언에 접근하고 하위 클래스를 만들 수 있다는 걸 지시한다
public 접근-수준 자정자를 표시한 선언은 해당 선언이 담긴 모듈을 불러온 모듈 코드도 접근할 수 있다
하위 클래스 만들기는 안된다
internal
해당 지정자를 선언에 적용하면 선언과 동일한 모듈 안의 코드만 선언에 접근할 수 있다는 걸 지시한다
기본적으로 대부분의 선언에 internal 접근-수준 지정자를 암시적으로 표시한다
fileprivate
해당 지정자를 선언에 적용하면 선언과 동일한 소스 파일 안의 코드만 선언에 접근할 수 있다는 걸 지시한다
private
해당 지정자를 선언에 적용하면 선언을 직접 둘러싼 영역 안의 코드만 선언에 접근할 수 있다는 걸 지시한다
접근 제어를 위해 동일한 파일에 있는 동일한 타입의 익스텐션들은 접근-제어 영역을 공유한다
확장한 타입도 동일한 파일 안에 있으면, 타입의 접근-제어 영역을 공유한다
타입 선언 안에서 선언한 개인 전용(private) 멤버를 익스텐션에서 접근할 수 있고,
한 익스텐션 안에서 선언한 개인 전용 멤버를 다른 익스텐션 및 타입 선언에서 접근할 수도 있다
위에 있는 각각의 접근-수준 지정자는 옵션으로 단일 인자를 받는데,
예를 들어, private(set)처럼 set 키워드를 괄호로 테두리 쳐서 구성한다 Getters and Setters에서 설명한 것처럼,
변수나 서브 스크립트의 세터 접근 수준이 변수나 서브 스크립트 그 자체의 접근 수준 보다 낮거나 같게 지정하고 싶을 때
이런 형식의 접근-제어 지정자를 사용한다
The text was updated successfully, but these errors were encountered:
선언(declaration)은 프로그램에 새로운 이름이나 구조물(construct)을 구성한다
예를 들어, 선언을 사용하여 함수와 메서드를 도입하고, 변수와 상수를 도입하며,
열거체와, 구조체, 클래스, 및 프로토콜 타입을 정의한다
선언을 사용하면 기존 이름 붙인 타입의 동작을 확장하고
다른 곳에서 선언한 기호(symbols)를 자신의 프로그램 안으로 불러 올 수도 있다
스위프트에선 선언과 동시에 구현 또는 초기화한다는 점에서 대부분의 선언이 정의이기도 하다
그렇더라도, 프로토콜은 자신의 멤버를 구현하지 않기 때문에
대부분의 프로토콜 멤버는 선언일 뿐이다
편의상 그리고 스위프트에선 이런 구분이 그다지 중요하지 않기 때문에,
선언(declarations)이라는 용어가 선언과 정의 둘 다를 아우르게 된다
Top-Level Code(최상단 코드)
스위프트 소스 파일 안의 최상단 코드는 0개 이상의 구문과, 선언, 및 표현식으로 구성된다
기본적으로 소스 파일 최상단에 선언한 변수와 상수 및 그 외 이름 붙인 선언은
동일 모듈 안의 모든 소스 파일 코드에 접근 가능하다
Access Control Levels 페이지에서 설명한 접근 지정자로 선언을 표시함으로써
이런 기본 동작을 재정의할 수 있다
두 종류의 최상단 코드가 있는데,
최상단 선언 (top-level declarations)과 실행 가능한 최상단 코드(excutable top-level code)가 있다
최상단 선언은 선언으로만 구성하며, 모든 스위프트 소스 파일에서 허용한다
실행 가능한 최상단 코드는 선언만이 아니라 구문과 표현식도 담으며
프로그램의 최상단 진입점(top-level entry point)으로만 허용하게 된다
실행 파일(executable)을 만들려고 컴파일하는 스위프트 코드는
파일과 모듈로 어떻게 코드를 정돈하는지에 상관없이
다음 접근법 중 최대 하나만을 담아 최상단 진입점을 표시할 수 있는데
이는 main 특성, NSApplicationMain 특성, UIApplicationMain 특성,
main.swift 파일 또는 최상단(에서) 실행 가능한 코드를 담은 파일로 이를 명시할 수 있다
Code Blocks(코드 블럭)
코드 블럭(code block)은 구문을 서로 그룹묶기 위해 다양한 선언 및 제어 구조에서 사용한다
형식은 다음과 같다
코드 블럭 안의 구문(statements)은 선언과, 표현식 및 다른 종류의 구문을 포함하며
소스 코드에 나타난 순서대로 실행한다
Import 선언
import 선언은 현재 파일 밖에서 선언한 기호에 접근하게 해준다
기본 형식은 전체 모듈을 불러오며; import 키워드와 그 뒤의 모듈 이름으로 구성합니다
어느 기호를 불러올지 더 자세하게 제한하면
모듈이나 하위 모듈 안의 특정 하위 모듈 또는 특정 선언을 지정할 수 있다
이런 자세한 형식을 사용할 땐, 선언한 모듈이 아닌 불러온 기호만 현재 영역에서 사용 가능하다
상수 선언
상수 선언(constant declaration)은 프로그램에 상수 이름 값을 도입한다
상수 선언은 let 키워드로 선언하며 형식은 다음과 같다
상수 선언은 상수 이름(constant name)과 이니셜라이저 표현식(expression) 값 사이에 변경 불가한 연결을 정의하여
상수 값을 설정한 후엔 바꿀 수 없다
그렇더라도, 상수를 클래스 객체로 초기화하면
상수 이름과 그게 참조할 수 객체 사이의 연결은 바꿀 수 없지만
객체 그 자체는 바꿀 수 있다
즉, 상수는 값을 바꿀 수 없는데
클래스 같은 참조 타입은 주소가 값이기 때문에 바꿀 수 없는 것은 주소이며
해당 주소가 가리키는 곳의 내용은 바뀌어질 수 있다
전역, 프로그램의 최상단 진입점에서 상수를 선언할 땐
반드시 값으로 초기화해야 한다
함수나 메서드에서 상수 선언을 할 때
값을 최초로 읽기 전에 설정한다고 보증하는 한 나중에 초기화할 수 있다(lazy)
만약 컴파일러가 상수의 값이 절대 읽히지 않는다는 것을 증명할 수 있다면,
상수는 값이 설정될 필요가 전혀 없다
클래스나 구조체 선언에서 상수 선언을 할 땐, 상수 프로퍼티라고 고려한다
상수 선언은 계산 프로퍼티가 아니며 따라서 게터나 세터가 없다
상수 선언의 상수 이름(constant name)이 튜플 패턴이면,
튜플 안의 각 항목 이름을 이니셜라이저 표현식(expression) 안의 해당 값과 연결한다
예제에서, firstNumber는 10 이라는 값에 ‘이름 붙인 상수’ 이며,
secondNumber는 42 라는 값에 ‘이름 붙인 상수’ 다
두 상수 모두 이제 독립적으로 사용할 수 있다
Type Inference에서 설명한 것처럼,
상수 이름(constant name)의 타입을 추론할 수 있을 땐 : type 이라는 타입 명시 주석은 옵션이다
상수 타입 프로퍼티를 선언하려면, 선언에 static 선언 수정자를 표시한다
클래스의 상수 타입 프로퍼티는 항상 암시적으로 이미 final이며
class 나 final 선언 수정자를 표시하여 하위 클래스 재정의를 허용하거나 불허할 수 없다
타입 프로퍼티에 대한 더 많은 정보는 Type Properties 페이지에서 알 수 있다
상수에 대한 더 많은 정보와 사용 시점에 대한 예제는
Constants and Variables와 Stored Properties 페이지에서 알 수 있다
변수 선언
변수 선언(variable declaration)은 프로그램에 변수 이름 값을 도입하며 var 키워드로 선언한다
변수 선언은 여러 가지 형식으로 서로 다른 종류의 이름을 붙인 변경 가능한 값을 선언하는데,
이는 저장 및 계산 변수 / 프로퍼티, 저장 변수와 프로퍼치 옵저버 및 static 변수 프로퍼티을 포함한다
사용할 적절한 형식은 변수가 선언되는 scope와 선언하려는 변수의 종류에 따라 달라진다
Overriding에서 설명한 것처럼
하위 클래스의 프로퍼티 선언을 override 선언 수정자로 표시함으로써
하위 클래스에서 프로퍼티를 재정의할 수 있다
1) 저장 변수와 저장 변수 프로퍼티(Stored Variables and Stored Variable Properties)
다음 형식은 저장 변수 또는 저장 변수 프로퍼티를 선언한다
해당 형식의 변수 선언은 전역이나 함수 지역, 또는 클래스나 구조체 선언에서 정의한다
전역이나 함수 지역에서 할 때, 저장 변수(stored variable)라고 한다
클래스나 구조체 선언에서 선언할 때, 저장 변수 프로퍼티(stored variable property)라고 한다
이니셜라이저 표현식(expression)은 프로토콜 선언에 있을 수 없지만,
다른 모든 상황에서 이니셜라이저 표현식(expression)은 옵션이다
그렇더라도, 이니셜라이저 표현식 (expression)이 아무 것도 없으면,
반드시 : type 이라는 타입 명시 주석을 변수 선언에 포함해야 한다
상수 선언 처럼 변수 이름(variable name)이 튜플 패턴이면,
튜플 안의 각 항목 이름을 이니셜라이저 표현식(expression) 안의 해당 값과 연결한다
이름이 알 수 있듯이 저장 변수 또는 저장 변수 프로퍼티 값은 메모리에 저장한다
2) 계산 변수와 계산 프로퍼티(Computed Variables and Computed Properties)
다음 형식은 계산 변수 또는 계산 프로퍼티를 선언한다
이 형식의 변수 선언은 전역이나 함수 지역 또는 클래스, 구조체, 열거체 및 익스텐션 선언에서 정의한다
전역이나 함수 지역에서 할 때, 계산 변수 (computed variable)라고 한다
클래스, 구조체 또는 익스텐션 선언에서 할 때, 계산 프로퍼티(computed property)라고 한다
게터(getter)로 값을 읽고, 세터(setter)로 값을 쓴다
Read-Only Computed Properties에서 설명한 것 처럼 세터 구성은 옵션이라
게터만 필요할 땐, 두 절 모두 생략하고 단순히 요청 값만 직접 반환할 수 있다
세터 절을 제공하면, 게터 절도 반드시 제공해야 한다
setter name과 테두리 괄호는 옵션이다
setter name을 제공하면 설정자의 매개 변수 이름으로 이걸 사용한다
setter name을 제공하지 않으면,
Shorthand Setter Declaration에서 설명한 것처럼, newValue 가 설정자의 기본 매개 변수 이름이 된다
저장 변수 및 저장 변수 프로퍼티와는 달리,
계산 변수와 계산 프로퍼티를 메모리에 저장하지 않는다
계산 프로퍼티에 대한 더 자세한 정보 및 예제를 보려면,
Computed Properties 페이지에서 알 수 있다
3) 저장 변수 옵저버와 프로퍼티 옵저버(Stored Variable Observers and Property Observers)
willSet 과 didSet 옵저버가 있는 저장 변수나 프로퍼티를 선언할 수도 있다
옵저버가 있는 저장 변수나 프로퍼티의 선언 형식은 다음과 같다
이 형식의 변수 선언은 전역, 함수 지역, 또는 클래스, 구조체 선언에서 정의한다
전역이나 함수 지역에서 할 때, 옵저버를 저장 변수 옵저버(stored variable observers)라고 한다
클래스나 구조체 선언에서 선언할 때, 옵저버를 프로퍼티 옵저버(property observers) 라고 한다
프로퍼티 옵저버는 어떤 저장 프로퍼티에든 추가할 수 있다
Overriding Property Observers에서 설명한 것 처럼
하위 클래스 안에서 프로퍼티를 재정의함으로써
저장이든 계산이든 상관없이 어떤 상속 프로퍼티에도 프로퍼티 옵저버를 추가할 수 있다
이니셜라이저 표현식(expression)은 클래스나 구조체 선언에서는 옵션이지만 다른 곳에선 필수다
이니셜라이저 표현식 (expression)으로 타입을 추론할 수 있을 땐
타입 명시 주석이 옵션이다
프로퍼티 값을 최초로 읽을 때 해당 표현식을 평가한다
프로퍼티 초기 값을 읽지 않고 덮어 쓰면, 프로퍼티에 최초로 쓰기 전 해당 표현식을 평가한다
willSet과 didSet 옵저버는 변수나 프로퍼티 값을 설정할 때
이를 관찰하고 적절히 응답할 방법을 제공한다
변수나 프로퍼티를 최초로 초기화할 땐 옵저버를 호출하지 않는다
대신, 초기화 컨텍스트 외부에서 값을 설정할 때만 호출한다
willSet 옵저버는 변수나 프로퍼티 값 설정 직전에 호출된다
새 값은 willSet 옵저버에 상수로 전달되어, 따라서 willSet 절 구현부에선 바꿀 수 없다
didSet 옵저버는 새 값 설정 직후에 호출된다
willSet 옵저버와는 대조적으로, 여전히 접근이 필요한 경우를 위해
didSet 옵저버에 변수나 프로퍼티의 예전 값을 전달한다
즉, 변수나 프로퍼티 자신의 didSet 옵저버 절에서 값을 할당하면,
할당된 새 값이 방금 설정되어 willSet 옵저버에게 전달된 값을 대체한다
willSet 과 didSet 절의 setter name 과 테두리 괄호는 옵션이다
setter name을 제공하면, willSet 과 didSet 옵저버의 매개 변수 이름으로 이걸 사용한다
설정자 이름을 제공하지 않으면,
willSet 옵저버의 기본 매개 변수 이름은 newValue이고
didSet 옵저버의 기본 매개 변수 이름은 oldValue다
willSet 절을 제공할 뗀 didSet 절이 옵션이며,
didSet 절을 제공할 땐 willSet 절이 옵션이다
didSet 옵저버 본문에서 예전 값을 참조하면, 예전 값이 사용 가능하도록 옵저버 전에 게터를 호출한다
그 외 경우, 상위 클래스의 게터를 호출하지 않고 새 값을 저장한다
아래 예제는 상위 클래스에서 정의한 계산 프로퍼티에 옵저버를 추가하도록 하위 클래스에서 재정의하는 걸 보여준다
프로퍼티 옵저버에 대한 더 많은 정보와 사용법 예제를 보려면,
Property Observers 페이지를 참고하자
4) 타입 변수 프로퍼티(Type Variable Properties)
타입 변수 프로퍼티를 선언하려면, 선언에 static 선언 수정자를 표시한다
클래스에선 그 대신 타입 계산 프로퍼티에 class 선언 수정자를 표시하여
하위 클래스에서 상위 클래스 구현을 재정의하게 할 수 있다
타입 프로퍼티는 Type Properties 페이지에서 더 알아 볼 수 있다
타입 별명 선언(Type Alias Declaration)
타입 별명 선언(type alias declaration)은 프로그램에 기존 타입의 별명을 도입할 수 있다
타입 별명 선언은 typealias 키워드로 하며 형식은 다음과 같다
타입 별명의 선언 후엔 프로그램 어디서나 기존 타입(existing type) 대신 별명 이름(name)을 사용할 수 있다
기존 타입(existing type)은 이름 붙인 타입이나 복합 타입일 수 있다
타입 별명은 새로운 타입을 생성하지 않으며
단순히 한 이름이 기존 타입을 가리키도록 한다
타입 별명 선언은 제네릭 매개 변수를 사용하여 기존 제네릭 타입에 이름을 줄 수 있다
타입 별명은 기존 타입의 제네릭 매개 변수 중 일부 또는 모두에 고정 타입을 제공할 수 있다
예를 들면 다음과 같다
제네릭 매개 변수가 있는 타입 별명을 선언할 땐,
해당 매개 변수에 대한 제약 조건과 기존 타입의 제네릭 매개 변수에 대한 제약 조건이 반드시 정확히 일치해야 한다
예를 들면 다음과 같다
타입 별명과 기존 타입은 서로 바꿔 쓸 수 있기 때문에,
타입 별명에 추가적인 제네릭 제약 조건을 도입할 순 없다
타입 별명은 선언에 있는 모든 제네릭 매개 변수를 생략함으로써
기존 타입의 제네릭 매개 변수를 보내줄 수 있다
예를 들어, 아래 선언한 Diccionario라는 타입 별명은 Dictionary와 제네릭 매개 변수 및 제약 조건이 똑같다
프로토콜 선언 안에선 타입 별명이 자주 사용할 타입에 더 짧고 편리한 이름을 줄 수 있다
예를 들면 다음과 같다
해당 타입 별명이 없다면 sum 함수가 associatedtype을
T.Element 대신 T.Iterator.Element 라고 참조해야 작동하게 된다
Protocol Associated Type Declaration 페이지 부분을 통해 해당 메커니즘을 확인할 수 있다
함수 선언
함수 선언(function declaration)은 프로그램에 함수나 메서드를 도입하게 된다
클래스, 구조체, 열거체 또는 프로토콜 안에서 선언한 함수를 메서드(method)라고 하며
함수 선언은 func 키워드로 하며 형식은 다음과 같다
함수 반환 타입이 Void면 다음 처럼 반환 타입을 생략할 수 있다
각각의 매개 변수 타입은 반드시 포함해야 하며 이는 추론할 수 있는게 아니다
매개 변수 타입 앞에 inout 을 쓰면, 함수 영역 안에서 매개 변수를 수정할 수 있다
In-Out 매개 변수는, 밑의, In-Out Parameters에서 자세히 논의해보자
함수 선언 구문(statements)이 단일 표현식만 포함하고 있으면
해당 표현식 값을 반환하는 걸로 이해한다
표현식 타입과 함수 반환 타입이 Void도 아니고
Never 처럼 어떤 case 값도 없는 열거체가 아닐 경우에만 이런 암시적 반환 구문을 고려한다
튜플 타입을 함수의 반환 타입으로 사용하면 함수가 여러 개의 값을 반환할 수 있다
함수 정의를 또 다른 함수 선언 안의 본문에서 나타낼 수 있는데
이런 종류의 함수를 중첩 함수(nested function)라고 한다
In-Out 매개 변수처럼 절대 escape 하지 않는다는 걸 보증한 값
또는 nonescaping 함수 인수로 전달한 값을 붙잡으면
중첩 함수는 벗어나지 않는다
중첩 함수에 대한 논의는 Nested Functions 페이지에서 확인할 수 있다
1) 매개 변수 이름(Parameter Names)
함수 매개 변수는 여러 가지 형식으로 된 각각의 매개 변수들을 쉼표로 구분한 목록이다
함수 호출의 인수 순서는 함수 선언의 매개 변수 순서와 반드시 일치해야 한다
매개 변수 목록에서 가장 단순한 요소의 형식은 다음과 같다
매개 변수에는 함수 본문 안에서 사용하는 이름뿐 아니라,
함수나 메서드 호출 때 사용하는 인수 라벨도 있다
기본적으로 매개 변수 이름을 인수 라벨로도 사용한다
예를 들면 다음과 같다
인수 라벨의 기본 동작을 다음 형식 중 하나로 재정의할 수도 있다
매개 변수 이름 앞의 이름은 매개 변수에 명시적 인수 라벨을 주는데,
이는 매개 변수 이름과 다를 수 있다
해당 인수는 함수나 메서드 호출에서 반드시 주어진 인수 라벨을 사용해야 한다
매개 변수 이름 앞의 밑줄(_)이 있으면 인수 라벨이 표시되지 않는다
해당 인수는 함수나 메서드 호출 쪽에서도 반드시 아무런 라벨이 없어야 한다
2) In-Out 매개변수
In-Out 매개 변수는 다음 처럼 전달한다
해당 동작을 복사-입력 복사-출력 (copy-in copy-out) 또는 값-결과에 의한 호출 (call by value result)이라고 한다
‘값-결과에 의한 호출 (call by value result)’ 은
함수 안에서는 ‘값에 의한 호출 (call by value)’ 처럼 동작하고,
함수 반환 시에는 ‘참조에 의한 호출 (call by reference)’ 처럼 동작한다
swift는 상황에 따라 참조에 의한 호출과 값-결과에 의한 호출을 적절하게 선택하여 인자를 전달한다
예를 들어, 계산 프로퍼티 또는 옵저버가 있는 프로퍼티를
In-Out 매개 변수로 전달할 땐, 함수 호출 시엔 게터를 호출하고 함수 반환 시엔 세터를 호출하게 된다
최적화로써 인수가 메모리의 물리 주소에 저장된 reference 값일 땐,
함수 본문 안과 밖 모두에서 동일한 메모리 위치를 사용한다
이런 최적화 동작을 참조에 의한 호출(call by reference)이라고 하며
복사-입력 복사-출력 모델의 모든 필수 조건을 만족하면서도 복사의 오버헤드를 제거한다
즉, 스위프트는 인수가 value인지 reference인지에 따라,
값에 의한 호출과 참조에 의한 호출이라는 두 가지 방식으로 In-Out 매개 변수 전달을 최적화한다
참조에 의한 호출 최적화에 의존하지 않고
주어진 복사-입력 복사-출력 모델로 코드를 작성하는 경우에는
최적화 여부와 관계없이 올바르게 작동하게 된다
In-Out 매개 변수의 최적화는 컴파일러가 컴파일 단게에서 알아서 신경쓰는 문제이므로,
개발자는 값에 의한 호출(복사-입력 복사 출력) 방식을 사용하기만 하면 된다
함수 안에선 현재 영역에서 원본 값이 사용 가능하더라도,
In-Out 매개변수에 인수로 전달한 값엔 접근하지 않아야 한다
원본에 접근하면 값에 대한 동시 접근이라, 스위프트의 메모리 독점권 보장을 위반한다
똑같은 이유로 여러 개의 In-Out 매개 변수에 동일한 값을 전달할 순 없다
어길 경우 메모리 접근 충돌이 발생하게 된다
메모리 안전성과 메모리 독점권에 대한 더 많은 정보는 Memory Safety 페이지를 참고해보자
In-Out 매개 변수를 붙잡는 클로저나 중첩 함수는 반드시 nonescaping 해야한다
In-Out 매개 변수를 변경(mutating) 없이 캡쳐해야 하는 경우가 있다면,
캡쳐 리스트를 써서 매개 변수가 변경 불가능하다는 걸 명시하고 캡쳐해야 한다
In-Out 매개 변수를 캡쳐하고 변경할 필요가 있다면,
함수 반환 전에 모든 변경의 종료를 보장하는 다중 쓰레드 코드처럼,
명시적 지역 복사본(local copy)을 사용하면 된다
In-Out 매개 변수에 대한 더 많은 논의와 예제는
In-Out Parameters 페이지에서 확인할 수 있다
3) 특별한 종류의 매개 변수
다음 형식을 사용하면 매개 변수를 무시하고 여러 개의 값을 취하며 기본 값을 제공할 수 있다
밑줄(_) 매개 변수는 명시적으로 무시하며 함수 본문 안에서 접근할 수 없다
기초 타입 이름 바로 뒤에 세 점(...)이 있는 매개 변수는 가변 매개 변수라고 이해한다
가변 매개 변수 바로 뒤에 있는 매개 변수엔 반드시 인수 라벨이 있어야 한다
인수 라벨이 없으면 새로운 매개 변수가 아닌 가변 매개 변수의 한 원소라고 인식하기 때문이다
함수의 가변 매개 변수는 여러 개일 수 있다
가변 매개 변수는 기초 타입 이름의 원소를 담은 배열로 취급하는데
예를 들어, Int... 라는 가변 매개 변수는 [Int] 로 취급한다
가변 매개 변수의 사용 예는, Variadic Parameters 부분을 보도록하자
자신의 타입 뒤에 같음 기호(=)와 표현식이 있는 매개 변수는
주어진 표현식에 해당하는 기본 값을 가진다고 이해한다
주어진 표현식은 함수 호출 때 평가하며
함수 호출 때 매개 변수를 생략하면 기본 값을 대신 사용하게 된다
4) 특별한 종류의 메서드
self를 수정하는 열거체나 구조체의 메서드엔 반드시 mutating 선언 수정자를 표시해야 한다
상위 클래스 메서드를 재정의한 메서드엔 반드시 override 선언 수정자를 표시해야 한다
override 수정자 없이 메서드를 재정의하거나
상위 클래스 메서드를 재정의하지 않는 메서드에 override 수정자를 사용하면 컴파일 에러가 발생한다
타입의 인스턴스라기 보단 타입 자체에 결합된 메서드는
반드시 열거체와 구조체라면 static 선언 수정자를,
클래스라면 static 이나 class 선언 수정자를 표시해야 한다
class 선언 수정자로 표시한 클래스 타입 메사드는 하위 클래스 구현에서 재정의할 수 있으며
class final 이나 static 으로 표시한 클래스 타입 메서드는 재정의할 수 없다
5) 특별한 이름의 메서드
특수한 이름의 여러가지 메서드로 함수 호출 구문을 수월하게 할 수 있다
타입에서 해당 메서드 중 하나를 정의하면, 타입의 인스턴스를 함수 호출 구문에 사용할 수 있다
함수 호출은 인스턴스의 특수 이름 메서드 중 하나를 호출하는 걸로 이해한다
클래스, 구조체, 열거체 타입은 dynamicCallable(동적으로 호출 가능)에서 설명한 것처럼
dynamicallyCall(withArguments:) 메서드나 dynamicallyCall(withKeywordArguments:) 메서드를 정의하거나
또는, 아래 설명 처럼 함수-처럼-호출하는 메서드를 정의함으로써 함수 호출 구문을 지원할 수 있다
타입에서 함수-처럼-호출하는 메서드와 dynamicCallable 특성의 메서드를 둘 다 정의하면,
어느 메서드든 사용할 수 있는 상황에선 컴파일러가 함수-처럼-호출하는 메서드에 우선권을 주게된다
함수-처럼-호출하는 메서드의 이름은 callAsFunction()이거나,
아니면 callAsFunction( 으로 시작하고 라벨이 있거나 없는 인수를 추가한 이름이다
예를 들어, callAsFunction(::) 과 callAsFunction(something:) 모두 함수-처럼-호출하는 메서드 이름으로 유효하다
다음의 함수 호출들은 서로 같다
함수-처럼-호출하는 메서드와 dynamicCallable 특성인 메서드 사이에는
얼마나 많은 정보를 타입 시스템에 부호화(encode)해서 넣을 것인지
그리고 런타임에 얼마나 많은 동적 동작이 가능한지에 대해서 서로 다르게 절충(trade-offs) 한다
함수-처럼-호출하는 메서드를 선언할 땐, 인수의 개수와, 각각의 인수 타입과 라벨을 지정한다
dynamicCallable 특성인 메서드는 인수 배열을 들고 있는데 쓸 타입만을 지정한다
함수-처럼-호출하는 메서드나, dynamicCallable 특성인 메서드를 정의한다고,
함수 호출 표현식이 아닌 곳에서 해당 타입의 인스턴스를 마치 함수인 것처럼 사용할 수 있는 건 아니다
예를 들면 다음과 같다
dynamicMemberLookup에서 설명한 것처럼,
subscript(dynamicMemberLookup:) 서브 스크립트는 멤버 찾아보기 구문을 수월하게 할 수 있다
6) Throwing 함수와 메서드
에러를 던질 수 있는 함수와 메서드엔 반드시 throws 키워드를 표시해야 한다
이러한 함수와 메서드를 throwing 함수와 throwing 메서드라고 한다
형식은 다음과 같다
throwing 함수나 메서드 호출문은 반드시 try 나 try! 표현식 즉, try 나 try! 연산자 영역으로 감싸야 한다
throws 키워드는 함수 타입의 일부이며,
던지지 않는 함수는 throwing 함수의 하위 타입이다
그 결과, throwing 함수를 예상한 곳에서 던지지 않는 함수를 사용할 수도 있다
함수가 에러를 던질 수 있는지 여부만 가지고선 함수를 중복 정의할 순 없다
에러를 던지는 것과 던지지 않는 것만 차이가 나고 나머지는 똑같은 함수를 둘 수는 없다
그렇더라도 함수의 매개 변수(parameter)가 에러를 던질 수 있는지를 기초로는 함수를 중복 정의할 수 있다
throwing 메서드는 던지지 않는 메서드를 재정의할 수도 없고,
던지지 않는 메서드에 대한 프로토콜 요구 사항을 throwing 메서드로 만족할 수도 없다
그렇더라도, 던지지 않는 메서드는 throwing 메서드를 재정의할 수 있고,
throwing 메서드에 대한 프로토콜 요구 사항을 던지지 않는 메서드로 만족할 수도 있다
throwing 함수는 에러를 던질 수도 있고 아닐 수도 있다
그러므로 throwing 함수를 던지지 않는 함수로 재정의하는 건 자연스럽다
하지만, 던지지 않는 함수를 throwing 함수로 재정의하면 원래 정의한 성질에 맞지 않게 된다
7) Rethrowing 함수와 메서드
함수나 메서드를 rethrows 키워드로 선언하면
함수 매개 변수 중 하나가 에러를 던지는 경우에만
에러를 발생시킨다는 것을 나타내기 위해 rethrows 키워드를 사용하여 선언할 수 있다
이러한 함수와 메서드를 rethrowing 함수와 rethrowing 메서드라고 한다
rethrowing 함수와 메서드에는 반드시 적어도 하나의 throwing 함수 매개 변수가 있어야 한다
rethrowing 함수나 메서드는 catch 절 안에서만 throw 문을 담을 수 있다
이는 throwing 함수 호출을 do-catch 문 안에서 하게 하고
catch 절의 에러 처리를 또 다른 에러를 던지는 걸로 하게 해준다
또한, catch 절은 반드시 rethrowing 함수의 throwing 매개 변수가 던진 에러만 처리해야 한다
예를 들어, 다음은 catch절이 alwaysThrows()가 던진 에러도 처리할 것이기 때문에 무효한 구분이다
throwing 메서드는 rethrowing 메서드를 재정의할 수도 없고,
rethrowing 메서드에 대한 프로토콜 요구 사항을 throwing 메서드로 만족할 수도 없다
그렇더라도, rethrowing 메서드는 throwing 메서드를 재정의 할 수 있고,
throwing 메서드에 대한 프로토콜 필수 조건을 rethrowing 메서드로 만족할 수도 있다
rethrowing 함수는 함수 매개 변수가 던진 에러를 다시 던진다는 걸 빼면,
함수 자체로는 에러를 던지지 않는 함수라고 할 수 있다
8) 비동기 함수와 메서드
비동기로 실행하는 함수와 메서드엔 반드시 async 키워드를 표시해야 한다
이러한 함수와 메서드를 비동기 함수(asynchronous functions)와 비동기 메서드(asynchronous methods)라고 한다
형식은 다음과 같다
비동기 함수나 메서드 호출문은 반드시 await 표현식으로 감싸야 한다
즉, 반드시 await 연산자 영역 안에 있어야 한다
async 키워드는 함수 타입의 일부이며, 동기 함수는 비동기 함수의 하위 타입이다
그 결과, 비동기 함수를 예상한 곳에서 동기 함수를 사용할 수도 있다
예를 들어, 비동기 메서드를 동기 메서드로 재정의할 수도 있고,
비동기 메서드를 요구한 프로토콜 요구 사항을 동기 메서드로 만족시킬 수 있다
함수가 비동기인지 아닌지 여부로 함수를 중복 정의할 수 있다
호출한 쪽에서 어느 중복 정의를 사용할지는 상황으로 결정하는데
비동기 상황이면 비동기 함수를 사용하고, 동기 상황이면 동기 함수를 사용한다
비동기 메서드는 동기 메서드를 재정의할 수도 없고,
동기 메서드에 대한 프로토콜 요구 사항을 비동기 메서드로 만족할 수도 없다
그렇더라도, 동기 메서드는 비동기 메서드를 재정의할 수 있고
비동기 메서드에 대한 프로토콜 요구 사항을 동기 메서드로 만족시킬 수 있다
9) 절대 반환하지 않는 함수
스위프트는 함수 또는 메서드가 호출자에게 반환되지 않음을 나타내는 Never 타입을 정의 한다
Never 반환 타입인 함수와 메서드를 nonreturning이라고 한다
nonreturning 함수와 메서드는 복구할 수 없는 에러를 일으키거나
무한정 계속하는 일련의 작업을 시작한다
그렇지 않으면 호출 바로 뒤에 실행될 코드를 절대 실행하지 않는다는 걸 의미한다
throwing 및 rethrowing 함수는 자신이 반환하지 않더라도,
적절한 catch 절로 프로그램 제어를 옮길 순 있다
Guard Statement에서 논의한 것처럼,
nonreturning 함수나 메서드를 호출하여 ‘guard’ 문의 else 절로 결론지어 끝낼 수 있다
nonreturning 메소드를 재정의할 순 있지만,
새 메소드는 자신의 반환 타입과 반환하지 않는다는 동작을 반드시 유지해야 한다
열거체 선언
열거체 선언(enumeration declaration)은 프로그램에 이름지은 열거체 타입을 도입한다
열거체 선언에는 두 개의 기초 형식이 있으며 enum 키워드로 선언한다
어느 형식의 열거체 선언 본문이든 0개 이상의 열거체 case(enumeration cases)라는-값을 담고 있는데,
여기엔 계산 프로퍼티, 인스턴스 메소드, 타입 메소드, 이니셜라이저, type aliases 및
심지어 다른 열거체, 구조체, 클래스, Actor 선언도 포함한다
열거체 선언에 디이니셜라이저나 프로토콜 선언을 담을 순 없다
열거체 타입은 어떤 개수의 프로토콜이든 채택할 수 있지만
클래스, 구조체, 다른 열거체를 상속할 순 없다
클래스 및 구조체와는 달리, 열거체 타입에는 암시적으로 제공하는 기본 이니셜라이저가 없으며
모든 이니셜라이저는 반드시 명시적으로 선언해야 한다
이니셜라이저는 열거체 안의 다른 이니셜라이저에 작업을 위임할 수 있지만,
이니셜라이저가 self에 열거체 case 중 하나를 할당한 후에만 초기화 과정이 완료된다
구조체와 같지만 클래스와는 달리 열거체는 값 타입이며
열거체의 인스턴스는 변수나 상수에 할당할 때나 함수 호출의 인자로 전달할 때 복사된다
값 타입에 대한 정보는 Structures and Enumerations Are Value Types 페이지에서 더 알 수 있다
Extension Declaration에서 논의한 것처럼, 익스텐션 선언으로 열거체 타입의 동작을 확장할 수 있다
1) 어떤 타입의 case 든 가지는 열거체(Enumerations with Cases of Any Type)
다음 형식으로 선언한 열거체 타입은 어떤 타입의 열거체 case든 담는다
이런 형식으로 선언한 열거체를 다른 프로그래밍 언어에서는 discriminated unions(차별화된 공용체)라고 한다
이런 형식에선 각각의 case 블럭을 case 키워드와
그 뒤에 쉼표로 구분한, 하나 이상의 열거체 case 로 구성한다
각각의 case 이름은 반드시 유일해야 한다
각각의 case 가 주어진 타입의 값을 저장한다고 지정할 수도 있다
case 이름 바로 뒤의 결합 값 타입(associated value types) 튜플 안에 이러한 타입을 지정하면 된다
결합 값을 저장한 열거체 case는 지정한 결합 값으로 열거체 인스턴스를 생성하는 함수처럼 사용할 수 있다
그리고 함수처럼 열거체 case로의 참조를 가질 수도 나중에 이를 코드에 적용할 수도 있다
결합 값 타입이 있는 case 에 대한 더 많은 정보와 예제를 보려면,
Associated Values 부분을 보도록하자
2) 간접 사용을 포함하는 열거체(Enumerations with Indirection)
열거체는 재귀 구조, 즉, 결합 값이 열거체 타입 그 자체의 인스턴스인 case를 가질 수 있다
‘재귀 (recursion)’ 란 하나의 컴퓨터 문제를 더 작은 동일 문제를 풀어서 해결해 나가는 방법으로
하나의 함수 안에서 자기 자신을 호출하여 이를 구현한다
하지만, 열거체 타입 인스턴스는 값 의미 구조를 가지며, 이는 메모리 안에 고정 구획을 가진다
여기서 말하는 메모리 안의 고정 구획(fixed layout)을 보통 스택(stack)이라고 한다
열거체 인스턴스는 값 의미 구조라 스택에 저장하므로,
한 열거체 인스턴스가 다른 인스턴스를 호출하려면
해당 인스턴스로의 참조도 따로 저장해야 한다
해당 인스턴스로의 참조를 추가하여 저장하는 걸 ‘간접 계층 (layer-of-indirection)을 집어 넣는다’ 고 한다
재귀를 지원하려면 컴파일러가 반드시 간접 계층을 집어 넣어야 한다
특별한 한 열거체 case가 간접 사용을 할 수 있게 하려면,
거기에 indirect 선언 수정자를 표시하면 된다
간접 case 엔 반드시 결합 값이 있어야 한다
결합 값이 있는 모든 열거체 case가 간접 사용을 할 수 있게 하려면,
전체 열거체에 indirect 수정자를 표시한다
이는 열거체가 indirect 수정자를 표시해야 할 case를 많이 담고 있을 때 편리하게 사용할 수 있다
indirect 수정자로 표시한 열거체는 결합 값이 있는 case와 그렇지 않은 case를 섞어서 담을 수 있다
그렇더라도, indirect 수정자를 표시한 어떤 case를 다시 담을 순 없다
간접 사용에 대한 더 많은 정보와 예제를 보려면, Recursive Enumerations 페이지에서 확인 할 수 있다
3) Raw-Value 타입의 case 를 가지는 열거체(Enumerations with Cases of a Raw-Value Type)
다음 형식으로 선언한 열거체 타입은 동일한 기초 타입의 열거체 case를 담는다
해당 형식에선 각각의 case 블럭을 case 키워드와 뒤에 쉼표로 구분한 하나 이상의 열거체 case로 구성한다
첫 번째 형식의 case와는 달리, 각각의 case는, 원시 값(raw value)이라는 동일한 기초 타입의 실제 값을 가진다
이러한 값의 타입은 원시 값 타입(raw-value type)에서 정하며
반드시 정수, 부동 소수점 수, 문자열, 단일 문자를 나타내야 한다
특히, 원시 값 타입(raw-value type)은 Equatable 프로토콜과
다음의 프로토콜 중 하나를 반드시 준수해야 하는데
정수 글자 값: ExpressibleByIntegerLiteral,
부동-소수점 글자 값: ExpressibleByFloatLiteral,
어떤 개수의 문자든 담는 문자열 글자 값: ExpressibleByStringLiteral,
단일 문자만 담는 문자열 글자 값: ExpressibleByUnicodeScalarLiteral / ExpressibleByExtendedGraphemeClusterLiteral
각각의 case는 반드시 유일한 이름을 가지고 유일한 원시 값을 할당해야한다
원시 값 타입을 Int로 정했는데 case 값을 명시하지 않으면,
0, 1, 2, 등의 값을 암시적으로 할당한다
할당 안한 각각의 Int 타입 case엔 이전 case 원시 값에서 자동 증가한 원시 값을 암시적으로 할당하게 된다
위 예제에서,
ExampleEnum.a 의 원시 값은 0 이고 ExampleEnum.b 값은 1이다
ExampleEnum.c 값은 5로 명시하여 설정했기 때문에,
ExampleEnum.d 값은 따라서 5 에서 자동 증가한 6이 된다
원시 값 타입을 String으로 정했는데 case 값을 명시하지 않으면,
할당 안한 각각의 case에는 해당 case 이름과 똑같은 글자의 문자열을 암시적으로 할당한다
위 예제에서,
GamePlayMode.cooperative 의 원시 값은 "cooperative" 이고,
GamePlayMode.individual 의 원시 값은 "individual" 이며,
GamePlayMode.competitive 의 원시 값은 "competitive" 가 된다
원시 값 타입의 case를 가진 열거체는 암시적으로,
스위프트 표준 라이브러리에서 정의한 RawRepresentable 프로토콜을 준수한다
그 결과, rawValue 프로퍼티와 init?(rawValue: RawValue)라는 서명의 실패 가능한 이니셜라이저를 가진다
ExampleEnum.b.rawValue처럼 rawValue 프로퍼티를 사용하여 열거체 case의 원시 값에 접근할 수 있다
원시 값을 사용하여 해당하는 case 를 찾을 수도 있다
예를 들면, ExampleEnum(rawValue: 5)처럼 열거체의 실패 가능 이니셜라이저를 호출하면 되는데 이는 옵셔널 case를 반환한다
원시 값 타입을 가지는 case에 대한 더 많은 정보와 예제를 보려면 Raw Values 페이지를 참고하자
4) 열거체 case 에 접근하기(Accessing Enumeration Cases)
열거체 타입의 case를 참조하려면, EnumerationType.enumerationCase 처럼 점(.)구문을 사용한다
Enumeration Syntax와 Implicit Member Expression 에서 설명한 것처럼,
열거체 타입을 추론할 수 있는 상황일 땐 이를 생략할 수 있으며, 이 때 점은 여전히 필요하다
열거체 case의 값을 확인하려면, Matching Enumeration Values with a Switch Statement에서 본 것처럼 switch 문을 사용한다
Enumeration Case Pattern에서 설명한 것처럼,
열거체 타입은 switch 문 case 블럭 안의 열거체 case 패턴과 패턴을 맞춰보게 된다
구조체 선언
구조체 선언(structure declaration)은 프로그램에 이름지은 구조체 타입을 도입한다
구조체 선언은 struct 키워드로 하며 형식은 다음과 같다
구조체 본문은 0개 이상의 선언(declarations)을 담는다
이러한 선언(declarations)은 저장 및 계산 프로퍼티, 타입 프로퍼티,
인스턴스 메소드, 타입 메소드, 이니셜라이저,
서브 스크립트, type aliases 및 심지어 다른 구조체와, 클래스, Actor, 열거체 선언을 포함할 수 있다
구조체 선언에 디이니셜라이저나 프로토콜 선언을 담을 순 없다
다양한 종류의 선언을 포함한 구조체에 대한 논의 및 여러 가지 예제는,
Structures and Classes 페이지를 통해 확인할 수 있다
구조체 타입은 어떤 개수의 프로토콜도 채택할 수 있지만, 클래스나, 열거체, 또는 다른 구조체를 상속할 순 없다
이전에 선언한 구조체로 인스턴스를 생성하는 데는 세 가지 방법이 있다
Default Initializers에서 설명한 것처럼, 구조체의 기본 이니셜라이저를 호출한다
구조체에서 선언한 프로퍼티의 초기화 과정은 Initialization에서 설명한다
Accessing Properties에서 설명한 것처럼, 점(.)구문으로 구조체 인스턴스의 프로퍼티에 접근할 수 있다
구조체는 값 타입이며 변수나 상수에 할당할 때, 또는 함수 호출의 인자로 전달할 때, 구조체 인스턴스가 복사된다
값 타입에 대한 정보는, Structures and Enumerations Are Value Types 부분을 보도록하자
Extension Declaration에서 논의한 것처럼, 익스텐션 선언으로 구조체 타입의 동작을 확장할 수 있다
클래스 선언
클래스 선언(class declaration)은 프로그램에 이름지은 클래스 타입을 도입한다
클래스 선언은 class 키워드로 하며 형식은 다음과 같다
클래스 본문은 0개 이상의 선언(declaration)을 담는다
이러한 선언(declarations)은 저장 및 계산 프로퍼티,
인스턴스 메소드, 타입 메소드, 이니셜라이저, 단일한 디이니셜라이저,
서브 스크립트, type aliases 및 심지어 다른 클래스, 구조체, Actor 및 열거체 선언을 포함할 수 있다
클래스 선언에 프로토콜 선언을 담을 순 없다
다양한 종류의 선언을 포함한 클래스에 대한 논의 및 여러 가지 예제는,
Structures and Classes 페이지를 통해 확인할 수 있다
클래스 타입은 자신의 상위 클래스(superclass)라는 단 하나의 부모 클래스만 상속할 수 있지만
프로토콜은 어떤 개수든 채택할 수 있다
클래스 이름(class anme)과 콜론 뒤에 상위 클래스(superclass)를 첫 번째로 나타내고,
그 뒤에 채택한 프로토콜(adopted protocols)을 작성한다
제네릭 클래스는 다른 제네릭 및 제네릭 하지 않은 클래스를 상속할 수 있지만,
제네릭 하지 않은 클래스는 다른 제네릭 하지 않은 클래스만 상속할 수 있다
콜론 뒤에 제네릭 상위 클래스 이름을 쓸 때는,
반드시 해당 제네릭 클래스의 전체 이름을 포함해야 해서, 제네릭 매개 변수 절도 포함해야 한다
Initializer Declaration에서 논의한 것처럼,
클래스엔 지정 이니셜라이저와 convenience 이니셜라이저가 있을 수 있다
클래스의 지정 이니셜라이저는 반드시 클래스가 선언한 모든 프로퍼티를 초기화해야 하며
그걸 반드시 상위 클래스의 지정 이니셜라이저 호출 전에 해야 한다
클래스는 상위 클래스의 프로퍼티, 메소드, 서브 스크립트 및 이니셜라이저를 재정의할 수 있다
재정의한 프로퍼티, 메소드, 서브 스크립트, 지정 이니셜라이저엔 반드시 override 선언 수정자를 표시해야 한다
여기서 그냥 이니셜라이저가 아니라 지정 이니셜라이저라고 한 건,
재정의할 이니셜라이저가 상위 클래스에서 지정 이니셜라이저든 convenience 이니셜라이저든,
재정의하고 나면 무조건 지정 이니셜라이저가 되기 때문이다
하위 클래스가 상위 클래스 이니셜라이저를 구현하길 요구하려면,
상위 클래스 이니셜라이저에 required 선언 수정자를 표시한다
해당 이니셜라이저의 하위 클래스 구현에도 반드시 required 선언 수정자를 표시해야 한다
상위 클래스(superclass)에서 선언한 프로퍼티와 메소드를 현재 클래스가 상속하긴 하지만,
상위 클래스(superclass)에서 선언한 지정 이니셜라이저는 Automatic Initializer Inheritance의 설명 조건과 만날 때만 하위 클래스가 상속한다
스위프트 클래스는 범용 기초 클래스를 상속하지 않는다
‘범용 기초 클래스 (universal base class)’ 는 오브젝티브-C 언어의 NSObject 같은 클래스를 말하며
대부분의 OOP 클래스들은 이러한 범용 기초 클래스를 상속한다
이전에 선언한 클래스로 인스턴스를 생성하는 데는 두 가지 방법이 있다
Default Initializers에서 설명한 것처럼, 클래스의 기본 이니셜라이저를 호출한다
Accessing Properties에서 설명한 것처럼, 점(.)구문으로 클래스 인스턴스의 프로퍼티에 접근할 수 있다
클래스는 참조 타입이며 변수나 상수에 할당할 때 또는 함수 호출의 인자로 전달할 때,
클래스 인스턴스를,복사하기 보단 참조한다
참조 타입에 대한 정보는 Classes Are Reference Types 페이지 부분을 보도록 하자
Extension Declaration에서 논의한 것처럼, 익스텐션 선언으로 클래스 타입의 동작을 확장할 수 있다
Actor 선언
Actor 선언은 프로그램에 이름지은 액터 타입을 도입한다
Actor 선언은 actor 키워드로 하며 형식은 다음과 같다
액터 본문은 0개 이상의 선언(declarations)을 담는다
이러한 선언(declarations)은 저장 및 계산 프로퍼티,
인스턴스 메소드, 타입 메소드, 이니셜라이저, 단일한 디이니셜라이저,
서브 스크립트, type aliases,
심지어 다른 클래스와, 구조체, 및 열거체 선언을 포함할 수 있다
다양한 종류의 선언을 포함한 행위자에 대한 논의 및 여러 가지 예제는, Actors 페이지에서 확인할 수 있다
액터 타입은 어떤 개수의 프로토콜이든 채택할 수 있지만,
클래스, 열거체, 구조체, 또는 다른 액터를 상속할 수 없다
하지만, @objc 특성을 표시한 액터는 암시적으로 NSObjectProtocol 프로토콜을 준수하며
오브젝티브-C 런타임에서 NSObject의 하위 타입으로 노출된다
이전에 선언한 액터로 인스턴스를 생성하는 데는 두 가지 방법이 있다
기본적으로 액터의 멤버는 해당 액터로 격리(actor isolation)된다
메소드 본문이나 프로퍼티 게터 같은 코드는 해당 액터 위에서 실행한다
액터 안의 코드는 이미 동일한 액터 위에서 실행하고 있기 때문에 서로 동기로 상호 작용할 수 있지만,
액터 밖의 코드엔 반드시 await 를 표시해서 해당 코드가 다른 액터 위에서 비동기로 실행 중이라는 걸 지시해야 한다
키 경로(key paths)는 액터의 격리 멤버를 참조할 수 없다
액터로 격리한 저장 프로퍼티를 동기 함수의 In-Out 매개 변수로 전달할 순 있지만 비동기 함수로는 불가능하다
액터는 격리 안한 멤버도 가질 수 있는데, 이들의 선언엔 nonisolated 키워드를 표시한다
격리 안한 멤버는 액터 밖의 코드 처럼 실행하는데, 액터의 어떤 격리 상태와도 상호 작용할 수 없으며
호출하는 쪽에서 사용할 때 await 키워드를 표시하지 않는다
액터의 멤버는 그게 격리 안한 것이나 비동기인 경우에만 @objc 특성을 표시할 수 있다
액터가 선언한 프로퍼티의 초기화 과정은 Initialization 에서 설명한다
Accessing Properties에서 설명한 것처럼, 점(.) 구문으로 액터 인스턴스의 프로퍼티에 접근할 수 있다
액터는 참조 타입이며 변수나 상수에 할당할 때, 또는 함수 호출의 인수로 전달할 때,
액터의 인스턴스를 복사하지 않고 참조한다
참조 타입에 대한 정보는 Classes Are Reference Types 페이지를 참고하자
Extension Declaration에서 논의한 것처럼, 익스텐션 선언으로 액터 타입의 동작을 확장할 수 있다
프로토콜 선언
프로토콜 선언(protocol declaration)은 프로그램에 이름지은 프로토콜 타입을 도입한다
프로토콜 선언은 protocol 키워드로 전역에서 선언하며 형식은 다음과 같다
프로토콜 본문은 0개 이상의 프로토콜 멤버 선언(protocol member declarations)을 담는데,
어떤 프로토콜 채택 타입이든 반드시 충족해야 할 준수 요구 사항을 설명한다
특히, 프로토콜은 준수 타입이 반드시 구현해야 할 특정한 프로퍼티, 메소드, 이니셜라이저, 서브 스크립트를 선언할 수 있다
프로토콜은 결합 타입(associated types)이라는 특수한 종류의 타입 별명도 선언할 수 있는데
이것으로 프로토콜의 다양한 선언들 사이의 관계를 지정할 수 있다
프로토콜 선언엔 클래스, 구조체, 열거체 및 다른 프로토콜 선언을 담을 수 없다
프로토콜 멤버 선언(protocol member declarations)은 밑에서 자세히 논의한다
프로토콜 타입은 다른 프로토콜을 어떤 개수든 상속할 수 있다
프로토콜 타입이 다른 프로토콜을 상속할 땐, 다른 프로토콜의 필수 조건 집합을 한 군데로 모으므로(aggregated),
현재 프로토콜을 상속한 어떤 타입이든 반드시 모든 요구 사항을 준수해야 한다
프로토콜 상속 사용법에 대한 예제는, Protocol Inheritance 부분을 보도록 하자
이전에 선언한 타입에 프로토콜 준수성을 추가하려면
해당 타입의 익스텐션 선언에 프로토콜을 채택하면 된다
익스텐션에선 채택한 프로토콜의 모든 요구 사항들을 반드시 구현해야 한다
타입이 모든 요구 사항을 이미 구현하고 있다면, 익스텐션 선언의 본문을 비워둬도 된다
기본적으로 프로토콜을 준수하는 타입은 반드시 프로토콜 안에 선언한 모든 프로퍼티, 메소드, 및 서브 스크립트를 구현해야 한다
그렇더라도, 해당 프로토콜 멤버 선언에 optional 선언 수정자를 표시하면
준수 타입의 구현부가 옵셔널임을 지정할 수 있다
프로토콜에서 선언한 요구 사항의 구현 그 자체가 옵셔널이라는 의미며
프로토콜의 준수 타입이 구현을 하면 구현부가 있는 것이고 아니면 nil이게 된다
optional 수정자는 objc 특성을 표시한 멤버와 objc 특성을 표시한 프로토콜의 멤버에만 적용할 수 있다
그 결과, 옵셔널 멤버 요구 사항을 담은 프로토콜은 클래스 타입만이 채택하고 준수할 수 있다
optional 선언 수정자를 사용하는 방법과 선택적 프로토콜 멤버에 접근하는 방법에 대한 자세한 내용은 Optional Protocol Requirements을 참조하면 된다
ex. 준수 타입이 구현했는지 확신할 수 없을 경우
열거체 case도 타입 멤버의 프로토콜 요구 사항을 만족할 수 있다
어떤 결합 값도 없는 열거체 case는 Self 타입인 읽기 전용 타입 변수의 프로토콜 요구 사항을 만족하며,
결합 값이 있는 열거체 case는 매개 변수와 인수 라벨이 case의 결합 값과 일치하고 Self를 반환하는 함수의 프로토콜 요구 사항을 만족한다
예를 들면 다음과 같다
클래스만 프로토콜을 채택하게 제약하려면,
콜론 뒤의 상속 프로토콜(inherited protocols) 목록에 AnyObject 프로토콜을 포함시키면 된다
예를 들어, 다음 프로토콜은 클래스 타입만 채택할 수 있다
AnyObject 요구 사항을 표시한 어떤 프로토콜을 상속한 프로토콜도 마찬가지로 클래스 타입만 채택할 수 있다
프로토콜은 이름 있는 타입이며
따라서, Protocols as Types에서 논의한 것처럼
다른 이름 있는 타입과 동일한 위치에 모두 나타날 수 있다
하지만, 프로토콜로 인스턴스를 생성할 순 없는데
프로토콜이 자신이 지정한 요구 사항의 구현을 실제로 제공하진 않기 때문이다
Delegation에서 설명한 것처럼, 프로토콜을 사용하여 클래스 또는 구조체의 delegate가 구현해야 하는 메서드를 선언할 수 있다
1) 프로토콜 프로퍼티 선언
프로토콜 요구 사항이 반드시 프로퍼티를 구현해야 한다고 선언하려면
프로토콜 선언 본문 안에 프로토콜 프로퍼티 선언을 포함하면 된다
프로토콜 프로퍼티 선언은 특수한 형식의 변수 선언이다
다른 프로토콜 멤버 선언 처럼 해당 프로퍼티 선언은
프로토콜을 준수할 타입의 게터 및 세터 요구 사항만 선언한다
그 결과, 이를 선언한 프로토콜이 게터와 세터를 직접 구현하진 않는다
준수 타입은 다양한 방식으로 게터 및 세터 요구 사항을 만족할 수 있다
프로퍼티 선언이 get 과 set 키워드를 둘 다 포함하면,
준수 타입이 읽기 쓰기가 둘 다 가능한
즉, 게터와 세터를 둘 다 구현한 저장 변수 프로퍼티 또는 계산 프로퍼티로 이를 구현할 수 있다
하지만, 해당 프로퍼티 선언을 상수 프로퍼티 또는 읽기 전용 계산 프로퍼티로 구현할 순 없다
프로퍼티 선언이 get 키워드만 포함하면, 어떤 종류의 프로퍼티로도 이를 구현할 수 있다
준수 타입이 프로토콜의 프로퍼티 요구 사항을 구현하는 예제는,
Property Requirements 페이지 부분을 보도록 하자
프로토콜 선언 안에서 타입 프로퍼티 요구 사항을 선언하려면,
프로퍼티 선언에 static 키워드를 표시한다
프로토콜을 준수하는게 구조체와 열거체면 프로퍼티를 static 키워드로 선언하고,
프로토콜을 준수하는게 클래스면 프로퍼티를 static 이나 class 키워드로 선언한다
구조체나, 열거체 또는 클래스에 프로토콜 준수성을 추가하는 익스텐션은
자신이 확장할 타입과 똑같은 키워드를 사용해야한다.
타입 프로퍼티 요구 사항에 기본 구현을 제공하는 익스텐션은 static 키워드를 사용한다
Variable Declaration 페이지 부분도 보도록 하자
2) 프로토콜 메소드 선언
프로토콜 요구 사항이 반드시 메소드를 구현해야 한다고 선언하려면
프로토콜 선언 본문 안에 프로토콜 메소드 선언을 포함하면 된다
프로토콜 메소드 선언의 형식은 함수 선언과 똑같으나, 두 가지 예외가 있는데
함수 본문을 포함하지 않고 함수 선언 부분에서 어떤 기본 매개 변수 값도 제공할 수 없다
프로토콜의 메소드 요구 사항을 구현한 준수 타입 예제는, Method Requirements 부분을 보도록 하자
프로토콜 선언 안에서 클래스 및 정적 메소드 요구 사항을 선언하려면,
메소드 선언에 static 선언 수정자를 표시한다
프로토콜을 준수하는게 구조체와 열거체면 메소드를 static 키워드로 선언하고,
프로토콜을 준수하는게 클래스면 메소드를 static 이나 class 키워드로 선언한다
구조체나, 열거체, 또는 클래스에 프로토콜 준수성을 추가하는 익스텐션은
자신이 확장할 타입과 똑같은 키워드를 사용해야 한다
타입 메소드 요구 사항에 기본 구현을 제공하는 익스텐션은 static 키워드를 사용한다
Function Declaration 페이지 부분도 보도록 하자
3) 프로토콜 이니셜라이저 선언
프로토콜 준수 타입이 반드시 이니셜라이저를 구현해야 한다고 선언하려면
프로토콜 선언 본문 안에 프로토콜 이니셜라이저 선언을 포함하면 된다
프로토콜 이니셜라이저 선언은 이니셜라이저 본문을 포함하지 않는다는 것만 제외하면 이니셜라이저 선언과 형식이 똑같다
준수 타입이 실패하지 않는 프로토콜 이니셜라이저 요구 사항을 만족하려면
실패하지 않는 이니셜라이저나 init! 실패 가능 이니셜라이저를 구현하면 된다
준수 타입이 실패 가능 프로토콜 이니셜라이저 요구 사항을 만족하려면 어떤 종류의 이니셜라이저든 구현하면 된다
클래스가 프로토콜 이니셜라이저 요구 사항을 만족하기 위해 이니셜라이저를 구현할 때,
이미 final 선언 수정자로 표시한 클래스가 아니라면,
이니셜라이저에 반드시 required 선언 수정자를 표시해야 한다
Initializer Declaration 페이지 부분도 보도록 하자
4) 프로토콜 서브 스크립트 선언
프로토콜 준수 타입이 반드시 서브 스크립트를 구현해야 한다고 선언하려면
프로토콜 선언 본문 안에 프로토콜 서브 스크립트 선언을 포함하면 된다
프로토콜 서브 스크립트 선언은 특수한 형식의 서브 스크립트 선언이다
서브 스크립트 선언은 프로토콜을 준수하는 타입에 대한 최소한의 게터 및 세터 구현 요구 사항만 선언한다
서브 스크립트 선언이 get 과 set 키워드를 둘 다 포함하면,
준수 타입은 반드시 게터와 세터 절을 둘 다 구현해야 한다
서브 스크립트 선언이 get 키워드만 포함하면,
준수 타입은 적어도 게터 절은 반드시 구현해야 하며 세터 절 구현은 옵션으로 할 수 있다
프로토콜 선언 안에서 정적 서브 스크립트 요구 사항을 선언하려면,
서브 스크립트 선언에 static 선언 수정자를 표시하면 된다
프로토콜을 준수하는게 구조체와 열거체면 서브 스크립트를 static 키워드로 선언하고,
프로토콜을 준수하는게 클래스면 서브 스크립트를 static 이나 class 키워드로 선언한다
구조체, 열거체, 또는 클래스에 프로토콜 준수성을 추가하는 익스텐션은
자신이 확장할 타입과 똑같은 키워드를 사용한다
정적 서브 스크립트 요구 사항에 기본 구현을 제공하는 익스텐션은 static 키워드를 사용한다
Subscript Declaration 페이지 부분도 보도록 하자
5) 프로토콜 associatedtype 선언
프로토콜은 associatedtype 키워드를 사용하여 연관 타입을 선언한다
연관 타입은 프로토콜 선언의 일부로 사용할 타입에 별명을 제공한다
연관 타입은 제네릭 매개 변수 절에 있는 타입 매개 변수와 비슷하지만,
선언된 프로토콜에서 Self와 결합되어 있다
이러한 context 안에서 Self는 프로토콜을 준수한 최종 결과 타입(eventual type)을 의미한다
더 많은 정보와 예제는 Associated Types 페이지 부분을 보도록 하자
프로토콜 선언에서 제네릭 where 절을 사용하여 연관 타입을 다시 선언하지 않고
다른 프로토콜에서 상속된 연관 타입에 요구 사항을 추가할 수 있다
예를 들어, 아래의 하위 프로토콜 선언은 다음과 같다
Type Alias Declaration 페이지 부분도 보도록 하자
이니셜라이저 선언
이니셜라이저 선언은 프로그램에 클래스, 구조체, 열거체의 이니셜라이저를 도입한다
이니셜라이저 선언은 init 키워드로 하며 두 가지 기초 형식이 있다
구조체, 열거체, 클래스 타입엔 이니셜라이저가 어떤 개수든 있을 수 있지만,
클래스 이니셜라이저들에선 규칙과 결합 동작이 다르다
구조체 및 열거체와는 달리, 클래스엔 두 종류의 이니셜라이저가 있는데
Initialization에서 설명한 지정(designated) 이니셜라이저와 편리한(convenience) 이니셜라이저가 있다
다음 형식으론 구조체, 열거체의 이니셜라이저 및 클래스의 지정 이니셜라이저를 선언한다
클래스의 지정 이니셜라이저는 모든 클래스 프로퍼티를 직접 초기화한다
이는 같은 클래스에 있는 어떤 다른 이니셜라이저도 호출할 수 없으며,
클래스에 상위 클래스가 있으면, 반드시 상위 클래스 지정 이니셜라이저를 호출해야 한다
클래스가 상위 클래스에서 어떤 프로퍼티를 상속했으면,
반드시 상위 클래스 지정 이니셜라이저를 호출하고 나야 현재 클래스에서 해당 프로퍼티를 설정할 수 있다
지정 이니셜라이저는 클래스 선언 상황에서만 선언할 수 있으므로 익스텐션 선언으로 클래스에 추가할 순 없다
구조체와 열거체 안의 이니셜라이저는 선언한 다른 이니셜라이저를 호출하여 일부 또는 전체 초기화 과정을 위임할 수 있다
클래스의 편리한 이니셜라이저를 선언하려면, 이니셜라이저 선언에 convenience 선언 수정자를 표시하면 된다
편라한 이니셜라이저는 또 다른 편라한 이니셜라이저나 클래스의 지정 이니셜라이저 로 초기화 과정을 위임할 수 있다
그렇더라도, 초기화 과정은 반드시 지정 이니셜라이저 호출로 끝나야 궁극적으로 클래스 프로퍼티를 초기화하게 된다
편라한 이니셜라이저는 상위 클래스 이니셜라이저를 호출할 수 없다
지정 및 편리한 이니셜라이저에 required 선언 수정자를 표시하면
모든 하위 클래스가 이니셜라이저를 필수로 구현하길 요구할 수 있다
해당 이니셜라이저의 하위 클래스 구현부에도 반드시 required 선언 수정자를 표시해야 한다
기본적으로, 상위 클래스 안에 선언한 이니셜라이저를 하위 클래스가 상속하진 않는다
그렇더라도, 하위 클래스가 자신의 모든 저장 프로퍼티를 기본 값으로 초기화하면서
자기 자신은 어떤 이니셜라이저도 직접 정의하지 않으면, 상위 클래스의 모든 이니셜라이저를 상속하게 된다
하위 클래스가 상위 클래스의 모든 지정 이니셜라이저를 재정의하면, 상위 클래스의 편리한 이니셜라이저를 상속하게 된다
메소드, 프로퍼티, 서브 스크립트에서 처럼
재정의한 지정 이니셜라이저엔 override 선언 수정자를 표시할 필요가 있다
함수와 메소드처럼 이니셜라이저도 에러를 던지거나 다시 던질 수 있다
그리고 함수와 메소드처럼 이니셜라이저의 매개 변수 뒤에 throws 나 rethrows 키워드를 써서 적절한 동작을 지시한다
마찬가지로, 이니셜라이저는 비동기일 수 있으며 async 키워드로 이를 명시한다
다양한 타입 선언 안의 이니셜라이저 예제를 보려면, Initialization 페이지를 보도록 하자
1) 실패 가능한 이니셜라이저
실패 가능한 이니셜라이저(failable initializer)는 이니셜라이저를 선언한 타입의
옵셔널 인스턴스나 암시적으로 포장 푸는 옵셔널 인스턴스를 만들어 내는 타입의 이니셜라이저다
그 결과, 실패 가능한 이니셜라이저는 nil 을 반환하여 초기화 실패를 지시할 수 있다
옵셔널 인스턴스를 만드는 실패 가능한 이니셜라이저를 선언하려면,
이니셜라이저 선언의 init 키워드에 물음표를 덧붙이면 된다 - init?
암시적으로 포장 푸는 옵셔널 인스턴스를 만드는 실패 가능한 이니셜라이저를 선언하려면,
이니셜라이저 선언의 init 키워드에 느낌표를 덧붙이면 된다 - init!
아래 예제는 init? 실패 가능한 이니셜라이저로 구조체의 옵셔널 인스턴스를 만드는 걸 보여준다
init? 실패 가능한 이니셜라이저는 반드시 결과를 옵셔널로 다룬다는 것만 제외하면,
실패하지 않는 이니셜라이저 호출과 똑같은 방식으로 호출한다
실패 가능한 이니셜라이저는 이니셜라이저 본문 구현부의 어떤 시점에서든 nil 을 반환할 수 있다
실패 가능한 이니셜라이저는 어떤 종류의 이니셜라이저로도 일을 위임할 수 있다
실패하지 않는 이니셜라이저는 또 다른 실패하지 않는 이니셜라이저 또는 init! 실패 가능한 이니셜라이저로 일을 위임할 수 있다
실패하지 않는 이니셜라이저가 init? 실패 가능한 이니셜라이저로 일을 위임하려면
상위 클래스 이니셜라이저의 결과를 강제 언래핑하여 작성하면 된다
ex. super.init()!
초기화 실패는 이니셜라이저의 위임을 통하여 전파된다
특히, 실패 가능한 이니셜라이저가 위임한 이니셜라이저가 실패하여 nil 을 반환하면,
일을 위임 했던 이니셜라이저도 실패하여 암시적으로 nil 을 반환하게 된다
실패하지 않는 이니셜라이저가 일을 위임한 init! 실패 가능한 이니셜라이저가 실패하여 nil 을 반환하면,
마치 ! 연산자로 nil 값인 옵셔널의 포장을 풀려고 한 것처럼 런타임 에러가 발생하게 된다
실패 가능한 지정 이니셜라이저를 하위 클래스에서 재정의하는 건
어떤 종류의 지정 이니셜라이저로도 할 수 있다
실패하지 않는 지정 이니셜라이저를 하위 클래스에서 재정의하는 건
실패하지 않는 지정 이니셜라이저만 할 수 있다
실패 가능한 이니셜라이저에 대한 더 많은 정보와 예제를 보려면,
Failable Initializers 페이지 부분을 보도록 하자
디이니셜라이저 선언
디이니셜라이저 선언은 클래스 타입의 디이니셜라이저를 선언한다
디이니셜라이저는 매개 변수를 취하지 않으며 형식은 다음과 같다
클래스 객체의 메모리 해제 직전, 더 이상 클래스 객체로의 어떤 참조도 없을 때 디이니셜라이저를 자동으로 호출한다
디이니셜라이저는 클래스 선언 본문에서만 선언할 수 있으며 익스텐션에선 안되고 클래스마다 최대 하나만 가질 수 있다
하위 클래스는 상위 클래스의 디이니셜라이저를 상속하며,
하위 클래스 객체의 해제 직전 암시적으로 호출한다
하위 클래스 객체는 자신의 상속 사슬 안의 모든 디이니셜라이저를 실행 종료할 때까지 해제되지 않는다
디이니셜라이저는 직접 호출할 수 없다
클래스 선언에서의 디이니셜라이저 사용법에 대한 예제는, Deinitialization 페이지를 보도록 하자
익스텐션 선언
익스텐션 선언은 기존 타입의 동작을 확장하도록 한다
익스텐션 선언은 extension 키워드로 하며 형식은 다음과 같다
익스텐션 선언 본문은 0개 이상의 선언(declarations)을 담는다
이러한 선언은 계산 프로퍼티, 계산 타입 프로퍼티,
인스턴스 메소드, 타입 메소드, 이니셜라이저, 서브 스크립트 선언,
심지어 클래스, 구조체 및 열거체 선언을 포함할 수 있다
익스텐션 선언엔 디이니셜라이저 및 프로토콜 선언이나,
저장 프로퍼티, 프로퍼티 옵저버, 또는 다른 익스텐션 선언을 담을 수 없다
프로토콜 익스텐션 안의 선언엔 final 을 표시할 수 없다
다양한 종류의 선언을 포함한 익스텐션에 대한 논의 및 여러 가지 예제는, Extensions 페이지를 보도록 하자
타입 이름(type name)이 클래스, 구조체, 열거체 타입이면 익스텐션은 해당 타입을 확장한다
타입 이름(type name)이 프로토콜 타입이면, 익스텐션은 해당 프로토콜을 준수한 모든 타입을 확장한다
제네릭 타입 또는 연관 타입을 가진 프로토콜을 확장하는 익스텐션 선언은 요구 사항을 포함할 수 있다
확장한 타입 또는 확장한 프로토콜을 준수한 타입의 인스턴스가 요구 사항을 만족하면,
선언 안에서 지정한 동작을 인스턴스가 얻게 된다
익스텐션 선언에는 이니셜라이저 선언이 포함될 수 있습니다
즉, 확장하려는 타입이 다른 모듈에 정의되어 있는 경우,
이니셜라이저 선언은 해당 타입의 멤버가 적절하게 초기화되도록 보장하기 위해
해당 모듈에 이미 정의된 이니셜라이저에게 업무를 위임해야 한다
기존 타입의 프로퍼티, 메소드, 이니셜라이저를 해당 타입의 익스텐션에서 재정의할 순 없다
익스텐션 선언으로 기존 클래스, 구조체, 열거체 타입에 프로토콜 준수성을 추가하려면
채택한 프로토콜(adopted protocols)을 지정하면 된다
익스텐션 선언으로 기존 클래스에 클래스 상속성을 추가할 순 없으므로,
타입 이름(type name)과 콜론 뒤엔 프로토콜 목록만 지정할 수 있다
1) 조건부 준수(Conditional Conformance)
제네릭 타입이 프로토콜을 조건부로 준수하도록 확장해서,
타입의 인스턴스가 특정한 요구 사항과 만날 때만 프로토콜을 준수하게 할 수 있다
프로토콜에 조건부 준수를 추가하려면 익스텐션 선언이 요구 사항을 포함하면 된다
재정의된 요구 사항이 일부 제네릭 컨텍스트에서 사용되지 않는 경우(Overridden Requirements Aren’t Used in Some Generic Contexts)
일부 제네릭 상황에선 프로토콜 조건부 준수로 동작을 획득한 타입이
해당 프로토콜 요구 사항의 특수화 구현을 항상 사용하는 건 아니다
‘특수화 구현(specialized implementations)’은 제네릭 타입의 타입 매개 변수를 특수한 타입으로 고정하여, 적용 범위를 좁힌 구현을 의미한다
해당 동작을 묘사하기 위해, 다음 예제는 두 프로토콜과 그 두 프로토콜을 모두 조건부로 준수하는 제네릭 타입을 하나 정의한다
Pair 구조체는 자신의 제네릭 타입이 각 Loggable이나 TitledLoggable을 준수할 때마다 Loggable과 TitledLoggable을 준수한다
아래 예제에서, oneAndTwo 는 Pair 의 인스턴스로,
TitledLoggable을 준수하는데 이는 String이 TitleLoggable을 준수하기 때문이다
oneAndTwo의 log() 메소드를 직접 호출할 땐, 제목 문자열을 담은 특수화 버전을 사용하게 된다
하지만, 제네릭 상황 안에서나 Loggable 프로토콜의 인스턴스로써 oneAndTwo 를 사용할 땐, 특수화 버전을 사용하지 않게 된다
스위프트는 Pair가 Loggable을 준수하는 데 필요한 최소 요구 사항만을 참고하여 호출할 log() 구현을 고른다
이런 이유로, 그 대신 Loggable 프로토콜이 제공하는 기본 구현을 사용하게 된다
doSomething(_:) 으로 전달한 인스턴스의 log() 를 호출할 땐, 기록 문자열에서 커스텀된 제목을 생략한다
2) Protocol Conformance Must Not Be Redundant(프로토콜 준수는 중복되어서는 안 된다)
구체적인 타입(concrete type)은 특정 프로토콜을 단 한 번만 준수할 수 있다
스위프트는 중복 프로토콜 준수를 에러로 표시한다
이런 종류의 에러는 두 가지 종류의 상황에서 마주칠 수 있다
첫 번째 상황은 동일한 프로토콜을 서로 다른 요구 사항으로 명시적으로 여러 번 준수할 때다
두 번째 상황은 동일한 프로토콜을 암시적으로 여러 번 상속할 때다
아래에서 이러한 상황을 논의해보자
명시적 중복 해결(Resolving Explicit Redundancy)
구체적인 타입에 대한 여러 개의 익스텐션은 익스텐션의 요구 사항이 상호 배타적(mutually exclusive)이더라도,
동일한 프로토콜로의 준수를 추가할 수 없다
아래 예제에서 이러한 제약을 실제로 보여준다
두 개의 익스텐션 선언이 Serializable 프로토콜로의 조건부 준수를 추가하려고 하는데,
하나는 원소가 Int 인 배열을 위해서, 다른 하나는 원소가 String 인 배열을 위한 것이다
여러 개의 구체적인 타입을 기준으로 조건부 적합성을 추가해야 하는 경우에는
각 타입이 준수할 수 있는 새 프로토콜을 만들고 조건부 적합성을 선언할 때 해당 프로토콜을 요구 사항으로 사용해야 한다
암시적인 중복 해결(Resolving Implicit Redundancy)
구체적인 타입이 조건부로 프로토콜을 준수하는 경우,
해당 타입은 동일한 요구 사항을 가진 모든 상위 프로토콜을 암시적으로 준수한다
단일 상위 프로토콜에서 상속되는 두 개의 프로토콜을 조건부로 준수하는 유형이 필요한 경우
상위 프로토콜에 대한 적합성을 명시적으로 선언한다
이는 서로 다른 요구 사항으로 부모 프로토콜을 두 번 암묵적으로 준수하는 것을 방지한다
다음 예제에서는 TitledLoggable 프로토콜과 새로운 MarkedLoggable 프로토콜 모두에 대한 조건부 적합성을 선언할 때
충돌을 피하기 위해 Array의 Loggable의 조건부 적합성을 명시적으로 선언한다
Loggable에 대한 조건부 적합성을 명시적으로 선언하는 익스텐션이 없으면
다른 Array 확장은 암시적으로 다음과 같은 선언을 생성하여 에러를 발생시킨다
부모 프로토콜로의 준수성을 명시하면 자식 프로토콜로의 준수성으로 인하여
부모 프로토콜로의 준수성이 암시적으로 생기는 것을 막아준다
서브 스크립트 선언
서브 스크립트 선언은 특별한 한 타입의 객체에 서브 스크립트 지원 기능을 추가하도록 하며
전형적으로 이를 사용하여 집합체(collection), 리스트(list), 시퀀스(sequence) 원소 접근의 편의 구문을 제공한다
첨자 선언은 subscript 키워드로 하며 형식은 다음과 같다
서브 스크립트 선언은 클래스, 구조체, 열거체, 익스텐션, 프로토콜 선언 안에서만 있을 수 있다
매개 변수(paramter)는 서브 스크립트 표현식에서 해당 타입의 원소 접근에 사용하는 하나 이상의 인덱스를 지정한다
ex. object[i] 표현식의 i
원소 접근에 사용할 인덱스는 어떤 타입이어도 되긴 하지만,
각 매개 변수는 반드시 타입 명시 주석을 포함하여 각각의 인덱스 타입을 지정해야 한다
반환 타입(return type)은 접근할 원소의 타입을 지정한다
계산 프로퍼티처럼 서브 스크립트 선언은 접근한 원소 값의 읽기와 쓰기를 지원한다
게터를 써서 값을 읽고, 세터를 써서 값을 쓴다
세터 절은 옵션이며, 게터만 필요할 땐 두 절 모두 생략하고 단순히 요청 값을 직접 반환 할 수도 있다
세터 절을 제공한다면 반드시 게터 절도 제공해야 한다
세터 이름(setter name)과 테두리 괄호는 옵션이다
세터 이름을 제공하면, 이를 세터의 매개 변수 이름으로 사용한다
세터 이름을 제공하지 않으면, 세터의 기본 매개 변수 이름은 value가 된다
세터의 매개 변수 타입은 반환 타입(return type)과 똑같다
자신을 선언한 타입 안에서 서브 스크립트 선언을 중복 정의하려면,
중복 정의할 것의 매개 변수(paramter)나 반환 타입(return type)이 다르기만 하면 된다
상위 클래스에서 상속한 서브 스크립트 선언을 재정의할 수도 있다
그럴 땐, 반드시 재정의한 서브 스크립트 선언에 override 선언 수정자를 표시해야 한다
서브 스크립트 매개 변수는 함수 매개 변수와 동일한 규칙을 따르지만, 두 가지 예외가 있다
기본적으로, 서브 스크립트 연산에서 쓰는 매개 변수엔, 함수, 메소드, 이니셜라이저와 달리, 인수 라벨이 없다
하지만, 함수와 메소드 및 이니셜라이저에서 쓰는 것과 똑같은 구문의 명시적 인수 라벨을 제공할 순 있다
또한, 서브 스크립트 연산엔 In-Out 매개 변수가 있을 수 없다
서브 스크립트 연산 매개 변수는 Special Kinds of Parameters에서 설명한대로 구문의 기본 값을 가질 수 있다
Protocol Subscript Declaration에서 설명한 것처럼, 프로토콜 선언 안에서 서브 스크립트 연산을 선언할 수도 있다
서브 스크립트에 대한 더 많은 정보 및 첨자 선언 예제를 보려면, Subscripts 페이지를 보도록 하자
1) 타입 서브 스크립트 선언
서브 스크립트를 타입의 인스턴스가 아닌 타입이 드러내도록 선언하려면,
서브 스크립트 선언에 static 선언 수정자를 표시한다
클래스의 타입 계산 프로퍼티는 class 선언 수정자를 대신 표시하여 상위 클래스 구현을 하위 클래스가 재정의하는 걸 허용할 수 있다
클래스 선언에서 static 키워드는 선언에 class 와 final 선언 수정자를 둘 다 표시한 것과 똑같은 효과를 가진다
연산자 선언
연산자 선언(operator declaration)은 프로그램에 새로운 중위(infix), 접두사(prefix), 접미사(postfix) 연산자를 도입하며
operator 키워드로 선언한다
연산자는 서로 다른 세 개의 고정 위치(fixity)로 선언할 수 있는데
중위, 접두사, 접미사가 있다
연산자의 고정 위치(fixity)가 지정하는 건 피연산자에 대한 연산자의 상대 위치가 된다
연산자 선언은 각각의 고정 위치마다 하나씩, 총 세 개의 기초 형식이 있다
연산자 고정 위치는 연산자 선언에서 operator 키워드 앞에 infix, prefix, postfix 선언 수정자를 표시하여 지정한다
각 형식에서 연산자 이름엔 Operators에서 정의한 연산자 문자만 담을 수 있다
다음 형식은 새로운 중위 연산자를 선언한다
중위 연산자(infix operator)는 두 개의 피연산자 사이에 작성하는 이항(binary) 연산자로,
익숙한 1 + 2 표현식 안의 덧셈 연산자(+)가 있다
중위 연산자는 옵션으로 우선 순위 그룹을 지정할 수 있다
연산자의 우선 순위 그룹을 생략하면,
DefaultPrecedence라는 기본 우선권 그룹을 스위프트가 사용하며,
이는 TernaryPrecedence 바로 위의 우선 순위를 가진다
더 많은 정보는 Precedence Group Declaration 페이지 부분을 보도록 하자
다음 형식은 새로운 접두사 연산자를 선언한다
접두사 연산자(prefix operator)는 피연산자 바로 앞에 작성하는 단항(unary) 연산자로,
!a 표현식 안의 접두사 논리 부정(NOT) 연산자(!)가 있다
접두사 연산자 선언은 우선 순위를 지정하지 않는다
접두사 연산자는 비-결합적(nonassociative)이다
다음 형식은 새로운 접미사 연산자를 선언한다
접미사 연산자(postfix operator)는 피연산자 바로 뒤에 작성하는 단항(unary) 연산자로,
a! 표현식 안의 강제-언래핑 연산자(!)가 있다
접두사 연산자처럼 접미사 연산자 선언도 우선 순위를 지정하지 않는다
접미사 연산자는 비-결합적이다
새로운 연산자를 선언한 후, 연산자와 똑같은 이름의 정적 메소드를 선언하여 이를 구현한다
정적 메소드는 연산자가 인수로 취하는 값 타입 안의 멤버다
예를 들어, Double 과 Int 를 곱하는 연산자는 Double 이나 Int 구조체의 정적 메소드로 구현한다
접두사나 접미사 연산자를 구현할 거면, 반드시 해당 메소드 선언에 해당하는 prefix 나 postfix 선언 수정자도 표시해야 한다
새로운 연산자의 생성 및 구현 방법에 대한 예제를 보려면, Custom Operators 페이지 부분을 보도록 하자
우선 순위 그룹 선언
우선 순위 그룹 선언(precedence group declaration)은 프로그램에 새로운 중위 연산자 우선 순위 그룹을 도입한다
연산자 우선 순위는 괄호 그룹이 없을 때, 연산자와 피-연산자를 얼마나 밀접하게 연결할지 지정한다
우선 순위 그룹 선언의 형식은 다음과 같다
더 낮은 그룹 이름들(lower group names) 및 더 높은 그룹 이름들(higher group names) 목록은
새로운 우선 순위 그룹과 기존 우선 순위 그룹 사이의 관계를 지정한다
lowerThan 우선 순위 그룹 특성은 현재 모듈 밖에서 선언한 우선 순위 그룹의 참조에만 사용할 수도 있다
2 + 3 * 5 표현식처럼 두 연산자가 피-연산자를 두고 경쟁할 때는,
상대적으로 더 높은 우선 순위의 연산자가 피-연산자와 더 밀접하게 연결된다
스위프트는 수 없이 많은 우선 순위 그룹을 정의하여
표준 라이브러리에서 제공한 연산자와 나란히 사용한다
ex. 덧셈 (+) 및 뺄셈 (-) 연산자는 AdditionPrecedence 그룹에 속하고,
곱셈 (*) 및 나눗셈 (/) 연산자는 MultiplicationPrecedence 그룹에 속한다
스위프트 표준 라이브러리가 제공하는 우선 순위 그룹의 완전한 목록은,Operator Declarations 페이지 항목을 보도록 하자
연산자 결합성(associativity)은 괄호 그룹이 없을 때
동일한 우선 순위를 가진 일렬로 나열된 연산자들을 어떻게 함께 그룹지을지 지정한다
연산자 결합성은 left나 right, 또는 none 이라는 상황에-민감한(context-sensitive) 키워드 중 하나를 작성하여 지정한다
결합성을 생략하면 기본 값은 none이 된다
왼쪽 결합(left-associative) 연산자는 왼쪽에서-오른쪽으로 그룹 짓는다
ex. 뺄셈 연산자 (-) 는 왼쪽-결합이라서, 표현식 4 - 5 - 6 은 (4 - 5) - 6 으로 그룹지어 -7 이라고 평가된다
오른쪽-결합 연산자는 오른쪽에서-왼쪽으로 그룹짓고,
none 이라는 결합성을 지정한 연산자는 아예 결합을 하지 않는다
동일한 우선 순위인 비-결합 연산자는 서로 인접하여 나타날 수 없다
ex. < 연산자의 결합성은 none 이며, 이는 1 < 2 < 3 같은 건 유효한 표현식이 아니다
우선 순위 그룹의 할당(assignment)은 옵셔널 체이닝을 포함한 연산에 사용할 때의 연산자 우선 순위를 지정한다
true 로 설정할 땐, 옵셔널 체이닝 중에 해당 우선 순위 그룹 안의 연산자가 표준 라이브러리 할당 연산자와 동일한 그룹 규칙을 사용한다
false 로 설정하거나 생략할 땐, 우선 순위 그룹 안의 연산자가 할당을 하지 않는 연산자와 동일한 옵셔널 체이닝 규칙을 따른다
선언 수정자(Declaration Modifiers)
선언 수정자 (declaration modifiers) 는 선언의 동작이나 의미를 수정하는 키워드 또는 상황에-민감한 키워드다
선언 수정자는 있다면 선언의 특성과 선언을 도입한 키워드 사이에 적절한 키워드 또는 상황에-민감한 키워드를 작성하여 지정한다
class
해당 수정자를 클래스 멤버에 적용하면 멤버가 클래스 인스턴스의 멤버가 아닌 클래스 그 자체의 멤버라는 걸 지시한다
해당 수정자는 있고 final 수정자는 없는 상위 클래스 멤버는 하위 클래스에서 재정의할 수 있다
dynamic
해당 수정자를 적용한 어떤 클래스 멤버든 오브젝티브-C 로 나타낼 수 있다
멤버 선언에 dynamic 수정자를 표시할 땐, 그 멤버로의 접근은 항상 오브젝티브-C 런타임을 써서 동적 급파(dispatched)한다
C++ 등에서 사용하는 가상 함수 테이블 (virtual function table)을 사용한다는 의미로
해당 멤버로의 접근은 컴파일러가 절대 인라인(inline; 코드 줄에 넣기)하거나 탈-가상화(devirtualized)하지 않습니다
dynamic 수정자로 표시한 선언은 오브젝티브-C 런타임을 써서 급파하기 때문에, 반드시 objc 특성을 표시해야 한다
final
해당 수정자는 클래스 또는 클래스의 프로퍼티, 메소드, 서브 스크립트 멤버에 적용한다
클래스에 적용하면 클래스로 하위 클래스를 만들 수 없다는 걸 지시한다
클래스의 프로퍼티, 메소드, 서브 스크립트에 적용하면 클래스 멤버를 어떤 하위 클래스에서도 재정의할 수 없다는 걸 지시한다
final 사용법의 예제는 Preventing Overrides 페이지 부분을 보도록 하자
lazy
해당 수정자를 클래스나 구조체의 저장 변수 프로퍼티에 적용하면,
프로퍼티에 최초로 접근할 때, 최대 한 번만 프로퍼티의 초기 값을 계산하고 저장한다는 걸 지시한다
lazy 수정자의 사용법에 대한 예제는, Lazy Stored Properties 페이지 부분을 보도록 하자
optional
해당 수정자를 프로토콜의 프로퍼티, 메소드, 서브 스크립트 멤버에 적용하면
준수 타입이 이러한 멤버를 구현하는 게 필수는 아니라는 걸 지시한다
optional 수정자는 objc 특성을 표시한 프로토콜에만 적용할 수 있다
그 결과, 클래스 타입만 옵셔널 멤버 요구 사항을 담은 프로토콜을 채택하고 준수할 수 있다
optional 수정자 사용법에 대한 더 많은 정보와 옵셔널 프로토콜 멤버의 접근법에 대한 길잡이는, Optional Protocol Requirements 페이지 부분을 보도록 하자
ex. 준수 타입이 구현했는지 확실하지 않을 경우
required
해당 수정자를 클래스의 지정 또는 편리한 이니셜라이저에 적용하면
반드시 모든 하위 클래스가 그 이니셜라이저를 구현해야 한다는 걸 지시한다
해당 이니셜라이저의 하위 클래스 구현에도 반드시 required 수정자를 표시해야 한다
static
해당 수정자를 구조체, 클래스, 열거체, 프로토콜 멤버에 적용하면
그 타입의 인스턴스에 대한 멤버라기 보단, 타입 자체의 멤버라는 걸 지시한다
클래스 선언 영역 안에서 멤버 선언에 static 수정자를 작성하면
해당 멤버 선언에 class 와 final 수정자를 모두 작성하는 것과 똑같은 효과를 가진다
하지만, 클래스의 상수 타입 프로퍼티는 예외인데
class 또는 final 선언을 쓸 수 없기 때문에 static은 클래스 선언 영역에서의 의미가 아닌 일반적인 의미를 가진다
unowned
해당 수정자를 저장 변수, 상수, 저장 프로퍼티에 적용하면
변수나 프로퍼티의 값이 객체로의 unowned 참조라는 걸 지시한다
객체를 메모리에서 해제한 후에 변수나 프로퍼티에 접근하려 하면 런타임 에러가 발생하게 된다
weak 참조와 마찬가지로 프로퍼티나 값의 타입은 반드시 클래스 타입이어야 하며
weak 참조와 달리 옵셔널이 아닌 타입이다
unowned 수정자에 대한 예제 및 더 많은 정보는, Unowned References 페이지 부분을 보도록 하자
unowned(safe)
unowned의 전체 철자를 명시한 것이다
unowned(unsafe)
해당 수정자를 저장 변수, 상수, 저장 프로퍼티에 적용하면
변수나 프로퍼티의 값이 객체로의 소유하지 않은 참조라는 걸 지시한다
객체를 메모리에서 해제한 후에 변수나 프로퍼티에 접근하려 하면,
객체였던 메모리 장소에 접근할 건데, 이는 안전하지 않은-메모리 연산이다
weak 참조와 마찬가지로 프로퍼티나 값의 타입은 반드시 클래스 타입이어야 하며
weak 참조와 달리 옵셔널이 아닌 타입이다
unowned 수정자에 대한 예제 및 더 많은 정보는, Unowned References 페이지 부분을 보도록 하자
weak
해당 수정자를 저장 변수나 저장 변수 프로퍼티에 적용하면
변수나 프로퍼티의 값이 객체로의 weak 참조라는 걸 지시한다
변수나 프로퍼티의 타입은 반드시 옵셔널 클래스 타입이어야 한다
객체를 해제한 후에 변수나 프로퍼티에 접근하면, 값이 nil이다
weak 수정자에 대한 예제 및 더 많은 정보는, Unowned References 페이지 부분을 보도록 하자
1) 접근 지정자
스위프트는 다섯 가지 수준의 접근 제어를 제공하는데
공개(open), 공용(public), 내부(internal), 파일 전용(file private), 개인 전용(private)이 있다
선언에 아래의 접근 수준 중 하나를 표시하여 선언의 접근 수준을 지정한다
Access Control 페이지에서 자세히 알아보자
open
해당 지정자를 선언에 적용하면
선언과 동일한 모듈 안의 코드가 선언에 접근하고 하위 클래스를 만들 수 있다는 걸 지시한다
open 접근-수준 지정자를 표시한 선언은 해당 선언이 담긴 모듈을 불러온 모듈 코드도 접근하고 하위 클래스를 만들 수 있다
public
해당 지정자를 선언에 적용하면
선언과 동일한 모듈 안의 코드가 선언에 접근하고 하위 클래스를 만들 수 있다는 걸 지시한다
public 접근-수준 자정자를 표시한 선언은 해당 선언이 담긴 모듈을 불러온 모듈 코드도 접근할 수 있다
하위 클래스 만들기는 안된다
internal
해당 지정자를 선언에 적용하면 선언과 동일한 모듈 안의 코드만 선언에 접근할 수 있다는 걸 지시한다
기본적으로 대부분의 선언에 internal 접근-수준 지정자를 암시적으로 표시한다
fileprivate
해당 지정자를 선언에 적용하면 선언과 동일한 소스 파일 안의 코드만 선언에 접근할 수 있다는 걸 지시한다
private
해당 지정자를 선언에 적용하면 선언을 직접 둘러싼 영역 안의 코드만 선언에 접근할 수 있다는 걸 지시한다
접근 제어를 위해 동일한 파일에 있는 동일한 타입의 익스텐션들은 접근-제어 영역을 공유한다
확장한 타입도 동일한 파일 안에 있으면, 타입의 접근-제어 영역을 공유한다
타입 선언 안에서 선언한 개인 전용(private) 멤버를 익스텐션에서 접근할 수 있고,
한 익스텐션 안에서 선언한 개인 전용 멤버를 다른 익스텐션 및 타입 선언에서 접근할 수도 있다
위에 있는 각각의 접근-수준 지정자는 옵션으로 단일 인자를 받는데,
예를 들어, private(set)처럼 set 키워드를 괄호로 테두리 쳐서 구성한다
Getters and Setters에서 설명한 것처럼,
변수나 서브 스크립트의 세터 접근 수준이 변수나 서브 스크립트 그 자체의 접근 수준 보다 낮거나 같게 지정하고 싶을 때
이런 형식의 접근-제어 지정자를 사용한다
The text was updated successfully, but these errors were encountered: