前言学:位域和共用体

一:isa指针--runtime之前的学习

1.1:苹果应用的按位或、按位与

二:类对象信息

2.1:类对象信息:rw_t

2.2:类对象信息:方法缓存(很关键)

2.2:类对象信息:查看缓存

2.3:objc_msgSend

三个阶段:消息发送、动态解析、消息转发

2.4:super

2.5:isKindOfClass AND isMemberOfClass

2.6:一个综合底层问题:super/栈空间/消息机制/访问成员变量的本质/

三:Runtime

3.1:类

3.2:成员变量

3.3:属性

3.4:方法

Objective-C是一门动态性比较强的编程语言,跟C、C++等语言有着很大的不同

Objective-C的动态性是由Runtime API来支撑的

Runtime API提供的接口基本都是C语言的,源码由C\C++\汇编语言编写,调用频次比较高的都是用汇编实现的

前言学:位域和共用体

先学习下c语言的基础知识:位域和共用体,学了这个可以更加了解isa内部,了解了isa,才能更加了解runtime.

api中一旦带有mask的, 有很多,这都是掩码,利用mask取出对应的东西

现在想把这三个bool值存放在一个字节中,声明如下

有详细位运算 请看(有详解 >>1)

方案一:& 、| 和 左移运算符 <<

简单解释一下

与运算符 &

只有对应的二个二进位都为1时,结果位才是1

10010 & 00010 = 00010

或运算符 |

只要对应的二个二进位有一个为1时,结果位就为1

00010 | 10000  = 10010

左移运算符 <<

公式 x << 3 就是把x的各二进位左移3位

1<<1  实际就是  0001 << 1  = 0010   转成十进制后就是  2

1<<4  实际就是  0001 << 4  = 10000  转成十进制后就是  16

#import "MJPerson.h"// &可以用来取出特定的位// 0000 0111
//&0000 0100
//------
// 0000 0100// 掩码,一般用来按位与(&)运算的
//#define MJTallMask 1
//#define MJRichMask 2
//#define MJHandsomeMask 4//#define MJTallMask 0b00000001
//#define MJRichMask 0b00000010
//#define MJHandsomeMask 0b00000100#define MJTallMask (1<<0)  // 1左移一位0 不移动 还是 1
#define MJRichMask (1<<1)   // 1左移一位1  0b0000 001 左移一位 0b0000 0010  是2
#define MJHandsomeMask (1<<2) // 1左移两位 0b0000 001 左移一位 0b0000 0100  是4@interface MJPerson()
{char _tallRichHansome;
}
@end@implementation MJPerson// 0010 1010
//&1111 1101
//----------
// 0010 1000- (instancetype)init
{if (self = [super init]) {_tallRichHansome = 0b00000100;}return self;
}- (void)setTall:(BOOL)tall
{if (tall) { // 传进来的是YES:1_tallRichHansome |= MJTallMask; // 拿到以前的值 或 上掩码,  _tallRichHansome =  _tallRichHansome | MJTallMask;} else {  // 传进来的是NO:0  其他位是1 只有那位是0 再按位与_tallRichHansome &= ~MJTallMask; // 按位取反}
}- (BOOL)isTall
{return !!(_tallRichHansome & MJTallMask);  // 加两个! 就是强转成对应的bool类型,因为一个!代表取反,再加一个就是对应的bool类型
}- (void)setRich:(BOOL)rich
{if (rich) {_tallRichHansome |= MJRichMask;} else {_tallRichHansome &= ~MJRichMask;}
}- (BOOL)isRich
{return !!(_tallRichHansome & MJRichMask);
}- (void)setHandsome:(BOOL)handsome
{if (handsome) {_tallRichHansome |= MJHandsomeMask;} else {_tallRichHansome &= ~MJHandsomeMask;}
}- (BOOL)isHandsome
{return !!(_tallRichHansome & MJHandsomeMask);
}@end

这样调用


// 编写代码 -> 编译链接 -> 运行// Runtime:运行时
// 提供了一套C语言APIint main(int argc, const char * argv[]) {@autoreleasepool {MJPerson *person = [[MJPerson alloc] init];person.rich = YES;person.tall = NO;person.handsome = NO;NSLog(@"tall:%d rich:%d hansome:%d", person.isTall, person.isRich, person.isHandsome);
//        person.tall = YES;
//        person.rich = YES;
//        person.handsome = NO;//        NSLog(@"%zd", class_getInstanceSize([MJPerson class]));}return 0;
}
2018-09-14 11:03:20.098862+0800 Interview01-Runtime[3312:201940] tall:0 rich:1 hansome:0

上面已经实现了取值 并把三个原来的bool属性存放在了一个字节中。并且取值和赋值都实现了。

详细看图解释一下

  • 按位或运算符:

只要你想将某一位置位1,并且其他的都不变,:我只需要找到哪一位 或上 这个掩码就行

把对应的位 变成想要的东西。其他值不要动。按位与,这样就把倒数第二位置位0 :拿到掩码 进行取反,再按位与操作即可

  • 按位与运算符:

取反是:~

继续完善上面的方案 (上述维护起来比较麻烦)可读性更好些

方案二:结构体的位域实现

位域是指信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几个不同的区域, 并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。

注意

  • 使用位域的结构体所占的内存空间为结构体中占用内存空间最多的结构体变量所占用的内存空间大小,如:有long和int,最终结构所占的内存空间为long类型所占用的内存空间(8字节)


#import "MJPerson.h"//#define MJTallMask (1<<0)
//#define MJRichMask (1<<1)
//#define MJHandsomeMask (1<<2)@interface MJPerson()
{// 位域 结构体支持:位域这个结构struct {  // 所以这个整体就是一个字节,里面有三位是下面的 先写的位数越低,会在最右边的一位,后写的往前char tall : 1; //  char tall; 只是这样的话会占三个字节, 但是写个:1 就代表只占一位,char rich : 1;char handsome : 1;} _tallRichHandsome;
}
@end@implementation MJPerson- (void)setTall:(BOOL)tall
{_tallRichHandsome.tall = tall;
}- (BOOL)isTall
{//  这里用双!:这里赋值的是1 但是取出来_tallRichHandsome.tall的却是-1,因为bool类型是8位,而_tallRichHandsome.tall只有一个二进制位,讲一个二进制位强制转成8位,所以其他位置强制转成了1,所以用双!解决。
// 同时领一个解决方案是吧 char rich : 1; 这三个都变成2位,就是 char rich : 2;因为rich是2位,:0b01 转成0b0000 0001 倒数第二个是赋值的0 位域的特点。return !!_tallRichHandsome.tall;
}- (void)setRich:(BOOL)rich
{_tallRichHandsome.rich = rich;
}- (BOOL)isRich
{return !!_tallRichHandsome.rich;
}- (void)setHandsome:(BOOL)handsome
{_tallRichHandsome.handsome = handsome;
}- (BOOL)isHandsome
{return !!_tallRichHandsome.handsome;
}@end
int main(int argc, const char * argv[]) {@autoreleasepool {MJPerson *person = [[MJPerson alloc] init];person.rich = YES;person.tall = YES;person.handsome = YES;NSLog(@"tall:%d rich:%d hansome:%d", person.isTall, person.isRich, person.isHandsome);}return 0;
}

方案三:苹果官方用的方案:依然采取位运算 +共用体

共用体把几种不同数据类型的变量存放在同一块内存里。共用体中的变量共享同一块内存。

定义共用体类型变量的一般形式:

union 共用体名

{

成员列表

}变量列表;

与结构体类似,变量列表是可选的。如果没有变量列表,只是定义了一种共用体类型。

(0xff是255 是-1 用一个字节存 都是0xff  区别是:无符号是255 有符号就是-1)


@interface MJPerson : NSObject
//@property (assign, nonatomic, getter=isTall) BOOL tall;
//@property (assign, nonatomic, getter=isRich) BOOL rich;
//@property (assign, nonatomic, getter=isHansome) BOOL handsome;- (void)setTall:(BOOL)tall;
- (void)setRich:(BOOL)rich;
- (void)setHandsome:(BOOL)handsome;
- (void)setThin:(BOOL)thin;- (BOOL)isTall;
- (BOOL)isRich;
- (BOOL)isHandsome;
- (BOOL)isThin;@end#import "MJPerson.h"#define MJTallMask (1<<0)
#define MJRichMask (1<<1)
#define MJHandsomeMask (1<<2)
#define MJThinMask (1<<3)/*存取继续使用位运算, 使用位域来增加可读性 存放的位置依旧由上面的mask来决定的*/// 结构体可以拥有很多成员,并且独立存在。比方说tall 和rich、handsome、thin,每个都占1个字节@interface MJPerson()
{union { // 共用体 :大家共用一块内存,bits和里面的结构体 最大占用是1个字节(里面的结构体占一个字节),所以共用1个字节。char bits;struct { // 这样的结构仅仅是为了可读性,而不影响运算 :因为下面我取的是bits 而不是这个里面的结构体。char tall : 1;char rich : 1;char handsome : 1;char thin : 1;};} _tallRichHandsome;//    union { // 这样跟以前讲的char 字节是没有什么区别的。
//        char bits;
//    } _tallRichHandsome;
}
@end@implementation MJPerson- (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);
}- (void)setThin:(BOOL)thin
{if (thin) {_tallRichHandsome.bits |= MJThinMask;} else {_tallRichHandsome.bits &= ~MJThinMask;}
}- (BOOL)isThin
{return !!(_tallRichHandsome.bits & MJThinMask);
}@end
// 如果是4位 掩码就要变union { int bits;struct { char tall : 4;  // 掩码 就是 4位都要是4char rich : 4;char handsome : 4;char thin : 4;};} _tallRichHandsome;#define MJTallMask (0b1111<<0)  //0b11110000   掩码就不能按照之前那么写

一:isa指针--runtime之前的学习

要想学习Runtime,首先要了解它底层的一些常用数据结构,比如isa指针

在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址

从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息

首先在源码中找到isa指针,看一下isa指针的本质

// 截取objc_object内部分代码
struct objc_object {
private:isa_t isa;
}

isa指针其实是一个isa_t类型的共用体,来到isa_t内部查看其结构

// 精简过的isa_t共用体
union isa_t
{isa_t() { }isa_t(uintptr_t value) : bits(value) { }Class cls;uintptr_t bits;  // 只需要关注这个即可#if SUPPORT_PACKED_ISA
# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULLstruct {uintptr_t nonpointer        : 1;uintptr_t has_assoc         : 1;uintptr_t has_cxx_dtor      : 1;uintptr_t shiftcls          : 33; // MACH_VM_MAX_ADDRESS 0x1000000000uintptr_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)};# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULLstruct {uintptr_t nonpointer        : 1;uintptr_t has_assoc         : 1;uintptr_t has_cxx_dtor      : 1;uintptr_t shiftcls          : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000uintptr_t magic             : 6;uintptr_t weakly_referenced : 1;uintptr_t deallocating      : 1;uintptr_t has_sidetable_rc  : 1;uintptr_t extra_rc          : 8;
#       define RC_ONE   (1ULL<<56)
#       define RC_HALF  (1ULL<<7)};# else
#   error unknown architecture for packed isa
# endif
#endif

上面精简一下 就是下面这个

位域详解

shiftcls 这个就是类和原类的值

这33个1就是取类对象和原类对象的值。

类对象和原类对象 最后三位 一定是0,因为&的值,在源码中那个值右边空出了三位,最后三位需加上这000

一个16进制位代表4个二进制位看打印结果最后一个是0就是代表4个二进制的0,8:0x1000.

在arm64之前,isa就是一个普通的指针,里面存储着类对象和原类对象的地址值。从arm64开始,isa经过了优化,它采用共用体的结构,将一个64位的内存数据分来存储很多东西,其中33位存储地址值。

1.1:苹果应用的按位或、按位与

我们在oc项目中经常会用到按位或,看下面例子

    self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin;NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;[self addObserver:self forKeyPath:@"age" options:options context:NULL];

看UIViewAutoresizing源码是

typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {UIViewAutoresizingNone                 = 0,UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,UIViewAutoresizingFlexibleWidth        = 1 << 1,UIViewAutoresizingFlexibleRightMargin  = 1 << 2,UIViewAutoresizingFlexibleTopMargin    = 1 << 3,UIViewAutoresizingFlexibleHeight       = 1 << 4,UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};

那这个设计原理是什么呢?下面模拟

#import "ViewController.h"//typedef enum {
//    MJOptionsOne = 1,   // 0b0001
//    MJOptionsTwo = 2,   // 0b0010
//    MJOptionsThree = 4, // 0b0100
//    MJOptionsFour = 8   // 0b1000
//} MJOptions;typedef enum {
//    MJOptionsNone = 0,    // 0b0000MJOptionsOne = 1<<0,   // 0b0001  2的零次方MJOptionsTwo = 1<<1,   // 0b0010  2的一次方MJOptionsThree = 1<<2, // 0b0100  2的二次方MJOptionsFour = 1<<3   // 0b1000  2的三次方
} MJOptions;@interface ViewController ()@end@implementation ViewController/*0b00010b00100b1000------ 上面是所有传进来的值 或在一起的结果是下面这个
|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 | MJOptionsFour]; 这种情况下跟下面相加的是一致的,但是这个值是有技巧的都是2的次幂。
//    [self setOptions: MJOptionsOne + MJOptionsTwo + MJOptionsFour];
}@end

设置一个枚举,枚举内部值都是设定好的值,在外部传入的时候 都是按位或,某些方面上可以相加,得到最终一个综合的传入的枚举值,然后拿到这个枚举综合值后需要在内部判断前面传入了那些值,所以再按位与上值判断传入的是否有某个值,就知道传入的是什么值了。

二:类对象信息

类对象内部结构和元类对象内部结构一致,只是里面的数据亦一样,元类对象是一种特殊的类对象,里面放的东西比较少,只有类方法。

bits 与上mask得到tw_t结构,rw是r:可读,w:可写,加起来就是可读写的意思。ro:只读的意思。

2.1:类对象信息:rw_t

我们先看这个方法列表:

其实这个methods事一个二维数组列表。那我们可以看到ro_t中也有一份结构,也有一个列表,但是thehodList结构确实一维的。

原本RO_T 类的所有的方法 成员变量 协议等信息 一开始都是放在ro_t里面,当程序运行起来,会将分类和原来的类合并在一起的时候,就会将ro_t里面的东西 跟分类的东西合并起来 放在rw_t中,相当于有一部分tw_t中的东西是从ro_t中来的。从分类的源码中可以看到,在合并分类的时候rw的内存是重新分配的,可以看分类合并的源码

/***********************************************************************
* realizeClass
* Performs first-time initialization on class cls,
* including allocating its read-write data.
* Returns the real class structure for the class.
* Locking: runtimeLock must be write-locked by the caller
**********************************************************************/
static Class realizeClass(Class cls)
{runtimeLock.assertWriting();const class_ro_t *ro;class_rw_t *rw;Class supercls;Class metacls;bool isMeta;if (!cls) return nil;if (cls->isRealized()) return cls;assert(cls == remapClass(cls));// fixme verify class is not in an un-dlopened part of the shared cache?ro = (const class_ro_t *)cls->data();if (ro->flags & RO_FUTURE) {// This was a future class. rw data is already allocated.rw = cls->data();ro = cls->data()->ro;cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);} else {// Normal class. Allocate writeable class data.rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);rw->ro = ro;  // 把rw指向原来的ro !!rw->flags = RW_REALIZED|RW_REALIZING;cls->setData(rw);  // 这里把原来的 cls指向重新分配内存的rw,!!}isMeta = ro->flags & RO_META;rw->version = isMeta ? 7 : 0;  // old runtime went up to 6// Choose an index for this class.// Sets cls->instancesRequireRawIsa if indexes no more indexes are availablecls->chooseClassArrayIndex();if (PrintConnecting) {_objc_inform("CLASS: realizing class '%s'%s %p %p #%u", cls->nameForLogging(), isMeta ? " (meta)" : "", (void*)cls, ro, cls->classArrayIndex());}// Realize superclass and metaclass, if they aren't already.// This needs to be done after RW_REALIZED is set above, for root classes.// This needs to be done after class index is chosen, for root metaclasses.supercls = realizeClass(remapClass(cls->superclass));metacls = realizeClass(remapClass(cls->ISA()));#if SUPPORT_NONPOINTER_ISA// Disable non-pointer isa for some classes and/or platforms.// Set instancesRequireRawIsa.bool instancesRequireRawIsa = cls->instancesRequireRawIsa();bool rawIsaIsInherited = false;static bool hackedDispatch = false;if (DisableNonpointerIsa) {// Non-pointer isa disabled by environment or app SDK versioninstancesRequireRawIsa = true;}else if (!hackedDispatch  &&  !(ro->flags & RO_META)  &&  0 == strcmp(ro->name, "OS_object")) {// hack for libdispatch et al - isa also acts as vtable pointerhackedDispatch = true;instancesRequireRawIsa = true;}else if (supercls  &&  supercls->superclass  &&  supercls->instancesRequireRawIsa()) {// This is also propagated by addSubclass() // but nonpointer isa setup needs it earlier.// Special case: instancesRequireRawIsa does not propagate // from root class to root metaclassinstancesRequireRawIsa = true;rawIsaIsInherited = true;}if (instancesRequireRawIsa) {cls->setInstancesRequireRawIsa(rawIsaIsInherited);}
// SUPPORT_NONPOINTER_ISA
#endif// Update superclass and metaclass in case of remappingcls->superclass = supercls;cls->initClassIsa(metacls);// Reconcile instance variable offsets / layout.// This may reallocate class_ro_t, updating our ro variable.if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);// Set fastInstanceSize if it wasn't set already.cls->setInstanceSize(ro->instanceSize);// Copy some flags from ro to rwif (ro->flags & RO_HAS_CXX_STRUCTORS) {cls->setHasCxxDtor();if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {cls->setHasCxxCtor();}}// Connect this class to its superclass's subclass listsif (supercls) {addSubclass(supercls, cls);} else {addRootClass(cls);}// Attach categoriesmethodizeClass(cls);return cls;
}

看上文中摘出来的部分 是下面,做了解释

 if (ro->flags & RO_FUTURE) {// This was a future class. rw data is already allocated.rw = cls->data();ro = cls->data()->ro;cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);} else {// Normal class. Allocate writeable class data.rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);rw->ro = ro;  // 把rw指向原来的ro !!rw->flags = RW_REALIZED|RW_REALIZING;cls->setData(rw);  // 这里把原来的 cls指向重新分配内存的rw,!!}

2.2:method_t

这个结构存在于列表信息中,让我们看一下组成结构

struct method_t {SEL name;  // 选择器 方法名 @selector 或者sel_registerName获得const char *types; // 编码:返回值类型、参数类型IMP imp;  // 指向函数的指针 指针类型 存放函数地址struct SortBySELAddress :public std::binary_function<const method_t&,const method_t&, bool>{bool operator() (const method_t& lhs,const method_t& rhs){ return lhs.name < rhs.name; }};
};

这里需要介绍一下范围值类型和参数,看一个Person返回的方法返回值

v16@0:8

v:void

@:id类型

: seletor类型

i: 返回值类型 int类型

@:是第一个参数是id类型 就是消息接受者

:seletor类型 _cmd 方法名 第二个参数,

i: 第三个参数了 int类型 age

f: 第四个参数,height

最前面的24:代表所有参数占的字节数.

id 和SEL都是指针 加起来是16个字节,int 和float都是4 加起来是8 所以就是16+ 8 = 24个字节

@0 :@代表第一个参数,这个0代表这个参数从哪里开始,第一个参数是从0这个位置开始的。也就是0id

:8       :代表第二个参数,说明这个参数从第8个字节开始的参数。

i16 :    i:代表int,int前面已经是16个字节了,从16个字节开始的参数。

f20:  f:float 表示从第20个字节开始的参数。

这种技术是为了配合运行时的一种技术,把一个方法的返回值类型和字符类型转换成字符串的形式储存起来。

要了解 上面 我们需要知道Type Encoding

2.2:类对象信息:方法缓存(很关键)

我们可以知道,一个class结构中有这个结构cache_t cache;

struct objc_class : objc_object {Class isa;Class superclass;cache_t cache;             // formerly cache pointer and vtableclass_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
}

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

那我们需要先知道一下正常的没有cache的方法访问流程。这个流程不清楚的点这里

通过isa先找到类,在类中找到方法列表,因为这是一个数组,所以需要遍历寻找,如果类中没有,需要接着通过superclass找到父类的方法列表遍历寻找,如果没找到,就一直这样找,直到NSObject,如果还没找到就消息转发,最后报错。

但是我们cache的作用就在这,当调用过一次的方法,就会放在这个类对象的chache里面,当下次再调用这个方法的时候,它会直接从cache里面寻找而不是直接去遍历查找。这样就大大缩减了寻找速度(即使这个方法第一次是从基类中查找到的)当然从基类中找到了(先找基类缓存再找方法列表) 也会把这个方法缓存到自己类对象的缓存中(一直是这个流程)。

那我看看一下cache的内部结构 这个散列表 底层结构就是一个数组

那这个寻找过程是什么样的呢?

当isa找到cache_t这个结构体,再找buckets这个数组中的bucket_t(这个查找过程是下面的方式,通过哈希算法),然后比较外部调用的key跟bucket_t里面的key是否一致,如果一致,就证明是一个,然后直接调用这个_imp,也就是这个函数的地址。

补充知识:

散列表知识结构:空间换时间

散列表跟普通遍历不同 这是利用一套算法,哈希算法(不同人写的散列表算法是不同的 有些是求余)。这个详细解读请看这里

这种做法是 牺牲一些内存空间来提高效率,以空间换时间

这个运行过程简单的说就是,刚开始第一次存放的时候就以这个方法名&mask(散列表长度-1)得到的值就是存放的位置。下次寻找的时候就依旧&mask得到这个值然后找到这个值的位置直接取出来。效率大大的高。

如果mask的值的结果都是一样的呢

会将得到的值减去1 如果没有值就存进去,如果还一样,就继续减 如果是0 就变成mask(也就是数组长度-1)直到找到有空间为止  看源码

static inline mask_t cache_hash(cache_key_t key, mask_t mask)
{return (mask_t)(key & mask);
}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);// hackClass cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));cache_t::bad_cache(receiver, (SEL)k, cls);
}#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;
}

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 masknewCapacity = 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 thisassert(newCapacity > 0);assert((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1);setBucketsAndMask(newBuckets, newCapacity - 1);if (freeOld) {   // 看这里free旧的cache_collect_free(oldBuckets, oldCapacity);cache_collect(false);}
}

一开始的长度是一定的,不够了再扩充。

2.2:类对象信息:查看缓存

struct cache_t {bucket_t *_buckets;mask_t _mask;mask_t _occupied;IMP imp(SEL selector){mask_t begin = _mask & (long long)selector;mask_t i = begin;do {if (_buckets[i]._key == 0  ||  _buckets[i]._key == (long long)selector) {return _buckets[i]._imp;}} while ((i = cache_next(i, _mask)) != begin);return NULL;}
};

上面是自定义的MJClassInfo类中的关于imp的写法,这个跟源码内部的写法一致,现在就直接可以取出来,并且取出来的值都是对的

2.3:objc_msgSend

  MJGoodStudent *gs = [[MJGoodStudent alloc] init];[gs personTest];
        MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init"));((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("personTest"));

sel_registerName("personTest") = @selector(personTest);

@selector的本质可以说是转成了sel_registerName 这个c语言函数

// OC的方法调用:消息机制,给方法调用者发送消息

[person personTest];

// objc_msgSend(person, @selector(personTest));

// 消息接收者(receiver):person

// 消息名称:personTest

objc_msgSend执行流程

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

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

a:消息发送((找遍所有的类 父类 自己类)如果发送成功,就不会进入动态方法解析阶段)

b:动态方法解析(如果消息发送阶段没有找到合适的方法进行调用,允许我们开发者动态创建一个自己的类,到动态解析阶段)

c:消息转发(如果动态方法解析阶段,没有任何的动态调用,就会转成别的调用。)(如果三个阶段都没有找到则报一个错误:unrecognized selector sent to instance)

源码跟读

接着详细解读三个阶段

阶段一:begin=消息发送=================================

.macro ENTRY /* name */.text.align 5.globl    $0
$0:

.macro:宏定义

看一下objc_msgsend的内部实现:这是汇编语言

 ENTRY _objc_msgSendUNWIND _objc_msgSend, NoFrameMESSENGER_STARTcmp  x0, #0          // nil check and tagged pointer check //  x0寄存器:消息接受者 receiver:相当于传进去的第一个参数  先判断消息接受者是否<=0 一旦<=0 一旦是nil就会跳转到LNilOrTagged:b.le LNilOrTagged        //  (MSB tagged pointer looks negative)ldr  x13, [x0]       // x13 = isaand    x16, x13, #ISA_MASK // x16 = class
LGetIsaDone:CacheLookup NORMAL      // calls imp or objc_msgSend_uncached  缓存查找LNilOrTagged:b.eq    LReturnZero     // nil check 返回0// taggedmov    x10, #0xf000000000000000cmp x0, x10b.hs LExtTagadrp x10, _objc_debug_taggedpointer_classes@PAGEadd x10, x10, _objc_debug_taggedpointer_classes@PAGEOFFubfx    x11, x0, #60, #4ldr x16, [x10, x11, LSL #3]b    LGetIsaDoneLExtTag:// ext taggedadrp    x10, _objc_debug_taggedpointer_ext_classes@PAGEadd x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFFubfx    x11, x0, #52, #8ldr x16, [x10, x11, LSL #3]b    LGetIsaDoneLReturnZero:// x0 is already zeromov x1, #0movi  d0, #0movi  d1, #0movi  d2, #0movi  d3, #0MESSENGER_END_NILret  // 相当于returnEND_ENTRY _objc_msgSend

CacheLookup是一个宏 可以看到内部_cmd & mask:查缓存

.macro CacheLookup// x1 = SEL, x16 = isaldp    x10, x11, [x16, #CACHE] // x10 = buckets, x11 = occupied|maskand  w12, w1, w11        // x12 = _cmd & maskadd    x12, x10, x12, LSL #4   // x12 = buckets + ((_cmd & mask)<<4)ldp    x9, x17, [x12]      // {x9, x17} = *bucket
1:  cmp x9, x1          // if (bucket->sel != _cmd)b.ne 2f          //     scan moreCacheHit $0         // call or return imp CacheHit:命中,查找到,直接调用2:  // not hit: x12 = not-hit bucketCheckMiss $0           // miss if bucket->sel == 0  没有命中cmp   x12, x10        // wrap if bucket == bucketsb.eq  3fldp   x9, x17, [x12, #-16]!   // {x9, x17} = *--bucketb  1b          // loop3:   // wrap: x12 = first bucket, w11 = maskadd    x12, x12, w11, UXTW #4  // x12 = buckets+(mask<<4)// Clone scanning loop to miss instead of hang when cache is corrupt.// The slow path may detect any corruption and halt later.ldp    x9, x17, [x12]      // {x9, x17} = *bucket
1:  cmp x9, x1          // if (bucket->sel != _cmd)b.ne 2f          //     scan moreCacheHit $0         // call or return imp2: // not hit: x12 = not-hit bucketCheckMiss $0           // miss if bucket->sel == 0cmp x12, x10        // wrap if bucket == bucketsb.eq  3fldp   x9, x17, [x12, #-16]!   // {x9, x17} = *--bucketb  1b          // loop3:   // double wrapJumpMiss $0.endmacro

查看宏CheckMiss

.macro CheckMiss// miss if bucket->sel == 0
.if $0 == GETIMPcbz   x9, LGetImpMiss
.elseif $0 == NORMAL  // 照这个因为刚开始传进去的就是mormalcbz  x9, __objc_msgSend_uncached
.elseif $0 == LOOKUPcbz   x9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro

入口:STATIC_ENTRY  这个__objc_msgSend_uncached 会调用MethodTableLookup

.endmacroSTATIC_ENTRY __objc_msgSend_uncachedUNWIND __objc_msgSend_uncached, FrameWithNoSaves// THIS IS NOT A CALLABLE C FUNCTION// Out-of-band x16 is the class to searchMethodTableLookupbr    x17END_ENTRY __objc_msgSend_uncached
.macro MethodTableLookup// push framestp fp, lr, [sp, #-16]!mov  fp, sp// save parameter registers: x0..x8, q0..q7sub    sp, sp, #(10*8 + 8*16)stp  q0, q1, [sp, #(0*16)]stp    q2, q3, [sp, #(2*16)]stp    q4, q5, [sp, #(4*16)]stp    q6, q7, [sp, #(6*16)]stp    x0, x1, [sp, #(8*16+0*8)]stp   x2, x3, [sp, #(8*16+2*8)]stp   x4, x5, [sp, #(8*16+4*8)]stp   x6, x7, [sp, #(8*16+6*8)]str   x8,     [sp, #(8*16+8*8)]// receiver and selector already in x0 and x1mov  x2, x16bl   __class_lookupMethodAndLoadCache3

汇编中已经找不到__class_lookupMethodAndLoadCache3了,那从c语言中找需要去掉一个下划线_class_lookupMethodAndLoadCache3

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver)
{IMP imp = nil;bool triedResolver = NO;runtimeLock.assertUnlocked();// Optimistic cache lookupif (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. 刚才在汇编中找过,现在又找一遍,怕动态添加后,// 第一阶段:消息发送阶段imp = cache_getImp(cls, sel);if (imp) goto done;// Try this class's method lists.{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. 去父类的缓存和方法中查找{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中,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. 父类缓存中没有 从方法中找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.// 第二阶段:动态解析阶段 没有经历过动态解析,才会进行 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.// 第三阶段:消息转发阶段imp = (IMP)_objc_msgForward_impcache;cache_fill(cls, sel, imp, inst);done:runtimeLock.unlockRead();return imp;  // 这里返回返回到上一个函数 再返回就是返回到汇编
}

查找getMethodNoSuper_nolock

类对象调用data方法返回的就是rw_t 也即是拿的是rw_t中的methods

getMethodNoSuper_nolock(Class cls, SEL sel)
{runtimeLock.assertLocked();assert(cls->isRealized());// fixme nil cls? // fixme nil sel?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;
}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 listfor (auto& meth : *mlist) {  // for循环查找if (meth.name == sel) return &meth;}}#if DEBUG// sanity-check negative resultsif (mlist->isFixedUp()) {for (auto& meth : *mlist) {if (meth.name == sel) {_objc_fatal("linear search worked when binary search did not");}}}
#endifreturn 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;
}

现在形象化的解释一下

阶段二:begin=动态解析=================================

如果当初编写代码的时候没有那个方法,可以在程序运行的时候添加一个方法来实现。

// 这里接上面   lookUpImpOrForward内部  前面的方法没有找到,这里就进行到动态解析阶段 解析过一遍,下一次就不会动态解析了
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;  // 这里找到之后又会回到第一个阶段,因为已经动态添加了一个方法}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);}}
}static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) {// Resolver not implemented.return;}BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;bool resolved = msg(cls, SEL_resolveInstanceMethod, sel); // 让类调用这个方法// Cache the result (good or bad) so the resolver doesn't fire next time.// +resolveInstanceMethod adds to self a.k.a. clsIMP imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/);if (resolved  &&  PrintResolving) {if (imp) {_objc_inform("RESOLVE: method %c[%s %s] ""dynamically resolved to %p", cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel), imp);}else {// Method resolver didn't add anything?_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"", but no new implementation of %c[%s %s] was found",cls->nameForLogging(), sel_getName(sel), cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel));}}
}

动态方法解析阶段 会调用这两个方法

//+ (BOOL)resolveClassMethod:(SEL)sel

//+ (BOOL)resolveInstanceMethod:(SEL)sel

这个对类和元类的流程是一样的。只有调用方法不一样,

对于类调用的是resolveInstanceMethod

对于元类 调用的是resolveClassMethod

好,接着用图形象化表示一下

现在看一下oc代码

@interface MJPerson : NSObject
- (void)test;
//+ (void)test;
@end#import "MJPerson.h"
#import <objc/runtime.h>@implementation MJPersonvoid c_other(id self, SEL _cmd)
{// 来到c语言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];
//}// typedef struct objc_method *Method;  一个Method代表一个方法
// struct objc_method == struct method_t
//        struct method_t *otherMethod = (struct method_t *)class_getInstanceMethod(self, @selector(other));//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];
//}@end

调用

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

动态的方法添加到了rw_t的 method_list_t *methods;中 。在goty retry中会添加到缓存中;

应用场景:并不是很常用。可以在用dynamic的时候用。在编译的时候不生成实现,在运行的时候添加

#import "MJPerson.h"
#import <objc/runtime.h>@implementation MJPerson// 提醒编译器不要自动生成setter和getter的实现、不要自动生成成员变量
@dynamic age;void setAge(id self, SEL _cmd, int age)
{NSLog(@"age is %d", age);
}int age(id self, SEL _cmd)
{return 120;
}+ (BOOL)resolveInstanceMethod:(SEL)sel
{if (sel == @selector(setAge:)) {class_addMethod(self, sel, (IMP)setAge, "v@:i");return YES;} else if (sel == @selector(age)) {class_addMethod(self, sel, (IMP)age, "i@:");return YES;}return [super resolveInstanceMethod:sel];
}//@synthesize age = _age, height = _height;//- (void)setAge:(int)age
//{
//    _age = age;
//}
//
//- (int)age
//{
//    return _age;
//}@end

阶段三:begin=消息转发=================================

消息转发:将消息转发给别人。

  // 消息转发阶段imp = (IMP)_objc_msgForward_impcache;cache_fill(cls, sel, imp, inst);

查找_objc_msgForward_impcache这个方法,会发现这个方法在汇编中但是里面会找不到最终的代码实现,有国外写的伪代码

但是都会调用__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 获取方法签名后再调用 forwardInvocationif (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);
}

看一下oc代码,调用逻辑

#import "MJCat.h"@implementation MJCat
- (void)test
{NSLog(@"%s", __func__);
}
@end#import <Foundation/Foundation.h>@interface MJPerson : NSObject
- (void)test;
@end#import "MJPerson.h"
#import <objc/runtime.h>
#import "MJCat.h"@implementation MJPerson//+ (BOOL)resolveInstanceMethod:(SEL)sel
//{
//    class_addMethod(<#Class  _Nullable __unsafe_unretained cls#>, <#SEL  _Nonnull name#>, <#IMP  _Nonnull imp#>, <#const char * _Nullable types#>)
//}// 如果没有实现这个方法 就相当于返回空 就会调用签名方法methodSignatureForSelector
- (id)forwardingTargetForSelector:(SEL)aSelector
{if (aSelector == @selector(test)) {// objc_msgSend([[MJCat alloc] init], aSelector)return [[MJCat alloc] init];}return [super forwardingTargetForSelector:aSelector];
}// 方法签名:返回值类型、参数类型 如果这个返回的是有值的,则就会调用forwardInvocation方法 会把返回结果封装到NSInvocation中
// 这两个方法特别之处是:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{if (aSelector == @selector(test)) {return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];}return [super methodSignatureForSelector:aSelector];
}// NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
//    anInvocation.target 方法调用者
//    anInvocation.selector 方法名
//    [anInvocation getArgument:NULL atIndex:0]
- (void)forwardInvocation:(NSInvocation *)anInvocation
{// 一种方式
//    anInvocation.target = [[MJCat alloc] init]; // 将来方法调用者就是cat了,方法名和参数不需要给,因为掐面已经告诉它了
//    [anInvocation invoke];// 第二种方式[anInvocation invokeWithTarget:[[MJCat alloc] init]];
}@end

