Swift protokolünde isteğe bağlı yöntemler nasıl tanımlanır?


359

Swift'te mümkün mü? Değilse, bunu yapmak için bir geçici çözüm var mı?


1
Sonunda kendimi cevabımı güncellemeye motive ettim, kabul etmeyi tekrar düşünebilirsiniz.
akashivskyy

@akashivskyy Cevabınızı kabul ediyorum çünkü mevcut tüm seçenekleri, artılarını ve eksilerini daha iyi gösteriyor.
Selvin

Yanıtlar:


507

1. Varsayılan uygulamaları kullanma (tercih edilir).

protocol MyProtocol {
    func doSomething()
}

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

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

Avantajları

  • Hiçbir Objective-C çalışma zamanı söz konusu değildir (en azından açıkça değil). Bu, yapıları, sıralamaları ve NSObjectsınıf dışı yapılara uyum sağlayabileceğiniz anlamına gelir . Ayrıca, bu güçlü jenerik sistemden yararlanabileceğiniz anlamına gelir.

  • Böyle bir protokole uyan türlerle karşılaştığınızda her zaman tüm gereksinimlerin karşılandığından emin olabilirsiniz . Her zaman ya somut uygulama ya da varsayılan uygulama. "Arayüzler" veya "sözleşmeler" diğer dillerde böyle davranır.

Dezavantajları

  • Olmayan için Voidgereksinimleri, makul bir varsayılan değeri olması gerekir her zaman mümkün değildir. Ancak, bu sorunla karşılaştığınızda, bu tür bir gereksinimin gerçekten varsayılan bir uygulaması olmaması veya API tasarımı sırasında bir hata yaptığınız anlamına gelir.

  • En azından özel dönüş değerleriyle ilgili sorunu ele almadan, varsayılan bir uygulama ile hiçbir uygulama arasında ayrım yapamazsınız . Aşağıdaki örneği düşünün:

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

    Yalnızca dönen varsayılan bir uygulama sağlarsanız true- ilk bakışta iyi olur. Şimdi, aşağıdaki sahte kodu düşünün:

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

    Böyle bir optimizasyon yapmanın bir yolu yoktur - temsilcinizin bir yöntem uygulayıp uygulamadığını bilemezsiniz.

    Bu sorunun üstesinden gelmek için birkaç farklı yol olmasına rağmen (isteğe bağlı kapanışları kullanarak, farklı işlemler için birkaç isim vermek için farklı temsilci nesneleri), bu örnek sorunu açıkça ortaya koymaktadır.


2. kullanma @objc optional.

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

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

Avantajları

  • Varsayılan uygulamaya gerek yoktur. Sadece isteğe bağlı bir yöntem veya değişken bildirirsiniz ve hazırsınız demektir.

Dezavantajları

  • Tüm uygun tiplerin Objective-C uyumlu olmasını gerektirerek protokolünüzün yeteneklerini ciddi şekilde sınırlar . Bu, yalnızca devralınan sınıfların bu NSObjectprotokole uygun olabileceği anlamına gelir . Yapı yok, numara yok, ilişkili tür yok.

  • Her zaman isteğe bağlı olarak çağrılarak veya uygun türün uygulanıp uygulanmadığını denetleyerek isteğe bağlı bir yöntemin uygulanıp uygulanmadığını kontrol etmelisiniz . Bu, sık sık isteğe bağlı yöntemler arıyorsanız, çok sayıda kaynak plakası getirebilir.


17
Bir uzantı kullanarak hızlı bir şekilde isteğe bağlı yöntemlerin geliştirilmiş yoluna bakın (aşağıda)
Daniel Kanaan

1
Ve bir örnekte isteğe bağlı protokol yönteminin desteklenmesi nasıl test edilir? respondsToSelector?
devios1

3
Sadece bunu yapma! Swift, bir nedenle protokollerde isteğe bağlı yöntemi desteklemez.
fpg1503

2
Bu yöntem parametrelerdeki opsiyonelleri desteklemez. Yani bunu yapamazsınoptional func doSomething(param: Int?)
SoftDesigner

4
Şüphesiz bu cevap bugünlerde, yıllar sonra yanlıştır . Bugün Swift'te sadece varsayılan uygulama için bir uzantı ekliyorsunuz, bu Swift'in temel bir yönü. (Aşağıdaki tüm modern cevapların gösterdiği gibi.) Bugünlerde objc bayrağını eklemek yanlış olur.
Haziran'ta Fattie

