leverTsui

要有狮子的力量, 菩萨的心肠

0%

main.m中包含的代码如下所示,使用 clang -rewrite-objc 命令将下面的 Objective-C 代码重写成 C++ 代码:

1
2
3
4
5
6
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Hello, World!");
}
return 0;
}

将会得到以下输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);

struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};

int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;

NSLog((NSString *)&__NSConstantStringImpl__var_folders_x__wh4dgr654mx__qc0b9bqyg5w0000gn_T_main_826771_mi_0);
}
return 0;
}

通过声明一个 __AtAutoreleasePool 类型的局部变量 __autoreleasepool 来实现 @autoreleasepool {} 。当声明 __autoreleasepool 变量时,构造函数 __AtAutoreleasePool() 被调用,即执行 atautoreleasepoolobj = objc_autoreleasePoolPush(); ;当出了当前作用域时,析构函数 ~__AtAutoreleasePool() 被调用,即执行 objc_autoreleasePoolPop(atautoreleasepoolobj); 。也就是说 @autoreleasepool {} 的实现代码可以进一步简化如下:

1
2
3
4
5
/* @autoreleasepool */ {
void *atautoreleasepoolobj = objc_autoreleasePoolPush();
// 用户代码,所有接收到 autorelease 消息的对象会被添加到这个 autoreleasepool 中
objc_autoreleasePoolPop(atautoreleasepoolobj);
}

前言

对设计模式的理解,不够透彻,有时去找资料,总是零零散散,不成系统,故将《大话设计模式》中的干货整理下,方便需要使用时可以参考使用。

设计模式六大原则

单一职责原则

  • 定义:就一个类而言,应该仅有一个引起它变化的原因。通俗的说,即一个类只负责一项职责。
    问题由来:类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。
  • 解决方案:遵循单一职责原则。分别建立两个类T1、T2,使T1完成职责P1功能,T2完成职责P2功能。这样,当修改类T1时,不会使职责P2发生故障风险;同理,当修改T2时,也不会使职责P1发生故障风险。
  • 最佳实践:
    • 可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;
    • 提高类的可读性,提高系统的可维护性;
    • 降低需求变更引起的风险。

里氏替换原则

  • 定义:子类型必须能够替换掉它们的父类型。
  • 问题由来:有一功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。
  • 解决方案:当使用继承时,遵循里氏替换原则。类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。

依赖倒转原则

  • 定义:A.高层模块不应该依赖底层模块。两者都应该依赖抽象。B.抽象不应该依赖细节。细节应该依赖抽象。通俗理解:针对接口编程,不要针对实现编程。
  • 问题由来:类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。
  • 解决方案:将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。
  • 最佳实践:
    • 低层模块尽量都要有抽象类或接口,或者抽象类或接口两者都具备。
    • 变量的声明类型尽量是抽象类或接口。
    • 使用继承时遵循里氏替换原则。

接口隔离原则

  • 定义:一个类对另一个类的依赖,应该建立在最小的接口上。
  • 问题由来:类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。
  • 解决方案:将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。
    未遵循接口隔离原则的设计
    遵循接口隔离原则的设计
  • 最佳实践:
    • 接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度;
    • 为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系;
    • 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情;
    • 适度使用,接口设计的过大或过小都不好。

迪米特法则

  • 定义:又叫最少知道原则,如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
    一个对象应该对其他对象保持最少的了解。
  • 问题由来:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
  • 最佳实践:尽量降低类与类之间的耦合。可以通过第三者来进行通信。

开放-封闭原则

  • 定义:软件实体(类、模块、函数等等)应该可以扩展,但是不可修改。
  • 问题由来:在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。
  • 解决方案:当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。

六大原则总结

用抽象构建框架,用实现扩展细节。

  • 单一职责原则告诉我们实现类要职责单一;
  • 里氏替换原则告诉我们不要破坏继承体系;
  • 依赖倒置原则告诉我们要面向接口编程;
  • 接口隔离原则告诉我们在设计接口的时候要精简单一;
  • 迪米特法则告诉我们要降低耦合。
  • 而开闭原则是总纲,它告诉我们要对扩展开放,对修改关闭。

设计模式六大原则

UML基础知识

UML类图图示

类与类之间的关系

  • 泛化:是一种继承关系,表示一般与特殊的关系,存在于父类与子类、父接口与子接口之间,表示子类如何特
    化父类的所有特征和行为,比如动物和鸟之间的关系;
  • 实现:一种类与接口的关系,表示类对接口所有特征和行为的实现,比如大雁与接口飞翔的关系;
  • 组合:是一种强的‘拥有’关系,体现了严格的整体和部分的关系,部分和整体的生命周期一样,比如鸟与翅膀的关系;
  • 聚合:是一种弱的‘拥有’关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分,比如雁群和大雁的关系;
  • 关联:描述了类与类之间的结构化关系,具有方向、名字、角色和多重性等信息,是一种拥有的关
    系,它使一个类知道另一个类的属性和方法,比如企鹅与气候的关系,人与住址的关系;
  • 依赖:是一种使用关系,特定事物的改变有可能会影响到使用该事物的其他事物,表示一个事
    物使用另一个事物时使用依赖关系。大多数情况下,依赖关系体现在某个类的方法使用另一个类的对象作为参数。
    1、将一个类的对象作为另一个类中方法的参数
    2、在一个类的方法中将另一个类的对象作为其局部变量
    3、在一个类的方法中调用另一个类的静态方法。

各种关系的强弱顺序:
泛化 = 实现 > 组合 > 聚合 > 关联 > 依赖

常用的23中设计模式

创建型模式

简单工厂模式

模式动机

考虑一个简单的软件应用场景,一个计算器有不同的按钮(如+、-、*、÷), 这些按钮都源自同一个基类,不过在继承基类后不同的子类修改了部分属性从而使得它们可以完成不同的功能,如果我们希望在使用这些按钮时,不需要知道这些具体按钮类的名字,只需要知道表示该按钮类的一个参数,并提供一个调用方便的方法,把该参数传入方法即可返回一个相应的按钮对象,此时,就可以使用简单工厂模式。

模式定义

简单工厂模式(Simple Factory Pattern):它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。

模式结构

简单工厂模式包含如下角色:

  • HCDCalcuteFactory:工厂角色
    工厂角色负责实现创建所有实例的内部逻辑
  • HCDCalculate:抽象产品角色
    抽象产品角色是所创建的所有对象的父类,负责描述所有实例所共有的公共接口
  • HCDCalculateAdd:具体产品角色
    具体产品角色是创建目标,所有创建的对象都充当这个角色的某个具体类的实例。

简单工厂模式类图

时序图

简单工厂模式时序图

源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//HCDCalculateProtocol.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

typedef NS_ENUM(NSInteger, HCDCalculateType) {
HCDCalculateTypeAdd = 0, //加
HCDCalculateTypeMinus, //减
HCDCalculateTypeMultipy, //乘
HCDCalculateTypeDivide //除
};

@protocol HCDCalculateProtocol <NSObject>

@optional
-(CGFloat)calculate;

@end
1
2
3
4
5
6
7
8
9
10
11
//HCDCalculate.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "HCDCalculateProtocol.h"

@interface HCDCalculate : NSObject <HCDCalculateProtocol>

@property(nonatomic, assign) CGFloat numberA;
@property(nonatomic, assign) CGFloat numberB;

@end
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
39
40
41
42
43
//HCDCalcuteFactory.h
#import <Foundation/Foundation.h>
#import "HCDCalculate.h"

@interface HCDCalcuteFactory : NSObject

+(HCDCalculate *)createCalcute:(NSString *)calculatetype;

@end

//HCDCalcuteFactory.m
#import "HCDCalcuteFactory.h"
#import "HCDCalculateAdd.h"
#import "HCDCalculateDivide.h"
#import "HCDCalculateMinus.h"
#import "HCDCalcuteMultiply.h"

@implementation HCDCalcuteFactory

+(HCDCalculate *)createCalcute:(NSString *)calculatetype {

NSArray *calculateArray = @[@"+",@"-",@"*",@"/"];

if (![calculateArray containsObject:calculatetype]) {
return nil;
}
HCDCalculateType calType = [calculateArray indexOfObject:calculatetype];


switch (calType) {
case HCDCalculateTypeAdd:
return [[HCDCalculateAdd alloc]init];
break;
case HCDCalculateTypeMinus:
return [[HCDCalculateMinus alloc]init];
break;
case HCDCalculateTypeMultipy:
return [[HCDCalcuteMultiply alloc]init];
case HCDCalculateTypeDivide:
return [[HCDCalculateDivide alloc]init];
}
}
@end
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
39
40
41
42
43
44
45
//HCDCalculateAdd.m
#import "HCDCalculateAdd.h"

@implementation HCDCalculateAdd

-(CGFloat)calculate{
return self.numberA + self.numberB;
}
@end

//HCDCalculateMinus.m
#import "HCDCalculateMinus.h"

@implementation HCDCalculateMinus

-(CGFloat)calculate{
return self.numberA - self.numberB;
}

@end

//HCDCalcuteMultiply
#import "HCDCalcuteMultiply.h"

@implementation HCDCalcuteMultiply

-(CGFloat)calculate{
return self.numberA * self.numberB;
}
@end

//HCDCalculateDivide.m
#import "HCDCalculateDivide.h"

@implementation HCDCalculateDivide

- (CGFloat)calculate{
if (self.numberB == 0) {
NSLog(@"dividend is can not be zero!");
return 0;
}
return self.numberA/self.numberB;
}

@end

工厂方法模式

模式动机

现在对该系统进行修改,不再设计一个运算工厂类来统一负责所有产品的创建,而是将具体运算的创建过程交给专门的工厂子类去完成,我们先定义一个抽象的运算工厂类,再定义具体的工厂类来生成加法运算、减法运算、乘法运算等,它们实现在抽象按钮工厂类中定义的方法。这种抽象化的结果使这种结构可以在不修改具体工厂类的情况下引进新的产品,如果出现新的按钮类型,只需要为这种新类型的按钮创建一个具体的工厂类就可以获得该新运算的实例,这一特点无疑使得工厂方法模式具有超越简单工厂模式的优越性,更加符合“开闭原则”。

模式定义

工厂方法模式,它属于类创建型模式。在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。

模式结构

工厂方法模式包含如下角色:

  • HCDCalculate:抽象产品
  • HCDCalculateAdd:具体产品
  • HCDfactory:抽象工厂
  • HCDfactoryAdd:具体工厂

工厂方法模式类图

时序图

工厂方法模式时序图

源码
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//HCDfactory.h

#import <Foundation/Foundation.h>
#import "HCDCalculate.h"

@interface HCDfactory : NSObject

-(HCDCalculate *)createFactory;

@end

//HCDfactory.m
#import "HCDfactory.h"

@implementation HCDfactory

-(HCDCalculate *)createFactory {
return nil;
}

@end

//HCDfactoryAdd.m
@implementation HCDfactoryAdd

-(HCDCalculate *)createFactory {
return [[HCDCalculateAdd alloc]init];
}
@end

//HCDfactoryMinus.m
@implementation HCDfactoryMinus

-(HCDCalculate *)createFactory {
return [[HCDCalculateMinus alloc]init];
}
@end

//HCDfactoryMultiply.m
@implementation HCDfactoryMultiply

-(HCDCalculate *)createFactory {
return [[HCDCalcuteMultiply alloc]init];
}
@end

//HCDfactoryDivide.m
@implementation HCDfactoryDivide

-(HCDCalculate *)createFactory {
return [[HCDCalculateDivide alloc]init];
}
@end
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//HCDCalculate.h
@interface HCDCalculate : NSObject <HCDCalculateProtocol>

@property(nonatomic, assign) CGFloat numberA;
@property(nonatomic, assign) CGFloat numberB;

@end

//HCDCalculateAdd.m
@implementation HCDCalculateAdd

-(CGFloat)calculate{
return self.numberA + self.numberB;
}
@end

//HCDCalculateMinus.m
@implementation HCDCalculateMinus

-(CGFloat)calculate{
return self.numberA - self.numberB;
}

@end

//HCDCalcuteMultiply.m
@implementation HCDCalcuteMultiply

-(CGFloat)calculate{
return self.numberA * self.numberB;
}
@end

//HCDCalculateDivide.m
@implementation HCDCalculateDivide

- (CGFloat)calculate{
if (self.numberB == 0) {
NSLog(@"dividend is can not be zero!");
return 0;
}
return self.numberA/self.numberB;
}

@end

//HCDCalculateProtocol.h
typedef NS_ENUM(NSInteger, HCDCalculateType) {
HCDCalculateTypeAdd = 0, //加
HCDCalculateTypeMinus, //减
HCDCalculateTypeMultipy, //乘
HCDCalculateTypeDivide //除
};

@protocol HCDCalculateProtocol <NSObject>

@optional
-(CGFloat)calculate;

@end
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
//使用示例
HCDfactory *addFactory = [[HCDfactoryAdd alloc]init];
HCDCalculate *addCalculate = [addFactory createFactory];
addCalculate.numberA = 10;
addCalculate.numberB = 15;
NSLog(@"结果是%f\n",[addCalculate calculate]);

HCDfactory *minusFactory = [[HCDfactoryMinus alloc]init];
HCDCalculate *minusCalculate = [minusFactory createFactory];
minusCalculate.numberA = 10;
minusCalculate.numberB = 15;
NSLog(@"结果是%f\n",[minusCalculate calculate]);

HCDfactory *multiplyFactory = [[HCDfactoryMultiply alloc]init];
HCDCalculate *multiplyCalculate = [multiplyFactory createFactory];
multiplyCalculate.numberA = 10;
multiplyCalculate.numberB = 15;
NSLog(@"结果是%f\n",[multiplyCalculate calculate]);

HCDfactory *divideFactory = [[HCDfactoryDivide alloc]init];
HCDCalculate *divideCalculate = [divideFactory createFactory];
divideCalculate.numberA = 10;
divideCalculate.numberB = 15;
NSLog(@"结果是%f\n",[divideCalculate calculate]);

抽象工厂模式

模式动机
  • 在工厂方法模式中具体工厂负责生产具体的产品,每一个具体工厂对应一种具体产品,工厂方法也具有唯一性,一般情况下,一个具体工厂中只有一个工厂-方法或者一组重载的工厂方法。但是有时候我们需要一个工厂可以提供多个产品对象,而不是单一的产品对象。
  • 当系统所提供的工厂所需生产的具体产品并不是一个简单的对象,而是多个位于不同产品等级结构中属于不同类型的具体产品时需要使用抽象工厂模式。
模式定义

抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。属于对象创建型模式。

模式结构

抽象工厂模式包含如下角色:

  • HCDFactory:抽象工厂
  • HCDSqlserverFactory:具体工厂
  • HCDDepartment:抽象产品
  • HCDSqlserverDepartment:具体产品

抽象工厂模式类图

时序图

抽象工厂模式时序图

源码
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
//HCDFactory.h
@protocol HCDFactory <NSObject>

-(id<HCDUser>)createUser;
-(id<HCDDepartment>)createDepartment;

@end

//HCDSqlserverFactory.m
@implementation HCDSqlserverFactory

-(id<HCDUser>)createUser{
return [[HCDSqlserverUser alloc]init];
}

-(id<HCDDepartment>)createDepartment{
return [[HCDSqlserverDepartment alloc]init];
}

@end

//HCDAccessFactory.m
-(id<HCDUser>)createUser{
return [[HCDAccessUser alloc]init];
}

-(id<HCDDepartment>)createDepartment{
return [[HCDAccessDepartment alloc]init];
}

@end
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
//HCDDepartment.h
@protocol HCDDepartment <NSObject>

-(void)insertDepartment:(SQLDepartment *)department;

-(SQLDepartment *)getDepartment;

@end

//HCDSqlserverDepartment.m
@implementation HCDSqlserverDepartment

-(SQLDepartment *)getDepartment{
NSLog(@"新建一个Sqlserver的SQLDepartment对象");
return [[SQLDepartment alloc]init];
}

-(void)insertDepartment:(SQLDepartment *)department{
NSLog(@"插入一个Sqlserver的SQLDepartment对象");
}

@end

//HCDAccessDepartment.m
@implementation HCDAccessDepartment

-(SQLDepartment *)getDepartment{
NSLog(@"新建一个Access的SQLDepartment对象");
return [[SQLDepartment alloc]init];
}

-(void)insertDepartment:(SQLDepartment *)department{
NSLog(@"插入一个Access的SQLDepartment对象");
}

@end
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
//HCDUser.h
@protocol HCDUser <NSObject>

-(void)insertUser:(SQLUser *)user;

-(SQLUser *)getUser;

@end

//HCDSqlserverUser.m
@implementation HCDSqlserverUser

-(SQLUser *)getUser{
NSLog(@"新建一个Sqlserver的SQLUser对象");
return [[SQLUser alloc]init];
}

-(void)insertUser:(SQLUser *)user{
NSLog(@"插入一个Sqlserver的SQLUser对象");
}

@end

//HCDAccessUser.m
@implementation HCDAccessUser

-(SQLUser *)getUser{
NSLog(@"新建一个Access的SQLUser对象");
return [[SQLUser alloc]init];
}

-(void)insertUser:(SQLUser *)user{
NSLog(@"插入一个Access的SQLUser对象");
}

@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//使用示例
id<HCDFactory> factory0 = [[HCDSqlserverFactory alloc]init];
id<HCDDepartment> department0 = [factory0 createDepartment];
[department0 insertDepartment:[[SQLDepartment alloc]init]];
[department0 getDepartment];

id<HCDUser> user0 = [factory0 createUser];
[user0 insertUser:[[SQLUser alloc] init]];
[user0 getUser];

id<HCDFactory> factory1 = [[HCDAccessFactory alloc]init];
id<HCDDepartment> department1 = [factory1 createDepartment];
[department1 insertDepartment:[[SQLDepartment alloc]init]];
[department1 getDepartment];

id<HCDUser> user1 = [factory1 createUser];
[user1 insertUser:[[SQLUser alloc] init]];
[user1 getUser];

建造者模式

模式动机

无论是在现实世界中还是在软件系统中,都存在一些复杂的对象,它们拥有多个组成部分,如汽车,它包括车轮、方向盘、发送机等各种部件。而对于大多数用户而言,无须知道这些部件的装配细节,也几乎不会使用单独某个部件,而是使用一辆完整的汽车,可以通过建造者模式对其进行设计与描述,建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。

模式定义

造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

模式结构

建造者模式包含如下角色:

  • HCDPresionBuilder:抽象建造者
  • HCDPersonFatBuilder:具体建造者
  • HCDPersonBuilderDirector:指挥者
  • HCDProduct:产品角色

建造者模式类图

时序图

建造者模式时序图

源码
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
//HCDPersonBuilderDirector.h
@class HCDPresionBuilder;
@interface HCDPersonBuilderDirector : NSObject

- (instancetype)initWithBuilder:(HCDPresionBuilder *)builder;

- (void)buildPerson;

@end

//HCDPersonBuilderDirector.m
@interface HCDPersonBuilderDirector ()

@property (nonatomic, strong) HCDPresionBuilder *builder;

@end

@implementation HCDPersonBuilderDirector

- (instancetype)initWithBuilder:(HCDPresionBuilder *)builder {
self = [super init];
if (self) {
self.builder = builder;
}
return self;
}

- (void)buildPerson {
[self.builder buildHead];
[self.builder buildBody];
[self.builder buildArmLeft];
[self.builder buildArmRight];
[self.builder buildLegLeft];
[self.builder buildLegRight];
[self.builder getResult];
}

@end
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
//HCDPresionBuilderProtocol.h
ypedef NS_ENUM(NSUInteger,HCDBuildOption){
HCDBuildOptionFat = 0, //胖的人
HCDBuildOptionThin //瘦的人
};

@protocol HCDPresionBuilderProtocol <NSObject>

- (void)buildHead;
- (void)buildBody;
- (void)buildArmLeft;
- (void)buildArmRight;
- (void)buildLegLeft;
- (void)buildLegRight;

- (HCDProduct *)getResult;

@end

//HCDPresionBuilder.h
@interface HCDPresionBuilder : NSObject<HCDPresionBuilderProtocol>

@end

//HCDPersonFatBuilder.m
@interface HCDPersonFatBuilder()

@property (nonatomic, strong) HCDProduct *product;

@end

@implementation HCDPersonFatBuilder

-(instancetype)init{
self = [super init];
if (self) {
_product = [[HCDProduct alloc] init];
}
return self;
}

- (void)buildHead {
NSLog(@"建造胖子的头部");
[self.product.header work];
}

- (void)buildBody {
NSLog(@"建造胖子的身体");
[self.product.body work];
}

- (void)buildArmLeft {
NSLog(@"建造胖子的左手");
[self.product.arm work];
}

- (void)buildArmRight {
NSLog(@"建造胖子的右手");
[self.product.arm work];
}

- (void)buildLegLeft {
NSLog(@"建造胖子的左脚");
[self.product.leg work];
}

- (void)buildLegRight {
NSLog(@"建造胖子的右脚");
[self.product.leg work];
}

- (HCDProduct *)getResult {
return self.product;
}

@end

//HCDPersonThinBuilder.m
@interface HCDPersonThinBuilder ()

@property (nonatomic, strong) HCDProduct *product;

@end

@implementation HCDPersonThinBuilder

-(instancetype)init{
self = [super init];
if (self) {
_product = [[HCDProduct alloc] init];
}
return self;
}

- (void)buildHead {
NSLog(@"建造瘦子的头部");
[self.product.header work];
}

- (void)buildBody {
NSLog(@"建造瘦子的身体");
[self.product.body work];
}

- (void)buildArmLeft {
NSLog(@"建造瘦子的左手");
[self.product.arm work];
}

- (void)buildArmRight {
NSLog(@"建造瘦子的右手");
[self.product.arm work];
}

- (void)buildLegLeft {
NSLog(@"建造瘦子的左脚");
[self.product.leg work];
}

- (void)buildLegRight {
NSLog(@"建造瘦子的右脚");
[self.product.leg work];
}

- (HCDProduct *)getResult {
return self.product;
}

@end
1
2
3
4
5
6
7
8
9
10
11
12
//使用示例
- (IBAction)buildFat:(id)sender {
HCDPersonFatBuilder *builder = [[HCDPersonFatBuilder alloc]init];
HCDPersonBuilderDirector *director = [[HCDPersonBuilderDirector alloc] initWithBuilder:builder];
[director buildPerson];
}

- (IBAction)buildThin:(id)sender {
HCDPersonThinBuilder *builder = [[HCDPersonThinBuilder alloc]init];
HCDPersonBuilderDirector *director = [[HCDPersonBuilderDirector alloc] initWithBuilder:builder];
[director buildPerson];
}

单例模式

模式动机

对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。
如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。
一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机

模式定义

单例模式:单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。

模式结构

抽象工厂模式类图

时序图

抽象工厂模式时序图

源码
1
2
3
4
5
6
7
8
+(instancetype)sharedInstance{
static HCDSingleton *singleton = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
singleton = [[HCDSingleton alloc]init];
});
return singleton;
}

原型模式

模式动机
  • 将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝自己来实现创建过程。
  • 通过克隆方法所创建的对象是全新的对象,它们在内存中拥有新的地址,通常对克隆所产生的对象进行修改对原型对象不会造成任何影响,每一个克隆对象都是相互独立的。通过不同的方式修改可以得到一系列相似但不完全相同的对象。
模式定义

使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。原型模式是一种对象创建型模式。
备注:复制分浅复制和深复制

  • 浅复制:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象;
  • 深复制:把引用对象的变量指向复制过的新对象,而不是原有的被引用的对象。

iOS深拷贝和浅拷贝

结构式模式

组合模式

模式动机
  • 对于树形结构,当容器对象的某一个方法被调用时,将遍历整个树形结构,寻找也包含这个方法的成员对象(可以是容器对象,也可以是叶子对象,如子文件夹和文件)并调用执行。(递归调用)
  • 由于容器对象和叶子对象在功能上的区别,在使用这些对象的客户端代码中必须有区别地对待容器对象和叶子对象,而实际上大多数情况下客户端希望一致地处理它们,因为对于这些对象的区别对待将会使得程序非常复杂。
    组合模式描述了如何将容器对象和叶子对象进行递归组合,使得用户在使用时无须对它们进行区分,可以一致地对待容器对象和叶子对象,这就是组合模式的模式动机。
模式定义

将对象组合成树形结构以表示‘部分-整体’的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

模式结构

模板方法模式包含如下角色:

  • Component: 对象声明接口
  • Leaf: 叶节点对象
  • Composite: 枝节点行为

组合模式类图

时序图

源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//使用示例
HCDConcreteCompany *root = [[HCDConcreteCompany alloc] initWithName:@"总公司"];
[root add:[[HCDHRDepartment alloc] initWithName:@"总公司人力资源部"]];
[root add:[[HCDFinanceDepartment alloc] initWithName:@"总公司财务部"]];

HCDConcreteCompany *comp = [[HCDConcreteCompany alloc] initWithName:@"上海华东分公司"];
[comp add:[[HCDHRDepartment alloc] initWithName:@"上海华东分公司人力资源部"]];
[comp add:[[HCDFinanceDepartment alloc] initWithName:@"上海华东分公司财务部"]];
[root add:comp];

HCDConcreteCompany *office = [[HCDConcreteCompany alloc] initWithName:@"杭州办事处"];
[office add:[[HCDHRDepartment alloc] initWithName:@"杭州办事处人力资源部"]];
[office add:[[HCDFinanceDepartment alloc] initWithName:@"杭州办事处财务部"]];
NSLog(@"\n");
[comp add:office];
NSLog(@"结构图:--------------------------");
[root display:1];
NSLog(@"\n");
NSLog(@"职责:---------------------------");
[root lineofDuty];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//HCDCompany.h
@interface HCDCompany : NSObject

@property (nonatomic,copy) NSString *name;

- (instancetype)initWithName:(NSString *)name;

-(void)add:(HCDCompany *)company;

-(void)remove:(HCDCompany *)company;

-(void)display:(NSInteger)depth;

-(void)lineofDuty;

@end
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
39
40
41
42
43
44
45
46
47
48
49
//HCDConcreteCompany.h
@interface HCDConcreteCompany : HCDCompany

@end

//HCDConcreteCompany.m
@interface HCDConcreteCompany ()

@property (nonatomic, strong) NSMutableArray *childList;

@end

@implementation HCDConcreteCompany

- (instancetype)initWithName:(NSString *)name{
self = [super initWithName:name];
if (self) {
_childList = [NSMutableArray array];
}
return self;
}

- (void)add:(HCDCompany *)company{
[self.childList addObject:company];
}

- (void)remove:(HCDCompany *)company{
[self.childList removeObject:company];
}

- (void)display:(NSInteger)depth {
NSString *seperate = @"";
for (NSInteger i = 0; i < depth; i++) {
seperate = [seperate stringByAppendingString:@"-"];
}
NSLog(@"%@%@的子公司",seperate,self.name);
for (HCDCompany * company in self.childList) {
[company display:depth + 2];
}
}

- (void)lineofDuty{
NSLog(@"%@的子公司的职责",self.name);
for (HCDCompany * company in self.childList) {
[company lineofDuty];
}
}

@end
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
//HCDHRDepartment.h
@interface HCDHRDepartment : HCDCompany

@end

//HCDHRDepartment.m
@implementation HCDHRDepartment

-(void)add:(HCDCompany *)company{

}

- (void)display:(NSInteger)depth {
NSString *seperate = @"";
for (NSInteger i = 0; i < depth; i++) {
seperate = [seperate stringByAppendingString:@"-"];
}
NSLog(@"%@%@的HR部门",seperate,self.name);
}

-(void)remove:(HCDCompany *)company{

}

-(void)lineofDuty{
NSLog(@"%@,培训员工",self.name);
}

@end
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
//HCDFinanceDepartment.h
@interface HCDFinanceDepartment : HCDCompany

@end

//HCDFinanceDepartment.m
@implementation HCDFinanceDepartment

-(void)add:(HCDCompany *)company{

}

-(void)remove:(HCDCompany *)company{

}

- (void)display:(NSInteger)depth {
NSString *seperate = @"";
for (NSInteger i = 0; i < depth; i++) {
seperate = [seperate stringByAppendingString:@"-"];
}
NSLog(@"%@%@的财务部门",seperate,self.name);
}

-(void)lineofDuty{
NSLog(@"%@,给员工发钱",self.name);
}

@end

适配器模式

模式动机

适配器提供客户类需要的接口,适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。也就是说:当客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。因此,适配器可以使由于接口不兼容而不能交互的类可以一起工作。这就是适配器模式的模式动机。

模式定义

适配器模式:将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。

模式结构

适配器模式包含如下角色:

  • HCDPlayer:目标抽象类
  • HCDTranslator:适配器类
  • HCDForeignCenter:适配者类

适配器模式类图

时序图

适配器模式时序图

源码
1
2
3
4
5
6
7
8
9
//调用方式
HCDPlayer *forward = [[HCDForwards alloc] initWithName:@"maidi"];
[forward attack];
[forward defense];

HCDForeignCenter *foreignCenter = [[HCDForeignCenter alloc] initWithName:@"姚明"];
HCDPlayer *translator = [[HCDTranslator alloc] initWithForeigncenter:foreignCenter];
[translator attack];
[translator defense];
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
//HCDPlayer.h
@interface HCDPlayer : NSObject

@property (nonatomic,copy) NSString *name;

-(void)attack;

-(void)defense;

-(instancetype)initWithName:(NSString *)name;

@end

//HCDForwards.m
@implementation HCDForwards

-(void)attack{
NSLog(@"前锋%@进攻",self.name);
}

-(void)defense{
NSLog(@"前锋%@防守",self.name);
}

@end

//HCDTranslator.m
@interface HCDTranslator ()

@property(nonatomic, strong) HCDForeignCenter *foreigncenter;

@end

@implementation HCDTranslator

-(instancetype)initWithForeigncenter:(HCDForeignCenter *)foreigncenter {
self = [super init];
if (self) {
_foreigncenter = foreigncenter;
}
return self;
}

-(void)defense{
[self.foreigncenter foreignDefent];
}

-(void)attack{
[self.foreigncenter foreignAttact];
}

@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//HCDForeignCenter.m
@implementation HCDForeignCenter

- (instancetype)initWithName:(NSString *)name{
self = [super init];
if (self) {
_name = name;
}
return self;
}

- (void)foreignAttact{
NSLog(@"外籍中锋%@进攻",self.name);
}

- (void)foreignDefent{
NSLog(@"外籍中锋%@防守",self.name);
}

@end

桥接模式

模式动机

如果需要开发一个跨平台视频播放器,可以在不同操作系统平台(如Windows、Linux、Unix等)上播放多种格式的视频文件,常见的视频格式包括MPEG、RMVB、AVI、WMV等。现使用桥接模式设计该播放器。此时至少有如下两种设计方案:

  • 第一种设计方案是为每一种操作系统都提供一套支持各种视频格式的版本;
  • 第二种设计方案是根据实际需要对操作系统和支持的视频格式进行组合。

对于有两个变化维度(即两个变化的原因)的系统,采用方案二来进行设计系统中类的个数更少,且系统扩展更为方便。设计方案二即是桥接模式的应用。桥接模式将继承关系转换为关联关系,从而降低了类与类之间的耦合,减少了代码编写量。
尽量使用组合,而不要使用继承。

模式定义

桥接模式:将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式。

模式结构

桥接模式包含如下角色:

  • Abstraction:抽象类
  • RefinedAbstraction:扩充抽象类
  • Implementor:实现类接口
  • ConcreteImplementor:具体实现类

桥接模式类图

时序图

桥接模式时序图

源码

装饰模式

模式动机

一般有两种方式可以实现给一个类或对象增加行为:

  • 继承机制,使用继承机制是给现有类添加功能的一种有效途径,通过继承一个现有类可以使得子类在拥有自身方法的同时还拥有父类的方法。但是这种方法是静态的,用户不能控制增加行为的方式和时机;
  • 关联机制,即将一个类的对象嵌入另一个对象中,由另一个对象来决定是否调用嵌入对象的行为以便扩展自己的行为,我们称这个嵌入的对象为装饰器(Decorator)。

装饰模式以对客户透明的方式动态地给一个对象附加上更多的责任。装饰模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。这就是装饰模式的模式动机。

模式定义

装饰模式(Decorator Pattern) :动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。它是一种对象结构型模式。

模式结构

装饰模式包含如下角色:

  • Component: 抽象构件
  • ConcreteComponent: 具体构件
  • Decorator: 抽象装饰类
  • ConcreteDecorator: 具体装饰类

装饰模式类图

时序图

装饰模式时序图

源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//使用示例
LTPerson *xc = [[LTPerson alloc] initWithName:@"小菜"];

NSLog(@"\n第一种装扮");
LTSneakers *sneaker = [[LTSneakers alloc] init];
LTBigTrouser *bigTrouser = [[LTBigTrouser alloc] init];
LTTshirts *tshirts = [[LTTshirts alloc] init];

[sneaker decorate:xc];
[bigTrouser decorate:sneaker];
[tshirts decorate:bigTrouser];
[tshirts show];

NSLog(@"\n第二种装扮");
LTLeatherShoes *leatherShoes = [[LTLeatherShoes alloc] init];
LTTie *tie = [[LTTie alloc] init];
LTSuit *suit = [[LTSuit alloc] init];

[leatherShoes decorate:xc];
[tie decorate:leatherShoes];
[suit decorate:tie];
[suit show];
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
//LTPerson.h
@interface LTPerson : NSObject

- (instancetype)initWithName:(NSString *)name;

- (void)show;

@end

//LTPerson.m
@interface LTPerson ()

@property (nonatomic, copy) NSString *name;

@end

@implementation LTPerson

- (instancetype)initWithName:(NSString *)name {
self = [super init];
if (self) {
_name = name;
}
return self;
}

- (void)show {
NSLog(@"装扮的%@",self.name);
}

@end
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
//LTFinery.h
@interface LTFinery : LTPerson

- (void)decorate:(LTPerson *)person;

@end

@interface LTTshirts : LTFinery

@end

@interface LTBigTrouser : LTFinery

@end

@interface LTSneakers : LTFinery

@end

@interface LTLeatherShoes : LTFinery

@end

@interface LTTie : LTFinery

@end

@interface LTSuit : LTFinery

@end
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
//LTFinery.m
@interface LTFinery ()

@property (nonatomic, strong) LTPerson *person;

@end

@implementation LTFinery

- (void)decorate:(LTPerson *)person {
self.person = person;
}

- (void)show {
if(self.person) {
[self.person show];
}
}

@end

@implementation LTTshirts

- (void)show {
NSLog(@"大T恤");
[super show];
}

@end

@implementation LTBigTrouser

- (void)show {
NSLog(@"垮裤");
[super show];
}

@end

@implementation LTSneakers

- (void)show {
NSLog(@"破球鞋");
[super show];
}

@end

@implementation LTLeatherShoes

- (void)show {
NSLog(@"皮鞋");
[super show];
}

@end

@implementation LTTie

- (void)show {
NSLog(@"领带");
[super show];
}

@end

@implementation LTSuit

- (void)show {
NSLog(@"西装");
[super show];
}

@end

外观模式

模式动机

为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层的接口,这个接口使得这一子系统更加容易使用。

模式定义

外观模式(Facade Pattern):外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。它是一种对象结构型模式。

模式结构

外观模式包含如下角色:

  • Facade: 外观角色
  • SubSystem:子系统角色

外观模式类图

时序图

外观模式时序图

源码
1
2
3
4
//使用示例
HCDFound *found = [[HCDFound alloc]init];
[found buyFund];
[found sellFund];
1
2
3
4
//HCDstock.h
@interface HCDstock : NSObject<HCDStockProtocol>

@end
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
@interface HCDFound()

@property (nonatomic, strong) HCDstock1 *stock1;
@property (nonatomic, strong) HCDstock2 *stock2;
@property (nonatomic, strong) HCDstock3 *stock3;

@end

@implementation HCDFound

-(instancetype)init{
self = [super init];
if (self) {
_stock1 = [[HCDstock1 alloc]init];
_stock2 = [[HCDstock2 alloc]init];
_stock3 = [[HCDstock3 alloc]init];
}
return self;
}

-(void)buyFund{
[self.stock1 buy];
[self.stock2 buy];
[self.stock3 buy];
}

-(void)sellFund{
[self.stock1 sell];
[self.stock2 sell];
[self.stock3 sell];
}

享元模式

模式动机

面向对象技术可以很好地解决一些灵活性或可扩展性问题,但在很多情况下需要在系统中增加类和对象的个数。当对象数量太多时,将导致运行代价过高,带来性能下降等问题。

  • 享元模式正是为解决这一类问题而诞生的。享元模式通过共享技术实现相同或相似对象的重用。
  • 在享元模式中可以共享的相同内容称为内部状态(IntrinsicState),而那些需要外部环境来设置的不能共享的内容称为外部状态(Extrinsic State),由于区分了内部状态和外部状态,因此可以通过设置不同的外部状态使得相同的对象可以具有一些不同的特征,而相同的内部状态是可以共享的。
  • 在享元模式中通常会出现工厂模式,需要创建一个享元工厂来负责维护一个享元池(Flyweight Pool)用于存储具有相同内部状态的享元对象。
  • 在享元模式中共享的是享元对象的内部状态,外部状态需要通过环境来设置。在实际使用中,能够共享的内部状态是有限的,因此享元对象一般都设计为较小的对象,它所包含的内部状态较少,这种对象也称为细粒度对象。享元模式的目的就是使用共享技术来实现大量细粒度对象的复用。
模式定义

享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。

模式结构

享元模式包含如下角色:

  • Flyweight: 抽象享元类
  • ConcreteFlyweight: 具体享元类
  • UnsharedConcreteFlyweight: 非共享具体享元类
  • FlyweightFactory: 享元工厂类

享元模式类图

时序图

享元模式时序图

源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//使用示例
HCDWebSiteFactory *facoty = [[HCDWebSiteFactory alloc]init];
HCDWebSiteType fx = [facoty getWebSiteCategory:@"产品展示"];
HCDUser *user = [[HCDUser alloc]init];
user.name = @"小菜";
[fx use:user];

HCDWebSiteType fy = [facoty getWebSiteCategory:@"产品展示"];
HCDUser *user1 = [[HCDUser alloc]init];
user1.name = @"大鸟";
[fy use:user1];

HCDWebSiteType fz = [facoty getWebSiteCategory:@"博客"];
HCDUser *user2 = [[HCDUser alloc]init];
user2.name = @"咪咪";
[fz use:user2];
1
2
3
4
5
6
7
8
9
10
//HCDWebSiteFactory.h
@interface HCDWebSiteFactory : NSObject

@property(nonatomic,strong) NSDictionary *flyweights;

-(id<HCDWebSite> )getWebSiteCategory:(NSString *)webkey;

-(NSInteger)getWebSiteCount;

@end
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
//HCDWebSiteFactory.m
@implementation HCDWebSiteFactory

-(instancetype)init{
self = [super init];
if (self) {
_flyweights = [NSDictionary dictionary];
}
return self;
}

-(id<HCDWebSite> )getWebSiteCategory:(NSString *)webkey{
__block id<HCDWebSite> webset = nil;
[self.flyweights enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if (webkey == key) {
webset = obj;
*stop = YES;
}
}];
if (webset == nil) {
HCDConcreteWebSite *concreteset = [[HCDConcreteWebSite alloc] init];
concreteset.webName = webkey;
webset = concreteset;

NSMutableDictionary *mutabledic = [NSMutableDictionary dictionaryWithDictionary:self.flyweights];
[mutabledic setObject:webset forKey:webkey];
self.flyweights = [NSDictionary dictionaryWithDictionary:mutabledic];
}
return webset;
}

-(NSInteger)getWebSiteCount{
return self.flyweights.count;
}

@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//HCDConcreteWebSite.h
@interface HCDConcreteWebSite : NSObject<HCDWebSite>

@property(nonatomic,strong)NSString *webName;

@end

//HCDConcreteWebSite.m
@implementation HCDConcreteWebSite

-(void)use:(HCDUser *)user{
NSLog(@"网站分类:%@,用户:%@",self.webName,user.name);
}

@end

代理模式

模式动机

在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不能看到的内容和服务或者添加客户需要的额外服务。

模式定义

在某些情况下,一个客户不想或者不能直接引用一个对 象,此时可以通过一个称之为“代理”的第三者来实现 间接引用。代理对象可以在客户端和目标对象之间起到 中介的作用,并且可以通过代理对象去掉客户不能看到 的内容和服务或者添加客户需要的额外服务。

模式结构

代理模式包含如下角色:

  • Subject: 抽象主题角色
  • Proxy: 代理主题角色
  • RealSubject: 真实主题角色

代理模式类图

时序图

代理模式时序图

源码
1
2
3
4
5
6
7
//使用示例
HCDschoolGirl *girl = [[HCDschoolGirl alloc] init];
girl.name = @"哈哈哈哈哈";
HCDproxy *proxy = [[HCDproxy alloc] initWithSchoolGirl:girl];
[proxy giveFlowers];
[proxy giveDolls];
[proxy giveChocolate];
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//HCDschoolGirl.h
@interface HCDschoolGirl : NSObject

@property(nonatomic,strong)NSString *name;

@end

//HCDgiveGift.h
@protocol HCDgiveGift <NSObject>
/// 送洋娃娃
- (void)giveDolls;

/// 送鲜花
- (void)giveFlowers;

/// 送巧克力
- (void)giveChocolate;
@end

//HCDpursuit.m
@interface HCDpursuit ()

@property(nonatomic,strong)HCDschoolGirl *schoolGirl;

@end

@implementation HCDpursuit
-(instancetype)initWithSchoolGirl:(HCDschoolGirl *)schoolGirl{
self = [super init];
if (self) {
_schoolGirl = schoolGirl;
}
return self;
}
-(void)giveChocolate{
NSLog(@"送你巧克力%@",self.schoolGirl.name);
}
-(void)giveDolls{
NSLog(@"送你洋娃娃%@",self.schoolGirl.name);
}
-(void)giveFlowers{
NSLog(@"送你玫瑰花%@",self.schoolGirl.name);
}
@end

//HCDproxy.m
@interface HCDproxy ()

@property (strong, nonatomic) HCDpursuit *pursuit;

@end

@implementation HCDproxy

- (instancetype)initWithSchoolGirl:(HCDschoolGirl *)schoolGirl {
self = [super init];
if (self) {
self.pursuit = [[HCDpursuit alloc] initWithSchoolGirl:schoolGirl];
}
return self;
}

- (void)giveDolls {
[self.pursuit giveDolls];
}

- (void)giveFlowers {
[self.pursuit giveFlowers];
}

- (void)giveChocolate {
[self.pursuit giveChocolate];
}

@end

行为型模式

迭代器模式

模式动机

针对不同的需要,可能还要以不同的方式遍历整个聚合对象,但是我们并不希望在聚合对象的抽象层接口中充斥着各种不同遍历的操作。
怎样遍历一个聚合对象,又不需要了解聚合对象的内部结构,还能够提供多种不同的遍历方式,这就是迭代器模式的模式动机。

模式定义

提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。

模式结构

模板方法模式包含如下角色:

  • Aggregate: 聚集抽象类
  • ConcreteAggregate: 具体聚集类
  • Iterator: 迭代抽象类
  • ConcreteIterator: 具体迭代器类

迭代器模式类图

时序图

源码

命令模式

模式动机

在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计,使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活。

命令模式可以对发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。这就是命令模式的模式动机。

模式定义

命令模式(Command Pattern):将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction)模式。

模式结构

命令模式包含如下角色:

  • Command: 抽象命令类
  • ConcreteCommand: 具体命令类
  • Invoker: 调用者
  • Receiver: 接收者
  • Client:客户类

命令模式类图

时序图

命令模式时序图

源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//使用示例
//waiter用于接收各种类型的order。waiter是请求接收者。
//接收不同customer产生的不同order,并且都存入waiter这个接受者中,type表示不同类型的order。
HCDWaiter *waiter = [[HCDWaiter alloc]init];

//顾客一
HCDCustomr *customer = [[HCDCustomr alloc]init];
HCDOrder *customerOrder1 = [customer pushOrderWithString:@"顾客一要十串羊肉" type:orderTypeMutton];
HCDOrder *customerOrder2 = [customer pushOrderWithString:@"顾客一要十串鸭肉" type:orderTypeDuck];
[waiter addOrder:customerOrder1];
[waiter addOrder:customerOrder2];

//顾客二
HCDCustomr *customer1 = [[HCDCustomr alloc]init];
HCDOrder *customer1Order1 = [customer1 pushOrderWithString:@"顾客二要二十串鸡肉" type:orderTypeChicken];
HCDOrder *customer1Order2 = [customer1 pushOrderWithString:@"顾客二要二十串鸭肉" type:orderTypeDuck];
[waiter addOrder:customer1Order1];
[waiter addOrder:customer1Order2];
[waiter deleteOrder:customer1Order2];

//waiter发送order,背后有一个HCDWorker这个单列作为行为实现者来处理具体的order。命令接收完毕,开始发送命令。
[waiter notifyOrder];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//HCDCustomr.m
@implementation HCDCustomr

-(HCDOrder *)pushOrderWithString:(NSString *)string type:(orderType)type{
HCDOrder *order = nil;
switch (type) {
case orderTypeMutton:
order = [[HCDMuttonOrder alloc]initWithOrderString:string];
break;
case orderTypeChicken:
order = [[HCDChickenOrder alloc]initWithOrderString:string];
break;
case orderTypeDuck:
order = [[HCDDuckOrder alloc]initWithOrderString:string];
break;
}
return order;
}
@end
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
//HCDWaiter.m
@implementation HCDWaiter

-(instancetype)init{
self = [super init];
if (self) {
_orderList = [NSMutableArray array];
}
return self;
}
-(void)addOrder:(HCDOrder *)order{
NSLog(@"添加Order");
[self.orderList addObject:order];
}
-(void)deleteOrder:(HCDOrder *)order{
NSLog(@"取消Order");
[self.orderList removeObject:order];
}
/*
命令接收完毕,开始执行命令
*/
-(void)notifyOrder{
NSLog(@"====开始执行Order===");
for (HCDOrder *order in self.orderList) {
[order executeOrder];
}
}
@end
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
39
//HCDOrder.h
@interface HCDOrder : NSObject

@property(nonatomic,copy)NSString *orderString;

-(instancetype)initWithOrderString:(NSString *)orderString;
//执行命令
-(void)executeOrder;

@end

//HCDMuttonOrder.m
@implementation HCDMuttonOrder

-(void)executeOrder{
NSLog(@"烤羊");
[[HCDWorker sharedWorker] doMuttonWork:self.orderString];
}
@end

//HCDChickenOrder.m
@implementation HCDChickenOrder

-(void)executeOrder{
NSLog(@"烤鸡");
[[HCDWorker sharedWorker] doChickenWork:self.orderString];
}

@end

//HCDDuckOrder.m
@implementation HCDDuckOrder

-(void)executeOrder{
NSLog(@"烤鸭");
[[HCDWorker sharedWorker] doChickenWork:self.orderString];
}

@end
1
2
3
4
5
6
7
8
9
10
11
12
//HCDWorker.h
@interface HCDWorker : NSObject

+(instancetype)sharedWorker;

-(void)doMuttonWork:(NSString *)work;

-(void)doChickenWork:(NSString *)work;

-(void)doDuckWork:(NSString *)work;

@end

中介者模式

模式动机
  • 在用户与用户直接聊天的设计方案中,用户对象之间存在很强的关联性,将导致系统出现如下问题:
  • 系统结构复杂:对象之间存在大量的相互关联和调用,若有一个对象发生变化,则需要跟踪和该对象关联的其他所有对象,并进行适当处理。
  • 对象可重用性差:由于一个对象和其他对象具有很强的关联,若没有其他对象的支持,一个对象很难被另一个系统或模块重用,这些对象表现出来更像一个不可分割的整体,职责较为混乱。
  • 系统扩展性低:增加一个新的对象需要在原有相关对象上增加引用,增加新的引用关系也需要调整原有对象,系统耦合度很高,对象操作很不灵活,扩展性差。
  • 在面向对象的软件设计与开发过程中,根据“单一职责原则”,我们应该尽量将对象细化,使其只负责或呈现单一的职责。
  • 对于一个模块,可能由很多对象构成,而且这些对象之间可能存在相互的引用,为了减少对象两两之间复杂的引用关系,使之成为一个松耦合的系统,我们需要使用中介者模式,这就是中介者模式的模式动机。
模式定义

中介者模式(Mediator Pattern)定义:用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。它是一种对象行为型模式。

模式结构

中介者模式包含如下角色:

  • Mediator: 抽象中介者
  • ConcreteMediator: 具体中介者
  • Colleague: 抽象同事类
  • ConcreteColleague: 具体同事类

中介者模式类图

时序图

中介者模式时序图

源码
1
2
3
4
5
6
7
8
9
10
11
12
13
//使用示例
ConcreteMediator *mediator = [[ConcreteMediator alloc] init];

//初始化并且让两个同事有相同的中介者对象
ConcreteColleague1 *c1 = [[ConcreteColleague1 alloc] initWithMediator:mediator];
ConcreteColleague2 *c2 = [[ConcreteColleague2 alloc] initWithMediator:mediator];

//给中介者对象绑定两个要交互的同事对象
mediator.colleague1 = c1;
mediator.colleague2 = c2;

[c1 send:@"吃过饭了吗?"];
[c2 send:@"没有呢,你打算请客?"];
1
2
3
4
5
6
7
8
9
10
//Mediator.h
@class Colleague;
@interface Mediator : NSObject

@property (nonatomic, strong) Colleague *colleague1;
@property (nonatomic, strong) Colleague *colleague2;

-(void)send:(NSString *)message colleague:(Colleague *)colleague;

@end
1
2
3
4
5
6
7
8
9
10
11
12
//ConcreteMediator.m
@implementation ConcreteMediator

-(void)send:(NSString *)message colleague:(Colleague *)colleague{
if (colleague == self.colleague1) {
[self.colleague2 notify:message];
}else{
[self.colleague1 notify:message];
}
}

@end
1
2
3
4
5
6
7
8
9
10
11
12
13
//Colleague.h
@class Mediator;
@interface Colleague : NSObject

@property(nonatomic, strong) Mediator *mediator;

-(instancetype)initWithMediator:(Mediator *)mediator;

-(void)notify:(NSString *)message;

-(void)send:(NSString *)message;

@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//ConcreteColleague1.m
@implementation ConcreteColleague1

-(instancetype)initWithMediator:(Mediator *)mediator{
self = [super init];
if (self) {
self.mediator = mediator;
}
return self;
}

-(void)send:(NSString *)message{
NSLog(@"同事1发送了信息");
[self.mediator send:message colleague:self];
}

-(void)notify:(NSString *)message{
NSLog(@"%@%@",@"同事1得到消息:", message);
}

@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//ConcreteColleague2.m
@implementation ConcreteColleague2

-(instancetype)initWithMediator:(Mediator *)mediator{
self = [super init];
if (self) {
self.mediator = mediator;
}
return self;
}

-(void)send:(NSString *)message{
NSLog(@"同事2发送了信息");
[self.mediator send:message colleague:self];
}

-(void)notify:(NSString *)message{
NSLog(@"%@%@",@"同事2得到消息", message);
}

@end

观察者模式

模式动机

建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。在此,发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展,这就是观察者模式的模式动机。

模式定义

观察者模式(Observer Pattern):定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式是一种对象行为型模式。

模式结构

观察者模式包含如下角色:

  • Subject: 目标
  • ConcreteSubject: 具体目标
  • Observer: 观察者
  • ConcreteObserver: 具体观察者

时序图

源码
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
39
//使用示例
- (void)viewDidLoad {
[super viewDidLoad];

HCDServiceCenter *serviceCenter = [[HCDServiceCenter alloc] init];

[serviceCenter addDelegate:self.nbaobserver];
[serviceCenter addDelegate:self.stockobserver];
[serviceCenter addDelegate:self.gameObserver];

NSLog(@"秘书通知:老板回来了,大家赶紧撤");
[serviceCenter notifyServiceDelegate:@selector(update)
perform:^(id responder) {
[responder update];
}];
}

#pragma mark - getter & setter

-(HCDNBAObserver *)nbaobserver {
if (!_nbaobserver) {
_nbaobserver = [[HCDNBAObserver alloc] init];
}
return _nbaobserver;
}

-(HCDStockObserver *)stockobserver {
if (!_stockobserver) {
_stockobserver = [[HCDStockObserver alloc] init];
}
return _stockobserver;
}

-(HCDGameObserver *)gameObserver {
if (!_gameObserver) {
_gameObserver = [[HCDGameObserver alloc] init];
}
return _gameObserver;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//HCDServiceCenter.h
@interface HCDServiceCenter : NSObject

@property(nonatomic, strong, readonly) NSHashTable *responders;

- (instancetype)initWithNotifyQueue:(dispatch_queue_t)notifyQueue;

- (void)addDelegate:(id)delegate;

- (void)removeDelegate:(id)delegate;

- (void)notifyServiceDelegate:(SEL)aSelector
perform:(void (^)(id responder))perform;

@end
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
//HCDServiceCenter.m
#define LOCK(...) dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER); \
__VA_ARGS__; \
dispatch_semaphore_signal(_lock);

@interface HCDServiceCenter()

@property(nonatomic, strong) dispatch_queue_t notifyQueue;

@property(nonatomic, strong) NSHashTable *responders;

@end

@implementation HCDServiceCenter {
dispatch_semaphore_t _lock;
}

- (instancetype)init {
self = [super init];
if (self) {
self.responders = [NSHashTable weakObjectsHashTable];
self.notifyQueue = dispatch_get_main_queue();
_lock = dispatch_semaphore_create(1);
}
return self;
}

- (instancetype)initWithNotifyQueue:(dispatch_queue_t)notifyQueue {
self = [super init];
if (self) {
self.responders = [NSHashTable weakObjectsHashTable];
self.notifyQueue = notifyQueue;
}
return self;
}

- (void)addDelegate:(id)delegate {
LOCK([self.responders addObject:delegate]);
}

- (void)removeDelegate:(id)delegate {
LOCK([self.responders removeObject:delegate]);
}

- (void)notifyServiceDelegate:(SEL)aSelector
perform:(void (^)(id responder))perform {
dispatch_async(self.notifyQueue, ^{
NSArray *responders = self.responders.allObjects;
for (id responder in responders) {
if ([responder respondsToSelector:aSelector]) {
@try {
perform(responder);
}
@catch (NSException *exception) {
NSLog(@"catch notifyServiceDelegate exception: %@", exception);
}
}
}
});
}

@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//HCDObserver.h
@protocol HCDObserver <NSObject>

@optional
- (void)update;

@end

//HCDStockObserver.h
@interface HCDStockObserver : NSObject<HCDObserver>

@end

//HCDNBAObserver.h
@interface HCDNBAObserver : NSObject<HCDObserver>

@end

//HCDGameObserver.h
@interface HCDGameObserver : NSObject<HCDObserver>

@end

状态模式

模式动机

在很多情况下,一个对象的行为取决于一个或多个动态变化的属性,这样的属性叫做状态,这样的对象叫做有状态的(stateful)对象,这样的对象状态是从事先定义好的一系列值中取出的。当一个这样的对象与外部事件产生互动时,其内部状态就会改变,从而使得系统的行为也随之发生变化。

状态模式主要解决的是当控制一个对象状态转换的条件表达式过于负责时的情况。把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化。

模式定义

允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。状态模式是一种对象行为型模式。

模式结构

状态模式包含如下角色:

  • Context: 环境类
  • State: 抽象状态类
  • ConcreteState: 具体状态类

状态模式类图

时序图

状态模式类图

源码
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
//使用示例
HCDWork *work = [[HCDWork alloc]init];
work.hour = 9;
[work writeProgram];

work.hour = 10;
[work writeProgram];

work.hour = 12;
[work writeProgram];

work.hour = 13;
[work writeProgram];

work.hour = 14;
[work writeProgram];

work.hour = 17;
[work writeProgram];

work.finished = NO;
[work writeProgram];

work.hour = 19;
[work writeProgram];

work.hour = 22;
[work writeProgram];
1
2
3
4
5
6
7
8
9
10
11
12
//HCDWork.h
@interface HCDWork : NSObject

@property(nonatomic, assign) CGFloat hour;

@property(nonatomic, assign) BOOL finished;

- (void)writeProgram;

- (void)changeState:(HCDState *)state;

@end
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
//HCDWork.m
@interface HCDWork()

@property (nonatomic, strong) HCDState *state;

@end

@implementation HCDWork

- (instancetype)init{
self = [super init];
if (self) {
self.state = [[HCDForenoonState alloc]init];
}
return self;
}

- (void)writeProgram {
[self.state writeProgram:self];
}

- (void)changeState:(HCDState *)state {
self.state = state;
}

@end
1
2
3
4
5
6
7
8
//HCDProtocol.h
@class HCDWork;
@protocol HCDProtocol <NSObject>

@optional
- (void)writeProgram:(HCDWork *)work;

@end
1
2
3
4
//HCDState.h
@interface HCDState : NSObject<HCDProtocol>

@end
1
2
3
4
5
6
7
8
9
10
11
12
13
//HCDForenoonState.m
@implementation HCDForenoonState

-(void)writeProgram:(HCDWork *)work{
if (work.hour < 12) {
NSLog(@"当前时间:{%.f}点,上午工作,精神百倍", work.hour);
}else{
HCDNoonState *noonState = [[HCDNoonState alloc] init];
[work changeState:noonState];
[work writeProgram];
}
}
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
//HCDNoonState.m
@implementation HCDNoonState

-(void)writeProgram:(HCDWork *)work{
if (work.hour < 13) {
NSLog(@"当前时间:{%.f}点,饿了,午饭;犯困,午休", work.hour);
} else {
HCDAfternoonState *afternoonState = [[HCDAfternoonState alloc] init];
[work changeState:afternoonState];
[work writeProgram];
}
}
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
//HCDAfternoonState.m
@implementation HCDAfternoonState

-(void)writeProgram:(HCDWork *)work{
if (work.hour < 17) {
NSLog(@"当前时间:{%.f}点,下午状态还不错,继续努力", work.hour);
} else {
HCDEventState *eventState = [[HCDEventState alloc] init];
[work changeState:eventState];
[work writeProgram];
}
}
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//HCDEventState.m
@implementation HCDEventState

-(void)writeProgram:(HCDWork *)work{
if (work.finished) {
HCDRestState *restState = [[HCDRestState alloc] init];
[work changeState:restState];
[work writeProgram];
} else {
if (work.hour < 21) {
NSLog(@"当前时间:{%.f}点,加班哦,疲累之极", work.hour);
} else {
HCDSleepState *sleepState = [[HCDSleepState alloc] init];
[work changeState:sleepState];
[work writeProgram];
}
}

}

@end
1
2
3
4
5
6
7
//HCDSleepState.m
@implementation HCDSleepState

- (void)writeProgram:(HCDWork *)work {
NSLog(@"当前时间:{%.f}点,不行了,睡着了", work.hour);
}
@end
1
2
3
4
5
6
7
//HCDRestState.m
@implementation HCDRestState

- (void)writeProgram:(HCDWork *)work {
NSLog(@"当前时间:{%.f}点,下班回家了", work.hour);
}
@end

策略模式

模式动机
  • 完成一项任务,往往可以有多种不同的方式,每一种方式称为一个策略,我们可以根据环境或者条件的不同选择不同的策略来完成该项任务。
  • 在软件开发中也常常遇到类似的情况,实现某一个功能有多个途径,此时可以使用一种设计模式来使得系统可以灵活地选择解决途径,也能够方便地增加新的解决途径。
  • 为了解决这些问题,可以定义一些独立的类来封装不同的算法,每一个类封装一个具体的算法,在这里,每一个封装算法的类我们都可以称之为策略(Strategy),为了保证这些策略的一致性,一般会用一个抽象的策略类来做算法的定义,而具体每种算法则对应于一个具体策略类。
模式定义

定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。

模式结构

策略模式包含如下角色:

  • Context: 环境类
  • Strategy: 抽象策略类
  • ConcreteStrategy: 具体策略类

策略模式类图

时序图

策略模式时序图

源码
1
2
3
4
5
6
7
8
9
//使用示例
HCDCashContext *context = [[HCDCashContext alloc] initWithCashType:HCDCashTypeNormal];
NSLog(@"结果是%f",[context getResult:100]);

HCDCashContext *contextReturn = [[HCDCashContext alloc] initWithCashType:HCDCashTypeReturn];
NSLog(@"结果是%f",[contextReturn getResult:100]);

HCDCashContext *contextRobate = [[HCDCashContext alloc] initWithCashType:HCDCashTypeRobate];
NSLog(@"结果是%f",[contextRobate getResult:100]);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//HCDCashContext.h
typedef NS_ENUM(NSInteger, HCDCashType){
HCDCashTypeNormal = 0,
HCDCashTypeRobate,
HCDCashTypeReturn
};

@interface HCDCashContext : NSObject

-(instancetype)initWithCashType:(HCDCashType)type;

-(CGFloat)getResult:(CGFloat)money;

@end
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
39
//HCDCashContext.m
@interface HCDCashContext()

@property (nonatomic, strong) id<HCDCashProtocol> cash;

@end

@implementation HCDCashContext

-(instancetype)initWithCashType:(HCDCashType)type{
self = [super init];
if (self) {
[self cofigureWithCashType:type];
}
return self;
}

- (void)cofigureWithCashType:(HCDCashType)type {
switch (type) {
case HCDCashTypeNormal: {
self.cash = [[HCDCashNormal alloc] init];
}
break;
case HCDCashTypeRobate: {
self.cash = [[HCDCashRobate alloc] initWithMoneyRebate:0.8];
}
break;
case HCDCashTypeReturn: {
self.cash = [[HCDCaseReturn alloc] initWithMoneyReturn:5];
}
break;
}
}

- (CGFloat)getResult:(CGFloat)money {
return [self.cash acceptCash:money];
}

@end
1
2
3
4
5
6
//HCDCashProtocol.h
@protocol HCDCashProtocol <NSObject>

- (CGFloat)acceptCash:(CGFloat)cash;

@end
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
//HCDCaseReturn.h
@interface HCDCaseReturn : NSObject<HCDCashProtocol>

-(instancetype)initWithMoneyReturn:(CGFloat)moneyReturn;

@end

//HCDCaseReturn.m
@interface HCDCaseReturn ()

@property (nonatomic, assign)CGFloat moneyReturn;

@end

@implementation HCDCaseReturn

#pragma mark - life cycle

-(instancetype)initWithMoneyReturn:(CGFloat)moneyReturn{
self = [super init];
if (self) {
_moneyReturn = moneyReturn;
}
return self;
}

#pragma mark - HCDCashProtocol

-(CGFloat)acceptCash:(CGFloat)cash{
return cash - self.moneyReturn;
}

@end
1
2
3
4
5
6
7
8
//HCDCashNormal.m
@implementation HCDCashNormal

-(CGFloat)acceptCash:(CGFloat)cash{
return cash;
}

@end
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
//HCDCashRobate.h
@interface HCDCashRobate : NSObject<HCDCashProtocol>

-(instancetype)initWithMoneyRebate:(CGFloat)moneyRebate;

@end

//HCDCashRobate.m
@interface HCDCashRobate ()

@property (nonatomic, assign) CGFloat moneyRebate;

@end

@implementation HCDCashRobate

-(instancetype)initWithMoneyRebate:(CGFloat)moneyRebate{
self = [super init];
if (self) {
_moneyRebate = moneyRebate;
}
return self;
}

-(CGFloat)acceptCash:(CGFloat)cash{
return self.moneyRebate * cash;
}

@end

模板方法模式

模式动机

模板方法模式是基于继承的代码复用基本技术,模板方法模式的结构和用法也是面向对象设计的核心之一。
在模板方法模式中,可以将相同的代码放在父类中,而将不同的方法实现放在不同的子类中。

模式定义

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

模式结构

模板方法模式包含如下角色:

  • AbstractClass: 抽象类
  • ConcreteClass: 具体子类

策略模式类图

时序图

源码
1
2
3
4
5
6
7
8
//使用示例
HCDtextPaper *paperA = [[HCDtextPaperA alloc]init];
[paperA testQuestion1];
[paperA testQuestion2];

HCDtextPaper *paperB = [[HCDtextPaperB alloc]init];
[paperB testQuestion1];
[paperB testQuestion2];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//HCDtextPaper.h
@interface HCDtextPaper : NSObject

- (void)testQuestion1;

- (void)testQuestion2;

/**
子类需要重写这个方法

@return 结果
*/
- (NSString *)answer1;

/**
子类需要重写这个方法

@return 结果
*/
- (NSString *)answer2;

@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//HCDtextPaper.m
@implementation HCDtextPaper

-(void)testQuestion1 {
NSLog(@"问题:杨过得到,后来给了郭靖,炼成倚天剑、屠龙刀的玄铁可能是[ ]:a.球磨铸铁 b.马口铁 c.高速合金钢 d.碳素纤维");
NSLog(@"答案:%@", [self answer1]);
}

-(NSString *)answer1 {
return @"";
}

-(void)testQuestion2 {
NSLog(@"问题:杨过、程英、陆无双铲除了情花,造成[ ]:a.使这种植物不再害人 b.使一种珍稀物种灭绝 c.破坏了那个生物圈的生态平衡 d.造成该地区沙漠化");
NSLog(@"答案:%@", [self answer2]);
}

-(NSString *)answer2{
return @"";
}

@end
1
2
3
4
5
6
7
8
9
10
11
12
//HCDtextPaperA.m
@implementation HCDtextPaperA

-(NSString *)answer1{
return @"b";
}

-(NSString *)answer2{
return @"c";
}

@end
1
2
3
4
5
6
7
8
9
10
11
12
//HCDtextPaperB.m
@implementation HCDtextPaperB

-(NSString *)answer1{
return @"a";
}

-(NSString *)answer2{
return @"d";
}

@end

职责链模式

模式动机

很多情况下,在一个软件系统中可以处理某个请求的对象不止一个。例如采购审批子系统,主任、副董事长、董事长和董事会都可以处理采购单,他们可以构成一条处理采购单的链式结构,采购单(可以看作是要处理的信息)沿着这条链进行传递,这条链就称为责任链。责任链可以是一条直线、一个环或者一个树形结构,最常见的职责链是直线型,即沿着一条单向的链来传递请求,如下图所示。链上的每一个对象都是请求处理者,责任链模式可以将请求的处理者组织成一条链,并让请求沿着链传递,由链上的处理者对请求进行相应的处理。在此过程中,客户端实际上无须关心请求的处理细节以及请求的传递,只需将请求发送到链上即可,从而实现请求发送者和请求处理者解耦。

模式定义

使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

模式结构

职责链模式包含如下角色:

  • Handler: 处理者抽象类
  • ConcreteAggregate: 具体处理者类

职责链模式类图

时序图

源码
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
//使用示例
HCDCommonManager *jinli = [[HCDCommonManager alloc]initWithName:@"经理"];
HCDMajorManager *zongjian = [[HCDMajorManager alloc]initWithName:@"总监"];
HCDGenaralManager *zongjinli = [[HCDGenaralManager alloc]initWithName:@"总经理"];
jinli.superior = zongjian;
zongjian.superior = zongjinli;

HCDReuquest *request = [[HCDReuquest alloc] init];
request.requestType = @"请假";
request.requestContent = @"小菜请假";
request.number = 1;
[jinli dealRequest:request];

HCDReuquest *request1 = [[HCDReuquest alloc] init];
request1.requestType = @"请假";
request1.requestContent = @"小菜请假";
request1.number = 4;
[jinli dealRequest:request1];

HCDReuquest *request2 = [[HCDReuquest alloc] init];
request2.requestType = @"加薪";
request2.requestContent = @"小菜请求加薪";
request2.number = 500;
[jinli dealRequest:request2];

HCDReuquest *request4 = [[HCDReuquest alloc] init];
request4.requestType = @"加薪";
request4.requestContent = @"小菜请求加薪";
request4.number = 1000;
[jinli dealRequest:request4];
1
2
3
4
5
6
7
8
9
10
11
12
13
//HCDMnager.h
@class HCDReuquest;
@interface HCDMnager : NSObject

@property (nonatomic, copy) NSString *name;

@property (nonatomic, strong) HCDMnager *superior;

- (instancetype)initWithName:(NSString *)name;

- (void)dealRequest:(HCDReuquest *)request;

@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//HCDCommonManager.h
@interface HCDCommonManager : HCDMnager

@end

//HCDCommonManager.m
@implementation HCDCommonManager

- (void)dealRequest:(HCDReuquest *)request{
if ([request.requestType isEqualToString:@"请假"] && request.number <= 2) {
NSLog(@"%@:%@ 数量%ld 被批准",self.name,request.requestType,request.number);
}else{
if (self.superior) {
[self.superior dealRequest:request];
}
}
}

@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//HCDMajorManager.h
@interface HCDMajorManager : HCDMnager

@end

//HCDMajorManager.m
@implementation HCDMajorManager

-(void)dealRequest:(HCDReuquest *)request{
if ([request.requestType isEqualToString:@"请假"] && request.number <= 5) {
NSLog(@"%@:%@ 数量%ld 被批准",self.name,request.requestType,request.number);
}else{
if (self.superior) {
[self.superior dealRequest:request];
}
}
}
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//HCDGenaralManager.h
@interface HCDGenaralManager : HCDMnager

@end

//HCDGenaralManager.m
@implementation HCDGenaralManager

-(void)dealRequest:(HCDReuquest *)request{
if ([request.requestType isEqualToString:@"请假"]) {
NSLog(@"%@:%@ 数量%ld 被批准",self.name,request.requestType,request.number);
} else if ([request.requestType isEqualToString:@"加薪"]){
if (request.number <= 500) {
NSLog(@"%@:%@ 数量%ld 被批准",self.name,request.requestType,request.number);
} else {
NSLog(@"%@:%@ 数量%ld 再说吧",self.name,request.requestType,request.number);
}
} else {

}
}

@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//HCDReuquest.h
@interface HCDReuquest : NSObject

/**
请求类型
*/
@property (nonatomic, copy) NSString *requestType;

/**
请求内容
*/
@property (nonatomic, copy) NSString *requestContent;

/**
请求的数量
*/
@property(nonatomic, assign) NSInteger number;

@end

解释器模式

模式动机

如果在系统中某一特定类型的问题发生的频率很高,此时可以考虑将这些问题的实例表述为一个语言中的句子,因此可以构建一个解释器,该解释器通过解释这些句子来解决这些问题。
解释器模式描述了如何构成一个简单的语言解释器,主要应用在使用面向对象语言开发的编译器中。

模式定义

给定一种语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

模式结构

解释器模式模式包含如下角色:

  • AbstractExpression: 抽象表达式
  • TerminalExpression: 终结符表达式
  • NonterminalExpression: 非终结符表达式
  • Context: 环境类
  • Client: 客户类

解释器模式类图

时序图

源码

访问者模式

模式动机

访问者模式的目的是封装一些施加于某种数据结构元素之上的操作,一旦这些操作需要修改的话,接受这个操作的数据结构可以保持不变。为不同类型的元素提供多种访问操作方式,且可以在不修改原有系统的情况下增加新的操作方式,这就是访问者模式的模式动机。

模式定义

表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

模式结构

访问者模式包含如下角色:

  • Vistor: 抽象访问者。为该对象结构中的ConcreteElement的每一个类声明的一个操作。
  • ConcreteVisitor: 具体访问者。实现Visitor申明的每一个操作,每一个操作实现算法的一部分。
  • Element: 抽象元素。定义一个Accept操作,它以一个访问者为参数。
  • ConcreteElement: 具体元素 。实现Accept操作。
  • ObjectStructure: 对象结构。能够枚举它的元素,可以提供一个高层的接口来允许访问者访问它的元素。

访问者模式类图

时序图

源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//使用示例
HCDObjectStructure *o = [[HCDObjectStructure alloc]init];

//初始化不同的element对象
HCDConcreteElementA *eA = [HCDConcreteElementA new];
HCDConcreteElementB *eB = [HCDConcreteElementB new];
//加入o对象里面,存在一个数据结构o中。
[o attach:eA];
[o attach:eB];

//初始化不同的visitor对象。
HCDConcreteVisitor1 *v1 = [HCDConcreteVisitor1 new];
HCDConcreteVisitor2 *v2 = [HCDConcreteVisitor2 new];
//eA,eB(男人女人)接收到访问者v1(喜)的不同反应。
[o accept:v1];
NSLog(@"================================");
//eA,eB(男人女人)接收到访问者v2(怒)的不同反应。
[o accept:v2];
1
2
3
4
5
6
7
8
9
10
11
12
13
//HCDObjectStructure.h
@class HCDElements;
@class HCDVisitors;

@interface HCDObjectStructure : NSObject

-(void)attach:(HCDElements *)element;

-(void)detach:(HCDElements *)element;

-(void)accept:(HCDVisitors *)visitor;

@end
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
//HCDObjectStructure.m
@interface HCDObjectStructure ()

@property (nonatomic, strong) NSMutableArray *elements;

@end

@implementation HCDObjectStructure

-(instancetype)init{
self = [super init];
if (self) {
_elements = [[NSMutableArray alloc]init];
}
return self;
}

-(void)attach:(HCDElements *)element{
[self.elements addObject:element];
}

-(void)detach:(HCDElements *)element{
[self.elements removeObject:element];
}

-(void)accept:(HCDVisitors *)visitor{
for (HCDElements *e in self.elements) {
[e accept:visitor];
}
}

@end
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
39
40
41
42
43
44
//HCDVisitors.h
@interface HCDVisitors : NSObject

-(void)visitConcreteElementA:(HCDConcreteElementA *)concreteElementA;

-(void)visitConcreteElementB:(HCDConcreteElementB *)concreteElementB;

@end

//HCDConcreteVisitor1.h
@interface HCDConcreteVisitor1 : HCDVisitors

@end

//HCDConcreteVisitor1.m
@implementation HCDConcreteVisitor1

-(void)visitConcreteElementA:(HCDConcreteElementA *)concreteElementA{
NSLog(@"男人接收到喜这个visitor============我要飞");
}

-(void)visitConcreteElementB:(HCDConcreteElementB *)concreteElementB{
NSLog(@"女人接收到喜这个visitor============我要跳");
}

@end

//HCDConcreteVisitor2.h
@interface HCDConcreteVisitor2 : HCDVisitors

@end

//HCDConcreteVisitor2.m
@implementation HCDConcreteVisitor2

-(void)visitConcreteElementA:(HCDConcreteElementA *)concreteElementA{
NSLog(@"男人接收到怒这个visitor============我要叫");
}

-(void)visitConcreteElementB:(HCDConcreteElementB *)concreteElementB{
NSLog(@"女人接收到怒这个visitor============我要苦");
}

@end
1
2
3
4
5
6
7
//HCDElements.h
@class HCDVisitors;
@interface HCDElements : NSObject

-(void)accept:(HCDVisitors *)visitor;

@end
1
2
3
4
5
6
7
8
9
10
11
12
//HCDConcreteElementA.m
@implementation HCDConcreteElementA

-(void)operationA{
return;
}

-(void)accept:(HCDVisitors *)visitor{
[visitor visitConcreteElementA:self];
}

@end
1
2
3
4
5
6
7
8
9
10
11
12
//HCDConcreteElementB.m
@implementation HCDConcreteElementB

-(void)operationB{
return;
}

-(void)accept:(HCDVisitors *)visitor{
[visitor visitConcreteElementB:self];
}

@end

小结

在实际的项目开发过程中,使用某些特定的设计模式能够很好的解决问题。同时,在维护他人编写的代码时,如果对设计模式特别熟悉,遇到时也比较容易定位问题。
源码和demo请点这里
参考的文章链接如下
Graphic Design Patterns

概述

Apps receive and handle events using responder objects. A responder object is any instance of the UIResponder class, and common subclasses include UIViewUIViewController, and UIApplication. Responders receive the raw event data and must either handle the event or forward it to another responder object. When your app receives an event, UIKit automatically directs that event to the most appropriate responder object, known as the first responder.

Unhandled events are passed from responder to responder in the active responder chain, which is the dynamic configuration of your app’s responder objects. Figure 1 shows the responders in an app whose interface contains a label, a text field, a button, and two background views. The diagram also shows how events move from one responder to the next, following the responder chain.

Figure 1.png

If the text field does not handle an event, UIKit sends the event to the text field’s parent UIView object, followed by the root view of the window. From the root view, the responder chain diverts to the owning view controller before directing the event to the window. If the window cannot handle the event, UIKit delivers the event to the UIApplication object, and possibly to the app delegate if that object is an instance of UIResponder and not already part of the responder chain.

基于ResponderChain实现对象交互

我们可以借用responder chain实现了一个自己的事件传递链。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//UIResponder的分类
//.h文件
#import <UIKit/UIKit.h>

@interface UIResponder (Router)

- (void)routerEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo;

@end

//.m文件
#import "UIResponder+Router.h"

@implementation UIResponder (Router)

- (void)routerEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo {
[[self nextResponder] routerEventWithName:eventName userInfo:userInfo];
}

@end
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
//NSObject
//.h文件
#import <Foundation/Foundation.h>

@interface NSObject (Invocation)

- (NSInvocation *)createInvocationWithSelector:(SEL)aSelector;

@end

//.m文件

#import "NSObject+Invocation.h"

@implementation NSObject (Invocation)

- (NSInvocation *)createInvocationWithSelector:(SEL)aSelector {
//1、创建签名对象
NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:aSelector];

//2、判断传入的方法是否存在
if (signature==nil) {
//传入的方法不存在 就抛异常
NSString*info = [NSString stringWithFormat:@"-[%@ %@]:unrecognized selector sent to instance",[self class],NSStringFromSelector(aSelector)];
@throw [[NSException alloc] initWithName:@"方法没有" reason:info userInfo:nil];
return nil;
}
//3、、创建NSInvocation对象
NSInvocation*invocation = [NSInvocation invocationWithMethodSignature:signature];
//4、保存方法所属的对象
invocation.target = self;
invocation.selector = aSelector;
return invocation;
}

@end

在需要响应事件的类中重载routerEventWithName::方法

1
2
3
- (void)routerEventWithName:(NSString *)eventName userInfo:(NSDictionary *)userInfo {
[self.eventProxy handleEvent:eventName userInfo:userInfo];
}

使用EventProxy类来专门处理对应的事件

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//EventProxy.h
#import <Foundation/Foundation.h>

@interface EventProxy : NSObject

- (void)handleEvent:(NSString *)eventName userInfo:(NSDictionary *)userInfo;

@end

//EventProxy.m
#import "EventProxy.h"
#import "ResponderChainDefine.h"
#import "UIResponder+Router.h"
#import "NSObject+Invocation.h"

@interface EventProxy ()


@property (nonatomic, strong) NSDictionary *eventStrategy;

@end

@implementation EventProxy

- (void)handleEvent:(NSString *)eventName userInfo:(NSDictionary *)userInfo {

NSInvocation *invocation = self.eventStrategy[eventName];
[invocation setArgument:&userInfo atIndex:2];
[invocation invoke];
}

- (void)cellLeftButtonClick:(NSDictionary *)userInfo {
NSIndexPath *indexPath = userInfo[@"indexPath"];
NSLog(@"indexPath:section:%ld, row:%ld 左边按钮被点击啦!",indexPath.section, indexPath.row);
}

- (void)cellMiddleButtonClick:(NSDictionary *)userInfo {
NSIndexPath *indexPath = userInfo[@"indexPath"];
NSLog(@"indexPath:section:%ld, row:%ld 中间按钮被点击啦!",indexPath.section, indexPath.row);
}

- (void)cellRightButtonClick:(NSDictionary *)userInfo {
NSIndexPath *indexPath = userInfo[@"indexPath"];
NSLog(@"indexPath:section:%ld, row:%ld 右边按钮被点击啦!",indexPath.section, indexPath.row);
}

#pragma mark - getter & setter
- (NSDictionary <NSString *, NSInvocation *>*)eventStrategy {
if (!_eventStrategy) {
_eventStrategy = @{
kTableViewCellEventTappedLeftButton:[self createInvocationWithSelector:@selector(cellLeftButtonClick:)],
kTableViewCellEventTappedMiddleButton:[self createInvocationWithSelector:@selector(cellMiddleButtonClick:)],
kTableViewCellEventTappedRightButton:[self createInvocationWithSelector:@selector(cellRightButtonClick:)]
};
}
return _eventStrategy;
}

@end

TableViewCell的事件中,调用routerEventWithName:userInfo:方法,就会调用到EventProxy类中的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@implementation TableViewCell

- (IBAction)leftButtonClick:(UIButton *)sender {
[self routerEventWithName:kTableViewCellEventTappedLeftButton userInfo:@{@"indexPath":self.indexPath}];
}

- (IBAction)middelButtonClick:(UIButton *)sender {
[self routerEventWithName:kTableViewCellEventTappedMiddleButton userInfo:@{@"indexPath":self.indexPath}];
}

- (IBAction)rightButtonClick:(UIButton *)sender {
[self routerEventWithName:kTableViewCellEventTappedRightButton userInfo:@{@"indexPath":self.indexPath}];
}

@end

总结

  • 使用这种基于Responder Chain的方式来传递事件,在复杂UI层级的页面中,可以避免无谓的delegate声明。
  • 事件处理的逻辑得到归拢,在这个方法里面下断点就能够管理所有的事件处理。

参考文章

Using Responders and the Responder Chain to Handle Events
一种基于ResponderChain的对象交互方式
responderChainDemo

前言

最近参与了事务流程工具化组件的开发,其中有一个模块需要通过长按移动Table View Cells,来达到调整任务的需求,再次记录下开发过程中的实现思路。完成后的效果如下图所示:

长按移动cell.gif

实现思路

添加手势

首先给 collection view 添加一个 UILongGestureRecognizer,在项目中一般使用懒加载的方式来对对象进行初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (UICollectionView *)collectionView {
if (!_collectionView) {
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:self.flowLayout];

_collectionView.backgroundColor = [UIColor whiteColor];
_collectionView.dataSource = self;
_collectionView.delegate = self;

[_collectionView registerClass:[TLCMainCollectionViewCell class] forCellWithReuseIdentifier:[TLCMainCollectionViewCell identifier]];

_collectionView.showsHorizontalScrollIndicator = NO;
_collectionView.showsVerticalScrollIndicator = NO;
_collectionView.bounces = YES;
_collectionView.decelerationRate = 0;

[_collectionView addGestureRecognizer:self.longPress];
}
return _collectionView;
}
1
2
3
4
5
6
- (UILongPressGestureRecognizer *)longPress {
if (!_longPress) {
_longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressGestureRecognized:)];
}
return _longPress;
}

在用户长按后,触犯长按事件,先获取到当前手势所在的collection view位置,再做后续的处理。

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
- (void)longPressGestureRecognized:(UILongPressGestureRecognizer *)sender {

CGPoint location = [sender locationInView:sender.view];

UIGestureRecognizerState state = sender.state;
switch (state) {
case UIGestureRecognizerStateBegan: {
[self handleLongPressStateBeganWithLocation:location];
}
break;

case UIGestureRecognizerStateChanged: {
}
break;

case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled: {
[self longGestureEndedOrCancelledWithLocation:location];
}
break;

default:
break;
}
}
长按手势状态为开始

主要处理两个方面的事务,一为获取当前长按手势所对应的Table View Cell的镜像,将其添加到 Collection View上。二为一些初始状态的设置,后续在移动后网络请求出错及判断当前手势所处的Table View和上一次是否一致需要使用到。最后调用startPageEdgeScroll 开启定时器。

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
- (void)handleLongPressStateBeganWithLocation:(CGPoint)location {

TLCMainCollectionViewCell *selectedCollectionViewCell = [self currentTouchedCollectionCellWithLocation:location];

NSIndexPath *touchIndexPath = [self longGestureBeganIndexPathForRowAtPoint:location atTableView:selectedCollectionViewCell.tableView];

if (!selectedCollectionViewCell || !touchIndexPath) {
return ;
}
self.selectedCollectionViewCellRow = [self.collectionView indexPathForCell:selectedCollectionViewCell].row;

// 已完成的任务,不支持排序
TLPlanItem *selectedItem = [self.viewModel itemAtIndex:self.selectedCollectionViewCellRow
subItemIndex:touchIndexPath.section];
if (!selectedItem || selectedItem.finish) {
return;
}
selectedItem.isHidden = YES;

self.snapshotView = [self snapshotViewWithTableView:selectedCollectionViewCell.tableView
atIndexPath:touchIndexPath];
[self.collectionView addSubview:self.snapshotView];

self.selectedIndexPath = touchIndexPath;
self.originalSelectedIndexPathSection = touchIndexPath.section;
self.originalCollectionViewCellRow = self.selectedCollectionViewCellRow;
self.previousPoint = CGPointZero;

[self startPageEdgeScroll];
}
长按手势状态为改变

longPressGestureRecognized 方法中,可以发现,长按手势状态改变时,并未做任何的操作,主要原因是如果在此做Table View Cells的移动操作,如果数据超过一屏幕,无法自动将未在屏幕上的数据滚动显示出来。所以在长按手势状态为开始时,如果触摸点在Table View Cell上,开启定时器,来处理长按手势状态为改变时的情况。

1
2
3
4
- (void)startPageEdgeScroll {
self.edgeScrollTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(pageEdgeScrollEvent)];
[self.edgeScrollTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}

在定时器触发的事件中,处理两个方面的事情,移动cell和滚动ScrollView

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)pageEdgeScrollEvent {
[self longGestureChanged:self.longPress];

CGFloat snapshotViewCenterOffsetX = [self touchSnapshotViewCenterOffsetX];

if (fabs(snapshotViewCenterOffsetX) > (TLCMainViewControllerFlowLayoutWidthOffset-20)) {
//横向滚动
[self handleScrollViewHorizontalScroll:self.collectionView viewCenterOffsetX:snapshotViewCenterOffsetX];
} else {
//垂直滚动
[self handleScrollViewVerticalScroll:[self selectedCollectionViewCellTableView]];
}
}

在长按手势触摸点位置改变时,处理对应cell的移除和插入动作。横向滚动和垂直滚动主要是根据不同情况设置对应的 Table ViewCollection View的内容偏移量。可以在文末的链接中查看源码。

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
- (void)longGestureChanged:(UILongPressGestureRecognizer *)sender {

CGPoint currentPoint = [sender locationInView:sender.view];
TLCMainCollectionViewCell *currentCollectionViewCell = [self currentTouchedCollectionCellWithLocation:currentPoint];
if (!currentCollectionViewCell) {
currentCollectionViewCell = [self collectionViewCellAtRow:self.selectedCollectionViewCellRow];
}

TLCMainCollectionViewCell *lasetSelectedCollectionViewCell = [self collectionViewCellAtRow:self.selectedCollectionViewCellRow];

//判断targetTableView是否改变
BOOL isTargetTableViewChanged = NO;
if (self.selectedCollectionViewCellRow != currentCollectionViewCell.indexPath.row) {
isTargetTableViewChanged = YES;
self.selectedCollectionViewCellRow = currentCollectionViewCell.indexPath.row;
}
//获取到需要移动到的目标indexpath
NSIndexPath *targetIndexPath = [self longGestureChangeIndexPathForRowAtPoint:currentPoint
collectionViewCell:currentCollectionViewCell];

NSIndexPath *lastSelectedIndexPath = self.selectedIndexPath;

TLCMainCollectionViewCell *selectedCollectionViewCell = [self collectionViewCellAtRow:self.selectedCollectionViewCellRow];
//判断跟上一次长按手势所处的Table View是否相同,如果相同,移动cell,
//如果不同,删除上一次所定义的cell,插入到当前位置
if (isTargetTableViewChanged) {
if ([[self selectedCollectionViewCellTableView] numberOfSections]>targetIndexPath.section) {
[[self selectedCollectionViewCellTableView] scrollToRowAtIndexPath:targetIndexPath
atScrollPosition:UITableViewScrollPositionNone animated:YES];
}

TLPlanItem *moveItem = [self.viewModel itemAtIndex:lasetSelectedCollectionViewCell.indexPath.row
subItemIndex:lastSelectedIndexPath.section];
[self.viewModel removeObject:moveItem
itemIndex:lasetSelectedCollectionViewCell.indexPath.row];
[self.viewModel insertItem:moveItem
index:self.selectedCollectionViewCellRow
subItemIndex:targetIndexPath.section];

[lasetSelectedCollectionViewCell updateCellWithData:[self planItemsAtIndex:lasetSelectedCollectionViewCell.indexPath.row]];
[lasetSelectedCollectionViewCell.tableView deleteSections:[NSIndexSet indexSetWithIndex:lastSelectedIndexPath.section]
withRowAnimation:UITableViewRowAnimationNone];

[selectedCollectionViewCell updateCellWithData:[self planItemsAtIndex:self.selectedCollectionViewCellRow]];
[selectedCollectionViewCell.tableView insertSections:[NSIndexSet indexSetWithIndex:targetIndexPath.section]
withRowAnimation:UITableViewRowAnimationNone];
} else {
BOOL isSameSection = lastSelectedIndexPath.section == targetIndexPath.section;
UITableViewCell *targetCell = [self tableView:[self selectedCollectionViewCellTableView]
selectedCellAtSection:targetIndexPath.section];
if (isSameSection || !targetCell ) {
[self modifySnapshotViewFrameWithTouchPoint:currentPoint];
return;
}

TLPlanItem *item = [self.viewModel itemAtIndex:self.selectedCollectionViewCellRow
subItemIndex:lastSelectedIndexPath.section];
[self.viewModel removeObject:item
itemIndex:self.selectedCollectionViewCellRow];
[self.viewModel insertItem:item
index:self.selectedCollectionViewCellRow
subItemIndex:targetIndexPath.section];

[selectedCollectionViewCell updateCellWithData:[self planItemsAtIndex:self.selectedCollectionViewCellRow]];
[selectedCollectionViewCell.tableView moveSection:lastSelectedIndexPath.section
toSection:targetIndexPath.section];
}

self.selectedIndexPath = targetIndexPath;
//改变长按cell镜像的位置
[self modifySnapshotViewFrameWithTouchPoint:currentPoint];
}
长按手势状态为取消或结束

取消计时器,设置Collection View的偏移量,让其Collection View Cell位于屏幕的中心,发送网络请求,去调整任务的排序,同时将镜像视图隐藏,并将其所对应的Table View Cell显示出来。

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
- (void)longGestureEndedOrCancelledWithLocation:(CGPoint)location {

[self stopEdgeScrollTimer];

CGPoint contentOffset = [self.flowLayout targetContentOffsetForProposedContentOffset:self.collectionView.contentOffset
withScrollingVelocity:CGPointZero];
[self.collectionView setContentOffset:contentOffset animated:YES];

UITableViewCell *targetCell = [[self selectedCollectionViewCellTableView] cellForRowAtIndexPath:self.selectedIndexPath];

if ([self canAdjustPlanRanking]) {
[self adjustPlanRanking];
}
TLPlanItem *slectedItem = [self.viewModel itemAtIndex:self.selectedCollectionViewCellRow subItemIndex:self.selectedIndexPath.section];
[UIView animateWithDuration:0.25 animations:^{
self.snapshotView.transform = CGAffineTransformIdentity;
self.snapshotView.frame = [self snapshotViewFrameWithCell:targetCell];

} completion:^(BOOL finished) {
targetCell.hidden = NO;
slectedItem.isHidden = NO;
[self.snapshotView removeFromSuperview];
self.snapshotView = nil;
}];
}
数据的处理

在移动和插入Table View Cell时,需要将其所对应的数据做响应的改变,数据相关的操作均放在TLCMainViewModel对象中。

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
@interface TLCMainViewModel : NSObject 

/**
今日要做、下一步要做和以后要做
*/
@property (nonatomic, readonly, strong) NSArray <NSString *> *titleArray;

/**
获取计划列表

@param completion TLTodoModel
*/
- (void)obtainTotalPlanListWithTypeCompletion:(TLSDKCompletionBlk)completion;

/**
添加计划

@param requestItem requestItem
@param completion 完成回调
*/
- (void)addPlanWithReq:(TLPlanItemReq *)requestItem
atIndexPath:(NSIndexPath *)indexPath
completion:(TLSDKCompletionBlk)completion;

/**
返回显示的collectionViewCell的个数

@return 数据的个数
*/
- (NSInteger)numberOfItems;

/**
根据type获取对应的数据

@param index 位置
@return 此计划所对应的数据
*/
- (NSMutableArray<TLPlanItem *> *)planItemsAtIndex:(NSInteger)index;

/**
删除某个计划

@param itemIndex 单项数据在数组中的位置,如今日计划中的数据,itemIndex为0
@param subItemIndex 单项数据数组中所在的位置
@param completion 完成回调
*/
- (void)deletePlanAtItemIndex:(NSInteger)itemIndex
subItemIndex:(NSInteger)subItemIndex
completion:(dispatch_block_t)completion;


/**
修改计划状态:完成与非完成

@param itemIndex 单项数据在数组中的位置,如今日计划中的数据,itemIndex为0
@param subItemIndex 单项数据数组中所在的位置
@param completion 完成回调
*/
- (void)modiflyPlanStateAtItemIndex:(NSInteger)itemIndex
subItemIndex:(NSInteger)subItemIndex
completion:(TLSDKCompletionBlk)completion;


/**
修改计划的title和重点标记状态

@param itemIndex 单项数据在数组中的位置,如今日计划中的数据,itemIndex为0
@param subItemIndex 单项数据数组中所在的位置
@param targetItem 目标对象
@param completion 完成回调
*/
- (void)modiflyItemAtIndex:(NSInteger)itemIndex
subItemIndex:(NSInteger)subItemIndex
targetItem:(TLPlanItem *)targetItem
completion:(dispatch_block_t)completion;


/**
移除数据

@param item item
@param itemIndex 单项数据在数组中的位置
*/
- (void)removeObject:(TLPlanItem *)item
itemIndex:(NSInteger)itemIndex;

/**
插入数据

@param item 插入的对象模型
@param itemIndex 单项数据在数组中的位置,如今日计划中的数据,itemIndex为0
@param subItemIndex 单项数据数组中所在的位置
*/
- (void)insertItem:(TLPlanItem *)item
index:(NSInteger)itemIndex
subItemIndex:(NSInteger)subItemIndex;

/**
获取数据

@param itemIndex 一级index
@param subItemIndex 二级index
@return 数据模型
*/
- (TLPlanItem *)itemAtIndex:(NSInteger)itemIndex
subItemIndex:(NSInteger)subItemIndex;

/**
重置数据
*/
- (void)reset;

/**
保存长按开始时的数据
*/
- (void)storePressBeginState;

@end

代码完善

cell未居中显示问题

2018年2月1号
在iPhone系统版本为iOS8.xiOS9.x时,会出现以后要做界面不会回弹的情况。如下图所示:
bug1.png

经排查,是在UICollectionViewFlowLayout类中的

1
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity

方法计算得出的proposedContentOffset有偏差,修改后如下所示:

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
- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity {
CGFloat rawPageValue = self.collectionView.contentOffset.x / [self tlc_pageWidth];
CGFloat currentPage = (velocity.x > 0.0) ? floor(rawPageValue) : ceil(rawPageValue);
CGFloat nextPage = (velocity.x > 0.0) ? ceil(rawPageValue) : floor(rawPageValue);

BOOL pannedLessThanAPage = fabs(1 + currentPage - rawPageValue) > 0.5;
BOOL flicked = fabs(velocity.x) > [self tlc_flickVelocity];
CGFloat actualPage = 0.0;

if (pannedLessThanAPage && flicked) {
proposedContentOffset.x = nextPage * [self tlc_pageWidth];
actualPage = nextPage;
} else {
proposedContentOffset.x = round(rawPageValue) * [self tlc_pageWidth];
actualPage = round(rawPageValue);
}
if (lround(actualPage) >= 1) {
proposedContentOffset.x -= 4.5;
}
//下面为添加的代码
if (lround(actualPage) >= 2) {
proposedContentOffset.x = self.collectionView.contentSize.width - TLCScreenWidth;
}

return proposedContentOffset;
}
在系统版本为iOS9.x时,输入框会上一段距离问题

2018年2月12号
在机型为iPhone SE,系统版本为iOS9.x时,新建计划时,新建窗口会上移一段,如下图所示:
适配问题.png
分析发现,应该是监听键盘高度变化时,输入框的高度计算在特定机型的特定版本上计算错误,将原有的计算frame的来布局的方式改为自动布局。监听键盘高度改变的代码修改后如下:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95

- (void)viewWillAppear:(BOOL)animated{

[super viewWillAppear:animated];
[self addObserverForKeybord];
}

- (void)viewWillDisappear:(BOOL)animated {

[super viewWillDisappear:animated];
[self.view endEditing:YES];
[self removeobserverForKeybord];
}
#pragma mark - keyboard observer

- (void)addObserverForKeybord {

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
}

- (void)removeobserverForKeybord {

[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillShowNotification
object:nil];

[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillHideNotification
object:nil];
}

- (void)keyboardWillShow:(NSNotification *)notification {

CGRect keyboardBounds;
[[notification.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardBounds];
NSNumber *duration = [notification.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey];
NSNumber *curve = [notification.userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey];

keyboardBounds = [self.view convertRect:keyboardBounds toView:nil];

[self.inputProjectView mas_updateConstraints:^(MASConstraintMaker *make) {
make.bottom.equalTo(self.view).offset(-CGRectGetHeight(keyboardBounds));
}];

//设置动画
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:[duration doubleValue]];
[UIView setAnimationCurve:[curve intValue]];

[self.inputProjectView layoutIfNeeded];

[UIView commitAnimations];
}

- (void)keyboardWillHide:(NSNotification *)notification {

if([self.inputProjectView inputText].length > 0) {
[self.inputProjectView resetText];
}

CGRect keyboardBounds;
[[notification.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardBounds];
NSNumber *duration = [notification.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey];
NSNumber *curve = [notification.userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey];

keyboardBounds = [self.view convertRect:keyboardBounds toView:nil];

[self.inputProjectView mas_updateConstraints:^(MASConstraintMaker *make) {
if (@available(iOS 11.0, *)) {
make.bottom.equalTo(self.view).offset(self.view.safeAreaInsets.bottom+88);
} else {
make.bottom.equalTo(self.view).offset(88);
}
make.height.mas_equalTo(88);
}];

//设置动画
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:[duration doubleValue]];
[UIView setAnimationCurve:[curve intValue]];

[self.inputProjectView layoutIfNeeded];

[UIView commitAnimations];
}
切换输入法时,输入框被键盘遮住问题

在修复此问题后,自测时发现,输入法由简体拼音切换为表情符号时,输入框会被键盘挡住,在代码中打断点发现UIKeyboardWillShowNotificationUIKeyboardWillChangeFrameNotification通知均未被触发,同时对比微信发现,切换输入法时,同时开启了自动校正功能,所以参考添加如下代码:

1
_textView.internalTextView.autocorrectionType = UITextAutocorrectionTypeYes;

解决切换输入法时,输入框被键盘遮住的问题。

总结

除了上述Table View Cell移动的操作,在项目中还处理了创建事务和事务详情相关的业务。在整个过程中,比较棘手的还是Table View Cell的移动,在开发过程中,有时数据的移动和Table View Cell的移动未对应上,造成Table View Cell布局错乱,排查了很久。在项目开发过程中,还是需要仔细去分析需求。

文章所对应的Demo请点这里

在工作中,有时会涉及到深拷贝和浅拷贝的内容,发现有些地方理解的不够透彻,所以在网上搜集资料总结一下,主要分四个方面来介绍下iOS中深拷贝和浅拷贝:

  • 对象的拷贝;
  • 集合的拷贝;
  • 如何对集合进行深拷贝?
  • 总结

    对象的拷贝

    对对象进行拷贝,通过调用copymutableCopy方法来实现:
  • 调用 copy方法返回的对象为不可变对象,需要实现NSCopying协议 ;
  • 调用mutableCopy方法返回的对象为可变对象,需要实现NSMutableCopying协议 ;

object copying
上图是苹果文档中关于对象拷贝的实例图,从图中可知:

浅拷贝:object Aobject B及其属性使用同样的内存地址,简单说明的话,可以认为是指针复制;
深拷贝:object Aobject B及其属性使用不同的内存地址,简单说明的话,可以认为是内容复制。

下面通过分析NSStringNSMutableString、和自定义对象DBTestModel,调用copymutableCopy之后,分析其返回的对象及此对象的属性的内存地址来判断其行为是深拷贝还是浅拷贝。

NSString

NSString.png

通过打印的信息可知:

  • 对象string调用copy方法后返回的对象copySting,其所对应的内存地址和对象string一致,即为指针复制;
  • 对象string调用mutableCopy方法后返回的对象mutaCopySting,其所对应的内存地址和对象string不一致,即为指内容复制;
NSMutableString

NSMutableString.png

通过打印的信息可知:

  • 对象mutaString调用copy方法后返回的对象copyMutaString,其所对应的内存地址和对象mutaString不一致,即为内容复制;
  • 对象mutaString调用mutableCopy方法后返回的对象mutaCopyMutaString,其所对应的内存地址和对象mutaString不一致,即为指内容复制;
DBTestModel

下面为自定义的对象’DBTestModel’:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#import <Foundation/Foundation.h>
#import <Mantle/Mantle.h>

@interface DBTestModel : MTLModel

@property (nonatomic, copy) NSString *text;

@property (nonatomic, strong) NSArray *sourceArray;

@property (nonatomic, strong) NSMutableArray *mutableArray;

- (instancetype)initWithText:(NSString *)text
sourceArray:(NSArray *)sourceArray
mutableArray:(NSMutableArray *)mutableArray;
@end

定义了三个属性,textsourceArraymutableArray

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
- (void)testCustomObject {
NSMutableArray *mutableArray = [NSMutableArray array];
DBTestModel *model = [[DBTestModel alloc] initWithText:@"text"
sourceArray:@[@"test1",@"test2"]
mutableArray:mutableArray];
DBTestModel *copyModel = [model copy];
DBTestModel *mutaCopyModel = [model mutableCopy];

NSLog(@"DBTestModel memory address");
NSLog(@"original :%p", model);
NSLog(@"copy :%p", copyModel);
NSLog(@"mutableCopy:%p", mutaCopyModel);
NSLog(@"\n");

NSLog(@"text memory address");
NSLog(@"original :%p", model.text);
NSLog(@"copy :%p", copyModel.text);
NSLog(@"mutableCopy:%p", mutaCopyModel.text);
NSLog(@"\n");

NSLog(@"sourceArray memory address");
NSLog(@"original :%p", model.sourceArray);
NSLog(@"copy :%p", copyModel.sourceArray);
NSLog(@"mutableCopy:%p", mutaCopyModel.sourceArray);
NSLog(@"\n");

NSLog(@"mutableArray memory address");
NSLog(@"original :%p", model.mutableArray);
NSLog(@"copy :%p", copyModel.mutableArray);
NSLog(@"mutableCopy:%p", mutaCopyModel.mutableArray);
NSLog(@"\n");
}

打印结果如下:
image.png
分析其打印数据可知:

  • DBTestModel实例对象中的属性textsourceArray调用copy后,没有产生一个新的对象,为指针复制,其余均为内容复制。

集合的拷贝

image.png

不可变集合NSArray
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
- (void)testCollectiveCopy {
NSMutableArray *mutableArray1 = [NSMutableArray array];
DBTestModel *model1 = [[DBTestModel alloc] initWithText:@"text"
sourceArray:@[@"test1",@"test2"]
mutableArray:mutableArray1];

NSMutableArray *mutableArray2 = [NSMutableArray array];
DBTestModel *model2 = [[DBTestModel alloc] initWithText:@"text"
sourceArray:@[@"test1",@"test2"]
mutableArray:mutableArray2];

NSMutableArray *mutableArray3 = [NSMutableArray array];
DBTestModel *model3 = [[DBTestModel alloc] initWithText:@"text"
sourceArray:@[@"test1",@"test2"]
mutableArray:mutableArray3];
NSArray *array = @[model1,model2,model3];
NSArray *copyArray = [array copy];
NSMutableArray *mutaCopyArray = [array mutableCopy];

NSLog(@"\nNSArray memory address\noriginal :%p\ncopy :%p\nmutableCopy:%p\n",
array,copyArray,mutaCopyArray);

DBTestModel *firstCopyModel = [copyArray firstObject];
DBTestModel *firstMutableCopyModel = [mutaCopyArray firstObject];
NSLog(@"\nDBTestModel memory address\noriginal :%p\ncopy :%p\nmutableCopy:%p\n",
model1,firstCopyModel,firstMutableCopyModel);

NSLog(@"\nDBTestModel sourceArray memory address\noriginal :%p\ncopy :%p\nmutableCopy:%p\n",
model1.sourceArray,firstCopyModel.sourceArray,firstMutableCopyModel.sourceArray);
}

打印结果如下:

NSArray.png

分析其打印数据可知:

  • NSArray实例对象调用mutableCopy方法为内容复制外,其余的均为指针拷贝。
可变集合NSMutableArray

测试代码如下:

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
- (void)testCollectiveMutacopy {
NSMutableArray *mutableArray1 = [NSMutableArray array];
DBTestModel *model1 = [[DBTestModel alloc] initWithText:@"text"
sourceArray:@[@"test1",@"test2"]
mutableArray:mutableArray1];

NSMutableArray *mutableArray2 = [NSMutableArray array];
DBTestModel *model2 = [[DBTestModel alloc] initWithText:@"text"
sourceArray:@[@"test1",@"test2"]
mutableArray:mutableArray2];

NSMutableArray *mutableArray3 = [NSMutableArray array];
DBTestModel *model3 = [[DBTestModel alloc] initWithText:@"text"
sourceArray:@[@"test1",@"test2"]
mutableArray:mutableArray3];

NSArray *array1 = @[model1,model2,model3];
NSMutableArray *mutaArray = [NSMutableArray arrayWithArray:array1];
NSMutableArray *copyMutaArray = [mutaArray copy];
NSMutableArray *mutaCopyMutaArray= [mutaArray mutableCopy];

NSLog(@"\nNSMutableArray memory address\noriginal :%p\ncopy :%p\nmutableCopy:%p\n",
mutaArray,copyMutaArray,mutaCopyMutaArray);

DBTestModel *firstCopyModel = [copyMutaArray firstObject];
DBTestModel *firstMutableCopyModel = [mutaCopyMutaArray firstObject];
NSLog(@"\nDBTestModel memory address\noriginal :%p\ncopy :%p\nmutableCopy:%p\n",
model1,firstCopyModel,firstMutableCopyModel);

NSLog(@"\nDBTestModel sourceArray memory address\noriginal :%p\ncopy :%p\nmutableCopy:%p\n",
model1.sourceArray,firstCopyModel.sourceArray,firstMutableCopyModel.sourceArray);
}

测试结果如下:

NSMutableArray.png

分析其打印数据可知:
NSMutableArray 实例对象调用copymutableCopy方法为内容复制外,数组内容的元素均为指针拷贝。
结合上述测试代码得出的测试数据,得出如下的表格:
image.png

从上图可以看出,NSArrayNSMutableArray对象调用copymutableCopy时,得到的集合中的元素均为指针拷贝,如果想要实现集合对象的深拷贝,应该怎么办呢?

  • 如何对集合进行深拷贝?

  • 集合的单层深复制 (one-level-deep copy)
    可以用 initWithArray:copyItems: 将第二个参数设置为YES即可深复制,如
    1
    NSArray *copyArray = [[NSArray alloc] initWithArray:array copyItems:YES];
    如果你用这种方法深复制,集合里的每个对象都会收到 copyWithZone: 消息。同时集合里的对象需遵循 NSCopying 协议,否则会崩溃。
    image.png

得到的结果如下:

image.png

从打印结果可以看出,拷贝后和拷贝前的数组中的DBTestModel对象的sourceArray的内存地址是相同的,这种拷贝方式只能够提供一层内存拷贝(one-level-deep copy),而非真正的深复制。

  • A true deep copy
    使用归档来实现:
1
2
NSArray *copyArray = [NSKeyedUnarchiver unarchiveObjectWithData:
[NSKeyedArchiver archivedDataWithRootObject:array]];

####小结
在项目中遇到需要需要对对象进行拷贝时,需要理解以下两点:
1、对系统自带的不可变对象进行copy时,为指针复制;
2、对集合类对象进行copymutableCopy时,集合类的元素均为指针复制;
3、如只需对集合进行单层深拷贝,可以使用initWithArray:copyItems:类似的方法,将第二个参数设为YES来实现,如需实现集合的完全复制,可以使用归解档来实现;
4、第三方库 Mantle中的MTLModel类有实现NSCodingNSCopying协议,自定义的类继承MTLModel类即可实现NSCodingNSCopying协议。

参考链接

Object copying
Copying Collections

在项目中经常会使用MBProgressHUD来实现弹窗提醒,所有来分析下MBProgressHUD这个三方库的代码。所分析的源码版本号为1.0.0。


这篇总结主要分三个部分来介绍分析这个框架:

  • 代码结构
  • 方法调用流程图
  • 方法内部实现

代码结构
类图

MBProgressHUD.png

核心API
属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
* 用来推迟HUD的显示,避免HUD显示时间过短,出现一闪而逝的情况,默认值为0。
*/
@property (assign, nonatomic) NSTimeInterval graceTime;

/**
* HUD最短显示时间,单位为s,默认值为0。
*/
@property (assign, nonatomic) NSTimeInterval minShowTime;

/**
* HUD隐藏时,将其从父视图上移除 。默认值为NO
*/
@property (assign, nonatomic) BOOL removeFromSuperViewOnHide;

/**
* HUD显示类型,默认为 MBProgressHUDModeIndeterminate.
*/
@property (assign, nonatomic) MBProgressHUDMode mode;
类方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 创建HUD,添加到提供的视图上并显示
*/
+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated;

/**
* 找到最上层的HUD,并隐藏。
*/
+ (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated;

/**
* 在传入的View上找到最上层的HUD并隐藏此HUD
*/
+ (nullable MBProgressHUD *)HUDForView:(UIView *)view;
实例方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 构造函数,用来初始化HUD
*/
- (instancetype)initWithView:(UIView *)view;

/**
* 显示HUD
*/
- (void)showAnimated:(BOOL)animated;

/**
* 隐藏HUD
*/
- (void)hideAnimated:(BOOL)animated;
方法调用流程图

MBProgressHUD提供的主要接口可以看出,主要有显示HUD和隐藏HUD这两个功能,一步步追溯,得出的方法调用流程图如下:
MBProgressHUD流程图.png

方法内部实现

方法的内部实现主要从两个方面来分析,显示HUD和隐藏HUD。

显示HUD

首先是MBProgressHUD的构造方法

1
2
3
4
5
6
7
8
9
+ (instancetype)showHUDAddedTo:(UIView *)view animated:(BOOL)animated {
//初始化MBProgressHUD
MBProgressHUD *hud = [[self alloc] initWithView:view];
hud.removeFromSuperViewOnHide = YES;
[view addSubview:hud];
[hud showAnimated:animated];
[[UINavigationBar appearance] setBarTintColor:nil];
return hud;
}

首先进入- (id)initWithView:(UIView *)view方法,再进入- (instancetype)initWithFrame:(CGRect)frame方法,最后调用- (void)commonInit方法,进行属性的初始化和添加子视图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (void)commonInit {
// Set default values for properties
_animationType = MBProgressHUDAnimationFade;
_mode = MBProgressHUDModeIndeterminate;
_margin = 20.0f;
_opacity = 1.f;
_defaultMotionEffectsEnabled = YES;

// Default color, depending on the current iOS version
BOOL isLegacy = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iOS_7_0;
_contentColor = isLegacy ? [UIColor whiteColor] : [UIColor colorWithWhite:0.f alpha:0.7f];
// Transparent background
self.opaque = NO;
self.backgroundColor = [UIColor clearColor];
// Make it invisible for now
self.alpha = 0.0f;
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.layer.allowsGroupOpacity = NO;
//添加子视图
[self setupViews];
//更新指示器
[self updateIndicators];
[self registerForNotifications];
}

添加子视图都是常见的方式,让视图跟随陀螺仪运动,这个之前没有接触过,后续需要了解下。

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
- (void)updateBezelMotionEffects {
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 || TARGET_OS_TV
MBBackgroundView *bezelView = self.bezelView;
if (![bezelView respondsToSelector:@selector(addMotionEffect:)]) return;

if (self.defaultMotionEffectsEnabled) {
CGFloat effectOffset = 10.f;
UIInterpolatingMotionEffect *effectX = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
effectX.maximumRelativeValue = @(effectOffset);
effectX.minimumRelativeValue = @(-effectOffset);

UIInterpolatingMotionEffect *effectY = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y" type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
effectY.maximumRelativeValue = @(effectOffset);
effectY.minimumRelativeValue = @(-effectOffset);

UIMotionEffectGroup *group = [[UIMotionEffectGroup alloc] init];
group.motionEffects = @[effectX, effectY];

[bezelView addMotionEffect:group];
} else {
NSArray *effects = [bezelView motionEffects];
for (UIMotionEffect *effect in effects) {
[bezelView removeMotionEffect:effect];
}
}
#endif
}

再主要看下更新指示器的代码。

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
- (void)updateIndicators { 
UIView *indicator = self.indicator;
BOOL isActivityIndicator = [indicator isKindOfClass:[UIActivityIndicatorView class]];
BOOL isRoundIndicator = [indicator isKindOfClass:[MBRoundProgressView class]];

MBProgressHUDMode mode = self.mode;
//菊花动画
if (mode == MBProgressHUDModeIndeterminate) {
if (!isActivityIndicator) {
// Update to indeterminate indicator
[indicator removeFromSuperview];
indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[(UIActivityIndicatorView *)indicator startAnimating];
[self.bezelView addSubview:indicator];
}
}
//水平进度条动画
else if (mode == MBProgressHUDModeDeterminateHorizontalBar) {
// Update to bar determinate indicator
[indicator removeFromSuperview];
indicator = [[MBBarProgressView alloc] init];
[self.bezelView addSubview:indicator];
}
//圆形进度动画
else if (mode == MBProgressHUDModeDeterminate || mode == MBProgressHUDModeAnnularDeterminate) {
if (!isRoundIndicator) {
// Update to determinante indicator
[indicator removeFromSuperview];
indicator = [[MBRoundProgressView alloc] init];
[self.bezelView addSubview:indicator];
}
//环形动画
if (mode == MBProgressHUDModeAnnularDeterminate) {
[(MBRoundProgressView *)indicator setAnnular:YES];
}
}
//自定义动画
else if (mode == MBProgressHUDModeCustomView && self.customView != indicator) {
// Update custom view indicator
[indicator removeFromSuperview];
indicator = self.customView;
[self.bezelView addSubview:indicator];
}
//只显示文本
else if (mode == MBProgressHUDModeText) {
[indicator removeFromSuperview];
indicator = nil;
}
indicator.translatesAutoresizingMaskIntoConstraints = NO;
self.indicator = indicator;

if ([indicator respondsToSelector:@selector(setProgress:)]) {
[(id)indicator setValue:@(self.progress) forKey:@"progress"];
}

[indicator setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisHorizontal];
[indicator setContentCompressionResistancePriority:998.f forAxis:UILayoutConstraintAxisVertical];

[self updateViewsForColor:self.contentColor];
[self setNeedsUpdateConstraints];
}

在这个方法中,主要是根据显示的模式,将不同的indicator视图赋值给indicator属性。更新完指示器后,就是开始将视图显示在界面上。调用的是- (void)showAnimated:(BOOL)animated方法。

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

- (void)showAnimated:(BOOL)animated {
//保证当前线程是主线程
MBMainThreadAssert();
[self.minShowTimer invalidate];
self.useAnimation = animated;
self.finished = NO;
// 如果设置了宽限时间,则推迟HUD的显示
if (self.graceTime > 0.0) {
NSTimer *timer = [NSTimer timerWithTimeInterval:self.graceTime
target:self
selector:@selector(handleGraceTimer:)
userInfo:nil
repeats:NO];
//默认把你的Timer以NSDefaultRunLoopMode添加到MainRunLoop上,而当当前视图在滚动时,当前的MainRunLoop是处于UITrackingRunLoopMode的模式下,在这个模式下,是不会处理NSDefaultRunLoopMode的消息,要想在scrollView滚动的同时Timer也执行的话,我们需要将Timer以NSRunLoopCommonModes的模式注册到当前RunLoop中.
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
self.graceTimer = timer;
}
// ... otherwise show the HUD immediately
else {
[self showUsingAnimation:self.useAnimation];
}
}

- (void)showAnimated:(BOOL)animated方法中,主要做的是判断是否设置了推迟显示HUD的时间,如果设置了,就推迟设置的时间再显示。最后,执行- (void)showUsingAnimation:(BOOL)animated方法。

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

- (void)showUsingAnimation:(BOOL)animated {
// Cancel any previous animations
[self.bezelView.layer removeAllAnimations];
[self.backgroundView.layer removeAllAnimations];

// Cancel any scheduled hideDelayed: calls
[self.hideDelayTimer invalidate];
//记录当前显示的时间,在HUD隐藏时,比较HUD显示到HUD隐藏之间的间隔与最小显示时间,
//如果小于,继续显示,直到显示时间等于最小显示时间,再隐藏HUD
self.showStarted = [NSDate date];
self.alpha = 1.f;

// Needed in case we hide and re-show with the same NSProgress object attached.
//好像是通过这个去刷新进度,这个需要再查下。
[self setNSProgressDisplayLinkEnabled:YES];

if (animated) {
[self animateIn:YES withType:self.animationType completion:NULL];
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
self.bezelView.alpha = self.opacity;
#pragma clang diagnostic pop
self.backgroundView.alpha = 1.f;
}
}

最后执行- (void)animateIn:(BOOL)animatingIn withType:(MBProgressHUDAnimation)type completion:(void(^)(BOOL finished))completion方法,这个方法显示和隐藏均会调用。

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
39
40
41
42
43
//这个方法主要对self.bezelView视图进行动画
- (void)animateIn:(BOOL)animatingIn withType:(MBProgressHUDAnimation)type completion:(void(^)(BOOL finished))completion {
// Automatically determine the correct zoom animation type
if (type == MBProgressHUDAnimationZoom) {
type = animatingIn ? MBProgressHUDAnimationZoomIn : MBProgressHUDAnimationZoomOut;
}

CGAffineTransform small = CGAffineTransformMakeScale(0.5f, 0.5f);
CGAffineTransform large = CGAffineTransformMakeScale(1.5f, 1.5f);

// Set starting state
UIView *bezelView = self.bezelView;
if (animatingIn && bezelView.alpha == 0.f && type == MBProgressHUDAnimationZoomIn) {
bezelView.transform = small;
} else if (animatingIn && bezelView.alpha == 0.f && type == MBProgressHUDAnimationZoomOut) {
bezelView.transform = large;
}

// 使用动画
dispatch_block_t animations = ^{
if (animatingIn) {
bezelView.transform = CGAffineTransformIdentity;
} else if (!animatingIn && type == MBProgressHUDAnimationZoomIn) {
bezelView.transform = large;
} else if (!animatingIn && type == MBProgressHUDAnimationZoomOut) {
bezelView.transform = small;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
bezelView.alpha = animatingIn ? self.opacity : 0.f;
#pragma clang diagnostic pop
self.backgroundView.alpha = animatingIn ? 1.f : 0.f;
};

// Spring animations are nicer, but only available on iOS 7+
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 || TARGET_OS_TV
if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0) {
[UIView animateWithDuration:0.3 delay:0. usingSpringWithDamping:1.f initialSpringVelocity:0.f options:UIViewAnimationOptionBeginFromCurrentState animations:animations completion:completion];
return;
}
#endif
[UIView animateWithDuration:0.3 delay:0. options:UIViewAnimationOptionBeginFromCurrentState animations:animations completion:completion];
}

从代码可以看出,这里只是对指示器的父视图做了放大缩小的动画。

隐藏HUD
1
2
3
4
5
6
7
8
9
10
11

+ (BOOL)hideHUDForView:(UIView *)view animated:(BOOL)animated {
//获取当前显示的hud,如果存在,当前隐藏时,将其从父视图移除
MBProgressHUD *hud = [self HUDForView:view];
if (hud != nil) {
hud.removeFromSuperViewOnHide = YES;
[hud hideAnimated:animated];
return YES;
}
return NO;
}

在这个方法的执行过程中,调用- (void)hideAnimated:(BOOL)animated 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 - (void)hideAnimated:(BOOL)animated {
MBMainThreadAssert();
[self.graceTimer invalidate];
self.useAnimation = animated;
self.finished = YES;
// 如果设置了最小显示时间,计算HUD显示时长,
// 如果HUD显示时长小于最小显示时间,延迟显示
if (self.minShowTime > 0.0 && self.showStarted) {
NSTimeInterval interv = [[NSDate date] timeIntervalSinceDate:self.showStarted];
if (interv < self.minShowTime) {
NSTimer *timer = [NSTimer timerWithTimeInterval:(self.minShowTime - interv) target:self selector:@selector(handleMinShowTimer:) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
self.minShowTimer = timer;
return;
}
}
// ... otherwise hide the HUD immediately
[self hideUsingAnimation:self.useAnimation];
}

- (void)hideAnimated:(BOOL)animated 方法中,主要做的是判断是否需要推迟隐藏HUD,最后调用- (void)hideUsingAnimation:(BOOL)animated方法,

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

- (void)hideUsingAnimation:(BOOL)animated {
//判断是否需要动画效果,如无,则直接隐藏
if (animated && self.showStarted) {
self.showStarted = nil;
//跟显示HUD差不多,只是指示器父视图没有做放大缩小的动画
[self animateIn:NO withType:self.animationType completion:^(BOOL finished) {
[self done];
}];
} else {
self.showStarted = nil;
self.bezelView.alpha = 0.f;
self.backgroundView.alpha = 1.f;
[self done];
}
}

最后,调用- (void)done方法。这个方法主要负责属性的释放和隐藏完成回调的处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)done {
// Cancel any scheduled hideDelayed: calls
[self.hideDelayTimer invalidate];
//指示进度的显示问题,后续还需再补充
[self setNSProgressDisplayLinkEnabled:NO];

if (self.hasFinished) {
self.alpha = 0.0f;
if (self.removeFromSuperViewOnHide) {
[self removeFromSuperview];
}
}
MBProgressHUDCompletionBlock completionBlock = self.completionBlock;
if (completionBlock) {
completionBlock();
}
id<MBProgressHUDDelegate> delegate = self.delegate;
if ([delegate respondsToSelector:@selector(hudWasHidden:)]) {
[delegate performSelector:@selector(hudWasHidden:) withObject:self];
}
}
总结

从代码来看,MBProgressHUD这个三方库有几个地方值借鉴:

  • graceTimeminShowTime,在开发的时候会出现显示HUD后,存在缓存或者网速较好时,HUD显示到HUD隐藏的时间较短,界面出现闪动的情况,这时,就可以通过设置graceTimeminShowTime来处理,达到更好的用户体验。 这个在封装弹窗控件时,可以参考。

前言

UI布局对于iOS开发者来说并不陌生,在iOS6之前,大家都是通过UI控件的Frame属性和Autoresizing Mask来进行UI布局的(简称为手动布局)。AutoLayout则是苹果公司在iOS6推出的一种基于约束的,描述性的布局系统(简称为自动布局),这里主要从四个方面来阐述iOS布局及实践。

  • 手动布局和自动布局
  • AutoLayout原理
  • AutoLayout的性能
  • Masnory的使用

首先对手动布局和自动布局做一个简单的介绍:

手动布局和自动布局
  • 手动布局:指的是通过直接修改视图的frame属性的方式对界面进行布局。

    对于IOSapp开发者来说,不会像Android开发者一样为很多的屏幕尺寸来做界面适配,因此手动调整 frame的方式来布局也能工作良好。但是还是会有一些问题,如设备发生旋转、适配ipad等,并且保证视图原来之间的相对关系,则以上的方法都是无法解决的。如果要做这些适配,在AutoLayout未出来之前需要编写大量的代码,并且花费大量的调试适配时间。

  • 自动布局:指的是使用AutoLayout的方式对界面进行布局。

AutoLayout 是苹果本身提倡的技术,在大部分情况下也能很好的提升开发效率,但是 AutoLayout 对于复杂视图来说常常会产生严重的性能问题。随着视图数量的增长,AutoLayout 带来的 CPU 消耗会呈指数级上升。 如果对界面流畅度要求较高(如微博界面),可以通过提前计算好布局,在需要时一次性调整好对应属性 ,或者使用 ComponentKitAsyncDisplayKit 等框架来处理界面布局。

下面,我们来分析下 AutoLayout的原理。

AutoLayout的原理

这里通过使用Masonry来进行布局,从而来分析AutoLayout的原理,先简要了解下Masonry
Masonry是一个轻量级的布局框架,拥有自己的描述语法,采用更优雅的链式语法封装自动布局,简洁明了,并具有高可读性,而且同时支持 iOSMax OS X
Masnory支持的常用属性如下:

1
2
3
4
5
6
7
8
9
10
11
@property (nonatomic, strong, readonly) MASConstraint *left;     //左侧
@property (nonatomic, strong, readonly) MASConstraint *top; //上侧
@property (nonatomic, strong, readonly) MASConstraint *right; //右侧
@property (nonatomic, strong, readonly) MASConstraint *bottom; //下侧
@property (nonatomic, strong, readonly) MASConstraint *leading; //首部
@property (nonatomic, strong, readonly) MASConstraint *trailing; //首部
@property (nonatomic, strong, readonly) MASConstraint *width; //宽
@property (nonatomic, strong, readonly) MASConstraint *height; //高
@property (nonatomic, strong, readonly) MASConstraint *centerX; //横向中点
@property (nonatomic, strong, readonly) MASConstraint *centerY; //纵向中点
@property (nonatomic, strong, readonly) MASConstraint *baseline; //文本基线

其中leadinglefttrailingright 在正常情况下是等价的,但是当一些布局是从右至左时(比如阿拉伯语) 则会对调。
同时,在Masonry中能够添加AutoLayout约束有三个函数:

1
2
3
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block;//只负责新增约束` AutoLayout`不能同时存在两条针对于同一对象的约束,否则会报错
- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make))block;//针对上面的情况 会更新在block中出现的约束 不会导致出现两个相同约束的情况
- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block;//则会清除之前的所有约束 仅保留最新的约束

我们在代码中,经常会使用到equalTomas_equalTo,那它们的区别是什么呢?从代码中找到他们的定义如下:

1
2
3
#define mas_equalTo(...)                 equalTo(MASBoxValue((__VA_ARGS__)))
...
#define MASBoxValue(value) _MASBoxValue(@encode(__typeof__((value))), (value))

可以看到 mas_equalTo只是对其参数进行了一个BOX操作(装箱) ,所支持的类型,除了NSNumber支持的那些数值类型之外,还支持CGPointCGSizeUIEdgeInsets类型。
下面,我们通过一个例子,一步步来看下界面是怎么布局的,代码如下:

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
39
40
41
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor blackColor];

UIView *v1 = [[UIView alloc] init];
v1.backgroundColor = [UIColor orangeColor];
[v1 showPlaceHolder];

UIView *v2 = [[UIView alloc] init];
v2.backgroundColor = [UIColor orangeColor];
[v2 showPlaceHolder];

UIView *v3 = [[UIView alloc] init];
v3.backgroundColor = [UIColor orangeColor];
[v3 showPlaceHolder];

[self.view addSubview:v1];
[self.view addSubview:v2];
[self.view addSubview:v3];

[v1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(100);
make.leading.mas_equalTo(100);
make.width.mas_equalTo(70);
make.height.mas_equalTo(65);
}];

[v2 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(v1.mas_top);
make.leading.mas_equalTo(v1.mas_trailing).offset(20);
make.width.equalTo(v1.mas_width);
make.height.equalTo(v1.mas_height);
}];

[v3 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(v1.mas_bottom).offset(20);
make.leading.equalTo(v1.mas_leading);
make.trailing.equalTo(v2.mas_trailing);
make.height.equalTo(v1.mas_height);
}];
}

界面运行结果如下图:
CD52E302-FAFD-4D6E-9DFF-F5DB44C6098B.png
下面,我们将界面中的左上角的视图视为视图1,右上角的视图视为视图2,底部视图视为视图3,使用x1、y1、m1、n1来标识视图1的lefttopwidthheight,以此类推。
通过以上举例抽象出自动布局数学公式:
1C7344E7-1ED1-421A-B84E-ACBD70F98859.png
将以上等式变形为:

8AAD54BE-0157-4591-A117-0094F69BE6E7.png
此时,以上方程组,大家肯定很熟悉了,也就是《线性代数》中的线性方程组,现在将以上线性方程组抽象为:

B5E84F57-07DE-4A15-9D06-3D0ADD7E6EBD.png
上图表示“等式”方程组,那么是否还可以继续抽象?也就是说上述方程组能否完全表示未知元素之间与已知元素之间的关系,显然还不全面,因为还有(<,>,<=,>=)不等关系,因此将“=”等号抽象为关系”R”,在数学上关系R也就包括了“=”,”<”,”>”,”<=”,”>=”等关系。上述线程方程组变形为:(实质上,AutoLayout中所有的约束确实都是用数学关系式y R ax + b描述)

9A877277-5DEB-437C-AEF6-D6530AB6FE6F.png
现在已经将自动布局一步步抽象为数学公式,那么对视图的布局其实就是对线性方程组的求解。线性方程组解的情况有三种,实质上也对应着自动布局对视图的三种布局方案:

  • 唯一解:所有方程中的未知数能够解出唯一解。 充分约束:给一个视图添加的约束必须是充分的,才能正确布局一个视图;
  • 多个解:未知数不能求解出准确的唯一解,即未知数可能存在多个或者无限个解满足线性方程组。 欠约束:给视图所添加的约束不能够充分的表达视图的准确位置,在这种情况下自动布局会随意给视图一个布局方案,也就是自动布局中视图不能够正确布局或者视图丢失的情况。
  • 无解:不存在满足线性方程组的解。 冲突约束:给视图添加的约束表达视图布局出现了冲突,比如同时满足同一个视图宽度即为100又为200,这是不可能存在的。此时程序会出现崩溃。

通过以上描述,将AutoLayout系统的作用描述如图所示:

FBB5F53F-0B23-48B2-B150-39B405BFB335.png

AutoLayout的性能

AutoLayout的原理,我们可以得出布局系统最后仍然需要通过frame来进行布局,相比原有的布局系统加入了从约束计算 出frame 的过程,那么这个过程对性能是否会影响呢?
你可以在 这里 找到这次对 Layout 性能测量使用的代码。
代码分别使用Auto Layout、嵌套视图层级中使用 Auto Layout frameN 个视图进行布局,测算其运行时间。

对视图数量在 1~35 之间布局时间进行测量,结果如下:

视图数量范围为 1~35.png

对视图数量在 10~500 之间布局时间进行测量,结果如下:

视图数量范围为 10~500.png
从上述的测试数据可以看出,使用frameAutoLayout和嵌套视图层级中使用 Auto Layout进行布局、对应的视图数量分别为50个、6个和12个,所需要的时间就会在 16.67 ms 左右。,而想要让 iOS 应用的视图保持 60 FPS 的刷新频率,我们必须在 1/60 = 16.67 ms 之内完成包括布局、绘制以及渲染等操作。
综上所述,虽然说 Auto Layout 为开发者在多尺寸布局上提供了遍历,而且支持跨越视图层级的约束,但是由于其实现原理导致其时间复杂度为多项式时间,其性能损耗是仅使用 frame 的十几倍,所以在处理庞大的 UI 界面时表现差强人意。

Masnory的使用

下面,我们通过4个实例,来了解下Masnory的使用。

  • ######case 1: 并排显示两个label,宽度由内容决定。父视图宽度不够时,优先显示右边label的内容。

在默认情况下,我们没有设置各个布局的优先级,那么他就会优先显示左边的label,左边的完全显示后剩余的空间都是右边的label,如果整个空间宽度都不够左边的label的话,那么右边的label就没有显示的机会了。
如果我们现在的需求是优先显示右边的label,左边的label内容超出的省略,这时就需要我们调整约束的优先级了。
UIView中关于Content Hugging Content Compression Resistance的方法有:

1
2
3
4
5
- (UILayoutPriority)contentHuggingPriorityForAxis:(UILayoutConstraintAxis)axis NS_AVAILABLE_IOS(6_0);
- (void)setContentHuggingPriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis NS_AVAILABLE_IOS(6_0);

- (UILayoutPriority)contentCompressionResistancePriorityForAxis:(UILayoutConstraintAxis)axis NS_AVAILABLE_IOS(6_0);
- (void)setContentCompressionResistancePriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis NS_AVAILABLE_IOS(6_0);

那么这两个东西到底是什么呢?可以这样形象的理解一下:

  • contentHugging: 抱住使其在“内容大小”的基础上不能继续变大,这个属性的优先级越高,就要越“抱紧”视图里面的内容。也就是视图的大小不会随着父视图的扩大而扩大。
  • contentCompression: 撑住使其在在其“内容大小”的基础上不能继续变小,这个属性的优先级越高,越不“容易”被压缩。也就是说,当整体的空间装不下所有的视图时,Content Compression Resistance优先级越高的,显示的内容越完整。
    这两个属性分别可以设置水平方向和垂直方向上的,而且一个默认优先级是250, 一个默认优先级是750. 因为这两个很有可能与其他Constraint冲突,所以优先级较低。
1
2
3
4
static const UILayoutPriority UILayoutPriorityRequired NS_AVAILABLE_IOS(6_0) = 1000; // A required constraint.  Do not exceed this.
static const UILayoutPriority UILayoutPriorityDefaultHigh NS_AVAILABLE_IOS(6_0) = 750; // This is the priority level with which a button resists compressing its content.
static const UILayoutPriority UILayoutPriorityDefaultLow NS_AVAILABLE_IOS(6_0) = 250; // This is the priority level at which a button hugs its contents horizontally.
static const UILayoutPriority UILayoutPriorityFittingSizeLevel NS_AVAILABLE_IOS(6_0) = 50;
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
- (void)layoutPageSubViews {

[self.leftLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.contentView1.mas_top).with.offset(5);
make.left.equalTo(self.contentView1.mas_left).with.offset(2);
make.height.equalTo(@40);
}];

[self.rightLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.leftLabel.mas_right).with.offset(2);
make.top.equalTo(self.contentView1.mas_top).with.offset(5);
make.right.lessThanOrEqualTo(self.contentView1.mas_right).with.offset(-2);
make.height.equalTo(@40);

}];

[self.leftLabel setContentHuggingPriority:UILayoutPriorityRequired
forAxis:UILayoutConstraintAxisHorizontal];
[self.leftLabel setContentCompressionResistancePriority:UILayoutPriorityDefaultLow
forAxis:UILayoutConstraintAxisHorizontal];

[self.rightLabel setContentHuggingPriority:UILayoutPriorityRequired
forAxis:UILayoutConstraintAxisHorizontal];
[self.rightLabel setContentCompressionResistancePriority:UILayoutPriorityRequired
forAxis:UILayoutConstraintAxisHorizontal];
}
  • ######case 2: 四个ImageView整体居中,可以任意显示、隐藏。

blog_autolayout_example_with_masonry_3.png

下面的四个Switch控件分别控制上面对应位置的图片是否显示。

分析:首先就是整体居中,为了实现这个,最简单的办法就是将四个图片“装进”一个容器View里面,然后让这个容器View在整个页面中居中即可。这样就不用控制每个图片的居中效果了。
然后就是显示与隐藏。在这里我直接控制图片ImageView的宽度,宽度为0的时候不就“隐藏”了吗。

具体代码如下:

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
39
40
41
42
- (void)layoutPageSubViews {

[self.containerView mas_makeConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(IMAGE_SIZE);
make.centerX.equalTo(self.view.mas_centerX);
make.top.equalTo(self.view.mas_top).offset(200);
}];

//分别设置每个imageView的宽高、左边、垂直中心约束,注意约束的对象
//每个View的左边约束和左边的View的右边相等
__block UIView *lastView = nil;
__block MASConstraint *widthConstraint = nil;
NSUInteger arrayCount = self.imageViews.count;
[self.imageViews enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop) {
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(lastView ? lastView.mas_right : view.superview.mas_left);
make.centerY.equalTo(view.superview.mas_centerY);
if (idx == arrayCount - 1) {
make.right.equalTo(view.superview.mas_right);
}

widthConstraint = make.width.mas_equalTo(IMAGE_SIZE);
make.height.mas_equalTo(IMAGE_SIZE);

[self.widthConstraints addObject:widthConstraint];
lastView = view;
}];
}];
}

#pragma mark - event response
//点击switch按钮,如果打开,对应视图的宽约束设置为32,否则,设置为0
- (IBAction)showOrHideImage:(UISwitch *)sender {
NSUInteger index = (NSUInteger) sender.tag;
MASConstraint *width = self.widthConstraints[index];

if (sender.on) {
width.mas_equalTo(IMAGE_SIZE);
} else {
width.mas_equalTo(0);
}
}
  • #####case 3: 子视图的宽度始终是父视图的四分之三(或者任意百分比)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    //宽度为父view的宽度的四分之三
    [subView mas_makeConstraints:^(MASConstraintMaker *make) {
    //上下左贴边
    make.left.equalTo(_containerView.mas_left);
    make.top.equalTo(_containerView.mas_top);
    make.bottom.equalTo(_containerView.mas_bottom);
    //宽度为父view的宽度的一半
    make.width.equalTo(_containerView.mas_width).multipliedBy(0.75);
    }];
  • #####case 4 给同一个属性添加多重约束,实现复杂关系
1
2
3
4
5
6
7
8
9
10
11
12
13
14

- (void)layoutPageSubviews {

[self.greenLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.containerView);
make.right.lessThanOrEqualTo(self.containerView);
make.left.greaterThanOrEqualTo(self.containerView.mas_right).multipliedBy((CGFloat)(1.0f / 3.0f));
for (UILabel *label in self.leftLabels) {
make.left.greaterThanOrEqualTo(label.mas_right).offset(8);
}
}];

[self.greenLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
}
总结

通过上述分析,我们可以发现:

  • AutoLayout的原理就是对线性方程组或者不等式的求解,最终使用frame来绘制视图;
  • 使用AutoLayout进行布局时, 由于其实现原理导致其时间复杂度为多项式时间,其性能损耗是仅使用 frame 的十几倍,所以在处理庞大的 UI界面时表现差强人意。

前言

之前做过一个关于二维码的组件,已发布,现总结下。
开发的APP所需支持的最低版本为8.0,最初的方案为扫描使用苹果自带的API实现扫一扫的功能、使用ZXing识别从相册或别人转发的二维码图片。但发现ZXing识别从相册中来的图片性能很差,很多图片识别不了,且耗时较长,遂使用ZBar来实现识别从相册或别人转发的二维码图片。
这个组件重要实现了三个功能,扫一扫识别二维码图片、长按图片识别二维码图片和生成二维码图片。
首先来看下扫一扫识别二维码图片的代码实现:

功能实现

扫一扫识别二维码图片
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
- (void)initCapture {
AVCaptureDevice* inputDevice =
[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
[inputDevice lockForConfiguration:nil];
if ([inputDevice hasTorch]){
inputDevice.torchMode = AVCaptureTorchModeAuto;
}
AVCaptureFocusMode foucusMode = AVCaptureFocusModeContinuousAutoFocus;
if ([inputDevice isFocusModeSupported:foucusMode]) {
inputDevice.focusMode = foucusMode;
}
[inputDevice unlockForConfiguration];

AVCaptureDeviceInput *captureInput =
[AVCaptureDeviceInput deviceInputWithDevice:inputDevice error:nil];

if (!captureInput) {
//支持的最低版本为iOS8
UIAlertController *alterVC = [UIAlertController alertControllerWithTitle:MUIQRCodeLocalizedString(@"ScanViewController_system_tip") message:MUIQRCodeLocalizedString(@"ScanViewController_camera_permission") preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *confirmAction = [UIAlertAction actionWithTitle:MUIQRCodeLocalizedString(@"ScanViewController_yes") style:UIAlertActionStyleDefault handler:nil];
[alterVC addAction:confirmAction];
[self presentViewController:alterVC animated:YES completion:nil];
[self.activityView stopAnimating];
[self onVideoStart:nil];
return;
}

AVCaptureMetadataOutput *captureOutput = [[AVCaptureMetadataOutput alloc] init];
[captureOutput setMetadataObjectsDelegate:self queue:_queue];
self.captureOutput = captureOutput;

self.captureSession = [[AVCaptureSession alloc] init];
[self.captureSession addInput:captureInput];
[self.captureSession addOutput:captureOutput];

CGFloat w = 1920.f;
CGFloat h = 1080.f;
if ([self.captureSession canSetSessionPreset:AVCaptureSessionPreset1920x1080]) {
self.captureSession.sessionPreset = AVCaptureSessionPreset1920x1080;
} else if ([self.captureSession canSetSessionPreset:AVCaptureSessionPreset1280x720]) {
self.captureSession.sessionPreset = AVCaptureSessionPreset1280x720;
w = 1280.f;
h = 720.f;
} else if ([self.captureSession canSetSessionPreset:AVCaptureSessionPreset640x480]) {
self.captureSession.sessionPreset = AVCaptureSessionPreset640x480;
w = 960.f;
h = 540.f;
}
captureOutput.metadataObjectTypes = [captureOutput availableMetadataObjectTypes];
CGRect bounds = [[UIScreen mainScreen] bounds];

if (!self.prevLayer) {
self.prevLayer = [AVCaptureVideoPreviewLayer layerWithSession:self.captureSession];
}
self.prevLayer.frame = bounds;
self.prevLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
[self.view.layer insertSublayer:self.prevLayer atIndex:0];
//下面代码主要用来设置扫描的聚焦范围,计算rectOfInterest
CGFloat p1 = bounds.size.height/bounds.size.width;
CGFloat p2 = w/h;

CGRect cropRect = CGRectMake(CGRectGetMinX(_cropRect) - kSNReaderScanExpandWidth, CGRectGetMinY(_cropRect) - kSNReaderScanExpandHeight, CGRectGetWidth(_cropRect) + 2*kSNReaderScanExpandWidth, CGRectGetHeight(_cropRect) + 2*kSNReaderScanExpandHeight);

// CGRect cropRect = _cropRect;
if (fabs(p1 - p2) < 0.00001) {
captureOutput.rectOfInterest = CGRectMake(cropRect.origin.y /bounds.size.height, cropRect.origin.x/bounds.size.width,
cropRect.size.height/bounds.size.height,
cropRect.size.width/bounds.size.width);
} else if (p1 < p2) {
//实际图像被截取一段高
CGFloat fixHeight = bounds.size.width * w / h;
CGFloat fixPadding = (fixHeight - bounds.size.height)/2;
captureOutput.rectOfInterest = CGRectMake((cropRect.origin.y + fixPadding)/fixHeight,
cropRect.origin.x/bounds.size.width,
cropRect.size.height/fixHeight,
cropRect.size.width/bounds.size.width);
} else {
CGFloat fixWidth = bounds.size.height * h / w;
CGFloat fixPadding = (fixWidth - bounds.size.width)/2;
captureOutput.rectOfInterest = CGRectMake(cropRect.origin.y/bounds.size.height,
(cropRect.origin.x + fixPadding)/fixWidth,
cropRect.size.height/bounds.size.height,
cropRect.size.width/fixWidth);
}
}
识别二维码图片

识别二维码图片的功能,最初的方案是使用三方库ZXing来实现,因为ZXing有人在维护,但ZXing识别相册中的二维码图片或本地的图片时,有些图片根本就识别不出来,且耗时较长,所以改为使用ZBar。在网上找到一篇文章再见ZXing 使用系统原生代码处理QRCode,实测发现使用系统原生代码来识别二维码图片时,在,iphone4s,系统为iOS9的手机发现传回来的数组为空。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (NSString *)decodeQRImageWith:(UIImage*)aImage {
NSString *qrResult = nil;
//iOS8及以上可以使用系统自带的识别二维码图片接口,但此api有问题,在一些机型上detector为nil。
if (iOS8_OR_LATER) {
CIContext *context = [CIContext contextWithOptions:nil];
CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:context options:@{CIDetectorAccuracy:CIDetectorAccuracyHigh}];
CIImage *image = [CIImage imageWithCGImage:aImage.CGImage];
NSArray *features = [detector featuresInImage:image];
CIQRCodeFeature *feature = [features firstObject];
qrResult = feature.messageString;
} else {
ZBarReaderController* read = [ZBarReaderController new];
CGImageRef cgImageRef = aImage.CGImage;
ZBarSymbol* symbol = nil;
for(symbol in [read scanImage:cgImageRef]) break;
qrResult = symbol.data ;
return qrResult;
}
}

无图无真相:

14567CBE-E1D2-4FA7-AFA3-8B2037171F38.jpg

detector的值为nil,也就是说

1
CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:context options:@{CIDetectorAccuracy:CIDetectorAccuracyHigh}];

CIDetector的初始化方法无效。推测是苹果API的问题。

生成二维码图片

iOS8及以上版本使用苹果的API生成二维码图片,代码如下:

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
- (UIImage *)encodeQRImageWithContent:(NSString *)content size:(CGSize)size {
UIImage *codeImage = nil;
if (iOS8_OR_LATER) {
NSData *stringData = [content dataUsingEncoding: NSUTF8StringEncoding];
//生成
CIFilter *qrFilter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
[qrFilter setValue:stringData forKey:@"inputMessage"];
[qrFilter setValue:@"M" forKey:@"inputCorrectionLevel"];
UIColor *onColor = [UIColor blackColor];
UIColor *offColor = [UIColor whiteColor];
//上色
CIFilter *colorFilter = [CIFilter filterWithName:@"CIFalseColor"
keysAndValues:
@"inputImage",qrFilter.outputImage,
@"inputColor0",[CIColor colorWithCGColor:onColor.CGColor],
@"inputColor1",[CIColor colorWithCGColor:offColor.CGColor],
nil];

CIImage *qrImage = colorFilter.outputImage;
CGImageRef cgImage = [[CIContext contextWithOptions:nil] createCGImage:qrImage fromRect:qrImage.extent];
UIGraphicsBeginImageContext(size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetInterpolationQuality(context, kCGInterpolationNone);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextDrawImage(context, CGContextGetClipBoundingBox(context), cgImage);
codeImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGImageRelease(cgImage);
} else {
codeImage = [QRCodeGenerator qrImageForString:content imageSize:size.width];
}
return codeImage;
}

iOS8以下使用libqrencode库来生成二维码图片。

代码完善

2015年12月11日

QA测试发现,服务端生成的二维码,使用ZBar识别不出来,但将这张图片保存到相册,然后发送就可以识别出来。最初的想法是要服务端修改生成的二维码,但安卓能够识别出来,此路不通,那只有看ZBar的源码了。

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
- (id <NSFastEnumeration>) scanImage: (CGImageRef) image {
        timer_start;
        int nsyms = [self scanImage: image
                          withScaling: 0];
//没有识别出来,判断CGImageRef对象的宽和高是否大于640,大于或等于的话进行缩放再进行扫描
        if(!nsyms &&
           CGImageGetWidth(image) >= 640 &&
           CGImageGetHeight(image) >= 640)
            // make one more attempt for close up, grainy images
            nsyms = [self scanImage: image
                          withScaling: .5];

        NSMutableArray *syms = nil;
        if(nsyms) {
            // quality/type filtering
            int max_quality = MIN_QUALITY;
            for(ZBarSymbol *sym in scanner.results) {
                zbar_symbol_type_t type = sym.type;
                int quality;
                if(type == ZBAR_QRCODE)
                    quality = INT_MAX;
                else
                    quality = sym.quality;

                if(quality < max_quality) {
                    zlog(@"    type=%d quality=%d < %d\n",
                         type, quality, max_quality);
                    continue;
                }

                if(max_quality < quality) {
                    max_quality = quality;
                    if(syms)
                        [syms removeAllObjects];
                }
                zlog(@"    type=%d quality=%d\n", type, quality);
                if(!syms)
                    syms = [NSMutableArray arrayWithCapacity: 1];

                [syms addObject: sym];
            }
        }

        zlog(@"read %d filtered symbols in %gs total\n",
              (!syms) ? 0 : [syms count], timer_elapsed(t_start, timer_now()));
        return(syms);
}
if(max_quality < quality) {
max_quality = quality;
if(syms)
[syms removeAllObjects];
}
zlog(@"    type=%d quality=%d\n", type, quality);
if(!syms)
syms = [NSMutableArray arrayWithCapacity: 1];

[syms addObject: sym];
}
}
zlog(@"read %d filtered symbols in %gs total\n",
(!syms) ? 0 : [syms count], timer_elapsed(t_start, timer_now()));
return(syms);
}

在这里就产生了一个解决有些二维码图片识别不出来的解决思路:将传过来的UIImage的宽和高设置为640,识别不出来再进行缩放识别。修改UIImage的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
-(UIImage *)TransformtoSize:(CGSize)Newsize {
// 创建一个bitmap的context
UIGraphicsBeginImageContext(Newsize);
// 绘制改变大小的图片
[self drawInRect:CGRectMake(0, 0, Newsize.width, Newsize.height)];
// 从当前context中创建一个改变大小后的图片
UIImage *TransformedImg=UIGraphicsGetImageFromCurrentImageContext();
// 使当前的context出堆栈
UIGraphicsEndImageContext();
// 返回新的改变大小后的图片
return TransformedImg;
}

这样类似于将ZXing中的tryHard设置为YES。识别不出来的二维码图片就可以识别了。

2016年5月20日
遗留的bug: 点击进入扫一扫界面,退出,再进入,这样重复5次左右,扫一扫之前的界面的会出现卡顿。
原因:多次进入扫一扫界面,再退出,因此界面未被系统回收,captureSession对象一直在运行,会造成内存泄露,引起上一个界面卡顿。
解决方案:在视图将要消失的时候,确保captureSession对象停止运行。

1
2
3
4
5
6
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if ([self.captureSession isRunning]) {
[self.captureSession stopRunning];
}
}

2018年4月28日
识别二维码图片优化

近期通过bugly收集卡顿问题发现,二维码组件在识别二维码图片时,会出现卡顿问题。为优化识别速度,采用了三种方案,并分别进行测试,并对测试数据进行分析,最终挑选出最优的方案。

任务A:使用系统提供的CoreImage的CIDetector接口去识别二维码图片,返回对应的字符串;
任务B:使用zbar中的方法去识别二维码图片,返回对应的字符串。

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
//任务A
+ (NSString *)useSystemMethodDecodeImage:(UIImage *)image {
NSString *resultString = nil;
CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode
context:nil
options:@{CIDetectorAccuracy:CIDetectorAccuracyHigh}];
if (detector) {
CIImage *ciImage = [CIImage imageWithCGImage:image.CGImage];
NSArray *features = [detector featuresInImage:ciImage];
CIQRCodeFeature *feature = [features firstObject];
resultString = feature.messageString;
}
return resultString;
}
//任务B
+ (NSString *)useZbarMethodDecodeImage:(UIImage *)image {
UIImage *decodeImage = image;
if (decodeImage.size.width < 641) {
decodeImage = [decodeImage TransformtoSize:CGSizeMake(640, 640)];
}
QRCodeZBarReaderController* read = [QRCodeZBarReaderController new];
CGImageRef cgImageRef = decodeImage.CGImage;
QRCodeZBarSymbol *symbol = nil;
for(symbol in [read scanImage:cgImageRef]) break;
return symbol.data;
}
  • 方案A:先执行任务A,如果获取到的字符串为空,再执行任务B。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
+ (NSString *)planOneDecodeWithImage:(UIImage *)image index:(NSInteger)index{

NSMutableString *costTimeInfo = [NSMutableString stringWithFormat:@"%ld\r\n",index];
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
NSString *detectorString = [MUIQRCodeDecoder useSystemMethodDecodeImage:image];
CFAbsoluteTime detectorCostTime = (CFAbsoluteTimeGetCurrent() - startTime);

[costTimeInfo appendString:[NSString stringWithFormat:@"detector : %f ms\r\n",detectorCostTime *1000.0]];

NSAssert(detectorString.length > 0, @"detector fail!");
CFAbsoluteTime zbarStartTime = CFAbsoluteTimeGetCurrent();
NSString *zbarSymbolString = [MUIQRCodeDecoder useZbarMethodDecodeImage:image];
NSAssert(zbarSymbolString.length > 0, @"zbar fail!");
CFAbsoluteTime zbarCostTime = (CFAbsoluteTimeGetCurrent() - zbarStartTime);

[costTimeInfo appendString:[NSString stringWithFormat:@"zbar : %f ms\r\n",zbarCostTime *1000.0]];

CFAbsoluteTime totalCostTime = (CFAbsoluteTimeGetCurrent() - startTime);

[costTimeInfo appendString:[NSString stringWithFormat:@"total cost : %f ms\r\n",totalCostTime *1000.0]];
[costTimeInfo appendString:[NSString stringWithFormat:@"detectorString : %@ ms\r\n",detectorString]];
[costTimeInfo appendString:[NSString stringWithFormat:@"zbarSymbolString : %@ ms\r\n",zbarSymbolString]];
return [costTimeInfo copy];
}
  • 方案B:同时执行任务A和任务B,两者均执行完后,返回识别的结果;
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
+ (NSString *)planTwoDecodeWithImage:(UIImage *)image index:(NSInteger)index { 
__block NSMutableString *costTimeInfo = [NSMutableString stringWithFormat:@"%ld\r\n",index];
__block NSString *detectorString = nil;
__block NSString *zbarSymbolString = nil;

CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
detectorString = [MUIQRCodeDecoder useSystemMethodDecodeImage:image];
NSAssert(detectorString.length > 0, @"detector fail!");
CFAbsoluteTime costTime = (CFAbsoluteTimeGetCurrent() - startTime);
[costTimeInfo appendString:[NSString stringWithFormat:@"detector : %f ms\r\n",costTime *1000.0]];
});

dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
zbarSymbolString = [MUIQRCodeDecoder useZbarMethodDecodeImage:image];
NSAssert(zbarSymbolString.length > 0, @"zbar fail!");
CFAbsoluteTime costTime = (CFAbsoluteTimeGetCurrent() - startTime);
[costTimeInfo appendString:[NSString stringWithFormat:@"zbar : %f ms\r\n",costTime *1000.0]];
});

dispatch_group_notify(group, dispatch_get_global_queue(0,0), ^{
dispatch_semaphore_signal(semaphore);
});

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

CFAbsoluteTime totalCostTime = (CFAbsoluteTimeGetCurrent() - startTime);
[costTimeInfo appendString:[NSString stringWithFormat:@"total cost : %f ms\r\n",totalCostTime *1000.0]];
[costTimeInfo appendString:[NSString stringWithFormat:@"detectorString : %@ ms\r\n",detectorString]];
[costTimeInfo appendString:[NSString stringWithFormat:@"zbarSymbolString : %@ ms\r\n",zbarSymbolString]];
return [costTimeInfo copy];
}
  • 方案C:同时执行任务A和任务B
    1、任务A先执行完且识别成功,返回识别结果;
    2、任务B先执行完且识别成功,返回识别结果;
    3、任务A和任务B均识别失败,两者均执行完后,返回识别的结果。
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
39
40
41
42
43
44
45
46
47
+ (NSString *)planThreeDecodeWithImage:(UIImage *)image index:(NSInteger)index {
__block NSMutableString *costTimeInfo = [NSMutableString stringWithFormat:@"%ld\r\n",index];
__block NSString *detectorString = nil;
__block NSString *zbarSymbolString = nil;
__block BOOL isNeedSendSignal = YES;

CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
detectorString = [MUIQRCodeDecoder useSystemMethodDecodeImage:image];
//NSAssert(detectorString.length > 0, @"detector fail!");
CFAbsoluteTime costTime = (CFAbsoluteTimeGetCurrent() - startTime);
[costTimeInfo appendString:[NSString stringWithFormat:@"detector : %f ms\r\n",costTime *1000.0]];
if (detectorString.length > 0 && isNeedSendSignal) {
isNeedSendSignal = NO;
dispatch_semaphore_signal(semaphore);
}
});

dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{
zbarSymbolString = [MUIQRCodeDecoder useZbarMethodDecodeImage:image];
//NSAssert(zbarSymbolString.length > 0, @"zbar fail!");
CFAbsoluteTime costTime = (CFAbsoluteTimeGetCurrent() - startTime);
[costTimeInfo appendString:[NSString stringWithFormat:@"zbar : %f ms\r\n",costTime *1000.0]];
if (zbarSymbolString.length > 0 && isNeedSendSignal) {
isNeedSendSignal = NO;
dispatch_semaphore_signal(semaphore);
}
});

dispatch_group_notify(group, dispatch_get_global_queue(0,0), ^{
if (isNeedSendSignal) {
dispatch_semaphore_signal(semaphore);
}
});

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

CFAbsoluteTime totalCostTime = (CFAbsoluteTimeGetCurrent() - startTime);
[costTimeInfo appendString:[NSString stringWithFormat:@"total cost : %f ms\r\n",totalCostTime *1000.0]];
[costTimeInfo appendString:[NSString stringWithFormat:@"detectorString : %@ ms\r\n",detectorString]];
[costTimeInfo appendString:[NSString stringWithFormat:@"zbarSymbolString : %@ ms\r\n",zbarSymbolString]];
return [costTimeInfo copy];
}

测试数据如下所示:(取了前10张图片)

识别二维码图片耗时.png

分析测试数据发现:
1、在测试第一张二维码图片时,总耗时均较大,如果第一次识别使用的是系统方法,耗时超过500ms,这也是为什么会出现卡顿的原因;
2、使用系统方法去识别二维码图片时,如果不是第一次去识别,耗时较小,在65ms以内;
3、使用zbar的方法去识别二维码图片,耗时均值在200ms以内;
4、在方案C中,如果第一次使用系统方法,耗时为226ms。

总结得出,从优化卡顿问题的角度出发,使用方案C最优,同时发现,如果使用系统方法能识别出二维码图片,在初始化之后(也就是第二次使用),耗时最短。同时因为在实际的使用场景中,图片是一张一张识别的,识别过程有一个间隔时间,如果已经使用系统方法识别过二维码图片,那下次识别就能达到最优。所以使用方案C的话,最差情况均值在200ms左右,最好的情况和方案A中第二次使用系统方法耗时基本一致。综合考虑,使用方案C。

小结

在实际的项目开发过程中,设想的情况和实际情况会存在偏差,需要自己时刻使用性能调优工具,根据数据去进行优化,而不能想当然的认为某种方式是最优的。
源码和demo请点这里
参考的文章链接如下
再见ZXing 使用系统原生代码处理QRCode
IOS二维码扫描,你需要注意的两件事
Zbar算法流程介绍