IOS源碼解析:Alamofire 5 核心

原創(chuàng):知識(shí)點(diǎn)總結(jié)性文章
創(chuàng)作不易,請(qǐng)珍惜蛉谜,之后會(huì)持續(xù)更新稚晚,不斷完善
個(gè)人比較喜歡做筆記和寫總結(jié),畢竟好記性不如爛筆頭哈哈悦陋,這些文章記錄了我的IOS成長(zhǎng)歷程蜈彼,希望能與大家一起進(jìn)步
溫馨提示:由于簡(jiǎn)書不支持目錄跳轉(zhuǎn)筑辨,大家可通過command + F 輸入目錄標(biāo)題后迅速尋找到你所需要的內(nèi)容

目錄

  • 一俺驶、抓包工具
    • 1、Charles 抓包工具
    • 2棍辕、Fiddler Everywhere 抓包工具
  • 二暮现、Alamofire 架構(gòu)
    • 1、接口
    • 2楚昭、請(qǐng)求
    • 3栖袋、響應(yīng)
    • 4、底層
    • 5抚太、其他
  • 三电媳、核心流程
  • 四、功能點(diǎn)解析
    • 1拼缝、AF
    • 2衰齐、Request
    • 3、HTTPMethod
    • 4犬第、ParameterEncoding
    • 5玛歌、Validation
    • 6带膀、DispatchQueue
    • 7伦糯、AFError
    • 8、DataResponse
  • Demo
  • 參考文獻(xiàn)

一淤翔、抓包工具

1、Charles 抓包工具

a、配置各種證書比較麻煩

b童叠、Charles抓包工具總是會(huì)瘋狂報(bào)錯(cuò)(這個(gè)問題可以重裝Charles抓包工具解決掉)

c、而且還存在抓取的內(nèi)容亂碼顯示問題

這個(gè)問題解決起來比較麻煩,我差不多都快放棄使用這個(gè)工具了诞挨,突然卻被我解決掉這個(gè)問題了,在這里畫個(gè)圈圈詛咒軟件的設(shè)計(jì)者银室,你這么復(fù)雜的配置讓人怎么玩你?

在電腦端安裝并信任證書

Charles中,設(shè)置ssl proxy Setting中的ssl proxying的代理網(wǎng)址,按圖中填寫即可叠纹,這一步非常重要与涡,我就是設(shè)置了這一步后才沒有亂碼的

設(shè)置抓包的網(wǎng)址和端口,設(shè)置為全部都抓

大功告成,終于抓取到正常的內(nèi)容了恳守,留下了喜極而泣的淚水沥阱,沒忍住想把創(chuàng)作者拖出去打的沖動(dòng)。


2、Fiddler Everywhere 抓包工具

推薦使用簡(jiǎn)單明了的Fiddler Everywhere抓包工具易茬,這是它的下載地址 Fiddler Everywhere,免費(fèi)版本的就可以用得很順溜了食铐。

界面整體看起來也比較干凈簡(jiǎn)潔寞秃,而且安裝好以后就可以直接使用朗涩,不用處理一大堆的配置問題。

和其它抓包工具一樣,Fiddler Everywhere默認(rèn)也是只能抓取HTTP請(qǐng)求渡讼,需要通過下載證書或進(jìn)行相關(guān)配置,才能正常攔截HTTPS請(qǐng)求析藕,配置如下,信任證書并勾選捕捉治泥。


二、Alamofire 架構(gòu)

1准脂、接口

Alamofire.swift:api 聲明
// 全局靜態(tài)變量
public let AF = Session.default
// 當(dāng)前Alamofire版本
let version = "5.4.1"

2、請(qǐng)求

Request.swift:請(qǐng)求類,用于構(gòu)建請(qǐng)求
public protocol RequestDelegate: AnyObject

public class Request
public class DataRequest: Request
public final class DataStreamRequest: Request
public class DownloadRequest: Request
public class UploadRequest: DataRequest
ParameterEncoding.swift:參數(shù)編碼
public protocol ParameterEncoder

open class JSONParameterEncoder: ParameterEncoder
open class URLEncodedFormParameterEncoder: ParameterEncoder
MultipartFormData.swift:自定義表單類
open class MultipartFormData 
ServerTrustEvaluation.swift:服務(wù)器驗(yàn)證
public protocol ServerTrustEvaluating

open class ServerTrustManager 
public final class DefaultTrustEvaluator: ServerTrustEvaluating
public final class RevocationTrustEvaluator: ServerTrustEvaluating
public final class PinnedCertificatesTrustEvaluator: ServerTrustEvaluating
public final class PublicKeysTrustEvaluator: ServerTrustEvaluating
public final class CompositeTrustEvaluator: ServerTrustEvaluating
public final class DisabledTrustEvaluator: ServerTrustEvaluating

3砾脑、響應(yīng)

Response.swift:響應(yīng)類,用于構(gòu)建響應(yīng)
public struct DataResponse<Success, Failure: Error> 
public struct DownloadResponse<Success, Failure: Error>
Validation.swift:響應(yīng)數(shù)據(jù)驗(yàn)證
extension Request
extension DataRequest
extension DataStreamRequest
extension DownloadRequest
Result+Alamofire.swift:請(qǐng)求結(jié)果表示
extension Result 
AFError.swift:錯(cuò)誤類型
public enum AFError: Error
public enum MultipartEncodingFailureReason
public enum ParameterEncodingFailureReason
public enum ParameterEncoderFailureReason
public enum ResponseValidationFailureReason
public enum ResponseSerializationFailureReason
public enum ServerTrustFailureReason
public enum URLRequestValidationFailureReason

4、底層

Session.swift:請(qǐng)求session的管理類,底層使用NSURLSession實(shí)現(xiàn)
open class Session
MARK: - DataRequest
MARK: - DataStreamRequest
MARK: - UploadRequest
SessionDelegate.swift:請(qǐng)求Session的代理對(duì)象,主要實(shí)現(xiàn)NSURLSession的代理方法以及回調(diào)閉包
open class SessionDelegate: NSObject 
extension SessionDelegate: URLSessionDelegate
extension SessionDelegate: URLSessionTaskDelegate
extension SessionDelegate: URLSessionDataDelegate
extension SessionDelegate: URLSessionDownloadDelegate
DispatchQueue+Alamofire.swift:GCD擴(kuò)展坑资,增加了一個(gè)名為after的延遲調(diào)用方法
extension DispatchQueue

5、其他

NetworkReachabilityManager.swift:網(wǎng)絡(luò)狀態(tài)監(jiān)聽類
open class NetworkReachabilityManager 
Notifications.swift:定義通知
extension Notification
extension NotificationCenter
public final class AlamofireNotifications: EventMonitor

三攒巍、核心流程

這里并不會(huì)貼上源碼里的大段代碼,而是通過一個(gè)例子來進(jìn)入到Alamofire中的各個(gè)模塊兢孝,讓讀者更好理解橘沥。

a品姓、安全認(rèn)證

info.plist中添加App Transport Security Settings腹备,再將Allow Arbitrary Loads修改為YES后控制臺(tái)輸出結(jié)果為如下:


b弦牡、核心流程的范例
// 淘寶的一個(gè)搜索api
let url = "http://suggest.taobao.com/sug"
// 對(duì)襪子進(jìn)行搜索
let parameters: [String: Any] = [
    "code" : "utf-8",
    "q" : "襪子"
]

AF.request(url, method: .get, parameters: parameters)
    .validate(statusCode: [200])
    .responseData(queue: DispatchQueue.global())
    { (responseData) in
        switch responseData.result
        {
        case .success(let data):
          guard let jsonString = String(data: data, encoding: .utf8) else { return }
          print("json字符串:\(jsonString)")
        case .failure(let error):
          print("錯(cuò)誤信息:\(error)")
        }
    }

c卸留、范例中的功能點(diǎn)解析
  • 通過Alamofirerequest開始調(diào)用
  • 傳入url
  • 使用get方法
  • 傳入parameters旨指,并在encoding中定義了參數(shù)的編碼方式
  • 這里并不需要headers
  • validate中傳入需要驗(yàn)證的statusCode的數(shù)組
  • responseData方法里傳入全局隊(duì)列和回調(diào)的處理閉包completionHandler
  • 在閉包里對(duì)返回的數(shù)據(jù)進(jìn)行判斷,若返回成功,打印jsonString熬尺,若失敗則打印錯(cuò)誤

d、輸出結(jié)果
json字符串:
{"result":[["襪子女","988016.6933754483"],["襪子男","865386.2105082254"],["襪子女冬","662313.8682009963"],["襪子女中筒襪","390508.191495028"],["襪子男長(zhǎng)襪","650152.593118986"],["襪子男冬","747552.2895411798"],["襪子女ins潮","286597.0935191857"],["襪子男純棉","324485.1344208922"],["襪子女冬季 加絨","329439.1491313167"],["襪子男中筒","300330.61181601905"]]}

四、功能點(diǎn)解析

1、AF

a艺挪、全局靜態(tài)變量
AF是最外層用于發(fā)起網(wǎng)絡(luò)請(qǐng)求的靜態(tài)變量,來自于全局靜態(tài)變量Session.default
  • 降低使用門檻,將復(fù)雜的功能實(shí)現(xiàn)進(jìn)行下沉
  • 提供單一接口來實(shí)現(xiàn)功能上的全面覆蓋疆瑰,沒有多余代碼,非常簡(jiǎn)潔
  • 方便編程者解讀源碼,探究SDK的封裝思路
public let AF = Session.default
default是提供給全局使用的共享單例
open class Session
{
    public static let default = Session()
}

b掰读、請(qǐng)求實(shí)際的調(diào)用者是URLSession
通過使用 URLSession 來初始化 Session
public init(session: URLSession,
            delegate: SessionDelegate,
            rootQueue: DispatchQueue,
            startRequestsImmediately: Bool = true,
            requestQueue: DispatchQueue? = nil,
            serializationQueue: DispatchQueue? = nil,
            interceptor: RequestInterceptor? = nil,
            serverTrustManager: ServerTrustManager? = nil,
            redirectHandler: RedirectHandler? = nil,
            cachedResponseHandler: CachedResponseHandler? = nil,
            eventMonitors: [EventMonitor] = [])
通過使用URLSessionConfiguration來初始化 Session,最終還是使用了URLSession
public convenience init(configuration: URLSessionConfiguration = URLSessionConfiguration.af.default,
                        delegate: SessionDelegate = SessionDelegate(),
                        rootQueue: DispatchQueue = DispatchQueue(label: "org.alamofire.session.rootQueue"),
                        startRequestsImmediately: Bool = true,
                        requestQueue: DispatchQueue? = nil,
                        serializationQueue: DispatchQueue? = nil,
                        interceptor: RequestInterceptor? = nil,
                        serverTrustManager: ServerTrustManager? = nil,
                        redirectHandler: RedirectHandler? = nil,
                        cachedResponseHandler: CachedResponseHandler? = nil,
                        eventMonitors: [EventMonitor] = [])
{
    let delegateQueue = OperationQueue(maxConcurrentOperationCount: 1, underlyingQueue: rootQueue, name: "org.alamofire.session.sessionDelegateQueue")
    let session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: delegateQueue)

    self.init(session: session,
              delegate: delegate,
              rootQueue: rootQueue,
              startRequestsImmediately: startRequestsImmediately,
              requestQueue: requestQueue,
              serializationQueue: serializationQueue,
              interceptor: interceptor,
              serverTrustManager: serverTrustManager,
              redirectHandler: redirectHandler,
              cachedResponseHandler: cachedResponseHandler,
              eventMonitors: eventMonitors)
}

2靖诗、Request

