zoukankan      html  css  js  c++  java
  • block使用小结、在arc中使用block、如何防止循环引用

    引言

    使用block已经有一段时间了,感觉自己了解的还行,但是几天前看到CocoaChina上一个关于block的小测试主题 : 【小测试】你真的知道blocks在Objective-C中是怎么工作的吗?,发现竟然做错了几道, 才知道自己想当然的理解是错误的,所以抽时间学习了下,并且通过一些测试代码进行测试,产生这篇博客。

    Block简介(copy一段)

    Block作为C语言的扩展,并不是高新技术,和其他语言的闭包或lambda表达式是一回事。需要注意的是由于Objective-C在iOS中不支持GC机制,使用Block必须自己管理内存,而内存管理正是使用Block坑最多的地方,错误的内存管理 要么导致return cycle内存泄漏要么内存被提前释放导致crash。 Block的使用很像函数指针,不过与函数最大的不同是:Block可以访问函数以外、词法作用域以内的外部变量的值。换句话说,Block不仅 实现函数的功能,还能携带函数的执行环境。

    可以这样理解,Block其实包含两个部分内容

    1. Block执行的代码,这是在编译的时候已经生成好的;
    2. 一个包含Block执行时需要的所有外部变量值的数据结构。 Block将使用到的、作用域附近到的变量的值建立一份快照拷贝到栈上。

    Block与函数另一个不同是,Block类似ObjC的对象,可以使用自动释放池管理内存(但Block并不完全等同于ObjC对象,后面将详细说明)。

    Block基本语法

    基本语法在本文就不赘述了,同学们自学。

    Block的类型与内存管理

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

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

    1、NSGlobalBlock如下,我们可以通过是否引用外部变量识别,未引用外部变量即为NSGlobalBlock,可以当做函数使用。

    {
        //create a NSGlobalBlock
        float (^sum)(float, float) = ^(float a, float b){
     
            return a + b;
        };
     
        NSLog(@"block is %@", sum); //block is <__NSGlobalBlock__: 0x47d0>
    }
    

     2、NSStackBlock如下:

     1 {
     2     NSArray *testArr = @[@"1", @"2"];
     3  
     4     void (^TestBlock)(void) = ^{
     5  
     6         NSLog(@"testArr :%@", testArr);
     7     };
     8  
     9     NSLog(@"block is %@", ^{
    10  
    11         NSLog(@"test Arr :%@", testArr);
    12     });
    13     //block is <__NSStackBlock__: 0xbfffdac0>
    14     //打印可看出block是一个 NSStackBlock, 即在栈上, 当函数返回时block将无效
    15  
    16     NSLog(@"block is %@", TestBlock);
    17     //block is <__NSMallocBlock__: 0x75425a0>
    18     //上面这句在非arc中打印是 NSStackBlock, 但是在arc中就是NSMallocBlock
    19     //即在arc中默认会将block从栈复制到堆上,而在非arc中,则需要手动copy.
    20 }

    3、NSMallocBlock只需要对NSStackBlock进行copy操作就可以获取,但是retain操作就不行,会在下面说明

    Block的copy、retain、release操作 (还是copy一段)

    不同于NSObjec的copy、retain、release操作:

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

    Block对外部变量的存取管理

    基本数据类型

    1、局部变量

    局部自动变量,在Block中只读。Block定义时copy变量的值,在Block中作为常量使用,所以即使变量的值在Block外改变,也不影响他在Block中的值。

     1 {
     2     int base = 100;
     3     long (^sum)(int, int) = ^ long (int a, int b) {
     4  
     5         return base + a + b;
     6     };
     7  
     8     base = 0;
     9     printf("%ld
    ",sum(1,2));
    10     // 这里输出是103,而不是3, 因为块内base为拷贝的常量 100
    11 }

    2、STATIC修饰符的全局变量

    因为全局变量或静态变量在内存中的地址是固定的,Block在读取该变量值的时候是直接从其所在内存读出,获取到的是最新值,而不是在定义时copy的常量.

     1 {
     2     static int base = 100;
     3     long (^sum)(int, int) = ^ long (int a, int b) {
     4         base++;
     5         return base + a + b;
     6     };
     7  
     8     base = 0;
     9     printf("%ld
    ",sum(1,2));
    10     // 这里输出是4,而不是103, 因为base被设置为了0
    11     printf("%d
    ", base);
    12     // 这里输出1, 因为sum中将base++了
    13 }

    3、__BLOCK修饰的变量

    Block变量,被__block修饰的变量称作Block变量。 基本类型的Block变量等效于全局变量、或静态变量。

    注:BLOCK被另一个BLOCK使用时,另一个BLOCK被COPY到堆上时,被使用的BLOCK也会被COPY。但作为参数的BLOCK是不会发生COPY的

    OBJC对象

    block对于objc对象的内存管理较为复杂,这里要分static global local block变量分析、还要分非arc和arc分析

    非ARC中的变量

    先看一段代码(非arc)

     1 @interface MyClass : NSObject {
     2     NSObject* _instanceObj;
     3 }
     4 @end
     5  
     6 @implementation MyClass
     7  
     8 NSObject* __globalObj = nil;
     9  
    10 - (id) init {
    11     if (self = [super init]) {
    12         _instanceObj = [[NSObject alloc] init];
    13     }
    14     return self;
    15 }
    16  
    17 - (void) test {
    18     static NSObject* __staticObj = nil;
    19     __globalObj = [[NSObject alloc] init];
    20     __staticObj = [[NSObject alloc] init];
    21  
    22     NSObject* localObj = [[NSObject alloc] init];
    23     __block NSObject* blockObj = [[NSObject alloc] init];
    24  
    25     typedef void (^MyBlock)(void) ;
    26     MyBlock aBlock = ^{
    27         NSLog(@"%@", __globalObj);
    28         NSLog(@"%@", __staticObj);
    29         NSLog(@"%@", _instanceObj);
    30         NSLog(@"%@", localObj);
    31         NSLog(@"%@", blockObj);
    32     };
    33     aBlock = [[aBlock copy] autorelease];
    34     aBlock();
    35  
    36     NSLog(@"%d", [__globalObj retainCount]);
    37     NSLog(@"%d", [__staticObj retainCount]);
    38     NSLog(@"%d", [_instanceObj retainCount]);
    39     NSLog(@"%d", [localObj retainCount]);
    40     NSLog(@"%d", [blockObj retainCount]);
    41 }
    42 @end
    43  
    44 int main(int argc, char *argv[]) {
    45     @autoreleasepool {
    46         MyClass* obj = [[[MyClass alloc] init] autorelease];
    47         [obj test];
    48         return 0;
    49     }
    50 }

    执行结果为1 1 1 2 1。

    __globalObj和__staticObj在内存中的位置是确定的,所以Block copy时不会retain对象。

    _instanceObj在Block copy时也没有直接retain _instanceObj对象本身,但会retain self。所以在Block中可以直接读写_instanceObj变量。

    localObj在Block copy时,系统自动retain对象,增加其引用计数。

    blockObj在Block copy时也不会retain。

    ARC中的变量测试

    由于arc中没有retain,retainCount的概念。只有强引用和弱引用的概念。当一个变量没有__strong的指针指向它时,就会被系统释放。因此我们可以通过下面的代码来测试。

    代码片段1(globalObject全局变量)

     1 NSString *__globalString = nil;
     2  
     3 - (void)testGlobalObj
     4 {
     5     __globalString = @"1";
     6     void (^TestBlock)(void) = ^{
     7  
     8         NSLog(@"string is :%@", __globalString); //string is :(null)
     9     };
    10  
    11     __globalString = nil;
    12  
    13     TestBlock();
    14 }
    15  
    16 - (void)testStaticObj
    17 {
    18     static NSString *__staticString = nil;
    19     __staticString = @"1";
    20  
    21     printf("static address: %p
    ", &__staticString);    //static address: 0x6a8c
    22  
    23     void (^TestBlock)(void) = ^{
    24  
    25         printf("static address: %p
    ", &__staticString); //static address: 0x6a8c
    26  
    27         NSLog(@"string is : %@", __staticString); //string is :(null)
    28     };
    29  
    30     __staticString = nil;
    31  
    32     TestBlock();
    33 }
    34  
    35 - (void)testLocalObj
    36 {
    37     NSString *__localString = nil;
    38     __localString = @"1";
    39  
    40     printf("local address: %p
    ", &__localString); //local address: 0xbfffd9c0
    41  
    42     void (^TestBlock)(void) = ^{
    43  
    44         printf("local address: %p
    ", &__localString); //local address: 0x71723e4
    45  
    46         NSLog(@"string is : %@", __localString); //string is : 1
    47     };
    48  
    49     __localString = nil;
    50  
    51     TestBlock();
    52 }
    53  
    54 - (void)testBlockObj
    55 {
    56     __block NSString *_blockString = @"1";
    57  
    58     void (^TestBlock)(void) = ^{
    59  
    60         NSLog(@"string is : %@", _blockString); // string is :(null)
    61     };
    62  
    63     _blockString = nil;
    64  
    65     TestBlock();
    66 }
    67  
    68 - (void)testWeakObj
    69 {
    70     NSString *__localString = @"1";
    71  
    72     __weak NSString *weakString = __localString;
    73  
    74     printf("weak address: %p
    ", &weakString);  //weak address: 0xbfffd9c4
    75     printf("weak str address: %p
    ", weakString); //weak str address: 0x684c
    76  
    77     void (^TestBlock)(void) = ^{
    78  
    79         printf("weak address: %p
    ", &weakString); //weak address: 0x7144324
    80         printf("weak str address: %p
    ", weakString); //weak str address: 0x684c
    81  
    82         NSLog(@"string is : %@", weakString); //string is :1
    83     };
    84  
    85     __localString = nil;
    86  
    87     TestBlock();
    88 }

    由以上几个测试我们可以得出:
    1、只有在使用local变量时,block会复制指针,且强引用指针指向的对象一次。其它如全局变量、static变量、block变量等,block不会拷贝指针,只会强引用指针指向的对象一次。
    2、即时标记了为__weak或__unsafe_unretained的local变量。block仍会强引用指针对象一次。(这个不太明白,因为这种写法可在后面避免循环引用的问题)

    循环引用retain cycle

    循环引用指两个对象相互强引用了对方,即retain了对方,从而导致谁也释放不了谁的内存泄露问题。如声明一个delegate时一般用assign而不能用retain或strong,因为你一旦那么做了,很大可能引起循环引用。在以往的项目中,我几次用动态内存检查发现了循环引用导致的内存泄露。

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

    1 self.myblock = ^{
    2  
    3             [self doSomething];
    4         };

    为测试循环引用,写了些测试代码用于避免循环引用的方法,如下,(只有arc的,懒得做非arc测试了)

     1 - (void)dealloc
     2 {
     3  
     4     NSLog(@"no cycle retain");
     5 }
     6  
     7 - (id)init
     8 {
     9     self = [super init];
    10     if (self) {
    11  
    12 #if TestCycleRetainCase1
    13  
    14         //会循环引用
    15         self.myblock = ^{
    16  
    17             [self doSomething];
    18         };
    19 #elif TestCycleRetainCase2
    20  
    21         //会循环引用
    22         __block TestCycleRetain *weakSelf = self;
    23         self.myblock = ^{
    24  
    25             [weakSelf doSomething];
    26         };
    27  
    28 #elif TestCycleRetainCase3
    29  
    30         //不会循环引用
    31         __weak TestCycleRetain *weakSelf = self;
    32         self.myblock = ^{
    33  
    34             [weakSelf doSomething];
    35         };
    36  
    37 #elif TestCycleRetainCase4
    38  
    39         //不会循环引用
    40         __unsafe_unretained TestCycleRetain *weakSelf = self;
    41         self.myblock = ^{
    42  
    43             [weakSelf doSomething];
    44         };
    45  
    46 #endif
    47  
    48         NSLog(@"myblock is %@", self.myblock);
    49  
    50     }
    51     return self;
    52 }
    53  
    54 - (void)doSomething
    55 {
    56     NSLog(@"do Something");
    57 }
    58  
    59 int main(int argc, char *argv[]) {
    60     @autoreleasepool {
    61         TestCycleRetain* obj = [[TestCycleRetain alloc] init];
    62         obj = nil;
    63         return 0;
    64     }
    65 }

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

    end

  • 相关阅读:
    EBS SQL > Form & Report
    oracle sql 优化分析点
    MRP 物料需求计划
    MRPII 制造资源计划
    Barcode128 应用实务
    Oracle SQL语句优化技术分析
    APPSQLAP10710 Online accounting could not be created. AP Invoice 无法创建会计分录
    Oracle数据完整性和锁机制
    ORACLE Responsibility Menu Reference to Other User
    EBS 常用 SQL
  • 原文地址:https://www.cnblogs.com/PressII/p/3925630.html
Copyright © 2011-2022 走看看