Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

타입 #69

Open
simoniful opened this issue Nov 17, 2022 · 0 comments
Open

타입 #69

simoniful opened this issue Nov 17, 2022 · 0 comments

Comments

@simoniful
Copy link
Owner

simoniful commented Nov 17, 2022

스위프트에는, 두 가지 종류의 이름 붙인 타입과 복합 타입이 있다

이름 붙인 타입(named type)은 정의할 때 한 특별한 이름을 줄 수 있는 타입이다
이름 붙인 타입은 클래스와, 구조체, 열거체, 및 프로토콜을 포함한다
예를 들어, MyClass 라는 이름의 커스텀 클래스의 인스턴스는 MyClass 라는 타입을 가지게 된다
스위프트 표준 라이브러리는 커스텀 타입 외에도
배열, 딕셔너리 및 옵셔널 값을 나타내는 타입을 포함하여
일반적으로 사용되는 많은 이름 붙인 타입을 정의하고 있다

다른 언어에선 보통 기초 또는 근원으로 고려하는 수치 값, 문자, 및 문자열 같은 자료(data) 타입은 실제론 이름 붙인 타입으로,
스위프트 표준 라이브러리 안에서 구조체로 정의 및 구현되어 있다
이름 붙인 타입이기 때문에 ExtensionsExtension Declaration에서 논한 것처럼,
프로그램의 필요에 적합하도록, 익스텐션 선언으로, 이들의 동작을 확장할 수 있다

복합 타입(compound types)은 이름이 없는 타입으로, 스위프트 언어 그 자체에 정의되어 있다
두 가지의 복합 타입이 있는데 함수 타입과 튜플 타입이다
복합 타입 안에 이름 붙인 타입과 다른 복합 타입이 담겨 있을 수도 있다

ex. 튜플 타입인 (Int, (Int, Int))는 두 원소를 담고 있는데
첫 번째는 이름 붙인 타입인 Int 이고,
두 번째는 또 다른 복합 타입인 (Int, Int) 이다

이름 붙인 타입이나 복합 타입 주위에 괄호를 둘 수 있다
하지만, 타입 주위에 괄호를 추가한다고 어떤 효과가 있는 건 아니다
ex. (Int) 는 Int 와 같다

이번 챕터는 스위프트 언어 자체가 정의하는 타입을 논의하고 스위프트의 타입 추론 동작도 설명한다

캡쳐 2022-11-17 오후 8 12 30

타입 명시 주석 (Type Annotation)

타입 명시 주석(type annotation)은 변수 또는 표현식의 타입을 명시적으로 지정한다
타입 명시 주석은, 다음 예제에서 보듯, 콜론 (:) 으로 시작해서 타입으로 끝난다

let someTuple: (Double, Double) = (3.14159, 2.71828)
func someFunction(a: Int) { /* ... */ }

첫 번째 예제에선, someTuple 표현식이 (Double, Double)이라는 튜플 타입을 가진다고 지정한다
두 번째 예제에선, someFunction 함수의 매개 변수 a가 Int 타입을 가진다고 지정한다

타입 명시 주석은 타입 앞에 타입 특성 목록을 옵션으로 담을 수 있다
@escaping (Int) -> Int 같이 (Int) -> Int 앞에 @escaping 등을 둘 수 있다는 의미다

캡쳐 2022-11-17 오후 8 15 55

타입 식별자 (Type Identifier)

타입 식별자(type identifier)는 이름 붙인 타입 또는 이름 붙거나 복합인 타입의 typealias을 가리킨다

대부분의 시간 동안, 타입 식별자는 식별자와 동일한 이름을 가진 이름 붙인 타입을 직접적으로 가리킨다

ex. Int 라는 타입 식별자는 Int 라고 이름 붙인 타입을 직접적으로 가리키며,
ex. Dictionary <String, Int>라는 타입 식별자는 Dictionary <String, Int>라고 이름 붙인 타입을 직접적으로 가리킨다

타입 식별자가 동일한 이름의 타입을 가리키지 않는 경우가 두 가지 있다
첫 번째 경우는, 타입 식별자가 이름 붙거나 복합인 타입의 typealias을 가리키는 경우다
구체적인 사례로, 아래 예제의, 타입 명시 주석에서 사용한 Point는 (Int, Int)라는 튜플 타입을 가리킨다

typealias Point = (Int, Int)
let origin: Point = (0, 0)

두 번째 경우는, 타입 식별자가 점 (.) 구문을 사용하여
다른 모듈에서 선언하거나 다른 타입 안에 중첩한 이름 붙인 타입을 가리키는 경우다

ex. 다음 코드에 있는 타입 식별자는 ExampleModule 모듈 안에서 선언한 MyType 이라는 이름 붙인 타입을 참조한다

var someValue: ExampleModule.MyType

image

튜플 타입

튜플 타입(tuple type)은 쉼표로 구분한 타입 목록을 괄호로 테두리 쳐서 표현한다

함수 반환 타입으로 튜플 타입을 사용하면 여러 값을 담은 단일 튜플로 함수 반환을 할 수 있다
튜플 타입의 원소에 이름을 붙일 수도 있고 해당 이름을 사용하여 개별 원소 값을 참조할 수도 있다
원소 이름은 식별자와 그 바로 뒤의 콜론(:)으로 구성된다
이러한 특징 둘 다를 실제로 보여주는 예제는,
Functions with Multiple Return Values 페이지 부분을 보도록 하자

튜플 타입의 원소에 이름이 있을 땐, 그 이름도 타입의 일부가 된다

var someTuple = (top: 10, bottom: 12)  // someTuple is of type (top: Int, bottom: Int)
someTuple = (top: 4, bottom: 42) // OK: names match
someTuple = (9, 99)              // OK: names are inferred
someTuple = (left: 5, right: 5)  // Error: names don't match

모든 튜플 타입엔 두 개 이상의 타입이 담겨 있지만,
()라는, 빈 튜플 타입의 타입 별명인 Void는 예외다

캡쳐 2022-11-17 오후 8 25 51

함수 타입

함수 타입 (funtion type) 은 함수나, 메소드, 또는 클로저의 타입을 나타내며
화살표(->)로 구분된 매개 변수와 반환 타입으로 구성된다

(parameter type) -> return type

매개 변수 타입(parameter type)은 쉼표로 구분한 타입 목록이다
반환 타입(return type)이 튜플 타입일 수 있기 때문에,
여러 개의 값을 반환하는 함수와 메소드도 지원한다

어떤 타입 T에 대한 함수 타입 () -> T의 매개 변수에 autoclosure 특성을 적용하면
자신을 호출한 쪽에서 암시적으로 클로저를 생성할 수 있다
이는 함수를 호출할 때 클로저를 명시하지 않아도
표현식의 평가를 미루게 하는 구문상의 편의를 제공한다
자동 클로저 함수 타입 매개 변수의 예제는, Autoclosures 페이지에서 확인 할 수 있다

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
let customerProvider = { customersInLine.remove(at: 0) }

print("Now serving \(customerProvider())!")
// Prints "Now serving Chris!"
// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]

func serve(customer customerProvider: () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: { customersInLine.remove(at: 0) } )
// Prints "Now serving Alex!"
// customersInLine is ["Ewa", "Barry", "Daniella"]

func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}
serve(customer: customersInLine.remove(at: 0))
// Prints "Now serving Ewa!"
// customersInLine is ["Barry", "Daniella"]

var customerProviders: [() -> String] = []
func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
    customerProviders.append(customerProvider)
}
collectCustomerProviders(customersInLine.remove(at: 0))
collectCustomerProviders(customersInLine.remove(at: 0))

print("Collected \(customerProviders.count) closures.")
// Prints "Collected 2 closures."

for customerProvider in customerProviders {
    print("Now serving \(customerProvider())!")
}
// Prints "Now serving Barry!"
// Prints "Now serving Daniella!"

함수 타입의 매개 변수 타입(parameter type)는 가변(variadic)매개 변수를 가질 수 있다
구문상, 가변 매개 변수는 기초 타입 이름과 바로 뒤의 세 점 (...) 으로, Int... 처럼, 구성된다
가변 매개 변수는 기초 타입 이름의 원소를 담은 배열로 취급한다
구체적인 사례로, 가변 매개 변수 Int... 는 [Int]로 취급된다
가변 매개 변수의 사용 예제는, Variadic Parameters 페이지에서 확인 할 수 있다

func arithmeticMean(_ numbers: Double...) -> Double {
    var total: Double = 0
    for number in numbers {
        total += number
    }
    return total / Double(numbers.count)
}

arithmeticMean(1, 2, 3, 4, 5)
// returns 3.0, which is the arithmetic mean of these five numbers
arithmeticMean(3, 8.25, 18.75)
// returns 10.0, which is the arithmetic mean of these three numbers

in-out 매개 변수를 지정하려면,
매개 변수 타입의 접두사로 inout 키워드를 앞에 붙인다
가변 매개 변수나 반환 타입은 inout 키워드로 표시할 수 없다
in-out 매개 변수는 In-Out Parameters 페이지에서 더 상세하게 알 수 있다

