【iOS】--KVC and KVO

Key-value coding (KVC) and key-value listening (KVO)

KVC (Key Value Coding) allows indirect manipulation of object properties in the form of strings.

Simple KVC

The most basic KVC is supported by the NSKeyValueCoding protocol. The two methods of the most basic operation properties are as follows

  • setVaule: attribute value forKey: set the value for the specified attribute
  • valueForKey: attribute name: get the value of the specified attribute

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface FKUser : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *pass;
@property (nonatomic, copy) NSDate *birth;
@end

NS_ASSUME_NONNULL_END


//

#import <Foundation/Foundation.h>
#import "FKUser.h"
int main(int argc, const char * argv[]) {
    
    
    @autoreleasepool {
    
    
        // insert code here...
        NSLog(@"Hello, World!");
        FKUser *user = [[FKUser alloc] init];
        [user setValue:@"花城" forKey:@"name"];
        [user setValue:@"1155" forKey:@"pass"];
        [user setValue:[[NSDate alloc] init] forKey:@"birth"];
        NSLog(@"user的name为%@", [user valueForKey:@"name"]);
        NSLog(@"user的pass为%@", [user valueForKey:@"pass"]);
        NSLog(@"user的birth为%@", [user valueForKey:@"birth"]);
        
    }
    return 0;
}

Please add a picture description
The above code first creates a user object, sets the attribute value of the object's attribute through the KVC method, and finally obtains the value of the specified attribute through the KVC method. In the KVC programming method, no matter calling the setValue:forKey: method or
calling the valueForKey: method, The property to be operated is specified through the NSString object, where the forKey tag is used to pass in the property name

setValue: attribute value forKey@"name" code

  • The program gives priority to calling "setName: attribute value"; the code completes the setting through the setter method
  • If the class does not have a setName: method, then the KVC mechanism will search for a member variable named _name in the class, no matter whether the member variable is defined in the class interface part or in the class implementation part, and no matter which access control is modified , the bottom layer of this KVC code is actually to assign a value to the _name member variable
  • If the class does not have a setName: method and does not define a _name member variable, then the KVC mechanism searches for a member variable named name in the class, regardless of whether the member variable is defined in the class interface or in the class implementation. No matter which access control character is used, the bottom layer of this KVC code is actually to assign a value to the name member variable.
  • If none of the above three steps are found, the system will execute the setValue:forUndefinrdKey: method of the object
    The default valueForUndefinedKey: method implementation is to raise an exception, which will cause the program to end due to an exception.

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface FKDog : NSObject {
    
    
    @package
    NSString *name;
    NSString *_name;
}

@end

NS_ASSUME_NONNULL_END
//

#import "FKDog.h"

@implementation FKDog {
    
    
    int age;
}
@end




        FKDog *dog = [[FKDog alloc] init];
        [dog setValue:@"戚容" forKey:@"name"];
        NSLog(@"dog->name = %@", dog->name);
        NSLog(@"dog->_name = %@", dog->_name);
        [dog setValue:[NSNumber numberWithInt:800] forKey:@"age"];
        NSLog(@"dog的age%@", [dog valueForKey:@"age"]);

Please add a picture description
We first assign a value to the name attribute of dog, but since the FKDog class does not have a setName: method, we assign a value to the member variable _name of dog. When we output dog->name, we can see the output "Qi Rong". So when we output dog->name, it is empty, but when we output dog->_name, it is not empty.
Finally, we assign and access the age attribute of dog through KVC

Handle non-existent keys

When we use the KVC method to operate attributes, these attributes may not exist, that is, there are no corresponding setter, getter methods, and corresponding member variables. KVC will automatically call the setValue:forUndefinedKey: and valueForUndefinedKey: methods, but the system defaults The two methods implemented just throw exceptions without any special handling

//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface FKApple : NSObject

@end

NS_ASSUME_NONNULL_END



//

#import "FKApple.h"

@implementation FKApple

@end



FKApple *apple = [[FKApple alloc] init];
        [apple setValue:@"monster" forKey:@"name"];
        [apple valueForKey:@"name"];
        

Please add a picture description
The prompt says that the name object the program is trying to set does not exist, so an exception is thrown.

This prompt is setValue: the default implementation of the forUndefinedKey method. This implementation triggers an NSUnknownKeyExpection exception and ends the program. The implementation of this default method may not be suitable for reality, and we may not want it to end. At this time, this method must be rewritten.

//

#import "FKApple.h"

@implementation FKApple
- (void)setValue:(id)value forUndefinedKey:(nonnull NSString *)key{
    
    
    NSLog(@"这个key不存在,你是不是敲错了?");
    NSLog(@"你尝试设定的value为:%@", value);
}
@end

Please add a picture description
Obviously, no exception occurred when we set the properties of the object through KVC, so why did it still crash?
Because there is this line of code

[apple valueForKey:@“name”];

We try to access the name key of the object through KVC, because we do not have a name method, nor do we have name, _name member variables, so KVC will call the valueForUndefinedKey: method, and we will override this method.
Please add a picture description


#import "FKApple.h"

@implementation FKApple
- (void)setValue:(id)value forUndefinedKey:(nonnull NSString *)key{
    
    
    NSLog(@"这个key不存在,你是不是敲错了?");
    NSLog(@"你尝试设定的value为:%@", value);
}
- (id)valueForUndefinedKey:(NSString *)key {
    
    
    NSLog(@"这个key不存在,你是不是敲错了?");
    return key;
}
@end

It can be found that there is no exception after rewriting.
When KVC operates a key that does not exist, the KVC mechanism always calls the rewritten method for processing. Through this processing mechanism, you can customize your own processing behavior very conveniently.

Handle nil values

When setting the properties of an object through KVC, if the property is a basic type (int, float, doule), and we give him a corresponding property value, then the program can be set correctly, but if we set a basic type of property nil, what will happen, will KVC treat this thing as 0 or 1?

//  Created by 王璐 on 2023/5/13.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface FKItem : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int price;
@end


NS_ASSUME_NONNULL_END



FKItem *item = [[FKItem alloc] init];
        [item setValue:nil forKey:@"name"];
        [item setValue:nil forKey:@"price"];
        NSLog(@"item的name为%@", [item valueForKey:@"name"]);
        NSLog(@"item的price为%@", [item valueForKey:@"price"]);
        
        

We set two properties, one is of type NSString, which can accept nil, and the other is of type int, which cannot accept nil.
Please add a picture description
Prompt us that an NSInvidalArguementExpection exception was raised. This is because the value of int type cannot accept nil.
This message is generated by the setNilValueForKey: method in the program. That is to say, when the program tries to set a nil value for a property value, if the property does not accept the nil value, then the program will automatically execute the setNilValueForKey: method of the property. Let's rewrite this method.

//

#import "FKItem.h"

@implementation FKItem
- (void)setNilValueForKey:(NSString *)key{
    
    
    if ([key isEqualToString:@"price"]) {
    
    
        _price = 0;
    } else {
    
    
        [super setNilValueForKey:key];
    }
}
@end

Please add a picture description
If we try to set the attribute whose key is price to nil, then the program will directly set the _price member variable to 0, indicating that the price attribute of FKItem will treat nil as 0, and other attributes will not be processed.

key path

In addition to the properties of the operable object, KVC can also operate the compound properties of the object. The so-called composite attribute, KVC calls it the key path. For example, the KFOrder object contains a FKItem type attribute item, and FKitem contains the name attribute and price attribute, then KVC can operate the item attribute and price attribute in the FKOrder object through the key path of item.name, item.price
The method of operating the key path in KVC is as follows.

  • setValue: forKeyPath: set the attribute value according to the key path
  • valueForKeyPath: Get the attribute value according to the key path

Please add a picture description


#import <Foundation/Foundation.h>
#import "FKItem.h"
NS_ASSUME_NONNULL_BEGIN

@interface FKOrder : NSObject
@property (nonatomic, strong) FKItem *item;
@property (nonatomic, assign) int amount;
- (int)totalPrice;
@end

NS_ASSUME_NONNULL_END



FKOrder *order = [[FKOrder alloc] init];
        [order setValue:@"5000" forKey:@"amount"];
        [order setValue:[[FKItem alloc] init] forKey:@"item"];
        [order setValue:@"书包" forKeyPath:@"item.name"];
        [order setValue:[NSNumber numberWithInt:5] forKeyPath:@"item.price"];
        NSLog(@"订单包含%@个%@, 总价为%@",[order valueForKey:@"amount"], [order valueForKeyPath:@"item.name"], [order valueForKey:@"totalPrice"]);
        

setValue: forKey:, valueForKey: Set the composite attribute and get the value of the composite attribute, so that you can directly operate the name attribute of the item attribute in the FKOreder object, and you can also directly operate the price attribute of the item attribute in the FKOreder object. The collection of OC also
supports Use KVC for overall operation
Why use KVC for operation? Isn't it okay to use setters and getters? Is this more efficient? In fact, the performance of KVC operations is worse than that of setters and getters. The advantage of using KVC is that it is more flexible and more suitable for extracting some general-purpose codes. Because the KVC method allows the properties of objects to be manipulated through strings, this string can be either a constant or a variable. So it has a very high flexibility.

KVO (key-value listening)

During the development of iOS applications, iOS applications usually divide the application components into data model components and view components. The data model component is responsible for maintaining the state data of the application, and the view component is responsible for displaying the state data inside the data model component.

Our requirement is: when the state data of the data model component changes, the view component can dynamically update itself and display the updated data of the data model component in time.
insert image description here
Please add a picture description

//

#import <Foundation/Foundation.h>
#import "FKItem.h"
NS_ASSUME_NONNULL_BEGIN

@interface FKItemView : NSObject
@property (nonatomic, weak) FKItem *item;
- (void)showItemInfo;
@end

NS_ASSUME_NONNULL_END

In order to allow FKItemView to monitor the state changes of the FKItem component, we need to add a listener for monitoring property changes to the FKItem component. and override the observerValueForKeyPath:change:context: method of the listener

//

#import <Foundation/Foundation.h>
#import "FKItem.h"
NS_ASSUME_NONNULL_BEGIN

@interface FKItemView : NSObject
@property (nonatomic, weak) FKItem *item;
- (void)showItemInfo;
@end

NS_ASSUME_NONNULL_END



//

#import "FKItemView.h"

@implementation FKItemView
- (void)showItemInfo {
    
    
    NSLog(@"物品名称为%@, 物品价格为%d", self.item.name, self.item.price);
}
- (void)setItem:(FKItem *)item {
    
    
    self->_item = item;
    [self.item addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    [self.item addObserver:self forKeyPath:@"price" options:NSKeyValueObservingOptionNew context:nil];
    
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    
    
    NSLog(@"bserveValueForKeyPath");
    NSLog(@"keyPath:%@", keyPath);
    NSLog(@"object:%@", object);
    NSLog(@"change objectForKey:%@", [change objectForKey:@"new"]);
    NSLog(@"change:%@", context);
}
- (void)dealloc {
    
    
    [self.item removeObserver:self forKeyPath:@"name"];
    [self.item removeObserver:self forKeyPath:@"price"];
}
@end



FKItem *item = [[FKItem alloc] init];
        item.name = @"花城";
        item.price = 800;
        FKItemView *view = [[FKItemView alloc] init];
        view.item = item;
        [view showItemInfo];
        item.name = @"huacheng";
        item.price = 10000;
        

Please add a picture description
We can see the output of the listener's monitoring method. It can be seen that the listener can get the properties we modified, and then we can add these modified properties to the UI.

Guess you like

Origin blog.csdn.net/weixin_61196797/article/details/130647341