本頁面由 Cloud Translation API 翻譯而成。
Switch to English

在iOS上讀取和寫入數據

獲取FIRDatabaseReference

要從數據庫讀取或寫入數據,您需要一個FIRDatabaseReference實例:

迅速

var ref: DatabaseReference!

ref = Database.database().reference()

目標C

@property (strong, nonatomic) FIRDatabaseReference *ref;

self.ref = [[FIRDatabase database] reference];

讀寫數據

本文檔介紹了讀取和寫入Firebase數據的基礎知識。

Firebase數據將寫入FIRDatabase引用,並通過將異步偵聽器附加到該引用來進行檢索。數據的初始狀態會觸發一次偵聽器,並且在數據更改時會再次觸發。

基本寫操作

對於基本寫操作,可以使用setValue將數據保存到指定的引用,從而替換該路徑上的任何現有數據。您可以使用此方法執行以下操作:

  • 與可用的JSON類型相對應的傳遞類型如下:
    • NSString
    • NSNumber
    • NSDictionary
    • NSArray

例如,您可以添加具有setValue的用戶,如下所示:

迅速

self.ref.child("users").child(user.uid).setValue(["username": username])

目標C

[[[self.ref child:@"users"] child:authResult.user.uid]
    setValue:@{@"username": username}];

以這種方式使用setValue覆蓋指定位置(包括所有子節點)上的數據。但是,您仍然可以更新子項而無需重寫整個對象。如果要允許用戶更新其個人資料,則可以按以下方式更新用戶名:

迅速

self.ref.child("users/\(user.uid)/username").setValue(username)

目標C

[[[[_ref child:@"users"] child:user.uid] child:@"username"] setValue:username];

聆聽價值事件

要閱讀在路徑數據和監聽更改,請使用observeEventType:withBlockobserveSingleEventOfType:withBlock方法FIRDatabaseReference觀察FIRDataEventTypeValue事件。

事件類型典型用法
FIRDataEventTypeValue 讀取和偵聽路徑全部內容的更改。

您可以使用FIRDataEventTypeValue事件讀取給定路徑上的數據,因為該數據在事件發生時就已經存在。附加了偵聽器後,將觸發此方法,並且每次數據(包括任何子級)發生更改時,都會觸發此方法。將向事件回調傳遞snapshot ,該snapshot包含該位置的所有數據,包括子數據。如果沒有數據,則快照將在您調用exists()時返回false ,在讀取其value屬性時返回nil

以下示例演示了一個社交博客應用程序,該應用程序從數據庫中檢索帖子的詳細信息:

迅速

refHandle = postRef.observe(DataEventType.value, with: { (snapshot) in
  let postDict = snapshot.value as? [String : AnyObject] ?? [:]
  // ...
})

目標C

_refHandle = [_postRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
  NSDictionary *postDict = snapshot.value;
  // ...
}];

偵聽器在事件發生時在其value屬性中接收到FIRDataSnapshot ,該FIRDataSnapshot包含數據庫中指定位置的數據。您可以將值分配給適當的本機類型,例如NSDictionary 。如果該位置沒有數據,則value nil

一次讀取數據

在某些情況下,您可能希望調用一次回調然後立即將其刪除,例如在初始化您不希望更改的UI元素時。您可以使用observeSingleEventOfType方法簡化這種情況:添加的事件回調觸發一次,然後不再觸發。

這對於只需要加載一次且不會經常更改或需要主動監聽的數據很有用。例如,前面示例中的博客應用程序使用此方法在用戶開始撰寫新帖子時加載其個人資料:

迅速

let userID = Auth.auth().currentUser?.uid
ref.child("users").child(userID!).observeSingleEvent(of: .value, with: { (snapshot) in
  // Get user value
  let value = snapshot.value as? NSDictionary
  let username = value?["username"] as? String ?? ""
  let user = User(username: username)

  // ...
  }) { (error) in
    print(error.localizedDescription)
}

目標C

