參考 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,要講的差很少就這麼多了。暫時沒有想到還有什麼要補充的,後面想到了再補上來吧。
但願對你們有幫助,也歡迎你們指正錯誤或者進行討論。