OC Runtime探索

runtime是什么

Objective-C 是开源的,任何时候你都能从 http://opensource.apple.com. 获取。

这里提供了一份可以debug的runtime版本,供学习使用 链接:https://pan.baidu.com/s/1MOwok3lAMOS2hvxTqoqx3A 密码:jqw0

Objective-CRuntime 是一个运行时库(Runtime Library),它是一个主要使用 C 和汇编写的库,为 C 添加了面相对象的能力并创造了 Objective-C。我们使用时,runtime是一套由c/c++以及汇编语言写成的api, runtime 创建了所有需要的结构体,让 Objective-C 的面相对象编程变为可能。

runtime分两个版本,Modern Runtime(现代的 Runtime)Legacy Runtime(过时的 Runtime)
Modern Runtime:覆盖所有 64 位的 Mac OS X 应用和所有 iPhone OS 的应用。 在Runtime源码中,__OBJC2__表示Modern Runtime
Legacy Runtime: 覆盖其他的所有应用(所有 32 位的 Mac OS X 应用)

运行时

当我们编写c/c++语言时,如果函数不存在,编译器会马上报错,而在Object-C中,执行一个方法,并不会报错,直到你真正执行这个方法时,才会报出unrecognized selector错误,这就是OC中的运行时体现。

OC对象和消息发送

对象的本质是结构体,方法的本质是发送消息,Class 如果你查看一个类的runtime信息,你会看到这个…

typedef struct objc_class *Class;
typedef struct objc_object {
    Class isa;
} *id;

objc_object 只有一个指向类的 isa 指针,就是我们说的术语 “isa pointer”(isa 指针)。
当我们发送一个消息给对象时,通过这个 isa 指针,Runtime 检查并且查看它的类是什么,能否响应这些消息。
最后我么看到了 id 指针。默认情况下 id 指针除了告诉我们它们是 Objective-C 对象外没有其他用了。当你有一个 id 指针,然后你就可以问这个对象是什么类的,看看它是否响应一个方法,等等,然后你就可以在知道这个指针指向的是什么对象后执行更多的操作了。

给对象发送方法

说到我们熟悉的objc_msgSend了,objc_msgSend其实是用汇编语言写的,看runtime的源码知道,其文件格式为.s

LGPerson *p = [[LGPerson alloc] init];
objc_msgSend(p,sel_registerName("run"));

我们通过objc_msgSend给一个对象发送一个方法SEL,这里的sel_registerName("name")其实就是@selector(name),通过打印指针发现获得的是同一个东西

NSLog(@"%p %p",sel_registerName("new"),@selector(new));
0x7fff44d8bee0 0x7fff44d8bee0

给类发送消息

id cls = [LGPerson class];
void *point = &cls;
objc_msgSend(objc_getClass("LGPerson"),sel_registerName("walk"));
[(__bridge id)point run];

 +[LGPerson walk]
 -[LGPerson run]

这里发现只有objc_msgSend调用了类方法,而直接取point调用的是实例方法
如果有如下错误,是xcode没有配置

Too many arguments to function call, expected 0, have 2

在这里插入图片描述
这设置为NO即可

给父类消息

对象方法

这里LGStudent继承自LGPerson

LGStudent *s = [LGStudent new];
struct objc_super mySuper;
mySuper.receiver = s; //对象
mySuper.super_class = class_getSuperclass([s class]); //类的superclass
objc_msgSendSuper(&mySuper, @selector(run));

结果为执行了

-[LGPerson run]

这里需要注意,参数需要传入objc_super结构体

类方法

LGStudent *s = [LGStudent new];
struct objc_super myClassSuper;
myClassSuper.receiver = [s class]; //类
myClassSuper.super_class = class_getSuperclass(object_getClass([s class]));//元类的superclass
objc_msgSendSuper(&myClassSuper, @selector(walk));

这里需要注意的是object_getClassobjc_getClass的区别,如果把object_getClass写成objc_getClass是不对的,需要取元类

object_getClass : 均获得isa指针类型
objc_getClass:返回类的类型

见源码:

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

