Swift'de, bir veya daha fazla protokole uyan belirli tipte bir değişkeni nasıl tanımlayabilirim?


97

Swift'de bir değişkenin türünü aşağıdaki gibi bildirerek açıkça belirleyebilirim:

var object: TYPE_NAME

Bunu bir adım daha ileri götürmek ve birden çok protokole uyan bir değişken bildirmek istiyorsak, protocolbildirimi kullanabiliriz :

var object: protocol<ProtocolOne,ProtocolTwo>//etc

Bir veya daha fazla protokole uyan ve aynı zamanda belirli bir temel sınıf türünde olan bir nesneyi bildirmek istersem ne olur? Objective-C eşdeğeri şöyle görünür:

NSSomething<ABCProtocolOne,ABCProtocolTwo> * object = ...;

Swift'de şöyle görünmesini bekliyorum:

var object: TYPE_NAME,ProtocolOne//etc

Bu bize, protokolde tanımlanan ilave arayüzün yanı sıra temel tipin uygulanmasının üstesinden gelme esnekliği verir.

Kaçırabileceğim daha bariz başka bir yol var mı?

Misal

Örnek olarak, UITableViewCellhücreleri bir protokole uygun olarak iade etmekten sorumlu bir fabrikam olduğunu varsayalım. Bir protokole uygun hücreleri döndüren genel bir işlevi kolayca kurabiliriz:

class CellFactory {
    class func createCellForItem<T: UITableViewCell where T:MyProtocol >(item: SpecialItem,tableView: UITableView) -> T {
        //etc
    }
}

Daha sonra, hem türü hem de protokolü kullanarak bu hücrelerin kuyruğunu açmak istiyorum

var cell: MyProtocol = CellFactory.createCellForItem(somethingAtIndexPath) as UITableViewCell

Tablo görüntüleme hücresi protokole uymadığı için bu bir hata döndürür ...

Hücrenin a olduğunu UITableViewCellve MyProtocoldeğişken bildirimindeki ile uyumlu olduğunu belirtmek isterim ?

Meşrulaştırma

Fabrika Örüntüsüne aşina iseniz, bu, belirli bir arabirimi uygulayan belirli bir sınıfın nesnelerini döndürebilme bağlamında mantıklı olacaktır.

Tıpkı benim örneğimde olduğu gibi, bazen belirli bir nesneye uygulandığında anlamlı olan arayüzler tanımlamayı seviyoruz. Tablo görünümü hücresi örneğim böyle bir gerekçedir.

Sağlanan tür, belirtilen arabirime tam olarak uymasa da, fabrikanın döndürdüğü nesne uyuyor ve bu nedenle hem temel sınıf türü hem de bildirilen protokol arabirimi ile etkileşimde esneklik istiyorum.


Özür dilerim, ama bunun çabucak anlamı ne? Türler, hangi protokollere uyduklarını zaten bilirler. Sadece türü kullanmayan nedir?
Kirsteins

1
@Kirsteins Tür bir fabrikadan iade edilmedikçe ve bu nedenle ortak bir temel sınıfa sahip genel bir tür
olmadıkça hayır

Lütfen mümkünse bir örnek veriniz.
Kirsteins

NSSomething<ABCProtocolOne,ABCProtocolTwo> * object = ...;. Bu nesne, NSSomethingneye uyduğunu zaten bildiği için oldukça faydasız görünüyor . İçeri protokollerin birine uymazsa <>alacak size unrecognised selector ...çöküyor. Bu hiçbir şekilde tip güvenliği sağlamaz.
Kirsteins

@Kirsteins Lütfen tekrar örneğime bakın, fabrikanızın sattığı nesnenin belirli bir protokole uyan belirli bir temel sınıf olduğunu bildiğinizde kullanılır
Daniel Galasko

Yanıtlar:


73

Swift 4'te, bir türün alt sınıfı olan ve aynı anda bir veya daha fazla protokolü uygulayan bir değişkeni bildirmek artık mümkün.

var myVariable: MyClass & MyProtocol & MySecondProtocol

İsteğe bağlı bir değişken yapmak için:

var myVariable: (MyClass & MyProtocol & MySecondProtocol)?

veya bir yöntemin parametresi olarak:

func shakeEm(controls: [UIControl & Shakeable]) {}

Apple bunu WWDC 2017'de Oturum 402'de duyurdu : Swift'teki yenilikler

İkinci olarak, sınıflar ve protokoller oluşturmaktan bahsetmek istiyorum. Bu yüzden, burada kendine dikkat çekmek için biraz sarsılma efekti verebilen bir UI öğesi için bu sarsılabilir protokolü tanıttım. Ve bu sallama işlevini gerçekten sağlamak için bazı UIKit sınıflarını genişlettim. Ve şimdi basit görünen bir şey yazmak istiyorum. Sadece sarsılabilir bir dizi kontrolü alan ve onlara dikkat çekmeyi sağlayanları sarsan bir fonksiyon yazmak istiyorum. Bu diziye buraya ne tür yazabilirim? Aslında sinir bozucu ve aldatıcı. Yani, bir UI kontrolü kullanmayı deneyebilirim. Ancak bu oyunda tüm UI kontrolleri sarsılabilir değildir. Sarsılabilir deneyebilirim, ancak tüm titreyen öğeler UI kontrolleri değildir. Ve bunu Swift 3'te temsil etmenin aslında iyi bir yolu yok.Swift 4, herhangi bir sayıda protokol ile bir sınıf oluşturma fikrini sunar.


3
Sadece hızlı evrim önerisine bir bağlantı ekleyerek github.com/apple/swift-evolution/blob/master/proposals/…
Daniel Galasko

Teşekkür ederim Philipp!
Omar Albeik

ya bu tip bir isteğe bağlı değişkene ihtiyaç duyulursa?
Vyachaslav Gerchicov

2
@VyachaslavGerchicov: Çevresine parantez koyabilir ve ardından şöyle bir soru işareti koyabilirsiniz: var myVariable: (MyClass & MyProtocol & MySecondProtocol)?
Philipp Otto

30

Değişkeni şöyle tanımlayamazsınız

var object:Base,protocol<ProtocolOne,ProtocolTwo> = ...

ne de işlev dönüş türü bildirmek

func someFunc() -> Base,protocol<MyProtocol,Protocol2> { ... }

Bunun gibi bir işlev parametresi olarak bildirebilirsiniz, ancak temelde yukarı çevirmedir.

func someFunc<T:Base where T:protocol<MyProtocol1,MyProtocol2>>(val:T) {
    // here, `val` is guaranteed to be `Base` and conforms `MyProtocol` and `MyProtocol2`
}

class SubClass:BaseClass, MyProtocol1, MyProtocol2 {
   //...
}

let val = SubClass()
someFunc(val)

Şu an itibariyle yapabileceğiniz tek şey şöyle:

class CellFactory {
    class func createCellForItem(item: SpecialItem) -> UITableViewCell {
        return ... // any UITableViewCell subclass
    }
}

let cell = CellFactory.createCellForItem(special)
if let asProtocol = cell as? protocol<MyProtocol1,MyProtocol2> {
    asProtocol.protocolMethod()
    cell.cellMethod()
}

Bununla teknik cellolarak aynıdır asProtocol.

Ama, derleyici olarak, cellbir arayüze sahip UITableViewCellolurken, sadece asProtocoltek protokoller arayüze sahiptir. Dolayısıyla, UITableViewCellyöntemlerini çağırmak istediğinizde , celldeğişken kullanmanız gerekir . Protokol yöntemini çağırmak istediğinizde asProtocoldeğişken kullanın .

Hücrenin protokollere uygun olduğundan eminseniz, kullanmak zorunda değilsiniz if let ... as? ... {}. sevmek:

