Cocoa数据绑定

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

MVCMode

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

KVOObserve

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绑定技术提供了简单优雅的实现,是系统级的模型和视图之间双向数据同步机制。模型数据的修改能实时更新到视图上,反之视图的修改也能更新到模型上。

传统的数据更新流程

EmploeeMVC

传统的编程方法中,假如我们要修改员工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。

NSTextFiledTransFormatter

在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。

SimpleBindXib

视图绑定到Controller

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

SimpleBindTextFiled

Controller绑定到模型

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

SimpleBindEmploee

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

ViewBindModel

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,最终更新了界面上的数据。

BindProcedure

使用NSArrayController管理数据

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

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

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

NSArrayBindXib

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中的属性字段

NSArrayTextFiledBindEmploee

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

NSArrayTableColumnBindEmploee

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

NSArrayTableButtonEventBind

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

使用NSTreeController管理数据

创建Xcode新工程BindTreeObject。

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

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

NSTreeBindXib

NSTreeBindRun

树节点模型

对于树形结构的视图,每个节点都要包括名称和子节点信息。我们可以用一个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。

NSTreeControllerModel

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绑定

NSTreeVCBindOutlineView

从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。

NSTreeCellBind

NSTreeController 跟NSMutableArray数据源绑定

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

@property(nonatomic,strong) NSMutableArray *treeNodes;

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

NSTreeControllerBindContent

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个功能按钮可以正常管理增加删除节点了。

NSTreeControllerRun