Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >OC对象模型

OC对象模型

作者头像
Helloted
发布于 2022-06-06 11:35:08
发布于 2022-06-06 11:35:08
67400
代码可运行
举报
文章被收录于专栏:HellotedHelloted
运行总次数:0
代码可运行

先看OC关于NSObject的源码

NSObject源码

一、alloc与init

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
NSObject *obj = [NSObject alloc]init];

alloc与init发生了什么呢?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
+ (id)alloc {
    return _objc_rootAlloc(self);
}

id _objc_rootAlloc(Class cls)
{
#if 0  &&  __OBJC2__
    // Skip over the +allocWithZone: call if the class doesn't override it.
    // fixme not - this breaks ObjectAlloc
    if (! ((class_t *)cls)->isa->hasCustomAWZ()) {
        return class_createInstance(cls, 0);
    }
#endif
    return [cls allocWithZone: nil];
}

id class_createInstance(Class cls, size_t extraBytes) {
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}

static id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, bool cxxConstruct = true, size_t *outAllocatedSize = nil) {
    size_t size = cls->instanceSize(extraBytes);

    id obj = (id)calloc(1, size);
    if (!obj) return nil;
    obj->initInstanceIsa(cls, hasCxxDtor);

    return obj;
}

可以看出,alloc类方法是开辟了一块内存,生成了一个实例对象,并且对实例对象进行了初始化

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
- (id)init {
    return _objc_rootInit(self);
}

id _objc_rootInit(id obj)
{
    // In practice, it will be hard to rely on this function.
    // Many classes do not properly chain -init calls.
    return obj;
}

init方法只是返回了该实例对象

二、NSObject与Class

1、对象

NSObject

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@interface NSObject <NSObject>
{
    Class isa;
}
@end

每一个NSObject里都有一个Class(isa指针),表明这个类的类别Class

id类型

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
typedef struct objc_object { 
    Class isa; 
} *id; 

从上面可以看出id类型里面也有一个Class

2、Class

Class的定义

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
typedef struct objc_class *Class;

objc_class

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
struct objc_class {
    Class isa;

#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;

isa指针指向哪里?

从图上的虚线箭头就能看出,实例对象的isa指向类,类的isa指向元类(meta),元类的isa指向Root元类

三、isKindOfClass与isMemberOfClass

1、获取class
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
+ (Class)class {
    return self;
}

- (Class)class {
    return object_getClass(self);
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static inline Class _object_getClass(id obj)
{
#if SUPPORT_TAGGED_POINTERS
    if (OBJC_IS_TAGGED_PTR(obj)) {
        uint8_t slotNumber = ((uint8_t) (uint64_t) obj) & 0x0F;
        Class isa = _objc_tagged_isa_table[slotNumber];
        return isa;
    }
#endif
    if (obj) return obj->isa;
    else return Nil;
}

从上面可以看到,class类方法和实例方法都是获取当前Class也就是isa指针

  • 实例方法调用时,通过对象的 isa 在类中获取方法的实现
  • 类方法调用时,通过类的 isa 在元类中获取方法的实现
2、isMemberOfClass

isMemberOfClass: Returns a Boolean value that indicates whether the receiver is an instance of a given class.

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
+ (BOOL)isMemberOfClass:(Class)cls {
    return object_getClass((id)self) == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

可以看出,isMemberOfClass是判断当前实例/类是否是那个类型

3、isKindOfClass

isKindOfClass: Returns a Boolean value that indicates whether the receiver is an instance of given class or an instance of any class that inherits from that class.

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = class_getSuperclass(tcls)) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = class_getSuperclass(tcls)) {
        if (tcls == cls) return YES;
    }
    return NO;
}

isKindOfClass是用来判断实例/类是否是那个类型,或者继承自那个类。

经典题目

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; //YES
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; //NO
BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]]; //NO
BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]]; //NO
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
NSObject = [NSObject class]
[NSObject isMemberOfClass:NSObject]?
题目变成:
object_getClass(NSObject)==NSObject?
metat_NSObject = object_getClass(NSObject) 
所以最终题目转化为
metat_NSObject != NSObject?  ==> NO;

四、KVO与KVC

1、KVO(Key-Value Observing)

先看看官方文档

Key-Value Observing Implementation Details Automatic key-value observing is implemented using a technique called isa-swizzling. The isa pointer, as the name suggests, points to the object’s class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data. When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance. You should never rely on the isa pointer to determine class membership. Instead, you should use the class method to determine the class of an object instance.

KVO运用了一个isa-swizzling的机制,runtime还有一个method-swizzling的机制,称为’黑魔法’。

当某个类的对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的 setter 方法。然后在派生类的setter方法里实现通知机制。

同时派生类还重写了 class 方法以“欺骗”外部调用者它就是起初的那个类。然后系统将这个对象的 isa 指针指向这个新诞生的派生类,因此这个对象就成为该派生类的对象了,因而在该对象上对 setter 的调用就会调用重写的 setter,从而激活键值通知机制。此外,派生类还重写了 dealloc 方法来释放资源。

当没有observer观察任何一个property时,删除动态创建的子类。

简单而言:实例对象在被观察时,生成派生类,派生类在setter方法中valuewillchange方法和valuesdidchanged方法里发出通知,并且通过isa-swizzling,从而使实例对象成为派生类的对象,所以实例对象在setter属性时可以产生通知。达到观察的目的。

2、KVC(Key Value Coding)

KVC是是一种可以通过字符串的名字(key)来访问类属性的机制。

修改值 setValue:forKey: setValue:forKeyPath: setValue:forUnderfinedKey: setNilValueForKey: 对非类对象属性设置nil时调用,默认抛出异常。

1、首先搜索setKey:方法。(key指成员变量名,首字母大写)

2、上面的setter方法没找到,如果类方法accessInstanceVariablesDirectly返回YES。那么按 _key,_isKey,key,iskey的顺序搜索成员名。(NSKeyValueCodingCatogery中实现的类方法,默认实现为返回YES)

3、如果没有找到成员变量,调用setValue:forUnderfinedKey:

获取值 valueForKey: 传入NSString属性的名字。 valueForKeyPath: 属性的路径,xx.xx valueForUndefinedKey 默认实现是抛出异常,可重写这个函数做错误处理

1、首先按getKey,key,isKey的顺序查找getter方法,找到直接调用。如果是BOOL、int等内建值类型,会做NSNumber的转换。

2、上面的getter没找到,查找countOfKey、objectInKeyAtindex、KeyAtindexes格式的方法。如果countOfKey和另外两个方法中的一个找到,那么就会返回一个可以响应NSArray所有方法的代理集合的NSArray消息方法。

3、还没找到,查找countOfKey、enumeratorOfKey、memberOfKey格式的方法。如果这三个方法都找到,那么就返回一个可以响应NSSet所有方法的代理集合。 4、还是没找到,如果类方法accessInstanceVariablesDirectly返回YES。那么按 _key,_isKey,key,iskey的顺序搜索成员名。

5、再没找到,调用valueForUndefinedKey。

原理

isa指针(is kind of 的意思)指向维护分发表的对象的类,该分发表实际上包含了指向实现类中的方法的指针和其他数据。比如说如下的一行KVC代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[site setValue:@"sitename" forKey:@"name"];

//会被编译器处理成

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
SEL sel = sel_get_uid(setValue:forKey);
IMP method = objc_msg_loopup(site->isa,sel);
method(site,sel,@"sitename",@"name");

每个类都有一张方法表,是一个hash表,值是还书指针IMP,SEL的名称就是查表时所用的键。

SEL数据类型:查找方法表时所用的键。定义成char*,实质上可以理解成int值。

IMP数据类型:他其实就是一个编译器内部实现时候的函数指针。当Objective-C编译器去处理实现一个方法的时候,就会指向一个IMP对象,这个对象是C语言表述的类型。

五、Self与Super

1、[self class]与[super class]

有一个有意思的题目,有一个Son类继承自Father类

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    @implementation Son : Father
    - (id)init
    {
        self = [super init];
        if (self) {
            NSLog(@"%@", NSStringFromClass([self class]));
            NSLog(@"%@", NSStringFromClass([super class]));
        }
        return self;
    }
    @end

最终结果都是Son,为什么呢?

官方文档中self相关解释 Whenever you’re writing a method implementation, you have access to an important hidden value, self. Conceptually, self is a way to refer to “the object that’s received this message.” It’s apointer, just like the greeting value above, and can be used to call a method on the current receiving object. super解释 There’s anotherimportant keyword available to you in Objective-C, called super. Sending a message to super is a way to call through to a method implementation defined by a superclass further up the inheritance chain. The most common use of super is when overriding a method.

简而言之是self调用自己方法,super调用父类方法

但是底层原理呢?我们知道,OC的消息转发机制,当self时,方法转换成

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
id objc_msgSend(id receiver, SEL theSelector, ...)

​ objc_msgSend sends a message with a simple return value to an instance of a class

而super关键字调用方法则转换成

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
id objc_msgSendSuper(struct objc_super *super, SEL op, ...)

struct objc_super {
  id receiver;
  Class superClass;
};

​ objc_msgSendSuper sends a message with a simple return value to the superclass of an instance of a class.

可以看到objc_msgSendSuper的receiver还是son。

objc_msgSend与objc_msgSendSuperd都去查找class的Seletor,一直查到NSObject类才查到class方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
- (Class)class {
    return object_getClass(self);
}

也就是说,最终都是调用的receiver也就是son,获取到了Class.

2、self = [super init]

If a class does implement an initializer, it should invoke an initializer of its superclass as the first step. This requirement ensures a series of initializations for an object down the inheritance chain, starting with the root object. The NSObject class declares the init method as the default object initializer, so it is always invoked last but returns first.

所以标准的初始化代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
- (id)init {
    if (self = [super init]) { // equivalent to "self does not equal nil"
        self ...
    }
    return self;
}

六、属性与变量(property & instance variable)

在ios5以后我们使用@property来声明属性变量,编译器会自动(@syntheszie var = _var)为我们生成对应的一个以下 划线加属性名 命名的实例变量,还有其对应的getter、setter

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@property (copy, nonatomic) NSString *var;
 
------------------等效分割线------------------
NSString *_var;
 
- (NSString *)var {
    return _var;
}
 
- (void)setVar:(NSString *)var {
    _var = var;
}

这样一来我们就可以看出通过self.var和_var访问实例变量的区别,在.m文件中可以通过_var来访问实例变量,但是getter、setter不会被调用,而来自外部的访问,需要通过getter、setter。

注意,使用readonly关键字修饰后,编译器只会为我们生成getter。

假如一个属性被关键字@dynamic所修饰,则编译器不会自动生成其对应的getter、setter,然而如果开发者没有自行创造getter、setter,将不会在编译期提醒,运行时触发则会发生crash。 顺便一提@dynamic还能帮助我们替换掉某类中本来就存在的,而我们又想自己创造的property。


本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
OC-经典面试题分析(一)OC-经典面试题分析(一)
事实上这个方法并不会被调用,而是由llvm处理为调用objc_opt_isKindOfClass这个方法,具体可以打断点或者使用汇编的方式查看(猜测:方便做一些兼容)
用户8893176
2021/08/09
2830
OC-经典面试题分析(一)OC-经典面试题分析(一)
神经病院Objective-C Runtime入院第一天—isa和Class
我第一次开始重视Objective-C Runtime是从2014年11月1日,@唐巧老师在微博上发的一条微博开始。
一缕殇流化隐半边冰霜
2018/08/30
8130
神经病院Objective-C Runtime入院第一天—isa和Class
[Objective-C Runtime] 类与对象
概述 常说Objective-C是一门动态语言,那么问题来了,这个动态表现在那些方面呢? 其实最主要的表现就是Objective-C将很多静态语言在编译和链接时做的事情放到了运行时去做。 它在运行时实现了对类、方法、成员变量、属性等信息的管理机制,同时,运行时机制为我们开发过程提供很多便利之处,比如: 在运行时创建或者修改一个类; 在运行时修改成员变量、属性等; 在运行时进行消息分发和分发绑定; ...... 与之对应实现的就是Objective-C的Runtime机制。 Runtime基本是C和汇编编写的
Jacklin
2018/05/15
9370
图解Objective-C对象模型
MelonTeam
2018/01/04
1.3K0
图解Objective-C对象模型
深入浅出 Runtime(六):相关面试题
目的是将实例和类的相关方法列表以及构建信息区分开来,方便各司其职,符合单一职责设计原则。
师大小海腾
2020/04/16
7170
深入浅出 Runtime(六):相关面试题
Objective-C Runtime:深入理解类与对象
常说Objective-C是一门动态语言,那么问题来了,这个动态表现在那些方面呢?
Jacklin999
2018/09/12
1.3K0
Objective-C Runtime:深入理解类与对象
NSObject头文件解析 / 消息机制 / Runtime解读 (一)
上面是NSObject对象的头文件类部分, 可以看到还有一个NSObject protocol 我们也仔细看看都有什么协议方法@protocol NSObjec
周希
2019/10/15
1.3K0
Runtime学习:面试题狙击
前面两篇文章分别记录了自己学习 Runtime 的一些知识点以及常见的一些应用。之前立下 flag 说准备写三篇关于 Runtime 的文章,于是就有了这篇文章。
iOSSir
2019/06/21
5600
方法的查找流程——快速查找
消息的接收者是objc_super类型,其内部携带了当前方法的调用者——实例对象自身,以及实例对象的父类。
拉维
2021/03/10
6560
方法的查找流程——快速查找
iOS底层原理总结 - 探寻Runtime本质(四)
首先来看一道面试题。 下列代码中Person继承自NSObject,Student继承自Person,写出下列代码输出内容。
xx_Cc
2018/08/02
9350
iOS底层原理总结 - 探寻Runtime本质(四)
iOS开发·runtime原理与实践: 基本知识篇(类,超类,元类,super_class,isa,对象,方法,SEL,IMP)
Tips:苹果公开的源代码在这里可以查,https://opensource.apple.com/tarballs/
陈满iOS
2018/09/10
1.8K1
iOS开发·runtime原理与实践: 基本知识篇(类,超类,元类,super_class,isa,对象,方法,SEL,IMP)
【IOS开发高级系列】Objective-c Runtime专题总结
http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/
江中散人_Jun
2023/10/16
4040
【IOS开发高级系列】Objective-c Runtime专题总结
Objc Runtime 总结
Objc Runtime使得C具有了面向对象能力,在程序运行时创建,检查,修改类、对象和它们的方法。Runtime是C和汇编编写的,这里http://www.opensource.apple.com/source/objc4/可以下到苹果维护的开源代码,GNU也有一个开源的runtime版本,他们都努力的保持一致。苹果官方的Runtime编程指南
用户7451029
2020/06/16
7990
super(二) 以及内存分布
在ViewController 书写以下代码。问是否能编译通过,如果可以输出什么是什么?
老沙
2019/09/28
6340
神奇的Runtime
[receiver message]不是一个简单地方法调用,而是在编译阶段被编译器转化为
Helloted
2022/06/06
6370
神奇的Runtime
iOS开发-Runtime详解
iOS开发-Runtime详解 简介 Runtime 又叫运行时,是一套底层的 C 语言 API,其为 iOS 内部的核心之一,我们平时编写的 OC 代码,底层都是基于它来实现的。比如: [receiver message]; // 底层运行时会被编译器转化为: objc_msgSend(receiver, selector) // 如果其还有参数比如: [receiver message:(id)arg...]; // 底层运行时会被编译器转化为: objc_msgSend(receiver, selec
用户1941540
2018/05/11
7520
iOS运行时(1)——类(Class)和对象(id)
objc_class结构体内,有一个Class类型的变量叫isa,由上面可以知道Class是一个objc_class指针,因此isa是一个objc_class指针,通常如果在一个objc_object(下面会说到)中,也会有一个isa指针,指向的是这个对象所对应的类(objc_class)。 如果是在objc_class中的isa指针,指向的则是这个类的元类(metaClass)
羊羽shine
2019/05/29
1.1K0
iOS运行时Runtime基础
本文主要整理了Runtime的相关知识。对于一个iOS开发者来说,掌握Runtime的重要性早已不言而喻。OC能够作为一门优秀的动态特性语言,在其背后默默工作着的就是Runtime。在网上也看过很多资
梧雨北辰
2018/07/11
9420
动态的Objective-C——关于消息机制与运行时的探讨
    Objective-C是一种很优美的语言,至少在我使用其进行编程的过程中,是很享受他那近乎自然语言的函数命名、灵活多样的方法调用方式以及配合IDE流顺畅快编写体验。Objective-C是扩展与C面向对象的编程语言,然而其方法的调用方式又和大多面向对象语言大有不同,其采用的是消息传递、转发的方式进行方法的调用。因此在Objective-C中对象的真正行为往往是在运行时确定而非在编译时确定,所以Objective-C又被称为是一种运行时的动态语言。
珲少
2018/08/15
8410
动态的Objective-C——关于消息机制与运行时的探讨
iOS-class方法和objc_getClass方法
根据上一篇博客iOS-class、object_getClass、objc_getClass、objc_getMetaClass区别的研究发现,发现主要还是class方法和objc_getClass方法的区别,因此本篇文章主要讲述一下class方法和objc_getClass方法。
全栈程序员站长
2022/09/07
6250
推荐阅读
相关推荐
OC-经典面试题分析(一)OC-经典面试题分析(一)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验