为什么要用GCD-Swift2.x

当今世界,多核已然普及。但是APP却不见得很好的跟上了这个趋势。APP
想要利用好多核就必须可以保证任务能有效的分配。并行执行可以让APP同时执行很多
的任务。这个其实很难,但是有了GCD一切都变得简单了很多。

你并不是一定要写一个大并发的APP才需要用GCD。使用GCD可以让你的APP更快的
响应用户的操作,不用要等到你的UI或者服务等到执行完成。一般来说你会把各种任务
都分配给其他核心去执行,而你的主线程(UI线程)可以随时处理用户的操作。
GCD可以让这些变得简单。

线程

传统的多任务分发方式是使用线程。在一个多核的设备上,每一个新的线程都可以被分配在一个cpu核心
上,与其他的线程并行执行。

单核心的cpu麻烦一些,系统会不停地在几个线程之间切换以保证每个线程都有机会执行。这样做的效果
是看起来像是并行执行的,但是其实多个线程的不同任务之间是顺序执行的。

但是使用线程也会遇到一个很大的问题。数据在线程之间的正确传递就是一个很大的难题了。线程之间的
同步和互斥又会变得很诡异难以调试。而且,为了保证APP的高效快速运行,开辟多少线程也是一个需要思考的
问题。因为,创建和销毁线程也是有很大的资源消耗的。于是很多的系统都提供了一个叫做线程池
的概念来解决这个问题。所有的线程创建后都放在这个池子里统一管理。

同步

当你有多个线程在执行的时候,你一般都会遇到一个问题Race Condition,实际的运算结果
取决于那个线程先获得共享数据。一个经典的例子就是:银行账户问题。

class BankAccount {
    var balance: Double?

    //...
}

// 创建一个账号,给这个账号存100块
var account = BankAccount()
account.balance = 100

// 第一个线程:取10块
func withdraw() {
    let balance = account.balance
    account.balance = balance! - 10
}

// 第二个线程:增加10%的利息
func accrue() {
    let balance = account.balance
    account.balance = balance! * 1.10
}

// 那么最后:account.balance = ?

最后的结果取决于这两个线程哪一个先执行。在并行的条件下执行的先后顺序是不定的。但是执行顺序不同
最后的balance值就是不同的。

上面的代码会有多少个可能的不同结果?balance都会是什么值?
Race Condition即使在单核设备下也会发生。

对于这些问题,传统的解决方法如下:
* 信号量(semaphore)- 用来控制一组有限资源的消费。线程等待,知道资源可用。
* 互斥(Mutex)- 一次只允许一个线程执行。当一个线程持有mutex的时候其他线程等待。
* 条件变量(Condvar)- 线程等待直到某些条件为真。

队列

GCD把线程的创建、回收以及线程的同步等进一步抽象为队列(Queue)统一管理。

队列,简单而言,就是一个可以让数据按照先进先出的顺序执行。一个APP可以创建多个队列,并且多个
队列之间可以并行的处理各自的任务,每个队列内部的任务顺序执行。

队列比线程有很多的优势。第一,GCD库屏蔽了线程管理的繁琐部分。队列会在需要的时候自动创建线程
并且在不需要的时候回收。其次,GCD库会根据系统的cpu核心数创建最佳数量的线程。最后,队列只会
在需要的时候创建线程,所以资源利用会得到优化。

总之,队列给你了你线程能给的,但是又不用考虑具体线程的操作。

GCD有三种队列:
* 顺序队列(Serial)- 每次执行一个任务,按照任务加入队列的顺序。一个任务执行完成后执行下一个。
* 并发(Concurrent)- 按照任务加入队列的顺序开始,但是后面的任务不用等到前面的任务执行完成就可以开始。
* 主线程(Main)- 一个预先开启的序列线程。这个队列中包含一个NSRunLoop实例。你的APP总是在这个队列中运行。

