YYModel--源码解析

via http://www.lijianfei.cn/2016/07/13/learnYYModelSomeGain/?utm_source=tuicool&utm_medium=referral

YYModel 作者性能优化的几个 Tip:

缓存

Model JSON 转换过程中需要很多类的元数据,如果数据足够小,则全部缓存到内存中。

查表

当遇到多项选择的条件时,要尽量使用查表法实现,比如 switch/case,C Array,如果查表条件是对象,则可以用 NSDictionary 来实现。

避免 KVC

Key-Value Coding 使用起来非常方便,但性能上要差于直接调用 Getter/Setter,所以如果能避免 KVC 而用 Getter/Setter 代替,性能会有较大提升。

避免 Getter/Setter 调用

如果能直接访问 ivar,则尽量使用 ivar 而不要使用 Getter/Setter 这样也能节省一部分开销。

避免多余的内存管理方法

在 ARC 条件下,默认声明的对象是 strong 类型的,赋值时有可能会产生 retain/release 调用,如果一个变量在其生命周期内不会被释放,则使用 unsafe_unretained 会节省很大的开销。

访问具有 weak 属性的变量时,实际上会调用 objc_loadWeak() 和 objc_storeWeak() 来完成,这也会带来很大的开销,所以要避免使用 weak 属性。

创建和使用对象时,要尽量避免对象进入 autoreleasepool,以避免额外的资源开销。

遍历容器类时,选择更高效的方法

相对于 Foundation 的方法来说,CoreFoundation 的方法有更高的性能,用 CFArrayApplyFunction() 和 CFDictionaryApplyFunction() 方法来遍历容器类能带来不少性能提升,但代码写起来会非常麻烦。

尽量用纯 C 函数、内联函数

使用纯 C 函数可以避免 ObjC 的消息发送带来的开销。如果 C 函数比较小,使用 inline 可以避免一部分压栈弹栈等函数调用的开销。

减少遍历的循环次数

在 JSON 和 Model 转换前,Model 的属性个数和 JSON 的属性个数都是已知的,这时选择数量较少的那一方进行遍历,会节省很多时间。

Runtime MetaClass
Objective-c 中的 每一个类 如: Person类 其实包含两部分
普通类(类本身)如 person 类本身(我们创建的类)

元类(系统创建)Meta Person类,由系统默认创建
这个创建Meta Person类的过程,我们不知道而已

关于meta class的系统方法
判断一个objc_class实例是否是Meta类的Class

BOOL class_isMetaClass(Class cls)

获取一个NSObject类对应的Meta类的Class

Class objc_getMetaClass(const char *name)

类 与 Meta类
类,存放对象的相关数据实例成员变量
实例方法

Meta类,存放类的先关数据类的成员变量(static 类成员变量)
类方法(+开头的方法)

区分 对象方法 与 类方法 调用过程向一个对象发送消息通过对象的 isa指针找到 对象所属类对应的的 objc_class结构体实例
然后开始查找objc_class实例的 method_list 查找 Method

向一个类发送消息通过类Class的 isa指针找到 Meta类对应的 objc_class结构体实例
然后从objc_class实例的 method_list 查找 Method

结合 Demo 中 Runtime_MataClass

import "RuntimeViewController.h"

@interface RuntimeViewController ()

@end

void ReportFunction(id self, SEL _cmd)
{
//1. 对象
NSLog(@"This object is %p.", self);

//2. 对象所属的类
NSLog(@"Class is %@", [self class]);

//3. 所属类的父类
NSLog(@"super is %@.",[self superclass]);

//4. 每一个类都有两部分
//类的第一部分、类本身
//类的第二部分、元类
Class currentClass = [self class];
for (int i = 1; i < 10; i++)//i的次数随便改都可以
{
NSLog(@"Following the isa pointer times = %d, ClassValue = %@, ClassAddress = %p", i, currentClass, currentClass);

//通过Class的 isa指针 找到 MetaClass
currentClass = object_getClass(currentClass);
}

//5. NSObject类本身
NSLog(@"NSObject's class is %p", [NSObject class]);

//6. NSObject类的元类
NSLog(@"NSObject's meta class is %p", object_getClass([NSObject class]));
}

@implementation RuntimeViewController

  • (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

[self setOSProps];

//运行时创建类
[self createClass];

[self imp_implementationWithBlock];
}

  • (void)setOSProps
    {
    self.view.backgroundColor = [UIColor whiteColor];
    self.navigationItem.title = @"Runtime Meta Learn";
    self.navigationController.navigationBar.translucent = YES;
    }

  • (void)createClass
    {
    //1. 创建一个Class
    Class MyClass = objc_allocateClassPair([NSObject class],
    "myclass",
    0);

//2. 添加一个NSString的变量,第四个参数是对其方式,第五个参数是参数类型
if (class_addIvar(MyClass, "itest", sizeof(NSString *), 0, "@")) {
NSLog(@"add ivar success");
}

//3. 添加一个函数
class_addMethod(MyClass,
@selector(report),
(IMP)ReportFunction,
"v@:");

//4. 注册这个类到runtime系统中就可以使用他了
objc_registerClassPair(MyClass);

//5. 测试创建的类
[self test:MyClass];
}

  • (void)report {
    //什么都不做,只是为了OC对象能够调用到c函数
    }

  • (void)test:(Class)class {

//1.
id obj = [[class alloc] init];

//2.
[obj report];
}

控制台输出如下

2016-07-12 11:37:23.281 LJFSourceCodeLearn[1353:75617] add ivar success
2016-07-12 11:37:23.281 LJFSourceCodeLearn[1353:75617] This object is 0x7ff42bc291d0.
2016-07-12 11:37:23.281 LJFSourceCodeLearn[1353:75617] Class is myclass
2016-07-12 11:37:23.282 LJFSourceCodeLearn[1353:75617] super is NSObject.
2016-07-12 11:37:23.282 LJFSourceCodeLearn[1353:75617] Following the isa pointer times = 1, ClassValue = myclass, ClassAddress = 0x7ff42bf0f180
2016-07-12 11:37:23.282 LJFSourceCodeLearn[1353:75617] Following the isa pointer times = 2, ClassValue = myclass, ClassAddress = 0x7ff42bf36550
2016-07-12 11:37:23.282 LJFSourceCodeLearn[1353:75617] Following the isa pointer times = 3, ClassValue = NSObject, ClassAddress = 0x110ecb198
2016-07-12 11:37:23.282 LJFSourceCodeLearn[1353:75617] Following the isa pointer times = 4, ClassValue = NSObject, ClassAddress = 0x110ecb198
2016-07-12 11:37:23.282 LJFSourceCodeLearn[1353:75617] Following the isa pointer times = 5, ClassValue = NSObject, ClassAddress = 0x110ecb198
2016-07-12 11:37:23.282 LJFSourceCodeLearn[1353:75617] Following the isa pointer times = 6, ClassValue = NSObject, ClassAddress = 0x110ecb198
2016-07-12 11:37:23.304 LJFSourceCodeLearn[1353:75617] Following the isa pointer times = 7, ClassValue = NSObject, ClassAddress = 0x110ecb198
2016-07-12 11:37:23.304 LJFSourceCodeLearn[1353:75617] Following the isa pointer times = 8, ClassValue = NSObject, ClassAddress = 0x110ecb198
2016-07-12 11:37:23.304 LJFSourceCodeLearn[1353:75617] Following the isa pointer times = 9, ClassValue = NSObject, ClassAddress = 0x110ecb198
2016-07-12 11:37:23.304 LJFSourceCodeLearn[1353:75617] NSObject's class is 0x110ecb170
2016-07-12 11:37:23.304 LJFSourceCodeLearn[1353:75617] NSObject's meta class is 0x110ecb198

方法object_getClass(id obj)会根据 Class的私有成员变量isa指针 找到一个类,有两种情况:对象的isa >>> 所属类
类的isa >>> Meta 类
Meta类的isa >>> Meta NSObject
Meta NSObject的isa >>> 永远指向自己,形成环路

times = 2时、根据myclass->isa指针,找到其对应的 Meta myclass
times = 3时、根据 Meta myclass->isa 指针,找到了 Meta NSObject
times = 4,5,6,7,8…、根据 元类NSObject 的 isa指针,最后都是指向自己

使用imp_implementationWithBlock()替代上面例子需要一个辅助的static c函数来完成运行时创建Class.

  • (void)imp_implementationWithBlock
    {
    //////////////////////////////////////////////////////
    ///创建一个类
    //////////////////////////////////////////////////////
    Class People = objc_allocateClassPair([NSObject class], "People", 0);

//////////////////////////////////////////////////////
///添加两个变量
//////////////////////////////////////////////////////

if (class_addIvar(People, "name", sizeof(NSString *), 0, @encode(NSString *))) {
NSLog(@"add ivar name success");
}
if (class_addIvar(People, "age", sizeof(int), 0, @encode(int))) {
NSLog(@"add ivar age success");
}

//////////////////////////////////////////////////////
///创建方法的SEL
//////////////////////////////////////////////////////
SEL selector = sel_registerName("talk:");

//////////////////////////////////////////////////////
///创建方法的IMP指针,并指向Block给出的代码
//////////////////////////////////////////////////////
IMP impl = imp_implementationWithBlock(^(id self, NSString *arg1){

//age变量值
//通过KVC
//int age = (int)[[self valueForKey:@"age"] integerValue];

//通过Ivar
Ivar ageIvar = class_getInstanceVariable([self class], "age");
int age = (int)[object_getIvar(self, ageIvar) integerValue];

//name变量值
//通过KVC
//NSString *name = [self valueForKey:@"name"];

//通过Ivar
Ivar nameIvar = class_getInstanceVariable([self class], "name");
NSString *name = object_getIvar(self, nameIvar);

NSLog(@"age = %d, name = %@, msgSay = %@", age, name, arg1);
});

//////////////////////////////////////////////////////
///添加一个方法, 将SEL与IMP组装成一个Method结构体实例,添加到Class中的 method_list数组
//////////////////////////////////////////////////////
class_addMethod(People, selector, impl, "v@:@");

//////////////////////////////////////////////////////
///注册这个类到系统
//////////////////////////////////////////////////////
objc_registerClassPair(People);

//////////////////////////////////////////////////////
///生成一个实例
//////////////////////////////////////////////////////
id instanceP = [[People alloc] init];

//////////////////////////////////////////////////////
///给Ivar赋值
//////////////////////////////////////////////////////

//通过KVC赋值
[instanceP setValue:@"变量字符串值" forKey:@"name"];
//[instanceP setValue:@19 forKey:@"age"];

//通知Ivar赋值
Ivar ageIvar = class_getInstanceVariable(People, "age");
object_setIvar(instanceP, ageIvar, @19);

//////////////////////////////////////////////////////
///发送消息
//////////////////////////////////////////////////////
((void (*)(id, SEL, NSString *))(void *) objc_msgSend)(instanceP, selector, @"参数值");

//////////////////////////////////////////////////////
///释放对象、销毁类
//////////////////////////////////////////////////////
instanceP = nil;
objc_disposeClassPair(People);
}

方法的编码格式:v@: >>> 返回值void,参数1:self,参数2:SEL >>> - (void)funcName;
v@:@ >>> 返回值void,参数1:self,参数2:SEL, 参数3:NSString* >>> - (void)funcName:(NSSring *)name;

对应的Objective-C方法的SEL也应该是 >>> talk:带一个参数
imp_implementationWithBlock()接收一个添加方法被调用时的回调Block其格式为: method_return_type ^(id self, method_args …)

Meta Class 元类

Class objc_allocateClassPair(Class superclass,
const char *name,
size_t extraBytes)

函数名 objc_allocateClassPair.. 中的 ClassPair意思是说一对Class.
但是该函数只返回了一个Class
没有被返回的另一个Class就是 MetaClass 元类,由系统创建.
Meta Class与普通类Class 都是结构体 objc_class实例

Objective-c中的 对象 与 类

经常写获取一个对象的类型Class的代码

Class cls = [Person class];

就用到了Class这个数据结构,其定义为如下,可以得到Class是 结构体objc_class实例的 指针类型

typedef struct objc_class *Class;

那么能够成为一个类(Class)的数据结构为

struct objc_class {

/*
又指向一个结构体objc_class实例
类的isa指针,指向类的 元类 MetaClass
*/
Class isa OBJC_ISA_AVAILABILITY;

if !OBJC2

/**
指向其父类(普通类)
*/
Class super_class OBJC2_UNAVAILABLE;

const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
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;
/* Use Class instead of struct objc_class * */

而Class数据结构中最重要的一个数据项是 Class isa; 这样的isa指针.
对象
能够成为一个对象(Object)的数据结构如下,可以看到对象数据结构中最重要的就是Class isa;指针项

struct objc_object {

/**
对象的isa指针,指向其 所属类本身(不是元类)
*/
Class isa OBJC_ISA_AVAILABILITY;
};

Class是struct objc_class的指针类型,那么struct objc_object也有其对于的指针类型
调用类方法与对象方法给对象发送消息时,消息是在对象所属类的方法列表method_list或cache中查询Method
给类发消息时,消息是在这个类的元类 meta class的方法列表或缓存中查询Method

所以也就是说对象方法与类方法存放地点的不同
对象方法、存放在 类本身
类方法、存放在 类的元类MetaClass

所以,元类是必不可少的,它存储了类的所有类方法
对象、类、元类三者之间的关系图



对象的 isa指针 指向 所属类
每一个类,都有一个 isa指针 指向一个 唯一的 MetaClass
每一个Meta Class,拥有的 isa指针 都指向 顶层 NSObject Meta Class
最上层的Meta Class(Meta NSObject)的isa指针指向自己,形成一个 回路
类本身的 super_class 指向其父类,如果该类为根类则值为 nil
每一个 MetaClass 的 super_class指针 都指向其 原本Class的superClass对应的 MetaClass特列: 但是最上层的 Meta Class 的 super_class 指针,指向的却是 NSObject 原本 Class

所有的 类 NSObject子类 都是 objc_class结构体 的实例
NSObject类获取Class的源码如下:

  • (Class)class {
    return self;
    }

Class是 objc_class结构体实例的指针变量类型
那么+[NSObject class]返回就是一个 objc_class结构体实例
而 self 一般是 一个指向对象地址的指针
而此时的 self 所代表的就是 NSObject类本身

那么可以猜测: NSObject 是 objc_class的实例

@implementation RuntimeViewController

  • (void)viewDidLoad {
    [super viewDidLoad];

//名字
NSLog(@"%@", [RuntimeViewController class]);

//多次输出其地址
NSLog(@"%p", [self class]);
NSLog(@"%p", [self class]);
NSLog(@"%p", [RuntimeViewController class]);

}

输出结果

2016-07-12 13:30:27.264 LJFSourceCodeLearn[1640:121986] RuntimeViewController
2016-07-12 13:30:27.265 LJFSourceCodeLearn[1640:121986] 0x101d03cd0
2016-07-12 13:30:27.265 LJFSourceCodeLearn[1640:121986] 0x101d03cd0
2016-07-12 13:30:27.265 LJFSourceCodeLearn[1640:121986] 0x101d03cd0

每一个NSObject类在运行阶段,是按照一个单例保存

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理。这种动态语言的...
    有一种再见叫青春阅读 577评论 0 3
  • Objective-C语言是一门动态语言,他将很多静态语言在编译和链接时期做的事情放到了运行时来处理。这种动态语言...
    tigger丨阅读 1,381评论 0 8
  • 参考链接: http://www.cnblogs.com/ioshe/p/5489086.html 简介 Runt...
    乐乐的简书阅读 2,129评论 0 9
  • 简介 Runtime 又叫运行时,是一套底层的 C 语言 API,其为 iOS 内部的核心之一,我们平时编写的 O...
    专业男神经阅读 901评论 0 2