iOS内存管理(4)--Block属性用copy修饰 & 避免循环引用的问题

一、Block的类型

根据Block在内存中的位置分为三种类型NSGlobalBlockNSStackBlock, NSMallocBlock

  • NSGlobalBlock:类似函数,位于text段;
  • NSStackBlock:位于栈内存,函数返回后Block将无效;
  • NSMallocBlock:位于堆内存。


二、Blockcopyretainrelease操作

  不同于NSObjeccopyretainrelease操作:

  • Block_copycopy等效,Block_releaserelease等效;
  • Block不管是retaincopyrelease都不会改变引用计数retainCountretainCount终是1
  • NSGlobalBlockretaincopyrelease操作都无效;
  • NSStackBlockretainrelease操作无效,必须注意的是,NSStackBlock在函数返回后,Block内存将被回收。即使retain也没用。容易犯的错误是[[mutableAarry addObject:stackBlock],(补:在arc中不用担心此问题,因为arc中会默认将实例化的block贝到堆上在函数出栈后,从mutableArray中取到的stackBlock经被回收,变成了野指针。正确的做法是先将stackBlock copy到堆上,然后加入数组:[mutableArray addObject:[[stackBlock copy] autorelease]]支持copycopy之后生成新的NSMallocBlock类型对象
  • NSMallocBlock:支持retainrelease,虽然retainCount终是1,但内存管理器中仍然会增加、减少计数。copy之后不会生成新的对象,只是增加了一次引用,类似retain
  • 尽量不要Block使用retain操作

三、ARC与非ARC下的block

对于引用了外部变量的Block,如果没有对他进行copy,他的作用域只会在声明他的函数栈内(类型是__NSStackBlock__),如果想在非ARC下直接返回此类Block,Xcode会提示编译错误的,如下图:

屏幕快照 2013-12-13 下午5.15.13

(Xcode提示Returning block that lives on the local stack)

而在ARC环境下,上述代码会编译通过,因为ARC会自动加入copy操作。

比如可以在ARC下运行如下代码:

//ARC
MyBlock block = func();
NSLog(@"%d", block());
NSLog(@"%@", [block class]);

输出:

123
__NSMallocBlock__

类型是__NSMallocBlock__,说明Block已经被copy到了堆中了。

即便把Block用strong修饰,系统也会把block copy到堆中。

例如:@property (nonatomic,strong)void (^SubmitBlock)(GoodsModel *goodsModel,int number);

 打印一下block的类型为如下图


当然其实在非ARC下,也可以使上面有错误的函数编译通过。如下代码:

typedef int(^MyBlock)();
MyBlock func()
{
    int i = 123;
    //非ARC下不要这样!!!
    MyBlock ret = ^{ return i; };
    return ret;
}

    我们把原来的返回值赋给一个变量,然后再返回这个变量,就可以编译通过了。不过虽然编译通过了,这个返回的Block作用域仍是在函数栈中的,因此一旦函数运行完毕后再使用这个Block很可能会引发BAD_ACCESS错误。

以在非ARC下,必须把Block复制到堆中才可以在函数外使用Block,如下正确的代码:

typedef int(^MyBlock)();
MyBlock func()
{
    //非ARC
    int i = 123;
    return [^{ return i; } copy];
}

    我们可以直接通过输出变量的指针,就可以验证Block被copy后,他所引用的变量被复制到了堆中的情况,如下代码(非ARC下):

//非ARC
void func()
{
    int a = 123;
    __block int b = 123;
    NSLog(@"%@", @"=== block copy前");
    NSLog(@"&a = %p, &b = %p", &a, &b);
    
    void(^block)() = ^{
        NSLog(@"%@", @"=== Block");
        NSLog(@"&a = %p, &b = %p", &a, &b);
        NSLog(@"a = %d, b = %d", a, b = 456);
    };
    block = [block copy];
    block();
    
    NSLog(@"%@", @"=== block copy后");
    NSLog(@"&a = %p, &b = %p", &a, &b);
    NSLog(@"a = %d, b = %d", a, b);
    
    [block release];
}

输出:

=== block copy前
&a = 0x7fff5fbff8bc, &b = 0x7fff5fbff8b0
=== Block
&a = 0x100201048, &b = 0x100201068
a = 123, b = 456
=== block copy后
&a = 0x7fff5fbff8bc, &b = 0x100201068
a = 123, b = 456

   可以看到,在Block执行中,他所引用的变量a和b都被复制到了堆上。而被标记__block的变量事实上应该说是被移动到了堆上,因此,当Block执行后,函数栈内访问b的地址会变成堆中的地址。而变量a,仍会指向函数栈内原有的变量a的空间。

四、So,Block属性的声明,首先需要用copy修饰符。Block默认存放在栈中,可能随时被销毁,需要作用域在堆中,所以只有copy后的Block才会在堆中,栈中的Block的生命周期是和栈绑定的


五、循环引用的问题

   循环引用是另一个使用Block时常见的问题。为什么会循环引用?因为retain。

     因为block在拷贝到堆上的时候,会retain其引用的外部变量,那么如果block中如果引用了他的宿主对象,那很有可能引起循环引用,如:

self.myblock = ^{
    
    [self doSomething];
    
};

      下面是对ARC环境做的测试:

- (void)dealloc
{
    NSLog(@"no cycle retain");
}



- (id)init
{
    self = [super init];
    
    if (self) {
        
        
#if TestCycleRetainCase1
        
        
        
        //会循环引用
        
        self.myblock = ^{
            
            [self doSomething];
            
        };
        
#elif TestCycleRetainCase2
        
        
        
        //会循环引用
        
        __block TestCycleRetain *weakSelf = self;
        
        self.myblock = ^{
            
            [weakSelf doSomething];
            
        };
        
        
        
#elif TestCycleRetainCase3
        
        
        
        //不会循环引用
        
        __weak TestCycleRetain *weakSelf = self;
        
        self.myblock = ^{
            
            [weakSelf doSomething];
            
        };
        
        
        
#elif TestCycleRetainCase4
        
        //不会循环引用
        
        __unsafe_unretained TestCycleRetain *weakSelf = self;
        
        self.myblock = ^{
            
            [weakSelf doSomething];
            
        };
        
        
#endif
        
        NSLog(@"myblock is %@", self.myblock);
        
    }
    
    return self;
}



- (void)doSomething
{
    NSLog(@"do Something");
}



int main(int argc, char *argv[]) {
    
    @autoreleasepool {
        
        TestCycleRetain* obj = [[TestCycleRetain alloc] init];
        
        obj = nil;
        
        return 0;
    }
}


    经过上面的测试发现,在加了__weak__unsafe_unretained变量引入后,TestCycleRetain方法可以正常执行dealloc方法,而不转换和用__block转换的变量都会引起循环引用。
    因此防止循环引用的方法如下:  __weak TestCycleRetain *weakSelf = self;


   所以,在ARC下,由于__block抓取的变量一样会被Block retain,所以必须用弱引用才可以解决循环引用问题,iOS 5之后可以直接使用__weak,之前则只能使用__unsafe_unretained了,__unsafe_unretained缺点是指针释放后自己不会置空。示例代码:

//iOS 5之前可以用__unsafe_unretained
//__unsafe_unretained typeof(self) weakSelf = self;
__weak typeof(self) weakSelf = self;
self.myBlock = ^(int paramInt)
{
    //使用weakSelf访问self成员
    [weakSelf anotherFunc];
};

在非ARC下,显然无法使用弱引用,这里就可以直接使用__block来修饰变量,它不会被Block所retain的,参考代码:

//非ARC
__block typeof(self) weakSelf = self;
self.myBlock = ^(int paramInt)
{
    //使用weakSelf访问self成员
    [weakSelf anotherFunc];
};


六、__weak 和 __block的区别


1. 循环引用时:有上文可知,__weak用在ARC环境时;__block仅可用在非ARC环境时(因为ARC环境下仍然会被retain)。

2. 修改局部变量时:需要加__block,否则不能在block中修改局部变量。如下:

__block int multiplier = 7;
     int (^myBlock)(int) = ^(int num) {
         multiplier ++;//这样就可以了
         return num * multiplier;
     };



    

本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。
前言 module 怎能少得了动画呢~ 代码解读 weex code API 接口 transition (node, options, callback) Arguments 参数node(Node):将要动画的元素。options( object ):操作选项styles( object ):指定要应用的过渡效果的样式的名称和值。color( string ):色彩的元素时,animaiton完成。transform( object ):变换函数被应用到元素。支持下列值。translate/ tran
目录 概述 这是一个关于 RecycleView 滑动事件的辅助类,该辅助类可以检测 RecycleView 滑动到顶部或者底部的状态. 可用于实现 RecycleView 加载更多或者刷新(虽然刷新可以直接用 SwipeRefreshLayout ).也可用于某些滑动相关的需求,如 FloatingActionButton 的隐藏与显示之类的. 关于 RecycleView 的滑动监听 RecycleView 本身已经提供了滑动的监听接口, OnScrollListener ,这个接口包含了以下的方法.
这个小案例建议在手机上运行。 package com.example.camera;import java.io.File;import android.net.Uri;import android.os.Bundle;import android.os.Environment;import android.provider.MediaStore;import android.app.Activity;import android.content.Intent;import android.view.Me
简介 NSScanner是一个类,用于在字符串中扫描指定的字符,尤其是把它们翻译/转换为数字和别的字符串。可以在创建NSScaner时指定它的string属性,然后scanner会按照你的要求从头到尾地扫描这个字符串的每个字符。 NSScanner官方文档 NSScanner类是一个类簇的抽象父类,该类簇为一个从NSString对象扫描值的对象提供了程序接口。 NSScanner对象把NSString 对象的的字符解释和转化成 number和string 类型的值。在创建NSScanner对象的时候为它分
目录 概述 StickHeaderItemDecoration 是用于显示固定头部的item装饰类,扩展来自系统的 ItemDecoration .本文参考了一部分 sticky-headers-recyclerview 原理 绘制头部 固定头部的 ItemDecoration 本质是在 RecycleView 上覆盖一个界面.该界面没有随着滑动变动所以看起来就像一个固定的头部. 绘制item间隔 ItemDecoration 也可以实现每个item之间的间隔的绘制(比如分隔线之类的),这种情况下就不是在
android中网络请求回来数据之后,我们要对其解析。请求的返回的结果格式如果不是自定义协议;那么返回的数据通常是xml,json,html形式的数据了。 下面就是针对上面3种格式进行解析。 xml解析使用工具:在android中推荐使用pull解析,还有其他的dom,sax解析。 json解析使用工具:推荐使用Fastjson,由阿里提供。还有其他的如JackSon,Gson解析。 html解析使用工具:推荐使用Jsoup,还有其他的如HtmlParser;关于使用这个,网络上的爬虫就是这样子的。 1.
一个SpriteKit项目在其他设备上运行都无问题(无论是真机或是模拟器),但是在iPhone6 Plus上会出现精灵对象纹理被过度放大的现象: 从上图中大家可以看到无论是主角或是道具球都过大了. 看了一下精灵图片是放在atlas纹理集文件夹中的: 可以看到PowerUp和Player都有对应缩放的版本:Player.png,Player@2x.png以及Player@3x.png. 在各个图片的属性中检查图片的尺寸也都正确,看不出神马问题… 找到SKSpriteNode对象初始化的代码看看: let o
转载请标明出处: 一片枫叶的专栏 本文中我将介绍一下我自己封装的一个小的工具类库:按钮点击事件类库。 作用: 该类库可以防止按钮重复点击,可以判断网络状态,可以判断用户登录状态,以及自定义验证条件等等。 说明: 其实现的核心原理就是通过自定义实现自身的OnClickListener类,并重写其中的onClick方法,在onClick方法中执行相应的判断逻辑之后回调我们自定义的抽象方法。 具体效果如下图所示: 使用方式 屏蔽多次点击事件 /** * 测试快速点击事件 */ fastButton.setOnC

算法基础:排序与查找 - 2016-06-24 17:06:40

1、直接插入排序 1.1、基本思想: 在要排序的一组数中,假设前面(n-1) [n=2] 个数已经是排好顺序的,现在要把第n个数插到前面的有序数中,使得这n个数也是排好顺序的;如此反复循环,直到全部排好顺序。 1.2、实现思路: INSERTION_SORT( A )for i = 2 to n j = i- 1 key = A [i] while j 0 A [j] key A [j+ 1 ] = A [j] j-- A [j+ 1 ] = key 1.3、算法实现: public static int
【腾讯TMQ】5小时搞定谷歌原生自动化框架UiAutomator1.0 前言 谷歌对UI测试(UI Tetsting)的概念是:确保用户在一系列操作过程中(例如键盘输入、点击菜单、弹出对话框、图像显示以及其他UI控件的改变),你的应用程序做出正确的UI响应。 UI测试(功能测试、黑盒测试)的好处是不需要测试者了解应用程序的内部实现细节,只需要知道当执行了某些特定的动作后是否会得到其预期的输出。这种测试方法,在团队合作中可以更好地分离的开发和测试角色。然而常见的UI测试多是以手动方式去执行,然后去验证程序是