系统分配了16个字节给NSObject对象(通过malloc_size函数获得)
但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得)
#import
#import
NSObject * p = [[NSObject alloc] init];
NSLog(@" %zd", class_getInstanceSize([p class]));
NSLog(@" %zd", malloc_size((__bridge const void *)(p)));
源码下载地址:
https://opensource.apple.com/source/
class_getInstanceSize是获取类实例对象的大小
在objc源码中查找class_getInstanceSize的实现
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
将Objective-C代码转换为C\C++代码,查看底层实现
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件
如果需要链接其他框架,使用-framework参数。比如-framework UIKit
如果需要支持arc和runtime,使用-fobjc-arc和-fobjc-runtime=ios-8.0.0参数
NSObject对象的底层实现
struct NSObject_IMPL {
Class isa;
};
一个对象继承与NSObject
@interface TestObject : NSObject
{
int _age;
double _price;
}
@end
查看转换成底层C++代码的实现
struct TestObject_IMPL {
struct NSObject_IMPL NSObject_IVARS; // 等价于 Class isa;
int _age;
double _price;
};
instance对象的isa指向class对象
class对象的isa指向meta-class对象
meta-class对象的isa指向基类的meta-class对象
TestObject * p = [[TestObject alloc] init];
获取一个实例对象的类对象
[p class]
获取一个实例对象的元类对象
id metaClass = object_getClass([p class]);
判断是否是元类对象
NSLog(@" %d" , class_isMetaClass(metaClass));
NSLog(@" %d" , class_isMetaClass([p class]));
每个类在内存中有且只有一个class对象
objectMetaClass是NSObject的meta-class对象(元类对象)
每个类在内存中有且只有一个meta-class对象
meta-class对象和class对象的内存结构是一样的,但是用途不一样
instance的isa指向class
当调用对象方法时,通过instance的isa找到class,最后找到对象方法的实现进行调用
class的isa指向meta-class
当调用类方法时,通过class的isa找到meta-class,最后找到类方法的实现进行调用
总结:
instance的isa指向class
class的isa指向meta-class
meta-class的isa指向基类的meta-class
class的superclass指向父类的class,如果没有父类,superclass指针为nil
meta-class的superclass指向父类的meta-class,基类的meta-class的superclass指向基类的class
instance调用对象方法的轨迹,isa找到class,方法不存在,就通过superclass找父类
class调用类方法的轨迹,isa找meta-class,方法不存在,就通过superclass找父类
对象方法、属性、成员变量、协议信息,存放在class对象中
类方法,存放在meta-class对象中
成员变量的具体值,存放在instance对象中
查看objc源码查看类底层实现代码
在objc-runtime-new.h中
struct objc_class : objc_object {
Class superclass;
cache_t cache;
class_data_bits_t bits;
class_rw_t *data() {}
}
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
}
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
method_list_t *baseMethods() const {
return baseMethodList;
}
};
利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类
当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数
_NSSetXXXValueAndNotify函数中的实现原理:
willChangeValueForKey:
父类原来的setter
didChangeValueForKey:
内部会触发监听器(Oberser)的监听方法(observeValueForKeyPath:ofObject:change:context:)
如何查看重写的set方法的内部实现呢?
利用Runtime方法获取添加KVO之后的setKey方法的实现
NSLog(@"添加KVO监听之后 - %p", [object methodForSelector:@selector(setAge:)]);
利用lldb打印该地址的实现
(lldb) po (IMP)0x10e33dcf2
(Foundation`_NSSetIntValueAndNotify)
如何窥探Foundation框架中的_NSSetIntValueAndNotify方法的内部实现呢?
1、利用逆向知识在越狱手机找到动态库共享缓存/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64
2、拷贝到电脑利用dsc_extractor将Foundation框架拆分出来
./dsc_extractor dyld_shared_cache_arm64 ./frameworks
3、利用hopper或者IDA查看_NSSetIntValueAndNotify汇编实现
手动调用willChangeValueForKey:和didChangeValueForKey:
不会触发KVO
会触发KVO
- (void)setValue: forKey:的赋值过程
首先按照setkey、_setkey的顺序寻找该方法为key赋值,如果没有找到两个方法,则会查看+ (BOOL)accessInstanceVariablesDirectly方法返回值,如果返回NO,则会抛出NSUnknownKeyException异常,如果返回YES,则按照_key、_isKey、key、isKey的顺序寻找成员变量的顺序赋值,如果都没有,则会抛出NSUnknownKeyException异常
验证setkey、_setkey方法
object中是没有name属性的
[object setValue:@"zhangsan" forKey:@"name"];
实现两个方法验证,然后屏蔽改方法
- (void)setName:(NSString *)name {
NSLog(@"setName");
}
- (void)_setName:(NSString *)name {
NSLog(@"_setName");
}
验证_key、_isKey、key、isKey的顺序
依次屏蔽属性打断点验证
+ (BOOL)accessInstanceVariablesDirectly {
return YES;
}
@interface TestObject : NSObject
{
NSString * _name; // 1
NSString * name; // 3
NSString * _isName; // 2
NSString * isName; // 4
}
@end
- (id)valueForKey:取值过程
首先按照getKey、key、isKey、_key的顺序查找方法获取返回值,如果都没有,则会获取accessInstanceVariablesDirectly方法的返回值,返回NO,抛出NSUnknownKeyException异常,如果返回YES、会按照_key、_isKey、key、isKey的顺序获取属性的值,如果都没有找到则会抛出NSUnknownKeyException异常
Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息
在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中),后参与编译的Category数据会在前面。
将类目文件转换成C++查看类目编译结构
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc TestObject+CategoryOne.m -o testobject_category.cpp
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
objc中的源码结构
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;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
objc关于objc的源码解读顺序
objc-os.mm
1、_objc_init
2、map_images
3、map_images_nolock
objc-runtime-new.mm
1、_read_images
2、remethodizeClass
3、attachCategories
4、attachLists
5、realloc、memmove、 memcpy
Class Extension在编译的时候,它的数据就已经包含在类信息中
Category是在运行时,才会将数据合并到类信息中
有load方法
load方法在runtime加载类、分类的时候调用
load方法可以继承,但是一般情况下不会主动去调用load方法,都是让系统自动调用
每个类、分类的+load,在程序运行过程中只调用一次
调用顺序:
1、先调用类的+load
按照编译先后顺序调用(先编译,先调用)
调用子类的+load之前会先调用父类的+load
2、再调用分类的+load
按照编译先后顺序调用(先编译,先调用)
objc4源码解读过程:objc-os.mm
_objc_init
load_images
1. prepare_load_methods
2. schedule_class_load
3. add_class_to_loadable_list
4. add_category_to_loadable_list
call_load_methods
1. call_class_loads
2. call_category_loads
3. (*load_method)(cls, SEL_load)
+load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用
load、initialize方法的区别什么?
1.调用方式
1> load是根据函数地址直接调用
2> initialize是通过objc_msgSend调用
2.调用时刻
1> load是runtime加载类、分类的时候调用(只会调用1次)
2> initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
load、initialize的调用顺序?
1.load
1> 先调用类的load
a) 先编译的类,优先调用load
b) 调用子类的load之前,会先调用父类的load
2> 再调用分类的load
a) 先编译的分类,优先调用load
2.initialize
1> 先初始化父类
2> 再初始化子类(可能最终调用的是父类的initialize方法)
objc4源码解读过程:
objc-msg-arm64.s
1. objc_msgSend
objc-runtime-new.mm
1. class_getInstanceMethod
2. lookUpImpOrNil
3. lookUpImpOrForward
4. _class_initialize
5. callInitialize
6. objc_msgSend(cls, SEL_initialize)
不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果。
默认情况下,因为分类底层结构的限制,不能添加成员变量到分类中。但可以通过关联对象来间接实现。
添加关联对象
void objc_setAssociatedObject(id object, const void * key,id value, objc_AssociationPolicy policy)
获得关联对象
id objc_getAssociatedObject(id object, const void * key)
移除所有的关联对象
void objc_removeAssociatedObjects(id object)
key的常见用法
1. static void *MyKey = &MyKey;
2. static char MyKey;
3. 使用属性名作为key
4. 使用get方法的@selecor作为key
原理:
核心对象
AssociationsManager
AssociationsHashMap
ObjectAssociationMap
ObjcAssociation
objc4源码解读:objc-references.mm
void objc_setAssociatedObject(id object, const void * key,id value, objc_AssociationPolicy policy)
class AssociationsManager {
// associative references: object pointer -> PtrPtrHashMap.
static AssociationsHashMap *_map;
};
class AssociationsHashMap : public unordered_map {}
class ObjectAssociationMap : public std::map {}
class ObjcAssociation {
uintptr_t _policy;
id _value;
}
关联对象并不是存储在被关联对象本身内存中
关联对象存储在全局的统一的一个AssociationsManager中
设置关联对象为nil,就相当于是移除关联对象
封装了函数调用以及调用环境的OC对象
利用clang查看Block编译成C++后的代码结构
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block)(void) = ^(void){
NSLog(@"block");
};
block();
}
return 0;
}
C++结构代码:
int main(int argc, const char * argv[]) {
{
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_t7_fvvtxpcs1ysg4pb96h77scv40000gn_T_main_70e44b_mi_0);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
auto变量就是普通的局部变量
block的3种类型
__NSGlobalBlock__ ( _NSConcreteGlobalBlock )
__NSStackBlock__ ( _NSConcreteStackBlock )
__NSMallocBlock__ ( _NSConcreteMallocBlock )
MRC模式下
int a = 5;
void(^block)(void) = ^(void){
NSLog(@"block a = %d", a);
};
static int b = 5;
void(^block1)(void) = ^(void){
NSLog(@"block b = %d", b);
};
NSLog(@"%@", block);
NSLog(@"%@", block1);
NSLog(@"%@", [block copy]);
block = <__NSStackBlock__: 0x7ffeefbff558>
block1 = <__NSGlobalBlock__: 0x100001078>
[block copy] = <__NSMallocBlock__: 0x10078d190>
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况
block访问了对象类型的局部变量,编译成C++的代码中增加了内存管理的代码
block = ^(void){
NSLog(@"name = %@", object.name);
};
C++代码:
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->object, (void*)src->object, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->object, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
当block内部访问了对象类型的auto变量时
如果block是在栈上,将不会对auto变量产生强引用
如果block被拷贝到堆上
如果block从堆上移除
编译成C++代码分析:
__block int a = 5;
block = ^(void){
a = 10;
NSLog(@"a = %d", a);
};
在使用__blcok修饰变量之后,实际上a变成了一个对象。
C++代码分析:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
在访问a的时候(a->__forwarding->a) = 10;
对象类型的局部变量(auto)
当block在栈上时,对它们都不会产生强引用
对象类型的编译源码查看:
普通的对象类型
block = ^(void){
NSLog(@"name = %@", object);
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
TestObject *__strong object;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, TestObject *__strong _object, int flags=0) : object(_object) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
使用__weak修饰的对象类型
__weak typeof(object) weakObject = object;
TestBlock block;
block = ^(void){
NSLog(@"name = %@", weakObject);
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
TestObject *__weak weakObject;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, TestObject *__weak _weakObject, int flags=0) : weakObject(_weakObject) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
使用__block修饰的对象类型
__block typeof(object) strongObject = object;
TestBlock block;
block = ^(void){
NSLog(@"name = %@", strongObject);
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_strongObject_0 *strongObject; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_strongObject_0 *_strongObject, int flags=0) : strongObject(_strongObject->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
当block拷贝到堆上时,都会通过copy函数来处理它们
__block变量(假设变量名叫做a)
_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
对象类型的auto变量(假设变量名叫做p)
_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
当block从堆上移除时,都会通过dispose函数来释放它们
__block变量(假设变量名叫做a)
_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
对象类型的auto变量(假设变量名叫做p)
_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
block的属性修饰词为什么是copy?使用block有哪些使用注意?
block一旦没有进行copy操作,就不会在堆上
使用注意:循环引用问题
block在修改NSMutableArray,需不需要添加__block?
不需要,block内部操作的是NSMutableArray的指针,并不是进行修改。