您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
iOS开发:设计模式那点事
 
作者:MexiQQ的博客 来自于:china 发布于 2015-10-19
   次浏览      
 

说起设计模式,感觉自己把握不了笔头,所以单拿出iOS开发中的几种常用设计模式谈一下。

单例模式(Singleton)

概念:整个应用或系统只能有该类的一个实例

在iOS开发我们经常碰到只需要某类一个实例的情况,最常见的莫过于对硬件参数的访问类,比如UIAccelerometer.这个类可以帮助我们获得硬件在各个方向轴上的加速度,但是我们仅仅需要它的一个实例就够了,再多,只会浪费内存。

所以苹果提供了一个UIAccelerometer的实例化方法+sharedAccelerometer,从名字上我们也能看出此方法让整个应用共享一个UIAccelerometer实例(PS:iOS 的开放中,我们往往能从方法名中就了解这个方法的作用),它内部的如何实现我们暂且不谈,先来看看还有哪些类同样使用了单例模式。

UIApplication类提供了 +sharedAPplication方法创建和获取UIApplication单例

NSBundle类提供了 +mainBunle方法获取NSBundle单例

NSFileManager类提供了 +defaultManager方法创建和获得NSFileManager单例。(PS:有些时候我们得放弃使用单例模式,使用-init方法去实现一个新的实例,比如使用委托时)

NSNotificationCenter提供了 +defaultCenter方法创建和获取NSNotificationCenter单例(PS:该类还遵循了另一个重要的设计模式:观察者模式)

NSUserDefaults类提供了 +defaultUserDefaults方法去创建和获取NSUserDefaults单例

等等,苹果的SDK中大量的遵循此设计模式,那么它的内部是如何实现的呢?

首先给大家介绍一下GCD技术,是苹果针对于多核CPU的多任务解决方案。你不需要了解更多,只需要知道是一组基于C语言开发的API(详细内容可以看一下唐巧前辈的这篇博文:http://blog.devtang.com/blog/2012/02/22/use-gcd/ )。GCD提供了一个dispatch_once函数,这个函数的作用就是保证block(代码块:暂时理解为一个跟函数相近的东西,具体可以参照这篇文章:http://blog.csdn.net/enuola/article/details/8674063 )里的语句在整个应用的生命周期里只执行一次。

OK,接下来就给出一个使用了单例模式新建和获取实例的类模版,代码如下:

//Singleton.h
@interface Singleton : NSObject
+ (Singleton *)sharedSingleton; <1>
@end

/***************************************************************/

//Singleton.m
#import "Singleton.h"
@implementation Singleton
static Singleton *sharedSingleton = nil;<2>

+ (Singleton *)sharedSingleton{
static dispatch_once_t once;<3>
dispatch_once(&once,^{
sharedSingleton = [[self alloc] init];<4>
//dosometing
});
return sharedSingleton;<5>
}

上述代码中有5小步,解释如下:

1. 声明一个可以新建和获取单个实例对象的方法

2. 声明一个static类型的类变量

3. 声明一个只执行一次的任务

4. 调用dispatch_once执行该任务指定的代码块,在该代码块中实例化上文声明的类变量

5. 返回在整个应用的生命周期中只会被实例化一次的变量

OK,这就是iOS开发中单例模式的机制,下面我们就看看如何在实际开发中使用此模式?(PS:为了尽可能的突出核心内容,我们会对设计中的其他模式或内容一笔带过)

假如我们需要在iOS应用中实现分层的架构设计,即我们需要把数据的持久层,展示层,和逻辑层分开。为了突出重点,我们直接把目光转到持久层,而根据MVC的设计模式,我们又可以把持久层细分为DAO层(放置访问数据对象的四类方法)和Domain层(各种实体类,比如学生),这样就可以使用DAO层中的方法,配合实体类Domain层对数据进行清晰的增删改查。那么我们如何设计呢?

从使用者的角度看,我们期望获得DAO层的类实例,然后调用它的增删改查四大方法。可是这个类实例,我们似乎只需要一个就足够了,再多的话不利于管理且浪费内存。OK,我们可以使用单例模式了,代码如下:

.h文件:

//StudentDAO.h
@interface StudentDAO:NSObject
@property (nonatomic,strong) NSMutaleArray *StudentsInfo;

+ (StudentDAO *)sharedStudentDAO;

-(int) create:(Student*)student;
-(int) remove:(Student*)student;
-(int) modify:(Student*)student;
-(NSMutaleArray) findAll;
@end

.m文件:

//StudentDAO.m
#import "StudentDAO.h"
#import "Student.h"
@implementation StudentDAO

static StudentDAO *studentDao = nil;
+ (StudentDAO)sharedStudentDAO{
static dispatch_once_t once;
dispatch_once(&once,^{
Student *student1 = [[Student alloc]init];
student1.name = "MexiQQ";
student1.studentNum = "201200301101";

Student *student2 = [[Student alloc]init];
student2.name = "Ricardo_LI";
student2.studentNum = "201200301102";

studentDao = [[self alloc] init];
studentDao._StudentsInfo = [[NSMutaleArray alloc]init];
[studentDao._StudentsInfo addObject:student1];
[studentDao._StudentsInfo addObject:student2];
});
return studentDao;
}
//插入的方法
-(int)create:(Student*)stu{
[self._StudentsInfo addObject:stu];
return 0;
}
//删除的方法
-(int)remove:(Student*)stu{
for(Student* s in self._StudentsInfo){
if([stu.studentNum isEqual:s.studentNum]){
[self._StudentsInfo removeObject:s]
break;
}
}
}
-(int)modify...... //省略不写
-(NSMutaleArray)findAll...... //省略不写

上述例子不难理解,其中用到的Student类我这里就不给出了,只是一个含有姓名和学号属性的实体类。

观察者模式

概念:一个对象状态改变,通知正在对他进行观察的对象,这些对象根据各自要求做出相应的改变

图例:

如图所示:操作对象向被观察者对象投送消息,使得被观察者的状态得以改变,在此之前已经有观察者向被观察对象注册,订阅它的广播,现在被观察对象将自己状态发生改变的消息广播出来,观察者接收到消息各自做出应变。

OK,我们先来看看在苹果的Cocoa Touch框架中有谁使用了观察者模式:

通知(notification)机制

原理图如下:

如图所示,在通知机制中对某个通知感兴趣的所有对象都可以成为接受者。首先,这些对象需要向通知中心(NSNotificationCenter)发出addObserver:selector:name:object:消息进行注册,在投送对象投送通知送给通知中心时,通知中心就会把通知广播给注册过的接受者。所有的接受者不知道通知是谁投送的,不去关心它的细节。投送对象和接受者是一对多的关系。接受者如果对通知不再关注,会给通知中心发送removeObserver:name:Object:消息解除注册,以后不再接受通知。
(ps:这段话内容摘抄自关东升先生的文章)

OK,我们试着去使用一下通知机制:

新建一个Single view Project,对项目中的文件做以下修改:

AppDelegate.m

- (void)applicationDidEnterBackground:(UIApplication *)application {
[[NSNotificationCenter defaultCenter]postNotificationName:@"APPTerminate" object:self];
}

ViewController.m

//
// ViewController.m
// TestNotification
//
// Created by liwenqian on 14-10-18.
// Copyright (c) 2014年 liwenqian. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];
//注意此处的selector有参数,要加冒号
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(doSomething:) name:@"APPTerminate" object:nil];
// Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
[[NSNotificationCenter defaultCenter]removeObserver:self];
// Dispose of any resources that can be recreated.
}

#pragma mark -处理通知
-(void)doSomething:(NSNotification*)notification{
NSLog(@"收到通知");
}

@end

如上所示,对模版项目的两个文件的方法或整个文件做出修改,Command+R运行你的APP,再按下Home键(Command+H),会发现打印出一行收到通知的文字,如下:

在APP退到后台时,发出广播,而viewController因为时观察者,收到广播,执行doSomething方法,打印出收到广播的文字。

KVO(Key-Value-Observing)机制

原理图如下:

如图所示:

该机制下观察者的注册是在被观察者的内部进行的,不同于通知机制(由观察者自己注册),需要被观察者和观察者同时实现一个协议:NSKeyValueObserving,被观察者通过addObserver:forKeypath:options:context方法注册观察者,以及要被观察的属性。

新建一个single view project,同时新建一个继承自NSObject的TestWatche类:文件结构如下图:

对文件进行如下修改:

AppDelegate.h

//
// AppDelegate.h
// TestNotification
//
// Created by liwenqian on 14-10-18.
// Copyright (c) 2014年 liwenqian. All rights reserved.
//

#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
#import "TestWatche.h"

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
@property (strong,nonatomic) NSString *state;
@property (strong,nonatomic) TestWatche *watcher;


- (void)saveContext;
- (NSURL *)applicationDocumentsDirectory;


@end

AppDelegate.m 对如下方法做出修改

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)
launchOptions {    // Override point for customization after application launch.

self.watcher = [TestWatche alloc];

[self addObserver:self.watcher forKeyPath:@"state" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:@"pass content"]; self.state = @"launch"; return YES;
}

- (void)applicationDidEnterBackground:(UIApplication *)application { self.state = @"backgroud";
}

TestWatche.m(由于继承自NSObject,而NSObject已实现了NSKeyValueObserving协议,所以不需要做声明)

//
// TestWatche.m
// TestNotification
//
// Created by liwenqian on 14-10-18.
// Copyright (c) 2014年 liwenqian. All rights reserved.
//

#import "TestWatche.h"

@implementation TestWatche

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

OK,Command+B Command+R Command+H看看你的应用输出了什么,如果你的操作无误的话,会显示如下内容:

委托模式

个人认为委托模式大多数人解释的复杂了,其实就像是java中的接口,类可以实现或不实现协议(接口)中的方法。通过此种方式,达到最大的解耦目的,方便项目的扩展。不过你需要设置应用的委托对象,以确定协议中的方法为谁服务。

拿最常用的UITableViewDelegate UITableViewDataSource来举例:

实现一个页面有一个UItableView,UItableView的每一栏(cell)的数据由我们指定,那么我们该如何做呢?苹果也自然想到了这一点,于是定义了一个接口,这个接口有许多的方法,只需要我们把要服务的对象传进去,就可以使用这些方法了,这个接口就是委托和协议。而UITableViewDelegate 和 UITableViewDataSource 就是专为UITableView而写的委托和协议。用法如下:

ViewController.h

//
// ViewController.h
// RecipeBookMe
//
// Created by liwenqian on 14-9-10.
// Copyright (c) 2014年 liwenqian. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>

@property (nonatomic, strong) IBOutlet UITableView *mytableView;

@end

ViewController.m

//
// ViewController.m
// RecipeBookMe
//
// Created by liwenqian on 14-9-10.
// Copyright (c) 2014年 liwenqian. All rights reserved.
//

#import "ViewController.h"
#import "DetailViewController.h"
#import "Recipe.h"
#import "RecipeTableCellTableViewCell.h"

@interface ViewController ()

@end

@implementation ViewController{
NSArray *recipes;
}

@synthesize mytableView;

- (void)viewDidLoad {
[super viewDidLoad];

// Initialize the recipes array
Recipe *recipe1 = [Recipe new];

recipe1.name = @"Egg Benedict";
recipe1.prepTime = @"30 min";
recipe1.image = @"egg_benedict.jpg";
recipe1.ingredients = [NSArray arrayWithObjects:@"2 fresh English muffins", @"4 eggs", @"4 rashers of back bacon", @"2 egg yolks", @"1 tbsp of lemon juice", @"125 g of butter", @"salt and pepper", nil];

Recipe *recipe2 = [Recipe new];
recipe2.name = @"Mushroom Risotto";
recipe2.prepTime = @"30 min";
recipe2.image = @"mushroom_risotto.jpg";
recipe2.ingredients = [NSArray arrayWithObjects:@"1 tbsp dried porcini mushrooms", @"2 tbsp olive oil", @"1 onion, chopped", @"2 garlic cloves", @"350g/12oz arborio rice", @"1.2 litres/2 pints hot vegetable stock", @"salt and pepper", @"25g/1oz butter", nil];

recipes = [NSArray arrayWithObjects:recipe1, recipe2, nil];

}

- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}

/*--------------------------------------------------------------------*/
//定义UITableview的栏目数量
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [recipes count];
}

//定义UITableviewCell的显示内容
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CoustomerTableIdentifier = @"RecipeTableCellTableViewCell";

RecipeTableCellTableViewCell *cell =(RecipeTableCellTableViewCell *) [tableView dequeueReusableCellWithIdentifier:CoustomerTableIdentifier];

if (cell == nil) {
cell = [[RecipeTableCellTableViewCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier:CoustomerTableIdentifier];
}

recipe = [recipes objectAtIndex:indexPath.row];

cell.nameLabel.text = recipe.name;
cell.prepTimeLabel.text= recipe.prepTime;
cell.thumbnailImageView.image = [UIImage imageNamed:recipe.image];

return cell;
}

//点击每一栏执行跳转时的处理
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"showRecipeDetail"]) {

NSIndexPath *indexPath = nil;
Recipe *recipe = nil;

indexPath = [self.mytableView indexPathForSelectedRow];
recipe = [recipes objectAtIndex:indexPath.row];

DetailViewController *destViewController = segue.destinationViewController;
destViewController.recipe = [recipes objectAtIndex:indexPath.row];
}
}

//定义cell的高度
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 71;
}
/*--------------------------------------------------------------------*/
@end

如上所示,两条/------/注释间的方法全部来自于委托和协议。利用委托和协议,你可以把主要精力放到逻辑业务上,将数据绑定和事件处理交给委托和协议去完成。

   
次浏览       
 
相关文章

手机软件测试用例设计实践
手机客户端UI测试分析
iPhone消息推送机制实现与探讨
Android手机开发(一)
 
相关文档

Android_UI官方设计教程
手机开发平台介绍
android拍照及上传功能
Android讲义智能手机开发
相关课程

Android高级移动应用程序
Android系统开发
Android应用开发
手机软件测试
最新活动计划
LLM大模型应用与项目构建 12-26[特惠]
QT应用开发 11-21[线上]
C++高级编程 11-27[北京]
业务建模&领域驱动设计 11-15[北京]
用户研究与用户建模 11-21[北京]
SysML和EA进行系统设计建模 11-28[北京]

android人机界面指南
Android手机开发(一)
Android手机开发(二)
Android手机开发(三)
Android手机开发(四)
iPhone消息推送机制实现探讨
手机软件测试用例设计实践
手机客户端UI测试分析
手机软件自动化测试研究报告
更多...   

Android高级移动应用程序
Android应用开发
Android系统开发
手机软件测试
嵌入式软件测试
Android软、硬、云整合

领先IT公司 android开发平台最佳实践
北京 Android开发技术进阶
某新能源领域企业 Android开发技术
某航天公司 Android、IOS应用软件开发
阿尔卡特 Linux内核驱动
艾默生 嵌入式软件架构设计
西门子 嵌入式架构设计
更多...