调用

int main(int argc, const char * argv[]) {@autoreleasepool {MJPerson *person = [[MJPerson alloc] init];[person test];}return 0;
}

这里补充一下,forwardingTargetForSelector方法是选择器转发目标,找到一个转发的目标,也是找一个新的可以实现这个方法的对象来达到不崩溃的目的。所以这里面转发给MJCat这个类,底部调用方法          objc_msgSend(forwardingTarget, sel, ...);也就是说给MJCat这个类走了一遍objc_msgSend找方法。

如果MJCat实现了test方法,则直接调用。

如果MJCat的分类实现了test方法,那先调用分类的

如果MJCat没有实现,test方法,同样实现了forwardingTargetForSelector,里面转发给MJDog,而MJDog里面也实现了test方法,则最后会调用MJDog的test方法实现。其实就是调用Person的test方法最后会转发到MJDog的test方法里。

那有问题会问,为什么forwardingTargetForSelector 也可以直接返回对象,为啥还要签名?因为如果一旦返回到签名这里,权限就要比forwardingTargetForSelector大的多,可以想做什么就做什么。因为在forwardingTargetForSelector方法里需要返回对象,然后有这个对象的方法实现。但是在签名后的forwardInvocation方法中,却不用,想不做都可以,可以设置一个新的对象,可以不设置对象直接拿参数,从NSInvocation 新包装的类中取出来,同样还可以什么都不做。

#import <Foundation/Foundation.h>@interface MJPerson : NSObject
- (int)test:(int)age;
@end#import "MJPerson.h"
#import <objc/runtime.h>
#import "MJCat.h"@implementation MJPerson// 如果这里返回为nil 则这里就不想调用
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{if (aSelector == @selector(test:)) {
//        return [NSMethodSignature signatureWithObjCTypes:"v20@0:8i16"];return [NSMethodSignature signatureWithObjCTypes:"i@:i"];
//        return [[[MJCat alloc] init] methodSignatureForSelector:aSelector]; // 前提是这个对象有这个方法}return [super methodSignatureForSelector:aSelector];
}// 尽情的来这里处理 权限很大 即使不做任何事情都可以 可以添加对象 也可以不用
- (void)forwardInvocation:(NSInvocation *)anInvocation
{//          0         1        2// 参数顺序:receiver、selector、other arguments// 1:没有传对象,直接取参数int age;[anInvocation getArgument:&age atIndex:2];NSLog(@"%d", age + 10);// anInvocation.target == [[MJCat alloc] init]// anInvocation.selector == test:// anInvocation的参数:15// [[[MJCat alloc] init] test:15]// 2:新对象,直接取参数
//    [anInvocation invokeWithTarget:[[MJCat alloc] init]]; // 这里可以让MJCat的test方法得到调用
//
//    int ret;
//    [anInvocation getReturnValue:&ret];
//
//    NSLog(@"%d", ret);
// 这里还可以记录一下所有的未找到方法的列表// 3:这个作用域里什么代码都不写
}@end

调用

int main(int argc, const char * argv[]) {@autoreleasepool {MJPerson *person = [[MJPerson alloc] init];[person test:15];}return 0;
}

Objective-C运行时的核心就在于消息分派器objc_msgSend,消息分派器把选择器映射为函数指针,并调用被引用的函数。 要想理解objc_msgSend的背后原理,先来理解下NSInvocation这个类。

NSInvocation是命令模式的一种传统实现,它把一个目标、一个选择器、一个方法签名和所有的参数都塞进一个对象里,这个对象可以先存储起来,以备将来调用。当NSInvocation被调用时,它会发送信息,Objective-C运行时会找到正确的方法实现来执行。我们通过一个例子来理解下NSInvocation的作用,比如[NSObject alloc],此时会发送一个alloc消息,这条消息都包含什么内容呢?它怎么找到alloc的实现方法呢?这些都是通过NSInvocation来完成的,它包含了消息要传递的内容,也告诉了该怎么找到对应的方法实现

解释一下什么是方法实现?一个方法实现(IMP)是一个指向具有如下签名的C函数的函数指针,注意是指针

id function(id self, SEL _cmd, ...)

NSInvocation包含了一个目标和选择器,目标是一个可接受的对象,选择器则是被发送的消息。比如[NSObject alloc],目标就是NSObject,选择器就是alloc。一个选择器大致是一个方法的名称,之所以说是大致是因为选择器不必精确映射到方法。比如[NSString length]和[NSData length]会映射到不同方法的实现,但他们拥有相同的选择器。

NSInvocation还包含一个方法签名(NSMethodSignature),它封装了一个方法的返回类型和参数类型,记住它不包括方法名称,只有返回类型和参数类型。你可以手动创建一个方法签名,如下:

NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"@@:*"];

但是应该尽可能少使用signatureWithObjCTypes:方法,获得方法签名常用的方法是为它请求一个类或实例,比如可以使用methodSignatureForSelector:方法从实例中请求实例方法签名,或者从类中请求类方法签名。也可以使用instanceMethodSignatureForSelector:方法从一个类中获取实例方法签名。两个方法有点绕口,我们通过一个例子来看下区别:

SEL initSEL = @selector(init);
SEL allocSEL = @selector(alloc);// 从NSString类中获取实例方法(init)的方法签名
NSMethodSignature *initSig = [NSString instanceMethodSignatureForSelector:initSEL];
// 从test实例中获取实例方法(init)的方法签名
initSig = [@"test" methodSignatureForSelector:initSEL];
// 从NSString类中获取类方法签名
NSMethodSignature *allocSig = [NSString methodSignatureForSelector:allocSEL];

最后,NSInvocation还包含了所有的参数。至此,对于[NSString length]和[NSData length]就可以通过NSInvocation对象包含的信息,找到它们分别对应的方法实现。我们来看一个具体的例子,如下:

NSMutableSet *set = [NSMutableSet set];
NSString *stuff = @"stuff";
SEL selector = @selector(addObject:);
NSMethodSignature *sig = [set methodSignatureForSelector:selector];
44
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
[invocation setTarget:set];
[invocation setSelector:selector];
[invocation setArgument:&stuff atIndex:2];
[invocation invoke];
44
NSLog(@"set is : %@", set);

注意,第一个参数被置于索引2处,索引0是目标(self),索引1是选择器(_cmd),NSInvocation会自动设置它们。另外,必须把参数指针传递给参数,而不能传递参数本身。

现在讲 类的消息转发


#import <Foundation/Foundation.h>@interface MJPerson : NSObject
+ (void)test;
@end#import "MJPerson.h"
#import <objc/runtime.h>
#import "MJCat.h"@implementation MJPerson// 动态解析阶段
//+ (BOOL)resolveClassMethod:(SEL)sel
//{
//
//}// 类方法的消息转发是存在的  但是直接打 没有提示而已
+ (id)forwardingTargetForSelector:(SEL)aSelector
{// 一旦不返回空 这里里面调用的就是objc_msgSend// objc_msgSend([[MJCat alloc] init], @selector(test))// [[[MJCat alloc] init] test]
//    if (aSelector == @selector(test)) return [[MJCat alloc] init]; // 这个是可以的,因为内部调用的是objc_msgSendif (aSelector == @selector(test)) return [MJCat 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
//{
//    NSLog(@"1123");
//}@end
// 元类对象是一种特殊的类对象int main(int argc, const char * argv[]) {@autoreleasepool {[MJPerson test];}return 0;
}

消息转发用途:解决找不到问题的错误

通过perform传过来的方法,传过来的东西不确定,降低崩溃率,思路是

