欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Cocoa 数据绑定 bind 教程

程序员文章站 2022-04-26 22:11:00
...

Cocoa数据绑定

MVC架构编程模式中,Controller负责将Model模型数据更新到View视图,同时当用户对视图View数据做了修改后,还需要Controller将变换的数据更新到Model模型中。

Cocoa 数据绑定 bind 教程

模型Model到视图View,视图View到模型Model,这种双向的数据更新涉及到大量繁琐的数据转换和赋值操作。因此OSX Cocoa从系统层面设计了Cocoa数据绑定机制,用以简化MVC编程中这种双向更新操作。

Cocoa数据绑定机制的实现依赖KVC,KVO,KVB 三种技术,下面分别来介绍说明。

KVC

KVC是Key-value coding 键值编码的简称,提供了通过类的属性字符名称来读写属性值的方法。你不需要通过调用类的不同的方法去实现不同的属性读写修改,而只需要通过不同的属性名称,通过统一一致的接口实现读写。

KVC的好处是明显的,可以实现循环遍历读写所有的属性值,也可以支持属性路径链式调用,即嵌套对象的访问。

Cocoa在基类NSObject上实现了KVC的接口方法,因此所有类天然支持KVC方式属性读写。

我们定义Person和Phone 2个类便于后面说明KVC的属性访问方式。

Person对象类,有3个属性:名字,年龄,电话。

@interface Person : NSObject
@property(nonatomic,strong)NSString *name;
@property(nonatomic,assign)int age;
@property(nonatomic,strong)Phone  *phone;
@end

电话对象类,有3个属性:办公电话,家庭电话,手机。

@interface Phone : NSObject
@property(nonatomic,strong)NSString *office;
@property(nonatomic,strong)NSString *family;
@property(nonatomic,strong)NSString *mobile;
@end

定义Person和Phone实例

Person *person= [[Person alloc]init];
person.name = @"john";
person.age = 20;

Phone *phone = [[Phone alloc]init];
phone.office = @"010-67854545";

person.phone = phone;

属性读写接口

1.通过名字访问对象属性值

-(id)valueForKey:(NSString *)key;

2.修改属性名为key的值为value

-(void)setValue:(id)value forKey:(NSString *)key;

//使用KVC方法获取属性    
NSString *name = [person valueForKey:@"name"];
//使用KVC修改属性
[person setValue:@"Habo" forKey:@"name"];

路径访问接口

1.使用keyPath路径去获取属性

-(id)valueForKeyPath:(NSString *)keyPath;

2.更新keyPath路径对应的属性值为value

-(void)setValue:(id)value forKeyPath:(NSString *)keyPath;

//使用path获取属性
NSString *officePhone = [person valueForKeyPath:@"phone.office"];
//修改属性
[person setValue:@"010-678545466" forKeyPath:@"phone.office"];

异常属性接口

1.访问不存在的属性,会调用此方法,如果此方法没有实现,会抛出异常。

-(id)valueForUndefinedKey:(NSString *)key;

2.修改不存在的属性值,会调用此方法,如果此方法没有实现,会抛出异常。

-(void)setValue:(id)value forUndefinedKey:(NSString *)key;

3.修改属性值为nil,会调用此方法,如果此方法没有实现,会抛出异常

-(void)setNilValueForKey:(NSString *)key;

下面是用使用setValue方法对hidden属性修改为nil,默认设置为YES

- (void)setNilValueForKey:(NSString *)theKey {
    if ([theKey isEqualToString:@"hidden"]) {
        [self setValue:@YES forKey:@"hidden"];
} else { 
        [super setNilValueForKey:theKey];
    }
} 

4.修改key对应的value值,对value进行有效性验证。

对每个Key单独实现validate方法
-(BOOL)validate:(id *)ioValue error:(NSError * *)outError;

下面是对Person对象的age年龄进行validate的例子:

- (BOOL)validateAge:(id *)ioValue error:(NSError * __autoreleasing *)outError 
    if ([*ioValue integerValue] <= 0) {
        if (outError != NULL) {
            NSString *errorString = NSLocalizedStringFromTable(@"Age must be greater than zero", @"Person",
                                                               @"validation: zero age error");
            NSDictionary *userInfoDict = @{ NSLocalizedDescriptionKey : errorString };
            
            NSError *error = [[NSError alloc] initWithDomain:@""     code:10005   userInfo:userInfoDict];
                                            *outError = error;
        };
        return NO;
    }
    return YES;
}

