Alamofire 5 的使用 - 基本用法

Alamofire 5 的使用 - 基本用法

https://github.com/Alamofire/Alamofire/blob/master/Documentation/Usage.md

特性

可鏈接的請(qǐng)求/響應(yīng)函數(shù)

URL / JSON 參數(shù)編碼

上傳文件/ Data / 流 / 多表單數(shù)據(jù)

使用請(qǐng)求或者恢復(fù)數(shù)據(jù)下載文件

使用URLCredential 進(jìn)行身份驗(yàn)證

HTTP 響應(yīng)驗(yàn)證

帶有進(jìn)度的上傳和下載閉包

cURL 命令的輸出

]動(dòng)態(tài)調(diào)整和重試請(qǐng)求

TLS 證書和公鑰固定

網(wǎng)絡(luò)可達(dá)性

全面的單元和集成測(cè)試覆蓋率

組件庫(kù)

為了讓Alamofire 專注于核心網(wǎng)絡(luò)實(shí)現(xiàn)聂渊,Alamofire 軟件基金會(huì)創(chuàng)建了額外的組件庫(kù),為 Alamofire 生態(tài)系統(tǒng)帶來額外的功能胡岔。

AlamofireImage:一個(gè)圖片庫(kù)穿撮,包括圖像響應(yīng)序列化器、UIImage?和?UIImageView?的擴(kuò)展、自定義圖像濾鏡界拦、自動(dòng)清除內(nèi)存緩存和基于優(yōu)先級(jí)的圖像下載系統(tǒng)。

AlamofireNetworkActivityIndicator: 使用 Alamofire 控制 iOS 上網(wǎng)絡(luò)活動(dòng)指示器的可見性梗劫。它包含可配置的延遲計(jì)時(shí)器享甸,有助于減少閃爍,并且可以支持不由 Alamofire 管理的 URLSession 實(shí)例梳侨。

要求的使用環(huán)境

iOS 10.0+ / macOS 10.12+ / tvOS 10.0+ / watchOS 3.0+

Xcode 10.2+

Swift 5+

安裝方法

CocoaPods

source 'https://github.com/CocoaPods/Specs.git'

platform :ios, '10.0'

use_frameworks!


target '項(xiàng)目名稱'?do

????pod 'Alamofire', '~> 5.0'end

iOS 版本和?Alamofire?版本可以自己根據(jù)實(shí)際情況自行更改蛉威。CocoaPods 是比較常用的第三方庫(kù)管理工具,其他方法就不詳細(xì)說了走哺。其他集成方法可以查看原文檔蚯嫌。

基本用法

介紹

Alamofire 為 HTTP 網(wǎng)絡(luò)請(qǐng)求提供了一個(gè)優(yōu)雅且可組合的接口。它沒有實(shí)現(xiàn)自己的 HTTP 網(wǎng)絡(luò)功能割坠。取而代之的是齐帚,它建立在由 Foundation 框架提供的?URL 加載系統(tǒng)之上妒牙。系統(tǒng)的核心是URLSession?和?URLSessionTask?子類彼哼。Alamofire 將這些 APIs 和許多其他 APIs 封裝在一個(gè)更易于使用的接口中,并提供使用 HTTP 網(wǎng)絡(luò)進(jìn)行現(xiàn)代應(yīng)用程序開發(fā)所必需的各種功能湘今。但是敢朱,了解 Alamofire 的許多核心行為來自何處很重要,因此熟悉 URL 加載系統(tǒng)非常重要摩瞎。歸根結(jié)底拴签,Alamofire 的網(wǎng)絡(luò)特性受到該系統(tǒng)功能的限制,應(yīng)該始終記住并遵守其行為和最佳實(shí)踐旗们。

此外蚓哩,Alamofire(以及 URL 加載系統(tǒng))中的聯(lián)網(wǎng)是異步完成的。異步編程可能會(huì)讓不熟悉這個(gè)概念的程序員感到沮喪上渴,但是有很好的理由這樣做岸梨。

另外:AF?命名空間和引用

以前的Alamofire 文檔使用了類似Alamofire.request()?的示例喜颁。這個(gè) API 雖然看起來需要?Alamofire?前綴,但實(shí)際上在沒有它的情況下也可以曹阔。request?方法和其他函數(shù)在任何帶有?import Alamofire?的文件中都是全局可用的半开。從 Alamofire 5 開始,此功能已被刪除赃份,被更改為?AF?寂拆,它是對(duì)?Session.default?的引用。這允許 Alamofire 提供同樣的便利功能抓韩,同時(shí)不必每次使用 Alamofire 時(shí)都污染全局命名空間纠永,也不必全局復(fù)制?Session?API。類似地谒拴,由 Alamofire 擴(kuò)展的類型將使用?af?屬性擴(kuò)展來將 Alamofire 添加的功能與其他擴(kuò)展分開渺蒿。

發(fā)起請(qǐng)求

Alamofire 為發(fā)出 HTTP 請(qǐng)求提供了多種方便的方法。最簡(jiǎn)單的是彪薛,只需提供一個(gè)可以轉(zhuǎn)換為 URL 的?String?:

AF.request("https://httpbin.org/get").response { response in

????debugPrint(response)

}

所有示例都需要在源文件中的某個(gè)位置import Alamofire茂装。

這實(shí)際上是AlamofireSession?類型上用于發(fā)出請(qǐng)求的兩個(gè)頂層 APIs 的一種形式。它的完整定義如下:

open?func?request<Parameters: Encodable>(

????_?convertible: URLConvertible,

????method: HTTPMethod = .get,

????parameters: Parameters? = nil,

????encoder: ParameterEncoder = URLEncodedFormParameterEncoder.default,

????headers: HTTPHeaders? = nil,

????interceptor: RequestInterceptor? = nil

)?-> DataRequest

此方法創(chuàng)建一個(gè)DataRequest善延,同時(shí)允許組合來自各個(gè)組件(如method?和?headers?)的請(qǐng)求少态,同時(shí)還允許每個(gè)傳入?RequestInterceptors?和?Encodable?參數(shù)。

還有其他方法允許您使用Parameters?字典和?ParameterEncoding?類型來發(fā)出請(qǐng)求易遣。不再推薦此 API彼妻,最終將被棄用并從 Alamofire 中刪除。

這個(gè)API 的第二個(gè)版本要簡(jiǎn)單得多:

open?func?request(

????_?urlRequest: URLRequestConvertible,

????interceptor: RequestInterceptor? = nil

)?-> DataRequest

此方法為遵循Alamofire?的?URLRequestConvertible?協(xié)議的任何類型創(chuàng)建?DataRequest?豆茫。所有不同于前一版本的參數(shù)都封裝在該值中侨歉,這會(huì)產(chǎn)生非常強(qiáng)大的抽象。這將在我們的高級(jí)用法中討論揩魂。

HTTP Methods

HTTPMethod?類型列出了?RFC 7231 §4.3?中定義的 HTTP 方法:

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?options = HTTPMethod(rawValue: "OPTIONS")

????public?static?let?patch = HTTPMethod(rawValue: "PATCH")

????public?static?let?post = HTTPMethod(rawValue: "POST")

????public?static?let?put = HTTPMethod(rawValue: "PUT")

????public?static?let?trace = HTTPMethod(rawValue: "TRACE")


????public?let?rawValue: String


????public?init(rawValue: String) {

????????self.rawValue = rawValue

????}

}

這些值可以作為method?參數(shù)傳遞給?AF.request?API:

AF.request("https://httpbin.org/get")AF.request("https://httpbin.org/post", method: .post)AF.request("https://httpbin.org/put", method: .put)AF.request("https://httpbin.org/delete", method: .delete)

重要的是要記住思恐,不同的HTTP 方法可能有不同的語義,需要不同的參數(shù)編碼垒迂,這取決于服務(wù)器的期望堂污。例如,URLSession 或 Alamofire 不支持在GET?請(qǐng)求中傳遞?body?數(shù)據(jù)倦挂,并將返回錯(cuò)誤畸颅。

Alamofire 還提供了對(duì)?URLRequest?的擴(kuò)展,以橋接將字符串返回到?HTTPMethod?值的?httpMethod?屬性:

public?extension?URLRequest?{

????/// Returns the `httpMethod` as Alamofire's `HTTPMethod` type.

????var?method: HTTPMethod??{

????????get?{ return?httpMethod.flatMap(HTTPMethod.init) }

????????set?{ httpMethod = newValue?.rawValue }

????}

}

如果需要使用Alamofire 的HTTPMethod?類型不支持的 HTTP 方法方援,可以擴(kuò)展該類型以添加自定義值:

extension?HTTPMethod?{

????static?let?custom = HTTPMethod(rawValue: "CUSTOM")

}

請(qǐng)求參數(shù)和參數(shù)編碼器

Alamofire 支持將任何?Encodable?類型作為請(qǐng)求的參數(shù)没炒。然后,這些參數(shù)通過遵循?ParameterEncoder?協(xié)議的類型傳遞犯戏,并添加到?URLRequest?中送火,然后通過網(wǎng)絡(luò)發(fā)送祖很。Alamofire 包含兩種遵循?ParameterEncoder?的類型:JSONParameterEncoder?和?URLEncodedFormParameterEncoder?。這些類型涵蓋了現(xiàn)代服務(wù)使用的最常見的編碼漾脂。

struct?Login: Encodable?{

????let?email: String

????let?password: String

}

let?login = Login(email: "test@test.test", password: "testPassword")

AF.request("https://httpbin.org/post",

???????????method: .post,

???????????parameters: login,

???????????encoder: JSONParameterEncoder.default).response { response in

????debugPrint(response)

}

URLEncodedFormParameterEncoder

URLEncodedFormParameterEncoder?將值編碼為 URL 編碼字符串假颇,以將其設(shè)置為或附加到任何現(xiàn)有 URL 查詢字符串,或設(shè)置為請(qǐng)求的 HTTP body骨稿。通過設(shè)置編碼的目的地笨鸡,可以控制編碼字符串的設(shè)置位置。URLEncodedFormParameterEncoder.Destination?枚舉有三種情況:

.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è)置為?URLRequest?的 HTTP body倦踢。

如果尚未設(shè)置Content-Type,那么會(huì)把具有HTTP body 的已編碼請(qǐng)求的 HTTP header 設(shè)置為application/x-www-form-urlencoded; charset=utf-8侠草。

在內(nèi)部辱挥,URLEncodedFormParameterEncoder?使用?URLEncodedFormEncoder?把?Encodable?類型編碼為 URL 編碼形式的?String。此編碼器可用于自定義各種類型的編碼边涕,包括使用ArrayEncoding?的?Array晤碘、使用BoolEncoding?的Bool、使用DataEncoding?的?Data功蜓、使用DateEncoding?的?Date园爷、使用KeyEncoding?的 keys 以及使用?SpaceEncoding?的空格。

使用URL 編碼參數(shù)的 GET 請(qǐng)求

let?parameters = ["foo": "bar"]

// 下面三種方法都是等價(jià)的AF.request("https://httpbin.org/get", parameters: parameters) // encoding defaults to `URLEncoding.default`AF.request("https://httpbin.org/get", parameters: parameters, encoder: URLEncodedFormParameterEncoder.default)AF.request("https://httpbin.org/get", parameters: parameters, encoder: URLEncodedFormParameterEncoder(destination: .methodDependent))

// https://httpbin.org/get?foo=bar

使用URL 編碼參數(shù)的 POST 請(qǐng)求

let?parameters: [String: [String]] = [

????"foo": ["bar"],

????"baz": ["a", "b"],

????"qux": ["x", "y", "z"]

]

// 下面三種方法都是等價(jià)的AF.request("https://httpbin.org/post", method: .post, parameters: parameters)AF.request("https://httpbin.org/post", method: .post, parameters: parameters, encoder: URLEncodedFormParameterEncoder.default)AF.request("https://httpbin.org/post", method: .post, parameters: parameters, encoder: URLEncodedFormParameterEncoder(destination: .httpBody))

// HTTP body: "qux[]=x&qux[]=y&qux[]=z&baz[]=a&baz[]=b&foo[]=bar"

配置已編碼參數(shù)的排序

