《Effective Objective-C 2.0编写高质量iOS与OS X代码的52个方法》要点回顾(六)第六章 块(block)与大中枢派发(GCD)

37. 理解 “块” 这一概念

实例:

    /*
     
     块(block)语法:
     return_type (^block_name) (parameters)
     
     */
    //1 全局块
//    void (^someBlock)(void) = ^ {
//        NSLog(@"这是一个块");
//    };
//
//    someBlock();
    //2 带参数
//    int (^intBlock) (int a, int b) = ^(int aaa, int bbb) {
//        return aaa + bbb;
//    };
//
//    NSLog(@"%d", intBlock(900, 98));

    //3 在声明它的范围里,所有变量都可为其捕获。
    int addi = 1000;
    int (^intBlock) (int a, int b) = ^(int aaa, int bbb) {
        return aaa + bbb + addi;
    };

    NSLog(@"%d", intBlock(900, 98));

    //4 内联块
    
    NSArray *array = @[@0, @1, @2, @3, @4, @5];
    __block NSInteger count = 0;
    [array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        /*
         NSOrderedAscending     小于
         NSOrderedSame          等于
         NSOrderedDescending    大于
         */
        if ([obj compare:@2] == NSOrderedAscending) {
            count++;
        }
    }];
    NSLog(@"%ld", (long)count);
    
    
    //self 也是对象,块在捕获它时也会将其保留,所以一般block容易产生“保留循环”

全局块、栈块及堆块

//全局块: 由于运行该块所需的全部信息 都能在编译期确定,所以可以称为“全局块”。如下
void (^someBlock)() = ^ {
    NSLog(@"这是一个块");
};
    
//    //栈块
//    void (^block) ();
//
//    if (count == 2) {
//        block = ^{
//            NSLog(@"这是一个栈块");
//        };
//    }else {
//        block = ^{
//            NSLog(@"这是一个栈块");
//        };
//    }
//
//    block();//此处却在堆。不安全
    
//改良
//堆块
void (^block) ();
    
if (count == 2) {
    block = [^{
        NSLog(@"这是一个栈块");
    } copy];
}else {
    block = [^{
        NSLog(@"这是一个栈块");
    } copy];
}
    
block();//把块复制到堆,就安全了

要点总结

  • 块(block)是C、OC、C++中的词法闭包
  • 块 可以接收参数,也可返回值
  • 块 可以分配到上,也可以是全局的。分配到栈上的块可拷贝到堆里,这样的话,就和标准的OC对象一样,具有引用计数了。

38. 为常用的块类型创建 typedef

//定义
typedef void(^EOCCompletionHandler)(NSData *data, NSError *error);
//做参数。
- (void)startWithCompletionHandler:(EOCCompletionHandler)completion;

要点总结

  • typedef重新定义 块类型,可令块类型用起来更加简单
  • 定义新类型时应遵从现有的命名习惯,勿使其名称与别的类型名称冲突。
  • 不妨为同一个块类型定义多种类型别名。如果要重构的代码中使用了块类型的某个别名,那么只需修改响应 typedef 中的块签名即可,无须改动其它 typedef。

39. 用 handle 块降低代码分散程度

块,有时可以代替代理(委托)方法

要点总结

  • 在创建对象时,可以使用 内联的handler块 将相关业务一并声明。
  • 多个实例需要监控时,若采用委托模式,那么经常需要根据传入的对象来切换。而若改用 handler块 来实现,则可以直接将块和相关对象放在一起。
  • 设计API时如果用到了 handler块,那么可以增加一个参数,是调用者可以通过此参数来决定 块在哪个队列上执行任务。

40. 用块引用其所属对象时不要出现保留环

避免保留环的封装方法

1:
_networkFetcher = [[EOCNetworkFetcher alloc] initWithUrl:[NSURL URLWithString:@"www.baidu.com"]];
[_networkFetcher startWithCompletionHandler:^(NSData *data, NSError *error) {
   
    _fetcherData = data;
    
    //避免保留环的方法1
    _networkFetcher = nil;
    
}];