NSString *userID = [FIRAuth auth].currentUser.uid;
[[[_ref child:@"users"] child:userID] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
  // Get user value
  User *user = [[User alloc] initWithUsername:snapshot.value[@"username"]];

  // ...
} withCancelBlock:^(NSError * _Nonnull error) {
  NSLog(@"%@", error.localizedDescription);
}];

更新或刪除數據

更新特定字段

要同時寫入節點的特定子節點而不覆蓋其他子節點,請使用updateChildValues方法。

調用updateChildValues ,可以通過指定鍵的路徑來更新較低級別的子值。如果數據存儲在多個位置以更好地擴展,則可以使用數據扇出更新該數據的所有實例。例如,社交博客應用可能想要創建一個帖子,同時將其更新為最近的活動供稿和發布用戶的活動供稿。為此,博客應用程序使用如下代碼:

迅速

guard let key = ref.child("posts").childByAutoId().key else { return }
let post = ["uid": userID,
            "author": username,
            "title": title,
            "body": body]
let childUpdates = ["/posts/\(key)": post,
                    "/user-posts/\(userID)/\(key)/": post]
ref.updateChildValues(childUpdates)

目標C

NSString *key = [[_ref child:@"posts"] childByAutoId].key;
NSDictionary *post = @{@"uid": userID,
                       @"author": username,
                       @"title": title,
                       @"body": body};
NSDictionary *childUpdates = @{[@"/posts/" stringByAppendingString:key]: post,
                               [NSString stringWithFormat:@"/user-posts/%@/%@/", userID, key]: post};
[_ref updateChildValues:childUpdates];

本示例使用childByAutoId/posts/$postid包含所有用戶的帖子的節點中創建一個帖子,並同時使用getKey()檢索密鑰。然後可以使用該密鑰在/user-posts/$userid/$postid的用戶帖子中創建第二個條目。

使用這些路徑,您可以通過一次調用updateChildValues來同時更新JSON樹中的多個位置,例如本示例在兩個位置中創建新帖子的方式。通過這種方式進行的同時更新是原子性的:要么所有更新成功,要么所有更新失敗。

添加一個完成塊

如果您想知道何時提交數據,可以添加一個完成塊。 setValueupdateChildValuesupdateChildValues一個可選的完成塊,當寫入已提交給數據庫時,將調用該完成塊。該偵聽器對於跟踪已保存哪些數據以及仍在同步哪些數據很有用。如果調用失敗,則向偵聽器傳遞一個錯誤對象,指示發生失敗的原因。

迅速

ref.child("users").child(user.uid).setValue(["username": username]) {
  (error:Error?, ref:DatabaseReference) in
  if let error = error {
    print("Data could not be saved: \(error).")
  } else {
    print("Data saved successfully!")
  }
}

目標C

[[[_ref child:@"users"] child:user.uid] setValue:@{@"username": username} withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) {
  if (error) {
    NSLog(@"Data could not be saved: %@", error);
  } else {
    NSLog(@"Data saved successfully.");
  }
}];

刪除資料

刪除數據的最簡單方法是在對數據位置的引用上調用removeValue

