Swift는 프로토콜 포괄적인 패키징 네트워크 계층을 사용합니다.

14383 단어
swift 버전 4.1
Xcode 버전 9.3(9E145)
Alamofire 및 Moya 기반 재봉인
코드 Github 주소: Moyademo

앞말


최근에 새로운 회사에 들어와 새로운 프로젝트를 전개했다. 나는 회사 프로젝트의 네트워크 층이 OC라는 것을 발견했다. 가장 참을 수 없는 것은 데이터 해석이 네트워크 층 이외의 것이기 때문에 모든 데이터 모델은 해석 코드를 따로 써야 한다는 것이다.프로젝트가 시작된 틈을 타서 나는 이전의 네트워크 층을 대체하기 위해 네트워크 층의 작은 도구를 쓰는 것을 제의했다. 게다가 국화를 싣고 캐시도 안에 봉인했다.

2. Moya 도구 및 Codable 프로토콜 소개


여기에는 Moya의 기본 사용 방법과 Codable 의 기본 지식을 보여줄 뿐, 이 두 가지에 관심이 있으면 독자가 스스로 검색 연구를 할 수 있다.

2.1 Moya 도구


사용Moya은 필자가 편리하다고 생각하기 때문에 만약에 독자가 사용하고 싶지 않다면Moya 이 글의 내용을 읽는 데 영향을 주지 않는다.Alamofire여기는 소개하지 않겠습니다. 접촉한 적이 없으면 Swift버전AFNetworking으로 간주할 수 있습니다.MoyaAlamofire를 다시 봉인한 도구 라이브러리입니다.Alamofire만 사용하는 경우 네트워크 요청이 다음과 같을 수 있습니다.
let url = URL(string: "your url")!
Alamofire.request(url).response { (response) in
    // handle response
}

물론 독자들도 이를 바탕으로 2차 봉인을 할 것이며 위 코드만 간단하지는 않을 것이다.Moya를 사용하면 직접 요청이 아니라 프로젝트 모듈에 따라 파일 정의 인터페이스를 만듭니다.예를 들어 나는 모듈의 기능에 따라 이름 + API을 지은 다음에 우리가 사용해야 할 인터페이스를 정의하는 것을 좋아한다. 예를 들어 다음과 같다.
import Foundation
import Moya

enum YourModuleAPI {
    case yourAPI1
    case yourAPI2(parameter: String)
}

extension YourModuleAPI: TargetType {
    var baseURL : URL {
        return URL(string: "your base url")!
    }
    
    var headers : [String : String]? {
        return "your header"
    }
    
    var path: String {
        switch self {
            case .yourAPI1:
                return "yourAPI1 path"
            case .yourAPI2:
                return "yourAPI2 path"
        }
    }
    
    var method: Moya.Method {
        switch self {
            case .yourAPI1:
                return .post
            default:
                return .get
        }
    }
    
    //             
    var task: Task {
        var parameters: [String: Any] = [:]
        switch self {
            case let .yourAPI1:
                parameters = [:]
            case let .yourAPI2(parameter):
                parameters = ["  ":parameter]
        }
        return .requestParameters(parameters: parameters,
                                    encoding: URLEncoding.default)
    }
    
    //           
    var sampleData : Data {
        return Data()
    }
}

위의 파일을 정의하면 다음과 같은 방법으로 네트워크 요청을 수행할 수 있습니다.
MoyaProvider().request(YourModuleAPI.yourAPI1) { (result) in
    // handle result            
}

2.2 Codable 프로토콜

Codable Swift4만 업데이트된 것으로 데이터를 해석하고 인코딩하는 데 사용되며 인코딩 프로토콜과 디코딩 프로토콜로 구성되어 있다.
public typealias Codable = Decodable & Encodable
Swift 업데이트Codable 에 앞서 필자는 줄곧 SwiftyJSON으로 네트워크 요청이 되돌아오는 데이터를 분석했다.최근에Codable 사용하다보니좋더라구요,바로써버렸어요.
그러나 Codable 에는 아직도 약간의 구덩이가 있다. 예를 들어 이 글에서 묘사한 바와 같다.
When JSONDecoder meets the real world, things get ugly…
다음Person 모델 클래스는 간단한 개인 정보를 저장했는데 여기는 디코딩만 사용했기 때문에 Decodable 만 준수했다.
struct Person: Decodable {
  var name: String
  var age: Int
}
StringInt는 시스템의 기본 디코딩 유형이기 때문에 다른 코드를 쓸 필요가 없습니다. 컴파일러는 기본적으로 저희에게 실현됩니다.
let jsonString = """
        {   "name": "swordjoy",
            "age": 99
        }
"""

if let data = jsonString.data(using: .utf8) {
    let decoder = JSONDecoder()
    if let person = try? decoder.decode(Person.self, from: data) {
        print(person.age)    // 99
        print(person.name)   // swordjoy
    }
}
Person 유형을 JSONDecoder 대상에게 전달하기만 하면 JSON 데이터를 Person 데이터 모델 대상으로 직접 변환할 수 있다.실제 사용에서 규칙을 해석하는 각종 엄격한 제한 때문에 위에서 보기에 이렇게 간단하지 않다.

3. 분석과 해결 방안


3.1.1 데이터를 모델에 중복 해석


예를 들어 여기에 두 개의 인터페이스가 있는데 하나는 요청 상품 목록이고 하나는 요청 상점의 첫 페이지이다.필자는 이전에 이렇게 썼다.
enum MallAPI {
    case getMallHome
    case getGoodsList
}
extension MallAPI: TargetType {
    //     
}
let mallProvider = MoyaProvider()
mallProvider.request(MallAPI.getGoodsList) { (response) in
    //   response     Goods       success      
}

mallProvider.request(MallAPI.getMallHome) { (response) in
    //   response     Home     success      
}

이상은 간소화된 실용 장면으로 모든 네트워크 요청은 되돌아오는 데이터를 데이터 모델이나 데이터 모델 그룹으로 단독으로 한 번씩 작성한다.데이터 분석 기능을 하나의 단일 도구 클래스로 봉인해도 조금 나을 뿐이다.
필자가 원하는 것은 데이터 모델 유형을 지정한 후 네트워크층이 해석이 완성된 데이터 모델을 직접 되돌려 우리가 사용할 수 있도록 하는 것이다.

3.1.2 일반형을 활용하여 해결


범형은 상기 문제를 해결하는 데 사용되는 것으로 범형을 이용하여 네트워크 도구 클래스를 만들고 범형의 조건 제약을 정한다. Codable 프로토콜을 준수한다.
struct NetworkManager where T: Codable {
    
}

이렇게 하면 우리가 사용할 때 해석해야 할 데이터 모델 유형을 지정할 수 있다.
NetworkManager().reqest...
NetworkManager().reqest...

세심한 독자들은 이것이 Moya초기화MoyaProvider류의 사용 방식과 같다는 것을 발견할 수 있다.

3.2.1 Moya 사용 후 로드 컨트롤러와 캐시를 네트워크 계층으로 캡슐화하는 방법

Moya를 사용하여 다시 봉인하기 때문에 코드에 대해 한 번씩 봉인하는 대가가 자유의 희생이다.어떻게 캐리어 컨트롤러 & 캐시 기능과 Moya를 결합합니까?
아주 간단한 방법은 요청 방법에 컨트롤러를 표시할지, 캐시 브리지 파라미터를 추가할지 여부입니다.나의 요청 방법의 매개 변수가 이미 5, 6개가 된 것을 보고 이 방안은 즉시 배제되었다.MoyaTargetType협의를 보면서 영감을 주었어요.

3.2.2 협의를 활용하여 해결

MallAPITargetType를 준수하여 네트워크 요청 정보를 설정할 수 있다면 당연히 우리의 협의를 준수하여 일부 설정을 할 수 있다.
사용자 정의Moya 추가 프로토콜
protocol MoyaAddable {
    var cacheKey: String? { get }
    var isShowHud: Bool { get }
}

이렇게 MallAPI하면 두 개의 협의를 준수해야 한다
extension MallAPI: TargetType, MoyaAddable {
    //     
}

4. 부분 코드 전시와 해석


온전한 코드는 독자가 Github에 올라가서 다운로드할 수 있다.

4.1 패키징된 네트워크 요청


되돌아올 데이터 형식을 정하면 되돌아오는 response 데이터 모델 그룹을 직접 추출dataList 속성으로 해석된 Goods 데이터 모델 그룹을 얻을 수 있습니다.오류 클립에서도 error.message를 통해 오류 정보를 직접 얻고 업무 수요에 따라 팝업 상자를 사용할지 여부를 선택하여 사용자에게 알릴 수 있습니다.
NetworkManager().requestListModel(MallAPI.getOrderList, 
completion: { (response) in
    let list = response?.dataList
    let page = response?.page
}) { (error) in
    if let msg = error.message else {
        print(msg)
    }
}

