block b成员里为什么不能操作成员变量

1、在使用block前需要对block指针做判空处理。
不判空直接使用,一旦指针为空直接产生崩溃。
if (!self.isOnlyNet) {
if (succBlock == NULL) { //后面使用block之前要先做判空处理
id data = [NSKeyedUnarchiver unarchiveObjectWithFile:[self favoriteFile]];
if ([data isKindOfClass:[NSMutableArray class]]) {
succBlock(data,YES);
succBlock(nil,YES);
2、在MRC的编译环境下,block如果作为成员参数要copy一下将栈上的block拷贝到堆上(示例见下,)
3、在block使用之后要对,block指针做赋空值处理,如果是MRC的编译环境下,要先release掉block对象。
block作为类对象的成员变量,使用block的人有可能用类对象参与block中的运算而产生循环引用。
将block赋值为空,是解掉循环引用的重要方法。(不能只在dealloc里面做赋空值操作,这样已经产生的循环引用不会被破坏掉)
typedef void(^SuccBlock)(id data);
@interface NetworkClass {
SuccessBlock _sucB
@property (nonatomic,assign)BOOL propertyUseInCallB
- (void) requestWithSucBlock: (SuccessBlock) callbackB
@implementation NetworkClass
- (void) requestWithSucBlock: (SuccessBlock) callbackBlock {
_sucBlock = callbackB//MRC下:_sucBlock = [callbackBlock copy]; 不copy block会在栈上被回收。
- (void) netwrokDataBack: (id) data {
if (data != nil && _sucBlock != NULL) {
_sucBlock(data);
//MRC下:要先将[_sucBlock release];(之前copy过)
_sucBlock = //Importent: 在使用之后将Block赋空值,解引用 !!!
//=======================以下是使用方===========================
@implementation UserCode
- (void) temporaryNetworkCall
NetworkClass *netObj = [[NetworkClass alloc] init];
netObj.propertyUseInCallBack = NO;
[netObj requestWithSucBlock: ^(id data) {
//由于block里面引用netObj的指针所以这里产生了循环引用,且由于这个block是作为参数传入对象的,编译器不会报错。
//因此,NetworkClass使用完block之后一定要将作为成员变量的block赋空值。
if (netObj.propertyUseInCallBack == YES) {
//Do Something...
还有一种改法,在block接口设计时,将可能需要的变量作为形参传到block中,从设计上解决循环引用的问题。
如果上面Network类设计成这个样子:
@class NetowrkC
typedef void(^SuccBlock)(NetworkClass *aNetworkObj, id data);
@interface NetworkClass
@implementation NetworkClass
@implementation UserCode
- (void) temporaryNetworkCall
NetworkClass *netObj = [[NetworkClass alloc] init];
netObj.propertyUseInCallBack = NO;
[netObj requestWithSucBlock: ^(NetworkClass *aNetworkObj, id data) {
//这里参数中已经有netObj的对象了,使用者不用再从block外引用指针了。
if (aNetworkObj.propertyUseInCallBack == YES) {
//Do Something...
4、使用方将self或成员变量加入block之前要先将self变为__weak
5、在多线程环境下(block中的weakSelf有可能被析构的情况下),需要先将self转为strong指针,避免在运行到某个关键步骤时self对象被析构。
第四、第五条合起来有个名词叫weak&strong dance,来自于
以下代码来自AFNetworking,堪称使用weak&strong dance的经典。
__weak __typeof(self)weakSelf =
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakS
strongSelf.networkReachabilityStatus =
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
第一行:__weak __typeof(self)weakSelf =
如之前第四条所说,为防止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 = weakS
按照之前第五条的说法给转回strong了,这里__typeof()里面写的是weakSelf,里面写self也没有问题,因为typeof是编译时确定变量类型,所以这里写self 不会被循环引用。
第四、五、六行,如果不转成strongSelf而使用weakSelf,后面几句话中,有可能在第四句执行之后self的对象可能被析构掉,然后后面的StausBlock没有执行,导致逻辑错误。
最后第五行,使用前对block判空。
写在最后,阅读好的开源库源码是提高个人水平的一个很好途径,看见不懂的地方去查去摸索会得到更多。
&这是个工具网站,我每次写block的时候都会用到。
阅读(...) 评论()如何在block里面正确的调用变量以及方法!!!!!
[问题点数:200分,结帖人rollrock1987]
如何在block里面正确的调用变量以及方法!!!!!
[问题点数:200分,结帖人rollrock1987]
不显示删除回复
显示所有回复
显示星级回复
显示得分回复
只显示楼主
2012年10月 其他开发语言大版内专家分月排行榜第二2011年7月 其他开发语言大版内专家分月排行榜第二2010年3月 其他开发语言大版内专家分月排行榜第二2007年10月 其他开发语言大版内专家分月排行榜第二2007年9月 其他开发语言大版内专家分月排行榜第二2005年3月 Windows专区大版内专家分月排行榜第二2005年2月 Windows专区大版内专家分月排行榜第二2005年6月 扩充话题大版内专家分月排行榜第二2006年9月 其他开发语言大版内专家分月排行榜第二2006年5月 其他开发语言大版内专家分月排行榜第二2006年3月 其他开发语言大版内专家分月排行榜第二2006年2月 其他开发语言大版内专家分月排行榜第二2005年12月 其他开发语言大版内专家分月排行榜第二2005年4月 其他开发语言大版内专家分月排行榜第二2004年11月 其他开发语言大版内专家分月排行榜第二2005年3月 硬件使用大版内专家分月排行榜第二
2011年11月 其他开发语言大版内专家分月排行榜第三2011年8月 其他开发语言大版内专家分月排行榜第三2008年10月 其他开发语言大版内专家分月排行榜第三2004年9月 硬件/嵌入开发大版内专家分月排行榜第三
匿名用户不能发表回复!|
每天回帖即可获得10分可用分!小技巧:
你还可以输入10000个字符
(Ctrl+Enter)
请遵守CSDN,不得违反国家法律法规。
转载文章请注明出自“CSDN(www.csdn.net)”。如是商业用途请联系原作者。浅谈 block(2) - 截获变量方式 - 推酷
浅谈 block(2) - 截获变量方式
本文会通过 clang 的 -rewrite-objc 选项来分析 block 的 C 转换源代码。其分析方式在该系列上一篇有详细介绍。请先阅读
截获自动变量
首先需要做代码准备工作,我们编写一段 block 引用外部变量的 c 代码。
编译运行成功后,使用 -rewrite-objc 进行改写。
clang -rewrite-objc block.c
简化代码后,得到以下主要代码:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
char *str;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, char *_str, int flags=0) : str(_str) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
char *str = __cself-&str; // bound by copy
printf(&%s\n&, str);
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = {
sizeof(struct __main_block_impl_0)
int main() {
char *str = &Desgard_Duan&;
void (*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, str));
((void (*)(__block_impl *))((__block_impl *)block)-&FuncPtr)((__block_impl *)block);
与上一篇转换的源码不同的是,block 语法表达中的变量作为成员添加到了 __main_block_func_0 结构体中。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
char *str; // 外部引用变量
并且,在该结构体中的应用变量类型与外部的类型完全相同。在初始化该结构体实例的构造函数也自然会有所差异:
void (*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, str));
去掉强转语法简化代码:
void (*block)() = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, str);
在构造时,除了要传递自身(self) __main_block_func_0 结构体,而且还要传递 block 的基本信息,即 reserved 和 size 。这里传递了一个全局结构体对象 __main_block_desc_0_DATA ,因为他是为 block 量身设计的。最后在将引用值参数传入构造函数中,以便于构造带外部引用参数的 block。
进入构造函数后,发现了含有冒号表达的构造语法:
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, char *_str, int flags=0) : str(_str) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
其实,冒号表达式是 C++ 中的一个固有语法。这是显示构造的方法之一。另外还有一种构造显示构造方式,其语法较为繁琐,即使用 this 指针构造。(关于 C++ 构造函数,可以学习 msdn 文档
之后的代码与前一篇分析相同,不再讨论。
通过整个构造 block 流程分析,我们发现当 block 引用外部对象时,会在结构体内部新建立一个成员进行存储。此处我们使用的是 char* 类型,而在结构体中所使用的 char* 是结构体的成员,所以可以得知: block 引用外部对象时候,不是简单的指针引用(浅复制),而是一种重建(深复制)方式 (括号内外分别对于基本数据类型和对象分别描述) ) 。所以如果在 block 中对外部对象进行修改,无论是值修改还是指针修改,自然是没有任何效果。
引入 __block 关键字对截取变量一探究竟
上文中的 block 所引用的外部成员是一个字符型指针,当我们在 block 内部对其修改后,很容易的想到,会改变该指针的指向。而当 block 中引用外部变量为常用数据类型会有些许的不同:
我们来看这个例子 (这是来自 Pro multithreading and memory management for iOS and OS X 2.3.3 一节的例子):
int val = 0;
void (^blk)(void) = ^{val = 1};
执行代码后会报 error :
error: variable is not assignable (missing __block type specifier)
void (^blk)(void) = ^{val = 1};
上述书中对此情况是这样解释的:
block 中所使用的被截获自动变量如同“带有自动变量值的匿名函数”,仅截获自动变量的值。 block 中使用自动变量后,在 block 的结构体实力中重写该自动变量也不会改变原先截获的自动变量。
这应该是 clang 对 block 的引用外界局部值做的保护措施,也是为了维护 C 语言中的作用域特性。既然谈到了作用域,那么是否可以使用显示声明存储域类型从而在 block 中修改该变量呢?答案是可以的。当 block 中截取的变量为静态变量(static),使用下例进行试验:
int main() {
static int static_val = 2;
void (^blk)(void) = ^{
static_val = 3;
装换后的代码:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
int main() {
static int static_val = 2;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_
会发现在构造函数中使用的静态指针 int *_static_val 对其进行访问。将静态变量 static_val 的指针传递给 __main_block_impl_0 结构体的构造函数并加以保存。通过指针进行作用域拓展,是 C 中很常见的思想及做法,也是超出作用域使用变量的最简单方法。
那么我们为什么在引用自动变量的时候,不使用该自动变量的指针呢?是应为在 block 截获变量后,原来的自动变量已经废弃,因此block 中超过变量作用域从而无法通过指针访问原来的自动变量。
为了解决这个问题,其实在 block 扩展中已经提供了方法(
)。即使用 __block 关键字。
__block 关键字更准确的表达应为 __block说明符(__block storage-class-specifier) ,用来描述存储域。在 C 语言中已经存有如下存储域声明关键字:
typedef:常用在为数据类型起别名,而不是一般认识的存储域声明关键字作用。但在归类上属于存储域声明关键字。
extern:限制标示,限制定义变量在所有模块中作为全局变量,并只能被定义一次。
static:静态变量存储在 .data 区。
auto:自动变量存储在栈中。
register:约束变量为单值,存储在CPU寄存器内。
__block 关键字类似于 static 、 auto 、 register ,用于将变量存于指定存储域。来分析一下在变量声明前增加 __block 关键字后 clang 对于 block 的转换动作。
__block int val = 1;
void (^blk)(void) = ^ {
// 要点 1:__block 变量转换结构
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val-&__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself-&val; // bound by ref
// 要点 2:__forwarding 自环指针存在意义
(val-&__forwarding-&val) = 2;
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
// 要点 3:copy/dispose 方法内部实现
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = {
sizeof(struct __main_block_impl_0),
__main_block_copy_0,
__main_block_dispose_0
int main() {
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {
(__Block_byref_val_0 *)&val,
sizeof(__Block_byref_val_0), 1
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, ));
发现核心代码部分有所增加,我们先从入口函数看起。
__Block_byref_val_0 val = {
(__Block_byref_val_0 *)&val,
sizeof(__Block_byref_val_0),
原先的 val 变成了 __Block_byre_val_0 结构体类型变量。并且这个结构体的定义是之前未曾见过的。并且我们将 val 初始化的数值 1,也出现在这个构造中,说明该结构体持有原成员变量。
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
在 __block 变量的结构体中,除了有指向类对象的 isa 指针,对象负载信息 flags ,大小 size ,以及持有的原变量 val ,还有一个自身类型的 __forwarding 指针。从构造函数中,会发现一个有趣的现象,
__forwarding 指针会指向自身,形成自环
。后面会详细介绍它。
而在 block 体执行段,是这样定义的。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself-&val; // bound by ref
(val-&__forwarding-&val) = 2;
第一步中获得 val 的方法和 block 中引用外部变量的方式是一致的,通过 self 来获取变量。而对于外部 __block 变量赋值的时候,这种写法引起了我们的注意: (val-&__forwarding-&val) = 2; ,这样做的目的何在,在后文会做出分析。
__block 变量结构
当 block 内部引用外部的 __block 变量,会使用以上结构对 __block 做出转换。另外,该结构体并不声明在 __main_block_impl_0 block 结构体中,是因为这样可以对多个 block 引用 __block 情况下,达到复用效果,从而节省不必要的空间开销。
__block int val = 0;
void (^blk1)(void) = ^{val = 1;};
void (^blk2)(void) = ^{val = 2;};
只观察入口方法:
__Block_byref_val_0 = {0, &val, 0, sizeof(__Block_byref_val_0), 10};
blk1 = &__main_block_impl_0(__main_block_func_0
, &__main_block_desc_0_DATA
blk2 = &__main_block_impl_0(__main_block_func_1
, &__main_block_desc_1_DATA
发现 val 指针被复用,使得两个 block 同时使用一个 __block 只需要对其结构声明一次即可。
接触 Objective-C 语言环境下的 block
通过两篇文的 block 的结构转换,我们发现其实 block 的实质是一个 对象 (Object) ,从封装成结构体对象,再到 isa 指针结构,都是明显的体现。对于 __block 也是如此,在转换后将其封装成了 __block 结构体类型,以对象方式处理。
带着 C 代码中的 block 扩展转换规则开始进入 Objective-C block 的学习。首先需要知道 block 的三个类型。
对象存储域
_NSConcreteStackBlock
_NSConcreteMallocBlock
_NSConcreteGloalBlock
静态区(.data)
在上一篇文中的末尾部分,简单的说了一下全局静态的存储问题。这里再一次强调, _NSConcreteGloalBlock 的 block 会在一下两种情况下出现(与 clang 转换结果不大相同):
全局变量位置
block 中不引用外部变量
而在其他情况下,基本上 block 的类型都为 _NSConcreteStackBlock 。但是在栈上的 block 会受到作用域的限制,一旦所属的变量作用域结束,该 block 就会被释放。由此,引出了 _NSConcreteMallocBlock 堆 block 类型。
block 提供了将 block 和 __block 变量从栈上复制到堆上的方法来解决这个问题。将配置在站上的 block 复制到堆上,这样可以保证在 block 变量作用域结束后,堆上仍旧可访问。
__block 变量通过 __forwarding 可以无论在堆上还是栈上都能正常访问。当 block 存储在堆上的时候,对应的栈上 block 的 __forwarding 成员会断开自环,而指向堆上的 block 对象。这也就是 __forwarding 指针存在的真实用意。
在复制到堆的过程中,__forwarding 指针是如何更改指向的?这个问题在下一篇中进行介绍。这篇文主要讲述了 __block 变量在 block 中的结构,以及如何获取外部变量,并可以对其修改的详细过程,希望有所收获。
已发表评论数()
请填写推刊名
描述不能大于100个字符!
权限设置: 公开
仅自己可见
正文不准确
标题不准确
排版有问题
主题不准确
没有分页内容
图片无法显示
视频无法显示
与原文不一致ios中为什么block用copy属性_百度知道可能碰到的iOS笔试面试题(8)--Block - 简书
下载简书移动应用
写了191679字,被136人关注,获得了111个喜欢
可能碰到的iOS笔试面试题(8)--Block
Block底层原理实现
首先我们来看四个函数
void test1()
int a = 10;
void (^block)() = ^{
NSLog(@"a is %d", a);
block(); // 10
void test2()
__block int a = 10;
void (^block)() = ^{
NSLog(@"a is %d", a);
block(); // 20
void test3()
static int a = 10;
void (^block)() = ^{
NSLog(@"a is %d", a);
block(); // 20
int a = 10;
void test4()
void (^block)() = ^{
NSLog(@"a is %d", a);
block();//20
造成这样的原因是:传值和传址。为什么说会有传值和传址,把.m编译成c++代码。得到.cpp文件,我们来到文件的最后,看到如下代码
struct __test1_block_impl_0 {
struct __block_
struct __test1_block_desc_0* D
__test1_block_impl_0(void *fp,struct __test1_block_desc_0* Desc,int _a,int flag=0): a(_a){
impl.isa = &_NSConcreteStackB
impl.Flags =
impl.FuncPtr =
static void __test1_block_func_0(struct __test1_block_imp_0 *__cself)
int a = __cself-&a;
NSLog(a);//这里就是打印a的值,代码太长,而且没意义,我就不敲出来了。
void test1()
int a = 10;
void (*block)() = (void (*)())&__test1_block_impl_0((void *))__test1_block_func_0,&__test1_block_desc_0_DATA,a);
((void (*)(__block_impl *))((__block_ipml *)block)-&FuncPtr)((_block_impl *)block);
int main(int argc, const char * argv[])
/* @autoreleasepool */ { __AtAutoreleasePool __
static struct IMAGE_INFO { } _OBJC_IMAGE_INFO = { 0, 2 };
我们看到void test1()中,void (*block)() 右边最后面 ,把a传进去了,也就是把10这个值传进去了.
而且对void (block)()简化分析,void (block)() = &__test1_block_impl_0();所以block就是指向结构体的指针。
10传入block后,代码最上面创建的__test1_block_impl_0结构体中,a = 10;
对void test1()中最下面的函数进行简化分析,得到(block)-&FuncPtr)(block),我们在回到刚才test1_block_impl_0这个结构体中,impl.FuncPtr =而fp又是传入结构体的第一个参数,而在void (*block)()中,传入结构体的第一个参数为test1_block_func_0,也就是说(block)-&FuncPtr)(block) =》__test1_block_func_0(block);
上一步相当于调用test1_block_func_0()这个函数,我们来看这个函数,有这样一段代码:int a = cself-&a;访问block中的a值,传递给a;所以是10.这种就是传值!!!
我们再来看test2( );添加了__block会发送什么变化呢
void test2()
__attribute__((_blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a,0,sizeof(__Block_byref_a_0),10};
void(*block)() =
(void (*)())&__test2_block_impl_0((void *))__test2_block_func_0,&__test2_block_desc_0_DATA,(__Block_byref_a_0 *)&a,);
(a.__forwarding-&a) = 20;
((void (*)(__block_impl *))((__block_ipml *)block)-&FuncPtr)((_block_impl *)block);
int main(int argc, const char * argv[])
/* @autoreleasepool */ { __AtAutoreleasePool __
static struct IMAGE_INFO { } _OBJC_IMAGE_INFO = { 0, 2 };
代码虽然很多看着很复杂,但是我们只需要看我们想要知道的,睁大你的眼睛,看到void(*block)()这个函数的最后面,有个&a,天啊,这里传的是a的地址。从test2到test4,都是传址,所以a的值发生改变,block打印出来的是a的最终值。
总结:只有普通局部变量是传值,其他情况都是传址。
block的定义
// 无参无返回
void(^block)();
// 无参有返回
int(^block1)();
// 有参有返回
int(^block1)(int number);
也可以直接打入inline来自动生成block格式
&#returnType#&(^&#blockName#&)(&#parameterTypes#&) = ^(&#parameters#&) {
&#statements#&
block的内存管理
无论当前环境是ARC还是MRC,只要block没有访问外部变量,block始终在全局区
block如果访问外部变量,block在栈里
不能对block使用retain,否则不能保存在堆里
只有使用copy,才能放到堆里
block如果访问外部变量,block在堆里
block可以使用copy和strong,并且block是一个对象
block的循环引用
如果要在block中直接使用外部强指针会发生错误,使用以下代码在block外部实现可以解决
__weak typeof(self) weakSelf =
但是如果在block内部使用延时操作还使用弱指针的话会取不到该弱指针,需要在block内部再将弱指针强引用一下
__strong typeof(self) strongSelf = weakS
描述一个你遇到过的retain cycle例子。
block中的循环引用:一个viewController
@property (nonatomic,strong)HttpRequestHandler *
@property (nonatomic,strong)NSData
_handler = [httpRequestHandler sharedManager];
[ downloadData:^(id responseData){
_data = responseD
self 拥有_handler, _handler 拥有block, block拥有self(因为使用了self的_data属性,block会copy 一份self)
解决方法:
__weak typedof(self)weakSelf = self
[ downloadData:^(id responseData){
weakSelf.data = responseD
block中的weak self,是任何时候都需要加的么?
不是什么任何时候都需要添加的,不过任何时候都添加似乎总是好的。只要出现像self-&block-&self.property/self-&_ivar这样的结构链时,才会出现循环引用问题。好好分析一下,就可以推断出是否会有循环引用问题。
通过block来传值
在控制器间传值可以使用代理或者block,使用block相对来说简洁
在前一个控制器的touchesBegan:方法内实现如下代码
ModalViewController *modalVc = [[ModalViewController alloc] init];
modalVc.valueBlcok = ^(NSString *str){
NSLog(@"ViewController拿到%@",str);
[self presentViewController:modalVc animated:YES completion:nil];
在ModalViewController控制器的.h文件中声明一个block属性 @property (nonatomic ,strong) void(^valueBlcok)(NSString *str);
并在.m文件中实现方法
-(void)touchesBegan:(NSSet&UITouch *& *)touches withEvent:(UIEvent *)event
// 传值:调用block
if (_valueBlcok) {
_valueBlcok(@"123");
这样在ModalViewController回到上一个控制器的时候,上一个控制器的label就能显示ModalViewController传过来的字符串
block作为一个参数使用
新建一个类,在.h文件中声明一个方法- (void)calculator:(int(^)(int result))
并在.m文件中实现该方法
-(void)calculator:(int (^)(int))block
self.result = block(self.result);
在其他类中调用该方法
CalculatorManager *mgr = [[CalculatorManager alloc] init];
[mgr calculator:^(int result){
result += 5;
block作为返回值使用
在masonry框架中我们可以看到如下用法make.top.equalTo(superview.mas_top).with.offset(padding.top); 这个方法实现就是将block作为返回值来使用
来分析一下这段代码:其实可以将这段代码看成make.top,make.equalTo,make.with,make.offset,所以可以得出一个结论是make.top返回了一个make,才能实现make.top.equalTo
那来模仿一下这种功能的实现
新建一个类,在.h文件中声明一个方法- (CalculatorManager *(^)(int a))
在.m文件中实现方法
-(CalculatorManager * (^)(int a))add
return ^(int a){
_result +=
这样就可以在别的类中实现上面代码的用法
mgr.add(1).add(2).add(3);
block的变量传递
如果block访问的外部变量是局部变量,那么就是值传递,外界改了,不会影响里面
如果block访问的外部变量是__block或者static修饰,或者是全局变量,那么就是指针传递,block里面的值和外界同一个变量,外界改变,里面也会改变
验证一下是不是这样
通过Clang来将main.m文件编译为C++
在终端输入如下命令clang -rewrite-objc main.m
void(*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,
&__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, ));
void(*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,
&__main_block_desc_0_DATA, a));
可以看到在编译后的代码最后可以发现被__block修饰过得变量使用的是&a,而局部变量是a
block的注意点
在block内部使用外部指针且会造成循环引用情况下,需要用__weak修饰外部指针
__weak typeof(self) weakSelf =
在block内部如果调用了延时函数还使用弱指针会取不到该指针,因为已经被销毁了,需要在block内部再将弱指针重新强引用一下
__strong typeof(self) strongSelf = weakS
如果需要在block内部改变外部变量的话,需要在用__block修饰外部变量
使用block有什么好处?使用NSTimer写出一个使用block显示(在UILabel上)秒表的代码。
说到block的好处,最直接的就是代码紧凑,传值、回调都很方便,省去了写代理的很多代码。
对于这里根本没有必要使用block来刷新UILabel显示,因为都是直接赋值。当然,笔者觉得这是在考验应聘者如何将NSTimer写成一个通用用的Block版本。
NSTimer封装成Block版: /nstimer-block/
使用起来像这样:
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0
repeats:YES
callback:^() {
weakSelf.secondsLabel.text = ...
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
block跟函数很像:
可以保存代码
调用方式一样
使用系统的某些block api(如UIView的block版本写动画时),是否也考虑引用循环问题?
系统的某些block api中,UIView的block版本写动画时不需要考虑,但也有一些api需要考虑。所谓“引用循环”是指双向的强引用,所以那些“单向的强引用”(block 强引用 self )没有问题,比如这些:
[UIView animateWithDuration:duration animations:^{ [self.superview layoutIfNeeded]; }];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.someProperty = }];
[[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification"
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification * notification) {
self.someProperty =
这些情况不需要考虑“引用循环”。但如果你使用一些参数中可能含有成员变量的系统api,如GCD、NSNotificationCenter就要小心一点。比如GCD内部如果引用了 self,而且GCD的其他参数是成员变量,则要考虑到循环引用:
__weak __typeof(self) weakSelf =
dispatch_group_async(_operationsGroup, _operationsQueue, ^{
__typeof__(self) strongSelf = weakS
[strongSelf doSomething];
[strongSelf doSomethingElse];
__weak __typeof(self) weakSelf =
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"
object:nil
usingBlock:^(NSNotification *note) {
__typeof__(self) strongSelf = weakS
[strongSelf dismissModalViewControllerAnimated:YES];
self –& _observer –& block –& self 显然这也是一个循环引用。
谈谈对Block 的理解?并写出一个使用Block执行UIVew动画?
Block是可以获取其他函数局部变量的匿名函数,其不但方便开发,并且可以大幅提高应用的执行效率(多核心CPU可直接处理Block指令)
[UIView transitionWithView:self.view duration:0.2
ptions:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{ [[blueViewController view] removeFromSuperview]; [[self view] insertSubview:yellowViewController.view atIndex:0]; }
completion:NULL];
写出上面代码的Block的定义。
typedef void(^animations) (void);
typedef void(^completion) (BOOL finished);
什么是block
对于闭包(block),有很多定义,其中闭包就是获取其它函数局部变量的匿名函数,这个定义即接近本质又较好理解。
对于刚接触Block的同学,会觉得有些绕,因为我们习惯写这样的程序main(){ funA();} funA(){funB();} funB(){…..}; 就是函数main调用函数A,函数A调用函数B… 函数们依次顺序执行,但现实中不全是这样的,例如项目经理M,手下有3个程序员A、B、C,当他给程序员A安排实现功能F1时,他并不等着A完成之后,再去安排B去实现F2,而是安排给A功能F1,B功能F2,C功能F3,然后可能去写技术文档,而当A遇到问题时,他会来找项目经理M,当B做完时,会通知M,这就是一个异步执行的例子。
在这种情形下,Block便可大显身手,因为在项目经理M,给A安排工作时,同时会告诉A若果遇到困难,如何能找到他报告问题(例如打他手机号),这就是项目经理M给A的一个回调接口,要回掉的操作,比如接到电话,百度查询后,返回网页内容给A,这就是一个Block,在M交待工作时,已经定义好,并且取得了F1的任务号(局部变量),却是在当A遇到问题时,才调用执行,跨函数在项目经理M查询百度,获得结果后回调该block。
block 实现原理
Objective-C是对C语言的扩展,block的实现是基于指针和函数指针。
从计算语言的发展,最早的goto,高级语言的指针,到面向对象语言的block,从机器的思维,一步步接近人的思维,以方便开发人员更为高效、直接的描述出现实的逻辑(需求)。
使用实例:cocoaTouch框架下动画效果的Block的调用
使用typed声明block
typedef void(^didFinishBlock) (NSObject *ob);
这就声明了一个didFinishBlock类型的block,
然后便可用
@property (nonatomic,copy) didFinishBlock finishB
声明一个blokc对象,注意对象属性设置为copy,接到block 参数时,便会自动复制一份。
__block是一种特殊类型,
使用该关键字声明的局部变量,可以被block所改变,并且其在原函数中的值会被改变。
答: 面试时,面试官会先问一些,是否了解block,是否使用过block,这些问题相当于开场白,往往是下面一系列问题的开始,所以一定要如实根据自己的情况回答。
1). 使用block和使用delegate完成委托模式有什么优点?
首先要了解什么是委托模式,委托模式在iOS中大量应用,其在设计模式中是适配器模式中的对象适配器,Objective-C中使用id类型指向一切对象,使委托模式更为简洁。了解委托模式的细节:
iOS设计模式—-委托模式
使用block实现委托模式,其优点是回调的block代码块定义在委托对象函数内部,使代码更为紧凑;
适配对象不再需要实现具体某个protocol,代码更为简洁。
2). 多线程与block
GCD与Block
使用 dispatch_async 系列方法,可以以指定的方式执行block
GCD编程实例
dispatch_async的完整定义
void dispatch_async(
dispatch_queue_t queue,
dispatch_block_t block);
功能:在指定的队列里提交一个异步执行的block,不阻塞当前线程
通过queue来控制block执行的线程。主线程执行前文定义的 finishBlock对象
dispatch_async(dispatch_get_main_queue(),^(void){finishBlock();});
解释以下代码的内存泄漏原因
@implementation HJTestViewController
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
HJTestCell *cell = [tableView dequeueReusableCellWithIdentifier:@"TestCell" forIndexPath:indexPath];
[cell setTouchBlock:^(HJTestCell *cell) {
[self refreshData];
[cell setTouchBlock:^(HJTestCell *cell) {
[self refreshData];
产生内存泄露的原因是因为循环引用
在给cell设置的TouchBlock中,使用了__strong修饰的self,由于Block的原理,当touchBlock从栈复制到堆中时,self会一同复制到堆中,retain一次,被touchBlock持有,而touchBlock又是被cell持有的,cell又被tableView持有,tableView又被self持有,因此形成了循环引用:self间接持有touchBlock,touchBlock持有self
一旦产生了循环引用,由于两个object都被强引用,所以retainCount始终不能为0,无发释放,产生内存泄漏
解决办法:使用weakSelf解除touchBlock对self的强引用
__weak __typeof__(self) weakSelf =
[cell setTouchBlock:^(HJTestCell *cell) {
[weakSelf refreshData];
文章如有问题,请留言,我将及时更正。
满地打滚卖萌求赞,如果本文帮助到你,轻点下方的红心,给作者君增加更新的动力。
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮
被以下专题收入,发现更多相似内容:
· 19人关注
选取了OC高级特性的一些思考:包括Runtime、Runloop、MRC、ARC、内存管理、多线程、Block、Delegate、Pr...
· 17人关注
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
选择支付方式:

我要回帖

更多关于 block 修改成员变量 的文章

 

随机推荐