iOS快速开发框架Bee-Framework应用和解析(三) --- Message, Model, Signal

        这一次分享一下BeeMessage, BeeModel, 和BeeUISignal。这三个东东就是Controller, Model, 和Event的主要实现。您也可以到Bee的/documents/developer_manual.pdf中查看详细的开发手册,希望您看了这篇文章能对这几个组件理解更深,适合干什么,从而更得心应手得使用。本文试图解答几个问题:

  •         BeeMessage是如何实现的?如何做到封装Http过程的,有什么优缺点和适用场合?
  •         BeeModel是如何实现的?和Core Data在使用上有何取舍?有哪些使用方式满足不同的需求?
  •         BeeUISignal是如何实现的?为了满足什么需求而实现成这样?与NSNotification比较有什么优缺点和适用场合?
       A1:BeeMessage就是对网络数据请求的封装,也就是常说的“协议”。BeeMessage和BeeHttpRequest(ASIDataFormRequest的封装)协同工作,通过内置的多个状态更新同步BeeHttpRequest的状态。包括发送和接收请求,解析数据,将数据发送给感兴趣的上层对象(UI或Model)。BeeMessage和BeeHttpRequest都持有responder数据成员,其中BeeHttpRequest的Responder就是对应的BeeMessage, BeeMessage对应的Responder就是对BeeMessage感兴趣的UI或者Model。看下图:

        技术分享
        
        可以看到,UIResponder将创建BeeMessage,填入Http参数,并将自己传入做为Responder。BeeMessage将自己传入BeeHttpRequest。UIResponder通过BeeMessage提供的Block来检查BeeMessage的状态。BeeMessage和BeeHttpRequest都实现为6个子状态,表示Http请求的状态流转并同步。这些状态流转都在BeeMessage的routine函数里完成。来看看UIResponder,和BeeMessage routine的代码:

        UIResponder:
-(void) getNewsList {

    BeeMesage * api = [API_NEWS_LIST api];
   
    //加入Http参数
    api.INPUT( @"uid", [NSString stringWithFormat:@"%d", _uid]);<p style="margin-top: 0px; margin-bottom: 0px; font-size: 11px; font-family: Menlo;">
</p>    api.whenUpdate = ^
    {        
        if ( api.sending )
        {
            //处理发送
        }
        else if(api.succeed)
        {
            //发送成功,获得BeeMessage解析好的数据
            [self.news addObjectsFromArray:api.resp.news];
        }
        else if ( api.failed )
        {
             //处理失败
        }
        else if ( api.cancelled )
        {
             //处理取消
        }
    };
    
    [api send];
}

        BeeMessage routine函数:
- (void)routine
{
    if ( self.sending )
    {
        NSString * requestURI = [[[ServerConfig sharedInstance] url] stringByAppendingString:api_news_list];
        
        self.HTTP_GET( requestURI );
    }
    else if ( self.succeed )
    {
        NSError* error;
        //通过XML解析回文
        CXMLDocument * document = [[CXMLDocument alloc] initWithXMLString:self.responseString options:NSUTF8StringEncoding error:&error];
        
        NSArray* newslist = [document nodesForXPath:@"oschina/newslist/news" error:&error];
               
        for ( CXMLElement* newsXML in newslist )
        {
            if(newsXML == nil) continue;
            
            NEWS* news = [NEWS createByXML:newsXML];
            [self.resp.news addObject:news];
        }
        
        if ( nil == self.resp || NO == [self.resp validate] )
        {
            self.failed = YES;
            return;
        }
    }
    else if ( self.failed )
    {
    }
    else if ( self.cancelled )
    {
    }
}
@end

        以上是BeeMessage的工作流程。BeeMessage的优点是大幅简化了数据协议的编写,省略了delegate, 散落在代码各处的block,让代码好维护。BeeHttpRequest封装了JSON kit, 可以非常容易得将回文转换为嵌套字典, XML则需要自己写parser。在实际的App中往往编写几十条这样的BeeMessage是十分枯燥,Bee在/Tools/Scaffold中提供自动化生成BeeMessage代码的工具,支持JSON数据。

        A2:BeeModel是本地数据存储的基类,附带了观察者支持。BeeModel的观察者一般来说是UI, 持有这个BeeModel, 比如说上面的新闻列表数据,UI是新闻列表展示的ViewController。BeeModel需要开发者在子类中自己实现本地存储,函数是saveCache, loadCache。Bee实现了基于plist的BeeUserDefaults, 实现了基于文件的BeeFileCache, 实现了基于内存字典或数组的BeeMemCache, BeeImageCache, 可以根据需求灵活使用。也可以在BeeModel里使用BeeDatabase和BeeActiveRecord, 支持本地数据库存储,具体方法参见开发者手册。
        BeeModel为什么引入观察者?当然还是为了能简化delegate和notification, 从代码里看感觉应该是为BeeUISignal做铺垫。BeeUISignal的路由方式底层用反射实现,对BeeModel来说,其观察者的NSClass可以存入到缓存里,路由事件时先去缓存寻找这些观察者是否实现了事件的接收方法并优先发送,如此可以加快事件路由的效率。
        BeeModel只是数据模型的基类,可以封装一个NSMutableArray,由NSMutableArray存储具体的数据对象。Core data个人认为只是SQLLite的封装,用于处理数据关联的情景。Apple大概在2004年引入Core data到OS X, 并移植到iOS, 似乎并不是很受开发者的欢迎。BeeModel处理本地存储比较灵活,提供了便利的UISignal事件,比较实用。具体使用时可以使用支持BeeUISignal的BeeViewModel。
        BeeViewModel包含若干子类,主要是对几种典型的UI展现方式抽象。BeeOnceViewModel一次展示数据,BeePageViewModel支持翻页管理,BeeStreamViewModel支持瀑布式页面管理,使用很方便。比如BeeStreamViewModel, 只用实现firstPage, 和nextPage两个方法,并在Model数据更新的时候发送事件到UIViewController即可。代码如下:

        
- (void)firstPage
{
    _pages = 0;
    
    [API_POST_LIST cancel];
    
    API_POST_LIST *api = [API_POST_LIST api];
    
    //每页20个
    api.INPUT( @"pageIndex", @"0" );
    api.INPUT( @"pageSize", @"20" );
    
    api.whenUpdate = ^
    {
        @normalize( api );
        
        if ( api.sending )
        {
            [self sendUISignal:self.RELOADING];
        }
        else if ( api.succeed )
        {
            //第一页拉取成功
            [self.posts removeAllObjects];
                
            [self.posts addObjectsFromArray:api.resp.posts];
                
            self.loaded = YES;
            self.pages  = 1;
                
            [self sendUISignal:self.RELOADED];
        }
        else if ( api.failed )
        {
            [self sendUISignal:self.RELOADED];
        }
    };
    
    [api send];
}

- (void)nextPage
{
    [API_POST_LIST cancel];
    
    API_POST_LIST *api = [API_POST_LIST api];
    
    int curBegin = self.pages * 20;
    int curEnd   = curBegin + 20;
    api.INPUT( @"pageIndex", [NSString stringWithFormat:@"%d", curBegin]);
    api.INPUT( @"pageSize", [NSString stringWithFormat:@"%d", curEnd] );
    
    api.whenUpdate = ^
    {
        @normalize( api );
        
        if ( api.sending )
        {
            [self sendUISignal:self.RELOADING];
        }
        else if ( api.succeed )
        {
            //处理下一页
            if( _pages == 0)
            {
                [self.posts removeAllObjects];
            }
            
            [self.posts addObjectsFromArray:api.resp.posts];
         
            self.loaded = YES;
            self.pages  = self.pages + 1;
            [self sendUISignal:self.RELOADED];        
        }
        else if ( api.failed )
        {
            [self sendUISignal:self.FAILED];
        }
        else if ( api.cancelled )
        {
            [self sendUISignal:self.CANCELLED];
        }
    };
    
    [api send];
}

        很简单就完成了瀑布式分页的流程,不是吗?合适的时候发送 RELOADING和RELOADED事件给UI, UI更新界面就可以了。

         A3: BeeUISignal是Bee实现的UI事件,主要有以下的特点:
  •          对象化的事件,可指定source, target
  •          可携带附件对象
  •          事件对象有通过带名字空间的三段取名方式。比如:"signal.NewsModel.RELOADED", 表示来源于NewsModel类型的RELOADED事件。
  •          事件对象和接收端解耦合,接收端采用“固定前缀 + 事件名”的方法生成selector, 比如-(void) handleUISignal_NewsModel_RELOADED: (BeeUISignal*) signal;
  •          普通UI对象可以自己实现signalTarget方法,自己决定UISignal的转发路径,比如UIView的转发路径是对应的[view viewController];
        BeeUISignal通过BeeUISignalBus转发,具体的转发顺序比较复杂,有兴趣的同学可以调试代码梳理清楚。但事件的发送目标只有四个选择: 尝试给Target class,尝试给Observer classes,尝试给UIView的superView,尝试给某个class的signalTarget(UIView的signalTarget是UIViewController)。通过这四个选择,BeeUISignal 既解决了定向发送(代替delegate),解决了观察者发送(NSNotification),也支持了UIView的Responder发送(UI Responder Chain),一种事件糅合了苹果常用的事件发送支持。

        可以看出,BeeUISignal比NSNotification的适用面更广,人为融合了多种通信模式。书写起来因为有宏也比较简单,具体写法可以参考开发者手册。可以看出BeeUISignal带来的好处还挺多,引入了一个齐全而稍显复杂的机制。如果不是针对UI的事件传送,或者简单的带附件的事件通知,个人认为不必引入BeeUISignal, 用NSNotification和delegate解决相关问题。
        


郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。