九龙冰仕 發表於 2021-8-24 15:31:00

iOS底层学习——KVC

<p><img src="https://upload-images.jianshu.io/upload_images/18569867-9c88d30e30ec4a77.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"></p>
<h1 id="1kvc协议定义">1.KVC协议定义</h1>
<p>键值编码是由<code>NSKeyValueCoding非正式协议</code>启用的一种机制,对象采用该机制来提供<code>对其属性的间接访问</code>。当对象符合键值编码时,其属性可通过字符串参数通过简洁、统一的<code>消息传递接口</code>进行寻址。这种间接访问机制补充了实例变量及其相关访问器方法提供的直接访问。</p>
<p>本文收录:掘金【gufs镜像】《iOS底层学习——KVC》</p>
<ul>
<li>
<p><strong>KVC在Objective-C中的定义</strong></p>
<p><code>KVC</code>的定义都是对<code>NSObject</code>的扩展来实现的(<code>Objective-C</code>中有个显式的<code>NSKeyValueCoding</code>类别名-<code>分类</code>)。查看<code>setValueForKey</code>方法,发现其在<code>Foundation</code>里面,而<code>Foundation</code>框架是<code>不开源</code>的,只能在苹果官方文档查找。见下图:</p>
<p><img src="https://upload-images.jianshu.io/upload_images/18569867-a4f604e1b4113b98.image?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"></p>
</li>
</ul>
<p>如果你正在面试,或者正准备跳槽,不妨看看我精心总结的面试资料:&nbsp;BAT 大厂最新面试题+答案合集(持续更新中)&nbsp;来获取一份详细的大厂面试资料 为你的跳槽加薪多一份保障</p>
<h1 id="推荐">推荐👇:</h1>
<blockquote>
<p><strong>作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:834688868&nbsp;,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!</strong></p>
</blockquote>
<h1 id="2kvc提供的api方法">2.KVC提供的API方法</h1>
<ul>
<li>
<p>我们可以学习解读苹果的官方文档,对<code>KVC</code>有更深的理解。</p>
<p>Key-Value Coding Programming Guide</p>
<p>苹果对一些容器类比如<code>NSArray</code>或者<code>NSSet</code>等,<code>KVC</code>有着特殊的实现。</p>
</li>
<li>
<p>常用方法</p>
<p>对于所有继承了<code>NSObject</code>的类型,也就是几乎所有的<code>Objective-C</code>对象都能使用<code>KVC</code>,下面是<code>KVC</code>最为重要的四个方法:</p>
<pre><code>   -&nbsp;(nullable&nbsp;id)valueForKey:(NSString&nbsp;*)key;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 直接通过Key来取值
   -&nbsp;(void)setValue:(nullable&nbsp;id)value&nbsp;forKey:(NSString&nbsp;*)key;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 通过Key来设值
   -&nbsp;(nullable&nbsp;id)valueForKeyPath:(NSString&nbsp;*)keyPath;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 通过KeyPath来取值
   -&nbsp;(void)setValue:(nullable&nbsp;id)value&nbsp;forKeyPath:(NSString&nbsp;*)keyPath;&nbsp;&nbsp;// 通过KeyPath来设值

</code></pre>
</li>
<li>
<p>特殊方法</p>
<p>当然<code>NSKeyValueCoding</code>类别中还有其他的一些方法,这些方法在碰到特殊情况或者有特殊需求还是会用到的。</p>
<pre><code>// 默认返回YES,表示如果没有找到Set方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员,设置成NO就不这样搜索
+&nbsp;(BOOL)accessInstanceVariablesDirectly;

// KVC提供属性值正确性验证的API,它可以用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因。
-&nbsp;(BOOL)validateValue:(inout&nbsp;id&nbsp;__nullable&nbsp;*&nbsp;__nonnull)ioValue&nbsp;forKey:(NSString&nbsp;*)inKey&nbsp;error:(out&nbsp;NSError&nbsp;**)outError;

// 这是集合操作的API,里面还有一系列这样的API,如果属性是一个NSMutableArray,那么可以用这个方法来返回。
-&nbsp;(NSMutableArray&nbsp;*)mutableArrayValueForKey:(NSString&nbsp;*)key;

// 如果Key不存在,且没有KVC无法搜索到任何和Key有关的字段或者属性,则会调用这个方法,默认是抛出异常。
-&nbsp;(nullable&nbsp;id)valueForUndefinedKey:(NSString&nbsp;*)key;

// 和上一个方法一样,但这个方法是设值。
-&nbsp;(void)setValue:(nullable&nbsp;id)value&nbsp;forUndefinedKey:(NSString&nbsp;*)key;

// 如果你在SetValue方法时面给Value传nil,则会调用这个方法
-&nbsp;(void)setNilValueForKey:(NSString&nbsp;*)key;

// 输入一组key,返回该组key对应的Value,再转成字典返回,用于将Model转到字典。
-&nbsp;(NSDictionary&lt;NSString&nbsp;*,&nbsp;id&gt;&nbsp;*)dictionaryWithValuesForKeys:(NSArray&lt;NSString&nbsp;*&gt;&nbsp;*)keys;


</code></pre>
</li>
<li>
<p>结构体处理</p>
<p><code>KVC</code>在进行结构体处理时,需要用到<code>NSValue</code>,设值时,将结构体封装成<code>NSValue</code>,进行键值设值;取值同样返回<code>NSValue</code>,然后按照结构体格式进行解析,见下面代码:</p>
<pre><code>    // 结构体
    ThreeFloats floats = {1.,2.,3.};
    // 封装成NSValue
    NSValue *value &nbsp; &nbsp; = ;
    // 设值
    ;

    // 取值
    NSValue *value1&nbsp; &nbsp; = ;
    // 结构体解析
    ThreeFloats th;
    ;
    NSLog(@"%f-%f-%f",th.x,th.y,th.z);

</code></pre>
</li>
<li>
<p>字典处理(模型转换)</p>
<p>字典可以实现与模型进行装换,也可以通过键值数组从模型中获取字典数据。见下面代码:</p>
<pre><code>- (void)dictionaryTest{
    // 字典
    NSDictionary* dict = @{
                           @"name":@"Cooci",
                           @"nick":@"KC",
                           @"subject":@"iOS",
                           @"age":@18,
                           @"length":@180
                           };
    // 模型
    LGStudent *p = [ init];
    // 字典转模型
    ;

    // 键值数组
    NSArray *array = @[@"name",@"age"];
    // 从模型中获取响应的字典数据
    NSDictionary *dic = ;
    NSLog(@"%@",dic);
}

</code></pre>
</li>
</ul>
<h1 id="3kvc设值取值顺序">3.KVC设值取值顺序</h1>
<p><code>KVC</code>是怎么使用的,我们都很清楚,那么<code>KVC</code>在内部是按什么样的顺序来寻找<code>key</code>的呢?这是我们要探索的重点。</p>
<h3 id="1设值">1.设值</h3>
<p>当调用<code>setValue:forKey:</code>代码时,底层的执行机制是怎样的呢?在官方文档中有相关的说明,见下图:</p>
<p><img src="https://upload-images.jianshu.io/upload_images/18569867-60eaaf25d68a7c8b.image?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"></p>
<ul>
<li>
<p>翻译过来的意思是:</p>
<p><code>setValue:forKey:</code> 的默认实现,给定<code>key</code>和<code>value</code>参数作为输入,尝试将名为<code>key</code>的属性设置为<code>value</code>,在接收调用的对象内部,使用以下过程:按顺序查找名为 <code>set&lt;Key&gt;:</code> 或 <code>_set&lt;Key&gt;</code> 的第一个访问器。 如果找到,则使用输入值(或根据需要展开的值)调用它并完成。如果未找到简单访问器,并且类方法 <code>accessInstanceVariablesDirectly</code>返回 <code>YES</code>,则按顺序查找名称类似于 <code>_&lt;key&gt;</code>、<code>_is&lt;Key&gt;</code>、<code>&lt;key&gt;</code> 或 <code>is&lt;Key&gt;</code> 的实例变量。 如果找到,直接使用输入值(或解包值)设置变量并完成。 在未找到访问器或实例变量时,调用 <code>setValue:forUndefinedKey:</code>。 默认情况下,这会引发异常,但 <code>NSObject</code> 的子类可能会提供特定于键的行为。</p>
</li>
<li>
<p>根据上的官方内容,可以得出如下实现机制:</p>
<ul>
<li>按顺序查找名为<code>set&lt;Key&gt;</code>,<code>_set&lt;Key&gt;</code>&nbsp;或者<code>setIs&lt;Key&gt;</code>的<code>setter</code>访问器顺序查找,如果找到就调用它</li>
<li>只要实现任意一个,那么就会将调用这个方法,将属性的值设为传进来的值</li>
<li>如果没有找到这些<code>setter</code>方法,<code>KVC</code>机制会检查<code>+ (BOOL)accessInstanceVariablesDirectly</code>方法有没有返回<code>YES</code>,默认该方法会返回<code>YES</code>,如果重写了该方法让其返回<code>NO</code>的话,那么在这一步<code>KVC</code>会执行<code>setValue:forUndefinedKey:</code>方法;</li>
<li>如果返回<code>YES</code>,<code>KVC</code>机制会优先搜索该类里面有没有名为<code>_&lt;Key&gt;</code>的成员变量,无论该变量是在类接口处定义,还是在类实现处定义,也无论用了什么样的访问修饰符,只在存在以<code>_&lt;Key&gt;</code>命名的变量,<code>KVC</code>都可以对该成员变量赋值</li>
<li><code>KVC</code>机制再会继续搜索<code>_is&lt;Key&gt;</code>、<code>&lt;key&gt;</code>和<code>is&lt;key&gt;</code>的成员变量,再给它们赋值</li>
<li>如果上面列出的方法或者成员变量都不存在,系统将会执行该对象的<code>setValue:forUndefinedKey:</code>方法,默认是抛出异常。</li>
</ul>
</li>
<li>
<p>以<code>;</code>为例,可以得出以下结论:</p>
<ul>
<li>优先通过<code>setter</code>方法,进行属性设置,调用顺序是:
<ol>
<li><code>setName</code></li>
<li><code>_setName</code></li>
<li><code>setIsName</code></li>
</ol>
</li>
<li>如果以上方法均未找到,并且<code>accessInstanceVariablesDirectly</code>返回<code>YES</code>,则通过成员变量进行设置,顺序是:
<ol>
<li><code>_name</code></li>
<li><code>_isName</code></li>
<li><code>name</code></li>
<li><code>isName</code></li>
</ol>
</li>
</ul>
<p>可通过案例进行验证,这里不再展示。</p>
</li>
<li>
<p><code>accessInstanceVariablesDirectly</code>说明</p>
<p>重写<code>+ (BOOL)accessInstanceVariablesDirectly</code>方法让其返回<code>NO</code>,这样的话,如果<code>KVC</code>没有找到<code>set&lt;Key&gt;</code>、<code>_set&lt;Key&gt;</code>、<code>setIs&lt;Key&gt;</code>相关方法时,会直接用<code>setValue:forUndefinedKey:</code>方法。我们用代码来测试一下上面的<code>KVC</code>机制:</p>
<pre><code>@interface&nbsp;LGPerson&nbsp;:&nbsp;NSObject
{
    @public
      NSString *_isName;
      NSString *name;
      NSString *isName;
      NSString *_name;
}
@end
@implementation&nbsp;LGPerson

+(BOOL)accessInstanceVariablesDirectly{
    return&nbsp;NO;
}

-(id)valueForUndefinedKey:(NSString&nbsp;*)key{
    NSLog(@"出现异常,该key不存在%@",key);
    return&nbsp;nil;
}
-(void)setValue:(id)value&nbsp;forUndefinedKey:(NSString&nbsp;*)key{
   NSLog(@"出现异常,该key不存在%@",key);
}

// 设置方法全部注释掉
//&nbsp;-(void)setName:(NSString*)name{
//&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;toSetName&nbsp;=&nbsp;name;
//&nbsp;}
// - (void)_setName:(NSString *)name{
// &nbsp;   NSLog(@"%s - %@",__func__,name);
// }
// - (void)setIsName:(NSString *)name{
// &nbsp; &nbsp; NSLog(@"%s - %@",__func__,name);
// }

@end

