6

Giao thức Swift có thể cung cấp việc triển khai mặc định cho các hàm và thuộc tính được tính bằng cách thêm tiện ích vào chúng. Tôi đã làm rất nhiều lần. Đó là sự hiểu biết của tôi rằng việc triển khai mặc định chỉ được sử dụng làm "dự phòng": Nó được thực thi khi một loại phù hợp với giao thức nhưng không cung cấp việc triển khai riêng của nó.Thực hiện một hàm có tham số mặc định được xác định trong giao thức

Ít nhất đó là cách tôi đọc The Swift Programming Language dẫn:

Nếu một loại Tuân cung cấp thực hiện riêng của mình một phương pháp cần thiết hoặc tài sản, thực hiện sẽ được sử dụng thay cho một cung cấp bởi phần mở rộng.

Bây giờ tôi chạy vào một tình huống mà kiểu tùy chỉnh của tôi mà thực hiện một giao thức nhất định không cung cấp một thực hiện một chức năng cụ thể nhưng nó không thực hiện - thực hiện theo quy định tại phần mở rộng giao thức được thực hiện để thay thế.


Như một ví dụ, tôi xác định một giao thức Movable mà có một chức năng move(to:) và một phần mở rộng cung cấp một cài đặt mặc định cho chức năng này:

protocol Movable { 

    func move(to point: CGPoint) 

} 

extension Movable { 

    func move(to point: CGPoint = CGPoint(x: 0, y: 0)) { 
     print("Moving to origin: \(point)") 
    } 

} 

Tiếp theo, tôi định nghĩa một lớp Car rằng phù hợp với Movable nhưng cung cấp triển khai riêng cho chức năng move(to:):

class Car: Movable { 

    func move(to point: CGPoint = CGPoint(x: 0, y: 0)) { 
     print("Moving to point: \(point)") 
    } 

} 

Bây giờ tôi tạo ra một mới Car và downCast nó như là một Movable:

let castedCar = Car() as Movable 

Tùy thuộc vào việc tôi vượt qua một giá trị cho tham số tùy chọn point tôi quan sát hai hành vi khác nhau:


  1. Khi chuyển một điểm cho thông số tùy chọn

    việc thực hiện 's Car được gọi:

    castedCar.move(to: CGPoint(x: 20, y: 10)) 
    

    Output:

    Di chuyển đến thời điểm: (20.0, 10.0)


  1. Khi tôi gọi move() chức năng mà không cung cấp giá trị cho các tham số tùy chọn việc thực hiện 's Car bị bỏ qua và

    triển khai mặc định của giao thức Movable được gọi thay vì:

    castedCar.move() 
    

    Output:

    Di chuyển đến xuất xứ: (0,0, 0,0)


Tại sao?

+3

tôi tin rằng các giá trị mặc định được thêm vào lúc biên dịch do đó loại tĩnh của biến phải được sử dụng. – Sulthan

+1

Bạn đang truyền ô tô sang Movable để nó sẽ gọi phương thức Movable. Nếu bạn không cast Car to Movable, nó sẽ gọi phương thức di chuyển của ô tô của bạn –

+0

@LeoDabus: Bạn đúng rằng nếu tôi không đưa 'Car' sang' Movable' thì tôi không gặp vấn đề này. Tuy nhiên, lý do tại sao vấn đề xảy ra khi tôi _do_ thực hiện dàn diễn viên là toàn bộ vấn đề của câu hỏi của tôi. Trong một thực hiện theo định hướng giao thức thực tế, tôi sẽ không biết lớp thực sự của đối tượng mà tôi đang xử lý - tôi chỉ biết rằng nó tuân theo giao thức 'Movable'. Dòng 'let castedCar = Car() là Movable' chỉ đơn giản là phương tiện để bắt chước tình huống này để giữ mã ví dụ rõ ràng. – Mischa

Trả lời

8

này chỉ đơn giản là do thực tế rằng cuộc gọi

castedCar.move(to: CGPoint(x: 20, y: 10)) 

có thể được giải quyết đối với yêu cầu giao thức func move(to point: CGPoint) - do đó cuộc gọi sẽ được tự động gửi đi để qua bảng giao thức chứng (phương pháp mà trường hợp giao thức đánh máy đạt được đa hình), cho phép thực hiện Car được gọi.

Tuy nhiên, các cuộc gọi

castedCar.move() 

không không trận đấu yêu cầu giao thức func move(to point: CGPoint). Do đó, nó sẽ không được gửi đến thông qua bảng nhân chứng giao thức (chỉ chứa các mục phương thức cho các yêu cầu giao thức). Thay vào đó, vì castedCar được nhập là Movable, trình biên dịch sẽ phải dựa vào công văn tĩnh. Do đó việc thực hiện trong phần mở rộng giao thức sẽ được gọi.

Giá trị tham số mặc định chỉ là một tính năng tĩnh của hàm - chỉ một quá tải của hàm sẽ thực sự được trình biên dịch phát ra (một với tất cả các tham số). Cố gắng áp dụng hàm bằng cách loại trừ một trong các tham số của nó có giá trị mặc định sẽ chỉ kích hoạt trình biên dịch để chèn giá trị tham số mặc định (vì nó có thể không cố định) và sau đó chèn giá trị đó vào gọi điện.

Vì lý do đó, các hàm có giá trị tham số mặc định chỉ không hoạt động tốt với công văn động. Bạn cũng có thể nhận được kết quả không mong muốn với các lớp ghi đè các phương thức với các giá trị tham số mặc định - xem ví dụ this bug report.


Một cách để có được những cử động bạn muốn cho các giá trị tham số mặc định sẽ được chỉ cần xác định một yêu cầu static tài sản trong giao thức của bạn, cùng với một tình trạng quá tải move() trong một phần mở rộng giao thức mà chỉ đơn giản áp dụng move(to:) với nó.

protocol Moveable { 
    static var defaultMoveToPoint: CGPoint { get } 
    func move(to point: CGPoint) 
} 

extension Moveable { 

    static var defaultMoveToPoint: CGPoint { 
     return .zero 
    } 

    // simply apply move(to:) with our given defined default. 
    // as defaultMoveToPoint is a protocol requirement, 
    // it can be dynamically dispatched to. 
    func move() { 
     move(to: type(of: self).defaultMoveToPoint) 
    } 

    func move(to point: CGPoint) { 
     print("Moving to origin: \(point)") 
    } 
} 

class Car: Moveable { 

    static let defaultMoveToPoint = CGPoint(x: 1, y: 2) 

    func move(to point: CGPoint) { 
     print("Moving to point: \(point)") 
    } 

} 

let castedCar: Moveable = Car() 
castedCar.move(to: CGPoint(x: 20, y: 10)) // Moving to point: (20.0, 10.0) 
castedCar.move() // Moving to point: (1.0, 2.0) 

defaultMoveToPoint hiện nay là một yêu cầu giao thức - nó có thể được tự động cử đến, do đó đem lại cho bạn hành vi mong muốn của bạn.

Là phụ lục, lưu ý rằng chúng tôi đang gọi defaultMoveToPoint trên type(of: self) thay vì Self. Điều này sẽ cung cấp cho chúng tôi giá trị siêu dữ liệu động cho trường hợp này, chứ không phải giá trị kiểu dữ liệu tĩnh của phương thức được gọi, đảm bảo rằng defaultMoveToPoint được gửi đi chính xác. Tuy nhiên, nếu loại tĩnh của bất cứ điều gì move() được gọi là (ngoại trừ Moveable chính nó) là đủ, bạn có thể sử dụng Self.

Tôi đi vào sự khác biệt giữa giá trị siêu dữ liệu động và tĩnh có sẵn trong tiện ích mở rộng giao thức chi tiết hơn in this Q&A.

+0

Cảm ơn bạn vì câu trả lời rất phức tạp này! Tôi đã học được rất nhiều từ nó. Nên có một lưu ý phụ về hành vi này trong hướng dẫn Ngôn ngữ lập trình Swift vì nó không thực sự tầm thường, hành vi mong đợi (trừ khi bạn biết về một số chi tiết về trình biên dịch, cụ thể là việc gửi tĩnh và động). – Mischa

+0

Vì vậy, nói chung ** thông số chức năng mặc định không hoạt động với giao thức **. Chúng luôn được giải quyết tĩnh và do đó nó luôn là giao thức thực hiện của giao thức _extension_ đang được gọi bởi vì đó là những gì "có thể nhìn thấy" đối với trình biên dịch tại thời gian xây dựng. – Mischa

+2

@Mischa Happy to help :) Có, nói chung, các giá trị tham số hàm mặc định không hoạt động với các giao thức. Nếu bạn cố gắng áp dụng chúng bằng cách loại trừ một trong các tham số, nó sẽ không còn khớp với yêu cầu giao thức nữa và do đó sẽ mất công văn động. Thậm chí nếu có * là một cách để yêu cầu giao thức thể hiện rằng nó phải được thỏa mãn bởi một hàm có giá trị tham số mặc định đã cho, thì việc thực hiện * đánh giá * của giá trị đó sẽ * vẫn * được quyết định tĩnh bởi trình biên dịch. – Hamish

Các vấn đề liên quan