SVN

目录

1.Mac下搭建SVN服务器

1

SVN中trunk,branches,tags用法详解

svn://proj/|+-trunk+-branches+-tags
这是一个标准的布局,trunk为主开发目录,branches为分支开发目录,tags为tag存档目录(不允许修改)。但是具体这几个目录应该如何使用,svn并没有明确的规范,更多的还是用户自己的习惯。

对于这几个开发目录,一般的使用方法有两种。我更多的是从软件产品的角度出发(比如freebsd),因为互联网的开发模式是完全不一样的。 1.第一种方法,使用trunk作为主要的开发目录
一般的,我们的所有的开发都是基于trunk进行开发,当一个版本/release开发告一段落(开发、测试、文档、制作安装程序、打包等)结束后,代码处于冻结状态(人为规定,可以通过hook来进行管理)。此时应该基于当前冻结的代码库,打tag。当下一个版本/阶段的开发任务开始,继续在trunk进行开发。
此时,如果发现了上一个已发行版本(Released Version)有一些bug,或者一些很急迫的功能要求,而正在开发的版本(Developing Version)无法满足时间要求,这时候就需要在上一个版本上进行修改了。应该基于发行版对应的tag,做相应的分支(branch)进行开发。
例如,刚刚发布1.0,正在开发2.0,此时要在1.0的基础上进行bug修正。

SVN 中trunk、tags、branches使用

svn中的revert和update

iOS 用CornerStone配置SVN,HTTP及svn简单使用说明

Mac下搭建SVN服务器

有些MAC用户想在自己的电脑中建立一个SVN服务器,那么该怎么做呢?
具体步骤:

  • 第一步,启动终端。命令如下:svnadmin create /Users/lichaoqian/Project/SVN,这样我们就在自己的主目录下建立了一个名为“SVN”的代码库。 得到的目录为:
    SVN创建后的目录结构
  • 第二步,配置这个代码仓库。
  • ①首先修改conf文件夹中的svnserve.conf文件,只要把#password-db = passwd这句话的#去掉,使得这个配置项生效即可。保存退出。
    SVN配置代码库1
  • ②接着修改passwd文件,这个里面存着这个代码库接受的用户名和密码
      采用的形式是[用户名]=[密码]。可以增加一个用户“user”,密码“pass”,形如:user=pass,
      SVN配置代码库2
      这里改为只接受用户名为lichaoqian,密码也为lichaoqian的用户。
      SVN配置代码库3
      保存退出。
  • 第三步,启动服务器。终端中继续,命令如下:
    svnserve -d -r /Users/lichaoqian/Project/SVN
    没有任何提示就说明启动成功了。附停止服务器:killall -9 svnserve
    以上就是在MAC中建立SVN服务器的具体步骤了,想要自己建立SVN服务器的用户,快来看看吧

CornerStone的使用

svn的merge使用例子
Cornerstone Merge操作详解
CornerStone的使用

[toc]

框架设计模式-③观察者模式(Notification&KVO)

精选文章

iOS 语言之KVO

ProtocolCenter

从两张图片可以看到,最大的区别是调度的地方。

虽然两种模式都存在订阅者和发布者(具体观察者可认为是订阅者、具体目标可认为是发布者),但是观察者模式是由具体目标调度的,而发布/订阅模式是统一由调度中心调的,所以观察者模式的订阅者与发布者之间是存在依赖的,而发布/订阅模式则不会。

观察者模式

UserManager

postUserNameUpdate:xxx

img

发布订阅模式

img

END

框架设计模式-②单例模式

精选文章

以下目录内容全部摘自:iOS中的单例模式

目录

1
2
...
>

一、单例

< 返回目录

1、单例:dispatch_once (使用dispatch_once时,不用使用@synchronized)
单例是一种用于实现单例的数学概念,即将类的实例化限制成仅一个对象的设计模式。
或者我的理解是:单例是一种类,该类只能实例化一个对象。

实现单例模式的函数就是void dispatch_once( dispatch_once_t *predicate, dispatch_block_t block);
该函数接收一个dispatch_once用于检查该代码块是否已经被调度的谓词(是一个长整型,实际上作为BOOL使用)。它还接收一个希望在应用的生命周期内仅被调度一次的代码块,对于本例就用于shared实例的实例化。
dispatch_once不仅意味着代码仅会被运行一次,而且还是线程安全的,这就意味着你不需要使用诸如@synchronized之类的来防止使用多个线程或者队列时不同步的问题。
Apple的GCD Documentation证实了这一点:
如果被多个线程调用,该函数会同步等等直至代码块完成。

示例:在整个应用中访问某个类的共享实例

1
2
3
4
5
6
7
8
9
10
11
+ (NetworkManager *)sharedInstance
{
static NetworkManager *sharedManager;

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[NetworkManager alloc] init];
});

return sharedManager;
}

就这些,你现在在应用中就有一个共享的实例,该实例只会被创建一次。
下次你任何时候访问共享实例,需要做的仅是:NetworkManager *networkManager = [NetworkManager sharedInstance];

2、线程的同步执行@synchronized
为了防止多个线程同时执行同一个代码块,OC提供了@synchronized()指令。使用@synchronized()指令可以锁住在线程中执行的某一个代码块。存在被保护(即被锁住)的代码块的其他线程,将被阻塞,这也就意味着,他们将在@synchronized()代码块的最后一条语句执行结束后才能继续执行。
@synchronized()指令的唯一参数可以使用任何OC对象,包括self。这个对象就是我们所谓的信号量。

END

< 返回目录

[toc]

框架设计模式-④适配器模式、策略模式、责任链模式(Adapter、Strategy、Chain of Responsibility)

设计模式 核心概念 场景
适配器模式 将一个类的接口变换成所期待的另一种接口 各支付SDK支付服务接口的整合
策略模式 定义共同行为,各策略遵循,以达到可切换策略 支付SDK的选择、计价方案的选择
责任链模式 添加责任,形成责任链,成功则传递以执行下一个责任 请求拦截器的责任链、内容发布(订单创建)

零、组合模式

场景:评论系统的评论和回复、组织架构、文件系统

判断代码是否使用了组合模式,可以检查以下几个关键点:

  1. 对象的树形结构:代码中是否存在对象的树形结构,即对象间存在父子关系,并且可以递归地组织在一起。
  2. 共享接口:是否定义了一个共享接口或抽象类,它被叶子对象和容器对象所实现或继承。
  3. 叶子对象(Leaf):是否存在叶子对象,它们是树形结构中的末端节点,没有子节点。

如果你在代码中看到了这些特征,那么它很可能使用了组合模式。组合模式的目的是将对象组合成树形结构,并使得客户端可以统一地对待叶子对象和容器对象。

一、适配器模式(Adapter)

适配器模式(Adapter Pattern):将一个类的接口变换成所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。

说人话:这个模式就是用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。

案例1:各支付SDK

1.1、背景:假设各支付SDK对外提供的支付接口如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 微信支付
class WeChatPay: NSObject {
// completion
func payWithMoney(amount: Double, completion: @escaping (Bool, Error?) -> Void) {
completion(true, nil) // 假设支付成功
}
}
// 支付宝支付
class AlipayPayment: NSObject {
// success + failure
func payWithAmount(amount: Double, success: (Bool) -> Void, failure: (Error) -> Void) {
success(true, nil) // 假设支付成功
}
}

适配器模式,将支付宝支付由 success + failure 改成 completion 方式,使得不兼容的接口转换为可兼容的接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
class AlipayPaymentAdapter: NSObject {
private let alipayPayment = AlipayPayment()

func payWithAmount(amount: Double, completion: @escaping (Bool, Error?) -> Void) {
alipayPayment.payWithAmount(amount: amount) { success in
if success {
completion(true, nil)
} else {
completion(false, NSError(domain: "PaymentError", code: -1, userInfo: [NSLocalizedDescriptionKey: "Payment failed"]))
}
}
}
}

1.2、适配器模式优化

1.2.1、适配器模式+面向接口编程

为了更好的使用适配器模式,我们可以通过面向接口编程,提前定义适配器接口(其实这个公共接口也可作为策略接口)。

面向协议编程(POP)和面向接口编程(IOP)都是设计原则,它们都强调了依赖抽象而不是具体实现。在某些方面,面向协议可以看作是面向接口的一个扩展或特定语言环境下的实现。

面向接口编程(IOP)

面向接口编程是一种设计原则,它强调通过接口来定义对象的行为,而不是依赖于具体的类。接口定义了一组方法签名,但不提供实现。类通过实现这些接口来声明它们具有特定的行为。

面向协议编程(POP)

面向协议编程是面向接口编程的一个特定实现,特别是在支持协议的语言(如Swift)中。协议定义了一组方法、属性或其他要求,类、结构体或其他类型的实例需要遵循这些要求。协议可以包含默认实现,这使得它们更加灵活。

包含关系

从概念上讲,面向协议编程可以看作是面向接口编程的一个扩展。在面向协议编程中,你定义协议(类似于接口),但协议提供了更多的功能,如:

  1. 默认实现:协议可以提供方法的默认实现,这在接口中通常不可行。
  2. 扩展性:协议可以被扩展,添加更多的方法或属性。
  3. 多重继承:协议支持多重继承,即一个类可以同时遵循多个协议。

在iOS开发中,实现支付系统集成时,可以使用适配器模式(将不兼容的接口转换为可兼容的接口)来创建一个统一的支付接口,然后为每种支付方式(如支付宝、微信支付)提供一个具体的适配器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
protocol PaymentProtocol {
func pay(amount: Double, completion: @escaping (Bool, Error?) -> Void)
}

class WeChatPayAdapter: PaymentProtocol {
private let weChatPay = WeChatPay()
func pay(amount: Double, completion: @escaping (Bool, Error?) -> Void) {
weChatPay.payWithMoney(amount: amount, completion: completion)
}
}
class AlipayAdapter: PaymentProtocol {
private let alipayPayment = AlipayPayment()
func pay(amount: Double, completion: @escaping (Bool, Error?) -> Void) {
alipayPayment.payWithAmount(amount: amount) { success in
if success {
completion(true, nil)
} else {
completion(false, NSError(domain: "PaymentError", code: -1, userInfo: [NSLocalizedDescriptionKey: "Payment failed"]))
}
}
}
}

案例2:UITableView优化

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

- (void)viewDidLoad {
[super viewDidLoad];

// 处理UI和数据的步骤省略....
GameCellAdapter *cellAdapter = [[GameCellAdapter alloc] initWithTableView:self.tableView datas:datas];
GameSectionAdapter *fpsSectionAdapter = [[GameSectionAdapter alloc] initWithCellAdapter:cellAdapter sectionTitle:@"FPS Games" sectionHeight:60];
GameSectionAdapter *roleSectionAdapter = [[GameSectionAdapter alloc] initWithCellAdapter:cellAdapter sectionTitle:@"Role Play Games" sectionHeight:60];

self.adapter = [[GameDetailListAdapter alloc] init];
self.adapter.sections = [@[fpsSectionAdapter, roleSectionAdapter] mutableCopy];

self.tableView.dataSource = self.adapter;
self.tableView.delegate = self.adapter;
}

以上UITableView使用适配器模式优化的代码摘自:iOS模式分析 使用适配器模式重构TableView

二、策略模式(Strategy)

app正常业务请求、埋点请求、数美请求、同盾请求。

策略模式(Strategy Pattern)是一种行为设计模式,它定义了一系列的算法,并将每一个算法封装起来,使它们可以互换使用。这种模式让算法的变化独立于使用算法的客户。策略模式属于对象行为型模式,它能够让你根据不同的情况选择不同的算法或行为。

策略模式的主要组成

  1. 策略接口(Strategy Interface):定义了一个公共的接口,各种算法以不同的方式实现这个接口。
  2. 具体策略类(Concrete Strategy):实现策略接口的具体算法。
  3. 上下文(Context):使用策略接口,维护一个对策略对象的引用,可以设置和切换不同的策略。

策略模式的优点

  1. 算法的多样性:可以在运行时选择不同的算法,增加新的算法而不需要修改原有代码。
  2. 避免使用多重条件判断:策略模式可以将算法族封装在不同的策略类中,避免在条件判断中使用大量的if-elseswitch-case语句。
  3. 扩展性:增加新的策略时,不需要修改原有代码,符合开闭原则。

策略模式的使用场景

  • 当需要在运行时选择算法或行为时。
  • 当需要避免使用多重条件判断时。

案例1:支付SDK选择

1、不使用策略模式时候,支付接口使用的常见写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class PayUtil: NSObject {
// PayUtil 统一管理支付逻辑:解决支付接口分散问题
static func pay(amount: Double, payType: PaymentMethod, completion: @escaping (Bool, Error?) -> Void) {
if payType == .wechat {
WeChatPay().payWithMoney(amount: amount, completion: completion)
} else {
AlipayPaymentAdapter().payWithAmount(amount: amount, completion: completion)
}
}
}

// 使用示例:
PayUtil.pay(amount: 100, payType:.alipay) { success, error in
if success {
print("支付成功")
} else {
print("支付失败: \(error?.localizedDescription ?? "未知错误")")
}
}

2、而使用策略模式时(可配合依赖注入,让解耦性好上加好)

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
enum PaymentMethod {
case alipay
case wechat
}
class PaymentService {
private var paymentAdapter: PaymentProtocol?

// 如果你希望进一步实现依赖注入,可以将 paymentAdapter 作为依赖注入到 PaymentService 中,而不是在 PaymentService 内部创建。这样可以**更好地解耦和提高可测试性**。
// func init(adapter: PaymentProtocol) { // 构造器注入
// func setPaymentAdapter(adapter: PaymentProtocol) { // 属性注入
// paymentAdapter = adapter
// }
func setPaymentMethod(method: PaymentMethod) {
switch method {
case .alipay:
paymentAdapter = AlipayAdapter()
case .wechat:
paymentAdapter = WeChatPayAdapter()
}
}

func payment(amount: Double, completion: @escaping (Bool, Error?) -> Void) {
paymentAdapter?.pay(amount: amount, completion: completion)
}
}

// 使用示例:
let paymentService = PaymentService()
paymentService.setPaymentMethod(method: .alipay)
paymentService.payment(amount: 100) { success, error in
if success {
print("支付成功")
} else {
print("支付失败: \(error?.localizedDescription ?? "未知错误")")
}
}

3、问:不使用PayUtil,而是选择使用策略模式的原因/使用策略模式的优点?

3.1、更符合开闭原则

当需要添加银联支付时候,即使是使用策略模式,也需要对PaymentService进行修改。即在 PaymentService 中添加一个新的 case 来处理新的支付方式,这样,当需要支付时,可以根据用户选择的支付方式动态地切换支付策略。该操作确实和PayUtil一样涉及到对现有代码的修改,这在一定程度上违反了开闭原则的理想状态,即不应该通过修改现有代码来扩展系统。然而,在实际的软件开发中,完全避免修改任何现有代码是非常困难的,特别是在设计初期可能未能预见到所有未来需求的情况下。开闭原则更多地是一种设计哲学,指导我们尽可能地设计出可扩展的系统,而不是绝对的规则。但是相比之下使用策略模式对中间类PaymentService改动还是更小的。

3.2、策略接口多的时候相比Util其就更符合开闭原则了

此时策略模式的优势可能还没发挥出来,但当我们的各平台钱包SDK,如果除pay操作外,还有余额查询,查询交易记录等接口时候,策略模式的优势就显示出来了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class PaymentService {
private var paymentAdapter: PaymentProtocol?

func setPaymentMethod(method: PaymentMethod) {
switch method {
case .alipay:
paymentAdapter = AlipayAdapter()
case .wechat:
paymentAdapter = WeChatPayAdapter()
}
}
// 支付
func payment(amount: Double, completion: @escaping (Bool, Error?) -> Void) {
paymentAdapter?.pay(amount: amount, completion: completion)
}
// 查询余额
func checkBalance(completion: @escaping (Bool, Error?) -> Void) {
paymentAdapter?.pay(completion: completion)
}
// 查询交易记录
func checkOrders(completion: @escaping (Bool, Error?) -> Void) {
paymentAdapter?.pay(completion: completion)
}
}

案例2:计价

假设咖啡添加不同成分时,计价不同,常见的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 咖啡基类
abstract class BaseCoffee {
int get price;
}

class Coffee extends BaseCoffee {
Sugar? sugar;
Milk? milk;

int get price {
if (sugar != nil) {
if (milk != nil) {
return 15;
} else {
return 6;
}
} else {
return 10;
}
}
}

而使用策略模式时候,会将不同成分的咖啡分成不同策略(策略中有计价方式):

好处:每个策略看起来很简洁,而不是堆在一块。

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
// 定义一个定价策略协议
protocol PricingStrategy {
func calculatePrice() -> Int
}

// 定义各种定价策略:没有额外成分的、添加糖的、添加牛奶的、同时添加糖和牛奶的
struct BasePricingStrategy: PricingStrategy {
func calculatePrice() -> Int {
return 10
}
}
struct SugarPricingStrategy: PricingStrategy {
func calculatePrice() -> Int {
return 6
}
}
struct MilkPricingStrategy: PricingStrategy {
func calculatePrice() -> Int {
return 10
}
}
struct SugarAndMilkPricingStrategy: PricingStrategy {
func calculatePrice() -> Int {
return 15
}
}

调用

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
// 定义 Coffee 类
class Coffee {
var pricingStrategy: PricingStrategy
private var _sugar = false
private var _milk = false

init() {
self.pricingStrategy = BasePricingStrategy()
}

var sugar: Bool {
get {
return _sugar
}
set {
_sugar = newValue
updateStrategy()
}
}

var milk: Bool {
get {
return _milk
}
set {
_milk = newValue
updateStrategy()
}
}

private func updateStrategy() {
if _sugar && _milk {
self.pricingStrategy = SugarAndMilkPricingStrategy()
} else if _sugar {
self.pricingStrategy = SugarPricingStrategy()
} else if _milk {
self.pricingStrategy = MilkPricingStrategy()
} else {
self.pricingStrategy = BasePricingStrategy()
}
}

func getPrice() -> Int {
return pricingStrategy.calculatePrice()
}
}

// 使用示例
let coffee = Coffee()
print("Basic Coffee: \(coffee.getPrice())") // 输出 10

coffee.sugar = true
print("Coffee with Sugar: \(coffee.getPrice())") // 输出 6

coffee.milk = true
print("Coffee with Sugar and Milk: \(coffee.getPrice())") // 输出 15

let coffee2 = Coffee()
coffee2.milk = true
print("Coffee with Milk: \(coffee2.getPrice())") // 输出 10

三、责任链模式

常见场景:请求拦截器、内容发布、订单创建(创建前有存不同定金的可获取不同优惠券)

浅谈对责任链模式的理解?应用场景?

场景1:订单创建(创建前有存不同定金的可获取不同优惠券)

场景3:请求拦截器的责任链

场景2:内容发布的责任链

  1. 内容验证/检查:在内容发布之前,可以有一个验证器来检查内容是否符合特定的规则,比如检查是否有敏感词汇、是否符合格式要求等。
  2. 权限检查:在内容发布之前,可以检查用户是否有发布内容的权限。
  3. 内容格式化:内容可能需要经过格式化处理,比如自动添加引用、格式化代码块等。
  4. 发布
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
// 责任链遵循的协议
abstract class ContentHandler {
bool handleContent(String content);
}

// 创建一个链来管理这些处理器,拦截器管理类
class ContentChain {
List<ContentHandler> _handlers = [];
void addHandler(ContentHandler handler) {
_handlers.add(handler);
}

void handle(String content) {
for(ContentHandler iHandler in _handlers) {
bool success = iHandler.handleContent(content);
if (!success) {
return;
}
}
}
}

// 责任链调用方法
void main() {
var chain = ContentChain();
chain.addHandler(LogContentHandler());
chain.addHandler(ValidateContentHandler());
chain.addHandler(FormatContentHandler());
chain.addHandler(PublishContentHandler());

chain.handle("This is a sample content with badword.");
}

// 各责任链定义与实现
abstract class ContentHandler {
void handleContent(String content, Function next);
}

class LogContentHandler extends ContentHandler {
@override
bool handleContent(String content) {
print('Logging content: $content');
return true;
}
}

class ValidateContentHandler extends ContentHandler {
@override
bool handleContent(String content) {
if (content.contains('badword')) {
print('Content validation failed.');
return false;
}
print('Content is valid.');
return true;
}
}

class FormatContentHandler extends ContentHandler {
@override
bool handleContent(String content) {
// 假设这里对内容进行了格式化
String formattedContent = content.replaceAll('\n', '<br/>');
print('Formatted content: $formattedContent');
return true;
}
}

class PublishContentHandler extends ContentHandler {
@override
bool handleContent(String content) {
print('Publishing content: $content');
return true;
}
}

代码示例中,ContentHandler 抽象类定义了一个 handleContent 方法,该方法返回一个布尔值来指示处理是否成功。ContentChain 类管理处理器链,并在 handle 方法中遍历链中的每个处理器,调用 handleContent 方法,并根据返回值决定是否继续执行。

这种方法确实可以工作,并且它简化了责任链的实现。然而,它有几个潜在的问题:

  1. 内容修改:如果处理器需要修改内容并希望这些修改对后续处理器可见,这种方法就无法满足需求。在当前的设计中,内容的修改不会传递给链中的下一个处理器。
  2. 错误处理:如果某个处理器失败,整个链将停止执行。但是,没有提供一种机制来通知调用者哪个处理器失败了,或者为什么失败。
  3. 异步处理(最常见):如果处理器需要执行异步操作(例如网络请求),当前的设计不支持异步处理。

为了解决这些问题,我们可以对责任链模式进行一些改进。以下是一个改进后的示例:

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
abstract class ContentHandler {
void handleContent(String content, {required void Function(String) success, required void Function() failure});
}

class LogContentHandler extends ContentHandler {
@override
void handleContent(String content, {required void Function(String) success, required void Function() failure}) {
print('Logging content: $content');
success(content);
}
}

class ValidateContentHandler extends ContentHandler {
@override
void handleContent(String content, {required void Function(String) success, required void Function() failure}) {
if (content.contains('badword')) {
print('Content validation failed.');
failure();
return;
}
print('Content is valid.');
success(content);
}
}

class FormatContentHandler extends ContentHandler {
@override
void handleContent(String content, {required void Function(String) success, required void Function() failure}) {
try {
String formattedContent = content.replaceAll('\n', '<br/>');
print('Formatted content: $formattedContent');
success(formattedContent);
} catch (e) {
failure();
}
}
}

class PublishContentHandler extends ContentHandler {
@override
void handleContent(String content, {required void Function(String) success, required void Function() failure}) {
try {
print('Publishing content: $content');
success(content);
} catch (e) {
failure();
}
}
}

class ContentChain {
List<ContentHandler> _handlers = [];

void addHandler(ContentHandler handler) {
_handlers.add(handler);
}

void handle(String content) {
_functionChain(0, content);
}

void _functionChain(int index, String content) {
if (index >= _handlers.length) return;

_handlers[index].handleContent(content,
success: (String newContent) {
_functionChain(index + 1, newContent);
},
failure: () {
print("Error handling content. Stopping the chain.");
}
);
}
}

void main() {
var chain = ContentChain();
chain.addHandler(LogContentHandler());
chain.addHandler(ValidateContentHandler());
chain.addHandler(FormatContentHandler());
chain.addHandler(PublishContentHandler());

chain.handle("This is a sample content with badword.");
}

四、装饰器模式

1、继承的缺点

使用继承的缺点包括:

  1. 类爆炸:随着新特性的增加,你需要创建越来越多的子类来组合这些特性,这会导致类的数目急剧增加。
  2. 耦合性:子类与父类高度耦合,父类的任何改变都可能影响到子类。
  3. 违反开闭原则:继承结构通常违反开闭原则,因为添加新功能需要修改现有类。

装饰器模式通过组合(这里说的不是组合模式)而不是继承来解决这些问题,提供了更大的灵活性和可扩展性。

装饰模式(Decorator),动态地为一个对象添加额外的职责,是继承的替代方案,属于结构型模式。通过装饰模式扩展对象的功能比继承子类方式更灵活,使用继承子类的方式,是在编译时静态决定的,即编译时绑定,而且所有的子类都会继承相同的行为。然而,如果使用组合的方式扩展对象的行为,就可以在运行时动态地进行扩展,将来如果需要也可以动态的撤销,而不会影响原类的行为。

实现装饰器模式的关键在于创建一个包装类,该类持有对被装饰对象的引用,并包含被装饰对象的行为。包装类应该能够扩展被装饰对象的行为,并在需要时将其附加到被装饰对象的逻辑中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
> class Student{
> var name: String
> init(name: String) {
> self.name = name
> }
> }
>
> // 角色二: 具体的装饰者 服饰
> class StudentClothes {
> var student : Student
> var clothes: String?
> init(student: Student) {
> self.student = student
> self.clothes = "校服"
> }
> ....
> }
>
> // 实际更常用:直接将 clothes 设为 Student 的属性。因为正常情况下不用为了避免继承出的子类臃肿,而使用装饰器模式
> class DetailStudent extends Student {
> var clothes: String?
> }
>

上述代码参见:ios-装饰器模式

举个例子,如果你有一个Coffee类。但如果你想要为咖啡添加糖或奶,使用继承就不太合适,因为可能会创建出大量的子类(如MilkSugarCoffeeSugarEspresso等),这会导致类的爆炸。这时,装饰器模式就显得更为合适,你可以创建一个CoffeeDecorator类,它包裹一个Coffee对象,并在其中添加添加糖或奶的方法。

使用装饰器模式来为Coffee类添加额外的组件(比如牛奶或糖)时,代码可以按照下面的结构来实现:

附1:策略模式在Flutter中的使用代码示例

常见场景:支付(微信支付、支付宝支付、银联支付、信用卡支付)

缓存?磁盘缓存、内存缓存?

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
// 1、定义策略接口:
// 里氏替换原则:应用程序中任何父类对象出现的地方,我们都可以用其子类的对象来替换,并且可以保证原有程序的逻辑行为和正确性。因为这里父类是抽象类,所以肯定遵守里氏替换原则。
abstract class PaymentStrategy {
void pay(double amount);
}

// 2、定义具体策略:
class CreditCardPayment implements PaymentStrategy {
@override
void pay(double amount) {
print('Paying $amount using Credit Card');
}
}

class AlipayPayment implements PaymentStrategy {
@override
void pay(double amount) {
print('Paying $amount using Alipay');
}
}

class WeChatPayment implements PaymentStrategy {
@override
void pay(double amount) {
print('Paying $amount using WeChat Pay');
}
}


// 3、依赖注入
class ShoppingCart {
PaymentStrategy? _paymentStrategy; // 依赖倒置原则:ShoppingCart高层模块依赖于PaymentStrategy抽象(接口或抽象类),而不是具体实现

void setPaymentStrategy(PaymentStrategy? strategy) {
_paymentStrategy = strategy;
}

void checkout(double amount) {
_paymentStrategy?.pay(amount);
}
}

使用

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
void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Strategy Pattern Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Strategy Pattern Demo'),
);
}
}

class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, this.title}) : super(key: key);

final String title;

@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
ShoppingCart _cart = ShoppingCart();

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
child: Text('Pay with Credit Card'),
onPressed: () {
_cart.setPaymentStrategy(CreditCardPayment());
_cart.checkout(100.0);
},
),
ElevatedButton(
child: Text('Pay with Alipay'),
onPressed: () {
_cart.setPaymentStrategy(AlipayPayment());
_cart.checkout(100.0);
},
),
ElevatedButton(
child: Text('Pay with WeChat Pay'),
onPressed: () {
_cart.setPaymentStrategy(WeChatPayment());
_cart.checkout(100.0);
},
),
],
),
),
);
}
}

END

框架设计模式-⑤多代理模式

[toc]

前言

1、什么是多代理

引文①、环信SDK

用过环信SDK的同学应该对多代理不陌生了,请看下面代码:

1
2
3
4
5
6
7
8
9
/*
@method
@brief 注册一个监听对象到监听列表中
@discussion 把监听对象添加到监听列表中准备接收相应的事件
@param delegate 需要注册的监听对象
@param queue 通知监听对象时的线程
@result
*/
- (void)addDelegate:(id<EMChatManagerDelegate>)delegate delegateQueue:(dispatch_queue_t)queue;

平时我们写得比较多的代理: @property (nonatomic,weak) id<EMChatManagerDelegate>delegate; 写了上面属性后系统会默认生成set方法: - (void)setDelegate:(id<EMChatManagerDelegate>)delegate;通过对两个接口的比较就不难看出:单代理只能设置一个,而多代理可以设置多个,准确来说应该是多代理可以添加多个

以上摘自:iOS 实现多代理的方法及实例代码

引文②、XMPP

XMPP以及类似IM框架里通常会有这种需求:打开多个聊天窗口,和多个人聊天。然鹅框架底层消息转发管理器却只有一个。通常是这两个窗口都要收到消息回调,然后取自己有用的消息。。。
大概就这么个意思,我两年前用了下,具体也解释不清楚,欢迎指点交流,反正就是有一个需求需要多代理回调,这种IM框架通常有这种方法
[xmppRoom addDelegate:self delegateQueue:dispatch_get_main_queue()];

以上摘自:iOS 多代理的实现

引文③、类似IM库,当接受到消息,在几个不同地方做回调,比如改变消息数,显示小红点等

参考文章:iOS实现多重代理及应用场景

2、为什么不用NSNotificationCenter

系统不是已经有通知中心NSNotificationCenter了吗?为什么还要自己实现一个呢?

举个例子,现在我们有一个模块需要抛一个通知出来,通知其它模块用户名改变了,我们来看代码大致是怎么写的

1
2
3
4
5
6
7
8
9
10
11
12
13
// 发通知一方
NSString *const kUserNameChangedNotification = @"UserNameChangedNotification";

NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter postNotificationName:UserNameChangedNotification object:nil
userInfo:@{@"oldName":@"zhangsan","newName":"lisi"}];

// 接收通知的一方可以是
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(UserNameChanged1:)
name:kUserNameChangedNotification object:nil];
// 也可以是
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(UserNameChanged2:)
name:kUserNameChangedNotification object:nil];

从例子中可以看到有的缺点:
1.对于接收同一个事件的通知,不同的人可能会用不同的方法名来执行(例子中是UserNameChanged1UserNameChanged1),无法统一。
2.对于多参数支持不方便。

所以本文我们的优化的是统一接收方的执行方法,并为该方法提供明确的参数。

CJProtocolCenter

CJProtocolCenter.m

项目背景举例:

背景描述如下:

vc中所做的某个操作(指一个操作),想要发送给多个人,让他们接收到信息后,自己处理。

我们假设接收者为

1
2
3
4
DelegateReceivedViewModel1 *delegateReceiver1 = [[DelegateReceivedViewModel1 alloc] init];
DelegateReceivedViewModel2 *delegateReceiver2 = [[DelegateReceivedViewModel2 alloc] init];
self.delegateReceiver1 = delegateReceiver1;
self.delegateReceiver2 = delegateReceiver2;

一、正常操作

发送状态变化的信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// OneToManyDelegateNormalViewController1.h
#import "CJUIKitBaseHomeViewController.h"
#import "TSDelegate.h"

@interface OneToManyDelegateNormalViewController1 : CJUIKitBaseHomeViewController {

}
@property (nonatomic, weak) id<TSDelegate> delegate1; // 提供给外部设置
@property (nonatomic, weak) id<TSDelegate> delegate2; // 提供给外部设置

@end


// OneToManyDelegateNormalViewController1.m
loginModule.actionBlock = ^{
[self.delegate1 delegate_didUpdateLoginState:YES];
[self.delegate2 delegate_didUpdateLoginState:YES];
}

②设置代理的地方会是如下:

1
2
3
4
OneToManyDelegateNormalViewController1 *viewController = [[OneToManyDelegateNormalViewController1 alloc] init];
viewController.delegate1 = delegateReceiver1;
viewController.delegate2 = delegateReceiver2;
[self.navigationController pushViewController:viewController animated:YES];

二、优化delegate

依赖注入

依赖注入:把做的事情交给某人。至于依赖的某人有什么能力,依靠外部决定。

参考文档:

注入可以通过对象的初始化(或者可以说构造器)或者通过特性(setter),即构造器注入和setter方法注入。

OneToManyDelegateNormalMediator21

1
2
3
4
5
6
7
8
9
10
11
OneToManyDelegateNormalMediator21 *delegateMediator

// 方法1:构造器注入
- (instancetype)initWithDelegate1:(Delegate1 *)delegate1
delegate2:(Delegate2 *)delegate2;
@property (nonatomic, weak, readonly) Delegate1 *delegate1;
@property (nonatomic, weak, readonly) Delegate2 *delegate2;

// 方法2:Setter 注入
@property (nonatomic, weak) id<Delegate1> delegate1;
@property (nonatomic, weak) id<Delegate2> delegate2;

参考的其他文档:

EXTConcreteProtocol 虽然没有直接叫做依赖注入,而是叫做混合协议,但是充分使用了 OC 动态语言的特性,不侵入项目,高度自动化,框架十分轻量,使用非常简单。

1、优化思路一:从vc中抽离delegate到Mediator

为了优化代码,我们从vc中抽离delegate,则此时

①设置代理的地方会是如下:

1
2
3
4
5
6
7
OneToManyDelegateNormalMediator21 *delegateMediator = [[OneToManyDelegateNormalMediator21 alloc] init];
delegateMediator.delegate1 = self.delegateReceiver1;
delegateMediator.delegate2 = self.delegateReceiver2;

OneToManyDelegateNormalViewController21 *viewController = [[OneToManyDelegateNormalViewController21 alloc] init];
viewController.delegateMediator = delegateMediator;
[self.navigationController pushViewController:viewController animated:YES];

②发送状态变化的信息如下:

1
2
3
4
5
6
// OneToManyDelegateNormalViewController21.m
loginModule.actionBlock = ^{
//[self.delegate1 delegate_didUpdateLoginState:YES];
//[self.delegate2 delegate_didUpdateLoginState:YES];
[self.delegateMediator delegate_didUpdateLoginState:YES];
};

③此时delegateMediator中的delegate_didUpdateLoginState:方法如下:

1
2
3
4
5
6
7
8
9
10
#pragma mark - TSDelegate
- (void)delegate_didUpdateLoginState:(BOOL)loginState {
if (self.delegate1 && [self.delegate1 respondsToSelector:@selector(delegate_didUpdateLoginState:)]) {
[self.delegate1 delegate_didUpdateLoginState:YES];
}

if (self.delegate2 && [self.delegate2 respondsToSelector:@selector(delegate_didUpdateLoginState:)]) {
[self.delegate2 delegate_didUpdateLoginState:YES];
}
}

2、优化思路二:在Mediator中管理delegate数组

为了减少设置代理的变量,我们使用delegate数组,则此时

①设置代理的地方会是如下:

1
2
3
4
5
6
7
OneToManyDelegateArrayMediator31 *delegateMediator = [[OneToManyDelegateArrayMediator31 alloc] init];
[delegateMediator addDelegate:self.delegateReceiver1];
[delegateMediator addDelegate:self.delegateReceiver2];

OneToManyDelegateArrayViewController31 *viewController = [[OneToManyDelegateArrayViewController31 alloc] init];
viewController.delegateMediator = delegateMediator;
[self.navigationController pushViewController:viewController animated:YES];

②发送状态变化的信息如下:不变

③此时delegateMediator中的delegate_didUpdateLoginState:方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//  OneToManyDelegateArrayMediator31.m
_delegates = [NSPointerArray weakObjectsPointerArray];

- (void)addDelegate:(id)delegate {
[_delegates addPointer:(__bridge void*)delegate];
}

#pragma mark - TSDelegate
- (void)delegate_didUpdateLoginState:(BOOL)loginState {
for (NSUInteger i = 0; i < self.delegates.count; i++) {
id delegate = (__bridge id)[self.delegates pointerAtIndex:i];
if (delegate && [delegate respondsToSelector:@selector(delegate_didUpdateLoginState:)]) {
[delegate delegate_didUpdateLoginState:YES];
}
}
}

三、优化状态变化的发送方法

1、未优化时候:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// vc.m
loginModule.actionBlock = ^{
if (self.delegateMediator && [self.delegateMediator respondsToSelector:@selector(delegate_didUpdateLoginState:)]) {
[self.delegateMediator delegate_didUpdateLoginState:YES];
}
};

// Mediator.m
#pragma mark - TSDelegate
- (void)delegate_didUpdateLoginState:(BOOL)loginState {
for (NSUInteger i = 0; i < self.delegates.count; i++) {
id delegate = (__bridge id)[self.delegates pointerAtIndex:i];
if (delegate && [delegate respondsToSelector:@selector(delegate_didUpdateLoginState:)]) {
[delegate delegate_didUpdateLoginState:YES];
}
}
}

为了能够在Mediator中减少我们要实现每个代理的每个方法,我们进行如下优化:

2、增加个发送的封装方法,将协议要执行的方法改为参数等传进去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// vc.m
loginModule.actionBlock = ^{
[self.delegateMediator broadcastProtocol:@protocol(TSDelegate)
selector:@selector(delegate_didUpdateLoginState:)
responder:^(id<TSDelegate> delegateReceiver) {
[delegateReceiver delegate_didUpdateLoginState:YES];
}];
};

// Mediator.m
- (void)broadcastProtocol:(Protocol * _Nonnull)protocol selector:(SEL _Nullable)selector responder:(void (^_Nonnull)(id _Nonnull delegateReceiver))block {
NSAssert(protocol, @"Protocol is nil.");
NSAssert(block, @"Block is nil.");

for (NSUInteger i = 0; i < self.delegates.count; i++) {
id delegateReceiver = (__bridge id)[self.delegates pointerAtIndex:i];
if (!selector || [delegateReceiver respondsToSelector:selector]) {
block(delegateReceiver);
}
}
}

3、如果你不用上述的封装方法,你还可以利用消息转发

对Mediator对象调用delegate_didUpdateLoginState方法时,因为含delegates的Mediator对象并没有实现协议中的方法,如delegate_didUpdateLoginState,所以,我们只能要么补充实现,要么不实现的话,就利用消息转发,将协议中的方法转发到自己delegate链中的对象

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
// vc.m
loginModule.actionBlock = ^{
if (self.delegateMediator && [self.delegateMediator respondsToSelector:@selector(delegate_didUpdateLoginState:)]) {
[self.delegateMediator delegate_didUpdateLoginState:YES];
}
};

// Mediator.m
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (signature) {
return signature;
}
[_delegates addPointer:nil]; //不加上这句的话,直接调用compact,并不能清除 _delegates 数组中的 nil 对象。
[_delegates compact]; //注意 [_delegates compact],这个方法可以帮助你去掉数组里面的野指针,避免你在快速遍历的时候拿到一个指向不存在对象的地址

for (id delegate in _delegates) {
if (!delegate) {
continue;
}
signature = [delegate methodSignatureForSelector:aSelector];
if (signature) {
break;
}
}
return signature;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL selector = [anInvocation selector];
BOOL responded = NO;
for (id delegate in _delegates) {
if (delegate && [delegate respondsToSelector:selector]) {
[anInvocation invokeWithTarget:delegate];
responded = YES;
}
}
if (!responded) {
[self doesNotRecognizeSelector:selector];
}
}

此方式的问题:由于[self.delegateMediator delegate_didUpdateLoginState:YES];,所以delegateMediator需要在@interface支持,同样的因为这个支持,我们需要在.m文件中去实现它里面的方法,否则会有警告。除非说协议里的那些方法全部都是设置为了@optional

结论:基于此方式的问题,所以为了减少对其他的约束,我们这里最后采用2的优化方式。

四、发现潜在问题

1、如果一个类同时依赖两个protocal,且刚好两个protocal中有同名方法怎么办?

举例:在vc.m中想要告诉大家去实现协议1中的showMessage方法,但是如果不说明是协议1中的方法,可能有个协议2也有同样的方法,导致执行的方法错了。

分析:这种情况是可能出现的,但是这种情况出现的时候,不会被引用到同一个监听者,不然分不清delegateReceiver中现在执行的是哪一个。

如原本名字不一样,我们区分得开。

1
2
3
4
5
6
7
8
9
10
// viewModel.m
#pragma mark - TSUserDelegate
- (void)userDelegate_didUpdateLoginState:(BOOL)loginState {
NSLog(@"vm收到userDelegate:登录状态发生变化,您已%@", loginState ? @"登录" : @"登出");
}

#pragma mark - TSMessageDelegate
- (void)messageDelegate_didUpdateMessageState:(BOOL)messageState {
NSLog(@"vm收到messageDelegate:信息状态发生变化");
}

我们直接在上述原本实现的基础上看问题,修改后为:

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
// vc.m
loginModule.actionBlock = ^{
[self.delegateMediator broadcastProtocol:@protocol(TSDelegate)
selector:@selector(delegate_didUpdateLoginState:)
responder:^(id<TSDelegate> delegateReceiver) {
[delegateReceiver delegate_didUpdateLoginState:YES];
}];
};

// Mediator.m
- (void)broadcastProtocol:(Protocol * _Nonnull)protocol selector:(SEL _Nullable)selector responder:(void (^_Nonnull)(id _Nonnull delegateReceiver))block {
NSAssert(protocol, @"Protocol is nil.");
NSAssert(block, @"Block is nil.");

for (NSUInteger i = 0; i < self.delegates.count; i++) {
id delegateReceiver = (__bridge id)[self.delegates pointerAtIndex:i];

if (![delegateReceiver conformsToProtocol:protocol]) { // 处理多个protocal中有同名方法,通过传进来的protocol参数,我们在判断接收者有我们想要的方法前,先找到有实现我们想要协议的那个接收者
continue;
}

if (!selector || [delegateReceiver respondsToSelector:selector]) {
block(delegateReceiver);
}
}
}

同理:添加delegate的时候,也

1
2
3
if (![delegateReceiver conformsToProtocol:protocol]) {  // 处理多个protocal中有同名方法,通过传进来的protocol参数,我们在判断接收者有我们想要的方法前,先找到有实现我们想要协议的那个接收者
// continue or return or ...;
}

2、如何保证delegates操作的数据安全

场景:B页面发送一个信号,B页面的viewModel会去执行,当然还有其他地方也有可能执行。

那么当B发送信号出去,还在遍历执行的时候,突然某个从B页面返回A页面,B页面释放,B的viewModel也跟着释放,即这个信号的其中一个接收者B的viewModel被释放了,那么当执行的时候相当于给nil发送信息,不会崩溃。

1
2
3
4
5
6
7
8
9
10
11
for (NSUInteger i = 0; i < self.delegates.count; i++) {
id delegateReceiver = (__bridge id)[self.delegates pointerAtIndex:i];

if (![delegateReceiver conformsToProtocol:protocol]) { // 处理多个protocal中有同名方法,通过传进来的protocol参数,我们在判断接收者有我们想要的方法前,先找到有实现我们想要协议的那个接收者
continue;
}

if (!selector || [delegateReceiver respondsToSelector:selector]) {
block(delegateReceiver);
}
}

需要注意数据安全的地方,应该是避免重复添加和重复删除

所以,我们使用@synchronized来保护。

1
2
3
@synchronized(xxx) {

}

五、delegateMediator升级为中心单例

发送时候的核心代码,大概如下:

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
// vc.m
loginModule.actionBlock = ^{
[[TSNotificationCenter defaultCenter] broadcastProtocol:@protocol(TSDelegate)
selector:@selector(delegate_didUpdateLoginState:)
responder:^(id<TSDelegate> delegateReceiver) {
[delegateReceiver delegate_didUpdateLoginState:YES];
}];
};


// 该发送方法在`TSNotificationCenter.m`中的代码如下
- (void)broadcastProtocol:(Protocol * _Nonnull)protocol selector:(SEL _Nullable)selector responder:(void (^_Nonnull)(id _Nonnull delegateReceiver))block {
NSAssert(protocol, @"Protocol is nil.");
NSAssert(block, @"Block is nil.");

for (NSUInteger i = 0; i < self.delegates.count; i++) {
id delegateReceiver = (__bridge id)[self.delegates pointerAtIndex:i];

if (![delegateReceiver conformsToProtocol:protocol]) { // 处理多个protocal中有同名方法,通过传进来的protocol参数,我们在判断接收者有我们想要的方法前,先找到有实现我们想要协议的那个接收者
continue;
}

if (!selector || [delegateReceiver respondsToSelector:selector]) {
block(delegateReceiver);
}
}
}

接收时候的核心代码,大概如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// receiver.m
[[TSNotificationCenter defaultCenter] addDelegate:self.diffDelegateReceiver
forProtocol:@protocol(TSUserDelegate)];
[[TSNotificationCenter defaultCenter] addDelegate:self.diffDelegateReceiver
forProtocol:@protocol(TSMessageDelegate)];

// 该添加方法在`TSNotificationCenter.m`中的代码如下
- (void)addDelegate:(id __nonnull)listener forProtocol:(Protocol * _Nonnull )protocol {
NSAssert(listener, @"listener is nil");
NSAssert(protocol, @"Protocol is nil");
#ifdef DEBUG
NSAssert([listener conformsToProtocol:protocol], @"This listener does not conform to the protocol");
#endif

@synchronized(protocol) {
[self.hashTable addObject:listener];
}
}

说说上面我们应该用什么来管理遵守协议的类(NSHashTable)?

答:因为delegate本身为了避免循环应用,所以其是弱引用对象。而要保存弱引用对象,我们不能够用NSArray,因为NSArray是强引用,而应该用NSHashTable。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 方法1:NSPointerArray
_delegates = [NSPointerArray weakObjectsPointerArray];
- (void)addDelegate:(id)delegate {
[_delegates addPointer:(__bridge void*)delegate];
}

// 方法2:NSHashTable
_hashTable = [NSHashTable weakObjectsHashTable];
- (void)addDelegate:(id)delegate {
[_hashTable addObject:delegate];
}


@property (nonatomic, strong, readonly) NSMutableSet *listenerLists; // 保存多个NSHashTable,即其是数组的数组,里面的元素是 NSHashTable *listeners

这边用了NSHashTable来存储遵守协议的类,NSHashTable和NSSet类似,但又有所不同,总的来说有这几个特点:

NSHashTable中的元素可以通过Hashable协议来判断是否相等.

NSHashTable中的元素如果是弱引用,对象销毁后会被移除,可以避免循环引用.

附一:block 或 delegate 等弱引用对象用什么保存

如果你需要保存一堆block,并且希望它们能够响应特定的事件或操作,你通常会使用数组来存储这些block。然而,由于block在Objective-C中是对象,并且默认情况下它们会被强引用,你需要考虑循环引用的问题,特别是当block内部捕获了它们的捕获环境(例如,捕获了它们的创建者对象的强引用)时。

以下是几种处理block存储的方法:

  1. 使用__weakweak修饰符:如果你的block捕获了它们的创建者对象的引用,你可以在block内部使用__weak(在Objective-C中)或weak(在Swift中)来避免强引用循环。
  2. 使用NSHashTableNSPointerArray:如果你需要存储block的弱引用,可以使用NSHashTableNSPointerArray,并设置它们的pointerFunctions属性以使用NSPointerFunctionsWeakMemory,这样它们就会存储block的弱引用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@property (nonatomic, strong) NSPointerArray *blocks;

- (void)addBlock:(void (^)(void))block {
if (!self.blocks) {
self.blocks = [NSPointerArray weakObjectsPointerArray];
}
[self.blocks addPointer:(__bridge void *)(block ? block : [NSNull null])];
}

- (void)executeBlocks {
for (NSUInteger i = 0; i < self.blocks.count; i++) {
void *pointer = [self.blocks pointerAtIndex:i];
if (pointer != [NSNull null]) {
void (^block)(void) = (__bridge void (^)(void))pointer;
block();
}
}
}

附:不常见集合NSHashTable和NSMapTable

不常见集合NSHashTable和NSMapTable

5框架设计模式-⑦组件化.md

END

其他参考文章:

iOS 实现多代理的方法及实例代码

系统控制

[toc]

系统控制

为了统一,各视图分别使用如下组件

一、键盘遮挡问题

方案一:设置resizeToAvoidBottomPadding 为false

1
2
3
4
5
6
7
return Scaffold(
resizeToAvoidBottomPadding: false, //输入框抵住键盘
appBar: AppBar(
elevation: 0.0,
title: Text("登陆"),
),
);

方案二:在界面的body上用SingleChildScrollView包裹一下

1
2
body: SingleChildScrollView(
child:...

数据类

[toc]

数据类

为了统一,各数据类(包含宏)分别使用如下:

一、颜色

暂无

二、TextStyle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import 'package:flutter_effect/flutter_effect.dart';

return Column(
children: <Widget>[
Text(
'Bold',
style: BoldTextStyle(fontSize: 17, color: Colors.red), // bold
),
Text(
'Medium',
style: MediumTextStyle(fontSize: 17, color: Colors.red), // medium
),
],
);