文章目录
runtime是什么
Objective-C
是开源的,任何时候你都能从 http://opensource.apple.com. 获取。
这里提供了一份可以debug的runtime版本,供学习使用 链接:https://pan.baidu.com/s/1MOwok3lAMOS2hvxTqoqx3A 密码:jqw0
Objective-C
的 Runtime
是一个运行时库(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_getClass
和objc_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);
}
NSObject
中Class
方法的描述
+ (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对其实现机制,篇幅较大,这里在其他文章中说明。
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:¶m1 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_class
中class_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
的接口可以帮我们做
正确的方式是使用isKindOfClass
和object_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
,那么数据类型int
,long
哪里表示呢?我们通过下面代码:
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
如果对你有帮助,请点个赞
?