1. Protocols πŸ‘©β€πŸ’»

1. Protocols

Protocol은 Methods, Properties, 그리고 νŠΉμ • μž‘μ—…μ΄λ‚˜ κΈ°λŠ₯의 μš”κ΅¬μ‚¬ν•­μ„ μ •μ˜ν•˜κΈ°μœ„ν•œ blueprint둜, Protocol 은 Class, Structure, Enumeration 에 채택(adopt)λ˜μ–΄ μš”κ΅¬μ‚¬ν•­μ„ κ΅¬ν˜„ν•˜λ„λ‘ ν•œλ‹€. 그리고 Protocol 의 λͺ¨λ“  μš”κ΅¬μ‚¬ν•­μ— μΆ©μ‘±ν•˜λ©΄ κ·Έ Type 은 ν•΄λ‹Ή Protocol 을 μ€€μˆ˜(conform)ν•œλ‹€κ³  ν‘œν˜„ν•œλ‹€.

2. Protocol Syntax

Syntax

protocol SomeProtocol {
    // protocol definition goes here
}

Protocol 을 μ •μ˜ν•˜λŠ” 방법은 Class, Structure, Enumeration 을 μ •μ˜ν•˜λŠ” 방법과 μœ μ‚¬ν•˜λ‹€.

3. Adopt Protocol

Protocol 을 μ±„νƒν•˜λŠ” 것 μ—­μ‹œ Class 의 Inheritance 와 μœ μ‚¬ν•˜λ‹€.

struct SomeStructure: FirstProtocol, AnotherProtocol {
    // structure definition goes here
}

단, Classμ—μ„œλŠ” μ£Όμ˜ν•΄μ•Όν•  것이 Inheritanceκ°€ μ’…λ£Œλœ ν›„ Protocol의 채택이 κ°€λŠ₯ν•˜λ‹€.

class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
    // class definition goes here
}

4. Adopt Protocol vs. Class Inheritance

Β  Protocol Class
Class O O
Structure O X
Enumeration O X
Multiple Inheritance(or Adapt) O X

2. Protocol Requirements πŸ‘©β€πŸ’»

1. Property Requirements

1 ) Syntax

You can define

  • var keyword
  • type
  • name
  • { get set } or { get }
  • static, class keyword
protocol SomeProtocol {
    var mustBeSettable: Int { get set }
    var doesNotNeedToBeSettable: Int { get }
}

get set을 λͺ¨λ‘ μ •μ˜ν•  경우 μžλ™μœΌλ‘œ Constant Stored Properties 와 Read-Only Computed Properties λŠ” μ€€μˆ˜ν•˜λŠ” 것이 λΆˆκ°€λŠ₯ν•˜λ‹€.

반면 get만 μ •μ˜ν•  경우 λͺ¨λ“  μ’…λ₯˜μ˜ Properties 에 λŒ€ν•΄ Protocol 을 μ€€μˆ˜ν•  수 μžˆλ‹€. 그리고 이것이 μœ νš¨ν•  λ•Œ set이 μœ νš¨ν•œ νƒ€μž…μ΄λΌλ©΄ set은 μžλ™μœΌλ‘œ μœ νš¨ν•˜λ‹€.

μ—¬κΈ°μ„œ μ£Όμ˜ν•΄μ•Ό ν•  것이 { get set }은 이 Protocol 을 μ±„νƒν•˜λŠ” Type 이 λ°˜λ“œμ‹œ get set을 λ§Œμ‘±ν•˜λ„λ‘ κ΅¬ν˜„ν•΄μ•Όν•œλ‹€λŠ” 의미이고, { get }은 λ°˜λ“œμ‹œ get을 λ§Œμ‘±ν•˜λ„λ‘ κ΅¬ν˜„ν•΄μ•Όν•œλ‹€λŠ” μ˜λ―Έλ‹€. β€˜get’ 만 λ§Œμ‘±ν•˜κ³  β€˜set’ 을 λ§Œμ‘±ν•˜μ§€ μ•Šλ„λ‘ Read-Onlyμž„μ„ κ°•μ œν•˜λŠ” 것이 μ•„λ‹ˆλ‹€.


You cannot define

  • let keyword
  • What type of properties (i.e. stored properties or computed properties)

Protocol 이 Properties μš”κ΅¬μ‚¬ν•­μ„ μ •μ˜ν•  λ•ŒλŠ” λ°˜λ“œμ‹œ var keyword 만 μ‚¬μš©ν•˜λ©°, Properties 의 μœ ν˜•μ€ μ •μ˜ν•  수 μ—†λ‹€.


2 ) Type Properties

protocol AnotherProtocol {
    static var someTypeProperty: Int { get set }
}

λ˜ν•œ Protocol 이 Type Properties λ₯Ό μ •μ˜ν•  λ•ŒλŠ” λ§ˆμ°¬κ°€μ§€λ‘œ static keyword λ₯Ό λ°˜λ“œμ‹œ μž‘μ„±ν•΄μ•Όν•œλ‹€(이 κ·œμΉ™μ€ Classes μ—μ˜ν•΄ κ΅¬ν˜„λ  λ•Œ class λ˜λŠ” static keyword λ₯Ό μš”κ΅¬ν•˜λŠ” 경우 λͺ¨λ‘ μ μš©λœλ‹€).


3 ) Examples

single instance property 만 μš”κ΅¬ν•˜λŠ” 맀우 κ°„λ‹¨ν•œ Protocol FullyNamed λ₯Ό μ •μ˜ν•œλ‹€.

protocol FullyNamed {
    var fullName: String { get }
}

이λ₯Ό μ±„νƒν•˜λŠ” Structure λ₯Ό ν•˜λ‚˜ λ§Œλ“€μ–΄λ³΄μž.

struct Person: FullyNamed {
    var fullName: String
}

μœ„ Person 은 FullyNamed Protocol 을 μ™„λ²½ν•˜κ²Œ μ€€μˆ˜ν•˜κ³  μžˆλ‹€.

var john = Person(fullName: "John Park")
print(john.fullName)    // John Park

john 의 fullName 은 β€œJohn Park” 이닀.

john.fullName = "John Kim"
print(john.fullName)    // John Kim

이제 john 의 fullName 은 β€œJohn Kim” 이닀. Protocol μ—μ„œ var fullName: String { get }둜 μ •μ˜ν–ˆμœΌλ‚˜ 이것은 get만 λ§Œμ‘±ν•΄μ•Όν•œλ‹€λŠ” μ˜λ―Έκ°€ μ•„λ‹ˆκ³  get을 λ§Œμ‘±ν•΄μ•Όν•œλ‹€λŠ” 의미이고, 이것을 μ±„νƒν•œ Person Structure λŠ” fullName 을 Variable Stored Properties둜 μ •μ˜ν–ˆκΈ° λ•Œλ¬Έμ— set μ—­μ‹œ μžλ™μœΌλ‘œ μœ νš¨ν•˜κ²Œλœλ‹€. λ”°λΌμ„œ set μ—­μ‹œ μœ νš¨ν•œ 것이닀.


μ΄λ²ˆμ—λŠ” μœ„ FullyNamed Protocol 을 μ±„νƒν•˜λŠ” μ’€ 더 λ³΅μž‘ν•œ Class λ₯Ό ν•˜λ‚˜ μ •μ˜ν•΄λ³Έλ‹€.

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

μ΄λ²ˆμ—λŠ” fullName 을 Read-Only Computed Properties둜 μ •μ˜ν–ˆκ³ , Protocol 이 get set이 μ•„λ‹Œ get만 μ •μ˜ν–ˆκΈ° λ•Œλ¬Έμ— μ—­μ‹œ 이 Starship 은 FullyNamed Protocol 을 μ™„λ²½ν•˜κ²Œ μ€€μˆ˜ν•˜κ³  μžˆλ‹€.

var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
print(ncc1701.fullName) // USS Enterprise

2. Method Requirements

Methods 에 λŒ€ν•œ μš”κ΅¬μ‚¬ν•­ μ—­μ‹œ Properties 와 μœ μ‚¬ν•˜λ‹€.

1 ) Syntax

You can define

  • name
  • parameter(including variadic parameter)
  • return type
  • static keyword
protocol SomeProtocol {
    func someTypeMethod() -> SomeType
}


You cannot define

  • parameter default value
  • method body


2 ) Type Methods

protocol AnotherProtocol {
    static func anotherTypeMethod() -> SomeType
}


3 ) Examples

protocol RandomNumberGenerator {
    func random() -> Double
}

이λ₯Ό μ±„νƒν•˜λŠ” Class λ₯Ό ν•˜λ‚˜ λ§Œλ“€μ–΄λ³΄μž.

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 λŠ” μ„ ν˜• 합동 생성기(linear congruential generator) 둜 μ•Œλ €μ§„ μ˜μ‚¬ λ‚œμˆ˜(pseudorandom number) 생성기 μ•Œκ³ λ¦¬μ¦˜μ΄λ‹€.

let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
print("And another one: \(generator.random())")
Here's a random number: 0.23928326474622771
And another one: 0.4782664609053498

3. Mutating Method Requirements

Protocol μ—μ„œ Methods λ₯Ό mutating으둜 μ •μ˜ν–ˆμ„ λ•Œ 이 Protocol 을 μ±„νƒν•˜λŠ” Type 이 Classes인 κ²½μš°λŠ” Reference Types μ΄λ―€λ‘œ mutating keyword λ₯Ό μž‘μ„±ν•  ν•„μš”κ°€ μ—†λ‹€. 였직 Value Types 인 Structures 와 Enumerationsμ—μ„œλ§Œ μž‘μ„±ν•œλ‹€.

Example

protocol Toggleable {
    mutating func toggle()
}
enum OnOffSwitch: Toggleable {
    case off, on
    
    mutating func toggle() {
        switch self {
        case .off: self = .on
        case .on: self = .off
        }
    }
}
var lightSwitch = OnOffSwitch.off
print("light switch is \(lightSwitch) now.")

lightSwitch.toggle()
print("light switch is \(lightSwitch) now.")

lightSwitch.toggle()
print("light switch is \(lightSwitch) now.")
light switch is off now.
light switch is on now.
light switch is off now.

4. Initializer Requirements

Methods 에 λŒ€ν•œ μš”κ΅¬μ‚¬ν•­ μ—­μ‹œ Properties 와 μœ μ‚¬ν•˜λ‹€.

1 ) Syntax

You can define

  • parameter

Methods 와 μœ μ‚¬ν•˜λ‹€. ν•˜μ§€λ§Œ Initializers λŠ” name κ³Ό explicit return type, static 이 ν—ˆμš©λ˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— λ‹Ήμ—°νžˆ Protocol μ—­μ‹œ λΆˆκ°€λŠ₯ν•˜λ‹€. 즉, μ–΄λ–€ Custom Initializrerλ₯Ό κ΅¬ν˜„ν•΄μ•Ό ν•˜λŠ”μ§€ κ·Έ Type 만 μ •μ˜ν•œλ‹€.

protocol SomeProtocol {
    init(someParameter: SomeType)
}


You cannot define

  • parameter default value
  • method body


2 ) Examples

protocol Human {
    var name: String { get set }
    var age: Int { get set }
    
    init(name: String, age: Int)
}
struct Student: Human {
    var name: String
    
    var age: Int
    
    init(name: String = "[Unknown]", age: Int) {
        self.name = name
        self.age = age
    }
    
    func identification() {
        print("My name is \(self.name) and I am \(self.age) years old")
    }
}
var jamie = Student(name: "Jamie", age: 20)
jamie.identification()

var kate = Student(age: 22)
kate.identification()
My name is Jamie and I am 20 years old
My name is [Unknown] and I am 22 years old


3 ) Class Implementations of Protocol Initializer Requirements

μœ„μ—μ„œ Structures λ₯Ό μ΄μš©ν•œ 예제λ₯Ό μ‚΄νŽ΄λ³΄μ•˜λ‹€. 그런데 Protocol 의 Initializers λ₯Ό Classes에 μ±„νƒν•˜λ €λ©΄ λ°˜λ“œμ‹œ Required Initializers λ₯Ό μ‚¬μš©ν•΄ 이 Class 의 Subclasses κ°€ λ°˜λ“œμ‹œ 이λ₯Ό κ΅¬ν˜„ν•˜λ„λ‘ κ°•μ œν•΄μ•Όν•œλ‹€.

class Student: Human {
    var name: String
    
    var age: Int
    
    required init(name: String = "[Unknown]", age: Int) {
        self.name = name
        self.age = age
    }
    
    func identification() {
        print("My name is \(self.name) and I am \(self.age) years old")
    }
}

required keyword λ₯Ό μž‘μ„±ν•˜μ§€ μ•ŠμœΌλ©΄ compile-time error κ°€ λ°œμƒν•œλ‹€.


ν•˜μ§€λ§Œ Classes κ°€ final modifier λ₯Ό μ΄μš©ν•΄ μ •μ˜λ˜λŠ” 경우, 이 Class λŠ” 더 이상 Subclassing 될 수 μ—†κΈ° λ•Œλ¬Έμ— requiredλ₯Ό μž‘μ„±ν•  ν•„μš”κ°€ μ—†λ‹€.

final class Student: Human {
    var name: String

    var age: Int

    init(name: String = "[Unknown]", age: Int) {
        self.name = name
        self.age = age
    }

    func identification() {
        print("My name is \(self.name) and I am \(self.age) years old")
    }
}


4 ) Class Implementations Overriding Designated Initializer by Protocol Initializer Requirements

λ§Œμ•½ μ–΄λ–€ Subclassκ°€ Protocol 에 μ˜ν•΄ Initializers 의 κ΅¬ν˜„μ„ μš”κ΅¬λ°›λŠ” 데, κ·Έ Initializers 의 Type 이 Superclass 의 Designated Initializer인 경우 override keyword 와 ν•¨κ»˜ μ‚¬μš©ν•œλ‹€.

protocol SomeProtocol {
    init()
}

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

class SomeSubClass: SomeSuperClass, SomeProtocol {
    // "required" from SomeProtocol conformance; "override" from SomeSuperClass
    required override init() {
        // initializer implementation goes here
    }
}


5 ) Failable Initializer Requirements

Failable Initializers μ—­μ‹œ ν•΄λ‹Ή Protocol 을 채택항 Types κ°€ 이λ₯Ό μ€€μˆ˜ν•˜λ„λ‘ μ •μ˜ν•  수 μžˆλ‹€.

  • Failable Initializer RequirementsλŠ” Failable Initializer λ˜λŠ” Nonfailable Initializer 에 μ˜ν•΄ 좩쑱될 수 μžˆλ‹€.
  • Nonfailable Initializer RequirementsλŠ” Nonfailable Initializer λ˜λŠ” Implicitly Unwrapped Failable Initializer 에 μ˜ν•΄ 좩쑱될 수 μžˆλ‹€.

3. Protocols as Types πŸ‘©β€πŸ’»

1. Protocols as Types

Protocols λŠ” 자체적으둜 μ–΄λ– ν•œ κΈ°λŠ₯도 κ΅¬ν˜„ν•˜μ§€ μ•ŠλŠ”λ‹€. κ·ΈλŸΌμ—λ„ λΆˆκ΅¬ν•˜κ³  μ½”λ“œμ—μ„œ Fully Fledged Types으둜 μ‚¬μš©ν•  수 μžˆλ‹€. Types 둜 Protocols λ₯Ό μ‚¬μš©ν•˜λŠ” 것은 β€œthere exists a type T such that T conforms to the protocolβ€λΌλŠ” κ΅¬μ ˆμ—μ„œ λΉ„λ‘―λœ 쑴재 νƒ€μž…(Existential Type)이라 ν•œλ‹€.

즉, Protocols μ—­μ‹œ First-Class Citizen 으둜 λ‹€λ£° 수 μžˆλ‹€λŠ” 것을 μ˜λ―Έν•œλ‹€.

  • Function, Method, Initializer 의 Parameter Type λ˜λŠ” Return Type으둜 μ‚¬μš©λ  수 μžˆλ‹€.
  • Constant, Variable, Property 의 Type으둜 μ‚¬μš©λ  수 μžˆλ‹€.
  • Array, Dictionary, λ˜λŠ” λ‹€λ₯Έ Container 의 Type으둜 μ‚¬μš©λ  수 μžˆλ‹€.

Protocols μ—­μ‹œ Swift Typesμ΄λ―€λ‘œ 이름은 λŒ€λ¬Έμžλ‘œ μ‹œμž‘ν•œλ‹€.

Superclass μ—μ„œ Subclasss 둜 Downcasting ν•˜λ˜ κ²ƒμ²˜λŸΌ Protocol Typeμ—μ„œ 이것을 μ€€μˆ˜ν•˜λŠ” Underlying Type으둜 Downcasting ν•  수 μžˆλ‹€.

2. Examples

protocol RandomNumberGenerator {
    func random() -> Double
}

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

var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())

Array(1...5).forEach { _ in print("Random dice roll is \(d6.roll())") }
Random dice roll is 2
Random dice roll is 3
Random dice roll is 5
Random dice roll is 6
Random dice roll is 2

4. Delegation πŸ‘©β€πŸ’»

1. Delegation

Delegation은 Class λ˜λŠ” Structure κ°€ 일뢀 μ±…μž„μ„ λ‹€λ₯Έ Type 의 Instance μ—κ²Œ hand off(or delegate) ν•  수 μžˆλ„λ‘ ν•˜λŠ” Design Pattern이닀. 이 Design Pattern 은 μœ„μž„λœ μ±…μž„μ΄ μΊ‘μŠν™”(encapsulates)된 Protocol 을 μ •μ˜ν•˜λŠ” κ²ƒμœΌλ‘œ κ΅¬ν˜„λ˜μ–΄μ§€λ©°, λŒ€λ¦¬μž(delegate)κ°€ μœ„μž„λœ κΈ°λŠ₯을 μ œκ³΅ν•˜λŠ” 것을 보μž₯ν•œλ‹€. λ”°λΌμ„œ Delegation 은 νŠΉμ • μž‘μ—…μ— μ‘λ‹΅ν•˜κ±°λ‚˜ μΊ‘μŠν™”λœ μœ ν˜•μ„ μ•Œ ν•„μš” 없이 κΈ°λŠ₯을 μ œκ³΅ν•˜κ³ μž ν•˜λŠ”λ° μ‚¬μš©λœλ‹€.

2. Examples

μœ„μ—μ„œ λ§Œλ“  RandomNumberGenerator Protocol, Dice Class λ₯Ό μ΄μš©ν•΄ λ‹€μŒ 두 Protocols λ₯Ό μ •μ˜ν•œλ‹€.

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 Protocol 은 μ£Όμ‚¬μœ„λ₯Ό μ΄μš©ν•œ μ–΄λ–€ κ²Œμž„μ—κ²Œλ‚˜ 채택될 수 있고, DiceGameDelegate Protocol 은 DiceGame 의 진행 μƒνƒœλ₯Ό μΆ”μ ν•˜κΈ° μœ„ν•΄ 채택될 수 μžˆλ‹€.

Snake and Ladders Game

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[3] = 8; board[6] = 11; board[9] = 9; board[10] = 2
        board[14] = -10; board[19] = -11; board[22] = -2; board[24] = -8
    }

    weak var delegate: DiceGameDelegate?

    func play() {
        square = 0
        delegate?.gameDidStart(self)
        gameLoop: while square != finalSquare {
            let diceRoll = dice.roll()
            delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)
            switch square + diceRoll {
            case finalSquare:                                   // newSquare == finalSqure : finish
                break gameLoop
            case let newSquare where newSquare > finalSquare:   // roll dice again until it euqals finalSquare
                continue gameLoop
            default:                                            // progress game play
                square += diceRoll
                square += board[square]
            }
        }
        delegate?.gameDidEnd(self)
    }
}

Strong Reference Cycles Between Class instances λ₯Ό μ˜ˆλ°©ν•˜κΈ° μœ„ν•΄ delegates λŠ” Week References둜 μ„ μ–Έλ˜μ—ˆλ‹€.

Class-Only Protocols μ—μ„œ λ‹€μ‹œ μ‚΄νŽ΄λ³΄κ² μ§€λ§Œ, AnyObjectλ₯Ό μƒμ†μ‹œν‚€λŠ”κ²ƒμœΌλ‘œ Protocol 은 Class-Only Protocols둜 marking λœλ‹€. 그리고 Class-Only Protocols λ₯Ό μ±„νƒν•œ Class λŠ” λ°˜λ“œμ‹œ delegate λ₯Ό Week Reference 둜 μ„ μ–Έν•΄μ•Όν•œλ‹€.


이제 DiceGameDelegate Protocol 을 μ±„νƒν•œ Game Tracker λ₯Ό λ§Œλ“€μ–΄λ³΄μž.

class DiceGameTracker: DiceGameDelegate {
    var numberOfTurns = 0

    func gameDidStart(_ game: DiceGame) {
        numberOfTurns = 0
        if game is SnakesAndLadders {
            print("Started a new game of Snakes and Ladders")
        }
        print("The game is using a \(game.dice.sides)-side 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")
    }
}


κ²Œμž„μ„ μ§„ν–‰μ‹œμΌœ Delegateλ₯Ό μ–΄λ–»κ²Œ μœ„μž„μ‹œμΌœ μž‘λ™ν•˜λ„λ‘ ν•˜λŠ”μ§€ μ‚΄νŽ΄λ³΄μž.

let tracker = DiceGameTracker()
let game = SnakesAndLadders()
game.delegate = tracker
game.play()
Started a new game of Snakes and Ladders
The game is using a 6-side dice
Rolled a 2
Rolled a 3
Rolled a 5
Rolled a 6
Rolled a 2
Rolled a 3
Rolled a 5
Rolled a 6
Rolled a 1
Rolled a 3
Rolled a 4
Rolled a 6
Rolled a 1
Rolled a 3
Rolled a 4
Rolled a 5
Rolled a 1
Rolled a 2
Rolled a 4
Rolled a 5
Rolled a 1
Rolled a 2
Rolled a 3
Rolled a 5
Rolled a 6
Rolled a 2
Rolled a 3
Rolled a 5
Rolled a 6
Rolled a 2
The game lasted for 30 turns

