二、Objective-C对象模型

1、简介

主要介绍OC对象模型的实现细节,以及OC对象模型对isa swizzling和method swizzling的支持。

2、isa指针

OC是一门面向对象的编程语言,每一个对象都是一个类的实例,在OC语言的内部,每一个对象都有一个名为isa的指针,指向该对象的
每一个类描述了一系列它的实例的特点,包括成员变量的列表、成员函数的列表等。
每一个对象都可以接受消息,而对象能够接受的消息列表保存在它所对应的类中。
按照面向对象语言的设计原则,所有事物都应该是对象,在OC语言中,每一个类实际上也是一个对象,每一个类也有一个isa指针,每一个类也可以接收消息:

[NSObject alloc]

NSObject其实就是Class对象:
NSObject

Class其实只是一个结构体的指针:
Class

objc_class是一个包含isa指针结构体:
objc_class
上图中除了isa外还有其他成员变量,但那是为了兼容非2.0版本的OC的遗留逻辑,大家可以忽略!

因为类也是一个对象,所以它也必须是另一个类的实例,这个类就是元类(metaclass)。元类保存了类方法的列表,当一个类方法被调用时,元类会首先查找它本身是否有该类方法的实现,如果没有,则该元类会向它的父类查找,以此类推,一直找到继承链的头!元类也是一个对象,为了设计上的完整,元类的isa指针都会指向一个根元类(root metaclass),跟元类本身的isa指针指向自己,这样就形成了一个闭环!所有子类的元类都会继承父类的元类,即类对象和元类对象有着同样的继承关系:
image.png
我们从图中可以看出:
  1. NSObject的类中定义了实例方法,例如-(id)init方法和-(void)dealloc方法
  2. NSObject的元类中定义了类方法,例如+(id)alloc方法、+(void)load方法和+(void)initialize方法
  3. NSObject的元类继承自NSObject类,所有NSObject类是所有类的根,因此NSObject中定义的实例方法可以被所有对象调用,例如-(id)init方法和-(void)dealloc方法
  4. NSObject的元类的isa指向自己

3、类的成员变量

把类的实例看成一个C语言的结构体,成员变量排列顺序如下:

isa指针
NSObject的成员变量
NSObject子类的成员变量
NSObject子类的子类的成员变量
.....
父类的成员变量
类本身的成员变量

验证程序:

@interface car : NSObject {
    int _carNO;
}
@end
@implementation car
@end

@interface SUV : car {
    int _SUVNO;
}
@end
@implementation SUV
@end

SUV *suv = [[SUV alloc] init];  //创建对象

在程序中插入断点,断点处利用调试器输出对象的结构:

image.png

因为对象在内存中的排布可以看成一个结构体,该结构体的大小并不能动态变化,所以无法在运行时动态地给对象增加成员变量

对象的方法定义都保存在类的可变区域中,OC2.0并未在头文件中将实现暴露出来,但在OC1.0中,我们可以看到方法的定义列表是一个名为methodLists的指针的指针,通过修改该指针指向的指针的值,就可以动态地为某一个类增加成员方法,这也是category的原理,同时也说明了为什么category不能增加成员变量!!!通过associatedObject关联对象可以变相地给对象增加成员变量,但由于实现机制不一样,所以并不是真正改变了对象的内存结构!
methodLists

因为isa本身也只是一个指针,我们也可以在运行时动态地修改isa指针的值,打到替换对象整个行为的目的!

4、对象模型的应用

4.1 动态创建对象

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //创建一个名为XZCustomView的类,它是UIView的子类
    Class newClass = objc_allocateClassPair([UIView class], "XZCustomView", 0);
    //为该类增加一个名为report的方法
    class_addMethod(newClass, @selector(report), (IMP)ReportFunction, "v@:");
    //为该类增加一个名为name的成员变量,虽然无法在运行时为已有对象增加成员变量,但是在使用运行时方法创建对象时,可以为对象增加成员变量
    //class_addProperty增加属性
    class_addIvar(newClass, "_name", sizeof(NSString *), log(sizeof(NSString *)), "I");
    //注册该类
    objc_registerClassPair(newClass);
    
    id instanceOfNewClass = [[newClass alloc] init];
    object_setIvar(instanceOfNewClass, class_getInstanceVariable(newClass, "_name"), @"Lance");
    [instanceOfNewClass performSelector:@selector(report)];
}

void ReportFunction(id self, SEL _cmd){
    NSLog(@"This object is %p", self);
    NSLog(@"This object's name is %@", object_getIvar(self, class_getInstanceVariable([self class], "_name")));
    NSLog(@"Class is %@, and super is %@.", [self class], [self superclass]);
    
    Class currentClass = [self class];
    for (int i = 1; i < 5; i++) {
        NSLog(@"Following the isa pointer %d thimes gives %p", i, currentClass);
        currentClass = object_getClass(currentClass);
    }
    
    NSLog(@"NSObject's class is %p", [NSObject class]);
    NSLog(@"NSObject's meta class is %p", object_getClass([NSObject class]));
}

运行测试程序,log打印如下:


程序log

要点如下:

  1. #import <objc/runtime.h>
  2. objc_allocateClassPair方法创建新的类
  3. class_addMethod方法来给类增加新的方法
object_setIvar
class_getInstanceVariable
object_getIvar
  1. class_addIvar方法来给类增加新的成员变量
  2. objc_registerClassPair方法来注册新的类
  3. object_getClass方法来获得对象的isa指针所指向的对象

4.2 系统相关API及应用

应用一、isa swizzling
isa-swizzling

其实KVO的实现可能是:

  • 添加Observer
    通过runtime偷偷实现了一个子类,并且以NSKVONotifying_+类名来命名,将之前那个对象的isa指针指向了这个子类
    重写了观察的对象setter方法,并且在重写的中添加了willChangeValueForKey:以及didChangeValueForKey:
  • 移除Observer
    只是简单的将其的isa指向原来的类对象中

然后我们在分析一下, 在真正调用的setAge:的情况下, 根据消息机制我们知道它先通过isa找到对应对象的类,也就是现在NSKVONotifying_Person,然后再去找setAge:,由于NSKVONotifying_Person这个对象重写了这个方法,那么就会直接取当前的实现,也就是带有willChangeValueForKey:以及didChangeValueForKey:,那么自然就实现了对KVO的实现了。
参考:
isa-swizzling
整理了一下关于KVO的姿势

应用二、Method Swizzling

OC提供了以下API来动态替换类方法或实例方法的实现:

  • class_replaceMethod替换类方法的定义
    当类中没有想替换的原方法时,该方法会调用class_addMethod来为该类增加一个新方法,这也是该API需要传入types参数的原因!!
    当需要替换的方法有可能不存在时,可以考虑该方法!
  • method_exchangeImplementations交换两个方法的实现
    exchange

    其实内部实现是调用了两次method_setImplementation!当需要交换两个方法的实现时使用!

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

推荐阅读更多精彩内容