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

栏目: Objective-C · 发布时间: 5年前

内容简介:级别: ★★☆☆☆标签:「iOS」「Block」「Objective-C」作者:MrLiuQ

级别: ★★☆☆☆

标签:「iOS」「Block」「Objective-C」

作者:MrLiuQ

审校:QiShare团队

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

本篇的主题是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对象。还有一个函数指针,指向了块的实现代码。

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

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 修饰。否则修改不了。

我们来看下打印:

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

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

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

iOS 编写高质量Objective-C代码(六)
  • 2. NSMallocBlock 堆块: 堆block内存位于堆区,在变量作用域结束时依然可以使用。

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

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

例如:

void (^qiShare)(void) = ^{
    
    NSLog(@"We love sharing.");
};
NSLog(@"%@",qiShare);
复制代码
iOS 编写高质量Objective-C代码(六)

二、为常用的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(微信公众号)


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

嵌入式Linux应用开发完全手册

嵌入式Linux应用开发完全手册

韦东山 主编 / 人民邮电出版社 / 2008-8 / 69.00元

本书全部实例代码及相关工具。 基于ARM 9+Linux 206平台,从基础讲起,引导读者快速入门,实例丰富,可直接应用于工程实践。 本书全面介绍了嵌入式Linux系统开发过程中,从底层系统支持到上层GUI应用的方方面面,内容涵盖Linux操作系统的安装及相关工具的使用、配置,嵌入式编程所需要的基础知识(交叉编译工具的选项设置、Makefile语法、ARM汇编指令等),硬件部件的使用及......一起来看看 《嵌入式Linux应用开发完全手册》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具