함수 타입에 매개 변수가 하나만 있는데 그 매개 변수 타입이 튜플 타입이면,
함수 타입을 작성할 때 튜플 타입에 반드시 괄호가 있어야 한다

ex. ((Int, Int)) -> Void 는 단일한 튜플 타입 매개 변수인 (Int, Int) 를 취하고
어떤 값도 반환하지 않는 함수 타입이다
이와 대조하여, 괄호가 없는, (Int, Int) -> Void 는 두 개의 Int 매개 변수를 취하고
어떤 값도 반환하지 않는 함수 타입이다

마찬가지로, Void는 ()의 typealias이기 때문에,
함수 타입 (Void) -> Void 는 단일한 빈 튜플 인자를 취하는 함수인 (()) -> () 와 똑같다
해당 타입은 인자를 취하지 않는 함수인 () -> () 와는 다르다

함수와 메소드 안의 인수 라벨은 해당 함수 타입의 일부분이 아니다
예를 들면 다음과 같은데

func someFunction(left: Int, right: Int) {}
func anotherFunction(left: Int, right: Int) {}
func functionWithDifferentLabels(top: Int, bottom: Int) {}

var f = someFunction // The type of f is (Int, Int) -> Void, not (left: Int, right: Int) -> Void.
f = anotherFunction              // OK
f = functionWithDifferentLabels  // OK

func functionWithDifferentArgumentTypes(left: Int, right: String) {}
f = functionWithDifferentArgumentTypes     // Error

func functionWithDifferentNumberOfArguments(left: Int, right: Int, top: Int) {}
f = functionWithDifferentNumberOfArguments // Error

인수 라벨은 함수 타입의 일부분이 아니기 때문에, 함수 타입을 작성할 때 생략한다

var operation: (lhs: Int, rhs: Int) -> Int     // Error
var operation: (_ lhs: Int, _ rhs: Int) -> Int // OK
var operation: (Int, Int) -> Int               // OK

함수 타입이 하나 이상의 화살표(->)를 포함하고 있으면,
함수 타입을 오른쪽에서 왼쪽으로 그룹짓는다
ex. (Int) -> (Int) -> Int 함수 타입은 (Int) -> ((Int) -> Int) 라고 이해해야 한다
즉, 함수가 취하는 건 Int 고 반환하는 건 Int 를 취하고 반환하는 또 다른 함수(클로저)가 된다

에러를 던지거나(throw) 다시 던질 수 있는(rethrow) 함수 타입엔
반드시 throws 키워드를 표시해야 한다
throws 키워드는 함수 타입의 일부분이며,
던지지 않는 함수는 던지는 함수의 하위 타입이다
그 결과, 던지는 함수를 쓸 수 있는 곳이면 던지지 않는 함수를 사용할 수 있다
던지는 및 다시 던지는 함수는 Throwing Functions and MethodsRethrowing Functions and Methods 부분에서 설명한다

1) Nonescaping 클로저의 제약 사항 (Restrictions for Nonescaping Closures)

매개 변수가 non-escaping 함수면 Any 타입의 속성이나, 변수, 또는 상수에 저장할 수 없는데,
이들이 값이 escaping할 지도 모르기 때문이다

매개 변수가 non-escaping 함수면 또 다른 non-escaping 함수 매개 변수에 인자로 전달할 수 없다
이런 제약 사항은 스위프트가 메모리 충돌 검사를 실행 시간 대신 컴파일 시간에 더 많이 하도록 돕는데
예제는 다음과 같다

let external: (() -> Void) -> Void = { _ in () }
func takesTwoFunctions(first: (() -> Void) -> Void, second: (() -> Void) -> Void) {
    first { first {} }       // Error
    second { second {}  }    // Error

    first { second {} }      // Error
    second { first {} }      // Error

    first { external {} }    // OK
    external { first {} }    // OK
}

위 코드에서, takesTwoFunctions(first:second:)의 매개 변수는 둘 다 함수다
둘 중 어떤 매개 변수도 @escaping으로 표시하지 않아서, 그 결과 둘 다 non-escaping 함수다

위 예제에서 “에러(Error)” 로 표시한 네 함수 호출은 컴파일 에러를 일으킨다
first 와 second 매개 변수는 non-escaping 함수이기 때문에,
이들을 또 다른 non-escaping 함수 매개 변수의 인자로 전달할 순 없다
이와 대조적으로, “괜찮음(OK)” 으로 표시한 두 함수 호출은 컴파일러 에러를 일으키지 않는다
이러한 함수 호출은 제약 사항을 위반하지 않는데
이는 external이 takesTwoFunctions(first:second:)의 매개 변수가 아니기 때문이다

해당 제약 사항을 피하고자 한다면, 매개 변수 하나를 @escaping로 표시하거나,
아니면 withoutActuallyEscaping(_:do:) 함수를 써서
non-escaping 함수 매개 변수 중 하나를 임시로 escaping 함수로 변환할 수 있다
메모리 접근 충돌을 피하는 것에 대한 정보는, Memory Safety 페이지에서 확인할 수 있다

image

배열 타입(Array Type)

스위프트 언어는 표준 라이브러리의 Array 타입에 대해 다음 같은 편리한 구문을 제공한다

[type]

다음의 두 선언은 서로 같다

let someArray: Array<String> = ["Alex", "Brian", "Dave"]
let someArray: [String] = ["Alex", "Brian", "Dave"]

두 경우 모두, someArray 상수가 문자열 배열이라고 선언
배열 원소는 대괄호 안에 유효 색인 값을 지정하는 첨자를 통하여 접근할 수 있는데
someArray[0] 는 0번 색인의 원소인, "Alex" 를, 참조한다

다차원 배열을 생성하려면 대괄호 쌍을 중첩하면 되는데,
여기서 가장 안쪽 대괄호 쌍 안에 원소의 기초 타입 이름을 담는다
예를 들어, 세 쌍의 대괄로로 3차원 정수 배열을 생성할 수 있다

var array3D: [[[Int]]] = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]

다차원 배열 안의 원소에 접근할 땐, 가장-왼쪽 서브 스크립트의 인덱스가 가장 바깥쪽 배열 인덱스에 있는 원소를 참조한다
오른쪽으로 그 다음 서브 스크립트의 인덱스는 한 수준 안에 중첩된 배열 색인에 있는 원소를 참조한다

위 예제에서 보면,
array3D[0] 는 [[1, 2], [3, 4]] 를 참조하고,
array3D[0][1] 은 [3, 4] 를 참조하며,
array3D[0][1][1] 은 4 라는 값을 참조한다는 걸 의미한다

스위프트 표준 라이브러리의 Array 타입에 대한 자세한 논의는,Arrays 페이지에서 확인할 수 있다

캡쳐 2022-11-21 오전 10 58 11

딕셔너리 타입(Dictionary Type)

스위프트 언어는 표준 라이브러리의 Dictionary<Key, Value> 타입에 대해 다음 같은 편리한 구문을 제공한다

[key type: value type]

다음의 두 선언은 서로 같다

let someDictionary: [String: Int] = ["Alex": 31, "Paul": 39]
let someDictionary: Dictionary<String, Int> = ["Alex": 31, "Paul": 39]

두 경우 모두, someDictionary 상수가 키는 문자열이고 값은 정수인 딕셔너리라고 선언한다

딕셔너리의 값은 대괄호 안에 해당 키를 지정하는 서브 스크립트를 통하여 접근할 수 있는데
someDictionary["Alex"] 는 "Alex" 키와 결합된 값을 참조한다
서브 스크립트는 딕셔너리의 값 타입에 대한 옵셔널 값을 반환하게 된다
딕셔너리 안에 지정한 키가 담겨 있지 않으면, 서브 스크립트가 nil 을 반환합니다

딕셔너리의 키 타입은 스위프트 표준 라이브러리의 Hashable 프로토콜을 반드시 준수해야 한다

스위프트 표준 라이브러리의 Dictionary 타입에 대한 자세한 논의는, Dictionaries 페이지에서 확인할 수 있다

캡쳐 2022-11-21 오전 11 01 26

옵셔널 타입(Optional Type)

스위프트 언어는, 스위프트 표준 라이브러리 안에서,
Optional라는 이름 붙인 타입의 편리한 구문으로 접미사 ? 를 정의한다
다음의 두 선언은 서로 같다

var optionalInteger: Int?
var optionalInteger: Optional<Int>

두 경우 모두, optionalInteger 변수가 옵셔널 정수 타입을 가진다고 선언한다
타입과 ? 사이엔 공백이 없다 - 삼항 연산자와 구분

Optional 타입은, none 과 some(Wrapped) 라는, 두 case 값을 가진 열거체로써,
이를 사용하여 있을 수도 있고, 없을 수도 있는 값을 나타낸다
어떤 타입이든 옵셔널 타입이라고 명시적으로 선언 (하거나 암시적으로 변환) 할 수 있다
옵셔널 변수나 속성을 선언할 때 초기 값을 제공하지 않으면, 그 기본 값은 자동으로 nil이 된다