open func request(_ convertible: URLConvertible,
                  method: HTTPMethod = .get,
                  parameters: Parameters? = nil,
                  encoding: ParameterEncoding = URLEncoding.default,
                  headers: HTTPHeaders? = nil,
                  interceptor: RequestInterceptor? = nil,
                  requestModifier: RequestModifier? = nil) -> DataRequest

open func request(_ convertible: URLRequestConvertible, interceptor: RequestInterceptor? = nil) -> DataRequest
a、URLConvertible協(xié)議

我們?cè)诶永飩魅氲?code>url是String類型攒庵,也可以將它轉(zhuǎn)成URL類型再傳入尖坤,結(jié)果都正確场梆。這是因?yàn)?code>String和URL都遵循URLConvertible協(xié)議感昼,通過實(shí)現(xiàn)協(xié)議里的asURL()方法,從StringURLURLComponents類型轉(zhuǎn)換為URL雏搂,若失敗則拋出為AFError的錯(cuò)誤。

public protocol URLConvertible
{
    func asURL() throws -> URL
}
extension String: URLConvertible
{
    public func asURL() throws -> URL
    {
        guard let url = URL(string: self) 
        return url
    }
}
extension URL: URLConvertible
{
    public func asURL() throws -> URL { self }
}
extension URLComponents: URLConvertible
{
    public func asURL() throws -> URL
    {
        // 如果轉(zhuǎn)換 url 失敗, 拋出一個(gè)異常
        guard let url = url else { throw AFError.invalidURL(url: self) }
        return url
    }
}

b凸郑、URLRequestConvertible協(xié)議

負(fù)責(zé)URLRequest的轉(zhuǎn)換裳食,和上面類似,就不多介紹了芙沥。

// 可以轉(zhuǎn)換成 urlrequest 的協(xié)議
public protocol URLRequestConvertible
{
    // 返回一個(gè) urlrequest, 如果有錯(cuò), 可以拋出異常
    func asURLRequest() throws -> URLRequest
}

// 創(chuàng)建 urlRequest
extension URLRequestConvertible
{
    
    public var urlRequest: URLRequest? { try? asURLRequest() }
}

// 返回自身
extension URLRequest: URLRequestConvertible
{
    public func asURLRequest() throws -> URLRequest { self }
}

c诲祸、返回Request的子類
? 包括request救氯、download、upload等方法都是傳入所需參數(shù)于个,發(fā)起請(qǐng)求,再返回Request的子類
open func request(_ convertible: URLConvertible,
                  method: HTTPMethod = .get,
                  parameters: Parameters? = nil,
                  encoding: ParameterEncoding = URLEncoding.default,
                  headers: HTTPHeaders? = nil,
                  interceptor: RequestInterceptor? = nil,
                  requestModifier: RequestModifier? = nil) -> DataRequest
open func download(_ convertible: URLConvertible,
                   method: HTTPMethod = .get,
                   parameters: Parameters? = nil,
                   encoding: ParameterEncoding = URLEncoding.default,
                   headers: HTTPHeaders? = nil,
                   interceptor: RequestInterceptor? = nil,
                   requestModifier: RequestModifier? = nil,
                   to destination: DownloadRequest.Destination? = nil) -> DownloadRequest
open func upload(_ data: Data,
                 to convertible: URLConvertible,
                 method: HTTPMethod = .post,
                 headers: HTTPHeaders? = nil,
                 interceptor: RequestInterceptor? = nil,
                 fileManager: FileManager = .default,
                 requestModifier: RequestModifier? = nil) -> UploadRequest 
? 返回給我們的Request粒没,可以讓我們控制請(qǐng)求的暫停硕勿,恢復(fù)查排,取消等

請(qǐng)求狀態(tài)

fileprivate var mutableState = MutableState()

public var state: State { mutableState.state }
public var isInitialized: Bool { state == .initialized }// 初始化
public var isResumed: Bool { state == .resumed }// 恢復(fù)請(qǐng)求
public var isSuspended: Bool { state == .suspended }// 暫停請(qǐng)求
public var isCancelled: Bool { state == .cancelled }// 取消請(qǐng)求
public var isFinished: Bool { state == .finished }// 完成請(qǐng)求

暫停請(qǐng)求

public func suspend() -> Self
{
    $mutableState.write
    { mutableState in
        // 如果不能暫停, 那么就跳過
        guard mutableState.state.canTransitionTo(.suspended) else { return }
        // 變更可變狀態(tài)為暫停
        mutableState.state = .suspended
        // 在下層隊(duì)列中更新暫停狀態(tài)刻伊,didSuspend()在暫停完成時(shí)調(diào)用
        underlyingQueue.async { self.didSuspend() }

        guard let task = mutableState.tasks.last, task.state != .completed else { return }
        // 調(diào)用真正的網(wǎng)絡(luò)請(qǐng)求URLSessionTask的suspend()方法
        task.suspend()
        // 在下層隊(duì)列中更新暫停狀態(tài)晨川,didSuspendTask()在URLSessionTask暫停時(shí)調(diào)用
        underlyingQueue.async { self.didSuspendTask(task) }
    }

    return self
}

取消請(qǐng)求

public func cancel() -> Self
{
    $mutableState.write
    { mutableState in
        guard mutableState.state.canTransitionTo(.cancelled) else { return }

        mutableState.state = .cancelled

        underlyingQueue.async { self.didCancel() }

        // Resume to ensure metrics are gathered.
        task.resume()
        // 取消
        task.cancel()
        underlyingQueue.async { self.didCancelTask(task) }
    }

    return self
}

恢復(fù)請(qǐng)求

public func resume() -> Self
{
    $mutableState.write { mutableState in
        guard mutableState.state.canTransitionTo(.resumed) else { return }

        mutableState.state = .resumed

        underlyingQueue.async { self.didResume() }

        guard let task = mutableState.tasks.last, task.state != .completed else { return }

        task.resume()
        underlyingQueue.async { self.didResumeTask(task) }
    }

    return self
}

did方法,以恢復(fù)請(qǐng)求為例。

func didResume()
{
    dispatchPrecondition(condition: .onQueue(underlyingQueue))

    eventMonitor?.requestDidResume(self)
}

func didResumeTask(_ task: URLSessionTask)
{
    dispatchPrecondition(condition: .onQueue(underlyingQueue))

    eventMonitor?.request(self, didResumeTask: task)
}

3港粱、HTTPMethod

a望忆、HTTPMethod是個(gè)結(jié)構(gòu)體
public struct HTTPMethod: RawRepresentable, Equatable, Hashable
{
    public static let connect = HTTPMethod(rawValue: "CONNECT")
    public static let delete = HTTPMethod(rawValue: "DELETE")
    public static let get = HTTPMethod(rawValue: "GET")
    public static let head = HTTPMethod(rawValue: "HEAD")
    public static let post = HTTPMethod(rawValue: "POST")
    ...

    public let rawValue: String

    public init(rawValue: String)
    {
        self.rawValue = rawValue
    }
}

b谎懦、打印HTTPMethod的值
open func request(_ convertible: URLConvertible,
                  method: HTTPMethod = .get,
                  ...
{
    print("打印HTTPMethod的枚舉:\(method)")
    print("打印HTTPMethod的枚舉關(guān)聯(lián)值:\(method.rawValue)")
    ......
}

輸出結(jié)果

打印HTTPMethod的值:HTTPMethod(rawValue: "GET")
打印HTTPMethod的原始值:GET

c走哺、會(huì)話配置
? Session的初始化方法中使用了默認(rèn)的會(huì)話配置
public convenience init(configuration: URLSessionConfiguration = URLSessionConfiguration.af.default....)
? 使用了系統(tǒng)提供的默認(rèn)會(huì)話配置,并為其配置了默認(rèn)的HTTP的頭部信息
public static var default: URLSessionConfiguration
{
    let configuration = URLSessionConfiguration.default
    configuration.headers = .default

    return configuration
}
? HTTP默認(rèn)的頭部信息包括User-Agent、Accept-Encoding、Accept-Language
extension HTTPHeaders
{
    public static let default: HTTPHeaders = [.defaultAcceptEncoding,
                                                .defaultAcceptLanguage,
                                                .defaultUserAgent]
}
public static let defaultAcceptEncoding: HTTPHeader = {
    let encodings: [String]
    encodings = ["br", "gzip", "deflate"]
    return .acceptEncoding(encodings.qualityEncoded())
}()
public static let defaultAcceptLanguage: HTTPHeader =
{
    .acceptLanguage(Locale.preferredLanguages.prefix(6).qualityEncoded())
}()
public static let defaultUserAgent: HTTPHeader =
{
    let bundle = info?[kCFBundleIdentifierKey as String] as? String ?? "Unknown"
    let appVersion = info?["CFBundleShortVersionString"] as? String ?? "Unknown"
    let appBuild = info?[kCFBundleVersionKey as String] as? String ?? "Unknown"
    let alamofireVersion = "Alamofire/\(version)"
    ...
    let userAgent = "\(executable)/\(appVersion) (\(bundle); build:\(appBuild); \(osNameVersion)) \(alamofireVersion)"
    return .userAgent(userAgent)
}
? 運(yùn)行結(jié)果
{
  "args": {},
  "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "br;q=1.0, gzip;q=0.9, deflate;q=0.8",
    "Accept-Language": "en;q=1.0",
    "Host": "httpbin.org",
    "User-Agent": "UseAlamofire/1.0 (com.xiejiapei.UseAlamofire; build:1; iOS 14.3.0) Alamofire/5.4.1",
    "X-Amzn-Trace-Id": "Root=1-600e58ba-22848fce5ae105571e598f1e"
  },
  "origin": "222.76.251.163",
  "url": "https://httpbin.org/get"
}

4啤覆、ParameterEncoding

https://johnny:p4ssw0rd@www.example.com:443/script.ext;param=value?query=value#ref

==========url的組成=========

scheme //https
user //johnny
password //p4ssw0rd
host //www.example.com
port //443
path // /script.ext
pathExtension //ext
pathComponents //["/", "script.ext"]
parameterString //param=value
query //query=value
fragment //ref
encoding: ParameterEncoding = URLEncoding.default,

如果我有一個(gè)參數(shù)字典火脉,這個(gè)參數(shù)字典又是如何從客戶端傳遞到服務(wù)器的呢先匪?Alamofire中是這樣實(shí)現(xiàn)的:URLEncoding編碼方式會(huì)把參數(shù)直接拼接到URL中或通過requesthttpBody傳值。JSONEncoding編碼方式會(huì)把參數(shù)字典編碼成JSONData后賦值給requesthttpBody功蜓。


a庆冕、ParameterEncoding協(xié)議
  • ParameterEncoding協(xié)議:是一個(gè)定義如何編碼的協(xié)議响巢,常會(huì)用到的URLEncodingJSONEncoding編碼方式都遵循這個(gè)協(xié)議
  • encode函數(shù):用來完成對(duì)傳入的parameters的編碼工作,把參數(shù)綁定到urlRequest之中
  • urlRequest參數(shù):需要實(shí)現(xiàn)URLRequestConvertible協(xié)議燕刻,實(shí)現(xiàn)該協(xié)議的對(duì)象能夠轉(zhuǎn)換成URLRequest
  • parameters參數(shù):類型為Parameters偶摔,也就是字典:public typealias Parameters = [String: Any]
  • 函數(shù)返回值類型為URLRequest:至于返回的urlRequest是不是之前的urlRequest暇唾,這個(gè)不一定。另一個(gè)比較重要的是該函數(shù)會(huì)拋出異常
public protocol ParameterEncoding
{
    func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest
}

b辰斋、URLEncoding
? url-encoded編碼方式
  • URLEncoding會(huì)生成一個(gè)使用url-encoded方式編碼過的字符串策州,可以添加到url或是請(qǐng)求體中,至于使用何種方式取決于編碼的目的地參數(shù)
  • http 頭中的 Content-Type 字段會(huì)被設(shè)置為 application/x-www-form-urlencoded; charset=utf-8
  • 由于沒有一個(gè)明確的規(guī)定如何編碼一個(gè)集合宫仗,我們?cè)谶@里約定够挂,對(duì)于數(shù)組,我們會(huì)在名字后面加上一個(gè)中括號(hào)[] 如(foo[]=1&foo[]=2)锰什,對(duì)于字典下硕,則在中括號(hào)中再加入鍵值丁逝,如foo[bar]=baz
public struct URLEncoding: ParameterEncoding
? 編碼后的參數(shù)位置:定義編碼后的字符串是放到url還是請(qǐng)求體中
  • methodDependent:對(duì)于 .get.head梭姓、.delete 請(qǐng)求霜幼,它會(huì)將已編碼查詢字符串應(yīng)用到現(xiàn)有的查詢字符串中;對(duì)于其他類型的請(qǐng)求誉尖,會(huì)將其設(shè)置為 HTTP body
  • queryString: 將編碼字符串設(shè)置或追加到請(qǐng)求的 URL
  • httpBody:將編碼字符串設(shè)置為 URLRequestHTTP body
public enum Destination
{
    case methodDependent
    case queryString
    case httpBody

    // 是否將編碼字符串放到url中
    func encodesParametersInURL(for method: HTTPMethod) -> Bool
    {
        switch self
        {
        case .methodDependent: return [.get, .head, .delete].contains(method)
        case .queryString: return true
        case .httpBody: return false
        }
    }
}
? ParameterEncoding 協(xié)議的實(shí)現(xiàn):編碼并設(shè)置 request對(duì)象
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest
{
    // 獲取 request
    var urlRequest = try urlRequest.asURLRequest()
    // 獲取參數(shù)罪既,如果沒有參數(shù),那么直接返回
    guard let parameters = parameters else { return urlRequest }

    // 獲取請(qǐng)求方法铡恕,同時(shí)根據(jù)請(qǐng)求方法來判斷是否需要編碼參數(shù)到 url 中
    if let method = urlRequest.method, destination.encodesParametersInURL(for: method)// 直接編碼到 url 中
    {
        // 獲取 url
        guard let url = urlRequest.url else
        {
            throw AFError.parameterEncodingFailed(reason: .missingURL)
        }

        // 構(gòu)建一個(gè)URLComponents對(duì)象琢感,并在其中添加參數(shù)
        if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty
        {
            // 此處 map 是 optional 的map,如果 optionvalue 不為空探熔,則會(huì)調(diào)用 map 內(nèi)的閉包
            // 如果 url 中本來就有一部分參數(shù)了驹针,那么就將新的參數(shù)附加在后面
            let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)
            urlComponents.percentEncodedQuery = percentEncodedQuery
            urlRequest.url = urlComponents.url
        }
    }
    else// 這里是要添加到請(qǐng)求體中
    {
        // 如果請(qǐng)求頭尚未設(shè)置 Content-Type
        if urlRequest.headers["Content-Type"] == nil
        {
            // 在請(qǐng)求頭中設(shè)置編碼格式
            urlRequest.headers.update(.contentType("application/x-www-form-urlencoded; charset=utf-8"))
        }
        // 編碼到請(qǐng)求體中
        urlRequest.httpBody = Data(query(parameters).utf8)
    }
}
? 將參數(shù)編碼為查詢字符串

可以看到URLEncoding方式是將parameters通過添加 & ,= 的方式拼接到url身后诀艰。

private func query(_ parameters: [String: Any]) -> String
{
    // 創(chuàng)建一個(gè)數(shù)組柬甥,這個(gè)數(shù)組中存放的是元組數(shù)據(jù),元組中存放的是key和字符串類型的value
    var components: [(String, String)] = []

    // 遍歷參數(shù)其垄,對(duì)參數(shù)做進(jìn)一步的處理苛蒲,然后拼接到數(shù)組中
    for key in parameters.keys.sorted(by: <)
    {
        let value = parameters[key]!

        // key的類型是String,但value的類型是any
        // 也就是說value不一定是字符串绿满,也有可能是數(shù)組或字典臂外,因此針對(duì)value需要做進(jìn)一步的處理
        components += queryComponents(fromKey: key, value: value)
    }
    // 把元組內(nèi)部的數(shù)據(jù)用=號(hào)拼接,然后用符號(hào)&把數(shù)組拼接成字符串
    return components.map { "\($0)=\($1)" }.joined(separator: "&")
}
? 根據(jù)value類型進(jìn)行百分號(hào)轉(zhuǎn)義

key的類型是String喇颁,但value的類型是any漏健,也就是說value不一定是字符串,也有可能是數(shù)組或字典橘霎,因此針對(duì)value需要做進(jìn)一步的處理漾肮。

public func queryComponents(fromKey key: String, value: Any) -> [(String, String)]
{
    // 最終結(jié)果
    var components: [(String, String)] = []
    
    switch value
    {
    // 如果value依然是字典,那么鍵后面加上[key]再調(diào)用自身茎毁,也就是做遞歸處理
    case let dictionary as [String: Any]:
        for (nestedKey, value) in dictionary
        {
            components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value)
        }
    // 如果value是數(shù)組,通過遍歷在鍵后面加上[]后依然調(diào)用自身
    // 把數(shù)組拼接到url中的規(guī)則是這樣的:數(shù)組["a", "b", "c"]拼接后的結(jié)果是key[]="a"&key[]="b"&key[]="c"
    case let array as [Any]:
        for value in array
        {
            components += queryComponents(fromKey: arrayEncoding.encode(key: key), value: value)
        }
    // 如果value是NSNumber忱辅,要進(jìn)一步判斷這個(gè)NSNumber是不是表示布爾類型
    case let number as NSNumber:
        if number.isBool// bool 值的處理
        {
            components.append((escape(key), escape(boolEncoding.encode(value: number.boolValue))))
        }
        else
        {
            components.append((escape(key), escape("\(number)")))
        }
    // 如果value是Bool七蜘,轉(zhuǎn)義后直接拼接進(jìn)數(shù)組
    case let bool as Bool:
        components.append((escape(key), escape(boolEncoding.encode(value: bool))))
    // 其他情況,轉(zhuǎn)義后直接拼接進(jìn)數(shù)組
    default:
        components.append((escape(key), escape("\(value)")))
    }
    return components
}
? 百分號(hào)轉(zhuǎn)義

