Block是C语言的扩充功能,带有自动变量(局部变量)的匿名函数。另外“带有自动变量值的匿名函数”这一概念也并不仅指blocks,它还存在于其它许多程序语言中。
其他语言中Block的名称:
其他语言中Block的名称2、闭包闭包 = 一个函数(或指向函数的指针) + 该函数执行的上下文变量(也就是自由变量); Block是Object-C对于闭包的实现。
其中,Block:
可以嵌套定义,定义Block方法和定义函数方法相似;Block可以定义在方法内部或外部; 只有调用Block的时候,才会执行其{}体内的代码。 本质是对象,使代码高聚合;3、代码转换使用clang将OC代码转换为C++文件查看Block的方法:
在命令行输入cd 并把要转换的文件拖到命令行中,然后回车,进入转换文件的目录中; 在命令行输入代码:clang -rewrite-objc main.m -o main.cpp,此处命令是将main.m文件,转换成C++的main.cpp文件;二、Block定义与使用1、无参数,无返回值// 无参数,无返回值,声明和定义void (^MyBlock)(void) = ^(void) { NSLog(@"Block:无参数,无返回值");};// block的调用MyBlock();2、无参数,有返回值// 无参数,有返回值,声明和定义int (^MyBlock)(void) = ^astm认证{ NSLog(@"Block: 无参数,有返回值(9)"); return 9;};MyBlock();3、有参数,无返回值// 有参数,无返回值,声明和定义void (^MyBlock)(int a) = ^(int a) { NSLog(@"Block: 有参数(%ld),无返回值", (long)a);};MyBlock(5);4、有参数,有返野菊花图片回值// 有参数,有返回值int (^MyBlock)(int, int) = ^(int a, int b) { NSLog(@"Block: 有参数(a:%ld, b:%ld)", (long)a,(long)b); NSLog(@"Block: 有返回值(%ld)",(long)(a+b)); return a+b;};MyBlock(1, 2); 注意:实际开发中,经常用到typedef定义一个Block; 例如:用typedef定义一个Block: typedef int (^MyBlock)(int, int); 这样我们定义了一个带有两个整型参数的Block,并且返回值也是一个整型。 定义类属性华容道走法:@property (nonatomic, copy) MyBlock myBlock; 使用: self.myBlock = ^int(int, int) { }; 二、Block对变量的捕获为了保证Block内部能正常访问外部变量,Block有个变量捕获机制:
block的变量捕获1、局部变量截获,是值截获:对于block外的变量引用,block默认是将其复制到其数据结构中来实现访问的。也就是说block的自动变量截获只拉扎尔针对block内部使用的自动变量,不使用则不截获,因为截获的自动变量会存储于block的结构体内部,会导致block体积变大。特别要注意的是公务员工资标准默认情况下block只能访问,不能修改局部变量的值;
NSInteger nNum = 3;NSInteger (^MyBlock)(NSInteger) = ^NSInteger(NSInteger n) { return n*nNum;};nNum = 1;NSLog(@"nNum:%zd", MyBlock(2));// 打印结果:nNum:6解析: 这里输出结果是6而不是2; 原因就是对局部变量nNum的截获是值截获。同样,在Block里如果修改变量nNum也是无效的,甚至编译器会出错;
2、局部静态变量截获,是指针截获:static NSInteger nNum = 3;NSInteger (^MyBlock)(NSInteger) = ^NSInteger(NS新房贷款Integer n) { return n*nNum;};nNum = 1;NSLog(@"nNum:%zd", MyBlock(2));// 打印结果:nNum:2解析: 这里输出的结果是:nNum:2,意味着nNum=1,这里的修改是有效的,即指针截获。同样在Block里去修改变量nNum也是有效的;
可以修改静态变量的值: 静态变量是用静态变量的指针来对其进行访问的,这是超出作用域使用变量的最简单方法;
3、全局变量,静态全局变量截获,不截获,直接取值:在函数外定义变量:NSInteger nGlobalNum_ = 1;static NSInteger nStaticNum = 3;NSInteger (^MyBlock)(NSInteger) = ^NSInteger(NSInteger n) { return n*nGlobalNum_;};nGlobalNum_ = 1;NSLog(@"nNum:%zd", MyBlock(2));// 打印结果:nNum:2NSInteger (^MyBlock)(NSInteger) = ^NSInteger(NSInteger n) { return n*nStaticNum;};nStaticNum = 1;NSLog(@"nNum:%zd", MyBlock(2));// 打印结果:nNum:2从Block语法转换成的C语言函数中访问静态全局变量/全局变量并没有任何改变,可以直接使用。
三、Block的分类与存储域block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型;
1、NSGlobalBlock (_NSConcreteGlobalBlock)没有访问auto变量的block是__NSGlobalBlock__;
全局块:存储在全局内存中,相当于单例;
2、NSStrackBlock (_NSConcreteStackBlock)访问auto变量的block是__NSStrackBlock__;
栈块:存在于栈内存中,超出其作用域则马上销毁;
3、NSMallocBlock (_NSConcreteMallocBlock)__NSStrackBlock__ 调用了copy 是 __NSMallocBlock__
堆块:存在于堆内存中,是一个带引用计数的对象,需要自行管理其内存;
block存储域类型Block的内存分配情况如下图:
block内存分配区域简而言之:存储在栈中的Block就是栈块,存储在堆中的Block就是堆块,既不在栈中也不在堆中的就是全局块;
四、block的copy1、block的copy效果流程图Block的复制操作执行的是copy实例方法。
block的copy效果图根据表得知,Block在堆中copy会造成引用计数增加,这与其他Object-C对象是一样的。虽然Block在栈中也是以对象的身份存在,但是栈块没有引用计数,因为不需要,我们都知道栈区的内存由编译器自动分配释放。
2、ARC环境下的copy在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况:
block作为函数返回值; 将block赋值给__strong指针时;block作为Cocoa API中方法名含有using Block的方缓解胃痛的方法法参数时 block作为GCD API的方法参数时;3、Block属性的写法建议MRC下block属性的建议写法:
@property (copy, nonatomic) void (^bl黄财神ock)(void);
ARC下block属性的建议写法:
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
五、Block的本质1、Block的本质Block的本质也是一个OC对象,它内部也有一个isa指针; Block是封装了函数调用以及函数调用环境的OC对象; Block的底层结构如下图所示: Block底层结构2、Block源码转换查看block在实际编译时无法转换成我们能够理解的源代码,但可以通过clang(LLVM编译器)转换成可读的源代码,步骤如下:
打开终端,输入cd 把要转换的文件拖到终端,然后回车进入要转换旅游同业文件的目录; 输入命令clang -rewrite-objc main.m -o main.cpp要转换文件代码如下:
int main(int argc, const char * argv[]) { @autoreleasepool { void (^MyBlock)(void) = ^{ NSLog(@"MyBlocl"天龙八部王大妈;); }; MyBlock(); } return NSApplicationMain(argc, argv);}3、__weak问题解决
在使用clang转换OC为C++代码时,可能会遇到以下问题
cannot create __weak reference in file using manual reference
解决方案:支持ARC、指定运行时系统版本,比如:
xcurn -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
六、__block__block存储域类说明符。存储域类说明符,指定将变量设置到哪个存储域中,如:auto栈、static堆、extern全局、register寄存器,
1、__block修饰符对于用__block修饰的外部变量引用,block是复制其引用地址来实现访问的。block可以修改_block修饰的外部变量的值;
__block可以用于解决block内部无法修改auto变量值的问题;__block不能修饰全局变量、静态变量(static) 编译器会将_block变量包装成一个对象,对象里有一个_forwarding指针,此指针指向自身对象;struct __Block_b苗方清颜yref_age_0 { void *__isa;__Block_byref_age_0 *__forwarding; int __flags; int __size; int age;};struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_age_0 *age; // by ref};__block的的复制通过_forwarding,无论是在block中还是在block外访问block变量,也不管该变量在栈上还是要堆上,都顺利访问同一个__block变量;
2、__block的内存管理当block在栈上时,并不会对__block变量产生强引用;当block被copy到堆时:会调用block内部的copy函数;copy函数内部会调用Blockobjectassign函数对_blcok变量形成强引用(retain)当block从堆中移除时 会调用block内部的dispose函数; dispose函数内部调用_Block_object_dispose函数; _Block_object_dispose函数会自动释放引用的__block变量(release);3、对象类型的auto变量当block内部访问了对象类型的auto变量时:
如果block是在栈上,将不会对auto变量产生强引用; 如果block被拷贝到堆上: 会调用block内部的copy函数; copy函数内部会调用Block_object_assign函数;_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用;如果bl波兰苹果ock从堆上移除: 会调用block内部的dispose函数; dispose函数内部会调用_Blockobjectdispose函数;_Block_object_dispose函数会自动释放引用的auto变量(release);block调用时机4、对象类型的auto变量、__block变量当block在栈上时,对它们都不会产生强引用; 当block拷贝到堆上时,都会通过copy函数来处理它们;copy函数内部会调用_Block_object_assign函数;
_Block_object_assign函数会根据所指向对象的修饰符(strong、weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain);
中国老师来了a、__block变量(假设变量名叫做a) _Block_object_assign((void *)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF */); b、对象类型的auto变量(假设变量名叫做p) _Block_object_assign((void *)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
当block从堆上移除时,都会通过dispose函数来释放它们;dispose函数内部会调用_Block_object_dispose函数;
_Block_object_dispose函数会自动释放指向的对象(release)
a、__block变量(假设变量名叫做a) _Block_object_dispose((void *)src->a, 8 /*BLOCK_FIELD_IS_BYREF*/);
b、对象类型的auto变量(假设变量名叫做p) _Block_object_dispose((void*)src->, 3 /*BLOCK_FIELD_IS_OBJECT */
七、循环引用问题1、循环引用产生原因某个类将block作为自己的属性变量(强引用),然后该类在block的方法体里面又使用了该类本身(block内部使用强引用),这里会产生循环引用问题,
如:
self.someBlock = ^(Type var) { [self dosomething];};2、解决循环引用问题 - ARC用_weak、__unsafe_unretained解决 用__block 解决(必须调用block,否则依然会产生循环引用)__weak typeof(self) weakSelf = self;self.someBlock = ^(Type var) { [weakSelf dosomething]; };3、解决循环引用问题 - MRC用__unsafe_unretained解决 用_block解决__unsafe_unretained id weakSelf = self;self.block = ^{ NSLog(@"%p", weaSelf);};__block typeof(self) blockSelf = self;self.someBlock = ^(Type var) { [blockSelf dosomething];}4、使用__block变量的优缺优点:
通过block变量可控制对象的持有期间;不能使用weak修饰符的环境中不使用unsafe_unretained修饰符即可(不必担心选垂指针)在执行block时可动态地决定是否将nil或其他对象赋值在__block变量中;缺点:
为避免循环引用必须执行block;八、block的使用示例1、block作为变量// 定义一个Block变量sumint (^sum)(int, int);// 给Block变量赋值sum怀孕后会来月经吗 = ^int (int a, int b) { return a+b;};// 调用Block变量int a = sum(10, 20);2、Block作为属性typedef int (^CalculateBlock)(int a, int b);CalculateBlock sum = ^(int a, int b) { return a+b;};int a = sum(10, 20);typedef int (^CalculateBlock)(int a, int b);self.sum = ^int(int a, int b) {return a+b;};int a = self.sum(10, 20);// 不使用typedef@property (nonatomic, copy) int (^sum)(int, int);self.sum = ^int(int, int) { return a+b;};int a = self.sum(10, 20);3、作为OC中的方法参数// 无参数传递的Block- (CGFloat)testTimeConsume:(void(^)())middleBlock { // 执行前记录下当前时间 CFTimeInterval startTime = CACurrentMediaTime(); middleBlock(); // 执行后记录下当前时间 CFTimeInterval endTime = CACurrentMediaTime(); return endTime - startTime;}// 调用[self testTimeConsume:^{ // 放入Block中的代码}];// 有参数传递的Block- (CGFloat)testTimeConsume:(void (^)(NSString *name))middleBlock { // 记录下当前时间 CFTimeInterval stateTime = CACurrentMediaTime(); NSString *strName = @"传递参数"; middleBlock(strName); // 记录下执行block后的当前时间 CFTimeInterval endTime = CACurrentMediaTime(); return endTime - stateTime;}// 调用[self testTimeConsume:^(NSString *name) { // 放入Block中的代码,可以使用参数name // 参数name是实现代码中传入的,在调用时只能使用,不能传值}];4、block回调Block回调是关于Block最常用的内容,比如网络下载,我们可以用Block实现下载成功与失败的反馈。开发者在Block没发布前,实现回调基本都是通过代理的方式进行的,比如负责网络请求的原生类NSURLConnection类,通过多个协议方法实现请求中的事件处理。而在最新的环境下,使用的NSURLSession已经采用Block的方式处理任务请求了。这种转变很大一部分原因在于Block使用简单,逻辑清晰,灵活等原因。
@interface ViewController()<NSURLSessionDo塞罕坝精神wnloadDelegate>typedef void (^DownHandler)(NSData *receiveData, NSError *error);@end- (void)downloadWithURL:(NSString *)strURL para鸡尾酒大全meters:(NSDictionary *)parameters Handler:(DownHandler)downHandler { NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithStri李寻欢林诗音ng:strURL]]; NSURLSession *session = [NSURLSession sharedSession]; // 执行请求任务 NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { if ( downHandler ) { dispatch_async(dispatch_get_main_queue(), ^{ downHandler(data, error); }); } }]; [task resume];}- (void)btnClickDown:(id)sender { NSString *strImagURL = @"codeload.github/AFNetworking/AFNetworking/zip/master"; [self downloadWithURL:strImagURL parameters:nil Handler:^(NSData *receiveData, NSError *error) { if ( error ) { NSLog(@"下载失败:%@", error); } else { NSLog(@"下载成功:%@", receiveData); } }];}九、其它1、如何截获自动变量:Block语法转换成C函数后,Block语法表达式中用到的自动变量会被作为成员变量追回到了__main_block_impl_0结构体中。而此结构体中的成员变量类型与自动变量类型完全相同,但仅限于Block语法中使用到的自动变量。
因此,所就业指导谓“截获自动变量值”意味着执行Block语法时,Block语法表达式所使用自动变量值被保存到了Block的结构体实例中。
2、为什么Block语法中不能使用数组?因为结构体中的成员变量与自动变量类型完全相同。 榴莲酥的做法所以结构体中使用数组截取数组值,而后调用时再赋值给另一个数组。 也就是数组赋值给数组,在这C语言中是不被允许的。3、在block中修改截获自动变量值的两种方法?使用静态变量,静态全局变量,全局变量; 使用__block修饰符;4、为何静态变量的这种方法不适用于自动变量?因为静态变量会存储在堆上,而自动变量却存在拼装玩具栈上。
当超出其作用域的进修,静态变量还会存在,而自动变量所占内存则会被释放因而废弃。所以不能通过指针访问原来的自动变量;
5、什么时候栈上的block会复制到堆上?调用block的copy实例方法; 将block作为函数参数返回值返回时,编译器会自动进行copy操作;将block赋值给附有__strong修饰符id类型的类或block类型成员变量时;在方法名中含有usingBlock的cocoa框架方法或GCD(Grand Central Dispatch)的API中传递block时;6、遇到一个block,我们怎么知道这个block的存储位置呢?全局块:block不访问外界变量(包括栈和堆中的变量),block既不在栈中也不在堆中,在代码段中,ARC和MRC都是如此,此时为全局块;block访问外界变量; MRC环境下,访问外界变量的block默认存储栈中; ARC环境下,访问外界变量的block默认存储在堆中(实际是放在栈区,然后ARC情况下自动又拷贝到堆区,自动释放)在栈区的block调用了copy,拷贝到了堆区;7、ARC下,访问外界变量的block为什么要自动从栈区拷贝到堆区呢?RC下,访问外界变量的Block为什么要自动从栈区拷贝到堆区呢? 栈上的Block,如果其所属的变量作用域结束,该Block就被废弃,如同一般的自动变量。当然,Block中的__block变量也同时被废弃。
为了解决栈块在其变量作用域结束之后被废弃(释放)的问题,我们需要把Block复制到堆中,延长其生命周期。开启ARC时,大多数情况下编译器会恰当地进行判断是否有需要将Block从栈复制到堆,如果有,自动生成将Block从栈上复制到堆上的代码。 Block的复制操作执行的是copy实例方法。 Block只要调用了copy方法,栈块就会变成堆块。
本文发布于:2023-06-02 10:39:05,感谢您对本站的认可!
本文链接:http://www.ranqi119.com/ge/85/189878.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |