I want to understand basic, abstract and correct architectural approach for networking applications in iOS
: Orada hiçbir uygulama inşa edilmesi için, "en iyi" ya da "en doğru" yaklaşımı. Bu ise çok yaratıcı bir iş. Her zaman projenizde veya ekibinizdeki diğer geliştiriciler için çalışmaya başlayan herhangi bir geliştirici için açık olacak en basit ve genişletilebilir mimariyi seçmelisiniz, ancak katılıyorum, "iyi" ve "kötü" olabileceğini kabul ediyorum. " mimari.
Dedin ki:, collect the most interesting approaches from experienced iOS developers
Yaklaşımımın en ilginç ya da doğru olduğunu düşünmüyorum, ama bunu birkaç projede kullandım ve memnunum. Yukarıda bahsettiklerinizin melez bir yaklaşımı ve aynı zamanda kendi araştırma çabalarımdaki gelişmelerle. Birkaç iyi bilinen desen ve deyimi birleştiren bina yaklaşımları sorunuyla ilgileniyorum. Fowler'in kurumsal modellerinin çoğunun mobil uygulamalara başarıyla uygulanabileceğini düşünüyorum . Bir iOS uygulama mimarisi oluşturmak için başvurabileceğimiz en ilginç olanların bir listesi ( bence ): Hizmet Katmanı , İş Birimi , Uzak Cephe , Veri Aktarım Nesnesi , Ağ Geçidi , Katman Üst Tipi , Özel Durum , Etki Alanı Modeli . Bir model katmanını her zaman doğru bir şekilde tasarlamalısınız ve her zaman kalıcılığı unutmamalısınız (uygulamanızın performansını önemli ölçüde artırabilir). Bunun için kullanabilirsiniz Core Data
. Ama olmamalı ki unutmak Core Data
bir ORM veya veritabanı, ama bunun iyi bir seçenek olarak sebat ile bir nesne grafiği yöneticisi değil. Yani, çoğu zaman Core Data
ihtiyaçlarınız için çok ağır olabilir ve Realm ve Couchbase Lite gibi yeni çözümlere bakabilir veya ham SQLite veya LevelDB'ye dayanan kendi hafif nesne haritalama / kalıcılık katmanınızı oluşturabilirsiniz.. Ayrıca .
Etki Alanına Dayalı Tasarım ve CQRS
İlk başta, sanırım, biz gerektiğini semirip denetleyicileri veya ağır, boğulmuş modelleri istemiyoruz, çünkü ağ için başka bir katman oluşturur. O fat model, skinny controller
şeylere inanmıyorum . Ama inanıyorum içinde skinny everything
hiçbir sınıf, hiç şişman olmalı, çünkü yaklaşımı. Tüm ağ iletişimi genellikle iş mantığı olarak soyutlanabilir, sonuç olarak koyabileceğimiz başka bir katmanımız olmalıdır. Servis Katmanı ihtiyacımız olan şey:
It encapsulates the application's business logic, controlling transactions
and coordinating responses in the implementation of its operations.
Bizim de MVC
alemine Service Layer
etki alanı modeli ve denetleyicileri arasında bir arabulucu gibi bir şeydir. MVCS adı verilen bu yaklaşımın Store
, aslında bizim katmanımız olduğu oldukça benzer bir varyasyonu vardır Service
. Store
model örnekleri ve ağ, önbellek vb işliyor . Hizmet katmanınıza tüm ağ ve iş mantığınızı yazmamanız gerektiğini belirtmek isterim . Bu da kötü bir tasarım olarak kabul edilebilir. Daha fazla bilgi için Anemic ve Rich alan modellerine bakın. Bazı servis yöntemleri ve iş mantığı modelde ele alınabileceğinden, "zengin" (davranışlı) bir model olacaktır.
Her zaman kapsamlı iki kütüphane kullanıyorum: AFNetworking 2.0 ve ReactiveCocoa . Ben ağ ve web hizmetleri ile etkileşim veya karmaşık UI mantığı içeren herhangi bir modern uygulama için olması gerektiğini düşünüyorum .
MİMARİ
İlk başta AFHTTPSessionManager'ınAPIClient
bir alt sınıfı olan genel bir sınıf oluşturuyorum . Bu, uygulamadaki tüm ağ iletişimlerinin bir görevidir: tüm hizmet sınıfları ona gerçek REST isteklerini verir. Belirli bir uygulamada ihtiyacım olan HTTP istemcisinin tüm özelleştirmelerini içerir: SSL sabitleme, hata işleme ve ayrıntılı hata nedenleri ve tüm ve bağlantı hatalarının açıklamaları ile basit nesneler oluşturma (bu durumda denetleyici için doğru mesajları gösterebilecektir) kullanıcı), istek ve yanıt serileştiricilerini, http üstbilgilerini ve ağla ilgili diğer öğeleri ayarlama. Sonra mantıksal Subservices veya daha doğrusu içine hiçbir API isteği, bölmek microservices : , , ,NSError
API
UserSerivces
CommonServices
SecurityServices
FriendsServices
ve böylece, uyguladıkları iş mantığına göre. Bu mikro hizmetlerin her biri ayrı bir sınıftır. Birlikte a oluştururlar Service Layer
. Bu sınıflar, her API isteği, işlem etki alanı modelleri için yöntemler içerir ve her zaman bir RACSignal
çözümlü yanıt modeliyle a döndürür veyaNSError
arayan kişiye .
Karmaşık model serileştirme mantığınız varsa - bunun için başka bir katman oluşturduğunuzu belirtmek isterim: Veri Eşleyici gibi ama daha genel bir şey örneğin JSON / XML -> Model eşleyici. Önbelleğiniz varsa: ayrı bir katman / hizmet olarak da oluşturun (iş mantığını önbellekleme ile karıştırmamalısınız). Neden? Çünkü doğru önbellek katmanı kendi gotcha'ları ile oldukça karmaşık olabilir. İnsanlar, profiktörlere dayalı projeksiyonlarla monoidal önbellekleme gibi geçerli, öngörülebilir önbellekleme elde etmek için karmaşık mantık uygularlar. Daha fazla anlamak için Carlos adlı bu güzel kütüphaneyi okuyabilirsiniz . Ayrıca, Temel Verilerin tüm önbellek sorunları için size gerçekten yardımcı olabileceğini ve daha az mantık yazmanıza izin vereceğini unutmayın. Ayrıca, Depo arasında bir mantığınız varsaNSManagedObjectContext
sunucu ile modeller varsa,verileri alan ve model üzerinde hareket eden iş mantığından varlık modeliyle eşleyen mantığı ayıran desen. Bu nedenle, Temel Veri tabanlı bir mimariye sahip olsanız bile Havuz desenini kullanmanızı öneririm. Depo kutu soyut şeyler gibi NSFetchRequest
, NSEntityDescription
, NSPredicate
ve bu yüzden gibi düz yöntemlere üzerine get
veyaput
.
Hizmet katmanındaki tüm bu eylemlerden sonra, arayan (görünüm denetleyicisi) yanıtla bazı karmaşık asenkron şeyler yapabilir: sinyal manipülasyonları, zincirleme, haritalama, vb. ReactiveCocoa
İlkellerin yardımıyla veya sadece abone olun ve sonuçları görünümde gösterin . Birlikte enjekte Dependency Injection tüm bu hizmet sınıflarında benim de APIClient
karşılık gelen belirli bir hizmet çağrısı çevirecek, GET
, POST
, PUT
, DELETE
, vb DİNLENME bitiş noktasına isteği. Bu durumda APIClient
tüm denetleyicilere dolaylı olarak aktarılırsa, bunu açık bir parametreyleAPIClient
hizmet sınıfları . Farklı özelleştirmeler kullanmak istiyorsanız bu mantıklı olabilir.APIClient
belirli hizmet sınıfları için, ancak bazı nedenlerden dolayı fazladan kopya istemiyorsanız veya her zaman belirli bir örneğini (özelleştirmeler olmadan) kullanacağınızdan eminseniz APIClient
- tek birton yapın, ancak YAPMAYIN, lütfen YAPMAYIN Hizmet sınıflarını tekil olarak yapmak.
Daha sonra DI ile birlikte her bir görünüm kontrolörü ihtiyaç duyduğu servis sınıfını enjekte eder, uygun servis yöntemlerini çağırır ve sonuçlarını UI mantığıyla oluşturur. Bağımlılık enjeksiyonu için BloodMagic veya daha güçlü bir çerçeve Typhoon kullanmayı seviyorum . Asla singleton, God APIManagerWhatever
sınıfı veya başka yanlış şeyler kullanmam. Çünkü sınıfınızı çağırırsanız WhateverManager
, amacını bilmediğinizden daha iyi olur ve bu kötü bir tasarım seçeneğidir . Singletons da bir anti-modeldir ve çoğu durumda (nadir olanlar hariç) yanlış bir çözümdür. Singleton, yalnızca aşağıdaki kriterlerin üçü de karşılandığında dikkate alınmalıdır:
- Tek bir örneğin mülkiyeti makul şekilde atanamaz;
- Tembel başlatma arzu edilir;
- Global erişim başka türlü sağlanmamaktadır.
Bizim durumumuzda, tek örneğin mülkiyeti bir sorun değildir ve ayrıca tanrı yöneticimizi hizmetlere böldükten sonra küresel erişime ihtiyacımız yoktur, çünkü şimdi sadece bir veya birkaç özel denetleyicinin belirli bir hizmete ihtiyacı vardır (örn. UserProfile
Denetleyici ihtiyaçları UserServices
vb.) .
SOLID'de her zaman S
ilkeye saygı göstermeli ve endişelerin ayrılmasını kullanmalıyız , bu nedenle tüm hizmet yöntemlerinizi ve ağ çağrılarınızı tek bir sınıfa koymayın, çünkü özellikle büyük bir kurumsal uygulama geliştiriyorsanız çılgınca. Bu yüzden bağımlılık enjeksiyonu ve hizmet yaklaşımını düşünmeliyiz. Bu yaklaşımı modern ve post-OO olarak görüyorum . Bu durumda uygulamamızı iki bölüme ayırdık: kontrol mantığı (kontrolörler ve olaylar) ve parametreler.
Bir tür parametre sıradan “veri” parametreleri olacaktır. Fonksiyonlar etrafında aktardığımız, manipüle ettiğimiz, değiştirdiğimiz, devam ettiğimiz vb. Bunlar varlıklar, kümeler, koleksiyonlar, vaka sınıflarıdır. Diğer tür "hizmet" parametreleri olacaktır. Bunlar, iş mantığını çevreleyen, harici sistemlerle iletişime izin veren, veri erişimi sağlayan sınıflardır.
Örnek olarak mimarimin genel bir iş akışı. Diyelim ki FriendsViewController
kullanıcı arkadaşlarının listesini gösteren bir arkadaşımız var ve arkadaşlarımızdan kaldırma seçeneğimiz var. Benim FriendsServices
sınıfta bir yöntem oluşturmak :
- (RACSignal *)removeFriend:(Friend * const)friend
burada Friend
bir model / etki alanı nesnesidir (veya User
benzer özniteliklere sahiplerse yalnızca bir nesne olabilir). Bu yöntem ayrıştırır Underhood Friend
için NSDictionary
JSON parametrelerinin friend_id
, name
, surname
, friend_request_id
vb. Her zaman Mantle kütüphanesini bu tür ortak plaka ve model katmanım için kullanıyorum (ileri ve geri ayrıştırma, JSON'da iç içe nesne hiyerarşilerini yönetme vb.). Ayrıştırma sonra çağıran APIClient
DELETE
gerçek bir REST isteği ve döner yapmak için kullanılan yöntem Response
içinde RACSignal
(arayana FriendsViewController
kullanıcı ya da her neyse uygun mesaj görüntülemek için bizim durumumuzda).
Uygulamamız çok büyükse, mantığımızı daha da net ayırmamız gerekir. Örneğin mantığı biriyle karıştırmak veya modellemek her zaman iyi değildir . Yaklaşımımı tarif ettiğimde, yöntemin katmanda olması gerektiğini söylemiştim , ancak daha bilgiç olacaksak bunun daha iyi olduğunu fark edebiliriz . Deponun ne olduğunu hatırlayalım. Eric Evans kitabında [DDD] kesin bir açıklama yaptı:Repository
Service
removeFriend
Service
Repository
Havuz, belirli bir türdeki tüm nesneleri kavramsal küme olarak temsil eder. Daha ayrıntılı sorgulama yeteneği dışında bir koleksiyon gibi davranır.
Bu nedenle, a Repository
, veriye / nesnelere erişim sağlamak için Koleksiyon stil semantiği (Ekle, Güncelle, Kaldır) kullanan bir cephe. : Eğer böyle bir şey varsa yüzden getFriendsList
, getUserGroups
, removeFriend
sen yerleştirebilirsiniz Repository
toplama benzeri semantik oldukça burada temizlemek, çünkü. Ve kodu şöyle:
- (RACSignal *)approveFriendRequest:(FriendRequest * const)request;
kesinlikle bir iş mantığıdır, çünkü temel CRUD
işlemlerin ötesindedir ve iki alan nesnesini ( Friend
ve Request
) bağlar , bu yüzden Service
katmana yerleştirilmelidir . Ayrıca dikkat etmek istiyorum: gereksiz soyutlamalar yaratmayın . Tüm bu yaklaşımları akıllıca kullanın. Çünkü uygulamanızı soyutlamalarla boğacaksanız, bu onun karmaşıklığını artıracak ve karmaşıklık yazılım sistemlerinde her şeyden
daha fazla soruna neden olacaktır.
Size "eski" bir Objective-C örneği tanımlıyorum, ancak bu yaklaşım Swift diline çok daha fazla iyileştirme ile çok kolay adapte edilebilir, çünkü daha kullanışlı özelliklere ve fonksiyonel şekere sahiptir. Bu kütüphaneyi kullanmanızı kesinlikle tavsiye ederim: Moya . Daha zarif bir APIClient
katman oluşturmanıza izin verir (hatırladığınız gibi iş atımız). Şimdi APIClient
sağlayıcımız, protokollere uygun uzantılar ve yıkım modeli eşleşmesini kullanan bir değer türü (enum) olacaktır. Hızlı numaralandırmalar + desen eşleme, klasik fonksiyonel programlamada olduğu gibi cebirsel veri türleri oluşturmamızı sağlar . Mikro hizmetlerimiz bu geliştirilmiş APIClient
sağlayıcıyı her zamanki Objective-C yaklaşımında olduğu gibi kullanacaktır . Model katmanı yerine ObjectMapper kitaplığınıMantle
kullanabilirsinizveya daha zarif ve fonksiyonel Argo kütüphanesini kullanmayı seviyorum .
Bu yüzden, herhangi bir uygulamaya uyarlanabilecek genel mimari yaklaşımımı anlattım. Tabii ki çok daha fazla gelişme olabilir. Fonksiyonel programlamayı öğrenmenizi tavsiye ederim, çünkü bundan çok faydalanabilirsiniz, ancak onunla fazla ileri gitmeyin. Aşırı, paylaşılan, küresel değişebilir durumu ortadan kaldırmak, değişmez bir alan modeli oluşturmak veya dış yan etkiler olmadan saf işlevler oluşturmak genellikle iyi bir uygulamadır ve yeni Swift
dil bunu teşvik eder. Ancak her zaman unutmayın, kodunuzu ağır saf fonksiyonel kalıplarla aşırı yüklemenin, kategori-teorik yaklaşımlar kötü bir fikirdir, çünkü diğer geliştiriciler kodunuzu okuyacak ve destekleyecek ve hayal kırıklığına uğrayabilir veyaprismatic profunctors
ve değişmez modelinizde bu tür şeyler. İle aynı şey ReactiveCocoa
: RACify
kodunuzu çok fazla etmeyin , çünkü özellikle yeni başlayanlar için gerçekten hızlı okunamaz hale gelebilir. Hedeflerinizi ve mantığınızı gerçekten basitleştirebiliyorsa kullanın.
Yani read a lot, mix, experiment, and try to pick up the best from different architectural approaches
,. Size verebileceğim en iyi tavsiye.