اگر تا اینجا مقالههای قبلی رو دنبال کرده باشید دیگه میدونید که چه طوری میشه یه tableView ساده رو با RxSwift پیاده سازی کردش و یا اینکه اگر tableView مون چند section مختلف داشت و یا از چند نوع cell تشکیل شده بود چه طوری باید اون رو پیاده سازی کردش. اما تا الان این طوری بوده که ما tableView مون رو به یه datasource وصل کرده بودیم و از اون میخوندیم که حالا یا ثابت بود یا از دیتابیس، وب سرویس و یا ... اما اگر بخواییم از چندجا این اطلاعات رو دریافت کنیم و بخونیم چی؟ مثلا فرض کنید یه table داریم از اسامی استانهای ایران که بالاش هم یه searchBar هستش و وقتی کاربر اسم استانی رو جستجو کردش اسم اون شهرهای فیلتر شده بیاد نه همه شهرها و وقتی هم اسمش رو پاک کرد همه شهرها! یعنی tableView بتونی بین این دو تا datasource تغییر کنه.
مثلا فرض کنید ما یه آرایه داریم که tableView مون اون رو نمایش میده و بعدی ما یه searchBar داریم که وقتی چیزی کاربر type کردش اون اطلاعات رو فیلتر کنه و نمایش بده، بالفرض یه لیستی از اسامی شهرها داریم و وقتی کاربر تایپ میکنه "کرم" توی لیست نتایج باید اسم شهرهای کرمان و کرمانشاه رو نشون بده! خوب توضیح بسته و بریم سراغ کدها اما قبلش نیاز هستش با چندتا Operator آشنا بشید.
راستش به نظر خودم بعضی مفاهیم رو هرچقدر هم ترجمه بشه اما باز مثل خوندن منابع اصلی نمیشه برای همین برای اشنایی با operator ها اول تعریف کتاب raywenderlich رو بخونیم:
Operators are the building blocks of Rx, which you can use to transform, process, and react to events emitted by observables. Just as you can combine simple arithmetic operators like +, -, and / to create complex math expressions, you can chain and compose together Rx's simple operators to express complex app logic.
پس Operator ها سازنده قسمتهایی در Rx هستن که، توسط اونها شما میتونید انتقال بدید، پردازش کنید، و یا واکنشی نسبت به اتفاق افتادن یک روی داد توسط observable ها رو نشون بدید. همون طوری که شما میتونید با ترکیب کردن عملگرهای ساده ای مثل جمع و تفریق و ضرب و ... یک عبارت پیچیده بسازید، میتونید با کنارهم قرار دادن این Operator ها عملکردهای پیشرفته ای رو بسازید.
یکی از پرکاربردترین operatorها combineLatest هستش که وظیفه اش این هستش که مقادیری رو که sequence های مختلف از خودشون منتشر میکنند رو باهم دیگه یکی کنه. ( مشاهده دیاگرام اصلی )
تمامی operator ها تو Rx همچین نموداری رو برای خودشون دارن که بهش میگن RxMarables Diagrams (برای اطلاعات بیشتر اینجا رو ببینید) که خط های فلش دار تایم لاین ایجاد داده روی هر sequence رو نشون میدن و خط عمودی قبل از فلش لحظه تموم شدن اون sequence رو. مثلا توی این نمودار روی خط زمانی اول مقدار ۱ ایجاد میشه بعدش مقدار ۴ روی خط زمانی دوم ایجاد میشه و بعد ۵ روی خط زمانی دوم و بعدش ۲ روی خط زمانی اول و ... خوب حالا فرض کنید ما بخوایم یه جایی رو داشته باشیم که به اتفاقات هر دو جریان زمانی گوش بدیم و مقادیر ایجاد شده رو به صورت هم زمان در هر لحظه داشته باشیم! اینجا جایی هستش که combinLatest به کمک مون میادش که خط زمانی سوم رو برامون به وجود میاره، که توی هر لحظه ای که خط زمانی اول یا دوم مقداری رو ایجاد کنه توی اون لحظه اخرین مقدار هر دو رو بهمون میده و ما بر حسب نیاز میتونیم عملیات مورد نظرمون رو روی اون اطلاعات انجام بدیم.
چون قسمتهای پیاده سازی خود tableView رو توی مقالات قبل گفتم دیگه اینجا فاکتور میگیرم ازش و میرم سر اصل ماجرا. اول از همه میام searchBar ای رو که اماده کردیم رو مقدارش رو به روش Rx ای گوش میدیم تا از تغییراتش مطلع بشیم:
خوب اینجا ما اومدیم گفتیم مقدار text که توی searchBar وارد شده رو اگر خالی نیست زمانی که تغییر پیدا کردش بهمون بده. به این شکل متغییر query ما جریان تغییرات متن searchBar رو، توی خودش نگه میداره.
کار این operator این هستش که از ایجاد مقادیر تکراری جلوگیری میکنه، و زمانی مقدار جدید emit میشه که، مقدار تولید شده تکراری نباشه وگرنه جلوی emit شدن رو میگیره:
همون طوری که میبینید مقدار ۱ تولید شد، مقدار دو هم تولید شد اما بار دومی که مقدار ۲ تولید شد جلوش گرفته شد و دوباره که مقدار ۱ تولید شد چون با مقدار قبلی متفاوت بود مقدارش emit شد.
خوب حالا که ما مقدار وارد شده داخل searchBar رو به صورت observable داریم و متغییر rows هم مقادیری رو که tableView باید نمایش بده رو به صورت observable داره، کافی هستش که اول این دوتا observable رو باهم ترکیب کنیم و بعدش نتیجه رو به tableView مون bind کنیم، این طوری tableView ما همیشه یا لیست کل شهرها رو نشون میده یا شهرهایی رو که کاربر داره سرچ میکنه رو!
خوب همون طوری که از کد هم معلومه ما نتیجه ترکیب شدن دو تا observable رو bind کردیم به tableView مون و اونجا هم cell مون رو مقدار دهی کردیم، اما یه قسمتی رو که توضیح ندادیم تابع filteredCities هستش، وقتی ما دوتا observable رو با combineLatest ترکیب میکنیم، داخل closure ای که داریم مقادیر اون ها رو در اختیار داریم و خروجی مون آرایه ای از جنس CityModel هستش که توسط تابع filteredCities به وجود میادش، این تابع میادش، هم شهر تایپ شده رو میگیره و هم لیست کل شهرها رو، و چک میکنه شهری با اون نام داریم یا نه؛ اگر داشتیم یه آرایه ای از اونها رو برمیگردونه و اگرم نداشتیم ارایه اصلی که لیست کل شهرها هست رو برمیگردونه.
با این حساب ما روی کلیهی تغییراتی که برای searchBar اتفاق مییوفته تسلط داریم و هندلشون میکنیم اینکه اگر مقدار جدید وارد شه، مقدارش تغییر کنه، کلا مقدارش رو پاک کنه کاربر و ... .
خوب به همین راحتی میشه یه tableView داشته باشیم که به یه searchBar متصل باشه! خوب حالا که با operator ها اشنا شدیم چندتا کاربرد دیگه شون هم باهم ببینیم.
یکی از مواردی رو که تا الان بهش اشاره نکردم این هستش که اگر بخواییم یکی از row های tableView رو انتخاب کنیم باید چی کار کنیم توی Rx ؟ برای این کار ما میتونیم از دو تا دستور modelSelected و itemSelected استفاده کنیم، اولی دیتای سطری که انتخاب شده و دومی indexPath سطری که انتخاب شده رو بهمون برمیگردونه.
حالا اگر بخواییم همزمان هم دیتایی که انتخاب شده رو داشته باشیم و هم indexPath سطری که انتخاب شده رو چی؟
این بار به جای اینکه از combineLatest استفاده کنیم از Operator ای به اسم zip استفاده کردیم اما کاربردش چه طوری هستش، این هم میادش مثل combineLatest مقدار دو تا observable رو باهم یکی میکنه و خروجی میده به ما که RxMarbles به این شکل هستش ( مشاهده دیاگرام اصلی ):
و حالا کدمون میشه به این شکل:
اما خوب اگر این طوری باشه فرقش با combineLatest توی چی هستش؟!
بیاید یه بار دیگه دیاگرام هاشون رو کنار هم بذاریم و نگاهشون کنیم با دقت بیشتری:
توی اپراتور combineLatest هر کدوم از sequence ها مقداری رو emit کنه ما یه خروجی ای داریم البته بعد از اینکه هر دو sequence اولین خروجی هاشون رو دادن و در واقع یه sequence معطل emit جریان دوم نمیشه، توی تصویر سمت چپی ۱ تولید شده بعدش ۴ که اینجا اولین خروجی هست و بعدش ۵ که با تولیدش ما خروجی بعدی رو داریم که ۱و۵ هستش، اما توی zip وقتی هر دو sequence اولین خروجی هاشون رو دادن ما زمانی خروجی داریم که با دوباره هر دو sequence مقادیر جدیدی رو emit کنن، مثلا توی شکل سمت راست اول sunny تولید شده بعدش Lisbon که خروجی این موقع emit شد و بعدش مقدار cloudy توی جریان اول تولید شده ولی ما خروجی ای نداریم تا اینکه sequence دومی بیادش و مقدار london رو emit کنه که توی این لحظه هستش ما خروجی جدید رو میگیریم.
خوب به نظر میرشه هرچیزی که لازم باشه برای tableView توی RxSwift رو بدونیم رو باهم مرور کردیم و یا اگر نیاز جدید باشه میتونیم با چیزایی که یاد گرفتیم اون رو برطرف کنیم و راهی براش به وجود بیاریم.
سورس کدهای این مقاله رو هم میتونید از این ادرس گیتهاب دانلود کنید.
توی مقالههای بعدی سعی میکنم مباحث دیگهای از RxSwift رو با هم بررسی کنیم و مرور کنیم و مطمئن باشید که هرچی بیشتر با RxSwift درگیر بشید لذت کد زدن باهاش رو بهتر لمس میکنید و میبینید که reactiev programming تا چه حد میتونه کمک کننده باشه و تسلط مون رو روی جریانهای داده ای داخل برنامه بیشتر کنه.
مثل همیشه خوشحال میشم که اگر نظر یا پیشنهادی داشتید در مورد مقاله، نوع کد زدن و ... رو باهام در میون بذارید، تا با هم و در کنار هم بیشتر پیشرفت کنیم.?
ممنون از همراهیتون.