zoukankan      html  css  js  c++  java
  • 一些关于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];
            
            ...
        });
    }
    
  • 相关阅读:
    图片的像素和位图相关的知识
    LeetCode第四十四题-字符串匹配
    LeetCode第四十三题-字符串相乘
    LeetCode第四十二题-可装雨水的容量
    LeetCode第四十一题-寻找数组中的最小的缺失正整数
    LeetCode第四十题-求组合所有可能性(2)
    LeetCode第三十九题-求组合所有可能性
    LeetCode第三十八题-报数
    LeetCode第三十七题-实现数独
    LeetCode第三十六题-判断九宫格是否有效
  • 原文地址:https://www.cnblogs.com/csutanyu/p/3850016.html
Copyright © 2011-2022 走看看