2017-07-26 86 views
2

我正試圖通過NSURLSession訪問我之前下載的文件。看起來我無法讀取文件的位置,即使我在委託方法結束之前這樣做(因爲文件是臨時文件)。NSURLSessionDownloadTask移動臨時文件

不過,我越來越nil嘗試訪問下位置的數據時,從NSURLSession delegate和錯誤257

的返回代碼去如下:

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { 
    NSError *movingError = nil; 
    NSData *fileData = [NSData dataWithContentsOfFile:location.path options:0 error:&movingError]; // is nil 
    NSLog(@"%@", movingError); // is error 257 
} 

有什麼不對的代碼.. ?我看到類似的問題NSURLSessionDownloadTask - downloads but ends with erroriPhone - copying a file from resources to Documents gives an error,但這些完全不適用於我的情況。

- 編輯 -

我創建了一個新的項目,並粘貼非常相同的代碼。它的工作原理。所以:

1)在我的項目,我收到錯誤257,該項目可能是一些配置是無效的或我使用backgroundTasks別的地方應用的事實弄亂的東西了

2)同在1發生,如果我把這個下載的源文件,以迦太基

3)在演示項目鏈接外部框架我創建了1 & 2)一切使用(複製粘貼文件的工作corretly 。

如果有人有一個想法是什麼會導致它不工作的事實 - 會很棒。

回答

0

最後討論,但我看到相同/相似的問題和調查。

隨着我的調查,你所看到的是預期的(我應該說「這是觀察」?)。 但是因爲我不是100%確定你是如何使用URL會話的,所以我放下了我想要做的事情,然後是我的觀察結果(僅限於重要的觀察)。 另外,我創建了一個sample project並將它作爲作爲downLoader.zip。您可以播放並檢查URL會話後臺下載的工作方式。但在嘗試玩之前最好先閱讀下面的內容,儘管這是一個很長的筆記。

A.什麼,我試圖做

我開發怎樣的一個地圖應用程序,在這裏我需要下載體積小的1000+(0.5-150kB,大多〜20KB)一次PNG文件。它們的總下載大小爲〜50MB,並且需要幾分鐘的時間來下載它們。我認爲保持用戶到我的應用程序只是等待下載是不好的設計,所以我讓應用程序使用URL會話的後臺下載。 然而,我不得不承認,Apple’s doc表示「後臺會話已針對傳輸少量可根據需要恢復的大型資源進行了優化」。因此,我使用後臺會話的方式完全相反。但是,無論如何...

B.我所觀察到的

下面,我列出我的看法和我的原因猜測。我應該猜測,因爲他們沒有記錄。

(1)觀察:有時候,應該附帶didFinishDownlaodingTo的文件不存在。

臨時文件放在: /var/mobile/Containers/Data/Application/randomHexString/Library/Caches/com.apple.nsurlsessiond/Downloads/yourName.yourApp。 randomHexString從應用程序的運行變爲運行。當文件不存在時,didFinishDownloadingTo附帶的randomHexString就是「上一個」會話中的一個。現在,這裏的「previous」意味着應用程序上一次運行時的會話!在當前運行的當前會話中顯然不存在。 對於不存在的文件還有另外一種情況,那就是randomHexString正常,但文件不存在。這似乎發生在請求會話取消(invalidateAndCancel)並且會話變爲無效之前(didBecomeInvalidWithError)。

猜測:特別是在開發階段發生這種情況,因爲我們在下載時手動或通過調試器或僅僅通過錯誤來終止應用程序。似乎一旦下載請求被交給操作系統並被接受,操作系統即使在應用程序退出後也會處理我們的請求。請注意,我們無法知道操作系統是否明確接受了請求。即使從URLSessionDownloadTask返回:resume(),有時文件不會在下次啓動時退出,有時它們也會退出。

解決方法:如果文件不存在,請忽略它們。有一段時間,「這次」的文件應該會出現。 (2)觀察:有時候,重複(相同)的文件會附帶didFinishDownloadingTo。

我的應用程序將下載的PNG文件轉換爲其他格式。 W/in didFinishDownlaodingTo,我將臨時文件(== OS指定的)移動到另一個應用程序的目錄,然後產生線程(GCD)來轉換格式並刪除下載的臨時文件。所以,其他線程(didFinishDownlaodingTo)被覆蓋是一個問題。

解決方法:我使URLSessionTask列表:taskIdentifier和w/in didFinishDownlaodingTo,通過檢索taskID列表來檢查重複項以忽略重複的文件。 (3)觀察:即使在用戶終止應用程序之後,OS再次重新啓動應用程序。

用戶從任務切換器終止應用程序後,OS經常重新運行應用程序:handleEventsForBackgroundURLSession:completionHandler。 請注意,重新啓動的順序是didFinishLaunchingWithOptions首先作爲常規啓動,然後是handleEventsForBackgroundURLSession。 從用戶的POV,當他/她終止應用程序,就是這樣,完成!即使在終止應用程序後,它看起來很奇怪,但它本身會重新啓動並通知他們。它就像一個殭屍。

猜測:Apple’s document說「如果用戶終止您的應用程序,系統取消所有未決任務」。 「待定任務」的定義沒有明確說明,但我明白這是來自iOS POV,而不是用戶或程序開發人員。正如(1)中所猜測的,iOS似乎已經接受了下載請求,並且它們不再是「待處理任務」。

解決方法:在所有下載請求交給iOS w/URLSessionDownloadTask:resume()後,我創建了一個「flagFile」,其中文件意味着「下載正在進行中」。當用戶終止應用程序時,在UIApplicationDelegate:applicationWillTerminate處,我刪除標誌文件。或者,如果所有的下載請求都沒有交給iOS,那麼就沒有標誌文件。然後在應用程序重新啓動UIApplicationDelegate:handleEventsForBackgroundURLSession,我檢查是否有標誌文件。如果缺少,那麼我可以假設用戶終止。這裏有兩個選擇。選擇-1:我不會重新創建URL會話。接下來會發生的事情是iOS將在20秒內終止我的應用程序。我不知道這個(==不創建URL會話)是否是合法的操作,但它的工作原理。用戶可以在這20秒內啓動w /,所以我放了一些代碼來處理這種情況。選擇2:我創建了URL會話。接下來會發生的事情是,iOS調用委託方法didFinishDownlaodingTo/didCompleteWithError,然後調用urlSessionDidFinishEvents。如果我在這裏沒有做任何事情,這個過程(應用程序)會無限期地保持活動狀態,而不會向用戶發送任何通知:任務切換器中沒有任何事情。這只不過是浪費記憶。此處的選項是激發本地通知並讓用戶知道我的應用程序,以便他們可以回到我的應用程序並可以終止(再次!),儘管我的應用程序顯然是殭屍。兩種選擇都有一個問題:applicationWillTerminate在某些情況下可能不會被調用(儘管我還沒有確認)。在這種情況下,標誌文件保留爲常規操作並向用戶顯示殭屍。所以,標誌文件方法只是緩解了這個問題,但我認爲它對我的應用程序來說大部分時間都適用。

請注意,應用程序有時會在被xcode調試器殺死或被操作系統w/bug(SEGFALUT)殺死時重新啓動。 (4)觀察:應用程序終止後(通過用戶等),然後由OS重新啓動,應用程序偶爾處於活動狀態(UIApplication.shared.applicationState爲.active)。

我想通過本地通知通知用戶下載完成,但由於它是活動的,本地通知不會觸發。所以,我需要使用UIAlertController。因此,我無法提供一致的用戶體驗,並且應該讓用戶看起來很奇怪:大多數情況下,本地通知和偶爾使用UIAlert。注意當應用程序啓動時處於活動狀態,它出現在任務切換器中。

猜測:完全不知道如何發生。一件好事(?)的事情只是偶爾發生。

解決方案:似乎沒有。

(5)觀察:handleEventsForBackgroundURLSession/urlSessionDidFinishEvents只被調用一次。

我開始下載後啓動後臺任務(application.beginBackgroundTask)。然後在beginBackgroundTask的到期處理程序中,我調用endBackgroundTask。我不知道爲什麼,但在endBackgroundTask之後,我的進程仍然有很多進程時間,所以我可以繼續請求下載。這可能是因爲下載文件不斷傳入w/didFinishDownlaodingTo。爲了成爲一名優秀的公民,我暫停以請求進一步下載,並向用戶發出本地通知,以將應用程序置於前臺。現在,一旦我暫停請求,在4-5秒內,OS確定URL會話結束,並調用handleEventsForBackgroundURLSession,然後調用urlSessionDidFinishEvents。這是一次性事件。當用戶將應用程序置於前臺以恢復下載時,再將其放在後臺,不再有handleEventsForBackgroundURLSession/urlSessionDidFinishEvents。我不清楚的是會話開始和結束的定義。看起來會話從第一個URLSessionTask.resume()開始,然後結束w/timeout,這似乎是由URLSessionConfiguration.timeoutIntervalForRequest決定的。但是,在這裏設置大數字(1000秒)不會影響任何內容,它總是4-5秒。

猜測:不知道

解決方法:當應用程序是活不上urlSessionDidFinishEvents中繼。只有當應用程序由操作系統重新啓動並在最初的handleEventsForBackgroundURLSession/urlSessionDidFinishEvents時才中繼。

===============

下面,我列出了有關sample project(downLoader.zip)。您可以使用示例驗證上述所有內容。

  1. 該應用程序有一個下載文件的列表。文件的數量是1921年,共56MB。它們是位於由Geo Spatial Information Authority of Japan(GSI)管理的服務器中的256x256 PNG地圖瓦片文件。下載後,它們被移動到庫/緩存/下載。如果您的設備遭到破解,您可以使用Filza查看它們。
  2. 崩潰本身測試重新推出
  3. 模擬後臺任務期滿
  4. 記錄到文件,因爲調試程序不通過OS重新開張後的工作。該文件在Documents中,可以移動到PC。
  5. 不要玩真正的設備。模擬器不會重新啓動應用程序。
  6. 項目建於w/Xcode 8.3.3和測試w/iphone6 +/9.3.3和iphone7 +/10.3.1
  7. 要查看是否重新啓動w/xcode,請轉到調試/附加到進程菜單,看看是否downLoader已列出。

===============

我覺得URL會話後臺下載的行爲特殊重,尤其是在重新開張。我們至少需要考慮上面列出的觀察結果,否則應用程序用戶會感到困惑。