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

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

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

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

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

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

یک بررسی دقیق شامل پایگاه داده بیدرنگ و توابع ابری در دسترس است. همچنین باید نگاهی به معرفی Local Emulator Suite داشته باشید.

یک مرجع FIRDatabase دریافت کنید

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

سریع

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

ref = Database.database().reference()

هدف-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])

هدف-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)

هدف-C

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

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

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

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

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

می‌توانید از رویداد FIRDataEventTypeValue برای خواندن داده‌ها در یک مسیر مشخص استفاده کنید، همانطور که در زمان رویداد وجود دارد. این روش یک بار در زمانی که شنونده متصل می شود و دوباره هر بار که داده ها، از جمله هر فرزند، تغییر می کند، فعال می شود. پاسخ تماس رویداد یک snapshot حاوی تمام داده‌ها در آن مکان، از جمله داده‌های فرزند ارسال می‌شود. اگر داده‌ای وجود نداشته باشد، عکس فوری با فراخوانی exists() nil false و زمانی که ویژگی value آن را می‌خوانید، عدد صفر را برمی‌گرداند.

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

سریع

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

هدف-C

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

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

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

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

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

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

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

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

سریع

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

هدف-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)
}

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

سریع

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

هدف-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 postid استفاده می کند و همزمان کلید را با getKey() بازیابی می کند. سپس می توان از کلید برای ایجاد یک ورودی دوم در پست های کاربر در /user-posts/$userid/$postid استفاده کرد.

با استفاده از این مسیرها، می‌توانید به‌روزرسانی‌های همزمان چندین مکان در درخت JSON را با یک فراخوانی برای updateChildValues ، مانند اینکه این مثال چگونه پست جدید را در هر دو مکان ایجاد می‌کند. به‌روزرسانی‌های همزمان ساخته شده به این روش اتمی هستند: یا همه به‌روزرسانی‌ها موفق می‌شوند یا همه به‌روزرسانی‌ها با شکست مواجه می‌شوند.

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

اگر می خواهید بدانید چه زمانی داده های شما تعهد شده است، می توانید یک بلوک تکمیل اضافه کنید. هر دو setValue و updateChildValues یک بلوک تکمیل اختیاری می گیرند که زمانی فراخوانی می شود که نوشتن به پایگاه داده متعهد شده باشد. این شنونده می تواند برای پیگیری اینکه کدام داده ها ذخیره شده اند و کدام داده ها هنوز همگام سازی می شوند مفید باشد. اگر تماس ناموفق بود، یک شی خطایی به شنونده ارسال می شود که نشان می دهد چرا شکست رخ داده است.

سریع

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

هدف-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)
  }
}

هدف-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);

هدف-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 آن داده ها را با سرورهای پایگاه داده راه دور و با سایر مشتریان بر اساس "بهترین تلاش" همگام سازی می کند.

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

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

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

مراحل بعدی