let cell = CellFactory.createCellForItem(special)
let asProtocol = cell as protocol<MyProtocol1,MyProtocol2>

Fabrika iade türlerini belirttiğine göre, teknik olarak isteğe bağlı atama yapmam gerekmiyor mu? Protokolleri açıkça beyan ettiğim yazımı gerçekleştirmek için sadece swifts örtük yazmaya güvenebilir miyim?
Daniel Galasko

Ne demek istediğini anlamıyorum, kötü İngilizce becerilerim için üzgünüm. Hakkında diyorsan -> UITableViewCell<MyProtocol>, bu geçersiz çünkü UITableViewCellgenel bir tür değil. Bence bu derlenmiyor bile.
rintaro

Genel uygulamanıza değil, uygulama örneğinize atıfta bulunuyorum. let asProtocol = ... diyorsun
Daniel Galasko

ya da şunu yapabilirdim: var cell: protocol <ProtocolOne, ProtocolTwo> = UITableViewCell olarak
bazıObject

2
Ben öyle düşünmüyorum. Böyle yapabilseniz bile cell, yalnızca protokol yöntemlerine sahiptir (derleyici için).
rintaro

2

Ne yazık ki Swift, nesne düzeyinde protokol uyumluluğunu desteklemiyor. Bununla birlikte, amaçlarınıza hizmet edebilecek biraz garip bir çözüm var.

struct VCWithSomeProtocol {
    let protocol: SomeProtocol
    let viewController: UIViewController

    init<T: UIViewController>(vc: T) where T: SomeProtocol {
        self.protocol = vc
        self.viewController = vc
    }
}

Daha sonra, UIViewController'ın sahip olduğu herhangi bir şeyi yapmanız gereken her yerde yapının .viewController yönüne ve protokol yönüne ihtiyacınız olan herhangi bir şeye erişirsiniz, .protocol'a başvurursunuz.

Örneğin:

class SomeClass {
   let mySpecialViewController: VCWithSomeProtocol

   init<T: UIViewController>(injectedViewController: T) where T: SomeProtocol {
       self.mySpecialViewController = VCWithSomeProtocol(vc: injectedViewController)
   }
}

Artık, UIViewController ile ilgili herhangi bir şey yapmak için mySpecialViewController'a ne zaman ihtiyacınız olursa olsun, sadece mySpecialViewController.viewController'a başvurursunuz ve bir protokol işlevi yapmak için buna ne zaman ihtiyacınız olursa, mySpecialViewController.protocol'a başvurursunuz.

Umarım Swift 4, gelecekte kendisine eklenen protokollere sahip bir nesne ilan etmemize izin verir. Ama şimdilik bu işe yarıyor.

Bu yardımcı olur umarım!


1

DÜZENLEME: Yanılıyordum , ama bu yanlış anlamayı benim gibi biri okursa , bu cevabı orada bırakırım. OP, belirli bir alt sınıfın nesnesinin protokol uygunluğunun kontrol edilmesini istedi ve bu, kabul edilen yanıtın gösterdiği gibi başka bir hikaye. Bu cevap, temel sınıf için protokol uygunluğundan bahseder.

Belki yanılıyorum ama UITableCellViewsınıfa protokol uygunluğu eklemekten bahsetmiyor musunuz? Protokol bu durumda nesneye değil, temel sınıfa genişletilir. Sizin durumunuzda aşağıdakilere benzer bir Uzantı ile Protokol Kabulünü Bildirme hakkındaki Apple belgelerine bakın :

extension UITableCellView : ProtocolOne {}

// Or alternatively if you need to add a method, protocolMethod()
extension UITableCellView : ProcotolTwo {
   func protocolTwoMethod() -> String {
     return "Compliant method"
   }
}

Zaten atıfta bulunulan Swift belgelerine ek olarak, başka örneklerle birlikte Nate Cooks'un uyumsuz türler için genel işlevler makalesine de bakın .

