iOS编程基础-OC(十五)-块的内存管理和使用

该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!


     第15章 块

     

   

     15.3 块的内存管理

     

     在运行程序时,块常量表达式会获得栈内存;

        也因此,会拥有与局部变量相同的声明周期;

        如果想在定义范围之外使用块常量表达式的话,就必须复制他们到永久存储区域中(即堆);

        例如,如果你想要从方法获得类型为块常量的返回值或者存储块常量,就必须将块复制到堆中并在不使用它们时释放这些块;

        否则的话,可能会产生导致程序崩溃的悬挂指针;

     

     示例:在块常量的词汇范围之外使用它

  

  void (^greetingBlock)(void);
    {//范围开始,将局部变量压入栈(注意这里入栈的是块常量表达式,下边出栈的也是他;而不是greetingBlock这个块变量)
        greetingBlock = ^{
            NSLog(@"Greeting");
        };
    }//范围终点,从栈中弹出变量
    greetingBlock();

     

     OC为块常量的内存管理提供了复制(Block_copy())和释放(Block_release())命令;

        使用Block_copy()命令可以将块常量复制到堆中;

            就像实现了一个将块常量引用作为输入参数并返回相同类型块常量的函数;

        使用Block_release()命令可以释放堆中的块常量;

            就行实现了一个将块常量引用作为输入参数的函数;

            只有当堆中的块引用与块常量对应时,才能从堆中释放块常量引用;否则该函数不会有效果;

        为了避免内存泄漏,Block_copy()和Block_release()命令必须平衡;

     

     Foundation框架提供了处理块的copy和release方法,他们拥有与Block_copy()和Block_release()函数相同的功能;

     

     示例:使用块的复制和释放方法

     void (^greetingBlock1)(void);

     {

         greetingBlock1 = [^{

             NSLog(@"Greeting1");

         } copy];

     }

     greetingBlock1();

     [greetingBlock1 release];

    

        这是在MRC在需要做的;在ARC下,只要块没有返回id类型值或将id类型值用作参数,编译器就会自动执行块的复制和释放操作;

        否则,就必须执行复制和释放操作的方式;

     

     示例:对使用id类型参数的块调用Block_copy()和Block_release()函数

     void (^greetingBlock2)(id solution);

     {

         greetingBlock2 = Block_copy( ^(id solution){

             NSLog(@"Greeting1 %@",solution);

         });

     }

     greetingBlock2(@"flower");

     Block_release( greetingBlock2);

    

     注意:其实这个两个函数也是在MRC下调用的;

     

     __block的不同语义:

        MRR下:

            如果块常量中使用__block变量,那么这些__block变量并不会保留;

        ARC下:

            如果块常量中使用__block变量,那么这些__block变量就会保留;

        这意味着你在使用ARC时不想保留__block变量(如避免循环引用),还应对变量应用__weak存储类型修改符;

     

     我们已经接触了__block和__weak两个存储类型修改符,相应的还有__strong;

     

     15.4 使用块

     

     已经介绍了块语法和一些语义以及内存管理的关键点;

     接下来介绍使用块的方式;

        将块和GCD一同使用(17章将详细介绍GCD),为并行编程提供支持;

        通常,块用于实现小型的、用于封装任务单元的独立代码段;

            他们通常以并行方式(如GCD)执行集合中的多个项目,或者被用作完成操作后的回调函数;

        块还可以使有关联的代码被放在一起,而不会将这些代码分割存储在不同的文件中;

     

     我们来看看块经常使用的情况;

     

     15.4.1 使用块为数组排序

     

     示例:BlockArraySorter  

   long ArrayElements = 10;
    //创建一个含有随机数(0~99)的数组
    NSMutableArray * numbers = [NSMutableArray arrayWithCapacity:ArrayElements];
    for (int elem = 0; elem < ArrayElements; elem++) {
        unsigned int value = arc4random() % 100;
        [numbers addObject:[NSNumber numberWithUnsignedInteger:value]];
    }
    NSLog(@"numbers:%@",numbers);
    //以升序方式为数组数值排序
    [numbers sortUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
        if ([obj1 integerValue] > [obj2 integerValue]) {
            return (NSComparisonResult)NSOrderedDescending;
        }
        if ([obj1 integerValue] < [obj2 integerValue]) {
            return (NSComparisonResult)NSOrderedAscending;
        }
        return (NSComparisonResult)NSOrderedSame;
    }];
    NSLog(@"sorted numbers:%@",numbers);

     log:

     2017-12-28 14:16:33.395978+0800 精通Objective-C[44631:11171308] numbers:(

     30,

     86,

     79,

     18,

     9,

     56,

     27,

     18,

     15,

     66

     )

     2017-12-28 14:16:33.396128+0800 精通Objective-C[44631:11171308] sorted numbers:(

     9,

     15,

     18,

     18,

     27,

     30,

     56,

     66,

     79,

     86

     )


     分析:

     sortUsingComparator方法接受的是一个NSComparator的块类型;块类型的返回值是NSComparisonResult枚举类型,用于指明请求中项目的顺序;

        NSOrderedAscending

        NSOrderedDescending

        NSOrderedSame

     这个块常量表达式比较了数组元素的值,并返回了以升序排列数组元素的相应NSComparisonResult枚举值;

     

     很多Foundation框架的集合类都含有使用块常量排序其中元素的方法;

     

     接下来再来实践一个使用块实现异步回调函数的程序;

     

     15.4.2 使用块加载URL

     

     示例:BlockURLLoader

  

  NSString * IndexURL = @"http://www.wikipedia.com/index.html";
    //为连接获取当前的运行循环
    NSRunLoop * loop = [NSRunLoop currentRunLoop];
    BOOL __block downloadComplete = NO;
    //创建请求
    NSURLRequest * request = [NSURLRequest requestWithURL:[NSURL URLWithString:IndexURL]];
    [NSURLConnection sendAsynchronousRequest:request
                                       queue:[NSOperationQueue currentQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
                                           if (data == nil) {
                                               NSLog(@"connectionError:%@",[connectionError localizedDescription]);
                                           }else{
                                               NSLog(@"\nDownload %lu bytes from request %@",[data length],[request URL]);
                                           }
                                           downloadComplete = YES;
                                       }];
    
    //一直循环直到完成加载资源的操作为止
    while (!downloadComplete && [loop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) {
        //保证程序一直运行直到完成加载资源的操作为止
        //之前也有用过这条语句:总而言之,它会运行循环,接收输入源的事件,并执行所有相应的委托或回调方法,直到连接完成加载资源的操作为止;
    }


     log:

     Download 1242 bytes from request http://www.wikipedia.com/index.html


     很多框架和第三方API都使用了块常量执行回调函数的方法,熟练使用这种方式很有必要;

     

     下面我们编写一个使用块实现并行操作的程序;

     

     15.4.3 使用块的并行编程方式

     

     示例:本应用使用GCD,以并行方式执行通过块定义的过个任务;

     我们新建一个类C15BlockConcurrentTasks来实践这部分内容;

     

     (Code C15BlockConcurrentTasks)

#import "C15BlockConcurrentTasks.h"

#define YahooURL @"http://www.yahoo.com/index.html"
#define ApressURL @"http://www.apress.com/index.html"

typedef void (^DownloadURL)(void);

@implementation C15BlockConcurrentTasks

DownloadURL getDownloadURLBlock(NSString * url){
    NSString * urlString = url;
    return ^{
        NSURLRequest * request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]];
        NSError * error;
        NSDate * startTime = [NSDate date];
        NSData * data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&error];
        if (data == nil) {
            NSLog(@"Error %@",[error localizedDescription]);
        }else{
            NSDate * endTime = [NSDate date];
            NSTimeInterval timeInterval = [endTime timeIntervalSinceDate:startTime];
            NSLog(@"Time takes %f seconds,for url %@",timeInterval,urlString);
        }
    };
}

-(void)testBlockConcurrentTasks{
    //创建任务请求
    dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_queue_t queue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    if (queue1 == queue2) {
        NSLog(@"=========");
    }
    //创建任务分组
    dispatch_group_t group = dispatch_group_create();
    
    //获取当前时间
    NSDate * startTime = [NSDate date];
    
    //创建并分派异步任务
    dispatch_group_async(group, queue1, getDownloadURLBlock(YahooURL));
    dispatch_group_async(group, queue2, getDownloadURLBlock(ApressURL));

    //等待,直到分组中的所有任务完成为止
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    NSDate * endTime = [NSDate date];
    NSTimeInterval timeInterval = [endTime timeIntervalSinceDate:startTime];
    NSLog(@"Time takes %f seconds,for all urls",timeInterval);
}

@end

  

    [[C15BlockConcurrentTasks new] testBlockConcurrentTasks];


     log:

     2017-12-28 17:08:27.412710+0800 精通Objective-C[46079:11385262] =========

     2017-12-28 17:08:28.932882+0800 精通Objective-C[46079:11385382] Time takes 1.519951 seconds,for url http://www.yahoo.com/index.html

     2017-12-28 17:08:30.160462+0800 精通Objective-C[46079:11385374] Time takes 2.747524 seconds,for url http://www.apress.com/index.html

     2017-12-28 17:08:30.160709+0800 精通Objective-C[46079:11385262] Time takes 2.747868 seconds,for all urls


     分析:

        创建了两个全局的并发分派队列(其实就是一个),用以并行方式执行任务;

        创建一个任务组,用于对任务进行编组,为执行异步操作对任务进行排队、等待已排队的任务组等;

        使用dispatch_group_async(group, queue1, getDownloadURLBlock(YahooURL));将两个任务分派给这个任务组;

        被执行的任务是通过C函数获取的块常量设置的;

     

     可以看到,以并行方式执行这些任务所消耗的实践比以异步方式所消耗的时间更少;

     我们将在第17章,详细介绍GCD实现并行操作的编程方式;

     

     15.5 小结

     

     本章介绍了使用块编写程序的方式,块是OC语言提供的一种强大特性;

        具体讨论了块的语法和语义、块的内存管理、使用块编写程序的方式和如何使用现有API中的块;

     

     1)块和函数与方法类似,但块除了是可执行代码,还含有与栈内存和堆内存绑定的变量;

        块就是一个闭包,一个允许访问其常规范围之外变量的函数;

     2)可以将块声明为块类型的变量,在可以使用普通变量的地方就可以使用块对象;

        如作为函数/方法的参数等;

     3)块常量表达式含有调用块时执行的代码;

        可以通过内嵌的方式定义块常量表达式,然后将块用作函数/方法调用的参数;

     4)与C语言相比,通过词汇范围、__block变量、实例变量访问和常数导入,块参数具有更好的可见性;

     5)运行程序时,块常量表达式会获得栈内存,因而拥有与局部变量一样的生命周期;

        因此,需要将他们复制到永久存储区(即堆内存),才能在定义它们的范围之外使用它们;

     6)使用MRR时,使用Block_copy()和Block_release()命令可以复制和释放堆内存中的块;

        使用ARC时,编译器能自动执行复制和释放块的操作(对id上不明确,使用时请避开);

     7)通常,块用于实现小型的、用于封装任务单元的独立代码段;

        通常以并行的方式执行集合中的多个项目,或者被用作完成操作后的回调函数;

        因为可以通过内嵌方式定义块,所以不用为上下文关联的代码(如异步完成处理程序)创建完全独立的类和方法;

     

     so,你应该仔细学习本章,熟练掌握块的基本使用。

     


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值