iOS 编写高质量Objective-C代码(六)
《编写高质量OC代码》已经顺利完成一二三四五六七篇!
附上链接:
iOS 编写高质量Objective-C代码(一)—— 简介
iOS 编写高质量Objective-C代码(二)—— 面向对象
iOS 编写高质量Objective-C代码(三)—— 接口和API设计
iOS 编写高质量Objective-C代码(四)—— 协议与分类
iOS 编写高质量Objective-C代码(五)—— 内存管理机制
iOS 编写高质量Objective-C代码(六)—— block专栏
iOS 编写高质量Objective-C代码(七)—— GCD专栏
本篇的主题是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对象。还有一个函数指针,指向了块的实现代码。
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
修饰。否则修改不了。
我们来看下打印:
什么?居然是
__NSMallocBlock__
(堆块)?
那是因为ARC环境下,编译器自动帮我们加了copy操作。
这时我们关掉ARC:设置Objective-C Automatic Reference Counting = NO
。再来看下打印:
- 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 内存泄漏排查方法及原因分析
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
安卓开发学习笔记(五):史上最简单且华丽地实现Android Stutio当中Webview控件https/http协议的方法
一.我们先在XML当中自定义一个webview(Second_layout.xml) 代码如下: 1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:app="http://schemas.android.com/apk/res-auto" 4 xmlns:tools="http://schemas.android.com/tools" 5 android:layout_width="match_parent" 6 android:layout_height="match_parent" 7 android:orientation="vertical" 8 tools:context=".SecondActivity" 9 android:background="@drawable/ic_launcher"> 10 <WebView 11 android:id="...
- 下一篇
Android组件化开发实践(九):自定义Gradle插件
本文紧接着前一章Android组件化开发实践(八):组件生命周期如何实现自动注册管理,主要讲解怎么通过自定义插件来实现组件生命周期的自动注册管理。 1. 采用groovy创建插件 新建一个Java Library module,命名为lifecycle-plugin,删除 src->main 下面的java目录,新建一个groovy目录,在groovy目录下创建类似java的package,在 src->main 下面创建一个 resources 目录,在resources目录下依次创建 META-INF/gradle-plugins 目录,最后在该目录下创建一个名为 com.hm.plugin.lifecycle.properties的文本文件,文件名是你要定义的插件名,按需自定义即可。最后的工程结构如图所示: 修改module的build.gradle文件,引入groovy插件等: apply plugin: 'java-library' apply plugin: 'groovy' apply plugin: 'maven' dependencies { implem...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS8编译安装MySQL8.0.19
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS关闭SELinux安全模块
- CentOS7设置SWAP分区,小内存服务器的救世主
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS8安装Docker,最新的服务器搭配容器使用