[Alamofire 소스 해석] 09 - Parameter Encoding

10580 단어
이 파일에는 세 가지 매개 변수 인코딩 방식이 적혀 있다. URLEncoding, JSONEncoding, PropertyListEncoding.
1. 보조 유형
//           ,           rawValue
public enum HTTPMethod: String {
    case options = "OPTIONS"
    case get     = "GET"
    case head    = "HEAD"
    case post    = "POST"
    case put     = "PUT"
    case patch   = "PATCH"
    case delete  = "DELETE"
    case trace   = "TRACE"
    case connect = "CONNECT"
}

// Dictionary     
public typealias Parameters = [String: Any]


2. ParameterEncoding 프로토콜
요청에 매개 변수를 어떻게 인코딩하는지 규정했다.
public protocol ParameterEncoding {
    func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest
}

3. URLEncoding
매개변수를 URL에 직접 인코딩합니다.
1). Destination
요청에 매개 변수를 인코딩하는 세 가지 방식을 열거했다.
// methodDependent:          ,`GET`, `HEAD`   `DELETE`       url ,      HTTP body
// queryString:        url 
// httpBody:   HTTP body
public enum Destination {
    case methodDependent, queryString, httpBody
}

2). 속성 및 초기화
//      methodDependent URLEncoding  
public static var `default`: URLEncoding { return URLEncoding() }

//      methodDependent URLEncoding  ,   default    
public static var methodDependent: URLEncoding { return URLEncoding() }

//      queryString URLEncoding  
public static var queryString: URLEncoding { return URLEncoding(destination: .queryString) }

//      httpBody URLEncoding  
public static var httpBody: URLEncoding { return URLEncoding(destination: .httpBody) }

public let destination: Destination

public init(destination: Destination = .methodDependent) {
    self.destination = destination
}

3). 부호화
//   ParameterEncoding  
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
    var urlRequest = try urlRequest.asURLRequest()
    
    //         ,    
    guard let parameters = parameters else { return urlRequest }
    
    //       URL 
    if let method = HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET"), encodesParametersInURL(with: method) {
        guard let url = urlRequest.url else {
            throw AFError.parameterEncodingFailed(reason: .missingURL)
        }
        
        if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty {
            let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)
            urlComponents.percentEncodedQuery = percentEncodedQuery
            urlRequest.url = urlComponents.url
        }
    } else { //       HTTP body
        if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
            urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
        }
        
        urlRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false)
    }
    
    return urlRequest
}

//      key value,             
public func queryComponents(fromKey key: String, value: Any) -> [(String, String)] {
    var components: [(String, String)] = []
    
    if let dictionary = value as? [String: Any] { // value   
        for (nestedKey, value) in dictionary {
            //        key value
            components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value)
        }
    } else if let array = value as? [Any] { // value   
        for value in array {
            //        key value
            components += queryComponents(fromKey: "\(key)[]", value: value)
        }
    } else if let value = value as? NSNumber { // value NSNumber
        if value.isBool {
            // NSNumber   Bool  , 1  0  
            components.append((escape(key), escape((value.boolValue ? "1" : "0"))))
        } else {
            components.append((escape(key), escape("\(value)")))
        }
    } else if let bool = value as? Bool { // value Bool  , 1  0  
        components.append((escape(key), escape((bool ? "1" : "0"))))
    } else {
        components.append((escape(key), escape("\(value)")))
    }
    
    return components
}

//            。
//              :
// - General Delimiters: ":", "#", "[", "]", "@"
// - Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="


public func escape(_ string: String) -> String {
    let generalDelimitersToEncode = ":#[]@"
    let subDelimitersToEncode = "!$&'()*+,;="
    
    var allowedCharacterSet = CharacterSet.urlQueryAllowed
    allowedCharacterSet.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
    
    var escaped = ""
    
    //==========================================================================================================
    //
    //   iOS 8.1 8.2 ,                       crash,         
    //  (  bug      ,        issue:https://github.com/Alamofire/Alamofire/issues/206)
    //
    //==========================================================================================================
    
    if #available(iOS 8.3, *) {
        escaped = string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? string
    } else {
        let batchSize = 50
        var index = string.startIndex
        
        while index != string.endIndex {
            let startIndex = index
            let endIndex = string.index(index, offsetBy: batchSize, limitedBy: string.endIndex) ?? string.endIndex
            let range = startIndex.. String {
    var components: [(String, String)] = []
    
    // ` Bool {
    switch destination {
    case .queryString:
        return true
    case .httpBody:
        return false
    default:
        break
    }
    
    switch method {
    case .get, .head, .delete:
        return true
    default:
        return false
    }
}

4. JSONEncoding
요청체에 SJON 형식으로 매개변수를 인코딩합니다.
1). 속성 및 초기화
//    JSONEncoding  
public static var `default`: JSONEncoding { return JSONEncoding() }

//         JSONEncoding  
public static var prettyPrinted: JSONEncoding { return JSONEncoding(options: .prettyPrinted) }

public let options: JSONSerialization.WritingOptions

public init(options: JSONSerialization.WritingOptions = []) {
    self.options = options
}

2). 부호화
//           
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
    var urlRequest = try urlRequest.asURLRequest()
    
    //     ,    
    guard let parameters = parameters else { return urlRequest }
    
    do {
        //        JSON
        let data = try JSONSerialization.data(withJSONObject: parameters, options: options)
        
        //   Content-Type
        if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
            urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
        }
        
        urlRequest.httpBody = data
    } catch {
        throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
    }
    
    return urlRequest
}

//              ,           
public func encode(_ urlRequest: URLRequestConvertible, withJSONObject jsonObject: Any? = nil) throws -> URLRequest {
    var urlRequest = try urlRequest.asURLRequest()
    
    guard let jsonObject = jsonObject else { return urlRequest }
    
    do {
        //        JSON
        let data = try JSONSerialization.data(withJSONObject: jsonObject, options: options)
        
        //   Content-Type
        if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
            urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
        }
        
        urlRequest.httpBody = data
    } catch {
        throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
    }
    
    return urlRequest
}

5. PropertyListEncoding
매개변수를 PropertyList 형식으로 요청체에 인코딩합니다.
1). 속성 및 초기화
//    PropertyListEncoding  , xml      
public static var `default`: PropertyListEncoding { return PropertyListEncoding() }

//  xm       PropertyListEncoding  
public static var xml: PropertyListEncoding { return PropertyListEncoding(format: .xml) }

//            PropertyListEncoding  
public static var binary: PropertyListEncoding { return PropertyListEncoding(format: .binary) }

public let format: PropertyListSerialization.PropertyListFormat
public let options: PropertyListSerialization.WriteOptions

public init(
    format: PropertyListSerialization.PropertyListFormat = .xml,
    options: PropertyListSerialization.WriteOptions = 0)
{
    self.format = format
    self.options = options
}

2). 부호화
//   ParameterEncoding  
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
    var urlRequest = try urlRequest.asURLRequest()
    
    guard let parameters = parameters else { return urlRequest }
    
    do {
        //        PropertyList
        let data = try PropertyListSerialization.data(
            fromPropertyList: parameters,
            format: format,
            options: options
        )
        
        //   Content-Type
        if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
            urlRequest.setValue("application/x-plist", forHTTPHeaderField: "Content-Type")
        }
        
        urlRequest.httpBody = data
    } catch {
        throw AFError.parameterEncodingFailed(reason: .propertyListEncodingFailed(error: error))
    }
    
    return urlRequest
}

어떤 문제가 있으면 여러분의 메모를 환영합니다!
제가 관리하는 Swift 개발 그룹에 가입하신 것을 환영합니다. 536353151 이 그룹에서는 Swift와 관련된 내용만 토론합니다.
오리지널 문장, 전재는 출처를 밝혀 주십시오.감사합니다!

좋은 웹페이지 즐겨찾기