一、OC本质
模拟器(i386)、32bit(armv7)、64bit(arm64)
-
查看C语言实现的指令
- 不包含
__weak
关键字
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
- 包含
__weak
关键字
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios8.0.0 main.m -o main.cpp
- 不包含
-
常用的LLDB指令
iOS的内存显示是小端模式,也就是读取内存值是从高地址到低地址
二、OC对象的分类:instance对象(实例对象)、class对象(类对象)、meta-class对象(元类对象)
-
instance对象
- 通过类
alloc
出来的对象,每次调用alloc
都会产生新的instance对象 - instance对象在内存中存储的信息包括:isa指针、其他成员变量的值
- 通过类
-
class对象
- 获取方法
- 每个类在内存中有且只有一个class对象
-
class对象在内存中存储的信息包括:isa指针、superclass指针、类的属性信息(@property)、类的对象方法信息(instance method)、类的协议信息(protocol)、类的成员变量信息(ivar的类型和名称)
- 获取方法
-
meta-class对象
- 获取方法
- 每个类在内存中有且只有一个meta-class对象
- meta-class对象和class对象的内存结构是一样的,但是用途不一样,在内存中主要包括:isa指针、superclass指针、类的类方法信息(class method)
- 查看是否为meta-class对象
BOOL class_isMetaClass(Class cls);
- 补充
-
Class objc_getClass(const char *aClassName)
* 传入字符串类名
* 返回对应的类对象
-
Class object_getClass(id obj)
* 传入的obj可能是instance对象、class对象、meta-class对象
* 返回值
* 如果是instance对象,返回class对象
* 如果是class对象,返回meta-class对象
* 如果是meta-class对象,返回NSObject(基类)的meta-class对象
-
- (Class)class、+ (Class)class
* 返回的就是类对象
- 获取方法
-
isa总结
- instance的isa指向class
- class的isa指向meta-class
- meta-class的isa指向基类的meta-class
- 基类meta-class的isa指向自己
- class的superclass指向父类class
- 如果没有父类,superclass为nil
- meta-class的superclass指向父类meta-class
- 基类meta-class的superclass指向元类class
-
isa指针指向
- 从64bit开始,isa需要进行一次位运算,才能计算出真实地址
- class、meta-class对象的本质结构都是struct objc_class
-
struct objc_class 的内部结构图
三、KVO
- 全称:Key-Value Observing,俗称“键值监听”,可用于监听某个对象属性值的改变。
- 未使用KVO监听的对象
- 使用了KVO监听的对象
- 补充:重写的
-class
方法内部直接返回了它父类的类对象,属于指针混淆,隐藏了NSKVONotifying_ClassName的存在。
- 补充:重写的
- KVO实现的过程
当添加监听方法以后,被监听对象的isa指针会指向一个由Runtime动态生成的类:NSKVONotifying_ClassName,它是被监听类的子类;当被监听属性发生变化时,新生成子类的属性setter
方法中会调用Foundation框架里的函数_NSSet*ValueAndNotify
,在该函数中会依次调用-willChangeValueForKey:
,父类的属性setter
方法,-didChangeValueForKey:
,在-didChangeValueForKey:
中会调用-observeValueForKeyPath:ofObject:change:context:
,完成对属性的监听。
四、KVC
全称:Key-Value Coding,俗称“键值编码”,可以通过一个key来访问某个属性。
-
常见API
- (void)setValue:(id)value forKey:(NSString *)key
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath
- (id)valueForKey:(NSString *)key
- (id)valueForKeyPath:(NSString *)keyPath
-
+ (BOOL)accessInstanceVariablesDirectly
,默认YES
-
setValue:forKey原理
-
valueForKey:原理
对于被KVO监听的属性,使用KVC赋值,会触发监听事件,因为KVC内部调用了
-willChangeValueForKey:
和-didChangeValueForKey:
五、Category
- category在runtime中的结构(定义在objc-runtime-new.h中)
- category的加载处理过程
- 通过runtime加载某个类的所有category数据
- 把所有category的方法、属性、协议数据,合并到一个大数组中
- 后参与编译的category数据,会放在数组的前边
- 将合并后的分类数据(方法、属性、协议),插入到类原来数据前面
六、+load方法
- +load方法会在runtime加载类、分类的时候调用
- 每个类、分类的+load,在程序运行过程中只调用一次
- 调用顺序
- 先调用类的+load
- 按照编译先后顺序调用(先编译,先调用)
- 调用子类的+load方法之前会先调用父类的+load(但是每个类只调用一次)
- 再调用分类的+load
- 按照编译先后顺序调用(先编译,先调用)
- 先调用类的+load
七、+initialize方法
- 调用时间:+initialize会在类第一次接受消息时调用
- 调用顺序:先调用父类的+initialize,再调用子类的+initialize(先初始化父类,再初始化子类,每个类只初始化一次)
- +initialize是通过objc_msgSend方式进行调用
- 如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
- 如果category实现了+initialize,则会覆盖类本身的+initialize
八、关联对象
因为category的底层结构限制,不能直接在category添加成员变量。
-
category添加属性后,不会自动生成成员变量和
setter
,getter
方法。尝试的方案及不足:- category添加全局变量,手动生成
setter
和getter
。问题:不同的实例对象访问到的属性值是相同的。 - category添加全局字典,使用
self
的地址作为key来存取属性值。问题:存在线程问题;每添加一个属性,就需要重新增加一个全局字典,比较麻烦。
- category添加全局变量,手动生成
-
关联对象API
- 添加关联对象
void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nonnull value, objc_AssociationPolicy policy)
- 获得关联对象
id _Nullable objc_getAssociatedObject(id _Nonnull object, const void * key)
- 移除所有的关联对象
id objc_removeAssociatedObjects(id _Nonnull object)
- 添加关联对象
-
设置关联对象key的常见用法
static void *MyKey = &MyKey;
objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC) objc_getAssociatedObject(obj, Mykey)
static char MyKey;
objc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC) objc_getAssociatedObject(obj, &Mykey)
使用属性名作为key
objc_setAssociatedObject(obj, @"property", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC) objc_getAssociatedObject(obj, @"property")
- 使用
getter
方法的@selector作为key(推荐写法)
objc_setAssociatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC) objc_getAssociatedObject(obj, @selector(getter))
-
关联策略
-
关联对象原理
-
实现关联对象技术的核心对象有
- AssociationsManager
- AssociationsHashMap
- ObjectAssociationMap
- ObjcAssociation
-
源码解读
-
关联对象图解
void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nonnull value, objc_AssociationPolicy policy)
- 关联对象并不是存储在被关联对象本身内存中
- 关联对象统一存储在一个全局的AssociationsManager中
- 设置关联对象的值为nil,就会移除该关联对象
- 移除某个对象的所有关联对象,使用
id objc_removeAssociatedObjects(id _Nonnull object)
-
九、block
本质:是一个oc对象,内部也有一个isa指针。它是封装了函数调用以及函数调用环境的oc对象。
-
block的变量捕获机制(capture)
- block会捕获auto变量的值,是因为auto变量会随时释放,如果block访问一个已经释放的变量,就会出现问题。
- auto不能修饰全局变量
- static局部变量在函数执行完后,不会释放,所以block捕获的是它的地址
- 技巧:判断block会不会捕获变量,只需看下这个变量是否为局部变量,是则捕获,不是就不捕获
-
捕获auto变量后的结构图
-
block有三种类型,都是继承自NSBlock类型
__NSGlobalBlock__(_NSConcreteGlobalBlock)
__NSMallockBlock__(_NSConcreteMallockBlock)
__NSStackBlock__(_NSConcreteStackBlock)
- 从程序区域到栈区,地址从小到大
- 程序区域:存放程序代码
- 数据区域:存放全局变量
- 堆区:动态分配的内存,需要程序员手动分配和释放
- 栈区:存放局部变量
- 技巧:要查看某个东西存放在什么区域,可以分别打印下已知区域的地址,然后打印要查看东西的地址,大概比较下跟哪个比较接近,一般就属于哪个区域。
-
不同环境下block的类型(查看的话需要在MRC环境)
block类型 环境 __NSGlobalBlock
没有访问auto变量 __NSStackBlock
访问了auto变量 __NSMallocBlock
__NSStackBlock
调用了copy
-
不同block调用copy后结果
block的类 副本源的配置存储域 复制效果 _NSConcreteStackBlock 栈 从栈复制到堆 _NSConcreteGlobalBlock 程序的数据区域 什么也不做 _NSConcreteMallocBlock 堆 引用计数增加 -
ARC环境下,以下情况,block会自动进行copy操作
- block作为函数返回值
- 将block赋值给__strong指针时
- block作为Cocoa API中方法名含有usingBlock方法的参数时
- block作为GCD API方法的参数时
-
捕获对象类型的auto变量
- 如果block是在栈上,将不会对auto变量进行强引用
- 如果block被拷贝到堆上
- 会调用block内部的
copy
函数 -
copy
函数内部会调用_Block_object_assign
函数 -
_Block_object_assign
函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained
)做出相应的操作,形成强引用(retain
)或弱引用
- 会调用block内部的
- 如果block从堆上移除
- 会调用block内部的
dispose
函数 -
dispose
函数内部会调用_Block_object_dispose
函数 -
_Block_object_dispose
函数会自动释放auto变量(release
)
- 会调用block内部的
函数 调用时机 copy
函数栈上的block复制到堆上时 dispose
函数堆上的block被废弃时 在使用clang转换oc为c++代码时,使用以下命令:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios 8.0.0 main.m
-
在block内部修改变量的方法
- 使用static变量(缺点:会一直存在内存中)
- 使用__block变量
-
__block修饰符
- 不能修饰全局变量和静态变量(static)
- 编译器会将__block变量包装成一个对象
-
__block变量的内存管理
当block在栈上时,不会对__block变量产生强引用
-
当block被拷贝到堆上时
- 会调用block内部的
copy
函数 -
copy
函数内部会调用_Block_object_assign
函数 -
_Block_object_assign
函数会对__block变量产生强引用(retain)
- 会调用block内部的
-
当block从堆上移除时
- 会调用block内部的
dispose
函数 -
dispose
函数内部会调用_Block_object_dispose
函数 -
_Block_object_dispose
会自动释放引用的__block变量(release)
- 会调用block内部的
-
__block的forwarding指针
- 目的就是要确保指向的是堆上的内存
- 目的就是要确保指向的是堆上的内存
-
对象类型的auto变量和__block普通变量的比较
当block在栈上时,都不会对它们产生强引用
-
当block被拷贝到堆上时,都会通过
copy
函数来处理它们-
对象类型的auto变量(假设变量名叫p)
_Block_object_assign((void*)&dst->p, (void*)&src->p, 3/*BLOCK_FIELD_IS_OBJECT*/)
-
__block变量(假设变量名叫a)
_Block_object_assign((void*)&dst->a, (void*)&src->a, 8/*BLOCK_FIELD_IS_BYREF*/)
-
-
当block从堆上移除时,都会通过
dispose
函数释放它们-
对象类型的auto变量(假设变量名叫p)
_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/)
-
__block变量(假设变量名叫a)
_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/)
变量类型 标识符 对象 BLOCK_FIELD_IS_OBJECT __block变量 BLOCK_FIELD_IS_BYREF -
-
__block修饰的对象类型
- 当__block对象变量在栈上时,不会对指向的对象产生强引用
- 当__block对象变量被拷贝到堆上时
- 会调用__block对象变量内部的
__Block_byref_id_object_copy
函数 -
__Block_byref_id_object_copy
内部会调用_Block_object_assign
函数 -
_Block_object_assign
函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained
)做出相应的操作,形成强引用(retain)或者弱引用。(注意:这里仅限于ARC时会retain,MRC时不会retain,都是弱引用)
- 会调用__block对象变量内部的
- 当__block对象变量从堆上移除
- 会调用__block对象变量内部的
__Block_byref_id_object_dispose
函数 -
__Block_byref_id_object_dispose
函数内部会调用_Block_object_dispose
函数 -
_Block_object_dispose
函数会自动释放指向的对象(release)
- 会调用__block对象变量内部的
-
block的循环引用问题
出现的原因:对象引用block,block引用对象,并且都是强引用,就会产生循环引用
-
解决循环引用-ARC
- 使用__weak、__unsafe_unretained修饰对象
不同:当引用的对象被释放后,__weak对象会被置为nil,__unsafe_unretained对象不会置为nil,成为野指针
注意:在block中,为防止__weak修饰的对象提前被释放(一般出现在多线程中),通常使用__strong修饰弱指针,以确保在block执行期间一直存在
- 使用__block修饰对象
注意:必须要在block中将__block修饰的对象置为nil;必须要调用block方法
-
解决循环引用-MRC
- 使用__unsafe_unretained修饰对象(在MRC中没有__weak)
- 使用__block修饰对象(在MRC中__block变量不会强引用对象)