일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- @state
- 비동기
- Apple Developer Academy
- Swift
- SwiftUI
- 앨런
- Linked List
- 운영체제
- UserDefaults
- deadlock
- struct
- 오브젝트
- forEach
- scrollview
- 동기화
- 상호배제
- 가상 메모리
- core data
- decode
- Codable
- Algorithm
- 데드락
- 동시성
- 인프런
- 프로세스 스케줄링
- COLOR
- IOS
- 100 days of SwiftUI
- 알고리즘
- async
- Today
- Total
기어가더라도 제대로
[Swift - 기초] Protocol, Opaque return type(some) 본문
- Protocol 의 생성과 사용
- Opaque(불투명) 반환 타입의 사용
프로토콜이란?
- 데이터 타입에게 기대하는 기능을 정의
- Swift 식으로 하는 계약
- 앱의 나머지 부분에서 이 프로토콜을 따라야함
- 실제 구현은 고려하지 않고 "이 타입은 이 기능을 따를거야~" 라고 명시
예시
- 출퇴근을 하는 사람을 시뮬레이션 하는 코드가 있다고 가정
- 이 사람은 다양한 교통 수단을 이용해서 "통근" 이라는 기능을 수행해야함
- 기차, 차, 오토바이, 공유 킥보드, 비행기 등 무슨 교통 수단을 타더라도 다음의 두 기능은 수행해야함
- 어떤 수단을 타는지보다 "통근"을 "얼마의 시간" 동안 했다는 것이 중요
- 새로운 프로토콜 타입이므로 가장 앞 대문자를 쓰는 캐멀케이스 사용
- 이 프로토콜이 수행해야하는 행동(메서드)을 리스트업
- 구체적인 "코드"는 적지 않는다.
- 함수의 바디 부분을 나타내는 스코프{ } 가 없음
- 메서드 이름, 파라미터, 반환값을 명시할 뿐
- implement type(구체 타입) 의 설계도를 작성 -> 프로토콜을 "추상타입" 이라고도 함
- 프로토콜의 요구사항을 충족할 실제 구현 타입을 만들어야함
- "프로토콜을 만족한다, 충족한다(adopting, conforming)" 고 표현
- 프로토콜이 구체 타입이 수행할 모든 기능과 속성을 명시할 필욘 없음
- 필요 최소한으로만 구현해 놓고 구체 타입이 선택적으로 추가 가능
주의 사항
- 서브 클래스 명시하는 것 처럼 프로토콜을 명시
- 프로토콜(Vehicle)에서 선언한 메서드는 구체타입(Car) 에서 반드시 선언되어야함
- 이름, 반환타입, 파라메터 까지 동일해야함
- Car 의 메서드들은 우리가 프로토콜에서 선언한 메서드들의 실제 구현 사항을 제공
- "func openSunroof()" 는 프로토콜에 없지만 Car 에서 자의적으로 추가된 사항
- 프로토콜은 아주 최소한의 요구사항만을 적어놓는다. 필요하면 구체타입에서 더 구현해도 됨
그래서 우리가 뭘 하려 했더라? 아 맞다 출근!
우리는 다양한 교통수단을 이용해 "출근"을 해야한다.
Car 를 이용해 성공적으로 출근할 수 있었는데, 다른 기차나, 전동 킥보드를 타고선 출근하기가 어려워 보인다.
여기서 프로토콜이 도움을 준다.
스위프트는 어떤 Vehicle 이든지 estimateTime(), travel() 이 두 함수를 만족한다는 걸 안다. 교통수단을 Car 타입이 아닌 Vehicle 을 이용해서 다녀보자
이제 우리는 Vehicle 프로토콜을 만족하는 어떠한 타입이여도 "통근"에 이용할 수 있게 되었다.
자전거를 이용해서 출퇴근이 가능해졌다.
Vehicle 프로토콜을 만족한다면 Car 든 Bicycle 이든 모두 Vehicle 이다. 그래서 commute() 함수에 사용 가능하다.
프로토콜을 사용하면 commute 함수의 vehicle 자리에 구체적인 타입을 밝혀 놓지 않고 Vehicle 을 만족하는 어떤 타입도 받을 수 있다.
변수도 프로토콜에 선언
- 메서드와 프로퍼티를 프로토콜에 선언 가능
- var 키워드로 선언 가능
- 프로퍼티의 경우 읽기 전용인지, 읽고 쓸 수 있는지 명시
- { get }: 읽기 전용
- { get set }: 읽고 쓰기 가능
- 기본 값을 프로토콜에 넣을 수 없기 때문에 프로퍼티에선 타입 추론이 필요
- 프로토콜에 변경 사항이 생겼으니 기존에 채택하는 Car, Bicycle 은 오류를 냄
- name 을 보면 프로토콜에선 var { get } 으로 선언
- 실제 구현은 let 으로 선언
- 실제 구현에서 상수로 선언하면 읽기는 가능하지만 쓰기가 불가능함
- { get } 만족
- currentPassengers 를 보면 프로토콜에선 var { get set } 으로 선언
- 실제 구현은 var 로 선언
- 실제 구현에서 변수로 선언하면 읽기, 쓰기 가능
- { get set } 모두 만족
func getTravelEstimates(using vehicles: [Vehicle], distance: Int) {
for vehicle in vehicles {
let estimate = vehicle.estimateTime(for: distance)
print("\(vehicle.name): \(estimate) hours to travel \(distance)km")
}
}
vehicles 파라메터를 보면 Vehicle 배열을 받는다.
이는 [car, bike] 이런 값도 들어갈 수 있다는 것이다.
더불어 프로토콜은 구체 타입이 무한으로 채택할 수 있다. 클래스 상속은 한 부모만 가질 수 있는것과 대조적이다.
순서는 "SomeType: 부모클래스, 프로토콜1, 프로토콜2 ..." 순서이다.
[심화] 불투명 반환 타입 - Opaque return type
- 서로 타입이 다른 것만 빼면 비슷한 일을 하는 두 함수
- Int 와 Bool 의 공통점 "Equatable" 프로토콜을 만족한다.
- Equatable: 동일한 데이터인지 비교 가능하다
- "==" 메서드 사용 가능
- struct 는 class 와 다르게 그 타입이 가지고 있는 data의 값으로 스스로를 식별!
- class 는 인스턴스 고유의 정체성이 있음
- struct는 자기가 가지고 있는 속성의 값 그 자체가 정체성임
- 에러 발생 코드
- Equatable 을 반환하는 것이 에러
- 이 코드가 왜 실행이 안되는지를 이해하는 것이 Opaque return type을 이해하는데 키 포인트!
- 무엇보다도.. 함수에서 프로토콜을 리턴 가능
- 근데 여기선 왜 안될까?
예를들어 수화물과 승객 수를 파라메터로 받아서 적합한 차량을 렌트해주는 함수가 있다고 생각해보자.
결국 승합차, SUV, 미니밴 중에서 반환 타입이 나올 것이고, 이 타입들은 Vehicle 프로토콜을 만족하니까
반환 타입에 따라 함수를 따로 선언할 필요 없이 Vehicle 만 내보내면 된다.
어차피 Vehicle 은 선언된 프로퍼티와 메서드를 수행할 수 있을 테니까 별 문제가 없어 보인다.
- 근데 진짜 문제가 없을까? 코딩적인 관점에서는 문제가 될 수 있다.
- 받아보는 Vehicle 이 미니밴인지, SUV인지, 승합차인지 알 수 가 없다.
- 서로 대치가 가능하기 때문
다시 돌아와서 Int 가 반환되는지, Bool 이 반환 되는지는 위의 함수에서 중요한 쟁점이다.
뭉뚱 그려서 Equatable 타입이라고 하면 구체적인 타입을 알 수 없다.
두 타입 모두 Equatable 을 만족하기는 하지만, 서로 대치 될 수 없다. Int 와 Bool 을 ==(비교) 할 수는 없지 않은가?
정보를 숨겨주는 프로토콜
- 함수에서 리턴값으로 프로토콜을 보내는 것은 유용하다
- 정확한 타입을 명시하는 것보다는 반환되는 타입의 기능성에 집중하기 때문
- 위 예시의 Vehicle 프로토콜은
- 좌석수, 예상 연료 사용량, 가격
- 등의 기능을 가지겠지만, 미니밴인지 승합차인지 뭔지 알 수 없다.
- 만약 RaceCar, PickUpTruck 이 Vehicle 에 추가되더라도 문제 없이 수행할 수 있다.
- 8명의 승객을 받아도 코딩적으론 레이싱카를 반환할 수 있게 된다.
- 정보를 숨기는 건 유용하다. 다만 조금 더 구체적일 필요가 있다.
- Equatable 을 만족하는 두 타입을 비교하는 것이 불가능 하기 때문에, Equatable 은 사용할 수 없다.
- 만약에(되지도 않겠지만) Equatable 을 반환하는 getRandomNumber() 를 두번 수행해서 정수 두개를 얻어도 서로를 비교할 수 없다.
- 실제로는 정수라서 비교할 수 있지만 프로토콜을 반환함으로 써 정보를 숨겼기 때문에 비교할 수 없다.
- Equatable 인지만 Swift 가 알지 실제로 예가 Int 일지, Bool일지 아니면 또다른 타입일지 어떻게 안단 말인가?
Opaque return type 이 생긴 이유 - Some
- 코드에서는 정보를 숨겨주지만 Swift 컴파일러에게는 정보를 숨기지 않는다.
- 이는 우리가 내부적으로는 유연한 코드를 만들 수 있게 한다.
- 미래에 다른 타입(데이터) 를 반환해도
- Swift 가 알아서 반환되는 실제 데이터 타입을 이해하고 적절히 확인
- getRandomNumber() 를 두 번 호출하고 그 결과로 == 를 이용해 비교 가능
- some 키워드
- 여전히 Equatable 을 반환하지만 some 으로 인해 Swift 는 저 타입이 실제로는 Int 라고 확인 가능
- Opaque return type 을 반환하는 것
- 실질적인 타입이 아니라 기능에 집중하면서도
- 추후에 변경이 있을 때 코드를 수정하지 않고 대응 가능
- getRandomNumber() 함수의 바디가 Double.random(in: 1...6) 이여도 가능
- Swift 가 그 속에 있는 실제 타입을 인식할 수 있다는 것
- Vehicle
- 뭔진 모르겠지만 Vehicle 만 만족하는 어떤 타입
- some Vehicle
- 특정한 하나를 지정하진 않겠지만, 특정 종류의 Vehicle 타입
SwiftUI 에서 Opaque return type 이 중요한 이유
- SwiftUI 에선 스크린에 레이아웃을 보여주기 위해 특정 타입을 알아야함
- 레이아웃을 그린다고 해보자.
- "스크린에 맨 위에 툴바 있고요
- 맨 밑엔 탭바 있고요
- 중간엔 컬러 아이콘의 그리드 한 스크롤 구현되어있고요
- 아이콘 밑에 이를 설명하는 레이블이 있는데요
- 이 레이블은 굵은 폰트로 되어있고요
- 아이콘을 누르면 이 메시지가 나타나요"
스위프트 UI에선 이 모든 설명이 레이아웃의 리턴 타입이다.
구체적으로 하나하나 다 밝힌 저게 하나의 리턴 타입이라고 명시해야한다.
저걸 리턴 타입이라 할 수 있을까?
아니면 some View 하나 놓고 말까..
레이아웃의 변경점이 생길때마다 새로운 타입을 선언해야하나?
아니면 some View 하나 놓고 말까..
여기서 Opaque return type 이 우리를 구원하러 오셨다.
some View 라고만 하면 구체적인 타입을 주저리 주저리 늘어 놓지 않아도
Swift 컴파일러는 실제 늘어놓은 타입을 알 수 있지만 개발자는 some View 라고만 하면 끝이다.
Swift 는 항상 반환된 실제 데이터의 타입을 알고 SwiftUI 는 레이아웃을 만드는데 사용할 것이다.
'Swift - 기초' 카테고리의 다른 글
[Swift - 기초] Optional - 100 days of swiftUI (0) | 2022.10.04 |
---|---|
[Swift-기초] Extension, protocol Extension - 100 days of SwiftUI (0) | 2022.10.02 |
[Swift-기초] class - 100 days of swiftUI (2) | 2022.09.30 |
[Swift - 기초] 100 Days of SwiftUI - Day 11, 접근 제어자, Static 키워드 (0) | 2022.09.29 |
[10일차] struct (0) | 2022.09.28 |