block是在iOS开发中用的比较多的,我对block的理解也不是很透彻,学习一下进行分析分析。
Block
1、block有几种?
一般我们能用到的知道的block有三种:
- NSGlobalBlock (全局block )
- NSStackBlock (栈block )
- NSMallocBlock(堆block)
但是其实block总共有6种,剩下的3种是系统级别的:
通过查看源码 libclosure-73 -> data.c文件可以知道
void * _NSConcreteStackBlock[32] = { 0 }; 栈block
void * _NSConcreteMallocBlock[32] = { 0 }; 堆block
void * _NSConcreteAutoBlock[32] = { 0 };
void * _NSConcreteFinalizingBlock[32] = { 0 };
void * _NSConcreteGlobalBlock[32] = { 0 }; 全局block
void * _NSConcreteWeakBlockVariable[32] = { 0 };
常用到的三种block验证
1.1、 堆block
访问外部变量的时候 堆block
int a = 10;
void (^block)(void)=^{
NSLog(@"1233 - %d",a);
};
block();
NSLog(@"%@",block);
// <__NSMallocBlock__: 0x600002d6a0d0> 堆block
1.2、全局block
不访问外部变量 全局block
void (^block)(void)=^{
NSLog(@"1233");
};
block();
NSLog(@"%@",block);
// <__NSGlobalBlock__: 0x10ade6150> 全局block
1.3、栈block
堆block copy之前是栈block
NSLog(@"%@",^{
NSLog(@"1233 - %d",a);
});
//<__NSStackBlock__: 0x7ffeeeed41b8>
全局block copy之前还是全局block
NSLog(@"%@",^{
NSLog(@"1233");
});
//<__NSGlobalBlock__: 0x10a356190>
注:上面所说的copy 与 copy之前的解释如下。
1、
void (^block)(void)=^{
NSLog(@"1233");
};
block();
NSLog(@"%@",block);
//这个地方打印的 block = ^{ }; 这个 =(等号)就是copy了一份,所以这样打印是打印的copy之后的block
2、
NSLog(@"%@",^{
NSLog(@"1233");
});
//这样直接打印 ^{ }; 这样打印是没有copy 的block。
2、block的用法
1、block作为返回值的链式传参
masonry
mark.width.equal(100); 链式编程
mark.left.top.height.width.mas_equalTo(100);
不断的进行调用,同时还能传值。这个地方block 做为返回值。
masonry mas_equalTo 封装了equalTo()
- (MASConstraint * (^)(id))equalTo 带了一个返回值id
这样做的目的是可以在 .mas_equalTo(100) 之后继续追加操作,例如:make.top.equalTo(loginView.mas_top).with.offset(0);
2、block 参数
函数式 可以设计成 y = f(x) - y=f(f(x)) 传的是一个表达式 封装block比较灵活
响应式编程 RAC
3、循环引用
#import "LJLBlockViewController.h"
@protocol LJLDelegate <NSObject>
-(void)showName;
@end
typedef void(^LJLBlock)(LJLBlockViewController *);
@interface LJLBlockViewController ()
@property(nonatomic, assign) int age;
@property(nonatomic, copy) LJLBlock ljlBlock;
@property(nonatomic, copy) NSString * name;
@property(nonatomic, strong) NSObject * obj;
@property(nonatomic, weak) id <LJLDelegate>delegate;
@end
这个地方不构成循环引用,但是如果嵌套层次深的话就会有问题。
详见 RAC weakly
self.name = @"LJL";
[UIView animateWithDuration:1 animations:^{
NSLog(@"%@",self.name);
}];
1、强弱共舞(中介者)
中介者模式
持有情况说明 stongWeakSelf -> weakSelf -> self -> ljlBlock -> stongWeakSelf
weakSelf加在了弱引用表中,持有了self(weakSelf弱引用,引用计数不处理,只是指针指向)
因为self 引用计数不处理,所以退出的时候会调用析构函数dealloc ,self会置为nil。
一般情况下会直接使用weakSelf即可,但是如下代码中加了一个dispatch_after 的等待,如果进入到该页面直接返回,这个时候如果到该页面后2秒内直接返回的话,先调用了dealloc 析构,这个时候self先释放了,那么weakSelf也就为nil了,那么weakSelf.name就成null了,提前释放了。为了处理这个问题需要再强引用一下weakSlef。由于有stongWeakSelf持有weakSelf,所以在self 释放的时候,weakSelf并不会立即释放,而是等stongWeakSelf 释放的时候才被置为nil。这样就能正常方访问name了。
// strong-weak-dance 强弱共舞
self.name = @"LJL";
__weak typeof(self) weakSelf = self;
self.ljlBlock = ^(LJLBlockViewController * vc) {
// 2秒后调度
__strong typeof(weakSelf) stongWeakSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"stongWeakSelf.name = %@",stongWeakSelf.name);
});
};
self.ljlBlock(nil);
2、中介者
中介者模式,用一个中介者来代替self,用完之后手动置为nil。
持有关系: VC -> self -> block -> VC
self.name = @"LJL";
__block LJLBlockViewController * vc = self;
self.ljlBlock = ^(LJLBlockViewController * myVC) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
vc = nil;
});
};
self.ljlBlock(nil);
3、传参
通过传参的形式,这样就不构成持有
传参过来的值是在内部进行了copy,在block释放的时候就会释放。
self.name = @"LJL";
self.ljlBlock = ^(LJLBlockViewController * myVC) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",myVC.name);
});
};
self.ljlBlock(self);
扩展:
1、NSObject
这个地方的obj不能用weak修饰,obj创建出来之后因为没有人使用所以引用计数不变就会直接释放掉了,没有持有关系,这个时候打印出来的结果就是null。
那为什么xib 拖过来的变量是用weak 修饰的呢?因为xib 已经对这个变量持有,所以拖过来之后的要使用弱引用。
当然了,在底层分析的时候会发现(后面说到埋点的时候会遇到)NSObject有用weak修饰的,因为会造成循环引用互相持有,所以具体用什么修饰需要根据实际情况来确定。
self.obj = [[NSObject alloc] init];
NSLog(@"%@",self.obj);
2、delegate 需要使用weak
weak 和 assign 区别:
weak在释放的时候会自动置为nil assign 不会置为nil,所以assign一般用来修饰基本数据类型。因为基本数据类型一般分配在栈上,栈的内存由系统自动处理。如果用来修饰其他会出现释放后不置为nil,存在野指针。
self -> delegate -> self 如果不使用weak 就会造成循环引用,所以必须self 不持有delegate
self --> delegate -> self