一. 單元測試覆蓋率&調(diào)試測試代碼
1.1 查看單元測試覆蓋率
打開開源項(xiàng)目SYTimer橄唬,如下圖所示
開源項(xiàng)目SYTimer介紹:
- SYTimer基于RunLoop Timer二次封裝
- 我們在不同頁面使用不同NSTimer的時(shí)候,多個(gè)NSTimer的啟動(dòng)時(shí)機(jī)不同榜揖。其實(shí)我們在底層使用一個(gè)NSTimer就夠用了,只需要控制NSTimer在不同啟動(dòng)時(shí)機(jī)運(yùn)行相應(yīng)代碼就可實(shí)現(xiàn)抗蠢。SYTimer就是使用一個(gè)RunLoop Timer在不同線程里提供一個(gè)比NSTimer更好的運(yùn)行機(jī)制举哟。
設(shè)計(jì)此項(xiàng)目需要考慮的問題?
- 使用一個(gè)Timer來運(yùn)行迅矛,需要對不同啟動(dòng)時(shí)機(jī)進(jìn)行排序
- 對啟動(dòng)時(shí)機(jī)進(jìn)行排序妨猩,使用堆排序會(huì)更好(堆排序分為 大頂堆 小頂堆)
我們在進(jìn)行單元測試的時(shí)候需要關(guān)注,堆排序在進(jìn)行不同timer排序時(shí)的單元測試覆蓋率秽褒?接下來測試代碼在進(jìn)行timer排序時(shí)調(diào)用了多少次壶硅?該怎么進(jìn)行測試?
選擇Edit Scheme -- Test -- Options销斟,如下圖所示
// SYHeapTest.m文件內(nèi)單元測試方法
- (void)testSimple {
// 堆排序的初始化庐椒,指定SYMaxHeap
SYHeap<NSNumber *>* h = [[SYHeap alloc] initWithHeapType:SYMaxHeap usingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
// 指定堆里面元素的排序規(guī)則
return [obj1 compare:obj2];
}];
[h addObject:@(1)];
[h addObject:@(3)];
[h addObject:@(2)];
XCTAssertEqual(@(3), [h removeRootObject]);
XCTAssertTrue([h checkHeapProperty]);
}
執(zhí)行單元測試方法testSimple,測試成功
查看文件的單元測試覆蓋率
展開SYHeap.mm文件蚂踊,查看文件內(nèi)方法的單元測試覆蓋率
點(diǎn)擊進(jìn)入SYHeap查看comparator:b: 方法
- 紅色代表未使用的代碼
- 綠色代表使用了的代碼
- 紅色注釋形狀约谈,代表此處代碼部分被使用
1.2 調(diào)試測試代碼
如果我們不了解堆排序的初始化方式,testSimple方法寫成如下形式
- (void)testSimple {
// SYMaxHeap
SYHeap<NSNumber *>* h = [[SYHeap alloc] init];
[h addObject:@(1)];
[h addObject:@(3)];
[h addObject:@(2)];
XCTAssertEqual(@(3), [h removeRootObject]);
XCTAssertTrue([h checkHeapProperty]);
}
執(zhí)行單元測試方法testSimple,報(bào)錯(cuò)如下
直接顯示了報(bào)錯(cuò)信息窗宇,并沒有給我們調(diào)試的機(jī)會(huì)措伐,此時(shí)我們可以添加如下斷點(diǎn)來進(jìn)行調(diào)試
再次執(zhí)行單元測試方法testSimple,斷點(diǎn)斷在了報(bào)錯(cuò)地方军俊,這時(shí)我們就可以進(jìn)行相關(guān)的調(diào)試
二. 集成XCTest與Unit測試
打開LoginApp工程侥加,引入SYCSSColor SYTimer兩個(gè)庫
// podfile文件配置
pod 'SYCSSColor'
pod 'SYTimer'
pod install之后會(huì)報(bào)警告,提示CocoaPods did not set the base configuration of your project already has a custom config set.
解決辦法:
// UnitTest.debug.xcconfig文件內(nèi)導(dǎo)入CocoaPods中的config文件
#include "Pods/Target Support Files/Pods-LoginApp/Pods-LoginApp.debug.xcconfig"
接下來再把SYTimer工程中的單元測試文件導(dǎo)入LoginApp工程(下圖選中的藍(lán)色文件)粪躬,最終工程目錄如下
現(xiàn)在我們想邊運(yùn)行工程邊測試担败,我們在ViewController.m文件中的兩個(gè)按鈕點(diǎn)擊事件中寫單元測試如下
// ViewController.m文件按鈕點(diǎn)擊事件
- (IBAction)testCSSColorTap:(id)sender {
LGXCTestCenter *center = [LGXCTestCenter testSuiteForTestCaseClassString:@"SYCSSColorTests"];
for (XCTest *test in center.tests) {
[test runTest];
}
}
- (IBAction)testTimerTap:(id)sender {
LGXCTestCenter *center = [LGXCTestCenter testSuiteForTestCaseClassString:@"SYHeapTest"];
for (XCTest *test in center.tests) {
[test runTest];
}
}
// LGXCTestCenter文件方法
#import "LGXCTestCenter.h"
@implementation LGXCTestCenter
+ (instancetype)testSuiteForTestCaseClassString:(NSString *)cls {
Class cl = NSClassFromString(cls);
if (cl) {
return [self testSuiteForTestCaseClass:cl];
}
return nil;
}
@end
// Run工程
// 點(diǎn)擊觸發(fā)testCSSColorTap方法,測試成功打印如下
Test Case '-[SYCSSColorTests testExample]' started.
2021-03-28 16:24:21.842481+0800 LoginApp[3808:11039066] rgb(26, 115, 0)
Test Case '-[SYCSSColorTests testExample]' passed (0.014 seconds).
// 點(diǎn)擊觸發(fā)testTimerTap方法镰官,測試成功打印如下
Test Case '-[SYHeapTest testAddAndRemoveRandomNumbers]' started.
Test Case '-[SYHeapTest testAddAndRemoveRandomNumbers]' passed (0.002 seconds).
Test Case '-[SYHeapTest testRemoveElement]' started.
Test Case '-[SYHeapTest testRemoveElement]' passed (0.000 seconds).
Test Case '-[SYHeapTest testSimple]' started.
Test Case '-[SYHeapTest testSimple]' passed (0.000 seconds).
Test Case '-[SYHeapTest testSortedDesc]' started.
Test Case '-[SYHeapTest testSortedDesc]' passed (0.001 seconds).
上面單元測試方法在SYCSSColorTests SYHeapTest兩個(gè)類中已經(jīng)寫好了提前,現(xiàn)在猜想能不能邊運(yùn)行工程,邊寫一些測試用例泳唠?
方向: 可以使用runtime來實(shí)現(xiàn)這一猜想
上面導(dǎo)入主工程的三個(gè)類SYCSSColorTests SYHeapTest SYTimerTests代碼參與了編譯狈网,意味著增加了代碼體積,現(xiàn)在想通過不同環(huán)境來規(guī)避掉已經(jīng)測試過的方法笨腥,以減小代碼體積拓哺,該怎么解決?
方案一:創(chuàng)建新的target脖母,不同target工程 Build Phases -- Compile Sources 中配置文件士鸥,把單元測試文件刪掉
方案二:使用宏判斷工程中有沒有導(dǎo)入XCTest來進(jìn)行判斷
XConfig官方文檔
三. XCTest與UI測試
打開LoginApp工程,我們來進(jìn)行UI測試
運(yùn)行testExample方法可以恢復(fù)我們之前點(diǎn)擊頁面進(jìn)行的UI測試
接下來我們分析生成的UI測試代碼
- (void)testExample {
XCUIApplication *app = [[XCUIApplication alloc] init];
[app launch];
// 從app中取出指定控件
XCUIElement *nameinputTextField = app.textFields[@"nameInput"];
XCUIElement *passwordInputTextField = app.secureTextFields[@"passwordInput"];
// 對控件進(jìn)行模擬點(diǎn)擊與賦值
[nameinputTextField tap];
[nameinputTextField typeText:@"Cat\n"];
[passwordInputTextField doubleTap];
[passwordInputTextField typeText:@"123"];
[app.buttons.staticTexts[@"登錄"] tap];
}
上面根據(jù)nameInput取出輸入框谆级,其中nameInput是我們Xib創(chuàng)建UITextField時(shí)設(shè)置的烤礁,如下圖
接下來我們探討怎么實(shí)現(xiàn)邊運(yùn)行邊進(jìn)行UI測試?
- 我們發(fā)現(xiàn)在進(jìn)行UI測試的時(shí)候肥照,會(huì)先創(chuàng)建一個(gè)LoginAppUITest的app脚仔,再由這個(gè)app調(diào)起我們的主工程LoginApp,一共創(chuàng)建了兩個(gè)app
- 上面的UI測試能否在主工程使用舆绎?我們修改testCSSColorTap點(diǎn)擊方法內(nèi)容如下
- (IBAction)testCSSColorTap:(id)sender {
XCUIApplication *app = [[XCUIApplication alloc] init];
[app launch];
XCUIElement *nameinputTextField = app.textFields[@"nameInput"];
XCUIElement *passwordInputTextField = app.secureTextFields[@"passwordInput"];
[nameinputTextField tap];
[nameinputTextField typeText:@"Cat\n"];
[passwordInputTextField doubleTap];
[passwordInputTextField typeText:@"123"];
[app.buttons.staticTexts[@"登錄"] tap];
}
// 運(yùn)行LoginApp玻侥,點(diǎn)擊按鈕,程序閃退
2021-03-28 20:23:11.033373+0800 LoginApp[4973:11129917] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'No target application path specified via test configuration: (null)'
// 原因是XCUIApplication只在UI測試target下才能有效
- 我們想讓UI測試在主工程起作用亿蒸,實(shí)現(xiàn)邊運(yùn)行邊進(jìn)行UI測試該怎么辦凑兰?
- 我們可以使用KIF框架,KIF代表Keep It Functional边锁,是一款iOS集成測試框架姑食。 通過利用操作系統(tǒng)為具有視覺障礙的用戶提供的輔助功能屬性,可以輕松實(shí)現(xiàn)iOS應(yīng)用程序的自動(dòng)化茅坛。
- KIF使用標(biāo)準(zhǔn)的XCTest測試目標(biāo)來構(gòu)建和執(zhí)行測試音半。
- KIF中UI測試與XCTest實(shí)現(xiàn)是有不同的
接下來我們來學(xué)習(xí)使用KIF框架
// podfile文件中配置则拷,導(dǎo)入KIF框架
pod 'SYCSSColor'
pod 'SYTimer'
pod 'KIF', '3.7.13', :configurations => ['Debug']
創(chuàng)建LGKIFTests.m文件,內(nèi)容如下
#import <XCTest/XCTest.h>
#import <KIF/KIF.h>
@interface LGKIFTests : KIFTestCase
@end
@implementation LGKIFTests
// 對應(yīng)通用的UI測試文件中的setUp方法
// 測試之前做一些初始化操作
- (void)beforeEach {
}
// 對應(yīng)tearDownWithError方法
// 測試之后做一些收尾工作
- (void)afterEach {
}
// 測試用例
- (void)testSuccessfulLogin {
// test 測試的標(biāo)準(zhǔn)
[tester enterText:@"Cat1237@example.com" intoViewWithAccessibilityLabel:@"nameInput"];
[tester enterText:@"Cat1237" intoViewWithAccessibilityLabel:@"passwordInput"];
[tester tapViewWithAccessibilityLabel:@"loginButton"];
}
@end
接下來我們修改testCSSColorTap內(nèi)容如下
- (IBAction)testCSSColorTap:(id)sender {
// 這里修改類名為LGKIFTests
LGXCTestCenter *suite = [LGXCTestCenter testSuiteForTestCaseClassString:@"LGKIFTests"];
for (XCTest *test in suite.tests) {
[test runTest];
}
}
// Run工程
// 點(diǎn)擊觸發(fā)testCSSColorTap方法曹鸠,測試成功打印如下
est Case '-[LGKIFTests testSuccessfulLogin]' started.
2021-03-28 20:54:11.362113+0800 LoginApp[5112:11151519] [TraitCollection] Class CKBrowserSwitcherViewController overrides the -traitCollection getter, which is not supported. If you're trying to override traits, you must use the appropriate API.
2021-03-28 20:54:12.213945+0800 LoginApp[5112:11151519] WARN: Main thread was blocked for more than 0.500000s after animations completed!
Test Case '-[LGKIFTests testSuccessfulLogin]' passed (20.319 seconds).
最后我們的單元測試與UI測試都實(shí)現(xiàn)了 邊運(yùn)行邊測試的目標(biāo)
探討方向: 使用OC運(yùn)行時(shí)來實(shí)現(xiàn)邊運(yùn)行煌茬,邊寫測試用例?
四. TDD與線程存儲(chǔ)數(shù)據(jù)
TDD是測試驅(qū)動(dòng)開發(fā)(Test-Driven Development)彻桃,是敏捷開發(fā)中的一項(xiàng)核心實(shí)踐和技術(shù)坛善,也是一種設(shè)計(jì)方法論,測試驅(qū)動(dòng)開發(fā)的步驟如下圖
- 寫一個(gè)失敗的測試用例
- 再讓這個(gè)測試用例通過
- 再去進(jìn)行重構(gòu)
按照上面步驟我們來做這樣一件事邻眷,在線程中存儲(chǔ)數(shù)據(jù)眠屎?比如斷點(diǎn)續(xù)傳 下載等
這里我們舉一個(gè)NSRunLoop的例子
// 我們從別的線程切換回來之后都可以使用以下兩句獲取當(dāng)前線程的RunLoop
[NSRunLoop currentRunLoop];
[NSRunLoop mainRunLoop];
// 從別的線程切換回來,RunLoop并沒有改變肆饶,線程對應(yīng)的RunLoop并不是重復(fù)創(chuàng)建改衩,其本質(zhì)就是在當(dāng)前線程中存儲(chǔ)RunLoop實(shí)例結(jié)構(gòu)體
接下來就讓我們一起來實(shí)現(xiàn)在線程中存儲(chǔ)數(shù)據(jù)(可以參考SYThreadSpecificVariable類)
前提: CFRunLoop與NSRunLoop本質(zhì)對面向?qū)ο蟮姆庋b并不友好,如果我們能基于CFRunLoop對SYTimer進(jìn)行面向?qū)ο蟮姆庋b驯镊,后面我們就可以按照面向?qū)ο蟮乃枷雭硎褂肦unLoop葫督。我們要實(shí)現(xiàn)類似NSRunLoop currentRunLoop的功能,就要在線程中存儲(chǔ)數(shù)據(jù)板惑。
打開LGTimer工程候衍,Cmd + N 創(chuàng)建類LGThreadSpecificVariable
// LGThreadSpecificVariable.h 文件內(nèi)容
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGThreadSpecificVariable : NSObject
- (instancetype)initWithValue:(id)value;
@property (nonatomic, strong, readonly) id currentValue;
@end
NS_ASSUME_NONNULL_END
// LGThreadSpecificVariable.m 文件內(nèi)容
#import "LGThreadSpecificVariable.h"
#import <pthread.h>
@interface LGThreadSpecificVariable() {
pthread_key_t _key;
id _value;
}
@end
@implementation LGThreadSpecificVariable
- (instancetype)initWithValue:(id)value
{
self = [super init];
if (self) {
int error = pthread_key_create(&_key, nil);
_value = value;
if (error != 0) {
NSAssert(error == 0, @"pthread_key_delete failed, error %d", error);
}
// 把數(shù)據(jù)存入線程
pthread_setspecific(_key, (__bridge_retained const void * _Nullable)(_value));
}
return self;
}
- (id)currentValue {
id data = (__bridge id)(pthread_getspecific(_key));
if (data) {
return data;
}
return nil;
}
@end
// 單元測試文件LGTimerTests.m中添加方法
// 需要導(dǎo)入頭文件#import "LGThreadSpecificVariable.h"
- (void)test_ThreadSpecificVariable {
LGThreadSpecificVariable *vc = [[LGThreadSpecificVariable alloc] initWithValue: self];
XCTestExpectation *expectation = [self expectationWithDescription:@"Test ThreadSpecificVariable"];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
XCTAssertNil(vc.currentValue);
[expectation fulfill];
});
XCTAssertNotNil(vc.currentValue);
[self waitForExpectationsWithTimeout:10 handler:nil];
}
// 測試test_ThreadSpecificVariable方法,Test Success
現(xiàn)在有一個(gè)問題洒放,當(dāng)創(chuàng)建的vc銷毀時(shí),線程中的數(shù)據(jù)依然存在滨砍,我們可以進(jìn)行相應(yīng)驗(yàn)證
// 創(chuàng)建LGThreadSpecificVariable+Private.h(匿名分類)文件往湿,內(nèi)容如下
#import "LGThreadSpecificVariable.h"
#import <pthread.h>
@interface LGThreadSpecificVariable ()
- (pthread_key_t)getKey;
@end
// LGThreadSpecificVariable.m文件添加getKey方法實(shí)現(xiàn)
- (pthread_key_t)getKey {
return _key;
}
// 我們在單元測試文件LGTimerTests.m中再次添加方法
// 需要導(dǎo)入頭文件#import "LGThreadSpecificVariable+Private.h"
- (void)test_ThreadSpecificVariable_delloc {
LGThreadSpecificVariable *vc = [[LGThreadSpecificVariable alloc] initWithValue: self];
pthread_key_t key = [vc getKey];
vc = nil;
id data = (__bridge id)(pthread_getspecific(key));
XCTAssertNil(data);
[self waitForExpectationsWithTimeout:10 handler:nil];
}
// 測試test_ThreadSpecificVariable_delloc方法,發(fā)現(xiàn)單元測試失敗惋戏,data有值
從架構(gòu)層面分析领追,線程中存數(shù)據(jù),數(shù)據(jù)應(yīng)與線程的生命周期綁定在一起
// nil參數(shù)响逢,應(yīng)該傳入函數(shù)指針绒窑,指針用于當(dāng)線程被銷毀時(shí),調(diào)用這個(gè)函數(shù)做一些操作
int error = pthread_key_create(&_key, nil);
- (instancetype)initWithValue:(id)value 方法內(nèi)
修改int error = pthread_key_create(&_key, nil);
為int error = pthread_key_create(&_key, destroy);
// LGThreadSpecificVariable.m文件添加銷毀方法
static inline void destroy(void* ptr) {
CFRelease(ptr);
}
// 單元測試文件LGTimerTests.m中添加方法
- (void)test_ThreadSpecificVariable_destroy {
LGThreadSpecificVariable *vc = [[LGThreadSpecificVariable alloc] initWithValue: self];
// 線程銷毀方法
pthread_exit(0);
pthread_key_t key = [vc getKey];
vc = nil;
id data = (__bridge id)(pthread_getspecific(key));
XCTAssertNil(data);
[self waitForExpectationsWithTimeout:10 handler:nil];
}
// 測試test_ThreadSpecificVariable_destroy方法舔亭,發(fā)現(xiàn)會(huì)調(diào)用LGThreadSpecificVariable.m文件的destroy方法
// 也可以在LGThreadSpecificVariable銷毀的方法中調(diào)用destroy((__bridge void *)(_value))
- (void)dealloc {
destroy((__bridge void *)(_value));
}
推薦看 AsyncDisplayKit庫源碼對RunLoop異步的使用
五. 調(diào)試debugserver
接下來我們來學(xué)習(xí)如何調(diào)試debugserver些膨?
打開SBAPI學(xué)習(xí)工程,我們之前在調(diào)試lldb的時(shí)候通過宏LLDB_DEBUGSERVER_PATH指定debugserver的路徑钦铺,如下圖所示
現(xiàn)在請思考一個(gè)問題订雾,我們能否編譯自己的帶調(diào)試符號(hào)的debugserver?
- 只要帶調(diào)試符號(hào)矛洞,我們就可以通過可執(zhí)行文件找到源碼洼哎,debugserver源碼可以在llvm中找到,找到之后打開工程如下圖
- debugserver中最難的部分為codesign簽名,因?yàn)楫?dāng)前Mac OS要求使用的debugserver必須要有l(wèi)ldb_codesign證書簽名
- 系統(tǒng)證書里面默認(rèn)帶有l(wèi)ldb_codesign證書噩峦,但是我們自己編譯的debugserver不能使用lldb_codesign證書進(jìn)行簽名锭沟,所以需要我們自己創(chuàng)建lldb_codesign證書
// debugserver工程中腳本
if [ "${CONFIGURATION}" != BuildAndIntegration ]
then
if [ -n "${DEBUGSERVER_USE_FROM_SYSTEM}" ]
then
ditto "${DEVELOPER_DIR}/../SharedFrameworks/LLDB.framework/Resources/debugserver" "${TARGET_BUILD_DIR}/${TARGET_NAME}"
elif [ "${DEBUGSERVER_DISABLE_CODESIGN}" == "" ]
then
// 通過lldb_codesign證書往里面?zhèn)魅胍恍┳兞坑脕碚埱髾?quán)限
codesign -f -s lldb_codesign --entitlements ${SRCROOT}/../../resources/debugserver-macosx-entitlements.plist "${TARGET_BUILD_DIR}/${TARGET_NAME}"
fi
fi
根據(jù)上面腳本路徑找到debugserver-macosx-entitlements.plist 文件,這個(gè)文件就是對一些權(quán)限的請求
編譯debugserver工程识补,并把生成的可執(zhí)行文件放入SBAPI學(xué)習(xí)工程根目錄族淮,并進(jìn)行路徑配置如下圖所示
在main.mm文件的main(int argc, const char * argv[])方法下打斷點(diǎn),并運(yùn)行SBAPI學(xué)習(xí) Debug -- Attach to Process 頂部會(huì)顯示可能關(guān)聯(lián)的target李请,運(yùn)行完成之后瞧筛,斷點(diǎn)進(jìn)入debugserver源碼
此時(shí)就可以進(jìn)行SBAPI學(xué)習(xí) 與 debugserver源碼進(jìn)行聯(lián)調(diào)
六. block相關(guān)
Block的類型
- A. GlobalBlock
- 位于全局區(qū)
- 在Block內(nèi)部不使用外部變量,或者只使用靜態(tài)變量和全局變量
- B. MallocBlock
位于堆區(qū)导盅。
在Block內(nèi)部使用局部變量或者OC屬性较幌,并且賦值給強(qiáng)引用或者Copy修飾的變量 - C. StackBlock
位于棧區(qū)
與 MallocBlock一樣,可以在內(nèi)部使用局部變量或者OC屬性白翻。但是不能賦值給強(qiáng)引用或者 Copy修飾的變量
判斷以下代碼的正確乍炉,需要掌握
- 明確全局block 堆區(qū)block 棧區(qū)block 的區(qū)別
- 明確堆上 棧上 變量的區(qū)別
- 掌握block底層源碼
block代碼塊一
// 下面代碼在執(zhí)行過程中會(huì)不會(huì)報(bào)錯(cuò)?
- (void)blockStack_Stack {
int a;
void(^__weak weakBlock)(void) = nil;
{
int b = 2;
// 這里是一個(gè)stack block
void(^ __weak weakBlock1)(void) = ^{
NSLog(@"-----%d", b);
};
a = b;
// block是一個(gè)結(jié)構(gòu)體滤馍,結(jié)構(gòu)體 = 結(jié)構(gòu)體
weakBlock = weakBlock1;
}
// 只要weakBlock1在作用域內(nèi)沒有被銷毀岛琼,weakBlock就可以調(diào)用
weakBlock();
}
// 運(yùn)行工程,正常執(zhí)行
block代碼塊二
- (void)blockStack_malloc {
int a = 0;
void(^__weak weakBlock)(void) = nil;
{
//這是堆上的block
void(^__strong strongBlock)(void) = ^{
NSLog(@"---%d", a);
};
//這里跟上面block代碼塊一并無區(qū)別巢株,仍然是 結(jié)構(gòu)體 = 結(jié)構(gòu)體
weakBlock = strongBlock;
// 出了作用域后槐瑞,strongBlock會(huì)銷毀
// block_relase
// free
}
// 對 block 做 release 操作。
// block 在堆上阁苞,才需要 release困檩,在全局區(qū)和棧區(qū)都不需要 release.
// 先將引用計(jì)數(shù)減 1,如果引用計(jì)數(shù)減到了 0那槽,就將 block 銷毀
// void _Block_release(const void *arg)
// 堆上的變量已經(jīng)釋放
// free
weakBlock();
}
// 運(yùn)行工程悼沿,會(huì)報(bào)錯(cuò)Thread 1: EXC_BAD_ACCESS (code=1, address=0x10)
block代碼塊三
- (void)blockHeap {
int a = 0;
// stack block
void(^ __weak block)(void) = ^{
NSLog(@"---%d", a);
};
dispatch_block_t dispatch_block = ^{
// 調(diào)用棧上block
block();
};
// 延遲3秒調(diào)用dispatch_block
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), dispatch_block);
}
// 運(yùn)行工程,會(huì)報(bào)錯(cuò)Thread 1: EXC_BAD_ACCESS (code=1, address=0x22)
// 修改代碼如下骚灸,就可以運(yùn)行成功
- (void)blockHeap {
int a = 0;
// stack block
void(^ __weak block)(void) = ^{
NSLog(@"---%d", a);
};
dispatch_block_t dispatch_block = ^{
// 調(diào)用棧上block
block();
};
// 延遲3秒調(diào)用dispatch_block
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), dispatch_block);
// 設(shè)置RunLoop過期時(shí)間
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
}
block代碼塊四
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
int a = 0;
void(^ __weak weakBlock)(void) = ^{
NSLog(@"-----%d", a);
};
// block轉(zhuǎn)換成結(jié)構(gòu)體形式
struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;
// strongBlock? 結(jié)構(gòu)體 = 結(jié)構(gòu)體 strongBlock為棧區(qū)block
void(^ __strong strongBlock)(void) = weakBlock;
blc->invoke = nil;
strongBlock();
}
// _LGBlock介紹
struct _LGBlock {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
// 函數(shù)指針糟趾,上面weakBlock代碼塊中代碼就保存在函數(shù)指針內(nèi)
LGBlockInvokeFunction invoke;
struct _LGBlockDescriptor1 *descriptor;
};
// 運(yùn)行工程,會(huì)報(bào)錯(cuò)Thread 1: EXC_BAD_ACCESS (code=1, address=0x0)
// 現(xiàn)在修改代碼如下
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
int a = 0;
void(^ __weak weakBlock)(void) = ^{
NSLog(@"-----%d", a);
};
struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;
// strongBlock為堆區(qū)block甚牲,與weakBlock是兩個(gè)不同的block
void(^ __strong strongBlock)(void) = [weakBlock copy];
// 棧區(qū)block置為nil义郑,malloc block并不影響
blc->invoke = nil;
// 不報(bào)錯(cuò) strongBlock malloc
strongBlock();
}
// 運(yùn)行工程,正常執(zhí)行
block代碼塊五
// 下面方法都在ViewController.m中
static ViewController *staticSelf_;
- (void)blockWeak_static {
// 把我們的self放入弱引用表里
__weak typeof(self) weakSelf = self;
// 再從弱引用表里取出self
staticSelf_ = self;
}
// 這里產(chǎn)生了循環(huán)引用
block代碼塊六
// 請求下面網(wǎng)址非常慢丈钙,請問self會(huì)不會(huì)立馬釋放魔慷?
// malloc block -> 捕獲變量
// 如果是__weak修飾的變量,捕獲之后self引用計(jì)數(shù)不會(huì)加1
// __strong修飾的變量著恩,捕獲之后self引用計(jì)數(shù)才會(huì)加1
- (void)block_weak_strong {
[[[NSURLSession sharedSession] dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.raywenderlich.com"]] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// 這里的self相當(dāng)于__strong來修飾院尔,只有當(dāng)block執(zhí)行完成之后蜻展,引用計(jì)數(shù)才會(huì)減1
NSLog(@"%@", self);
}] resume];
}
// 運(yùn)行工程,會(huì)發(fā)現(xiàn)ViewController延遲銷毀
// 現(xiàn)在修改代碼如下
- (void)block_weak_strong {
__weak typeof(self) weakSelf = self;
[[[NSURLSession sharedSession] dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.raywenderlich.com"]] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// 請問執(zhí)行到dispatch_after邀摆,能否正常打印strongSelf纵顾?
__strong typeof(self) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", strongSelf);
});
}] resume];
}
// 運(yùn)行工程,會(huì)發(fā)現(xiàn)不會(huì)打印栋盹。原因是執(zhí)行到dispatch_after self已經(jīng)被釋放了
// 繼續(xù)修改代碼如下施逾,這樣會(huì)導(dǎo)致強(qiáng)引用
- (void)block_weak_strong {
// 導(dǎo)致強(qiáng)引用,blcok捕獲變量是通過傳遞的方式捕獲
self.doWork = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", self);
});
};
self.doWork();
}
// 接下來我們繼續(xù)修改如下例获,會(huì)不會(huì)正常打雍憾睢?
- (void)block_weak_strong {
__weak typeof(self) weakSelf = self;
self.doWork = ^{
// 強(qiáng)制持有self榨汤,使用weakSelf意味著從弱引用表里取出self
__strong typeof(self) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", strongSelf);
});
};
self.doWork();
}
// 運(yùn)行工程蠕搜,等待5秒鐘會(huì)發(fā)現(xiàn)打印