1、什么是block
block是将函数及其执行上下文封装起来的对象。
block调用实际就是函数调用。
2、变量截获
1、不截获全局变量和静态全局变量。
2、对于基础数据类型的局部变量,截获其值。
3、对于对象类型的局部变量,连同其所有修饰符一同截获。
4、以指针的形式截获局部静态变量。
cd 到文件所在目录后
执行 clang -rewrite-objc HFBlockObject.m,可查看编译后的c++代码,帮助理解。
若代码中使用了__weak,则执行
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 HFBlockObject.m
// 全局变量
int varIntOne = 100;
NSString *varStringOne = @"testOne";
// 全局静态变量
static int varStaticIntOne = 101;
static NSString *varStaticStringOne = @"testStaticOne";
@implementation HFBlockObject
- (void)testCaptureVariable {
// 局部变量
int tempA = 200;
NSString *tempOne = @"tempOne";
__weak NSString *tempWeakString = @"weakString";
// 局部静态变量
static int tempStaticA = 201;
static NSString *tempStaticOne = @"tempStaticOne";
void (^blockOne)(void) = ^ {
NSLog(@"varIntOne = %d",varIntOne);
NSLog(@"varStaticIntOne = %d",varStaticIntOne);
NSLog(@"tempA = %d",tempA);
NSLog(@"tempStaticA = %d",tempStaticA);
NSLog(@"varStringOne = %@",varStringOne);
NSLog(@"varStaticStringOne = %@",varStaticStringOne);
NSLog(@"tempOne = %@",tempOne);
NSLog(@"tempStaticOne = %@",tempStaticOne);
NSLog(@"tempWeakString = %@",tempWeakString);
};
blockOne();
}
简化一下编译后的block是一个struct类型的对象
struct __HFBlockObject__testCaptureVariable_block_impl_0 {
struct __block_impl impl;
struct __HFBlockObject__testCaptureVariable_block_desc_0* Desc;
int tempA;
int *tempStaticA;
NSString *__strong tempOne;
NSString *__strong *tempStaticOne;
NSString *__weak tempWeakString;
};
block
基础成员:
结构体__block_impl
结构体__HFBlockObject__testCaptureVariable_block_desc_0
其他成员:截获的变量
struct __block_impl {
void *isa; // 指针
int Flags;
int Reserved;
void *FuncPtr;// 指向block函数的指针,指向执行代码的位置
};
static struct __HFBlockObject__testCaptureVariable_block_desc_0 {
size_t reserved;
size_t Block_size; //block占的内存大小
}
3、blcok的三种类型
全局block(GlobalBlock) - 存储在全局区
block内部使用的变量只能是局部或全局)静态变量和全局变量,不能是外部的局部变量或属性。
执行copy- - - >无效果 - 什么也没做
栈block(MallocBlock) - - 存储在栈区-
block内部使用了外部局部变量或属性,但没有将这个block赋值给强引用或用copy修饰的变量。
执行copy- - - >被拷贝到堆区
堆block(StackBlock) - -存储在在堆区 -
block内部使用了局部局部变量或属性,并将这个block赋值给强引用或用copy修饰的变量。
执行copy- - - >增加引用计数
如果一个对象的成员变量是block,若将栈上的block赋值给这个成员变量
若此成员变量没有使用copy关键字,当通过成员变量访问此block时,可能因为栈上内存被释放,导致访问异常,引起崩溃。使用copy关键字后,会复制到堆区,之后访问不会异常。
栈上block的销毁:变量作用域结束之后就被销毁
需要管理的是堆区block的内存。
4、__block
一般情况下,对被截获变量进行赋值(不等于使用)操作时需要加__block修饰符。
对变量进行赋值时
对于局部变量(基本数据类型/对象类型)修改时需要__block修饰符
对于静态全局变量/全部变量/静态局部变量修改时不需要__block修饰符
// 局部变量
__block int tempA = 200;
再次clang 后看源码
tempA 被__block修饰后变为结构体对象,temp变成这个新对象的成员变量。
struct __Block_byref_tempA_0 {
void *__isa; // isa指针,表示这是一个对象
__Block_byref_tempA_0 *__forwarding;//指向自身的指针
int __flags;
int __size;
int tempA; //变量值
};
__block 修饰后局部变量可以修改的原因是,从原来的传递数值,变成了传递指针,因此可以在block内部修改(对比两个包含方法入口的截图中tempA就可发现)。
__forwarding指针
栈上block的__forwarding指针指向的是它copy出的堆上的__block变量,
若栈上的block无copy,它的__forwarding指针指向的还是这个栈上的__block变量。
__forwarding存在的意义:无论做任何内存位置,都可以顺利访问到同一个__block变量
__block MRC/ARC下的区别
MRC下不会产生循环引用,ARC下会产生循环引用产生内存泄漏。
ARC下的循环引用:
上面的解决方案是在return之前设置blockSelf = nil,就断开了__block变量和对象之前的持有连结。
但是这样解决弊端是:如果这个block不被调用,这个循环就会一直存在。
5、循环引用
产生原因:
block是vc的属性,那么vc就强持有block。若block内使用vc其他属性,就强持有vc,这样就产生了循环引用。
解决方法:
1、weak - strong
2、self当作参数传递给block
这样self就相当于局部变量,block执行结束后会自动销毁。这种操作不涉及到捕获变量。
6、下面代码会有什么问题
void(^__weak weakBlock)(void) = nil;
- (void)viewDidLoad {
[super viewDidLoad];
[self blockStackA];
//执行weakBlock的时候崩溃
weakBlock();
}
-(void)blockStackA {
int a = 0;
int b = 2;
// weakBlock1存在栈区
void(^__weak weakBlock1)(void) = ^ {
NSLog(@"b - - - = %d",b);
};
a = b;
// block是引用类型,赋值是多个指针指向栈区block
weakBlock = weakBlock1;
NSLog(@"weakBlock = %@, weakBlock1 = %@",weakBlock,weakBlock1);
NSLog(@"weakBlock = %p, weakBlock1 = %p",&weakBlock,&weakBlock1);
//栈区的block在函数作用域内可正常执行,出了函数作用域,就被释放。
//可正常执行
weakBlock();
}
7、block 访问宏会循环引用吗?
不会。
宏是预编译的时候被替换,不是全局静态变量。
8、常见的第三方库:Masonry、MJRefresh等,使用self,会不会产生强引用?
Masonry不会,MJRefresh调用内部方法不会。内部使用了弱引用(weak)。