iOS之Runtime(一)彻底搞清类和对象
Objective-C是基于动态运行时类型。C++程序编译时就直接编译成了可令机器读懂的机器语言;用Objective-C编写的程序不能直接编译成机器语言,而是在程序运行时期通过Runtime把程序转为机器语言。Runtime是OC不可缺少的重要一部分。
OC就基于运行时来工作。运行时环境可处理弱类型、函数存在检查工作,会检测注册列表里是否存在对应的函数,类型是否正确,最后确定正确的函数地址,在进行保存寄存器状态,压栈、函数调用等实际操作,确保了OC的灵活性。
理解 实例对象(instance)-》类对象(class object)-》元类对象(meta class) 的概念
我们创建的一个实例对象 底层编译出来其实就是一个结构体(struct objc_object)
, objc_object
这个结构体里有一个isa指针
指向了它的类对象 结构体 objc_class
,类对象里也有一个isa指针
指向Meta Class 元类对象
,其实元类对象也是一个objc_class
的结构体。因此,元类对象也有一个isa指针
,那么它的isa又是指向什么呢?
为了不让这种结构无限延伸下去,OC的设计者让所有的元类对象的isa指针
都统一指向基类的元类对象(如果是从NSObject中继承而来的那么基类的元类对象就是指的NSObject的元类对象)
可直接理解为继承自NSObject
的类,它们的类对象的isa指针
都统一指向NSObject的元类对象,而NSObject的元类对象的isa指针是指向它自己。
1 先看看对应的结构体代码
// 实例对象的结构体
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
// 类对象的结构体
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
Class super_class
const char *name
long version
long info
long instance_size
struct objc_ivar_list *ivars
struct objc_method_list **methodLists
struct objc_cache *cache
struct objc_protocol_list *protocols
}
// id 类型
typedef struct objc_object *id;
// Class 类型
typedef struct objc_class *Class;
在上述objc_class结构体
定义中,主要关注以下几个字段:
-
isa:
在OC中对象的isa指针
指向类,类的isa指针
指向元类。 -
super_class:
isa
用于自省确定所属类,super_class
确定继承关系 。所以super_class
指向该类的父类,如果该类已经是最顶层的根类(如NSObject或NSProxy),则super_class
指向nil
。 -
cache:
如果每次消息来时,都是methodLists
中遍历一遍,性能势必很差。这时,cache
就派上用场了。在每次调用过一个方法后,这个方法就会被缓存到cache列表
中,下次调用的时候runtime就会优先去cache
中查找,如果cache
没有,才去methodLists
中查找方法。这样,对于那些经常用到的方法的调用,但提高了调用的效率。 -
version:
使用这个字段来提供类的版本信息。这对于对象的序列化非常有用,它可以让我们识别出不同类定义版本中实例变量布局的改变。
2 通过下面的图来再次说一下实例对象、类对象、元类对象的关系
![img2.png](https://img.haomeiwen.com/i2470124/b316d5979d7e97bd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)说明: 虚线箭头代表isa指针
(表示指向);实线箭头代表super_class指针
(表示super_class指针的指向)
-
instance实例对象
的isa指针
指向class类对象
,类对象的isa指针
都指向了meta元类对象
,所有的元类对象(不论是Subclass的元类对象、还是Superclass的元类对象)都统一指向了Root基类
的元类对象,而Root基类
的元类对象,它isa指针
指向了它自己。 -
类对象的
super_class指针
指向关系:Subclass类对象-》Superclass类对象-》Rootclass类对象-》nil (即 NSObject的类对象的super_class指针
指向了nil) -
元类对象的
super_class指针
指向关系:Subclass元类对象-》Superclass元类对象-》Rootclass类对象-》Rootclass类对象 (即 NSObject的元类对象的super_class指针
指向了NSObject的类对象)
如果上的解释没明白,看下面的解释
img2.pngD3继承D2,D2继承D1,D1最终继承NSObject。
-
D3实例对象有一个
isa指针
指向D3的类对象 -
D2实例对象有一个
isa指针
指向D2的类对象 -
D1实例对象有一个
isa指针
指向D1的类对象
-
D3的类对象有一个
super_class指针
指向D2的类对象 -
D2的类对象有一个
super_class指针
指向D1的类对象 -
D1的类对象有一个
super_class指针
指向NSObject的类对象 -
NSObject的类对象有一个
super_class指针
指向nil
-
D3的类对象有一个
isa指针
指向D3的元类对象 -
D2的类对象有一个
isa指针
指向D2的元类对象 -
D1的类对象有一个
isa指针
指向D1的元类对象 -
NSObject的类对象有一个
isa指针
指向NSObject的元类对象
-
D3、D2、D1元类对象的
isa指针
都统一指向NSObject的元类对象 -
NSObject元类对象的
isa指针
指向了自身 -
NSObject元类对象的
super_class指针
指向了NSObject的类对象
那么如果我们想获取isa指针的指向对象呢?
-
object_getClass
: 获取isa指针指向的对象 -
class_isMetaClass
:用于判断Class对象是否为元类
Person *p = [[Person alloc] init];
Class p_isa = object_getClass(p); // 获取p对象isa指针指向的类对象
Class pclass_isa = object_getClass(p_isa); // 获取类对象isa指针指向的元类对象
Class pmetaclass_isa = object_getClass(pclass_isa);// 获取元类对象isa指针指向的NSObject的元类对象
// class_isMetaClass 判断是否是元类对象
BOOL b1 = class_isMetaClass(p_isa);
BOOL b2 = class_isMetaClass(pclass_isa);
BOOL b3 = class_isMetaClass(pmetaclass_isa);
NSLog(@"---%@---%zd",p_isa,b1);
NSLog(@"---%@---%zd",pclass_isa,b2);
NSLog(@"---%@---%zd",pmetaclass_isa,b3);
// 打印结果
---Person---0
---Person---1
---NSObject---1
通过代码可以看出
-
一个实例对象通过
object_getClass
方法获取的Class
就是实例对象的isa指针
指向的类对象 -
类对象通过
object_getClass
方法获取的Class
就是类对象的isa指针
指向的元类对象 -
实例对象的
Class
是 类对象 -
类对象的
Class
是 元类对象
获取 superclass
@interface Person : TestObj
@end
#############
Person *p = [[Person alloc] init];
Class p_isa = object_getClass(p); // 获取p对象isa指针指向的类对象
Class pclass_isa = object_getClass(p_isa); // 获取类对象isa指针指向的元类对象
Class pmetaclass_isa = object_getClass(pclass_isa);// 获取元类对象isa指针指向的NSObject的元类对象
Class p_superclass = class_getSuperclass(p_isa); // 获取p的类对象的superclass
Class p_superclass_s = class_getSuperclass(p_superclass); // 获取p的类对象的superclass 的superclass
Class p_superclass_s_s = class_getSuperclass(p_superclass_s); // 获取p的类对象的superclass 的superclass 的superclass
NSLog(@"---%@---%@",p_isa,p_superclass);
NSLog(@"---%@---%@",pclass_isa,p_superclass_s);
NSLog(@"---%@---%@",pmetaclass_isa,p_superclass_s_s);
//打印结果
---Person---TestObj
---Person---NSObject
--NSObject---(null)
神经病院objc runtime入院考试
(1) 下面的代码输出什么?
@implementation Son : Father
- (id)init {
self = [super init];
if (self) {
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
(2) 下面代码的结果?
BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
// 上面的题换个写法
Class c1 = [NSObject class];
Class s1 = [Sark class];
BOOL res1 = [c1 isKindOfClass:[NSObject class]];
BOOL res2 = [c1 isMemberOfClass:[NSObject class]];
BOOL res3 = [s1 isKindOfClass:[Sark class]];
BOOL res4 = [s1 isMemberOfClass:[Sark class]];
// 因为 NSObject meta class –super -> NSObject class
NSObject的元类对象的super_class指向了NSObject class
相当于c1 是 NSObject class 子类的实例 所以第一个为真
所以第二个c1是不是NSObject class 当前类的实例就为假了
同样Sark的元类对象的super_class 最终指向了 NSObject class 所以s1 并不是 Sark 或者其子类的实例 ,所以 第三和第四都为假
(3) 下面的代码会?Compile Error / Runtime Crash / NSLog…?
@interface NSObject (Sark)
+ (void)foo;
@end
@implementation NSObject (Sark)
- (void)foo {
NSLog(@"IMP: -[NSObject (Sark) foo]");
}
@end
// 测试代码
[NSObject foo];
[[NSObject new] foo];
(4) 下面的代码会?Compile Error / Runtime Crash / NSLog…?
@interface Sark : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation Sark
- (void)speak {
NSLog(@"my name's %@", self.name);
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
id cls = [Sark class];
void *obj = &cls;
[(__bridge id)obj speak];
}
@end
答案
-(BOOL) isKindOfClass: classObj判断是否是这个类或者这个类的子类的实例
-(BOOL) isMemberOfClass: classObj 判断是否是这个类的实例
-
(1) Son / Son 因为super为编译器标示符,向super发送的消息被编译成objc_msgSendSuper,但仍以self作为reveiver
-
(2) YES / NO / NO / NO <NSObject>协议有一套类方法的隐藏实现,所以编译运行正常;由于NSObject meta class的父类为NSObject class,所以只有第一句为YES
-
(3) 编译运行正常,两行代码都执行-foo。 [NSObject foo]方法查找路线为 NSObject meta class –super-> NSObject class,和第二题知识点很相似。
-
(4)编译运行正常,输出ViewController中的self对象。 编译运行正常,调用了-speak方法,由于
id cls = [Sark class];
void *obj = &cls;
obj已经满足了构成一个objc对象的全部要求(首地址指向ClassObject),遂能够正常走消息机制;
由于这个人造的对象在栈上,而取self.name的操作本质上是self指针在内存向高位地址偏移(32位下一个指针是4字节),按viewDidLoad执行时各个变量入栈顺序从高到底为(self, _cmd, self.class, self, obj)(前两个是方法隐含入参,随后两个为super调用的两个压栈参数),遂栈低地址的obj+4取到了self。
类与对象相关的操作函数
runtime
提供了大量的函数
来操作类与对象。
- 类的操作方法大部分是以class为前缀
- 对象的操作方法大部分是以objc或object_为前缀
1 类相关操作函数
-
类名(name)
// 获取类的类名 const char * class_getName ( Class cls ); 如果传入的cls为Nil,则返回一个空字符串。
-
父类(super_class) 和是否为元类(meta-class)
// 获取类的父类 Class class_getSuperclass ( Class cls ); 当cls为Nil或者cls为根类时,返回Nil。 不过通常我们可以使用NSObject类的superclass方法来达到同样的目的
-
是否为元类(meta-class)
// 判断给定的Class是否是一个元类 BOOL class_isMetaClass ( Class cls ); 如果是cls是元类,则返回YES; 如果否或者传入的cls为Nil,则返回NO。
-
实例变量大小(instance_size)
// 获取实例大小 size_t class_getInstanceSize ( Class cls );
-
成员变量(ivars)及属性
// 获取类中指定名称实例成员变量的信息 Ivar class_getInstanceVariable ( Class cls, const char *name ); // 获取类成员变量的信息 Ivar class_getClassVariable ( Class cls, const char *name ); // 添加成员变量 BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types ); // 获取整个成员变量列表 Ivar * class_copyIvarList ( Class cls, unsigned int *outCount ); // 获取指定的属性 objc_property_t class_getProperty ( Class cls, const char *name ); // 获取属性列表 objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount ); // 为类添加属性 BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount ); // 替换类的属性 void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
-
方法(methodLists)
// 添加方法 BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types ); // 获取实例方法 Method class_getInstanceMethod ( Class cls, SEL name ); // 获取类方法 Method class_getClassMethod ( Class cls, SEL name ); // 获取所有方法的数组 Method * class_copyMethodList ( Class cls, unsigned int *outCount ); // 替代方法的实现 IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types ); // 返回方法的具体实现 IMP class_getMethodImplementation ( Class cls, SEL name ); IMP class_getMethodImplementation_stret ( Class cls, SEL name ); // 类实例是否响应指定的selector BOOL class_respondsToSelector ( Class cls, SEL sel );
-
协议(objc_protocol_list)
// 添加协议 BOOL class_addProtocol ( Class cls, Protocol *protocol ); // 返回类是否实现指定的协议 BOOL class_conformsToProtocol ( Class cls, Protocol *protocol ); // 返回类实现的协议列表 Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );
2 实例相关的操作函数
-
对实例对象进行操作的函数
// 返回指定对象的一份拷贝 id object_copy ( id obj, size_t size ); // 释放指定对象占用的内存 id object_dispose ( id obj ); // 举例子 NSObject *a = [[NSObject alloc] init]; id newB = object_copy(a, class_getInstanceSize(MyClass.class)); object_setClass(newB, MyClass.class); object_dispose(a);
-
操作对象的实例变量
// 修改类实例的实例变量的值 Ivar object_setInstanceVariable ( id obj, const char *name, void *value ); // 获取对象实例变量的值 Ivar object_getInstanceVariable ( id obj, const char *name, void **outValue ); // 返回指向给定对象分配的任何额外字节的指针 void * object_getIndexedIvars ( id obj ); // 返回对象中实例变量的值 id object_getIvar ( id obj, Ivar ivar ); // 设置对象中实例变量的值 void object_setIvar ( id obj, Ivar ivar, id value );
-
操作对象的类
// 返回给定对象的类名 const char * object_getClassName ( id obj ); // 返回对象的类 Class object_getClass ( id obj ); // 设置对象的类 Class object_setClass ( id obj, Class cls );
三、什么是‘关联对象’?
所谓“关联对象” 说白了也就是一个对象。 就是把一个类的实例(也叫对象)
通过一个**唯一的key**
动态绑定到另外一个对象
上的过程叫做关联对象。
举例说明:
我们知道一个类的实例(对象)
可以作为另外一个类的属性
出现。比如有一个Dog类和Person类,而Person下有一个属性是dog。
@interface Person ()
@property (nonatomic, strong) Dog *dog;
@end
通俗的理解就是: 可以给一个对象关联许多其它对象,关联的这些对象通过“key”来区分。
怎么进行关联那?
答: 需要用到 Runtime(运行时)
里提供的两个方法。
- objc_setAssociatedObject
- objc_getAssociatedObject
/* Returns the value associated with a given object for a given key.
此方法根据给定的键(key)从某对象(object)中获取相应的关联对象值 */
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key);
/* Sets an associated value for a given object using a given key and association policy.
此方法用给定的键(key)和策略(policy)给某对象(object)设置关联对象值(value) */
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
参数说明:
参数 | 说明 |
---|---|
id object | 给哪个对象 设置关联对象 (比如这里的 Person类的实例对象) |
const void *key | 指定关联对象唯一的key(之后通过该key来 获取) |
id value | 指定关联的对象 (比如dog) |
objc_AssociationPolicy | 指定内存管理策略(枚举) |
以下几种内存管理策略:
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
};
说明:
如果想要解除关联对象
,我们不需要
去主动调用runtime提供的removeAssociated函数,可以使用setAssociatedObject置为nil来解除关联对象。
参考文档: