-
Protocol 2편 (Delegation, Extension, 상속, 합성)오늘의 Swift 상식 2021. 8. 25. 19:03728x90반응형
안녕하세요. iOS 개발자 에이든입니다!👦🏻
Protocol 1편에 이어서 바로 2편 시작하겠습니다!
1편 아래 링크입니다
https://aiden-ios.tistory.com/12
Protocol 1편 (Protocol 정의 방법)
안녕하세요. iOS 개발자 에이든입니다!👦🏻 이번 시간은 Protocol에 대해 알아보겠습니다! 이번 시간은 내용이 좀 많아요. 그래서 1,2편으로 진행해보려고 합니다. 바로 그냥 들어가시죠~ Protocol은
aiden-ios.tistory.com
준비됐나요?
2편 바로 꼬우!!
Delegation(위임)
Class나 Struct의 Instance에 특정 행위에 대한 책임을 넘기는 디자인 패턴 중 하나예요. Delegation 된 기능을 제공할 수 있도록 Delegation된 책임을 캡슐화하는 Protocol을 정의하는 것으로 구현 Delegation은 특정 Action에 응답하거나 해당 소스의 기본 Type을 알 필요 없이 외부 소스에서 데이터를 검색하는 데 사용할 수 있어요.
! 주의: 코드가 많이 깁니다. ( 내부 코드는 자세히 볼 필요 없고 주석만 보셔도 됩니다. )
protocol RandomNumberGenerator { func random() -> Double } class LinearCongruentialGenerator: RandomNumberGenerator { var lastRandom = 42.0 let m = 139968.0 let a = 3877.0 let c = 29573.0 func random() -> Double { lastRandom = ((lastRandom * a + c).truncatingRemainder(dividingBy:m)) return lastRandom / m } } class Dice { let sides: Int let generator: RandomNumberGenerator init(sides: Int, generator: RandomNumberGenerator) { self.sides = sides self.generator = generator } func roll() -> Int { return Int(generator.random() * Double(sides)) + 1 } } protocol DiceGame { var dice: Dice { get } func play() } /// DiceGame의 책임을 DiceGameDelegate를 따르는 Instance에 위임합니다. // AnyObject로 선언하면 Class만 이 Protocol을 채택할 수 있는 속성이 됩니다. protocol DiceGameDelegate: AnyObject { func gameDidStart(_ game: DiceGame) func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) func gameDidEnd(_ game: DiceGame) } // Protocol의 조건에 맞추기위해 dice라는 Property는 gettable하게 구현되어 있고, play() Method가 구현되어 있습니다. class SnakesAndLadders: DiceGame { let finalSquare = 25 let dice = Dice(sides: 6, generator: LinearCongruentialGenerator()) var square = 0 var board: [Int] init() { board = Array(repeating: 0, count: finalSquare + 1) board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02 board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08 } // 강한 참조 순환을 막기위해 약한 참조를 합니다. (강한 참조와 약한 참조는 추후에 알아보죠!) weak var delegate: DiceGameDelegate? // 게임 진행에 반드시 필요한 것은 아니기 때문에 Optional로 정의하겠습니다. /// 게임의 전체 로직이 들어있는 Method // 바로 위에 정의한 delegate가 DiceGameDelegation Optional Type이므로 play() Method는 delegate의 Method를 호출할때마다 Optional Chaining을 합니다. func play() { square = 0 /// DiceGameDelegation의 게임 진행상황을 tracking하는 Method(게임 시작) delegate?.gameDidStart(self) gameLoop: while square != finalSquare { let diceRoll = dice.roll() /// DiceGameDelegation의 게임 진행상황을 tracking하는 Method(게임 진행) delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll) switch square + diceRoll { case finalSquare: break gameLoop case let newSquare where newSquare > finalSquare: continue gameLoop default: square += diceRoll square += board[square] } } /// DiceGameDelegation의 게임 진행상황을 tracking하는 Method(게임 종료) delegate?.gameDidEnd(self) } } // DiceGameDelegate Protocol을 채택하였고, 조건에 맞추기위해 3개의 Method가 구현되어 있습니다. class DiceGameTracker: DiceGameDelegate { var numberOfTurns = 0 func gameDidStart(_ game: DiceGame) { numberOfTurns = 0 if game is SnakesAndLadders { // SnakesAndLadders Class의 Instance가 Parameter로 들어오면 실행 print("Started a new game of Snakes and Ladders") } print("The game is using a \(game.dice.sides)-sided dice") } func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) { numberOfTurns += 1 print("Rolled a \(diceRoll)") } func gameDidEnd(_ game: DiceGame) { print("The game lasted for \(numberOfTurns) turns") } } lettracker =DiceGameTracker() letgame =SnakesAndLadders() game.delegate =tracker game.play() // Started a new game of Snakes and Ladders // The game is using a 6-sided dice // Rolled a 3 // Rolled a 5 // Rolled a 4 // Rolled a 5 // The game lasted for 4 turns
하나하나 보여드리려다 보니 코드가 많이 길어졌네요ㅜㅜ
코드는 전체적으로 볼 필요없고 주석 부분만 잘 보시면 될 것 같아요!
Extension
흔히 Struct, Class에서도 많이 사용되는데 Type에 기능을 추가하기 위해 사용하는 문법입니다!
1. 기존의 기능에 기능을 덛붙히는 느낌!
2. 따로 Method를 모아둔다던가 함으로써 코드의 가독성을 높일 수 있습니다.
3. 새로운 Protocol을 채택하기 위해 사용할 수 있습니다.
4. Property는 연산 Property만 사용 가능합니다.
기본 형태
extension SomeType { // 구현부 } // 새로운 Protocol을 채택하는 경우 extension SomeType: SomeProtocol { // 구현부 } // Ex extension String { var length: Int { var string: NSString = NSString(string: self) return sting.length } }
- Extension에 조건 추가
where를 사용하여 특정 조건을 만족시킬 때만 기능을 확장시키거나 Protocol을 채택하도록 제한할 수 있습니다.
// 배열의 원소가 Int Type일때만 Extension 됩니다. extension Array: SomeProtocol where Element: Int { // 구현부 }
Protocol 상속
Class의 상속처럼 Protocol도 상속할 수 있습니다. Class와 마찬가지로 ", "로 구별합니다.
protocol Movable { func go(to destination: String) } protocol OnBoardable { var numberOfPassangers: Int { get } } protocol Vehicle: Movable, OnBoardable { } // typealias Vehicle = Movable & OnBoardable <- 위와 같은 효과 struct Car: Vehicle { func go(to destination: String) { print("\(destination)(으)로 갑니다") } var numberOfPassangers: Int = 4 } var car = Car(numberOfPassangers: 9) car.go(to: "집") // 집(으)로 갑니다 print(car.numberOfPassangers) // 9
자 여기까지 보시면서 한 가지 생각이 드는 생각!
꼭 Protocol을 채택하면 꼭 정의된 부분은 필수로 구현을 해야 할까?
선택적으로 구현할 수 있는 방법이 있어요!
바로 @objc를 활용하거나 Extension을 활용한 방법입니다.
한번 살펴보시죠~!
@objc를 사용한 방법
1. Objective-C를 사용한 방법입니다.
2. @objc 키워드를 Protocol 앞에 붙이고, Method나 Property에는 @objc와 optional 키워드를 붙입니다.
3. @objc Protocol은 Class만 채택할 수 있어요! (Struct, Enum은 불가능합니다ㅠ)@objc protocol Person { @objc optional var name: String {get} @objc optional func speak() } class Aiden: Person { func notChoice() { print("name, speak()를 사용하지 않았습니다.") } } let aiden = Aiden() aiden.notChoice() // name, speak()를 사용하지 않았습니다.
@objc는 사용자가 정의한 Type을 사용할 수 없다는 단점이 있어요!
이를 해결하기 위해서는 NSObject를 상속받습니다.
class CustomType: NSObject { var property: String = "특수타입" } @objc protocol Person { @objc optional var name: CustomType {get} @objc optional func speak() } class Aiden: Person { func notChoice() { print("name, speak()를 사용하지 않았습니다.") } } let aiden = Aiden() aiden.notChoice()
Extension을 활용한 방법
@objc의 단점을 해결하는 또 다른 방법으로, Protocol을 정의한 후 구현하고 싶은 기능만 Extension 해서 이를 채택하게 하는 방법입니다. 해당 Protocol을 채택하는 Type들이 기능들을 따로 구현하지 않고, 기능이 구현된 상태로 그대로 받길 원할 때도 사용하는 방법이에요.
struct CustomType { var name: String } protocol Person { var specialPeople: CustomType {get} func speak() func sleep() } extension Person { func speak() { print(specialPeople.name+"이 말합니다.") } } class Aiden: Person { // name과 sleep() 메소드만 구현하면 된다. var specialPeople: CustomType = CustomType(property: "Aiden") func sleep() { print(specialPeople.name+"잡니다.zzz") } } let aiden = Aiden() aiden.speak() // Aiden이 말합니다. aiden.sleep() // Aiden잡니다.zzz
이 방법을 사용하면 사용자 정의 Type을 사용할 수 있습니다.
둘은 각각 장단점이 달라요.
먼저 Extension을 활용한 방법에서 염려되었던 부분은
1. Extension 하고 Method를 빈 구현체로 구현하면 기능은 없지만 그 Method는 남아있어요.
2. return Type을 가지는 경우 반드시 return값을 정해줘야 하기에 메모리 낭비가 생길 수 있어요.
@objc를 사용한 방법에서 염려되었던 부분은
1. Class만 채택할 수 있다는 점.
2. 사용자 정의 Type도 Class로만 정의 가능하다는 점.만약 Protocol을 채택하는 타입이 Class고 사용자 정의 Type이 없거나 Class로 정의할 Type이라면
@objc를 사용하는 것이 좋은 것 같고,
그 외에는 Extension을 활용한 방법이 좋은 것 같습니다!
자 오늘은 2번에 걸쳐 Protocol에 대해 알아봤습니다.
대부분이 그러하듯 이 녀석도 잘 활용하면 코드를 매우 깔끔하게 정리할 수 있어요!
만약 비슷한 기능을 가진 Type들을 여러 개 구현해야 한다면
꼭 사용하는 게 좋을 것 같아요!
혹시라도 부족하거나 잘못된 부분 그리고 질문 있으시면 언제든 댓글 부탁드려요! 감사합니다!👦🏻👋🏻
728x90반응형'오늘의 Swift 상식' 카테고리의 다른 글
QR code - Scan! (0) 2021.09.21 QR Code - 만들기편! (0) 2021.09.15 Protocol 1편 (Protocol 정의 방법) (0) 2021.08.16 Class의 상속 (0) 2021.08.15 Initializer 2편 (Class의 Initializer) (0) 2021.08.08