iOS 编写高质量Objective-C代码(六)

作者: QiShare | 来源:发表于2018-10-15 11:16 被阅读431次

    级别: ★★☆☆☆
    标签:「iOS」「Block」「Objective-C」
    作者: MrLiuQ
    审校: QiShare团队

    前言:
    这几篇文章是小编在钻研《Effective Objective-C 2.0》的知识产出,其中包含作者和小编的观点,以及小编整理的一些demo。希望能帮助大家以简洁的文字快速领悟原作者的精华。
    在这里,QiShare团队向原作者Matt Galloway表达诚挚的敬意。

    文章目录如下:
    iOS 编写高质量Objective-C代码(一)
    iOS 编写高质量Objective-C代码(二)
    iOS 编写高质量Objective-C代码(三)
    iOS 编写高质量Objective-C代码(四)
    iOS 编写高质量Objective-C代码(五)
    iOS 编写高质量Objective-C代码(六)


    本篇的主题是iOS中的 “Block的原理及应用”

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

    • block(块):是一种 “ 词法闭包 ”,通过block,开发者可将代码块像对象一样传递。

    一、理解“block”的概念:

    1. block的数据结构:

    通过clang命令行工具(OC转C++),我们先来看一下block的内部数据结构大概是什么样子的?

    struct Block_descriptor {
        unsigned long int reserved;
        unsigned long int size;
        void (*copy)(void *dst, void *src);
        void (*dispose)(void *);
    };
    
    struct Block_layout {
        void *isa;
        int flags;
        int reserved; 
        void (*invoke)(void *, ...);
        struct Block_descriptor *descriptor;
        /* Imported variables. */
    };
    

    解析:很显然,Block_layout是一个结构体:里面有一个isa指针,指向Class对象。还有一个函数指针,指向了块的实现代码。

    block的数据结构
    2. block的三种类型:全局块、栈块、堆块。

    根据block在内存中的位置,block被分成三种类型:

    类型 内存位置 介绍
    __NSStackBlock__ 栈区 栈内有效,出栈后销毁。
    __NSMallocBlock__ 堆区 copy到堆空间上。可以在定义的那个范围之外使用。
    __NSGlobalBlock__ 全局区 不捕捉任何外部变量,全部信息在编译器就已确定。

    • 1. NSStackBlock 栈块:
      栈块保存于栈区,超出变量作用域,栈上的block以及声明的_block都会被销毁。

    例如:

    __block NSString *name = @"QiShare";
    void (^block)(void) = ^{
        NSLog(@"%@ is an iOS team which loves to share technology.", name);
    };
    NSLog(@"block = %@", block);
    

    小知识点:当block内部需要修改或访问外部变量时,外部变量需要额外用__block修饰。否则修改不了。

    我们来看下打印:

    ARC的场景

    什么?居然是__NSMallocBlock__(堆块)?
    那是因为ARC环境下,编译器自动帮我们加了copy操作。

    这时我们关掉ARC:设置Objective-C Automatic Reference Counting = NO。再来看下打印:

    MRC场景
    • 2. NSMallocBlock 堆块:
      堆block内存位于堆区,在变量作用域结束时依然可以使用。

    通过上面的例子:
    在ARC下,block会默认加上copy操作:变成__NSMallocBlock__


    • 3. NSGlobalBlock 全局块:
      块中无任何外界对象,所需的内存在编译时就可以确定,内存位于全局区。
      类似于“单例”,copy是一个空操作。

    例如:

    void (^qiShare)(void) = ^{
        
        NSLog(@"We love sharing.");
    };
    NSLog(@"%@",qiShare);
    

    二、为常用的block类型创建typedef

    为了增加代码的可读性可拓展性
    需要为常用的block起个别名。

    typedef为块起别名,也可令块变量用起来更加简单~
    比如:

    - (void)getDataWithToken:(NSString *)token success:(void (^)(id responseDic))success;
    
    //! 以上要改成下面这种
    typedef void (^SuccessBlock)(id responseDic);
    - (void)getDataWithToken:(NSString *)token success:(SuccessBlock)success;
    

    三、用handler块降低代码分散程度

    在我们iOS开发中,经常会异步执行一些任务,等待任务执行结束后再通知对象调用相关方法。
    一般有三种做法:

    • 第一种:使用NSNotificationCenter:NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    • 第二种:使用委托协议:详情见iOS 编写高质量Objective-C代码(四)
    • 第三种:使用block回调:直接把block对象当做参数传给相关方法执行。

    举个例子:AFNetworking的API设计及使用就是block回调

    • 接口设计:
    - (NSURLSessionDataTask *)POST:(NSString *)URLString
                        parameters:(id)parameters
                           success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
                           failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure {
    
        return [self POST:URLString parameters:parameters progress:nil success:success failure:failure];
    }
    
    • 使用:
        AFHTTPSessionManager *manger =[AFHTTPSessionManager manager];
        NSString *urlString = @"";
        NSMutableDictionary *parameter= @{@"":@"",@"":@""};
        
        [manger POST:urlString
                parameters:parameter 
                success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                    NSLog(@"成功");
                } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                    NSLog(@"%@",error);
                }];
    }
    

    四、用block引用其所属对象时避免出现循环引用

    在我们日常开发中,如果block使用不当,很容易导致内存泄漏。

    • 理由:如果block被当前ViewController(self)持有,这时,如果block内部再持有ViewController(self),就会造成循环引用。
    • 解决方案:在block外部对弱化self,再在block内部强化已经弱化的weakSelf

    For Example:

    __weak typeof(self) weakSelf = self;
    
    [self.operationQueue addOperationWithBlock:^{
    
        __strong typeof(weakSelf) strongSelf = weakSelf;
    
        if (completionHandler) {
    
            KTVHCLogDataStorage(@"serial reader async end, %@", request.URLString);      
            completionHandler([strongSelf serialReaderWithRequest:request]);
        }
    }];
    

    当然,也不是所有block中使用到self都要先弱化成weakSelf,再强化成strongSelf
    只要block没有被self所持有的,在block中就可以使用self。
    比如下面:

    [QiNetwork requestBlock:^(id responsObject) {
          NSLog(@"%@",self.name);
      }];
    

    小贴士:内存泄漏检测相关知识请看:iOS 内存泄漏排查方法及原因分析


    关注我们的途径有:
    QiShare(简书)
    QiShare(掘金)
    QiShare(知乎)
    QiShare(GitHub)
    QiShare(CocoaChina)
    QiShare(StackOverflow)
    QiShare(微信公众号)


    推荐文章:
    奇舞周刊278期
    iOS 推送通知

    相关文章

      网友评论

      • vvvei:clang 把objc转为c的命令是:
        clang -rewrite-objc test.m
        直接在文章里贴出来会方便读者。

      本文标题:iOS 编写高质量Objective-C代码(六)

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