iOS网络层架构

时间:2017-08-06 20:44:52

标签: ios oop networking mvvm rx-swift

发布原因

有许多不同的解决方案&关于如何构建适当的网络层的示例,但每个应用程序都有不同的约束,并且基于权衡取决于设计决策,让我不确定我编写的代码的质量。如果我的代码中存在任何Anti-Patternsredundancies或不完整的错误解决方案,我忽略了或者只是缺乏要解决的知识,做批评。这是一个我想添加到我的投资组合中的项目,所以我在这里发布它以了解它,并提供一些建议/提示

感谢您提前的时间!

我认为我的网络层的一些特征可能引起人们的注意:

Method包含GETALL个案例,用于指示必须提取的数据列表。我没有在我读过的任何开源代码中看到过这个。这是代码味吗?

enum Method {
    case GET
    /// Indicates how JSON response should be handled differently to abastract a list of entities
    case GETALL
    case PUT
    case DELETE
}

我已经做到了,所以每个Swift实体都符合JSONable协议,这意味着它可以用json初始化并转换为json。

protocol JSONable {
    init?(json: [String: AnyObject])
    func toJSON() -> Data?
}

JSONable在实践中使用我的一个实体:

struct User {
    var id: String
    var name: String
    var location: String
    var rating: Double
    var keywords: NSArray
    var profileImageUrl: String
}

extension User: JSONable {
    init?(json: [String : AnyObject]) {
        guard let id = json[Constant.id] as? String, let name = json[Constant.name] as? String, let location = json[Constant.location] as? String, let rating = json[Constant.rating] as? Double, let keywords = json[Constant.keywords] as? NSArray, let profileImageUrl = json[Constant.profileImageUrl] as? String else {
            return nil
        }
        self.init(id: id, name: name, location: location, rating: rating, keywords: keywords, profileImageUrl: profileImageUrl)
    }

    func toJSON() -> Data? {

        let data: [String: Any] = [Constant.id: id, Constant.name: name, Constant.location: location, Constant.rating: rating, Constant.keywords: keywords, Constant.profileImageUrl: profileImageUrl]
        let jsonData = try? JSONSerialization.data(withJSONObject: data, options: [])
        return jsonData
    }
}

这使我可以在检索JSON响应后使用generics初始化我的客户端中的所有实体 - FirebaseAPI。我也没有在我读过的代码中看到过这种技术。

在下面的代码中,请注意如何实现GETALL以展平JSON对象列表。我应该这么做吗?有没有更好的方法来处理任何类型的Json结构响应?

AND 实体一般初始化,并以Observable(使用RxSwift)返回。

你感觉到任何代码味道?

/// Responsible for Making actual API requests & Handling response
    /// Returns an observable object that conforms to JSONable protocol.
    /// Entities that confrom to JSONable just means they can be initialized with json & transformed from swift to JSON.
    func rx_fireRequest<Entity: JSONable>(_ endpoint: FirebaseEndpoint, ofType _: Entity.Type ) -> Observable<[Entity]> {

        return Observable.create { [weak self] observer in

            self?.session.dataTask(with: endpoint.request, completionHandler: { (data, response, error) in

                /// Parse response from request.
                let parsedResponse = Parser(data: data, response: response, error: error)
                    .parse()

                switch parsedResponse {

                case .error(let error):
                    observer.onError(error)
                    return

                case .success(let data):
                    var entities = [Entity]()
                    switch endpoint.method { 

                    /// Flatten JSON strucuture to retrieve a list of entities.
                    /// Denoted by 'GETALL' method.
                    case .GETALL:

                        /// Key (underscored) is unique identifier for each entity
                        /// value is k/v pairs of entity attributes.
                        for (_, value) in data {
                            if let value = value as? [String: AnyObject], let entity = Entity(json: value) {
                                entities.append(entity)
                            }
                        }
                        /// Force downcast for generic type inference.
                        observer.onNext(entities as! [Entity])
                        //observer.onCompleted()

                    /// All other methods return JSON that can be used to initialize JSONable entities 
                    default:
                        if let entity = Entity(json: data) {
                        observer.onNext([entity] as! [Entity])
                        //observer.onCompleted()
                    } else {
                        observer.onError(NetworkError.initializationFailure)
                        }
                    }
                }
            }).resume()
            return Disposables.create()
        }
    }
}

我像这样管理不同的端点:

enum FirebaseEndpoint {
    case saveUser(data: [String: AnyObject])
    case fetchUser(id: String)
    case removeUser(id: String)

    case saveItem(data: [String: AnyObject])
    case fetchItem(id: String)
    case fetchItems
    case removeItem(id: String)

    case saveMessage(data: [String: AnyObject])
    case fetchMessages(chatroomId: String)
    case removeMessage(id: String)
}


extension FirebaseEndpoint: Endpoint {