從Swift 4.2 開始式撼,Swift 的Dictionary?類型使用的隨機(jī)算法在運(yùn)行時(shí)產(chǎn)生一個(gè)隨機(jī)的內(nèi)部順序童社,并且在應(yīng)用程序的每次啟動(dòng)都是不同的。這可能會(huì)導(dǎo)致已編碼參數(shù)更改順序端衰,這可能會(huì)影響緩存和其他行為叠洗。默認(rèn)情況下,URLEncodedFormEncoder?將對(duì)其編碼的鍵值對(duì)進(jìn)行排序旅东。雖然這會(huì)為所有?Encodable?類型生成常量輸出,但它可能與該類型實(shí)現(xiàn)的實(shí)際編碼順序不匹配十艾。您可以將?alphabetizeKeyValuePairs?設(shè)置為?false?以返回到實(shí)現(xiàn)的順序抵代,因此這將變成隨機(jī)?Dictionary?順序。

您可以創(chuàng)建自己的URLEncodedFormParameterEncoder忘嫉,在初始化時(shí)荤牍,可以在URLEncodedFormEncoder?參數(shù)中設(shè)置?alphabetizeKeyValuePairs?的值:

let?encoder = URLEncodedFormParameterEncoder(encoder: URLEncodedFormEncoder(alphabetizeKeyValuePairs: false))

配置Array?參數(shù)的編碼

由于沒有關(guān)于如何對(duì)集合類型進(jìn)行編碼的規(guī)范案腺,默認(rèn)情況下,Alamofire 遵循以下約定:將[]?附加到數(shù)組值的鍵(foo[]=1&foo[]=2)康吵,并附加由中括號(hào)包圍的嵌套字典值的鍵(foo[bar]=baz)劈榨。

URLEncodedFormEncoder.ArrayEncoding?枚舉提供了以下對(duì)數(shù)組參數(shù)進(jìn)行編碼的方法:

.brackets?- 為每個(gè)值在鍵后附加一組空的中括號(hào)。這是默認(rèn)情況晦嵌。

.noBrackets?- 不附加括號(hào)同辣。key 按原樣編碼。

默認(rèn)情況下惭载,Alamofire 使用.brackets?編碼旱函,其中?foo = [1, 2]?編碼為?foo[]=1&foo[]=2。

使用.noBrackets?編碼描滔,foo = [1, 2]?編碼為?foo=1&foo=2棒妨。

您可以創(chuàng)建自己的URLEncodedFormParameterEncoder,在初始化時(shí)含长,可以在URLEncodedFormEncoder?參數(shù)中設(shè)置?arrayEncoding?的值:

let?encoder = URLEncodedFormParameterEncoder(encoder: URLEncodedFormEncoder(arrayEncoding: .noBrackets))

配置Bool?參數(shù)的編碼

URLEncodedFormEncoder.BoolEncoding?枚舉提供了以下用于編碼?Bool?參數(shù)的方法:

.numeric?- 把?true?編碼為?1券腔,false?編碼為?0。這是默認(rèn)情況拘泞。

.literal?- 把?true?和?false?編碼為字符串文本颅眶。

默認(rèn)情況下,Alamofire 使用.numeric田弥。

您可以創(chuàng)建自己的URLEncodedFormParameterEncoder涛酗,在初始化時(shí),可以在URLEncodedFormEncoder?參數(shù)中設(shè)置?boolEncoding?的值:

let?encoder = URLEncodedFormParameterEncoder(encoder: URLEncodedFormEncoder(boolEncoding: .numeric))

配置Data?參數(shù)的編碼

DataEncoding?包括以下用于編碼?Data?參數(shù)的方法:

.deferredToData?- 使用?Data?的自帶?Encodable?支持偷厦。

.base64?- 將?Data?編碼為 base64 編碼的字符串商叹。這是默認(rèn)情況。

.custom((Data) -> throws -> String)?- 使用給定的閉包對(duì)?Data?進(jìn)行編碼只泼。

您可以創(chuàng)建自己的URLEncodedFormParameterEncoder剖笙,在初始化時(shí),可以在URLEncodedFormEncoder?參數(shù)中設(shè)置?dataEncoding?的值:

let?encoder = URLEncodedFormParameterEncoder(encoder: URLEncodedFormEncoder(dataEncoding: .base64))

配置Date?參數(shù)的編碼

鑒于將Date?編碼為?String?的方法非常多请唱,DateEncoding?包括以下用于編碼?Date?參數(shù)的方法:

.deferredToDate?- 使用?Date?的自帶?Encodable?支持弥咪。這是默認(rèn)情況。

.secondsSince1970?- 將?Date?編碼為 1970 年 1 月 1 日 UTC 零點(diǎn)的秒數(shù)十绑。

.millisecondsSince1970?- 將?Date?編碼為 1970 年 1 月 1 日 UTC 零點(diǎn)的毫秒數(shù)聚至。

.iso8601?- 根據(jù) ISO 8601 和 RFC3339 標(biāo)準(zhǔn)對(duì)?Date?進(jìn)行編碼。

.formatted(DateFormatter)?- 使用給定的?DateFormatter?對(duì)?Date?進(jìn)行編碼本橙。

.custom((Date) throws -> String)?- 使用給定的閉包對(duì)?Date?進(jìn)行編碼扳躬。

您可以創(chuàng)建自己的URLEncodedFormParameterEncoder,在初始化時(shí),可以在URLEncodedFormEncoder?參數(shù)中設(shè)置?dateEncoding?的值:

let?encoder = URLEncodedFormParameterEncoder(encoder: URLEncodedFormEncoder(dateEncoding: .iso8601))

配置Coding Keys 的編碼

由于key 參數(shù)樣式的多樣性贷币,KeyEncoding?提供了以下方法來從?lowerCamelCase?中自定義 key 編碼:

.useDefaultKeys?- 使用每種類型指定的 key击胜。這是默認(rèn)情況。

.convertToSnakeCase?- 將 key 轉(zhuǎn)換為 snake case:oneTwoThree?變成?one_two_three役纹。

.convertToKebabCase?- 將 key 轉(zhuǎn)換為 kebab case:oneTwoThree?變成?one-two-three偶摔。

.capitalized?- 將第一個(gè)字母大寫,例如?oneTwoThree?變?yōu)?OneTwoThree促脉。

.uppercased?- 所有字母大寫:oneTwoThree?變成?ONETWOTHREE辰斋。

