执行许多 UIKit 操作阻塞主线程

Posted

技术标签:

【中文标题】执行许多 UIKit 操作阻塞主线程【英文标题】:Performing many UIKit operations blocking main thread 【发布时间】:2013-07-24 17:47:35 【问题描述】:

当应用程序启动时,我以编程方式创建了许多具有透明背景颜色的复杂 UIView,每个都是应用程序中的“屏幕”(必须这样,因为下面有动画背景)。这会导致应用在静态启动图像上停留很长时间(大约 4 秒)。

我想在显示动画时创建所有 UIView。我无法在后台线程上创建 UIViews。我怎样才能做到这一点?

尝试 1:

-(void)viewDidLoad 

    [super viewDidLoad];


    dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^

        UIView *baseView = [[UIView alloc] initWithFrame:CGRectMake(20, 20, 200, 100)];
        [baseView setBackgroundColor:[UIColor blueColor]];

        UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(10, 10, 180, 80)];
        [button setBackgroundColor:[UIColor redColor]];
        [button setTitle:@"button" forState:UIControlStateNormal]; //doesn't work
        [baseView addSubview:button];

        dispatch_async( dispatch_get_main_queue(), ^ //Finished

            [self.view addSubview:baseView];
        );

    );

添加了按钮,但从未设置标题。点按后出现。

【问题讨论】:

背景是什么类型的动画?电影?您如何以编程方式创建视图?绘制矩形?有多少浏览量?它们重叠吗? 不,我需要在主线程上同时创建一个动画 UIView,因为正在创建许多 (10+) 复杂的 UIView。复杂的视图不应该是当前可见的,只是创建的。 UIView 包含其他 UIView,如 UIButton、UIImageView 和 UILabel。有很多,因为视图是游戏应用的完整“屏幕”。 虽然这不是您问题的答案...您提到“复杂的视图不应该是当前可见的,只是创建的”。您确定必须在应用启动时创建它们,并且不能在不减慢启动速度的更方便的时间创建它们吗? 也许,但我想避免在“屏幕”即将被查看之前加载它。我想不出适合装载的情况。是否有可能每个 runloop 只做一个“屏幕”,也给动画一些时间? 【参考方案1】:

UIView 在正常使用中不是线程安全的,所有交互都应该发生在主线程上。也就是说,您应该能够在某个后台线程(调度队列)上创建视图、修改其参数、设置复杂的背景颜色等——只要视图没有连接到任何东西(从而接收来自 ios 的事件)。一旦视图完全设置和连接,然后将一个块与它一起分派到主队列,然后才将其作为子视图添加到其他视图。

我要做的是在设置完我的初始视频剪辑后将所有这些工作分派到正常的优先级队列。当所有视图都已发布回主队列(因此​​此工作完成)后,您可以停止视频并开始与用户交互。

【讨论】:

所以我实际上可以创建视图,向它们添加子视图,只要它们没有添加到任何可见视图中?这很有趣。 @lukas798,“可见”视图链接到窗口(UIWindow)并接收事件。链中没有窗口意味着没有事件。 我尝试过这样做,但没有成功。查看更新后的问题。 能否请您分享一些可以做到这一点的示例代码。提前致谢。 看,你几乎拥有它。当您设置这些属性时,它们会发布一个“setNeedsDisplay”,因为它们不可见,所以没有任何影响。首先,只需在将其添加到视图后尝试执行“setNeedsDisplay”(即,在将其添加到视图后在调度块中执行此操作。其次,您可能需要在将控件添加到视图后设置标题 -我隐约记得几年前在 OSX 上遇到过同样的问题。您总是可以在后台进行繁重的工作,然后在将控件添加到视图后最后设置背景颜色/标题/等。【参考方案2】:

感谢您的帮助。这是我测试并完美运行的最终解决方案。

我创建了一个任务管理器类,FCTaskManager:

//Header File

typedef void (^TaskBlock)(void);
typedef void (^TaskCompletedBlock)(void);

@interface FCTaskManager : NSObject

-(void)performUITask:(TaskBlock)taskBlock completionBlock:(TaskCompletedBlock)taskCompletedBlock;
-(void)performBackgroundTask:(TaskBlock)taskBlock completionBlock:(TaskCompletedBlock)taskCompletedBlock;

@end



//Implementation

#import "FCTaskManager.h"

@implementation FCTaskManager

-(void)performUITask:(TaskBlock)taskBlock completionBlock:(TaskCompletedBlock)taskCompletedBlock 

    dispatch_async( dispatch_get_main_queue(), ^

        @autoreleasepool 

            taskBlock();

            dispatch_async( dispatch_get_main_queue(), ^

                taskCompletedBlock();
            );
        
    );


-(void)performBackgroundTask:(TaskBlock)taskBlock completionBlock:(TaskCompletedBlock)taskCompletedBlock 

    dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^

        @autoreleasepool 

            taskBlock();

            dispatch_async( dispatch_get_main_queue(), ^

                taskCompletedBlock();
            );
        
    );


@end

然后我可以像这样使用任务管理器:

-(void)viewDidLoad 

    [super viewDidLoad];

    //Create taskManager
    taskManager = [[FCTaskManager alloc] init];

    //Create UIViews, etc WITHOUT blocking main queue
    [taskManager performUITask:^

        button = [[UIButton alloc] initWithFrame:CGRectMake(30, 30, 300, 300)];
        [button setBackgroundColor:[UIColor redColor]];
        [button setTitle:@"Working" forState:UIControlStateNormal];
        [button addTarget:self action:@selector(buttonAction) forControlEvents:UIControlEventTouchUpInside];

     completionBlock:^

        NSLog(@"CREATED BUTTON");

        [self.view addSubview:button];
    ];

【讨论】:

看起来是个不错的方法,但您不是要打电话给-performBackgroundTask: completionBlock: 而不是-performUITask:completionBlock: 吗?另外,在这个设置中,当completionBlock 被执行时,你怎么能确定button 视图确实已经在后台线程中创建了? (对不起,如果这些是愚蠢的问题,但我对所有这些多线程业务非常不熟悉。)

以上是关于执行许多 UIKit 操作阻塞主线程的主要内容,如果未能解决你的问题,请参考以下文章

主线程中也不绝对安全的 UI 操作

Qt:如何在不阻塞主线程的情况下播放声音?

主线程 Runloop 在打开 nsmenu 时被阻塞

异步编程规避Redis的阻塞(下)

异步编程规避Redis的阻塞(下)

java多线程并发执行demo,主线程阻塞