Effective Objective-C 2.0: Item 42: Prefer GCD to performSelector and Friends

来源:互联网 发布:gta5捏脸数据日本妹子 编辑:程序博客网 时间:2024/04/30 04:41

Item 42: Prefer GCD to performSelector and Friends

Thanks to the extremely dynamic nature of Objective-C (see Item 11), a few methods defined on NSObject allow you to call any method you wish. They allow delayed execution of a method call or specification of which thread it should be run on. They were once a very useful feature; now, however, technologies such as Grand Central Dispatch and blocks are making their use less important. Although you will often still see code using them, I encourage you to stay clear of them.

The most basic method in this family is performSelector:. It takes a single argument, which is the selector to perform, with the following signature:

- (id)performSelector:(SEL)selector

This is equivalent to calling the selector directly. So the following two lines of code are equivalent:

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

It might seem as though this is redundant. It would be if this were the only way the method could be used. However, its real power comes from the fact that the selector can be decided at runtime. Such dynamic binding on top of dynamic binding means that you can do something like this:

SEL selector;
if ( /* some condition */ ) {
    selector = @selector(foo);
else if ( /* some other condition */ ) {
    selector = @selector(bar);
else {
    selector = @selector(baz);
}
[object performSelector:selector];

This code is extremely flexible and can often be used to simplify complex code. Another use is to store a selector that should be performed after an event has happened. In either case, the compiler doesn’t know until runtime which selector is going to be performed. But the cost of this feature is that if you compile this under ARC, the compiler emits the following warning:

warning: performSelector may cause a leak because its selector
is unknown [-Warc-performSelector-leaks]

You probably weren’t expecting that! If you were, you probably know why you should be careful with these methods. This message may look strange to you, and you’re wondering why a leak is mentioned. After all, you were simply trying to call a method. The reason is that the compiler doesn’t know what selector is going to be invoked and therefore doesn’t know the method signature, the return type, or even if there is a returned value. Also, the compiler doesn’t know the method name and therefore cannot apply ARC’s memory-management rules to determine whether the return value should be released. For this reason, ARC plays it safe and doesn’t add a release. However, the result might be a memory leak, as the object might be being returned as a retained object.

Consider the following code:

SEL selector;
if ( /* some condition */ ) {
    selector = @selector(newObject);
else if ( /* some other condition */ ) {
    selector = @selector(copy);
else {
    selector = @selector(someProperty);
}
id ret = [object performSelector:selector];

This is a slight variation on the preceding example to show the problem. In the case of the first two selectors, the ret object would need to be released by this code; with the third selector, it would not. This is true not only in an ARC world but also in a non-ARC world, which is strictly following the method-naming guidelines. Without ARC (and therefore no compiler warning), theret object would need to be released if either of the first two conditions were true but not otherwise. This could easily be overlooked, and even a static analyzer would not help detect the subsequent memory leak. This is one reason for treating the performSelector family of methods with caution.

Another reason these methods are not ideal is that the return type can be only void or an object type. The performSelector method’s return type is id, although it’s also valid that the selector being performed returns void. Although intricate casting can use selectors that return other values, such as integers or floats, it can be fragile. It’s technically possible to return any type that has the same size as a pointer, as the id type is a pointer to any Objective-C object: on 32-bit architectures, any type that is 32 bits wide; on 64-bit architectures, any type that is 64 bits wide. If the return type is a C struct, the performSelector method cannot be used.

A couple of other performSelector variants that can pass arguments with the message are defined as follows:

- (id)performSelector:(SEL)selector
           withObject:(id)object
- (id)performSelector:(SEL)selector
           withObject:(id)objectA
           withObject:(id)objectB

For example, these variants can be used to set a property called value on an object:

id object = /* an object with a property called 'value' */;
id newValue = /* new value for the property */;
[object performSelector:@selector(setValue:)
             withObject:newValue];

These methods may seem useful, but they have serious limitations. The objects being passed in must be objects, as the type is always id. So if the selector takes an integer or a float, these methods cannot be used. In addition, the selector can take a maximum of two parameters, using the method performSelector:withObject:withObject:. There are no equivalent methods to perform selectors that take more than two parameters.

One of the other features of the performSelector family of methods is the fact that the selector can be run after a delay or on another thread. Some of the more common of these methods are as follows:

- (void)performSelector:(SEL)selector
             withObject:(id)argument
             afterDelay:(NSTimeInterval)delay
- (void)performSelector:(SEL)selector
               onThread:(NSThread*)thread
             withObject:(id)argument
          waitUntilDone:(BOOL)wait
- (void)performSelectorOnMainThread:(SEL)selector
                         withObject:(id)argument
                      waitUntilDone:(BOOL)wait

However, these methods soon become too constraining. For example, there is no method to perform a given selector that takes two arguments after a delay. The threading methods are not very generic, for the same reason. Code that wants to make use of these methods often packs multiple arguments into a dictionary and unpacks in the called method, thereby adding overhead and the potential for bugs.

All these limitations are solved by using one of the alternatives. The main alternative is using blocks (see Item 37). Furthermore, using blocks with Grand Central Dispatch (GCD) enables you to achieve all the threading-related reasons for using one of the performSelector methods. Performing after a delay can be achieved with dispatch_after, and performing on another thread can be achieved with dispatch_sync and dispatch_async.

For example, to perform a task after a delay, you should prefer the latter to the former:

// Using performSelector:withObject:afterDelay:
[self performSelector:@selector(doSomething)
           withObject:nil
           afterDelay:5.0];

// Using dispatch_after
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,
                                (int64_t)(5.0 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^(void){
    [self doSomething];
});

To perform a task on the main thread:

// Using performSelectorOnMainThread:withObject:waitUntilDone:
[self performSelectorOnMainThread:@selector(doSomething)
                       withObject:nil
                    waitUntilDone:NO];

// Using dispatch_async
// (or if waitUntilDone is YES, then dispatch_sync)
dispatch_async(dispatch_get_main_queue(), ^{
    [self doSomething];
});

Things to Remember

Image The performSelector family of methods is potentially dangerous with respect to memory management. If it has no way of determining what selector is going to be performed, the ARC compiler cannot insert the appropriate memory-management calls.

Image The family of methods is very limited with respect to the return type and the number of parameters that can be sent to the method.

Image The methods that allow performing a selector on a different thread are better replaced with certain Grand Central Dispatch (GCD) calls using blocks.

0 0
原创粉丝点击