본문 바로가기
FRONT-END/iOS

Swift 심화 학습하기 (프로토콜, Never 타입, #keyPath와 #selector, 메타타입, availability)

by 랄라J 2023. 8. 30.

프로토콜

Equatable 프로토콜

동일성 비교를 위한 프로토콜이다. 스위프트에서 제공하는 기본타입은 모두 채택하고 있다. 아래 메서드 구현이 해당 프로토콜의 요구사항이다.

static func == (lhs: Self, rhs: Self) -> Bool

 

구조체, 열거형의 경우 Equatable 프로토콜 채택 시 모든 저장 속성이 Equatable 프로토콜을 채택한 타입이라면 비교연산자 메서드가 자동 구현된다. 단, 예외 케이스가 몇 가지 존재한다.

1) 클래스는 인스턴스를 비교하는 항등연산자(===)가 존재하기 때문에 비교연산자(==)는 개발자에게 구현이 위임된다.

2) 열거형의 경우 연관값이 없다면 기본적으로 Equatable, Hashable하기 때문에  Equatable 프로토콜을 채택하지 않아도 된다.

 

아래 살펴 볼 Comparable 프로토콜과 Hashable 프로토콜은 Equatable 프로토콜을 상속받아 구현되어 있다.

Comparable 프로토콜

크기 비교를 하기 위한 프로토콜이다. 스위프트에서 제공하는 기본 숫자타입, String 타입은 모두 채택하고 있다. 아래 메서드 구현이 해당 프로토콜의 요구사항이다.

static func < (lhs: Self, rhs: Self) -> Bool

Comparable 프로토콜을 채택 시 열거형, 구조체, 클래스의 모든 저장 속성이 Comparable 프로토콜을 채택한 타입이어도 메서드를 직접 구현해야 한다.  클래스의 경우 Comparable 프로토콜을 채택한다면 ==과 < 메서드 구현을 해줘야 한다.

일반적으로 < 만 구현하면 <=, >=, >는 스위프트 내부 컴파일러가 자동으로 알아서 구현해 준다. 

 

Hashable 프로토콜

유일한 값을 갖도록 해서 Dictionary의 키, Set의 요소가 될 수 있게 하는 프로토콜이다. 아래 메서드 구현이 해당 프로토콜의 요구사항이다. 

func hash(into hashier: inout hashier)

구조체, 열거형의 경우 Hashable 프로토콜 채택 시 모든 저장 속성이 Hashable 프로토콜을 채택한 타입이면 hash 메서드가 자동 구현된다. 단, 클래스는 인스턴스의 유일성을 갖게 하기 위해서는 hash 메서드를 직접 구현해야 한다. 열거형의 경우 연관값이 없다면 기본적으로 Equatable, Hashable 하기 때문에 Hashable 프로토콜을 채택할 필요가 없다. 

 

CaseIterable 프로토콜

Enum 타입에서 사용할 수 있는 프로토콜로 case들이 반복가능하게 만드는 프로토콜이다. 열거형에서 해당 프로토콜을 채택하면 타입 계산속성이 자동으로 구현된다. 모든 케이스들을 포함해 정의한 순서대로 배열을 리턴한다. 연관값이 없는 경우에만 채택할 수 있다.

static var allCases: Self.AllCases { get }

해당 프로토콜을 채택하면 배열의 장점을 활용할 수 있다. 반복문에서 사용하거나, .count를 활용해 케이스의 개수를 세기가 편해지고, 배열을 활용한 고차함수를 사용할 수 있고, .randomElement()를 활용할 수 있는  장점이 있다.

 

Never 타입과 검증함수

Never 타입을 사용하는 이유는 런타임에 발생할 수 있는 에러를 미리 발견하고 해결하기 위함이다. Never 타입은 일부러 crash를 발생시키겠다는 의미이다. 원래의 함수 실행 위치로 제어권을 전달하지 않아 앱을 종료시키겠다는 의미이다. 이러한 Never 타입을 리턴하는 함수를 Nonreturning 함수라고 한다. 이는 제어권을 전달하지 않는 함수로 이해하면 된다. Never 타입은 빈 열거형으로 내부가 선언되어있고 인스턴스를 생성할 수 없다.

이러한 Never 타입을 사용하는 경우는 함수 내부에서 프로그램을 종료시켜야 할 때 fatalError("")를 return 하도록 하면 된다. 항상 error를 던지기 때문에 catch문에서 처리해야 한다. 일부러 앱을 종료시키기 위한 함수로는 총 5가지가 있다.  assert(), assertionFailure(), precondition(), preconditionFailure(), fatalError()이다. fatalError()는 디버그 모드 및 출시 모드 상관없이 동작한다. assert()는 디버그 모드에서만 동작하고, precondition()는 출시 모드에서 작동한다.

 

#keyPath와 #selector 개념

두 개념 모두 Objective-C에서부터 사용된 개념이다. 예전 코드를 보고 이해할 수 있는 정도로 이해하면 된다.

#keyPath는 속성에 접근하기 위한 기술이다. 미리 경로를 만들어 놓을 수 있다.

// Swift 5 방식 \를 사용
let path = \School.affiliate.classMember
// 경로 추가도 가능
let namePath = path.appending(path: \.name)
school1[keyPath: namePath]

// 이전 방식 #keyPath를 사용
// 예전에는 구조체는 지원하지 않음
// value(forKey:) 메서드가 구현되어있는 NSObject를 상속해야함
// 속성에도 @objc를 붙여야 함
let path = Person.name
let name = person.value(forKeyPath: path) as? String

 

#selector는 메서드 주소를 통해 메서드를 가리키기 위한 기술이다. 어떤 이름을 가진 속성/메서드를 가리킬 것인지 전달하는 연결고리 역할이라고 이해하면된다.

let runSelector = #selector(Dog.run)

우리가 실제로 스토리보드가 아닌 코드로 버튼을 눌렀을 때 실행할 함수를 연결하는 것을 구현할 때 아래와 같은 코드를 사용해 작성한다. 이때 #selector로 작성하는 부분이 바로 함수를 가리키는 메모리 주소를 전달하는 역할을 하는 것이다.

button.addTarget(self, action: #selector(ViewController.doSomething), for: .touchUpInside)

@objc func doSomething() {
    //
}

 

메타 타입

타입의 타입이다. 데이터 영역에 실제 메모리를 차지하고 가지고 있는 것을 타입 인스턴스라고 하는데, 이 타입 인스턴스의 타입을 메타 타입이라고 한다. 클래스, 구조체, 열거형의 메타타입은 이름.Type인데 프로토콜의 메타타입은 이름.Protocol 이라는 점이 조금 다르다.

class Dog {
    static let name = "멍멍이"
    var legs = 4
}

// Dog.self <- 타입 인스턴스
// Dog.self의 타입 <- 메타 타입

 테이블뷰 셀을 등록하는 경우와 JSONDecoder 객체를 사용해서 디코드 메서드 사용시에 사용된다.

 

availability

@available(iOS: 10.0)
class Dog { ... }

if #available(iOS: 11, *) {
    ...
}

@available 키워드는 타입, 속성, 메서드 앞에 사용되며 컴파일러가 API의 사용 가능성을 결정하는 용도이다.

#available 키워드는 조건문(if, guard, while)에 사용되며 런타임에 API의 사용가능성을 결정하는 용도이다.

반응형

댓글