SUNNYXX的面試題

1.@property 后面可以有哪些修飾符?

1.線程安全的: atomic, nonatomic
2.訪問權(quán)限的: readonly助泽, readwrite
3.內(nèi)存管理(ARC) assign啰扛,strong,weak嗡贺,copy
4.內(nèi)存管理(MRC)assign隐解, retain,copy
5.指定方法名稱: setter= getter=

2.什么情況使用 weak 關(guān)鍵字诫睬,相比 assign 有什么不同煞茫?

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

如果用weak聲明的變量在棧中就會自動清空,賦值為nil摄凡。

如果用assign聲明的變量在棧中可能不會自動賦值為nil续徽,就會造成野指針錯(cuò)誤!

assign一般用在基本數(shù)據(jù)類型上面, 比如int\double等

weak一般用在代理對象上面, 或者用來解決循環(huán)強(qiáng)引用的問題

3.怎么用 copy 關(guān)鍵字亲澡?

NSString钦扭、NSArray、NSDictionary 等經(jīng)常使用 copy 關(guān)鍵字谷扣,當(dāng)多個(gè)指針指向同一個(gè)對象時(shí),為避免一個(gè)指針對對象的改動對其他指針的使用產(chǎn)生影響,使用copy來創(chuàng)建對象的副本
如頁面間傳值使用copy,A向B控制器傳屬性(屬性為自定義對象),為避免因A的屬性變化對B的屬性產(chǎn)生影響
再如多人開發(fā)或封裝庫,在不明確傳入值為可變還是不可變的情況下,使用copy更安全土全。

block 使用copy關(guān)鍵字是從 MRC 遺留下來的“傳統(tǒng)”捎琐,在 MRC 中会涎,方法內(nèi)部的 block 是在棧區(qū)的,使用 copy 可以把它放到堆區(qū)瑞凑。在 ARC 中寫不寫都行末秃,對于 block 使用 copy 還是 strong 效果是一樣的,但寫上 copy 也無傷大雅籽御,還是能時(shí)刻提醒我們练慕,編譯器自動對 block 進(jìn)行了 copy 操作

4.這個(gè)寫法會出什么問題: @property (copy) NSMutableArray *array;

Copy 修飾 可變的對象的話, 會生成一個(gè)不可變的NSCFConstantSting對象,賦值給可變屬性,編譯沒問題, 調(diào)方法修改其內(nèi)容時(shí)崩潰. unrecognized selector sent to instance

可以使用mutableCopy去解決這個(gè)問題惰匙。
copy相關(guān)

5.如何讓自己的類用 copy 修飾符?如何重寫帶 copy 關(guān)鍵字的 setter铃将?

若想令自己所寫的對象具有拷貝功能项鬼,則需實(shí)現(xiàn) NSCopying 協(xié)議。如果自定義的對象分為可變版本與不可變版本劲阎,那么就要同時(shí)實(shí)現(xiàn) NSCopying 與 NSMutableCopying 協(xié)議绘盟。
//重寫這個(gè)方法
- (id)copyWithZone:(NSZone *)zone
//重寫setter
- (void)setName:(NSString *)name {
    _name = [name copy];
}

6.@property 的本質(zhì)是什么?ivar悯仙、getter龄毡、setter 是如何生成并添加到這個(gè)類中的

@property = ivar + getter + setter;
“屬性” (property)有兩大概念
ivar(實(shí)例變量)、存取方法(access method = getter + setter)锡垄。

//以下代碼
@interface Person : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end

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

//ivar沦零、getter、setter 是如何生成并添加到這個(gè)類中的?
編譯器會自動編寫訪問這些屬性所需的方法货岭,此過程叫做“自動合成”(autosynthesis)路操。
需要強(qiáng)調(diào)的是,這個(gè)過程由編譯器在編譯期執(zhí)行千贯,所以編輯器里看不到這些“合成方法”(synthesized method)的源代碼寻拂。
除了生成方法代碼 getter、setter 之外丈牢,編譯器還要自動向類中添加適當(dāng)類型的實(shí)例變量祭钉,并且在屬性名前面加下劃線,以此作為實(shí)例變量的名字己沛。
在前例中慌核,會生成兩個(gè)實(shí)例變量,其名稱分別為 _firstName 與 _lastName申尼。也可以在類的實(shí)現(xiàn)代碼里通過 @synthesize 語法來指定實(shí)例變量的名字.

@implementation Person
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end
  1. @protocol 和 category 中如何使用 @property
在 protocol 中使用 property 只會生成 setter 和 getter 方法聲明,我們使用屬性的目的,是希望遵守我協(xié)議的對象能實(shí)現(xiàn)該屬性

category 使用 @property 也是只會生成 setter 和 getter 方法的聲明,如果我們真的需要給 category 增加屬性的實(shí)現(xiàn),需要借助于運(yùn)行時(shí)的兩個(gè)函數(shù):

objc_setAssociatedObject

objc_getAssociatedObject

8.runtime 如何實(shí)現(xiàn) weak 屬性

weak 此特質(zhì)表明該屬性定義了一種“非擁有關(guān)系” (nonowning relationship)垮卓。
為這種屬性設(shè)置新值時(shí),設(shè)置方法既不保留新值师幕,也不釋放舊值粟按。此特質(zhì)同 assign 類似, 然而在屬性所指的對象遭到摧毀時(shí)霹粥,屬性值也會清空(nil out)

runtime 對注冊的類灭将, 會進(jìn)行布局,對于 weak 對象會放入一個(gè) hash 表中后控。
用 weak 指向的對象內(nèi)存地址作為 key庙曙,當(dāng)此對象的引用計(jì)數(shù)為0的時(shí)候會 dealloc,假如 weak 指向的對象內(nèi)存地址是a浩淘,那么就會以a為鍵捌朴, 在這個(gè) weak 表中搜索吴攒,找到所有以a為鍵的 weak 對象,從而設(shè)置為 nil砂蔽。

{
    id obj1 = [[NSObject alloc] init];
    id __weak obj2 = obj1;
}
//轉(zhuǎn)換后
id obj2;
objc_initWeak(&obj2, obj1);
objc_destroyWeak(&obj2);

id objc_initWeak(id *location, id newObj) {
// 查看對象實(shí)例是否有效,無效對象直接導(dǎo)致指針釋放
    if (!newObj) {
        *location = nil;
        return nil;
    }
    // 這里傳遞了三個(gè) bool 數(shù)值
    // 使用 template 進(jìn)行常量參數(shù)傳遞是為了優(yōu)化性能
    return storeWeak false/*old*/, true/*new*/, true/*crash*/>
    (location, (objc_object*)newObj);
}

添加引用時(shí):objc_initWeak函數(shù)會調(diào)用 objc_storeWeak() 函數(shù)洼怔, objc_storeWeak() 的作用是更新指針指向,創(chuàng)建對應(yīng)的弱引用表

如果obj1的引用計(jì)數(shù)變?yōu)?左驾,obj1的內(nèi)存地址就會用作一個(gè)key去weak表中查詢茴厉,與之相關(guān)聯(lián)的value就會變成nil。weak表中的記錄會被清除什荣。

  1. @property中有哪些屬性關(guān)鍵字矾缓?/ @property 后面可以有哪些修飾符?

原子性--- nonatomic 特質(zhì)在默認(rèn)情況下稻爬,由編譯器合成的方法會通過鎖定機(jī)制確保其原子性(atomicity)嗜闻。如果屬性具備 nonatomic 特質(zhì),則不使用自旋鎖桅锄。
請注意琉雳,盡管沒有名為“atomic”的特質(zhì)(如果某屬性不具備 nonatomic 特質(zhì),那它就是“原子的” ( atomic) )友瘤,但是仍然可以在屬性特質(zhì)中寫明這一點(diǎn)翠肘,編譯器不會報(bào)錯(cuò)。若是自己定義存取方法辫秧,那么就應(yīng)該遵從與屬性特質(zhì)相符的原子性束倍。

讀/寫權(quán)限---readwrite(讀寫)、readonly (只讀)

內(nèi)存管理語義---assign盟戏、strong绪妹、 weak、unsafe_unretained柿究、copy

方法名---getter=<name> 邮旷、setter=<name>

不常用的:nonnull不能為空(用來修飾屬性,或者方法的參數(shù),方法的返回值)
null_resettable不能返回空, set可以為空(注意:如果使用null_resettable,必須 重寫get方法或者set方法,處理傳遞的值為空的情況)

nullable表示可以為空(使用方法和上面幾乎一樣,但是沒有發(fā)現(xiàn)和上面類似的宏)

  1. weak屬性需要在dealloc中置nil么蝇摸?

在ARC環(huán)境無論是強(qiáng)指針還是弱指針都無需在 dealloc 設(shè)置為 nil 婶肩, ARC 會自動幫我們處理

  1. @synthesize和@dynamic分別有什么作用?

1.@property有兩個(gè)對應(yīng)的詞貌夕,一個(gè)是 @synthesize律歼,一個(gè)是 @dynamic。如果 @synthesize和 @dynamic都沒寫蜂嗽,那么默認(rèn)的就是@syntheszie var = _var;

2.@synthesize 的語義是如果你沒有手動實(shí)現(xiàn) setter 方法和 getter 方法苗膝,那么編譯器會自動為你加上這兩個(gè)方法。

3.@dynamic 告訴編譯器:屬性的 setter 與 getter 方法由用戶自己實(shí)現(xiàn)植旧,不自動生成辱揭。(當(dāng)然對于 readonly 的屬性只需提供 getter 即可)。假如一個(gè)屬性被聲明為 @dynamic var病附,然后你沒有提供 @setter方法和 @getter 方法问窃,編譯的時(shí)候沒問題,但是當(dāng)程序運(yùn)行到 instance.var = someVar完沪,由于缺 setter 方法會導(dǎo)致程序崩潰域庇;或者當(dāng)運(yùn)行到 someVar = var 時(shí),由于缺 getter 方法同樣會導(dǎo)致崩潰覆积。編譯時(shí)沒問題听皿,運(yùn)行時(shí)才執(zhí)行相應(yīng)的方法,這就是所謂的動態(tài)綁定宽档。

  1. ARC下尉姨,不顯式指定任何屬性關(guān)鍵字時(shí),默認(rèn)的關(guān)鍵字都有哪些吗冤?

1.對應(yīng)基本數(shù)據(jù)類型默認(rèn)關(guān)鍵字是atomic,readwrite,assign

2.對于普通的 Objective-C 對象atomic,readwrite,strong

  1. 用@property聲明的NSString(或NSArray又厉,NSDictionary)經(jīng)常使用copy關(guān)鍵字,為什么椎瘟?如果改用strong關(guān)鍵字覆致,可能造成什么問題?

1.因?yàn)楦割愔羔樋梢灾赶蜃宇悓ο?使用 copy 的目的是為了讓本對象的屬性不受外界影響,使用 copy 無論給我傳入是一個(gè)可變對象還是不可對象,我本身持有的就是一個(gè)不可變的副本.
2.如果我們使用是 strong ,那么這個(gè)屬性就有可能指向一個(gè)可變對象,如果這個(gè)可變對象在外部被修改了,那么會影響該屬性.

