2016-09-22 18 views
1

我在我的應用程序中使用官方Ricoh Theta iOS SDK(Link)連接到360°理光Theta相機。 SDK使用多個HTTP請求來觸發圖像的捕獲和從相機下載圖像。自NSRunLoop無法工作,因爲升級到iOS10

SDK內部使用信號來同步請求,但自從升級到iOS 10後,由於某種原因,這似乎不再有效。根據開發者論壇,這個問題是衆所周知的,但理光顯然並不在乎。

我將其縮小到SDK中的特定部分,其中SDK通過RunLoop每0.5秒發送一次請求來檢查圖像是否可用。

這發生在這個函數:

/** 
* Start status monitoring 
* @param command ID of command to be monitored 
* @return Status indicating completion or error 
*/ 
- (NSString*)run:(NSString*)command 
{ 
    // Create and keep HTTP session 
    NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration]; 
    _session= [NSURLSession sessionWithConfiguration:config]; 

    _commandId = command; 

    // Semaphore for synchronization (cannot be entered until signal is called) 
    _semaphore = dispatch_semaphore_create(0); 

    // Create and start timer 
    NSTimer *timer = [NSTimer timerWithTimeInterval:0.5f 
              target:self 
              selector:@selector(getState:) 
              userInfo:nil 
              repeats:YES]; 
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; 
    [runLoop addTimer:timer forMode:NSRunLoopCommonModes]; 
    [runLoop run]; 

    // Wait until signal is called 
    dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER); 
    NSLog(@"HERE"); 
    return _state; 
} 

的問題是,即使信號燈的方法的getState(dispatch_semaphore_signal(_semaphore);)觸發NSLog(@"HERE");永遠不會被調用。我確實通過在調試器中逐行了解這一點。

/** 
    * Delegate called during each set period of time 
    * @param timer Timer 
    */ 
- (void)getState:(NSTimer*)timer { 
    // Create JSON data 
    NSDictionary *body = @{@"id": _commandId}; 

    // Set the request-body. 
    [_request setHTTPBody:[NSJSONSerialization dataWithJSONObject:body options:0 error:nil]]; 

    // Send the url-request. 
    NSURLSessionDataTask* task = 
    [_session dataTaskWithRequest:_request 
       completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { 
        if (!error) { 
         NSArray* array = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil]; 
         _state = [array valueForKey:@"state"]; 
         _fileUri = [array valueForKeyPath:@"results.fileUri"]; 
         NSLog(@"result: %@", _state); 
        } else { 
         _state = @"error"; 
         NSLog(@"GetStorageInfo: received data is invalid."); 
        } 
        if (![_state isEqualToString:@"inProgress"]) { 
         [timer invalidate]; 
         dispatch_semaphore_signal(_semaphore); 


         // Stop timer 
         [timer invalidate]; 
        } 
       }]; 
    [task resume]; } 

我已經看到出現了iOS的10一些小的改動NSRunLoop,是這可能與要面對什麼,我在這裏?我得到的另一種感覺是,也許iOS沒有得到一個新的線程,將調用dispatch_semaphore_wait

在過去的幾個小時裏,我一直對着桌子猛撞我的頭,真的希望你們中的一個能幫助我們!

回答

3

這與信號量無關。

documentation for -[NSRunLoop run]說:

放接收機變爲永久循環,在此期間從所有連接的輸入源時它 處理數據。 [...]

如果沒有輸入源或定時器附加到運行循環,則此方法立即退出;否則,它通過反覆調用runMode:beforeDate:來運行 NSDefaultRunLoopMode中的接收器。換句話說,這種方法有效地開始了一個無限循環, 處理來自運行循環的輸入源和定時器的數據。

從運行循環中手動刪除所有已知的輸入源和定時器 不能保證運行循環將退出。 macOS可以安裝並根據需要移除其他輸入源,以便在接收方的線程處理目標爲 的請求。這些來源因此可以防止運行循環退出。

如果您希望運行循環終止,則不應使用此方法。 而是使用其他運行方法之一,並在循環中檢查您自己的任意條件的其他 。一個簡單的例子 是:

BOOL shouldKeepRunning = YES; // global 
NSRunLoop *theRL = [NSRunLoop currentRunLoop]; 
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]); 

其中shouldKeepRunning設置爲NO在程序中其他地方。

請特別注意我粗體顯示的句子。沒有理由期望[runLoop run]調用之後的-run:方法中的任何代碼都將被執行。

看起來像原始程序員希望無效的計時器將刪除保持運行循環運行的最後一個輸入,它將從其-run方法返回。這個希望是沒有道理的。它在某些版本的操作系統的某些情況下工作的事實是不幸的。 (不幸的,因爲如果它沒有工作,他們會意識到他們的錯誤,並發現了一種不同的方法。)

如果您嘗試使用Apple的建議解決方法,請注意,它只適用於輸入源(不是計時器)是shouldKeepRunning設置爲false。這是因爲,如文件所述,-runMode:beforeDate:在定時器啓動時不一定會返回。所以,您需要使用輸入源來觸發退出。您可以使用NSPort「戳」運行循環。您可以使用-performSelector:onThread:withObject:waitUntilDone:,定位正在運行循環的線程。您不能執行跨線程NSRunLoop訪問,但可以通過-getCFRunLoop下拉到CFRunLoop API,這是線程安全的。然後,您可以使用CFRunLoopPerformBlock()CFRunLoopWakeUp()運行將shouldKeepRunning設置爲false的塊。 -run:方法應該CFRetain()CFRunLoop然後代碼調用CFRunLoopWakeUp()應該CFRelease()它,只是爲了確保它的生活需要的時間。否則,原始線程和CFRunLoopWakeUp()內部的任何最終工作之間可能存在競爭條件。

所有這一切都說,你可以通過使用GCD調度計時器源代替NSTimer,然後停止使用NSRunLoop的東西來替代很多這種情況。這取決於-getState:是否確實需要在-run:的同一線程上運行。使用當前代碼,它可以在同一個線程上運行。使用調度計時器源,它不會。只用部分代碼說有點難,但在我看來,使用調度計時器源不應該有任何問題。

+0

非常感謝您的非常詳細的答案!對此,我真的非常感激! 我發現了這個要點,它會遵循你的建議方法(https://gist.github.com/maicki/7622108),但我現在想知道如何將它包含在現有的SDK中。使用調度定時器,我可能會被迫使用完成塊,但是當前SDK在以信號量的同步方式等待之後返回值。有沒有辦法將這兩種方法結合起來? –

+0

你仍然會使用信號量。它只是用一個調度計時器源代替'NSTimer',然後你不需要運行運行循環。 –

+0

有道理!你做了我的一天,嚴重不能夠感謝你! –

0

我做了以下更改,它只是爲我工作。

- (NSString*)run:(NSString*)command{ 
NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration]; 

_session= [NSURLSession sessionWithConfiguration:config]; 

_commandId = command; 

_semaphore = dispatch_semaphore_create(0); 

NSTimer *timer = [NSTimer timerWithTimeInterval:0.5f 
             target:self 
             selector:@selector(getState:) 
             userInfo:nil 
             repeats:YES]; 

NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; 

[runLoop addTimer:timer forMode:NSRunLoopCommonModes]; 

[runLoop run]; 

dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER); 

return _state; 
} 


- (void)getState:(NSTimer*)timer{ 
if ([_state isEqualToString:@"done"]) 
{ 
    dispatch_semaphore_signal(_semaphore); 
    [timer invalidate]; 
    return; 
} 
NSDictionary *body = @{@"id": _commandId}; 

[_request setHTTPBody:[NSJSONSerialization dataWithJSONObject:body options:0 error:nil]]; 

NSURLSessionDataTask* task = 
[_session dataTaskWithRequest:_request 
      completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { 
       if (!error) { 
        NSArray* array = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil]; 
        _state = [array valueForKey:@"state"]; 
        _fileUri = [array valueForKeyPath:@"results.fileUri"]; 
        NSLog(@"result: %@", _state); 
       } else { 
        _state = @"error"; 
       } 
       if (![_state isEqualToString:@"inProgress"]) { 

       } 
      }]; 
[task resume];} 

希望這會幫助別人。

相關問題