Triển khai mặc định giao thức gọi từ phương thức thông thường


82

Tôi tự hỏi liệu có thể đạt được điều đó không.
Tôi có một Sân chơi như thế này:

protocol Foo {
    func testPrint()
}

extension Foo {
    func testPrint() {
        print("Protocol extension call")
    }
}

struct Bar: Foo {
    func testPrint() {
        // Calling self or super go call default implementation
        self.testPrint()
        print("Call from struct")
    }
}


let sth = Bar()
sth.testPrint()

Tôi có thể cung cấp triển khai mặc định trong extensionnhưng nếu Barcần mọi thứ có trong triển khai mặc định cộng với những thứ bổ sung?
Nó bằng cách nào đó tương tự như việc gọi super.các phương thức trong classes để đáp ứng yêu cầu thực hiện mọi thuộc tính, v.v. nhưng tôi không thấy có khả năng đạt được điều tương tự với structs.


Tôi sẽ sử dụng Foo.testPrint(self)()- vấn đề là nó không thành công do lỗi phân đoạn (đã thử nghiệm trên cả 7.0 GM và 7.1 beta)
Antonio

1
Đó là một công trình kỳ lạ mà bạn đã trình bày 😯
cojoj

4
Mỗi phương pháp dụ là một phương pháp cà ri tĩnh lấy một ví dụ như tham số đầu tiên của nó
Antonio

Tuy nhiên, tôi đã thử xóa tiện ích mở rộng và nó gây ra cùng một lỗi phân đoạn. Có lẽ điều đó không được cho là hoạt động với các giao thức
Antonio

Hmmm, xấu hổ mà tôi đã lặp lại bản thân mình trong mã trong khi điều này có thể dễ dàng cố định bằng cách sử dụng cài đặt mặc định ...
cojoj

Câu trả lời:


90

Tôi không biết nếu bạn vẫn đang tìm kiếm câu trả lời cho điều này, nhưng cách thực hiện là xóa hàm khỏi định nghĩa giao thức, truyền đối tượng của bạn tới Foovà sau đó gọi phương thức trên đó:

protocol Foo { 
    // func testPrint() <- comment this out or remove it
}

extension Foo {
    func testPrint() {
        print("Protocol extension call")
    }
}

struct Bar: Foo {
    func testPrint() {
        print("Call from struct")
        (self as Foo).testPrint() // <- cast to Foo and you'll get the  default
                                  //    function defined in the extension
    }
}

Bar().testPrint()

// Output:    "Call from struct"
//            "Protocol extension call"

Vì một số lý do, nó chỉ hoạt động nếu hàm không được khai báo như một phần của giao thức, nhưng được định nghĩa trong phần mở rộng của giao thức. Đi tìm hình. Nhưng nó hoạt động.


1
Ôi Chúa ơi! Tôi không thể tin rằng họ buộc bạn phải đưa func ra khỏi giao thức! câu trả lời tốt, cảm ơn bạn!
SkyWalker

5
Điều này trông giống như một lỗi đối với tôi, điều này sẽ không thể thực hiện các phương pháp khác nhau bằng cách chỉ truyền cùng một phiên bản.
MANIAK_dobrii

15
Điều này thực sự thay đổi đáng kể ngữ nghĩa của giao thức + phần mở rộng. Nếu bạn bỏ khai báo ra khỏi giao thức, bạn sẽ nhận được điều phối tĩnh khi gọi hàm trên một kiểu phù hợp với giao thức - đây là lý do tại sao bạn có thể ép kiểu và triển khai từ tiện ích mở rộng. Nếu bạn thêm khai báo vào giao thức, lệnh gọi hàm của bạn sẽ được gửi động .
Thorsten Karrer

2
Điều này chỉ hoạt động nếu Foogiao thức không kế thừa từ bất kỳ giao thức nào khác.
iyuna 20/02/17

4
Điều này không dẫn đến một vòng lặp vô hạn?
stan liu

9

Chà, bạn có thể tạo một kiểu lồng nhau phù hợp với giao thức, khởi tạo nó và gọi phương thức trên kiểu đó (không quan trọng là bạn không thể truy cập dữ liệu kiểu của mình vì việc triển khai bên trong tiện ích mở rộng giao thức không thể tham chiếu đến nó). Nhưng đó không phải là một giải pháp mà tôi gọi là thanh lịch.

struct Bar: Foo {
    func testPrint() {
        // Calling default implementation
        struct Dummy : Foo {}
        let dummy = Dummy()
        dummy.testPrint()
        print("Call from struct")
    }
}

1
Dường như đó là khả năng duy nhất ngay bây giờ (xác nhận bởi Apple) ... Tôi sẽ nộp radar tính năng cho một này vì nó có thể hữu ích 👌
cojoj

4

Cảm ơn vì bài đăng! Nếu bạn đặt định nghĩa hàm trong giao thức thì khi đối tượng được truyền làm giao thức, nó chỉ nhìn thấy phiên bản của đối tượng của hàm và vì bạn đang gọi nó bên trong chính nó nên bạn sẽ nhận được địa chỉ mới của Apple ...

Tôi đã thử một phiên bản như thế này:

import UIKit
protocol MyProc
{
}

protocol MyFuncProc
{
    func myFunc()
}

extension MyProc
{
    func myFunc()
    {
        print("Extension Version")
    }
}

struct MyStruct: MyProc, MyFuncProc
{
    func myFunc()
    {
        print("Structure Version")
        (self as MyProc).myFunc()
    }
}

(MyStruct() as MyFuncProc).myFunc()

Điều này cho kết quả là:

Structure Version
Extension Version

3

Trong trường hợp giao thức của bạn có associatedTypehoặc Selfyêu cầu, thì quá trình truyền sẽ không hoạt động. Để giải quyết vấn đề này, hãy tạo một triển khai mặc định "bóng" mà cả triển khai mặc định thông thường và kiểu tuân thủ đều có thể gọi.

protocol Foo { 
    associatedType Bar
}

extension Foo {
    func testPrint() {
        defaultTestPrint()
    }
}

fileprivate extension Foo { // keep this as private as possible
    func defaultTestPrint() {
        // default implementation
    }
}

struct Bar: Foo {
    func testPrint() {
        // specialized implementation
        defaultTestPrint()
    }
}

Không thể đồng ý với bạn nhiều hơn. defaultXX()diễn đạt và dễ đọc hơn nhiều so với các câu trả lời khác.
DawnSong

Và tôi nghĩ Amin Madani đã cải thiện câu trả lời của bạn.
DawnSong

2

bạn nghĩ gì về cách khắc phục điều này?

protocol Foo {
    func testPrint()
}

extension Foo {
    func testPrint() {
        defaultTestPrint()
    }

    func defaultTestPrint() {
        print("Protocol extension call")
    }
}

struct Bar: Foo {
    func testPrint() {
        // Calling self or super go call default implementation
        defaultTestPrint()
        print("Call from struct")
    }
}


let sth = Bar()
sth.testPrint()

Đây có phải là một câu trả lời?
Anh Pham

@AnhPham thats nó, chỉ là một phương pháp để thực hiện chức năng mặc định
Amin Madani

máy làm sạch và giải pháp dễ dàng
Stephan Januar

Câu trả lời biểu cảm và linh hoạt nhất.
DawnSong
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.