OC 物件模型
類的記憶體結構
NSObject :絕大多數OC物件的基類(NSProxy除外)
在NSObject類中可以看到一個Class型別的isa 例項變數
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
在objc類中可以看到(isa)Class實際上是指向結構體為objc_class的指標
/// A pointer to an instance of a class.
typedef struct objc_object *id;(表示一個類的物件)
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
表示每一個類的物件都有一個指向他自己類的isa指標
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class; (表示一個類)
1.結構體objc_class定義
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;(instance指向自己類物件的指標,class指向自己的元類metaClass metaClass裡有關Class物件的方法)
#if !__OBJC2__
Class super_class (指向自己父類的指標) OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size (例項的大小 最小為16個位元組) OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars (成員變數連結串列) OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists (方法連結串列 二級指標) OBJC2_UNAVAILABLE;
struct objc_cache *cache (快取) OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols (代理連結串列) OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
問題:
@interface NSObject(mkCategory)
+(void)test;
@end
@implementation NSObject(mkCategory)
-(void)test{
NSLog(@"this mkcategory method %s",__func__);
}
[NSObject test]; [NSMutableArray test];執行情況
為什麼Objective-C類不能動態新增成員變數
在Objective-C提供的runtime函式中,確實有一個class_addIvar()函式用於給類新增成員變數,但是文件中特別說明:
1 |
This function may only be called after objc_allocateClassPair and before objc_registerClassPair. Adding an instance variable to an existing class is not supported. |
意思是說,這個函式只能在“構建一個類的過程中”呼叫。一旦完成類定義,就不能再新增成員變量了。經過編譯的類在程式啟動後就被runtime載入,沒有機會呼叫addIvar。程式在執行時動態構建的類需要在呼叫objc_registerClassPair之後才可以被使用,同樣沒有機會再新增成員變數。 為基類動態增加成員變數會導致所有已創建出的子類例項都無法使用。那為什麼runtime允許動態新增方法和屬性,而不會引發問題呢?因為方法和屬性並不“屬於”類例項,而成員變數“屬於”類例項。我們所說的“類例項”概念,指的是一塊記憶體區域,包含了isa指標和所有的成員變數。所以假如允許動態修改類成員變數佈局,已經創建出的類例項就不符合類定義了,變成了無效物件。但方法定義是在objc_class中管理的,不管如何增刪類方法,都不影響類例項的記憶體佈局,已經創建出的類例項仍然可正常使用。
類增加成員變數會增加例項物件的存在大小,程式碼如下:
BOOL class_addIvar(Class cls, const char *name, size_t size,
uint8_t alignment, const char *type)
{
bool result = YES;
if (!cls) return NO;
if (ISMETA(cls)) return NO;
if (!(cls->info & CLS_CONSTRUCTING)) return NO;
if (!type) type = "";
if (name && 0 == strcmp(name, "")) name = nil;
mutex_locker_t lock(classLock);
// Check for existing ivar with this name
// fixme check superclasses?
if (cls->ivars) {
int i;
for (i = 0; i < cls->ivars->ivar_count; i++) {
if (0 == strcmp(cls->ivars->ivar_list[i].ivar_name, name)) {
result = NO;
break;
}
}
}
if (result) {
old_ivar_list *old = cls->ivars;
size_t oldSize;
int newCount;
old_ivar *ivar;
size_t alignBytes;
size_t misalign;
if (old) {
oldSize = sizeof(old_ivar_list) +
(old->ivar_count - 1) * sizeof(old_ivar);
newCount = 1 + old->ivar_count;
} else {
oldSize = sizeof(old_ivar_list) - sizeof(old_ivar);
newCount = 1;
}
// allocate new ivar list
cls->ivars = (old_ivar_list *)
calloc(oldSize+sizeof(old_ivar), 1);//開闢空間
if (old) memcpy(cls->ivars, old, oldSize);//把old記憶體的資源拷貝到cls->ivars所指的地方
if (old && malloc_size(old)) free(old);
cls->ivars->ivar_count = newCount;
ivar = &cls->ivars->ivar_list[newCount-1];
// set ivar name and type
ivar->ivar_name = strdup(name);//把name拷貝到ivar->ivar_name
ivar->ivar_type = strdup(type);
// align if necessary
alignBytes = 1 << alignment;
misalign = cls->instance_size % alignBytes;
if (misalign) cls->instance_size += (long)(alignBytes - misalign);
// set ivar offset and increase instance size
ivar->ivar_offset = (int)cls->instance_size;
cls->instance_size += (long)size;
}
return result;
}