iOS面试题(十)Objective-C语言--KVO、KVC&属性关键字

Objective-C语言

  •  分类(什么是分类?分类的实现机制、原理是怎样的?我们应该怎样为一个分类提供实例变量呢?--涉及到第2点关联对象的考察)
  • 关联对象(Runtime分析和查看 分类和关联对象的实现机制、原理)
  • 扩展、代理怎么使用(分类和扩展的区别在哪里?)
  • NSNotification(通知的实现机制和原理)
  • KVO、KVC( KVO、KVC的实现机制、原理、包括涉及到了哪些设计思想)
  • 属性关键字(声明属性时,会用到一些关键字的实际问题)

KVO的相关面试问题

什么是KVO,KVO的实现机制是怎样的?

  1. KVO是NSObject的一个分类
  2. 是OC对于观察者设计模式的一种实现(每次当被观察对象的某个属性值发生改变时,注册的观察者便能获得通知)
  3. Apple使用isa混写技术(isa-swizzling)来实现KVO,本质是重写了setter方法

isa混写技术在KVO当中是怎样实现的呢?

  • A-->当调用了addObserver: forKeyPath: 方法
  • 系统会在运行时,为这个类动态的创建了一个子类(NSKVONotifying_A  继承关系),改写isa指向(将原来类A的isa指针指向NSKVONotifying_A这个类),同时重写setter方法,来实现KVO机制

通过下图可以看到,在调用[addObserver forKeyPath]之前,类还是MObject,在调用方法之后,类就变成了NSKVONotifying_MObject,其中isa指针的指向也发生了修改

KVO原理是重写setter方法,NSKVONotifying_A类实际是类A的子类,之所以继承是为了重写类A的setter方法, NSKVONotifying_A通过对setter方法的重写,达到了可以通知所有观察者的目的

为什么更改setter就可以实现KVO的监听呢

NSKVONotifying_A重写了原来类的setter方法,具体实现是下面两行代码


- (void)setValue:(id)obj
{
    
    [self willChangeValueForKey:@"keyPath"];
    //调用父类实现,也即原类的实现
    [super setValue:obj];
    [self didChangeValueForKey:@"keyPath"];
}
  • 首先调用willChangeValueForKey方法
  • 然后调用父类实现,也就是原来类的setter实现
  • 然后调用didChangeValueForKey,这个方法会触发observeValueForKeyPath这个KVO回调,来通知观察者value发生了变化
     

   1 通过kvc设置value能否生效?2. 通过成员变量直接赋值value能否生效?

 1.kvc  [obj setValue:@2 forKey:@"value"]  可以调到对应对象的set方法上面,set方法已经被被动态运行时创建的子类重写了,那么就可以使KVO拿到生效这样的一个结果。

2.手动kvo,我们在通过成员变量直接赋值的时候,在他之前和之后分别添加上willChangeValueForKey和didChangeValueForKey就可以实现手动kvo。然后didChangeValueForKey在系统内部实现当中,会触发我们的kvo回调observeValueForKeyPath。


@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    //实现KVO
    MObject *obj = [[MObject alloc] init];
    MObserver *observer = [[MObserver alloc] init];
    
    //调用kvo方法监听obj的value属性的变化
    [obj addObserver:observer forKeyPath:@"value" options:NSKeyValueObservingOptionNew context:NULL];
   
    //通过setter方法修改value
    obj.value = 1;
    // 1 通过kvc设置value能否生效?
    [obj setValue:@2 forKey:@"value"];
    // 2. 通过成员变量直接赋值value能否生效?
    [obj increase];
    
    return YES;
}



- (void)increase
{//手动kvo
    //直接为成员变量赋值   模拟系统方法的setter实现
    [self willChangeValueForKey:@"value"];
    _value += 1;
    //模拟系统方法的setter实现
    [self didChangeValueForKey:@"value"];
}

KVO总结:

  1. 使用setter方法改变值,KVO回调可以生效
  2. 使用setValue: forKey: 改变值,KVO回调可以生效
  3. 成员变量直接赋值KVO无法生效,必须手动添加KVO两个方法,KVO回调才会生效

 

 

KVC的相关面试问题

什么是KVC?(我们通过键值编码技术,是否会破坏面向对象的编程方法,过着说有违背于面向对象编程思想呢?会破坏。)

KVC是一种键值编码机制(key-value),通过NSKeyValueCoding协议来间接访问成员变量
它会破坏面向对象编程思想,上面的key是没有任何限制的,当我们知道一个类内部的某个私有成员变量名称,就可以通过key进行设置和访问

KVC怎么用?

可以随意修改一个对象的属性或者成员变量,因为key没有限制,私有也可以

赋值: 设置某一对象当中和key同名或者相似名字的实例变量的值
- (void)setValue:(id)value forKey:(NSString *)key;
- (void)setValue:(id)value forKeyPath:(NSString *)key; 通过点可层层赋值

取值: 获取和key同名或者相似名字的实例变量的值
- (id)valueForKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)key;

KVC系统内部实现流程:

`赋值过程`
先找相关方法set<Key>  _set<Key>  setIs<Key>
若没有相关方法,判断是否可以直接方法成员变量accessInstanceVariablesDirectly == YES ?
YES:继续找相关变量  _<Key>  _is<Key>  <Key>  is<Key>赋值,若都不存在,则调forUndefinedKey,抛出异常
NO:执行setValue: forUndefinedKey 系统抛出异常:未定义的key


取值过程
先找相关方法get<Key>  <Key>  <countOfKey> && objectInKeyAtIndex
若没有相关方法,判断是否可以直接方法成员变量accessInstanceVariablesDirectly == YES ?
YES: 继续找相关变量  _<Key>  _is<Key>  <Key>  is<Key>赋值,若都不存在,则调forUndefinedKey,抛出异常
NO: 执行setValue: forUndefinedKey 系统抛出异常:未定义的key

