Runtime底层原理

Objective-C是一门动态性比较强的编程语言,跟C、C++等语言有着很大的不同
Objective-C的动态性是由Runtime API来支撑的
Runtime API提供的接口基本都是C语言的,源码由C\C++\汇编语言编写

isa详解

  • 要想学习Runtime,首先要了解它底层的一些常用数据结构,比如isa指针
  • 在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址
  • 从arm64架构开始,对isa进行了优化,变成了一个共用体union)结构,还使用位域来存储更多的信息
typedef unsigned long           uintptr_t; 8个字节,64位
union isa_t 
{
    Class cls;
    uintptr_t bits;
# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    struct {
        uintptr_t nonpointer        : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        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;
#       define RC_ONE   (1ULL<<45)
#       define RC_HALF  (1ULL<<18)
    };
};

isa详解 – 位域

nonpointer
0,代表普通的指针,存储着Class、Meta-Class对象的内存地址
1,代表优化过,使用位域存储更多的信息

has_assoc
是否有设置过关联对象,如果没有,释放时会更快

has_cxx_dtor
是否有C++的析构函数(.cxx_destruct),如果没有,释放时会更快

shiftcls
存储着Class、Meta-Class对象的内存地址信息

magic
用于在调试时分辨对象是否未完成初始化

weakly_referenced
是否有被弱引用指向过,如果没有,释放时会更快

deallocating
对象是否正在释放

extra_rc
里面存储的值是引用计数器减1

has_sidetable_rc
引用计数器是否过大无法存储在isa中
如果为1,那么引用计数会存储在一个叫SideTable的类的属性中

共用体(union)例子:

#import <Foundation/Foundation.h>

@interface Person : NSObject

- (void)setTall:(BOOL)tall;
- (void)setRich:(BOOL)rich;
- (void)setHandsome:(BOOL)handsome;

- (BOOL)isTall;
- (BOOL)isRich;
- (BOOL)isHandsome;

@end

#import "Person.h"

#define MJTallMask (1<<0)
#define MJRichMask (1<<1)
#define MJHandsomeMask (1<<2)

@interface Person()
{
    union {
      ***************
      char占用1个字节,8位
      ***************
        char bits;
        struct {
            char tall : 1;
            char rich : 1;
            char handsome : 1;
        };
    } _tallRichHandsome;
}
@end

@implementation Person

- (void)setTall:(BOOL)tall
{
    if (tall) {
        _tallRichHandsome.bits |= MJTallMask;
    } else {
        _tallRichHandsome.bits &= ~MJTallMask;
    }
}

- (BOOL)isTall
{
    return !!(_tallRichHandsome.bits & MJTallMask);
}

- (void)setRich:(BOOL)rich
{
    if (rich) {
        _tallRichHandsome.bits |= MJRichMask;
    } else {
        _tallRichHandsome.bits &= ~MJRichMask;
    }
}

- (BOOL)isRich
{
    return !!(_tallRichHandsome.bits & MJRichMask);
}

- (void)setHandsome:(BOOL)handsome
{
    if (handsome) {
        _tallRichHandsome.bits |= MJHandsomeMask;
    } else {
        _tallRichHandsome.bits &= ~MJHandsomeMask;
    }
}

- (BOOL)isHandsome
{
    return !!(_tallRichHandsome.bits & MJHandsomeMask);
}


@end

Class的结构

苹果开源源码https://opensource.apple.com/tarballs/objc4/

struct objc_object {
private:
    isa_t isa;
}

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;               方法缓存
    class_data_bits_t bits;      用于获取具体的类信息
}

