位運算世界暢遊指南

目錄

概念介紹

咱們都知道,計算機中的全部數據最終都是以二進制(bit)的形式存儲在計算機的。而在咱們平時開發中所接觸數據的大可能是字節爲單位的,有了位運算以後咱們就能夠操做字節中的比特位了。在iOS的runtime源碼以及NS_OPTIONS 中都運用了位運算的知識,可見其重要性了。另外值得一提的是,大部分語言的位運算符都相同,因此這是一篇老小皆宜的文章。html

閱讀本篇文章前,你須要知道的一些東西:c++

  1. 咱們在討論二進制的時候,位序通常都是從右(低位)到左(高位)的。舉個例子,對於二進制0011來講,第0位是1,第1位是1,第二位是0,第三位是0。
  2. 在大部分編程語言中,0x開頭表明十六進制,好比0xff表明十六進制ff;0b開頭表明二進制,好比0b11111111表明二進制的11111111;0開頭表明8進制,好比0377表明8進制377;若是你不寫前綴,那默認就是10進制。不管你是幾進制,你所描述的本質實際上是同樣的,只是表現形式不一樣而已。好比前面的0xff、0b111111十一、037七、255,它們都是等價的。你能夠在編程語言中語言測試下。
bool c  = 0xff == 0b11111111; // true
  bool c0 = 0b11111111 == 0377; // true
  bool c1 = 0377 == 255; //true
複製代碼
  1. 之因此程序員都喜歡用16進制,首先是由於16進制和二進制的轉換實在太方便了。好比0xFAFA,直把每一個字母轉成4位二進制拼接在一塊兒便可,0xF:0b1111 ,0xA:0b1010,因此0xFAFA二進制是0b1111101011111010。另一點,由於16進制中2個字母表明8位二進制(一個字節),因此當咱們看到16進制的時候就能立馬知道多少個字節了。好比前面的0xFAFA,第一個字節是0xFA,第二個字節也是0xFA,共兩個字節。

基本位運算符

位運算符就像是控制比特位的扳手,在學習位運算前先介紹下每一個運算符的意義及其用法。
程序員

按位與 ( AND )

運算規則:只有在兩個值都爲1時結果才爲1,兩個值中有一個爲0結果便爲0。在編程語言裏通常用 & 表示與運算符。編程


舉個例子,10100001 & 10001001 = 10000001。(注:操做數都爲二進制。)
緩存

按位或 ( OR )

運算規則: 兩個值中有一個爲1結果便爲1,兩個值都爲0時結果才爲0。在編程語言裏通常用 | 表示或運算符。網絡


舉個例子,10100001 | 10001001 = 10101001。架構


按位異或 ( XOR )

運算規則: 只有當兩個值不相同時結果才爲1,兩個值相同時結果爲0。在編程語言裏通常用 ^ 表示異或運算符。編程語言


舉個例子,10100001 ^ 10001001 = 00101000。
ide

取反 ( NOT )

在數值的二進制表示方式上,將0變爲1,將1變爲0。在編程語言裏通常用 ~ 表示取反運算符。
來看一個例子可能會更加直觀:函數

右移 ( >> )

右移將操做數的二進制位總體向右移動N位,空出部分用0填充,在編程語言裏通常用 >>表示右移運算符。

舉個例子,下圖對二進制 10111101 右移3位後,最右邊的101被移除,左邊空出來3位用0填充(本文章默認全部數據都爲無符號類型,因此本操做爲邏輯右移)。


左移 ( << )

左移將操做數的二進制位總體向左移動N位,空出部分用0填充,在編程語言裏通常用 << 表示左移運算符。

舉個例子,下圖對二進制 10111101 左移4位後,最左邊的1011被移除,右邊空出來4位用0填充。

基礎技巧

這裏先介紹一些比較簡單實用的位運算技巧,這些技巧在平常開發中也是比較經常使用的。

將某些二進制位設置爲1

假設x=0b10011010,如今我想將第五、6位置爲1,將其變爲0b11111010,那麼執行 (x = x | 0b01100000) 就是咱們想要的結果;那如果想將第0、五、6爲置爲1,變成0b11111011呢?那麼執行(x = x | 0b01100001)就是咱們想要的結果。 根據上面的兩個例子,咱們能夠獲得一個結論:

  • x = x | SET_ON ,該語句將x中對應於SET_ON中爲1的二進制位設置爲1;x中對應於SET_ON中爲0的二進制位保持不變。

掩碼

掩碼這個詞常常能在計算機網絡裏聽到,比較熟悉的話就是子網掩碼了。掩碼是起的很是好的一個名字,當咱們的操做數和掩碼進行與運算(&)後,掩碼中二進制爲0的位會屏蔽掉原操做數對應的二進制位。 舉個例子,假如如今我有一個2個字節的數據0xBA15,若要屏蔽掉中間0xA1這8位二進制變成0xB005,該如何設計掩碼呢?答案很簡單,只要將掩碼中間8位設爲0其餘設爲1便可,因此本例中的掩碼應爲0xF00F,0xBA15 & 0xF00F=0xB005。能夠結合下圖理解:


取出第i位二進制值

這個函數傳入一個data,返回其二進制從右邊開始數第i位的值。

unsigned char getBit( unsigned long data , int i ) {

    // i = 0時,表明取最右邊的哪一位。
    data  = data >> i ;
    return data & 1 ;
}
複製代碼

原理很簡單,先將data右移i位,這樣能保證第i位的值處於data的最右邊,而後再用data & 1取出便可。 舉個例子,若是我調用了{getBit(168,3)},168對應的二進制爲10101000,右移3位後變成00010101,最後00010101 & 00000001 = 1,取出成功。

計算無符號變量的取值範圍

筆者在mac上的unsigned long 是8個字節,能夠存儲64位二進制,因爲沒有符號位,故只需將這64位二進制都填充爲1就獲得unsigned long變量的最大值了。

// 將全0取反變爲全1裝進變量x中。
   unsigned long x = ~0;
   // 輸出二進制爲全1的變量x
   printf("unsigned long max = %lu\n",x);複製代碼

奇偶判斷

若是最後一位二進制爲0,那麼這個數字就是偶數,若是爲1就是奇數。這裏給出實現函數:

int isOdd(int value) {
    return (value & 1); // 取出最後一位二進制,若爲1則是奇數
}
複製代碼

說下大體原理:若最後一位二進制爲0,那麼二進制轉成十進制後必然能夠寫成2n的形式,必爲偶數。好比我隨便寫一個最後一位爲0的二進制數字 10001010,那麼其十進制數爲2+2^3+2^7 = 2*(1+2^2+2^6),故爲偶數,你們能夠多寫幾組數字驗證。

關於負數:雖然負數在計算機中以補碼的方式存儲,但因爲補碼最後一位和原碼最後一位相同,因此上面的函數一樣適用於負數。爲何呢?舉個例子:

  • 假如最後一位是0,取反後變成1,而後再+1又變成0。
  • 假如最後一位是1,取反後變成0,而後再+1又變成1。

看到了吧,最後又變回去了。

統計二進制中1個數

int x =0xba;//10111010
int count = 0;
while (x!=0) {
    x = x&(x-1);
    count++;
}
printf("%d\n",count);
複製代碼

循環中每次執行x = x&(x-1)後,x的二進制的最後一個1就會被消去。當全部1都被消去後,count計數完畢,x=0,退出循環。

那麼爲何x = x&(x-1)可以消去其二進制的最後一個1呢?舉個例子:

  • 假如x=101000,那麼 x-1=100111。
  • 假如x=101011,那麼 x-1=101010。

能夠發現規律:

  • 當x=nn..nn100..00這種形式時,x-1=nn..nn011..11。 這個時候x & (x-1) = nn..nn100..00 & nn..nn011..11 = nn..nn000.00,x最後一個1被消去。
  • 當x=nn..nn1這種形式時,x-1=nn..nn0。 這個時候x & (x-1) = nn..nn1 & nn..nn0 = nn..nn0,x最後一個1被消去。

交換兩個變量的值(無臨時變量)

// 注:參數是c++的引用類型。
void swap(int &a,int &b) {
    a=a^b;
    b=a^b;
    a=a^b;
}
複製代碼

想要了解原理,須要先知道幾個異或運算的性質:

  • 交換律:a^b = b^a
  • 結合律:(a^b)^c = a^(b^c)
  • 恆等律:a^0 = a
  • 規零律:a^a = 0
  • 自反性:a^b^b = a

假設一開始,a=k,b=t

  • 執行a=a^ba=k^t;
  • 執行b=a^bb = k^t^t=k,注意這裏用到了自反性;
  • 執行a=a^ba=k^t^k=t^k^k=t,注意這裏用到了交換律和自反性;
  • 最後獲得a=t,b=k,交換完成。

固然了,不只限於交換整型變量。舉一個不太經常使用的例子,咱們能夠不用臨時變量交換兩個c語言字符串。下面代碼中的a和b本質上是在交換"a-a-a-a-a-a"和"b-b-b-b-b-b"地址,因此效果也是同樣的。

char *a = "a-a-a-a-a-a";// 存儲在數據區的字符串常量
 char *b = "b-b-b-b-b-b";//存儲在數據區的字符串常量
 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-b after exchange: a=b-b-b-b-b-b,b=a-a-a-a-a-a */
複製代碼

子集生成

假設如今有一集合A={a,b,c},要求生成這個集合的全部子集。構造子集時,咱們可使用二進制中第i位的值決定是否要選取集合A中的第i個元素。其中值爲1表明選取,值爲0表明不選取。舉個例子,100表明只選第一個元素a,其構成的子集爲{a};101表明選取第一個a以及第三個c,其構成的子集爲{a,c}。

下面列舉出A的全部子集:

編號 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

細心的話,應該能發現上面的表格中的編號和二進制剛恰好能對的上。因此對於有個n個元素的集合,只要生成0到2^n-1個整數編號,而後根據每一個編號對應的二進制解析出相應的子集便可。 下面是c語言實現的代碼:

#include <stdio.h>
#include <string.h>
void prinstSubSet(char *S,int id) {
    int n = (int)strlen(S);// 集合的元素個數
    char result[100];
    int index=0;
    printf("{");
    for(int i=n-1;i>=0;i--){
        if ((id>>i)&1) {
            // 若第i位值爲1,表明選擇第i個元素(從右邊開始數)
            result[index++]=S[n-1-i];//因爲字符串第0個字符是最左邊,因此要顛倒下。
        }
    }
    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);// 集合的元素個數
    int begin = 0;
    int end = (1<<n)-1; // 2^n-1
    
    //生成0到2^n-1個編號(id)
    for (int id = begin;id<=end;id++) {
        prinstSubSet(S, id);// 根據編號對應的二進制輸出子集
    }
}
int main(int argc, const char * argv[]) {
    create("abc");// 生成{a,b,c}的子集
    return 0;
}
複製代碼

進階技巧

這裏介紹C語言程序設計這本書中的兩個很是實用的函數,相信你在平時的項目中也能應用的到。

getBits

該函數用來返回x中從右邊數第p位開始向右數n位二進制。

unsigned getBits(unsigned x,int p,int n) {
    return (x>>(p+1-n)) & ~(~0<<n);
}
複製代碼

舉個例子,調用getBits(168,5,3)後,返回168對應二進制10101000從右邊數第5位開始向右3位二進制,也就是101。能夠結合下圖理解:

下面來講如下原理:
  • 一開始執行(x>>(p+1-n)) 這裏是爲了將指望得到的字段移動到最右邊。用上面的例子,執行完後x變成:


  • ~(~0<<n) 是爲了生成右邊n位全1的掩碼。 對於上面的例子~(~0<<3) ,咱們一塊兒來分析下過程。

    1. 一開始執行~0生成全1的二進制,11111111。
    2. 而後左移3位變成11111000。
    3. 最後執行圓括號左邊的~,取反變成00000111,如今掩碼生成完成。

  • 最後執行中間的那個&,將(x>>(p+1-n))~(~0<<n)與運算,取出指望字段。對於上面的例子,對應過程圖以下:

setBits

該函數返回x中從第p位開始的n個(二進制)位設置爲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);
}
複製代碼

舉個例子(#2),調用setbits(168, 5, 4, 0b0101)後,將168對應二進制10101000從右邊數第5位開始的4個二進制位設置爲0101,設置完後變成10010100,最後將結果返回。能夠結合下圖理解:

第一眼看到這個函數代碼仍是有一些恐怖的,不用懼怕,咱們一層層解析,相信你必定能感覺位運算的精妙之處!

咱們先要將函數拆成兩個部分看待,第一部分是( x & ~( ~( ~0 << n ) << ( p+1-n ) ) )記爲$0;另外一部分是(y & ~(~0 << n) ) << (p+1-n)記爲$1。 下面分析下$0和$1的做用:

  • 其中,$0是爲了將x中指望修改的n個二進制清0。在例子(#2)中,$0返回的二進制應該爲:10000000,注意到紅體部分已經被清0。
  • $1是爲了取出y最右邊n個二進制,並與x中待修改的那n個二進制對齊。在例子(#2)中,$1返回的二進制應該:00010100。
  • 最後$0 | $1 ,也就是將$1的值設置到$0當中。在在例子(#2)中,$0 | $1 = 10000000 | 00010100 = 10010100,設置完成。

下面具體分析下$0是如何將指望修改的n個二進制清0的:

  • 既然是清0,咱們能夠想使用到最先所學的掩碼,因此能夠將$0以&爲分割符拆成兩段看待,其中~( ~( ~0 << n ) << ( p+1-n ) )生成x清0所須要的掩碼。
  • 一開始執行 ~(~0 << n) 生成右邊n個1,其他全爲0的。代入例子(#2)的參數,也就是~(~0 << 4),結果爲:00001111。這裏爲了方便記憶,把~(~0 << n)記爲$$0 。
  • 而後接着執行$$0 << (p+1-n),將右邊n個1左移到相應位置上。代入例子(#2)的參數及上一步的結果,應執行00001111 << (5+1-4),結果爲00111100。這裏將$$0 << (p+1-n)記爲$$1。
  • 最後執行最外層~$$1,生成清零所需的掩碼。代入例子(#2)的參數及上一步的結果,應執行~00111100 ,結果爲11000011,掩碼生成完畢。
  • 最後執行 x & ~$$1,用掩碼將x中待清零的位清0。代入例子(#2)的參數及上一步的結果,應執行10101000 & 11000011結果爲10000000,清0成功。

下面具體分析下$1是如何取出y最右邊n個二進制並與x中待修改的那n個二進制對齊的:

  • 首先 ~(~0 << n)和$0第一個步驟同樣,不過此次直接用這個結果看成掩碼。代入例子(#2)的參數,也就是~(~0 << 4),結果爲00001111。這裏將~(~0 << n)記爲@@0
  • 接着 執行 y & @@0 ,用掩碼的原理將y最右邊的n位取出。代入例子(#2)的參數及上一步的結果,應執行00000101 & 00001111,結果爲00000101。這裏將y & @@0記爲$$1 。
  • 最後執行 $$1 << (p+1-n),左移到對應位置上。代入例子(#2)的參數及上一步的結果,也就是00000101 << (5+1-4),結果爲00010100,生成結束。

Objective-C的Runtime中的位運算應用


這裏會介紹一些runtime源碼中使用位運算的例子。

判斷是不是TaggedPointer

在runtime源碼中,判斷是不是TaggedPointer的函數定義以下:

static inline bool 
_objc_isTaggedPointer(const void * _Nullable ptr)
{
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
複製代碼

其中參數const void * _Nullable ptr爲對象的地址。_OBJC_TAG_MASK是一個掩碼,其宏定義比較長,我將它簡單的整理了一下:

#if (TARGET_OS_OSX || TARGET_OS_IOSMAC) && __x86_64__
    // 64-bit Mac - tag bit is LSB
# define _OBJC_TAG_MASK 1UL
#else
    // Everything else - tag bit is MSB
# define _OBJC_TAG_MASK (1UL<<63)
#endif
複製代碼

咱們獲得告終論:

  • 64-bit Mac下, _OBJC_TAG_MASK爲1UL,對應二進制爲:00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
  • 其餘平臺下, _OBJC_TAG_MASK 爲(1UL<<63),對應二進制爲:10000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

根據以上結論,結合_objc_isTaggedPointer函數的代碼,很容易理解它的原理:

  • 在64-bit MAC下,取出對象地址二進制的最低位(LSB),若最低位爲1則爲TaggedPointer。
  • 在其餘平臺下,取出對象地址二進制的最高位(MSB),若爲1則爲TaggedPointer。

isa_t

在ARM64架構以前,對象的isa指針直接指向類對象地址;在ARM64架構以後,一個8字節的isa變量額外存儲了許多與當前對象相關的信息。 咱們先看來看一下最新的isa結構定義:

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

複製代碼

相關的宏定義:

# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \ uintptr_t nonpointer : 1; \ uintptr_t has_assoc : 1; \ uintptr_t has_cxx_dtor : 1; \ uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \ uintptr_t magic : 6; \ uintptr_t weakly_referenced : 1; \ uintptr_t deallocating : 1; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)

# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_BITFIELD \ uintptr_t nonpointer : 1; \ uintptr_t has_assoc : 1; \ uintptr_t has_cxx_dtor : 1; \ uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \ uintptr_t magic : 6; \ uintptr_t weakly_referenced : 1; \ uintptr_t deallocating : 1; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)

# else
# error unknown architecture for packed isa
# endif
複製代碼

上面代碼中用了c語言的聯合體以及位段的技術,固然這不是咱們的重點,有興趣的話能夠去了解下。 我在Mac上編寫了一段代碼,用來展現這8個字節裏所存儲的數據有多麼豐富。要知道,8個字節僅僅是一個long變量的大小。

#import <Foundation/Foundation.h>
# define ISA_MASK 0x00007ffffffffff8ULL
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]; // 這塊內存記爲#1,obj指向#1,#1的引用計數器+1
        NSObject *obj2 = obj; // obj2也指向#1,#1的引用計數器+1
        NSObject *obj3 = obj; // obj3也指向#1,#1的引用計數器+1
        __weak NSObject *weak_obj = obj;// 弱引用
        union isa_t _isa_t;
        void *_obj = (__bridge void *)(obj);
        _isa_t.bits = *((uintptr_t*)_obj);
        printf("是否使用isa指針優化:%x\n",_isa_t.nonpointer);
        printf("是否有用關聯對象:%x\n",_isa_t.has_assoc);
        printf("是否有C++析構函數:%x\n",_isa_t.has_cxx_dtor);
        printf("isa取出類對象:%llx\n",_isa_t.bits & ISA_MASK);
        printf("class方法取出類對象:%lx\n",(long)[NSObject class]);
        printf("調試時是否完成初始化:%x\n",_isa_t.magic);
        printf("是否有被弱引用指向過:%x\n",_isa_t.weakly_referenced);
        printf("是否正在釋放:%x\n",_isa_t.deallocating);
        printf("是否使用了sidetable:%x\n",_isa_t.has_sidetable_rc);
        printf("引用計數器-1:%x\n",_isa_t.extra_rc);
    }
    return 0;
}
複製代碼

輸出的結果:

  • 是否使用isa指針優化:1
  • 是否有用關聯對象:0
  • 是否有C++析構函數:0
  • isa取出類對象:7fffb506f140
  • class方法取出類對象:7fffb506f140
  • 調試時是否完成初始化:3b
  • 是否有被弱引用指向過:1
  • 是否正在釋放:0
  • 是否使用了sidetable:0
  • 引用計數器-1:2

方法緩存中的Hash函數

先給出Runtime源碼裏從緩存中查找方法的函數:

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);

    // hack
    Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
    cache_t::bad_cache(receiver, (SEL)k, cls);
}
複製代碼

再來看下cache_hash的實現:

// 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);
}
複製代碼

這裏須要說明cache_hash函數中幾個參數的意義:

  • key: 方法SEL的地址(8字節64位)
  • mask: 哈希表長度 -1

(key & mask)的結果能保證在[0,mask]整數範圍內,因此能夠正確的映射到Hash表上。

NS_OPTIONS

NS_OPTIONS如其名「選項」,可讓你在一個8字節NSUInteger變量中最多保存64個選項開關。 先來看看KVO中NSKeyValueObservingOptions的定義:

typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) {

    NSKeyValueObservingOptionNew = 0x01,
    NSKeyValueObservingOptionOld = 0x02,
    NSKeyValueObservingOptionInitial = 0x04,
    NSKeyValueObservingOptionPrior = 0x08
};
複製代碼

一共有4個選項,其對應的二進制分別爲:

  • 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

能夠看得出,每一個選項都是獨立一個1,而且和其餘選項的位置不同。若是對某幾個選項進行或運算(|)就會合並它們的選項。 舉個平時經常使用的例子:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld 的結果爲:

  • 0000000000 0000000000 0000000000 0000000000 0000000000 0000000000 0000000000 000000011

下面給出讀取這些選項的代碼:

- (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 NSKeyValueObservingOptionNew contain NSKeyValueObservingOptionOld ---------------end----------------- ---------------begin--------------- contain NSKeyValueObservingOptionNew contain NSKeyValueObservingOptionInitial contain NSKeyValueObservingOptionPrior ---------------end----------------- */
複製代碼

調用:

[self readOptions:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew];
 [self readOptions:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial |NSKeyValueObservingOptionPrior];
複製代碼

輸出:

/* ---------------begin--------------- contain NSKeyValueObservingOptionNew contain NSKeyValueObservingOptionOld ---------------end----------------- ---------------begin--------------- contain NSKeyValueObservingOptionNew contain NSKeyValueObservingOptionInitial contain NSKeyValueObservingOptionPrior ---------------end----------------- */
複製代碼

參考資料


本篇已同步到我的博客:位運算世界暢遊指南

相關文章
相關標籤/搜索