5. Adding Protocol Conformance with an Extension πŸ‘©β€πŸ’»

1. Adding Protocol Conformance with an Extension

κΈ°μ‘΄ νƒ€μž…μ— λŒ€ν•΄ μ†ŒμŠ€ μ½”λ“œμ— 직접 μ ‘κ·Όν•  수 없더라도 μƒˆλ‘œμš΄ ν”„λ‘œν† μ½œμ„ μ±„νƒν•˜κ³  μ€€μˆ˜ν•˜λ„λ‘ ν•΄ ν™•μž₯ν•  수 μžˆλ‹€. 이λ₯Ό μ΄μš©ν•΄ κΈ°μ‘΄ νƒ€μž…μ— μƒˆλ‘œμš΄ Properties, Methods, Subscripts λ₯Ό μΆ”κ°€ν•  수 μžˆλ‹€.

μ΄μ „μ˜ Swift Extensions μ—μ„œ extension keyword 만 μ΄μš©ν•΄ ν™•μž₯을 ν–ˆλŠ”λ° 이번 μ±•ν„°μ—μ„œλŠ” extension을 ν™•μž₯ν•  λ•Œ Protocol을 μ±„νƒμ‹œμΌœ ν™•μž₯ν•˜λ„λ‘ ν•΄λ³Έλ‹€.

protocol TextRepresentable {
    var textualDescription: String { get }
}

Dice Class λ₯Ό μœ„ Protocol 을 μ΄μš©ν•΄ ν™•μž₯ν•΄λ³΄μž.

extension Dice: TextRepresentable {
    var textualDescription: String {
        return "A \(sides)-sided dice"
    }
}
let d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())

print(d6.textualDescription)    // A 6-sided dice
print(d12.textualDescription)   // A 12-sided dice

2. Extending Primitive Types using Protocols

μ΄λ²ˆμ—λŠ” Swift Strings and Characters μ±•ν„°μ—μ„œ μ‚¬μš©ν•΄λ³Έ Swift 의 λΆˆνŽΈν•œ λ¬Έμžμ—΄ μ ‘κ·Όκ³Ό Extensions - Subscripts μ±•ν„°μ—μ„œ ν™•μž₯ν•  λ•Œ μ‚¬μš©ν–ˆλ˜ Subscripts λ₯Ό Protocol 을 μ΄μš©ν•΄ ν™•μž₯ν•΄λ³΄μž.

κ³΅ν†΅μœΌλ‘œ μ‚¬μš©ν•  Protocol 을 ν•˜λ‚˜ μ •μ˜ν•œλ‹€.

protocol easyIndex {
    subscript(_ digitIndex: Int) -> Self { get }
}


1 ) μš°μ„  Extensions - Subscripts λ₯Ό Protocol 을 μ΄μš©ν•˜λŠ” κ²ƒμœΌλ‘œ λ°”κΏ”λ³΄μž

extension Int: easyIndex {
    subscript(digitIndex: Int) -> Int {
        var decimalBase = 1
        for _ in 0..<digitIndex {
            decimalBase *= 10
        }
        return (self / decimalBase) % 10
    }
}

μœ„ Subscript λŠ” κΈ°μ‘΄ μ±•ν„°μ—μ„œ μ‚΄νŽ΄λ³Έ 것과 λ§ˆμ°¬κ°€μ§€λ‘œ 10μ§„λ²•μ˜ n 승수 λ₯Ό index둜 μ ‘κ·Όν•œλ‹€.

3782[0] // 2, 10^0 의 μžλ¦Ώμˆ˜λŠ” 2λ‹€.
3782[1] // 8, 10^1 의 μžλ¦Ώμˆ˜λŠ” 8이닀.
3782[2] // 7, 10^2 의 μžλ¦Ώμˆ˜λŠ” 7이닀.
3782[3] // 3, 10^3 의 μžλ¦Ώμˆ˜λŠ” 3이닀.
3782[4] // 0, 10^4 의 μžλ¦Ώμˆ˜λŠ” μ‘΄μž¬ν•˜μ§€ μ•ŠμœΌλ―€λ‘œ 0이닀.


2 ) Swift Strings and Characters μ—­μ‹œ index λ₯Ό μ΄μš©ν•΄ μ ‘κ·Όν•  수 μžˆλ„λ‘ λ°”κΏ”λ³΄μž

let greeting = "Guten Tag!"

print(greeting.startIndex)                              // Index(_rawBits: 15)
print(greeting.index(greeting.startIndex, offsetBy: 3)) // Index(_rawBits: 196871)

이미 이전 μ±•ν„°μ—μ„œ μ‚΄νŽ΄λ³΄μ•˜μ§€λ§Œ Swift λŠ” λ¬Έμžμ—΄μ— λŒ€ν•œ indexκ°€ λ‹€λ₯Έ μ–Έμ–΄μ™€λŠ” μ’€ λ‹€λ₯΄λ‹€. λ”°λΌμ„œ μ ‘κ·Όν•  λ•Œλ„ λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•΄ λ‹€μŒκ³Ό 같이 μ ‘κ·Όν•΄μ•Όλ§Œν•œλ‹€.

print(greeting[greeting.startIndex])                                // G
print(greeting[greeting.index(greeting.startIndex, offsetBy: 1)])   // u
print(greeting[greeting.index(greeting.startIndex, offsetBy: 2)])   // t
print(greeting[greeting.index(greeting.endIndex, offsetBy: -1)])    // !

반면 TypeScript λ‚˜ Python λ“± λ‹€λ₯Έ μ–Έμ–΄μ—μ„œλŠ” λ‹€μŒκ³Ό 같이 직관적이고 μ‰½κ²Œ 접근이 κ°€λŠ₯ν•˜λ‹€.

const greeting: string = "Guten Tag!"

console.log(greeting[0])                    // G
console.log(greeting[1])                    // u
console.log(greeting[2])                    // t
console.log(greeting[greeting.length - 1])  // !


μœ„ Protocol 을 μ΄μš©ν•΄ Swift 의 String 을 ν™•μž₯ν•΄λ³΄μž.

extension String: easyIndex {
    subscript(digitIndex: Int) -> String {
        String(self[self.index(self.startIndex, offsetBy: digitIndex)])
    }
}

μœ„ Subscript λŠ” TypeScript 와 λ™μΌν•˜κ²Œ μ•žμ—μ„œλΆ€ν„° zero-based index둜 μ ‘κ·Όν•œλ‹€.

print(greeting[0])                  // G
print(greeting[1])                  // u
print(greeting[2])                  // t
print(greeting[greeting.count - 1]) // !

3. Conditionally Conforming to a Protocol (where)

Generic Type은 였직 Generic parameter κ°€ Protocol 을 μ€€μˆ˜ν•˜λŠ” κ²½μš°μ™€ 같은 νŠΉμ • μ‘°κ±΄μ—μ„œλ§Œ Protocol 의 μš”κ΅¬μ‚¬ν•­μ„ λ§Œμ‘±ν•  수 μžˆλ‹€. λ”°λΌμ„œ Generic Type 을 ν™•μž₯ν•  λ•Œ whereλ₯Ό μ΄μš©ν•΄ constraintsλ₯Ό λ‚˜μ—΄ν•΄ μ‘°κ±΄λΆ€λ‘œ μ€€μˆ˜ν•˜λ„λ‘ λ§Œλ“€μ–΄μ•Όν•œλ‹€. 이것은 μΆ”ν›„ Generic Where Clauses μ—μ„œ μžμ„Ένžˆ 닀룬닀.

Switch Value Binding with Where μ—μ„œ λ³Έ 것 처럼 쑰건을 λ§€μΉ­μ‹œν‚¬ λ•Œ whereλŠ” 주둜 좔가적인 쑰건을 constraints둜 μΆ”κ°€ν•˜κΈ° μœ„ν•΄ μ‚¬μš©λœλ‹€.


extension Array: TextRepresentable where Element: TextRepresentable {
    var textualDescription: String {
        let itemsAsText = self.map { $0.textualDescription }
        return "[" + itemsAsText.joined(separator: ", ") + "]"
    }
}

μœ„ Protocol 은 Array 에 TextRepresentable Protocol 을 μ±„νƒν•˜λŠ” κ²ƒμœΌλ‘œ ν™•μž₯ν•œλ‹€. 그리고 이것이 μž‘λ™ν•˜λŠ” 쑰건은 Array 의 Element κ°€ TextRepresentable 을 μ€€μˆ˜ν•˜λŠ” 경우둜 μ œν•œν•œλ‹€.
κ·Έλž˜μ•Όλ§Œ self.map { $0.textualDescription }μ—μ„œ μ—λŸ¬κ°€ λ°œμƒν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ΄λ‹€.

let myDice = [d6, d12]
print(myDice.textualDescription)    // [A 6-sided dice, A 12-sided dice]

Element κ°€ TextRepresentable Protocol 을 λ”°λ₯΄λŠ” Arrayμ΄λ―€λ‘œ Computed Property textualDescriptionλ₯Ό Member 둜 κ°–λŠ”λ‹€.

let myNumber = [1, 2, 4, 6]
let myString = ["A", "C", "F"]

myNumber.textualDescription // Property 'textualDescription' requires that 'Int' conform to 'TextRepresentable'
myString.textualDescription // Property 'textualDescription' requires that 'String' conform to 'TextRepresentable'

Element κ°€ TextRepresentable Protocol 을 λ”°λ₯΄μ§€ μ•ŠλŠ” Arrayμ΄λ―€λ‘œ Computed Property textualDescriptionλ₯Ό Member 둜 갖지 μ•Šμ•„ μ—λŸ¬κ°€ λ°œμƒν•œλ‹€.

4. Declaring Protocol Adoption with an Extension

Protocol 을 채택해 ν™•μž₯ν•˜λ €λŠ” κΈ°λŠ₯이 이미 Type 에 μ‘΄μž¬ν•œλ‹€λ©΄, μ–΄λ–»κ²Œ ν•΄μ•Όν• κΉŒ? Swift Extensions μ—μ„œ μ‚΄νŽ΄λ³Έ κ²ƒμ²˜λŸΌ Extension 은 Overriding 을 ν•  수 μ—†λ‹€.

ν•˜μ§€λ§Œ 이 Type 이 μ–΄λ–€ Protocol 을 λ§Œμ‘±ν•¨μ„ λͺ…μ‹œμ μœΌλ‘œ ν‘œν˜„ν•΄μ•Ό ν•  수 μžˆλ‹€. 이 경우 extension을 μ΄μš©ν•΄ Protocol 을 μ±„νƒν•˜λ˜, μ•„λ¬΄λŸ° κ΅¬ν˜„λ„ ν•˜μ§€ μ•ŠμœΌλ©΄ λœλ‹€. 즉, extension 의 body λ₯Ό μ•„μ˜ˆ λΉ„μ›Œλ‘λ©΄ λœλ‹€.

