13. 用@property聲明的NSString(或NSArray,NSDictionary)經(jīng)常使用copy關(guān)鍵字,為什么?如果改用strong關(guān)鍵字,可能造成什么問題?
- 因?yàn)楦割愔羔樋梢灾赶蜃宇悓ο?使用 copy 的目的是為了讓本對象的屬性不受外界影響,使用 copy 無論給我傳入是一個(gè)可變對象還是不可對象,我本身持有的就是一個(gè)不可變的副本.
- 如果我們使用是 strong ,那么這個(gè)屬性就有可能指向一個(gè)可變對象,如果這個(gè)可變對象在外部被修改了,那么會影響該屬性.
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è)置完屬性之后,字符串的值就可能會在對象不知情的情況下遭人更改。所以,這時(shí)就要拷貝一份“不可變” (immutable)的字符串,確保對象中的字符串值不會無意間變動。只要實(shí)現(xiàn)屬性所用的對象是“可變的” (mutable),就應(yīng)該在設(shè)置新屬性值時(shí)拷貝一份。
舉例說明:
定義一個(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
)
(詳見倉庫內(nèi)附錄的 Demo。)
為了理解這種做法,首先要知道,兩種情況:
- 對非集合類對象的 copy 與 mutableCopy 操作;
- 對集合類對象的 copy 與 mutableCopy 操作。
1. 對非集合類對象的copy操作:
在非集合類對象中:對 immutable 對象進(jìn)行 copy 操作,是指針復(fù)制,mutableCopy 操作時(shí)內(nèi)容復(fù)制;對 mutable 對象進(jìn)行 copy 和 mutableCopy 都是內(nèi)容復(fù)制。用代碼簡單表示如下:
- [immutableObject copy] // 淺復(fù)制
- [immutableObject mutableCopy] //深復(fù)制
- [mutableObject copy] //深復(fù)制
- [mutableObject mutableCopy] //深復(fù)制
比如以下代碼:
NSMutableString *string = [NSMutableString stringWithString:@"origin"];//copy
NSString *stringCopy = [string copy];
查看內(nèi)存,會發(fā)現(xiàn) string、stringCopy 內(nèi)存地址都不一樣,說明此時(shí)都是做內(nèi)容拷貝、深拷貝。即使你進(jìn)行如下操作:
[string appendString:@"origion!"]
stringCopy 的值也不會因此改變,但是如果不使用 copy,stringCopy 的值就會被改變。
集合類對象以此類推。
所以,
用 @property 聲明 NSString、NSArray、NSDictionary 經(jīng)常使用 copy 關(guān)鍵字,是因?yàn)樗麄冇袑?yīng)的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary,他們之間可能進(jìn)行賦值操作,為確保對象中的字符串值不會無意間變動,應(yīng)該在設(shè)置新屬性值時(shí)拷貝一份。
2、集合類對象的copy與mutableCopy
集合類對象是指 NSArray、NSDictionary、NSSet ... 之類的對象。下面先看集合類immutable對象使用 copy 和 mutableCopy 的一個(gè)例子:
NSArray *array = @[@[@"a", @"b"], @[@"c", @"d"]];
NSArray *copyArray = [array copy];
NSMutableArray *mCopyArray = [array mutableCopy];
查看內(nèi)容,可以看到 copyArray 和 array 的地址是一樣的,而 mCopyArray 和 array 的地址是不同的。說明 copy 操作進(jìn)行了指針拷貝,mutableCopy 進(jìn)行了內(nèi)容拷貝。但需要強(qiáng)調(diào)的是:此處的內(nèi)容拷貝,僅僅是拷貝 array 這個(gè)對象,array 集合內(nèi)部的元素仍然是指針拷貝。這和上面的非集合 immutable 對象的拷貝還是挺相似的,那么mutable對象的拷貝會不會類似呢?我們繼續(xù)往下,看 mutable 對象拷貝的例子:
NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
NSArray *copyArray = [array copy];
NSMutableArray *mCopyArray = [array mutableCopy];
查看內(nèi)存,如我們所料,copyArray、mCopyArray和 array 的內(nèi)存地址都不一樣,說明 copyArray、mCopyArray 都對 array 進(jìn)行了內(nèi)容拷貝。同樣,我們可以得出結(jié)論:
在集合類對象中,對 immutable 對象進(jìn)行 copy,是指針復(fù)制, mutableCopy 是內(nèi)容復(fù)制;對 mutable 對象進(jìn)行 copy 和 mutableCopy 都是內(nèi)容復(fù)制。但是:集合對象的內(nèi)容復(fù)制僅限于對象本身,對象元素仍然是指針復(fù)制。用代碼簡單表示如下:
[immutableObject copy] // 淺復(fù)制
[immutableObject mutableCopy] //單層深復(fù)制
[mutableObject copy] //單層深復(fù)制
[mutableObject mutableCopy] //單層深復(fù)制
這個(gè)代碼結(jié)論和非集合類的非常相似。
14. @synthesize合成實(shí)例變量的規(guī)則是什么?假如property名為foo,存在一個(gè)名為_foo的實(shí)例變量,那么還會自動合成新變量么?
在回答之前先說明下一個(gè)概念:
實(shí)例變量 = 成員變量 = ivar
這些說法,筆者下文中,可能都會用到,指的是一個(gè)東西。
正如
Apple官方文檔 You Can Customize Synthesized Instance Variable Names 所說:

如果使用了屬性的話,那么編譯器就會自動編寫訪問屬性所需的方法,此過程叫做“自動合成”( auto synthesis)。需要強(qiáng)調(diào)的是,這個(gè)過程由編譯器在編譯期執(zhí)行,所以編輯器里看不到這些“合成方法” (synthesized method)的源代碼。除了生成方法代碼之外,編譯器還要自動向類中添加適當(dāng)類型的實(shí)例變量,并且在屬性名前面加下劃線,以此作為實(shí)例變量的名字。
@interface CYLPerson : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end
在上例中,會生成兩個(gè)實(shí)例變量,其名稱分別為
_firstName 與 _lastName。也可以在類的實(shí)現(xiàn)代碼里通過 @synthesize 語法來指定實(shí)例變量的名字:
@implementation CYLPerson
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end
上述語法會將生成的實(shí)例變量命名為 _myFirstName 與 _myLastName ,而不再使用默認(rèn)的名字。一般情況下無須修改默認(rèn)的實(shí)例變量名,但是如果你不喜歡以下劃線來命名實(shí)例變量,那么可以用這個(gè)辦法將其改為自己想要的名字。筆者還是推薦使用默認(rèn)的命名方案,因?yàn)槿绻腥硕紙?jiān)持這套方案,那么寫出來的代碼大家都能看得懂。
總結(jié)下 @synthesize 合成實(shí)例變量的規(guī)則,有以下幾點(diǎn):
如果指定了成員變量的名稱,會生成一個(gè)指定的名稱的成員變量,
如果這個(gè)成員已經(jīng)存在了就不再生成了.
如果是
@synthesize foo;還會生成一個(gè)名稱為foo的成員變量,也就是說:
如果沒有指定成員變量的名稱會自動生成一個(gè)屬性同名的成員變量,
- 如果是
@synthesize foo = _foo;就不會生成成員變量了.
假如 property 名為 foo,存在一個(gè)名為 _foo 的實(shí)例變量,那么還會自動合成新變量么?
不會。如下圖:

15. 在有了自動合成屬性實(shí)例變量之后,@synthesize還有哪些使用場景?
回答這個(gè)問題前,我們要搞清楚一個(gè)問題,什么情況下不會autosynthesis(自動合成)?
- 同時(shí)重寫了 setter 和 getter 時(shí)
- 重寫了只讀屬性的 getter 時(shí)
- 使用了 @dynamic 時(shí)
- 在 @protocol 中定義的所有屬性
- 在 category 中定義的所有屬性
- 重載的屬性
當(dāng)你在子類中重載了父類中的屬性,你必須 使用 @synthesize 來手動合成ivar。
除了后三條,對其他幾個(gè)我們可以總結(jié)出一個(gè)規(guī)律:當(dāng)你想手動管理 @property 的所有內(nèi)容時(shí),你就會嘗試通過實(shí)現(xiàn) @property 的所有“存取方法”(the accessor methods)或者使用 @dynamic 來達(dá)到這個(gè)目的,這時(shí)編譯器就會認(rèn)為你打算手動管理 @property,于是編譯器就禁用了 autosynthesis(自動合成)。
因?yàn)橛辛?autosynthesis(自動合成),大部分開發(fā)者已經(jīng)習(xí)慣不去手動定義ivar,而是依賴于 autosynthesis(自動合成),但是一旦你需要使用ivar,而 autosynthesis(自動合成)又失效了,如果不去手動定義ivar,那么你就得借助 @synthesize 來手動合成 ivar。
其實(shí),@synthesize 語法還有一個(gè)應(yīng)用場景,但是不太建議大家使用:
可以在類的實(shí)現(xiàn)代碼里通過 @synthesize 語法來指定實(shí)例變量的名字:
@implementation CYLPerson
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end
上述語法會將生成的實(shí)例變量命名為 _myFirstName 與 _myLastName,而不再使用默認(rèn)的名字。一般情況下無須修改默認(rèn)的實(shí)例變量名,但是如果你不喜歡以下劃線來命名實(shí)例變量,那么可以用這個(gè)辦法將其改為自己想要的名字。筆者還是推薦使用默認(rèn)的命名方案,因?yàn)槿绻腥硕紙?jiān)持這套方案,那么寫出來的代碼大家都能看得懂。
舉例說明:應(yī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)就不會生成 ivar(實(shí)例變量/成員變量)。這時(shí)候有兩種選擇:
- 要么如第14行:手動創(chuàng)建 ivar
- 要么如第17行:使用
@synthesize foo = _foo;,關(guān)聯(lián) @property 與 ivar。
更多信息,請戳- 》 When should I use @synthesize explicitly?
16. objc中向一個(gè)nil對象發(fā)送消息將會發(fā)生什么?
在 Objective-C 中向 nil 發(fā)送消息是完全有效的——只是在運(yùn)行時(shí)不會有任何作用:
- 如果一個(gè)方法返回值是一個(gè)對象,那么發(fā)送給nil的消息將返回0(nil)。例如:
Person * motherInlaw = [[aPerson spouse] mother];
如果 spouse 對象為 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是動態(tài)語言,每個(gè)方法在運(yùn)行時(shí)會被動態(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)識
long instance_size OBJC2_UNAVAILABLE; // 該類的實(shí)例變量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 該類的成員變量鏈表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定義的鏈表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法緩存,對象接到一個(gè)消息會根據(jù)isa指針查找消息對象,這時(shí)會在method Lists中遍歷,如果cache了,常用的方法調(diào)用時(shí)就能夠提高調(diào)用的效率。
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;
objc在向一個(gè)對象發(fā)送消息時(shí),runtime庫會根據(jù)對象的isa指針找到該對象實(shí)際所屬的類,然后在該類中的方法列表以及其父類方法列表中尋找方法運(yùn)行,然后在發(fā)送消息的時(shí)候,objc_msgSend方法不會返回值,所謂的返回內(nèi)容都是具體調(diào)用時(shí)執(zhí)行的。
那么,回到本題,如果向一個(gè)nil對象發(fā)送消息,首先在尋找對象的isa指針時(shí)就是0地址返回了,所以不會出現(xiàn)任何錯(cuò)誤。
17. objc中向一個(gè)對象發(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動態(tài)編譯時(shí),會被轉(zhuǎn)意為:
objc_msgSend(obj, @selector(foo));。
18. 什么時(shí)候會報(bào)unrecognized selector的異常?
簡單來說:
當(dāng)調(diào)用該對象上某個(gè)方法,而該對象上沒有實(shí)現(xiàn)這個(gè)方法的時(shí)候,
可以通過“消息轉(zhuǎn)發(fā)”進(jìn)行解決。
簡單的流程如下,在上一題中也提到過:
objc是動態(tài)語言,每個(gè)方法在運(yùn)行時(shí)會被動態(tài)轉(zhuǎn)為消息發(fā)送,即:objc_msgSend(receiver, selector)。
objc在向一個(gè)對象發(fā)送消息時(shí),runtime庫會根據(jù)對象的isa指針找到該對象實(shí)際所屬的類,然后在該類中的方法列表以及其父類方法列表中尋找方法運(yùn)行,如果,在最頂層的父類中依然找不到相應(yīng)的方法時(shí),程序在運(yùn)行時(shí)會掛掉并拋出異常unrecognized selector sent to XXX 。但是在這之前,objc的運(yùn)行時(shí)會給出三次拯救程序崩潰的機(jī)會:
- Method resolution
objc運(yùn)行時(shí)會調(diào)用+resolveInstanceMethod:或者 +resolveClassMethod:,讓你有機(jī)會提供一個(gè)函數(shù)實(shí)現(xiàn)。如果你添加了函數(shù),那運(yùn)行時(shí)系統(tǒng)就會重新啟動一次消息發(fā)送的過程,否則 ,運(yùn)行時(shí)就會移到下一步,消息轉(zhuǎn)發(fā)(Message Forwarding)。
- Fast forwarding
如果目標(biāo)對象實(shí)現(xiàn)了-forwardingTargetForSelector:,Runtime 這時(shí)就會調(diào)用這個(gè)方法,給你把這個(gè)消息轉(zhuǎn)發(fā)給其他對象的機(jī)會。
只要這個(gè)方法返回的不是nil和self,整個(gè)消息發(fā)送的過程就會被重啟,當(dāng)然發(fā)送的對象會變成你返回的那個(gè)對象。否則,就會繼續(xù)Normal Fowarding。
這里叫Fast,只是為了區(qū)別下一步的轉(zhuǎn)發(fā)機(jī)制。因?yàn)檫@一步不會創(chuàng)建任何新的對象,但下一步轉(zhuǎn)發(fā)會創(chuàng)建一個(gè)NSInvocation對象,所以相對更快點(diǎn)。
- Normal forwarding
這一步是Runtime最后一次給你挽救的機(jī)會。首先它會發(fā)送-methodSignatureForSelector:消息獲得函數(shù)的參數(shù)和返回值類型。如果-methodSignatureForSelector:返回nil,Runtime則會發(fā)出-doesNotRecognizeSelector:消息,程序這時(shí)也就掛掉了。如果返回了一個(gè)函數(shù)簽名,Runtime就會創(chuàng)建一個(gè)NSInvocation對象并發(fā)送-forwardInvocation:消息給目標(biāo)對象。
為了能更清晰地理解這些方法的作用,git倉庫里也給出了一個(gè)Demo,名稱叫“ _objc_msgForward_demo ”,可運(yùn)行起來看看。
19. 一個(gè)objc對象如何進(jìn)行內(nèi)存布局?(考慮有父類的情況)
- 所有父類的成員變量和自己的成員變量都會存放在該對象所對應(yīng)的存儲空間中.
- 每一個(gè)對象內(nèi)部都有一個(gè)isa指針,指向他的類對象,類對象中存放著本對象的
- 對象方法列表(對象能夠接收的消息列表,保存在它所對應(yīng)的類對象中)
- 成員變量的列表,
- 屬性列表,
它內(nèi)部也有一個(gè)isa指針指向元對象(meta class),元對象內(nèi)部存放的是類方法列表,類對象內(nèi)部還有一個(gè)superclass的指針,指向他的父類對象。
每個(gè) Objective-C 對象都有相同的結(jié)構(gòu),如下圖所示:

翻譯過來就是
| Objective-C 對象的結(jié)構(gòu)圖 |
|---|
| ISA指針 |
| 根類的實(shí)例變量 |
| 倒數(shù)第二層父類的實(shí)例變量 |
| ... |
| 父類的實(shí)例變量 |
| 類的實(shí)例變量 |
根對象就是NSobject,它的superclass指針指向nil
類對象既然稱為對象,那它也是一個(gè)實(shí)例。類對象中也有一個(gè)isa指針指向它的元類(meta class),即類對象是元類的實(shí)例。元類內(nèi)部存放的是類方法列表,根元類的isa指針指向自己,superclass指針指向NSObject類。
如圖:

20. 一個(gè)objc對象的isa的指針指向什么?有什么作用?
指向他的類對象,從而可以找到對象上的方法
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 中對 self 和 super 的理解。
我們都知道:self 是類的隱藏參數(shù),指向當(dāng)前調(diào)用方法的這個(gè)類的實(shí)例。那 super 呢?
很多人會想當(dāng)然的認(rèn)為“ super 和 self 類似,應(yīng)該是指向父類的指針吧!”。這是很普遍的一個(gè)誤區(qū)。其實(shí) super 是一個(gè) Magic Keyword, 它本質(zhì)是一個(gè)編譯器標(biāo)示符,和 self 是指向的同一個(gè)消息接受者!他們兩個(gè)的不同點(diǎn)在于:super 會告訴編譯器,調(diào)用 class 這個(gè)方法時(shí),要去父類的方法,而不是本類里的。
上面的例子不管調(diào)用[self class]還是[super class],接受消息的對象都是當(dāng)前 Son *xxx 這個(gè)對象。
當(dāng)使用 self 調(diào)用方法時(shí),會從當(dāng)前類的方法列表中開始找,如果沒有,就從父類中再找;而當(dāng)使用 super 時(shí),則從父類的方法列表中開始找。然后調(diào)用父類的這個(gè)方法。
這也就是為什么說“不推薦在 init 方法中使用點(diǎn)語法”,如果想訪問實(shí)例變量 iVar 應(yīng)該使用下劃線( _iVar ),而非點(diǎn)語法( self.iVar )。
點(diǎn)語法( self.iVar )的壞處就是子類有可能覆寫 setter 。假設(shè) Person 有一個(gè)子類叫 ChenPerson,這個(gè)子類專門表示那些姓“陳”的人。該子類可能會覆寫 lastName 屬性所對應(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__, @"會調(diào)用這個(gè)方法,想一下為什么?");
}
@end
在基類 Person 的默認(rèn)初始化方法中,可能會將姓氏設(shè)為空字符串。此時(shí)若使用點(diǎn)語法( self.lastName )也即 setter 設(shè)置方法,那么調(diào)用將會是子類的設(shè)置方法,如果在剛剛的 setter 代碼中采用設(shè)置方法一,那么就會拋出異常,
為了方便采用打印的方式展示,究竟發(fā)生了什么,我們使用設(shè)置方法二。
如果基類的代碼是這樣的:
//
// Person.m
// nil對象調(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__, @"根本不會調(diào)用這個(gè)方法");
_lastName = @"炎黃";
}
@end
那么打印結(jié)果將會是這樣的:
??類名與方法名:-[ChenPerson setLastName:](在第36行),描述:會調(diào)用這個(gè)方法,想一下為什么?
??類名與方法名:-[ChenPerson init](在第19行),描述:ChenPerson
??類名與方法名:-[ChenPerson init](在第20行),描述:ChenPerson
我在倉庫里也給出了一個(gè)相應(yīng)的 Demo(名字叫:Demo_21題_下面的代碼輸出什么)。有興趣可以跑起來看一下,主要看下他是怎么打印的,思考下為什么這么打印。
接下來讓我們利用 runtime 的相關(guān)知識來驗(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í),會轉(zhuǎn)化成 objc_msgSend函數(shù)??聪潞瘮?shù)定義:
id objc_msgSend(id self, SEL op, ...)
我們把 self 做為第一個(gè)參數(shù)傳遞進(jìn)去。
而在調(diào)用 [super class]時(shí),會轉(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開源代碼對- (Class)class方法的實(shí)現(xiàn):
- (Class)class {
return object_getClass(self);
}
而當(dāng)調(diào)用 [super class]時(shí),會轉(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找到對應(yīng)的IMP地址?(分別考慮類方法和實(shí)例方法)
每一個(gè)類對象中都一個(gè)方法列表,方法列表中記錄著方法的名稱,方法實(shí)現(xiàn),以及參數(shù)類型,其實(shí)selector本質(zhì)就是方法名稱,通過這個(gè)方法名稱就可以在方法列表中找到對應(yīng)的方法實(shí)現(xiàn).
23. 使用runtime Associate方法關(guān)聯(lián)的對象,需要在主對象dealloc的時(shí)候釋放么?
- 在ARC下不需要。
- <p><del> 在MRC中,對于使用retain或copy策略的需要 。</del></p>在MRC下也不需要
無論在MRC下還是ARC下均不需要。
2011年版本的Apple API 官方文檔 - Associative References 一節(jié)中有一個(gè)MRC環(huán)境下的例子:
// 在MRC下,使用runtime Associate方法關(guān)聯(lián)的對象,不需要在主對象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
overviewis still valid because theOBJC_ASSOCIATION_RETAINpolicy specifies that the array retains the associated object. When the array is deallocated, however (at point 2),overviewis released and so in this case also deallocated.
我們可以看到,在[array release];之后,overview就會被release釋放掉了。
既然會被銷毀,那么具體在什么時(shí)間點(diǎn)?
根據(jù) WWDC 2011, Session 322 (第36分22秒) 中發(fā)布的內(nèi)存銷毀時(shí)間表,被關(guān)聯(lián)的對象在生命周期內(nèi)要比對象本身釋放的晚很多。它們會在被 NSObject -dealloc 調(diào)用的 object_dispose() 方法中釋放。
對象的內(nèi)存銷毀時(shí)間表,分四個(gè)步驟:
// 對象的內(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)榱? * 對象正在被銷毀,生命周期即將結(jié)束.
* 不能再有新的 __weak 弱引用, 否則將指向 nil.
* 調(diào)用 [self dealloc]
2. 父類 調(diào)用 -dealloc
* 繼承關(guān)系中最底層的父類 在調(diào)用 -dealloc
* 如果是 MRC 代碼 則會手動釋放實(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)的對象
* 解除所有 __weak 引用
* 調(diào)用 free()
對象的內(nèi)存銷毀時(shí)間表:參考鏈接。
24. objc中的類方法和實(shí)例方法有什么本質(zhì)區(qū)別和聯(lián)系?
類方法:
- 類方法是屬于類對象的
- 類方法只能通過類對象調(diào)用
- 類方法中的self是類對象
- 類方法可以調(diào)用其他的類方法
- 類方法中不能訪問成員變量
- 類方法中不定直接調(diào)用對象方法
實(shí)例方法:
- 實(shí)例方法是屬于實(shí)例對象的
- 實(shí)例方法只能通過實(shí)例對象調(diào)用
- 實(shí)例方法中的self是實(shí)例對象
- 實(shí)例方法中可以訪問成員變量
- 實(shí)例方法中直接調(diào)用實(shí)例方法
- 實(shí)例方法中也可以調(diào)用類方法(通過類名)
Posted by 微博@iOS程序犭袁
原創(chuàng)文章,版權(quán)聲明:自由轉(zhuǎn)載-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0