上邊函數(shù)中的key已經(jīng)是字符串類型了墙懂,那么為什么還要進(jìn)行轉(zhuǎn)義的橡卤?這是因?yàn)樵?code>url中有些字符是不允許的,這些字符會(huì)干擾url的解析损搬。:#[]@!$&'()*+,;=這些字符必須要做轉(zhuǎn)義碧库,而?/可以不用轉(zhuǎn)義柜与。轉(zhuǎn)義的意思就是百分號(hào)編碼。

public func escape(_ string: String) -> String
{
    // 使用了系統(tǒng)自帶的函數(shù)來進(jìn)行百分號(hào)編碼
    string.addingPercentEncoding(withAllowedCharacters: .afURLQueryAllowed) ?? string
}
? 進(jìn)行驗(yàn)證

將核心流程范例里的url變?yōu)槭褂?code>URLEncoding對(duì)參數(shù)編碼后的字符串嵌灰,并且向parameternil弄匕,再進(jìn)行請(qǐng)求依然會(huì)得到同樣的結(jié)果。

let url = "http://suggest.taobao.com/sug?code=utf-8&q=%E8%A2%9C%E5%AD%90"
AF.request(url, method: .get, parameters: nil)

輸出結(jié)果為:

json字符串:
{"result":[["襪子女","981907.3641717125"],["襪子男","774851.6019127683"],["襪子女冬","627697.2976415411"],["襪子女中筒襪","350247.61393421463"],["襪子男長(zhǎng)襪","615388.650406337"],["襪子男冬","703565.6863077434"],["襪子女ins潮","285066.1735027441"],["襪子男純棉","303921.3111099697"],["襪子女冬季 加絨","335283.97906813805"],["襪子男中筒","331615.3393111639"]]}

d沽瞭、JSONParameterEncoder

JSONEncoding的主要作用是把參數(shù)以JSON的形式編碼到request之中,當(dāng)然是通過requesthttpBody進(jìn)行賦值的驹溃。JSONEncoding提供了兩種處理函數(shù)城丧,一種是對(duì)普通的字典參數(shù)進(jìn)行編碼,另一種是對(duì)字符串?dāng)?shù)組進(jìn)行編碼豌鹤,處理這兩種情況的函數(shù)基本上是相同的亡哄。

? 使用 json 編碼參數(shù)
public struct JSONEncoding: ParameterEncoding
{
    // 使用默認(rèn)參數(shù)構(gòu)造
    public static var `default`: JSONEncoding { JSONEncoding() }

    // 讓其擁有更好的展示效果
    public static var prettyPrinted: JSONEncoding { JSONEncoding(options: .prettyPrinted) }

    // JSON序列化的寫入方式
    public let options: JSONSerialization.WritingOptions
    public init(options: JSONSerialization.WritingOptions = [])
    {
        self.options = options
    }
}
? ParameterEncoding 協(xié)議的實(shí)現(xiàn):將parameters轉(zhuǎn)化為二進(jìn)制放入httpBody里

因此也很好理解,為什么get方法的安全性不如post方法布疙,因?yàn)?code>get方法將請(qǐng)求參數(shù)暴露在外蚊惯,而post放在請(qǐng)求體內(nèi)。

public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest
{
    var urlRequest = try urlRequest.asURLRequest()

    guard let parameters = parameters else { return urlRequest }

    do
    {
        // json 格式化數(shù)據(jù)
        let data = try JSONSerialization.data(withJSONObject: parameters, options: options)

        // 如果 Content-Type 尚未設(shè)置
        if urlRequest.headers["Content-Type"] == nil
        {
            // 設(shè)置請(qǐng)求頭的Content-Type
            urlRequest.headers.update(.contentType("application/json"))
        }
        // 加上請(qǐng)求體
        urlRequest.httpBody = data
    }
    catch
    {
        throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
    }

    return urlRequest
}
還有一個(gè)encode方法拐辽,基本同上一致拣挪,但是可以接受數(shù)組的 json
public func encode(_ urlRequest: URLRequestConvertible, withJSONObject jsonObject: Any? = nil) throws -> URLRequest

5、Validation

.validate(statusCode: [200])
a俱诸、響應(yīng)碼的認(rèn)證

validateDataRequest的擴(kuò)展中增加的方法菠劝。我們這里先看對(duì)statusCode的認(rèn)證。

extension DataRequest
{

    public typealias Validation = (URLRequest?, HTTPURLResponse, Data?) -> ValidationResult

    public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int
    {
        validate { [unowned self] _, response, _ in
            self.validate(statusCode: acceptableStatusCodes, response: response)
        }
    }
}

那么到底是如何認(rèn)證的呢睁搭?這里我們?cè)龠M(jìn)入它的內(nèi)部實(shí)現(xiàn)赶诊。

fileprivate func validate<S: Sequence>(statusCode acceptableStatusCodes: S,
                                       response: HTTPURLResponse)
    -> ValidationResult
    where S.Iterator.Element == Int
{
    // 若validate傳入code包含response響應(yīng)的code則認(rèn)證成功
    if acceptableStatusCodes.contains(response.statusCode)
    {
        return .success(())
    }
    // 若不包括,則返回失敗园骆,拋出AFError
    else
    {
        let reason: ErrorReason = .unacceptableStatusCode(code: response.statusCode)
        return .failure(AFError.responseValidationFailed(reason: reason))
    }
}

