Swift는 프로토콜 포괄적인 패키징 네트워크 계층을 사용합니다.
Xcode 버전 9.3(9E145)
Alamofire 및 Moya 기반 재봉인
코드 Github 주소: Moyademo
앞말
최근에 새로운 회사에 들어와 새로운 프로젝트를 전개했다. 나는 회사 프로젝트의 네트워크 층이 OC라는 것을 발견했다. 가장 참을 수 없는 것은 데이터 해석이 네트워크 층 이외의 것이기 때문에 모든 데이터 모델은 해석 코드를 따로 써야 한다는 것이다.프로젝트가 시작된 틈을 타서 나는 이전의 네트워크 층을 대체하기 위해 네트워크 층의 작은 도구를 쓰는 것을 제의했다. 게다가 국화를 싣고 캐시도 안에 봉인했다.
2. Moya 도구 및 Codable 프로토콜 소개
여기에는
Moya
의 기본 사용 방법과 Codable
의 기본 지식을 보여줄 뿐, 이 두 가지에 관심이 있으면 독자가 스스로 검색 연구를 할 수 있다.2.1 Moya 도구
사용
Moya
은 필자가 편리하다고 생각하기 때문에 만약에 독자가 사용하고 싶지 않다면Moya
이 글의 내용을 읽는 데 영향을 주지 않는다.Alamofire
여기는 소개하지 않겠습니다. 접촉한 적이 없으면 Swift
버전AFNetworking
으로 간주할 수 있습니다.Moya
는 Alamofire
를 다시 봉인한 도구 라이브러리입니다.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
}
String
와 Int
는 시스템의 기본 디코딩 유형이기 때문에 다른 코드를 쓸 필요가 없습니다. 컴파일러는 기본적으로 저희에게 실현됩니다.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개가 된 것을 보고 이 방안은 즉시 배제되었다.
Moya
의TargetType
협의를 보면서 영감을 주었어요.3.2.2 협의를 활용하여 해결
MallAPI
가 TargetType
를 준수하여 네트워크 요청 정보를 설정할 수 있다면 당연히 우리의 협의를 준수하여 일부 설정을 할 수 있다.사용자 정의
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
}
}
}
이 확장은 오류를 처리할 때 오류
message
와 code
를 가져오는 데 도움이 됩니다.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
의 정의를 얻는 인터페이스로 TargetType
와 MoyaAddable
프로토콜을 동시에 준수해야 하며 나머지는 모두 일반적인 조작이다.봉인된 반환 데이터와 마찬가지로 여기도 일반 인터페이스와 목록 인터페이스로 나뉜다.@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. 후기
현재 봉인된 이 네트워크 계층 코드는 약간 강한 업무 유형이다. 왜냐하면 나의 초심은 자신의 회사 프로젝트에 네트워크 계층을 다시 쓰는 것이기 때문에 일부 상황에 적합하지 않을 수도 있다.그러나 여기서 범주형과 프로토콜을 사용하는 방법은 통용되고 독자는 같은 방식으로 자신의 프로젝트에 일치하는 네트워크 층을 실현할 수 있다.만약 독자들이 더 좋은 건의가 있다면, 평론을 내서 함께 토론하기를 바란다.
전재 평론 전재 주소를 남기면 전재할 수 있다.문장 발굴 주소
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.