Làm cách nào để xác định các phương thức tùy chọn trong giao thức Swift?


359

Có thể có trong Swift? Nếu không thì có một cách giải quyết để làm điều đó?


1
Cuối cùng tôi đã thúc đẩy bản thân cập nhật câu trả lời của mình, bạn có thể xem xét lại việc chấp nhận nó.
akashivskyy

@akashivskyy Tôi chấp nhận câu trả lời của bạn vì nó hiển thị tốt hơn tất cả các tùy chọn có sẵn và ưu và nhược điểm của chúng.
Selvin

Câu trả lời:


504

1. Sử dụng triển khai mặc định (ưu tiên).

protocol MyProtocol {
    func doSomething()
}

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

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

Ưu điểm

  • Không có thời gian chạy Objective-C có liên quan (tốt, ít nhất là không rõ ràng). Điều này có nghĩa là bạn có thể tuân thủ các cấu trúc, enum và các NSObjectlớp không theo nó. Ngoài ra, điều này có nghĩa là bạn có thể tận dụng hệ thống tướng mạnh mẽ.

  • Bạn luôn có thể chắc chắn rằng tất cả các yêu cầu được đáp ứng khi gặp các loại phù hợp với giao thức đó. Nó luôn luôn là triển khai cụ thể hoặc mặc định. Đây là cách "giao diện" hoặc "hợp đồng" hoạt động trong các ngôn ngữ khác.

Nhược điểm

  • Dành cho người khôngVoid yêu cầu yêu cầu, bạn cần phải có một giá trị mặc định hợp lý , điều này không phải lúc nào cũng có thể. Tuy nhiên, khi bạn gặp phải vấn đề này, điều đó có nghĩa là yêu cầu đó thực sự không có triển khai mặc định hoặc bạn đã mắc lỗi trong quá trình thiết kế API.

  • Bạn không thể phân biệt giữa thực hiện mặc định và không thực hiện gì cả , ít nhất là không giải quyết vấn đề đó bằng các giá trị trả về đặc biệt. Hãy xem xét ví dụ sau:

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

    Nếu bạn cung cấp một triển khai mặc định mà chỉ trả về true - nó sẽ ổn ngay từ cái nhìn đầu tiên. Bây giờ, hãy xem xét mã giả sau đây:

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

    Không có cách nào để thực hiện tối ưu hóa như vậy - bạn không thể biết liệu đại biểu của mình có thực hiện phương pháp hay không.

    Mặc dù có một số cách khác nhau để khắc phục vấn đề này (sử dụng các bao đóng tùy chọn, các đối tượng ủy nhiệm khác nhau cho các hoạt động khác nhau để đặt tên cho một số), ví dụ đó trình bày rõ vấn đề.


2. Sử dụng @objc optional.

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

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

Ưu điểm

  • Không cần thực hiện mặc định. Bạn chỉ cần khai báo một phương thức tùy chọn hoặc một biến và bạn đã sẵn sàng để đi.

Nhược điểm

  • Nó hạn chế nghiêm trọng khả năng của giao thức của bạn bằng cách yêu cầu tất cả các loại tuân thủ phải tương thích với Objective-C. Điều này có nghĩa, chỉ các lớp kế thừa từ NSObjectcó thể phù hợp với giao thức đó. Không cấu trúc, không enums, không có loại liên quan.

  • Bạn phải luôn kiểm tra xem một phương thức tùy chọn có được thực hiện hay không bằng cách gọi hoặc kiểm tra tùy chọn nếu loại tuân thủ thực hiện nó. Điều này có thể giới thiệu rất nhiều mẫu soạn sẵn nếu bạn thường xuyên gọi các phương thức tùy chọn.


17
Nhìn vào cách cải tiến của các phương pháp tùy chọn trong swift bằng cách sử dụng tiện ích mở rộng (bên dưới)
Daniel Kanaan

1
Và làm thế nào để kiểm tra sự hỗ trợ của một phương thức giao thức tùy chọn trong một ví dụ? respondsToSelector?
devios1

3
Đừng làm điều này! Swift không hỗ trợ phương thức tùy chọn trong các giao thức vì một lý do.
fpg1503

2
Phương pháp này không hỗ trợ tùy chọn trong các tham số. Tức là bạn không thể làm điều đóoptional func doSomething(param: Int?)
SoftDesigner

4
Chắc chắn câu trả lời này về cơ bảnsai ngày nay, nhiều năm sau. Ngày nay, trong Swift, bạn chỉ cần thêm một tiện ích mở rộng để triển khai mặc định, đó là khía cạnh cơ bản của Swift. (Như tất cả các câu trả lời hiện đại dưới đây cho thấy.) Sẽ thật sai lầm khi thêm cờ objc, ngày nay.
Fattie

394

Trong Swift 2 trở đi, có thể thêm các cài đặt mặc định của giao thức. Điều này tạo ra một cách mới của các phương thức tùy chọn trong các giao thức.

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

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

Đây không phải là một cách thực sự hay trong việc tạo các phương thức giao thức tùy chọn, nhưng cung cấp cho bạn khả năng sử dụng các cấu trúc trong các cuộc gọi lại giao thức.

Tôi đã viết một bản tóm tắt nhỏ ở đây: https://www.avanderlee.com/swift-2-0/optional-protatio-methods/


2
Đây có lẽ là cách sạch nhất để làm điều đó trong Swift. Quá tệ, nó không hoạt động trước Swift 2.0.
Entalpi

13
@MattQuiros Tôi thấy rằng trên thực tế bạn cần phải khai báo hàm trong định nghĩa giao thức, nếu không thì hàm mở rộng no-op sẽ không bị ghi đè trong các lớp phù hợp với giao thức.
Ian Pearce

3
@IanPearce là chính xác, và điều này dường như là do thiết kế. Trong bài nói chuyện "Lập trình hướng giao thức" (408) tại WWDC, họ nói về các phương thức trong giao thức chính là "các điểm tùy chỉnh" được cung cấp cho các loại tuân thủ. Một điểm tùy chỉnh bắt buộc không nhận được định nghĩa trong phần mở rộng; một điểm tùy chọn nào. Các phương thức trên giao thức thường không được tùy chỉnh được khai báo / xác định toàn bộ trong phần mở rộng để không cho phép các loại tuân thủ để tùy chỉnh trừ khi bạn đặc biệt bỏ xuống DynamicType để hiển thị bạn muốn triển khai tùy chỉnh của trình tuân thủ.
matthias

4
@FranklinYu Bạn có thể làm điều này nhưng sau đó bạn đang biến đổi thiết kế API của mình bằng 'cú ném' trong thực tế không cần thiết. Tôi thích nhiều hơn ý tưởng về "giao thức vi mô". Ví dụ: mỗi phương thức xác nhận một giao thức và sau đó bạn có thể kiểm tra: nếu đối tượng là giao thức
Darko

3
@Antoine Tôi nghĩ bạn nên đặt chức năng trong phần mở rộng công khai, vì các chức năng trong các giao thức được công khai theo định nghĩa. Giải pháp của bạn sẽ không hoạt động khi giao thức sẽ được sử dụng bên ngoài mô-đun.
Vadim Eisenberg

39

Vì có một số câu trả lời về cách sử dụng công cụ sửa đổi tùy chọnthuộc tính @objc để xác định giao thức yêu cầu tùy chọn, tôi sẽ đưa ra một ví dụ về cách sử dụng tiện ích mở rộng giao thức xác định giao thức tùy chọn.

Mã bên dưới là 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*

Vui lòng lưu ý các phương thức mở rộng giao thức không thể được gọi bằng mã Objective-C và tệ hơn là nhóm Swift sẽ không sửa nó. https://bugs.swift.org/browse/SR-492


1
Làm tốt lắm! Đây phải là câu trả lời chính xác vì nó giải quyết vấn đề mà không liên quan đến thời gian chạy Objective-C.
Lukas

thực sự tốt đẹp
tania_S

từ tài liệu swift - "Các yêu cầu giao thức với các triển khai mặc định được cung cấp bởi các tiện ích mở rộng khác với các yêu cầu giao thức tùy chọn. Mặc dù các loại tuân thủ không phải cung cấp thực thi riêng của chúng, các yêu cầu với triển khai mặc định có thể được gọi mà không có chuỗi tùy chọn."
Mec Os

34

Các câu trả lời khác ở đây liên quan đến việc đánh dấu giao thức là "@objc" không hoạt động khi sử dụng các loại dành riêng cho swift.

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"

Để khai báo các giao thức tùy chọn hoạt động tốt với swift, hãy khai báo các hàm dưới dạng các biến thay vì của func.

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

Và sau đó thực hiện giao thức như sau

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

Sau đó bạn có thể sử dụng "?" để kiểm tra xem chức năng đã được thực hiện hay chưa

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

var anEntity: Health = returnEntity()

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

3
Với giải pháp này, bạn không thể tự truy cập từ bất kỳ chức năng giao thức nào. Điều này có thể gây ra vấn đề trong một số trường hợp!
George Green

1
@GeorgeGreen Bạn có thể tự truy cập. Đánh dấu biến chức năng là lười biếng và sử dụng danh sách chụp bên trong bao đóng .
Zag

