Grand Central Dispatch 基础教程:Part 2/2

原文 Grand Central Dispatch Tutorial for Swift: Part 2/2html

原文做者:Bj1435030954663162.pngrn Olav Ruudios

译者:Ethan Joeswift

欢迎来到Grand Central Dispatch系列教程的第二部分!数组

在教程的第一部分,你学到了一些关于并发,线程及GCD工做原理的知识。你经过并用dispatch_barrier_async与dispatch_sync保证了PhotoManager单例在读取与写入照片过程当中的线性安全性。值得一提的是,你不只经过dispatch_after及时地向用户发出提醒以此优化了App的UX并且还经过dispatch_async将部分工做从一个View Controller的实例化过程当中分割至另外一线程以此实现CPU高密度处理工做。xcode

假如你是一路从上一部分教程学过来的话,你彻底能够在之前的工程文件上继续Coding。但假如你没有完成教程的第一部分或是不想继续使用本身的工程文件的话,你可从这里下载到教程第一部分的完整工程文件。安全

OK! 是时候探索一下更多关于GCD的知识了。服务器

修复提前出现的Popup网络

也许你已经注意到了当你经过Le Internet的方式添加照片时,在全部照片下载完成前AlertView就已经跳出来提醒你“Download Complete”。闭包

blob.png

See That?并发

其实问题出在PhotoManaer的downloadPhotosWithCompletion函数中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) {
   var  storedError: NSError!
   for  address  in  [OverlyAttachedGirlfriendURLString,
               SuccessKidURLString,
               LotsOfFacesURLString] {
     let url = NSURL(string: address)
     let photo = DownloadPhoto(url: url!) {
       image, error  in
       if  error != nil {
         storedError = error
       }
     }
     PhotoManager.sharedManager.addPhoto(photo)
   }
   if  let completion = completion {
      completion(error: storedError)
   }
}

在函数结尾部分调用completion闭包--这就表明着你认为全部的照片的下载任务都已经完成。但不幸的是此时此刻你并没有法保证全部的下载任务都已经完成。

DownloadPhoto类的实例化方法开始从一个URL下载文件并在下载完成前当即返回值。换句话说,downloadPhotosWithCompletion在函数结尾处调用其本身的completion闭包就好像它本身就是个有着直线型同步执行代码的方法体,而且每一个方法执行完本身的工做后都会调用这个completed。

无论怎样,DownloadPhoto(url:)是异步执行的而且当即返回--因此这个解决方案无论用。

再有,downloadPhotosWithCompletion应该在全部的照片下载任务都调用了completion闭包后再调用本身的completion闭包。那么问题来了:你怎样去监管那些同时执行的异步事件呢?你根本不会知道它们会什么时候并以何种顺序结束。

或许你能够写多个Bool值去跟踪每一个任务的下载状态。说实话,这样作的话会感受有些low而且代码看起来会很乱。

万幸的是,派发组(dispatch groups)正是为知足监管这种多异步completion的须要所设计的。

派发组(Dispatch Group)

当整组的任务都完成时派发组会提醒你。这些任务既能够是异步的也能够是同步的而且能够在不一样队列中被监管。当全组任务完成时派发组能够经过同步或异步的方式提醒你。只要有任务在不一样队列中被监管,dispatch_group_t实例便会在多个队列中的持续监管这些不一样的任务。

当组中的所有任务执行完毕后,GCD的API提供两种方式向你发出提醒。

第一个即是dispatch_group_wait,这是一个在组内全部任务执行完毕前或在处理超时的状况下限制当前线程运行的函数。在AlertView提前出现的状况下,使用disptach_group_wait绝对是你的最佳解决方案。

打开PhotoManager.swift并用以下代码替换原downloadPhotosWithCompletion函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) {
   dispatch_async(GlobalUserInitiatedQueue) {  // 1
     var  storedError: NSError!
     var  downloadGroup = dispatch_group_create()  // 2
     for  address  in  [OverlyAttachedGirlfriendURLString,
                 SuccessKidURLString,
                 LotsOfFacesURLString]
     {
       let url = NSURL(string: address)
       dispatch_group_enter(downloadGroup)  // 3
       let photo = DownloadPhoto(url: url!) {
         image, error  in
         if  let error = error {
           storedError = error
         }
         dispatch_group_leave(downloadGroup)  // 4
       }
       PhotoManager.sharedManager.addPhoto(photo)
     }
     dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER)  // 5
     dispatch_async(GlobalMainQueue) {  // 6
       if  let completion = completion {  // 7
         completion(error: storedError)
        }
     }
   }
}

代码分步解释:

  1. 一旦使用限制当前线程运行的同步式dispatch_group_wait,你必须用dispatch_async将整个方法调至后台队列以保证主线程的正常运行。

  2. 在这里声称了一个你能够将其视为未完成任务计数器的新派发组。

  3. dispatch_group_enter用来向派发组提醒新任务执行的开始。你必须使调用dispatch_group_enter的次数要相称于调用dispatch_group_leave的次数,否则将会致使App崩溃。

  4. 在这里,你向派发组提醒任务的执行结束。再强调一遍,进出派发组的次数必定要相等。

  5. 在全部任务执行结束后或者在处理超时的状况下dispatch_group_wait才会执行。假如在全部任务执行结束前就出现了处理超时的状况,函数便会返回一个非零结果。你能够将其放在一个特殊的闭包中以检查是否会发生处理超时的状况。固然,在本教程的状况下你可使用DISPATCH_TIME_FOREVER令其保持等待请求状态,这就意味它会一直等下去,由于照片的下载任务总会完成的。

  6. 到目前为止,你保证了照片下载任务要么顺利完成要么出现处理超时的状况。其后你即可以返回至主队列运行你的completion闭包。这将向主线程添加稍后将被执行的任务。

  7. 条件容许的状况下执行completion闭包。

编译并运行你的App,你会发如今点击下载照片的选项后你的completion闭包将会在正确的时间执行。

提醒:当你在实体设备上运行App的时候,假如网络机制运行过快以致于你没法判断的completion闭包开始执行时间的话,你能够到App的Setting中的Developer Section中进行一些网络调整。打开Network Link Conditioner,选择Very Bad Network是一个不错的选择。

假如你在模拟器上运行App的话,你能够经过使用Network Link Conditioner included in the Hardare IO Tools for Xcode调整你的网络速度。这是一个当你须要了解在网络情况很差的状况下App执行状况的绝佳工具。

这个解决方法的好处不止于此,但整体上来讲它在大多数状况下避免了限制线程正常运行的可能。你接下来的任务即是写一个相同的并以异步的方式向你发出'照片下载完成'提醒的方法。

在开始以前先了解一下对于不一样类型的队列来讲应该什么时候使用并怎样使用派发组的简短教程。

  • 自定义连续队列(Custom Serial Queue): 在组内任务所有完成时须要发出提醒的状况下,自定义连续队列是一个不错的选择。

  • 主队列(Main Queue[Serial]):在当你以同步的方式等待全部任务的完成且你也不想限制主队列的运行的状况下你应该在主线程上警戒使用它。但好比像网络请求这种长时间运行的任务结束时异步模型是用来更新UI的绝佳方式。

  • 并发队列(Concurrent Queue):这对于派发组及完成提醒也是个不错的选择。

派发组,再来一次!

出于精益求精的目的,经过异步的方式将下载任务派发到另外一个队列并用dispatch_group_wait限制其运行的作法是否是有些stupid呢?试试另外一种方法吧...

用以下实现代码代替PhtotManager.swift中的downloadPhotosWithCompletion函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) {
   // 1
   var  storedError: NSError!
   var  downloadGroup = dispatch_group_create()
   for  address  in  [OverlyAttachedGirlfriendURLString,
               SuccessKidURLString,
               LotsOfFacesURLString]
   {
     let url = NSURL(string: address)
     dispatch_group_enter(downloadGroup)
     let photo = DownloadPhoto(url: url!) {
       image, error  in
       if  let error = error {
         storedError = error
       }
       dispatch_group_leave(downloadGroup)
     }
     PhotoManager.sharedManager.addPhoto(photo)
   }
   dispatch_group_notify(downloadGroup, GlobalMainQueue) {  // 2
     if  let completion = completion {
       completion(error: storedError)
     }
   }
}

这就是你的新异步方法的工做原理:

  1. 在这个新的实现方法中,当你再也不限制主线程的时候你就没有必要将其放进dispatch_async的调用中。

  2. dispatch_group_notify至关于一个异步completion闭包。当派发组中再也不剩余任何任务且轮到completion闭包运行时,这段代码将会执行。你也能够定义你的completion代码在哪一个队列上运行。在这段代码中你便要运行在主队列上。

对于在不限制任何线程运行的状况下处理这种特殊需求的例子来讲,这是一种较为简洁的方式。

过多使用并发机制形成的危险

