位运算世界畅游指南
目錄
- 概念介紹
- 基本位運(yùn)算符
- 按位與 ( AND )
- 按位或 ( OR )
- 按位異或 ( XOR )
- 取反 ( NOT )
- 右移 ( >> )
- 左移 ( << )
- 基礎(chǔ)技巧
- 將某些二進(jìn)制位設(shè)置為1
- 掩碼
- 取出第i位二進(jìn)制值
- 計(jì)算無(wú)符號(hào)變量的取值范圍
- 奇偶判斷
- 統(tǒng)計(jì)二進(jìn)制中1個(gè)數(shù)
- 交換兩個(gè)變量的值(無(wú)臨時(shí)變量)
- 子集生成
- 進(jìn)階技巧
- getBits
- setBits
- Objective-C的Runtime中的位運(yùn)算應(yīng)用
- 判斷是否是TaggedPointer
- isa_t
- 方法緩存中的Hash函數(shù)
- NS_OPTIONS
- 參考資料
概念介紹
我們都知道,計(jì)算機(jī)中的所有數(shù)據(jù)最終都是以二進(jìn)制(bit)的形式存儲(chǔ)在計(jì)算機(jī)的。而在我們平時(shí)開發(fā)中所接觸數(shù)據(jù)的大多是字節(jié)為單位的,有了位運(yùn)算之后我們就可以操作字節(jié)中的比特位了。在iOS的runtime源碼以及NS_OPTIONS 中都運(yùn)用了位運(yùn)算的知識(shí),可見其重要性了。另外值得一提的是,大部分語(yǔ)言的位運(yùn)算符都相同,所以這是一篇老少皆宜的文章。
閱讀本篇文章前,你需要知道的一些東西:
基本位運(yùn)算符
位運(yùn)算符就像是控制比特位的扳手,在學(xué)習(xí)位運(yùn)算前先介紹下每個(gè)運(yùn)算符的意義及其用法。
按位與 ( AND )
運(yùn)算規(guī)則:只有在兩個(gè)值都為1時(shí)結(jié)果才為1,兩個(gè)值中有一個(gè)為0結(jié)果便為0。在編程語(yǔ)言里一般用 & 表示與運(yùn)算符。
舉個(gè)例子,10100001 & 10001001 = 10000001。(注:操作數(shù)都為二進(jìn)制。)
按位或 ( OR )
運(yùn)算規(guī)則: 兩個(gè)值中有一個(gè)為1結(jié)果便為1,兩個(gè)值都為0時(shí)結(jié)果才為0。在編程語(yǔ)言里一般用 | 表示或運(yùn)算符。
舉個(gè)例子,10100001 | 10001001 = 10101001。
按位異或 ( XOR )
運(yùn)算規(guī)則: 只有當(dāng)兩個(gè)值不相同時(shí)結(jié)果才為1,兩個(gè)值相同時(shí)結(jié)果為0。在編程語(yǔ)言里一般用 ^ 表示異或運(yùn)算符。
舉個(gè)例子,10100001 ^ 10001001 = 00101000。
取反 ( NOT )
在數(shù)值的二進(jìn)制表示方式上,將0變?yōu)?,將1變?yōu)?。在編程語(yǔ)言里一般用 ~ 表示取反運(yùn)算符。
來(lái)看一個(gè)例子可能會(huì)更加直觀:
右移 ( >> )
右移將操作數(shù)的二進(jìn)制位整體向右移動(dòng)N位,空出部分用0填充,在編程語(yǔ)言里一般用 >>表示右移運(yùn)算符。
舉個(gè)例子,下圖對(duì)二進(jìn)制 10111101 右移3位后,最右邊的101被移除,左邊空出來(lái)3位用0填充(本文章默認(rèn)所有數(shù)據(jù)都為無(wú)符號(hào)類型,所以本操作為邏輯右移)。
左移 ( << )
左移將操作數(shù)的二進(jìn)制位整體向左移動(dòng)N位,空出部分用0填充,在編程語(yǔ)言里一般用 << 表示左移運(yùn)算符。
舉個(gè)例子,下圖對(duì)二進(jìn)制 10111101 左移4位后,最左邊的1011被移除,右邊空出來(lái)4位用0填充。
基礎(chǔ)技巧
這里先介紹一些比較簡(jiǎn)單實(shí)用的位運(yùn)算技巧,這些技巧在日常開發(fā)中也是比較常用的。
將某些二進(jìn)制位設(shè)置為1
假設(shè)x=0b10011010,現(xiàn)在我想將第5、6位置為1,將其變?yōu)?b11111010,那么執(zhí)行 (x = x | 0b01100000) 就是我們想要的結(jié)果;那若是想將第0、5、6為置為1,變成0b11111011呢?那么執(zhí)行(x = x | 0b01100001)就是我們想要的結(jié)果。 根據(jù)上面的兩個(gè)例子,我們可以得到一個(gè)結(jié)論:
- x = x | SET_ON ,該語(yǔ)句將x中對(duì)應(yīng)于SET_ON中為1的二進(jìn)制位設(shè)置為1;x中對(duì)應(yīng)于SET_ON中為0的二進(jìn)制位保持不變。
掩碼
掩碼這個(gè)詞經(jīng)常能在計(jì)算機(jī)網(wǎng)絡(luò)里聽到,比較熟悉的話就是子網(wǎng)掩碼了。掩碼是起的非常好的一個(gè)名字,當(dāng)我們的操作數(shù)和掩碼進(jìn)行與運(yùn)算(&)后,掩碼中二進(jìn)制為0的位會(huì)屏蔽掉原操作數(shù)對(duì)應(yīng)的二進(jìn)制位。 舉個(gè)例子,假如現(xiàn)在我有一個(gè)2個(gè)字節(jié)的數(shù)據(jù)0xBA15,若要屏蔽掉中間0xA1這8位二進(jìn)制變成0xB005,該如何設(shè)計(jì)掩碼呢?答案很簡(jiǎn)單,只要將掩碼中間8位設(shè)為0其他設(shè)為1即可,所以本例中的掩碼應(yīng)為0xF00F,0xBA15 & 0xF00F=0xB005。可以結(jié)合下圖理解:
取出第i位二進(jìn)制值
這個(gè)函數(shù)傳入一個(gè)data,返回其二進(jìn)制從右邊開始數(shù)第i位的值。
unsigned char getBit( unsigned long data , int i ) {// i = 0時(shí),代表取最右邊的哪一位。data = data >> i ;return data & 1 ; } 復(fù)制代碼原理很簡(jiǎn)單,先將data右移i位,這樣能保證第i位的值處于data的最右邊,然后再用data & 1取出即可。 舉個(gè)例子,如果我調(diào)用了{(lán)getBit(168,3)},168對(duì)應(yīng)的二進(jìn)制為10101000,右移3位后變成00010101,最后00010101 & 00000001 = 1,取出成功。
計(jì)算無(wú)符號(hào)變量的取值范圍
筆者在mac上的unsigned long 是8個(gè)字節(jié),可以存儲(chǔ)64位二進(jìn)制,由于沒有符號(hào)位,故只需將這64位二進(jìn)制都填充為1就得到unsigned long變量的最大值了。
// 將全0取反變?yōu)槿?裝進(jìn)變量x中。unsigned long x = ~0;// 輸出二進(jìn)制為全1的變量xprintf("unsigned long max = %lu\n",x);復(fù)制代碼奇偶判斷
如果最后一位二進(jìn)制為0,那么這個(gè)數(shù)字就是偶數(shù),如果為1就是奇數(shù)。這里給出實(shí)現(xiàn)函數(shù):
int isOdd(int value) {return (value & 1); // 取出最后一位二進(jìn)制,若為1則是奇數(shù) } 復(fù)制代碼說(shuō)下大致原理:若最后一位二進(jìn)制為0,那么二進(jìn)制轉(zhuǎn)成十進(jìn)制后必然可以寫成2n的形式,必為偶數(shù)。比如我隨便寫一個(gè)最后一位為0的二進(jìn)制數(shù)字 10001010,那么其十進(jìn)制數(shù)為2+2^3+2^7 = 2*(1+2^2+2^6),故為偶數(shù),大家可以多寫幾組數(shù)字驗(yàn)證。
關(guān)于負(fù)數(shù):雖然負(fù)數(shù)在計(jì)算機(jī)中以補(bǔ)碼的方式存儲(chǔ),但由于補(bǔ)碼最后一位和原碼最后一位相同,所以上面的函數(shù)同樣適用于負(fù)數(shù)。為什么呢?舉個(gè)例子:
- 假如最后一位是0,取反后變成1,然后再+1又變成0。
- 假如最后一位是1,取反后變成0,然后再+1又變成1。
看到了吧,最后又變回去了。
統(tǒng)計(jì)二進(jìn)制中1個(gè)數(shù)
int x =0xba;//10111010 int count = 0; while (x!=0) {x = x&(x-1);count++; } printf("%d\n",count); 復(fù)制代碼循環(huán)中每次執(zhí)行x = x&(x-1)后,x的二進(jìn)制的最后一個(gè)1就會(huì)被消去。當(dāng)所有1都被消去后,count計(jì)數(shù)完畢,x=0,退出循環(huán)。
那么為什么x = x&(x-1)能夠消去其二進(jìn)制的最后一個(gè)1呢?舉個(gè)例子:
- 假如x=101000,那么 x-1=100111。
- 假如x=101011,那么 x-1=101010。
可以發(fā)現(xiàn)規(guī)律:
- 當(dāng)x=nn..nn100..00這種形式時(shí),x-1=nn..nn011..11。 這個(gè)時(shí)候x & (x-1) = nn..nn100..00 & nn..nn011..11 = nn..nn000.00,x最后一個(gè)1被消去。
- 當(dāng)x=nn..nn1這種形式時(shí),x-1=nn..nn0。 這個(gè)時(shí)候x & (x-1) = nn..nn1 & nn..nn0 = nn..nn0,x最后一個(gè)1被消去。
交換兩個(gè)變量的值(無(wú)臨時(shí)變量)
// 注:參數(shù)是c++的引用類型。 void swap(int &a,int &b) {a=a^b;b=a^b;a=a^b; } 復(fù)制代碼想要了解原理,需要先知道幾個(gè)異或運(yùn)算的性質(zhì):
- 交換律:a^b = b^a
- 結(jié)合律:(a^b)^c = a^(b^c)
- 恒等律:a^0 = a
- 規(guī)零律:a^a = 0
- 自反性:a^b^b = a
假設(shè)一開始,a=k,b=t。
- 執(zhí)行a=a^b后 a=k^t;
- 執(zhí)行b=a^b后 b = k^t^t=k,注意這里用到了自反性;
- 執(zhí)行a=a^b后 a=k^t^k=t^k^k=t,注意這里用到了交換律和自反性;
- 最后得到a=t,b=k,交換完成。
當(dāng)然了,不僅限于交換整型變量。舉一個(gè)不太常用的例子,我們可以不用臨時(shí)變量交換兩個(gè)c語(yǔ)言字符串。下面代碼中的a和b本質(zhì)上是在交換"a-a-a-a-a-a"和"b-b-b-b-b-b"地址,所以效果也是一樣的。
char *a = "a-a-a-a-a-a";// 存儲(chǔ)在數(shù)據(jù)區(qū)的字符串常量char *b = "b-b-b-b-b-b";//存儲(chǔ)在數(shù)據(jù)區(qū)的字符串常量printf("before exchange: a=%s,b=%s\n",a,b);a = (char*)((long)a^(long)b);b = (char*)((long)a^(long)b);a = (char*)((long)a^(long)b);printf("after exchange: a=%s,b=%s\n",a,b);/*最終輸出為:/*最終輸出為:before exchange: a=a-a-a-a-a-a,b=b-b-b-b-b-bafter exchange: a=b-b-b-b-b-b,b=a-a-a-a-a-a*/ 復(fù)制代碼子集生成
假設(shè)現(xiàn)在有一集合A={a,b,c},要求生成這個(gè)集合的所有子集。構(gòu)造子集時(shí),我們可以使用二進(jìn)制中第i位的值決定是否要選取集合A中的第i個(gè)元素。其中值為1代表選取,值為0代表不選取。舉個(gè)例子,100代表只選第一個(gè)元素a,其構(gòu)成的子集為{a};101代表選取第一個(gè)a以及第三個(gè)c,其構(gòu)成的子集為{a,c}。
下面列舉出A的所有子集:
| 0 | {} | 什么都不選 | 000 |
| 1 | {c} | 不選a,不選b,選c | 001 |
| 2 | {b} | 不選a,選b,不選c | 010 |
| 3 | {b,c} | 不選a,選b,選c | 011 |
| 4 | {a} | 選a,不選b,不選c | 100 |
| 5 | {a,c} | 選a,不選b,選c | 101 |
| 6 | {a,b} | 選a,選b,不選c | 110 |
| 7 | {a,b,c} | 選a,選b,選c | 111 |
細(xì)心的話,應(yīng)該能發(fā)現(xiàn)上面的表格中的編號(hào)和二進(jìn)制剛剛好能對(duì)的上。所以對(duì)于有個(gè)n個(gè)元素的集合,只要生成0到2^n-1個(gè)整數(shù)編號(hào),然后根據(jù)每個(gè)編號(hào)對(duì)應(yīng)的二進(jìn)制解析出相應(yīng)的子集即可。 下面是c語(yǔ)言實(shí)現(xiàn)的代碼:
void prinstSubSet(char *S,int id) {int n = (int)strlen(S);// 集合的元素個(gè)數(shù)char result[100];int index=0;printf("{");for(int i=n-1;i>=0;i--){if ((id>>i)&1) {// 若第i位值為1,代表選擇第i個(gè)元素(從右邊開始數(shù))result[index++]=S[n-1-i];//由于字符串第0個(gè)字符是最左邊,所以要顛倒下。}}for(int i =0;i<index;i++) {printf("%c",result[i]);if(i!=index-1)printf(",");}printf("}\n"); } void create(char *S) {int n = (int)strlen(S);// 集合的元素個(gè)數(shù)int begin = 0;int end = (1<<n)-1; // 2^n-1//生成0到2^n-1個(gè)編號(hào)(id)for (int id = begin;id<=end;id++) {prinstSubSet(S, id);// 根據(jù)編號(hào)對(duì)應(yīng)的二進(jìn)制輸出子集} } int main(int argc, const char * argv[]) {create("abc");// 生成{a,b,c}的子集return 0; } 復(fù)制代碼進(jìn)階技巧
這里介紹C語(yǔ)言程序設(shè)計(jì)這本書中的兩個(gè)非常實(shí)用的函數(shù),相信你在平時(shí)的項(xiàng)目中也能應(yīng)用的到。
getBits
該函數(shù)用來(lái)返回x中從右邊數(shù)第p位開始向右數(shù)n位二進(jìn)制。
unsigned getBits(unsigned x,int p,int n) {return (x>>(p+1-n)) & ~(~0<<n); } 復(fù)制代碼舉個(gè)例子,調(diào)用getBits(168,5,3)后,返回168對(duì)應(yīng)二進(jìn)制10101000從右邊數(shù)第5位開始向右3位二進(jìn)制,也就是101。可以結(jié)合下圖理解:
下面來(lái)說(shuō)以下原理:-
一開始執(zhí)行(x>>(p+1-n)) 這里是為了將期望獲得的字段移動(dòng)到最右邊。用上面的例子,執(zhí)行完后x變成:
-
~(~0<<n) 是為了生成右邊n位全1的掩碼。 對(duì)于上面的例子~(~0<<3) ,我們一起來(lái)分析下過(guò)程。
- 一開始執(zhí)行~0生成全1的二進(jìn)制,11111111。
- 然后左移3位變成11111000。
- 最后執(zhí)行圓括號(hào)左邊的~,取反變成00000111,現(xiàn)在掩碼生成完成。
- 最后執(zhí)行中間的那個(gè)&,將(x>>(p+1-n))和~(~0<<n)與運(yùn)算,取出期望字段。對(duì)于上面的例子,對(duì)應(yīng)過(guò)程圖如下:
setBits
該函數(shù)返回x中從第p位開始的n個(gè)(二進(jìn)制)位設(shè)置為y中最右邊n位的值,x的其余各位保持不變。
unsigned setBits(unsigned x, int p,int n , unsigned y) {return ( x & ~( ~( ~0 << n ) << ( p+1-n ) ) ) |(y & ~(~0 << n) ) << (p+1-n); } 復(fù)制代碼舉個(gè)例子(#2),調(diào)用setbits(168, 5, 4, 0b0101)后,將168對(duì)應(yīng)二進(jìn)制10101000從右邊數(shù)第5位開始的4個(gè)二進(jìn)制位設(shè)置為0101,設(shè)置完后變成10010100,最后將結(jié)果返回。可以結(jié)合下圖理解:
第一眼看到這個(gè)函數(shù)代碼還是有一些恐怖的,不用害怕,我們一層層解析,相信你一定能感受位運(yùn)算的精妙之處!
我們先要將函數(shù)拆成兩個(gè)部分看待,第一部分是( x & ~( ~( ~0 << n ) << ( p+1-n ) ) )記為$0;另一部分是(y & ~(~0 << n) ) << (p+1-n)記為$1。 下面分析下$0和$1的作用:
- 其中,$0是為了將x中期望修改的n個(gè)二進(jìn)制清0。在例子(#2)中,$0返回的二進(jìn)制應(yīng)該為:10000000,注意到紅體部分已經(jīng)被清0。
- $1是為了取出y最右邊n個(gè)二進(jìn)制,并與x中待修改的那n個(gè)二進(jìn)制對(duì)齊。在例子(#2)中,$1返回的二進(jìn)制應(yīng)該:00010100。
- 最后$0 | $1 ,也就是將$1的值設(shè)置到$0當(dāng)中。在在例子(#2)中,$0 | $1 = 10000000 | 00010100 = 10010100,設(shè)置完成。
下面具體分析下$0是如何將期望修改的n個(gè)二進(jìn)制清0的:
- 既然是清0,我們可以想使用到最早所學(xué)的掩碼,所以可以將$0以&為分割符拆成兩段看待,其中~( ~( ~0 << n ) << ( p+1-n ) )生成x清0所需要的掩碼。
- 一開始執(zhí)行 ~(~0 << n) 生成右邊n個(gè)1,其余全為0的。代入例子(#2)的參數(shù),也就是~(~0 << 4),結(jié)果為:00001111。這里為了方便記憶,把~(~0 << n)記為$$0 。
- 然后接著執(zhí)行$$0 << (p+1-n),將右邊n個(gè)1左移到相應(yīng)位置上。代入例子(#2)的參數(shù)及上一步的結(jié)果,應(yīng)執(zhí)行00001111 << (5+1-4),結(jié)果為00111100。這里將$$0 << (p+1-n)記為$$1。
- 最后執(zhí)行最外層~$$1,生成清零所需的掩碼。代入例子(#2)的參數(shù)及上一步的結(jié)果,應(yīng)執(zhí)行~00111100 ,結(jié)果為11000011,掩碼生成完畢。
- 最后執(zhí)行 x & ~$$1,用掩碼將x中待清零的位清0。代入例子(#2)的參數(shù)及上一步的結(jié)果,應(yīng)執(zhí)行10101000 & 11000011結(jié)果為10000000,清0成功。
下面具體分析下$1是如何取出y最右邊n個(gè)二進(jìn)制并與x中待修改的那n個(gè)二進(jìn)制對(duì)齊的:
- 首先 ~(~0 << n)和$0第一個(gè)步驟一樣,不過(guò)這次直接用這個(gè)結(jié)果當(dāng)作掩碼。代入例子(#2)的參數(shù),也就是~(~0 << 4),結(jié)果為00001111。這里將~(~0 << n)記為@@0。
- 接著 執(zhí)行 y & @@0 ,用掩碼的原理將y最右邊的n位取出。代入例子(#2)的參數(shù)及上一步的結(jié)果,應(yīng)執(zhí)行00000101 & 00001111,結(jié)果為00000101。這里將y & @@0記為$$1 。
- 最后執(zhí)行 $$1 << (p+1-n),左移到對(duì)應(yīng)位置上。代入例子(#2)的參數(shù)及上一步的結(jié)果,也就是00000101 << (5+1-4),結(jié)果為00010100,生成結(jié)束。
Objective-C的Runtime中的位運(yùn)算應(yīng)用
這里會(huì)介紹一些runtime源碼中使用位運(yùn)算的例子。
判斷是否是TaggedPointer
在runtime源碼中,判斷是否是TaggedPointer的函數(shù)定義如下:
static inline bool _objc_isTaggedPointer(const void * _Nullable ptr) {return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK; } 復(fù)制代碼其中參數(shù)const void * _Nullable ptr為對(duì)象的地址。_OBJC_TAG_MASK是一個(gè)掩碼,其宏定義比較長(zhǎng),我將它簡(jiǎn)單的整理了一下:
// 64-bit Mac - tag bit is LSB // Everything else - tag bit is MSB 復(fù)制代碼我們得到了結(jié)論:
- 64-bit Mac下, _OBJC_TAG_MASK為1UL,對(duì)應(yīng)二進(jìn)制為:00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
- 其他平臺(tái)下, _OBJC_TAG_MASK 為(1UL<<63),對(duì)應(yīng)二進(jìn)制為:10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
根據(jù)以上結(jié)論,結(jié)合_objc_isTaggedPointer函數(shù)的代碼,很容易理解它的原理:
- 在64-bit MAC下,取出對(duì)象地址二進(jìn)制的最低位(LSB),若最低位為1則為TaggedPointer。
- 在其他平臺(tái)下,取出對(duì)象地址二進(jìn)制的最高位(MSB),若為1則為TaggedPointer。
isa_t
在ARM64架構(gòu)之前,對(duì)象的isa指針直接指向類對(duì)象地址;在ARM64架構(gòu)之后,一個(gè)8字節(jié)的isa變量額外存儲(chǔ)了許多與當(dāng)前對(duì)象相關(guān)的信息。 我們先看來(lái)看一下最新的isa結(jié)構(gòu)定義:
union isa_t {isa_t() { }isa_t(uintptr_t value) : bits(value) { }Class cls;uintptr_t bits; struct {ISA_BITFIELD; // defined in isa.h}; };復(fù)制代碼相關(guān)的宏定義:
復(fù)制代碼上面代碼中用了c語(yǔ)言的聯(lián)合體以及位段的技術(shù),當(dāng)然這不是我們的重點(diǎn),有興趣的話可以去了解下。 我在Mac上編寫了一段代碼,用來(lái)展示這8個(gè)字節(jié)里所存儲(chǔ)的數(shù)據(jù)有多么豐富。要知道,8個(gè)字節(jié)僅僅是一個(gè)long變量的大小。
union isa_t {Class cls;uintptr_t bits;struct {uintptr_t nonpointer : 1;uintptr_t has_assoc : 1;uintptr_t has_cxx_dtor : 1;uintptr_t shiftcls : 44;uintptr_t magic : 6;uintptr_t weakly_referenced : 1;uintptr_t deallocating : 1;uintptr_t has_sidetable_rc : 1;uintptr_t extra_rc : 8;}; };int main(int argc, const char * argv[]) {@autoreleasepool {NSObject *obj = [[NSObject alloc]init]; // 這塊內(nèi)存記為#1,obj指向#1,#1的引用計(jì)數(shù)器+1NSObject *obj2 = obj; // obj2也指向#1,#1的引用計(jì)數(shù)器+1NSObject *obj3 = obj; // obj3也指向#1,#1的引用計(jì)數(shù)器+1__weak NSObject *weak_obj = obj;// 弱引用union isa_t _isa_t;void *_obj = (__bridge void *)(obj);_isa_t.bits = *((uintptr_t*)_obj);printf("是否使用isa指針優(yōu)化:%x\n",_isa_t.nonpointer);printf("是否有用關(guān)聯(lián)對(duì)象:%x\n",_isa_t.has_assoc);printf("是否有C++析構(gòu)函數(shù):%x\n",_isa_t.has_cxx_dtor);printf("isa取出類對(duì)象:%llx\n",_isa_t.bits & ISA_MASK);printf("class方法取出類對(duì)象:%lx\n",(long)[NSObject class]);printf("調(diào)試時(shí)是否完成初始化:%x\n",_isa_t.magic);printf("是否有被弱引用指向過(guò):%x\n",_isa_t.weakly_referenced);printf("是否正在釋放:%x\n",_isa_t.deallocating);printf("是否使用了sidetable:%x\n",_isa_t.has_sidetable_rc);printf("引用計(jì)數(shù)器-1:%x\n",_isa_t.extra_rc);}return 0; } 復(fù)制代碼輸出的結(jié)果:
- 是否使用isa指針優(yōu)化:1
- 是否有用關(guān)聯(lián)對(duì)象:0
- 是否有C++析構(gòu)函數(shù):0
- isa取出類對(duì)象:7fffb506f140
- class方法取出類對(duì)象:7fffb506f140
- 調(diào)試時(shí)是否完成初始化:3b
- 是否有被弱引用指向過(guò):1
- 是否正在釋放:0
- 是否使用了sidetable:0
- 引用計(jì)數(shù)器-1:2
方法緩存中的Hash函數(shù)
先給出Runtime源碼里從緩存中查找方法的函數(shù):
bucket_t * cache_t::find(cache_key_t k, id receiver) {assert(k != 0);bucket_t *b = buckets();mask_t m = mask();mask_t begin = cache_hash(k, m);mask_t i = begin;do {if (b[i].key() == 0 || b[i].key() == k) {return &b[i];}} while ((i = cache_next(i, m)) != begin);// hackClass cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));cache_t::bad_cache(receiver, (SEL)k, cls); } 復(fù)制代碼再來(lái)看下cache_hash的實(shí)現(xiàn):
// Class points to cache. SEL is key. Cache buckets store SEL+IMP. // Caches are never built in the dyld shared cache. static inline mask_t cache_hash(cache_key_t key, mask_t mask) {return (mask_t)(key & mask); } 復(fù)制代碼這里需要說(shuō)明cache_hash函數(shù)中幾個(gè)參數(shù)的意義:
- key: 方法SEL的地址(8字節(jié)64位)
- mask: 哈希表長(zhǎng)度 -1
(key & mask)的結(jié)果能保證在[0,mask]整數(shù)范圍內(nèi),所以可以正確的映射到Hash表上。
NS_OPTIONS
NS_OPTIONS如其名「選項(xiàng)」,可以讓你在一個(gè)8字節(jié)NSUInteger變量中最多保存64個(gè)選項(xiàng)開關(guān)。 先來(lái)看看KVO中NSKeyValueObservingOptions的定義:
typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) {NSKeyValueObservingOptionNew = 0x01,NSKeyValueObservingOptionOld = 0x02,NSKeyValueObservingOptionInitial = 0x04,NSKeyValueObservingOptionPrior = 0x08 }; 復(fù)制代碼一共有4個(gè)選項(xiàng),其對(duì)應(yīng)的二進(jìn)制分別為:
- 0000000000 0000000000 0000000000 0000000000 0000000000 0000000000 0000000000 0000000001
- 0000000000 0000000000 0000000000 0000000000 0000000000 0000000000 0000000000 0000000010
- 0000000000 0000000000 0000000000 0000000000 0000000000 0000000000 0000000000 0000000100
- 0000000000 0000000000 0000000000 0000000000 0000000000 0000000000 0000000000 0000001000
可以看得出,每個(gè)選項(xiàng)都是獨(dú)立一個(gè)1,并且和其他選項(xiàng)的位置不一樣。如果對(duì)某幾個(gè)選項(xiàng)進(jìn)行或運(yùn)算(|)就會(huì)合并它們的選項(xiàng)。 舉個(gè)平時(shí)常用的例子:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld 的結(jié)果為:
- 0000000000 0000000000 0000000000 0000000000 0000000000 0000000000 0000000000 000000011
下面給出讀取這些選項(xiàng)的代碼:
- (void)readOptions:(NSKeyValueObservingOptions)options {NSLog(@"---------------begin---------------");if (options & NSKeyValueObservingOptionNew ) {NSLog(@"contain NSKeyValueObservingOptionNew");}if (options & NSKeyValueObservingOptionOld ) {NSLog(@"contain NSKeyValueObservingOptionOld");}if (options & NSKeyValueObservingOptionInitial ) {NSLog(@"contain NSKeyValueObservingOptionInitial");}if (options & NSKeyValueObservingOptionPrior ) {NSLog(@"contain NSKeyValueObservingOptionPrior");}NSLog(@"---------------end-----------------"); }// 輸出 /*---------------begin---------------contain NSKeyValueObservingOptionNewcontain NSKeyValueObservingOptionOld---------------end--------------------------------begin---------------contain NSKeyValueObservingOptionNewcontain NSKeyValueObservingOptionInitialcontain NSKeyValueObservingOptionPrior---------------end----------------- */ 復(fù)制代碼調(diào)用:
[self readOptions:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew];[self readOptions:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial |NSKeyValueObservingOptionPrior]; 復(fù)制代碼輸出:
/*---------------begin---------------contain NSKeyValueObservingOptionNewcontain NSKeyValueObservingOptionOld---------------end--------------------------------begin---------------contain NSKeyValueObservingOptionNewcontain NSKeyValueObservingOptionInitialcontain NSKeyValueObservingOptionPrior---------------end----------------- */ 復(fù)制代碼參考資料
- 《C語(yǔ)言程序設(shè)計(jì)(K & R)》
本篇已同步到個(gè)人博客:位運(yùn)算世界暢游指南
轉(zhuǎn)載于:https://juejin.im/post/5ce1324af265da1b8f1a9162
總結(jié)
- 上一篇: kubernetes一次生产故障日记
- 下一篇: PDF页眉页脚怎么设置