struct class_rw_t {
   ......
private:
    using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t *, class_rw_ext_t *>;
}
struct class_rw_ext_t {
**************************
成员变量是只读的,在类objc_registerClassPair之后就不能添加了
**************************
    const class_ro_t *ro;
**************************
方法是可读写的,objc_registerClassPair之后也可以添加
**************************
    method_array_t methods;  //方法列表
    property_array_t properties; //属性列表
    protocol_array_t protocols; //协议列表
    char *demangledName;
    uint32_t version;
};

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize; //instance对象z占用的内存空间
#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;
}
  • class_rw_ext_t里面的methods、properties、protocols是二维数组,是可读可写的,- 包含了类的初始内容、分类的内容

  • class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容

  • method_t是对方法\函数的封装

struct method_t {
    SEL name;           函数名
    const char *types;  编码(返回值类型、参数类型)
    MethodListIMP imp;  指向函数的指针(函数地址)
...
};

IMP代表函数的具体实现

typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 

SEL代表方法\函数名,一般叫做选择器,底层结构跟char *类似
可以通过@selector()sel_registerName()获得
可以通过sel_getName()NSStringFromSelector()转成字符串
不同类中相同名字的方法,所对应的方法选择器是相同的

typedef struct objc_selector *SEL;

types包含了函数返回值、参数编码的字符串


iOS中提供了一个叫做@encode的指令,可以将具体的类型表示成字符串编码


方法缓存

Class内部结构中有个方法缓存(cache_t),用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度

struct cache_t {
    explicit_atomic<struct bucket_t *> _buckets; 散列表
    explicit_atomic<mask_t> _mask; 散列表的长度
     mask_t _occupied; 已经缓存的方法数量
}
struct bucket_t {
    explicit_atomic<uintptr_t> _imp; 函数内存地址
    explicit_atomic<SEL> _sel;       sel作为key
}

缓存查找
objc-cache.mm

ALWAYS_INLINE
void cache_t::insert(Class cls, SEL sel, IMP imp, id receiver)
{
    ......
    bucket_t *b = buckets();
    mask_t m = capacity - 1;
    mask_t begin = cache_hash(sel, m);
    mask_t i = begin;
    do {
        if (fastpath(b[i].sel() == 0)) {
            incrementOccupied();
            b[i].set<Atomic, Encoded>(sel, imp, cls);
            return;
        }
        if (b[i].sel() == sel) {
            // The entry was added to the cache by some other thread
            // before we grabbed the cacheUpdateLock.
            return;
        }
    } while (fastpath((i = cache_next(i, m)) != begin));
    ......
}
__arm64__
static inline mask_t cache_next(mask_t i, mask_t mask) {
    return i ? i-1 : mask;
}

objc_msgSend执行流程

OC中的方法调用,其实都是转换为objc_msgSend函数的调用
例如:

NSObject *obj = [[NSObject alloc] init];
[obj init];
[NSObject initialize];
***********************
消息接收者(receiver):obj
消息名称:init
***********************
objc_msgSend(obj, @selector(init));
***********************
消息接收者(receiver):[NSObject class]
消息名称:initialize
***********************
objc_msgSend([NSObject class], @selector(initialize));

objc_msgSend的执行流程可以分为3大阶段

1. 消息发送

源码https://opensource.apple.com/tarballs/objc4/
objc-msg-arm64.s

ENTRY _objc_msgSend
.....
END_ENTRY _objc_msgSend

objc-runtime-new.mm

lookUpImpOrForward

源码流程如上,东西太多,就不解析了😊
执行流程如下图


2. 动态方法解析

源码如下:

  // No implementation found. Try method resolver once.
    *************************
    动态方法解析
    *************************
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        *************************
        标志位,执行过后就不在进入该方法
        *************************
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
    .....
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNil(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }
    // chances are that calling the resolver have populated the cache
    // so attempt using it
    *************************
    进入消息发送
    *************************
    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}

动态解析:

#import "Person.h"
#import <objc/runtime.h>

@implementation Person

void c_other(id self, SEL _cmd)
{
    NSLog(@"c_other - %@ - %@", self, NSStringFromSelector(_cmd));
}

****************************
类C方法动态解析
****************************
+ (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];
}