学了这么多的新东西后,你是否是该令你的代码所有实现线程化呢?

blob.png

Do It !!!

看看你在PhotoManger中的downloadPhotosWithCompletion函数。你应该注意到了那个循环在三个参数间并下载三张不一样照片的for循环。你接下来的工做即是尝试经过并发机制加快for循环的运行速度。

该轮到dispatch_apply上场了。

dispatch_apply就像是一个以并发的形式执行不一样迭代过程的for循环。就像是一个普通的for循环,dispatch_apply是一个同步运行且全部工做完成后才会返回的函数。

当你在对闭包内已给定任务的数量进行最优化迭代过程数量的设定时必定要小心,由于这种存在多个迭代过程且每一个迭代过程仅包含少许工做的状况所消耗的资源会抵消掉并发调用所产生的优化效果。这个叫作striding的技术会在你处理多任务的每一个迭代过程的地方帮到你。

何时适合用dispatch_apply呢?

  • 自定义连续队列(Custome Serial Queue):对于连续队列来讲,dispatch_apply没什么用处;你仍是老实地用普通的for循环吧。

  • 主队列(Main Queue[Serial]):跟上面状况同样,老实地用普通for循环。

  • 并发队列(Concurrent Queue):当你须要监管你的任务处理进程时,并发循环绝对是一个好主意。

回到downloadPhotosWithCompletion并替换成以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) {
    var  storedError: NSError!
    var  downloadGroup = dispatch_group_create()
    let addresses = [OverlyAttachedGirlfriendURLString,
                SuccessKidURLString,
                LotsOfFacesURLString]
    dispatch_apply(addresses.count, GlobalUserInitiatedQueue) {
      in
      let index = Int(i)
      let address = addresses[index]
      let url = NSURL(string: address)
      dispatch_group_enter(downloadGroup)
      let photo = DownloadPhoto(url: url!) {
        image, error  in
        if  let error = error {
          storedError = error
        }
        dispatch_group_leave(downloadGroup)
      }
      PhotoManager.sharedManager.addPhoto(photo)
     }
     dispatch_group_notify(downloadGroup, GlobalMainQueue) {
       if  let completion = completion {
         completion(error: storedError)
       }
     }
   }

你的循环块如今就是以并发的形式运行;在上述代码中,你为调用dispatch+apply提供了三个参数。第一参数声明了迭代过程的数量,第二个参数声明了将要运行多个任务的队列,第三个参数声明了闭包。

要知道尽管你已经有了在线程安全模式下添加照片的代码,可是照片顺序会根据最早完成的线程的顺序所排列。

编译并运行,经过Le Internet的方式添加一些照片。注意到有什么不一样吗?

在真实设备上运行修改后的代码偶尔会运行得快一些。因此,上面作出的修改值得吗?

其实,在这种状况下它不值得你这么作。缘由以下:

  • 你已经调用出了比for循环再同种状况下消耗更多资源的线程。dispatch_apply在这里显得有些小题大作了。

  • 你写App的时间是有限的--不要为那些'抓鸡不成蚀把米'的优化代码浪费时间,把你的时间用在优化得有明显效果的代码上。你能够选择使用Xcode中的Instruments来测试出你App中执行时间最长的方法。

  • 在某些状况下,优化后的代码甚至会增长你和其余开发者理解其逻辑结构的难度,因此优化效果必定要是物有所值的。

记住,不要痴迷于优化,要否则你就是和本身过不去了。

取消派发块(dispatch block)的执行

要知道在iOS 8和OS X Yosemite中加入了名为dispatch block objects的新功能(中文叫‘派发块对象’感受老是怪怪的,因此就继续用英文原名)。Dispatch block objects能够作很多事儿了,好比经过为每一个对象设定一个QoS等级来决定其在队列中的优先级,但它最特别的功能即是取消block objects的执行。但你须要知道的是一个block object只有在到达队列顶端且开始执行前才能被取消。

我们能够经过‘利用Le Internet开始并再取消照片下载任务’的方式详细描述取消Dispatch Block的运行机制。用下述代码代替PhotoManager.swift中的downloadPhotosWithCompletion函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) {
   var  storedError: NSError!
   let downloadGroup = dispatch_group_create()
   var  addresses = [OverlyAttachedGirlfriendURLString,
                SuccessKidURLString,
                LotsOfFacesURLString]
   addresses += addresses + addresses  // 1
   var  blocks: [dispatch_block_t] = []  // 2
   for  in  0 ..< addresses.count {
     dispatch_group_enter(downloadGroup)
     let block = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS) {  // 3
       let index = Int(i)
       let address = addresses[index]
       let url = NSURL(string: address)
       let photo = DownloadPhoto(url: url!) {
         image, error  in
         if  let error = error {
           storedError = error
         }
         dispatch_group_leave(downloadGroup)
       }
       PhotoManager.sharedManager.addPhoto(photo)
     }
     blocks.append(block)
     dispatch_async(GlobalMainQueue, block)  // 4
   }
   for  block  in  blocks[3 ..< blocks.count] {  // 5
     let cancel = arc4random_uniform(2)  // 6
     if  cancel == 1 {
       dispatch_block_cancel(block)  // 7
       dispatch_group_leave(downloadGroup)  // 8
     }
   }
   dispatch_group_notify(downloadGroup, GlobalMainQueue) {
     if  let completion = completion {
       completion(error: storedError)
     }
   }
}
  1. addresses数组内包含每一个都被复制了三份的address变量。

  2. 这个数组包含着晚些时候将被使用的block objects。

  3. dispatch_block_create声明了一个新的block object。第一个参数是定义了不一样block特性的标志。这个标志使得block继承了其在被分配至队列中的QoS等级。第二个参数是以一个闭包形式定义的block。

  4. 这是一个以异步形式分发到全局主队列的block。在这个例子中所使用的主队列是一个连续队列,因此其更容易取消所选的blocks。定义了分发blocks的代码已在主队列上的执行保证了下载blocks的稍后执行。

  5. 除去前三次的下载,在剩余的数组元素中执行for循环。

  6. arc4random_uniform提供一个在0至上限范围内(不包含上限)的整数。就像掷硬币那样,将2设定为上限后你将会获得0或1中的某一个整数。

  7. 假如在随机数是一、block还在队列中且没有正在被执行的状况下,block则被取消。在执行过程当中的block是不能被取消的。

  8. 当全部blocks都被加入分发队列后,不要忘记删除被取消的队列。

编译并运行App,经过Le Internet 的方式添加照片。你会发现App在下载了原来的三张照片后还会再下载一个随机数量的照片。分配至队列后,剩余的blocks将被取消。尽管这是一个很牵强的例子但起码它很好的描述了dispatch block objects如何被使用或被取消的。

Dispatch block objects能作的还有不少,使用前别忘了看看官方文档。

GCD带来的各类各样的乐趣

且慢!再容我讲点儿东西!其实这还有些不经常使用的函数,但在特殊状况下它们是很是有用的。

测试异步代码

这也许听起来有些不靠谱,但你知道Xcode上的确有这项测试功能吗?:]其实在某些状况下我是伪装不知道有这项功能的,可是在处理具备复杂关系的代码的时候,代码编写与运行的测试是很是重要的。

Xcode中的测试功能是以XCTestCase的子类形式出现其且在其中运行的任何方法都是以test开头出现的。测试功能在主线程上运行,因此你能够假设每一个测试都是以一种连续(serial)的方式运行的。

只要一个给定的测试方法完成了执行,XCTest方法就会认定一个测试已经完成并开始下一个测试,这就意味着在新的测试运行的同时,上一个测试中的异步代码还会继续运行。

当你在执行一个网络请求任务且不想限制主线程的运行时,那么这类网络任务一般是以异步方式执行的。这种“测试方法的结束表明着整个测试过程的结束”的机制加大了网络代码测试的难度。

别紧张,接下来我们看两个经常使用的且专门用来测试以异步方式执行的代码的技术:一个使用了信号量(semaphores),另外一个使用了指望(expectations)。

信号量(Semaphores)

在不少学校的OS课中,一提到大名鼎鼎的Edsger W.Dijkstra时确定会讲到信号量这个跟线程相关的概念。信号量难懂之处在于它创建在那些复杂的操做系统的函数之上。

假如你想学习更多关于信号量的知识,请到这里了解更多关于信号量理论的细节。假如你是个专一于学术研究的家伙,从软件开发的角度来看,关于信号量的经典例子确定就是哲学家进餐问题了。

信号量适用于让你在资源有限的状况下控制多个单位的资源消耗。举个例子,假如你声明了一个其中包含两个资源的信号量,在同一时间内最多只能有两个线程访问临界区。其余想使用资源的线程必须以FIFO(First Come, First Operate)的顺序在队列中等待。

