99热99这里只有精品6国产,亚洲中文字幕在线天天更新,在线观看亚洲精品国产福利片 ,久久久久综合网

歡迎加入QQ討論群258996829
麥子學(xué)院 頭像
蘋果6袋
6
麥子學(xué)院

iOS學(xué)習(xí)之位運(yùn)算詳解

發(fā)布時(shí)間:2016-11-08 16:38  回復(fù):0  查看:2389   最后回復(fù):2016-11-08 16:38  

從現(xiàn)代計(jì)算機(jī)電路來(lái)說(shuō),只有 通電/沒電 兩種狀態(tài),即為 0/1 狀態(tài),計(jì)算機(jī)中所有的數(shù)據(jù)按照具體的編碼格式以二進(jìn)制的形式存儲(chǔ)在設(shè)備中。

  直接操作這些二進(jìn)制數(shù)據(jù)的位數(shù)據(jù)就是位運(yùn)算,在iOS開發(fā)中基本所有的位運(yùn)算都通過(guò)枚舉聲明傳值的方式將位運(yùn)算的實(shí)現(xiàn)細(xì)節(jié)隱藏了起來(lái):

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)算是一種極為高效乃至可以說(shuō)最為高效的計(jì)算方式,雖然現(xiàn)代程序開發(fā)中編譯器已經(jīng)為我們做了大量的優(yōu)化,但是合理的使用位運(yùn)算可以提高代碼的可讀性以及執(zhí)行效率。

基礎(chǔ)計(jì)算

  在了解怎么使用位運(yùn)算之前,筆者簡(jiǎn)單說(shuō)一下CPU處理計(jì)算的過(guò)程。如果你對(duì) CPU的計(jì)算方式有所了解,可以跳過(guò)這一節(jié)。

  當(dāng)代碼 int sum = 11 + 79 被執(zhí)行的時(shí)候,計(jì)算機(jī)直接將兩個(gè)數(shù)的二進(jìn)制位進(jìn)行相加和進(jìn)位操作:

11: 0 0 0 0 1 0 1 179: 0 1 0 0 1 1 1 1

————————————————————90: 0 1 0 1 1 0 1 0

  通常來(lái)說(shuō)CPU執(zhí)行兩個(gè)數(shù)相加操作所花費(fèi)的時(shí)間被我們稱作一個(gè)時(shí)鐘周期,而2.0GHz頻率的CPU表示可以在一秒執(zhí)行運(yùn)算2.0*1024*1024*1024 個(gè)時(shí)鐘周期。相較于加法運(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

  簡(jiǎn)單來(lái)說(shuō),不難發(fā)現(xiàn)當(dāng)某個(gè)數(shù)乘以 2N次冪 的時(shí)候,結(jié)果等同于將這個(gè)數(shù)的二進(jìn)制位置向左移動(dòng) N 位,在代碼中我們使用 num << N 表示將 num 的二進(jìn)制數(shù)據(jù)左移N 個(gè)位置,其效果等同于下面這段代碼:

for (int idx = 0; idx < N; idx++) {

num *= 2;

}

  假如相乘的兩個(gè)數(shù)都不是 2N次冪 ,這時(shí)候編譯器會(huì)將其中某個(gè)值分解成多個(gè) 2N次冪 相加的結(jié)果進(jìn)行運(yùn)算。比如 37 * 69 ,這時(shí)候CPU會(huì)將 37 分解成 32+4+1,然后換算成 (69<<5) + (69<<2) + (69<<0)的方式計(jì)算出結(jié)果。因此,計(jì)算兩個(gè)數(shù)相乘通常需要十個(gè)左右的時(shí)鐘周期。 同理,代碼 num >> N 的作用等效于:

for (int idx = 0; idx < N; idx++) {

num /= 2;

}

  但是兩個(gè)數(shù)相除花費(fèi)的時(shí)鐘周期要比乘法還要多得多,其大部分消耗在將數(shù)值分解成多個(gè) 2N次冪 上。除此之外,浮點(diǎn)數(shù)涉及到的計(jì)算更為復(fù)雜,這里也簡(jiǎn)單聊聊浮點(diǎn)數(shù)的準(zhǔn)確度問題。拿 float 類型來(lái)說(shuō),總共使用了 32bit 的存儲(chǔ)空間,其中第一位表示正負(fù), 2~13 表示整數(shù)部分的值, 14~32 之中分別存儲(chǔ)了小數(shù)位以及科學(xué)計(jì)數(shù)的標(biāo)識(shí)值(這里可能并不那么準(zhǔn)確,主要是為了給讀者一個(gè)大概的介紹)。由于小數(shù)位的二進(jìn)制數(shù)據(jù)依舊保持 2N次冪 特性,假如下面的二進(jìn)制屬于小數(shù)位:

  那么這部分小數(shù)位的值等于: 1/2 + 1/4 + 1/8 + 1/16 + 1/128 = 0.9453125。因此,當(dāng)你把一個(gè)沒有任何規(guī)律的小數(shù)例如3.1415926535898 存入計(jì)算機(jī)的時(shí)候,小數(shù)點(diǎn)后面會(huì)被拆解成很多的 2N次冪 進(jìn)行保存。由于小數(shù)位總是有限的,因此當(dāng)分解的 N 超出這些位數(shù)時(shí)導(dǎo)致存儲(chǔ)不下,就會(huì)出現(xiàn)精度偏差。另一方面,這樣的分解計(jì)算勢(shì)必要消耗大量的時(shí)鐘周期,這也是大量的浮點(diǎn)數(shù)運(yùn)算 (cell動(dòng)態(tài)計(jì)算) 容易引發(fā)卡頓的原因。所以,當(dāng)小數(shù)位過(guò)多時(shí),改用字符串存儲(chǔ)是一個(gè)更優(yōu)的選擇。

位運(yùn)算符

  使用的運(yùn)算符包括下面:

含義運(yùn)算符

 iOS開發(fā)之位運(yùn)算&nbsp;iOS學(xué)習(xí)之位運(yùn)算詳解

操作

0 0 1 0 1 1 1 0 46

1 0 0 1 1 1 0 1 157

———————————————

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

 

色彩存儲(chǔ)

  使用位運(yùn)算包括下面幾個(gè)原因: 1、代碼更簡(jiǎn)潔 2、更高的效率 3、更少的內(nèi)存

  簡(jiǎn)單來(lái)說(shuō),我們?nèi)绾螁渭兊谋4嬉粡?/span> RGB 色彩空間下的圖片?由于圖片由一系列的像素組成,每個(gè)像素有著自己表達(dá)的顏色,因此需要這么一個(gè)類用來(lái)表示圖片的單個(gè)像素:

@interface Pixel

@property (nonatomic, assign) CGFloat red;@property (nonatomic, assign) CGFloat green;@property (nonatomic, assign) CGFloat blue;@property (nonatomic, assign) CGFloat alpha;

@end

  那么在4.7寸的屏幕上,啟動(dòng)圖需要 750*1334 個(gè)這樣的類,不計(jì)算其他數(shù)據(jù),單單是變量的存儲(chǔ)需要 750*1334*4*8 = 32016000 個(gè)字節(jié)的占用內(nèi)存。但實(shí)際上我們使用到的圖片總是將 RGBA 這四個(gè)屬性保存在一個(gè) int 類型或者其它相似的少字節(jié)變量中。

  由于色彩取值范圍為 0~255 ,即 2^1 ~ 2^8-1 不超過(guò)一個(gè)字節(jié)的整數(shù)占用內(nèi)存。因此可以通過(guò)左移運(yùn)算保證每一個(gè)字節(jié)只存儲(chǔ)了一個(gè)決定色彩的值:

- (int)rgbNumberWithRed: (int)red green: (int)green blue: (int)blue alpha: (float)alpha {

int bitPerByte = 8;

int maxNumber = 255;

int alphaInt = alpha * maxNumber;

int rgbNumber = (red << (bitPerByte*3)) + (green << (bitPerByte*2)) + (blue << bitPerByte) + alphaInt;

}

  同理,通過(guò)右移操作保證數(shù)值的最后一個(gè)字節(jié)存儲(chǔ)著需要的數(shù)據(jù),并用 0xff 將值取出來(lái):

- (void)obtainRGBA: (int)rgbNumber {

int mask = 0xff;

int bitPerByte = 8;

double alphaInt = (rgbNumber & mask) / 255.0;

int blue = ((rgbNumber >> bitPerByte) & mask);

int green = ((rgbNumber >> (bitPerByte*2)) & mask);

int red = ((rgbNumber >> (bitPerByte*3)) & mask);

}

  對(duì)比使用類和位運(yùn)算存儲(chǔ),效率跟內(nèi)存占用上可以說(shuō)是完敗。

位運(yùn)算應(yīng)用

  蘋果在類對(duì)象的結(jié)構(gòu)中使用了位運(yùn)算這一設(shè)計(jì):每個(gè)對(duì)象都有一個(gè)整型類型的標(biāo)識(shí)符 flags ,其中多個(gè)不同的位表示了是否存在弱引用、是否被初始化等信息,對(duì)于這些存儲(chǔ)的數(shù)據(jù)通過(guò) & 、 | 等運(yùn)算符獲取出來(lái)。這些在 runtime源碼 中都能看到,下面是一段偽代碼(參數(shù)請(qǐng)勿對(duì)號(hào)入座)

#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)算操作,可以聲明一個(gè)應(yīng)用常用權(quán)限的枚舉,來(lái)獲取我們的應(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, ///< 相冊(cè)授權(quán)

LXDAuthorizationTypeAudio = 1 << 4, ///< 麥克風(fēng)授權(quán)

LXDAuthorizationTypeContacts = 1 << 5, ///< 通訊錄授權(quán)

};

  通過(guò)聲明一個(gè)全局的權(quán)限變量來(lái)保存不同的授權(quán)信息。當(dāng)應(yīng)用擁有對(duì)應(yīng)的授權(quán)時(shí),通過(guò) | 操作符保證對(duì)應(yīng)的二進(jìn)制位的值被修改成 1 。否則對(duì)對(duì)應(yīng)授權(quán)枚舉進(jìn)行~ 取反后再 & 操作消除二進(jìn)制位的授權(quán)表達(dá)。為了完成這些工作,建立一個(gè)工具類來(lái)獲取以及更新授權(quán)的狀態(tài):

/*!

* @brief 獲取應(yīng)用授權(quán)信息工具,最低使用版本:iOS8.0

*/NS_CLASS_AVAILABLE_IOS(8_0) @interface LXDAuthObtainTool : NSObject

/// 獲取當(dāng)前應(yīng)用權(quán)限

+ (LXDAuthorizationType)obtainAuthorization;/// 更新應(yīng)用權(quán)限

+ (void)updateAuthorization;

@end

#pragma mark - LXDAuthObtainTool.mstatic LXDAuthorizationType kAuthorization;

@implementation LXDAuthObtainTool

+ (void)initialize

{

kAuthorization = LXDAuthorizationTypeNone;

[self updateAuthorization];

}

/// 獲取當(dāng)前應(yīng)用權(quán)限

+ (LXDAuthorizationType)obtainAuthorization

{

return kAuthorization;

}

/// 更新應(yīng)用權(quán)限

+ (void)updateAuthorization

{

/// 推送

if ([UIApplication sharedApplication].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);

}

/// 相冊(cè)

if ([PHPhotoLibrary authorizationStatus] == PHAuthorizationStatusAuthorized) {

kAuthorization |= LXDAuthorizationTypePhoto;

else {

kAuthorization &= (~LXDAuthorizationTypePhoto);

}

/// 麥克風(fēng)

[[AVAudioSession sharedInstance] requestRecordPermission: ^(BOOL granted) {

if (granted) {

kAuthorization |= LXDAuthorizationTypeAudio;

else {

kAuthorization &= (~LXDAuthorizationTypeAudio);

}

}];

/// 通訊錄

if ([UIDevice currentDevice].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)的時(shí)候,例如打開相冊(cè)時(shí),直接使用 & 運(yùn)算符判斷權(quán)限即可:

- (void)openCamera {

LXDAuthorizationType type = [LXDAuthObtainTool obtainAuthorization];

if (type & LXDAuthorizationTypeCamera) {

/// open camera

else {

/// alert

}

}

  在數(shù)據(jù)存儲(chǔ)的方面位運(yùn)算擁有著占用內(nèi)存少,高效率的優(yōu)點(diǎn),當(dāng)然位運(yùn)算能做的不僅僅是這些,比如筆者項(xiàng)目有這樣的一個(gè)需求:用戶登錄成功之后在首頁(yè)界面請(qǐng)求服務(wù)器下載所有金額相關(guān)的數(shù)據(jù)。這個(gè)需求最大的問題是:

AFN2.3+ 版本的請(qǐng)求庫(kù)不支持同步請(qǐng)求,當(dāng)需要多個(gè)請(qǐng)求任務(wù)一次性執(zhí)行時(shí),判斷請(qǐng)求任務(wù)完成是很麻煩的一件事情。

  由于 NSInteger 擁有8個(gè)字節(jié)64位的二進(jìn)制位,因此筆者將每一個(gè)二進(jìn)制位用來(lái)表示單個(gè)任務(wù)請(qǐng)求的完成狀態(tài)。已知登陸后需要同步數(shù)據(jù)的接口為 N(<64)個(gè),因此可以聲明一個(gè)全部請(qǐng)求任務(wù)完成后的狀態(tài)變量:

NSInteger complete = 0;for (int idx = 0; idx < N; idx++) {

complete |= (1 << idx);

}

  然后使用一個(gè)標(biāo)志變量 flags 用來(lái)記錄當(dāng)前任務(wù)請(qǐng)求的完成情況,每一個(gè)數(shù)據(jù)同步的任務(wù)完成之后對(duì)應(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];

}

}];

}

位運(yùn)算與算法

  在普遍使用高級(jí)語(yǔ)言開發(fā)的大環(huán)境下,位運(yùn)算的實(shí)現(xiàn)更多的被封裝起來(lái),因此大多數(shù)開發(fā)者在項(xiàng)目開發(fā)中不見得會(huì)使用這一機(jī)制。在上面基礎(chǔ)計(jì)算 一節(jié)中筆者說(shuō)過(guò)兩個(gè)數(shù)相加只需要一個(gè)時(shí)鐘周期(雖然 CPU 從寄存器讀取存放數(shù)據(jù)也需要額外的時(shí)鐘周期,但通常這部分的花銷總是常量級(jí),可以忽略不計(jì))

  由于位運(yùn)算的處理基本也在一個(gè)時(shí)鐘周期完成,位運(yùn)算這一操作備受算法封裝者的喜愛。比如交換兩個(gè)變量的值一般情況下代碼是:

int sum = a;a = b;b = sum;

  又或者:

a = a + b;b = a - b;a = a - b;

  如果通過(guò)位運(yùn)算的方式則不需要任何加減操作或者臨時(shí)變量:

a ^= b;b = a ^ b;a = a ^ b;

  上面的代碼和第二種方式的實(shí)現(xiàn)思路類似,都是將 a  b 合并成單個(gè)變量,再分別消除變量中的 a  b 的值( ^ 運(yùn)算會(huì)對(duì)相同二進(jìn)制位的值置0,意味著 b^b 的結(jié)果等于0

  進(jìn)階題:找出整型數(shù)組中唯一的單獨(dú)數(shù)字,數(shù)組中的其他數(shù)字的個(gè)數(shù)為2個(gè)

  通過(guò)上面不用中間變量交換 a  b 的值可以得出下面的最簡(jiǎn)代碼:

- (int)singleDog(int * nums) {

int singleDog = 0;

for (int idx = 0; idx < sizeof(nums)/sizeof(int); idx++) {

singleDog ^= nums[idx];

}

return singleDog;

}

 

文章來(lái)源:Bison的技術(shù)博客

您還未登錄,請(qǐng)先登錄

熱門帖子

最新帖子

?