浅谈ios之kvc底层执行原理
开发中常用赋值方式:1、直接通过setter方法赋值;2、通过kvc赋值
公司主营业务:成都做网站、成都网站制作、成都外贸网站建设、移动网站开发等业务。帮助企业客户真正实现互联网宣传,提高企业的竞争能力。成都创新互联是一支青春激扬、勤奋敬业、活力青春激扬、勤奋敬业、活力澎湃、和谐高效的团队。公司秉承以“开放、自由、严谨、自律”为核心的企业文化,感谢他们对我们的高要求,感谢他们从不同领域给我们带来的挑战,让我们激情的团队有机会用头脑与智慧不断的给客户带来惊喜。成都创新互联推出阳曲免费做网站回馈大家。
KVC:键值编码,使用字符串访问对象的属性
使用方式:
例如对key为name赋值、取值
当通过setValue:forKey:赋值时,其底层流程为:
1、查找是否有setName,_setName,setIsName的set方法,如果有任意一种,直接赋值。若没有进入第二步
2、查找accessInstanceVariablesDirectly是否允许访问成员变量,若为YES,则查找实例变量_name,_isName,name,isName,查到任意一个则进行赋值
3、setter方法和实例变量都没有找到,系统会执行该对象的setValue:forUndefinekey:抛异常
当通过valueForKey取值时,其底层执行流程为:
1、查找是否有getName,name,isName,_name的get方法,若找到则根据找到的属性值类型,返回对应结果。若没找到进入第二步
2、检查InstanceVariablesDirectly是否为YES,查找_name,_isName,name,isName,查到直接获取对应的值
3、getter方法和实例变量都没找到,系统会执行valueForUndefinekey方法抛异常
什么是KVC
在iOS开发过程中,我们经常会听到或者用到KVO/KVC,但是对于什么是KVO和KVC,我们可能没有那么了解。下面先让我们来了解一下什么是KVC.
什么是KVC
在苹果的官方文档中是这样描述KVC的:它是一种通过字符串描述符而不是通过调用访问方法或者直接使用实例变量的非直接的访问对象属性的机制,说白了就是KVO是一种通过非常规方法访问成员变量或者属性的机制,这种非常规方式就是通过一个字符串标示符也就是所谓的key来访问属性或者成员变量。而这个key一般就是属性名或者实例变量名。
对于KVC的基本的方法都定义在NSKeyValueCoding的非正式协议中,并且NSObject默认实现了该协议。
KVC不仅支持对象类型,也支持数值类型和结构体。非对象类型的参数和返回类型会自动封装成NSValue或NSNumber类型。
KVO可以用来访问三种不同的对象值类型:属性、一对一关系、一对多关系
属性可以是诸如数值、字符串、bool类型等简单的值。也可以NSNumber或者NSColor这样的对象值。
在一对一关系里的对象可以拥有它自己的属性,这些属性可以在不改变对象的情况下被改变。像UIView的superView的属性,我们可以更改superView的属性,而不需要更改UIView。
一对多属性是一些相关对象的集合。通常用NSArray或者NSSet来存储这些集合。KVO也允许用户自定义集合类,但依然是像访问NSArray或者NSSet一样访问它们。
键和键路径
键是用来标识一个对象属性的字符串。一般情况下,键就是访问方法或者是对象的实例变量的名字。键必须是ASCII编码,以小写字母开头,并且不能包含空格。举几个键的例子:age、firstName、lastNmame等。
键路径是一串由点分隔的键组成的字符串,它是用来遍历一系列的对象属性的。第一个键的属性是跟接收者相关的,而每一个子系列是跟前一个属性相关的。比如键路径address.street,这个键路径会首先从接收者获得address属性,然后从address属性中获得street属性。
用KVC获得属性的值
方法valueForKey:会返回跟接收者相关的key的值。如果对于指定的key没有访问器或者实例变量,则给自己发送一个valueForUndefinedKey:消息,这个方法的默认实现是抛出一个NSUndefinedKeyException。子类可以重写这个方法。
同样的,valueForKeyPath:返回跟接收者相关的键路径的值。对于子系列中任何不遵循KVC的对象,都会收到一个valueForUndefineKey:消息。
dictionaryWithValuesForKeys:会检索数组中所有跟接收者相关的key的值。返回的NSDictionary中包含了数组中所有key的值。
注意:集合对象,比如NSArray、NSSet和NSDictionary中不能将nil作为一个值。相反的,应该用NSNull对象代替nil。NSNull是一个代表nil的对象属性。dictionaryWithValuesForKeys:和setValuesForKeysWithDictionary:方法的实现中,默认会在nil和NSNull之间进行转换。在你的对象中,不需要对nil做显示的测试。
用KVC设置属性的值
方法setValue:forKey:是将接收者中相关key的值设置成指定的值。在这个方法的实现中,会将NSValue的值转换成普通的数值然后赋给属性。
如果指定的key不存在,会给接收者发送一个setValue:forUndefinedKey:消息。这个方法的默认实现是抛出一个NSUndefinedKeyException异常,子类可以重写这个方法来自定义默认行为。
方法setValue:forKeyPath:的实现跟前面的一样,只不过它是用来处理键路径的。
setValuesForKeysWithDictionary:方法是用指定字典里的值来赋给接收者相关的属性。这个方法的默认实现是对每一对键-值都调用一次setValue:forKey:方法,并且自动将nil转成NSNull。
最后,你要关心的当尝试将一个nil值赋给一个非对象类型的时候该怎么办。这种情况下,接收者会发出一个setNilValueForKey:消息,这个方法的默认实现是抛出一个NSInvalidArgumentException。在你的应用中可以重写这个方法来定义一个默认值,然后用新的值触发setValue:forKey
iOS中的KVC简介
Key-Value Coding 俗称"键值编码",苹果官方简称这个模式为KVC编码模式,也就是说可以通过一个Key去访问某一个属性,或者给对象去赋值,而不需要去明确存取方法,这样就可以动态的访问和修改对象的属性,而不是在编译的时候去确定,这也是iOS开发中的一大便利,其实有很多的框架和功能是用KVC去实现的,这个技术存在已经很长时间了,在网上也有很多相关的教程去教童鞋们如何去使用KVC,在这里,我们就只是简单的介绍一下KVC的底层实现和使用方法。
从苹果官方对KVC的解释来看,其实KVC在Fundation框架中占有很高的地位,诸如Core-Data之类的框架都使用到了KVC技术,我们在开发中可能常见的API有:
NSKeyValueCoding类别中还有其他的一些比较重要方法,如下:
说起KVC的执行流程,我们有很多初级工程师都不大清楚,只知道KVC是如何使用的,而不知道KVC是怎么Key的寻找策略的。下图我们借鉴了MJ老师的两幅PPT来解释
上图我们可以看到
简单说KVC机制在设值的时候会按照 setKey: 》_setKey 》_key 》_isKey 》key 》 isKey 顺序搜索成员并进行赋值操作,但是如果开发者重写了类方法+ (BOOL)accessInstanceVarialbesDirectly并且让其返回NO,这样在搜索的时候会直接从步骤 1跳转到步骤5 。
举一个例子,我们先创建一个Person类
然后用KVC赋值
最终在控制台打印的结果是
当调用valueForKey:方法时,KVC对key的搜索顺序有点不同于setValue:forKey:方法,大致步骤如下:
如
最终打印为
上述可以看出,当Key查找不到值的时候会走 valueForUndefinedKey 方法中抛出异常
类的成员变量有可能是自定义类或其他复杂数据类型,对这种成员变量可以先用KVC获取该属性,然后再用KVC来获取这个自定义类的属性,这样一层层去获取,但这样比较繁琐。对此KVC提供一个解决方案,就是键路径keyPath,顾名思义就是按照路径寻找key。主要有两个以下两个方法:
在上述Person中我们创建一个Cat类
在Cat类中我们创建一个属性 name
我们如果需要用KVC对Person对象中Cat对象赋值的话,我们就必须用到KeyPath了
KVC对于keyPath的搜索机制第一步就是分离key,用小数点.来分割key,然后再像普通key一样按照上面介绍的顺序搜索。
使用KVC过程中最常见的异常就是不小心使用了错误的key,或者在设值中不小心传了nil的值,KVC有专门的方法处理这些异常。
该方法返回一个可变有序数组。对于无序的容器,可以用以下方法:
该方法返回一个可变的无序集合。同时他们也有对应的keyPath版本:
当NSDictionary对象使用KVC时,valueForKey:的表现行为和objectForKey:一样,使用valueForKeyPath:可访问多层嵌套的字典会方便点,在KVC中有两个关于NSDictionary的方法:
当开发者需要验证能不能用KVC设定某个值时,就需要在进行KVC赋值前验证值value的有效性,API文档里面提供下面的方法进行判断值的有效性。
该方法的工作原理:先找一下你的类中是否实现了方法-(BOOL)validateKey:error:,如果实现了就会根据实现方法里面的自定义逻辑返回NO或者YES,如果没有实现这个方法,则系统默认返回就是YES。
输出结果为
这里首先调用方法 [self validateValue:value forKey:key error:error] ;,这里,由于我实现了方法- (BOOL)validatePersonName:(id *)value error:(out NSError * _Nullable __autoreleasing *)outError ,所以就在这里进行值value有效性的判断,这里 [name isEqualToString:@"小明"] 我就给返回YES,否则就返回NO。
KVC在iOS开发中非常的灵活,提供了开发者更多的赋值和取值操作的选择,它的有点明显,缺点也有,如果key只写错,编写的时候不会报错,但是运行的时候会报错,在实际开发中需要开发者时刻小心自己输入的键值,也时刻提醒着开发者一旦使用KVC就要做容错处理。
iOS KVC的理解与crash场景
实现原理:
KVC的赋值本质上只是调用了属性的setter方法,setter方法会按照setKey、_setKey、setIsKey的优先级进行调用,还没有,则按_key、_isKey、key、isKey查找成员变量。
如果accessInstanceVariablesDirectly返回NO,则不会查找_key、_isKey、key、isKey,会直接调用- (void)setValue:(id)value forUndefinedKey:(NSString *)key。
若查找到isKey还是没找到,也会调用(void)setValue:(id)value forUndefinedKey:(NSString *)key,该方法默认会抛出异常。
crash的场景:
key 不是对象的属性,造成崩溃。
keyPath 不正确,造成崩溃。
key 为 nil,造成崩溃。
value 为 nil,为非对象设值,造成崩溃: [objc setValue:nil forKey:@"age"];
crash的防护:
load方法中对setValue: forUndefinedKey:和valueForUndefinedKey方法做方法替换,判断key或value为nil时return;
iOS开发常见问题
如果 xcode 中配置文件安装比较多,有些用不到了,可以到下面路径进行删除.
~/Library/MobileDevice/Provisioning Profiles
1 直接剪切
通过打印 tabBar 的子 view, 我们发现黑线的 y 值是-0.5;
这种方法虽然直接,对于去除 navi 的黑线也是有效的.但是本人不推荐这么去做.如果你的应用里没有突出的当然是可以的.
2 删除黑线
和删除 navi 的差不多,不过不用进行二次遍历.
当然这种删除方式,适合自定义的 tabBar, 如果是使用tabbarController 的属性的话,就不适用了.因为在tabbarController里我们拿不到 tabBar 的 subViews. 通过打印,会发现是空数组.这时我们可以使用KVC 使用自定义的 tabBar 去替换系统属性.
后台返回的经常会见到(null), 那么null是什么鬼, 改怎么处理.使用.通过测试,我们可以看出来下面的结果.
由上面得出以下结论.
1.nil, 表示的是空指针,指向的对象是空,地址是0x0, 打印返回的结果是(null).
2.[NSNull null],表示一个对象是空值,是有内存地址的.打印结果是null.
3.对于字符串来讲一个空字符串和字符串是空是两种概念.如 str 和str2,两种写法. 如果有时候不确定返回的字符串是@""还是 nil, 最好使用 [str isEqualToString:@""] || str.length == 0 这种判断方式. str 只使用** [str isEqualToString:@""] ** 是无效的.
在某个功能中,后台返回的是字符串,但是要显示在不同的label 中,才注意到了这个方法.
由于现在的请求是 HTTP,运行时报以下信息内容
App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.
这时候需要在info.plist中添加一个字段.
通过打印webView 的 subViews 发现, webView 的子 View 中有个 _UIWebViewScrollView 的成员变量, 所以进行猜测.
注意到了 webView 有一个scrollView 的属性,那么更简单的做法是:
只需要给 tableView 的 tableFooterView 添加一个空的 View 即可
有一个属性 background ,在习惯性找backgroundImage 的时候,有时忽略了这个属性.
有时候需要替换字符串里的字符.
在实际应用中需要获取设备的信息.系统给了一个类UIDevice, 可以用来获取一些基本信息.
如果要获取具体的设备型号,比如要给专门的机型推送一些服务的话.那个就得拿到设备的类型.
通过拿到产品类型我们可以进而判断出机型. 下面是网上总结出来的
由于创建项目时,项目名称包含中文会,Bundle Identifier 中会直接使用-代替中文.所以项目名称一般会使用英文,实在不行要使用拼音.这样的话,安装到手机中的名称要显示自己想要的名称的话.
需要在** info.plist ** 中添加一项.** Bundle display name **.
对应的 Value 就是安装app 后,显示的名称.
由于现在是Retina 屏幕,使用 CATextLayer时,设置完字体后显示会模糊.
下面看一下映射关系.
非Retina:1 Point = 1 x 1 Pixel
Retina:1 Point = 2 x 2 Pixel
由于 Retina 屏一个 Point 映射4个(2 * 2) Pixel,所以 scale为2.
所以这时牵扯到一个属性:contentsScale
有两种方案:
方案一 : 使用带属性的字符串,这种方法简单明了.
方案二 : 设置navigationItem.titleView. 给titleView赋值一个 label, 这样子的话修改起来都是常规做法.
iOS修改私有属性&&成员变量(KVC)
修改私有属性,是我们日常开发中常见的一个场景。比如修改三方库的某个属性,可三方库并没有暴露该属性在头文件。总的来说,修改私有属性,两种方案:KVC和Runtime。今天这里不介绍原理,只讲实操。
总体思路:通过继承要修改类,拿到要修改的属性或者成员变量,改变其值,再赋值给该类。
以下举例,通过子类ZXSonViewController继承父类ZXParentViewController和,在viewDidLoad方法里做演示。
父类定义了私有属性name
子类继承父类,通过KVC修改
此时使用的Api由[setValue: forKey:]换为[setValue:forKeyPath:]
父类使用自定义类一个作为属性
子类通过[setValue:forKeyPath:] 修改
当修改的是成员变量时
子类
由于struct本身并不遵循KVC协议,但是我们可以转化为NSValue解决
父类使用一个枚举做成员变量
子类通过五步操作,修改父类成员变量值
截图如下
看下打印结果
通过KVC修改私有属性或者成员变量的方法基本都列举完了,但注意KVC本身属于硬编码形式,容易操作闪退,使用时要做好安全防护。
名称栏目:ios开发kvc,ios开发kvc原理
分享URL:http://lswzjz.com/article/dsdpejc.html