系统提供了几种并发队列。这些队列有自己专属的QoS(服务质量种类)。这个服务质量种类是用来表示你提交的
任务的意图是什么,这样GCD可以有针对性的优化。
* QOS_CLASS_USER_INteraCTIVE 这个用户交互(user interactive)表示任务需要立即执行,以便APP
给用户一个良好的用户体验。一般用于更新UI、处理事件和小延迟的处理。在你的整个APP中,这以种类的任务应该保持
一个较低的总量。

  • QOS_CLASS_USER_INITIATED 用户初始化(user initiated)表示任务是在UI初始化的,并且可以
    异步执行。这个一般用于处理用户等待的需要理解给出运行结果,或者任务需要理解完成用户交互的情况。

  • QOS_CLASS_UTILITY 通用(utility)表示长期执行的任务,一般来说用户可以见到任务执行的比例。
    一般用来处理大的计算、I/O、网络以及不简单的数据提交等类似任务。这一种类做了电量优化。

  • QOS_CLASS_BACKGROUND 后台运行(background)表示用户并不知道任务在运行中。这个一般用于
    数据的提前加载、维护,以及其他无需用户交互和任务完成时间无明显限制的情况。

这里需要注意一点,苹果的API也会用到这些全局分发的队列。所以,在这些队列里并不是只有你的任务在执行。当然,
如前文所述你也可以创建你自己的队列。你可以选择4种全局队列,主队列,还有两种自定义队列可以选择。

Closure

队列的任务使用closure封装。

你也可以使用方法加入队列,不过这里只使用block

这里有一个closure的例子,让你对closure有一个大概的印象。swift的closure就和Objective-C的block
差不多。

func makeIncrementer() -> ((Int) -> Int) { func addOne(number: Int) -> Int {
        return 1 + number
    }

    return addOne
}

var increment = makeIncrementer()

increment(9)

上面的例子makeIncrementer返回一个closure,这个closure需要一个整型参数。

Hello dispatch world!

这里需要注意一点:Objective-C的block和Swift的closure。
Swift的closure和Objective-C的block是兼容的。所以你完全可以把Swift的closure传入Objective-C
中需要block参数的方法中。Swift的closure和方法是同一类型的,所以你甚至可以把swift的方法名传递过去。

Closure和block的上下文处理语法基本一样。唯一不同的是变量在closure中直接就是可变的。也就是说Objective-C
中的__block关键字在Swift的closure中是默认行为。

dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INteraCTIVE,0),{ print("hello dispatch:- user interactive") })

dispatch_async(dispatch_get_main_queue(),{ print("hello dispatch:- main queue") })

调用dispatch_async(),GCD就会给队列添加一个closure任务,然后继续代码的执行完全不会等待closure
的结束。在我们的例子中,第一次dispatch_async是给QOS_CLASS_USER_INteraCTIVE的类型的全局
队列添加了一个任务。第二次是给dispatch_get_main_queue,也就是主线程添加了一个任务。

下面看一个自创队列的例子:

@IBAction func concurrentAction(sender: AnyObject) {
    let concurrentQueue = dispatch_queue_create("concurrent.test.queue",disPATCH_QUEUE_CONCURRENT) //1
    for i in 0...1000 { 
        dispatch_async(concurrentQueue){ //2
            NSThread.sleepForTimeInterval(1) //3 
            print("print concurrent queue:- \(i)") //4
        }
    }
}

这里一步一步的介绍一下:
1. 创建一个名称为concurrent.test.queue的并发队列。
2. 做一个1001次的循环,每个循环给这个队列添加一个closure。以上的写法只是一个简写,其实是这样的:

dispatch_async(concurrentQueue,{ //2
        NSThread.sleepForTimeInterval(1) //3
        print("print concurrent queue:- \(i)") 
})
  1. 当前线程休眠一秒

Barriers

很多人看到这里就会想,并发队列在哪儿体现出来队列的概念了呢?这分明就是一个把一堆closure扔进去分开执行的或者Set(集合)
而已嘛。

