iOS面試題 - 內(nèi)存管理

1唱捣、什么是ARC?

  • 為什么要使用內(nèi)存管理

嚴(yán)格的內(nèi)存管理翰舌,能夠是我們的應(yīng)用程在性能上有很大的提高束莫。如果忽略內(nèi)存管理,可能導(dǎo)致應(yīng)用占用內(nèi)存過高巷懈,導(dǎo)致程序崩潰

  • 引用計(jì)數(shù)工作原理

對(duì)象創(chuàng)建出來(lái)時(shí)该抒,其保留計(jì)數(shù)至少為1。若想令其繼續(xù)存活顶燕,則調(diào)用retain方法凑保,這時(shí)對(duì)象引用計(jì)數(shù)+1。要是不再使用此對(duì)象涌攻,不想令其繼續(xù)存活欧引,那就調(diào)用releaseautorelease方法使對(duì)象引用計(jì)數(shù)減1。最終當(dāng)保留計(jì)數(shù)歸零時(shí)恳谎,對(duì)象就回收了(deallocated)

NSObject協(xié)議聲明了下面三個(gè)方法用于操作計(jì)數(shù)器芝此,以遞增或遞減其值:

  • retain遞增保留計(jì)數(shù)。
  • release遞減保留計(jì)數(shù)因痛。
  • autorelease自動(dòng)釋放(即一個(gè)Runloop結(jié)束時(shí))婚苹,再遞減保留計(jì)數(shù)。

注意:查看保留計(jì)數(shù)的方法叫做retainCount鸵膏,此方法不太有用膊升。

對(duì)象操作與Objective-C方法的對(duì)應(yīng)

對(duì)象操作 Objective-C方法
生成并持有對(duì)象 alloc/new/copy/mutableCopy等方法
持有對(duì)象 retain方法
釋放對(duì)象 release方法
廢棄對(duì)象 dealloc 方法
  • MRC(手動(dòng)引用計(jì)數(shù))

MRC(手動(dòng)引用計(jì)數(shù))顧名思義就是自己管理對(duì)象的引用計(jì)數(shù),經(jīng)歷過MRC時(shí)代谭企,肯定知道需要使用手動(dòng)的通過retain使對(duì)象引用計(jì)數(shù)加1廓译,使對(duì)象不被釋放,當(dāng)不需要使用對(duì)象的時(shí)候使用release使對(duì)象的引用計(jì)數(shù)減1债查,當(dāng)對(duì)象的引用計(jì)數(shù)為0使责循,對(duì)象被釋放。所以代碼中大量的retainrelease操作

  • ARC(自動(dòng)引用計(jì)數(shù))

ARC(Automatic Reference Couting)即自動(dòng)引用計(jì)數(shù)攀操,它是OC的內(nèi)存管理機(jī)制,簡(jiǎn)單來(lái)說(shuō)秸抚,就是代碼中自動(dòng)加入retain/release速和,原先需要手動(dòng)添加用來(lái)處理內(nèi)存管理的引用計(jì)數(shù)的代碼可以自動(dòng)由編譯器完成了。

ARC的使用是為了解決對(duì)象retain/release匹配問題剥汤,以前因手動(dòng)管理而造成內(nèi)存泄露或者重復(fù)釋放的問題將不復(fù)存在颠放。ARC 管理對(duì)象生命期的辦法基本上就是:在合適的地方插入 “保留” 及 “釋放”操作。

  • 注意

ARC只負(fù)責(zé)管理Objective-C對(duì)象的內(nèi)存吭敢。尤其要注意: CoreFoundation 對(duì)象不歸 ARC管理碰凶,開發(fā)者必須適時(shí)調(diào)用 CFRetain/CFRelease。

2、什么情況下會(huì)出現(xiàn)循環(huán)引用欲低?

循環(huán)引用是指兩個(gè)或兩個(gè)以上的對(duì)象相互強(qiáng)引用辕宏,導(dǎo)致所有對(duì)象無(wú)法被釋放的現(xiàn)象。這是內(nèi)存泄露的一種情況砾莱∪鹂穑看個(gè)例子:

@interface Father: NSObject
@property (nonatomic, strong) Son *son;
@end

@interface Son: NSObject
@property (nonatomic, strong) Father *father;
@end

可以看到上述代碼有兩個(gè)類分別是FatherSonFather對(duì)Son進(jìn)行了強(qiáng)引用腊瑟,Son又對(duì)Father進(jìn)行了強(qiáng)引用聚假,因而造成了循環(huán)引用。

解決辦法是將Father中的Son對(duì)象屬性由Strong改為weak闰非,或者將Son中的father屬性改為weak膘格,一方為弱引用即可。

常用的循環(huán)引用情況:

  • 代理的循環(huán)引用
  • 定時(shí)器的循環(huán)引用
  • block的循環(huán)引用
  • OC對(duì)象內(nèi)存處理

3财松、說(shuō)明并比較關(guān)鍵詞:Strong瘪贱,weakassigncopy

OC中游岳,基本數(shù)據(jù)類型的默認(rèn)關(guān)鍵字是atomic政敢、readwrite、 assgin胚迫;普通屬性的默認(rèn)關(guān)鍵詞是atomic喷户、readwrite、strong

strong:表示指向并擁有該對(duì)象访锻。其修飾的對(duì)象引用計(jì)數(shù)會(huì)增加1.該對(duì)象只要引用計(jì)數(shù)不為0褪尝,就不會(huì)被銷毀。當(dāng)然期犬,強(qiáng)行將其設(shè)置為nil河哑,也可以銷毀它

weak:表示指向但不擁有該對(duì)象。其修飾的對(duì)象引用計(jì)數(shù)不會(huì)增加龟虎。無(wú)須手動(dòng)設(shè)置璃谨,該對(duì)象會(huì)自行在內(nèi)存中銷毀。weak在對(duì)象釋放后會(huì)置為nil

