iOS底层第11-->16天 -- Runtime

引导语:

  • OC消息机制是什么?
  • 消息转发流程是什么?
  • 什么是Runtime,项目中如何使用?

前言引导:
为了避免读者观文跑偏,先简单介绍一些基础。

  • OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行(代码运行某个方法函数的时候,不一定执行这个函数而可能执行的是其他方法函数,这就是动态性比较强的表现。这个是通过runtime实现的。)
  • OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数 (我们平时写的OC代码,在转成机器语言0、1之前先转成C语言,转成的底层C语言基本就都是这些RuntimeAPI函数。)
  • 平时编写的OC代码,底层都是转换成了Runtime API进行调用

为甚我们一提到runtime就会讲到消息机制,消息动态解析,消息转发呢?其实是因为这些功能用到的runtimeAPI最明显,也就是runtime的使用场景最明了。
要想学习好runtime还要明白Cass类结构,明白Class内存的结构才能更好的理解为什么方法添加可以在内存结构确定前后都可,而属性添加必须在类结构确定前才可以。
要想清晰了解class内存结构,需要探究一下掩码(也就是需要了解一下按位与)


第一部分 ---> isa

基础学习:iOS底层第三天--isa 和 superclass()

  • 按位与(&),同为1才为1
  • 按位或(|),只要有1就是1
  • 按位取反(~),1置为0,0置为1
  • 取反运算符(!),判断表达式是否为真,为真取假,为假取真

A:什么是isa?
Q:在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址
从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息

A:如何使用一个字节表示三个属性呢?
Q:比如一个类对象有三个属性:高、富、帅。分别是bool值类型,所以这三个属性应该一共占用三个字节。但是我们想优化内存,想通过一个字节表示,然后一个字节中8个位上面占用3个位分别表示这三个属性 比如一个char字符_isTallRichHandsom 值0b 0000 0101 中后三位分别表示高富帅,1表示yes 、0表示no。然后我们对后三位记性位操作即可进行存取操作,从而知道这个人属性。

A:如何通过位操作进行存取?

  • 取值:取第3位bool值 !!(xx &0000 0100 即 xx &4) 此处0000 0100通常定义为宏 作为掩码xxxMask 用来取位的值。 0000 0100 写法优化1<<2代表1向左移动2位。
  • 设值: 如果mask掩码是 0000 0100,则bool设为yes xx | mask 如果bool设为no的话 xx & (~)mask 也就是& 1111 1011

A:什么是位域?
结构体里面的成员,可指定成员占用的位数。

struct {
   char tall;  占1字节
   char rich; 占1字节
   char handsom; 占1字节
} person;

person结构体的成员该占几个字节还是占用几个字节。我们可以通过指定位域来指定占用位数,从而保证结构体的大小

struct {
   char tall :1;      占1位
   char rich:1;      占1位
   char handsom:1;      占1位
} person;

A:什么是共用体?

union {
    char bits;
    struct {
       char tall :1;      
       char rich:1;     
       char handsom:1;      
    } ;
}person;

取对应tall,rich等属性值得时候person.bits & tallMaskperson.bits & richMask这样方式获取指定位的数据
共用体:大家共用一块内存。上面结构体根本不影响结构,只是增加可读性。如果结构体大于一个字节,则bits用int,更大了还可以用long

A:isa底层代码是什么?

union isa_t 
{
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits; //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)
        };
};

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

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

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

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

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

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

7.deallocating
对象是否正在释放

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

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

  • 通过isa指针可以知道是否有关联对象、是否被弱引用过

释放对象调用的方法