int&nbsp;main(int&nbsp;argc,&nbsp;const&nbsp;char&nbsp;*&nbsp;argv[])&nbsp;{
    @autoreleasepool&nbsp;{
      //&nbsp;insert&nbsp;code&nbsp;here...
      LGPerson*&nbsp;person&nbsp;=&nbsp;;
      ;
      NSString*&nbsp;name&nbsp;=&nbsp;;
      NSLog(@"value for key : %@",name);

      NSLog(@"取值_name:%@",person-&gt;_name);
      NSLog(@"取值_isName:%@",person-&gt;_isName);
      NSLog(@"取值name:%@",person-&gt;name);
      NSLog(@"取值isName:%@",person-&gt;isName);
    }
    return&nbsp;0;
}

</code></pre>
<p>运行结构见下图:</p>
<p><img src="https://upload-images.jianshu.io/upload_images/18569867-9f2e08e2ed2ddc9c.image?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"></p>
<p>这说明了重写<code>+(BOOL)accessInstanceVariablesDirectly</code>方法让其返回<code>NO</code>后,<code>KVC</code>找不到<code>set&lt;Key&gt;</code>等方法后,不再去找<code>&lt;Key&gt;</code>系列成员变量,而是直接调用<code>setValue:forUndefinedKey:</code>方法,所以开发者如果不想让自己的类实现<code>KVC</code>,就可以这么做。</p>
</li>
<li>
<p>KVC设值流程图</p>
<p><img src="https://upload-images.jianshu.io/upload_images/18569867-b376aebbb1c547a2.image?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"></p>
</li>
</ul>
<h3 id="2取值">2.取值</h3>
<p>当调用<code>valueForKey:</code>的代码时,底层的执行机制又是怎样的呢?在官方文档中有相关的说明,见下图:</p>
<p><img src="https://upload-images.jianshu.io/upload_images/18569867-b88e26d117ec6ba6.image?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"></p>
<ul>
<li>
<p>根据上的官方内容,翻译之后可以得出如下实现机制:</p>
<ul>
<li>首先按<code>get&lt;Key&gt;</code>,<code>&lt;Key&gt;</code>,<code>is&lt;Key&gt;</code>,<code>_&lt;Key&gt;</code>的顺序方法查找<code>getter</code>方法,找到的话会直接调用,如果是<code>BOOL</code>或者<code>Int</code>等值类型, 会将其包装成一个<code>NSNumber</code>对象。</li>
<li>如果上面的<code>getter</code>没有找到,<code>KVC</code>则会查找<code>countOf&lt;Key&gt;</code>,<code>objectIn&lt;Key&gt;AtIndex</code>或<code>&lt;Key&gt;AtIndexes</code>格式的方法。如果<code>countOf&lt;Key&gt;</code>方法和另外两个方法中的一个被找到,那么就会返回一个可以响应<code>NSArray</code>所有方法的代理集合(它是<code>NSKeyValueArray</code>,是<code>NSArray</code>的子类),调用这个代理集合的方法,或者说给这个代理集合发送属于<code>NSArray</code>的方法,就会以<code>countOf&lt;Key&gt;</code>,<code>objectIn&lt;Key&gt;AtIndex</code>或<code>At&lt;Key&gt;Indexes</code>这几个方法组合的形式调用。还有一个可选的<code>get&lt;Key&gt;:range:</code>方法。所以你想重新定义<code>KVC</code>的一些功能,你可以添加这些方法,需要注意的是你的方法名要符合<code>KVC</code>的标准命名方法,包括方法签名。</li>
<li>如果上面的方法没有找到,那么会同时查找<code>countOf&lt;Key&gt;</code>,<code>enumeratorOf&lt;Key&gt;</code>,<code>memberOf&lt;Key&gt;</code>格式的方法。如果这三个方法都找到,那么就返回一个可以响应<code>NSSet</code>所的方法的代理集合,和上面一样,给这个代理集合发<code>NSSet</code>的消息,就会以<code>countOf&lt;Key&gt;</code>,<code>enumeratorOf&lt;Key&gt;</code>,<code>memberOf&lt;Key&gt;</code>组合的形式调用。</li>
<li>如果还没有找到,再检查类方法<code>+ (BOOL)accessInstanceVariablesDirectly</code>,如果返回<code>YES</code>(默认行为),那么和先前的设值一样,会按<code>_&lt;Key&gt;</code>,<code>_is&lt;Key&gt;</code>,<code>&lt;Key&gt;</code>,<code>is&lt;Key&gt;</code>的顺序搜索成员变量名,这里不推荐这么做,因为这样直接访问实例变量破坏了封装性,使代码更脆弱。如果重写了类方法<code>+ (BOOL)accessInstanceVariablesDirectly</code>返回<code>NO</code>的话,那么会直接调用<code>valueForUndefinedKey:</code></li>
<li>还没有找到的话,调用<code>valueForUndefinedKey:</code></li>
</ul>
</li>
<li>
<p>以<code>;</code>为例</p>
<ul>
<li><code>getter</code>方法的调用顺序是:
<ol>
<li><code>getName</code></li>
<li><code>name</code></li>
<li><code>isName</code></li>
<li><code>_name</code></li>
</ol>
</li>
<li>如果以上方法没有找到,accessInstanceVariablesDirectly返回YES,则直接返回成员变量,获取顺序依然是:
<ol>
<li><code>_name</code></li>
<li><code>_isName</code></li>
<li><code>name</code></li>
<li><code>isName</code></li>
</ol>
</li>
</ul>
<p>可通过案例进行验证,这里不再展示。</p>
</li>
<li>
<p>KVC取值流程图</p>
<p><img src="https://upload-images.jianshu.io/upload_images/18569867-050c5e53943144cf.image?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"></p>
</li>
</ul>
<p>可以通过下面的代码对以上结论进行验证!</p>
<pre><code>    @interface&nbsp;LGPerson&nbsp;:&nbsp;NSObject
    {
      @public
            NSString *_isName;
            NSString *name;
            NSString *isName;
            NSString *_name;
    }
    @end
    @implementation&nbsp;LGPerson

    +(BOOL)accessInstanceVariablesDirectly{
      return&nbsp;NO;
    }

    -(id)valueForUndefinedKey:(NSString&nbsp;*)key{
      NSLog(@"出现异常,该key不存在%@",key);
      return&nbsp;nil;
    }
    -(void)setValue:(id)value&nbsp;forUndefinedKey:(NSString&nbsp;*)key{
         NSLog(@"出现异常,该key不存在%@",key);
    }

    // 设置方法全部注释掉
    //&nbsp;-(void)setName:(NSString*)name{
    //&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;toSetName&nbsp;=&nbsp;name;
    //&nbsp;}
    // - (void)_setName:(NSString *)name{
    // &nbsp;   NSLog(@"%s - %@",__func__,name);
    // }
    // - (void)setIsName:(NSString *)name{
    // &nbsp; &nbsp; NSLog(@"%s - %@",__func__,name);
    // }

    // 取值方法
    //- (NSString *)getName{
    //&nbsp; &nbsp; return NSStringFromSelector(_cmd);
    //}
    //- (NSString *)name{
    //&nbsp; &nbsp; return NSStringFromSelector(_cmd);
    //}
    //- (NSString *)isName{
    //&nbsp; &nbsp; return NSStringFromSelector(_cmd);
    //}
    //- (NSString *)_name{
    //&nbsp; &nbsp; return NSStringFromSelector(_cmd);
    //}
    @end

    int&nbsp;main(int&nbsp;argc,&nbsp;const&nbsp;char&nbsp;*&nbsp;argv[])&nbsp;{
      @autoreleasepool&nbsp;{
            //&nbsp;insert&nbsp;code&nbsp;here...
            LGPerson*&nbsp;person&nbsp;=&nbsp;;
            ;
            NSString*&nbsp;name&nbsp;=&nbsp;;
            NSLog(@"value for key : %@",name);

            NSLog(@"取值_name:%@",person-&gt;_name);
            NSLog(@"取值_isName:%@",person-&gt;_isName);
            NSLog(@"取值name:%@",person-&gt;name);
            NSLog(@"取值isName:%@",person-&gt;isName);
      }
      return&nbsp;0;
    }

