(אופציונלי) אב-טיפוס ובדיקה עם Firebase Local Emulator Suite
לפני שנדבר על האופן שבו האפליקציה שלך קוראת וכותבת למסד נתונים בזמן אמת, הבה נציג קבוצה של כלים שבהם אתה יכול להשתמש כדי לבחון את הפונקציונליות של מסד נתונים בזמן אמת: Firebase Local Emulator Suite. אם אתה מנסה מודלים שונים של נתונים, מייעל את כללי האבטחה שלך, או עובד כדי למצוא את הדרך המשתלמת ביותר לאינטראקציה עם הקצה האחורי, היכולת לעבוד מקומית מבלי לפרוס שירותים חיים יכולה להיות רעיון מצוין.
אמולטור של מסד נתונים בזמן אמת הוא חלק מחבילת האמולטור המקומי, המאפשרת לאפליקציה שלך לקיים אינטראקציה עם תוכן מסד הנתונים ותצורת הדמיית שלך, כמו גם עם משאבי הפרויקט המחויכים שלך (פונקציות, מסדי נתונים אחרים וכללי אבטחה).
השימוש באמולטור Realtime Database כולל רק כמה שלבים:
- הוספת שורת קוד לתצורת הבדיקה של האפליקציה שלך כדי להתחבר לאמולטור.
- מהשורש של ספריית הפרויקט המקומית שלך, הפעל
firebase emulators:start
. - ביצוע שיחות מקוד אב הטיפוס של האפליקציה שלך באמצעות SDK של פלטפורמת Realtime Database כרגיל, או באמצעות Realtime Database REST API.
הדרכה מפורטת הכוללת מסד נתונים ופונקציות ענן בזמן אמת זמין. כדאי גם לעיין במבוא של Local Emulator Suite .
קבל FIRDatabaseReference
כדי לקרוא או לכתוב נתונים ממסד הנתונים, אתה צריך מופע של FIRDatabaseReference
:
מָהִיר
var ref: DatabaseReference! ref = Database.database().reference()
Objective-C
@property (strong, nonatomic) FIRDatabaseReference *ref; self.ref = [[FIRDatabase database] reference];
כתוב נתונים
מסמך זה מכסה את היסודות של קריאה וכתיבה של נתוני Firebase.
נתוני Firebase נכתבים להפניה של Database
ומאוחזרים על ידי הצמדת מאזין אסינכרוני להפניה. המאזין מופעל פעם אחת עבור המצב ההתחלתי של הנתונים ושוב בכל פעם שהנתונים משתנים.
פעולות כתיבה בסיסיות
עבור פעולות כתיבה בסיסיות, אתה יכול להשתמש setValue
כדי לשמור נתונים בהפניה שצוינה, תוך החלפת כל הנתונים הקיימים בנתיב זה. אתה יכול להשתמש בשיטה זו כדי:
- סוגי מעבר התואמים לסוגי ה-JSON הזמינים באופן הבא:
-
NSString
-
NSNumber
-
NSDictionary
-
NSArray
-
לדוגמה, אתה יכול להוסיף משתמש עם setValue
באופן הבא:
מָהִיר
self.ref.child("users").child(user.uid).setValue(["username": username])
Objective-C
[[[self.ref child:@"users"] child:authResult.user.uid] setValue:@{@"username": username}];
שימוש setValue
באופן זה מחליף נתונים במיקום שצוין, כולל כל צמתים צאצאים. עם זאת, אתה עדיין יכול לעדכן ילד מבלי לשכתב את כל האובייקט. אם אתה רוצה לאפשר למשתמשים לעדכן את הפרופילים שלהם, תוכל לעדכן את שם המשתמש באופן הבא:
מָהִיר
self.ref.child("users/\(user.uid)/username").setValue(username)
Objective-C
[[[[_ref child:@"users"] child:user.uid] child:@"username"] setValue:username];
קרא נתונים
קרא נתונים על ידי האזנה לאירועים ערכיים
כדי לקרוא נתונים בנתיב ולהאזין לשינויים, השתמש ב- observeEventType:withBlock
של FIRDatabaseReference
כדי לצפות באירועי FIRDataEventTypeValue
.
סוג אירוע | שימוש אופייני |
---|---|
FIRDataEventTypeValue | קרא והקשב לשינויים בכל התוכן של נתיב. |
אתה יכול להשתמש באירוע FIRDataEventTypeValue
כדי לקרוא את הנתונים בנתיב נתון, כפי שהוא קיים בזמן האירוע. שיטה זו מופעלת פעם אחת כאשר המאזין מחובר ושוב בכל פעם שהנתונים, כולל ילדים כלשהם, משתנים. ההתקשרות חזרה לאירוע מועברת snapshot
המכילה את כל הנתונים באותו מיקום, כולל נתוני ילדים. אם אין נתונים, תמונת המצב תחזיר false
כאשר תקרא exists()
ו- nil
כאשר תקרא את מאפיין value
שלו.
הדוגמה הבאה מדגימה אפליקציית בלוגים חברתית המאחזרת את הפרטים של פוסט ממסד הנתונים:
מָהִיר
refHandle = postRef.observe(DataEventType.value, with: { snapshot in // ... })
Objective-C
_refHandle = [_postRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) { NSDictionary *postDict = snapshot.value; // ... }];
המאזין מקבל FIRDataSnapshot
שמכיל את הנתונים במיקום המצוין במסד הנתונים בזמן האירוע במאפיין value
שלו. אתה יכול להקצות את הערכים לסוג המקורי המתאים, כגון NSDictionary
. אם לא קיימים נתונים במיקום, value
הוא nil
.
קרא נתונים פעם אחת
קרא פעם אחת באמצעות getData()
ה-SDK נועד לנהל אינטראקציות עם שרתי מסד נתונים בין אם האפליקציה שלך מקוונת או לא מקוונת.
באופן כללי, עליך להשתמש בטכניקות אירועי ערך שתוארו לעיל כדי לקרוא נתונים כדי לקבל הודעה על עדכונים לנתונים מה-backend. טכניקות אלו מפחיתות את השימוש והחיוב שלך, והן מותאמות כדי להעניק למשתמשים שלך את החוויה הטובה ביותר כשהם עוברים לאינטרנט ולא מקוון.
אם אתה צריך את הנתונים פעם אחת בלבד, אתה יכול להשתמש getData()
כדי לקבל תמונת מצב של הנתונים ממסד הנתונים. אם מסיבה כלשהי getData()
אינו מסוגל להחזיר את ערך השרת, הלקוח יבדוק את מטמון האחסון המקומי ויחזיר שגיאה אם הערך עדיין לא נמצא.
הדוגמה הבאה מדגימה אחזור של שם משתמש הפונה לציבור של משתמש פעם אחת ממסד הנתונים:
מָהִיר
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; }];
שימוש מיותר ב- getData()
יכול להגביר את השימוש ברוחב הפס ולהוביל לאובדן ביצועים, שניתן למנוע על ידי שימוש במאזין בזמן אמת כפי שמוצג לעיל.
קרא נתונים פעם אחת עם צופה
במקרים מסוימים ייתכן שתרצה שהערך מהמטמון המקומי יוחזר באופן מיידי, במקום לבדוק אם יש ערך מעודכן בשרת. במקרים אלה אתה יכול להשתמש 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) }
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); }];
עדכון או מחיקת נתונים
עדכן שדות ספציפיים
כדי לכתוב בו-זמנית לילדים ספציפיים של צומת מבלי לדרוס צמתים צאצאים אחרים, השתמש בשיטת 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)
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];
דוגמה זו משתמשת childByAutoId
כדי ליצור פוסט בצומת המכיל פוסטים עבור כל המשתמשים ב- /posts/$postid
ובו-זמנית לאחזר את המפתח עם getKey()
. לאחר מכן ניתן להשתמש במפתח ליצירת ערך שני בפוסטים של המשתמש ב- /user-posts/$userid/$postid
.
באמצעות נתיבים אלה, אתה יכול לבצע עדכונים בו-זמנית למספר מיקומים בעץ ה-JSON בקריאה בודדת ל- updateChildValues
, כגון האופן שבו דוגמה זו יוצרת את הפוסט החדש בשני המיקומים. עדכונים בו-זמניים שנעשו בדרך זו הם אטומיים: או שכל העדכונים מצליחים או שכל העדכונים נכשלים.
הוסף בלוק השלמה
אם אתה רוצה לדעת מתי הנתונים שלך בוצעו, אתה יכול להוסיף חסימת השלמה. הן setValue
והן updateChildValues
לוקחים בלוק השלמה אופציונלי שנקרא כאשר הכתיבה הוגדרה למסד הנתונים. מאזין זה יכול להיות שימושי למעקב אחר אילו נתונים נשמרו ואילו נתונים עדיין מסונכרנים. אם השיחה לא הצליחה, למאזין מועבר אובייקט שגיאה המציין מדוע התרחש הכשל.
מָהִיר
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."); } }];
מחק נתונים
הדרך הפשוטה ביותר למחוק נתונים היא לקרוא ל- removeValue
בהתייחסות למיקום הנתונים הללו.
אתה יכול גם למחוק על ידי ציון nil
כערך עבור פעולת כתיבה אחרת כגון setValue
או updateChildValues
. אתה יכול להשתמש בטכניקה זו עם updateChildValues
כדי למחוק ילדים מרובים בקריאה אחת ל-API.
לנתק מאזינים
צופים לא מפסיקים אוטומטית לסנכרן נתונים כאשר אתה עוזב ViewController
. אם צופה אינו מוסר כראוי, הוא ממשיך לסנכרן נתונים לזיכרון המקומי. כאשר אין עוד צורך בצופה, הסר אותו על ידי העברת FIRDatabaseHandle
המשויך לשיטת removeObserverWithHandle
.
כאשר אתה מוסיף בלוק התקשרות חזרה להפניה, מוחזר FIRDatabaseHandle
. ניתן להשתמש בידיות אלה כדי להסיר את חסימת ההתקשרות חזרה.
אם נוספו מספר מאזינים להפניה למסד נתונים, כל מאזין נקרא כאשר אירוע מועלה. על מנת להפסיק את סנכרון הנתונים במיקום זה, עליך להסיר את כל הצופים במיקום על ידי קריאה לשיטת removeAllObservers
.
קריאה ל- removeObserverWithHandle
או removeAllObservers
במאזין אינה מסירה אוטומטית מאזינים הרשומים בצמתי הצאצא שלו; עליך גם לעקוב אחר ההפניות או נקודות האחיזה כדי להסיר אותן.
שמור נתונים כעסקאות
כשאתה עובד עם נתונים שעלולים להיפגם על ידי שינויים בו-זמניים, כגון מונים מצטברים, אתה יכול להשתמש בפעולת עסקה . אתה נותן לפעולה זו שני ארגומנטים: פונקציית עדכון והתקשרות אופציונלית להשלמה. פונקציית העדכון לוקחת את המצב הנוכחי של הנתונים כארגומנט ומחזירה את המצב הרצוי החדש שתרצה לכתוב.
לדוגמה, באפליקציית הבלוגים החברתיים לדוגמה, תוכל לאפשר למשתמשים לסמן ולבטל כוכב בפוסטים ולעקוב אחר כמה כוכבים פוסט קיבל באופן הבא:
מָהִיר
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); } }];
השימוש בעסקה מונע מספירת כוכבים להיות שגויה אם מספר משתמשים מככבים באותו פוסט בו-זמנית או שללקוח היו נתונים מיושנים. הערך הכלול במחלקה FIRMutableData
הוא בתחילה הערך הידוע האחרון של הלקוח עבור הנתיב, או nil
אם אין כזה. השרת משווה את הערך ההתחלתי מול הערך הנוכחי שלו ומקבל את העסקה אם הערכים תואמים, או דוחה אותה. אם העסקה נדחתה, השרת מחזיר את הערך הנוכחי ללקוח, אשר מריץ את העסקה שוב עם הערך המעודכן. זה חוזר על עצמו עד שהעסקה מתקבלת או שנעשו יותר מדי ניסיונות.
מרווחים בצד השרת האטומי
במקרה השימוש שלמעלה אנו כותבים שני ערכים למסד הנתונים: מזהה המשתמש שמככב/מבטל את הכוכב בפוסט, וספירת הכוכבים המוגדלת. אם אנחנו כבר יודעים שהמשתמש מסמן את הפוסט בכוכב, נוכל להשתמש בפעולת תוספת אטומית במקום בעסקה.
מָהִיר
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];
קוד זה אינו משתמש בפעולת עסקה, כך שהוא אינו מופעל מחדש באופן אוטומטי אם יש עדכון מתנגש. עם זאת, מכיוון שפעולת ההגדלה מתרחשת ישירות על שרת מסד הנתונים, אין סיכוי להתנגשות.
אם ברצונך לזהות ולדחות התנגשויות ספציפיות ליישום, כגון משתמש המסמן פוסט שהוא כבר סימן בכוכב לפני כן, עליך לכתוב כללי אבטחה מותאמים אישית עבור מקרה השימוש הזה.
עבודה עם נתונים במצב לא מקוון
אם לקוח מאבד את חיבור הרשת שלו, האפליקציה שלך תמשיך לפעול כהלכה.
כל לקוח המחובר למסד נתונים של Firebase שומר על גרסה פנימית משלו של כל נתונים פעילים. כאשר נתונים נכתבים, הם נכתבים תחילה לגרסה המקומית הזו. לאחר מכן, לקוח Firebase מסנכרן את הנתונים האלה עם שרתי מסד הנתונים המרוחקים ועם לקוחות אחרים על בסיס "המאמץ הטוב ביותר".
כתוצאה מכך, כל הכתיבה למסד הנתונים מפעילה אירועים מקומיים באופן מיידי, לפני כל כתיבה של נתונים לשרת. משמעות הדבר היא שהאפליקציה שלך נשארת מגיבה ללא קשר לאחזור הרשת או הקישוריות.
ברגע שהקישוריות מתבססת מחדש, האפליקציה שלך מקבלת את קבוצת האירועים המתאימה כך שהלקוח מסתנכרן עם מצב השרת הנוכחי, ללא צורך לכתוב שום קוד מותאם אישית.
נדבר יותר על התנהגות לא מקוונת במידע נוסף על יכולות מקוונות ולא מקוונות .