Class objc_getClass(const char *aClassName)
{
    if (!aClassName) return Nil;

    // NO unconnected, YES class handler
    return look_up_class(aClassName, NO, YES);
}

NSObjectClass方法的描述

+ (Class)class {
    return self;
}

- (Class)class {
    return object_getClass(self);
}
NSLog(@"%p %p",objc_getClass("LGStudent"),[s class]);
NSLog(@"%p %p",objc_getMetaClass("LGStudent"),object_getClass([s class]));
0x100002508 0x100002508 //都是类
0x1000024e0 0x1000024e0 //都是元类

说明[s class]objc_getClass等同
说明objc_getMetaClass("LGStudent")object_getClass([s class])等同

类的元类

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

从runtime源码中我们看到,一个object对象,有一个isa指针,表示它是什么类型,我们点进去

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class  
    ......

我们发现,一个类除了有super_class指针外,还有一个isa指针,这个isa指针就指向它的元类

  • 我们知道,当给一个对象发送消息时(即执行实例方法),要判断它是否能执行这个消息(方法),那么我们会从它的类中查找(即通过isa指针),如果类中有这个方法,则能响应
  • 那么,当我们给一个类发送消息时呢(即执行类方法),那么我们从哪里判断呢,也同样从isa指针中查找,那么这个isa,我们就称之为元类
  • 所以对象方法存在于类中,以实例方法存在
  • 类方法存在于元类中,也以实例方法存在

So 最重要的是下面的

  • 对象在类中,以实例存在
  • 类在元类中,以实例存在

在这里插入图片描述
如果你能看懂上图了,那么说明你懂了,这张图其实不是那么好理解?,即每个类,都有一个元类,元类也能通过superclass方法取到其superclass

block即对象

你可以在 LLVM/Clang 的文档中的 Block 中看到

struct Block_literal_1 {
    void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock 
    int flags;    
    int reserved;     
    void (*invoke)(void *, ...); 
    struct Block_descriptor_1 { 
        unsigned long int reserved; // NULL     
        unsigned long int size;  // sizeof(struct Block_literal_1)
        // optional helper functions     
        void (*copy_helper)(void *dst, void *src);
        void (*dispose_helper)(void *src);     
    } *descriptor;    
    // imported variables
};

Blocks 被设计为兼容 Objective-C 的 runtime,所以他们被作为对象对待,因此他们可以响应消息,比如 -retain,-release,-copy ,等等。

这里的block isa只有两种类型,我们常见的_NSConcreteMallocBlock是通过copy生成的,初始化的时候是_NSConcreteStackBlock,在_Block_copy_internal方法内部,会转换为 _NSConcreteMallocBlock

什么是 Objective-C 类?在 Objective-C 中的一个类实现看起来像这样:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

这里的!__OBJC2__表示的老版的runtime,新版runtime的class在objc-runtime-new.h文件中有声明

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
    ....... //这里省略

我们可以根据运行时,获取类的变量列表,方法列表,这里有个objc_cache,当我们发送一个SEL给对象时,首先会从cache中寻找,这个cache是一个hash表,能够高速的通过SEL找到IMP,当cache中没有时,runtime会创建一个加入到cache中,这个过程是比较长的。

类的加载

类的+load方法在分类之前,这里探索下runtime对其实现机制,篇幅较大,这里在其他文章中说明。

1.OC中Runtime的类+load方法调用

objc_msgSend消息发送

通过搜索objc_msgSend,找到汇编入口,这查看arm64,即objc-msg-arm64.s文件中代码

/********************************************************************
 *
 * id objc_msgSend(id self, SEL _cmd, ...);
 * IMP objc_msgLookup(id self, SEL _cmd, ...);
 * 
 * objc_msgLookup ABI:
 * IMP returned in r12
 * Forwarding returned in Z flag
 * r9 reserved for our use but not used
 *
 ********************************************************************/

	ENTRY _objc_msgSend   //!!!!_objc_msgSend开始
	MESSENGER_START
	
	cbz	r0, LNilReceiver_f

	ldr	r9, [r0]		// r9 = self->isa
	GetClassFromIsa			// r9 = class
	CacheLookup NORMAL
	// cache hit, IMP in r12, eq already set for nonstret forwarding
	MESSENGER_END_FAST
	bx	r12			// call imp

	CacheLookup2 NORMAL
	// cache miss
	ldr	r9, [r0]		// r9 = self->isa
	GetClassFromIsa			// r9 = class
	MESSENGER_END_SLOW
	b	__objc_msgSend_uncached

