Sử dụng -performSelector: so với chỉ gọi phương thức


Câu trả lời:


191

Về cơ bản PerformSelector cho phép bạn xác định động bộ chọn nào sẽ gọi bộ chọn trên đối tượng nhất định. Nói cách khác, bộ chọn không cần được xác định trước thời gian chạy.

Vì vậy, mặc dù chúng tương đương:

[anObject aMethod]; 
[anObject performSelector:@selector(aMethod)]; 

Hình thức thứ hai cho phép bạn làm điều này:

SEL aSelector = findTheAppropriateSelectorForTheCurrentSituation();
[anObject performSelector: aSelector];

trước khi bạn gửi tin nhắn.


3
Cần chỉ ra rằng bạn sẽ thực sự gán kết quả của findTheA phù hợpSelectorForTheCurrentSituation () cho aSelector, sau đó gọi [anObject performanceSelector: aSelector]. @selector tạo ra một SEL.
Daniel Yankowsky 29/09/09

4
Sử dụng performSelector:là điều bạn có thể chỉ làm nếu bạn triển khai target-action trong lớp của mình. Các anh chị em performSelectorInBackground:withObject:performSelectorOnMainThread:withObject:waitUntilDone:thường hữu ích hơn. Để tạo chuỗi nền và gọi lại kết quả cho chuỗi chính từ chuỗi nền đã nói.
PeyloW 30/09/09

2
performSelectorcũng hữu ích để ngăn chặn các cảnh báo biên dịch. Nếu bạn biết phương thức tồn tại (như sau khi sử dụng respondsToSelector), nó sẽ ngăn Xcode nói "có thể không phản hồi your_selector". Chỉ cần không sử dụng nó thay vì tìm ra nguyên nhân thực sự của cảnh báo. ;)
Marc

Tôi đã đọc một số luồng khác trên StackOverflow rằng việc sử dụng performanceSelector là sự phản ánh của một thiết kế khủng khiếp và nó có rất nhiều lượt thích. Tôi ước tôi có thể tìm thấy nó một lần nữa. Tôi đã tìm kiếm trên google, giới hạn kết quả trong stackoverflow và nhận được 18.000 kết quả. Ghê quá.
Logicsaurus Rex

"phản ánh của một thiết kế khủng khiếp" là quá đơn giản. Đây là những gì chúng ta đã có trước khi các khối có sẵn và không phải tất cả các cách sử dụng đều xấu, lúc đó hoặc bây giờ. Mặc dù bây giờ các khối đó đã có sẵn, đó có lẽ là lựa chọn tốt hơn cho mã mới trừ khi bạn đang làm điều gì đó rất đơn giản.
Ethan

16

Đối với ví dụ rất cơ bản này trong câu hỏi,

[object doSomething];
[object performSelector:@selector(doSomething)]; 

không có sự khác biệt về những gì sắp xảy ra. doSomething sẽ được thực thi đồng bộ bởi đối tượng. Chỉ có "doSomething" là một phương thức rất đơn giản, không trả về bất kỳ thứ gì và không yêu cầu bất kỳ tham số nào.

nó có phải là một cái gì đó phức tạp hơn một chút, như:

(void)doSomethingWithMyAge:(NSUInteger)age;

mọi thứ sẽ trở nên phức tạp, bởi vì [object doSomethingWithMyAge: 42];

không còn có thể được gọi với bất kỳ biến thể nào của "performanceSelector", bởi vì tất cả các biến thể có tham số chỉ chấp nhận tham số đối tượng.

Bộ chọn ở đây sẽ là "doSomethingWithMyAge:" nhưng bất kỳ nỗ lực nào để

[object performSelector:@selector(doSomethingWithMyAge:) withObject:42];  

đơn giản là sẽ không biên dịch. chuyển một NSNumber: @ (42) thay vì 42, cũng không giúp ích được gì, bởi vì phương thức yêu cầu một kiểu C cơ bản - không phải một đối tượng.

Ngoài ra, còn có các biến thể performanceSelector lên đến 2 tham số, không hơn. Trong khi các phương thức nhiều lần có nhiều tham số hơn.

Tôi đã phát hiện ra rằng mặc dù các biến thể đồng bộ của PerformSelector:

- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

luôn trả về một đối tượng, tôi cũng có thể trả về một BOOL hoặc NSUInteger đơn giản và nó đã hoạt động.

Một trong hai cách sử dụng chính của PerformSelector là soạn động tên của phương thức bạn muốn thực thi, như đã giải thích trong câu trả lời trước. Ví dụ

 SEL method = NSSelectorFromString([NSString stringWithFormat:@"doSomethingWithMy%@:", @"Age");
[object performSelector:method];

Cách sử dụng khác là gửi một thông điệp đến đối tượng một cách không đồng bộ, thông báo đó sẽ được thực thi sau trên runloop hiện tại. Đối với điều này, có một số biến thể PerformSelector khác.

- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
- (void)performSelector:(SEL)aSelector target:(id)target argument:(id)arg order:(NSUInteger)order modes:(NSArray *)modes;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg;

(vâng, tôi đã tập hợp chúng từ một số danh mục lớp Foundation, như NSThread, NSRunLoop và NSObject)

Mỗi biến thể có hành vi đặc biệt của riêng nó, nhưng tất cả đều có điểm chung (ít nhất là khi waitUntilDone được đặt thành KHÔNG). Lệnh gọi "performanceSelector" sẽ trả về ngay lập tức và thông báo tới đối tượng sẽ chỉ được đưa vào runloop hiện tại sau một thời gian.

Do quá trình thực thi bị trì hoãn - tự nhiên không có giá trị trả về nào tạo nên phương thức của bộ chọn, do đó giá trị trả về - (void) trong tất cả các biến thể không đồng bộ này.

Tôi hy vọng tôi đã che điều này bằng cách nào đó ...


12

@ennuikiller là đúng. Về cơ bản, các bộ chọn được tạo động sẽ hữu ích khi bạn không (và thường là không thể) biết tên của phương thức bạn sẽ gọi khi biên dịch mã.

Một điểm khác biệt chính là -performSelector:và bạn bè (bao gồm cả biến thể đa luồng và biến thể trễ ) có phần hạn chế ở chỗ chúng được thiết kế để sử dụng với các phương pháp có tham số 0-2. Ví dụ: gọi -outlineView:toolTipForCell:rect:tableColumn:item:mouseLocation:với 6 tham số và trả về NSStringlà khá khó sử dụng và không được hỗ trợ bởi các phương thức được cung cấp.


5
Để làm điều đó, bạn cần sử dụng một NSInvocationđối tượng.
Dave DeLong 29/09/09

6
Một sự khác biệt khác: performSelector:và bạn bè đều lấy đối số đối tượng, nghĩa là bạn không thể sử dụng chúng để gọi (ví dụ) setAlphaValue:, vì đối số của nó là một float.
Chuck

4

Bộ chọn hơi giống con trỏ hàm trong các ngôn ngữ khác. Bạn sử dụng chúng khi bạn không biết lúc biên dịch phương thức nào bạn muốn gọi trong thời gian chạy. Ngoài ra, giống như con trỏ hàm, chúng chỉ gói gọn phần động từ của lời gọi. Nếu phương thức có các tham số, bạn cũng cần phải chuyển chúng.

An NSInvocationphục vụ một mục đích tương tự, ngoại trừ việc nó liên kết với nhiều thông tin hơn. Nó không chỉ bao gồm phần động từ, nó còn bao gồm tân ngữ và các tham số. Điều này rất hữu ích khi bạn muốn gọi một phương thức trên một đối tượng cụ thể với các tham số cụ thể, không phải bây giờ mà trong tương lai. Bạn có thể xây dựng một thích hợp NSInvocationvà khai hỏa nó sau.


5
Các bộ chọn thực sự không giống như một con trỏ hàm ở chỗ con trỏ hàm là thứ bạn có thể gọi với các đối số và bộ chọn có thể được sử dụng để gọi một phương thức cụ thể trên bất kỳ đối tượng nào triển khai nó; một bộ chọn không có ngữ cảnh gọi đầy đủ như một con trỏ hàm.
bbum 29/09/09

1
Các bộ chọn không giống như con trỏ hàm, nhưng tôi vẫn nghĩ chúng tương tự nhau. Chúng đại diện cho động từ. Con trỏ hàm C cũng đại diện cho động từ. Không hữu ích nếu không có ngữ cảnh bổ sung. Bộ chọn yêu cầu một đối tượng và các tham số; con trỏ hàm yêu cầu các tham số (có thể bao gồm một đối tượng để hoạt động). Ý của tôi là làm nổi bật chúng khác với các đối tượng NSInvocation như thế nào, chúng chứa tất cả các ngữ cảnh cần thiết. Có lẽ sự so sánh của tôi là khó hiểu, trong trường hợp đó tôi xin lỗi.
Daniel Yankowsky

1
Bộ chọn không phải là con trỏ chức năng. Thậm chí không gần. Chúng là các chuỗi C đơn giản trong thực tế, chứa "tên" của một phương thức (trái ngược với 'hàm'). Chúng thậm chí không phải là chữ ký của phương thức, bởi vì chúng không nhúng các loại tham số. Một đối tượng có thể có nhiều phương thức cho cùng một bộ chọn (các kiểu tham số khác nhau hoặc kiểu trả về khác nhau).
Motti Shneor

-7

Có một sự khác biệt nhỏ giữa hai điều này.

    [object doSomething]; // is executed right away

    [object performSelector:@selector(doSomething)]; // gets executed at the next runloop

Đây là đoạn trích từ Tài liệu Apple

"performanceSelector: withObject: afterDelay: Thực hiện bộ chọn được chỉ định trên luồng hiện tại trong chu kỳ vòng lặp chạy tiếp theo và sau một khoảng thời gian trễ tùy chọn. Bởi vì nó đợi cho đến chu kỳ vòng lặp chạy tiếp theo để thực hiện bộ chọn, các phương pháp này cung cấp độ trễ nhỏ tự động từ mã hiện đang thực thi. Nhiều bộ chọn trong hàng đợi được thực hiện lần lượt theo thứ tự chúng đã được xếp hàng. "


1
Câu trả lời của bạn thực tế không chính xác. Tài liệu bạn trích dẫn là về performSelector:withObject:afterDelay:, nhưng câu hỏi và đoạn mã của bạn đang sử dụng performSelector:, là một phương pháp hoàn toàn khác. Từ tài liệu dành cho nó: <quote> performSelector:Phương pháp này tương đương với việc gửi aSelectortin nhắn trực tiếp đến người nhận. </quote>
jscs

3
cảm ơn Josh đã làm rõ. Bạn nói đúng; Tôi nghĩ performSelector/performSelector:withObject/performSelector:withObject:afterDelaytất cả đều hành xử theo cùng một cách, đó là một sai lầm.
avi
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.