در پارت اول یه مقدمه ای از MVVM و RxSwift گفته شد و اگر به نظرتون RxSwift سخت و با ابهام بوده نگران نباشید، Rx در اول شاید سخت به نظر برسه اما با مثال های این پارت و تمرین ،ساده و قابل فهم میشه.
در این پارت یک پروژه کامل رو با دیزاین پترن MVVM با RxSwift می زنیم و از مزایای Rx و MVVM در پروژه ی واقعی بهره میگیریم.
پروژه رو می تونید از این لینک تو گیتهاب دانلود کنید.
پروژه ای که مد نظر گرفتم یه اپ خیلی سادس که لیستی از آلبوم ها و آهنگ های گروه Linkin Park رو در قالب UICollectionView و UITableView نشون میده.
برای اینکه جلو تر در پروژه مجبور نباشیم کالکشن ویو آلبوم ها و تیبل ویو لیست آهنگ ها رو دوباره بزنیم بهتره یه جوری بزنیم که بتونیم بعدا به راحتی ازشون استفاده کنیم ( Reusability )
برای اینکار UIViewController مادر رو با استفاده از ContainerView تقسیم به دو قسمت میکنیم:
پس یعنی کلاس ویوکنترلر مادر از دو ChildViewController تشکیل شده ( برای مطالعه در مورد ChildViewController و استفاده از آن میتونید این مقاله رو بخونید )
برای Cellهامون هم از nib استفاده می کنیم که اونم بتونیم به راحتی reuse کنیم :
برای رجیستر کردن cell های فایل nib باید این کد رو در متد viewDidLoad کلاس AlbumCollectionViewVC بزارید که UICollectionView بفهمه نوع Cell هاش رو:
دقت کنید که این کد باید در کلاس AlbumCollectionViewVC قرار بگیره یعنی یکی از کلاس های فرزند کلاس مادر و کلاس مادر هیچ ارتباطی با ابجکت های کلاس فرزندش فعلا نداره.
برای TrackTableViewVC هم دقیقا همین روال با تفاوت اینکه فقط تیبل ویو هستش انجام میدیم.
حالا میریم سراغ کلاس مادر و باید ۲ کلاس فرزندمون رو ستاپ کنیم:
همون طور که در عکس استوری بورد دیدید جای کلاس های فرزند دو ویو هست که در واقع ویو کنترلر هامون توش هست به این ویو ها ContainerView میگند برای ستاپ این ویو ها از کد زیر استفاده می کنیم:
برای این از lazy استفاده کردیم جدا از اینکه initialize خود آلبوم خیلی خوانایی بیشتر داره تا اینکه تو viewDidload انجام شه، تا موقعی که به albumsViewController دستیابی نکنید وارد مموری نشده بالعکس بقیه متغیر های کلاس که موقع ساخته شدن کلاس وارد مموری میشند ( این بخاطر lazy بودن متغیر هست )
برای track نیز همین کارا دقیقا انجام میشه .
حالا میریم سراغ viewModel و ًRxSwift پروژه:
در کلاس ویو مدل صفحه اصلی ما باید دیتا رو از سرورمون بگیریم و پارس کنیم به صورتی که دقیقا ویو میخواد سپس به کلاس مادر بدیم و کلاس مادر اون دیتا ها رو پاس بده به دو تا ویو کنتلر های فرزند پس یعنی کلاس مادر از ویو مدلش درخواست دیتا می کنه و ویو مدل از لایه نتورک درخواستشو میفرسته بعد ویو مول دیتای بازگشتی رو پارس می کنه و آماده به کلاس مادر میده.
حالا قسمت قشنگ ماجرا میرسه و Rxswift وارد میشه:
خب قبل اینکه وارد کد بشیم ببینیم ویو مدل جز دیتا دیگه چه چیزایی به کلاس ما میده:
۲. ارور های ممکنه از سرور و غیره که میتونه هر چی باشه : پاپ آپ یا ارور اینترنت و ... اینم باید observable از نوع ارور دلخواهمون باشه که اگر مقداری داشت رو صفحه نشون بدیم
۳. دیتا های کالکشن و تیبل ویو...
پس ۳ نوع Observable داریم که باید کلاس مادرمون به اینا رجیستر بشه حالا چطوری:
این ها متغیر های کلاس ویو مدلمون هستند که هر ۴ تا Observable هستند و بدون مقدار اولیه . سوال براتون پیش میاد که خب PublishSubject چیه؟
Subjects
در دنیای Rx همون طور که گفته شد یه سری متغیر ها Observer و یه سری Observable هستند، حالا یه سری دیگه هستند که همزمان هم میتونن Observer باشند هم Observable به اینا Subjects میگند.
خود Subjects ها به ۴ نوع تقسیم میشند که توضیح راجب تک تکشون مقاله جدا می خواد ولی واسه این پروژه من از PublishSubject استفاده کردم که تقریبا پر کاربرتریته (اگر راجع به ۴ سابجکت دوس دارید بخونید این مقالرو پیشنهاد میکنم )
یکی از دلایل خوب استفاده از این نوع سابجکت اینه که بدون مقدار اولیه میتونه initialize بشه.
ViewModel Request:
حالا میریم سراغ کدش و ببینیم چطوری دیتا ها رو به ویومون بدیم:
قبل اینکه بریم سراغ کد ویو مدل بهتره اول بریم سراغ کد کلاس HomeVC و ۴ متغیر بالا رو وصل کنیم به کلاسمون به ( اصطلاح قسمت data binding ) که کلاسمون آماده باشه برای دیتا هایی که ویو مدل میده بهش در واقع observe میکنیم دیتا های تولیدی ViewModel رو.
در کلاس HomeVC در متد ViewDidLoad :
در این قسمت داریم loading رو bind می کنیم و بهش میگیم هر مقداری میگیری وصلش کن به rx.isAnimating .. خب الان دارید میگید به این راحتی هر وقت ویومدل true فرستاد به این راحتی لودینگ رو نشون میده باید هم بگم اره و هم بگم نه ...
RxCocoa:
برای اینکه دیتا ها بتونن بایند بشند به UIkit به لطف RxCocoa پراپرتی های زیادی از ویو های مختلف با دستور .Rx در دسترسند که همه از نوع observable که بتونید خیلی راحت عملیات بایندینگ رو انجام بدید بعضی از observable های کلاس UIView:
خب طبیعتا همه ی پراپرتی ها موجود نیست و بعضی ها رو باید خودتون بنویسید مثل isAnimating بالا که من لودینگ رو بهش بایند کردم با این extension : ( فایل ReactiveExtensiones.swift در پروژه)
حالا که لودینگمون آمادس که ViewModel بهش دیتا بده میریم سراغ بقیه بایندر ها.
برای ارور ها مثل پارت اول سابسکرایبشون کافیه بکنیم و به باندینگی نیاز نیست:
در کد بالا هر وقت اروری از ویو مدل به وجود بیاد ما اینجا داریم بهش گوش می کنیم چون subscribe کردیم ، حالا شما میتونید هر کاری بکنید با این ارور، که من دارم یه پاپ اپ مانند نشون میدم .باید اشاره کنم که در کد بالا .observeOn(MainScheduler.instance) چیه: این قسمت از کد میاد سیگنال های دریافتی ( اینجا ارورها ) رو از هر thread ی که هست به main thread میاره چون میخوایم رو ui نشون بدیم باید از بکگراند ترد به این ترد بیایم )
حالا میریم سراغ آلبوم ها و ترک ها که وصل بشند به UICollectionView و UITableView چون خود پراپرتی های collectionView , tableView در دو کلاس فرزند هستند فعلا کافیه آرایه ای از آلبوم ها و ترک ها رو بایند کنیم به کلاس فرزند و به کلاس فرزند بسپریم که این ها رو نشون بده: (در آخر این مقاله این کارو می کنیم)
حالا برمیگردیم به ویو مدلمون و تولید دیتا رو ببینیم چجوریه:
۱. در اول داریم لودینگ رو true میکنیم و چون تو کلاسمون بایندینگ رو انجام دادیم صفحه کاربر به حالت لودینگ در میاد و تا وقتی که false نفرستیم ادامه داره.
۲.در خط بعدی داریم با apimanager که قسمت نتورک اپ و api کال هاست رو انجام میدیم که من از کتابخونه ای استفاده نمی کنم و شما از هر چیزی که واسه نتورکتون استفاده میکنید اینجا کال کنید api تون رو.
۳.حالا که جواب از سرور اومده باید به لودینگ خاتمه بدیم پس رو loading میایم false میفرستیم و اپمون از حالت لودینگ در میاد.
۴. حالا بنا به جواب سرور اگه به مشکل خوردیم میایم ارور رو با مقدار مورد نظر میفرستیم و دوباره چون کلاس مادر subscribe کرده لودینگ رو داره به لودینگ های emit شده از ویو مدلش گوش میده .
۵. اگر هم جواب موفقیت آمیز و بدون ارور گرفتیم میایم با این ۴ خط پارس و به کلاسمون میفرستیم که پاس بده به دو کلاس فرزند البوم و ترک.
خب دو خط اول شاید مبهم به نظر برسه یه توضیح مختصر بدم:
در بالا با استفاده از SwiftyJson جیسون بازگشتی رو پارس می کنیم و با استفاده از یکی از فانکشن های کاربردی functional programming نوعش رو از JSON به آلبوم تبدیل می کنیم. اینکه انقدر راحت مدلمون ساخته میشه بخاطر Codable بودن مدل هاست که از سوییفت ۴ اضافه شده?.
حالا که آماده شد دیتا و پاس دادیم به کلاس فرزند بریم در مرحله آخر که تو CollectionView و TableView نشون بدیم:
اگر یادتون باشه در کلاس مادر، ترک ها رو بانید کردیم به tracks از پراپرتی های trackTableViewVC که اینم subject گذاشتیم:
حالا در متد viewDidLoad کلاس trackTableViewVC باید ترک ها رو بایند کنیم به UITableView که این کار به لطف RxCocoa در ۲ خط زیر انجام میشه:
بله درسته فقط و فقط همین دو خط دیگه نه خبری از ست کردن delegateهست نه خبری از numberOfSections و numberOfRowsInSection و ... خود RxCocoa همرو براتون هندل می کنه خودش model ای که بهش دادید رو ( بایند کردید ) میفهمه و با دادن نوع cell همه ی متغیر های لازم برای ساختن tableView ,CollectionView رو بهتون تو closure میده:
اولیش row هست ( همون indexPath.row ) دومیش مدل متناظر با row و در آخر cell ای که دادید و
به همین راحتی که دیدید دیتا ها سر جاشون نشون داده میشند . راستی اینکه چطوری cell.track همه ی عکس و غیره رو هندل می کنه :
میتونید شما تو همون closure بالا ست کنید اما خب به نظرم تو خود کلاس UITableViewCell انجام بشه خیلی بهتره و قطعا خوانا تر....
قبل اینکه تموم کنم مقالرو میتونید با کد زیر انیمیشن خیلی قشنگی به پروژه بدید و حس خوبی به کاربرتون بده:
خب ما یه پروژه نسبتا کامل رو با دیزاین پترن MVVM با کمک RxSwift ,RxCocoa زدیم . امیدوارم شما رو تا حد خوبی با این کانسپت ها آشنا کرده باشم . پروژه تمام شده رو میتونید از این لینک دریافت کنید و خوشحال میشم به صفحه گیتهابم سری بزنید.
ایمیل من : mohammad_Z74@icloud.com خوشحال میشم نظراتون رو بدونم.