讀“編寫高質(zhì)量iOS與OSX代碼的52個有效方法”筆記(04)

塊和大中樞派發(fā)

第37條:理解”塊“這一概念

Block的了解還是很有必要的,它和函數(shù)類似,只不過是直接定義在另一個函數(shù)里的,和定義它的那個函數(shù)共享同一個范圍的東西。用^來表示,后面接一對花括號,括號里是blcok 的實(shí)現(xiàn)代碼。

void(^someBlcok)() = ^{

    NSLog(@"there has someBlock");

};

someBlcok();  // print:“there has someBlock”

格式: 返回類型 (^Block Name)(參數(shù)){};

int (^addBlock)(int a, int b) = ^(int a ,int b){
    
    return a + b;
    
};
int testAdd = addBlock(7,5);
NSLog(@"testAdd === %d",testAdd); // print: “testAdd === 12”


int additonal = 5;
int (^minusBlock)(int a, int b) = ^(int a,int b){
    
    return  additonal -  a - b;
};
int testMinus = minusBlock(2,6);
NSLog(@"minus === %d",testMinus);  //print :"minus === -3"

注意當(dāng)要改變塊里面的變量的時候,需要加上__block修飾符

NSArray * array = @[@0,@1,@2,@3,@4,@5];
__block NSInteger count = 0;
[array enumerateObjectsUsingBlock:^(NSNumber * number,NSUInteger idx,BOOL *stop){
    if([number compare:@4] == NSOrderedAscending)
    {
        count++;
    }
    
}];

NSLog(@"count ==== %lu",count);  // print : "count ==== 4"
注意還可以使用反序和順序
- (void)enumerateObjectsWithOptions:(NSEnumerationOptions)opts 
                         usingBlock:(void (^)(ObjectType obj, NSUInteger idx, BOOL *stop))block

typedef NS_OPTIONS(NSUInteger, NSEnumerationOptions) {
    NSEnumerationConcurrent = (1UL << 0), // 順序
    NSEnumerationReverse = (1UL << 1), // 倒序
};

定義block的時候,所占的內(nèi)存區(qū)域是分配在棧中的,也就是block只在定它的那個范圍內(nèi)有效。所以我們我們需要對其對象發(fā)送copy消息以拷貝值,這樣的話,就可以把塊從棧復(fù)制到堆了,塊這樣就成了帶引用計數(shù)的對象啦。

第38條:為常用的塊類型創(chuàng)建typedef

由于在定義塊變量時,需要把變量名放在類型之中,而不要放在右側(cè),這樣非常難記,也非常難懂,鑒于此,我們應(yīng)該為常用的塊類型起個別名,此時typedef關(guān)鍵字就用到了。

typedef int (^YPQBlcok)(BOOL flag,int value);
第39條:用handler塊降低代碼分散程度

當(dāng)用Handler塊的時候,可以直接將塊和相關(guān)對象放在一起。這樣代碼更清晰而緊湊。

#import <Foundation/Foundation.h>

typedef void(^YPQNetworkCompletionHandler)(NSData * data);

@interface YPQNetworkFetcher: NSObject

@property (nonatomic, readonly, strong) NSURL * url;

- (id)initWithURL:(NSURL *)url;
- (void)startWithCompletionHandler:(YPQNetworkCompletionHandler)comletion;

@end

注意實(shí)現(xiàn)文件中 的 copy

#import "YPQNetworkFetcher.h"

@interface YPQNetworkFetcher()

@property (nonatomic, readwrite, strong)NSURL * url;
@property (nonatomic, copy)YPQNetworkCompletionHandler completionHandle;
@property (nonatomic, strong) NSData * downloadedData;

@end

@implementation YPQNetworkFetcher

- (id)initWithURL:(NSURL *)url
{
    if(self = [super init])
    {
        _url = url;
    }
    return self;
}
- (void)startWithCompletionHandler:(YPQNetworkCompletionHandler)comletion
{
    self.completionHandle = comletion;
    // 開始 request
    // downloadedData 獲值
    // 當(dāng)申請完成,調(diào)用 [self p_requestCompleted];
   
}

- (void)p_requestCompleted
{
    if(_completionHandle)
    {
        _completionHandle(_downloadedData);
    }
}


@end
第40條:用塊引用其所屬對象時不要出現(xiàn)保留環(huán)

就是我們使用Block的時候很容易出現(xiàn)循環(huán)引用。

#import "ViewController.h"
#import "YPQNetworkFetcher.h"

@interface ViewController ()
{
    YPQNetworkFetcher * _networkFetcher;
    
}
@property (nonatomic ,strong) NSData * downloadData;

@end


- (void)downloadTheData
{
    NSURL * url = [NSURL URLWithString:@"https://www.example.com/...."];
    _networkFetcher = [[YPQNetworkFetcher alloc] initWithURL:url];
    [_networkFetcher startWithCompletionHandler:^(NSData * data){
       self.downloadData = data;
    }];
    
}

這樣看起來沒問題,但是已經(jīng)形成循環(huán)引用啦

循環(huán)引用
  1. block保留了viewController實(shí)例;
  2. viewController實(shí)例則通過_networkFetcher保留YPQNetworkFetcher
  3. _completionHandle又保留了block。

這樣環(huán)就形成了,打破環(huán)就得打破其中某一個環(huán)節(jié),比較好用的方法可以通過;

// 打破上述的第一環(huán)節(jié)
__weak __typeof(self) weakSelf = self;
 [_networkFetcher startWithCompletionHandler:^(NSData * data){
   weakSelf.downloadData = data;
}];

或者

// 打破上述的第二環(huán)節(jié)
[_networkFetcher startWithCompletionHandler:^(NSData * data){

   self.downloadData = data;
    _networkFetcher = nil;
   
}];
第41條:多用派發(fā)隊列,少用同步鎖

如果多個線程要執(zhí)行同一份代碼的時候,通常都要使用鎖實(shí)現(xiàn)某種同步機(jī)制。

 @synchronized(self) {
    //safe
}

_lock = [[NSLock alloc] init];
[_lock lock];
//safe
[_lock unlock];

但是這兩種方式效率不高,而且也無法提供絕對的線程安全。有種簡單而高效的辦法可以代替他們,那即是使用”串行同步隊列“,將讀取操作和寫入操作都安排在同一個隊列中。

_syncQuene = dispatch_queue_create("com.yang.test", NULL); - (NSString *)testGCDString
{
      __block NSString * localString;
      dispatch_sync(_syncQuene, ^{
           localString = _testGCDString;
      });
      return localString;
}

- (void)setTestGCDString:(NSString *)testGCDString
{
       dispatch_sync(_syncQuene, ^{
          _testGCDString = testGCDString;
       });
}

當(dāng)然還可以繼續(xù)優(yōu)化, 將同步派發(fā)改為異步派發(fā),并且由于多個獲取方法可以并發(fā)執(zhí)行,而獲取方法和設(shè)置方法之間不能并發(fā)執(zhí)行,此時改用并發(fā)隊列。

_syncQuene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- (NSString *)testGCDString
{
      __block NSString * localString;
      dispatch_sync(_syncQuene, ^{
           localString = _testGCDString;
      });
      return localString;
}

- (void)setTestGCDString:(NSString *)testGCDString
{
       dispatch_async(_syncQuene, ^{
          _testGCDString = testGCDString;
       });
}

但是像上面這樣,還是無法正確實(shí)現(xiàn)同步。讀取和寫入可以隨時執(zhí)行,為了不讓其任意執(zhí)行,此時dispatch_barrier_async就出現(xiàn)啦

_syncQuene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- (NSString *)testGCDString
{
    __block NSString * localString;
    dispatch_sync(_syncQuene, ^{
        
        localString = _testGCDString;
    });
    return localString;

}

- (void)setTestGCDString:(NSString *)testGCDString
{
    dispatch_barrier_async(_syncQuene, ^{
        
        _testGCDString = testGCDString;
    });
}

dispatch_barrier_async必須單獨(dú)執(zhí)行,不能與其他塊并行。這只對并發(fā)隊列有意義,因為串行隊里中塊總是按順序逐個來執(zhí)行的。并發(fā)隊列如果發(fā)現(xiàn)接下來要處理的塊是個barrier block,那么就一直要等到當(dāng)前所有并發(fā)塊都執(zhí)行完畢,才會單獨(dú)執(zhí)行這個barrier block。待barrier block執(zhí)行過后,再按正常方式繼續(xù)向下處理。

在上面這個隊列中,在寫入操作用了dispatch_barrier_async來實(shí)現(xiàn)后,對屬性的讀取操作依然可以并行,但寫入操作必須單獨(dú)執(zhí)行。當(dāng)然設(shè)置函數(shù)中,我們也可以用dispatch_barrier_sync同步來實(shí)現(xiàn),有時可能更高效,看具體場景吧。

將同步與異步派發(fā)結(jié)合起來,實(shí)現(xiàn)與普通加鎖機(jī)制一樣的同步行為,也不會阻塞執(zhí)行異步派發(fā)的線程,所以是 OK 的。

第42條:多用GCD,少用performSelector

在我們用到推遲執(zhí)行方法的時候,我們可以有種選擇。

// performSelector
 [self performSelector:@selector(afterThreeSecondBeginAction)
            withObject:nil
            afterDelay:3.0f];
// dispatch after
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^(void){

    [self afterThreeSecondBeginAction];

});

performSelector系列方法在內(nèi)存管理方面容易疏失,并且所處理的選擇子太過局限了,GCD 作為純C的API,一般擔(dān)憂還是沒必要的,所以說少用performSelector還是有理由的。

第43條:掌握GCD及操作隊列的使用時機(jī)

簡單的說,不要過度使用GCD,要合理使用它,這個可能需要項目經(jīng)驗的積累的。像NSOperationQueue類也可以多了解下。

第44條:通過Dispatch Group 機(jī)制,根據(jù)系統(tǒng)資源狀況執(zhí)行任務(wù)

dispatch group是GCD的一項特性,能夠把任務(wù)分組。

這里是GCD的詳細(xì)介紹,推薦一篇文章@ 少君的GCD

第45條:使用dispatch_once 來執(zhí)行只需要運(yùn)行一次的線程安全代碼
static id _instace;
+ (instancetype)sharedInstanceWithSome
{
      static dispatch_once_t onceToken;
      dispatch_once(&onceToken, ^{
           _instace = [[self alloc] init];
      });
      return _instace;
}

使用 dispatch_once可以簡化代碼并且徹底保證線程安全,我們根本無須擔(dān)心加鎖或同步,另外它沒有使用重量級的同步機(jī)制,所以也更高效。

第46條:不要使用dispatch_get_current_queue

其實(shí)這個已經(jīng)在iOS6.0之后被棄用了,它可以做調(diào)試,但實(shí)際也用的不多,所以一般我想應(yīng)該是不會用到吧。

持續(xù)筆記中····

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容