shafi.ee
shafi.ee
خواندن ۸ دقیقه·۶ سال پیش

UITableView + RxSwift قسمت اول

RxSwift
RxSwift

امروز می‌خوام کمی از اطلاعاتی رو که این چند وقته یاد گرفتم رو باهاتون به اشتراک بذارم، اونم در مورد موضوع جذابی به اسم RxSwift! خوب همون طوری که می‌دونید RxSwift یک فریم ورک Reactive Programming هستش که این امکان رو به ما میده، تا بتونیم از مزیت های این سبک برنامه نویسی توی Swift بهرمند بشیم، اگر نمی‌دونید اینهایی که گفتم چی هستن و می‌خوایید اطلاعات بیشتر و بهتری رو به زبون فارسی ازشون داشته باشید می‌تونید مقاله MVVM + RxSwift on iOS part 1 از دوست خوبم محمد زکی زاده رو مطالعه کنید.

چیزی که توی ذهنم هست اینه که، توی مقالات جداگانه ای باهم المان های مختلف رو به روش Rx پیاده سازی کنیم. که توی این مقاله نحوه پیاده سازی یک TableView ساده رو به روش Rx باهم انجام می‌دیم، البته با معماری MVVM. برای همین سعی می‌کنم تا کمتر موارد پیرامونیش رو توضیح بدم و برای توضیحاتشون به مقالات جداگانه ای که هم بهتر توضیح دادن و هم مفصل تر لینک بدم چون قطعا با یک مقاله نمی‌شه همه موارد رو عنوان کرد و این تقسیم بندی می‌تونه کالکشن بهتری رو به وجود بیاره و هم اینکه وقتی یه مقاله خوبی هست بهتره به اشتراک گذاشته بشه تا دوباره نوشته بشه!

  • روش سنتی استفاده از TableView

خوب برای پیاده سازی کردن یه TableView به روش عادی همون طوری که می‌دونید باید اول خود TableView رو یا به صورت دستی و یا از طریق StoryBoard پیاده کنیم و بعدش برای تنظیماتش بیام از توابع Delegate اش استفاده کنیم Delegate هایی مثل:

UITableView Delegates
UITableView Delegates

که می‌تونه حوصله سر بر باشه و ViewController ها مون رو مدام شلوغ کنه مخصوصا توی پروژه های بزرگ، تازه اگر خوب بتونیم هندل کنیم توابع رو و تر و تمیز در بیاریم و از روش هایی که مثل این مقاله گفته استفاده کنیم، اگر این مقاله رو تا الان نخوندید حتما و حتما توصیه می‌کنم که مطالعه اش کنید Complex Table Views in iOS؛ خوب حالا توی این مورد RxSwift چه کمکی می‌تونه به ما بکنه و چیا رو ساده تر و تمیزتر کنه توی کدهامون؟

  • پیاده سازی TableView به روش RxSwift

در این مقاله سراغ tableView هایی میریم که یک سکشن دارن و چند سکشنی نیستن، چون هم ساده تر هستش و هم برای نقطه شروع بهتر هستن و توی مقالات بعدی میریم سراغ TableView های چند سکشنی که باید از مفهومی به نام RxDataSource استفاده بشه برای پیاده سازیشون. البته TableView های تک سکشنی اما چند نوع cell ای هم هستش که اون رو هم توی مقالات بعدی بهش میرسیم.

قدم اول اضافه کردن لایبرری RxSwift هستش، که راحت ترین راهش می‌تونه اضافه کردن اون از طریق CocoaPods باشه، که با اضافه کردن دو خط زیر در فایل pod پروژه‌تون این کار رو انجام بدید:

  • pod 'RxSwift', '~> 4.0'
  • pod 'RxCocoa', '~> 4.0'

حالا تقسیم بندی فایل های پروژه تون باید به شکل زیر باشه که در ادامه هر کدوم از فایل ها و عملکردشون
رو توضیح میدم.

ساختار فایل‌های پروژه
ساختار فایل‌های پروژه
  • توضیحات لایه View:

اول از همه باید دو تا لایبرری RxSwift و RxCocoa رو به پروژه import کنیم تا بتونیم ازشون استفاده کنیم، اما خوب این دوتا چی هستن و چه فرقی باهم دارن؟ شاید یکی از مختصرترین و بهترین توضیحاتی رو که برای تعریف این دو لایبرری میشه عنوان کردش این توضیح هستش:

