iOS 에서 다 중 스 레 드 의 고전 충돌 요약
iOS 붕 괴 는 iOS 개발 자 들 을 골 치 아 프 게 하 는 일이 다.앱 이 붕 괴 된 것 은 코드 에 문제 가 있다 는 것 을 설명 한다.이때 붕 괴 된 곳 을 어떻게 빨리 찾 느 냐 가 중요 하 다.디 버 깅 단 계 는 문제 가 발생 한 곳 을 쉽게 찾 을 수 있 지만 이미 출시 된 app 과 붕괴 보고 서 를 분석 하 는 것 은 비교적 번거롭다.
본 고 는 iOS 의 다 중 스 레 드 에 관 한 전형 적 인 붕 괴 를 정리 하고 자 합 니 다.다음은 더 이상 말 하지 않 겠 습 니 다.상세 한 소 개 를 해 보 겠 습 니 다.
0x0 Block 반전 의 붕괴
MRC 환경 에서 블록 을 사용 하여 다운로드 에 성공 한 그림 을 설정 합 니 다.self 가 풀 려 난 후,weak Self 는 야생 지침 이 되 었 고,이어서 비극 이 되 었 다.
__block ViewController *weakSelf = self;
[self.imageView imageWithUrl:@"" completedBlock:^(UIImage *image, NSError *error) {
NSLog(@"%@",weakSelf.imageView.description);
}];
0x1 다 중 스 레 드 에서 Setter 의 충돌Getter&Setter 를 많이 썼 습 니 다.단일 스 레 드 의 경우 문제 가 없습니다.하지만 다 중 스 레 드 의 경우 무 너 질 수 있다.왜냐하면imageView release]; 이 코드 는 두 번 실 행 될 수 있 습 니 다.oops!
UIKit 는 스 레 드 가 아니 므 로 메 인 스 레 드 가 아 닌 곳 에서 UIKit 를 호출 하 는 것 은 개발 단계 에서 전혀 문제 가 없 을 수 있 으 며 직접 테스트 를 면제 할 수 있 습 니 다.하지만 온라인 에 도착 하면 붕괴 시스템 은 모두 당신 의 붕괴 로그 일 수 있 습 니 다.Holy shit!
해결 방법:hook 을 통 해 setNeedsLayout,setNeedsDisplay,setNeedsDisplayInRect 를 통 해 현재 호출 된 스 레 드 가 주 스 레 드 인지 확인 합 니 다.
- (void)setImageView:(UIImageView *)imageView
{
if (![_imageView isEqual:imageView])
{
[_imageView release];
_imageView = [imageView retain];
}
}
0x2 더 많은 Setter 형식의 충돌property 의 속성,가장 많이 쓴 것 은 nonatomic 입 니 다.일반적인 상황 에서 도 문제 가 없습니다!
@interface ViewController ()
@property (strong,nonatomic) NSMutableArray *array;
@end
아래 코드 를 뛰 어보 면 보 실 수 있 습 니 다.malloc: error for object 0x7913d6d0: pointer being freed was not allocated
for (int i = 0; i < 100; i++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
self.array = [[NSMutableArray alloc] init];
});
}
대상 이 중복 되 었 기 때문이다.runtime 소스 코드 을 살 펴 보 겠 습 니 다.해결 방법:속성 성명 은 atomic 입 니 다.
더 흔히 볼 수 있 는 예:
if(handler == nil)
{
hander = [[Handler alloc] init];
}
return handler;
만약 에 A,B 두 스 레 드 가 if 문 구 를 동시에 방문 하면 handler == nil
조건 이 만족 하고 두 스 레 드 는 모두 다음 문장 으로 가서 인 스 턴 스 를 초기 화 합 니 다.이때 A 스 레 드 는 초기 화 를 완료 하고 값 을 부여 합 니 다(이 인 스 턴 스 는 a 라 고 합 니 다).그리고 계속 뒤로 이동 합 니 다.이때 B 스 레 드 는 초기 화 되 고 값 을 부여 하기 시 작 했 습 니 다(이 인 스 턴 스 는 b 라 고 합 니 다).handler 는 B 스 레 드 가 초기 화 된 대상 을 가리 키 고 A 초기 화 된 인 스 턴 스 a 는 인용 계수 가 1(0 으로 감소)줄 어 들 었 기 때문에 방출 되 었 습 니 다.그러나 A 스 레 드 에 서 는....코드 는 a 가 있 는 주 소 를 방문 하려 고 시도 합 니 다.이 주소 의 내용 은 방출 되 어 예측 할 수 없 게 되 어 포인터 가 됩 니 다.
문 제 는 또 하나의 관건 적 인 점 이 있다.한 대상 의 특정한 방법 을 호출 하 는 과정 에서 이 대상 의 인용 수 는 증가 하지 않 고 풀 려 나 면 후속 적 인 실행 과정 에서 이 대상 에 대한 방문 은 야생 지침[1]을 초래 할 수 있다.
Exception Type: SIGSEGV
Exception Codes: SEGV_ACCERR at 0x12345678
Triggered by Thread: 1
간단하게 자 물 쇠 를 넣 으 면 문 제 를 해결 할 수 있다.
@synchronized(self){
if(handler == nil)
{
hander = [[Handler alloc] init];
}
}
return handler;
0x3 다 중 스 레 드 에서 변수 에 대한 액세스
if (self.xxx) {
[self.dict setObject:@"ah" forKey:self.xxx];
}
여러분 은 이런 코드 를 처음 보 았 는데,정확 하 다 고 생각 하 시 겠 습 니까?키 를 설정 할 때 self.xxx 가 비 nil 로 판단 되 었 기 때문에 비 nil 이 아 닌 경우 에 만 후속 명령 을 수행 할 수 있 습 니 다.그러나 상기 코드 는 단일 라인 의 전제 하에 서 만 정확 하 다.만약 에 우리 가 상기 코드 가 현재 실행 중인 스 레 드 를 Thread A 라 고 가정 하면
if (self.xxx)
의 문 구 를 실행 한 후에 CPU 는 실행 권 을 Thread B 로 전환 시 켰 고 이때 Thread B 에서 self.xxx = nil
을 호출 했다.부분 변 수 를 사용 하면 이 문 제 를 해결 할 수 있다.
__strong id val = self.xxx;
if (val) {
[self.dict setObject:@"ah" forKey:val];
}
이렇게 하면 아무리 많은 스 레 드 가 self.xxx 를 수정 하려 고 시도 하 더 라 도 본질 적 인 val 은 기 존의 상 태 를 유지 하고 비 nil 의 판단 에 부합된다.0x4 dispatch_그룹 붕괴
dispatch_group_enter 와 leave 는 일치 해 야 합 니 다.그렇지 않 으 면 crash 가 됩 니 다.다 중 자원 을 다운로드 할 때 다 중 스 레 드 를 사용 하여 다운로드 하고 모두 다운로드 한 후에 사용자 에 게 알려 야 합 니 다.다운로드 시작,dispatchgroup_enter,다운로드 완료 dispatchgroup_leave 。 매우 간단 한 절차 이지 만 코드 가 어느 정도 복잡 하거나 제3자 라 이브 러 리 를 사 용 했 을 때 문제 가 생 길 수 있 습 니 다.
dispatch_group_t serviceGroup = dispatch_group_create();
dispatch_group_notify(serviceGroup, dispatch_get_main_queue(), ^{
NSLog(@"Finish downloading :%@", downloadUrls);
});
// t
[downloadUrls enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
dispatch_group_enter(serviceGroup);
SDWebImageCompletionWithFinishedBlock completion =
^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
dispatch_group_leave(serviceGroup);
NSLog(@"idx:%zd",idx);
};
[[SDWebImageManager sharedManager] downloadImageWithURL:[NSURL URLWithString: downloadUrls[idx]] options:SDWebImageLowPriority progress:nil completed:completion];
}];
다 중 스 레 드 를 사용 하여 동시 다운 로드 를 진행 합 니 다.모든 그림 을 다운로드 할 때 까지(실패 할 수 있 습 니 다)리 셋 을 진행 합 니 다.그 중에서 그림 다운 로드 는 SDWebImage 를 사용 합 니 다.충돌 하 는 장면 은 10 장의 그림 이 있 고 두 번 나 누 어 다운로드(A&B)합 니 다.그 중 B 조 안에 한 장의 그림 과 A 조 가 다운로드 한 그림 이 중복 되 었 다.A 조 가 대응 하 는 그룹 A,B 조 그룹 B 를 다운로드 한다 고 가정 합 니 다.다음은 SDWebImage 원본 코드 를 캡 처 합 니 다.
dispatch_barrier_sync(self.barrierQueue, ^{
SDWebImageDownloaderOperation *operation = self.URLOperations[url];
if (!operation) {
operation = createCallback();
// **** ****
self.URLOperations[url] = operation;
__weak SDWebImageDownloaderOperation *woperation = operation;
operation.completionBlock = ^{
SDWebImageDownloaderOperation *soperation = woperation;
if (!soperation) return;
if (self.URLOperations[url] == soperation) {
[self.URLOperations removeObjectForKey:url];
};
};
}
// **** ****
id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
}
SDWebImage 의 다운로드 기 는 URL 에 따라 NSOperation 맵 에 대응 하고 같은 URL 은 실행 되 지 않 은 NSOperation 에 매 핑 됩 니 다.A 조 그림 다운로드 가 완료 되면 같은 url 리 셋 은 Group A 가 아 닌 Group B 입 니 다.이때 그룹 B 의 계 수 는 1 이다.B 조 그림 을 모두 다운로드 한 후 종료 수 는 5+1 입 니 다.엔 터 의 횟수 는 5,leave 의 횟수 는 6 이기 때문에 붕 괴 됩 니 다!0x5 마지막 소지 자 방출 후 붕괴
대상 A 는 관리자 가 보유 하고 있 으 며 A 에서
[Manager removeObjectA]
을 호출 합 니 다.A 대상자 의 retainCount -1
,retainCount 가 0 일 때 대상자 A 는 이미 석방 되 기 시작 했다.removeObjectA 를 호출 한 뒤 곧바로 [self doSomething]
을 호출 하면 붕괴 된다.
- (void)finishEditing
{
[Manager removeObject:self];
[self doSomething];
}
이 경우 배열 이나 사전 에 대상 을 포함 하고 대상 의 마지막 소지 자 에서 발생 한다.대상 이 잘 처리 되 지 않 으 면 위의 붕괴 가 있 을 것 이다.또 하나의 경 우 는 배열 이나 사전 의 대상 이 풀 려 났 을 때 배열 을 옮 겨 다 니 거나 사전 의 값 을 가 져 오 면 무 너 지 는 것 이다.이런 상황 은 사람 을 매우 붕괴 시 킬 수 있다.왜냐하면 가끔 스 택 이 이 렇 기 때문이다.
Thread 0 Crashed:
0 libobjc.A.dylib 0x00000001816ec160 _objc_release :16 (in libobjc.A.dylib)
1 libobjc.A.dylib 0x00000001816edae8 __ZN12_GLOBAL__N_119AutoreleasePoolPage3popEPv :508 (in libobjc.A.dylib)
2 CoreFoundation 0x0000000181f4c9fc __CFAutoreleasePoolPop :28 (in CoreFoundation)
3 CoreFoundation 0x0000000182022bc0 ___CFRunLoopRun :1636 (in CoreFoundation)
4 CoreFoundation 0x0000000181f4cc50 _CFRunLoopRunSpecific :384 (in CoreFoundation)
5 GraphicsServices 0x0000000183834088 _GSEventRunModal :180 (in GraphicsServices)
6 UIKit 0x0000000187236088 _UIApplicationMain :204 (in UIKit)
7 Tmall4iPhone 0x00000001000b7ae4 main main.m:50 (in Tmall4iPhone)
8 libdyld.dylib 0x0000000181aea8b8 _start :4 (in libdyld.dylib)
이런 스 택 이 생 길 수 있 는 장면 은:Dictionary 를 풀 때,어떤 값(value)은 다른 코드 에 의 해 미리 풀 려 서 야 지침 이 되 었 습 니 다.이때 다시 풀 려 서 Crash 를 촉발 합 니 다.모든 Dictionary 가 풀 릴 때 모든 key/value 를 걸 수 있다 면,어떤 key/value 가 딱 맞 으 면 crash 가 발생 합 니 다.그러면 방금 걸 린 key/value 에 걸 립 니 다.
0x6 대상 의 방출 스 레 드 는 일 을 처리 하 는 스 레 드 와 일치 해 야 합 니 다.
대상 A 는 메 인 스 레 드 에서 Notification 사건 을 감청 합 니 다.만약 이 대상 이 다른 스 레 드 에 의 해 방출 된다 면.이때 대상 A 가 notification 과 관련 된 작업 을 수행 하고 있 으 면 대상 관련 자원 에 접근 하면 야 지침 이 되 고 crash 가 발생 합 니 다.
0x7 performSelector:withObject:afterDelay:
이 방법 을 사용 합 니 다.주 스 레 드 가 아니라면 현재 스 레 드 의 ruuloop 이 존재 하 는 지 확인 해 야 합 니 다.performSelectorxxx_after Delay 는 runlopp 에 의존 해 야 실행 할 수 있 습 니 다.또한
performSelector:withObject:afterDelay:
과 cancelPreviousPerformRequestsWithTarget
을 조합 할 때 조심해 야 한다.
__weak typeof(self) weakSelf = self;
[NSObject cancelPreviousPerformRequestsWithTarget:self];
if (!weakSelf)
{
//NSLog(@"self ");
return;
}
[self doOther];
총결산이상 은 이 글 의 전체 내용 입 니 다.본 논문 의 내용 이 여러분 의 학습 이나 업무 에 어느 정도 참고 학습 가치 가 있 기 를 바 랍 니 다.궁금 한 점 이 있 으 시 면 댓 글 을 남 겨 주 셔 서 저희 에 대한 지지 에 감 사 드 립 니 다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Swift의 패스트 패스Objective-C를 대체하기 위해 만들어졌지만 Xcode는 Objective-C 런타임 라이브러리를 사용하기 때문에 Swift와 함께 C, C++ 및 Objective-C를 컴파일할 수 있습니다. Xcode는 S...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.