firebase database read and write

Read and Write Data on iOS

Get a FIRDatabaseReference

To read or write data from the database, you need an instance of FIRDatabaseReference:

SWIFT

OBJECTIVE-C

@property (strong, nonatomic) FIRDatabaseReference *ref;

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

Reading and writing data

This document covers the basics of reading and writing Firebase data.

Firebase data is written to a FIRDatabase reference and retrieved by attaching an asynchronous listener to the reference. The listener is triggered once for the initial state of the data and again anytime the data changes.

Note: By default, read and write access to your database is restricted so only authenticated users can read or write data. To get started without setting up Authentication, you can configure your rules for public access. This does make your database open to anyone, even people not using your app, so be sure to restrict your database again when you set up authentication.

Basic write operations

For basic write operations, you can use setValue to save data to a specified reference, replacing any existing data at that path. You can use this method to:

  • Pass types that correspond to the available JSON types as follows:
    • NSString
    • NSNumber
    • NSDictionary
    • NSArray

For instance, you can add a user with setValue as follows:

SWIFT

OBJECTIVE-C

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

Using setValue in this way overwrites data at the specified location, including any child nodes. However, you can still update a child without rewriting the entire object. If you want to allow users to update their profiles you could update the username as follows:

SWIFT

OBJECTIVE-C

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

Listen for value events

To read data at a path and listen for changes, use the observeEventType:withBlockorobserveSingleEventOfType:withBlock methods of FIRDatabaseReference to observe FIRDataEventTypeValue events.

Event typeTypical usage
FIRDataEventTypeValueRead and listen for changes to the entire contents of a path.

You can use the FIRDataEventTypeValue event to read the data at a given path, as it exists at the time of the event. This method is triggered once when the listener is attached and again every time the data, including any children, changes. The event callback is passed a snapshot containing all data at that location, including child data. If there is no data, the value of the snapshot returned is nil.

Important: The FIRDataEventTypeValue event is fired every time data is changed at the specified database reference, including changes to children. To limit the size of your snapshots, attach only at the highest level needed for watching changes. For example, attaching a listener to the root of your database is not recommended.

The following example demonstrates a social blogging application retrieving the details of a post from the database:

SWIFT

OBJECTIVE-C

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

The listener receives a FIRDataSnapshot that contains the data at the specified location in the database at the time of the event in its value property. You can assign the values to the appropriate native type, such as NSDictionary. If no data exists at the location, the value is nil.

Read data once

In some cases you may want a callback to be called once and then immediately removed, such as when initializing a UI element that you don't expect to change. You can use the observeSingleEventOfType method to simplify this scenario: the event callback added triggers once and then does not trigger again.

This is useful for data that only needs to be loaded once and isn't expected to change frequently or require active listening. For instance, the blogging app in the previous examples uses this method to load a user's profile when they begin authoring a new post:

SWIFT

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

Updating or deleting data

Update specific fields

To simultaneously write to specific children of a node without overwriting other child nodes, use the updateChildValues method.

When calling updateChildValues, you can update lower-level child values by specifying a path for the key. If data is stored in multiple locations to scale better, you can update all instances of that data using data fan-out. For example, a social blogging app might want to create a post and simultaneously update it to the recent activity feed and the posting user's activity feed. To do this, the blogging application uses code like this:

SWIFT

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

This example uses childByAutoId to create a post in the node containing posts for all users at /posts/$postid and simultaneously retrieve the key with getKey(). The key can then be used to create a second entry in the user's posts at /user-posts/$userid/$postid.

Using these paths, you can perform simultaneous updates to multiple locations in the JSON tree with a single call to updateChildValues, such as how this example creates the new post in both locations. Simultaneous updates made this way are atomic: either all updates succeed or all updates fail.

Delete data

The simplest way to delete data is to call removeValue on a reference to the location of that data.

You can also delete by specifying nil as the value for another write operation such as setValue or updateChildValues. You can use this technique with updateChildValues to delete multiple children in a single API call.

Detach listeners

Observers don't automatically stop syncing data when you leave a ViewController. If an observer isn't properly removed, it continues to sync data to local memory. When an observer is no longer needed, remove it by passing the associated FIRDatabaseHandle to the removeObserverWithHandlemethod.

When you add a callback block to a reference, a FIRDatabaseHandle is returned. These handles can be used to remove the callback block.

If multiple listeners have been added to a database reference, each listener is called when an event is raised. In order to stop syncing data at that location, you must remove all observers at a location by calling the removeAllObservers method.

Calling removeObserverWithHandle or removeAllObservers on a listener does not automatically remove listeners registered on its child nodes; you must also keep track of those references or handles to remove them.

Save data as transactions

When working with data that could be corrupted by concurrent modifications, such as incremental counters, you can use a transaction operation. You give this operation two arguments: an update function and an optional completion callback. The update function takes the current state of the data as an argument and returns the new desired state you would like to write.

For instance, in the example social blogging app, you could allow users to star and unstar posts and keep track of how many stars a post has received as follows:

SWIFT

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

Using a transaction prevents star counts from being incorrect if multiple users star the same post at the same time or the client had stale data. The value contained in the FIRMutableData class is initially the client's last known value for the path, or nil if there is none. The server compares the initial value against it's current value and accepts the transaction if the values match, or rejects it. If the transaction is rejected, the server returns the current value to the client, which runs the transaction again with the updated value. This repeats until the transaction is accepted or too many attempts have been made.

Note: Because runTransactionBlock:andCompletionBlock: is called multiple times, it must be able to handle nil data. Even if there is existing data in your remote database, it may not be locally cached when the transaction function is run, resulting in nil for the initial value.

Write data offline

If a client loses its network connection, your app will continue functioning correctly.

Every client connected to a Firebase database maintains its own internal version of any active data. When data is written, it's written to this local version first. The Firebase client then synchronizes that data with the remote database servers and with other clients on a "best-effort" basis.

As a result, all writes to the database trigger local events immediately, before any data is written to the server. This means your app remains responsive regardless of network latency or connectivity.

Once connectivity is reestablished, your app receives the appropriate set of events so that the client syncs with the current server state, without having to write any custom code.

Next Steps

發佈了5 篇原創文章 · 獲贊 5 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章