设为首页 加入收藏

TOP

在Swift中应用Grand Central Dispatch(上)转载自的goldenfiredo001的博客(四)
2017-10-13 10:33:32 】 浏览:7638
Tags:Swift 应用 Grand Central Dispatch 转载 goldenfiredo001 博客
;   else  {
       self.navigationItem.prompt =  "Add photos with faces to Googlyify them!"
     }
   }
}

showOrHideNavPrompt会在viewDidLoad以及UICollectionView重新加载的时候被执行。代码解释如下: 1. 声明推迟的时间。 2. 等待delayInSeconds所表示的时间,然后将闭包异步地加入主队列中。

运行app。在短暂的延迟后,提示会出现并吸引用户的注意。

dispatch_after的工作原理就像推迟的dispatch_async。一旦dispatch_after返回,你还是无法掌握实际的执行时间抑或是取消任务。

想知道何时使用dispatch_after?

  • 自定义顺序队列:慎用。在自定义顺序队列中慎用dispatch_after。你最好留在主队列中。

  • 主队列(顺序):好主意。在主队列中使用dispatch_after是一个好主意;Xcode对此有自动补全模板。

  • 并发队列:慎用。很少会这样使用,最好留在主队列中。

单例和线程安全

单例。爱也好,恨也罢,它们在iOS中就像猫之于互联网一样流行。

经常有人因为单例不是线程安全的而忧虑。这种担忧是很有道理的,考虑到他们的用法:单例经常被多个控制器同时使用。PhotoManager类是一个单例,所以你要仔细思考这个问题。

思考两种情形,初始化单例的过程和对他进行读写的过程。

先 来看初始化。这看起来很简单,因为Swift在全局域中初始化变量。在Swift中,全局变量在首次使用时被初始化,并且保证初始化是原子操作。也就是 说,初始化代码被视为临界区从而保证了初始化在其他线程使用全局变量之前就完成了。Swift是怎么做到的?其实,Swift在幕后使用了GCD中的 dispatch_once,详见博客

dispatch_once 以线程安全的方式执行且仅执行一次闭包。如果一个线程正处于临界区中 — 被提交给dispatch_once的任务 — 其他线程会阻塞直到它完成。并且一旦它完成,其他线程不会再执行临界区中的代码。用let将单例定义为全局常量,我们可以进一步保证变量在初始化后不会发 生变化。从某种意义上说,所有Swift全局常亮量都天生是单例,并且线程安全地初始化。

但是我们仍需要考虑读和写。尽管Swift使用 dispatch_once来确保单例初始化是线程安全的,但不能保证它所表示的数据类型也是线程安全的。例如用一个全局变量来声明一个类实例,但在类中 还是会有修改类内部数据的临界区。此时就需要其他方式来达成线程安全,比如通过对数据的同步化使用(synchronizing access)。

处理读写问题

实例化线程安全性不是单例的唯一问题。如果单例的属性表示一个可变对象,比如PhotoManager中的photos,那么你就需要考虑那个对象是否线程安全。

在 Swift中任意用let声明的常量都是只读并且线程安全的。用var声明的变量是可变且非线程安全的,除非数据类型本身被设计成线程安全。Swift中 的集合类型比如Array和Dictionary,当声明为变量时不是线程安全的。那么像Foundation的容器NSArray呢?是线程安全的吗? 答案是—“可能不是”!Apple维护的一个帮助列表中有许多Foundation中非线程安全的类。

尽管很多线程可以同时读取一个Array的可变实例而不出问题,但如果一个线程在修改数组的同时另一个线程却在读取这个数组,这是不安全的。你的单例目前还不能阻止这种情况发生。

为了弄清楚问题,看看PhotoManager.swift中的addPhoto:

1
2
3
4
5
6
func addPhoto(photo: Photo) {
   _photos.append(photo)
   dispatch_async(dispatch_get_main_queue()) {
     self.postContentAddedNotification()
   }
}

这是一个写方法,因为它修改了一个可变数组。

再看看photos属性:

1
2
3
4
private  var  _photos: [Photo] = []
var  photos: [Photo] {
   return  _photos
}

这个属性的getter方法是一个读方法。调用者得到一个数组的拷贝并且保护了原始数组不被改变,但是这不能保证一个线程在调用addPhoto来写的时候没有另一个线程同时也在调用getter方法读photos属性。

注意:
在 上面的代码中,为什么调用者要获取photo数组的拷贝?在Swift中,参数或函数返回是通过值或引用来传递的。引用传递和OC中的传指针一样,这意味 着你得到的是原始的对象,对这个对象的修改会影响到其他使用了这个对象引用的代码。值传递拷贝了对象本身,对拷贝的修改不会影响原始的对象。默认情况 下,Swift类实例是引用传递而结构体是值传递。

Swift内置的数据类型,如Array和Dictionary,是用结构体来实现的,看起来传递集合类型会造成代码中出现大量的拷贝。不要因此担心内存使用问题。Swift的集合类型经过优化,只有在需要的时候才进行拷贝,比如通过值传递的数组在第一次被修改的时候。

这是软件开发中经典的读者写者问题(Readers-Writers Problem)。GCD使用调度屏障(dispatch barriers)提供了一个优雅的解决方案来生成读写锁

当跟并发队列一起工作时,调度屏障是一族行为像序列化瓶颈的函数。使用GCD的barrier API确保了提交的闭包是指定队列中在特定时段唯一在执行的一个。也就是说必须在所有先于调度屏障提交的任务已经完成的情况下,闭包才能开始执行。

当轮到闭包时,屏障执行这个闭包并确保队列在此过程不会执行其他任务。一旦闭包完成,队列返回到默认的执行方式。GCD同时提供了同步和异步两种屏障函数。

下图说明了屏障函数应用于多个异步任务的效果:

025.png

注意队列开始就像普通的并发队列一样工作。但当屏障执行的时候,队列变成像顺序队列一样。就是说,屏障是唯一一个在执行的任务。在屏障完成后,队列恢复成普通的并发队列。

下面说明什么时候用 — 什么时候不应该用 — 屏障函数:

  • 自定义顺序队列:坏选择。因为顺序队列本

首页 上一页 1 2 3 4 5 6 下一页 尾页 4/6/6
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇iOS开发代码规范(通用) 下一篇iOS动画

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目