對(duì)象模型
新建一個(gè)類:
#import <Foundation/Foundation.h>
@interface CustomClass : NSObject
@end
@implementation CustomClass
@end
int main(int argc, char * argv[]) {
CustomClass *customObject = [[CustomClass alloc] init];
}
在終端運(yùn)行以下命令:
xcrun -sdk iphonesimulator12.0 clang -rewrite-objc CustomObject.m
.m文件被編譯成C++文件,截取部分代碼如下:
...
typedef struct objc_object CustomClass;
...
int main(int argc, char * argv[]) {
CustomClass *customObject = ((CustomClass *(*)(id, SEL))(void *)objc_msgSend)((id)((CustomClass *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("CustomClass"), sel_registerName("alloc")), sel_registerName("init"));
}
...
可以看到我們定義的CustomClass
被定義成objc_object
結(jié)構(gòu)體,在main函數(shù)里面的CustomClass *customObject
其實(shí)就是struct objc_object *customObject
,所以customObject
的本質(zhì)就是一個(gè)指向objc_object
結(jié)構(gòu)體的指針。
objc_object
的源碼:
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
可以看到objc_object有一個(gè)Class類型的isa
變量,這個(gè)isa是指向該實(shí)例所屬的類的。而Class其實(shí)本質(zhì)就是objc_class
:
typedef struct objc_class *Class;
objc_class
的源碼:
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
...
};
類繼承自對(duì)象, 所有類其實(shí)也是對(duì)象,它的isa指向的類就是這個(gè)類的元類(meta-class)
。元類只有一個(gè)對(duì)象,就是類對(duì)象
。類方法就在類對(duì)象里面。
給一個(gè)對(duì)象發(fā)送消息,會(huì)到這個(gè)對(duì)象所屬的類里面找對(duì)應(yīng)的方法;給一個(gè)類發(fā)送消息,會(huì)到這個(gè)類的元類(也就是類對(duì)象所屬的類)里面去找對(duì)應(yīng)的方法。
而元類也是類,它也有isa指針,這個(gè)isa指針統(tǒng)一都直接指向NSObject的元類。而NSObject的元類的isa則指向自己。另外NSObject的元類的父類則指向NSObject。而其他子類的元類指向其父類的元類。
調(diào)用實(shí)例的方法,會(huì)到類里面找。調(diào)用類的方法,也就是元類的實(shí)例方法,因?yàn)轭惥褪窃惖膶?shí)例,回到元類里面找,在元類里面找不到就會(huì)去元類的父類,最后來(lái)到NSObject元類,NSObject元類的父類就是NSObject。
objc_method
的定義:
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
SEL
的定義:
typedef objc_selector * SEL;
可以理解為區(qū)分方法的 ID。
在Object-C中可以通過(guò)以下方法獲得SEL:
SEL @selector(<selector)
SEL sel_registerName(const char * _Nonnull str)
IMP
的定義:
typedef id (*IMP)(id, SEL, ...);
指向最終實(shí)現(xiàn)程序的內(nèi)存地址的指針。
消息機(jī)制
在Object-C里的函數(shù)調(diào)用其實(shí)都是發(fā)送消息,[receiver massage]
會(huì)被編譯成
objc_msgSend(receiver, selector)
如果有參數(shù),則為:
objc_msgSend(receiver, selector, arg1, arg2, ...)
objc_class 里面還有一個(gè)變量:struct objc_cache *cache
,它主要是為了調(diào)用的性能進(jìn)行優(yōu)化的。每當(dāng)實(shí)例對(duì)象接收到一個(gè)消息時(shí),它會(huì)先到cache中去找能夠響應(yīng)的方法,找不到再去方法列表里面找。找到之后就會(huì)保存到cache里面。已備下次再被調(diào)用。
如果對(duì)象接收到無(wú)法解讀的消息,那將會(huì)調(diào)用其所屬類的以下類方法:
+ (BOOL)resolveInstanceMethod:(SEL)sel
sel就是那個(gè)未知的選擇子,返回的BOOL類型來(lái)表示這個(gè)類能否新增一個(gè)實(shí)例方法來(lái)處理這個(gè)選擇子。所以本類有機(jī)會(huì)新增一個(gè)處理此選擇子的方法。一般是在resolveInstanceMethod:方法里面調(diào)用class_addMethod方法來(lái)動(dòng)態(tài)添加方法,但前提是實(shí)現(xiàn)代碼已經(jīng)提前寫(xiě)好了。
如果上述方法返回的是NO,那么當(dāng)前接受者還有第二次機(jī)會(huì),runtime還會(huì)調(diào)用該對(duì)象的以下方法,看能否將該消息轉(zhuǎn)發(fā)給其他接受者處理:
- (id)forwardingTargetForSelector:(SEL)aSelector
如果此時(shí)還是返回nil,那么會(huì)來(lái)到完整的消息轉(zhuǎn)發(fā)。runtime會(huì)調(diào)用以下方法:
- (void)forwardInvocation:(NSInvocation *)anInvocation
anInvocation包含了那條尚未處理的消息的全部細(xì)節(jié),包括選擇子、目標(biāo)以及參數(shù)。這個(gè)步驟可以修改消息的內(nèi)容,比如追加參數(shù)或者改變選擇子等。如果本類不處理此消息,會(huì)調(diào)用超類的同名方法。最后傳到NSObject時(shí)會(huì)調(diào)用“doesNotRecognizeSelector:”拋出異常。
Category
我們先給前面自定義的類實(shí)現(xiàn)一個(gè)分類:
@interface CustomClass : NSObject
@end
@implementation CustomClass
- (void)originMethod {
NSLog(@"originMethod");
}
@end
@interface CustomClass(MyAddition)<NSCopying>
@property (nonatomic, copy) NSString *customProperty;
@end
@implementation CustomClass(MyAddition)
+ (void)addClassMethod {
NSLog(@"addClassMethod");
}
- (void)addInstanceMethod {
NSLog(@"addInstanceMethod");
}
- (id)copyWithZone:(NSZone *)zone {
return nil;
}
@end
運(yùn)行以下命令,得到一個(gè)C++文件
clang -rewrite-objc CustomClass.m
這個(gè)文件很長(zhǎng),先看其中的一部分:
static struct _category_t _OBJC_$_CATEGORY_CustomClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"CustomClass",
0, // &OBJC_CLASS_$_CustomClass,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_CustomClass_$_MyAddition,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_CustomClass_$_MyAddition,
(const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_CustomClass_$_MyAddition,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_CustomClass_$_MyAddition,
};
可以看到這個(gè)就是分類的定義,其本質(zhì)就是category_t,可以在runtime的源碼中找到category_t的定義:
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta) {
if (isMeta) return nil; // classProperties;
else return instanceProperties;
}
};
其中定義了分類的名字、類、實(shí)例方法、類方法、實(shí)現(xiàn)的協(xié)議、屬性。
對(duì)應(yīng)起來(lái)OBJC_$_CATEGORY_INSTANCE_METHODS_CustomClass_$_MyAddition
則保存了所添加的實(shí)例方法:
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[2];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_CustomClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
2,
{{(struct objc_selector *)"addInstanceMethod", "v16@0:8", (void *)_I_CustomClass_MyAddition_addInstanceMethod},
{(struct objc_selector *)"copyWithZone:", "@24@0:8^{_NSZone=}16", (void *)_I_CustomClass_MyAddition_copyWithZone_}}
};
method_list_t 里面包含了方法的大小、數(shù)目、方法數(shù)組。
OBJC_$_CATEGORY_CLASS_METHODS_CustomClass_$_MyAddition
保存了類方法;
OBJC_CATEGORY_PROTOCOLS_$_CustomClass_$_MyAddition
是協(xié)議列表:
static struct /*_protocol_list_t*/ {
long protocol_count; // Note, this is 32/64 bit
struct _protocol_t *super_protocols[1];
} _OBJC_CATEGORY_PROTOCOLS_$_CustomClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1,
&_OBJC_PROTOCOL_NSCopying
};
OBJC_$_PROP_LIST_CustomClass_$_MyAddition
屬性列表:
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_CustomClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"customProperty","T@\"NSString\",C,N"}}
};
在DATA段我們還可以看到一個(gè)保存分類列表的數(shù)組:
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_CustomClass_$_MyAddition,
};
Category被附加到類上面是在objc-os.mm的_objc_init方法里發(fā)生的,_objc_init里面的調(diào)用的map_images最終會(huì)調(diào)用objc-runtime-new.mm里面的_read_images方法,而在_read_images方法的結(jié)尾,有以下的代碼片段:
// Discover categories.
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Disavow any knowledge of this category.
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
if (cat->classMethods || cat->protocols
/* || cat->classProperties */)
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}
1、通過(guò)_getObjc2CategoryList
拿到所有的分類;
2、對(duì)每個(gè)分類,拿到所屬的類;
3、Register the category with its target class;addUnattachedCategoryForClass(cat, cls, hi)
;
4、Rebuild the class's method lists (etc) if the class is realized;remethodizeClass(cls)
;
來(lái)看addUnattachedCategoryForClass
的源碼:
static void addUnattachedCategoryForClass(category_t *cat, Class cls,
header_info *catHeader)
{
runtimeLock.assertWriting();
// DO NOT use cat->cls! cls may be cat->cls->isa instead
NXMapTable *cats = unattachedCategories();
category_list *list;
list = (category_list *)NXMapGet(cats, cls);
if (!list) {
list = (category_list *)
calloc(sizeof(*list) + sizeof(list->list[0]), 1);
} else {
list = (category_list *)
realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
}
list->list[list->count++] = (locstamped_category_t){cat, catHeader};
NXMapInsert(cats, cls, list);
}
注釋里說(shuō)是把分類注冊(cè)到類中,其實(shí)就是把類和category做一個(gè)關(guān)聯(lián)映射。remethodizeClass
則真正在把方法添加到類中:
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertWriting();
isMeta = cls->isMetaClass();
// Re-methodizing: check for more categories
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// fixme rearrange to remove these intermediate allocations
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
// Count backwards through cats to get newest categories first
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;
bool fromBundle = NO;
while (i--) {
auto& entry = cats->list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist = entry.cat->propertiesForMeta(isMeta);
if (proplist) {
proplists[propcount++] = proplist;
}
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
rw->properties.attachLists(proplists, propcount);
free(proplists);
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
一個(gè)關(guān)鍵的代碼:auto rw = cls->data()
;
根據(jù)objc_class的定義我們可以知道rw就是class_rw_t
,而class_rw_t里面還包含著class_ro_t
。事實(shí)上,類中的屬性、方法還有遵循的協(xié)議等信息都保存在 class_rw_t 中,而class_ro_t則存儲(chǔ)了當(dāng)前類在編譯期就已經(jīng)確定的屬性、方法以及遵循的協(xié)議。所以分類是把方法添加到class_rw_t當(dāng)中的
。
分類本質(zhì)上就是一個(gè)保存了一系列方法的結(jié)構(gòu)體,在運(yùn)行時(shí)分類的方法會(huì)被附加到類的方法前面。
KVO原理
KVO即Key-Value-Observer
鍵值觀察,是通過(guò)isa-swizzling
技術(shù)實(shí)現(xiàn)的:
- 1、當(dāng)類A的實(shí)例第一次被觀察的時(shí)候,系統(tǒng)會(huì)在運(yùn)行期動(dòng)態(tài)地創(chuàng)建類A的派生類NSKVONotifying_A,其實(shí)就是
A的子類
; - 2、類NSKVONotifying_A會(huì)重寫(xiě)被觀察的屬性的setter方法,在修改值之前會(huì)調(diào)用
willChangeValueForKey:
方法,修改值之后會(huì)調(diào)用didChangeValueForKey:
方法,這兩個(gè)方法最終都會(huì)被調(diào)用到observeValueForKeyPath:ofObject:change:context:
方法中; - 3、類NSKVONotifying_A重寫(xiě)
class
方法,返回類A,類NSKVONotifying_A還會(huì)重寫(xiě)dealloc方法釋放資源; - 4、被觀察對(duì)象的
isa
指針從指向原來(lái)的 A 類,被 KVO 機(jī)制修改為指向系統(tǒng)新創(chuàng)建的子類 NSKVONotifying_A類,來(lái)實(shí)現(xiàn)當(dāng)前類屬性值改變的監(jiān)聽(tīng)。
KVO的使用其實(shí)比較容易崩潰,addObserver和removeObserver需要是成對(duì)的,如果重復(fù)remove則會(huì)導(dǎo)致NSRangeException類型的Crash,如果忘記remove則會(huì)在觀察者釋放后再次接收到KVO回調(diào)時(shí)Crash。蘋(píng)果官方推薦的方式是,在init的時(shí)候進(jìn)行addObserver,在dealloc時(shí)removeObserver,這樣可以保證add和remove是成對(duì)出現(xiàn)的,是一種比較理想的使用方式。
KVOController
是Facebook的一個(gè)KVO開(kāi)源第三方框架,它具有原生KVO所有的功能,但規(guī)避了原生KVO的很多問(wèn)題,而且支持block回調(diào)。
KVC
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKey:(NSString *)key;
先查找setKey或_setKey方法存不存在,如果存在則直接調(diào)用設(shè)值,否則詢問(wèn)是否可以直接訪問(wèn)變量+ (BOOL)accessInstanceVariablesDirectly
,如果可以則直接設(shè)置,否則報(bào)錯(cuò)
- (id)valueForKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key;
先查找getKey、key、isKey、_isKey方法存不存在,如果存在則直接調(diào)用,否則詢問(wèn)是否可以直接訪問(wèn)變量+ (BOOL)accessInstanceVariablesDirectly
,如果可以則直接讀取,否則報(bào)錯(cuò)。
被監(jiān)聽(tīng)的實(shí)例一定有set方法,所以KVC會(huì)觸發(fā)KVO。
strong、weak、assign、copy
有一個(gè)值為B
,它的內(nèi)存地址為A
,現(xiàn)在把A賦值給C會(huì)發(fā)生什么呢?如下例子:
NSMutableString *C = [[NSMutableString alloc] initWithString:@"B"];
這里的A就是創(chuàng)建字符串@"B"時(shí)的值地址。引用計(jì)數(shù)的概念就是針對(duì)A的,但A賦值給其他變量或者指針設(shè)置為nil,如A=nil,都會(huì)導(dǎo)致引用計(jì)數(shù)有所增減。當(dāng)內(nèi)容區(qū)域引用計(jì)數(shù)為0的時(shí)候就會(huì)將數(shù)據(jù)B抹除。
使用copy、strong、retain、weak、assign區(qū)別就在:
- 是否對(duì)A有引用計(jì)數(shù)增加;
- 是否開(kāi)辟新的內(nèi)存。
@property (nonatomic, strong) NSMutableString *strongC;
@property (nonatomic, weak) NSMutableString *weakC;
@property (nonatomic, assign) NSMutableString *assignC;
@property (nonatomic, copy) NSMutableString *copyC;
...
NSMutableString *C = [[NSMutableString alloc] initWithString:@"B"];
self.strongC = C;
self.weakC = C;
self.copyC = C;
self.assignC = C;
strongC會(huì)增加A的引用計(jì)數(shù);weakC不會(huì)增加A的引用計(jì)數(shù);copyC也不會(huì)增加A的引用計(jì)數(shù),它會(huì)開(kāi)辟一塊新的內(nèi)存。
assign和weak類似,都不會(huì)增加A的引用計(jì)數(shù),但是區(qū)別在于但weak指向的對(duì)象被釋放后weak指針會(huì)被置為nil,而assign不會(huì),就會(huì)導(dǎo)致野指針的問(wèn)題。但assign修飾基本數(shù)據(jù)類型是安全的,而weak只能修飾對(duì)象類型。
retain和strong一樣,會(huì)增加A的引用計(jì)數(shù),但在MRC時(shí)會(huì)有差別。
copy NSString NSArray不會(huì)開(kāi)辟新的內(nèi)存,因?yàn)樗鼈兪遣豢勺冏兞俊?/p>
對(duì)不可變對(duì)象進(jìn)行copy,是淺拷貝;其余的對(duì)不可變對(duì)象進(jìn)行mutableCopy,對(duì)可變對(duì)象進(jìn)行copy,對(duì)可變對(duì)象進(jìn)行mutableCopy都是深拷貝。
copy之后得到的都是不可變對(duì)象,mutableCopy之后得到的都是可變對(duì)象。
@property (nonatomic, assign) float a;
是保存在它所在的對(duì)象所存在的堆上面的。
iOS中copy,strong,retain,weak和assign的區(qū)別
參考