一些关于Block, ARC, GCD的总结

基础解释不做。基础的东西链接如下:

1. Block:https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Blocks/Articles/00_Introduction.html#//apple_ref/doc/uid/TP40007502

2. ARC:https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html#//apple_ref/doc/uid/10000011i

3. GCD: https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html

===================分割线是不是这样用的====下面讲Block=======================================

该部份只是把不同语言中的类似的东西放一起讲了一下。如果不关心C++可以直接跳到下一个分割线以后。

 

Block其实很像C++中的函数对象(function object),都定义了一个可调用的对象。以下把一些类似的玩意放一起比较一下:

先上一个C++的Sort函数:

template <class RandomAccessIterator, class Compare>
  void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);

最后一个Compare就是一个可调用的比较函数(对象)。

现有一个C++ Vector:

 int myints[] = {32,71,12,45,26,80,53,33};
  std::vector<int> myvector (myints, myints+8);

1. 函数(方法)/函数指针:这个我们都很熟悉,也可以传递函数指针做为参数,返回值等等。

 所以你可以先定义一个比较这函数,然后在排序的时候传递下面这个函数的指针:

bool myfunction (int i,int j) { return (i<j); }

std::sort (myvector.begin()+4, myvector.end(), myfunction); // 12 32 45 71(26 33 53 80)

2. C++函数对象:上面说了函数,但我们不能把函数做为参数这些传来传去,只能传函数指针,而函数指针不能保存值(或者说状态)。所以函数对象出现了,函数对象可以调用,与函数指针相比较他有两个方面的优点:首先如果被重载的调用操作符是inline函数则编译器能够执行内联编译,提供可能的性能好处;其次函数对象可以拥有任意数目的额外数据,用这些数据可以缓冲结果,也可以缓冲有助于当前操作的数据

你可以这样定义一个函数对象然后调用sort方法:

struct myclass {
// 这里你可以存任意的中间数据,或者状态 bool operator() (int i,int j) { return (i<j);} } myobject; std::sort (myvector.begin(), myvector.end(), myobject); //(12 26 32 33 45 53 71 80)

3. Lambd表达式(Reference)。由于函数和函数对象都需要先定义后使用(难道还有先使用后定义的?,我说的是在使用的地方就地解决/定义)。像现在这种情况,这个函数就是一个简单的比较而已,我们希望就地定义这个比较函数(可调用对象)。因此有了lambd表达式,这货最开始只有在引入了C++ Bost库的情况下才能用,不过C++ 11之后我们可以幸福的使用之了:

  std::sort (myvector.begin(), myvector.end(), [](int l, int r){return l < r;});     //(12 26 32 33 45 53 71 80)

其实C++里面把lambd也看着函数对象,只是匿名而已,不过在我知道lambd的时候,C++里还没有,只能引入boost库才能用,现在新标准出来,我还没系统的去学地学过,所以我还是把它分开吧。

好了扯回iOS了,估计好多为看iOS而点进来的胖友都走掉了。

4. iOS之Block  其实就是函数对象和lambd的合体。(就一句?有一种被骗的赶脚是不是)

===================分割线是不是这样用的====ARC & Block=======================================

