iOS ------ Block的总结

北冰洋以北  论坛元老 | 2024-6-13 12:00:01 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1042|帖子 1042|积分 3126

前面看了Block的基本知识,和一些源码。但对于block怎么用的还不了解,代码中出现block会看不懂,如今来具体看一下Block的用法并做个总结。
1.Block是什么

block对象是一个C语言布局体,可以并入C和OC的代码中,Block本质是一个匿名函数,以及与该函数一起使用的数据,其他语言偶然称为闭包或ambda。Block特殊适合于回调,或者是在你为了让代码看起来具有更清楚的逻辑进行代码的组合时使用。
上面是苹果官方的解释,告诉我们:首先Block是一个OC中的对象,而且这个对象是一个C语言的布局体,它可以使用在C语言和OC中;同时,Block本质是一个匿名函数和其包罗的数据会集
2.为什么用Block

苹果官方的文档的描述

  • 代替代理和委托方法
  • 作为回调函数的代替
  • 一次性编写多次处理的任务
  • 方便对聚集中的所有项执行任务
  • 与GCD队列一起执行异步任务
以上这些环境,我们都能够使用Block。我感受最深刻的是使用Block回调,许多环境下,我们可能只必要对某个变乱进行一个简朴的回调,也许就仅仅一次,假如我们使用代理的话,我们必要创建类,编写协议,仅仅对一个小地方的回调本钱很高,那么Block的使用就恰如其分。除此之外,Block的特性还可以让代剖析合在某处,我们只必要在一个地方就可以完成回调之前和回调时的代码,相比,使用回调函数和代理都没有这个优势。另外,我门可以想到,OC中封装了一些聚集分方法,比如数组的排序,这里就使用Block进行回调操作的。
简朴讲一下回调的概念
将一段代码和一个特定的变乱接洽在一起,当特定变乱发生后,这段代码才会被执行。
OC的几种回调


  • Targe-action回调
  • delegate方式
  • NOtification方式
  • block方式
OC的的回调几种方式
3.怎么使用Block

我们先创建一个Block,并简朴的使用

运行效果

这里是为了强调block的回调效果。可以发现,只管block的代码早就声明了,但是没用立刻调用,而是在block的调用的时候,才被执行。到这里应该对Block的回调有一定的理解。
这好像并没有什么用处,下面看一看另外两种Block的使用。前面说block是OC的一个对象,既然是对象,我们可以把它当做一个类分属性,应该也可以更其他属性一样,被当作一个方法的参数吧。这也是block被大家承认的地方。
假设我们有这样一个类:包罗一个block属性testBlock,包罗一个调用自己的block属性的方法blockDo。
  1. @interface Computer : NSObject
  2. @property (nonatomic, copy) NSString* (^testBlock)(NSString*);//将block作为computer的属性
  3. - (void)blockDo;
  4. @end
  5. #import "Computer.h"
  6. @implementation Computer
  7. - (void)blockDo {
  8.     NSString* testString = @"textData_old";
  9.     if (self.testBlock) {
  10.         NSLog(@"%@", self.testBlock(testString));//调用并打印
  11.     }
  12. }
  13. @end
复制代码
在其他地方写下这些代码
  1. Computer* computer = [[Computer alloc] init];
  2.         computer.testBlock =  ^(NSString* parStr){
  3.             NSLog(@"%@",parStr);
  4.             parStr = @"testData_New";
  5.             return parStr;
  6.         };
  7.         [computer blockDo]; //执行Block
复制代码
运行效果

