KVO子類的創(chuàng)建過程

KVO的實(shí)現(xiàn)原理是利用runtime動(dòng)態(tài)生成一個(gè)被觀察對(duì)象的子類具篇,重新子類的4個(gè)方法纬霞,實(shí)現(xiàn)通知監(jiān)聽者。

一栽连。子類的生成

1.runtime動(dòng)態(tài)生成類

先調(diào)用 objc_allocateClassPair函數(shù)

/* Adding Classes */

/** 
 * Creates a new class and metaclass.
 * 
 * @param superclass The class to use as the new class's superclass, or \c Nil to create a new root class.
 * @param name The string to use as the new class's name. The string will be copied.
 * @param extraBytes The number of bytes to allocate for indexed ivars at the end of 
 *  the class and metaclass objects. This should usually be \c 0.
 * 
 * @return The new class, or Nil if the class could not be created (for example, the desired name is already in use).
 * 
 * @note You can get a pointer to the new metaclass by calling \c object_getClass(newClass).
 * @note To create a new class, start by calling \c objc_allocateClassPair. 
 *  Then set the class's attributes with functions like \c class_addMethod and \c class_addIvar.
 *  When you are done building the class, call \c objc_registerClassPair. The new class is now ready for use.
 * @note Instance methods and instance variables should be added to the class itself. 
 *  Class methods should be added to the metaclass.
 *
 *  創(chuàng)建一個(gè)類或者元類
 *  superclass: 父類的名稱
 *  name : 創(chuàng)建的新的類名
 *  extraBytes:ivars 分配的字節(jié)
 *
 *  如果類名已經(jīng)存在险领,則返回nil,否則返回新的class
 *
 *  class_addMethod 添加方法
 *  class_addIvar 添加成員變量
 *  執(zhí)行完成之后需要調(diào)用objc_registerClassPair函數(shù)
 */
OBJC_EXPORT Class _Nullable
objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name, 
                       size_t extraBytes) 



/***********************************************************************
* objc_allocateClassPair
* fixme
* Locking: acquires runtimeLock
* 動(dòng)態(tài)創(chuàng)建一個(gè)類
**********************************************************************/
Class objc_allocateClassPair(Class superclass, const char *name, 
                             size_t extraBytes)
{
    Class cls, meta;
    const char *superName = object_getClassName(superclass);
    printf("\n objc_allocateClassPair: 父類:%s-----子類:%s\n",superName,name);
    
    
    // Fail if the class name is in use.
    // 查找名稱為name的類是否存在秒紧,如果存在绢陌,返回nil
    if (look_up_class(name, NO, NO)) return nil;

    mutex_locker_t lock(runtimeLock);

    // Fail if the class name is in use.
    // Fail if the superclass isn't kosher.
    // 父類非法,返回nil
    if (getClassExceptSomeSwift(name)  ||
        !verifySuperclass(superclass, true/*rootOK*/))
    {
        return nil;
    }

    // Allocate new classes.
    // 創(chuàng)建一個(gè)新類
    cls  = alloc_class_for_subclass(superclass, extraBytes);
    meta = alloc_class_for_subclass(superclass, extraBytes);
    
    // fixme mangle the name if it looks swift-y?
    objc_initializeClassPair_internal(superclass, name, cls, meta);

    return cls;
}

1.1先調(diào)用 objc_allocateClassPair函數(shù)中調(diào)用look_up_class熔恢,判斷需要?jiǎng)?chuàng)建的類是否存在

