Поиск…


Вступление

Протоколы - это способ указать, как использовать объект. Они описывают набор свойств и методов, которые должен предоставлять класс, структура или перечисление, хотя протоколы не создают ограничений для реализации.

замечания

Протокол Swift представляет собой набор требований, которые должны выполняться соответствующими типами. Протокол затем может использоваться в большинстве мест, где ожидается тип, например массивы и общие требования.

Члены протокола всегда используют один и тот же квалификатор доступа, что и весь протокол, и не могут указываться отдельно. Хотя протокол может ограничивать доступ с помощью требований геттера или сеттера, как описано выше.

Дополнительные сведения о протоколах см. В разделе Язык быстрого программирования .

Протоколы Objective-C аналогичны протоколам Swift.

Протоколы также сопоставимы с интерфейсами Java .

Основы протокола

О протоколах

Протокол определяет инициализаторы, свойства, функции, индексы и связанные типы, необходимые для типа объекта Swift (класс, структура или перечисление), соответствующего протоколу. На некоторых языках подобные идеи для спецификаций требований последующих объектов называются «интерфейсами».

Объявленный и определенный Протокол является Типом, сам по себе, с подписями его заявленных требований, несколько похожим на то, как Swift Functions являются типом, основанным на их сигнатуре параметров и возвратов.

Спецификации Swift Protocol могут быть необязательными, явно требуемыми и / или заданными реализациями по умолчанию через средство, известное как расширения протокола. Тип Swift Object (класс, структура или перечисление), желающий соответствовать Протоколу, который связан с расширениями для всех его требований, требует только заявить о своем желании соответствовать полностью соответствовать. Возможности реализации по умолчанию для расширений протокола могут быть достаточными для выполнения всех обязательств, связанных с Протоколом.

Протоколы могут быть унаследованы другими протоколами. Это, в сочетании с расширением протокола, означает, что протоколы могут и должны рассматриваться как важная функция Swift.

Протоколы и расширения важны для реализации более широких целей и подходов Swift к гибкости разработки и развития программ. Первичной целью Swift's Protocol and Extension является упрощение дизайна композиции в архитектуре и разработке программ. Это называется программно-ориентированным программированием. Крепкие старые таймеры считают, что это превосходит фокус на дизайне ООП.

Протоколы определяют интерфейсы, которые могут быть реализованы любой структурой , классом или перечислением :

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 } означает, что свойство должно быть gettable, и поэтому оно может быть реализовано как любое свойство. { get set } означает, что свойство должно быть настраиваемым, а также gettable.

Структура, класс или перечисление могут соответствовать протоколу:

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<...> где протоколы представляют собой список, разделенный запятыми между угловыми скобками <> .

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 с associatedtype Element протокола. Компилятор все равно выведет, что общий Element заполнитель используется для удовлетворения associatedtype Element Требование associatedtype Element .)

associatedtype typealias также может быть удовлетворен явно посредством использования 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

Модель делегата

Делегат - это общий шаблон проектирования, используемый в рамках Cocoa и CocoaTouch, где один класс делегирует ответственность за реализацию некоторых функций другому. Это следует принципу разделения проблем, когда класс framework реализует общие функции, в то время как отдельный экземпляр делегата реализует конкретный прецедент.

Еще один способ взглянуть на шаблон делегата - с точки зрения связи объектов. Objects часто приходится разговаривать друг с другом, и для этого объект должен соответствовать protocol , чтобы стать делегатом другого объекта. Как только эта настройка будет выполнена, другой объект вернется к своим делегатам, когда произойдет интересное.

Например, представление в пользовательском интерфейсе для отображения списка данных должно отвечать только за логику отображения данных, а не за определение того, какие данные должны отображаться.

Давайте перейдем к более конкретному примеру. если у вас есть два класса: родительский и дочерний:

class Parent { }
class Child { }

И вы хотите уведомить родителя об изменении из ребенка.

В Swift делегаты реализуются с использованием объявления protocol поэтому мы объявим protocol который будет реализован delegate . Здесь делегат является parent объектом.

protocol ChildDelegate: class {
    func childDidSomething()
}

Ребенок должен объявить свойство для хранения ссылки на делегат:

class Child {
    weak var delegate: ChildDelegate?
}

Обратите внимание, что delegate переменной является факультативным, а протокол ChildDelegate отмечен как реализуемый только типом класса (без этого переменная delegate не может быть объявлена ​​как weak ссылка, избегая любого цикла сохранения. Это означает, что если переменная delegate больше не является упоминается где-нибудь еще, он будет выпущен). Это значит, что родительский класс регистрирует делегат только тогда, когда он необходим и доступен.

Также для того, чтобы отметить, что наш делегат weak мы должны ограничить наш протокол ChildDelegate ссылкой на типы, добавив ключевое слово class в объявление протокола.

В этом примере, когда ребенок что-то делает и ему нужно уведомить своего родителя, ребенок вызовет:

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

По умолчанию protocol Swift не позволяет реализовать дополнительную функцию. Они могут быть указаны только в том случае, если ваш протокол отмечен атрибутом @objc и optional модификатором.

Например, UITableView реализует общее поведение представления таблицы в iOS, но пользователь должен реализовать два класса делегатов, называемые UITableViewDelegate и UITableViewDataSource которые реализуют способы отображения конкретных ячеек и поведения.

@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

Любые методы, не отмеченные как optional в определении протокола ( UITableViewDelegate в этом случае), должны быть реализованы.

Расширение протокола для определенного класса соответствия

Вы можете написать реализацию протокола по умолчанию для определенного класса.

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 (Extensible Enum)

// 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 типа или, в частности, вашей структуры перечисления

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

На сайте вызова вы можете использовать сокращенную синтаксическую разметку для typeafe 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 
}

Если тип non-class пытается реализовать 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 - это протокол только для классов, назначение в bar является допустимым, поскольку компилятор знает, что foo является типом класса и поэтому имеет ссылочную семантику.

Если Foo не был протоколом только для класса, была бы допущена ошибка компилятора - поскольку соответствующий тип может быть типом значения , для которого требуется аннотация var , чтобы быть изменчивым.

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 var weakReference : ClassOnlyProtocol?

Реализация протокола Hashable

Типы, используемые в Sets и Hashable Dictionaries(key) должны соответствовать протоколу Hashable , который наследуется от Equatable protocol.

Пользовательский тип, соответствующий протоколу Hashable , должен реализовывать

  • Вычисленное свойство hashValue
  • Определите один из операторов равенства ie == or != .

В следующем примере реализуется протокол Hashable для настраиваемой struct :

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