这里的调用就比前面的复杂了。由于我给Computer添加了一个方法,而且将block的调用交给了Computer,我只是实现了block而已,最后启动调用它的方法。我们在另一个地方对Computer类模仿了一个方法(也就是块,这个方法没有在Computer类中实现,我们乃至可以在任何地方实现它,最后我们可以在其他地方调用。这就是Block的神奇的地方。
再来看一个
  1. @interface Computer2 : NSObject
  2. - (void)doSomthingFeedBakck:(NSString* (^)(NSString*))handle;
  3. @end
  4. #import "Computer2.h"
  5. @implementation Computer2
  6. -(void)doSomthingFeedBakck:(NSString * _Nonnull (^)(NSString * _Nonnull))handle {
  7.     NSString* handleStr = @"Old";
  8.     sleep(3.0);
  9.     NSLog(@"%@", handle(handleStr));
  10. }
  11. @end
复制代码
其他地方调用
  1. Computer2* computer2 = [[Computer2 alloc] init];
  2.         [computer2 doSomthingFeedBakck:^NSString * _Nonnull(NSString * parStr) {
  3.             NSLog(@"%@", parStr);
  4.             NSString* returnstr = [NSString stringWithFormat:@"add %@", parStr];
  5.             return returnstr;
  6.         }];
复制代码
运行效果

这里讲Block作为一个参数放在doSomthingFeedBakck函数面。表现了block的对象本质,相比之下,代码非常简洁。这种实现回调的方法逻辑更加清楚,明朗。
上面三个例子,展示了block的三种差别的使用方式。它们分别是:


  • 将block界说成员变量
  • 将block界说成属性
  • 将block作为参数
4.总结

通过上面的block的额用法发现,block每次回调是通过它的匿名函数进行的,也就是每次最多执行一个回调,在必要进行大批量的回调的时候,就必要写许多差别的block回调,这样就不合适这时使用协媾和代理的方式就自然多了。除此之外,block还比较适合线程之间的切换回调,GCD就是采用了多线程结合block来做的。
5.下面我们记录一些block的原理性知识



  • 为什么说block是一个布局体,也是一个对象,同时还是携带数据的匿名函数
  • 全局block,栈block,以及堆区block的区别和他们之间的接洽,探究block的内存管理
  • 为什么使用_block就可以使block可以修改外部变量
  • 引起强引用的缘故起因是什么,我们解决的方法和原理是什么
1,为什么说block是一个布局体,也是一个对象,同时还是携带数据的匿名函数

使用Mac终端,cd将图中的第一个文件拖入

文件写以下内容
  1. #import <Foundation/Foundation.h>
  2. int main(int argc, const char * argv[]) {
  3.     @autoreleasepool {
  4.         // insert code here...
  5.         NSLog(@"Hello, World!");
  6.         void (^block)(void) = ^ {
  7.             printf("a block");
  8.         };
  9.         block();
  10.     }
  11.     return 0;
  12. }
复制代码
查看clang中心文件
  1. clang -rewrite-objc main.m
复制代码

回车会在刚拖入的文件中天生.cpp文件
  1. struct __main_block_impl_0 {
  2.   struct __block_impl impl;
  3.   struct __main_block_desc_0* Desc;
  4.   __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
  5.     impl.isa = &_NSConcreteStackBlock;
  6.     impl.Flags = flags;
  7.     impl.FuncPtr = fp;
  8.     Desc = desc;
  9.   }
  10. };
  11. //block 结构体
  12. //这一部分在.cpp文件中没有找到
  13. struct __block_impl {
  14.   void *isa;//block 的类型
  15.   int Flags;
  16.   int Reserved;
  17.   void *FuncPtr;// block的执行函数指针,指向__main_block_func_0
  18. };
  19.                         
  20. static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  21.             printf("a block");
  22.         }
  23. static struct __main_block_desc_0 {
  24.   size_t reserved;
  25.   size_t Block_size;
  26. } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
  27. int main(int argc, const char * argv[]) {
  28.     /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
  29.         void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
  30.         ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
  31.     }
  32.     return 0;
  33. }
复制代码
到这里,我们可以看到,在编译之前,block复原成以上四个布局体。 它们分别是:


  • __main_block_impl_0
  • __block_impl
  • __main_block_desc_0
  • __main_block_func_0
我们临时不管这些布局体代表的都是什么。但可以阐明block是布局体。过细观察__main_block_impl_0 的机构中,有isa指针一项(黄色标出)。看到这里理解为什么苹果强调block也是一个对象了。再看还有 __main_block_func_0 ,这里其实就是我们对block函数体的实现,它实际上是一个匿名函数,作为block的浩繁布局体的一部分。
全局block,栈block,以及堆区block的区别和他们之间的接洽,探究block的内存管理

在OC中用三种差别的block类型。它们分别是全局block _NSConcretionGlobalBlock, 栈block _NSConcretionStackBlock,以及堆block _NSConcretionMallocBlock.
全局block
假如我们这样创建一个block并使用

通过clang 下令获得中心编译内容
  1. int GlobalInt = 0;
  2. struct __getGlobalInt_block_impl_0 {
  3.   struct __block_impl impl;
  4.   struct __getGlobalInt_block_desc_0* Desc;
  5.   __getGlobalInt_block_impl_0(void *fp, struct __getGlobalInt_block_desc_0 *desc, int flags=0) {
  6.     impl.isa = &_NSConcreteGlobalBlock;
  7.     impl.Flags = flags;
  8.     impl.FuncPtr = fp;
  9.     Desc = desc;
  10.   }
  11. };
  12. static int __getGlobalInt_block_func_0(struct __getGlobalInt_block_impl_0 *__cself) {
  13.     return GlobalInt;
  14. }
  15. static struct __getGlobalInt_block_desc_0 {
  16.   size_t reserved;
  17.   size_t Block_size;
  18. } __getGlobalInt_block_desc_0_DATA = { 0, sizeof(struct __getGlobalInt_block_impl_0)};
  19. static __getGlobalInt_block_impl_0 __global_getGlobalInt_block_impl_0((void *)__getGlobalInt_block_func_0, &__getGlobalInt_block_desc_0_DATA);
  20. int (*getGlobalInt)(void) = ((int (*)())&__global_getGlobalInt_block_impl_0);
  21. int main(int argc, const char * argv[]) {
  22.     /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
  23.         NSLog((NSString *)&__NSConstantStringImpl__var_folders_46_qzlmlhgd0xd2xjp590bfb5_00000gn_T_main_14343d_mi_0, ((int (*)(__block_impl *))((__block_impl *)getGlobalInt)->FuncPtr)((__block_impl *)getGlobalInt));
  24.     }
  25.     return 0;
  26. }