b舔痪、錯(cuò)誤驗(yàn)證

acceptableStatusCodes為我們實(shí)際在validate中傳入的statusCode數(shù)組,在例子中為[200]锌唾。將例子中的[200]變?yōu)閇201]就會(huì)打印出拋出的錯(cuò)誤锄码,因?yàn)?code>http的正確返回碼為200,我們傳入的數(shù)組不包含200晌涕,就會(huì)拋出錯(cuò)誤滋捶。當(dāng)然我們也可以在例子中不調(diào)用validate這樣就不會(huì)有這些驗(yàn)證。

錯(cuò)誤信息:responseValidationFailed(reason: Alamofire.AFError.ResponseValidationFailureReason.unacceptableStatusCode(code: 200))

c余黎、響應(yīng)內(nèi)容的認(rèn)證

這里只介紹了statusCode響應(yīng)碼的認(rèn)證重窟,實(shí)際上validate里還有對(duì)contentType響應(yīng)內(nèi)容等的認(rèn)證。

public func validate<S: Sequence>(contentType acceptableContentTypes: @escaping @autoclosure () -> S) -> Self where S.Iterator.Element == String
{
    validate { [unowned self] _, response, data in
        self.validate(contentType: acceptableContentTypes(), response: response, data: data)
    }
}
fileprivate func validate<S: Sequence>(contentType acceptableContentTypes: S,
                                       response: HTTPURLResponse,
                                       data: Data?)
    -> ValidationResult
    where S.Iterator.Element == String
{
    guard let data = data, !data.isEmpty else { return .success(()) }

    return validate(contentType: acceptableContentTypes, response: response)
}

6惧财、DispatchQueue

a巡扇、responseData

我們會(huì)把請(qǐng)求返回的處理放入queue所傳入的隊(duì)列中扭仁,若我們沒有向queue傳入隊(duì)列,那么會(huì)默認(rèn)把處理放入主隊(duì)列中厅翔。范例子中我們選擇將處理放入全局隊(duì)列中乖坠。

.responseData(queue: DispatchQueue.global())

public func responseData(queue: DispatchQueue = .main...)

b、DispatchQueue+Alamofire.swift

可以順便看一下 DispatchQueue+Alamofire.swift 文件知给,里面增加了一個(gè)名為after的延遲調(diào)用方法瓤帚,沒有添加public關(guān)鍵字,那么說明只想在Alamofire內(nèi)部調(diào)用涩赢,而不想暴露給我們戈次。

extension DispatchQueue
{
    func after(_ delay: TimeInterval, execute closure: @escaping () -> Void)
    {
        asyncAfter(deadline: .now() + delay, execute: closure)
    }
}

c、Request.swift
// 所有內(nèi)部異步操作的串行隊(duì)列
public let underlyingQueue: DispatchQueue
// 用于所有序列化操作的隊(duì)列筒扒。默認(rèn)情況下怯邪,它是一個(gè)以 underlyingQueue 為目標(biāo)的串行隊(duì)列
public let serializationQueue: DispatchQueue

init(id: UUID = UUID(),
     underlyingQueue: DispatchQueue,
     serializationQueue: DispatchQueue,
     eventMonitor: EventMonitor?,
     interceptor: RequestInterceptor?,
     delegate: RequestDelegate)
{
    self.id = id
    self.underlyingQueue = underlyingQueue
    self.serializationQueue = serializationQueue
    self.eventMonitor = eventMonitor
    self.interceptor = interceptor
    self.delegate = delegate
}

7、AFError

a花墩、AFDataResponse
.responseData(queue: DispatchQueue.global())
{ (responseData) in
    switch responseData.result
    {
    case .failure(let error):
      print("錯(cuò)誤信息:\(error)")
    }
}

public func responseData(completionHandler: @escaping (AFDataResponse<Data>) -> Void)
public typealias AFDataResponse<Success> = DataResponse<Success, AFError>

b悬秉、AFError的類型

Alamofire里的錯(cuò)誤都已經(jīng)被定義到AFError中,我們打開 AFError.swift冰蘑。在下面列舉的AFError的類型中和泌,除了invalidURL的參數(shù)是遵循協(xié)議URLConvertible,其余類型的參數(shù)又由枚舉類型組成祠肥。這樣就將很多種的錯(cuò)誤類型武氓,包括在了這5種類型中。

//這里為了看得方便仇箱,將AFError重新整理了下县恕,源碼中的內(nèi)容要更多,但類型是一樣的
enum AFError 
{     
  case invalidURL(url: URLConvertible) //無效的URL 
  case parameterEncodingFailed(reason: ParameterEncodingFailureReason) //請(qǐng)求參數(shù)編碼失敗 
  case multipartEncodingFailed(reason: MultipartEncodingFailureReason) //多部分編碼失敗 
  case responseValidationFailed(reason: ResponseValidationFailureReason) //響應(yīng)驗(yàn)證失敗 
  case responseSerializationFailed(reason: ResponseSerializationFailureReason) //響應(yīng)序列化失敗 
}

c剂桥、localizedDescription

AFError.swift里面還有很多內(nèi)容忠烛,例如對(duì)localizedDescription的實(shí)現(xiàn),在請(qǐng)求拋出錯(cuò)誤時(shí)权逗,我們打印error.localizedDescription看到的錯(cuò)誤信息就是在這里定義的美尸。

// 請(qǐng)求參數(shù)編碼失敗
extension AFError.ParameterEncodingFailureReason
{
    var localizedDescription: String
    {
        switch self
        {
        case .missingURL:
            return "URL request to encode was missing a URL"
        case let .jsonEncodingFailed(error):
            return "JSON could not be encoded because of error:\n\(error.localizedDescription)"
        case let .customEncodingFailed(error):
            return "Custom parameter encoder failed with error: \(error.localizedDescription)"
        }
    }
}

8、DataResponse

.responseData(queue: DispatchQueue.global())
{ (responseData) in
a斟薇、Any類型

假如說序列化后的數(shù)據(jù)是data火惊,最直接的想法就是把data設(shè)置為Any類型,在實(shí)際用到的時(shí)候在進(jìn)行判斷奔垦,這也是最普通的一種開發(fā)思維。現(xiàn)在我們就要打破這種思維尸疆。我們需要封裝一個(gè)對(duì)象椿猎,這個(gè)對(duì)象能夠表達(dá)任何結(jié)果惶岭,這就用到了swift中的泛型。

public func responseJSON(queue: DispatchQueue = .main,
                         dataPreprocessor: DataPreprocessor = JSONResponseSerializer.defaultDataPreprocessor,
                         emptyResponseCodes: Set<Int> = JSONResponseSerializer.defaultEmptyResponseCodes,
                         emptyRequestMethods: Set<HTTPMethod> = JSONResponseSerializer.defaultEmptyRequestMethods,
                         options: JSONSerialization.ReadingOptions = .allowFragments,
                         completionHandler: @escaping (AFDataResponse<Any>) -> Void) -> Self

b犯眠、Result<Any>

上邊的這個(gè)函數(shù)的主要目的是把請(qǐng)求成功后的結(jié)果序列化為JSON按灶。completionHandler函數(shù)的參數(shù)類型為DataResponse<Any>,其中的Any就會(huì)傳遞給Result筐咧,也就是Result<Any>鸯旁。

public typealias AFDataResponse<Success> = DataResponse<Success, AFError>
public struct DataResponse<Success, Failure: Error>
{
    public let result: Result<Success, Failure>
}

@frozen public enum Result<Success, Failure> where Failure : Error
{
    /// A success, storing a `Success` value.
    case success(Success)

    /// A failure, storing a `Failure` value.
    case failure(Failure)
}

c、jsonObject

那么問題來了量蕊,不是把數(shù)據(jù)解析成JSON了嗎铺罢?為什么要返回Any類型呢?json本質(zhì)上很類似于JavaScript中的對(duì)象和數(shù)組残炮。JSONSerialization.jsonObject返回的類型是Any韭赘,這是因?yàn)榻馕龊蟮臄?shù)據(jù)有可能是數(shù)組,也有可能是字典势就。當(dāng)然如果不是這兩種格式的數(shù)據(jù)泉瞻,使用JSONSerialization.jsonObject解析會(huì)拋出異常。

// 字典
{
    "people":
    [
        {"firstName":"Brett","lastName":"McLaughlin","email":"aaaa"},
        {"firstName":"Jason","lastName":"Hunter","email":"bbbb"},
        {"firstName":"Elliotte","lastName":"Harold","email":"cccc"}
    ]
}

// 數(shù)組
[
    "a",
    "b",
    "c"
]

d苞冯、打印信息

為了能夠打印更加詳細(xì)的信息袖牙,又使DataResponse實(shí)現(xiàn)了CustomStringConvertibleCustomDebugStringConvertible協(xié)議。

description
extension DataResponse: CustomStringConvertible, CustomDebugStringConvertible
{
    public var description: String
    {
        "\(result)"
    }
}
debugDescription
extension DataResponse: CustomStringConvertible, CustomDebugStringConvertible
{
    public var debugDescription: String
    {
        guard let urlRequest = request else { return "[Request]: None\n[Result]: \(result)" }

        let requestDescription = DebugDescription.description(of: urlRequest)

        let responseDescription = response.map { response in
            let responseBodyDescription = DebugDescription.description(for: data, headers: response.headers)

            return """
            \(DebugDescription.description(of: response))
                \(responseBodyDescription.indentingNewlines())
            """
        } ?? "[Response]: None"

        let networkDuration = metrics.map { "\($0.taskInterval.duration)s" } ?? "None"

        return """
        \(requestDescription)
        \(responseDescription)
        [Network Duration]: \(networkDuration)
        [Serialization Duration]: \(serializationDuration)s
        [Result]: \(result)
        """
    }
}

續(xù)文見下篇:IOS源碼解析:Alamofire 5 功能模塊


Demo

Demo在我的Github上舅锄,歡迎下載鞭达。
SourceCodeAnalysisDemo

參考文獻(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過簡(jiǎn)信或評(píng)論聯(lián)系作者巧娱。
  • 序言:七十年代末碉怔,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子禁添,更是在濱河造成了極大的恐慌撮胧,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件老翘,死亡現(xiàn)場(chǎng)離奇詭異芹啥,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)铺峭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門墓怀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人卫键,你說我怎么就攤上這事傀履。” “怎么了莉炉?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵钓账,是天一觀的道長(zhǎng)碴犬。 經(jīng)常有香客問我,道長(zhǎng)梆暮,這世上最難降的妖魔是什么服协? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮啦粹,結(jié)果婚禮上偿荷,老公的妹妹穿的比我還像新娘。我一直安慰自己唠椭,他們只是感情好跳纳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著泪蔫,像睡著了一般棒旗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上撩荣,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天铣揉,我揣著相機(jī)與錄音,去河邊找鬼餐曹。 笑死逛拱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的台猴。 我是一名探鬼主播朽合,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼饱狂!你這毒婦竟也來了曹步?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤休讳,失蹤者是張志新(化名)和其女友劉穎讲婚,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體俊柔,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡筹麸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了雏婶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片物赶。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖留晚,靈堂內(nèi)的尸體忽然破棺而出酵紫,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布奖地,位于F島的核電站状蜗,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏鹉动。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一宏邮、第九天 我趴在偏房一處隱蔽的房頂上張望泽示。 院中可真熱鬧,春花似錦蜜氨、人聲如沸械筛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)埋哟。三九已至,卻和暖如春郎汪,著一層夾襖步出監(jiān)牢的瞬間赤赊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工煞赢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留抛计,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓照筑,卻偏偏與公主長(zhǎng)得像吹截,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子凝危,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容