- (void)other
{
    NSLog(@"%s", __func__);
}

****************************
实例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];
}

****************************
实例OC方法动态解析
****************************
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(test)) {
        *****************************
         Method可以理解为等价于
          struct method_t {
                  SEL name;           函数名
                  const char *types;  编码(返回值类型、参数类型)
                  MethodListIMP imp;  指向函数的指针(函数地址)
                  ...
          }
        *****************************
        // 获取其他方法
        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
3. 消息转发

源码没有开源,下面是伪代码

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) {
            return objc_msgSend(forwardingTarget, sel, ...);
        }
    }

    // 调用 methodSignatureForSelector 获取方法签名后再调用 forwardInvocation
    if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
        NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
        if (methodSignature && class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
            NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];

            [receiver forwardInvocation:invocation];

            void *returnValue = NULL;
            [invocation getReturnValue:&value];
            return returnValue;
        }
    }

    if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
        [receiver doesNotRecognizeSelector:sel];
    }

    // The point of no return.
    kill(getpid(), 9);
}

实例方法示例:

@interface Student : NSObject
- (void)test;
@end

@implementation Student
- (void)test
{
    NSLog(@"%s", __func__);
}
@end
@interface Person : NSObject
- (void)test;
@end

#import "Person.h"
#import <objc/runtime.h>
#import "Student.h"

@implementation Person

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) {
        ***********************
        objc_msgSend([[Student alloc] init], aSelector)
        如果 return nil 就进入 methodSignatureForSelector:方法;
        ***********************
        return [[Student alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}
*****************************
方法签名:返回值类型、参数类型
*****************************
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) {
        ***********************
        - (void)test转为C++
        void test(id * self, SEL _cmd)
        void->v  id->@  SEL->: 16->两个参数都是指针占用16个字节,其中id类型参数从0开始
        SEL类型参数从第8个字节开始
        type也可以省略数字v@:
        ***********************
        return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    }
    return [super methodSignatureForSelector:aSelector];
}
  ***********************
  NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
  anInvocation.target 方法调用者
  anInvocation.selector 方法名
  [anInvocation getArgument:NULL atIndex:0]
  ***********************
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    Student *s = [[Student alloc] init];
    SEL sel = anInvocation.selector;
    if ([s respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:s];
    }else{
        [self doesNotRecognizeSelector:sel];
    }
}

@end

调用

Person *person = [[Person alloc] init];
[person test];

类方法示例:

@interface Student : NSObject
+ (void)test;
@end

@implementation Student
+ (void)test{
    NSLog(@"%s", __func__);
}
@end
@interface Person : NSObject
+ (void)test;
@end

#import "Person.h"
#import <objc/runtime.h>
#import "Student.h"

@implementation Person

+ (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) return [Student class];
    return [super forwardingTargetForSelector:aSelector];
}

+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    return [super methodSignatureForSelector:aSelector];
}

+ (void)forwardInvocation:(NSInvocation *)anInvocation
{
    SEL sel = anInvocation.selector;
    if ([[Student class] respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:[Student class]];
    }else{
        [self doesNotRecognizeSelector:sel];
    }
}

@end

调用

 [Person test];

具体流程如下:


forwardInvocation:方法中自定义任何逻辑
以上方法都有对象方法、类方法2个版本(前面可以是加号+,也可以是减号-)

面试题:

1.讲一下 OC 的消息机制
OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
objc_msgSend底层有3大阶段
消息发送(当前类、父类中查找)、动态方法解析、消息转发
....
2.下面代码输出是什么,为什么

@interface Person : NSObject
- (void)run;
@end

@implementation Person
- (void)run
{
    NSLog(@"%s", __func__);
}
@end
@interface Student : Person
@end

@implementation Student

- (void)run
{
    [super run];
}

- (instancetype)init
{
    if (self = [super init]) {
        NSLog(@"[self class] = %@", [self class]); // Student
        NSLog(@"[self superclass] = %@", [self superclass]); // Person
        NSLog(@"--------------------------------");
        NSLog(@"[super class] = %@", [super class]); // Student
        NSLog(@"[super superclass] = %@", [super superclass]); // Person
    }
    return self;
}

@end

结果如下

[self class] = Student
[self superclass] = Person
--------------------------------
[super class] = Student
[super superclass] = Person

首先看[self class]和[self superclass]的底层实现

**************************
实例方法返回self的类对象
**************************
- (Class)class
{
    return object_getClass(self); 返回self的isa指针指向的Class
}
**************************
实例方法返回self的superclass对象
**************************
- (Class)superclass
{
    return class_getSuperclass(object_getClass(self)); 返回self的isa指针指向的Class的Superclass
}

接下来看run方法super调用

- (void)run
{
    [super run];
}

转为C++

static void _I_Student_run(Student * self, SEL _cmd) {
    struct objc_super arg = {(id)self, [Person class]};
    objc_msgSendSuper(arg, @selector(run));
}

struct objc_super {
    __unsafe_unretained _Nonnull id receiver; // 消息接收者
    __unsafe_unretained _Nonnull Class super_class; // 消息接收者的父类
};

********************************
the instance of the class that is to receive the
message and the superclass at which to start searching for the method implementation.
实例的类对象接收消息,也就是方法的调用者Student,方法从父类开始查找
********************************
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

[super message]的底层实现
1.消息接收者仍然是子类对象
2.从父类开始查找方法的实现

注意:
上面super调用转为C++代码是

static void _I_Student_run(Student * self, SEL _cmd) {
    struct objc_super arg = {(id)self, [Person class]};
    objc_msgSendSuper(arg, @selector(run));
}

实际上在运行过程中是转成了

static void _I_Student_run(Student * self, SEL _cmd) {
    struct objc_super2 arg = {(id)self, [Student class]};
    objc_msgSendSuper2(arg, @selector(run));
}

struct objc_super2 {
    id receiver;
    Class current_class;
};

_objc_msgSendSuper2的汇编源码

    ENTRY _objc_msgSendSuper2
    UNWIND _objc_msgSendSuper2, NoFrame

    ldp p0, p16, [x0]       // p0 = real receiver, p16 = class
    ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass
    CacheLookup NORMAL, _objc_msgSendSuper2

    END_ENTRY _objc_msgSendSuper2

_objc_msgSendSuper2虽然接收的两个参数中的class是receiver的Class对象,但是方法寻找还是从父类寻找

证明

- (void)run
{
**********************
      断点
**********************
    [super run];
}

在Debug Workflow->Always Show Disassembly中查看

Interview-super`-[Student run]:
   ......
    0x100000d9e <+46>: call   0x100000f16               ; symbol stub for: objc_msgSendSuper2
   .....

如果不想程序跑起来也可以通过Product->Perform Action ->Assemble "xxx.m"查看当前文件的汇编代码

3.以下代码能不能执行成功?如果可以,打印结果是什么?

@interface Person : NSObject
@property (copy, nonatomic) NSString *name;

- (void)print;
@end

@implementation Person
- (void)print
{
    NSLog(@"my name is %@", self->_name);
}
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    id cls = [Person class];
    void *obj = &cls;
    [(__bridge id)obj print];
}
@end

解答:


person的实例对象指向的内存地址的前8个字节(isa指针)存储的就是Person类对象的内存地址
obj对象指向的内存地址cls存储的是Person类对象
所以cls其实就是isa指针,所以obj就是Person的实例对象,可以调用print

self->_name调用是先找到person对象指向的内存地址,然后忽略isa,在找到_name

- (void)viewDidLoad {
    [super viewDidLoad];
}

static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
    struct objc_super2 arg = {(id)self, [ViewController class]};
    objc_msgSendSuper2(arg, @selector(ViewController));
}

viewDidLoad其实是创建了一个临时的结构体变量(先创建的,位于栈空间的高地址),其中有两个参数self 和 [ViewController class]


类似_name的查找,obj指向的内存地址先忽略cls指针,去找结构体的临时变量,然后忽略self
最终找到[ViewController class],所以最终打印的是

my name is <ViewController: 0x100603610>

4.isKindOfClass和isMemberOfClass的区别
源码NSObject.mm中
先看实例方法源码实现

********************************
判断方法的调用者的类对象是否和传入的cls类型相同
********************************
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

********************************
判断方法的调用者的类对象是否和cls类型相同,或者是cls的子类
********************************
- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
@interface Person : NSObject
@end

@implementation Person
@end
id person = [[Person alloc] init];
NSLog(@"%d", [person isMemberOfClass:[Person class]]);  1
NSLog(@"%d", [person isMemberOfClass:[NSObject class]]); 0
NSLog(@"%d", [person isKindOfClass:[Person class]]); 1
NSLog(@"%d", [person isKindOfClass:[NSObject class]]); 1

在看类方法实现

********************************
判断方法的调用者的元类对象是否和传入的cls类型相同
********************************
+ (BOOL)isMemberOfClass:(Class)cls {
    return object_getClass((id)self) == cls;
}
********************************
判断方法的调用者的元类对象是否和cls类型相同,或者是cls的子类
********************************
+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
 NSLog(@"%d", [Person isMemberOfClass:object_getClass([Person class])]);  1
 NSLog(@"%d", [Person isMemberOfClass:object_getClass([NSObject class])]); 0
********************************
传入的不是元类对象
********************************
 NSLog(@"%d", [Person isMemberOfClass:[NSObject class]]); 0
 NSLog(@"%d", [Person isKindOfClass:object_getClass([NSObject class])]); 1
********************************
基元类的superclass->基类
********************************
 NSLog(@"%d", [Person isKindOfClass:[NSObject class]]); 1

LLVM的中间代码(IR)

  • Objective-C在变为机器代码之前,会被LLVM编译器转换为中间代码(Intermediate Representation)

  • 可以使用以下命令行指令生成中间代码

clang -emit-llvm -S main.m
  • 语法简介

  • @ - 全局变量

  • % - 局部变量

  • alloca - 在当前执行的函数的堆栈帧中分配内存,当该函数返回到其调用者时,将自动释放内存

  • i32 - 32位4字节的整数

  • align - 对齐

  • load - 读出,store 写入

  • icmp - 两个整数值比较,返回布尔值

  • br - 选择分支,根据条件来转向label,不根据条件跳转的话类似 goto

  • label - 代码标签

  • call - 调用函数

  • 具体可以参考官方文档:https://llvm.org/docs/LangRef.html

1.Runtime API – 类

动态创建一个类(参数:父类,类名,额外的内存空间)

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)

2.Runtime API – 成员变量

获取一个实例变量信息

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 run(id self, SEL _cmd)
{
    NSLog(@"_____%@ - %@", self, NSStringFromSelector(_cmd));
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
          ***********************
          创建类
          ***********************
          Class newClass = objc_allocateClassPair([NSObject class], "Person", 0);
          ***********************
          成员变量是只读的,添加要在objc_registerClassPair之前
          ***********************
          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);
          id person = [[newClass alloc] init];
          //KVC设值
          [person setValue:@10 forKey:@"_age"];
          [person setValue:@20 forKey:@"_weight"];
          [person run];
          NSLog(@"%@ %@", [person valueForKey:@"_age"], [person valueForKey:@"_weight"]);
          // 在不需要这个类时释放
          objc_disposeClassPair(newClass);
    }
    return 0;
}
_____ <Person: 0x10053f770> - run
10 20

示例(获取和设置成员变量,遍历一个类的所有成员变量):

@interface Person : NSObject
@property (assign, nonatomic) int age;
@property (copy, nonatomic) NSString *name;
@end
#import <objc/runtime.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
          ***********************
          获取成员变量信息
          ***********************
           Ivar ageIvar = class_getInstanceVariable([Person class], "_age");
           NSLog(@"%s %s", ivar_getName(ageIvar), ivar_getTypeEncoding(ageIvar));
          ***********************
          设置和获取成员变量的值
          ***********************
           Ivar nameIvar = class_getInstanceVariable([Person class], "_name");
           
           Person *person = [[Person alloc] init];
           object_setIvar(person, nameIvar, @"lilei");
           object_setIvar(person, ageIvar, (__bridge id)(void *)10);
           NSLog(@"%@ %d", person.name, person.age);
           
          ***********************
          遍历一个类的所有成员变量
          ***********************
           unsigned int count;
           Ivar *ivars = class_copyIvarList([Person 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);
    }
    return 0;
}

结果如下

 _age i
lilei 10
_age i
_name @"NSString"

运用(字典转模型):

@interface NSObject (Json)
+ (instancetype)njf_objectWithJson:(NSDictionary *)json;
@end

#import "NSObject+Json.h"
#import <objc/runtime.h>

@implementation NSObject (Json)

+ (instancetype)njf_objectWithJson:(NSDictionary *)json
{
    id obj = [[self alloc] init];
    unsigned int count;
    Ivar *ivars = class_copyIvarList(self, &count);
    for (int i = 0; i < count; i++) {
        // 取出i位置的成员变量
        Ivar ivar = ivars[i];
        NSMutableString *name = [NSMutableString stringWithUTF8String:ivar_getName(ivar)];
        [name deleteCharactersInRange:NSMakeRange(0, 1)];
        [obj setValue:json[name] forKey:name];
    }
    free(ivars);
    
    return obj;
}

@end

3.Runtime API – 属性

获取一个属性

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)

4.Runtime API – 方法

获得一个实例方法、类方法

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)

运用(交换方法):

@interface Person : NSObject
- (void)run;

- (void)test;
@end

@implementation Person
- (void)run
{
    NSLog(@"%s", __func__);
}

- (void)test
{
    NSLog(@"%s", __func__);
}
@end

调用

#import <objc/runtime.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        Method runMethod = class_getInstanceMethod([Person class], @selector(run));
        Method testMethod = class_getInstanceMethod([Person class], @selector(test));
        method_exchangeImplementations(runMethod, testMethod);
        [person run];
    }
    return 0;
}

结果如下

-[Person test]
runtime关联对象

思考:如何实现给分类“添加成员变量”?
默认情况下,因为分类底层结构的限制,不能添加成员变量到分类中。但可以通过关联对象来间接实现
关联对象提供了以下API

  • 添加关联对象
void objc_setAssociatedObject(id object, const void * key,
                                id value, objc_AssociationPolicy policy)
  • 获得关联对象
id objc_getAssociatedObject(id object, const void * key)
  • 移除所有的关联对象
void objc_removeAssociatedObjects(id object)

关联对象的原理


例子:

@interface Person (Test)
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) int weight;

@end
#import "Person+Test.h"
#import <objc/runtime.h>

@implementation Person (Test)

- (void)setName:(NSString *)name
{
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name
{
    // 隐式参数
    // _cmd == @selector(name)
    return objc_getAssociatedObject(self, _cmd);
}

- (void)setWeight:(int)weight
{
    objc_setAssociatedObject(self, @selector(weight), @(weight), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (int)weight
{
    // _cmd == @selector(weight)
    return [objc_getAssociatedObject(self, _cmd) intValue];
}

面试题:

什么是Runtime?平时项目中有用过么?
OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行
OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数
平时编写的OC代码,底层都是转换成了Runtime API进行调用

具体应用

  • 利用关联对象(AssociatedObject)给分类添加属性
  • 遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
  • 交换方法实现(交换系统的方法)
  • 利用消息转发机制解决方法找不到的异常问题
    ......
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271