@property (nonatomic ,readwrite, strong) NSArray *array;

   NSArray *array = @[ @1, @2, @3, @4 ];
   NSMutableArray *mutableArray = [NSMutableArray arrayWithArray:array];
   
   self.array = mutableArray;
   [mutableArray removeAllObjects];;
   NSLog(@"%@",self.array);
   
   [mutableArray addObjectsFromArray:array];
   self.array = [mutableArray copy];
   [mutableArray removeAllObjects];;
   NSLog(@"%@",self.array);

//打印結(jié)果
2015-09-27 19:10:32.523 CYLArrayCopyDmo[10681:713670] (
)
2015-09-27 19:10:32.524 CYLArrayCopyDmo[10681:713670] (
   1,
   2,
   3,
   4
)
為了理解這種做法肺蔚,首先要知道煌妈,兩種情況:

1.對非集合類對象的 copy 與 mutableCopy 操作;
2.對集合類對象的 copy 與 mutableCopy 操作宣羊。

1. 對非集合類對象的copy操作:

在非集合類對象中:對immutable對象進(jìn)行 copy 操作声旺,是指針復(fù)制,mutableCopy 操作時(shí)內(nèi)容復(fù)制段只;對 mutable對象進(jìn)行 copy 和 mutableCopy 都是內(nèi)容復(fù)制腮猖。用代碼簡單表示如下:

[immutableObject copy] // 淺復(fù)制
[immutableObject mutableCopy] //深復(fù)制
[mutableObject copy] //深復(fù)制
[mutableObject mutableCopy] //深復(fù)制

比如以下代碼:

NSMutableString *string = [NSMutableString stringWithString:@"origin"];//copy
NSString *stringCopy = [string copy];

查看內(nèi)存,會發(fā)現(xiàn) string赞枕、stringCopy 內(nèi)存地址都不一樣澈缺,說明此時(shí)都是做內(nèi)容拷貝、深拷貝炕婶。即使你進(jìn)行如下操作:

[string appendString:@"origion!"]

stringCopy 的值也不會因此改變姐赡,但是如果不使用 copy,stringCopy 的值就會被改變柠掂。

用 @property 聲明 NSString项滑、NSArray、NSDictionary 經(jīng)常使用 copy 關(guān)鍵字涯贞,是因?yàn)樗麄冇袑?yīng)的可變類型:NSMutableString枪狂、NSMutableArray危喉、NSMutableDictionary,他們之間可能進(jìn)行賦值操作州疾,為確保對象中的字符串值不會無意間變動辜限,應(yīng)該在設(shè)置新屬性值時(shí)拷貝一份。

2严蓖、集合類對象的copy與mutableCopy

集合類對象是指 NSArray薄嫡、NSDictionary、NSSet ... 之類的對象颗胡。下面先看集合類immutable對象使用 copy 和 mutableCopy 的一個(gè)例子:

NSArray *array = @[@[@"a", @"b"], @[@"c", @"d"]];
NSArray *copyArray = [array copy];
NSMutableArray *mCopyArray = [array mutableCopy];

查看內(nèi)容毫深,可以看到 copyArray 和 array 的地址是一樣的,而 mCopyArray 和 array 的地址是不同的毒姨。說明 copy 操作進(jìn)行了指針拷貝哑蔫,mutableCopy 進(jìn)行了內(nèi)容拷貝。但需要強(qiáng)調(diào)的是:此處的內(nèi)容拷貝手素,僅僅是拷貝 array 這個(gè)對象鸳址,array 集合內(nèi)部的元素仍然是指針拷貝。

看 mutable 對象拷貝的例子:
NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
NSArray *copyArray = [array copy];
NSMutableArray *mCopyArray = [array mutableCopy];

查看內(nèi)存泉懦,如我們所料稿黍,copyArray、mCopyArray和 array 的內(nèi)存地址都不一樣崩哩,說明 copyArray巡球、mCopyArray 都對 array 進(jìn)行了內(nèi)容拷貝。同樣邓嘹,我們可以得出結(jié)論:

在集合類對象中酣栈,對 immutable 對象進(jìn)行 copy,是指針復(fù)制汹押, mutableCopy 是內(nèi)容復(fù)制矿筝;對 mutable 對象進(jìn)行 copy 和 mutableCopy 都是內(nèi)容復(fù)制。但是:集合對象的內(nèi)容復(fù)制僅限于對象本身棚贾,對象元素仍然是指針復(fù)制窖维。用代碼簡單表示如下:

[immutableObject copy] // 淺復(fù)制
[immutableObject mutableCopy] //單層深復(fù)制
[mutableObject copy] //單層深復(fù)制
[mutableObject mutableCopy] //單層深復(fù)制
  1. @synthesize合成實(shí)例變量的規(guī)則是什么?假如property名為foo妙痹,存在一個(gè)名為_foo的實(shí)例變量铸史,那么還會自動合成新變量么?

實(shí)例變量 = 成員變量 = ivar

@synthesize 合成實(shí)例變量的規(guī)則怯伊,有以下幾點(diǎn):

1.如果指定了成員變量的名稱,會生成一個(gè)指定的名稱的成員變量
2.如果這個(gè)成員已經(jīng)存在了就不再生成了
3.如果是 @synthesize foo; 還會生成一個(gè)名稱為foo的成員變量琳轿,也就是說:如果沒有指定成員變量的名稱會自動生成一個(gè)屬性同名的成員變量

  1. 在有了自動合成屬性實(shí)例變量之后,@synthesize還有哪些使用場景?

回答這個(gè)問題前崭篡,我們要搞清楚一個(gè)問題挪哄,什么情況下不會autosynthesis(自動合成)?

同時(shí)重寫了 setter 和 getter 時(shí)
重寫了只讀屬性的 getter 時(shí)
使用了 @dynamic 時(shí)
在 @protocol 中定義的所有屬性
在 category 中定義的所有屬性
重載的屬性

除了后三條媚送,對其他幾個(gè)我們可以總結(jié)出一個(gè)規(guī)律:當(dāng)你想手動管理 @property 的所有內(nèi)容時(shí)中燥,你就會嘗試通過實(shí)現(xiàn) @property 的所有“存取方法”(the accessor methods)或者使用 @dynamic 來達(dá)到這個(gè)目的寇甸,這時(shí)編譯器就會認(rèn)為你打算手動管理 @property塘偎,于是編譯器就禁用了 autosynthesis(自動合成)。

因?yàn)橛辛?autosynthesis(自動合成)拿霉,大部分開發(fā)者已經(jīng)習(xí)慣不去手動定義ivar吟秩,而是依賴于 autosynthesis(自動合成),但是一旦你需要使用ivar绽淘,而 autosynthesis(自動合成)又失效了涵防,如果不去手動定義ivar,那么你就得借助 @synthesize 來手動合成 ivar沪铭。

// 打開第14行和第17行中任意一行壮池,就可編譯成功

@import Foundation;

@interface CYLObject : NSObject
@property (nonatomic, copy) NSString *title;
@end

@implementation CYLObject {
   //    NSString *_title;
}

//@synthesize title = _title;

- (instancetype)init
{
   self = [super init];
   if (self) {
       _title = @"微博@iOS程序犭袁";
   }
   return self;
}

- (NSString *)title {
   return _title;
}

- (void)setTitle:(NSString *)title {
   _title = [title copy];
}

@end

當(dāng)你同時(shí)重寫了 setter 和 getter 時(shí),系統(tǒng)就不會生成 ivar(實(shí)例變量/成員變量)杀怠。這時(shí)候有兩種選擇:
要么如第14行:手動創(chuàng)建 ivar
要么如第17行:使用@synthesize foo = _foo; 椰憋,關(guān)聯(lián) @property 與 ivar。

  1. objc中向一個(gè)nil對象發(fā)送消息將會發(fā)生什么赔退?

在 Objective-C 中向 nil 發(fā)送消息是完全有效的——只是在運(yùn)行時(shí)不會有任何作用:objc是動態(tài)語言橙依,每個(gè)方法在運(yùn)行時(shí)會被動態(tài)轉(zhuǎn)為消息發(fā)送,即:objc_msgSend(receiver, selector)硕旗。

// runtime.h(類在runtime中的定義)
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong

struct objc_class {
  Class isa OBJC_ISA_AVAILABILITY; //isa指針指向Meta Class窗骑,因?yàn)镺bjc的類的本身也是一個(gè)Object缔逛,為了處理這個(gè)關(guān)系动漾,runtime就創(chuàng)造了Meta Class,當(dāng)給類發(fā)送[NSObject alloc]這樣消息時(shí)县爬,實(shí)際上是把這個(gè)消息發(fā)給了Class Object
  #if !__OBJC2__
  Class super_class OBJC2_UNAVAILABLE; // 父類
  const char *name OBJC2_UNAVAILABLE; // 類名
  long version OBJC2_UNAVAILABLE; // 類的版本信息墙基,默認(rèn)為0
  long info OBJC2_UNAVAILABLE; // 類信息软族,供運(yùn)行期使用的一些位標(biāo)識
  long instance_size OBJC2_UNAVAILABLE; // 該類的實(shí)例變量大小
  struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 該類的成員變量鏈表
  struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定義的鏈表
  struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法緩存,對象接到一個(gè)消息會根據(jù)isa指針查找消息對象碘橘,這時(shí)會在method Lists中遍歷互订,如果cache了,常用的方法調(diào)用時(shí)就能夠提高調(diào)用的效率痘拆。
  struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協(xié)議鏈表
  #endif
  } OBJC2_UNAVAILABLE;

objc在向一個(gè)對象發(fā)送消息時(shí)仰禽,runtime庫會根據(jù)對象的isa指針找到該對象實(shí)際所屬的類,然后在該類中的方法列表以及其父類方法列表中尋找方法運(yùn)行,然后在發(fā)送消息的時(shí)候吐葵,objc_msgSend方法不會返回值规揪,所謂的返回內(nèi)容都是具體調(diào)用時(shí)執(zhí)行的。 那么温峭,回到本題猛铅,如果向一個(gè)nil對象發(fā)送消息,首先在尋找對象的isa指針時(shí)就是0地址返回了凤藏,所以不會出現(xiàn)任何錯(cuò)誤奸忽。

  1. objc中向一個(gè)對象發(fā)送消息[obj foo]和objc_msgSend()函數(shù)之間有什么關(guān)系?
#import "CYLTest.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        CYLTest *test = [[CYLTest alloc] init];
        [test performSelector:(@selector(iOSinit))];
        return 0;
    }
}

在終端輸入clang -rewrite-objc main.m

轉(zhuǎn)換后的代碼:

((void ()(id, SEL))(void )objc_msgSend)((id)obj, sel_registerName("foo"));

[obj foo];在objc編譯時(shí)揖庄,會被轉(zhuǎn)意為:objc_msgSend(obj, @selector(foo));栗菜。

  1. 什么時(shí)候會報(bào)unrecognized selector的異常?

當(dāng)調(diào)用該對象上某個(gè)方法,而該對象上沒有實(shí)現(xiàn)這個(gè)方法的時(shí)候蹄梢, 可以通過“消息轉(zhuǎn)發(fā)”進(jìn)行解決疙筹。

objc是動態(tài)語言,每個(gè)方法在運(yùn)行時(shí)會被動態(tài)轉(zhuǎn)為消息發(fā)送禁炒,即:objc_msgSend(receiver, selector)而咆。

objc在向一個(gè)對象發(fā)送消息時(shí),runtime庫會根據(jù)對象的isa指針找到該對象實(shí)際所屬的類幕袱,然后在該類中的方法列表以及其父類方法列表中尋找方法運(yùn)行暴备,如果,在最頂層的父類中依然找不到相應(yīng)的方法時(shí)凹蜂,程序在運(yùn)行時(shí)會掛掉并拋出異常unrecognized selector sent to XXX 馍驯。但是在這之前,objc的運(yùn)行時(shí)會給出三次拯救程序崩潰的機(jī)會:

1.Method resolution
objc運(yùn)行時(shí)會調(diào)用+resolveInstanceMethod:或者 +resolveClassMethod:玛痊,讓你有機(jī)會提供一個(gè)函數(shù)實(shí)現(xiàn)汰瘫。如果你添加了函數(shù),那運(yùn)行時(shí)系統(tǒng)就會重新啟動一次消息發(fā)送的過程擂煞,否則 混弥,運(yùn)行時(shí)就會移到下一步,消息轉(zhuǎn)發(fā)(Message Forwarding)对省。

2.Fast forwarding
如果目標(biāo)對象實(shí)現(xiàn)了-forwardingTargetForSelector:蝗拿,Runtime 這時(shí)就會調(diào)用這個(gè)方法,給你把這個(gè)消息轉(zhuǎn)發(fā)給其他對象的機(jī)會蒿涎。 只要這個(gè)方法返回的不是nil和self哀托,整個(gè)消息發(fā)送的過程就會被重啟,當(dāng)然發(fā)送的對象會變成你返回的那個(gè)對象劳秋。否則仓手,就會繼續(xù)Normal Fowarding胖齐。 這里叫Fast,只是為了區(qū)別下一步的轉(zhuǎn)發(fā)機(jī)制嗽冒。因?yàn)檫@一步不會創(chuàng)建任何新的對象呀伙,但下一步轉(zhuǎn)發(fā)會創(chuàng)建一個(gè)NSInvocation對象,所以相對更快點(diǎn)添坊。
3.Normal forwarding

這一步是Runtime最后一次給你挽救的機(jī)會剿另。首先它會發(fā)送-methodSignatureForSelector:消息獲得函數(shù)的參數(shù)和返回值類型。如果-methodSignatureForSelector:返回nil贬蛙,Runtime則會發(fā)出-doesNotRecognizeSelector:消息雨女,程序這時(shí)也就掛掉了。如果返回了一個(gè)函數(shù)簽名速客,Runtime就會創(chuàng)建一個(gè)NSInvocation對象并發(fā)送-forwardInvocation:消息給目標(biāo)對象戚篙。

  1. 一個(gè)objc對象如何進(jìn)行內(nèi)存布局五鲫?(考慮有父類的情況)

所有父類的成員變量和自己的成員變量都會存放在該對象所對應(yīng)的存儲空間中.

每一個(gè)對象內(nèi)部都有一個(gè)isa指針,指向他的類對象,類對象中存放著本對象的
對象方法列表(對象能夠接收的消息列表溺职,保存在它所對應(yīng)的類對象中)
成員變量的列表,
屬性列表,

  1. 一個(gè)objc對象的isa的指針指向什么?有什么作用位喂?

指向他的類對象,從而可以找到對象上的方法

  1. 下面的代碼輸出什么浪耘?
   @implementation Son : Father
   - (id)init
   {
       self = [super init];
       if (self) {
           NSLog(@"%@", NSStringFromClass([self class]));
           NSLog(@"%@", NSStringFromClass([super class]));
       }
       return self;
   }
   @end

都輸出 Son

我們都知道:self 是類的隱藏參數(shù),指向當(dāng)前調(diào)用方法的這個(gè)類的實(shí)例塑崖。那 super 呢七冲?

很多人會想當(dāng)然的認(rèn)為“ super 和 self 類似,應(yīng)該是指向父類的指針吧规婆!”澜躺。這是很普遍的一個(gè)誤區(qū)。其實(shí) super 是一個(gè) Magic Keyword抒蚜, 它本質(zhì)是一個(gè)編譯器標(biāo)示符掘鄙,和 self 是指向的同一個(gè)消息接受者!他們兩個(gè)的不同點(diǎn)在于:super 會告訴編譯器嗡髓,調(diào)用 class 這個(gè)方法時(shí)操漠,要去父類的方法,而不是本類里的饿这。

上面的例子不管調(diào)用[self class]還是[super class]浊伙,接受消息的對象都是當(dāng)前 Son *xxx 這個(gè)對象。

當(dāng)使用 self 調(diào)用方法時(shí)长捧,會從當(dāng)前類的方法列表中開始找嚣鄙,如果沒有,就從父類中再找串结;而當(dāng)使用 super 時(shí)哑子,則從父類的方法列表中開始找廓八。然后調(diào)用父類的這個(gè)方法。

  1. runtime如何通過selector找到對應(yīng)的IMP地址赵抢?(分別考慮類方法和實(shí)例方法)

每一個(gè)類對象中都一個(gè)方法列表,方法列表中記錄著方法的名稱,方法實(shí)現(xiàn),以及參數(shù)類型,其實(shí)selector本質(zhì)就是方法名稱,通過這個(gè)方法名稱就可以在方法列表中找到對應(yīng)的方法實(shí)現(xiàn).

  1. objc中的類方法和實(shí)例方法有什么本質(zhì)區(qū)別和聯(lián)系剧蹂?

類方法:
1.類方法是屬于類對象的
2.類方法只能通過類對象調(diào)用
3.類方法中的self是類對象
4.類方法可以調(diào)用其他的類方法
5.類方法中不能訪問成員變量
6.類方法中不能直接調(diào)用對象方法

實(shí)例方法:
1.實(shí)例方法是屬于實(shí)例對象的
2.實(shí)例方法只能通過實(shí)例對象調(diào)用
3.實(shí)例方法中的self是實(shí)例對象
4.實(shí)例方法中可以訪問成員變量
5.實(shí)例方法中直接調(diào)用實(shí)例方法
6.實(shí)例方法中也可以調(diào)用類方法(通過類名)

  1. _objc_msgForward函數(shù)是做什么的,直接調(diào)用它將會發(fā)生什么烦却?

_objc_msgForward是 IMP 類型宠叼,用于消息轉(zhuǎn)發(fā)的:當(dāng)向一個(gè)對象發(fā)送一條消息,但它并沒有實(shí)現(xiàn)的時(shí)候其爵,_objc_msgForward會嘗試做消息轉(zhuǎn)發(fā)冒冬。

在“消息傳遞”過程中,objc_msgSend的動作比較清晰:首先在 Class 中的緩存查找 IMP (沒緩存則初始化緩存)摩渺,如果沒找到简烤,則向父類的 Class 查找。如果一直查找到根類仍舊沒有實(shí)現(xiàn)摇幻,則用_objc_msgForward函數(shù)指針代替 IMP 横侦。

對 objc-runtime-new.mm文件里與_objc_msgForward有關(guān)的三個(gè)函數(shù)使用偽代碼展示下:

id objc_msgSend(id self, SEL op, ...) {
    if (!self) return nil;
    IMP imp = class_getMethodImplementation(self->isa, SEL op);
    imp(self, op, ...); //調(diào)用這個(gè)函數(shù),偽代碼...
}
 
//查找IMP
IMP class_getMethodImplementation(Class cls, SEL sel) {
    if (!cls || !sel) return nil;
    IMP imp = lookUpImpOrNil(cls, sel);
    if (!imp) return _objc_msgForward; //_objc_msgForward 用于消息轉(zhuǎn)發(fā)
    return imp;
}
 
IMP lookUpImpOrNil(Class cls, SEL sel) {
    if (!cls->initialize()) {
        _class_initialize(cls);
    }
 
    Class curClass = cls;
    IMP imp = nil;
    do { //先查緩存,緩存沒有時(shí)重建,仍舊沒有則向父類查詢
        if (!curClass) break;
        if (!curClass->cache) fill_cache(cls, curClass);
        imp = cache_getImp(curClass, sel);
        if (imp) break;
    } while (curClass = curClass->superclass);
 
    return imp;
}

_objc_msgForward是一個(gè)函數(shù)指針(和 IMP 的類型一樣)绰姻,是用于消息轉(zhuǎn)發(fā)的:當(dāng)向一個(gè)對象發(fā)送一條消息枉侧,但它并沒有實(shí)現(xiàn)的時(shí)候,_objc_msgForward會嘗試做消息轉(zhuǎn)發(fā)狂芋。

_objc_msgForward消息轉(zhuǎn)發(fā)做的幾件事:

1.調(diào)用resolveInstanceMethod:方法 (或 resolveClassMethod:)榨馁。允許用戶在此時(shí)為該 Class 動態(tài)添加實(shí)現(xiàn)。如果有實(shí)現(xiàn)了帜矾,則調(diào)用并返回YES翼虫,那么重新開始o(jì)bjc_msgSend流程。這一次對象會響應(yīng)這個(gè)選擇器屡萤,一般是因?yàn)樗呀?jīng)調(diào)用過class_addMethod珍剑。如果仍沒實(shí)現(xiàn),繼續(xù)下面的動作灭衷。

2.調(diào)用forwardingTargetForSelector:方法次慢,嘗試找到一個(gè)能響應(yīng)該消息的對象。如果獲取到翔曲,則直接把消息轉(zhuǎn)發(fā)給它迫像,返回非 nil 對象。否則返回 nil 瞳遍,繼續(xù)下面的動作闻妓。注意,這里不要返回 self 掠械,否則會形成死循環(huán)由缆。

3.調(diào)用methodSignatureForSelector:方法注祖,嘗試獲得一個(gè)方法簽名。如果獲取不到均唉,則直接調(diào)用doesNotRecognizeSelector拋出異常是晨。如果能獲取,則返回非nil:創(chuàng)建一個(gè) NSlnvocation 并傳給forwardInvocation:舔箭。

4.調(diào)用forwardInvocation:方法罩缴,將第3步獲取到的方法簽名包裝成 Invocation 傳入,如何處理就在這里面了层扶,并返回非nil箫章。

5.調(diào)用doesNotRecognizeSelector: ,默認(rèn)的實(shí)現(xiàn)是拋出異常镜会。如果第3步?jīng)]能獲得一個(gè)方法簽名檬寂,執(zhí)行該步驟。

  1. 能否向編譯后得到的類中增加實(shí)例變量戳表?能否向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量桶至?為什么?

不能向編譯后得到的類中增加實(shí)例變量扒袖;
能向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量塞茅;

1.因?yàn)榫幾g后的類已經(jīng)注冊在 runtime 中,類結(jié)構(gòu)體中的 objc_ivar_list 實(shí)例變量的鏈表 和 instance_size 實(shí)例變量的內(nèi)存大小已經(jīng)確定季率,同時(shí)runtime 會調(diào)用 class_setIvarLayout 或 class_setWeakIvarLayout 來處理 strong weak 引用。所以不能向存在的類中添加實(shí)例變量描沟;

2.運(yùn)行時(shí)創(chuàng)建的類是可以添加實(shí)例變量飒泻,調(diào)用 class_addIvar 函數(shù)。但是得在調(diào)用 objc_allocateClassPair 之后吏廉,objc_registerClassPair 之前泞遗,原因同上。

  1. objc使用什么機(jī)制管理對象內(nèi)存席覆?

通過 retainCount 的機(jī)制來決定對象是否需要釋放史辙。 每次 runloop 的時(shí)候,都會檢查對象的 retainCount佩伤,如果retainCount 為 0聊倔,說明該對象沒有地方需要繼續(xù)使用了,可以釋放掉了生巡。

27.ARC通過什么方式幫助開發(fā)者管理內(nèi)存耙蔑?

編譯時(shí)根據(jù)代碼上下文,插入 retain/release

28.不手動指定autoreleasepool的前提下孤荣,一個(gè)autorealese對象在什么時(shí)刻釋放甸陌?(比如在一個(gè)vc的viewDidLoad中創(chuàng)建)

分兩種情況:手動干預(yù)釋放時(shí)機(jī)须揣、系統(tǒng)自動去釋放。
手動干預(yù)釋放時(shí)機(jī)--指定autoreleasepool 就是所謂的:當(dāng)前作用域大括號結(jié)束時(shí)釋放钱豁。
系統(tǒng)自動去釋放--不手動指定autoreleasepool
Autorelease對象會在當(dāng)前的 runloop 迭代結(jié)束時(shí)釋放耻卡。
如果在一個(gè)vc的viewDidLoad中創(chuàng)建一個(gè) Autorelease對象,那么該對象會在 viewDidAppear 方法執(zhí)行前就被銷毀了牲尺。

29.BAD_ACCESS在什么情況下出現(xiàn)劲赠?

訪問了野指針,比如對一個(gè)已經(jīng)釋放的對象執(zhí)行了release秸谢、訪問已經(jīng)釋放對象的成員變量或者發(fā)消息凛澎。 死循環(huán)

30.蘋果是如何實(shí)現(xiàn)autoreleasepool的?

autoreleasepool以一個(gè)隊(duì)列數(shù)組的形式實(shí)現(xiàn),主要通過下列三個(gè)函數(shù)完成.
objc_autoreleasepoolPush
objc_autoreleasepoolPop
objc_aurorelease
看函數(shù)名就可以知道估蹄,對autorelease分別執(zhí)行push塑煎,和pop操作。銷毀對象時(shí)執(zhí)行release操作臭蚁。

31.使用block時(shí)什么情況會發(fā)生引用循環(huán)最铁,如何解決?

一個(gè)對象中強(qiáng)引用了block垮兑,在block中又使用了該對象冷尉,就會發(fā)射循環(huán)引用。 解決方法是將該對象使用__weak或者_(dá)_block修飾符修飾之后再在block中使用系枪。

id weak weakSelf = self; 

或者 weak __typeof(&*self)weakSelf = self該方法可以設(shè)置宏

id __block weakSelf = self;

32.在block內(nèi)如何修改block外部變量雀哨?

默認(rèn)情況下,在block中訪問的外部變量是復(fù)制過去的私爷,即:寫操作不對原變量生效雾棺。但是你可以加上__block來讓其寫操作生效,示例代碼如下:

__block int a = 0;
void  (^foo)(void) = ^{ 
    a = 1; 
}
f00(); 
//這里衬浑,a的值被修改為1

33.使用系統(tǒng)的某些block api(如UIView的block版本寫動畫時(shí))捌浩,是否也考慮引用循環(huán)問題?

系統(tǒng)的某些block api中工秩,UIView的block版本寫動畫時(shí)不需要考慮尸饺,但也有一些api 需要考慮。

所謂“引用循環(huán)”是指雙向的強(qiáng)引用助币,所以那些“單向的強(qiáng)引用”(block 強(qiáng)引用 self )沒有問題浪听,比如這些:

[UIView animateWithDuration:duration animations:^{ 
       [self.superview layoutIfNeeded];
 }]; 
[[NSOperationQueue mainQueue] addOperationWithBlock:^{ 
        self.someProperty = xyz; 
}]; 
[[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification" 
                                                  object:nil 
                           queue:[NSOperationQueue mainQueue]                                              usingBlock:^(NSNotification * notification) {
                                                    self.someProperty = xyz; }];

但如果你使用一些參數(shù)中可能含有 ivar 的系統(tǒng) api ,如 GCD 奠支、NSNotificationCenter就要小心一點(diǎn):比如GCD 內(nèi)部如果引用了 self馋辈,而且 GCD 的其他參數(shù)是 ivar,則要考慮到循環(huán)引用:

__weak __typeof__(self) weakSelf = self;
dispatch_group_async(_operationsGroup, _operationsQueue, ^
{
__typeof__(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doSomethingElse];
} );

類似的:

__weak __typeof__(self) weakSelf = self;
  _observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"
                                                                object:nil
                                                                 queue:nil
                                                            usingBlock:^(NSNotification *note) {
      __typeof__(self) strongSelf = weakSelf;
      [strongSelf dismissModalViewControllerAnimated:YES];
  }];

34.GCD的隊(duì)列(dispatch_queue_t)分哪兩種類型倍谜?

串行隊(duì)列Serial Dispatch Queue
并行隊(duì)列Concurrent Dispatch Queue

35.如何用GCD同步若干個(gè)異步調(diào)用迈螟?(如根據(jù)若干個(gè)url異步加載多張圖片叉抡,然后在都下載完成后合成一張整圖)

使用Dispatch Group追加block到Global Group Queue,這些block如果全部執(zhí)行完畢,就會執(zhí)行Main Dispatch Queue中的結(jié)束處理的block答毫。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{ /*加載圖片1 */ });
dispatch_group_async(group, queue, ^{ /*加載圖片2 */ });
dispatch_group_async(group, queue, ^{ /*加載圖片3 */ }); 
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 合并圖片
});

36.dispatch_barrier_async的作用是什么褥民?

在并行隊(duì)列中,為了保持某些任務(wù)的順序洗搂,需要等待一些任務(wù)完成后才能繼續(xù)進(jìn)行消返,使用 barrier 來等待之前任務(wù)完成,避免數(shù)據(jù)競爭等問題耘拇。 dispatch_barrier_async 函數(shù)會等待追加到Concurrent Dispatch Queue并行隊(duì)列中的操作全部執(zhí)行完之后撵颊,然后再執(zhí)行 dispatch_barrier_async 函數(shù)追加的處理,等 dispatch_barrier_async 追加的處理執(zhí)行結(jié)束之后惫叛,Concurrent Dispatch Queue才恢復(fù)之前的動作繼續(xù)執(zhí)行倡勇。

37.蘋果為什么要廢棄dispatch_get_current_queue?

dispatch_get_current_queue容易造成死鎖

38.以下代碼運(yùn)行結(jié)果如何嘉涌?

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSLog(@"1");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2");
    });
    NSLog(@"3");
}

只輸出:1 妻熊。發(fā)生主線程鎖死。

39.addObserver:forKeyPath:options:context:各個(gè)參數(shù)的作用分別是什么仑最,observer中需要實(shí)現(xiàn)哪個(gè)方法才能獲得KVO回調(diào)扔役?

// 添加鍵值觀察
/*
1 觀察者,負(fù)責(zé)處理監(jiān)聽事件的對象
2 觀察的屬性
3 觀察的選項(xiàng)
4 上下文
*/
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"Person Name"];

需要實(shí)現(xiàn)下面這個(gè)方法:

// 所有的 kvo 監(jiān)聽到事件警医,都會調(diào)用此方法
/*
 1. 觀察的屬性
 2. 觀察的對象
 3. change 屬性變化字典(新/舊)
 4. 上下文亿胸,與監(jiān)聽的時(shí)候傳遞的一致
 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;

39.apple用什么方式實(shí)現(xiàn)對一個(gè)對象的KVO?

當(dāng)你觀察一個(gè)對象時(shí)法严,一個(gè)新的類會被動態(tài)創(chuàng)建损敷。這個(gè)類繼承自該對象的原本的類,并重寫了被觀察屬性的 setter 方法深啤。重寫的 setter 方法會負(fù)責(zé)在調(diào)用原 setter 方法之前和之后,通知所有觀察對象:值的更改路星。最后通過 isa 混寫(isa-swizzling) 把這個(gè)對象的 isa 指針 ( isa 指針告訴 Runtime 系統(tǒng)這個(gè)對象的類是什么 ) 指向這個(gè)新創(chuàng)建的子類溯街,對象就神奇的變成了新創(chuàng)建的子類的實(shí)例。

鍵值觀察通知依賴于 NSObject 的兩個(gè)方法: willChangeValueForKey: 和 didChangevlueForKey: 洋丐。在一個(gè)被觀察屬性發(fā)生改變之前呈昔, willChangeValueForKey: 一定會被調(diào)用,這就 會記錄舊的值友绝。而當(dāng)改變發(fā)生后堤尾, didChangeValueForKey: 會被調(diào)用,繼而 observeValueForKey:ofObject:change:context: 也會被調(diào)用迁客」Γ可以手動實(shí)現(xiàn)這些調(diào)用辞槐,但很少有人這么做。一般我們只在希望能控制回調(diào)的調(diào)用時(shí)機(jī)時(shí)才會這么做粘室。大部分情況下榄檬,改變通知會自動調(diào)用。

40.IBOutlet連出來的視圖屬性為什么可以被設(shè)置成weak?

因?yàn)榧热挥型怄溎敲匆晥D在xib或者storyboard中肯定存在衔统,視圖已經(jīng)對它有一個(gè)強(qiáng)引用了鹿榜。

不過這個(gè)回答漏了個(gè)重要知識,使用storyboard(xib不行)創(chuàng)建的vc锦爵,會有一個(gè)叫_topLevelObjectsToKeepAliveFromStoryboard的私有數(shù)組強(qiáng)引用所有top level的對象舱殿,所以這時(shí)即便outlet聲明成weak也沒關(guān)系

41.IB中User Defined Runtime Attributes如何使用?

它能夠通過KVC的方式配置一些你在interface builder 中不能配置的屬性险掀。當(dāng)你希望在IB中作盡可能多得事情沪袭,這個(gè)特性能夠幫助你編寫更加輕量級的viewcontroller

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市迷郑,隨后出現(xiàn)的幾起案子枝恋,更是在濱河造成了極大的恐慌,老刑警劉巖嗡害,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件焚碌,死亡現(xiàn)場離奇詭異,居然都是意外死亡霸妹,警方通過查閱死者的電腦和手機(jī)十电,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來叹螟,“玉大人鹃骂,你說我怎么就攤上這事“照溃” “怎么了畏线?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長良价。 經(jīng)常有香客問我寝殴,道長,這世上最難降的妖魔是什么明垢? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任蚣常,我火速辦了婚禮,結(jié)果婚禮上痊银,老公的妹妹穿的比我還像新娘抵蚊。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布贞绳。 她就那樣靜靜地躺著谷醉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪熔酷。 梳的紋絲不亂的頭發(fā)上孤紧,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天,我揣著相機(jī)與錄音拒秘,去河邊找鬼号显。 笑死,一個(gè)胖子當(dāng)著我的面吹牛躺酒,可吹牛的內(nèi)容都是我干的押蚤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼羹应,長吁一口氣:“原來是場噩夢啊……” “哼揽碘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起园匹,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤雳刺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后裸违,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掖桦,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年供汛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了枪汪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,133評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡怔昨,死狀恐怖雀久,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情趁舀,我是刑警寧澤赖捌,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站矮烹,受9級特大地震影響巡蘸,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜擂送,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望唯欣。 院中可真熱鬧嘹吨,春花似錦、人聲如沸境氢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至问芬,卻和暖如春悦析,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背此衅。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工强戴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人挡鞍。 一個(gè)月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓骑歹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親墨微。 傳聞我的和親對象是個(gè)殘疾皇子道媚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評論 2 355

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