我们平时写的Objective-C代码,底层实现都是c/c++代码,所以Objective-C的面向对象都是基于c/c++数据结构实现的
可以用命令行把oc代码转成c/c++代码
clang -rewrite-objc main.m -o main.cpp
建一个mac命令行项目, 编译器重写oc main.m文件 输出 main.cpp文件
上面这个没有写明架构,应该输出iphone arm64
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
在使用clang转换OC为C++代码时,可能会遇到以下问题
cannot create __weak reference in file using manual reference
解决方案:支持ARC、指定运行时系统版本,比如
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
一: 类的结构
苹果源码地址 https://opensource.apple.com/tarballs/
源码文件版本数字越大 越新
1 对象的isa指针指向哪里?
instance对象的isa指向class对象
class对象的isa指向meta-class对象
meta-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找父类
OC的类信息存放在哪里?
对象方法、属性、成员变量、协议信息,存放在class对象中
类方法,存放在meta-class对象中
成员变量的具体值,存放在instance对象
//instance对象,实例对象
NSObject *object1 = [[NSObject alloc] init];
// class对象,类对象
// class方法返回的一直是class对象,类对象
Class objectClass1 = [object1 class];
Class objectClass3 = object_getClass(object1);
Class objectClass5 = [NSObject class];
// meta-class对象,元类对象
// 将类对象当做参数传入,获得元类对象
Class objectMetaClass = object_getClass(objectClass5);
NSLog(@"objectMetaClass - %p %d", objectMetaClass, class_isMetaClass(objectMetaClass));
类的结构
struct NSObject_IMPL {
Class isa;
};
typedef struct objc_class *Class;
//在runtime源码里面搜索下面
struct objc_class :
struct objc_object {
struct class_rw_t {
二: KVO
调用set方法里面
Foundation
_NSSetIntValueAndNotify
三: KVC
KVC的全称是Key-Value Coding,俗称“键值编码”,可以通过一个key来访问某个属性
常见的API有
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key;
四: 分类
源码搜索 struct category_t {
通过Runtime加载某个类的所有Category数据
把所有Category的方法、属性、协议数据,合并到一个大数组中
后面参与编译的Category数据,会在数组的前面将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面
源码解读顺序
objc-os.mm
_objc_init
map_images
map_images_nolock
objc-runtime-new.mm
_read_images
remethodizeClass
attachCategories
attachLists
realloc、memmove、 memcpy
void attachLists(List* const * addedLists, uint32_t addedCount)
objc-os.mm objc_init
void attachLists(List* const * addedLists, uint32_t addedCount)
//分类附加方法
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
+load方法会在runtime加载类、分类时调用
每个类、分类的+load,在程序运行过程中只调用一次
实现关联对象技术的核心对象有
AssociationsManager
AssociationsHashMap
ObjectAssociationMap
ObjcAssociation
源码搜索
objc_setA
objc4源码解读:objc-references.mm
五: +load方法
调用顺序
先调用类的+load
按照编译先后顺序调用(先编译,先调用)
调用子类的+load之前会先调用父类的+load
再调用分类的+load
按照编译先后顺序调用(先编译,先调用)
六: +initialize方法
+initialize方法会在类第一次接收到消息时调用
调用顺序
先调用父类的+initialize,再调用子类的+initialize
(先初始化父类,再初始化子类,每个类只会初始化1次)
+initialize和+load的很大区别是,+initialize是通过objc_msgSend进行调用的,所以有以下特点
如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
如果分类实现了+initialize,就覆盖类本身的+initialize调用
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方法)
七: block的本质
block本质上也是一个OC对象,它内部也有个isa指针
block是封装了函数调用以及函数调用环境的OC对象
为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
NSGlobalBlock ( _NSConcreteGlobalBlock )
NSStackBlock ( _NSConcreteStackBlock )
NSMallocBlock ( _NSConcreteMallocBlock )
// 堆:动态分配内存,需要程序员申请申请,也需要程序员自己管理内存
void (^block1)(void) = ^{
NSLog(@"Hello");
};
int age = 10;
void (^block2)(void) = ^{
NSLog(@"Hello - %d", age);
};
NSLog(@"%@ %@ %@", [block1 class], [block2 class], [^{
NSLog(@"%d", age);
} class]);
NSGlobalBlock NSMallocBlock NSStackBlock
block的copy
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况
block作为函数返回值时
将block赋值给__strong指针时
block作为Cocoa API中方法名含有usingBlock的方法参数时 :动画 排序
block作为GCD API的方法参数时: 延时
auto 修饰的变量 就是会自动释放的局部变量
static int * 一直在内存中,block访问的指针
全局变量: block直接访变量
MRC下block属性的建议写法
@property (copy, nonatomic) void (^block)(void);
栈空间block不能持有局部变量,堆空间block可以持有局部变量
ARC下block属性的建议写法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
当block内部访问了对象类型的auto变量时
如果block是在栈上,将不会对auto变量产生强引用
如果block被拷贝到堆上
会调用block内部的copy函数
copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
如果block从堆上移除
会调用block内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放引用的auto变量(release)
当block从堆中移除时
会调用block内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放引用的__block变量(release)
当block在栈上时,对它们都不会产生强引用
当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变量在栈上时,不会对指向的对象产生强引用
当__block变量被copy到堆时
会调用__block变量内部的copy函数
copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain)
如果__block变量从堆上移除
会调用__block变量内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放指向的对象(release)