前言:
本来想写原创来着,因为为了印象深刻都是手动从头整理一遍资料,但是奈何宇神写的太详细,要是写原创他一定会把我拉出去重打八十大板,还让我卖个萌什么的,得不偿失啊。(向晨宇的博客-Blcok)
这篇文章主要是用来学习Block的原理和一些Block的用法,相信有很多小伙伴每天都在用Block,但是对于它在MRC和ARC中的运行模式,包括Block在内存中的位置并不是很清楚,所以咱还是赶紧看我摘取过来的资料吧。
全文的七段文字:
1.Block入门
2.Block原理
3.利用Block实现一对多
4.Delegate、Notification、Block 的对比
5.测试
6.Demo下载
7.参考资料
正题:
一、Block入门
1.概念
Block是iOS4.0+ 和Mac OS X 10.6+ 引进的对C语言的扩展,用来实现匿名函数的特性
闭包是一个能够访问其他函数内部变量的函数.
2.基本用法
每次写block都很绕,语法很麻烦。好吧,再此记录一下。
a.作为方法时
- (void )testGlobalBlock:(NSString*) url success:(void (^)(BOOL isSuccess))success failure:(void (^)(BOOL isSuccess))failure;
typedef void (^SUCCESSBLOCK)(BOOL isSuccess); typedef void (^FAILEDBLOCK)(BOOL isSuccess); @interface XXEngine() @property(nonatomic,copy) SUCCESSBLOCK successBlock; @property(nonatomic,copy) FAILEDBLOCK failedBlock; @end
c.写在函数内部
void (^_block)() = ^{ _a = 10; };
3.内存的五个区
这里先普及一个基本的5个概念,内存的5个区。否则在具体讲堆栈的时候怕大家会不理解。
4.使用场景
二、Block原理
1.Block的三种类型
2.类型判断
如何判断在哪个区,我们分ARC和MRC两种情况
int i = 10; void (^block)() = ^{printf("%d",i);} ; __weak void (^weakBlock)() = ^{printf("%d",i);}; //被weak修饰之后还是stack void (^globalStack)() = ^{}; NSLog(@"%@", ^{printf("%d",i);}); NSLog(@"%@", block); NSLog(@"%@", weakBlock); NSLog(@"%@", globalStack);
testARC[76639:507728] <__NSStackBlock__: 0x7fff54c9fbc8> testARC[76639:507728] <__NSMallocBlock__: 0x7fdaaae161c0> testARC[76639:507728] <__NSStackBlock__: 0x7fff54c9fbf8> testARC[76639:507728] <__NSGlobalBlock__: 0x10af60230>
testARC[76639:507728] <__NSStackBlock__: 0x7fff54c9fba0> testARC[76639:507728] <__NSStackBlock__: 0x7fff54c9fc00> testARC[76639:507728] <__NSStackBlock__: 0x7fff54c9fbd0> testARC[76639:507728] <__NSGlobalBlock__: 0x10af60130>
3.判断原则
a.ARC下判断原则
b.MRC下判断原则
3.ARC与MRC区别
ARC引用外部对象会自动copy,MRC则不会。所以MRC要将栈对象变成堆对象只要执行一次copy即可。
3.循环引用
a.造成循环引用的实例
@implementation Person { int _a; void (^_block)(); } - (void)test { void (^_block)() = ^{ _a = 10; }; } @end此时_a在用clang编译后可以很明显看到有会self的身影,所以此时我们可以将其理解成self.a(便于记忆),此时Person持有block,block持有self,造成了循环引用.
b. 解决方式
- (void)test { __weak typeof(self) weakSelf = self; void (^_block)() = ^{ weakSelf->a = 10; }; }
3.系统Block
1.经验谈之GCD/UIView的系统动画的Block中,为何可以写self?
因为self并没有对其进行持有,循环引用的原理是两个对象之间相互有持有关系,现在仅仅是GCD持有self,但是self并没有持有GCD,所以是没有问题的。(当GCD对象为其成员变量时才具有持有的关系)
4._block原理
1.我们先看如下代码
void test() { __block int a; ^{ a = 10; }; }
2.用clang -re-write testBlock.c
命令后
看关键代码如下:
struct __Block_byref_a_0 { void *__isa; __Block_byref_a_0 *__forwarding; int __flags; int __size; int a; }; struct __test_block_impl_0 { struct __block_impl impl; struct __test_block_desc_0* Desc; __Block_byref_a_0 *a; // by ref __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __test_block_func_0(struct __test_block_impl_0 *__cself) { __Block_byref_a_0 *a = __cself->a; // bound by ref (a->__forwarding->a) = 10; } static void __test_block_copy_0(struct __test_block_impl_0*dst, struct __test_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);} static void __test_block_dispose_0(struct __test_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);} static struct __test_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(struct __test_block_impl_0*, struct __test_block_impl_0*); void (*dispose)(struct __test_block_impl_0*); } __test_block_desc_0_DATA = { 0, sizeof(struct __test_block_impl_0), __test_block_copy_0, __test_block_dispose_0}; void test() { __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0)}; ; ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344)); }
3.结论
我们观察Block_byref_a_0结构体,这个结构体中含有isa指针,所以也是一个对象,它是用来包装局部变量a的。当block被copy到堆中时,Persontest_block_impl_0的拷贝辅助函数Persontest_block_copy_0会将Block_byref_a_0拷贝至堆中,所以即使局部变量所在堆被销毁,block依然能对堆中的局部变量进行操作。其中Block_byref_a_0成员指针forwarding用来指向它在堆中的拷贝.
三、利用Block实现一对多
1.直接block
这里我直接copy一段AFNetworking
的代码,这个库写的太好了,大家有空可以参考一下
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method URLString:(NSString *)URLString parameters:(id)parameters uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress success:(void (^)(NSURLSessionDataTask *, id))success failure:(void (^)(NSURLSessionDataTask *, NSError *))failure { NSError *serializationError = nil; NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError]; if (serializationError) { if (failure) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{ failure(nil, serializationError); }); #pragma clang diagnostic pop } return nil; } __block NSURLSessionDataTask *dataTask = nil; dataTask = [self dataTaskWithRequest:request uploadProgress:uploadProgress downloadProgress:downloadProgress completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) { if (error) { if (failure) { failure(dataTask, error); } } else { if (success) { success(dataTask, responseObject); } } }]; return dataTask; }
2.利用对象做block
现在有RequestObject
和XXNetEngine
两个文件.
1.RequestObject
#import <Foundation/Foundation.h> typedef void (^ReqSuccessBlock)(BOOL isSuccess); typedef void (^FailedBlock)(BOOL isSuccess); @interface RequestObject : NSObject @property(nonatomic,copy) ReqSuccessBlock successBlock; @property(nonatomic,copy) FailedBlock failedBlock; @property(nonatomic,assign) NSInteger CMD; @property(nonatomic,assign) NSInteger seq; @end
2.XXNetEngine
#import "XXNetEngine.h" #import "RequestObject.h" @interface XXNetEngine() @property (nonatomic, strong) NSMutableDictionary *requestDict; @end @implementation XXNetEngine -(instancetype)init { if(self = [super init]){ self.requestDict = [NSMutableDictionary dictionary]; } return self; } +(instancetype) sharedInstance { static XXNetEngine* instance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[XXNetEngine alloc] init]; }); return instance; } -(void)requestData:(RequestObject*) reqObject successBlock:(void (^)(BOOL isSuccess))successBlock failureBlock:(void (^)(BOOL isSuccess))failureBlock { if ( reqObject && successBlock && failureBlock ) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ //requestSeq是一个随机数 int requestSeq = arc4random() % 100;; //网络操作 reqObject.successBlock = successBlock; reqObject.failedBlock = failureBlock; reqObject.seq = requestSeq; [self addRequestItem:reqObject seq:requestSeq]; //模拟一个2秒的网络操作 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self didReceiveData:reqObject.seq]; }); }); } } - (void) didReceiveData:(NSInteger) seq { RequestObject *request = [self getRequestItem:seq]; if ( request ) { [self removeRequestItem:seq]; dispatch_async(dispatch_get_main_queue(), ^{ if ( NULL != request.successBlock ) { request.successBlock(YES); } }); } } //用命令字做参数传递 - (RequestObject *) getRequestItem:(NSInteger)seq { RequestObject *request = nil; @synchronized(self) { NSNumber *numSeq = [NSNumber numberWithInteger:seq]; request = [self.requestDict objectForKey:numSeq]; } return request; } - (void) addRequestItem:(RequestObject *)request seq:(NSInteger)seq { @synchronized(self) { if ( request && (0!=seq) ) { NSNumber *numSeq = [NSNumber numberWithInteger:seq]; [self.requestDict setObject:request forKey:numSeq]; } } } - (void) removeRequestItem:(NSInteger)seq { @synchronized(self) { NSNumber *numSeq = [NSNumber numberWithInteger:seq]; [self.requestDict removeObjectForKey:numSeq]; } } @end
3.输出结果
2016-02-02 00:14:59.824 testARC[1232:45931] XXNetEngine successBlock
四、Delegate、Notification、Block 的对比
1.优缺点
2.宇神的个人见解
如果方法过多,一般大于3个的时候,用delegate,因为此时用block,代码将很难维护。比如UITableViewDelegate等等UI组建,都是用的delegate。其他方式用block。
Notification能不用的时候尽量不用,缺点太多明显,增加调试难度。在工程大的情况下,极其难以维护。当然有些情况下也是必不可少的,比如观察者模式.
五、测试
1.测试一
快来看看大家现在对Block的理解吧
Block测试题, 从唐巧大神博客看到的转载链接
2.测试二
现在ViewController中有一个XXEngine对象
1.XXEngine代码如下
typedef void (^SUCCESSBLOCK)(BOOL isSuccess); typedef void (^FAILEDBLOCK)(BOOL isSuccess); @interface XXEngine() @property(nonatomic,copy) SUCCESSBLOCK successBlock; @property(nonatomic,copy) FAILEDBLOCK failedBlock; @end @implementation XXEngine -(instancetype)init { if (self = [super init]) { NSLog(@"XXEngine init"); } return self; } -(void)dealloc { NSLog(@"XXEngine dealloc"); } - (void )testGlobalBlock:(NSString*) url success:(void (^)(BOOL isSuccess))success failure:(void (^)(BOOL isSuccess))failure { //设置时间 double delayInSeconds = 2.0; dispatch_time_t delayInNanoSeconds = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_after(delayInNanoSeconds, concurrentQueue, ^(void){ NSLog(@"网络操作完成"); if(success){ NSLog(@"%@",success); success(YES); } }); } - (void )testMallocBlock:(NSString*) url success:(void (^)(BOOL isSuccess))success failure:(void (^)(BOOL isSuccess))failure { self.successBlock = success; self.failedBlock = failure; //设置时间 double delayInSeconds = 2.0; dispatch_time_t delayInNanoSeconds = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_after(delayInNanoSeconds, concurrentQueue, ^(void){ NSLog(@"网络操作完成"); if(self.successBlock){ NSLog(@"%@",self.successBlock); self.successBlock(YES); } }); }
- (IBAction)testXXEngine:(UIButton *)sender { XXEngine *xxEngine = [[XXEngine alloc] init]; [xxEngine testGlobalBlock:nil success:^(BOOL isSuccess) { NSLog(@"2秒已到"); } failure:^(BOOL isSuccess) { NSLog(@"2秒结束"); }]; }此时的输出结果大家能想到吗?这里公布答案:
2016-02-01 23:10:24.100 testARC[699:16506] XXEngine init 2016-02-01 23:10:24.101 testARC[699:16506] XXEngine dealloc 2016-02-01 23:10:26.101 testARC[699:16615] 网络操作完成 2016-02-01 23:10:26.102 testARC[699:16615] <__NSGlobalBlock__: 0x102bbd110> 2016-02-01 23:10:26.102 testARC[699:16615] 2秒已到
- (IBAction)testXXEngineMalloc:(id)sender { XXEngine *xxEngine = [[XXEngine alloc] init]; [xxEngine testMallocBlock:nil success:^(BOOL isSuccess) { NSLog(@"2秒已到"); } failure:^(BOOL isSuccess) { NSLog(@"2秒结束"); }]; }此时的输出结果大家能想到吗?这里公布答案:
2016-02-01 23:11:16.416 testARC[699:16506] XXEngine init 2016-02-01 23:11:18.417 testARC[699:17079] 网络操作完成 2016-02-01 23:11:18.417 testARC[699:17079] <__NSGlobalBlock__: 0x102bbd190> 2016-02-01 23:11:18.417 testARC[699:17079] 2秒已到 2016-02-01 23:11:18.417 testARC[699:17079] XXEngine dealloc
4.结论
在(1)中,success是一个NSGlobalBlock对象,dispatch对success做了持有操作,GCD我们可以认为是一个系统单例对象,此时XXEngine虽然被释放了,但是success被GCD持有了一份,也就是引用计数+1,所以success这个block不会被释放。GCD持有的对象会被拷贝到堆中,现在GCD执行完成,堆中对象要进行释放,所以知道success完成后,GCD释放。Block可以回调的。
在(2)中,GCD对XXEngine进行了持有,也就是引用计数+1,此时ViewController执行完对XXEngine引用计数-1,但是还是无法释放XXEngine,等到GCD结束之后,对XXEngine引用计数-1,此时会进行XXEngine的dealloc
六、Demo下载
testBlock(可以看下呦)
七、参考资料
1.Delegate,Notification,Block
2.AFNetworking 3.0迁移指南
3.Block技巧与底层解析
4.谈Objective-C Block的实现
最后:终于在回家过年前整理完了,虽然是拿别人现成的资料,整理起来也是很费劲的,Block天天在用,你确定真的已经了解他了么~ 希望这篇文章对你有用。
感谢观看,学以致用更感谢.