您還可以通過將nil指定為另一個寫操作(例如setValueupdateChildValues的值來刪除。您可以將此技術與updateChildValues結合使用,以在單個API調用中刪除多個子級。

分離聽眾

當您離開ViewController時,觀察者不會自動停止同步數據。如果未正確刪除觀察者,它將繼續將數據同步到本地內存。當不再需要觀察者時,通過將關聯的FIRDatabaseHandle傳遞給removeObserverWithHandle方法來將其removeObserverWithHandle

當您將回調塊添加到引用時,將返回FIRDatabaseHandle 。這些句柄可用於刪除回調塊。

如果已將多個偵聽器添加到數據庫引用,則在引發事件時將調用每個偵聽器。為了停止在該位置同步數據,必須通過調用removeAllObservers方法刪除該位置的所有觀察者。

在偵聽器上調用removeObserverWithHandleremoveAllObservers不會自動刪除在其子節點上註冊的偵聽器。您還必須跟踪這些引用或句柄以將其刪除。

將數據另存為交易

當使用可能被並發修改破壞的數據(例如增量計數器)時,可以使用事務操作 。您為該操作提供了兩個參數:更新函數和可選的完成回調。 update函數將數據的當前狀態作為參數,並返回您想要寫入的新期望狀態。

例如,在示例社交博客應用程序中,您可以允許用戶對帖子加註星標和取消注星,並跟踪帖子獲得了多少星標,如下所示:

迅速

ref.runTransactionBlock({ (currentData: MutableData) -> TransactionResult in
  if var post = currentData.value as? [String : AnyObject], let uid = Auth.auth().currentUser?.uid {
    var stars: Dictionary<String, Bool>
    stars = post["stars"] as? [String : Bool] ?? [:]
    var starCount = post["starCount"] as? Int ?? 0
    if let _ = stars[uid] {
      // Unstar the post and remove self from stars
      starCount -= 1
      stars.removeValue(forKey: uid)
    } else {
      // Star the post and add self to stars
      starCount += 1
      stars[uid] = true
    }
    post["starCount"] = starCount as AnyObject?
    post["stars"] = stars as AnyObject?

    // Set value and report transaction success
    currentData.value = post

    return TransactionResult.success(withValue: currentData)
  }
  return TransactionResult.success(withValue: currentData)
}) { (error, committed, snapshot) in
  if let error = error {
    print(error.localizedDescription)
  }
}

目標C

[ref runTransactionBlock:^FIRTransactionResult * _Nonnull(FIRMutableData * _Nonnull currentData) {
  NSMutableDictionary *post = currentData.value;
  if (!post || [post isEqual:[NSNull null]]) {
    return [FIRTransactionResult successWithValue:currentData];
  }

  NSMutableDictionary *stars = post[@"stars"];
  if (!stars) {
    stars = [[NSMutableDictionary alloc] initWithCapacity:1];
  }
  NSString *uid = [FIRAuth auth].currentUser.uid;
  int starCount = [post[@"starCount"] intValue];
  if (stars[uid]) {
    // Unstar the post and remove self from stars
    starCount--;
    [stars removeObjectForKey:uid];
  } else {
    // Star the post and add self to stars
    starCount++;
    stars[uid] = @YES;
  }
  post[@"stars"] = stars;
  post[@"starCount"] = @(starCount);

  // Set value and report transaction success
  currentData.value = post;
  return [FIRTransactionResult successWithValue:currentData];
} andCompletionBlock:^(NSError * _Nullable error,
                       BOOL committed,
                       FIRDataSnapshot * _Nullable snapshot) {
  // Transaction completed
  if (error) {
    NSLog(@"%@", error.localizedDescription);
  }
}];

如果多個用戶同時對同一個帖子加註星標或客戶端具有陳舊的數據,則使用事務可防止星級計數不正確。 FIRMutableData類中包含的值最初是該路徑的客戶端最後一個已知值,如果沒有,則為nil 。服務器將初始值與當前值進行比較,如果值匹配,則接受交易,否則拒絕交易。如果交易被拒絕,則服務器將當前值返回給客戶端,客戶端將使用更新後的值再次運行交易。重複此操作,直到接受交易或進行過多嘗試為止。

離線寫入數據

如果客戶端失去網絡連接,則您的應用將繼續正常運行。

連接到Firebase數據庫的每個客戶端都維護自己的內部版本的任何活動數據。寫入數據後,首先將其寫入此本地版本。然後,Firebase客戶端會根據“最大努力”與遠程數據庫服務器和其他客戶端同步該數據。

結果,所有寫入數據庫的操作都會在任何數據寫入服務器之前立即觸發本地事件。這意味著您的應用程序始終保持響應狀態,無論網絡延遲或連接性如何。

重新建立連接後,您的應用將接收適當的事件集,以便客戶端與當前服務器狀態同步,而無需編寫任何自定義代碼。

下一步