OC高级编程iOS内存管理-第3章-GCD

2018-07-06  本文已影响164人  凡几多

3.1 Grand Central Dispatch(GCD)概要

3.1.1 什么是GCD

Grand Central Dispatch(GCD)是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级中实现。开发者只需要定义想要执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可统一管理,也可执行任务,这样就比以前的线程更有效率。
也就是说,GCD用我们难以置信的非常简洁的技术方法,实现了极为复杂繁琐的多线程编程,可以说这是一项划时代的技术。下面是使用了GCD源代码的例子,虽然稍显抽象,但从中也能感受到GCD的威力。

    dispatch_async(queue, ^{
        /*
         *长时间处理
         *例如AR用画像识别
         *例如数据库访问
         */
        
        /*
         *长时间处理结束,主线程使用该处理结果。
         */
        dispatch_async(dispatch_get_main_queue(), ^{
            /*
             *只在主线程可以执行的处理
             *例如用户界面刷新
             */
        });
    });

上面的就是在后台线程中执行长时间处理,处理结束时,主线程使用该处理结果的源代码。

    dispatch_async(queue, ^{

这仅有一行的代码表示让处理在后台线程中执行。

    dispatch_async(dispatch_get_main_queue(), ^{

这样,仅此一行代码就能够让处理在主线程中执行。
另外,大家看到脱字(caret)符号“^”就能发现,GCD使用了上一章讲到的“Blocks”,进一步简化了应用程序代码。

3.1.2 多线程编程

线程到底是什么呢?我们来温习一下。先看一下下面的Objective-C源代码。

    int main()
    {
          id o = [[MyObject alloc] init];
          [o execBlock];
          return 0;
    }

虽然调用了几个方法,但代码行基本上是按从上到下的顺序执行的。
该源代码通过编译器转换为如下的CPU命令列(二进制代码)。

000001ac:      b590 push {r4,  r7,  lr}
000001ae:  f240019c movw  r1,  :lower16:0x260-0x1c0+0xfffffffc
000001b2:      af01 add   r7,  sp,  #4
000001b4:  f2c00100 movt  r1,  :upper16:0x260-0x1c0+0xfffffffc
000001b8:  f24010be movw  r0,  :lower16:0x384-0x1c2+0xfffffffc
000001bc:  f2c00100 movt  r0,  :upper16:0x384-0x1c0+0xfffffffc
000001c0:     4479  add   r1,  pc
000001c2:     4478  add   r0,  pc
000001c4:     6809  add   r1,  [r1,  #0]
000001c6:     6800  add   r0,  [r0,  #0]
...

汇集CPU命令行和数据,将其作为一个应用程序安装到Mac或iPhone上。CPU从应用程序指定的地址开始,一个一个地执行CPU命令列。先执行lac的命令行push,接着向后移动,执行地址lae的命令行movw,这样不断循环下去。
由于一个CPU一次只能执行一个命令,不能执行某处分开的并列的两个命令,因此通过CPU执行的CPU命令列就好比一条无分叉的达到,其执行不会出现分歧。
这里所说的“一个CPU执行的CPU命令列为一条无分叉路径”即为“线程”。
多线程是指在软件或硬件上实现多个线程并发执行的技术。通俗讲就是在同步或异步的情况下,开辟新线程,进行线程间的切换,以及对线程进行合理的调度,做到优化提升程序性能的目的。

但是多线程编程是一种容易发生各种问题的编程技术。比如多个线程更新相同的资源会导致数据的不一致(数据竞争),停止等待时间的线程会导致多个线程相互持续等待(死锁),使用太多线程会消耗大量内存。入下图: 1437388-a23fa2505700b891.png
尽管多线程编程极易发生各种问题,但是呢也应当使用多线程编程,因为使用多线程编程可以保证应用程序的相应性能。
应用程序在启动时,通过主线程描绘用户界面、处理触摸屏幕的事件等。如果在该主线程中进行长时间的处理,如数据库访问等,就会妨碍主线程的执行(阻塞)。这样会妨碍主线程中被称为RunLoop的主循环的执行,从而导致不能更新用户界面、应用程序的画面长时间停滞等问题。
使用多线程编程,在执行长时间处理时仍可保证用户界面的响应性能。如下图 1437388-cc40417ee3176fe5.png

GCD大大简化了偏于复杂的多线程编程的源代码。

3.2 GCD的API

3.2.1 Dispatch Queue

苹果官方对GCD的说明:
开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue中。

dispatch_async(queue,^{
    /*
     * 想执行的任务
     */
});

上面的代码用了Block语法“定义想执行的任务”,通过dispatch_async函数“追加”赋值在变量queue的“Dispatch Queue中”。仅这样就可使指定的Block在另一线程中执行。

“Dispatch Queue”是什么呢?如其名称所示,是执行处理的等待队列。程序员通过dispatch_async函数等API,在Block语法中记述想执行的处理并将其追加到Dispatch Queue中。Dispatch Queue按照追加的顺序(先进先出FIFO,First-In-First-Out)执行处理。如下图: 1437388-b58eb356ba623900.png
另外在执行处理时存在两种Dispatch Queue,一种是等待现在执行中处理的Serial Dispatch Queue,另一种是不等待现在执行中处理的Concurrent Dispatch Queue。如下表:
Dispatch Queue 的种类 说明
Serial Dispatch Queue 等待现在执行中处理结束
Concurrent Dispatch Queue 不等待现在执行中处理结束
dispatch.png

举例来区分一下两种调度队列的区别:
有下面8个任务等待执行,

dispatch_async(queue,blk0)
dispatch_async(queue,blk1)
dispatch_async(queue,blk2)
dispatch_async(queue,blk3)
dispatch_async(queue,blk4)
dispatch_async(queue,blk5)
dispatch_async(queue,blk6)
dispatch_async(queue,blk7)

如果queue使用Serial Dispatch Queue,则同时执行的处理数只有一个,blk0执行结束才能执行blk1,blk1执行结束才能执行blk2,blk0-> blk1-> blk2-> blk3-> blk4-> blk5-> blk6-> blk7顺序执行。

如果queue使用Concurrent Dispatch Queue,这样不用等待现在执行中的处理结束,可以并行执行多个处理,但并行执行的处理数取决于iOS和OS X的CPU核数以及CPU负荷等当前系统状态。所谓“并发执行”,就是使用多个线程同时执行多个处理。 serialQueue.png

前面的代码如下表,在多个线程中执行Block。

线程0 线程1 线程2 线程3
blk0 blk1 blk2 blk3
blk4 blk6 blk5
blk7

如果才能得到这些Dispatch Queue?方法有两种。

3.2.2 dispatch_queue_create

说明dispatch_queue_create函数前先看一下Serial Dispatch Queue生成个数的注意事项。
如前所述,Concurrent Dispatch Queue 并行执行多个追加处理, Serial Dispatch Queue同时只能执行1个追加处理。虽然它们都受到系统资源的限制,但用dispatch_queue_create函数可以生成任意多个Dispatch Queue。

当生成多个Serial Dispatch Queue时,各个Serial Dispatch Queue将并行执行。虽然一个Serial Dispatch Queue中同时只能执行一个追加处理,但如果将处理分别追加到4个Serial Dispatch Queue中,各个Serial Dispatch Queue执行一个,就同事执行4个处理了。如下图: Serial Dispatch Queue.png

3.2.3 Main Dispatch Queue/Global Dispatch Queue

第二种方法就是获取系统标准提供的Dispatch Queue。
不用特意生成Dispatch Queue,系统也会给我们提供几个,那就是Main Dispatch Queue 和 Global Dispatch Queue。
Main Dispatch Queue 是在主线程中执行的,因为主线程只有1个,所以它自然就是Serial Dispatch Queue。
追加到Main Dispatch Queue的处理会在主线程的RunLoop中执行。所以要将用户界面更新等一些必须再主线程中执行的处理追加到这里来使用。
Global Dispatch Queue是所有应用程序都能够使用的Concurrent Dispatch Queue。它有4个执行优先级。

名称 Dispatch Queue 的种类 说明
Main Dispatch Queue Serial Dispatch Queue 主线程执行
Global Dispatch Queue (High Priority) Concurrent Dispatch Queue 执行优先级:高(最高优先)
Global Dispatch Queue (Default Priority) Concurrent Dispatch Queue 执行优先级:默认
Global Dispatch Queue (Low Priority) Concurrent Dispatch Queue 执行优先级:低
Global Dispatch Queue (Background Priority) Concurrent Dispatch Queue 执行优先级:后台

各种 Dispatch Queue 的获取方法如下:

// Main Dispatch Queue 的获取方法
dispatch_queue_t   mainDispatchQueue = dispath_get_main_queue();
// Global Dispatch Queue 的获取方法
dispatch_queue_t globalDispatchQueueHigh = dispath_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0)  //高优先级
dispatch_queue_t globalDispatchQueueDefault = dispath_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)  //默认优先级
dispatch_queue_t globalDispatchQueueLow = dispath_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW,0)  //低优先级
dispatch_queue_t globalDispatchQueueBackgroud = dispath_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0)  //高优先级

以下举例两种联合使用:

//在默认优先级的Global Dispatch Queue中执行block
dispatch_async(dispatch_get_global_queue,0),^{

   // 可并行执行的处理

   //在Main Dispatch Queue中执行block
   dispatch_async(dispatch_get_main_queue(),^{
      //只能在主线程中执行的代码,如刷新UI

   });
});

3.2.4 dispatch_set_target_queue

此3.2.4小节部分摘抄自作者 lltree的简书。感谢原作者“ lltree”。

  1. 使用dispatch_set_target_queue可以更改Dispatch Queue的执行优先级
dispatch_queue_t serialQueue = dispatch_queue_create("com.oukavip.www",NULL);
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);

