大家好!我是Tony,一個熱愛技術,希望運用技術改變生活的的追夢男孩。閑話不多說,下面帶大家認識一下Objective-C的對象。
從OC的角度看對象本質
OC是一門面向對象的語言,我們從一開始接觸OC的時候,肯定都接觸過NSObject對象,和其他語言一樣,如Java的Object,是整個OC的根類型,通俗的講就是OC中所有的類都是直接或者間接的繼承于此類,下面我們先移步到NSObject的頭文件,看看里面有哪些內容。
NSObject協議
@protocol NSObject
//判斷對象相等的方法
- (BOOL)isEqual:(id)object;
//獲取對象的hash值
@property (readonly) NSUInteger hash;
//父類的類對象
@property (readonly) Class superclass;
- (Class)class ;
//自身指針
- (instancetype)self;
//直接向對象發(fā)送消息的方法,繞過編譯器的檢測(運行時系統負責去找方法,在編譯時候不做任何校驗),
//Cocoa支持在運行時向某個類添加方法,即方法編譯時不存在,但是運行時候存在,這時候必然需要使用performSelector去調用。
//為了程序的健壯性,會使用檢查方法- (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;
//自省方法 用來判斷一個對象是不是某一個類的對象(包括子類)
- (BOOL)isKindOfClass:(Class)aClass;
//自省方法 用來判斷一個對象是不是某一個類的對象(不包括子類)
- (BOOL)isMemberOfClass:(Class)aClass;
//判斷對象是否尊守某個協議
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
//判斷方法是否存在
- (BOOL)respondsToSelector:(SEL)aSelector;
//內存管理相關的方法
- (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類
這里僅留下了方法調用相關的方法
//方法查找相關的方法
+ (BOOL)instancesRespondToSelector:(SEL)aSelector;
- (IMP)methodForSelector:(SEL)aSelector;
+ (IMP)instanceMethodForSelector:(SEL)aSelector;
//第一步:在方法查找過程中,未處理掉的方法就會來到此處,詢問是否在運行時增加該方法的IMP
//類方法決議(動態(tài)方法解析),調用resolveClassMethod給個機會讓類添加這個實現這個函數
+ (BOOL)resolveClassMethod:(SEL)sel
//類實例方法決議(動態(tài)方法解析),調用resolveInstanceMethod給個機會讓類添加這個實現這個函數
+ (BOOL)resolveInstanceMethod:(SEL)sel;
//第二步:快速方法轉發(fā),讓其他類處理
- (id)forwardingTargetForSelector:(SEL)aSelector;
//第三步:快速方法轉發(fā),讓其他類處理
//對象方法簽名,用于生成NSInvocation對象
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
//實例方法簽名,用于生成NSInvocation對象
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation ;
//第四步:方法找不到時,來到此方法,拋出異常
- (void)doesNotRecognizeSelector:(SEL)aSelector;
NSObject類干的事情有hash、equal、對象的原型、自省、內存管理、方法查找、動態(tài)方法解析、方法的快速轉發(fā)等,
在頭文件中能夠看到和對象本質相關聯的就是Class,isa等關鍵詞,下面我們將繼續(xù)學習Class和isa
Class的本質
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();
..........
};
可以看見objc_object的定義非常簡單,內部有一個isa成員。
下面我們看看objc4中objc_class的結構

下面是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方法緩存,當調用一次方法后就會緩存進vtable中,加速下次調用
- bits這是今天的主角,就是存儲類的方法、屬性和遵循的協議等信息的地方
我們從Class的結構中可以看見,類對象其實存儲了實例的變量和方法,及相關的協議;對象則是存儲實例變量的值;我們經常會使用類的類方法,在Class的結構中我們并沒有看見類方法的存儲位置,類對象的isa其實指向的就是metaclass,而metaclass中存儲了類方法。
看了這些是不是還有很多疑惑,如objc_cache方法緩存原理是啥?什么時候使用到?為什么要設計metaclass?方法的調用過程是什么?不著急后面將一一解答
從C/C++的角度看看對象本質
我們通過創(chuàng)建OC對象,并將OC文件轉化為C++文件來探尋OC對象的本質,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對象實際需要的內存大小: %zd", class_getInstanceSize([object class]));//objc對象實際需要的內存大小: 8
NSLog(@"objc對象實際分配的內存大小: %zd", malloc_size((__bridge const void *)(object)));//objc對象實際分配的內存大小: 16
}
return 0;
}
使用 clang 將 OC 代碼轉換為 C++ 代碼
clang -rewrite-objc main.m -o main.cpp
或者使用 XCode 工具 xcrun 進行轉換
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
打開main.cpp定位到main函數
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
//簡化版本
NSObject *object = objc_msgSend(objc_msgSend(objc_getClass("NSObject"),sel_registerName("alloc")),sel_registerName("init"))
}
return 0;
}
這里可以看到,objc_msgSend先向NSObject發(fā)送了alloc,然后發(fā)送了init消息;此時對象就被初始化完畢,下面我們打印一下內存占用情況,代碼如下
NSLog(@"objc對象實際需要的內存大小: %zd", class_getInstanceSize([object class]));//打印結果:8
NSLog(@"objc對象實際分配的內存大小: %zd", malloc_size((__bridge const void *)(object)));//打印結果:16
是不是很疑惑,對象僅僅使用了8字節(jié)為什么會分配16個字節(jié)的大小呢?對于這個問題我們可以通過閱讀objc4的源代碼來找到答案。通過查看跟蹤obj4中alloc和allocWithZone兩個函數的實現,會發(fā)現這個連個函數都會調用一個instanceSize的函數:
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;
}
是不是豁然開朗,對象的內存大小最低消費就是16字節(jié)。
下面我們看看對象中有成員變量時,對象的內存占用變化
@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對象實際需要的內存大小: %zd", class_getInstanceSize([object class]));//打印結果:16
NSLog(@"objc對象實際分配的內存大小: %zd", malloc_size((__bridge const void *)(object)));//打印結果:16
}
return 0;
}
此時對象的實際內存大小和分配內存大小都是16字節(jié),我們知道int類型是占4個字節(jié),而8+4=12,這應該才是實際內存占用的大小,為什么是16字節(jié)呢?
這里就需要引入內存地址對齊的問題,地址對齊意思就是內存的增加必須滿足是8字節(jié)的倍數,所以12字節(jié)不滿16,則分配16字節(jié),其中增加了4字節(jié)的占位
有了這個概念后,如果我為Person對象再增加一個int類型number的屬性,實際內存大小是否會改變呢?下面我們看一下打印結果:
@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對象實際需要的內存大小: %zd", class_getInstanceSize([object class]));//打印結果:16
NSLog(@"objc對象實際分配的內存大小: %zd", malloc_size((__bridge const void *)(object)));//打印結果:16
}
return 0;
}
可以看見打印結果都是16,說明內存大小并無增加,此時number的內存剛好填補了之前的4字節(jié)占位內存,不會造成內存的浪費
上篇文章先到此,感謝大家的閱讀!