Protocols Swift


Protocols

프로토콜은 특정 작업이나 기능의 일부를 충족하는 메소드, 프로퍼티, 다른 요구사항의 설계도를 정의하는 것이다. 프로토콜은 클래스, 구조체, 또는 열거형으로 차용되어 이들 요구사항의 실제 구현을 제공한다.Any type that satisfies the requirements of a protocol is said to conform to that protocol.

In addition to specifying requirements that conforming types must implement, you can extend a protocol to implement some of these requirements or to implement additional functionality that conforming types can take advantage of.

프로토콜 문법

프로토콜은 클래스, 구조체, 열거형과 아주 비슷하게 정의된다.

protocol SomeProtocol {
  // protocol definition goes here
}

Custom types state that they adopt a particular protocol by placing the protocol’s name after the type’s name, separated by a colon, as part of their definition. Multiple protocols can be listed, and are separated by commas:

struct SomeStructure: FirstProtocol, AnotherProtocol {
    // structure definition goes here
}
If a class has a superclass, list the superclass name before any protocols it adopts, followed by a comma:

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

프로퍼티 요구사항

A protocol can require any conforming type to provide an instance property or type property with a particular name and type. The protocol doesn’t specify whether the property should be a stored property or a computed property—it only specifies the required property name and type. The protocol also specifies whether each property must be gettable or gettable and settable.

If a protocol requires a property to be gettable and settable, that property requirement can’t be fulfilled by a constant stored property or a read-only computed property. If the protocol only requires a property to be gettable, the requirement can be satisfied by any kind of property, and it’s valid for the property to be also settable if this is useful for your own code.

프로퍼티 요구사항은 항당 변수 프로퍼티, 로 정의되며 var 키워드로 지정된다. 형식 정의 이후에 {get set}을 씀으로서 겟가능, 셋 가능여부를 지정한다.

protocol SomeProtocol {
    var mustBeSettable: Int { get set }
    var doesNotNeedToBeSettable: Int { get }
}
Always prefix type property requirements with the static keyword when you define them in a protocol. This rule pertains even though type property requirements can be prefixed with the class or static keyword when implemented by a class:

protocol AnotherProtocol {
    static var someTypeProperty: Int { get set }
}
Here’s an example of a protocol with a single instance property requirement:

protocol FullyNamed {
    var fullName: String { get }
}
The FullyNamed protocol requires a conforming type to provide a fully-qualified name. The protocol doesn’t specify anything else about the nature of the conforming type—it only specifies that the type must be able to provide a full name for itself. The protocol states that any FullyNamed type must have a gettable instance property called fullName, which is of type String.

Here’s an example of a simple structure that adopts and conforms to the FullyNamed protocol:

struct Person: FullyNamed {
    var fullName: String
}
let john = Person(fullName: "John Appleseed")
// john.fullName is "John Appleseed"
This example defines a structure called Person, which represents a specific named person. It states that it adopts the FullyNamed protocol as part of the first line of its definition.

Each instance of Person has a single stored property called fullName, which is of type String. This matches the single requirement of the FullyNamed protocol, and means that Person has correctly conformed to the protocol. (Swift reports an error at compile-time if a protocol requirement is not fulfilled.)

Here’s a more complex class, which also adopts and conforms to the FullyNamed protocol:

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"
This class implements the fullName property requirement as a computed read-only property for a starship. Each Starship class instance stores a mandatory name and an optional prefix. The fullName property uses the prefix value if it exists, and prepends it to the beginning of name to create a full name for the starship.

메소드 요구사항들

Protocols can require specific instance methods and type methods to be implemented by conforming types. These methods are written as part of the protocol’s definition in exactly the same way as for normal instance and type methods, but without curly braces or a method body. Variadic parameters are allowed, subject to the same rules as for normal methods. Default values, however, can’t be specified for method parameters within a protocol’s definition.

As with type property requirements, you always prefix type method requirements with the static keyword when they’re defined in a protocol. This is true even though type method requirements are prefixed with the class or static keyword when implemented by a class:

protocol SomeProtocol {
    static func someTypeMethod()
}
The following example defines a protocol with a single instance method requirement:

protocol RandomNumberGenerator {
    func random() -> Double
}
This protocol, RandomNumberGenerator, requires any conforming type to have an instance method called random, which returns a Double value whenever it’s called. Although it’s not specified as part of the protocol, it’s assumed that this value will be a number from 0.0 up to (but not including) 1.0.

