Firebase Storage に癖や問題はありますか? 対処しなければならないある種のキャッシング?
AnUploadTask
は を実行しasynchronously
ます。画像をアップロードした直後に画像をダウンロードしようとすると、エラーを再現できます。何が起こっているかというと、画像のアップロードが完了する前にダウンロード コードが実行され、画像が存在しないというエラーが発生します。コールバックでいくつかのメッセージを出力することで、ダウンロード コードの実行が早すぎることがわかります。
let storage = FIRStorage.storage()
let storageRef = storage.reference() //You don't need to explicitly write the url in your code.
//The config file GoogleService-Info.plist will handle that.
let imageRef = storageRef.child("images/align_menu.tiff")
let localURL = NSBundle.mainBundle().URLForResource(
"align_menu",
withExtension: "tiff"
)!
//Upload the image:
let uploadTask = imageRef.putFile(localURL, metadata: nil) { (metadata, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Upload Error]: \(returnedError)")
} else {
// Metadata contains file metadata such as size, content-type, and download URL.
print("[My Upload Success]:")
let downloadURL = metadata!.downloadURL()!
print("[URL for download]: \(downloadURL)")
}
}
//Download the image:
imageRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Download Error]: \(returnedError)")
}
else {
print("[My Download Success]:")
if let validImage = UIImage(data: data!) {
NSOperationQueue.mainQueue().addOperationWithBlock() {
self.imageView.image = validImage
}
}
}
}
そのコードは次の出力を生成します。
[My Download Error]: ...."Object images/align_menu.tiff does not exist."...
数秒後、出力が表示されます。
[My Upload Success]:
[URL for download]: ...
これは、ダウンロード コールバックがアップロード コールバックの前に実行されていることを示しています。なぜそれが起こるのかの詳細はよくわかりませんが、コールバックがシリアル キューに追加されていないことは明らかです。*
非同期の問題を解決するには、いくつかのオプションがあります。
1)アップロード コードのコールバック内にダウンロード コードを配置します。
そうすれば、イメージが正常にアップロードされるまで、ダウンロードの実行は開始されません。その後、アプリを実行する前に Firebase Storage Web ページを使用して画像を削除しても、アップロード/ダウンロードに悪影響はなく、メッセージは予想どおりの順序で出力されました。
[My Upload Success]:
[URL for download]: ...
[My Download Success]:
2) .Success オブザーバーを uploadTask にアタッチします。
Firebase ドキュメントに記載されているように、「アップロードの進行状況を監視する」セクションで、uploadTask が画像を正常にアップロードした場合に通知を受け取ることができます。
let storage = FIRStorage.storage()
let storageRef = storage.reference() //You don't need to explicitly write the url in your code.
//The config file GoogleService-Info.plist will handle that.
let imageRef = storageRef.child("images/align_menu.tiff")
let localURL = NSBundle.mainBundle().URLForResource(
"align_menu",
withExtension: "tiff"
)!
//Upload the image:
let uploadTask = imageRef.putFile(localURL, metadata: nil) { (metadata, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Upload Error]: \(returnedError)")
} else {
// Metadata contains file metadata such as size, content-type, and download URL.
print("[My Upload Success]:")
let downloadURL = metadata!.downloadURL()!
print("[URL for download]: \(downloadURL)")
}
}
let observer = uploadTask.observeStatus(.Success) { (snapshot) -> Void in
//Download the image:
imageRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Download Error]: \(returnedError)")
}
else {
print("[My Download Success]:")
if let validImage = UIImage(data: data!) {
NSOperationQueue.mainQueue().addOperationWithBlock() {
self.imageView.image = validImage
}
}
}
}
}
3)アップロードが成功したら、Grand Central Dispatch を使用して通知します。
コールバックが追加されるキューを制御することはできませんが (Firebase メソッドの実装によって決定されます)、Grand Central Dispatch を使用して、任意のコードの実行が終了したときに通知することができます。以下は私にとってはうまくいきます:
let storage = FIRStorage.storage()
let storageRef = storage.reference() //You don't need to explicitly write the url in your code.
//The config file GoogleService-Info.plist will handle that.
let imageRef = storageRef.child("images/align_menu.tiff")
let localURL = NSBundle.mainBundle().URLForResource(
"align_menu",
withExtension: "tiff"
)!
let myExecutionGroup = dispatch_group_create()
dispatch_group_enter(myExecutionGroup)
//Upload the image:
let _ = imageRef.putFile(localURL, metadata: nil) { (metadata, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Upload Error]: \(returnedError)")
} else {
// Metadata contains file metadata such as size, content-type, and download URL.
print("[My Upload Success]:")
let downloadURL = metadata!.downloadURL()!
print("[URL for download]: \(downloadURL)")
dispatch_group_leave(myExecutionGroup)
}
}
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
dispatch_group_notify(myExecutionGroup, queue) {
//This callback executes for every dispatch_group_leave().
//Download the image:
imageRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Download Error]: \(returnedError)")
}
else {
print("[My Download Success]:")
if let validImage = UIImage(data: data!) {
NSOperationQueue.mainQueue().addOperationWithBlock() {
self.imageView.image = validImage
}
}
}
}
}
*
sleep(10)
元のアップロード コードとダウンロード コードの間に を入れてみましたが、問題は緩和されませんでした。アップロード コールバックがバックグラウンド スレッドで実行されている場合、メイン スレッドがスリープしている間にアップロード コールバックが完了するまでの時間があり、スリープが終了した後にダウンロード コードが実行され、ダウンロード コールバックがキューに追加されると考えました。どこかで、ダウンロード コールバックが実行されます。sleep(10) では問題が解決されなかったため、メイン スレッドの実行キューにアップロード コールバックを追加する必要があり、スリープによってメイン スレッドとキュー内のすべての実行が停止されました。
これにより、アップロードとダウンロードのコールバックがメイン スレッドの非同期キューに追加されていると思われます(同期キューではない場合、コールバックは順番に実行されます)。メインスレッドの非同期キューは、メインスレッドにデッドタイムがある場合、キュー内のタスクが実行され、特定のタスクにデッドタイムがある場合、さまざまなタスク間で迅速に切り替えられることを意味すると思います。 HTTP 応答を待っています。たとえば、メイン スレッドの非同期キューに 2 つのタスクがある場合、メイン スレッド、タスク 1、およびタスク 2 の間でいずれかのデッド タイムが発生すると、それらの間で急速な切り替えが行われます。