Chapters

Hide chapters

Design Patterns by Tutorials

Third Edition · iOS 13 · Swift 5 · Xcode 11

18. Flyweight Pattern
Written by Jay Strawn

The flyweight pattern is a structural design pattern that minimizes memory usage and processing.

This pattern provides objects that all share the same underlying data, thus saving memory. They are usually immutable to make sharing the same underlying data trivial.

The flyweight pattern has objects, called flyweights, and a static method to return them.

Does this sound familiar? It should! The flyweight pattern is a variation on the singleton pattern. In the flyweight pattern, you usually have multiple different objects of the same class. An example is the use of colors, as you will experience shortly. You need a red color, a green color and so on. Each of these colors are a single instance that share the same underlying data.

When should you use it?

Use a flyweight in places where you would use a singleton, but you need multiple shared instances with different configurations. If you have an object that’s resource intensive to create and you can’t minimize the cost of creation process, the best thing to do is create the object just once and pass it around instead.

Playground example

Open AdvancedDesignPatterns.xcworkspace in the starter directory and then click on the Flyweight link to open the page. Here, you’ll use UIKit. Flyweights are very common in UIKit. UIColor, UIFont, and UITableViewCell are all examples of classes with flyweights.

Add the following right after Code Example:

import UIKit

let red = UIColor.red
let red2 = UIColor.red
print(red === red2)

This code proves that UIColor uses flyweights. Comparing the colors with === statements shows that each variable has the same memory address, which means .red is a flyweight and is only instantiated once.

Of course, not all UIColor objects are flyweights. Add the following below:

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)

This time, your console will log false! Custom UIColor objects aren’t flyweights. This method takes red, green and blue and returns a new UIColor every time it’s called.

If UIColor checked the values to see if a color was already made, it could return flyweight instances instead. Why don’t you do that? Extend the UIColor class with the following code:

extension UIColor {
  
  // 1
  public static var colorStore: [String: UIColor] = [:]
  
  // 2
  public class func rgba(_ red: CGFloat,
                         _ green: CGFloat,
                         _ blue: CGFloat,
                         _ alpha: CGFloat) -> UIColor {
    
    let key = "\(red)\(green)\(blue)\(alpha)"
    if let color = colorStore[key] {
      return color
    }
    
    // 3
    let color = UIColor(red: red,
                        green: green,
                        blue: blue,
                        alpha: alpha)
    colorStore[key] = color
    return color
  }
}

Here’s what you did:

  1. You created a dictionary called colorStore to store RGBA values.
  2. You wrote your own method that takes red green, blue and alpha like the UIColor method. You store the RGB values in a string called key. If a color with that key already exists in colorStore, use that one instead of creating a new one.
  3. If the key does not already exist in the colorStore, create the UIColor and store it along with its key.

Lastly, add the following code to the end of the playground:

let flyColor = UIColor.rgba(1, 0, 0, 1)
let flyColor2 = UIColor.rgba(1, 0, 0, 1)
print(flyColor === flyColor2) 

This tests the extension method. You’ll see that the console prints true, which means you’ve successfully implemented the flyweight pattern!

What should you be careful about?

In creating flyweights, be careful about how big your flyweight memory grows. If you’re storing several flyweights, as in colorStore above, you minimize memory usage for the same color, but you can still use too much memory in the flyweight store.

To mitigate this, set bounds on how much memory you use or register for memory warnings and respond by removing some flyweights from memory. You could use a LRU (Least Recently Used) cache to handle this.

Also be mindful that your flyweight shared instance must be a class and not a struct. Structs use copy semantics, so you don’t get the benefits of shared underlying data that comes with reference types.

Tutorial project

Throughout this section, you’ll create a tutorial app called YetiJokes.

It’s a joke-reading app that uses custom fonts and snowcases some great puns. ;] For the purposes of this tutorial, most of the setup has been done already.

Open starter\YetiJokes\YetiJokes.xcodeproj in this chapter’s directory for the flyweight pattern.

Build and run. At the bottom of the screen, you’ll see a toolbar with the following options:

The goal of this project is to use the buttons on the segmented control to change the font to large, medium and small sizes. These fonts will be dynamically loaded as… you guessed it, flyweights!

Return to Finder and you’ll see that there are two folders in the Starter directory: YetiJokes and YetiTheme. YetiTheme is a framework with a custom font inside.

Open Starter\YetiJokes\YetiTheme\YetiTheme.xcodeproj and select Fonts.swift in the left menu of the app.

Replace the contents of the file with the following:

import Foundation

public final class Fonts {

  // 1
  public static let large = loadFont(name: fontName,
                                     size: 30.0)
  public static let medium = loadFont(name: fontName,
                                      size: 25.0)
  public static let small = loadFont(name: fontName,
                                     size: 18.0)
  
  // 2
  private static let fontName = "coolstory-regular"
  
  // 3
  private static func loadFont(name: String,
                               size: CGFloat) -> UIFont {

    if let font = UIFont(name: name, size: size) {
      return font
    }

    let bundle = Bundle(for: Fonts.self)
    
    // 4
    guard 
      let url = bundle.url(forResource: name,
                           withExtension: "ttf"),
      let fontData = NSData(contentsOf: url),
      let provider = CGDataProvider(data: fontData),
      let cgFont = CGFont(provider),
      let fontName = cgFont.postScriptName as String? else {
        preconditionFailure("Unable to load font named \(name)")
    }
    
    CTFontManagerRegisterGraphicsFont(cgFont, nil)
    
    // 5
    return UIFont(name: fontName, size: size)!
  }
}

Here’s what you’ve done:

  1. You create three flyweights, each one a font with a different size.
  2. You create a private constant for the font file name to use.
  3. You create the method that loads a font of the given name at a certain size.
  4. In this guard statement, you load the font as a CGFont, then register it to the app with CTFontManagerRegisterGraphicsFont. If the font has already been registered, it will not be registered again.
  5. Now that it’s registered, you can load your custom font as a UIFont by name.

“Why load a font this way?” you may be thinking. “Why not just include the font in the main bundle?”

Yes, there’s an easier way of doing this in the app’s main bundle. However, if YetiTheme is a shared library between several apps, you may not want each app to add this font to the main bundle. In a real-world example, you may have many fonts and don’t want consuming apps to have the hassle of adding them whenever new ones are added or existing fonts changes.

If your framework provides trademarked fonts, you could even be required to encrypt the font data. You aren’t doing this here, but if you needed to, you could more easily do so since the font is in a separate bundle.

Now that you can load fonts, close YetiTheme and go back to YetiJokes.xcodeproj. It’s time to actually add this framework to the app.

Right-click on the top of the navigation tree and select Add files to “YetiJokes”… and add YetiTheme.xcodeproj:

Once you’ve added YetiTheme.xcodeproj, your project structure will look similar to the following:

Next, click on YetiJokes and select General. Scroll to the bottom and add YetiTheme.framework to Frameworks, Libraries and Embedded Content as shown below.

OK cool, can you use YetiTheme yet? Not yet-i! You need to import the framework first.

Open ViewController.swift and import the Framework at the top of the file:

import YetiTheme

Next, you’ll want to use the new font when the View controller loads. Add the following below your IBOutlet definitions:

// MARK: - View Life Cycle
public override func viewDidLoad() {
  super.viewDidLoad()

  textLabel.font = Fonts.small
}

This code will set the textLabel font to the small custom font on the views initial load. Finally, time to set up the segmented control. Add the following to the existing 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
}

Build and run the app. You can now switch between fonts quickly and easily! Each font is only loaded once, and the font won’t be registered more than once. You’ve successfully cut back on processing and load times in your app. Build and run the app to verify this functionality.

Key points

You learned about the flyweight pattern in this chapter. Here are its key points:

  • The flyweight pattern minimizes memory usage and processing.

  • This pattern has objects, called flyweights, and a static method to return them. It’s a variation on the singleton pattern.

  • When creating flyweights, be careful about the size of your flyweight memory. If you’re storing several flyweights, it’s still possible to use too much memory in the flyweight store.

  • Examples of flyweights include caching objects such as images, or keeping a pool of objects stored in memory for quick access.

Feel free to add functionality to YetiJokes and even change up the jokes; you can only get so many laughs with dad puns!

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.