13. 用@property聲明的NSString(或NSArray晾咪,NSDictionary)經(jīng)常使用copy關(guān)鍵字,為什么?如果改用strong關(guān)鍵字榄攀,可能造成什么問(wèn)題?
- 因?yàn)楦割愔羔樋梢灾赶蜃宇悓?duì)象,使用 copy 的目的是為了讓本對(duì)象的屬性不受外界影響,使用 copy 無(wú)論給我傳入是一個(gè)可變對(duì)象還是不可對(duì)象,我本身持有的就是一個(gè)不可變的副本.
- 如果我們使用是 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碟摆。)
為了理解這種做法晃财,首先要知道,兩種情況:
- 對(duì)非集合類對(duì)象的 copy 與 mutableCopy 操作典蜕;
- 對(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é)論和非集合類的非常相似鱼鸠。
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ō):
如果使用了屬性的話扮授,那么編譯器就會(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):
如果指定了成員變量的名稱,會(huì)生成一個(gè)指定的名稱的成員變量,
如果這個(gè)成員已經(jīng)存在了就不再生成了.
如果是
@synthesize foo;
還會(huì)生成一個(gè)名稱為foo的成員變量碍侦,也就是說(shuō):
如果沒(méi)有指定成員變量的名稱會(huì)自動(dòng)生成一個(gè)屬性同名的成員變量,
- 如果是
@synthesize foo = _foo;
就不會(huì)生成成員變量了.
假如 property 名為 foo粱坤,存在一個(gè)名為 _foo
的實(shí)例變量,那么還會(huì)自動(dòng)合成新變量么瓷产?
不會(huì)站玄。如下圖:
15. 在有了自動(dòng)合成屬性實(shí)例變量之后,@synthesize還有哪些使用場(chǎng)景濒旦?
回答這個(gè)問(wèn)題前株旷,我們要搞清楚一個(gè)問(wèn)題,什么情況下不會(huì)autosynthesis(自動(dòng)合成)尔邓?
- 同時(shí)重寫(xiě)了 setter 和 getter 時(shí)
- 重寫(xiě)了只讀屬性的 getter 時(shí)
- 使用了 @dynamic 時(shí)
- 在 @protocol 中定義的所有屬性
- 在 category 中定義的所有屬性
- 重載的屬性
當(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ò):
當(dāng)你同時(shí)重寫(xiě)了 setter 和 getter 時(shí)蔓纠,系統(tǒng)就不會(huì)生成 ivar(實(shí)例變量/成員變量)。這時(shí)候有兩種選擇:
- 要么如第14行:手動(dòng)創(chuàng)建 ivar
- 要么如第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ì)有任何作用:
- 如果一個(gè)方法返回值是一個(gè)對(duì)象敷燎,那么發(fā)送給nil的消息將返回0(nil)。例如:
Person * motherInlaw = [[aPerson spouse] mother];
如果 spouse 對(duì)象為 nil箩言,那么發(fā)送給 nil 的消息 mother 也將返回 nil硬贯。
- 如果方法返回值為指針類型,其指針大小為小于或者等于sizeof(void*)陨收,float饭豹,double,long double 或者 long long 的整型標(biāo)量务漩,發(fā)送給 nil 的消息將返回0墨状。
- 如果方法返回值為結(jié)構(gòu)體,發(fā)送給 nil 的消息將返回0。結(jié)構(gòu)體中各個(gè)字段的值將都是0菲饼。
- 如果方法的返回值不是上述提到的幾種情況,那么發(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千行左右)
我們可以看到大概是這樣的:
((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ì):
- 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)。
- 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)讥耗。
- 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ì)象的
- 對(duì)象方法列表(對(duì)象能夠接收的消息列表呜达,保存在它所對(duì)應(yīng)的類對(duì)象中)
- 成員變量的列表,
- 屬性列表,
它內(nèi)部也有一個(gè)isa指針指向元對(duì)象(meta class),元對(duì)象內(nèi)部存放的是類方法列表,類對(duì)象內(nèi)部還有一個(gè)superclass的指針,指向他的父類對(duì)象。
每個(gè) Objective-C 對(duì)象都有相同的結(jié)構(gòu)粟耻,如下圖所示:
翻譯過(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類婿禽。
如圖:
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 theOBJC_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)系栈妆?
類方法:
- 類方法是屬于類對(duì)象的
- 類方法只能通過(guò)類對(duì)象調(diào)用
- 類方法中的self是類對(duì)象
- 類方法可以調(diào)用其他的類方法
- 類方法中不能訪問(wèn)成員變量
- 類方法中不定直接調(diào)用對(duì)象方法
實(shí)例方法:
- 實(shí)例方法是屬于實(shí)例對(duì)象的
- 實(shí)例方法只能通過(guò)實(shí)例對(duì)象調(diào)用
- 實(shí)例方法中的self是實(shí)例對(duì)象
- 實(shí)例方法中可以訪問(wèn)成員變量
- 實(shí)例方法中直接調(diào)用實(shí)例方法
- 實(shí)例方法中也可以調(diào)用類方法(通過(guò)類名)
Posted by 微博@iOS程序犭袁
原創(chuàng)文章,版權(quán)聲明:自由轉(zhuǎn)載-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0