The RandomNumberGenerator protocol doesn’t make any assumptions about how each random number will be generated—it simply requires the generator to provide a standard way to generate a new random number.

Here’s an implementation of a class that adopts and conforms to the RandomNumberGenerator protocol. This class implements a pseudorandom number generator algorithm known as a linear congruential generator:

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"

메소드 요구사항 변경하기

간혹 메소드에대해 이를 가진 인스턴스에서 수정할 필요가 있을 수 있다. 값 형식에 대한 인스턴스 메소드 (구조체, 열거형) 은 메소드의 func 키워드 앞에 mutating 을 지정하여 변경가능함을 지정했다. This process is described in Modifying Value Types from Within Instance Methods.

If you define a protocol instance method requirement that is intended to mutate instances of any type that adopts the protocol, mark the method with the mutating keyword as part of the protocol’s definition. This enables structures and enumerations to adopt the protocol and satisfy that method requirement.

NOTE

If you mark a protocol instance method requirement as mutating, you don’t need to write the mutating keyword when writing an implementation of that method for a class. The mutating keyword is only used by structures and enumerations.

The example below defines a protocol called Togglable, which defines a single instance method requirement called toggle. As its name suggests, the toggle() method is intended to toggle or invert the state of any conforming type, typically by modifying a property of that type.

The toggle() method is marked with the mutating keyword as part of the Togglable protocol definition, to indicate that the method is expected to mutate the state of a conforming instance when it’s called:

protocol Togglable {
    mutating func toggle()
}
If you implement the Togglable protocol for a structure or enumeration, that structure or enumeration can conform to the protocol by providing an implementation of the toggle() method that is also marked as mutating.

The example below defines an enumeration called OnOffSwitch. This enumeration toggles between two states, indicated by the enumeration cases on and off. The enumeration’s toggle implementation is marked as mutating, to match the Togglable protocol’s requirements:

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()
// lightSwitch is now equal to .on

이니셜라이져 요구사항

Protocols can require specific initializers to be implemented by conforming types. You write these initializers as part of the protocol’s definition in exactly the same way as for normal initializers, but without curly braces or an initializer body:

protocol SomeProtocol {
    init(someParameter: Int)
}

프로토콜 이니셜라이져 요구사항의 클래스 구현

You can implement a protocol initializer requirement on a conforming class as either a designated initializer or a convenience initializer. In both cases, you must mark the initializer implementation with the required modifier:

class SomeClass: SomeProtocol {
    required init(someParameter: Int) {
        // initializer implementation goes here
    }
}
The use of the required modifier ensures that you provide an explicit or inherited implementation of the initializer requirement on all subclasses of the conforming class, such that they also conform to the protocol.

For more information on required initializers, see Required Initializers.

NOTE

You don’t need to mark protocol initializer implementations with the required modifier on classes that are marked with the final modifier, because final classes can’t subclassed. For more about the final modifier, see Preventing Overrides.

If a subclass overrides a designated initializer from a superclass, and also implements a matching initializer requirement from a protocol, mark the initializer implementation with both the required and override modifiers:

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

실패가능 이니셜라이져 요구사항

Protocols can define failable initializer requirements for conforming types, as defined in Failable Initializers.

A failable initializer requirement can be satisfied by a failable or nonfailable initializer on a conforming type. A nonfailable initializer requirement can be satisfied by a nonfailable initializer or an implicitly unwrapped failable initializer.

형식으로서 프로토콜

Protocols don’t actually implement any functionality themselves. Nonetheless, any protocol you create will become a fully-fledged type for use in your code.

Because it’s a type, you can use a protocol in many places where other types are allowed, including:

As a parameter type or return type in a function, method, or initializer
As the type of a constant, variable, or property
As the type of items in an array, dictionary, or other container
NOTE

Because protocols are types, begin their names with a capital letter (such as FullyNamed and RandomNumberGenerator) to match the names of other types in Swift (such as Int, String, and Double).

Here’s an example of a protocol used as a 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(generator.random() * Double(sides)) + 1
  }
}

This example defines a new class called Dice, which represents an n-sided dice for use in a board game. Dice instances have an integer property called sides, which represents how many sides they have, and a property called generator, which provides a random number generator from which to create dice roll values.

The generator property is of type RandomNumberGenerator. Therefore, you can set it to an instance of any type that adopts the RandomNumberGenerator protocol. Nothing else is required of the instance you assign to this property, except that the instance must adopt the RandomNumberGenerator protocol.

Dice also has an initializer, to set up its initial state. This initializer has a parameter called generator, which is also of type RandomNumberGenerator. You can pass a value of any conforming type in to this parameter when initializing a new Dice instance.

Dice provides one instance method, roll, which returns an integer value between 1 and the number of sides on the dice. This method calls the generator’s random() method to create a new random number between 0.0 and 1.0, and uses this random number to create a dice roll value within the correct range. Because generator is known to adopt RandomNumberGenerator, it’s guaranteed to have a random() method to call.

Here’s how the Dice class can be used to create a six-sided dice with a LinearCongruentialGenerator instance as its random number generator:

var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
for _ in 1...5 {
    print("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 is a design pattern that enables a class or structure to hand off (or delegate) some of its responsibilities to an instance of another type. This design pattern is implemented by defining a protocol that encapsulates the delegated responsibilities, such that a conforming type (known as a delegate) is guaranteed to provide the functionality that has been delegated. Delegation can be used to respond to a particular action, or to retrieve data from an external source without needing to know the underlying type of that source.

The example below defines two protocols for use with dice-based board games:

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)
}
The DiceGame protocol is a protocol that can be adopted by any game that involves dice.

The DiceGameDelegate protocol can be adopted to track the progress of a DiceGame. To prevent strong reference cycles, delegates are declared as weak references. For information about weak references, see Strong Reference Cycles Between Class Instances. Marking the protocol as class-only lets the SnakesAndLadders class later in this chapter declare that its delegate must use a weak reference. A class-only protocol is marked by its inheritance from AnyObject as discussed in Class-Only Protocols.

Here’s a version of the Snakes and Ladders game originally introduced in Control Flow. This version is adapted to use a Dice instance for its dice-rolls; to adopt the DiceGame protocol; and to notify a DiceGameDelegate about its progress:

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?
    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:
                break gameLoop
            case let newSquare where newSquare > finalSquare:
                continue gameLoop
            default:
                square += diceRoll
                square += board[square]
            }
        }
        delegate?.gameDidEnd(self)
    }
}
For a description of the Snakes and Ladders gameplay, see Break.

This version of the game is wrapped up as a class called SnakesAndLadders, which adopts the DiceGame protocol. It provides a gettable dice property and a play() method in order to conform to the protocol. (The dice property is declared as a constant property because it doesn’t need to change after initialization, and the protocol only requires that it must be gettable.)

The Snakes and Ladders game board setup takes place within the class’s init() initializer. All game logic is moved into the protocol’s play method, which uses the protocol’s required dice property to provide its dice roll values.

Note that the delegate property is defined as an optional DiceGameDelegate, because a delegate isn’t required in order to play the game. Because it’s of an optional type, the delegate property is automatically set to an initial value of nil. Thereafter, the game instantiator has the option to set the property to a suitable delegate. Because the DiceGameDelegate protocol is class-only, you can declare the delegate to be weak to prevent reference cycles.

DiceGameDelegate provides three methods for tracking the progress of a game. These three methods have been incorporated into the game logic within the play() method above, and are called when a new game starts, a new turn begins, or the game ends.

Because the delegate property is an optional DiceGameDelegate, the play() method uses optional chaining each time it calls a method on the delegate. If the delegate property is nil, these delegate calls fail gracefully and without error. If the delegate property is non-nil, the delegate methods are called, and are passed the SnakesAndLadders instance as a parameter.

This next example shows a class called DiceGameTracker, which adopts the DiceGameDelegate protocol:

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)-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")
    }
}
DiceGameTracker implements all three methods required by DiceGameDelegate. It uses these methods to keep track of the number of turns a game has taken. It resets a numberOfTurns property to zero when the game starts, increments it each time a new turn begins, and prints out the total number of turns once the game has ended.

