架构模式-②MVC与MVVM

## 一、框架

二、MVC,MVP 和 MVVM 的图示

MVC,MVP 和 MVVM 的图示

MVC(Model-View-Controller)既可以被视为一种复合设计模式(由多个基本设计模式组合而成),也可以被看作一种架构模式(Architectural Pattern),具体取决于讨论的上下文和应用的规模。

作为复合设计模式

  • MVC结合了多种基本设计模式:
    • 观察者模式(Observer):View观察Model的变化
    • 策略模式(Strategy):Controller是可替换的行为策略
    • 组合模式(Composite):用于构建View的层次结构
  • 在这个层面上,MVC解决的是UI层的组织问题,关注的是代码层面的职责分离

(三)MVC模式

应用场景:MVC模式是一种非常古老的设计模式,它通过数据模型,控制器逻辑,视图展示将应用程序进行逻辑划分。

实例:model-即数据模型,view-视图展示,controller进行UI展现和数据交互的逻辑控制。

三、MVC与MVVM

1、MVC的开发模式

MVC是模型、视图、控制器开发模式,对于iOS SDK,所有的View都是视图层的,它应该独立于模型层,由视图器来控制。所有的用户数据都是模型层,它应该独立于视图。所有的ViewController都是视图器,由它负责控制视图,访问模型数据。

2、关于MVC 和 MVVM 的区别

MVVM 介绍

1. 传统的 MVC 架构模式

mvc_1

2. MVP 架构模式

mvp_1

用户在view上的操作,会通过vm对model进行数据的修改;

数据的修改引起的属性变化会通知到vm上;

vm根据变化进行view UI的更新;

3. MVVM 架构模式

MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。

img

唯一的区别是,它采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。

MVVM的代码实现

ViewModel设计的了解,请进入项目:《DvlproadDesignPatternCollect》中的 CJViewModelDemo
该项目采用blockdelegateKVORAC四种方式介绍ViewModel的设计。 详情请查看:CJViewModelDemoViewModelDemos 文件夹下的代码

Flutter 中可查看 001-UIKit-CQDemo-Flutter 项目中的 tsdemodemo_flutter 工程里的 architecture 文件目录

该MVVM设计在其他项目中的使用,可见:CJStandardProject 的 LoginViewModel.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//  LoginViewModel.h
#import <Foundation/Foundation.h>
@protocol LoginViewModelDelegate <NSObject>

/// userName 的有效性发生变化
- (void)logic_checkUserNameWithValid:(BOOL)valid;
/// password 的有效性发生变化
- (void)logic_checkPasswordWithValid:(BOOL)valid;
/// 登录按钮 的有效性发生变化
- (void)logic_checkLoginWithValid:(BOOL)valid;

@end



@interface LoginViewModel : NSObject {

}
@property (nonatomic, weak) id<LoginViewModelDelegate> delegate;
@property (nonatomic, copy, readonly) NSString *userName;
@property (nonatomic, copy, readonly) NSString *password;

- (instancetype)initWithUserName:(NSString *)userName password:(NSString *)password;

#pragma mark - Update
- (void)updateUserName:(NSString *)userName;
- (void)updatePassword:(NSString *)password;

#pragma mark - Do
/// 检查是否可登录
- (NSString *)checkLoginCondition;

/// 执行登录
- (void)loginWitLoginSuccess:(void (^)(NSString *successMessage, DemoUser *user))loginSuccess
loginFailure:(void (^)(NSString *errorMessage))loginFailure;

@end

其他MVVM说明示例

设有如下原始Model

1
2
3
4
5
6
7
8
9
10
@interface Person : NSObject

- (instancetype)initwithSalutation:(NSString *)salutation firstName:(NSString *)firstName lastName:(NSString *)lastName birthdate:(NSDate *)birthdate;

@property (nonatomic, readonly) NSString *salutation;
@property (nonatomic, readonly) NSString *firstName;
@property (nonatomic, readonly) NSString *lastName;
@property (nonatomic, readonly) NSDate *birthdate;

@end

则如果仅有此Model的话,我们在PersonViewController中

1
2
3
4
5
6
7
8
9
10
11
12
- (void)viewDidLoad {
[super viewDidLoad];

NSString *nameText = nil;
if (self.personModel.salutation.length > 0) {
nameText = [NSString stringWithFormat:@"%@ %@ %@", self.personModel.salutation, self.personModel.firstName, self.personModel.lastName];
} else {
nameText = [NSString stringWithFormat:@"%@ %@", self.personModel.firstName, self.personModel.lastName];
}

self.nameLabel.text = nameText;
}

而如果我们进而增加一个ViewModel,则如果我们PersonViewController中可以简化成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@implementation PersonViewModel

- (instancetype)initWithPerson:(Person *)person {
self = [super init];
if (!self) return nil;

_person = person;
if (person.salutation.length > 0) {
_nameText = [NSString stringWithFormat:@"%@ %@ %@", self.person.salutation, self.person.firstName, self.person.lastName];
} else {
_nameText = [NSString stringWithFormat:@"%@ %@", self.person.firstName, self.person.lastName];
}

return self;
}

@end

// 此时我们PersonViewController中可以简化成
- (void)viewDidLoad {
[super viewDidLoad];

self.nameLabel.text = self.personViewModel.nameText;
}

END

新项目实施步骤

一、建立项目工程(0.5h)

1、工程配置

1.1、pod处理–Podfile

1
2
3
4
5
pod 'SnapKit'
pod 'Masonry'
pod 'CJBaseUIKit'
pod 'CJFoundation'
pod 'CJBaseHelper'

1.2、混编处理

OC+Swift混编问题.md

①、OC 调用 Swift

1、建立XXX-Swift.h

附:如果你是OC工程中第一次创建swift文件,则系统会在自动帮你搭建XXX-Bridging-Header.h的同时,为你生成该文件。即那种情况下,此步可略过

2、Defines Module 设置为YES (对Project操作,非Targets)

image-20201106181320734

3、oc 文件中 #import “XXX-Swift.h”

②、Swift 调用 OC

XXX-Bridging-Header.h

2、工程入口

2.1、入口首页:方便他人进入开发

二、架构设计

1、架构文件管理

  • AppDelegate

    AppDelegate+StartUp.swift

  • CommonUI

  • CommonUtil

  • Service

  • Module

  • Resources

三、功能模块规划

1、登录模块 Page5 24h

1.1、登录模块

Page 5 24h

  • 登录的首页
    • 隐私政策弹窗页
  • 登录方式选择页面
    • 本机号码获取
    • 第三方登录(微信、)
  • 登录的手机号码输入页
  • 登录的手机验证码输入页

1.2、登录后的信息完善

Page

  • 欢迎页 -> 内容介绍页1 -> 内容介绍页2
  • 个人信息完善
    • 个人性别 + 交往喜好(性别+年龄)

1.3、信息完善后的使用引导

Page3 1d

2、主页模块 Page1 4h

2.1、主页框架 Page1 4h

Page1 4h

3、发现模块

Page 17 = 11 + 6 + 2

h 40 = 28 + 8 + 4

3.1、更好使用的信息完善-表 Page11 28h+

涉及的组件开发 8

  • 完善进度组件 2
  • 照片弹窗组件 6
  • 照片列表组件 3

涉及的页面开发(不含请求)20+

  • 昵称完善页 1

  • 生日完善页(日期选择 + 年龄 + 星座) 4

  • 身高完善页 1

  • 家乡完善页 4

  • 学校完善页

  • 职业完善页(选一选+自定义) 4+2

  • 日常完善页

  • 愿望完善页

  • 照片完善页 1

3.2、更好使用的信息完善-里 Page6 8h

3.3、更好使用的信息完善-匹配偏好 Page2 4h

4、我的模块

4.1、我的首页

4.2、资料信息-表

涉及的组件开发 2

  • 完善进度组件 2
  • 照片弹窗组件 已在上述完成
  • 照片列表组件 已在上述完成

涉及的页面开发(不含请求)

  • 表资料 2*8
  • 照片

RAC:让你不知道怎么死的RAC

前言:RAC学习起来的特点

  • 学习起来比较难
  • 团队开发的时候需要谨慎使用
  • 团队代码需要不断的评审,保证团队中所有人代码的风格一致!避免阅读代码的困难

一、RAC双向绑定UITextField的正确姿势

1、先说结果

1
2
3
4
5
6
7
8
9
// textField1: 键盘修改textField有问题的例子
RACChannelTo(self.viewModel, text1) = RACChannelTo(self.textField1, text);

// textField2: 代码修改textField有问题的例子
RACChannelTo(self.viewModel, text2) = self.textField2.rac_newTextChannel;

// textField3: 键盘和代码修改textField都没问题的例子
RACChannelTo(self.viewModel, text3) = RACChannelTo(self.textField3, text);
[self.textField3.rac_textSignal subscribe:RACChannelTo(self.textField3, text)];

比较结果如下列表所示:

未完整的双向绑定1 未完整的双向绑定2 完整的双向绑定
代码文本 RACBindTextField1 RACBindTextField2 RACBindTextField3
代码截图 // textField1: 键盘修改textField有问题的例子
RACChannelTo(self.viewModel, text1) = RACChannelTo(self.textField1, text);
// textField2: 代码修改textField有问题的例子
RACChannelTo(self.viewModel, text2) = self.textField2.rac_newTextChannel;
// textField3: 键盘和代码修改textField都没问题的例子
RACChannelTo(self.viewModel, text3) = RACChannelTo(self.textField3, text);
[self.textField3.rac_textSignal subscribe:RACChannelTo(self.textField3, text)];
textField textField1: 键盘修改textField有问题的例子 textField2: 代码修改textField有问题的例子 textField3: 键盘和代码修改textField都没问题的例子
(通过代码)改变model时 textField会改变 textField会改变 textField会改变
通过代码改变textField时 model会改变 model不会改变 model会改变
通过键盘改变textField时 model不会改变 model会改变 model会改变

2、分析原因

要弄清为什么通过如上两种方式分别对textField1和textField2进行双向绑定会有问题,那么你需要先认识一下以下两个与textField有关的值的比较

RACChannelTo(self.textField, text) self.textField.rac_newTextChannel
原理 当通过code改变self.textField.text的值的时候,才会把RACChannelTo(self.textField, text)这个值发送出去 当通过键盘改变self.textField.text的值的时候,才会把self.textField.rac_newTextChannel这个值发送出去
后果 所以只使用这个时,键盘修改textField会有问题 所以只使用这个时,代码修改textField会有问题

如果你还存疑惑,那么我们拿textField1: 键盘修改textField有问题的例子来说明:

1
2
// textField1: 键盘修改textField有问题的例子
RACChannelTo(self.viewModel, text1) = RACChannelTo(self.textField1, text);

我们在RACKVOProxy中的observeValueForKeyPath处设置断点,会发现,当我们只设置如上代码时候,通过键盘改变textField的值的时,其并未走入所设断点中,即其此时并未能检测到文本框的文本已经改变了。所以,也就出现了只设置如上代码,会出现当通过键盘改变文本框(键盘未收起)的时候,viewModel中的值没法改变的情况。

RACKVOProxy observeValueForKeyPath

同理,另一个的验证也如此。

3、解决问题

下面我们对RACChannelTo(self.viewModel, text3) = RACChannelTo(self.textField3, text);绑定方式进行键盘修改的完善,完善方式如下:

1
2
3
4
5
6
RACChannelTo(self.viewModel, text3) = RACChannelTo(self.textField3, text);
@weakify(self);
[self.textField3.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
@strongify(self);
self.viewModel.text3 = x;
}];

该部分代码,可简化为:

1
2
RACChannelTo(self.viewModel, text3) = RACChannelTo(self.textField3, text);
[self.textField3.rac_textSignal subscribe:RACChannelTo(self.textField3, text)];

即我们上诉开头时候的正确代码。

4、实际应用中还存在的bindViewModel的问题

4.1、在RAC绑定常见view中的textField的例子RACBindNorTextFieldViewController
1
2
3
4
5
6
7
8
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.title = NSLocalizedString(@"RAC Bind Normal TextField", nil);
[self setupViews];

[self bindViewModel];
}
4.2、在RAC绑定tableView中的textField的例子RACBindTvTextFieldViewController
1
2
3
4
5
6
7
8
9
10
11
12
13
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
RACTextFieldBindTableViewCell *cell = (RACTextFieldBindTableViewCell *)[tableView dequeueReusableCellWithIdentifier:@"RACTextFieldBindTableViewCell" forIndexPath:indexPath];
cell.backgroundColor = [UIColor greenColor];

if (indexPath.row == 0) {
self.textField1 = cell.textField;
} else if (indexPath.row == 1) {
self.textField2 = cell.textField;
} else if (indexPath.row == 2) {
self.textField3 = cell.textField;
}
return cell;
}

那么bindViewModel的时机比较容易出错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.title = NSLocalizedString(@"RAC Bind TableView TextField", nil);

[self setupViews];
//[self bindViewModel]; //textField未获取,无法进行绑定
}

- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
//[self bindViewModel]; //textField未获取,无法进行绑定
}

- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self bindViewModel]; //tableView中的textField绑定的正确时机
}

5、遗留问题

遗留问题1:为什么只有键盘没回收时候的修改文本框值才有问题,而键盘回收时候不会也存在问题?

二、RAC监听的属性如何正确改变值(KVO的坑)

假设有如下需求,根据viewModel中的@property (nonatomic, assign) BOOL textFieldValid;值修改UIViewController中textField的自定义属性leftButtonSelected的值,怎么做???

答:RAC(self.textField, leftButtonSelected) = RACObserve(viewModel, textFieldValid);

问题是:是不是只要实现了这行代码就没什么问题了?或者说在实现这行代码前,你有什么需要注意的?如果你不知道,你不搞清楚原因,那么有一天RAC让你怎么死的,你都不知道。

1、下面将常见的你修改属性时候使用的代码及其效果,列举如下:

修改属性时候使用的代码 代码位置 效果
self.inTextFieldValid1 = inTextFieldValid1; viewModel中 正确
_inTextFieldValid2 = inTextFieldValid2; viewModel中 错误
[self setValue:@(inTextFieldValid3) forKey:@"inTextFieldValid3"]; viewModel中 正确
[self setValue:@(inTextFieldValid4) forKey:@”_inTextFieldValid4”]; viewModel中 错误
self.viewModel.outTextFieldValid1 = outTextFieldValid1; UIViewController中 正确
[self.viewModel setValue:@(outTextFieldValid2) forKey:@"outTextFieldValid2"]; UIViewController中 正确
[self.viewModel setValue:@(outTextFieldValid3) forKey:@”_outTextFieldValid3”]; UIViewController中 错误

上面正确与否的判断标准是什么?

其实如果熟悉KVO机制的你,应该知道KVO的本质是通过isa-swizzling新建了一个子类,并且重写了属性的setter方法,在setter方法的头和尾分别执行了willChangeValueForKey:didChangevlueForKey:两个方法来实现监听的。

所以,如果你修改属性时候使用的代码不会走setter方法,那么也就无法触发监听了。因而也就出现了你明明监听了属性,却无法正确运行的情况。

如上表格中的错误方法皆是不会走setter的。

下面是一张别人的图:

img

2、有无规避方法

问:通过如上解析,我们知道问题的根源是没调用setter,那我们可否通过编译器提示不能使用_xxx来规避???

在这里我们补充讲下@synthesize

写法1 写法2
源代码 @synthesize student; @synthesize student = _student;
等价代码 @synthesize student = student; —-同上—-

synthesize的作用就是让student = ?中的后者这个变量来“代替”属性,从而可以通过操作变量来进行属性的操作。但是有一点最关键的是,使用变量进行操作,属性本身的引用计数是不会增加的,因为没有经过调用setter方法或者是getter方法。但是如果使用self.student这种操作方式的话,实质上是通过setter或者是getter方法进行操作,引用计数会随着不同的操作而改变,了解了这点后就能够更好的避免内存泄露问题。

三、RAC监听数组的变化(KVO的坑)

iOS默认不支持KVO的形式来监听数组的变化,数组改变的时候,只是数组里面的值变化,但数组的地址没有变化,KVO监听的对象地址的变化。

由于不支持KVO来监听数组变化,就无法使用RAC来监听数组。

1、传统方式(我们不需要监听时候常使用的代码):
1
2
3
4
// 只是修改数组,无法触发监听
if (self.flawArray.count) {
[self.flawArray removeLastObject];
}
2、需要使用监听时候的数组修改
1
2
3
4
5
   // 修改数组时候同时能确保触发KVO的操作
if (self.okArray.count) {
NSMutableArray *kvo_okArray = [self mutableArrayValueForKey:@"okArray"];
[kvo_okArray removeLastObject];
}

四、RAC中监听通知的坑

请查看:RAC中监听通知的坑!

五、结束语

暂时到此!感谢查阅!

iOS代码开发规范

iOS 够逼格的注释总结

https://blog.csdn.net/yusirxiaer/article/details/52511059

正确使用NS_DESIGNATED_INITIALIZER

https://blog.csdn.net/zcube/article/details/51657417

NS_REQUIRES_SUPER:表示类的这个方法在子类重写时,必须在方法内部使用super调用父类的这个方法。

代码编写技巧

1
2
3
4
5
self.selectedBackgroundView = ({
UIView *view = [UIView new];
view.backgroundColor = [UIColor colorWithRed:244/255.0 green:244/255.0 blue:244/255.0 alpha:1.0]; //#f4f4f4
view;
});

Swift中$0、$1的实际含义

https://www.jianshu.com/p/b36a0f8108aa

Swift:如何优雅地使用 print()(一)

楼房常识

高楼晃动正常吗?买房可能遇到哪些质量问题

文章来源:https://zhidao.baidu.com/special/view?id=7ca55a24626975510400

专业人士称,超高层建筑来回摇动是正常的,有利于楼体安全,高层建筑大部分都是钢结构,对于侧向压力会起到缓冲作用。高层建筑“打太极”,摇摆而“定”,也是对风压的一种缓冲。高层建筑不会摇摆才是危险的,一般来说越高的建筑,摇摆幅度越大。

在建造高层建筑时,风是首先要考虑的要素之一,高空风力增大的原因是高楼林立的“狭管效应”。由于高楼阻挡,如果风的通行面减小一半的话,其穿行速度则会增加约一倍。遇见高楼狭小的通道,风就更猛烈了。因此,超高层建筑顶部一般都会有位移表现,只是幅度不同。楼层越高,幅度越大。因此住在顶楼的居民感觉晃动是正常的。

编程范式-面向协议(POP)

一、产生背景

1.1 传统面向对象编程(OOP)的弊端/继承的痛点:

继承的痛点:滥用继承的话,后面如果要抽离功能就会牵一发而动全身。即抽离一个功能会导致也要把继承链上涉及的功能都抽出来。显然太麻烦了。

痛点的解决:使用面向协议,将每个单独功能一个协议,复杂功能将协议组合起来即可。如此后续抽离时,抽单独协议和实现类即可(swift中可以将实现写在扩展中,可能不涉及实现类)。

我们将网络库CJNetwork使用面向协议编程后

1、定义协议CQNetworkRequestCompletionClientProtocal.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
NS_ASSUME_NONNULL_BEGIN

@protocol CQNetworkRequestCompletionClientProtocal <NSObject>

@required
/*
* 请求
*
* @param model 请求相关的信息(包含请求方法、请求地址、请求参数等)real\simulate\local
* @param completeBlock 请求结束的回调(为方便接口的重复利用回调中的responseModel使用id类型)
*
* @return 执行请求的任务
*/
- (NSURLSessionDataTask *)requestModel:(__kindof NSObject<CJRequestModelProtocol> *)model
completeBlock:(void (^)(CJResponeFailureType failureType, CJResponseModel *responseModel))completeBlock;
@end

NS_ASSUME_NONNULL_END

2、使用CQNetworkRequestCompletionClientProtocal

1
2
3
4
5
6
7
NS_ASSUME_NONNULL_BEGIN

@interface CJNetworkClient (Completion) <CQNetworkRequestCompletionClientProtocal>

@end

NS_ASSUME_NONNULL_END

1.2 什么是面向协议编程(POP)?

这个问题,我感觉比较明确的定义就是2015年Apple WWDC中说的一句话很直了的解释了:

1
2
Don't start with a class.
Start with a protocol.

即在程序设计中,不要以一个类开始设计,应该从一个协议开始,应抛弃之前OOP的对象设计理念,设计协议,这样不同的继承链之间也可以使用同一个协议。可以将协议看做一个组件,哪里需要哪里继承协议即可,而且协议是可以多继承的,iOS中的类只能单继承,这也是面向协议相对面向对象的一大优势。

1.3 Objective-C 和Swift的面向协议编程区别

OC和swift面向协议编程一个最大区别是OC的 Protocol 没有默认的实现,需要依赖具体的实现类实现协议定义的方法,而Swift2.0开始提供了Protocol + Extension,协议可以再 Extension中提供默认的实现,这样上层调用可以直接调用协议的默认实现。

严谨来说,OC不是一门面向协议编程的语言,因为 Protocol 只提供定义,而不提供实现,所以叫他 面向接口编程 更合适一些。

面向协议和依赖注入

依赖注入要求我们传入的参数遵循协议,这样做的好处是:

  • 我们可以在外部注入一个通用类,只要它遵循协议。
  • 也可以注入一个可测试的模拟类,便于测试。

参考文章:iOS-面向协议编程(POP)

二、替代继承的方式

参考文章:

1、协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 在屏幕中间弹出
@protocol ShowInCenterProtocol <NSObject>
- (void)show;
@end

// 在屏幕底部弹出
@protocol ShowInBottomProtocol <NSObject>
- (void)show;
@end

@class NormalVoicePlayer : NSObject <ShowInCenterProtocol>

@end

2、用组合替代继承

eg1:想要为一个视图做一个show操作。(设想这个show方法后面可能随时会变,比如show的位置,或者动画)

AlertUtil:使用由AlertView/PopupView+PopupAction来组合,而不使用继承AlertView+为其添加show方法。

eg2:常见的举例还有,文本框TextField的根据历史记录填充、根据@邮箱填充。

3、类别category

1
2
3
@interface NSArray (OBJExtras)
- (void)removingFirstObject;
@end

三、使用面向协议为视图添加抖动功能

举例:为UIImageView和UIButton等添加视图的抖动

1、方法1:添加Category方式

如果你写过 Objective-C, 你很可能会把 shake() 写到一个 UIView 的分类(Category) 中(也就是 Swift 中的拓展 (extension)):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//  UIViewExtension.swift

import UIKit

extension UIView {

func shake() {
let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.05
animation.repeatCount = 5
animation.autoreverses = true
animation.fromValue = NSValue(CGPoint: CGPointMake(self.center.x - 4.0, self.center.y))
animation.toValue = NSValue(CGPoint: CGPointMake(self.center.x + 4.0, self.center.y))
layer.addAnimation(animation, forKey: "position")
}
}

2、方法2:面向协议方式

但在swift里,我们除了对UIView添加extension外,其实还有另一种更好的方式:创建面向协议的视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class FoodImageView: UIImageView, Shakeable {
// 其他自定义写在这儿
}

class ActionButton: UIButton, Shakeable {
// 其他自定义写在这儿
}

class ViewController: UIViewController {

@IBOutlet weak var foodImageView: FoodImageView!
@IBOutlet weak var actionButton: ActionButton!

@IBAction func onShakeButtonTap(sender: AnyObject) {
foodImageView.shake()
actionButton.shake()
}
}

附上述面向协议的视图里所对的Shakeable 协议如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//  Shakeable.swift

import UIKit

protocol Shakeable { }

// 你可以只为 UIView 添加 shake 方法!
extension Shakeable where Self: UIView {

// shake 方法的默认实现
func shake() {
let animation = CABasicAnimation(keyPath: "position")
animation.duration = 0.05
animation.repeatCount = 5
animation.autoreverses = true
animation.fromValue = NSValue(CGPoint: CGPointMake(self.center.x - 4.0, self.center.y))
animation.toValue = NSValue(CGPoint: CGPointMake(self.center.x + 4.0, self.center.y))
layer.addAnimation(animation, forKey: "position")
}
}

参考文章:

四、使用面向协议处理reuseIdentifier

tableView的cell注册的基本代码如下

1
2
let foodCellNib = UINib(NibName: "FoodTableViewCell", bundle: nil)
tableView.registerNib(foodCellNib, forCellReuseIdentifier: "FoodTableViewCell")

改进1:FoodTableViewCell字符串改成由类获得

1
2
let foodCellNib = UINib(NibName: String(FoodTableViewCell), bundle: nil)
tableView.registerNib(foodCellNib, forCellReuseIdentifier: String(FoodTableViewCell))

改进2:

1
2
3
4
5
6
7
8
9
10
11
12
13
protocol ReusableView: class {}
// 你可以只为 UIView 添加 ReusableView 方法!
extension ReusableView where Self: UIView {
static var reuseIdentifier: String {
return String(self)
}
}


extension UITableViewCell: ReusableView { }

let foodCellNib = UINib(NibName: “FoodTableViewCell”, bundle: nil)
tableView.registerNib(foodCellNib, forCellReuseIdentifier:FoodTableViewCell.reuseIdentifier)

附:面向对象(OOP)

面向对象的三个特性:封装、继承和多态。

所谓封装,也就是把客观事物封装成抽象的类;

所谓多态就是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。(或者说是不同对象以自己的方式响应相同的消息的能力叫做多态。)

继承的问题:如果架构工程师写父类,业务工程师实现子类。那么业务工程师很可能不清楚:哪些方法需要被覆盖重载,哪些不需要。如果子类没有覆重方法,而父类提供的只是空方法,就很容易出问题。如果子类在覆重的时候引入了其他不相关逻辑,那么子类对象就显得不够单纯,角色复杂了。

多态面临的四个问题:

  • 父类有部分public的方法是不需要,也不允许子类覆重。

    答:公司内部可以规定:不允许覆盖重载父类中的方法子类需要实现接口协议中的方法

  • 父类有一些特别的方法是必须要子类去覆重的,在父类的方法其实是个空方法。

  • 父类有一些方法即便被覆重,父类原方法还是要执行的。

  • 父类有一些方法是可选覆重的,一旦覆重,则以子类为准。

END

瘦身-②AppDelegate瘦身

前言

CJModuleManager:CJUIKitDemo-CJBaseUtil-CJModuleManager

1、AppDelegate瘦身

举个更实际的例子,就是你在为AppDelegate瘦身的时候,将AppDelegate上的方法,分散到各自的Manager(UserManager、PushManager、LocationManager等)的时候,就肯定会需要处理这些dele

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.

[[CJModuleManager sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions]; //即一启动,就会去执行所有添加到ModuleManager中所有的 application:application didFinishLaunchingWithOptions:launchOptions 方法

// 设置主窗口,并设置根控制器
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];

UIViewController *viewController = [[CTMediator sharedInstance] BBXPLogin_loginViewControllerWithLoginSuccessBlock:loginSuccessBlock getUserInfoSuccessBlock:getUserInfoSuccessBlock];

self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:viewController];

[self.window makeKeyAndVisible];

return YES;
}

// 附:上述AppDelegete.m执行的CJModuleManager.m中方法为
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
for (id<CJModule> module in self.modules) {
if ([module respondsToSelector:_cmd]) {
[module application:application didFinishLaunchingWithOptions:launchOptions];
}
}
return YES;
}


END