20-2. 타입으로서의 프로토콜, 확장을 통한 프로토콜 준수

🌈 devleeky16498·2022년 4월 21일
0

타입으로 프로토콜(protocol as type)

  1. 프로토콜 자체는 어떠한 기능도 구현하지 않는다. 그럼에도 불구하고 완전한 타입으로서 사용 가능하다. 타입으로서 프로토콜을 사용하는 것은 존재타입(existential type)이라고 한다.
  • 함수, 메서드 또는 초기화 구문에서 파라미터 타입 또는 반환타입
  • 상수, 변수 또는 프로퍼티 타입
  • 배열, 딕셔너리 또는 다른 컬렉션 항목의 타입
class Dice 
	let sides : Int
    let generator : RandomNumberGenerator
    init(sides : Int, generator : RandomNumberGenerator) {
    	self.sides = sides
        self.generator = generator
    }
    
    func roll() -> Int {
    	return Int(gererator.random() * Double(sides)) + 1
    }
}
//Dice라는 클래스를 정의한다.
//여기에서 generator라는 프로퍼티는 난수생성기로서 역할하며 RandonNumberGenerator(프로토콜) 타입이다.
//이는 앞에서 프로토콜로 정의하였으며 generator프로퍼티는 프로토콜의 모든 인스턴스를 사용 가능하다.

 var d6 = Dice(sides : 6, generator : LinearCongruentialGenerator())
 for _ in 1...5 {
 	print("RandomDice roll! \(d6.roll)")
}
//여기에서 generator는 프로토콜을 타입으로 채택하므로 프로토콜 내부의 Random()
//메서드를 호출하는 것이 가능하다.
  1. 위임(Delegation)은 클래스 또는 구조체가 책임의 일부를 다른 타입의 인스턴스에 넘겨주거나 위임할 수 있도록 하는 디자인 패턴이다. 이 패턴은 위임된 기능을 제공하기 위해 준수하는 타입이 보장되도록 위임된 책임을 캡슐화하는 프로토콜을 정의하여 구현한다.
protocol DiceGame {
	var dice : Dice {get}
    func play()
}

protocol DiceGameDelegate : AnyObject {
	func gameDidStart(_ game : DiceGame)
    func game(_ game : DiceGame, didStartNewTurnWithDiceRoll diceRoll : Int)
    func gameDidEnd(_ game : DiceGame)
}

//DiceGame 프로토콜은 주사위를 포함하는 모든 게임에 의해서 채택되는 프로토콜이다.
//이 때 강한 참조 사이클을 방지하기 위해서 weak(약한참조)로 선언된다.
//클래스 전용 프로토콜은 AnyObject의 상속으로 표시된다.

class SnakesAndLadders : DiceGame {
	let finalSquare = 25
    let dice = Dice(sides : 6, generator : LinearCongruentialGenerator())
    //Dicegame프로토콜을 준수하므로 프로퍼티와 함수를 구현해준다.
    // dice: Dice {}, func()
    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
    }
    
    weak var delegate : DiceGameDelegate?
    //약한 참조 선언
    //위임자(delegate)는 게임 플레이를 위해서 필요하진 않기 때문에 요구되지 않으므로 옵셔널이다.
    //이 때, DiceGameDelegate 프로토콜은 클래스 전용이기 때문에 참조 사이클을 막기 위해 weak로 선언한다.
    func play() {
    	square = 0
        delegate?.gameDidStart(self)
        //delegate패턴을 통해서 DiceGameDelegate 프로토콜 내 함수를 구현한다.
        gameLoop: while square != finalSquare {
        	let diceRoll = dice.roll()
            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]
            }
        }
        delegate?.gameDidEnd(self)
    }
}

확장을 통한 프로토콜 준수성 추가

  1. 기존 타입에 대해 새로운 프로토콜을 채택하고 준수하기 위해서 기존 타입을 확장할 수 있다. 확장은 기존 타입에 프로퍼티, 메서드, 서브 스크립트의 추가가 가능하므로 프로토콜이 요구하는 모든 요구사항을 추가할 수 있다.
protocol TextRepresentable {
	var textualDescription : String {get}
}
//프로토콜과 해당 프로토콜의 읽기 전용 프로퍼티를 선언한다.
//위의 Dice클래스는 위의 프로토콜 준수를 위해 확장 가능하다.

extension Dice : TextRepresentable {
	var textualDescription : String {
    	return "A \(sides) -sided dice"
    }
}

//이제 확장을 통해서 새로운 프로토콜을 준수하도록 추가했으므로 모든 인스턴스에서 프로토콜의 프로퍼티 활용이 가능하다.
let d12 = Dice(sides : 12, generator : LinearCongruentialGenerator())
print(d12.textualDescription)
  1. 타입을 확장할 때 제약조건을 나열해서 일반 타입이 프로토콜을 조건적으로 준수하도록 만들 수 있다. 일반적 where절을 작성해서 제약조건을 명시한다.
extension Array: TextRepresentable where Element : TextRepresentable {
	var textualDescription : String {
    	let itemsAsText = self.map { $0.textualDescription }
        return "[" + itemAsText.joined(separator: ",") + "]"
    }
}
//다음과 같이 Array라는 일반타입에 where절을 통해 조건적인 프로토콜을 명시할 수 있다.
  1. 확장과 함께 프로토콜 채택 선언은 프로토콜을 늦게나마 명시하는 경우 비어있는 확장을 통해서 프로토콜 채택이 가능하다.
struct Hamster {
	var mane : String
    var textualDescription : String {
    	return "A hamster \(name)"
    }
}

extension Hamster : TextRepresentable {}
//다음과 같이 구조체에 프로토콜의 속성을 추가할 수 있다.
//요구사항이 충족된다고 프로토콜이 자동 채택되지 않으므로 반드시 명시적으로 작성한다.
profile
Welcome to Growing iOS developer's Blog! Enjoy!🔥

0개의 댓글