The implementation of gameDidStart(_:) shown above uses the game parameter to print some introductory information about the game that is about to be played. The game parameter has a type of DiceGame, not SnakesAndLadders, and so gameDidStart(_:) can access and use only methods and properties that are implemented as part of the DiceGame protocol. However, the method is still able to use type casting to query the type of the underlying instance. In this example, it checks whether game is actually an instance of SnakesAndLadders behind the scenes, and prints an appropriate message if so.

The gameDidStart(_:) method also accesses the dice property of the passed game parameter. Because game is known to conform to the DiceGame protocol, it’s guaranteed to have a dice property, and so the gameDidStart(_:) method is able to access and print the dice’s sides property, regardless of what kind of game is being played.

Here’s how DiceGameTracker looks in action:

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-sided dice
// Rolled a 3
// Rolled a 5
// Rolled a 4
// Rolled a 5
// The game lasted for 4 turns

익스텐션에 프로토콜 컨포먼스 추가하기

You can extend an existing type to adopt and conform to a new protocol, even if you don’t have access to the source code for the existing type. Extensions can add new properties, methods, and subscripts to an existing type, and are therefore able to add any requirements that a protocol may demand. For more about extensions, see Extensions.

NOTE

Existing instances of a type automatically adopt and conform to a protocol when that conformance is added to the instance’s type in an extension.

For example, this protocol, called TextRepresentable, can be implemented by any type that has a way to be represented as text. This might be a description of itself, or a text version of its current state:

protocol TextRepresentable {
    var textualDescription: String { get }
}
The Dice class from above can be extended to adopt and conform to TextRepresentable:

extension Dice: TextRepresentable {
    var textualDescription: String {
        return "A \(sides)-sided dice"
    }
}
This extension adopts the new protocol in exactly the same way as if Dice had provided it in its original implementation. The protocol name is provided after the type name, separated by a colon, and an implementation of all requirements of the protocol is provided within the extension’s curly braces.

Any Dice instance can now be treated as TextRepresentable:

let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
print(d12.textualDescription)
// Prints "A 12-sided dice"
Similarly, the SnakesAndLadders game class can be extended to adopt and conform to the TextRepresentable protocol:

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

조건부 프로토콜 컨포밍

제네릭형식은 특정 프로토콜을 따라야 한다와 같은 특정 조건이 만족해져야 할 때가 있다. 형식 확장시 제약을 리스팅함으로서 조건적으로 따르는 제네릭형식을 만들 수 있다. 제네릭 where  절을 프로토콜 이름 뒤에 씀으로서 이를 처리할 수 있다. For more about generic where clauses, see Generic Where Clauses.

다음 익스텐션은 TextRepresentable 프로토콜을 따르는 배열 인스턴스를 만드는 것으로 형식의 엘리먼트는 TextRepresentable 을 따르고 있다.

extension Array: TextRepresentable where Element: TextRepresentable {
  var textDescription: String {
    let itemsAsText = self.map { $0.textualDescription }
    return "["+itemAsText.joined(separator: ", ") + "]"
  }
}
let myDice = [d6, d12]
print(myDice.textualDescription)
// Prints "[A 6-sided dice, A 12-sided dice]"

익스텐션으로 프로토콜 도입 정의하기

만약  프로토콜이 프로토콜 요구사항을 이미 따르지만, 해당 프로토콜을 지정하지 않은 경우 빈 익스텐션으로 도입할 수 있다.

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

Hamster의 인스턴스는 이제 TextRepresentable 이 필 수 형식인 곳에 쓰여질 수 있다.

let simonTheHamster = Hamster(name: "Simon")
let somethingTextRepresentable: TextRepresentable = simonTheHamster
print(somethingTextRepresentable.textDescription)
// Prints "A hamster named Simon"

NOTE

Types don’t automatically adopt a protocol just by satisfying its requirements. They must always explicitly declare their adoption of the protocol.

프로토콜 형식의 콜렉션

A protocol can be used as the type to be stored in a collection such as an array or a dictionary, as mentioned in Protocols as Types. This example creates an array of TextRepresentable things:

let things: [TextRepresentable] = [game, d12, simonTheHamster]
It’s now possible to iterate over the items in the array, and print each item’s textual description:

for thing in things {
    print(thing.textualDescription)
}
// A game of Snakes and Ladders with 25 squares
// A 12-sided dice
// A hamster named Simon
Note that the thing constant is of type TextRepresentable. It’s not of type Dice, or DiceGame, or Hamster, even if the actual instance behind the scenes is of one of those types. Nonetheless, because it’s of type TextRepresentable, and anything that is TextRepresentable is known to have a textualDescription property, it’s safe to access thing.textualDescription each time through the loop.

