Block的那些事

前言:

本来想写原创来着,因为为了印象深刻都是手动从头整理一遍资料,但是奈何宇神写的太详细,要是写原创他一定会把我拉出去重打八十大板,还让我卖个萌什么的,得不偿失啊。(向晨宇的博客-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的那些事_第1张图片

每次写block都很绕,语法很麻烦。好吧,再此记录一下。

a.作为方法时

- (void )testGlobalBlock:(NSString*) url
          success:(void (^)(BOOL isSuccess))success
          failure:(void (^)(BOOL isSuccess))failure;

b.作为成员变量进行声明时

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个区。否则在具体讲堆栈的时候怕大家会不理解。

Block的那些事_第2张图片

4.使用场景

  • 任务完成时回调处理
  • 消息监听回调处理
  • 错误回调处理
  • 枚举回调
  • 视图动画、变换
  • 排序


    二、Block原理

    1.Block的三种类型

    Block的那些事_第3张图片

    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);


    1.ARC下
    testARC[76639:507728] <__NSStackBlock__: 0x7fff54c9fbc8>
    testARC[76639:507728] <__NSMallocBlock__: 0x7fdaaae161c0>
    testARC[76639:507728] <__NSStackBlock__: 0x7fff54c9fbf8>
    testARC[76639:507728] <__NSGlobalBlock__: 0x10af60230>

    2.MRC下

    testARC[76639:507728] <__NSStackBlock__: 0x7fff54c9fba0>
    testARC[76639:507728] <__NSStackBlock__: 0x7fff54c9fc00>
    testARC[76639:507728] <__NSStackBlock__: 0x7fff54c9fbd0>
    testARC[76639:507728] <__NSGlobalBlock__: 0x10af60130>
    

    3.判断原则

    a.ARC下判断原则

    Block的那些事_第4张图片

    b.MRC下判断原则

    Block的那些事_第5张图片

    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

    Block的那些事_第6张图片

    现在有RequestObjectXXNetEngine两个文件.

    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.优缺点

    Block的那些事_第7张图片

    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);
            }
        });
    }

    2.我们在ViewController中做如下处理(1)

    - (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秒已到


    3.我们在ViewController中做如下处理(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天天在用,你确定真的已经了解他了么~ 希望这篇文章对你有用。

    感谢观看,学以致用更感谢.





  • 你可能感兴趣的:(ios,block)