protocol SomeProtocol {
// protocol definition goes here
}
struct SomeStructure: FirstProtocol, AnotherProtocol {
// structure definition goes here
}
class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
// class definition goes here
}
프로토콜은 속성이 저장 속성이어야 하는지 계산 속성이어야 하는지를 지정하지 않으며 필요한 속성 이름과 유형만 지정합니다.
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}
protocol FullyNamed {
var fullName: String { get }
}
struct Person: FullyNamed {
var fullName: String
}
let john = Person(fullName: "John Appleseed")
// john.fullName is "John Appleseed"
class Starship: FullyNamed {
var prefix: String?
var name: String
init(name: String, prefix: String? = nil) {
self.name = name
self.prefix = prefix
}
var fullName: String {
return (prefix != nil ? prefix! + " " : "") + name
}
}
var ncc1701 = Starship(name: "Enterprise", prefix: "USS")
// ncc1701.fullName is "USS Enterprise"
protocol SomeProtocol {
static func someTypeMethod()
}
protocol RandomNumberGenerator {
func random() -> Double
}
class LinearCongruentialGenerator: RandomNumberGenerator {
var lastRandom = 42.0
let m = 139968.0
let a = 3877.0
let c = 29573.0
func random() -> Double {
lastRandom = ((lastRandom * a + c)
.truncatingRemainder(dividingBy:m))
return lastRandom / m
}
}
let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
// Prints "Here's a random number: 0.3746499199817101"
print("And another one: \(generator.random())")
// Prints "And another one: 0.729023776863283"
프로토콜 인스턴스 메서드 요구 사항을 mutating으로 표시하면 클래스에 대한 해당 메서드 구현을 작성할 때 mutating 키워드를 작성할 필요가 없습니다.
mutating 키워드는 구조체와 열거형에서만 사용됩니다.
protocol Togglable {
mutating func toggle()
}
num 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
protocol SomeProtocol {
init(someParameter: Int)
}
지정된 이니셜라이저 또는 편의 이니셜라이저로 준수 클래스에 대한 프로토콜 이니셜라이저 요구 사항을 구현할 수 있습니다.
두 경우 모두 이니셜라이저 구현을 필수 수정자로 표시해야 합니다.
class SomeClass: SomeProtocol {
required init(someParameter: Int) {
// initializer implementation goes here
}
}
필수 수정자를 사용하면 준수하는 클래스의 모든 하위 클래스에 대한 초기화 요구 사항의 명시적 또는 상속된 구현을 제공하여 해당 클래스도 프로토콜을 준수하도록 합니다.
최종 클래스는 서브클래싱할 수 없기 때문에 final 수정자로 표시된 클래스에 필요한 수정자로 프로토콜 이니셜라이저 구현을 표시할 필요가 없습니다.
최종 수정자에 대한 자세한 내용은 재정의 방지를 참조하십시오.
서브클래스가 슈퍼클래스의 지정된 이니셜라이저를 재정의하고 프로토콜에서 일치하는 이니셜라이저 요구 사항도 구현하는 경우 필수 및 재정의 수정자를 모두 사용하여 이니셜라이저 구현을 표시합니다.
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
}
}
프로토콜은 유형이기 때문에 Swift의 다른 유형(예: Int, String 및 Double)의 이름과 일치하도록 이름을 대문자(FullyNamed 및 RandomNumberGenerator 등)로 시작합니다.
class Dice {
let sides: Int
let generator: RandomNumberGenerator
init(sides: Int, generator: RandomNumberGenerator) {
self.sides = sides
self.generator = generator
}
func roll() -> Int {
return Int(generator.random() * Double(sides)) + 1
}
}
var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
for _ in 1...5 {
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
위임은 클래스 또는 구조가 일부 책임을 다른 유형의 인스턴스에 넘길 수 있도록 하는 디자인 패턴입니다.
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)
}
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)
}
}
초기화 후 변경할 필요가 없기 때문에 상수 속성으로 선언되며 프로토콜은 gettable이어야만 합니다.
)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")
}
}
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
extension은 기존 유형에 새 속성, 메서드 및 첨자를 추가할 수 있으므로 프로토콜이 요구할 수 있는 요구 사항을 추가할 수 있습니다.
유형의 기존 인스턴스는 해당 준수가 extension의 인스턴스 유형에 추가될 때 프로토콜을 자동으로 채택하고 준수합니다.
protocol TextRepresentable {
var textualDescription: String { get }
}
extension Dice: TextRepresentable {
var textualDescription: String {
return "A \(sides)-sided dice"
}
}
let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
print(d12.textualDescription)
// Prints "A 12-sided dice"
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"
extension Array: TextRepresentable where Element: TextRepresentable {
var textualDescription: String {
let itemsAsText = self.map { $0.textualDescription }
return "[" + itemsAsText.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 {}
let simonTheHamster = Hamster(name: "Simon")
let somethingTextRepresentable: TextRepresentable = simonTheHamster
print(somethingTextRepresentable.textualDescription)
// Prints "A hamster named Simon"
유형은 요구 사항을 충족한다고 해서 프로토콜을 자동으로 채택하지 않습니다. 그들은 항상 프로토콜의 채택을 명시적으로 선언해야 합니다.
Swift는 많은 간단한 경우에 Equatable, Hashable 및 Comparable에 대한 프로토콜 적합성을 자동으로 제공할 수 있습니다.
이 합성된 구현을 사용하면 프로토콜 요구 사항을 직접 구현하기 위해 반복적인 상용구 코드를 작성할 필요가 없습니다.
Swift는 다음 종류의 사용자 정의 유형에 대해 Equatable의 합성 구현을 제공합니다.
==의 합성된 구현을 수신하려면 == 연산자를 직접 구현하지 않고 원래 선언이 포함된 파일에서 Equatable에 대한 준수를 선언합니다. Equatable 프로토콜은 !=의 기본 구현을 제공합니다.
아래 예제는 Vector2D 구조와 유사한 3차원 위치 벡터(x, y, z)에 대한 Vector3D 구조를 정의합니다.
x, y 및 z 속성은 모두 Equatable 유형이므로 Vector3D는 등가 연산자의 합성된 구현을 수신합니다.
struct Vector3D: Equatable {
var x = 0.0, y = 0.0, z = 0.0
}
let twoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)
let anotherTwoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)
if twoThreeFour == anotherTwoThreeFour {
print("These two vectors are also equivalent.")
}
// Prints "These two vectors are also equivalent."
Swift는 다음과 같은 종류의 사용자 정의 유형에 대해 Hashable의 합성 구현을 제공합니다.
hash(into:)의 합성된 구현을 수신하려면 hash(into:) 메서드를 직접 구현하지 않고 원래 선언이 포함된 파일에서 Hashable 준수를 선언합니다.
Swift는 원시 값이 없는 열거형에 대해 Comparable의 합성된 구현을 제공합니다.
열거형에 연결된 유형이 있는 경우 모두 Comparable 프로토콜을 준수해야 합니다.
<의 합성된 구현을 수신하려면 < 연산자를 직접 구현하지 않고 원래 열거 선언이 포함된 파일에서 Comparable에 대한 준수를 선언합니다.
Comparable 프로토콜의 기본 구현인 <=, > 및 >=는 나머지 비교 연산자를 제공합니다.
아래 예는 초보자, 중급자 및 전문가를 위한 사례로 SkillLevel 열거를 정의합니다. 전문가는 보유한 별의 수에 따라 추가로 순위가 매겨집니다.
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)]
for level in levels.sorted() {
print(level)
}
// Prints "beginner"
// Prints "intermediate"
// Prints "expert(stars: 3)"
// Prints "expert(stars: 5)"
프로토콜은 유형으로서의 프로토콜에서 언급한 것처럼 array or a dictionary과 같은 컬렉션에 저장할 유형으로 사용할 수 있습니다.
이 예에서는 TextRepresentable 항목의 배열을 만듭니다.let things: [TextRepresentable] = [game, d12, simonTheHamster]
for thing in things {
print(thing.textualDescription)
}
// A game of Snakes and Ladders with 25 squares
// A 12-sided dice
// A hamster named Simon
프로토콜은 하나 이상의 다른 프로토콜을 상속할 수 있으며 상속된 요구 사항에 추가 요구 사항을 추가할 수 있습니다.
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
// protocol definition goes here
}
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
}
}
print(game.prettyTextualDescription)
// A game of Snakes and Ladders with 25 squares:
// ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○
프로토콜의 상속 목록에 AnyObject 프로토콜을 추가하여 프로토콜 채택을 클래스 유형(구조 또는 열거가 아님)으로 제한할 수 있습니다.
protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
// class-only protocol definition goes here
}
해당 프로토콜의 요구 사항에 의해 정의된 동작이 일치하는 유형에 값 의미가 아닌 참조 의미가 있다고 가정하거나 요구할 때 클래스 전용 프로토콜을 사용합니다.
참조 및 값 의미 체계에 대한 자세한 내용은 구조 및 열거형은 값 형식이고 클래스는 참조 형식입니다.
동시에 여러 프로토콜을 준수하는 형식을 요구하는 것이 유용할 수 있습니다.
프로토콜 구성을 사용하여 여러 프로토콜을 단일 요구 사항으로 결합할 수 있습니다.
프로토콜 구성은 구성에 있는 모든 프로토콜의 요구 사항이 결합된 임시 로컬 프로토콜을 정의한 것처럼 작동합니다.
프로토콜 구성은 새로운 프로토콜 유형을 정의하지 않습니다.
프로토콜 구성은 SomeProtocol 및 AnotherProtocol 형식입니다. 앰퍼샌드(&)로 구분하여 필요한 만큼 프로토콜을 나열할 수 있습니다. 나
프로토콜 목록 외에도 프로토콜 구성에는 필수 수퍼클래스를 지정하는 데 사용할 수 있는 하나의 클래스 유형이 포함될 수 있습니다.
다음은 Named 및 Aged라는 두 가지 프로토콜을 함수 매개변수에 대한 단일 프로토콜 구성 요구 사항으로 결합하는 예입니다.
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!"
이 예에서 Named 프로토콜에는 name이라는 gettable String 속성에 대한 단일 요구 사항이 있습니다.
Aged 프로토콜에는 age라는 gettable Int 속성에 대한 단일 요구 사항이 있습니다. 두 프로토콜 모두 Person이라는 구조에 의해 채택됩니다.
이 예제는 또한 WishHappyBirthday(to:) 함수를 정의합니다. celebrator 매개변수의 유형은 Named & Aged이며, 이는 "Named 및 Aged 프로토콜을 모두 준수하는 모든 유형"을 의미합니다.
두 가지 필수 프로토콜을 모두 준수하는 한 어떤 특정 유형이 함수에 전달되는지는 중요하지 않습니다.
그런 다음 예제에서는 birthdayPerson이라는 새 Person 인스턴스를 만들고 이 새 인스턴스를 WishHappyBirthday(to:) 함수에 전달합니다.
Person이 두 프로토콜을 모두 준수하기 때문에 이 호출은 유효하고 WishHappyBirthday(to:) 함수는 생일 인사말을 인쇄할 수 있습니다.
다음은 이전 예제의 Named 프로토콜을 Location 클래스와 결합한 예제입니다.
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!"
beginConcert(in:) 함수는 Location & Named 유형의 매개변수를 사용합니다.
이는 "Location의 하위 클래스이고 Named 프로토콜을 준수하는 모든 유형"을 의미합니다. 이 경우 시는 두 가지 요건을 모두 충족합니다.
Person이 Location의 하위 클래스가 아니기 때문에 beginConcert(in:) 함수에 birthdayPerson을 전달하는 것은 유효하지 않습니다.
마찬가지로, Named 프로토콜을 따르지 않는 Location 의 하위 클래스를 만든 경우 해당 유형의 인스턴스로 beginConcert(in:) 를 호출하는 것도 유효하지 않습니다.
protocol HasArea {
var area: Double { get }
}
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 }
}
class Animal {
var legs: Int
init(legs: Int) { self.legs = legs }
}
let objects: [AnyObject] = [
Circle(radius: 2.0),
Country(area: 243_610),
Animal(legs: 4)
]
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
프로토콜에 대한 선택적 요구 사항을 정의할 수 있습니다.
프로토콜과 선택적 요구 사항은 모두 @objc 속성으로 표시되어야 합니다.
선택적 프로토콜 요구사항은 요구사항이 프로토콜을 준수하는 유형에 의해 구현되지 않았을 가능성을 설명하기 위해 선택적 연결을 사용하여 호출할 수 있습니다.
@objc protocol CounterDataSource {
@objc optional func increment(forCount count: Int) -> Int
@objc optional var fixedIncrement: Int { get }
}
엄밀히 말하면 프로토콜 요구 사항을 구현하지 않고도 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
}
}
}
첫째, dataSource가 nil일 수 있으므로 dataSource의 이름 뒤에 물음표가 있어 dataSource가 nil이 아닌 경우에만 increment(forCount:)를 호출해야 함을 나타냅니다.
둘째, dataSource가 존재하더라도 선택적 요구 사항이기 때문에 increment(forCount:)를 구현한다는 보장이 없습니다.
이 두 가지 이유로 인해 increment(forCount:) 호출이 실패할 수 있으므로 호출은 선택적 Int 값을 반환합니다.
class ThreeSource: NSObject, CounterDataSource {
let fixedIncrement = 3
}
var counter = Counter()
counter.dataSource = ThreeSource()
for _ in 1...4 {
counter.increment()
print(counter.count)
}
// 3
// 6
// 9
// 12
class TowardsZeroSource: NSObject, CounterDataSource {
func increment(forCount count: Int) -> Int {
if count == 0 {
return 0
} else if count < 0 {
return 1
} else {
return -1
}
}
}
counter.count = -4
counter.dataSource = TowardsZeroSource()
for _ in 1...5 {
counter.increment()
print(counter.count)
}
// -3
// -2
// -1
// 0
// 0
프로토콜은 메소드, 이니셜라이저, 첨자 및 계산된 속성 구현을 준수 유형에 제공하도록 extension될 수 있습니다.
이를 통해 각 유형의 개별 적합성 또는 전역 기능이 아닌 프로토콜 자체에 대한 동작을 정의할 수 있습니다.
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"
프로토콜 extension을 사용하여 해당 프로토콜의 모든 메서드 또는 계산된 속성 요구 사항에 대한 기본 구현을 제공할 수 있습니다.
extension에 의해 제공되는 기본 구현의 프로토콜 요구 사항은 선택적 프로토콜 요구 사항과 다릅니다.
준수하는 유형이 자체 구현을 제공할 필요는 없지만 기본 구현이 있는 요구 사항은 선택적 연결 없이 호출할 수 있습니다.
extension PrettyTextRepresentable {
var prettyTextualDescription: String {
return textualDescription
}
}