프로토콜 상속

A protocol can inherit one or more other protocols and can add further requirements on top of the requirements it inherits. The syntax for protocol inheritance is similar to the syntax for class inheritance, but with the option to list multiple inherited protocols, separated by commas:

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

TextRepresentable 을 상속받는 프로토콜의 한 예시이다.

protocol PrettyTextRepresentable : Text Representable {
  var prettyTextualDescription: String { get }
}

This example defines a new protocol, PrettyTextRepresentable, which inherits from TextRepresentable. Anything that adopts PrettyTextRepresentable must satisfy all of the requirements enforced by TextRepresentable, plus the additional requirements enforced by PrettyTextRepresentable. In this example, PrettyTextRepresentable adds a single requirement to provide a gettable property called prettyTextualDescription that returns a String.

The SnakesAndLadders class can be extended to adopt and conform to PrettyTextRepresentable:

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
    }
}
This extension states that it adopts the PrettyTextRepresentable protocol and provides an implementation of the prettyTextualDescription property for the SnakesAndLadders type. Anything that is PrettyTextRepresentable must also be TextRepresentable, and so the implementation of prettyTextualDescription starts by accessing the textualDescription property from the TextRepresentable protocol to begin an output string. It appends a colon and a line break, and uses this as the start of its pretty text representation. It then iterates through the array of board squares, and appends a geometric shape to represent the contents of each square:

If the square’s value is greater than 0, it’s the base of a ladder, and is represented by ▲.
If the square’s value is less than 0, it’s the head of a snake, and is represented by ▼.
Otherwise, the square’s value is 0, and it’s a “free” square, represented by ○.
The prettyTextualDescription property can now be used to print a pretty text description of any SnakesAndLadders instance:

print(game.prettyTextualDescription)
// A game of Snakes and Ladders with 25 squares:
// ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○

클래스 온리 프로토콜

AnyObject 를 프로토콜 상속 리스트에 넣으면 클래스 형식에만 할 수 있게 제한할 수 있다. (구조체나 열거형이 안됨)

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

In the example above, SomeClassOnlyProtocol can only be adopted by class types. It’s a compile-time error to write a structure or enumeration definition that tries to adopt SomeClassOnlyProtocol.

NOTE

Use a class-only protocol when the behavior defined by that protocol’s requirements assumes or requires that a conforming type has reference semantics rather than value semantics. For more about reference and value semantics, see Structures and Enumerations Are Value Types and Classes Are Reference Types.

프로토콜 콤포지션

It can be useful to require a type to conform to multiple protocols at the same time. You can combine multiple protocols into a single requirement with a protocol composition. Protocol compositions behave as if you defined a temporary local protocol that has the combined requirements of all protocols in the composition. Protocol compositions don’t define any new protocol types.

Protocol compositions have the form SomeProtocol & AnotherProtocol. You can list as many protocols as you need, separating them with ampersands (&). In addition to its list of protocols, a protocol composition can also contain one class type, which you can use to specify a required superclass.

Here’s an example that combines two protocols called Named and Aged into a single protocol composition requirement on a function parameter:

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)
// Prints "Happy birthday, Malcolm, you're 21!"

In this example, the Named protocol has a single requirement for a gettable String property called name. The Aged protocol has a single requirement for a gettable Int property called age. Both protocols are adopted by a structure called Person.

The example also defines a wishHappyBirthday(to:) function. The type of the celebrator parameter is Named & Aged, which means “any type that conforms to both the Named and Aged protocols.” It doesn’t matter which specific type is passed to the function, as long as it conforms to both of the required protocols.

The example then creates a new Person instance called birthdayPerson and passes this new instance to the wishHappyBirthday(to:) function. Because Person conforms to both protocols, this call is valid, and the wishHappyBirthday(to:) function can print its birthday greeting.

Here’s an example that combines the Named protocol from the previous example with a Location class:

class Location {
    var latitude: Double
    var longitude: Double
    init(latitude: Double, longitude: Double) {
        self.latitude = latitude
        self.longitude = longitude
    }
}
class City: Location, Named {
    var name: String
    init(name: String, latitude: Double, longitude: Double) {
        self.name = name
        super.init(latitude: latitude, longitude: longitude)
    }
}
func beginConcert(in location: Location & Named) {
    print("Hello, \(location.name)!")
}

let seattle = City(name: "Seattle", latitude: 47.6, longitude: -122.3)
beginConcert(in: seattle)
// Prints "Hello, Seattle!"
The beginConcert(in:) function takes a parameter of type Location & Named, which means “any type that’s a subclass of Location and that conforms to the Named protocol.” In this case, City satisfies both requirements.

Passing birthdayPerson to the beginConcert(in:) function is invalid because Person isn’t a subclass of Location. Likewise, if you made a subclass of Location that didn’t conform to the Named protocol, calling beginConcert(in:) with an instance of that type is also invalid.

프로토콜 일치 확인하기

Type Casting 에서 설명된 is와 as 연산자를 사용해 프로토콜 일치를 확인하며 특정 프로토콜로 캐스트한다. Checking for and casting to a protocol follows exactly the same syntax as checking for and casting to a type:

- The is operator returns true if an instance conforms to a protocol and returns false if it doesn’t.
- as? 는 프로토콜 형식의 옵셔널을 만약 프로토콜을 따르지 않으면 nil
- as! 프로토콜 형식으로 다운 캐스트하거나 런타임 에러

This example defines a protocol called HasArea, with a single property requirement of a gettable Double property called area:

protocol HasArea {
    var area: Double { get }
}
Here are two classes, Circle and Country, both of which conform to the HasArea protocol:

class Circle: HasArea {
    let pi = 3.1415927
    var radius: Double
    var area: Double { return pi * radius * radius }
    init(radius: Double) { self.radius = radius }
}
class Country: HasArea {
    var area: Double
    init(area: Double) { self.area = area }
}
The Circle class implements the area property requirement as a computed property, based on a stored radius property. The Country class implements the area requirement directly as a stored property. Both classes correctly conform to the HasArea protocol.

Here’s a class called Animal, which doesn’t conform to the HasArea protocol:

class Animal {
    var legs: Int
    init(legs: Int) { self.legs = legs }
}
The Circle, Country and Animal classes don’t have a shared base class. Nonetheless, they’re all classes, and so instances of all three types can be used to initialize an array that stores values of type AnyObject:

let objects: [AnyObject] = [
    Circle(radius: 2.0),
    Country(area: 243_610),
    Animal(legs: 4)
]
The objects array is initialized with an array literal containing a Circle instance with a radius of 2 units; a Country instance initialized with the surface area of the United Kingdom in square kilometers; and an Animal instance with four legs.

The objects array can now be iterated, and each object in the array can be checked to see if it conforms to the HasArea protocol:

for object in objects {
  if let objectWithArea = object 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

Whenever an object in the array conforms to the HasArea protocol, the optional value returned by the as? operator is unwrapped with optional binding into a constant called objectWithArea. The objectWithArea constant is known to be of type HasArea, and so its area property can be accessed and printed in a type-safe way.

Note that the underlying objects aren’t changed by the casting process. They continue to be a Circle, a Country and an Animal. However, at the point that they’re stored in the objectWithArea constant, they’re only known to be of type HasArea, and so only their area property can be accessed.

옵셔널 프로토콜 요구사항

프로토콜에 대한 옵셔널 요구사항을 정의할 수 있는데 이 요구사항은 프로토콜에 따르는 형식에 대한 구현이 반드해 해야하는건 아님을 나타낸다. 옵셔널 요구사항은 optional 수정자를 프로토콜의 정의에 지정한다. 옵셔널 요구사항은 Objective-C와의 상호연산이 가능하게 한다. 프로토콜과 옵셔널 요구사항은 반드시 @objc 속성으로 지정한다. @objc 프로토콜은 클래스에 의해서만 도입될 수 있으며 Objective-C 를 도입하거나 다른 @objc 클래스로부터 상속받는다. 이들은 구조체나 열거형에는 도입될 수 없다.

옵셔널 요구사항내에서 메소드나 프로퍼티를 사용할 때 이들 형식은 자동적으로 옵셔널이 된다. 예를 들어, (Int) -> String 은 ((Int) -> String)? 이 된다. 메소드의 반환형식만이 아닌 전체 함수형식이 옵셔널로 랩되는 것임을 기억한다.

옵셔널 프로토콜 요구사항은 옵셔널 체이닝으로 호출될 수 있으며 프로토콜을 따르는 형식이 요구사항을 구현하지 않을 때를 대비한다. 호출할 때 someOptionalMethod?(someArgument) 와 같은 방식으로 메소드의 이름뒤에 ? 를 씀으로서 옵셔널 메소드의 구현을 체크한다.For information on optional chaining, see Optional Chaining.

다음예시는 Counter 라 불리는 정수카운팅 클래스를 정의하며 그 증가 양을 제공하기 위해 외부 데이터 소스를 사용한다. 이 데이터 소스는 CounterDataSource 프로토콜이며 이 것은 두 개의 옵셔널 요구사항을 포함한다.

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

The CounterDataSource protocol defines an optional method requirement called increment(forCount:) and an optional property requirement called fixedIncrement. These requirements define two different ways for data sources to provide an appropriate increment amount for a Counter instance.

NOTE

Strictly speaking, you can write a custom class that conforms to CounterDataSource without implementing either protocol requirement. They’re both optional, after all. Although technically allowed, this wouldn’t make for a very good data source.

The Counter class, defined below, has an optional dataSource property of type 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
    }
  }
}

Counter 클래스는 count 라는 변수형 속성에 현재값을 저장한다. Counter 클래스는 또한 increment 라는 메소드를 정의하며 메소드가 호출될 때마다 count 프로퍼티를 증가한다.

increment() 메소드는 먼저 데이터 소스의 increment(forCount:) 메소드의 구현이 있는지를 보고 증가양을 얻기를 시도한다. increment() 메소드는 옵셔널 체이닝을 이용해 increment(forCount:) 호출을 시도하고 현 count 값을 메소드의 단일 매개변수로 전달한다.

옵셔널 체이닝의 두 레벨이 현재 사용됨을 기억한다. 먼저 dataSource 가 닐일 수 있으며 그러니 increment(forCount:) 는 dataSource 가 닐이 아닐 때만 호출된다. 두 번째, dataSource 가 존재할 때 increment(forCount:) 가 구현되어 있는지는 보증이안되는데 그 이유는 옵셔널 요구사항이기 때문이다.Here, the possibility that increment(forCount:) might not be implemented is also handled by optional chaining. The call to increment(forCount:) happens only if increment(forCount:) exists—that is, if it isn’t nil. This is why increment(forCount:) is also written with a question mark after its name.

increment(forCount:) 를 호출하는 것은 두 이유로 실패할 수 있는데 호출이 옵셔널 Int값을 반환한다는 점이다. increment(forCount:) 가 비옵셔널 Int를 CounterDataSource정의에 의해 반환하더라도 참이다. 두개의 옵셔널 체이닝이 사용되었지만 하나 이후 다른 하나로서 결과는 여전히 단일 옵셔널의 랩이다.For more information about using multiple optional chaining operations, see Linking Multiple Levels of Chaining.

increment(forCount:) 를 호출한 후 옵셔널 Int는 amount 라는 상수로 언랩되며 옵셔널 바인딩을 사용한다. 옵셔널 Int가 값을 포함하면that is, if the delegate and method both exist, and the method returned a value—the unwrapped amount is added onto the stored count property, and incrementation is complete.

만약 increment(forCount:) 에서 값을 얻지 못했다면 - dataSource가 닐, 또는 데이터 소스가 increment(forCount:) 를 구현하지 않았다면 - increment() 는 데이터소스의 fixedIncrement 를 대신 얻기 시도 한다. The fixedIncrement property is also an optional requirement, so its value is an optional Int value, even though fixedIncrement is defined as a nonoptional Int property as part of the CounterDataSource protocol definition.

Here’s a simple CounterDataSource implementation where the data source returns a constant value of 3 every time it’s queried. It does this by implementing the optional fixedIncrement property requirement:

class ThreeSource: NSObject, CounterDataSource {
    let fixedIncrement = 3
}
You can use an instance of ThreeSource as the data source for a new Counter instance:

var counter = Counter()
counter.dataSource = ThreeSource()
for _ in 1...4 {
    counter.increment()
    print(counter.count)
}
// 3
// 6
// 9
// 12

The code above creates a new Counter instance; sets its data source to be a new ThreeSource instance; and calls the counter’s increment() method four times. As expected, the counter’s count property increases by three each time increment() is called.

Here’s a more complex data source called TowardsZeroSource, which makes a Counter instance count up or down towards zero from its current count value:

class TowardsZeroSource: NSObject, CounterDataSource {
    func increment(forCount count: Int) -> Int {
        if count == 0 {
            return 0
        } else if count < 0 {
            return 1
        } else {
            return -1
        }
    }
}
The TowardsZeroSource class implements the optional increment(forCount:) method from the CounterDataSource protocol and uses the count argument value to work out which direction to count in. If count is already zero, the method returns 0 to indicate that no further counting should take place.

You can use an instance of TowardsZeroSource with the existing Counter instance to count from -4 to zero. Once the counter reaches zero, no more counting takes place:

counter.count = -4
counter.dataSource = TowardsZeroSource()
for _ in 1...5 {
    counter.increment()
    print(counter.count)
}
// -3
// -2
// -1
// 0
// 0


프로토콜 익스텐션

프로토콜은 제공된 메소드, 이니셜라이져, 서브스크립트, 그리고 계산된 프로퍼티 구현으로 형식을 따르기 위해 확장될 수 있다. 이는 각 형식 각자의 일치나 전역함수보다는 프로토콜 자체의 작용을 정의하기 위한것이다.

예를 들어, RandomNumberGenerator 프로토콜은 randomBool() 메소드를 제공하기 위해 확장될 수 있는데, 요구되는 random() 메소드를 사용하여 랜덤 Bool값을 반환한다.

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

프로토콜에 익스텐션을 생성함으로서 모든 일치하는 형식들은 추가 수정없이 자동적으로 이 메소드 구현을 얻는다.

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"

프로토콜 확장은 일치하는 형식에 구현을 추가할 수 있지만 프로토콜을 확장을 만들거나 다른 프로토콜로부터 상속은 되지 않는다. 프로토콜 상속은 항상 프로토콜 자체에서 지정된다.

기본 구현 제공하기

프로토콜 확장은 프로토콜의 모든 메소드나 계산된 프로퍼티 요구사항에 기본 구현을 제공하는데 사용될 수 있다. 만약 일치형식이 요구된 메소드나 프로퍼티의 자체 구현을 제공하면 구현은 익스텐션에 의해 제공되는 것을 대신해 사용된다.

일러두기)
익스텐션에 의해 제공되는 기본 구현을 통한 프로토콜 요구사항은 옵셔널 프로토콜 요구사항과 구별된다. 비록 일치형식이 이들 자체 구현을 제공하지 않지만 기본 구현을 통한 요구사항은 옵셔널 체이닝없이 호출될 수 있다.

For example, the PrettyTextRepresentable protocol, which inherits the TextRepresentable protocol can provide a default implementation of its required prettyTextualDescription property to simply return the result of accessing the textualDescription property:

extension PrettyTextRepresentable  {
    var prettyTextualDescription: String {
        return textualDescription
    }
}

프로토콜 확장에 제약 추가하기

프로토콜 확장을 구현할 때 일치형식이 반드시 만족해야 하는 제약을 지정할 수 있다. 이들 제약은 제공하는 프로토콜의 이름 뒤에 where 문을 사용해 작성한다. For more about generic where clauses, see Generic Where Clauses.

예를 들어, Collection 프로토콜에 확장을 지정하여 어떤 콜렉션이든 Equatable 을 따르게 적용한다. 콜렉션의 엘리먼트를 Equatable 로 제약함으로서 == 와 != 연산자를 등가, 비등가 확인에 사용한다.

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

The allEqual() method returns true only if all the elements in the collection are equal.

Consider two arrays of integers, one where all the elements are the same, and one where they aren’t:

let equalNumbers = [100, 100, 100, 100, 100]
let differentNumbers = [100, 100, 200, 100, 200]
Because arrays conform to Collection and integers conform to Equatable, equalNumbers and differentNumbers can use the allEqual() method:

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

일러두기)
만약 일치 형식이 같은 메소드나 프로퍼티를 위한 구현을 제공하는 다중 제약된 확장을 위한 요구사항을 만족하면 스위프트는 가장 특수한 제약에 연계된 구현을 사용한다.



덧글

댓글 입력 영역