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: '[<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;
     }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 1.自定义控件 a.继承某个控件 b.重写initWithFrame方法可以设置一些它的属性 c.在layouts...
    圍繞的城阅读 3,342评论 2 4
  • *7月8日上午 N:Block :跟一个函数块差不多,会对里面所有的内容的引用计数+1,想要解决就用__block...
    炙冰阅读 2,468评论 1 14
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,121评论 29 470
  • 哦吼吼,又研究了几天,把FMDB这个封装好的数据库搞定了,写了个简单的例子,基于FMDB的添删改查操作,界面很一般...
    lichengjin阅读 513评论 0 0
  • 星儿醒了, 月儿笑了。 天际沦陷暮色。 霓虹灯光刺眼, 而你闪烁刺心。 总是想和你不期而遇, 相遇之后才知 距...
    关于我的情话阅读 225评论 0 1