《招聘一個(gè)靠譜的 iOS》—參考答案上

1. 風(fēng)格糾錯(cuò)題

修改完的代碼:

修改方法有很多種,現(xiàn)給出一種做示例:

// .h文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 修改完的代碼身辨,這是第一種修改方法丐谋,后面會(huì)給出第二種修改方法

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

下面對(duì)具體修改的地方,分兩部分做下介紹:硬傷部分優(yōu)化部分 煌珊。因?yàn)?strong>硬傷部分沒什么技術(shù)含量号俐,為了節(jié)省大家時(shí)間,放在后面講定庵,大神請(qǐng)直接看優(yōu)化部分吏饿。

優(yōu)化部分

  1. enum 建議使用 NS_ENUMNS_OPTIONS 宏來定義枚舉類型,參見官方的 Adopting Modern Objective-C 一文:
//定義一個(gè)枚舉
   typedef NS_ENUM(NSInteger, CYLSex) {
       CYLSexMan,
       CYLSexWoman
   };

(僅僅讓性別包含男和女可能并不嚴(yán)謹(jǐn)蔬浙,最嚴(yán)謹(jǐn)?shù)淖龇梢詤⒖?這里 猪落。)

  1. age 屬性的類型:應(yīng)避免使用基本類型,建議使用 Foundation 數(shù)據(jù)類型敛滋,對(duì)應(yīng)關(guān)系如下:
 int -> NSInteger
 unsigned -> NSUInteger
 float -> CGFloat
 動(dòng)畫時(shí)間 -> NSTimeInterval

同時(shí)考慮到 age 的特點(diǎn),應(yīng)使用 NSUInteger 兴革,而非 int 绎晃。 這樣做的是基于64-bit 適配考慮,詳情可參考出題者的博文《64-bit Tips》杂曲。

  1. 如果工程項(xiàng)目非常龐大庶艾,需要拆分成不同的模塊,可以在類擎勘、typedef宏命名的時(shí)候使用前綴咱揍。

  2. doLogIn方法不應(yīng)寫在該類中:

    雖然LogIn的命名不太清晰,但筆者猜測(cè)是login的意思棚饵, (勘誤:Login是名詞煤裙,LogIn 是動(dòng)詞掩完,都表示登陸的意思。見: Log in vs. login

    登錄操作屬于業(yè)務(wù)邏輯硼砰,觀察類名 UserModel 且蓬,以及屬性的命名方式,該類應(yīng)該是一個(gè) Model 而不是一個(gè)“ MVVM 模式下的 ViewModel ”:

無論是 MVC 模式還是 MVVM 模式题翰,業(yè)務(wù)邏輯都不應(yīng)當(dāng)寫在 Model 里:MVC 應(yīng)在 C恶阴,MVVM 應(yīng)在 VM。

(如果拋開命名規(guī)范豹障,假設(shè)該類真的是 MVVM 模式里的 ViewModel 冯事,那么 UserModel 這個(gè)類可能對(duì)應(yīng)的是用戶注冊(cè)頁面,如果有特殊的業(yè)務(wù)需求血公,比如: -logIn 對(duì)應(yīng)的應(yīng)當(dāng)是注冊(cè)并登錄的一個(gè) Button 昵仅,出現(xiàn) -logIn 方法也可能是合理的。)

  1. doLogIn 方法命名不規(guī)范:添加了多余的動(dòng)詞前綴坞笙。 請(qǐng)牢記:

如果方法表示讓對(duì)象執(zhí)行一個(gè)動(dòng)作岩饼,使用動(dòng)詞打頭來命名,注意不要使用 do薛夜,does 這種多余的關(guān)鍵字籍茧,動(dòng)詞本身的暗示就足夠了。

應(yīng)為 -logIn (注意: Login 是名詞梯澜, LogIn 是動(dòng)詞寞冯,都表示登陸。 見 Log in vs. login

  1. -(id)initUserModelWithUserName: (NSString*)name withAge:(int)age;方法中不要用 with 來連接兩個(gè)參數(shù): withAge: 應(yīng)當(dāng)換為age:晚伙,age: 已經(jīng)足以清晰說明參數(shù)的作用吮龄,也不建議用 andAge: :通常情況下,即使有類似 withA:withB: 的命名需求咆疗,也通常是使用withA:andB: 這種命名漓帚,用來表示方法執(zhí)行了兩個(gè)相對(duì)獨(dú)立的操作(從設(shè)計(jì)上來說,這時(shí)候也可以拆分成兩個(gè)獨(dú)立的方法)午磁,它不應(yīng)該用作闡明有多個(gè)參數(shù)尝抖,比如下面的:
//錯(cuò)誤,不要使用"and"來連接參數(shù)
- (int)runModalForDirectory:(NSString *)path andFile:(NSString *)name andTypes:(NSArray *)fileTypes;
//錯(cuò)誤迅皇,不要使用"and"來闡明有多個(gè)參數(shù)
- (instancetype)initWithName:(CGFloat)width andAge:(CGFloat)height;
//正確昧辽,使用"and"來表示兩個(gè)相對(duì)獨(dú)立的操作
- (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag;
  1. 由于字符串值可能會(huì)改變,所以要把相關(guān)屬性的“內(nèi)存管理語義”聲明為 copy 登颓。(原因在下文有詳細(xì)論述:用@property聲明的NSString(或NSArray搅荞,NSDictionary)經(jīng)常使用copy關(guān)鍵字,為什么?)
  2. “性別”(sex)屬性的:該類中只給出了一種“初始化方法” (initializer)用于設(shè)置“姓名”(Name)和“年齡”(Age)的初始值咕痛,那如何對(duì)“性別”(Sex)初始化痢甘?

Objective-C 有 designated 和 secondary 初始化方法的觀念。 designated 初始化方法是提供所有的參數(shù)暇检,secondary 初始化方法是一個(gè)或多個(gè)产阱,并且提供一個(gè)或者更多的默認(rèn)參數(shù)來調(diào)用 designated 初始化方法的初始化方法。舉例說明:

// .m文件
   // http://weibo.com/luohanchenyilong/
   // https://github.com/ChenYilong
   //

   @implementation CYLUser

   - (instancetype)initWithName:(NSString *)name
                            age:(NSUInteger)age
                            sex:(CYLSex)sex {
       if(self = [super init]) {
           _name = [name copy];
           _age = age;
           _sex = sex;
       }
       return self;
   }

   - (instancetype)initWithName:(NSString *)name
                            age:(NSUInteger)age {
       return [self initWithName:name age:age sex:nil];
   }

   @end

上面的代碼中initWithName:age:sex: 就是 designated 初始化方法块仆,另外的是 secondary 初始化方法构蹬。因?yàn)閮H僅是調(diào)用類實(shí)現(xiàn)的 designated 初始化方法。

因?yàn)槌鲱}者沒有給出 .m 文件悔据,所以有兩種猜測(cè):1:本來打算只設(shè)計(jì)一個(gè) designated 初始化方法庄敛,但漏掉了“性別”(sex)屬性。那么最終的修改代碼就是上文給出的第一種修改方法科汗。2:不打算初始時(shí)初始化“性別”(sex)屬性藻烤,打算后期再修改,如果是這種情況头滔,那么應(yīng)該把“性別”(sex)屬性設(shè)為 readwrite 屬性怖亭,最終給出的修改代碼應(yīng)該是:

// .h文件
   // http://weibo.com/luohanchenyilong/
   // https://github.com/ChenYilong
   // 第二種修改方法(基于第一種修改方法的基礎(chǔ)上)

   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, readwrite, assign) CYLSex sex;

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

   @end

.h 中暴露 designated 初始化方法,是為了方便子類化 (想了解更多坤检,請(qǐng)戳--》 《禪與 Objective-C 編程藝術(shù) (Zen and the Art of the Objective-C Craftsmanship 中文翻譯)》兴猩。)

  • 按照接口設(shè)計(jì)的慣例,如果設(shè)計(jì)了“初始化方法” (initializer)早歇,也應(yīng)當(dāng)搭配一個(gè)快捷構(gòu)造方法倾芝。而快捷構(gòu)造方法的返回值,建議為 instancetype箭跳,為保持一致性晨另,init 方法和快捷構(gòu)造方法的返回類型最好都用 instancetype。
  • 如果基于第一種修改方法:既然該類中已經(jīng)有一個(gè)“初始化方法” (initializer)谱姓,用于設(shè)置“姓名”(Name)借尿、“年齡”(Age)和“性別”(Sex)的初始值: 那么在設(shè)計(jì)對(duì)應(yīng) @property 時(shí)就應(yīng)該盡量使用不可變的對(duì)象:其三個(gè)屬性都應(yīng)該設(shè)為“只讀”立润。用初始化方法設(shè)置好屬性值之后钢悲,就不能再改變了。在本例中泽谨,仍需聲明屬性的“內(nèi)存管理語義”奶躯。于是可以把屬性的定義改成這樣
       @property (nonatomic, readonly, copy) NSString *name;
       @property (nonatomic, readonly, assign) NSUInteger age;
       @property (nonatomic, readonly, assign) CYLSex sex;
  1. initUserModelWithUserName 如果改為 initWithName 會(huì)更加簡潔帚桩,而且足夠清晰亿驾。
  2. UserModel 如果改為 User 會(huì)更加簡潔嘹黔,而且足夠清晰。
  3. UserSex如果改為Sex 會(huì)更加簡潔,而且足夠清晰儡蔓。
  4. 第二個(gè) @property 中 assign 和 nonatomic 調(diào)換位置郭蕉。 推薦按照下面的格式來定義屬性
@property (nonatomic, readwrite, copy) NSString *name;

屬性的參數(shù)應(yīng)該按照下面的順序排列: 原子性,讀寫 和 內(nèi)存管理喂江。 這樣做你的屬性更容易修改正確召锈,并且更好閱讀。這在《禪與Objective-C編程藝術(shù) >》里有介紹获询。而且習(xí)慣上修改某個(gè)屬性的修飾符時(shí)涨岁,一般從屬性名從右向左搜索需要修動(dòng)的修飾符。最可能從最右邊開始修改這些屬性的修飾符吉嚣,根據(jù)經(jīng)驗(yàn)這些修飾符被修改的可能性從高到底應(yīng)為:內(nèi)存管理 > 讀寫權(quán)限 >原子操作梢薪。

硬傷部分

  1. 在-和(void)之間應(yīng)該有一個(gè)空格
  2. enum 中駝峰命名法和下劃線命名法混用錯(cuò)誤:枚舉類型的命名規(guī)則和函數(shù)的命名規(guī)則相同:命名時(shí)使用駝峰命名法,勿使用下劃線命名法尝哆。
  3. enum 左括號(hào)前加一個(gè)空格秉撇,或者將左括號(hào)換到下一行
  4. enum 右括號(hào)后加一個(gè)空格
  5. UserModel :NSObject 應(yīng)為UserModel : NSObject,也就是:右側(cè)少了一個(gè)空格秋泄。
  6. @interface@property 屬性聲明中間應(yīng)當(dāng)間隔一行琐馆。
  7. 兩個(gè)方法定義之間不需要換行,有時(shí)為了區(qū)分方法的功能也可間隔一行恒序,但示例代碼中間隔了兩行瘦麸。
  8. -(id)initUserModelWithUserName: (NSString*)name withAge:(int)age; 方法中方法名與參數(shù)之間多了空格。而且 -(id) 之間少了空格奸焙。
  9. -(id)initUserModelWithUserName: (NSString*)name withAge:(int)age; 方法中方法名與參數(shù)之間多了空格:(NSString*)name 前多了空格瞎暑。
  10. -(id)initUserModelWithUserName: (NSString*)name withAge:(int)age; 方法中 (NSString*)name,應(yīng)為 (NSString *)name,少了空格与帆。
  11. doLogIn方法中的 LogIn 命名不清晰:筆者猜測(cè)是login的意思了赌,應(yīng)該是粗心手誤造成的。

(勘誤: Login 是名詞玄糟, LogIn 是動(dòng)詞勿她,都表示登陸的意思。見: Log in vs. login

2. 什么情況使用 weak 關(guān)鍵字阵翎,相比 assign 有什么不同逢并?

什么情況使用 weak 關(guān)鍵字?
1郭卫、在 ARC 中,在有可能出現(xiàn)循環(huán)引用的時(shí)候,往往要通過讓其中一端使用 weak 來解決,比如: delegate 代理屬性
2砍聊、自身已經(jīng)對(duì)它進(jìn)行一次強(qiáng)引用,沒有必要再強(qiáng)引用一次,此時(shí)也會(huì)使用 weak,自定義 IBOutlet 控件屬性一般也使用 weak;當(dāng)然贰军,也可以使用strong玻蝌。在下文也有論述:《IBOutlet連出來的視圖屬性為什么可以被設(shè)置成weak?》

不同點(diǎn):
1、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 等)的簡單賦值操作球化。
2、assign 可以用非 OC 對(duì)象,而 weak 必須用于 OC 對(duì)象

3. 怎么用 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

block 使用 copy 是從 MRC 遺留下來的“傳統(tǒng)”,在 MRC 中,方法內(nèi)部的 block 是在棧區(qū)的,使用 copy 可以把它放到堆區(qū).在 ARC 中寫不寫都行:對(duì)于 block 使用 copy 還是 strong 效果是一樣的,但寫上 copy 也無傷大雅斋竞,還能時(shí)刻提醒我們:編譯器自動(dòng)對(duì) block 進(jìn)行了 copy 操作倔约。如果不寫 copy ,該類的調(diào)用者有可能會(huì)忘記或者根本不知道“編譯器會(huì)自動(dòng)對(duì) block 進(jìn)行了 copy 操作”坝初,他們有可能會(huì)在調(diào)用之前自行拷貝屬性值浸剩。這種操作多余而低效。你也許會(huì)感覺我這種做法有些怪異鳄袍,不需要寫依然寫绢要。如果你這樣想,其實(shí)是你“日用而不知”拗小,你平時(shí)開發(fā)中是經(jīng)常在用我說的這種做法的重罪,比如下面的屬性不寫copy也行,但是你會(huì)選擇寫還是不寫呢哀九?

下面做下解釋: copy 此特質(zhì)所表達(dá)的所屬關(guān)系與 strong 類似剿配。然而設(shè)置方法并不保留新值,而是將其“拷貝” (copy)阅束。 當(dāng)屬性類型為 NSString 時(shí)呼胚,經(jīng)常用此特質(zhì)來保護(hù)其封裝性,因?yàn)閭鬟f給設(shè)置方法的新值有可能指向一個(gè) NSMutableString 類的實(shí)例息裸。這個(gè)類是 NSString 的子類蝇更,表示一種可修改其值的字符串琢融,此時(shí)若是不拷貝字符串,那么設(shè)置完屬性之后簿寂,字符串的值就可能會(huì)在對(duì)象不知情的情況下遭人更改。所以宿亡,這時(shí)就要拷貝一份“不可變” (immutable)的字符串常遂,確保對(duì)象中的字符串值不會(huì)無意間變動(dòng)。只要實(shí)現(xiàn)屬性所用的對(duì)象是“可變的” (mutable)挽荠,就應(yīng)該在設(shè)置新屬性值時(shí)拷貝一份克胳。

@property 聲明 NSString、NSArray圈匆、NSDictionary 經(jīng)常使用 copy 關(guān)鍵字漠另,是因?yàn)樗麄冇袑?duì)應(yīng)的可變類型:NSMutableString、NSMutableArray跃赚、NSMutableDictionary笆搓,他們之間可能進(jìn)行賦值操作,為確保對(duì)象中的字符串值不會(huì)無意間變動(dòng)纬傲,應(yīng)該在設(shè)置新屬性值時(shí)拷貝一份满败。

該問題在下文中也有論述:用@property聲明的NSString(或NSArray,NSDictionary)經(jīng)常使用copy關(guān)鍵字叹括,為什么算墨?如果改用strong關(guān)鍵字,可能造成什么問題汁雷?

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

兩個(gè)問題:1净嘀、添加,刪除,修改數(shù)組內(nèi)的元素的時(shí)候,程序會(huì)因?yàn)檎也坏綄?duì)應(yīng)的方法而崩潰.因?yàn)?copy 就是復(fù)制一個(gè)不可變 NSArray 的對(duì)象;2侠讯、使用了 atomic 屬性會(huì)嚴(yán)重影響性能 挖藏;

第1條的相關(guān)原因在下文中有論述《用@property聲明的NSString(或NSArray,NSDictionary)經(jīng)常使用 copy 關(guān)鍵字厢漩,為什么熬苍?如果改用strong關(guān)鍵字,可能造成什么問題袁翁?》 以及上文《怎么用 copy 關(guān)鍵字柴底?》也有論述。

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

// .h文件
@property (nonatomic, copy) NSMutableArray *mutableArray;
// .m文件
NSMutableArray *array = [NSMutableArray arrayWithObjects:@1,@2,nil];
self.mutableArray = array;
[self.mutableArray removeObjectAtIndex:0];

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

-[__NSArrayI removeObjectAtIndex:]: unrecognized selector sent to instance 0x7fcd1bc30460

第2條原因粱胜,如下:

該屬性使用了自旋鎖柄驻,會(huì)在創(chuàng)建時(shí)生成一些額外的代碼用于幫助編寫多線程程序,這會(huì)帶來性能問題焙压,通過聲明 nonatomic 可以節(jié)省這些雖然很小但是不必要額外開銷鸿脓。

在默認(rèn)情況下抑钟,由編譯器所合成的方法會(huì)通過鎖定機(jī)制確保其原子性(atomicity)。如果屬性具備 nonatomic 特質(zhì)野哭,則不使用自旋鎖在塔。請(qǐng)注意,盡管沒有名為“atomic”的特質(zhì)(如果某屬性不具備 nonatomic 特質(zhì)拨黔,那它就是“原子的”(atomic))蛔溃。

在iOS開發(fā)中,你會(huì)發(fā)現(xiàn)篱蝇,幾乎所有屬性都聲明為 nonatomic贺待。

一般情況下并不要求屬性必須是“原子的”,因?yàn)檫@并不能保證“線程安全” ( thread safety)零截,若要實(shí)現(xiàn)“線程安全”的操作麸塞,還需采用更為深層的鎖定機(jī)制才行。例如涧衙,一個(gè)線程在連續(xù)多次讀取某屬性值的過程中有別的線程在同時(shí)改寫該值哪工,那么即便將屬性聲明為 atomic,也還是會(huì)讀到不同的屬性值弧哎。

因此正勒,開發(fā)iOS程序時(shí)一般都會(huì)使用 nonatomic 屬性。但是在開發(fā) Mac OS X 程序時(shí)傻铣,使用 atomic 屬性通常都不會(huì)有性能瓶頸章贞。

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

若想令自己所寫的對(duì)象具有拷貝功能鸭限,則需實(shí)現(xiàn) NSCopying 協(xié)議。如果自定義的對(duì)象分為可變版本與不可變版本两踏,那么就要同時(shí)實(shí)現(xiàn) NSCopyingNSMutableCopying 協(xié)議败京。

具體步驟:

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

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

以第一題的代碼為例:

   // .h文件
   // http://weibo.com/luohanchenyilong/
   // https://github.com/ChenYilong
   // 修改完的代碼

   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)目中帕识,不可能這么簡單泛粹,遇到更復(fù)雜一點(diǎn),比如類對(duì)象中的數(shù)據(jù)結(jié)構(gòu)可能并未在初始化方法中設(shè)置好肮疗,需要另行設(shè)置晶姊。舉個(gè)例子,假如 CYLUser 中含有一個(gè)數(shù)組伪货,與其他 CYLUser 對(duì)象建立或解除朋友關(guān)系的那些方法都需要操作這個(gè)數(shù)組们衙。那么在這種情況下钾怔,你得把這個(gè)包含朋友對(duì)象的數(shù)組也一并拷貝過來。下面列出了實(shí)現(xiàn)此功能所需的全部代碼:

// .h文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 以第一題《風(fēng)格糾錯(cuò)題》里的代碼為例

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;
- (void)addFriend:(CYLUser *)user;
- (void)removeFriend:(CYLUser *)user;

@end

// .m文件

// .m文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
//

@implementation CYLUser {
   NSMutableSet *_friends;
}

- (void)setName:(NSString *)name {
   _name = [name copy];
}

- (instancetype)initWithName:(NSString *)name
                        age:(NSUInteger)age
                        sex:(CYLSex)sex {
   if(self = [super init]) {
       _name = [name copy];
       _age = age;
       _sex = sex;
       _friends = [[NSMutableSet alloc] init];
   }
   return self;
}

- (void)addFriend:(CYLUser *)user {
   [_friends addObject:user];
}

- (void)removeFriend:(CYLUser *)user {
   [_friends removeObject:user];
}

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

- (id)deepCopy {
   CYLUser *copy = [[[self class] alloc]
                    initWithName:_name
                    age:_age
                    sex:_sex];
   copy->_friends = [[NSMutableSet alloc] initWithSet:_friends
                                            copyItems:YES];
   return copy;
}

@end

以上做法能滿足基本的需求蒙挑,但是也有缺陷:

如果你所寫的對(duì)象需要深拷貝宗侦,那么可考慮新增一個(gè)專門執(zhí)行深拷貝的方法。

【注:深淺拷貝的概念忆蚀,在下文中有介紹矾利,詳見下文的:用@property聲明的 NSString(或NSArray,NSDictionary)經(jīng)常使用 copy 關(guān)鍵字蜓谋,為什么?如果改用 strong 關(guān)鍵字炭分,可能造成什么問題桃焕?

在例子中,存放朋友對(duì)象的 set 是用 “copyWithZone:” 方法來拷貝的捧毛,這種淺拷貝方式不會(huì)逐個(gè)復(fù)制 set 中的元素观堂。若需要深拷貝的話,則可像下面這樣呀忧,編寫一個(gè)專供深拷貝所用的方法:

- (id)deepCopy {
   CYLUser *copy = [[[self class] alloc]
                    initWithName:_name
                    age:_age
                    sex:_sex];
   copy->_friends = [[NSMutableSet alloc] initWithSet:_friends
                                            copyItems:YES];
   return copy;
}

至于如何重寫帶 copy 關(guān)鍵字的 setter這個(gè)問題师痕,

如果拋開本例來回答的話,如下:

- (void)setName:(NSString *)name {
    //[_name release];
    _name = [name copy];
}

不過也有爭議而账,有人說“蘋果如果像下面這樣干胰坟,是不是效率會(huì)高一些?”

- (void)setName:(NSString *)name {
   if (_name != name) {
       //[_name release];//MRC
       _name = [name copy];
   }
}

你可能會(huì)說:

之所以在這里做if判斷 這個(gè)操作:是因?yàn)橐粋€(gè) if 可能避免一個(gè)耗時(shí)的copy泞辐,還是很劃算的笔横。 (在剛剛講的:《如何讓自己的類用 copy 修飾符?》里的那種復(fù)雜的copy咐吼,我們可以稱之為 “耗時(shí)的copy”吹缔,但是對(duì) NSString 的 copy 還稱不上。)

但是你有沒有考慮過代價(jià):

你每次調(diào)用 setX: 都會(huì)做 if 判斷锯茄,這會(huì)讓 setX: 變慢厢塘,如果你在 setX:寫了一串復(fù)雜的 if+elseif+elseif+... 判斷,將會(huì)更慢肌幽。

要回答“哪個(gè)效率會(huì)高一些晚碾?”這個(gè)問題,不能脫離實(shí)際開發(fā)喂急,就算 copy 操作十分耗時(shí)迄薄,if 判斷也不見得一定會(huì)更快,除非你把一個(gè)“ @property他當(dāng)前的值 ”賦給了他自己煮岁,代碼看起來就像:

[a setX:x1];
[a setX:x1];    //你確定你要這么干讥蔽?與其在setter中判斷涣易,為什么不把代碼寫好?

或者

[a setX:[a x]];   //隊(duì)友咆哮道:你在干嘛冶伞?P轮ⅰ!

不要在 setter 里進(jìn)行像 if(_obj != newObj) 這樣的判斷响禽。(該觀點(diǎn)參考鏈接: How To Write Cocoa Object Setters: Principle 3: Only Optimize After You Measure

什么情況會(huì)在 copy setter 里做 if 判斷徒爹? 例如,車速可能就有最高速的限制芋类,車速也不可能出現(xiàn)負(fù)值隆嗅,如果車子的最高速為300,則 setter 的方法就要改寫成這樣:

-(void)setSpeed:(int)speed {
    if(speed < 0) speed = 0;
    if(speed > 300) speed = 300;
   _speed = speed;
}

回到這個(gè)題目侯繁,如果單單就上文的代碼而言胖喳,我們不需要也不能重寫 name 的 setter :由于是 name 是只讀屬性,所以編譯器不會(huì)為其創(chuàng)建對(duì)應(yīng)的“設(shè)置方法”贮竟,用初始化方法設(shè)置好屬性值之后丽焊,就不能再改變了。( 在本例中咕别,之所以還要聲明屬性的“內(nèi)存管理語義”--copy技健,是因?yàn)椋喝绻粚?copy,該類的調(diào)用者就不知道初始化方法里會(huì)拷貝這些屬性惰拱,他們有可能會(huì)在調(diào)用初始化方法之前自行拷貝屬性值雌贱。這種操作多余而低效)。

那如何確保 name 被 copy偿短?在初始化方法(initializer)中做:

   - (instancetype)initWithName:(NSString *)name 
                             age:(NSUInteger)age 
                             sex:(CYLSex)sex {
        if(self = [super init]) {
            _name = [name copy];
            _age = age;
            _sex = sex;
            _friends = [[NSMutableSet alloc] init];
        }
        return self;
   }

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

@property 的本質(zhì)是什么导街?

@property = 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)來訪問泽论。其中,“獲取方法” (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 這門語言才能根據(jù)名稱自動(dòng)創(chuàng)建出存取方法古话。其實(shí)也可以把屬性當(dāng)做一種關(guān)鍵字雏吭,其表示:

編譯器會(huì)自動(dòng)寫出一套存取方法,用以訪問給定類型中具有給定名稱的變量陪踩。 所以你也可以這么說:

@property = getter + setter;

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

@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

更新

property在runtime中是objc_property_t定義如下:

typedef struct objc_property *objc_property_t;

objc_property是一個(gè)結(jié)構(gòu)體杖们,包括name和attributes,定義如下:

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)存語義和對(duì)應(yīng)的實(shí)例變量栅螟。

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

其中T就代表類型篱竭,可參閱Type Encodings力图,C就代表Copy,N代表nonatomic掺逼,V就代表對(duì)應(yīng)的實(shí)例變量吃媒。

ivar、getter吕喘、setter 是如何生成并添加到這個(gè)類中的?

“自動(dòng)合成”( autosynthesis)

完成屬性定義后赘那,編譯器會(huì)自動(dòng)編寫訪問這些屬性所需的方法,此過程叫做“自動(dòng)合成”(autosynthesis)氯质。需要強(qiáng)調(diào)的是募舟,這個(gè)過程由編譯 器在編譯期執(zhí)行,所以編輯器里看不到這些“合成方法”(synthesized method)的源代碼闻察。除了生成方法代碼 getter拱礁、setter 之外,編譯器還要自動(dòng)向類中添加適當(dāng)類型的實(shí)例變量辕漂,并且在屬性名前面加下劃線呢灶,以此作為實(shí)例變量的名字。在前例中钉嘹,會(huì)生成兩個(gè)實(shí)例變量鸯乃,其名稱分別為 _firstName_lastName。也可以在類的實(shí)現(xiàn)代碼里通過@synthesize 語法來指定實(shí)例變量的名字.

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

我為了搞清屬性是怎么實(shí)現(xiàn)的,曾經(jīng)反編譯過相關(guān)的代碼,他大致生成了五個(gè)東西

  1. OBJC_IVAR_$類名$屬性名稱 :該屬性的“偏移量” (offset)跋涣,這個(gè)偏移量是“硬編碼” (hardcode)缨睡,表示該變量距離存放對(duì)象的內(nèi)存區(qū)域的起始地址有多遠(yuǎn)鸟悴。
  2. setter 與 getter 方法對(duì)應(yīng)的實(shí)現(xiàn)函數(shù)
  3. ivar_list :成員變量列表
  4. method_list :方法列表
  5. prop_list :屬性列表

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

7. @protocol 和 category 中如何使用 @property

  1. 在 protocol 中使用 property 只會(huì)生成 setter 和 getter 方法聲明,我們使用屬性的目的,是希望遵守我協(xié)議的對(duì)象能實(shí)現(xiàn)該屬性

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

  3. objc_setAssociatedObject

  4. objc_getAssociatedObject

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

要實(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友浸。

(注:在下文的《使用runtime Associate方法關(guān)聯(lián)的對(duì)象,需要在主對(duì)象dealloc的時(shí)候釋放么偏窝?》里給出的“對(duì)象的內(nèi)存銷毀時(shí)間表”也提到__weak引用的解除時(shí)間收恢。)

先看下 runtime 里源碼的實(shí)現(xiàn):

/**
* The internal structure stored in the weak references table. 
* It maintains and stores
* a hash set of weak references pointing to an object.
* If out_of_line==0, the set is instead a small inline array.
*/
#define WEAK_INLINE_COUNT 4
struct weak_entry_t {
   DisguisedPtr<objc_object> referent;
   union {
       struct {
           weak_referrer_t *referrers;
           uintptr_t        out_of_line : 1;
           uintptr_t        num_refs : PTR_MINUS_1;
           uintptr_t        mask;
           uintptr_t        max_hash_displacement;
       };
       struct {
           // out_of_line=0 is LSB of one of these (don't care which)
           weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
       };
   };
};

/**
* The global weak references table. Stores object ids as keys,
* and weak_entry_t structs as their values.
*/
struct weak_table_t {
   weak_entry_t *weak_entries;
   size_t    num_entries;
   uintptr_t mask;
   uintptr_t max_hash_displacement;
};

具體完整實(shí)現(xiàn)參照 objc/objc-weak.h

我們可以設(shè)計(jì)一個(gè)函數(shù)(偽代碼)來表示上述機(jī)制:

objc_storeWeak(&a, b)函數(shù):

objc_storeWeak函數(shù)把第二個(gè)參數(shù)--賦值對(duì)象(b)的內(nèi)存地址作為鍵值key祭往,將第一個(gè)參數(shù)--weak修飾的屬性變量(a)的內(nèi)存地址(&a)作為value伦意,注冊(cè)到 weak 表中。如果第二個(gè)參數(shù)(b)為0(nil)硼补,那么把變量(a)的內(nèi)存地址(&a)從weak表中刪除驮肉,

你可以把objc_storeWeak(&a, b)理解為:objc_storeWeak(value, key),并且當(dāng)key變nil已骇,將value置nil离钝。

在b非nil時(shí),a和b指向同一個(gè)內(nèi)存地址疾捍,在b變nil時(shí)奈辰,a變nil。此時(shí)向a發(fā)送消息不會(huì)崩潰:在Objective-C中向nil發(fā)送消息是安全的乱豆。

而如果a是由 assign 修飾的奖恰,則: 在 b 非 nil 時(shí),a 和 b 指向同一個(gè)內(nèi)存地址,在 b 變 nil 時(shí)瑟啃,a 還是指向該內(nèi)存地址论泛,變野指針。此時(shí)向 a 發(fā)送消息極易崩潰蛹屿。

下面我們將基于objc_storeWeak(&a, b)函數(shù)屁奏,使用偽代碼模擬“runtime如何實(shí)現(xiàn)weak屬性”:

// 使用偽代碼模擬:runtime如何實(shí)現(xiàn)weak屬性
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong

 id obj1;
 objc_initWeak(&obj1, obj);
/*obj引用計(jì)數(shù)變?yōu)?,變量作用域結(jié)束*/
 objc_destroyWeak(&obj1);

下面對(duì)用到的兩個(gè)方法objc_initWeakobjc_destroyWeak做下解釋:

總體說來错负,作用是: 通過objc_initWeak函數(shù)初始化“附有weak修飾符的變量(obj1)”坟瓢,在變量作用域結(jié)束時(shí)通過objc_destoryWeak函數(shù)釋放該變量(obj1)。

下面分別介紹下方法的內(nèi)部實(shí)現(xiàn):

objc_initWeak函數(shù)的實(shí)現(xiàn)是這樣的:在將“附有weak修飾符的變量(obj1)”初始化為0(nil)后犹撒,會(huì)將“賦值對(duì)象”(obj)作為參數(shù)折联,調(diào)用objc_storeWeak函數(shù)。

obj1 = 0识颊;
obj_storeWeak(&obj1, obj);

也就是說:

weak 修飾的指針默認(rèn)值是 nil (在Objective-C中向nil發(fā)送消息是安全的)

然后obj_destroyWeak函數(shù)將0(nil)作為參數(shù)诚镰,調(diào)用objc_storeWeak函數(shù)。

objc_storeWeak(&obj1, 0);

前面的源代碼與下列源代碼相同祥款。

// 使用偽代碼模擬:runtime如何實(shí)現(xiàn)weak屬性
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong

id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
/* ... obj的引用計(jì)數(shù)變?yōu)?清笨,被置nil ... */
objc_storeWeak(&obj1, 0);

objc_storeWeak 函數(shù)把第二個(gè)參數(shù)--賦值對(duì)象(obj)的內(nèi)存地址作為鍵值,將第一個(gè)參數(shù)--weak修飾的屬性變量(obj1)的內(nèi)存地址注冊(cè)到 weak 表中刃跛。如果第二個(gè)參數(shù)(obj)為0(nil)抠艾,那么把變量(obj1)的地址從 weak 表中刪除,在后面的相關(guān)一題會(huì)詳解奠伪。

使用偽代碼是為了方便理解跌帐,下面我們“真槍實(shí)彈”地實(shí)現(xiàn)下:

如何讓不使用weak修飾的@property首懈,擁有weak的效果绊率。

我們從setter方法入手:

(注意以下的 cyl_runAtDealloc 方法實(shí)現(xiàn)僅僅用于模擬原理,如果想用于項(xiàng)目中究履,還需要考慮更復(fù)雜的場(chǎng)景滤否,想在實(shí)際項(xiàng)目使用的話,可以使用我寫的一個(gè)小庫最仑,可以使用 CocoaPods 在項(xiàng)目中使用: CYLDeallocBlockExecutor

- (void)setObject:(NSObject *)object
{
   objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN);
   [object cyl_runAtDealloc:^{
       _object = nil;
   }];
}

也就是有兩個(gè)步驟:

  1. 在setter方法中做如下設(shè)置:
objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN);
  1. 在屬性所指的對(duì)象遭到摧毀時(shí)藐俺,屬性值也會(huì)清空(nil out)。做到這點(diǎn)泥彤,同樣要借助 runtime:
//要銷毀的目標(biāo)對(duì)象
id objectToBeDeallocated;
//可以理解為一個(gè)“事件”:當(dāng)上面的目標(biāo)對(duì)象銷毀時(shí)欲芹,同時(shí)要發(fā)生的“事件”。
id objectWeWantToBeReleasedWhenThatHappens;
objc_setAssociatedObject(objectToBeDeallocted,
                        someUniqueKey,
                        objectWeWantToBeReleasedWhenThatHappens,
                        OBJC_ASSOCIATION_RETAIN);

知道了思路吟吝,我們就開始實(shí)現(xiàn) cyl_runAtDealloc 方法菱父,實(shí)現(xiàn)過程分兩部分:

第一部分:創(chuàng)建一個(gè)類,可以理解為一個(gè)“事件”:當(dāng)目標(biāo)對(duì)象銷毀時(shí),同時(shí)要發(fā)生的“事件”浙宜。借助 block 執(zhí)行“事件”官辽。

// .h文件

// .h文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 這個(gè)類,可以理解為一個(gè)“事件”:當(dāng)目標(biāo)對(duì)象銷毀時(shí)粟瞬,同時(shí)要發(fā)生的“事件”同仆。借助block執(zhí)行“事件”。

typedef void (^voidBlock)(void);

@interface CYLBlockExecutor : NSObject

- (id)initWithBlock:(voidBlock)block;

@end

// .m文件

// .m文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 這個(gè)類裙品,可以理解為一個(gè)“事件”:當(dāng)目標(biāo)對(duì)象銷毀時(shí)俗批,同時(shí)要發(fā)生的“事件”。借助block執(zhí)行“事件”市怎。

#import "CYLBlockExecutor.h"

@interface CYLBlockExecutor() {
   voidBlock _block;
}
@implementation CYLBlockExecutor

- (id)initWithBlock:(voidBlock)aBlock
{
   self = [super init];
   
   if (self) {
       _block = [aBlock copy];
   }
   
   return self;
}

- (void)dealloc
{
   _block ? _block() : nil;
}

@end

第二部分:核心代碼:利用runtime實(shí)現(xiàn)cyl_runAtDealloc方法

// CYLNSObject+RunAtDealloc.h文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 利用runtime實(shí)現(xiàn)cyl_runAtDealloc方法

#import "CYLBlockExecutor.h"

const void *runAtDeallocBlockKey = &runAtDeallocBlockKey;

@interface NSObject (CYLRunAtDealloc)

- (void)cyl_runAtDealloc:(voidBlock)block;

@end


// CYLNSObject+RunAtDealloc.m文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 利用runtime實(shí)現(xiàn)cyl_runAtDealloc方法

#import "CYLNSObject+RunAtDealloc.h"
#import "CYLBlockExecutor.h"

@implementation NSObject (CYLRunAtDealloc)

- (void)cyl_runAtDealloc:(voidBlock)block
{
   if (block) {
       CYLBlockExecutor *executor = [[CYLBlockExecutor alloc] initWithBlock:block];
       
       objc_setAssociatedObject(self,
                                runAtDeallocBlockKey,
                                executor,
                                OBJC_ASSOCIATION_RETAIN);
   }
}

@end

使用方法: 導(dǎo)入

   #import "CYLNSObject+RunAtDealloc.h"

然后就可以使用了:

NSObject *foo = [[NSObject alloc] init];

[foo cyl_runAtDealloc:^{
   NSLog(@"正在釋放foo!");
}];

如果對(duì) cyl_runAtDealloc 的實(shí)現(xiàn)原理有興趣扶镀,可以看下我寫的一個(gè)小庫,可以使用 CocoaPods 在項(xiàng)目中使用: CYLDeallocBlockExecutor

參考博文: Fun With the Objective-C Runtime: Run Code at Deallocation of Any Object

9. @property中有哪些屬性關(guān)鍵字焰轻?/ @property 后面可以有哪些修飾符臭觉?

屬性可以擁有的特質(zhì)分為四類:

  1. 原子性--- nonatomic 特質(zhì)

    在默認(rèn)情況下,由編譯器合成的方法會(huì)通過鎖定機(jī)制確保其原子性(atomicity)辱志。如果屬性具備 nonatomic 特質(zhì)蝠筑,則不使用自旋鎖。請(qǐng)注意揩懒,盡管沒有名為“atomic”的特質(zhì)(如果某屬性不具備 nonatomic 特質(zhì)什乙,那它就是“原子的” ( atomic) ),但是仍然可以在屬性特質(zhì)中寫明這一點(diǎn)已球,編譯器不會(huì)報(bào)錯(cuò)臣镣。若是自己定義存取方法,那么就應(yīng)該遵從與屬性特質(zhì)相符的原子性智亮。

  2. 讀/寫權(quán)限---readwrite(讀寫)忆某、readonly (只讀)

  3. 內(nèi)存管理語義---assignstrong阔蛉、 weak弃舒、unsafe_unretainedcopy

  4. 方法名---getter=<name> 状原、setter=<name>

getter=<name>的樣式:

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

setter=這種不常用聋呢,也不推薦使用。故不在這里給出寫法颠区。)

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í)你就可以使用下面的方式來避免編譯器報(bào)錯(cuò):

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

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

@property(nonatomic, readwrite, copy, null_resettable) NSString *initBy;
- (NSString *)initBy __attribute__((objc_method_family(none)));
  1. 不常用的:nonnull,null_resettable,nullable

注意:很多人會(huì)認(rèn)為如果屬性具備 nonatomic 特質(zhì)稚字,則不使用 “同步鎖”。其實(shí)在屬性設(shè)置方法中使用的是自旋鎖厦酬,自旋鎖相關(guān)代碼如下:

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
   if (offset == 0) {
       object_setClass(self, newValue);
       return;
   }

   id oldValue;
   id *slot = (id*) ((char*)self + offset);

   if (copy) {
       newValue = [newValue copyWithZone:nil];
   } else if (mutableCopy) {
       newValue = [newValue mutableCopyWithZone:nil];
   } else {
       if (*slot == newValue) return;
       newValue = objc_retain(newValue);
   }

   if (!atomic) {
       oldValue = *slot;
       *slot = newValue;
   } else {
       spinlock_t& slotlock = PropertyLocks[slot];
       slotlock.lock();
       oldValue = *slot;
       *slot = newValue;        
       slotlock.unlock();
   }

   objc_release(oldValue);
}

void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 
{
   bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
   bool mutableCopy = (shouldCopy == MUTABLE_COPY);
   reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}

10. weak屬性需要在dealloc中置nil么胆描?

不需要。

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

即便是編譯器不幫我們做這些昌讲,weak也不需要在 dealloc 中置nil:

正如上文的:runtime 如何實(shí)現(xiàn) weak 屬性 中提到的:

我們模擬下 weak 的 setter 方法,應(yīng)該如下:

- (void)setObject:(NSObject *)object
{
   objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN);
   [object cyl_runAtDealloc:^{
       _object = nil;
   }];
}

如果對(duì) cyl_runAtDealloc 的實(shí)現(xiàn)原理有興趣减噪,可以看下我寫的一個(gè)小庫短绸,可以使用 CocoaPods 在項(xiàng)目中使用: CYLDeallocBlockExecutor

也即:

在屬性所指的對(duì)象遭到摧毀時(shí),屬性值也會(huì)清空(nil out)筹裕。

11. @synthesize和@dynamic分別有什么作用醋闭?

  1. @property有兩個(gè)對(duì)應(yīng)的詞,一個(gè)是 @synthesize朝卒,一個(gè)是 @dynamic证逻。如果 @synthesize和 @dynamic都沒寫,那么默認(rèn)的就是@syntheszie var = _var;
  2. @synthesize 的語義是如果你沒有手動(dòng)實(shí)現(xiàn) setter 方法和 getter 方法抗斤,那么編譯器會(huì)自動(dòng)為你加上這兩個(gè)方法囚企。
  3. @dynamic 告訴編譯器:屬性的 setter 與 getter 方法由用戶自己實(shí)現(xiàn),不自動(dòng)生成瑞眼。(當(dāng)然對(duì)于 readonly 的屬性只需提供 getter 即可)龙宏。假如一個(gè)屬性被聲明為 @dynamic var,然后你沒有提供 @setter方法和 @getter 方法负拟,編譯的時(shí)候沒問題烦衣,但是當(dāng)程序運(yùn)行到 instance.var = someVar歹河,由于缺 setter 方法會(huì)導(dǎo)致程序崩潰掩浙;或者當(dāng)運(yùn)行到 someVar = var 時(shí),由于缺 getter 方法同樣會(huì)導(dǎo)致崩潰秸歧。編譯時(shí)沒問題厨姚,運(yùn)行時(shí)才執(zhí)行相應(yīng)的方法,這就是所謂的動(dòng)態(tài)綁定键菱。

12. ARC下谬墙,不顯式指定任何屬性關(guān)鍵字時(shí)今布,默認(rèn)的關(guān)鍵字都有哪些?

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

atomic,readwrite,assign 2. 對(duì)于普通的 Objective-C 對(duì)象

atomic,readwrite,strong

參考鏈接:

  1. Objective-C ARC: strong vs retain and weak vs assign

  2. Variable property attributes or Modifiers in iOS

13. 用@property聲明的NSString(或NSArray拭抬,NSDictionary)經(jīng)常使用copy關(guān)鍵字部默,為什么?如果改用strong關(guān)鍵字造虎,可能造成什么問題傅蹂?

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

copy 此特質(zhì)所表達(dá)的所屬關(guān)系與 strong 類似。然而設(shè)置方法并不保留新值算凿,而是將其“拷貝” (copy)份蝴。 當(dāng)屬性類型為 NSString 時(shí),經(jīng)常用此特質(zhì)來保護(hù)其封裝性氓轰,因?yàn)閭鬟f給設(shè)置方法的新值有可能指向一個(gè) NSMutableString 類的實(shí)例婚夫。這個(gè)類是 NSString 的子類,表示一種可修改其值的字符串署鸡,此時(shí)若是不拷貝字符串案糙,那么設(shè)置完屬性之后,字符串的值就可能會(huì)在對(duì)象不知情的情況下遭人更改靴庆。所以侍筛,這時(shí)就要拷貝一份“不可變” (immutable)的字符串,確保對(duì)象中的字符串值不會(huì)無意間變動(dòng)撒穷。只要實(shí)現(xiàn)屬性所用的對(duì)象是“可變的” (mutable)匣椰,就應(yīng)該在設(shè)置新屬性值時(shí)拷貝一份。

舉例說明:

定義一個(gè)以 strong 修飾的 array:

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

然后進(jìn)行下面的操作:

 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
)

(詳見倉庫內(nèi)附錄的 Demo端礼。)

為了理解這種做法禽笑,首先要知道,兩種情況:

  1. 對(duì)非集合類對(duì)象的 copy 與 mutableCopy 操作蛤奥;
  2. 對(duì)集合類對(duì)象的 copy 與 mutableCopy 操作佳镜。

1. 對(duì)非集合類對(duì)象的copy操作:

在非集合類對(duì)象中:對(duì) immutable 對(duì)象進(jìn)行 copy 操作,是指針復(fù)制凡桥,mutableCopy 操作時(shí)內(nèi)容復(fù)制蟀伸;對(duì) mutable 對(duì)象進(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)存缅刽,會(huì)發(fā)現(xiàn) string啊掏、stringCopy 內(nèi)存地址都不一樣,說明此時(shí)都是做內(nèi)容拷貝衰猛、深拷貝迟蜜。即使你進(jìn)行如下操作:

[string appendString:@"origion!"]

stringCopy 的值也不會(huì)因此改變,但是如果不使用 copy啡省,stringCopy 的值就會(huì)被改變娜睛。 集合類對(duì)象以此類推髓霞。 所以,

用 @property 聲明 NSString畦戒、NSArray方库、NSDictionary 經(jīng)常使用 copy 關(guān)鍵字,是因?yàn)樗麄冇袑?duì)應(yīng)的可變類型:NSMutableString障斋、NSMutableArray薪捍、NSMutableDictionary,他們之間可能進(jìn)行賦值操作配喳,為確保對(duì)象中的字符串值不會(huì)無意間變動(dòng)酪穿,應(yīng)該在設(shè)置新屬性值時(shí)拷貝一份。

2晴裹、集合類對(duì)象的copy與mutableCopy

集合類對(duì)象是指 NSArray被济、NSDictionary、NSSet ... 之類的對(duì)象涧团。下面先看集合類immutable對(duì)象使用 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è)對(duì)象元媚,array 集合內(nèi)部的元素仍然是指針拷貝。這和上面的非集合 immutable 對(duì)象的拷貝還是挺相似的苗沧,那么mutable對(duì)象的拷貝會(huì)不會(huì)類似呢刊棕?我們繼續(xù)往下,看 mutable 對(duì)象拷貝的例子:

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 都對(duì) array 進(jìn)行了內(nèi)容拷貝。同樣怜庸,我們可以得出結(jié)論:

在集合類對(duì)象中当犯,對(duì) immutable 對(duì)象進(jìn)行 copy,是指針復(fù)制休雌, mutableCopy 是內(nèi)容復(fù)制灶壶;對(duì) mutable 對(duì)象進(jìn)行 copy 和 mutableCopy 都是內(nèi)容復(fù)制。但是:集合對(duì)象的內(nèi)容復(fù)制僅限于對(duì)象本身杈曲,對(duì)象元素仍然是指針復(fù)制驰凛。用代碼簡單表示如下:

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

這個(gè)代碼結(jié)論和非集合類的非常相似。

參考鏈接:iOS 集合的深復(fù)制與淺復(fù)制

14. @synthesize合成實(shí)例變量的規(guī)則是什么担扑?假如property名為foo恰响,存在一個(gè)名為_foo的實(shí)例變量,那么還會(huì)自動(dòng)合成新變量么涌献?

在回答之前先說明下一個(gè)概念:

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

這些說法胚宦,筆者下文中,可能都會(huì)用到燕垃,指的是一個(gè)東西枢劝。

正如 Apple官方文檔 You Can Customize Synthesized Instance Variable Names 所說:

如果使用了屬性的話,那么編譯器就會(huì)自動(dòng)編寫訪問屬性所需的方法卜壕,此過程叫做“自動(dòng)合成”( auto synthesis)您旁。需要強(qiáng)調(diào)的是,這個(gè)過程由編譯器在編譯期執(zhí)行轴捎,所以編輯器里看不到這些“合成方法” (synthesized method)的源代碼鹤盒。除了生成方法代碼之外,編譯器還要自動(dòng)向類中添加適當(dāng)類型的實(shí)例變量侦副,并且在屬性名前面加下劃線侦锯,以此作為實(shí)例變量的名字。

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

在上例中秦驯,會(huì)生成兩個(gè)實(shí)例變量尺碰,其名稱分別為 _firstName_lastName。也可以在類的實(shí)現(xiàn)代碼里通過 @synthesize語法來指定實(shí)例變量的名字:

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

上述語法會(huì)將生成的實(shí)例變量命名為 _myFirstName_myLastName 译隘,而不再使用默認(rèn)的名字葱蝗。一般情況下無須修改默認(rèn)的實(shí)例變量名,但是如果你不喜歡以下劃線來命名實(shí)例變量细燎,那么可以用這個(gè)辦法將其改為自己想要的名字两曼。筆者還是推薦使用默認(rèn)的命名方案,因?yàn)槿绻腥硕紙?jiān)持這套方案玻驻,那么寫出來的代碼大家都能看得懂悼凑。

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

  1. 如果指定了成員變量的名稱,會(huì)生成一個(gè)指定的名稱的成員變量,

  2. 如果這個(gè)成員已經(jīng)存在了就不再生成了.

  3. 如果是 @synthesize foo; 還會(huì)生成一個(gè)名稱為foo的成員變量璧瞬,也就是說:

如果沒有指定成員變量的名稱會(huì)自動(dòng)生成一個(gè)屬性同名的成員變量,

  1. 如果是 @synthesize foo = _foo; 就不會(huì)生成成員變量了.

假如 property 名為 foo户辫,存在一個(gè)名為 _foo 的實(shí)例變量,那么還會(huì)自動(dòng)合成新變量么嗤锉? 不會(huì)渔欢。如下圖:

15. 在有了自動(dòng)合成屬性實(shí)例變量之后,@synthesize還有哪些使用場(chǎng)景瘟忱?

回答這個(gè)問題前奥额,我們要搞清楚一個(gè)問題苫幢,什么情況下不會(huì)autosynthesis(自動(dòng)合成)?

  1. 同時(shí)重寫了 setter 和 getter 時(shí)
  2. 重寫了只讀屬性的 getter 時(shí)
  3. 使用了 @dynamic 時(shí)
  4. 在 @protocol 中定義的所有屬性
  5. 在 category 中定義的所有屬性
  6. 重寫(overridden)的屬性

當(dāng)你在子類中重載了父類中的屬性垫挨,你必須 使用 @synthesize 來手動(dòng)合成ivar韩肝。

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

因?yàn)橛辛?autosynthesis(自動(dòng)合成)剩蟀,大部分開發(fā)者已經(jīng)習(xí)慣不去手動(dòng)定義ivar,而是依賴于 autosynthesis(自動(dòng)合成)切威,但是一旦你需要使用ivar育特,而 autosynthesis(自動(dòng)合成)又失效了,如果不去手動(dòng)定義ivar牢屋,那么你就得借助 @synthesize來手動(dòng)合成 ivar且预。

其實(shí),@synthesize 語法還有一個(gè)應(yīng)用場(chǎng)景烙无,但是不太建議大家使用:

可以在類的實(shí)現(xiàn)代碼里通過 @synthesize 語法來指定實(shí)例變量的名字:

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

上述語法會(huì)將生成的實(shí)例變量命名為 _myFirstName_myLastName锋谐,而不再使用默認(rèn)的名字。一般情況下無須修改默認(rèn)的實(shí)例變量名截酷,但是如果你不喜歡以下劃線來命名實(shí)例變量涮拗,那么可以用這個(gè)辦法將其改為自己想要的名字。筆者還是推薦使用默認(rèn)的命名方案迂苛,因?yàn)槿绻腥硕紙?jiān)持這套方案三热,那么寫出來的代碼大家都能看得懂。

舉例說明:應(yīng)用場(chǎng)景:

//
// .m文件
// http://weibo.com/luohanchenyilong/ (微博@iOS程序犭袁)
// https://github.com/ChenYilong
// 打開第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

結(jié)果編譯器報(bào)錯(cuò):

當(dāng)你同時(shí)重寫了 setter 和 getter 時(shí)就漾,系統(tǒng)就不會(huì)生成 ivar(實(shí)例變量/成員變量)。這時(shí)候有兩種選擇:

  1. 要么如第14行:手動(dòng)創(chuàng)建 ivar
  2. 要么如第17行:使用@synthesize foo = _foo; 念搬,關(guān)聯(lián) @property 與 ivar抑堡。

更多信息,請(qǐng)戳- 》 When should I use @synthesize explicitly?

16. objc中向一個(gè)nil對(duì)象發(fā)送消息將會(huì)發(fā)生什么朗徊?

在 Objective-C 中向 nil 發(fā)送消息是完全有效的——只是在運(yùn)行時(shí)不會(huì)有任何作用:

  1. 如果一個(gè)方法返回值是一個(gè)對(duì)象首妖,那么發(fā)送給nil的消息將返回0(nil)。例如:
Person * motherInlaw = [[aPerson spouse] mother];

如果 spouse 對(duì)象為 nil爷恳,那么發(fā)送給 nil 的消息 mother 也將返回 nil有缆。 2. 如果方法返回值為指針類型,其指針大小為小于或者等于sizeof(void*),float棚壁,double杯矩,long double 或者 long long 的整型標(biāo)量,發(fā)送給 nil 的消息將返回0灌曙。 2. 如果方法返回值為結(jié)構(gòu)體,發(fā)送給 nil 的消息將返回0菊碟。結(jié)構(gòu)體中各個(gè)字段的值將都是0节芥。 2. 如果方法的返回值不是上述提到的幾種情況在刺,那么發(fā)送給 nil 的消息的返回值將是未定義的。

具體原因如下:

objc是動(dòng)態(tài)語言头镊,每個(gè)方法在運(yùn)行時(shí)會(huì)被動(dòng)態(tài)轉(zhuǎn)為消息發(fā)送蚣驼,即:objc_msgSend(receiver, selector)。

那么相艇,為了方便理解這個(gè)內(nèi)容颖杏,還是貼一個(gè)objc的源代碼:

// 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)識(shí)
  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; // 方法緩存活喊,對(duì)象接到一個(gè)消息會(huì)根據(jù)isa指針查找消息對(duì)象丐膝,這時(shí)會(huì)在method Lists中遍歷,如果cache了钾菊,常用的方法調(diào)用時(shí)就能夠提高調(diào)用的效率帅矗。
  struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協(xié)議鏈表
  #endif
  } OBJC2_UNAVAILABLE;

objc在向一個(gè)對(duì)象發(fā)送消息時(shí),runtime庫會(huì)根據(jù)對(duì)象的isa指針找到該對(duì)象實(shí)際所屬的類煞烫,然后在該類中的方法列表以及其父類方法列表中尋找方法運(yùn)行浑此,然后在發(fā)送消息的時(shí)候,objc_msgSend方法不會(huì)返回值滞详,所謂的返回內(nèi)容都是具體調(diào)用時(shí)執(zhí)行的莽使。 那么,回到本題做盅,如果向一個(gè)nil對(duì)象發(fā)送消息匣缘,首先在尋找對(duì)象的isa指針時(shí)就是0地址返回了,所以不會(huì)出現(xiàn)任何錯(cuò)誤稀火。

17. objc中向一個(gè)對(duì)象發(fā)送消息[obj foo]和objc_msgSend()函數(shù)之間有什么關(guān)系暖哨?

具體原因同上題:該方法編譯之后就是objc_msgSend()函數(shù)調(diào)用.

我們用 clang 分析下,clang 提供一個(gè)命令,可以將Objective-C的源碼改寫成C++語言篇裁,借此可以研究下[obj foo]和objc_msgSend()函數(shù)之間有什么關(guān)系沛慢。

以下面的代碼為例,由于 clang 后的代碼達(dá)到了10萬多行达布,為了便于區(qū)分团甲,添加了一個(gè)叫 iOSinit 方法,

//
//  main.m
//  http://weibo.com/luohanchenyilong/
//  https://github.com/ChenYilong
//  Copyright (c) 2015年 微博@iOS程序犭袁. All rights reserved.
//


#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

就可以生成一個(gè)main.cpp的文件黍聂,在最低端(10萬4千行左右)

我們可以看到大概是這樣的:

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

也就是說:

[obj foo];在objc編譯時(shí)躺苦,會(huì)被轉(zhuǎn)意為:objc_msgSend(obj, @selector(foo));

18. 什么時(shí)候會(huì)報(bào)unrecognized selector的異常产还?

簡單來說:

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

簡單的流程如下脐区,在上一題中也提到過:

objc是動(dòng)態(tài)語言愈诚,每個(gè)方法在運(yùn)行時(shí)會(huì)被動(dòng)態(tài)轉(zhuǎn)為消息發(fā)送,即:objc_msgSend(receiver, selector)牛隅。

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

  1. Method resolution

objc運(yùn)行時(shí)會(huì)調(diào)用+resolveInstanceMethod:或者 +resolveClassMethod:,讓你有機(jī)會(huì)提供一個(gè)函數(shù)實(shí)現(xiàn)巡验。如果你添加了函數(shù)际插,那運(yùn)行時(shí)系統(tǒng)就會(huì)重新啟動(dòng)一次消息發(fā)送的過程,否則 显设,運(yùn)行時(shí)就會(huì)移到下一步框弛,消息轉(zhuǎn)發(fā)(Message Forwarding)。

  1. Fast forwarding

如果目標(biāo)對(duì)象實(shí)現(xiàn)了-forwardingTargetForSelector:捕捂,Runtime 這時(shí)就會(huì)調(diào)用這個(gè)方法瑟枫,給你把這個(gè)消息轉(zhuǎn)發(fā)給其他對(duì)象的機(jī)會(huì)。 只要這個(gè)方法返回的不是nil和self指攒,整個(gè)消息發(fā)送的過程就會(huì)被重啟慷妙,當(dāng)然發(fā)送的對(duì)象會(huì)變成你返回的那個(gè)對(duì)象。否則允悦,就會(huì)繼續(xù)Normal Fowarding膝擂。 這里叫Fast,只是為了區(qū)別下一步的轉(zhuǎn)發(fā)機(jī)制。因?yàn)檫@一步不會(huì)創(chuàng)建任何新的對(duì)象架馋,但下一步轉(zhuǎn)發(fā)會(huì)創(chuàng)建一個(gè)NSInvocation對(duì)象狞山,所以相對(duì)更快點(diǎn)。 3. Normal forwarding

這一步是Runtime最后一次給你挽救的機(jī)會(huì)叉寂。首先它會(huì)發(fā)送-methodSignatureForSelector:消息獲得函數(shù)的參數(shù)和返回值類型萍启。如果-methodSignatureForSelector:返回nil,Runtime則會(huì)發(fā)出-doesNotRecognizeSelector:消息屏鳍,程序這時(shí)也就掛掉了勘纯。如果返回了一個(gè)函數(shù)簽名,Runtime就會(huì)創(chuàng)建一個(gè)NSInvocation對(duì)象并發(fā)送-forwardInvocation:消息給目標(biāo)對(duì)象孕蝉。

為了能更清晰地理解這些方法的作用屡律,git倉庫里也給出了一個(gè)Demo腌逢,名稱叫“ _objc_msgForward_demo ”,可運(yùn)行起來看看降淮。

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

  • 所有父類的成員變量和自己的成員變量都會(huì)存放在該對(duì)象所對(duì)應(yīng)的存儲(chǔ)空間中.
  • 每一個(gè)對(duì)象內(nèi)部都有一個(gè)isa指針,指向他的類對(duì)象,類對(duì)象中存放著本對(duì)象的
  1. 對(duì)象方法列表(對(duì)象能夠接收的消息列表搏讶,保存在它所對(duì)應(yīng)的類對(duì)象中)
  2. 成員變量的列表,
  3. 屬性列表,

它內(nèi)部也有一個(gè)isa指針指向元對(duì)象(meta class),元對(duì)象內(nèi)部存放的是類方法列表,類對(duì)象內(nèi)部還有一個(gè)superclass的指針,指向他的父類對(duì)象佳鳖。

每個(gè) Objective-C 對(duì)象都有相同的結(jié)構(gòu),如下圖所示:

翻譯過來就是

Objective-C 對(duì)象的結(jié)構(gòu)圖
ISA指針
根類的實(shí)例變量
倒數(shù)第二層父類的實(shí)例變量
...
父類的實(shí)例變量
類的實(shí)例變量
  • 根對(duì)象就是NSObject媒惕,它的superclass指針指向nil

  • 類對(duì)象既然稱為對(duì)象系吩,那它也是一個(gè)實(shí)例。類對(duì)象中也有一個(gè)isa指針指向它的元類(meta class)妒蔚,即類對(duì)象是元類的實(shí)例穿挨。元類內(nèi)部存放的是類方法列表,根元類的isa指針指向自己肴盏,superclass指針指向NSObject類科盛。

如圖:

20. 一個(gè)objc對(duì)象的isa的指針指向什么?有什么作用菜皂?

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

21. 下面的代碼輸出什么贞绵?

@implementation Son : Father
   - (id)init
   {
       self = [super init];
       if (self) {
           NSLog(@"%@", NSStringFromClass([self class]));
           NSLog(@"%@", NSStringFromClass([super class]));
       }
       return self;
   }
   @end

答案:
都輸出 Son

NSStringFromClass([self class]) = Son
NSStringFromClass([super class]) = Son

這個(gè)題目主要是考察關(guān)于 Objective-C 中對(duì) self 和 super 的理解。

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

很多人會(huì)想當(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 會(huì)告訴編譯器焦辅,調(diào)用 class 這個(gè)方法時(shí)博杖,要去父類的方法,而不是本類里的筷登。

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

當(dāng)使用 self 調(diào)用方法時(shí)前方,會(huì)從當(dāng)前類的方法列表中開始找狈醉,如果沒有,就從父類中再找惠险;而當(dāng)使用 super 時(shí)苗傅,則從父類的方法列表中開始找。然后調(diào)用父類的這個(gè)方法班巩。

這也就是為什么說“不推薦在 init 方法中使用點(diǎn)語法”渣慕,如果想訪問實(shí)例變量 iVar 應(yīng)該使用下劃線( _iVar ),而非點(diǎn)語法( self.iVar )抱慌。

點(diǎn)語法( self.iVar )的壞處就是子類有可能覆寫 setter 逊桦。假設(shè) Person 有一個(gè)子類叫 ChenPerson,這個(gè)子類專門表示那些姓“陳”的人抑进。該子類可能會(huì)覆寫 lastName 屬性所對(duì)應(yīng)的設(shè)置方法:

//
//  ChenPerson.m
//  
//
//  Created by https://github.com/ChenYilong on 15/8/30.
//  Copyright (c) 2015年 http://weibo.com/luohanchenyilong/ 微博@iOS程序犭袁. All rights reserved.
//

#import "ChenPerson.h"

@implementation ChenPerson

@synthesize lastName = _lastName;

- (instancetype)init
{
   self = [super init];
   if (self) {
       NSLog(@"??類名與方法名:%s(在第%d行)强经,描述:%@", __PRETTY_FUNCTION__, __LINE__, NSStringFromClass([self class]));
       NSLog(@"??類名與方法名:%s(在第%d行),描述:%@", __PRETTY_FUNCTION__, __LINE__, NSStringFromClass([super class]));
   }
   return self;
}

- (void)setLastName:(NSString*)lastName
{
   //設(shè)置方法一:如果setter采用是這種方式寺渗,就可能引起崩潰
//    if (![lastName isEqualToString:@"陳"])
//    {
//        [NSException raise:NSInvalidArgumentException format:@"姓不是陳"];
//    }
//    _lastName = lastName;
   
   //設(shè)置方法二:如果setter采用是這種方式匿情,就可能引起崩潰
   _lastName = @"陳";
   NSLog(@"??類名與方法名:%s(在第%d行),描述:%@", __PRETTY_FUNCTION__, __LINE__, @"會(huì)調(diào)用這個(gè)方法,想一下為什么信殊?");

}

@end

在基類 Person 的默認(rèn)初始化方法中炬称,可能會(huì)將姓氏設(shè)為空字符串。此時(shí)若使用點(diǎn)語法( self.lastName )也即 setter 設(shè)置方法鸡号,那么調(diào)用將會(huì)是子類的設(shè)置方法转砖,如果在剛剛的 setter 代碼中采用設(shè)置方法一,那么就會(huì)拋出異常鲸伴,

為了方便采用打印的方式展示府蔗,究竟發(fā)生了什么,我們使用設(shè)置方法二汞窗。

如果基類的代碼是這樣的:

//
//  Person.m
//  nil對(duì)象調(diào)用點(diǎn)語法
//
//  Created by https://github.com/ChenYilong on 15/8/29.
//  Copyright (c) 2015年 http://weibo.com/luohanchenyilong/ 微博@iOS程序犭袁. All rights reserved.
//  

#import "Person.h"

@implementation Person

- (instancetype)init
{
   self = [super init];
   if (self) {
       self.lastName = @"";
       //NSLog(@"??類名與方法名:%s(在第%d行)姓赤,描述:%@", __PRETTY_FUNCTION__, __LINE__, NSStringFromClass([self class]));
       //NSLog(@"??類名與方法名:%s(在第%d行),描述:%@", __PRETTY_FUNCTION__, __LINE__, self.lastName);
   }
   return self;
}

- (void)setLastName:(NSString*)lastName
{
   NSLog(@"??類名與方法名:%s(在第%d行)仲吏,描述:%@", __PRETTY_FUNCTION__, __LINE__, @"根本不會(huì)調(diào)用這個(gè)方法");
   _lastName = @"炎黃";
}

@end

那么打印結(jié)果將會(huì)是這樣的:

??類名與方法名:-[ChenPerson setLastName:](在第36行)不铆,描述:會(huì)調(diào)用這個(gè)方法,想一下為什么蝌焚?
??類名與方法名:-[ChenPerson init](在第19行),描述:ChenPerson
??類名與方法名:-[ChenPerson init](在第20行)誓斥,描述:ChenPerson

我在倉庫里也給出了一個(gè)相應(yīng)的 Demo(名字叫:Demo_21題_下面的代碼輸出什么)只洒。有興趣可以跑起來看一下,主要看下他是怎么打印的劳坑,思考下為什么這么打印毕谴。

接下來讓我們利用 runtime 的相關(guān)知識(shí)來驗(yàn)證一下 super 關(guān)鍵字的本質(zhì),使用clang重寫命令:

$ clang -rewrite-objc test.m

將這道題目中給出的代碼被轉(zhuǎn)化為:

NSLog((NSString *)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_0, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"))));

   NSLog((NSString *)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_1, NSStringFromClass(((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){ (id)self, (id)class_getSuperclass(objc_getClass("Son")) }, sel_registerName("class"))));

從上面的代碼中距芬,我們可以發(fā)現(xiàn)在調(diào)用 [self class] 時(shí)涝开,會(huì)轉(zhuǎn)化成 objc_msgSend函數(shù)】蜃校看下函數(shù)定義:

id objc_msgSend(id self, SEL op, ...)

我們把 self 做為第一個(gè)參數(shù)傳遞進(jìn)去舀武。

而在調(diào)用 [super class]時(shí),會(huì)轉(zhuǎn)化成 objc_msgSendSuper函數(shù)离斩∫眨看下函數(shù)定義:

id objc_msgSendSuper(struct objc_super *super, SEL op, ...)

第一個(gè)參數(shù)是 objc_super 這樣一個(gè)結(jié)構(gòu)體,其定義如下:

struct objc_super {
      __unsafe_unretained id receiver;
      __unsafe_unretained Class super_class;
};

結(jié)構(gòu)體有兩個(gè)成員捐腿,第一個(gè)成員是 receiver, 類似于上面的 objc_msgSend函數(shù)第一個(gè)參數(shù)self 纵朋。第二個(gè)成員是記錄當(dāng)前類的父類是什么。

所以茄袖,當(dāng)調(diào)用 [self class] 時(shí),實(shí)際先調(diào)用的是 objc_msgSend函數(shù)嘁锯,第一個(gè)參數(shù)是 Son當(dāng)前的這個(gè)實(shí)例宪祥,然后在 Son 這個(gè)類里面去找 - (Class)class這個(gè)方法,沒有家乘,去父類 Father里找蝗羊,也沒有,最后在 NSObject類中發(fā)現(xiàn)這個(gè)方法仁锯。而 - (Class)class的實(shí)現(xiàn)就是返回self的類別耀找,故上述輸出結(jié)果為 Son。

objc Runtime開源代碼對(duì)- (Class)class方法的實(shí)現(xiàn):

- (Class)class {
   return object_getClass(self);
}

而當(dāng)調(diào)用 [super class]時(shí)业崖,會(huì)轉(zhuǎn)換成objc_msgSendSuper函數(shù)野芒。第一步先構(gòu)造 objc_super 結(jié)構(gòu)體,結(jié)構(gòu)體第一個(gè)成員就是 self 双炕。 第二個(gè)成員是 (id)class_getSuperclass(objc_getClass(“Son”)) , 實(shí)際該函數(shù)輸出結(jié)果為 Father狞悲。

第二步是去 Father這個(gè)類里去找 - (Class)class,沒有妇斤,然后去NSObject類去找摇锋,找到了丹拯。最后內(nèi)部是使用 objc_msgSend(objc_super->receiver, @selector(class))去調(diào)用,

此時(shí)已經(jīng)和[self class]調(diào)用相同了荸恕,故上述輸出結(jié)果仍然返回 Son乖酬。

參考鏈接:微博@Chun_iOS的博文刨根問底Objective-C Runtime(1)- Self & Super

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

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

23. 使用runtime Associate方法關(guān)聯(lián)的對(duì)象融求,需要在主對(duì)象dealloc的時(shí)候釋放么剑刑?

  • 在ARC下不需要。

  • 在MRC中,對(duì)于使用retain或copy策略的需要 双肤。

    在MRC下也不需要

無論在MRC下還是ARC下均不需要施掏。

2011年版本的Apple API 官方文檔 - Associative References 一節(jié)中有一個(gè)MRC環(huán)境下的例子:

// 在MRC下,使用runtime Associate方法關(guān)聯(lián)的對(duì)象茅糜,不需要在主對(duì)象dealloc的時(shí)候釋放
// http://weibo.com/luohanchenyilong/ (微博@iOS程序犭袁)
// https://github.com/ChenYilong
// 摘自2011年版本的Apple API 官方文檔 - Associative References 

static char overviewKey;
 
NSArray *array =
    [[NSArray alloc] initWithObjects:@"One", @"Two", @"Three", nil];
// For the purposes of illustration, use initWithFormat: to ensure
// the string can be deallocated
NSString *overview =
    [[NSString alloc] initWithFormat:@"%@", @"First three numbers"];
 
objc_setAssociatedObject (
    array,
    &overviewKey,
    overview,
    OBJC_ASSOCIATION_RETAIN
);
 
[overview release];
// (1) overview valid
[array release];
// (2) overview invalid

文檔指出

At point 1, the string overview is still valid because the OBJC_ASSOCIATION_RETAIN policy specifies that the array retains the associated object. When the array is deallocated, however (at point 2), overview is released and so in this case also deallocated.

我們可以看到七芭,在[array release];之后,overview就會(huì)被release釋放掉了蔑赘。

既然會(huì)被銷毀狸驳,那么具體在什么時(shí)間點(diǎn)?

根據(jù) WWDC 2011, Session 322 (第36分22秒) 中發(fā)布的內(nèi)存銷毀時(shí)間表缩赛,被關(guān)聯(lián)的對(duì)象在生命周期內(nèi)要比對(duì)象本身釋放的晚很多耙箍。它們會(huì)在被 NSObject -dealloc 調(diào)用的 object_dispose() 方法中釋放。

對(duì)象的內(nèi)存銷毀時(shí)間表酥馍,分四個(gè)步驟:

// 對(duì)象的內(nèi)存銷毀時(shí)間表
// http://weibo.com/luohanchenyilong/ (微博@iOS程序犭袁)
// https://github.com/ChenYilong
// 根據(jù) WWDC 2011, Session 322 (36分22秒)中發(fā)布的內(nèi)存銷毀時(shí)間表 

 1. 調(diào)用 -release :引用計(jì)數(shù)變?yōu)榱?     * 對(duì)象正在被銷毀辩昆,生命周期即將結(jié)束.
     * 不能再有新的 __weak 弱引用, 否則將指向 nil.
     * 調(diào)用 [self dealloc] 
 2. 子類 調(diào)用 -dealloc
     * 繼承關(guān)系中最底層的子類 在調(diào)用 -dealloc
     * 如果是 MRC 代碼 則會(huì)手動(dòng)釋放實(shí)例變量們(iVars)
     * 繼承關(guān)系中每一層的父類 都在調(diào)用 -dealloc
 3. NSObject 調(diào) -dealloc
     * 只做一件事:調(diào)用 Objective-C runtime 中的 object_dispose() 方法
 4. 調(diào)用 object_dispose()
     * 為 C++ 的實(shí)例變量們(iVars)調(diào)用 destructors 
     * 為 ARC 狀態(tài)下的 實(shí)例變量們(iVars) 調(diào)用 -release 
     * 解除所有使用 runtime Associate方法關(guān)聯(lián)的對(duì)象
     * 解除所有 __weak 引用
     * 調(diào)用 free()

對(duì)象的內(nèi)存銷毀時(shí)間表:參考鏈接旨袒。

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

類方法:

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

實(shí)例方法:

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

注:面試題來源是微博@我就叫Sunny怎么了的這篇博文:《招聘一個(gè)靠譜的 iOS》,其中共55題砚尽,除第一題為糾錯(cuò)題外施无,其他54道均為簡答題。

出題者簡介: 孫源(sunnyxx)必孤,目前就職于百度猾骡,負(fù)責(zé)百度知道 iOS 客戶端的開發(fā)工作,對(duì)技術(shù)喜歡刨根問底和總結(jié)最佳實(shí)踐敷搪,熱愛分享和開源兴想,維護(hù)一個(gè)叫 forkingdog 的開源小組。

轉(zhuǎn)自## 《招聘一個(gè)靠譜的iOS》面試題參考答案

答案為微博@iOS程序犭袁整理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末购啄,一起剝皮案震驚了整個(gè)濱河市襟企,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌狮含,老刑警劉巖顽悼,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件曼振,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蔚龙,警方通過查閱死者的電腦和手機(jī)冰评,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來木羹,“玉大人甲雅,你說我怎么就攤上這事】犹睿” “怎么了抛人?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長脐瑰。 經(jīng)常有香客問我妖枚,道長,這世上最難降的妖魔是什么苍在? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任绝页,我火速辦了婚禮,結(jié)果婚禮上寂恬,老公的妹妹穿的比我還像新娘续誉。我一直安慰自己,他們只是感情好初肉,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布酷鸦。 她就那樣靜靜地躺著,像睡著了一般朴译。 火紅的嫁衣襯著肌膚如雪井佑。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天眠寿,我揣著相機(jī)與錄音,去河邊找鬼焦蘑。 笑死盯拱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的例嘱。 我是一名探鬼主播狡逢,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼拼卵!你這毒婦竟也來了奢浑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤腋腮,失蹤者是張志新(化名)和其女友劉穎雀彼,沒想到半個(gè)月后壤蚜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡徊哑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年袜刷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片莺丑。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡著蟹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出梢莽,到底是詐尸還是另有隱情萧豆,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布昏名,位于F島的核電站涮雷,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏葡粒。R本人自食惡果不足惜份殿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望嗽交。 院中可真熱鬧卿嘲,春花似錦、人聲如沸夫壁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盒让。三九已至梅肤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間邑茄,已是汗流浹背姨蝴。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留肺缕,地道東北人左医。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像同木,于是被迫代替她去往敵國和親浮梢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350