编写高质量代码的52个有效方法

52个有效方法(42) - 多用GCD,少用performSel

2018-09-29  本文已影响24人  SkyMing一C

performSelector系列方法

NSObject定义的performSelector系列方法可以推迟执行方法调用,也可以指定运行方法所用的线程。

这其中最简单的是performSelector:(SEL)selector。该方法与直接调用选择子等效。所以下面两行代码的执行效果相同。

[self performSelector:@selector(selectorName)];
[self selectorName];
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用了比较谨慎的做法,不添加释放操作,但是这么做可能会导致内存泄漏。

 id ret = [object performSelector:selector]

如果想返回整数或浮点数等scalar类型值,那么就需要执行一些复杂的转换操作,而这种转换操作很容易出错。由于id类型表示指向任意Objective-C对象的指针,所以从技术上来讲,只要返回的大小和指针所占大小相同就行,也就是说,在32位架构的计算机上,可以返回任意32位大小的类型;而在64位架构的计算机上,则可以返回任意64位大小的类型。除此之外,还可以返回NSNumber进行转换…若返回的类型为C语言结构体,则不可使用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系列方法

//该方法的第一个参数是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(<#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(@"=======================");
    });
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.
/*!
 * @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_async(dispatch_get_main_queue(), ^(){
});

要点
  1. performSelector系列方法在内存管理方法容易有疏失。它无法确定将要执行的选择子具体是什么,因而ARC编译器也就无法插入适当的内存管理方法。

  2. performSelector系列方法所能处理的选择子太多局限了,选择子的返回值类型及发送给方法的参数个数都受到限制。

  3. 如果想把任务放另一个线程上执行,那么最好不要用performSelector系列方法,而是应该把任务封装到块里,然后调用大中枢派发机制的相关方法来实现。

上一篇 下一篇

猜你喜欢

热点阅读