Pertanyaan Di Swift, bagaimana saya bisa mendeklarasikan variabel dari tipe tertentu yang sesuai dengan satu atau lebih protokol?


Di Swift saya dapat secara eksplisit mengatur jenis variabel dengan menyatakannya sebagai berikut:

var object: TYPE_NAME

Jika kita ingin melangkah lebih jauh dan mendeklarasikan variabel yang sesuai dengan beberapa protokol yang dapat kita gunakan protocol deklaratif:

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

Bagaimana jika saya ingin menyatakan suatu objek yang sesuai dengan satu atau lebih protokol dan juga dari jenis kelas dasar tertentu? The Objective-C equivalent akan terlihat seperti ini:

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

Dalam Swift, saya mengharapkannya terlihat seperti ini:

var object: TYPE_NAME,ProtocolOne//etc

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

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

Contoh

Sebagai contoh, katakan saya punya UITableViewCell pabrik yang bertanggung jawab untuk mengembalikan sel-sel yang sesuai dengan protokol. Kita dapat dengan mudah mengatur fungsi umum yang mengembalikan sel-sel yang sesuai dengan protokol:

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

nanti saya ingin dequeue sel-sel ini sambil memanfaatkan baik 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 UITableViewCell dan sesuai dengan MyProtocol dalam deklarasi variabel?

Pembenaran

Jika Anda terbiasa dengan Pola Pabrik ini akan masuk akal dalam konteks mampu mengembalikan objek dari kelas tertentu yang mengimplementasikan antarmuka tertentu.

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

Sementara tipe yang disediakan tidak tepat sesuai dengan antarmuka yang disebutkan, objek pengembalian pabrik tidak dan jadi saya ingin fleksibilitas dalam berinteraksi dengan kedua jenis kelas dasar dan antarmuka protokol yang dinyatakan


76
2017-10-16 10:12


asal


Jawaban:


Dalam Swift 4 sekarang mungkin untuk mendeklarasikan variabel yang merupakan subkelas dari suatu jenis dan mengimplementasikan satu atau lebih protokol pada saat yang sama.

var myVariable: MyClass & MyProtocol & MySecondProtocol

atau sebagai parameter dari suatu metode:

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

Apple mengumumkan ini di WWDC 2017 di Sesi 402: Apa yang baru di Swift

Kedua, saya ingin berbicara tentang menyusun kelas dan protokol. Jadi disini   Saya telah memperkenalkan protokol yang dapat dirubah ini untuk elemen UI yang dapat diberikan   efek goyangan kecil untuk menarik perhatian ke dirinya sendiri. Dan saya pergi ke depan   dan memperluas beberapa kelas UIKit untuk benar-benar memberikan goyangan ini   fungsionalitas. Dan sekarang saya ingin menulis sesuatu yang tampaknya sederhana. saya   hanya ingin menulis fungsi yang mengambil banyak kontrol yang   goyang dan getar orang-orang yang diaktifkan untuk menarik perhatian   mereka. Tipe apa yang bisa saya tulis di sini dalam array ini? Itu sebenarnya   frustasi dan sulit. Jadi, saya bisa mencoba menggunakan kontrol UI. Tapi tidak   semua kontrol UI dapat digoyang dalam game ini. Saya bisa mencoba goyah, tapi   tidak semua shakables adalah kontrol UI. Dan sebenarnya tidak ada cara yang baik untuk melakukannya   mewakili ini di Swift 3. Swift 4 memperkenalkan gagasan menulis   kelas dengan sejumlah protokol.


42
2017-07-24 09:07



Anda tidak dapat mendeklarasikan variabel seperti

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

atau mendeklarasikan fungsi tipe kembalian seperti

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

Anda dapat mendeklarasikan sebagai parameter fungsi seperti ini, tetapi pada dasarnya 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)

Seperti yang sekarang, semua yang dapat Anda lakukan adalah 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 cell identik dengan asProtocol.

