Giải pháp
Trình biên dịch cảnh báo về điều này vì một lý do. Rất hiếm khi cảnh báo này đơn giản nên được bỏ qua và thật dễ dàng để xử lý. Đây là cách thực hiện:
if (!_controller) { return; }
SEL selector = NSSelectorFromString(@"someMethod");
IMP imp = [_controller methodForSelector:selector];
void (*func)(id, SEL) = (void *)imp;
func(_controller, selector);
Hoặc chặt chẽ hơn (mặc dù khó đọc và không có người bảo vệ):
SEL selector = NSSelectorFromString(@"someMethod");
((void (*)(id, SEL))[_controller methodForSelector:selector])(_controller, selector);
Giải trình
Điều đang diễn ra ở đây là bạn đang hỏi bộ điều khiển cho con trỏ hàm C cho phương thức tương ứng với bộ điều khiển. Tất cả đều NSObject
phản hồi methodForSelector:
, nhưng bạn cũng có thể sử dụng class_getMethodImplementation
trong thời gian chạy Objective-C (hữu ích nếu bạn chỉ có một tham chiếu giao thức, như id<SomeProto>
). Các con trỏ hàm này được gọi là IMP
s và là typedef
các con trỏ hàm ed đơn giản ( id (*IMP)(id, SEL, ...)
) 1 . Điều này có thể gần với chữ ký phương thức thực tế của phương thức, nhưng sẽ không luôn luôn khớp chính xác.
Khi bạn đã có IMP
, bạn cần truyền nó tới một con trỏ hàm bao gồm tất cả các chi tiết mà ARC cần (bao gồm cả hai đối số ẩn ẩn self
và _cmd
của mỗi lệnh gọi phương thức Objective-C). Điều này được xử lý trong dòng thứ ba ( (void *)
phía bên tay phải chỉ cần cho trình biên dịch biết bạn đang làm gì và không tạo cảnh báo do các loại con trỏ không khớp).
Cuối cùng, bạn gọi con trỏ hàm 2 .
Ví dụ phức tạp
Khi bộ chọn nhận đối số hoặc trả về giá trị, bạn sẽ phải thay đổi mọi thứ một chút:
SEL selector = NSSelectorFromString(@"processRegion:ofView:");
IMP imp = [_controller methodForSelector:selector];
CGRect (*func)(id, SEL, CGRect, UIView *) = (void *)imp;
CGRect result = _controller ?
func(_controller, selector, someRect, someView) : CGRectZero;
Lý do cảnh báo
Lý do cho cảnh báo này là với ARC, bộ thực thi cần biết phải làm gì với kết quả của phương thức bạn đang gọi. Kết quả có thể là bất cứ điều gì: void
, int
, char
, NSString *
, id
,, vv ARC thường lấy thông tin này từ tiêu đề của các loại đối tượng bạn đang sử dụng. 3
Thực sự chỉ có 4 điều mà ARC sẽ xem xét cho giá trị trả về: 4
- Loại bỏ qua phi vật thể (
void
, int
, vv)
- Giữ lại giá trị đối tượng, sau đó giải phóng khi nó không còn được sử dụng (giả định tiêu chuẩn)
- Giải phóng các giá trị đối tượng mới khi không còn được sử dụng (các phương thức trong
init
/ copy
gia đình hoặc được quy cho ns_returns_retained
)
- Không làm gì cả & giả sử giá trị đối tượng được trả về sẽ hợp lệ trong phạm vi cục bộ (cho đến khi phần lớn nhóm phát hành bên trong bị cạn kiệt, được quy cho
ns_returns_autoreleased
)
Cuộc gọi methodForSelector:
giả định rằng giá trị trả về của phương thức mà nó gọi là một đối tượng, nhưng không giữ lại / giải phóng nó. Vì vậy, cuối cùng bạn có thể tạo ra một rò rỉ nếu đối tượng của bạn được cho là sẽ được phát hành như trong mục số 3 ở trên (nghĩa là phương thức bạn đang gọi trả về một đối tượng mới).
Đối với các bộ chọn bạn đang cố gắng gọi trả về void
hoặc các đối tượng khác, bạn có thể kích hoạt các tính năng của trình biên dịch để bỏ qua cảnh báo, nhưng nó có thể nguy hiểm. Tôi đã thấy Clang trải qua một vài lần lặp lại về cách nó xử lý các giá trị trả về không được gán cho các biến cục bộ. Không có lý do gì khi ARC được kích hoạt mà nó không thể giữ lại và giải phóng giá trị đối tượng được trả về methodForSelector:
ngay cả khi bạn không muốn sử dụng nó. Từ quan điểm của nhà soạn nhạc, nó là một đối tượng. Điều đó có nghĩa là nếu phương thức bạn đang gọi,someMethod
đang trả về một đối tượng không (bao gồm void
), bạn có thể kết thúc bằng một giá trị con trỏ rác được giữ lại / giải phóng và sụp đổ.
Đối số bổ sung
Một xem xét là đây là cảnh báo tương tự sẽ xảy ra performSelector:withObject:
và bạn có thể gặp phải các vấn đề tương tự mà không khai báo phương thức đó tiêu thụ tham số như thế nào. ARC cho phép khai báo các tham số đã tiêu thụ và nếu phương thức tiêu thụ tham số đó, cuối cùng bạn có thể sẽ gửi tin nhắn đến zombie và gặp sự cố. Có nhiều cách để giải quyết vấn đề này với việc đúc cầu, nhưng thực sự sẽ tốt hơn nếu chỉ sử dụng IMP
phương pháp con trỏ hàm và hàm ở trên. Vì các tham số tiêu thụ hiếm khi là một vấn đề, điều này không có khả năng xuất hiện.
Bộ chọn tĩnh
Thật thú vị, trình biên dịch sẽ không phàn nàn về các bộ chọn được khai báo tĩnh:
[_controller performSelector:@selector(someMethod)];
Lý do cho điều này là bởi vì trình biên dịch thực sự có thể ghi lại tất cả các thông tin về bộ chọn và đối tượng trong quá trình biên dịch. Nó không cần phải đưa ra bất kỳ giả định về bất cứ điều gì. (Tôi đã kiểm tra điều này một năm trước bằng cách xem nguồn, nhưng không có tài liệu tham khảo ngay bây giờ.)
Ức chế
Khi cố gắng nghĩ về một tình huống mà việc ngăn chặn cảnh báo này là cần thiết và thiết kế mã tốt, tôi sẽ trở nên trống rỗng. Ai đó hãy chia sẻ nếu họ đã có kinh nghiệm trong đó việc tắt tiếng cảnh báo này là cần thiết (và những điều trên không xử lý mọi việc đúng cách).
Hơn
Có thể xây dựng một NSMethodInvocation
để xử lý việc này, nhưng làm như vậy đòi hỏi phải gõ nhiều hơn và cũng chậm hơn, vì vậy có rất ít lý do để làm điều đó.
Lịch sử
Khi mà performSelector:
họ phương thức lần đầu tiên được thêm vào Objective-C, ARC không tồn tại. Trong khi tạo ARC, Apple đã quyết định rằng nên tạo cảnh báo cho các phương thức này như một cách hướng dẫn các nhà phát triển sử dụng các phương tiện khác để xác định rõ cách xử lý bộ nhớ khi gửi tin nhắn tùy ý qua bộ chọn có tên. Trong Objective-C, các nhà phát triển có thể thực hiện điều này bằng cách sử dụng phôi kiểu C trên các con trỏ hàm thô.
Với sự ra đời của Swift, Apple đã ghi nhận performSelector:
họ các phương pháp là "vốn không an toàn" và chúng không có sẵn cho Swift.
Theo thời gian, chúng ta đã thấy sự tiến triển này:
- Phiên bản đầu của Objective-C cho phép
performSelector:
(quản lý bộ nhớ thủ công)
- Objective-C với ARC cảnh báo việc sử dụng
performSelector:
- Swift không có quyền truy cập
performSelector:
và ghi lại các phương thức này là "vốn không an toàn"
Tuy nhiên, ý tưởng gửi tin nhắn dựa trên bộ chọn có tên không phải là một tính năng "vốn không an toàn". Ý tưởng này đã được sử dụng thành công trong một thời gian dài trong Objective-C cũng như nhiều ngôn ngữ lập trình khác.
1 Tất cả các phương thức Objective-C có hai đối số ẩn self
và _cmd
được thêm vào khi bạn gọi một phương thức.
2 Gọi một NULL
chức năng là không an toàn trong C. Bộ phận bảo vệ được sử dụng để kiểm tra sự hiện diện của bộ điều khiển đảm bảo rằng chúng ta có một đối tượng. Do đó, chúng tôi biết rằng chúng tôi sẽ nhận được IMP
từ methodForSelector:
(mặc dù có thể _objc_msgForward
, nhập vào hệ thống chuyển tiếp tin nhắn). Về cơ bản, với người bảo vệ tại chỗ, chúng tôi biết chúng tôi có một chức năng để gọi.
3 Trên thực tế, nó có thể nhận được thông tin sai nếu khai báo cho bạn các đối tượng id
và bạn không nhập tất cả các tiêu đề. Bạn có thể kết thúc với sự cố trong mã mà trình biên dịch cho là ổn. Điều này rất hiếm, nhưng có thể xảy ra. Thông thường, bạn sẽ chỉ nhận được một cảnh báo rằng nó không biết nên chọn chữ ký nào trong hai chữ ký phương thức.
4 Xem tài liệu tham khảo ARC về giá trị lợi nhuận giữ lại và giá trị trả về unretained để biết thêm chi tiết.