Swift에서는 Protocol이라는 일급객체가 있음!
Protocol이 뭐에요? 라고 물어보면 어떻게 대답할 수 있을까.. 상당히 모호함
공식문서에 따르면 프로토콜은 특정 유형이 구현해야 하는 요구사항의 정의임
특정 작업이나 기능에 맞는 메서드, 속성, 요구사항의 청사진이라고 함
예를 들어서 "동물"이라는 프로토콜이 있다고 해보면 동물은 닭도 있고 소도 있고.. 매우 다양함이 동물들은 "먹이를 먹는다"라는 공통의 동작이 있고, 이 동작을 일관되게 수행함
그렇기 때문에 프로토콜로 eat이라는 요구사항을 정의하고, 채택한 곳에서 해당 함수를 각각의 동물에 맞게 구현할 수 있음
protocol Animal {
func eat()
}
struct Cow: Animal {
func eat() {
print("소의 밥 먹기")
}
}
struct Chicken: Animal {
func eat() {
print("닭의 밥 먹기")
}
}
이제 이 프로토콜에서 동물은 단지 먹는거에서 벗어나서 음식을 생산한다고 가정해보겠음
produce함수에서 어떤 음식을 생산할거임. 근데 무슨 음식일지는 다 다를거임. 닭이면 달갈...이런식으로!
그렇기 때문에 프로토콜에서 제네릭을 쓸 수 있는 방법인 associatedtype으로 정의해주었음
protocol Animal {
associatedtype CommodityType: Food
func eat()
func produce() -> CommodityType
}
protocol Food { }
struct Egg: Food { }
struct Milk: Food { }
이제 Cow에서 produce 함수를 호출하려고 했더니 some Food라함. some? some이 뭘까요.
추측을 해보면 일단 produce()함수에서 반환값으로 Food프로토콜을 준수한 애가 들어와야한 거잖음
일단 some이 뭔지 알아야 할 것 같음
Some
Some은 불투명 타입(Opaque type)을 만들어냄
불투명 타입이 뭔데요? 어떤 프로토콜을 준수한다고 할 때 구체적인 값을 감추는 것임
struct Chicken: Animal {
func produce() -> Food {
return Egg()
}
func eat() {
print("닭의 밥 먹기")
}
}
만약에 이런 코드를 작성한다고 해보겠음. 음식을 생산하는거 맞잖음..? 근데 오류남
치킨은 애니멀 프로토콜을 준수하지 않는다고 함... 그니까 어떤 음식이 올지 모르니까 any Food상태가 되는거임
그래서 묻는게 너 opaque result Type쓸거야?
some Food로 어떤 음식을 리턴하는건 알수있지만, 안에서 뭐가 리턴하는지 모르게 하고 싶은거야? 묻는거임
struct ScrambleEgg: Food { }
struct Chicken: Animal {
func produce() -> some Food {
return ScrambleEgg()
}
}
//제네릭 구성
func feed<A: Animal>(animal: A) {
print(animal.name + " 먹이주기")
}
feed(animal: Cow())
feed(animal: Chicken())
//some으로 구성
//호출 하는 곳에서 어떤 값이 들어올지 명시
func feed(animal: some Animal) {
print(animal.name + " 먹이주기")
}
//여기서 구체적인 타입 명시
feed(animal: Cow())
feed(animal: Chicken())
만약에 달걀이 아니라 스크램블 에그를 만드는 닭...이라고 해보면 return 값만 수정할 수 있음.
이렇게 "구체"타입들을 일일이 명시하지 않고, 아무튼 Food 프로토콜 타입이야! 라고 하는것이 some임
SwiftUI에서 body안에서 여러 뷰를 구성하면 타입이 굉장히 길어지는데 body프로퍼티는 그냥 "some View"타입임
동일 맥락이라고 보면 될 것 같음.
SwiftUI에서 if컨디션에 따라서 뷰 타입이 달라지면 @ViewBuilder 어노테이션을 사용하는데 이건 왜일까?
불투명 결과 유형을 가진 메서드나 계산된 속성의 경우 "사용관점"에서 전역적임. 그렇기 때문에 일관된 동일값이 나와야 함.
때문에 ViewBuilder를 통해 분기에 대해 동일한 값을 갖게 해주기 위해 붙이는 것임
그래서 메소드 디스패치 관점에서 보면 동적 디스패치임 정적 디스패치임 ???
이전에는 프로토콜을 타입으로 지정하고 값을 할당할 때 값이 정해지지 않아서 타입이 런타임에 결정되었다고 함.
근데 some을 붙이면 이거 컴파일 시점에 구체적인 타입을 알 수 있기 때문에 정적 디스패치 됨
-> 성능상 이점을 가지게 됨
근데 얘네 array에 들어갈려면 똑같은 애만 들어가야 됨
그니까 [Cow(), Chicken()] 처럼 쓸 수 없음......그래서 등장한게 any임
Any
struct Farm {
var animals: [any Animal] = [Cow(), Chicken()]
func produceCommodities() -> [any Food] {
return animals.map { animal in
animal.produce()
}
}
}
let animalFarm = Farm()
let foods = animalFarm.produceCommodities()
농장에는 여러 동물이 있음. 이걸 배열로 표현하려면 any를 붙여야 함.
왜냐하면 해당 프로토콜을 준수하지만, 실존타입(닭, 소)은 다른 애들이 들어올 수 있어! 라고 알려주어야 함
이때 사용하는게 any임. any는 뭐든 다 들어올 수 있는 하나의 빈 박스라고 보면 됨.
그 동물들은 각자의 생산품을 내는데 이때도 실존타입이 다 달라짐(닭 -> 달걀, 소 -> 우유)
이처럼 any는 연관타입(associatedType)을 동등한 제약조건(Food)의 실존타입(Egg)으로 대응할 수 있음
타입소거: 다른 구체적인 유형에 대해 동일한 표현을 사용하는 전략
왜 사용하면 좋을까?
구체 타입은 구현을 해야 볼 수 있는데, 그 결과 타입을 추상화할 수 있어 모듈화나 변화에 강하게 만들 수 있다고 함.
근데 Any는 다이나믹 디스패치이기 때문에 필요할 때만 사용하는게 좋아보임
참고자료
'iOS > iOS 지식' 카테고리의 다른 글
Swift Concurrency(2) - Continutation (0) | 2024.11.11 |
---|---|
Swift Concurrency(1) - Async와 Await (2) | 2024.11.10 |
[Swift] Struct와 Class (1) - Class 안에 Class, Struct 안에 Class (0) | 2024.10.05 |
[iOS] ViewController 생명주기(2) - 실험 (2) | 2024.10.04 |
[iOS] GCD Priority Inversion (우선 순위의 뒤바뀜) (0) | 2024.08.03 |