์๋ ํ์ธ์ Foma ์ ๋๋ค!
์ค๋์ ์ ๋ฒ ๊ธ์ธ Embrace Swift Generics์์ ๊ณ์ ์ธ๊ธํ๋ Design protocol interface in Swift ์ธ์ ์ ๋ํด์ ๋ค๋ค๋ณด๋ ค๊ณ ํฉ๋๋ค!
(์ ๋ฒ ๊ธ๊ณผ ๋ง์ด ์ฐ๊ด๋์ด ์์ผ๋ ์๋ณด์ ๋ถ๋ค์ ๊ผญ ์ฌ๊ธฐ ์์ ๋ณด๊ณ ์์ฃผ์ธ์!)
๋ฐ๋ก ์์ํ ๊ฒ์~
Understand type erasure
๊ฐ์ฅ ๋จผ์ ํ์ ์ด๋ ์ด์ ธ์ ๋ํด์ ์์๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค. (erasure๋ฅผ ์ ํํ ์ด๋ป๊ฒ ํด์ํด์ผ ํ ์ง ๋ชจ๋ฅด๊ฒ ๋ค์.. ์ญ์ ? ์ง์?)
๊ฐ์ฅ ๋จผ์ ๋๋ฌผ ํ๋กํ ์ฝ์ ์ดํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
๋๋ฌผ ํ๋กํ ์ฝ์ associatedtype์ผ๋ก ๋จน์ดํ์ ๊ณผ ์์ฐํํ์ ์ด ์๊ณ , ๋ฉ์๋๋ก ๋จน์ดํ์ ์ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ๋ eat, ์์ฐํ ํ์ ์ ๋ฐํํ๋ produce ๋ฉ์๋๊ฐ ์์ต๋๋ค.
protocol Animal {
associatedtype FeedType: AnimalFeed
associatedtype ProductType: Product
func eat(_ food: FeedType)
func produce() -> ProductType
}
๊ฐ ๊ตฌ์ฒด์ ์ธ ๋๋ฌผ๋ค์ ๋๋ฌผ ํ๋กํ ์ฝ์ ๋ฐ๋ฅด๋ฉฐ ๊ตฌ์ฒด์ ์ธ ๋จน์ด์ ์์ฐํ ํ์ ์ ๋ช ์ํ์ฃ .
struct Cow: Animal {
func eat(_ food: Hay) {}
func produce() -> Milk {
return Milk()
}
}
struct Chicken: Animal {
func eat(_ food: Grain) {}
func produce() -> Egg {
return Egg()
}
}
struct Horse: Animal {
func eat(_ food: Carrot) {}
func produce() -> Vehicle {
return Vehicle()
}
}
๋๋ฌผ๋ค์๊ฒ ๋จน์ด์ ์์ฐํ์ ๊ธธ๋ฌ๋ด๋ ๋์ฅ ์คํธ๋ญํธ๊ฐ ์์ต๋๋ค.
์ฌ๊ธฐ์๋ any Animal, any Product์ ๊ฐ์ด ์์ any ํค์๋์ ํจ๊ป ํ๋กํ ์ฝ์ด ๋ช ์๋ ๋ฐฐ์ด๋ค์ด ์์ต๋๋ค.
์ด๊ฒ๋ค์ "ํด๋น ํ๋กํ ์ฝ์ ๋ฐ๋ฅด๋ ์ด๋ค ๊ตฌ์ฒด์ ์ธ ํ์ ๋ค์ด ์ฑ์์ง๊ฑฐ์ผ" ๋ผ๊ณ ๋ช ์๋ง ํ ๋ฟ ๊ตฌ์ฒด์ ์ธ ํ์ ๋ค์ด ์ง์์ ธ ์์ต๋๋ค.
([any Animal] == [Cow, Horse, Chicken], [any Product] == [Milk, Vehicle, Egg]์ ๊ฐ์ ๊ฒ์ด์ฃ .)
๋ฐ๋ก ์ด๋ ๊ฒ ํ์ ์ ๋ช ์ํ์ง ์๊ณ ์ง์์ ธ ์๋ ์ํ๋ฅผ "ํ์ ์ด๋ ์ด์ ธ" ๋ผ๊ณ ๋ถ๋ฅด๋ ๊ฒ์ด์ฃ .
struct Farm {
var animals:[any Animal]
func feed(_ animal: some Animal) {
let crop = type(of: animal).FeedType.grow()
let produce = crop.harvest()
animal.eat(produce)
}
func feedAll(_ animals: [any Animal]) {
for animal in animals {
feed(animal)
}
}
func produce() -> [any Product] {
return animals.map { animal in
animal.produce()
}
}
}
Hide implementation details
๊ทธ ๋ค์์ผ๋ก ์์๋ณผ ๊ฒ์ ๊ตฌ์ฒด์ ์ธ ํ์ ์ ์จ๊ธฐ๋ ๊ฒ ์ ๋๋ค.
์ด๊ฒ์ ์ ์ ํ์ ํ ๋น์ ๋ ๋ชจ๋ํํ๊ณ ๊ฒฌ๊ณ ํ๊ฒ ๋ง๋ค ์ ์๋๋ฐ์.
์๋ฅผ ๋ค๋ฉด ์๋์ ๊ฐ์ด ๋๋ฌผ ํ๋กํ ์ฝ์ isHungry ํ๋กํผํฐ๊ฐ ์๋ค๊ณ ๊ฐ์ ํ๊ฒ ์ต๋๋ค.
protocol Animal {
var isHungry:Bool { get }
...
}
๋์ฅ์์ ์ด ๋ฐฐ๊ณ ํ ๋๋ฌผ๋ค์๊ฒ๋ง ๋จน์ด๋ฅผ ์ฃผ๋ ค๊ณ ํ๋๋ฐ์.
์ด๊ฒ์ ์ํด isHungry๊ฐ true๋ก ๋ ๋๋ฌผ๋ค์ filter ๋ฉ์๋๋ก ๊ฑธ๋ฌ ๋จน์ด๋ฅผ ์ฃผ๋๋ก ๊ตฌํํ๊ฒ ์ต๋๋ค.
struct Farm {
var hungryAnimals: [any Animal] {
animals.filter(\.isHungry)
}
func feedToHungryAnimals() {
for animal in hungryAnimals {
...
}
}
...
์์์ filter ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ hungryAnimals๋ฅผ ๋ง๋๋ ๊ณผ์ ์์ ๋ฌธ์ ์ ์ด ๋ฐ์ํ๋๋ฐ์.
filter๋ฅผ ์ฌ์ฉํ์ฌ ๊ฑธ๋ฌ๋ด๋ hungryAnimals๋ ํญ์ ์์์ ์ผ๋ก ์๊ฒจ๋๊ณ ์ฌ๋ผ์ง๊ฒ ๋ฉ๋๋ค.
์ด๊ฒ์ ๋ง์ฝ ์์ฒญ๋๊ฒ ๋ง์ ๋ฐฐ๊ณ ํ ๋๋ฌผ๋ค์ด ์กด์ฌํ๋ค๋ฉด ๊ณ์ํด์ ๋ง์ ์์ ์ฐ์ฐํด์ผ ํ๊ธฐ ๋๋ฌธ์ ๊ต์ฅํ ๋นํจ์จ์ ์ธ ์ฝ๋๊ฐ ๋ ๊ฒ ์ ๋๋ค.
์ด ๋ฌธ์ ๋ฅผ ๊ฐ์ ํ๊ธฐ ์ํด์ ์๋์ ๊ฐ์ด Lazy๋ฅผ ์ฌ์ฉํ ์ ์๋๋ฐ์.
lazy์ ๋ํด ๊ฐ๋จํ๊ฒ ์ค๋ช ๋๋ฆฌ๋ฉด ํญ์ ์ฐ์ฐ์ ์คํํ๋ ๊ฒ์ด ์๋ ๊ฒ์ผ๋ฅธ ์ํ๋ก ์๋ค๊ฐ feedToHungryAnimals๊ฐ ์คํ๋ ๋๋ง ์ฐ์ฐ์ ํ๊ฒ ๋์ด ์์์ ์ผ๋ก ํ ๋น๋๋ ๊ฒ์ ํผํด์ค๋๋ค.
(lazy์ ๋ํ ์์ธํ ์ค๋ช ์ ์ฌ๊ธฐ ๊ฐ์ ๋๋์ ์ค๋ช ์ ๋ณด๋ฉด ์ดํดํ๊ธฐ ์ฌ์ฐ์ค ๊ฑฐ์์!)
struct Farm {
var hungryAnimals: LazyFilterSequence<[any Animal]> {
animals.lazy.filter(\.isHungry)
}
...
LazyFilterSequence๋ Collection ํ๋กํ ์ฝ์ ๋ฐ๋ฅด๊ณ ์๊ธฐ ๋๋ฌธ์
์ ๋ฒ ๊ธ์์ ๋ฐฐ์ด some์ ํ์ฉํ๋ฉด ๋ถํฌ๋ช ํ ๊ฒฐ๊ณผ ํ์ ์ ์ฌ์ฉํ์ฌ ๋ณต์กํ ๊ตฌ์ฒด์ ์ธ ํ์ (LazyFilterSequence)์ ์จ๊ธธ ์ ์์ต๋๋ค.
์ด๋ ๊ฒ ์ฝ๋๋ฅผ ์์ฑํ๋ฉด ์ฌ์ฉํ๋ ํด๋ผ์ด์ธํธ๋ Collection ํ๋กํ ์ฝ์ ๋ฐ๋ฅด๋ฉฐ ๊ทธ ์์ Animal ํ๋กํ ์ฝ์ ๋ฐ๋ฅด๋ ๋ฌด์์ธ๊ฐ๋ฅผ ์ป๊ณ ์๋ค๋ ๊ฒ๋ง ์ ๋ฟ, ๊ตฌ์ฒด์ ์ธ ํ์ ์ ๋ํด ์ ์๊ฐ ์์ต๋๋ค.
struct Farm {
var hungryAnimals: some Collection<any Animal> {
animals.lazy.filter(\.isHungry)
}
...
๋ง์ฝ hungryAnimals๋ฅผ ์ด๋ค ๊ฒฝ์ฐ์ LazyFilterSequence๋ก ์ด๋ค ๊ฒฝ์ฐ์ Array๋ก ๋ฐํํ๊ฒ ํ๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํ ๊น์?
์๋์ ๊ฐ์ด ์์ฑํ ์ ์์ ๊ฒ ์ ๋๋ค.
var hungryAnimals: some Collection<any Animal> {
if isLazy {
return animals.lazy.filter(\.isHungry)
}else {
return animals.filter(\.isHungry)
}
}
ํ์ง๋ง ์ด๋ ๊ฒ ์์ฑํ๋ฉด ๋ถํฌ๋ช ํ๊ฒ ๋ฐํํ๊ธฐ ๋๋ฌธ์ ๊ธฐ๋ณธ ์ ํ์ ์ถ๋ก ํ ์๊ฐ ์๋ค๋ ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.
๊ณ ๋ก ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์ some ๋์ any ํค์๋๋ฅผ ์ฌ์ฉํ์ฌ Collection ํ๋กํ ์ฝ์ ๋ฐ๋ฅด๋ ํ์ ์ ๋ชจ๋ ๋ฐํํ ์ ์๋๋ก ๋ง๋ค์ด ์ค๋๋ค.
์ด๋ ๊ฒ ํ๋ฉด ๊ตฌ์ฒด์ ์ธ ๊ฒฐ๊ณผ ํ์ ์ ์จ๊ธฐ๋ฉด์ ํด๋น ํ๋กํ ์ฝ์ ๋ฐ๋ฅด๋ ๋ชจ๋ ํ์ ์ ๋ฐํํ ์ ์๋ ๊ฒ์ด์ฃ !
var hungryAnimals: any Collection<any Animal> {
if isLazy {
return animals.lazy.filter(\.isHungry)
}else {
return animals.filter(\.isHungry)
}
}
Identify type relationships
์ด์ ๋ถํฐ๋ ์ฌ๋ฌ ์ถ์ํ๋ ํ๋กํ ์ฝ์ ๊ด๊ณ๋ฅผ ์๋ณํ๊ณ ๋ณด์ฅํ๋ ๋ฒ์ ๋ํด์ ๋ค๋ค ๋ณด๊ฒ ์ต๋๋ค.
๋๋ฌผ์๊ฒ ์ฃผ๋ ๋จน์ด๋ฅผ ๊ธฐ๋ฅด๊ธฐ ์ํด์ ์๋์ ๊ฐ์ด 3๋จ๊ณ๊ฐ ํ์ํฉ๋๋ค.
1. ๋๋ฌผ์ ๋จน์ด ํ์ ์ ๊ธฐ๋ฅด๋ฉด ๊ณก๋ฌผ์ด ๋๊ณ
2. ๊ณก๋ฌผ์ ์ํํ๋ฉด ๋จน์ด๊ฐ ๋๊ณ
3. ๋๋ฌผ๋ค์๊ฒ ๊ทธ ๋จน์ด๋ฅผ ์ฃผ๊ฒ ๋๋ ๊ฒ ์ ๋๋ค.
func feed(_ animal: some Animal) {
let crop = type(of: animal).FeedType.grow()
let food = crop.harvest()
animal.eat(food)
}
๊ทธ๋ฌ๋ฉด ๋๋ฌผ์ ๋จน์ด์ ๊ณก๋ฌผ์ ๋ํ ์ ๋ณด๋ฅผ ๊ฐ์ง๊ณ ์์ด์ผ ํ๊ฒ ์ฃ ?
protocol AnimalFeed {
associatedtype CropType: Crop
static func grow() -> CropType
}
๋ง์ฐฌ๊ฐ์ง๋ก ๊ณก๋ฌผ๋ ๋๋ฌผ์ ๋จน์ด์ ๋ํ ์ ๋ณด๋ฅผ ๊ฐ์ง๊ณ ์์ด์ผ ํ ๊ฒ ์ ๋๋ค.
protocol Crop {
associatedtype FeedType: AnimalFeed
func harvest() -> FeedType
}
ํ์ง๋ง ์์ ๊ฐ์ด ๊ตฌํ๋์ด ์๋ค๋ฉด Crop์ FeedType์ ์์๋ด๊ณ ํด๋น Feed์ CropType์ ์์๋ด๊ณ Cropt์ FeedType์ ์์๋ด๊ณ ... ์ด๋ฐ ์์ผ๋ก ๋ฌดํ๋๋ก ์ฐ๊ฒฐ์ด ๋๋ ๋ฌธ์ ์ ์ด ๋ฐ์ํฉ๋๋ค.
๊ทธ๋์ ๊ตฌํ๋ถ์์ ์๋์ ๊ฐ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ฒ ๋๋ ๊ฒ์ด์ฃ .
๋ ๋ค๋ฅธ ๋ฌธ์ ์ ์ด ํ๋ ๋ ์กด์ฌํ๋๋ฐ์.
Hay๋ฅผ ๊ธธ๋ฌ๋ด๋ฉด Alfalfa๊ฐ ๋๊ณ
struct Hay: AnimalFeed {
static func grow() -> Alfalfa {
return Alfalfa()
}
}
Alfalfa๋ฅผ ์ํํ๋ฉด Hay๊ฐ ๋ฉ๋๋ค.
struct Alfalfa: Crop {
func harvest() -> Hay {
return Hay()
}
}
ํ์ง๋ง ์ฌ๊ธฐ์ Alfalfa๋ฅผ ๋ญ์ด ๋จน๋ Scratch์ผ๋ก ๋ณ๊ฒฝ์ด ๊ฐ๋ฅํ๋ค๋ ๊ฒ ์ ๋๋ค.
struct Alfalfa: Crop {
func harvest() -> Scratch {
return Scratch()
}
}
์ด๋ ๊ฒ ๋๋ฉด Alfalfa์ ๋จน์ด ํ์ ์ Scratch๊ฐ ๋๊ณ ํด๋น Crop ํ์ ์ Millet์ผ๋ก ๋ณ๊ฒฝ๋์ด ๋ฒ๋ฆฌ๋ ๊ฒ์ด์ฃ .
๊ทธ๋ฌ๋ฉด ์์๊ฒ ์ค ๋จน์ด๊ฐ ๊ฑด์ด์์ ๋ฐ๋ ๋ก ๋ฐ๋์ด ๋ฒ๋ฆฌ๋ ๊ฒ์ด์ฃ .
์ด๋ฌํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋ ๋ฐฉ๋ฒ์ where์ ์ ์ฌ์ฉํ์ฌ ์๋์ ๊ฐ์ด ์ ํํ ํ์ ์ ๋ช ์ํด ์ฃผ๋ ๊ฒ ์ ๋๋ค.
Self๋ ์์ ์ ํ์ ์ ์๋ฏธํ๋ ๊ฒ์ด๋ฏ๋ก Crop์ Feed๋ Feed ์์ ์ด ๋์ด์ผ ํด ๊ทธ๋ฆฌ๊ณ Crop์ Feed๋ Crop ์์ ์ด ๋์ด์ผ ํ๋ค๊ณ ๊ตฌํํ๋ ๊ฒ์ด์ฃ .
์ด๋ ๊ฒ ๊ตฌํํ๋ฉด ๋๋ฌผ์๊ฒ ์๋ง์ ๊ณก๋ฌผ๊ณผ ๋จน์ด๋ฅผ ์ค์์์ด ์ค ์ ์๊ฒ ๋๋ ๊ฒ์ด์ฃ !
protocol AnimalFeed {
associatedtype CropType: Crop
where CropType.FeedType == Self
static func grow() -> CropType
}
protocol Crop {
associatedtype FeedType: AnimalFeed
where FeedType.CropType == Self
func harvest() -> FeedType
}
๋ํ ๋ฌดํ๋๋ก ์ด์ด์ก๋ ๋ ํ๋กํ ์ฝ์ ๊ด๊ณ๋ ์ด๋ ๊ฒ ํ ์์ผ๋ก ๊ฐ๋จํ๊ฒ ๊ด๊ณ๊ฐ ๋ณํ๊ฒ ๋ฉ๋๋ค.
Source Code
GitHub - fomagran/WWDC: Repository to study content published by WWDC
Repository to study content published by WWDC. Contribute to fomagran/WWDC development by creating an account on GitHub.
github.com
Reference
Design protocol interfaces in Swift - WWDC22 - Videos - Apple Developer
Learn how you can use Swift 5.7 to design advanced abstractions using protocols. We'll show you how to use existential types, explore how...
developer.apple.com
๋๊ธ