Chỉ các lớp, giao thức, phương thức và thuộc tính có thể sử dụng @objc. Trong trường hợp bạn đang sử dụng tham số Enum trong định nghĩa phương thức giao thức @ objc, bạn sẽ phải chịu số phận.
khun Sơn

1
@khun Sơn, phương pháp này không yêu cầu gì được đánh dấu @ objc, bạn đang đề cập đến cái gì?
Zag

đó là một thông tin về chủ đề mà enums không thể được sử dụng ở giữa swift và objc mà các câu lệnh khác có thể được bắc cầu bằng từ khóa @objc.
khun Sơn

34

Dưới đây là một ví dụ cụ thể với mô hình đoàn.

Thiết lập giao thức của bạn:

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

class MyClass: NSObject
{
    weak var delegate:MyProtocol?

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

Đặt đại biểu cho một lớp và thực hiện Giao thức. Xem rằng phương pháp tùy chọn không cần phải được thực hiện.

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

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

    func requiredMethod()
    {
    }
}

Một điều quan trọng là phương thức tùy chọn là tùy chọn và cần "?" khi gọi. Đề cập đến dấu hỏi thứ hai.

delegate?.optionalMethod?()

2
Đây phải là câu trả lời chính xác. Đơn giản, sạch sẽ và giải thích.
Echelon

Tôi đồng ý, câu trả lời này ngắn gọn, ngọt ngào và đi thẳng vào vấn đề. cảm ơn!
LuAndre

31

Trong Swift 3.0

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

Nó sẽ tiết kiệm thời gian của bạn.


6
Bất kỳ nguồn nào tại sao @objccần thiết cho tất cả các thành viên bây giờ?
DevAndArtist

@Gautam Sareriya: Bạn nghĩ gì là cách tiếp cận tốt nhất hoặc tạo ra các phương thức mở rộng trống?
eonist

Tôi đã thử với các phương thức của bạn bằng requiredcờ, nhưng có lỗi: requiredchỉ có thể được sử dụng trên các khai báo 'init'.
Ben

27
  • Bạn cần thêm optionaltừ khóa trước mỗi phương pháp.
  • Tuy nhiên, xin lưu ý rằng để điều này hoạt động, giao thức của bạn phải được đánh dấu bằng thuộc tính @objc.
  • Điều này tiếp tục ngụ ý rằng giao thức này sẽ được áp dụng cho các lớp nhưng không phải là cấu trúc.

Chỉnh sửa nhỏ (quá nhỏ để chỉnh sửa!) Nhưng phải là 'tùy chọn' chứ không phải '@optional'
Ali Beadle

5
Điều này cũng yêu cầu bạn đánh dấu lớp của bạn thực hiện giao thức @objc, không chỉ là giao thức.
Johnathon Sullinger 6/07/2015

13

Một cách tiếp cận Swift thuần túy với kế thừa giao thức:

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

Để minh họa các cơ chế trả lời của 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"

8

Tôi nghĩ rằng trước khi hỏi làm thế nào bạn có thể thực hiện một phương thức giao thức tùy chọn, bạn nên hỏi tại sao bạn nên thực hiện một phương thức.

Nếu chúng ta nghĩ về các giao thức nhanh như một Giao diện trong lập trình hướng đối tượng cổ điển, các phương thức tùy chọn không có nhiều ý nghĩa và có lẽ một giải pháp tốt hơn là tạo ra triển khai mặc định hoặc tách giao thức thành một bộ giao thức (có thể có một số quan hệ thừa kế giữa chúng) để thể hiện sự kết hợp có thể của các phương thức trong giao thức.

Để đọc thêm, hãy xem https://useyourloaf.com/blog/swift-optional-protatio-methods/ , trong đó cung cấp một cái nhìn tổng quan tuyệt vời về vấn đề này.


Điều này. Đó là tuân thủ nguyên tắc "Tôi" trong các nguyên tắc phát triển phần mềm RẮN .
Eric

7

Hơi lạc đề khỏi câu hỏi ban đầu, nhưng nó xây dựng ý tưởng của Antoine và tôi nghĩ nó có thể giúp được ai đó.

Bạn cũng có thể làm cho các thuộc tính được tính tùy chọn cho các cấu trúc có phần mở rộng giao thức.

Bạn có thể tạo một thuộc tính trên giao thức tùy chọn

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

Thực hiện thuộc tính giả trong phần mở rộng giao thức

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

Và bây giờ bạn có thể sử dụng các cấu trúc có hoặc không có thuộc tính tùy chọn được triển khai

struct ConformsWithoutOptional {
    let required: String
}

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