</code></pre>
<h1 id="4在kvc中使用keypath">4.在KVC中使用keyPath</h1>
<p>除了对当前对象的属性进行赋值外,还可以对其更深层的对象进行赋值。例如,对当前对象的<code>location</code>属性的<code>country</code>属性进行赋值。<code>KVC</code>进行多级访问时,直接类似于属性调用一样用点语法进行访问即可。</p>
<pre><code>    ;

</code></pre>
<p>通过<code>keyPath</code>对数组进行取值时,并且数组中存储的对象类型都相同,可以通过<code>valueForKeyPath:</code>方法指定取出数组中所有对象的某个字段。例如下面例子中,通过<code>valueForKeyPath:</code>将数组中所有对象的<code>name</code>属性值取出,并放入一个数组中返回。</p>
<pre><code>    NSArray *names = ;

</code></pre>
<p><img src="https://upload-images.jianshu.io/upload_images/18569867-e64499475497edd5.image?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"></p>
<h1 id="5异常处理">5.异常处理</h1>
<p>当根据<code>KVC</code>搜索规则,没有搜索到对应的<code>key</code>或者<code>keyPath</code>,则会调用对应的异常方法。异常方法的默认实现,在异常发生时会抛出一个异常,并且应用程序<code>Crash</code>。见下图:</p>
<p><img src="https://upload-images.jianshu.io/upload_images/18569867-c75665c29e859578.image?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"></p>
<p>我们可以重写下面两个方法:</p>
<pre><code>    -(id)valueForUndefinedKey:(NSString&nbsp;*)key{
      NSLog(@"出现异常,该key不存在%@",key);
      return&nbsp;nil;
    }

    -(void)setValue:(id)value&nbsp;forUndefinedKey:(NSString&nbsp;*)key{
         NSLog(@"出现异常,该key不存在%@",key);
    }

</code></pre>
<p>重写这两个方法之后,运行程序不再崩溃,见下图:</p>
<p><img src="https://upload-images.jianshu.io/upload_images/18569867-dd88dde87a9c1e40.image?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"></p>
<p>但是我们可以根据业务需要,合理的处理<code>KVC</code>导致的异常。比如下面的处理方式:</p>
<pre><code>- (void)setNilValueForKey:(NSString *)key {
    if () {
      ;
    } else {
      ;
    }
}

</code></pre>
<h1 id="6自定义kvc">6.自定义KVC</h1>
<p>根据苹果官方文档提供的设值、取值规则,我们可以自己进行KVC的自定义实现。见下面实现代码:</p>
<pre><code>// KVC 自定义
@implementation NSObject (LGKVC)

// 设置
- (void)lg_setValue:(nullable id)value forKey:(NSString *)key{
&nbsp; &nbsp; // 1: 判断什么 key
&nbsp; &nbsp; if (key == nil || key.length == 0) {
&nbsp; &nbsp; &nbsp; &nbsp; return;
&nbsp; &nbsp; }

&nbsp; &nbsp; // 2: setter set&lt;Key&gt;: or _set&lt;Key&gt;,
&nbsp; &nbsp; // key 要大写
&nbsp; &nbsp; NSString *Key = key.capitalizedString;

&nbsp; &nbsp; // 拼接方法
&nbsp; &nbsp; NSString *setKey = ;
&nbsp; &nbsp; NSString *_setKey = ;
&nbsp; &nbsp; NSString *setIsKey = ;

    // 是否存在方法
&nbsp; &nbsp; if () {
&nbsp; &nbsp; &nbsp; &nbsp; NSLog(@"*********%@**********",setKey);
&nbsp; &nbsp; &nbsp; &nbsp; return;
&nbsp; &nbsp; }else if () {
&nbsp; &nbsp; &nbsp; &nbsp; NSLog(@"*********%@**********",_setKey);
&nbsp; &nbsp; &nbsp; &nbsp; return;
&nbsp; &nbsp; }else if () {
&nbsp; &nbsp; &nbsp; &nbsp; NSLog(@"*********%@**********",setIsKey);
&nbsp; &nbsp; &nbsp; &nbsp; return;
&nbsp; &nbsp; }

&nbsp; &nbsp; // 3: 判断是否响应 accessInstanceVariablesDirectly 返回YES NO 奔溃
&nbsp; &nbsp; // 3:判断是否能够直接赋值实例变量——NO
&nbsp; &nbsp; if (! ) {
&nbsp; &nbsp; &nbsp; &nbsp; @throw : this class is not key value coding-compliant for the key name.****",self] userInfo:nil];

&nbsp; &nbsp; }

&nbsp; &nbsp; // 4: 间接变量
&nbsp; &nbsp; // 获取 ivar -&gt; 遍历 containsObjct -
&nbsp; &nbsp; // 4.1 定义一个收集实例变量的可变数组
&nbsp; &nbsp; NSMutableArray *mArray = ;
&nbsp; &nbsp; // _&lt;key&gt; _is&lt;Key&gt; &lt;key&gt; is&lt;Key&gt;
    // 拼接成员变量
&nbsp; &nbsp; NSString *_key = ;
&nbsp; &nbsp; NSString *_isKey = ;
&nbsp; &nbsp; NSString *isKey = ;

    // 是否存在对应的变量
&nbsp; &nbsp; if () {
&nbsp; &nbsp; &nbsp; &nbsp; // 4.2 获取相应的 ivar
&nbsp;&nbsp; &nbsp; &nbsp; Ivar ivar = class_getInstanceVariable(, _key.UTF8String);
&nbsp; &nbsp; &nbsp; &nbsp; // 4.3 对相应的 ivar 设置值
&nbsp;&nbsp; &nbsp; &nbsp; object_setIvar(self , ivar, value);
&nbsp;&nbsp; &nbsp; &nbsp; return;
&nbsp; &nbsp; }else if () {
&nbsp;&nbsp; &nbsp; &nbsp; Ivar ivar = class_getInstanceVariable(, _isKey.UTF8String);
&nbsp;&nbsp; &nbsp; &nbsp; object_setIvar(self , ivar, value);
&nbsp;&nbsp; &nbsp; &nbsp; return;
&nbsp; &nbsp; }else if () {
&nbsp;&nbsp; &nbsp; &nbsp; Ivar ivar = class_getInstanceVariable(, key.UTF8String);
&nbsp;&nbsp; &nbsp; &nbsp; object_setIvar(self , ivar, value);
&nbsp;&nbsp; &nbsp; &nbsp; return;
&nbsp; &nbsp; }else if () {
&nbsp;&nbsp; &nbsp; &nbsp; Ivar ivar = class_getInstanceVariable(, isKey.UTF8String);
&nbsp;&nbsp; &nbsp; &nbsp; object_setIvar(self , ivar, value);
&nbsp;&nbsp; &nbsp; &nbsp; return;
&nbsp; &nbsp; }

&nbsp; &nbsp; // 5:如果找不到相关实例

&nbsp; &nbsp; @throw : this class is not key value coding-compliant for the key name.****",self,NSStringFromSelector(_cmd)] userInfo:nil];
}

// 取值
- (nullable id)lg_valueForKey:(NSString *)key{

&nbsp; &nbsp; // 1:刷选key 判断非空
&nbsp; &nbsp; if (key == nil&nbsp; || key.length == 0) {
&nbsp; &nbsp; &nbsp; &nbsp; return nil;
&nbsp; &nbsp; }

&nbsp; &nbsp; // 2:找到相关方法 get&lt;Key&gt; &lt;key&gt; countOf&lt;Key&gt;&nbsp; objectIn&lt;Key&gt;AtIndex
&nbsp; &nbsp; // key 要大写
&nbsp; &nbsp; NSString *Key = key.capitalizedString;

&nbsp; &nbsp; // 拼接方法
&nbsp; &nbsp; NSString *getKey = ;
&nbsp; &nbsp; NSString *countOfKey = ;
&nbsp; &nbsp; NSString *objectInKeyAtIndex = ;

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"

&nbsp; &nbsp; if () {
&nbsp; &nbsp; &nbsp; &nbsp; return ;
&nbsp; &nbsp; }else if (){
&nbsp; &nbsp; &nbsp; &nbsp; return ;
&nbsp; &nbsp; }else if (){
&nbsp; &nbsp; &nbsp; &nbsp; if () {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; int num = (int);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; NSMutableArray *mArray = ;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for (int i = 0; i&lt;num-1; i++) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; num = (int);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for (int j = 0; j&lt;num; j++) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; id objc = ;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return mArray;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
#pragma clang diagnostic pop

&nbsp; &nbsp; // 3:判断是否能够直接赋值实例变量-YES、NO
&nbsp; &nbsp; if (! ) {
&nbsp; &nbsp; &nbsp; &nbsp; @throw : this class is not key value coding-compliant for the key name.****",self] userInfo:nil];
&nbsp; &nbsp; }

&nbsp; &nbsp; // 4.找相关实例变量进行赋值
&nbsp; &nbsp; // 4.1 定义一个收集实例变量的可变数组
&nbsp; &nbsp; NSMutableArray *mArray = ;

&nbsp; &nbsp; // _&lt;key&gt; _is&lt;Key&gt; &lt;key&gt; is&lt;Key&gt;
&nbsp; &nbsp; // _name -&gt; _isName -&gt; name -&gt; isName
&nbsp; &nbsp; NSString *_key = ;
&nbsp; &nbsp; NSString *_isKey = ;
&nbsp; &nbsp; NSString *isKey = ;

    // 判断是否存在对应的成员变量
&nbsp; &nbsp; if () {
&nbsp; &nbsp; &nbsp; &nbsp; Ivar ivar = class_getInstanceVariable(, _key.UTF8String);
&nbsp; &nbsp; &nbsp; &nbsp; return object_getIvar(self, ivar);;
&nbsp; &nbsp; }else if () {
&nbsp; &nbsp; &nbsp; &nbsp; Ivar ivar = class_getInstanceVariable(, _isKey.UTF8String);
&nbsp; &nbsp; &nbsp; &nbsp; return object_getIvar(self, ivar);;
&nbsp; &nbsp; }else if () {
&nbsp; &nbsp; &nbsp; &nbsp; Ivar ivar = class_getInstanceVariable(, key.UTF8String);
&nbsp; &nbsp; &nbsp; &nbsp; return object_getIvar(self, ivar);;
&nbsp; &nbsp; }else if () {
&nbsp; &nbsp; &nbsp; &nbsp; Ivar ivar = class_getInstanceVariable(, isKey.UTF8String);
&nbsp; &nbsp; &nbsp; &nbsp; return object_getIvar(self, ivar);;
&nbsp; &nbsp; }

&nbsp; &nbsp; return @"";
}

#pragma mark **- 相关方法**

- (BOOL)lg_performSelectorWithMethodName:(NSString *)methodName value:(id)value{
&nbsp; &nbsp; if () {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
&nbsp; &nbsp; &nbsp; &nbsp; ;
#pragma clang diagnostic pop
&nbsp; &nbsp; &nbsp; &nbsp; return YES;
&nbsp; &nbsp; }
&nbsp; &nbsp; return NO;
}

- (id)performSelectorWithMethodName:(NSString *)methodName{

&nbsp; &nbsp; if () {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
&nbsp; &nbsp; &nbsp; &nbsp; return ;
#pragma clang diagnostic pop
&nbsp; &nbsp; }
&nbsp; &nbsp; return nil;
}

- (NSMutableArray *)getIvarListName{
&nbsp; &nbsp; NSMutableArray *mArray = ;
&nbsp; &nbsp; unsigned int count = 0;
&nbsp; &nbsp; Ivar *ivars = class_copyIvarList(, &amp;count);
&nbsp; &nbsp; for (int i = 0; i&lt;count; i++) {
&nbsp; &nbsp; &nbsp; &nbsp; Ivar ivar = ivars;
&nbsp; &nbsp; &nbsp; &nbsp; const char *ivarNameChar = ivar_getName(ivar);
&nbsp; &nbsp; &nbsp; &nbsp; NSString *ivarName = ;
&nbsp; &nbsp; &nbsp; &nbsp; NSLog(@"ivarName == %@",ivarName);
&nbsp; &nbsp; &nbsp; &nbsp; ;
&nbsp; &nbsp; }

&nbsp; &nbsp; free(ivars);
&nbsp; &nbsp; return mArray;
}

@end
</code></pre><br><br>
来源:https://www.cnblogs.com/iOSer1122/p/15180571.html
頁: [1]
查看完整版本: iOS底层学习——KVC