394

Swift 2 ve sonrasında, bir protokolün varsayılan uygulamalarını eklemek mümkündür. Bu protokollerde isteğe bağlı yöntemlerin yeni bir yolunu oluşturur.

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

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

İsteğe bağlı protokol yöntemleri oluşturmak için gerçekten hoş bir yol değildir, ancak protokol geri çağrılarında yapıları kullanma olanağı sunar.

Burada küçük bir özet yazdım: https://www.avanderlee.com/swift-2-0/optional-protocol-methods/


2
Bu muhtemelen Swift'te yapmanın en temiz yoludur. Çok kötü, Swift 2.0'dan önce çalışmıyor.
Entalpi

13
@MattQuiros Aslında aslında protokol tanımındaki işlevi bildirmek gerektiğini buluyorum, aksi takdirde no-op genişletme işlevi protokolü uygun sınıflarda geçersiz kılınmaz.
Ian Pearce

3
@IanPearce doğru ve bu tasarım gereği görünüyor. WWDC'deki "Protokol Odaklı Programlama" konuşmasında (408), ana protokolde uygun tiplere sunulan "özelleştirme noktaları" olan yöntemler hakkında konuşurlar. Gerekli bir özelleştirme noktası bir uzantıda tanım almaz; isteğe bağlı bir nokta. Protokolde genellikle özelleştirilmemesi gereken yöntemler, uyumluluğun özel uygulamasını istediğinizi göstermek için özellikle dynamicType'a geçmedikçe , uyumluluk türlerinin özelleştirilmesine izin vermemek için uzantıda tamamen bildirilir / tanımlanır .
1th

4
@FranklinYu Bunu yapabilirsiniz, ancak daha sonra API tasarımınızı aslında gerekli olmadığı yerlerde 'atar' ile kirletiyorsunuz. Daha çok "mikro protokoller" fikrini seviyorum. Örneğin, her yöntem bir protokolü onaylar ve sonra aşağıdakileri kontrol edebilirsiniz: eğer nesne protokol ise
Darko

3
@Antoine Protokollerdeki işlevler tanım gereği herkese açık olduğundan, uzantıdaki işlevi herkese açık hale getirmeniz gerektiğini düşünüyorum. Protokol modülünün dışında kullanıldığında çözümünüz çalışmaz.
Vadim Eisenberg

39

İsteğe bağlı değiştiricinin ve @objc özelliğinin nasıl kullanılacağına ilişkin bazı yanıtlar bulunduğundan bağlı gereksinim protokolünü tanımlamak için isteğe isteğe bağlı protokol tanımlayan protokol uzantılarının nasıl kullanılacağı hakkında bir örnek vereceğim.

Aşağıdaki kod 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*

Lütfen protokol uzantısı yöntemlerinin Objective-C kodu tarafından çağrılamadığını ve Swift ekibinin bunu düzeltmeyeceğini unutmayın. https://bugs.swift.org/browse/SR-492


1
Aferin! Objective-C çalışma zamanı dahil olmadan sorunu çözdüğü için bu doğru cevap olmalıdır.
Lukas

gerçekten hoş biri!
tania_S

hızlı belgelerden - "Uzantılar tarafından sağlanan varsayılan uygulamalara sahip protokol gereksinimleri, isteğe bağlı protokol gereksinimlerinden farklıdır. Uyumlu türlerin kendi uygulamalarını sağlaması gerekmese de, varsayılan uygulamalara sahip gereksinimler, isteğe bağlı zincirleme olmadan çağrılabilir."
Mec Os

34

Burada, protokole "@objc" olarak işaretleme ile ilgili diğer cevaplar, hızlı özel türler kullanılırken çalışmaz.

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"

Hızlı ile iyi çalışan isteğe bağlı protokolleri bildirmek için işlevleri işlevler yerine değişkenler olarak bildirin.

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

Ve sonra protokolü aşağıdaki gibi uygulayın

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

Daha sonra "?" işlevin uygulanıp uygulanmadığını kontrol etmek

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

var anEntity: Health = returnEntity()

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

3
Bu çözüm sayesinde hiçbir protokol fonksiyonunun içinden kendinize erişemezsiniz. Bu, bazı durumlarda sorunlara neden olabilir!
George Green

1
@GeorgeGreen Kendine erişebilirsiniz. İşlev değişkenini tembel olarak işaretleyin ve kapağın içinde bir yakalama listesi kullanın .
Zag