이미 TextRepresentable 이 κ΅¬ν˜„λ˜μ–΄μžˆλŠ” Hamster Structure κ°€ μžˆλ‹€.

struct Hamster {
    var name: String
    var textualDescription: String {
        return "A hamster named \(name)"
    }
}
let simonTheHamster = Hamster(name: "Simon")
print(simonTheHamster.textualDescription)   // A hamster named Simon

let somethingTextRepresentable: TextRepresentable = simonTheHamster // Value of type 'Hamster' does not conform to specified type 'TextRepresentable'

이미 ν•΄λ‹Ή κΈ°λŠ₯이 κ΅¬ν˜„λ˜μ–΄μžˆκΈ° λ•Œλ¬Έμ— μ‚¬μš© κ°€λŠ₯ν•˜μ§€λ§Œ, TextRepresentable Protocol 을 λ”°λ₯΄κ³  μžˆλŠ” 것은 μ•„λ‹ˆκΈ° λ•Œλ¬Έμ— μ—λŸ¬κ°€ λ°œμƒν•œλ‹€.
λ”°λΌμ„œ μœ„ Hamster κ°€ TextRepresentable Protocol 을 λ§Œμ‘±ν•˜λ„λ‘ λ§Œλ“€μ–΄λ³΄μž.

extension Hamster: TextRepresentable {}

let somethingTextRepresentable: TextRepresentable = simonTheHamster
print(somethingTextRepresentable.textualDescription)    // A hamster named Simon

Protocol 을 μ±„νƒν•˜μ§€λ§Œ κ΅¬ν˜„μ„ ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— Overriding이 λ°œμƒν•˜μ§€ μ•ŠμœΌλ―€λ‘œ μ •μƒμ μœΌλ‘œ Extension 이 κ°€λŠ₯ν•˜λ‹€. 결과적으둜 이제 Hamster λŠ” TextRepresentable λ₯Ό λ§Œμ‘±ν•˜λŠ” 것을 확인할 수 μžˆλ‹€.


6. Adopting a Protocol Using a Synthesized Implementation πŸ‘©β€πŸ’»

Swift λŠ” λ§Žμ€ Simple Cases 에 λŒ€ν•΄ μžλ™μœΌλ‘œ Equatable, Hashable, Comparable Protocol 을 λ§Œμ‘±ν•˜λ„λ‘ ν•  수 μžˆλ‹€. 이λ₯Ό Synthesized Implementation(ν•¨μ„±λœ κ΅¬ν˜„)이라 ν•˜λ©°, Protocol μš”κ΅¬μ‚¬ν•­ κ΅¬ν˜„μ„ 직접 ν•  ν•„μš”κ°€ μ—†λ‹€.

1. Synthesized Implementation of Equatable

Swift λŠ” λ‹€μŒ 쑰건을 λ§Œμ‘±ν•˜λŠ” Custom Types μ—κ²Œ Equatable을 μ œκ³΅ν•œλ‹€.

  • Structures that have only stored properties & That stored properties conform to the Equatable protocol
  • Enumerations that have only associated types & That associated types conform to the Equatable protocol
  • Enumerations that have no associated types

μœ„ 쑰건을 λ§Œμ‘±ν•˜λŠ” 경우 ==, != λ₯Ό 직접 κ΅¬ν˜„ν•˜μ§€ μ•Šκ³  Equatabe Protocol 을 μ±„νƒν•¨μœΌλ‘œμ¨ Synthesized Implementation을 μ œκ³΅ν•  수 μžˆλ‹€.

struct Vector3D {
    var x = 0.0, y = 0.0, z = 0.0
}

let alpha = Vector3D(x: 2.0, y: 3.0, z: 4.0)
let beta = Vector3D(x: 2.0, y: 3.0, z: 4.0)

if alpha == beta { // Binary operator '==' cannot be applied to two 'Vector3D' operands
    print("These two vectors are also equivalent.")
}

==비ꡐλ₯Ό ν•˜κΈ° μœ„ν•œ ν”Όμ—°μ‚° ν•¨μˆ˜κ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ”λ‹€κ³  μ—λŸ¬κ°€ λ°œμƒλœλ‹€. 그런데 이 Structure λŠ” Stored Properties 만 μ €μž₯ ν•˜κ³  있으며, κ·Έ Stored Properties λŠ” Equatable Protocol 을 λ§Œμ‘±ν•˜λ―€λ‘œ 첫 번째 쑰건에 μ˜ν•΄ Equatable Protocol 을 μ±„νƒν•˜λŠ” 것 만으둜 μžλ™μœΌλ‘œ Synthesized Implementation을 μ œκ³΅ν•  수 μžˆλ‹€.

struct Vector3D: Equatable {
    var (x, y, z) = (0.0, 0.0, 0.0)
}

let alpha = Vector3D(x: 2.0, y: 3.0, z: 4.0)
let beta = Vector3D(x: 2.0, y: 3.0, z: 4.0)

if alpha == beta {
    print("These two vectors are also equivalent.")
}
These two vectors are also equivalent.

2. Synthesized Implementation of Hashable

Swift λŠ” λ‹€μŒ 쑰건을 λ§Œμ‘±ν•˜λŠ” Custom Types μ—κ²Œ Hashable을 μ œκ³΅ν•œλ‹€.

  • Structures that have only stored properties & That stored properties conform to the Hashable protocol
  • Enumerations that have only associated types & That associated types conform to the Hashable protocol
  • Enumerations that have no associated types

μœ„ Equatableκ³Ό 거의 λ™μΌν•˜λ‹€λŠ” 것을 μ•Œ 수 μžˆλ‹€. μœ„ 쑰건을 λ§Œμ‘±ν•˜λŠ” 경우 hashValue, hash(into:)λ₯Ό 직접 κ΅¬ν˜„ν•˜μ§€ μ•Šκ³  Hashable Protocol 을 μ±„νƒν•¨μœΌλ‘œμ¨ Synthesized Implementation을 μ œκ³΅ν•  수 μžˆλ‹€.

참고둜 Hashable Protocol 은 Equatable Protocol 을 μ€€μˆ˜ν•œλ‹€.

extension AnyHashable : Equatable {}

λ”°λΌμ„œ Hashable Protocol 을 μ€€μˆ˜ν•˜λŠ” 경우 Equatable Protocol 의 Synthesized Implementation을 ν•¨κ»˜ μ œκ³΅ν•œλ‹€.

struct Vector3D: Hashable {
    var (x, y, z) = (0.0, 0.0, 0.0)
}

let alpha = Vector3D(x: 2.0, y: 3.0, z: 4.0)
let beta = Vector3D(x: 2.0, y: 3.0, z: 4.0)

let hashAlpha = alpha.hashValue
let hashBeta = beta.hashValue

if alpha == beta {
    print("These two vectors are also equivalent.")
}

print(hashAlpha)
print(hashBeta)
These two vectors are also equivalent.
-4042012231002845599
-4042012231002845599

3. Synthesized Implementation of Comparable

Swift λŠ” Raw Values λ₯Ό κ°–κ³  μžˆμ§€ μ•Šμ€ Enumerations μ—κ²Œ λ‹€μŒ 쑰건을 λ§Œμ‘±ν•˜λŠ” 경우 Comparable을 μ œκ³΅ν•œλ‹€.

  • Enumerations that have no Raw Values
  • Enumerations that have no Raw Values
    & Enumerations that have associated types
    & That associated types conform to the Comparable protocol

μœ„ 쑰건을 λ§Œμ‘±ν•˜λŠ” 경우 <, <=, >, >= operator λ₯Ό 직접 κ΅¬ν˜„ν•˜μ§€ μ•Šκ³  Comparable Protocol 을 μ±„νƒν•¨μœΌλ‘œμ¨ Synthesized Implementation을 μ œκ³΅ν•  수 μžˆλ‹€.

enum SkillLevel: Comparable {
    case beginner
    case intermediate
    case expert(stars: Int)
}

var levels = [SkillLevel.intermediate,
              SkillLevel.beginner,
              SkillLevel.expert(stars: 5),
              SkillLevel.expert(stars: 3)]

if SkillLevel.intermediate > SkillLevel.beginner {
    print("intermediate is higher level than beginner")
}

for level in levels.sorted() {
    print(level)
}
intermidiate is higer level than beginner

beginner
intermediate
expert(stars: 3)
expert(stars: 5)

7. Collections of Protocol Types πŸ‘©β€πŸ’»

Protocols as Types 이미 μ‚΄νŽ΄λ³΄μ•˜λ“―μ΄ Protocols μ—­μ‹œ First-Class Citizen 으둜 λ‹€λ£° 수 μžˆμœΌλ―€λ‘œ 이것을 Collections 에 μ €μž₯ν•˜λŠ” 것 μ—­μ‹œ κ°€λŠ₯ν•˜λ‹€.

let d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
let simonTheHamster = Hamster(name: "Simon")

let things: [TextRepresentable] = [d6, d12, simonTheHamster]

for thing in things {
    print(thing.textualDescription)
}
A 6-sided dice
A 12-sided dice
A hamster named Simon

8. Protocol Inheritance πŸ‘©β€πŸ’»

1. Protocol Inheritance

Protocol 을 Classes, Structures, Enumerations 에 Adapt μ‹œν‚€λŠ” 것 말고도 Protocol 이 λ‹€λ₯Έ Protocol 을 Inheritanceν•˜λŠ” 것 μ—­μ‹œ κ°€λŠ₯ν•˜λ‹€.

Multiple Adapt 이 κ°€λŠ₯ν–ˆλ˜ κ²ƒμ²˜λŸΌ Multiple Inherit μ—­μ‹œ κ°€λŠ₯ν•˜λ‹€.

protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
    // protocol definition goes here
}

2. Examples

SnakesAndLadders 에 TextRepresentable Protocol 을 μ±„νƒν•˜κ³  λ‹€μŒκ³Ό 같이 κ²Œμž„ 정보λ₯Ό 좜λ ₯ν•  수 μžˆλ‹€.

protocol TextRepresentable {
    var textualDescription: String { get }
}

extension SnakesAndLadders: TextRepresentable {
    var textualDescription: String {
        return "A game of Snakes and Ladders with \(finalSquare) squares"
    }
}
let game = SnakesAndLadders()
print(game.textualDescription)
A game of Snakes and Ladders with 25 squares


이제 이 TextRepresentable λ₯Ό 상속해 PrettyTextRepresentable Protocol 을 λ§Œλ“€κ³ , 이것을 ν•œ 번 더 SnakesAndLadders 에 ν™•μž₯ν•΄λ³΄μž.

protocol PrettyTextRepresentable: TextRepresentable {
    var prettyTextualDescription: String { get }
}