4.2 반환 데이터의 봉인


필자 회사 서버에서 되돌아오는 데이터 구조는 대체로 다음과 같다.
{
    "code": 0,
    "msg": "  ",
    "data": {
        "hasMore": false,
        "list": []
    }
}

현재의 업무와 해석 데이터에 대한 고려에서 필자는 되돌아오는 데이터 유형을 두 가지로 봉인하고 해석하는 조작도 그 안에 두었다.
뒤의 요청 방법도 두 가지로 나뉘는데 이것은 필요한 것이 아니며 독자는 자신의 업무와 취향에 따라 선택할 수 있다.
  • 요청 목록 인터페이스에서 반환된 데이터
  • 일반 인터페이스에 요청된 데이터
  • class BaseResponse {
        var code: Int { ... } //   
        var message: String? { ... } //   
        var jsonData: Any? { ... } //   
        
        let json: [String : Any]
        init?(data: Any) {
            guard let temp = data as? [String : Any] else {
                return nil
            }
            self.json = temp
        }
        
        func json2Data(_ object: Any) -> Data? {
            return try? JSONSerialization.data(
            withJSONObject: object,
            options: [])
        }
    }
    
    class ListResponse: BaseResponse where T: Codable {
        var dataList: [T]? { ... } //   
        var page: PageModel? { ... } //   
    }
    
    class ModelResponse: BaseResponse where T: Codable {
        var data: T? { ... } //   
    }
    

    이렇게 하면 우리는 상응하는 봉인 클래스의 대상을 직접 되돌려주면 해석된 데이터를 얻을 수 있다.

    4.3 잘못된 패키징


    네트워크 요청 과정에서 틀림없이 여러 가지 오류가 있을 것이다. 여기Swift 언어의 오류 메커니즘을 사용했다.
    //         
    public enum NetworkError: Error  {
        //  ...
        //         
        case serverResponse(message: String?, code: Int)
    }
    
    extension NetworkError {
        var message: String? {
            switch self {
                case let .serverResponse(msg, _): return msg
                default: return nil
            }
        }
        
        var code: Int {
            switch self {
                case let .serverResponse(_, code): return code
                default: return -1
            }
        }
    }
    

    이 확장은 오류를 처리할 때 오류 messagecode 를 가져오는 데 도움이 됩니다.

    4.4 네트워크 요청 방법


    최종 요청 방법
    private func request(
        _ type: R,
        test: Bool = false,
        progressBlock: ((Double) -> ())? = nil,
        modelCompletion: ((ModelResponse?) -> ())? = nil,
        modelListCompletion: ((ListResponse?) -> () )? = nil,
        error: @escaping (NetworkError) -> () )
        -> Cancellable?
    {}
    

    여기의 R 범위는 Moya의 정의를 얻는 인터페이스로 TargetTypeMoyaAddable 프로토콜을 동시에 준수해야 하며 나머지는 모두 일반적인 조작이다.봉인된 반환 데이터와 마찬가지로 여기도 일반 인터페이스와 목록 인터페이스로 나뉜다.
    @discardableResult
    func requestModel(
        _ type: R,
        test: Bool = false,
        progressBlock: ((Double) -> ())? = nil,
        completion: @escaping ((ModelResponse?) -> ()),
        error: @escaping (NetworkError) -> () )
        -> Cancellable?
    {
        return request(type,
                        test: test,
                        progressBlock: progressBlock,
                        modelCompletion: completion,
                        error: error)
    }
    
    @discardableResult
    func requestListModel(
        _ type: R,
        test: Bool = false,
        completion: @escaping ((ListResponse?) -> ()),
        error: @escaping (NetworkError) -> () )
        -> Cancellable?
    {
        return request(type,
                        test: test,
                        modelListCompletion: completion,
                        error: error)
    }
    

    나는 현재 프로젝트와 Codable 협의의 구덩이를 종합하여 고려하여 이곳을 좀 융통성 없이 썼는데 만일 목록과 다른 데이터가 있다면 적용되지 않을 것이다.그러나 그때는 이와 유사한 방법을 추가해 데이터를 전송하여 처리할 수 있다.
    // Demo       
    func requestCustom(
        _ type: R,
        test: Bool = false,
        completion: (Response) -> ()) -> Cancellable? 
    {
        //  
    }
    

    4.5 캐시 및 로딩 컨트롤러

    MoyaAddable 프로토콜을 추가하면 다른 것은 어렵지 않습니다. type에 따라 인터페이스 정의 파일의 설정을 가져와 해당하는 조작을 하면 됩니다.
    var cacheKey: String? {
        switch self {
            case .getGoodsList:
                return "cache goods key"
            default:
                return nil
        }
    }
    
    var isShowHud: Bool {
        switch self {
            case .getGoodsList:
                return true
            default:
                return false
        }
    }
    

    이것은 getGoodsList 인터페이스 요청의 두 가지 기능을 추가했다
  • 데이터 반환을 요청하면 지정된 캐시Key를 통해 캐시
  • 네트워크 요청 시 로드 컨트롤러를 자동으로 표시하고 숨깁니다.

  • 만약 독자의 로드 컨트롤러가 서로 다른 스타일을 가지고 있다면, 로드 컨트롤러 스타일의 속성을 추가할 수도 있다.심지어 캐시를 동기화하거나 비동기화하는 방식도 이것MoyaAddable을 통해 추가할 수 있다.
    //   
    private func cacheData(
        _ type: R,
        modelCompletion: ((Response?) -> ())? = nil,
        modelListCompletion: ( (ListResponse?) -> () )? = nil,
        model: (Response?, ListResponse?))
    {
        guard let cacheKey = type.cacheKey else {
            return
        }
        if modelComletion != nil, let temp = model.0 {
            //   
        }
        if modelListComletion != nil, let temp = model.1 {
            //   
        }
    }
    

    로딩 컨트롤러의 디스플레이와 숨김은 Moya 자체 플러그인 도구를 사용합니다.
    //   moya   
    private func createProvider(
        type: T,
        test: Bool) 
        -> MoyaProvider 
    {
        let activityPlugin = NetworkActivityPlugin { (state, targetType) in
            switch state {
            case .began:
                DispatchQueue.main.async {
                    if type.isShowHud {
                        SVProgressHUD.showLoading()
                    }
                    self.startStatusNetworkActivity()
                }
            case .ended:
                DispatchQueue.main.async {
                    if type.isShowHud {
                        SVProgressHUD.dismiss()
                    }
                    self.stopStatusNetworkActivity()
                }
            }
        }
        let provider = MoyaProvider(
            plugins: [activityPlugin,
            NetworkLoggerPlugin(verbose: false)])
        return provider
    }
    

    4.6 중복 요청 방지


    네트워크에서 요청한 정보를 저장할 수 있는 그룹을 정의하고, 병렬 대기열은 barrier 함수를 사용하여 그룹 요소의 추가와 제거 라인의 안전을 보장합니다.
    //               
    private let barrierQueue = DispatchQueue(label: "cn.tsingho.qingyun.NetworkManager",
    attributes: .concurrent)
    //             ,          
    private var fetchRequestKeys = [String]()
    
    private func isSameRequest(_ type: R) -> Bool {
        switch type.task {
            case let .requestParameters(parameters, _):
                let key = type.path + parameters.description
                var result: Bool!
                barrierQueue.sync(flags: .barrier) {
                    result = fetchRequestKeys.contains(key)
                    if !result {
                        fetchRequestKeys.append(key)
                    }
                }
                return result
            default:
                //     
                return false
        }
    }
    
    private func cleanRequest(_ type: R) {
        switch type.task {
            case let .requestParameters(parameters, _):
                let key = type.path + parameters.description
                barrierQueue.sync(flags: .barrier) {
                    fetchRequestKeys.remove(key)
                }
            default:
                //     
                ()
        }
    }
    

    이런 실현 방식은 현재 작은 문제가 하나 있는데 여러 인터페이스가 같은 인터페이스를 사용하고 매개 변수도 같으면 한 번만 요청할 수 있다. 그러나 이런 상황은 매우 적고 잠시 만나지 않으면 처리되지 않는다.

    5. 후기


    현재 봉인된 이 네트워크 계층 코드는 약간 강한 업무 유형이다. 왜냐하면 나의 초심은 자신의 회사 프로젝트에 네트워크 계층을 다시 쓰는 것이기 때문에 일부 상황에 적합하지 않을 수도 있다.그러나 여기서 범주형과 프로토콜을 사용하는 방법은 통용되고 독자는 같은 방식으로 자신의 프로젝트에 일치하는 네트워크 층을 실현할 수 있다.만약 독자들이 더 좋은 건의가 있다면, 평론을 내서 함께 토론하기를 바란다.
    전재 평론 전재 주소를 남기면 전재할 수 있다.문장 발굴 주소

    좋은 웹페이지 즐겨찾기