Pendekatan arsitektur terbaik untuk membangun aplikasi jaringan iOS (klien REST)


323

Saya seorang pengembang iOS dengan pengalaman dan pertanyaan ini sangat menarik bagi saya. Saya melihat banyak sumber dan bahan yang berbeda tentang topik ini, tetapi saya masih bingung. Apa arsitektur terbaik untuk aplikasi jaringan iOS? Maksud saya kerangka abstrak dasar, pola, yang akan cocok dengan setiap aplikasi jaringan apakah itu aplikasi kecil yang hanya memiliki beberapa permintaan server atau klien REST yang kompleks. Apple merekomendasikan untuk digunakan MVCsebagai pendekatan arsitektur dasar untuk semua aplikasi iOS, tetapi tidak satu MVCpun MVVMpola yang lebih modern menjelaskan di mana harus meletakkan kode logika jaringan dan bagaimana mengaturnya secara umum.

Apakah saya perlu mengembangkan sesuatu seperti MVCS( Suntuk Service) dan dalam Servicelapisan ini menaruh semua APIpermintaan dan logika jaringan lainnya, yang dalam perspektif mungkin sangat kompleks? Setelah melakukan penelitian, saya menemukan dua pendekatan dasar untuk ini. Di sini direkomendasikan untuk membuat kelas terpisah untuk setiap permintaan jaringan ke layanan web API(seperti LoginRequestkelas atau PostCommentRequestkelas dan sebagainya) yang semuanya mewarisi dari kelas abstrak permintaan dasar AbstractBaseRequestdan selain itu untuk membuat beberapa manajer jaringan global yang merangkum kode jaringan umum dan preferensi lain (mungkin AFNetworkingkustomisasi atauRestKittuning, jika kita memiliki pemetaan objek yang kompleks dan kegigihan, atau bahkan implementasi komunikasi jaringan sendiri dengan API standar). Tetapi pendekatan ini sepertinya merupakan biaya tambahan bagi saya. Pendekatan lain adalah untuk memiliki beberapa tunggal APIoperator atau kelas manajer seperti dalam pendekatan pertama, tetapi tidak untuk membuat kelas untuk setiap permintaan dan bukan untuk merangkum setiap permintaan sebagai metode contoh umum dari kelas ini manager seperti: fetchContacts, loginUsermetode, dll Jadi, apa yang apakah cara terbaik dan benar? Apakah ada pendekatan menarik lainnya yang belum saya ketahui?

Dan haruskah saya membuat lapisan lain untuk semua hal-hal jaringan seperti ini Service, atau NetworkProviderlapisan atau apa pun di atas MVCarsitektur saya , atau lapisan ini harus diintegrasikan (disuntikkan) ke dalam MVClapisan yang ada misalnya Model?

Saya tahu ada pendekatan yang indah, atau bagaimana monster seluler seperti klien Facebook atau klien LinkedIn menghadapi kompleksitas logika jaringan yang tumbuh secara eksponensial?

Saya tahu tidak ada jawaban pasti dan formal untuk masalah ini. Tujuan dari pertanyaan ini adalah untuk mengumpulkan pendekatan yang paling menarik dari pengembang iOS yang berpengalaman . Pendekatan yang disarankan terbaik akan ditandai sebagai diterima dan diberikan dengan hadiah reputasi, yang lain akan dibatalkan suara. Sebagian besar pertanyaan teoritis dan penelitian. Saya ingin memahami pendekatan arsitektur dasar, abstrak, dan benar untuk aplikasi jaringan di iOS. Saya berharap penjelasan rinci dari pengembang berpengalaman.


14
Bukankah ini pertanyaan "daftar belanja"? Saya hanya punya pertanyaan turun ke neraka dan ditutup karena dinyatakan "apa yang terbaik" pertanyaan memicu terlalu banyak perdebatan tidak konstruktif. Apa yang membuat daftar belanja ini mempertanyakan pertanyaan bagus yang layak mendapat pujian dan hadiah sementara yang lain tutup?
Alvin Thompson

1
Biasanya logika jaringan akan masuk ke controller, yang akan mengubah objek model dan memberi tahu delegasi atau pengamat.
quellish

1
Pertanyaan dan jawaban yang sangat menarik. Setelah 4 tahun pengkodean iOS, dan mencoba menemukan cara paling indah untuk menambahkan lapisan jaringan ke aplikasi. Kelas mana yang harus memiliki tanggung jawab untuk mengelola permintaan jaringan? Jawaban di bawah ini benar-benar relevan. Terima kasih
darksider

@ JoBlow, ini tidak benar. Industri aplikasi seluler masih sangat bergantung pada komunikasi server-klien.
scord

Jawaban:


327

I want to understand basic, abstract and correct architectural approach for networking applications in iOS: tidak ada pendekatan "yang terbaik", atau "yang paling benar" untuk membangun arsitektur aplikasi. Ini adalah pekerjaan yang sangat kreatif. Anda harus selalu memilih arsitektur yang paling mudah dan dapat dikembangkan, yang akan jelas bagi pengembang mana pun, yang mulai mengerjakan proyek Anda atau pengembang lain di tim Anda, tetapi saya setuju, bahwa mungkin ada "baik" dan "buruk" " Arsitektur.