    var base: String {
        // Add this as a constant to APP Secrts struct & dont include secrets file when pushed to github.
        return "https://AppName.firebaseio.com"
    }

    var path: String {
        switch self {
        case .saveUser(let data): return "/\(Constant.users)/\(data[Constant.id])"
        case .fetchUser(let id): return "/\(Constant.users)/\(id)"
        case .removeUser(let id): return "/\(Constant.users)/\(id)"

        case .saveItem(let data): return "/\(Constant.items)/\(data[Constant.id])"
        case .fetchItem(let id): return "/\(Constant.items)/\(id)"
        case .fetchItems: return "/\(Constant.items)"
        case .removeItem(let id): return "/\(Constant.items)/\(id)"

        case .saveMessage(let data): return "/\(Constant.messages)/\(data[Constant.id])"
        case .fetchMessages(let chatroomId): return "\(Constant.messages)/\(chatroomId)"
        case .removeMessage(let id): return "/\(Constant.messages)/\(id)"
        }
    }

    var method: Method {
        switch self {
        case .fetchUser, .fetchItem: return .GET
        case .fetchItems, .fetchMessages: return .GETALL
        case .saveUser, .saveItem, .saveMessage: return .PUT
        case .removeUser, .removeItem, .removeMessage: return .DELETE
        }
    }

    var body: [String : AnyObject]? {
        switch self {
        case .saveItem(let data), .saveUser(let data), .saveMessage(let data): return data
        default: return nil
        }
    }
}

最后,我希望有专业人士看一眼,我是如何使用MVVM的。我从视图模型中发出所有网络请求,看起来像这样:

struct SearchViewModel {

    // Outputs
    var collectionItems: Observable<[Item]>
    var error: Observable<Error>

    init(controlValue: Observable<Int>, api: FirebaseAPI, user: User) {

        let serverItems = controlValue
            .map { ItemCategory(rawValue: $0) }
            .filter { $0 != nil }.map { $0! }
            .flatMap { api.rx_fetchItems(for: user, category: $0)
                .materialize()
            }
            .filter { !$0.isCompleted }
            .shareReplayLatestWhileConnected()

        collectionItems = serverItems.filter { $0.element != nil }.dematerialize()
        error = serverItems.filter { $0.error != nil }.map { $0.error! }
    }

}

为了以更具表现力,形式化的方式调用api请求,我可以在api.rx_fetchItems(for:)上方flatmap内拨打extend,因为我FirebaseAPI FetchItemsAPI符合extension FirebaseAPI: FetchItemsAPI { // MARK: Fetch Items Protocol func rx_fetchItems(for user: User, category: ItemCategory) -> Observable<[Item]> { // fetched items returns all items in database as Observable<[Item]> let fetchedItems = rx_fireRequest(.fetchItems, ofType: Item.self) switch category { case .Local: let localItems = fetchedItems .flatMapLatest { (itemList) -> Observable<[Item]> in return self.rx_localItems(user: user, items: itemList) } return localItems case .RecentlyAdded: // Compare current date to creation date of item. If its within 24 hours, It makes the cut. let recentlyAddedItems = fetchedItems .flatMapLatest { (itemList) -> Observable<[Item]> in return self.rx_recentlyAddedItems(items: itemList) } return recentlyAddedItems case .Trending: let trendingItems = fetchedItems .flatMapLatest { (itemList) -> Observable<[Item]> in return self.rx_trendingItems(items: itemList) } return trendingItems default: let stubItem = Item(id: "DEFAULT", createdById: "createdBy", creationDate: 1.3, expirationDate: 2.4, title: "title", price: 2, info: "info", imageUrl: "url", bidCount: 4, location: "LA") return Observable.just([stubItem]) } } // MARK: Helper Methods private func rx_localItems(user: User, items: [Item]) -> Observable<[Item]> { return Observable<[Item]>.create { observer in observer.onNext(items.filter { $0.location == user.location }) // LA Matches stubs in db return Disposables.create() } } func rx_recentlyAddedItems(items: [Item]) -> Observable<[Item]> { return Observable<[Item]>.create { observer in let recentItems = items .filter { let now = Date(timeIntervalSinceReferenceDate: 0) let creationDate = Date(timeIntervalSince1970: $0.creationDate) if let hoursAgo = now.offset(from: creationDate, units: [.hour], maxUnits: 1) { return Int(hoursAgo)! < 24 } else { return false } } observer.onNext(recentItems) return Disposables.create() } } func rx_trendingItems(items: [Item]) -> Observable<[Item]> { return Observable<[Item]>.create { observer in observer.onNext(items.filter { $0.bidCount > 8 }) return Disposables.create() } } } 。对于大多数其他请求,我可能必须遵循相同的模式。

android:layout_alignParentBottom="true"

我正在尝试遵循SOLID原则,并使用RxSWift + MVVM升级,因此我仍然不确定最佳的OOP设计是否适用于干净,可维护的代码。

0 个答案:

没有答案
相关问题