随笔 - 75  文章 - 1  评论 - 183 

第一节里,我们了解了ObjectC的语法,在第二节里,在正式动手之前,先要了解一些iOS中的基本约定与模式。

 

Foundation.h

我们之所以能够方便的使用ObjectC中的诸如NSString、NSNumber等类型,是因为在Foundation这个框架中对C语言基本类型进行了封装,并以对象的形式公开给我们使用。

所以我们在使用前都要#import <Fonduation/Foundation.h>,事实上,XCode会帮我们这么做。

 

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结尾,例如CALayoutManagerCTFontManager。这些通常都是单例。

 

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不是在堆上创建的,而是在栈上(ARC会自动copy,但是block还是在栈上创建的)。主要原因有两 个,首先是因为性能——在栈上分配空间几乎总是比在堆上快;其次是出于访问其他局部变量的需要。

但是,当函数的作用域结束时,栈会被销毁。如果Block被传递给一个方法,此方法会在定义Block的作用域销毁后才调用到这个Block,所以应该用copy 方法把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 是copy而不是retain

这样还可以避免循环保留导致对象无法被释放的问题。

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

    __block VideoPlayerController *vpc = self;
_timeObserver
= [_player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(interval, NSEC_PER_SEC) queue:NULL usingBlock:                     ^(CMTime time)                     {                     [vpc doSth];                     }];

 

 

 

 

 

 最后,如果你觉得这篇文章有帮助,请点右下角一下推荐,这是我继续下去的动力,谢谢!

posted on 2013-04-22 19:12 一路转圈的雪人 阅读(...) 评论(...) 编辑 收藏