옵셔널 타입의 인스턴스에 값이 담겨 있으면,
밑에서 보는 것처럼, 접미사 연산자 ! 로 강제해제를 통해 그 값에 접근할 수 있다

optionalInteger = 42
optionalInteger! // 42

! 연산자를 사용하여 nil 값을 가진 옵셔널의 포장을 풀면 런타임 에러가 발생하게 된다

옵셔널 테이닝과 옵셔널 바인딩을 사용하여
옵셔널 표현식에 대한 연산을 조건부로 수행할 수도 있다
값이 nil 이면, 연산 수행을 안하며 따라서 런타임 에러도 발생하지 않도록 구성할 수 있다

옵셔널 타입의 더 많은 정보와 사용 방법 예제를 보려면, Optionals 페이지를 통해 알 수 있다

캡쳐 2022-11-21 오전 11 07 22

암시적으로 언래핑된 옵셔널 타입(Implicitly Unwrapped Optional Type)

스위프트 언어는 표준 라이브러리 안에서, Optional라는 이름 붙인 타입의 구문으로 접미사 ! 를 정의
접근할 때 자동으로 포장을 푸는 추가적인 동작을 가진다
사용하려는 암시적으로 언래핑된 옵셔널이 nil 값이면, 런타임 에러가 발생하게 된다
암시적으로 언래핑하는 동작을 예외로 하면, 다음의 두 선언은 서로 같다

var implicitlyUnwrappedString: String!
var explicitlyUnwrappedString: Optional<String>

타입과 ! 사이에는 공백이 없다

암시적으로 언래핑을 하면 해당 타입을 담은 선언의 의미를 바꾸기 때문에,
딕셔너리나 배열 원소 타입 같이 튜플 타입이나 제네릭 타입 안에 중첩된 옵셔널 타입은 암시적으로 언래핑한다고 표시할 수 없다
예를 들면 다음과 같다

let tupleOfImplicitlyUnwrappedElements: (Int!, Int!)  // Error
let implicitlyUnwrappedTuple: (Int, Int)!             // OK

let arrayOfImplicitlyUnwrappedElements: [Int!]        // Error
let implicitlyUnwrappedArray: [Int]!                  // OK

암시적으로 언래핑된 옵셔널은
옵셔널 값과 똑같은 Optional 타입이기 때문에,
옵셔널을 사용할 수 있는 코드와 동일한 모든 곳에서 암시적으로 언래핑된 옵셔널을 사용할 수 있다

예를 들어, 암시적으로 언래핑된 옵셔널 값을
옵셔널 변수와 상수 및 프로퍼티에 할당할 수도 있고,
그 반대로 지정할 수 있다

옵셔널 처럼 암시적으로 언래핑된 옵셔널 변수나 프로퍼티를 선언할 때
초기 값을 제공하지 않으면 그 기본 값은 자동으로 nil이 된다

옵셔널 체이닝을 사용하여 암시적으로 언래핑하는 옵셔널 표현식에 대한 연산을 조건부로 수행할 수 있다
값이 nil 이면, 연산 수행을 안하며 따라서 런타임 에러도 발생하지 않도록 구성 가능하다

암시적으로 포장 푸는 옵셔널 타입에 대한 더 많은 정보는, Implicitly Unwrapped Optionals 페이지에서 확인할 수 있다

캡쳐 2022-11-21 오전 11 30 14

프로토콜 합성 타입(Protocol Composition Type)

프로토콜 합성 타입(protocol composition type)은
지정한 프로토콜 목록 안의 각 프로토콜을 준수한 타입
또는, 주어진 클래스의 자식 클래스면서 지정한 프로토콜 목록 안의 각 프로토콜을 준수한 타입으로 정의한다
타입 명시 주석과, 제네릭 매개 변수절 및 제네릭 where 절 안의 타입을 지정할 때만 프로토콜 합성 타입을 사용할 수 있다

프로토콜 합성 타입의 형식은 다음과 같다

Protocol 1 & Protocol 2

프로토콜 합성 타입은 여러 프로토콜의 필수 조건을 준수한 타입의 값을,
타입이 준수하고자 하는 각각의 프로토콜을 상속한 새로운 이름 붙인 프로토콜을 명시하여 정의하지 않고도 지정하는 걸 허용한다

예를 들어, ProtocolA와 ProtocolB, 및 ProtocolC를 상속한 새로운 프로토콜을 선언하는 대신
ProtocolA & ProtocolB & ProtocolC 라는 프로토콜 합성 타입을 사용할 수 있다
마찬가지로, 어떤 부모 클래스의 자식 클래스면서 ProtocolA도 준수하는 새로운 프로토콜을 선언하는 대신
부모클래스 & ProtocolA를 사용할 수 있다

