大家好泛源!我是Tony,一個(gè)熱愛(ài)技術(shù),希望運(yùn)用技術(shù)改變生活的的追夢(mèng)男孩。閑話不多說(shuō),下面帶大家認(rèn)識(shí)一下Objective-C的對(duì)象从藤。
從OC的角度看對(duì)象本質(zhì)
OC是一門面向?qū)ο蟮恼Z(yǔ)言,我們從一開(kāi)始接觸OC的時(shí)候锁蠕,肯定都接觸過(guò)NSObject對(duì)象夷野,和其他語(yǔ)言一樣,如Java的Object荣倾,是整個(gè)OC的根類型悯搔,通俗的講就是OC中所有的類都是直接或者間接的繼承于此類,下面我們先移步到NSObject的頭文件舌仍,看看里面有哪些內(nèi)容鳖孤。
NSObject協(xié)議
@protocol NSObject
//判斷對(duì)象相等的方法
- (BOOL)isEqual:(id)object;
//獲取對(duì)象的hash值
@property (readonly) NSUInteger hash;
//父類的類對(duì)象
@property (readonly) Class superclass;
- (Class)class ;
//自身指針
- (instancetype)self;
//直接向?qū)ο蟀l(fā)送消息的方法,繞過(guò)編譯器的檢測(cè)(運(yùn)行時(shí)系統(tǒng)負(fù)責(zé)去找方法抡笼,在編譯時(shí)候不做任何校驗(yàn))苏揣,
//Cocoa支持在運(yùn)行時(shí)向某個(gè)類添加方法,即方法編譯時(shí)不存在推姻,但是運(yùn)行時(shí)候存在平匈,這時(shí)候必然需要使用performSelector去調(diào)用。
//為了程序的健壯性,會(huì)使用檢查方法- (BOOL)respondsToSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
//自省方法 用來(lái)判斷一個(gè)對(duì)象是不是某一個(gè)類的對(duì)象(包括子類)
- (BOOL)isKindOfClass:(Class)aClass;
//自省方法 用來(lái)判斷一個(gè)對(duì)象是不是某一個(gè)類的對(duì)象(不包括子類)
- (BOOL)isMemberOfClass:(Class)aClass;
//判斷對(duì)象是否尊守某個(gè)協(xié)議
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
//判斷方法是否存在
- (BOOL)respondsToSelector:(SEL)aSelector;
//內(nèi)存管理相關(guān)的方法
- (instancetype)retain OBJC_ARC_UNAVAILABLE;
- (oneway void)release OBJC_ARC_UNAVAILABLE;
- (instancetype)autorelease OBJC_ARC_UNAVAILABLE;
- (NSUInteger)retainCount OBJC_ARC_UNAVAILABLE;
- (struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;
@end
NSObject類
這里僅留下了方法調(diào)用相關(guān)的方法
//方法查找相關(guān)的方法
+ (BOOL)instancesRespondToSelector:(SEL)aSelector;
- (IMP)methodForSelector:(SEL)aSelector;
+ (IMP)instanceMethodForSelector:(SEL)aSelector;
//第一步:在方法查找過(guò)程中增炭,未處理掉的方法就會(huì)來(lái)到此處忍燥,詢問(wèn)是否在運(yùn)行時(shí)增加該方法的IMP
//類方法決議(動(dòng)態(tài)方法解析),調(diào)用resolveClassMethod給個(gè)機(jī)會(huì)讓類添加這個(gè)實(shí)現(xiàn)這個(gè)函數(shù)
+ (BOOL)resolveClassMethod:(SEL)sel
//類實(shí)例方法決議(動(dòng)態(tài)方法解析)隙姿,調(diào)用resolveInstanceMethod給個(gè)機(jī)會(huì)讓類添加這個(gè)實(shí)現(xiàn)這個(gè)函數(shù)
+ (BOOL)resolveInstanceMethod:(SEL)sel;
//第二步:快速方法轉(zhuǎn)發(fā)梅垄,讓其他類處理
- (id)forwardingTargetForSelector:(SEL)aSelector;
//第三步:快速方法轉(zhuǎn)發(fā),讓其他類處理
//對(duì)象方法簽名输玷,用于生成NSInvocation對(duì)象
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector队丝;
//實(shí)例方法簽名,用于生成NSInvocation對(duì)象
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation ;
//第四步:方法找不到時(shí)欲鹏,來(lái)到此方法机久,拋出異常
- (void)doesNotRecognizeSelector:(SEL)aSelector;
NSObject類干的事情有hash、equal赔嚎、對(duì)象的原型膘盖、自省、內(nèi)存管理尤误、方法查找侠畔、動(dòng)態(tài)方法解析、方法的快速轉(zhuǎn)發(fā)等损晤,
在頭文件中能夠看到和對(duì)象本質(zhì)相關(guān)聯(lián)的就是Class,isa等關(guān)鍵詞软棺,下面我們將繼續(xù)學(xué)習(xí)Class和isa
Class的本質(zhì)
Class的定義是typedef struct objc_class *Class;
,在objc4中objc_class是繼承objc_object,所以我們先看看objc_object的定義:
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// object may have associated objects?
bool hasAssociatedObjects();
void setHasAssociatedObjects();
// object may be weakly referenced?
bool isWeaklyReferenced();
void setWeaklyReferenced_nolock();
..........
};
可以看見(jiàn)objc_object的定義非常簡(jiǎn)單,內(nèi)部有一個(gè)isa
成員沉馆。
下面我們看看objc4中objc_class的結(jié)構(gòu)
下面是objc4中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
bool isMetaClass() {
ASSERT(this);
ASSERT(isRealized());
#if FAST_CACHE_META
return cache.getBit(FAST_CACHE_META);
#else
return data()->ro->flags & RO_META;
#endif
}
// Like isMetaClass, but also valid on un-realized classes
bool isMetaClassMaybeUnrealized() {
return bits.safe_ro()->flags & RO_META;
}
// NOT identical to this->ISA when this is a metaclass
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
bool isRootClass() {
return superclass == nil;
}
bool isRootMetaclass() {
return ISA() == (Class)this;
}
...........
};
- isa(繼承自objc_object)指向元類
- superclass指向父類
- cache方法緩存,當(dāng)調(diào)用一次方法后就會(huì)緩存進(jìn)vtable中,加速下次調(diào)用
- bits這是今天的主角,就是存儲(chǔ)類的方法码党、屬性和遵循的協(xié)議等信息的地方
我們從Class
的結(jié)構(gòu)中可以看見(jiàn)德崭,類對(duì)象其實(shí)存儲(chǔ)了實(shí)例的變量和方法斥黑,及相關(guān)的協(xié)議;對(duì)象則是存儲(chǔ)實(shí)例變量的值眉厨;我們經(jīng)常會(huì)使用類的類方法锌奴,在Class
的結(jié)構(gòu)中我們并沒(méi)有看見(jiàn)類方法的存儲(chǔ)位置,類對(duì)象的isa
其實(shí)指向的就是metaclass
,而metaclass
中存儲(chǔ)了類方法憾股。
看了這些是不是還有很多疑惑鹿蜀,如objc_cache
方法緩存原理是啥?什么時(shí)候使用到服球?為什么要設(shè)計(jì)metaclass
?方法的調(diào)用過(guò)程是什么茴恰?不著急后面將一一解答
從C/C++的角度看看對(duì)象本質(zhì)
我們通過(guò)創(chuàng)建OC對(duì)象,并將OC文件轉(zhuǎn)化為C++文件來(lái)探尋OC對(duì)象的本質(zhì)斩熊,OC如下代碼
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSObject *object = [[NSObject alloc] init];
NSLog(@"objc對(duì)象實(shí)際需要的內(nèi)存大小: %zd", class_getInstanceSize([object class]));//objc對(duì)象實(shí)際需要的內(nèi)存大小: 8
NSLog(@"objc對(duì)象實(shí)際分配的內(nèi)存大小: %zd", malloc_size((__bridge const void *)(object)));//objc對(duì)象實(shí)際分配的內(nèi)存大小: 16
}
return 0;
}
使用 clang 將 OC 代碼轉(zhuǎn)換為 C++ 代碼
clang -rewrite-objc main.m -o main.cpp
或者使用 XCode 工具 xcrun 進(jìn)行轉(zhuǎn)換
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
打開(kāi)main.cpp定位到main函數(shù)
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
//簡(jiǎn)化版本
NSObject *object = objc_msgSend(objc_msgSend(objc_getClass("NSObject"),sel_registerName("alloc")),sel_registerName("init"))
}
return 0;
}
這里可以看到往枣,objc_msgSend先向NSObject發(fā)送了alloc,然后發(fā)送了init消息;此時(shí)對(duì)象就被初始化完畢,下面我們打印一下內(nèi)存占用情況分冈,代碼如下
NSLog(@"objc對(duì)象實(shí)際需要的內(nèi)存大小: %zd", class_getInstanceSize([object class]));//打印結(jié)果:8
NSLog(@"objc對(duì)象實(shí)際分配的內(nèi)存大小: %zd", malloc_size((__bridge const void *)(object)));//打印結(jié)果:16
是不是很疑惑圾另,對(duì)象僅僅使用了8字節(jié)為什么會(huì)分配16個(gè)字節(jié)的大小呢?對(duì)于這個(gè)問(wèn)題我們可以通過(guò)閱讀objc4的源代碼來(lái)找到答案雕沉。通過(guò)查看跟蹤obj4中alloc和allocWithZone兩個(gè)函數(shù)的實(shí)現(xiàn)集乔,會(huì)發(fā)現(xiàn)這個(gè)連個(gè)函數(shù)都會(huì)調(diào)用一個(gè)instanceSize的函數(shù):
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16bytes.
if (size < 16) size = 16;
return size;
}
是不是豁然開(kāi)朗,對(duì)象的內(nèi)存大小最低消費(fèi)就是16字節(jié)坡椒。
下面我們看看對(duì)象中有成員變量時(shí)扰路,對(duì)象的內(nèi)存占用變化
@interface Person : NSObject
@property (nonatomic,assign) int age;
@end
@implementation Person
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Person *object = [[Person alloc] init];
NSLog(@"objc對(duì)象實(shí)際需要的內(nèi)存大小: %zd", class_getInstanceSize([object class]));//打印結(jié)果:16
NSLog(@"objc對(duì)象實(shí)際分配的內(nèi)存大小: %zd", malloc_size((__bridge const void *)(object)));//打印結(jié)果:16
}
return 0;
}
此時(shí)對(duì)象的實(shí)際內(nèi)存大小和分配內(nèi)存大小都是16字節(jié),我們知道int類型是占4個(gè)字節(jié)肠牲,而8+4=12幼衰,這應(yīng)該才是實(shí)際內(nèi)存占用的大小,為什么是16字節(jié)呢缀雳?
這里就需要引入內(nèi)存地址對(duì)齊的問(wèn)題渡嚣,地址對(duì)齊意思就是內(nèi)存的增加必須滿足是8字節(jié)的倍數(shù),所以12字節(jié)不滿16肥印,則分配16字節(jié)识椰,其中增加了4字節(jié)的占位
有了這個(gè)概念后,如果我為Person對(duì)象再增加一個(gè)int類型number的屬性深碱,實(shí)際內(nèi)存大小是否會(huì)改變呢腹鹉?下面我們看一下打印結(jié)果:
@interface Person : NSObject
@property (nonatomic,assign) int age;
@property (nonatomic,assign) int number;
@end
@implementation Person
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Person *object = [[Person alloc] init];
NSLog(@"objc對(duì)象實(shí)際需要的內(nèi)存大小: %zd", class_getInstanceSize([object class]));//打印結(jié)果:16
NSLog(@"objc對(duì)象實(shí)際分配的內(nèi)存大小: %zd", malloc_size((__bridge const void *)(object)));//打印結(jié)果:16
}
return 0;
}
可以看見(jiàn)打印結(jié)果都是16,說(shuō)明內(nèi)存大小并無(wú)增加敷硅,此時(shí)number的內(nèi)存剛好填補(bǔ)了之前的4字節(jié)占位內(nèi)存功咒,不會(huì)造成內(nèi)存的浪費(fèi)
上篇文章先到此,感謝大家的閱讀绞蹦!