iOS圖像最佳實(shí)踐總結(jié)

1. 前言

2018 WWDC 蘋果官方給出了關(guān)于iOS圖像處理的最佳實(shí)踐,本文主要是就官方文檔進(jìn)行分析總結(jié)以及較為全面的拓展延伸感局。

官方文檔:Image and Graphics Best Practices

2. 基礎(chǔ)預(yù)備知識(shí)

本地圖片顯示到屏幕中狗超,經(jīng)歷了哪些過(guò)程

代碼很easy呀,兩行搞定

    UIImage *image = [UIImage imageNamed:@"xxxxx"];
    imageView.image = image;

但是這中間的圖片加載真實(shí)過(guò)程如下

圖片加載過(guò)程
  1. 從磁盤讀取原始?jí)嚎s的圖片數(shù)據(jù)(png/jpeg格式等等)緩存到內(nèi)存
  2. CPU解壓成未壓縮的圖片數(shù)據(jù) (imageBuffer)
  3. 渲染圖片(會(huì)生成frameBuffer,幀緩存算芯,最終顯示到手機(jī)屏幕)

按照經(jīng)典的MVC架構(gòu),UIImage扮演model角色娄帖,負(fù)責(zé)承載圖片數(shù)據(jù)也祠,UIImageView充當(dāng)View的角色,負(fù)責(zé)渲染和展示圖片近速。系統(tǒng)提供接口非常的簡(jiǎn)單诈嘿,這中間隱藏了解碼的過(guò)程。

Buffers

Buffer是一段連續(xù)的內(nèi)存區(qū)域削葱,下面我們看下圖片處理相關(guān)的Buffer

Data Buffer

Data Buffer

Data Buffer存儲(chǔ)了圖片的元數(shù)據(jù)奖亚,我們常見(jiàn)的圖片格式,jpeg析砸,png等都是壓縮圖片格式昔字。Data Buffer的內(nèi)存大小就是源圖片在磁盤中的大小。

Image Buffer

Image Buffer

Image Buffer存儲(chǔ)的就是圖片解碼后的像素?cái)?shù)據(jù)首繁,也就是我們常說(shuō)的位圖作郭。
Buffer中每一個(gè)元素描述的一個(gè)像素的顏色信息,buffer的size和圖片的size成正相關(guān)關(guān)系弦疮。

Frame Buffer

Frame Buffer

Frame Buffer 存儲(chǔ)了app每幀的實(shí)際輸出

和OpenGL中FrameBuffer類似夹攒,蘋果不允許我們直接渲染操作屏幕顯示,而是把渲染數(shù)據(jù)放入幀緩存中胁塞,由系統(tǒng)按照60hz-120hz的頻率掃描顯示咏尝。

當(dāng)app視圖層級(jí)發(fā)生變化時(shí),UIKit 會(huì)結(jié)合 UIWindow 和 Subviews啸罢,渲染出一個(gè) frame buffer编检,然后按60hz的頻率掃描(ipad最高可以達(dá)到120hz)顯示到屏幕上。

解碼操作

UIImage負(fù)責(zé)解壓Data Buffer內(nèi)容并申請(qǐng)buffer(Image Buffer)存儲(chǔ)解壓后的圖片信息扰才。UIImageView負(fù)責(zé)將Image Buffer 拷貝至 framebuffer允懂,用于顯示屏幕展示。

解壓過(guò)程會(huì)大量占用cpu衩匣,所以UIImage會(huì)持有解壓后的圖片數(shù)據(jù)累驮,以便給需要渲染的地方復(fù)用數(shù)據(jù)酣倾。

渲染流程

渲染流程

綜上我們可以看到渲染的全過(guò)程。這里需要注意的是谤专,解碼后的ImageBuffer大小理論上只和圖片尺寸相關(guān)躁锡。

ImageBuffer按照每個(gè)像素RGBA四個(gè)字節(jié)大小,一張1080p的圖片解碼后的位圖大小是1920 * 1080 * 4 / 1024 / 1024置侍,約7.9mb映之,而原圖假設(shè)是jpg,壓縮比1比20蜡坊,大約350kb杠输,可見(jiàn)解碼后的內(nèi)存占用是相當(dāng)大的。

3. 官方最佳實(shí)踐

內(nèi)存的占用會(huì)導(dǎo)致我們app的CPU占用高秕衙,直接導(dǎo)致耗電大蠢甲,APP響應(yīng)慢

Memory & CPU

DownSampling(降低采樣)

