iOS进阶-深度学习block原理

目录

  1. 基本声明使用总结
  2. block在ARC、MRC下内存方式
  3. 使用block遇到的坑
  4. 更深次的看block的本质

基本声明使用总结

声明方式总结:

声明格式:typeReturn(^blockName)(typeParameters)
声明+实现: <#returnType#>(^blockName)(<#parameterTypes#>) = ^(<#parameters#>) {
        <#statements#>
    };

使用方式一:

    void(^myblock)(void) = ^{
        NSLog(@"没有参数没有返回值的block");
    };

使用方式二:

 NSString*(^myblock)(void) = ^{
        return @"有返回值没有参数的block";
    };

使用后方式三:

    void(^myblock)(NSString*) = ^(NSString * a){
        NSLog(@"%@",[NSString stringWithFormat:@"没有返回值 有参数:%@",a]) ;
    };

总结:

无论block声明的是否有返回值,在实现中都可以不写出来,单是如果声明里有参数时,实现必须加入参数。

block在ARC、MRC下内存方式

ARC下:

/*
 ARC只存在两种内存情况:  1. 获取局部变量的block都会复制到堆区  2. 获取全局、静态变量或者不获取变量都会加载搭配全局区
 2019-05-17 09:14:17.186183+0800 test-block[44562:5593068] myblock 没有获取外部变量
 2019-05-17 09:14:17.186344+0800 test-block[44562:5593068] myblock: <__NSGlobalBlock__: 0x1037881c8>
 2019-05-17 09:14:17.186433+0800 test-block[44562:5593068] myblock2获取到局部变量:3
 2019-05-17 09:14:17.186531+0800 test-block[44562:5593068] myblock2: <__NSMallocBlock__: 0x600001aec060>
 2019-05-17 09:14:17.186611+0800 test-block[44562:5593068] myblock3获取到局部变量:3
 2019-05-17 09:14:17.186681+0800 test-block[44562:5593068] myblock3: <__NSMallocBlock__: 0x600001aec0c0>
 2019-05-17 09:14:17.186763+0800 test-block[44562:5593068] myblock4获取到全局变量:7
 2019-05-17 09:14:17.186838+0800 test-block[44562:5593068] myblock4: <__NSGlobalBlock__: 0x1037881e8>
 2019-05-17 09:14:17.186920+0800 test-block[44562:5593068] 我是用copys修饰的属性block 加载局部变量 4
 2019-05-17 09:14:17.187008+0800 test-block[44562:5593068] myblockT1: <__NSMallocBlock__: 0x600001af5da0>
 2019-05-17 09:14:17.187103+0800 test-block[44562:5593068] 我是用copys修饰的属性block 加载q全局变量7
 2019-05-17 09:14:17.187326+0800 test-block[44562:5593068] myblockT2: <__NSGlobalBlock__: 0x103788208>

 */
- (void) storageAboutBlockInARC{
    
    void(^myblock)(void) = ^{
        NSLog(@"myblock 没有获取外部变量");
    };
    myblock();
    NSLog(@"myblock: %@",myblock);  // <__NSGlobalBlock__: 0x1037881c8>
    
    
    int i=3;
    void(^myblock2)(void) = ^{
        NSLog(@"myblock2获取到局部变量:%d",i);
    };
    myblock2();
    NSLog(@"myblock2: %@",myblock2); // <__NSMallocBlock__: 0x600001aec060>
    
    
    __block int h=3;
    void(^myblock3)(void) = ^{
        NSLog(@"myblock3获取到局部变量:%d",h);
    };
    myblock3();
    NSLog(@"myblock3: %@",myblock3); // <__NSMallocBlock__: 0x600001aec0c0>
    
    void(^myblock4)(void) = ^{
        NSLog(@"myblock4获取到全局变量:%d",m);
    };
    myblock4();
    NSLog(@"myblock4: %@",myblock4); //  <__NSGlobalBlock__: 0x1037881e8>
    
    int l =4;
    self.myblockT1 = ^{
        NSLog(@"我是用copys修饰的属性block 加载局部变量 %d",l);
    };
    self.myblockT1();
    NSLog(@"myblockT1: %@",self.myblockT1); // <__NSMallocBlock__: 0x600001af5da0>
    
    
    self.myblockT2 = ^{
        NSLog(@"我是用copys修饰的属性block 加载q全局变量%d",m);
    };
    self.myblockT2();
    NSLog(@"myblockT2: %@",self.myblockT2);  // <__NSGlobalBlock__: 0x103788208>

}


