Як визначити необов'язкові методи у протоколі Swift?


359

Чи можливо в Свіфті? Якщо ні, то чи можливо це зробити?


1
Нарешті я мотивував себе оновити свою відповідь, ви можете переглянути її прийняття.
акашівський

@akashivskyy Я приймаю вашу відповідь, оскільки вона краще відображає всі наявні варіанти та їх плюси та мінуси.
Селвін

Відповіді:


504

1. Використання стандартних реалізацій (бажано).

protocol MyProtocol {
    func doSomething()
}

extension MyProtocol {
    func doSomething() {
        /* return a default value or just leave empty */
    }
}

struct MyStruct: MyProtocol {
    /* no compile error */
}

Переваги

  • Жоден час виконання Objective-C не бере участь (ну, принаймні, явно, принаймні) Це означає, що ви можете відповідати структурам, перерахункам та некласам NSObject. Також це означає, що ви можете скористатися потужною системою дженериків.

  • Ви завжди можете бути впевнені, що всі вимоги виконуються, стикаючись із типами, які відповідають такому протоколу. Це завжди або конкретна реалізація, або за замовчуванням. Ось як поводяться "інтерфейси" чи "контракти" іншими мовами.

Недоліки

  • Для невимогливості Voidпотрібно мати розумне значення за замовчуванням , що не завжди можливо. Однак, коли ви зіткнулися з цією проблемою, це означає, що або така вимога дійсно не повинна мати реальної реалізації за замовчуванням, або ви помилилися під час проектування API.

  • Ви не можете розрізняти реалізацію за замовчуванням і жодну реалізацію , принаймні, не вирішуючи цю проблему зі спеціальними значеннями повернення. Розглянемо наступний приклад:

    protocol SomeParserDelegate {
        func validate(value: Any) -> Bool
    }

    Якщо ви надаєте реалізацію за замовчуванням, яка просто повертається true- це добре на перший погляд. Тепер розглянемо наступний псевдо-код:

    final class SomeParser {
        func parse(data: Data) -> [Any] {
            if /* delegate.validate(value:) is not implemented */ {
                /* parse very fast without validating */
            } else {
                /* parse and validate every value */
            }
        }
    }

    Немає способу здійснити таку оптимізацію - ви не можете знати, чи реалізує ваш делегат метод чи ні.

    Хоча існує декілька різних способів подолати цю проблему (використовуючи необов'язкові закриття, різні об'єкти делегування для різних операцій, щоб назвати декілька), цей приклад чітко представляє проблему.


2. Використання @objc optional.

@objc protocol MyProtocol {
    @objc optional func doSomething()
}

class MyClass: NSObject, MyProtocol {
    /* no compile error */
}

Переваги

  • Не потрібна реалізація за замовчуванням. Ви просто оголосите необов'язковий метод або змінну і готові до роботи.

Недоліки

  • Це суттєво обмежує можливості вашого протоколу, вимагаючи, щоб усі відповідні типи були сумісні з Objective-C. Це означає, що тільки класи, які успадковують, NSObjectможуть відповідати такому протоколу. Ніяких структур, жодних перерахунків, жодних асоційованих типів.

  • Ви завжди повинні перевірити, чи реалізований необов'язковий метод , або необов'язково викликаючи або перевіряючи, чи відповідає відповідний тип. Це може спричинити багато шаблонів, якщо ви часто використовуєте необов'язкові методи.


17
Подивіться на покращений спосіб додаткових методів у швидкому використанні розширення (внизу)
Даніель Канан

1
І як один тест на підтримку факультативного методу протоколу в екземплярі? respondsToSelector?
devios1

3
Тільки не робіть цього! Swift чомусь не підтримує необов'язковий метод у протоколах.
fpg1503

2
Цей метод не підтримує необов'язкові параметри. Тобто ви не можете цього зробитиoptional func doSomething(param: Int?)
SoftDesigner