Anda berkata: collect the most interesting approaches from experienced iOS developersSaya tidak berpikir bahwa pendekatan saya adalah yang paling menarik atau benar, tetapi saya telah menggunakannya dalam beberapa proyek dan puas dengan itu. Ini adalah pendekatan hybrid dari yang telah Anda sebutkan di atas, dan juga dengan peningkatan dari upaya penelitian saya sendiri. Saya menarik dalam masalah membangun pendekatan, yang menggabungkan beberapa pola dan idiom terkenal. Saya pikir banyak pola perusahaan Fowler dapat berhasil diterapkan ke aplikasi seluler. Berikut adalah daftar yang paling menarik, yang dapat kami terapkan untuk membuat arsitektur aplikasi iOS ( menurut saya ): Lapisan Layanan , Unit Kerja , Fasad Jarak Jauh , Objek Transfer Data ,Gateway , Layer Supertype , Kasus Khusus , Model Domain . Anda harus selalu merancang lapisan model dengan benar dan jangan lupa tentang kegigihan (ini dapat secara signifikan meningkatkan kinerja aplikasi Anda). Anda bisa menggunakannya Core Datauntuk ini. Tapi Anda tidak boleh lupa, itu Core Databukan ORM atau database, tetapi manajer grafik objek dengan ketekunan sebagai pilihan yang baik. Jadi, sangat sering Core Databisa terlalu berat untuk kebutuhan Anda dan Anda dapat melihat solusi baru seperti Realm dan Couchbase Lite , atau membangun pemetaan objek / lapisan kegigihan ringan Anda sendiri, berdasarkan pada SQLite mentah atau LevelDB. Saya juga menyarankan Anda untuk membiasakan diri dengan Desain Berbasis Domain dan CQRS .

Pada awalnya, saya pikir, kita harus membuat layer lain untuk jaringan, karena kita tidak ingin pengendali gemuk atau model yang berat dan kewalahan. Saya tidak percaya pada fat model, skinny controllerhal - hal itu. Tetapi saya benar- benar percaya pada skinny everythingpendekatan, karena tidak ada kelas yang harus gemuk, tidak pernah. Semua jaringan secara umum dapat diabstraksi sebagai logika bisnis, akibatnya kita harus memiliki lapisan lain, di mana kita dapat meletakkannya. Layer Layanan adalah yang kami butuhkan:

It encapsulates the application's business logic,  controlling transactions 
and coordinating responses in the implementation of its operations.

Di MVCranah kami Service Layeradalah sesuatu seperti mediator antara model domain dan pengontrol. Ada variasi yang agak mirip dari pendekatan ini yang disebut MVCS di mana a Storesebenarnya adalah Servicelayer kita . Storecontoh vends model dan menangani jaringan, caching dll. Saya ingin menyebutkan bahwa Anda tidak harus menulis semua jaringan dan logika bisnis di lapisan layanan Anda. Ini juga dapat dianggap sebagai desain yang buruk. Untuk info lebih lanjut, lihat model domain Anemic and Rich . Beberapa metode layanan dan logika bisnis dapat ditangani dalam model, sehingga akan menjadi model "kaya" (dengan perilaku).

Saya selalu menggunakan dua pustaka: AFNetworking 2.0 dan ReactiveCocoa . Saya pikir itu harus dimiliki untuk aplikasi modern yang berinteraksi dengan jaringan dan layanan web atau mengandung logika UI yang kompleks.

ARSITEKTUR

Pada awalnya saya membuat APIClientkelas umum , yang merupakan subclass dari AFHTTPSessionManager . Ini adalah pekerja keras dari semua jaringan dalam aplikasi: semua kelas layanan mendelegasikan permintaan REST yang sebenarnya. Ini berisi semua penyesuaian klien HTTP, yang saya butuhkan dalam aplikasi tertentu: SSL menyematkan, memproses kesalahan dan membuat NSErrorobjek langsung dengan alasan kegagalan terperinci dan deskripsi semua APIdan kesalahan koneksi (dalam hal ini pengontrol akan dapat menampilkan pesan yang benar untuk pengguna), mengatur permintaan dan tanggapan serializer, header http dan hal-hal lain yang berhubungan dengan jaringan. Lalu aku logis membagi semua permintaan API ke subservices atau, lebih tepatnya, microservices : UserSerivces, CommonServices, SecurityServices,FriendsServicesdan seterusnya, sesuai dengan logika bisnis yang mereka terapkan. Masing-masing layanan microser ini adalah kelas yang terpisah. Mereka, bersama-sama, membentuk a Service Layer. Kelas-kelas ini berisi metode untuk setiap permintaan API, memproses model domain dan selalu mengembalikan a RACSignaldengan model respons yang diurai atau NSErrorke pemanggil.

Saya ingin menyebutkan bahwa jika Anda memiliki logika serialisasi model yang kompleks - kemudian buat layer lain untuk itu: sesuatu seperti Data Mapper tetapi lebih umum misalnya JSON / XML -> Model mapper. Jika Anda memiliki cache: maka buatlah sebagai layer / layanan terpisah juga (Anda seharusnya tidak mencampur logika bisnis dengan caching). Mengapa? Karena lapisan caching yang benar bisa sangat kompleks dengan gotcha sendiri. Orang-orang menerapkan logika kompleks untuk mendapatkan caching yang valid dan dapat diprediksi seperti caching monoid dengan proyeksi berdasarkan para profesional. Anda dapat membaca tentang perpustakaan yang indah ini bernama Carlos untuk lebih mengerti. Dan jangan lupa bahwa Data Inti benar-benar dapat membantu Anda dengan semua masalah caching dan akan memungkinkan Anda untuk menulis lebih sedikit logika. Juga, jika Anda memiliki beberapa logika antara NSManagedObjectContextdan model permintaan server, Anda dapat menggunakanPola repositori , yang memisahkan logika yang mengambil data dan memetakannya ke model entitas dari logika bisnis yang bertindak pada model. Jadi, saya menyarankan untuk menggunakan pola Repositori bahkan ketika Anda memiliki arsitektur berbasis Core Data. Hal-hal abstrak Repository kaleng, seperti NSFetchRequest, NSEntityDescription, NSPredicatedan sebagainya untuk metode biasa seperti getatau put.

Setelah semua tindakan ini di lapisan Layanan, pemanggil (view controller) dapat melakukan beberapa hal asinkron yang kompleks dengan respons: manipulasi sinyal, rantai, pemetaan, dll. Dengan bantuan ReactiveCocoaprimitif, atau hanya berlangganan dan menunjukkan hasilnya dalam tampilan . Saya menyuntikkan dengan Dependency Injection di semua kelas layanan ini saya APIClient, yang akan menerjemahkan panggilan layanan tertentu ke yang sesuai GET, POST, PUT, DELETE, dll permintaan ke titik akhir REST. Dalam hal APIClientini diteruskan secara implisit ke semua pengontrol, Anda dapat membuat ini eksplisit dengan parametris atas APIClientkelas layanan. Ini bisa masuk akal jika Anda ingin menggunakan berbagai penyesuaianAPIClientuntuk kelas layanan tertentu, tetapi jika Anda, untuk beberapa alasan, tidak ingin salinan tambahan atau Anda yakin bahwa Anda akan selalu menggunakan satu contoh khusus (tanpa penyesuaian) dari APIClient- membuat singleton, tapi JANGAN, tolong JANGAN Akan membuat kelas layanan sebagai lajang.

Kemudian setiap view controller lagi dengan DI menyuntikkan kelas layanan yang dibutuhkan, memanggil metode layanan yang sesuai dan menyusun hasilnya dengan logika UI. Untuk injeksi ketergantungan saya suka menggunakan BloodMagic atau kerangka kerja Topan yang lebih kuat . Saya tidak pernah menggunakan lajang, APIManagerWhateverkelas dewa atau hal-hal salah lainnya. Karena jika Anda memanggil kelas Anda WhateverManager, ini menunjukkan bahwa Anda tidak tahu tujuannya dan itu adalah pilihan desain yang buruk . Lajang juga merupakan anti-pola, dan dalam kebanyakan kasus (kecuali yang langka) adalah solusi yang salah . Singleton harus dipertimbangkan hanya jika ketiga kriteria berikut dipenuhi:

  1. Kepemilikan instance tunggal tidak dapat ditetapkan secara wajar;
  2. Inisialisasi malas diinginkan;
  3. Akses global tidak disediakan.

Dalam kasus kami, kepemilikan satu instance tidak menjadi masalah dan kami juga tidak memerlukan akses global setelah kami membagi manajer god kami menjadi layanan, karena sekarang hanya satu atau beberapa pengontrol khusus yang memerlukan layanan tertentu (mis. UserProfileKebutuhan pengontrol UserServicesdan sebagainya) .

Kami harus selalu menghormati Sprinsip dalam SOLID dan menggunakan pemisahan kekhawatiran , jadi jangan letakkan semua metode layanan dan panggilan jaringan Anda dalam satu kelas, karena ini gila, terutama jika Anda mengembangkan aplikasi perusahaan besar. Itu sebabnya kita harus mempertimbangkan injeksi ketergantungan dan pendekatan layanan. Saya menganggap pendekatan ini sebagai modern dan pasca-OO . Dalam hal ini kami membagi aplikasi kami menjadi dua bagian: kontrol logika (pengontrol dan peristiwa) dan parameter.

Satu jenis parameter adalah parameter "data" biasa. Itulah yang kami sampaikan fungsi, memanipulasi, memodifikasi, bertahan, dll. Ini adalah entitas, agregat, koleksi, kelas kasus. Jenis lain akan menjadi parameter "layanan". Ini adalah kelas yang merangkum logika bisnis, memungkinkan berkomunikasi dengan sistem eksternal, menyediakan akses data.

Berikut ini adalah alur kerja umum arsitektur saya. Misalkan kita memiliki FriendsViewController, yang menampilkan daftar teman pengguna dan kita memiliki opsi untuk menghapus dari teman. Saya membuat metode di FriendsServiceskelas saya yang disebut:

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

di mana Friendmodel / objek domain (atau bisa juga hanya Userobjek jika mereka memiliki atribut yang sama). Underhood metode ini mem-parsing Frienduntuk NSDictionaryparameter JSON friend_id, name, surname, friend_request_iddan sebagainya. Saya selalu menggunakan pustaka Mantle untuk jenis boilerplate dan untuk lapisan model saya (parsing maju dan mundur, mengelola hierarki objek bersarang di JSON dan sebagainya). Setelah parsing itu panggilan APIClient DELETEmetode untuk membuat permintaan SISA aktual dan kembali Responsedalam RACSignalke pemanggil ( FriendsViewControllerdalam kasus kami) untuk menampilkan pesan yang sesuai untuk pengguna atau apa pun.

Jika aplikasi kita sangat besar, kita harus memisahkan logika kita lebih jelas. Misalnya tidak selalu baik untuk mencampur Repositoryatau memodelkan logika dengan Servicesatu. Ketika saya menjelaskan pendekatan saya, saya telah mengatakan bahwa removeFriendmetode harus di Servicelapisan, tetapi jika kita akan lebih bertele-tele kita dapat melihat bahwa itu lebih baik milik Repository. Mari kita ingat apa itu Repositori. Eric Evans memberikan deskripsi yang tepat dalam bukunya [DDD]:

Repositori merepresentasikan semua objek dari tipe tertentu sebagai set konseptual. Kerjanya seperti koleksi, kecuali dengan kemampuan kueri yang lebih rumit.

Jadi, pada Repositorydasarnya adalah fasad yang menggunakan semantik gaya Koleksi (Tambah, Perbarui, Hapus) untuk menyediakan akses ke data / objek. Itu sebabnya ketika Anda memiliki sesuatu seperti: getFriendsList, getUserGroups, removeFriendAnda dapat menempatkannya di Repository, karena koleksi-seperti semantik yang cukup jelas di sini. Dan kode seperti:

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

jelas merupakan logika bisnis, karena itu di luar CRUDoperasi dasar dan menghubungkan dua objek domain ( Frienddan Request), itu sebabnya ia harus ditempatkan di Servicelapisan. Saya juga ingin memperhatikan: jangan membuat abstraksi yang tidak perlu . Gunakan semua pendekatan ini dengan bijak. Karena jika Anda akan membanjiri aplikasi Anda dengan abstraksi, ini akan meningkatkan kompleksitas aksidentalnya, dan kompleksitas menyebabkan lebih banyak masalah dalam sistem perangkat lunak daripada yang lainnya

Saya menggambarkan Anda contoh Objective-C "lama" tetapi pendekatan ini bisa sangat mudah diadaptasi untuk bahasa Swift dengan lebih banyak perbaikan, karena ia memiliki fitur yang lebih berguna dan gula fungsional. Saya sangat merekomendasikan untuk menggunakan perpustakaan ini: Moya . Hal ini memungkinkan Anda untuk membuat APIClientlayer yang lebih elegan (pekerja keras kami seperti yang Anda ingat). Sekarang APIClientpenyedia kami akan menjadi tipe nilai (enum) dengan ekstensi yang sesuai dengan protokol dan meningkatkan pencocokan pola perusakan. Pencocokan enum + pola Swift memungkinkan kita untuk membuat tipe data aljabar seperti pada pemrograman fungsional klasik. Layanan microser kami akan menggunakan APIClientpenyedia yang ditingkatkan ini seperti pada pendekatan Objective-C yang biasa. Untuk lapisan model alih-alih MantleAnda bisa menggunakan perpustakaan ObjectMapperatau saya suka menggunakan perpustakaan Argo yang lebih elegan dan fungsional .

Jadi, saya menggambarkan pendekatan arsitektur umum saya, yang dapat disesuaikan untuk aplikasi apa pun, saya pikir. Tentu saja bisa ada banyak perbaikan. Saya menyarankan Anda untuk belajar pemrograman fungsional, karena Anda bisa mendapatkan banyak manfaat dari itu, tetapi jangan terlalu jauh dengan itu. Menghilangkan keadaan yang bisa berubah secara global, dibagikan, dapat diubah, membuat model domain yang tidak dapat diubah atau membuat fungsi murni tanpa efek samping eksternal, umumnya, merupakan praktik yang baik, dan Swiftbahasa baru mendorong hal ini. Tetapi selalu ingat, bahwa membebani kode Anda dengan pola fungsional murni yang murni, pendekatan kategori-teoretis adalah ide yang buruk , karena pengembang lain akan membaca dan mendukung kode Anda, dan mereka dapat frustrasi atau menakutkan dariprismatic profunctorsdan hal-hal semacam itu dalam model abadi Anda. Hal yang sama dengan ReactiveCocoa: jangan RACifykode Anda terlalu banyak , karena dapat menjadi sangat cepat dibaca, terutama untuk pemula. Gunakan ketika itu benar-benar dapat menyederhanakan tujuan dan logika Anda.

Jadi, read a lot, mix, experiment, and try to pick up the best from different architectural approaches. Itu adalah saran terbaik yang bisa saya berikan kepada Anda.


Juga pendekatan yang menarik dan solid. Terima kasih.
MainstreamDeveloper00

1
@darken Seperti yang sudah saya tulis dalam jawaban saya: "` Saya tidak pernah menggunakan lajang, Tuhan APIManager. Apapun kelas atau hal-hal salah lainnya, karena singleton adalah anti-pola, dan dalam kebanyakan kasus (kecuali yang jarang) adalah solusi yang salah. ". 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 sekali `) di setiap pengontrol
Oleksandr Karaberov

14
Hai @alexander. Apakah Anda memiliki contoh proyek di GitHub? Anda menggambarkan pendekatan yang sangat menarik. Terima kasih. Tapi saya seorang pemula dalam pengembangan Objective-C. Dan bagi saya sulit untuk memahami beberapa aspek. Mungkin Anda dapat mengunggah beberapa proyek uji di GitHub dan memberikan tautan?
Denis

1
Halo @AlexanderKaraberov, saya agak bingung dengan penjelasan Store yang Anda berikan. Misalkan saya punya 5 model, untuk masing-masing saya punya 2 kelas, satu yang memelihara jaringan dan caching objek lainnya. Sekarang saya harus memiliki kelas Store terpisah untuk setiap model yang memanggil fungsi jaringan dan kelas cache atau kelas Toko tunggal yang memiliki semua fungsi untuk setiap model, sehingga controller selalu mengakses file tunggal untuk data.
meteor

1
@icodebuster proyek demo ini telah membantu saya memahami banyak konsep yang diuraikan di sini: github.com/darthpelo/NetworkLayerExample

31

Menurut tujuan dari pertanyaan ini, saya ingin menggambarkan pendekatan arsitektur kami.

Pendekatan arsitektur

Arsitektur aplikasi iOS umum kami didasarkan pada pola-pola berikut: Lapisan layanan , MVVM , Pengikatan Data UI , Injeksi Ketergantungan ; dan paradigma Pemrograman Reaktif Fungsional .

Kami dapat mengiris aplikasi khas konsumen menghadap ke lapisan logis berikut:

  • Majelis
  • Model
  • Jasa
  • Penyimpanan
  • Manajer
  • Koordinator
  • UI
  • Infrastruktur

Lapisan perakitan adalah titik bootstrap aplikasi kita. Ini berisi wadah Injeksi Ketergantungan dan deklarasi objek aplikasi dan dependensinya. Lapisan ini juga mungkin berisi konfigurasi aplikasi (url, kunci layanan pihak ke-3 dan seterusnya). Untuk tujuan ini kami menggunakan perpustakaan Typhoon .

Lapisan model berisi kelas model domain, validasi, pemetaan. Kami menggunakan perpustakaan Mantle untuk memetakan model kami: mendukung serialisasi / deserialisasi ke dalam JSONformat dan NSManagedObjectmodel. Untuk validasi dan bentuk representasi dari model kami, kami menggunakan FXForms dan FXModelValidation perpustakaan.

Lapisan Layanan menyatakan layanan yang kami gunakan untuk berinteraksi dengan sistem eksternal untuk mengirim atau menerima data yang direpresentasikan dalam model domain kami. Jadi biasanya kami memiliki layanan untuk komunikasi dengan API server (per entitas), layanan pengiriman pesan (seperti PubNub ), layanan penyimpanan (seperti Amazon S3), dll. Pada dasarnya layanan membungkus objek yang disediakan oleh SDK (misalnya PubNub SDK) atau mengimplementasikan komunikasi mereka sendiri logika. Untuk jaringan umum kami menggunakan perpustakaan AFNetworking .

Tujuan lapisan penyimpanan adalah untuk mengatur penyimpanan data lokal pada perangkat. Kami menggunakan Data Inti atau Realm untuk ini (keduanya memiliki pro dan kontra, keputusan apa yang akan digunakan didasarkan pada spesifikasi nyata). Untuk pengaturan Data Inti, kami menggunakan pustaka MDMCoreData dan banyak kelas - penyimpanan - (mirip dengan layanan) yang menyediakan akses ke penyimpanan lokal untuk setiap entitas. Untuk Realm kami hanya menggunakan penyimpanan serupa untuk memiliki akses ke penyimpanan lokal.

Lapisan manajer adalah tempat di mana abstraksi / pembungkus kami tinggal.

Dalam peran manajer dapat:

  • Credentials Manager dengan implementasinya yang berbeda (gantungan kunci, NSDefaults, ...)
  • Session Manager saat ini yang tahu bagaimana menjaga dan menyediakan sesi pengguna saat ini
  • Capture Pipeline yang menyediakan akses ke perangkat media (perekaman video, audio, pengambilan gambar)
  • BLE Manager yang menyediakan akses ke layanan dan periferal bluetooth
  • Manajer Lokasi Geo
  • ...

Jadi, peran manajer dapat berupa objek apa pun yang mengimplementasikan logika aspek atau perhatian tertentu yang diperlukan untuk aplikasi yang berfungsi.

Kami mencoba untuk menghindari lajang, tetapi lapisan ini adalah tempat di mana mereka tinggal jika mereka diperlukan.

Lapisan koordinator menyediakan objek yang bergantung pada objek dari lapisan lain (Layanan, Penyimpanan, Model) untuk menggabungkan logika mereka ke dalam satu urutan pekerjaan yang diperlukan untuk modul tertentu (fitur, layar, kisah pengguna atau pengalaman pengguna). Biasanya rantai operasi asinkron dan tahu bagaimana bereaksi pada kasus-kasus keberhasilan dan kegagalan mereka. Sebagai contoh, Anda dapat membayangkan fitur olahpesan dan MessagingCoordinatorobjek yang sesuai . Menangani operasi pengiriman pesan mungkin terlihat seperti ini:

  1. Validasi pesan (lapisan model)
  2. Simpan pesan secara lokal (penyimpanan pesan)
  3. Unggah lampiran pesan (layanan amazon s3)
  4. Perbarui status pesan dan url lampiran dan simpan pesan secara lokal (penyimpanan pesan)
  5. Serialisasi pesan ke format JSON (lapisan model)
  6. Publikasikan pesan ke PubNub (layanan PubNub)
  7. Perbarui status dan atribut pesan dan simpan secara lokal (penyimpanan pesan)

Pada masing-masing langkah di atas, sebuah kesalahan ditangani bersamaan.

Lapisan UI terdiri dari sublayer berikut:

  1. ViewModels
  2. ViewControllers
  3. Tampilan

Untuk menghindari Pengontrol Tampilan Masif kami menggunakan pola MVVM dan mengimplementasikan logika yang diperlukan untuk presentasi UI di ViewModels. ViewModel biasanya memiliki koordinator dan manajer sebagai dependensi. ViewModels digunakan oleh ViewControllers dan beberapa jenis Tampilan (misalnya sel tampilan tabel). Perekat antara ViewControllers dan ViewModels adalah Data Binding dan pola Command. Untuk memungkinkan lem itu kami gunakan pustaka ReactiveCocoa .

Kami juga menggunakan ReactiveCocoa dan RACSignalkonsepnya sebagai antarmuka dan mengembalikan tipe nilai dari semua koordinator, layanan, metode penyimpanan. Ini memungkinkan kita untuk rantai operasi, menjalankannya paralel atau seri, dan banyak hal berguna lainnya yang disediakan oleh ReactiveCocoa.

Kami mencoba menerapkan perilaku UI dengan cara deklaratif. Penjilidan Data dan Tata Letak Otomatis sangat membantu untuk mencapai tujuan ini.

Lapisan infrastruktur berisi semua pembantu, ekstensi, utilitas yang diperlukan untuk pekerjaan aplikasi.


Pendekatan ini bekerja dengan baik untuk kami dan jenis aplikasi yang biasanya kami bangun. Tetapi Anda harus mengerti, bahwa ini hanyalah pendekatan subjektif yang harus disesuaikan / diubah untuk tujuan tim konkret.

Semoga ini bisa membantu Anda!

Anda juga dapat menemukan informasi lebih lanjut tentang proses pengembangan iOS di blog ini memposting Pengembangan iOS sebagai Layanan


Mulai menyukai arsitektur ini beberapa bulan yang lalu, terima kasih Alex untuk membagikannya! Saya ingin mencobanya dengan RxSwift dalam waktu dekat!
ingaham

18

Karena semua aplikasi iOS berbeda, saya pikir ada beberapa pendekatan berbeda di sini untuk dipertimbangkan, tetapi saya biasanya menggunakan cara ini:
Buat kelas manajer pusat (tunggal) untuk menangani semua permintaan API (biasanya bernama APICkomunikator) dan setiap contoh metode adalah panggilan API . Dan ada satu metode pusat (non-publik):

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

Sebagai catatan, saya menggunakan 2 pustaka / kerangka kerja utama, ReactiveCocoa dan AFNetworking. ReactiveCocoa menangani respons jaringan async dengan sempurna, Anda dapat melakukannya (sendNext :, sendError :, dll.).
Metode ini memanggil API, mendapatkan hasilnya dan mengirimkannya melalui RAC dalam format 'mentah' (seperti NSArray pengembalian AFNetworking).
Kemudian metode seperti getStuffList:yang disebut metode di atas berlangganan sinyal itu, mem-parsing data mentah menjadi objek (dengan sesuatu seperti Motis) dan mengirimkan objek satu per satu ke penelepon ( getStuffList:dan metode serupa juga mengembalikan sinyal yang dapat berlangganan pengontrol untuk berlangganan ).
Kontroler berlangganan menerima objek oleh subscribeNext:blok dan menanganinya.

Saya mencoba banyak cara di aplikasi yang berbeda tetapi yang ini bekerja paling baik dari semua jadi saya telah menggunakan ini di beberapa aplikasi baru-baru ini, cocok untuk proyek-proyek kecil dan besar dan mudah untuk memperpanjang dan memelihara jika ada sesuatu yang perlu dimodifikasi.
Semoga ini bisa membantu, saya ingin mendengar pendapat orang lain tentang pendekatan saya dan mungkin bagaimana orang lain berpikir ini dapat ditingkatkan.


2
Terima kasih atas jawabannya +1. Pendekatan yang bagus. Saya meninggalkan pertanyaan. Mungkin kita akan memiliki beberapa pendekatan lain dari pengembang lain.
MainstreamDeveloper00

1
Saya suka variasi pada pendekatan ini - saya menggunakan manajer API pusat yang menangani mekanisme komunikasi dengan API. Namun saya mencoba untuk membuat semua fungsionalitas terpapar pada objek model saya. Model akan menyediakan metode seperti + (void)getAllUsersWithSuccess:(void(^)(NSArray*))success failure:(void(^)(NSError*))failure;dan - (void)postWithSuccess:(void(^)(instancetype))success failure:(void(^)(NSError*))failure;yang melakukan persiapan yang diperlukan dan kemudian memanggil manajer API.
jsadler

1
Pendekatan ini sangat mudah, tetapi karena jumlah API meningkat, semakin sulit untuk mempertahankan manajer API tunggal. Dan setiap API baru yang ditambahkan akan berhubungan dengan manajer, terlepas dari modul mana API ini milik. Coba gunakan github.com/kevin0571/STNetTaskQueue untuk mengelola permintaan API.
Kevin

Selain alasan mengapa Anda mengiklankan perpustakaan Anda yang sejauh mungkin dari solusi saya dan jauh lebih rumit, saya sudah mencoba pendekatan ini pada proyek yang tak terhitung jumlahnya baik yang kecil dan besar seperti yang disebutkan dan saya telah menggunakannya tepat pada sama sejak saya menulis jawaban ini. Dengan konvensi penamaan yang cerdas tidak sulit untuk mempertahankannya.
Rickye

8

Dalam situasi saya, saya biasanya menggunakan perpustakaan ResKit untuk mengatur lapisan jaringan. Ini menyediakan penguraian yang mudah digunakan. Ini mengurangi upaya saya mengatur pemetaan untuk berbagai respons dan hal lain.

Saya hanya menambahkan beberapa kode untuk mengatur pemetaan secara otomatis. Saya mendefinisikan kelas dasar untuk model saya (bukan protokol karena banyak kode untuk memeriksa apakah beberapa metode diterapkan atau tidak, dan lebih sedikit kode dalam model itu sendiri):

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

Hubungan adalah objek yang mewakili objek bersarang dalam respons:

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

Lalu saya mengatur pemetaan untuk RestKit seperti ini:

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

Beberapa contoh implementasi MappableEntry:

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

Pengguna.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

Sekarang tentang pembungkus Permintaan:

Saya memiliki file header dengan definisi blok, untuk mengurangi panjang baris di semua kelas APIRequest:

APICallbacks.h

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

Dan Contoh kelas permintaan APIR yang saya gunakan:

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

Dan semua yang perlu Anda lakukan dalam kode, cukup menginisialisasi objek API dan menyebutnya kapan pun Anda membutuhkannya:

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

Kode saya tidak sempurna, tetapi mudah diatur sekali dan digunakan untuk proyek yang berbeda. Jika itu menarik bagi siapa pun, mb saya bisa menghabiskan waktu dan membuat solusi universal untuknya di suatu tempat di GitHub dan CocoaPods.


7

Menurut saya semua arsitektur perangkat lunak didorong oleh kebutuhan. Jika ini untuk tujuan belajar atau pribadi, maka putuskan tujuan utama dan buatlah yang mendorong arsitektur. Jika ini adalah pekerjaan untuk disewa, maka kebutuhan bisnis adalah yang terpenting. Caranya adalah dengan tidak membiarkan benda-benda mengkilap mengalihkan perhatian Anda dari kebutuhan sebenarnya. Saya menemukan ini sulit dilakukan. Selalu ada hal-hal mengkilap baru yang muncul dalam bisnis ini dan banyak dari mereka tidak berguna, tetapi Anda tidak selalu bisa mengatakannya di muka. Berfokuslah pada kebutuhan dan rela meninggalkan pilihan-pilihan buruk jika Anda bisa.

Sebagai contoh, saya baru-baru ini membuat prototipe cepat dari aplikasi berbagi foto untuk bisnis lokal. Karena kebutuhan bisnis adalah untuk melakukan sesuatu yang cepat dan kotor, arsitektur akhirnya menjadi beberapa kode iOS untuk memunculkan kamera dan beberapa kode jaringan yang melekat pada Tombol Kirim yang mengunggah gambar ke toko S3 dan menulis ke domain SimpleDB. Kode itu sepele dan biayanya minimal dan klien memiliki koleksi foto yang dapat diukur yang dapat diakses melalui web dengan panggilan REST. Murah dan bodoh, aplikasi ini memiliki banyak kekurangan dan kadang-kadang akan mengunci UI, tetapi itu akan sia-sia untuk melakukan lebih banyak untuk prototipe dan memungkinkan mereka untuk digunakan untuk staf mereka dan menghasilkan ribuan gambar uji dengan mudah tanpa kinerja atau skalabilitas keprihatinan. Arsitektur jelek, tapi itu sesuai dengan kebutuhan dan biaya sempurna.

Proyek lain yang terlibat menerapkan database aman lokal yang disinkronkan dengan sistem perusahaan di latar belakang ketika jaringan tersedia. Saya membuat sinkronisasi latar belakang yang menggunakan RestKit karena tampaknya memiliki semua yang saya butuhkan. Tetapi saya harus menulis begitu banyak kode khusus untuk RestKit untuk berurusan dengan JSON istimewa sehingga saya bisa melakukan semuanya lebih cepat dengan menulis JSON saya sendiri ke transformasi CoreData. Namun, pelanggan ingin membawa aplikasi ini ke rumah dan saya merasa bahwa RestKit akan serupa dengan kerangka kerja yang mereka gunakan pada platform lain. Saya menunggu untuk melihat apakah itu keputusan yang bagus.

Sekali lagi, masalah bagi saya adalah fokus pada kebutuhan dan biarkan yang menentukan arsitektur. Saya berusaha keras untuk menghindari menggunakan paket pihak ketiga karena mereka membawa biaya yang hanya muncul setelah aplikasi berada di lapangan untuk sementara waktu. Saya mencoba menghindari membuat hierarki kelas karena jarang menghasilkan. Jika saya dapat menulis sesuatu dalam periode waktu yang masuk akal alih-alih mengadopsi paket yang tidak cocok dengan sempurna, maka saya melakukannya. Kode saya terstruktur dengan baik untuk debugging dan berkomentar dengan tepat, tetapi paket pihak ketiga jarang. Dengan itu, saya menemukan Jaringan AF terlalu berguna untuk diabaikan dan terstruktur dengan baik, berkomentar dengan baik, dan dipelihara dan saya sering menggunakannya! RestKit mencakup banyak kasus umum, tapi saya merasa seperti saya berkelahi ketika saya menggunakannya, dan sebagian besar sumber data yang saya temui penuh dengan kebiasaan dan masalah yang paling baik ditangani dengan kode khusus. Dalam beberapa aplikasi terakhir saya, saya hanya menggunakan konverter JSON bawaan dan menulis beberapa metode utilitas.

Satu pola yang selalu saya gunakan adalah untuk mendapatkan panggilan jaringan dari utas utama. 4-5 aplikasi terakhir yang saya lakukan mengatur tugas penghitung waktu latar belakang menggunakan dispatch_source_create yang sering bangun dan melakukan tugas jaringan sesuai kebutuhan. Anda perlu melakukan beberapa pekerjaan keselamatan utas dan memastikan bahwa kode modifikasi UI dikirim ke utas utama. Ini juga membantu untuk melakukan onboarding / inisialisasi Anda sedemikian rupa sehingga pengguna tidak merasa terbebani atau tertunda. Sejauh ini ini telah bekerja dengan cukup baik. Saya sarankan melihat hal-hal ini.

Akhirnya, saya berpikir bahwa ketika kita bekerja lebih banyak dan seiring OS berkembang, kita cenderung mengembangkan solusi yang lebih baik. Butuh waktu bertahun-tahun untuk melupakan keyakinan saya bahwa saya harus mengikuti pola dan desain yang menurut orang lain wajib. Jika saya bekerja dalam konteks di mana itu adalah bagian dari agama setempat, ahem, maksud saya praktik teknik terbaik departemen, maka saya mengikuti kebiasaan surat itu, itulah yang mereka bayar untuk saya. Tetapi saya jarang menemukan bahwa mengikuti desain dan pola lama adalah solusi optimal. Saya selalu mencoba melihat solusi melalui prisma kebutuhan bisnis dan membangun arsitektur untuk mencocokkannya dan menjaga hal-hal sesederhana mungkin. Ketika saya merasa tidak ada cukup di sana, tetapi semuanya bekerja dengan benar, maka saya berada di jalur yang benar.


4

Saya menggunakan pendekatan yang saya dapatkan dari sini: https://github.com/Constantine-Fry/Foursquare-API-v2 . Saya telah menulis ulang perpustakaan itu di Swift dan Anda dapat melihat pendekatan arsitektur dari bagian-bagian kode ini:

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}}
}

Pada dasarnya, ada subkelas NSOperation yang membuat NSURLRequest, mem-parsing respons JSON dan menambahkan blok panggilan balik dengan hasilnya ke antrian. Kelas API utama membangun NSURLRequest, menginisialisasi subclass NSOperation dan menambahkannya ke antrian.


3

Kami menggunakan beberapa pendekatan tergantung pada situasinya. Untuk sebagian besar hal, AFNetworking adalah pendekatan paling sederhana dan paling kuat di mana Anda dapat mengatur header, mengunggah data multi-bagian, menggunakan GET, POST, PUT & DELETE dan ada banyak kategori tambahan untuk UIKit yang memungkinkan Anda untuk misalnya mengatur gambar dari sebuah url. Dalam aplikasi yang kompleks dengan banyak panggilan, kadang-kadang kita abstraksi ke metode kenyamanan kita sendiri yang akan menjadi seperti:

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

Ada beberapa situasi di mana AFNetworking tidak sesuai namun seperti di mana Anda membuat kerangka kerja atau komponen pustaka lainnya karena AFNetworking mungkin sudah ada di basis kode lain. Dalam situasi ini, Anda akan menggunakan NSMutableURLRequest baik inline jika Anda membuat satu panggilan atau diabstraksi menjadi kelas permintaan / respons.


Bagi saya ini adalah jawaban terbaik dan paling jelas, tepuk tangan. "Sesederhana itu". @martin, secara pribadi kami hanya menggunakan NSMutableURLRequest sepanjang waktu; Adakah alasan nyata untuk menggunakan AFNetworking?
Fattie

AFNetworking benar-benar nyaman. Bagi saya blok sukses dan gagal menghasilkan nilai sementara karena membuat kode lebih mudah untuk dikelola. Saya setuju bahwa itu kadang-kadang total berlebihan.
Martin

Poin luar biasa di blok, terima kasih untuk itu. Saya kira, sifat spesifik dari ini semua akan berubah dengan Swift.
Fattie

2

Saya menghindari lajang ketika merancang aplikasi saya. Mereka adalah tipikal untuk banyak orang, tetapi saya pikir Anda dapat menemukan solusi yang lebih elegan di tempat lain. Biasanya yang saya lakukan adalah membangun entitas saya di CoreData dan kemudian meletakkan kode REST saya dalam kategori NSManagedObject. Jika misalnya saya ingin membuat dan POST Pengguna baru, saya akan melakukan ini:

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

Saya menggunakan RESTKit untuk pemetaan objek dan menginisialisasi saat start up. Saya menemukan routing semua panggilan Anda melalui singleton menjadi buang-buang waktu dan menambahkan banyak boilerplate yang tidak diperlukan.

Dalam NSManagedObject + Extensions.m:

+ (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];
}

Dalam NSManagedObject + Networking.m:

- (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];
}

Mengapa menambahkan kelas pembantu tambahan ketika Anda dapat memperluas fungsionalitas kelas dasar umum melalui kategori?

Jika Anda tertarik pada informasi lebih rinci tentang solusi saya, beri tahu saya. Saya senang berbagi.


3
Pasti akan tertarik membaca tentang pendekatan ini secara lebih rinci dalam posting blog.
Danyal Aytekin


0

Dari perspektif desain murni kelas, Anda biasanya akan memiliki sesuatu seperti ini:

  • Pengontrol tampilan Anda mengendalikan satu atau beberapa tampilan
  • Kelas model data - Ini benar-benar tergantung pada berapa banyak entitas nyata yang Anda hadapi, dan bagaimana mereka terkait.

    Misalnya, jika Anda memiliki array item yang akan ditampilkan dalam empat representasi berbeda (daftar, bagan, grafik dll), Anda akan memiliki satu kelas model data untuk daftar item, satu lagi untuk sebuah item. The daftar kelas item akan dibagi oleh empat pengendali tampilan - semua anak dari tab bar controller atau nav kontroler.

    Kelas-kelas model data akan berguna tidak hanya menampilkan data, tetapi juga membuat serial dimana mereka masing-masing dapat mengekspos format serialisasi mereka sendiri melalui metode ekspor JSON / XML / CSV (atau apa pun).

  • Penting untuk dipahami bahwa Anda juga memerlukan kelas pembuat permintaan API yang memetakan langsung dengan titik akhir REST API Anda. Katakanlah Anda memiliki API yang mencatat pengguna - jadi kelas pembuat API Login Anda akan membuat payload POST JSON untuk api login. Dalam contoh lain, kelas pembuat permintaan API untuk daftar item katalog, API akan membuat string kueri GET untuk api terkait dan mengaktifkan kueri REST GET.

    Kelas pembuat permintaan API ini biasanya akan menerima data dari pengontrol tampilan dan juga mengembalikan data yang sama untuk melihat pengontrol untuk pembaruan UI / operasi lainnya. View controller kemudian akan memutuskan bagaimana memperbarui objek Model Data dengan data itu.

  • Akhirnya, inti dari klien mengambil data API - REST yang tidak menyadari segala macam permintaan API yang dibuat oleh aplikasi Anda. Kelas ini lebih cenderung menjadi singleton, tetapi seperti yang ditunjukkan orang lain, itu tidak harus singleton.

    Perhatikan bahwa tautan itu hanya implementasi biasa dan tidak mempertimbangkan skenario pertimbangan seperti sesi, cookie, dll, tetapi cukup untuk membuat Anda pergi tanpa menggunakan kerangka kerja pihak ke-3.


0

Pertanyaan ini sudah memiliki banyak jawaban yang sangat bagus dan luas, tetapi saya merasa saya harus menyebutkannya karena tidak ada orang lain yang memilikinya.

Alamofire for Swift. https://github.com/Alamofire/Alamofire

Ini dibuat oleh orang yang sama dengan AFNetworking, tetapi lebih dirancang secara langsung dengan mempertimbangkan Swift.


0

Saya pikir untuk saat ini proyek sedang menggunakan arsitektur MVVM dan Proyek besar menggunakan arsitektur VIPER dan berusaha untuk mencapai

  • Pemrograman berorientasi protokol
  • Pola desain perangkat lunak
  • Prinsip TERJUAL
  • Pemrograman generik
  • Jangan ulangi diri sendiri (KERING)

Dan pendekatan Arsitektur untuk membangun aplikasi jaringan iOS (klien REST)

Masalah pemisahan untuk kode yang bersih dan mudah dibaca menghindari duplikasi:

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)
}

inversi ketergantungan

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

Penanggung jawab utama:

  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()
        }
    }

Anda akan menemukan di sini adalah arsitektur GitHub MVVM dengan Proyek API Swift lainnya


0

Dalam rekayasa perangkat lunak seluler, yang paling banyak digunakan adalah pola Clean Architecture + MVVM dan Redux.

Arsitektur Bersih + MVVM terdiri dari 3 lapisan: Domain, Presentasi, Lapisan data. Di mana Lapisan Presentasi dan Lapisan Gudang Data bergantung pada Lapisan Domain:

Presentation Layer -> Domain Layer <- Data Repositories Layer

Dan Presentation Layer terdiri dari ViewModels and Views (MVVM):

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

Dalam artikel ini, ada deskripsi yang lebih rinci tentang Arsitektur Bersih + MVVM https://tech.olx.com/clean-architecture-and-mvvm-on-ios-c9d167d9f5b3

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.