LNilReceiver:
	// r0 is already zero
	mov	r1, #0
	mov	r2, #0
	mov	r3, #0
	FP_RETURN_ZERO
	MESSENGER_END_NIL
	bx	lr	
	
	END_ENTRY _objc_msgSend //!!!!_objc_msgSend结束


	ENTRY _objc_msgLookup
	UNWIND _objc_msgLookup, NoFrame

	cmp	x0, #0			// nil check and tagged pointer check
	b.le	LLookup_NilOrTagged	//  (MSB tagged pointer looks negative)
	ldr	x13, [x0]		// x13 = isa
	and	x16, x13, #ISA_MASK	// x16 = class	
LLookup_GetIsaDone:
	CacheLookup LOOKUP		// returns imp

LLookup_NilOrTagged:
	b.eq	LLookup_Nil	// nil check

	// tagged
	mov	x10, #0xf000000000000000
	cmp	x0, x10
	b.hs	LLookup_ExtTag
	adrp	x10, _objc_debug_taggedpointer_classes@PAGE
	add	x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
	ubfx	x11, x0, #60, #4
	ldr	x16, [x10, x11, LSL #3]
	b	LLookup_GetIsaDone

LLookup_ExtTag:	
	adrp	x10, _objc_debug_taggedpointer_ext_classes@PAGE
	add	x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
	ubfx	x11, x0, #52, #8
	ldr	x16, [x10, x11, LSL #3]
	b	LLookup_GetIsaDone

LLookup_Nil:
	adrp	x17, __objc_msgNil@PAGE
	add	x17, x17, __objc_msgNil@PAGEOFF
	ret

	END_ENTRY _objc_msgLookup

	
	STATIC_ENTRY __objc_msgNil

	// x0 is already zero
	mov	x1, #0
	movi	d0, #0
	movi	d1, #0
	movi	d2, #0
	movi	d3, #0
	ret
	
	END_ENTRY __objc_msgNil


	ENTRY _objc_msgSendSuper
	UNWIND _objc_msgSendSuper, NoFrame
	MESSENGER_START

	ldp	x0, x16, [x0]		// x0 = real receiver, x16 = class
	CacheLookup NORMAL		// calls imp or objc_msgSend_uncached

	END_ENTRY _objc_msgSendSuper

	// no _objc_msgLookupSuper

	ENTRY _objc_msgSendSuper2
	UNWIND _objc_msgSendSuper2, NoFrame
	MESSENGER_START

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

	END_ENTRY _objc_msgSendSuper2

	
	ENTRY _objc_msgLookupSuper2
	UNWIND _objc_msgLookupSuper2, NoFrame

	ldp	x0, x16, [x0]		// x0 = real receiver, x16 = class
	ldr	x16, [x16, #SUPERCLASS]	// x16 = class->superclass
	CacheLookup LOOKUP

	END_ENTRY _objc_msgLookupSuper2

这里整个流程如下:

1.objc_msgSend
2.LLookup_NilOrTagged
3.CacheLookup
4.CacheHit $0			// call or return imp
5.调用IMP
如果没有则调用
4.CheckMiss $0			// miss if bucket->sel == 0
5.__objc_msgSend_uncached or  __objc_msgLookup_uncached
6.MethodTableLookup
7.__class_lookupMethodAndLoadCache3
8.跳到c函数lookUpImpOrForward

通过观察lookUpImpOrForward的实现,执行顺序为:

1. 无锁的缓存查找
2. 如果类没有实现(isRealized)或者初始化(isInitialized),实现或者初始化类
3. 加锁
4. 缓存以及当前类中方法的查找
5. 尝试查找父类的缓存以及方法列表
6. 没有找到实现,尝试方法解析器 resolveInstanceMethod:
7. 进行消息转发 forwardingTargetForSeletor:
8. 解锁、返回实现

自己和父类里面都没有找到IMP时,会通过_class_resolveMethod

/***********************************************************************
* _class_resolveMethod
* Call +resolveClassMethod or +resolveInstanceMethod.
* Returns nothing; any result would be potentially out-of-date already.
* Does not check if the method already exists.
**********************************************************************/
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        _class_resolveInstanceMethod(cls, sel, inst);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        _class_resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}

这里就是接下来需要说明的动态方法解析,消息转发。

这里附上一份百度网盘的runtime代码,方便下载
https://pan.baidu.com/s/1tMXQdPZTVfIMuKEUajmh4w

消息转发机制


消息转发流程如下:

 * 1.resolveInstanceMethod: or resolveClassMethod:若返回NO则2,否则终止 
 * 2.forwardingTargetForSelector: 若返回nil则3,否则终止 
   3.methodSignatureForSelector: 若返回不为空则4,否则终止
 * 4.forwardInvocation: 手动让备用对象响应

这里的124步,则是我们可以防止方法unrecognized selector错误的点

  • 1.resolveInstanceMethod: or resolveClassMethod: 动态方法解析
+ (BOOL)resolveInstanceMethod:(SEL)sel{
	NSLog(@"sel = %@",NSStringFromSelector(sel));
	//1.判断没有实现方法, 那么我们就是动态添加一个方法
	if (sel == @selector(run)) {
		class_addMethod(self, sel, (IMP)newFix, "v@:@");
		return YES;
	}
	return [super resolveInstanceMethod:sel];
}

+ (BOOL)resolveClassMethod:(SEL)sel{
    NSLog(@"%@ resolveClassMethod",[self class]);
    if (sel == @selector(walk)) {
        BOOL r = class_addMethod(object_getClass(self), sel, (IMP)newFix, "v@:@");//object_getClass获取的是元类,objc_getClass获取的是类的类型
        return r;
    }
    return  [super resolveClassMethod:sel];
}

static void newFix(id obj,SEL sel,NSString *param) {
    printf("fix 1111\n");
}

这里的"v@:@"如何理解,我们知道调用方法通过objc_msgSend,首先我们要制定其返回类型,然后其参数是target,sel,然后才是参数

v就对应返回类型
@对应target,这里是对象所以为@
:表示sel
@表示参数也是个对象

这里参照官方文档
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html

  • 2.forwardingTargetForSelector: 快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector{
   if ([NSStringFromSelector(aSelector) isEqualToString:@"test"]) {
       return [SecondObject new];
   }
   return [super forwardingTargetForSelector:aSelector];
}

+ (id)forwardingTargetForSelector:(SEL)aSelector { 
//......
}

这里返回第二个对象实例处理test方法

  • 3.methodSignatureForSelector:
    有时候我们在这个类中并找不到这个方法,也就找不到这个方法的方法签名,所以我们要自己创建
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE(""){
   //转化字符
   NSString *sel = NSStringFromSelector(aSelector);
   //判断, 手动生成签名
   if([sel isEqualToString:@"run"]){
   	return [NSMethodSignature signatureWithObjCTypes:"v@:"];
   }else{
   	return [super methodSignatureForSelector:aSelector];
   }
}

+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE(""){
   //......
}

NSMethodSignature也可以调用block块,有兴趣可以看下这个
https://www.jianshu.com/p/e1b0aeaed174

  • 4.forwardInvocation: 3+4为慢速转发
-(void)forwardInvocation:(NSInvocation *)anInvocation{
    //创建备用对象
    ThirdObject * obj = [ThirdObject new];
    SEL sel = anInvocation.selector;
    //判断备用对象是否可以响应传递进来等待响应的SEL
    if ([obj respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:obj];
    }else{
       // 如果备用对象不能响应 则抛出异常
        [self doesNotRecognizeSelector:sel];
    }
}
//当然这里也可换一种处理方式,当一个方法不能响应时,我们去调用另一个方法
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    //转而执行另一个方法
    NSString *param1 = @"参数1";
    SEL sel = anInvocation.selector;
    anInvocation.target = [LGPerson class];
    [anInvocation setArgument:&param1 atIndex:2];
    anInvocation.selector = @selector(readbook:);
    [anInvocation invoke];
}

+ (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s",__func__);
    NSString *sto = @"奔跑少年";
    anInvocation.target = [LGStudent class];
    //    [anInvocation setArgument:&sto atIndex:2];
    NSLog(@"%@",anInvocation.methodSignature);
    anInvocation.selector = @selector(walk);
    [anInvocation invoke];
    
}

需要注意的是,整个过程中,只要有一段处理了,后面的就不会执行了。
发现这块内容还挺多,单独一篇博文来说明了:Runtime消息转发机制-关键点GET
越往后面处理代价越高,最好的情况是在第一步就处理消息,这样runtime会在处理完后缓存结果,下回再发送同样消息的时候,可以提高处理效率。

官方文档:
https://developer.apple.com/library/archive/samplecode/ForwardInvocation/Listings/main_m.html#//apple_ref/doc/uid/DTS40008833-main_m-DontLinkElementID_4
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtForwarding.html#//apple_ref/doc/uid/TP40008048-CH105-SW2

Method Swizzling

//交换类方法 
void swizzleClassMethod(Class cls, SEL origSelector, SEL newSelector)
{
    if (!cls) return;
    Method originalMethod = class_getClassMethod(cls, origSelector);
    Method swizzledMethod = class_getClassMethod(cls, newSelector);
    
    Class metacls = objc_getMetaClass(NSStringFromClass(cls).UTF8String);
    if (class_addMethod(metacls,
                        origSelector,
                        method_getImplementation(swizzledMethod),
                        method_getTypeEncoding(swizzledMethod)) ) {
        /* swizzing super class method, added if not exist */
        class_replaceMethod(metacls,
                            newSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
        
    } else {
        /* swizzleMethod maybe belong to super */
        class_replaceMethod(metacls,
                            newSelector,
                            class_replaceMethod(metacls,
                                                origSelector,
                                                method_getImplementation(swizzledMethod),
                                                method_getTypeEncoding(swizzledMethod)),
                            method_getTypeEncoding(originalMethod));
    }
}
//交换实例方法
void swizzleInstanceMethod(Class cls, SEL origSelector, SEL newSelector)
{
    if (!cls) {
        return;
    }
    /* if current class not exist selector, then get super*/
    Method originalMethod = class_getInstanceMethod(cls, origSelector);
    Method swizzledMethod = class_getInstanceMethod(cls, newSelector);
    
    /* add selector if not exist, implement append with method */
    if (class_addMethod(cls,
                        origSelector,
                        method_getImplementation(swizzledMethod),
                        method_getTypeEncoding(swizzledMethod)) ) {
        /* replace class instance method, added if selector not exist */
        /* for class cluster , it always add new selector here */
        class_replaceMethod(cls,
                            newSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod));
        
    } else {
        /* swizzleMethod maybe belong to super */
        class_replaceMethod(cls,
                            newSelector,
                            class_replaceMethod(cls,
                                                origSelector,
                                                method_getImplementation(swizzledMethod),
                                                method_getTypeEncoding(swizzledMethod)),
                            method_getTypeEncoding(originalMethod));
    }
}

这块需要注意的内容也多,在另一博文中说明:Runtime交换类方法和实例方法

动态创建类

如何通过运行时创建一个类,一般来说我们创建一个类,需要声明它的父类,属性,以及需要的方法。

const char * className = "MyClass";
Class kclass = objc_getClass(className);
if (!kclass)
{
    Class superClass = [NSObject class];
    kclass = objc_allocateClassPair(superClass, className, 0);
}
//添加成员变量 1
NSUInteger size;
NSUInteger alignment;
NSGetSizeAndAlignment(@encode(NSString *), &size, &alignment);
class_addIvar(kclass, "favour", size, alignment, @encode(NSString *));
//添加成员变量 2
class_addIvar(kclass, "name", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *));
//注册到runtime 注意ivarlist 注册后就只读了,所有这个需要在addivar之后
objc_registerClassPair(kclass);
//添加方法
class_addMethod(kclass, @selector(readBook:), (IMP)readBook, "v@:@");

id instance = [[kclass alloc] init];
[instance setValue:@"haocaihaocai" forKey:@"name"];
[instance setValue:@"ios" forKey:@"favour"];
NSLog(@"%@ %@",[instance valueForKey:@"name"],[instance valueForKey:@"favour"]);

[instance performSelector:@selector(readBook:) withObject:@"三国志"];

输出日志如下

haocaihaocai ios
read book 三国志

这里主要注意objc_registerClassPair的时机要在addIvar之后,原因是源码的objc_classclass_ro_t结构体声明的是const,创建后无法修改,如下源码所示


struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
    ......
}

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro; //这里为只读 const

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;
	......
};

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;  //这里为const

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

如何查看Runtime的实现信息

使用instrumentObjcMessageSends配置,我们能查看系统底层的调用log信息

/***********************************************************************
* instrumentObjcMessageSends
**********************************************************************/
// Define this everywhere even if it isn't used to simplify fork() safety code.

这个日志信息会存储在Macintosh HD/private/tmp/文件夹中(路径/private/tmp)

下面来操作,main.m文件实现如下

#import <Foundation/Foundation.h>
#import "LGStudent.h"
#include <objc/message.h>

extern void instrumentObjcMessageSends(BOOL);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        instrumentObjcMessageSends(YES);
        LGPerson *p = [LGPerson new];
        [p run];
        instrumentObjcMessageSends(NO);
    }
    return 0;
}

执行程序之后,目录下多出了一个文件
在这里插入图片描述
我们可以从文件中看到它的执行日志

+ LGPerson NSObject initialize
+ LGPerson NSObject new
- LGPerson NSObject init
- LGPerson LGPerson run
- LGPerson NSObject class
+ LGPerson NSObject instanceMethodSignatureForSelector:
- LGPerson LGPerson run
+ NSMethodSignature NSObject initialize
+ NSMethodSignature NSMethodSignature signatureWithObjCTypes:
+ NSMethodSignature NSObject alloc
- NSMethodSignature NSMethodSignature _typeString
- NSMethodSignature NSMethodSignature _frameDescriptor
- NSTaggedPointerString NSTaggedPointerString retain
- NSMethodSignature NSObject release
- NSMethodSignature NSMethodSignature methodReturnType
- NSMethodSignature NSMethodSignature _argInfo:
- NSMethodSignature NSMethodSignature _frameDescriptor
- __NSCFConstantString __NSCFString _fastCStringContents:
- __NSCFConstantString __NSCFString _fastCStringContents:

NSTagged Pointer

NSNumber

在object_msgSend的汇编代码中,我们常常会看到tagged point类型的判断,如下段代码

END_ENTRY _objc_msgSend


	ENTRY _objc_msgLookup
	UNWIND _objc_msgLookup, NoFrame

	cmp	x0, #0			// nil check and tagged pointer check
	b.le	LLookup_NilOrTagged	//  (MSB tagged pointer looks negative)
	ldr	x13, [x0]		// x13 = isa
	and	x16, x13, #ISA_MASK	// x16 = class	