Bu bize, protokolde tanımlanan ilave arayüzün yanı sıra temel tipin uygulanmasının üstesinden gelme esnekliği verir.

Kaçırabileceğim daha bariz başka bir yol var mı?

Protokol Kabulü tam da bunu yapacak, bir nesneyi verilen protokole bağlı kılacaktır. Belirli bir protokol tip bir değişken olmadığını, olumsuz tarafının Ancak unutmayın değildir protokol şey dışını biliyorum. Ancak bu, gerekli tüm yöntemleri / değişkenleri / ... içeren bir protokol tanımlayarak aşılabilir.

Sağlanan tür, belirtilen arabirime tam olarak uymasa da, fabrikanın döndürdüğü nesne uyuyor ve bu nedenle hem temel sınıf türü hem de bildirilen protokol arabirimi ile etkileşimde esneklik istiyorum.

Hem bir protokole hem de temel sınıf türlerine uyacak bir değişken olan genel bir yöntem istiyorsanız, şansınız kalmayabilir. Ancak, protokolü ihtiyaç duyulan uygunluk yöntemlerine sahip olacak kadar geniş ve aynı zamanda çok fazla çalışma olmadan temel sınıflara uyarlama seçeneğine sahip olmak için yeterince dar tanımlamanız gerekiyor gibi görünüyor (yani bir sınıfın protokol).


1
Bahsettiğim şey bu değil ama teşekkürler :) Bir nesne ile hem sınıfı hem de belirli bir protokol aracılığıyla arayüz oluşturmak istedim. Tıpkı obj-c'de olduğu gibi NSObject <MyProtocol> obj = ... Bunun hızlı bir şekilde yapılamayacağını söylemeye gerek yok, nesneyi kendi protokolüne
çevirmelisiniz

0

Bir zamanlar genel interaktör bağlantılarımı Storyboard'larda bağlamaya çalışırken benzer bir durum yaşadım (IB, çıkışları protokollere bağlamanıza izin vermez, yalnızca nesne örneklerine izin vermez), basitçe temel sınıf public ivar'ı özel bir hesaplama ile maskeleyerek buldum. Emlak. Bu, birinin yasa dışı atamalar yapmasını engellemese de, çalışma zamanında uygun olmayan bir örnekle istenmeyen herhangi bir etkileşimi güvenli bir şekilde önlemek için uygun bir yol sağlar. (yani protokole uymayan nesnelere delege yöntemlerinin çağrılmasını engelleyin.)

Misal:

@objc protocol SomeInteractorInputProtocol {
    func getSomeString()
}

@objc protocol SomeInteractorOutputProtocol {
    optional func receiveSomeString(value:String)
}

@objc class SomeInteractor: NSObject, SomeInteractorInputProtocol {

    @IBOutlet var outputReceiver : AnyObject? = nil

    private var protocolOutputReceiver : SomeInteractorOutputProtocol? {
        get { return self.outputReceiver as? SomeInteractorOutputProtocol }
    }

    func getSomeString() {
        let aString = "This is some string."
        self.protocolOutputReceiver?.receiveSomeString?(aString)
    }
}

"OutputReceiver", özel "protocolOutputReceiver" gibi isteğe bağlı olarak ilan edilir. Her zaman outputReceiver'a (diğer adıyla temsilci) ikincisi (hesaplanan özellik) aracılığıyla erişerek, protokole uymayan tüm nesneleri etkin bir şekilde filtreliyorum. Artık, protokolü uygulayıp uygulamadığına veya var olup olmadığına bakılmaksızın delege nesnesine güvenle çağrı yapmak için isteğe bağlı zincirlemeyi kullanabilirim.

Bunu sizin durumunuza uygulamak için, genel ivar'ın "YourBaseClass?" Türünde olmasını sağlayabilirsiniz. (AnyObject'in aksine) ve protokol uygunluğunu uygulamak için özel hesaplanmış özelliği kullanın. FWIW.

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.