ios block和代理的区别

ios block和代理的区别,第1张

首先两者作用是一样的,都是进行单一回调。

不通的是,delegate是个对象,然后用过一个对象自己调用代理协议函数来完成整个流程。

block是传递一个函数指针,利用函数指针执行来进行回调。

还有在内存管理上需要注意,delegate不需要保存引用。block对引用数据有copy的处理。

前言:最近看了好多block相关的东西,block在项目中很常见,想必大家也都比较熟悉,可是,细细想下,又有好多迷惑?为什么会产生循环引用 为什么要用__block __block到底是干什么的如果你也有类似的困惑,那就跟我一起探究下吧。

首先探究下block的本质,他到底是个啥东东呢?

通过clang编译,我们可以看到block的内存布局,就是一个结构体,里面包含一个我么熟悉的isa指针,显然就是一个oc对象。它的底层结构就是:

通过class方法我们可以看到block有3种类型

NSGlobalBlock : 存放在数据段,没有访问auto变量(局部变量)的block(基本没啥用)

NSStackBlock:存放在栈区,访问了auto变量

NSMallocBlock:存放在堆区,NSStackBlock通过copy,从栈区拷贝到堆区

每一种block被copy后的结果

在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上。

MRC下block属性的建议写法

@property (copy, nonatomic) void (^block)(void);

ARC下block属性的建议写法

@property (strong, nonatomic) void (^block)(void);

@property (copy, nonatomic) void (^block)(void);

当block内部访问了对象类型的趋同变量时

1如果block在栈上,将不会对auto变量产生强引用

2如果block被拷贝到堆上

   21 会调用block内部的copy函数

    22 copy 函数内部会调用_block_object_assign函数

    23 _block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)作出相应的 *** 作,形成强引用还是弱引用。

3如果将block从堆上移除

    31 会调用block内部的dispose函数

    32 dispose函数会调用_Block_object_dispose函数

    33 _Block_object_dispose函数自动释放引用的auto变量

当我们想在block内部修改auto变量的时候,总是需要用__block来修饰,这是为什么呢?

31 为什么不能直接在block内部修改auto变量?

当block捕获auto变量的时候,只是获取了它的值,没有获取到它的地址,所以不能进行修改。

那么我们都知道为了解决block无法修改auto变量的问题,需要用到__block来修饰auto变量。

32 那么__block做了什么呢?

被__block 修饰过的auto变量会生成一个结构体

其中__main_block_impl_0是个新的结构体,里面包含一个age指针,这是又是一个结构体__block_byref_age_0,这个结构体的age 才是我们真正传入的值,forwarding也是一样的结构体,他是指向自己的一个指针

总结下__block的使用:

1__block可以用于解决block内部无法修改auto变量值的问题

2 __block不能修饰全局变量,静态变量

3 编译器会将__block变量包装成一个对象

41 当block在栈上,并不会对__block变量产生强引用

42 当block被拷贝到堆时

    421 会调用block内部的copy函数

    422 copy函数会调用_Block_object_assign函数   

    423 _Block_object_assign函数会对__block变量行程强引用

43 当block从堆中移除时

    431 会调用block内部的disease函数

    432 dispose函数内部会调用_Block_object_dispose函数

    433 _Block_object_dispose函数会自动释放饮用的__block变量

双方互相持有对方,都无法释放,这就是循环引用

我们经常会想到的解决方案就是用__weak 修饰,下面看看还有哪些解决方法

61 ARC

611 用__weak、__unsafe_unretained解决

612 用__block解决(必须要调用block)

62 MRC

621 用__unsafe_unretained解决

622 用__block解决

看完后,心中好多疑惑大概都解决了,那下面几道面试题来回顾下

1block的本质是什么?

答:封装了函数调用以及调用环境的oc对象

2__block 的作用是什么?有什么需要注意的?

答:作用:__block可以用于解决block内部无法修改auto变量值的问题

       注意点: __block不能修饰全局变量,静态变量;在MRC环境中,不会被强引用。

3block的属性修饰为什么是copy,使用block有哪些注意点?

答:block如果没有被copy,就不会在堆上,这样我们无法预知它的生命周期。只有将block被拷贝到堆上,我们才能管理其生命周期,想什么时候释放都行。注:ARC环境下,strong和copy没有区别,为了跟MRC环境中一样,我们喜欢用copy。

     需要注意的是,使用block不要产生循环引用。解决循环饮用的方法之前都提到过。

block是一个代码块,比如一些其它Web编程语言中的“匿名函数”。在objc中通常使用block实现代理方法实现的功能,也就是回调。使用代理需要设置代理的数据接收者,而且代理方法是被分离开来处理的,block可以将这些分离的代码放到一个代码块中。

/////////h

typedef void(^MyBlock)(int value);

@property (nonatomic,weak) MyBlock block;

-(void)setMyBlock:(void(^)(int value)) block;

/////////m

-(void)setMyBlock:(void(^)(int value)) block {

if (block) {

selfblock=block;

}

}

我们都知道block在oc中是很常用的,随处可见,越来越多的原先delegate的实现现在都用block去处理了,可见block的重要性。

那么什么是block呢?block在使用的时候要注意什么呢?

直接show code,看看底层block到底是变成了啥数据结果的

So,NSGlobalBlock、NSStackBlock、NSMallocBlock这3种又有啥区别呢

在MRC下:

NSGlobalBlock:没有访问(捕获)auto变量(局部变量)的block (数据区)

NSStackBlock:访问(捕获)auto变量(局部变量)的block (栈区)

NSMallocBlock: 对NSStackBlock做了一次copy *** 作后得到的block。(堆区)

在ARC下:

被强指针引用的block且引用了外部变量,那么会自动做一次copy *** 作,即把NSStackBlock上的block copy到NSMallocBlock上。即被strong,copy修饰的block且用了外部变量就是NSMallocBlock。

判断捕获对象释放:

NSStackBlock(栈上的block)会对捕获对象进行强引用。(在arc模式下,block作为函数的参数传递,此时的block是NSStackBlock)

NSMallocBlock(堆上的block)会对捕获的对象进行引用,捕获的对象也会被copy到堆空间上。(如果捕获的对象是strong类型的,就会强引用,如果是__weak 修饰的weak类型,就会弱引用)。

block访问外部变量有几种方式呢?

那么__block 做了啥呢?

__block 修饰后,底层会把捕获的局部变量包装成一个对象,通过捕获这个变量来修改局部变量的值。

总结来说,如果是__block修饰的变量,在block内部被修改了,这个过程中存在了3层copy *** 作:

1_NSConcreteGlobalBlock

全局的静态block,不会访问外部的变量。就是说block没有调用其他的外部变量。

2_NSConcreteStackBlock

保存在栈中的 block,当函数返回时会被销毁。这个block就是没有被赋值,并且block访问了外部变量。

3_NSConcreteMallocBlock

保存在堆中的 block,当引用计数为 0 时会被销毁,对_NSConcreteStackBlock 执行copy得来的。

block 使用copy 修饰的原因:

我们知道,函数的声明周期是随着函数调用的结束就终止了。我们的block是写在函数中的。

如果是全局静态block的话,他直到程序结束的时候,才会被被释放。但是我们实际 *** 作中基本上不会使用到不访问外部变量的block(_NSConcreteGlobalBlock)。

如果是保存在栈中的block,他会随着函数调用结束被销毁。从而导致我们在执行一个包含block的函数之后,就无法再访问这个block。因为(函数结束,函数栈就销毁了,存在函数里面的block也就没有了),我们再使用block时,就会产生空指针异常(_NSConcreteStackBlock)。

如果是堆中的block,也就是copy修饰的block。他的生命周期就是随着对象的销毁而结束的。只要对象不销毁,我们就可以调用的到在堆中的block(_NSConcreteMallocBlock)。

这就是为什么我们要用copy来修饰block。因为不用copy修饰的访问外部变量的block,只在他所在的函数被调用的那一瞬间可以使用。之后就消失了。

block 什么时候被自动copy

作为变量:

一个 block 刚声明的时候是在栈上

赋值给一个普通变量之后就会被 copy 到堆上

赋值给一个 weak 变量不会被 copy

函数传参:

作为参数传入函数  block不会被 copy

作为函数的返回值  block会被 copy

针对 block 做为参数 传入函数 block 不会被copy,这些知名的开源库是这么做的

这里写描述

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/12185313.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-21
下一篇 2023-05-21

发表评论

登录后才能评论

评论列表(0条)

保存