關于Block

關鍵字

block一般使用copy關鍵字進行修飾,block使用copy是從MRC遺留下來的“傳統”,在MRC中,方法內容的block是在棧區的,使用copy可以把它放到堆區。但在ARC中寫不寫都行:編譯器自動對block進行了copy操作。

捕獲自動變量

int val = 10;  
void (^blk)(void) = ^{printf("val=%d\n",val);};  
val = 2;  
blk();    

上面這段代碼,輸出值是:val = 10.而不是2.
block 在實現時就會對它引用到的它所在方法中定義的棧變量進行一次只讀拷貝,然后在 block 塊內使用該只讀拷貝;換句話說block截獲自動變量的瞬時值;或者block捕獲的是自動變量的副本。(我更喜歡這么理解:block捕獲變量的機制更像是函數按值傳遞而非按地址傳遞)

block函數中不能隨意變量值,但是可以修改靜態變量、外部變量以及用__block關鍵字修飾的普通變量。同時,對于這幾種情況時,在block函數后修改靜態變量、外部變量以及用__block關鍵字修飾的普通變量時,也會影響block函數內這些變量的值。

NSInteger CounterGlobal = 0; //不使用extern為消除警告。寫在函數外默認存在extern。
static NSInteger CounterStatic = 0;

- (void)block4
{
        NSInteger localCounter = 42;
        __block char localCharacter;
        
        void (^aBlock)(void) = ^(void) {
            ++CounterGlobal;
            ++CounterStatic;
            CounterGlobal = localCounter; // localCounter fixed at block creation
            localCharacter = 'a'; // sets localCharacter in enclosing scope
            
            //result:CounterGlobal(42),CounterStatic(5),localCharacter(‘a’)
        };
        CounterStatic = 4;
        ++localCounter; // unseen by the block
        localCharacter = 'b';
        
        aBlock(); // execute the block
    
}

block循環引用的原理

@interface KSViewController ()  
{  
    id _observer;  
}  
  
@end  
  
@implementation KSViewController  
  
- (void)viewDidLoad  
{  
    [super viewDidLoad];  
    // Do any additional setup after loading the view, typically from a nib.  
      
    KSTester * tester = [[KSTester alloc] init];  
    [tester run];  
      
    _observer = [[NSNotificationCenter defaultCenter]  
                 addObserverForName:@"TestNotificationKey"  
                 object:nil queue:nil usingBlock:^(NSNotification *n) {  
                     NSLog(@"%@", self);  
                 }];  
}  
  
- (void)dealloc  
{  
    if (_observer) {  
        [[NSNotificationCenter defaultCenter] removeObserver:_observer];  
    }  
} 

self持有observer,observer持有block,block中持有self。因為block始終強引用self導致self無法釋放,進而dealloc方法不會執行,observer不會釋放,內存泄漏。

如下圖:


循環引用原理.png

使用weak,strong解決

__weak KSViewController * wself = self;  
    _observer = [[NSNotificationCenter defaultCenter]  
                 addObserverForName:@"TestNotificationKey"  
                 object:nil queue:nil usingBlock:^(NSNotification *n) {  
                     KSViewController * sself = wself;  
                     if (sself) {  
                         NSLog(@"%@", sself);  
                     }  
                     else {  
                         NSLog(@"<self> dealloc before we could run this code.");  
                     }  
                 }];  

在 block 之前定義對 self 的一個弱引用 wself,因為是弱引用,所以當 self 被釋放時 wself 會變為 nil;然后在 block 中將該弱引用置為強引用,便于Block內后續方法調用。

如下圖:

避免循環引用的原理.png

當外部引用中斷,self釋放,dealloc方法執行,observer釋放,進而全部釋放。

弱引用與強引用的區別

當還有一個strong(繩子)指向對象(狗)的時候,對象(狗)無法釋放(跑掉)。
當沒有strong(繩子)指向對象(狗)的時候,無論有多少weak(看著)對象(狗),對象(狗)都會釋放(跑掉)。

Block的使用

在函數式編程中,把函數當參數來回傳遞,而這個,說成術語,我們把他叫做高階函數。而在oc中,blocks是被廣泛使用的參數傳遞,它實際上是匿名函數
一個簡單的帶有成功失敗的網絡請求,簡單block作為參數。

- (void)unBindDevice:(NSString *)mac success:(dispatch_block_t)success failure:(HWBusinessFailureBlock)failure
{
    HWAPIV2UnBindDevice *client = [[HWAPIV2UnBindDevice alloc] initWithdeviceId:mac];

    [client startWithCompletionBlockWithSuccess:^(__kindof YTKBaseRequest *_Nonnull request) {
      HWBusinessException *enception = [[HWBusinessException alloc] initWithRequest:request];
      HWLogDeBug(enception.retInfo);
      if (enception.success) {
          [self removeDeviceFromUserDeviceListWithMac:mac];
          if (success) {
              success();
          }
      }
      else if (failure) {

          failure(enception);
      }
    }
        failure:^(__kindof YTKBaseRequest *_Nonnull request) {
          if (failure) {
              failure([HWBusinessException netException]);
          }
        }];
}

rac使用block作為參數的一個經典代碼事例。

RACSignal *filteredSsid = [ssidSourceSignal filter:^BOOL(NSString *value) {
    NSString *text = value;
    return text.length > 1;
}];

其中block:^BOOL(NSString *value)作為參數傳入。 value是filter函數內部返回,我們用于Block體內使用實現。而bool值作為block的返回值由block體內返回給filter函數內部。而filter內部使用該bool值對數據進行處理返回帶篩選結果的RACSignal類型變量。

同樣,block也可以在方法中作為閉包進行代碼抽象。

typedef void (^successBlock)(NSDictionary * dict);
successBlock block = ^(NSDictionary * dict){
  //統一處理
};

if (self.login) {
  [self requestLoginWith:param completion:block];
} else {
  [self requestNotLoginWith:param completion:block];
}
block作為屬性
@property (nonatomic , strong) void(^textChangeBlock)(NSString *text);
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容