你真的了解iOS怎么取屬性的嗎?
如果iOS中談到取屬性,相信大家都會夸夸其談,不就是get方法嗎?或者大談kvc取屬性的機制。不得不說這些也是對的。這時大家可能就疑惑了,那你還要說啥的!!大家不妨想想,這些都是代碼層的實現,其實我們的代碼最終都會被編譯,然后加載到內存中,那你在內存中是怎么取到屬性的呢??對的我們討論就是它!
指針
如果說到內存,不知道大家會不會想到**指針**呢?這里簡單介紹一下,讓大家有個簡單的理解。如果理解不了的話,建議大家找一個C語言的教程,學一下指針。
指針(Pointer)是編程語言中的一個對象,利用地址,它的值直接指向(points to)存在電腦存儲器中另一個地方的值。由于通過地址能找到所需的變量單元,可以說,地址指向該變量單元。因此,將地址形象化的稱為“指針”。意思是通過它能找到以它為地址的內存單元。
-
*那到底什么是指針呢??
?類型 * 變量名
這就是聲明了一個指針變量
-
?指針類型有什么作用呢?
比如:
1 | int *?num; |
指針變量的類型決定了通過這個指針找到變量的首地址以后,連續操作多少個字節空間
為什么會說連續操作多少個字節空間??主要是指針有算術運算加減,說白了就是指針的移動。
-
?指針是int* 連續操作4個字節
-
指針是double* 連續操作8個字節
比如:
1 2 | int *?p?=?# p++; |
當指針+1的時候,這時候指針要移動1個單元,而不是1個字節!!
那到底這1個單元是多大呢?其實1個單元的大小就是指針類型的大小。這里是`int`型,所以移動了4個字節
-------------------------------------------------------------------------------------------
以上就是簡單給大家做了**指針**介紹,其實理解了指針,對于我們出現的一些野指針的bug、runtime源碼中的一些機制等等是有所幫助的。言歸正傳。接下來讓我看一道題,真正的去了解內存和指針的關系。
1 2 3 4 5 6 | int ?num1?=?10; int ?num?=?20; int *?p?=?# p++; printf ("%d ",*p); //打印為10,因為p++,指針已經移動了4個字節,下一個內存存儲10正好是4個字節 |
這里其實是前邊聲明了一個num1,正好是4個字節,所以就將10取出來了。(說白了就是內存中下一個連續的4個字節存的是什么取出來就是什么)
說了這么多都是指針和內存,建議大家搞明白以上內容再讀以下的內容,如果上邊都搞不明白的話,下邊有關iOS中runtime取屬性的內容有可能就會云里霧里。
iOS中成員變量與屬性
以下題目是sunnyxx習題中的一題,網上也有詳細的[答案](http://blog.csdn.net/shznt/article/details/50481819)。這里作者就簡述一下自己的理解,如果想看非常詳細的答案的話可以點擊上邊的鏈接。
下面代碼會? Compile Error / Runtime Crash / NSLog…?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | @interface?Sark?:?NSObject @property?(nonatomic,?copy)?NSString?*name; @end @implementation?Sark -?( void )speak { ???? NSLog(@ "my?name?is?%@" ,?self.name); } @end @interface?Test?:?NSObject @end @implementation?Test -?(instancetype)init { ???? self?=?[super?init]; ???? if ?(self)?{ ???????? id?cls?=?[Sark? class ]; ???????? void ?*obj?=?&cls; ???????? [(__bridge?id)obj?speak]; ???? } ???? return ?self; } @end int ?main( int ?argc,? const ?char ?*?argv[])?{ ???? @autoreleasepool?{ ???????? [[Test?alloc]?init]; ???? } ???? return ?0; |
}
答案:代碼正常輸出,輸出結果為:
2014-11-07 14:08:25.698 Test[1097:57255] my name is
-
?為什么能夠正常運行,并調用到speak方法?
計算機將我們的`Sark`類信息通過
`id cls = [Sark class];`這一行加載到內存中,并且取得了`cls`變量。這個時候其實我們只要知道`cls`這個變量的地址就行了,其實相當于類的對象的地址。`void *obj = &cls;`這句話就讓我們獲得了對象的地址。(平時我們`new`對象的時候就干了兩件事:1、申請內存;2、獲取內存的地址(對象變量的地址就是內存的地址),這里的對象與我們`new`出來的對象有所不同。但是雖然不是new對象,iOS中`Class`對象已經存儲了我們需要的東西。比如有關變量的內存**偏移**、方法等等所有的信息)接下來可以干我們想干的任何事情了。
>iOS中`Class`中存儲了我們想要的東西,這一塊的知識要上升到了runtime的源碼,上邊給到的鏈接中有詳細介紹。其實大家想想編譯完之后肯定得有一個類或者其他東西存儲著有關內存等等相關的信息的。
-
為什么self.name會輸出?
我們程序在編譯之后其實就是一堆的匯編指令,匯編操作的就是**內存地址**。所以當我們程序運行的時候都是**寄存器**一條條的執行匯編指令。其實執行匯編指令最重要的就是變量、方法、對象等等的一大堆地址,因為寄存器有限,所以會把有限的數據從內存中加載到寄存器。所以總得來說是操作寄存器的地址和內存地址。如果沒有地址那怎么知道執行什么呢?所以只要有地址了就好辦了。
指令如下圖:
?
變量對應于runtime的objc_ivar代碼如下:
?
1 2 3 4 5 6 7 8 | struct ?objc_ivar?{ ???? char ?*ivar_name??????????????????????????????????????????OBJC2_UNAVAILABLE; ???? char ?*ivar_type??????????????????????????????????????????OBJC2_UNAVAILABLE; ???? int ?ivar_offset??????????????????????????????????????????OBJC2_UNAVAILABLE; #ifdef?__LP64__ ???? int ?space????????????????????????????????????????????????OBJC2_UNAVAILABLE; #endif } |
其中?`ivar_offset`就是變量的地址偏移字節。
變量地址=對象地址 + 基類大小 + ivar偏移字節
到這里再結合我上邊指針的鋪墊相信大家應該明白了為什么為什self.name會輸出吧。
其實通過這里我們也知道了其實iOS中取對象就是指針的偏移。
1 2 3 4 5 6 | Student?*student?=?[[Student?alloc]?init]; ?? Ivar?age_ivar?=?class_getInstanceVariable(object_getClass(student),? "age" ); ?? int ?*age_pointer?=?( int ?*)((__bridge? void ?*)(student)?+?ivar_getOffset(age_ivar)); ?? NSLog(@ "age?ivar?offset?=?%td" ,?ivar_getOffset(age_ivar)); ?? *age_pointer?=?10; |