对所有Key统一实现validate方法

-(BOOL)validateValue:(inout id )ioValue forKey:(NSString *)inKey error:(NSError **)outError;
-(BOOL)validateValue:(inout id )ioValue forKeyPath:(NSString *)inKeyPath error:(out NSError **)outError;

在-set:方法中不要执行validation方法,如果需要对key的值修改进行有效性验证,在类中实现validation方法。

批量属性访问接口

1.访问多个key对应的属性,返回key-value形式的字典对象

-(NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;

2.以字典对应的key-value去更新对象对应的属性。

-(void)setValuesForKeysWithDictionary:(NSDictionary *)keyedValues;

使用这个方法在网络处理JSON对象解析中可以方便通过字典对象来更新模型对象。

@interface Phone : NSObject
@property(nonatomic,strong)NSString *office;
@property(nonatomic,strong)NSString *family;
@property(nonatomic,strong)NSString *mobile;
-(id)initWithDictionary:(NSDictionary*)attributes;
@end

#import "Phone.h"

@implementation Phone
-(id)initWithDictionary:(NSDictionary*)attributes {
    self = [super init];
    if(self){
        [self setValuesForKeysWithDictionary:attributes];
    }
    return self;
}
@end

KVO

Observer观察者模式(也称为发布-订阅模式)应该可以说是应用非常广泛的设计模式之一,它定义了一种一对多的依赖关系。当一个对象的状态/属性发生变化时,会通知所有的观察者及时更新。

Cocoa 数据绑定 bind 教程

KVO是Key-value observing 简称,它是Cocoa系统实现的对象属性变化的通知机制,也可以理解为观察者模式的系统实现。

KVO系统接口

1.注册观察者对象

对象使用addObserver方法来增加观察者

-(void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;

observer:观察者

keyPath: 对象的keyPath

options参数为配置项,一般为下面2个参数逻辑或组合
NSKeyValueObservingOptionNew = 0x01,
NSKeyValueObservingOptionOld = 0x02,
context:附加的上下文参数,为对象指针或其他标识值

将teacher实例注册为student实例的观察者,当student的address有变化时,会得到通知。

Teacher *teacher = [[Teacher alloc]init]; 
Student *student = [[Student alloc]init];
[student addObserver:teacher forKeyPath:@"address" options:NSKeyValueObservingOptionNew context:nil];

2.接收变化通知

观察者类通过实现observeValueForKeyPath接口实现接收变化通知

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context

change字典中是变化的数据,有3个key,分别为kind,old,new。

kind指示是修改,插入,删除,替换变化的类型。

typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
    NSKeyValueChangeSetting = 1,
    NSKeyValueChangeInsertion = 2,
    NSKeyValueChangeRemoval = 3,
    NSKeyValueChangeReplacement = 4,
};

old代表修改前的旧值,new为修改后的新值。

3.删除观察者

通过removeObserver方法删除观察者对象

-(void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath

手工管理KVO

手工触发变化通知

NSObject本身提供了自动化的通知机制,有些情况下我们需要人工触发通知,就需要做额外的处理。

使用automaticallyNotifiesObserversForKey方法来约定key的通知方式,默认情况下所有的key都是系统自动通知,返回NO表示需要手工通知变化。

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
    BOOL automatic = NO;
    if ([theKey isEqualToString:@"address"]) {
        automatic = NO;
    }
    else {
        automatic = [super automaticallyNotifiesObserversForKey:theKey];
    }
    return automatic;
}

重写key属性的set方法,通过willChangeValueForKey 和 didChangeValueForKey完成变化通知。

- (void)setAddress:(NSString*)address {
    [self willChangeValueForKey:@"address"];
    _address = address;
    [self didChangeValueForKey:@"address"];
}

实现依赖通知

有依赖关系的属性,当依赖有变化时,希望收到这个属性变化的通知。

比如数据库路径是有文件根目录和名字合成,有getter方法如下:

- (NSString *)dbPath {
    return [NSString stringWithFormat:@"%@ %@",path, fileName];
}

当文件目录或名字属性变化时,数据库的路径实际上也发生了变化。

有2种方式实现了依赖变化通知,实现下面任意一种类方法即可。

1.通用的类方法

+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
      NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
      if ([key isEqualToString:@"dbPath"]) {
          NSArray *affectingKeys = @[@"path", @"fileName"];
          keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
      }
      return keyPaths;
}

2.针对key的类方法

+ (NSSet *)keyPathsForValuesAffectingDbPath {
    return [NSSet setWithObjects:@"path", @"fileName", nil];
}

KVO的一个简单例子

使用KVO观察者模式,学生和老师对象之间当student的address有变化时,老师得到通知。

1.对象模型类

老师 Teacher模型类

#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
@interface Teacher : NSObject
@property(nonatomic,strong)NSString *school;
@property(nonatomic,strong)NSString *address;
@end

学生Studentr模型类

#import <Foundation/Foundation.h>

@interface Student : NSObject
@property(nonatomic,strong)NSString *name;
@property(nonatomic,strong)NSString *address;
@end

2.注册观察者

@interface AppDelegate ()
@property (weak) IBOutlet NSWindow *window;
@property(nonatomic,strong)Teacher *teacher;
@property(nonatomic,strong)Student *student;
@end

@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    Teacher *teacher = [[Teacher alloc]init];
    Student *student = [[Student alloc]init];
    student.address = @"Beijing";
    self.teacher =teacher;
    self.student =student;
    
    //注册观察者
    [student addObserver:teacher forKeyPath:@"address" options:NSKeyValueObservingOptionNew context:nil];
    student.address = @"Nanjing";
    
}

3.接收通知

Teacher类接收到Student属性变化的处理

@implementation Teacher
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    NSLog(@"change %@",change);
}
@end

数据绑定

MVC编程模式中,模型和视图之间数据同步更新,需要编写很多繁琐的代码。Cocoa绑定技术提供了简单优雅的实现,是系统级的模型和视图之间双向数据同步机制。模型数据的修改能实时更新到视图上,反之视图的修改也能更新到模型上。

传统的数据更新流程

Cocoa 数据绑定 bind 教程

传统的编程方法中,假如我们要修改员工Employee模型对应的数据,流程如下:

  1. 从数据库查询到工号id=xxx的员工信息,保存到Emploee模型类中。

  2. 将Employee模型中每个属性信息,更新到Employee Profile界面视图上。

  3. 修改界面上员工address地址信息。

  4. 修改完成后将界面视图上address新值传回到Employee模型类中。

  5. 保存Employee模型类到数据库。

    其中第2步需要完成模型到视图的数据同步,在视图类的界面初始化中,你需要写大致如下的代码

    self.idTextFiled.string = self.employee.id;
    self.nameTextFiled.string = self.employee.name;
    self.addressTextFiled.string = self.employee.address;
    self.ageTextFiled.string = self.employee.age;
    

    第4步需要完成界面视图上变化的数据到模型的数据同步,代码如下

    self.employee.address = self.addressTextFiled.string;

    可以看到,这种模型到视图,视图到模型的代码编写是相当重复繁琐和无趣的。

使用Bind技术简化更新流程

我们创建一个工程BindSimpleObject,使用bind技术来实现模型<->视图双向更新的例子。

Emploee模型类

 @interface Employee : NSObject
 @property(nonatomic,assign)NSInteger id;
 @property(nonatomic,strong)NSString *name;
 @property(nonatomic,assign)NSInteger age;
 @property(nonatomic,strong)NSString *address;
 @end

点击MainMenu.xib,从控件工具箱拖放一个Object Controller到左侧xib导航面板区

点击xib导航面板区Window,在window界面增加ID,name,address,age 4个文本label和4个NSTextField控件,完成界面设计。

注意一下这里ID和age不是string类型,如果使用普通的NSTextFiled控件,需要NSInteger到NSString的双向转换,比较麻烦,这里我们使用带Transformatter的NSTextFiled,它能自动完成这个转换。

在增加一个NSButton到界面上,绑定事件函数为okAction。

Cocoa 数据绑定 bind 教程

在AppDelegate.m 文件中增加一个employee属性变量,一个IBOutlet变量objectController。

@interface AppDelegate ()
@property (weak) IBOutlet NSWindow *window;
@property(nonatomic,strong)Employee *employee;
@property (strong) IBOutlet NSObjectController *objectController;
@end

从MainMenu.xib面板导航区点击Object Controller,从Connections面板将其绑定到IBOutlet变量objectController。

Cocoa 数据绑定 bind 教程

视图绑定到Controller

点击ID对应的NSTextField控件,切换到inspector的Bindings面板, Value部分勾选Bind to,下拉框中选择Object Controller。Controller Key默认是selection,Model Key Path部分输入模型类Employee中的对应的id属性。
其他3个文件输入控件类似设置完成绑定。

Cocoa 数据绑定 bind 教程

Controller绑定到模型

点击左边Object Controller,切换到inspector的Bindings面板, Controller Content部分勾选Bind to,下拉框选择Delegate(表示AppDelegate,Model Key Path:输入employee属性变量,完成了 NSObjectController绑定到Employee模型的绑定。

Cocoa 数据绑定 bind 教程

最终的View<->NSObjectController<->Model 3个对象之间绑定关系如下图:

Cocoa 数据绑定 bind 教程

AppDelege.m的applicationDidFinishLaunching中输入下面代码,完成employee变量初始化。

-(void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    
    Employee *em = [[Employee alloc]init];
    em.id = 123213123;
    em.name = @"John";
    em.address = @"China Beijing";
    em.age = 25;
    self.employee  = em;
}

在按钮事件函数中输入下面代码,打印输出属性变量employee的各个字段值。

-(IBAction)okAction:(id)sender {
    NSLog(@"employee = %@",[self.employee objectAsDictionary]);
}

运行工程,看到界面上4个输入框都有了对应于self.employee属性变量的值。这样已经自动完成了模型到视图的数据更新。

在输入修改任意一个文本框里面的内容,点击ok按钮,查看打印输出。看到打印出来模型的字段值也相应的发生了变化。这里自动完成了视图数据到模型数据的同步更新。

绑定关键技术

Cocoa 绑定依赖几个关键技术,key-value coding (KVC),key-value observing (KVO),key-value binding (KVB), NSEditor/NSEditorRegistration。

KVC和KVO我们在前面章节已经做了介绍,下面我们主要讨论下其他相关技术。

KVB 绑定接口

1)建立绑定

-(void)bind:(NSString *)binding toObject:(id)observable withKeyPath:(NSString *)keyPath options:(NSDictionary*)options;  

binding:绑定的名称

observable:绑定的对象

keyPath:绑定的对象的keyPath

options:参数设置,可以为空

上面例子中的NSObjectController到Employee模型的绑定可以这样编码实现:

[self.objectController bind:@"content"
                      toObject:self
                   withKeyPath:@"employee"
                       options:0];

2)解除绑定

-(void)unbind:(NSString *)binding;

手工编码实现的绑定必须在dealloc方法或其他合适的时机去除绑定,否则绑定对象被retain不能及时释放内容资源。

xib中建立的绑定当视图对象释放时会自动完成解除绑定。

NSEditor/NSEditorRegistration协议

NSObjectController的父类NSController实现了NSEditor/NSEditorRegistration协议方法。

NSEditor/NSEditorRegistration协议提供了视图对象到NSObjectController对象的通信接口,当用户编辑界面内容开始时,发送objectDidBeginEditing消息。结束编辑发送objectDidEndEditing。当用户关闭窗口前使用discardEditing,commitEditing通知NSObjectController丢弃数据或提交保存数据。

NSEditorRegistration协议

-(void)objectDidBeginEditing:(id)editor;
-(void)objectDidEndEditing:(id)editor;

NSEditor协议

-(void)discardEditing;
-(BOOL)commitEditing;

NSController

NSController为抽象类,它有4个子类:
NSObjectController,NSUserDefaultsController,NSArrayController,NSTreeController 。

NSObjectController管理单一的对象。

NSUserDefaultsController管理系统配置NSUserDefaults对象。

NSArrayController,NSTreeController 用来管理集合类对象,分别用在NSTableView和NSOutlineView视图的管理中。

NSObjectController对象除了实现了NSEditor/NSEditorRegistration协议外,当绑定对象为空时提供placeholder;对于集合类对象可以方便的管理当前选中的对象,同时提供对象删除/增加等管理方法实现。

只要模型类满足KVC,KVO兼容,实际上是可以直接绑定视图控件到模型,而不需要通过中间的NSController来桥接。但这样对处理集合类对象要增加很多额外的编码工作。

NSController类几个关键属性:

  1. content: 管理的内容
  2. selection:当前选中的对象,是一个代理类
  3. selectedObjects:选中的对象
  4. arrangedObjects: 排序过的对象,是一个代理类
  5. objectClass:配置的模型类,创建新对象是使用

KVB 绑定的处理流程

我们以前面例子中 NSTextField <-> NSObjectController <-> Employee 3个对象之间绑定处理过程来说明。

1.建立对象之间绑定

NSObjectController调用bind方法发送绑定消息

[objectController bind:@contentObject toObject:appDelegate withKeyPath:@employee options:nil];

NSTextField调用bind方法发送绑定消息

[textFiled bind:@value toObject:objectController withKeyPath:@selection.id options:nil];

2.注册KVO

NSObjectController注册KVO,增加NSTextField对象为自己的观察者对象

[objectController addObserver:NSTextField forKeyPath:@selection.id options:0 context:context];

Employee注册KVO,增加NSObjectController对象为自己的观察者对象

[employee addObserver:objectController forKeyPath:@id options:0 context:context];

3.用户输入数据

当用户输入数据修改了内容后,NSTextField通过KVC更新NSObjectController的内容

[objectController setValue:4 forKeyPath:@selection.id];

NSObjectController通过KVC更新Employee的内容

[employee setValue:4 forKeyPath:@id];

4.修改模型Employee数据

由于NSObjectController是Employee的观察者,因此发生KVO通知到NSObjectController,NSObjectController更新数据。
NSObjectController的变化同样由于KVO会通知到NSTextField,最终更新了界面上的数据。

Cocoa 数据绑定 bind 教程

使用NSArrayController管理数据

创建工程BindArrayObject,模型类使用之前例子中定义的Employee。

打开MainMenu.xib,添加NSArrayController到xib导航区。window界面增加4个Label和4个NSTextField控件,添加NSTableView控件,完成设计设计如下图。

ArrayController的Attributes属性面板配置Class Name为Employee,Keys列表增加4个属性key。

Cocoa 数据绑定 bind 教程

AppDelegate.m 接口部分定义NSArrayController类型arrayController变量,建立xib中NSArrayController对象到IBOutlet变量关联。

@property(strong) IBOutlet NSArrayController *arrayController;

定义数组变量

@property(nonatomic,strong)NSMutableArray *anArray;

代码完成arrayController到数组anArray的绑定

NSDictionary *options = @{ NSAllowsEditingMultipleValuesSelectionBindingOption:@YES };
[self.arrayController bind:@"contentArray"
                      toObject:self
                   withKeyPath:@"anArray"
                       options:options];

4个文本输入框依次完成绑定,Model Key Path对应Emploee中的属性字段

Cocoa 数据绑定 bind 教程

NSTableView中的4个NSTableColumn 依次完成绑定,Model Key Path对应Employee中的属性字段

Cocoa 数据绑定 bind 教程

Add,Remove Button分别绑定事件响应函数,Array Controller的add: remove:方法即可。

Cocoa 数据绑定 bind 教程

运行工程修改编辑4个文本框的内容,表格内容会同步更新。修改表格中单元内容也会更新到上面的对应的文本输入框,点击增加,删除都可以正常工作。

使用NSTreeController管理数据

创建Xcode新工程BindTreeObject。

MainMenu.xib在window界面上拖放一个NSOutlineView控件,添加5个增删相关按钮如下图。

从控件工具箱拖放一个Tree Controller到左边xib结构导航区。

Cocoa 数据绑定 bind 教程

Cocoa 数据绑定 bind 教程

树节点模型

对于树形结构的视图,每个节点都要包括名称和子节点信息。我们可以用一个TreeNode模型来来描述节点信息。

@interface TreeNode : NSObject
@property(nonatomic,strong)NSString *nodeName;//名称
@property(nonatomic,assign)NSInteger count;//子节点个数
@property(nonatomic,assign)BOOL isLeaf;//是否叶子节点
@property(nonatomic,strong)NSArray *children;//子节点
@end

bind数据在处理过程中都是通过KVC的keyPath访问数据的,因此我们也可以等价的使用Cocoa的字典NSDictionary来直接描述节点信息。

NSDictionary *node = @{ @nodeName: @Group,
@children: @[
@{@name: @m1,},
@{@name: @m2}
]
};

其中子节点个数和是否是子节点都不需要了,count(子节点个数),isLeaf(是否子节点)的值都可以通过children 子节点数组推算出来,如果children的count为0,则子节点个数个数count为0,isLeaf为YES。

NSTreeController 配置节点模型

NSTreeController.h中几个重要的属性方法定义

@property (copy) NSString *childrenKeyPath; // key used to find the children of a model object.
@property (copy) NSString *countKeyPath; // optional for performance
@property (copy) NSString *leafKeyPath; 

上面这3个属性对应于xib中NSTreeController 对象属性页面上的Key Paths里面的3个设置项:
Children,Count,Leaf。根据之前的分析 只要设置了Children就可以了。

这里我们在NSTreeController Attributes面板配置Key Paths下的Children为上面字典NSDictionary中定义的key children,并在下面 Class Name中输入NSMutableDictionary,在keys列表中增加name,children 2个key。

Cocoa 数据绑定 bind 教程

NSTreeController 的事件方法

NSTreeController定义实现了增加删除节点的方法

-(void)add:(id)sender;    // 增加新节点
-(void)remove:(id)sender;   //删除选择的节点
-(void)addChild:(id)sender;    // 在当前选中的节点增加一个子节点
-(void)insert:(id)sender;    // 插入一个节点在当前选中的节点之前
-(void)insertChild:(id)sender;    // inserts a new first child into the children array of the first selected node

将上图中5个按钮Add,Add Child,Insert,Insert Child,Remove分别绑定到NSTreeController到上面5个方法,这样就可以管理树的节点数据了。

NSTreeController 跟NSOutlineView绑定

NSOutlineView数据源Content绑定

Cocoa 数据绑定 bind 教程

从xib界面上选中NSOutlineView,右侧inspector面板上切换到bindings绑定面板。从Outline View Content部分中Content勾选Bind to,下拉列表选择Tree Controller,Selection index Paths 勾选Bind to,从下拉列表选择Tree Controller。完成NSOutlineView到NSTreeController数据绑定。

NSOutlineView列单元cell数据绑定

点击NSOutlineView列上面的Table View Cell部分,实际上Table View Cell是一个NSTextField控件,将其Value绑定到Table Cell View,勾选Bind to 选择即可。Model Key Path绑定到 ObjectValue.name。 ObjectValue实际上代表的是当前节点对应的模型对象。name为其名字的key。

Cocoa 数据绑定 bind 教程

NSTreeController 跟NSMutableArray数据源绑定

AppDelegate.m的接口中增加treeNodes属性变量

@property(nonatomic,strong) NSMutableArray *treeNodes;

Tree Controller绑定到Delegate对象,Model Key Path为treeNodes。

Cocoa 数据绑定 bind 教程

AppDelegate.m实现init方法完成数据初始化。

- (id)init {
    self = [super init];
    if (self) {
        NSMutableDictionary *mNode =  [self newObject];
        self.treeNodes = [NSMutableArray array];
        [self.treeNodes addObject:mNode];
    }
    return self;
}

newObject为创建一个节点数据的方法。

- (NSMutableDictionary*)newObject {
    NSMutableDictionary *node  = [NSMutableDictionary dictionary];
    node[@"name"]=  @"Group";
    NSMutableArray *children=[NSMutableArray array];;
    node[@"children"]=  children;
    NSMutableDictionary *m1  = [NSMutableDictionary dictionary];
    
    m1[@"name"]=  @"m1";
    [children addObject:m1];
    
    NSMutableDictionary *m2  = [NSMutableDictionary dictionary];
    m2[@"name"]=  @"m1";
    [children addObject:m2];
    
    return node;
}

运行App看到初始的数据已经正常显示出来,点击5个功能按钮可以正常管理增加删除节点了。

Cocoa 数据绑定 bind 教程