iOS KVO 관찰자 자동 제거 방법

문제.
KVO:Key-Value Observing,키 값 기반 관찰자.지정 한 대상 의 속성 이 수정 되면 대상 이 알림 을 받 을 수 있 는 메커니즘 을 제공 합 니 다.쉽게 말 하면 지 정 된 관찰 대상 의 속성 이 수 정 될 때마다 KVO 는 해당 관찰자 에 게 자동 으로 알 린 다 는 것 이다.
KVO 의 장점:속성 이 바 뀌 면 KVO 는 자동 으로 메시지 알림 을 제공 합 니 다.이렇게 개발 자 들 은 이러한 방안 을 스스로 실현 할 필요 가 없다.속성 이 바 뀔 때마다 메 시 지 를 보낸다.KVO 메커니즘 이 제공 하 는 가장 큰 장점 이다.이 방안 은 이미 명확 하 게 정의 되 어 프레임 워 크 급 지원 을 받 아 편리 하 게 사용 할 수 있 기 때문이다.개발 자 는 코드 를 추가 할 필요 가 없고 자신의 관찰자 모델 을 설계 할 필요 가 없 으 며 공사 에서 직접 사용 할 수 있다.그 다음으로 KVO 의 구조 가 매우 강해 서 여러 관찰자 가 같은 속성 과 관련 된 값 을 관찰 하 는 것 을 쉽게 지원 할 수 있다.
그러나 우 리 는 KVO 모드 를 사용 하여 특정한 속성 을 감청 할 때 Observer 는 필요 할 때 제거 해 야 한 다 는 것 을 잘 알 고 있 습 니 다.그렇지 않 으 면 App 은 반드시 Crash 가 될 것 입 니 다.이 문 제 는 좀 귀 찮 습 니 다.가끔 Observer 를 제거 하 는 코드 를 쓰 는 것 을 잊 어 버 리 기 때 문 입 니 다.
나 는 줄곧 이런 효 과 를 원한 다.
감청 만 하고 감청 방법 을 처리 합 니 다.신경 쓰 지 마 세 요.Observer 를 언제 제거 하 든 적시에 자동 으로 처리 할 수 있 습 니 다.
다행히도 그것 이 실 현 될 수 있 으 니 미리 보기:

@interface NSObject (SJObserverHelper)

- (void)sj_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;

@end

@interface SJObserverHelper : NSObject
@property (nonatomic, unsafe_unretained) id target;
@property (nonatomic, unsafe_unretained) id observer;
@property (nonatomic, strong) NSString *keyPath;
@property (nonatomic, weak) SJObserverHelper *factor;
@end

@implementation SJObserverHelper
- (void)dealloc {
 if ( _factor ) {
 [_target removeObserver:_observer forKeyPath:_keyPath];
 }
}
@end

@implementation NSObject (ObserverHelper)

- (void)sj_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath {
 
 [self addObserver:observer forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:nil];
 
 SJObserverHelper *helper = [SJObserverHelper new];
 SJObserverHelper *sub = [SJObserverHelper new];
 
 sub.target = helper.target = self;
 sub.observer = helper.observer = observer;
 sub.keyPath = helper.keyPath = keyPath;
 helper.factor = sub;
 sub.factor = helper;
 
 const char *helpeKey = [NSString stringWithFormat:@"%zd", [observer hash]].UTF8String;
 objc_setAssociatedObject(self, helpeKey, helper, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
 objc_setAssociatedObject(observer, helpeKey, sub, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end
프로젝트 원본 코드
한 걸음 한 걸음 이 루어 지 는 것 에 대해 말씀 드 리 겠 습 니 다.
초보적인 사고방식 실현:
대상 이 풀 려 나 기 전에 dealloc 방법 을 사용 하고 있 는 인 스 턴 스 변수 도 풀 려 난 다 는 것 을 잘 알 고 있 습 니 다.
저 는 이렇게 생각 합 니 다.감청 등록 을 할 때 self 와 Observer 에 임시 대상 을 연결 합 니 다.이들 이 인 스 턴 스 변 수 를 방출 할 때 저 는 이 시 기 를 빌려 임시 대상 의 dealloc 방법 에서 Observer 를 제거 하면 됩 니 다.
생각 은 좋 은 데,모든 종류 에 임시 대상 의 속성 을 추가 할 수 는 없 잖 아 요.그러면 어떻게 원래 의 종 류 를 바 꾸 지 않 고 임시 대상 과 연결 할 수 있 습 니까?
관련 속성
원래 의 종 류 를 바 꾸 지 않 으 면 이 때 는 반드시 Category 를 사용 해 야 합 니 다.시스템 프레임 워 크 에는 많은 분류 가 있 고 관련 속성 이 많 습 니 다.예 를 들 어 다음 그림 UIView 헤더 파일 180 번 째 줄 과 같 습 니 다.

위의 그림 에 따 르 면 NSObject 에 Category 를 추가 하고 property 를 추가 하여.m 에서 setter 와 getter 방법 을 실현 합 니 다.

#import <objc/message.h>
@interface NSObject (Associate)
@property (nonatomic, strong) id tmpObj;
@end
@implementation NSObject (Associate)
static const char *testKey = "TestKey";
- (void)setTmpObj:(id)tmpObj {
 // objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
 objc_setAssociatedObject(self, testKey, tmpObj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)tmpObj {
 // objc_getAssociatedObject(id object, const void *key)
 return objc_getAssociatedObject(self, testKey);
}
@end
명확 해,obbcsetAssociated Object 는 관련 속성의 setter 방법 이 고 obbcgetAssociated Object 는 관련 속성의 getter 방법 입 니 다.가장 주목 해 야 할 것 은 setter 방법 입 니 다.관련 속성 대상 을 추가 하 는 데 사용 해 야 하기 때 문 입 니 다.
초보 적 사고 탐색
초기 시도:
속성 은 언제든지 obbc 를 사용 할 수 있 습 니 다.setAssociated Object 가 연결 되 었 습 니 다.먼저 self 에 임시 대상 을 연결 하고 dealloc 에서 Observer 를 제거 하려 고 합 니 다.

@interface SJObserverHelper : NSObject
@property (nonatomic, weak) id target;
@property (nonatomic, weak) id observer;
@property (nonatomic, strong) NSString *keyPath;
@end
@implementation SJObserverHelper
- (void)dealloc {
 [_target removeObserver:_observer forKeyPath:_keyPath];
}
@end
- (void)addObserver {
 NSString *keyPath = @"name";
 [_xiaoM addObserver:_observer forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:nil]; 
 SJObserverHelper *helper_obj = [SJObserverHelper new];
 helper_obj.target = _xiaoM;
 helper_obj.observer = _observer;
 helper_obj.keyPath = keyPath;
 const char *helpeKey = [NSString stringWithFormat:@"%zd", [_observer hash]].UTF8String;
 //   
 objc_setAssociatedObject(_xiaoM, helpeKey, helper_obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
그래서 프로그램 을 흐뭇 하 게 실행 하 였 습 니 다.xiaom 이 nil 로 설정 되 었 을 때,펑 앱 크 래 시...

reason: 'An instance 0x12cd1c370 of class Person was deallocated while key value observers were still registered with it.
분석:임시 대상 의 dealloc,확실히 도 망 갔 어.왜 registered 가 있 지?그래서 저 는 임시 대상 의 dealloc 에서 인 스 턴 스 변 수 를 target 으로 인쇄 하려 고 했 습 니 다.이것 이 바로 Crash 문제 입 니 다!
unsafe 시도unretained
위의 조작 을 통 해 우 리 는 self 가 석방 되 기 전에 자신 이 가지 고 있 는 관련 속성 을 먼저 방출 한 다 는 것 을 알 고 있 습 니 다.self 는 완전히 방출 되 지 않 았 지만 임시 대상 에서 target 은 nil 이 되 었 습 니 다.동시에 self 는 효과 적 입 니 다.그러면 어떻게 nil 이 아 닌 것 을 유지 합 니까?
OC 에 있 는 두 개의 수정자 weak 와 unsafe 를 봅 시다.unretained:
  • weak:소지 자 는 목 표를 retain 하지 않 습 니 다.목표 가 소각 되면 소지 자의 인 스 턴 스 변 수 는 비어 있 습 니 다
  • unsafe_unretained:소지 자 는 목 표를 retain 하지 않 습 니 다.목표 가 풀 린 후에 도 소지 자의 인 스 턴 스 변 수 는 이전의 메모리 공간(포인터)
  • 을 가리 킬 것 입 니 다.
    위 에서,unsafeunretained 는 우리 의 문 제 를 잘 해결 했다.그래서 나 는 다음 과 같은 수정 을 했다.
    
    @interface SJObserverHelper : NSObject
    @property (nonatomic, unsafe_unretained) id target;
    @property (nonatomic, unsafe_unretained) id observer;
    @property (nonatomic, strong) NSString *keyPath;
    @end
    프로그램 을 다시 실행 하 는 것 은 괜 찮 습 니 다.관찰자 가 제거 하 였 습 니 다.
    최종 실현
    문제점
    현재 우 리 는 self 가 풀 릴 때 자신 에 게 있 는 Observer 를 제거 하 는 방법 을 실 현 했 을 뿐이다.
    근 데 Observer 가 미리 풀 려 나 면 요?
    관련 속성 을 추가 하면 둘 은 임시 대상 을 동시에 보유 할 수 없고 그렇지 않 으 면 임시 대상 도 제때에 방출 되 지 않 습 니 다.
    좋아,하나 가 안 되 는 이상 각자 하 나 를 연결 하 자.
    
    - (void)addObserver {
     .....  
     SJObserverHelper *helper_obj = [SJObserverHelper new];
     SJObserverHelper *sub_obj = [SJObserverHelper new];
     sub_obj.target = helper_obj.target = _xiaoM;
     sub_obj.observer = helper_obj.observer = _observer;
     sub_obj.keyPath = helper_obj.keyPath = keyPath;
     const char *helpeKey = [NSString stringWithFormat:@"%zd", [_observer hash]].UTF8String;
     //   
     objc_setAssociatedObject(_xiaoM, helpeKey, helper_obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
     //   
     objc_setAssociatedObject(_observer, helpeKey, sub_obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    위 와 같이 곰 곰 이 생각해 보면 현저 한 문제 가 존재 합 니 다.두 개의 관련 속성 이 방출 되 는 동시에 두 번 관찰 하고 제거 하 는 작업 을 했 습 니 다.이 문 제 를 피하 기 위해 저 는 다음 과 같은 수정 을 했 습 니 다.
    
    @interface SJObserverHelper : NSObject
    @property (nonatomic, unsafe_unretained) id target;
    @property (nonatomic, unsafe_unretained) id observer;
    @property (nonatomic, strong) NSString *keyPath;
    @property (nonatomic, weak) SJObserverHelper *factor; // 1.      weak   
    @end
    
    @implementation SJObserverHelper
    - (void)dealloc {
     if ( _factor ) {
      [_target removeObserver:_observer forKeyPath:_keyPath];
     }
    }
    @end
    
    - (void)addObserver {
     ..... 
     SJObserverHelper *helper_obj = [SJObserverHelper new];
     SJObserverHelper *sub_obj = [SJObserverHelper new];
     sub_obj.target = helper_obj.target = _xiaoM;
     sub_obj.observer = helper_obj.observer = _observer;
     sub_obj.keyPath = helper_obj.keyPath = keyPath;
     // 2.    weak   
     helper_obj.factor = sub_obj; 
     sub_obj.factor = helper_obj;
     const char *helpeKey = [NSString stringWithFormat:@"%zd", [_observer hash]].UTF8String;
     //   
     objc_setAssociatedObject(_xiaoM, helpeKey, helper_obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
     //   
     objc_setAssociatedObject(_observer, helpeKey, sub_obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    이전 작업 에서 우 리 는 weak 수식 변 수 를 알 고 있 습 니 다.목표 가 풀 릴 때 소지 자의 인 스 턴 스 변 수 는 자동 으로 nil 로 설 치 됩 니 다.따라서 위의 dealloc 방법 에서 우 리 는 weak 가 인용 한 인 스 턴 스 변수 factor 가 비어 있 는 지 판단 하면 됩 니 다.
    뽑다
    이상 의 조작 을 통 해 우 리 는 가끔 Observer 를 제거 하 는 코드 를 쓰 는 것 을 잊 어 버 리 는 것 을 해결 할 수 있 습 니 다.이 제 는 추출 을 통 해 일반적인 도구 방법 으로 만 들 수 있 습 니 다.
    나 는 NSObject 의 Category 를 새로 만 들 었 고 다음 과 같은 방법 을 추가 했다.

    그리고 상술 한 실현 을 m 에 통합 시 켰 다.

    여기 서 앞으로- (void)sj_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath;이 방법 만 호출 하면 됩 니 다.제거 하면 임시 변수 에 맡 깁 니 다.
    총결산
    이상 은 이 글 의 전체 내용 입 니 다.본 논문 의 내용 이 여러분 의 학습 이나 업무 에 어느 정도 참고 학습 가치 가 있 기 를 바 랍 니 다.궁금 한 점 이 있 으 시 면 댓 글 을 남 겨 주 셔 서 저희 에 대한 지지 에 감 사 드 립 니 다.

    좋은 웹페이지 즐겨찾기