如何在Swift中使用后台线程?

422

如何在Swift中使用多线程?

dispatchOnMainThread:^{

    NSLog(@"Block Executed On %s", dispatch_queue_get_label(dispatch_get_current_queue()));

}];

你在转换哪个部分遇到了困难? - nschum
2
为什么在最后一行分号前面有一个]符号? - akashivskyy
3
如果您能解释一下您卡在哪里或需要什么帮助,那将会很有帮助。 - nsuinteger
4
如果正确答案真的帮助了你,你必须接受它,这也会帮助其他人找到正确的解决方案。 - Amit Singh
1
`DispatchQueue.global(qos: .background).async { print("Run on background thread")DispatchQueue.main.async { print("We finished that.") // only back on the main thread, may you access UI: label.text = "Done." }}` - Anurag Sharma
17个回答

847

Swift 3.0+

Swift 3.0进行了很多改进。在后台队列上运行某些内容,可以像这样实现:

DispatchQueue.global(qos: .userInitiated).async {
    print("This is run on a background queue")

    DispatchQueue.main.async {
        print("This is run on the main queue, after the previous code in outer block")
    }
}

Swift 1.2到2.3

let qualityOfServiceClass = QOS_CLASS_USER_INITIATED
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
    print("This is run on a background queue")

    dispatch_async(dispatch_get_main_queue(), { () -> Void in
        print("This is run on the main queue, after the previous code in outer block")
    })
})

Swift 1.2之前 - 已知问题

在Swift 1.1中,没有进行一些修改的情况下,苹果不支持上述语法。传递QOS_CLASS_USER_INITIATED实际上不起作用,而是使用Int(QOS_CLASS_USER_INITIATED.value)

更多信息请参见苹果文档


30
如果有人想要更像Swift的语法,我创建了Async,它在语法上添加了一些简便方法,例如Async.background {} - tobiasdm
我正在使用您的代码在xCode 6.0.1和iOS 8中。它会报错,显示“QOS_CLASS_BACKGROUND”返回类别是UInt32类型,而“dispatch_get_global_queue”需要第一个参数为int类型,因此出现了类型错误。 - Zalak Patel
1
@NikitaPronchik 这个答案不清楚吗?否则请随意编辑它。 - tobiasdm
请注意,您应该仅在具有低优先级的任务中使用.background QoS类。通常.userInitiated才是您真正需要的。 - Balázs Vincze
非常同意@BalázsVincze,谢谢!我会更新.userInitiated,因为它似乎是更明智的默认选项。 - tobiasdm
显示剩余3条评论

203

Dan Beaulieu在Swift5中给出的答案(自Swift 3.0.1以来也可行)。

Swift 5.0.1

extension DispatchQueue {

    static func background(delay: Double = 0.0, background: (()->Void)? = nil, completion: (() -> Void)? = nil) {
        DispatchQueue.global(qos: .background).async {
            background?()
            if let completion = completion {
                DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: {
                    completion()
                })
            }
        }
    }

}

使用方法

DispatchQueue.background(delay: 3.0, background: {
    // do something in background
}, completion: {
    // when background job finishes, wait 3 seconds and do something in main thread
})

DispatchQueue.background(background: {
    // do something in background
}, completion:{
    // when background job finished, do something in main thread
})

DispatchQueue.background(delay: 3.0, completion:{
    // do something in main thread after 3 seconds
})

1
太棒了,感谢您如此好地更新到Swift 3.0.1格式! - Dale Clifford
1
我比任何人都更多地使用扩展。但是,使用与原始版本完全相同的扩展存在真正的危险! - Fattie
@GitSyncApp - 你可能需要研究一下DispatchGroups。这里有很多关于它的问答。 - Fattie
1
忘掉那个链接吧。你只需要一个调度组 - 这非常非常简单;完全不用担心! - Fattie
1
@DilipJangid,除非您在“background”闭包中的工作非常非常非常长(~=无限),否则您无法这样做。此方法的持续时间为有限时间:即您的后台作业执行所需的时间。因此,只要您的后台作业执行时间+延迟已过去,就会立即调用“completion”闭包。 - frouo
显示剩余2条评论

126

最佳实践是定义一个可重复使用的函数,可以多次访问。

可重用函数:

例如,在全局函数中的类似AppDelegate.swift的位置。

func backgroundThread(_ delay: Double = 0.0, background: (() -> Void)? = nil, completion: (() -> Void)? = nil) {
    dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)) {
        background?()

        let popTime = dispatch_time(DISPATCH_TIME_NOW, Int64(delay * Double(NSEC_PER_SEC)))
        dispatch_after(popTime, dispatch_get_main_queue()) {
            completion?()
        }
    }
}

