参考 Apple Developer 官方文档:Type Encodings,Objective-C Runtimehtml
原文地址:苹果梨的博客数组
咱们在 JSON <-> Dictionary <-> Model 中面临的一个很大的问题就是判断数据须要转换成什么样的类型。好在 ObjC 做为一款动态语言,利用 runtime 能够轻松解决这个问题。再配合转换器和 KVC,就能够轻松把咱们解析好的值放进对应 Model 里。今天要给你们介绍的就是这个类型编码(Type Encodings)的具体细节。bash
编码 | 意义 |
---|---|
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 类型的指针类型 |
? | 未知类型 |
简单给你们举个例子,咱们先来看看经常使用的数值类型,用下面的代码来打印日志:app
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));
复制代码
在 32-bit 设备上输出日志以下:ide
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
复制代码
你们注意下上面日志里的 long
类型输出结果,而后咱们再看下在 64-bit 设备上的输出日志:函数
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
复制代码
能够看到 long
的长度变成了 8,并且类型编码也变成 q
,这就是表格里那段话的意思。ui
因此呢,通常若是想要整形的长度固定且长度能被一眼看出,建议使用例子最后的 int32_t
和 int64_t
,尽可能少去使用 long
类型。编码
而后要提一下 NSInteger
和 CGFloat
,这俩都是针对不一样 CPU 分开定义的:spa
#if __LP64__ || (TARGET_OS_EMBEDDED && !TARGET_OS_IPHONE) || TARGET_OS_WIN32 || NS_BUILD_32_LIKE_64
typedef long NSInteger;
#else
typedef int NSInteger;
#endif
#if defined(__LP64__) && __LP64__
# define CGFLOAT_TYPE double
#else
# define CGFLOAT_TYPE float
#endif
typedef CGFLOAT_TYPE CGFloat;
复制代码
因此他们在 32-bit 设备上长度为 4,在 64-bit 设备上长度为 8,对应类型编码也会有变化。指针
用下面的代码打印日志:
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));
复制代码
在 32-bit 设备上输出日志以下:
bool : B, 1
_Bool : B, 1
BOOL : c, 1
Boolean : C, 1
boolean_t: i, 4
复制代码
在 64-bit 设备上输出日志以下:
bool : B, 1
_Bool : B, 1
BOOL : B, 1
Boolean : C, 1
boolean_t: I, 4
复制代码
能够看到咱们最经常使用的 BOOL
类型还真的是有点妖,这个妖一句两句还说不清楚,我在下一篇博客里会介绍一下。在本篇博客里,这个变化却是对咱们解析模型不会产生很大的影响,因此先略过。
用下面的代码打印日志:
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]));
复制代码
在 64-bit 设备上输出日志以下:
void : v, 1
char * : *, 8
short * : ^s, 8
int * : ^i, 8
char[3] : [3c], 3
short[3]: [3s], 6
int[3] : [3i], 12
复制代码
在 32-bit 设备上指针类型的长度会变成 4,这个就很少介绍了。
能够看到只有 C 字串类型比较特殊,会处理成 *
编码,其它整形数据的指针类型仍是正常处理的。
用下面的代码打印日志:
NSLog(@"CGSize: %s, %lu", @encode(CGSize), sizeof(CGSize));
复制代码
在 64-bit 设备上输出日志以下:
CGSize: {CGSize=dd}, 16
复制代码
由于 CGSize
内部的字段都是 CGFloat
的,在 64-bit 设备上实际是 double
类型,因此等于号后面是两个 d
编码,总长度是 16。
联合体的编码格式十分相似,很少赘述。而位段如今用到的十分少,也不介绍了,有兴趣了解位段的能够参考维基百科。
ObjC 数据类型大部分状况下要配合 runtime 使用,单独用 @encode
操做符的话,基本上也就能作到下面这些:
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: :
复制代码
能够看到对象的类名称的编码方式跟结构体类似,等于号后面那个 #
就是 isa
指针了,是一个 Class
类型的数据。
咱们能够用 runtime 去得到类的属性对应的 type encoding:
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
,后面跟的这段 @"NSString"
就是 type encoding 了。能够看到 runtime 比较贴心的用双引号的方式告诉了咱们这个对象的实际类型是什么。
关于属性的修饰词,更多内容能够参考 Apple 文档。其中 T
段始终会是第一个 attribute,因此处理起来会简单点。
而若是是成员变量的话,咱们能够用相似下面的办法去得到 type encoding:
@interface TestObject : NSObject {
int testInt;
NSString *testStr;
}
@end
Ivar ivar = class_getInstanceVariable([TestObject class], "testInt");
if (ivar) {
NSLog(@"%s - %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
} else {
NSLog(@"not found");
}
ivar = class_getInstanceVariable([TestObject class], "testStr");
if (ivar) {
NSLog(@"%s - %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
} else {
NSLog(@"not found");
}
复制代码
得到的输出会是这样:
testInt - i
testStr - @"NSString"
复制代码
由于成员变量没有属性修饰词那些,因此直接得到的就是 type encoding,格式和属性的 T
attribute 同样。
有的时候模型设置数据的方式并非用属性的方式,而是用方法的方式。咱们举个例子:
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
复制代码
输出就是整个类方法的 type encoding,关于这个我没找到官方文档的介绍,因此只能根据本身的推测来介绍这个编码的格式:
v
是表示函数的返回值是 void 类型48
表示函数参数表的长度(指返回值以后的全部参数,虽然返回值在 runtime 里也算是个参数)@
表示一个对象,在 ObjC 里这里传递的是 self
,实例方法是要传递实例对象给函数的0
上面参数对应的 offset:
表示一个 selector,用来指出要调用的函数是哪一个8
是 selector 参数的 offset,由于这是跑在 64-bit 设备上的,因此 @
和 :
的长度都是 8{CGRect={CGPoint=dd}{CGSize=dd}}
是 CGRect 结构体的 type encoding,从这里也能够看出结构体嵌套使用时对应的 type encoding 是这种格式的,这个结构体包含 4 个 double 类型的数据,因此总长度应该是 3216
是最后一个参数的 offset,加上刚刚的参数长度 32 正好是整个函数参数表的长度咱们拿另外一个类方法来验证下:
Method method = class_getInstanceMethod([UIViewController class], @selector(title));
if (method) {
NSLog(@"%@ - %s", NSStringFromSelector(method_getName(method)), method_getTypeEncoding(method));
} else {
NSLog(@"not found");
}
复制代码
输出:
@16@0:8
复制代码
能够看到很惋惜,NSString 类型在类方法的 type encoding 里是不会有引号内容的,因此咱们只能知道这个参数是个 id 类型。编码的具体解析:
@
- 返回 id 类型16
- 参数表总长度@
- 用来传递 self
,是 id 类型0
- self
参数的 offset:
- 传递具体要调用哪一个方法,selector 类型8
- selector 参数的 offset若是是类的静态方法而不是实例方法,咱们能够用相似这样的代码得到 Method 结构体:
Method method = class_getClassMethod([TestObject class], @selector(testMethod));
复制代码
不过提及来这种格式的编码仍是不容易解析,因此咱们能够用另外一种方式直接拿对应位置的参数的编码:
Method method = class_getInstanceMethod([UIView class], @selector(setFrame:));
if (method) {
NSLog(@"%@ - %d", NSStringFromSelector(method_getName(method)), method_getNumberOfArguments(method));
NSLog(@"%@ - %s", NSStringFromSelector(method_getName(method)), method_copyArgumentType(method, 2));
} else {
NSLog(@"not found");
}
复制代码
输出内容以下,这里是得到了 index 为 2 的参数的编码:
setFrame: - 3
setFrame: - {CGRect={CGPoint=dd}{CGSize=dd}}
复制代码
这样就只会得到 type encoding 而不会带上 offset 信息,就容易解析多了。
另外从这里也能够看到,返回值其实也是算一个参数。
还有些 type encodings 的细节和解析模型其实不太相关,不过也在这里介绍一下。
用如下代码打印日志:
objc_property_t property = class_getProperty([UIScrollView class], "delegate");
if (property) {
NSLog(@"%s - %s", property_getName(property), property_getAttributes(property));
} else {
NSLog(@"not found");
}
复制代码
会得到输出:
delegate - T@"<UIScrollViewDelegate>",W,N,V_delegate
复制代码
能够看到在属性的 type encoding 里,会用双引号和尖括号表示出 protocol 的类型
可是去查看方法的话:
Method method = class_getInstanceMethod([UIScrollView class], @selector(setDelegate:));
if (method) {
NSLog(@"%@ - %d", NSStringFromSelector(method_getName(method)), method_getNumberOfArguments(method));
NSLog(@"%@ - %s", NSStringFromSelector(method_getName(method)), method_copyArgumentType(method, 2));
} else {
NSLog(@"not found");
}
复制代码
依然仍是只能获得这样的编码:
setDelegate: - 3
setDelegate: - @
复制代码
protocol 类型在模型解析中并无很大的指导做用,由于咱们没法知道具体实现了 protocol 协议的 class 是什么。
直接亮结果吧,得到的 type encoding 是 @?
,没有任何参考意义,还好咱们作模型解析用不到这个。
对 setEnable:
方法取 type encoding 的话会获得:
setEnabled: - v20@0:8B16
复制代码
但是 bool 的长度明明只有 1 啊,因此这是为何呢?感兴趣的朋友能够了解下内存对齐。
关于 Type Encodings,要讲的差很少就这么多了。暂时没有想到还有什么要补充的,后面想到了再补上来吧。
但愿对你们有帮助,也欢迎你们指正错误或者进行讨论。