Đọc và ghi dữ liệu trên các nền tảng của Apple

(Không bắt buộc) Tạo nguyên mẫu và thử nghiệm bằng Firebase Local Emulator Suite

Trước khi nói về cách ứng dụng đọc và ghi vào Realtime Database, hãy giới thiệu một bộ công cụ mà bạn có thể dùng để tạo nguyên mẫu và kiểm thử Realtime Database chức năng: Firebase Local Emulator Suite. Nếu bạn đang thử dùng dữ liệu khác mô hình, tối ưu hoá các quy tắc bảo mật hoặc nỗ lực tìm ra cách tiết kiệm chi phí để tương tác với hệ thống phụ trợ, nhờ đó có thể làm việc tại địa phương mà không triển khai dịch vụ trực tiếp có thể là ý tưởng hay.

Trình mô phỏng Realtime Database là một phần của Local Emulator Suite, cho phép ứng dụng của bạn tương tác với cấu hình và nội dung cơ sở dữ liệu được mô phỏng, như cũng như các tài nguyên dự án được mô phỏng (không bắt buộc) (các hàm, cơ sở dữ liệu khác, và quy tắc bảo mật).

Bạn chỉ cần thực hiện vài bước để sử dụng trình mô phỏng Realtime Database:

  1. Thêm một dòng mã vào cấu hình kiểm thử của ứng dụng để kết nối với trình mô phỏng.
  2. Trên gốc của thư mục dự án cục bộ, chạy firebase emulators:start.
  3. Gọi điện từ mã nguyên mẫu của ứng dụng bằng nền tảng Realtime Database SDK như thường lệ hoặc sử dụng API REST Realtime Database.

Bạn có thể tham khảo hướng dẫn chi tiết liên quan đến Realtime DatabaseCloud Functions. Bạn cũng nên xem giới thiệu về Local Emulator Suite.

Tải FIRDatabaseReference

Để đọc hoặc ghi dữ liệu từ cơ sở dữ liệu, bạn cần có một thực thể của FIRDatabaseReference:

Swift

Lưu ý: Sản phẩm Firebase này không dùng được cho mục tiêu Đoạn video ngắn.
var ref: DatabaseReference!

ref = Database.database().reference()

Objective-C

Lưu ý: Sản phẩm Firebase này không dùng được trên mục tiêu App Clip (Đoạn video ứng dụng).
@property (strong, nonatomic) FIRDatabaseReference *ref;

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

Ghi dữ liệu

Tài liệu này trình bày các kiến thức cơ bản về cách đọc và ghi dữ liệu Firebase.

Dữ liệu Firebase được ghi vào tệp tham chiếu Database và truy xuất bằng đính kèm một trình nghe không đồng bộ vào tham chiếu. Trình nghe này được kích hoạt một lần cho trạng thái ban đầu của dữ liệu và lặp lại một lần nữa bất cứ khi nào dữ liệu thay đổi.

Thao tác ghi cơ bản

Đối với các thao tác ghi cơ bản, bạn có thể sử dụng setValue để lưu dữ liệu vào một thuộc tính cụ thể thay thế mọi dữ liệu hiện có tại đường dẫn đó. Bạn có thể sử dụng phương thức này để:

  • Sau đây là các loại thẻ và vé tương ứng với các loại JSON hiện có:
    • NSString
    • NSNumber
    • NSDictionary
    • NSArray

Ví dụ: bạn có thể thêm người dùng có setValue như sau:

Swift

Lưu ý: Sản phẩm Firebase này không dùng được cho mục tiêu Đoạn video ngắn.
self.ref.child("users").child(user.uid).setValue(["username": username])

Objective-C

Lưu ý: Sản phẩm Firebase này không dùng được trên mục tiêu App Clip (Đoạn video ứng dụng).
[[[self.ref child:@"users"] child:authResult.user.uid]
    setValue:@{@"username": username}];

Việc sử dụng setValue theo cách này sẽ ghi đè dữ liệu tại vị trí được chỉ định, bao gồm mọi nút con. Tuy nhiên, bạn vẫn có thể cập nhật tài khoản của trẻ mà không cần viết lại toàn bộ đối tượng. Nếu bạn muốn cho phép người dùng cập nhật hồ sơ của họ bạn có thể cập nhật tên người dùng như sau:

Swift

Lưu ý: Sản phẩm Firebase này không dùng được cho mục tiêu Đoạn video ngắn.
self.ref.child("users/\(user.uid)/username").setValue(username)

Objective-C

Lưu ý: Sản phẩm Firebase này không dùng được trên mục tiêu App Clip (Đoạn video ứng dụng).
[[[[_ref child:@"users"] child:user.uid] child:@"username"] setValue:username];

Đọc dữ liệu

Đọc dữ liệu bằng cách theo dõi các sự kiện giá trị

Để đọc dữ liệu tại một đường dẫn và theo dõi các thay đổi, hãy sử dụng observeEventType:withBlock/FIRDatabaseReference để quan sát Sự kiện FIRDataEventTypeValue.

Loại sự kiện Mức sử dụng thông thường
FIRDataEventTypeValue Đọc và theo dõi các thay đổi đối với toàn bộ nội dung của đường dẫn.

Bạn có thể sử dụng sự kiện FIRDataEventTypeValue để đọc dữ liệu ở một đường dẫn nhất định, như tồn tại tại thời điểm diễn ra sự kiện. Phương thức này được kích hoạt một lần khi trình nghe được đính kèm và một lần nữa mỗi khi dữ liệu, bao gồm cả mọi thành phần con, thay đổi. Lệnh gọi lại sự kiện được truyền snapshot chứa tất cả dữ liệu tại đó thông tin vị trí, bao gồm cả dữ liệu của trẻ. Nếu không có dữ liệu, thông tin tổng quan nhanh sẽ trả về false khi gọi exists()nil khi bạn đọc thuộc tính value của đối tượng đó.

Ví dụ sau minh hoạ một ứng dụng viết blog trên mạng xã hội truy xuất thông tin chi tiết về một bài đăng trong cơ sở dữ liệu:

Swift

Lưu ý: Sản phẩm Firebase này không dùng được cho mục tiêu Đoạn video ngắn.
refHandle = postRef.observe(DataEventType.value, with: { snapshot in
  // ...
})

Objective-C

Lưu ý: Sản phẩm Firebase này không dùng được trên mục tiêu App Clip (Đoạn video ứng dụng).
_refHandle = [_postRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
  NSDictionary *postDict = snapshot.value;
  // ...
}];

Trình nghe sẽ nhận được một FIRDataSnapshot chứa dữ liệu theo phương thức đã chỉ định vị trí trong cơ sở dữ liệu tại thời điểm diễn ra sự kiện trong thuộc tính value. Bạn có thể gán các giá trị cho kiểu gốc thích hợp, chẳng hạn như NSDictionary. Nếu không có dữ liệu nào ở vị trí này, thì value sẽ là nil.

Đọc dữ liệu một lần

Đọc một lần bằng getData()

SDK này được thiết kế để quản lý hoạt động tương tác với máy chủ cơ sở dữ liệu cho dù đang trực tuyến hoặc ngoại tuyến.

Thông thường, bạn nên sử dụng các kỹ thuật sự kiện giá trị được mô tả ở trên để đọc để nhận thông báo về nội dung cập nhật đối với dữ liệu từ phần phụ trợ. Những kỹ thuật đó giảm mức sử dụng và thanh toán, đồng thời được tối ưu hoá để cung cấp cho người dùng trải nghiệm trực tuyến và ngoại tuyến.

Nếu chỉ cần dữ liệu một lần, bạn có thể sử dụng getData() để có ảnh chụp nhanh khỏi cơ sở dữ liệu. Nếu vì bất kỳ lý do gì, getData() không thể trả lại máy chủ, máy khách sẽ thăm dò bộ nhớ đệm của bộ nhớ cục bộ và trả về một lỗi nếu vẫn không tìm thấy giá trị đó.

Ví dụ sau minh hoạ việc truy xuất tên người dùng công khai của một người dùng một lần duy nhất từ cơ sở dữ liệu:

Swift

Lưu ý: Sản phẩm Firebase này không dùng được cho mục tiêu Đoạn video ngắn.
do {
  let snapshot = try await ref.child("users/\(uid)/username").getData()
  let userName = snapshot.value as? String ?? "Unknown"
} catch {
  print(error)
}

Objective-C

