Objective-C 对象的本质是什么

Objective-C 对象的本质是什么

作为一个iOS开发,接触最多的可能就是Objective-C语言了,众所周知Objective-C是一种面向对象的语言,苹果用C/C++为其封装了成员变量、方法的存储和方法调用逻辑,但是具体的实现并没有暴露给我们。

分析之前,需要先新建个Command Line Tool工程,然后再main文件中写入以下代码:

@interface Ghost : NSObject
{
    int age;
}
@end

@implementation Ghost
@end

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

然后需要把它转成C/C++代码,打开终端,进入main文件所在的目录,执行以下命令

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

这个命令的意思是,把main.m文件中的代码,转换成iOS平台对应的C++代码。接下来打开main_arm64.cpp文件,搜索类名 Ghost,会找到Ghost_IMPL的结构体如下

typedef struct objc_object Ghost;
struct Ghost_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
        int age;
};

从这里不难发现Objective-C对象的本质就是objc_object类型的结构体。结构体里面有一个NSObject_IMPL类型的结构体和一个int类型名字为age的变量,这个NSObject_IMPL又是什么呢?可以在main_arm64.cpp文件里面搜索NSObject_IMPL,会发现一个结构体如下

typedef struct objc_object NSObject;
struct NSObject_IMPL {
	Class isa;
};

不难猜出,这个应该是NSobject类的底层实现,通过Xcode进入NSObject的头文件,会发现类的实现是这样的

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}

上下对比下,可以证明猜测是正确的。并且可以看到NSObject对象里面只有一个 Class 类型的isa指针。

这个Class类型又是什么呢,可以通过Xcode进入objc.h找到如下代码

typedef struct objc_class *Class;

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

typedef struct objc_object *id;

这里能看到Class是struct objc_class类型的指针,同时可以看到id是struct objc_object类型的指针。

那么struct objc_class内部又是什么样子的结构呢,Xcode并不能跳到定义的地方,这时候就需要查看苹果开源的代码了。

从这里下载最新源码,版本号最大的就是苹果开源的最新的代码。下载完成后解压,我下载的(objc4-818.2),用Xcode打开工程。这时候搜索struct objc_class,发现有两个文件定义了这个结构体(objc-runtime-new.h和objc-runtime-old.h),忽略old直接看new,可以找到如下定义(line 1688)

struct objc_class : objc_object {
    //省略...
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;   
    //省略...
}

可以看到objc_class是继承于objc_object的结构体,然后再搜索下struct objc_object,会在objc-private.h找到如下定义

struct objc_object {
private:
    isa_t isa;
//省略 ...
}

发现isa指针并不是Class类型的了,那是因为苹果在64位系统,为了让指针存储更多的数据,isa指针优化成union isa_t的类型,找到isa_t的定义

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

private:
    Class cls;
    void setClass(Class cls, objc_object *obj);
    Class getClass(bool authenticated);
    Class getDecodedClass(bool authenticated);
};

进入getClass方法里面,会发现 clsbits &= ISA_MASK,可以得知现在指向Class的指针需要再按位与ISA_MASK后才可以得到。

综上,Objective-C 中

1、实例对象是objc_object类型的结构体,里面包含isa指针和成员变量。

2、类对象是objc_class类型的结构体,因为继承于objc_object类型,所以同样包含isa指针,还有superclass指针等类的一些信息。

3、元类也是Class类型,同样也是objc_class类型的结构体。

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