#import "MJPerson.h"@implementation MJPerson- (void)run
{NSLog(@"run-123");
}- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{// 本来能调用的方法if ([self respondsToSelector:aSelector]) {return [super methodSignatureForSelector:aSelector];}// 找不到的方法return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}// 找不到的方法,都会来到这里
- (void)forwardInvocation:(NSInvocation *)anInvocation
{// 可以再这里收集一下找不到方法的记录NSLog(@"找不到%@方法", NSStringFromSelector(anInvocation.selector));
}
@end// NSProxy :专门用来做消息转发的类

补充一个小知识点

@dynamic

@dynamic是告诉编译器不用自动生成getter和setter的实现,等到运行时再添加方法实现

一般来说@prperty会自动帮我们生成set和get方法,这个自动生成是因为@synehesize这个关键字

@synehesize age = _age;  为age这个属性 生成一个_age的成员变量 并且自动生成set方法 并且赋值 自动生成get方法并返回,

@synehesize age;只写age, 那成员变量名就叫age。

@dynamic age;没有set和get方法的实现了,但是不影响外面的声明,因为你依旧可以调用。

这个时候就可以用消息动态解析 的过程了了。

2.4:super

对于这个经常有一个面试题就是这样的

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

上面打印的结果分别是什么呢?结果是

2018-09-20 13:48:20.964936+0800 Interview05-super[14679:292783] [self class] = MJStudent
2018-09-20 13:48:20.965191+0800 Interview05-super[14679:292783] [self superclass] = MJPerson
2018-09-20 13:48:20.965203+0800 Interview05-super[14679:292783] --------------------------------
2018-09-20 13:48:20.965215+0800 Interview05-super[14679:292783] [super class] = MJStudent
2018-09-20 13:48:20.965226+0800 Interview05-super[14679:292783] [super superclass] = MJPerson

那对于[super class] 为什么结果也是student呢?为啥不是父类person呢?为啥 [super superclass]结果是person呢

我们来看super的内部结构,先看下面这个代码

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

源码是

struct __rw_objc_super { struct objc_object *object; struct objc_object *superClass; __rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {}
};static void _I_MJStudent_run(MJStudent * self, SEL _cmd) {((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("MJStudent"))}, sel_registerName("run"));((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("MJStudent"))},sel_registerName("class"));
}

整理一下就是下面这个样子

    objc_msgSendSuper((__rw_objc_super){(id)self,(id)class_getSuperclass(objc_getClass("MJStudent"))  // class_getSuperclass 拿到student的父类},sel_registerName("run"));  //  简化就是下面的形式struct objc_super arg = {self, [MJPerson class]};objc_msgSendSuper(arg, @selector(run));

可以看到objc_msgSendSuper里面有两个值,一个是__rw_objc_super类型的结构体,一个是selector,结构体一个是消息接受者,一个是接受者的父类

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

再从objc源码中查找objc_msgSendSuper是啥意思 只有一段英语注释

/**

* 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

*/

从这段注释中可以得知,第一个就是消息接受者,第二个就是父类,作用是啥呢,就是start searching for the method implementation.也就是从哪里开始搜索这个方法。

所以,当调用[super run];这个方法的时候,首先会把自己和自己的父类封装在objc_super结构体中,然后跟方法selector一起放入objc_msgSendSuper中,查找的过程就是,super调用的receiver仍然是MJStudent对象,但是查找的时候是从父类开始查找,并不是student查找。如果是正常的方法调用,是从自己的方法中开始找,而super的话是从父类中开始找。

so,紧接着说class。

首先我们需要知道,class这个类的实现是

@implementation NSObject- (Class)class
{return object_getClass(self);
}- (Class)superclass
{return class_getSuperclass(object_getClass(self));
}@end

所以返回值取决于self是谁,也就是方法调用者,也就是消息接受着:receiver。 很明显,你传入的是谁,这个调用者就是谁,返回的根据这个传入者返回。所以当[super class]的时候其实就是发送了一个这样的消息

objc_msgSendSuper({self, [MJPerson class]}, @selector(class));

而这个消息的接收者是self,但是会先从person中查找class,因为是从父类查找,但是父类没有class,一直找到了NSObject的class,这个class返回的是object_getClass(self);也就是消息接收者self,也就是student,所以这得到的结果是MJStudent。

而[super superclass],这个也是从person中查找class,接收者是student,也就是消息接收者是student,所以传进去的是student,但是superclass返回的是父类,所以student的父类就是person。so,结果是MJPerson。

so,总结一下就是

[super message] 的底层实现

1.消息接收者仍然是子类对象

2.从父类开始查找方法的实现

super的本质

之前我们说super内部调用的是objc_msgSendSuper,但其实真正的不是这样的,真正转成的底层代码是别的东西,真正生成的跟我们之前看到的c++代码是有点差异的。我们可以在super处打个断点,然后Debug  -> Debug Workingflow -> Aways show Disassembly,开始看汇编代码,会发现真正调用的是这个objc_msgSendSuper2

super调用,底层会转换为objc_msgSendSuper2函数的调用,接收2个参数

struct objc_super2

SEL

receiver是消息接收者

current_class是receiver的Class对象

这里会发现传进去的发生了变化,current_class是当前类对象,实际上函数内部逻辑也发生了变化,当拿到这个结构体的时候,会调用第二个成员的superclass,找到父类,从它父类身上开始搜索方法。

看底层汇编源码

ENTRY _objc_msgSendSuper2UNWIND _objc_msgSendSuper2, NoFrameMESSENGER_STARTldp   x0, x16, [x0]       // x0 = real receiver, x16 = classldr x16, [x16, #SUPERCLASS] // x16 = class->superclass  这里就是superclassCacheLookup NORMALEND_ENTRY _objc_msgSendSuper2

从另外一个角度证明是objc_msgSendSuper2 :看结构体中放的是什么

可以根据下面cls跳过16个字节找到那个存储的是啥即可(这里看不懂的请看下面这个题即可)。

所以这里调用的是objc_msgSendSuper2 因为第二个传进去的是当前控制器的类型 而不是UIViewcontroller。

所以要知道这个c++代码大部分是一样的,但是某些细节性的东西是不一样的,是有些诧异的,但是还是可以做参考的

还可以通过另外一种方式来查看汇编代码

将person.m文件转成汇编代码(不用将程序跑起来): Product -> Perform Action -> Assemble "MJPerson.m"

其实

LLVM 编译器 (中间代码是这个编译器特有的) 中间代码是通用的,希望跨平台,不用指定平台 但是语法又是全新的一种

OC -> 中间代码(.ll) -> 汇编、机器代码

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

2.5:isKindOfClass AND isMemberOfClass

这两个用途,是比较是否是这个类 或者这个类的子类,看应用

int main(int argc, const char * argv[]) {@autoreleasepool {id person = [[MJPerson alloc] init];// 是否刚好等于右边的类型NSLog(@"%d", [person isMemberOfClass:[MJPerson class]]);NSLog(@"%d", [person isMemberOfClass:[NSObject class]]);// 左边是否是有右边的类或者子类// 实例对象的类对象是类对象NSLog(@"%d", [person isKindOfClass:[MJPerson class]]); // 1NSLog(@"%d", [person isKindOfClass:[NSObject class]]); // 1// 类对象的类对象是元类对象NSLog(@"%d", [[MJPerson class] isMemberOfClass:object_getClass([MJPerson class])]); // 1NSLog(@"%d", [MJPerson isKindOfClass:object_getClass([NSObject class])]); // 1NSLog(@"%d", [MJPerson isKindOfClass:[NSObject class]]);// 1 这个比较特殊 因为一直到基类的元类对象,基类的元类对象的superclass指向基类的类对象,所以nsobject==nsobject}return 0;
}

上面已经给出答案了,看这个需要看源码

@implementation NSObject- (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;
}
@end

可以看到,对于

实例方法isMemberOfClass:比较的是自己的类对象,所以这个需要传入的就是自己的类对象[Person class]

实例方法isKindOfClass:比较的是不是自己的类或者自己的子类,因为这有一个for循环,一直寻找父类然后比较,需要传入的也是类对象[Person class]

类方法isMemberOfClass:比较的是自己的元类对象,所以这个需要传入的就是自己的元类对象object_getClass([MJPerson class])

类方法isKindOfClass:比较的是不是自己的原类对象或者原类对象的子类,所以这个需要传入的也是自己的元类对象object_getClass([MJPerson class])

这个里面需要注意一点就是[MJPerson isKindOfClass:[NSObject class]]  这个不需要传元类对象都可以,这个比较特殊 因为一直到基类的元类对象,基类的元类对象的superclass指向基类的类对象,所以nsobject==nsobject。

so

      // 这句代码的方法调用者不管是哪个类(只要是NSObject体系下的),都返回YESNSLog(@"%d", [NSObject isKindOfClass:[NSObject class]]); // 1NSLog(@"%d", [NSObject isMemberOfClass:[NSObject class]]); // 0NSLog(@"%d", [MJPerson isKindOfClass:[MJPerson class]]); // 0NSLog(@"%d", [MJPerson isMemberOfClass:[MJPerson class]]); // 0         

总结

实例对象:是拿类对象来比较

类对象:是拿元类对象来比较

2.6:一个综合底层问题:super/栈空间/消息机制/访问成员变量的本质/


@interface MJPerson : NSObject
@property (copy, nonatomic) NSString *name;- (void)print;
@end#import "MJPerson.h"@implementation MJPerson- (void)print
{NSLog(@"my name is %@", self->_name); // 这个self这个指针找到实例对象的内存,在寻找的就是MJPerson_IMP之后的isa(8个字节)之后的8个字节也就是_name。
}@end

调用

- (void)viewDidLoad {[super viewDidLoad];NSString *test = @"123";
//    NSObject *obj2 = [[NSObject alloc] init];
//id cls = [MJPerson class];void *obj = &cls;[(__bridge id)obj print];}

这个结果是啥呢,是123

2018-09-20 19:57:08.796869+0800 Interview02-super[25924:719905] my name is 123

那为啥是这样呢,如果我们把test注掉,结果又发生了变化,变成

2018-09-20 20:35:03.636348+0800 Interview02-super[26220:792569] my name is <ViewController: 0x7fe44151f0e0>

那为啥是这样呢?

先看一个小知识

// 局部变量分配在栈空间
// 栈空间分配,从高地址到低地址 连续分配
void test()
{long long a = 4; // 0x7ffee638bff8long long b = 5; // 0x7ffee638bff0long long c = 6; // 0x7ffee638bfe8long long d = 7; // 0x7ffee638bfe0NSLog(@"%p %p %p %p", &a, &b, &c, &d);
}int main(int argc, char * argv[]) {@autoreleasepool {test();return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));}
}

会发现在栈上分配的局部变量 都是连续的,从高地址,到低地址,这个非常重要。接着分析

看这个,这两条路线是一致的,下面只是我们普通的指针路线,也是寻找路线,上面是这个题目里给出的obj指向cls的地址,而这个cls存储的是Person的地址。所以,理论上这两条线是一样的,唯一的不一样就是cls这里没有另外的变量,而isa这里是有_name这个变量的。

那现在我们需要知道一个很重要的知识,就是:

正常person寻找name的原理是:self.name:

这个self这个指针找到实例对象的内存,在寻找的就是跳过MJPerson_IMP的isa(8个字节)找到之后的8个字节也就是_name。

这个很重要是因为这个解释了为啥打印出来时test的123.

请看下面

这个test cls  obj三个局部变量都在栈空间  test这个局部变量内存地址最大 而且顺序排列 所以找到的cls后面的8个字节 所以打印出来的就是123。

但是为啥不写变量之后,会有打印:Viewcontroller呢?

看下图

因为在cls之前调用了[super viewDidLoad];这个很关键啊,因为super内部实现会发现,它实现的方法是这样的

    struct abc = {self,[ViewController class]};objc_msgSendSuper2(abc, sel_registerName("viewDidLoad"));

相当于这个时候定义了一个abc的结构体变量,这个变量有两个成员,self和[viewcontroller class],注意,这里是结构体,而不是结构体指针。所以这里成员变量的内存占用情况如上图所示,所以obj指向name的时候就会跳过cls这个8个字节,找下8个字节也就是self了,也就是UIViewcontroller了。

如果这里把[super viewDidLoad]; 去掉,那么打印的会是谁呢?这个就不一定了,因为不知道会打印谁了。就会产生坏内存访问了。

栈空间的分配:都是从高地址向低地址分配的

这里插入一个图,以便分析理解 :栈:先创建先放入(地址高),后放入地址低,查找顺序,从高地址向低地址。

三:Runtime

3.1:类

首先看一下所有方法

动态创建一个类(参数:父类,类名,额外的内存空间)
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));
}

1: 动态修改内存中的语言,如果是c++ java是无法修改的

void testcard()
{MJPerson *person = [[MJPerson alloc] init];[person run];object_setClass(person, [MJCar class]);[person run];
}

这里再setclass之后 run调用的就是car的run方法

2:  动态创建类

void testClass()
{Class newClass = objc_allocateClassPair([NSObject class], "MJDog", 0);// 建议在注册类之前添加这些成员变量、属性和方法
//(成员变量是只读的,类的结构一旦创建,就不能再添加成员变量 ro_t 方法的添加可以放在注册之后,
// 因为方法是放在rw_t中)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);
}

3.2:成员变量

获取一个实例变量信息 (获取的描述信息,并不是实际值和信息)
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)

3.2.1 获取成员变量信息 

void testIvars()
{// 3: 获取成员变量信息 获取的描述信息,并不是实际值和信息Ivar ageIvar = class_getInstanceVariable([MJPerson class], "_age");NSLog(@"%s %s", ivar_getName(ageIvar), ivar_getTypeEncoding(ageIvar));// 3.1: 设置和获取成员变量的值Ivar nameIvar = class_getInstanceVariable([MJPerson class], "_name");MJPerson *person = [[MJPerson alloc] init];object_setIvar(person, nameIvar, @"123");  // 设置的name stringobject_setIvar(person, ageIvar, (__bridge id)(void *)10);NSLog(@"%@ %d", person.name, person.age);// 4: 成员变量的数量  copy一个成员变量列表 最常用unsigned int count;Ivar *ivars = class_copyIvarList([MJPerson class], &count);for (int i = 0; i < count; i++) {// 取出i位置的成员变量Ivar ivar = ivars[i]; // 指针可以当做数组来用,相当于是*(ivars + i) 指针跟着移动 然后取出地址NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));}free(ivars); // 需要释放
}

3.2.2字典转模型

void testModel()
{// 5:字典转模型功能NSDictionary *json = @{@"id" : @20,@"age" : @20,@"weight" : @60,@"name" : @"Jack"//                               @"no" : @30};MJPerson *person = [MJPerson mj_objectWithJson:json];[MJCar mj_objectWithJson:json];MJStudent *student = [MJStudent mj_objectWithJson:json];NSLog(@"123");
}#import "NSObject+Json.h"
#import <objc/runtime.h>@implementation NSObject (Json)// 这里只是非常简单的字典转模型
//(万一这里有继承体系,而父类的属性没有赋值,因为方法获取体系只能获取本类的,
// 这个就还需要获取superclass,还有可能数值是空的,还有模型嵌套模型的问题 还有id的问题)
+ (instancetype)mj_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)];// 删除下划线// 设值id value = json[name];// 这样写不合适,因为这是一个框架,不能写死if ([name isEqualToString:@"ID"]) {value = json[@"id"];}[obj setValue:value forKey:name];}free(ivars);return obj;
}@end

3.2.3:修改系统类的未公开成员变量

- (void)viewDidLoad {[super viewDidLoad];// 方法2  通过runtime来获得内部有啥成员变量 然后设置那个颜色值
//    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 = @"请输入用户名";// 方法2 后续 直接比下面更一步到位[self.textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];// 方法2 后续 用kvc直接赋值UILabel *placeholderLabel = [self.textField valueForKeyPath:@"_placeholderLabel"];placeholderLabel.textColor = [UIColor redColor];// 方法1NSMutableDictionary *attrs = [NSMutableDictionary dictionary];attrs[NSForegroundColorAttributeName] = [UIColor redColor];self.textField.attributedPlaceholder = [[NSMutableAttributedString alloc] initWithString:@"请输入用户名" attributes:attrs];
}

3.3:属性

获取一个属性
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)

3.4:方法

获得一个实例方法、类方法
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)

3.4.1:替换方法

void test()
{MJPerson *person = [[MJPerson alloc] init];// 1:替换方法:对象方法,(一般要替换一个类方法 这里需要传进去的就是元类对象,需注意)class_replaceMethod([MJPerson class], @selector(run), (IMP)myrun, "v");// 2:imp_implementationWithBlock 这里传入的是block,将block包装秤了一个imp方法class_replaceMethod([MJPerson class], @selector(run), imp_implementationWithBlock(^{NSLog(@"123123");}), "v");[person run];
}void myrun()
{NSLog(@"---myrun");
}

3.4.2:交换方法

int main(int argc, const char * argv[]) {@autoreleasepool {MJPerson *person = [[MJPerson alloc] init];// 3 交换方法实现,很常用,Method runMethod = class_getInstanceMethod([MJPerson class], @selector(run));Method testMethod = class_getInstanceMethod([MJPerson class], @selector(test));method_exchangeImplementations(runMethod, testMethod);[person run];}return 0;
}#import "MJPerson.h"@implementation MJPerson
- (void)run
{NSLog(@"%s", __func__);
}- (void)test
{NSLog(@"%s", __func__);
}
@end

比较大的价值是交换别人写的框架或者系统写的一些类,(比方,我需要拿到整个项目所有的按钮的点击事件,但是还需要实现原来的方法 跟覆盖不同)

例子1:

#import "UIControl+Extension.h"
#import <objc/runtime.h>@implementation UIControl (Extension)+ (void)load
{// hook:钩子函数Method method1 = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));Method method2 = class_getInstanceMethod(self, @selector(mj_sendAction:to:forEvent:));method_exchangeImplementations(method1, method2);
}- (void)mj_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{NSLog(@"%@-%@-%@", self, target, NSStringFromSelector(action));// 调用系统原来的实现  :此时两个方法已经置换了[self mj_sendAction:action to:target forEvent:event];//    [target performSelector:action];//    if ([self isKindOfClass:[UIButton class]]) {
//        // 拦截了所有按钮的事件
//
//    }
}@end#import "ViewController.h"@interface ViewController ()@end@implementation ViewController- (IBAction)click1 {NSLog(@"%s", __func__);
}- (IBAction)click2 {NSLog(@"%s", __func__);
}- (IBAction)click3 {NSLog(@"%s", __func__);
}@end

