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

(اختیاری) نمونه اولیه و تست با Firebase Local Emulator Suite

Before talking about how your app reads from and writes to Realtime Database , let's introduce a set of tools you can use to prototype and test Realtime Database functionality: Firebase Local Emulator Suite . If you're trying out different data models, optimizing your security rules, or working to find the most cost-effective way to interact with the back-end, being able to work locally without deploying live services can be a great idea.

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

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

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

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

دریافت یک مرجع پایگاه داده FIR

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

سویفت

توجه: این محصول Firebase در App Clip target موجود نیست.
var ref: DatabaseReference!

ref = Database.database().reference()

هدف-سی

توجه: این محصول Firebase در App Clip target موجود نیست.
@property (strong, nonatomic) FIRDatabaseReference *ref;

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

نوشتن داده

این سند اصول اولیه خواندن و نوشتن داده‌های Firebase را پوشش می‌دهد.

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

عملیات نوشتن پایه

برای عملیات نوشتن اولیه، می‌توانید setValue برای ذخیره داده‌ها در یک مرجع مشخص استفاده کنید و هر داده موجود در آن مسیر را جایگزین کنید. می‌توانید از این متد برای موارد زیر استفاده کنید:

  • انواع داده‌ای که با انواع JSON موجود مطابقت دارند را به صورت زیر ارسال کنید:
    • NSString
    • NSNumber
    • NSDictionary
    • NSArray

برای مثال، می‌توانید یک کاربر را با استفاده setValue به صورت زیر اضافه کنید:

سویفت

توجه: این محصول Firebase در App Clip target موجود نیست.
self.ref.child("users").child(user.uid).setValue(["username": username])

هدف-سی

توجه: این محصول Firebase در App Clip target موجود نیست.
[[[self.ref child:@"users"] child:authResult.user.uid]
    setValue:@{@"username": username}];

استفاده از setValue به این روش، داده‌ها را در مکان مشخص شده، از جمله هر گره فرزند، بازنویسی می‌کند. با این حال، شما همچنان می‌توانید یک فرزند را بدون بازنویسی کل شیء به‌روزرسانی کنید. اگر می‌خواهید به کاربران اجازه دهید پروفایل‌های خود را به‌روزرسانی کنند، می‌توانید نام کاربری را به صورت زیر به‌روزرسانی کنید:

سویفت

توجه: این محصول Firebase در App Clip target موجود نیست.
self.ref.child("users/\(user.uid)/username").setValue(username)

هدف-سی

توجه: این محصول Firebase در App Clip target موجود نیست.
[[[[_ref child:@"users"] child:user.uid] child:@"username"] setValue:username];

خواندن داده‌ها

خواندن داده‌ها با گوش دادن به رویدادهای ارزشمند

برای خواندن داده‌ها در یک مسیر و گوش دادن به تغییرات، از observeEventType:withBlock از FIRDatabaseReference برای مشاهده رویدادهای FIRDataEventTypeValue استفاده کنید.

نوع رویداد کاربرد معمول
FIRDataEventTypeValue تغییرات کل محتوای یک مسیر را بخوانید و بشنوید.

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 snapshot will return false when you call exists() and nil when you read its value property.

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

سویفت

توجه: این محصول Firebase در App Clip target موجود نیست.
refHandle = postRef.observe(DataEventType.value, with: { snapshot in
  // ...
})

هدف-سی

توجه: این محصول Firebase در App Clip target موجود نیست.
_refHandle = [_postRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
  NSDictionary *postDict = snapshot.value;
  // ...
}];

شنونده یک FIRDataSnapshot دریافت می‌کند که شامل داده‌های موجود در مکان مشخص‌شده در پایگاه داده در زمان رویداد در ویژگی value آن است. می‌توانید مقادیر را به نوع native مناسب، مانند NSDictionary ، اختصاص دهید. اگر هیچ داده‌ای در مکان وجود نداشته باشد، value nil است.

یک بار خواندن داده‌ها

یک بار خواندن با استفاده از getData()

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

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

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

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

سویفت

توجه: این محصول Firebase در App Clip target موجود نیست.
do {
  let snapshot = try await ref.child("users/\(uid)/username").getData()
  let userName = snapshot.value as? String ?? "Unknown"
} catch {
  print(error)
}

هدف-سی

توجه: این محصول Firebase در App Clip target موجود نیست.
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 target موجود نیست.
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)
}

هدف-سی

توجه: این محصول Firebase در App Clip target موجود نیست.
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 ​​استفاده کنید.

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:

سویفت

توجه: این محصول Firebase در App Clip target موجود نیست.
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)

هدف-سی

توجه: این محصول Firebase در App Clip target موجود نیست.
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 ​​انجام دهید، مانند نحوه ایجاد پست جدید در هر دو مکان در این مثال. به‌روزرسانی‌های همزمان انجام شده به این روش اتمیک هستند: یا همه به‌روزرسانی‌ها موفق می‌شوند یا همه به‌روزرسانی‌ها شکست می‌خورند.

اضافه کردن یک بلوک تکمیل

If you want to know when your data has been committed, you can add a completion block. Both setValue and updateChildValues take an optional completion block that is called when the write has been committed to the database. This listener can be useful for keeping track of which data has been saved and which data is still being synchronized. If the call was unsuccessful, the listener is passed an error object indicating why the failure occurred.

سویفت

توجه: این محصول Firebase در App Clip target موجود نیست.
do {
  try await ref.child("users").child(user.uid).setValue(["username": username])
  print("Data saved successfully!")
} catch {
  print("Data could not be saved: \(error).")
}

هدف-سی

توجه: این محصول Firebase در App Clip target موجود نیست.
[[[_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 را ترک می‌کنید، Observerها به‌طور خودکار همگام‌سازی داده‌ها را متوقف نمی‌کنند. اگر یک observer به درستی حذف نشود، همچنان به همگام‌سازی داده‌ها با حافظه محلی ادامه می‌دهد. وقتی دیگر به یک observer نیازی نیست، آن را با ارسال FIRDatabaseHandle مرتبط به متد removeObserverWithHandle حذف کنید.

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

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

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

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

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

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

سویفت

توجه: این محصول Firebase در App Clip target موجود نیست.
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)
  }
}

هدف-سی

توجه: این محصول Firebase در App Clip target موجود نیست.
[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 its 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.

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

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

سویفت

توجه: این محصول Firebase در App Clip target موجود نیست.
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)

هدف-سی

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

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

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

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

مراحل بعدی