新葡京8455:block不一定可以自动拷贝

作者:澳门新葡京平台游戏

没有copy 使用完就释放了 是不会造成循环引用的.

       需要封装的代码块;
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
    __strong __typeof(weakSelf)strongSelf = weakSelf;

    strongSelf.networkReachabilityStatus = status;
    if (strongSelf.networkReachabilityStatusBlock) {//很关键 判空if (strongSelf) 是这段代码的亮点。之所以在Block的代码执行之前加上这样一个判断,就是为了防止在把 weakSelf 转换成 strongSelf 之前 weakSelf 就已经为 nil 了,这样才能确保万无一失。
        strongSelf.networkReachabilityStatusBlock(status);
    }

};
strong typeof(weakSelf)strongSelf = weakSelf;就是解决这个问题的关键~先将强引用的对象转为弱引用指针,防止了 Block 和对象之间的循环引用。再在 Block 的第一句代码出将 weakSelf 的弱引用转换成 strongSelf 这样的强引用指针,防止了多线程和 ARC 环境下弱引用随时被释放的问题(因为强引用的引用计数至少为1)。

这里大家一定要有一个认识,weakSelf 位于 Block 的外面,strongSelf 位于 Block 的里面。从内存管理的角度来看,weakSelf 是要比 strongSelf 的声明周期要长的。这样就形成了从弱引用到强引用,再从强引用到弱引用的一种变化,也称作 weak-strong dance。

摘要:OC中的block和swift中的闭包在iOS开发中很常见,使用场景也非常多,例如块动画.GCD的回调.迭代器以及逆向传值等.下面将从相关原理. 逆向传值. 内存管理和循环引用几个方面详细介绍OC中的block.

- (NSArray *)mas_makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;- (NSArray *)mas_makeConstraints:(MASConstraintMaker *))block { self.translatesAutoresizingMaskIntoConstraints = NO; MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self]; block(constraintMaker); return [constraintMaker install];}

因为block创建的时候,它的内存是分配在栈上的(stack),所以如果除了这个作用域他就会被销毁,所以如果在作用域外使用block的话就会崩溃,使用copy修饰block会把block拷贝到堆(heap)上,所以用copy修饰.

在 block 中先写一个 strong self,其实是为了避免在 block 的执行过程中,突然出现 self 被释放的尴尬情况。通常情况下,如果不这么做的话,还是很容易出现一些奇怪的逻辑,甚至闪退。

  • 其中比较有意思的地方就是先在block外定义一个弱引用的weakself指向self,然后在block内定义一个强引用的self指向weakSelf。那么就针对这两点来讲讲block里weak/strong self的问题。。
    weakSelf->self 我们都知道,block比较常见的一个问题就是循环引用问题,简单描述即:self已经持有了block,如果block里再使用self,self将被block截获,然后block持有self,导致循环引用。那么我们在block外定义一个weakSelf指向self,然后block就会截获这个weakSelf,不会再产生循环引用的问题。

  • strongSelf->weakSelf block中还有个有意思的点就是block截获的变量在超出其作用域后仍能使用(比如block截获了self,然后block又被传递到其它地方使用,此时self按理已经释放)。这其实是因为系统会自动的根据情况将block从栈拷贝到堆中并强引用它截获的变量(一般我们的block最开始都是在栈中的),我们知道栈的内存是由系统管理的,而堆是由程序猿管理的,所以实现了变量超出作用域仍能使用。
    根据这个说法,我们应该不需要自己强引用weakSelf,我们的weakSelf应该也会被block自己强引用,那我们何必多次一举呢…事实也确实如此,block确实强引用了我们的weakSelf,就算我们不自己强引用weakSelf代码也不会有问题。但是我们在上一段中提到了系统拷贝block是有条件的,有些条件下系统不会自动拷贝block,这种情况下weakSelf超出作用域将被释放。那么哪些情况下系统不会自动copy呢?最常见的一个——block作为参数传递,这也是使用频率非常高的一个点。所以,自己动手是为了更加保险。
    下面列出block能够拷贝的情况:

调用block的copy方法
block作为返回值
block赋值时
Cocoa框架中方法名中含有usingBlock的方法
GCD中

*关于GCD中block再提一点: GCD中的block并没有直接或间接被self强引用的,所以不会存在循环引用,故不需要weakSelf;又GCD中block能够自动copy,所以self超出作用域仍可用,故不需要写strongSelf
*总结: weakSelf是为了解决循环引用 strongSelf是为了保证任何情况下self在超出作用域后仍能够使用


block 调用self 有可能会导致循环引用,但是是否会引起循环引用你只要看函数内部是否copy了这个block就知道了.创建的block在栈中使用完就释放了,生命周期不超出作用域,copy了就将block复制到堆中查看Masonry mas_makeConstraints方法

比如说有一个类A,在A类中有一个block属性,在控制器中,我们创建A对象,并且把它赋值给一个A类型的属性,再给A的block属性赋值,如果这时候在block代码块中引用了self就会出现一下这种现象,

在用到block时,我们经常会有这样一种用法

二.Block和控制器的循环引用

1.控制器没有引用block,而block引用了控制器,示例代码:

- (void)viewDidLoad {   

 [super viewDidLoad];

// 定义block

void (^block)() = ^ {

NSLog(@"%@",self.view);   

 };

// 调用block

block();

}

在控制器的dealloc方法中打印输出,验证控制器的销毁:

- (void)dealloc {

NSLog(@"%s", __FUNCTION__);

}

运行结果:控制器的dealloc方法被触发,说明没有循环引用.因为block执行完毕后会释放,从而释放对self的引用.

2.将block定义为控制器的属性,block内部又引用了self(ViewController),示例代码:

*定义属性

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

*在 viewDidLoad 中记录 block

- (void)viewDidLoad {    

[super  viewDidLoad];

void (^block)() = ^ {

NSLog(@"%@",self.view);    

};

// 记录 block

self.demoBlock= block;

}

        同样,在控制器的dealloc方法中打印输出,验证控制器的销毁情况.

运行结果:dealloc方法没有被触发,说明发生了循环引用.因为控制器引用了block,block内部又引用了self.相互强引用,造成了循环引用.

解决方案:使用__weak修饰符,定义一个弱引用的对象,示例代码:

- (void)viewDidLoad {    

[super viewDidLoad];

// 把self临时定义成弱引用

__weak typeof(self) weakSelf = self;

void (^block)() = ^ {

NSLog(@"%@",weakSelf.view);   

};

// 记录 block

self.demoBlock= block;

}

       特别要注意的时,在使用block时,如果block内部引用了self,同时使用了属性记录了block,要格外注意是否出现循环引用.

       另外,不是所有的self,都会出现循环引用,像UIView的动画代码块在执行完就销毁.

        再就是ARC下,成员变量也是被控制器对象强引用,如果在block内部引用了成员变量,也相当于间接强引用了控制器,那么要注意这个时候,控制器有没有也对block形成强引用.示例代码:

@implementation ViewController {

NSMutableArray* _arrayM;

}

- (void)viewDidLoad {   

 [super viewDidLoad];    

__weak typeof(self) weakSelf = self;

void(^block)() = ^ {

// 循环引用点在 `_arrayM`

NSLog(@"%@ %@", weakSelf.view, _arrayM);   

 };

// 记录 block

self.demoBlock= block;

}

在swift里有一个很好的雅称,中文翻译版叫非逃逸闭包。这个闭包只能在当前作用域里执行,不能超出。即无法将这个闭包异步调用或等待时机调用。这种闭包用完就被释放了,所以不会有引用循环之类的问题。

在新的iOS API中block被大量用来取代传统的delegate和callback,而新的API会大量使用block主要是基于以下两个原因:

三.block的回调

    block的回调功能是GCD诞生的基础.显而易见,它在反向传值中的使用非常广泛.在iOS开发中,block和代理都能实现反向传值的功能,那么它们有什么区别呢?

block用法分为四步:

被调用方:

定义block属性 ->  调用block

调用方: 

初始化block -> 在需要的时候执行block(前提是block已经初始化,否则运行时会crash).

代理的实现步骤有七步:

被调用方:

定义协议 -> 声明代理方法 ->定义代理属性 ->需要的时候判断代理对象是否能响应代理方法,能的情况下进行调用

调用方:

遵守协议 -> 成为代理对象 ->实现代理方法

       另外,block的所有的代码写在一起,可读性更好,使用代理的话,代码相对分散.但是block的一个回调对应一个属性,属性定义在头文件中,容易和其他属性混在一起.而代理设计模式通过协议预先定好代理方法,更加严谨.尤其是在协议方法很多的时候,使用协议更加直观清晰.

内部引用变量 self,进入block的是self.view,block调用了self,但self并没有调用block,所以不会循环引用

Block 一般是用来表示、简化一小段的程式码,它特别适合用来建立一些同步执行的程式片段、封装一些小型的工作或是用来做为某一个工作完成时的回传呼叫(callback) 。

一.Block的内存管理

    1.不引用任何外部变量的block,保存在全局区(__NSGlobalBlock__),代码的执行效率高,但这种情况在实际开发中几乎是不存在的.eg:

- (void)blockDemo1 {

    void(^myBlock)() = ^ {

     NSLog(@"hello world");

     };

     NSLog(@"%@", myBlock);

}

  2.引用外部变量的block.在MRC环境下,block默认存储在栈区,出了作用域就会弹栈.要想作为能够被全局访问的属性,就要用copy修饰,因为在对全局属性的block实例化的时候,走setter方法,用copy修饰系统会将block从栈区拷贝到堆区,实现全局共享.目前开发基本都在ARC环境下,block本来就在堆区,做为全局属性既可以用strong修饰,但apple建议使用copy修饰.

3.在MRC和ARC下,block做为属性和成员变量的区别:

       MRC下,block默认存储在栈区.做为属性赋值时,系统会将其拷贝到堆区,引用计数器也会 1.而对成员变量赋值时,仅仅是计数器 1,不会进行copy操作.

        ARC下,block无论作为属性还是成员变量都是强引用.赋值时都会copy.

2>block的标志是^

四.关于block使用的补充

1.当block引用外部的变量时,会对外部变量进行一次拷贝,对外部变量的真实值不会造成影响;

2.如果要在block内部修改外部的局部变量,需要使用__block修饰这个局部变量.在block内部使用了这个变量后,这个变量的地址在后续的使用中都在堆区.

        可以这样理解,block内部不能修改外部局部变量的原因是,block一般作为回调使用,经常性的需要传递到别的类中,局部变量出了block的作用域就会被销毁,为了让局部的变量不销毁,就用__block来进行标记.

1>block是可以用来保存一段代码或者说封装一段代码,-->代码块

可以存取区域变数,在传统的callback实作时,若想要存取区域变数得将变数封装成结构才能使用,而block则是可以很方便地直接存取区域变数。

定义block的时候,变量a的值就传递到了block结构体中,仅仅是值传递,所以在block中修改a是不会影响到外面的a变量的。
根据isa指针,block一共有3种类型的block_NSConcreteGlobalBlock 全局静态_NSConcreteStackBlock 保存在堆中,出函数作用域就销毁_NSConcreteMallocBlock 保存在栈中,retainCount == 0销毁

返回值类型 (^名称)(参数类型...) = ^(参数类型:参数名...){

3,Block的定义

因为声明的block只在所属的域中生效,因为调用block()时,定义的两个block实现已经失效了,内存已经被释放了,在stack中推出,这就是stack block
.
heap block
为了解决这个问题,我们可以通过copy
将block由stack copy至 heap,这样block就能够在它所属域之外被引用。当block保存在stack中时,系统机制会在调用完毕后自动清理它,相比之下,当在heap中时,block就与其他变量类似,接受引用计数管理,当block没必有再进行持有时,需要对其进行release
操作(在ARC中,会自动插入release
代码),没有对象持有它时,就会对其heap中的内存进行释放。如以下代码
void (^block)();if(/true/){ block = [^{ NSLog(@"AAAA"); } copy];}else{ block = [^{ NSLog(@"BBBB"); } copy];}block();
这样的话这段代码是正确的,当然如果在非ARC环境下,需要对block执行release操作。

3>block跟函数很像(可以有返回值,可以有参数,使用时必须调用)

定义一个Block:

类A强引用block, 控制器强引用类A, block强引用控制器self, 造成循环引用.
那么如何解决循环引用呢,其实就是使一方变成弱引用就可以了,在这里把block对self的强引用变成弱引用,

4,Block为什么用copy修饰和循环引用问题

1.Block是什么:
我们所需要知道的是 block 就是一个对象,一个block本质上就是一个函数指针,即那个代码快的内存地址。block常用作传值,实际上就是把block的地址传到要调用block的地方。在它所在的内存中,保存着block自身的实现函数,可在调用block时用block自身的代码替代,同时保持着一个Block描述,标志着block的内存size与持有对象的指针。
2.Block的实现:
当声明与实现一个Block时,创建的闭包会捕获在它的域中的任何涉及的变量,通过在内存中持有他们,能够在block的实现中对其进行访问。在默认情况下,任何在block的域中被捕获的变量都不能被修改,除非这个变量已被给予了__block的标志,如果是用block(用static也可以)修饰的局部变量,在block内部访问的话,而是把这个局部变量的地址传递过去了,所以会跟踪这个局部变量的变化,并且可以修改,
如果block内部引用的变量是全局变量的话,那么在block内部访问,他也是把这个变量的地址传递过去了.。当block捕获了一个对象时,它会对其进行retain操作,并在block代码执行完毕完release对象,这样才能保证在block执行过程中,对象不会因引用计数为0而被释放掉。我们需要理解的是,block本身就是一个对象,它对其他对象的引用与一般的对象引用类似,都是需要对引用对象进行retain与release

可以直接在程式码中撰写等会要接着执行的程式,直接将程式码变成函数的参数传入函数中,这是新API最常使用block的地方。

5,Block的实际使用

__weak typeof(self) weakSelf = self; 使用weakSelf代替self即可.

}

grobal block
全局 blcok与之前的stack block 、 heap block 不同,当一个block在闭包中不捕获程序的任何上下文(如各种程序中的变量)时,编译器在编译阶段就能够知道这个block执行的所需要的所有信息,这时,block会当做全局变量保存在全局内存中,以相当于单例的形式存在,它将不会收到任何release消息。这是编译器的一个优化点,减少了当block被copy或销毁时的多余操作。

Block的类型
stack block
看看下面这段代码,当block被定义时,block会被分配在stack(堆)中的一块内存中,这意味着这个block仅在自己所声明的域中生效,因此,这份代码是会出错的

1.所谓block就是Objective-C的对象一个block本质上就是一个函数指针,即那个代码快的内存地址。block常用作传值,实际上就是把block的地址传到要调用block的地方。闭包就是能够读取其它函数内部变量的函数.

因为block创建的时候,它的内存是分配在栈上的(stack),所以如果除了这个作用域他就会被销毁,所以如果在作用域外使用block的话就会崩溃,使用copy修饰block会把block拷贝到堆(heap)上,所以用copy修饰.

如果在block中访问到self的时候一定要格外小心,很有可能造成循环引用的问题,但是也不是绝对,那么什么时候会引起循环引用的问题呢?

本文由新葡京8455发布,转载请注明来源

关键词: