Phương pháp kiến ​​trúc tốt nhất để xây dựng các ứng dụng mạng iOS (ứng dụng khách REST)


323

Tôi là một nhà phát triển iOS có một số kinh nghiệm và câu hỏi này thực sự thú vị đối với tôi. Tôi đã thấy rất nhiều tài nguyên và tài liệu khác nhau về chủ đề này, tuy nhiên tôi vẫn còn bối rối. Kiến trúc tốt nhất cho ứng dụng nối mạng iOS là gì? Ý tôi là khung trừu tượng cơ bản, các mẫu, sẽ phù hợp với mọi ứng dụng mạng cho dù đó là một ứng dụng nhỏ chỉ có một vài yêu cầu máy chủ hoặc máy khách REST phức tạp. Apple khuyên bạn nên sử dụng MVCnhư một phương pháp kiến ​​trúc cơ bản cho tất cả các ứng dụng iOS, nhưng cũng không MVCphải các MVVMmẫu hiện đại hơn giải thích nơi đặt mã logic mạng và cách tổ chức chung.

Tôi có cần phải phát triển một cái gì đó như MVCS( Scho Service) và trong Servicelớp này đặt tất cả các APIyêu cầu và logic mạng khác, trong quan điểm có thể thực sự phức tạp? Sau khi thực hiện một số nghiên cứu tôi đã tìm thấy hai cách tiếp cận cơ bản cho việc này. Ở đây, chúng tôi khuyên bạn nên tạo một lớp riêng cho mọi yêu cầu mạng đối với dịch vụ web API(như LoginRequestlớp hoặc PostCommentRequestlớp, v.v.) mà tất cả đều kế thừa từ lớp trừu tượng yêu cầu cơ sở AbstractBaseRequestvà ngoài ra để tạo một số trình quản lý mạng toàn cầu đóng gói mã mạng chung và các ưu tiên khác (có thể là AFNetworkingtùy chỉnh hoặcRestKitđiều chỉnh, nếu chúng ta có ánh xạ và sự kiên trì của đối tượng phức tạp, hoặc thậm chí là một triển khai giao tiếp mạng riêng với API tiêu chuẩn). Nhưng cách tiếp cận này có vẻ là một chi phí cho tôi. Một cách tiếp cận khác là có một số trình APIđiều phối đơn lẻ hoặc lớp trình quản lý như trong cách tiếp cận đầu tiên, nhưng không tạo các lớp cho mọi yêu cầu và thay vào đó để đóng gói mọi yêu cầu như một phương thức công khai của lớp trình quản lý này như : fetchContacts, loginUserphương thức, v.v. cách tốt nhất và đúng? Có những cách tiếp cận thú vị khác mà tôi chưa biết?

Và tôi có nên tạo một lớp khác cho tất cả các công cụ kết nối mạng này như Service, hoặc NetworkProviderlớp hoặc bất cứ thứ gì trên MVCkiến trúc của tôi không , hay lớp này nên được tích hợp (được tiêm) vào các MVClớp hiện có, vd Model?

Tôi biết có tồn tại những cách tiếp cận đẹp, hoặc làm thế nào những quái vật di động như khách hàng Facebook hay khách hàng LinkedIn đối phó với sự phức tạp ngày càng tăng theo cấp số nhân của logic mạng?

Tôi biết không có câu trả lời chính xác và chính thức cho vấn đề. Mục tiêu của câu hỏi này là thu thập các cách tiếp cận thú vị nhất từ ​​các nhà phát triển iOS có kinh nghiệm . Cách tiếp cận được đề xuất tốt nhất sẽ được đánh dấu là được chấp nhận và được trao bằng tiền thưởng danh tiếng, những cách khác sẽ được nâng cao. Nó chủ yếu là một câu hỏi lý thuyết và nghiên cứu. Tôi muốn hiểu cách tiếp cận kiến ​​trúc cơ bản, trừu tượng và chính xác cho các ứng dụng mạng trong iOS. Tôi hy vọng cho lời giải thích chi tiết từ các nhà phát triển có kinh nghiệm.


14
Đây không phải là một câu hỏi "danh sách mua sắm"? Tôi vừa có một câu hỏi được bình chọn xuống địa ngục và đóng lại bởi vì nó được nêu là câu hỏi loại "tốt nhất" là gì gây ra quá nhiều cuộc tranh luận không có kết quả. Điều gì làm cho danh sách mua sắm này là một câu hỏi hay xứng đáng với sự ủng hộ và tiền thưởng trong khi những người khác bị đóng cửa?
Alvin Thompson

1
Thông thường logic mạng sẽ đi vào bộ điều khiển, điều này sẽ thay đổi một đối tượng mô hình và thông báo cho bất kỳ đại biểu hoặc quan sát viên nào.
quellish

1
Những câu hỏi và câu trả lời rất thú vị. Sau 4 năm mã hóa iOS và cố gắng tìm ra cách đẹp nhất để thêm một lớp mạng vào ứng dụng. Lớp nào có trách nhiệm quản lý yêu cầu mạng? Các câu trả lời dưới đây thực sự thích hợp. Cảm ơn bạn
darksider

@JoeBlow điều này không đúng. Ngành công nghiệp ứng dụng di động vẫn phụ thuộc rất nhiều vào thông tin liên lạc giữa máy chủ và máy khách.
scord

Câu trả lời:


327

I want to understand basic, abstract and correct architectural approach for networking applications in iOS: Có không "tốt nhất", hay "nhất đúng" cách tiếp cận để xây dựng một kiến trúc ứng dụng. Đó là một công việc rất sáng tạo. Bạn nên luôn luôn chọn kiến ​​trúc đơn giản và có thể mở rộng nhất, điều này sẽ rõ ràng đối với bất kỳ nhà phát triển nào, những người bắt đầu làm việc với dự án của bạn hoặc cho các nhà phát triển khác trong nhóm của bạn, nhưng tôi đồng ý rằng có thể có "tốt" và "xấu" " ngành kiến ​​trúc.

Bạn nói:, collect the most interesting approaches from experienced iOS developerstôi không nghĩ rằng cách tiếp cận của tôi là thú vị hay đúng đắn nhất, nhưng tôi đã sử dụng nó trong một số dự án và hài lòng với nó. Đó là một cách tiếp cận hỗn hợp của những người bạn đã đề cập ở trên, và cũng với những cải tiến từ những nỗ lực nghiên cứu của riêng tôi. Tôi thú vị trong các vấn đề xây dựng phương pháp tiếp cận, kết hợp một số mô hình và thành ngữ nổi tiếng. Tôi nghĩ rằng rất nhiều mẫu doanh nghiệp của Fowler có thể được áp dụng thành công cho các ứng dụng di động. Dưới đây là danh sách những thứ thú vị nhất mà chúng ta có thể áp dụng để tạo kiến ​​trúc ứng dụng iOS ( theo ý kiến ​​của tôi ): Tầng dịch vụ , Đơn vị công việc , Mặt tiền từ xa , Đối tượng truyền dữ liệu ,Cổng , Lớp siêu lớp , Trường hợp đặc biệt , Mô hình miền . Bạn phải luôn thiết kế chính xác một lớp mô hình và luôn không quên sự kiên trì (nó có thể làm tăng đáng kể hiệu suất của ứng dụng của bạn). Bạn có thể sử dụng Core Datacho việc này. Nhưng bạn không nên quên, đó Core Datakhông phải là ORM hay cơ sở dữ liệu, mà là một trình quản lý biểu đồ đối tượng với sự kiên trì là một lựa chọn tốt của nó. Vì vậy, rất thường xuyên Core Datacó thể quá nặng đối với nhu cầu của bạn và bạn có thể xem xét các giải pháp mới như RealmCouchbase Lite hoặc xây dựng lớp duy trì / ánh xạ đối tượng nhẹ của riêng bạn, dựa trên SQLite hoặc LevelDB thô. Ngoài ra, tôi khuyên bạn nên làm quen với Thiết kế hướng miềnCQRS .

Lúc đầu, tôi nghĩ, chúng ta nên tạo một lớp khác để kết nối mạng, vì chúng ta không muốn các bộ điều khiển chất béo hoặc các mô hình nặng, quá tải. Tôi không tin vào những fat model, skinny controllerđiều đó. Nhưng tôi tin vào skinny everythingcách tiếp cận, bởi vì không có lớp học nào nên béo cả. Tất cả các mạng có thể được trừu tượng hóa như logic kinh doanh, do đó chúng ta nên có một lớp khác, nơi chúng ta có thể đặt nó. Lớp dịch vụ là những gì chúng ta cần:

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

Trong MVCvương quốc của chúng ta Service Layerlà một cái gì đó giống như một trung gian hòa giải giữa mô hình miền và bộ điều khiển. Có một biến thể khá giống nhau của phương pháp này được gọi là MVCS trong đó a Storethực sự là Servicelớp của chúng tôi . Storebán các phiên bản mô hình và xử lý kết nối mạng, bộ đệm, v.v. Tôi muốn đề cập rằng bạn không nên viết tất cả logic mạng và kinh doanh trong lớp dịch vụ của mình. Đây cũng có thể được coi là một thiết kế xấu. Để biết thêm thông tin, hãy xem các mô hình miền Thiếu máuGiàu . Một số phương thức dịch vụ và logic nghiệp vụ có thể được xử lý trong mô hình, vì vậy nó sẽ là mô hình "phong phú" (có hành vi).

Tôi luôn sử dụng rộng rãi hai thư viện: AFNetworking 2.0ReactiveCocoa . Tôi nghĩ rằng nó là phải có cho bất kỳ ứng dụng hiện đại nào tương tác với mạng và dịch vụ web hoặc chứa logic UI phức tạp.

NGÀNH KIẾN TRÚC

Đầu tiên tôi tạo một APIClientlớp chung , đó là một lớp con của AFHTTPSessionManager . Đây là một đặc điểm của tất cả các mạng trong ứng dụng: tất cả các lớp dịch vụ ủy thác các yêu cầu REST thực tế cho nó. Nó chứa tất cả các tùy chỉnh của ứng dụng khách HTTP mà tôi cần trong ứng dụng cụ thể: Ghim SSL, xử lý lỗi và tạo NSErrorcác đối tượng đơn giản với lý do lỗi và mô tả chi tiết về tất cả APIvà lỗi kết nối (trong trường hợp đó, bộ điều khiển sẽ có thể hiển thị thông báo chính xác cho người dùng), cài đặt tuần tự hóa yêu cầu và phản hồi, tiêu đề http và các nội dung liên quan đến mạng khác. Sau đó, tôi một cách logic chia tất cả các yêu cầu API vào subservices hoặc, đúng hơn, microservices : UserSerivces, CommonServices, SecurityServices,FriendsServicesvà như vậy, theo logic kinh doanh họ thực hiện. Mỗi microservice này là một lớp riêng biệt. Họ, cùng nhau, tạo thành một Service Layer. Các lớp này chứa các phương thức cho từng yêu cầu API, xử lý các mô hình miền và luôn trả về a RACSignalvới mô hình phản hồi được phân tích cú pháp hoặc NSErrorcho người gọi.

Tôi muốn đề cập rằng nếu bạn có logic tuần tự hóa mô hình phức tạp - thì hãy tạo một lớp khác cho nó: một cái gì đó giống như Data Mapper nhưng tổng quát hơn, ví dụ JSON / XML -> Model mapper. Nếu bạn có bộ đệm: thì cũng tạo nó dưới dạng một lớp / dịch vụ riêng biệt (bạn không nên trộn logic nghiệp vụ với bộ đệm). Tại sao? Bởi vì lớp bộ nhớ đệm chính xác có thể khá phức tạp với các vấn đề riêng của nó. Mọi người thực hiện logic phức tạp để có được bộ nhớ đệm hợp lệ, có thể dự đoán được, ví dụ như bộ nhớ đệm đơn hình với các phép chiếu dựa trên các profunctor. Bạn có thể đọc về thư viện xinh đẹp này có tên Carlos để hiểu thêm. Và đừng quên rằng Core Data thực sự có thể giúp bạn trong mọi vấn đề về bộ nhớ đệm và sẽ cho phép bạn viết ít logic hơn. Ngoài ra, nếu bạn có một số logic giữa NSManagedObjectContextvà các mô hình yêu cầu máy chủ, bạn có thể sử dụngMẫu kho lưu trữ , phân tách logic lấy dữ liệu và ánh xạ nó tới mô hình thực thể từ logic nghiệp vụ hoạt động trên mô hình. Vì vậy, tôi khuyên bạn nên sử dụng mẫu Kho lưu trữ ngay cả khi bạn có kiến ​​trúc dựa trên Dữ liệu lõi. Kho lon những thứ trừu tượng, giống như NSFetchRequest, NSEntityDescription, NSPredicatevà vân vân với các phương pháp đơn giản như gethay put.

Sau tất cả các hành động này trong lớp Dịch vụ, trình gọi (trình điều khiển xem) có thể thực hiện một số nội dung không đồng bộ phức tạp với phản hồi: thao tác tín hiệu, xâu chuỗi, ánh xạ, v.v. với sự trợ giúp của ReactiveCocoanguyên thủy hoặc chỉ đăng ký vào nó và hiển thị kết quả trong chế độ xem . Tôi tiêm với dependency injection trong tất cả các lớp dịch vụ của tôi APIClient, mà sẽ dịch một cuộc gọi dịch vụ cụ thể vào tương ứng GET, POST, PUT, DELETEvv yêu cầu đến thiết bị đầu cuối REST. Trong trường hợp APIClientnày được truyền hoàn toàn cho tất cả các bộ điều khiển, bạn có thể làm cho điều này rõ ràng với một tham số trên APIClientcác lớp dịch vụ. Điều này có thể có ý nghĩa nếu bạn muốn sử dụng các tùy chỉnh khác nhau củaAPIClientđối với các lớp dịch vụ cụ thể, nhưng nếu bạn, vì một số lý do, không muốn có thêm bản sao hoặc bạn chắc chắn rằng bạn sẽ luôn sử dụng một phiên bản cụ thể (không có tùy chỉnh) của APIClient- hãy biến nó thành một singleton, nhưng KHÔNG, xin vui lòng 'T làm cho các lớp dịch vụ như singletons.

Sau đó, mỗi bộ điều khiển khung nhìn lại với DI tiêm lớp dịch vụ mà nó cần, gọi các phương thức dịch vụ phù hợp và tổng hợp kết quả của chúng với logic UI. Đối với tiêm phụ thuộc, tôi thích sử dụng BloodMagic hoặc Typhoon khung mạnh hơn . Tôi không bao giờ sử dụng singletons, APIManagerWhateverlớp Chúa hoặc những thứ sai trái khác. Bởi vì nếu bạn gọi lớp của bạn WhateverManager, điều này cho thấy bạn không biết mục đích của nó và đó là một lựa chọn thiết kế tồi . Singletons cũng là một mô hình chống, và trong hầu hết các trường hợp (trừ những trường hợp hiếm) là một giải pháp sai . Singleton chỉ nên được xem xét nếu cả ba tiêu chí sau đây được thỏa mãn:

  1. Quyền sở hữu của cá thể đơn lẻ có thể được chỉ định một cách hợp lý;
  2. Khởi tạo lười biếng là mong muốn;
  3. Truy cập toàn cầu không được quy định khác.

Trong trường hợp của chúng tôi, quyền sở hữu đối với một trường hợp duy nhất không phải là vấn đề và chúng tôi cũng không cần quyền truy cập toàn cầu sau khi chúng tôi chia người quản lý thần của mình thành các dịch vụ, vì hiện tại chỉ có một hoặc một số bộ điều khiển chuyên dụng cần một dịch vụ cụ thể (ví dụ: UserProfilenhu cầu của bộ điều khiển UserServices, v.v.) .

Chúng ta nên luôn luôn tôn trọng Snguyên tắc trong RẮN và sử dụng các mối quan tâm riêng biệt , vì vậy đừng đặt tất cả các phương thức dịch vụ và cuộc gọi mạng của bạn vào một lớp, vì điều đó thật điên rồ, đặc biệt là nếu bạn phát triển một ứng dụng doanh nghiệp lớn. Đó là lý do tại sao chúng ta nên xem xét phương pháp tiêm phụ thuộc và dịch vụ. Tôi coi cách tiếp cận này là hiện đại và hậu OO . Trong trường hợp này, chúng tôi chia ứng dụng của chúng tôi thành hai phần: logic điều khiển (bộ điều khiển và sự kiện) và tham số.

Một loại tham số sẽ là thông số dữ liệu thông thường của người dùng. Đó là những gì chúng ta chuyển qua các hàm, thao tác, sửa đổi, tồn tại, v.v. Đây là các thực thể, tập hợp, tập hợp, lớp tình huống. Các loại khác sẽ là các dịch vụ khác Đây là các lớp đóng gói logic nghiệp vụ, cho phép giao tiếp với các hệ thống bên ngoài, cung cấp quyền truy cập dữ liệu.

Dưới đây là một quy trình làm việc chung của kiến ​​trúc của tôi. Giả sử chúng ta có một FriendsViewController, hiển thị danh sách bạn bè của người dùng và chúng ta có một tùy chọn để xóa khỏi bạn bè. Tôi tạo một phương thức trong FriendsServiceslớp gọi là:

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

trong đó Friendmột đối tượng mô hình / miền (hoặc nó có thể chỉ là một Userđối tượng nếu chúng có các thuộc tính tương tự). Underhood phương pháp này phân tích Friendđể NSDictionarycác thông số JSON friend_id, name, surname, friend_request_idvà vân vân. Tôi luôn sử dụng thư viện Mantle cho kiểu soạn sẵn này và cho lớp mô hình của mình (phân tích ngược và chuyển tiếp, quản lý phân cấp đối tượng lồng nhau trong JSON, v.v.). Sau khi phân tích nó gọi APIClient DELETEphương pháp để thực hiện một yêu cầu REST thực tế và lợi nhuận Responsetrong RACSignalcho người gọi ( FriendsViewControllertrong trường hợp của chúng tôi) để hiển thị thông báo thích hợp cho người sử dụng hay bất cứ điều gì.

Nếu ứng dụng của chúng tôi là một ứng dụng rất lớn, chúng tôi phải phân tách logic của chúng tôi thậm chí rõ ràng hơn. Ví dụ, không phải lúc nào cũng tốt để trộn Repositoryhoặc mô hình logic với Servicemột. Khi tôi mô tả cách tiếp cận của mình, tôi đã nói rằng removeFriendphương thức đó nên nằm trong Servicelớp, nhưng nếu chúng ta sẽ có tính mô phạm hơn, chúng ta có thể nhận thấy rằng nó tốt hơn thuộc về nó Repository. Chúng ta hãy nhớ Kho lưu trữ là gì. Eric Evans đã cho nó một mô tả chính xác trong cuốn sách của mình [DDD]:

Kho lưu trữ đại diện cho tất cả các đối tượng của một loại nhất định dưới dạng một tập hợp khái niệm. Nó hoạt động như một bộ sưu tập, ngoại trừ với khả năng truy vấn phức tạp hơn.

Vì vậy, a Repositoryvề cơ bản là một mặt tiền sử dụng ngữ nghĩa kiểu Bộ sưu tập (Thêm, Cập nhật, Xóa) để cung cấp quyền truy cập vào dữ liệu / đối tượng. Đó là lý do tại sao khi bạn có một cái gì đó như: getFriendsList, getUserGroups, removeFriendbạn có thể đặt nó trong Repository, bởi vì bộ sưu tập giống như ngữ nghĩa được khá rõ ràng ở đây. Và mã như:

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

chắc chắn là một logic nghiệp vụ, bởi vì nó nằm ngoài các CRUDhoạt động cơ bản và kết nối hai đối tượng miền ( FriendRequest), đó là lý do tại sao nó nên được đặt trong Servicelớp. Ngoài ra tôi muốn thông báo: không tạo ra sự trừu tượng không cần thiết . Sử dụng tất cả các phương pháp tiếp cận một cách khôn ngoan. Bởi vì nếu bạn sẽ áp đảo ứng dụng của mình bằng sự trừu tượng, điều này sẽ làm tăng độ phức tạp ngẫu nhiên của nó và sự phức tạp gây ra nhiều vấn đề trong hệ thống phần mềm hơn bất kỳ thứ gì khác

Tôi mô tả cho bạn một ví dụ Objective-C "cũ" nhưng cách tiếp cận này có thể được điều chỉnh rất dễ dàng cho ngôn ngữ Swift với nhiều cải tiến hơn, bởi vì nó có nhiều tính năng hữu ích hơn và đường chức năng. Tôi rất khuyên bạn nên sử dụng thư viện này: Moya . Nó cho phép bạn tạo một APIClientlớp thanh lịch hơn (công việc của chúng tôi như bạn nhớ). Bây giờ, APIClientnhà cung cấp của chúng tôi sẽ là một loại giá trị (enum) với các phần mở rộng tuân thủ các giao thức và tận dụng việc khớp mẫu phá hủy. Swift enums + khớp mẫu cho phép chúng ta tạo các kiểu dữ liệu đại số như trong lập trình hàm cổ điển. Các dịch vụ siêu nhỏ của chúng tôi sẽ sử dụng APIClientnhà cung cấp được cải tiến này như trong cách tiếp cận Objective-C thông thường. Đối với lớp mô hình thay vì Mantlebạn có thể sử dụng thư viện ObjectMapperhoặc tôi thích sử dụng thư viện Argo thanh lịch và chức năng hơn .

Vì vậy, tôi đã mô tả phương pháp kiến ​​trúc chung của tôi, có thể được điều chỉnh cho bất kỳ ứng dụng nào, tôi nghĩ vậy. Có thể có nhiều cải tiến hơn, tất nhiên. Tôi khuyên bạn nên học lập trình chức năng, bởi vì bạn có thể hưởng lợi từ nó rất nhiều, nhưng đừng đi quá xa với nó. Loại bỏ trạng thái đột biến, chia sẻ, toàn cầu quá mức, tạo ra một mô hình miền bất biến hoặc tạo các hàm thuần túy mà không có tác dụng phụ bên ngoài, nói chung, là một thực tiễn tốt và Swiftngôn ngữ mới khuyến khích điều này. Nhưng hãy luôn nhớ rằng, quá tải mã của bạn với các mẫu chức năng thuần túy nặng nề, các cách tiếp cận lý thuyết danh mục là một ý tưởng tồi , bởi vì các nhà phát triển khác sẽ đọc và hỗ trợ mã của bạn, và họ có thể thất vọng hoặc đáng sợ vềprismatic profunctorsvà những thứ như vậy trong mô hình bất biến của bạn. Điều tương tự với ReactiveCocoa: đừng RACifymã của bạn quá nhiều , bởi vì nó có thể trở nên không thể đọc được rất nhanh, đặc biệt là đối với người mới. Sử dụng nó khi nó thực sự có thể đơn giản hóa các mục tiêu và logic của bạn.

Vì vậy, read a lot, mix, experiment, and try to pick up the best from different architectural approaches. Đó là lời khuyên tốt nhất tôi có thể cung cấp cho bạn.


Cũng là một cách tiếp cận thú vị và vững chắc. Cảm ơn.
MainstreamDeveloper00

1
@darksider Như tôi đã viết trong câu trả lời của tôi: "` Tôi không bao giờ sử dụng độc thân, lớp Thiên Chúa APIManagerWhatever hoặc thứ sai khác, vì singleton là một mô hình chống, và trong nhiều trường hợp (trừ những người hiếm) là một giải pháp sai. ". 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 lần `) trong mọi bộ điều khiển.
Oleksandr Karaberov

14
Xin chào @alexander. Bạn có dự án ví dụ nào trên GitHub không? Bạn mô tả cách tiếp cận rất thú vị. Cảm ơn. Nhưng tôi là người mới bắt đầu phát triển Objective-C. Và đối với tôi là khó hiểu một số khía cạnh. Có lẽ bạn có thể tải lên một số dự án thử nghiệm trên GitHub và cung cấp một liên kết?
Denis

1
Xin chào @AlexanderKaraberov, tôi hơi bối rối về lời giải thích của Cửa hàng mà bạn đã đưa ra. Giả sử tôi có 5 mô hình, mỗi mô hình tôi có 2 lớp, một lớp duy trì kết nối mạng và bộ nhớ đệm khác của các đối tượng. Bây giờ tôi nên có lớp Store riêng cho từng mô hình gọi chức năng của lớp mạng và bộ đệm hoặc một lớp Store duy nhất có tất cả chức năng cho từng mô hình, vì vậy bộ điều khiển luôn truy cập tệp duy nhất cho dữ liệu.
thiên thạch

1
@icodebuster dự án demo này đã giúp tôi hiểu nhiều khái niệm được nêu ở đây: github.com/darthpelo/NetworkLayerExample

31

Theo mục tiêu của câu hỏi này, tôi muốn mô tả phương pháp kiến ​​trúc của chúng tôi.

Phương pháp kiến ​​trúc

Kiến trúc ứng dụng iOS chung của chúng tôi đứng trên các mẫu sau: Các lớp dịch vụ , MVVM , Binding Data UI , Dependency Injection ; và mô hình lập trình phản ứng chức năng .

Chúng ta có thể cắt một ứng dụng tiêu dùng phải đối mặt thành các lớp logic sau:

  • hội,, tổ hợp
  • Mô hình
  • Dịch vụ
  • Lưu trữ
  • Quản lý
  • Điều phối viên
  • Giao diện người dùng
  • Cơ sở hạ tầng

Lớp hội là một điểm bootstrap của ứng dụng của chúng tôi. Nó chứa một thùng chứa Dependency Injection và khai báo các đối tượng của ứng dụng và các phụ thuộc của chúng. Lớp này cũng có thể chứa cấu hình của ứng dụng (url, khóa dịch vụ của bên thứ 3, v.v.). Đối với mục đích này, chúng tôi sử dụng thư viện Typhoon .

Lớp mô hình chứa các lớp mô hình miền, xác nhận hợp lệ, ánh xạ. Chúng tôi sử dụng thư viện Mantle để ánh xạ các mô hình của mình: nó hỗ trợ tuần tự hóa / giải tuần tự hóa thành JSONđịnh dạng và NSManagedObjectmô hình. Để xác nhận và đại diện hình thức mô hình của chúng tôi, chúng tôi sử dụng FXFormsFXModelValidation thư viện.

Lớp dịch vụ khai báo các dịch vụ mà chúng tôi sử dụng để tương tác với các hệ thống bên ngoài để gửi hoặc nhận dữ liệu được thể hiện trong mô hình miền của chúng tôi. Vì vậy, thông thường chúng tôi có các dịch vụ để liên lạc với API máy chủ (theo thực thể), dịch vụ nhắn tin (như PubNub ), dịch vụ lưu trữ (như Amazon S3), v.v. Về cơ bản, các dịch vụ bao bọc các đối tượng được cung cấp bởi SDK (ví dụ SDK PubNub) hoặc thực hiện giao tiếp của riêng họ Hợp lý. Đối với mạng chung, chúng tôi sử dụng thư viện AFNetworking .

Mục đích của lớp lưu trữ là để tổ chức lưu trữ dữ liệu cục bộ trên thiết bị. Chúng tôi sử dụng Core Data hoặc Realm cho việc này (cả hai đều có ưu và nhược điểm, quyết định sử dụng cái gì dựa trên thông số kỹ thuật cụ thể). Để thiết lập Dữ liệu lõi, chúng tôi sử dụng thư viện MDMCoreData và một loạt các lớp - kho lưu trữ - (tương tự như các dịch vụ) cung cấp quyền truy cập vào bộ nhớ cục bộ cho mọi thực thể. Đối với Realm, chúng tôi chỉ sử dụng các kho tương tự để có quyền truy cập vào bộ lưu trữ cục bộ.

Lớp người quản lý là nơi mà sự trừu tượng / trình bao bọc của chúng ta sinh sống.

Trong vai trò người quản lý có thể là:

  • Trình quản lý thông tin xác thực với các triển khai khác nhau (móc khóa, NSDefaults, ...)
  • Trình quản lý phiên hiện tại biết cách giữ và cung cấp phiên người dùng hiện tại
  • Capture Pipeline cung cấp quyền truy cập vào các thiết bị đa phương tiện (quay video, âm thanh, chụp ảnh)
  • BLE Manager cung cấp quyền truy cập vào các dịch vụ và thiết bị ngoại vi bluetooth
  • Trình quản lý vị trí địa lý
  • ...

Vì vậy, trong vai trò của người quản lý có thể là bất kỳ đối tượng nào thực hiện logic của một khía cạnh cụ thể hoặc mối quan tâm cần thiết cho ứng dụng làm việc.

Chúng tôi cố gắng tránh Singletons, nhưng lớp này là nơi họ sống nếu cần thiết.

Lớp điều phối viên cung cấp các đối tượng phụ thuộc vào các đối tượng từ các lớp khác (Dịch vụ, Lưu trữ, Mô hình) để kết hợp logic của chúng thành một chuỗi công việc cần thiết cho mô-đun nhất định (tính năng, màn hình, câu chuyện người dùng hoặc trải nghiệm người dùng). Nó thường xâu chuỗi các hoạt động không đồng bộ và biết cách phản ứng với các trường hợp thành công và thất bại của họ. Như một ví dụ bạn có thể tưởng tượng một tính năng nhắn tin và MessagingCoordinatorđối tượng tương ứng . Xử lý hoạt động gửi tin nhắn có thể trông như thế này:

  1. Xác thực tin nhắn (lớp mô hình)
  2. Lưu tin nhắn cục bộ (lưu trữ tin nhắn)
  3. Tải lên tệp đính kèm tin nhắn (dịch vụ amazon s3)
  4. Cập nhật trạng thái tin nhắn và tệp đính kèm và lưu tin nhắn cục bộ (lưu trữ tin nhắn)
  5. Nối tiếp thông điệp sang định dạng JSON (lớp mô hình)
  6. Xuất bản tin nhắn lên PubNub (dịch vụ PubNub)
  7. Cập nhật trạng thái tin nhắn và thuộc tính và lưu cục bộ (lưu trữ tin nhắn)

Trên mỗi bước trên, một lỗi được xử lý tương ứng.

Lớp UI bao gồm các lớp con sau:

  1. ViewModels
  2. ViewControllers
  3. Lượt xem

Để tránh Bộ điều khiển Chế độ xem Lớn, chúng tôi sử dụng mẫu MVVM và triển khai logic cần thiết cho bản trình bày UI trong ViewModels. Một ViewModel thường có các điều phối viên và người quản lý làm phụ thuộc. ViewModels được sử dụng bởi ViewControllers và một số loại Chế độ xem (ví dụ: các ô xem bảng). Chất kết dính giữa ViewControllers và ViewModels là Liên kết dữ liệu và mẫu Lệnh. Để có thể có keo đó, chúng tôi sử dụng thư viện ReactiveCocoa .

Chúng tôi cũng sử dụng ReactiveCocoa và RACSignalkhái niệm của nó như một giao diện và trả về loại giá trị của tất cả các điều phối viên, dịch vụ, phương thức lưu trữ. Điều này cho phép chúng tôi thực hiện các hoạt động chuỗi, chạy chúng song song hoặc ser seri và nhiều thứ hữu ích khác được cung cấp bởi ReactiveCocoa.

Chúng tôi cố gắng thực hiện hành vi UI của chúng tôi theo cách khai báo. Binding Data và Auto Layout giúp rất nhiều để đạt được mục tiêu này.

Lớp cơ sở hạ tầng chứa tất cả các trợ giúp, tiện ích mở rộng, tiện ích cần thiết cho công việc ứng dụng.


Cách tiếp cận này hoạt động tốt cho chúng tôi và những loại ứng dụng chúng tôi thường xây dựng. Nhưng bạn nên hiểu rằng, đây chỉ là một cách tiếp cận chủ quan cần được điều chỉnh / thay đổi cho mục đích cụ thể của nhóm.

Hy vọng điều này sẽ giúp bạn!

Ngoài ra, bạn có thể tìm thêm thông tin về quy trình phát triển iOS trong bài đăng trên blog này Phát triển iOS dưới dạng Dịch vụ


Bắt đầu thích kiến ​​trúc này vài tháng trước, cảm ơn Alex vì đã chia sẻ nó! Tôi muốn dùng thử với RxSwift trong tương lai gần!
ingaham

18

Bởi vì tất cả các ứng dụng iOS đều khác nhau, tôi nghĩ có nhiều cách tiếp cận khác nhau để xem xét, nhưng tôi thường đi theo cách này:
Tạo lớp trình quản lý trung tâm (singleton) để xử lý tất cả các yêu cầu API (thường được đặt tên là APICransicator) và mọi phương thức cá thể là một lệnh gọi API . Và có một phương pháp trung tâm (không công khai):

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

Để ghi lại, tôi sử dụng 2 thư viện / khung chính là ReactiveCocoa và AFNetworking. ReactiveCocoa xử lý các phản hồi mạng không đồng bộ một cách hoàn hảo, bạn có thể làm (sendNext:, sendError:, v.v.).
Phương thức này gọi API, nhận kết quả và gửi chúng thông qua RAC ở định dạng 'thô' (như NSArray những gì AFNetworking trả về).
Sau đó, một phương thức getStuffList:được gọi là phương thức trên đăng ký tín hiệu của nó, phân tích dữ liệu thô thành các đối tượng (với một cái gì đó giống như Motis) và gửi từng đối tượng cho người gọi ( getStuffList:và các phương thức tương tự cũng trả về tín hiệu mà bộ điều khiển có thể đăng ký ).
Bộ điều khiển đã đăng ký nhận các đối tượng theo subscribeNext:khối và xử lý chúng.

Tôi đã thử nhiều cách trong các ứng dụng khác nhau nhưng cách này hiệu quả nhất vì vậy tôi đã sử dụng ứng dụng này trong một vài ứng dụng gần đây, nó phù hợp với cả các dự án nhỏ và lớn và dễ dàng mở rộng và duy trì nếu cần sửa đổi.
Hy vọng điều này có ích, tôi muốn nghe ý kiến ​​của người khác về cách tiếp cận của tôi và có thể cách người khác nghĩ điều này có thể được cải thiện.


2
Cảm ơn câu trả lời +1. Cách tiếp cận tốt. Tôi để lại câu hỏi. Có thể chúng ta sẽ có một số cách tiếp cận khác từ các nhà phát triển khác.
MainstreamDeveloper00

1
Tôi thích một biến thể của cách tiếp cận này - Tôi sử dụng trình quản lý API trung tâm, đảm nhiệm các cơ chế giao tiếp với API. Tuy nhiên tôi cố gắng làm cho tất cả các chức năng được hiển thị trên các đối tượng mô hình của tôi. Các mô hình sẽ cung cấp các phương thức như + (void)getAllUsersWithSuccess:(void(^)(NSArray*))success failure:(void(^)(NSError*))failure;- (void)postWithSuccess:(void(^)(instancetype))success failure:(void(^)(NSError*))failure;thực hiện các bước chuẩn bị cần thiết và sau đó gọi qua trình quản lý API.
jsadler

1
Cách tiếp cận này rất đơn giản, nhưng khi số lượng API đang tăng lên, việc duy trì trình quản lý API đơn lẻ trở nên khó khăn hơn. Và mọi API được thêm mới sẽ liên quan đến người quản lý, bất kể API này thuộc về mô-đun nào. Hãy thử sử dụng github.com/kevin0571/STNetTaskQueue để quản lý các yêu cầu API.
Kevin

Khác với lý do tại sao bạn quảng cáo thư viện của mình càng xa giải pháp của tôi và phức tạp hơn nhiều, tôi đã thử phương pháp này trên vô số dự án cả nhỏ và lớn như đã đề cập và tôi đã sử dụng chính xác tương tự kể từ khi tôi viết câu trả lời này. Với các quy ước đặt tên thông minh, không khó để duy trì.
Rickye

8

Trong tình huống của tôi, tôi thường sử dụng thư viện ResKit để thiết lập lớp mạng. Nó cung cấp phân tích cú pháp dễ sử dụng. Nó làm giảm nỗ lực của tôi trong việc thiết lập ánh xạ cho các phản hồi và nội dung khác nhau.

Tôi chỉ thêm một số mã để thiết lập ánh xạ tự động. Tôi định nghĩa lớp cơ sở cho các mô hình của mình (không phải giao thức vì có nhiều mã để kiểm tra xem một số phương thức có được thực hiện hay không và ít mã hơn trong chính các mô hình):

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

Mối quan hệ là các đối tượng đại diện cho các đối tượng lồng nhau trong phản ứng:

Mối quan hệObject.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

Mối quan hệObject.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

Sau đó, tôi đang thiết lập ánh xạ cho RestKit như thế này:

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

Một số ví dụ về triển khai MappableEntry:

Người dùng

@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

Người dùng

@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

Bây giờ về gói Yêu cầu:

Tôi có tệp tiêu đề với định nghĩa khối, để giảm độ dài dòng trong tất cả các lớp APIRequest:

APICallbacks.h

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

Và ví dụ về lớp APIRequest của tôi mà tôi đang sử dụng:

Đăng nhậpAPI.h

@interface LoginAPI : NSObject

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

@end

Đăng nhậpAPI.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

Và tất cả những gì bạn cần làm trong mã, chỉ cần khởi tạo đối tượng API và gọi nó bất cứ khi nào bạn cần:

Một sốViewControll.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

Mã của tôi không hoàn hảo, nhưng thật dễ dàng để đặt một lần và sử dụng cho các dự án khác nhau. Nếu nó thú vị với bất cứ ai, mb tôi có thể dành thời gian và tạo ra một giải pháp phổ quát cho nó ở đâu đó trên GitHub và CocoaPods.


7

Theo tôi, tất cả các kiến ​​trúc phần mềm được điều khiển bởi nhu cầu. Nếu điều này là cho mục đích học tập hoặc cá nhân, sau đó quyết định mục tiêu chính và điều đó thúc đẩy kiến ​​trúc. Nếu đây là một công việc cho thuê, thì nhu cầu kinh doanh là tối quan trọng. Bí quyết là đừng để những thứ bóng bẩy làm bạn mất tập trung khỏi nhu cầu thực sự. Tôi thấy điều này khó để làm. Luôn có những thứ sáng bóng mới xuất hiện trong doanh nghiệp này và rất nhiều trong số chúng không hữu ích, nhưng bạn không thể luôn nói trước điều đó. Tập trung vào nhu cầu và sẵn sàng từ bỏ những lựa chọn tồi nếu bạn có thể.

Ví dụ, gần đây tôi đã làm một nguyên mẫu nhanh chóng của một ứng dụng chia sẻ ảnh cho một doanh nghiệp địa phương. Vì nhu cầu kinh doanh là phải làm một cái gì đó nhanh chóng và bẩn thỉu, kiến ​​trúc cuối cùng là một số mã iOS để bật camera và một số mã mạng được gắn vào Nút Gửi đã tải hình ảnh lên cửa hàng S3 và ghi vào miền SimpleDB. Mã này không đáng kể và chi phí tối thiểu và khách hàng có một bộ sưu tập ảnh có thể mở rộng có thể truy cập trên web bằng các cuộc gọi REST. Giá rẻ và ngu ngốc, ứng dụng có rất nhiều lỗi và thỉnh thoảng sẽ khóa UI, nhưng sẽ lãng phí khi làm nhiều hơn cho một nguyên mẫu và nó cho phép họ triển khai cho nhân viên của mình và tạo ra hàng ngàn hình ảnh thử nghiệm một cách dễ dàng mà không cần hiệu năng hoặc khả năng mở rộng mối quan tâm. Kiến trúc crappy, nhưng nó phù hợp với nhu cầu và chi phí hoàn hảo.

Một dự án khác liên quan đến việc thực hiện một cơ sở dữ liệu an toàn cục bộ đồng bộ hóa với hệ thống công ty trong nền khi mạng có sẵn. Tôi đã tạo một trình đồng bộ hóa nền sử dụng RestKit vì nó dường như có mọi thứ tôi cần. Nhưng tôi đã phải viết rất nhiều mã tùy chỉnh cho RestKit để đối phó với JSON bình dị mà tôi có thể thực hiện nhanh hơn bằng cách viết JSON của riêng mình cho các phép biến đổi CoreData. Tuy nhiên, khách hàng muốn mang ứng dụng này vào nhà và tôi cảm thấy RestKit sẽ giống với các khung mà họ đã sử dụng trên các nền tảng khác. Tôi chờ đợi để xem đó là một quyết định tốt.

Một lần nữa, vấn đề với tôi là tập trung vào nhu cầu và để điều đó quyết định kiến ​​trúc. Tôi cố gắng như địa ngục để tránh sử dụng các gói của bên thứ ba vì chúng mang lại chi phí chỉ xuất hiện sau khi ứng dụng đã hoạt động được một thời gian. Tôi cố gắng tránh thực hiện phân cấp lớp vì chúng hiếm khi trả hết. Nếu tôi có thể viết một cái gì đó trong một khoảng thời gian hợp lý thay vì chấp nhận một gói không phù hợp hoàn hảo, thì tôi sẽ làm nó. Mã của tôi được cấu trúc tốt để gỡ lỗi và nhận xét thích hợp, nhưng các gói của bên thứ ba hiếm khi được. Như đã nói, tôi thấy Mạng AF quá hữu ích để bỏ qua và có cấu trúc tốt, được nhận xét tốt và được duy trì và tôi sử dụng nó rất nhiều! RestKit bao gồm rất nhiều trường hợp phổ biến, nhưng tôi cảm thấy như mình đã đánh nhau khi sử dụng nó, và hầu hết các nguồn dữ liệu tôi gặp phải có đầy đủ các vấn đề và vấn đề được xử lý tốt nhất với mã tùy chỉnh. Trong vài ứng dụng gần đây của tôi, tôi chỉ sử dụng các trình chuyển đổi JSON tích hợp và viết một vài phương thức tiện ích.

Một mẫu tôi luôn sử dụng là loại bỏ các cuộc gọi mạng ra khỏi luồng chính. 4-5 ứng dụng gần đây tôi đã hoàn thành thiết lập tác vụ hẹn giờ nền bằng cách sử dụng Clark_source_create thường xuyên thức dậy và thực hiện các tác vụ mạng khi cần. Bạn cần thực hiện một số công việc an toàn luồng và đảm bảo rằng mã sửa đổi UI được gửi đến luồng chính. Nó cũng giúp thực hiện việc khởi động / khởi tạo của bạn theo cách mà người dùng không cảm thấy gánh nặng hoặc trì hoãn. Cho đến nay điều này đã được làm việc khá tốt. Tôi đề nghị xem xét những điều này.

Cuối cùng, tôi nghĩ rằng khi chúng ta làm việc nhiều hơn và khi HĐH phát triển, chúng ta có xu hướng phát triển các giải pháp tốt hơn. Tôi đã mất nhiều năm để vượt qua niềm tin của mình rằng tôi phải tuân theo các mẫu và thiết kế mà người khác tuyên bố là bắt buộc. Nếu tôi đang làm việc trong bối cảnh đó là một phần của tôn giáo địa phương, ahem, ý tôi là các thực hành kỹ thuật tốt nhất của bộ phận, sau đó tôi theo phong tục thư, đó là những gì họ đang trả tiền cho tôi. Nhưng tôi hiếm khi thấy rằng theo các thiết kế và mẫu cũ hơn là giải pháp tối ưu. Tôi luôn cố gắng xem xét giải pháp thông qua lăng kính của nhu cầu kinh doanh và xây dựng kiến ​​trúc để phù hợp với nó và giữ mọi thứ đơn giản nhất có thể. Khi tôi cảm thấy như không có đủ ở đó, nhưng mọi thứ hoạt động chính xác, thì tôi đang đi đúng hướng.


4

Tôi sử dụng cách tiếp cận mà tôi đã nhận được từ đây: https://github.com/Constantine-Fry/Fiến-API-v2 . Tôi đã viết lại thư viện đó trong Swift và bạn có thể thấy cách tiếp cận kiến ​​trúc từ các phần của mã này:

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

Về cơ bản, có lớp con NSOperation tạo NSURLRequest, phân tích phản hồi JSON và thêm khối gọi lại với kết quả vào hàng đợi. Lớp API chính xây dựng NSURLRequest, khởi tạo lớp con NSOperation đó và thêm nó vào hàng đợi.


3

Chúng tôi sử dụng một vài cách tiếp cận tùy thuộc vào tình huống. Đối với hầu hết mọi thứ, AFNetworking là cách tiếp cận đơn giản và mạnh mẽ nhất ở chỗ bạn có thể đặt tiêu đề, tải lên dữ liệu nhiều phần, sử dụng GET, POST, PUT & DELETE và có một loạt các danh mục bổ sung cho UIKit, ví dụ cho phép bạn đặt hình ảnh từ một url. Trong một ứng dụng phức tạp với rất nhiều cuộc gọi, đôi khi chúng ta trừu tượng hóa nó thành một phương thức tiện lợi của riêng chúng ta, nó sẽ giống như:

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

Tuy nhiên, có một vài tình huống trong đó AFNetworking không phù hợp, chẳng hạn như nơi bạn đang tạo khung hoặc thành phần thư viện khác vì AFNetworking có thể đã ở trong một cơ sở mã khác. Trong tình huống này, bạn sẽ sử dụng NSMutableURLRequest hoặc nội tuyến nếu bạn đang thực hiện một cuộc gọi hoặc được trừu tượng hóa thành một lớp yêu cầu / phản hồi.


Đối với tôi đây là câu trả lời tốt nhất và rõ ràng nhất, chúc mừng. "Nó đơn giản mà". @martin, cá nhân chúng tôi chỉ sử dụng NSMutableURLRequest mọi lúc; Có lý do thực sự nào để sử dụng AFNetworking không?
Fattie

AFNetworking chỉ là thuận tiện thực sự. Đối với tôi thành công và các khối thất bại tạo ra nếu có giá trị trong khi nó làm cho mã dễ quản lý hơn. Tôi đồng ý rằng đôi khi nó là quá mức cần thiết.
Martin

Một điểm tuyệt vời trên các khối, cảm ơn vì điều đó. Tôi đoán, bản chất cụ thể của tất cả sẽ thay đổi với Swift.
Fattie

2

Tôi tránh singletons khi thiết kế các ứng dụng của tôi. Họ là một điển hình cho nhiều người nhưng tôi nghĩ bạn có thể tìm thấy các giải pháp thanh lịch hơn ở nơi khác. Thông thường, những gì tôi làm là xây dựng các thực thể của mình trong CoreData và sau đó đặt mã REST của tôi vào danh mục NSManagedObject. Ví dụ: nếu tôi muốn tạo và POST một Người dùng mới, tôi sẽ làm điều này:

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

Tôi sử dụng RESTKit cho ánh xạ đối tượng và khởi tạo nó khi khởi động. Tôi thấy việc định tuyến tất cả các cuộc gọi của bạn thông qua một đơn lẻ là một sự lãng phí thời gian và thêm rất nhiều bản tóm tắt không cần thiết.

Trong NSManagedObject + Tiện ích mở rộng.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];
}

Trong NSManagedObject + Mạng.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];
}

Tại sao thêm các lớp trợ giúp bổ sung khi bạn có thể mở rộng chức năng của một lớp cơ sở chung thông qua các danh mục?

Nếu bạn quan tâm đến thông tin chi tiết hơn về giải pháp của tôi, hãy cho tôi biết. Tôi rất vui được chia sẻ.


3
Chắc chắn sẽ quan tâm đến việc đọc về phương pháp này chi tiết hơn trong một bài đăng trên blog.
Danyal Aytekin


0

Từ quan điểm thiết kế hoàn toàn đẳng cấp, bạn thường sẽ có một cái gì đó như thế này:

  • Bộ điều khiển xem của bạn kiểm soát một hoặc nhiều chế độ xem
  • Lớp mô hình dữ liệu - Nó thực sự phụ thuộc vào số lượng thực thể riêng biệt mà bạn đang xử lý và chúng có liên quan như thế nào.

    Ví dụ: nếu bạn có một mảng các mục được hiển thị trong bốn biểu diễn khác nhau (danh sách, biểu đồ, biểu đồ, v.v.), bạn sẽ có một lớp mô hình dữ liệu cho danh sách các mục, thêm một mục cho một mục. Các danh sách các lớp item sẽ được chia sẻ bởi bốn bộ điều khiển xem - tất cả các trẻ em của một bộ điều khiển thanh tab hoặc một bộ điều khiển nav.

    Các lớp mô hình dữ liệu sẽ trở nên tiện dụng trong việc không chỉ hiển thị dữ liệu mà còn tuần tự hóa chúng trong đó mỗi lớp có thể hiển thị định dạng tuần tự hóa của riêng chúng thông qua các phương thức xuất JSON / XML / CSV (hoặc bất cứ thứ gì khác).

  • Điều quan trọng là phải hiểu rằng bạn cũng cần các lớp trình tạo yêu cầu API ánh xạ trực tiếp với các điểm cuối API REST của bạn. Giả sử bạn có một API đăng nhập người dùng - vì vậy lớp trình tạo API đăng nhập của bạn sẽ tạo tải trọng POST JSON cho api đăng nhập. Trong một ví dụ khác, lớp trình tạo yêu cầu API cho danh sách các mục danh mục API sẽ tạo chuỗi truy vấn GET cho api tương ứng và thực hiện truy vấn REST GET.

    Các lớp trình tạo yêu cầu API này thường sẽ nhận dữ liệu từ các bộ điều khiển xem và cũng chuyển lại cùng một dữ liệu để xem các bộ điều khiển để cập nhật UI / các hoạt động khác. Xem bộ điều khiển sau đó sẽ quyết định cách cập nhật các đối tượng Mô hình Dữ liệu với dữ liệu đó.

  • Cuối cùng, trái tim của trình khách REST - lớp trình tải dữ liệu API không biết đến tất cả các loại yêu cầu API mà ứng dụng của bạn thực hiện. Lớp này nhiều khả năng sẽ là một người độc thân, nhưng như những người khác đã chỉ ra, nó không phải là một người độc thân.

    Lưu ý rằng liên kết chỉ là một triển khai điển hình và không xem xét các tình huống như phiên, cookie, v.v., nhưng nó đủ để giúp bạn đi mà không cần sử dụng bất kỳ khuôn khổ bên thứ 3 nào.


0

Câu hỏi này đã có rất nhiều câu trả lời xuất sắc và rộng rãi, nhưng tôi cảm thấy tôi phải đề cập đến nó vì không có ai khác.

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

Nó được tạo bởi cùng một người với AFNetworking, nhưng được thiết kế trực tiếp hơn với Swift.


0

Tôi nghĩ hiện tại dự án trung bình sử dụng kiến ​​trúc MVVM và dự án lớn sử dụng kiến ​​trúc VIPER và cố gắng đạt được

  • Lập trình định hướng giao thức
  • Mẫu thiết kế phần mềm
  • Nguyên tắc bán
  • Lập trình chung
  • Đừng lặp lại chính mình (DRY)

Và phương pháp kiến ​​trúc để xây dựng các ứng dụng mạng iOS (ứng dụng khách REST)

Phân tách mối quan tâm cho mã sạch và dễ đọc tránh trùng lặp:

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

đảo ngược phụ thuộc

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

Chịu trách nhiệm chính:

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

Bạn sẽ tìm thấy ở đây là kiến trúc MVVM của GitHub với dự án API Swift còn lại


0

Trong kỹ thuật phần mềm di động, được sử dụng rộng rãi nhất là các mẫu Clean Architecture + MVVM và Redux.

Kiến trúc sạch + MVVM bao gồm 3 lớp: Miền, Trình bày, Lớp dữ liệu. Trường hợp lớp trình bày và lớp lưu trữ dữ liệu phụ thuộc vào lớp miền:

Presentation Layer -> Domain Layer <- Data Repositories Layer

Và Lớp trình bày bao gồm ViewModels và Lượt xem (MVVM):

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

Trong bài viết này, có một mô tả chi tiết hơn về Kiến trúc sạch + MVVM https: // tech. ERIC.com/clean-arch architecture-and-rosvm-on-ios-c9d167d9f5b3

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.