1.對(duì)象模型
Objective-C是一門面向?qū)ο蟮恼Z言男杈,對(duì)象是我們編程的基本單元丈屹,所有的操作都是通過對(duì)象。對(duì)象其實(shí)是對(duì) 數(shù)據(jù) 和 行為 的封裝伶棒。在OC中旺垒,數(shù)據(jù)的載體就是實(shí)例變量彩库,我們可以通過屬性便捷的訪問到實(shí)例變量,行為其實(shí)就是對(duì)象的方法先蒋,也可以稱為發(fā)消息骇钦,方法內(nèi)部可以傳遞數(shù)據(jù)和操作數(shù)據(jù)。
平時(shí)我們聲明一個(gè)對(duì)象竞漾,例如:NSString *string = @"zzy";
這條語句的意思是司忱,創(chuàng)建了一個(gè)NSString
類型的對(duì)象實(shí)例,實(shí)例的內(nèi)容是zzy
畴蹭,并返回這個(gè)實(shí)例的內(nèi)存地址給string
這個(gè)變量保存,之后我們就可以通過變量string
來操作這個(gè)實(shí)例鳍烁。學(xué)過C語言的都知道叨襟,其實(shí)*string
就是指這是一個(gè)NSString
類型的指針,所以OC對(duì)象的本質(zhì)其實(shí)就是指向某塊內(nèi)存地址的指針幔荒。
對(duì)象的結(jié)構(gòu)
在OC中每個(gè)對(duì)象都是一個(gè)類的實(shí)例糊闽,對(duì)象的結(jié)構(gòu)如下:
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
可以看到對(duì)象是一個(gè)結(jié)構(gòu)體,結(jié)構(gòu)體當(dāng)中有一個(gè)Class類型的成員變量isa爹梁。Class的定義為typedef struct objc_class *Class;右犹,這是一個(gè)指向objc_class結(jié)構(gòu)體的一個(gè)結(jié)構(gòu)體指針。而objc_class其實(shí)就是對(duì)象所屬類的原型:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
可以看到Class
中也存放了一個(gè)isa
指針姚垃,所以Class
也是一個(gè)對(duì)象念链,被稱為類對(duì)象。同時(shí)Class
結(jié)構(gòu)體當(dāng)中還存著:指向父類的super_class
指針积糯、類名掂墓、版本、實(shí)例對(duì)象的大小看成、實(shí)例變量列表君编、方法列表、緩存川慌、協(xié)議等信息吃嘿。通過類創(chuàng)建出來的實(shí)例對(duì)象所擁有的屬性,方法梦重,協(xié)議等都是存儲(chǔ)在類結(jié)構(gòu)體當(dāng)中兑燥。
實(shí)例對(duì)象的isa
指針指向其所屬的類,類結(jié)構(gòu)體當(dāng)中存放著對(duì)象的屬性和方法列表忍饰。類對(duì)象的isa
指針指向其所屬的元類贪嫂,元類中存放著類對(duì)象的方法列表(類方法),而元類內(nèi)部也有一個(gè)isa
指針艾蓝,指向的是基類的元類力崇,而基類的元類的isa
指針指向自身斗塘。用一個(gè)經(jīng)典的圖類表示整個(gè)關(guān)系鏈路:
不能向編譯后得到的類中增加實(shí)例變量:因?yàn)榫幾g后的類已經(jīng)注冊(cè)在 runtime 中,類結(jié)構(gòu)體中的 objc_ivar_list 實(shí)例變量的鏈表 和 instance_size 實(shí)例的內(nèi)存大小已經(jīng)確定亮靴,同時(shí)runtime 會(huì)調(diào)用 class_setIvarLayout 或 class_setWeakIvarLayout 來處理 strong weak 引用馍盟。所以不能向存在的類中添加實(shí)例變量。
運(yùn)行時(shí)創(chuàng)建的類是可以添加實(shí)例變量茧吊,調(diào)用 class_addIvar 函數(shù)贞岭。但是得在調(diào)用 objc_allocateClassPair 之后,objc_registerClassPair 之前搓侄,否則類一旦注冊(cè)到runtime中后就不能改變實(shí)例變量了瞄桨。
2. 屬性
對(duì)象中的數(shù)據(jù)是通過實(shí)例變量來保存的,OC提供了一種便捷的訪問實(shí)例變量的方式:屬性讶踪。屬性的本質(zhì)就是包括實(shí)例變量 + setter方法 + getter方法
芯侥。實(shí)例變量的值保存在對(duì)象內(nèi)部,通過“偏移量(offset)”來保存乳讥,即:該變量距離對(duì)象內(nèi)存區(qū)域的起始地址的距離柱查,這個(gè)是通過硬編碼來標(biāo)識(shí)的。當(dāng)我們聲明一個(gè)屬性之后云石,系統(tǒng)會(huì)自動(dòng)幫我們生成一個(gè)成員變量和屬性唉工,這個(gè)成員變量和屬性的描述(類型,名稱等)分別存放在類的ivar_list
和property_list
當(dāng)中汹忠,并且生成setter
方法和getter
方法追加到method_list
當(dāng)中淋硝,然后計(jì)算屬性在對(duì)象內(nèi)部的偏移量,并實(shí)現(xiàn)setter
方法和getter
方法错维。
聲明屬性的方式:@property (nonatomic, readwrite, copy) NSString *name;
這個(gè)時(shí)候編譯器會(huì)自動(dòng)幫我們合成實(shí)例變量_name
奖地。默認(rèn)的實(shí)例變量名為屬性名前面加下劃線。如果不想用系統(tǒng)默認(rèn)的實(shí)例變量名赋焕,也可以通過@synthesize
關(guān)鍵字手動(dòng)指定實(shí)例變量名参歹,例如@synthesize name = _myName;
那么我們的實(shí)例變量就變成了_myName
,不過一般為了規(guī)范沒有人這么做隆判。
如果不想讓系統(tǒng)自動(dòng)幫我們合成實(shí)例變量和setter
犬庇、getter
方法,也可以通過關(guān)鍵字@dynamic
聲明(例如:@dynamic name;
)侨嘀,然后自己手動(dòng)去合成實(shí)例變量和相關(guān)存取方法臭挽。如果沒有實(shí)現(xiàn)這些合成方法的話,那么是無法正常使用屬性的咬腕。
目前使用@synthesize
的一般場(chǎng)景:
當(dāng)我們手動(dòng)實(shí)現(xiàn)了
setter
和getter
方法的時(shí)候欢峰,系統(tǒng)默認(rèn)我們自己管理屬性,所以就不再幫我們合成實(shí)例變量了,這個(gè)時(shí)候需要用@synthesize
手動(dòng)合成實(shí)例變量纽帖,不然編譯器就會(huì)報(bào)錯(cuò)宠漩。當(dāng)重寫父類屬性的時(shí)候,在子類中需要用
@synthesize
手動(dòng)合成實(shí)例變量懊直,否則無法使用扒吁。使用了
@dynamic
的時(shí)候。
屬性包含的三種語義:
atomic && nonatomic :原子性 && 非原子性
在聲明屬性的時(shí)候室囊,編譯器默認(rèn)的屬性語義是atomic
原子性的雕崩。atomic
和nonatomic
的區(qū)別是前者在setter方法
賦值的時(shí)候會(huì)進(jìn)行加鎖保證賦值過程的完整性(安全性),而后者不會(huì)使用同步鎖融撞。在iOS中盼铁,由于頻繁加鎖會(huì)導(dǎo)致性能問題,而且即使采用了atomic
尝偎,在多線程操作的情況下捉貌,也并不能保證線程的安全性。如果要保證線程安全冬念,需要專門進(jìn)行其他加鎖機(jī)制處理。所以一般情況下牧挣,我們平時(shí)聲明屬性用的都是nonatomic
急前。
readwrite && readonly :可讀可寫 && 只讀
readwrite
表示屬性可讀可寫。readonly
表示屬性只能讀取不能修改瀑构,一般用于在.h
中對(duì)外暴露的屬性不想被別人修改時(shí)這么聲明裆针,然后在.m的extension
中再重新定義為可讀可寫。編譯器默認(rèn)的屬性語義為readwrite
寺晌。
內(nèi)存管理語義:strong世吨、weak、copy呻征、assign耘婚、unsafe_unretained
-
strong
表示對(duì)屬性所指對(duì)象的一種強(qiáng)引用的關(guān)系。當(dāng)聲明為strong
時(shí)陆赋,在setter
方法中會(huì)先持有新值沐祷,再釋放舊值,然后再把新值賦值給實(shí)例變量攒岛。
eg:
@property (nonatomic, strong) NSMutableArray *array;
- (void)setArray:(NSMutableArray *)array {
// [array retain];
// [_array release];
_array = array;
}
-
weak
表示對(duì)屬性所指對(duì)象的一種弱引用的關(guān)系赖临。當(dāng)聲明為weak
時(shí),在setter
方法中即不會(huì)持有新值灾锯,也不會(huì)釋放舊值兢榨,只是進(jìn)行一次簡單的賦值操作。但是當(dāng)屬性所指的對(duì)象被銷毀時(shí),屬性值會(huì)自動(dòng)置為nil 比較安全吵聪。
eg:
@property (nonatomic, weak) NSMutableArray *array;
- (void)setArray:(NSMutableArray *)array {
_array = array;
}
-
copy
類似于strong
凌那,也表示對(duì)屬性所指對(duì)象的一種強(qiáng)引用的關(guān)系,不同的是暖璧,copy
語義在setter
方法中并不是直接持有新值案怯,而是會(huì)拷貝出一份不可變的副本持有,然后再賦值給實(shí)例變量澎办。
eg:
@property (nonatomic, copy) NSString *name;
- (void)setName:(NSString *)name {
NSString *copyName = [name copy];
//[_name release];
_name = copyName;
//[copyName release]
}
copy
語義一般是用于那些具有可變子類的類型如:NSArray
嘲碱、NSDictionary
、NSString
等局蚀。這些類族都有其對(duì)應(yīng)的可變子類麦锯,如果聲明一個(gè)不可變的NSString
類型的屬性,由于父類指針可以指向子類對(duì)象琅绅,在給屬性賦值的時(shí)候扶欣,傳遞給setter
方法的值有可能是一個(gè)可變子類NSMutableString
的實(shí)例對(duì)象,那么在該屬性值賦值完成后千扶,由于屬性所指的對(duì)象其實(shí)是個(gè)可變的字符串料祠,就有可能被外界所篡改。所以為了保證屬性的安全澎羞,在賦值的時(shí)候需要先copy出一份不可變的對(duì)象髓绽,然后再賦值。
assign
表示在setter
方法賦值時(shí)只會(huì)進(jìn)行簡單的賦值操作妆绞,只用于修飾基本類型的數(shù)據(jù)(例如NSInteger顺呕、CGFloat等)。unsafe_unretained
語義類似于assign
括饶,不同的是它用于修飾對(duì)象類型株茶。如同它的字面意義一樣,它不會(huì)持有屬性所指的對(duì)象(類似于weak
)图焰,但是當(dāng)屬性所指的對(duì)象被銷毀時(shí)启盛,屬性值不會(huì)自動(dòng)置為nil,所以它并不安全技羔,可能會(huì)導(dǎo)致野指針驰徊。
3. 方法
在OC中,方法又被稱為發(fā)消息堕阔,對(duì)象需要調(diào)用方法來傳遞數(shù)據(jù)棍厂,而方法是什么呢?例如一個(gè)方法:[self doSomething:@"something"];
超陆,編譯器會(huì)將這個(gè)方法轉(zhuǎn)為如下的C語言函數(shù):
objc_msgSend(self, @selector(doSomething:), @"something");
objc_msgSend(id self, SEL cmd, ...)
這個(gè)函數(shù)就是OC消息傳遞的核心函數(shù)牺弹。我們平時(shí)調(diào)用的方法最終都會(huì)轉(zhuǎn)為這個(gè)函數(shù)調(diào)用浦马。這個(gè)函數(shù)接受兩個(gè)及以上的參數(shù),分別是:消息的接收者张漂、消息的簽名selector晶默、消息的參數(shù)。消息的接收者就是該方法的調(diào)用者航攒,消息的簽名是一個(gè)SEL類型的數(shù)據(jù)磺陡,可以理解為方法名的包裝,參數(shù)就是調(diào)用方法所傳遞的參數(shù)漠畜,按順序傳入币他。
當(dāng)調(diào)用這個(gè)函數(shù)后,objc_msgSend
會(huì)根據(jù)當(dāng)前消息接收者的isa
指針找到其所屬的類憔狞,然后在類的方法列表中根據(jù)selector
的名稱找到對(duì)應(yīng)的方法并執(zhí)行蝴悉,同時(shí)會(huì)將查找的結(jié)果緩存起來以供下次查找時(shí)快速的執(zhí)行。如果找不到瘾敢,就會(huì)根據(jù)super_class
指針沿著繼承體系一直往上查找拍冠,直到找到合適的方法之后跳轉(zhuǎn)到方法的實(shí)現(xiàn)并執(zhí)行。如果直到找到基類還是找不到對(duì)應(yīng)方法的話簇抵,那就開始執(zhí)行消息轉(zhuǎn)發(fā)機(jī)制庆杜。如果消息轉(zhuǎn)發(fā)的過程中也沒有找到的話,那就拋出異常程序終止碟摆。異常信息是常見的unrecognized selector send to instace xxxx
消息轉(zhuǎn)發(fā)
上面在介紹方法調(diào)用的實(shí)現(xiàn)流程時(shí)說到了消息轉(zhuǎn)發(fā)欣福,消息轉(zhuǎn)發(fā)是發(fā)生在當(dāng)給一個(gè)對(duì)象發(fā)送沒有實(shí)現(xiàn)的方法時(shí)會(huì)啟動(dòng)消息轉(zhuǎn)發(fā)。例如我們給一個(gè)對(duì)象發(fā)送沒有實(shí)現(xiàn)的消息:
[obj performSelector:@selector(doWork:array:) withObject:@"somthing" withObject:@[@"work"]];
此時(shí)消息轉(zhuǎn)發(fā)一共分為三個(gè)階段:
第一個(gè)階段:動(dòng)態(tài)方法解析
runtime會(huì)先詢問對(duì)象所屬的類焦履,看其能否動(dòng)態(tài)的添加方法以處理當(dāng)前未處理的消息。處理的方法有兩個(gè):
實(shí)例方法:+ (BOOL)resolveInstanceMethod:(SEL)sel
類方法:+ (BOOL)resolveClassMethod:(SEL)sel
方法的參數(shù)就是當(dāng)前消息的selector
雏逾,返回值是來標(biāo)識(shí)當(dāng)前的類是否能動(dòng)態(tài)的添加方法來處理這個(gè)selector
eg:
@interface MessageInvokeObj : NSObject
@end
@implementation MessageInvokeObj
void doWork(id self, SEL cmd, NSString *work, NSArray *array) {
NSLog(@"work = %@, array = %@", work, array);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *selectorString = NSStringFromSelector(sel);
if ([selectorString isEqualToString:@"doWork:array:"]) {
class_addMethod(self, sel, (IMP)doWork, "v@:@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
通過class_addMethod
函數(shù)動(dòng)態(tài)的添加一個(gè)方法來處理當(dāng)前的消息嘉裤。此時(shí)動(dòng)態(tài)添加方法成功,消息轉(zhuǎn)發(fā)結(jié)束栖博。
第二個(gè)階段:備援接收者
如果第一個(gè)階段無法處理該消息的話屑宠,runtime會(huì)詢問當(dāng)前的類能否把這個(gè)消息轉(zhuǎn)發(fā)給其他對(duì)象去處理,如果可以的話仇让,就返回這個(gè)對(duì)象典奉,否則就返回nil。
處理方法:- (id)forwardingTargetForSelector:(SEL)aSelector
eg:
@interface InvokeMethodObj : NSObject
@end
@implementation InvokeMethodObj
- (void)doWork:(NSString *)work array:(NSArray *)array {
NSLog(@"work = %@, array = %@", work, array);
}
@end
@interface MessageInvokeObj : NSObject
@end
@implementation MessageInvokeObj
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSString *selString = NSStringFromSelector(aSelector);
if ([selString isEqualToString:@"doWork:array:"]) {
InvokeMethodObj *obj = [[InvokeMethodObj alloc] init];
return obj;
}
return [super forwardingTargetForSelector:aSelector];
}
@end
第三個(gè)階段: forwardInvocation
最后一個(gè)階段丧叽,runtime會(huì)將這個(gè)消息的所有信息封裝到NSInvocation
對(duì)象當(dāng)中卫玖,包括消息的接收者target
、消息的selector
以及消息的所有參數(shù)踊淳。最后一次詢問接收者能否處理假瞬,如果不能將拋出異常陕靠。如果可以,直接把消息指派給目標(biāo)對(duì)象脱茉。
先返回正確的方法簽名:- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
然后執(zhí)行消息轉(zhuǎn)發(fā):- (void)forwardInvocation:(NSInvocation *)anInvocation
eg:
@interface MessageInvokeObj : NSObject
@end
@implementation MessageInvokeObj
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSString *selString = NSStringFromSelector(aSelector);
if ([selString isEqualToString:@"doWork:array:"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
InvokeMethodObj *obj = [[InvokeMethodObj alloc] init];
if ([obj respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:obj];
}
}
@end
OC對(duì)象模型大概就是這些剪芥,要想更深入的了解,需要讀下runtime的源碼琴许。下一篇寫下OC的內(nèi)存管理...