Blocks、Block基础理解 \总结

本文详细介绍了Objective-C中的Blocks,包括其作为C语言扩展的功能、Block的语法、截获自动变量值、__block说明符的使用、Block的存储域以及如何避免循环引用等问题,深入解析了Blocks在Objective-C中的实现机制和应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、  Blocks是C语言的 扩展功能带有自动变量(局部变量)的匿名函数。

C语言中用到的变量:自动变量(局部变量)、静态变量、静态局部变量、全局变量、函数参数

函数多次调用之间能够传递值的变量有:静态变量(静态局部变量)、静态全局变量、全局变量。因为在整个程序中,一个变量总保持在一个内存区域,虽然多次调用但是该变量值总能保持不变,在任何时候以任何状态调用,使用的都是同样的变量值。

二、Blocks模式

1、Block语法:^ 返回值类型 (参数列表) {表达式}

Block类型变量

a  通过typedef声明blk_type类型变量

typedef int (^blk_type)(int);

blk_type blk_1;

b   一般声明Block类型变量:

int (^blk_1)(int);

将Block赋值为Block类型变量:

int (^blk_1)(int) = ^(int count){return count+1;};

int (^blk_2)(int);

blk_2=blk_1;

Block类型变量与C语言变量一样,可以作为:自动变量、静态变量、静态局部变量、全局变量、函数参数

(函数返回值、指向Blcok类型的变量即Block的指针类型变量)。

2、截获自动变量值

“带有自动变量值”在Blocks中表现为“截获自动变量值”:即保存该自动变量的瞬间值,保存后就不能改写该值

3、__block说明符

若想在Blocks语法表达式中将 值 赋给在Blocks语法外声明的 自动变量,需要在该自动变量上附加__block说明符。

4、截获的自动变量(非OC对象、OC对象、数组)

a 将值赋值给Block中截获的自动变量,会产生编译错误。

b 截获OC对象,对OC对象赋值会产生编译错误( obj=obj ),但是使用OC对象就不会产生编译错误( [obj addObject:**] )

c 在Blocks中截获自动变量的方法并没有实现对C语言数组的截获,可以使用指针解决该问题。

  const char text[]="hello";

void(^blk)(void)=^{printf("%c\n",text[2]);}

以上代码报错。修改为:

const char *text="hello";

void(^blk)(void)=^{printf("%c\n",text[2]);}

三、Blocks实现

1、Block的实质:一个Block是一个结构体实例,结构体中有isa指针,指向所属类,说明:Block是OC对象。

注:所属类也是一个结构体实例,结构体中有isa指针,指向其父类-----一级一级直到超类

“OC中由类生成对象”:意味着“生成由该类生成的对象的结构体实例”,生成的各个对象,即由该类生成的对象的各个结构体实例,结构体中成员变量isa指针保持该类的结构体实例指针。

2、截获自动变量值

“截获自动变量值”:在执行Block语法时,Block语法表达式所 使用自动变量值 被保存到Block的结构体实例(Block自身)中。

  问题:c 在Blocks中截获自动变量的方法并没有实现对C语言数组的截获

  因为C语言中不能数组赋值给数组,当自动变量为数组,保存到Block的结构体实例中出现数组赋值给数组,所以报错。

3、__block说明符

Block中 仅 “截获自动变量值”,重写该自动变量的值也不会改变原先截获的自动变量。

这样造成无法在Block中保存值,解决这个问题的两种方法:

a 使用静态变量、静态全局变量、全局变量,允许在Block中改值。

(注: 保存到Block结构体中的是指针,使用静态变量的指针进行对其访问)

b 使用__block说明符

  自动变量附上__block存储域说明符:__block变量同Block一样变成结构体类型的自动变量,即栈上的生成一个结构体,持有原自动变量的成员变量。并且结构体中有成员变量__forwarding持有指向自身的指针。

注意:Block变量中有成员变量指针:指向__block变量的结构体。Block变量使用多个__block变量,就有多个成员变量指向不同__block变量。

4、Block存储域

Block对象的类有三种:

                类                                    设置对象的存储域

_NSConcreteStackBlock                    栈

_NSConcreteGlobalBlock                   程序的数据区域(.data区)

_NSConcreteMallocBlock                   堆

a   结构体类型的自动变量,即栈上生成的该结构体的实例。

     前面Block转换为Block的结构体类型的自动变量,__block变量转换为__block变量的结构体类型的自动变量,都是在栈上的_NSConcreteStackBlock对象。

b   1、Block表达式中不使用应截获的自动变量时(不截获自动变量,可以将Block用结构体实例存储在数据区)

     2、在写全局变量的有Block时(在写全局变量的地方不能使用自动变量,不存在对自动变量截获,Block用结构体的实例内容不 依赖执行时状态,整个程序中只需一个实例)

     以上两种情况:Block为_NSConcreteGlobalBlock类对象。

c  将 _NSConcreteStackBlock对象 复制到堆上,就是_NSConcreteMallocBlock对象

-----------------Block超出变量作用域可存在的原因-----------------Blocks提供了将Block和__block变量从栈上复制到堆上的方法,这样即使Block变量的作用域结束,堆上的Block还可以存在,不受作用域影响。

-----------------__block变量用结构体成员变量__forwarding存在的原因-----------------__block变量的成员变量__forwarding可以实现,无论__block变量在栈上还是在堆上,都能正确访问__block变量。栈上__block变量的成员变量__forwarding指向堆上的结构体实例,堆上的__block变量的成员变量__forwarding指向自身结构体。

 

ARC有效时,以下情况,编译器提供自动生成将Block从栈上复制到堆上的代码

a、Block作为函数返回值时

b、Cocoa框架的方法且方法名含有usingBlock等时、GCD的API

编译器不能判断,需要手动copy情况:

a、Block作为函数参数时

---------------手动用copy复制效果(ARC有效时,多次调用也没有问题)--------------------

  类                                              设置对象的存储域                                       复制效果

_NSConcreteStackBlock                    栈                                                           从栈复制到堆

_NSConcreteGlobalBlock                   程序的数据区域(.data区)                   什么也不做

_NSConcreteMallocBlock                   堆                                                           引用计数增强

5、__block变量存储域

                                                ---------------Block从栈复制到堆时对__block变量产生对影响---------------

__block变量对配置存储域 Block从栈复制到堆时的影响
 栈 从栈复制到堆并被Block持有
堆  被Block持有

  多个Block中使用__block变量,在任何一个Block从栈复制到堆时,__block变量也会一并从栈复制到堆并被Block持有,剩下的Block从栈复制到堆时,被复制的__block变量引用计数增加。在堆上的Block被废弃,它所持有的__block变量也就被释放。

__block变量用结构体成员变量__forwarding存在的原因。通过Block的复制,__block变量也会一并从栈复制到堆,可以同时访问栈上和堆上的__block变量,栈上__block变量的成员变量__forwarding指向堆上的结构体实例,堆上的__block变量的成员变量__forwarding指向自身结构体。

val.__forwarding->val;

无论栈上__block变量val还是堆上__block变量val,此时访问的都是同一个__block变量(堆上的)。

6、截获对象

Block中截获的对象(有_strong修饰),是在Block用的结构体中附有_strong修饰符的成员变量,堆上Block持有该截获的对象,【和__block变量一样,由于被堆上Block持有】,因而超出其变量作用域可存在。

只有调用copy函数(自动或手动)才能持有 截获的【有_strong修饰的对象类型】、【__block变量】。

---------------调用copy函数和dispose函数的时机--------------

            copy函数                                栈上的Block复制到堆时

          dispose函数                              堆上的Block被废弃时

Block中使用对象类型自动变量时,什么时候栈上的Block会被复制到堆?

ARC有效时,以下情况,编译器提供自动生成将Block从栈上复制到堆上的代码

a、Block作为函数返回值时

b、Cocoa框架的方法且方法名含有usingBlock等时、GCD的API中传递Block时

c、调用Block的copy实例方法时

d、将Block赋值给附有_strong修饰符id类型的类或者Block类型成员变量时

       id_obj  =  ^(){} ;

        Block类型成员变量  =    ^(){} ;    

7、__block变量和对象

__block说明符可以指定任何类型的自动变量。

ARC有效时,id类型以及对象类型变量必定附加所有权修饰符,缺省为_strong修饰符。

__block id obj=[[NSObject alloc] init];      ------等同于------      __block id __strong obj = [[NSObject alloc] init];//超出作用域可存在

 id __weak obj=obj1;      ---------等同于----------      __block id __weak obj =obj1;//超出作用域无效

8、Block循环引用

如果Block中使用附有__strong修饰符的对象类型自动变量,那么当Block从栈复制到堆时,该对象为Block 持有,容易引起循环引用

typedef void (^blk_t)(void);
@interface MyObject:NSObject
{
   blk_t blk;
}
@end

@implementation MyObject
-(id)init{
  self = [super init];
  blk = ^{NSLog(@"self = %@",self);}//将Block赋值给Block类型的成员变量,自动copy到堆
  return self;
}


-(void)dealloc{
  NSLog(@"dealloc");
}

@end

int main()
{
  id o=[[MyObject alloc] init];
  NSLog(@"%@",o);
  return 0;
}

     

使用Block成员变量循环引用 

a  声明附有__weak修饰符的变量并赋值self,可以避免循环引用。也可以使用__unsafe_unretained修饰符。     

//@interface 声明中MyObject中有属性id obj;
-(id)init{
  self=[super init];
  id __weak tmp=self;//声明附有--weak修饰符的变量,并将self赋值使用
  blk=^{NSLog(@"self = %@",tmp);};
  id __weak obj0=obj;
  blk=^{NSLog(@"self = %@",obj0);};//此时如果写obj相当于self->obj,依然会引起循环引用
  return self;
}

 

 使用Block成员变量避免循环引用

b 使用__block变量避免循环引用

typedef void (^blk_t)(void);
@interface MyObject:NSObject
{
   blk_t blk;
}
@end

@implementation MyObject
-(id)init{
  self = [super init];
  __block id tmp = self;//使用__block变量
  blk = ^{  NSLog(@"self = %@",tem);
            tmp=ni;  //变量赋值nil
         }//将Block赋值给Block类型的成员变量,自动copy到堆
  return self;
}

-(void)execBlock
{
  blk();
}

-(void)dealloc{
  NSLog(@"dealloc");
}

@end

int main()
{
  id o=[[MyObject alloc] init];
  [o execBlock];//执行Block
  return 0;
}

如果没有调用execBlock实例方法,即不执行赋值给成员变量blk的Block,会引起循环引用并引起内存泄漏。

                              

通过执行execBlock实例方法,Block被执行,__block变量tmp被赋值nil,__block变量tmp对MyObject类对象的强引用失效。

使用__block变量 和 使用__weak 及__unsafe_unretained 避免循环引对比:

使用__block变量优点:

1、通过__block变量可控制对象的持有期间

2、在不能使用__weak的环境中不使用__unsafe_unretained即可。

在执行Block时可动态决定是否将nil或其他对象赋值在__block变量中。

使用__block变量缺点:

为避免循环引用必须执行Block

9、copy/release

ARC无效时,需要手动将Block从栈复制到堆。用copy实例方法用来复制,用release实例方法来释放。

只要Block有一次复制到堆上,就可以用retain方法持有。在栈上Block调用retain不起任何作用。

ARC无效时,__block说明符被用来避免Block中的循环引用。

这是由于当Block从栈复制到堆时,若Block使用的变量为附有__block的id类型或对象类型的自动变量,不会被retain,,若Block使用的变量为没有__block的id类型或对象类型的自动变量,则被retain,引起循环引用。

 由于ARC有效和无效时,__block说明符用途有很大区别,需要注意!

 

 

 

 

 

 

 

 

 

                                                           

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值