OC语言Block 续

OC语言 Block

转载:http://blog.csdn.net/weidfyr/article/details/48138167

1.Block对象中的变量行为

变量行为

结论:

  1. 在block代码块内部可以访问定义的全局变量,局部变量,静态局部变量,但是访问局部静态变量时候是只读的并且局部变量和在代码块中访问到的不是同一个地址的变量,他们在数值上相等,互相似乎没什么联系。 因为代码块中使用到局部变量的时候,会将局部变量进行const类型的copy,所以在代码块中访问到的局部变量都是只读的;静态变量和全局变量都存放在静态区,在程序运行过程中都存在,他们可以在不同的代码块中共享,不同代码块中访问到的同一个全局变量,局部变量是同一块内存的数据;对于普通局部变量在代码块中只读,全局变量和静态局部变量在代码块中可以读写。

  2. 在块句法的主体中,除块句法内部的局部变量和形参之外,还包含块句法当前位置处可以访问的变量;这些变量中包含外部变量也包含块中可以访问的局部变量。

  3. 代码块中访问局部变量时候,局部变量会从栈内存被const类型的copy一份到堆内存中。

块对象和函数指针的定义使用功能都差不多,块对象的精髓之处就在于,在块对象中可以访问到上下文的变量,而函数指针不能。

2.块对象的实例和生命周期

  • 1)块句法也可以写在函数的外部,当写在函数外面时候,只是在静态数据区分配一块内存给块对象,这块区域在程序执行期间会一直存在。
  • 2)块句法写在函数内部的时候,块对象和变量的生命周期和普通局部变量一样,块对象的内存区域会在执行包含块对象的函数时保存在栈上;该块对象的生命周期就是函数运行期间。
  • 3)在现实的实现中,当函数内的块语法不包含自动变量的时候,就没必要进行复制值,所以块对象的内存区域也会被保存在静态数据区。
  • 4)block代码块被保存在堆或者静态区中,不会被保存在栈中,如下图可以说明这一点。

生命周期

示例
- (void)function
{
    int i;
    int (^blocks[10])(); //定义一个块对象类型的数组
    for (i = 0; i < 10; i++) { //for循环给数组赋值
        blocks[i] = ^{ return i; };
    }
    for (i = 0; i < 10; i++) { //打印数组中的内容,就是每个数组存放的代码块的返回值
        NSLog(@"%d", blocks[i]());
    }
}
// 如上代码,在非ARC环境下运行结果是10个9,原因是虽然循环了十次,但是只有一个实体。
// 以上代码在ARC环境下是正确的,后面做说明。

3.块对象的复制

  • 函数内的块对象和局部变量的生命周期相同,都只是在函数的执行期间。但是在函数的方法调用参数中直接代入块对象也是块对象的一种非常常见的用法,这时候使用与函数调用关系或栈状态无关的块对象是非常必要的。
  • 有一个函数可以复制块对象到新的堆内存,通过使用该函数,即使是在函数内部定义的块对象也能独立于栈被持续的使用,此外还有一个函数可以释放不需要的块对象。

Block_copy( block )

  • 1.参数为栈上的块对象的时候,返回堆上的块对象。参数为堆上的块对象或者静态区的块对象,不进行复制,直接返回原对象,但是会增加参数块对象的引用计数。

Block_release( block )

  • 2.减少参数块对象的引用计数。当引用计数减到0时候,块对象被释放。
  • 3.在使用这些函数的时候,需要引入头文件Block.h .堆上的块对象使用引用计数的方式来管理。即使使用垃圾回收也必须成对出现。使用ARC时候可以不考虑这些,编译器会自动帮我们判断什么时候释放,什么时候保持。
// 用法示例:
g = Block_copy(block);
Block_rlease(g);

4.指定特殊变量 __block

  • ARC下测试结果和总结:
    特殊变量

  • 非ARC下测试结果和总结:
    特殊变量

使用block时候注意事项:

使用注意事项:

  • 1)在块内改变外部变量的值时候,在外部变量前加__block,否则该值在block块内部是只读的。

  • 2)在引用某个实例变量或者所在控制器本身时候,在ARC下,要再前面加__weak如:__weak (typeof(self) weak self = self), 在mrc下用__block, 这样做是为了避免内存泄露和循环引用。

  • 3)在使用block前需要对block指针做判空处理,如果是MRC的编译环境下,要先release掉block对象。

  • 4)在MRC的编译环境下,block如果作为成员参数要copy一下将栈上的block拷贝到堆上(因为block默认是在栈上创建的,如果在定义block的作用于外部使用block那么需要使用copy将block放到堆上)//MRC下:_sucBlock = [callbackBlock copy]; 不copy block会在栈上被回收。

  • 5)将block赋值为空,是解掉循环引用的重要方法。

  • 6)还有一种改法,在block接口设计时,将可能需要的变量作为形参传到block中,从设计上解决循环引用的问题。

  • 7)在多线程环境下(block中的weakSelf有可能被析构的情况下),需要先将self转为strong指针,避免在运行到某个关键步骤时self对象被析构。
    第四、第五条合起来有个名词叫weak–strong dance,来自于2011 WWDC Session #322 (Objective-C Advancements in Depth)

  • 以下代码来自AFNetworking,堪称使用weak–strong dance的经典。

	__weak __typeof(self)weakSelf = self;
	AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {  
    	__strong __typeof(weakSelf)strongSelf = weakSelf;  
	    strongSelf.networkReachabilityStatus = status;  
    	if (strongSelf.networkReachabilityStatusBlock) {  
        	strongSelf.networkReachabilityStatusBlock(status);  
	    }
	};
  • Review一下上面这段代码,里面玄机不少。

    • 第一行:__weak __typeof(self)weakSelf = self;
    • 如之前第四条所说,为防止callback内部对self强引用,weak一下。
    • 其中用到了__typeof(self),这里涉及几个知识点:
  • a. __typeof、typeof、typeof的区别

    • 恩~~他们没有区别,但是这牵扯一段往事,在早期C语言中没有typeof这个关键字,__typeof、__typeof__是在C语言的扩展关键字的时候出现的。
    • typeof是现代GNU C++的关键字,从Objective-C的根源说,他其实来自于C语言,所以AFNetworking使用了继承自C的关键字。
  • b.对于老的LLVM编译器上面这句话会编译报错,所以在很早的ARC使用者中流行__typeof(&*self)这种写法,

    • 原因如下大致说法是老LLVM编译器会将__typeof转义为 XXX类名 const __strong的__strong和前面的__weak关键字对指针的修饰又冲突了,所以加上&对指针的修饰。

    • 第三行:__strong __typeof(weakSelf)strongSelf = weakSelf;
      按照之前第五条的说法给转回strong了,这里__typeof()里面写的是weakSelf,里面写self也没有问题,因为typeof是编译时确定变量类型,所以这里写self 不会被循环引用。

    • 第四、五、六行,如果不转成strongSelf而使用weakSelf,后面几句话中,有可能在第四句执行之后self的对象可能被析构掉,然后后面的StausBlock没有执行,导致逻辑错误。

    • 最后第五行,使用前对block判空。

posted @ 2017-11-12 13:18  ShaoYL  阅读(177)  评论(0编辑  收藏  举报