读“编写高质量iOS与OSX代码的52个有效方法”笔记(04)

块和大中枢派发

第37条:理解”块“这一概念

Block的了解还是很有必要的,它和函数类似,只不过是直接定义在另一个函数里的,和定义它的那个函数共享同一个范围的东西。用^来表示,后面接一对花括号,括号里是blcok 的实现代码。

void(^someBlcok)() = ^{

    NSLog(@"there has someBlock");

};

someBlcok();  // print:“there has someBlock”

格式: 返回类型 (^Block Name)(参数){};

int (^addBlock)(int a, int b) = ^(int a ,int b){
    
    return a + b;
    
};
int testAdd = addBlock(7,5);
NSLog(@"testAdd === %d",testAdd); // print: “testAdd === 12”


int additonal = 5;
int (^minusBlock)(int a, int b) = ^(int a,int b){
    
    return  additonal -  a - b;
};
int testMinus = minusBlock(2,6);
NSLog(@"minus === %d",testMinus);  //print :"minus === -3"

注意当要改变块里面的变量的时候,需要加上__block修饰符

NSArray * array = @[@0,@1,@2,@3,@4,@5];
__block NSInteger count = 0;
[array enumerateObjectsUsingBlock:^(NSNumber * number,NSUInteger idx,BOOL *stop){
    if([number compare:@4] == NSOrderedAscending)
    {
        count++;
    }
    
}];

NSLog(@"count ==== %lu",count);  // print : "count ==== 4"
注意还可以使用反序和顺序
- (void)enumerateObjectsWithOptions:(NSEnumerationOptions)opts 
                         usingBlock:(void (^)(ObjectType obj, NSUInteger idx, BOOL *stop))block

typedef NS_OPTIONS(NSUInteger, NSEnumerationOptions) {
    NSEnumerationConcurrent = (1UL << 0), // 顺序
    NSEnumerationReverse = (1UL << 1), // 倒序
};

定义block的时候,所占的内存区域是分配在栈中的,也就是block只在定它的那个范围内有效。所以我们我们需要对其对象发送copy消息以拷贝值,这样的话,就可以把块从栈复制到堆了,块这样就成了带引用计数的对象啦。

第38条:为常用的块类型创建typedef

由于在定义块变量时,需要把变量名放在类型之中,而不要放在右侧,这样非常难记,也非常难懂,鉴于此,我们应该为常用的块类型起个别名,此时typedef关键字就用到了。

typedef int (^YPQBlcok)(BOOL flag,int value);
第39条:用handler块降低代码分散程度

当用Handler块的时候,可以直接将块和相关对象放在一起。这样代码更清晰而紧凑。

#import 

typedef void(^YPQNetworkCompletionHandler)(NSData * data);

@interface YPQNetworkFetcher: NSObject

@property (nonatomic, readonly, strong) NSURL * url;

- (id)initWithURL:(NSURL *)url;
- (void)startWithCompletionHandler:(YPQNetworkCompletionHandler)comletion;

@end

注意实现文件中 的 copy

#import "YPQNetworkFetcher.h"

@interface YPQNetworkFetcher()

@property (nonatomic, readwrite, strong)NSURL * url;
@property (nonatomic, copy)YPQNetworkCompletionHandler completionHandle;
@property (nonatomic, strong) NSData * downloadedData;

@end

@implementation YPQNetworkFetcher

- (id)initWithURL:(NSURL *)url
{
    if(self = [super init])
    {
        _url = url;
    }
    return self;
}
- (void)startWithCompletionHandler:(YPQNetworkCompletionHandler)comletion
{
    self.completionHandle = comletion;
    // 开始 request
    // downloadedData 获值
    // 当申请完成,调用 [self p_requestCompleted];
   
}

- (void)p_requestCompleted
{
    if(_completionHandle)
    {
        _completionHandle(_downloadedData);
    }
}


@end
第40条:用块引用其所属对象时不要出现保留环

就是我们使用Block的时候很容易出现循环引用。

#import "ViewController.h"
#import "YPQNetworkFetcher.h"

@interface ViewController ()
{
    YPQNetworkFetcher * _networkFetcher;
    
}
@property (nonatomic ,strong) NSData * downloadData;

@end


- (void)downloadTheData
{
    NSURL * url = [NSURL URLWithString:@"https://www.example.com/...."];
    _networkFetcher = [[YPQNetworkFetcher alloc] initWithURL:url];
    [_networkFetcher startWithCompletionHandler:^(NSData * data){
       self.downloadData = data;
    }];
    
}

这样看起来没问题,但是已经形成循环引用啦

读“编写高质量iOS与OSX代码的52个有效方法”笔记(04)_第1张图片
循环引用
  1. block保留了viewController实例;
  2. viewController实例则通过_networkFetcher保留YPQNetworkFetcher
  3. _completionHandle又保留了block

