OC学习_12_KVO_反射_简单工厂

本文介绍了Objective-C中的KVC KeyPath用法,详细阐述了KVO(Key-Value Observing)的工作原理及注意事项,探讨了如何通过反射获取Class并创建对象,以及简单工厂模式的应用。内容包括KVC的setValue:forKeyPath:方法,KVO的观察者模式,反射中的NSClassFromString函数,以及简单工厂模式的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

======文档更新状态=====
2015-12-27:发布

==================

day12-[2015-12-25]
一、KVC之KeyPath
1.当一个类具有嵌套对象属性时,需要使用KeyPath来给嵌套对象的属性直接赋值
2.语法:setValue:forKeyPath:
对应的get方法为:valueforKeyPath:
其中参数keyPath的写法为:使用.符号分隔的若干属性名称,如:horse.name、self.horse.speed
3.执行过程(以set为例):keyPath中最后一个分隔符.前面的都是通过valueForKey:进行查找,最后一个分隔符.后面的才通过setValue:forKey:赋值

//  YYHero1.h

#import "YYHorse.h"


@interface YYHero: NSObject
@property (nonatomic,copy)NSString* name;
@property (nonatomic,strong)YYHorse* horse;

@end
//  YYHero1.m

#import "YYHero.h"


@implementation YYHero

-(NSString *)description{
    return [NSString stringWithFormat:@"Hero %@ has horse %@.",_name,_horse.name];
}

-(void)setValue:(id)value forUndefinedKey:(NSString *)key{
    NSLog(@"set:%@ not founded.",key);
}

-(id)valueForUndefinedKey:(NSString *)key{
    NSLog(@"get:%@ not founded.",key);
    return nil;
}


@end
//  YYHorse.h

#import <Foundation/Foundation.h>


@interface YYHorse : NSObject
@property (nonatomic,copy)NSString* name;
@property (nonatomic,assign)int speed;
@end
//  YYHorse.m


#import "YYHorse.h"

@implementation YYHorse
-(NSString *)description{
    return [NSString stringWithFormat:@"Horse %@'s speed is %d.",_name,_speed];
}

-(void)setValue:(id)value forUndefinedKey:(NSString *)key{
    NSLog(@"horse set:%@ not founded.",key);
}


@end
//  main.m

#import "YYHero.h"

int main0(int argc, const char * argv[]) {
    @autoreleasepool {
        YYHero* h=[YYHero new];
        YYHorse* m=[YYHorse new];

        //通过KVC给Horse赋值
        [m setValue:@"Chitu" forKey:@"name"];
        [m setValue:@100 forKey:@"speed"];
        NSLog(@"m:%@",m);
        //通过KVC给Hero赋值
        [h setValue:@"Lvbu" forKey:@"name"];
        [h setValue:m forKey:@"horse"];
        NSLog(@"h:%@",h);

        //通过KeyPath为嵌套对象赋值
        YYHero* h1=[YYHero new];
        [h1 setValue:@"Tang" forKey:@"name"];

        [h1 setValue:[YYHorse new] forKey:@"horse"];
        // 如果没有上面把马horse赋值给hero则下面的赋值无效但不会报错因为nil的存在
        [h1 setValue:@"Bailong" forKeyPath:@"horse.name"];
        [h1 setValue:@80 forKeyPath:@"horse.speed"];
//        NSLog(@"hero.horse.name:%@",h.horse.name);
        NSLog(@"h1:%@",h1);
        NSLog(@"h1.horse:%@",h1.horse);
        NSLog(@"h1.horse.name:%@",
              [h1 valueForKeyPath:@"horse.name"]);
        //执行过程:最后一个分隔符.前面的都是通过valueForKey进行查找,最后一个分隔符.后面的才通过setValue:forKey:赋值
        [h1 setValue:@"Heilong" forKeyPath:@"horse.name"];
        //可以使用keyPath为当前对象的属性赋值,实际意思是:self.属性名
        [h1 setValue:@"Xuanzang" forKeyPath:@"self.name"];
        NSLog(@"h1.name:%@",[h1 valueForKeyPath:@"name"]);
    }
    return 0;
}

