عاشق دنیای برنامه نویسی و هوش مصنوعی
MVVM + RxSwift on iOS part 1
امروز میخوام دیزاین پترن MVVM رو در دنیای برنامه نویسی iOS تا یه حد خوبی معرفی کنم که البته همراه با RxSwift. مقاله در ۲ پارت تقسیم میشه که در پارت اول یه توضیح خیلی کلی از دیزاین پترن ها میدم و دانش اولیه و مورد نیاز از RxSwift و در پارت دوم یک پروژه ی سمپل رو با MVVM و RxSwift میزنیم.
Design patterns:
قبل از هر چیزی بهتره یه توضیح بدم که چرا اصلا از دیزاین پترن ها استفاده می کنیم خیلی خلاصه بگم برای اینکه کدمون به اصطلاح spaghetti نشه و بهم نریزه و خب قطعا این تنها دلیلش نیست یکی از دلایلش تست پذیری ه ، دیزاین پترن های زیادی هست که میتونیم به معروف ترین این ها به ٖٖMVC, MVP, MVVM, VIPER اشاره کنیم یه عکس خوب از اسلاید های NSLondon که این ها رو از سه لحاظ تست پذیری،توزیع پذیری و راحتی استفاده مقایسه میکنه:
همه ی این دیزاین پترن ها خوبی ها و بدی های خودشونو دارند اما در اخر هر کدوم به یک نحوی کد مارو تمیز تر و ساده تر میکنند. در این مقاله روی MVVM تاکید شده که امیدوارم آخر قسمت دوم مقاله دلیلشو بفهمید و خوشحال میشم نظرتون رو بدونم.
خب اول یه نگاه خیلی کوچیک به MVC داشته باشیم و بعد بریم سراغ MVVM
MVC:
احتمالا اگر مدتی باشه که برنامه نویسی iOS کرده باشید با دیزاین پترن MVC آشنا باشید. ( در دیزاین پترن ها برای برنامه نویسی iOS، پیشنهاد خود اپل MVC هست )این لایه از سه قسمت اصلی Model , View ,Controller تشکیل شده که ViewController مسولیت به هم وصل کردن ویو و مدل رو داره که از لحاظ تيوری به نظر View و Controller جداهستند اما در دنیای برنامه نویسی iOS این دو لایه تا حد زیادی متاسفانه یکی میشوند ، البته در پروژه های کوچیک همه چی مرتب به نظر میرسه و مشکل خاصی نیست اما وقتی پروژتون از یه حدی بیشتر میشه تقریبا Controller تمامی مسولیت رو به عهده داره ( معروف به Massive View Controller :دی )که باعث میشه خیلی شلوغ باشه، البته MVC اگر واقعا درست پیاده سازی شه و کنترلتون رو تا حد امکان تقسیم کنید این مشکل تا حدودی حل میشه ولی در این مقاله یه پروژه ی سمپل رو هم با MVC و هم با MVVM میزنیم و خودتون تصمیم بگیرید براتون کدوم بهتره.
MVVM:
خب MVVM مخفف Model View ViewModel هست که کنترلتون و انیمیشن های ویو ها و در کل هر متدی که با ویو سر و کار داره تو View قرار می گیره و در ViewModel کال های api و بیزنس لاجیکتون در این لایه قرار میگیره .در واقع این لایه واسط بین مدل و ویو هست و قراره دیتا رو همون طوری که View میخواد از این لایه آماده کنید و به View بدید، و یه نکته ای که هست اگر تو فایل ViewModel دیدید :
import UIKit
گذاشتید بدونید یه جای کار رو اشتباه کردید و ویومدل نباید هیچ اطلاعی ازش داشته باشه.در پارت دوم مقاله این پترن رو با مثال دقیق بررسی میکنیم.
RxSwift:
یکی از ویژگی های MVVM بایند بودن ( متصل بودن و سینک بودن ) دیتا و ویو هست که اینکار با RxSwift بسیار دلپذیر میشه.. البته می تونید با delegate یا KVO یا Closure هم اینکارو کنید اما یکی از ویژگی های دنیای Rx اینه که شما اگرRx رو در یک زبون تا حد خوبی یاد بگیرید می تونید در بقیه پلتفرم ها هم استفاده کنید چون بیس و ساختار اصلی Rx تو همه ی زبونای موجودش یکیه حالا در قسمت اول مقاله می خوام کلیات RxSwift رو توضیح بدم که در واقع بیس دنیای Rx هست و بعد در پارت دوم میریم یه پروژه رو MVVM میزنیم با RxSwift.
Reactive programming:
خب RxSwift بر مبنای Reactive Programming ساخته شده حالا این یعنی چی:
In computing, reactive programming is a programming paradigm oriented around data flows and the propagation of change. This means that it should be possible to express static or dynamic data flows with ease in the programming languages used, and that the underlying execution model will automatically propagate changes through the data flow. — Wikipedia
احتمال خیلی قوی بعد خوندن این پاراگراف هیچی نفهمیدید و بهتره با توضیحات ادامه مقاله درکش کنید:
بخوایم مثال خیلی ساده بگیم فرض کنید شما سه متغیر a,b,c دارید که :
حالا اگر a رو از ۱ به ۲ تغییر بدیم و c رو پرینت کنیم باز همون ۳ هست مقدار c .اما دنیای reactive مسئله جور دیگریست و مقدار c وابسته به a , b هست و شما اگر a رو از ۱ به ۲ تغییر بدید c هم مقدارش از ۳ به ۴ تغییر می کنه و لازم نیست خودتون مقدار c رو دوباره تغییر بدید:
به شیوه ی اول و رایج کد زنی Imperative programming می گویند.
حالا بریم سراغ مباحث اصلی RxSwift
در دنیای RxSwift و در کل Rx، همه چیز استریمی از اتفاق ها و رویداد هاست ( اعم از UI events, Network requests ) حالا این رو تو ذهنتون باشه که در ادامه با مثال توضیح بدم:
گوشی شما یک observable (به فارسی: قابل مشاهده) هست به طور مثال با انتشار یک سری رویداد مثلا زنگ زدن ، نوتیفیکیشن های اپلیکیشن و ... شما رو صدا میزنه و در واقع شما گوشیتون رو subscribe کردید و تصمیم می گیرید با این اتفاق چه کنید به طور مثال از بعضی از نوتیفیکشین ها می گذرید یا بعضیاشونو جواب میدید یا ... ( در واقع این اتفاق ها برای شما signal هستند و شما یک observer هستید و تصمیم گیرنده.)
حالا وارد مثال دنیای برنامه نویسیش میشیم:
Observables and observers (subscribers):
در دنیای Rx یک سری از متغیر ها Obsarvable هستند و یک سری observers یا همون subscribers
از اونجایی که Obsarvable جنریک هست شما می تونید Obsarvable از تایپی که می خواید بسازید ( هر تایپی که به پروتکول ObservableType کانفرم بشه) :
خب حالا یک سری Obsarvable تعریف کنیم:
در مثال بالا در خط اول Obsarvable از نوع String ، در خط دوم از نوع Int و در آخرین خط از نوع دیکشنری درست کردیم حالا این متغیرهای Obsarvable امون رو باید subscribe کنیم تا بتونیم ازشون سیگنال های منتشر شده رو بخونیم:
ممکنه براتون سوال پیش بیاد که در خروجی next و complete چی هستند و چرا فقط "Hello Rx World " چاپ نشده خب برای جواب این سوال باید اصلی ترین ویژگی Observable ها رو بگم:
تمامی Observable ها در واقع همان sequence هستند که فرق اصلی این sequence با swift sequence در این هست که میتونه مقادیرش asynchronously باشه ( اگر این دو خط رو متوجه نشدید زیاد اشکالی نداره و با توضیح بعدی هم کارتون راه میفته ) بخوایم با شکل بگیم :
در عکس بالا سه نوع Observable داریم که اولی از نوع Int هست و ۶ مقدار ۱ تا ۶ رو طی زمان منتشر کرده و بعد تموم شده. در نوع دوم Observable امون از نوع String هستش و از a تا f رو طی زمان منتشر کرده بعد با یک ارور به پایان رسیده. در نوع سوم Observable از نوع تپ دکمه هستش و به پایان نرسیده و ادامه داره.
به این شکل ها که Observable ها رو نشون میدن marble diagrams نامیده میشوند و برای اطلاع بیشتر میتونید هم سایتش و هم اپلیکیشنش رو از استور دانلود کنید. ( اپش اپن سورسم هست :دی )
در دنیای Rx برای هر Observable در طول زمان وجودش ۰ یا تعداد بیشتری event منتشر می کنه ( مثال بالا ) که این event ها enum ای از سه حالت ممکن زیر هستند:
1. .next(value: T)
2. .error(error: Error)
3. .completed
وقتی Observable مقدار یا مقادیری به آن اضافه می شود Observable اونت next را اجرا می کند و مقدار یا مقادیر اضافه شده را از طریق value به subscribers یا observers میدهد .( اعداد ۱ تا ۶ در مثال نوع ۱ ، a تا f در مثال نوع ۲ و tap ها در مثال نوع ۳ )،۹۰ درصد مواقع در پروژه هاتون این متد به کار میاد تا ۲ متد بعدی.
اگر Observable با خطا مواجه شود اونت error را منتشر می کنه و Observable به اصطلاح تمام میشود . ( مثال نوع ۲ )
اگر Observable به اتمام برسه completed صدا زده میشه ( بعد از صدا زده شدن ۶ در مثال نوع ۱ )
برای اینکه یک subscription رو کنسل کنیم و دیگه یک Observable رو unsubscribe کنیم متد dispose رو می تونیم صدا کنیم یا اگر می خواهید بعد از deinit شدن ویوتون خودبه خود اینکار انجام شه یه باید یک متغیر از نوع DisposeBag درست کنیم و این متغیر خودش کار dispose رو در زمان deinit کلاس انجام میده باید اضافه کنم اگر این کار رو نکنید subscribers هاتون باعث به وجود اومدن memory leak میشند.
به طور مثال در مثال شکل Obsarvable ها باید subscription به صورت زیر انجام بشه:
حالا یکم از زیبایی های ترکیب دنیای Rx با functional programming صحبت کنیم، فرض کنید شما Observable از نوع Int دارید و شما subscribe کردید به اون Obsarvable ، حالا Obsarvable بهتون یه سری از Int میده شما میتونید با فانکشن های فوق العاده کارآمد functional programming یه سری تغییرات روی سیگنال های emit شده از Obsarvable انجام بدید مثلا :
map:
برای تبدیل سیگنال های منتشر شده قبل اینکه به subscriber یا observer مورد نظر برسه از متد map استفاده میتونید بکنید مثلا یک obsarvable از نوع Int داریم که در طی زمان ۳ عدد ۲و۳و۴ رو emit یا منتشر می کنه حالا می خوایم قبل اینکه به subscriber برسه ۱۰ برابرش کنیم و عدد ۱۰ برابر شده به subscriber برسه:
filter:
شاید بخواید قبل اینکه به subscriber تون برسه بعضی از اونا رو فیلتر کنید مثلا در مثال بالا میخواید بعد از اینکه ضرب در ۱۰ شدن فقط اعدادی که بالای ۲۵ هستند رو چاپ کنه:
flatMap:
فرض کنید دو عدد obsarvable دارید حالا می خواید این دو تا رو ترکیب کنید و از ترکیب آن ها یک obsarvable بسازید:
در مثال بالا obsarvable A و obsarvable B ترکیب شدند و یک obsarvable جدید از ترکیب این دو به وجود آوردند کد:
DistinctUntilChanged یا Debounce
این دو متد یکی از پر کاربردترین متد ها هنگام سرچ هستند مثلا کاربر در textfield یه کلمرو سرچ میخواد بکنه شما هم با نوشتن هر کاراکتر از کاربر یه ریکوست به سرور می زنید خب اگر کاربر سریع تایپ کنه شما کلی ریکوست بیهوده به سرور دادید و در صورتی که آخرین جایی که کاربر یه ایست کوتاه کرده باید ریکوست به سرور بدید برای حل این مشکل شما میتونید از تابع Debounce استفاده کنید بدین صورت که :
در مثال بالا تا وقتی که متن usernameOutlet هر زیر ۰.۳ ثانیه تغییر کنه به subscriber نمیرسه و طبیعتا تابع سرچ صدا زده نمیشه .
تابع DistinctUntilChanged حساس به تغییرات هست بدین معنی که اگر دو سیگنال یکسان برسه تا وقتی که سیگنال تغییر نکنه به subscriber دیگه فرستاده نمیشه.
دنیای Rx خیلی بزرگ تر از چیزی که میتونید فکرش رو بکنید هست و من فقط چندین مفاهیم اولیه که به نظرم مورد نیاز پارت بعدی مقاله که یک پروژه ی واقعی رو با RxSwift پیاده می کنیم، بود رو گفتم.
کتاب RxSwift از raywenderlich خیلی خوب RxSwift رو از ۰ توضیح داده که خیلی پیشنهاد می کنم خوندنشو.
خیلی طبیعیه که با یک مقاله از RxSwift شاید زیاد متوجه نشده باشید چون از مفاهیم پیشرفته سویفت هست و شاید روز ها باید مقاله های مختلف بخونید تا بفهمیدش. در این لینک چندین مقاله خوب رو از قسمت RxSwift اش میتونید ببینید.
در آخر بگم که تصمیم ترجمه کردن و نکردن بعضی از اصطلاحات کار ساده ای نبود? و خودتون ببخشید اگر اضافی یا کم ترجمه شده.
امیدوارم با قسمت بعدی مقاله که ًRx رو در پروژه ی واقعی با MVVM پیاده سازی میکنیم مفاهیم RxSwift رو بهتر متوجه شید چون با مثال های واقعی درک کردنش بسیار راحت تر خواهد بود.
ایمیل من mohammad_z74@icloud.com هست اگر سوالی داشتید در خدمتم.
مطلبی دیگر از این انتشارات
کار با متغیرها در iOS از چند رشته (Multi-thread) - بخش ۱
مطلبی دیگر از این انتشارات
نوشتن اپ iOS/mac را از کجا شروع کنم؟ و چگونه مدل خوب بنویسم؟
مطلبی دیگر از این انتشارات
کار با متغیرها در iOS از چند رشته (Multi-thread) - بخش ۲