[Swift] associatedtype 이야기 iOS Development/Swift2025. 3. 23. 08:22
1. Intro
: 현재는 iOS 개발 실무를 하고있지 않아서, 알고있던 지식들이 희미해지는 것을 느낍니다. 그냥 놓아버리기에는 아깝다고 생각하여, 공부삼아 정리한 내용들을 tistory에 정리해나가고자 합니다. 첫 시작은 associatedtype으로 정했습니다.
2. associatedtype syntax의 등장
: associatedtype syntax가 등장하기 전, typealias를 이용하여 Associated Type(protocol에서 사용하는 type placeholder)를 선언했었습니다.
protocol MyProtocol {
typealias Item
}
이렇게 작성된 Code를 처음 본다고 가정해봅시다. Item은 type 별칭일까요? 아니면, Associated Type일까요? 헷갈립니다. 이를 해결하기 위해, 누군가가 Associated Type을 선언하기 위한 syntax를 제안했었습니다(SE-0011, link).
protocol MyProtocol {
associatedtype Item
}
이 제안은 받아들여졌고, Swift 2.2부터 associatedtype을 사용할 수 있게 되었습니다.
3. associatedtype과 constraint
: 개발자들은 associatedtype syntax 추가에 만족하지 않고, 보다 복잡한 제약조건을 associatedtype에 추가할 수 있길 원했습니다. 그중 몇몇 개발자들이 associatedtype에 직접 where 조건을 추가하는 것을 제안(SE-0142, link)했었고, 이는 받아들여져서 Swift 4.0부터 아래와 같은 Code를 작성할 수 있게 되었습니다.
protocol Human {
associatedtype Age where Age: Equatable
func introduce() -> Age
}
struct Syj: Human {
func introduce() -> Int {
return 0
}
}
얼마지나지 않아, associatedtype과 관련된 기능이 하나 더 추가됩니다. 이전까지 associatedtype은 자신이 속한 protocol을 제약조건으로 사용할 수 없었습니다. 이것에 대해 이야기하는 제안서(SE-0157, link)를 보면, 아래와 같은 Code 작성이 불가능했었습니다.
// SE-0157이 받아들여지기 전에는 Compile되지 않는 Code였음.
protocol Sequence {
associatedtype SubSequence: Sequence
where Iterator.Element == SubSequence.Iterator.Element, SubSequence.SubSequence == SubSequence
func dropFirst(_ n: Int) -> Self.SubSequence
그래서 어쩔 수 없이 아래에 작성한 Code처럼, 좀 장황하게 Code를 작성했어야 했었습니다.
protocol Sequence {
associatedtype SubSequence
func dropFirst(_ n: Int) -> Self.SubSequence
}
struct SimpleSubSequence<Element> : Sequence {
typealias SubSequence = SimpleSubSequence<Element>
typealias Iterator.Element = Element
}
struct SequenceOfInts : Sequence {
func dropFirst(_ n: Int) -> SimpleSubSequence<Int> {
// ...
}
}
SE-0157은 채택되었고 Swift 4.1부터 associatedtype은 자신이 속한 protocol을 제약조건으로 사용할 수 있게 되었습니다.
4. Opaque Result Type
: 2019년, 어떻게하면 Swift Generic의 사용성을 더 개선할 수 있을지를 이야기해보는 글이 Swift Forum에 올라왔었습니다(link - Improving the UI of generics). Forum에 내용중 하나를 제안했었는데(SE-0244, link), return에 대한 Type-Level Abstraction 문제를 이야기하고 해결책을 제시했었습니다.
제안서에서 말하는 문제가 무엇일까요? 아래의 Code를 살펴봅시다.
protocol Shape {
func draw(to: Surface)
}
struct Rectangle: Shape { /* ... */ }
struct Union<A: Shape, B: Shape>: Shape { /* ... */ }
struct Transformed<S: Shape>: Shape { /* ... */ }
protocol GameObject {
associatedtype Shape: Shapes.Shape
var shape: Shape { get }
}
위의 Code에서, GameObject protocol을 따르게 하려면 아래와 같이 장황하게 Return type을 명시해야 했었습니다.
struct Star: GameObject {
var shape: Union<Rectangle, Transformed<Rectangle>> {
return Union(Rectangle(), Transformed(Rectangle()))
}
}
shape의 type이 장황하죠? 정확한 Return type을 명시하는 것은 별로 중요하지 않고, Shape protocol을 따른다는 것이 중요합니다. 따라서 지금의 저 장황한 Return type은 Code를 읽는 사람에게도 별로 도움이 되지 않습니다. 그래서 SE-0244에서는 다음과 같은 syntax를 제안합니다.
struct Star: GameObject {
var shape: some Shape {
return Union(Rectangle(), Transformed(Rectangle()))
}
}
SE-0244는 채택되었고, Swift 5.1부터 some Protocol
syntax를 사용할 수 있게 되었습니다.
5. Primary Associated Types
: Protocol은 associatedtype을 가질 수 있지만 constraint를 설정하려면 where을 이용해야 했었습니다.
func process<S: Sequence>(sequence: S) where S.Element = String {
// ...
}
func concatenate<S : Sequence>(_ lhs: S, _ rhs: S) -> S where S.Element == String {
// ...
}
위의 Code는 단순하지만, where이 길어진다면 가독성이 떨어지게 되겠죠. 개선의 필요성과 해결책을 이야기한 제안서(SE-0346, link)에서는 primary associatedtype을 Protocol 선언 시 명시하고, generic type처럼 사용할 수 있는 Syntax를 제안했습니다.
// 기존 Syntax
protocol Container {
associatedtype Item
func append(_ item: Item)
}
struct StringContainer: Container {
typealias Item = String
func append(_ item: String) { }
}
func process<T: Container>(container: T) where T.Item == String { }
// SE-0346에서 제안하는 Syntax
protocol Container<Item> {
func append(_ item: Item)
}
struct StringContainer: Container<String> {
func append(_ item: String) { }
}
func process(container: some Container<String>) { }
확실히, SE-0346에서 제안한 Syntax가 Code 가독성을 개선시켜주는 것 같네요. SE-0346은 채택되었고 Swift 5.7부터 primary associatedtype을 정의할 수 있게 되었습니다.
이후, SE-0358(link)에서는 Swift Standard Library의 여러 Protocol에 primary associatedtype을 정의하자고 제안하였습니다. SE-0358도 Swift 5.7에 채택되었습니다.
99. Reference
1) Swift Evolution - Proposals
- SE-0011 : replace typealias associated (link)
- SE-0142 : associated types constraints (link)
- SE-0157 : recursive protocol constraints (link)
- SE-0244 : opaque result types (link)
- SE-0346 : light weight same type syntax (link)
- SE-0358 : primary associated types in stdlib (link)
2) Swift Documents
3) Others
'iOS Development > Swift' 카테고리의 다른 글
inout parameter는 reference를 전달하는 것이 아니다. (4) | 2018.11.24 |
---|