D20:KVC, KVO, MRC手动内存管理实践

一. KVC

key value coding(键值编码)

setValue:forKey:
valueForKey:

KVC是给对象属性或成员变量赋值的一种方式
系统内部采用的是元数据的方式

  1. KVC如何设置属性或成员变量的值
  1. 如果将成员变量设置为nil值
  2. keyPath 设置属性值
  3. KVC的获取方法

二. KVO :以实现tableView的滚动指示视图为例

key value observe

MVC
模型数据和视图对象之间需要通信

  1. 给视图类加一个模型类的属性
  2. 使用通知中心(NSNotificationCenter)
  3. KVO

KVO: 一个对象去监听另外一个对象的某一个属性, 当该属性变化时, 做相应的处理

ColorView对象去监听ColorModel对象的color属性, 当color属性变化时,

  1. 设置tableView, 手动添加30条数据
  1. 在主视图控制器的viewDidLoad方法中添加两个视图, 显示表格滑动的百分比, 被观察者对象调用方法- (void)addObserver:<#(NSObject *)#> forKeyPath:<#(NSString *)#> options:<#(NSKeyValueObservingOptions)#> context:<#(void *)#>
  2. 观察者对象所属类实现 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

三. 利用MRC手动内存管理编写程序

黄金法则:

  • alloc copy new mutableCopy创建对象, 自动引用计数器为1
  • 调用retain时会将引用计数加1, release会将引用计数减1
  • 在什么地方增加了引用计数, 就要在什么地方减少引用计数

释放次数多了, 程序会崩溃
忘记释放, 内存泄露(不影响程序的使用)

  1. 新建5个标签栏视图控制器类
  1. MRC黄金法则和小试牛刀
  2. 创建导航视图控制器, 设置完导航视图控制器的根视图控制器后将根视图控制器对象释放; 设置主视窗的视图控制器 为tabBar, 然后释放TabBar
  3. InfoViewController.m: 类方法新建成员对象后需要retain
  4. 下载数据, 遵守协议, 新建表格视图(要在dealloc方法中释放数据源和表格视图属性)
  5. 新建模型类(retain属性) , 需要在模型类的dealloc方法中释放模型类的成员变量
  6. 处理下载数据(只写了需要注意内存释放的部分), tableView的代理方法(自动释放池的使用)

一. KVC

  1. KVC如何设置属性或成员变量的值
     User *user1 = [[User alloc] init];
     user1.name = @"余文乐";
     NSLog(@"%@", user1.name);
     
     // KVC
     // 一. KVC如何设置属性或成员变量的值
     
     // 1. 使用KVC的方式赋值默认首先调用对应的set方法
     // 默认去找setName:方法, 如果找到就调用
     [user1 setValue:@"吴彦祖" forKey:@"name"];
     NSLog(@"name:%@", user1.name);
     
     // 2. 找不到set方法, 会去找同名的以下划线开头的成员变量
     [user1 setValue:@"China" forKey:@"country"];
     NSLog(@"Country:%@", user1->_country);
     
     // 3. 找不到前两者的情况下, 找同名的成员变量
     [user1 setValue:@"HongKong" forKey:@"city"];
     [user1 showCity];
     
     // 4. 上面三种情况都不符合
     // 会调用setValue:forUndefinedKey:方法
     // 这个方法的默认实现是抛出一个异常
     [user1 setValue:@"尖沙咀" forKey:@"address"];
     /*
      Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key address.'
      */
     /*
      是将字典里面的key值遍历, 调用了setValue:forKey:方法
      - (void)setValuesForKeysWithDictionary:(NSDictionary *)keyedValues
      */  
    
  2. 如果将成员变量设置为nil值
    • 成员变量

        // 性别
        NSString *_gender;
        // 年龄
        int age;  
      
    • 设置成员变量

        [user1 setValue:nil forKey:@"gender"];
        
        // 设置基本类型的值, 不会报错
        // @50 == [NSNumber numberWithInt:50];
        [user1 setValue:@50 forKey:@"age"];
        
        [user1 setValue:nil forKey:@"age"];
        /*
         Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '[ setNilValueForKey]: could not set nil as the value for the key age.'
         */  
      
    • 将基本类型设为nil时报错的解决办法

        // 设置nil值的时候调用这个方法
        - (void)setNilValueForKey:(NSString *)key
        {
            if ([key isEqualToString:@"age"]) {
                age = 0;
            } else {
                [super setNilValueForKey:key];
            }
        }
      
  3. keyPath 设置属性值
    • house.h

        #import 
        
        @interface House : NSObject
        
        @property (nonatomic, assign) float price; // 百万元为单位
        
        @end
      
  • ViewController.m
    House *h = [[House alloc] init];
    user1.house = h;

      // 设置房子的价格
      [user1 setValue:@3 forKeyPath:@"house.price"];
      NSLog(@"%F", user1.house.price);
    
  1. KVC的获取方法

    valueForKey:@"name"

    1. 先去找对应的getter方法(例如@"name"会去找 -(NSString *)name; 方法)
    2. 找以下划线开始的成员变量的值(例如: _name)
    3. 获取同名的成员变量的值(例如name)
    4. 上面三个不符合, 调用valueForUndefinedKey:方法

二. KVO: 以实现tableView的滚动指示视图为例

  1. 设置tableView, 手动添加30条数据
  2. 在主视图控制器的viewDidLoad方法中添加两个视图, 显示表格滑动的百分比, 被观察者对象调用方法- (void)addObserver:<#(NSObject *)#> forKeyPath:<#(NSString *)#> options:<#(NSKeyValueObservingOptions)#> context:<#(void *)#>
     - (void)viewDidLoad {
         [super viewDidLoad];
         // Do any additional setup after loading the view, typically from a nib.
         [self createTableView];
         [self creatDataArray];
         
         // 添加两个视图, 显示表格滑动的百分比
         UIView *grayView = [[UIView alloc] initWithFrame:CGRectMake(330, 60, 20, 500)];
         grayView.backgroundColor = [UIColor grayColor];
         [self.view addSubview:grayView];
     
         UIView *redView = [[UIView alloc] initWithFrame:CGRectMake(330, 60, 20, 0)];
         redView.backgroundColor = [UIColor redColor];
         redView.tag = 200;
         [self.view addSubview:redView];
         
         // KVO实现
         // contentOffset
         // self对象监听_tableView的contentOffset属性的变化
         
         // 被观察者对象调用这个方法
         [self.tableView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
         
     }
    
  3. 观察者对象所属类实现 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
     - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
     {
         // 属性
         if ([keyPath isEqualToString:@"contentOffset"]) {
             
             // 被观察者对象
             if ([object isKindOfClass:[UITableView class]]) {
     //            NSLog(@"%s", __func__);
                 
                 NSValue *value = change[@"new"]; // NSValue类型的值: change[@"new"]
                 CGPoint point = [value CGPointValue];
                 
                 // 计算当前的高度
                 float ratio = point.y/(self.tableView.contentSize.height - self.tableView.bounds.size.height);
                 CGFloat height = ratio * 500;
                 
                 // 设置redView的高度
                 UIView *redView = [self.view viewWithTag:200];
                 
                 if (height >= 0) {
                     CGRect frame = redView.frame;
                     frame.size.height = height;
                     redView.frame = frame;
                     
                 }
     
             }
         }
     }
    

三. 利用MRC手动内存管理编写程序

  1. 新建5个标签栏视图控制器类
  2. MRC黄金法则和小试牛刀
     // 黄金法则:
     // 1. alloc copy new mutableCopy创建对象, 自动引用计数器为1
     // 2. 调用retain时会将引用计数加1, release会将引用计数减1
     // 3. 在什么地方增加了引用计数, 就要在什么地方减少引用计数
     
     // 综合
     InfoViewController *infoCtrl = [[InfoViewController alloc] init];
     infoCtrl.tabBarItem.title = @"综合";
     infoCtrl.tabBarItem.image = [UIImage imageNamed:@"info.png"]; // .png默认可以省略
     UINavigationController *infoNavCtrl = [[UINavigationController alloc] initWithRootViewController:infoCtrl];
     // infoCtrl 对象不再需要, 释放这个对象
     [infoCtrl release];
     
     return YES;
    
  3. 创建导航视图控制器, 设置完导航视图控制器的根视图控制器后将根视图控制器对象释放; 设置主视窗的视图控制器为tabBar, 然后释放TabBar
         // 问答
         AnswerViewController *answerCtrl = [[AnswerViewController alloc] init];
         answerCtrl.tabBarItem.title = @"问答";
         answerCtrl.tabBarItem.image = [UIImage imageNamed:@"answer"];
         UINavigationController *answerNavCtrl = [[UINavigationController alloc] initWithRootViewController:answerCtrl];
         // 释放answerCtrl对象
         [answerCtrl release];
         
         // 动弹
         TweetViewController *tweetCtrl = [[TweetViewController alloc] init];
         tweetCtrl.tabBarItem.title = @"动弹";
         tweetCtrl.tabBarItem.image = [UIImage imageNamed:@"tweet"]; // .png默认可以省略
         UINavigationController *tweetNavCtrl = [[UINavigationController alloc] initWithRootViewController:tweetCtrl];
         // tweetCtrl 对象不再需要, 释放这个对象
         [tweetCtrl release];
         
         // 我的
         MineViewController *mineCtrl = [[MineViewController alloc] init];
         mineCtrl.tabBarItem.title = @"我的";
         mineCtrl.tabBarItem.image = [UIImage imageNamed:@"active"];
         UINavigationController *mineNavCtrl = [[UINavigationController alloc] initWithRootViewController:mineCtrl];
         // 释放mineCtrl对象
         [mineCtrl release];
         
         // 更多
         MoreViewController *moreCtrl = [[MoreViewController alloc] init];
         moreCtrl.tabBarItem.title = @"更多";
         moreCtrl.tabBarItem.image = [UIImage imageNamed:@"more"];
         UINavigationController *moreNavCtrl = [[UINavigationController alloc] initWithRootViewController:moreCtrl];
         // 释放moreCtrl对象
         [moreCtrl release];
         
         // 创建tabBar对象
         UITabBarController *tabBarCtrl = [[UITabBarController alloc] init];
         tabBarCtrl.viewControllers = @[infoNavCtrl, answerNavCtrl, tweetNavCtrl, mineNavCtrl, moreNavCtrl];
         // 所有导航视图控制器对象不再需要, 释放内存
         [infoNavCtrl release];
         [answerNavCtrl release];
         [tweetNavCtrl release];
         [mineNavCtrl release];
         [moreNavCtrl release];
         
         // 主窗口的视图控制器
         self.window.rootViewController = tabBarCtrl;
         [tabBarCtrl release];
    
  4. InfoViewController.m: 类方法新建成员对象后需要retain
     #import "InfoViewController.h"
     
     @interface InfoViewController ()
     {
         UITableView *_tbView;
         NSMutableArray *_dataArray;
     }
     
     @end
     
     @implementation InfoViewController
     
     - (void)viewDidLoad {
         [super viewDidLoad];
         // Do any additional setup after loading the view.
         
         // _dataArray = [[NSMutableArray alloc] init];
         _dataArray = [NSMutableArray array];
         [_dataArray retain];
         
     }
    
  5. 下载数据, 遵守协议, 新建表格视图(要在dealloc方法中释放数据源和表格视图属性)
     - (void)viewDidLoad {
         [super viewDidLoad];
         // Do any additional setup after loading the view.
         
         // _dataArray = [[NSMutableArray alloc] init];
         _dataArray = [NSMutableArray array];
         [_dataArray retain];
         
         // 导航
         self.automaticallyAdjustsScrollViewInsets = NO;
         
         // 表格
         _tbView = [[UITableView alloc] initWithFrame:CGRectMake(0, 64, 375, 667 - 64 - 49) style:UITableViewStylePlain];
         _tbView.delegate = self;
         _tbView.dataSource = self;
         [self.view addSubview:_tbView];
         
         // 下载数据
         _receivedData = [[NSMutableData alloc] init];
         [self downloadData];
     }  
     
     - (void)downloadData
     {
         // http://www.oschina.net/action/api/tweet_list?uid=0&pageIndex=0&pageSize=20
         // 不需要考虑内存问题
         [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.oschina.net/action/api/tweet_list?uid=0&pageIndex=0&pageSize=20"]] delegate:self];
     }    
    
    • 在dealloc中释放对象, dealloc方法不一定立即执行
      - (void)dealloc
      {
      [_dataArray release];
      _tbView.delegate = nil;
      _tbView.dataSource = nil;
      [_tbView release];

            [_receivedData release];
            
            // 父类的方法
            // MRC下一定要调用父类的dealloc, ARC下不能调用
            [super dealloc];
        }  
      
  6. 新建模型类(retain属性) , 需要在模型类的dealloc方法中释放模型类的成员变量
    • TweetModel.h
      #import

        @interface TweetModel : NSObject
        
        // 使用retain
        @property (nonatomic, retain) NSString *author;
        @property (nonatomic, retain) NSString *body;
        
        @end
      
    • TweetModel.m
      #import "TweetModel.h"

        @implementation TweetModel
        
        - (void)dealloc
        {
            [_author release];
            [_body release];
            
            [super dealloc];
        }
        
        //- (void)setAuthor:(NSString *)author
        //{
        //    if (_author != author) {
        //        [_author release];
        //        _author = [author retain];
        //    }
        //}
        
        @end
      
  7. 处理下载数据(只写了需要注意内存释放的部分), tableView的代理方法(自动释放池的使用)
     - (void)connectionDidFinishLoading:(NSURLConnection *)connection
     {
         GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:_receivedData options:0 error:nil];
         // "//" 获取任意位置的tweet节点
         NSArray *tweetNodes = [doc nodesForXPath:@"//tweet" error:nil];
         for (GDataXMLElement *elements in tweetNodes) {
             
             // 创建模型对象
             TweetModel *model = [[TweetModel alloc] init];
             model.author = [[[elements elementsForName:@"author"] lastObject] stringValue];
             model.body = [[[elements elementsForName:@"body"] lastObject] stringValue];
             
             [_dataArray addObject:model];
             // 释放model对象
             [model release];
         }
         
         // 刷新表格
         [_tbView reloadData];
         
         // 释放doc兑现
         [doc release];
     }
     
     #pragma mark - UITableView代理方法
     - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
     {
         return _dataArray.count;
     }
     
     - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
     {
         static NSString *cellId = @"cellID";
         
         UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
         if (nil == cell) {
     #warning 自动释放池
             cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellId] autorelease];
         }
         
         // 模型对象
         TweetModel *model = _dataArray[indexPath.row];
         cell.textLabel.text = model.author;
         cell.detailTextLabel.text = model.body;
     
         return cell;
     }

你可能感兴趣的:(D20:KVC, KVO, MRC手动内存管理实践)