美文网首页ios基础
iOS 编写高质量Objective-C代码(七)—— GCD专

iOS 编写高质量Objective-C代码(七)—— GCD专

作者: 齐舞647 | 来源:发表于2018-10-15 11:46 被阅读90次

    《编写高质量OC代码》已经顺利完成一二三四五六七篇!
    附上链接:
    iOS 编写高质量Objective-C代码(一)—— 简介
    iOS 编写高质量Objective-C代码(二)—— 面向对象
    iOS 编写高质量Objective-C代码(三)—— 接口和API设计
    iOS 编写高质量Objective-C代码(四)—— 协议与分类
    iOS 编写高质量Objective-C代码(五)—— 内存管理机制
    iOS 编写高质量Objective-C代码(六)—— block专栏
    iOS 编写高质量Objective-C代码(七)—— GCD专栏


    本篇的主题是iOS中的 “ 大中枢开发 GCD ”

    先简单介绍一下今天的主角:GCD

    • GCD(Grand Central Dispatch):一种与块相关的技术,提供了对线程的抽象管理(基于派发队列dispatch queue)。GCD会根据系统资源情况,适时且高效地 “创建线程” 、“复用线程” 、 “销毁线程”

    一、多用派发队列,少用同步锁

    问:在iOS开发中,如何通过锁来提供同步机制?(以前面试中,经常问道的问题..)

    答:在GCD出现之前,有两种方式:

    • 同步块:@synchronized(self) {...}
    - (void)synchronizedMethod {
        
        @synchronized (self) {
            
            // Safe area...
        }
    }
    
    • NSLock:[_lock lock]; & [_lock unlock];
    _lock = [[NSLock alloc] init];
    
    - (void)synchronizedMethod {
        
        [_lock lock];    
        // Safe area..    
        [_lock unlock];
    }
    

    不过这两种写法效率很低,如果有很多属性,那么每个属性的同步块都要等其他同步块执行完毕才能执行。

    GCD出现后,GCD与Block相结合,使开发变得更加简单、高效。

    问:如何保证属性读写时线程绝对安全?
    答:在属性写入时,使用栅栏块barrier。只有当前所有并发块都执行完毕后,才会执行barrier块,然后才会继续向下处理。

    • 思路如下:
    • 代码如下:
    _syncQueue = dispatch_queue_create("syncQueue", DISPATCH_QUEUE_CONCURRENT);
    
    //! 读取字符串
    - (NSString *)someString {
    
        __block NSString *localSomeString;
    
        dispatch_sync(_syncQueue, ^{
            localSomeString = _someString;
        });
    
        return localSomeString;
    }
    
    - (void)setSomeString:(NSString *)someString {
    
         dispatch_barrier_async(_syncQueue, ^{
            _someString = someString;
        });
    }
    

    二、多用GCD,少用performSelector系列方法

    performSelector系列方法的缺点有两个:

    1. performSelector系列方法可能引起内存泄漏:
      在ARC环境下,编译器并不知道将要调用的选择子是什么,有没有返回值,返回值是什么,所以ARC不能判断返回值是否能释放,因此ARC做了一个比较谨慎的做法:只添加retain,不添加release。因此在有返回值或参数的时候可能导致内存泄漏。
    2. performSelector系列方法的返回值只能是void或OC对象类型。
    3. performSelector系列方法最多只能传入两个参数。

    因此可以使用GCD来代替performSelector系列方法:

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
        //do something..
    });
    

    三、掌握GCD及操作队列的使用时机

    GCD性能很棒,但在执行后台任务时,GCD并不一定是最佳选择。在iOS开发中,还有一种技术叫NSOperationQueueGCD是基于C语言的API,性能较高。而NSOperationQueue是基于GCD的抽象。

    使用NSOperationNSOperationQueue的优点:

    • 支持取消某个NSOperation
      在运行任务前,可以在NSOperation对象上调用cancel方法,用以表明此任务不需要执行。不过已经启动的任务无法取消。iOS 8之前,GCD队列是无法取消的,GCD是“安排好之后就不管了(fire and forget)”。iOS 8之后,支持dispatch_canceldispatch_block_cancel

    • NSOperation支持多任务操作的依赖关系:
      比如:任务A、B、C必须在任务D完成后执行。

    • 支持通过KVO监控NSOperation对象的属性:
      例如:可以通过isCancelled属性来判断任务是否已取消,通过isFinished属性来判断任务是否已经完成等等;

    • 支持指定NSOperationQueue的优先级:
      操作的优先级表示此操作与队列中其他操作之间的优先关系,优先级高的NSOperationQueue先执行,优先级低的后执行。GCD的队列也有优先级,不过不是针对整个队列的;

    • 重用NSOperation对象:
      在开发中你可以使用NSOperation的子类或者自己创建NSOperation对象来保存一些信息,可以在类中定义方法,使得代码能够多次使用;

    四、通过Dispatch Group机制,根据系统资源状况来执行任务

    dispatch groupGCD的一项特性,能够把任务进行分组管理,然后等待这组任务执行完毕时会有通知,开发者可以拿到结果然后继续下一步操作。
    另外,通过dispatch group在并发队列上同时执行多项任务的时候,GCD会根据系统资源状态来帮忙调度这些并发执行的任务。

    五、使用dispatch_once来执行只需要运行一次的线程安全代码

    例如:我们开发中写一个单例,就可以使用dispatch_once

    + (instancetype)sharedInstance {
        
        static Class *manager = nil;
    
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            manager = [[Class alloc] init];
        });
    
        return manager;
    }
    

    六、不要使用dispatch_get_current_queue

    理由如下:

    • dispatch_get_current_queue 函数的行为常常与开发者所预期的不同,此函数已经废弃,只应做调试之用。
    • 由于GCD是按层级来组织的,所以无法单用某个队列对象来描述"当前队列"这一概念。
    • dispatch_get_current_queue 函数用于解决由不可以重入的代码所引发的死锁,然后能用此函数解决的问题,通常也可以用"队列特定数据"来解决。

    相关文章

      网友评论

      本文标题:iOS 编写高质量Objective-C代码(七)—— GCD专

      本文链接:https://www.haomeiwen.com/subject/jzkjaftx.html