Most apps need to request data from the server. Generally speaking, an APP only needs to design a package of network requests based on a background.

However, in the development work, an APP may need to access the functions of other production lines, and even the interfaces returned by the same background may not be applicable to the same

Parsing rules. When this happens, MJExtension, ObjectMapper, HandyJSON and other tools for model transformation come into being.

Model transformation

When we use these tools, we often need to have a certain type in order to complete the mapping of data to model. At this stage, it’s usually here

Sample design model:

class BaseRespose {
    var code: Int?
    var msg: String?
}

class UserInfo {
    var name: String?
    var age: Int?
}

class UserInfoResponse: BaseRespose {
    var data: UserInfo?
}
Copy the code

To design a Network:

network<T: BaseResponse>(api: String, success((data: T) - > ()))Copy the code

At this stage, we use generics to constrain the model classes. Makes any class or knot that inherits BaseResponse or implements the BaseResponse protocol

The construct can be successfully parsed. In this way, it seems that all data structures can be parsed, but it is important to note that the Network at this time

Only BaseRespose can be handled, which means that the Network can only handle one type.

For example, when a new interface is added and the parsing rules of code or MSG change, the current Network becomes unusable.

Of course, in this example, there are ways, such as:

class BaseRespose {}

class UserInfo {
    var name: String?
    var age: Int?
}

class UserInfoResponse: BaseRespose {
    var code: Int?
    var msg: String?
    var data: UserInfo?
}
Copy the code

BaseRespose does not handle any parsing implementation, relying on the identified type UserInfoResponse for parsing, but then you will find that it is impossible to parse from

The Network gets the code internally to determine the request status. Uniform processing and, second, redundant code.

In this case, Network request methods can only be added to adapt to the two different structures.

Also, besides adding request methods, you can’t make them return data, string, JSON, etc.

Second, you can’t use structs to declare models that rely on inheritance.

Therefore, a componentized Network, in order to adapt to different backgrounds or different data structures, should be able to resolve any incoming type, and

At the same time, the request results can be uniformly processed within the Network. Classes and structures should be supported.

JingDataNetwork

Let’s try to solve and discuss the above problem with an already implemented network request component. This component consists of the following four parts.

. ├ ─ ─ JingDataNetworkError. Swift ├ ─ ─ JingDataNetworkManager. Swift ├ ─ ─ JingDataNetworkResponseHandler. Swift └ ─ ─ JingDataNetworkSequencer.swiftCopy the code

In this component, we rely on the following excellent open source tools, which are no longer detailed:

  ## Network request
  s.dependency 'Moya'.'~ > 11.0' 	
  # # response type
  s.dependency 'RxSwift'.'~ > 4.0'
  s.dependency 'RxCocoa'.'~ > 4.0'
Copy the code

How to set for different background

For each kind of background, or the same backend return different Response of the structure, we regard it as a kind of Response, through JingDataNetworkResponseHandler to process a Response.

public protocol JingDataNetworkResponseHandler {
    associatedtype Response
    var response: Response? { set get }
    var networkManager: Manager { get }
    var plugins: [PluginType] { get }
    func makeResponse(_ data: Data) throws -> Response
    func makeCustomJingDataNetworkError(a) -> JingDataNetworkError?
    func handleJingDataNetworkError(_ error: JingDataNetworkError)
    init()}public extension JingDataNetworkResponseHandler {
    var networkManager: Manager {
        let configuration = URLSessionConfiguration.default
        configuration.httpAdditionalHeaders = Manager.defaultHTTPHeaders
        configuration.timeoutIntervalForRequest = 15
        configuration.timeoutIntervalForResource = 60
        let manager = Manager(configuration: configuration)
        manager.startRequestsImmediately = false
        return manager
    }
    var plugins: [PluginType] {
        return[]}}Copy the code

Each ResponseHandler requires the ability to provide networkManager, plugins, and basic network requests. Capable of completing Data to Response mapping, throwing custom errors and handling global errors.

Plugins are Moya plug-in mechanism, which can realize log, cache and other functions.

How to implement the mapping from Data to Response

Implement JingDataNetworkResponseHandler agreement that how to complete parsing becomes quite clear.

struct BaseResponseHandler: JingDataNetworkResponseHandler {
    
    var response: String?
    
    func makeResponse(_ data: Data) throws -> String {
         return String.init(data: data, encoding: .utf8) ?? "unknow"
    }
    
