例子一
堆棧信息
根據(jù)堆棧分析:
1,野指針
2搅荞,有對應(yīng)的堆棧
查看堆棧代碼,看那些有可能野指針:
+ (NSURLSessionDataTask *)httpAsyncPostWithUrl:(NSString *)urlHost
dictionary:(NSDictionary *)dictionary
userAgent:(NSString *)userAgent
completionBlock:(BDHKNetworkServiceResponse)block {
[self setupNetworkService];
// 檢查網(wǎng)絡(luò)
if ([BDHKNetworkInfoManager currentNetworkStatus] == BDHKNetworkInfoStatusNotReachable) {
if (block) {
NSError *error = [NSError errorWithDomain:kBDHKNetworkingErrorDomain
code:kBDHKNetworkingUnreachableCode
userInfo:nil];
// [BDHKCoreDataCheck apiErrorCheckApi:urlHost tab:@"haokan" tag:@"haokan" videoType:@"video" vid:@"" error:error extraDict:@{}];
block(nil, error);
}
return nil;
}
if ([Pyramid.bdhk_commonParams boolUploadZid]) {
[Pyramid.bdhk_commonParams zid]; //更新zid
}
NSTimeInterval startTime = [[NSDate date] timeIntervalSince1970];
__block NSDictionary *timeDic = nil;
BDHKNetworkingResult resultBlock = ^(NSURLSessionTask * _Nullable task,
NSDictionary * _Nullable response,
NSError * _Nullable error) {
// passposrt下沉后已修改
if (!HK_isEmptyDictionary(response)) {
[[BDHKCorePassportService sharedInstance] updatePassportStatusWithLoginInfo:response];
}
// if (!HK_isEmptyDictionary(response)) {
// @try {
// id obj = [NSClassFromString(@"BDHKPassportService") performSelector:@selector(sharedInstance)];
// [obj performSelector:@selector(updatePassportStatusWithLoginInfo:) withObject:response afterDelay:0];
// } @catch (NSException *exception) {
//
// }
// }
if (error.code == kBDHKNetworkingUntrustCerErrorCode) {
bdhk_toast(@"檢測到代理連接,關(guān)閉代理后可正常使用");
NSDictionary *errorInfo = @{@"k": @"hk_stability",
@"v": @"untrust_user_cer",
@"url": urlHost};
[BDHKNewLogService send760PackLogWithLogExtra:@{@"extra": errorInfo}];
NSString * apiName = [dictionary.allKeys objectAtIndexCheck:0];
[BDHKCoreDataCheck apiErrorCheckApi:apiName?:urlHost tab:@"haokan" tag:@"haokan" videoType:@"video" vid:@"" error:error extraDict:errorInfo];
}
if (block) {
block(response, error);
}
// 打點
NSTimeInterval responseTime = [[NSDate date] timeIntervalSince1970];
BOOL isTurbo = [BDHKNetworking isTurboSession:task.bdhkSession];
NSInteger errorCode = error ? error.code : 0;
[BDHKNetWorkService apiRequestTime:startTime
type:isTurbo ? @"turbo" : @"native"
code:errorCode
message:error.description
response:task.response
dictionary:dictionary
responseObject:response
timeDic:timeDic
url:urlHost
responseTime:responseTime];
if (error && error.code != NSURLErrorCancelled) {
BOOL isEmpty = HK_isEmptyDictionary(response);
[BDHKNetWorkService pageErrorLogDic:dictionary
type:isEmpty ? @"2" : @"0"
error:error];
NSString * apiName = [dictionary.allKeys objectAtIndexCheck:0];
[BDHKCoreDataCheck apiErrorCheckApi:apiName?:urlHost tab:@"haokan" tag:@"haokan" videoType:@"video" vid:@"" error:error extraDict:@{}];
}
};
NSURLSessionDataTask *dataTask = nil;
if (@available(iOS 10.0, *)) {
dataTask = [BDHKNetworking postHost:urlHost
apiAndParameters:dictionary
userAgent:userAgent
completionBlock:[resultBlock copy]
metricsBlock:^(NSURLSessionTask * _Nullable task,
NSURLSessionTaskMetrics * _Nullable metrics) {
timeDic = [self dicFromMetrics:metrics];
}];
} else {
dataTask = [BDHKNetworking postHost:urlHost
apiAndParameters:dictionary
userAgent:userAgent
completionBlock:[resultBlock copy]];
}
if ([dictionary objectForKey:@"feed"] != nil) {
dataTask.priority = NSURLSessionTaskPriorityHigh; // 將feed請求調(diào)為最高優(yōu)先級
}
return dataTask;
}
分析所有參數(shù):
urlHost,dictionary构蹬,userAgent悔据, block, startTime科汗, timeDic,dataTask
以上所有參數(shù)怖亭,除了timeDic坤检,其他的都不存在多線程讀寫安全的問題,因為他們要不就是個真正的局部變量早歇,或者不存在一個線程在讀另一個線程在寫的可能
只有timeDic,在當(dāng)前函數(shù)的下方有賦值的寫操作晨另,但是在block里面有讀操作衅码,并且不能保證當(dāng)前函數(shù)的線程和block回調(diào)的線程是在同一個線程,所以是有造成多線程讀寫crash的
解決方案
對timeDic 讀寫進行加鎖操作
@synchronized (self) {
timeDic = [self dicFromMetrics:metrics];
}
讀操作這里用了copy,因為這個方法調(diào)用用到了timeDic割捅,沒法直接加鎖
如果強行加鎖只能把整個方法加鎖
@synchronized (self) {
[BDHKNetWorkService apiRequestTime:startTime
type:isTurbo ? @"turbo" : @"native"
code:errorCode
message:error.description
response:task.response
dictionary:dictionary
responseObject:response
timeDic:tmpTimeDic
url:urlHost
responseTime:responseTime];
}
這種加鎖方式風(fēng)險極高亿驾,因為你不知道這個方法實現(xiàn)內(nèi)部是否有耗時操作,如果有,這里就會造成阻塞郭蕉,所以把timeDic直接加鎖拷貝喂江,因為方法內(nèi)部沒有對timeDic修改,只是獲取內(nèi)容获询,沒有修改的操作,所以copy也不影響梢薪,最終修改方案如下:
NSDictionary *tmpTimeDic = nil;
@synchronized (self) {
tmpTimeDic = [timeDic copy];
}
[BDHKNetWorkService apiRequestTime:startTime
type:isTurbo ? @"turbo" : @"native"
code:errorCode
message:error.description
response:task.response
dictionary:dictionary
responseObject:response
timeDic:tmpTimeDic
url:urlHost
responseTime:responseTime];
Q&A
多線程同時讀寫為什么會造成crash:
1尝哆,基本數(shù)據(jù)類型(非指針類型):不會造成crash,就是讀取到的數(shù)據(jù)可能不對畜疾,不一致
2印衔,指針類型:由于指針類型是直接賦值的操作,多線程多寫瞎暑,a線程讀的時候与帆,b線程可能剛好給重新賦值,因此a線程拿到的指針就指向了一個異常內(nèi)存玄糟,造成crash