BlueAroma

[Crash] KVO_IS_RETAINING_ALL_OBSERVERS_OF_THIS_OBJECT_IF_IT_CRASHES_AN_OBSERVER_WAS_OVERRELEASED_OR_SMASHED 본문

내맘대로 프로그래밍/iOS

[Crash] KVO_IS_RETAINING_ALL_OBSERVERS_OF_THIS_OBJECT_IF_IT_CRASHES_AN_OBSERVER_WAS_OVERRELEASED_OR_SMASHED

BlueAroma 2017. 11. 9. 17:38


KVO_IS_RETAINING_ALL_OBSERVERS_OF_THIS_OBJECT_IF_IT_CRASHES_AN_OBSERVER_WAS_OVERRELEASED_OR_SMASHED.. 






GMSGoogleMap 과 GMUClusterManager를 사용하는 경우 발생.


빈번하게 맵을 오가는 경우에 항상은 아니고 간헐적으로 (또는 자주) Crash 가 발생합니다.



원인은  KVO ( Key Value Observer ) 의 추가 제거가 정상적으로 이루어지지 않아 발생하는 것으로 짐작됩니다.




그렇다면 그 문제는 어디서 발생하는 것일까요?



IOS에서 구글 맵. 클러스터를 사용하는 경우 아래와 같이 클러스터 매니져를 생성해서 이용하게 됩니다.


 - (void)viewDidLoad {

    

    [super viewDidLoad];


    //-- 맵 마커 클러스터링 기본 셋팅

    id<GMUClusterAlgorithm> algorithm = [[GMUNonHierarchicalDistanceBasedAlgorithm alloc] init];

    id<GMUClusterIconGenerator> iconGenerator = [[GMUDefaultClusterIconGenerator alloc] init];

    _renderer = [[CMClusterRenderer alloc] initWithMapView:_mapView

                                      clusterIconGenerator:iconGenerator];

    

    _clusterManager = [[GMUClusterManager alloc] initWithMap:_mapView algorithm:algorithm renderer:_renderer];

    [_clusterManager setDelegate:self mapDelegate:self];



}





문제는 해당 MapViewController(가칭)을 빈번하게 생성하여 Push Pop을 하게되는 경우.  

(예를들어 맵 -> 상세정보 -> 맵 -> 상세정보 등등..)


문제의메세지와 함께 어플이 종료됩니다.




GMUClusterManager.m 파일에서 문제의 소스를 보자면 아래와 같습니다.



static NSString *const kGMUCameraKeyPath = @"camera";


.....


- (instancetype)initWithMap:(GMSMapView *)mapView

                  algorithm:(id<GMUClusterAlgorithm>)algorithm

                   renderer:(id<GMUClusterRenderer>)renderer {


  if ((self = [super init])) {

    _algorithm = [[GMUSimpleClusterAlgorithm alloc] init];

    _mapView = mapView;

    _previousCamera = _mapView.camera;

    _algorithm = algorithm;

    _renderer = renderer;


        // 이 Observer가 NSKeyValueObservingOptionNew(KVO) 다 

        [_mapView addObserver:self

               forKeyPath:kGMUCameraKeyPath

                  options:NSKeyValueObservingOptionNew

                  context:nil];

  }


  return self;

}


- (void)dealloc {


        // KVO의 제거부분이 dealloc 함수에 들어있다.

        [_mapView removeObserver:self forKeyPath:kGMUCameraKeyPath];

}


.......

 




여담이지만... 애플이 ARC 를 도입하면서  dealloc을 직접 호출할 일이 없어지게 되었고. 

해당 dealloc 함수는  [super dealloc] 이 사라진 단순 형태만 남아있습니다.


ARC 가 처음 도입되고 나서  기존 개발자들은 (저를 포함하여..) 한동안 ARC를 사용하지 못했습니다..

이유는 정말 내가 원하는 타이밍에 메모리 해제 (dealloc)이 호출될 것인가?? 에 대한 믿음이 없었기 때문이죠.


남아있는 dealloc은 물론 언젠간 불리겠지만 원하는 타이밍에 빠릿빠릿 실행되지 않을 수 있습니다.





그렇담 이제 해결방법.


문제가 되는 KVO를 직접 관리한다면 문제가 깔끔하게 해결됩니다.

1. GMUClusterManager 에서 해당 옵저버를 제거합니다.


- (instancetype)initWithMap:(GMSMapView *)mapView

                  algorithm:(id<GMUClusterAlgorithm>)algorithm

                   renderer:(id<GMUClusterRenderer>)renderer {


  if ((self = [super init])) {

    _algorithm = [[GMUSimpleClusterAlgorithm alloc] init];

    _mapView = mapView;

    _previousCamera = _mapView.camera;

    _algorithm = algorithm;

    _renderer = renderer;


//    [_mapView addObserver:self

//               forKeyPath:kGMUCameraKeyPath

//                  options:NSKeyValueObservingOptionNew

//                  context:nil];

  }


  return self;

}


- (void)dealloc {

//    [_mapView removeObserver:self forKeyPath:kGMUCameraKeyPath];

}

 



2. GMUClusterManager 를 사용하는 MapViewController의 viewDidAppear: viewDidDisappear: 에서 직접 생성/제거 합니다.


........


- (void)viewDidAppear:(BOOL)animated {

    [super viewDidAppear:animated];


    @try {

        [_mapView addObserver:_clusterManager

                   forKeyPath:@"camera"      // kGMUCameraKeyPath = @"camera" 

                      options:NSKeyValueObservingOptionNew

                      context:nil];

        

    }@catch (NSException * e) {

        NSLog(@"Error: %@%@", [e name], [e reason]);

    }

}


- (void)viewDidDisappear:(BOOL)animated {

    [super viewDidDisappear:animated];

    

    @try {

          [self.mapView removeObserver:_clusterManager

                          forKeyPath:@"camera"];     // kGMUCameraKeyPath = @"camera"


    }@catch (NSException * e) {

        NSLog(@"Error: %@%@", [e name], [e reason]);

    }

}

 

........




위 방식대로 수정하면 더이상 해당 Crash는 발생하지 않게됩니다. (try catch는 2단 방어용...)

구글, 스택오버플로우 를 뒤지다가 해결법이 없어서 종일 삽질한 끝에 해결하였고..

혹시 같은 문제로 고생할 분들을 위해 글을 남깁니다.


댓글은 글쓴이에게 힘이 됩니다 :ㅇ









Comments