ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Protocol 2편 (Delegation, Extension, 상속, 합성)
    오늘의 Swift 상식 2021. 8. 25. 19:03
    728x90
    반응형

     

     

     

     

     

     

    안녕하세요. 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

    댓글

Designed by Tistory.