Nuke框架詳細(xì)解析(一) —— 基本概覽(一)

版本記錄

版本號 時間
V1.0 2020.10.09 星期五

前言

在我們開發(fā)中總有從遠(yuǎn)程下載圖片帖族,OC中有個很成熟的三方框架甚垦,大家都知道的SDWebImage艰亮,同樣Swift中也有類似的三方框架Nuke迄埃,接下來幾篇我們就一起看一下這個框架侄非。

開始

首先我們看一下該項目的GitHub地址 Nuke者疤。

Nuke提供了一種簡單有效的方法來下載和顯示應(yīng)用程序中的圖像驹马。 簡潔明了的API背后是先進(jìn)的體系結(jié)構(gòu)糯累,該體系結(jié)構(gòu)具有其獨(dú)特的功能并提供了幾乎無限的自定義可能性。 Nuke的主要功能是性能 - performance

Fast LRU memory and disk cache · SwiftUI · Smart background decompression · Image processing · Elegant builder API · Resumable downloads · Intelligent deduplication · Request prioritization · Low data mode · Prefetching · Rate limiting · Progressive JPEG, HEIF, WebP, SVG, GIF · Alamofire · Combine · Reactive extensions

Nuke易于學(xué)習(xí)和使用借笙。 以下是其API和功能的概述:

要了解更多信息,請參閱完整的API Reference,并查看存儲庫中包含的演示項目熔号。 準(zhǔn)備安裝時引镊,請遵循Installation Guide弟头。 請參閱Requirements赴恨,以獲取受支持平臺的列表雨饺。 如果遇到任何問題沛膳,請參閱FAQTroubleshooting Guide锹安。

要了解有關(guān)管道和支持的格式的更多信息,請參見專用指南风罩。


Image View Extensions

用單行代碼下載并在image view中顯示圖像:

Nuke.loadImage(with: url, into: imageView)

Nuke將檢查圖像是否在內(nèi)存緩存中舵稠,如果存在超升,將立即顯示它。 否則哺徊,圖像數(shù)據(jù)將在后臺加載室琢,解碼,處理和解壓縮

請參閱 Image Pipeline Guide以了解如何下載和處理圖像落追。

1. In a Table View

當(dāng)您為現(xiàn)有視圖請求新圖像時,Nuke會為重用做好準(zhǔn)備轿钠,并取消對該視圖的所有未完成請求巢钓。

func tableView(_ tableView: UITableView, cellForItemAt indexPath: IndexPaths) -> UITableViewCell {
    /* Create a cell ... */
    Nuke.loadImage(with: url, into: cell.imageView)
}

視圖deallocated后,關(guān)聯(lián)的請求將自動取消疗垛。 要手動取消請求症汹,請調(diào)用Nuke.cancelRequest(for:imageView)

2. Placeholders, Transitions, Content Modes, Tint Colors

使用ImageLoadingOptions設(shè)置一個placeholder继谚,選擇一個內(nèi)置transitions烈菌,或提供一個自定義過渡阵幸。

let options = ImageLoadingOptions(
    placeholder: UIImage(named: "placeholder"),
    transition: .fadeIn(duration: 0.33)
)
Nuke.loadImage(with: url, options: options, into: imageView)

您甚至可以自定義content mode或每種圖像類型的tint color

let options = ImageLoadingOptions(
    placeholder: UIImage(named: "placeholder"),
    failureImage: UIImage(named: "failureImage"),
    contentModes: .init(success: .scaleAspectFill, failure: .center, placeholder: .center),
    tintColors: .init(success: .green, failure: .red, placeholder: .yellow)
)

如果希望所有圖像視圖都具有相同的行為,則可以修改ImageLoadingOptions.shared芽世。

請記住挚赊,圖像視圖的內(nèi)置擴(kuò)展程序旨在使您盡快啟動并運(yùn)行。 如果您想擁有更多控制權(quán)或使用某些高級功能(例如動畫圖像)济瓢,建議直接在自定義視圖中使用ImagePipeline荠割。

3. ImageRequest

ImageRequest允許您設(shè)置圖像處理器,更改請求優(yōu)先級等:

let request = ImageRequest(
    url: URL(string: "http://..."),
    processors: [ImageProcessors.Resize(size: imageView.bounds.size)],
    priority: .high
)

應(yīng)用處理器的另一種方法是在ImagePipeline.Configuration上設(shè)置默認(rèn)processors旺矾。 這些處理器將應(yīng)用于管道加載的所有圖像蔑鹦。 如果請求中包含非空的處理器數(shù)組,則將應(yīng)用它們箕宙。