    func makeCustomJingDataNetworkError(a) -> JingDataNetworkError? {
        return nil
    }
    
    func handleJingDataNetworkError(_ error: JingDataNetworkError){}}Copy the code

How do you implement parsing arbitrary types

See here you may have a doubt, the Response for every type need to implement a JingDataNetworkResponseHandler? Is it too much work?

That’s right. This problem can be solve through JingDataNetworkResponseHandler generics to:

struct BaseTypeResponseHandler<R> :JingDataNetworkResponseHandler {
    
    var response: R?
    
    func makeResponse(_ data: Data) throws -> R {
        if R.Type.self= =String.Type.self {
            throw JingDataNetworkError.parser(type: "\(R.Type.self)")}else if R.Type.self= =Data.Type.self {
            throw JingDataNetworkError.parser(type: "\(R.Type.self)")}else if R.Type.self= =UIImage.Type.self {
            throw JingDataNetworkError.parser(type: "\(R.Type.self)")}else {
            throw JingDataNetworkError.parser(type: "\(R.Type.self)")}}func makeCustomJingDataNetworkError(a) -> JingDataNetworkError? {
        return nil
    }
    
    func handleJingDataNetworkError(_ error: JingDataNetworkError){}}Copy the code

But everyone knows that if a class or method has too much functionality, it will become bloated, branching conditions will increase, and it will become illogical and difficult to maintain. Therefore, a moderate level of abstraction, stratification and decoupling is necessary for medium and large projects.

And in this case, Response is just the base type. ResponseHandler is even more complicated if it’s an object type. UserInfo and OrderList can be fundamentally different in parsing, error throwing, error handling, and so on.

Which brings us to the following question.

How do you handle different types of error handling and throwing

In order to deal with this problem, we can declare a JingDataNetworkDataResponse, constraint has the same ability and JingDataNetworkResponseHandler.

public protocol JingDataNetworkDataResponse {
    associatedtype DataSource
    var data: DataSource { set get }
    func makeCustomJingDataNetworkError(a) -> JingDataNetworkError?
    func handleJingDataNetworkError(_ error: JingDataNetworkError)
    init? (_ data: Data)}public extension JingDataNetworkDataResponse {
    public func makeCustomJingDataNetworkError(a) -> JingDataNetworkError? {
        return nil
    }
    public func handleJingDataNetworkError(_ error: JingDataNetworkError){}}public protocol JingDataNetworkDataResponseHandler: JingDataNetworkResponseHandler where Response: JingDataNetworkDataResponse {}
Copy the code

Implementing this protocol, you can see that UserInfo and OrderList can be handled completely differently:

struct BaseDataResponse: JingDataNetworkDataResponse {

    var data: String = ""
    var code: Int = 0
    
    init? (_ data: Data) {
        self.data = "str"
        self.code = 0
    }
    
    func makeCustomJingDataNetworkError(a) -> JingDataNetworkError? {
        switch code {
        case 0:
            return nil
        default:
            return JingDataNetworkError.custom(code: code)
        }
    }
}

struct BaseDataResponseHandler<R: JingDataNetworkDataResponse> :JingDataNetworkDataResponseHandler {
    var response: R?
}
Copy the code

How to initiate a request

Jingdata Network WorkManager uses Moya and RxSwift to encapsulate network requests, mainly doing the following things:

  • Network request error code thrown;
  • Data to Response error thrown;
  • ProgressBlock set;
  • The Test set.
  • Network request Observer construction;

Use the sample

        / / get the response
        JingDataNetworkManager.base(api: TestApi.m)
            .bind(BaseResponseHandler.self)
            .single()
            .observeOn(MainScheduler.instance)
            .subscribe(onSuccess: { (response) in
                print(response)
            })
            .disposed(by: bag)
        
        / / get the response data
        JingDataNetworkManager.base(api: TestApi.m)
            .bind(BaseDataResponseHandler<BaseDataResponse>.self)
            .single()
            .observeOn(MainScheduler.instance)
            .subscribe(onSuccess: { (data) in
                print(data.count)
            })
            .disposed(by: bag)
        
        / / get the response. ListData
        JingDataNetworkManager.base(api: TestApi.m)
            .bind(BaseListDataResponseHandler<BaseListDataResponse>.self)
            .single()
            .observeOn(MainScheduler.instance)
            .subscribe(onSuccess: { (listData) in
                print(listData.count)
            })
            .disposed(by: bag)
