iOS面試題集錦 – iPhone手機開發 iPhone軟體開發教學課程

1.什麼情況使用 weak 關鍵字,相比 assign 有什麼不同?
2.怎麼用 copy 關鍵字?
3.這個寫法會出什麼問題: @property (copy) NSMutableArray *array;
4.如何讓自己的類用 copy 修飾符?如何重寫帶 copy 關鍵字的 setter?
5.@property 的本質是什麼?ivar、getter、setter 是如何生成並添加到這個類中的
6.@protocol 和 category 中如何使用 @property
7.runtime 如何實現 weak 屬性
8.@property中有哪些屬性關鍵字?/ @property 後面可以有哪些修飾符?
9.weak屬性需要在dealloc中置nil麼?
10.@synthesize和@dynamic分別有什麼作用?
11.ARC下,不顯式指定任何屬性關鍵字時,默認的關鍵字都有哪些?
12.用@property聲明的NSString(或NSArray,NSDictionary)經常使用13.copy關鍵字,為什麼?如果改用strong關鍵字,可能造成什麼問題?
14.對非集合類對象的copy操作
15集合類對象的copy與mutableCopy
16.@synthesize合成實例變量的規則是什麼?假如property名為foo,存在一個名為_foo的實例變量,那麼還會自動合成新變量麼?
17.在有瞭自動合成屬性實例變量之後,@synthesize還有哪些使用場景?
18.objc中向一個nil對象發送消息將會發生什麼?
19.objc中向一個對象發送消息[obj foo]和objc_msgSend()函數之間有什麼關系?
20.什麼時候會報unrecognized selector的異常?
21.一個objc對象如何進行內存佈局?(考慮有父類的情況)
22.一個objc對象的isa的指針指向什麼?有什麼作用?
23.下面的代碼輸出什麼?
@implementation Son : Father
– (id)init
{
self = [super init];
if (self) {
NSLog(@”%@”, NSStringFromClass([self class]));
NSLog(@”%@”, NSStringFromClass([super class]));
}
return self;
}
@end
24._objc_msgForward 函數是做什麼的,直接調用它將會發生什麼?
25.runtime如何實現weak變量的自動置nil?
26.能否向編譯後得到的類中增加實例變量?能否向運行時創建的類中添加實例變量?為什麼?
27.runloop和線程有什麼關系?
28.runloop的mode作用是什麼?
29.以+ scheduledTimerWithTimeInterval…的方式觸發的timer,在滑動頁面上的列表時,timer會暫定回調,為什麼?如何解決?
30.猜想runloop內部是如何實現的?
31.objc使用什麼機制管理對象內存?
32.ARC通過什麼方式幫助開發者管理內存?
33.不手動指定autoreleasepool的前提下,一個autorealese對象在什麼時刻釋放?(比如在一個vc的viewDidLoad中創建)
34.BAD_ACCESS在什麼情況下出現?
35.蘋果是如何實現autoreleasepool的?
36.使用block時什麼情況會發生引用循環,如何解決?
37.在block內如何修改block外部變量?
38.使用系統的某些block api(如UIView的block版本寫動畫時),是否也考慮引用循環問題?
39.GCD的隊列(dispatch_queue_t)分哪兩種類型?
40.如何用GCD同步若幹個異步調用?(如根據若幹個url異步加載多張圖片,然後在都下載完成後合成一張整圖)
41.dispatch_barrier_async的作用是什麼?
42.蘋果為什麼要廢棄dispatch_get_current_queue?
43.以下代碼運行結果如何?
– (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@”1”);
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@”2”);
});
NSLog(@”3”);
}
44.addObserver:forKeyPath:options:context:各個參數的作用分別是什麼,observer中需要實現哪個方法才能獲得KVO回調?
45.如何手動觸發一個value的KVO
46.若一個類有實例變量 NSString *_foo ,調用setValue:forKey:時,可以以foo還是 _foo 作為key?
47.KVC的keyPath中的集合運算符如何使用?
48.KVC和KVO的keyPath一定是屬性麼?
49.如何關閉默認的KVO的默認實現,並進入自定義的KVO實現?
50.apple用什麼方式實現對一個對象的KVO?
51.IBOutlet連出來的視圖屬性為什麼可以被設置成weak?
52.IB中User Defined Runtime Attributes如何使用?
53.如何調試BAD_ACCESS錯誤
54.lldb(gdb)常用的調試命令?

1.什麼情況使用 weak 關鍵字,相比 assign 有什麼不同?
什麼情況使用 weak 關鍵字?
在 ARC 中,在有可能出現循環引用的時候,往往要通過讓其中一端使用 weak 來解決,比如: delegate 代理屬性
自身已經對它進行一次強引用,沒有必要再強引用一次,此時也會使用 weak,自定義 IBOutlet 控件屬性一般也使用 weak;當然,也可以使用strong。在下文也有論述:《IBOutlet連出來的視圖屬性為什麼可以被設置成weak?》

不同點:
weak 此特質表明該屬性定義瞭一種“非擁有關系” (nonowning relationship)。為這種屬性設置新值時,設置方法既不保留新值,也不釋放舊值。此特質同assign類似, 然而在屬性所指的對象遭到摧毀時,屬性值也會清空(nil out)。 而 assign 的“設置方法”隻會執行針對“純量類型” (scalar type,例如 CGFloat 或 NSlnteger 等)的簡單賦值操作。

assigin 可以用非 OC 對象,而 weak 必須用於 OC 對象

2.怎麼用 copy 關鍵字?
NSString、NSArray、NSDictionary 等等經常使用copy關鍵字,是因為他們有對應的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary;
block 也經常使用 copy 關鍵字,具體原因見官方文檔:Objects Use Properties to Keep Track of Blocks:

block 使用 copy 是從 MRC 遺留下來的“傳統”,在 MRC 中,方法內部的 block 是在棧區的,使用 copy 可以把它放到堆區.在 ARC 中寫不寫都行:對於 block 使用 copy 還是 strong 效果是一樣的,但寫上 copy 也無傷大雅,還能時刻提醒我們:編譯器自動對 block 進行瞭 copy 操作。如果不寫 copy ,該類的調用者有可能會忘記或者根本不知道“編譯器會自動對 block 進行瞭 copy 操作”,他們有可能會在調用之前自行拷貝屬性值。這種操作多餘而低效。

enter image description here

下面做下解釋: copy 此特質所表達的所屬關系與 strong 類似。然而設置方法並不保留新值,而是將其“拷貝” (copy)。 當屬性類型為 NSString 時,經常用此特質來保護其封裝性,因為傳遞給設置方法的新值有可能指向一個 NSMutableString 類的實例。這個類是 NSString 的子類,表示一種可修改其值的字符串,此時若是不拷貝字符串,那麼設置完屬性之後,字符串的值就可能會在對象不知情的情況下遭人更改。所以,這時就要拷貝一份“不可變” (immutable)的字符串,確保對象中的字符串值不會無意間變動。隻要實現屬性所用的對象是“可變的”
(mutable),就應該在設置新屬性值時拷貝一份。

用 @property 聲明 NSString、NSArray、NSDictionary 經常使用 copy 關鍵字,是因為他們有對應的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary,他們之間可能進行賦值操作,為確保對象中的字符串值不會無意間變動,應該在設置新屬性值時拷貝一份。
該問題在下文中也有論述:用@property聲明的NSString(或NSArray,NSDictionary)經常使用copy關鍵字,為什麼?如果改用strong關鍵字,可能造成什麼問題?

3.這個寫法會出什麼問題: @property (copy) NSMutableArray *array;

兩個問題:1、添加,刪除,修改數組內的元素的時候,程序會因為找不到對應的方法而崩潰.因為 copy 就是復制一個不可變 NSArray 的對象;2、使用瞭 atomic 屬性會嚴重影響性能 ;

第1條的相關原因在下文中有論述《用@property聲明的NSString(或NSArray,NSDictionary)經常使用 copy 關鍵字,為什麼?如果改用strong關鍵字,可能造成什麼問題?》 以及上文《怎麼用 copy 關鍵字?》也有論述。

比如下面的代碼就會發生崩潰

// .h文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 下面的代碼就會發生崩潰

@property (nonatomic, copy) NSMutableArray *mutableArray;
// .m文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 下面的代碼就會發生崩潰

NSMutableArray *array = [NSMutableArray arrayWithObjects:@1,@2,nil];
self.mutableArray = array;
[self.mutableArray removeObjectAtIndex:0];
接下來就會奔潰:

-[__NSArrayI removeObjectAtIndex:]: unrecognized selector sent to instance 0x7fcd1bc30460
第2條原因,如下:

該屬性使用瞭同步鎖,會在創建時生成一些額外的代碼用於幫助編寫多線程程序,這會帶來性能問題,通過聲明 nonatomic 可以節省這些雖然很小但是不必要額外開銷。
在默認情況下,由編譯器所合成的方法會通過鎖定機制確保其原子性(atomicity)。如果屬性具備 nonatomic 特質,則不使用同步鎖。請註意,盡管沒有名為“atomic”的特質(如果某屬性不具備 nonatomic 特質,那它就是“原子的”(atomic))。

在iOS開發中,你會發現,幾乎所有屬性都聲明為 nonatomic。

一般情況下並不要求屬性必須是“原子的”,因為這並不能保證“線程安全” ( thread safety),若要實現“線程安全”的操作,還需采用更為深層的鎖定機制才行。例如,一個線程在連續多次讀取某屬性值的過程中有別的線程在同時改寫該值,那麼即便將屬性聲明為 atomic,也還是會讀到不同的屬性值。

因此,開發iOS程序時一般都會使用 nonatomic 屬性。但是在開發 Mac OS X 程序時,使用 atomic 屬性通常都不會有性能瓶頸。

4.如何讓自己的類用 copy 修飾符?如何重寫帶 copy 關鍵字的 setter?

若想令自己所寫的對象具有拷貝功能,則需實現 NSCopying 協議。如果自定義的對象分為可變版本與不可變版本,那麼就要同時實現 NSCopying 與 NSMutableCopying 協議。
具體步驟:

需聲明該類遵從 NSCopying 協議
實現 NSCopying 協議。該協議隻有一個方法:

(id)copyWithZone:(NSZone *)zone;
註意:一提到讓自己的類用 copy 修飾符,我們總是想覆寫copy方法,其實真正需要實現的卻是 “copyWithZone” 方法。

以第一題的代碼為例:
// .h文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 修改完的代碼

typedef NS_ENUM(NSInteger, CYLSex) {
    CYLSexMan,
    CYLSexWoman
};

@interface CYLUser : NSObject

@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

然後實現協議中規定的方法:

(id)copyWithZone:(NSZone *)zone {
CYLUser *copy = [[[self class] allocWithZone:zone]
initWithName:_name
age:_age
sex:_sex];
return copy;
}
但在實際的項目中,不可能這麼簡單,遇到更復雜一點,比如類對象中的數據結構可能並未在初始化方法中設置好,需要另行設置。舉個例子,假如 CYLUser 中含有一個數組,與其他 CYLUser 對象建立或解除朋友關系的那些方法都需要操作這個數組。那麼在這種情況下,你得把這個包含朋友對象的數組也一並拷貝過來。下面列出瞭實現此功能所需的全部代碼:

// .h文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 以第一題《風格糾錯題》裡的代碼為例

typedef NS_ENUM(NSInteger, CYLSex) {
CYLSexMan,
CYLSexWoman
};

@interface CYLUser : NSObject

@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] allocWithZone:zone]
initWithName:_name
age:_age
sex:_sex];
copy->_friends = [[NSMutableSet alloc] initWithSet:_friends
copyItems:YES];
return copy;
}

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

如果你所寫的對象需要深拷貝,那麼可考慮新增一個專門執行深拷貝的方法。
【註:深淺拷貝的概念,在下文中有介紹,詳見下文的:用@property聲明的 NSString(或NSArray,NSDictionary)經常使用 copy 關鍵字,為什麼?如果改用 strong 關鍵字,可能造成什麼問題?】

在例子中,存放朋友對象的 set 是用 “copyWithZone:” 方法來拷貝的,這種淺拷貝方式不會逐個復制 set 中的元素。若需要深拷貝的話,則可像下面這樣,編寫一個專供深拷貝所用的方法:

(id)deepCopy {
CYLUser *copy = [[[self class] allocWithZone:zone]
initWithName:_name
age:_age
sex:_sex];
copy->_friends = [[NSMutableSet alloc] initWithSet:_friends
copyItems:YES];
return copy;
}
至於如何重寫帶 copy 關鍵字的 setter這個問題,

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

(void)setName:(NSString *)name {
//[_name release];
_name = [name copy];
}
不過也有爭議,有人說“蘋果如果像下面這樣幹,是不是效率會高一些?”

(void)setName:(NSString *)name {
if (_name != name) {
//[_name release];//MRC
_name = [name copy];
}
}
這樣真得高效嗎?不見得!這種寫法“看上去很美、很合理”,但在實際開發中,它更像下圖裡的做法:

enter image description here

克強總理這樣評價你的代碼風格:

enter image description here

我和總理的意見基本一致:

老百姓 copy 一下,咋就這麼難?
你可能會說:

之所以在這裡做if判斷 這個操作:是因為一個 if 可能避免一個耗時的copy,還是很劃算的。 (在剛剛講的:《如何讓自己的類用 copy 修飾符?》裡的那種復雜的copy,我們可以稱之為 “耗時的copy”,但是對 NSString 的 copy 還稱不上。)

但是你有沒有考慮過代價:

你每次調用 setX: 都會做 if 判斷,這會讓 setX: 變慢,如果你在 setX:寫瞭一串復雜的 if+elseif+elseif+… 判斷,將會更慢。
要回答“哪個效率會高一些?”這個問題,不能脫離實際開發,就算 copy 操作十分耗時,if 判斷也不見得一定會更快,除非你把一個“ @property他當前的值 ”賦給瞭他自己,代碼看起來就像:

[a setX:x1];
[a setX:x1]; //你確定你要這麼幹?與其在setter中判斷,為什麼不把代碼寫好?
或者

[a setX:[a x]]; //隊友咆哮道:你在幹嘛?!!
不要在 setter 裡進行像 if(_obj != newObj) 這樣的判斷。(該觀點參考鏈接: How To Write Cocoa Object Setters: Principle 3: Only Optimize After You Measure )
什麼情況會在 copy setter 裡做 if 判斷? 例如,車速可能就有最高速的限制,車速也不可能出現負值,如果車子的最高速為300,則 setter 的方法就要改寫成這樣:

-(void)setSpeed:(int)_speed{
if(_speed < 0) speed = 0;
if(_speed > 300) speed = 300;
_speed = speed;
}
回到這個題目,如果單單就上文的代碼而言,我們不需要也不能重寫 name 的 setter :由於是 name 是隻讀屬性,所以編譯器不會為其創建對應的“設置方法”,用初始化方法設置好屬性值之後,就不能再改變瞭。( 在本例中,之所以還要聲明屬性的“內存管理語義”–copy,是因為:如果不寫 copy,該類的調用者就不知道初始化方法裡會拷貝這些屬性,他們有可能會在調用初始化方法之前自行拷貝屬性值。這種操作多餘而低效)。

那如何確保 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;
}

5.@property 的本質是什麼?ivar、getter、setter 是如何生成並添加到這個類中的

@property 的本質是什麼?

@property = ivar + getter + setter;
下面解釋下:

“屬性” (property)有兩大概念:ivar(實例變量)、存取方法(access method = getter + setter)。
“屬性” (property)作為 Objective-C 的一項特性,主要的作用就在於封裝對象中的數據。 Objective-C 對象通常會把其所需要的數據保存為各種實例變量。實例變量一般通過“存取方法”(access method)來訪問。其中,“獲取方法” (getter)用於讀取變量值,而“設置方法” (setter)用於寫入變量值。這個概念已經定型,並且經由“屬性”這一特性而成為 Objective-C 2.0 的一部分。 而在正規的 Objective-C 編碼風格中,存取方法有著嚴格的命名規范。
正因為有瞭這種嚴格的命名規范,所以 Objective-C 這門語言才能根據名稱自動創建出存取方法。其實也可以把屬性當做一種關鍵字,其表示:

編譯器會自動寫出一套存取方法,用以訪問給定類型中具有給定名稱的變量。 所以你也可以這麼說:

@property = getter + setter;
例如下面這個類:

@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
ivar、getter、setter 是如何生成並添加到這個類中的?

“自動合成”( autosynthesis)
完成屬性定義後,編譯器會自動編寫訪問這些屬性所需的方法,此過程叫做“自動合成”(autosynthesis)。需要強調的是,這個過程由編譯 器在編譯期執行,所以編輯器裡看不到這些“合成方法”(synthesized method)的源代碼。除瞭生成方法代碼 getter、setter 之外,編譯器還要自動向類中添加適當類型的實例變量,並且在屬性名前面加下劃線,以此作為實例變量的名字。在前例中,會生成兩個實例變量,其名稱分別為 _firstName 與 _lastName。也可以在類的實現代碼裡通過 @synthesize
語法來指定實例變量的名字.

@implementation Person
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end
我為瞭搞清屬性是怎麼實現的,曾經反編譯過相關的代碼,他大致生成瞭五個東西

OBJC_IVAR_類名屬性名稱
:該屬性的“偏移量” (offset),這個偏移量是“硬編碼” (hardcode),表示該變量距離存放對象的內存區域的起始地址有多遠。
setter 與 getter 方法對應的實現函數
ivar_list :成員變量列表
method_list :方法列表
prop_list :屬性列表
也就是說我們每次在增加一個屬性,系統都會在 ivar_list 中添加一個成員變量的描述,在 method_list 中增加 setter 與 getter 方法的描述,在屬性列表中增加一個屬性的描述,然後計算該屬性在對象中的偏移量,然後給出 setter 與 getter 方法對應的實現,在 setter 方法中從偏移量的位置開始賦值,在 getter 方法中從偏移量開始取值,為瞭能夠讀取正確字節數,系統對象偏移量的指針類型進行瞭類型強轉.

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

在 protocol 中使用 property 隻會生成 setter 和 getter 方法聲明,我們使用屬性的目的,是希望遵守我協議的對象能實現該屬性
category 使用 @property 也是隻會生成 setter 和 getter 方法的聲明,如果我們真的需要給 category 增加屬性的實現,需要借助於運行時的兩個函數:

objc_setAssociatedObject
objc_getAssociatedObject
7.runtime 如何實現 weak 屬性

要實現 weak 屬性,首先要搞清楚 weak 屬性的特點:

weak 此特質表明該屬性定義瞭一種“非擁有關系” (nonowning relationship)。為這種屬性設置新值時,設置方法既不保留新值,也不釋放舊值。此特質同 assign 類似, 然而在屬性所指的對象遭到摧毀時,屬性值也會清空(nil out)。
那麼 runtime 如何實現 weak 變量的自動置nil?

runtime 對註冊的類, 會進行佈局,對於 weak 對象會放入一個 hash 表中。 用 weak 指向的對象內存地址作為 key,當此對象的引用計數為0的時候會 dealloc,假如 weak 指向的對象內存地址是a,那麼就會以a為鍵, 在這個 weak 表中搜索,找到所有以a為鍵的 weak 對象,從而設置為 nil。
(註:在下文的《使用runtime Associate方法關聯的對象,需要在主對象dealloc的時候釋放麼?》裡給出的“對象的內存銷毀時間表”也提到__weak引用的解除時間。)

我們可以設計一個函數(偽代碼)來表示上述機制:

objc_storeWeak(&a, b)函數:

objc_storeWeak函數把第二個參數–賦值對象(b)的內存地址作為鍵值key,將第一個參數–weak修飾的屬性變量(a)的內存地址(&a)作為value,註冊到 weak 表中。如果第二個參數(b)為0(nil),那麼把變量(a)的內存地址(&a)從weak表中刪除,

你可以把objc_storeWeak(&a, b)理解為:objc_storeWeak(value, key),並且當key變nil,將value置nil。

在b非nil時,a和b指向同一個內存地址,在b變nil時,a變nil。此時向a發送消息不會崩潰:在Objective-C中向nil發送消息是安全的。

而如果a是由 assign 修飾的,則: 在 b 非 nil 時,a 和 b 指向同一個內存地址,在 b 變 nil 時,a 還是指向該內存地址,變野指針。此時向 a 發送消息極易崩潰。

下面我們將基於objc_storeWeak(&a, b)函數,使用偽代碼模擬“runtime如何實現weak屬性”:

// 使用偽代碼模擬:runtime如何實現weak屬性
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong

id obj1;
objc_initWeak(&obj1, obj);
/obj引用計數變為0,變量作用域結束/
objc_destroyWeak(&obj1);
下面對用到的兩個方法objc_initWeak和objc_destroyWeak做下解釋:

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

下面分別介紹下方法的內部實現:

objc_initWeak函數的實現是這樣的:在將“附有weak修飾符的變量(obj1)”初始化為0(nil)後,會將“賦值對象”(obj)作為參數,調用objc_storeWeak函數。

obj1 = 0;
obj_storeWeak(&obj1, obj);
也就是說:

weak 修飾的指針默認值是 nil (在Objective-C中向nil發送消息是安全的)
然後obj_destroyWeak函數將0(nil)作為參數,調用objc_storeWeak函數。

objc_storeWeak(&obj1, 0);

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

// 使用偽代碼模擬:runtime如何實現weak屬性
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong

id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
/* … obj的引用計數變為0,被置nil … */
objc_storeWeak(&obj1, 0);
objc_storeWeak 函數把第二個參數–賦值對象(obj)的內存地址作為鍵值,將第一個參數–weak修飾的屬性變量(obj1)的內存地址註冊到 weak 表中。如果第二個參數(obj)為0(nil),那麼把變量(obj1)的地址從 weak 表中刪除,在後面的相關一題會詳解。

使用偽代碼是為瞭方便理解,下面我們“真槍實彈”地實現下:

如何讓不使用weak修飾的@property,擁有weak的效果。
我們從setter方法入手:

(void)setObject:(NSObject *)object
{
objc_setAssociatedObject(self, “object”, object, OBJC_ASSOCIATION_ASSIGN);
[object cyl_runAtDealloc:^{
_object = nil;
}];
}
也就是有兩個步驟:

在setter方法中做如下設置:

  objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN);

在屬性所指的對象遭到摧毀時,屬性值也會清空(nil out)。做到這點,同樣要借助 runtime:

//要銷毀的目標對象
id objectToBeDeallocated;
//可以理解為一個“事件”:當上面的目標對象銷毀時,同時要發生的“事件”。
id objectWeWantToBeReleasedWhenThatHappens;
objc_setAssociatedObject(objectToBeDeallocted,
someUniqueKey,
objectWeWantToBeReleasedWhenThatHappens,
OBJC_ASSOCIATION_RETAIN);
知道瞭思路,我們就開始實現 cyl_runAtDealloc 方法,實現過程分兩部分:

第一部分:創建一個類,可以理解為一個“事件”:當目標對象銷毀時,同時要發生的“事件”。借助 block 執行“事件”。

// .h文件

// .h文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 這個類,可以理解為一個“事件”:當目標對象銷毀時,同時要發生的“事件”。借助block執行“事件”。

typedef void (^voidBlock)(void);

@interface CYLBlockExecutor : NSObject

(id)initWithBlock:(voidBlock)block;

@end
// .m文件

// .m文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 這個類,可以理解為一個“事件”:當目標對象銷毀時,同時要發生的“事件”。借助block執行“事件”。

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實現cyl_runAtDealloc方法

// CYLNSObject+RunAtDealloc.h文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 利用runtime實現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實現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
使用方法: 導入

#import "CYLNSObject+RunAtDealloc.h"

然後就可以使用瞭:

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

[foo cyl_runAtDealloc:^{
NSLog(@”正在釋放foo!”);
}];
如果對 cyl_runAtDealloc 的實現原理有興趣,可以看下這篇博文 Fun With the Objective-C Runtime: Run Code at Deallocation of Any Object

8.@property中有哪些屬性關鍵字?/ @property 後面可以有哪些修飾符?

屬性可以擁有的特質分為四類:

原子性— nonatomic 特質

在默認情況下,由編譯器合成的方法會通過鎖定機制確保其原子性(atomicity)。如果屬性具備 nonatomic 特質,則不使用同步鎖。請註意,盡管沒有名為“atomic”的特質(如果某屬性不具備 nonatomic 特質,那它就是“原子的” ( atomic) ),但是仍然可以在屬性特質中寫明這一點,編譯器不會報錯。若是自己定義存取方法,那麼就應該遵從與屬性特質相符的原子性。

讀/寫權限—readwrite(讀寫)、readonly (隻讀)

內存管理語義—assign、strong、 weak、unsafe_unretained、copy
方法名—getter= 、setter=

getter=的樣式:

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

setter=這種不常用,也不推薦使用。故不在這裡給出寫法。)

setter=一般用在特殊的情境下,比如:

在數據反序列化、轉模型的過程中,服務器返回的字段如果以 init 開頭,所以你需要定義一個 init 開頭的屬性,但默認生成的 setter 與 getter 方法也會以 init 開頭,而編譯器會把所有以 init 開頭的方法當成初始化方法,而初始化方法隻能返回 self 類型,因此編譯器會報錯。

這時你就可以使用下面的方式來避免編譯器報錯:

@property(nonatomic, strong, getter=p_initBy, setter=setP_initBy:)NSString *initBy;
另外也可以用關鍵字進行特殊說明,來避免編譯器報錯:

@property(nonatomic, readwrite, copy, null_resettable) NSString *initBy;
– (NSString *)initBy attribute((objc_method_family(none)));
不常用的:nonnull,null_resettable,nullable
9.weak屬性需要在dealloc中置nil麼?

不需要。

在ARC環境無論是強指針還是弱指針都無需在 dealloc 設置為 nil , ARC 會自動幫我們處理
即便是編譯器不幫我們做這些,weak也不需要在 dealloc 中置nil:

正如上文的:runtime 如何實現 weak 屬性 中提到的:

我們模擬下 weak 的 setter 方法,應該如下:

(void)setObject:(NSObject *)object
{
objc_setAssociatedObject(self, “object”, object, OBJC_ASSOCIATION_ASSIGN);
[object cyl_runAtDealloc:^{
_object = nil;
}];
}
也即:

在屬性所指的對象遭到摧毀時,屬性值也會清空(nil out)。
10.@synthesize和@dynamic分別有什麼作用?

@property有兩個對應的詞,一個是 @synthesize,一個是 @dynamic。如果 @synthesize和 @dynamic都沒寫,那麼默認的就是@syntheszie var = _var;
@synthesize 的語義是如果你沒有手動實現 setter 方法和 getter 方法,那麼編譯器會自動為你加上這兩個方法。
@dynamic 告訴編譯器:屬性的 setter 與 getter 方法由用戶自己實現,不自動生成。(當然對於 readonly 的屬性隻需提供 getter 即可)。假如一個屬性被聲明為 @dynamic var,然後你沒有提供 @setter方法和 @getter 方法,編譯的時候沒問題,但是當程序運行到 instance.var = someVar,由於缺 setter 方法會導致程序崩潰;或者當運行到 someVar = var 時,由於缺 getter 方法同樣會導致崩潰。編譯時沒問題,運行時才執行相應的方法,這就是所謂的動態綁定。
11.ARC下,不顯式指定任何屬性關鍵字時,默認的關鍵字都有哪些?

對應基本數據類型默認關鍵字是

atomic,readwrite,assign

對於普通的 Objective-C 對象

atomic,readwrite,strong

參考鏈接:

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

Variable property attributes or Modifiers in iOS

12.用@property聲明的NSString(或NSArray,NSDictionary)經常使用copy關鍵字,為什麼?如果改用strong關鍵字,可能造成什麼問題?

因為父類指針可以指向子類對象,使用 copy 的目的是為瞭讓本對象的屬性不受外界影響,使用 copy 無論給我傳入是一個可變對象還是不可對象,我本身持有的就是一個不可變的副本.
如果我們使用是 strong ,那麼這個屬性就有可能指向一個可變對象,如果這個可變對象在外部被修改瞭,那麼會影響該屬性.

copy 此特質所表達的所屬關系與 strong 類似。然而設置方法並不保留新值,而是將其“拷貝” (copy)。 當屬性類型為 NSString 時,經常用此特質來保護其封裝性,因為傳遞給設置方法的新值有可能指向一個 NSMutableString 類的實例。這個類是 NSString 的子類,表示一種可修改其值的字符串,此時若是不拷貝字符串,那麼設置完屬性之後,字符串的值就可能會在對象不知情的情況下遭人更改。所以,這時就要拷貝一份“不可變” (immutable)的字符串,確保對象中的字符串值不會無意間變動。隻要實現屬性所用的對象是“可變的”
(mutable),就應該在設置新屬性值時拷貝一份。

舉例說明:

定義一個以 strong 修飾的 array:

@property (nonatomic ,readwrite, strong) NSArray *array;
然後進行下面的操作:

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);

打印結果如下所示:

2015-09-27 19:10:32.523 CYLArrayCopyDmo[10681:713670] (
)
2015-09-27 19:10:32.524 CYLArrayCopyDmo[10681:713670] (
1,
2,
3,
4
)
(詳見倉庫內附錄的 Demo。)

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

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

在非集合類對象中:對 immutable 對象進行 copy 操作,是指針復制,mutableCopy 操作時內容復制;對 mutable 對象進行 copy 和 mutableCopy 都是內容復制。用代碼簡單表示如下:

[immutableObject copy] // 淺復制
[immutableObject mutableCopy] //深復制
[mutableObject copy] //深復制
[mutableObject mutableCopy] //深復制
比如以下代碼:

NSMutableString *string = [NSMutableString stringWithString:@”origin”];//copy
NSString *stringCopy = [string copy];
查看內存,會發現 string、stringCopy 內存地址都不一樣,說明此時都是做內容拷貝、深拷貝。即使你進行如下操作:

[string appendString:@”origion!”]
stringCopy 的值也不會因此改變,但是如果不使用 copy,stringCopy 的值就會被改變。 集合類對象以此類推。 所以,

用 @property 聲明 NSString、NSArray、NSDictionary 經常使用 copy 關鍵字,是因為他們有對應的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary,他們之間可能進行賦值操作,為確保對象中的字符串值不會無意間變動,應該在設置新屬性值時拷貝一份。
2、集合類對象的copy與mutableCopy

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

NSArray *array = @[@[@”a”, @”b”], @[@”c”, @”d”]];
NSArray *copyArray = [array copy];
NSMutableArray *mCopyArray = [array mutableCopy];
查看內容,可以看到 copyArray 和 array 的地址是一樣的,而 mCopyArray 和 array 的地址是不同的。說明 copy 操作進行瞭指針拷貝,mutableCopy 進行瞭內容拷貝。但需要強調的是:此處的內容拷貝,僅僅是拷貝 array 這個對象,array 集合內部的元素仍然是指針拷貝。這和上面的非集合 immutable 對象的拷貝還是挺相似的,那麼mutable對象的拷貝會不會類似呢?我們繼續往下,看 mutable 對象拷貝的例子:

NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@”a”],@”b”,@”c”,nil];
NSArray *copyArray = [array copy];
NSMutableArray *mCopyArray = [array mutableCopy];
查看內存,如我們所料,copyArray、mCopyArray和 array 的內存地址都不一樣,說明 copyArray、mCopyArray 都對 array 進行瞭內容拷貝。同樣,我們可以得出結論:

在集合類對象中,對 immutable 對象進行 copy,是指針復制, mutableCopy 是內容復制;對 mutable 對象進行 copy 和 mutableCopy 都是內容復制。但是:集合對象的內容復制僅限於對象本身,對象元素仍然是指針復制。用代碼簡單表示如下:

[immutableObject copy] // 淺復制
[immutableObject mutableCopy] //單層深復制
[mutableObject copy] //單層深復制
[mutableObject mutableCopy] //單層深復制
這個代碼結論和非集合類的非常相似。

參考鏈接:iOS 集合的深復制與淺復制

13.@synthesize合成實例變量的規則是什麼?假如property名為foo,存在一個名為_foo的實例變量,那麼還會自動合成新變量麼?

在回答之前先說明下一個概念:

實例變量 = 成員變量 = ivar
這些說法,筆者下文中,可能都會用到,指的是一個東西。

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

如果使用瞭屬性的話,那麼編譯器就會自動編寫訪問屬性所需的方法,此過程叫做“自動合成”( auto synthesis)。需要強調的是,這個過程由編譯器在編譯期執行,所以編輯器裡看不到這些“合成方法” (synthesized method)的源代碼。除瞭生成方法代碼之外,編譯器還要自動向類中添加適當類型的實例變量,並且在屬性名前面加下劃線,以此作為實例變量的名字。

@interface CYLPerson : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end
在上例中,會生成兩個實例變量,其名稱分別為 _firstName 與 _lastName。也可以在類的實現代碼裡通過 @synthesize 語法來指定實例變量的名字:

@implementation CYLPerson
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end
上述語法會將生成的實例變量命名為 _myFirstName 與 _myLastName ,而不再使用默認的名字。一般情況下無須修改默認的實例變量名,但是如果你不喜歡以下劃線來命名實例變量,那麼可以用這個辦法將其改為自己想要的名字。筆者還是推薦使用默認的命名方案,因為如果所有人都堅持這套方案,那麼寫出來的代碼大傢都能看得懂。

總結下 @synthesize 合成實例變量的規則,有以下幾點:

如果指定瞭成員變量的名稱,會生成一個指定的名稱的成員變量,

如果這個成員已經存在瞭就不再生成瞭.

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

如果沒有指定成員變量的名稱會自動生成一個屬性同名的成員變量,
如果是 @synthesize foo = _foo; 就不會生成成員變量瞭.

假如 property 名為 foo,存在一個名為 _foo 的實例變量,那麼還會自動合成新變量麼? 不會。如下圖:

enter image description here

14.在有瞭自動合成屬性實例變量之後,@synthesize還有哪些使用場景?

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

同時重寫瞭 setter 和 getter 時
重寫瞭隻讀屬性的 getter 時
使用瞭 @dynamic 時
在 @protocol 中定義的所有屬性
在 category 中定義的所有屬性
重載的屬性

當你在子類中重載瞭父類中的屬性,你必須 使用 @synthesize 來手動合成ivar。

除瞭後三條,對其他幾個我們可以總結出一個規律:當你想手動管理 @property 的所有內容時,你就會嘗試通過實現 @property 的所有“存取方法”(the accessor methods)或者使用 @dynamic 來達到這個目的,這時編譯器就會認為你打算手動管理 @property,於是編譯器就禁用瞭 autosynthesis(自動合成)。

因為有瞭 autosynthesis(自動合成),大部分開發者已經習慣不去手動定義ivar,而是依賴於 autosynthesis(自動合成),但是一旦你需要使用ivar,而 autosynthesis(自動合成)又失效瞭,如果不去手動定義ivar,那麼你就得借助 @synthesize 來手動合成 ivar。

其實,@synthesize 語法還有一個應用場景,但是不太建議大傢使用:

可以在類的實現代碼裡通過 @synthesize 語法來指定實例變量的名字:

@implementation CYLPerson
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end
上述語法會將生成的實例變量命名為 _myFirstName 與 _myLastName,而不再使用默認的名字。一般情況下無須修改默認的實例變量名,但是如果你不喜歡以下劃線來命名實例變量,那麼可以用這個辦法將其改為自己想要的名字。筆者還是推薦使用默認的命名方案,因為如果所有人都堅持這套方案,那麼寫出來的代碼大傢都能看得懂。

舉例說明:應用場景:

//
// .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
結果編譯器報錯: enter image description here

當你同時重寫瞭 setter 和 getter 時,系統就不會生成 ivar(實例變量/成員變量)。這時候有兩種選擇:

要麼如第14行:手動創建 ivar
要麼如第17行:使用@synthesize foo = _foo; ,關聯 @property 與 ivar。
更多信息,請戳- 》 When should I use @synthesize explicitly?

15.objc中向一個nil對象發送消息將會發生什麼?

在 Objective-C 中向 nil 發送消息是完全有效的——隻是在運行時不會有任何作用:

如果一個方法返回值是一個對象,那麼發送給nil的消息將返回0(nil)。例如:

Person * motherInlaw = [[aPerson spouse] mother];
如果 spouse 對象為 nil,那麼發送給 nil 的消息 mother 也將返回 nil。

如果方法返回值為指針類型,其指針大小為小於或者等於sizeof(void*),float,double,long double 或者 long long 的整型標量,發送給 nil 的消息將返回0。
如果方法返回值為結構體,發送給 nil 的消息將返回0。結構體中各個字段的值將都是0。
如果方法的返回值不是上述提到的幾種情況,那麼發送給 nil 的消息的返回值將是未定義的。
具體原因如下:

objc是動態語言,每個方法在運行時會被動態轉為消息發送,即:objc_msgSend(receiver, selector)。
那麼,為瞭方便理解這個內容,還是貼一個objc的源代碼:

// runtime.h(類在runtime中的定義)
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong

struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; //isa指針指向Meta Class,因為Objc的類的本身也是一個Object,為瞭處理這個關系,runtime就創造瞭Meta Class,當給類發送[NSObject alloc]這樣消息時,實際上是把這個消息發給瞭Class Object
#if !OBJC2
Class super_class OBJC2_UNAVAILABLE; // 父類
const char *name OBJC2_UNAVAILABLE; // 類名
long version OBJC2_UNAVAILABLE; // 類的版本信息,默認為0
long info OBJC2_UNAVAILABLE; // 類信息,供運行期使用的一些位標識
long instance_size OBJC2_UNAVAILABLE; // 該類的實例變量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 該類的成員變量鏈表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定義的鏈表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法緩存,對象接到一個消息會根據isa指針查找消息對象,這時會在method Lists中遍歷,如果cache瞭,常用的方法調用時就能夠提高調用的效率。
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協議鏈表
#endif
} OBJC2_UNAVAILABLE;
objc在向一個對象發送消息時,runtime庫會根據對象的isa指針找到該對象實際所屬的類,然後在該類中的方法列表以及其父類方法列表中尋找方法運行,然後在發送消息的時候,objc_msgSend方法不會返回值,所謂的返回內容都是具體調用時執行的。 那麼,回到本題,如果向一個nil對象發送消息,首先在尋找對象的isa指針時就是0地址返回瞭,所以不會出現任何錯誤。

16 objc中向一個對象發送消息[obj foo]和objc_msgSend()函數之間有什麼關系?

具體原因同上題:該方法編譯之後就是objc_msgSend()函數調用.

我們用 clang 分析下,clang 提供一個命令,可以將Objective-C的源碼改寫成C++語言,借此可以研究下[obj foo]和objc_msgSend()函數之間有什麼關系。

以下面的代碼為例,由於 clang 後的代碼達到瞭10萬多行,為瞭便於區分,添加瞭一個叫 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
就可以生成一個main.cpp的文件,在最低端(10萬4千行左右)

enter image description here

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

((void ()(id, SEL))(void )objc_msgSend)((id)obj, sel_registerName(“foo”));
也就是說:

[obj foo];在objc動態編譯時,會被轉意為:objc_msgSend(obj, @selector(foo));。
17.什麼時候會報unrecognized selector的異常?

簡單來說:

當調用該對象上某個方法,而該對象上沒有實現這個方法的時候, 可以通過“消息轉發”進行解決。
簡單的流程如下,在上一題中也提到過:

objc是動態語言,每個方法在運行時會被動態轉為消息發送,即:objc_msgSend(receiver, selector)。
objc在向一個對象發送消息時,runtime庫會根據對象的isa指針找到該對象實際所屬的類,然後在該類中的方法列表以及其父類方法列表中尋找方法運行,如果,在最頂層的父類中依然找不到相應的方法時,程序在運行時會掛掉並拋出異常unrecognized selector sent to XXX 。但是在這之前,objc的運行時會給出三次拯救程序崩潰的機會:

Method resolution

objc運行時會調用+resolveInstanceMethod:或者 +resolveClassMethod:,讓你有機會提供一個函數實現。如果你添加瞭函數,那運行時系統就會重新啟動一次消息發送的過程,否則 ,運行時就會移到下一步,消息轉發(Message Forwarding)。

Fast forwarding

如果目標對象實現瞭-forwardingTargetForSelector:,Runtime 這時就會調用這個方法,給你把這個消息轉發給其他對象的機會。 隻要這個方法返回的不是nil和self,整個消息發送的過程就會被重啟,當然發送的對象會變成你返回的那個對象。否則,就會繼續Normal Fowarding。 這裡叫Fast,隻是為瞭區別下一步的轉發機制。因為這一步不會創建任何新的對象,但下一步轉發會創建一個NSInvocation對象,所以相對更快點。

Normal forwarding

這一步是Runtime最後一次給你挽救的機會。首先它會發送-methodSignatureForSelector:消息獲得函數的參數和返回值類型。如果-methodSignatureForSelector:返回nil,Runtime則會發出-doesNotRecognizeSelector:消息,程序這時也就掛掉瞭。如果返回瞭一個函數簽名,Runtime就會創建一個NSInvocation對象並發送-forwardInvocation:消息給目標對象。

為瞭能更清晰地理解這些方法的作用,git倉庫裡也給出瞭一個Demo,名稱叫“ _objc_msgForward_demo ”,可運行起來看看。

18.一個objc對象如何進行內存佈局?(考慮有父類的情況)

所有父類的成員變量和自己的成員變量都會存放在該對象所對應的存儲空間中.
每一個對象內部都有一個isa指針,指向他的類對象,類對象中存放著本對象的

對象方法列表(對象能夠接收的消息列表,保存在它所對應的類對象中)
成員變量的列表,
屬性列表,
它內部也有一個isa指針指向元對象(meta class),元對象內部存放的是類方法列表,類對象內部還有一個superclass的指針,指向他的父類對象。

每個 Objective-C 對象都有相同的結構,如下圖所示:

enter image description here

翻譯過來就是

Objective-C 對象的結構圖
ISA指針
根類的實例變量
倒數第二層父類的實例變量

父類的實例變量
類的實例變量
根對象就是NSObject,它的superclass指針指向nil

類對象既然稱為對象,那它也是一個實例。類對象中也有一個isa指針指向它的元類(meta class),即類對象是元類的實例。元類內部存放的是類方法列表,根元類的isa指針指向自己,superclass指針指向NSObject類。

如圖: enter image description here

19.一個objc對象的isa的指針指向什麼?有什麼作用?

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

20.下面的代碼輸出什麼?

@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
這個題目主要是考察關於 Objective-C 中對 self 和 super 的理解。

我們都知道:self 是類的隱藏參數,指向當前調用方法的這個類的實例。那 super 呢?

很多人會想當然的認為“ super 和 self 類似,應該是指向父類的指針吧!”。這是很普遍的一個誤區。其實 super 是一個 Magic Keyword, 它本質是一個編譯器標示符,和 self 是指向的同一個消息接受者!他們兩個的不同點在於:super 會告訴編譯器,調用 class 這個方法時,要去父類的方法,而不是本類裡的。

上面的例子不管調用[self class]還是[super class],接受消息的對象都是當前 Son *xxx 這個對象。

當使用 self 調用方法時,會從當前類的方法列表中開始找,如果沒有,就從父類中再找;而當使用 super 時,則從父類的方法列表中開始找。然後調用父類的這個方法。

這也就是為什麼說“不推薦在 init 方法中使用點語法”,如果想訪問實例變量 iVar 應該使用下劃線( _iVar ),而非點語法( self.iVar )。

點語法( self.iVar )的壞處就是子類有可能覆寫 setter 。假設 Person 有一個子類叫 ChenPerson,這個子類專門表示那些姓“陳”的人。該子類可能會覆寫 lastName 屬性所對應的設置方法:

//
// 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
{
//設置方法一:如果setter采用是這種方式,就可能引起崩潰
// if (![lastName isEqualToString:@”陳”])
// {
// [NSException raise:NSInvalidArgumentException format:@”姓不是陳”];
// }
// _lastName = lastName;

//設置方法二:如果setter采用是這種方式,就可能引起崩潰
_lastName = @”陳”;
NSLog(@”??類名與方法名:%s(在第%d行),描述:%@”, PRETTY_FUNCTION, LINE, @”會調用這個方法,想一下為什麼?”);

}

@end
在基類 Person 的默認初始化方法中,可能會將姓氏設為空字符串。此時若使用點語法( self.lastName )也即 setter 設置方法,那麼調用將會是子類的設置方法,如果在剛剛的 setter 代碼中采用設置方法一,那麼就會拋出異常,

為瞭方便采用打印的方式展示,究竟發生瞭什麼,我們使用設置方法二。

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

//
// Person.m
// nil對象調用點語法
//
// 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, @”根本不會調用這個方法”);
_lastName = @”炎黃”;
}

@end
那麼打印結果將會是這樣的:

??類名與方法名:-[ChenPerson setLastName:](在第36行),描述:會調用這個方法,想一下為什麼?
??類名與方法名:-[ChenPerson init](在第19行),描述:ChenPerson
??類名與方法名:-[ChenPerson init](在第20行),描述:ChenPerson
我在倉庫裡也給出瞭一個相應的 Demo(名字叫:Demo_21題_下面的代碼輸出什麼)。有興趣可以跑起來看一下,主要看下他是怎麼打印的,思考下為什麼這麼打印。

接下來讓我們利用 runtime 的相關知識來驗證一下 super 關鍵字的本質,使用clang重寫命令:

$ clang -rewrite-objc test.m

將這道題目中給出的代碼被轉化為:

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"))));

從上面的代碼中,我們可以發現在調用 [self class] 時,會轉化成 objc_msgSend函數。看下函數定義:

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

我們把 self 做為第一個參數傳遞進去。

而在調用 [super class]時,會轉化成 objc_msgSendSuper函數。看下函數定義:

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

第一個參數是 objc_super 這樣一個結構體,其定義如下:

struct objc_super {
__unsafe_unretained id receiver;
__unsafe_unretained Class super_class;
};
結構體有兩個成員,第一個成員是 receiver, 類似於上面的 objc_msgSend函數第一個參數self 。第二個成員是記錄當前類的父類是什麼。

所以,當調用 [self class] 時,實際先調用的是 objc_msgSend函數,第一個參數是 Son當前的這個實例,然後在 Son 這個類裡面去找 – (Class)class這個方法,沒有,去父類 Father裡找,也沒有,最後在 NSObject類中發現這個方法。而 – (Class)class的實現就是返回self的類別,故上述輸出結果為 Son。

objc Runtime開源代碼對- (Class)class方法的實現:

(Class)class {
return object_getClass(self);
}
而當調用 [super class]時,會轉換成objc_msgSendSuper函數。第一步先構造 objc_super 結構體,結構體第一個成員就是 self 。 第二個成員是 (id)class_getSuperclass(objc_getClass(“Son”)) , 實際該函數輸出結果為 Father。

第二步是去 Father這個類裡去找 – (Class)class,沒有,然後去NSObject類去找,找到瞭。最後內部是使用 objc_msgSend(objc_super->receiver, @selector(class))去調用,

此時已經和[self class]調用相同瞭,故上述輸出結果仍然返回 Son。

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

21.runtime如何通過selector找到對應的IMP地址?(分別考慮類方法和實例方法)

每一個類對象中都一個方法列表,方法列表中記錄著方法的名稱,方法實現,以及參數類型,其實selector本質就是方法名稱,通過這個方法名稱就可以在方法列表中找到對應的方法實現.

22.使用runtime Associate方法關聯的對象,需要在主對象dealloc的時候釋放麼?

在ARC下不需要。
在MRC中,對於使用retain或copy策略的需要 。

在MRC下也不需要
無論在MRC下還是ARC下均不需要。
2011年版本的Apple API 官方文檔 – Associative References 一節中有一個MRC環境下的例子:

// 在MRC下,使用runtime Associate方法關聯的對象,不需要在主對象dealloc的時候釋放
// 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釋放掉瞭。

既然會被銷毀,那麼具體在什麼時間點?

根據 WWDC 2011, Session 322 (第36分22秒) 中發佈的內存銷毀時間表,被關聯的對象在生命周期內要比對象本身釋放的晚很多。它們會在被 NSObject -dealloc 調用的 object_dispose() 方法中釋放。
對象的內存銷毀時間表,分四個步驟:

// 對象的內存銷毀時間表
// http://weibo.com/luohanchenyilong/ (微博@iOS程序犭袁)
// https://github.com/ChenYilong
// 根據 WWDC 2011, Session 322 (36分22秒)中發佈的內存銷毀時間表

調用 -release :引用計數變為零

對象正在被銷毀,生命周期即將結束.不能再有新的 __weak 弱引用, 否則將指向 nil.調用 [self dealloc]
子類 調用 -dealloc

繼承關系中最底層的子類 在調用 -dealloc如果是 MRC 代碼 則會手動釋放實例變量們(iVars)繼承關系中每一層的父類 都在調用 -dealloc
NSObject 調 -dealloc

隻做一件事:調用 Objective-C runtime 中的 object_dispose() 方法
調用 object_dispose()

為 C++ 的實例變量們(iVars)調用 destructors 為 ARC 狀態下的 實例變量們(iVars) 調用 -release 解除所有使用 runtime Associate方法關聯的對象解除所有 __weak 引用調用 free()
對象的內存銷毀時間表:參考鏈接。

23.objc中的類方法和實例方法有什麼本質區別和聯系?

類方法:

類方法是屬於類對象的
類方法隻能通過類對象調用
類方法中的self是類對象
類方法可以調用其他的類方法
類方法中不能訪問成員變量
類方法中不定直接調用對象方法
實例方法:

實例方法是屬於實例對象的
實例方法隻能通過實例對象調用
實例方法中的self是實例對象
實例方法中可以訪問成員變量
實例方法中直接調用實例方法
實例方法中也可以調用類方法(通過類名)

24._objc_msgForward函數是做什麼的,直接調用它將會發生什麼?

_objc_msgForward是 IMP 類型,用於消息轉發的:當向一個對象發送一條消息,但它並沒有實現的時候,_objc_msgForward會嘗試做消息轉發。
我們可以這樣創建一個_objc_msgForward對象:

IMP msgForwardIMP = _objc_msgForward;
在上篇中的《objc中向一個對象發送消息[obj foo]和objc_msgSend()函數之間有什麼關系?》曾提到objc_msgSend在“消息傳遞”中的作用。在“消息傳遞”過程中,objc_msgSend的動作比較清晰:首先在 Class 中的緩存查找 IMP (沒緩存則初始化緩存),如果沒找到,則向父類的 Class 查找。如果一直查找到根類仍舊沒有實現,則用_objc_msgForward函數指針代替 IMP 。最後,執行這個 IMP 。

Objective-C運行時是開源的,所以我們可以看到它的實現。打開 Apple Open Source 裡Mac代碼裡的obj包 下載一個最新版本,找到 objc-runtime-new.mm,進入之後搜索_objc_msgForward。

enter image description here

裡面有對_objc_msgForward的功能解釋:

enter image description here

/*************************************************************
* lookUpImpOrForward.
* The standard IMP lookup.
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* inst is an instance of cls or a subclass thereof, or nil if none is known.
* If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use
* must be converted to _objc_msgForward or _objc_msgForward_stret.
* If you don’t want forwarding at all, use lookUpImpOrNil() instead.
************************************************************/
對 objc-runtime-new.mm文件裡與_objc_msgForward有關的三個函數使用偽代碼展示下:

// objc-runtime-new.mm 文件裡與 _objc_msgForward 有關的三個函數使用偽代碼展示
// Created by
https://github.com/ChenYilong
// Copyright (c) 微博@iOS程序犭袁(http://weibo.com/luohanchenyilong/). All rights reserved.
// 同時,這也是 obj_msgSend 的實現過程

id objc_msgSend(id self, SEL op, …) {
if (!self) return nil;
IMP imp = class_getMethodImplementation(self->isa, SEL op);
imp(self, op, …); //調用這個函數,偽代碼…
}

//查找IMP
IMP class_getMethodImplementation(Class cls, SEL sel) {
if (!cls || !sel) return nil;
IMP imp = lookUpImpOrNil(cls, sel);
if (!imp) return _objc_msgForward; //_objc_msgForward 用於消息轉發
return imp;
}

IMP lookUpImpOrNil(Class cls, SEL sel) {
if (!cls->initialize()) {
_class_initialize(cls);
}

Class curClass = cls;
IMP imp = nil;
do { //先查緩存,緩存沒有時重建,仍舊沒有則向父類查詢
    if (!curClass) break;
    if (!curClass->cache) fill_cache(cls, curClass);
    imp = cache_getImp(curClass, sel);
    if (imp) break;
} while (curClass = curClass->superclass);

return imp;

}
雖然Apple沒有公開_objc_msgForward的實現源碼,但是我們還是能得出結論:

_objc_msgForward是一個函數指針(和 IMP 的類型一樣),是用於消息轉發的:當向一個對象發送一條消息,但它並沒有實現的時候,_objc_msgForward會嘗試做消息轉發。

在上篇中的《objc中向一個對象發送消息[obj foo]和objc_msgSend()函數之間有什麼關系?》曾提到objc_msgSend在“消息傳遞”中的作用。在“消息傳遞”過程中,objc_msgSend的動作比較清晰:首先在 Class 中的緩存查找 IMP (沒緩存則初始化緩存),如果沒找到,則向父類的 Class 查找。如果一直查找到根類仍舊沒有實現,則用_objc_msgForward函數指針代替 IMP 。最後,執行這個 IMP 。
為瞭展示消息轉發的具體動作,這裡嘗試向一個對象發送一條錯誤的消息,並查看一下_objc_msgForward是如何進行轉發的。

首先開啟調試模式、打印出所有運行時發送的消息: 可以在代碼裡執行下面的方法:

(void)instrumentObjcMessageSends(YES);
或者斷點暫停程序運行,並在 gdb 中輸入下面的命令:

call (void)instrumentObjcMessageSends(YES)
以第二種為例,操作如下所示:

enter image description here

之後,運行時發送的所有消息都會打印到/tmp/msgSend-xxxx文件裡瞭。

終端中輸入命令前往:

open /private/tmp
enter image description here

可能看到有多條,找到最新生成的,雙擊打開

在模擬器上執行執行以下語句(這一套調試方案僅適用於模擬器,真機不可用,關於該調試方案的拓展鏈接: Can the messages sent to an object in Objective-C be monitored or printed out? ),向一個對象發送一條錯誤的消息:

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

import

import “AppDelegate.h”

import “CYLTest.h”

int main(int argc, char * argv[]) {
@autoreleasepool {
CYLTest *test = [[CYLTest alloc] init];
[test performSelector:(@selector(iOS程序犭袁))];
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
enter image description here

你可以在/tmp/msgSend-xxxx(我這一次是/tmp/msgSend-9805)文件裡,看到打印出來:

enter image description here

CYLTest NSObject initializeCYLTest NSObject allocCYLTest NSObject initCYLTest NSObject performSelector:CYLTest NSObject resolveInstanceMethod:CYLTest NSObject resolveInstanceMethod:CYLTest NSObject forwardingTargetForSelector:CYLTest NSObject forwardingTargetForSelector:CYLTest NSObject methodSignatureForSelector:CYLTest NSObject methodSignatureForSelector:CYLTest NSObject classCYLTest NSObject doesNotRecognizeSelector:CYLTest NSObject doesNotRecognizeSelector:CYLTest NSObject class
結合《NSObject官方文檔》,排除掉 NSObject 做的事,剩下的就是_objc_msgForward消息轉發做的幾件事:

調用resolveInstanceMethod:方法 (或 resolveClassMethod:)。允許用戶在此時為該 Class 動態添加實現。如果有實現瞭,則調用並返回YES,那麼重新開始objc_msgSend流程。這一次對象會響應這個選擇器,一般是因為它已經調用過class_addMethod。如果仍沒實現,繼續下面的動作。

調用forwardingTargetForSelector:方法,嘗試找到一個能響應該消息的對象。如果獲取到,則直接把消息轉發給它,返回非 nil 對象。否則返回 nil ,繼續下面的動作。註意,這裡不要返回 self ,否則會形成死循環。

調用methodSignatureForSelector:方法,嘗試獲得一個方法簽名。如果獲取不到,則直接調用doesNotRecognizeSelector拋出異常。如果能獲取,則返回非nil:創建一個 NSlnvocation 並傳給forwardInvocation:。

調用forwardInvocation:方法,將第3步獲取到的方法簽名包裝成 Invocation 傳入,如何處理就在這裡面瞭,並返回非ni。

調用doesNotRecognizeSelector: ,默認的實現是拋出異常。如果第3步沒能獲得一個方法簽名,執行該步驟。

上面前4個方法均是模板方法,開發者可以override,由 runtime 來調用。最常見的實現消息轉發:就是重寫方法3和4,吞掉一個消息或者代理給其他對象都是沒問題的

也就是說_objc_msgForward在進行消息轉發的過程中會涉及以下這幾個方法:

resolveInstanceMethod:方法 (或 resolveClassMethod:)。

forwardingTargetForSelector:方法

methodSignatureForSelector:方法

forwardInvocation:方法

doesNotRecognizeSelector: 方法

為瞭能更清晰地理解這些方法的作用,git倉庫裡也給出瞭一個Demo,名稱叫“ _objc_msgForward_demo ”,可運行起來看看。

下面回答下第二個問題“直接_objc_msgForward調用它將會發生什麼?”

直接調用_objc_msgForward是非常危險的事,如果用不好會直接導致程序Crash,但是如果用得好,能做很多非常酷的事。

就好像跑酷,幹得好,叫“耍酷”,幹不好就叫“作死”。

正如前文所說:

_objc_msgForward是 IMP 類型,用於消息轉發的:當向一個對象發送一條消息,但它並沒有實現的時候,_objc_msgForward會嘗試做消息轉發。
如何調用_objc_msgForward? _objc_msgForward隸屬 C 語言,有三個參數 :

– _objc_msgForward參數 類型
1. 所屬對象 id類型
2. 方法名 SEL類型
3. 可變參數 可變參數類型
首先瞭解下如何調用 IMP 類型的方法,IMP類型是如下格式:

為瞭直觀,我們可以通過如下方式定義一個 IMP類型 :

typedef void (*voidIMP)(id, SEL, …)
一旦調用_objc_msgForward,將跳過查找 IMP 的過程,直接觸發“消息轉發”,

如果調用瞭_objc_msgForward,即使這個對象確實已經實現瞭這個方法,你也會告訴objc_msgSend:

“我沒有在這個對象裡找到這個方法的實現”
想象下objc_msgSend會怎麼做?通常情況下,下面這張圖就是你正常走objc_msgSend過程,和直接調用_objc_msgForward的前後差別:

enter image description here

有哪些場景需要直接調用_objc_msgForward?最常見的場景是:你想獲取某方法所對應的NSInvocation對象。舉例說明:

JSPatch (Github 鏈接)就是直接調用_objc_msgForward來實現其核心功能的:

JSPatch 以小巧的體積做到瞭讓JS調用/替換任意OC方法,讓iOS APP具備熱更新的能力。
作者的博文《JSPatch實現原理詳解》詳細記錄瞭實現原理,有興趣可以看下。

同時 RAC(ReactiveCocoa) 源碼中也用到瞭該方法。

25.runtime如何實現weak變量的自動置nil?

runtime 對註冊的類, 會進行佈局,對於 weak 對象會放入一個 hash 表中。 用 weak 指向的對象內存地址作為 key,當此對象的引用計數為0的時候會 dealloc,假如 weak 指向的對象內存地址是a,那麼就會以a為鍵, 在這個 weak 表中搜索,找到所有以a為鍵的 weak 對象,從而設置為 nil。
在上篇中的《runtime 如何實現 weak 屬性》有論述。(註:在上篇的《使用runtime Associate方法關聯的對象,需要在主對象dealloc的時候釋放麼?》裡給出的“對象的內存銷毀時間表”也提到__weak引用的解除時間。)

我們可以設計一個函數(偽代碼)來表示上述機制:

objc_storeWeak(&a, b)函數:

objc_storeWeak函數把第二個參數–賦值對象(b)的內存地址作為鍵值key,將第一個參數–weak修飾的屬性變量(a)的內存地址(&a)作為value,註冊到 weak 表中。如果第二個參數(b)為0(nil),那麼把變量(a)的內存地址(&a)從weak表中刪除,

你可以把objc_storeWeak(&a, b)理解為:objc_storeWeak(value, key),並且當key變nil,將value置nil。

在b非nil時,a和b指向同一個內存地址,在b變nil時,a變nil。此時向a發送消息不會崩潰:在Objective-C中向nil發送消息是安全的。

而如果a是由assign修飾的,則: 在b非nil時,a和b指向同一個內存地址,在b變nil時,a還是指向該內存地址,變野指針。此時向a發送消息極易崩潰。

下面我們將基於objc_storeWeak(&a, b)函數,使用偽代碼模擬“runtime如何實現weak屬性”:

// 使用偽代碼模擬:runtime如何實現weak屬性
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong

id obj1;
objc_initWeak(&obj1, obj);
/obj引用計數變為0,變量作用域結束/
objc_destroyWeak(&obj1);
下面對用到的兩個方法objc_initWeak和objc_destroyWeak做下解釋:

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

下面分別介紹下方法的內部實現:

objc_initWeak函數的實現是這樣的:在將“附有weak修飾符的變量(obj1)”初始化為0(nil)後,會將“賦值對象”(obj)作為參數,調用objc_storeWeak函數。

obj1 = 0;
obj_storeWeak(&obj1, obj);
也就是說:

weak 修飾的指針默認值是 nil (在Objective-C中向nil發送消息是安全的)
然後obj_destroyWeak函數將0(nil)作為參數,調用objc_storeWeak函數。

objc_storeWeak(&obj1, 0);

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

// 使用偽代碼模擬:runtime如何實現weak屬性
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong

id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
/* … obj的引用計數變為0,被置nil … */
objc_storeWeak(&obj1, 0);
objc_storeWeak函數把第二個參數–賦值對象(obj)的內存地址作為鍵值,將第一個參數–weak修飾的屬性變量(obj1)的內存地址註冊到 weak 表中。如果第二個參數(obj)為0(nil),那麼把變量(obj1)的地址從weak表中刪除。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *