KVC 流程分析 自定义及异常处理

今天学习一下 KVC 深层次的东西 喜欢的可以进来看看,也许有你中意的哦~

1.成员变量&实例变量&属性
2.KVC初探
3.KVC赋值
4.KVC取值
5.KVC的异常处理
6.KVC的进阶用法
7.YYModel原理分析
8.category源码分析

1.成员变量&实例变量&属性 的区别是什么

//实例变量是一种特殊的成员变量
// class 类实例出来的变量 就是 实例变量  Btn 就是
@interface LGPerson : NSObject
{ //这个里面全部为成员变量
    @public
    NSString *Name; 
    UIButton *Btn ;  //实例变量
    id hello; //id 是一种特殊的class
}
// 这个是属性无疑  属性会有一个默认的  setter + getter 方法
@property (nonatomic, copy) NSString *nameX;
  1. 苹果早期的编译器是GCC 后面升级 变成 LLVM
  2. synthsize Name = _Name; //自动生成 setter 和 getter
  3. llvm之后 如果发现实例变量或者成员变量之后 没有匹配到实例变量的相应属性的时候 就会自动创建一个带 (_Name)

2.KVC初探

KVC 文档详情移步
先提几个问题

  1. KVC是什么 是一种 机制 -- 通过xx 间接访问成员变量
    ------------------------ -- 通过键值编码
  2. 有什么作用 进行一系列的键值编码

KVC使用

[self.textFiled setValue:[UIColor orangeColor] forKeyPath:@"_placeholderLabel.textColor"];

3.KVC赋值

当调用setValue:forKey: 代码时候,底层会

  • 优先调用 setKey: 属性值 方法,代码通过setter 方法完成设置。 注意这里的key是指成员变量名,首字母大小写要符合KVC的命名规范, 下同
  • 如果没有找到setName: 方法,KVC机制会检查 + (BOOL)accessInstanceVariablesDirectly 方法有没有返回YES,默认YES,如果你重写该方法为NO。KVC就直接执行setValue: forUndefineKey:方法,
    不过一般不会这么做,所以KVC机制会搜索该类里面有没有名为_key的成员变量,无论是.h | .m 里面定义,也不论用什么样的访问修饰符,只要存在_key命名的变量,KVC都可以对该成员变量赋值。
  • 如果该类没有setKey: 方法,也没有_key成员变量,KVC机制会搜索_isKey成员变量
  • 继续没有就 继续搜索 _key 和 _isKey 成员变量,KVC也会继续搜索key 和 isKey 的成员变量,在给他们赋值。
  • 如果上面列出的方法或者成员变量都不存在,系统将会执行该对象 setValue:forUndefinedKey:方法,默认是抛出异常。

4.KVC取值

当调用valueForKey的代码时候,

  • 首先会按照 get 或_ 顺序来查找 getter方法,找到这些方法就会直接调用,调用的过程遇到BOOL 或者Int 类型,会把他包装成NSNumber对象
  • 如果以上四个方法没有找打 KVC就会查找 countOf and objectInAtIndex: 和 AtIndexes 格式的方法。如果找到countOf 和另外两个方法中的一个,那么就会返回一个可以响应NSArray所有方法的代理集合(他是NSKeyValueArray, 是NSArray的子类),并返回该对象
  • 如果上面的方法没有找到,就会查找countOf, enumeratorOf, 和 memberOf, 如果这个三个方法都找到就会返回一个NSSet 方法代理集合,这个代理集合会发NSSet的消息,就会以countOfKey,enumeratorOfKey,memberOfKey组合的形式调用。

简单的说就是如果你在自己的类定义了KVC的实现,并且实现了上面的方法,那么你可以将返回的对象当做数组(NSArray)/集合(NSSet)用了。

  • 这个时候还没找到,就会检查类方法+(BOOL)accessInstanceVariablesDirectly,如果返回YES ,那么和之前的设值一样,会按_key,_isKey,key,isKey的顺序搜索成员变量名,这里不推荐这样做,因为这样直接访问实例变量破坏了封装性,使代码更脆弱。如果重写了+(BOOL)accessInstanceVariablesDirectly返回NO的话,那么直接调用valueForUndefinedKey:
  • 最后再没找到就 调用 valueForUndefinedKey:

5.KVC的异常处理

几个比较常见的异常有

  1. 没有找到的一些值
[p setValue:nil forKey:@"subject"];