2:
/** 避免保留环的方法2 */
- (void)p_requestCompleted {
    
    if (_handler) {
        _handler(_downloadData, nil);
    }
    self.handler = nil;
}

要点总结

  • 如果 块所捕获的对象 直接或间接地保留了块本身,那么就得担心保留环问题。
  • 一定要找个合适的时机解除保留环,而不能把责任推给API的调用者。

41. 多用派发队列,少用同步锁

.h
{
    NSString *_someString;
}

- (NSString *)someString;
- (void)setSomeString:(NSString *)someString;


/*************************************/
.m
{
    dispatch_queue_t _queue;
}
- (instancetype)init {
    if (self = [super init]) {
        //_queue = dispatch_queue_create("EOC by Wzz", nil);
        _queue = dispatch_get_global_queue(0, 0);
    }
    return self;
}


- (NSString *)someString {

    __block NSString *localSomeString;

    dispatch_sync(_queue, ^{
        localSomeString = _someString;
    });

    return localSomeString;
}


- (void)setSomeString:(NSString *)someString {
    dispatch_barrier_async(_queue, ^{
//        [NSThread sleepForTimeInterval:3.0f];
        _someString = someString;
    });
    
}

要点总结

  • 派发队列(dispatch queue)可用来表述同步语义(synchronization semantic),这种做法要比使用 @synchronized块NSLock对象 更简单。
  • 将同步与异步派发结合起来,可以实现与普通加锁机制一样的同步行为,而这么做却不会阻塞执行异步派发的线程。
  • 使用同步队列及栅栏块,可以令同步行为更加高效。

42. 多用GCD,少用 performSelector 系列方法

要点总结

  • performSelector 系统方法在内存管理方面容易有疏失。它无法确定将要执行的选择子具体是什么,因而 ARC 编译器也就无法插入适当的内存管理方法。
  • performSelctor 系列方法所能处理的选择子太过局限了,选择子的返回类型及发送给方法的参数个数都受到限制。
  • 如果想把任务放到另一个线程上执行,那么最好不要用 performSelector 系列方法,而是应该把任务封装到块里,然后调用大中枢派发机制(GCD)的相关方法来实现。

43. 掌握GCD及操作队列的使用时机

使用 NSOperation 及 NSOperationQueue 的好处:
  • 取消某个操作。

    已启动的任务无法取消。

  • 指定操作间的依赖关系。

  • 通过键值观测机制监控 NSOperation 对象的属性。

  • 指定操作的优先级

  • 重用 NSOperation 对象

要点总结:

  • 在解决多线程与任务管理问题时,派发队列并非是唯一解决方案;
  • 操作队列提供了一套高层的 Objective-C API,能实现GCD所具备的绝大部门功能,而且还能完成一些更为复杂的操作,这些操作若改用GCD来实现,需另写代码。

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

要点总结:

  • 一系列任务可归入一个dispatch group之中。开发者可以在这组任务执行完毕时获得通知。
  • 通过dispatch group,可以在并发式队列里同时执行多项任务。此时GCD会根据系统资源状况来调度这些并发执行的任务。开发者若自己来实现此功能,则需要编写大量代码。

45. 使用dispatch_once来执行只需执行一次的线程安全代码

要点总结

  • 经常需要编写“只需执行一次的线程安全代码”。通过GCD所提供的dispatch_once函数,很容易就能实现此功能。

只需执行一次的线程安全代码: thread-safe single-code execution

  • 标记应该生命在static或global作用域中。这样的话,在把只需要执行一次的块传给dispatch_once函数时,传进入的标记也是相同的。

46. 不要使用 dispatch_get_current_queue

要点总结

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

系列文章

  • 第一章 熟悉 Objective-C
  • 第二章 对象、消息、运行期
  • 第三章 接口和API设计
  • 第四章 协议与分类
  • 第五章 内存管理
  • 第七章 系统框架

你可能感兴趣的:(《Effective Objective-C 2.0编写高质量iOS与OS X代码的52个方法》要点回顾(六)第六章 块(block)与大中枢派发(GCD))