Objective-C中self和super的区别

Objective-C中self和super的区别

self和super在开发中经常遇到,最常见的场景就是 self=[super init],很明显这是两个不同的关键字,肯定是有区别的。

在写区别之前,先新建个工程,然后创建个Ghost类文件,在文件中写入以下方法并调用。

- (void)run{
    NSLog(@"%@", [self class]);
    NSLog(@"%@", [super class]);
    
    NSLog(@"%@", [self superclass]);
    NSLog(@"%@", [super superclass]);
}

会神奇的发现,self和super调用相同方法,输入的结果也是一样的。

为什么呢?Objective-C的底层是C/C++封装的,把上面代码转成C++代码以更好的分析。转代码之前,先把代码简化为以下,可以更方面查看。

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

打开终端,进入到Ghost.m目录,然后执行以下命令

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Ghost.m -o Ghost.cpp

然后打开Ghost.cpp文件,搜索NSLog就可以找到以下代码

static void _I_Ghost_run(Ghost * self, SEL _cmd) {
    ((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class"));
    ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Ghost"))}, sel_registerName("class"));
    ((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("superclass"));
    ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Ghost"))}, sel_registerName("superclass"));
}

简化代码,只留下了 self和super方法调用相关内容

objc_msgSend(self, sel_registerName("class"));

objc_msgSendSuper((__rw_objc_super){self, class_getSuperclass(objc_getClass("Ghost"))},
sel_registerName("class"));

objc_msgSend(self, sel_registerName("superclass"));

objc_msgSendSuper((__rw_objc_super){self, class_getSuperclass(objc_getClass("Ghost"))}, sel_registerName("superclass"));

发现self的调用被转换成了objc_msgSend调用,就是常见的方法调用,这里就不多做解释。

而super调用的地方,被转换成了objc_msgSendSuper调用,在objc4-818.2源码中搜索这个方法定义找到如下

objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)

struct objc_super的定义如下

struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;

    /// Specifies the particular superclass of the instance to message. 
    __unsafe_unretained _Nonnull Class super_class;
};

可以看出,第一个参数是实例对象,第二个参数是实例对象的父类,因为Ghost对象继承于NSObject那上面关于super调用的相关代码 就可以简化成如下

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

objc_msgSendSuper方法里面做了什么呢?源码中搜索下,找到各个平台的汇编代码,arm平台相关的代码如下(arm64汇编比这个复杂,整体逻辑是一样的)

ENTRY _objc_msgSendSuper

ldr    r9, [r0, #CLASS]    // r9 = struct super->class
CacheLookup NORMAL, _objc_msgSendSuper
// cache hit, IMP in r12, eq already set for nonstret forwarding
ldr    r0, [r0, #RECEIVER]    // load real receiver
bx    r12            // call imp

CacheLookup2 NORMAL, _objc_msgSendSuper
// cache miss
ldr    r9, [r0, #CLASS]    // r9 = struct super->class
ldr    r0, [r0, #RECEIVER]    // load real receiver
b    __objc_msgSend_uncached

END_ENTRY _objc_msgSendSuper

汇编的大概意思是,r0(第一个参数 super)中取出class(这里是NSObject) 存入r9, 然后从这个class方法缓存中找方法的实现,如果找到就放入r12中,再把receiver(这里是self)放入如r0中,然后再调用方法。再下面是如果在方法缓存中没找到方法实现的话,把superclass放入r9,把receiver放入r0,然后跳转到__objc_msgSend_uncached方法,汇编代码如下

STATIC_ENTRY __objc_msgSend_uncached

// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band r9 is the class to search

MethodTableLookup NORMAL    // returns IMP in r12
bx    r12

END_ENTRY __objc_msgSend_uncached

意思是从r9中搜索方法实现,MethodTableLookup 会把方法实现放到r12寄存其中,bx r12就是方法调用。

上面逻辑简化就是,从r9中(也就是父类)搜索方法实现,搜索到放入r12中,然后调用。了解arm汇编的就会知道,在 Objective-C中r0寄存器中的对象就是这个方法的调用者。

总是,self其实就是一个指向当前调用对象的指针,在调用方法时会作为第一个参数传入objc_msgSend方法中。super是一个编译器指示符,在编译的时候会被替换为

objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)

并且会把当前调用对象和对象的父类放入到objc_super结构体中作为方法的第一个参数。

而objc_msgSendSuper的本质就是,从父类的方法列表中查找方法,让当前类调用。因为class和superclass的方法实现本身就在NSObject中,然后调用者其实都是self,也就是为什么输出结果都是一样的原因。

Read more

Category和关联对象

Category和关联对象

Category也就是分类,可以在没有类定义的情况下为现有类添加方法、属性、协议,众所周知是不能增加成员变量的,但是是可以通过关联对象给类增加的。 struct category_t { const char *name; classref_t cls; WrappedPtr instanceMethods; WrappedPtr classMethods; struct protocol_list_t *protocols; struct property_list_t *instanceProperties; // Fields below this point are not always present on disk. struct property_list_t *_classProperties; method_list_t *methodsF

By LEMON
内存管理-AutoreleasePool

内存管理-AutoreleasePool

AutoreleasePool(自动释放池)是Objective-C中的一种内存自动回收机制,它可以延迟加入AutoreleasePool中的变量release的时机。 @autoreleasepool { ... }可以通过 clang -rewrite-objc 命令转成C++代码如下 /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; } 然后可以从objc源码中找到__AtAutoreleasePool的定义 struct __AtAutoreleasePool { __AtAutoreleasePool() { atautoreleasepoolobj = objc_autoreleasePoolPush(); } ~__AtAutoreleasePool() { objc_autoreleasePoolPop(atautoreleasepoolobj); } void * atautoreleasepoolobj; }; 这个结构体先

By LEMON
如何为macOS应用开发插件

如何为macOS应用开发插件

本文章只用于学习和交流,转载请注明出处。 玩过越狱手机的应该都了解,iOS的越狱插件是可以为系统或者应用添加一些特色的功能的。越狱插件的开发,已经有完整的工具链,并且插件的加载运行Substrate框架已经为我们做好。 更改应用的功能实现有两种方法,一种是通过反编译工具更改汇编代码后生成新的可执行文件。另一种是写一个动态库,然后注入到可执行文件。第一种适合简单的逻辑修改,毕竟用汇编实现功能是需要很大的工作量的。 第二种要怎么注入呢?了解Mach-O 二进制文件的应该知道,Mach-O 二进制文件Load Commands中的 LC_LOAD_DYLIB 标头告诉 macOS在执行期间要加载哪些动态库 (dylib)。所以我们只需要在二进制文件中添加一条LC_LOAD_DYLIB就可以。而insert_dylib工具已经为我们实现了添加的功能。所以现在唯一需要考虑的就是插件如何开发。 废话不多说,现在就以mac版的迅雷为例开发一款属于我们自己的插件。首先下载最新版本的迅雷(版本5.0.2)。然后用Xcode新建一个macOS下的Framework,如下 新建完目录结构如下,如果

By LEMON
深入分析objc_msgSend尾调用优化

深入分析objc_msgSend尾调用优化

无意间看到了这篇文章 iOS objc_msgSend尾调用优化详解,讲的是在Release模式下,当某函数的最后一项操作是调用另外一个函数,编译器会生成调转至另一函数所需的指令码,而且不会向调用堆栈中推入新的“栈帧”(frame stack),以实现栈帧复用。 栈帧就是一个函数执行的环境:函数参数、函数的局部变量、函数执行完后返回到哪里等等。所以在每一次函数调用之前,需要保存下执行的环境,以确保正常返回到调用位置。下面是- (void)viewDidLoad方法的汇编代码 可以看出在调用[super viewDidLoad]之前,先申请栈空间,然后保存x29和x30寄存器的值,调用结束后恢复寄存器的值,然后平栈。x29寄存器也就是fp寄存器,保存栈底的地址。x30寄存器也就是lr寄存器,保存返回地址。那栈帧复用又是怎么实现的呢?本着知其然知其所以然的精神,深入研究下。用下文章中的例子 - (NSInteger)func1:(NSInteger)num{ if (num >= 2000000) { return num; } return

By LEMON
京ICP备15024336号-4