在視圖比較小,圖片比較大的場(chǎng)景下据忘,直接展示原圖片會(huì)造成不必要的內(nèi)存和CPU消耗鹦牛,這里就可以使用ImageIO的接口,DownSampling勇吊,也就是生成縮略圖

DownSampling

具體代碼如下曼追,指定顯示區(qū)域大小


DownSampling Code

這里有兩個(gè)注意事項(xiàng)

  • 設(shè)置kCGImageSourceShouldCache為false,避免緩存解碼后的數(shù)據(jù)汉规,64位設(shè)置上默認(rèn)是開(kāi)啟緩存的礼殊,(很好理解,因?yàn)橄麓问褂迷搱D片的時(shí)候针史,可能場(chǎng)景不同晶伦,需要生成的縮略圖大小是不同的,顯然不能做緩存處理)
  • 設(shè)置kCGImageSourceShouldCacheImmediately為true啄枕,避免在需要渲染的時(shí)候才做解碼婚陪,默認(rèn)選項(xiàng)是false

這樣的縮略圖方式可以省去大量的內(nèi)存和CPU消耗,官方Case給出的前后內(nèi)存對(duì)比

DownSampling內(nèi)存對(duì)比

Prefetching && Background decoding

解碼過(guò)程是非常占用CPU資源的射亏,放在主線程一定會(huì)造成阻塞近忙,所以這個(gè)操作應(yīng)該放在異步線程竭业。代碼如下


code

Prefetching:預(yù)加載智润,也就是提前為之后的cell預(yù)加載數(shù)據(jù)(基本上主流的app都有這么做滴,iOS10之后未辆,系統(tǒng)引入的tableView(_:prefetchRowsAt:) 可以更加方便的實(shí)現(xiàn)預(yù)加載窟绷。)

小tips: 這里使用串行隊(duì)列可以很好地避免Thread Explosion,線程切換的代價(jià)是非常昂貴的咐柜,所以在我們app中應(yīng)該使用GCD串行隊(duì)列創(chuàng)建一個(gè)解碼線程兼蜈。

官方實(shí)現(xiàn)UI實(shí)例

我們現(xiàn)在需要實(shí)現(xiàn)下面的live按鈕


Live按鈕

先看一種不合理的實(shí)現(xiàn)方式


不合理繪制

我們先來(lái)分析這種方案的問(wèn)題所在攘残,


系統(tǒng)draw實(shí)現(xiàn)

UIView是通過(guò)CALayer創(chuàng)建FrameBuffer最后顯示的。重寫了drawRect方法为狸,Calayer會(huì)創(chuàng)建一個(gè)Backing Store歼郭,然后在Backing Store上執(zhí)行draw函數(shù),最后將內(nèi)容傳遞給frameBuffer最終顯示辐棒。

Backing Store的默認(rèn)大小和View的大小成正比病曾,以iphone6為例,750 * 1134 * 4 字節(jié) ≈ 3.4 Mb漾根。

iOS 12泰涂,對(duì) backing store 有做優(yōu)化,它的大小會(huì)根據(jù)圖片的色彩空間辐怕,動(dòng)態(tài)改變逼蒙。
在此之前,如果你使用 sRGB 格式寄疏,但是實(shí)際繪制的內(nèi)容是牢,只使用了單通道,那么大小會(huì)比實(shí)際要的大赁还,造成不必要開(kāi)銷妖泄。iOS 12 會(huì)自動(dòng)優(yōu)化這部分。

總結(jié)下這種使用drawRect繪制方案的問(wèn)題

    1. Backing Store的創(chuàng)建造成了不必要的內(nèi)存開(kāi)銷
    1. UIImage先繪制到Backing Store艘策,再渲染到frameBuffer蹈胡,中間多了一層內(nèi)存拷貝
    1. 背景顏色不需要繪制到Backing Store,直接使用BackGroundColor繪制到FrameBuffer

所以朋蔫,正確的實(shí)現(xiàn)姿勢(shì)是將這個(gè)大的view拆分成小的subview逐個(gè)實(shí)現(xiàn)罚渐。

背景顏色實(shí)現(xiàn)

這里有一個(gè)圓角的處理

UIView的maskView 及CALayer.maskLayer都會(huì)將圖層渲染到臨時(shí)的image buffer中,也就是我們常說(shuō)的離屏渲染驯妄,而CALayer.cornerRadius不會(huì)造成離屏渲染荷并,真正造成離屏渲染的是設(shè)置MaskToBounds這樣的屬性。所以背景圖直接使用UIView設(shè)置BackGroudColor即可青扔。