目前来看是的,但是当你遇到barrier的时候对列就真的变成队列了。你可以使用dispatch_barrier_sync
dispatch_barrier_async两个方法把closure加入队列中。这个时候就很有意思了。扔进去的closure并
不会立刻执行,而是要等。等到在这个closure之前扔进队列的全部closure都执行完成之后才开始执行。然后,
在这个barrier的closure执行完成之后,在它后面扔进队列的closure才会被执行。可以把barrier的closure认为
是一个特殊的点,在这个点的从前到后都是顺序执行的。除此之外的点,还是并行执行。

我们来看看具体的例子:

let concurrentQueue = dispatch_queue_create("concurrent.test.queue",disPATCH_QUEUE_CONCURRENT)

var count: Int = 0

for _ in 0...100 {
    dispatch_async(concurrentQueue){
        NSThread.sleepForTimeInterval(1)
        print("print concurrent queue:- \(count++)")
    }
}

dispatch_barrier_async(concurrentQueue,{
    print("##ASYNC in barrier,concurrent queue - START")
    for _ in 1...10 {
        NSThread.sleepForTimeInterval(0.5)
    }
    print("##ASYNC in barrier,concurrent queue - END")
})

for _ in 0...100 {
    dispatch_async(concurrentQueue){
        NSThread.sleepForTimeInterval(1)
        print("print concurrent queue:- \(count++)")
    }
}

dispatch_barrier_sync(concurrentQueue,{
    print("##SYNC in barrier,concurrent queue - START")
    for _ in 1...10 {
        NSThread.sleepForTimeInterval(0.5)
    }
    print("##SYNC in barrier,concurrent queue - END")
})

for _ in 0...100 {
    dispatch_async(concurrentQueue){
        NSThread.sleepForTimeInterval(1)
        print("print concurrent queue:- \(count++)")
    }
}

注意barrier最好是用在自己创建的并发队列上。否则的话dispatch_barrier_async的效果就和dispatch_async一样,而
dispatch_barrier_sync就和dispatch_sync一样。dispatch_barrier_sync要分配的队列是当前队列
的话会造成死锁。

读写锁

先看一个新闻累的单例的例子。这个例子的最初形态中不是一个线程安全的单例:

class NewsFeed {
    static let sharedInstance = NewsFeed() //1

    private init() {} //2

    private var _news: [String] = []
    var news: [String] {
        return _news
    }

    func addNews(n: String) {
        _news.append(n)
    }
}

有这么一个新闻的类。
1. 实现一个单例。Swift的单例明显要比Objective-C简单了很多。是的,Swift的static属性自动内置了
dispatch_once。所以,这么一样就实现了Swift的单例。
2. init方法只能给类的内部调用,但是不能给外部在调用,否则就不是单例了。

用户可以调用news属性来读取全部的新闻,也可以通过调用方法addNews来添加新闻。
当时当多个线程都可以访问addNews方法的时候,那么news属性读出来的新闻列表就有很大的可能是错的。
我们现在使用barrier来确保这个功能可以正确的执行。

解决办法就是任何的线程要添加新闻,那么就必须通过barrier这一道关口。在添加新闻的时候只有一个closure执行。
其他的添加closure等待。

为了保证线程的安全,读取新闻的操作也只能在concurrentQueue上执行。但是这次我们是需要立即返回结果的
没法使用dispatch_async。这个时候使用dispatch_sync就是最好的选择了。使用dispatch_sync
可以等到方法执行完毕,并返回我们需要的结果。dispatch sync可以知道dispatch barrier的进度如何,添加了
几条新闻。也可以让closure执行完成并返回。

但是使用dispatch sync需要很小心。如前所述,如果你给dispatch sync分配的队列是当前正在运行的队列
会造成死锁。因为调用会等待这个closure结束,这个closure甚至可能都没法开始。因为这个closure需要等到
它前面运行的closure结束之后才能开始。

private var _news: [String] = []
    var news: [String] {
        var newscopy: [String]!
        dispatch_sync(concurrentQueue){ //1
            newscopy = self._news //2
        }
        return newscopy
    }

下面逐一解释:
1. dispatch sync同步分配到concurrentQueue上执行读取操作。
2. 保存一份新闻的拷贝给newscopy并返回。

现在这个新闻单例就是线程安全的了。下面我们看一下完整的代码:

import Foundation

class NewsFeed {
    static let sharedInstance = NewsFeed()
    private let concurrentQueue = dispatch_queue_create("newsFeed.queue.concurrent",disPATCH_QUEUE_CONCURRENT)

    private init() {}

    private var _news: [String] = []
    var news: [String] {
        var newscopy: [String]!
        dispatch_sync(concurrentQueue){
            newscopy = self._news
        }
        return newscopy
    }

    func addNews(n: String) {
        dispatch_barrier_async(concurrentQueue){
            self._news.append(n)
            dispatch_async(dispatch_get_main_queue()){
                self.newsAddednotification()
            }
        }
    }

    func newsAddednotification() {
        // post notification
    }
}

欢迎加群互相学习,共同进步。QQ群:iOS: 58099570 | Android: 330987132 | nodejs:329118122 | Go-Scala:217696290 | Python:336880185 | 做人要厚道,转载请注明出处!

为什么要用GCD-Swift2.x的更多相关文章

  1. HTML5 WebSocket实现点对点聊天的示例代码

    这篇文章主要介绍了HTML5 WebSocket实现点对点聊天的示例代码的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. ios – 在Swift的UIView中找到UILabel

    我正在尝试在我的UIViewControllers的超级视图中找到我的UILabels.这是我的代码:这是在Objective-C中推荐的方式,但是在Swift中我只得到UIViews和CALayer.我肯定在提供给这个方法的视图中有UILabel.我错过了什么?我的UIViewController中的调用:解决方法使用函数式编程概念可以更轻松地实现这一目标.

  3. ios – 异常断点处于活动状态时,应用程序在启动时崩溃

    我刚开始继续开发一款适用于商店的传统iPad应用程序.我注意到项目中的异常断点未启用.当我启用它时,应用程序在启动时崩溃,但在输出窗口中没有给出任何信息,而在线程视图中只有相当无用的信息(见下文)我试着解决它..>将Autolayout设置为关闭.>通过编辑和重新保存故事板文件..但到目前为止没有运气.我的猜测是,故事板中的某些内容被破坏了,因为AppDelegates“确实完成了启动……”

  4. ios – 在Swift中将输入字段字符串转换为Int

    所以我非常擅长制作APP广告Swift,我试图在文本字段中做一些非常简单的输入,取值,然后将它们用作Int进行某些计算.但是’vardistance’有些东西不正确它是导致错误的最后一行代码.它说致命错误:无法解开Optional.None解决方法在你的例子中,距离是一个Int?否则称为可选的Int..toInt()返回Int?因为从String到Int的转换可能失败.请参阅以下示例:

  5. 如何在iOS中检测文本(字符串)语言?

    例如,给定以下字符串:我想检测每个声明的字符串中使用的语言.让我们假设已实现函数的签名是:如果没有检测到语言,则返回可选字符串.因此,适当的结果将是:有一个简单的方法来实现它吗?

  6. ios – Swift 4添加手势:覆盖vs @objc

    我想在我的视图中添加一个手势,如下所示:但是,在Swift4中,我的编译器给出了以下错误:建议添加@objc以将此实例方法公开给Objective-C.实现此目的的另一个选项将覆盖touchesBegan()函数并使用它来处理点击.我试图以“Swift”的方式做到这一点,而不必带入Obj-C.有没有纯粹的Swift方式来添加这个轻击手势而不使用@objc?

  7. xamarin – 崩溃在AccountStore.Create().保存(e.Account,“);

    在Xamarin.Forms示例TodoAwsAuth中https://developer.xamarin.com/guides/xamarin-forms/web-services/authentication/oauth/成功登录后,在aOnAuthenticationCompleted事件中,应用程序在尝试保存到Xamarin.Auth时崩溃错误说不能对钥匙串说期待着寻求帮助.解决方法看看你

  8. ios – 将视频分享到Facebook

    我正在编写一个简单的测试应用程序,用于将视频从iOS上传到Facebook.由于FacebookSDK的所有文档都在Objective-C中,因此我发现很难在线找到有关如何使用Swift执行此操作的示例/教程.到目前为止我有这个在我的UI上放置一个共享按钮,但它看起来已禁用,从我读到的这是因为没有内容设置,但我看不出这是怎么可能的.我的getVideoURL()函数返回一个NSURL,它肯定包含视

  9. ios – Objective-C中“and”关键字的含义是什么?

    我在Xcode中输入了一条评论,但忘了领先//.我注意到了这一点并且突出显示为关键字.我做了一些谷歌搜索,但我似乎无法弄清楚它做了什么.这是什么意思?解决方法它是&&的同义词.见iso646.h.

  10. xcode – 错误“线程1:断点2.1”

    我正在研究RESTAPI管理器.这是一个错误,我无法解决它.我得到的错误在下面突出显示.当我打电话给这个班级获取资源时:我评论的线打印:Thread1:breakpoint2.1我需要修复错误的建议.任何建议都非常感谢解决方法您可能在不注意的情况下意外设置了断点.单击并拖动代表断路器外部断点的蓝色刻度线以将其擦除.

