先看下类对应的结构描述:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
、 #if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识
long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定义的链表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
#endif
} OBJC2_UNAVAILABLE;
@property的本质
这里有一个孙源的面试题是:@property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的。
简单点说就是:@property = ivar + getter + setter;
也就是生成实例变量及对应的存取方法。
那这跟我们这里所讲的objc_class结构体有什么关系呢?
因为@property对应的ivar、getter和setter都会对应添加到我们结构体中的ivar_list、method_list中。也就是说我们每次增加一个属性,系统都会在ivar_list添加一个成员变量的描述,在 method_list 中增加 setter 与 getter 方法的描述。
Category结构体
Category的定义如下:
typedef struct objc_category *Category;
Category是一个objc_category结构体的指针,objc_category的定义如下:
struct objc_category {
char *category_name OBJC2_UNAVAILABLE;
char *class_name OBJC2_UNAVAILABLE;
struct objc_method_list *instance_methods OBJC2_UNAVAILABLE;
struct objc_method_list *class_methods OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
method: 对象的每个方法的结构体,SEL是方法选择器,是HASH后的值,可以通过这个值找到函数体的实现,IMP 是函数指针
[objc] view plain copy
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
charchar *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
objc_var:变量结构体---名称,类型,偏移字节和占用的空间
[objc] view plain copy
struct objc_ivar {
charchar *ivar_name OBJC2_UNAVAILABLE;
charchar *ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
YYModel:具体以下特点:高性能、自动类型转换、类型安全、非侵入性、轻量等。
FOUNDATION_EXPORT是用来定义常量的,另外一个经常用到的#define定义常量。
那么两者的区别?
假设分别使用两者定义字符串常量,前者可以通过==来判断字符串是否相等,后者则需要使用isEqualToString:来判断。因为,前者比较的是字符串指针地址,后者比较每个字符,因此前者效率更高
YYClassIvarInfo
该类对应实例变量信息(ivars),包含:名称,偏移量,类型编码,类型;其中类型请查看【官方文档Type Encodings】
YYClassMethodInfo
方法的信息类,包含,方法名称,SEL,IMP,参数类型编码,返回值类型编码等。
YYClassPropertyInfo
属性信息类,包含名称,类型,类型编码,ivar名称,类,协议列表,setter/getter等。
YYClassInfo:类信息。
接下再看一下Objc_method结构体
structobjc_method{
SEL method_name
char* method_types
IMP method_imp
}
SEL指方法名称,IMP指方法实现。
NSObject + YYModel
该文件定义三个分类和一个协议,以及两个内部类,下面是.h文件中提供的接口。
+ (NSDictionary *)modelCustomPropertyMapper {
return@{@"name" : @"n",
@"page" : @"p",
@"desc" : @"ext.desc",
@"bookID": @[@"id", @"ID", @"book_id"]};
}
+ (NSDictionary *)modelContainerPropertyGenericClass {
return@{@"shadows" : [YYShadow class],
@"borders" : YYBorder.class,
@"attachments" : @"YYAttachment" };
}
创建两个缓存 classCache、metaCache
1.使用runtime的动态性,为对象动态添加属性,方法(set,get)
2.利用runtime获取属性列表,方法列表,遍历属性,为属性赋值
所有解析json 或者自动实现其他数据转换为model的,最终都是利用runtime 来动态获取model的属性、示例变量等
特色功能
高性能:转换效率接近手写代码。
自动类型转换:对象类型能自动转换。
类型安全:在转换过程中所有的类型都会被验证,以确保类型安全。
非侵入性:不需要让模型类继承自基类。
轻量级:整个库只包含5个文件。
文档和测试覆盖:100%文档覆盖,99.6代码覆盖。
YYClassInfo功能主要是将Runtime层级中的一些结构体封装到NSObject中调用;
/ 单例缓存 classCache 与 metaCache,对应缓存类和元类
首先创建单例缓存,类缓存和元类缓存;
使用dispatch_semaphore 保证缓存线程安全;
初始化操作之前首先缓存中查找是否已经向缓存中注册过的当前要初始化的YYClassInfo;
如果查找缓存对象,需要判断对象是否需要更新以及其他相关操作;
如果没有找到缓存对象,就开始初始化;
初始化成功之后,向缓存中注册YYClassInfo实例。
NSObject+YYModel重新定义了两个类,来使用 YYClassInfo 中的封装。
YModel的核心是通过runtime获取结构体中得Ivars的值,将此值定义为key,然后给key赋value值,所以我们需要自己遍历容器(NSArray,NSSet,NSDictionary),获取每一个值,然后KVC。
Runloop 总结
可以看出,RunLoop被开启的线程会一直存在。因为在没有事件发生的时候处于休眠状态,有事件发生的时候处于工作状态。以此来节约CPU资源。这样就可以让一个线程成为常驻线程,也就是说该线程一直存在。
RunLoop和线程的关系:
RunLoop是用来管理线程的,每个线程对应一个RunLoop对象。我们不可以去创建当前线程的RunLoop对象,但是我们可以去获取当前线程的RunLoop。RunLoop就是来监听该线程有无事件发生,如果有就工作,如果没有就休眠。
主线程的RunLoop对象默认开启,其他线程默认不开启。
其实NSTimer定时器的触发正是基于RunLoop运行的,所以使用NSTimer之前必须注册到RunLoop,但是RunLoop为了节省资源并不会在非常准确的时间点调用定时器,如果一个任务执行时间较长,那么当错过一个时间点后只能等到下一个时间点执行,并不会延后执行
原因是:为了确保定时器正常运转,当加入到RunLoop以后系统会对NSTimer执行一次retain操作(特别注意:timer2创建时并没直接赋值给timer2,原因是timer2是weak属性,如果直接赋值给timer2会被立即释放,因为timerWithXXX方法创建的NSTimer默认并没有加入RunLoop,只有后面加入RunLoop以后才可以将引用指向timer2)。
那就是NSTimer不是一种实时机制,官方文档明确说明在一个循环中如果RunLoop没有被识别(这个时间大概在50-100ms)或者说当前RunLoop在执行一个长的call out(例如执行某个循环操作)则NSTimer可能就会存在误差,RunLoop在下一次循环中继续检查并根据情况确定是否执行(NSTimer的执行时间总是固定在一定的时间间隔,例如1:00:00、1:00:01、1:00:02、1:00:05则跳过了第4、5次运行循环)。