수색…


소개

프로토콜은 객체 사용 방법을 지정하는 방법입니다. 프로토콜은 클래스, 구조체 또는 열거 형이 제공해야하는 속성 및 메서드 집합을 설명하지만 프로토콜은 구현에 대한 제한이 없습니다.

비고

스위프트 프로토콜은 준수 유형이 구현해야하는 요구 사항 모음입니다. 그런 다음 프로토콜은 예를 들어 어레이 및 일반적인 요구 사항과 같이 유형이 예상되는 대부분의 장소에서 사용될 수 있습니다.

프로토콜 멤버는 항상 전체 프로토콜과 동일한 액세스 한정자를 공유하므로 별도로 지정할 수 없습니다. 프로토콜은 위의 예제에 따라 getter 또는 setter 요구 사항을 사용하여 액세스를 제한 할 수 있습니다.

프로토콜에 대한 자세한 내용은 Swift 프로그래밍 언어를 참조하십시오.

Objective-C 프로토콜 은 Swift 프로토콜과 유사합니다.

프로토콜은 Java 인터페이스 와 비슷합니다.

프로토콜 기본 사항

프로토콜 정보

프로토콜은 프로토콜을 준수하는 Swift 객체 유형 (클래스, 구조체 또는 열거 형)에 필요한 초기화 프로그램, 속성, 함수, 하위 스크립트 및 관련 유형을 지정합니다. 일부 언어에서는 후속 객체의 요구 사항 사양에 대한 유사한 개념을 '인터페이스'라고합니다.

선언되고 정의 된 의정서는 명시된 요구 사항의 서명이있는 유형이며, 신속 기능이 매개 변수 및 반환의 서명을 기반으로 한 유형과 다소 유사합니다.

스위프트 프로토콜 사양은 선택 사양이거나 명시 적으로 요구되거나 프로토콜 확장이라고하는 기능을 통해 기본 구현을 제공 할 수 있습니다. 모든 지정된 요구 사항에 대해 Extension으로 구현 된 프로토콜을 준수하기를 원하는 Swift Object Type (클래스, 구조체 또는 열거 형)은 완전히 준수해야한다는 요구 사항을 명시해야합니다. 프로토콜 확장의 기본 구현 기능은 의정서 준수의 모든 의무를 이행하기에 충분할 수 있습니다.

프로토콜은 다른 프로토콜에 의해 상속 될 수 있습니다. 이것은 프로토콜 확장과 함께 프로토콜이 스위프트의 중요한 특징으로 간주 될 수 있고 또 그렇게되어야 함을 의미합니다.

프로토콜 및 확장은 프로그램 설계 유연성 및 개발 프로세스에 대한 Swift의 광범위한 목표와 접근 방식을 실현하는 데 중요합니다. Swift의 프로토콜 및 확장 기능의 주요 목적은 프로그램 아키텍처 및 개발의 구성 디자인을 용이하게하는 것입니다. 이를 프로토콜 지향 프로그래밍이라고합니다. 피곤한 옛 타이머는 OOP 디자인에 초점을 맞추고이 우수한 제품을 고려합니다.

프로토콜 은 모든 구조체 , 클래스 또는 열거 형 으로 구현할 수있는 인터페이스를 정의합니다.

protocol MyProtocol {
    init(value: Int)                      // required initializer
    func doSomething() -> Bool            // instance method
    var message: String { get }           // instance read-only property
    var value: Int { get set }            // read-write instance property
    subscript(index: Int) -> Int { get }  // instance subscript
    static func instructions() -> String  // static method
    static var max: Int { get }           // static read-only property
    static var total: Int { get set }     // read-write static property
}

프로토콜에 정의 된 속성{ get } 또는 { get set } 으로 주석되어야합니다. { get } 은 속성을 { get } 있어야한다는 것을 의미하므로 모든 속성으로 구현할 수 있습니다. { get set } 은 속성이 gettable뿐만 아니라 설정 가능해야 함을 의미합니다.

구조체, 클래스 또는 enum은 프로토콜을 준수 할 수 있습니다.

struct MyStruct : MyProtocol {
    // Implement the protocol's requirements here
}
class MyClass : MyProtocol {
    // Implement the protocol's requirements here
}
enum MyEnum : MyProtocol {
    case caseA, caseB, caseC
    // Implement the protocol's requirements here
}

프로토콜은 확장을 통해 요구 사항에 대한 기본 구현 을 정의 할 수도 있습니다.

extension MyProtocol {
    
    // default implementation of doSomething() -> Bool
    // conforming types will use this implementation if they don't define their own
    func doSomething() -> Bool {
        print("do something!")
        return true
    }
}

프로토콜 종류로 사용할 수 있습니다, 그것은하지 않습니다 제공 associatedtype 요구 사항을 :

func doStuff(object: MyProtocol) {
    // All of MyProtocol's requirements are available on the object
    print(object.message)
    print(object.doSomething())
}

let items : [MyProtocol] = [MyStruct(), MyClass(), MyEnum.caseA]

여러 프로토콜을 준수하는 추상 형식을 정의 할 수도 있습니다.

3.0

Swift 3 이상에서는 앰퍼샌드 ( & )로 프로토콜 목록을 구분하여이 작업을 수행합니다.

func doStuff(object: MyProtocol & AnotherProtocol) {
    // ...
}

let items : [MyProtocol & AnotherProtocol] = [MyStruct(), MyClass(), MyEnum.caseA]
3.0

이전 버전에는 구문 protocol<...> 여기서 프로토콜은 꺾쇠 괄호 <> 사이의 쉼표로 구분 된 목록입니다.

protocol AnotherProtocol {
    func doSomethingElse()
}

func doStuff(object: protocol<MyProtocol, AnotherProtocol>) {
    
    // All of MyProtocol & AnotherProtocol's requirements are available on the object
    print(object.message)
    object.doSomethingElse()
}

// MyStruct, MyClass & MyEnum must now conform to both MyProtocol & AnotherProtocol
let items : [protocol<MyProtocol, AnotherProtocol>] = [MyStruct(), MyClass(), MyEnum.caseA]

기존 유형을 확장 하여 프로토콜을 준수 할 수 있습니다.

extension String : MyProtocol {
    // Implement any requirements which String doesn't already satisfy
}

관련 유형 요구 사항

프로토콜은 associatedtype 키워드를 사용하여 연관된 유형 요구 사항 을 정의 할 수 있습니다.

protocol Container {
    associatedtype Element
    var count: Int { get }
    subscript(index: Int) -> Element { get set }
}

연관된 유형 요구 사항을 가진 프로토콜 일반 제약 조건으로 만 사용할 수 있습니다 .

// These are NOT allowed, because Container has associated type requirements:
func displayValues(container: Container) { ... }
class MyClass { let container: Container }
// > error: protocol 'Container' can only be used as a generic constraint
// > because it has Self or associated type requirements

// These are allowed:
func displayValues<T: Container>(container: T) { ... }
class MyClass<T: Container> { let container: T }

만족시킬 수있는 프로토콜을 준수 유형 associatedtype 프로토콜이 기대 지정된 유형 제공함으로써, 암시 적 요구 사항을 associatedtype 표시를 :

struct ContainerOfOne<T>: Container {
    let count = 1          // satisfy the count requirement
    var value: T
    
    // satisfy the subscript associatedtype requirement implicitly,
    // by defining the subscript assignment/return type as T
    // therefore Swift will infer that T == Element
    subscript(index: Int) -> T {
        get {
            precondition(index == 0)
            return value
        }
        set {
            precondition(index == 0)
            value = newValue
        }
    }
}

let container = ContainerOfOne(value: "Hello")