Copy the code

Timing management

In addition to the analysis of the model, the management of request order is also an important part of Network work. The order of their requests generally has several cases.

  • The request results are parsed in the same model
    • Request callbacks in turn
    • Callback when all requests are complete
  • Request results are parsed in different models
    • Request callbacks in turn
    • Callback when all requests are complete

Here’s how to do it in turn.

The same Response

public struct JingDataNetworkSameHandlerSequencer<Handler: JingDataNetworkResponseHandler> {
    
    public init () {}
    
    public func zip(apis: [TargetType], progress: ProgressBlock? = nil, test: Bool = false) -> PrimitiveSequence<SingleTrait[Handler.Response] > {var singles = [PrimitiveSequence<SingleTrait.Handler.Response>] ()for api in apis {
            let single = JingDataNetworkManager.base(api: api).bind(Handler.self).single(progress: progress, test: test)
            singles.append(single)
        }
        return Single.zip(singles)
    }
    
    public func map(apis: [TargetType], progress: ProgressBlock? = nil, test: Bool = false) -> Observable<Handler.Response> {
        var singles = [PrimitiveSequence<SingleTrait.Handler.Response>] ()for api in apis {
            let single = JingDataNetworkManager.base(api: api).bind(Handler.self).single(progress: progress, test: test)
            singles.append(single)
        }
        return Observable.from(singles).merge()
    }
}
Copy the code

RxSwift is used to package and sequentially process the request results.

Example:

        let sequencer = JingDataNetworkSequencer.sameHandler(BaseListDataResponseHandler<BaseListDataResponse>.self)
        sequencer.zip(apis: [TestApi.m, Test2Api.n])
            .subscribe(onSuccess: { (responseList) in
                print(responseList.map({$0.listData}))
            })
        .disposed(by: bag)
        
        sequencer.map(apis: [TestApi.m, Test2Api.n])
            .subscribe(onNext: { (response) in
                print(response.listData)
            })
        .disposed(by: bag)
Copy the code

Different Response

The order request

The different models are relatively complex because they imply different background or parsing rules, and at the same time, sequential requests require that the results of the previous request be obtained, and sequential requests complete, and the final request result be obtained.

In the following implementation:

Blocks saves the code block for each request and interrupts the next one if the request fails.

Semaphore is a semaphore that guarantees that the next block will be blocked before this block completes.

Data is the result of this request and is passed to the next request.

public class JingDataNetworkDifferentMapHandlerSequencer {
    
    var blocks = [JingDataNetworkViodCallback] ()let semaphore = DispatchSemaphore(value: 1)
    var data: Any?
    var bag = DisposeBag(a)var requestSuccess = true
    var results = [Any] ()var index: Int = 0
    
    public init() {}
    
    @discardableResult
    public func next<C: JingDataNetworkResponseHandler, T: TargetType, P>(bind: C.Type, with: @escaping (P) -> T? , progress:ProgressBlock? = nil, success: @escaping (C.Response) -> (), error: ((Error) - > ())? =nil, test: Bool = false) - >JingDataNetworkDifferentMapHandlerSequencer {
        let api: () -> T? = {
            guard let preData = self.data as? P else { return nil }
            return with(preData)
        }
        return next(bind: bind, api: api, progress: progress, success: success, error: error, test: test)
    }
    
    @discardableResult
    public func next<C: JingDataNetworkResponseHandler, T: TargetType>(bind: C.Type, api: @escaping (a) -> T? , progress:ProgressBlock? = nil, success: @escaping (C.Response) -> (), error: ((Error) - > ())? =nil, test: Bool = false) - >JingDataNetworkDifferentMapHandlerSequencer {
        let block: JingDataNetworkViodCallback = {
            guard let api = api() else {
                self.requestSuccess = false
                return
            }
            self.semaphore.wait()
            JingDataNetworkManager.base(api: api).bind(C.self)
            .single(progress: progress, test: test)
            .observeOn(MainScheduler.instance)
            .subscribe(onSuccess: { [weak self] (data) in
                self? .data = dataself? .results.append(data)self? .requestSuccess =true
                success(data)
                self? .semaphore.signal() }, onError: { [weak self] (e) in
                    self? .requestSuccess =falseerror? (e)self? .semaphore.signal() }) .disposed(by:self.bag)

            self.semaphore.wait()
            // print("xxxxxxxxx")
            self.semaphore.signal()
        }
        blocks.append(block)
        return self
    }
    
