【OC梳理】Copy、KVC、KVO

Copy

OC中copy的作用是:利用一个源对象产生一个副本对象,它们之间不会相互影响。

关于深拷贝与浅拷贝

深拷贝是指对对象的具体内容进行复制,并占用新的内存空间,浅拷贝就是对内存地址的复制。
自定义的类如果要深拷贝,需要遵循 NSCopying, NSMutableCopying 协议,在协议方法中实现copy相关方法。
数组的深拷贝,也需要自己将所有对象拷贝一份再添加。
下面的代码:

    NSMutableArray *array = [NSMutableArray array];
    [array addObject:[NSObject new]];
    [array addObject:[NSObject new]];
    [array addObject:[NSObject new]];
    [array addObject:[NSObject new]];
    NSArray *copyArray = [array copy];
    NSArray *copycopyArray = [copyArray copy];
    NSMutableArray *mutACopy = [array mutableCopy];
    NSMutableArray *arrCopy = [copyArray mutableCopy];
    NSLog(@"原来的数组            :%p ----> %p",&array,*&array);
    NSLog(@"copy的数组           :%p ----> %p",©Array,*©Array);
    NSLog(@"copycopy的数组       :%p ----> %p",©copyArray,*©copyArray);
    NSLog(@"mutableCopy原来的数组 :%p ----> %p",&mutACopy,*&mutACopy);
    NSLog(@"mutableCopy copy的数组:%p ----> %p",&arrCopy,*&arrCopy);

输出的结果如下:

... Demo[11327:4606426] 原来的数组            :0x7ffee226d9a0 ----> 0x60400044a9e0
... Demo[11327:4606426] copy的数组           :0x7ffee226d998 ----> 0x60400044aa10
... Demo[11327:4606426] copycopy的数组       :0x7ffee226d990 ----> 0x60400044aa10
... Demo[11327:4606426] mutableCopy原来的数组 :0x7ffee226d988 ----> 0x60400044fcc0
... Demo[11327:4606426] mutableCopy copy的数组:0x7ffee226d980 ----> 0x60400044fcf0

打断点可以看到所有的Array中的元素的地址都是相同的,并没有进行复制,并从输出结果看出:

copy的对象为MutableArray时,会有一个新的指针指向新的内存地址(新的Array对象)。
copy的对象为Array时,会有一个新的指针指向原来的内存地址(原来的Array对象)。
mutableCopy的对象为MutableArray时,会有一个新的指针指向新的内存地址(新的MutableArray对象)。
mutableCopy的对象为Array时,会有一个新的指针指向新的内存地址(新的MutableArray对象)。

参考文章:
Objective-C中的浅拷贝和深拷贝
OC数组中的深拷贝

KVC

KVC(Key Valued Coding),键值编码,即常说的反射机制,是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性,进行属性的动态读写。
对于某些private属性,如果使用KVC进行修改,这就破坏了类的封装性(当然了有些情况不得不使用KVC进行修改)。

  • KVC的主要用途是ORM映射,就是dictionary与model的互转。

常用方法

//获取值的方法
valueForKey:            //传入NSString属性的名字。
valueForKeyPath:        //传入NSString属性的路径,xx.xx形式。
valueForUndefinedKey    //它的默认实现是抛出异常,可以重写这个函数做错误处理。

修改值的方法
setValue:forKey:
setValue:forKeyPath:
setValue:forUndefinedKey:

使用KVC实现ORM

假定有一个商品model,其定义属性如下:

@property (nonatomic,copy) NSString * goodsId;      //商品ID
@property (nonatomic,copy) NSString * coverImage;   //封面
@property (nonatomic,copy) NSString * shopPrice;    //商城价(零售价)
@property (nonatomic,copy) NSString * sales;        //销量
//...

服务器返回的字符串为:

{
  "goods_id":"7",
  "cover_image":"http://www.nenyimall.com/products/H6200IFLO.jpg",
  "shop_price":"¥15.00",
  "sales":"139",
  ...
}

其中有3个字段的key与我们定义的属性名有区别,这时候,只需重写下面两个方法:

-(void)setValue:(id)value forUndefinedKey:(NSString *)key{
    if ([key isEqualToString:@"goods_id"]) {
        [self setValue:value forKey:@"goodsId"];
    }else
    if ([key isEqualToString:@"cover_image"]) {
        [self setValue:value forKey:@"coverImage"];
    }else
    if ([key isEqualToString:@"shop_price"]) {
        [self setValue:value forKey:@"shopPrice"];
    }
}

-(id)valueForUndefinedKey:(NSString *)key{
    id result = nil;
    if ([key isEqualToString:@"goods_id"]) {
        result = [self valueForKey:@"goodsId"];
    }else
    if ([key isEqualToString:@"cover_image"]) {
        result = [self valueForKey:@"coverImage"];
    }else
    if ([key isEqualToString:@"shop_price"]) {
        result = [self valueForKey:@"shopPrice"];
    }
    return result;
}

然后就可以使用setValuesForKeysWithDictionary:将dictionary转换为model;
使用dictionaryWithValuesForKeys:将属性转换为dictionary。
参考文章:
iOS开发-OC篇-KVC详解

KVO

KVO(Key Valued Observer),键值观察,是使用获取其他对象的特定属性变化的通知机制。所有NSObject的子类都支持这个机制。

常用语法

/**
 创建一个观察者
 @param observer 观察者
 @param keyPath 被观察的属性
 @param options 传递给接收者的值的类型 NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
 @param context 上下文,可用于区分注册者
 */
[被观察对象 addObserver:<#(nonnull NSObject *)#> forKeyPath:<#(nonnull NSString *)#> options:<#(NSKeyValueObservingOptions)#> context:<#(nullable void *)#>];

/**
 观察者的回调方法
 @param keyPath 被观察的属性
 @param object 被观察的属性值
 @param change 变化的记录
 @param context 上下文
 */
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
  //do something
}

/**
 移除观察者
 @param observer 观察者
 @param keyPath 被观察的属性
 */
[被观察对象 removeObserver:<#(nonnull NSObject *)#> forKeyPath:<#(nonnull NSString *)#>];

局限性

父类和子类同时存在KVO时(监听同一个对象的同一个属性),很容易出现对同一个keyPath进行两次removeObserver操作,从而导致程序crash。要避免这个问题,就需要区分出KVO是self注册的,还是superClass注册的,我们可以在 -addObserver:forKeyPath:options:context:和-removeObserver:forKeyPath:context这两个方法中传入不同的context进行区分。

参考文章:
OC中KVO的基本概念和使用方法
iOS开发-KVO的奥秘
iOS下KVO使用过程中的陷阱

你可能感兴趣的:(【OC梳理】Copy、KVC、KVO)