extension SnakesAndLadders: PrettyTextRepresentable {
    var prettyTextualDescription: String {
        var output = textualDescription + ":\n"
        for index in 1...finalSquare {
            switch board[index] {
            case let ladder where ladder > 0:
                output += "β–² "
            case let snake where snake < 0:
                output += "β–Ό "
            default:
                output += "β—‹ "
            }
        }
        return output
    }
}
let game = SnakesAndLadders()
print(game.prettyTextualDescription)
A game of Snakes and Ladders with 25 squares:
β—‹ β—‹ β–² β—‹ β—‹ β–² β—‹ β—‹ β–² β–² β—‹ β—‹ β—‹ β–Ό β—‹ β—‹ β—‹ β—‹ β–Ό β—‹ β—‹ β–Ό β—‹ β–Ό β—‹ 

9. Class-Only Protocols πŸ‘©β€πŸ’»

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

Delegation Examples μ—μ„œ λ³Έ κ²ƒμ²˜λŸΌ, Class 의 μ±„νƒλ§Œ ν—ˆμš©ν•˜λ €λ©΄, AnyObjectλ₯Ό μƒμ†μ‹œν‚΄μœΌλ‘œμ¨ Class-Only Protocols둜 marking λœλ‹€.

Class-Only Protocols λ₯Ό μ±„νƒν•œ Class λŠ” λ°˜λ“œμ‹œ delegate λ₯Ό Week Reference 둜 μ„ μ–Έν•΄μ•Όν•œλ‹€.

Protocol 의 μš”κ΅¬μ‚¬ν•­μ— μ •μ˜λœ μž‘λ™μ΄ Value Semanticsκ°€ μ•„λ‹Œ Reference Semanticsμž„μ„ κ°€μ •ν•˜κ±°λ‚˜ μš”κ΅¬ν•˜λŠ” 경우 Class-Only Protocolsλ₯Ό μ‚¬μš©ν•œλ‹€.

Which one choose Structures or Classes μ—μ„œ μ• ν”Œμ€ Inheritance 관계λ₯Ό 섀계할 λ•Œ μ²˜μŒλΆ€ν„° Protocol을 μ‚¬μš©ν•˜λŠ” 것을 ꢌμž₯ν•˜κ³ μžˆλ‹€. λ”°λΌμ„œ Class μ—λ§Œ μ±„νƒλ˜μ–΄μ•Ό ν•˜λŠ” κΈ°λŠ₯을 상속 ꡬ쑰둜 섀계할 λ•Œ Class Inheritance λŒ€μ‹  Class-Only Protocolsλ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€.


10. Protocol Composition πŸ‘©β€πŸ’»

1. Protocol Composition between Protocols

λ™μ‹œμ— μ—¬λŸ¬ Protocols λ₯Ό μ€€μˆ˜ν•˜λŠ” 경우, 이것을 단일 μš”κ΅¬μ‚¬ν•­μœΌλ‘œ κ²°ν•©ν•˜λŠ” 것이 μœ μš©ν•  수 μžˆλ‹€.

Protocol Composition은 SomeProtocol & Another Protocolκ³Ό 같이 & λ₯Ό μ΄μš©ν•΄ κ²°ν•©ν•˜λ©°, 이것은 Temporary Local Protocol인 κ²ƒμ²˜λŸΌ μž‘λ™ν•œλ‹€.

λ‹€μŒμ€ Named 와 Aged Protocols 의 두 μš”κ΅¬μ‚¬ν•­μ„ ν•˜λ‚˜λ‘œ κ²°ν•©ν•œλ‹€.

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)!")
}

&에 μ˜ν•΄ Named 와 Aged Protocols λŠ” κ²°ν•©λ˜μ–΄ μš”κ΅¬μ‚¬ν•­μ„ ν•œ λ²ˆμ— λ§Œμ‘±ν•˜λ„λ‘ κ΅¬ν˜„ν•  수 μžˆλ‹€.

let birthdayPerson = Person(name: "Harry", age: 11)
wishHappyBirthday(to: birthdayPerson)   // Happy birthday, Harry, you're 11!

2. Protocol Composition with Classes

Named Protocol κ³Ό Location Class λ₯Ό μ •μ˜ν•œλ‹€.

protocol Named {
    var name: String { get }
}

class Location {
    var latitude: Double
    var longitude: Double
    init(latitude: Double, longitude: Double) {
        self.latitude = latitude
        self.longitude = longitude
    }
}

이제 Location 을 μƒμ†ν•˜κ³  Named λ₯Ό μ±„νƒν•˜λŠ” City Class λ₯Ό μ •μ˜ν•œλ‹€.

class City: Location, Named {
    var name: String
    init(name: String, latitude: Double, longitude: Double) {
        self.name = name
        super.init(latitude: latitude, longitude: longitude)
    }
}
let seattle = City(name: "Seattle", latitude: 47.6, longitude: -122.3)

이제 City seattle의 이름과 μœ„μΉ˜λ₯Ό 좜λ ₯ν•˜λŠ” ν•¨μˆ˜λ₯Ό λ§Œλ“€μ–΄λ³΄μž.

1 ) Case 1 - Subclass

func whereIs(_ city: City) {
    print("\(city.name), latitude: \(city.latitude), longitude: \(city.longitude)")
}

κ°€μž₯ κ°„λ‹¨ν•œ 방법이닀. μ²˜μŒλΆ€ν„° Named λ₯Ό μ€€μˆ˜ν•˜λŠ” Subclass Cityλ₯Ό μ‚¬μš©ν•˜λŠ” 것이닀.

2 ) Case 2 - Downcasting

ν•˜μ§€λ§Œ Cityκ°€ μ•„λ‹Œ μœ„μΉ˜ 정보와 이름을 κ°–λŠ” λ‹€λ₯Έ Subclass Type 이 μΆ”κ°€λœλ‹€λ©΄ μœ„ ν•¨μˆ˜λŠ” μž¬μ‚¬μš©μ„ ν•  수 μ—†κ²Œλœλ‹€. λ”°λΌμ„œ Parameter λ₯Ό Superclass Location을 받도둝 ν•΄μ•Όν•œλ‹€.

func whereIs(_ location: Location) {
    print("\((location as? City)!.name), latitude: \(location.latitude), longitude: \(location.longitude)")
}

Downcating을 μ΄μš©ν•˜λ©΄ Location 을 μƒμ†ν•˜κ³  Named λ₯Ό μ±„νƒν•˜λŠ”, λ‹€λ₯Έ Subclass Type 이 μΆ”κ°€λ˜λ”λΌλ„ Switch와 asλ₯Ό μ΄μš©ν•œ Type Casting 을 μ΄μš©ν•΄ ν•¨μˆ˜λ₯Ό μž¬μ‚¬μš© ν•  수 μžˆλ‹€.

3 ) Protocol Composition with Class

μœ„ κ²½μš°λ„ ν•¨μˆ˜λ₯Ό μž¬μ‚¬μš© ν•  μˆ˜λŠ” μžˆμ§€λ§Œ, Type 이 좔가될 λ•Œλ§ˆλ‹€ ν•¨μˆ˜μ˜ κ΅¬ν˜„μ„ 맀번 μˆ˜μ •ν•΄μ€˜μ•Όν•˜λŠ” λ¬Έμ œκ°€ μžˆλ‹€. Protocol CompositionλŠ” μ΄λŸ¬ν•œ 경우 λ”μš± μœ μ—°ν•˜κ²Œ λŒ€μ‘ν•  수 μžˆλ‹€.

func whereIs(_ location: Location & Named) {
    print("\(location.name), latitude: \(location.latitude), longitude: \(location.longitude)")
}

Location 을 μƒμ†ν•˜κ³  Named λ₯Ό μ±„νƒν•˜λŠ”, λ‹€λ₯Έ Subclass Type이 μΆ”κ°€λ˜λ”λΌλ„ ν•¨μˆ˜λŠ” μž¬μ‚¬μš© κ°€λŠ₯ν•˜λ©°, κ΅¬ν˜„μ„ μˆ˜μ •ν•  ν•„μš”κ°€ μ—†λ‹€.


μœ„ μ„Έ 가지 방법 쀑 μ–΄λ–€ 방법을 μ‚¬μš©ν•˜λ“  λ‹€μŒ κ²°κ³Όλ₯Ό μ–»λŠ”λ‹€. λ‹€λ§Œ Protocol Compositionλ₯Ό μ‚¬μš©ν•˜λŠ” 것이 μ½”λ“œλ₯Ό 더 μœ μ—°ν•˜κ²Œ λ§Œλ“ λ‹€.

whereIs(seattle)    // Seattle, latitude: 47.6, longitude: -122.3

11. Checking for Protocol Conformance πŸ‘©β€πŸ’»

1. Checking for Protocol Conformance

Type Casting μ—μ„œ μ„€λͺ…ν–ˆλ“―이 is와 as μ—°μ‚°μžλ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€.

  • is : Instance κ°€ Protocol 을 μ€€μˆ˜ν•˜λ©΄ true, μ•„λ‹ˆλ©΄ falseλ₯Ό λ°˜ν™˜.
  • as? : Instance κ°€ Protocol 을 μ€€μˆ˜ν•˜λ©΄ Optional<Protocol Type>, μ•„λ‹ˆλ©΄ nil을 λ°˜ν™˜.
  • as! : Instance κ°€ Protocol 을 μ€€μˆ˜ν•˜λ©΄ Protocol Type, μ•„λ‹ˆλ©΄ Runtime Errorλ₯Ό trigger.


Protocol 을 ν•˜λ‚˜ μ •μ˜ν•˜μž.

protocol HasArea {
    var area: Double { get }
}

이제 μœ„ Protocol 을 μ€€μˆ˜ν•˜λŠ” κ°„λ‹¨ν•œ Class λ₯Ό ν•˜λ‚˜ μΆ”κ°€ν•΄λ³Έλ‹€.

class Country: HasArea {
    var area: Double
    init(area: Double) { self.area = area }
}

let country = Country(area: 100.0)

country Instance λ₯Ό is, as?, as! μ—°μ‚°μžλ₯Ό μ΄μš©ν•΄ νƒ€μž…μ„ 체크해본닀.

  • is
print(country is HasArea)   // true
print(country is Int)       // false
if country is HasArea {
    print("country conforms to HasArea protocol.")
} else {
    print("country do not conforms to HasArea protocol.")
}
country conforms to HasArea protocol.
  • as?
print(country as? HasArea)   // Optional(__lldb_expr_7.Country)
print(country as? Int)       // nil
if let country = country as? HasArea {
    print(country)
    print("country conforms to HasArea protocol.")
} else {
    print("country do not conforms to HasArea protocol.")
}
Optional(__lldb_expr_7.Country)
country conforms to HasArea protocol.
  • as!
print(country as! HasArea)   // __lldb_expr_11.Country
print(country as! Int)       // Could not cast value of type '__lldb_expr_11.Country' (0x1025a8720) to 'NSNumber' (0x1b8cd7ff0).

Forced Unwrapping으둜 인해 Type 뢈일치 μ‹œ Runtime Errorκ°€ λ°œμƒν•œλ‹€. as!λŠ” μ—λŸ¬κ°€ λ°œμƒν•˜μ§€ μ•ŠμŒμ΄ λͺ…ν™•ν•œ 경우의 Downcastingμ—μ„œ μ‚¬μš©ν•΄μ•Όν•œλ‹€. ν™•μΈν•˜λŠ” μš©λ„λ‘œλŠ” μ ν•©ν•˜μ§€ μ•Šλ‹€.