//注意:
//1>[h1 setValue:[YYHorse new] forKey:@"horse"];这行
// 因为YYHero对YYHorse是强引用@property (nonatomic,strong)YYHorse* horse;
// 顾没有问题,但是如果上面的strong改为weak [YYHorse new]创建后就被销毁
//
//2>setValue:@"xxx"forKeyPath:aaa.bbb执行过程
//最后一个分隔符.前面的都是通过valueForKey进行查找(如果在当前类中没有找到就在当前类
//中报valueForUndefined错误),最后一个分隔符.后面的才通过setValue:forKey:赋值
//
//3>注意
//-(id)valueForUndefinedKey:(NSString *)key
//{
//    
//    NSLog(@"can't found key[%@]",key);
//    
//    return nil;
//    
//}
//返回的nil
//当以这种形式:setValue:@"xxx"forKeyPath:aaa.bbb时
//访问aaa是以valueForKey的方式,当访问不到aaa时会调用valueForUndefinedKey:返回nil表示后面的都不在执行
//

二、KVO
1.观察者模式:在某个对象上针对某个或某几个属性添加观察者(addObserver:),当对象被观察的属性值发生变化,则观察者会执行相应方法(observeForKeyPath:……),在观察完成后。对象应及时移除(removeObserver:)观察者,否则销毁对象时会引起异常
2. addObserver :方法的options参数采用8421编码:1000 0100 0010 0001
1<<1 1<<2 1<<10
一般采用8421编码的枚举值都是可以使用多个值的,建议使用按位或|,使用+运算符也可以,但效率较低
3.注意事项:当重写setter:方法添加观察者时,需判断目标对象是否与参数对象相同,如果不同,需将原有对象的观察者移除才能添加,否则有可能造成销毁异常

//  YYData.h

#import <Foundation/Foundation.h>

@interface YYData : NSObject
@property (nonatomic,copy)NSString* name;
@property (nonatomic,assign)int price;
@end
//  YYData.m

#import "YYData.h"

@implementation YYData
-(void)dealloc{
    NSLog(@"data %@ dealloc",_name);
}
@end
//  YYView.h

#import "YYData.h"

@interface YYView : NSObject
@property (nonatomic,strong)YYData* data;
@end
//  YYView.m


#import "YYView.h"

@implementation YYView
-(void)setData:(YYData *)data{
    if (_data!=data) {
        [_data removeObserver:self
                   forKeyPath:@"name"];
        [_data removeObserver:self
                   forKeyPath:@"price"];
        _data=data;
        [_data addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
        [_data addObserver:self forKeyPath:@"price" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
    }    
}

-(void)dealloc{
    [_data removeObserver:self forKeyPath:@"name"];
    [_data removeObserver:self
               forKeyPath:@"price"];
    NSLog(@"view dealloc");
}
/**
 *  观察者观察到目标数据发生变化时执行的方法,必须由用户给出实现
 *
 *  @param keyPath 受观察的目标的属性
 *  @param object  受观察的目标
 *  @param change  数据的变化,里面存放的内容由addObserver时的options参数决定
 *  @param context nil
 */
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    NSLog(@"keyPath:%@",keyPath);//name
    NSLog(@"object:%@",[object class]);//YYData
    NSLog(@"change:%@",change);//{old=xxx,new=xxx}
    NSLog(@"new value:%@",[change valueForKey:@"new"]);
    NSLog(@"old value:%@",[change valueForKey:@"old"]);
    // 当添加多个属性监控时对每个属性监控的操作可能不一样
    //如果是目标对象的price属性发生变化,输出变化前后的差值
    if ([keyPath isEqual:@"price"]) {
        int diff=[[change valueForKey:@"new"]intValue]-[[change valueForKey:@"old"]intValue];
        NSLog(@"diff:%d",diff);
    }

}
@end
//  test_kvo2.m

#import "YYView.h"

int main(){

    @autoreleasepool {
        YYData* data1=[YYData new];
        YYData* data2=[YYData new];
        YYView* view=[YYView new];
        data1.name=@"tom";
        data2.name=@"mike";

        view.data=data1;
        data1.name=@"jerry";
        NSLog(@"-----------");
        data1.price=10;
        NSLog(@"-----------");
        view.data=data2;// 注意这里换了data,view类中setter的写法
        data2.name=@"jack";
        NSLog(@"-----------");
        data2.price=20;
    }
    return 0;
}



//注意:
// 1> 在被观察者data被销毁之前要保证观察者view移除观察否则运行会出错(牢记!!)
//    所有在view中用strong还是weak引用data要遵循上面的规则,自己斟酌到底用哪个修饰符!!
//    修饰符会影响到代码的顺序:如果用strong无需特别关注代码的顺序,如果用weak要注意代码的顺序
//
// 2> 注意上面这句:view.data=data2;注意这里换了data,view类中setter的写法中加了判断,如果不换data则在
// setter中不需要加判断正是由于换了所以要加判断
//
// 3> 当添加多个属性监控时对每个属性监控的操作可能不一样
//  注意observeValueForKeyPath:ofObject: change: context: 方法中的操作写法

三、反射
1.获取Class,除了以前的类和对象方法class外,还可以通过库函数NSClassFromString(类名字符串)获取
2.通过获取到的类类型对象,可以用于创建该类实例,再通过KVC给对象实例赋值,整个过程无需导入该类的头文件
3.简单工厂模式,三大要素:父类产品、子类产品(可以多个),工厂类(包含一个创建产品的类方法)
// 简单工厂模式实现

//  YYDrink.h
//  1225

#import <Foundation/Foundation.h>
/**
 *  易拉罐饮料(父类产品)
 */
@interface YYDrink : NSObject
@property (nonatomic,copy)NSString* name;
@property (nonatomic,assign)int price;
@end
//  YYDrink.m

#import "YYDrink.h"

@implementation YYDrink
-(NSString *)description{
    return [NSString stringWithFormat:@"Drink %@'s price is %d.",_name,_price];
}
@end
//  YYCole.h

#import "YYDrink.h"
/**
 *  可乐(子类产品A)
 */
@interface YYCole : YYDrink

@end
//  YYCole.m

#import "YYCole.h"

@implementation YYCole

@end
//  YYSpirit.h

#import "YYDrink.h"
/**
 *  雪碧(子类产品B)
 */
@interface YYSpirit : YYDrink

@end


//  YYSpirit.m

#import "YYSpirit.h"

@implementation YYSpirit

@end
//  YYFactory.h

#import "YYDrink.h"
/**
 *  简单工厂类:用于生产YYDrink的子类产品
 */
@interface YYFactory : NSObject
+(YYDrink*)createDrink:(NSString*)type;
@end
//  YYFactory.m

#import "YYFactory.h"

@implementation YYFactory
+(YYDrink *)createDrink:(NSString *)type{
    Class clz=NSClassFromString(type);
    id dnk=[clz new];
    [dnk setValue:type forKey:@"name"];
    [dnk setValue:@3 forKey:@"price"];
    return dnk;    
}
@end
//  test_fac.m

#import "YYFactory.h"
#import "YYDrink.h"
int main0() {
    //通过配置文件存储要创建的子类对象类型

    //将指定文件中的内容按照指定编码格式存储为NSString对象
    NSString* type=[NSString stringWithContentsOfFile:@"/Users/yangweibing/Desktop/drink.txt" encoding:NSUTF8StringEncoding error:nil];

    YYDrink* d=[YYFactory createDrink:type];
    NSLog(@"%@",d);
    NSLog(@"class:%@",[d class]);//YYCole
    return 0;
}



===========EOF===============

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值