一曼玩、先從常用的交換兩個變量的值說起渣刷。
一般情況下,交換變量值都是如下的方法:
int sum = a;
a = b;
b = sum;
有時會用這樣的方法:
a = a + b;
b = a - b;
a = a - b;
通過位運(yùn)算算法,則可以不用添加任何其他變量節(jié)省內(nèi)存開銷坡脐,并且不用進(jìn)行加減運(yùn)算:
a ^= b;
b = a ^ b;
a = a ^ b;
上面的代碼和第二種方式的實(shí)現(xiàn)思路類似奥务,都是將a和b合并成單個變量物独,再分別消除變量中的a和b的值(^運(yùn)算會對相同二進(jìn)制位的值置0,意味著b^b的結(jié)果等于0)汗洒。
在普遍使用高級語言開發(fā)的大環(huán)境下议纯,位運(yùn)算的實(shí)現(xiàn)更多的被封裝起來,因此大多數(shù)開發(fā)者在項目開發(fā)中不見得會使用這一機(jī)制溢谤。位運(yùn)算的時鐘周期為一瞻凤,同加減運(yùn)算一樣憨攒。位運(yùn)算是直接操作計算機(jī)中的二進(jìn)制數(shù)據(jù)位的數(shù)據(jù)。
在iOS中基本所有的位運(yùn)算都通過枚舉聲明傳值的方式將位運(yùn)算的實(shí)現(xiàn)細(xì)節(jié)隱藏了起來:
typedef NS_OPTIONS(NSUInteger, UIRectEdge) {
UIRectEdgeNone? = 0,
UIRectEdgeTop? ? = 1 << 0,
UIRectEdgeLeft? = 1 << 1,
UIRectEdgeBottom = 1 << 2,
UIRectEdgeRight? = 1 << 3,
UIRectEdgeAll? ? = UIRectEdgeTop | UIRectEdgeLeft | UIRectEdgeBottom | UIRectEdgeRight
} NS_ENUM_AVAILABLE_IOS(7_0);
位運(yùn)算是一種極為高效乃至可以說最為高效的計算方式阀参,雖然現(xiàn)代程序開發(fā)中編譯器已經(jīng)為我們做了大量的優(yōu)化肝集,但是合理的使用位運(yùn)算可以提高代碼的可讀性以及執(zhí)行效率。
二蛛壳、基礎(chǔ)計算
在了解怎么使用位運(yùn)算之前杏瞻,筆者簡單說一下CPU處理計算的過程。如果你對CPU的計算方式有所了解衙荐,可以跳過這一節(jié)捞挥。
當(dāng)代碼int sum = 11 + 79被執(zhí)行的時候,計算機(jī)直接將兩個數(shù)的二進(jìn)制位進(jìn)行相加和進(jìn)位操作:
11:? 0 0 0 0 1 0 1 1
79:? 0 1 0 0 1 1 1 1
————————————————————
90:? 0 1 0 1 1 0 1 0
通常來說CPU執(zhí)行兩個數(shù)相加操作所花費(fèi)的時間被我們稱作一個時鐘周期忧吟,而2.0GHz頻率的CPU表示可以在一秒執(zhí)行運(yùn)算2.0*1024*1024*1024個時鐘周期砌函。相較于加法運(yùn)算,下面看一下11*2溜族、11*4的二進(jìn)制結(jié)果:
11:? 0 0 0 0 1 0 1 1? *? 2
————————————————————
22:? 0 0 0 1 0 1 1 0
11:? 0 0 0 0 1 0 1 1? *? 4
————————————————————
44:? 0 0 1 0 1 1 0 0
簡單來說讹俊,不難發(fā)現(xiàn)當(dāng)某個數(shù)乘以2的N次冪的時候,結(jié)果等同于將這個數(shù)的二進(jìn)制位置向左移動N位煌抒,在代碼中我們使用num << N表示將num的二進(jìn)制數(shù)據(jù)左移N個位置仍劈,其效果等同于下面這段代碼:
for (int idx = 0; idx < N; idx++) {
num *= 2;
}
假如相乘的兩個數(shù)都不是2的N次冪,這時候編譯器會將其中某個值分解成多個2的N次冪相加的結(jié)果進(jìn)行運(yùn)算寡壮。比如37 * 69贩疙,這時候CPU會將37分解成32+4+1,然后換算成(69<<5) + (69<<2) + (69<<0)的方式計算出結(jié)果诬像。因此屋群,計算兩個數(shù)相乘通常需要十個左右的時鐘周期。 同理坏挠,代碼num >> N的作用等效于:
for (int idx = 0; idx < N; idx++) {
num /= 2;
}
但是兩個數(shù)相除花費(fèi)的時鐘周期要比乘法還要多得多芍躏,其大部分消耗在將數(shù)值分解成多個2的N次冪上。除此之外降狠,浮點(diǎn)數(shù)涉及到的計算更為復(fù)雜对竣,這里也簡單聊聊浮點(diǎn)數(shù)的準(zhǔn)確度問題。拿float類型來說榜配,總共使用了32bit的存儲空間否纬,其中第一位表示正負(fù),2~13位表示整數(shù)部分的值蛋褥,14~32位之中分別存儲了小數(shù)位以及科學(xué)計數(shù)的標(biāo)識值(這里可能并不那么準(zhǔn)確临燃,主要是為了給讀者一個大概的介紹)。由于小數(shù)位的二進(jìn)制數(shù)據(jù)依舊保持2的N次冪特性,假如下面的二進(jìn)制屬于小數(shù)位:
1 0 1 1 1 0 0 1
那么這部分小數(shù)位的值等于:1/2 + 1/4 + 1/8 + 1/16 + 1/128 = 0.9453125膜廊。因此乏沸,當(dāng)你把一個沒有任何規(guī)律的小數(shù)例如3.1415926535898存入計算機(jī)的時候,小數(shù)點(diǎn)后面會被拆解成很多的2的N次冪進(jìn)行保存爪瓜。由于小數(shù)位總是有限的蹬跃,因此當(dāng)分解的N超出這些位數(shù)時導(dǎo)致存儲不下,就會出現(xiàn)精度偏差铆铆。另一方面蝶缀,這樣的分解計算勢必要消耗大量的時鐘周期,這也是大量的浮點(diǎn)數(shù)運(yùn)算(cell動態(tài)計算)容易引發(fā)卡頓的原因薄货。所以翁都,當(dāng)小數(shù)位過多時,改用字符串存儲是一個更優(yōu)的選擇菲驴。
三荐吵、位運(yùn)算符
使用的運(yùn)算符包括下面:
含義運(yùn)算符
左移?
右移?
按位或︳
按位并&
按位取反~
按位異或^
& 操作
0 0 1 0 1 1 1 0 ? ? ? ? ? ? ? 46 ??
1 0 0 1 1 1 0 1 1 ? ? ? ? ? ?57
?———————————————
?0 0 0 0 1 1 0 0 ? ? ? ? ? ? ?12
| ?操作
0 0 1 0 1 1 1 0? ? ? ? ? ? ? 46
1 0 0 1 1 1 0 1 ? ? ? ? ? ? 157
?———————————————?
1 0 1 1 1 1 1 1 ? ? ? ? ? ? ?191
~ 操作
0 0 1 0 1 1 1 0 ? ? ? ? ? ? ? ? 46?
———————————————?
1 1 0 1 0 0 0 1 ? ? ? ? ? ? ? ?225
^ 操作
0 0 1 0 1 1 1 0 ? ? ? ? ? ? ? ? 46?
1 0 0 1 1 1 0 1 ? ? ? ? ? ? ? 157?
———————————————?
1 0 1 1 0 0 1 1 ? ? ? ? ? ? ? 179
四、位運(yùn)算應(yīng)用
蘋果在類對象的結(jié)構(gòu)中使用了位運(yùn)算這一設(shè)計:每個對象都有一個整型類型的標(biāo)識符flags赊瞬,其中多個不同的位表示了是否存在弱引用、是否被初始化等信息贼涩,對于這些存儲的數(shù)據(jù)通過&巧涧、|等運(yùn)算符獲取出來映皆。這些在runtime源碼中都能看到血公,下面是一段偽代碼(參數(shù)請勿對號入座)
#define IS_TAGGED_POINTER (1 << 12);
#define HAS_WEAK_REFERENCE (1 << 13);
inline void objc_object::free() {
if (this->flags | HAS_WEAK_REFERENCE) {
///? set all weak reference point to nil
}
}
inline int objc_object::retainCount() {
if (this.flags | IS_TAGGED_POINTER) {
return (int)INT_MAX;
} else {
return this->retainCount;
}
}
......
借鑒蘋果的運(yùn)算操作,可以聲明一個應(yīng)用常用權(quán)限的枚舉塌计,來獲取我們的應(yīng)用權(quán)限:
typedef NS_ENUM(NSInteger, LXDAuthorizationType)
{
LXDAuthorizationTypeNone = 0,
LXDAuthorizationTypePush = 1 << 0,? ///<? ? 推送授權(quán)
LXDAuthorizationTypeLocation = 1 << 1,? ///<? ? 定位授權(quán)
LXDAuthorizationTypeCamera = 1 << 2,? ? ///<? ? 相機(jī)授權(quán)
LXDAuthorizationTypePhoto = 1 << 3,? ? ///<? ? 相冊授權(quán)
LXDAuthorizationTypeAudio = 1 << 4,? ///<? ? 麥克風(fēng)授權(quán)
LXDAuthorizationTypeContacts = 1 << 5,? ///<? ? 通訊錄授權(quán)
};
通過聲明一個全局的權(quán)限變量來保存不同的授權(quán)信息袒哥。當(dāng)應(yīng)用擁有對應(yīng)的授權(quán)時缩筛,通過|操作符保證對應(yīng)的二進(jìn)制位的值被修改成1。否則對對應(yīng)授權(quán)枚舉進(jìn)行~取反后再&操作消除二進(jìn)制位的授權(quán)表達(dá)堡称。為了完成這些工作瞎抛,建立一個工具類來獲取以及更新授權(quán)的狀態(tài):
/*!
*@brief獲取應(yīng)用授權(quán)信息工具,最低使用版本:iOS8.0
*/
NS_CLASS_AVAILABLE_IOS(8_0)@interfaceLXDAuthObtainTool :NSObject
///獲取當(dāng)前應(yīng)用權(quán)限
+ (LXDAuthorizationType)obtainAuthorization;
///更新應(yīng)用權(quán)限
+ (void)updateAuthorization;
@end
#pragma mark -LXDAuthObtainTool.m
staticLXDAuthorizationType kAuthorization;
@implementationLXDAuthObtainTool
+ (void)initialize
{
kAuthorization = LXDAuthorizationTypeNone;
[selfupdateAuthorization];
}
///獲取當(dāng)前應(yīng)用權(quán)限
+ (LXDAuthorizationType)obtainAuthorization
{
returnkAuthorization;
}
///更新應(yīng)用權(quán)限
+ (void)updateAuthorization
{
///推送
if([UIApplicationsharedApplication].currentUserNotificationSettings.types==UIUserNotificationTypeNone) {
kAuthorization &= (~LXDAuthorizationTypePush);
}else{
kAuthorization |= LXDAuthorizationTypePush;
}
///定位
if([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedAlways || [CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedWhenInUse) {
kAuthorization |= LXDAuthorizationTypeLocation;
}else{
kAuthorization &= (~LXDAuthorizationTypeLocation);
}
///相機(jī)
if([AVCaptureDevice authorizationStatusForMediaType: AVMediaTypeVideo] == AVAuthorizationStatusAuthorized) {
kAuthorization |= LXDAuthorizationTypeCamera;
}else{
kAuthorization &= (~LXDAuthorizationTypeCamera);
}
///相冊
if([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized) {
kAuthorization |= LXDAuthorizationTypePhoto;
}else{
kAuthorization &= (~LXDAuthorizationTypePhoto);
}
///麥克風(fēng)
[[AVAudioSessionsharedInstance]requestRecordPermission: ^(BOOLgranted) {
if(granted) {
kAuthorization |= LXDAuthorizationTypeAudio;
}else{
kAuthorization &= (~LXDAuthorizationTypeAudio);
}
}];
///通訊錄
if([UIDevicecurrentDevice].systemVersion.doubleValue>=9) {
if([CNContactStore authorizationStatusForEntityType: CNEntityTypeContacts] == CNAuthorizationStatusAuthorized) {
kAuthorization |= LXDAuthorizationTypeContacts;
}else{
kAuthorization &= (~LXDAuthorizationTypeContacts);
}
}else{
if(ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized) {
kAuthorization |= LXDAuthorizationTypeContacts;
}else{
kAuthorization &= (~LXDAuthorizationTypeContacts);
}
}
}
@end
在我們需要使用某些授權(quán)的時候却紧,例如打開相冊時桐臊,直接使用&運(yùn)算符判斷權(quán)限即可:
- (void)openCamera {
LXDAuthorizationType type = [LXDAuthObtainTool obtainAuthorization];
if (type & LXDAuthorizationTypeCamera) {
///? open camera
} else {
/// alert
}
}
在數(shù)據(jù)存儲的方面位運(yùn)算擁有著占用內(nèi)存少,高效率的優(yōu)點(diǎn)晓殊,當(dāng)然位運(yùn)算能做的不僅僅是這些断凶,比如筆者項目有這樣的一個需求:用戶登錄成功之后在首頁界面請求服務(wù)器下載所有金額相關(guān)的數(shù)據(jù)。這個需求最大的問題是:
AFN2.3+版本的請求庫不支持同步請求巫俺,當(dāng)需要多個請求任務(wù)一次性執(zhí)行時认烁,判斷請求任務(wù)完成是很麻煩的一件事情。
由于NSInteger擁有8個字節(jié)64位的二進(jìn)制位,因此筆者將每一個二進(jìn)制位用來表示單個任務(wù)請求的完成狀態(tài)却嗡。已知登陸后需要同步數(shù)據(jù)的接口為N(<64)個舶沛,因此可以聲明一個全部請求任務(wù)完成后的狀態(tài)變量:
NSInteger complete = 0;
for (int idx = 0; idx < N; idx++) {
complete |= (1 << idx);
}
然后使用一個標(biāo)志變量flags用來記錄當(dāng)前任務(wù)請求的完成情況,每一個數(shù)據(jù)同步的任務(wù)完成之后對應(yīng)的二進(jìn)制位就置為1:
__block NSInteger flags = 0;
NSArray * urls = @[......];
NSArray * params = @[......];
for (NSInteger idx = 0; idx < urls.count; idx++) {
NSString * url = urls[idx];
NSDictionary * param = params[idx];
[LXDDataSyncTool syncWithUrl: url params: param complete: ^{
flags |= (1 << idx);
if ( (flags ^ complete) == 0 ) {
[self completeDataSync];
}
}];
}
參考鏈接:iOS開發(fā)之位運(yùn)算