RxSwift is a framework for interacting with the Swift programming language, while RxCocoa is a framework that helps make Cocoa APIs used in iOS and OS X easier to use with reactive techniques.

این بخشی از توضیحاتی هستش که در این مقاله از سایت raywenderlich آورده شده، در واقع RxSwift اومده خود ویژگی های زبون برنامه نویسی swift رو reactive کرده و RxCocoa اومده المان های گرافیکی و اینترفیس هایی رو که باهاش کار می‌کنیم رو روی این ساختار اورده. (Cocoa APIs)

حالا ما نیاز به ۳ تا متغییر زیر داریم:

همون طوری که معلوم هستش متغییر viewModel یه instante ای از لایه ViewModel مون هستش که بتونیم از توابعی که اونجا تعریف کریم استفاده کنیم، در مورد DisposeBag باید کمی بیشتر توضیح بدیم؛ در واقع برای مدیریت حافظه و جلوگیری از memory leak ما باید متغییرهایی رو که ساختیم در زمان مناسبی از حافظه آزاد کنیم تا خللی در اجرای برنامه به وجود نیاد و حافظه به درستی مدیریت بشه، و این موضوع توی reactive programming خیلی مهم هستش به خاطر نوع متغییرهایی که تعریف می‌کنیم. برای همین ما یه سبدی درست می‌کنیم و تمام متغییرهای rx ای مون رو توی اون قرار میدیم تا با dismiss شدن اون کنترلرمون تمامی اون متغییرها هم از حافظه آزاد بشن برای همین اومدیم یه متغییری به نام disposeBag رو از نوع DisposeBag تعریف کردیم. و در اخر هم rows رو تعریف کردیم که یک متغییر از نوع ارایه ای از ViewControllerModel هستش، اگر نوع های Subject ها و Variable ها رو توی RxSwift باشون اشنا نیستید و یا تفاوت هاشون رو نمی‌دونید پیشنهاد می‌کنیم این مقاله کوتاه رو بخونید، RxSwift — Subjects، اما مختصرش این هستش که این آرایه اطلاعاتی رو که قرار tableView ما با اون‌ها پر بشه رو تو خودش نگه می‌داره.

حالا نوبت binding هستش یعنی در واقع ما باید بیایم اطلاعاتی رو که قرار هستش tableView مون نشون بده رو متصل کنیم بهش که به این کار اصلاحا bind کردن میگن. اول مثل همیشه cell ای رو که tableView مون ازش استفاده می‌کنه رو register میکنیم:

Register Cell
Register Cell

حالا کافی هستش که بگیم متغییره rows ما وصل بشه به tableView مون و اونجا هندل کنیم بقیه کارها رو، برای این کار از این کد استفاده میکنیم:

Bind Datasource array to tableView
Bind Datasource array to tableView

اول میگیم rows باید bind بشه به tableView و بعدش به لطفا rxCocoa میام tableView مون رو هم به cell ای که براش تعریف کردیم متصل می‌کنیم و بعدش وصلش می‌کنیم به disposeBag ای که داریم. و داخل closure ای هم که داریم به cell و row و دیتایی که اون سطر براش اماده شده دسترسی داریم که اومدیم label های cell مون رو باهاش مقدار دهی کردیم. حالا نتیجه اش چی میشه؟ این میشه که هر وقت که متغییر rows ما مقدار دهی بشه چون rows هم به tableView بایند شده میادش به صورت خودکار tableView ما رو هم پر میکنه بدون اینکه ما نیاز داشته باشیم کاری کرده باشیم! جالبه نه؟

اما خوب متغییر rows ما کجا مقدار دهی شده؟ توی این قسمت از کد:

Bind viewModel datasource variable to viewController variable
Bind viewModel datasource variable to viewController variable

در واقع ما اینجا اومدیم چی کار کردیم، اومدیم گفتیم متغییر tableRowsItem که داخل لایه viewModel ما قرار داره رو bind کردیم به متغییر rows و بعدش قرارش دادیم داخل disposeBag مون تا وقتی که صفحه بسته شد این متغییر هم از حافظه آزاد بشه. این طوری در واقع ما میایم:

۱- متغییر tableRowsItem رو داخل لایه viewModel مقدار دهی می‌کنیم

۲- متغییر tableRowsItem چون به rows بایند شده rows رو هم مقدار دهی می‌کنه

۳- متغییر rows هم چون به tableView مون بایند شده میادش و tableView رو پر و مقدار دهی می‌کنه!

فکنم حالا متوجه شدید که چرا میگن reactive programming روی data streams متمرکز هستش :) .

توجه: فقط به این موضوع هم توجه داشته باشید چون توی این bindig ما با ui سر و کار داریم و ui امون به روزرسانی میشه اومدیم بایند کردن tableRowsItem به rows رو اوردیم روی thread اصلی برنامه، چون به صورت پیش فرض این کارها در پس زمینه انجام میشن نه thread اصلی اپلیکیشن، که این کار توسط این تیکه کد انجام شده:

.observeOn(MainScheduler.instance)

خوب حالا برای اینکه برنامه مون کار کنه کافی هستش که لایه ViewModel مون بیادش و DataSource مون رو که tableRowsItem هستش رو پر کنه. برای اینکه کارمون با این لایه تموم بشه کافی هستش که در تابع viewDidLoad کنترلرمون تابع setDataSoruce رو که در viewModel مون هستش رو صدا کنیم.

  • توضیحات لایه ViewModel:

توی این لایه چون با UI کاری نداریم پس فقط RxSwift رو import می‌کنیم و نیازی به RxCocoa نداریم. حالا باید متغییر tableRowsItem رو که قبلا گفته بودیم رو تعریف کنیم:

var tableRowsItem = PublishSubject<[ViewControllerModel]>()

و بعد از اون هم تابع setDataSource مون که کارش ساخت اطلاعات نمایش داده شده توی tableView هستش:

Generate mock data to set on tableView
Generate mock data to set on tableView

خوب بعد از توضیحات تا اینجای مقاله فکنم بدنه این تابع کاملا واضح باشه و تنها موردش onNext ای باشه که برای متغییر tableRowsItem استفاده شده. برای هر Subject بنا بر نوع اش معمولا ۴ تا state وجود داره: onNext, onError, onCompleted و onDisposed که ما برای مقدار دهی به یه subject از onNext اون استفاده می کنیم.

  • توضیحات لایه Model :

این لایه هم فقط مدل ای رو که ازش استفاده می‌کنیم رو تعریف می‌کنیم که یک Struct ساده هستش:

ViewModel structure
ViewModel structure

حالا کافی هستش که برنامه رو اجرا کنید تا ببینید همه چیز به درستی کار می‌کنه و نیازی به پیاده سازی هیچ کدوم از delegate های tableView نیست!

چند نکته:

  • برای بعضی موارد مثل ست کردن ارتفاع cell ها باید از همون توابع قدیمی delegate استفاده بشه
  • برای ساختن header در tableView باز باید از توابع قدیمی delegate استفاده بشه اما فقط در tableView ولی در CollectionView می‌تونید header و footer کالکشن ویو رو به روش rx ای ست کنید. که این نوع تعریف کردن header نیاز به استفاده از rxDataSource هستش که در مقاله بعد در موردش توضیح می‌دم، البته اگر بخواید یه customView بذارید برای header تیبل تون ( بحث در مورد این مشکل در گیت هاب RxSwift)

به عنوان مثال برای ست کردن ارتفاع cell های tableView می‌تونیم دلیگیت tableView مون رو self ست کنیم و تابع heightForRowAt تیبل رو پیاده سازی کنیم:

خوب سعی کردم که خیلی کوتاه و ساده و کامل روال کامل پیاده سازی یه tableView رو به روش RxSwift توضیح بدم و اون رو با روش سنتی مقایسه ای کرده باشم.

خوشحال میشیم نظراتتون رو بدونم تا مقاله بعدی رو که در مورد tableView پیچیده تری هستش رو بهتر بنویسم.

راستی سورس کد این مقاله رو هم می‌تونید از این لینک github دانلود کنید.

ممنون از همراهی تون.

امین شفیعی

swiftreactive programmingreactive
شاید از این پست‌ها خوشتان بیاید