2. Examples

HasArea Protocol 을 μ€€μˆ˜ν•˜λŠ” Class 와 μ€€μˆ˜ν•˜μ§€ μ•ŠλŠ” Class λ₯Ό μΆ”κ°€λ‘œ μ •μ˜ν•œλ‹€.

class Circle: HasArea {
    let pi = 3.1415927
    var radius: Double
    var area: Double { pi * radius * radius }
    init(radius: Double) { self.radius = radius }
}

class Animal {
    var legs: Int
    init(legs: Int) { self.legs = legs }
}


이제 3개의 Classes λ₯Ό ν•˜λ‚˜μ˜ 배열에 λ‹΄μ•„ Type Checking 을 μ΄μš©ν•΄ μ•ˆμ „ν•˜κ²Œ μˆœν™˜μ‹œμΌœλ³΄μž.

let objects: [AnyObject] = [
    Circle(radius: 2.0),
    Country(area: 243_610),
    Animal(legs: 4)
]

objects.forEach {
    if let objectWithArea = $0 as? HasArea {
        print("Area is \(objectWithArea.area)")
    } else {
        print("Something that doesn't have an area")
    }
}
Area is 12.5663708
Area is 243610.0
Something that doesn't have an area

12. Optional Protocol Requirements πŸ‘©β€πŸ’»

1. Optional Protocol Requirements Syntax

Protocol 의 Requirements λ₯Ό μ •μ˜ν•  λ•Œ Optional 을 μ‚¬μš©ν•  수 μžˆλ‹€. 이λ₯Ό Optional Requirements라 ν•˜λ©°, 이것은 λ°˜λ“œμ‹œ κ΅¬ν˜„ν•΄μ•Όν•˜λŠ” μ±…μž„μ„ 갖지 μ•ŠλŠ”λ‹€.

μ£Όμ˜ν•΄μ•Όν•  것이 Optional Requirements λŠ” Objective-C 와 μƒν˜Έ 운용(interoperates) 을 μœ„ν•œ κ²ƒμœΌλ‘œ, Protocol 의 Type 은 λ°˜λ“œμ‹œ @objc λ₯Ό μ΄μš©ν•΄ @objc Protocol둜 μ •μ˜ν•΄μ•Όν•œλ‹€. λ˜ν•œ Optional Requirements λ₯Ό μ μš©ν•  attributes λŠ” λ°˜λ“œμ‹œ @objcλ₯Ό μ΄μš©ν•΄ @objc attribute둜만 μ •μ˜λ  수 μžˆλ‹€.

λ§ˆμ§€λ§‰μœΌλ‘œ 이것이 Optionalμž„μ„ λ‚˜νƒ€λ‚΄κΈ° μœ„ν•΄ optional modifier 도 ν•¨κ»˜ μž‘μ„±ν•΄μ€˜μ•Όν•œλ‹€.

Syntax

@objc protocol SomeProtocol {
    @objc optional var mustBeSettable: Int { get set }
    @objc optional var doesNotNeedToBeSettable: Int { get }
    @objc optional func someTypeMethod() -> SomeType
    @objc optional init(someParameter: SomeType)
}

참고둜 Protocol 이 κ΅¬ν˜„ 의무λ₯Ό 갖지 μ•Šλ„λ‘ ν•˜λŠ” 방법은 Optional Protocol 외에도 Protocol Extensions κ°€ μžˆλ‹€. λ¬Όλ‘ , Optional Protocols 와 μž‘λ™ 방식은 λ‹€λ₯΄μ§€λ§Œ κΈ°λ³Έ κ΅¬ν˜„μ„ μ œκ³΅ν•˜λ©°, μ‚¬μš©μž μ •μ˜ κ΅¬ν˜„λ„ κ°€λŠ₯ν•˜κ²Œ ν•  뿐 μ•„λ‹ˆλΌ Class κ°€ μ•„λ‹Œ Structure λ‚˜ Enumeration μ—μ„œλ„ μ‚¬μš©ν•  수 μžˆλ‹€λŠ” μž₯점을 κ°–λŠ”λ‹€.

Optional Protocols 의 κ΅¬ν˜„ 의무 λ©΄μ œκ°€ μ™œ μœ„ν—˜ν•˜κ³  μ£Όμ˜ν•΄μ•Όν•˜λŠ”μ§€ μž μ‹œ ν›„ 4. Optional Protocols as Types μ—μ„œ μ†Œκ°œν•œλ‹€.

λ‹¨μˆœνžˆ Protocol 의 일뢀λ₯Ό Optional둜 μ •μ˜ν•˜λŠ” 것이 λͺ©μ μ΄λΌλ©΄ λ‹€μŒ 챕터인 Protocol Extensions λ₯Ό μ‚¬μš©ν•˜λŠ” 것이 쒋은 λŒ€μ•ˆμ΄ 될 수 μžˆλ‹€.

2. Examples

protocol Member {
    var name: String { get set }
    var age: Int { get set }
    optional var address: String { get }    // 'optional' can only be applied to members of an @objc protocol
}

optional을 μ‚¬μš©ν•˜λ €λ©΄ λ°˜λ“œμ‹œ Objective-C μ™€μ˜ interoperatesκ°€ ν•„μš”ν•˜λ‹€. λ”°λΌμ„œ @objc protocol의 member κ°€ λ˜μ–΄μ•Όν•˜λ―€λ‘œ Protocol μ •μ˜λ₯Ό λ³€κ²½ν•΄μ•Όν•œλ‹€.

@objc protocol Member {
    var name: String { get set }
    var age: Int { get set }
    optional var address: String { get }   // 'optional' requirements are an Objective-C compatibility feature; add '@objc'
}

Protocol μ •μ˜λ₯Ό @objc protocol둜 λ³€κ²½ν–ˆμ§€λ§Œ μ—¬μ „νžˆ μ—λŸ¬κ°€ λ°œμƒν•œλ‹€. optional을 μ‚¬μš©ν•˜λ €λ©΄ κ·Έ member μ—­μ‹œ @objc둜 marking λ˜μ–΄μ•Όν•œλ‹€.

@objc protocol Member {
    var name: String { get set }
    var age: Int { get set }
    @objc optional var address: String { get }
}

λ“œλ””μ–΄ μ •μƒμ μœΌλ‘œ μ •μ˜λ˜μ—ˆλ‹€. 즉, Swift 만 μ‚¬μš©ν•΄ μ½”λ“œλ₯Ό μž‘μ„±ν•˜λ”λΌλ„ Optional Requirements λ₯Ό μ‚¬μš©ν•˜λ €λ©΄ λ°˜λ“œμ‹œ @objc둜 μ •μ˜ν•΄μ•Όν•œλ‹€.


struct Teacher: Member {    // Non-class type 'Teacher' cannot conform to class protocol 'Member'
    var name: String
    var age: Int
    var address: String
}

Objective-C 와 μƒν˜Έ μš΄μš©ν•œλ‹€λŠ” 것은 이것이 Class이어야 함을 μ˜λ―Έν•œλ‹€. λ”°λΌμ„œ Structure 둜 μ •μ˜ν•  수 μ—†λ‹€.

class Teacher: Member {
    var name: String
    var age: Int
    var address: String
    init(name: String, age: Int, address: String) {
        self.name = name
        self.age = age
        self.address = address
    }
}

class Student: Member {
    var name: String
    var age: Int
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

Teacher λŠ” optional 을 포함해 name, age, address λ₯Ό λͺ¨λ‘ member 둜 κ°–λŠ”λ‹€. 반면, Student λŠ” optional 을 μ œμ™Έν•˜κ³  name, age 만 member 둜 κ°–λŠ”λ‹€. μ‹€μ œ 객체가 μ •μƒμ μœΌλ‘œ μž‘λ™λ˜λŠ”μ§€ ν™•μΈν•΄λ³΄μž.

let jamie = Teacher(name: "Jamie", age: 42, address: "μ„œμšΈμ‹œ 강남ꡬ")
let mike = Student(name: "Mike", age: 20)

var MemberList: [Member] = [jamie, mike]

for member in MemberList {
    switch member {
    case let manager as Teacher:
        print("Teacher name is \(manager.name), he(she) is \(manager.age) years old, and lives in \(manager.address).")
    case let student as Student:
        print("Student name is \(student.name), he(she) is \(student.age) years old.")
    default: break
    }
}
Teacher name is Jamie, he(she) is 42 years old, and lives in μ„œμšΈμ‹œ 강남ꡬ.
Student name is Mike, he(she) is 20 years old.

3. Optional Members make them Optional Types

μœ„ Examples 만 보면 ꡉμž₯히 μœ μš©ν•΄ 보인닀. ν•˜μ§€λ§Œ Optional Protocols λ₯Ό μ‚¬μš©ν•˜λŠ” 것은 맀우 μ‘°μ‹¬ν•΄μ•Όν•œλ‹€.

Protocol 은 직접 μ±„νƒν•˜λŠ” 것 뿐 μ•„λ‹ˆλΌ Protocol 을 Type 으둜 μ‚¬μš©ν•  수 μžˆμŒμ„ μ•žμ—μ„œ ν™•μΈν–ˆλ‹€. λ°”λ‘œ μ΄λ•Œ Optional Protocols λ₯Ό Types 둜 μ‚¬μš©ν•  λ•Œ μ™œ μœ„ν—˜ν•œμ§€ μ•Œμ•„λ³΄μž.

Optional Members are Optional Types

Optional Members λŠ” κ΅¬ν˜„ μ˜λ¬΄κ°€ μ—†κΈ° λ•Œλ¬Έμ— 이것을 Types 둜 μ‚¬μš©ν•  λ•Œ, ν•΄λ‹Ήν•˜λŠ” Members 의 Type 은 항상 Optional 이닀(Member protocol 의 경우 address member κ°€ 항상 Optional 이닀).

즉, @objc optional var something: Int { get }의 Type 은 Intκ°€ μ•„λ‹ˆλΌ Int?λ‹€.
λ§ˆμ°¬κ°€μ§€λ‘œ @objc optional func someFunc(num: Int) -> String의 Type 은 (Int) -> String이 μ•„λ‹ˆλΌ ((Int) -> String)?이닀.

for member in MemberList {
    userInformation(user: member)
    print("")
}

func userInformation(user: Member) {
    print(user.name)
    print(user.age)
    print(user.address as Any)
}
Jamie
42
Optional("μ„œμšΈμ‹œ 강남ꡬ")

Mike
20
nil
  • μœ„ μ˜ˆμ œμ—μ„œ Teacher, Student λŠ” switch λ₯Ό 톡해 Type Casting을 ν–ˆκΈ° λ•Œλ¬Έμ— Member Protocol 을 μ±„νƒν•œ Teacher, Student Typesμž„μ„ λͺ…ν™•νžˆ μ•Œ 수 μžˆλ‹€. λ”°λΌμ„œ Teacher Class λŠ” address λ₯Ό String Type 을 λͺ…λ°±νžˆ κ°–κ³  μžˆμœΌλ―€λ‘œ Optional 이 μ•„λ‹ˆλ‹€. λ˜ν•œ, Student Class λŠ” address λ₯Ό κ°–κ³  μžˆμ§€ μ•ŠμŒμ„ ν™•νžνžˆ μ•Œ 수 μžˆλ‹€.
  • ν•˜μ§€λ§Œ 이번 μ˜ˆμ œλŠ” Member λ₯Ό Type 으둜 μ‚¬μš©ν–ˆλ‹€. 즉, Member Protocol 을 λ”°λ₯΄μ§€λ§Œ Optional 이기 λ•Œλ¬Έμ— Class κ°€ κ΅¬ν˜„ ν–ˆλŠ”μ§€ μ—¬λΆ€λ₯Ό μ•Œ 수 μ—†λ‹€. κ·Έλ ‡κΈ° λ•Œλ¬Έμ— Optional("μ„œμšΈμ‹œ 강남ꡬ")κ°€ 좜λ ₯λ˜λŠ” 것이닀. λ”°λΌμ„œ Optional Protocol 을 μ±„νƒν•˜λŠ” Classes λ₯Ό μ‚¬μš©ν•  λ•ŒλŠ” Protocols λ₯Ό Type 으둜 μ‚¬μš©ν•˜λŠ” λŒ€μ‹  μ μ ˆν•œ Type 으둜 Downcastingν•˜κ±°λ‚˜ Optional Chaining으둜 μ ‘κ·Όν•΄μ•Όν•œλ‹€.

4. Optional Protocols as Types

μœ„μ—μ„œ μ‚΄νŽ΄λ³Έ κ²ƒμ²˜λŸΌ Optional Protocols λ₯Ό Type 으둜 μ‚¬μš©ν•  λ•ŒλŠ” μ£Όμ˜ν•΄μ•Όν•œλ‹€. 이것을 μ’€ 더 극단적인 μΌ€μ΄μŠ€λ₯Ό μ΄μš©ν•΄ 더 깊게 μ•Œμ•„λ³΄μž.

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

CounterDataSource λŠ” increment λΌλŠ” Optional Method 와 fixedIncrement λΌλŠ” Optional Property λ₯Ό κ°–κ³  있으며, λ‘˜ λ‹€ Optional Membersλ‹€. 즉, Protocol 을 μ±„νƒν•˜λ”λΌλ„ μ•„λ¬΄λŸ° κ΅¬ν˜„λ„ ν•˜μ§€ μ•Šμ•˜μ„ κ°€λŠ₯성이 μ‘΄μž¬ν•œλ‹€.

이런 μš”κ΅¬μ‚¬ν•­μ„ μ€€μˆ˜ν•˜λŠ” Class λ₯Ό λ§Œλ“œλŠ” 것이 κΈ°μˆ μ μœΌλ‘œλŠ” κ°€λŠ₯ν•˜μ§€λ§Œ, 쒋은 방법은 μ•„λ‹ˆλ‹€. 이 Protocol 을 μ‚¬μš©ν•˜μ§€ μ•Šκ³  ν•΄λ‹Ή μš”κ΅¬μ‚¬ν•­μ„ μ€€μˆ˜ν•˜λŠ” Class 의 κ΅¬ν˜„μ„ ν•  수 μžˆλ‹€.

이 Protocol 을 Class κ°€ 직접 μ±„νƒν•˜μ§€ 말고 Type 으둜 μ‚¬μš©ν•΄λ³΄μž.

class Counter {
    var count = 0
    var dataSource: CounterDataSource
    func increment() {
        if let amount = dataSource.increment?(forCount: count) {
            count += amount
        } else if let amount = dataSource.fixedIncrement {
            count += amount
        }
    }
    init(dataSource: CounterDataSource) {
        self.dataSource = dataSource
    }
    convenience init(count: Int, dataSource: CounterDataSource) {
        self.init(dataSource: dataSource)
        self.count = count
    }
}

그런데 dataSource κ°€ Type 으둜 μ‚¬μš©ν•˜λŠ” CounterDataSource Protocol 은 λͺ¨λ“  Members λ₯Ό κ΅¬ν˜„ν•˜μ§€ μ•Šμ•„λ„ λ˜λ―€λ‘œ, μ‹€μ œ μ•„λ¬΄λŸ° κ΅¬ν˜„λ„ ν•˜μ§€ μ•Šμ•˜μ„ κ°€λŠ₯성이 μ‘΄μž¬ν•œλ‹€. λ”°λΌμ„œ CounterDataSource κ°€ μ•„λ‹Œ CounterDataSource?λ₯Ό μ‚¬μš©ν•˜λŠ” 것이 μ ν•©ν•˜λ‹€.

class Counter {
    var count = 0
    var dataSource: CounterDataSource?
    func increment() {
        if let amount = dataSource?.increment?(forCount: count) {
            count += amount
        } else if let amount = dataSource?.fixedIncrement {
            count += amount
        }
    }
}
  • increment(forCount:) ν˜ΈμΆœμ„ 보자. 첫 번째 ?은 CounterDataSource? Typeμ΄λ―€λ‘œ μ‚¬μš©λ˜μ—ˆκ³ , 두 번째 ?은 increment κ°€ Optional Memberμ΄λ―€λ‘œ κ΅¬ν˜„ μ—¬λΆ€λ₯Ό μ•Œ 수 μ—†μ–΄ μ‚¬μš©λ˜μ—ˆλ‹€. 즉, μ΄λ ‡κ²Œ Optional Chaining을 μ΄μš©ν•΄ μ ‘κ·Όν•΄μ•Ό μ•ˆμ „ν•˜λ‹€.
  • ν•¨μˆ˜μ—μ„œ if clause 와 else clause μ—μ„œ let amountκ°€ κ°€λŠ₯ν•œ μ΄μœ λŠ” increment(forCount:)와 fixedIncrement λͺ¨λ‘ Optional Types μ΄λ―€λ‘œ Optional Binding이 κ°€λŠ₯ν•œ 것이닀.


Counter λ₯Ό μž‘λ™μ‹œμΌœλ³΄μž.

var counter = Counter()

for _ in 1...4 {
    counter.increment()
    print(counter.count)
}
0
0
0
0

var dataSource: CounterDataSource?κ°€ nil이기 λ•Œλ¬Έμ— count = 0 에 μ˜ν•΄ 0에 맀번 0을 λ”ν•˜λ―€λ‘œ λͺ¨λ‘ 0이닀.


dataSource 에 ν• λ‹Ήν•  CounterDataSource Type 의 Class λ₯Ό ν•˜λ‚˜ λ§Œλ“€μ–΄λ³΄μž.

class ThreeSource: NSObject, CounterDataSource {
    let fixedIncrement = 3
}

μ΄λ²ˆμ—λŠ” 이 Class λ₯Ό var dataSource: CounterDataSource?에 ν• λ‹Ή ν›„ Counter λ₯Ό μž‘λ™μ‹œμΌœλ³΄μž.

var counter = Counter()
counter.dataSource = ThreeSource()

for _ in 1...4 {
    counter.increment()
    print(counter.count)
}
3
6
9
12


μ΄λ²ˆμ—λŠ” fixedIncrementκ°€ μ•„λ‹Œ increment(forCount:)λ₯Ό μ΄μš©ν•΄ Counter λ₯Ό μž‘λ™μ‹œμΌœλ³΄μž.

class TowardsZeroSource: NSObject, CounterDataSource {
    func increment(forCount count: Int) -> Int {
        if count == 0 {
            return 0
        } else if count < 0 {
            return 1
        } else {
            return -1
        }
    }
}
var counter = Counter()
counter.count = -4
counter.dataSource = TowardsZeroSource()

Array(1...5).forEach { _ in
    counter.increment()
    print(counter.count)
}
-3
-2
-1
0
0

13. Protocol Extensions πŸ‘©β€πŸ’»

1. Protocol Extensions

Protocol 은 이것을 μ€€μˆ˜ν•˜λŠ” Type 에 κΈ°λŠ₯을 μ œκ³΅ν•˜κΈ° μœ„ν•΄ Extensions 을 μ΄μš©ν•΄ Computed Properties, Initializers, Methods, Subscripts의 κΈ°λ³Έ κ΅¬ν˜„μ„ 적합성을 μ€€μˆ˜ν•˜λŠ” Type 에 μΆ”κ°€ν•  수 μžˆλ‹€.

μ΄λŠ” Global Function 을 μΆ”κ°€ν•˜κ±°λ‚˜ μΆ”κ°€λœ Protocol μ±„νƒμœΌλ‘œ 인해 κ°œλ³„ Type λ§ˆλ‹€ 적합성을 λ‹€μ‹œ μΆ”κ°€ν•˜λŠ” λŒ€μ‹  Protocol Extensionsλ₯Ό μ‚¬μš©ν•΄ κΈ°λŠ₯을 μ œκ³΅ν•  수 μžˆλ‹€.

Protocol Extensions이 κΈ°λ³Έ κ΅¬ν˜„μ„ λ°˜λ“œμ‹œ μ œκ³΅ν•˜κΈ° λ•Œλ¬Έμ— 이 Protocols λ₯Ό μ±„νƒν•˜λŠ” Types λŠ” 적합성을 λ§Œμ‘±ν•˜κΈ° μœ„ν•œ κ΅¬ν˜„μ„ κ°•μš”λ°›μ§€ μ•ŠμœΌλ©°, κΈ°λŠ₯의 κ΅¬ν˜„μ΄ 보μž₯λ˜λ―€λ‘œ Optional Protocols 와 λ‹€λ₯΄κ²Œ Optional Chaining 없이 호좜될 수 μžˆλ‹€.


μ˜μ‚¬ λ‚œμˆ˜(pseudorandom number) 생성기λ₯Ό λ‹€μ‹œ λ– μ˜¬λ €λ³΄μž.

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()
Array(1...5).forEach { _ in print("Here's a random number: \(generator.random())") }
Here's a random number: 0.23928326474622771
Here's a random number: 0.4782664609053498
Here's a random number: 0.7172496570644719
Here's a random number: 0.956232853223594
Here's a random number: 0.19521604938271606

그런데 이 μ˜μ‚¬ λ‚œμˆ˜ 생성기λ₯Ό μ΄μš©ν•œ Bool을 λ°˜ν™˜ν•˜λŠ” ν•¨μˆ˜λ₯Ό μΆ”κ°€ν•˜κ³  μ‹Άλ‹€λ©΄ μ–΄λ–»κ²Œ ν•΄μ•Όν• κΉŒ?

μ•žμ—μ„œ Protocol 의 κΈ°λŠ₯을 ν™•μž₯ν•˜κ³ μž ν•  λ•Œ Protocol Inheritance λ₯Ό μ‚¬μš©ν•΄ λ‹€μŒκ³Ό 같이 κΈ°λŠ₯을 μΆ”κ°€ν–ˆλ‹€.

protocol RandomBoolGenerator: RandomNumberGenerator {
    func randomBool() -> Bool
}

extension LinearCongruentialGenerator: RandomBoolGenerator {
    func randomBool() -> Bool {
        random() > 0.5
    }
}
Array(1...5).forEach { _ in print("Here's a random Boolean: \(generator.randomBool())") }
Here's a random Boolean: false
Here's a random Boolean: false
Here's a random Boolean: true
Here's a random Boolean: true
Here's a random Boolean: false

상속을 μ΄μš©ν•  경우 μš°λ¦¬λŠ” λ‹€μŒκ³Ό 같이 3가지λ₯Ό κ΅¬ν˜„ν•΄μ•Όν•œλ‹€.