這里拓展下圓角的處理源织,先看一種不正確的做法


override func drawRect(rect: CGRect) {
    let maskPath = UIBezierPath(roundedRect: rect,
                                byRoundingCorners: .AllCorners,
                                cornerRadii: CGSize(width: 5, height: 5))
    let maskLayer = CAShapeLayer()
    maskLayer.frame = self.bounds
    maskLayer.path = maskPath.CGPath
    self.layer.mask = maskLayer
}

首先同理,重寫drawRect會(huì)造成不必要的backing store內(nèi)存開(kāi)銷微猖,并且這種做法的本質(zhì)是創(chuàng)建遮罩mask谈息,再進(jìn)行圖層混合,同樣會(huì)離屏渲染凛剥。

正確的姿勢(shì)侠仇,
對(duì)于UIView直接使用CornerRadius,CoreAnimation可以為我們?cè)诓活~外創(chuàng)建內(nèi)存開(kāi)銷的情況下繪制出圓角。

對(duì)于UIImageView可以使用CoreGraphics自己裁剪出帶圓角的Image逻炊,實(shí)例代碼如下


extension UIImage {
    func drawRectWithRoundedCorner(radius radius: CGFloat, _ sizetoFit: CGSize) -> UIImage {
        let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: sizetoFit)
        
        UIGraphicsBeginImageContextWithOptions(rect.size, false, UIScreen.mainScreen().scale)
        CGContextAddPath(UIGraphicsGetCurrentContext(),
                         UIBezierPath(roundedRect: rect, byRoundingCorners: UIRectCorner.AllCorners,
                                      cornerRadii: CGSize(width: radius, height: radius)).CGPath)
        CGContextClip(UIGraphicsGetCurrentContext())
        
        self.drawInRect(rect)
        CGContextDrawPath(UIGraphicsGetCurrentContext(), .FillStroke)
        let output = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        
        return output
    }
}

Live圖片實(shí)現(xiàn)

直接使用UIImageView互亮,這里有個(gè)技巧,如果是純色圖片余素,想要展示不同顏色的同一張圖片豹休,可以使用UIImageView的tintColor屬性平鋪顏色,來(lái)達(dá)到復(fù)用圖片的目的桨吊。


image

代碼如下:


UIImage.withRenderingMode(_:)
UIImageView.tintColor

文本實(shí)現(xiàn)

文本使用UILabel可以減少百分之75的Backing Store開(kāi)銷慕爬,系統(tǒng)針對(duì)UILabel做了優(yōu)化,并且自動(dòng)更新Backing Store的size,針對(duì)emoji和富文本內(nèi)容屏积。

最終實(shí)現(xiàn)

最終Live按鈕的正確實(shí)現(xiàn)方案如下圖

image

推薦使用Image Assets

  • 基于名稱和特效優(yōu)化了查找效率医窿,更快的查找圖片
  • 運(yùn)行時(shí),對(duì)內(nèi)存的管理也有優(yōu)化
  • App Slicing炊林,app安裝包瘦身姥卢。iOS 9 后會(huì)從 Image Assets 中保留設(shè)備支持的圖片 (2x 或者 3x)
  • iOS 11 后的 Preserve Vector Data。支持矢量圖的功能渣聚,放大也不會(huì)失真

Advanced Image Effects

對(duì)于圖片的實(shí)時(shí)處理推薦使用CoreImage框架独榴。
例如將一張圖片的灰度值進(jìn)行調(diào)整這樣的操作,有滴小伙伴可能使用CoreGraphics獲取圖像的每個(gè)像素點(diǎn)數(shù)據(jù)奕枝,然后改變灰度值棺榔,最終生成目標(biāo)圖標(biāo),這種做法將大量gpu擅長(zhǎng)的工作放在了cpu上處理隘道,合理的做法是: 使用CoreImage的濾鏡filter或者metal症歇,OpenGL的shader,讓圖像處理的工作交給GPU去做谭梗。

Drawing Off-Screen

對(duì)于需要離屏渲染的場(chǎng)景推薦使用UIGraphicsImageRenderer替代UIGraphicsBeginImageContext忘晤,性能更好,并且支持廣色域激捏。

4. 拓展與思考

用提問(wèn)的方式來(lái)拓展一下设塔,針對(duì)每個(gè)問(wèn)題進(jìn)行深入的思考

問(wèn)題一:圖像展示有這么多細(xì)節(jié)在里面,可是為什么在平常開(kāi)發(fā)中為什么沒(méi)有感覺(jué)到远舅,可以從哪些地方對(duì)自己的工程進(jìn)行優(yōu)化闰蛔。

答:我們平常大部分會(huì)使用UIImage imageNamed這樣的API加載了本地圖片,而網(wǎng)絡(luò)圖片則使用了SDWebImage或者YYWebImage等框架來(lái)加載图柏。所以沒(méi)有去細(xì)究序六。

進(jìn)而引申出

問(wèn)題二: 使用imageNamed,系統(tǒng)何時(shí)去解碼爆办,有沒(méi)有緩存难咕,緩存的大小是多少课梳,有沒(méi)有性能問(wèn)題距辆,和imageWithContentsOfFile有什么區(qū)別

答: 一一來(lái)解答這個(gè)問(wèn)題

  1. 首先先說(shuō)imageNamed和imageWithContentsOfFile有什么區(qū)別余佃,想必大部分小伙伴都很清楚,因?yàn)檫@也是面試?yán)仙U劦臇|西跨算。imageNamed加載本地圖片會(huì)緩存圖片爆土,也就是加載一千張相同的本地圖片,內(nèi)存中也只會(huì)有一份诸蚕,而imageWithContentsOfFile不會(huì)緩存步势,也就是重復(fù)加載相同圖片,在內(nèi)存中會(huì)有多份圖片數(shù)據(jù)背犯。
  2. imageNamed加載圖片會(huì)將圖片源數(shù)據(jù)和解碼后的數(shù)據(jù)加載入內(nèi)存緩存中坏瘩,只有收到內(nèi)存警告的時(shí)候才會(huì)釋放,有興趣的小伙伴可以自行調(diào)試一下漠魏。
  3. 關(guān)于UIImage對(duì)象何時(shí)去解碼倔矾,其實(shí)剛剛我們?cè)诮档筒蓸拥臅r(shí)候已經(jīng)提到了,kCGImageSourceShouldCacheImmediately屬性系統(tǒng)默認(rèn)是false柱锹,我們可以看ImageIO/CGImageSource.h文件中kCGImageSourceShouldCache的注釋

pecifies whether image decoding and caching should happen at image creation time.
The value of this key must be a CFBooleanRef. The default value is kCFBooleanFalse (image decoding will happen at rendering time).

也就是說(shuō)UIImage只有在屏幕上渲染時(shí)再去解碼的哪自。而關(guān)于UIImageView的操作一定是在主線程,解碼操作是放在主線程的禁熏。所以如果在tableview滑動(dòng)中頻繁的創(chuàng)建較大的UIImage渲染展示壤巷,會(huì)造成主線程阻塞。

總結(jié): imageNamed默認(rèn)帶緩存瞧毙,緩存通過(guò)NSCache實(shí)現(xiàn)胧华。適用于需要頻繁復(fù)用的圖片的加載,而imageWithContentsOfFile不會(huì)緩存宙彪,適用于不常用的較大圖片的加載撑柔,由于系統(tǒng)默認(rèn)主線程解碼UIImage,所以imageNamed僅僅適用于加載較小的例如APP各個(gè)tab的icon您访,需要在首屏展示的圖片铅忿。而不適用于滑動(dòng)的下載好的大量網(wǎng)絡(luò)圖片的本地加載。會(huì)造成主線程阻塞灵汪。

5. 正確的網(wǎng)絡(luò)圖片加載方式

其實(shí)這里SDWebImage或者YYWebImage等框架已經(jīng)給出了正確的姿勢(shì)檀训,細(xì)節(jié)可以挑其中一個(gè)閱讀源碼即可。

分享下優(yōu)秀的源碼解析

YImage 設(shè)計(jì)思路享言,實(shí)現(xiàn)細(xì)節(jié)剖析

YYWebImage 源碼剖析:線程處理與緩存策略

下載圖片主要簡(jiǎn)化流程如下

  1. 從網(wǎng)絡(luò)下載圖片源數(shù)據(jù)峻凫,默認(rèn)放入內(nèi)存和磁盤緩存中
  2. 異步解碼,解碼后的數(shù)據(jù)放入內(nèi)存緩存中
  3. 回調(diào)主線程渲染圖片
  4. 內(nèi)部維護(hù)磁盤和內(nèi)存的cache览露,支持設(shè)置定時(shí)過(guò)期清理荧琼,內(nèi)存cache的上限等

加載圖片的主要簡(jiǎn)化流程如下

  1. 從內(nèi)存中查找圖片數(shù)據(jù),如果有并且已經(jīng)解碼,直接返回?cái)?shù)據(jù)命锄,如果沒(méi)有解碼堰乔,異步解碼緩存內(nèi)存后返回
  2. 內(nèi)存中未查找到圖片數(shù)據(jù),從磁盤查找脐恩,磁盤查找到后镐侯,加載圖片源數(shù)據(jù)到內(nèi)存,異步解碼緩存內(nèi)存后返回驶冒,如果沒(méi)有去網(wǎng)絡(luò)下載圖片苟翻。走上面的流程。

分析:

  • 這樣滴流程解決了UIImage imageNamed這種加載一定在主線程解碼圖片的問(wèn)題骗污,異步加載崇猫,避免了主線程阻塞。
  • 通過(guò)緩存內(nèi)存方式需忿,避開(kāi)了頻繁的磁盤IO
  • 通過(guò)緩存解碼后的圖片數(shù)據(jù)邓尤,避開(kāi)了頻繁解碼的CPU消耗。

6. 超大圖片的處理

之前我們分析過(guò)1080p的圖片解碼后的內(nèi)存大小贴谎,大約是7.9mb汞扎,如果是4k,8k圖擅这,這個(gè)內(nèi)存占用將會(huì)非常的大澈魄,如果使用SDWebImage或者YYWebImage的默認(rèn)解碼緩存技術(shù)方案去加載多張這樣的大圖,帶來(lái)的結(jié)果會(huì)是內(nèi)存爆掉仲翎。閃退痹扇。

可以設(shè)置SDWebImage或者YYWebImage的Option選項(xiàng)不解碼下載好的圖片

那么大圖該怎么處理呢,這里有兩個(gè)場(chǎng)景

  1. 一張超大圖加載在一個(gè)小的view上

解決方法: 使用蘋果推薦的縮略圖DownSampling方案即可

  1. 像微信溯香,微博長(zhǎng)圖詳情那樣鲫构,全屏加載大圖,通過(guò)拖動(dòng)來(lái)查看不同位置圖片細(xì)節(jié)

解決方法: 使用蘋果的CATiledLayer去加載玫坛。原理是分片渲染结笨,滑動(dòng)時(shí)通過(guò)指定目標(biāo)位置,通過(guò)映射原圖指定位置的部分圖片數(shù)據(jù)解碼渲染湿镀。這里不再累述炕吸,有興趣的小伙伴可以自行了解下官方API。

7. 總結(jié)

了解圖像加載的細(xì)節(jié)和全過(guò)程非常有必要勉痴,有助于我們?cè)谄匠i_(kāi)發(fā)中選擇合適的方案赫模,做出合理的性能優(yōu)化。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蒸矛,一起剝皮案震驚了整個(gè)濱河市瀑罗,隨后出現(xiàn)的幾起案子胸嘴,更是在濱河造成了極大的恐慌,老刑警劉巖斩祭,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件劣像,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡停忿,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門蚊伞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)席赂,“玉大人,你說(shuō)我怎么就攤上這事时迫÷#” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵掠拳,是天一觀的道長(zhǎng)癞揉。 經(jīng)常有香客問(wèn)我,道長(zhǎng)溺欧,這世上最難降的妖魔是什么喊熟? 我笑而不...
    開(kāi)封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮姐刁,結(jié)果婚禮上芥牌,老公的妹妹穿的比我還像新娘。我一直安慰自己聂使,他們只是感情好壁拉,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著柏靶,像睡著了一般弃理。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上屎蜓,一...
    開(kāi)封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天痘昌,我揣著相機(jī)與錄音,去河邊找鬼炬转。 笑死控汉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的返吻。 我是一名探鬼主播姑子,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼测僵!你這毒婦竟也來(lái)了街佑?” 一聲冷哼從身側(cè)響起谢翎,我...
    開(kāi)封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎沐旨,沒(méi)想到半個(gè)月后森逮,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡磁携,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年褒侧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谊迄。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡闷供,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出统诺,到底是詐尸還是另有隱情歪脏,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布粮呢,位于F島的核電站婿失,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏啄寡。R本人自食惡果不足惜豪硅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挺物。 院中可真熱鬧舟误,春花似錦、人聲如沸姻乓。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蹋岩。三九已至赖草,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間剪个,已是汗流浹背秧骑。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留扣囊,地道東北人乎折。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像侵歇,于是被迫代替她去往敵國(guó)和親骂澄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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