Lưu ý: Sản phẩm Firebase này không dùng được trên mục tiêu App Clip (Đoạn video ứng dụng).
NSString *userPath = [NSString stringWithFormat:@"users/%@/username", uid];
[[ref child:userPath] getDataWithCompletionBlock:^(NSError * _Nullable error, FIRDataSnapshot * _Nonnull snapshot) {
  if (error) {
    NSLog(@"Received an error %@", error);
    return;
  }
  NSString *userName = snapshot.value;
}];

Việc sử dụng getData() không cần thiết có thể làm tăng mức sử dụng băng thông và dẫn đến mất mát về hiệu suất. Bạn có thể ngăn chặn điều này bằng cách sử dụng trình nghe theo thời gian thực như minh hoạ ở trên.

Đọc dữ liệu một lần với trình quan sát

Trong một số trường hợp, bạn có thể muốn giá trị từ bộ nhớ đệm cục bộ được trả về ngay lập tức, thay vì kiểm tra giá trị đã cập nhật trên máy chủ. Trong những trường hợp đó, bạn có thể sử dụng observeSingleEventOfType để lấy dữ liệu từ bộ nhớ đệm ổ đĩa cục bộ ngay lập tức.

Điều này hữu ích đối với dữ liệu chỉ cần tải một lần và dự kiến sẽ không thay đổi thường xuyên hoặc cần chủ động lắng nghe. Ví dụ: ứng dụng viết blog trong các ví dụ trước sẽ sử dụng phương thức này để tải hồ sơ của người dùng khi họ bắt đầu viết một bài đăng mới:

Swift

Lưu ý: Sản phẩm Firebase này không dùng được cho mục tiêu Đoạn video ngắn.
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)
}

Objective-C

Lưu ý: Sản phẩm Firebase này không dùng được trên mục tiêu App Clip (Đoạn video ứng dụng).
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);
}];

Cập nhật hoặc xoá dữ liệu

Cập nhật các trường cụ thể

Để ghi đồng thời vào các phần tử con cụ thể của một nút mà không ghi đè lên các nút khác nút con, hãy sử dụng phương thức updateChildValues.

Khi gọi updateChildValues, bạn có thể cập nhật các giá trị con ở cấp thấp hơn bằng cách chỉ định đường dẫn cho khoá. Nếu dữ liệu được lưu trữ ở nhiều vị trí để mở rộng quy mô tốt hơn, bạn có thể cập nhật tất cả các phiên bản của dữ liệu đó bằng cách sử dụng ngừng sử dụng dữ liệu. Ví dụ: một ứng dụng viết blog xã hội có thể muốn tạo một bài đăng và đồng thời cập nhật bài đăng đó nguồn cấp dữ liệu hoạt động gần đây và nguồn cấp dữ liệu hoạt động của người dùng đăng bài. Để làm được điều này, ứng dụng viết blog sử dụng mã như sau:

Swift

Lưu ý: Sản phẩm Firebase này không dùng được cho mục tiêu Đoạn video ngắn.
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)

Objective-C

Lưu ý: Sản phẩm Firebase này không dùng được trên mục tiêu App Clip (Đoạn video ứng dụng).
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];

Ví dụ này sử dụng childByAutoId để tạo bài đăng trong nút chứa bài đăng cho tất cả người dùng tại /posts/$postid và truy xuất khoá đồng thời bằng getKey(). Sau đó, khoá này có thể được dùng để tạo mục nhập thứ hai trong tài khoản bài đăng tại /user-posts/$userid/$postid.

Khi sử dụng các đường dẫn này, bạn có thể cập nhật đồng thời nhiều vị trí trong cây JSON với một lệnh gọi duy nhất đến updateChildValues, chẳng hạn như trong ví dụ này tạo bài đăng mới ở cả hai vị trí. Các bản cập nhật đồng thời được thực hiện theo cách này là nguyên tử: tất cả các bản cập nhật đều thành công hoặc tất cả các bản cập nhật đều không thành công.

Thêm một khối hoàn thành

Nếu muốn biết thời điểm dữ liệu của bạn đã được xác nhận, bạn có thể thêm khối hoàn thành. Cả setValueupdateChildValues đều không bắt buộc khối hoàn thành được gọi khi quá trình ghi đã được xác nhận cơ sở dữ liệu. Trình nghe này có thể hữu ích trong việc theo dõi dữ liệu nào đã được đã lưu và dữ liệu nào vẫn đang được đồng bộ hoá. Nếu lệnh gọi không thành công, trình nghe sẽ được truyền một đối tượng lỗi cho biết lý do xảy ra lỗi.

