IOS之控制器与子控件之间的交流

Posted by Heber on March 18, 2019

说明

这个标题的描述可能不清楚,我举个例子,现在有个购物车程序,我需要对商品栏(tableCell)的添加按钮进行点击,点击之后会使商品栏里的数量+1,与此同时,该页面也有一个总数label用于记录所有商品的数量,点击添加按钮也会让这里的数量+1。最直接的实现就是在商品栏的model文件中绑定添加按钮的点击事件,同时通过self.superView来拿到上层的总数label,然后进行+1操作。但是这样有一个明显的问题是耦合太高。接下来介绍另外三种耦合低的解决办法。

NSNotification

NSNotification是ios的消息分发类,解决思路简单来说,就是按钮点击后,在model绑定的点击方法中修改商品栏的数量,同时向消息中心发送一条消息,如果已经在页面控制器中注册了消息的监听事件,那么页面控制器就能收到一条消息,并执行注册时绑定的方法,将总数label的值+1。dealloc是用于结束时移除监听。

viewController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center addObserver:self selector:@selector(addButtonClick:) name:@"addButtonClick" object:nil];
    [center addObserver:self selector:@selector(minusButtonClick:) name:@"minusButtonClick" object:nil];
}

-(void)dealloc{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

-(void)addButtonClick:(NSNotification*)note{
    LHBWineCell *wineCell = note.object;
    
    int totalPrice = self.totalPrice.text.intValue+wineCell.wine.money.intValue;
    self.totalPrice.text = [[NSString alloc] initWithFormat:@"%d",totalPrice];
}

shopCell.m

- (IBAction)addButtonClick {
    self.wine.count++;
    self.countLabel.text = [NSString stringWithFormat:@"%d",self.wine.count];
    self.minusButton.enabled = YES;
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center postNotificationName:@"addButtonClick" object:self userInfo:nil];
}

kvo

既然cell有计数label,model文件中自然也有这个字段,通过kvo在页面控制器中在赋值阶段对这个字段的值添加监控,如果有变化就触发绑定的方法来修改总数label。

viewController.m

- (NSArray *)wineArray{
    
    if(!_wineArray){
        _wineArray = [LHBWine mj_objectArrayWithFilename:@"wine.plist"];
        for(LHBWine *wine in _wineArray){
            [wine addObserver:self forKeyPath:@"count" options:NSKeyValueObseringOptionNew NSKeyValueObservingOptionOld context:nil];
        }
    }
    return _wineArray;
}

-(void)dealloc{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    int new = [change[NSKeyValueChangeNewKey] intValue];
    int old = [change[NSKeyValueChangeOldKey] intValue];
    if(new > old){
        NSLog(@"add");
    }else{
        NSLog(@"minus");
    }
}

delegate

代理也是一种让控制器与子控件交流的方式,大致流程是先在子控件的h文件中声明代理变量,然后在子控件的对象创建的时候为代理变量赋值为控制器,这样,控制器就成为子控件的代理。之后是在控制器的m文件中声明几个方法,比如addButtonClick(),这样,子控件中的加号按钮被点击的时候,我们在修改label数字的同时,还能通过[self.delegate addButtonClick()]来调用控制器的方法修改总价。为了方便并保证控制器成为代理时拥有子控件需要调用方法(比如刚才的addButtonClick),还需要制定一个协议,控制器在成为代理的同时必须遵守协议,实现协议方法,才能成为代理。

通常推荐使用第一种和第三种方法,因为kvo的监控是全局的,如果有其他事件改变了被监听的对象的值,也会触发绑定的方法。同时,最推荐的是代理方式,这种方式较消息通知来说更为规范。但是一个对象只能有一个代理,在需要多个控制器响应的情况下,还是要使用消息通知。