Di Swift, bagaimana cara mendeklarasikan variabel dengan tipe tertentu yang sesuai dengan satu atau lebih protokol?

96

Di Swift saya dapat secara eksplisit mengatur tipe variabel dengan mendeklarasikannya sebagai berikut:

var object: TYPE_NAME

Jika kita ingin melangkah lebih jauh dan mendeklarasikan variabel yang sesuai dengan beberapa protokol, kita dapat menggunakan protocoldeklaratif:

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

Bagaimana jika saya ingin mendeklarasikan objek yang sesuai dengan satu atau lebih protokol dan juga dari tipe kelas dasar tertentu? Persamaan Objective-C akan terlihat seperti ini:

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

Di Swift saya berharap akan terlihat seperti ini:

var object: TYPE_NAME,ProtocolOne//etc

Ini memberi kami fleksibilitas untuk dapat menangani implementasi tipe dasar serta antarmuka tambahan yang ditentukan dalam protokol.

Apakah ada cara lain yang lebih jelas yang mungkin saya lewatkan?

Contoh

Sebagai contoh, katakanlah saya memiliki UITableViewCellpabrik yang bertanggung jawab untuk mengembalikan sel yang sesuai dengan protokol. Kita dapat dengan mudah mengatur fungsi generik yang mengembalikan sel yang sesuai dengan protokol:

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

nanti saya ingin menghapus sel-sel ini sambil memanfaatkan jenis dan protokolnya

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

Ini mengembalikan kesalahan karena sel tampilan tabel tidak sesuai dengan protokol ...

Saya ingin dapat menentukan bahwa sel adalah a UITableViewCelldan sesuai dengan MyProtocoldeklarasi variabel?

Pembenaran

Jika Anda sudah familiar dengan Factory Pattern, ini akan masuk akal dalam konteks untuk mengembalikan objek dari kelas tertentu yang mengimplementasikan antarmuka tertentu.

Sama seperti dalam contoh saya, terkadang kami ingin mendefinisikan antarmuka yang masuk akal saat diterapkan ke objek tertentu. Contoh saya dari sel tampilan tabel adalah salah satu justifikasi tersebut.

Sementara tipe yang disediakan tidak sepenuhnya sesuai dengan antarmuka yang disebutkan, objek yang dikembalikan pabrik melakukannya dan saya ingin fleksibilitas dalam berinteraksi dengan tipe kelas dasar dan antarmuka protokol yang dinyatakan

Daniel Galasko
sumber
Maaf, tapi apa gunanya cepat. Tipe sudah tahu protokol apa yang mereka sesuaikan. Apa tidak hanya menggunakan tipe?
Kirsteins
1
@Kirsteins Tidak, kecuali tipe dikembalikan dari pabrik dan karenanya merupakan tipe generik dengan kelas dasar yang sama
Daniel Galasko
Tolong beri contoh jika memungkinkan.
Kirstein
NSSomething<ABCProtocolOne,ABCProtocolTwo> * object = ...;. Objek ini sepertinya tidak berguna karena NSSomethingsudah tahu apa yang sesuai dengannya. Jika tidak sesuai dengan salah satu protokol di <>Anda akan mengalami unrecognised selector ...crash. Ini tidak memberikan keamanan tipe sama sekali.
Kirsteins
@Kirsteins Silakan lihat contoh saya lagi, ini digunakan ketika Anda tahu bahwa objek yang dijual pabrik Anda adalah kelas dasar tertentu yang sesuai dengan protokol yang ditentukan
Daniel Galasko

Jawaban:

72

Di Swift 4 sekarang dimungkinkan untuk mendeklarasikan variabel yang merupakan subclass dari suatu tipe dan mengimplementasikan satu atau lebih protokol pada saat yang bersamaan.

var myVariable: MyClass & MyProtocol & MySecondProtocol

Untuk melakukan variabel opsional:

var myVariable: (MyClass & MyProtocol & MySecondProtocol)?

atau sebagai parameter metode:

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

Apple mengumumkan ini di WWDC 2017 di Sesi 402: Yang baru di Swift

