【學習總結】03 | Auto Layout 是怎么進行自動布局的,性能如何?

1、前言

首先,我認為學習總結,要有所總,所結,就是有歸納后,能用自己的話告訴別人!有所結,就是有所收獲輸出,一般我認為是思維導圖,所以,每篇文章前,我都會先給出文章的腦圖:

iOS開發高手課-03-AutoLayout是怎么進行自動布局的,性能如何?.png

2、正文

注意,本系列總結不會引用或提供原課程文章所有的內容或代碼,只會作出思維導圖,需要學習可購買課程 《iOS開發高手課 - 極客時間》

帶問題找答案

  1. Auto Layout 如何實現自動布局的?
  2. 這種布局算法真的會影響性能嗎?
  3. 應該選擇手動布局還是選擇Auto Layout呢?

文中提了3個問題,那么這3個問題怎么解答呢?可以利用第一篇文章說的,一個知識點的方法論來解答。這里就不展開了,具體可以查看 如何建立你自己的開發知識體系 | iHTCboy's blog

Auto Layout

  1. 為什么需要 Auto Layout
  2. 什么是 Auto Layout
  3. 怎么使用 Auto Layout
  4. 使用 Auto Layout 時注意的問題
  5. Auto Layout 的應用領域
  6. Auto Layout 的優缺點
  7. Auto Layout 觸類旁通

1. 為什么需要 Auto Layout

為什么需要?一般遇到解答不了的問題,可以試試逆向!,那就反推,就是 沒有 Auto Layout 之前是怎么樣的 ?沒有 Auto Layout 時,我們是通過設置元素的 Frame 來手動指定界面布局的大小和位置。

剛開始,大家的App并不復雜,頁面布局也很簡單,并且有一個歷史原因,就是 iPhone4/s(960x640像素)時代 (更早的 iPhone 3G/S,3G網絡,S是速度 Speed,因為08、09年那會,一般國內開發者估計都沒有見過,所以這里就簡單提一下。) ,iPhone 的寬度都是 640 像素, 開發時用 320 個點計算, 直到 2012年9月發布 iPhone5 (1136x640像素),屏幕高度增長了!4寸,當時三星都開始5寸大屏了,大家當年吐槽iPhone長的圖片大家可以搜索看看!此時此刻,適配 iPhone5 依然并沒有太大難度,蘋果默認針對沒有適配 iPhone5 的App,在 iPhone5 打開App時,上下2端黑屏,這樣來過度。

就在當年,2012年的 WWDC2012 蘋果發布了 Auto Layout 技術,從 iOS6 以后開始支持(Xcode4)。2013年9月發布 iPhone5s,但是大多數的開發者還是習慣使用傳統的UI布局方式,大家的開發App布局并不會覺得麻煩,直到2014年才發生變化!

2014年9月,蘋果發布了 iPhoe 6(1334x750像素)、iPhone 6 plus(1920x1080像素),屏幕適配工作變得非常必要!如果用計算數值的方法工作量增加了幾倍。因為4個尺寸的屏幕不一樣!iPhone4/s(960x640像素)、iPhone 5/s (1136x640像素)、iPhoe 6(1334x750像素)、iPhone 6 plus(1920x1080像素),雖然大家還是可以按比例計算做縮放,但是這樣并不能解決所有問題,因為如果想像素級還原設計、效果圖調整尺寸等,可能都需要重新手動計算一次。所以,這樣算過程,應該是2014年后才是 Auto Layout技術被大家廣泛應用。我在 GitHub 查看了 iOS 最經典的 Masonry 庫是 2013年7月22號 創建的,也是符合這個技術歷史的進程啊~ (ps: Masonry 源碼值得研究學習,有很多可學習的知識,詳細搜索引擎一下,已經有很多好的文章啦~)

那么到此,大家明白了,為什么需要 Auto Layout 了嗎?

2. 什么是 Auto Layout

Auto Layout 是一種基于約束的、描述性的布局系統。也就是使用約束條件來描述布局,View 的 Frame 會根據這些描述來進行計算。

3. 怎么使用 Auto Layout

2012年,Xcode4,iOS6 引入了 NSLayoutConstraint 類,并且 VFL (Visual Format Language,視覺格式語言) 的方式創建約束。通過下面2個方式來生成布局約束組:

+ (NSArray *)constraintsWithVisualFormat:(NSString *)format options:(NSLayoutFormatOptions)opts metrics:(NSDictionary *)metrics views:(NSDictionary *)views;
+(id)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1 relatedBy:(NSLayoutRelation)relation toItem:(id)view2 attribute:(NSLayoutAttribute)attr2 multiplier:(CGFloat)multiplier constant:(CGFloat)c;

關于具體使用和 VFL 相關使用可以查看官方文檔:Auto Layout Guide: Visual Format Language,所有學習資料官方文檔是第一手!

4. 使用 Auto Layout 時注意的問題

Constraint Churn(約束流失)
  1. Avoid removing all constraints (避免刪除所有約束)
  2. Add static constraints once (僅一次添加靜態約束,且不要再改變它們)
  3. Only change the constraints that need changing(只更改需要更改的約束)
  4. Hide views instead of removing them(隱藏視圖而不是刪除它們)

注:來自:High Performance Auto Layout - WWDC 2018 - Videos - Apple Developer

Summary
  1. Stack Views help build easily maintainable layouts (堆棧視圖有助于構建易于維護的布局)
  2. Use activate and deactivate for constraints (使用激活和停用來約束)
  3. Determine size through constraints (通過約束確定尺寸)
  4. Override intrinsicContentSize judiciously (明智地覆蓋內在內容大小)
  5. Use priorities to properly solve your layout (使用優先級來正確解決您的布局)
  6. Alignment goes beyond top, bottom, and center (對齊超出頂部,底部和中心)
  7. Keep localization in mind (記住本地化)

注:來自:Mysteries of Auto Layout, Part 1 - WWDC 2015 - Videos - Apple Developer

以上注意事項來自 WWDC,具體視頻可參考文章末尾的引用來源。這里不打算詳細解說,因為視頻真的說的很好,推薦大家去看看啊。

5. Auto Layout 的應用領域

Auto Layout 其實,除了可以用代碼來創建,蘋果的可視化布局,也有使用,nibxibstoryboard,那么大家對這3個東西了解熟悉嗎?

Interface Builder 工具

Interface Builder 在 Xcode 4 之前,是一個獨立的軟件,Xcode 4 開始集成到Xcode 中的。這個大家現在比較熟悉,就是可以用鼠標以圖形化的方式,拖拉元素來創建UI界面。

NIB、XIB 區別

Xcode 3 前,Interface Builder 創建的文件是 NIB(二進制格式,NeXT Interface Builder),不利于版本控制。

Xcode 3 開始,Interface Builder 使用了一種新的文件格式 XIB(XML文本格式,OS X Interface Builder或XML Interface Builder),XIB在工程編譯時被轉換成NIB;

XIB 文件

XIB 是一個描述文件,包含了用戶界面及相關元素;一個 XIB 文件對應一個ViewController,也可以通過使用XIB來自定義View。

StoryBoard 故事板

iOS 5 (Xcode3)開始,Apple提供了一種全新的布局界面方式 StoryBoard 來拖拉創建界面;StoryBoard 是一組 ViewController 對應的 XIB,以及它們之間的轉換方式的集合;在StoryBoard 中,不僅可以看到每個 ViewController 的布局樣式,也可以知道各個ViewController 之間的轉換關系。

對于2013~2015年,當然非常的缺乏 iOS 開發者,所以一般的公司只有一個 iOS 開發,那么這時候,StoryBoard 就是最快速的創建界面的工具!此時,面對多種設備時,Auto Layout 就是錦上添花,可以大幅提高 UI 開發效率,一次性做出適合所有屏幕尺寸的 UI。

現在,對使用 StoryBoard + Auto Layout 還是使用 代碼 + Masonry/SnapKit,依然沒有最終的答案,因為各有優缺點。

6. Auto Layout 的優缺點

Auto Layout的優點不用多說,解決手動計算每個屏幕尺寸的布局,提高工程效率!缺點的話,大概就是適當的導致性能降低?

Auto Layout 導致性能降低?是嗎?為什么是?為什么不是?前面的“什么是 Auto Layout”只是簡單的簡介,Auto Layout 是一個 布局系統,沒有深入的介紹,不知道大家有沒有讀到那里時,產生疑問或興趣呢?

所以,要知道 Auto Layout 的優缺點,還需要深入了解它的原理,才能理解優缺點!否則,死記硬背過后還是不明不白。Auto Layout 是一套 布局引擎系統,叫作 Layout Engine ,是 Auto Layout 的核心。了解 Layout Engine 的布局原理,是理解它的性能(優缺點)的基礎。

所以在這里補充一下,主要參考蘋果官方的 WWDC 視頻來解說 Layout Engine,引用主講者 Jason Yao 說的:我們并不只想說這樣不好,我們相信大家真正的理解它,理解這個過程!剝開表面!了解真正發生了什么?

The Render Loop (渲染循環)
01-The-Render-Loop.jpg

布局引擎是工作流程如上圖所示,Update Constraints(更新約束)流,從父視圖的約束開始更新,傳遞到子視圖,再到子子視圖,這里有2點要說明,一是這個更新約束只是從有約束變化的視圖開始,并不是所有視圖都更新;二是這個傳遞過程為什么是從父級開始,因為當一個約束變化時,這個約束是自己與父級的關系或自己與子級的關系,所以會通知給子級視圖來響應!Layout(布局)流,是從子視圖開始布局,為什么是反向呢?一個視圖的布局,它一定是由自己和所有的子視圖組成,那自己的布局一定是受子視圖的布局和約束影響,所以先確認所有子視圖的約束,那自己的視圖就確實了,這個可能有點繞,大家可以看看原視頻來理解。Display (渲染顯示)流,因為子視圖布局確定了,那顯示的大小和位置就能確定,所以也是從子視圖開始顯示。

What is updateConstraints?(什么是 updateConstraints ?)
02-The-Render-Loop.jpg

updateConstraints() 是視圖的約束更新時會調用的方法,可以重寫這個方法來自行設置約束條件。圖中列出了 Update Constraints(更新約束)、Layout(布局)、Display (渲染顯示) 的對應關系的方法。這里與第一個圖并不是對應的關系!而是這3個狀態的生命周期分別有對應的方法來響應,要怎么理解?

要理解這個圖,最簡單是從了解 layoutSubviews()setNeedsLayout()layoutIfNeeded() 三者的關聯和作用,明白這3個方法的作用,那么就知道這個生命周期是什么意思。

setNeedsLayout():當一個UIView對象此方法時,實際上等同于做了一個標記,告訴系統需要重新布局,但不會立刻執行,直到 drawing cycle 循環到達該節點時,才會調用layoutSubviews() 方法重新布局。

layoutIfNeeded():允許在 drawing cycle 循環到達該節點之前,就立刻執行布局刷新調用 layoutSubviews() 方法。

layoutSubviews():在上面2個方法調用后,都會被調用。另外,當 addSubviewsize 改變或滑動UIScrollView、旋轉Screen等都會觸發。

回到 updateConstraints()setNeedsUpdateConstraints()updateConstraintsIfNeeded(),那它們的作用也是對應的關系。現在剩下的問題就是,為什么需要這些方法???我們在開發中,是不是會改變視圖后,更馬上刷新這個頁面就會用到這些方法。那么同時,我們改變(更新)約束后,是不是也希望刷新約束?所以,這些方法的作用就是這樣,那么為什么要這樣做,有什么好處呢?因為約束更新,需要重新計算一次,那我們可以自己來控制要不要更新,能減少Constraint Churn(約束流失),減少性能損耗。為什么?舉例為說,你給一個Lable設置了新的字體,此時要不要馬上更新約束?如果你下一句代碼是設置新的字體大小呢?所以,你可以控制一組約束,什么時候才更新,現在明白了這些方法和它們對應的關系了吧。剩下的 draw(_:)setNeedsDisplay() 也是同理,只是層級更底,渲染的調用刷新。

Activating a Constraint(激活一個約束)
03-Activating-a-Constraint.jpg

說了這么多,好像還沒有說到 Layout Engine 布局引擎的工作流程。這個圖就是這個流程,每個 Window 下有一個 Engine(引擎),那么 View(視圖) 包含 Variable(變量)和 Constraint(約束),那么 Engine 與 View 之間通過 Equation(等式) 聯系。

那么要理解布局引擎的工作,其實很簡單,就是Constraint約束描述了View視圖的位置和大小(size),那么就通過 Variable(變量)來向引擎獲取,需要獲取什么?
minY minX height width,那現在引擎需要做的就是把約束解析出這4個值,所以就是通過 Equation(等式)求解。Equation等式其實也很容易理解,就是像下面這些:

 text1.minX = 8
 text1.width = 100
 text2.minX = text1.minX + text1.width +20

最后解出等式,等到結果:

 text1.minX = 8
 text1.width = 100
 text2.minX = 128

所以,布局引擎的核心就是這個等式的計算,我們初中就算了一元二次方程式,后來學習了多次方程式求解,是不是覺得很簡單?那么這些數學問題,我們讀書時就知道數字題目答案只有一個,但是解法有很多!,所以原文章提到 Cassowary 算法Simplex 算法 就是解決這個方程式求解的問題。我們人可以做一些騷操作,但是要程序操作,一定是通用的解法,這也是算法為什么難!數學為什難!因為找到規律的人,往往是牛逼的人!

Equation(等式)求解的結果,通過 setNeedsLayout() 通知所有的視圖,視圖通過 UIView.layoutSubviews() 方法從引擎中復制數據到子視圖(Copying data from engine to subview)。

所以,這個就是整個引擎工作的過程!不知道我說的明白不明白!理解了這個過程,就能回復前面的 Auto Layout 會導致性能降低?從原理上說,引擎的工作方式和我們開發者手動計算的過程是一樣的!導致性能降低的原因,是因為一些約束流失(Constraint Churn)、不可滿足約束(Unsatisfiable Constraints)等導致消耗大量計算。可能你會有疑問:為什么引擎不會做傻事呢?比如我自己計算的布局,我都緩存了,引擎做了嗎?答案是肯定的,Engine is a layout cache and tracker(引擎是一個布局緩存和約束跟蹤器),也就是你設置的布局,如果不必更新是不會更新約束的;同時,約束更新時引擎會知道那些約束需要重新計算,那么不需要再計算!所以,引擎系統導致性能降低本質來說可以忽視,剩下的就是,如何避免我們設置約束時不合理導致性能降低呢?可以參考前面一節:“4. 使用 Auto Layout 時注意的問題”,詳細的所有原則這里就不展開了,下面會重點提幾個。到此,引擎的原理問題算是解決啦!

Building a More Performant Layout(構建一個更好的性能布局)
04-Building-a-More-Performant-Layout.jpg

這些列舉了我們常見的 TableView 滾動卡頓的問題,其實我們知道原因,就是滾動過程中 Cell 要重繪,需要我們知道 Cell 可以重用,但是每個 Cell 的布局和長度可能千變萬化,所以卡頓的問題,有一個就是 Cell 變化過程中,重新布局的問題。首先,避免刪除所有約束,因為所有約束重新計算可能不是必要的,比如上圖的用戶頭像的位置和大小,固定約束后就不要去改變它啦!那么,可能有一些 Cell 有圖片,有一些 Cell 沒有時,可以通過 setHidden: 方法 和 noImageConstraints 圖片的單獨約束來控制,這樣,盡最大可以減少約束的計算,導致性能的降低,從而盡可能的避免卡頓!另外,使用 Auto Layout 可以多使用 Compression Resistance PriorityHugging Priority,利用優先級的設置,讓布局更加靈活,代碼更少,更易于維護。

以上內容來源: High Performance Auto Layout - WWDC 2018 - Videos - Apple Developer

Independent Sibling Views(獨立的兄弟視圖)
05-Independent-Sibling-Views.jpg

前面說到 如何避免我們設置約束時不合理導致性能降低呢?我們已經知道引擎需要計算方程式求解,對于每個元素視圖單獨約束,相互不依賴時,其實就是解決一元一次方程式,所以就是一條直線。對于我們開發來說,避免約束的相互依賴就能減少性能消耗,所以我們設置約束時,能不依賴的約束的視圖,讓他們保持獨立!就是最優解!

Dependent Sibling Views(互相依賴的兄弟視圖)
06-Dependent-Sibling-Views.jpg

對于相互依賴的約束,它們就構形了多元方程式,依賴關系越多,曲線就越陡峭。所以,我們能做的還是一樣,盡量減少多個視圖之前的約束依賴!

Nested Views(嵌套的視圖)

07-Nested-Views.jpg

對于嵌套的視圖,同理,減少視圖的嵌套,減少嵌套的層級,都是解決性能的重要手段!

以上內容來源:What's New in Cocoa Touch - WWDC 2018 - Videos - Apple Developer 。更多的技巧,可以參考前面一節:“4. 使用 Auto Layout 時注意的問題”。

Building the Layout(構建布局)
08-Building-the-Layout.jpg

前面只是簡單的說明了引擎就是計算出視圖的位置和大小,那么具體是怎么計算的呢?這個圖片顯示了布局引擎的工作流。每個視圖在得到自己的布局之前,Layout Engine 會將視圖、約束、優先級、固定大小等通過計算轉換成最終的位置和大小。

09-Building-the-Layout.jpg

所以,最終的 Layout Engine 計算到布局就是這樣的過程,細節點還有很多。還是那句話,授人以魚不如授人以漁!大家有“漁”后,自然要自己捉魚!,這里就不詳細解進了,可觀看 WWDC 視頻:Mysteries of Auto Layout, Part 1 - WWDC 2015 - Videos - Apple Developer

最后,關于 Auto Layout 還有很多知識,比如怎么調試 Auto Layout 的 Debug?可以查看 What's New in Auto Layout - WWDC 2016 - Videos - Apple Developer 視頻。Auto Layout 的流程(The Layout Cycle),可以查看 Mysteries of Auto Layout, Part 2 - WWDC 2015 - Videos - Apple Developer

7. Auto Layout 觸類旁通

UIStackView 與 Flexbox

UIStackView 是2015年 iOS9 蘋果推出的一套 API,它可以很好地減輕手動寫或拖 constraint 帶來的重復繁瑣的工作,也可以自動化的處理排列和元素個數的變化。(當年因為需要iOS9+,導致很少有開發者使用,放在2020年的今年,這個控件可以熟悉一下啊!)與 Web 前端的 Flexbox 響應式布局是一個原理。UIStackView 特點有下面4個:

  1. Easy to build(容易構建)
  2. Easy to maintain (容易維護)
  3. Composable Stack Views (可組合的堆棧視圖)
  4. Lightweight (輕量級)

這里并不打算講解 UIStackView 有多利害!確實它很利害。具體可以查看 WWDC 演示的Demo Mysteries of Auto Layout, Part 1 - WWDC 2015 - Videos - Apple Developer

關于 Flexbox 的思想可以看看文章 30 分鐘學會 Flex 布局 - 知乎Flex 布局教程:語法篇 - 阮一峰的網絡日志,個人覺得文章寫得很好。另外關于 UIStackView 的強大就借 UIStackView 入坑指南 - 掘金 文章的一張圖來總結吧:

10-UIStackView-Layout.png
SwiftUI
  • 命令式編程(Imperative Programming):命令“機器”如何去做事情(how),這樣不管你想要的是什么(what),它都會按照你的命令實現。
  • 聲明式編程(Declarative Programming):告訴“機器”你想要的是什么(what),讓機器想出如何去做(how)。

SwiftUI聲明式編程,還有函數式編程響應式編程 等編程思想,這里就不說了。這里提的原因是,SwiftUI 采用不同的布局方式,但是依然使用 Auto Layout,并且 VStack 也是天合之作吧!

3、總結

首先,關于 iOS 、 Xcode 和對應年份的關系,可以梳理一下,2012年 Xcode4 對應 iOS 6 ,2013年 Xcode5 和 iOS 7。它們相差2,所以,2020年將發布 Xcode12 和 iOS 14。這個就是一個數字游戲,可記可不記,就是想說明,記憶可以找規律的。

回到最前面提到的3個疑問題,你是不是已經有了自己的答案了呢?

  1. Auto Layout 如何實現自動布局的?
  2. 這種布局算法真的會影響性能嗎?
  3. 應該選擇手動布局還是選擇Auto Layout呢?

本章內容夠不夠深不深入?這個大家的了解水平不一樣,如果覺得不夠深入,還是可以參考本文末的參考擴展了解更多,因為 WWDC 視頻中提到了很多細節的東西,有一些很棒,有一些很有趣,這里就不一一列出。因為原文還提到了 Cassowary 算法Simplex 算法,本文并沒有把它作為主角色來解讀,為什么呢?因為,它并不是我們了解和理解 Auto Layout 最核心的必備知識,并且它對技術有要求,不可能每個人都能看懂,否則文章一上來就會讓讀者害怕,適得其反。所以,在本文的基礎上,如果大家還想深入了解,那這將是一道窗口,而不是一道門口!

以后關于 Auto Layout 的知識,你是不是能更好的跟別人講解呢?這些就是本文想要做的事情,當然還有更深入的知識可以研究,切記這只是開始!修行在個人~


注:更多關于 iOS 開發和程序開發相關的內容,可以查看系列,目前還在連載中 【學習總結】iOS開發高手課 -- (連載中) | iHTCboy's blog,以上,希望對你有用!

參考

WWDC:

Article:


  • 如有侵權,聯系必刪!
  • 如有不正確的地方,歡迎指導!
  • 如有疑問,歡迎在評論區一起討論!


注:本文首發于 iHTCboy's blog,如若轉載,請注來源

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容