原文鏈接:https://www.debugger.wiki/article/html/1570775013637318
methodWillSetError會去異步設(shè)置error的值肠缨,然后另外一個地方在error設(shè)置后去訪問error的值。
實際上現(xiàn)在新版的Xcode已經(jīng)會對
*error = [NSError errorWithDomain:@"domain" code:1 userInfo:nil];
進行警告
Block captures an autoreleasing out-parameter, which may result in use-after-free bugs
那么這個警告是什么意思呢闻书?如何解決這個問題脑慧?
實際上這個方法
- (void)methodWillSetError:(NSError **)error group:(dispatch_group_t)group
的error闷袒,雖然沒有明確指定內(nèi)存的修飾符(strong, weak, autoreleasing,但是如果你直接定義NSError **error的臨時變量囊骤,在arc下xcode會編譯失敗,要求你明確指定內(nèi)存關(guān)系)但是編譯器會默認轉(zhuǎn)成NSError * __autoreleasing*,而在block中捕獲一個__autoreleasing的out-parameter是很容易造成錯誤的宫屠。
解決方法:
1滑蚯、形參 NSError**error 顯式指定 __strong 修飾, __strong修飾后坤次,xcode警告已經(jīng)消失斥赋。
2、block內(nèi)部引用的對象洛波,下邊示例代碼可以證明骚露,即使使用__block修飾對象缚窿,block獲取到的值也是block代碼被加載時對象的值,之后對象值再改變误续,__block對象的值也不會跟著變的。
為什么會變和不變: 棧內(nèi)存歸系統(tǒng)管蹋嵌,局部變量指針在棧中栽烂,方法結(jié)束被釋放很正常,指針防堆中腺办,就不會被釋放了,block被程序加載的時刻书妻,block對象的指針地址被替換為一個堆地址躬拢,所以它在方法結(jié)束不會被系統(tǒng)釋放。
NSInteger i = 1;
__block NSInteger blockI = i;
printf("\n改變前的i:%ld",(long)i);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_async(dispatch_get_main_queue(), ^{
printf("\n改變后的i:%ld",(long)blockI);
});
});
i = 2;
(這段代碼有些問題工猜,晚些修復(fù)馅袁,不影響下邊的方法)
methodWillSetError 方法傳入的error是雙層指針,對*error賦值改變的是error對應(yīng)的內(nèi)存地址犹褒,局部變量在當(dāng)前方法結(jié)束時被釋放弛针,所以已經(jīng)沒有指向error對象地址的指針了,那賦值完成后局部變量error的內(nèi)存地址會立即被覆蓋嗎宙枷?如果不會茧跋,是否可以通知讀取error的內(nèi)存地址直接獲取error的值?
沒有指針诅病,那就不用指針,所以實現(xiàn)流程是:
對象 => 內(nèi)存地址 => 對象
核心代碼:
NSError *error = [NSError errorWithDomain:@"com.test.domain" code:100 userInfo:nil];
NSInteger *path;
NSInteger path2 = (NSInteger)&error;
path = (NSInteger *)path2;
NSError *b ;
memcpy((void*)&b, path, sizeof(NSError *));
NSLog(@"%@",b);
考題實現(xiàn)的完整代碼:
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
@interface TestObj : NSObject
@end
@implementation TestObj
- (void)methodWillSetError:(__strong NSError **)error group:(dispatch_group_t)group {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
*error = [NSError errorWithDomain:@"domain2" code:1 userInfo:@{@"test2":@(303)}];
NSLog(@"%@", *error);
dispatch_group_leave(group);
});
}
@end
void testBlockAndAutoReleasePool() {
NSLog(@"Hello, World!");
NSError *error = nil;
NSLog(@"%p", &error);
TestObj *testObj = [TestObj new];
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[testObj methodWillSetError:&error group:group];
NSInteger *path;
NSInteger path2 = (NSInteger)&error;
path = (NSInteger *)path2;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"%@", error);
NSError *b ;
memcpy((void*)&b, path, sizeof(NSError *));
NSLog(@"%@",b);
});
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
testBlockAndAutoReleasePool();
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop run];
}
return 0;
}