52个有效方法(42) - 多用GCD,少用performSel
performSelector
系列方法
NSObject
定义的performSelector
系列方法可以推迟执行方法调用,也可以指定运行方法所用的线程。
这其中最简单的是performSelector:(SEL)selector
。该方法与直接调用选择子等效。所以下面两行代码的执行效果相同。
[self performSelector:@selector(selectorName)];
[self selectorName];
performSelector
系列方法的缺点
-
performSelector
方法内存管理容易有缺失。
performSelector
方法的区别在于,selector
是在running time
才决定的,这就是它的强大之处。这就等于在动态绑定之上再次使用动态绑定,因而可以实现出下面这种功能。
SEL selector;
if ( /* some condition */ ) {
selector = @selector(foo);
} else if ( /* some other condition */ ) {
selector = @selector(bar);
} else {
selector = @selector(baz);
}
[object performSelector:selector];
这种写法非常灵活,编译器只有到运行期的时候才知道选择子是什么,在ARC下编译器会报警告。因为编译器不知道要调用什么选择子,也就不了解其方法和返回值。就没办法运行ARC的规则执行内存管理操作。ARC用了比较谨慎的做法,不添加释放操作,但是这么做可能会导致内存泄漏。
- 返回值只能是void或对象类型。
id ret = [object performSelector:selector]
如果想返回整数或浮点数等scalar
类型值,那么就需要执行一些复杂的转换操作,而这种转换操作很容易出错。由于id类型表示指向任意Objective-C对象的指针,所以从技术上来讲,只要返回的大小和指针所占大小相同就行,也就是说,在32位架构的计算机上,可以返回任意32位大小的类型;而在64位架构的计算机上,则可以返回任意64位大小的类型。除此之外,还可以返回NSNumber
进行转换…若返回的类型为C语言结构体,则不可使用performSelector
方法。
-
performSelector
系列方法有局限性,所能处理的选择子太多局限了,选择子的返回值类型及发送给方法的参数个数都受到限制。
//performSelector系列传递参数方法。
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
//示例:
//比方说,可以用下面这两个版本来设置对象中名为value的属性值:
id object = /* an object with a property called value */
id newValue = /* new value for the property */
[object performSelector:@selector(setValue:) withObject:newValue];
//performSelector系列延后执行方法。
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
多用GCD,少用performSelector系列方法
-
performSelector
系列方法所提供的线程功能,可以通过GCD机制中的块来实现。 -
performSelector
系列方法所提供的延后执行功能,也可以用dispatch_after
来实现。
//该方法的第一个参数是time,第二个参数是dispatch_queue,第三个参数是要执行的block。
//在主线程中延迟执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
上面这句dispatch_after
的真正含义是在6秒后把任务添加进队列中,并不是表示在6秒后执行,大部分情况该函数能达到我们的预期,只有在对时间要求非常精准的情况下才可能会出现问题。
-
参数
dispatch_time_t
解析
dispatch_time_t
有两种形式的构造方式。
-
第一种相对时间:通过
dispatch_time
函数。 -
第二种是绝对时间,通过
dispatch_walltime
函数来获取,其需要使用一个timespec
的结构体来得到dispatch_time_t
。
dispatch_time(<#dispatch_time_t when#>, <#int64_t delta#>)
//DISPATCH_TIME_NOW表示现在,NSEC_PER_SEC表示的是秒数,它还提供了NSEC_PER_MSEC表示毫秒
dispatch_time_t time=dispatch_time(DISPATCH_TIME_NOW, 10*NSEC_PER_SEC);
dispatch_walltime(<#const struct timespec * _Nullable when#>, <#int64_t delta#>)
//要使用一个timespec的结构体
dispatch_walltime(<#const struct timespec * _Nullable when#>, <#int64_t delta#>)
dispatch_time_t time_w = dispatch_walltime(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
NSLog(@"——————————————————————————");
//在主线程中延迟执行
dispatch_after(time_w, dispatch_get_main_queue(), ^{
NSLog(@"=======================");
});
-
dipatch_time
和dispatch_walltime
解析
dispatch_time stops running when your computer goes to sleep. dispatch_walltime continues running. So if you want to do an action in one hour minutes, but after 5 minutes your computer goes to sleep for 50 minutes, dispatch_walltime will execute an hour from now, 5 minutes after the computer wakes up. dispatch_time will execute after the computer is running for an hour, that is 55 minutes after it wakes up.
-
dispatch_time
得到的时间长度是相对的,与设备running
时间相关,即设备运行时才计时。 -
dispatch_walltime
设定的时间段是绝对的,与设备是否running无关。
-
在另一个线程上执行任务则可通过
dispatch_async
和dispatch_sync
来实现。
/*!
* @function dispatch_async
*
* @abstract
* Submits a block for asynchronous execution on a dispatch queue.
*
* @discussion
* The dispatch_async() function is the fundamental mechanism for submitting
* blocks to a dispatch queue.
*
* Calls to dispatch_async() always return immediately after the block has
* been submitted, and never wait for the block to be invoked.
*
* The target queue determines whether the block will be invoked serially or
* concurrently with respect to other blocks submitted to that same queue.
* Serial queues are processed concurrently with respect to each other.
*
* @param queue
* The target dispatch queue to which the block is submitted.
* The system will hold a reference on the target queue until the block
* has finished.
* The result of passing NULL in this parameter is undefined.
*
* @param block
* The block to submit to the target dispatch queue. This function performs
* Block_copy() and Block_release() on behalf of callers.
* The result of passing NULL in this parameter is undefined.
*/
#ifdef __BLOCKS__
API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
void
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
#endif
/*!
* @function dispatch_sync
*
* @abstract
* Submits a block for synchronous execution on a dispatch queue.
*
* @discussion
* Submits a workitem to a dispatch queue like dispatch_async(), however
* dispatch_sync() will not return until the workitem has finished.
*
* Work items submitted to a queue with dispatch_sync() do not observe certain
* queue attributes of that queue when invoked (such as autorelease frequency
* and QOS class).
*
* Calls to dispatch_sync() targeting the current queue will result
* in dead-lock. Use of dispatch_sync() is also subject to the same
* multi-party dead-lock problems that may result from the use of a mutex.
* Use of dispatch_async() is preferred.
*
* Unlike dispatch_async(), no retain is performed on the target queue. Because
* calls to this function are synchronous, the dispatch_sync() "borrows" the
* reference of the caller.
*
* As an optimization, dispatch_sync() invokes the workitem on the thread which
* submitted the workitem, except when the passed queue is the main queue or
* a queue targetting it (See dispatch_queue_main_t,
* dispatch_set_target_queue()).
*
* @param queue
* The target dispatch queue to which the block is submitted.
* The result of passing NULL in this parameter is undefined.
*
* @param block
* The block to be invoked on the target dispatch queue.
* The result of passing NULL in this parameter is undefined.
*/
#ifdef __BLOCKS__
API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
void
dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
#endif
-
dispatch_sync(queue,block)
:同步队列,dispatch_sync
函数不会立即返回,会阻塞当前线程,将block放到指定的queue上面执行,等待该block同步执行完成后才会返回。 -
dispatch_async(queue,block)
:异步队列,dispatch_sync
函数会立即返回,block放到指定queue中等待执行,该block是并行还是串行只跟queue定义有关。 -
在iOS中是无法使用
dispatch_sync(dispatch_get_main_queue()^(){block体})
;
原因如下:
在iOS使用dispatch_sync(dispatch_get_main_queue()^(){block体});
时,dispath向主队列加一个同步的block;此时主队列在等待dispatch_sync(dispatch_get_main_queue(),^(){block体});
执行,dispatch_sync在等待主队列执行完毕。所以在此过程中,我们最初调用的dispatch_sync
函数一直得不到返回,main queue被阻塞,而我们的block又需要等待main queue来执行它。造成死锁。
- 在iOS 使用主队列
dispatch_get_main_queue()
时 应该使用异步执行
dispatch_async(dispatch_get_main_queue(), ^(){
});
要点
-
performSelector系列方法在内存管理方法容易有疏失。它无法确定将要执行的选择子具体是什么,因而ARC编译器也就无法插入适当的内存管理方法。
-
performSelector系列方法所能处理的选择子太多局限了,选择子的返回值类型及发送给方法的参数个数都受到限制。
-
如果想把任务放另一个线程上执行,那么最好不要用performSelector系列方法,而是应该把任务封装到块里,然后调用大中枢派发机制的相关方法来实现。