V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
iOS 开发实用技术导航
NSHipster 中文版
http://nshipster.cn/
cocos2d 开源 2D 游戏引擎
http://www.cocos2d-iphone.org/
CocoaPods
http://cocoapods.org/
Google Analytics for Mobile 统计解决方案
http://code.google.com/mobile/analytics/
WWDC
https://developer.apple.com/wwdc/
Design Guides and Resources
https://developer.apple.com/design/
Transcripts of WWDC sessions
http://asciiwwdc.com
Cocoa with Love
http://cocoawithlove.com/
Cocoa Dev Central
http://cocoadevcentral.com/
NSHipster
http://nshipster.com/
Style Guides
Google Objective-C Style Guide
NYTimes Objective-C Style Guide
Useful Tools and Services
Charles Web Debugging Proxy
Smore
iOran
V2EX  ›  iDev

使用 GCD 进行线程切换,主/背景 线程切换的最佳用例是?

  •  1
     
  •   iOran · 2016-01-12 10:23:52 +08:00 · 3734 次点击
    这是一个创建于 3243 天前的主题,其中的信息可能已经有所发展或是发生改变。

    在背景线程请求完数据, block 异步带回需要的数据,此时,应该切换到主线程更新 UI 。更新 UI 可能是一大坨代码,所以我的做法是尽可能将这一坨封装成一个函数。更复杂一些,这个更新 UI 的函数的内部,又要异步去请求数据,请求完数据 block 回来又要切换回主线程更新 UI 。

    最优的推荐做法,应该是进入这个更新 UI 的封装函数之前,切换到 main-thread;还是在这个封装函数内部最开始的位置,切换到 Main-thread?

    我使用 Masonry 来布局,最近老是报:

    This application is modifying the autolayout engine from a background thread, which can lead to engine corruption and weird crashes. This will cause an exception in a future release.

    我知道 layout 应该在主线程做,但难免,这里还是出了问题,那有没有什么最优做法,从编码习惯上就可以规范,保证线程切换不出错呢。所以有了如上问题。

    第 1 条附言  ·  2016-01-13 09:32:58 +08:00

    关于原问题,我个人目前知道的比较好的实践方法是在离开函数前,就切换到主线程,从编程习惯上减少犯错(遗漏)的可能性。

    关于增加的问题(dispatch_group_async/notify 押尾执行),请参考如下 Blog: http://www.jianshu.com/p/5617ad407678

    里面关于 dispatch_group_enter/leave 手动控制 一些列异步任务 完成的使用方法。

    21 条回复    2016-01-28 10:42:55 +08:00
    kobe1941
        1
    kobe1941  
       2016-01-12 10:26:47 +08:00
    你应该改设计,避免在更新 UI 的时候还要再去请求数据,多个数据请求可以用 dispatch_group ,请求都完成后再统一更新 UI
    vincentxue
        2
    vincentxue  
       2016-01-12 10:48:34 +08:00
    楼主最好贴点代码看看,我觉得你代码可能写的有问题。

    是进入这个更新 UI 的封装函数之前,切换到 main-thread;还是在这个封装函数内部最开始的位置,切换到 Main-thread?

    无论是苹果官方 Demo 还是开源库绝大多数都是用的第一种情况。
    iOran
        3
    iOran  
    OP
       2016-01-12 10:56:27 +08:00
    @kobe1941 那如果有些数据是要基于 **用户的选择** 或者 **不同的用户** 而请求的数据,还是有可能碰到这种情况吧?这里应该如何做?

    既然说到 dispatch_group_async/notify ,我还想问问,如果加入到 queue 中的 **一系列任务** 是 **异步的**,此时, notify 到的 block 只是代表 group 已经将这 **一系列任务** 执行完,但它们(一系列任务)的异步执行结果 block 回来可能在 notify 之后,这种情况应该怎么处理?

    例如这种情况,使用 group 真心爽:

    // 获得系统提供的 Global Dispatch Queue 。
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // 创建 Dispatch Group ,注意跟 dispatch_queue_create 一样,在 MRC 环境下或者 iOS 6 之前的版本 ARC 环境下需要 用 dispatch_release(group)。

    dispatch_group_t group = dispatch_group_create();

    // 添加任务 Block 到任务队列 queue 中。
    dispatch_group_async(group, queue, ^{NSLog(@"blk0");});
    dispatch_group_async(group, queue, ^{NSLog(@"blk1");});
    dispatch_group_async(group, queue, ^{NSLog(@"blk2");});

    // 通知 group ,那几个任务都结束了。” Done ”消息就属于押尾执行的任务。
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"done");});

    但是如果 **添加任务 Block 到任务队列 queue 中** 变成这样:

    // 添加任务 Block 到任务队列 queue 中。
    dispatch_group_async(group, queue, ^{异步任务 1 ,执行完执行 ^(parameters){} });
    dispatch_group_async(group, queue, ^{异步任务 2 ,执行完执行 ^(parameters){} });
    dispatch_group_async(group, queue, ^{异步任务 3 ,执行完执行 ^(parameters){} });

    问题出现了:它们(异步任务 1 , 2 , 3)的异步执行结果 block 回来可能在 notify 之后。怎么破?
    iOran
        4
    iOran  
    OP
       2016-01-12 10:59:47 +08:00
    @vincentxue 谢谢了。我也侧重第一种情况。应该是我代码看得少了,应该找点开源库看看人家的实现。
    aaaron7
        5
    aaaron7  
       2016-01-12 13:40:29 +08:00
    都一样。你遇到这报错,八成是你代码问题。
    aaaron7
        6
    aaaron7  
       2016-01-12 13:41:55 +08:00
    @iOran
    你爽点也太低了。

    你去用 reactive cocoa ,那才叫爽。
    iOran
        7
    iOran  
    OP
       2016-01-12 14:13:37 +08:00
    @aaaron7 谢谢提醒,反过头会去看看。能否先帮忙顺便解答下 dispatch_group_async 中的任务,如果也是异步的情况下,如何保证 notify 押尾执行。
    aaaron7
        8
    aaaron7  
       2016-01-12 14:21:19 +08:00
    @iOran
    GCD 用得不多,我想到的方法就是利用变量来 track 每一个任务的完成情况,每个任务执行完毕后都检查一次。

    但是用 rac 就可以很优雅的实现这个逻辑:


    // Performs 2 network operations and logs a message to the console when they are
    // both completed.
    //
    // +merge: takes an array of signals and returns a new RACSignal that passes
    // through the values of all of the signals and completes when all of the
    // signals complete.
    //
    // -subscribeCompleted: will execute the block when the signal completes.

    [[RACSignal
    merge:@[ [client fetchUserRepos], [client fetchOrgRepos] ]]
    subscribeCompleted:^{
    NSLog(@"They're both done!");
    }];
    EPr2hh6LADQWqRVH
        9
    EPr2hh6LADQWqRVH  
       2016-01-12 14:21:21 +08:00
    非 ios 程序员我 Google 了一下才了解到这个语境下 GCD 的意义。。
    ios 开发论坛药丸的节奏
    raysonx
        10
    raysonx  
       2016-01-12 14:31:56 +08:00
    @avastms +23333333333333
    標題被屏蔽為「使用 *** 进行线程切换,主 /背景 线程切换的最佳用例是?」就笑尿了
    loveuqian
        11
    loveuqian  
       2016-01-12 14:34:34 +08:00 via iPhone
    @avastms
    哈哈哈哈哈哈哈
    huangweihua
        12
    huangweihua  
       2016-01-12 14:44:25 +08:00
    ```
    /**
    主线程回调

    - parameter block: 执行 Block
    */
    func dispatch_async_safely_main_queue(block: ()->()) {
    if NSThread.isMainThread() {
    block()
    } else {
    dispatch_async(dispatch_get_main_queue()) {
    block()
    }
    }
    }
    ```
    vincentxue
        13
    vincentxue  
       2016-01-12 14:50:59 +08:00 via iPhone
    @iOran 你把 group 用错了,但你这样用也不能算错。

    你加进去的三个 nslog 是同步任务,那你直接执行或者直接放在一个队列里就好了,没必要用 group 。

    如果是异步操作,应该配合 group 的 enter/leave 方法使用,这样才能达到你要的效果。
    hzm0318hzm
        14
    hzm0318hzm  
       2016-01-12 14:51:40 +08:00
    @iOran 这样异步使用 group 的话使用 dispatch_group_enter()/dispatch_group_leave() 看你的异步队列到最后能不能执行你收尾的函数
    vincentxue
        15
    vincentxue  
       2016-01-12 14:53:10 +08:00 via iPhone
    @huangweihua 你的方法如果 block 传入 nil 会怎样? oc 是肯定 crash 了。
    iOran
        16
    iOran  
    OP
       2016-01-12 15:54:08 +08:00
    @aaaron7 Merge 的对象,不管时异步/同步都是一样吗?
    iOran
        17
    iOran  
    OP
       2016-01-12 15:56:37 +08:00
    @vincentxue @hzm0318hzm 谢谢两位,知道怎么操作了。后续补答案。

    @vincentxue 三个 nslog 是同步任务是考虑到这些同步任务无所谓执行的先后关系,所以这么丢。
    LGA1150
        18
    LGA1150  
       2016-01-12 21:41:40 +08:00
    @raysonx 几年前贴吧确实屏蔽了这个关键字,不过没有影响到标题
    iOran
        19
    iOran  
    OP
       2016-01-13 09:33:32 +08:00
    @LGA1150 原来这些人是这个意思。。。。
    huangweihua
        20
    huangweihua  
       2016-01-27 17:51:54 +08:00
    @vincentxue 没做测试,传入 nil , nil send_msg 不会 crash 。 swift 的话,后边没有加 ?,所以没办法传入 nil
    vincentxue
        21
    vincentxue  
       2016-01-28 10:42:55 +08:00
    @huangweihua 你的比较特殊,是 block ,执行一个 为 nil 的 block 会 crash 的。你试试。所以回调一般会写成:!completionHandler ?: completionHandler();
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3186 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 14:04 · PVG 22:04 · LAX 06:04 · JFK 09:04
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.