Tapi, seperti untuk compiler, cell memiliki antarmuka UITableViewCell hanya, sementara asProtocol hanya memiliki antarmuka protokol. Jadi, ketika Anda ingin menelepon UITableViewCellmetode, Anda harus menggunakan cell variabel. Ketika Anda ingin memanggil metode protokol, gunakan asProtocol variabel.

Jika Anda yakin bahwa sel sesuai dengan protokol yang tidak perlu Anda gunakan if let ... as? ... {}. seperti:

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

30
2017-10-16 11:51



Sayangnya, Swift tidak mendukung penyesuaian protokol tingkat objek. Namun, ada pekerjaan yang agak canggung yang dapat melayani 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 saja 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.

Contohnya:

class SomeClass {
   let mySpecialViewController: VCWithSomeProtocol

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

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

Semoga Swift 4 akan memungkinkan kita untuk mendeklarasikan objek dengan protokol yang melekat padanya di masa depan. Tetapi untuk sekarang, ini berhasil.

Semoga ini membantu!


2
2017-12-19 15:53



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 objek dari subkelas yang diberikan, dan itu adalah cerita lain sebagai jawaban yang diterima menunjukkan. Jawaban ini berbicara tentang kesesuaian protokol untuk kelas dasar.

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

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 sudah direferensikan, lihat juga artikel Nate Cooks Fungsi umum untuk jenis yang tidak kompatibel dengan contoh lebih lanjut.

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

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

Adopsi Protokol akan melakukan hal ini, membuat objek mematuhi protokol yang diberikan. Namun sadarilah sisi buruknya, bahwa variabel dari tipe protokol yang diberikan tidak tidak tahu apa pun di luar protokol. Tapi ini dapat dielakkan dengan mendefinisikan protokol yang memiliki semua metode / variabel yang dibutuhkan / ...

Sementara tipe yang disediakan tidak tepat sesuai dengan antarmuka yang disebutkan, objek pengembalian pabrik tidak dan jadi saya ingin fleksibilitas dalam berinteraksi dengan kedua jenis kelas dasar dan antarmuka protokol yang dinyatakan

Jika Anda ingin metode generik, variabel agar sesuai dengan tipe protokol dan kelas dasar, Anda mungkin kurang beruntung. Tapi kedengarannya seperti Anda perlu mendefinisikan protokol yang cukup luas untuk memiliki metode kesesuaian yang diperlukan, dan pada saat yang sama cukup sempit untuk memiliki opsi untuk mengadopsinya ke kelas dasar tanpa terlalu banyak pekerjaan (yaitu hanya menyatakan bahwa kelas sesuai dengan protokol).


1
2018-03-07 04:37



Saya pernah memiliki situasi yang sama ketika mencoba untuk menghubungkan koneksi interaksi generik saya di Storyboard (IB tidak akan memungkinkan Anda untuk menghubungkan outlet ke protokol, hanya contoh objek), yang saya dapatkan hanya dengan menutupi ivar kelas dasar publik dengan perhitungan pribadi milik. Meskipun hal ini tidak mencegah seseorang dari membuat penugasan ilegal, itu menyediakan cara yang nyaman untuk secara aman mencegah interaksi yang tidak diinginkan dengan instance yang tidak sesuai pada waktu proses. (mis. mencegah memanggil 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)
    }
}

The "outputReceiver" dinyatakan opsional, seperti "protocolOutputReceiver" pribadi. Dengan selalu mengakses outputReceiver (delegasi a.k.a) melalui yang terakhir (properti yang dihitung), saya secara efektif memfilter objek apa pun yang tidak sesuai dengan protokol. Sekarang saya hanya bisa menggunakan chaining opsional untuk memanggil aman ke objek delegasi apakah atau tidak mengimplementasikan protokol atau bahkan ada.

Untuk menerapkan ini ke situasi Anda, Anda dapat memiliki ivar publik bertipe "YourBaseClass?" (Berbeda dengan AnyObject), dan gunakan properti pribadi yang dikomputasi untuk menerapkan kesesuaian protokol. FWIW.


0
2017-11-05 05:51