OC中有一个经常使用到的概念叫做类型编码(type encodings).这个编码规则在方法的类型编码以及类型的动态动态转化中都有着非常重要的作用.
1. ObjC 的 type encodings 列表
编码 | 意义 |
---|---|
c | char 类型 |
i | int 类型 |
s | short 类型 |
l | long 类型,仅用在 32-bit 设备上 |
q | long long 类型 |
C | unsigned char 类型 |
I | unsigned int 类型 |
S | unsigned short 类型 |
L | unsigned long 类型 |
Q | unsigned long long 类型 |
f | float 类型 |
d | double 类型,long double 不被 ObjC 支持,所以也是指向此编码 |
B | bool 或 _Bool 类型 |
v | void 类型 |
* | C 字串(char *)类型 |
@ | 对象(id)类型 |
# | Class 类型 |
: | SEL 类型 |
[array type] | C 数组类型(注意这不是 NSArray) |
{name=type...} | 结构体类型 |
(name=type...) | 联合体类型 |
bnum | 位段(bit field)类型用 b 表示,num 表示字节数,这个类型很少用 |
^type | 一个指向 type 类型的指针类型 |
? | 未知类型 |
2.基础数据类型的type encodings
2.1 整型和浮点型数据
NSLog(@"char : %s, %lu", @encode(char), sizeof(char));
NSLog(@"short : %s, %lu", @encode(short), sizeof(short));
NSLog(@"int : %s, %lu", @encode(int), sizeof(int));
NSLog(@"long : %s, %lu", @encode(long), sizeof(long));
NSLog(@"long long. : %s, %lu", @encode(long long), sizeof(long long));
NSLog(@"float : %s, %lu", @encode(float), sizeof(float));
NSLog(@"double : %s, %lu", @encode(double), sizeof(double));
NSLog(@"NSInteger. : %s, %lu", @encode(NSInteger), sizeof(NSInteger));
NSLog(@"CGFloat : %s, %lu", @encode(CGFloat), sizeof(CGFloat));
NSLog(@"int32_t : %s, %lu", @encode(int32_t), sizeof(int32_t));
NSLog(@"int64_t : %s, %lu", @encode(int64_t), sizeof(int64_t));
在IA-32位系统上,输出结果:
char : c, 1 short : s, 2 int : i, 4 long : l, 4 long long. : q, 8 float : f, 4 double : d, 8 NSInteger : i, 4 CGFloat : f, 4 int32_t : i, 4 int64_t : q, 8
在IA-64位系统上进行同样的操作,输出结果:
char : c, 1
short : s, 2
int : i, 4
long : q, 8
long long. : q, 8
float : f, 4
double : d, 8
NSInteger : q, 8
CGFloat : d, 8
int32_t : i, 4
int64_t : q, 8
所以在IA-32和IA-64位操作系统上,long和CGFloat类型的类型编码发生了变化,所以在使用long和CGFloat类型时应该注意数据本身的长度需求,最高不好直接使用这两这种类型以免在不同的操作系统上造成数据的溢出.
2.2 布尔类型
NSLog(@"bool : %s, %lu", @encode(bool), sizeof(bool));
NSLog(@"_Bool : %s, %lu", @encode(_Bool), sizeof(_Bool));
NSLog(@"BOOL : %s, %lu", @encode(BOOL), sizeof(BOOL));
NSLog(@"Boolean : %s, %lu", @encode(Boolean), sizeof(Boolean));
NSLog(@"boolean_t: %s, %lu", @encode(boolean_t), sizeof(boolean_t));
在IA-32位操作系统上,输出结果为:
bool : B, 1
_Bool : B, 1
BOOL : c, 1
Boolean : C, 1
boolean_t: i, 4
在IA-64位操作系统上,输出结果为:
bool : B, 1
_Bool : B, 1
BOOL : B, 1
Boolean : C, 1
boolean_t: I, 4
可以看出不同的布尔表示编码还是比较作妖的.
2.3 void、指针和数组
NSLog(@"void : %s, %lu", @encode(void), sizeof(void));
NSLog(@"char * : %s, %lu", @encode(char *), sizeof(char *));
NSLog(@"short * : %s, %lu", @encode(short *), sizeof(short *));
NSLog(@"int * : %s, %lu", @encode(int *), sizeof(int *));
NSLog(@"char[3] : %s, %lu", @encode(char[3]), sizeof(char[3]));
NSLog(@"short[3] : %s, %lu", @encode(short[3]), sizeof(short[3]));
NSLog(@"int[3] : %s, %lu", @encode(int[3]), sizeof(int[3]));
在IA-32位操作系统上:
void : v, 1
char * : *, 4
short * : ^s, 4
int * : ^i, 4
char[3] : [3c], 3
short[3] : [3s], 6
int[3] : [3i], 12
在IA-64位操作系统上:
void : v, 1
char * : *, 8
short * : ^s, 8
int * : ^i, 8
char[3] : [3c], 3
short[3] : [3s], 6
int[3] : [3i], 12
指针类型与操作系统位数相同,所以在不同位长的操作系统上,指针类型有所不同.
2.4 结构体
NSLog(@"CGSize: %s, %lu", @encode(CGSize), sizeof(CGSize));
NSLog(@"CGPoint: %s, %lu", @encode(CGPoint), sizeof(CGPoint));
在IA-32位操作系统上:
CGSize: {CGSize=ff}, 8
CGPoint: {CGPoint=ff}, 8
在IA-64位操作系统上:
CGSize: {CGSize=dd}, 16
CGPoint: {CGPoint=dd}, 16
由于在IA-32位和IA-64位操作系统上对于CGFloat的定义不一样,所以关于这两个结构体的类型编码会不一致.
3. ObjC 数据类型的 type encodings
3.1 基本类型
NSLog(@"Class : %s", @encode(Class));
NSLog(@"NSObject: %s", @encode(NSObject));
NSLog(@"NSString: %s", @encode(NSString));
NSLog(@"id : %s", @encode(id));
NSLog(@"Selector : %s", @encode(SEL));
输出结果为:
Class : #
NSObject: {NSObject=#}
NSString : {NSString=#}
id : @
Selector: :
可以看到对象的类名称的编码方式跟结构体相似,等于号后面那个 #
就是Class指针了,是一个 Class
类型的数据。
3.2 属性和成员变量
- 属性
objc_property_t property = class_getProperty([NSObject class], "description");
if (property) {
NSLog(@"%s - %s", property_getName(property), property_getAttributes(property));
} else {
NSLog(@"not found");
}
- 输出结果为:
description - T@"NSString",R,C
其中:R
表示 readonly
,C
表示 copy
,是属性的修饰词;T表示类型Type.
- 成员变量
@interface TestClass : NSObject {
int testInt;
NSString *testStr;
}
@end
Ivar ivar = class_getInstanceVariable([TestClass class], "testInt");
if (ivar) {
NSLog(@"%s - %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
} else {
NSLog(@"not found");
}
ivar = class_getInstanceVariable([TestClass class], "testStr");
if (ivar) {
NSLog(@"%s - %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
} else {
NSLog(@"not found");
}
输出结果:
testInt - i
testStr - @"NSString"
Method method = class_getInstanceMethod([UIView class], @selector(setFrame:));
if (method) {
NSLog(@"%@ - %s", NSStringFromSelector(method_getName(method)), method_getTypeEncoding(method));
} else {
NSLog(@"not found");
}
输出结果:
setFrame: - v48@0:8{CGRect={CGPoint=dd}{CGSize=dd}}16
编码代表的意义:
- 第一个字符
v
是表示函数的返回值是 void 类型; -
48
表示方法参数表的字节长度; @
表示一个对象,在 ObjC 里这里传递的是self,
后续的0
代表参数self在结构体的位置,即对应的 offset;-
:
表示一个 selector,用来指出要调用方法选择器,后续的8
代表是 selector 参数在结构体中的位置,即selector对应的offset; {CGRect={CGPoint=dd}{CGSize=dd}}
是CGRect 结构体的 type encoding,从这里也可以看出结构体嵌套使用时对应的 type encoding 是这种格式的,这个结构体包含 4 个 double 类型的数据,所以总长度应该是 32,最后的16
是最后一个参数的在结构体中的offset,加上刚刚的参数长度 32 正好是整个函数参数表的长度.
然后我们再看一下类方法的方法编码
Method method = class_getInstanceMethod(object_getClass([UIView class]), @selector(alloc));
if (method) {
NSLog(@"%@ - %s", NSStringFromSelector(method_getName(method)), method_getTypeEncoding(method));
} else {
NSLog(@"not found");
}
输出结果为:
alloc - @16@0:8
编码代表的意义:
- @ 代表返回值是id类型;
- 16代表方法参数表的字节长度;
- @代表方法的第一个参数self,0代表self在结构体中的位置;
- :代表方法选择器,即sel,8代表sel在该结构体中的位置(即偏移量offset).
3.4 Block的编码
关于block的方法编码,我们可以借助于Aspects中重写的block结构体来获取:
typedef NS_OPTIONS(int, AspectBlockFlags) {
AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
AspectBlockFlagsHasSignature = (1 << 30)
};
typedef struct _AspectBlock {
__unused Class isa;
AspectBlockFlags flags;
__unused int reserved;
void (__unused *invoke)(struct _AspectBlock *block, ...);
struct {
unsigned long int reserved;
unsigned long int size;
// requires AspectBlockFlagsHasCopyDisposeHelpers
void (*copy)(void *dst, const void *src);
void (*dispose)(const void *);
// requires AspectBlockFlagsHasSignature
const char *signature;
const char *layout;
} *descriptor;
// imported variables
} *AspectBlockRef;
id(^testBlock)(NSString *, NSArray *) = ^id(NSString *str, NSArray *arr){
return str;
};
AspectBlockRef layout = (__bridge void *)testBlock;
void *des = layout->descriptor;
des += 2 * sizeof(unsigned long int);
if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) {
des += sizeof(void *);
}
if (layout->flags & AspectBlockFlagsHasSignature) {
const char *signature = *(void **)des;
NSLog(@"-----%s", signature);
}
输出结果为:-----@24@?0@"NSString"8@"NSArray"16
所以在block的方法编码中,由于没有selector这个参数,所以第一个传递进来的参数其实就是block自己.
- @ 返回值为id类型;
- @?block本身, 0是block在结构体中的位置偏移量;
- @"NSString" 代表第一个参数,8是NSString这个参数在结构体中的偏移量;
- @"NSArray"16代表第二个参数,16是第二个参数在结构体中的偏移量.