İOS ağ uygulamaları oluşturmak için en iyi mimari yaklaşımlar (REST istemcileri)


323

Biraz deneyime sahip bir iOS geliştiricisiyim ve bu soru benim için gerçekten ilginç. Bu konuda birçok farklı kaynak ve malzeme gördüm, ancak yine de kafam karıştı. İOS ağ bağlantılı bir uygulama için en iyi mimari hangisidir? Sadece birkaç sunucu isteği veya karmaşık bir REST istemcisi olan küçük bir uygulama olsun, her ağ uygulamasına uyacak temel soyut çerçeve, kalıpları kastediyorum. Apple MVC, tüm iOS uygulamaları için temel bir mimari yaklaşım olarak kullanılmasını önerir , ancak ikisi deMVC daha de daha modern MVVMmodeller, ağ mantık kodunun nereye yerleştirileceğini ve genel olarak nasıl düzenleneceğini açıklamaz.

MVCS( SFor Service) gibi bir şey geliştirmem gerekiyor mu ve bu Servicekatmanda tüm APItalepleri ve diğer ağ mantığını koydum , ki bu perspektifte gerçekten karmaşık olabilir mi? Biraz araştırma yaptıktan sonra bunun için iki temel yaklaşım buldum. Burada , karmaşık nesne eşlemeleri ve kalıcılığı varsa, hatta temel hizmetten soyut sınıf ayarını talep eden web hizmetine API( ağ LoginRequestveya PostCommentRequestsınıf gibi) her ağ isteği için ayrı bir sınıf oluşturulması önerildi. standart API ile kendi ağ iletişimi uygulaması). Ama bu yaklaşım benim için ek yük gibi görünüyor. Diğer bir yaklaşım, ilk yaklaşımda olduğu gibi bir singleton görevlisi veya yönetici sınıfına sahip olmaktır ,AbstractBaseRequest ve ek olarak ortak ağ kodu kapsüller bazı küresel ağ yöneticisi oluşturmak ve diğer tercihler ( AFNetworkingözelleştirme veyaRestKitAPI ancak böyle bu yönetici sınıfının bir örneği, kamu yöntemi olarak her talebi saklanması yerine her istek için sınıfları oluşturmak ve: fetchContacts, loginUseryöntemlerin vb Ne yani en iyi ve doğru yol nedir? Henüz bilmediğim başka ilginç yaklaşımlar var mı?

Ve bu ağ oluşturma şeyleri için başka bir katman oluşturmalı mıyım Service, yoksa NetworkProviderkatmanım veya MVCmimarimin üstünde herhangi bir şey varsa , ya da bu katman mevcut MVCkatmanlara entegre edilmeli (enjekte edilmelidir) örneğin Model?

Güzel yaklaşımlar olduğunu biliyorum veya Facebook istemcisi veya LinkedIn istemcisi gibi mobil canavarlar ağ mantığının katlanarak büyüyen karmaşıklığıyla nasıl başa çıkıyor?

Soruna kesin ve resmi bir cevap olmadığını biliyorum. Bu sorunun amacı deneyimli iOS geliştiricilerinden en ilginç yaklaşımları toplamaktır . En iyi önerilen yaklaşım kabul edilmiş ve itibar ödülü olarak işaretlenecek, diğerleri kaldırılacaktır. Bu çoğunlukla teorik ve araştırma sorusudur. İOS'ta ağ uygulamaları için temel, soyut ve doğru mimari yaklaşımı anlamak istiyorum. Deneyimli geliştiricilerin ayrıntılı açıklamalarını umuyorum.


14
Bu bir "alışveriş listesi" sorusu değil mi? Sadece bir cehenneme oy verdim ve kapattım, çünkü “en iyi olan ne” tipi sorular çok yapıcı olmayan bir tartışma ortaya çıkartıldı. Bu alışveriş listesi sorusunu, diğerleri kapatılırken upvotes ve lütufa layık iyi bir soru yapan nedir?
Alvin Thompson

1
Tipik olarak ağ mantığı, bir model nesnesini değiştirecek ve herhangi bir delege veya gözlemciyi bilgilendirecek olan denetleyiciye gider.
2014'te

1
Çok ilginç sorular ve cevaplar. 4 yıllık iOS kodlamasından sonra ve uygulamaya bir ağ katmanı eklemenin en güzel yolunu bulmaya çalışıyorum. Bir ağ isteğini yönetme sorumluluğu hangi sınıfta olmalıdır? Aşağıdaki cevaplar gerçekten önemlidir. Teşekkür ederim
darksider

@JoeBlow bu doğru değil. Mobil uygulama endüstrisi hala sunucu-istemci iletişimine dayanmaktadır.
scord

Yanıtlar:


327

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 developersYaklaşı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 Databir 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 Dataihtiyaç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 everythinghiç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 MVCalemine Service Layeretki 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. Storemodel ö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 : , , ,NSErrorAPIUserSerivcesCommonServicesSecurityServicesFriendsServicesve 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, NSPredicateve bu yüzden gibi düz yöntemlere üzerine getveyaput .

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 APIClientkarşılık gelen belirli bir hizmet çağrısı çevirecek, GET, POST, PUT, DELETE, vb DİNLENME bitiş noktasına isteği. Bu durumda APIClienttü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.APIClientbelirli 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 APIManagerWhateversı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:

  1. Tek bir örneğin mülkiyeti makul şekilde atanamaz;
  2. Tembel başlatma arzu edilir;
  3. 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. UserProfileDenetleyici ihtiyaçları UserServicesvb.) .

SOLID'de her zaman Silkeye 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 FriendsViewControllerkullanı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 FriendsServicessınıfta bir yöntem oluşturmak :

- (RACSignal *)removeFriend:(Friend * const)friend

burada Friendbir model / etki alanı nesnesidir (veya Userbenzer özniteliklere sahiplerse yalnızca bir nesne olabilir). Bu yöntem ayrıştırır Underhood Friendiçin NSDictionaryJSON parametrelerinin friend_id, name, surname, friend_request_idvb. 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 DELETEgerçek bir REST isteği ve döner yapmak için kullanılan yöntem Responseiçinde RACSignal(arayana FriendsViewControllerkullanı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ı:RepositoryServiceremoveFriendServiceRepository

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, removeFriendsen yerleştirebilirsiniz Repositorytoplama benzeri semantik oldukça burada temizlemek, çünkü. Ve kodu şöyle:

- (RACSignal *)approveFriendRequest:(FriendRequest * const)request;

kesinlikle bir iş mantığıdır, çünkü temel CRUDişlemlerin ötesindedir ve iki alan nesnesini ( Friendve Request) bağlar , bu yüzden Servicekatmana 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 APIClientkatman oluşturmanıza izin verir (hatırladığınız gibi iş atımız). Şimdi APIClientsağ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ş APIClientsağ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 Swiftdil 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 profunctorsve değişmez modelinizde bu tür şeyler. İle aynı şey ReactiveCocoa: RACifykodunuzu ç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.


Ayrıca ilginç ve sağlam bir yaklaşım.
MainstreamDeveloper00

1
@darksider Zaten cevap yazdım gibi: "` tekil bir anti-desen, ve çoğu durumda (nadir olanlar hariç) yanlış bir çözümdür çünkü singletons Allah APIManagerWhatever sınıfı veya başka yanlış şeyler hiç kullanmıyorum. ". I don't like singletons. I have an opinion that if you decided to use singletons in your project you should have at least three criteria why you do this (I edited my answer). So I inject them (lazy of course and not each time, but bir kere `) Her denetleyicide
Oleksandr Karaberov

14
Merhaba @alexander. GitHub'da örnek projeleriniz var mı? Çok ilginç bir yaklaşım anlatıyorsunuz. Teşekkürler. Ama ben Objective-C gelişimine yeni başladım. Ve benim için bazı yönleri anlamak zor. Belki GitHub'a bir test projesi yükleyebilir ve bağlantı verebilirsin?
Denis

1
Merhaba @AlexanderKaraberov, verdiğiniz Mağaza açıklaması konusunda biraz kafam karıştı. Her biri için 2 sınıf var, biri ağ ve diğer nesnelerin önbelleğe almasını sağlayan 5 modelim var. Şimdi, ağ ve önbellek sınıfının işlevini çağıran her model için ayrı bir Store sınıfına veya her model için tüm işlevlere sahip tek bir Store sınıfına sahip olmalıyım, böylece denetleyici her zaman veri için tek bir dosyaya erişir.
göktaşları

1
@icodebuster bu demo projesi burada özetlenen kavramların birçoğunu anlamama yardımcı oldu: github.com/darthpelo/NetworkLayerExample

31

Bu sorunun amacına göre, mimari yaklaşımımızı tanımlamak istiyorum.

Mimari yaklaşım

Genel iOS uygulamamızın mimarisi şu modellere dayanmaktadır : Servis katmanları , MVVM , UI Veri Bağlama , Bağımlılık Enjeksiyonu ; ve Fonksiyonel Reaktif Programlama paradigması.

Tipik bir tüketiciye yönelik uygulamayı aşağıdaki mantıksal katmanlara ayırabiliriz:

  • montaj
  • model
  • Hizmetler
  • Depolama
  • yöneticiler
  • Koordinatörler
  • UI
  • altyapı

Montaj katmanı uygulamamızın bir önyükleme noktasıdır. Bir Bağımlılık Enjeksiyon kabı ve uygulama nesnelerinin ve bağımlılıklarının bildirimlerini içerir. Bu katman ayrıca uygulamanın yapılandırmasını (URL'ler, 3. taraf hizmet anahtarları vb.) İçerebilir. Bu amaçla Typhoon kütüphanesini kullanıyoruz.

Model katmanı , etki alanı modelleri sınıflarını, doğrulamaları, eşlemeleri içerir. Modellerimizi haritalamak için Mantle kütüphanesini kullanıyoruz : JSONformat ve NSManagedObjectmodellere serileştirmeyi / serileştirmeyi destekler . Modellerimizin doğrulanması ve form gösterimi için FXForms ve FXModelValidation kütüphanelerini kullanıyoruz.

Hizmetler katmanı , alan modelimizde temsil edilen verileri göndermek veya almak için harici sistemlerle etkileşimde kullandığımız hizmetleri beyan eder. Bu nedenle, genellikle sunucu API'leri (varlık başına), mesajlaşma hizmetleri ( PubNub gibi ), depolama hizmetleri (Amazon S3 gibi) vb. İçin hizmetlerimiz vardır. Temel olarak, hizmetler SDK'lar (örneğin PubNub SDK) tarafından sağlanan nesneleri sarar veya kendi iletişimini uygular mantık. Genel ağ iletişimi için AFNetworking kütüphanesini kullanıyoruz.

Depolama katmanının amacı, cihazdaki yerel veri depolama alanını düzenlemektir. Bunun için Temel Verileri veya Bölge'yi kullanıyoruz (hem artıları hem de eksileri var, ne kullanacağına dair karar somut özelliklere dayanıyor). Temel Veri kurulumu için MDMCoreData kütüphanesini ve her varlık için yerel depolamaya erişim sağlayan sınıflar - depolar - (hizmetlere benzer) kullanırız. Realm için yerel depolamaya erişmek için benzer depoları kullanıyoruz.

Yöneticiler katmanı , soyutlarımızın / ambalajlarımızın yaşadığı bir yerdir.

Bir yönetici rolünde şunlar olabilir:

  • Farklı uygulamaları ile Kimlik Bilgileri Yöneticisi (anahtarlık, NSDefaults, ...)
  • Geçerli kullanıcı oturumunun nasıl tutulacağını ve sağlanacağını bilen Geçerli Oturum Yöneticisi
  • Medya cihazlarına (video kaydı, ses, fotoğraf çekme) erişim sağlayan Boru Hattı Yakalama
  • Bluetooth servislerine ve çevre birimlerine erişim sağlayan BLE Yöneticisi
  • Coğrafi Konum Yöneticisi
  • ...

Bu nedenle, yöneticinin rolü, uygulama çalışması için gereken belirli bir yön veya endişenin mantığını uygulayan herhangi bir nesne olabilir.

Singletons'dan kaçınmaya çalışıyoruz, ancak bu katman ihtiyaç duyduklarında yaşadıkları bir yerdir.

Koordinatörler katmanı , mantıklarını belirli bir modül (özellik, ekran, kullanıcı hikayesi veya kullanıcı deneyimi) için gereken tek bir çalışma dizisinde birleştirmek için diğer katmanlardaki nesnelere (Hizmet, Depolama, Model) bağlı nesneler sağlar. Genellikle eşzamansız işlemleri zincirler ve başarı ve başarısızlık durumlarına nasıl tepki vereceğini bilir. Örnek olarak bir mesajlaşma özelliğini ve karşılık gelen MessagingCoordinatornesneyi hayal edebilirsiniz . Mesaj gönderme işlemini yönetmek şu şekilde görünebilir:

  1. Mesajı doğrula (model katmanı)
  2. Mesajı yerel olarak kaydet (mesajların saklanması)
  3. İleti ekini yükle (amazon s3 hizmeti)
  4. İleti durumunu ve ek URL'lerini güncelleyin ve iletiyi yerel olarak kaydedin (iletilerin saklanması)
  5. İletiyi JSON biçimine seri hale getirme (model katmanı)
  6. PubNub'da mesaj yayınla (PubNub servisi)
  7. Mesaj durumunu ve niteliklerini güncelleme ve yerel olarak kaydetme (mesaj depolama)

Yukarıdaki adımların her birinde bir hata ilgili olarak ele alınır.

UI katmanı aşağıdaki alt katmanlardan oluşur:

  1. ViewModels
  2. ViewControllers
  3. Görüntüleme

Masif Görünüm Denetleyicilerinden kaçınmak için MVVM modelini kullanırız ve ViewModels'de UI sunumu için gerekli mantığı uygularız. Bir ViewModel genellikle bağımlılıklar olarak koordinatörlere ve yöneticilere sahiptir. ViewControllers ve bazı Görünümler tarafından kullanılan ViewModels (örn. Tablo görünümü hücreleri). ViewControllers ve ViewModels arasındaki tutkal Veri Bağlama ve Komut desenidir. Bu tutkalın elde edilmesini mümkün kılmak için ReactiveCocoa kütüphanesini kullanıyoruz.

Ayrıca ReactiveCocoa ve RACSignalkonseptini tüm koordinatörlerin, hizmetlerin, depolama yöntemlerinin arayüzü ve geri dönen değer türü olarak kullanıyoruz. Bu, işlemleri zincirlememize, paralel veya seri olarak çalıştırmamıza ve ReactiveCocoa tarafından sağlanan diğer birçok yararlı şeye izin vermemizi sağlar.

Kullanıcı arayüzü davranışımızı beyan edici bir şekilde uygulamaya çalışıyoruz. Veri Bağlama ve Otomatik Düzen bu hedefe ulaşmak için çok yardımcı olur.

Altyapı katmanı , uygulama çalışması için gereken tüm yardımcıları, uzantıları, yardımcı programları içerir.


Bu yaklaşım bizim için ve genellikle geliştirdiğimiz bu tür uygulamalar için iyi çalışır. Ancak şunu anlamalısınız ki, bu sadece somut ekibin amacı için uyarlanması / değiştirilmesi gereken öznel bir yaklaşımdır .

Bu size yardımcı olacağını umuyoruz!

Ayrıca iOS geliştirme süreci hakkında daha fazla bilgiyi bu blog yazısında iOS Geliştirme Hizmeti olarak bulabilirsiniz.


Birkaç ay önce bu mimariyi beğenmeye başladı, Alex'i paylaştığın için teşekkürler! Yakın gelecekte RxSwift ile denemek istiyorum!
ingaham

18

Tüm iOS uygulamaları farklı olduğundan, burada dikkate alınması gereken farklı yaklaşımlar olduğunu düşünüyorum, ancak genellikle bu şekilde gidiyorum:
Tüm API isteklerini (genellikle APICommunicator olarak adlandırılır) işlemek için bir merkezi yönetici (singleton) sınıfı oluşturun ve her örnek yöntemi bir API çağrısıdır . Ve bir merkezi (halka açık olmayan) yöntem var:

-(RACSignal *)sendGetToServerToSubPath:(NSString *)path withParameters:(NSDictionary *)params;

Kayıt için 2 büyük kütüphane / çerçeve, ReactiveCocoa ve AFNetworking kullanıyorum. ReactiveCocoa zaman uyumsuz ağ yanıtlarını mükemmel bir şekilde işler, yapabilirsiniz (sendNext :, sendError :, vb.).
Bu yöntem API'yı çağırır, sonuçları alır ve bunları RAC üzerinden 'raw' biçiminde gönderir (NSArray gibi AFNetworking'in döndürdüğü gibi).
Daha sonra getStuffList:yukarıdaki yöntem adı verilen bir yöntem onun sinyaline abone olur, ham verileri nesnelere (Motis gibi bir şeyle) ayrıştırır ve nesneleri arayana teker teker gönderir ( getStuffList:ve benzer yöntemler de denetleyicinin abone olabileceği bir sinyal döndürür) ).
Abone olunan denetleyici nesneleri subscribeNext:bloğa göre alır ve işler.

Farklı uygulamalarda birçok yol denedim, ancak bu en iyi şekilde çalıştı, bu yüzden son zamanlarda birkaç uygulamada kullanıyorum, hem küçük hem de büyük projelere uyuyor ve bir şeyin değiştirilmesi gerekiyorsa genişletilmesi ve bakımı kolaydır.
Umarım bu yardımcı olur, başkalarının benim yaklaşımım hakkındaki görüşlerini ve belki de başkalarının bunun nasıl iyileştirilebileceğini düşündüklerini duymak isterim.


2
+1 cevabı için teşekkürler. Iyi bir yaklaşım. Soruyu terk ediyorum. Belki diğer geliştiricilerin başka yaklaşımları olacaktır.
MainstreamDeveloper00

1
Bu yaklaşımdaki bir varyasyonu seviyorum - API ile iletişimin mekaniğine dikkat eden merkezi bir API yöneticisi kullanıyorum. Ancak model objelerimin tüm fonksiyonlarını ortaya koymaya çalışıyorum. Modeller gibi yöntemler sağlayacak + (void)getAllUsersWithSuccess:(void(^)(NSArray*))success failure:(void(^)(NSError*))failure;ve - (void)postWithSuccess:(void(^)(instancetype))success failure:(void(^)(NSError*))failure;hangi gerekli hazırlıkları yapmak ve sonra API yöneticisine aracılığıyla diyoruz.
jsadler

1
Bu yaklaşım basittir, ancak API sayısı arttıkça singleton API yöneticisinin bakımı zorlaşmaktadır. Ve eklenen her yeni API, bu API'nın hangi modüle ait olduğuna bakılmaksızın yönetici ile ilgilidir. API isteklerini yönetmek için github.com/kevin0571/STNetTaskQueue kullanmayı deneyin .
Kevin

Neden çözümümden olabildiğince uzak ve çok daha karmaşık olan kitaplığınızın reklamını yapmanın dışında, bu yaklaşımı hem küçük hem de büyük olan sayısız projede bahsettiğim gibi denedim ve tam olarak kullanıyorum Bu cevabı yazdığımdan beri aynı. Akıllı adlandırma kurallarıyla bakımı hiç zor değildir.
Rickye

8

Benim durumumda genellikle ağ katmanını ayarlamak için ResKit kütüphanesini kullanıyorum . Kullanımı kolay ayrıştırma sağlar. Farklı yanıtlar ve şeyler için harita oluşturma konusundaki çabalarımı azaltır.

Eşlemeyi otomatik olarak ayarlamak için yalnızca bazı kodlar ekliyorum. Modellerim için taban sınıfı tanımladım (bazı yöntemin uygulanıp uygulanmadığını kontrol etmek için çok kod nedeniyle protokol değil, modellerin kendisinde daha az kod):

MappableEntry.h

@interface MappableEntity : NSObject

+ (NSArray*)pathPatterns;
+ (NSArray*)keyPathes;
+ (NSArray*)fieldsArrayForMapping;
+ (NSDictionary*)fieldsDictionaryForMapping;
+ (NSArray*)relationships;

@end

MappableEntry.m

@implementation MappableEntity

+(NSArray*)pathPatterns {
    return @[];
}

+(NSArray*)keyPathes {
    return nil;
}

+(NSArray*)fieldsArrayForMapping {
    return @[];
}

+(NSDictionary*)fieldsDictionaryForMapping {
    return @{};
}

+(NSArray*)relationships {
    return @[];
}

@end

İlişkiler, yanıt olarak iç içe nesneleri temsil eden nesnelerdir:

RelationshipObject.h

@interface RelationshipObject : NSObject

@property (nonatomic,copy) NSString* source;
@property (nonatomic,copy) NSString* destination;
@property (nonatomic) Class mappingClass;

+(RelationshipObject*)relationshipWithKey:(NSString*)key andMappingClass:(Class)mappingClass;
+(RelationshipObject*)relationshipWithSource:(NSString*)source destination:(NSString*)destination andMappingClass:(Class)mappingClass;

@end

RelationshipObject.m

@implementation RelationshipObject

+(RelationshipObject*)relationshipWithKey:(NSString*)key andMappingClass:(Class)mappingClass {
    RelationshipObject* object = [[RelationshipObject alloc] init];
    object.source = key;
    object.destination = key;
    object.mappingClass = mappingClass;
    return object;
}

+(RelationshipObject*)relationshipWithSource:(NSString*)source destination:(NSString*)destination andMappingClass:(Class)mappingClass {
    RelationshipObject* object = [[RelationshipObject alloc] init];
    object.source = source;
    object.destination = destination;
    object.mappingClass = mappingClass;
    return object;
}

@end

Sonra RestKit için eşlemeyi şu şekilde ayarlıyorum:

ObjectMappingInitializer.h

@interface ObjectMappingInitializer : NSObject

+(void)initializeRKObjectManagerMapping:(RKObjectManager*)objectManager;

@end

ObjectMappingInitializer.m

@interface ObjectMappingInitializer (Private)

+ (NSArray*)mappableClasses;

@end

@implementation ObjectMappingInitializer

+(void)initializeRKObjectManagerMapping:(RKObjectManager*)objectManager {

    NSMutableDictionary *mappingObjects = [NSMutableDictionary dictionary];

    // Creating mappings for classes
    for (Class mappableClass in [self mappableClasses]) {
        RKObjectMapping *newMapping = [RKObjectMapping mappingForClass:mappableClass];
        [newMapping addAttributeMappingsFromArray:[mappableClass fieldsArrayForMapping]];
        [newMapping addAttributeMappingsFromDictionary:[mappableClass fieldsDictionaryForMapping]];
        [mappingObjects setObject:newMapping forKey:[mappableClass description]];
    }

    // Creating relations for mappings
    for (Class mappableClass in [self mappableClasses]) {
        RKObjectMapping *mapping = [mappingObjects objectForKey:[mappableClass description]];
        for (RelationshipObject *relation in [mappableClass relationships]) {
            [mapping addPropertyMapping:[RKRelationshipMapping relationshipMappingFromKeyPath:relation.source toKeyPath:relation.destination withMapping:[mappingObjects objectForKey:[relation.mappingClass description]]]];
        }
    }

    // Creating response descriptors with mappings
    for (Class mappableClass in [self mappableClasses]) {
        for (NSString* pathPattern in [mappableClass pathPatterns]) {
            if ([mappableClass keyPathes]) {
                for (NSString* keyPath in [mappableClass keyPathes]) {
                    [objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:[mappingObjects objectForKey:[mappableClass description]] method:RKRequestMethodAny pathPattern:pathPattern keyPath:keyPath statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
                }
            } else {
                [objectManager addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:[mappingObjects objectForKey:[mappableClass description]] method:RKRequestMethodAny pathPattern:pathPattern keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful)]];
            }
        }
    }

    // Error Mapping
    RKObjectMapping *errorMapping = [RKObjectMapping mappingForClass:[Error class]];
    [errorMapping addAttributeMappingsFromArray:[Error fieldsArrayForMapping]];
    for (NSString *pathPattern in Error.pathPatterns) {
        [[RKObjectManager sharedManager] addResponseDescriptor:[RKResponseDescriptor responseDescriptorWithMapping:errorMapping method:RKRequestMethodAny pathPattern:pathPattern keyPath:nil statusCodes:RKStatusCodeIndexSetForClass(RKStatusCodeClassClientError)]];
    }
}

@end

@implementation ObjectMappingInitializer (Private)

+ (NSArray*)mappableClasses {
    return @[
        [FruiosPaginationResults class],
        [FruioItem class],
        [Pagination class],
        [ContactInfo class],
        [Credentials class],
        [User class]
    ];
}

@end

MappableEntry uygulamasına bazı örnekler:

User.h

@interface User : MappableEntity

@property (nonatomic) long userId;
@property (nonatomic, copy) NSString *username;
@property (nonatomic, copy) NSString *email;
@property (nonatomic, copy) NSString *password;
@property (nonatomic, copy) NSString *token;

- (instancetype)initWithUsername:(NSString*)username email:(NSString*)email password:(NSString*)password;

- (NSDictionary*)registrationData;

@end

User.m

@implementation User

- (instancetype)initWithUsername:(NSString*)username email:(NSString*)email password:(NSString*)password {
    if (self = [super init]) {
        self.username = username;
        self.email = email;
        self.password = password;
    }
    return self;
}

- (NSDictionary*)registrationData {
    return @{
        @"username": self.username,
        @"email": self.email,
        @"password": self.password
    };
}

+ (NSArray*)pathPatterns {
    return @[
        [NSString stringWithFormat:@"/api/%@/users/register", APIVersionString],
        [NSString stringWithFormat:@"/api/%@/users/login", APIVersionString]
    ];
}

+ (NSArray*)fieldsArrayForMapping {
    return @[ @"username", @"email", @"password", @"token" ];
}

+ (NSDictionary*)fieldsDictionaryForMapping {
    return @{ @"id": @"userId" };
}

@end

Şimdi İstekler sarma hakkında:

Tüm APIRequest sınıflarında satır uzunluğunu azaltmak için blok tanımı ile başlık dosyası var:

APICallbacks.h

typedef void(^SuccessCallback)();
typedef void(^SuccessCallbackWithObjects)(NSArray *objects);
typedef void(^ErrorCallback)(NSError *error);
typedef void(^ProgressBlock)(float progress);

Ve kullandığım APIRequest sınıfımın örneği:

LoginAPI.h

@interface LoginAPI : NSObject

- (void)loginWithCredentials:(Credentials*)credentials onSuccess:(SuccessCallbackWithObjects)onSuccess onError:(ErrorCallback)onError;

@end

LoginAPI.m

@implementation LoginAPI

- (void)loginWithCredentials:(Credentials*)credentials onSuccess:(SuccessCallbackWithObjects)onSuccess onError:(ErrorCallback)onError {
    [[RKObjectManager sharedManager] postObject:nil path:[NSString stringWithFormat:@"/api/%@/users/login", APIVersionString] parameters:[credentials credentialsData] success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
        onSuccess(mappingResult.array);
    } failure:^(RKObjectRequestOperation *operation, NSError *error) {
        onError(error);
    }];
}

@end

Ve kodda yapmanız gereken tek şey, API nesnesini başlatmak ve ihtiyacınız olduğunda onu çağırmak:

SomeViewController.m

@implementation SomeViewController {
    LoginAPI *_loginAPI;
    // ...
}

- (void)viewDidLoad {
    [super viewDidLoad];

    _loginAPI = [[LoginAPI alloc] init];
    // ...
}

// ...

- (IBAction)signIn:(id)sender {
    [_loginAPI loginWithCredentials:_credentials onSuccess:^(NSArray *objects) {
        // Success Block
    } onError:^(NSError *error) {
        // Error Block
    }];
}

// ...

@end

Kodum mükemmel değil, ancak bir kez ayarlamak ve farklı projeler için kullanmak kolaydır. Herkes için ilginçse, mb Ben biraz zaman harcayabilir ve GitHub ve CocoaPods üzerinde bir yere evrensel bir çözüm yapabilirim.


7

Bana göre tüm yazılım mimarisi ihtiyaçtan kaynaklanıyor. Bu, öğrenme veya kişisel amaçlar içinse, birincil hedefe karar verin ve mimariyi yönlendirmesini sağlayın. Bu kiralık bir işse, iş gereksinimi çok önemlidir. İşin püf noktası, parlak şeylerin sizi gerçek ihtiyaçlardan uzaklaştırmasına izin vermemek. Bunu zor buluyorum. Bu işte her zaman yeni parlak şeyler ortaya çıkıyor ve birçoğu yararlı değil, ancak bunu her zaman önünüzde söyleyemezsiniz. İhtiyaca odaklanın ve mümkünse kötü seçimlerden vazgeçmeye istekli olun.

Örneğin, yakın zamanda yerel bir işletme için fotoğraf paylaşım uygulamasının hızlı bir prototipini yaptım. İşletmenin ihtiyacı hızlı ve kirli bir şey yapmak olduğundan, mimari bir kamera açmak için bir iOS kodu ve görüntüyü bir S3 deposuna yükleyen ve bir SimpleDB etki alanına yazan bir Gönder Düğmesine bağlı bazı ağ kodu oldu. Kod önemsiz ve minimum maliyet ve istemci REST çağrıları ile web üzerinden erişilebilir ölçeklenebilir bir fotoğraf koleksiyonu vardır. Ucuz ve aptal, uygulama çok fazla kusura sahipti ve ara sıra kullanıcı arayüzünü kilitleyecekti, ancak bir prototip için daha fazlasını yapmak bir atık olurdu ve personeline konuşlandırmaya ve performans veya ölçeklenebilirlik olmadan binlerce test görüntüsü oluşturmasına izin veriyor kaygılar. Berbat mimari, ama mükemmel bir ihtiyaç ve maliyet uyuyor.

Başka bir proje, ağ mevcut olduğunda arka planda şirket sistemi ile senkronize olan yerel bir güvenli veritabanı uygulanmasını içeriyordu. İhtiyacım olan her şeye sahip gibi görünen RestKit'i kullanan bir arka plan senkronizörü oluşturdum. Ama RestKit için kendine özgü JSON ile başa çıkmak için o kadar çok özel kod yazmak zorunda kaldı ki CoreData dönüşümlerine kendi JSON'umu yazarak hepsini daha hızlı yapabilirdim. Ancak, müşteri bu uygulamayı eve getirmek istedi ve RestKit'in diğer platformlarda kullandıkları çerçevelere benzer olacağını hissettim. Bunun iyi bir karar olup olmadığını görmek için bekliyorum.

Yine, benim için sorun ihtiyaca odaklanmak ve mimariyi belirlemesine izin vermektir. Üçüncü taraf paketlerini kullanmaktan kaçınmak için cehennem gibi çalışıyorum, çünkü bunlar sadece uygulama bir süre sahada kaldıktan sonra ortaya çıkan maliyetleri getiriyor. Nadiren ödediği için sınıf hiyerarşisi yapmaktan kaçınmaya çalışıyorum. Mükemmel uymayan bir paketi kabul etmek yerine makul bir süre içinde bir şeyler yazabilirsem, o zaman yaparım. Kodum hata ayıklama için iyi yapılandırılmış ve uygun şekilde yorumlanmış, ancak üçüncü taraf paketleri nadiren. Bununla birlikte, AF Ağı'nı görmezden gelmek ve iyi yapılandırılmış, iyi yorumlanmış ve sürdürmek için çok yararlı buluyorum ve çok kullanıyorum! RestKit birçok yaygın vakayı kapsar, ancak bunu kullanırken kavga ettiğimi hissediyorum, ve karşılaştığım veri kaynaklarının çoğu, en iyi özel kodla ele alınan tuhaflıklar ve sorunlarla doludur. Son birkaç uygulamamda yerleşik JSON dönüştürücülerini kullanıyorum ve birkaç yardımcı program yazıyorum.

Her zaman kullandığım bir model, şebeke çağrılarını ana iş parçacığından çıkarmaktır. Yaptığım son 4-5 uygulama dispatch_source_create kullanarak sık sık uyanan ve ağ görevlerini gerektiği gibi yapan bir arka plan zamanlayıcı görevi ayarladı. Bazı iş parçacığı güvenliği çalışması yapmanız ve UI değiştirme kodunun ana iş parçacığına gönderildiğinden emin olmanız gerekir. Ayrıca, alıştırma / başlatma işleminizi kullanıcının yükü veya gecikmesini hissetmeyecek şekilde yapmanıza yardımcı olur. Şimdiye kadar bu oldukça iyi çalışıyor. Bunlara bakmanızı öneririm.

Son olarak, daha çok çalıştıkça ve işletim sistemi geliştikçe daha iyi çözümler geliştirme eğiliminde olduğumuzu düşünüyorum. Diğer insanların zorunlu olduğunu iddia ettikleri desen ve tasarımları takip etmem gerektiğine inancımı aşmam yıllar aldı. Yerel dinin bir parçası olan bir bağlamda çalışıyorsam, ahem, bölümün en iyi mühendislik uygulamalarını kastediyorum, o zaman mektuba gelen gümrükleri takip ediyorum, bana bunun için para veriyorlar. Ancak nadiren eski tasarımları ve desenleri takip etmenin en uygun çözüm olduğunu görüyorum. Çözüme her zaman iş ihtiyaçlarının prizmasıyla bakmaya ve mimariyi buna uyacak şekilde inşa etmeye ve işleri olabildiğince basit tutmaya çalışıyorum. Orada yeterli olmadığını hissettiğimde, ama her şey doğru çalışıyorsa, o zaman doğru yoldayım.


4

Buradan aldığım yaklaşımı kullanıyorum: https://github.com/Constantine-Fry/Foursquare-API-v2 . Swift'teki bu kütüphaneyi yeniden yazdım ve kodun bu bölümlerinden mimari yaklaşımı görebilirsiniz:

typealias OpertaionCallback = (success: Bool, result: AnyObject?) -> ()

class Foursquare{
    var authorizationCallback: OperationCallback?
    var operationQueue: NSOperationQueue
    var callbackQueue: dispatch_queue_t?

    init(){
        operationQueue = NSOperationQueue()
        operationQueue.maxConcurrentOperationCount = 7;
        callbackQueue = dispatch_get_main_queue();
    }

    func checkIn(venueID: String, shout: String, callback: OperationCallback) -> NSOperation {
        let parameters: Dictionary <String, String> = [
            "venueId":venueID,
            "shout":shout,
            "broadcast":"public"]
        return self.sendRequest("checkins/add", parameters: parameters, httpMethod: "POST", callback: callback)
    }

    func sendRequest(path: String, parameters: Dictionary <String, String>, httpMethod: String, callback:OperationCallback) -> NSOperation{
        let url = self.constructURL(path, parameters: parameters)
        var request = NSMutableURLRequest(URL: url)
        request.HTTPMethod = httpMethod
        let operation = Operation(request: request, callbackBlock: callback, callbackQueue: self.callbackQueue!)
        self.operationQueue.addOperation(operation)
        return operation
    }