 ARC和Block合用的时候是最容易出问题的了。

1. Blocks 作为property的时候要使用copy

@property (nonatomic, copy) SomeBlockType someBlock;

2. 防止retain cycle造成内存泄漏。下面是一种比较好的作法。

SomeObjectClass *someObject = ...
__weak SomeObjectClass *weakSomeObject = someObject;

someObject.completionHandler = ^{
    SomeObjectClass *strongSomeObject = weakSomeObject;
    if (strongSomeObject == nil)
    {
        // The original someObject doesn't exist anymore.
        // Ignore, notify or otherwise handle this case.
    }
    else
    {
        // okay, NOW we can do something with someObject
        [strongSomeObject someMethod];
    }
};

当然,如果Block不会被赋值给SomeObjectClass的property就不需要先定义一个weak对象。 

===================分割线是不是这样用的==========================================

下面才是我写这篇博客的目的(是的,我也为我的表达能力捉鸡)

错误做法一:把block当作函数返回执行结果的途径。

从上面我们可以看到,以前在C/C++里,不管我们使用函数指针,函数对象还是lambda,我们的使用场景都是这样的:

我们定义了一个算法,或者模块,而其中的一些逻辑或者策略我们把它抽象出来,然后让用户去定制,而定制的途径就是把这些部分抽象成函数指针/函数对象,用户传递不同的函数和函数对象来实现定制。

Block其实就是函数指针,函数对象,因此他的使用也应该是作为不同策略的抽象,但是到了现在的ObjC中,我们很多人却把block当作一种函数调用后返回结果值给调用者(caller)的途径。我想说的是,这种设计是错误的。原因如下:

同步函数的情况:

1. 如果参数只有一个,那你直接返回不就得了,如果多了一个NSError,那你可以把NSError作为一个输出参数(output param.)

2. 那如果要返回很多值呢,用很多的输出参数有问题。。也不好看好像。。嗯。。。是啊那肿么办?

爱因斯坦说过写程序里一个很重要的准则:一个函数只完成单一功能。因此如果出现了需要返回很多值的情况,我只有说你的函数,你的设计有问题,需要重新设计。当然也有例外。不过我觉得3,5个输出参数也比completion block好。以下有例为证:

// 此为一个类的实例方法实现
- (void)doSomthinsWithCompletionBlock:(void (^)(id param1, id param2, id param3, NSError *error))completionBlock { // do it }

 现在如果你需要调用doSomthinsWithCompletionBlock,你会这样做

- (void)otherMethod
{
      MyClass *obj ......

      [obj doSomthinsWithCompletionBlock:(void (^)(id param1, id param2, id param3, NSError *error){
      // Do you job here
    });
] }

 如果有大量的方法都是通过这种block的方式返回的,而你要用这些方法去实现一个功能/逻辑/算法。那你的代码的缩进好大,结构好难看的说有没有,这样的代码看起来有想屎的感觉没有?反正我是有的。。。

如果doSomthinsWithCompletionBlock是同步的,现在为了解决这个问题你可以这样:

- (void)otherMethod
{
      MyClass *obj ......

      __block id obj1;
      __block id obj2;
      __block id obj3;
      [obj doSomthinsWithCompletionBlock:(void (^)(id param1, id param2, id param3, NSError *error){
             obj1 = param1;
             obj2 = paarm2;
             obj3 = param3;
  });

      // Do you job here
  
] }

如果你的逻辑中要用到很多的completon block的函数, 这样改了以后,缩进会减少很多,代码结构好看多了有木有。。但是,与其把block反回的参数又赋值给外面的__block 变量,那我们直接用output param.不是更好吗?何况我有太多返回值基本上来说可以从设计上找到问题修改。

还有就是在异步方法中用completon block。现在的iOS开源网络库基本都是异步的,然后通过completion block在完成后完成客户特定的行为逻辑。。。说实话我设计实现不出像AFNetwork这样的东东,但是我觉得如果很多方法改为同步的也不是不可行。

错误做法二:提前优化,提前把自认为耗时的方法用异步的方法dispatch_async了

我们要完成一块任务,里面的逻辑实现,绝大部分是要建立在同步方法上的。。想像一下,第三方公司给了你一个框架,里面的方法绝大多数都是异步的,而你现在要用这些接口去完成你自己的业务。。这个逻辑要怎么写。。不用写,光想想我都已经egg pain to dead了。

事实上,大多数情况下,"仅仅因为这个操作很耗时就把它写成异步的,执行结果用completion block返回"的做法是不对的。因为别人调用你的代码可能根本就不在main thread里,别人dong't care你的耗时。当然这样做也是有好处的:别人在main thread里调用的时候不需要自己去dispatch_asyn了,不过也仅此而已。

而这样做的坏处却更明显

1. 在过多异步函数的情况下,函数执行流程不好控制, 算法/逻辑不好写

2. 在调用多个异步接口的地方,会出现过多的dispatch_async,而且还是dispatch_async到同一个queue上,虽然没有证据证明dipatch_async有多耗时,但我们依然没有必要做。

下面是例子:

- (void)method1:(void (^)(id param1))completionBlock
{
   dispatch_async(someQueue, ^{
    // do the job
    // ...
    comletionBlock(result);
  });
}
- (void)method2:(void (^)(id param1))completionBlock
{
   dispatch_async(someQueue, ^{
    // do the job
    // ...
    comletionBlock(result);
  });
}

我们只有这样调用了:

- (void)anotherJob
{
//    MyClass *obj ...
    [self method1:^(id param1) {
       // do something
        [self method2:^(id param1) {
            // do somthing
        }];
    }];
}

这还只是两个异步方法,如果有更多的异步方法,写起了真的是很受伤。。。

如果不用提前去把方法改成异步的,那可以如下:

- (id)method1
{
    // do the job
    ...
    return result;
}

- (id)method2
{
    // do the job
    ...
    
    return result;
}

- (void)anotherJob
{
    MyClass *obj ...
    
    dispatch_async(someQueue, ^{
        id result1 =  [obj method1];
        ....
        
        id resutl2 = [obj method2];
        
        ...
    });
}
原文地址:https://www.cnblogs.com/csutanyu/p/3850016.html