您的位置:首页 > 移动开发 > Objective-C

Objective-C基础之常用简单设计模式

2015-11-18 11:30 363 查看
Objective-C基础之常用简单设计模式

1、模型-视图-控制器

(1)模型

其实这是一个不是很想谈论的问题,这个问题经常被讲,都讲烂了,模型谁都会建立,但是最好与数据源匹配。模型类不要将控制器引入,这不是一个好的代码,因为我们希望它和控制器的耦合度尽量低。但也许会有一个委托,委托与控制器间通过协议进行关联,这样我们可不比指明控制器的具体类型,将会有好的拓展性,我们的模块具有相对独立性,它不依赖于具体控制器。

模型的命名应该避免重复,我们应该加前缀比如一个人类应该叫FHPerson,以免跟其他的类造成命名冲突。

模型类可以是变的,也可以是不变的。乍一听,可能觉得奇怪,啥叫变得,啥叫不变的。我们举个例子,NSString就是一个不变类,它一旦初始化一个值,我们就不可以改变它。我们只能让指针指向另一个对象。但是对象本身是不能改变,可以理解为只读的。与之相反NSMutableString,就是可变的。

不可变的类有很多优点,它能更好的节约内存和时间。不可以变得retain相当于copy,我们进行复制的时候,不需要重新分配空间。而且它在没有锁的情况下是线程安全的,这很明显加快了程序的运行速度,并且保证了安全性。我们设计类,应该尽量设计不可变的类, 除非我们有重要的需求,需要改变它。

(2)视图

视图负责显示,并且接受用户事件,与用户进行交互的。层(Layer)是用来显示的,而视图是层的容器,换言之,层是不可交换的视图。

同样,视图也不该引用控制器对象。它与控制器应该也是低耦合的,视图负责接受事件,但是不做处理,该视图可能通过委托来告诉控制器,它已经被触碰,但是不应该执行其他逻辑(例如修改其他视图,视图不应该引入其他跟自己无关的视图(非SuperView个SubView)),而具体的逻辑执行,应该交给控制器。

视图可以引用模型,但是一般指引用与之显示直接相关的模型,例如视图显示Person,那么它可能需要引用一个Person对象。

视图的命名也该有意义,比如显示Person,那么应该叫PersonView,一般是以View为结束的。

相同视图出现的频率有时候会很高,有时候需要有复用机制。例如系统的UITableview,因为他们的不同部分,仅依赖于模型,所以根据模型刷新视图就可以了,可能并不需要重新建立对象,因为我们实际只需要管理好数据及可以了,显示仅仅是让用户看的,而数据才关乎我们的功能。但是,复用性有时候并不“易用”,所以我们应该做一个权衡。

(3)控制器

模型和视图应该通过控制器进行联系,控制器应该负责特定的逻辑。控制器是消息的传递着,功能来自于数据管理即模型层,而交互来源于视图层,控制层是两层的消息传递着和协调者。

控制器的复用性是三者中最低的,很难进行复用。因为我们要尽量避免视图和模型引用它,如果需要我们应该通过协议去进行消息传递。

有一种情况需要注意,很多人都会把全局变量作为应用程序委托(AppDelegate)的属性。虽然可以,但是尽量不要这样做,这样做代码难易重用,你的代码会依赖于AppDelegate,而这个类是很难移动的,如果属性多了,移动就非常繁琐。全局变量最好用单例模式实现,后面会讲到。

2、数据源-委托

我们以UITableView为例,由于UITableView有很多子视图,它们的布局很重要,它们每个元素都有自己的高度,UITableView需要知道它们的情况,来能进行合理布局,显示出预期的效果。但是如果将这些配置信息都作为成员变量的话,情况会变得很复杂,很难用。尤其是外部刷新的时候,外部又需要重新进行配置,增加了程序的代码冗余,并且容易出错。这时候我们可以通过协议来进行数据源的获取,将复杂的操作,交给内部,我们做程序也应该这样,对外暴露的接口应该尽量简单,而将复杂操作交给内部,才能有更好的封装性和复用性。当UITableView需要刷新的时候,仅仅需要一个外部简单的通知接口reloadData就可以。如果内部想获取数据源,那么用数据源委托对象来进行。这样外部仅仅需要实现数据源委托协议的一次配置,就可以很简单的实现刷新。

值得注意的是,对象没有必要保留(retain)他们的委托对象,因为如果它们所有可用的委托对象都消失,它们也就没有存在的意义。更重要的是,如果委托对象,会造成循环引用,导致内存泄露。所以我们的委托要定义成weak(ARC下)和assign(非ARC下)类型的。

对于UITableView的每个元素的操作,也需要用一个委托进行通知,因为无穷无尽的元素,你不可能为每一个事件都定义一个action回调,而且你的目的也很简单,只是知道点击的是哪一个就可以,所以需要通过一个委托来实现。由UITableView内部进行复制的索引管理,最后简单的呈现出来。

而且对于视图来讲,它还有一个作用,比如我的视图需要一个复杂的算法来求高度,不同算法结果不同,显示也不同,如果不使用委托,我们需要派生很多的子类,然而我们将逻辑交给外面,就避免了子类的派生。

当我们写一个复杂视图的时候不放设置一个数据源委托和一个事件委托。

3、目标-动作

目标动作是命令模式中最简单的一种。它不是完整的命令模式,它没有把请求封装成一个单独对象,但是允许请求灵活性。

其实它对于我们并不陌生,比如定时器(NSTimber),手势识别(UIGestureRecognized),控制(UIContro,UIButton是其子类),工具栏,IBAction,通知中心(NSNotificationCenter)都是采用了目标-动作模式。

#import <Foundation/Foundation.h>

@interface GHControl : NSObject

{

SEL m_action;

id m_target;

}

-(void)setAction:(SEL)action
Target:(id)target;

-(void)hehe;

@end

@implementation GHControl

-(void)setAction:(SEL)action
Target:(id)target

{

m_action = action;

m_target = target;

}

-(void)hehe

{

NSLog(@"hehe");

if ([m_target respondsToSelector:m_action])
{

[m_target performSelector:m_action withObject:self];

}

}

@end

AppDelegate的部分代码

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

GHControl* control = [[GHControl alloc]init];

[control setAction:@selector(xixi:) Target:self];

[control hehe];

[control release];

return YES;

}

-(void)xixi:(id)sender

{

NSLog(@"xixi");

}

上面代码是一个很简单的例子,当执行hehe的时候,会立刻执行xixi。

目标-动作模式需要一个目标对象和动作,而具体的函数名和函数原型并不需要知道。

目标-动作模式与委托

根据委托很像,主要区别在于,动作可以随意配,而且委托的动作必须有协议来定,严格规定了函数的原型以及名称。但是委托里面可以给定一系列的动作。而目标-动作模式则需要进行一些处理才可以实现。

委托允许程序检测动作是否实现,而目标-动作不允许。

注意:虽然无法检测动作是否以实现,但是我们可以打开未声明的选择器警告来进行处理,如果选择器未声明,会有一条警告出现。

一般来讲,目标-动作不是一个好的代码。如果少量回调建议用块(Block),而大量回调,尽量用委托(Delegate)。

4、签名-调用模式

签名-调用模式是命令模式的一种。它将目标,选择器,方法签名,参数等封装到一个对象里面。 NSInvocation是该模式的一种实现。当NSInvocation被调用时,它会发送消息,oc运行时候会找到正确的方法来执行。

NSMutableSet* set = [NSMutableSet set];

NSString* stuff = @"Stuff";

SEL selector = @selector(addObject:);

NSMethodSignature * sig = [set methodSignatureForSelector:selector];

NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:sig];

[invocation setTarget:set] ;

[invocation setSelector:selector] ;

//将第一个参数于索引2处

[ invocation setArgument:&stuff atIndex:2];

[invocation invoke] ;

注意:第一个参数被置于了索引2处,因为0是Target,1是Selector。必须用把指针传给参数,不能是参数本身。

它们还可以使用invokeWithTarget:方法或setTarget:方法分派到不同对象。创建调用的大部分开销都在签名阶段,所以缓存它们,可以提高性能。

如果你想保留参数你可以调用 retainArguments方法。当调用被释放,它们会自动释放。

注意:如果你用NSTimer timerWithTimeInterval:invocation:repeats:方法,定时器会自动调用retainArguments方法。

5、蹦床模式

蹦床很形象,是把一个对象的消息,反弹给另一个对象。该技术允许将消息弹到线程,缓存结果、合并重复消息、以及其他配置。蹦床主要通过forwardInvocation:方法实现。

如果一个oc对象提示错误之前不响应一个selector就会创建一个NSInvocation对象,并且传递给该对象的forwardInvocation:方法。它可以实现将所有的消息反弹给所有的观察者。(此篇文章暂时略过)

命令模式的撤销,需要NSUndoManager的prepareWithInvocationTarget:方法。撤销管理器会返回一个蹦床。它们被转换成NSInvocation对象存储到栈中,当用户想撤销一个操作的时候,管理器会调用栈中最后一条命令。

6、观察者模式与KVO

观察者模式允许一个对象的变化通知多个观察者。Cocoa有很多实现方法如:NSNotification、委托观察以及KVO。

委托已经讲过,不赘述。

通知比KVO容易理解,实现简单。 (此篇文章暂时略过)

7、 单例模式

单例模式控制全局变量是一个非常好的方案,而自身也是全局变量。

一个好的单例模式,建议用GCD技术

+(MySingleton *)sharedSingleton

{

static dispatch_once_t pred;

static MySingleton * instance = nil;

dispatch_once(&pred,^{instance = [[self alloc]init];});

return instance;

}

这样写速度快,方便,并且线程安全。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: