flyweight模式(享元模式)是一种结构化的设计模式,可以最大限度地减少内存的使用和处理。
这种模式提供的对象都共享相同的底层数据,从而节省内存。它们通常是不可改变的,以使共享相同的基础数据变得简单。
享元模式有一些对象,叫做flyweights,还有一个静态方法来返回它们。
这听起来很熟悉吗?应该是的。享元模式是单例模式的一个变种。在享元模式中,你通常有多个相同类别的不同对象。一个例子是颜色的使用,你很快就会体会到这一点。你需要一种红色,一种绿色,等等。每种颜色都是一个单一的实例,共享相同的基础数据。
在你可以使用单例,但你需要多个具有不同配置的共享实例的地方使用flyweight。如果你有一个创建资源密集型的对象,而你又不能将创建过程的成本降到最低,那么最好的办法就是只创建一次对象,然后将其传递出去。
在启动器目录中打开AdvancedDesignPatterns.xcworkspace,然后点击Flyweight链接来打开页面。在这里,你将使用UIKit。Flyweights在UIKit中非常常见。UIColor、UIFont和UITableViewCell都是具有飞行权重的类的例子。
import UIKit
let red = UIColor.red
let red2 = UIColor.red
print(red === red2)
这段代码证明了UIColor使用了flyweights。用===语句比较颜色显示,每个变量都有相同的内存地址,这意味着.red是一个flyweight,并且只被实例化一次。
当然,不是所有的UIColor对象都是flyweights。在下面添加以下内容。
let color = UIColor(red: 1, green: 0, blue: 0, alpha: 1)
let color2 = UIColor(red: 1, green: 0, blue: 0, alpha: 1)
print(color === color2)
这一次,你的控制台将记录false! 自定义UIColor对象不是flyweights。这个方法需要红色、绿色和蓝色,每次调用都会返回一个新的UIColor。
如果UIColor检查这些值,看看是否已经有了一个颜色,它可以返回flyweight实例来代替。你为什么不这样做呢?用下面的代码扩展UIColor类。
以下是你的做法。
你创建了一个叫做colorStore的字典来存储RGBA值。
你写了你自己的方法,像UIColor方法一样接收红绿蓝和alpha。你把RGB值存储在一个叫做key的字符串中。如果colorStore中已经存在一个带有该键的颜色,就使用该键而不是创建一个新的。
如果key不存在于colorStore中,则创建UIColor并将其与key一起存储。
最后,在playground的末尾添加以下代码。
let flyColor = UIColor.rgba(1, 0, 0, 1)
let flyColor2 = UIColor.rgba(1, 0, 0, 1)
print(flyColor === flyColor2)
这是对扩展方法的测试。你会看到控制台打印出true,这意味着你已经成功地实现了享元模式。
在创建flyweights时,要注意你的flyweight内存的大小。如果你要存储几个flyweights,就像上面的colorStore一样,你要尽量减少内存
但你仍然可能在flyweight存储中使用过多的内存。
为了减轻这种情况,可以对你使用的内存数量设置限制,或者注册内存警告,并通过从内存中删除一些flyweights来应对。你可以使用LRU(最近使用最少的)缓存来处理这个问题。
还要注意的是,你的flyweight共享实例必须是一个类而不是一个结构。结构体使用复制语义,所以你不能获得引用类型所带来的共享底层数据的好处。
在本节中,你将创建一个名为YetiJokes的教程应用。
这是一个阅读笑话的应用程序,使用自定义字体和雪花状的一些伟大的双关语。] 就本教程而言,大部分的设置已经完成了。
打开本章目录中的 starter\YetiJokes\YetiJokes.xcodeproj,查看flyweight模式。
建立并运行。在屏幕的底部,你会看到一个有以下选项的工具栏。
这个项目的目标是使用分段控制上的按钮来改变字体的大、中、小尺寸。这些字体将被动态加载为......你猜对了,是 "重量"!"重量 "是指字体的重量。
回到Finder,你会看到在Starter目录下有两个文件夹。YetiJokes和YetiTheme。YetiTheme是一个框架,里面有一个自定义字体。
打开Starter\YetiJokes\YetiTheme\YetiTheme.xcodeproj,在应用程序的左侧菜单中选择Fonts.swift。
将该文件的内容替换为以下内容。
以下是你所做的事情。
你创建了三个flyweights,每一个都是具有不同大小的字体。
为要使用的字体文件名创建一个私有常量。
你创建了一个方法,用来加载一个给定名称的、具有一定大小的字体。
在这个守护语句中,你将字体加载为CGFont,然后用CTFontManagerRegisterGraphicsFont将其注册到应用程序中。如果字体已经被注册了,就不会再被注册。
现在它已经被注册了,你可以按名称将你的自定义字体加载为UIFont。"为什么要这样加载字体?"你可能在想。"为什么不直接把字体包含在主捆绑包中?" 是的,在应用程序的主捆绑包里有一个更简单的方法。但是,如果YetiTheme是几个应用程序之间的共享库,你可能不希望每个应用程序都把这个字体添加到主捆绑包中。在一个现实世界的例子中,你可能有很多字体,但不希望每当有新的字体加入或现有的字体发生变化时,消费的应用程序都要麻烦地加入这些字体。
如果你的框架提供有商标的字体,你甚至可能被要求对字体数据进行加密。你在这里没有这样做,但如果你需要,你可以更容易地这样做,因为字体是在一个单独的包里。
现在你可以加载字体了,关闭YetiTheme并回到YetiJokes.xcodeproj。现在是时候将这个框架实际添加到应用程序中了。
右键点击导航树的顶部,选择添加文件到 "YetiJokes"......并添加YetiTheme.xcodeproj。
一旦你添加了YetiTheme.xcodeproj,你的项目结构将看起来类似于下面。
接下来,点击YetiJokes并选择General。滚动到底部,将YetiTheme.framework添加到框架、库和嵌入式内容中,如下所示。
好酷,你可以使用YetiTheme了吗?还不行。你需要先导入该框架。
打开ViewController.swift,在文件的顶部导入框架。
import YetiTheme
接下来,你要在视图控制器加载时使用新字体。在你的IBOutlet定义下面添加以下内容。
// MARK: - View Life Cycle
public override func viewDidLoad() {
super.viewDidLoad()
textLabel.font = Fonts.small
}
这段代码将在视图初始加载时将textLabel字体设置为小的自定义字体。最后,是时候设置分段式控件了。在现有的segmentedControlValueChanged(_:)中添加以下内容。
switch sender.selectedSegmentIndex {
case 0:
textLabel.font = Fonts.small
case 1:
textLabel.font = Fonts.medium
case 2:
textLabel.font = Fonts.large
default:
textLabel.font = Fonts.small }
构建并运行该应用程序。现在你可以在字体之间快速而轻松地切换了 每种字体只被加载一次,而且字体不会被多次注册。你已经成功地减少了你的应用程序的处理和加载时间。构建并运行应用程序以验证这一功能。
你在本章中了解了享元模式。以下是它的关键点。
享元模式可以最大限度地减少内存的使用和处理。
这种模式有一些对象,称为flyweights,还有一个静态方法来返回它们。它是单例模式的一个变种。
当创建flyweights时,要注意flyweight内存的大小。如果你存储了几个flyweights,仍然有可能在flyweight存储中使用太多的内存。
flyweights的例子包括缓存对象,如图像,或保持一个对象池存储在内存中,以便快速访问。
请随意为YetiJokes添加功能,甚至改变笑话的内容;你只能用爸爸的双关语来获得这么多的笑声。
上一章 | 目录 | 下一章 |
---|