dispatch_set_target_queue(serialQueue, globalQueue);

第一个参数为要设置优先级的queue,第二个参数是参照物,既将第一个queue的优先级和第二个queue的优先级设置一样。

  1. dispatch_set_target_queue还能够创建队列的层次体系,当我们想让不同队列中的任务同步的执行时,我们可以创建一个串行队列,然后将这些队列的target指向新创建的队列即可,比如 啊啊啊.png
dispatch_queue_t targetQueue = dispatch_queue_create("targetQueue", DISPATCH_QUEUE_SERIAL);//目标队列
    dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);//串行队列
    dispatch_queue_t queue2 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并发队列
    //设置参考
    dispatch_set_target_queue(queue1, targetQueue);
    dispatch_set_target_queue(queue2, targetQueue);
 
    dispatch_async(queue2, ^{
        NSLog(@"job3 in");
        [NSThread sleepForTimeInterval:2.f];
        NSLog(@"job3 out");
    });
    dispatch_async(queue2, ^{
        NSLog(@"job2 in");
        [NSThread sleepForTimeInterval:1.f];
        NSLog(@"job2 out");
    });
    dispatch_async(queue1, ^{
        NSLog(@"job1 in");
        [NSThread sleepForTimeInterval:3.f];
        NSLog(@"job1 out");
    });