复制代码
_NSConcretionGlobalBlock代表这是一个全局的block。全局block和全局变量一样,可以在整个数据域中使用。 这里的其他的任何retain, copy对它是没有影响的。它存储在静态地区,基本可以理解,在APP运行期间,它是不停存在的。
很明显看到,这个block作为全局变量的形式被创建出来的。还有一种更加隐秘的方式, 像下面这样。

打印

同样是一个全局的block。
所以全局block的天生有两种差别的环境,一个是直接将block创建成一个全局变量。这是苹果官方的用法。另一种是创建一个局部变量的block,在block的函数体中,不使用任何外部的全局变量。但这个block和全局变量的block是有所差别的。我们将这个block的cpp中心文件打开。
  1. struct __main_block_impl_0 {
  2.   struct __block_impl impl;
  3.   struct __main_block_desc_0* Desc;
  4.   __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
  5.     impl.isa = &_NSConcreteStackBlock;
  6.     impl.Flags = flags;
  7.     impl.FuncPtr = fp;
  8.     Desc = desc;
  9.   }
  10. };
  11. static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  12.             printf("a block");
  13.         }
  14. static struct __main_block_desc_0 {
  15.   size_t reserved;
  16.   size_t Block_size;
  17. } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
  18. int main(int argc, const char * argv[]) {
  19.     /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
  20.         void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
  21.         ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
  22.         NSLog((NSString *)&__NSConstantStringImpl__var_folders_46_qzlmlhgd0xd2xjp590bfb5_00000gn_T_main_c071cc_mi_0, block);
  23.     }
  24.     return 0;
  25. }
复制代码
竟然是 _NSConcreteStackBlock! 也即是我们所说的栈block! 这是为什么呢? 我个人的理解是,对于CPP文件中的指示,它是告诉用户这个布局体的存储的位置。 而在nslog打印中表现不一样是由于由于没有包罗局部变量,所以block本身不必要携带上下文环境,系统在编译的时候,默认Block是全局环境。 这才导致两种展示的方式不一样。
栈block
刚才说了,全局block的其中创建方式是作为一个不包罗外部变量的局部变量block。 那假如这个block变量包罗了外部变量那又会怎样呢。没错,当包罗了外部变量的时候,它是一个栈Blobk。

clang查看代码天生的中心文件。 发现是存在于栈中的。
  1. struct __main_block_impl_0 {
  2.   struct __block_impl impl;
  3.   struct __main_block_desc_0* Desc;
  4.   int a;
  5.   __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
  6.     impl.isa = &_NSConcreteStackBlock;
  7.     impl.Flags = flags;
  8.     impl.FuncPtr = fp;
  9.     Desc = desc;
  10.   }
  11. };
复制代码
打印效果,在ARC环境中

在MRC环境下

为什么在ARC的环境下的打印效果差别呢?
正常环境下,block的申明都是在栈中的,假如必要将它转到堆上,必要进行block_copy,或者其他发送copy消息。
假如在MRC没有进行copy的话,那么当处于栈中的block的环境被销毁的时候,block也等同被销毁了。
而在ARC中 ,由于系统会主动对block发送copy消息(缘故起因是为了确保在块作为对象时,其生命周期得到正确管理)所以我们打印的时候看到block是mallco类型,即位于堆上的。在没有进行copy 之前,栈上的Block使用retain 等操作都是没有实际作用的。
堆上的block
block的申明的时候都是在栈上的,假如发送了copy消息,那么block才会被复制到堆上。
当复制到堆上之后,我们使用block就可以像使用普通的属性一样,可以进行retain等。注意,重复发送copy 消息,也只会在堆上生存一份blcok。在block所在的栈中的内容没有被销毁之前,这个栈中的block还依然存在的。但是它多了一条跟堆中block的接洽。我们回过头看他的一个布局体:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf(“a block”);
}
在这个block的布局体中,布局体本身的类型是__main_block_func_0 。 内部有一个参数指针指向了一个__main_block_impl_0,cself 布局体,这个指针实际上指向的是自己,假如block担当了copy 消息之后,那么这个指针将指向堆上的那份block,而堆上的那份的block的cself 还是指向堆上的blobk布局体,这就是为什么在复制到堆上之后,当栈上的内容被销毁时,block调用不会crash的缘故起因了。
三,为什么使用_block就可以使block可以修改外部变量

