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

13. 用@property聲明的NSString(或NSArray晾咪,NSDictionary)經(jīng)常使用copy關(guān)鍵字,為什么?如果改用strong關(guān)鍵字榄攀,可能造成什么問(wèn)題?

  1. 因?yàn)楦割愔羔樋梢灾赶蜃宇悓?duì)象,使用 copy 的目的是為了讓本對(duì)象的屬性不受外界影響,使用 copy 無(wú)論給我傳入是一個(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ì)來(lái)保護(hù)其封裝性贞瞒,因?yàn)閭鬟f給設(shè)置方法的新值有可能指向一個(gè) NSMutableString 類的實(shí)例。這個(gè)類是 NSString 的子類趁曼,表示一種可修改其值的字符串军浆,此時(shí)若是不拷貝字符串,那么設(shè)置完屬性之后挡闰,字符串的值就可能會(huì)在對(duì)象不知情的情況下遭人更改乒融。所以,這時(shí)就要拷貝一份“不可變” (immutable)的字符串,確保對(duì)象中的字符串值不會(huì)無(wú)意間變動(dòng)簇抵。只要實(shí)現(xiàn)屬性所用的對(duì)象是“可變的” (mutable)庆杜,就應(yīng)該在設(shè)置新屬性值時(shí)拷貝一份。

舉例說(shuō)明:

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

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

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

   NSMutableArray *mutableArray = [[NSMutableArray alloc] init];
   NSArray *array = @[ @1, @2, @3, @4 ];
   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
)

(詳見(jiàn)倉(cāng)庫(kù)內(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ù)制。用代碼簡(jiǎn)單表示如下:

  • [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)存地址都不一樣,說(shuō)明此時(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ì)無(wú)意間變動(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 的地址是不同的蛔六。說(shuō)明 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)存地址都不一樣柑晒,說(shuō)明 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ù)制。用代碼簡(jiǎn)單表示如下:

[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)合成新變量么愉昆?

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

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

這些說(shuō)法,筆者下文中麻蹋,可能都會(huì)用到跛溉,指的是一個(gè)東西。

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

enter image description here
enter image description here

如果使用了屬性的話扮授,那么編譯器就會(huì)自動(dòng)編寫(xiě)訪問(wèn)屬性所需的方法芳室,此過(guò)程叫做“自動(dòng)合成”( auto synthesis)。需要強(qiáng)調(diào)的是刹勃,這個(gè)過(guò)程由編譯器在編譯期執(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)代碼里通過(guò) @synthesize 語(yǔ)法來(lái)指定實(shí)例變量的名字:

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

上述語(yǔ)法會(huì)將生成的實(shí)例變量命名為 _myFirstName_myLastName 卖毁,而不再使用默認(rèn)的名字揖曾。一般情況下無(wú)須修改默認(rèn)的實(shí)例變量名,但是如果你不喜歡以下劃線來(lái)命名實(shí)例變量势篡,那么可以用這個(gè)辦法將其改為自己想要的名字翩肌。筆者還是推薦使用默認(rèn)的命名方案,因?yàn)槿绻腥硕紙?jiān)持這套方案禁悠,那么寫(xiě)出來(lái)的代碼大家都能看得懂念祭。

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

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

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

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

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

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

假如 property 名為 foo粱坤,存在一個(gè)名為 _foo 的實(shí)例變量,那么還會(huì)自動(dòng)合成新變量么瓷产?
不會(huì)站玄。如下圖:

enter image description here
enter image description here

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

回答這個(gè)問(wèn)題前株旷,我們要搞清楚一個(gè)問(wèn)題,什么情況下不會(huì)autosynthesis(自動(dòng)合成)尔邓?

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

當(dāng)你在子類中重載了父類中的屬性晾剖,你必須 使用 @synthesize 來(lái)手動(dòng)合成ivar。

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

因?yàn)橛辛?autosynthesis(自動(dòng)合成),大部分開(kāi)發(fā)者已經(jīng)習(xí)慣不去手動(dòng)定義ivar炎疆,而是依賴于 autosynthesis(自動(dòng)合成)卡骂,但是一旦你需要使用ivar,而 autosynthesis(自動(dòng)合成)又失效了磷雇,如果不去手動(dòng)定義ivar偿警,那么你就得借助 @synthesize 來(lái)手動(dòng)合成 ivar。

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

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

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

上述語(yǔ)法會(huì)將生成的實(shí)例變量命名為 _myFirstName_myLastName盒使,而不再使用默認(rèn)的名字。一般情況下無(wú)須修改默認(rèn)的實(shí)例變量名七嫌,但是如果你不喜歡以下劃線來(lái)命名實(shí)例變量夯巷,那么可以用這個(gè)辦法將其改為自己想要的名字眶明。筆者還是推薦使用默認(rèn)的命名方案阴颖,因?yàn)槿绻腥硕紙?jiān)持這套方案顷蟀,那么寫(xiě)出來(lái)的代碼大家都能看得懂。

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


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


enter image description here
enter image description here

當(dāng)你同時(shí)重寫(xiě)了 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硬贯。

  1. 如果方法返回值為指針類型,其指針大小為小于或者等于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菲饼。
  3. 如果方法的返回值不是上述提到的幾種情況,那么發(fā)送給 nil 的消息的返回值將是未定義的列赎。

具體原因如下:

objc是動(dòng)態(tài)語(yǔ)言宏悦,每個(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庫(kù)會(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的源碼改寫(xiě)成C++語(yǔ)言眷昆,借此可以研究下[obj foo]和objc_msgSend()函數(shù)之間有什么關(guān)系。

以下面的代碼為例汁咏,由于 clang 后的代碼達(dá)到了10萬(wàn)多行亚斋,為了便于區(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萬(wàn)4千行左右)

enter image description here
enter image description here

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

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

也就是說(shuō):

[obj foo];在objc動(dòng)態(tài)編譯時(shí),會(huì)被轉(zhuǎn)意為:objc_msgSend(obj, @selector(foo));漂问。

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

簡(jiǎn)單來(lái)說(shuō):

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

簡(jiǎn)單的流程如下栏饮,在上一題中也提到過(guò):

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

objc在向一個(gè)對(duì)象發(fā)送消息時(shí),runtime庫(kù)會(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ā)送的過(guò)程,否則 吆你,運(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ā)送的過(guò)程就會(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)讥耗。

  1. 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倉(cāng)庫(kù)里也給出了一個(gè)Demo趋急,名稱叫“ _objc_msgForward_demo ”,可運(yùn)行起來(lái)看看。

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)粟耻,如下圖所示:

enter image description here
enter image description here

翻譯過(guò)來(lái)就是

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類婿禽。

如圖:


enter image description here
enter image description here

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)前類的方法列表中開(kāi)始找,如果沒(méi)有剿涮,就從父類中再找言津;而當(dāng)使用 super 時(shí),則從父類的方法列表中開(kāi)始找取试。然后調(diào)用父類的這個(gè)方法悬槽。

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

點(diǎn)語(yǔ)法( self.iVar )的壞處就是子類有可能覆寫(xiě) setter 。假設(shè) Person 有一個(gè)子類叫 ChenPerson,這個(gè)子類專門表示那些姓“陳”的人磅叛。該子類可能會(huì)覆寫(xiě) 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)語(yǔ)法( self.lastName )也即 setter 設(shè)置方法臣缀,那么調(diào)用將會(huì)是子類的設(shè)置方法坝橡,如果在剛剛的 setter 代碼中采用設(shè)置方法一,那么就會(huì)拋出異常精置,

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

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

//
//  Person.m
//  nil對(duì)象調(diào)用點(diǎn)語(yǔ)法
//
//  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

我在倉(cāng)庫(kù)里也給出了一個(gè)相應(yīng)的 Demo(名字叫:Demo_21題_下面的代碼輸出什么)。有興趣可以跑起來(lái)看一下苇侵,主要看下他是怎么打印的赶盔,思考下為什么這么打印。

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

   $ 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è)方法,沒(méi)有坦喘,去父類 Father里找盲再,也沒(méi)有,最后在 NSObject類中發(fā)現(xiàn)這個(gè)方法瓣铣。而 - (Class)class的實(shí)現(xiàn)就是返回self的類別答朋,故上述輸出結(jié)果為 Son。

objc Runtime開(kāi)源代碼對(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,沒(méi)有循捺,然后去NSObject類去找斩例,找到了。最后內(nèi)部是使用 objc_msgSend(objc_super->receiver, @selector(class))去調(diào)用从橘,

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

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

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

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

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

  • 在ARC下不需要牺勾。
  • <p><del> 在MRC中,對(duì)于使用retain或copy策略的需要 正罢。</del></p>在MRC下也不需要

無(wú)論在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. 類方法只能通過(guò)類對(duì)象調(diào)用
  3. 類方法中的self是類對(duì)象
  4. 類方法可以調(diào)用其他的類方法
  5. 類方法中不能訪問(wèn)成員變量
  6. 類方法中不定直接調(diào)用對(duì)象方法

實(shí)例方法:

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

Posted by 微博@iOS程序犭袁
原創(chuàng)文章,版權(quán)聲明:自由轉(zhuǎn)載-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末厢钧,一起剝皮案震驚了整個(gè)濱河市鳞尔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌早直,老刑警劉巖寥假,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異霞扬,居然都是意外死亡糕韧,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門祥得,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)兔沃,“玉大人,你說(shuō)我怎么就攤上這事级及∑故瑁” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵饮焦,是天一觀的道長(zhǎng)怕吴。 經(jīng)常有香客問(wèn)我,道長(zhǎng)县踢,這世上最難降的妖魔是什么转绷? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮硼啤,結(jié)果婚禮上议经,老公的妹妹穿的比我還像新娘。我一直安慰自己谴返,他們只是感情好煞肾,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著嗓袱,像睡著了一般籍救。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上渠抹,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天蝙昙,我揣著相機(jī)與錄音闪萄,去河邊找鬼。 笑死奇颠,一個(gè)胖子當(dāng)著我的面吹牛败去,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播烈拒,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼为迈,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了缺菌?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤搜锰,失蹤者是張志新(化名)和其女友劉穎伴郁,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蛋叼,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡焊傅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了狈涮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片狐胎。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖歌馍,靈堂內(nèi)的尸體忽然破棺而出握巢,到底是詐尸還是另有隱情,我是刑警寧澤松却,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布暴浦,位于F島的核電站,受9級(jí)特大地震影響晓锻,放射性物質(zhì)發(fā)生泄漏歌焦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一砚哆、第九天 我趴在偏房一處隱蔽的房頂上張望独撇。 院中可真熱鬧,春花似錦躁锁、人聲如沸纷铣。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)关炼。三九已至,卻和暖如春匣吊,著一層夾襖步出監(jiān)牢的瞬間儒拂,已是汗流浹背寸潦。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留社痛,地道東北人见转。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蒜哀,于是被迫代替她去往敵國(guó)和親斩箫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

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