Class 
look_up_class(const char *name, 
              bool includeUnconnected __attribute__((unused)), 
              bool includeClassHandler __attribute__((unused)))
{
    if (!name) return nil;

    Class result;
    bool unrealized;
    {
        runtimeLock.lock();
        result = getClassExceptSomeSwift(name);
        // 判斷name是否存在
        unrealized = result  &&  !result->isRealized();
        if (unrealized) {
            result = realizeClassMaybeSwiftAndUnlock(result, runtimeLock);
            // runtimeLock is now unlocked
        } else {
            runtimeLock.unlock();
        }
    }

    if (!result) {
        // Ask Swift about its un-instantiated classes.

        // We use thread-local storage to prevent infinite recursion
        // if the hook function provokes another lookup of the same name
        // (for example, if the hook calls objc_allocateClassPair)

        auto *tls = _objc_fetch_pthread_data(true);

        // Stop if this thread is already looking up this name.
        for (unsigned i = 0; i < tls->classNameLookupsUsed; i++) {
            if (0 == strcmp(name, tls->classNameLookups[i])) {
                return nil;
            }
        }

        // Save this lookup in tls.
        if (tls->classNameLookupsUsed == tls->classNameLookupsAllocated) {
            tls->classNameLookupsAllocated =
                (tls->classNameLookupsAllocated * 2 ?: 1);
            size_t size = tls->classNameLookupsAllocated *
                sizeof(tls->classNameLookups[0]);
            tls->classNameLookups = (const char **)
                realloc(tls->classNameLookups, size);
        }
        tls->classNameLookups[tls->classNameLookupsUsed++] = name;

        // Call the hook.
        Class swiftcls = nil;
        if (GetClassHook.get()(name, &swiftcls)) {
            ASSERT(swiftcls->isRealized());
            result = swiftcls;
        }

        // Erase the name from tls.
        unsigned slot = --tls->classNameLookupsUsed;
        ASSERT(slot >= 0  &&  slot < tls->classNameLookupsAllocated);
        ASSERT(name == tls->classNameLookups[slot]);
        tls->classNameLookups[slot] = nil;
    }

    return result;
}

1.2 調(diào)用 objc_registerClassPair函數(shù)

/***********************************************************************
* objc_registerClassPair
* fixme
* Locking: acquires runtimeLock
 
 注冊(cè)一個(gè)類
**********************************************************************/
void objc_registerClassPair(Class cls)
{
    
    
    const char *clsName = object_getClassName(cls);

    
    if (strstr(clsName, "NSKVONotifying_Person") ) {
        printf("\nobjc_registerClassPair:%s\n",clsName);

    }

    
    mutex_locker_t lock(runtimeLock);

    checkIsKnownClass(cls);

    //如果類已經(jīng)存在脐湾,直接返回
    if ((cls->data()->flags & RW_CONSTRUCTED)  ||
        (cls->ISA()->data()->flags & RW_CONSTRUCTED)) 
    {
        _objc_inform("objc_registerClassPair: class '%s' was already "
                     "registered!", cls->data()->ro()->name);
        return;
    }
   
    
    // 必須調(diào)用過objc_allocateClassPair
    if (!(cls->data()->flags & RW_CONSTRUCTING)  ||  
        !(cls->ISA()->data()->flags & RW_CONSTRUCTING))
    {
        _objc_inform("objc_registerClassPair: class '%s' was not "
                     "allocated with objc_allocateClassPair!", 
                     cls->data()->ro()->name);
        return;
    }

    // Clear "under construction" bit, set "done constructing" bit
    cls->ISA()->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);
    cls->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);

    // Add to named class table.
    // 添加到table中
    addNamedClass(cls, cls->data()->ro()->name);
}

經(jīng)過上面兩步,一個(gè)新的子類會(huì)創(chuàng)建

二.KVO動(dòng)態(tài)生成子類堆棧分析

在main函數(shù)中的測(cè)試代碼

#import <Foundation/Foundation.h>
#import "Person.h"
#import "Observer.h"


Observer *observer;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSLog(@" 進(jìn)入 main 函數(shù)");
        observer = [[Observer alloc] init];
        
        Person *p = [[Person alloc] init];
        [p addObserver:observer forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
//
//        sleep(5);
//        [p removeObserver:observer forKeyPath:@"name"];
    }
    return 0;
}

給person對(duì)象的name屬性添加KVO監(jiān)聽
在 objc_allocateClassPair函數(shù)中打上斷點(diǎn)叙淌,可以看到如下堆棧

01.png

如圖01所示:
當(dāng)給一個(gè)類添加kvo后秤掌,runtime動(dòng)態(tài)生成類的堆棧

  1. [NSObject(NSKeyValueObserverRegistration) addObserver:forKeyPath:options:context:]
  1. [NSObject(NSKeyValueObserverRegistration) _addObserver:forProperty:options:context:]:
  1. NSKeyValueUnnestedProperty isaForAutonotifying]
  1. [NSKeyValueUnnestedProperty _isaForAutonotifying]

5._NSKeyValueContainerClassGetNotifyingInfo

  1. _NSKVONotifyingCreateInfoWithOriginalClass
  1. objc_allocateClassPair

第6步的匯編代碼


02.png

__NSSetObjectValueAndNotify 偽代碼

//偽代碼,僅供理解
void __NSSetObjectValueAndNotify(id self, SEL _cmd, id value) {
    //獲取額外的變量
    void *indexedIvars = object_getIndexedIvars(object_getClass(self));
    //加鎖
    pthread_mutex_lock(indexedIvars + 0x20);
    //從SEL獲取KeyPath
    NSString *keyPath = [CFDictionaryGetValue(*(indexedIvars) + 0x18), _cmd) copyWithZone:0x0];
    //解鎖
    pthread_mutex_unlock(indexedIvars + 0x20);
    
    //改變前發(fā)通知
    [self willChangeValueForKey:keyPath];
    //實(shí)現(xiàn)Setter方法
    IMP imp = class_getMethodImplementation(*indexedIvars, _cmd);
    (imp)(self, _cmd, value);
    //改變后發(fā)通知
    [self didChangeValueForKey:keyPath];
}

[NSObject addObserver:forKeyPath:options:context:] 偽代碼

//偽代碼鹰霍,僅供理解
void -[NSObject addObserver:forKeyPath:options:context:]
(void * self, void * _cmd, void * arg2, void * arg3, unsigned long long arg4, void * arg5) {
    pthread_mutex_lock(__NSKeyValueObserverRegistrationLock);
    *__NSKeyValueObserverRegistrationLockOwner = pthread_self();
    rax = object_getClass(self);
    rax = _NSKeyValuePropertyForIsaAndKeyPath(rax, arg3);
    [self _addObserver:arg2 forProperty:rax options:arg4 context:arg5];
    *__NSKeyValueObserverRegistrationLockOwner = 0x0;
    pthread_mutex_unlock(__NSKeyValueObserverRegistrationLock);
    
    return;
}



//偽代碼闻鉴,僅供理解
- (void *)_addObserver:(id)observer 
           forProperty:(NSKeyValueProperty *)property 
               options:(NSKeyValueObservingOptions)option 
               context:(void *)context {
    //需要注冊(cè)通知
    if (option & NSKeyValueObservingOptionInitial) {
        //獲取屬性名路徑
        NSString *keyPath = [property keyPath];
        //解鎖
        pthread_mutex_unlock(__NSKeyValueObserverRegistrationLock);
        //如果注冊(cè)了獲得新值,就獲取數(shù)值
        id value = nil;
        if (option & NSKeyValueObservingOptionNew) {
            value = [self valueForKeyPath:keyPath];
            if (value == nil) {
                value = [NSNull null];
            }
        }
        //發(fā)送注冊(cè)通知
        _NSKeyValueNotifyObserver(observer, keyPath, self, context, value, 
        0 /*originalObservable*/, 1 /*NSKeyValueChangeSetting*/);
        //加鎖
        pthread_mutex_lock(__NSKeyValueObserverRegistrationLock);
    } 
    //獲取屬性的觀察信息
    Info *info = __NSKeyValueRetainedObservationInfoForObject(self, property->_containerClass);
    //判斷是否需要獲取新的數(shù)值
    id _additionOriginalObservable = nil;
    if (option & NSKeyValueObservingOptionNew) {
        //0?x15沒有找到定義茂洒,猜測(cè)為保存是否可觀察的數(shù)組

        id tsd = _CFGetTSD(0x15);
        if (tsd != nil) {
            _additionOriginalObservable = *(tsd + 0x10);
        }
    }
    //在原有信息上生成新的信息
    Info *newInfo = __NSKeyValueObservationInfoCreateByAdding
    (info, observer, property, option, context, _additionOriginalObservable, 0, 1);
    //替換屬性的觀察信息
    __NSKeyValueReplaceObservationInfoForObject(self, property->_containerClass, info, newInfo);
    //屬性添加后遞歸添加關(guān)聯(lián)屬性
    [property object:self didAddObservance:newInfo recurse:true];
    //獲取新的isa
   // 如果一個(gè)對(duì)象是第一次被觀察孟岛,需要替換isa對(duì)象
    Class cls = [property isaForAutonotifying];
    if ((cls != NULL) && (object_getClass(self) != cls)) {
        //如果是第一次就替換isa
        object_setClass(self, cls);
    }
    //釋放觀察信息
    [newInfo release];
    if (info != nil) {
        [info release];
    }
    return;
}


03.png

給一個(gè)類動(dòng)態(tài)的添加method

// 給一個(gè)類添加方法
BOOL 
class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
    if (!cls) return NO;
    
    
    mutex_locker_t lock(runtimeLock);
    return ! addMethod(cls, name, imp, types ?: "", NO);
}
04.png

如圖4所示,動(dòng)態(tài)添加了_isKVOA

三. GNU addObserver 實(shí)現(xiàn)

// 添加觀察者
- (void) addObserver: (NSObject*)anObserver
      forKeyPath: (NSString*)aPath
         options: (NSKeyValueObservingOptions)options
         context: (void*)aContext
{
  GSKVOInfo             *info;
  GSKVOReplacement      *r;
  NSKeyValueObservationForwarder *forwarder;
  NSRange               dot;

  setup();
  [kvoLock lock];

  // Use the original class
 // 先保存原來的類
  r = replacementForClass([self class]);

  /*
   * Get the existing observation information, creating it (and changing
   * the receiver to start key-value-observing by switching its class)
   * if necessary.
   */
    
 // 獲取對(duì)象的觀察者信息
  info = (GSKVOInfo*)[self observationInfo];
  // 如果info 為空
  if (info == nil)
    {
      //創(chuàng)建觀察者信息
      info = [[GSKVOInfo alloc] initWithInstance: self];
      // 保存觀察者信息
      [self setObservationInfo: info];
      // [r replacement] 就是子類,將self設(shè)置為子類類型(修改isa指向)
      object_setClass(self, [r replacement]);
    }

  /*
   * Now add the observer.
   
   */
    // 如果是深層路徑
  dot = [aPath rangeOfString:@"."];
  if (dot.location != NSNotFound)
    {
      forwarder = [[NSKeyValueObservationForwarder alloc]
        initWithKeyPath: aPath
           ofObject: self
         withTarget: anObserver
        context: aContext];
      [info addObserver: anObserver
             forKeyPath: aPath
                options: options
                context: forwarder];
    }
  else
    {
      // 重新setter方法
      [r overrideSetterFor: aPath];
      [info addObserver: anObserver
             forKeyPath: aPath
                options: options
                context: aContext];
    }

  [kvoLock unlock];
}

三移除KVO

   [p removeObserver:observer forKeyPath:@"name"];
06

如圖6所示:當(dāng)移除KVO時(shí)渠羞,如果該對(duì)象沒有監(jiān)聽者斤贰,會(huì)銷毀NSKeyValueObservance

釋放對(duì)象

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}

GNU removeObserver 實(shí)現(xiàn)

/*
 * removes the observer
  移除監(jiān)聽者
 */
- (void) removeObserver: (NSObject*)anObserver forKeyPath: (NSString*)aPath
{
  GSKVOPathInfo *pathInfo;

  [iLock lock];
    // 獲取觀察者信息
  pathInfo = (GSKVOPathInfo*)NSMapGet(paths, (void*)aPath);
  if (pathInfo != nil)
    {
       // 移除path對(duì)應(yīng)的所有觀察者
      unsigned  count = [pathInfo->observations count];

      pathInfo->allOptions = 0;
      while (count-- > 0)
        {
          GSKVOObservation      *o;

          o = [pathInfo->observations objectAtIndex: count];
          if (o->observer == anObserver || o->observer == nil)
            {
              [pathInfo->observations removeObjectAtIndex: count];
              if ([pathInfo->observations count] == 0)
                {
                  NSMapRemove(paths, (void*)aPath);
                }
            }
          else
            {
              pathInfo->allOptions |= o->options;
            }
    }
    }
  [iLock unlock];
}
        observer = [[Observer alloc] init];
        Person *p = [[Person alloc] init];
        [p addObserver:observer forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    
        [p removeObserver:observer forKeyPath:@"name"];
        Class cls = objc_lookUpClass("NSKVONotifying_Person");
    
        const char *clsName = object_getClassName(cls);
        NSLog(@" ------- %s",clsName);

打印結(jié)果-------NSKVONotifying_Person
說明移除KVO之后,NSKVONotifying_Person沒有被銷毀

NSKVODeallocate 實(shí)現(xiàn)

使用hopper查看蘋果實(shí)現(xiàn)

int _NSKVODeallocate(int arg0, int arg1) {
    r13 = rdi;
    var_-48 = **___stack_chk_guard;
    rax = object_getClass(rdi);
    r12 = __NSKVOUsesBaseClassObservationInfoImplementationForClass(rax);
    rax = object_getIndexedIvars(rax);
    r14 = rax;
    rbx = class_getInstanceMethod(*rax, rsi);
    if (r12 == 0x0) goto loc_7fff207b448e;

loc_7fff207b4461:
    if (**___stack_chk_guard == var_-48) {
            rdi = r13;
            rsi = rbx;
            rax = method_invoke(rdi, rsi);
    }
    else {
            rax = __stack_chk_fail();
    }
    return rax;

loc_7fff207b448e:
    rax = __NSKeyValueRetainedObservationInfoForObject(r13, 0x0);
    *var_-72 = r13;
    *(var_-72 + 0x8) = rax;
    *(var_-72 + 0x10) = 0x0;
    __NSKeyValueAddObservationInfoWatcher(var_-72);
    r12 = __NSKVOObservationInfoOverridenObjectMayThrowOnDealloc(r13);
    method_invoke(r13, rbx);
    if (var_-64 == 0x0) goto loc_7fff207b4570;

loc_7fff207b44d1:
    r15 = dyld_get_program_sdk_version();
    if (r12 != 0x0) {
            r12 = (*_objc_msgSend)(var_-64, *0x7fff86b9d448) ^ 0x1;
    }
    else {
            r12 = 0x0;
    }
    *(int8_t *)var_-73 = 0x0;
    rax = CFPreferencesGetAppBooleanValue(@"NSKVODeallocateCleansUpBeforeThrowing", **_kCFPreferencesCurrentApplication, var_-73);
    rcx = 0x0;
    CMP(r15, 0x7ffff);
    rdx = r12 & 0xff;
    rsi = 0x0;
    asm{ cmova      esi, edx };
    rbx = (var_-73 == rcx ? 0x1 : 0x0) | (rax == 0x0 ? 0x1 : 0x0);
    if (rbx == 0x0) {
            rsi = rdx;
    }
    if (rsi != 0x0) goto loc_7fff207b45b4;

loc_7fff207b4542:
    if ((r15 < 0x80000) || (r12 != 0x0)) {
            _NSLog(@"An instance %p of class %@ was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object. Set a breakpoint on NSKVODeallocateBreak to stop here in the debu…", r13, *r14);
            _NSKVODeallocateBreak(r13);
    }
    goto loc_7fff207b4570;

loc_7fff207b4570:
    __NSKeyValueRemoveObservationInfoWatcher(var_-72);
    [var_-64 release];
    if (0x0 == 0x0) {
            rax = *___stack_chk_guard;
            rax = *rax;
            if (rax != var_-48) {
                    rax = __stack_chk_fail();
            }
    }
    else {
            rax = objc_exception_rethrow();
    }
    return rax;

loc_7fff207b45b4:
    r15 = (*_objc_msgSend)(var_-64, *0x7fff86b9a5e8);
    if (rbx == 0x0) {
            __NSKeyValueRemoveObservationInfoForObject(var_-72);
    }
    rax = (*_objc_msgSend)(@class(NSString), *0x7fff86b9a4b8);
    rax = (*_objc_msgSend)(@class(), *0x7fff86b9a700);
    rax = objc_exception_throw(rax);
    return rax;
}

DIS_KVC_KVO的DSKVODeallocate實(shí)現(xiàn)

void DSKVODeallocate(id object, SEL selector) {
    DSKeyValueObservationInfo *observationInfo = _DSKeyValueRetainedObservationInfoForObject(object, nil);
    
    ObservationInfoWatcher watcher = {object, observationInfo, NULL};
    _DSKeyValueAddObservationInfoWatcher(&watcher);
    
    DSKeyValueNotifyingInfo *notifyInfo = (DSKeyValueNotifyingInfo *)object_getIndexedIvars(object_getClass(object));
    
    Method originDellocMethod = class_getInstanceMethod(notifyInfo->originalClass, selector);
    ((id (*)(id,Method))method_invoke)(object, originDellocMethod);
    
    @try {
        if(watcher.observationInfo) {
            BOOL keyExistsAndHasValidFormat = false;
            BOOL cleansUpBeforeThrowing = false;
            
            cleansUpBeforeThrowing = (BOOL)CFPreferencesGetAppBooleanValue(CFSTR("NSKVODeallocateCleansUpBeforeThrowing"), kCFPreferencesCurrentApplication, (Boolean *)&keyExistsAndHasValidFormat);
            
            cleansUpBeforeThrowing = cleansUpBeforeThrowing && keyExistsAndHasValidFormat;
            
            if (dyld_get_program_sdk_version() > 0x7FFFF || cleansUpBeforeThrowing) {
                if (cleansUpBeforeThrowing) {
                    _DSKeyValueRemoveObservationInfoForObject(object, watcher.observationInfo);
                }
                [NSException raise:NSInternalInconsistencyException format:@"An instance %p of class %@ was deallocated while key value observers were still registered with it. Current observation info: %@", object, notifyInfo->originalClass, watcher.observationInfo];
            }
            else {
                NSLog(@"An instance %p of class %@ was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object. Set a breakpoint on NSKVODeallocateBreak to stop here in the debugger. Here's the current observation info:\n%@", object, notifyInfo->originalClass, watcher.observationInfo);
                DSKVODeallocateBreak(object);
            }
        }

    }
    @catch (NSException *exception) {
        [exception raise];
    }
    @finally {
        _DSKeyValueRemoveObservationInfoWatcher(&watcher);
        
        [watcher.observationInfo release];
    }    
}

kvo子類重寫dealloc的目的次询?

存在這樣一種情況荧恍,person對(duì)象已經(jīng)被銷毀了,但是觀察者沒有被移除屯吊。從上面的分析可以看到 NSKeyValueObservance就無(wú)法銷毀送巡。當(dāng)person對(duì)象銷毀時(shí),會(huì)根據(jù)isa指針找到-dealloc 方法雌芽,由于觀察者沒有移除授艰,isa指向 NSKVONotifying_Person類,這個(gè)類重寫了-dealloc方法世落,會(huì)做一些清除kvo的操作淮腾,所以需要重新子類的-dealloc 方法。

KVO源碼中添加觀察者時(shí)整體的大致流程是什么?

  1. 將keyPath屉佳、class等信息封裝成NSKeyValueProperty谷朝,分別解析一般屬性(@”aa”)、可計(jì)算屬性(@”@aa”)武花、屬性鏈(@”aa.bb.@cc.dd“)圆凰,進(jìn)行子類化,緩存在CFMutableSet中方便下次快速取出体箕。
  2. 將NSKeyValueProperty专钉、context、options累铅、observer等信息封裝成NSKeyValueObservance跃须,緩存在NSHashTable中。
  3. 倘若設(shè)置了NSKeyValueObservingOptionInitial選項(xiàng)娃兽,會(huì)在注冊(cè)觀察服務(wù)時(shí)調(diào)用一次觸發(fā)方法菇民。
  4. 動(dòng)態(tài)創(chuàng)建名為NSKVONotifying_+原來類名的新類,重寫其dealloc投储、_isKVOA方法第练,再重寫class方法,利用object_setClass()函數(shù)將其isa指針指向原先的類玛荞。
  5. 重寫willChangeValueForKey:和didChangeValueForKey:方法娇掏,重寫被觀察屬性的setter方法,在setter中先調(diào)用willChangeValueForKey:方法勋眯,然后調(diào)用父類的 setter 方法對(duì)成員變量賦值婴梧,之后再調(diào)用 didChangeValueForKey: 方法壁涎。
  6. didChangeValueForKey: 方法中會(huì)調(diào)用observeValueForKeyPath:ofObject:change:context:方法。

引用文章


刨根問底KVO原理
Principle and application of KVO
DIS_KVC_KVO
KVO

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末志秃,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子嚼酝,更是在濱河造成了極大的恐慌浮还,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闽巩,死亡現(xiàn)場(chǎng)離奇詭異钧舌,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)涎跨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門洼冻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人隅很,你說我怎么就攤上這事撞牢。” “怎么了叔营?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵屋彪,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我绒尊,道長(zhǎng)畜挥,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任婴谱,我火速辦了婚禮蟹但,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘谭羔。我一直安慰自己华糖,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布口糕。 她就那樣靜靜地躺著缅阳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪景描。 梳的紋絲不亂的頭發(fā)上十办,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音超棺,去河邊找鬼向族。 笑死,一個(gè)胖子當(dāng)著我的面吹牛棠绘,可吹牛的內(nèi)容都是我干的件相。 我是一名探鬼主播再扭,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼夜矗!你這毒婦竟也來了泛范?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤紊撕,失蹤者是張志新(化名)和其女友劉穎罢荡,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體对扶,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡区赵,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了浪南。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片笼才。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖络凿,靈堂內(nèi)的尸體忽然破棺而出骡送,到底是詐尸還是另有隱情,我是刑警寧澤絮记,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布各谚,位于F島的核電站,受9級(jí)特大地震影響到千,放射性物質(zhì)發(fā)生泄漏昌渤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一憔四、第九天 我趴在偏房一處隱蔽的房頂上張望膀息。 院中可真熱鬧,春花似錦了赵、人聲如沸潜支。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)冗酿。三九已至,卻和暖如春络断,著一層夾襖步出監(jiān)牢的瞬間裁替,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工貌笨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留弱判,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓锥惋,卻偏偏與公主長(zhǎng)得像昌腰,于是被迫代替她去往敵國(guó)和親开伏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • iOS 底層探索系列iOS 底層探索 - alloc & initiOS 底層探索 - calloc 和 isai...
    leejunhui閱讀 690評(píng)論 0 1
  • 序言在iOS開發(fā)中遭商,蘋果提供了許多機(jī)制給我們進(jìn)行回調(diào)固灵。KVO(key-value-observing)是一種十分有...
    陌尚煙雨遙閱讀 479評(píng)論 0 0
  • 目錄:1.KVC用法;2.KVC和對(duì)象的setter劫流、getter方法的區(qū)別怎虫;3.key和keyPath的區(qū)別;4...
    倫倫子_f7b3閱讀 574評(píng)論 0 1
  • [TOC] (一)KVO 初探 1. 基本用法 添加觀察 監(jiān)聽觀察 移除觀察 通知使用完之后困介,一定要移除,否則會(huì)有...
    修_遠(yuǎn)閱讀 256評(píng)論 0 5
  • 最近學(xué)習(xí)Runtime蘸际,順便總結(jié)一下在Objective-C中KVO使用到的Runtime機(jī)制座哩。 系統(tǒng)的KVO使用...
    aron1992閱讀 178評(píng)論 0 0