.lowercased?- 所有字母小寫:oneTwoThree?變成?onetwothree。

.custom((String) -> String)?- 使用給定的閉包對(duì) key 進(jìn)行編碼嘲叔。

您可以創(chuàng)建自己的URLEncodedFormParameterEncoder亡呵,在初始化時(shí),可以在URLEncodedFormEncoder?參數(shù)中設(shè)置?keyEncoding?的值:

let?encoder = URLEncodedFormParameterEncoder(encoder: URLEncodedFormEncoder(keyEncoding: .convertToSnakeCase))

配置空格的編碼

舊的表單編碼器使用+?來對(duì)空格進(jìn)行編碼硫戈,而一些服務(wù)器仍然希望使用這種編碼锰什,而不是現(xiàn)代的百分比編碼,因此 Alamofire 包含以下對(duì)空格進(jìn)行編碼的方法:

.percentEscaped?- 通過應(yīng)用標(biāo)準(zhǔn)百分比轉(zhuǎn)義對(duì)空格字符進(jìn)行編碼丁逝。" "?編碼為"%20"汁胆。這是默認(rèn)情況。.plusReplaced?- 將空格字符替換為?+?霜幼," "?編碼為"+"嫩码。

您可以創(chuàng)建自己的URLEncodedFormParameterEncoder,在初始化時(shí)罪既,可以在URLEncodedFormEncoder?參數(shù)中設(shè)置?spaceEncoding?的值:

let?encoder = URLEncodedFormParameterEncoder(encoder: URLEncodedFormEncoder(spaceEncoding: .plusReplaced))

JSONParameterEncoder

JSONParameterEncoder?使用 Swift 的?JSONEncoder?對(duì)?Encodable?值進(jìn)行編碼铸题,并將結(jié)果設(shè)置為?URLRequest?的?httpBody。如果Content-Type?尚未設(shè)置琢感,則將其設(shè)置為?application/json丢间。

JSON 編碼參數(shù)的 POST 請(qǐng)求

let?parameters: [String: [String]] = [

????"foo": ["bar"],

????"baz": ["a", "b"],

????"qux": ["x", "y", "z"]

]

AF.request("https://httpbin.org/post", method: .post, parameters: parameters, encoder: JSONParameterEncoder.default)AF.request("https://httpbin.org/post", method: .post, parameters: parameters, encoder: JSONParameterEncoder.prettyPrinted)AF.request("https://httpbin.org/post", method: .post, parameters: parameters, encoder: JSONParameterEncoder.sortedKeys)

// HTTP body: {"baz":["a","b"],"foo":["bar"],"qux":["x","y","z"]}

自定義JSONEncoder

您可以自定義JSONParameterEncoder?的行為,方法是將自定義的?JSONEncoder?實(shí)例傳遞給它:

let?encoder = JSONEncoder()

encoder.dateEncoding = .iso8601

encoder.keyEncodingStrategy = .convertToSnakeCaselet?parameterEncoder = JSONParameterEncoder(encoder: encoder)

手動(dòng)對(duì)URLRequest?進(jìn)行參數(shù)編碼

ParameterEncoder?APIs 也可以在 Alamofire 之外使用驹针,方法是直接在URLRequest?中編碼參數(shù)烘挫。

let?url = URL(string: "https://httpbin.org/get")!var?urlRequest = URLRequest(url: url)

let?parameters = ["foo": "bar"]let?encodedURLRequest = try?URLEncodedFormParameterEncoder.default.encode(parameters, into: urlRequest)

HTTP Headers

Alamofire 包含自己的?HTTPHeaders?類型,這是一種順序保持且不區(qū)分大小寫的 HTTP header name/value 對(duì)的表示柬甥。HTTPHeader?類型封裝單個(gè) name/value 對(duì)饮六,并為常用的 headers 提供各種靜態(tài)值。

向Request?添加自定義?HTTPHeaders?就像向?request?方法傳遞值一樣簡(jiǎn)單:

let?headers: HTTPHeaders?= [

????"Authorization": "Basic VXNlcm5hbWU6UGFzc3dvcmQ=",

????"Accept": "application/json"

]

AF.request("https://httpbin.org/headers", headers: headers).responseJSON { response in

????debugPrint(response)

}

HTTPHeaders?也可以由?HTTPHeader?數(shù)組構(gòu)造:

let?headers: HTTPHeaders?= [

????.authorization(username: "Username", password: "Password"),

????.accept("application/json")

]

AF.request("https://httpbin.org/headers", headers: headers).responseJSON { response in

????debugPrint(response)

}

對(duì)于不會(huì)變的HTTP headers苛蒲,建議在URLSessionConfiguration?上設(shè)置它們卤橄,以便讓它們自動(dòng)應(yīng)用于底層?URLSession?創(chuàng)建的任何?URLSessionTask。

默認(rèn)的AlamofireSession?為每個(gè)?Request?提供一組默認(rèn)的 headers撤防。其中包括:

Accept-Encoding虽风,默認(rèn)為br;q=1.0, gzip;q=0.8, deflate;q=0.6棒口,根據(jù)RFC 7230 §4.2.3寄月。

Accept-Language辜膝,默認(rèn)為系統(tǒng)中最多6 種首選語言,格式為en;q=1.0漾肮,根據(jù)RFC 7231 §5.3.5厂抖。

]User-Agent,其中包含有關(guān)當(dāng)前應(yīng)用程序的版本信息克懊。例如:iOS Example/1.0 (com.alamofire.iOS-Example; build:1; iOS 13.0.0) Alamofire/5.0.0忱辅,根據(jù)RFC 7231 §5.5.3

如果需要自定義這些headers谭溉,則應(yīng)創(chuàng)建自定義URLSessionConfiguration墙懂,更新defaultHTTPHeaders?屬性,并將配置應(yīng)用于新?Session?實(shí)例扮念。使用URLSessionConfiguration.af.default?來自定義配置损搬,會(huì)保留 Alamofire 的默認(rèn) headers。

響應(yīng)驗(yàn)證

默認(rèn)情況下柜与,無論響應(yīng)的內(nèi)容如何巧勤,Alamofire 都會(huì)將任何已完成的請(qǐng)求視為成功。如果響應(yīng)具有不可接受的狀態(tài)代碼或 MIME 類型弄匕,則在響應(yīng)處理程序之前調(diào)用validate()?將導(dǎo)致生成錯(cuò)誤颅悉。

自動(dòng)驗(yàn)證

validate()?API 自動(dòng)驗(yàn)證狀態(tài)代碼是否在?200..<300?范圍內(nèi),以及響應(yīng)的?Content-Type?header 是否與請(qǐng)求的?Accept?匹配(如果有提供)迁匠。

AF.request("https://httpbin.org/get").validate().responseJSON { response in

????debugPrint(response)

}

手動(dòng)驗(yàn)證

AF.request("https://httpbin.org/get")

????.validate(statusCode: 200..<300)

????.validate(contentType: ["application/json"])

????.responseData { response in

????????switch?response.result {

????????case?.success:

????????????print("Validation Successful")

????????case?let?.failure(error):

????????????print(error)

????????}

????}

響應(yīng)處理

Alamofire 的?DataRequest?和?DownloadRequest?都有相應(yīng)的響應(yīng)類型:DataResponse<Success, Failure: Error>?和?DownloadResponse<Success, Failure: Error>剩瓶。這兩個(gè)類型都由兩個(gè)泛型組成:序列化類型和錯(cuò)誤類型。默認(rèn)情況下城丧,所有響應(yīng)值都將生成AFError?錯(cuò)誤類型(DataResponse<Success, AFError>)延曙。Alamofire 在其公共 API 中使用了更簡(jiǎn)單的AFDataResponse<Success>?和?AFDownloadResponse<Success>,它們總是有AFError?錯(cuò)誤類型芙贫。UploadRequest?是?DataRequest?的一個(gè)子類搂鲫,使用相同的?DataResponse?類型。

處理在Alamofire 中發(fā)出的DataRequest?或?UploadRequest?的?DataResponse?涉及到鏈接 response handler磺平,例如?responseJSON?鏈接到?DataRequest:

AF.request("https://httpbin.org/get").responseJSON { response in

????debugPrint(response)

}

在上面的示例中魂仍,responseJSON?handler 被添加到?DataRequest?中,以便在?DataRequest?完成后執(zhí)行拣挪。傳遞給 handler 閉包的參數(shù)是從響應(yīng)屬性來的?JSONResponseSerializer?生成的?AFDataResponse<Any>?值擦酌。

此閉包并不阻塞執(zhí)行以等待服務(wù)器的響應(yīng),而是作為回調(diào)添加菠劝,以便在收到響應(yīng)后處理該響應(yīng)赊舶。請(qǐng)求的結(jié)果僅在響應(yīng)閉包的范圍內(nèi)可用。任何依賴于從服務(wù)器接收到的響應(yīng)或數(shù)據(jù)的執(zhí)行都必須在響應(yīng)閉包中完成。

Alamofire 的網(wǎng)絡(luò)請(qǐng)求是異步完成的笼平。異步編程可能會(huì)讓不熟悉這個(gè)概念的程序員感到沮喪园骆,但是有很好的理由這樣做。

默認(rèn)情況下寓调,Alamofire 包含六個(gè)不同的數(shù)據(jù)響應(yīng) handlers锌唾,包括:

// Response Handler - 未序列化的 Responsefunc?response(

????queue: DispatchQueue = .main,

????completionHandler: @escaping (AFDataResponse<Data?>)?-> Void

) -> Self

// Response Serializer Handler - Serialize using the passed Serializerfunc?response<Serializer: DataResponseSerializerProtocol>(

????queue: DispatchQueue = .main,

????responseSerializer: Serializer,

????completionHandler: @escaping (AFDataResponse<Serializer.SerializedObject>)?-> Void

) -> Self

// Response Data Handler - Serialized into Datafunc?responseData(

????queue: DispatchQueue = .main,

????completionHandler: @escaping (AFDataResponse<Data>)?-> Void

) -> Self

// Response String Handler - Serialized into Stringfunc?responseString(

????queue: DispatchQueue = .main,

????encoding: String.Encoding? = nil,

????completionHandler: @escaping (AFDataResponse<String>)?-> Void

) -> Self

// Response JSON Handler - Serialized into Any Using JSONSerializationfunc?responseJSON(

????queue: DispatchQueue = .main,

????options: JSONSerialization.ReadingOptions = .allowFragments,

????completionHandler: @escaping (AFDataResponse<Any>)?-> Void

) -> Self

// Response Decodable Handler - Serialized into Decodable Typefunc?responseDecodable<T: Decodable>(

????of type: T.Type?= T.self,

????queue: DispatchQueue = .main,

????decoder: DataDecoder = JSONDecoder(),

????completionHandler: @escaping (AFDataResponse<T>) -> Void

) -> Self

沒有一個(gè)響應(yīng)handlers 對(duì)從服務(wù)器返回的HTTPURLResponse?執(zhí)行任何驗(yàn)證。

例如夺英,400..<500?和?500..<600?范圍內(nèi)的響應(yīng)狀態(tài)代碼不會(huì)自動(dòng)觸發(fā)錯(cuò)誤晌涕。Alamofire 使用響應(yīng)驗(yàn)證鏈接來實(shí)現(xiàn)這一點(diǎn)。

響應(yīng)Handler

響應(yīng)handler 不計(jì)算任何響應(yīng)數(shù)據(jù)痛悯。它只是直接從URLSessionDelegate?轉(zhuǎn)發(fā)所有信息余黎。它相當(dāng)于使用?cURL?執(zhí)行請(qǐng)求。

AF.request("https://httpbin.org/get").response { response in

????debugPrint("Response: \(response)")

}

我們強(qiáng)烈建議您利用Response?和?Result?類型來利用其他響應(yīng)序列化器载萌。

響應(yīng)Data Handler

responseData?handler 使用?DataResponseSerializer?提取并驗(yàn)證服務(wù)器返回的數(shù)據(jù)惧财。如果沒有發(fā)生錯(cuò)誤并且返回?cái)?shù)據(jù),則響應(yīng)結(jié)果將為?.success炒考,value?將為從服務(wù)器返回的?Data可缚。

AF.request("https://httpbin.org/get").responseData { response in

????debugPrint("Response: \(response)")

}

響應(yīng)String Handler

