# Swift3.0学习系列之Swift实战-享元模式

语言：zh-CN
原文：[Swift3.0学习系列之Swift实战-享元模式](<https://allluckly.cn/%E6%8A%95%E7%A8%BF/tougao66>)
中文：[中文 HTML](<https://allluckly.cn/%E6%8A%95%E7%A8%BF/tougao66>)
English: [English HTML](<https://allluckly.cn/en/journal/2016-10-13-swift-3-0-design-patterns-flyweight/>)
发布于: 2016-10-13T00:00:00+08:00
内容更新于: 2016-10-13T00:00:00+08:00
署名：Sindri

以共享本地数据管理对象为例介绍享元模式，使用字典复用已加载的数据并记录使用次数，在无人使用时保存和释放，讨论它与始终保留单例对象的差别。

原文署名Sindri，Bison 编辑收录。

[原文所列来源](<http://www.jianshu.com/p/25543ae95293>)

**阅读前** 这是 2015–2017 年间的技术记录，本次整理未重新运行验证示例。标题明确面向 Swift 3.0 的设计模式学习。

![1](<https://allluckly.cn/images/blog/tuogao/tougao66/1.png>)

编辑：[Bison](<https://allluckly.cn/>)  
 来源：[Sindri的小巢](<http://www.jianshu.com/p/25543ae95293>)

设计模式（Design pattern）是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。GoF提出了23种设计模式，本系列将使用Swift语言来实现这些设计模式

## 概述

通过共享已存在的对象，减少创建对象内存开销的设计模式被称作享元模式

享元模式Flyweight和单例模式Singleton像是一对孪生兄弟，二者的表现方式非常相似，但二者的存在目的却不一样：

- 单例模式

保证了整个应用声明周期内，同一个对象只会存在一份内存，并且任何时间都能被访问使用。 ![1](<https://allluckly.cn/images/blog/tuogao/tougao66/2.jpg>)

- 享元模式

如果存在可以复用的对象，那么对象将被共享而不是创建新的对象

![1](<https://allluckly.cn/images/blog/tuogao/tougao66/3.jpg>)

在iOS开发中，享元模式的最佳实践就是UITableView的复用机制——超出屏幕外的单元格统一被回收放到一个复用队列之中，等待着需要新的单元格时进行复用。

## 实战

笔者最近项目有一个需求，几乎所有的数据都要保存在本地。由于应用的特殊性，模块之间需要用到彼此的数据，如果使用单例模式来做，那么同一时间占用的内存是非常的大的，因此以享元模式的思想封装了一个数据管理类：

```
class DataManager {
    //MARK: - Variable
    private static var shareStorage = [String: AnyObject]()
    private var storeKey = "DefaultKey"
    private var storeData = [AnyObject]()
    var data: [AnyObject] {
        get {
            return storeData
        }
    }

    //MARK: - Operate
    func insert(data: AnyObject) { }
    func delete(at index: Int) { }
    func save() { }
}
```

笔者以数据模型的类名作为数据管理的关键字，因此创建一个私有的静态字典用来保存当前正在使用的数据。由于数据以加密的方式存储在沙盒目录下，在数据量足够大的时候，从本地读取这些数据会占用大量的花销，因此在数据管理对象被创建的时候需要判断是否存在可复用的数据，如果不存在再从本地加载：

```
class DataManager {
    init() {
        initalizeData()
    }

    init(storeKey: String) {
        self.storeKey = storeKey
        initalizeData()
    }

    private func initalizeData() {
        if let data = DataManager.shareStorage[storeKey] {
            storeData = data as! [AnyObject]
        } else {
            loadData()
            DataManager.shareStorage[storeKey] = storeData as AnyObject?
        }
    }

    private func loadData() {
        //  load data from local path
    }
}
```

ok，对于数据的复用已经完成了，剩下的问题是不可能让字典一直存储这些数据，否则直接使用单例要更加方便的多。对此，笔者使用了计数功能，保证数据可以在没有使用的时候进行本地存储然后释放：

class DataManager {

```
deinit {
    let count = DataManager.shareStorage[countKey()] as! Int
    if count == 1 {
        save()
        DataManager.shareStorage[storeKey] = nil
    } else {
        DataManager.shareStorage[countKey()] = (count - 1) as AnyObject?
    }
}

private func initalizeData() {
    if let data = DataManager.shareStorage[storeKey] {
        let count = DataManager.shareStorage[countKey()] as! Int
        DataManager.shareStorage[countKey()] = (count + 1) as AnyObject?
        storeData = data as! [AnyObject]
    } else {
        loadData()
        DataManager.shareStorage[countKey()] = 1 as AnyObject
        DataManager.shareStorage[storeKey] = storeData as AnyObject?
    }
}

private func countKey() -> String {
    return "\(storeKey)Count"
} }
```

上面的代码是初步的逻辑搭建，下一步还需要考虑线程安全等其他问题，这里就不再写出来了

## 总结

最开始接触享元模式概念的时候，笔者是有些混乱的，也不清楚它和单例的区别。简单来说，这是一种提供了一种拥有单例优点、以及改善了一部分单例缺点的设计模式，但是享元模式更加的复杂，在考虑到多线程的环境下，数据竞争要比单例激烈的多，也危险的多。

---

好文推荐：[分分钟解决iOS开发中App启动动画广告的功能](<https://allluckly.cn/lblaunchimagead/LBLaunchImageAd>)

