Runtime简介
Runtime 又叫运行时,是一套底层的 C 语言 API,是 iOS 系统的核心之一。Runtime API 提供的接口基本都是C语言的,源码由C\C++\汇编语言编写。开发者在编码过程中,可以给任意一个对象发送消息,在编译阶段只是确定了要向接收者发送这条消息,而接受者将要如何响应和处理这条消息,那就要看运行时来决定了。
C语言中,在编译期,函数的调用就会决定调用哪个函数。而OC的函数,属于动态调用过程,在编译期并不能决定真正调用哪个函数,只有在真正运行时才会根据函数的名称找到对应的函数来调用。
Objective-C 是一个动态语言,这意味着它不仅需要一个编译器,也需要一个运行时系统来动态得创建类和对象、进行消息传递和转发。
Objc 在三种层面上与 Runtime 系统进行交互:
-
通过 Objective-C 源代码
一般情况开发者只需要编写 OC 代码即可,Runtime 系统自动在幕后把我们写的源代码在编译阶段转换成运行时代码,在运行时确定对应的数据结构和调用具体哪个方法。
-
通过 Foundation 框架的 NSObject 类定义的方法
在OC的世界中,除了NSProxy类以外,所有的类都是NSObject的子类。在Foundation框架下,NSObject和NSProxy两个基类,定义了类层次结构中该类下方所有类的公共接口和行为。NSProxy是专门用于实现代理对象的类,这个类暂时本篇文章不提。这两个类都遵循了NSObject协议。在NSObject协议中,声明了所有OC对象的公共方法。
在NSObject协议中,有以下5个方法,是可以从Runtime中获取信息,让对象进行自我检查。
- (Class)class OBJC_SWIFT_UNAVAILABLE("use 'anObject.dynamicType' instead"); - (BOOL)isKindOfClass:(Class)aClass; - (BOOL)isMemberOfClass:(Class)aClass; - (BOOL)conformsToProtocol:(Protocol *)aProtocol; - (BOOL)respondsToSelector:(SEL)aSelector;-class方法返回对象的类;-isKindOfClass:和-isMemberOfClass:方法检查对象是否存在于指定的类的继承体系中(是否是其子类或者父类或者当前类的成员变量);-respondsToSelector:检查对象能否响应指定的消息;-conformsToProtocol:检查对象是否实现了指定协议类的方法;
在NSObject的类中还定义了一个方法:
- (IMP)methodForSelector:(SEL)aSelector; // 返回指定方法实现的地址IMP -
通过对 Runtime 库函数的直接调用
关于库函数可以在 Objective-C Runtime Reference 中查看 Runtime 函数的详细文档。
isa详解
要想学习Runtime,首先要了解它底层的一些常用数据结构,比如isa指针:
- 在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址
- 从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
// 所有值加起来刚好是64位
struct {
uintptr_t nonpointer : 1; // 是否优化过,使用位域存储更多信息
uintptr_t has_assoc : 1; // 是否设置过关联对象
uintptr_t has_cxx_dtor : 1; // 是否有C++的析构函数
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
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
是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快
-
shiftcls
存储着Class、Meta-Class对象的内存地址信息,占用33位
-
magic
用于在调试时分辨对象是否未完成初始化
-
weakly_referenced
是否有被弱引用指向过,如果没有,释放时会更快
-
deallocating
对象是否正在释放
-
extra_rc
里面存储的值是引用计数器减1
-
has_sidetable_rc
- 0,引用计数器是否过大无法存储在isa中
- 1,那么引用计数会存储在一个叫SideTable的类的属性中
在64位架构中,不管是类对象还是元类对象,它们的地址值的后三位一定是0
位运算
#import "MJPerson.h"
// &可以用来取出特定的位,
// 按位与(&),两位都是1结果才会是1
// 按位或(|),只要有一位是1结果就是1
// 0000 0111 0000 0111
//&0000 0100 |0000 0100
// --------- ---------
// 0000 0100 0000 0111
// 掩码,一般用来按位与(&)运算的
//#define MJTallMask 1
//#define MJRichMask 2
//#define MJHandsomeMask 4
//#define MJTallMask 0b00000001
//#define MJRichMask 0b00000010
//#define MJHandsomeMask 0b00000100
#define MJTallMask (1<<0) // 1向左移0位 == 0b00000001
#define MJRichMask (1<<1) // 1向左移1位 == 0b00000010
#define MJHandsomeMask (1<<2) // 1向左移2位 == 0b00000100
@interface MJPerson()
{
/*
char 占用1个字节的内存,这样做可以节省内存
原本三个bool属性,每个都要占用4个字节,而现在只占用1个字节中的1位
*/
char _tallRichHansome;
}
@end
@implementation MJPerson
- (instancetype)init
{
if (self = [super init]) {
_tallRichHansome = 0b00000100;
}
return self;
}
- (void)setTall:(BOOL)tall
{
if (tall) {
_tallRichHansome |= MJTallMask;
} else {
_tallRichHansome &= ~MJTallMask; // ~ 按位取反
}
}
- (BOOL)isTall
{
return !!(_tallRichHansome & MJTallMask); // 只要有值且不为0,连续取反,结果为true
}
@end
另一种方式,使用共用体(union):
#define MJTallMask (1<<0)
#define MJRichMask (1<<1)
#define MJHandsomeMask (1<<2)
@interface MJPerson()
{
// 共用体表示,里面的数据会共用同一块内存
union {
int bits;
/*
这里这个struct并没有实际意义,只是为了说明
某一位的值是代表什么意义
*/
struct {
char tall : 1;
char rich : 1;
char handsome : 1;
};
} _tallRichHandsome;
}
@end
@implementation MJPerson
- (void)setTall:(BOOL)tall
{
if (tall) {
_tallRichHandsome.bits |= MJTallMask;
} else {
_tallRichHandsome.bits &= ~MJTallMask;
}
}
- (BOOL)isTall
{
return !!(_tallRichHandsome.bits & MJTallMask);
}
@end
再来看一种情况,枚举采用位运算:
//typedef enum {
// MJOptionsOne = 1, // 0b0001
// MJOptionsTwo = 2, // 0b0010
// MJOptionsThree = 4, // 0b0100
// MJOptionsFour = 8 // 0b1000
//} MJOptions;
typedef enum {
// MJOptionsNone = 0, // 0b0000
MJOptionsOne = 1<<0, // 0b0001
MJOptionsTwo = 1<<1, // 0b0010
MJOptionsThree = 1<<2, // 0b0100
MJOptionsFour = 1<<3 // 0b1000
} MJOptions;
@implementation ViewController
/*
0b0001
0b0010
0b1000
------
0b1011
&0b0100
-------
0b0000
*/
- (void)setOptions:(MJOptions)options
{
if (options & MJOptionsOne) {
NSLog(@"包含了MJOptionsOne");
}
if (options & MJOptionsTwo) {
NSLog(@"包含了MJOptionsTwo");
}
if (options & MJOptionsThree) {
NSLog(@"包含了MJOptionsThree");
}
if (options & MJOptionsFour) {
NSLog(@"包含了MJOptionsFour");
}
}
- (void)viewDidLoad {
[super viewDidLoad];
[self setOptions: MJOptionsOne | MJOptionsTwo | MJOptionsFour];
// 因为值特殊,所以用 + 号也可以实现,但是不专业,不推荐
[self setOptions: MJOptionsOne + MJOptionsTwo + MJOptionsFour];
}
@end
Class详解
objc_class
在Objc2.0之前,objc_class源码如下:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#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;
然后在2006年苹果发布Objc 2.0之后,objc_class的定义就变成下面这个样子
typedef struct objc_class *Class;
typedef struct objc_object *id;
@interface Object {
Class isa;
}
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
struct objc_object {
private:
isa_t isa;
}
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
}
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
}
把源码的定义转化成类图,就是下图的样子
从上述源码中,我们可以看到,Objective-C 对象都是 C 语言结构体实现的,在objc2.0中,所有的对象都会包含一个 isa_t 类型的结构体。
objc_object 被源码 typedef 成了id类型,这也就是我们平时遇到的id类型。这个结构体中就只包含了一个isa_t类型的结构体。
objc_class 继承于 objc_object。所以在 objc_class 中也会包含 isa_t 类型的结构体 isa。至此,可以得出结论:Objective-C 中类也是一个对象。在 objc_class 中,除了isa之外,还有3个成员变量,一个是父类的指针,一个是方法缓存,最后一个用来存储类的具体信息。
Object 类和 NSObject 类里面分别都包含一个 objc_class 类型的 isa。
class_rw_t
class_rw_t 里面的 methods、properties、protocols 是二维数组,是可读可写的,包含了类的初始内容、分类的内容。
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;
};
class_ro_t
class_ro_t 里面的 baseMethodList、baseProtocols、ivars、baseProperties 是一维数组,是只读的,包含了类的初始内容。
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_t
method_t 是对方法 / 函数的封装
struct method_t {
SEL name; // 方法/函数名
const char *types; // 编码(返回值类型、参数类型)
IMP imp; // 指向函数的指针(函数地址)
};
-
SEL 代表方法 / 函数名,一般叫做选择器,底层结构跟
char *类似- 可以通过
@selector()和sel_registerName()获得 - 可以通过
sel_getName()和NSStringFromSelector()转成字符串 - 不同类中相同名字的方法,所对应的方法选择器是相同的
- 可以通过
-
IMP 代表函数的具体实现,实际上就是一个指向函数地址的指针
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); -
types 包含了函数返回值、参数编码的字符串,iOS中提供了一个叫做 @encode 的指令,可以将具体的类型表示成字符串编码。
NSLog(@"%s", @encode(SEL)); // @ /* types = "i24@0:8i16f20" i 表示返回值为 int,24表示所有数据占用的内存 @ 表示id或object,OC方法默认有两个参数,self 和 sel,0表示从第几个字节开始存储 : 表示SEL,8表示从第8个字节开始存储,因为前面的self已经占用了8个字节 ...... */ - (int)test:(int)age height:(float)height;
cache_t
Class 内部结构中有个方法缓存(cache_t),用**散列表(哈希表)**来缓存曾经调用过的方法,可以提高方法的查找速度。
#if __LP64__
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;
struct cache_t {
struct bucket_t *_buckets; // 方法缓存散列表
mask_t _mask; // 散列表的长度-1
mask_t _occupied; // 已经缓存的方法数量
};
struct bucket_t {
private:
cache_key_t _key; // SEL作为key
IMP _imp; // IMP为值
};
// objc-cache.mm
void cache_fill(Class cls, SEL sel, IMP imp, id receiver)
{
#if !DEBUG_TASK_THREADS
mutex_locker_t lock(cacheUpdateLock);
cache_fill_nolock(cls, sel, imp, receiver);
#else
_collecting_in_critical();
return;
#endif
}
// 装填
static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
{
cacheUpdateLock.assertLocked();
// Never cache before +initialize is done
if (!cls->isInitialized()) return;
// Make sure the entry wasn't added to the cache by some other thread
// before we grabbed the cacheUpdateLock.
if (cache_getImp(cls, sel)) return;
cache_t *cache = getCache(cls);
cache_key_t key = getKey(sel);
// Use the cache as-is if it is less than 3/4 full
mask_t newOccupied = cache->occupied() + 1;
mask_t capacity = cache->capacity();
if (cache->isConstantEmptyCache()) {
// Cache is read-only. Replace it.
cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
}
else if (newOccupied <= capacity / 4 * 3) {
// Cache is less than 3/4 full. Use it as-is.
}
else {
// Cache is too full. Expand it.
cache->expand();
}
// Scan for the first unused slot and insert there.
// There is guaranteed to be an empty slot because the
// minimum size is 4 and we resized at 3/4 full.
bucket_t *bucket = cache->find(key, receiver);
if (bucket->key() == 0) cache->incrementOccupied();
bucket->set(key, imp);
}
// 查找
bucket_t * cache_t::find(cache_key_t k, id receiver)
{
assert(k != 0);
bucket_t *b = buckets();
mask_t m = mask();
mask_t begin = cache_hash(k, m);
mask_t i = begin;
do {
if (b[i].key() == 0 || b[i].key() == k) {
return &b[i];
}
} 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);
}
// 获取哈希值
static inline mask_t cache_hash(cache_key_t key, mask_t mask)
{
return (mask_t)(key & mask);
}
// 寻找下一个空余空间,不同架构下方法不同
#if __arm__ || __x86_64__ || __i386__
// objc_msgSend has few registers available.
// Cache scan increments and wraps at special end-marking bucket.
#define CACHE_END_MARKER 1
static inline mask_t cache_next(mask_t i, mask_t mask) {
return (i+1) & mask;
}
#elif __arm64__
// objc_msgSend has lots of registers available.
// Cache scan decrements. No end marker needed.
#define CACHE_END_MARKER 0
static inline mask_t cache_next(mask_t i, mask_t mask) {
return i ? i-1 : 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);
setBucketsAndMask(newBuckets, newCapacity - 1);
if (freeOld) {
// 将之前的内容释放掉
cache_collect_free(oldBuckets, oldCapacity);
cache_collect(false);
}
}
objc_msgSend
OC中的方法调用,其实都是转换为 objc_msgSend 函数的调用。
objc_msgSend 的执行流程可以分为3大阶段:
- 消息发送
- 动态方法解析
- 消息转发
objc_msgSend 如果找不到合适的方法进行调用,会报错 unrecognized selector sent to instance
objc_msgSend源码解析
// ENTYR 表示入口
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
MESSENGER_START
// x0寄存器:消息接受者,receiver
cmp x0, #0 // nil check and tagged pointer check
// b 表示调整,b.le 表示在条件 le 成立的时候进行跳转,跳转至 LNilOrTagged
// le 表示小于等于,即 x0 <= #0,也就是判断receiver是否为nil,一旦为nil,就跳转到 LReturnZero
b.le LNilOrTagged // (MSB tagged pointer looks negative)
// ldr 把数据从内存中读取到寄存器中,这里x13寄存器里保存的是isa指针
ldr x13, [x0] // x13 = isa
// 将isa与ISA_MASK按位与,并将结果保存在x16寄存器中,x16寄存器保存的是class地址
and x16, x13, #ISA_MASK // x16 = class
LGetIsaDone:
// 获取到class地址以后,开始查找方法缓存
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
LNilOrTagged:
b.eq LReturnZero // nil check
// tagged
mov x10, #0xf000000000000000
cmp x0, x10
b.hs LExtTag
adrp x10, _objc_debug_taggedpointer_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
ubfx x11, x0, #60, #4
ldr x16, [x10, x11, LSL #3]
b LGetIsaDone
LExtTag:
// ext tagged
adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
ubfx x11, x0, #52, #8
ldr x16, [x10, x11, LSL #3]
b LGetIsaDone
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
MESSENGER_END_NIL
ret # 相当于OC里的return
END_ENTRY _objc_msgSend
objc_msgSend执行流程
-
objc-msg-arm64.s
- ENTRY _objc_msgSend
- b.le LNilOrTagged
- CacheLookup NORMAL
- .macro CacheLookup
- .macro CheckMiss
- STATIC_ENTRY
__objc_msgSend_uncached - .macro MethodTableLookup
__class_lookupMethodAndLoadCache3
-
objc-runtime-new.mm
- _class_lookupMethodAndLoadCache3
- lookUpImpOrForward
- getMethodNoSuper_nolock、search_method_list、log_and_fill_cache
- cache_getImp、log_and_fill_cache、getMethodNoSuper_nolock、log_and_fill_cache
- _class_resolveMethod (从这里开始进入动态方法解析)
- _class_resolveClassMethod (如果是元类)
- _class_resolveInstanceMethod (如果不是元类)
- _objc_msgForward_impcache (从这里开始进入消息转发)
-
objc-msg-arm64.s
- STATIC_ENTRY
__objc_msgForward_impcache - ENTRY
__objc_msgForward
- STATIC_ENTRY
-
Core Foundation
__forwarding__(不开源)
lookUpImpOrForward
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.
// 判断缓存中是否存在,存在,就跳转至done
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
// 缓存中不存在,就去类的方法列表中找
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
// 找到以后,添加至缓存中,然后跳转至done
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// Try superclass caches and method lists.
// 去superclass的缓存和方法列表中找
{
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.
// 找到以后,添加至缓存中(添加至当前Class的缓存,不是superclass),然后跳转至done
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.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
// 找到以后,添加至缓存中(添加至当前Class的缓存,不是superclass),然后跳转至done
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// No implementation found. Try method resolver once.
// 没有找到方法实现,开始尝试动态方法解析
// 先查看是否已经进行过动态方法解析,没有则进入,有则跳过
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.
// 将标记改为已经进行了动态方法解析,然后回到retry,重复消息发送流程
triedResolver = YES;
goto retry;
}
// No implementation found, and method resolver didn't help.
// Use forwarding.
// 没有找到方法实现,动态方法解析也没用,则进入消息转发
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlockRead();
return imp;
}
消息发送
动态方法解析
@implementation MJPerson
// C函数
void c_other(id self, SEL _cmd)
{
NSLog(@"c_other - %@ - %@", self, NSStringFromSelector(_cmd));
}
// 动态添加类方法
+ (BOOL)resolveClassMethod:(SEL)sel
{
if (sel == @selector(test)) {
// 第一个参数是object_getClass(self)
class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8");
return YES;
}
return [super resolveClassMethod:sel];
}
// 动态添加实例方法,也可以添加C函数
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(test)) {
// 动态添加test方法的实现
class_addMethod(self, sel, (IMP)c_other, "v16@0:8");
// 返回YES代表有动态添加方法
return YES;
}
return [super resolveInstanceMethod:sel];
}
- (void)other
{
NSLog(@"%s", __func__);
}
// 动态添加实例方法,可以添加其他OC方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(test)) {
// 获取其他方法
Method method = class_getInstanceMethod(self, @selector(other));
// 动态添加test方法的实现
class_addMethod(self, sel,
method_getImplementation(method),
method_getTypeEncoding(method));
// 返回YES代表有动态添加方法
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
MJPerson *person = [[MJPerson alloc] init];
[person test];
[MJPerson test];
}
return 0;
}
消息转发
消息转发,顾名思义就是把自己无法处理的消息转发给别的对象去处理。
通过分析报错信息,我们发现了一个 __forwarding__ 函数,但是这个函数是不开源的,万幸的是国外有网友通过汇编对代码进行了分析,猜测的写了份伪代码,总结如下:
// 伪代码
int __forwarding__(void *frameStackPointer, int isStret) {
id receiver = *(id *)frameStackPointer;
SEL sel = *(SEL *)(frameStackPointer + 8);
const char *selName = sel_getName(sel);
Class receiverClass = object_getClass(receiver);
// 调用 forwardingTargetForSelector:
if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
id forwardingTarget = [receiver forwardingTargetForSelector:sel];
if (forwardingTarget && forwardingTarget != receiver) {
if (isStret == 1) {
int ret;
objc_msgSend_stret(&ret,forwardingTarget, sel, ...);
return ret;
}
return objc_msgSend(forwardingTarget, sel, ...);
}
}
// 僵尸对象
const char *className = class_getName(receiverClass);
const char *zombiePrefix = "_NSZombie_";
size_t prefixLen = strlen(zombiePrefix); // 0xa
if (strncmp(className, zombiePrefix, prefixLen) == 0) {
CFLog(kCFLogLevelError,
@"*** -[%s %s]: message sent to deallocated instance %p",
className + prefixLen,
selName,
receiver);
<breakpoint-interrupt>
}
// 调用 methodSignatureForSelector 获取方法签名后再调用 forwardInvocation
if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
if (methodSignature) {
BOOL signatureIsStret = [methodSignature _frameDescriptor]->returnArgInfo.flags.isStruct;
if (signatureIsStret != isStret) {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'. Signature thinks it does%s return a struct, and compiler thinks it does%s.",
selName,
signatureIsStret ? "" : not,
isStret ? "" : not);
}
if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];
[receiver forwardInvocation:invocation];
void *returnValue = NULL;
[invocation getReturnValue:&value];
return returnValue;
} else {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message",
receiver,
className);
return 0;
}
}
}
SEL *registeredSel = sel_getUid(selName);
// selector 是否已经在 Runtime 注册过
if (sel != registeredSel) {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort",
sel,
selName,
registeredSel);
} // doesNotRecognizeSelector
else if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
[receiver doesNotRecognizeSelector:sel];
}
else {
CFLog(kCFLogLevelWarning ,
@"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort",
receiver,
className);
}
// The point of no return.
kill(getpid(), 9);
}
- 开发者可以在
forwardInvocation:方法中自定义任何逻辑 - 以上方法都有对象方法、类方法2个版本(前面可以是加号+,也可以是减号-)
@implementation MJPerson
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
// objc_msgSend([[MJCat alloc] init], aSelector)
// 如果这里没有做任何处理,或者返回nil,就进入methodSignatureForSelector:方法
return [[MJCat alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
// 方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector == @selector(test)) {
// 如果返回为nil,则直接调用doesNotRecognizeSelector:方法
// 如果返回一个合理的方法签名,则会调用forwardInvocation:方法
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
// return [[[MJCat alloc] init] methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
// NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
// anInvocation.target 方法调用者
// anInvocation.selector 方法名
// [anInvocation getArgument:NULL atIndex:0]
// 在这里你可以做你想做的一切,哪怕什么都不写,只是实现了这个方法,也不会报错
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
// anInvocation.target = [[MJCat alloc] init];
// [anInvocation invoke];
[anInvocation invokeWithTarget:[[MJCat alloc] init]];
// 参数顺序:receiver、selector、other arguments
// int age;
// [anInvocation getArgument:&age atIndex:2];
// NSLog(@"%d", age + 10);
// int ret;
// [anInvocation getReturnValue:&ret];
// NSLog(@"%d", ret);
}
@end
RuntimeAPI
类相关
-
动态创建一个类(参数:父类,类名,额外的内存空间)
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes) -
注册一个类(要在类注册之前添加成员变量)
void objc_registerClassPair(Class cls) -
销毁一个类
void objc_disposeClassPair(Class cls) -
获取isa指向的Class
Class object_getClass(id obj) -
设置isa指向的Class
Class object_setClass(id obj, Class cls) -
判断一个OC对象是否为Class
BOOL object_isClass(id obj) -
判断一个Class是否为元类
BOOL class_isMetaClass(Class cls) -
获取父类
Class class_getSuperclass(Class cls)
示例:
void run(id self, SEL _cmd)
{
NSLog(@"_____ %@ - %@", self, NSStringFromSelector(_cmd));
}
void testClass()
{
// 创建类
Class newClass = objc_allocateClassPair([NSObject class], "MJDog", 0);
class_addIvar(newClass, "_age", 4, 1, @encode(int));
class_addIvar(newClass, "_weight", 4, 1, @encode(int));
class_addMethod(newClass, @selector(run), (IMP)run, "v@:");
// 注册类
objc_registerClassPair(newClass);
// MJPerson *person = [[MJPerson alloc] init];
// object_setClass(person, newClass);
// [person run];
// id dog = [[newClass alloc] init];
// [dog setValue:@10 forKey:@"_age"];
// [dog setValue:@20 forKey:@"_weight"];
// [dog run];
//
// NSLog(@"%@ %@", [dog valueForKey:@"_age"], [dog valueForKey:@"_weight"]);
// 在不需要这个类时释放
objc_disposeClassPair(newClass);
}
void test()
{
MJPerson *person = [[MJPerson alloc] init];
[person run];
object_setClass(person, [MJCar class]);
[person run];
NSLog(@"%d %d %d",
object_isClass(person),
object_isClass([MJPerson class]),
object_isClass(object_getClass([MJPerson class]))
);
// NSLog(@"%p %p", object_getClass([MJPerson class]), [MJPerson class]);
}
成员变量
-
获取一个实例变量信息
Ivar class_getInstanceVariable(Class cls, const char *name) -
拷贝实例变量列表(最后需要调用free释放)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount) -
设置和获取成员变量的值
void object_setIvar(id obj, Ivar ivar, id value) id object_getIvar(id obj, Ivar ivar) -
动态添加成员变量(已经注册的类是不能动态添加成员变量的)
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types) -
获取成员变量的相关信息
const char *ivar_getName(Ivar v) const char *ivar_getTypeEncoding(Ivar v)
示例:
void testIvars()
{
// 获取成员变量信息
// Ivar ageIvar = class_getInstanceVariable([MJPerson class], "_age");
// NSLog(@"%s %s", ivar_getName(ageIvar), ivar_getTypeEncoding(ageIvar));
// 设置和获取成员变量的值
// Ivar nameIvar = class_getInstanceVariable([MJPerson class], "_name");
// MJPerson *person = [[MJPerson alloc] init];
// object_setIvar(person, nameIvar, @"123");
// 数字不能直接转成id类型,指针变量就是存值的,所以可以转为指针变量,可以将指针变量转为id类型
// object_setIvar(person, ageIvar, (__bridge id)(void *)10);
// NSLog(@"%@ %d", person.name, person.age);
// 成员变量的数量
unsigned int count;
Ivar *ivars = class_copyIvarList([MJPerson class], &count);
for (int i = 0; i < count; i++) {
// 取出i位置的成员变量
Ivar ivar = ivars[i];
NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
}
free(ivars);
}
属性
-
获取一个属性
objc_property_t class_getProperty(Class cls, const char *name) -
拷贝属性列表(最后需要调用free释放)
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) -
获取属性的一些信息
const char *property_getName(objc_property_t property) const char *property_getAttributes(objc_property_t property)
方法
-
获得一个实例方法、类方法
Method class_getInstanceMethod(Class cls, SEL name) Method class_getClassMethod(Class cls, SEL name) -
方法实现相关操作
IMP class_getMethodImplementation(Class cls, SEL name) IMP method_setImplementation(Method m, IMP imp) // 交换方法实现 void method_exchangeImplementations(Method m1, Method m2) -
拷贝方法列表(最后需要调用free释放)
Method *class_copyMethodList(Class cls, unsigned int *outCount) -
动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) -
动态替换方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types) -
获取方法的相关信息(带有copy的需要调用free去释放)
SEL method_getName(Method m) IMP method_getImplementation(Method m) const char *method_getTypeEncoding(Method m) unsigned int method_getNumberOfArguments(Method m) char *method_copyReturnType(Method m) char *method_copyArgumentType(Method m, unsigned int index) -
选择器相关
const char *sel_getName(SEL sel) SEL sel_registerName(const char *str) -
用block作为方法实现
IMP imp_implementationWithBlock(id block) id imp_getBlock(IMP anImp) BOOL imp_removeBlock(IMP anImp)
Runtime应用
查看并设置私有成员变量
- (void)viewDidLoad {
[super viewDidLoad];
unsigned int count;
Ivar *ivars = class_copyIvarList([UITextField class], &count);
for (int i = 0; i < count; i++) {
// 取出i位置的成员变量
Ivar ivar = ivars[i];
NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
}
free(ivars);
self.textField.placeholder = @"请输入用户名";
[self.textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
// UILabel *placeholderLabel = [self.textField valueForKeyPath:@"_placeholderLabel"];
// placeholderLabel.textColor = [UIColor redColor];
// NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
// attrs[NSForegroundColorAttributeName] = [UIColor redColor];
// self.textField.attributedPlaceholder = [[NSMutableAttributedString alloc] initWithString:@"请输入用户名" attributes:attrs];
}
字典转模型
@implementation NSObject (Json)
// 只是简单演示,离成型的字典转模型框架还差得远
+ (instancetype)mj_objectWithJson:(NSDictionary *)json
{
id obj = [[self alloc] init];
unsigned int count;
Ivar *ivars = class_copyIvarList(self, &count);
// 利用Runtime遍历所有的属性或者成员变量
for (int i = 0; i < count; i++) {
// 取出i位置的成员变量
Ivar ivar = ivars[i];
NSMutableString *name = [NSMutableString stringWithUTF8String:ivar_getName(ivar)];
[name deleteCharactersInRange:NSMakeRange(0, 1)];
id value = json[name];
if ([name isEqualToString:@"ID"]) {
value = json[@"id"];
}
// 利用KVC设值
[obj setValue:value forKey:name];
}
free(ivars);
return obj;
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 字典转模型
NSDictionary *json = @{
@"id" : @20,
@"age" : @20,
@"weight" : @60,
@"name" : @"Jack"
};
MJPerson *person = [MJPerson mj_objectWithJson:json];
NSLog(@"123");
}
return 0;
}
替换方法实现(Method Swizzle)
@implementation MJPerson
- (void)run
{
NSLog(@"%s", __func__);
}
- (void)test
{
NSLog(@"%s", __func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
MJPerson *person = [[MJPerson alloc] init];
Method runMethod = class_getInstanceMethod([MJPerson class], @selector(run));
Method testMethod = class_getInstanceMethod([MJPerson class], @selector(test));
method_exchangeImplementations(runMethod, testMethod);
[person run];
}
return 0;
}
这种应用对于我们自己编写的类其实用处不大,一般都是用来交换系统或者第三方库里面的方法,添加上一些我们自己的实现。
方法交换的底层逻辑是,将两个方法底层数据结构 Method_t 中的 IMP 进行交换。
@implementation NSMutableArray (Extension)
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 类簇:NSString、NSArray、NSDictionary,真实类型是其他类型
Class cls = NSClassFromString(@"__NSArrayM");
Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
Method method2 = class_getInstanceMethod(cls, @selector(mj_insertObject:atIndex:));
method_exchangeImplementations(method1, method2);
});
}
- (void)mj_insertObject:(id)anObject atIndex:(NSUInteger)index
{
if (anObject == nil) return;
/*
调用系统原来的实现,因为方法的IMP已经交换了,
所以要调用系统原来的实现,就要调用我们自己的方法
*/
[self mj_insertObject:anObject atIndex:index];
}
@end
Method Swizzle注意事项
- 避免交换父类方法
- 交换方法应该在 +load 方法
- 交换方法应该放到 dispatch_once 中执行
- 交换的分类方法应该添加自定义前缀,避免冲突
- 交换的分类方法应调用原实现
在每次的方法交换时都应该注意以上几点,最终我们可以将其封装到NSObject的分类中,方便在调用:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface NSObject (Swizzling)
+ (void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector
swizzledSelector:(SEL)swizzledSelector;
@end
#import "NSObject+Swizzling.h"
@implementation NSObject (Swizzling)
+ (void)methodSwizzlingWithOriginalSelector:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector{
Class class = [self class];
//原有被交换方法
Method originalMethod = class_getInstanceMethod(class, originalSelector);
//要交换的分类新方法
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
//避免交换到父类的方法,先尝试添加被交换方法的新实现IMP
BOOL didAddMethod = class_addMethod(class,originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {//添加成功:则表明没有实现IMP,将源SEL的IMP替换到交换SEL的IMP
class_replaceMethod(class,swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {//添加失败:表明已实现,则可以直接交换实现IMP
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
@end
扩展
Super的本质
[super message] 的消息接收者仍然是子类对象,只不过在查找方法实现的时候从父类开始查找,但是找到方法实现后,还是向子类发送消息。
super调用,底层会转换为 objc_msgSendSuper2 函数的调用,接收2个参数:
-
struct objc_super2
struct objc_super2 { id receiver; // 消息接收者,子类对象 Class current_class; // 消息接收者的Class对象 } -
SEL
在 objc_msgSendSuper2 方法内部,还是会根据 current_class -> superclass 来查找方法实现的。
isMemberOfClass 和 isKindOfClass
// 返回当前对象的类对象是否是传入的对象
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
// 返回当前对象的类对象是否是传入对象的子类
- (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 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;
}
// 这句代码的方法调用者不管是哪个类(只要是NSObject体系下的),都返回YES
NSLog(@"%d", [NSObject isKindOfClass:[NSObject class]]); // 1
NSLog(@"%d", [NSObject isMemberOfClass:[NSObject class]]); // 0
NSLog(@"%d", [MJPerson isKindOfClass:[MJPerson class]]); // 0
NSLog(@"%d", [MJPerson isMemberOfClass:[MJPerson class]]); // 0
LLVM中间代码
Objective-C在变为机器代码之前,会被LLVM编译器转换为中间代码(Intermediate Representation),扩展名为 .ll ,可以使用以下命令行指令生成中间代码:
clang -emit-llvm -S main.m
我们之前在分析底层的时候,都是将OC代码转成C++代码,实际上是有些不准确的,只不过绝大多数时候都是比较准确的,而且C++语法和OC比较类似,易于我们分析底层知识,如果想准确的了解底层到底做了什么,可以将其转成中间代码之后再分析。
语法简介
- @ - 全局变量
- % - 局部变量
- alloca - 在当前执行的函数的堆栈帧中分配内存,当该函数返回到其调用者时,将自动释放内存
- i32 - 32位4字节的整数
- align - 对齐
- load - 读出,store 写入
- icmp - 两个整数值比较,返回布尔值
- br - 选择分支,根据条件来转向label,不根据条件跳转的话类似 goto
- label - 代码标签
- call - 调用函数
具体可以参考官方文档:llvm.org/docs/LangRe…
面试题
1.讲一下OC的消息机制
OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
objc_msgSend底层有3大阶段:消息发送(当前类、父类中查找)、动态方法解析、消息转发
2.消息转发流程
3.什么是Runtime,平时项目中用到过吗?
- OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行
- OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数
- 平时编写的OC代码,底层都是转换成了Runtime API进行调用
具体应用:
- 利用关联对象(AssociatedObject)给分类添加属性
- 遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
- 交换方法实现(交换系统的方法)
- 利用消息转发机制解决方法找不到的异常问题
4.以下代码能不能执行成功?如果可以,打印结果是什么?
@interface MJPerson : NSObject
@property (copy, nonatomic) NSString *name;
- (void)print;
@end
@implementation MJPerson
- (void)print
{
NSLog(@"my name is %@", self->_name);
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
id cls = [MJPerson class];
void *obj = &cls;
[(__bridge id)obj print];
}
@end
可以调用成功,打印:
my name is <ViewController: 0x7fcfac804e00>
-
为什么可以调用成功?
因为obj调用方法和person对象调用方法在底层结构,本质上是一样的。
person对象在调用方法时,首先是找到自己的isa指针,然后根据指针指向的地址找到自己的类对象,从中找到方法实现进行调用,而查找isa指针的过程,其实就是取出自己指向的内存的前八个字节内的数据,因为我们知道isa在对象内存中永远是第一位,而且占用八个字节。
obj在调用print方法时,也是先找到自己指向的内存,也就是cls的内存,而cls里刚好只有8个字节的数据,用来存储指向 [MJPerson class] 的指针,所以obj也找到了类对象,并可以从中找到方法实现并调用。
-
为什么self.name变成了viewcontroller等其他内容?
这里是涉及到栈空间地址分配的问题,我们在使用person对象打印name时,首先要拿到name的值,而取值的时候,底层最根本的其实是找到person对象的isa指针的内存地址,再往后8个字节的内存地址就是name的内存地址。
而obj在调用print方法时,把cls当成是isa指针,所以取name值时,也是找到在cls的内存地址的基础上再加8个字节后的内存地址所指向的数据。
// 局部变量分配在栈空间 // 栈空间分配,从高地址到低地址 void test() { long long a = 4; // 0x7ffee638bff8 long long b = 5; // 0x7ffee638bff0 long long c = 6; // 0x7ffee638bfe8 long long d = 7; // 0x7ffee638bfe0 Abc e = { @"123", 4 }; NSLog(@"%p %p %p %p %p", &a, &b, &c, &d, &e); // 0x7ffee7bd9c18 0x7ffee7bd9c10 0x7ffee7bd9c08 0x7ffee7bd9c00 0x7ffee7bd9bf0 }[super viewDidLoad]内部会创建一个 super_obj 的结构体,也是存放在栈空间,这个结构体中有两个成员变量,self 和[UIViewController Class],所以 cls 的内存地址再加8个字节,刚好是存储的 self,所以打印出来的内容为<ViewController: 0x7fcfac804e00>。