  1. RandomNumberGenerator λ₯Ό μƒμ†ν•œ RandomBoolGenerator Protocol μ •μ˜.
  2. Extension 을 μ΄μš©ν•΄ LinearCongruentialGenerator Class 에 RandomBoolGenerator λ₯Ό μΆ”κ°€λ‘œ 채택.
  3. μ±„νƒλœ RandomBoolGenerator Protocol 을 μ€€μˆ˜ν•˜λ„λ‘ μ •μ˜.


그런데 LinearCongruentialGenerator Class λŠ” 이미 RandomNumberGenerator Protocol 을 μ€€μˆ˜ν•˜κ³ μžˆλ‹€. λ”°λΌμ„œ Protocol Extensionsλ₯Ό μ‚¬μš©ν•˜λ©΄ Protocol 을 μ€€μˆ˜ν•˜λŠ” Type 에 Protocol 자체λ₯Ό ν™•μž₯ν•¨μœΌλ‘œμ¨ κΈ°λŠ₯을 μ‰½κ²Œ μΆ”κ°€ν•  수 μžˆλ‹€.

extension RandomNumberGenerator {
    func randomBool() -> Bool {
        random() > 0.5
    }
}
Array(1...5).forEach { _ in print("Here's a random Boolean: \(generator.randomBool())") }
Here's a random Boolean: false
Here's a random Boolean: false
Here's a random Boolean: true
Here's a random Boolean: true
Here's a random Boolean: false

Protocol 을 ν™•μž₯ν•˜λŠ” 것이라 해도 Extension 은 Protocol 이 μ•„λ‹ˆλ―€λ‘œ κ΅¬ν˜„μ„ λ―Έλ£° 수 μ—†λ‹€.

extension RandomNumberGenerator {
    func randomBool() -> Bool     // Expected '{' in body of function declaration
}

λ”°λΌμ„œ Protocol 을 ν™•μž₯함과 λ™μ‹œμ— κ΅¬ν˜„μ„ λ°˜λ“œμ‹œ μ œκ³΅ν•΄μ•Όν•œλ‹€.

2. Providing Default Implementations

μœ„μ—μ„œ μ‚΄νŽ΄λ³΄μ•˜λ“―μ΄

protocol RandomNumberGenerator {
    func random() -> Double
}

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

RandomNumberGenerator λ₯Ό ν™•μž₯ν•˜κ³ , RandomNumberGenerator λ₯Ό 채택해 λ‹€μŒκ³Ό 같이 LinearCongruentialGenerator 에 적합성을 μΆ”κ°€ν•˜λ©΄

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

이 LinearCongruentialGenerator Class λŠ” ν™•μž₯된 RandomNumberGenerator의 κΈ°λ³Έ κ΅¬ν˜„μ„ λ°›μ•„ λ‹€μŒκ³Ό 같은 ν˜•νƒœμΈ κ²ƒμ²˜λŸΌ μž‘λ™ν•  것이닀.

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
    }

    func randomBool() -> Bool {
        random() > 0.5
    }
}

그런데 μ΄κ²ƒμ˜ κ΅¬ν˜„μ„ λ³€κ²½ν•˜κ³  μ‹Άλ‹€λ©΄ μ–΄λ–»κ²Œ ν•΄μ•Όν• κΉŒ? Default 둜 제곡된 이 κ΅¬ν˜„μ„ λ‹€λ₯΄κ²Œ ν•˜κ³  μ‹Άλ‹€λ©΄ μ–΄λ–»κ²Œ ν•΄μ•Όν• κΉŒ?


λ§Œμ•½ 이것을 Protocol Extensions κ°€ μ•„λ‹Œ Protocols 둜 μ •μ˜ν–ˆλ‹€λ©΄ 맀번 RandomBoolGenerator Protocol 을 채택할 λ•Œ 적합성 κ΅¬ν˜„μ„ ν•΄μ•Όν•˜λ―€λ‘œ ν•„μš”ν•œ Type 에 맞게 κ΅¬ν˜„μ„ λ³€κ²½ν•˜λ©΄ λœλ‹€.

extension LinearCongruentialGenerator: RandomBoolGenerator {
    func randomBool() -> Bool {
        random() > 0.8
    }
}


반면 Extensions 은 κ΅¬ν˜„μ˜ μ˜λ¬΄κ°€ μ—†κΈ° λ•Œλ¬Έμ— κ·Έλƒ₯ RandomNumberGenerator Protocol 을 μ±„νƒν•œ ν›„ Extensions κ°€ κΈ°λ³Έ κ΅¬ν˜„μ„ μ œκ³΅ν•˜κΈ°λ‘œ ν•œ κΈ°λŠ₯을 직접 κ΅¬ν˜„ν•˜λ©΄ λœλ‹€.

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
    }
    
    func randomBool() -> Bool {
        random() > 0.8
    }
}

그러면 Extensions 은 κΈ°λ³Έ κ΅¬ν˜„μ„ μ œκ³΅ν•  뿐 μ–΄λ– ν•œ κ΅¬ν˜„λ„ κ°•μš”ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— Protocol 의 κΈ°λŠ₯을 μ§μ ‘μ μœΌλ‘œ μˆ˜ν–‰ν•˜μ§€ μ•ŠλŠ”λ‹€. λ”°λΌμ„œ randomBool()은 LinearCongruentialGenerator Class 의 κ΅¬ν˜„μ— μ˜ν•΄ Overriding λœλ‹€.

let generator = LinearCongruentialGenerator()
Array(1...5).forEach { _ in print("Here's a random Boolean: \(generator.randomBool())") }
Here's a random Boolean: false
Here's a random Boolean: false
Here's a random Boolean: false
Here's a random Boolean: true
Here's a random Boolean: false

이둜써 λ³„λ„μ˜ κ΅¬ν˜„ 변경이 ν•„μš”ν•˜μ§€ μ•Šμ€ 경우 RandomBoolGenerator Protocol 을 μ±„νƒν•˜λŠ” 것 만으둜 μš°λ¦¬λŠ”

func randomBool() -> Bool {
    random() > 0.5
}

λ₯Ό κΈ°λ³Έ κ΅¬ν˜„μœΌλ‘œ μ‚¬μš©ν•  수 있으며, ν•„μš”μ‹œ 이λ₯Ό 직접 κ΅¬ν˜„ν•΄ Overriding μ‹œμΌœ μ‚¬μš©ν•  수 μžˆλ‹€.

3. Adding Constraints to Protocol Extensions (where)

Conditionally Conforming to a Protocol (where) μ—μ„œ 이미 Protocol 에 whereλ₯Ό μ΄μš©ν•΄ constraintsλ₯Ό μΆ”κ°€ν•˜λŠ” 것을 ν™•μΈν–ˆλ‹€.

extension Array: TextRepresentable where Element: TextRepresentable {
    var textualDescription: String {
        let itemsAsText = self.map { $0.textualDescription }
        return "[" + itemsAsText.joined(separator: ", ") + "]"
    }
}

μ΄λ²ˆμ—” 이λ₯Ό μ’€ 더 μΌλ°˜ν™” μ‹œμΌœ Collection 에 κΈ°λŠ₯을 μΆ”κ°€ν•΄λ³΄μž. 단, 정상적인 λ™μž‘μ„ μœ„ν•΄ Element 이 Equatable 에 μ ν•©ν•œ 경우둜 μ œν•œν•˜λ„λ‘ν•œλ‹€.

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

μœ„ Protocol 은 λͺ¨λ“  Element κ°€ Equatable을 λ§Œμ‘±ν•˜λŠ” Collection μ—κ²Œ 자기 μžμ‹ μ˜ λͺ¨λ“  Element κ°€ λ™μΌν•œμ§€λ₯Ό νŒλ³„ ν›„ Boolean 을 λ°˜ν™˜ν•˜λŠ” allEqual() λ©”μ„œλ“œλ₯Ό μΆ”κ°€ν•œλ‹€.

let equalNumbers = [100, 100, 100, 100, 100]
let differentNumbers = [100, 100, 200, 100, 200]

print(equalNumbers.allEqual())      // true
print(differentNumbers.allEqual())  // false


μœ„ μ½”λ“œλŠ” Protocol Extensions 와 constraints λ₯Ό μ΄μš©ν•΄ κΈ°λŠ₯을 ν™•μž₯ν•˜λŠ” 것을 μ–΄λ–€μ‹μœΌλ‘œ ν™œμš©ν•  수 μžˆλŠ”κ°€ μ„€λͺ…ν•˜κΈ° μœ„ν•œ κ²ƒμœΌλ‘œ μ‹€μ œ μœ„μ™€ 같이 λ‹¨μˆœν•œ μ½”λ“œλŠ” λ”°λ‘œ κ΅¬ν˜„ν•  ν•„μš” 없이 Swift κ°€ 이미 λͺ¨λ“ κ±Έ μ œκ³΅ν•˜κ³ μžˆλ‹€.

Higher-order Functionsλ₯Ό μ‚¬μš©ν•˜λ©΄ Collection 의 λͺ¨λ“  값이 같은지 λ˜λŠ” μ–΄λ–€ 값을 ν¬ν•¨ν•˜κ³  μžˆλŠ”μ§€λ₯Ό μ†μ‰½κ²Œ μ²˜λ¦¬ν•  수 μžˆλ‹€.

  • Swift λŠ” allSatisfy와 containsλ₯Ό μ΄μš©ν•΄ μ†μ‰½κ²Œ μ²˜λ¦¬ν•  수 μžˆλ‹€.
print(equalNumbers.allSatisfy { $0 == equalNumbers[0] })            // true
print(differentNumbers.allSatisfy { $0 == differentNumbers[0] })    // false

print(equalNumbers.contains { $0 == 200 })                          // false
print(differentNumbers.contains { $0 == 200 })                      // true
  • TypeScript λŠ” every와 some을 μ΄μš©ν•΄ μ†μ‰½κ²Œ μ²˜λ¦¬ν•  수 μžˆλ‹€.
const equalNumbers: Array<number> = [100, 100, 100, 100, 100]
const differentNumbers: Array<number> = [100, 100, 200, 100, 200]

console.log(equalNumbers.every(v => v === equalNumbers[0]))     // true
console.log(differentNumbers.every(v => v === equalNumbers[0])) // false

console.log(equalNumbers.some(v => v === 200))                  // false
console.log(differentNumbers.some(v => v === 200))              // true 




Reference

  1. β€œProtocols.” The Swift Programming Language Swift 5.7. accessed Feb. 20, 2023, Swift Docs Chapter 21 - Protocols.