Kedua, saya ingin berbicara tentang menyusun kelas dan protokol. Jadi, di sini saya telah memperkenalkan protokol yang dapat digoyahkan untuk elemen UI yang dapat memberikan sedikit efek goyang untuk menarik perhatian ke dirinya sendiri. Dan saya telah melanjutkan dan memperluas beberapa kelas UIKit untuk benar-benar menyediakan fungsionalitas goyang ini. Dan sekarang saya ingin menulis sesuatu yang kelihatannya sederhana. Saya hanya ingin menulis sebuah fungsi yang mengambil sekumpulan kontrol yang dapat diguncang dan mengguncang kontrol yang diaktifkan untuk menarik perhatian mereka. Jenis apa yang bisa saya tulis di sini dalam larik ini? Ini sebenarnya membuat frustrasi dan rumit. Jadi, saya bisa mencoba menggunakan kontrol UI. Namun tidak semua kontrol UI dapat diguncang dalam game ini. Saya dapat mencoba shakable, tetapi tidak semua shakable adalah kontrol UI. Dan sebenarnya tidak ada cara yang baik untuk merepresentasikan ini di Swift 3.Swift 4 memperkenalkan gagasan menyusun kelas dengan sejumlah protokol.

Philipp Otto
sumber
3
Hanya menambahkan tautan ke proposal evolusi cepat github.com/apple/swift-evolution/blob/master/proposals/…
Daniel Galasko
Terima kasih Philipp!
Omar Albeik
bagaimana jika membutuhkan variabel opsional jenis ini?
Vyachaslav Gerchicov
2
@VyachaslavGerchicov: Anda dapat meletakkan tanda kurung di sekitarnya dan kemudian tanda tanya seperti ini: var myVariable: (MyClass & MyProtocol & MySecondProtocol)?
Philipp Otto
30

Anda tidak dapat mendeklarasikan variabel seperti

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

atau mendeklarasikan tipe pengembalian fungsi seperti

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

Anda dapat mendeklarasikan sebagai parameter fungsi seperti ini, tetapi pada dasarnya ini adalah up-casting.

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)

Untuk saat ini, yang dapat Anda lakukan hanyalah seperti:

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

Dengan ini, secara teknis cellidentik dengan asProtocol.

Tetapi, untuk compiler, hanya cellmemiliki interface UITableViewCell, sedangkan asProtocolinterface hanya protokol. Jadi, ketika Anda ingin memanggil UITableViewCellmetode, Anda harus menggunakan cellvariabel. Jika Anda ingin memanggil metode protokol, gunakan asProtocolvariabel.

Jika Anda yakin bahwa sel sesuai dengan protokol, Anda tidak perlu menggunakan if let ... as? ... {}. Suka:

let cell = CellFactory.createCellForItem(special)
let asProtocol = cell as protocol<MyProtocol1,MyProtocol2>
rintaro
sumber
Karena pabrik menentukan jenis pengembalian, saya secara teknis tidak perlu melakukan pemeran opsional? Saya hanya bisa mengandalkan pengetikan implisit swift untuk melakukan pengetikan di mana saya secara eksplisit mendeklarasikan protokol?
Daniel Galasko
Saya tidak mengerti apa yang Anda maksud, maaf atas kemampuan bahasa Inggris saya yang buruk. Jika Anda mengatakan tentang -> UITableViewCell<MyProtocol>, ini tidak valid, karena UITableViewCellbukan tipe generik. Saya pikir ini bahkan tidak dapat dikompilasi.
rintaro
Saya tidak mengacu pada implementasi umum Anda melainkan ilustrasi contoh implementasi Anda. di mana Anda mengatakan biarkan asProtocol = ...
Daniel Galasko
atau, saya bisa melakukan: var cell: protocol <ProtocolOne, ProtocolTwo> = someObject as UITableViewCell dan mendapatkan manfaat keduanya dalam satu variabel
Daniel Galasko
2
Saya rasa tidak. Bahkan jika Anda bisa melakukan seperti itu, cellhanya memiliki metode protokol (untuk kompilator).
rintaro
2

Sayangnya, Swift tidak mendukung kesesuaian protokol tingkat objek. Namun, ada solusi yang agak canggung yang dapat memenuhi tujuan Anda.