注意:在Swift 2.0中,将上面的 QOS_CLASS_USER_INITIATED.value 替换为 QOS_CLASS_USER_INITIATED.rawValue

用法:

A. 延迟3秒后在后台运行进程:

    backgroundThread(3.0, background: {
            // Your background function here
    })

B. 在后台运行一个进程,然后在前台运行完成:

    backgroundThread(background: {
            // Your function here to run in the background
    },
    completion: {
            // A function to run in the foreground when the background thread is complete
    })

C. 延迟3秒 - 注意在不使用背景参数的情况下使用完成参数:

    backgroundThread(3.0, completion: {
            // Your delayed function here to be run in the foreground
    })

1
很好的代码片段,应该是正确的答案。@Dale Clifford - LoVo
这是一种出色的高级现代 Swift-y 方法,可以从低级 C 库中访问旧式 GCD 方法。应该成为 Swift 的标准功能。 - Craig Grummitt
2
非常好。请确认,延迟仅适用于完成块。这意味着A中的延迟没有影响,后台块立即执行而不延迟。 - ObjectiveTC
1
你应该可以用background?()替换if(background != nil){ background!(); }以获得更简洁的语法? - Simon Bengtsson
1
请问您能否将这个更新为Swift 3的版本?自动转换器将其转换成了DispatchQueue.global(priority: Int(DispatchQoS.QoSClass.userInitiated.rawValue)).async {但是这会抛出一个错误,如cannot invoke initializer for type 'Int' with an argument list of type '(qos_class_t)'。可以在这里找到一个可行的解决方案(DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated).async)。 - Dev-iL
显示剩余4条评论

58

在Swift 4.2和Xcode 10.1中,我们有三种类型的队列:

1. 主队列: 主队列是一个串行队列,由系统创建并与应用程序的主线程相关联。

2. 全局队列: 全局队列是一个并发队列,我们可以根据任务的优先级来请求。

3. 自定义队列:可以由用户创建。自定义并发队列通过指定服务质量属性(QoS)始终映射到其中一个全局队列中。

DispatchQueue.main//Main thread
DispatchQueue.global(qos: .userInitiated)// High Priority
DispatchQueue.global(qos: .userInteractive)//High Priority (Little Higher than userInitiated)
DispatchQueue.global(qos: .background)//Lowest Priority
DispatchQueue.global(qos: .default)//Normal Priority (after High but before Low)
DispatchQueue.global(qos: .utility)//Low Priority
DispatchQueue.global(qos: .unspecified)//Absence of Quality

这些队列可以通过两种方式执行:

1. 同步执行

2. 异步执行

DispatchQueue.global(qos: .background).async {
    // do your job here
    DispatchQueue.main.async {
        // update ui here
    }
}

//Perform some task and update UI immediately.
DispatchQueue.global(qos: .userInitiated).async {  
    // Perform task
    DispatchQueue.main.async {  
        // Update UI
        self.tableView.reloadData()  
    }
}

//To call or execute function after some time
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
    //Here call your function
}

//If you want to do changes in UI use this
DispatchQueue.main.async(execute: {
    //Update UI
    self.tableView.reloadData()
})

来自 AppCoda:https://www.appcoda.com/grand-central-dispatch/

//This will print synchronously means, it will print 1-9 & 100-109
func simpleQueues() {
    let queue = DispatchQueue(label: "com.appcoda.myqueue")

    queue.sync {
        for i in 0..<10 {
            print("", i)
        }
    }

    for i in 100..<110 {
        print("Ⓜ️", i)
    }
}

//This will print asynchronously 
func simpleQueues() {
    let queue = DispatchQueue(label: "com.appcoda.myqueue")

    queue.async {
        for i in 0..<10 {
            print("", i)
        }
    }

    for i in 100..<110 {
        print("Ⓜ️", i)
    }
}

1
最佳线程教程 https://medium.com/@gabriel_lewis/threading-in-swift-simply-explained-5c8dd680b9b2 - Naresh
当您使用.background QoS或.userInitiated时,我没有看到任何更改,但对我来说,使用.background是有效的。 - user5683940
您可能看不出使用.background和.userInitiated QoS之间的区别,因为系统可以覆盖您的设置并将.background QoS提升到.userInitiated。这是在幕后优化主UI队列中使用的队列以使它们与父级的QoS匹配。 您可以使用Thread.current.qualityOfService检查当前线程的QoS。 - Karoly Nyisztor
DispatchQueue.main.async 并不会立即在 UX 线程上执行任何操作。它只是将其堆叠以在其他已完成的项目之后完成。 - Fattie

49

Swift 3 版本

