OC底层原理总结笔记
一.对象的本质
- OC的类主要C\C++的 结构体 来实现的
- 一个NSObject对象占用多少内存?
- 系统分配给NSObject对象占用16个字节(通过malloc_size函数获取);
- 但NSObject对象只使用了8个字节(通过class_getInstanceSize函数获取),用来放isa指针,剩下8个字节空着;(64bit环境下)
- OC中CoreFoundation框架规定对象至少16个字节。
- iOS内存对齐分配的内存数为16个字节的倍数。
- class_getInstanceSize函数对象实际占用的内存,malloc_size函数获取系统为对象分配的内存
二.对象的分类
- 对象分为:
实例对象,
类对象(object_getClass(对象)),
元类对象(object_getClass(class对象)) - 对象里存储的是什么信息?
isa指针、自己的成员变量 - 类对象class里存储的是什么信息?
isa指针、superclass指针、
类的属性信息(@property)、类的对象方法、
类的协议信息(protocol)、类的成员变量信息(ivar) - 元类对象meta-class里存储的是什么信息?
isa指针、superclass指针、类的类方法信息(class method)
HHPerson *person = [[HHPerson alloc]init];//对象 包含:isa指针,对象成员变量的值
Class personClass = object_getClass(person);//类对象 (class返回的始终都是类对象) 包含:isa指针,superClass指针,类的属性信息(@property),成员变量信息(结构体ivar),类的对象方法,协议信息 (为什么存在类中:这些信息存一份就够了)
Class personMetalClass = object_getClass(personClass);//元类对象 包含:isa指针,superClass指针,类方法信息
NSLog(@"%@,%@",personClass,personMetalClass);//HHPerson,HHPerson
NSLog(@"%p,%p",personClass,personMetalClass);//0x100008210,0x1000081e8
NSLog(@"%d,%d",class_isMetaClass(personClass),class_isMetaClass(personMetalClass));//0,1
三.isa superclass 指针
isa指针 、superclass指针
四.KVO分析
1.kvo代码基础写法 代码片
.
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.person = [Preson new];
self.person.age = 10;
NSKeyValueObservingOptions options = NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew;
[self.person addObserver:self forKeyPath:@"age" options:options context:nil];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.person.age = 20;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(@"%@,-%@,-%@",keyPath,object,change);
}
- (void)dealloc{
[self.person removeObserver:self forKeyPath:@"age"];
}
//打印
age,-<Preson: 0x2804744e0>,-{
kind = 1;
new = 20;
old = 10;
}
2.使用了kvo监听的对象
- 对象的isa指针指向 -> NSKVONotifying_NSPreson(iOS动态创建的Preson的子类)
- 调用子类NSKVONotifying_NSPreson set方法
//先调用子类的setter方法
-(void)setAge:(int)age
{
//foundation c语言函数
_NSSetIntValueAndNotify();
}
//相当于以下代码
-(void)setAge:(int)age
{
[persion willChangeValueForKey:@"age"];//通知监听器
[super setAge:age];
[persion didChangeValueForKey:@"age"];//通知监听器
}
//手动监听属性变化
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self.person willChangeValueForKey:@"age"];//通知监听器
self.person -> _age = 20;
[self.person didChangeValueForKey:@"age"];//通知监听器
}
//打印
age,-<Preson: 0x283bbc4d0>,-{
kind = 1;
new = 20;
old = 0;
}
//调试lldb:p (IMP) 0x方法内存地址 -> 调用的方法信息
五.KVC分析
1.Key-Value Coding,“键值编码”
添加kvo监听,通过kvc修改属性的值,是可以触发kvo,没有set方法也是可以触发,kvc内部调用willChangeValueForKey:及didChangeValueForKey:方法进行触发。
2.setValue: forKey:原理
3.ValueForKey:原理
六.Category分类
结论:
- 内存中所有对象方法最终都会放在类中,所有类方法都会放在元类中。
- 通过runtime动态将分类的方法合并到类对象,元类对象中
将oc文件转为c++文件:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 文件名.m
1.每个分类编译完会生成c++ struct结构体_category_t
//比如Person有分类Eat
static struct _category_t _OBJC_$_CATEGORY_Person_$_Eat = {
"Person",
cls,
对象方法列表,
类方法列表,
协议列表,
属性列表
}
2.运行时,通过Runtime加载某个类的所有Category数据,把所有Category的方法、属性、协议数据,合并到类信息中(类对象、元类对象中)合并到一个大数组中后面参与编译的Category数据,会放在数组的前面,会被优先调用。
七.load、initialize
load方法
-
load方法会在Runtime加载类、分类时调用,通过函数指针调用
-
每个类、分类的+load,在程序运行过程中只调用一次
-
调用顺序
- 1.先调用类的+load方法
按照编译顺序调用(先编译,先调用)
调用子类的+load方法之前会先调用父类的+load方法 - 2.再调用分类的+load方法
按照编译先后顺序调用(先编译,先调用)
- 1.先调用类的+load方法
initialize方法
-
+initialize方法会在类第一次接收到消息时调用(alloc),通过消息机制调用
-
调用顺序
- 先调用父类的+initialize(父类调用过不会再调用),再调用子类的+initialize;
- 先初始化父类,再初始化子类,每个类只会初始化1次;但是如果子类没有实现+initialize,会调用父类的+initialize
-
+initialize和+load的区别是,+initialize是通过obj_msgSend进行调用,+load是通过函数进行调用
- 如果子类没有书实现+initialize,会调用父类的+initialize(所以父类的+initialize可以调用多次)
- 如果分类实现了+initialize,就覆盖类本身的+initialize调用
八.关联对象
如何为分类添加成员变量
- 首先不能直接为分类添加成员变量,只能添加属性及set,get方法申明,并没有生成成员变量及set,get方法的实现
- 使用runtime关联对象
//添加关联对象
objc_setAssociatedObject(<#id _Nonnull object#>, <#const void * _Nonnull key#>, <#id _Nullable value#>, <#objc_AssociationPolicy policy#>)
//获取关联对象
objc_getAssociatedObject(<#id _Nonnull object#>, <#const void * _Nonnull key#>)
//移除所有关联对象
objc_removeAssociatedObjects(<#id _Nonnull object#>)
//方案一:void *
#import <objc/runtime.h>
@interface Person (Test1)
@property (nonatomic,copy)NSString *name;
@end
@implementation Person (Test1)
static const void * NameKey = &NameKey;
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, NameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)name
{
return objc_getAssociatedObject(self, NameKey);
}
@end
//方案二:char
@interface Person (Test1)
@property (nonatomic,copy)NSString *name;
@end
@implementation Person (Test1)
static const char *NameKey;
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, &NameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)name
{
return objc_getAssociatedObject(self, &NameKey);
}
@end
//方案三:属性名作为key
@interface Person (Test1)
@property (nonatomic,copy)NSString *name;
@end
@implementation Person (Test1)
#define NameKey @"name"
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, NameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)name
{
return objc_getAssociatedObject(self, NameKey);
}
@end
//方案四:get方法@selector作为key
@interface Person (Test1)
@property (nonatomic,copy)NSString *name;
@end
@implementation Person (Test1)
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)name
{
// return objc_getAssociatedObject(self, @selector(name));
// _cmd = @selector(name)
return objc_getAssociatedObject(self, _cmd);
}
@end
- 底层实现原理
九.block
十.Runtime
十一.Runloop
十二.多线程
十三.内存管理
十四.性能优化
十五.架构设计
知识点后续有时间继续完善
以上知识点学习参考:MJ小码哥李明杰,如有疑问请留言,谢谢