프로토콜 합성 목록 안의 각 항목은 다음 중 하나이며, 최대 한 개의 클래스를 목록에 담을 수 있다

  • 클래스의 이름
  • 프로토콜의 이름
  • 실제 타입이 프로토콜 합성 타입이거나 프로토콜 또는 클래스인 타입 별명(type alias)

프로토콜 합성 타입에 타입 별명(type alias)를 포함된 경우,
동일한 프로토콜이 정의에 두 번 이상 나타날 수 있으며 중복은 무시된다
예를 들어, 아래 코드의 PQR 정의는 P & Q & R 과 같다

typealias PQ = P & Q
typealias PQR = PQ & Q & R

캡쳐 2022-11-21 오전 11 40 45

불투명 타입(Opaque Type)

불투명 타입 (opaque type)은 실제 고정 타입의 지정 없이,
프로토콜이나 프로토콜 합성을 준수한 타입을 정의한다

불투명 타입은 함수나 서브 스크립트 연산의 반환 타입, 또는 프로퍼티의 타입으로 나타난다
불투명 타입은, 배열의 원소 타입이나 옵셔널로 래핑된 타입처럼, 튜플 타입 또는 제네릭 타입의 일부분으로 표시될 수 없다

불투명 타입의 형식은 다음과 같다

some constraint

제약 조건(constraint)은 클래스 타입, 프로토콜 타입, 프로토콜 합성 타입, 또는 Any 가 될 수 있다
나열한 프로토콜이나 프로토콜 합성을 준수하거나 나열한 클래스를 상속한 타입의 인스턴스인 값만
불투명 타입의 인스턴스로 사용할 수 있게 된다
불투명 값과 상호 작용하는 코드는 제약 조건(constraint)에서 정의한 방식의 인터페이스로만 값을 사용할 수 있다

프로토콜 선언은 불투명 타입을 포함할 수 없다
클래스는 nonfinal 메소드의 반환 타입으로 불투명 타입을 사용할 수 없다

자신의 반환 타입으로 불투명 타입을 사용한 함수는
반드시 단일한 실제 타입을 공유하는 값을 반환해야 한다 - 함수 안의 모든 return 문으로 반환하는 타입이 모두 동일해야 한다는 의미
반환 타입은 함수의 제네릭 타입 매개 변수 부분인 타입을 포함할 수 있다
예를 들어, 함수 someFunction() 는 T 나 Dictionary<String, T> 타입의 값을 반환할 수 있다

캡쳐 2022-11-21 오전 11 47 30

메타타입 타입(Metatype Type)

메타타입 타입(metatype type)은, 클래스 타입과, 구조체 타입, 열거체 타입 및 프로토콜 타입을 포함한
어떤 타입에 대한 타입 자체를 가리킨다

클래스, 구조체, 또는 열거체 타입의 메타타입은 그 타입 이름 뒤에 .Type 이 붙은 형태다
런타임에 프로토콜을 준수한 고정 타입이 아닌 프로토콜(자체) 타입의 메타타입은 해당 프로토콜 이름 뒤에 .Protocol 이 붙은 형태다

예를 들어,
클래스 타입인 SomeClass 의 메타터압은 SomeClass.Type 이고
프로토콜인 SomeProtocol 의 메타타입은 SomeProtocol.Protocol 이다

self 접미사 표현식을 사용하여 타입을 값으로 접근할 수 있다
예를 들어, SomeClass.self 는, SomeClass 의 인스턴스가 아닌, SomeClass 그 자체를 반환한다
그리고, SomeProtocol.self 는 런타임에 SomeProtocol 을 준수한 타입의 인스턴스가 아닌, SomeProtocol 그 자체를 반환한다
다음 예제에서 보는 것처럼, type(of:) 함수를 타입의 인스턴스로 호출하면 해당 인스턴스의 동적인, 런타임 타입을 값으로 접근할 수 있다

class SomeBaseClass {
    class func printClassName() {
        print("SomeBaseClass")
    }
}

class SomeSubClass: SomeBaseClass {
    override class func printClassName() {
        print("SomeSubClass")
    }
}

let someInstance: SomeBaseClass = SomeSubClass()
// The compile-time type of someInstance is SomeBaseClass,
// and the runtime type of someInstance is SomeSubClass

type(of: someInstance).printClassName()
// Prints "SomeSubClass"

더 많은 정보는, 스위프트 표준 라이브러리 안의 type(of:) 페이지를 보도록 하자