@Objc işlevini yalnızca sınıflar, protokoller, yöntemler ve özellikler kullanabilir. @ Objc protokolü yöntemi tanımlamasında bir Enum parametresi kullanıyorsanız, mahkum olursunuz.
khunshan

1
@khunshan, bu yöntem @ objc olarak işaretlenmiş hiçbir şey gerektirmez, ne demek istiyorsun?
Zag

enums'ın swift ve objc arasında kullanılamadığı, diğer ifadeler için @objc anahtar sözcüğü ile köprülenebileceği bir bilgidir.
khunshan

34

İşte heyet örneği ile somut bir örnek.

Protokolünüzü kurun:

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

class MyClass: NSObject
{
    weak var delegate:MyProtocol?

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

Temsilciyi bir sınıfa ayarlayın ve Protokolü uygulayın. İsteğe bağlı yöntemin uygulanması gerekmediğine bakın.

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

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

    func requiredMethod()
    {
    }
}

Önemli bir şey, isteğe bağlı yöntemin isteğe bağlı olması ve bir "?" arama yaparken. İkinci soru işaretinden bahsedin.

delegate?.optionalMethod?()

2
Bu doğru cevap olmalı. Basit, temiz ve açıklayıcı.
Echelon

Katılıyorum, bu cevap kısa, tatlı ve doğrudan doğruya. Teşekkürler!
LuAndre

31

In Swift 3.0

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

Zaman kazandıracak.


6
@objcTüm üyelerde neden herhangi bir kaynağa ihtiyaç var?
DevAndArtist

@Gautam Sareriya: Sizce bu yaklaşım veya boş uzantı yöntemleri oluşturmak en iyisi nedir?
eonist

Ben yöntem ile requiredbayrak, ancak hatalarla denedim : requiredsadece 'init' bildirimlerinde kullanılabilir.
Ben

27
  • optionalHer yöntemden önce anahtar kelime eklemeniz gerekir .
  • Ancak, bunun çalışması için protokolünüzün @objc özelliğiyle işaretlenmesi gerektiğini lütfen unutmayın .
  • Bu ayrıca, bu protokolün sınıflar için geçerli olacağı, ancak yapılar için geçerli olmayacağı anlamına gelir.

Küçük düzeltme (düzenlemek için çok küçük!) Ancak 'isteğe bağlı' değil 'isteğe bağlı' olmalıdır
Ali Beadle

5
Bu aynı zamanda protokolü uygulayan sınıfınızı @objcsadece protokol olarak değil, işaretlemenizi gerektirir .
Johnathon Sullinger

13

Protokol devralma ile saf bir Swift yaklaşımı:

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

Antoine'nin cevabının mekaniğini göstermek için:

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

İsteğe bağlı bir protokol yöntemini nasıl uygulayabileceğinizi sormadan önce , neden bir protokol yöntemi uygulayacağınızı sormanız gerektiğini düşünüyorum .

Hızlı protokolleri klasik nesne yönelimli programlamada bir Arayüz olarak düşünürsek , isteğe bağlı yöntemler çok mantıklı değildir ve belki de daha iyi bir çözüm, varsayılan uygulamayı oluşturmak veya protokolü bir dizi protokole (belki de bazı miras ilişkileriyle) ayırmak olacaktır. (protokoller arasındaki yöntemlerin olası kombinasyonunu temsil etmek için).

Daha fazla okuma için, bu konu hakkında mükemmel bir genel bakış sunan https://useyourloaf.com/blog/swift-optional-protocol-methods/ adresine bakın .


Bu. SOLID yazılım geliştirme ilkelerindeki "I" ilkesine bağlılıktır .
Eric

7

Orijinal sorudan biraz konu dışı, ama Antoine'nin fikrini geliştiriyor ve birisine yardımcı olabileceğini düşündüm.

Ayrıca, hesaplanan özellikleri protokol uzantılarına sahip yapılar için isteğe bağlı yapabilirsiniz.

Protokolde bir özelliği isteğe bağlı yapabilirsiniz

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

Protokol uzantısına kukla hesaplanmış özelliği uygulayın

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

Ve şimdi isteğe bağlı özellik uygulanmış veya uygulanmamış yapıları kullanabilirsiniz

struct ConformsWithoutOptional {
    let required: String
}

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

Ayrıca , blogumdaki Swift protokollerinde isteğe bağlı özelliklerin nasıl yapılacağını da yazdım , bu da Swift 2 sürümlerinde bir şeylerin değişmesi durumunda güncellenmeye devam edeceğim.