LLookup_GetIsaDone:
	CacheLookup LOOKUP		// returns imp

LLookup_NilOrTagged:
	b.eq	LLookup_Nil	// nil check

// tagged
	mov	x10, #0xf000000000000000
	cmp	x0, x10
	b.hs	LLookup_ExtTag
	adrp	x10, _objc_debug_taggedpointer_classes@PAGE
	add	x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
	ubfx	x11, x0, #60, #4
	ldr	x16, [x10, x11, LSL #3]
	b	LLookup_GetIsaDone

LLookup_ExtTag:	
	adrp	x10, _objc_debug_taggedpointer_ext_classes@PAGE
	add	x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
	ubfx	x11, x0, #52, #8
	ldr	x16, [x10, x11, LSL #3]
	b	LLookup_GetIsaDone

当我们存储一个NSNumber时,我们一般想的是通过指针取到地址,通过地址读取内存中的数据,这默认的需要创建一块内存,还有一个指针。然后,对象在内存中是对齐的,它们的地址总是指针大小的整数倍,通常为16的倍数。对象指针是一个64位的整数,而为了对齐,一些位将永远是零。
Tagged Pointer利用了这一现状,它使对象指针中非零位有了特殊的含义。在苹果的64位Objective-C实现中,若对象指针的最低有效位为1(即奇数),则该指针为Tagged Pointer。尤其需要注意的是,这种指针不通过解引用isa来获取其所属类,而是通过接下来三位的一个类表的索引。该索引是用来查找所属类是采用Tagged Pointer的哪个类。剩下的60位则留给类来使用。

所有对象都有 isa 指针,而Tagged Pointer其实是没有的,因为它不是真正的对象。
所以判断一个Tagged Pointer对象的isa指针类型是错误的,例如:obj->isa = classType
正确的方式应该是通过表示类型的几位值来取,苹果runtime的接口可以帮我们做
正确的方式是使用isKindOfClassobject_getClass

最低有效位1位表示Tagged Pointer类型,接下来的三位表示存储的类型,然后60位为数据空间大小,然而这个规定并不是死的
在我的64位mac中,看下面代码运行结果:

for (int i = 0; i < 11; i++) {
   NSNumber *num = [NSNumber numberWithInt:i];
   // 打印出地址
   NSLog(@"%p\n", num);
}
0x27
0x127
0x227
0x327
0x427
0x527
0x627
0x727
0x827
0x927
0xa27

输出地址中,最低位7由1001表示,最低位为1表示为Tagged Pointer,紧接着是100,则表示为NSNumber,那么数据类型intlong哪里表示呢?我们通过下面代码:

        NSNumber *floatNum = [NSNumber numberWithFloat:2];
        printf("floatNum:%p\n", floatNum);
        NSNumber *intNum = [NSNumber numberWithInt:2];
        printf("intNum:%p\n", intNum);
        NSNumber *longNum = [NSNumber numberWithLong:1];
        printf("longNum:%p\n", longNum);
        NSNumber *charNum = [NSNumber numberWithChar:1];
        printf("charNum:%p\n", charNum);
        
floatNum:0x247
intNum:0x227
longNum:0x137
charNum:0x107

即可以发现,0x247,其中2表示值,47都表示类型,即64位的地址已经花费了8个位用来表示数据类型、类、指针类型了。留下56位来表示数值了。在int数据中,第一位是符号位,所以我们能表达的最大数值为2^55 - 1,当数值超过2^55 - 1则用NSNumber对象指针来存储,以下实验足以证明这个结论:

NSNumber *num1 = [NSNumber numberWithLong:((long)pow(2, 55)) - 1];
NSNumber *num2 = [NSNumber numberWithLong:((long)pow(2, 55))];
printf("num1:%p  numb2:%p\n", num1, num2);

num1:0x7fffffffffffff37  numb2:0x10060fa20

参考 https://juejin.im/post/5816079ca22b9d00678ca05b

NSString

NSString *str1 = [NSString stringWithFormat:@"abc"];
NSString *str2 = [NSString stringWithFormat:@"abcdefghijk"];
NSString *str3 = [NSString stringWithFormat:@"abcdeff"];
NSString *str4 = [NSString stringWithFormat:@"abcdeffg"];
printf("str1:%p\n", str1);
printf("str2:%p\n", str2);
printf("str3:%p\n", str3);
printf("str4:%p\n", str4);
        
str1:0x63626135
str2:0x100601d10
str3:0x6666656463626175
str4:0x22038a01145a85

可以看出,abc的字符串使用tagged Pointer处理。当我们在abcdeff基础上再加一位g时我们发现abcdeffg的地址对应关系变了,但仍然是tagged pointer,之前说过64位系统对象指针有56位用来存储值,按61=a,62=b的规律的话,一个字符串需要8位,那么只能存储7个字符串,这里应该是OC对其存储方式改变了。

这篇博文详细阐述了这个现象:http://www.cocoachina.com/articles/13449

如果对你有帮助,请点个赞?

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值