Làm cách nào để giải quyết lỗi biên dịch “sử dụng không rõ ràng” với cú pháp Swift #selector?


79

[ LƯU Ý Câu hỏi này ban đầu được xây dựng theo Swift 2.2. Nó đã được sửa đổi cho Swift 4, liên quan đến hai thay đổi quan trọng về ngôn ngữ: tham số phương thức đầu tiên bên ngoài không còn tự động bị loại bỏ và bộ chọn phải được hiển thị rõ ràng với Objective-C.]

Giả sử tôi có hai phương thức này trong lớp của mình:

@objc func test() {}
@objc func test(_ sender:AnyObject?) {}

Bây giờ tôi muốn sử dụng #selectorcú pháp mới của Swift 2.2 để tạo bộ chọn tương ứng với phương thức đầu tiên trong số các phương thức này , func test(). Tôi phải làm nó như thế nào? Khi tôi thử điều này:

let selector = #selector(test) // error

... Tôi gặp lỗi "Sử dụng mơ hồ test()". Nhưng nếu tôi nói điều này:

let selector = #selector(test(_:)) // ok, but...

... lỗi đã biến mất, nhưng tôi hiện đang đề cập đến phương pháp sai , phương thức tham số. Tôi muốn tham chiếu đến cái mà không có bất kỳ tham số nào. Tôi phải làm nó như thế nào?

[Lưu ý: ví dụ không phải là nhân tạo. NSObject có cả Objective-C copycopy:phương thức instance, Swift copy()copy(sender:AnyObject?); vì vậy vấn đề có thể dễ dàng phát sinh trong cuộc sống thực.]

Câu trả lời:


110

[ LƯU Ý Câu trả lời này ban đầu được xây dựng theo Swift 2.2. Nó đã được sửa đổi cho Swift 4, liên quan đến hai thay đổi quan trọng về ngôn ngữ: tham số phương thức đầu tiên bên ngoài không còn tự động bị loại bỏ và bộ chọn phải được hiển thị rõ ràng với Objective-C.]

Bạn có thể khắc phục sự cố này bằng cách truyền tham chiếu hàm của mình tới chữ ký phương pháp chính xác:

let selector = #selector(test as () -> Void)

(Tuy nhiên, theo ý kiến ​​của tôi, bạn không cần phải làm điều này. Tôi coi tình huống này là một lỗi, tiết lộ rằng cú pháp tham chiếu đến các hàm của Swift không đầy đủ. Tôi đã gửi báo cáo lỗi nhưng vô hiệu.)


Chỉ để tóm tắt #selectorcú pháp mới :

Mục đích của cú pháp này là để ngăn chặn sự cố thời gian chạy quá phổ biến (thường là "bộ chọn không được công nhận") có thể phát sinh khi cung cấp bộ chọn dưới dạng chuỗi ký tự. #selector()lấy một tham chiếu hàm và trình biên dịch sẽ kiểm tra xem hàm có thực sự tồn tại hay không và sẽ giải quyết tham chiếu đến bộ chọn Objective-C cho bạn. Vì vậy, bạn không thể dễ dàng mắc phải bất kỳ sai lầm nào.

( CHỈNH SỬA: Được rồi, bạn có thể. Bạn có thể trở thành một kẻ lừa đảo hoàn chỉnh và đặt mục tiêu thành một phiên bản không triển khai thông báo hành động được chỉ định bởi #selector. Trình biên dịch sẽ không dừng bạn và bạn sẽ gặp sự cố giống như trong ngày xưa tốt đẹp. Haizz ...)

Tham chiếu hàm có thể xuất hiện ở bất kỳ dạng nào trong ba dạng:

  • Tên trần của hàm. Điều này là đủ nếu chức năng không rõ ràng. Vì vậy, ví dụ:

    @objc func test(_ sender:AnyObject?) {}
    func makeSelector() {
        let selector = #selector(test)
    }
    

    Chỉ có một testphương thức, vì vậy điều này #selectorđề cập đến nó mặc dù nó nhận một tham số và #selectorkhông đề cập đến tham số. Bộ chọn Objective-C đã được giải quyết, ở đằng sau, vẫn sẽ chính xác "test:"(với dấu hai chấm, biểu thị một tham số).

  • Tên của hàm cùng với phần còn lại của chữ ký . Ví dụ:

    func test() {}
    func test(_ sender:AnyObject?) {}
    func makeSelector() {
        let selector = #selector(test(_:))
    }
    

    Chúng ta có hai testphương pháp, vì vậy chúng ta cần phân biệt; ký hiệu phân test(_:)giải thành ký hiệu thứ hai , ký hiệu có tham số.

  • Tên của hàm có hoặc không có phần còn lại của chữ ký, cộng với một phép ép kiểu để hiển thị các loại tham số. Như vậy:

    @objc func test(_ integer:Int) {}
    @nonobjc func test(_ string:String) {}
    func makeSelector() {
        let selector1 = #selector(test as (Int) -> Void)
        // or:
        let selector2 = #selector(test(_:) as (Int) -> Void)
    }
    

    Ở đây, chúng tôi đã quá tải test(_:) . Quá tải không thể được hiển thị cho Objective-C, vì Objective-C không cho phép quá tải, vì vậy chỉ một trong số chúng được hiển thị và chúng tôi có thể tạo một bộ chọn chỉ cho cái được hiển thị, vì bộ chọn là một tính năng Objective-C . Nhưng chúng ta vẫn phải phân biệt rõ ràng về Swift, và dàn diễn viên làm được điều đó.

    (Theo tôi, chính đặc điểm ngôn ngữ này đã được sử dụng - sử dụng sai mục đích - làm cơ sở cho câu trả lời ở trên.)

Ngoài ra, bạn có thể phải giúp Swift giải quyết tham chiếu hàm bằng cách cho nó biết hàm đang ở lớp nào:

  • Nếu lớp giống với lớp này hoặc lên chuỗi siêu lớp từ lớp này, thì thường không cần phân giải thêm (như thể hiện trong các ví dụ ở trên); tùy chọn, bạn có thể nói self, với ký hiệu dấu chấm (ví dụ #selector(self.test), và trong một số tình huống, bạn có thể phải làm như vậy.

  • Nếu không, bạn sử dụng một tham chiếu đến một phiên bản mà phương thức được triển khai, với ký hiệu dấu chấm, như trong ví dụ thực tế này ( self.mplà MPMusicPlayerController):

    let pause = UIBarButtonItem(barButtonSystemItem: .pause, 
        target: self.mp, action: #selector(self.mp.pause))
    

    ... hoặc bạn có thể sử dụng tên của lớp , với ký hiệu dấu chấm:

    class ClassA : NSObject {
        @objc func test() {}
    }
    class ClassB {
        func makeSelector() {
            let selector = #selector(ClassA.test)
        }
    }
    

    (Đây có vẻ là một ký hiệu gây tò mò, bởi vì có vẻ như bạn đang nói testlà một phương thức lớp chứ không phải là một phương thức cá thể, nhưng dù sao thì nó sẽ được giải quyết chính xác cho một bộ chọn, đó là tất cả những gì quan trọng.)


2
Xin chào @Sulthan, rất vui được nghe ý kiến ​​của bạn. - Không, đó được hiểu là một lệnh gọi hàm. Đơn giản là không có cách nào để ghi chú trực tiếp khái niệm "không có tham số". Đó là một cái lỗ; họ dường như đã tiếp tục với điều này mà không cần suy nghĩ kỹ (thường xuyên như vậy) ...
matt

4
@Sulthan Như tôi lo sợ, báo cáo lỗi đã trở lại "hoạt động như dự định". Vì vậy, câu trả lời của tôi là những câu trả lời: bạn sử dụng các aský hiệu để xác định các biến thể không có tham số.
matt

1
Một điểm nổi bật khác của trải nghiệm "tuyệt vời" đó là viết mã bằng Swift.
Leo Natan

5
Với Swift hiện 3, bạn cần phải đặt danh sách đối số trong ngoặc đơn: let selector = #selector(test as (Void) -> Void).
Martin R

1
Có thể không phải là nơi tốt nhất, nhưng trong Swift 3 thì đâu sẽ là cú pháp ưu tiên? test as (Void) -> Voidhay cú pháp ngắn hơn test as () -> ()?
Dam

1

Tôi muốn thêm một định nghĩa bị thiếu: truy cập một phương thức thể hiện từ bên ngoài lớp.

class Foo {
    @objc func test() {}
    @objc func test(_ sender: AnyObject?) {}
}

Từ quan điểm của lớp, chữ ký đầy đủ của test()phương thức là (Foo) -> () -> Void, bạn sẽ cần phải chỉ định để có được Selector.

#selector(Foo.test as (Foo) -> () -> Void)
#selector(Foo.test(_:))

Ngoài ra, bạn có thể tham khảo các trường hợp Selectornhư được hiển thị trong câu trả lời ban đầu.

let foo = Foo()
#selector(foo.test as () -> Void)
#selector(foo.test(_:))

Có, ký hiệu Foo.xxxđã kỳ lạ, vì đây không phải là các phương thức lớp bên ngoài. Vì vậy, có vẻ như trình biên dịch cung cấp cho bạn một đường chuyền, nhưng chỉ khi không có sự mơ hồ. Nếu có sự mơ hồ, bạn phải xắn tay áo lại và sử dụng ký hiệu dài hơn, điều này là hợp pháp và chính xác bởi vì một phương thức thể hiện là "bí mật" một phương thức lớp học. Phát hiện rất tốt các trường hợp cạnh còn lại!
matt

0

Trong trường hợp của tôi (Xcode 11.3.1), lỗi chỉ xảy ra khi sử dụng lldb trong khi gỡ lỗi. Khi chạy nó hoạt động bình thường.

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.