聊一聊Block(一)

一. Block的概念

1.1 带有自动变量的匿名函数。因为函数是对象,所以block本身也是一个对象
将Objective-C编译为C++代码

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 'class.m' -o 'class.cpp'

如果带有weak需要运行时
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios8.0.0 'class.m' -o 'class.cpp'

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
1.2 Block语法

^``返回值类型``参数列表``表达式

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

可以省略为:

^``参数列表``表达式

注:省略返回值类型时,如果表达式中有retrun语句就使用该返回值的类型,如果表达式中没有return语句就使用void类型。表达式中含有多个return语句时,所有return的返回值类型必须相同。

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

如果没有参数还可以省略为以下形式

^``表达式

^{printf("Blocks\n")}
1.3 完整形式的Block语法与一般的C语言函数定义相比,仅有两点不同
  • 没有函数名
  • 带有 ^
// c
void func(int a) {

}
// block
^(int a) {

};

二. Block类型变量

2.1 C语言中将函数地址赋值给函数指针类型变量:

Block除了没有名称以及带有^以外,其他都与C语言函数定义相同。在定义C语言函数时,就可以将所定义函数的地址赋值给函数指针类型变量中。

int func(int count) {
    return count + 1;
}
int (* funcptr) (int count) = &func;
2.2 声明Block类型变量,在Block语法中,也可将Block语法赋值给声明为Block类型的变量中

同样,在Block语法中,可将Block语法赋值给声明为block类型的变量中。即源代码中一旦使用Block语法就相当于生成了可赋值给Block类型变量的“值”。Blocks中由Block语法生成的值也被称为“Block”。在有Block的文档中,“Block”既指源代码中的Block语法,也指由Block语法所生成的值。

// Block类型变量结构
return_type (^blockName)(parameters)


// Block类型变量结构举例
int (^ blockName)(int count);

// Block语法
^ (int count){
    return count + 1;
};

// 使用Block语法将Block赋值为Block类型变量
int (^blockName)(int count) = ^ (int count){
        return count + 1;
};

赋值过程可以描述为:
^开始的Block语法生成的Block被赋值给变量blackName中。

2.3与前面的使用函数指针的源代码相比而知,声明Block类型变量仅仅是将声明函数指针类型变量的^变为^。该Block类型变量与一般的C语言变量完全相同,可作为以下用途使用:
  • 自动变量(局部变量)

    因为与通常的变量相同,所以当然也可以由Block类型变量向Block类型变量赋值。如下:

int (^blackName1)(int count) = blockName;
  • 函数参数
    作为函数参数

    void func(int (^blockName)(int)) {
    
    }
    

    将Block作为函数的返回值返回

    int (^func())(int) {
      return ^(int count){return count + 1;};
    }
    

    在使用Block的时候我们一般 使用typedef 来声明blockName类型的变量

    void func(int (^blockName)(int)) {
    
    }
    可改写为:
    typedef int (^blockName)(int);
    void func(blockName) {
    
    }
    
    

    Block类型变量可完全像通常的C语言变量一样使用,因此也可以使用指向Block类型变量的指针,即Block的指针类型变量

        typedef int (^blk_t)(int);
        blk_t blk = ^(int count) {return count + 1;};
        __strong blk_t *blkptr = &blk; // C语言中的代码因为没有引用计数内存管理,在Xcode中需要指定修饰符
        (*blkptr)(10);
    
  • 静态变量

  • 静态全局变量

  • 全局变量

三. Block的分类 NSGlobalBlock、NSMallocBlock、NSStackBlock

image.png

NSGlobalBlock 调用copy 还是 NSGlobalBlock
NSStackBlock 调用copy 变成 NSMallocBlocl
NSMallocBlock 调用copy 还是 NSMallocBlock 只不过是引用计数加一

  • NSGlobalBlock

    void (^globalBlock)(void) = ^{
          
      };
      NSLog(@"%@",globalBlock);
      <__NSGlobalBlock__: 0x107bf6060>
    

    存储在静态区

  • NSMallocBlock

    __block int a = 10;
      void (^mallocBlock)(void) = ^ {
          a++;
      };
      mallocBlock();
      NSLog(@"%@",mallocBlock);
    

    存在堆区

  • NSStackBlock

    int a = 10;
    NSLog(@"%@",^{
          NSLog(@"%d",a);
      });
    

    存储在栈区

四. 解决循环引用的三种方式

4.1 __weak 并使用 strong引用

self.string = @"helloWorld";
__weak typeof(self)weakSelf = self;
    
self.blockName = ^{
    __strong typeof(weakSelf)strongSelf = weakSelf;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",strongSelf.string);
    });
    
};
self.blockName();

此种方式会延迟delloc的执行时间,但是能够保证strongSelf.string正常输出,输出顺序为
helloWorld
delloc
如果不用__strong强引用weakSelf的话,weakSelf.string的输出会为nill,输出顺序为delloc
null

4.2 __block

self.string = @"helloWorld";
__block XSYViewController *vc = self;
    self.blockName = ^{
        NSLog(@"%@",vc.string);
        vc = nil;
    };
self.blockName();

因为__block修饰符会截获vc的指针,self->block->vc->self。形成了死循环,但是vc置为nil,环被打开,所以此方式可以防止死循环

4.3 传参数

self.blockName = ^(XSYViewController *vc) {
        NSLog(@"%@",vc.string);
    };
self.blockName(self);

五. block底层源码分析

5.1 没有截获自动变量时的Block

block的内存布局


image.png

void (^blockName)(void) = ^{
    printf("hello world");
}
blockName();

NSGlobalBlock源码分析:

struct __block_impl {
  void *isa;           //
  int Flags;           //
  int Reserved;        //
  void *FuncPtr;       //
};

// 
struct __main_block_impl_0 {
  struct __block_impl impl;,
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

/*
Block语法转换的C语言函数
^ {printf("hello word");};的源码
*/
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
   printf("hello word");
}


static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

// block的执行过程
int main() {

  /*
struct __main_block_impl_0 *tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blockName = &tmp;
描述:该源代码将__main_block_impl_0结构体类型的自动变量,即栈上生成的__main_block_impl_0结构体实例的指针,赋值给__main_block_impl_0结构体指针类型的变量blackName。一下为这部分代码对应的最初源代码:
void (^blockName)(void) = ^{
        printf("hello world");
     };
这句话也可描述为:将Block语法生成的Block赋值给Block类型的变量blockName。它等同于将__main_blocl_impl_0结构体实例的指针赋值给变量__main_blocl_impl_0结构体类型的变量blockName。
*/ 
  void(*blockName)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
  
  /*
  blockName() 的源码, 去掉转换部分(*blockName->impl.FuncPtr)(blockName);
  */ 
  ((void (*)(__block_impl *))((__block_impl *)blockName)->FuncPtr)((__block_impl *)blockName);
  return 0;
}

5.2 截获自动变量时的Block

int a = 10;
int (^block)(void) = ^{
    NSLog(@"%d",a);
};
block();

六. “id”这一变量类型用于存储Objective-C对象。

id 为objc_object结构体的指针类型。

typedef struct objc_object {
    Class isa;
} id;

Class为objc_class结构体的指针类型

typedef struct objc_class *Class;

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

objc_object 结构体和objc_class结构体归根到底是在各个对象和类的实现中使用的最基本的结构体。下面我们通过编写简单的Objective-C类声明来确认下:

@interface MyObject : NSObject
{
    int val0,
    int val1,
}
基于objc_object结构体,该类的对象的结构如下:
struct MyObject {
    Class isa;
    int val0;
    int val1;
}

对象都是结构体实例:
MyObject类的实例变量val0和val1被直接声明为对象的结构体成员。“Objective-C中由类生成对象”意味着,像该结构体这样“生成由该类生成的对象的结构体实例”。生成的各个对象,即由该类生成的对象的各个结构体实例,通过成员变量isa保持该类的结构体实例指针。如图:


image.png

5. 如何区分三种NSGlobalBlock、NSMallocBlock、NSStackBlock

6. 执行delloc方法会让自动释放池工作吗?

7. 自动释放池是每个对象都有的吗?

8. __strong 修饰 带有__weak修饰符的变量原理

9. 在block里改变基本类型的变量也需要添加__block

int a = 10
void(^block)(void) = ^ {
    a++; // 会报错
};
block()

原因:因为a在栈区,需要拷贝到堆区
有__block底层进行了地址的传递(指针传递)
没有__block底层进行了值的传递(值传递)

10.查看block的源码步骤

  1. cd XcodeDemo/
  2. mkdir testBlock
  3. cd testBlock/
  4. vim block.c
  5.  #include "stdio.h"
     int main() {
       void(^block)(void) = ^{
        printf("hello word");
     };
       block();
       return 0;
     }
    
  6. gcc block.c // 编译
  7. ./a.out // 执行
  8. clang -rewrite-objc block.c -o blockCPP.cpp // 用于指定输出(out)文件名
  9. 完成

block的copy操作,在堆上

  1. block作为返回值,在arc中会自动copy
  2. 当block被_strong指向是,在arc中自动copy
  3. enumerateObjectsUsingBlock:带有UsingBlock参数名的也会自动copy
  4. 作为gcd API的参数自动copy

被__block修饰的对象类型

  1. 当__block变量在栈上时不会对指向的类型产生强引用
  2. 当block变量被拷贝到堆上时
    会调用block内部的copy函数,
    copy内部会调用_block_objct_assing函数,
    _block_objct_assign 函数会根据指向对象的修饰符(_strong, __weak, __unsafe_unretain)做出响应的操作,形成强引用(retain)或弱引用(注意:这里仅限于ARC中时会retain,MRC时不会retain)
  3. 如果__block变量在堆中移除,
    会调用block内部的dispose函数,
    dispose函数内部会调用__block_object_dispose函数
    __block_object_dispose函数会自动释放指向的对象(release)

block的原理是什么?

带有自动变量的匿名函数
封装了函数调用的oc对象

__block 的作用是什么?有什么注意点

一旦使用了__block,会将变量包装成带有此变量的结构体。不管是int还是对象,都会产生内存管理。

为了修改变量的值

block的属性修饰词为什么是copy?调用block有哪些使用注意

block不经过copy,会存在于栈上或全局区,为了将block拷贝到堆上
避免循环引用

block在修改NSMutableArray时,需不需要添加__block?

不需要,只有在重新赋值的时候才需要。能不加尽量不加,因为会生成新的对象,耗费内存

参考文献

  • 《Objective-C高级编程》
  • 大厂核心block技术分享

你可能感兴趣的:(聊一聊Block(一))