一. 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
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的内存布局
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保持该类的结构体实例指针。如图:
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的源码步骤
- cd XcodeDemo/
- mkdir testBlock
- cd testBlock/
- vim block.c
#include "stdio.h" int main() { void(^block)(void) = ^{ printf("hello word"); }; block(); return 0; }
- gcc block.c // 编译
- ./a.out // 执行
- clang -rewrite-objc block.c -o blockCPP.cpp // 用于指定输出(out)文件名
- 完成
block的copy操作,在堆上
- block作为返回值,在arc中会自动copy
- 当block被_strong指向是,在arc中自动copy
- enumerateObjectsUsingBlock:带有UsingBlock参数名的也会自动copy
- 作为gcd API的参数自动copy
被__block修饰的对象类型
- 当__block变量在栈上时不会对指向的类型产生强引用
- 当block变量被拷贝到堆上时
会调用block内部的copy函数,
copy内部会调用_block_objct_assing函数,
_block_objct_assign 函数会根据指向对象的修饰符(_strong, __weak, __unsafe_unretain)做出响应的操作,形成强引用(retain)或弱引用(注意:这里仅限于ARC中时会retain,MRC时不会retain) - 如果__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技术分享