    public func run(a) -> PrimitiveSequence<SingleTrait[Any] > {let ob = Single"[Any]>.create { (single) -> Disposable in
            let queue = DispatchQueue(label: "\(JingDataNetworkDifferentMapHandlerSequencer.self)", qos: .default, attributes: .concurrent)
            queue.async {
                for i in 0 ..< self.blocks.count {
                    self.index = i
                    guard self.requestSuccess else {
                        break
                    }
                    self.blocks[i]()
                }
                if self.requestSuccess {
                    single(.success(self.results))
                }
                else {
                    single(.error(JingDataNetworkError.sequence(.break(index: self.index))))
                }
                self.requestFinish()
            }
            return Disposables.create()
        }
        return ob
    }
    
    func requestFinish(a) {
        requestSuccess = true
        index = 0
        blocks.removeAll()
        results.removeAll()
    }
    
    deinit {
        debugPrint("\(#file) \(#function)")}}Copy the code

Example:

        let sequencer = JingDataNetworkSequencer.differentHandlerMap
        sequencer.next(bind: BaseResponseHandler.self, api: {TestApi.m}, success: { (response) in
            print(response)
        })
        sequencer.next(bind: BaseListDataResponseHandler<BaseListDataResponse>.self, with: { (data: String) - >TestApi? in
            print(data)
            return .n
        }, success: { (response) in
            print(response)
        })
        sequencer.next(bind: BaseListDataResponseHandler<BaseListDataResponse>.self, with: { (data: BaseListDataResponse) - >Test2Api? in
            print(data)
            return .n
        }, success: { (response) in
            print(response)
        })
        sequencer.run().asObservable()
            .subscribe(onNext: { (results) in
                print(results)
            })
        .disposed(by: bag)
Copy the code

Package request

In a packaged request, we treat a request as a task:

public struct JingDataNetworkTask<H: JingDataNetworkResponseHandler> :JingDataNetworkTaskInterface {
    
    public var api: TargetType
    public var handler: H.Type
    public var progress: ProgressBlock? = nil
    public var test: Bool = false
    
    public init(api: TargetType, handler: Handler.Type, progress: ProgressBlock? = nil, test: Bool = false) {
        self.api = api
        self.handler = handler
        self.progress = progress
        self.test = test
    }
    
    public func single(a) -> PrimitiveSequence<SingleTrait.H.Response> {
        return JingDataNetworkManager.base(api: api).bind(handler).single(progress: progress, test: test)
    }
}
Copy the code

Accomplish the goals of the packaging request by repackaging single.zip:

public struct JingDataNetworkDifferentZipHandlerSequencer {
    
    public init() {}
    
    public func zip<H1: JingDataNetworkResponseHandler, H2: JingDataNetworkResponseHandler, H3: JingDataNetworkResponseHandler>(_ source1: JingDataNetworkTask<H1>, _ source2: JingDataNetworkTask<H2>, _ source3: JingDataNetworkTask<H3>) -> PrimitiveSequence<SingleTrait, (H1.Response.H2.Response.H3.Response) > {return Single.zip(source1.single(), source2.single(), source3.single())
    }
    
    public func zip<H1: JingDataNetworkResponseHandler, H2: JingDataNetworkResponseHandler>(_ source1: JingDataNetworkTask<H1>, _ source2: JingDataNetworkTask<H2>) -> PrimitiveSequence<SingleTrait, (H1.Response.H2.Response) > {return Single.zip(source1.single(), source2.single())
    }
}
Copy the code

Example:

        let task1 = JingDataNetworkTask(api: TestApi.m, handler: BaseResponseHandler.self)
        let task2 = JingDataNetworkTask(api: Test2Api.n, handler: BaseListDataResponseHandler<BaseListDataResponse>.self)
        let sequencer = JingDataNetworkSequencer.differentHandlerZip
        sequencer.zip(task1, task2).subscribe(onSuccess: { (data1, data2) in
            print(data1, data2)
        }).disposed(by: bag)
Copy the code

The project address

Github.com/tianziyao/J…

conclusion

At this point, the components for a network request are almost complete. And related to such as download, upload and other functions, has been implemented by Moya.

If I can help you, please click star.

Some of them are not perfect in the design, I hope you can make an issue.