iOS 开发基础(5)--设计模式

MVC:

Model: 负责存储,定义,操作数据;
View: 用来展示给用户数据,和用户进行交互操作的;
Controller:是 View 和 Model 的协调者,其可以直接与 Model 和 View 通信;而 View 不能和 Controller直接通信,需要利用代理协议的方式才能间接进行通信;或者通过 target-action 的方式进行通信, target 会留出 outlet 接口供 Controller 调用;同样 Model 和 Controller 也不能进行直接的通信,可以通过通知中心和 KVO 的方式来实现二者之间的通行; Model和 View 是不能直接进行通信的,只能通过 Controller 进行传递;

缺点:

  • 增加系统和实现的复杂性,意味着将要管理更多的文件;
  • 视图和控制器之间的联系过于紧密,耦合性太强;
  • MVC 不适合小型甚至中等规模的应用程序;
  • 视图和数据模型的低效率访问;

优点:

  • 低耦合性;
  • 可移植性好,可维护性强;
  • 利于管理,较低的声明周期成本;

MVVM:

iOS 开发基础(5)--设计模式_第1张图片
MVVM 和 MVC 的对比

在MVC模式的iOS开发中,Controller承担了太多的代码,包含着我们的视图处理逻辑和业务逻辑;使 Controller 显得很臃肿, MVVM 就应运而生;
在MVVM中,我们将视图处理逻辑从C中剥离出来给V,剩下的业务逻辑部分被称做View-Model;

Model:数据层。

ViewModel层,就是View和Model层的粘合剂,他是一个放置用户输入验证逻辑,视图显示逻辑,发起网络请求和其他各种各样的代码的极好的地方。说白了,就是把原来ViewController层的业务逻辑和页面逻辑等剥离出来放到ViewModel层。

View层,就是ViewController层,他的任务就是从ViewModel层获取数据,然后显示。

iOS 开发基础(5)--设计模式_第2张图片
MVVM 结构.png

优点:

  • MVVM 可以兼容你当下使用的 MVC 架构。
  • MVVM 增加你的应用的可测试性。
  • MVVM 配合一个绑定机制效果最好。
    如我们之前所见,MVVM 基本上就是 MVC 的改进版,所以很容易就能看到它如何被整合到现有使用典型 MVC 架构的应用中;

当Model 是不可变的,我们可以只在初始化的时候指定我们 View Model 的属性。对于可变 Model,我们还需要使用一些绑定机制,这样 View Model 就能在背后的 Model 改变时更新自身的属性。此外,一旦 View Model 上的 Model 发生改变,那 View 的属性也需要更新。Model 的改变应该级联向下通过 View Model 进入 View。
在 OS X 上,我们可以使用 Cocoa 绑定,但在 iOS 上我们并没有这样好的配置可用。我们想到了 KVO(Key-Value Observation),而且它确实做了很伟大的工作。然而,对于一个简单的绑定都需要很大的样板代码,更不用说有许多属性需要绑定了。作为替代,我个人喜欢使用 ReactiveCocoa,但 MVVM 并未强制我们使用ReactiveCocoa。MVVM 是一个伟大的典范,它自身独立,只是在有一个良好的绑定框架时做得更好。

那 MVVM 相比 MVC 到底有哪些好处呢?主要可以归纳为以下三点:
1.由于展示逻辑被抽取到了 viewModel 中,所以 view 中的代码将会变得非常轻量级;
2.由于 viewModel 中的代码是与 UI 无关的,所以它具有良好的可测试性;
3.对于一个封装了大量业务逻辑的 model 来说,改变它可能会比较困难,并且存在一定的风险。在这种场景下,viewModel 可以作为 model 的适配器使用,从而避免对 model 进行较大的改动。

观察者模式(通知中心,KVO)

NSNotificationCenter

NSNotificationCenter是一种典型的有调度中心的观察者模式实现方式。以NSNotificationCenter为中心,观察者往Center中注册对某个主题对象的变化感兴趣,主题对象通过NSNotificationCenter进行变化广播。这种模型就是文章开始发布订阅报纸在OC中的一种类似实现。所有的观察和监听行为都向同一个中心注册,所有对象的变化也都通过同一个中心向外广播。
NSNotificationCenter就像一个枢纽一样,处在整个观察者模式的核心位置,调度着消息在观察者和监听者之间传递。