我们先看一看当block对外部变量使用__block修饰局部变量前后的clang
使用前

clang
  1. struct __main_block_impl_0 {
  2.   struct __block_impl impl;
  3.   struct __main_block_desc_0* Desc;
  4.   int a;
  5.   __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
  6.     impl.isa = &_NSConcreteStackBlock;
  7.     impl.Flags = flags;
  8.     impl.FuncPtr = fp;
  9.     Desc = desc;
  10.   }
  11. };
  12. static void __main_block_func_0(struct __main_block_impl_0 *__cself) {  int a = __cself->a; // bound by copy            printf("%d", a);        }```
复制代码
使用后

clang
  1. struct __Block_byref_a_0 {
  2.   void *__isa;
  3. __Block_byref_a_0 *__forwarding;
  4. int __flags;
  5. int __size;
  6. int a;
  7. };
  8. struct __main_block_impl_0 {
  9.   struct __block_impl impl;
  10.   struct __main_block_desc_0* Desc;
  11.   __Block_byref_a_0 *a; // by ref
  12.   __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
  13.     impl.isa = &_NSConcreteStackBlock;
  14.     impl.Flags = flags;
  15.     impl.FuncPtr = fp;
  16.     Desc = desc;
  17.   }
  18. };
  19. static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  20.   __Block_byref_a_0 *a = __cself->a; // bound by ref
  21.             printf("%d", (a->__forwarding->a));
  22.         }
复制代码
我们可以看到区别:
当使用不加block修饰的局部变量时,直接通过__cself指针找到__main_block_impl_0布局体中主动天生的一个相同类型的age变量(为值传递),所以不能修改捕获的变量。
但使用block修饰的block变量时,通过__cself指针找到__main_block_impl_0布局体中__Block_byref_a_0 *的a布局体(_block修饰的局部变量),再在a中找到__forwarding指针,可以看到它的类型同样为__Block_byref_a_0 *类型,它指向布局体本身(block修饰的局部变量),最后通过_forwarding指针找到局部变量a本身,这样就可以进行修改外部变量。
简朴讲,就是一个修改的是值传递过来的外部变量,一个是通过指针找到外部变量本身,修改实际上就是对外部变量的修改
四,引起强引用的缘故起因是什么,我们解决的方法和原理是什么

什么环境下block会造成循环引用
block为了保证代码块内部对象不被提前释放,会对block中的对象进行强引用,就相当于持有了其中的对象,假如此时block中的对象有持有了该block,就会造成循环引用。

block作为self的一个属性,表明self是持有block的,这样,假如必要释放block,至少必要先把self对它的持有释放,而block是self的属性,要让self不在持有它,只有一种环境,self已经被释放。ARC会主动处理块对self的持有关系。当块被复制到堆上时,会主动将self的引用计数加1,以确保在块的生命周期内,self对象不会被提前释放,也即是持有了self.
那么这样的环境下,假如要释放self,至少要做的一件事变是让block不再持有self,显然,上面的这种环境,要不持有slef,只能等候block被销毁。 这时候,block和slef相互等候着对方先被释放,才气释放自己,不停矛盾着两个对象都得不到释放,就会造成循环引用。
解决方法
1,OC提供了weak修饰符
我们会申请一个weak self对象参与block的copy使用。

这样就不会发生警报。
2.使用弱引用和强引用的结合
当使用__weak修饰后,假如外部对象为空了,那么block的内部对象也将为空,这样偶然候并不是我们想要的。AFNetWorking中使用了一个办法解决这一问题。就是在block内部继续使用__Strong来修饰带进来的weakself。

这样做的好处就是避免了循环引用,同时保证对象在block中的持续存在,而不会由于block外的变量由于被释放掉使block内部的变量也为空了。
3.使用__block修饰符

当一个对象被__block修饰时,在块内部对该对象的引用会变为一个弱引用。这样,在块内部对该对象的使用不会制止对象的释放,从而冲破了循环引用。
必要注意的是,__block修饰符只在块内部起作用,不会改变对象在块之外的引用语义。因此,纵然对象被__block修饰,假如在块外部仍然存在其他强引用,循环引用仍然可能发生。因此,使用__block修饰符时,必要综合思量对象在块内外的引用关系,以避免潜在的循环引用问题。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

北冰洋以北

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表