Tôi cũng đã viết cách thực hiện các thuộc tính tùy chọn trong các giao thức Swift trên blog của mình , điều này tôi sẽ cập nhật trong trường hợp mọi thứ thay đổi thông qua các bản phát hành Swift 2.


5

Làm thế nào để tạo các phương thức đại biểu tùy chọn và bắt buộc.

@objc protocol InterViewDelegate:class {

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

}

3

Đây là một ví dụ rất đơn giản cho các lớp nhanh chóng CHỈ và không dành cho các cấu trúc hoặc bảng liệt kê. Lưu ý rằng phương thức giao thức là tùy chọn, có hai cấp độ chuỗi tùy chọn khi chơi. Ngoài ra lớp chấp nhận giao thức cần thuộc tính @objc trong khai báo của nó.

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

 }

Bạn có thể mô tả một chút hơn " hai mức độ bắt buộc ở vở kịch " một phần, cụ thể là thế này: delegate?.indexDidChange?(index!)?
Unheilig

2
Nếu chúng ta đã viết giao thức để có một phương thức không tùy chọn như thế này: protocol CollectionOfDataDelegate{ func indexDidChange(index: Int) } thì bạn sẽ gọi nó mà không có dấu hỏi: delegate?.indexDidChange(index!) Khi bạn đặt một yêu cầu tùy chọn cho một phương thức trong một giao thức, Loại sẽ tuân thủ theo phương thức đó có thể KHÔNG thực hiện phương thức đó , do đó, ?được sử dụng để kiểm tra việc thực hiện và Nếu không có chương trình sẽ không bị sập. @Unheilig
phước lành vào

weak var delegate : CollectionOfDataDelegate?(đảm bảo tham chiếu yếu?)
Alfie Hanssen

@BlessingLopes Bạn có thể thêm lời giải thích về delegate?cách sử dụng vào câu trả lời của mình không? Thông tin đó nên thực sự thuộc về những người khác trong tương lai. Tôi muốn nâng cao điều này, nhưng thông tin đó thực sự nên có trong câu trả lời.
Johnathon Sullinger 6/07/2015

3

nếu bạn muốn làm điều đó trong swift thuần túy, cách tốt nhất là cung cấp một chi tiết triển khai mặc định nếu bạn trả về một kiểu Swift như ví dụ struct với các kiểu Swift

thí dụ :

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

sau đó bạn có thể thực hiện giao thức mà không cần định nghĩa mỗi func


2

Có hai cách bạn có thể tạo phương thức tùy chọn trong giao thức swift.

1 - Tùy chọn đầu tiên là đánh dấu giao thức của bạn bằng thuộc tính @objc. Mặc dù điều này có nghĩa là nó chỉ có thể được chấp nhận bởi các lớp, nhưng điều đó có nghĩa là bạn đánh dấu các phương thức riêng lẻ là tùy chọn như thế này:

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

2 - Một cách nhanh hơn: Tùy chọn này là tốt hơn. Viết các triển khai mặc định của các phương thức tùy chọn không làm gì cả, như thế này.

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

extension MyProtocol {

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

Swift có một tính năng gọi là tiện ích mở rộng cho phép chúng tôi cung cấp triển khai mặc định cho các phương thức mà chúng tôi muốn là tùy chọn.


0

Một tùy chọn là lưu trữ chúng dưới dạng các biến chức năng tùy chọn:

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

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

-1

Xác định chức năng trong giao thức và tạo phần mở rộng cho giao thức đó, sau đó tạo triển khai trống cho chức năng mà bạn muốn sử dụng làm tùy chọn.


-2

Để xác định Optional Protocoltrong swift, bạn nên sử dụng @objctừ khóa trước khi Protocolkhai báo và attribute/ methodkhai báo bên trong giao thức đó. Dưới đây là một mẫu thuộc tính tùy chọn của một giao thức.

@objc protocol Protocol {

  @objc optional var name:String?

}

class MyClass: Protocol {

   // No error

}

2
Trong khi điều này có thể trả lời câu hỏi, tốt hơn là thêm một số mô tả về cách câu trả lời này có thể giúp giải quyết vấn đề. Xin vui lòng đọc Làm thế nào để tôi viết một câu trả lời tốt để biết thêm.
Roshana Pitigala

-23

Đặt @optionaltrước các phương thức hoặc thuộc tính.


Lỗi trình biên dịch: thuộc tính 'tùy chọn' chỉ có thể được áp dụng cho các thành viên của giao thức @objc.
Selvin

3
@optionalthậm chí không phải là từ khóa chính xác. Đó là optional, và bạn phải khai báo lớp giao thức với @objcthuộc tính.
Johnathon Sullinger
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.