iOS中的一些约定、模式与三种回调机制

iOS中的各类模式
1.MVC

先说说 一般你们所见的MVC模式
在MVC的教科书定义中,Model采用的是观察者模式,也就是Model是被观察者,View是观察者,Model有任何改变的状况下,View都会接受到通知。
可是在典型Web环境中,View不须要实时的改变,只有客户端发送request时,View才可能须要改变。
换句话说,只有当咱们须要生成一个页面做为响应返回给客户端的时候,建立一个View并使用Model才有意义。
因此View就再也不直接观察Model,而是经过Controller来做为中间人。

一个Asp.NET mvc的大概流程图以下,能够看出Controller是相应客户端请求的入口,当一个请求由客户端发过来的时候,router选择合适的Controller实例化,再把请求交给相应的action处理。




可是在iOS中,事情又变得不同了些,这是由于View类(一般指UIView和它的之类)负责与用户交互,它们提供信息而且接受用户事件。
View的逻辑一般委托给Controller处理,但View不该该直接引用Controller。除直接父View和子View外,它们也不该引用其余View。View能够引用Model类,但通常只引用它们正在显示的特定Model对象。例如,StudentView对象可能显示一个Student对象。

View负责从用户接受事件,但不处理它们。当用户触碰View时,该View可能提醒一个委托说明它已被触碰,但它不能执行逻辑或者修改其余View。例如,按下删除键这一事件发生时应该只提示一个委托说明一个删除键已被按下。View不能直接让Model类删除数据或者本身从屏幕上删除数据。这些功能应该由Controller来负责,也就是说View和Model的联系应该由Controller来创建。

Controller实现了大部分应用程序特定的逻辑。大多数Controller在“Model”类和“View“类之间起协调做用。例如,UITableViewController协调数据Model和UITableView。有些控制器在Model对象或者View对象之间进行协调。这些控制器的名称有时以Manager结尾,例如CALayoutManager和CTFontManager。这些一般都是单例。

 

2.iOS中的委托,使用组合代替继承
使用委托配置对象是策略模式的一种形式。策略模式封装了一个算法而且容许你经过附加不一样的策略(算法)对象改变该对象的行为。

委托是一种策略对象,它封装了决定另外一 个对象行为的算法。例如,一个UITableView的委托实现了一个算法,该算法决定UITableView的行高。

结合在MVC中讨论的View和Controller的关系,能够由下图来表现二者关系,实线是直接引用,虚线是间接引用。间接引用能够经过id来实现






举个自定义协议的例子来讲,在LWRequest.h中,咱们定义了一个协议LWRequestDelegate,目的是为了把协议ASIHTTPRequestDelegate的实现转交给不一样的委托:

复制代码
@class LWRequest;
@class ASIHTTPRequest;

@protocol LWRequestDelegate <NSObject>
@optional
- (void) requestDidFinish:(LWRequest*)request;
@end

@interface LWRequest : NSObject <ASIHTTPRequestDelegate>
{
@protected
    
    ASIHTTPRequest* _serverRequest;   
    id<LWRequestDelegate> _delegate;
}

@property (nonatomic, weak) id<LWRequestDelegate> delegate;
复制代码
 
在LWRequest.m中,咱们定义

复制代码
@interface LWRequest ()

@property (nonatomic, strong) ASIHTTPRequest* serverRequest;

//自定义了get方法,set方法交由@synthesize生成
- (ASIHTTPRequest*) serverRequest;

- (void) requestDidFinish:(ASIHTTPRequest*)request;


@end

@implementation LWRequest

@synthesize serverRequest = _serverRequest;

//交由子类来实现,实现向不一样的地址发送请求
- (ASIHTTPRequest*) serverRequest 
{
    return nil;
}

//这个方法是协议ASIHTTPRequestDelegate的方法
- (void) requestDidFinish:(ASIHTTPRequest*)request 
{
    if (request.responseStatusCode == 200 || request.responseStatusCode == 500)
    {
        //转交给咱们的委托去处理
        if ([delegate_ respondsToSelector:@selector(requestDidFinish:)])
            [delegate_ requestDidFinish:self];
    }
    else
        ...
}
复制代码
任何实现了咱们定义的协议  @protocol LWRequestDelegate <NSObject> LWRequestDelegate的类,能够做为LWRequest的delegate来使用

@class LWRequest;

@interface SomeClass : NSObject <LWRequestDelegate>
{
    - (void) requestDidFinish:(LWRequest*)request;
}
 

在把配置属性直接添加到类以前,应先考虑改成添加一个委托,这样能够得到更好的灵活性。

 

 
3.单例模式
单例模式在Cocoa中很是常见。按照习惯,你能够经过一个以shared开头的类方法识别它。

单例每每用于业务层对象,就如同前面所说的CALayoutManager类同样。

单例每每会伴随着线程安全问题,能够在+sharedSingleton中添加一个@synchronize以达到线程安全的目的,但这样就会使用到同步对象,性能会产生问题。

建议经过GCD内置的dispatch_once方法、速度快,并且线程安全。

+ (MYSingleton *)sharedSingleton {
  static dispatch_once_t pred;
  static MYSingleton *instance = nil;
  dispatch_once(&pred, ^{instance = [[self alloc] init];});
  return instance;
}
 

4.iOS中的命令模式
主要用于UIControl,在指定控件的对应事件的时候用,其余状况下比较少用,因此就不说了。建议用Block来代替(相似于C#中的匿名委托),Block下面会说。

一样有关联的有proxy模式,不过用起来相对复杂一些,之后再说

 

5.iOS中的观察者模式
 观察者模式容许一个对象在它的状态变化时通知多个观察者,而被观察的对象无需对观察者有特定的认识。观察者模式在 Cocoa 中以多种形式出现,包括 NSNotification、KVO。它促使对象之间产生弱耦合,以使组件更具可重用性而且更加健壮。

这里介绍一下NSNotification,它看起来就像个全局的事件注册对象。

比较常见的是UIDeviceNotification:

UIDeviceOrientationDidChangeNotification
UIDeviceBatteryStateDidChangeNotification
UIDeviceBatteryLevelDidChangeNotification
UIDeviceProximityStateDidChangeNotification
好比检测设备方向是否变化的时候,就能够相应这些事件

复制代码
- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

    UIDevice *device = [UIDevice currentDevice];

    [device beginGeneratingDeviceOrientationNotifications];

    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];

    
    [nc addObserver:self //把本身做为事件观察者
           selector:@selector(orientationChanged:) // 用来响应事件发生时的方法
               name:UIDeviceOrientationDidChangeNotification // 方向改变事件
             object:device]; //事件发出者

    [self.window makeKeyAndVisible];

    return YES;
}


//响应事件时的方法
- (void)orientationChanged:(NSNotification *)note
{
    NSLog(@"orientationChanged: %d", [[note object] orientation]);
}

复制代码
使用起来很简单,要注意的是dealloc前候要先取消注册

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
 

6. Block
其实熟练使用C#匿名委托和JavaScript的人对这种对方法的抽象再熟悉不过了。

先看看语法, 用^符号来代表这是一个block变量,下面就声明了一个MyBlock变量。左边是返回类型,右边是参数类型

复制代码
typedef int (^myBlockA) (int p1, double p2);

UIColor * (^myBlockB)(Line *)

void (^myBlockC) ();

//这么赋值,是否是很眼熟啊?
myBlockA = ^(int p1,double p2){
  return p1;
}
 //block也能够是匿名的

- (void (^)()) getBlock
{
    return ^{ NSLog(@"  block 0:%d", 1);};
}

//使用的时候和lambda同样,返回值和参数都不是必须的

^{}

相似于

()=>{}

复制代码
若是使用typedef关键字进行代码块定义,有时会使代码更易读。这使得你没必要从新敲入代码块的全部参数和返回类型就能够复用这些定义。

typedef void (^myBlockA)(NSString *);
在C#中已经帮咱们预约义了Func<T,T>,Action<T>等,这样就省得咱们本身再次定义,这是我以为作得好的地方。
和C#中的匿名委托和Javascript中的函数同样,Block也能捕获变量。这是它与C中的函数指针有区别的地方。

好吧,关于Block有别于其余语言下的特殊实现点来了!
Block是一种比较特殊的 Objective-C 对象。跟传统对象不一样的是,Block不是在堆上建立的,而是在栈上。主要缘由有两 个,首先是由于性能——在栈上分配空间几乎老是比在堆上快;其次是出于访问其余局部变量的须要。

可是,当函数的做用域结束时,栈会被销毁。若是Block被传递给一个方法,此方法会在定义Block的做用域销毁后才调用到这个Block,因此应该复制Block。能够在传递时就copy
[[myBlockA copy] autorelease],
或者像这段代码同样,在调用的方法里copy
 复制代码
@interface Foo : NSObject
{
    void (^myBlock)(NSString *);
}
-(void)setMyBlock:(void (^)(NSString *))inBlock;
@end;

@implementation Foo

-(void)dealloc;
{
    [myBlock release];
    [super dealloc];
}

-(void)setMyBlock:(void (^)(NSString *))inBlock
{
    myBlock = [inBlock copy];
}
@end
复制代码

当Block被复制时,它会从栈移动到堆上。在块引用其做用域中定义的局部变量时,局部变量会随着块一块儿移动。全部被引用的 NSObject子类对象都会被保留(retain)而不是复制(由于这些对象原本就已经在堆上 了,而保留的耗时要比复制少一些)。Objective-C 的运行时环境为Block建立每一个局部变量的常量引用(const reference)。这也意味着默认状况下块不能修改上下文数据,以下所示的代码会致使编译错误。 
int statusCode = 1;

Myblock b = ^{
       statusCode = 4;
};
为了能修改局部变量,你须要用__block(双下划线) 修饰符来 声明变量。因此 statusCode 的声明应该是这样:

__block int statusCode = 1;
用到这个额外的修饰符是为了命令编译器当Block被复制时也把变量__block 复制过去。复制是比保留和传递常量引用更耗时的操做,因此系统的实现者决定把选择权交给开发者。

总而言之,__block 是复制而不是保留变量

这样还能够避免循环保留致使对象没法被释放的问题。

举个视频播放的例子,假设咱们在VideoPlayerController里持有一个AVPlayer类的成员变量_player,若是不加上__block,就会出现循环保留问题。

复制代码
    __block VideoPlayerController *vpc = self;

    _timeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(interval, NSEC_PER_SEC)
                                                         queue:NULL
                                                    usingBlock:
                                         ^(CMTime time)
                                         {
                                             [vpc doSth];
                                         }];
复制代码
相关文章
相关标签/搜索