ㄷㅣㅆㅣ's Amusement

iOS/Objective-c, code block 내에서 self 다루기 본문

Programming/iOS

iOS/Objective-c, code block 내에서 self 다루기

ㄷㅣㅆㅣ 2015. 11. 19. 21:00

iOS/Objective-c, code block 내에서 self 다루기



1. code block을 받는 함수

 - Java/Android에서는 interface로 listener를 만들어 결과를 받는것이 일반적이지만, objective-c/iOS 에서는 코드블럭이나 델리게이트를 통해 주고받는것이 일반적이다.

  다음 예제코드는 iOS 앱에서 캘린더에대한 접근 권한을 요청하는 함수이다. 캘린더 접근권한을 요청하면 사용자에게 팝업이 제공되는데, 이때에 사용자가 "확인"버튼을 누를때까지 메인 스레드가 정지해서는 안되기 때문에 코드블럭을 넘겨받아 처리하였다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- (void)requestAccessToEvents:(void (^)(BOOL bGranted))complete {
    BOOL needsToRequestAccessToEventStore = NO;
    EKAuthorizationStatus authorizationStatus = EKAuthorizationStatusAuthorized;
    if ([[EKEventStore class] respondsToSelector:@selector(authorizationStatusForEntityType:)]) {
        authorizationStatus = [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent];
        needsToRequestAccessToEventStore = (authorizationStatus == EKAuthorizationStatusNotDetermined);
    }
 
    // 기존에 권한 설정을 하여 바로 리턴할 수 있는지 체크
    if (needsToRequestAccessToEventStore) {
        // 사용자에게 물어봐야 하는 경우.
        [self.eventManager.eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
            if (granted) {
                NSLog(@"event just granted");
                complete(YES);
            }
        }];
    } else if (authorizationStatus == EKAuthorizationStatusAuthorized) {
        NSLog(@"event granted");
        complete(YES); // 기존에 승인한 경우
    } else {
        // TODO : Access denied popup
        NSLog(@"event access denied");
        complete(NO); // 기존에 거절한 경우
    }
}
 
cs

 - 코드블럭을 넘겨받는 함수는 (void (^)(BOOL bGranted))complete 와 같은 형태로 선언하고, 함수 안에서는 JAVA의 리스너처럼 적절한 곳에서 실행(호출)만 해주면 된다.


2. code block을 넘기는 부분

 - granted를 체크하여 캘린더 백업파일과 실제 시스템 사이의 diff count를 체크하는 로직을 구현한다면 다음과 같다.

   위에서 설명한 것처럼 메인스레드가 block되면 안되기 때문에 dispatch_async를 사용하여 다른 쓰레드에서 동작하도록 하였다.

1
2
3
4
5
6
7
8
9
10
11
- (void)eventDiff {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // calculate differents between system and file
        [[PhoneDataManager sharedInstance] requestAccessToEvents:^(BOOL bGranted) {
            if (bGranted) {
            [self calculateEventDiffCnt];
            }
        }];
    });
}
 
cs

 - 그런데 이때에 주의할 점은 코드블럭 안에 self라는 strong reference가 사용되었다는 점이다. 이것은 error는 아닐지라도 memory leak을 유발(순환참조)하므로 xcode는 알아서 warning(capturing self strongly in this block is likely to lead to a retain cycle)을 나타내준다.

   이를 방지하려면 코드는 다음과 같이 작성되어야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)eventDiff {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      // warning : capturing self strongly in this block is likely to lead to a retain cycle
    // 코드를 넘기는 과정에서 self 또는 self property들을 사용하면  메모리 릭이 발생한다.
    // you can't refer to self or properties on self from within a block that will be strongly retained by self.
    // 따라서 다음과 같이 우회하여 사용하는것이 바람직하다.
    // You can get around this by creating a weak reference to self before accessing timerDisp inside the block:
    __weak typeof(self) weakSelf = self;
 
        // calculate differents between system and file
        [[PhoneDataManager sharedInstance] requestAccessToEvents:^(BOOL bGranted) {
            if (bGranted) {
            [weakSelf calculateEventDiffCnt];
            }
        }];
    });
}
 
cs


objective-c/iOS는 ARC를 이용하여 메모리 관리를 하기 때문에 순환참조같은 memory free에 걸림돌이 되는 것은 풀어서 정리해야하는데, 다행히도 xcode가 그런 것은 귀신같이 찾아서 warning을 띄워준다... 쵸 고맙 ㅋㅋ


그나저나 생산성이 어쩌구 저쩌구하면서 ipad pro는 왜 iOS를 탑재시킨건지 모르겠다.. 벤치마크 보니 맥북 에어보다도 성능이 좋은데.... 그러면 뭘하나.. xcode도 안돌아가는데... ㅡ,.ㅡ;;

2 Comments
댓글쓰기 폼