输出结果:
[3491:1491712] job3 in
[3491:1491712] job3 out
[3491:1491712] job2 in
[3491:1491712] job2 out
[3491:1491712] job1 in
[3491:1491712] job1 out
通过打印的结果说明我们设置了queue1和queue2队列以targetQueue队列为参照对象,那么queue1和queue2中的任务将按照targetQueue的队列处理。
适用场景:
一般都是把一个任务放到一个串行的queue中,如果这个任务被拆分了,被放置到多个串行的queue中,但实际还是需要这个任务同步执行,那么就会有问题,因为多个串行queue之间是并行的。这时候dispatch_set_target_queue将起到作用。

3.2.5 dispatch_after

经常会有这样的情况:想在3秒后执行处理。这种想在指定时间后执行处理的情况,可使用dispatch_after函数实现。

dispatch_time_t time = dispatch_time(DISPATCH_TIME_ROW, 3ull * NSEC_PER_SEC);
dispatch_after (time ,dispatch_get_main_queue(),^{
    //等待三秒之后要执行的操作

});

需要注意的是,dispatch_after函数并不是在指定时间后执行处理,而只是在指定时间追加处理到Dispatch Queue。此代码与3秒后用dispatch_async函数追加Block到Main Dispatch Queue的相同。
因为Main Dispatch Queue在主线程的RunLoop中执行,所以在比如每隔1/60秒执行的RunLoop中,Block最快在3秒后执行,最慢在3秒+1/60秒后执行,并且在Main Dispatch Queue有大量处理追加或主线程的处理本身有延迟时,这个时间会更长。
虽然在有严格时间的要求下使用时会出现问题,但在想大致延迟执行处理时,该函数是非常有效的。
另外,第二个参数指定要追加处理的 Dispatch Queue,第三个参数指定要执行处理的Block。
第一个参数是指定时间的 dispatch_time_t 类型的值。该值使用 dispatch_time 函数或 dispatch_walltime 函数作成。第一个参数经常使用DISPATCH_TIME_NOW。这表示现在的时间。下面代码表示获得从现在开始1秒后的dispatch_time_t类型的值。

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);

数值和 NSEC_PER_SEC 的乘积得到单位为毫微秒的数值。“ull”是C语言的数值字面量,是显式表明类型时使用的字符串(表示“unsigned long long”)。如果使用NESC_PER_MSEC 则以毫秒为单位计算。下面代码表示获取从现在开始150毫秒后时间的值。

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 150ull * NSEC_PER_MSEC);

dispatch_time函数通常用于计算相对时间,而dispatch_walltime函数用于计算绝对时间。

3.2.6 Dispatch Group

有时候我们会有这种需求,在刚进去一个页面需要发送两个请求,并且某种特定操作必须在两个请求都结束(成功或失败)的时候才会执行,最low的办法第二个请求嵌套在第一个请求结果后在发送,在第二个请求结束后再执行操作。
还有就是只使用一个 Serial Dispatch Queue,把想要执行的操作全部追加到这个 Serial Dispatch Queue 中并在最后追加结束处理。但在使用 Concurrent Dispatch Queue 或 同时使用多个 Dispatch Queue 时,代码会变得很复杂。
这种情况可以使用 Dispatch Group
我们将ABC三个任务block追加到 Global Dispatch Queue,ABC全部执行完,会执行 dispatch_group_notify中的block。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_group_t  group = dispatch_group_creat();
dispatch_group_async(group,queue,^{NSLog(@"执行任务A");});
dispatch_group_async(group,queue,^{NSLog(@"执行任务B");});
dispatch_group_async(group,queue,^{NSLog(@"执行任务C");});
dispatch_group_notify(group,dispatch_get_main_queue(),^{
  NSLog(@"执行最终的特定操作");
});
dispatch_release(group);

执行结果如下:

执行任务A
执行任务B
执行任务C
执行最终的特定操作

上面的

dispatch_group_notify(group,dispatch_get_main_queue(),^{NSLog(@"执行最终的特定操作");
});

操作还可以更改为

dispatch_group_wait(group,DISPATCH_TIME_FOREVER);

dispatch_group_wait第二个参数指定为等待的时间(超时),属于dispatch_time_t类型,在这里使用DISPATCH_TIME_FOREVER,意味着永久等待。如果dispatch group的处理尚未结束,就会一直等待,中途不能取消。
如果指定等待时间为1秒如下:

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NESC_PER_SEC);
long result = dispatch_group_wait(group,time);
if(result == 0) { 
// dispatch group的全部处理执行结束
}else {
 // dispatch groupe的某一处理还在执行中
};

3.2.7 dispatch_barrier_async

dispatch_barrier_async函数的作用与barrier的意思相同,在进程管理中起到一个栅栏的作用,它等待所有位于barrier函数之前的操作执行完毕后执行,并且在barrier函数执行之后,barrier函数之后的操作才会得到执行,该函数需要同dispatch_queue_create函数生成的concurrent Dispatch Queue队列一起使用。
作用:
1.实现高效率的数据库访问和文件访问
2.避免数据竞争

 //同dispatch_queue_create函数生成的concurrent Dispatch Queue队列一起使用
    dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        NSLog(@"----1-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----2-----%@", [NSThread currentThread]);
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"----barrier-----%@", [NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"----3-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----4-----%@", [NSThread currentThread]);
    });

输出结果:1 2 --> barrier -->3 4 其中12 与 34 由于并行处理先后顺序不定。

3.2.8 dispatch_sync

dispatch_sync 函数的“async”意味着“非同步”(asynchronous),就是将指定的Block“非同步”地追加到指定的 Dispatch Queue中。dispatch_async 函数不做任何等待。
有“async”,也有“sync”,即dispatch_sync函数。它意味着“同步”(synchronous),再追加Block结束之前,dispatch_sync函数会一直等待。
假设一种情况:执行Main Dispatch Queue时,使用另外的线程Global Dispatch Queue进行处理,处理结束后立即使用所得到的结果。在这种情况下就要使用dispatch_sync函数。

dispatch_queue_t  queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{NSLog(@"Hello?");});

一旦调用dispatch_sync函数,在指定处理执行结束前,该函数不会返回。它可以简化代码,可以说是简易版的dispatch_group_wait函数。
因为简单,所以容易引起问题,如果在主线程执行以下代码就会死锁。

dispatch_queue_t  queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{NSLog(@"Hello?");});

上面代码在Main Dispatch Queue即主线程中执行指定的Block,并等待其执行结束。而其实在主线程中正在执行这些代码,所以无法执行追加到Main Dispatch Queue的Block。
所以大家以后深思熟虑以后再使用dispatch_sync函数同步等待处理执行的API,不然稍有不慎就导致程序死锁。

3.2.9 dispatch_apply

这个函数可以给定指定的次数将block追加到指定的Dispatch Queue中,并且等待全部结束处理执行。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_apply(10,queue,^(size_t index){
  NSLog(@“%zu”,idnex);
});
NSLog(@“done”);
//执行结果:4,1,0,3,5,2,6,8,9,7,done

第一个参数是重复次数,第二个参数是追加对象的dispatch queue,第三个参数是追加的处理。dispatch_apply可以做遍历数组的操作,不必一个一个写for循环。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_apply([array count],queue,^(size_t index)){
  NSLog(@"%zu:%@",index,[array objectAtIndex:index]);
}

3.2.10 dispatch_suspend / dispatch_resume

当追加大量处理到Dispatch Queue时,在追加处理的过程中有时希望不执行已追加的处理。例如演算结果被Block截获时,一些处理会对这个演算结果造成影响。
在这种情况下,只要挂起Dispatch Queue即可。当可以执行时再恢复。
dispatch_suspend 函数挂起指定的 Dispatch Queue。

dispatch_suspend(queue);

dispatch_resume 函数恢复指定的 Dispatch Queue。

dispatch_resume(queue);

3.2.11 Dispatch Semaphore

此节3.2.11部分摘录于CSDN的一篇博客 iOS GCD中级篇 - dispatch_semaphore(信号量)的理解及使用。感谢原作者“那一抹风情”。
理解这个概念之前,先抛出一个问题
问题描述:
假设现在系统有两个空闲资源可以被利用,但同一时间却有三个线程要进行访问,这种情况下,该如何处理呢?
或者
我们要下载很多图片,并发异步进行,每个下载都会开辟一个新线程,可是我们又担心太多线程肯定cpu吃不消,那么我们这里也可以用信号量控制一下最大开辟线程数。

定义:
1、信号量:就是一种可用来控制访问资源的数量的标识,设定了一个信号量,在线程访问之前,加上信号量的处理,则可告知系统按照我们指定的信号量数量来执行多个线程。
其实,这有点类似锁机制了,只不过信号量都是系统帮助我们处理了,我们只需要在执行线程之前,设定一个信号量值,并且在使用时,加上信号量处理方法就行了。

2、信号量主要有3个函数,分别是:

//创建信号量,参数:信号量的初值,如果小于0则会返回NULL
dispatch_semaphore_create(信号量值)
//等待降低信号量
dispatch_semaphore_wait(信号量,等待时间)
//提高信号量
dispatch_semaphore_signal(信号量)

注意,正常的使用顺序是先降低然后再提高,这两个函数通常成对使用。(具体可参考下面的代码示例) 
3、那么就开头提的问题,我们用代码来解决

-(void)dispatchSignal{
    //crate的value表示,最多几个资源可访问
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);   
    dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     
    //任务1
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 1");
        sleep(1);
        NSLog(@"complete task 1");
        dispatch_semaphore_signal(semaphore);       
    });<br>
    //任务2
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 2");
        sleep(1);
        NSLog(@"complete task 2");
        dispatch_semaphore_signal(semaphore);       
    });<br>
    //任务3
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 3");
        sleep(1);
        NSLog(@"complete task 3");
        dispatch_semaphore_signal(semaphore);       
    });   
}
执行结果: 511196-20170111164237931-1818878873.png

总结:由于设定的信号值为2,先执行两个线程,等执行完一个,才会继续执行下一个,保证同一时间执行的线程数不超过2。
这里我们扩展一下,假设我们设定信号值=1

dispatch_semaphore_create(1)
那么结果就是: 511196-20170111164207869-1629237833.png

如果设定信号值=3

dispatch_semaphore_create(3)
那么结果就是:

其实设定为3,就是不限制线程执行了,因为一共才只有3个线程。

3.2.12 dispatch_once

dispatch_once函数是保证在程序执行中只执行一次指定处理的API。
使用dispatch_once函数为:

static dispatch_once_  onceToken;
dispatch_once( &onceToken,^{
  // 初始化操作
  对象A =[ [对象A  alloc] init];
});

通过dispatch_once创建的即使在多线程环境下执行也百分百安全。
这就是所说的单例模式,在生成单例对象时使用。

3.2.13 Dispatch I/O

在读取较大文件时,如果将文件分成合适的大小并使用Global Dispatch Queue 并发读取的话,应该会比一般的读取速度快不少。现在的输入/输出硬件已经可以做到一次使用多个线程更快地并发读取了。能实现这一功能的就是Dispatch I/O 和Dispatch Data。
通过Dispatch I/O读写文件时,使用Global Dispatch Queue将1个文件按某个大小read/write。

    dispatch_async(queue, ^{ /* 读取  0     ~ 8080  字节*/ });
    dispatch_async(queue, ^{ /* 读取  8081  ~ 16383 字节*/ });
    dispatch_async(queue, ^{ /* 读取  16384 ~ 24575 字节*/ });
    dispatch_async(queue, ^{ /* 读取  24576 ~ 32767 字节*/ });
    dispatch_async(queue, ^{ /* 读取  32768 ~ 40959 字节*/ });
    dispatch_async(queue, ^{ /* 读取  40960 ~ 49191 字节*/ });
    dispatch_async(queue, ^{ /* 读取  49192 ~ 57343 字节*/ });
    dispatch_async(queue, ^{ /* 读取  57344 ~ 65535 字节*/ });

像上面这样,将文件分割为一块一块地进行读取处理。分割读取的数据通过使用Dispatch Data 可更为简单地进行结合和分割。
请看下面苹果中使用Dispatch I/O 和 Dispatch Data的例子。下面的代码摘自Apple System Log API里的源代码(地址:http://opensource.apple.com/source/Libc/Libc-763.11/gen/asl.c

static int
_asl_auxiliary(aslmsg msg, const char *title, const char *uti, const char *url, int *out_fd)
{
    asl_msg_t *merged_msg;
    asl_msg_aux_t aux;
    asl_msg_aux_0_t aux0;
    fileport_t fileport;
    kern_return_t kstatus;
    uint32_t outlen, newurllen, len, where;
    int status, fd, fdpair[2];
    caddr_t out, newurl;
    dispatch_queue_t pipe_q;
    dispatch_io_t pipe_channel;
    dispatch_semaphore_t sem;
    /* ..... 此处省略若干代码.....*/
    
    // 创建串行队列
    pipe_q = dispatch_queue_create("PipeQ", NULL);
    // 创建 Dispatch I/O
    pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err){
        close(fd);
    });
    
    *out_fd = fdpair[1];
    
    // 该函数设定一次读取的大小(分割大小)
    dispatch_io_set_low_water(pipe_channel, SIZE_MAX);
    //
    dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q, ^(bool done, dispatch_data_t pipedata, int err){
        if (err == 0) // err等于0 说明读取无误
        {
            // 读取完“单个文件块”的大小
            size_t len = dispatch_data_get_size(pipedata);
            if (len > 0)
            {
                // 定义一个字节数组bytes
                const char *bytes = NULL;
                char *encoded;
                
                dispatch_data_t md = dispatch_data_create_map(pipedata, (const void **)&bytes, &len);
                encoded = asl_core_encode_buffer(bytes, len);
                asl_set((aslmsg)merged_msg, ASL_KEY_AUX_DATA, encoded);
                free(encoded);
                _asl_send_message(NULL, merged_msg, -1, NULL);
                asl_msg_release(merged_msg);
                dispatch_release(md);
            }
        }
        
        if (done)
        {
            dispatch_semaphore_signal(sem);
            dispatch_release(pipe_channel);
            dispatch_release(pipe_q);
        }
    });
}

设置一次读取的最大字节

void dispatch_io_set_high_water( dispatch_io_t channel, size_t high_water);

设置一次读取的最小字节

void dispatch_io_set_low_water( dispatch_io_t channel, size_t low_water);

dispatch_io_read 函数使用Global Dispatch Queue 开始并发读取。每当各个分割的文件块读取结束时,将含有文件块数据的 Dispatch Data(这里指pipedata) 传递给 “dispatch_io_read 函数指定的读取结束时回调用的block”,这个block拿到每一块读取好的Dispatch Data(这里指pipe data),然后进行合并处理。
如果想提高文件读取速度,可以尝试使用 Dispatch I/O.

上一篇下一篇

猜你喜欢

热点阅读