GCD,线程,程序流和UI更新

时间:2011-09-03 03:18:18

标签: iphone objective-c ios multithreading macos

我很难弄清楚如何把这些放在一起。 我在mac上有一个解谜应用程序。 你输入拼图,按一个按钮,当它试图找到解决方案的数量时, min移动等我想保持UI更新。 然后,一旦完成计算,重新启用按钮并更改标题。

下面是按钮选择器的一些示例代码,以及求解函数: (请记住,我从Xcode复制/粘贴,因此可能会丢失{}或 其他一些错别字..但它应该让你知道我想要做什么。

基本上,用户按下按钮,该按钮是ENABLED = NO,函数调用来计算拼图。在计算时,请使用移动/解决方案数据更新UI标签。 然后,一旦完成计算拼图,Button就会被启用= YES;

按下按钮时调用:

- (void) solvePuzzle:(id)sender{
    solveButton.enabled = NO;
    solveButton.title = @"Working . . . .";

    // I've tried using this as a Background thread, but I can't get the code to waitTilDone before continuing and changing the button state.
    [self performSelectorInBackground:@selector(createTreeFromNode:) withObject:rootNode];

    // I've tried to use GCD but similar issue and can't get UI updated.
    //dispatch_queue_t queue = dispatch_queue_create("com.gamesbychris.createTree", 0);
    //dispatch_sync(queue, ^{[self createTreeFromNode:rootNode];});

    }

    // Need to wait here until createTreeFromNode is finished.
    solveButton.enabled=YES;
    if (numSolutions == 0) {
    solveButton.title = @"Not Solvable";
    } else {
        solveButton.title = @"Solve Puzzle";
    }
}

需要在后台运行,以便更新UI:

-(void)createTreeFromNode:(TreeNode *)node
{
   // Tried using GCD
   dispatch_queue_t main_queue = dispatch_get_main_queue();

 ...Create Tree Node and find Children Code...

if (!solutionFound){
    // Solution not found yet so check other children by recursion.
   [self createTreeFromNode:newChild];
   } else {
   // Solution found.
   numSolutions ++;
   if (maxMoves < newChild.numberOfMoves) {
       maxMoves = newChild.numberOfMoves;
    }
    if (minMoves < 1 || minMoves > newChild.numberOfMoves) {
        solutionNode = newChild;
        minMoves = newChild.numberOfMoves;

        // Update UI on main Thread

        dispatch_async(main_queue, ^{
                        minMovesLabel.stringValue = [NSString stringWithFormat:@"%d",minMoves];
                        numSolutionsLabel.stringValue = [NSString stringWithFormat:@"%d",numSolutions];
                        maxMovesLabel.stringValue = [NSString stringWithFormat:@"%d",maxMoves];
                    });
                }                        

3 个答案:

答案 0 :(得分:25)

下面的GCD和performSelectorInBackground示例。但首先,让我们看看你的代码。

您不能在上面的代码中等到您想要的位置。 这是你的代码。你说在评论中等待的地方是不正确的。看看我添加了NO。

- (void) solvePuzzle:(id)sender{
    solveButton.enabled = NO;
    solveButton.title = @"Working . . . .";

    // I've tried using this as a Background thread, but I can't get the code to waitTilDone before continuing and changing the button state.
    [self performSelectorInBackground:@selector(createTreeFromNode:) withObject:rootNode];

    // NO - do not wait or enable here.
    // Need to wait here until createTreeFromNode is finished.
    solveButton.enabled=YES;

}

在主线程上运行UI消息循环,以保持UI运行。 solvePuzzle在主线程上被调用,所以你不能等待 - 它将阻止UI。它也无法将按钮设置为启用 - 工作尚未完成。

工作者函数在后台线程上的工作是完成工作,然后在完成后再更新UI。但是您无法从后台线程更新UI。如果您没有使用块并使用performSelectInBackground,那么当您完成后,请调用performSelectorOnMainThread,它调用选择器来更新您的UI。

performSelectorInBackground示例:

在这个片段中,我有一个调用长时间运行工作的按钮,一个状态标签,我添加了一个滑块,以显示我可以在bg工作完成时移动滑块。

// on click of button
- (IBAction)doWork:(id)sender
{
    [[self feedbackLabel] setText:@"Working ..."];
    [[self doWorkButton] setEnabled:NO];

    [self performSelectorInBackground:@selector(performLongRunningWork:) withObject:nil];
}

- (void)performLongRunningWork:(id)obj
{
    // simulate 5 seconds of work
    // I added a slider to the form - I can slide it back and forth during the 5 sec.
    sleep(5);
    [self performSelectorOnMainThread:@selector(workDone:) withObject:nil waitUntilDone:YES];
}

- (void)workDone:(id)obj
{
    [[self feedbackLabel] setText:@"Done ..."];
    [[self doWorkButton] setEnabled:YES];
}

GCD示例:

// on click of button
- (IBAction)doWork:(id)sender
{
    [[self feedbackLabel] setText:@"Working ..."];
    [[self doWorkButton] setEnabled:NO];

    // async queue for bg work
    // main queue for updating ui on main thread
    dispatch_queue_t queue = dispatch_queue_create("com.sample", 0);
    dispatch_queue_t main = dispatch_get_main_queue();

    //  do the long running work in bg async queue
    // within that, call to update UI on main thread.
    dispatch_async(queue, 
                   ^{ 
                       [self performLongRunningWork]; 
                       dispatch_async(main, ^{ [self workDone]; });
                   });    
}

- (void)performLongRunningWork
{
    // simulate 5 seconds of work
    // I added a slider to the form - I can slide it back and forth during the 5 sec.
    sleep(5);
}

- (void)workDone
{
    [[self feedbackLabel] setText:@"Done ..."];
    [[self doWorkButton] setEnabled:YES];
}

答案 1 :(得分:3)

  dispatch_queue_t backgroundQueue;

  backgroundQueue = dispatch_queue_create("com.images.bgqueue", NULL);        


    - (void)process {    
    dispatch_async(backgroundQueue, ^(void){
    //background task
        [self processHtml];
    dispatch_async(main, ^{ 
// UI updates in main queue
   [self workDone]; 
    });

    });  
    });    
 }

答案 2 :(得分:0)

总的来说,要提交到后台队列的任何工作都需要遵循这种代码模式:

dispatch_queue_t queue = dispatch_queue_create("com.myappname", 0);

__weak MyClass  *weakSelf = self;  //must be weak to avoid retain cycle

//Assign async work
dispatch_async(queue, 
^{ 
   [weakSelf doWork]; 
   dispatch_async(dispatch_get_main_queue(), 
   ^{ 
      [weakSelf workDone]; 
    });
 }); 
 queue = nil;  //Using ARC, we nil out. Block always retains the queue.

永远不要忘记:

1 - 上面的队列变量是reference counted object,因为它是一个私有队列,而不是全局队列。因此它由在该队列内执行的块保留。在此任务完成之前,它不会被释放。

2 - 每个队列都有自己的堆栈,它将作为递归操作的一部分进行分配/解除分配。您只需要担心引用计数的类成员变量(强,保留等),这些变量作为上述doWork的一部分进行访问。

3 - 在后台队列操作中访问这些引用计数变量时,您需要根据应用程序中的用例使它们成为线程安全的。示例包括对诸如字符串,数组等对象的写入。这些写入应封装在@synchronized关键字内,以确保线程安全访问。

@synchronized确保在其封装的块被执行时,没有其他线程可以访问它保护的资源。

@synchronized(myMutableArray)
{
    //operation
}

在上面的代码块中,任何其他线程都不允许对myMutableArray块内的@synchronized进行任何更改。