responseString?handler 使用?StringResponseSerializer?將服務(wù)器返回的數(shù)據(jù)轉(zhuǎn)換為具有指定編碼的?String。如果沒有發(fā)生錯(cuò)誤斋枢,并且服務(wù)器數(shù)據(jù)成功序列化為String帘靡,則響應(yīng)結(jié)果將為.success,并且值的類型為String瓤帚。

AF.request("https://httpbin.org/get").responseString { response in

????debugPrint("Response: \(response)")

}

如果未指定編碼描姚,Alamofire 將使用服務(wù)器HTTPURLResponse?中指定的文本編碼。如果服務(wù)器響應(yīng)無法確定文本編碼戈次,則默認(rèn)為?.isoLatin1轩勘。

響應(yīng)JSON Handler

responseJSON?handler 使用?JSONResponseSerializer?使用指定的?JSONSerialization.ReadingOptions?將服務(wù)器返回的數(shù)據(jù)轉(zhuǎn)換為?Any?類型。如果沒有出現(xiàn)錯(cuò)誤怯邪,并且服務(wù)器數(shù)據(jù)成功序列化為 JSON 對(duì)象绊寻,則響應(yīng)?AFResult?將為?.success,值將為Any?類型悬秉。

AF.request("https://httpbin.org/get").responseJSON { response in

????debugPrint("Response: \(response)")

}

響應(yīng)Decodable?Handler

responseDecodable?handler 使用?DecodableResponseSerializer?和 指定的?DataDecoder(Decoder?的協(xié)議抽象澄步,可以從?Data?解碼)將服務(wù)器返回的數(shù)據(jù)轉(zhuǎn)換為傳遞進(jìn)來的?Decodable?類型。如果沒有發(fā)生錯(cuò)誤和泌,并且服務(wù)器數(shù)據(jù)已成功解碼為?Decodable?類型村缸,則響應(yīng)?Result?將為?.success,并且value?將為傳遞進(jìn)來的類型武氓。

struct?HTTPBinResponse: Decodable?{

????let?url: String

}

AF.request("https://httpbin.org/get").responseDecodable(of: HTTPBinResponse.self) { response in

????debugPrint("Response: \(response)")

}

鏈?zhǔn)巾憫?yīng)handlers

響應(yīng)handlers 還可以連接起來:

Alamofire.request("https://httpbin.org/get")

????.responseString { response in

????????print("Response String: \(response.value)")

????}

????.responseJSON { response in

????????print("Response JSON: \(response.value)")

????}

需要注意的是梯皿,對(duì)同一請(qǐng)求使用多個(gè)響應(yīng)handlers 需要多次序列化服務(wù)器數(shù)據(jù)仇箱,每個(gè)響應(yīng) handlers 處理一次。作為最佳實(shí)踐东羹,通常應(yīng)避免對(duì)同一請(qǐng)求使用多個(gè)響應(yīng) handlers剂桥,特別是在生產(chǎn)環(huán)境中。它們只能用于調(diào)試或在沒有更好選擇的情況下使用百姓。

響應(yīng)Handler 隊(duì)列

默認(rèn)情況下渊额,傳遞給響應(yīng)handler 的閉包在.main?隊(duì)列上執(zhí)行况木,但可以傳遞一個(gè)指定的 DispatchQueue 來執(zhí)行閉包垒拢。實(shí)際的序列化工作(將?Data?轉(zhuǎn)換為其他類型)總是在后臺(tái)隊(duì)列上執(zhí)行。

let?utilityQueue = DispatchQueue.global(qos: .utility)

AF.request("https://httpbin.org/get").responseJSON(queue: utilityQueue) { response in

????print("Executed on utility queue.")

????debugPrint(response)

}

響應(yīng)緩存

響應(yīng)緩存使用系統(tǒng)自帶的URLCache?處理火惊。它提供了內(nèi)存和磁盤上的復(fù)合緩存求类,并允許您管理用于緩存的內(nèi)存和磁盤的大小。

默認(rèn)情況下屹耐,Alamofire 利用URLCache.shared?實(shí)例尸疆。要自定義使用的URLCache?實(shí)例,請(qǐng)查看高級(jí)用法惶岭。

身份驗(yàn)證

身份驗(yàn)證使用系統(tǒng)自帶的URLCredential?和?URLAuthenticationChallenge?處理寿弱。

這些身份驗(yàn)證APIs 用于提示授權(quán)的服務(wù)器,而不是一般用于需要身份驗(yàn)證或等效的 header APIs按灶。

支持的身份驗(yàn)證方案

HTTP Basic

HTTP Digest

Kerberos

NTLM

HTTP Basic 身份驗(yàn)證

Request?的?authenticate?方法將在使用?URLAuthenticationChallenge?進(jìn)行質(zhì)詢時(shí)自動(dòng)提供?URLCredential(如果適用):

let?user = "user"let?password = "password"

AF.request("https://httpbin.org/basic-auth/\(user)/\(password)")

????.authenticate(username: user, password: password)

????.responseJSON { response in

????????debugPrint(response)

????}

使用URLCredential?進(jìn)行驗(yàn)證

let?user = "user"let?password = "password"

let?credential = URLCredential(user: user, password: password, persistence: .forSession)

AF.request("https://httpbin.org/basic-auth/\(user)/\(password)")

????.authenticate(with: credential)

????.responseJSON { response in

????????debugPrint(response)

????}

需要注意的是症革,當(dāng)使用URLCredential?進(jìn)行身份驗(yàn)證時(shí),如果服務(wù)器發(fā)出質(zhì)詢鸯旁,底層?URLSession?實(shí)際上將發(fā)出兩個(gè)請(qǐng)求噪矛。第一個(gè)請(qǐng)求將不包括“可能”觸發(fā)服務(wù)器質(zhì)詢的 credential。然后铺罢,Alamofire 接收質(zhì)詢艇挨,追加 credential,并由底層?URLSession?重試請(qǐng)求韭赘。

手動(dòng)驗(yàn)證

如果您正在與始終需要Authenticate?或類似 header 而不提示的 API 通信缩滨,則可以手動(dòng)添加:

let?user = "user"let?password = "password"

let?headers: HTTPHeaders?= [.authorization(username: user, password: password)]

AF.request("https://httpbin.org/basic-auth/user/password", headers: headers)

????.responseJSON { response in

????????debugPrint(response)

????}

