250x250
반응형
05-12 14:14
Today
Total
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Notice
Recent Posts
Recent Comments
Link
Archives
관리 메뉴

Bill Kim's Life...

[Swift] Protocol(프로토콜) : 객체 확장 및 위임(Delegate) 본문

CS(컴퓨터 과학)/Swift

[Swift] Protocol(프로토콜) : 객체 확장 및 위임(Delegate)

billnjoyce 2020. 9. 8. 12:03
728x90
반응형
Swift5의 Protocol(프로토콜)에 대해서 그 정의와 사용 방법에 대해서 살펴봅니다.

 

 

#. 개발 환경

  • Xcode 11.x 이상
  • Swift 5

 

 


 

 

Protocols

 

프로토콜은 특정 기능 수행에 필수적인 요수를 청의한 청사진(blueprint)입니다. 

 

프로토콜을 만족시키는 타입을 프로토콜을 따른다(conform)고 말합니다. 

 

프로토콜에 필수 구현을 추가하거나 추가적인 기능을 더하기 위해 프로토콜을 확장(extend)하는 것이 가능합니다.

 

 

 


 

 

 

Protocol Syntax

 

프로토콜의 정의는 클래스, 구조체, 열거형 등과 유사합니다.

protocol SomeProtocol {
    // protocol definition goes here
}

struct SomeStructure: SomeProtocol {
    // structure definition goes here
}

class SomeClass: SomeProtocol {
    // class definition goes here
}

 

 

 

 


 

 

 

Property Requirements

 

프로토콜에서는 프로퍼티가 저장된 프로퍼티인지 계산된 프로퍼티인지 명시하지 않습니다. 

 

하지만 프로퍼티의 이름과 타입 그리고 gettable, settable한지는 명시합니다. 필수 프로퍼티는 항상 var로 선언해야 합니다.

protocol SomeProtocol {
    // 프로퍼티의 이름과 타입 그리고 gettable, settable한지는 명시합니다.
    var mustBeSettable: Int { get set }
    var doesNotNeedToBeSettable: Int { get }
}

protocol AnotherProtocol {
    // 타입 프로퍼티는 static 키워드를 적어 선언합니다.
    static var someTypeProperty: Int { get set }
}

protocol FullyNamed {
    // 하나의 프로퍼티를 갖는 프로토콜을 선언합니다.
    var fullName: String { get }
}

struct Person: FullyNamed {
    var fullName: String
}

let john = Person(fullName: "John Appleseed")
// john.fullName is "John Appleseed"

class Starship: FullyNamed {
    var prefix: String?
    var name: String
    init(name: String, prefix: String? = nil) {
        self.name = name
        self.prefix = prefix
    }
    var fullName: String {
        return (prefix != nil ? prefix! + " " : "") + name
    }
}

// 계산된 프로퍼티로 사용될 수 있습니다.
var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
// ncc1701.fullName is "USS Enterprise"

 

 

 

 


 

 

 

 

Method Requirements

 

익스텐션을 이용해 존재하는 타입에 새로운 이니셜라이저를 추가할 수 있습니다. 

 

이 방법으로 커스텀 타입의 이니셜라이저 파라미터를 넣을 수 있도록 변경하거나 원래 구현에서 포함하지 않는 초기화 정보를 추가할 수 있습니다.

protocol SomeProtocol {
    static func someTypeMethod()
}

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
    }
}

let generator = LinearCongruentialGenerator()

print("Here's a random number: \(generator.random())")
// Prints "Here's a random number: 0.3746499199817101”

print("And another one: \(generator.random())")
// Prints "And another one: 0.729023776863283"

 

mutating 키워드를 사용해 인스턴스에서 변경 가능하다는 것을 표시할 수 있습니다. 

이 mutating 키워드는 값타입 형에만 사용합니다.

protocol Togglable {
    mutating func toggle()
}

enum OnOffSwitch: Togglable {
     case off, on
     mutating func toggle() {
         switch self {
         case .off:
             self = .on
         case .on:
             self = .off
         }
     }
}

var lightSwitch = OnOffSwitch.off
        
lightSwitch.toggle()
print(lightSwitch) // lightSwitch is now equal to .on

 

 

 

 


 

 

 

 

Initializer Requirements

 

프로토콜에서 필수로 구현해야하는 이니셜라이저를 지정할 수 있습니다.

protocol SomeProtocol {
    init(someParameter: Int)
}

class SomeClass: SomeProtocol {
    
    // 클래스에서 프로토콜 필수 이니셜라이저의 구현
    // 프로토콜에서 특정 이니셜라이저가 필요하다고 명시했기 때문에 구현에서
    // 해당 이니셜라이저에 required 키워드를 붙여줘야 합니다.
    // 클래스 타입에서 final로 선언된 것에는 required를 표시하지 않도 됩니다.
    // final로 선언되면 서브클래싱 되지 않기 때문입니다.
    
    required init(someParameter: Int) {
        // initializer implementation goes here
    }
}

protocol SomeProtocol {
    init()
}

class SomeSuperClass {
    init() {
        // initializer implementation goes here
    }
}

class SomeSubClass: SomeSuperClass, SomeProtocol {
    // 특정 프로토콜의 필수 이니셜라이저를 구현하고, 수퍼클래스의 이니셜라이저를 서브클래싱하는 경우
    // 이니셜라이저 앞에 required 키워드와 override 키워드를 적어줍니다.
    required override init() {
        // initializer implementation goes here
    }
}

 

 

 

 


 

 

 

 

Protocols as Types

 

프로토콜도 하나의 타입으로 사용 가능합니다. 그렇기 때문에 다음과 같이 타입 사용이 허용되는 모든 곳에 프로토콜을 사용할 수 있습니다.

 

- 함수, 메소드, 이니셜라이저의 파라미터 타입 혹은 리턴 타입

- 상수, 변수, 프로퍼티의 타입

- 컨테이너인 배열, 사전 등의 아이템 타입

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
    }
}

var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
    for _ in 1...5 {
        rint("Random dice roll is \(d6.roll())")
    } 
    // Random dice roll is 3, Random dice roll is 5, Random dice roll is 4
    // Random dice roll is 5, Random dice roll is 4

 

 

 

 


 

 

 

 

Delegation

 

위임(Delegation)은 클래스 혹은 구조체 인스턴스에 특정 행위에 대한 책임을 넘길 수 있게 해주는 디자인 패턴 중 하나입니다.

class Dice {
    
}

protocol DiceGame {
    var dice: Dice { get }
    func play()
}

// DiceGameDelegate에 선언해서 실제 DiceGame의 행위와 관련된 구현을
// DiceGameDelegate를 따르는 인스턴스에 위임합니다.
protocol DiceGameDelegate: AnyObject {
    func gameDidStart(_ game: DiceGame)
    func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
    func gameDidEnd(_ game: DiceGame)
}

class SnakesAndLadders: DiceGame {
    var dice: Dice = Dice()
    weak var delegate: DiceGameDelegate?
    
    func play() {
        delegate?.gameDidStart(self)
    }
    
    func end() {
        delegate?.gameDidEnd(self)
    }
}

class DiceGameTracker: DiceGameDelegate {
    var numberOfTurns = 0
    
    func gameDidStart(_ game: DiceGame) {
        
    }
    
    func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
        
    }
    
    func gameDidEnd(_ game: DiceGame) {
        
    }
}

let tracker = DiceGameTracker()
let game = SnakesAndLadders()
        
game.delegate = tracker
game.play()

 

 

 


 

 

 

 

Adding Protocols Conformance with an Extension

 

이미 존재하는 타입에 새 프로토콜을 따르게 하기 위해 익스텐션을 사용할 수 있습니다. 

원래 값에 접근 권한이 없어도 익스텐션을 사용해 기능을 확장할 수 있습니다.

 

만약 어떤 프로토콜을 충족에 필요한 모든 조건을 만족하지만 아직 그 프로토콜을 따른다는 선언을 하지 않았다면 그 선언을 빈 익스텐션으로 선언할 수 있습니다.

class Dice {
    let sides: Int
    
    init(sides: Int) {
        self.sides = sides
    }
}

protocol TextRepresentable {
    var textualDescription: String { get }
}

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

let d12 = Dice(sides: 12)
print(d12.textualDescription) // Prints "A 12-sided dice"


protocol TextRepresentable {
    var textualDescription: String { get }
}

struct Hamster {
    var name: String
    var textualDescription: String {
        return "A hamster named \(name)"
    }
}

// Hamster 인스턴스인 simonTheHamster는 이제 TextRepresentable 타입으로 사용할 수 있습니다.
extension Hamster: TextRepresentable {}
        
let simonTheHamster = Hamster(name: "Simon")
let somethingTextRepresentable: TextRepresentable = simonTheHamster
print(somethingTextRepresentable.textualDescription) // A hamster named Simon

 

 

 


 

 

 

 

Collections of Protocol Types

 

프로토콜을 Array, Dictionary등 Collection 타입에 넣기위한 타입으로 사용할 수 있습니다.

let d12 = Dice(sides: 12)
let simonTheHamster = Hamster(name: "Simon")
        
let things: [TextRepresentable] = [d12, simonTheHamster]
        
for thing in things {
    print(thing.textualDescription)
    // A 12-sided dice
    // A hamster named Simon
}

 

 

 


 

 

 

 

Protocol Inheritance

 

클래스 상속같이 프로토콜도 상속할 수 있습니다. 여러 프로토콜을 상속받는 경우 콤마(,)로 구분합니다.

protocol InheritingProtocol : SomeProtocol, AnotherProtocol {
    // protocol definition goes here
}
        
protocol PrettyTextRepresentable : TextRepresentable {
    var prettyTextualDescription: String { get }
}

 

 

 


 

 

 

 

Class-Only Protocols

 

구조체, 열거형에서 사용하지 않고 클래스 타입에만 사용가능한 프로토콜을 선언하기 위해서는 프로토콜에 AnyObject를 추가합니다.

protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
    // class-only protocol definition goes here
}

 

 

 


 

 

 

 

Protocol Composition

 

동시에 여러 프로토콜을 따르는 타입을 선언할 수 있습니다.

protocol Named {
    var name: String { get }
}
        
protocol Aged {
    var age: Int { get }
}
        
struct Person: Named, Aged {
    var name: String
    var age: Int
}
        
func wishHappyBirthday(to celebrator: Named & Aged) {
     print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
        
let birthdayPerson = Person(name: "Malcolm", age: 21)
wishHappyBirthday(to: birthdayPerson) // Happy birthday, Malcolm, you're 21!

 

 

 


 

 

 

 

Optional Protocol Requirements

 

프로토콜을 선언하면서 필수 구현이 아닌 선택적 구현 조건을 정의할 수 있습니다. 

이 프로토콜의 정의를 위해서 @objc 키워드를 프로토콜 앞에 붙이고, 개별 함수 혹은 프로퍼티에는 @objc와 optional 키워드를 붙입니다. 

 

@objc 프로토콜은 클래스 타입에서만 채용될 수 있고 구조체나 열거형에서는 사용할 수 없습니다. 

@objc protocol CounterDataSource {
    @objc optional func increment(forCount count: Int) -> Int
    @objc optional var fixedIncrement: Int { get }
}

 

 

 


 

 

 

 

Protocol Extensions

 

익스텐션을 이용해 프로토콜을 확장할 수 있습니다. 

protocol RandomNumberGenerator {
    func random() -> Double
}

extension RandomNumberGenerator {
    func randomBool() -> Bool {
        return random() > 0.5
    }
}

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
    }
}

let generator = LinearCongruentialGenerator()

print("Here's a random number: \(generator.random())")
// Prints "Here's a random number: 0.3746499199817101”

print("And here's a random Boolean: \(generator.randomBool())")
// Prints "And here's a random Boolean: true"

 

프로토콜 익스텐션이 특정 조건에서만 적용되도록 선언할 수 있습니다. 이 선언에는 where 절을 사용합니다. 

다음은 Collection 엘리먼트가 Equatable인 경우에만 적용되는 allEqual()메소드를 구현한 예입니다.

extension Collection where Element: Equatable {
    func allEqual() -> Bool {
        for element in self {
            if element != self.first {
                return false
            }
        }
        return true
    }
}

let equalNumbers = [100, 100, 100, 100, 100]
let differentNumbers = [100, 100, 200, 100, 200]
        
print(equalNumbers.allEqual()) // true
print(differentNumbers.allEqual()) // false

 

 


 

 

 

이상으로 Swift에서의 Protocol(프로토콜)에 대해서 살펴보았습니다.

 

 

 

감사합니다.

 

 

 

 

www.slideshare.net/BillKim8/swift-protocol-12

 

[Swift] Protocol (1/2)

[Swift] Protocol (1/2) 에 관한 강의 자료입니다.

www.slideshare.net

www.slideshare.net/BillKim8/swift-protocol-22

 

[Swift] Protocol (2/2)

[Swift] Protocol (2/2) 에 관한 강의 자료입니다.

www.slideshare.net

 

 


[참고 자료(References)]

 

[1] [Swift]Protocols 정리 : http://minsone.github.io/mac/ios/swift-protocols-summary

[2] Protocols : https://docs.swift.org/swift-book/LanguageGuide/Protocols.html

[3] Swift ) Protocols (4) : https://zeddios.tistory.com/347

[4] Swift Protocol 적재적소에 사용하기 : https://academy.realm.io/kr/posts/understanding-swift-protocol/

[5] Swift 4.2 Protocol 공식 문서 정리 : https://medium.com/@kimtaesoo188/swift-4-2-protocol-공식-문서-정리-f3a97c6f8cc2

[6] 프로토콜 (Protocols) : https://jusung.gitbook.io/the-swift-language-guide/language-guide/21-protocols

[7] 오늘의 Swift 상식 (Protocol) : https://medium.com/@jgj455/오늘의-swift-상식-protocol-f18c82571dad

[8] [Swift] Protocol [01] : https://baked-corn.tistory.com/24

[9] [Swift] Protocol [02] : https://baked-corn.tistory.com/26

[10] Swift – 프로토콜 지향 프로그래밍 : https://blog.yagom.net/531/

 

728x90
반응형
Comments