交换本质:根据@selector(sendAction:to:forEvent)去寻找,rw_t中method_t中的IMP, 交换的是IMP. 一旦调用这个交换方法,就会清空缓存,内部源代码

void method_exchangeImplementations(Method m1, Method m2)
{if (!m1  ||  !m2) return;rwlock_writer_t lock(runtimeLock);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?flushCaches(nil); // 清空缓存updateCustomRR_AWZ(nil, m1);updateCustomRR_AWZ(nil, m2);
}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); // erase 橡皮擦 抹掉});}else {foreach_realized_class_and_metaclass(^(Class c){cache_erase_nolock(c);});}
}

例子2:

数组或者字典的判空处理。(详细数组请看这里)

#import "NSMutableArray+Extension.h"
#import <objc/runtime.h>@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;[self mj_insertObject:anObject atIndex:index];
}@end- (void)viewDidLoad {[super viewDidLoad];// Do any additional setup after loading the view, typically from a nib.// 调用NSString *obj = nil;NSMutableArray *array = [NSMutableArray array];[array addObject:@"jack"];[array insertObject:obj atIndex:0];
}

字典为空(集合类型的要注意是可变的还是不可变的,详细字典请看:这里)

#import "NSMutableDictionary+Extension.h"
#import <objc/runtime.h>@implementation NSMutableDictionary (Extension)+ (void)load
{static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{Class cls = NSClassFromString(@"__NSDictionaryM"); // 不可变 (最终父类)Method method1 = class_getInstanceMethod(cls, @selector(setObject:forKeyedSubscript:));Method method2 = class_getInstanceMethod(cls, @selector(mj_setObject:forKeyedSubscript:));method_exchangeImplementations(method1, method2);Class cls2 = NSClassFromString(@"__NSDictionaryI"); // 可变 (最终父类)Method method3 = class_getInstanceMethod(cls2, @selector(objectForKeyedSubscript:));Method method4 = class_getInstanceMethod(cls2, @selector(mj_objectForKeyedSubscript:));method_exchangeImplementations(method3, method4);});
}
// 赋值操作
- (void)mj_setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key
{if (!key) return;[self mj_setObject:obj forKeyedSubscript:key];
}
// 取值操作
- (id)mj_objectForKeyedSubscript:(id)key
{if (!key) return nil;return [self mj_objectForKeyedSubscript:key];
}@end- (void)viewDidLoad {[super viewDidLoad];// Do any additional setup after loading the view, typically from a nib.NSString *obj = nil;NSMutableDictionary *dict = [NSMutableDictionary dictionary];dict[@"name"] = @"jack";dict[obj] = @"rose";dict[@"age"] = obj;NSLog(@"%@", dict);NSDictionary *dict2 = @{@"name" : [[NSObject alloc] init],@"age" : @"jack"};NSString *value =  dict2[nil];NSLog(@"%@", [dict2 class]);
}

例子3:设置字体。

面试:

1:讲述一下OC的消息机制

OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)

objc_msgSend底层有3大阶段

消息发送(当前类、父类中查找)、动态方法解析、消息转发

2:消息转发机制流程

看前面三个图片总结

3:什么是Runtime?平时项目中有用过么?

OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行

OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数

平时编写的OC代码,底层都是转换成了Runtime API进行调用

具体应用

利用关联对象(AssociatedObject)给分类添加属性

遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)

交换方法实现(交换系统的方法)

利用消息转发机制解决方法找不到的异常问题

weak弱指针 底层实现 依赖runtime

......

iOS runtime 底层详解、内部原理、场景应用相关推荐

  1. ios Block底层详解、框架结构:捕获、对象类型、__block、__forwarding、循环引用

    1:捕获 2:block类型 2.1:问题 :mrc环境下  下面讲的都是mrc环境下,会真实很多 2.2:在arc下,block 自动加上copy的情况---:返回block. 2.3:在arc下, ...

  2. JVM 虚拟机详解内部原理(小白必看!)

    前言 作为一名Java软件开发程序猿,不了解JVM?那么你就只能干CRUD的工作! 前几天刚学习了JVM,把学习到的经验在这里和大家分享下,有啥意见,欢迎在下方评论交流! 1.什么是JVM? JVM全 ...

  3. iOS 开发:『Runtime』详解(二)Method Swizzling

    本文用来介绍 iOS 开发中『Runtime』中的黑魔法Method Swizzling. 通过本文,您将了解到: Method Swizzling(动态方法交换)简介 Method Swizzlin ...

  4. IOS 多线程04-GCD详解 底层并发 API

    IOS 多线程04-GCD详解 底层并发 API 注:本人是翻译过来,并且加上本人的一点见解. 前言 想要揭示出表面之下深层次的一些可利用的方面.这些底层的 API 提供了大量的灵活性,随之而来的是大 ...

  5. Redis五种基本数据类型底层详解(原理篇)

    Redis五种基本数据类型底层详解 详细介绍Redis用到的数据结构 简单动态字符串 SDS和C字符串的区别 总结 链表 字典 哈希表 字典 哈希算法 解决键冲突 rehash(重点) 渐进式reha ...

  6. P2P技术详解(一):NAT详解——详细原理、P2P简介(转)

    这是一篇介绍NAT技术要点的精华文章,来自华3通信官方资料库,文中对NAT技术原理的介绍很全面也很权威,对网络应用的应用层开发人员而言有很高的参考价值. <P2P技术详解>系列文章 ➊ 本 ...

  7. 015. P2P技术详解(一):NAT详解——详细原理、P2P简介

    http://www.52im.net/thread-50-1-1.html 这是一篇介绍NAT技术要点的精华文章,来自华3通信官方资料库,文中对NAT技术原理的介绍很全面也很权威,对网络应用的应用层 ...

  8. 【Redis】数据类型的详解与使用场景【原创】

    文章目录 Redis数据类型的详解与使用场景 1-1 NoSQL的概述 1. 概述 2. 为什么需要NoSQL 3. NoSQL产品 4. 分类 5. 特点 2-1 Redis的概述 1. 概述 2. ...

  9. 图文详解 epoll 原理【Redis,Netty,Nginx实现高性能IO的核心原理】epoll 详解

    [Redis,Netty,Nginx 等实现高性能IO的核心原理] I/O 输入输出(input/output)的对象可以是文件(file), 网络(socket),进程之间的管道(pipe).在li ...

最新文章

  1. 听说你 ping 用的很 6 ?给我图解一下 ping 的工作原理!
  2. mongodb type
  3. 3种设计模式java小程序_Java设计模式之单例模式(3种实现方式)
  4. mongodb 3.4 安装_暴雨免安装中文版下载
  5. Lua源码分析 - 基础篇 - Lua源码的结构和架构图(01)
  6. SAP 电商云 Spartacus UI ROUTING_FEATURE 的使用场景
  7. Ajax前后端对接---Springmvc
  8. vue 分享微信传参_vue实现微信分享链接添加动态参数的方法
  9. Python面试笔记二
  10. python有趣的代码-python菜鸟教程,python好玩又简单的代码
  11. 如何调整帆软件按钮的样式
  12. Anybody = Nobody
  13. 【本站公告】近期Picasa图片没法查看的解决方案
  14. 【SpringBoot】62、SpringBoot中接入xxl-job实现分布式任务调度
  15. 【计算机网络】---局域网
  16. B15 - 999、大数据组件学习⑫ - Hue
  17. win7 安装openssh_Windows安装OpenSSH服务
  18. EXCEL里如何知道某种颜色的ColorIndex的值
  19. static, const, static const 与 const static
  20. 【工业大数据】工业大数据:构建制造型企业新型能力

热门文章

  1. 计算机网络——哈工大
  2. c语言中parameter和argument的区别
  3. 「.XD 文件 」用什么软件打开?
  4. 八块石头工作坊作品成果展“哥哥的鱼塘由我守护”组拉票经验
  5. JVM相关问题整理(可用作高频面试题和查缺补漏)
  6. Vue3报错之 Failed to load resource: the server responded with a status of 404 (Not Found)
  7. 2023年浙大iMBA提前批面试已经启动,有想法就应该试一试!
  8. 洛谷1726 上白泽慧音 tarjan模板
  9. STL中容器vector迭代器失效的相关问题
  10. code up练习1153: C语言3.1(error)