קרא וכתוב נתונים בפלטפורמות של אפל

(אופציונלי) אב-טיפוס ובדיקה עם Firebase Local Emulator Suite

לפני שנדבר על האופן שבו האפליקציה שלך קוראת וכותבת למסד נתונים בזמן אמת, הבה נציג קבוצה של כלים שבהם אתה יכול להשתמש כדי לבחון את הפונקציונליות של מסד נתונים בזמן אמת: Firebase Local Emulator Suite. אם אתה מנסה מודלים שונים של נתונים, מייעל את כללי האבטחה שלך, או עובד כדי למצוא את הדרך המשתלמת ביותר לאינטראקציה עם הקצה האחורי, היכולת לעבוד מקומית מבלי לפרוס שירותים חיים יכולה להיות רעיון מצוין.

אמולטור של מסד נתונים בזמן אמת הוא חלק מחבילת האמולטור המקומי, המאפשרת לאפליקציה שלך לקיים אינטראקציה עם תוכן מסד הנתונים ותצורת הדמיית שלך, כמו גם עם משאבי הפרויקט המחויכים שלך (פונקציות, מסדי נתונים אחרים וכללי אבטחה).

השימוש באמולטור Realtime Database כולל רק כמה שלבים:

  1. הוספת שורת קוד לתצורת הבדיקה של האפליקציה שלך כדי להתחבר לאמולטור.
  2. מהשורש של ספריית הפרויקט המקומית שלך, הפעל firebase emulators:start .
  3. ביצוע שיחות מקוד אב הטיפוס של האפליקציה שלך באמצעות SDK של פלטפורמת Realtime Database כרגיל, או באמצעות Realtime Database REST API.

הדרכה מפורטת הכוללת מסד נתונים ופונקציות ענן בזמן אמת זמין. כדאי גם לעיין במבוא של Local Emulator Suite .

קבל FIRDatabaseReference

כדי לקרוא או לכתוב נתונים ממסד הנתונים, אתה צריך מופע של FIRDatabaseReference :

מָהִיר

הערה: מוצר Firebase זה אינו זמין ביעד App Clip.
var ref: DatabaseReference!

ref = Database.database().reference()

Objective-C

הערה: מוצר Firebase זה אינו זמין ביעד App Clip.
@property (strong, nonatomic) FIRDatabaseReference *ref;

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

כתוב נתונים

מסמך זה מכסה את היסודות של קריאה וכתיבה של נתוני Firebase.

נתוני Firebase נכתבים להפניה של Database ומאוחזרים על ידי הצמדת מאזין אסינכרוני להפניה. המאזין מופעל פעם אחת עבור המצב ההתחלתי של הנתונים ושוב בכל פעם שהנתונים משתנים.

פעולות כתיבה בסיסיות

עבור פעולות כתיבה בסיסיות, אתה יכול להשתמש setValue כדי לשמור נתונים בהפניה שצוינה, תוך החלפת כל הנתונים הקיימים בנתיב זה. אתה יכול להשתמש בשיטה זו כדי:

  • סוגי מעבר התואמים לסוגי ה-JSON הזמינים באופן הבא:
    • NSString
    • NSNumber
    • NSDictionary
    • NSArray

לדוגמה, אתה יכול להוסיף משתמש עם setValue באופן הבא:

מָהִיר

הערה: מוצר Firebase זה אינו זמין ביעד App Clip.
self.ref.child("users").child(user.uid).setValue(["username": username])

Objective-C

הערה: מוצר Firebase זה אינו זמין ביעד App Clip.
[[[self.ref child:@"users"] child:authResult.user.uid]
    setValue:@{@"username": username}];

שימוש setValue באופן זה מחליף נתונים במיקום שצוין, כולל כל צמתים צאצאים. עם זאת, אתה עדיין יכול לעדכן ילד מבלי לשכתב את כל האובייקט. אם אתה רוצה לאפשר למשתמשים לעדכן את הפרופילים שלהם, תוכל לעדכן את שם המשתמש באופן הבא:

מָהִיר

הערה: מוצר Firebase זה אינו זמין ביעד App Clip.
self.ref.child("users/\(user.uid)/username").setValue(username)

Objective-C

הערה: מוצר Firebase זה אינו זמין ביעד App Clip.
[[[[_ref child:@"users"] child:user.uid] child:@"username"] setValue:username];

קרא נתונים

קרא נתונים על ידי האזנה לאירועים ערכיים

כדי לקרוא נתונים בנתיב ולהאזין לשינויים, השתמש ב- observeEventType:withBlock של FIRDatabaseReference כדי לצפות באירועי FIRDataEventTypeValue .

סוג אירוע שימוש אופייני
FIRDataEventTypeValue קרא והקשב לשינויים בכל התוכן של נתיב.

אתה יכול להשתמש באירוע FIRDataEventTypeValue כדי לקרוא את הנתונים בנתיב נתון, כפי שהוא קיים בזמן האירוע. שיטה זו מופעלת פעם אחת כאשר המאזין מחובר ושוב בכל פעם שהנתונים, כולל ילדים כלשהם, משתנים. ההתקשרות חזרה לאירוע מועברת snapshot המכילה את כל הנתונים באותו מיקום, כולל נתוני ילדים. אם אין נתונים, תמונת המצב תחזיר false כאשר תקרא exists() ו- nil כאשר תקרא את מאפיין value שלו.

הדוגמה הבאה מדגימה אפליקציית בלוגים חברתית המאחזרת את הפרטים של פוסט ממסד הנתונים:

מָהִיר

הערה: מוצר Firebase זה אינו זמין ביעד App Clip.
refHandle = postRef.observe(DataEventType.value, with: { snapshot in
  // ...
})

Objective-C

הערה: מוצר Firebase זה אינו זמין ביעד App Clip.
_refHandle = [_postRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
  NSDictionary *postDict = snapshot.value;
  // ...
}];

המאזין מקבל FIRDataSnapshot שמכיל את הנתונים במיקום המצוין במסד הנתונים בזמן האירוע במאפיין value שלו. אתה יכול להקצות את הערכים לסוג המקורי המתאים, כגון NSDictionary . אם לא קיימים נתונים במיקום, value הוא nil .

קרא נתונים פעם אחת

קרא פעם אחת באמצעות getData()

ה-SDK נועד לנהל אינטראקציות עם שרתי מסד נתונים בין אם האפליקציה שלך מקוונת או לא מקוונת.

באופן כללי, עליך להשתמש בטכניקות אירועי ערך שתוארו לעיל כדי לקרוא נתונים כדי לקבל הודעה על עדכונים לנתונים מה-backend. טכניקות אלו מפחיתות את השימוש והחיוב שלך, והן מותאמות כדי להעניק למשתמשים שלך את החוויה הטובה ביותר כשהם עוברים לאינטרנט ולא מקוון.

אם אתה צריך את הנתונים פעם אחת בלבד, אתה יכול להשתמש getData() כדי לקבל תמונת מצב של הנתונים ממסד הנתונים. אם מסיבה כלשהי getData() אינו מסוגל להחזיר את ערך השרת, הלקוח יבדוק את מטמון האחסון המקומי ויחזיר שגיאה אם ​​הערך עדיין לא נמצא.

הדוגמה הבאה מדגימה אחזור של שם משתמש הפונה לציבור של משתמש פעם אחת ממסד הנתונים:

מָהִיר

הערה: מוצר Firebase זה אינו זמין ביעד App Clip.
do {
  let snapshot = try await ref.child("users/\(uid)/username").getData()
  let userName = snapshot.value as? String ?? "Unknown"
} catch {
  print(error)
}

Objective-C

הערה: מוצר Firebase זה אינו זמין ביעד App Clip.
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;
}];

שימוש מיותר ב- getData() יכול להגביר את השימוש ברוחב הפס ולהוביל לאובדן ביצועים, שניתן למנוע על ידי שימוש במאזין בזמן אמת כפי שמוצג לעיל.

קרא נתונים פעם אחת עם צופה

במקרים מסוימים ייתכן שתרצה שהערך מהמטמון המקומי יוחזר באופן מיידי, במקום לבדוק אם יש ערך מעודכן בשרת. במקרים אלה אתה יכול להשתמש observeSingleEventOfType כדי לקבל את הנתונים ממטמון הדיסק המקומי באופן מיידי.

זה שימושי עבור נתונים שצריך לטעון רק פעם אחת ואינם צפויים להשתנות לעתים קרובות או לדרוש האזנה פעילה. לדוגמה, אפליקציית הבלוגים בדוגמאות הקודמות משתמשת בשיטה זו כדי לטעון פרופיל של משתמש כאשר הוא מתחיל לכתוב פוסט חדש:

מָהִיר

הערה: מוצר Firebase זה אינו זמין ביעד App Clip.
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

הערה: מוצר Firebase זה אינו זמין ביעד App Clip.
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 , תוכל לעדכן ערכי צאצא ברמה נמוכה יותר על-ידי ציון נתיב עבור המפתח. אם הנתונים מאוחסנים במספר מיקומים כדי להרחיב את גודלם טוב יותר, אתה יכול לעדכן את כל המופעים של נתונים אלה באמצעות מניפת נתונים . לדוגמה, אפליקציית בלוגים חברתית עשויה לרצות ליצור פוסט ולעדכן אותו בו-זמנית לעדכון הפעילות האחרונה ולפיד הפעילות של המשתמש המפרסם. לשם כך, יישום הבלוגים משתמש בקוד כזה:

מָהִיר

הערה: מוצר Firebase זה אינו זמין ביעד App Clip.
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

הערה: מוצר Firebase זה אינו זמין ביעד App Clip.
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 .

באמצעות נתיבים אלה, אתה יכול לבצע עדכונים בו-זמנית למספר מיקומים בעץ ה-JSON בקריאה בודדת ל- updateChildValues , כגון האופן שבו דוגמה זו יוצרת את הפוסט החדש בשני המיקומים. עדכונים בו-זמניים שנעשו בדרך זו הם אטומיים: או שכל העדכונים מצליחים או שכל העדכונים נכשלים.

הוסף בלוק השלמה

אם אתה רוצה לדעת מתי הנתונים שלך בוצעו, אתה יכול להוסיף חסימת השלמה. הן setValue והן updateChildValues ​​לוקחים בלוק השלמה אופציונלי שנקרא כאשר הכתיבה הוגדרה למסד הנתונים. מאזין זה יכול להיות שימושי למעקב אחר אילו נתונים נשמרו ואילו נתונים עדיין מסונכרנים. אם השיחה לא הצליחה, למאזין מועבר אובייקט שגיאה המציין מדוע התרחש הכשל.

מָהִיר

הערה: מוצר Firebase זה אינו זמין ביעד App Clip.
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

הערה: מוצר Firebase זה אינו זמין ביעד App Clip.
[[[_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 כערך עבור פעולת כתיבה אחרת כגון setValue או updateChildValues . אתה יכול להשתמש בטכניקה זו עם updateChildValues ​​כדי למחוק ילדים מרובים בקריאה אחת ל-API.

לנתק מאזינים

צופים לא מפסיקים אוטומטית לסנכרן נתונים כאשר אתה עוזב ViewController . אם צופה אינו מוסר כראוי, הוא ממשיך לסנכרן נתונים לזיכרון המקומי. כאשר אין עוד צורך בצופה, הסר אותו על ידי העברת FIRDatabaseHandle המשויך לשיטת removeObserverWithHandle .

כאשר אתה מוסיף בלוק התקשרות חזרה להפניה, מוחזר FIRDatabaseHandle . ניתן להשתמש בידיות אלה כדי להסיר את חסימת ההתקשרות חזרה.

אם נוספו מספר מאזינים להפניה למסד נתונים, כל מאזין נקרא כאשר אירוע מועלה. על מנת להפסיק את סנכרון הנתונים במיקום זה, עליך להסיר את כל הצופים במיקום על ידי קריאה לשיטת removeAllObservers .

קריאה ל- removeObserverWithHandle או removeAllObservers במאזין אינה מסירה אוטומטית מאזינים הרשומים בצמתי הצאצא שלו; עליך גם לעקוב אחר הפניות או נקודות אחיזה כדי להסיר אותן.

שמור נתונים כעסקאות

כשאתה עובד עם נתונים שעלולים להיפגם על ידי שינויים בו-זמניים, כגון מונים מצטברים, אתה יכול להשתמש בפעולת עסקה . אתה נותן לפעולה זו שני ארגומנטים: פונקציית עדכון והתקשרות אופציונלית להשלמה. פונקציית העדכון לוקחת את המצב הנוכחי של הנתונים כארגומנט ומחזירה את המצב הרצוי החדש שתרצה לכתוב.

לדוגמה, באפליקציית הבלוגים החברתיים לדוגמה, תוכל לאפשר למשתמשים לסמן ולבטל כוכב בפוסטים ולעקוב אחר כמה כוכבים פוסט קיבל באופן הבא:

מָהִיר

הערה: מוצר Firebase זה אינו זמין ביעד App Clip.
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

הערה: מוצר Firebase זה אינו זמין ביעד App Clip.
[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 זה אינו זמין ביעד App Clip.
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

הערה: מוצר Firebase זה אינו זמין ביעד App Clip.
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];

קוד זה אינו משתמש בפעולת עסקה, כך שהוא אינו מופעל מחדש באופן אוטומטי אם יש עדכון מתנגש. עם זאת, מכיוון שפעולת ההגדלה מתרחשת ישירות על שרת מסד הנתונים, אין סיכוי להתנגשות.

אם ברצונך לזהות ולדחות התנגשויות ספציפיות ליישום, כגון משתמש המסמן פוסט שהוא כבר סימן בכוכב לפני כן, עליך לכתוב כללי אבטחה מותאמים אישית עבור מקרה השימוש הזה.

עבודה עם נתונים במצב לא מקוון

אם לקוח מאבד את חיבור הרשת שלו, האפליקציה שלך תמשיך לפעול כהלכה.

כל לקוח המחובר למסד נתונים של Firebase שומר על גרסה פנימית משלו של כל נתונים פעילים. כאשר נתונים נכתבים, הם נכתבים תחילה לגרסה המקומית הזו. לאחר מכן, לקוח Firebase מסנכרן את הנתונים האלה עם שרתי מסד הנתונים המרוחקים ועם לקוחות אחרים על בסיס "המאמץ הטוב ביותר".

כתוצאה מכך, כל הכתיבה למסד הנתונים מפעילה אירועים מקומיים באופן מיידי, לפני כל כתיבה של נתונים לשרת. משמעות הדבר היא שהאפליקציה שלך נשארת מגיבה ללא קשר לאחזור הרשת או הקישוריות.

ברגע שהקישוריות מתבססת מחדש, האפליקציה שלך מקבלת את סט האירועים המתאים כך שהלקוח מסתנכרן עם מצב השרת הנוכחי, ללא צורך בכתיבת קוד מותאם אישית.

נדבר יותר על התנהגות לא מקוונת במידע נוסף על יכולות מקוונות ולא מקוונות .

הצעדים הבאים