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

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

  1. 因?yàn)楦割愔羔樋梢灾赶蜃宇悓ο?使用 copy 的目的是為了讓本對象的屬性不受外界影響,使用 copy 無論給我傳入是一個(gè)可變對象還是不可對象,我本身持有的就是一個(gè)不可變的副本.
  2. 如果我們使用是 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。)

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

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

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

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

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

比如以下代碼:

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

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

[string appendString:@"origion!"]

stringCopy 的值也不會因此改變,但是如果不使用 copy,stringCopy 的值就會被改變。
集合類對象以此類推。
所以,

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

2、集合類對象的copy與mutableCopy

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

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

查看內(nèi)容,可以看到 copyArray 和 array 的地址是一樣的,而 mCopyArray 和 array 的地址是不同的。說明 copy 操作進(jìn)行了指針拷貝,mutableCopy 進(jìn)行了內(nèi)容拷貝。但需要強(qiáng)調(diào)的是:此處的內(nèi)容拷貝,僅僅是拷貝 array 這個(gè)對象,array 集合內(nèi)部的元素仍然是指針拷貝。這和上面的非集合 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é)論和非集合類的非常相似。

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

14. @synthesize合成實(shí)例變量的規(guī)則是什么?假如property名為foo,存在一個(gè)名為_foo的實(shí)例變量,那么還會自動合成新變量么?

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

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

這些說法,筆者下文中,可能都會用到,指的是一個(gè)東西。

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

enter image description here
enter image description here

如果使用了屬性的話,那么編譯器就會自動編寫訪問屬性所需的方法,此過程叫做“自動合成”( 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):

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

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

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

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

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

假如 property 名為 foo,存在一個(gè)名為 _foo 的實(shí)例變量,那么還會自動合成新變量么?
不會。如下圖:

enter image description here
enter image description here

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

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

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

當(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ò):


enter image description here
enter image description here

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

  1. 要么如第14行:手動創(chuàng)建 ivar
  2. 要么如第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í)不會有任何作用:

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

如果 spouse 對象為 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是動態(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千行左右)

enter image description here
enter image description here

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

((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ī)會:

  1. Method resolution

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

  1. 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)。

  1. 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指針,指向他的類對象,類對象中存放著本對象的
  1. 對象方法列表(對象能夠接收的消息列表,保存在它所對應(yīng)的類對象中)
  2. 成員變量的列表,
  3. 屬性列表,

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

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

enter image description here
enter image description here

翻譯過來就是

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

如圖:


enter image description here
enter image description here

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 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就會被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)系?

類方法:

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

實(shí)例方法:

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容