이 예에 명확성을 더하기 위해 일반적인 자리 표시 자 유형의 이름은 T 이며 더 적합한 이름은 Element 이며 프로토콜의 associatedtype Element 섀도 잉합니다. 컴파일러는 일반 placeholder Elementassociatedtype Element 을 충족시키는 데 사용됨을 계속 추측합니다 associatedtype Element 요구 사항.)

associatedtype 은 또한 typealias 사용하여 명시 적으로 만족 될 수 있습니다.

struct ContainerOfOne<T>: Container {

    typealias Element = T
    subscript(index: Int) -> Element { ... }

    // ...
}

확장 프로그램에서도 마찬가지입니다.

// Expose an 8-bit integer as a collection of boolean values (one for each bit).
extension UInt8: Container {

    // as noted above, this typealias can be inferred
    typealias Element = Bool

    var count: Int { return 8 }
    subscript(index: Int) -> Bool {
        get {
            precondition(0 <= index && index < 8)
            return self & 1 << UInt8(index) != 0
        }
        set {
            precondition(0 <= index && index < 8)
            if newValue {
                self |= 1 << UInt8(index)
            } else {
                self &= ~(1 << UInt8(index))
            }
        }
    }
}

준수 유형이 이미 요구 사항을 충족시키는 경우 구현할 필요가 없습니다.

extension Array: Container {}  // Array satisfies all requirements, including Element

위임 패턴

델리게이트 는 코코아 및 코코아 터치 (CocoaTouch) 프레임 워크에서 사용되는 일반적인 디자인 패턴으로, 한 클래스는 일부 기능을 구현하는 책임을 다른 클래스에 위임합니다. 이것은 프레임 워크 클래스가 제네릭 기능을 구현하고 별도의 위임 인스턴스가 특정 유스 케이스를 구현하는 우려의 분리 원칙을 따릅니다.

대리자 패턴을 조사하는 또 다른 방법은 객체 통신의 관점입니다. Objects 종종 서로 이야기하고 그렇게하기 위해 객체는 다른 객체의 델리게이트가되기 위해 protocol 을 따를 필요가 있습니다. 이 설정이 완료되면 다른 객체는 흥미로운 일이 발생할 때 해당 대리인에게 다시 말합니다.

예를 들어, 사용자 인터페이스에서 데이터 목록을 표시하는보기는 표시 할 데이터를 결정하는 것이 아니라 데이터 표시 방법의 논리에 대한 책임을 져야합니다.

좀 더 구체적인 예를 들어 봅시다. 학부모와 자녀가 두 가지 클래스 인 경우 :

class Parent { }
class Child { }

그리고 부모에게 자녀로부터의 변화를 알리고 싶습니다.

Swift에서 대리자는 protocol 선언을 사용하여 구현되므로 delegate 이 구현할 protocol 을 선언합니다. 여기에서 대리자는 parent 개체입니다.

protocol ChildDelegate: class {
    func childDidSomething()
}

자식은 대리자에 대한 참조를 저장하는 속성을 선언해야합니다.

class Child {
    weak var delegate: ChildDelegate?
}

delegate 변수는 선택적이며 ChildDelegate 프로토콜은 클래스 유형에 의해서만 구현되도록 표시되어 있습니다 (이 없이는 delegate 변수는 모든 보유주기를 피하기 위해 weak 참조로 선언 될 수 없습니다.) 이것은 delegate 변수가 더 이상 존재하지 않으면 그 밖의 다른 곳에서는 참조 할 수 있습니다. 따라서 부모 클래스는 필요하고 사용할 수있는 경우에만 대리자를 등록합니다.

또한 우리 대리자를 weak 표시하려면 프로토콜 선언에 class 키워드를 추가하여 ChildDelegate 프로토콜을 참조 유형으로 제한해야합니다.

이 예에서, 아이가 무언가를하고 부모에게 통보해야 할 때, 아이는 다음을 호출합니다 :

delegate?.childDidSomething()

위임자가 정의 된 경우 위임자에게 자녀가 무언가를했음을 알립니다.

