خواندن و نوشتن داده‌ها در پلاتفرم‌های Apple

(اختیاری) نمونه‌سازی و آزمایش با Firebase Local Emulator Suite

قبل‌از صحبت درباره نحوه خواندن و نوشتن برنامه‌تان در Realtime Database، مجموعه‌ای از ابزارها را معرفی می‌کنیم که می‌توانید برای نمونه‌سازی و آزمایش عملکرد Realtime Database استفاده کنید: Firebase Local Emulator Suite. اگر درحال امتحان کردن مدل‌های داده مختلف، بهینه‌سازی قوانین امنیتی، یا تلاش برای یافتن مقرون‌به‌صرفه‌ترین راه برای تعامل با پس‌بک هستید، توانایی کار کردن به‌صورت محلی بدون استقرار خدمات زنده می‌تواند ایده بسیار خوبی باشد.

شبیه‌ساز Realtime Database بخشی از Local Emulator Suite است که به برنامه شما امکان می‌دهد با محتوا و پیکربندی پایگاه داده شبیه‌سازی‌شده شما، و همچنین به‌صورت اختیاری با منابع پروژه شبیه‌سازی‌شده شما (توابع، پایگاه‌های داده دیگر، و قوانین امنیتی) تعامل داشته باشد.

استفاده از شبیه‌ساز Realtime Database فقط چند مرحله دارد:

  1. افزودن یک خط کد به پیکربندی آزمایشی برنامه‌تان برای اتصال به شبیه‌ساز.
  2. از ریشه دایرکتوری پروژه محلی شما، درحال اجرای firebase emulators:start.
  3. برقراری تماس از کد نمونه اولیه برنامه بااستفاده از کیت توسعه نرم‌افزار پلاتفرم Realtime Database طبق معمول، یا بااستفاده از REST API‏ Realtime Database.

راهنمای گام‌به‌گام دقیقی شامل Realtime Database و Cloud Functions دردسترس است. همچنین باید نگاهی به Local Emulator Suite مقدمه بیندازید.

دریافت FIRDatabaseReference

برای خواندن یا نوشتن داده‌ها از پایگاه داده، به نمونه‌ای از FIRDatabaseReference نیاز دارید:

Swift

توجه: این محصول 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 به‌صورت زیر اضافه کنید:

Swift

توجه: این محصول 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 به این روش، داده‌ها را در مکان مشخص‌شده، ازجمله گره‌های فرزند، بازنویسی می‌کند. بااین‌حال، همچنان می‌توانید فرزند را بدون بازنویسی کل شیء به‌روز کنید. اگر می‌خواهید به کاربران اجازه دهید نمایه‌هایشان را به‌روز کنند می‌توانید نام کاربری را به‌صورت زیر به‌روز کنید:

Swift

توجه: این محصول 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 آن برمی‌گرداند.

مثال زیر نشان می‌دهد که چگونه یک برنامه وبلاگ‌نویسی اجتماعی جزئیات یک پست را از پایگاه داده بازیابی می‌کند:

Swift

توجه: این محصول 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()‎

این کیت توسعه نرم‌افزار برای مدیریت تعاملات با سرورهای پایگاه داده طراحی شده است، چه برنامه شما آنلاین باشد و چه آفلاین.

به‌طور کلی، باید از تکنیک‌های رویدادهای مقدار که در بالا شرح داده شده است برای خواندن داده‌ها استفاده کنید تا از به‌روزرسانی‌های داده‌ها از زیرینه مطلع شوید. این روش‌ها استفاده و صورت‌حساب شما را کاهش می‌دهند و برای ارائه بهترین تجربه به کاربران شما هنگام آنلاین و آفلاین شدن بهینه‌سازی شده‌اند.

اگر فقط یک‌بار به داده‌ها نیاز دارید، می‌توانید از getData() برای دریافت عکس آنی از داده‌های پایگاه داده استفاده کنید. اگر به هر دلیلی getData() نتواند مقدار سرور را برگرداند، کارخواه حافظه نهان فضای ذخیره‌سازی محلی را کاوش می‌کند و اگر مقدار همچنان پیدا نشود، خطایی برمی‌گرداند.

مثال زیر بازیابی نام کاربری عمومی کاربر را یک‌بار از پایگاه داده نشان می‌دهد:

Swift

توجه: این محصول 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 استفاده کنید تا داده‌ها را فوراً از حافظه نهان دیسک محلی دریافت کنید.

این روش برای داده‌هایی مفید است که فقط یک‌بار باید بار شوند و انتظار نمی‌رود که به‌طور مکرر تغییر کنند یا به گوش دادن فعال نیاز داشته باشند. برای مثال، برنامه وبلاگ‌نویسی در نمونه‌های قبلی از این روش برای بار کردن نمایه کاربر هنگام شروع نوشتن پست جدید استفاده می‌کند:

Swift

توجه: این محصول 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، می‌توانید مقادیر فرزند سطح پایین‌تر را با مشخص کردن مسیر کلید به‌روز کنید. اگر داده‌ها در چندین مکان ذخیره شده‌اند تا مقیاس‌بندی بهتر انجام شود، می‌توانید همه نمونه‌های آن داده‌ها را بااستفاده از انشعاب داده به‌روز کنید. برای مثال، یک برنامه وبلاگ‌نویسی اجتماعی ممکن است بخواهد پستی ایجاد کند و هم‌زمان آن را در فید فعالیت‌های اخیر و فید فعالیت‌های کاربر پست‌کننده به‌روز کند. برای انجام این کار، برنامه وبلاگ‌نویسی از کدی مانند این استفاده می‌کند:

Swift

توجه: این محصول 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 یک بلوک تکمیل اختیاری می‌گیرند که وقتی نوشتن در پایگاه داده انجام شد فراخوانی می‌شود. این شنونده می‌تواند برای پیگیری اینکه کدام داده‌ها ذخیره شده‌اند و کدام داده‌ها هنوز درحال همگام‌سازی هستند مفید باشد. اگر تماس ناموفق بود، یک شیء خطا به شنونده ارسال می‌شود که دلیل وقوع خطا را نشان می‌دهد.

Swift

توجه: این محصول 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 برای حذف چند فرزند در یک فراخوانی میانای برنامه‌سازی کاربردی استفاده کنید.

جدا کردن شنونده‌ها

وقتی ViewController را ترک می‌کنید، همگام‌سازی داده‌های ناظران به‌طور خودکار متوقف نمی‌شود. اگر ناظر به‌درستی برداشته نشود، به همگام‌سازی داده‌ها در حافظه محلی ادامه می‌دهد. وقتی دیگر به ناظر نیازی نیست، آن را با ارسال FIRDatabaseHandle مربوطه به روش removeObserverWithHandle بردارید.

وقتی بلوک فراخوانی را به مرجع اضافه می‌کنید، FIRDatabaseHandle برگردانده می‌شود. از این دسته‌ها می‌توان برای برداشتن بلوک بازخوانی استفاده کرد.

اگر چند شنونده به مرجع پایگاه داده اضافه شده باشند، هر شنونده هنگام ایجاد رویداد فراخوانده می‌شود. برای متوقف کردن همگام‌سازی داده‌ها در آن مکان، باید همه ناظران را در مکانی با فراخوانی روش removeAllObservers بردارید.

فراخوانی removeObserverWithHandle یا removeAllObservers در شنونده به‌طور خودکار شنونده‌های ثبت‌شده در گره‌های فرزند آن را حذف نمی‌کند؛ همچنین باید آن ارجاع‌ها یا دستگیره‌ها را پیگیری کنید تا آن‌ها را حذف کنید.

ذخیره داده‌ها به‌عنوان تراکنش

هنگام کار با داده‌هایی که ممکن است به‌دلیل اصلاحات هم‌زمان خراب شوند، مثل شمارنده‌های افزایشی، می‌توانید از عملیات تراکنش استفاده کنید. دو آرگومان به این عملیات می‌دهید: یک تابع به‌روزرسانی و یک کال‌بک تکمیل اختیاری. تابع به‌روزرسانی وضعیت فعلی داده‌ها را به‌عنوان یک آرگومان درنظر می‌گیرد و وضعیت مطلوب جدیدی را که می‌خواهید بنویسید برمی‌گرداند.

برای مثال، در نمونه برنامه وبلاگ اجتماعی، می‌توانید به کاربران اجازه دهید پست‌ها را ستاره‌دار و بی‌ستاره کنند و تعداد ستاره‌هایی را که پست دریافت کرده است به‌صورت زیر پیگیری کنید:

Swift

توجه: این محصول 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 است. سرور مقدار اولیه را با مقدار فعلی مقایسه می‌کند و اگر مقادیر مطابقت داشته باشند، تراکنش را می‌پذیرد، درغیراین‌صورت آن را رد می‌کند. اگر تراکنش رد شود، سرور مقدار فعلی را به کارخواه برمی‌گرداند و کارخواه تراکنش را دوباره با مقدار به‌روزشده اجرا می‌کند. این کار تا زمانی که تراکنش پذیرفته شود یا تعداد تلاش‌ها بیش‌ازحد شود تکرار می‌شود.

افزایش اتمی سمت سرور

در مورد استفاده بالا، دو مقدار را در پایگاه داده می‌نویسیم: شناسه کاربری که پست را ستاره‌دار/بدون ستاره می‌کند، و تعداد ستاره‌های افزایش‌یافته. اگر ازقبل بدانیم که کاربر پست را ستاره‌دار می‌کند، می‌توانیم به‌جای تراکنش از عملیات افزایش اتمی استفاده کنیم.

Swift

توجه: این محصول 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 آن داده‌ها را با سرورهای پایگاه داده راه دور و با کارخواه‌های دیگر براساس «بهترین تلاش» همگام‌سازی می‌کند.

درنتیجه، همه نوشتن‌ها در پایگاه داده بلافاصله رویدادهای محلی را راه‌اندازی می‌کنند، قبل‌از اینکه داده‌ای در سرور نوشته شود. این یعنی برنامه‌تان بدون توجه به تأخیر شبکه یا اتصال، واکنش‌گرا باقی می‌ماند.

پس‌از برقراری مجدد اتصال، برنامه شما مجموعه مناسبی از رویدادها را دریافت می‌کند تا سرویس‌گیرنده با وضعیت فعلی سرور همگام‌سازی شود، بدون اینکه نیازی به نوشتن کد سفارشی باشد.

در درباره قابلیت‌های آنلاین و آفلاین بیشتر بدانید، درباره رفتار آفلاین بیشتر صحبت خواهیم کرد.

مراحل بعدی