但是,必須是所有請(qǐng)求的一部分的headers泉瞻,通常作為自定義URLSessionConfiguration?的一部分或通過使用?RequestAdapter?來更好地處理脉漏。

下載數(shù)據(jù)到文件中

除了將數(shù)據(jù)提取到內(nèi)存中之外,Alamofire 還提供了Session.download瓦灶、DownloadRequest?和?DownloadResponse<Success鸠删,F(xiàn)ailure:Error>?以方便下載數(shù)據(jù)到磁盤。雖然下載到內(nèi)存中對(duì)小負(fù)載(如大多數(shù) JSON API 響應(yīng))非常有用贼陶,但獲取更大的資源(如圖像和視頻)應(yīng)下載到磁盤刃泡,以避免應(yīng)用程序出現(xiàn)內(nèi)存問題巧娱。

AF.download("https://httpbin.org/image/png").responseData { response in

????if?let?data = response.value {

????????let?image = UIImage(data: data)

????}

}

DownloadRequest?具有與?DataRequest?相同的大多數(shù)響應(yīng) handlers。但是烘贴,由于它將數(shù)據(jù)下載到磁盤禁添,因此序列化響應(yīng)涉及從磁盤讀取,還可能涉及將大量數(shù)據(jù)讀入內(nèi)存桨踪。在設(shè)計(jì)下載處理時(shí)老翘,記住這些事實(shí)是很重要的。

下載文件的存放位置

所有下載的數(shù)據(jù)最初都存儲(chǔ)在系統(tǒng)臨時(shí)目錄中锻离。它最終會(huì)在將來的某個(gè)時(shí)候被系統(tǒng)刪除铺峭,所以如果它需要更長(zhǎng)的壽命,將文件移到其他地方是很重要的汽纠。

您可以提供Destination?閉包卫键,將文件從臨時(shí)目錄移動(dòng)到最終的存放位置。在臨時(shí)文件實(shí)際移動(dòng)到?destinationURL?之前虱朵,將執(zhí)行閉包中指定的?Options莉炉。當(dāng)前支持的兩個(gè)Options?是:

.createIntermediateDirectories?- 如果指定,則為目標(biāo) URL 創(chuàng)建中間目錄碴犬。

.removePreviousFile?- 如果指定絮宁,則從目標(biāo) URL 中刪除以前的文件。

let?destination: DownloadRequest.Destination?= { _, _?in

????let?documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]

????let?fileURL = documentsURL.appendingPathComponent("image.png")


????return?(fileURL, [.removePreviousFile, .createIntermediateDirectories])

}

AF.download("https://httpbin.org/image/png", to: destination).response { response in

????debugPrint(response)


????if?response.error == nil, let?imagePath = response.fileURL?.path {

????????let?image = UIImage(contentsOfFile: imagePath)

????}

}

您還可以使用建議的destination API:

let?destination = DownloadRequest.suggestedDownloadDestination(for: .documentDirectory)

AF.download("https://httpbin.org/image/png", to: destination)

下載進(jìn)度

很多時(shí)候向用戶報(bào)告下載進(jìn)度是有幫助的服协。任何DownloadRequest?都可以使用?downloadProgress?報(bào)告下載進(jìn)度绍昂。

AF.download("https://httpbin.org/image/png")

????.downloadProgress { progress in

????????print("Download Progress: \(progress.fractionCompleted)")

????}

????.responseData { response in

????????if?let?data = response.value {

????????????let?image = UIImage(data: data)

????????}

????}

URLSession?的進(jìn)度報(bào)告 APIs(也是 Alamofire 的)只有在服務(wù)器正確返回可用于計(jì)算進(jìn)度的?Content-Length?header 時(shí)才能工作。如果沒有這個(gè) header蚯涮,進(jìn)度將保持在?0.0治专,直到下載完成,此時(shí)進(jìn)度將跳到1.0遭顶。

downloadProgress?API 還可以接收一個(gè)?queue?參數(shù)张峰,該參數(shù)定義應(yīng)該對(duì)哪個(gè)?DispatchQueue?調(diào)用下載進(jìn)度閉包。

let?utilityQueue = DispatchQueue.global(qos: .utility)

AF.download("https://httpbin.org/image/png")

????.downloadProgress(queue: utilityQueue) { progress in

????????print("Download Progress: \(progress.fractionCompleted)")

????}

????.responseData { response in

????????if?let?data = response.value {

????????????let?image = UIImage(data: data)

????????}

????}

取消和恢復(fù)下載

除了所有請(qǐng)求類都有cancel()?API 外棒旗,DownloadRequest?還可以生成恢復(fù)數(shù)據(jù)喘批,這些數(shù)據(jù)可以用于以后恢復(fù)下載。此 API 有兩種形式:1)cancel(producingResumeData: Bool)铣揉,它允許控制是否生成恢復(fù)數(shù)據(jù)饶深,但僅在DownloadResponse?可用;2)cancel(byProducingResumeData: (_ resumeData: Data?) -> Void)逛拱,它執(zhí)行相同的操作敌厘,但恢復(fù)數(shù)據(jù)在completion handler 中可用。

如果DownloadRequest?被取消或中斷朽合,則底層的?URLSessionDownloadTask?可能會(huì)生成恢復(fù)數(shù)據(jù)俱两。如果發(fā)生這種情況饱狂,可以重新使用恢復(fù)數(shù)據(jù)來重新啟動(dòng)停止的?DownloadRequest。

重要提示:在所有Apple 平臺(tái)的某些版本(iOS 10 - 10.2宪彩、macOS 10.12 - 10.12.2休讳、tvOS 10 - 10.1、watchOS 3 - 3.1.1)上尿孔,resumeData?在后臺(tái)?URLSessionConfiguration?上被破壞俊柔。resumeData?生成邏輯中存在一個(gè)潛在的錯(cuò)誤,即數(shù)據(jù)寫入錯(cuò)誤活合,并且總是無法恢復(fù)下載雏婶。有關(guān)此錯(cuò)誤和可能的解決方法的詳細(xì)信息,請(qǐng)參閱此?Stack Overflow 的帖子芜辕。

var?resumeData: Data!

let?download = AF.download("https://httpbin.org/image/png").responseData { response in

????if?let?data = response.value {

????????let?image = UIImage(data: data)

????}

}

// download.cancel(producingResumeData: true) // Makes resumeData available in response only.

download.cancel { data in

????resumeData = data

}

AF.download(resumingWith: resumeData).responseData { response in

????if?let?data = response.value {

????????let?image = UIImage(data: data)

????}

}

上傳數(shù)據(jù)到服務(wù)器

當(dāng)使用JSON 或 URL 編碼的參數(shù)向服務(wù)器發(fā)送相對(duì)少量的數(shù)據(jù)時(shí)尚骄,request()?通常就足夠了。如果需要從內(nèi)存侵续、文件 URL 或 InputStream 中的?Data?發(fā)送大量數(shù)據(jù),那么?upload()?就是您想要使用的憨闰。

上傳Data

let?data = Data("data".utf8)

AF.upload(data, to: "https://httpbin.org/post").responseJSON { response in

????debugPrint(response)

}

上傳文件

let?fileURL = Bundle.main.url(forResource: "video", withExtension: "mov")

AF.upload(fileURL, to: "https://httpbin.org/post").responseJSON { response in

????debugPrint(response)

}

上傳多表單數(shù)據(jù)

AF.upload(multipartFormData: { multipartFormData in

????multipartFormData.append(Data("one".utf8), withName: "one")

????multipartFormData.append(Data("two".utf8), withName: "two")

}, to: "https://httpbin.org/post")

????.responseJSON { response in

????????debugPrint(response)

????}

上傳進(jìn)度

當(dāng)用戶等待上傳完成時(shí)状蜗,有時(shí)向用戶顯示上傳的進(jìn)度會(huì)很方便。任何UploadRequest 都可以使用uploadProgress?和?downloadProgress?報(bào)告響應(yīng)數(shù)據(jù)下載的上傳進(jìn)度和下載進(jìn)度鹉动。

let?fileURL = Bundle.main.url(forResource: "video", withExtension: "mov")

AF.upload(fileURL, to: "https://httpbin.org/post")

????.uploadProgress { progress in

????????print("Upload Progress: \(progress.fractionCompleted)")

????}

????.downloadProgress { progress in

????????print("Download Progress: \(progress.fractionCompleted)")

????}

????.responseJSON { response in

????????debugPrint(response)

????}

統(tǒng)計(jì)指標(biāo)

URLSessionTaskMetrics

Alamofire 為每個(gè)?Request?收集了?URLSessionTaskMetrics轧坎。URLSessionTaskMetrics?封裝了一些關(guān)于底層網(wǎng)絡(luò)連接、請(qǐng)求和響應(yīng)時(shí)間的奇妙統(tǒng)計(jì)信息泽示。

AF.request("https://httpbin.org/get").responseJSON { response in

????print(response.metrics)

}

cURL 的命令輸出

調(diào)試平臺(tái)問題可能令人沮喪缸血。幸運(yùn)的是,Alamofire 的Request?類型可以生成等效的 cURL 命令械筛,以便調(diào)試捎泻。由于 Alamofire?Request?創(chuàng)建的異步性,這個(gè) API 有同步和異步兩個(gè)版本埋哟。要盡快獲取 cURL 命令笆豁,可以將?cURLDescription?鏈接到請(qǐng)求上:

AF.request("https://httpbin.org/get")

????.cURLDescription { description in

????????print(description)

????}

????.responseJSON { response in

????????debugPrint(response.metrics)

????}

這將會(huì)生成:

$ curl -v \

-X GET \

-H "Accept-Language: en;q=1.0" \

-H "Accept-Encoding: br;q=1.0, gzip;q=0.9, deflate;q=0.8" \

-H "User-Agent: Demo/1.0 (com.demo.Demo; build:1; iOS 13.0.0) Alamofire/1.0" \

"https://httpbin.org/get"

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市赤赊,隨后出現(xiàn)的幾起案子闯狱,更是在濱河造成了極大的恐慌,老刑警劉巖抛计,帶你破解...
    沈念sama閱讀 218,640評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哄孤,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡吹截,警方通過查閱死者的電腦和手機(jī)瘦陈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門朦肘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人双饥,你說我怎么就攤上這事媒抠。” “怎么了咏花?”我有些...
    開封第一講書人閱讀 165,011評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵趴生,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我昏翰,道長(zhǎng)苍匆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,755評(píng)論 1 294
  • 正文 為了忘掉前任棚菊,我火速辦了婚禮浸踩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘统求。我一直安慰自己检碗,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評(píng)論 6 392
  • 文/花漫 我一把揭開白布码邻。 她就那樣靜靜地躺著折剃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪像屋。 梳的紋絲不亂的頭發(fā)上怕犁,一...
    開封第一講書人閱讀 51,610評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音己莺,去河邊找鬼奏甫。 笑死,一個(gè)胖子當(dāng)著我的面吹牛凌受,可吹牛的內(nèi)容都是我干的阵子。 我是一名探鬼主播,決...
    沈念sama閱讀 40,352評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼胁艰,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼款筑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起腾么,我...
    開封第一講書人閱讀 39,257評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤奈梳,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后解虱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體攘须,經(jīng)...
    沈念sama閱讀 45,717評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評(píng)論 3 336
  • 正文 我和宋清朗相戀三年殴泰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了于宙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片浮驳。...
    茶點(diǎn)故事閱讀 40,021評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖捞魁,靈堂內(nèi)的尸體忽然破棺而出至会,到底是詐尸還是另有隱情,我是刑警寧澤谱俭,帶...
    沈念sama閱讀 35,735評(píng)論 5 346
  • 正文 年R本政府宣布奉件,位于F島的核電站,受9級(jí)特大地震影響昆著,放射性物質(zhì)發(fā)生泄漏县貌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評(píng)論 3 330
  • 文/蒙蒙 一凑懂、第九天 我趴在偏房一處隱蔽的房頂上張望煤痕。 院中可真熱鬧,春花似錦接谨、人聲如沸摆碉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽兆解。三九已至,卻和暖如春跑揉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背埠巨。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工历谍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人辣垒。 一個(gè)月前我還...
    沈念sama閱讀 48,224評(píng)論 3 371
  • 正文 我出身青樓望侈,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親勋桶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子脱衙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評(píng)論 2 355