这样环就形成了,打破环就得打破其中某一个环节,比较好用的方法可以通过;

// 打破上述的第一环节
__weak __typeof(self) weakSelf = self;
 [_networkFetcher startWithCompletionHandler:^(NSData * data){
   weakSelf.downloadData = data;
}];

或者

// 打破上述的第二环节
[_networkFetcher startWithCompletionHandler:^(NSData * data){

   self.downloadData = data;
    _networkFetcher = nil;
   
}];
第41条:多用派发队列,少用同步锁

如果多个线程要执行同一份代码的时候,通常都要使用锁实现某种同步机制。

 @synchronized(self) {
    //safe
}

_lock = [[NSLock alloc] init];
[_lock lock];
//safe
[_lock unlock];

但是这两种方式效率不高,而且也无法提供绝对的线程安全。有种简单而高效的办法可以代替他们,那即是使用”串行同步队列“,将读取操作和写入操作都安排在同一个队列中。

_syncQuene = dispatch_queue_create("com.yang.test", NULL); - (NSString *)testGCDString
{
      __block NSString * localString;
      dispatch_sync(_syncQuene, ^{
           localString = _testGCDString;
      });
      return localString;
}

- (void)setTestGCDString:(NSString *)testGCDString
{
       dispatch_sync(_syncQuene, ^{
          _testGCDString = testGCDString;
       });
}

当然还可以继续优化, 将同步派发改为异步派发,并且由于多个获取方法可以并发执行,而获取方法和设置方法之间不能并发执行,此时改用并发队列。

_syncQuene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- (NSString *)testGCDString
{
      __block NSString * localString;
      dispatch_sync(_syncQuene, ^{
           localString = _testGCDString;
      });
      return localString;
}

- (void)setTestGCDString:(NSString *)testGCDString
{
       dispatch_async(_syncQuene, ^{
          _testGCDString = testGCDString;
       });
}

但是像上面这样,还是无法正确实现同步。读取和写入可以随时执行,为了不让其任意执行,此时dispatch_barrier_async就出现啦

_syncQuene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- (NSString *)testGCDString
{
    __block NSString * localString;
    dispatch_sync(_syncQuene, ^{
        
        localString = _testGCDString;
    });
    return localString;

}

- (void)setTestGCDString:(NSString *)testGCDString
{
    dispatch_barrier_async(_syncQuene, ^{
        
        _testGCDString = testGCDString;
    });
}

dispatch_barrier_async必须单独执行,不能与其他块并行。这只对并发队列有意义,因为串行队里中块总是按顺序逐个来执行的。并发队列如果发现接下来要处理的块是个barrier block,那么就一直要等到当前所有并发块都执行完毕,才会单独执行这个barrier block。待barrier block执行过后,再按正常方式继续向下处理。

在上面这个队列中,在写入操作用了dispatch_barrier_async来实现后,对属性的读取操作依然可以并行,但写入操作必须单独执行。当然设置函数中,我们也可以用dispatch_barrier_sync同步来实现,有时可能更高效,看具体场景吧。

将同步与异步派发结合起来,实现与普通加锁机制一样的同步行为,也不会阻塞执行异步派发的线程,所以是 OK 的。

第42条:多用GCD,少用performSelector

在我们用到推迟执行方法的时候,我们可以有种选择。

// performSelector
 [self performSelector:@selector(afterThreeSecondBeginAction)
            withObject:nil
            afterDelay:3.0f];
// dispatch after
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^(void){

    [self afterThreeSecondBeginAction];

});

performSelector系列方法在内存管理方面容易疏失,并且所处理的选择子太过局限了,GCD 作为纯C的API,一般担忧还是没必要的,所以说少用performSelector还是有理由的。

第43条:掌握GCD及操作队列的使用时机

简单的说,不要过度使用GCD,要合理使用它,这个可能需要项目经验的积累的。像NSOperationQueue类也可以多了解下。

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

dispatch group是GCD的一项特性,能够把任务分组。

这里是GCD的详细介绍,推荐一篇文章@ 少君的GCD

第45条:使用dispatch_once 来执行只需要运行一次的线程安全代码
static id _instace;
+ (instancetype)sharedInstanceWithSome
{
      static dispatch_once_t onceToken;
      dispatch_once(&onceToken, ^{
           _instace = [[self alloc] init];
      });
      return _instace;
}

使用 dispatch_once可以简化代码并且彻底保证线程安全,我们根本无须担心加锁或同步,另外它没有使用重量级的同步机制,所以也更高效。

第46条:不要使用dispatch_get_current_queue

其实这个已经在iOS6.0之后被弃用了,它可以做调试,但实际也用的不多,所以一般我想应该是不会用到吧。

持续笔记中····

你可能感兴趣的:(读“编写高质量iOS与OSX代码的52个有效方法”笔记(04))