struct VCWithSomeProtocol {
    let protocol: SomeProtocol
    let viewController: UIViewController

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

Kemudian, di mana pun Anda perlu melakukan apa pun yang dimiliki UIViewController, Anda akan mengakses aspek .viewController dari struct dan apa pun yang Anda butuhkan dari aspek protokol, Anda akan mereferensikan .protocol.

Misalnya:

class SomeClass {
   let mySpecialViewController: VCWithSomeProtocol

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

Sekarang kapan pun Anda membutuhkan mySpecialViewController untuk melakukan apa pun yang terkait dengan UIViewController, Anda cukup mereferensikan mySpecialViewController.viewController dan kapan pun Anda membutuhkannya untuk melakukan beberapa fungsi protokol, Anda mereferensikan mySpecialViewController.protocol.

Mudah-mudahan Swift 4 akan memungkinkan kita untuk mendeklarasikan objek dengan protokol yang menyertainya di masa mendatang. Tapi untuk saat ini, ini berhasil.

Semoga ini membantu!

Michael Curtis
sumber
1

EDIT: Saya salah , tetapi jika orang lain membaca kesalahpahaman ini seperti saya, saya meninggalkan jawaban ini di luar sana. OP bertanya tentang memeriksa kesesuaian protokol dari objek subkelas tertentu, dan itu adalah cerita lain seperti yang ditunjukkan oleh jawaban yang diterima. Jawaban ini berbicara tentang kesesuaian protokol untuk kelas dasar.

Mungkin saya salah, tetapi apakah Anda tidak berbicara tentang menambahkan kesesuaian protokol ke UITableCellViewkelas? Protokol dalam hal ini diperluas ke kelas dasar, dan bukan objeknya. Lihat dokumentasi Apple tentang Deklarasi Adopsi Protokol dengan Ekstensi yang dalam kasus Anda akan seperti ini:

extension UITableCellView : ProtocolOne {}

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

Selain dokumentasi Swift yang telah direferensikan, lihat juga artikel Nate Cooks Fungsi generik untuk tipe yang tidak kompatibel dengan contoh lebih lanjut.

Ini memberi kami fleksibilitas untuk dapat menangani implementasi tipe dasar serta antarmuka tambahan yang ditentukan dalam protokol.

Apakah ada cara lain yang lebih jelas yang mungkin saya lewatkan?

Protokol Adopsi hanya akan melakukan ini, membuat objek mematuhi protokol yang diberikan. Sadarilah sisi negatifnya, bahwa variabel dari jenis protokol tertentu tidak mengetahui apa pun di luar protokol. Tetapi ini dapat dielakkan dengan mendefinisikan protokol yang memiliki semua metode / variabel / ...

Sementara tipe yang disediakan tidak sepenuhnya sesuai dengan antarmuka yang disebutkan, objek yang dikembalikan pabrik melakukannya dan saya ingin fleksibilitas dalam berinteraksi dengan tipe kelas dasar dan antarmuka protokol yang dinyatakan

Jika Anda menginginkan metode umum, variabel untuk menyesuaikan dengan protokol dan tipe kelas dasar, Anda mungkin kurang beruntung. Tapi sepertinya Anda perlu mendefinisikan protokol cukup luas untuk memiliki metode kesesuaian yang diperlukan, dan pada saat yang sama cukup sempit untuk memiliki opsi untuk mengadopsi ke kelas dasar tanpa terlalu banyak pekerjaan (yaitu hanya menyatakan bahwa kelas sesuai dengan protokol).

Holroy
sumber
1
Bukan itu yang saya bicarakan sama sekali, tetapi terima kasih :) Saya ingin dapat berinteraksi dengan suatu objek melalui kelasnya dan protokol tertentu. Sama seperti bagaimana di obj-c saya dapat melakukan NSObject <MyProtocol> obj = ... Tidak perlu dikatakan ini tidak dapat dilakukan dengan cepat, Anda harus mentransmisikan objek ke protokolnya
Daniel Galasko
0

Saya pernah mengalami situasi serupa ketika mencoba menautkan koneksi interaktor generik saya di Storyboards (IB tidak akan mengizinkan Anda untuk menghubungkan outlet ke protokol, hanya contoh objek), yang saya hadapi hanya dengan menutupi ivar publik kelas dasar dengan komputasi pribadi Properti. Meskipun hal ini tidak mencegah seseorang untuk membuat penugasan ilegal, hal ini memberikan cara yang nyaman untuk mencegah interaksi yang tidak diinginkan dengan aman dengan instance yang tidak sesuai pada waktu proses. (yaitu mencegah pemanggilan metode delegasi ke objek yang tidak sesuai dengan protokol.)

Contoh:

@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" dideklarasikan opsional, begitu juga dengan "protocolOutputReceiver" pribadi. Dengan selalu mengakses outputReceiver (alias delegate) melalui yang terakhir (properti yang dihitung), saya secara efektif memfilter objek apa pun yang tidak sesuai dengan protokol. Sekarang saya cukup menggunakan rangkaian opsional untuk dengan aman memanggil objek delegasi apakah itu mengimplementasikan protokol atau bahkan ada.

Untuk menerapkan ini pada situasi Anda, Anda dapat memiliki ivar publik bertipe "YourBaseClass?" (sebagai lawan dari AnyObject), dan menggunakan properti komputasi privat untuk menegakkan kesesuaian protokol. FWIW.

quickthyme
sumber