打开GooglyPuffTests.swift并用以下代码替换downloadImageURLWithString函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func downloadImageURLWithString(urlString: String) {
   let url = NSURL(string: urlString)
   let semaphore = dispatch_semaphore_create(0)  // 1
   let photo = DownloadPhoto(url: url!) {
     image, error  in
     if  let error = error {
       XCTFail( "\(urlString) failed. \(error.localizedDescription)" )
     }
     dispatch_semaphore_signal(semaphore)  // 2
   }
   let timeout = dispatch_time(DISPATCH_TIME_NOW, DefaultTimeoutLengthInNanoSeconds)
   if  dispatch_semaphore_wait(semaphore, timeout) != 0 {  // 3
     XCTFail( "\(urlString) timed out" )
   }
}

如下即是信号量如何在上述代码中工做的解释:

  1. 建立信号量。参数代表了信号量的初始值。这个数字表明着能够访问信号量线程的数量,咱们常常以发送信号的方式来增长信号量。

  2. 你能够在completion闭包中向信号量声明你再也不须要资源了。这样的话,信号量的值会获得增长而且向其余资源声明此时信号量可用。

  3. 设定信号量请求超时的时间。在信号量声明可用前,当前线程的运行将被限制。若出现超时的话,函数将会返回一个非零的值。在这种状况下测试即是失败的,由于它认为网络请求的返回不应使用超过十秒的时间。

经过使用菜单中的Product/Test选项或者使用?+U快捷键测试App的运行。

断开网络链接后再次运行测试;假如你在实机上运行就打开飞行模式,如果模拟器的话就断开连接。10秒后此次测试便以失败了结。

假如你是一个服务器团队中一员,完成这些测试仍是挺重要的。

指望(Expectations)

XCTest框架提供了另外一个用来测试异步方式执行代码的解决方案,指望。这便容许你在一个异步任务开始执行前设定一个指望--一些你期待发生的事。在异步任务的指望被标记为已完成(fulfilled)前,你能够令test runner一直保持等待状态。

用如下代码代替GooglyPufftests.swift中的downloadImageWithString函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func downloadImageURLWithString(urlString: String) {
   let url = NSURL(string: urlString)
   let downloadExpectation = expectationWithDescription( "Image downloaded from \(urlString)" // 1
   let photo = DownloadPhoto(url: url!) {
     image, error  in
     if  let error = error {
   XCTFail( "\(urlString) failed. \(error.localizedDescription)" )
     }
downloadExpectation.fulfill()  // 2
   }
   waitForExpectationsWithTimeout(10) {  // 3
     error  in
     if  let error = error {
       XCTFail(error.localizedDescription)
     }
   }
}

解释一下:

  1. 经过expectationWithDescription参数声明了一个指望。当测试失败的时候,test runner将会在Log中显示这段字符串参数,以此表明着你所期待发生的事情。

  2. 调用以异步方式标记指望已完成的闭包中的fulfill.

  3. 调用的线程等待指望被waitForExpectationsWithTimeout函数标记完成。若等待超时,线程将被当作一个错误。

编译并运行测试。尽管测试结果跟使用信号量机制比起来并无太多的不一样,但这倒是一种使XCTest框架更加简洁易读的方法。

派发源(Dispatch Sources)的使用

GCD的一个很是有趣的特性就是派发源,它是包含了不少低层级别的功能。这些功能能够帮你对Unix的信号,文件描述符,Mach端口、VFS Nodes进行反馈以及检测。尽管这些东西超出了这篇教程的范围,但我以为你仍是要试着去实现一个派发源对象。

不少派发源的初学者常常被卡在如何使用一个源的的问题上,因此你要清楚dispatch_source_create的工做原理。下面的函数声明了一个源:

1
2
3
4
5
func dispatch_source_create(
   type: dispatch_source_type_t,
   handle: UInt,
   mask: UInt,
   queue: dispatch_queue_t!) -> dispatch_source_t!

做为第一个参数,type: dispatch_source_type_t决定了接下来的句炳以及掩码参数的类型。你能够去看一下相关内容的官方文档以便理解每一种dispatch_source_type_t的用法与解释。

在这里你将监管DISPATCH_SOURCE_TYPE_SIGNAL。如官方文档里解释的那样:派发源监管着当前进程的信号,其句炳的值是一个整数(int),而掩码值暂时没有用到而被设为0。

这些Unix信号能够在名为signal.h的头文件中找到。文件的顶端有不少的#defines。在这堆信号中你将对SIGSTOP信号进行监管。当进程收到一个不可避免的暂停挂起指令时这个信号将被发送。当你用LLDB的debugger除错的时候一样的信号也会被发送。

打开PhotoCollectionViewController.swift文件,在viewDidLoad函数附近添加以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#if DEBUG
private  var  signalSource: dispatch_source_t!
private  var  signalOnceToken = dispatch_once_t()
#endif
override func viewDidLoad() {
   super .viewDidLoad()
   #if DEBUG // 1
   dispatch_once(&signalOnceToken) {  // 2
     let queue = dispatch_get_main_queue()
     self.signalSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL,
                                            UInt(SIGSTOP), 0, queue)  // 3
     if  let source = self.signalSource {  // 4
       dispatch_source_set_event_handler(source) {  // 5
         NSLog( "Hi, I am: \(self.description)" )
       }
       dispatch_resume(source)  // 6
     }
   }
   #endif
   // The other stuff
}

分布解释以下:

  1. 从安全角度考虑,你最好在DEBUG模式下编译代码,以防其余人间接地看到你的代码。: ] 经过在如下路径,Project Settings -> Build Settings -> Swift Compiler – Custom Flags -> Other Swift Flags -> Debug,经过在Debug选项中添加-D DEBUG的方式来定义DEBUG。

  2. 利用dispatch_once对派发源进行单次的初始化设定。

  3. 在这里你实例化了一个signalSource变量并代表你只想进行信号监管并将SIGSTOP信号做为第二个参数。再有一点你须要知道的是你使用主队列处理接收到的事件--至于为何,你待会儿就会知道了。

  4. 假如你提供了一个错误的参数,派发源对象将不会被建立成功。总之,在使用派发源对象前你要肯定你已经建立了一个可用的派发源对象。

  5. dispatch_source_set_event_handler注册了一个在收到特定任务信号时将被调用的事件处理闭包。

  6. 在默认的状况下,全部派发源都是在暂停挂起的状态下开始执行的。当你打算开始监管事件时,你必须让派发源对象重新开始运行。

编译并运行App;以debugger方式暂停App的运行并再马上恢复运行。检查一下控制台,你会看到以下的反馈:

1
2014-08-12 12:24:00.514 GooglyPuff[24985:5481978] Hi, I am:

在某种程度上你的App如今知道debug了。这很是不错,可是如何较为实际地使用它呢?

当你重新恢复App运行时你可使用它对一个object进行debug并显示其相关数据;当某些不怀好意的人想利用debugger影响App正常运行的时候,你也能够为你的App写一个自定义安全登陆模块。

另外一个有趣的想法即是经过上述机制实现一个对于debugger中对象的堆栈跟踪器。

blob.png

What ?

考虑一下这种状况,你意外的中止了debugger的运行并在很大程度上debugger很难待在预计的栈帧上。但如今你能够任什么时候间中止debugger的运行并任何地方执行代码。假如你想在App的特定位置中执行代码,上述状况将会很是有用。

在viewDidLoad的事件处理闭包中的NSLog代码旁添加断点。在debugger中暂停运行,在恢复运行;你的App将运行至断点添加处。如今你即可以为所欲为地访问PhotoCollectionViewController中的实例了。

假如你不知道debugger中有哪些线程,能够去查看一下。主线程总会是第一个被libdispatch跟随的线程;GCD的协调器总会是第二个线程;其余的线程就要视具体状况而定了。

blob.png

blob.png

利用断点功能你能够逐步地更新UI、测试类的属性甚至在不从新运行App的状况下执行特定的方法,看起来很方便吧!

Where to Go From Here?

你能够在这里下载到教程的完整代码。

我挺不喜欢唠叨的,可是我以为你仍是应该去看看这篇关于如何使用Xcode中的Instruments的教程。假如你打算对你的App进行优化的话,你是绝对会用到Instruments的。要知道Instruments对于推断相对执行的问题是颇有用处的:对比不一样代码块中哪一块的相对执行时间是最长的。

与此同时,你也有必要去看看这篇How to Use NSOperations and NSOperationsQueue Tutorial in Swift的教程。NSOperations能够提供更良好的控制,实现多并发任务最大数量的处理以及在牺牲必定运行速度的状况下使得程序更加面向对象化。

记住!在大多数状况下,除非你有着特殊的缘由,必定要尽可能使用更高级别的API。只有当你真的想到学到或作到一些很是有趣的事情时再去探索苹果的Dark Arts吧!

祝你好运!

 

本文转载自:http://www.cocoachina.com/ios/20150626/12238.html

相关文章
相关标签/搜索