前言
說起block,想必作為一名iOS開發人員,不可能沒有接觸過,但是用的多不代表你就真正懂了,本篇的目的也就是鞏固一下對于block的學習,以及一些坑點和面試題進行分析,看我們到底有多懂block😄
一.block的分類

結合對于block的分類,我們分別把三種block展示出來,代碼如下
NSGlobalBlock
void (^block)(void) = ^{
NSLog(@"YCX");
};
NSLog(@"%@",block);
列印結果

NSMallocBlock
int a = 10;
void (^block)(void) = ^{
NSLog(@"Cooci - %d",a);
};
NSLog(@"%@",block);
列印結果

NSStackBlock
int a = 10;
void (^ __weak block)(void) = ^{
NSLog(@"Cooci - %d",a);
};
NSLog(@"%@",block);
列印結果

說明:前面也說了堆疊block區別在于是否被強參考或者賦值給copy修飾的屬性變數,void (^ block)(void) =不就是一個參考的程序嗎,而下面第三個例子之所以是堆疊block,主要是用__weak修飾了,弱參考沒有增加參考計數而已
二.block的參考問題
1.block拷貝到堆block
- 1.手動copy
- 2.block作為回傳值
- 3.被強參考或者copy修飾
- 4.系統API包含usingBlock
2.回圈參考
正常情況下

為什么會產生回圈參考,如下圖所示

3.解決回圈參考的方案
案例:常見的回圈參考 self持有block block持有self
self.name = @"ycx";
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",self.name);
});
};
解決方案1
- (void)block1{
__weak typeof(self) weakSelf = self;
self.block = ^(void){
__strong __typeof(weakSelf)strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",strongSelf.name);
});
};
self.block();
}
決議:這種方式也是最常見的方式,俗稱強弱共舞,通過__weak和__strong,從而達到改變參考關系,同時改變作用域,使得只有在block內容執行完畢后,才可以釋放self
解決方案2
__block ViewController *vc = self;
self.block = ^(void){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
vc = nil;
});
};
self.block();
決議:通過__block修飾的臨時變數vc去參考self,所以只有當block里面執行完畢,并且vc置空,才可以正常釋放self
解決方案3
self.block = ^(ViewController *vc){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
});
};
self.block(self);
決議:通過傳參的方式,其實和方案2原理有些相似,只不過通過傳參的方式,那么vc的作用域默認就是block代碼塊的范圍,執行完畢后,vc置空,自然就可以正常釋放self了
當然了,還有其他的方案可以解決,后續會補上的
三.面試題分析
例題1
- (void)blockDemo2{
NSObject *objc = [NSObject new];
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc))); // 1
void(^strongBlock)(void) = ^{ // 1 - block -> objc 捕獲 + 1 = 2
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
strongBlock();
void(^__weak weakBlock)(void) = ^{ // + 1
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
weakBlock();
void(^mallocBlock)(void) = [weakBlock copy];
mallocBlock();
}
決議:列印結果是1345,首先一點每一個block里面都有參考objc,所以每個地方都會是參考計數+1,這是沒問題的,但是為什么strongBlock里面會是參考計數+2呢?在這里我們要明白,block本身是堆疊block,只不過它的代碼塊當中參考了objc,所以才使得objc參考計數+1,但是因為堆疊block又被strongBlock所持有,也就是所謂的copy修飾的變數持有,意味著又拷貝了一份到了堆中,也就是堆block,因此使得參考計數再+1,而weakBlock本身是堆疊block,但是下面的mallocBlock又對weakBlock進行了一次copy,所以計數再+1,其實strongBlock完全可以看成下面2步操作的結合,通過這題也就充分說明了堆疊block的區別了
例題2
// 底層 參考底層仿寫的block
struct _LGBlock {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
// 函式指標
LGBlockInvokeFunction invoke;
struct _LGBlockDescriptor1 *descriptor;
};
- (void)blockDemo1{
int a = 0;
void(^ __weak weakBlock)(void) = ^{
NSLog(@"-----%d", a);
};
struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;
// 深淺拷貝
id __strong strongBlock = weakBlock;
//id __strong strongBlock = [weakBlock copy];
blc->invoke = nil;
void(^strongBlock1)(void) = strongBlock;
strongBlock1();
}
決議:這題主要是為了區分block的深淺拷貝問題,_LGBlock只是參考block底層原始碼,寫的一個自定義block,為的是便于修改block的一些屬性,上面demo方法運行會崩潰,因為strongBlock1參考的strongBlock,而strongBlock參考weakBlock,也就是說他們持有的是同一個block,而blc->invoke = nil,是把block中的一個屬性置空了,自然就會運行block崩潰,如果該用注釋的copy方法,就可以正常運行
例題3
問題:a能不能列印出來,weakBlock,strongBlock分別是什么型別block
- (void)blockDemo3{
int a= 10;
void(^__weak weakBlock)(void) = nil;
{
void(^__weak strongBlock)(void) = ^{
NSLog(@"---%d", a);
};
weakBlock = strongBlock;
NSLog(@"1 - %@ - %@",weakBlock,strongBlock);
}
weakBlock();
}
決議:可以,首先weakBlock,區域變數的作用域為blockDemo3方法中,而strongBlock也是一個堆疊block,而weakBlock弱參考了strongBlock,所以兩者都是堆疊block,雖然出了括號,但是因為weakBlock還沒出作用域,所以可以執行
例題4
問題同例題3
- (void)blockDemo3{
int a= 10;
void(^__weak weakBlock)(void) = nil;
{
void(^ strongBlock)(void) = ^{
NSLog(@"---%d", a);
};
weakBlock = strongBlock;
NSLog(@"1 - %@ - %@",weakBlock,strongBlock);
}
weakBlock();
}
決議:首先可以確定weakBlock和strongBlock都是堆block,而堆block的作用域就是在括號內,所以出了括號就釋放了,因此執行堆weakBlock()是會崩潰,當然從這里也可以看出堆疊的釋放記憶體的差異
例題5
問題:是否產生記憶體泄露
static ViewController *staticSelf;
- (void)viewDidLoad {
[self blockWeak_static];
}
- (void)blockWeak_static {
__weak typeof(self) weakSelf = self;
staticSelf = weakSelf;
}
決議:會,首先weakSelf和self是映射關系,弱參考,指向同一片記憶體空間,然而全域靜態變數staticSelf參考了weakSelf,其實也就是參考了這塊記憶體空間,但是全域靜態變數沒辦法釋放,所以self也釋放不掉
例題6
問題:是否產生記憶體泄露
@property (nonatomic, copy) YCXBlock doWork;
@property (nonatomic, copy) YCXBlock doStudent;
- (void)viewDidLoad {
[self block_weak_strong];
}
- (void)block_weak_strong {
__weak typeof(self) weakSelf = self;
self.doWork = ^{
__strong typeof(self) strongSelf = weakSelf;
weakSelf.doStudent = ^{
NSLog(@"%@", strongSelf);
};
weakSelf.doStudent();
};
self.doWork();
}
決議:會,可以把這個block代碼拆分看,首先按照我們的理解__weak和__strong,延遲了self的釋放是沒問題的,但是在weakSelf.doStudent的block中,再次參考了strongSelf,所以參考計數再次+1,所以最終self還是多參考了一次,導致最終無法釋放,
今天的分享就到次為止了,當然了后續如果有更多比較有趣的block題目也會補充進來,下一篇會對block的底層原始碼進行分析,敬請期待吧😄
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/295682.html
標籤:其他
