Swift 內存管理 & Runtime

前言

上篇文章Swift 指針重點介紹了指針的類別和對應的應用場景,本篇文章接著介紹Swift中的內存管理,以及Runtime的一些應用場景,盡管Swift是一門靜態語言

一、內存管理

和OC一樣,Swift中也是通過引用計數的方式來管理對象的內存的,之前的文章Swift編譯流程 & Swift類中,也分析過引用計數refCounts,它是類RefCounts類型,好比一個指針,占8字節大小。接下來我們重點看看強引用弱引用循環引用這幾個主要場景。

1.1 強引用

首先我們看一個例子??

class LGTeacher {
    var age: Int = 18
    var name: String = "Luoji"
}

var t = LGTeacher()
var t1 = t
var t2 = t

x/8g查看變量t的內存??

可以看到,t的引用計數是0x0000000600000003,why?不應該是個單獨的數字嗎?
接下來還是要回到類RefCounts,查看這個類的定義??


類RefCounts其實是個模板類,我們來看看傳入的模板類型是什么?

回到refCounts的定義?? 它是InlineRefCounts類型

#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS       \
  InlineRefCounts refCounts

接著搜索InlineRefCounts??

typedef RefCounts<InlineRefCountBits> InlineRefCounts;

所以類RefCounts是模板類,InlineRefCountsInlineRefCountBits別名,接著我們看看InlineRefCountBits的定義??

typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;

同理,InlineRefCountBits又是RefCountIsInline的別名,通過模板類RefCountBitsT,最終我們定位到RefCountBitsT??

這個模板類中,只有一個成員bits,它的實質是RefCountBitsInt中的type屬性取的一個別名,所以bits的真正類型是uint64_t64位整型數組??

至此,我們分析得出結論

referCounts的本質是64位整型數組

接下來,我們看看Swift底層創建對象的過程_swift_allocObject_??

其中調用了new (object) HeapObject(metadata);??

定位到refCounts對應的構造是InlineRefCounts::Initialized??

  enum Initialized_t { Initialized };

  // Refcount of a new object is 1.
  constexpr RefCounts(Initialized_t)
    : refCounts(RefCountBits(0, 1)) {}

Initialized是一個枚舉Initialized_t,而Initialized_t又是模板類RefCounts的類型T對應的是RefCountBits(0, 1),最終定位到RefCountBits??

之前我們分析過,referCounts的本質是RefCountBitsInt中的type屬性,而RefCountBitsInt又是模板類RefCountBitsT模板類型T,所以RefCountBits(0, 1)實質調用的是模板類RefCountBitsT的構造方法??

LLVM_ATTRIBUTE_ALWAYS_INLINE
constexpr
RefCountBitsT(uint32_t strongExtraCount, uint32_t unownedCount)
: bits((BitsType(strongExtraCount) << Offsets::StrongExtraRefCountShift) |
       (BitsType(1)                << Offsets::PureSwiftDeallocShift) |
       (BitsType(unownedCount)     << Offsets::UnownedRefCountShift))
{ }

strongExtraCount傳值為0,unownedCount傳值為1。
完整的bits位域結構體??

大致分布圖??

其中需要重點關注UnownedRefCountStrongExtraRefCount

那么至此,我們把樣例中的引用計數值0x0000000600000003用二進制展示??

可見,33位置開始的強引用計數StrongExtraRefCount0011,轉換成十進制就是3

SIL層驗證

我們查看樣例的SIL層代碼??

swiftc -emit-sil xx.swift | xcrun swift-demangle >> ./xx.sil && vscode xx.sil

SIL官方文檔中關于copy_addr的解釋??

其中的strong_retain對應的就是swift_retain,其內部是一個宏定義,內部是_swift_retain_,其實現是對object的引用計數作+1操作??

//內部是一個宏定義
HeapObject *swift::swift_retain(HeapObject *object) {
  CALL_IMPL(swift_retain, (object));
}
??
//本質調用的就是 _swift_retain_
static HeapObject *_swift_retain_(HeapObject *object) {
  SWIFT_RT_TRACK_INVOCATION(object, swift_retain);
  if (isValidPointerForNativeRetain(object))
    object->refCounts.increment(1);
  return object;
}
??
void increment(uint32_t inc = 1) {
    auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
    
    // constant propagation will remove this in swift_retain, it should only
    // be present in swift_retain_n
    if (inc != 1 && oldbits.isImmortal(true)) {
      return;
    }
    //64位bits
    RefCountBits newbits;
    do {
      newbits = oldbits;
      bool fast = newbits.incrementStrongExtraRefCount(inc);
      if (SWIFT_UNLIKELY(!fast)) {
        if (oldbits.isImmortal(false))
          return;
        return incrementSlow(oldbits, inc);
      }
    } while (!refCounts.compare_exchange_weak(oldbits, newbits,
                                              std::memory_order_relaxed));
  }

接著搜索incrementStrongExtraRefCount,定義如下??

LLVM_NODISCARD LLVM_ATTRIBUTE_ALWAYS_INLINE
bool incrementStrongExtraRefCount(uint32_t inc) {
// This deliberately overflows into the UseSlowRC field.
// 對inc做強制類型轉換為 BitsType
// 其中 BitsType(inc) << Offsets::StrongExtraRefCountShift 等價于 1<<33位,16進制為 0x200000000
//這里的 bits += 0x200000000,將對應的33-63轉換為10進制,為
bits += BitsType(inc) << Offsets::StrongExtraRefCountShift;
return (SignedBitsType(bits) >= 0);
}

所以,以t的refCounts為例(其中62-33位是strongCount,每次增加強引用計數增加都是在33-62位上增加的,固定的增量為1左移33位,即0x200000000

為何t的引用計數是0x0000000600000003
  1. 當代碼運行到var t = LGTeacher()時,t的refCounts是 0x0000000200000003
  2. var t1 = t時,refCounts是 0x0000000400000003 = 0x0000000200000003 + 0x200000000
  3. var t2 = t時,refCounts是0x0000000600000003 = 0x0000000400000003 + 0x200000000
Swift與OC初始化時的引用計數

我們注意到,var t = LGTeacher()此時已經有了引用計數,所以??

  • OC中創建實例對象時為0
  • Swift中創建實例對象時默認為1
CFGetRetainCOunt

可以通過CFGetRetainCOunt獲取引用計數,應用到上面的例子,運行查看??

如果把上述代碼放入方法中運行,則??

t的引用計數會再次增加。

1.2 弱引用

接下來,我們來看看弱引用,還是先看下面示例??

class LGTeacher {
    var age: Int = 18
    var name: String = "Luoji"
    var stu: LGStudent?
}

class LGStudent {
    var age = 20
    var teacher: LGTeacher?
}

func test(){
    var t = LGTeacher()
    weak var t1 = t
    
    print("end")
}

test()

運行??

t的引用計數是0xc0000000200abbca,why?接下來我們來看看原因??

弱引用聲明的變量是一個可選值,因為在程序運行過程中是允許將當前變量設置為nil

首先在t1處打上斷點,查看匯編

我們鎖定到swift_weakInit,接著在源碼中搜索swift_weakInit??

WeakReference *swift::swift_weakInit(WeakReference *ref, HeapObject *value) {
  ref->nativeInit(value);
  return ref;
}

接著看看nativeInit

  void nativeInit(HeapObject *object) {
    auto side = object ? object->refCounts.formWeakReference() : nullptr;
    nativeValue.store(WeakReferenceBits(side), std::memory_order_relaxed);
  }

接著看formWeakReference

// SideTableRefCountBits specialization intentionally does not exist.
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
  auto side = allocateSideTable(true);
  if (side)
    return side->incrementWeak();
  else
    return nullptr;
}

看到這里,就比較明朗了,系統會創建一個sideTable,創建成功的話,side->incrementWeak()增加弱引用計數,失敗則return nullptr。看來重點就是這個allocateSideTable了??

通過上圖的底層流程分析,我們可以get到關鍵的2點??

  1. 通過HeapObjectSideTableEntry初始化散列表??
class HeapObjectSideTableEntry {
  // FIXME: does object need to be atomic?
  std::atomic<HeapObject*> object;
  SideTableRefCounts refCounts;
...
}

上述源碼中可知

弱引用對象對應的引用計數refCountsSideTableRefCounts類型
強引用對象的是InlineRefCounts類型

接下來我們看看SideTableRefCounts??

typedef RefCounts<SideTableRefCountBits> SideTableRefCounts;

繼續搜索SideTableRefCountBits??

里面包含了成員uint32_t weakBits;,即一個32位域的信息。

  1. 通過InlineRefCountBits初始化散列表的數據??
  LLVM_ATTRIBUTE_ALWAYS_INLINE
  RefCountBitsT(HeapObjectSideTableEntry* side)
    : bits((reinterpret_cast<BitsType>(side) >> Offsets::SideTableUnusedLowBits)
           | (BitsType(1) << Offsets::UseSlowRCShift)
           | (BitsType(1) << Offsets::SideTableMarkShift))
  {
    assert(refcountIsInline);
  }

這里繼承的bits構造方法,而bits定義??

BitsType bits;

typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::Type
    BitsType;

和強引用一樣,來到了RefCountBitsInt,這個之前分析過,就是uint64_t類型,存的是64位域信息

綜合1 和 2兩點的論述可得出:

  • 64位 用于記錄 原有引用計數
  • 32位 用于記錄 弱引用計數
為何t的引用計數是0xc0000000200abbca

上述分析中我們知道,在InlineRefCountBits初始化散列表的數據時,執行了(reinterpret_cast<BitsType>(side) >> Offsets::SideTableUnusedLowBits這句代碼,而

static const size_t SideTableUnusedLowBits = 3;

對side右移了3位,所以此時,將0xc0000000200abbca左移3位是0x10055DE50,就是散列表的地址。再x/8g查看??

小結

對于HeapObject來說,其refCounts有兩種計算情況:

  1. 無弱引用:strongCount(強引用計數)+ unownedCount(無主引用計數)
  2. 有弱引用:object + xxx + (strongCount + unownedCount) + weakCount

1.3 循環引用

循環引用也是一個很經典的面試題,按照慣例,我們還是先看看案例??

var age = 10
let clourse = {
    age += 1
}
clourse()
print(age)

<!--打印結果-->
11

從輸出結果中可以看出:閉包內部對變量的修改改變外部原始變量的值,主要原因是閉包會捕獲外部變量,這個與OC中的block一致的

deinit

接著,我們看看deinit的作用

class LGTeacher {
    deinit {
        print("LGTeacher deinit")
    }
}
func test(){
    var t = LGTeacher()
}

test()

<!--打印結果-->
LGTeacher deinit

可見,deinit是在當前實例對象即將被回收時觸發。
接下來,我們把age放到類中,閉包中再去修改時會怎樣??

一樣,沒有問題,如果將閉包那塊代碼放入函數中呢??

func test(){
    var t = LGTeacher()
    let clourse = {
        t.age += 1
    }
    clourse()
}

test()

運行結果發現,閉包對 t 并沒有強引用,直接被釋放了。我們繼續修改??

  1. 在類LGTeacher中添加閉包completionBlock
class LGTeacher {
    var age = 18
    
    var completionBlock: (() ->())?
    
    deinit {
        print("LGTeacher deinit")
    }
}
  1. completionBlock中修改age
func test(){
    var t = CJLTeacher()
    t.completionBlock = {
        t.age += 1
    }
}
test()

運行??

從運行結果發現,t.age還是18,并且沒有執行deinit方法,所以這里存在循環引用

如何解決循環引用

有兩種方式:

  1. weak修飾閉包傳入的參數
func test(){
    var t = LGTeacher()
    t.completionBlock = { [weak t] in
        t?.age += 1
    }
}

因為weak修飾后的變量是optional類型,所以t?.age += 1

  1. unowned修飾閉包參數
func test(){
    var t = LGTeacher()
    t.completionBlock = { [unowned t] in
        t.age += 1
    }
}
捕獲列表

什么是捕獲列表?例如上面的代碼[weak t][unowned t] ,有以下特點:

  • 定義在參數列表之前
  • [變量]寫成用逗號連來的表達式列表,并用方括號括起來
  • 如果使用捕獲列表,那么即使省略參數名稱、參數類型和返回類型,也必須使用in關鍵字

捕獲的值的變化
看以下示例,輸出什么???

func test(){
    var age = 0
    var height = 0.0
    let clourse = {[age] in
        print(age)
        print(height)
    }
    age = 10
    height = 1.85
    clourse()
}

age被捕獲了,即使后面改變了它的值,但是結果還是0,而未被捕獲的height的值卻發生了變化。所以,從這個結果中可知:

對于捕獲列表中的每個常量,閉包會利用周圍范圍內具有相同名稱的常量/變量,來初始化捕獲列表中定義的常量

上述結論大致可以分為以下幾點:

  1. 捕獲列表中的常量是值拷貝,而不是引用拷貝
  2. 捕獲列表中的常量的相當于復制了變量age的值
  3. 捕獲列表中的常量是只讀的,即不可修改

二、Swift中的Runtime場景

Swift是一門靜態語言,本身不具備動態性,不像OC有Runtime運行時的機制(此處指OC提供運行時API供程序員操作)。但由于Swift兼容OC,所以可以轉成OC類和函數,利用OC的運行時機制,來實現動態性

2.1 探索

老規矩,先上示例代碼,

class LGTeacher {
    var age: Int = 18
    func teach(){
        print("teach")
    }
}

let t = LGTeacher()

func test(){
    var methodCount: UInt32 = 0
    let methodList = class_copyMethodList(LGTeacher.self, &methodCount)
    for i in 0..<numericCast(methodCount) {
        if let method = methodList?[i]{
            let methodName = method_getName(method)
            print("=-=-方法名稱:\(methodName)")
        }else{
            print("not found method")
        }
    }
    
    var count: UInt32 = 0
    let proList = class_copyPropertyList(LGTeacher.self, &count)
    for i in 0..<numericCast(count) {
        if let property = proList?[i]{
            let propertyName = String(utf8String: property_getName(property))
            print("=-=-成員屬性名稱:\(propertyName!)")
        }else{
            print("沒有找到你要的屬性")
        }
    }
    print("test run")
}
test()

代碼很檢點,test()方法中通過class_copyMethodListclass_copyPropertyList變量方法名稱和屬性名稱,并打印出來。我們運行來看看??

并沒有打印出來,下面我們來試試修改代碼,讓其能打印出來。

  • 修改1:給方法和屬性添加@objc修飾
class LGTeacher {
    @objc var age: Int = 18
    @objc func teach(){
        print("teach")
    }
}

可以打印。

  • 修改2:類LGTeacher繼承NSObject不用@objc修飾
class LGTeacher: NSObject{
    var age: Int = 18
    func teach(){
        print("teach")
    }
}

只打印了初始化方法,是因為在swift.h文件中暴露出來的只有init方法

注意:如果要讓OC調用,那么必須 繼承NSObject + @objc修飾??

class LGTeacher: NSObject{
    @objc var age: Int = 18
    @objc func teach(){
        print("teach")
    }
}
  • 修改3:去掉@objc修飾,改成dynamic修飾
class LGTeacher: NSObject{
    dynamic var age: Int = 18
    dynamic func teach(){
        print("teach")
    }
}

和第2種情況一樣。

*修改4:同時用@objc 和 dynamic修飾方法

class LGTeacher: NSObject{
    dynamic var age: Int = 18
    @objc dynamic func teach(){
        print("teach")
    }
}

可以輸出方法名稱。

小結
  1. 對于純Swift類來說,沒有動態特性dynamic(因為Swift是靜態語言),方法和屬性不加任何修飾符的情況下,不具備runtime特性,此時的方法調度,依舊是函數表調度,即·V_Table調度
  2. 純swift類的方法和屬性添加@objc修飾的情況下,可通過runtime API獲取到,但是在OC中無法調度的,原因是swift.h文件中沒有該Swift類的聲明。
  3. 對于繼承NSObject類來說,如果想要動態的獲取當前屬性+方法,必須在其聲明前添加 @objc關鍵字,如果想要使用方法交換,還必須在屬性+方法前添加dynamic關鍵字,否則當前屬性+方法只是暴露給OC使用,而不具備任何動態特性。

2.2 元類型、AnyClass、Self

元類型

主要是AnyAnyObject這兩個關鍵字。

  • AnyObject :可代表類的Instance實例類的類型類遵守的協議,但struct?不行
class LGPerson {
   var age = 18
}

// 1. 類實例
var p1: AnyObject =  LGPerson()

// 2. 類的類型
var p2: AnyObject = LGPerson.self

// 3. 類遵守的協議 (繼承AnyObjec)
protocol JSONMap: AnyObject { }

// 4. struct不是AnyObject類型
// struct報錯: [Non-class type 'HTJSON' cannot conform to class protocol 'JSONMap']
 struct HTJSON: JSONMap { }

// 5. 基礎類型強轉為Class,就屬于AnyObject
var age: AnyObject = 10 as NSNumber  // Int不屬于AnyObject,強轉NSNumber就屬于AnyObject
  • Any:Any比AnyObject代表的范圍更廣,不僅支持類實例對象類類型類協議,還支持struct函數以及Optioanl可選類型
// 1. 類實例
var p1: Any =  LGPerson()

// 2. 類的類型
var p2: Any = LGPerson.self

// 3. 類遵守的協議 (繼承AnyObjec)
protocol JSONMap: Any { }

// 4. struct
struct LGJSON: JSONMap { }

// 5. 函數
func test() {}

// 6. struct對象
let s = LGJSON()

// 7. 可選類型
let option: LGPerson? = nil

// Any類型的數組
var array: [Any] = [1,                // Int
                   "2",              // String
                   LGPerson.self,    // class類型
                   p1,               // 類的實例對象
                   JSONMap.self,     // 協議本身
                   LGJSON.self,      // struct類型
                   s,                // struct實例對象
                   option,           // 可選值
                   test()            // 函數
                   ]
print(array)

通過上述[Any]數組,我們可以看到Any可指代范圍是有多廣。
option是可選類型,所以會有警告,可以通過option as Any消除該警告。

AnyClass

AnyClass僅代表類的類型

// 1. 類實例
var p1: AnyObject =  LGPerson()

// 2. 類的類型
var p2: AnyObject = LGPerson.self

// 3. 類遵守的協議 (繼承AnyObjec)
protocol JSONMap: AnyObject { }

class LGTest: JSONMap { }
var p3: JSONMap = LGTest()

// Any類型的數組
var array: [AnyObject] = [ LGPerson.self,    // class類型
                           p1,               // 類的實例對象
                           p3                // 遵守AnyObject協議的類對象也符合(類對象本身符合)
                         ]

即使array是接受AnyObject所有對象,但實際只存儲了類的類型

Self

Self有關的關鍵字有T.selfT.Type。在講這兩個之前,我們先來看看type(of:)這個方法的作用。

  • type(of:)
    用于獲取一個值的動態類型
var age = 10

// 編譯器任務value接收Any類型
func test(_ value: Any) {
   
   // type(of:)可獲取真實類型
   print(type(of: value))    // 打印Int
   
}

test(age)

編譯期時,value的類型是Any類型??

運行期時,type(of:)獲取的是真實類型??

  • type(of:)三種特殊的應用場景
    1. 繼承場景:type(of:)是讀取真實調用的對象
class LGPerson { }

class LGStudent: LGPerson { }

func test(_ value: LGPerson) {
   print(type(of: value)) 
}

var person = LGPerson()
var student = LGStudent()

test(person)
test(student)
  1. 遵循協議的場景:type(of:)也是讀取真實調用的對象
protocol TestProtocol { }

class LGPerson: TestProtocol { }

func test(_ value: TestProtocol) {
   print(type(of: value))
}

var p = LGPerson()
var p1: TestProtocol = LGPerson()

test(p)
test(p1)

注意:p1是LGPerson類型,并不是TestProtocol協議類型。

  1. 使用泛型T時的場景:type(of:)讀取的就是T類型
protocol TestProtocol { }

class LGPerson: TestProtocol { }

func test<T>(_ value: T) {
   print(type(of: value))
}

var p = LGPerson()
var p1: TestProtocol = LGPerson()

test(p)
test(p1) 

這種情況下,p1是TestProtocol協議類型。

如果想讓p1取到的是LGPerson類型,需要改動代碼??

弄清楚了type(of:)的作用后,我們再回過頭看T.selfT.Type

T.self

如果T實例對象,就返回實例本身。如果T,就返回metadata(首地址:類的類型)。示例代碼??

class LGPerson {
    var age = 18
}

struct LGTest {
    var name = "test"
}

// 1. class實例對象,返回對象本身
var p =  LGPerson().self
print(type(of: p))

// 2. class類型, 返回class類型
var pClass = LGPerson.self
print(type(of: pClass))

// 3. struct實例對象,返回對象本身
var t = LGTest().self
print(type(of: t))

// 4. struct類型,返回struct類型
var tStruct = LGTest.self
print(type(of: tStruct))
T.Type

T.Type就是一種類型,T.self是T.Type類型。(使用type(of:)讀取)。上述例子中的??

總結

本篇文章主要講了2大知識點:內存管理Runtime相關,內存管理中主要分析了,在底層源碼中,強引用對象和弱引用對象的引用計數的計算方式的區別,通過示例證明了引用計數位域的存儲。接著講到了Runtime的場景,OC與Swift混編時會用到,最后講述了下元類型的幾種特殊的應用場景。

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

推薦閱讀更多精彩內容