[ 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 #selector
cú 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 test
phươ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à #selector
khô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 test
phươ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)
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.mp
là 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 test
là 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.)