4
Звичайно, ця відповідь по суті є помилковою в наші дні, через роки. Сьогодні у Swift ви просто додаєте розширення для реалізації за замовчуванням, це основний аспект Swift. (Як показують усі сучасні відповіді нижче.) Було б просто неправильно додавати прапор objc.
Fattie

394

У Swift 2 і далі можна додати реалізацію протоколу за замовчуванням. Це створює новий спосіб необов'язкових методів у протоколах.

protocol MyProtocol {
    func doSomethingNonOptionalMethod()
    func doSomethingOptionalMethod()
}

extension MyProtocol {
    func doSomethingOptionalMethod(){ 
        // leaving this empty 
    }
}

Це не дуже приємний спосіб створення необов'язкових методів протоколу, але дає можливість використовувати структури в зворотному виклику протоколу.

Тут я написав невеликий підсумок: https://www.avanderlee.com/swift-2-0/optional-protocol-methods/


2
Це, мабуть, найчистіший спосіб зробити це у Swift. Шкода, що він не працює до Swift 2.0.
Ентальпі

13
@MattQuiros Я знаходжу, що вам насправді потрібно оголосити функцію у визначенні протоколу, інакше функція розширення без відключення не буде перекрито у ваших класах, які відповідають протоколу.
Ян Пірс

3
@IanPearce правильний, і це, здається, дизайн. У розмові "Протокол, орієнтований на протокол" (408) в WWDC, вони говорять про методи, в основному протоколі яких є "точки налаштування", пропоновані відповідно до типів. Необхідний пункт налаштування не отримує визначення в розширенні; необов'язковий пункт. Методи протоколу, які, як правило, не слід налаштовувати, повністю оголошуються / визначаються у розширенні, щоб заборонити відповідні типи для налаштування, якщо ви спеціально не піддаєтеся його динамичному типу, щоб показати, що ви хочете користувальницької реалізації відповідника.
Маттіас

4
@FranklinYu Ви можете це зробити, але тоді ви шліфуєте свій дизайн API «кидками» там, де насправді це не потрібно. Мені більше подобається ідея "мікропротоколів". Наприклад, кожен метод підтверджується одним протоколом, і тоді ви можете перевірити: чи об'єкт є протоколом
Дарко

3
@Antoine Я думаю, що ви повинні зробити функцію в розширенні public, оскільки функції в протоколах є загальнодоступними за визначенням. Ваше рішення не працюватиме, коли протокол буде використовуватися поза його модулем.
Вадим Айзенберг

39

Оскільки є кілька відповідей про те, як використовувати необов’язковий модифікатор та атрибут @objc для визначення необов'язкового протоколу вимог, я наведу зразок про те, як використовувати розширення протоколу для визначення необов'язкового протоколу.

Нижче код - Swift 3. *.

/// Protocol has empty default implementation of the following methods making them optional to implement:
/// `cancel()`
protocol Cancelable {

    /// default implementation is empty.
    func cancel()
}

extension Cancelable {

    func cancel() {}
}

class Plane: Cancelable {
  //Since cancel() have default implementation, that is optional to class Plane
}

let plane = Plane()
plane.cancel()
// Print out *United Airlines can't cancelable*

Зверніть увагу, що методи розширення протоколу не можуть викликатися кодом Objective-C, і ще гірше, що команда Swift не виправить це. https://bugs.swift.org/browse/SR-492


1
Хороша робота! Це має бути правильною відповіддю, оскільки це вирішує проблему, не залучаючи час виконання об'єктивного C.
Лукас

дійсно приємний!
tania_S

зі швидкої документації - "Вимоги до протоколу з реалізацією за замовчуванням, що надаються розширеннями, відрізняються від необов'язкових вимог протоколу. Хоча відповідні типи не повинні забезпечувати їх власну реалізацію, вимоги з реалізаціями за замовчуванням можна викликати без додаткового ланцюжка."
Mec Os

34

Інші відповіді тут, пов’язані з позначенням протоколу як "@objc", не працюють при використанні типів, що відповідають швидкості.

struct Info {
    var height: Int
    var weight: Int
} 

@objc protocol Health {
    func isInfoHealthy(info: Info) -> Bool
} 
//Error "Method cannot be marked @objc because the type of the parameter cannot be represented in Objective-C"

Для того, щоб оголосити необов'язкові протоколи, які добре працюють зі швидким, оголошуйте функції змінними замість функціональних.

protocol Health {
    var isInfoHealthy: (Info) -> (Bool)? { get set }
}

А потім реалізуйте протокол так

class Human: Health {
    var isInfoHealthy: (Info) -> (Bool)? = { info in
        if info.weight < 200 && info.height > 72 {
            return true
        }
        return false
    }
    //Or leave out the implementation and declare it as:  
    //var isInfoHealthy: (Info) -> (Bool)?
}

Потім ви можете використовувати "?" перевірити, чи функція реалізована чи ні

func returnEntity() -> Health {
    return Human()
}

var anEntity: Health = returnEntity()

var isHealthy = anEntity.isInfoHealthy(Info(height: 75, weight: 150))? 
//"isHealthy" is true

3
За допомогою цього рішення ви не можете отримати доступ до себе з будь-якої функції протоколу. Це може спричинити проблеми в деяких випадках!
Джордж Грін

1
@GeorgeGreen Ви можете отримати доступ до себе. Позначте змінну функції як ледачу та використовуйте список захоплення всередині закриття .
Заг

Тільки класи, протоколи, методи та властивості можуть використовувати @objc. Якщо ви використовуєте параметр Enum при визначенні методу протоколу @ objc, ви приречені.
хуншань

1
@khunshan, цей метод не вимагає, щоб нічого не позначалося @ objc, на що ви звертаєтесь?
Заг

його інформація по темі, що переписки не можуть бути використані між swift та objc, які для інших висловлювань можна зв'язати з ключовим словом @objc.
хуншань

34

Ось конкретний приклад із схемою делегування.

Налаштуйте свій протокол:

@objc protocol MyProtocol:class
{
    func requiredMethod()
    optional func optionalMethod()
}

class MyClass: NSObject
{
    weak var delegate:MyProtocol?

    func callDelegate()
    {
        delegate?.requiredMethod()
        delegate?.optionalMethod?()
    }
}

Встановіть делегата до класу та реалізуйте Протокол. Зверніть увагу, що необов'язковий метод не потрібно реалізовувати.

class AnotherClass: NSObject, MyProtocol
{
    init()
    {
        super.init()

        let myInstance = MyClass()
        myInstance.delegate = self
    }

    func requiredMethod()
    {
    }
}

Важливим є те, що необов'язковий метод є необов’язковим і йому потрібно "?" при дзвінку. Згадайте другий знак питання.

delegate?.optionalMethod?()

2
Це має бути правильна відповідь. Простий, чистий і пояснювальний.
Ешелон

Я згоден, ця відповідь коротка, мила і прямо до суті. Дякую!
LuAndre

31

У Swift 3.0

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

Це заощадить ваш час.


6
Будь-яке джерело, навіщо @objcце потрібно всім членам зараз?
DevAndArtist

@Gautam Sareriya: На ваш погляд, найкраще такий підхід чи створення порожніх методів розширення?
еоніст

Я намагався з вашими методами з requiredпрапорцем, але з помилками: requiredможе використовуватися лише в оголошеннях 'init'.
Бен

27
  • optionalПеред кожним методом потрібно додати ключове слово.
  • Однак зауважте, що для цього ваш протокол повинен бути позначений атрибутом @objc.
  • Це далі означає, що цей протокол буде застосовний до класів, але не до структур.

Незначне виправлення (занадто мало для редагування!), Але воно повинно бути "необов’язковим", а не "@optional"
Ali Beadle

5
Це також вимагає відзначити ваш клас, що реалізує протокол як @objc, а не лише протокол.
Джоннатан Суллінгер

13

Чистий підхід Swift з успадкуванням протоколу:

//Required methods
protocol MyProtocol {
    func foo()
}

//Optional methods
protocol MyExtendedProtocol: MyProtocol {
    func bar()
}

class MyClass {
    var delegate: MyProtocol
    func myMethod() {
        (delegate as? MyExtendedProtocol).bar()
    }
}

11

Щоб проілюструвати механіку відповіді Антуана:

protocol SomeProtocol {
    func aMethod()
}

extension SomeProtocol {
    func aMethod() {
        print("extensionImplementation")
    }
}

class protocolImplementingObject: SomeProtocol {

}

class protocolImplementingMethodOverridingObject: SomeProtocol {
    func aMethod() {
        print("classImplementation")
    }
}

let noOverride = protocolImplementingObject()
let override = protocolImplementingMethodOverridingObject()

noOverride.aMethod() //prints "extensionImplementation"
override.aMethod() //prints "classImplementation"

8

Я думаю, що перш ніж запитати, як можна реалізувати необов’язковий метод протоколу, ви повинні запитати, чому ви повинні його реалізувати.

Якщо ми розглядаємо швидкі протоколи як інтерфейс у класичному об'єктно-орієнтованому програмуванні, необов'язкові методи не мають особливого сенсу, і, можливо, кращим рішенням було б створити реалізацію за замовчуванням або розділити протокол на набір протоколів (можливо, з деякими відносинами успадкування між ними) представляти можливе поєднання методів у протоколі.

Для подальшого читання див. Https://useyourloaf.com/blog/swift-optional-protocol-methods/ , який дає чудовий огляд цього питання.


Це. Це дотримання принципу "Я" в принципах розробки програмного забезпечення SOLID .
Ерік

7

Трохи від теми від початкового питання, але це вибудовує ідею Антуана, і я думав, що це може комусь допомогти.

Ви також можете зробити обчислювані властивості необов’язковими для структур з розширеннями протоколу.

Ви можете зробити властивість протоколу необов'язковою

protocol SomeProtocol {
    var required: String { get }
    var optional: String? { get }
}

Реалізуйте обчислену властивість фіктивного модуля в розширенні протоколу

extension SomeProtocol {
    var optional: String? { return nil }
}

Тепер ви можете використовувати структури, які не мають чи не реалізовані необов'язкові властивості

struct ConformsWithoutOptional {
    let required: String
}

struct ConformsWithOptional {
    let required: String
    let optional: String?
}

Я також написав, як робити необов'язкові властивості в протоколах Swift на своєму блозі , які я буду постійно оновлювати, якщо все зміниться через версії Swift 2.


5

Як створити необов'язкові та потрібні методи делегації.

@objc protocol InterViewDelegate:class {

    @objc optional func optfunc()  //    This is optional
    func requiredfunc()//     This is required 

}

3

Ось дуже простий приклад для швидких класів ТІЛЬКИ, а не для структур або перерахувань. Зауважте, що метод протоколу, необов'язковий, має два рівні необов'язкового ланцюга під час відтворення. Також класу, що приймає протокол, потрібен атрибут @objc у своїй декларації.

@objc protocol CollectionOfDataDelegate{
   optional func indexDidChange(index: Int)
}


@objc class RootView: CollectionOfDataDelegate{

    var data = CollectionOfData()

   init(){
      data.delegate = self
      data.indexIsNow()
   }

  func indexDidChange(index: Int) {
      println("The index is currently: \(index)")
  }

}

class CollectionOfData{
    var index : Int?
    weak var delegate : CollectionOfDataDelegate?

   func indexIsNow(){
      index = 23
      delegate?.indexDidChange?(index!)
    }

 }

Чи можете ви трохи детальніше описати частину " двох рівнів на вибір ", а саме цього delegate?.indexDidChange?(index!):?
Unheilig

2
Якби ми написали у протоколі такий необов'язковий метод, як цей: protocol CollectionOfDataDelegate{ func indexDidChange(index: Int) } тоді ви будете називати його без знака питання: delegate?.indexDidChange(index!) Коли ви встановлюєте необов'язкові вимоги до методу в протоколі, тип, який відповідає йому, НЕ може реалізувати цей метод , тому ?використовується для перевірки на реалізацію, а якщо немає, програма не вийде з ладу. @Unheilig
Blessing Lopes

weak var delegate : CollectionOfDataDelegate?(Забезпечуєте слабку орієнтацію?)
Альфі Ханссен

@BlessingLopes Чи можете ви додати у відповідь своє пояснення delegate?використання? Ця інформація дійсно повинна належати там іншим у майбутньому. Я хочу підкреслити це, але ця інформація дійсно повинна відповідати.
Джоннатан Суллінгер

3

якщо ви хочете зробити це в найкоротші терміни, найкращим способом є надання детальної інформації про реалізацію за замовчуванням, якщо ви повернете тип Swift, наприклад, структура з типами Swift

приклад:

struct magicDatas {
    var damagePoints : Int?
    var manaPoints : Int?
}

protocol magicCastDelegate {
    func castFire() -> magicDatas
    func castIce() -> magicDatas
}

extension magicCastDelegate {
    func castFire() -> magicDatas {
        return magicDatas()
    }

    func castIce() -> magicDatas {
        return magicDatas()
    }
}

тоді ви можете реалізовувати протокол без визначення кожного функціоналу


2

Є два способи створити необов'язковий метод у швидкому протоколі.

1 - Перший варіант - позначити ваш протокол за допомогою атрибута @objc. Хоча це означає, що його можна прийняти лише класами, це означає, що ви позначаєте окремі методи як необов'язкові, як це:

@objc protocol MyProtocol {
    @objc optional func optionalMethod()
}

2 - Швидший спосіб: цей варіант кращий. Напишіть реалізацію за замовчуванням додаткових методів, які нічого не роблять, як це.

protocol MyProtocol {
    func optionalMethod()
    func notOptionalMethod()
}

extension MyProtocol {

    func optionalMethod() {
        //this is a empty implementation to allow this method to be optional
    }
}

Swift має функцію під назвою розширення, яка дозволяє нам забезпечити реалізацію за замовчуванням для тих методів, які ми хочемо бути необов'язковими.


0

Один варіант - зберігати їх як необов’язкові змінні функції:

struct MyAwesomeStruct {
    var myWonderfulFunction : Optional<(Int) -> Int> = nil
}

let squareCalculator =
    MyAwesomeStruct(myWonderfulFunction: { input in return input * input })
let thisShouldBeFour = squareCalculator.myWonderfulFunction!(2)

-1

Визначте функцію в протоколі та створіть розширення для цього протоколу, а потім створіть порожню реалізацію для функції, яку ви хочете використовувати як необов'язковий.


-2

Щоб Optional Protocolшвидко визначитись, слід використати @objcключове слово перед Protocolоголошенням та attribute/ methodдекларацією всередині цього протоколу. Нижче наведено зразок необов’язкової властивості протоколу.

@objc protocol Protocol {

  @objc optional var name:String?

}

class MyClass: Protocol {

   // No error

}

2
Хоча це може відповісти на питання, краще додати деякий опис того, як ця відповідь може допомогти вирішити проблему. Прочитайте, будь ласка, як мені написати гарну відповідь, щоб дізнатися більше.
Рошана Пітігала

-23

Поставте @optionalперед методами чи властивостями.


Помилка компілятора: атрибут "необов'язковий" можна застосувати лише до членів протоколу @objc.
Селвін

3
@optionalнавіть не є правильним ключовим словом. Це так optional, і ви повинні оголосити клас та протокол з @objcатрибутом.
Джоннатан Суллінгер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.