其实在此之前笔者已经大致了解了block的概念和用法, 但是当被别人问起更更细的问题时, 自己的思路还是会有些乱, 所以借此机会总结一下自己的了解的block知识点
首先摆出三个常规问题, 希望大家可以跟笔者一样先思考一下:
1 你知道block的概念吗(包括它是什么,有哪些block)? 你知道MRC和ARC下block存在的方式吗?
2 你知道为什么这么多人用block吗
3 在使用block时, 你是否常用__weak __strong __block 这几个关键字? 你知道他们的意义和原理吗?
先来看看第一个问题, 苹果官方对block的定义是什么样子的:
Block objects are a C-level syntactic and runtime feature. They are similar to standard C functions, but in addition to executable code they may also contain variable bindings to automatic (stack) or managed (heap) memory. A block can therefore maintain a set of state (data) that it can use to impact behavior when executed. (苹果官方地址:block的知识点)
从字面上的意思来看就是: block对象是C语言语法还有运行时特点, 跟C函数相似, 除了执行代码, 还可能包含变量的绑定(这些变量存在栈和堆里面), 所以一个block可以维护一组数据的状态,可以在执行的时候影响行为 (翻译可能比较low)
从上述所说, 其实我们可以知道 block是C语言语法 又有运行时特点,那不就是OC对象吗? 其次, 它跟C函数很相似 所以它是有自己的一个代码执行区域的, 也就是我们简称的代码块
接下来我们再来看看 有哪些block, 首先我们先来看下代码:
我们可以看到,不管是定义在方法里面的block,还是定义在方法外面的block, 他都是NSGlobalBlock
我们再来看看下面这个两个block:
发现没有? 这两个一样的代码, 但是block却是不一样的? 其实是笔者做了一个小改动,第一个是在ARC环境下面打印的block, 第二个是在MRC环境下面打印的block
所以到这里, 大家应该可以总结几个点:
1 block 有NSGlobalBlock(全局) NSMallocBlock(堆) NSStackBlock(栈) 三种
2 当block不引用外部变量的时候, 在MRC和ARC下面都是全局block,不管是在方法内部定义的,还是在外部定义的
3 当block引用到外部变量的时候, 在MRC下是栈block, 而在ARC下是堆block, 这个是编译器为我们做的, 而ARC为什么直接是堆block呢, 其实是编译器自动把栈block事先做一次copy, 放在堆中而已
关于第二个为什么这么多人用block这个问题
其实笔者感觉它最大的特点就是相比较代理(delegate)来说更加直观和方便抒写,使得代码的上下文结构更加紧凑,因为block可以直接作为参数传递,所以在得到结果之后, 可以直接回到block,然后做后续的操作
第三个问题, 关于__weak __strong __block 这几个关键字,为什么跟block紧密相关? 我们先来看一段代码:
上面这个demo的代码, 大家一目了然, 因为黄色警告已经告诉我们, 这段代码将会引发循环引用, 所以这时我们就需要用到上述三个关键字之一的 __weak
其实循环引用的原理大家都知道, 无非就是 A->B->A的模式, 所以我们只要打破这个模式 就不会引发循环引用, 就如__weak, 其实__weak跟weak修饰属性的本质是一样的, 非强引用,当指针指向的对象释放时,指针地址就变成nil, 所以正确的代码如下:
当然, 不是说用了__weak, 解决了循环引用,代码就不会出问题. 正常情况下一般是不会出现问题的, 因为我们获取到self的属性str, 然后打印结果. 但是如果在打印之前, self先被释放呢? 这又会出现什么情况, 看如下代码:
正常情况下VC没有被提前释放时:
VC被提前释放:
所以, 大家会发现, 如果我们引用的对象, 被提前释放了, 那么它里面的属性也就成为nil了,这时候可能block里面运行的结果就会出现问题, 严重的时候可能会crash, 这个时候__strong就起到了关键作用,在block里面强引用,代码如下:
__strong可以保证在Block里面不会被提前释放, 等于是在block里面做了一个强引用, 在block走完之后,会释放对象. 等于就是加一个自动释放池, 然后在方法走完之前,不会被释放, 走完之后自动释放对象
而对于__block, 大家也都熟悉, 因为在方法中定义的block不能直接改变作用域外面的局部变量的值,这个时候可以使用__block, 这样可以在block里面改变局部变量的值. 其实原理就是copy这个局部变量的地址指针,指向这个值所在的内存块, 这块内容笔者单独做一个分享,这里面涉及到block的的底层结构体:
iOS-小谈block的底层数据结构 -