    func constructURL(path: String, parameters: Dictionary <String, String>) -> NSURL {
        var parametersString = kFSBaseURL+path
        var firstItem = true
        for key in parameters.keys {
            let string = parameters[key]
            let mark = (firstItem ? "?" : "&")
            parametersString += "\(mark)\(key)=\(string)"
            firstItem = false
        }
    return NSURL(string: parametersString.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding))
    }
}

class Operation: NSOperation {
    var callbackBlock: OpertaionCallback
    var request: NSURLRequest
    var callbackQueue: dispatch_queue_t

    init(request: NSURLRequest, callbackBlock: OpertaionCallback, callbackQueue: dispatch_queue_t) {
        self.request = request
        self.callbackBlock = callbackBlock
        self.callbackQueue = callbackQueue
    }

    override func main() {
        var error: NSError?
        var result: AnyObject?
        var response: NSURLResponse?

        var recievedData: NSData? = NSURLConnection.sendSynchronousRequest(self.request, returningResponse: &response, error: &error)

        if self.cancelled {return}

        if recievedData{
            result = NSJSONSerialization.JSONObjectWithData(recievedData, options: nil, error: &error)
            if result != nil {
                if result!.isKindOfClass(NSClassFromString("NSError")){
                    error = result as? NSError
            }
        }

        if self.cancelled {return}

        dispatch_async(self.callbackQueue, {
            if (error) {
                self.callbackBlock(success: false, result: error!);
            } else {
                self.callbackBlock(success: true, result: result!);
            }
            })
    }

    override var concurrent:Bool {get {return true}}
}

Temel olarak, NSURLRequest yapan, JSON yanıtını ayrıştıran ve sonuçla birlikte geri arama bloğunu kuyruğa ekleyen NSOperation alt sınıfı vardır. Ana API sınıfı NSURLRequest'i oluşturur, NSOperation alt sınıfını başlatır ve kuyruğa ekler.


3

Duruma bağlı olarak birkaç yaklaşım kullanıyoruz. Çoğu şey için AFNetworking, üstbilgileri ayarlayabilmeniz, çok parçalı veriler yükleyebilmeniz, GET, POST, PUT & DELETE kullanabilmeniz için en basit ve en sağlam yaklaşımdır ve UIKit için, örneğin bir url. Çok sayıda çağrı içeren karmaşık bir uygulamada, bazen bunu kendi gibi bir kolaylık yöntemine indiririz:

-(void)makeRequestToUrl:(NSURL *)url withParameters:(NSDictionary *)parameters success:(void (^)(id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure;

AFNetworking'in uygun olmadığı birkaç durum vardır, ancak AFNetworking zaten başka bir kod tabanında olabileceği için bir çerçeve veya başka bir kütüphane bileşeni oluşturduğunuz gibi. Bu durumda, tek bir çağrı yapıyorsanız ya da bir istek / yanıt sınıfına girerseniz, NSMutableURLRequest öğesini satır içi kullanırsınız.


Benim için bu en iyi ve en açık cevap, şerefe. "Bu kadar basit". @martin, bizzat biz sadece NSMutableURLRequest kullanıyoruz; AFNetworking'i kullanmak için gerçek bir neden var mı?
Fattie

AFNetworking gerçekten çok uygun. Benim için başarı ve başarısız blokları, kodun yönetilmesini kolaylaştırdığı için değer verirse. Buna rağmen bazen tamamen aşırı olduğunu kabul ediyorum.
Martin

Bloklar üzerinde mükemmel bir nokta, bunun için teşekkürler. Sanırım, Swift'in özel doğası değişecek.
Fattie

2

Uygulamalarımı tasarlarken singletonlardan kaçınırım. Birçok insan için tipik bir seçimdir, ancak başka yerlerde daha zarif çözümler bulabileceğinizi düşünüyorum. Tipik olarak ne yaptığımı CoreData benim varlıkları oluşturmak ve daha sonra bir NSManagedObject kategorisinde benim REST kodunu koymak olduğunu. Örneğin ben yeni bir kullanıcı oluşturmak ve POST istedim, bunu yapmak istiyorum:

User* newUser = [User createInManagedObjectContext:managedObjectContext];
[newUser postOnSuccess:^(...) { ... } onFailure:^(...) { ... }];

Nesne eşlemesi için RESTKit kullanıyorum ve başlangıçta başlattım. Tüm aramalarınızı tek birtondan yönlendirerek zaman kaybı olarak görüyorum ve ihtiyaç duyulmayan çok sayıda kazan plakası ekliyorum.

NSManagedObject + Extensions.m içinde:

+ (instancetype)createInContext:(NSManagedObjectContext*)context
{
    NSAssert(context.persistentStoreCoordinator.managedObjectModel.entitiesByName[[self entityName]] != nil, @"Entity with name %@ not found in model. Is your class name the same as your entity name?", [self entityName]);
    return [NSEntityDescription insertNewObjectForEntityForName:[self entityName] inManagedObjectContext:context];
}

NSManagedObject + Networking.m içinde:

- (void)getOnSuccess:(RESTSuccess)onSuccess onFailure:(RESTFailure)onFailure blockInput:(BOOL)blockInput
{
    [[RKObjectManager sharedManager] getObject:self path:nil parameters:nil success:onSuccess failure:onFailure];
    [self handleInputBlocking:blockInput];
}

Ortak bir temel sınıfın işlevselliğini kategorilerle genişletebildiğinizde neden ekstra yardımcı sınıflar eklemelisiniz?

Çözümümle ilgili daha ayrıntılı bilgi almak istiyorsanız bana bildirin. Paylaştığım için mutluyum.


3
Bir blog gönderisinde bu yaklaşımı daha ayrıntılı olarak okumak isterim.
Danyal Aytekin


0

Tamamen sınıf tasarım açısından, genellikle şöyle bir şey elde edersiniz:

  • Sizin bakış kontrolörleri kontrol eden bir veya daha fazla görünüm
  • Veri modeli sınıfı - Gerçekte kaç gerçek farklı varlıkla uğraştığınıza ve bunların nasıl ilişkili olduğuna bağlıdır.

    Örneğin, dört farklı sunumda görüntülenecek bir dizi öğeniz varsa (liste, grafik, grafik vb.), Öğe listesi için bir veri modeli sınıfına, bir öğe için bir veri sınıfına sahip olursunuz. Madde sınıfının liste bir sekme çubuğu kontrolörü veya bir gez kumandanın tüm çocuklar - dört görünüm denetleyicileri tarafından paylaşılacaktır.

    Veri modeli sınıfları, yalnızca verileri görüntülemekle kalmaz, aynı zamanda her birinin JSON / XML / CSV (veya başka bir şey) dışa aktarma yöntemleriyle kendi serileştirme formatlarını ortaya çıkarabildiği serileştirmede de kullanışlı olacaktır.

  • Doğrudan REST API uç noktalarınızla eşlenen API istek oluşturucu sınıflarına da ihtiyacınız olduğunu anlamak önemlidir . Kullanıcıyı oturum açan bir API'niz olduğunu varsayalım; bu nedenle Login API oluşturucu sınıfınız login api için POST JSON yükü oluşturur. Başka bir örnekte, katalog öğeleri API'si için bir API istek oluşturucu sınıfı, karşılık gelen api için GET sorgu dizesi oluşturur ve REST GET sorgusunu tetikler.

    Bu API istek oluşturucu sınıfları genellikle görünüm denetleyicilerinden veri alır ve aynı verileri kullanıcı arabirimi güncellemesi / diğer işlemler için denetleyicileri görüntülemek üzere geri gönderir. Daha sonra görünüm denetleyicileri Veri Modeli nesnelerini bu verilerle nasıl güncelleyeceğine karar verecektir.

  • Son olarak, REST istemcisinin kalbi - uygulamanızın yaptığı her türlü API isteğinden habersiz olan API veri alıcı sınıfı . Bu sınıf büyük olasılıkla bir singleton olacak, ancak diğerlerinin de belirttiği gibi, singleton olması gerekmiyor.

    Bağlantının sadece tipik bir uygulama olduğunu ve oturum, çerezler vb.


0

Bu sorunun zaten çok sayıda mükemmel ve kapsamlı cevabı var, ama kimsenin olmadığı için bundan bahsetmem gerektiğini hissediyorum.

Swift için Alamofire. https://github.com/Alamofire/Alamofire

AFNetworking ile aynı kişiler tarafından oluşturulur, ancak daha doğrudan Swift düşünülerek tasarlanmıştır.


0

Şimdilik orta ölçekli proje kullanımı MVVM mimarisi ve Büyük proje VIPER mimarisi kullanıyor ve

  • Protokol odaklı programlama
  • Yazılım tasarım modelleri
  • SATILDI prensibi
  • Genel programlama
  • Kendinizi tekrarlama (KURU)

Ve iOS ağ uygulamaları oluşturmak için mimari yaklaşımlar (REST istemcileri)

Temiz ve okunabilir kodlar için ayırma sorunu çoğaltmayı önler:

import Foundation
enum DataResponseError: Error {
    case network
    case decoding

    var reason: String {
        switch self {
        case .network:
            return "An error occurred while fetching data"
        case .decoding:
            return "An error occurred while decoding data"
        }
    }
}

extension HTTPURLResponse {
    var hasSuccessStatusCode: Bool {
        return 200...299 ~= statusCode
    }
}

enum Result<T, U: Error> {
    case success(T)
    case failure(U)
}

bağımlılık dönüşümü

 protocol NHDataProvider {
        func fetchRemote<Model: Codable>(_ val: Model.Type, url: URL, completion: @escaping (Result<Codable, DataResponseError>) -> Void)
    }

Ana sorumlu:

  final class NHClientHTTPNetworking : NHDataProvider {

        let session: URLSession

        init(session: URLSession = URLSession.shared) {
            self.session = session
        }

        func fetchRemote<Model: Codable>(_ val: Model.Type, url: URL,
                             completion: @escaping (Result<Codable, DataResponseError>) -> Void) {
            let urlRequest = URLRequest(url: url)
            session.dataTask(with: urlRequest, completionHandler: { data, response, error in
                guard
                    let httpResponse = response as? HTTPURLResponse,
                    httpResponse.hasSuccessStatusCode,
                    let data = data
                    else {
                        completion(Result.failure(DataResponseError.network))
                        return
                }
                guard let decodedResponse = try? JSONDecoder().decode(Model.self, from: data) else {
                    completion(Result.failure(DataResponseError.decoding))
                    return
                }
                completion(Result.success(decodedResponse))
            }).resume()
        }
    }

Burada dinlenme API Swift Project ile GitHub MVVM mimarisi bulacaksınız


0

Mobil yazılım mühendisliğinde en yaygın kullanılanlar Clean Architecture + MVVM ve Redux kalıplarıdır.

Temiz Mimari + MVVM 3 katmandan oluşur: Etki Alanı, Sunum, Veri katmanları. Sunum Katmanı ve Veri Depoları Katmanı Etki Alanı Katmanına bağlı olduğunda:

Presentation Layer -> Domain Layer <- Data Repositories Layer

Ve Sunum Katmanı ViewModels and Views (MVVM) öğelerinden oluşur:

Presentation Layer (MVVM) = ViewModels + Views
Domain Layer = Entities + Use Cases + Repositories Interfaces
Data Repositories Layer = Repositories Implementations + API (Network) + Persistence DB

Bu makalede, Temiz Mimari + MVVM'nin daha ayrıntılı bir açıklaması var https://tech.olx.com/clean-architecture-and-mvvm-on-ios-c9d167d9f5b3

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.