2.不存在的Key

[p setValue:@"hello" forKey:@"nickName"];

2.取值的时候不存在的Key

NSLog(@"%@",[p valueForKey:@"FKName"]);

需要对这些不安全的因素进行收集

// 对非对象类型赋值 不能设置空值
- (void)setNilValueForKey:(NSString *)key{
    NSLog(@"%@的值不能为空",key);
}

- (void)setValue:(id)value forUndefinedKey:(NSString *)key{
    NSLog(@"不能对不存在的健赋值");
}

- (id)valueForUndefinedKey:(NSString *)key{
    NSLog(@"不能对不存在的键取值");
    return @"error";
}

- (BOOL)validateValue:(inout id  _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError * _Nullable __autoreleasing *)outError
{
    if (*ioValue == nil || inKey == nil || inKey.length == 0) {
        NSLog(@"value 可能为nil  或者key为nil或者空值");
        return NO;
    }
    return YES;
}

6.KVC的进阶用法

  • 先看用法
//控制器调用   
-(void)arrayFKDemo
{
    FKPerson *fp = FKPerson.new;
    fp.mouseArr = [NSMutableArray arrayWithObjects:@"mouse0", @"mouse1", @"mouse2", @"mouse3", nil];
    NSArray *arr = [fp valueForKey:@"mouse"]; // 动态成员变量
    NSLog(@"mouse = %@", arr);
}
[20759:5051180] mouse = (
    "mouse 0",
    "mouse 1",
    "mouse 2",
    "mouse 3")

// 个数 
- (NSUInteger)countOfMouse
{
    return [self.mouseArr count];
}

//// 获取值
- (id)objectInMouseAtIndex:(NSUInteger)index
{
    return [NSString stringWithFormat:@"mouse %lu", index];
}

// 是否包含这个成员对象
- (id)memberOfPens:(id)object {
    return [self.penArr containsObject:object] ? object : nil;
}

// 迭代器
- (id)enumeratorOfPens {
    // objectEnumerator
    return [self.penArr reverseObjectEnumerator];
}

在上面person里面是没有mouse 这个key的,但是只要实现了 countOf 和 objectInAtIndex: 这连个方法,程序就不会蹦,照常进行

如果是set集合 就调用 memberOf 和 countOf, enumeratorOf其中一个方法 也是可以的 这里的用法可以根据官方文档来探索

7.YYModel原理分析

/// Returns the cached model class meta
+ (instancetype)metaWithClass:(Class)cls {
    if (!cls) return nil;
    static CFMutableDictionaryRef cache; //这里会声明一个字典,在下面做缓存使用,第一次进来缓存数据,
    static dispatch_once_t onceToken;
    static dispatch_semaphore_t lock;
    dispatch_once(&onceToken, ^{
        cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        lock = dispatch_semaphore_create(1);
    });
    dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
//第二次进来就会直接从全局的cache缓存里面去,性能高
    _YYModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls));
    dispatch_semaphore_signal(lock);
    if (!meta || meta->_classInfo.needUpdate) {
        meta = [[_YYModelMeta alloc] initWithClass:cls];
        if (meta) {
            dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
            CFDictionarySetValue(cache, (__bridge const void *)(cls), (__bridge const void *)(meta));
            dispatch_semaphore_signal(lock);
        }
    }
    return meta;
}
  • initWithClass 方法里面 先进去判断 黑白名单过滤操作 ,然后下一步会进行属性替换操作 (modelContainerPropertyGenericClass) 并进行处理(按照不同的类型处理)
+ (NSDictionary *)modelContainerPropertyGenericClass{
    return @{@"books" : FKSubModel.class,
             @"infoDict" : FKSubTModel.class,
             @"likedUserIds" : NSNumber.class
             };
}

方法进行到这一步之后会将里面各种不同的数据类型进行转换

+ (NSDictionary *)_yy_dictionaryWithJSON:(id)json {
    if (!json || json == (id)kCFNull) return nil;
    NSDictionary *dic = nil;
    NSData *jsonData = nil;
    if ([json isKindOfClass:[NSDictionary class]]) {
        dic = json;
    } else if ([json isKindOfClass:[NSString class]]) {
        jsonData = [(NSString *)json dataUsingEncoding : NSUTF8StringEncoding];
    } else if ([json isKindOfClass:[NSData class]]) {
        jsonData = json;
    }
    if (jsonData) {
        dic = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:NULL];
        if (![dic isKindOfClass:[NSDictionary class]]) dic = nil;
    }
    return dic;
}
转换完了之后,就会得到一个jsonData并序列化,结果就会返回一个dic
- (BOOL)yy_modelSetWithDictionary:(NSDictionary *)dic {
    if (!dic || dic == (id)kCFNull) return NO;
    if (![dic isKindOfClass:[NSDictionary class]]) return NO;
    

    _YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:object_getClass(self)];
    if (modelMeta->_keyMappedCount == 0) return NO;
    // 这里会拿到当前meta里面所有的map所有的类,发送一个响应的方法CFArrayApplyFunction
    if (modelMeta->_hasCustomWillTransformFromDictionary) {
        dic = [((id)self) modelCustomWillTransformFromDictionary:dic];
        if (![dic isKindOfClass:[NSDictionary class]]) return NO;
    }
    
    ModelSetContext context = {0};
    context.modelMeta = (__bridge void *)(modelMeta);
    context.model = (__bridge void *)(self);
    context.dictionary = (__bridge void *)(dic);
    
    //这个里面都会调用ModelSetWithPropertyMetaArrayFunction方法
    if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
        CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
        if (modelMeta->_keyPathPropertyMetas) {
            CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
        if (modelMeta->_multiKeysPropertyMetas) {
            CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas,
                                 CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)),
                                 ModelSetWithPropertyMetaArrayFunction,
                                 &context);
        }
    } else {
        CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas,
                             CFRangeMake(0, modelMeta->_keyMappedCount),
                             ModelSetWithPropertyMetaArrayFunction,
                             &context);
    }
    
    if (modelMeta->_hasCustomTransformFromDictionary) {
        return [((id)self) modelCustomTransformFromDictionary:dic];
    }
    return YES;
}

/**
 Apply function for model property meta, to set dictionary to model.
 
 @param _propertyMeta should not be nil, _YYModelPropertyMeta.
 @param _context      _context.model and _context.dictionary should not be nil.
 */
static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context) {
    ModelSetContext *context = _context;
    __unsafe_unretained NSDictionary *dictionary = (__bridge NSDictionary *)(context->dictionary);
    __unsafe_unretained _YYModelPropertyMeta *propertyMeta = (__bridge _YYModelPropertyMeta *)(_propertyMeta);
    if (!propertyMeta->_setter) return;
    id value = nil;
    //判断是否为array类型的
    if (propertyMeta->_mappedToKeyArray) {
        value = YYValueForMultiKeys(dictionary, propertyMeta->_mappedToKeyArray);
    } else if (propertyMeta->_mappedToKeyPath) {
//判断是否为KeyPath类型的
        value = YYValueForKeyPath(dictionary, propertyMeta->_mappedToKeyPath);
    } else {
//这里是直接转换值
        value = [dictionary objectForKey:propertyMeta->_mappedToKey];
    }
    
    if (value) {
        __unsafe_unretained id model = (__bridge id)(context->model);
        ModelSetValueForProperty(model, value, propertyMeta);
    }
}


/// Get the value with multi key (or key path) from dictionary
/// The dic should be NSDictionary
static force_inline id YYValueForMultiKeys(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *multiKeys) {
    id value = nil;
// for遍历里面所有的key拿出来 相应的进行下次路由  这里分string类型 和 其他类型,其他类型继续进行路由
    for (NSString *key in multiKeys) {
        if ([key isKindOfClass:[NSString class]]) {
            value = dic[key];
            if (value) break;
        } else {
// 路由这里就要进行遍历,将嵌套的一层一层全部拿出来 
            value = YYValueForKeyPath(dic, (NSArray *)key);
            if (value) break;
        }
    }
    return value;
}

//最后走 ModelSetValueForProperty 方法  ,NSNumber NSString NSAttributedString各种各样的类型在里面都要发送msg_send 调用  meta->_setter 方法,对 (也就是发送setter 信息)
/**
 Set value to model with a property meta.
 
 @discussion Caller should hold strong reference to the parameters before this function returns.
 
 @param model Should not be nil.
 @param value Should not be nil, but can be NSNull.
 @param meta  Should not be nil, and meta->_setter should not be nil.
 */
static void ModelSetValueForProperty(__unsafe_unretained id model,
                                     __unsafe_unretained id value,
                                     __unsafe_unretained _YYModelPropertyMeta *meta) ;

8. category源码分析

如果给一个类添加一个分类,并重写这个类的某一个方法,问,到底会不会覆盖类里面的方法

// 遍历方法  可以通过这个方法来验证是否覆盖
-(void) printMethodNameOfClass:(Class)cls
{
    unsigned int count;
    //获取方法数组
    Method *methodList = class_copyMethodList(cls, &count);
    //存储方法名
    NSMutableString *methodNames = [NSMutableString string];
    //遍历所有方法
    for (int i = 0 ; i 
  • 分类的方法是如何加载
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/
//这里就是所有的img进行的镜像加载
void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();
// 通过map_images 这个函数
//进入到 void map_images_nolock(unsigned mhCount, const char * const mhPaths[],const struct mach_header * const mhdrs[])
// 开始读所有的镜像文件
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

//读取函数
if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
//此处省略N多代码 

// 这里就会出来很多的类,包括子类等等都会加载过来
for (i = 0; i < count; i++) {
            Class cls = (Class)classlist[I];
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);

            //类加载
            if (newCls != cls  &&  newCls) {
                // Class was moved but not deleted. Currently this occurs 
                // only when the new class resolved a future class.
                // Non-lazily realize the class below.
                resolvedFutureClasses = (Class *)
                    realloc(resolvedFutureClasses, 
                            (resolvedFutureClassCount+1) * sizeof(Class));
                resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
            }
        }

//方法编号的加载
static size_t UnfixedSelectors;
    {
        mutex_locker_t lock(selLock);
        for (EACH_HEADER) {
            if (hi->isPreoptimized()) continue;
            
            bool isBundle = hi->isBundle();
            SEL *sels = _getObjc2SelectorRefs(hi, &count);
            UnfixedSelectors += count;
            for (i = 0; i < count; i++) {
                const char *name = sel_cname(sels[i]);
                sels[i] = sel_registerNameNoLock(name, isBundle);
            }
        }
    }

// Discover protocols. Fix up protocol refs.
//协议加载
    for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        assert(cls);
        NXMapTable *protocol_map = protocols();
        bool isPreoptimized = hi->isPreoptimized();
        bool isBundle = hi->isBundle();
        // 最后其实就是一个列表 通过这个_getObjc2ProtocolList函数
  // 整个进程会维护这张表
        protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map, 
                         isPreoptimized, isBundle);
        }
    }

// Discover categories. 
// 探索重点~~~!!!
// category 加载 他也是通过这个_getObjc2CategoryList来加载的
    for (EACH_HEADER) {
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        for (i = 0; i < count; i++) {
            category_t *cat = catlist[I];
            Class cls = remapClass(cat->cls);

            if (!cls) {
                // Category's target class is missing (probably weak-linked).
                // Disavow any knowledge of this category.
                catlist[i] = nil;
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class", 
                                 cat->name, cat);
                }
                continue;
            }

            // Process this category. 
            // First, register the category with its target class. 
            // Then, rebuild the class's method lists (etc) if 
            // the class is realized. 
            bool classExists = NO;
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) {
                    remethodizeClass(cls);
                    classExists = YES;
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category -%s(%s) %s", 
                                 cls->nameForLogging(), cat->name, 
                                 classExists ? "on existing class" : "");
                }
            }

            if (cat->classMethods  ||  cat->protocols  
                ||  (hasClassProperties && cat->_classProperties)) 
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized())
                {
                    // remethodizeClass 函数加载
                    remethodizeClass(cls->ISA());
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)", 
                                 cls->nameForLogging(), cat->name);
                }
            }
        }
    }
}

static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;

    runtimeLock.assertLocked();

    isMeta = cls->isMetaClass();

    // Re-methodizing: check for more categories
//判断是否能找到这个category
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        if (PrintConnecting) {
            _objc_inform("CLASS: attaching categories to class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
        //找到就attachCategories 把cats贴上去 
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats);
    }
}

// 通过- 汇编lldb - 源码 - 官方文档 - 坑

一级标题

二级标题

五级标题
  • 列表第一项
  • 列表第二项
  1. 有序列表第一项
  2. 有序列表第二项

[图片上传失败...(image-12bdd7-1557046383947)]
斜体
粗体

引用段落

代码块

你可能感兴趣的:(KVC 流程分析 自定义及异常处理)