WWDC 2020視頻中有詳細介紹html
在磁盤上,App
二進制文件中類如圖中MYClass
同樣,包含指向元類、超類、方法緩存的指針以及指向存儲額外信息的指針class_ro_t
。ro
表明只讀,它包括像類的名稱、方法、協議、實例變量的信息,Swift
和Objective-C
共享這一數據結構。c++
Clean Memory
是指加載後不會發生更改的內存,class_ro_t
就是Clean Memory
,它是隻讀的。Dirty Memory
是指在進程運行時發生更改的內存,類結構一經使用就會變成Dirty Memory
,由於運行時會向它寫入新的數據,例如建立一個新的方法緩存並從類中指向它。Dirty Memory
比Clean Memory
要昂貴得多,只要進程在運行,它就必須一直存在。Clean Memory
能夠進行移除從而節省更多的內存空間,由於若是你須要Clean Memory
系統能夠從磁盤中從新加載。MacOS
能夠選擇換出Dirty Memory
但由於iOS
不使用swap
因此Dirty Memory
在iOS
中的代價很大。當一個類首次被使用,運行時會爲他分配額外的存儲容量,這個運行時分配的存儲容量是class_rw_t
用於讀取-編寫數據。 例如全部的類都會連接生成一個樹狀結構,這是經過使用First Subclass
和Next Sibling Class
指針實現的,這容許運行時遍歷當前使用的全部類,這對於使方法緩存無效很是有用。面試
爲何方法和屬性在只讀數據class_ro_t
中,class_rw_t
還要方法和屬性呢?express
由於它們能夠在運行時更改。當Category
被加載時 他能夠向類中添加新的方法,由於class_ro_t
是隻讀的,咱們須要在class_rw_t
中追蹤這些東西。緩存
可是class_rw_t
佔用的是Dirty Memory
,這樣作的話會佔用至關多的內存。如何縮小這些結構呢?markdown
能夠拆掉那些平時不用的部分,這樣class_rw_t
就減小了一半。 對於那些確實須要額外信息的類,咱們能夠分配這些擴展記錄中的一個,並把它劃到類中供其使用。大約90%的類歷來不須要這些擴展數據。數據結構
注:只有Swift
類會使用這個Demangled Name
字段,而且Swift
類並不須要這一字段,除非有東西詢問他們的Objective-C
名稱時才須要。app
@interface ABPerson : NSObject
{
//成員變量
NSString *hobby;
//實例變量(特殊的成員變量)
NSObject *objc;
}
@property (nonatomic,copy) NSString *nickName;
@property (nonatomic,strong) NSString *name;
@property (atomic,strong) NSString *aname;
@end
@implementation ABPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
ABPerson *p = [ABPerson alloc];
NSLog(@"Hello, World!");
}
return 0;
}
複製代碼
編譯成.cpp
文件命令:ide
clang -rewrite-objc main.m -o main.cpp
複製代碼
ABPerson
結構體oop
struct ABPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS; //ISA
NSString *hobby;
NSObject *objc;
NSString *_nickName;
NSString *_name;
NSString *_aname;
};
複製代碼
在結構體中,屬性前面被加上了下劃線,成員變量沒有變。
nickName
的setter
方法:
static void _I_ABPerson_setNickName_(ABPerson * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct ABPerson, _nickName), (id)nickName, 0, 1); }
複製代碼
name
的setter
方法:
static void _I_ABPerson_setName_(ABPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_ABPerson$_name)) = name; }
複製代碼
對比兩個屬性的setter
方法,nickName
的setter
方法會調用objc_setProperty
,而name
的setter
是直接經過內存平移賦值。
objc_setProperty
至關於一個封裝,將尋找屬性的代碼封裝起來,再調用具體的底層實現。
那麼當調用setter
方法爲何會定位到objc_setProperty
呢?這多是在編譯階段就完成的工做。
打開LLVM
工程搜索objc_setProperty
往上依次找方法的調用 查看GetSetPropertyFn
的實現 返回了setPropertyFn
在這裏被調用,那麼調用的條件是什麼呢?
void CodeGenFunction::generateObjCSetterBody(const ObjCImplementationDecl *classImpl, const ObjCPropertyImplDecl *propImpl, llvm::Constant *AtomicHelperFn) {
省略部分代碼
PropertyImplStrategy strategy(CGM, propImpl);//獲取策略
switch (strategy.getKind()) {
case PropertyImplStrategy::Native: {
// We don't need to do anything for a zero-size struct.
if (strategy.getIvarSize().isZero())
return;
//下面這一段是在作內存平移
Address argAddr = GetAddrOfLocalVar(*setterMethod->param_begin());
LValue ivarLValue =
EmitLValueForIvar(TypeOfSelfObject(), LoadObjCSelf(), ivar, /*quals*/ 0);
Address ivarAddr = ivarLValue.getAddress(*this);
// Currently, all atomic accesses have to be through integer
// types, so there's no point in trying to pick a prettier type.
llvm::Type *bitcastType =
llvm::Type::getIntNTy(getLLVMContext(),
getContext().toBits(strategy.getIvarSize()));
// Cast both arguments to the chosen operation type.
argAddr = Builder.CreateElementBitCast(argAddr, bitcastType);
ivarAddr = Builder.CreateElementBitCast(ivarAddr, bitcastType);
// This bitcast load is likely to cause some nasty IR.
llvm::Value *load = Builder.CreateLoad(argAddr);
// Perform an atomic store. There are no memory ordering requirements.
llvm::StoreInst *store = Builder.CreateStore(load, ivarAddr);
store->setAtomic(llvm::AtomicOrdering::Unordered);
return;
}
case PropertyImplStrategy::GetSetProperty:
//省略不少個case
}
複製代碼
條件是找到匹配的策略調用相應的case
enum StrategyKind {
/// The 'native' strategy is to use the architecture's provided
/// reads and writes.
Native,
/// Use objc_setProperty and objc_getProperty.
GetSetProperty,
/// Use objc_setProperty for the setter, but use expression
/// evaluation for the getter.
SetPropertyAndExpressionGet,
/// Use objc_copyStruct.
CopyStruct,
/// The 'expression' strategy is to emit normal assignment or
/// lvalue-to-rvalue expressions.
Expression
};
複製代碼
PropertyImplStrategy::PropertyImplStrategy(CodeGenModule &CGM,
const ObjCPropertyImplDecl *propImpl) {
IsCopy = (setterKind == ObjCPropertyDecl::Copy);
IsAtomic = prop->isAtomic();
HasStrong = false; // doesn't matter here.
// Evaluate the ivar's size and alignment.
ObjCIvarDecl *ivar = propImpl->getPropertyIvarDecl();
QualType ivarType = ivar->getType();
auto TInfo = CGM.getContext().getTypeInfoInChars(ivarType);
IvarSize = TInfo.Width;
IvarAlignment = TInfo.Align;
//當是copy是使用GetSetProperty策略
if (IsCopy) {
Kind = GetSetProperty;
return;
}
省略部分代碼
複製代碼
當修飾屬性爲copy
時會調用objc_setProperty
在main.cpp
文件中,咱們看到了:
以@16@0:8
爲例,符號依次介紹: @
:是id
類型,是返回值類型 16
:這個編碼所佔用內存數 @
:方法參數,指方法的默認參數是id self
0
:表明self
從0
號位置開始 :
:表明SEL
8
:表明SEL
從8
號位置開始
不清楚對應符號什麼意思能夠查詢Objective-C type encodings:
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];
NSLog(@"\n re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];
NSLog(@"\n re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
複製代碼
isKindOfClass
會調用objc_opt_isKindOfClass
//x爲真得可能性比較大
#define fastpath(x) (__builtin_expect(bool(x), 1))
//x爲假的可能性比較大
#define slowpath(x) (__builtin_expect(bool(x), 0))
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
//objc2.0使用下面實現
#if __OBJC2__
if (slowpath(!obj)) return NO;
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
for (Class tcls = cls; tcls; tcls = tcls->getSuperclass()) {
if (tcls == otherClass) return YES;
}
return NO;
}
#endif
//不知足調整,調用isKindOfClass
return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
複製代碼
分析:re1
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
obj是NSObject的類對象
cls是NSObject的元類,即根元類
tcls根元類的父類就是NSObject
otherClass是NSObject
因此 tcls == otherClass 爲YES
複製代碼
分析:re3
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];
obj是LGPerson的類對象
cls是LGPerson的元類
tcls是元類的父類就是NSObject的元類,即根元類
otherClass是LGPerson
因此 tcls == otherClass 爲NO
複製代碼
分析:re5
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
obj是NSObject的實例對象
cls是NSObject的類對象
tcls=cls即NSObject的類對象
otherClass是NSObject
因此 tcls == otherClass 爲YES
複製代碼
分析:re7
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];
obj是LGPerson的實例對象
cls是LGPerson的類對象
tcls=cls即LGPerson的類對象
otherClass是LGPerson
因此 tcls == otherClass 爲YES
複製代碼
分析:re二、 re4
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
複製代碼
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
NSObject的元類是根元類和NSObject不是一個東西,返回NO
同理:
re4也是NO
複製代碼
分析:re6
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
NSObject實例對象的類對象就是NSObject
因此re6爲YES
複製代碼
分析:re8
re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];
LGPerson實例對象的類對象就是LGPerson
因此re8爲YES
複製代碼
打印結果:
class_rw_t
,用於讀取編寫數據,經過拆分class_rw_t
,將不經常使用的放到class_rw_ext_t
,當須要的時候加進去,從而達到減小內存的做用setter
方法有兩種方式,一種是經過內存平移賦值,一種是經過調用objc_setProperty
copy
時會調用objc_setProperty