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

Posted

技术标签:

【中文标题】如何在swift中使用后台线程?【英文标题】:How to use background thread in swift? 【发布时间】:2014-07-26 05:04:13 【问题描述】:

如何在swift中使用线程?

dispatchOnMainThread:^

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

];

【问题讨论】:

转换哪个部分有问题? 为什么最后一行的分号前有] 如果您能解释一下您遇到的问题或您需要帮助的地方,将会很有帮助。 如果它真的对你有帮助,你必须接受正确的答案,它也会帮助其他人找到正确的解决方案。 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." 【参考方案1】:

斯威夫特 3.0+

在 Swift 3.0 中有很多 modernized。在后台队列上运行看起来像这样:

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")
    )
)

Pre Swift 1.2 – 已知问题

从 Swift 1.1 开始,Apple 不支持上述语法,无需进行一些修改。传递QOS_CLASS_USER_INITIATED 并没有实际工作,而是使用Int(QOS_CLASS_USER_INITIATED.value)

欲了解更多信息,请参阅Apples documentation

【讨论】:

我在 xCode 6.0.1 和 ios 8 中使用您的代码。它给出错误为“QOS_CLASS_BACKGROUND”返回类,它是 UInt32 类型,“dispatch_get_global_queue”需要第一个参数作为 int,所以类型错误是来了。 所以在 Xcode 6.1.1 中我没有因为使用简单的“QOS_CLASS_BACKGROUND”而出错。修复了吗? @LucasGoossen 是的,它已被修复。我已经相应地更新了帖子。 @NikitaPronchik 答案不是很清楚吗?否则,请随时对其进行编辑。【参考方案2】:

Dan Beaulieu 在 swift5 中的回答(也适用于 swift 3.0.1)。

斯威夫特 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
)

【讨论】:

太棒了,感谢您更新到 Swift 3.0.1 格式! 我使用扩展的次数比任何活着的人都多。但是使用与原版完全不同的扩展程序存在真正的危险! @Frouo 非常优雅,是否可以在 4 个异步调用全部完成时添加完成处理程序?我知道这有点跑题了。 是的,忘记那个链接了。你所需要的只是一个调度组——非常非常简单;完全不用担心! @DilipJangid 你不能,除非你在background 闭包中的工作非常非常长(~=无限)。此方法将持续有限时间:您的后​​台作业需要执行的时间。因此,一旦您的后台作业执行时间+延迟过去,completion 闭包就会被调用。【参考方案3】:

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

可重用功能:

例如像 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
    )

【讨论】:

nice sn-p,应该是正确的答案。 @戴尔克利福德 出色的高级现代 Swift-y 方法可从低级 C 库访问老式 GCD 方法。应该成为 Swift 的标准。 非常好。请您确认一下,延迟仅适用于完成块。所以这意味着A.中的延迟没有影响,后台块立即执行,没有延迟。 您应该可以将if(background != nil) background!(); 替换为background?() 以获得更快捷的语法? 能否请您为 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)' 的错误。找到了一个可行的解决方案here (DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated).async)。【参考方案4】:

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."
    

您还可以在this tutorial 中获取有关 Swift 3 中 GDC 的一些深入信息。

【讨论】:

说。由于您的答案是最好的,所以我输入了一行代码,显示您如何“完成后回电”。随意放松或编辑,干杯【参考方案5】:

在 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)
    

【讨论】:

最佳线程教程medium.com/@gabriel_lewis/… 当您使用 .background QoS 或 .userInitiated 时,我没有看到任何变化,但对我来说,使用 .background 时效果很好 您可能看不到使用 .background 和 .userInitiated QoS 之间的任何区别,因为系统可以覆盖您的设置并将 .background QoS 提升为 .userInitiated。这是从主 UI 队列中使用的队列的幕后优化,以使它们与父级的 QoS 匹配。您可以使用 Thread.current.qualityOfService 检查当前线程的 QoS。 DispatchQueue.main.async 根本不会立即在 UX 线程上执行任何操作。它只是将要完成的事情堆叠起来,之后,其他正在完成的项目都完成了【参考方案6】:

来自Jameson Quave's tutorial

斯威夫特 2

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

【讨论】:

只是为了澄清,为什么要使用它而不是接受的答案?这只是一个较旧的 API 吗? @Sirens 我认为这对于支持 我将它用于 iOs 8.2 来强制进程。 DISPATCH_QUEUE_PRIORITY_DEFAULT 恢复为 QOS_CLASS_DEFAULT。所以我想你可以说它是更高级/可接受的语法。 DispatchQueue 不是线程。【参考方案7】:

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)
     

【讨论】:

【参考方案8】:

斯威夫特 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
    

【讨论】:

【参考方案9】:

您必须将要在后台运行的更改与要在 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 在后台语句(外块)执行完毕时被调用? 这不只适用于 Swift 2.3 及以下版本吗?【参考方案10】:

由于上面已经回答了 OP 问题,我只想添加一些速度注意事项:

我不建议以 .background 线程优先级运行任务,尤其是在任务似乎分配在低功耗内核上的 iPhone X 上。

以下是来自计算密集型函数的一些真实数据,该函数从 XML 文件(带有缓冲)读取并执行数据插值:

设备名称/.background/.utility/.default/.userInitiated/。用户交互

    iPhone X:18.7s / 6.3s / 1.8s / 1.8s / 1.8s iPhone 7:4.6s / 3.1s / 3.0s / 2.8s / 2.6s iPhone 5s:7.3s / 6.1s / 4.0s / 4.0s / 3.8s

请注意,并非所有设备的数据集都相同。它在 iPhone X 上是最大的,在 iPhone 5s 上是最小的。

【讨论】:

【参考方案11】:

虽然答案很好,但无论如何我想分享我的面向对象解决方案最新的 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

它有 2 种泛型类型:

BGParam - 执行时发送给任务的参数类型。

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

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

当一个异步任务执行时,它会经过 3 个步骤:

    beforeTask:()-&gt;Void 在任务执行前在 UI 线程上调用。 backgroundTask: (param:BGParam)-&gt;BGResult 之后立即在后台线程上调用 afterTask:(param:BGResult)-&gt;Void 在 UI 线程上调用,结果来自后台任务

【讨论】:

这对我来说非常有用。干得好,为什么不放到github上呢?【参考方案12】:

线程的多用途函数

public enum QueueType 
        case Main
        case Background
        case LowPriority
        case HighPriority

        var queue: DispatchQueue 
            switch self 
            case .Main:
                return DispatchQueue.main
            case .Background:
                return DispatchQueue(label: "com.app.queue",
                                     qos: .background,
                                     target: nil)
            case .LowPriority:
                return DispatchQueue.global(qos: .userInitiated)
            case .HighPriority:
                return DispatchQueue.global(qos: .userInitiated)
            
        
    

    func performOn(_ queueType: QueueType, closure: @escaping () -> Void) 
        queueType.queue.async(execute: closure)
    

像这样使用它:

performOn(.Background) 
    //Code

【讨论】:

【参考方案13】:

我真的很喜欢 Dan Beaulieu 的回答,但它不适用于 Swift 2.2,我认为我们可以避免那些讨厌的强制解包!

func backgroundThread(delay: Double = 0.0, background: (() -> Void)? = nil, completion: (() -> Void)? = nil) 

    dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) 

        background?()

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

【讨论】:

【参考方案14】:

Grand Central Dispatch 用于处理我们 iOS 应用程序中的多任务处理。

你可以使用这个代码

// Using time interval

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+1) 
    print("Hello World")


// Background thread
queue.sync 
     for i in 0..<10 
          print("Hello", i)
     


// Main thread
for i in 20..<30 
     print("Hello", i)

更多信息请使用此链接:https://www.programminghub.us/2018/07/integrate-dispatcher-in-swift.html

【讨论】:

【参考方案15】:
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), 
    // Conversion into base64 string
    self.uploadImageString =  uploadPhotoDataJPEG.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.EncodingEndLineWithCarriageReturn)
)

【讨论】:

【参考方案16】:

下面的代码是否有缺点(需要在之后启动前景屏幕时)?

import Foundation
import UIKit

class TestTimeDelay 

    static var connected:Bool = false
    
    static var counter:Int = 0

    static func showAfterDelayControl(uiViewController:UIViewController) 
        NSLog("TestTimeDelay", "showAfterDelayControl")
    
    
    static func tryReconnect() -> Bool 
        counter += 1
        NSLog("TestTimeDelay", "Counter:\(counter)")
        return counter > 4
    

    static func waitOnConnectWithDelay(milliseconds:Int, uiViewController: UIViewController) 
        
        DispatchQueue.global(qos: .background).async 
            DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + DispatchTimeInterval.milliseconds(milliseconds), execute: 
                waitOnConnect(uiViewController: uiViewController)
            )
        
    

    static func waitOnConnect(uiViewController:UIViewController) 

        connected = tryReconnect()
        if connected 
            showAfterDelayControl(uiViewController: uiViewController)
        
        else 
            waitOnConnectWithDelay(milliseconds: 200, uiViewController:uiViewController)
        
     
   

【讨论】:

更简单???.......【参考方案17】:

在 Swift 4.2 中这有效。

import Foundation

class myThread: Thread

    override func main() 
        while(true) 
            print("Running in the Thread");
            Thread.sleep(forTimeInterval: 4);
        
    


let t = myThread();
t.start();

while(true) 
    print("Main Loop");
    sleep(5);

【讨论】:

我希望这没有被否决。我一直在网上搜索有关如何在没有 GCD 的情况下在 Swift 中创建实际 threads 的示例,但很难找到任何东西。 GCD 不适合我的用例,我发现这个小例子很有帮助。

以上是关于如何在swift中使用后台线程?的主要内容,如果未能解决你的问题,请参考以下文章

如何从 Swift 的后台线程中从 CoreData 中获取未保存的数据?

如何从后台线程 Swift UI GET 请求发布更改

Core Data,在后台线程中修改 NSManagedObject

Swift - 内部有后台线程的退出功能

使用“自我”的更好方式。在后台线程中?

ios - 如何在 ios swift 中正确实现后台获取