随机推荐

  1. Swift UITextField,UITextView,UISegmentedControl,UISwitch

    下面我们通过一个demo来简单的实现下这些控件的功能.首先,我们拖将这几个控件拖到storyboard,并关联上相应的属性和动作.如图:关联上属性和动作后,看看实现的代码:

  2. swift UISlider,UIStepper

    我们用两个label来显示slider和stepper的值.再用张图片来显示改变stepper值的效果.首先,这三个控件需要全局变量声明如下然后,我们对所有的控件做个简单的布局:最后,当slider的值改变时,我们用一个label来显示值的变化,同样,用另一个label来显示stepper值的变化,并改变图片的大小:实现效果如下:

  3. preferredFontForTextStyle字体设置之更改

    即:

  4. Swift没有异常处理,遇到功能性错误怎么办?

    本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

  5. 字典实战和UIKit初探

    ios中数组和字典的应用Applicationschedule类别子项类别名称优先级数据包contactsentertainment接触UIKit学习用Swift调用CocoaTouchimportUIKitletcolors=[]varbackView=UIView(frame:CGRectMake(0.0,0.0,320.0,CGFloat(colors.count*50)))backView

  6. swift语言IOS8开发战记21 Core Data2

    上一话中我们简单地介绍了一些coredata的基本知识,这一话我们通过编程来实现coredata的使用。还记得我们在coredata中定义的那个Model么,上面这段代码会加载这个Model。定义完方法之后,我们对coredata的准备都已经完成了。最后强调一点,coredata并不是数据库,它只是一个框架,协助我们进行数据库操作,它并不关心我们把数据存到哪里。

  7. swift语言IOS8开发战记22 Core Data3

    上一话我们定义了与coredata有关的变量和方法,做足了准备工作,这一话我们来试试能不能成功。首先打开上一话中生成的Info类,在其中引用头文件的地方添加一个@objc,不然后面会报错,我也不知道为什么。

  8. swift实战小程序1天气预报

    在有一定swift基础的情况下,让我们来做一些小程序练练手,今天来试试做一个简单地天气预报。然后在btnpressed方法中依旧增加loadWeather方法.在loadWeather方法中加上信息的显示语句:运行一下看看效果,如图:虽然显示出来了,但是我们的text是可编辑状态的,在storyboard中勾选Editable,再次运行:大功告成,而且现在每次单击按钮,就会重新请求天气情况,大家也来试试吧。

  9. 【iOS学习01】swift ? and !  的学习

    如果不初始化就会报错。

  10. swift语言IOS8开发战记23 Core Data4

    接着我们需要把我们的Rest类变成一个被coredata管理的类,点开Rest类,作如下修改:关键字@NSManaged的作用是与实体中对应的属性通信,BinaryData对应的类型是NSData,CoreData没有布尔属性,只能用0和1来区分。进行如下操作,输入类名:建立好之后因为我们之前写的代码有些地方并不适用于coredata,所以编译器会报错,现在来一一解决。

返回
顶部