Swift

Lưu ý: Sản phẩm Firebase này không dùng được cho mục tiêu Đoạn video ngắn.
do {
  try await ref.child("users").child(user.uid).setValue(["username": username])
  print("Data saved successfully!")
} catch {
  print("Data could not be saved: \(error).")
}

Objective-C

Lưu ý: Sản phẩm Firebase này không dùng được trên mục tiêu App Clip (Đoạn video ứng dụng).
[[[_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.");
  }
}];

Xóa dữ liệu

Cách đơn giản nhất để xoá dữ liệu là gọi removeValue trên một tệp tham chiếu đến vị trí của dữ liệu đó.

Bạn cũng có thể xoá bằng cách chỉ định nil làm giá trị cho lần ghi khác chẳng hạn như setValue hoặc updateChildValues. Bạn có thể sử dụng kỹ thuật này với updateChildValues để xoá nhiều phần tử con trong một lệnh gọi API.

Tách trình nghe

Trình quan sát không tự động ngừng đồng bộ hoá dữ liệu khi bạn rời khỏi ViewController. Nếu đối tượng tiếp nhận dữ liệu không được xoá đúng cách, nó sẽ tiếp tục đồng bộ hoá dữ liệu vào bộ nhớ cục bộ. Khi không cần trình quan sát nữa, hãy xoá trình quan sát bằng cách truyền FIRDatabaseHandle được liên kết với phương thức removeObserverWithHandle.

Khi bạn thêm một khối gọi lại vào tham chiếu, FIRDatabaseHandle sẽ được trả về. Bạn có thể dùng các tên người dùng này để xoá khối lệnh gọi lại.

Nếu bạn thêm nhiều trình nghe vào một tham chiếu cơ sở dữ liệu, thì mỗi trình nghe sẽ được gọi khi một sự kiện được xảy ra. Để ngừng đồng bộ hoá dữ liệu tại vị trí đó, bạn phải xoá tất cả trình quan sát tại một vị trí bằng cách gọi phương thức removeAllObservers.

Việc gọi removeObserverWithHandle hoặc removeAllObservers trên trình nghe sẽ không tự động xoá các trình nghe đã đăng ký trên các nút con của trình nghe đó; bạn cũng phải theo dõi các tệp tham chiếu hoặc tên xử lý đó để xoá chúng.

Lưu dữ liệu dưới dạng giao dịch

Khi làm việc với dữ liệu có thể bị hỏng do đồng thời sửa đổi, chẳng hạn như bộ đếm tăng dần, bạn có thể sử dụng hoạt động giao dịch. Bạn cung cấp cho thao tác này hai đối số: một hàm cập nhật và một đối số không bắt buộc lệnh gọi lại hoàn thành. Hàm cập nhật lấy trạng thái hiện tại của dữ liệu làm một đối số và trả về trạng thái mong muốn mới mà bạn muốn viết.

Ví dụ: trong ứng dụng viết blog xã hội ví dụ, bạn có thể cho phép người dùng gắn dấu sao và bỏ gắn dấu sao bài đăng, đồng thời theo dõi số sao mà một bài đăng đã nhận được như sau:

Swift

Lưu ý: Sản phẩm Firebase này không dùng được cho mục tiêu Đoạn video ngắn.
ref.runTransactionBlock({ (currentData: MutableData) -> TransactionResult in
  if var post = currentData.value as? [String: AnyObject],
    let uid = Auth.auth().currentUser?.uid {
    var stars: [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)
  }
}

Objective-C

Lưu ý: Sản phẩm Firebase này không dùng được trên mục tiêu App Clip (Đoạn video ứng dụng).
[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);
  }
}];

Việc sử dụng giao dịch sẽ giúp tránh việc tính số sao không chính xác nếu có nhiều người dùng gắn dấu sao cùng một bài đăng cùng một lúc hoặc ứng dụng khách có dữ liệu cũ. Giá trị chứa trong lớp FIRMutableData ban đầu là giá trị mới nhất mà ứng dụng biết được cho đường dẫn hoặc nil nếu không có giá trị nào. Máy chủ so sánh giá trị ban đầu so với giá trị hiện tại và chấp nhận giao dịch nếu giá trị trùng khớp hoặc từ chối giá trị đó. Nếu giao dịch bị từ chối, máy chủ sẽ trả về giá trị hiện tại đối với máy khách, lệnh này sẽ chạy lại giao dịch với giá trị đã cập nhật. Thao tác này lặp lại cho đến khi giao dịch được chấp nhận hoặc có quá nhiều giao dịch .

Số gia ở phía máy chủ nguyên tử

Trong trường hợp sử dụng trên, chúng ta viết hai giá trị vào cơ sở dữ liệu: mã nhận dạng của người dùng gắn dấu sao/bỏ gắn dấu sao bài đăng và số sao tăng lên. Nếu chúng tôi đã biết rằng người dùng đang gắn dấu sao bài đăng, chúng ta có thể sử dụng gia số nguyên tử thay vì một giao dịch.

Swift

Lưu ý: Sản phẩm Firebase này không dùng được cho mục tiêu Đoạn video ngắn.
let updates = [
  "posts/\(postID)/stars/\(userID)": true,
  "posts/\(postID)/starCount": ServerValue.increment(1),
  "user-posts/\(postID)/stars/\(userID)": true,
  "user-posts/\(postID)/starCount": ServerValue.increment(1)
] as [String : Any]
Database.database().reference().updateChildValues(updates)

Objective-C

Lưu ý: Sản phẩm Firebase này không dùng được trên mục tiêu App Clip (Đoạn video ứng dụng).
NSDictionary *updates = @{[NSString stringWithFormat: @"posts/%@/stars/%@", postID, userID]: @TRUE,
                        [NSString stringWithFormat: @"posts/%@/starCount", postID]: [FIRServerValue increment:@1],
                        [NSString stringWithFormat: @"user-posts/%@/stars/%@", postID, userID]: @TRUE,
                        [NSString stringWithFormat: @"user-posts/%@/starCount", postID]: [FIRServerValue increment:@1]};
[[[FIRDatabase database] reference] updateChildValues:updates];

Mã này không sử dụng hoạt động giao dịch nên không tự động nhận được chạy lại nếu có thông tin cập nhật gây xung đột. Tuy nhiên, vì phép toán tăng xảy ra trực tiếp trên máy chủ cơ sở dữ liệu, nên không có khả năng xảy ra xung đột.

Nếu bạn muốn phát hiện và từ chối xung đột dành riêng cho ứng dụng, chẳng hạn như một người dùng gắn dấu sao bài đăng mà họ đã gắn dấu sao trước đó, bạn nên viết quy tắc bảo mật cho trường hợp sử dụng đó.

Làm việc với dữ liệu khi không có mạng

Nếu máy khách bị mất kết nối mạng, ứng dụng của bạn sẽ tiếp tục hoạt động chính xác.

Mỗi ứng dụng được kết nối với cơ sở dữ liệu Firebase đều duy trì phiên bản nội bộ riêng của mọi dữ liệu đang hoạt động. Khi được ghi, dữ liệu sẽ được ghi vào phiên bản cục bộ này đầu tiên. Sau đó, ứng dụng Firebase sẽ đồng bộ hoá dữ liệu đó với cơ sở dữ liệu từ xa máy chủ của bạn và với các ứng dụng khách khác với "nỗ lực tối đa" cơ sở.

Do đó, tất cả hoạt động ghi vào cơ sở dữ liệu sẽ kích hoạt các sự kiện cục bộ ngay lập tức, trước khi mọi dữ liệu đều được ghi vào máy chủ. Tức là ứng dụng của bạn vẫn hoạt động thích ứng bất kể độ trễ hoặc khả năng kết nối mạng như thế nào.

Sau khi kết nối được thiết lập lại, ứng dụng của bạn sẽ nhận được một tập hợp các sự kiện thích hợp để đồng bộ hoá ứng dụng với trạng thái máy chủ hiện tại mà không cần phải viết mã tuỳ chỉnh nào.

Chúng ta sẽ nói thêm về hành vi ngoại tuyến trong Tìm hiểu thêm về các chức năng trực tuyến và ngoại tuyến.

Các bước tiếp theo