KVO 요약 및 FBKVOcontroller

10538 단어
KVO는 IOS의 강력하고 효과적인 메커니즘으로 한 대상의 속성이 변할 때 이 대상에 등록된 관찰자의 다른 대상에게 통지를 받을 수 있다.KVO를 사용하여 객체 속성의 변화를 관찰할 수 있습니다.예를 들어 드롭다운 리셋 효과를 실현하려면 KVO를 사용하여 UItableView의contenOffset 속성의 변화를 관찰하여 실현할 수 있다.
In order to be considered KVO-compliant for a specific property, a class must ensure the following:
  • The class must be key-value coding compliant for the property, as specified in Ensuring KVC Compliance.KVO supports the same data types as KVC, including Objective-C objects and the scalars and structures listed in Scalar and Structure Support
  • The class emits KVO change notifications for the property.
  • Dependent keys are registered appropriately (see Registering Dependent Keys).

  • 문서에서 언급한 바와 같이 하나의 클래스의 속성이 KVO를 지원하도록 해야 한다. 이 클래스는 속성에 대해 KVC를 만족시키고 이 클래스는 KVO에 대한 알림을 보낼 것이다.
    KVO 알림을 보낼 수 있는 두 가지 기술이 있습니다.
  • Automatic support: 자동 지원은 NSObject에서 제공하며 KVC를 지원하는 속성에는 기본적으로 사용할 수 있습니다.속성 변경 알림을 보내기 위해 추가 코드를 쓸 필요가 없습니다.
  • Manual change notification: 수동으로 알림을 보내려면 추가 코드가 필요합니다.

  • Automatic Change Notification


    NSObject provides a basic implementation of automatic key-value change notification. Automatic key-value change notification informs observers of changes made using key-value compliant accessors, as well as the key-value coding methods.
    NSObject는 자동으로 변경되는 알림을 제공합니다.자동 알림은 두 가지가 있는데 하나는 속성을 사용하는 setter 방법이고 하나는 KVC를 사용합니다.

    KVO의 원리


    KVO의 실현은runtime에 의존한다. 애플 문서에서 KVO의 실현을 언급한 적이 있다.
    Automatic key-value observing is implemented using a technique called isa-swizzling.
    The isa pointer, as the name suggests, points to the object's class which maintains a dispatch table. This dispatch table essentially contains pointers to the methods the class implements, among other data.
    When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class. As a result the value of the isa pointer does not necessarily reflect the actual class of the instance.
    애플은 KVO를 위해 이사-swizzling을 사용했다.피관찰자의 속성에 관찰자를 추가하면 피관찰자의 이사 지침이 바뀌어 원래의 진정한 클라스가 아닌 중간의 클라스를 가리킨다.구체적으로 말하면 새로운 클래스를 만들 것이다. 이 클래스는 피관찰자로부터 계승되고 관찰된 속성의 setter 방법을 다시 썼다. 다시 쓴 setter 방법은 원래의 setter 방법을 호출하는 전후에 모든 관찰자에게 변화할 가치가 있음을 알린다willChangeValueForKeydidChangeValueForKey로 알린다.isa의 바늘을 새로 만든 하위 클래스에 가리킨다.
    -(void)setName:(NSString *)newName{ 
    [self willChangeValueForKey:@"name"];    //KVO  
    [super setValue:newName forKey:@"name"]; //  
    [self didChangeValueForKey:@"name"];     //KVO }
    

    다음 테스트 코드를 살펴보십시오.
    @interface Person : NSObject
    @property (nonatomic, copy) NSString *name;
    @end
    
    @implementation Person
    
    @end
    
    #import 
    #import "Person.h"
    int main(int argc, char * argv[]) {
        Person *p = [[Person alloc] init];
        PrintDescriptionid(p);
        [p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
        PrintDescriptionid(p);
        return 0;
    }
    
    static NSArray *ClassMethodNames(Class c)
    {
        NSMutableArray *array = [NSMutableArray array];
        
        unsigned int methodCount = 0;
        Method *methodList = class_copyMethodList(c, &methodCount);
        unsigned int i;
        for(i = 0; i < methodCount; i++)
            [array addObject: NSStringFromSelector(method_getName(methodList[i]))];
        free(methodList);
        
        return array;
    }
    
    static void PrintDescriptionid( id obj)
    {
        NSString *str = [NSString stringWithFormat:
                         @"NSObject class %s
    Libobjc class %s
    Super Class %s
    implements methods ", class_getName([obj class]), class_getName(object_getClass(obj)), class_getName(class_getSuperclass(object_getClass(obj))), [ClassMethodNames(object_getClass(obj)) componentsJoinedByString:@", "]]; printf("%s
    ", [str UTF8String]); }

    log:
    // 
    NSObject class Person
    Libobjc class Person
    Super Class NSObject
    implements methods <.cxx_destruct name="" setname:="">
    
    // 
    NSObject class Person
    Libobjc class NSKVONotifying_Person
    Super Class Person
    implements methods 
    

    object_getClass(obj)는 obj 대상isa가 가리키는 클래스를 가져옵니다.log에서 알 수 있듯이 관찰자를 첨가한 후obj대상isa지침지향NSKVONotifying_Person이라는 종류는 부류Person였고NSKVONotifying_Person에서라는 몇 가지 방법을 실현했다.

    Manual Change Notification


    속성 알림을 완전히 제어하려면 다시 써야 합니다 automaticallyNotifiesObserversForKey:
    +(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key{
        BOOL automatic = NO;
        if ([key isEqualToString:@"name"]) {
            automatic = NO;
        }
        else {
            automatic = [super automaticallyNotifiesObserversForKey:key];
        }
        return automatic;
    }
    

    실행하기 전의 코드 로그:
    // KVO 
    Libobjc class Person
    Super Class NSObject
    implements methods <.cxx_destruct name="" setname:="">
    // KVO 
    NSObject class Person
    Libobjc class Person
    Super Class NSObject
    implements methods <.cxx_destruct name="" setname:="">
    

    이때도 NSKVONotifying_Person이라는 종류를 만들지 않을 것이다.수동으로 알림을 보내기 위해서, 값을 바꾸기 전에 willChangeValueForKey를 호출하고, 값을 바꾸면didChangeValueForKey를 호출합니다.

    KVO 사용:

    [self.tableView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
    

    이 방법은tableView에 관찰자를 추가하여tableView의content Offset 속성의 변화를 모니터링하는 것이다.이 방법은 방법의 호출자(self.tableView)와 관찰자(self)의 인용 계수를 증가시키지 않는다.
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
    {
        NSLog(@"%@", change);
    }
    

    관찰자 중에서 이 방법을 실현하고 피관찰자의 피관찰자의 속성이 변화할 때 이 방법을 사용한다.
    마지막으로 관찰자를 제거하는 것을 잊지 마세요.
    - (void)dealloc
    {
        [self.tableView removeObserver:self forKeyPath:@"contentOffset"];
    }
    

    FBKVOController


    페이스북에서 시작된 FBKVOcontroller 프레임워크는 KVO를 쉽게 사용할 수 있다.
    FBKVOcontroller를 사용하면 위의 코드를 다음과 같이 바꿀 수 있습니다.
     [self.KVOController observe:self.tableView keyPath:@"contentOffset" options:NSKeyValueObservingOptionNew block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary * _Nonnull change) {
            NSLog(@"%@", change);
     }];
    
    

    FBKVOcontroller에 NSObject의category가 있는데 NSObject에 두 가지 속성을 추가했습니다
    @property (nonatomic, strong) FBKVOController *KVOController;
    
    @property (nonatomic, strong) FBKVOController *KVOControllerNonRetaining;
    

    KVOController를 사용할 때 관찰된 대상에 대해 강한 인용을 하고 KVOcontrollerNonRetaining을 사용하면 관찰된 대상에 대해 약한 인용을 한다.
    FBKVOcontroller 클래스에는 다음과 같은 인스턴스 변수가 있습니다.
      NSMapTable *> *_objectInfosMap;
    

    NSMapTable의 키는 관찰된 대상을 저장하고 초기화 방법에서 강한 인용이나 약한 인용으로 설정할 수 있다.그것의value는 에 저장됩니다FBKVOInfo 대상은 주로 피관찰자의 키Path 등에 대한 정보이다.FBKVOcontroller 사용-observer:keyPath:options:block: 대상의 속성 변화를 관찰할 때 사용한FBKVOShared Controller라는 클래스는 하나의 예로 관찰자를 추가한 실례적인 방법입니다.
    - (void)observe:(id)object info:(nullable _FBKVOInfo *)info
    {
      if (nil == info) {
        return;
      }
    
      // register info
      pthread_mutex_lock(&_mutex);
      [_infos addObject:info];
      pthread_mutex_unlock(&_mutex);
    
      // add observer
      [object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];
    
      if (info->_state == _FBKVOInfoStateInitial) {
        info->_state = _FBKVOInfoStateObserving;
      } else if (info->_state == _FBKVOInfoStateNotObserving) {
        // this could happen when `NSKeyValueObservingOptionInitial` is one of the NSKeyValueObservingOptions,
        // and the observer is unregistered within the callback block.
        // at this time the object has been registered as an observer (in Foundation KVO),
        // so we can safely unobserve it.
        [object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
      }
    }
    

    또한 변화를 관찰하는 방법을 실현했다.
    - (void)observeValueForKeyPath:(nullable NSString *)keyPath
                          ofObject:(nullable id)object
                            change:(nullable NSDictionary *)change
                           context:(nullable void *)context
    {
      NSAssert(context, @"missing context keyPath:%@ object:%@ change:%@", keyPath, object, change);
    
      _FBKVOInfo *info;
    
      {
        // lookup context in registered infos, taking out a strong reference only if it exists
        pthread_mutex_lock(&_mutex);
        info = [_infos member:(__bridge id)context];
        pthread_mutex_unlock(&_mutex);
      }
    
      if (nil != info) {
    
        // take strong reference to controller
        FBKVOController *controller = info->_controller;
        if (nil != controller) {
    
          // take strong reference to observer
          id observer = controller.observer;
          if (nil != observer) {
    
            // dispatch custom block or action, fall back to default action
            if (info->_block) {
              NSDictionary *changeWithKeyPath = change;
              // add the keyPath to the change dictionary for clarity when mulitple keyPaths are being observed
              if (keyPath) {
                NSMutableDictionary *mChange = [NSMutableDictionary dictionaryWithObject:keyPath forKey:FBKVONotificationKeyPathKey];
                [mChange addEntriesFromDictionary:change];
                changeWithKeyPath = [mChange copy];
              }
              info->_block(observer, object, changeWithKeyPath);
            } else if (info->_action) {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
              [observer performSelector:info->_action withObject:change withObject:object];
    #pragma clang diagnostic pop
            } else {
              [observer observeValueForKeyPath:keyPath ofObject:object change:change context:info->_context];
            }
          }
        }
      }
    }
    

    KVOcontroller가 dealloc를 호출하면 관찰자가 제거됩니다.

    좋은 웹페이지 즐겨찾기