- Runtime 简介
- Runtime 消息机制和相关函数
- Runtime 三次转发流程
- Runtime 应用
- Runtime 面试题
Objective-C
是一个动态语言,这意味着它不只须要一个编译器,也须要一个运行时系统来动态得建立类和对象、进行消息传递和转发。Runtime
是 Objective-C
面向对象和动态机制的基石,能够从系统层面解决一些设计或技术问题。它基本是用 C
和汇编写的,属于1个 C
语言库,包含了不少底层的 C
语言 API
,如跟类、成员变量、方法相关的API。它的核心是 - 消息传递 ( Messaging
)。html
isa
指针定位对象的类,并以此为起点肯定被调用的方法,方法和消息是动态绑定的。Runtime
交互Objective-C
从三种不一样的层级上与 Runtime
系统进行交互:
Objective-C
源代码Foundation
框架的 NSObject
类定义的方法runtime
函数的直接调用NSProxy
Cocoa
中大多数类都继承于 NSObject
类,也就天然继承了它的方法。最特殊的例外是 NSProxy
,它是个抽象超类,它实现了一些消息转发有关的方法,能够经过继承它来实现一个其余类的替身类或是虚拟出一个不存在的类。selector
是否是要忽略的。好比 Mac OS X 开发,有了垃圾回收就不理会 retain
, release
这些函数了。target
是否是 nil
对象。Objective-C
的特性是容许对一个 nil
对象执行任何一个方法不会 Crash
,由于会被忽略掉。IMP
,先从 cache
里面找,完了找获得就跳到对应的函数去执行。cache
找不到就找一下方法分发表。NSObject
类为止。doesNotRecognizeSelector:
方法报 unrecognized selector
错。[obj eat]
,编译器转成消息发送objc_msgSend(obj, eat)
,Runtime
时执行的流程是这样的:
obj
的 isa
指针找到它的 class
class
的 method list
找 eat
class
中没找到 eat
,继续往它的 superclass
中找,一旦找到 eat
这个函数,就去执行它的实现IMP
<objc/runtime.h>
<objc/message.h>
objc_object
objc_class
Meta Class
objc_method
objc_selector
objc_cache
objc_category
id objc_msgSend ( id self, SEL op, ... );
复制代码
objc_msgSend
第一个参数类型为id,是一个指向类实例的指针typedef struct objc_object *id;
复制代码
objc_selector
)objc_msgSend
第二个参数类型为SEL,它是 selector
在 Objective-C
中的表示类型( Swift
中是 Selector
类)。selector
是方法选择器,能够理解为区分方法的 ID
,而这个 ID
的数据结构是SEL
。能够看到selector
是SEL
的一个实例typedef struct objc_selector *SEL;
复制代码
@property SEL selector;
复制代码
其实它就是个映射到方法的C字符串,你能够用 Objc
编译器命令 @selector()
或者 Runtime
系统的 sel_registerName
函数来得到一个 SEL
类型的方法选择器。C
代码的时候,常常会用到函数重载,就是函数名相同,参数不一样,可是这在Objc
中是行不通的,由于selector
只记了 method
的 name
,没有参数,因此无法区分不一样的 method
。[[Person alloc] init]
objc_msgSend(objc_msgSend("Person" , "alloc"), "init")
objc_msgSend
第一个参数类型为id
指向类实例的指针,即objc_object
ios
objc_object
结构体包含一个 isa
指针,类型为 isa_t
联合体。根据 isa
指向对象所属的类。isa
这里还涉及到 tagged pointer 等概念。由于 isa_t
使用 union
实现,因此可能表示多种形态,既能够当成是指针,也能够存储标志位置。git
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
... 此处省略其余方法声明
}
复制代码
注意: isa
指针不老是指向实例对象所属的类,不能依靠它来肯定类型,而是应该用 class
方法来肯定实例对象的类。由于 KVO
的实现机理就是将被观察对象的 isa
指针指向一个中间类而不是真实的类,这是一种叫作 isa-swizzling
的技术。github
Objective-C
类是由 Class
类型来表示的,它其实是一个指向 objc_class
结构体的指针。面试
typedef struct objc_class *Class;
复制代码
objc/runtime.h
中 objc_class
结构体的定义以下:objective-c
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
复制代码
结构体里保存了指向父类的指针、类的名字、版本、实例大小、实例变量列表、方法列表、缓存、遵照的协议列表等。json
对象在内存中的排布能够当作一个结构体,该结构体的大小并不能动态变化,因此没法在运行时动态给对象增长成员变量。相对的,对象的方法定义都保存在类的可变区域中。以下图所示为 Class
的描述信息,其中 methodList
为可访问类中定义的方法的指针的指针,经过修改该指针所指向的指针的值,咱们能够实现为类动态增长方法实现。数组
objc_class
继承于 objc_object
,也就是说一个 Objective-C
类自己同时也是一个对象,咱们称之为类对象,类对象就是一个结构体 struct objc_class
,这个结构体存放的数据称为元数据。为了处理类和对象的关系,runtime
库建立了一种叫作元类 (Meta Class
) 的东西,类对象所属类型就叫作元类,它用来表述类对象自己所具有的元数据。类方法就定义于此处,由于这些方法能够理解成类对象的实例方法。每一个类仅有一个类对象,而每一个类对象仅有一个与之相关的元类。缓存
当你发出一个相似 [NSObject alloc]
的消息时,你事实上是把这个消息发给了一个类对象 (Class Object
) ,这个类对象必须是一个元类的实例,而这个元类同时也是一个根元类 (root meta class
) 的实例。全部的元类最终都指向根元类为其超类。全部的元类的方法列表都有可以响应消息的类方法。因此当 [NSObject alloc]
这条消息发给类对象的时候,objc_msgSend()
会去它的元类里面去查找可以响应消息的方法,若是找到了,而后对这个类对象执行方法调用。bash
元类(Meta Class
)是一个类对象的类。 在上面咱们提到,全部的类自身也是一个对象,咱们能够向这个对象发送消息(即调用类方法)。 为了调用类方法,这个类的 isa
指针必须指向一个包含这些类方法的一个 objc_class
结构体,这就引出了 meta-class
的概念。
类对象中的元数据存储的都是如何建立一个实例的相关信息,那么类对象和类方法应该从哪里建立呢? 就是从 isa
指针指向的结构体建立,类对象的 isa
指针指向的咱们称之为元类(metaclass
),元类中保存了建立类对象以及类方法所需的全部信息。
Class
都有一个 isa
指针指向一个惟一的 Meta Class
Meta Class
的 isa
指针都指向最上层的 Meta Class
(图中的 NSObject
的 Meta Class
)Meta Class
的 isa
指针指向本身,造成一个回路Meta Class
的 super_class
指针指向它本来 Class
的 super_class
的 Meta Class
。可是最上层的 Meta Class
的 super_class
指向 NSObject Class
自己NSObject Class
的 super_class
为 nil
,也就是它没有超类
objc/runtime.h
:
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
复制代码
SEL method_name
: 方法名,相同名字的方法即便在不一样类中定义,它们的方法选择器也相同char *method_types
: 方法类型,是个char指针,其实存储着方法的参数类型和返回值类型IMP method_imp
: 方法实现,本质上是一个函数指针在 iOS
的 Runtime
中,Method
经过 selector
和 IMP
两个属性,实现了快速查询方法及实现,相对提升了性能,又保持了灵活性
cache
为方法调用的性能进行优化。每一个消息都须要遍历一次 isa
指向的类的方法列表(objc_method_list
),这样效率过低了。Runtime
系统会把被调用的方法存到 cache
中( method_name
做为key
,method_imp
做为value
)。下次查找的时候会优先在 cache
中查找,效率更高。
objc_cache
是存在 objc_class
结构体中的。
cache_t
中 _buckets
、_mask
和 _occupied
:
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
... 省略其余方法
}
复制代码
bucket_t
中存储了 指针
与 IMP
的键值对:
struct bucket_t {
private:
cache_key_t _key;
IMP _imp;
public:
inline cache_key_t key() const { return _key; }
inline IMP imp() const { return (IMP)_imp; }
inline void setKey(cache_key_t newKey) { _key = newKey; }
inline void setImp(IMP newImp) { _imp = newImp; }
void set(cache_key_t newKey, IMP newImp);
};
复制代码
Category
为现有的类提供了拓展性,它是 category_t
一个指向分类的结构体的指针。
typedef struct category_t *Category;
复制代码
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
};
复制代码
name:是指 class_name 而不是 category_name。
cls:要扩展的类对象,编译期间是不会定义的,而是在Runtime阶段经过name对 应到对应的类对象。
instanceMethods:category中全部给类添加的实例方法的列表。
classMethods:category中全部添加的类方法的列表。
protocols:category实现的全部协议的列表。
instanceProperties:表示Category里全部的properties,这就是咱们能够经过objc_setAssociatedObject和objc_getAssociatedObject增长实例变量的缘由,不过这个和通常的实例变量是不同的。
复制代码
从上边category_t
的结构体中能够看出,分类中能够添加实例方法,类方法,甚至能够实现协议,添加属性,不能够添加成员变量。
Ivar
是一种表明类中实例变量的类型。
typedef struct ivar_t *Ivar;
复制代码
ivar_t
:
struct ivar_t {
int32_t *offset;
const char *name;
const char *type;
// alignment is sometimes -1; use alignment() instead
uint32_t alignment_raw;
uint32_t size;
uint32_t alignment() const {
if (alignment_raw == ~(uint32_t)0) return 1U << WORD_SHIFT;
return 1 << alignment_raw;
}
};
复制代码
class_copyIvarList
函数获取的不只有实例变量,还有属性。但会在本来的属性名前加上一个下划线。
@property
标记了类中的属性,它是一个指向objc_property
结构体的指针:
typedef struct property_t *objc_property_t;
复制代码
能够经过 class_copyPropertyList
和 protocol_copyPropertyList
方法来获取类和协议中的属性:
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)
复制代码
返回类型为指向指针的指针,由于属性列表是个数组,每一个元素内容都是一个 objc_property_t
指针,而这两个函数返回的值是指向这个数组的指针。
class_copyIvarList
和 class_copyPropertyList
对比:
- (void)runtimeGetPropertyList {
id RuntimeExploreInfo = objc_getClass("RuntimeExploreInfo");
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(RuntimeExploreInfo, &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
fprintf(stdout, "runtimeGetPropertyList---%s %s\n", property_getName(property), property_getAttributes(property));
}
}
- (void)runtimeGetIvarList {
id RuntimeExploreInfo = objc_getClass("RuntimeExploreInfo");
unsigned int numIvars = 0;
Ivar *ivars = class_copyIvarList(RuntimeExploreInfo, &numIvars);
for(int i = 0; i < numIvars; i++) {
Ivar thisIvar = ivars[i];
const char *type = ivar_getTypeEncoding(thisIvar);
NSString *stringType = [NSString stringWithCString:type encoding:NSUTF8StringEncoding];
if (![stringType hasPrefix:@"@"]) {
continue;
}
fprintf(stdout, "runtimeGetIvarList---%s\n", ivar_getName(thisIvar));
}
}
复制代码
打印结果:
就是指向最终实现程序的内存地址的指针。
typedef void (*IMP)(void /* id, SEL, ... */ );
复制代码
它就是一个函数指针,这是由编译器生成的。当你发起一个 Objective-C
消息以后,最终它会执行的那段代码,就是由这个函数指针指定的。而 IMP 这个函数指针就指向了这个方法的实现。
你会发现 IMP
指向的方法与 objc_msgSend
函数类型相同,参数都包含 id
和 SEL
类型。每一个方法名都对应一个 SEL
类型的方法选择器,而每一个实例对象中的 SEL
对应的方法实现确定是惟一的,经过一组 id
和 SEL
参数就能肯定惟一的方法实现地址;反之亦然。
进行一次发送消息会在相关的类对象中搜索方法列表,若是找不到则会沿着继承树向上一直搜索直到继承树根部(一般为 NSObject
),若是仍是找不到而且消息转发都失败了就回执行 doesNotRecognizeSelector:
方法报 unrecognized selector
错。
+resolveInstanceMethod:
, +resolveClassMethod:
forwardingTargetForSelector
methodSignatureForSelector:
, forwardInvocation:
Objective-C
运行时会调用 +resolveInstanceMethod:
或者 +resolveClassMethod:
,让你有机会提供一个函数实现。若是你添加了函数并返回YES, 那运行时系统就会从新启动一次消息发送的过程。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//执行foo函数
[self performSelector:@selector(foo:)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(foo:)) {//若是是执行foo函数,就动态解析,指定新的IMP
class_addMethod([self class], sel, (IMP)fooMethod, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void fooMethod(id obj, SEL _cmd) {
NSLog(@"Doing foo");//新的foo函数
}
复制代码
若是resolve
方法返回 NO
,运行时就会移到下一步 :forwardingTargetForSelector
若是目标对象实现了 -forwardingTargetForSelector:
,Runtime
这时就会调用这个方法,给你把这个消息转发给其余对象的机会。
Controller
:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self performSelector:@selector(runtimeMessageTest)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return YES; // 返回YES,进入下一步转发
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(runtimeMessageTest)) {
return [RuntimeExploreInfo new]; // 返回RuntimeExploreInfo对象,让RuntimeExploreInfon对象接收这个消息
}
return [super forwardingTargetForSelector:aSelector];
}
复制代码
RuntimeExploreInfo
:
#import "RuntimeExploreInfo.h"
@implementation RuntimeExploreInfo
- (void)runtimeMessageTest {
NSLog(@"runtimeMessageTest---");
}
@end
复制代码
经过 forwardingTargetForSelector
把当前 Controller
的方法转发给了 RuntimeExploreInfo
去执行。
若是在上一步还不能处理未知消息,则惟一能作的就是启用完整的消息转发机制了。 首先它会发送 -methodSignatureForSelector:
消息得到函数的参数和返回值类型。若是 -methodSignatureForSelector:
返回 nil
,Runtime
则会发出 -doesNotRecognizeSelector:
消息,程序这时也就挂掉了。若是返回了一个函数签名,Runtime
就会建立一个 NSInvocation
对象并发送 -forwardInvocation:
消息给目标对象。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self performSelector:@selector(runtimeMessageTest)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return YES; // 返回YES,进入下一步转发
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
return nil; // 返回nil,进入下一步转发
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([NSStringFromSelector(aSelector) isEqualToString:@"runtimeMessageTest"]) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"]; // 签名,进入forwardInvocation
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = anInvocation.selector;
RuntimeExploreInfo *p = [RuntimeExploreInfo new];
if([p respondsToSelector:sel]) {
[anInvocation invokeWithTarget:p];
}else {
[self doesNotRecognizeSelector:sel];
}
}
复制代码
咱们实现了完整的转发。经过签名,Runtime
生成了一个对象 anInvocation
,发送给了 forwardInvocation
,咱们在 forwardInvocation
方法里面让 RuntimeExploreInfo
对象去执行了 runtimeMessageTest
函数。
Objective-C Associated Objects
)给分类增长属性Method Swizzling
)方法添加和替换KVO
实现NSCoding
的自动归档和自动解档MJExtension
、 YYModel
)Objective-C Associated Objects
)给分类增长属性RuntimeExploreInfo+RuntimeAddProperty.h
添加了 phoneNum
属性
#import "RuntimeExploreInfo+RuntimeAddProperty.h"
#import "objc/runtime.h"
@implementation RuntimeExploreInfo (RuntimeAddProperty)
static char kPhoneNumKey;
- (void)setPhoneNum:(NSString *)phoneNum {
objc_setAssociatedObject(self, &kPhoneNumKey, phoneNum, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)phoneNum {
return objc_getAssociatedObject(self, &kPhoneNumKey);
}
@end
复制代码
- (void)runtimeAddProperty {
RuntimeExploreInfo *test = [RuntimeExploreInfo new];
test.phoneNum = @"12342424242";
NSLog(@"RuntimeAddProperty---%@", test.phoneNum);
}
复制代码
Method Swizzling
)方法添加和替换和 KVO
实现/**
class_addMethod(Class _Nullable __unsafe_unretained cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)
cls 被添加方法的类
name 添加的方法的名称的SEL
imp 方法的实现。该函数必须至少要有两个参数,self,_cmd
类型编码
*/
class_addMethod([self class], sel, (IMP)fooMethod, "v@:");
复制代码
class_replaceMethod
替换类方法的定义method_exchangeImplementations
交换两个方法的实现method_setImplementation
设置一个方法的实现class_replaceMethod
试图替换一个不存在的方法时候,会调用 class_addMethod
为该类增长一个新方法+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewDidLoad);
SEL swizzledSelector = @selector(runtimeReplaceViewDidLoad);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
//judge the method named swizzledMethod is already existed.
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
// if swizzledMethod is already existed.
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)runtimeReplaceViewDidLoad {
NSLog(@"替换的方法");
//[self runtimeReplaceViewDidLoad];
}
复制代码
swizzling应该只在 +load
中执行一次( dispatch_once
)完成。在 Objective-C
的运行时中,每一个类有两个方法都会自动调用。+load
是在一个类被初始装载时调用,+initialize
是在应用第一次调用该类的类方法或实例方法前调用的。两个方法都是可选的,而且只有在方法被实现的状况下才会被调用。Apple 使用了 isa-swizzling
来实现 KVO
。当观察对象A时,KVO
机制动态建立一个新的名为:NSKVONotifying_A
的新类,该类继承自对象A的本类,且 KVO
为 NSKVONotifying_A
重写观察属性的 setter
方法,setter
方法会负责在调用原 setter
方法以前和以后,通知全部观察对象属性值的更改状况。
NSKVONotifying_A
类剖析
NSLog(@"self->isa:%@",self->isa);
NSLog(@"self class:%@",[self class]);
复制代码
在创建KVO监听前,打印结果为:
self->isa:A
self class:A
复制代码
在创建KVO监听以后,打印结果为:
self->isa:NSKVONotifying_A
self class:A
复制代码
子类setter方法剖析:
KVO
的键值观察通知依赖于 NSObject
的两个方法: willChangeValueForKey:
和 didChangeValueForKey:
,在存取数值的先后分别调用 2 个方法: 被观察属性发生改变以前,willChangeValueForKey:
被调用,通知系统该 keyPath
的属性值即将变动;当改变发生后, didChangeValueForKey:
被调用,通知系统该keyPath
的属性值已经变动;以后, observeValueForKey:ofObject:change:context:
也会被调用。且重写观察属性的 setter
方法这种继承方式的注入是在运行时而不是编译时实现的。
KVO 为子类的观察者属性重写调用存取方法的工做原理在代码中至关于: - (void)setName:(NSString *)newName { [self willChangeValueForKey:@"name"]; //KVO 在调用存取方法以前总调用 [super setValue:newName forKey:@"name"]; //调用父类的存取方法 [self didChangeValueForKey:@"name"]; //KVO 在调用存取方法以后总调用 }
原理描述:用 runtime
提供的函数遍历 Model
自身全部属性,并对属性进行 encode
和 decode
操做。
核心方法:在Model的基类中重写方法:
- (id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
unsigned int outCount;
Ivar * ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int outCount;
Ivar * ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[aCoder encodeObject:[self valueForKey:key] forKey:key];
}
}
复制代码
原理描述:用runtime提供的函数遍历Model自身全部属性,若是属性在json中有对应的值,则将其赋值。
核心方法:在NSObject的分类中添加方法
- (instancetype)initWithDict:(NSDictionary *)dict {
if (self = [self init]) {
//(1)获取类的属性及属性对应的类型
NSMutableArray * keys = [NSMutableArray array];
NSMutableArray * attributes = [NSMutableArray array];
unsigned int outCount;
objc_property_t * properties = class_copyPropertyList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
objc_property_t property = properties[i];
//经过property_getName函数得到属性的名字
NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
[keys addObject:propertyName];
//经过property_getAttributes函数能够得到属性的名字和@encode编码
NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
[attributes addObject:propertyAttribute];
}
//当即释放properties指向的内存
free(properties);
//(2)根据类型给属性赋值
for (NSString * key in keys) {
if ([dict valueForKey:key] == nil) continue;
[self setValue:[dict valueForKey:key] forKey:key];
}
}
return self;
}
复制代码
Self & Super
@implementation Son : Father
- (id)init
{
self = [super init];
if (self)
{
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
复制代码
答案:都输出 Son
解惑:这个题目主要是考察关于 objc
中对 self
和 super
的理解。
self
是类的隐藏参数,指向当前调用方法的这个类的实例。而 super
是一个 Magic Keyword
, 它本质是一个编译器标示符,和 self
是指向的同一个消息接受者。上面的例子无论调用 [self class]
仍是 [super class]
,接受消息的对象都是当前 Son *xxx 这个对象。而不一样的是,super
是告诉编译器,调用 class
这个方法时,要去父类的方法,而不是本类里的。
当使用 self
调用方法时,会从当前类的方法列表中开始找,若是没有,就从父类中再找;
而当使用 super
时,则从父类的方法列表中开始找。而后调用父类的这个方法。
当调用 [self class]
时,实际先调用的是 objc_msgSend
函数,第一个参数是 Son
当前的这个实例,而后在 Son
这个类里面去找 - (Class)class
这个方法,没有,去父类 Father
里找,也没有,最后在 NSObject
类中发现这个方法。而 - (Class)class
的实现就是返回 self
的类别,故上述输出结果为 Son
。
当调用 [super class]
时,会转换成 objc_msgSendSuper
函数。第一步先构造 objc_super
结构体,结构体第一个成员就是 self
。第二个成员是 (id)class_getSuperclass(objc_getClass(“Son”))
, 实际该函数输出结果为 Father
。第二步是去 Father
这个类里去找 - (Class)class
,没有,而后去 NSObject
类去找,找到了。最后内部是使用 objc_msgSend(objc_super->receiver
, @selector(class))
去调用,此时已经和 [self class]
调用相同了,故上述输出结果仍然返回 Son
。
Object
& Class
& Meta Clas
@interface Sark : NSObject
@end
@implementation Sark
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
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]];
NSLog(@"%d %d %d %d", res1, res2, res3, res4);
}
return 0;
}
复制代码
答案: 1 0 0 0
咱们看到在 Objective-C
的设计哲学中,一切都是对象。Class在设计中自己也是一个对象。而这个 Class
对象的对应的类,咱们叫它 Meta Class
。即 Class
结构体中的 isa
指向的就是它的 Meta Class
。
Meta Class
理解为 一个 Class
对象的 Class
。简单的说:
当咱们发送一个消息给一个 NSObject
对象时,这条消息会在对象的类的方法列表里查找;
当咱们发送一个消息给一个类时,这条消息会在类的 Meta Class
的方法列表里查找
消息 和 Category
@interface NSObject (Sark)
+ (void)foo;
@end
@implementation NSObject (Sark)
- (void)foo
{
NSLog(@"IMP: -[NSObject(Sark) foo]");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
[NSObject foo];
[[NSObject new] foo];
}
return 0;
}
复制代码
答案:
IMP: -[NSObject(Sark) foo]
IMP: -[NSObject(Sark) foo]
解释:
objc runtime
加载完后,NSObject
的 Sark Category
被加载。而 NSObject
的 Sark Category
的头文件 + (void)foo
并无实质参与到工做中,只是给编译器进行静态检查,全部咱们编译上述代码会出现警告,提示咱们没有实现 + (void)foo
方法。而在代码编译中,它已经被注释掉了。Class
的 method list
的方法是 - (void)foo
,它是一个实例方法,因此加入到当前类对象 NSObject
的方法列表中,而不是 NSObject
Meta class
的方法列表中。[NSObject foo]
时,咱们看下整个 objc_msgSend
的过程:objc_msgSend
第一个参数是 (id)objc_getClass("NSObject")
,得到 NSObject Class
的对象。Meta Class
的方法列表中找,咱们在 load Category
方法时加入的是 - (void)foo
实例方法,因此并不在 NSOBject
Meta Class
的方法列表中super class
中找,NSObject
Meta Class
的 super class
是 NSObject
自己。因此,这个时候咱们可以找到 - (void)foo
这个方法。[[NSObject new] foo]
,咱们看下整个 objc_msgSend
的过程:[NSObject new]
生成一个 NSObject
对象。直接在该对象的类( NSObject
)的方法列表里找。可以找到,因此正常输出结果。成员变量与属性
@interface Sark : NSObject
@property (nonatomic, copy) NSString *name;
@end
@implementation Sark
- (void)speak
{
NSLog(@"my name is %@", self.name);
}
@end
@interface Test : NSObject
@end
@implementation Test
- (instancetype)init
{
self = [super init];
if (self) {
id cls = [Sark class];
void *obj = &cls;
[(__bridge id)obj speak];
}
return self;
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
[[Test alloc] init];
}
return 0;
}
复制代码
答案: my name is