/***********************************************************************
* 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;
}

代码可以看出释放对象回判断是否有C++函数和关联对象,如果有会进一步做释放工作。


第二部分--->Class 底层内存结构

Q:为什么此处讲class内存结构
A:我们接下来探讨runtime的消息发送,动态解析,消息转发等内容的时候,需要动态对class内部添加方法,所以需要先了解class内存结构,才能更好明白为什么能动态添加方法,为什么类内存结构确定后就不能再动态添加成员变量。

Q:Class内存结构到底什么样子?
class内存结构底层就是结构体,分别存放isa、superclass、cache、bits这几个成员。如下图
Class内存结构

Class内存结构.png

为了探索内存结构,我们引入小码哥的一个类MJClassInfo

mj_objc_class *cls = (_bridge mj_objc_class *)[MJPerson class];
class_rw_t *data = cls->data();

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

class_ro_t内存结构.png

class_rw_t 介绍
class_rw_t里面的methods、properties、protocols是二维数组,是可读可写的,包含了类的初始内容、分类的内容。(将来我们动态添加方法,协议等都是添加到此结构体里面的相关数组列表中)

class_rw_t内存结构.png

其实方法列表就是ro_t里面的方法,拷贝到rw_t里面。只是rw允许操作,可以后面动态再添加方法。

method_t介绍
method_t是对方法\函数的封装

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

1.SEL

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

2.IMP
IMP代表函数的具体实现

typedef id_Nullable (*IMP) (id_Nonnull,SEl_Nonnull,...);

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

返回参数.png

函数类型:
- (int)test:(int)age height:(float)height;

types显示内容
"i24@0:8i16f20"

types返回内容逐字解释:

  • i:表示返回值 int
  • @:表示id类型
  • “:”:表示SEL
  • f:表示 float
  • 24:表示总共占中24个字节,id(8)+SEL(8)+int(4)+float(4) = 24
  • 0:表示id类型从0字节开始
  • 8:表示SEL类型从8字节开始
  • 16:表示age(int)参数从16字节开始
  • 20:表示height(float)参数从20字节开始

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

Type Encoding.png


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

方法缓存.png

Q:什么是散列表缓存?

  • 在缓存和取值时有个策略:@selector(personTest) & _mask = 索引值f(key) = index
  • 根据这种策略把方法缓存到列表中,如果索引值相同就索引值-1,如果减少到0,就从_mask开始存放
  • 在取值时根据这种策略来直接找到索引值,判断该索引值存放的key是否相同,相同取出IMP;不相同,查找索引值-1 -> 0 ->_mask -> _mask-1 -> ···直到找到相同的key,取出IMP
  • 优点:牺牲内存空间换取读取时间,效率高
  • 一旦数组扩容,就会把缓存清掉,扩容数组容量 = 旧数组容量 * 2

第三部分--->objc_msgSend流程

分为三大阶段:

  • 消息发送
  • 动态方法解析
  • 消息转发

Runtime源码解读流程

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_resolveInstanceMethod
_objc_msgForward_impcache

objc-msg-arm64.s
STATIC_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward

Core Foundation
__forwarding__(不开源)
3.1 消息发送
消息发送.png
  • receiver通过isa指针找到receiverClass
  • receiverClass通过superclass指针找到superClass
  • 如果是从class_rw_t中查找方法,getMethodNoSuper_nolock
    1.已经排序的,二分查找
    2.没有排序的,遍历查找
3.2 动态解析
动态方法解析.png

开发者可以实现以下方法,来动态添加方法实现

  • +resolveInstanceMethod:添加对象方法
  • +resolveClassMethod:添加类方法

动态解析过后,会重新走“消息发送”的流程

  • 从receiverClass的cache中查找方法”这一步开始执行

Q:Runtime动态添加方法的几种方式?

Runtime方式

- (void)other{
    NSLog(@"%s",__func__);
}
+(BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"%s",__func__);
    if (sel == @selector(test)) {
        Method method = class_getInstanceMethod(self, @selector(other));
        //动态添加对象方法,需要给类添加方法
        class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

OC 方式

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

struct method_t {
    SEL sel;
    char *types;
    IMP imp;
};

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(test)) {
        // 获取其他方法
        struct method_t *method = (struct method_t *)class_getInstanceMethod(self, @selector(other));

        // 动态添加test方法的实现
        class_addMethod(self, sel, method->imp, method->types);

        // 返回YES代表有动态添加方法
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

C方式

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

+ (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 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];
}

Q:dynamic修饰变量的含义?
@dynamic是告诉编译器不用自动生成getter和setter的实现,等到运行时再添加方法实现
提醒编译器不要自动生成setter和getter的实现、不要自动生成成员变量

@interface Person : NSObject
@property (nonatomic, assign) int age;
@end

@implementation Person
@dynamic age;
@end
3.3 消息转发

当消息发送、动态解析都没找到方法的时候,则会进行消息转发。
消息转发:自己无能力处理,则转给其它类去处理。


消息转发.png

由于消息转发底层代码没有,则找到国外一份伪代码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) {
            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);
}

3.3.1 指首先派其他对象的方法来完成Person中test方法的调用

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test)) {
        // objc_msgSend([[Cat alloc] init], aSelector)
        return [[Cat alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}

3.3.2 未实现forwardingTargetForSelector,消息转发

// 方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
   if (aSelector == @selector(test)) {
       //types的规则一定要和anInvocation一一对应
       return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
//        return [[[Cat alloc] init] methodSignatureForSelector:aSelector];
   }
   return [super methodSignatureForSelector:aSelector];
}

//该方法可实现指派对象、指派方法,也可以什么都不做
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
//    anInvocation.target = [[Cat alloc] init];
//    [anInvocation invoke];

   [anInvocation invokeWithTarget:[[Cat alloc] init]];
}
// NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
//    anInvocation.target 方法调用者
//    anInvocation.selector 方法名
//    [anInvocation getArgument:NULL atIndex:0]

3.3.3 处理类方法的消息转发

+ (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test))
        return [Cat class];//类方法存在元类中
    return [super forwardingTargetForSelector:aSelector];
}

//如果没有实现+ (id)forwardingTargetForSelector:(SEL)aSelector方法
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test))
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    
    return [super methodSignatureForSelector:aSelector];
}

+ (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSLog(@"1123");
}

对类方法消息转发可采用类对象,不一定要使用元类对象
原因:消息转发objc_msgSend只看消息接收者和方法名

+ (id)forwardingTargetForSelector:(SEL)aSelector
{
    // objc_msgSend([[MJCat alloc] init], @selector(test))
    // [[[Cat alloc] init] test]
    if (aSelector == @selector(test))
        return [[MJCat alloc] init];
    return [super forwardingTargetForSelector:aSelector];
}

第四部分:Runtime之self、super、superclass

Q: 下面代码的输出结果?

Student 类的.m实现 Student的父类是Person
- (instancetype)init{
    if (self = [super init]) {
        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

[super message]的底层实现

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

C语言
objc_msgSendSuper({self,父类},@selector(xx))

再进一步通过汇编分析super本质
super调用,底层会转换为objc_msgSendSuper2函数的调用,接收2个参数:struct objc_super2 和 SEL

struct objc_super2 {
   id receiver;//是消息接收者
   Class current_class;//是receiver的Class对象

};

通过查看这个的源码发现,此处传入的是当前类。
汇编语言:
objc_msgSendSuper2({self,当前类current_class},@selector(xx))

我们可以明白objc_msgSendSuper2相对objc_msgSendSuper方法,其实内部多了一步操作,就是对当前类对象取父类这一步,即current_class->superClass

  1. 消息接收者仍然是子类对象
  2. 从父类开始查找方法的实现
  3. super:消息接收者仍然是当前类对象,只是从父类查找方法的实现

class和superclass

  1. 上述代码中class方法实现在NSObject上
  2. class方法作用:返回当前对象的类对象
  3. superclass方法作用: 返回当前对象的父类对象

Q:下列代码输出结果?

BOOL res1 = [[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [[NSObject class] isMemberOfClass:[NSObject class]];

BOOL res3 = [[Person class] isKindOfClass:[Person class]];
BOOL res4 = [[Person class] isMemberOfClass:[Person class]];
BOOL res5 = [[Person class] isMemberOfClass:[NSObject class]];

NSLog(@"r1:%d r2:%d r3:%d r4:%d r5:%d",res1,res2,res3,res4,res5);

输出结果:
r1:1 r2:0 r3:0 r4:0 r5:0

  • -isMemberOfClass:直接返回两个类是否相等
  • -isKindOfClass:判断调用方法类是否是传入方法的子类。
  • +isMemberOfClass:判断调用类的元类是否相等
  • +isKindOfClass:判断调用类的元类是否是传入类的子类。
  • 两者都能检测一个对象是否是某个类的成员, 两者之间的区别是:isKindOfClass不但可以用来确定一个对象是否是一个类的成员,也可以用来确定一个对象是否是派生自该类的类的成员 ,而isMemberOfClass做不到后一点。

比如classA派生自NSObject类,classA* x = [classA new]; [x isKindOfClass:[NSObject class]] 可以检查出x是否是NSObject派生类的成员,但isMemberOfClass做不到。
NSObject.mm部分源码

+ (void)load {
}

+ (void)initialize {
}

+ (id)self {
    return (id)self;
}

- (id)self {
    return self;
}

+ (Class)class {
    return self;
}

- (Class)class {
    return object_getClass(self);
}

+ (Class)superclass {
    return self->superclass;
}

- (Class)superclass {
    return [self class]->superclass;
}

+ (BOOL)isMemberOfClass:(Class)cls {
    return object_getClass((id)self) == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

+ (BOOL)isSubclassOfClass:(Class)cls {
    for (Class tcls = self; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

+ (BOOL)isAncestorOfObject:(NSObject *)obj {
    for (Class tcls = [obj class]; tcls; tcls = tcls->superclass) {
        if (tcls == self) return YES;
    }
    return NO;
}

第五部分:Runtime-API应用

Q:项目中Runtime应用?

  • 可用统计和处理传错方法或未实现的方法
  • 利用关联对象(AssociatedObject)给分类添加属性
  • 遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
  • 交换方法实现hook(黑魔法 交换系统的方法)
  • 利用消息转发机制解决方法找不到的异常问题

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)
object_setIvar(person,ageIvar,(__bridge id)(void *)10)//age赋值为10
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)

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)

拷贝方法列表(最后需要调用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)

Q:如何交换方法?
void method_exchangeImplementations(Method m1, Method m2)

方法交换操作,实际是把method_t的IMP交换了
自己的方法交换系统的方法(hook):自己的Method需要再次调用自己的Method(已经是系统方法了)
获取方法时候一定要选择当前类的真实类来获取方法(class_getInstanceMethod)
类簇:NSString、NSArray、NSDictionary,真实类型是其他类型。在遇到集合类型时还要注意区分可变不可变两种,此两种所用的真实类型也不同。

补充: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 - 调用函数

综合知识点:
1、super底层传入结构体
2、函数栈空间分配(从高到低地址分配)
3、消息机制,调用方法先找到isa指针
4、访问成员变量本质(访问结构体内某个成员,例如跳过8字节的isa后寻找后面的成员)

具体可以参考官方文档:https://llvm.org/docs/LangRef.html
注明:本文参照小码哥底层视频学习、小白兔等相关文章。

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