Bagaimana cara mendefinisikan metode opsional dalam protokol Swift?

359

Apakah mungkin di Swift? Jika tidak, adakah solusi untuk melakukannya?

Selvin
sumber
1
Saya akhirnya memotivasi diri saya untuk memperbarui jawaban saya, Anda dapat mempertimbangkan kembali menerimanya.
akashivskyy
@akashivskyy Saya menerima jawaban Anda karena lebih baik menampilkan semua opsi yang tersedia dan pro dan kontra mereka.
Selvin

Jawaban:

506

1. Menggunakan implementasi standar (lebih disukai).

protocol MyProtocol {
    func doSomething()
}

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

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

Keuntungan

  • Tidak ada runtime Objective-C yang terlibat (well, setidaknya tidak secara eksplisit). Ini berarti Anda dapat menyesuaikan struct, enum dan non- NSObjectkelas untuk itu. Juga, ini berarti Anda dapat memanfaatkan sistem obat generik yang kuat.

  • Anda selalu dapat memastikan bahwa semua persyaratan dipenuhi ketika menemukan tipe yang sesuai dengan protokol tersebut. Itu selalu merupakan implementasi konkret atau standar. Ini adalah bagaimana "antarmuka" atau "kontrak" berperilaku dalam bahasa lain.

Kekurangan

  • Untuk non- Voidpersyaratan, Anda harus memiliki nilai default yang masuk akal , yang tidak selalu memungkinkan. Namun, ketika Anda menghadapi masalah ini, itu berarti bahwa persyaratan seperti itu harus benar-benar tidak memiliki implementasi standar, atau bahwa Anda membuat kesalahan selama desain API.

  • Anda tidak dapat membedakan antara implementasi default dan tidak ada implementasi sama sekali , setidaknya tanpa mengatasi masalah itu dengan nilai pengembalian khusus. Perhatikan contoh berikut:

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

    Jika Anda memberikan implementasi default yang baru saja kembali true- baik-baik saja pada pandangan pertama. Sekarang, pertimbangkan kode pseudo berikut:

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

    Tidak ada cara untuk mengimplementasikan optimasi seperti itu - Anda tidak bisa tahu apakah delegasi Anda menerapkan metode atau tidak.

    Meskipun ada sejumlah cara berbeda untuk mengatasi masalah ini (menggunakan penutupan opsional, objek delegasi yang berbeda untuk beberapa operasi yang berbeda untuk menyebutkan beberapa), contoh itu menyajikan masalah dengan jelas.


2. Menggunakan @objc optional.

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

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

Keuntungan

  • Tidak diperlukan implementasi standar. Anda hanya mendeklarasikan metode opsional atau variabel dan Anda siap untuk pergi.

Kekurangan

  • Ini sangat membatasi kemampuan protokol Anda dengan mengharuskan semua jenis yang sesuai agar kompatibel dengan Objective-C. Ini berarti, hanya kelas-kelas yang diwarisi dari yang NSObjectdapat memenuhi protokol tersebut. Tidak ada struct, tidak ada enum, tidak ada tipe yang terkait.

  • Anda harus selalu memeriksa apakah metode opsional diterapkan dengan memanggil atau memeriksa secara opsional apakah tipe yang sesuai mengimplementasikannya. Ini mungkin memperkenalkan banyak pelat baja jika Anda sering memanggil metode opsional.

akashivskyy
sumber
17
Lihatlah cara peningkatan metode opsional di swift menggunakan ekstensi (di bawah)
Daniel Kanaan
1
Dan bagaimana satu tes untuk mendukung metode protokol opsional dalam sebuah instance? respondsToSelector?
devios1
3
Jangan lakukan ini! Swift tidak mendukung metode opsional dalam protokol karena suatu alasan.
fpg1503
2
Metode ini tidak mendukung parameter opsional. Yaitu Anda tidak bisa melakukan ituoptional func doSomething(param: Int?)
SoftDesigner
4
Tentunya jawaban ini pada dasarnya salah saat ini, bertahun-tahun kemudian. Hari ini di Swift Anda cukup menambahkan ekstensi untuk implementasi default, itu adalah aspek dasar dari Swift. (Seperti yang ditunjukkan oleh semua jawaban modern di bawah ini.) Adalah salah jika menambahkan bendera objc, saat ini.
Fattie
394

Di Swift 2 dan selanjutnya, mungkin untuk menambahkan implementasi protokol standar. Ini menciptakan cara baru metode opsional dalam protokol.

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

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

Ini bukan cara yang sangat bagus dalam membuat metode protokol opsional, tetapi memberi Anda kemungkinan untuk menggunakan struct di dalam panggilan balik protokol.

Saya menulis ringkasan kecil di sini: https://www.avanderlee.com/swift-2-0/optional-protocol-methods/

Antoine
sumber
2
Ini mungkin cara paling bersih untuk melakukannya di Swift. Sayang sekali itu tidak berfungsi sebelum Swift 2.0.
Entalpi
13
@MattQuiros Saya menemukan bahwa Anda memang perlu mendeklarasikan fungsi dalam definisi protokol, jika tidak, fungsi ekstensi no-op tidak akan ditimpa di kelas Anda yang sesuai dengan protokol.
Ian Pearce
3
@IanPearce benar, dan ini tampaknya dirancang. Dalam pembicaraan "Pemrograman Berorientasi Protokol" (408) di WWDC, mereka berbicara tentang metode dalam protokol utama yang menjadi "titik penyesuaian" yang ditawarkan untuk jenis yang sesuai. Titik penyesuaian yang diperlukan tidak menerima definisi dalam ekstensi; titik opsional tidak. Metode pada protokol yang umumnya tidak boleh dikustomisasi sepenuhnya dinyatakan / ditentukan dalam ekstensi untuk melarang tipe yang sesuai untuk dikustomisasi kecuali Anda secara spesifik dilemparkan ke dynamicType untuk menunjukkan Anda menginginkan implementasi kustom konformer.
matthias
4
@ FrankrankYu Anda dapat melakukan ini, tetapi kemudian Anda mencemari desain API Anda dengan 'lemparan' di mana sebenarnya tidak diperlukan. Saya lebih suka ide "protokol mikro". Misalnya setiap metode mengkonfirmasi ke satu protokol dan kemudian Anda dapat memeriksa: jika objek adalah protokol
Darko
3
@Antoine Saya pikir Anda harus membuat fungsi di ekstensi publik, karena fungsi-fungsi dalam protokol bersifat publik menurut definisi. Solusi Anda tidak akan berfungsi ketika protokol akan digunakan di luar modulnya.
Vadim Eisenberg
39

Karena ada beberapa jawaban tentang cara menggunakan pengubah opsional dan atribut @objc untuk mendefinisikan protokol persyaratan opsional, saya akan memberikan sampel tentang cara menggunakan ekstensi protokol untuk menentukan protokol opsional.

Kode di bawah ini adalah 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*

Harap perhatikan metode ekstensi protokol tidak dapat dipanggil oleh kode Objective-C, dan lebih buruk lagi adalah tim Swift tidak akan memperbaikinya. https://bugs.swift.org/browse/SR-492

Eddie
sumber
1
Kerja bagus! Ini harus menjadi jawaban yang benar karena ini memecahkan masalah tanpa melibatkan runtime Objective-C.
Lukas
benar-benar bagus!
tania_S
dari dokumentasi cepat - "Persyaratan protokol dengan implementasi standar yang disediakan oleh ekstensi berbeda dari persyaratan protokol opsional. Meskipun tipe yang sesuai tidak harus menyediakan implementasi mereka sendiri, persyaratan dengan implementasi standar dapat dipanggil tanpa rantai opsional."
Mec Os
34

Jawaban lain di sini yang melibatkan menandai protokol sebagai "@objc" tidak berfungsi saat menggunakan tipe swift-specific.

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"

Untuk mendeklarasikan protokol opsional yang bekerja dengan baik dengan cepat, deklarasikan fungsi sebagai variabel, bukan fungsi.

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

Dan kemudian implementasikan protokolnya sebagai berikut

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

Anda kemudian dapat menggunakan "?" untuk memeriksa apakah fungsi telah diterapkan atau tidak

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

var anEntity: Health = returnEntity()

var isHealthy = anEntity.isInfoHealthy(Info(height: 75, weight: 150))? 
//"isHealthy" is true
Zag
sumber
3
Dengan solusi ini Anda tidak dapat mengakses diri dari dalam salah satu fungsi protokol. Ini dapat menyebabkan masalah dalam beberapa kasus!
George Green
1
@ GeorgeGreen Anda dapat mengakses sendiri. Tandai variabel fungsi sebagai malas dan gunakan daftar tangkap di dalam penutupan .
Zag
Hanya kelas, protokol, metode, dan properti yang dapat menggunakan @objc. Jika Anda menggunakan parameter Enum dalam definisi metode protokol @ objc, Anda akan menemui ajal.
khunshan
1
@ Khunshan, metode ini tidak memerlukan apa pun yang ditandai @ objc, apa yang Anda maksud?
Zag
ini merupakan informasi tentang topik bahwa enum tidak dapat digunakan di antara swift dan objc yang untuk pernyataan lainnya dapat dijembatani dengan kata kunci @objc.
khunshan
34

Ini adalah contoh nyata dengan pola pendelegasian.

Siapkan Protokol Anda:

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

class MyClass: NSObject
{
    weak var delegate:MyProtocol?

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

Atur delegasi ke kelas dan mengimplementasikan Protokol. Pastikan metode opsional tidak perlu diterapkan.

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

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

    func requiredMethod()
    {
    }
}

Satu hal penting adalah bahwa metode opsional adalah opsional dan memerlukan "?" saat menelepon. Sebutkan tanda tanya kedua.

delegate?.optionalMethod?()
Raphael
sumber
2
Ini seharusnya jawaban yang benar. Sederhana, bersih dan jelas.
Eselon
Saya setuju, jawaban ini singkat, manis dan langsung ke intinya. Terima kasih!
LuAndre
31

Di Swift 3.0

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

Ini akan menghemat waktu Anda.

Gautam Sareriya
sumber
6
Adakah sumber mengapa @objcdiperlukan pada semua anggota sekarang?
DevAndArtist
@Gautam Sareriya: Menurut Anda apa yang terbaik dari pendekatan ini atau menciptakan metode ekstensi kosong?
eonist
Saya mencoba dengan metode Anda dengan requiredflag, tetapi dengan kesalahan: requiredhanya dapat digunakan pada deklarasi 'init'.
Ben
27
  • Anda perlu menambahkan optionalkata kunci sebelum setiap metode.
  • Harap perhatikan, agar ini berfungsi, protokol Anda harus ditandai dengan atribut @objc.
  • Ini lebih lanjut menyiratkan bahwa protokol ini akan berlaku untuk kelas tetapi bukan struktur.
Mehul Parmar
sumber
Koreksi kecil (terlalu kecil untuk diedit!) Tetapi harus 'opsional' bukan '@ pilihan'
Ali Beadle
5
Ini juga mengharuskan Anda untuk menandai kelas Anda mengimplementasikan protokol sebagai @objc, bukan hanya protokol.
Johnathon Sullinger
13

Pendekatan Swift murni dengan pewarisan protokol:

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

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

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

Untuk mengilustrasikan mekanisme jawaban Antoine:

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"
eLillie
sumber
8

Saya pikir sebelum bertanya bagaimana Anda dapat mengimplementasikan metode protokol opsional, Anda harus bertanya mengapa Anda harus menerapkannya.

Jika kita menganggap protokol cepat sebagai Antarmuka dalam pemrograman berorientasi objek klasik, metode opsional tidak masuk akal, dan mungkin solusi yang lebih baik adalah dengan membuat implementasi standar, atau memisahkan protokol menjadi satu set protokol (mungkin dengan beberapa hubungan warisan di antara mereka) untuk mewakili kemungkinan kombinasi metode dalam protokol.

Untuk bacaan lebih lanjut, lihat https://useyourloaf.com/blog/swift-optional-protocol-methods/ , yang memberikan gambaran yang sangat baik tentang masalah ini.

emem
sumber
Ini. Ini kepatuhan terhadap prinsip "I" dalam prinsip pengembangan perangkat lunak SOLID .
Eric
7

Agak keluar dari topik dari pertanyaan awal, tetapi itu membangun ide Antoine dan saya pikir itu mungkin membantu seseorang.

Anda juga dapat membuat properti yang dihitung opsional untuk struct dengan ekstensi protokol.

Anda dapat membuat properti pada protokol opsional

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

Menerapkan properti yang dihitung dummy dalam ekstensi protokol

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

Dan sekarang Anda dapat menggunakan struct yang memiliki atau tidak memiliki properti opsional diimplementasikan

struct ConformsWithoutOptional {
    let required: String
}

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

Saya juga menulis cara melakukan properti opsional dalam protokol Swift di blog saya , yang saya akan terus perbarui jika terjadi perubahan melalui rilis Swift 2.

matthewpalmer
sumber
5

Cara membuat metode delegasi opsional dan wajib.

@objc protocol InterViewDelegate:class {

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

}
Saurabh Sharma
sumber
3

Berikut adalah contoh yang sangat sederhana untuk Kelas swift SAJA, dan bukan untuk struktur atau enumerasi. Perhatikan bahwa metode protokol bersifat opsional, memiliki dua level rantai opsional yang dimainkan. Juga kelas yang mengadopsi protokol membutuhkan atribut @objc dalam deklarasi.

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

 }
Tali Berkat
sumber
Dapatkah Anda menjelaskan sedikit lebih dalam " dua tingkat opsional bermain " bagian, yaitu ini: delegate?.indexDidChange?(index!)?
Unheilig
2
Jika kami telah menulis protokol untuk memiliki metode non opsional seperti ini: protocol CollectionOfDataDelegate{ func indexDidChange(index: Int) } maka Anda akan memanggilnya tanpa tanda tanya: delegate?.indexDidChange(index!) Ketika Anda menetapkan persyaratan opsional untuk metode dalam protokol, Tipe yang akan sesuai dengannya mungkin TIDAK menerapkan metode itu , jadi ?digunakan untuk memeriksa implementasi dan Jika tidak ada program tidak akan crash. @Unheilig
Blessing Lopes
weak var delegate : CollectionOfDataDelegate?(pastikan referensi lemah?)
Alfie Hanssen
@BlessingLopes Bisakah Anda menambahkan penjelasan tentang delegate?penggunaan ke jawaban Anda? Informasi itu harus benar-benar menjadi milik orang lain di masa depan. Saya ingin mengungguli ini, tetapi informasi itu harus benar-benar ada dalam jawabannya.
Johnathon Sullinger
3

jika Anda ingin melakukannya dengan swift murni, cara terbaik adalah dengan memberikan partikel implementasi standar jika Anda mengembalikan tipe Swift seperti misalnya struct dengan tipe Swift

contoh:

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

maka Anda dapat mengimplementasikan protokol tanpa mendefinisikan setiap fungsi

p1ckl3
sumber
2

Ada dua cara Anda dapat membuat metode opsional dalam protokol cepat.

1 - Opsi pertama adalah menandai protokol Anda menggunakan atribut @objc. Meskipun ini berarti hanya dapat diadopsi oleh kelas, itu berarti Anda menandai metode individual sebagai opsional seperti ini:

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

2 - Cara yang lebih cepat: Opsi ini lebih baik. Tulis implementasi default dari metode opsional yang tidak melakukan apa-apa, seperti ini.

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

extension MyProtocol {

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

Swift memiliki ekstensi fitur yang disebut yang memungkinkan kami untuk memberikan implementasi default untuk metode yang kami inginkan opsional.

Himanshu
sumber
0

Salah satu opsi adalah menyimpannya sebagai variabel fungsi opsional:

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

let squareCalculator =
    MyAwesomeStruct(myWonderfulFunction: { input in return input * input })
let thisShouldBeFour = squareCalculator.myWonderfulFunction!(2)
FrankByte.com
sumber
-1

Tetapkan fungsi dalam protokol dan buat ekstensi untuk protokol itu, lalu buat implementasi kosong untuk fungsi yang ingin Anda gunakan sebagai opsional.

Rishu Gupta
sumber
-2

Untuk mendefinisikan Optional Protocoldengan cepat Anda harus menggunakan @objckata kunci sebelum Protocoldeklarasi dan attribute/ methoddeklarasi di dalam protokol itu. Di bawah ini adalah contoh Properti Opsional dari protokol.

@objc protocol Protocol {

  @objc optional var name:String?

}

class MyClass: Protocol {

   // No error

}
Ankur Gupta
sumber
2
Meskipun ini dapat menjawab pertanyaan, lebih baik menambahkan deskripsi tentang bagaimana jawaban ini dapat membantu menyelesaikan masalah. Silakan baca Bagaimana saya menulis jawaban yang bagus untuk mengetahui lebih banyak.
Roshana Pitigala
-23

Letakkan @optionaldi depan metode atau properti.

shucao
sumber
Kesalahan Kompiler: Atribut 'opsional' hanya dapat diterapkan pada anggota protokol @objc.
Selvin
3
@optionalbahkan bukan kata kunci yang benar. Itu optional, dan Anda harus mendeklarasikan kelas dan protokol dengan @objcatributnya.
Johnathon Sullinger