5

İsteğe bağlı ve gerekli temsilci yöntemleri nasıl oluşturulur.

@objc protocol InterViewDelegate:class {

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

}

3

İşte SADECE hızlı Sınıflar için çok basit bir örnek, yapılar veya numaralandırmalar için değil. Protokol yönteminin isteğe bağlı olduğunu, oyunda iki isteğe bağlı zincirleme seviyesine sahip olduğunu unutmayın. Ayrıca protokolü benimseyen sınıfın beyanında @objc özelliğine ihtiyaç vardır.

@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!)
    }

 }

Biraz daha " oyunda isteğe bağlı iki seviye " bölümünü, yani bu: açıklayabilir misiniz delegate?.indexDidChange?(index!)?
Unheilig

2
Protokolü böyle isteğe bağlı olmayan bir yönteme sahip olacak şekilde yazmış olsaydık: protocol CollectionOfDataDelegate{ func indexDidChange(index: Int) } o zaman soru işareti olmadan çağırırsınız: delegate?.indexDidChange(index!) Bir protokolde bir yöntem için isteğe bağlı bir gereksinim ayarladığınızda, buna uyan Tür bu yöntemi uygulamıyor olabilir , bu yüzden ?uygulama kontrol etmek için kullanılır ve hiçbiri yoksa program çökmez. @Unheilig
Blessing Lopes

weak var delegate : CollectionOfDataDelegate?(zayıf referansı sağlamak?)
Alfie Hanssen

@BlessingLopes delegate?Kullanımınıza ilişkin açıklamanızı cevabınıza ekleyebilir misiniz ? Bu bilgi, gelecekte başkaları için gerçekten orada olmalıdır. Bunu onaylamak istiyorum, ama bu bilgi gerçekten cevapta olmalı.
Johnathon Sullinger

3

Eğer bunu hızlı bir şekilde yapmak istiyorsanız, örneğin Swift türleriyle yapı gibi bir Swift türünü döndürürseniz, en iyi yol varsayılan bir uygulama parçacığı sağlamaktır.

misal :

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

o zaman her fonksiyonu tanımlamaksızın protokol uygulayabilirsiniz


2

Hızlı protokolde isteğe bağlı yöntem oluşturmanın iki yolu vardır.

1 - İlk seçenek @objc özelliğini kullanarak protokolünüzü işaretlemektir. Bu, yalnızca sınıflar tarafından benimsenebileceği anlamına gelse de, bireysel yöntemleri şu şekilde isteğe bağlı olarak işaretlediğiniz anlamına gelir:

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

2 - Daha hızlı bir yol: Bu seçenek daha iyidir. Bunun gibi hiçbir şey yapmayan isteğe bağlı yöntemlerin varsayılan uygulamalarını yazın.

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

extension MyProtocol {

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

Swift, isteğe bağlı olmasını istediğimiz yöntemler için varsayılan bir uygulama sağlamamıza izin veren uzantı adı verilen bir özelliğe sahiptir.


0

Bir seçenek bunları isteğe bağlı işlev değişkenleri olarak saklamaktır:

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

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

-1

Protokolde işlevi tanımlayın ve bu protokol için uzantı oluşturun, ardından isteğe bağlı olarak kullanmak istediğiniz işlev için boş uygulama oluşturun.


-2

Optional ProtocolHızlı bir şekilde tanımlamak için , bu protokol içindeki bildirim ve / bildirimden @objcönce anahtar kelime kullanmalısınız . Aşağıda bir protokolün İsteğe Bağlı Özelliğinin bir örneği verilmiştir.Protocolattributemethod

@objc protocol Protocol {

  @objc optional var name:String?

}

class MyClass: Protocol {

   // No error

}

2
Bu soruya cevap verebilirken, bu cevabın sorunu çözmeye nasıl yardımcı olabileceğine dair bir açıklama eklemek daha iyidir. Lütfen daha fazla bilgi için nasıl iyi bir cevap yazarım? Başlıklı konuyu okuyun .
Roshana Pitigala

-23

@optionalYöntemlerin veya özelliklerin önüne koyun .


Derleyici Hatası: 'isteğe bağlı' özniteliği yalnızca @objc protokolünün üyelerine uygulanabilir.
Selvin

3
@optionaldoğru anahtar kelime bile değil. Bu optional, sınıfı ve protokolü @objcözniteliğe bildirmeniz gerekir .
Johnathon Sullinger
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.