assgin:主要用于修飾基本數(shù)據(jù)類型鲤妥,如NSIntegerCGFloat佳吞,這些數(shù)值主要存在于棧中

copycopystrong類似。不同之處是棉安,strong的復(fù)制是多個(gè)指針指向同一個(gè)地址底扳,而copy的復(fù)制是每次會(huì)在內(nèi)存中復(fù)制一份對(duì)象,指針指向不同的地址贡耽。copy一般用在修飾有對(duì)應(yīng)可變類型的不可變對(duì)象上衷模,如:NSString鹊汛、NSArray、NSDictionary

4阱冶、說(shuō)明并比較關(guān)鍵詞:atomicnonatomic

atomic:屬性的默認(rèn)行為刁憋。修飾的對(duì)象會(huì)保證settergetter的完整性,任何線程訪問它都可以得到一個(gè)完整的初始化后的對(duì)象熙揍。因?yàn)橐WC操作完成职祷,所以速度比較慢。atomicnonatomic安全届囚,但是也不是絕對(duì)的安全有梆。

例如:當(dāng)多個(gè)線程同時(shí)調(diào)用setget時(shí),就會(huì)導(dǎo)致獲得的對(duì)象值不一樣意系。好比如三個(gè)線程A 泥耀、B 、C蛔添,如果線程A調(diào)了getter痰催,與此同時(shí)線程B線程C都調(diào)了setter迎瞧。那最后線程A 執(zhí)行getter獲得到的值有3種可能:可能是在線程B夸溶、線程C執(zhí)行setter之前的值,也可能是線程B執(zhí)行setter之后的值凶硅,也可能是線程C執(zhí)行setter 的值缝裁。最終這個(gè)屬性的值,是線程B線程C執(zhí)行setter之后的值

要想線程決定的安全足绅,需要使用鎖捷绑,比如:@synchronized、NSLock氢妈、pthread

nonatomic:修飾的對(duì)象不保證settergetter的完整性粹污,所以當(dāng)多個(gè)線程訪問它時(shí),它可能會(huì)返回未初始化的對(duì)象首量。正因?yàn)槿绱耍?code>nonatomic比atomic的速度快壮吩,但是線程也是不安全的。

但是實(shí)際的開發(fā)過程中加缘,經(jīng)常使用nonatomic

atomic and nonatomic

5粥航、說(shuō)明并比較關(guān)鍵詞:__weak__block

  • __weak
__weak specifies a reference that does not keep the referenced object alive. 
A weak reference is set to nil when there are no strong references to the object.

__weakweak基本相同,只能在ARC模式下使用生百,修飾對(duì)象可以避免循環(huán)引用,不會(huì)增加引用柄延。__weak只能修飾對(duì)象(比如NSString等)蚀浆,不能修飾基本數(shù)據(jù)類型(比如int等)

避免循環(huán)引用情況

__weak __typeof(self) weakSelf = self; 
self.testBlock = ^{
    [weakSelf doSomeThing];
});

弱引用不會(huì)影響對(duì)象的釋放缀程,但是當(dāng)對(duì)象被釋放時(shí),所有指向它的弱引用都會(huì)自定被置為nil市俊,這樣可以防止野指針杨凑。

  • __block
A powerful feature of blocks is that they can modify variables in the same lexical scope. 
You signal that a block can modify a variable using the __block storage type modifier. 

At function level are __block variables. These are mutable within the block (and the enclosing scope) 
and are preserved if any referencing block is copied to the heap.

__block不管是ARC還是MRC模式下都可以使用,可以修飾對(duì)象摆昧,還可以修飾基本數(shù)據(jù)類型撩满。

  • MRC下使用__block是可以避免循環(huán)引用的;
  • ARC下使用__block typeof(self)weakSelf = self;因?yàn)?code>block是用過添加引用來(lái)訪問實(shí)例變量的绅你,所以self會(huì)被retain一次伺帘,block也是一個(gè)強(qiáng)引用,會(huì)引起循環(huán)引用忌锯,所以使用__weak

使用__block修飾變量使之在block內(nèi)可被修改

在一個(gè)block里頭如果使用了在block之外的變量伪嫁,會(huì)將這份變量先復(fù)制一份再使用,也就是說(shuō)對(duì)于當(dāng)前的block來(lái)說(shuō)偶垮,所有的外部的變數(shù)都是只可讀的张咳,不可改的。

如果我們想讓某個(gè)block可以修改某個(gè)外部的變量似舵,則需要使用__block修飾變量

__block int i = 1;
void (^block)(void) = ^{
    i = i + 1;
};

從另一個(gè)角度說(shuō)脚猾,使用了__block關(guān)鍵字的變量會(huì)將變量從棧上復(fù)制到堆上。棧上那個(gè)變量會(huì)指向復(fù)制到堆上的變量砚哗。

__weak和__block對(duì)比

  • __block不管是ARC還是MRC模式下都可以使用龙助,可以修飾對(duì)象,還可以修飾基本數(shù)據(jù)類型频祝。
  • __weak只能在ARC模式下使用泌参,也只能修飾對(duì)象(NSString),不能修飾基本數(shù)據(jù)類型(int)常空。
  • __block對(duì)象可以在block中被重新賦值沽一,__weak不可以。
  • __block對(duì)象在ARC下可能會(huì)導(dǎo)致循環(huán)引用漓糙,非ARC下會(huì)避免循環(huán)引用铣缠,__weak只在ARC下使用,可以避免循環(huán)引用昆禽。

__weak and a __block

6蝗蛙、內(nèi)存管理語(yǔ)法

請(qǐng)問下面的代碼打印結(jié)果是什么?

NSString *firstString = @"hello";
NSString *secondString = @"hello";

if (firstString == secondString) {
    NSLog(@"Equal");
} else {
    NSLog(@"Not equal");
}

運(yùn)行輸出:Equal

  • 1醉鳖、==這個(gè)符號(hào)判斷的不是這兩個(gè)值是否相等捡硅,而是這兩個(gè)指針是否指向同一個(gè)對(duì)象。如果要判斷兩個(gè)NSString的值是否相等盗棵,那么應(yīng)該用isEqualToString這個(gè)方法

  • 2壮韭、上面代碼中北发,兩個(gè)指針指向不同的對(duì)象,盡管它們的值是相等喷屋。但是iOS的編譯器優(yōu)化了內(nèi)存分配琳拨,當(dāng)兩個(gè)指針指向兩個(gè)值一樣的NSString時(shí),兩者指向同一個(gè)內(nèi)存地址屯曹,所以會(huì)指向判斷為真狱庇,打印Equal

7、@property的本質(zhì)是什么恶耽?ivar密任、getter、setter 是如何生成并添加到這個(gè)類中的驳棱?

  • @property 的本質(zhì)

@property = ivar + getter + setter;

@property其實(shí)就是在編譯階段由編譯器自動(dòng)幫我們生成ivar成員變量批什,getter方法,setter方法

“屬性” (property)有兩大概念:ivar(實(shí)例變量)社搅、存取方法(access method = getter + setter)驻债。

“屬性” (property)作為Objective-C的一項(xiàng)特性,主要的作用就在于封裝對(duì)象中的數(shù)據(jù)形葬。Objective-C對(duì)象通常會(huì)把其所需要的數(shù)據(jù)保存為各種實(shí)例變量合呐。實(shí)例變量一般通過“存取方法”(access method)來(lái)訪問。其中笙以,“獲取方法” (getter)用于讀取變量值淌实,而“設(shè)置方法” (setter)用于寫入變量值。

這個(gè)概念已經(jīng)定型猖腕,并且經(jīng)由“屬性”這一特性而成為Objective-C 2.0的一部分拆祈。 而在正規(guī)的Objective-C編碼風(fēng)格中,存取方法有著嚴(yán)格的命名規(guī)范倘感。 正因?yàn)橛辛诉@種嚴(yán)格的命名規(guī)范放坏,所以Objective-C這門語(yǔ)言才能根據(jù)名稱自動(dòng)創(chuàng)建出存取方法。

其實(shí)也可以把屬性當(dāng)做一種關(guān)鍵字老玛,其表示:編譯器會(huì)自動(dòng)寫出一套存取方法淤年,用以訪問給定類型中具有給定名稱的變量。 所以你也可以這么說(shuō):

@property = getter + setter;

例如下面這個(gè)類:

@interface Person : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end

上述代碼寫出來(lái)的類與下面這種寫法等效:

@interface Person : NSObject
- (NSString *)firstName;
- (void)setFirstName:(NSString *)firstName;
- (NSString *)lastName;
- (void)setLastName:(NSString *)lastName;
@end

propertyruntime中是objc_property_t定義如下:

typedef struct objc_property *objc_property_t;

objc_property是一個(gè)結(jié)構(gòu)體蜡豹,包括nameattributes麸粮,定義如下:

struct property_t {
    const char *name;
    const char *attributes;
};

attributes本質(zhì)是objc_property_attribute_t,定義了property的一些屬性镜廉,定義如下:

/// Defines a property attribute
typedef struct {
    const char *name;           /**< The name of the attribute */
    const char *value;          /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;

attributes的具體內(nèi)容是什么呢弄诲?其實(shí),包括:類型娇唯,原子性齐遵,內(nèi)存語(yǔ)義和對(duì)應(yīng)的實(shí)例變量凤巨。

例如:我們定義一個(gè)string的屬性@property (nonatomic, copy) NSString *string;,通過 property_getAttributes(property)獲取到attributes并打印出來(lái)之后的結(jié)果為T@"NSString",C,N,V_string

其中T就代表類型洛搀,可參閱Type EncodingsC就代表Copy佑淀,N代表nonatomic留美,V就代表對(duì)于的實(shí)例變量。

  • ivar伸刃、getter谎砾、setter的生成

完成屬性定義后,編譯器會(huì)自動(dòng)編寫訪問這些屬性所需的方法捧颅,此過程叫做自動(dòng)合成(autosynthesis)景图。需要強(qiáng)調(diào)的是,這個(gè)過程由編譯器在編譯期執(zhí)行碉哑,所以編輯器里看不到這些合成方法的源代碼挚币。除了生成方法代碼getter、setter之外扣典,編譯器還要自動(dòng)向類中添加適當(dāng)類型的實(shí)例變量妆毕,并且在屬性名前面加下劃線,以此作為實(shí)例變量的名字

在前例中贮尖,會(huì)生成兩個(gè)實(shí)例變量笛粘,其名稱分別為 _firstName_lastName。也可以在類的實(shí)現(xiàn)代碼里通過 @synthesize 語(yǔ)法來(lái)指定實(shí)例變量的名字.

@implementation Person
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end

反編譯相關(guān)的代碼,大致生成了五個(gè)東西

  • OBJC_IVAR_$類名$屬性名稱 :該屬性的“偏移量” (offset)湿硝,這個(gè)偏移量是“硬編碼” (hardcode)薪前,表示該變量距離存放對(duì)象的內(nèi)存區(qū)域的起始地址有多遠(yuǎn)。
  • settergetter方法對(duì)應(yīng)的實(shí)現(xiàn)函數(shù)
  • ivar_list :成員變量列表
  • method_list :方法列表
  • prop_list :屬性列表

也就是說(shuō)我們每次在增加一個(gè)屬性,系統(tǒng)都會(huì)在 ivar_list中添加一個(gè)成員變量的描述,在 method_list中增加 settergetter方法的描述,在屬性列表中增加一個(gè)屬性的描述,然后計(jì)算該屬性在對(duì)象中的偏移量,然后給出 settergetter方法對(duì)應(yīng)的實(shí)現(xiàn),在setter方法中從偏移量的位置開始賦值,在 getter方法中從偏移量開始取值,為了能夠讀取正確字節(jié)數(shù),系統(tǒng)對(duì)象偏移量的指針類型進(jìn)行了類型強(qiáng)轉(zhuǎn).

8关斜、屬性的關(guān)鍵字有哪些示括?

  • 默認(rèn)使用關(guān)鍵字

對(duì)應(yīng)基本數(shù)據(jù)類型默認(rèn)關(guān)鍵字是:atomic、readwrite蚤吹、assign
對(duì)于普通的Objective-C對(duì)象默認(rèn)關(guān)鍵字是:atomic例诀、readwrite、strong

  • 讀寫權(quán)限

readonly:屬性只可讀裁着,表示僅擁有“獲取方法”繁涂,利用此屬性可以保證對(duì)外只讀,但是在分類中二驰,我們可以將其重新定義為讀寫屬性

readwrite:屬性可讀可寫扔罪,擁有“獲取方法”與“設(shè)置方法”,若該屬性由@synthesize實(shí)現(xiàn)桶雀,則編譯器會(huì)自動(dòng)生成這兩個(gè)方法

  • 內(nèi)存管理語(yǔ)義

strong:表示指向并擁有該對(duì)象矿酵,定義了一種其“擁有關(guān)系”唬复,當(dāng)為這種屬性設(shè)置新值時(shí),設(shè)置方法會(huì)先保留新值全肮,并釋放舊值敞咧,然后再將新值設(shè)置上去。修飾的對(duì)象引用計(jì)數(shù)會(huì)增加1辜腺。該對(duì)象只要引用計(jì)數(shù)不為0則不會(huì)被銷毀休建。當(dāng)然強(qiáng)行將其設(shè)為nil可以銷毀它。

copycopy所表達(dá)的屬性關(guān)系與strong類似评疗,然而設(shè)置方法并不保留新值测砂,而是將其拷貝(copy)。當(dāng)屬性類型為NSString *時(shí)百匆,經(jīng)常使用copy來(lái)保護(hù)其封裝性砌些,因?yàn)閭鬟f給設(shè)置方法的新值可能指向一個(gè)NSMutableString類的實(shí)例。該類是NSString的子類加匈,表示一種可以被修改 其值的字符串存璃,此時(shí)若是不拷貝字符串,那么設(shè)置完屬性之后矩动,字符串的值可能會(huì)在對(duì)象不知情況下遭若更改有巧。所以,這時(shí)候就需要拷貝一份不可變的字符串悲没。

