타입 Type

타입이란 직역하면 어떤 부류의 형, 유형, 모양, 생김새 입니다.

프로그래밍에서 타입은 값이 가질 수 있는 형태와 그 값에 대해 수행할 수 있는 연산을 정의하는 개념입니다.

더 나누면 정적타입, 동적타입이 존재하지만 이 글에서는 다루지 않습니다

 

이 글에서는 Swift에서 존재하는 타입들을 살펴볼 것 입니다.

더 자세한 내용은 Swift.org에 있습니다

 

 

Swift의 타입

Swift의 타입은 두가지 종류가 있습니다.

  • 명명된 타입 (named type): 특정 이름을 부여할 수 있는 타입으로 클래스, 구조체, 열거형, 프로토콜, 배열, 딕셔너리, 옵셔널 모두 해당됩니다. 필요에따라 확장할 수 있습니다.
  • 복합 타입 (compound type): 정의된 이름이 없는 타입으로 함수와 튜플이 있습니다. 복합 타입은 말 그대로 다른 명명된 타입과 다른 복합 타입을 합칠 수 있습니다.

 

Swift에서 정의된 타입은 아래와 같이 있습니다.

Grammar of a type

type → function-type 	(함수)
type → array-type		(배열)
type → dictionary-type	 	(딕셔너리)	
type → type-identifier		(타입 식별자)
type → tuple-type		(튜플)
type → optional-type	(옵셔널 타입)
type → implicitly-unwrapped-optional-type	(묵시적 언래핑 옵셔널)
type → protocol-composition-type	(프로토콜 조합 타입)
type → opaque-type (불투명 타입)	(some 키워드를 붙이는 것)
type → boxed-protocol-type	(existential type) (프로토콜 타입으로 선언한 것)
type → metatype-type (타입의 타입) (.self .Type)
type → any-type  
type → self-type
type → ( type )

 

 

타입 식별자 (Type Identifier)

타입 식별자는 타입 별칭(type alias)를 나타냅니다.

대부분은 식별자와 명명된 타입이 같아서 직접 참조합니다 (Int타입은 Int에 참조, Dictionary<String,Int>타입은 Dictionary<string,int></string,int>에 참조)

 

명명된 타입과 식별자가 같지 않은 두가지 경우가 있습니다.

첫번째로는 복합타입의 타입별칭입니다.

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

 

두번째로는 다른 모듈에 선언되거나 다른 중첩된 명명 타입입니다.

var someValue: ExampleModule.MyType

 

 

 

튜플 타입 (Tuple Type)

튜플 타입은 소괄호로 묶인 콤마로 구분되는 타입의 리스트입니다.

타입의 요소에 이름을 정할 수 있고, 이름과 타입을 구별하기 위해 세미콜론(:)을 사용합니다.

var someTuple = (top: 10, bottom: 12)
someTuple = (top: 4, bottom: 42)
someTuple = (9, 99)

 

튜플 타입에서 중요한건 요소들의 타입입니다.

someTuple의 타입은 (Int, Int) 이기 때문에 (Int, Int, Int)나 (Int, String)같은 타입은 someTuple에 할당할 수 없습니다.

 

 

 

함수 타입 (Function Type)

함수 타입은 함수, 메서드, 클로저의 타입을 나타냅니다.

함수 타입은 화살표(->)로 구분된 파라미터 타입과 리턴 타입으로 구성됩니다.

(<#parameter type#>) -> <#return type#>

 

문서에 따르면 함수타입 () -> T 의 파라미터는 autoclosure 속성을 적용하여 호출 부분에서 암시적으로 클로저를 생성할 수 있습니다. 라고 설명되어 있습니다.

 

autoclosure이란 함수에 인수로 전달되는 표현식을 래핑하기 위해 자동으로 생성되는 클로저입니다.

간단하게 파라미터에 클로저 () -> T 타입이 있다면, 해당 함수를 호출할 때, 대괄호( { )를 사용하지 않고 표현식만 사용해도 자동으로 클로저로 변환됩니다.

func someFun1(closure: () -> String) {
    print(closure())
}
someFun1 { "hello" }

func someFun2(closure: @autoclosure () -> String) {
    print(closure())
}
someFun2(closure: "hello")

 

함수도 타입으로 정의되면 autoclosure처럼 대괄호 { } 없이 호출 할 수 있습니다.

func someFun2(closure: @autoclosure () -> String) {
    print(closure())
}

let functionSomeFun2 = someFun2
functionSomeFun2("hello")

 

또한 가변파라미터와 inout파라미터를 갖는 함수도 타입으로 구현할 수 있습니다.

func swap(_ num1: inout Int,_ num2: inout Int) {
    let temp: Int = num1
    num1 = num2
    num2 = temp
}
let swap1 = swap
var numA = 10
var numB = 15
swap1(&numA, &numB)
print(numA, numB)
// 출력
// 15 10
func printRange(range: Int...) {
    for i in range {
        print(i)
    }
}
let printRange1 = printRange
printRange1(1, 2, 3)
// 출력
// 1
// 2
// 3

 

함수 타입은 위에 서술했듯이 파라미터 타입과 리턴 타입이 같으면 다른 같은 타입의 함수로도 변경할 수 있습니다.

someFunction을 변수 f에 할당하게 되면 f의 타입은 위 사진과 같이 (Int, Int) -> ()으로 설정됩니다.

f의 타입처럼 파라미터 타입은 (Int, Int), 리턴 타입은 Void인 함수라면 f에 다른 함수로 초기화 할 수 있습니다.

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

var f = someFunction
f = anotherFunction
f = functionWithDifferentLabels

위 세가지 함수는 인자의 이름이 달라도 파라미터 타입과 리턴 타입이 같기 때문에 f로 선언된 변수가 세 가지 함수로 타입을 지정 및 변경하여도 에러가 발생하지 않습니다. (모두 같은 타입이기 때문에)

 

다른 함수의 종류로 에러를 반환할 수 있는 throw 함수, async 함수 역시 타입으로 지정할 수 있습니다.

위 사진처럼 자동 완성으로 볼 때 해당 변수의 타입들은 throws, async 함수타입으로 지정된 것을 볼 수 있습니다.

 

 

배열 타입 Array Type

이미 많이 사용해봤던 배열 타입입니다.

[T], 혹은 Array<T> 형식으로 선언합니다.

let someArray1: Array<String> = ["Alex", "Brian", "Dave"]
let someArray2: [String] = ["Alex", "Brian", "Dave"]
let someArray3: Array<Array<String>> = [["Alex", "Brian"], ["Dave"]]
let someArray4: [[String]] = [["Alex", "Brian"], ["Dave"]]

 

 

딕셔너리 타입 Dictionary Type

Dictionary를 선언하는 방식은 아래와 같습니다!

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

 

 

옵셔널 타입 Optional Type

Swift에서는 Optional<Wrapped>에 대해 접미사 ? 구문을 정의합니다.

옵셔널 타입을 다음과 같이 설정할 수 있습니다.

// enum Optional<Wrapped>
var optionalInteger1: Int?
var optionalInteger2: Optional<Int>

 

 

 

암시적 언래핑 옵셔널 타입 Implicitly Unwrapped Optional Type

암시적 언래핑 옵셔널 타입은 스토리보드에 있는 UI를 코드영역으로 드래그하면 자동으로 생성되는 모습을 볼 수 있었습니다.

@IBOutlet var button: UIButton!

이는 해당 인스턴스가 옵셔널 타입이지만, 강제 언래핑 동작을 자동으로 추가하기 위해 타입 뒤에 접미사 ! 를 추가하여 선언합니다.

button이 nil이라면 런타임 에러를 발생하고, nil이 아닌 값이 존재한다면 따로 옵셔널 바인딩 없이 접근할 수 있습니다.

 

 

 

프로토콜 혼합 타입 Protocol Composition Type

프로토콜 혼합 타입은 말 그대로 다중 프로토콜이 혼합된 하나의 타입으로 나타내게 되고 타입은 아래와 같이 나타냅니다.

<#Protocol 1#> & <#Protocol 2#>
protocol Nameable {
    var name: String { get }
}

protocol Ageable {
    var age: Int { get }
}

typealias Person = Nameable & Ageable

class Jenikeju: Person {
    var name: String
    
    var age: Int
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

혼합된 프로토콜은 protocol1 & protocol2 로 나타낼 수도 있지만, 이를 typealias를 통해 새로운 타입으로 별칭하고 사용할 수 있습니다.

 

혼합된 프로토콜이여도 하위클래스에 상위클래스를 상속하고 다른 프로토콜을 사용할 때, 상위클래스와 프로토콜을 혼합할 수 있습니다.

protocol Nameable {
    var name: String { get }
}

protocol Ageable {
    var age: Int { get }
}

class Animal {
    func feed() { }
}

// class & protocol1 & protocol2
typealias CatCompositionType = Animal & Nameable & Ageable

class Cat: CatCompositionType {
    var name: String
    var age: Int
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}
let cat = Cat(name: "meow", age: 5)
cat.feed()

클래스는 하나의 클래스만 상속받을 수 있기 때문에 최대 하나의 클래스만 혼합할 수 있습니다.

 

또한 혼합 과정에서 중복된 프로토콜은 자동으로 무시됩니다.

// 중복된 프로토콜 혼합
// Ageable & Nameable & Ageable & Ageable = Ageable & Nameable
typealias ManyCompositionType = Ageable & Nameable & Ageable & Ageable

class SomeClass: ManyCompositionType {
    var age: Int
    var name: String
    
}

 

 

 

불투명한 타입 Opaque Type

불투명한 타입은 기본 타입 지정없이 프로토콜 또는 프로토콜 구성을 준수하는 타입을 정의합니다.

some <#constraint#>

constraint는 클래스, 프로토콜, 프로토콜 구성 타입, Any 입니다.

 

불투명한 타입을 이해하기위해 some 키워드를 먼저 이해해봅시다.

 

some

불투명한 타입에서 불투명한이란 의미는 어떤 타입인지 정확히 명시하지 않지만, 컴파일러는 내부적으로 알고 있다는 의미로 생각할 수 있습니다.

Swift에서는 프로토콜을 직접 반환할 수 없습니다.

그래서 아래 코드는 컴파일 에러를 발생합니다.

protocol Shape {
    func area() -> Double
}

struct Circle: Shape {
    var radius: Double = 0.0
    func area() -> Double { return .pi * radius * radius }
}

// protocol을 리턴해서 에러 발생
func makeShape() -> Shape {
    return Circle()
}

★ 하지만 Swift5부터 값 타입도 프로토콜 타입을 리턴할 수 있게되어서 위 코드는 이제 오류가 발생하지 않습니다.

그전에 protocol 타입을 리턴할 수 없어서 some 키워드를 추가하여 해당 프로토콜을 준수하는 객체를 리턴하도록 구현할 수 있습니다.

protocol Shape {
    func area() -> Double
}

struct Circle: Shape {
    var radius: Double = 0.0
    func area() -> Double { return .pi * radius * radius }
}

// some 키워드 추가
func makeShape() -> some Shape {
    return Circle()
}

some 키워드가 없을 땐 Shape 프로토콜을 준수하는 많은 객체중 어떤 객체가 리턴되는지 정확하게 명시되어 있지 않아서 컴파일러가 오류를 발생시켰습니다.

some 키워드를 추가하여 something?과 비슷한 의미로 'Shape 프로토콜을 준수하는 어떤 클래스나 구조체, 열거형 등의 타입'을 리턴하겠다고 지정하는 것입니다.

 

some 키워드는 프로토콜 뿐만 아니라 클래스도 사용 가능합니다.

상위 클래스를 리턴할 때 some 키워드와 함께 사용할 수 있습니다.

class Animal {
    func feed() { }
}

class Cat: Animal { }
class Dog: Animal { }

func returnAnimalClass() -> some Animal {
    return Cat()
}

 

 

박스형 프로토콜 타입 Boxed Protocol Type

 

 

'Swift > 문법' 카테고리의 다른 글

ARC dive deep  (0) 2025.03.21
후행 클로저  (0) 2025.03.21
enum의 다른 사용방법  (0) 2025.03.14
Swift Protocols  (0) 2025.03.11
Swift 구조체와 클래스  (0) 2025.02.12

+ Recent posts