MVVM tətbiqindən ümidsiz olmayın

Təsəvvür edin ki, cəmi 2 gün ərzində yeni funksiyalar əlavə etdiyiniz kiçik bir layihəniz var. Sonra layihəniz böyüyəcəkdir. Çatdırılma tarixi 2 gündən 1 həftəyə və sonra 2 həftəyə qədər idarəolunmaz olur. Sizi dəli edir! Şikayət etməyə davam edirlər: Yaxşı bir məhsul bu qədər çətin olmamalıdır! Məhz bu yaşadıqlarım və həqiqətən mənim üçün pis bir vaxt idi. Bir neçə il bu sahədə işlədikdən və bir çox böyük mühəndis ilə işlədikdən sonra məhsulun dizaynının həqiqətən mürəkkəb kod yaratmadığını başa düşdüm. Bunu bu qədər mürəkkəb edən mənəm.

Layihələrimizin performansını əhəmiyyətli dərəcədə təsir edən spagetti kodu yazmaq təcrübəmiz ola bilər. Sual budur ki, bunu necə düzəldə bilərik? Yaxşı bir memarlıq nümunəsi kömək edə bilər. Bu yazıda yaxşı bir memarlıqdan biri haqqında danışacağıq: Model-View-ViewModel (MVVM). MVVM, istifadəçi interfeysinin inkişafını iş məntiqinin inkişafından ayırmağa yönəlmiş bir uzanan iOS memarlığıdır.

"Yaxşı memarlıq" termini çox mücərrəd görünə bilər. Harada başlayacağını bilmək də çətindir. Budur bir məqam: memarlığı müəyyənləşdirmək yerinə, kodun sınanlığını necə inkişaf etdirəcəyimizi düşünə bilərik. MVC, MVP, MVVM, VIPER kimi bir çox proqram memarlığı var. Bu memarlığın hamısını mənimsəyə bilməyəcəyimiz aydındır. Ancaq yenə də sadə bir qaydaya riayət edə bilərik: hansı memarlığı seçməyimizdən asılı olmayaraq, əsas məqsəd sınağı sadələşdirməkdir. Bu yanaşma ilə kod yazmadan əvvəl düşünürük. Məsuliyyətin intuitiv bölünməsini qiymətləndiririk. Bundan əlavə, memarlıq dizaynı bu düşüncə tərzi ilə aydın və ağlabatan görünür, beləliklə, mənasız detallarla özümüzü məhdudlaşdırmamalıyıq

TL; DR

Bu yazıda öyrənəcəksiniz:

  • Apple MVC üzərində MVVM seçdiyimiz səbəb
  • Daha aydın bir memarlıq dizaynı üçün MVVM-i necə düzəldəcəksiniz
  • MVVM əsaslanan sadə bir real tətbiqetməni necə yazıram?

Aşağıdakıları görməyəcəksiniz:

  • MVVM, VIPER, Təmiz və s. Arasındakı müqayisə
  • Bütün problemləri həll edən bir gümüş top

Bütün bu arxitekturaların üstünlükləri və mənfi cəhətləri var, lakin hamısı kodu asan və aydınlaşdırmaq üçün hazırlanmışdır. Buna görə MVC yerinə MVVM seçdiyimizi və MVC-dən MVVM-ə necə keçdiyimizi düşündük. MVVM'nin çatışmazlıqları ilə maraqlanırsınızsa, bu məqalənin sonunda müzakirə oxuyun.

Beləliklə, işə başlayaq!

Apple MVC

MVC (Model View Controller), Apple tərəfindən tövsiyə olunan memarlıq nümunəsidir. Tərifini burada tapa bilərsiniz. MVC-də cisimlər arasındakı qarşılıqlı əlaqə aşağıdakı şəkildə göstərilmişdir:

İOS / MacOS inkişafında ViewController tətbiqi səbəbindən ümumiyyətlə:

ViewController görünüşü ehtiva edir və modelə sahibdir. Problem ondadır ki, həm nəzarətçi kodu, həm də ViewController-də görüntü kodunu yazırıq. Bu ViewController-i çox mürəkkəb edir. Buna görə onu Massive View Controller adlandırdıq. ViewController üçün bir test yazarkən, görünüşü və onun həyat dövrünü təqlid etməlisiniz. Baxışları lağa qoymaq çətindir. Yalnız nəzarət məntiqini sınamaq istəsək, həqiqətən də mənzərəni lağa qoymaq istəmirik. Bütün bunlar yazı testlərini bu qədər mürəkkəb edir.

Beləliklə MVVM qənaət etmək üçün buradadır.

MVVM - Model - View - ViewModel

MVVM John Gossman tərəfindən 2005-ci ildə təklif edilmişdir. MVVM-in əsas məqsədi məlumatların vəziyyətini ViewModel-ə keçirməkdir. MVVM-də məlumat axını aşağıdakı kimi təqdim edilə bilər:

Tərifə görə görünüş yalnız vizual elementlərdən ibarətdir. Görünüşdə yalnız layout, animasiya, UI komponentlərinin başlanması və s. Kimi işlər görülür. Görünüş və model arasında ViewModel arasında xüsusi bir təbəqə var. ViewModel, görünüşün kanonik bir ifadəsidir. Yəni ViewModel, hər biri görünüşdə bir UI komponentini təmsil edən bir sıra interfeys təklif edir. UI komponentlərini ViewModel interfeyslərinə bağlamaq üçün "bağlamaq" adlı bir texnikadan istifadə edirik. MVVM-də görünüşü birbaşa toxunmuruq, ancaq ViewModel-də iş məntiqi ilə məşğul oluruq və buna görə də görünüş müvafiq olaraq dəyişir. View'un yerinə ViewModel-də Tarixi String-ə çevirmək kimi təqdimat şeylərini yazırıq. Buna görə, görünüşün həyata keçirilməsini bilmədən təqdimat məntiqi üçün daha sadə bir test yazmaq mümkün olur.

Geri dönək yuxarıdakı görüntüyə daha yaxından baxaq. Ümumiyyətlə, ViewModel görünüşdən istifadəçi qarşılıqlı əlaqəsini alır, məlumatları modeldən götürür və sonra məlumatları displeyə hazır xüsusiyyətlər toplusuna çevirir. ViewModeldə dəyişiklik müşahidə edildikdən sonra görünüş avtomatik olaraq yenilənir. Bu MVVM-in bütün hekayəsidir.

Bu UIView / UIViewController xüsusilə iOS inkişafında MVVM üçün görünüşdür.

  1. UI komponentlərini başlat / təqdim et.
  2. UI komponentlərini ViewModel ilə bağlayın.

Digər tərəfdən, ViewModeldə edirik:

  1. Səhifələrin idarə edilməsi, səhvlərin idarə edilməsi və s. Kimi idarəetmə məntiqlərini yazın.
  2. Təqdimat məntiqini yazın, görüntü üçün interfeys təmin edin.

ViewModel-in bir qədər mürəkkəb olduğunu görə bilərsiniz. Bu yazının sonunda MVVM-in pis hissəsini müzakirə edəcəyik. Orta ölçülü bir layihə üçün MVVM hələ də bir fili digərinin ardınca yemək yaxşı seçimdir!

Növbəti hissələrdə MVC naxışı ilə sadə bir tətbiq yazacağıq və sonra tətbiqin MVVM modelinə necə uyğunlaşdırılacağını izah edəcəyik. Nümunə layihəsini GitHub-da bölmə testləri ilə tapa bilərsiniz:

Başlayaq!

Sadə qalereya tətbiqi - MVC

Sadə bir tətbiq yazacağıq:

  1. Tətbiq 500px API-dən populyar şəkilləri çıxarır və şəkilləri UITableView-da sadalayır.
  2. Cədvəl görünüşündəki hər bir hüceyrə başlığı, təsviri və bir fotoşəklin yaranma tarixini göstərir.
  3. İstifadəçilər satış üçün qeyd olunmayan fotoşəkilləri vurmamalıdır.

Bu tətbiqetmədə vahid bir şəkli təmsil edən bir şəkil var. Budur Şəkil sinif istifadəçi interfeysi:

Tətbiqin ilkin görünüş nəzarətçisi PhotoListViewController adlı bir cədvəl görünüşü olan UIViewControllerdir. PhotoListViewController-də APIService vasitəsilə şəkil obyektlərinə zəng edirik və şəkillər alındıqdan sonra masa görünüşünü yenidən yükləyirik:

PhotoListViewController də masa görünüşünün məlumat mənbəyidir:

Fəaliyyət cədvəli görüntüsündə (_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCelldə müvafiq foto obyektini seçirik və hüceyrəyə başlıq, təsvir və tarix təyin edirik. Photo.date bir tarix obyekti olduğundan, onu DateFormatter istifadə edərək bir simə çevirməliyik.

Aşağıdakı kod masa görünüşü nümayəndəsinin icrasıdır:

TableView funksiyasında müvafiq şəkil obyektini seçirik (_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath?, For_sale-in mülkiyyətini yoxlayın. Əgər belədirsə, seçilmişIndexPath'ı köçürmə üçün qeyd edin. Yoxdursa, səhv mesajı göstərin və çöküntünün qarşısını almaq üçün sıfırı qaytarın.

PhotoListViewController'in mənbə kodunu burada tapa bilərsiniz, "MVC" etiketinə diqqət yetirin.

Yuxarıdakı kodda səhv nə var? PhotoListViewController-də təqdimat məntiqi, məsələn, tapa bilərsiniz Məsələn, tarixin simə çevrilməsi və fəaliyyət göstərişinin başlanğıc / dayandırılması. Cədvəl görüntüsünü göstərmək və gizlətmək kimi görüntü kodlarımız da var. Görüntü nəzarətçisində, API xidmətində başqa bir asılılıq da var. PhotoListViewController üçün testlər yazmaq istəyirsinizsə, çox mürəkkəb olduğundan yapışdığınıza rast gələcəksiniz. Bütün PhotoListViewController-i sınamaq üçün APIService-i lağa qoymalı, masa görünüşünə lağ etməli və hücrəni lağa qoymalıyıq. Piy!

Testləri yazmağı asanlaşdırmaq istədiyimizi unutmayın? MVVM yanaşmasına cəhd edək!

MVVM cəhd edin

Problemi həll etmək üçün ilk prioritetimiz görünüş nəzarət cihazını təmizləmək və görünüş nəzarət cihazını iki hissəyə bölməkdir: görünüş və görünüş modeli. Dəqiq olmaq üçün:

  1. Bağlamaq üçün bir sıra interfeys dizayn edin.
  2. Təqdimat məntiqini və idarəetmə məntiqini ViewModel-ə köçürün.

Əvvəlcə görünüşdə istifadəçi interfeysinin komponentlərinə baxaq:

  1. Fəaliyyət göstəricisi (yükləmə / çıxma)
  2. tableView (göstərmək / gizlətmək)
  3. Hüceyrələr (başlıq, təsvir, yaradılış tarixi)

UI komponentlərini bir sıra kanonik nümayəndəliklərə mücərrəd edə biləcəyimizi burada təqdim edirik:

Hər UI komponenti ViewModel-də müvafiq bir xüsusiyyətə malikdir. Deyə bilərik ki, görünüşdə gördüklərimiz ViewModel-də gördüklərimizlə eyni olmalıdır.

Bəs bağlamanı necə edirik?

Bitirdikdən sonra bağlamanı həyata keçirin

Swift-də "bağlamağa" nail olmaq üçün bir neçə yol var:

  1. KVO (Açar Dəyər Müşahidəsi) naxışından istifadə edin.
  2. RxSwift və ReactiveCocoa kimi Funksional Reaktiv Proqramlaşdırma (FRP) üçün üçüncü kitabxanalardan istifadə edin.
  3. Özünüz olun.

KVO naxışını istifadə etmək pis bir fikir deyil, ancaq geniş bir heyət metodu ilə nəticələnə bilər və görünüşdə bir yük ola biləcək addObserver / removeObserver əmri ilə diqqətli olmalıyıq. Bağlamaq üçün ideal yol, FRP-də bağlayıcı həll yolundan istifadə etməkdir. Funksional reaktiv proqramlaşdırma ilə tanışsınızsa, edin! Yoxdursa, yalnız bir bağlama üçün FRP tövsiyə etməzdim, çünki balyozla qozun çırpılması qarışıqdır. Bu parlaq məqalə özünüzü bağlamaq üçün dekorativ naxışdan bəhs edir. Bu yazıda hər şeyi asanlaşdıracağıq. Hər şeyi bir qucaqla bağlayırıq. ViewModel-də bağlama üçün bir interfeys / əmlak praktik olaraq aşağıdakı kimi görünür:

Digər tərəfdən, dəyər yeniləmələri üçün geri çağırış müqaviləsi olaraq bir müqaviləni propChanged etdik.

Hər dəfə prop mülkiyyəti yeniləndikdə, propChanged adlanır. Beləliklə, ViewModel dəyişikliyinə görə görünüşü yeniləyə bilərik. Bu asandır, elə deyilmi?

ViewModel-də bağlama üçün interfeyslər

İndi ViewModelimizi, PhotoListViewModel dizayn etməyə başlayaq. Aşağıdakı üç UI komponentinə əsaslanır:

  1. Cədvəl görünüşü
  2. Hüceyrələr
  3. Fəaliyyət göstəricisi

PhotoListViewModel-də bağlamaq üçün interfeys / xüsusiyyətlər yaradırıq:

Hər PhotoListCellViewModel obyekti cədvəl görünüşündə bir hücrənin kanonik bir nümayəndəliyini meydana gətirir. Bir UITableView hücrəsini göstərmək üçün məlumat interfeysi təmin edir. Bütün PhotoListCellViewModel obyektlərini bir sıra cellViewModels-ə daxil edirik. Hüceyrələrin sayı bu serialdakı elementlərin sayına tam uyğundur. CellViewModels massivinin masa görünüşünü təmsil etdiyini söyləyə bilərik. ViewModel-də cellViewModels-i yenilədikdən sonra Bağlama reloadTableViewClosure adlanır və görünüş müvafiq olaraq yenilənir.

Tək PhotoListCellViewModel bu kimi görünür:

Gördüyünüz kimi PhotoListCellViewModel xüsusiyyətləri, görünüşdə UI komponentlərinə bağlanmaq üçün bir interfeys təmin edir.

Görünüşü ViewModel ilə bağlayın

Bağlamaq üçün interfeyslər üçün indi görünüş hissəsini cəmləşdiririk. Əvvəlcə PhotoListViewController-də ViewDidLoad-da geri çağırış bağlanışlarını işə salırıq:

Sonra məlumat mənbəyini yenidən tərtib edəcəyik. MVC nümunəsində tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell-də təqdimat məntiqi qurduq. İndi təqdimat məntiqini ViewModel-ə köçürməliyik. Yenidən işlənmiş məlumat mənbəyi belə görünür:

Məlumat axını indi olur:

  1. PhotoListViewModel məlumat almağa başlayır.
  2. Məlumat alındıqdan sonra PhotoListCellViewModel obyektləri yaradırıq və cellViewModels yeniləyirik.
  3. PhotoListViewController yeniləmə barədə məlumatlandırılır və yenilənmiş cellViewModels ilə hüceyrələri dizayn edir.

Aşağıdakı kimi təqdim edilə bilər:

İstifadəçinin qarşılıqlı əlaqəsi ilə məşğul olmaq

İstifadəçinin qarşılıqlı əlaqəsinə davam edək. PhotoListViewModel-də bir funksiya yaradırıq:

İstifadəçi bir hücrəyə vurduqda, PhotoListViewController bu funksiya ilə PhotoListViewModel-i xəbərdar edir. PhotoListViewController-də nümayəndə üsulunu yenidən necə təyin edə biləcəyimizi göstərin.

Bu funksiya tableView (_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath deməkdir? istifadəçinin qarşılıqlı əlaqəsi səbəbindən çağırıldı, hərəkət PhotoListViewModel-ə ötürüldü. Nümayəndəlik funksiyası, ayrılmağın və ya edilməməyin qərarını vermək üçün PhotoListViewModel tərəfindən təmin edilən isAllowSegue mülkiyyətindən istifadə edir. Vəziyyəti uğurla görünüşdən sildik.

PhotoListViewModel-in tətbiqi

Bu uzun bir səyahətdir, elə deyilmi? Davam edin, MVVM nüvəsinə toxunuruq! PhotoListViewModel'də cədvəl görünüşünü göstərən cellViewModels adlı bir sıra var.

Məlumatları necə alırıq və serial hazırlayırıq? ViewModel'i işə saldıqda iki seçimimiz var:

1. Asılılığı enjekte edin: APIService 2. APIService ilə məlumat alın

Yuxarıdakı kod parçasında, APIService-dən məlumat almağa başlamazdan əvvəl isLoading mülkünü gerçək olaraq təyin edirik. Əvvəllər bağladığımız bağlama sayəsində qəbulu "yükləyin" həqiqət, görünüşün aktiv elanın açılması deməkdir. APIService geri çağırış bağlandıqda, geri alınan foto modelləri emal edirik və isLoading-i yalan vəziyyətinə təyin edirik. UI komponentinə birbaşa toxunmamalıyıq, ancaq ViewModel'in bu xüsusiyyətlərini dəyişdirdiyimiz zaman UI komponentlərinin gözlədiyimiz kimi işlədiyi aydındır.

Budur prosesin həyata keçirilməsiFetchedPhoto (fotolar: [foto]):

Şəkil modellərini PhotoListCellViewModel bir sıra qucaqlayaraq sadə bir iş görür. CellViewModels xassəsi yeniləndikdə cədvəl görünüşü müvafiq olaraq görünüşdə yenidən yüklənir.

Bəli, MVVM etdik

Nümunə tətbiqini mənim GitHub-da tapa bilərsiniz:

MVC versiyasını (Tag: MVC), sonra MVVM versiyasını (son öhdəliyi) sınamaq istəyə bilərsiniz.

Recap

Bu yazıda sadə bir tətbiqimizi MVC naxışından MVVM naxışına çevirdik. Və biz:

  • Bağlanması ilə bağlayan bir mövzu etdi.
  • Bütün nəzarət məntiqləri görünüşdən çıxarıldı.
  • Testable ViewModel yaradır.

Müzakirə

Artıq qeyd edildiyi kimi, memarlıq bütün üstünlüklərə və çatışmazlıqlara malikdir. Məqaləmi oxuduqdan sonra MVVM-in mənfi cəhətləri haqqında bəzi fikirləriniz olmalıdır. MVVM-in pis hissələri haqqında yaxşı məqalələr var, məsələn:

MVVM çox yaxşı deyil - Soroush Hanlou iOS altında MVVM ilə bağlı problemlər - Daniel Hall

MVVM-də ən böyük narahatlığım ViewModel-in çox iş görməsidir. Bu yazıda qeyd edildiyi kimi ViewModel-də nəzarətçi və aparıcılarımız var. Bundan əlavə, inşaatçı və yönləndirici olan iki rol MVVM nümunəsinə daxil edilmir. İnşaatçı və yönləndiricini ViewController-ə daxil etdik. Daha aydın bir həll ilə maraqlanırsınızsa, MVVM + FlowController (FlowControllers ilə iOS Arxitekturanızı artırın) və iki tanınmış memar, VIPER və Əmi Bobadan təmizləyin.

Kiçik başlayın

Həmişə daha yaxşı bir həll var. Peşəkar mühəndis olaraq həmişə kod keyfiyyətini necə inkişaf etdirməyi öyrənirik. Mənim kimi tərtibatçılar bu qədər memarlığı məyus etdilər və vahid testlərini necə yazmağı bilmirlər. Beləliklə MVVM səyahətə başlamaq üçün yaxşı bir yerdir. Bu asandır və sınaq qabiliyyəti hələ yaxşıdır. Soroush Hanlou-nun başqa bir məqaləsində, Massive View Controller-lərini məhv etmək üçün istifadə edə biləcəyiniz 8 nümunə, MVVM tərəfindən qəbul edilən bir çox yaxşı nümunə var. Nəhəng bir arxitekturaya mane olmasaq, kiçik, lakin güclü MVVM nümunəsi ilə bir test yazsaydıq necə olardı?

"İrəliləməyin sirri başlanğıcdadır." - Mark Tven

Növbəti məqalədə sadə qalereya tətbiqetməmiz üçün yazı vahidləri haqqında danışmağa davam edəcəyəm. Davamlı olun

Hər hansı bir sualınız varsa, hər zaman rəy yaza bilərsiniz. Hər cür müzakirə də xoş gəlir! Diqqətinizə görə çox sağ olun.

İstinadlar

WPF tətbiqetmələri üçün Model / View / ViewModel nümunəsinə giriş - John Gossman MVVM-ə giriş - objc iOS memarlıq nümunəsi - Bohdan Orlov sürətli görünüşlü Model-View-ViewModel - SwiftyJimmy Swift Tutorial: MVVM Dizayn nümunəsinə giriş - DINO BARTOŠAK MVVM - MVVM - Brent Edwards bağlayıcıları, generikləri, Swift və MVVM ilə sınanabilən təqdimat səviyyəsini yazmaq - Srdan Rasic