2013-12-10 64 views
0

在我的iOS應用程序中,我有一個數據庫調用需要一些時間才能完成。此操作正在進行時,屏幕上會顯示一個微調框。我在應用程序崩潰時發生錯誤,「com.myapp未能及時恢復」,所以它好像在主線程上運行數據庫調用,導致問題。Objective-C - 等待另一個線程返回之前繼續返回

目前代碼

-(void)timeToDoWork 
{ 
    ... 
    [CATransaction flush]; 

    [[DatabaseWorker staticInstance] doWork]; 

    //Additional UI stuff here 
    ... 

    if([self->myReceiver respondsToSelector:self->myMessage]) 
    { 
     [self->myReceiver performSelector:self->myMessage]; 
    } 
} 

要獲得的doWork功能發生在後臺線程,它看起來像我可以使用大中央調度:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
dispatch_async(queue, ^{ 
    [[DatabaseWorker staticInstance] doWork]; 
}); 

但是,我怎麼預防繼續執行直至完成?我應該在doWork調用之後結束該方法,並將其下的所有內容移動到新函數中?

樣品

-(void)timeToDoWork 
{ 
    ... 
    [CATransaction flush]; 

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
    dispatch_async(queue, ^{ 
     [[DatabaseWorker staticInstance] doWork]; 
     dispatch_async(dispatch_get_main_queue(), ^{ 
      [self doneDoingWork]; 
     }); 
    }); 
} 

-(void)doneDoingWork 
{ 
    //Additional UI stuff here 
    ... 

    if([self->myReceiver respondsToSelector:self->myMessage]) 
    { 
     [self->myReceiver performSelector:self->myMessage]; 
    } 
} 

有沒有更好的方式來做到這一點?

+0

我認爲你的解決方案看起來不錯。很難確定什麼是「更好」的方式。你可以在一些共享標誌變量上使用KVO,但我認爲你提出的解決方案更清晰。 – uvesten

回答

0

您收到錯誤提示,你正在做的application:didFinishLaunchingWithOptions:applicationDidBecomeAction:或在發射週期別的地方的東西,正在時間太長,應用程序正在啓動看門狗定時器終止。最重要的是,儘可能快地從這些方法返回是至關重要的。我不確定您的代碼在啓動週期中的位置;但這種解釋似乎是合理的。

有各種各樣的方法來解決這個問題;但是從主隊列中取出冗長的進程是您記下的第一步。在不瞭解主要隊列對象(如UI)依賴於此數據庫事務的情況下,我會說您的建議解決方案非常好。也就是說,將工作分派到後臺隊列中;並在完成後將剩餘的UI工作分派給主隊列。

代表在其他地方被提議作爲解決方案。這也是可行的,儘管你仍然需要關心委託方法調用哪個隊列。

+0

從自動鎖回來時,我遇到了這個問題。我認爲主線程正在執行這種長時間的數據庫操作,然後當iPad解鎖時,由於主線程被數據庫調用佔用,所以能夠做任何事情都花費很長時間。這聽起來似乎合理嗎?還是應該在其他地方調查? – jkh

+0

我同意你的診斷,並且數據庫調用應該離開主隊列。 – FluffulousChimp

0

我認爲你應該在你的DatabaseWorker中使用一個委託並且doWork方法總是在後臺運行,所以當工作者完成工作時,它告訴它的委託工作已經完成。委託方法必須在主線程中調用。

如果您有許多需要知道DatabaseWorker何時完成而不是使用委託的對象,我會使用通知。

編輯:

在您需要實現的方法doWork這樣的DatabaseWorker類:​​

- (void) doWork{ 
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
     dispatch_async(queue, ^{ 
      //Do the work. 
      dispatch_async(dispatch_get_main_queue(), ^{ 
       [self.delegate finishWork]; 
      }); 
     }); 
} 

而且在類實現timeTodoWork:

-(void)timeToDoWork 
{ 
    ... 
    [CATransaction flush]; 

    [[DatabaseWorker staticInstance] setDelegate:self]; 
    [[DatabaseWorker staticInstance] doWork]; 
} 

#pragma mark DatabaseWorkerDelegate 
- (void) finishWork{ 
    //Additional UI stuff here 
    ... 

    if([self->myReceiver respondsToSelector:self->myMessage]) 
    { 
     [self->myReceiver performSelector:self->myMessage]; 
    } 
} 

您也可以使用:

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

代替:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
     dispatch_async(queue, ^{ 
      //Do the work. 
}); 

,並添加一個方法:

- (void) doWorkInBackground{ 
     //Do the work 
     [self.delegate performSelectorOnMainThread:@selector(finishWork) withObject:nil waitUntilDone:NO]; 
} 
+0

因此,而不是dispatch_async ...我會做類似[[DatabaseWorker staticInstance] performSelectorInBackground:doWork withObject:nil];然後在doWork結束時,它會執行SelectorOnMainThread命中doneDoingWork? – jkh

+0

不...讓我添加一些代碼。 – dcorbatta

2

您也可以使用塊。 e.g ..

- (void)doWorkWithCompletionHandler:(void(^)())handler { 
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
    dispatch_async(queue, ^{ 
     // do your db stuff here... 
     dispatch_async(dispatch_get_main_queue(), ^{ 
      handler(); 
     }); 
    }); 
} 

,然後用它這樣的:

[[DatabaseWorker staticInstance] doWorkWithCompletionHandler:^{ 
    // update your UI here, after the db operation is completed. 
}]; 

附: 複製handler塊可能是個好主意。

2

防止主線程繼續執行真的是個壞主意。由於主線程應始終與運行循環一起工作,因此iOS將終止您的應用程序 我建議你採用以下方式來處理你的問題: 寫一個「儲物櫃」。讓它顯示一些視圖,並帶有動畫旋鈕,並且沒有任何按鈕。 當你開始調度異步操作時,只需將它放在前面,讓它在運行循環中工作即可。 當異步操作完成後關閉儲物櫃。

相關問題