可通過ImageRequestOptions使用的高級選項嚎朽。 例如,如果URL包含瞬態(tài)查詢參數(shù)柬帕,則可以提供一個filteredURL用作緩存的鍵哟忍。

let request = ImageRequest(
    url: URL(string: "http://example.com/image.jpeg?token=123")!,
    options: ImageRequestOptions(
        filteredURL: "http://example.com/image.jpeg"
    )
)

有更多選項可用,要查看所有選項陷寝,請查看內(nèi)聯(lián)文檔以了解ImageRequestOptions锅很。


Image Processing

Nuke具有強(qiáng)大而高效的圖像處理基礎(chǔ)架構(gòu),具有多個內(nèi)置處理器凤跑,您可以在ImageProcessors命名空間中找到它們爆安,例如 ImageProcessors.Resize

此截圖和其他屏幕截圖來自倉庫中包含的演示項目仔引。

1. Resize

要調(diào)整圖像大小扔仓,請使用ImageProcessors.Resize

ImageRequest(url: url, processors: [
    ImageProcessors.Resize(size: imageView.bounds.size)
])

默認(rèn)情況下,target size以點(diǎn)為單位咖耘。 加載圖像后当辐,Nuke將縮小圖像以填充目標(biāo)區(qū)域,并保持寬高比鲤看。 要裁切圖像,請將crop設(shè)置為true耍群。 有關(guān)更多選項义桂,請參見ImageProcessors.Resize文檔。

使用可選的Builder軟件包獲得更簡潔的API蹈垢。

pipeline.image(with: URL(string: "https://")!)
    .resize(width: 320)
    .blur(radius: 10)

2. Circle

將圖像的角圓成帶有可選邊框的圓慷吊。

ImageRequest(url: url, processors: [
    ImageProcessors.Circle()
])

3. RoundedCorners

將圖像的角圓整到指定的半徑。 確保調(diào)整圖像大小以與顯示圖像的視圖大小完全匹配曹抬,以便正確顯示邊框溉瓶。

ImageRequest(url: url, processors: [
    ImageProcessors.Circle(radius: 16)
])

4. GaussianBlur

ImageProcessors.GaussianBlur使用Core Image濾鏡之一模糊輸入圖像。

5. CoreImageFilter

使用ImageProcessors.CoreImageFilter應(yīng)用大量的Core Image filters

ImageProcessors.CoreImageFilter(name: "CISepiaTone")

6. Custom Processors

對于簡單的一次性操作,請使用ImageProcessors.Anonymous創(chuàng)建帶閉包的處理器堰酿。

定制處理器需要實(shí)現(xiàn)ImageProcessing協(xié)議疾宏。 為了滿足基本的圖像處理需求,請實(shí)現(xiàn)process(_ :)方法并創(chuàng)建一個唯一標(biāo)識處理器的標(biāo)識符触创。 對于沒有輸入?yún)?shù)的處理器坎藐,您可以返回靜態(tài)字符串。

public protocol ImageProcessing {
    func process(image: UIImage) -> UIImage? // NSImage on macOS
    var identifier: String // get
}

如果您的處理器需要操縱圖像元數(shù)據(jù)(ImageContainer)哼绑,或者需要通過ImageProcessingContext訪問更多信息岩馍,那么除了process(_ :)之外,您還可以實(shí)現(xiàn)其他方法抖韩。

public protocol ImageProcessing {
    func process(_ image container: ImageContainer, context: ImageProcessingContext) -> ImageContainer?
}

除了var identfier:String外蛀恩,您還可以實(shí)現(xiàn)var hashableIdentifier:AnyHashable,以供內(nèi)存高速緩存使用茂浮,因?yàn)樵谶@種情況下双谆,字符串操作太慢。 默認(rèn)情況下励稳,此方法返回identifier字符串佃乘。 一種常見的方法是使處理器可Hashable,并從hashableIdentifier返回self驹尼。


Image Pipeline

Nuke的核心是ImagePipeline類趣避。 直接使用管道來加載圖像而不顯示它們:

let task = ImagePipeline.shared.loadImage(
    with: url,
    progress: { _, completed, total in
        print("progress updated")
    },
    completion: { result: Result<ImageResponse, ImagePipeline.Error> in
        print("task completed")
    }
)

loadImage返回值始終異步調(diào)用完成閉包。 若要檢查圖像是否存儲在內(nèi)存緩存中新翎,請使用pipeline.cachedImage(for:url)程帕。

要下載數(shù)據(jù)而不進(jìn)行任何昂貴的解碼或處理,請使用loadData(with:progress:completion :)地啰。

1. ImageTask

啟動請求時愁拭,管道將返回ImageTask對象,該對象可用于取消操作以及更多操作亏吝。

task.cancel()
task.priority = .high

2. Customize Image Pipeline

如果您想構(gòu)建一個適合您特定需求的系統(tǒng)岭埠,那么您將不會失望。 有很多事情需要調(diào)整蔚鸥。 您可以設(shè)置自定義數(shù)據(jù)加載器和緩存惜论,配置圖像編碼器和解碼器,更改每個單獨(dú)階段的并發(fā)操作數(shù)止喷,禁用和啟用重復(fù)數(shù)據(jù)刪除和速率限制等功能馆类。

要了解更多信息,請參見ImagePipeline.ConfigurationImage Pipeline Guide的內(nèi)聯(lián)文檔弹谁。

以下是可用于自定義的協(xié)議:

  • DataLoading –下載(或返回緩存的)圖像數(shù)據(jù)
  • DataCaching –將圖像數(shù)據(jù)存儲在磁盤上
  • ImageDecoding –將數(shù)據(jù)轉(zhuǎn)換為圖像(有關(guān)新的實(shí)驗(yàn)性解碼功能乾巧,請參見_ImageDecoding
  • ImageEncoding-將圖像轉(zhuǎn)換為數(shù)據(jù)
  • ImageProcessing –應(yīng)用圖像轉(zhuǎn)換
  • ImageCaching –將圖像存儲到內(nèi)存緩存中

整個配置由ImagePipeline.Configuration結(jié)構(gòu)描述句喜。 要使用自定義配置創(chuàng)建管道,請調(diào)用ImagePipeline(configuration :)初始值設(shè)定項或使用便捷的一種:

let pipeline = ImagePipeline {
    $0.dataLoader = ...
    $0.dataLoadingQueue = ...
    $0.imageCache = ...
    ...
}

然后將新管道設(shè)置為默認(rèn)值:

ImagePipeline.shared = pipeline

3. Default Image Pipeline

默認(rèn)圖像管道使用以下依賴項(dependencies)進(jìn)行初始化:

// Shared image cache with a size limit of ~20% of available RAM.
imageCache = ImageCache.shared

// Data loader with a default `URLSessionConfiguration` and a custom `URLCache`
// with memory capacity 0, and disk capacity 150 MB.
dataLoader = DataLoader()

// Custom aggressive disk cache is disabled by default.
dataCache = nil

// By default uses the decoder from the global registry and the default encoder.
makeImageDecoder = ImageDecoderRegistry.shared.decoder(for:)
makeImageEncoder = { _ in ImageEncoders.Default() }

管道中的每個操作都在專用隊列上運(yùn)行:

dataLoadingQueue.maxConcurrentOperationCount = 6
dataCachingQueue.maxConcurrentOperationCount = 2
imageDecodingQueue.maxConcurrentOperationCount = 1
imageEncodingQueue.maxConcurrentOperationCount = 1
imageProcessingQueue.maxConcurrentOperationCount = 2
imageDecompressingQueue.maxConcurrentOperationCount = 2

您可以調(diào)整以下管道設(shè)置列表:

// Automatically decompress images in the background by default.
isDecompressionEnabled = true

// Configure what content to store in the custom disk cache.
dataCacheOptions.storedItems = [.finalImage] // [.originalImageData]

// Avoid doing any duplicated work when loading or processing images.
isDeduplicationEnabled = true

// Rate limit the requests to prevent trashing of the subsystems.
isRateLimiterEnabled = true

// Progressive decoding is an opt-in feature because it is resource intensive.
isProgressiveDecodingEnabled = false

// Don't store progressive previews in memory cache.
isStoringPreviewsInMemoryCache = false

// If the data task is terminated (either because of a failure or a
// cancellation) and the image was partially loaded, the next load will
// resume where it was left off.
isResumableDataEnabled = true

在所有管道之間共享一些全局選項:

// Enable to start using `os_signpost` to monitor the pipeline
// performance using Instruments.
ImagePipeline.Configuration.isSignpostLoggingEnabled = false

Caching

1. LRU Memory Cache

Nuke的默認(rèn)ImagePipeline具有兩個緩存層沟于。

首先咳胃,有一個內(nèi)存緩存,用于存儲準(zhǔn)備顯示的已處理圖像社裆。

// Configure cache
ImageCache.shared.costLimit = 1024 * 1024 * 100 // 100 MB
ImageCache.shared.countLimit = 100
ImageCache.shared.ttl = 120 // Invalidate image after 120 sec

// Read and write images
let request = ImageRequest(url: url)
ImageCache.shared[request] = ImageContainer(image: image)
let image = ImageCache.shared[request]

// Clear cache
ImageCache.shared.removeAll()

ImageCache使用LRU算法-在掃描期間拙绊,首先刪除最近最少使用的條目。

2. HTTP Disk Cache

未處理的圖像數(shù)據(jù)存儲在URLCache中泳秀。

// Configure cache
DataLoader.sharedUrlCache.diskCapacity = 100
DataLoader.sharedUrlCache.memoryCapacity = 0

// Read and write responses
let request = ImageRequest(url: url)
let _ = DataLoader.sharedUrlCache.cachedResponse(for: request.urlRequest)
DataLoader.sharedUrlCache.removeCachedResponse(for: request.urlRequest)

// Clear cache
DataLoader.sharedUrlCache.removeAllCachedResponses()

3. Aggressive LRU Disk Cache

如果您不愿意使用HTTP緩存标沪,則可以嘗試使用自定義LRU磁盤緩存進(jìn)行快速可靠的主動數(shù)據(jù)緩存(忽略 HTTP cache control)。 您可以使用管道配置啟用它嗜傅。

ImagePipeline {
    $0.dataCache = try? DataCache(name: "com.myapp.datacache")

    // Also consider disabling the native HTTP cache, see `DataLoader`.
}

默認(rèn)情況下金句,管道僅存儲原始圖像數(shù)據(jù)。 要存儲已下載和已處理的圖像吕嘀,請將dataCacheOptions.storedItems設(shè)置為[.finalImage]违寞。 如果您要存儲已處理的內(nèi)容,例如下采樣的圖像偶房,或者如果您想將圖像轉(zhuǎn)碼為更有效的格式趁曼,例如HEIF

要節(jié)省磁盤空間棕洋,請參閱HEIF支持的ImageEncoders.ImageIOImageEncoder.isHEIFPreferred選項挡闰。


Advanced Features

1. Image Preheating

預(yù)先預(yù)取圖像可以顯著改善應(yīng)用程序的用戶體驗(yàn)。

// Make sure to keep a strong reference to preheater.
let preheater = ImagePreheater()

preheater.startPreheating(with: urls)

// Cancels all of the preheating tasks created for the given requests.
preheater.stopPreheating(with: urls)

要了解有關(guān)您可以執(zhí)行的其他性能優(yōu)化的更多信息掰盘,請參見Performance Guide摄悯。

請記住,預(yù)取會占用用戶的數(shù)據(jù)愧捕,并給CPU和內(nèi)存帶來額外的壓力奢驯。 為了減少CPU和內(nèi)存的使用,您可以選擇僅選擇磁盤緩存作為預(yù)取目標(biāo):

// The preheater with `.diskCache` destination will skip image data decoding
// entirely to reduce CPU and memory usage. It will still load the image data
// and store it in disk caches to be used later.
let preheater = ImagePreheater(destination: .diskCache)

iOS上次绘,您可以將prefetching APIsImagePreheater結(jié)合使用以自動執(zhí)行該過程瘪阁。

2. Progressive Decoding

要啟用漸進(jìn)式圖像解碼,請將isProgressiveDecodingEnabled配置選項設(shè)置為true邮偎。

let pipeline = ImagePipeline {
    $0.isProgressiveDecodingEnabled = true
    
    // If `true`, the pipeline will store all of the progressively generated previews
    // in the memory cache. All of the previews have `isPreview` flag set to `true`.
    $0.isStoringPreviewsInMemoryCache = true
}

就是這樣罗洗,管道將自動執(zhí)行正確的操作,并在進(jìn)度掃描到達(dá)時通過progress閉包進(jìn)行漸進(jìn)式掃描:

let imageView = UIImageView()
let task = ImagePipeline.shared.loadImage(
    with: url,
    progress: { response, _, _ in
        if let response = response {
            imageView.image = response.image
        }
    },
    completion: { result in
        // Display the final image
    }
)

Extensions

Nuke有多種擴(kuò)展:

Name Description
FetchImage SwiftUI integration
ImagePublisher Combine publishers for Nuke
ImageTaskBuilder A fun and convenient way to use Nuke
Alamofire Plugin Replace networking layer with Alamofire and combine the power of both frameworks
RxNuke RxSwift extensions for Nuke with examples of common use cases solved by Rx
WebP Plugin [Community] WebP support, built by Ryo Kosuge
Gifu Plugin Use Gifu to load and display animated GIFs
FLAnimatedImage Plugin Use FLAnimatedImage to load and display animated GIFs
Xamarin NuGet [Community] Makes it possible to use Nuke from Xamarin

1. FetchImage

FetchImage是一個Swift軟件包钢猛,可輕松使用Nuke下載圖像并將其顯示在SwiftUI應(yīng)用中。 有關(guān)更多信息轩缤,請參見introductory post命迈。

注意:這是一個API預(yù)覽贩绕,將來可能會更改。

public struct ImageView: View {
    @ObservedObject var image: FetchImage

    public var body: some View {
        ZStack {
            Rectangle().fill(Color.gray)
            image.view?
                .resizable()
                .aspectRatio(contentMode: .fill)
        }
    }
}

2. Low Data Mode

FetchImage還通過特殊的初始化程序?yàn)榈蛿?shù)據(jù)模式提供內(nèi)置支持:

FetchImage(regularUrl: highQualityUrl, lowDataUrl: lowQualityUrl)

3. Builder

覺得默認(rèn)的API有點(diǎn)無聊嗎壶愤? 試試ImageTaskBuilder淑倾,這是一種使用Nuke的有趣且方便的方法。

ImagePipeline.shared.image(with: URL(string: "https://")!)
    .fill(width: 320)
    .blur(radius: 10)
    .priority(.high)
    .start { result in
        print(result)
    }

// Returns `ImageTask` when started.
let imageView: UIImageView

ImagePipeline.shared.image(with: URL(string: "https://")!)
    .fill(width: imageView.size.width)
    .display(in: imageView)

4. RxNuke

RxNuke為Nuke添加了RxSwift擴(kuò)展征椒,并啟用了常見用例:Going from low to high resolution | Loading the first available image | Showing stale image while validating it | Load multiple images, display all at once | Auto retry on failures | And more

要了解使用此擴(kuò)展程序可以做什么娇哆,請看一下先加載低分辨率圖像然后切換到高分辨率有多么容易:

let pipeline = ImagePipeline.shared
Observable.concat(pipeline.loadImage(with: lowResUrl).orEmpty,
                  pipeline.loadImage(with: highResUrl).orEmpty)
    .subscribe(onNext: { imageView.image = $0 })
    .disposed(by: disposeBag)

5. Combine

ImagePublisherNuke添加了Combine發(fā)行者,并且與RxNuke一樣勃救,啟用了各種功能強(qiáng)大的用例碍讨。


Contribution

Nuke's roadmapTrello中管理,并且可以公開獲得蒙秒。 如果您想貢獻(xiàn)勃黍,請隨時創(chuàng)建PR

1. Minimum Requirements

Nuke Swift Xcode Platforms
Nuke 9.0 Swift 5.1 Xcode 11.0 iOS 11.0 / watchOS 4.0 / macOS 10.13 / tvOS 11.0
Nuke 8.0 Swift 5.0 Xcode 10.2 iOS 10.0 / watchOS 3.0 / macOS 10.12 / tvOS 10.0

有關(guān)舊版本的信息晕讲,請參見Installation Guide覆获。

后記

本篇主要講述了Swift中也有類似的三方框架Nuke,感興趣的給個贊或者關(guān)注~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瓢省,一起剝皮案震驚了整個濱河市弄息,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌勤婚,老刑警劉巖摹量,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蛔六,居然都是意外死亡荆永,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門国章,熙熙樓的掌柜王于貴愁眉苦臉地迎上來具钥,“玉大人,你說我怎么就攤上這事液兽÷钌荆” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵四啰,是天一觀的道長宁玫。 經(jīng)常有香客問我,道長柑晒,這世上最難降的妖魔是什么欧瘪? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮匙赞,結(jié)果婚禮上佛掖,老公的妹妹穿的比我還像新娘妖碉。我一直安慰自己,他們只是感情好芥被,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布欧宜。 她就那樣靜靜地躺著,像睡著了一般拴魄。 火紅的嫁衣襯著肌膚如雪冗茸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天匹中,我揣著相機(jī)與錄音夏漱,去河邊找鬼。 笑死职员,一個胖子當(dāng)著我的面吹牛麻蹋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播焊切,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼扮授,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了专肪?” 一聲冷哼從身側(cè)響起刹勃,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嚎尤,沒想到半個月后荔仁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡芽死,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年乏梁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片关贵。...
    茶點(diǎn)故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡遇骑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出揖曾,到底是詐尸還是另有隱情落萎,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布炭剪,位于F島的核電站练链,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏奴拦。R本人自食惡果不足惜媒鼓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧绿鸣,春花似錦瓷产、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽株旷。三九已至再登,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間晾剖,已是汗流浹背锉矢。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留齿尽,地道東北人沽损。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像循头,于是被迫代替她去往敵國和親绵估。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評論 2 354