/*
 声明方式:
 */
- (void) simpleUsage
{
//    使用方式一:
    void(^myblock1)(void) = ^{
        NSLog(@"没有参数没有返回值的block");
    };
//    使用方式二:
    NSString*(^myblock2)(void) = ^{
        return @"有返回值没有参数的block";
    };
//    使用后方式三:
    void(^myblock3)(NSString*) = ^(NSString * a){
        NSLog(@"%@",[NSString stringWithFormat:@"没有返回值 有参数:%@",a]) ;
    };

}

MRC下:

/*
 MRC情况下内存相关:1. 位于代码区的block访问局部变量的都会复制到栈区 2. 访问局部全局、静态变量会加载到全局区 3、被copy修饰的block访问局部变量会被加载到堆区
 2019-05-17 09:08:01.133444+0800 test-block[44272:5574974] myblock 没有获取外部变量
 2019-05-17 09:08:01.133628+0800 test-block[44272:5574974] myblock: <__NSGlobalBlock__: 0x1067ea100>
 2019-05-17 09:08:01.133728+0800 test-block[44272:5574974] myblock2获取到局部变量:3
 2019-05-17 09:08:01.133895+0800 test-block[44272:5574974] myblock2: <__NSStackBlock__: 0x7ffee94159c0>
 2019-05-17 09:08:01.134000+0800 test-block[44272:5574974] myblock3获取到局部变量:3
 2019-05-17 09:08:01.134188+0800 test-block[44272:5574974] myblock3: <__NSStackBlock__: 0x7ffee9415970>
 2019-05-17 09:08:01.134353+0800 test-block[44272:5574974] myblock4获取到全局变量:7
 2019-05-17 09:08:01.134427+0800 test-block[44272:5574974] myblock4: <__NSGlobalBlock__: 0x1067ea170>
 2019-05-17 09:08:01.134529+0800 test-block[44272:5574974] 我是用copys修饰的属性block 加载局部变量 4
 2019-05-17 09:08:01.134752+0800 test-block[44272:5574974] myblockT1: <__NSMallocBlock__: 0x600002f8a5e0>
 2019-05-17 09:08:01.135106+0800 test-block[44272:5574974] 我是用copys修饰的属性block 加载q全局变量7
 2019-05-17 09:08:01.135668+0800 test-block[44272:5574974] myblockT2: <__NSGlobalBlock__: 0x1067ea190>
 */
- (void) storageAboutBlockInMRC{
    void(^myblock)(void) = ^{
        NSLog(@"myblock 没有获取外部变量");
    };
    myblock();
    NSLog(@"myblock: %@",myblock); //  <__NSGlobalBlock__: 0x108d2d0f8>
    
    
    int i=3;
    void(^myblock2)(void) = ^{
        NSLog(@"myblock2获取到局部变量:%d",i);
    };
    myblock2();
    NSLog(@"myblock2: %@",myblock2); //   <__NSStackBlock__: 0x7ffee52969c0>
    
    
    __block int h=3;
    void(^myblock3)(void) = ^{
        NSLog(@"myblock3获取到局部变量:%d",h);
    };
    myblock3();
    NSLog(@"myblock3: %@",myblock3); //  <__NSStackBlock__: 0x7ffee5296970>
    
    
    void(^myblock4)(void) = ^{
        NSLog(@"myblock4获取到全局变量:%d",m);
    };
    myblock4();
    NSLog(@"myblock4: %@",myblock4); //   myblock: <__NSGlobalBlock__: 0x10535d178>
    
    int l =4;
    self.myblockT1 = ^{
        NSLog(@"我是用copys修饰的属性block 加载局部变量 %d",l);
    };
    self.myblockT1();
    NSLog(@"myblockT1: %@",self.myblockT1); //    <__NSMallocBlock__: 0x60000107dce0>
    
    
    self.myblockT2 = ^{
        NSLog(@"我是用copys修饰的属性block 加载q全局变量%d",m);
    };
    self.myblockT2();
    NSLog(@"myblockT2: %@",self.myblockT2); //   myblock: <__NSGlobalBlock__: 0x10535d178>
}

总结:

一、 在ARC内存管理方式下: block 只存在两种存储方式: 1. 不关是什么block只要加载局部变量都会被加载到堆区 2. 不管是什么block获取全局、静态变量或者不获取外部变量,都会被加载到全局区;
二、在MRC内存房里方式下: 1. 被copy修饰的block 、局部block获取获取局部变量不同,前者被加载到堆区,后者被加载到栈区 2. 访问局部全局、静态变量会加载到全局区

使用block遇到的坑

block属性在ARC、MRC下修饰符

ARC用strong、copy都可以,因为编译器会在编译的时候自动加载到堆区。即便是除了对应的方法仍然可以引用到。
MRC用copy修饰符将block拷贝到堆区,因为栈区中的变量出了作用域之后就会被销毁,无法在全局使用,是在堆区的block不会被提前销毁,可以作为公共资源进行访问,开发者可以手动进行它的内存管理。

什么修饰符随时外部变量,block内部可以修改

通常我们使用 __block __weak ; __block 即可以修饰指针类型也可以修饰基本数据类型,__weak只能修饰指针类型;

循环引用问题

- (void)testBlockRetainCycle {
    ClassB* objB = [[ClassB alloc] init];
    __weak typeof(objB) weakObjB = objB;
    self.myBlock = ^() {
        [weakObjB doSomething];
    };
    objB.objA = self;
}

上图: 由于iOS的内存管理方式是引用计数,出现循环引用的原因如下:


image.png

解决方案:

主要循环节点任意一个节点不是强引用即可解决,通常在block外部将内部需要引用的对象转化为弱引用对象即可解决,但是如果block需要用对象处理以下耗时操作,弱引用对象也会提前释放,导致空指针报错,解决方法是用转化为强引用对象即可解决,强引用的生命周期跟随当前的block。即解决循环引用又解决耗时操作业务。
两种转换方式如下:

 __weak typeof(self)  weakobj = selfObj;
 __strong typeof(weakobj) strongObj = weakobj;

更深次的看block的本质

总结:block 实质上是一个oc对象,内部也有一个isa指针,内部封装了函数调用和调用环境。

 // # main.m 文件
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
void(^myblock1)(void) = ^{
    NSLog(@"没有参数没有返回值的block");
};

对以上的代码执行以下指令将它转变为c++ 代码理解,由于#import <UIKit/UIKit.h>、#import "AppDelegate.h"会生成的库文件没有拷贝,我拷贝主要部分:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

struct __myblock1_block_impl_0 {
  struct __block_impl impl;
  struct __myblock1_block_desc_0* Desc;
  __myblock1_block_impl_0(void *fp, struct __myblock1_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __myblock1_block_func_0(struct __myblock1_block_impl_0 *__cself) {

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_r1_y39rnll97blgw_jhbgdjkh9c0000gn_T_main_eab483_mi_0);
}

static struct __myblock1_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __myblock1_block_desc_0_DATA = { 0, sizeof(struct __myblock1_block_impl_0)};
static __myblock1_block_impl_0 __global_myblock1_block_impl_0((void *)__myblock1_block_func_0, &__myblock1_block_desc_0_DATA);
void(*myblock1)(void) = ((void (*)())&__global_myblock1_block_impl_0);
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

定义block

void(*myblock1)(void) = ((void (*)())&__global_myblock1_block_impl_0);

定义部分调用 __global_myblock1_block_impl_0 调用 block内部;

__myblock1_block_impl_0 函数 是block 的主体部分

struct __myblock1_block_impl_0 {
  struct __block_impl impl;
  struct __myblock1_block_desc_0* Desc;
  __myblock1_block_impl_0(void *fp, struct __myblock1_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

1.__myblock1_block_impl_0内部定义了 isa 执行它所处于的类型,通常有6中,常用有static、globel、heap类型;


image.png

2、有__myblock1_block_impl_0的同名构造函数,穿入block实现、__main_block_desc_0对象;如果定义的block有参数的话也会在flags参数后面追加,

__main_block_func_0的实现部分,比如我纸打印了以下:

static void __myblock1_block_func_0(struct __myblock1_block_impl_0 *__cself) {

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_r1_y39rnll97blgw_jhbgdjkh9c0000gn_T_main_eab483_mi_0);
}

关于block的相关练习代码github地址:https://github.com/ge123/test-block-/blob/master/README.md

全部评论

相关推荐

Noob1024:一笔传三代,人走笔还在
点赞 评论 收藏
分享
10-30 22:18
已编辑
毛坦厂中学 C++
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务