KVO下地実装原理

2392 ワード

原理:サブクラスを動的に生成し、setメソッドを書き換え、observeValueForKeyPathメッセージを配布する
KVOで使用されるruntimeインタフェースのカスタマイズ
//      root         
objc_allocateClassPair(Class superclass, const char *name, 
                                         size_t extraBytes) 
//   
void objc_registerClassPair(Class cls) 
//        
BOOL class_addMethod(Class cls, SEL name, IMP imp, 
                                 const char *types) 

//       
Class object_setClass(id obj, Class cls) 

//      
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

カスタムKVO例
#import "NSObject+WHJKVO.h"
#import 


@implementation NSObject (WHJKVO)
- (void)whj_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
    //    
    NSString * superClass = NSStringFromClass([self  class]);
    NSString *subClassName = [NSString stringWithFormat:@"whj_%@",superClass];
    const char *name = [subClassName UTF8String];
    Class myClass =  objc_allocateClassPair([self class], name, 0);
    //    
    objc_registerClassPair(myClass);
    //     
    NSString *methodName = [NSString stringWithFormat:@"set%@:",[keyPath capitalizedString]];
    objc_setAssociatedObject(self, @"methodName", methodName, OBJC_ASSOCIATION_RETAIN_NONATOMIC); //Person       
    class_addMethod(myClass, NSSelectorFromString(methodName), (IMP)myImp, "V@:@");

    //    
    object_setClass(self, myClass);//      self,    whj_Person 
    //       ,    observer,   observer    
    objc_setAssociatedObject(self, @"objc", observer, OBJC_ASSOCIATION_ASSIGN); //whj_Person       

}


void myImp(id self, SEL _cmd,id param) {

    //  super    
    //     
    id class = [self class];
    //      
    object_setClass(self,class_getSuperclass(class));
    NSString *methodName = objc_getAssociatedObject(self, @"methodName"); // Person       
    //       
    objc_msgSend(self,NSSelectorFromString(methodName),param);

    //    
    id observer =  objc_getAssociatedObject(self, @"objc"); // Person       
    objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:),param,self,nil,nil);
    
    //      ,           whj_Person  myImp  
    object_setClass(self, class);
    
}