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

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

内容简介:级别: ★★☆☆☆标签:「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(微信公众号)


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

查看所有标签

猜你喜欢:

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

Head First jQuery

Head First jQuery

Ryan Benedetti , Ronan Cranley / O'Reilly Media / 2011-9 / USD 39.99

Want to add more interactivity and polish to your websites? Discover how jQuery can help you build complex scripting functionality in just a few lines of code. With Head First jQuery, you'll quickly g......一起来看看 《Head First jQuery》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器