• iOS block从零开始


    iOS block从零开始


    在iOS4.0之后,block横空出世,它本身封装了一段代码并将这段代码当做变量,通过block()的方式进行回调。

    block的结构

    先来一段简单的代码看看:

    
    void (^myBlock)(int a) = ^(int a){
            NSLog(@"%zd",a);
        };
    
        NSLog(@"旭宝爱吃鱼");
    
        myBlock(999);
    

    输出结果:

    2016-05-03 11:27:18.571 block[5340:706252] 旭宝爱吃鱼
    2016-05-03 11:27:18.571 block[5340:706252] 999

    下面我们解析一下:

    • void :返回的参数 void为没有返回值
    • (^myBlock):myBlock 为 block 的名称
    • (int a):此为参数列表
    • ^(int a):传入参数

    通过上面的简单介绍可以简单了解到block的结构那么下面便产生了四种格式的block。

    四种block

    有返回值无参:

    int (^myBlock)() = ^(){
    
            return 999;
    
        };
    

    有返回值有参:

    int (^myBlock)(int a) = ^(int a){
    
            NSLog(@"%zd",a);
            return a;
    
        };
    

    无返回值无参:

    void (^myBlock)() = ^(){
    
            NSLog(@"旭宝爱吃鱼");
    
        };
    

    无返回值有参:

    void (^myBlock)(int a) = ^(int a){
    
            NSLog(@"%zd",a);
    
        };
    

    block 捕获外界局部变量

    示例代码:

    int a = 10;
    
        void (^myBlock)() = ^(){
    
            NSLog(@"旭宝爱吃鱼");
            NSLog(@"%zd",a);
    
        };
    
        NSLog(@"旭宝爱吃鱼");
    
        myBlock();
    

    运行结果:

    2016-05-03 11:43:32.680 block[5406:713702] 旭宝爱吃鱼
    2016-05-03 11:43:32.681 block[5406:713702] 旭宝爱吃鱼
    2016-05-03 11:43:32.681 block[5406:713702] 10

    通过示例代码不难发现我们可以获取局部变量,那么改变局部变量是否也会改变block内的值呢。

    示例代码:

    int a = 10;
    
        void (^myBlock)() = ^(){
    
            NSLog(@"旭宝爱吃鱼");
            NSLog(@"%zd",a);
    
        };
    
        a = 20;
    
        NSLog(@"旭宝爱吃鱼");
    
        myBlock();
    

    运行结果:

    2016-05-03 11:47:07.669 block[5425:715861] 旭宝爱吃鱼
    2016-05-03 11:47:07.670 block[5425:715861] 旭宝爱吃鱼
    2016-05-03 11:47:07.670 block[5425:715861] 10

    正如结果显示,block内部的值是不会改变的,为什么呢????

    示例代码:

    int a = 10;
    
        a = 20;
    
        void (^myBlock)() = ^(){
    
            NSLog(@"旭宝爱吃鱼");
            NSLog(@"%zd",a);
    
        };
    
        NSLog(@"旭宝爱吃鱼");
    
        myBlock();
    

    运行结果:

    2016-05-03 11:49:19.749 block[5450:717309] 旭宝爱吃鱼
    2016-05-03 11:49:19.750 block[5450:717309] 旭宝爱吃鱼
    2016-05-03 11:49:19.750 block[5450:717309] 20

    情理之中的答案。
    之所以block内部的值不会改变是因为block copy了局部变量。

    这个问题解决后尝试在block中改变局部变量。
    很不幸失败了,当我在block内改变外界的局部变量时,报错了。
    可视化我想改变外界的局部变量我该怎么办呢???

    改变外界的局部变量

    示例代码:

    __block int a = 10;
    
    
        void (^myBlock)() = ^(){
    
            NSLog(@"旭宝爱吃鱼");
            NSLog(@"%zd",a);
    
            a = 20;
    
        };
    
        NSLog(@"旭宝爱吃鱼");
    
        myBlock();
    
        NSLog(@"%zd",a);
    

    运行结果:

    2016-05-03 11:55:02.736 block[5490:721033] 旭宝爱吃鱼
    2016-05-03 11:55:02.737 block[5490:721033] 旭宝爱吃鱼
    2016-05-03 11:55:02.737 block[5490:721033] 10
    2016-05-03 11:55:02.737 block[5490:721033] 20

    我们只需要在局部变量前加__block 即可。

    循环引用(我在前面的博客有所提及)

    block在iOS开发中被视作是对象,因此其生命周期会一直等到持有者的生命周期结束了才会结束。另一方面,由于block捕获变量的机制,使得持有block的对象也可能被block持有,从而形成循环引用,导致两者都不能被释放:

    @implementation CXObject
    {
       void (^_cycleReferenceBlock)(void);
    }
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        _cycleReferenceBlock = ^{ 
            NSLog(@"%@", self);   //引发循环引用
        };
    }
    
    @end
    

    遇到这种代码编译器只会告诉你存在警告,很多时候我们都是忽略警告的,这最后会导致内存泄露,两者都无法释放。跟普通变量存在block关键字一样的,系统提供给我们weak的关键字用来修饰对象变量,声明这是一个弱引用的对象,从而解决了循环引用的问题:

    __weak typeof(*&self) weakSelf = self;
    _cycleReferenceBlock = ^{ 
        NSLog(@"%@", weakSelf);   //弱指针引用,不会造成循环引用
    };
    

    使用block

    在block出现之前,开发者实现回调基本都是通过代理的方式进行的。比如负责网络请求的原生类NSURLConnection类,通过多个协议方法实现请求中的事件处理。而在最新的环境下,使用的NSURLSession已经采用block的方式处理任务请求了。各种第三方网络请求框架也都在使用block进行回调处理。这种转变很大一部分原因在于block使用简单,逻辑清晰,灵活等原因。接下来我会完成一次网络请求,然后通过block进行回调处理。这些回调包括请求完成、下载进度

    按照returnValue(^blockName)(parameters)的方式进行block的声明未免麻烦了些,我们可以通过关键字typedef来为block起类型名称,然后直接通过类型名进行block的创建:

    @interface CXDownloadManager: NSObject
    
    //block重命名
    typedef void(^CXDownloadHandler)(NSData * receiveData, NSError * error);
    typedef void(^CXDownloadProgressHandler)(CGFloat progress);
    
    - (void)downloadWithURL: (NSString *)URL parameters: (NSDictionary *)parameters handler: (CXDownloadHandler)handler progress: (CXDownloadProgressHandler)progress;
    
    @end
    
    @implementation CXDownloadManager
    {
        CXDownloadProgressHandler _progress;
    }
    
    - (void)downloadWithURL: (NSString *)URL parameters: (NSDictionary *)parameters handler: (CXDownloadHandler)handler progress: (CXDownloadProgressHandler)progress
    {
        //创建请求对象
        NSURLRequest * request = [self postRequestWithURL: URL params: parameters]; 
        NSURLSession * session = [NSURLSession sharedSession];
    
        //执行请求任务
        NSURLSessionDataTask * task = [session dataTaskWithRequest: request completionHandler: ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if (handler) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    handler(data, error);
                }); 
            }
        }];
        [task resume];
    }
    
    //进度协议方法
    - (void)URLSession:(NSURLSession *)session
         downloadTask:(NSURLSessionDownloadTask *)downloadTask 
        didWriteData:(int64_t)bytesWritten // 每次写入的data字节数  
       totalBytesWritten:(int64_t)totalBytesWritten // 当前一共写入的data字节数  
      totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite // 期望收到的所有data字节数  
    {   
        double downloadProgress = totalBytesWritten / (double)totalBytesExpectedToWrite;  
        if (_progress) { _progress(downloadProgress); }
    }  
    
    @end
    

    上面通过封装NSURLSession的请求,传入一个处理请求结果的block对象,就会自动将请求任务放到工作线程中执行实现,我们在网络请求逻辑的代码中调用如下:

    
    #define QQMUSICURL @"https://www.baidu.com/link?url=UTiLwaXdh_-UZG31tkXPU62Jtsg2mSbZgSPSR3ME3YwOBSe97Hw6U6DNceQ2Ln1vXnb2krx0ezIuziBIuL4fWNi3dZ02t2NdN6946XwN0-a&wd=&eqid=ce6864b50004af120000000656fe235f"
    [[CXDownloadManager alloc] downloadWithURL: QQMUSICURL parameters: nil handler ^(NSData * receiveData, NSError * error) {
        if (error) { NSLog(@"下载失败:%@", error) }
        else {
            //处理下载数据
        }
    } progress: ^(CGFloat progress) {
        NSLog(@"下载进度%lu%%", progress*100);
    }];
    

    总结

    block捕获变量、代码传递、代码内联等特性赋予了它多于代理机制的功能和灵活性,尽管它也存在循环引用、不易调试追溯等缺陷,但无可置疑它的优点深受码农们的喜爱。如何更加灵活的使用block需要我们对它不断的使用、探究了解才能完成

  • 相关阅读:
    ubuntu下配置Apache
    ubuntu 下配置Web服务器
    ubuntu 笔记一
    域名解析
    C# Enum,Int,String的互相转换
    C# 得到本机局域网IP地址
    C# 连接 SQLServer 及操作
    C# OpenFileDialog 使用
    如何解决 IntelliJ Idea 编译 Java 项目时,找不到包或找不到符号的问题?
    阿里巴巴 MySQL 数据库之 SQL 语句规约 (三)
  • 原文地址:https://www.cnblogs.com/xubaoaichiyu/p/5454557.html
Copyright © 2020-2023  润新知