IOS面试题(类相关) --- KVO

OC面试题目合集地址

问题1:什么是KVO

答案:

  • KVO 是key-value observing的缩写
  • KVO 是OC对观察者模式又一实现
  • 苹果用isa混写(isa-swizzling)方式来实现KVO

swizzling: 旋转



问题2: isa混写在KVO中是怎么实现的?

分析:

当我们注册KVO时候, 会调用这个方法

- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath 
options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

这个方法底层实现为


isa混写原理
  • 当我们创建类Test调用addObserver :(NSObject *)observer forKeyPath:这个方法的时候, 系统会为我们动态创建一个NSKVONotifying_Test的类, 并将test中的isa指针指向NSKVONotifying_Test

  • NSKVONotifying_TestTest子类, 目的是重写Test中的setter方法, 来达到实现改变观察者值的目的

上面其实就是KVO的机制和原理

KVO生效
  • 使用setter方法改变值KVO才会生效
  • 使用setValue:forKey:改变值KVO才会生效
  • 成员变量直接修改需手动添加KVO才会生效


KVO代码实现:

  • 创建2个类SRObject, SRObserver
观察者例子

其中SRObject.h

#import 

NS_ASSUME_NONNULL_BEGIN

@interface SRObject : NSObject

// 设置变量a, 观察变量a的变化
@property (nonatomic, assign) NSInteger a;

@end

SRObject.m

#import "SRObject.h"

@implementation SRObject

// 初始化方法
- (instancetype)init {
    
    self = [super init];
    
    if (self) {
        _a = 0;
    }
    
    return self;
}


@end

AppDelegate

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    
    // 创建类
    SRObject *obj = [[SRObject alloc] init];
    // 创建观察者
    SRObserver *observer = [[SRObserver alloc] init];
    
    // 调用kvo监听方法, 对a进行监听
    // forKeyPath 要与观察变量名字一致
    [obj addObserver:observer forKeyPath:@"a" options: NSKeyValueObservingOptionNew context:nil];
   
    // 对a赋值 
    obj.a = 666;
    
    return YES;
}

SRObserver.m

#import "SRObserver.h"
#import "SRObject.h"


@implementation SRObserver

// KVO回调方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

    // 判断是否是观察的类, 观察的值
    if ([object isKindOfClass:[SRObject class]] && [keyPath isEqualToString:@"a"]) {
        
        // 获取值打印结果
        NSNumber *testNum = [change valueForKey:NSKeyValueChangeNewKey];
        NSLog(@"SRObserver打印结果: %@", testNum);
    }
    
}

@end

创建子类

我们可以先打2个断点, 然后po读一下当前类名
可以看到当被观察者监听时会创建一个NSKVONotifying的子类

运行结果:

运行结果

可看到有 SRObserver打印结果: 666

KVO重写Set方法代码:

关键代码

  • - (void)willChangeValueForKey:(NSString *)key;
  • - (void)didChangeValueForKey:(NSString *)key;

原理

// NSKVONotifying_A的setter实现

- (void)setValue:(id)obj {
    
    [self willChangeValueForKey:@"a"];
    // 子类NSKVONotifying_A调用父类实现, 即原类实现
    [super setValue:obj]
    [self didChangeValueForKey:@"a"];
}



问题3: 成员变量赋值KVO是否生效

上面例子中的SRObject中.h, .m 和AppDelegate我们稍微变化一下

SRObject内部添加方法改变成员变量

- (void)increase {
    
    _a += 857;
}

AppDelegate调用一下

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    
    SRObject *obj = [[SRObject alloc] init];
    SRObserver *observer = [[SRObserver alloc] init];
    
    // 调用kvo监听方法
    [obj addObserver:observer forKeyPath:@"a" options: NSKeyValueObservingOptionNew context:nil];
    
    //obj.a = 666;
    [obj increase];
   
    return YES;
}

运行一下, 可以发现并无监听到, 不能监听到

但是我们可以通过手动KVO方式, 触发生效, 修改increase方法

- (void)increase {
    // 模拟系统写set 方法
    [self willChangeValueForKey:@"a"];
    _a += 857;
    [self didChangeValueForKey:@"a"];
}

手动KVO方式

didChangeValueForKey 方法之后会触发KVO回调



问题4: KVC设置成员变量赋值, KVO是否能生效

上面例子中的AppDelegate我们稍微变化一下

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    
    SRObject *obj = [[SRObject alloc] init];
    SRObserver *observer = [[SRObserver alloc] init];
    
    // 调用kvo监听方法
    [obj addObserver:observer forKeyPath:@"a" options: NSKeyValueObservingOptionNew context:nil];
    
    // obj.a = 666;
    // KVC 方法
    [obj setValue:@"12345" forKey:@"a"];
    return YES;
}

运行结果

KVC中setValue: forKey: 其实调用对象的set方法 与 .a = 666 是一致的

我们可以在SRObject中验证一下KVC是否走了对象set方法 如下

KVC调用set方法验证

重写set方法, 可看到KVC也是调用对象set方法

你可能感兴趣的:(IOS面试题(类相关) --- KVO)