weak:表示指向但不擁有該對(duì)象篮迎,定義了一種“非擁有關(guān)系”。為這種屬性設(shè)置新值時(shí)示姿,設(shè)置方法既不保留新值甜橱,也不釋放舊值。此特性跟assign是一樣的栈戳,然而在屬性所指的對(duì)象被摧毀的時(shí)候岂傲,屬性值會(huì)清空。其修飾的對(duì)象引用計(jì)數(shù)不會(huì)增加子檀。無(wú)需手動(dòng)設(shè)置镊掖,該對(duì)象會(huì)自行在內(nèi)存中銷毀。

assign:主要用于修飾基本數(shù)據(jù)類型褂痰,也可以說(shuō)“純量類型”亩进,如NSInteger和CGFloat,這些數(shù)值主要存在于棧上

  • 原子性

atomicatomic修飾的對(duì)象會(huì)保證settergetter的完整性缩歪,任何線程對(duì)其訪問都可以得到一個(gè)完整的初始化后的對(duì)象归薛。因?yàn)橐WC操作完成,所以速度慢。它比nonatomic安全主籍,但也并不是絕對(duì)的線程安全习贫,例如多個(gè)線程同時(shí)調(diào)用set和get就會(huì)導(dǎo)致獲得的對(duì)象值不一樣。絕對(duì)的線程安全就要用關(guān)鍵詞synchronized千元。

nonatomicnonatomic修飾的對(duì)象不保證setter和getter的完整性苫昌,所以多個(gè)線程對(duì)它進(jìn)行訪問箍鼓,它可能會(huì)返回未初始化的對(duì)象器钟。正因?yàn)槿绱伺⒎?code>atomic快忌怎,但也是線程不安全的。

在默認(rèn)情況下沉衣,由編譯器所合成的方法會(huì)通過鎖定機(jī)制確保其原子性,如果屬性具備nonatomic特質(zhì),則不使用同步鎖议纯。

兩者之間的區(qū)別在于:

具備atomic特質(zhì)的獲取方法會(huì)通過鎖定機(jī)制來(lái)確保其操作的原子性,這也就是說(shuō)溢谤,如果兩個(gè)線程讀寫同一個(gè)屬性瞻凤,那么不論何時(shí),總能看到有效的屬性值世杀,若是不加鎖的話阀参,那么當(dāng)其中的一個(gè)線程正在改寫某屬性值時(shí),另外一個(gè)線程突然闖入瞻坝,把尚未修改好的屬性值進(jìn)行讀取蛛壳,那么線程可能讀取不到對(duì)應(yīng)的值。

但是注意:做過iOS開發(fā)的應(yīng)該都明白所刀,當(dāng)我們使用屬性的時(shí)候衙荐,都是nonatomic,這樣做是因?yàn)楦〈矗趇OS中使用同步鎖開銷較大忧吟,這樣會(huì)帶來(lái)性能問題,一般情況下并不是要求屬性必須是原子的斩披,因?yàn)檫@樣并不能保證“線程安全”的操作溜族,如果真的想保證線程安全,還需要使用更深層次的鎖才行

  • 方法名---getter=<name> 垦沉、setter=<name>

getter=<name>的樣式:

  @property (nonatomic, getter=isOn) BOOL on;

setter=<name>一般用在特殊的情境下煌抒,比如:

在數(shù)據(jù)反序列化、轉(zhuǎn)模型的過程中乡话,服務(wù)器返回的字段如果以init開頭摧玫,所以你需要定義一個(gè)init開頭的屬性,但默認(rèn)生成的settergetter方法也會(huì)以 init 開頭,而編譯器會(huì)把所有以 init 開頭的方法當(dāng)成初始化方法诬像,而初始化方法只能返回 self 類型屋群,因此編譯器會(huì)報(bào)錯(cuò)。

這時(shí)你就可以使用下面的方式來(lái)避免編譯器報(bào)錯(cuò):

@property(nonatomic, strong, getter=p_initBy, setter=setP_initBy:)NSString *initBy;

另外也可以用關(guān)鍵字進(jìn)行特殊說(shuō)明坏挠,來(lái)避免編譯器報(bào)錯(cuò):

@property(nonatomic, readwrite, copy, null_resettable) NSString *initBy;
- (NSString *)initBy __attribute__((objc_method_family(none)));

9芍躏、怎么用 copy 關(guān)鍵字?

  • 1降狠、 NSString对竣、NSArray、NSDictionary 等等經(jīng)常使用copy關(guān)鍵字榜配,是因?yàn)樗麄冇袑?duì)應(yīng)的可變類型:NSMutableString否纬、NSMutableArray、NSMutableDictionary蛋褥;
  • 2临燃、block 也經(jīng)常使用 copy 關(guān)鍵字,具體原因見官方文檔:Objects Use Properties to Keep Track of Blocks

copy 關(guān)鍵字常見的問題和思考

  • 1烙心、這個(gè)寫法會(huì)出什么問題:@property (copy) NSMutableArray *array;

兩個(gè)問題:

1膜廊、添加,刪除,修改數(shù)組內(nèi)的元素的時(shí)候,程序會(huì)因?yàn)檎也坏綄?duì)應(yīng)的方法而崩潰淫茵。因?yàn)?code>copy就是復(fù)制一個(gè)不可變NSArray的對(duì)象爪瓜;
2、使用了atomic屬性會(huì)嚴(yán)重影響性能 匙瘪;

比如下面的代碼就會(huì)發(fā)生崩潰

// .h文件
// 下面的代碼就會(huì)發(fā)生崩潰
@property (nonatomic, copy) NSMutableArray *mutableArray;

// .m文件
// 下面的代碼就會(huì)發(fā)生崩潰
NSMutableArray *array = [NSMutableArray arrayWithObjects:@1,@2,nil];
self.mutableArray = array;
[self.mutableArray removeObjectAtIndex:0];

接下來(lái)就會(huì)奔潰:

 -[__NSArrayI removeObjectAtIndex:]: unrecognized selector sent to instance 0x7fcd1bc30460
  • 2铆铆、如何讓自己的類使用copy修飾符?

若想令自己所寫的對(duì)象具有拷貝功能丹喻,則需實(shí)現(xiàn) NSCopying 協(xié)議算灸。如果自定義的對(duì)象分為可變版本與不可變版本,那么就要同時(shí)實(shí)現(xiàn) NSCopying 與 NSMutableCopying 協(xié)議驻啤。

具體步驟:

  • 需聲明該類遵從NSCopying協(xié)議
  • 實(shí)現(xiàn)NSCopying協(xié)議菲驴,該協(xié)議只有一個(gè)方法:
- (id)copyWithZone:(NSZone *)zone;

注意:一提到讓自己的類用copy修飾符,我們總是想覆寫copy方法骑冗,其實(shí)真正需要實(shí)現(xiàn)的卻是copyWithZone方法赊瞬。

代碼為例:

// .h文件
// 修改完的代碼

typedef NS_ENUM(NSInteger, CYLSex) {
    CYLSexMan,
    CYLSexWoman
};

@interface CYLUser : NSObject<NSCopying>

@property (nonatomic, readonly, copy) NSString *name;
@property (nonatomic, readonly, assign) NSUInteger age;
@property (nonatomic, readonly, assign) CYLSex sex;

- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;
+ (instancetype)userWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;

@end

然后實(shí)現(xiàn)協(xié)議中規(guī)定的方法:

- (id)copyWithZone:(NSZone *)zone {
    CYLUser *copy = [[[self class] allocWithZone:zone] 
                     initWithName:_name
                                  age:_age
                                  sex:_sex];
    return copy;
}

但在實(shí)際的項(xiàng)目中,不可能這么簡(jiǎn)單贼涩,遇到更復(fù)雜一點(diǎn)巧涧,比如類對(duì)象中的數(shù)據(jù)結(jié)構(gòu)可能并未在初始化方法中設(shè)置好,需要另行設(shè)置遥倦。

  • 3谤绳、用@property聲明的 NSString / NSArray / NSDictionary 經(jīng)常使用 copy 關(guān)鍵字占锯,為什么?如果改用strong關(guān)鍵字缩筛,可能造成什么問題消略?

@property聲明 NSString、NSArray瞎抛、NSDictionary經(jīng)常使用 copy關(guān)鍵字艺演,是因?yàn)樗麄冇袑?duì)應(yīng)的可變類型:NSMutableString、NSMutableArray桐臊、NSMutableDictionary胎撤,他們之間可能進(jìn)行賦值操作(就是把可變的賦值給不可變的),為確保對(duì)象中的字符串值不會(huì)無(wú)意間變動(dòng)断凶,應(yīng)該在設(shè)置新屬性值時(shí)拷貝一份伤提。

因?yàn)楦割愔羔樋梢灾赶蜃宇悓?duì)象,使用 copy 的目的是為了讓本對(duì)象的屬性不受外界影響,使用 copy 無(wú)論給我傳入是一個(gè)可變對(duì)象還是不可對(duì)象,我本身持有的就是一個(gè)不可變的副本。

如果我們使用是strong ,那么這個(gè)屬性就有可能指向一個(gè)可變對(duì)象,如果這個(gè)可變對(duì)象在外部被修改了,那么會(huì)影響該屬性认烁。

總結(jié):使用copy的目的是飘弧,防止把可變類型的對(duì)象賦值給不可變類型的對(duì)象時(shí),可變類型對(duì)象的值發(fā)送變化會(huì)無(wú)意間篡改不可變類型對(duì)象原來(lái)的值砚著。

10、淺拷貝和深拷貝的區(qū)別痴昧?

一般情況下我們說(shuō)的拷貝分為兩種:

  • 淺拷貝:只復(fù)制指向?qū)ο蟮闹羔樆拢粡?fù)制引用對(duì)象本身即指針拷貝
  • 深拷貝:復(fù)制引用對(duì)象本身,內(nèi)存中存在了兩份獨(dú)立對(duì)象本身即對(duì)象拷貝

真正拷貝可以分為三層:

  • 淺拷貝(shallow copy):在淺拷貝操作時(shí)赶撰,對(duì)于被拷貝對(duì)象的每一層都是指針拷貝舌镶。
  • 深拷貝(one-level-deep copy):在深拷貝操作時(shí),對(duì)于被拷貝對(duì)象豪娜,至少有一層是深拷貝
  • 完全拷貝(real-deep copy):在完全拷貝操作時(shí)餐胀,對(duì)于被拷貝對(duì)象的每一層都是對(duì)象拷貝

非集合類對(duì)象的copymutableCopy

[不可變對(duì)象 copy] // 淺拷貝
[不可變對(duì)象 mutableCopy] // 深拷貝
[可變對(duì)象 copy] //深拷貝
[可變對(duì)象 mutableCopy] //深拷貝

集合類對(duì)象的copymutableCopy

[不可變對(duì)象 copy] // 淺拷貝
[不可變對(duì)象 mutableCopy] //單層深拷貝
[可變對(duì)象 copy] //單層深拷貝
[可變對(duì)象 mutableCopy] //單層深拷貝

這里需要注意的是集合對(duì)象的內(nèi)容復(fù)制僅限于對(duì)象本身,對(duì)象元素仍然是指針復(fù)制

iOS 集合的深復(fù)制與淺復(fù)制

11瘤载、蘋果是如何實(shí)現(xiàn)Autorelease Pool的否灾?

Autorelease Pool作用:緩存池,可以避免我們經(jīng)常寫relase的一種方式鸣奔。其實(shí)就是延遲release墨技,將創(chuàng)建的對(duì)象,添加到最近的Autorelease Pool中挎狸,等到Autorelease Pool作用域結(jié)束的時(shí)候扣汪,會(huì)將里面所有的對(duì)象的引用計(jì)數(shù)器-1.

Autorelease Pool 的實(shí)現(xiàn)原理

12、@synthesize和@dynamic

  • @synthesize

@synthesize的語(yǔ)義是如果你沒有手動(dòng)實(shí)現(xiàn)setter方法和getter方法锨匆,那么編譯器會(huì)自動(dòng)為你加上這兩個(gè)方法崭别,這個(gè)過程由編譯器在編譯期間執(zhí)行,所有編輯器里看不到這些“合成方法”。除了生成方法代碼之外茅主,編譯器還要自動(dòng)向類中添加適當(dāng)類型的實(shí)例變量舞痰,并且在屬性名前加下劃線,此次作為實(shí)例變量的名字暗膜。

  • @dynamic

@dynamic告訴編譯器:屬性的settergetter方法由用戶自己實(shí)現(xiàn)匀奏,不自動(dòng)生成(當(dāng)然對(duì)于readonly的屬性只需提供getter即可),也不需要實(shí)例變量学搜,所以即使編譯器沒有發(fā)現(xiàn)存取方法娃善,也不會(huì)報(bào)錯(cuò),它相信這些方法在運(yùn)行期能夠找到瑞佩。

假如一個(gè)屬性被聲明為@dynamic var聚磺,然后你沒有提供@setter方法和@getter方法,編譯的時(shí)候沒問題炬丸,但是當(dāng)程序運(yùn)行到instance.var = someVar瘫寝,由于缺 setter方法會(huì)導(dǎo)致程序崩潰;或者當(dāng)運(yùn)行到someVar = instance.var時(shí)稠炬,由于缺getter方法同樣會(huì)導(dǎo)致崩潰焕阿。編譯時(shí)沒問題,運(yùn)行時(shí)才執(zhí)行相應(yīng)的方法首启,這就是所謂的動(dòng)態(tài)綁定

13暮屡、weak 的內(nèi)部實(shí)現(xiàn)原理,weak 變量在引用計(jì)數(shù)為0時(shí)毅桃,會(huì)被自動(dòng)設(shè)置成 nil褒纲,這個(gè)特性是如何實(shí)現(xiàn)的?

要實(shí)現(xiàn) weak 屬性钥飞,首先要搞清楚 weak 屬性的特點(diǎn):

weak 此特質(zhì)表明該屬性定義了一種“非擁有關(guān)系” (nonowning relationship)莺掠。為這種屬性設(shè)置新值時(shí),設(shè)置方法既不保留新值读宙,也不釋放舊值彻秆。此特質(zhì)同 assign 類似, 然而在屬性所指的對(duì)象遭到摧毀時(shí)结闸,屬性值也會(huì)清空(nil out)掖棉。

那么runtime 如何實(shí)現(xiàn) weak 變量的自動(dòng)置nil

runtime 對(duì)注冊(cè)的類膀估, 會(huì)進(jìn)行布局幔亥,對(duì)于 weak 對(duì)象會(huì)放入一個(gè) hash 表中。 用 weak 指向的對(duì)象內(nèi)存地址作為 key察纯,當(dāng)此對(duì)象的引用計(jì)數(shù)為0的時(shí)候會(huì) dealloc帕棉,假如 weak 指向的對(duì)象內(nèi)存地址是a针肥,那么就會(huì)以a為鍵, 在這個(gè) weak 表中搜索香伴,找到所有以a為鍵的 weak 對(duì)象慰枕,從而設(shè)置為 nil。

Friday QA上即纲,有一期專門介紹 weak的實(shí)現(xiàn)原理具帮。Zeroing Weak References in Objective-C

Friday QA上實(shí)現(xiàn)的核心代碼,如下:

Class subclass = objc_allocateClassPair(class, newNameC, 0);

Method release = class_getInstanceMethod(class, @selector(release));

Method dealloc = class_getInstanceMethod(class, @selector(dealloc));

class_addMethod(subclass, @selector(release), (IMP)CustomSubclassRelease, method_getTypeEncoding(release));

class_addMethod(subclass, @selector(dealloc), (IMP)CustomSubclassDealloc, method_getTypeEncoding(dealloc));

objc_registerClassPair(subclass);

14低斋、weak和assgin?

  • 為什么基本類型和C數(shù)據(jù)類型的修飾用assign蜂厅?

因?yàn)榛緮?shù)據(jù)類型不是對(duì)象,在內(nèi)存中創(chuàng)建和使用后膊畴,在一個(gè)方法體結(jié)束后就被刪除掘猿。基本數(shù)據(jù)類型不需要引用計(jì)數(shù)唇跨,而其他修飾詞需要引用計(jì)數(shù)稠通,所以使用assign

  • 什么情況使用 weak 關(guān)鍵字买猖?

  • ARC中,在有可能出現(xiàn)循環(huán)引用的時(shí)候,往往要通過讓其中一端使用weak來(lái)解決,比如:delegate代理屬性

  • 自身已經(jīng)對(duì)它進(jìn)行一次強(qiáng)引用,沒有必要再?gòu)?qiáng)引用一次,此時(shí)也會(huì)使用 weak,自定義 IBOutlet 控件屬性一般也使用 weak改橘;當(dāng)然,也可以使用strong玉控。在下文也有論述:《IBOutlet連出來(lái)的視圖屬性為什么可以被設(shè)置成weak?》

不同點(diǎn):

weak 此特質(zhì)表明該屬性定義了一種“非擁有關(guān)系” (nonowning relationship)飞主。為這種屬性設(shè)置新值時(shí),設(shè)置方法既不保留新值奸远,也不釋放舊值。此特質(zhì)同assign類似讽挟, 然而在屬性所指的對(duì)象遭到摧毀時(shí)懒叛,屬性值也會(huì)清空(nil out)。 而 assign 的“設(shè)置方法”只會(huì)執(zhí)行針對(duì)“純量類型” (scalar type耽梅,例如 CGFloat 或 NSlnteger 等)的簡(jiǎn)單賦值操作薛窥。

assign 可以用非 OC 對(duì)象,而 weak 必須用于 OC 對(duì)象。那么assign為什么不用來(lái)修飾對(duì)象呢眼姐?因?yàn)閍ssign會(huì)導(dǎo)致下面這個(gè)問題:原因是assign修飾的對(duì)象被釋放后诅迷,指針的地址依然存在,造成野指針众旗,在堆上容易造成崩潰罢杉。而棧上的內(nèi)存系統(tǒng)會(huì)自動(dòng)處理,不會(huì)造成野指針贡歧。

15滩租、Autorelease與AutoreleasePool赋秀?

Autorelease顧名思義就是自動(dòng)釋放,Autorelease機(jī)制是iOS開發(fā)者管理對(duì)象內(nèi)存的好伙伴律想,MRC中猎莲,調(diào)用[obj autorelease]來(lái)延遲內(nèi)存的釋放是一件簡(jiǎn)單自然的事,ARC下技即,我們甚至可以完全不知道Autorelease就能管理好內(nèi)存著洼。

  • AutoreleasePool

自動(dòng)釋放池是用來(lái)存儲(chǔ)多個(gè)對(duì)象類型的指針變量。當(dāng)你確定要將對(duì)象放入到池中的時(shí)候而叼,只需要調(diào)用對(duì)象的autorelease對(duì)象方法就可以把對(duì)象放入到自動(dòng)釋放池中身笤。被存入到自動(dòng)釋放池內(nèi)的對(duì)象,當(dāng)自動(dòng)釋放池被銷毀時(shí)澈歉,會(huì)對(duì)池內(nèi)的對(duì)象全部做一次release操作

  • AutoreleasePool什么時(shí)候創(chuàng)建?

1展鸡、程序剛啟動(dòng)的時(shí)候,也會(huì)創(chuàng)建一個(gè)自動(dòng)釋放池
2埃难、產(chǎn)生事件以后莹弊,運(yùn)行循環(huán)開始處理事件,就會(huì)創(chuàng)建自動(dòng)釋放池

  • AutoreleasePool什么時(shí)候銷毀?

1涡尘、程序運(yùn)行結(jié)束之前銷毀
2忍弛、事件處理結(jié)束以后,會(huì)銷毀自動(dòng)釋放池
3考抄、還有在池子滿的時(shí)候细疚,也會(huì)銷毀

  • autorelease的對(duì)象是在什么時(shí)候被release的?

autorelease實(shí)際上只是把對(duì)release的調(diào)用延遲了川梅,對(duì)于每一個(gè)autorelease疯兼,系統(tǒng)只是把該對(duì)象放入了當(dāng)前的Autoreleasepool中,當(dāng)該Autoreleasepool被釋放時(shí)贫途,該Autoreleasepool中的所有對(duì)象會(huì)被調(diào)用release吧彪。對(duì)于每一個(gè)Runloop,系統(tǒng)會(huì)隱式創(chuàng)建一個(gè)Autoreleasepool丢早,這樣所有的Autoreleasepool會(huì)構(gòu)成一個(gè)象CallStack一樣的一個(gè)棧式結(jié)構(gòu)姨裸,在每一個(gè) Runloop結(jié)束時(shí),當(dāng)前棧頂?shù)?code>Autoreleasepool會(huì)被銷毀怨酝,這樣這個(gè)Autoreleasepool里的每個(gè)對(duì)象都會(huì)被release傀缩。

4010043-22266cb78facba1c.png

Runloop簡(jiǎn)單來(lái)說(shuō)就是一個(gè)事件循環(huán),有事情的時(shí)候啟動(dòng)农猬,沒事情的時(shí)候休閑赡艰。對(duì)于Runloop不了解,可以看深入理解RunLoop

16斤葱、怎么檢查內(nèi)存泄露瞄摊?

  • 靜態(tài)分析Analyze

1勋又、不運(yùn)行程序, 直接檢測(cè)代碼中是否有潛在的內(nèi)存問題(不一定百分百準(zhǔn)確, 僅僅是提供建議)
2、結(jié)合實(shí)際情況來(lái)分析, 是否真的有內(nèi)存問題

  • 使用Instruments工具進(jìn)行動(dòng)態(tài)分析

1换帜、運(yùn)行程序, 通過使用APP楔壤,查看內(nèi)存的分配情況(Allocations):可以查看做出了某個(gè)操作后(比如點(diǎn)擊了某個(gè)按鈕\顯示了某個(gè)控制器),內(nèi)存是否有暴增的情況(突然變化)
2惯驼、運(yùn)行程序, 通過使用APP, 查看是否有內(nèi)存泄漏(Leaks):紅色區(qū)域代表內(nèi)存泄漏出現(xiàn)的地方

memory-instruments-1.jpg

17、什么情況下會(huì)發(fā)生內(nèi)存泄漏和內(nèi)存溢出祟牲?

  • 內(nèi)存泄漏:堆里不再使用的對(duì)象沒有被銷毀隙畜,依然占據(jù)著內(nèi)存。
  • 內(nèi)存溢出:一次內(nèi)存泄露危害可以忽略说贝,但內(nèi)存泄露多了议惰,內(nèi)存遲早會(huì)被占光,最終會(huì)導(dǎo)致內(nèi)存溢出乡恕!當(dāng)程序在申請(qǐng)內(nèi)存時(shí)言询,沒有足夠的內(nèi)存空間供其使用,出現(xiàn)out of memory傲宜;比如數(shù)據(jù)長(zhǎng)度比較小的數(shù)據(jù)類型 存儲(chǔ)了數(shù)據(jù)長(zhǎng)度比較大的數(shù)據(jù)运杭。

18、weak屬性需要在dealloc中置nil么函卒?

ARC環(huán)境無(wú)論是強(qiáng)指針還是弱指針都無(wú)需在dealloc 設(shè)置為nil辆憔, ARC會(huì)自動(dòng)幫我們處理。即便是編譯器不幫我們做這些报嵌,weak也不需要在dealloc中置nil虱咧,在屬性所指的對(duì)象遭到摧毀時(shí),屬性值也會(huì)清空

19锚国、什么時(shí)候在block中不需要使用weakSelf?

我們知道腕巡,在使用block的時(shí)候,為了避免產(chǎn)生循環(huán)引用跷叉,通常需要使用weakSelfstrongSelf逸雹,寫下面這樣的代碼:

__weak typeof(self) weakSelf = self;
[self doSomeBlockJob:^{
    __strong typeof(weakSelf) strongSelf = weakSelf;
    if (strongSelf) {
        ...
    }
}];

當(dāng)block本身不被self持有营搅,而被別的對(duì)象持有云挟,同時(shí)不產(chǎn)生循環(huán)引用的時(shí)候,就不需要使用weakSelf了转质。

最常見的代碼就是UIView的動(dòng)畫代碼园欣,我們?cè)谑褂?code>UIView的animateWithDuration:animations方法做動(dòng)畫的時(shí)候,并不需要使用 weakself休蟹,因?yàn)橐贸钟嘘P(guān)系是:

  • UIView的某個(gè)負(fù)責(zé)動(dòng)畫的對(duì)象持有了block
  • block 持有了 self

因?yàn)?self并不持有 block沸枯,所以就沒有循環(huán)引用產(chǎn)生日矫,因?yàn)榫筒恍枰褂?code>weakSelf了

[UIView animateWithDuration:0.2 animations:^{
    self.alpha = 1;
}];

當(dāng)動(dòng)畫結(jié)束時(shí),UIView 會(huì)結(jié)束持有這個(gè)block绑榴,如果沒有別的對(duì)象持有block的話哪轿,block對(duì)象就會(huì)釋放掉,從而block會(huì)釋放掉對(duì)于self的持有翔怎。整個(gè)內(nèi)存引用關(guān)系被解除窃诉。

參考

iOSInterviewQuestions
iOS內(nèi)存管理詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市赤套,隨后出現(xiàn)的幾起案子飘痛,更是在濱河造成了極大的恐慌,老刑警劉巖容握,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宣脉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡剔氏,警方通過查閱死者的電腦和手機(jī)塑猖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)介蛉,“玉大人萌庆,你說(shuō)我怎么就攤上這事”揖桑” “怎么了践险?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)吹菱。 經(jīng)常有香客問我巍虫,道長(zhǎng),這世上最難降的妖魔是什么鳍刷? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任占遥,我火速辦了婚禮,結(jié)果婚禮上输瓜,老公的妹妹穿的比我還像新娘瓦胎。我一直安慰自己,他們只是感情好尤揣,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布搔啊。 她就那樣靜靜地躺著,像睡著了一般北戏。 火紅的嫁衣襯著肌膚如雪负芋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天嗜愈,我揣著相機(jī)與錄音旧蛾,去河邊找鬼莽龟。 笑死,一個(gè)胖子當(dāng)著我的面吹牛锨天,可吹牛的內(nèi)容都是我干的毯盈。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼病袄,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼奶镶!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起陪拘,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤厂镇,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后左刽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捺信,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年欠痴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了迄靠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡喇辽,死狀恐怖掌挚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情菩咨,我是刑警寧澤吠式,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站抽米,受9級(jí)特大地震影響特占,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜云茸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一是目、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧标捺,春花似錦懊纳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至萍倡,卻和暖如春身弊,著一層夾襖步出監(jiān)牢的瞬間辟汰,已是汗流浹背列敲。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工阱佛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人戴而。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓凑术,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親所意。 傳聞我的和親對(duì)象是個(gè)殘疾皇子淮逊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348