背后的编码规则:深入iOS类型编码器的实现机制

501 阅读3分钟

*Type Encodings *

To assist the runtime system, the compiler encodes the return and argument types for each method in a character string and associates the string with the method selector. The coding scheme it uses is also useful in other contexts and so is made publicly available with the @encode() compiler directive. When given a type specification, @encode() returns a string encoding that type. The type can be a basic type such as an int, a pointer, a tagged structure or union, or a class name—any type

前言

Objective-C的运行时环境中,类型编码器(Type Encoder)被广泛地应用,例如属性、变量、方法甚至结构体等任何类型的编码都是通过类型编码器来描述的,以方便编译器和运行时环境进行处理。了解类型编码器的语法和结构可以更好地理解Objective-C的运行时机制,从而更好地进行开发和调试。

@encode

Objective-C中,编译器指令@encode用于返回给定参数的Objective-C类型编码,它将指定的类型编码成一个字符串,可以用于序列化和反序列化。在一些情况下,我们需要使用@encode指令将一个对象或类型编码成字符串,以便于进行数据传输、存储和解析等操作。

语法格式

// type-name是要编码的类型,可以是一个基本类型、指针类型、结构体类型等等
@encode(type-name)

日常使用

数组类型编码

NSLog(@"%s", @encode(NSObject*[10]));       //  [10@]
NSLog(@"%s", @encode(float[10]));           //  [10f]
NSLog(@"%s", @encode(float*[10]));          //  [10^f]

一个数组的类型编码是用一个中括号[]表示的,中括号里面是数组的成员数量和类型,上面的例子中第一个表示10个对象的数组,第二个表示10个浮点型变量的数组,第三个便是10个浮点型指针的数组

结构体类型编码

typedef struct example {
    id anObject;
    int aInt;
    unsigned int bInt;
    char aChar;
    unsigned char bChar;
    char *aString;
} Example;

NSLog(@"encode %s", @encode(Example));          // {example=@icCI*}
NSLog(@"encode %s", @encode(Example *));        // ^{example=@icCI*}
NSLog(@"encode %s", @encode(Example **));       // ^^{example}

在这段代码中,我们定义了一个名为Example的结构体类型,其包含了多种类型的成员变量,通过@encode指令输出结构体Example的编码结果

解析编码结果:

  • { 和 } 括号表示一个结构体类型的开始和结束
  • @ 表示第一个成员变量的类型是一个对象
  • * 表示第二个成员变量的类型是一个字符数组
  • i 表示第三个成员变量的类型是一个整数

对象的类型编码

Objects are treated like structures.

The NSObject class declares just one instance variable, isa, of type Class

@interface Person : NSObject {
    NSString *_desc;
    int age;
    double assets;
}
@property (nonatomic, strong) NSString *name;
@end

NSLog(@"%s", @encode(Person));               //  {Person=#@id}

NSLog(@"%s", @encode(NSObject));             //  {NSObject=#}
NSLog(@"%s", @encode(NSArray));              //  {NSArray=#}
NSLog(@"%s", @encode(NSString));             //  {NSString=#}
NSLog(@"%s", @encode(NSMutableArray));       //  {NSMutableArray=#}

通过以上例子可以看出,对象是被视为特殊的结构体,因此它们的类型编码和结构体是类似的,都是用大括号{}表示 对于Person类,按照先后顺序依次为类对象Class,实例对象#,静态类型即NSString *类型声明的对象@, int类型i,double类型d

获取类所有成员变量的类型编码

通过Objective-C运行时库提供的一个函数ivar_getTypeEncoding(Ivar),用于获取指定实例变量(ivar)的类型编码

@interface Person : NSObject {
    int myIntVar;
    float myFloatVar;
    NSString *myStringVar;
}

@property (nonatomic, strong) NSString *name;
@property (nonatomic, assign) NSInteger age;

- (void)doSomething;
@end

#import <objc/runtime.h>

unsigned int count = 0;
Ivar *ivars = class_copyIvarList([Person class], &count);
for (int i = 0; i < count; i++) {
    Ivar ivar = ivars[i];
    ptrdiff_t offset = ivar_getOffset(ivar);
    const char *name = ivar_getName(ivar);
    const char *encoding = ivar_getTypeEncoding(ivar);
    NSLog(@"ivar: %s, offset: %td, encoding: %@", name, offset, encoding);
}
free(ivars);

/**
 打印结果如下:
 ivar: myIntVar, offset: 8, encoding: i
 ivar: myFloatVar, offset: 12, encoding: f
 ivar: myStringVar, offset: 16, encoding: @"NSString"
 ivar: _name, offset: 24, encoding: @"NSString"
 ivar: _age, offset: 32, encoding: q
 */

常见的类型编码

编码(Code)含义(Meaning)
c表示 char 类型的值
i表示 int 类型的值
s表示 short 类型的值
l表示 long 类型的值
q表示 long long 类型的值
C表示 unsigned char 类型的值
I表示 unsigned int 类型的值
S表示 unsigned short 类型的值
L表示 unsigned long 类型的值
Q表示 unsigned long long 类型的值
f表示 float 类型的值
d表示 double 类型的值
B表示 bool 类型的值
v表示 void 类型的值
*表示 NSString 类型的值
@表示对象(无论是静态类型还是 id 类型)的值
#表示 类对象 类型的值
:表示 SEL 类型的值
[array type]表示 数组NSArray 类型的值
{name=type... }表示 结构体 Struct 类型 的值
(name=type... )A union
bnumA bit field of num bits
^type表示指向某个类型的指针
?未知类型(除此之外,还可用户表示函数指针)

Objective-C不支持long double类型。@encode(long double)返回d,其类型编码与double相同。

参考文献

官方文档 - 类型编码器篇