부모 클래스는 그 동작에 응답 할 수 있도록 ChildDelegate 프로토콜을 확장해야합니다. 이 작업은 상위 클래스에서 직접 수행 할 수 있습니다.

class Parent: ChildDelegate {
    ...

    func childDidSomething() {
        print("Yay!")
    }
}

또는 확장 프로그램 사용 :

extension Parent: ChildDelegate {
    func childDidSomething() {
        print("Yay!")
    }
}

부모는 또한 아동에게 그 아동의 대표자임을 알릴 필요가 있습니다.

// In the parent
let child = Child()
child.delegate = self

기본적으로 Swift protocol 은 선택적 기능을 구현할 수 없습니다. 프로토콜이 @objc 속성 및 optional 수정 자로 표시되어있는 경우에만 지정할 수 있습니다.

예를 들어, UITableView 는 iOS에서 표보기의 일반적인 동작을 구현하지만 사용자는 특정 셀이 어떻게 보이고 동작하는지 구현하는 UITableViewDelegateUITableViewDataSource 라는 두 개의 대리자 클래스를 구현해야합니다.

@objc public protocol UITableViewDelegate : NSObjectProtocol, UIScrollViewDelegate {
        
        // Display customization
        optional public func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath)
        optional public func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int)
        optional public func tableView(tableView: UITableView, willDisplayFooterView view: UIView, forSection section: Int)
        optional public func tableView(tableView: UITableView, didEndDisplayingCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath)
        ...
}

클래스 정의를 변경하여이 프로토콜을 구현할 수 있습니다. 예를 들면 다음과 같습니다.

class MyViewController : UIViewController, UITableViewDelegate

프로토콜 정의 (이 경우 UITableViewDelegate 에서 optional 으로 표시되지 않은 메소드는 구현되어야합니다.

특정 준수 클래스에 대한 프로토콜 확장

특정 클래스에 대한 기본 프로토콜 구현 을 작성할 수 있습니다.

protocol MyProtocol {
    func doSomething()
}

extension MyProtocol where Self: UIViewController {
    func doSomething() {
        print("UIViewController default protocol implementation")
    }
}

class MyViewController: UIViewController, MyProtocol { }

let vc = MyViewController()
vc.doSomething() // Prints "UIViewController default protocol implementation"

RawRepresentable 프로토콜 (확장 가능 열거 형) 사용

// RawRepresentable has an associatedType RawValue.
// For this struct, we will make the compiler infer the type
// by implementing the rawValue variable with a type of String
//
// Compiler infers RawValue = String without needing typealias
//
struct NotificationName: RawRepresentable {
    let rawValue: String

    static let dataFinished = NotificationNames(rawValue: "DataFinishedNotification")
}

이 구조체는 다른 곳으로 확장되어 케이스를 추가 할 수 있습니다.

extension NotificationName {
    static let documentationLaunched = NotificationNames(rawValue: "DocumentationLaunchedNotification")
}

그리고 인터페이스는 모든 RawRepresentable 유형 또는 특히 enum 구조체를 중심으로 디자인 할 수 있습니다.

func post(notification notification: NotificationName) -> Void {
    // use notification.rawValue
}

호출 사이트에서 타입 안전 NotificationName에 대한 점 구문 축약을 사용할 수 있습니다.

post(notification: .dataFinished)

일반 RawRepresentable 함수 사용

// RawRepresentable has an associate type, so the 
// method that wants to accept any type conforming to
// RawRepresentable needs to be generic
func observe<T: RawRepresentable>(object: T) -> Void {
    // object.rawValue
}

클래스 전용 프로토콜

프로토콜 만 지정할 수 있습니다 클래스 사용을 통해 그것을 구현할 수있는 class 의 상속 목록에 키워드를. 이 키워드는이 목록에서 상속 된 다른 프로토콜보다 앞에 나와야합니다.

protocol ClassOnlyProtocol: class, SomeOtherProtocol {
    // Protocol requirements 
}

비 클래스 유형이 ClassOnlyProtocol 을 구현하려고하면 컴파일러 오류가 생성됩니다.

struct MyStruct: ClassOnlyProtocol { 
    // error: Non-class type 'MyStruct' cannot conform to class protocol 'ClassOnlyProtocol'  
}

다른 프로토콜은 ClassOnlyProtocol 에서 상속받을 수 있지만 동일한 클래스 전용 요구 사항을 갖습니다.

protocol MyProtocol: ClassOnlyProtocol {
    // ClassOnlyProtocol Requirements
    // MyProtocol Requirements
}

class MySecondClass: MyProtocol {
    // ClassOnlyProtocol Requirements
    // MyProtocol Requirements
}

클래스 전용 프로토콜의 참조 의미

클래스 전용 프로토콜을 사용하면 준수 유형을 알 수 없을 때 참조 의미를 사용할 수 있습니다.

protocol Foo : class {
    var bar : String { get set }
}

func takesAFoo(foo:Foo) {

    // this assignment requires reference semantics,
    // as foo is a let constant in this scope.
    foo.bar = "new value"
}

이 예에서 Foo 는 클래스 전용 프로토콜이므로 컴파일러에서 foo 가 클래스 유형이므로 참조 의미 체계가 있으므로 bar 대한 할당은 유효합니다.

Foo 가 클래스 전용 프로토콜이 아닌 경우 컴파일러 오류가 발생합니다. 일치하는 유형은 값 유형일 수 있기 때문에 가변적 인 var annotation이 필요합니다.

protocol Foo {
    var bar : String { get set }
}

func takesAFoo(foo:Foo) {
    foo.bar = "new value" // error: Cannot assign to property: 'foo' is a 'let' constant
}

func takesAFoo(foo:Foo) {
    var foo = foo // mutable copy of foo
    foo.bar = "new value" // no error – satisfies both reference and value semantics
}

프로토콜 유형의 약한 변수

프로토콜 유형의 변수에 weak 변경자 를 적용 할 때 weak 은 참조 유형에만 적용될 수 있기 weak 해당 프로토콜 유형은 클래스 전용이어야합니다.

weak var weakReference : ClassOnlyProtocol?

Hashable 프로토콜 구현하기

Sets Dictionaries(key) 에서 사용되는 형식은 Equatable 프로토콜에서 상속 한 Hashable 프로토콜을 준수해야합니다.

Hashable 프로토콜을 준수하는 사용자 정의 유형이 구현해야합니다.

  • 계산 된 프로퍼티 hashValue
  • 항등 연산자 ie == 또는 != 중 하나를 정의하십시오.

다음 예제는 사용자 정의 struct 대해 Hashable 프로토콜을 구현합니다.

struct Cell {
    var row: Int
    var col: Int
    
    init(_ row: Int, _ col: Int) {
        self.row = row
        self.col = col
    }
}

extension Cell: Hashable {
    
    // Satisfy Hashable requirement
    var hashValue: Int {
        get {
            return row.hashValue^col.hashValue
        }
    }

    // Satisfy Equatable requirement
    static func ==(lhs: Cell, rhs: Cell) -> Bool {
        return lhs.col == rhs.col && lhs.row == rhs.row
    }
    
}

// Now we can make Cell as key of dictonary
var dict = [Cell : String]()

dict[Cell(0, 0)] = "0, 0"
dict[Cell(1, 0)] = "1, 0"
dict[Cell(0, 1)] = "0, 1"

// Also we can create Set of Cells
var set = Set<Cell>()

set.insert(Cell(0, 0))
set.insert(Cell(1, 0))

참고 : 사용자 정의 유형의 여러 값이 서로 다른 해시 값을 가질 필요는 없으며 충돌은 허용됩니다. 해시 값이 같으면 평등 연산자가 실제 평등을 결정하는 데 사용됩니다.



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow