关于block内存问题的分析

来源:互联网 发布:淘宝装修队模板免费 编辑:程序博客网 时间:2024/05/17 22:35

话不多说, 先根据代码结果看block到底在内存的哪个分区:

一:

MRC下, 声明的block实现部分,没有引入外界的任何局部变量

int main(int argc, const char * argv[]) {

    @autoreleasepool {

        

        //定义bolck

        void(^myBlock)() = ^{

            NSLog(@"呵呵");

        };

        

        //打印block地址

        NSLog(@"%@", myBlock);

        //回调

        myBlock();

        

    }

    return 0;

}

//打印结果

 19:32:50.813 fenxiang[2174:162871] <__NSGlobalBlock__: 0x100001050>

 19:32:50.814 fenxiang[2174:162871]呵呵


此时block位于全局静态区,此时block的内存不需要程序管理,程序运行结束时, 内存被系统回收.


二:

MRC下, 声明的block实现部分引入全局变量

int a = 10;

int main(int argc, const char * argv[]) {

    @autoreleasepool {

        

        //定义bolck

        void(^myBlock)() = ^{

            NSLog(@"呵呵, %d", a);

        };

        

        //打印block地址

        NSLog(@"%@", myBlock);

        //回调

        myBlock();  

    }

    return 0;

}

//打印结果

 19:40:20.623 fenxiang[2236:166090] <__NSGlobalBlock__: 0x100001050>

 19:40:20.624 fenxiang[2236:166090]呵呵, 10


此时block位于全局静态区此时block的内存不需要程序管理程序运行结束时内存被系统回收.


三: MRC下, 声明的block实现部分引入static变量

int main(int argc, const char * argv[]) {

    @autoreleasepool {

        static int a = 10;

        //定义bolck

        void(^myBlock)() = ^{

            NSLog(@"呵呵, %d", a);

        };

        

        //打印block地址

        NSLog(@"%@", myBlock);

        //回调

        myBlock();  

    }

    return 0;

}

//打印结果

 19:40:20.623 fenxiang[2236:166090] <__NSGlobalBlock__: 0x100001050>

 19:40:20.624 fenxiang[2236:166090] 呵呵, 10

此时block位于全局静态区此时block的内存不需要程序管理程序运行结束时内存被系统回收.


四:这个注意看啦!  MRC下, 声明的block实现部分引入局部变量(基础类型和对象类型)

int main(int argc, const char * argv[]) {

    @autoreleasepool {

        int a = 10;

        //定义bolck

        void(^myBlock)() = ^{

            NSLog(@"呵呵, %d", a);

        };

        

        //打印block地址

        NSLog(@"%@", myBlock);

        //回调

        myBlock();  

    }

    return 0;

}

//打印结果

2016-01-22 19:45:55.477 fenxiang[2254:168302] <__NSStackBlock__: 0x7fff5fbff7c8>

2016-01-22 19:45:55.478 fenxiang[2254:168302]呵呵, 10


看结果,变化啦! 当block中引入局部变量(基础类型和对象类型),此时block位于栈区,出了函数作用域, 该内存就被释放掉了,在执行回调的时候, 使用栈区的block很危险,容易造成野指针问题! 怎么解决这个野指针问题呢, 请看第5个例子.


五: MRC下, 声明的block实现部分引入局部变量(基础类型和对象类型), 此时block位于栈区, 此时对栈区的block进行copy操作

int main(int argc, const char * argv[]) {

    @autoreleasepool { 

        int a = 10;

        //定义bolck

        void(^myBlock)() = ^{

            NSLog(@"呵呵, %d", a);

        };

        

        //block进行copy操作

        myBlock = [myBlock copy];

        //打印block地址

        NSLog(@"%@", myBlock);

        //回调

        myBlock();  

    }

    return 0;

}

//打印结果

20:07:44.521 fenxiang[2284:174041] <__NSMallocBlock__: 0x1002068a0>

20:07:44.522 fenxiang[2284:174041]呵呵, 10


看到结果没有, 此时block位于堆区, 我们对栈区的block进行copy操作此时block内存就会由栈区迁徙到堆区.

现在我们可以看出, 在block传值中, 后一个页面写block属性时修饰词用copy的原因, 因为在从后一个页面向前一个页面传值中, block中引入了局部变量(这里就是对象类型), 此时的block位于栈区, 如果不用copy, 就出现野指针, 程序就会崩溃.当我们用了copy后, 对栈区的block进行copy操作此时block内存就会由栈区迁徙到堆区, 堆区是可以由开发人员去管理的.

那么如果我们对位于全局静态区的block进行copy, 会出现什么结果呢, 请看例子6.


六: MRC下, 声明的block实现部分引入静态变量此时block位于全局静态区, 此时对全局静态block进行copy操作

int main(int argc, const char * argv[]) {

    @autoreleasepool { 

       static int a = 10;

        //定义bolck

        void(^myBlock)() = ^{

            NSLog(@"呵呵, %d", a);

        };

        

        //block进行copy操作

        myBlock = [myBlock copy];

        //打印block地址

        NSLog(@"%@", myBlock);

        //回调

        myBlock();  

    }

    return 0;

}

//打印结果

20:25:30.420 fenxiang[2306:179169] <__NSGlobalBlock__: 0x100001050>

20:25:30.421 fenxiang[2306:179169]呵呵, 10


结果显示, 此时block依然位于全局静态区.  结合第5个例子, 我们总结: 对栈区的block进行copy操作,此时block内存就会由栈区迁徙到堆区,我们对位于全局静态区的block进行copy,此时还在全局静态区.


下面我们来举一个block内部是对象类型的例子, 因为block内部是对象类型这种情况我们经常用到.结合上面的例子, 如果block内部是局部变量, 那么此时block位于栈区,  然后对栈区的block进行copy操作, 那么block内存就会由栈区迁徙到堆区, 那么block内部的对象会有怎样的改变呢?我们看例子7.


七: MRC下, 声明的block实现部分引入局部变量(此时是对象类型,  我们定义一个Person类, 这个类有个age属性), 此时block位于栈区, 此时对栈区的block进行copy操作, 然后打印person对象的引用计数

int main(int argc, const char * argv[]) {

    @autoreleasepool {

        //创建person

        Person *person1  = [[Person alloc] init];

        person1.age = 18;

        //定义bolck

        void(^myBlock)() = ^{

            NSLog(@"呵呵, %ld", (long)person1.age);

        };

        

        //block进行copy操作

        myBlock = [myBlock copy];

        //打印block地址

        NSLog(@"%@", myBlock);

        //回调

        myBlock();

        //打印person的引用计数

        NSLog(@"person.retainCount = %lu", (unsigned long)[person1 retainCount]);

        [person1 release];    

    }

    return 0;

}

//打印结果

 20:51:06.924 fenxiang[2372:186493] <__NSMallocBlock__: 0x100206a40>

 20:51:06.925 fenxiang[2372:186493]呵呵, 18

 20:51:06.925 fenxiang[2372:186493] person.retainCount = 2

看结果, person对象的引用计数为2, 我们在创建person时, alloc一次, 引用计数从0变成1, 那么为什么现在是2呢? 原因就是block的copy,  当栈区的block引用的是对象类型的局部变量,当进行copy, 内存迁徙到堆区的时候,会对所引用的对象类型进行引用计数+1. 根据内存管理原则, alloc一次, release一次, 但release后, 现在引用计数还有个1, 怎么办呢?请看例子8.


八: MRC下, 声明的block实现部分引入局部变量(此时是对象类型,  我们定义一个Person类, 这个类有个age属性), 此时block位于栈区, 此时对栈区的block进行copy操作, 然后打印person对象的引用计数

int main(int argc, const char * argv[]) {

    @autoreleasepool {

        //创建person类, 注意, 此时person类由__block修饰

        __block Person *person1  = [[Person alloc] init];

        person1.age = 18;

        //定义bolck

        void(^myBlock)() = ^{

            NSLog(@"呵呵, %ld", (long)person1.age);

        };

        

        //block进行copy操作, 内存从栈区迁徙到堆区, 这个时候person对象应该引用计数 +1, 但是由于__block的修饰, 它并没有+1

        myBlock = [myBlock copy];

        //打印block地址

        NSLog(@"%@", myBlock);

        //回调

        myBlock();

        //打印person的引用计数

        NSLog(@"person.retainCount = %lu", (unsigned long)[person1 retainCount]);

        [person1 release];    

    }

    return 0;

}

//打印结果

 20:51:06.924 fenxiang[2372:186493] <__NSMallocBlock__: 0x100206a40>

 20:51:06.925 fenxiang[2372:186493] 呵呵, 18

 20:51:06.925 fenxiang[2372:186493] person.retainCount = 1


我们仔细观察代码, 只在例子7的基础上, 加个__block, 让__block修饰person类,  这样person的引用计数问题就解决啦.在这里__block的用处在于block迁徙的时候,告诉系统不要对所引入的对象类型进行引用计数+1.


其实还有个解决办法, Block_release, 请看例子9.


九: MRC下, 声明的block实现部分引入局部变量(此时是对象类型,  我们定义一个Person类, 这个类有个age属性), 此时block位于栈区, 此时对栈区的block进行copy操作, 然后打印person对象的引用计数

int main(int argc, const char * argv[]) {

    @autoreleasepool {

        //创建person

         Person *person1  = [[Person alloc] init];

        person1.age = 18;

        //定义bolck

        void(^myBlock)() = ^{

            NSLog(@"呵呵, %ld", (long)person1.age);

        };

        

        //block进行copy操作

        myBlock = [myBlock copy];

        //打印block地址

        NSLog(@"%@", myBlock);

        //回调

        myBlock();

        //打印person的引用计数

        NSLog(@"person.retainCount = %lu", (unsigned long)[person1 retainCount]);

        [person1 release]; 


        // (MRC)堆区的block内存需要进行释放

        Block_release(myBlock);

   

    }

    return 0;

}

//打印结果

 20:51:06.924 fenxiang[2372:186493] <__NSMallocBlock__: 0x100206a40>

 20:51:06.925 fenxiang[2372:186493] 呵呵, 18

 20:51:06.925 fenxiang[2372:186493] person.retainCount = 2

观察代码, person对象引用计数为2, person对象release一下, block再release一下, 然后ok!

说到__block, 就要再说一下block的实质, 请看例子10

十: MRC下, 声明的block实现部分引入局部变量此时block位于栈区

int main(int argc, const char * argv[]) {

    @autoreleasepool { 

//定义a

        int a = 10;

        //定义bolck

        void(^myBlock)() = ^{

            NSLog(@"呵呵, %d", a);

        };

        

        //对a值进行修改

a = 20;

 

        //回调

        myBlock();  

    }

    return 0;

}

//打印结果

 22:08:59.587 fenxiang[2610:209577]呵呵, 10


为什么重新给a赋值, a的值还是10呢?  如果把a的定义改为:__block int a = 10; 打印结果就变成了20;

其实在底层上, block就是指向结构体的指针, 当我们局部变量前面加上static__block或者是一个全局变量的时候,他们在传入结构体的时候,相当于传入了一个变量的指针,所以当我们在调用之前改变a的值的时候,结果会发生变化.



0 0
原创粉丝点击