其他
赋值为空: setNilValueForKey
key值不存在: setValue: forUndefinedKey
validateValue方法的工作原理: 属性值正确性验证,用来检查set的值是否正确、为不正确的值做一个替换值或者拒绝设置新值并返回错误原因


 

属性关键字

#属性关键字可以分为三类#
//带星标的为默认
1.读写权限: *readwrite  readonly  
2.原子性:   *atomic    nonatomic
3.引用计数
*strong(ARC) / return(MRC)  #这两个关键字都是用来修饰对象
*assign / unsafe_unretained(MRC)  #assign既可以修饰基本数据类型,也可以修饰对象类型
weak
copy


//atomic修饰的属性: 可以保证对该成员变量的赋值和获取数据是线程安全的
ps:
@property(nonatomic, retain) UITextField *userName;
@property(atomic, retain) UITextField *userName;
如果我们用atomic修饰了一个数组,那么对这个数组进行赋值和获取,是可以保证线程安全的
但如果我们对数组进行操作,添加或移除对象,那么是不在atomic的负责范围内,所以是没有办法保证线程安全的
后两行是一样的,不写的话默认就是atomic。

atomic 和 nonatomic 的区别在于,系统自动生成的 getter/setter 方法不一样。如果你自己写 getter/setter,那 atomic/nonatomic/retain/assign/copy 这些关键字只起提示作用,写不写都一样。

对于atomic的属性,系统生成的 getter/setter 会保证 get、set 操作的完整性,不受其他线程影响。比如,线程 A 的 getter 方法运行到一半,线程 B 调用了 setter:那么线程 A 的 getter 还是能得到一个完好无损的对象。
而nonatomic就没有这个保证了。所以,nonatomic的速度要比atomic快。

不过atomic可并不能保证线程安全。如果线程 A 调了 getter,与此同时线程 B 、线程 C 都调了 setter——那最后线程 A get 到的值,3种都有可能:可能是 B、C set 之前原始的值,也可能是 B set 的值,也可能是 C set 的值。同时,最终这个属性的值,可能是 B set 的值,也有可能是 C set 的值。
保证数据完整性——这个多线程编程的最大挑战之一——往往还需要借助其他手段。

 

assign

  1. 修饰基本数据类型,如int, BOOL等
  2. 修饰对象类型时,不改变其引用计数
  3. 会产生悬垂指针: assign所修饰的对象,在被释放之后,assign指针仍指向原对象内存地址,如果这时通过assign指针,继续访问原对象,会导致内存泄露或程序异常

weak

  1. 不改变被修饰对象的引用计数,一般使用weak是用来解决循环引用问题
  2. 所修饰的对象在被释放之后会自动置为nil

weak和assign的区别

  1. weak可以修饰对象,assign既可以修饰对象,也可以修饰基本数据类型
  2. weak所修饰的对象,在被废弃之后,weak指针置为nil. assign所修饰的对象,在被释放之后,assign指针仍指向原对象内存地址

weak和assign的联系

在修饰对象时,weak和assign都不改变被修饰对象的引用计数

weak所修饰的对象,在被废弃之后,为什么置为nil(内存管理)

copy

#NSMutableArray用copy修饰会出现什么问题?#
@property (copy) NSMutableArray *array;

若上面的NSMutableArray类型的array被copy修饰,会导致程序异常
   1.因为如果赋值过来的是NSMutableArray对象A,会对可变对象A进行copy操作,拷贝结果是不可变的,那么copy后就是NSArray
   2.如果赋值过来的是NSArray对象B,会对不可变对象B进行copy操作,拷贝结果仍是不可变的,那么copy之后仍是NSArray
  所以不论赋值过来的是什么对象,只要对NSMutableArray进行copy操作,结果一定是不可变的

但因为原来属性声明的是NSMutableArray,就会调用添加或者移除方法
但因为拷贝后的结果是不可变对象,所以一旦调用这些方法就会程序崩溃(crash)

浅拷贝:是指针拷贝,会增加被拷贝对象的引用计数,是对内存地址的复制,让目标对象指针和源对象指向同一片内存空间


 

指针A指向某个对象的内存,对象在内存当中用黑框表示,若此时对对象A进行浅拷贝,就会有个B指针指向和A同一块的内存地址
所以说,浅拷贝是对内存地址的复制,但是并没有对内存空间复制,A和B两个指针是指向了同一片内存空间

深拷贝:是内存拷贝,是让目标对象指针和源对象指针分别指向两片内容相同的内存空间


 

指针A指向某个对象的内存,对象在内存当中用黑框表示,若此时对对象A进行深拷贝,就会产生新的内存分配,然后会有一个新的指针B,指向这块新的内存
两片内存空间只是内容相同,但不是同一块内存空间

深拷贝的特点

  1. 不会增加被拷贝对象的引用计数,因为没有新指针指向原来的内存空间
  2. 产生了新的内存分配,出现了两块内存

深拷贝和浅拷贝的区别

  1. 是否开辟了新的内存空间
    深拷贝开辟了新的内存空间,浅拷贝没有开辟

  2. 是否影响了引用计数
    深拷贝没有影响,浅拷贝增加了引用计

copy关键字的使用

  1. 可变对象(mutable)copy和mutableCopy都是深拷贝
  2. 不可变对象(immutable)的copy是浅拷贝,mutableCopy是深拷贝
  3. copy方法返回的都是不可变对象,若被拷贝对象是可变对象,返回的也是不可变对象


 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值