注意点:

  • 观察者Observer,一般在需要接受通知的地方注册观察者,通过NSNotificationCenter的addObserver:selector:name:object接口来注册对某一类型通知感兴趣.在注册时候一定要注意,NSNotificationCenter不会对观察者进行引用计数+1的操作,我们在程序中释放观察者的时候,一定要去报从center中将其注销了。
  • 主题对象,被观察的对象,通过postNotificationName:object:userInfo:发送某一类型通知,广播改变。
  • 通知对象NSNotification,当有通知来的时候,Center会调用观察者注册的接口来广播通知,同时传递存储着更改内容的NSNotification对象

KVO(Key-Value Observation)

即键值观察。是一种没有中心枢纽的观察者模式的实现方式。一个主题对象管理所有依赖于它的观察者对象,并且在自身状态发生改变的时候主动通知观察者对象。
完成一次完整的改变通知过程,经过以下几次过程:
1.注册观察者[message addObserver:self forKeyPath:KVOPathKey options:NSKeyValueObservingOptionNew context:Nil];
2.更改主题对象属性的值,即触发发送更改的通知 _message.key = @"message";
3.在制定的回调函数中,处理收到的更改通知;
4.注销观察者 [_message removeObserver:self forKeyPath:kKVOPathKey];

KVO 的底层实现原理

KVO 是基于 RunTime 机制实现的,当某一个类的实例第一次使用KVO的时候,系统就会在运行期间动态的创建该类的一个派生类,该类的命名规则一般是以NSKVONotifying为前缀,以原本的类名为后缀。并且将原型的对象的isa指针指向该派生类。同时在派生类中重载了使用KVO的属性的setter方法,在重载的setter方法中实现真正的通知机制。这么做是基于设置属性会调用 setter 方法,而通过重写就获得了 KVO 需要的通知机制。当然前提是要通过遵循 KVO 的属性设置方式来变更属性值,如果仅是直接修改属性对应的成员变量,是无法实现 KVO 的。

同时派生类还重写了 class 方法以“欺骗”外部调用者它就是起初的那个类。因此这个对象就成为该派生类的对象了,因而在该对象上对 setter 的调用就会调用重写的 setter,从而激活键值通知机制。此外,派生类还重写了 dealloc 方法来释放资源。因此,在使用的时候 KVO 的性能不如通知中心;

KVO 的缺陷和解决办法:

缺陷处理结果分析:

  • 所有的observe处理都放在一个方法里。解决。因为回调被分散成块了,不再集中。但是用块也不是很统一,但个人觉得还是比原生的好点。
  • 严重依赖于string。未解决。
  • 需要自己处理superclass的observe事务。解决。所有执行了xw_addObserverBlockForKeyPath的块都会被放入KVOBlockSet,对应的keyPath值被改变时会回调对应的所有block,不存在调用super的问题。
  • 多次相同KVO的removeObserve会导致crash。作者用的hook dealloc调剂remove方法达到自动移除功能,目前来看逻辑没问题。

代理(委托)设计模式

什么是代理?
这是苹果官方文档的解释:
Delegation is a simple and powerful pattern in which one object in a program acts on behalf of, or in coordination with, another object. The delegating object keeps a reference to the other object—the delegate—and at the appropriate time sends a message to it. The message informs the delegate of an event that the delegating object is about to handle or has just handled. The delegate may respond to the message by updating the appearance or state of itself or other objects in the application, and in some cases it can return a value that affects how an impending event is handled. The main value of delegation is that it allows you to easily customize the behavior of several objects in one central object.
翻译一下大概是:
代理是一种简单而功能强大的设计模式,这种模式用于一个对象“代表”另外一个对象和程序中其他的对象进行交互。 主对象(这里指的是delegating object)中维护一个代理(delegate)的引用并且在合适的时候向这个代理发送消息。这个消息通知“代理”主对象即将处理或是已经处理完了某一个事件。这个代理可以通过更新自己或是其它对象的UI界面或是其它状态来响应主对象所发送过来的这个事件的消息。或是在某些情况下能返回一个值来影响其它即将发生的事件该如何来处理。代理的主要价值是它可以让你容易的定制各种对象的行为。注意这里的代理是个名词,它本身是一个对象,这个对象是专门代表被代理对象来和程序中其他对象打交道的。
简单来说就是:
代理的目的是改变或传递控制链。允许一个类在某些特定时刻通知到其他类,而不需要获取到那些类的指针。可以解除对象之间的耦合性,属于一种回调机制,是一对一的关系。

代理设计模式的使用场景:

1,A 想让B 帮他做些事情,可以让B成为A的代理先
2,A 想通知B,A发生了一些事情,可以让B成为A的代理先
3,B 想监听A发生了一些事情,可以让B成为A的代理先

代理设置模式的标准4步

1,定义一份protocol协议
2,B想做代理,必须先遵守并实现上面那份协议
3,A里面定义一个成员 id  delegate
4,将B的实例对象,赋值给A的成员变量delegate

单例设计模式

顾名思义,单例,即是在整个项目中,这个类的对象只能被初始化一次。它的这种特性,可以广泛应用于某些需要全局共享的资源中,比如管理类,引擎类,也可以通过单例来实现传值。如UIApplication;

单例的创建方式:
1,懒汉式:

static id _instance;
+(id)allocWithZone:(struct _NSZone *)zone
// alloc方法内部会调用这个方法
{
    if (_instance == nil) { // 防止频繁加锁
        @synchronized(self) {
            if (_instance == nil) { // 防止创建多次
                _instance = [super allocWithZone:zone];
            }
        }
    }
    return _instance;
}
+(instancetype)sharedMusicTool
{
    if (_instance == nil) { // 防止频繁加锁
        @synchronized(self) {
            if (_instance == nil) { // 防止创建多次
                _instance = [[self alloc] init];
            }
        }
    }
    return _instance;
}
- (id)copyWithZone:(NSZone *)zone
{
    return _instance;
}

2,饿汉式

static id _instance;

/**
 *  当类加载到OC运行时环境中(内存),就会调用一次(一个类只会加载1次)
 */
+ (void)load
{
    _instance = [[self alloc] init];
}

+ (id)allocWithZone:(struct _NSZone *)zone
{
    if (_instance == nil) { // 防止创建多次
        _instance = [super allocWithZone:zone];
    }
    return _instance;
}

+ (instancetype)sharedSoundTool
{
    return _instance;
}

- (id)copyWithZone:(NSZone *)zone
{
    return _instance;
}

3,GCD

// 用来保存唯一的单例对象
static id _instace;

+ (id)allocWithZone:(struct _NSZone *)zone
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instace = [super allocWithZone:zone];
    });
    return _instace;
}

+ (instancetype)sharedDataTool
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instace = [[self alloc] init];
    });
    return _instace;
}

- (id)copyWithZone:(NSZone *)zone
{
    return _instace;
}

iPad在横屏的时候tableviewcell两边会有多余的空白处怎么处理?

在iOS9会后添加cellLayoutMarginsFollowReadableWidth这个属性,通过设置这个属性可以将两端的空白去掉;但是在iOS9以下的版本就不能使用这个属性了(使用的话会闪退),要想修改的话只能通过自定义,所有的控件都通过自定义来实现(包括cell上的横线),如果要时候系统的cell话,就必须重写cell的布局方法,将cell内部的控件重新布局,这个才能实现两端空白消除;在- (void)layoutSubviews;这个方法中进行重新布局!然后分割线的话就通过这个方法来让分割线达到整个屏幕的两端显示

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
    if ([cell respondsToSelector:@selector(setLayoutMargins:)]) {
        [cell setLayoutMargins:UIEdgeInsetsZero];
    }
    if ([cell respondsToSelector:@selector(setSeparatorInset:)]){
        [cell setSeparatorInset:UIEdgeInsetsMake(0, -1000, 0, -1000)];
        //注意这里要使用-1000才能让分割线达到整个屏幕的两端来显示;
    }
}

你可能感兴趣的:(iOS 开发基础(5)--设计模式)