Swift 3 使用新的 DispatchQueue 类来管理队列和线程。要在后台线程上运行某些内容,您可以使用以下代码:

let backgroundQueue = DispatchQueue(label: "com.app.queue", qos: .background)
backgroundQueue.async {
    print("Run on background thread")
}

或者如果你想要只用两行代码:

DispatchQueue.global(qos: .background).async {
    print("Run on background thread")

    DispatchQueue.main.async {
        print("We finished that.")
        // only back on the main thread, may you access UI:
        label.text = "Done."
    }
}

您还可以在这个教程中了解有关Swift 3中GDC的深入信息。


既然你的答案是最好的,我加入了一行代码,展示了如何“完成后回调”。随意修改或调整,祝愉快。 - Fattie

35

来自詹姆森·奎夫的教程

Swift 2

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
    //All stuff here
})

3
请确认一下,使用这个方法与使用已接受的答案相比有何优劣?这是因为它只是一个旧版本的 API 吗? - Allison
1
@Sirens 我认为这对于支持 < iOS 8 的应用程序非常有用。 - bperdue
我使用这个工具来强制处理iOS 8.2。 - μολὼν.λαβέ
DISPATCH_QUEUE_PRIORITY_DEFAULT 会回归到 QOS_CLASS_DEFAULT。所以你可以说它更高级/被广泛接受的语法。 - PostCodeism
一个DispatchQueue并不是一个线程。 - ScottyBlades

28

Swift 4.x

将此内容放入某个文件中:

func background(work: @escaping () -> ()) {
    DispatchQueue.global(qos: .userInitiated).async {
        work()
    }
}

func main(work: @escaping () -> ()) {
    DispatchQueue.main.async {
        work()
    }
}

然后在需要的地方调用它:

background {
     //background job
     main {
       //update UI (or what you need to do in main thread)
     }
}

28

Swift 5

为了简便起见,创建一个名为 "DispatchQueue+Extensions.swift" 的文件,并将以下内容复制进去:

import Foundation

typealias Dispatch = DispatchQueue

extension Dispatch {

    static func background(_ task: @escaping () -> ()) {
        Dispatch.global(qos: .background).async {
            task()
        }
    }

    static func main(_ task: @escaping () -> ()) {
        Dispatch.main.async {
            task()
        }
    }
}

使用方法:

Dispatch.background {
    // do stuff

    Dispatch.main { 
        // update UI
    }
}

22

你必须将想要在后台运行的更改与你想要在UI上运行的更新分开:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
    // do your task

    dispatch_async(dispatch_get_main_queue()) {
        // update some UI
    }
}

那么当后台语句(外部块)执行完毕时,dispatch_async(dispatch_get_main_queue()) { // update some UI } 会被调用吗? - justColbs
这不仅适用于Swift 2.3及以下版本吗? - Surz

9
虽然已有很好的答案,但我想分享一下我基于 Swift 5 的面向对象解决方案。
请查看:AsyncTask 概念上受 Android AsyncTask 启发,我用 Swift 编写了自己的类。 AsyncTask 使 UI 线程得到适当和轻松的使用。该类允许在后台执行操作并在 UI 线程上发布结果。
以下是几个使用示例:
示例1 -
AsyncTask(backgroundTask: {(p:String)->Void in//set BGParam to String and BGResult to Void
        print(p);//print the value in background thread
    }).execute("Hello async");//execute with value 'Hello async'

示例 2 -

let task2=AsyncTask(beforeTask: {
           print("pre execution");//print 'pre execution' before backgroundTask
        },backgroundTask:{(p:Int)->String in//set BGParam to Int & BGResult to String
            if p>0{//check if execution value is bigger than zero
               return "positive"//pass String "poitive" to afterTask
            }
            return "negative";//otherwise pass String "negative"
        }, afterTask: {(p:String) in
            print(p);//print background task result
    });
    task2.execute(1);//execute with value 1

它有两个通用类型:

  • BGParam - 在执行任务时发送到任务的参数的类型。

  • BGResult - 后台计算结果的类型。

    当创建AsyncTask时,可以将这些类型设置为任何您需要传入和传出后台任务的类型。但是,如果您不需要这些类型,您可以将其设置为未使用,仅使用Void或更短的语法:()

异步任务被执行时,经历三个步骤:

  1. beforeTask: ()->Void在UI线程上立即在任务执行之前调用。
  2. backgroundTask: (param: BGParam)->BGResult在后台线程上立即调用
  3. afterTask: (param: BGResult)->Void以来自后台任务的结果在UI线程上调用

4
这对我非常有效。做得很好,为什么不将它放到Github上呢? - 36 By Design

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接