해당 타입의 메타타입 값으로 타입의 인스턴스를 생성하려면, 이니셜라이저 표현식을 사용하면된다
클래스 인스턴스라면 호출한 이니셜라이저를 반드시 required 키워드로 표시하거나 전체 클래스를 final 키워드로 표시해야 한다

class AnotherSubClass: SomeBaseClass {
    let string: String

    required init(string: String) {
        self.string = string
    }

    override class func printClassName() {
        print("AnotherSubClass")
    }
}

let metatype: AnotherSubClass.Type = AnotherSubClass.self
let anotherInstance = metatype.init(string: "some string")

캡쳐 2022-11-21 오전 11 59 32

Any 타입

Any 타입은 다른 모든 타입 값을 담을 수 있다
Any 는 다음에 있는 어떤 타입의 인스턴스에 대한 고정 타입으로도 사용할 수 있습니다.

  • 클래스, 구조체 또는 열거체
  • Int.self 같은, 메타타입
  • 어떤 타입의 성분이든 가진 튜플
  • 클로저나 함수 타입
let mixed: [Any] = ["one", 2, true, (4, 5.3), { () -> Int in return 6 }]

Any 를 인스턴스의 고정 타입으로 사용할 땐,
프로퍼티나 메소드에 접근하기 전에 인스턴스를 알고 있는 타입으로 변환할 필요가 있다
고정 타입이 Any 인 인스턴스는 자신의 원본 동적 타입을 유지하며
as 나, as? 및 as! 타입 변환 연산자로 해당 타입으로 변환할 수 있다
예를 들어, 혼성 배열의 첫 번째 객체를 String 으로 조건부 내림 변환하려면 다음 처럼 as? 를 사용한다

‘혼성 배열 (heterogeneous array)’ 은 배열 안의 객체들이 모두 다른 클래스지만, 공통의 상위 클래스를 가진 배열을 의미한다
해당 정의는 OOP 를 기준으로 한 것이라, 클래스보다는 구조체를 많이 사용하는 스위프트에서는 의미가 조금 다르다.
스위프트라면 공통의 상위 클래스보단 공통으로 준수한 프로토콜이 알맞다
혼성 배열에 대한 더 자세한 정보는, ‘MathWorks’ 의 Designing Heterogeneous Class Hierarchies 페이지를 보도록 하자

if let first = mixed.first as? String {
    print("The first item, '\(first)', is a string.")
}
// Prints "The first item, 'one', is a string."

변환(casting) 에 대한 더 많은 정보는, Type Casting 페이지에서 알 수 있다

AnyObject 프로토콜은 Any 타입과 비슷하다
모든 클래스는 암시적으로 AnyObject를 준수하며
언어에서 정의하는 Any와 달리, AnyObject는 스위프트 표준 라이브러리에서 정의한다
더 많은 정보는 Class-Only ProtocolsAnyObject 페이지에서 알 수 있다

캡쳐 2022-11-21 오후 12 05 52

Self 타입

Self 타입은 특정한 타입이라기 보단, 현재 타입의 이름을 반복하거나 알지 않고도 그 타입을 편리하게 가리키게 해준다

프로토콜 선언이나 프로토콜 멤버 선언 안의,
Self 타입은 프로토콜을 준수하는 최종 결과 타입을 가리킨다

구조체나, 클래스 및 열거체 선언 안의,
Self 타입은 선언이 도입한 타입을 가리킨다

타입 멤버 선언 안의,
Self 타입은 해당 타입을 가리킨다

클래스 선언의 멤버 안에선, Self가 다음으로만 있을 수 있다

  • 메소드의 반환 타입으로
  • 읽기-전용 서브 스크립트의 반환 타입으로
  • 읽기-전용 계산 프로퍼티의 타입으로
  • 메소드 본문 안에서

예를 들어, 아래 코드는 반환 타입이 Self 인 인스턴스 메소드 f 를 보여준다

class Superclass {
    func f() -> Self { return self }
}

let x = Superclass()
print(type(of: x.f()))
// Prints "Superclass"

class Subclass: Superclass { }
let y = Subclass()
print(type(of: y.f()))
// Prints "Subclass"

let z: Superclass = Subclass()
print(type(of: z.f()))
// Prints "Subclass"

위 예제의 마지막 부분은 Self가
변수 자체의 컴파일시 정의한 타입인 Superclass가 아닌,
z 값의 런타임 타입인 Subclass를 가리킨다는 걸 보여준다

중첩 타입 선언 안에선, Self 타입이 가장 안쪽의 타입 선언이 도입한 타입을 가리키게 된다

Self 타입은 스위프트 표준 라이브러리의 type(of:) 함수와 동일한 타입을 가리킨다
Self.someStaticMember 라고 써서 현재 타입의 멤버에 접근하는 건 type(of: self).someStaticMember 라고 쓰는 것과 같다

설명만 보면, type(of:) 함수를 호출하면 되므로, Self가 필요 없을 것처럼 느껴질 수 있다
하지만, type(of:) 함수는 컴파일 시점에 호출하는 게 아닌 런타임에 호출된다
f() 함수의 반환 값은 컴파일 시점에 정해야 하는데, 이 때는 type(of:) 함수를 호출할 수 없다

즉, Self는 컴파일 시점에 현재 타입의 동적 타입을 지정하기 위해 사용한다
type(of:) 함수에 대한 더 자세한 내용은 Apple 개발자 문서의 type(of:) 항목을 보도록 하자

캡쳐 2022-11-21 오후 12 15 28

타입 상속절(Type Inheritance Clause)

타입 상속절 (type inheritance clause)은
이름 붙인 타입이 상속하는 클래스와 이름 붙인 타입이 준수하는 프로토콜이 어느 것인지 지정하는데 사용된다
타입 상속절은 콜론(:)으로 시작하고, 그 뒤에 타입 식별자 목록을 둔다

클래스 타입은 단일한 부모 클래스를 상속하며 프로토콜은 갯수에 상관없이 준수할 수 있다
클래스를 정의할 땐, 부모 클래스 이름이 반드시 타입 식별자 목록 첫 번째에 있어야 하며,
그 뒤로 클래스가 준수해야 할 프로토콜을 나열할 수 있다
클래스가 또 다른 클래스를 상속하지 않으면, 목록의 시작을 프로토콜로 대신할 수 있다
클래스 상속에 대한 논의 확장 및 여러 예제들은, Inheritance 페이지에서 확인할 수 있다

다른 이름 붙인 타입인 구조체와, 열거체 및 프로토콜은 프로토콜 목록만 상속 또는 준수할 수 있다

프로토콜 타입은 다른 프로토콜을 갯수에 상관없이 상속할 수 있다
프로토콜 타입이 다른 프로토콜을 상속할 땐,
다른 프로토콜에 있던 요구 사항 집합을 한 군데로 모으며,
현재 프로토콜을 상속한 어떤 타입이든 반드시 그 모든 요구 사항을 다 준수해야 한다

열거체의 타입 상속 절은 프로토콜 목록이거나,
자신의 case 값에 원시 값을 할당하는 열거체인 경우,
해당 원시 값의 타입을 지정하는 단일 이름 붙인 타입일 수 있다
타입 상속 절을 사용하여 자신의 원시 값 타입을 지정하는 열거체 정의 예제는, Raw Values 페이지를 통해 확인할 수 있다

캡쳐 2022-11-21 오후 12 21 36

타입 추론(Type Inference)

스위프트는 타입 추론 (type inference) 을 광범위하게 사용하여,
코드의 수 많은 변수와 표현식에서 타입 또는 타입 일부분을 생략하는 걸 허용한다

ex. var x: Int = 0 라고 쓰는 대신,
타입을 완전히 생략하여, var x = 0 라고 쓸 수 있다
컴파일러는 x가 Int 타입 값의 이름이라는 걸 올바로 추론한다

이와 비슷하게, context로 전체 타입을 추론할 수 있을 땐 타입 일부분을 생략할 수 있다

ex. let dict: Dictionary = ["A": 1] 이라고 쓰면,
컴파일러가 dict의 타입이 Dictionary<String, Int> 라고 추론한다

위의 두 예제 모두, 표현식 트리(tree)의 말단(leaves)부터 근원(root)까지 타입 정보를 전달한다
즉, var x: Int = 0 에 있는 x의 타입은
0의 타입을 첫 번째로 검사한 다음
해당 타입 정보를 변수 x 라는 근원까지 전달하여 추론하게 된다

스위프트의, 타입 정보는 반대로 근원에서 말단까지 흐를 수도 있다
다음 예제에서 구체적 사례로,
eFloat 상수에 타입 명시 주석(: Float)을 사용하면,
숫자 리터럴 2.71828 의 추론 타입이 Double 대신 Float 타입이도록 추론되게 한다

let e = 2.71828 // The type of e is inferred to be Double.
let eFloat: Float = 2.71828 // The type of eFloat is Float.

스위프트의 타입 추론은 단일 표현식 또는 구문 수준에서 작동한다
즉, 표현식에서 생략된 타입이나 타입 일부분의 추론에 필요한 모든 정보는
반드시 표현식 또는 해당 하위 표현식 중 하나를 타입 검사하여 접근할 수 있어야 한다

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant