文章目录
一、简介
二、isa 指针
二、Class 结构
1、class_rw_t
2、class_ro_t
3、method_t
4、cache_t
三、消息机制
1、简介
2、消息发送
3、动态方法解析
4、消息转发
四、关于 super 关键字
1、一道经典面试题
2、objc_msgSendSuper
五、isKindOfClass 和 isMemberOfClass
1、实现
2、[Person isKindOfClass:[NSObject class]]
六、runtime 常用 API
1、类相关
2、成员变量相关
3、属性相关(参照成员变量)
4、方法相关
七、具体应用
1、遍历访问成员变量(修改 textfield的占位属性、字典转模型、解归档)
2、关联对象
3、交换方法实现(hook)
4、利用消息转发机制解决方法找不到的异常
一、简介
OC
是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时才进行,OC 动态性都是由 runtime
的 API 来支撑的。runtime
的一套 C 语言的 API,封装了很多动态性相关的函数。平常编写的 OC 代码,底层都是转化成 Runtime API 进行调用的。下载链接
二、isa 指针
要想学习 runtime
,首先要了解他一些底层常用的数据结构,例如,isa
指针。在arm64
之前,isa
就是一个普通的指针,存储这 class、meta-class
对象的内存地址。从 arm64
架构开始,对 isa
进行优化,变成一个共用体(union
)结构,用一个 64 位内存数据来存储(位域)更多的数据,其中使用 33 位来存储具体的地址值。
union isa_t
{
Class cls;
uintptr_t bits;
//此结构体仅为了增加可读性,纯属摆设
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33;
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
};
nonpointer
为 0 ,代表普通指针,直接存储 class、meta-class对象的内存地址
为1,代表优化过,使用位域存储更多的信息
has_assoc
是否有设置过关联对象,如果未否,释放得会更快
has_cxx_dtor
是否有析构函数(类似 dealloc
),如果没有,会释放得更快
shiftcls
类对象、元类对象的地址值
weakly_referenced
是否被弱引用指向过
deallocating
对象是否正在释放
extra_rc
(rc是 retainCount的简写)
里面存储的值是引用计数减1
has_sidetable_rc
引用计数器是否过大无法存储在 isa
中,如果为 1,会存储在 一个 叫 sidetable
类的属性中。
对象在释放的时候,调用的是 objc_destructInstance
这个方法,在 objc-runtime-new.mm
中
/***********************************************************************
* objc_destructInstance
//销毁对象
* Destroys an instance without freeing memory.
* Calls C++ destructors.
* Calls ARC ivar cleanup.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is nil.
**********************************************************************/
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
//如果有析构函数
if (cxx) object_cxxDestruct(obj);
//如果有关联对象
if (assoc) _object_remove_assocations(obj);
//弱引用
obj->clearDeallocating();
}
return obj;
}
二、Class
结构
类对象和元类对象在内存中的结构是一样的,都遵循如下
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // 方法缓存
class_data_bits_t bits; // 获取具体的类信息
}
1、class_rw_t
通过调用 bits.data()
可获得 class_rw_t
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro; //只读信息
method_array_t methods; //方法列表
property_array_t properties; //属性列表
protocol_array_t protocols; //协议列表
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
}
其中,只读信息const class_ro_t *ro;
包括如下:
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; //内存中对象占用多少
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name; //类名
method_list_t * baseMethodList; //初始信息
protocol_list_t * baseProtocols; //初始信息
const ivar_list_t * ivars; //成员变量列表
const uint8_t * weakIvarLayout;
property_list_t *baseProperties; //初始信息
method_list_t *baseMethods() const {
return baseMethodList;
}
};
class_rw_t
里面的 methods、properties、protocols
是二维数组,可读可写,包含类的初始内容、分类的内容
注意: 设计成二维数组的好处是可以比较快捷的扩展,例如 methods,分类方法在运行时合并按照编译顺序插入二维数组最前面,指针指向分类的方法列表。
2、class_ro_t
而对于 class_ro_t
里面的baseMethodList、baseProtocols、ivars、baseProperties
是一维数组,只读,包含了类的初始内容(注意: 这里不包含分类,简单理解成 在 interface
里声明的东西)
3、method_t
method_t 是对方法/函数的封装,结构如下
struct method_t {
SEL name; //选择器,函数名
const char *types; //编码(返回值类型、参数类型) 其实就是 @encode 这个方法
IMP imp; //指向函数的指针(函数地址)其实,就是函数的实现和地址值
};
关于编码,其实调用的是 @encode()
这个函数,具体参照
例如:
编码为 :i24@0:8i16f20
函数的返回值为 int
类型,所有参数所占字节数为 24 (8 + 8 + 4 + 4 = 24)
第 一 个参数是 id
类型(消息接受者),占 8 字节,从 0 开始
第 二 个参数是 SEL
类型(_cmd),占 8 字节,从 8 开始
第 三 个参数是int
类型, 占 4 字节, 从 16 开始
第 四 个参数是 float
类型,占 4 字节,从 20 开始
备注: 每个方法都默认携带上两个参数,一个是消息接受者,另一个是_cmd
4、cache_t
方法缓存
Class
内部结构中有个方法缓存cache_t
,用 散列表
来缓存曾经调用过的方法,可以提高方法的查找速度。
struct cache_t {
struct bucket_t *_buckets; //散列表(是个数组)
mask_t _mask; //散列表长度 -1
mask_t _occupied; //已经缓存的方法数量
}
而 _buckets
结构如下
struct bucket_t {
cache_key_t _key; // SEL为 key 例如 : _key = @selector(test)
IMP _imp; // 函数的内存地址 例如 : _imp = &test
}
散列表(哈希表)查找流程简述
首先,一个方法被调用过,会放进类对象的的缓存数组里面_buckets
,放的位置是根据@selector(test) & _mask = index
,然后存进数组。当在缓存里面查找的时候,也是根据@selector(test) & _mask = index
计算得出的 index
(存: 如果 index 超出长度,就扩容(扩容后,已有的缓存会被清掉,因为_mask 发生了变化),如果 index 的位置已经存储了,那么直接 继续 index-1,找到未被存储的位置,直接存进去; 取 :@selector(test) & _mask = index
,比对 key,如果一样,完成调用。不一样 index -1 ,找到完成调用,到最后还是没找到,那么从末尾继续往前找。),找到 _bucket_t
,进而找到 _imp
完成调用流程。是以空间换时间来提高查找效率。
源码展示,在
objc-cache.mm
bucket_t * cache_t::find(cache_key_t k, id receiver)
{
assert(k != 0);
bucket_t *b = buckets();
mask_t m = mask();
//查找 index
mask_t begin = cache_hash(k, m);
mask_t i = begin;
do {
//找到了直接返回
if (b[i].key() == 0 || b[i].key() == k) {
return &b[i];
}
//如果找到了但是 key 不一样,那么 i-1,继续比对
} while ((i = cache_next(i, m)) != begin);
// hack
Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
cache_t::bad_cache(receiver, (SEL)k, cls);
}
看下 cache_hash(k, m)
查找流程
static inline mask_t cache_hash(cache_key_t key, mask_t mask)
{
// 就是 @selector() & _mask
return (mask_t)(key & mask);
}
没找到继续循环的流程 cache_next(i, m)
static inline mask_t cache_next(mask_t i, mask_t mask) {
//将其下标减 1 ,如果还是没找到,就从最末尾开始找
return i ? i-1 : mask;
}
还有一个,扩容的过程补充下,每次扩容容量直接 double
,更新_mask, 清除原有缓存
void cache_t::expand()
{
cacheUpdateLock.assertLocked();
uint32_t oldCapacity = capacity();
//扩容,每次容量直接翻倍
uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE;
if ((uint32_t)(mask_t)newCapacity != newCapacity) {
// mask overflow - can't grow further
// fixme this wastes one bit of mask
newCapacity = oldCapacity;
}
//重新分配
reallocate(oldCapacity, newCapacity);
}
void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity)
{
bool freeOld = canBeFreed();
bucket_t *oldBuckets = buckets();
bucket_t *newBuckets = allocateBuckets(newCapacity);
// Cache's old contents are not propagated.
// This is thought to save cache memory at the cost of extra cache fills.
// fixme re-measure this
assert(newCapacity > 0);
assert((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1);
//更新 mask.这里也可以得到,_mask 的值为容量 -1
setBucketsAndMask(newBuckets, newCapacity - 1);
//释放原有的缓存
if (freeOld) {
cache_collect_free(oldBuckets, oldCapacity);
cache_collect(false);
}
}
综合上面的知识,完善下方法调用流程, 假设 Student :Person
,在 Person
的头文件声明- (void)test;
方法,并在实现文件对所声明的方法进行实现。
int main(int argc, char * argv[]) {
@autoreleasepool {
Student *stu = [[Student alloc]init]; // cache_t->_occupied = 1
//第一次调用
[stu test]; // cache_t->_occupied = 2
//第二次调用
[stu test];
return 0
}
}
方法的调用流程如下:
0、转化成 objc_msgSend(stu,sel_registerName("test"))
sel_registerName("test") == @selector(test)
1、根据 stu 的 isa 找到 Student 的类对象
2、在类对象的 cache 散列表( _buckets)里面查找
2.1 @selector(test) & _mask = index
2.2 根据 index 取出对应的 _bucket_t 不为空
2.2.1 取出 _bucket_t._key == @selector(test)对比
相等,如果找到,取出 _bucket_t._imp 完成调用
不相等,将 index -1 ,继续 2.2.1 流程,直到 index = _mask
2.3 根据 index 取出对应的 _bucket_t 为空,走 3 流程
3、通过 `bits.data()` 拿到 类的具体信息 class_rw_t rw;
4、通过 rw 找到 method_array_t methods,即 rw->methods
5、遍历 methods 这个二维数组 (method_list_t),找到了(method_t)结束调用,走 9
6、未找到,通过 class-> superclass 指针找到 父类的类对象
7、继续走 2->3->4->5 的流程
8、最终在 Person 这个类对象的方法列表中找到方法 method_t
9、完成调用
9.1 同时,将 method_t 写入 stu 实例对应类对象的 cache 中
三、消息机制
1、简介
OC
方法调用,也称消息机制,给方法调用者发送消息。方法调用者称之为消息接受者。
OC
中的方法调用,其实都是转化成 objc_msgSend()
函数调用。
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc]init];
[person test];
//objc_msgSend(person, sel_registerName("test"));
//消息接受者: person
//发送消息: test
[Person initialize];
//objc_msgSend(objc_getClass("Person"), sel_registerName("initialize"));
//消息接受者: [Person class]
//发送消息: initialize
}
return 0;
}
objc_msgSend
的执行流程可以分为 3 大阶段:消息发送、动态方法解析、消息转发如果经历如上步骤还找不到合适的方法进行调用,会报错
unrecognized selector sent to instance
或者 unrecognized selector sent to class
贴下核心代码,源码中有注释,结合下注释一起看
/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup.
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* inst is an instance of cls or a subclass thereof, or nil if none is known.
* If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use
* must be converted to _objc_msgForward or _objc_msgForward_stret.
* If you don't want forwarding at all, use lookUpImpOrNil() instead.
**********************************************************************/
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// Optimistic cache lookup
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
// runtimeLock is held during isRealized and isInitialized checking
// to prevent races against concurrent realization.
// runtimeLock is held during method search to make
// method-lookup + cache-fill atomic with respect to method addition.
// Otherwise, a category could be added but ignored indefinitely because
// the cache was re-filled with the old value after the cache flush on
// behalf of the category.
runtimeLock.read();
if (!cls->isRealized()) {
// Drop the read-lock and acquire the write-lock.
// realizeClass() checks isRealized() again to prevent
// a race while the lock is down.
runtimeLock.unlockRead();
runtimeLock.write();
realizeClass(cls);
runtimeLock.unlockWrite();
runtimeLock.read();
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
retry:
runtimeLock.assertReading();
// Try this class's cache.
// 1、消息发送
/**
先找缓存 cache,再找 methodlist
*/
// 1.1 缓存查找
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
// 1.2 methodlist 列表查找
{
// 这里是具体的查找方法
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// Try superclass caches and method lists.
// 1.3 父类缓存查找
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass) //注意这里的赋值
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
// Superclass method list.
// 1.4 父类方法列表的查找
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// No implementation found. Try method resolver once.
// 2、动态方法解析
/**
如果没有进行动态解析,那么符合条件,进行消息解析,并将标志位设置为 YES
如果已经进行过动态解析,那么不满足条件,之后进行消息转发
也就是说,当动态解析过一次,之后就不会在动态解析了
*/
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
// No implementation found, and method resolver didn't help.
// Use forwarding.
// 3、消息转发
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlockRead();
return imp;
}
贴下 methodlist 如果查找的 ,getMethodNoSuper_nolock
具体实现
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
assert(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
// 相当于 调用类对象的 bits.data 得到 class_rw_t 得到类的具体信息
for (auto mlists = cls->data()->methods.beginLists(),
end = cls->data()->methods.endLists();
mlists != end;
++mlists)
{
//真正在查找的方法
method_t *m = search_method_list(*mlists, sel);
if (m) return m;
}
return nil;
}
方法查找的具体实现 search_method_list
/***********************************************************************
* getMethodNoSuper_nolock
* fixme
* Locking: runtimeLock must be read- or write-locked by the caller
**********************************************************************/
static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
//方法是否排序,如果是已经排序好了,那么使用二分法查找
if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
return findMethodInSortedMethodList(sel, mlist);
} else {
// Linear search of unsorted method list
// 如果方法未排序好,那么使用线性查找,就是 一层 for 循环
for (auto& meth : *mlist) {
if (meth.name == sel) return &meth;
}
}
#if DEBUG
// sanity-check negative results
if (mlist->isFixedUp()) {
for (auto& meth : *mlist) {
if (meth.name == sel) {
_objc_fatal("linear search worked when binary search did not");
}
}
}
#endif
return nil;
}
排序好的方法实现折半查找的具体实现
static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
assert(list);
const method_t * const first = &list->first;
const method_t *base = first;
const method_t *probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)probe->name;
if (keyValue == probeValue) {
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
probe--;
}
return (method_t *)probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
1、消息发送阶段
obj -> isa -> class -> cache -> methodlist -> superclass -> cache -> methodlist ->…->superclass = nil -> 动态方法解析
如果 obj
为 nil
,结束调用;
如果查找过程,找到方法实现,调用结束,并将方法写入 obj
的类对象 的 cache
中;
对于 methodlist 中方法查找,如果方法已经排序,则为 二分法查找,如果未排序,则为暴力遍历查找。
2、动态方法解析
如果方法接收者是实例对象, 调用 [cls resolveInstanceMethod:sel]
如果方法接受者是类对象,调用 [cls resolveClassMethod:sel]
/***********************************************************************
* _class_resolveMethod
* Call +resolveClassMethod or +resolveInstanceMethod.
* Returns nothing; any result would be potentially out-of-date already.
* Does not check if the method already exists.
**********************************************************************/
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
举个例子: Person:NSObject
在Person的实现文件里,重写 resolveInstanceMethod
方法
#import "Person.h"
#import <objc/runtime.h>
@implementation Person
- (void)test
{
NSLog(@"%s",__func__);
}
//实例方法
+(BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(abc)) {
//获取 method_t
Method method = class_getInstanceMethod(self, @selector(test));
//获取 (method_t->_imp)
IMP imp = method_getImplementation(method);
//获取参数编码(method_t->_types) v16@0:8 隐藏参数 (id)obj 和 _cmd (当前selector) 都是指针,占 8 字节
const char *encode = method_getTypeEncoding(method);
//动态添加方法
if (class_addMethod(self, sel, imp, encode)) {
//注意: 添加成功后,并标记 triedResolver = YES ,重新走消息发送流程
}
return YES;
}
return [super resolveClassMethod:sel];
}
@end
//main方法
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc]init];
// 调用成功,输出 -[Person test]
[person performSelector:@selector(abc)];
}
return 0;
}
注意: class_addMethod
添加成功后,会添加到 类对象信息 class_rw_t->methods
中。
3、消息转发
顾名思义: 当前对象无法处理消息,将消息转发给别人。
核心就下面三个方法,这三个方法都有对象方法、类方法 2 个版本(前面可以是 +、也可以是 -)
/*
返回可以处理该消息的对象
如果返回为空 `nil`,调用下 methodSignatureForSelector
如果不为空 ,相当于 [target aSelector];
*/
- (id)forwardingTargetForSelector:(SEL)aSelector
/*
如果上面 forwardingTargetForSelector 返回值为 nil,
则会调用 methodSignatureForSelector 该方法。返回一个函数签名
如果函数签名不为空,则调用 forwardInvocation 方法,将函数签名交给其他 target
其中: anInvocation 包含方法签名(返回值、参数) ,调用 [anInvocation invokeWithTarget:<#(nonnull id)#>] 传入一个可以处理该消息的 target 即可或者任意处理也没问题
如果函数签名为空,直接报 unrecognized selector sent to instance/class 错误
*/
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation
举个例子:
将 Person 实例对象接受的消息转发给 Student 实例
// 1、Person 实现文件
#import "Person.h"
#import "Student.h"
@implementation Person
//返回可以处理该消息的对象
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(abc)) {
return [[Student alloc]init];
}
return [super forwardingTargetForSelector:aSelector];
}
//返回一个函数签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
//参数编码必须与 test 保持一致
if (aSelector == @selector(test)) {
//这里其实就是 @encode() 参数编码. v16@0:8 解释:返回值为 void,默认参数(self 和 _cmd 指针)占 16 字节。第一个是 receiver(消息接受者)id 类型,编码后为 @ 从0开始,第二个是 SEL ,编码后为 : ,从第 8 个字节开始
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
return [super methodSignatureForSelector:aSelector];
}
// anInvocation 封装了一个方法调用,包括方法调用者,方法名,方法参数
// anInvocation.target 方法调用者
//anInvocation.selector; //方法名
//[anInvocation getArgument:<#(nonnull void *)#> atIndex:<#(NSInteger)#>] //参数
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
// 将消息转发给 target 去处理,当然你这里也可以任意处理
//NSLog(@"%s",anInvocation.selector); 只写这一行也没问题,不会 crash
[anInvocation invokeWithTarget:[Student new]];
}
// 2、Student 实现文件
#import "Student.h"
@implementation Student
- (void)abc
{
NSLog(@"%s",__func__);
}
- (void)test{
NSLog(@"%s",__func__);
}
@end
// 3、main 方法
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc]init];
[person performSelector:@selector(abc)];
[person performSelector:@selector(test)];
}
return 0;
}
输出结果如下:
-[Student abc]
-[Student test]
下面看下 类方法的消息转发,基于上面的修改下
// 1、Person 类实现文件
#import "Person.h"
#import "Student.h"
@implementation Person
//返回可以处理该消息的对象
+ (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(abc)) {
//这里返回的当然,你这里返回实例对象也是可以的。只要实例对象有对应的实现即可。
return [Student class];
}
return [super forwardingTargetForSelector:aSelector];
}
//返回一个函数签名
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
//这里其实就是 @encode() 参数编码
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
return [super methodSignatureForSelector:aSelector];
}
// anInvocation 封装了一个方法调用,包括方法调用者,方法名,方法参数
// anInvocation.target 方法调用者
//anInvocation.selector; //方法名
//[anInvocation getArgument:<#(nonnull void *)#> atIndex:<#(NSInteger)#>] //参数
+ (void)forwardInvocation:(NSInvocation *)anInvocation
{
// 将消息转发给 target 去处理
[anInvocation invokeWithTarget:[Student class]];
}
// 2、Student 类实现文件
#import "Student.h"
@implementation Student
+ (void)abc
{
NSLog(@"%s",__func__);
}
+ (void)test{
NSLog(@"%s",__func__);
}
@end
// 3、main 方法
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
[Person performSelector:@selector(abc)];
[Person performSelector:@selector(test)];
}
return 0;
}
输出结果如下:
+[Student abc]
+[Student test]
四、关于 super
关键字
1、一道经典面试题
Student
继承Person
,并在 Student
实现类重写 init
方法,重写如下,问输出结果
#import "Student.h"
@implementation Student
- (instancetype)init
{
self = [super init];
if (self) {
NSLog(@"[self class] : %@",[self class]);
NSLog(@"[self superclass] :%@",[self superclass]);
NSLog(@"[super class]: %@",[super class]);
NSLog(@"[super superclass] :%@",[super superclass]);
}
return self;
}
结果:
[self class] : Student
[self superclass] :Person
[super class]: Student
super superclass] :Person
2、 objc_msgSendSuper
其实,super 关键字底层调用的是 objc_msgSendSuper
这个方法,这个 API 源码在objc_msgSend
正下方。
/**
* Sends a message with a simple return value to the superclass of an instance of a class.
*
* @param super A pointer to an \c objc_super data structure. Pass values identifying the
* context the message was sent to, including the instance of the class that is to receive the
* message and the superclass at which to start searching for the method implementation.
* @param op A pointer of type SEL. Pass the selector of the method that will handle the message.
* @param ...
* A variable argument list containing the arguments to the method.
*
* @return The return value of the method identified by \e op.
*
* @see objc_msgSend
*/
OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
从对方法描述可以看出,方法的查找是从父类开始。而且objc_msgSendSuper
第一个参数是个指向 struct objc_super
类型的结构体指针,此结构体定义如下
struct objc_super {
__unsafe_unretained _Nonnull id receiver; //消息接受者
__unsafe_unretained _Nonnull Class super_class; //消息接受者的父类
};
可知, receiver
还是当前对象 self
, 而 calss
方法在 NSObject
中。从 GNUStep源码中,可知class
方法的实现就是
- (Class) class
{
return object_getClass(self);
}
由此,可以得出 : super
只是将方法的查找由父类方法列表开始查找,消息接受者还是当前对象。
五、 isKindOfClass
和 isMemberOfClass
1、实现
关于 isKindOfClass
和 isMemberOfClass
在 runtime
中实现如下
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
从上面可以看出,isKindOfClass
和 isMemberOfClass
同时存在实例方法 和 类方法。
实例方法 isKindOfClass
判断当前对象的类对象和参数是否一致;或者当前对象的父类的类对象与参数是否一致
实例方法 isMemberOfClass
判断当前对象的类对象和参数是否一致;
类方法 isKindOfClass
判断当前类对象的元类对象与参数是否一致;或者当前类对象的父类的元类对象与参数是否一致
类方法 isMemberOfClass
判断当前类对象的元类对象与参数是否一致
2、细节
这里需要注意一个非常小的细节:
NSLog("%d",[Person isKindOfClass:[NSObject class]]);
输出结果:
1
这是因为: Person
的元类对象 的 superclass
指针指向 NSobject
的元类对象,而NSobject
元类对象的superclass
指针,指向了 NSObject
的类对象!!!
六、 runtime
常用 API
1、类相关
动态创建一个类(父类、类名、额外的内存空间), 后缀 pair 是成对的意思,因为生成了 类 对象和元类对象
Class objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name, size_t extraBytes)
注册一个类(注册最好添加好成员变量、方法、协议、属性),类一旦被注册,就相当于这个类的类对象和元类对象就已经创建好了
objc_registerClassPair(Class cls)
销毁一个类
void objc_disposeClassPair(Class cls)
获取 isa
指向的 Class
(用于获取 类对象、元类对象)
Class object_getClass(id obj)
设置 isa
指向 Class
(KVO 底层实现原理有用到)
object_setClass(id obj,Class cls)
判断是否为 Class
对象
BOOL object_isClass(id obj)
判断一个 Class
是否为元类
BOOL class_isMetaClass(Class cls)
获取父类
Class class)getSuperClass(Class cls)
2、成员变量相关
动态添加成员变量(已注册的类不能添加成员变量,因为成员变量放在 类对象结构体 class_ro_t 中的)
BOOL
class_addIvar(Class _Nullable cls, const char * _Nonnull name, size_t size,
uint8_t alignment, const char * _Nullable types)
获取成员变量信息对象
Ivar class_getInstanceVariable(Class _Nullable cls, const char * _Nonnull name)
根据成员变量信息对象获取变量名
const char ivar_getName(Ivar _Nonnull v)
根据成员信息对象获取变量的类型编码(即:@encode(type) )
const char * ivar_getTypeEncoding(Ivar _Nonnull v)
拷贝成员变量列表(最后需要用 free()
函数进行释放)
Ivar * class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount)
3、属性相关(参照成员变量)
4、方法相关
获取一个实例方法
Method class_getInstanceMethod(Class cls, SEL name)
获取一个类方法
Method class_getClassMethod(Class cls, SEL name)
获取方法的实现
IMP class_getMethodImplementation(Class cls, SEL name)
交换两个方法的实现
void method_exchangeImplementations(Method m1, Method m2)
拷贝方法列表
Method *class_copyMethodList(Class cls, unsigned int *outCount)
动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
做个简单的demo
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
void run(id obj,SEL _md){
NSLog(@"%s",__func__);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
//创建这个类(Pair 成对的意思: 类对象、元类对象)
Class dogClass = objc_allocateClassPair([NSObject class], "Dog", 0);
//添加成员变量(一定要在注册类之前,因为成员变量是放在类对象结构中 class_ro_t ,是只读的)
class_addIvar(dogClass, "_age", 4, 1, @encode(int));
class_addIvar(dogClass, "_weight", 4, 1, @encode(int));
//注册这个类
objc_registerClassPair(dogClass);
//添加方法(方法可以放在注册类之后,因为方法是放在类对象结构体中 class_rw_t,可读可写)
class_addMethod(dogClass, @selector(run), (IMP)run, "v16@0:8");
id dog = [[dogClass alloc] init];
[dog setValue:@10 forKey:@"_age"];
[dog setValue:@15 forKey:@"_weight"];
[dog run];
//[dog performSelector:@selector(run)];
NSLog(@"%@---%@",[dog valueForKey:@"_age"],[dog valueForKey:@"_weight"]);
NSLog(@"%zd",class_getInstanceSize(dogClass));
//获取成员变量信息
Ivar ageIvar = class_getInstanceVariable(dogClass, "_age");
//获取成员变量的名字和编码
const char *name = ivar_getName(ageIvar);
//获取成员变量的编码
const char *nameEndode = ivar_getTypeEncoding(ageIvar);
NSLog(@"%s----%s",name,nameEndode);
//获取成员变量信息
unsigned int ivarCount;
Ivar *ivarlist = class_copyIvarList(dogClass, &ivarCount);
for (int i = 0; i < ivarCount; i++) {
Ivar ivar = ivarlist[i];
const char *ivarName = ivar_getName(ivar);
NSLog(@"%s",ivarName);
}
free(ivarlist);
//获取方法列表
unsigned int methodCount;
Method *methodList = class_copyMethodList(dogClass, &methodCount);
for (int index = 0; index < methodCount; index++) {
Method method = methodList[index];
NSString *methodName = NSStringFromSelector(method_getName(method));
NSLog(@"%@",methodName);
}
//释放
free(methodList);
//销毁类
objc_disposeClassPair(dogClass);
}
return 0;
}
七、相关应用
1、遍历访问成员变量(修改 textfield 的占位属性、字典转模型、解归档 等)
修改 TextField
的 placeholderText
文本颜色、字体大小
思路: 适应 class_copyIvarList
遍历 UITextField 的 Ivar
得到成员变量,然后 利用 KVC
直接修改
//修改字体颜色
[self.textField setValue:[UIColor redColor] forKeyPath:@"_placholderLable.textColor"]];
//修改字体大小
[self.textField setValue:[UIFont fontWithSize:18 ]forKeyPath:@"_placholderLable.textFont"]];
当然了,你也可以用 attribute
去进行设置啦。这里有个小细节需要注意下: UIKit
框架下的很多控件都是用懒加载实现的,所以必须现有placeholder
后上面的设置才能生效。
字典转模型
为 NSObject 添加分类,分类中实现字典转模型的方法(简配版)
#import "NSObject+Category.h"
#import <objc/runtime.h>
@implementation NSObject (Category)
+ (instancetype)yfl_objectWithJson:(NSDictionary*)json
{
id obj = [[self alloc]init];
//遍历
unsigned int count;
//得到成员变量列表
Ivar *ivars = class_copyIvarList(self, &count);
//变量成员变量列表
for (int index = 0; index < count; index++) {
//得到成员变量信息
Ivar ivar = ivars[index];
NSMutableString *name = [NSMutableString stringWithUTF8String:ivar_getName(ivar)];
//去掉开头"_"
[name deleteCharactersInRange:NSMakeRange(0, 1)];
//赋值
if ([json valueForKey:name]) {
[obj setValue:[json valueForKey:name] forKey:name];
}
}
return obj;
}
@end
自动解归解档
例子参见这里
2、关联对象
利用关联对象给 category
添加属性
//添加属性
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy)
//获取属性
id objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
3、交换方法实现(hook
)
交换方法的底层实现为: 实质为交换 IMP
,相当于 类对象中 bits.data()
获取类具体信息 class_rw_t->methods
中method_t
中的imp
,且当发生方法交换,类对象中的方法缓存 canhe
会被擦除(flushCaches(nil)
)
void method_exchangeImplementations(Method m1, Method m2)
{
if (!m1 || !m2) return;
rwlock_writer_t lock(runtimeLock);
//采用中间值,交换两者的 imp
IMP m1_imp = m1->imp;
m1->imp = m2->imp;
m2->imp = m1_imp;
// RR/AWZ updates are slow because class is unknown
// Cache updates are slow because class is unknown
// fixme build list of classes whose Methods are known externally?
//清除 class 中的 cache
flushCaches(nil);
updateCustomRR_AWZ(nil, m1);
updateCustomRR_AWZ(nil, m2);
}
缓存擦除(erase
)的方法
static void flushCaches(Class cls)
{
runtimeLock.assertWriting();
mutex_locker_t lock(cacheUpdateLock);
if (cls) {
foreach_realized_class_and_subclass(cls, ^(Class c){
cache_erase_nolock(c);
});
}
else {
foreach_realized_class_and_metaclass(^(Class c){
cache_erase_nolock(c);
});
}
}
事件埋点,统计项目中所有按钮的点击动作。由于UIButton:UIControl
,所以可以直接为UIControl
创建分类,自定义方法与sendAction:to:forEvent:
交换 IMP
,具体实现如下:
#import "UIControl+Category.h"
#import <objc/runtime.h>
@implementation UIControl (Category)
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originMethod = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
Method targetMethod = class_getInstanceMethod(self, @selector(yfl_sendAction:to:forEvent:));
// cache 会被清空
method_exchangeImplementations(originMethod, targetMethod);
});
}
- (void)yfl_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{
//注意: 这里不是递归,而是调用原来的实现
[self yfl_sendAction:action to:target forEvent:event];
if ([self isKindOfClass:[UIButton class]]) {
//可以在这里埋点按钮事件点击
NSLog(@"%@---%@--%ld",NSStringFromSelector(action),target,event.type);
}
}
@end
进一步图解
其他应用,例如 App 仿闪退 JJException
4、利用消息转发机制解决方法找不到的异常
思路: 重写消息转发的两个方法