Join us for Firebase Summit on November 10, 2021. Tune in to learn how Firebase can help you accelerate app development, release with confidence, and scale with ease. Register

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

(Tùy chọn) Nguyên mẫu và thử nghiệm với Firebase Local Emulator Suite

Trước khi nói về cách ứng dụng của bạn đọc và ghi vào Cơ sở dữ liệu thời gian thực, hãy giới thiệu một bộ công cụ bạn có thể sử dụng để tạo mẫu và thử nghiệm chức năng Cơ sở dữ liệu thời gian thực: Firebase Local Emulator Suite. Nếu bạn đang thử các mô hình dữ liệu khác nhau, tối ưu hóa các quy tắc bảo mật của mình hoặc làm việc để tìm ra cách hiệu quả nhất về chi phí để tương tác với back-end, có thể làm việc cục bộ mà không cần triển khai các dịch vụ trực tiếp có thể là một ý tưởng tuyệt vời.

Trình giả lập Cơ sở dữ liệu thời gian thực là một phần của Bộ mô phỏng cục bộ, 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, cũng như tùy chọn các tài nguyên dự án được mô phỏng của bạn (chức năng, cơ sở dữ liệu khác và quy tắc bảo mật).

Sử dụng trình giả lập Cơ sở dữ liệu thời gian thực chỉ bao gồm một vài bước:

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

Một chi tiết hương liên quan đến cơ sở dữ liệu thời gian thực và chức năng đám mây có sẵn. Bạn cũng nên có một cái nhìn tại giới thiệu địa phương Emulator Suite .

Nhận FIRDatabaseReference

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

Nhanh

var ref: DatabaseReference!

ref = Database.database().reference()

Objective-C

@property (strong, nonatomic) FIRDatabaseReference *ref;

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

Viết dữ liệu

Tài liệu này bao gồm các kiến ​​thức cơ bản về đọc và ghi dữ liệu Firebase.

Dữ liệu căn cứ hỏa lực được ghi vào một Database tham khảo và lấy ra bằng cách gắn một người biết lắng nghe không đồng bộ để tham chiếu. Trình nghe được kích hoạt một lần cho trạng thái ban đầu của dữ liệu và một lần nữa bất cứ lúc nào dữ liệu thay đổi.

Các thao tác viết cơ bản

Đối với các hoạt động ghi cơ bản, bạn có thể sử dụng setValue để lưu dữ liệu vào một tài liệu tham khảo quy định, thay thế bất kỳ dữ liệu hiện tại con đường đó. Bạn có thể sử dụng phương pháp này để:

  • Các loại pass tương ứng với các loại JSON có sẵn như sau:
    • NSString
    • NSNumber
    • NSDictionary
    • NSArray

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

Nhanh

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

Objective-C

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

Sử dụng setValue theo cách này ghi đè dữ liệu tại vị trí quy định, bao gồm bất kỳ nút con. Tuy nhiên, bạn vẫn có thể cập nhật một phần tử con 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:

Nhanh

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

Objective-C

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

Đọc dữ liệu

Đọc dữ liệu bằng cách lắng nghe các sự kiện giá trị

Để đọc dữ liệu tại một con đường và lắng nghe những thay đổi, sử dụng observeEventType:withBlock của FIRDatabaseReference để quan sát FIRDataEventTypeValue sự kiện.

Loại sự kiện Cách sử dụng điển hình
FIRDataEventTypeValue Đọc và lắng nghe các thay đổi đối với toàn bộ nội dung của một đường dẫn.

Bạn có thể sử dụng FIRDataEventTypeValue sự kiện để đọc dữ liệu tại một con đường nhất định, vì nó tồn tại vào thời điểm sự kiện này. Phương thức này được kích hoạt một lần khi người nghe được đính kèm và lặp lại mỗi khi dữ liệu, bao gồm bất kỳ phần tử con nào, thay đổi. Gọi lại sự kiện được thông qua một snapshot có chứa tất cả các dữ liệu tại địa điểm đó, bao gồm dữ liệu con. Nếu không có dữ liệu, ảnh chụp sẽ trở lại false khi bạn gọi exists()nil khi bạn đọc nó value bất động sản.

Ví dụ sau minh họa một ứng dụng viết blog xã hội lấy thông tin chi tiết của một bài đăng từ cơ sở dữ liệu:

Nhanh

refHandle = postRef.observe(DataEventType.value, with: { snapshot in
  // ...
})

Objective-C

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

Người nghe nhận được một FIRDataSnapshot có chứa dữ liệu tại vị trí nhất định trong cơ sở dữ liệu tại thời điểm sự kiện trong nó value bất động sản. Bạn có thể gán các giá trị cho các loại có nguồn gốc thích hợp, chẳng hạn như NSDictionary . Nếu không có dữ liệu tồn tại ở vị trí, valuenil .

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

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

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

Nói chung, bạn nên sử dụng các kỹ thuật sự kiện giá trị được mô tả ở trên để đọc dữ liệu và nhận thông báo về các cập nhật dữ liệu từ chương trình phụ trợ. Những kỹ thuật đó làm giảm việc sử dụng và thanh toán của bạn, đồng thời được tối ưu hóa để mang đến cho người dùng của bạn trải nghiệm tốt nhất khi họ trực tuyến và ngoại tuyến.

Nếu bạn cần dữ liệu một lần duy nhất, bạn có thể sử dụng getData() để có được một bản chụp của các dữ liệu từ cơ sở dữ liệu. Nếu vì bất cứ lý do getData() là không có khả năng trả về giá trị máy chủ, khách hàng sẽ tìm hiểu các bộ nhớ cache lưu trữ địa phương và trả về một lỗi nếu giá trị vẫn chưa được tìm thấy.

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

Nhanh

ref.child("users/\(uid)/username").getData(completion:  { error, snapshot in
  guard error == nil else {
    print(error!.localizedDescription)
    return;
  }
  let userName = snapshot.value as? String ?? "Unknown";
});

Objective-C

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;
}];

Sử dụng không cần thiết của getData() có thể tăng việc sử dụng băng thông và dẫn đến mất hiệu suất, có thể được ngăn ngừa bằng cách sử dụng một người biết lắng nghe thời gian thực như trình bày ở trên.

Đọc dữ liệu một lần với một người quan sát

Trong một số trường hợp, bạn có thể muốn trả lại giá trị từ bộ đệm cục bộ 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ớ cache đĩa địa phương ngay lập tức.

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

Nhanh

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

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 xóa dữ liệu

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

Đồng thời viết thư cho trẻ em cụ thể của một nút mà không ghi đè lên các nút con khác, sử dụng updateChildValues phương pháp.

Khi gọi updateChildValues , bạn có thể cập nhật các giá trị con cấp dưới bằng cách xác định một con đường cho chìa khóa. Nếu dữ liệu được lưu trữ trong nhiều địa điểm để mở rộng tốt hơn, bạn có thể cập nhật tất cả các trường hợp mà dữ liệu sử dụng dữ liệu fan-out . 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 nó vào 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 điều này, ứng dụng viết blog sử dụng mã như sau:

Nhanh

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

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ử childByAutoId để tạo ra một bài đăng trong các nút chứa bài viết cho tất cả người dùng tại /posts/$postid và đồng thời lấy chìa khóa với getKey() . Chìa khóa sau đó có thể được sử dụng để tạo ra một mục thứ hai trong bài viết của người dùng ở /user-posts/$userid/$postid .

Sử dụng các đường dẫn, bạn có thể thực hiện cập nhật đồng thời đến nhiều địa điểm trong cây JSON với một cuộc gọi duy nhất để updateChildValues , chẳng hạn như làm thế nào ví dụ này tạo bài 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 không thành công.

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

Nếu bạn muốn biết khi nào dữ liệu của mình đã được cam kết, bạn có thể thêm một khối hoàn thành. Cả hai setValueupdateChildValues lấy một khối hoàn thành tùy chọn đó được gọi là khi viết đã được cam kết với cơ sở dữ liệu. Trình nghe này có thể hữu ích để theo dõi dữ liệu nào đã được lưu và dữ liệu nào vẫn đang được đồng bộ hóa. Nếu cuộc gọi không thành công, người nghe được chuyển một đối tượng lỗi cho biết lý do tại sao lỗi xảy ra.

Nhanh

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!")
  }
}

Objective-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.");
  }
}];

Xóa dữ liệu

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

Bạn cũng có thể xóa bằng cách xác định nil làm giá trị cho một hoạt động viết như setValue hoặc updateChildValues . Bạn có thể sử dụng kỹ thuật này với updateChildValues để xóa nhiều trẻ em trong một cuộc gọi API duy nhất.

Tách người nghe

Các nhà quan sát không tự động dừng đồng bộ hoá dữ liệu khi bạn rời khỏi một ViewController . Nếu một trình quan sát không được xóa đúng cách, nó sẽ tiếp tục đồng bộ hóa dữ liệu vào bộ nhớ cục bộ. Khi một người quan sát không còn cần thiết, loại bỏ nó bằng cách thông qua các liên FIRDatabaseHandle đến removeObserverWithHandle phương pháp.

Khi bạn thêm một khối gọi lại để một tài liệu tham khảo, một FIRDatabaseHandle được trả về. Các chốt này có thể được sử dụng để loại bỏ khối gọi lại.

Nếu nhiều người nghe đã được thêm vào một tham chiếu cơ sở dữ liệu, thì mỗi người nghe sẽ được gọi khi một sự kiện được đưa ra. Để ngăn chặn đồng bộ hoá dữ liệu ở vị trí đó, bạn phải loại bỏ tất cả các quan sát viên tại một địa điểm bằng cách gọi removeAllObservers phương pháp.

Gọi removeObserverWithHandle hoặc removeAllObservers trên người nghe không tự động loại bỏ các thính giả đã đăng ký trên các nút con của nó; bạn cũng phải theo dõi các tham chiếu hoặc xử lý đó để loại bỏ 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 thay đổi đồng thời, chẳng hạn như quầy gia tăng, bạn có thể sử dụng một 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 lệnh gọi lại hoàn thành tùy chọn. Hàm cập nhật lấy trạng thái hiện tại của dữ liệu làm đối số và trả về trạng thái mong muốn mới mà bạn muốn ghi.

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

Nhanh

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

[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 một giao dịch ngăn không cho số lượng sao không chính xác nếu nhiều người dùng gắn dấu sao cho cùng một bài đăng tại cùng một thời điểm hoặc khách hàng có dữ liệu cũ. Giá trị chứa trong FIRMutableData lớp ban đầu là giá trị của khách hàng cuối cùng được biết cho đường dẫn, hoặc nil nếu có ai sánh kịp. Máy chủ so sánh giá trị ban đầu với giá trị hiện tại của nó và chấp nhận giao dịch nếu các giá trị khớp hoặc từ chối nó. Nếu giao dịch bị từ chối, máy chủ sẽ trả về giá trị hiện tại cho máy khách, giá trị này sẽ chạy lại giao dịch với giá trị được cập nhật. Điều này lặp lại cho đến khi giao dịch được chấp nhận hoặc quá nhiều lần thử đã được thực hiện.

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

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

Nhanh

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

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 thao tác giao dịch, vì vậy nó không tự động chạy lại nếu có bản cập nhật xung đột. Tuy nhiên, vì hoạt động tăng dần 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 các xung đột dành riêng cho ứng dụng, chẳng hạn như người dùng gắn dấu sao cho một bài đăng mà họ đã gắn dấu sao trước đó, bạn nên viết các quy tắc bảo mật tùy chỉnh cho trường hợp sử dụng đó.

Làm việc với dữ liệu ngoại tuyến

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

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

Kết quả là, tất cả các lần 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 bất kỳ dữ liệu nào được ghi vào máy chủ. Điều này có nghĩa là ứng dụng của bạn vẫn đáp ứng bất kể độ trễ mạng hoặc kết nối.

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

Chúng tôi sẽ nói thêm về hành vi ẩn trong Tìm hiểu thêm về trực tuyến và khả năng ngoại tuyến .

Bước tiếp theo