美文网首页面试iOS开发的那些事儿。。。iOS
D20:KVC, KVO, MRC手动内存管理实践

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

作者: Vinc | 来源:发表于2015-05-31 20:25 被阅读348次

    一. 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: '[<User 0x7fce21733a20> 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: '[<User 0x7fa188617e00> 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 <Foundation/Foundation.h>
          
          @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 <Foundation/Foundation.h>

          @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手动内存管理实践

        本文链接:https://www.haomeiwen.com/subject/awfdqttx.html