Swift Solutions: Flyweight Pattern

Swift Solutions is a series of articles covering design patterns. In each post we discuss what the pattern is, when it applies, and how to implement it in a Swifty way.

The flyweight pattern is a memory-saving pattern used when there are many objects to be instantiated that share similarities. In this post, we will illustrate and code the flyweight pattern in Swift. After reading this article, you will know how to save memory when having to create a high volume of related objects.

Illustration

To start, let’s use the classic example of a text editor. Text editors instantiate and use all 26 letters of the alphabet repeatedly. For instance, when typing “HELLO WORLD”, we recreate the “L” character three different times. This is wasteful because we create three character objects to represent the same exact letter. The goal of the flyweight pattern is to share reusable objects instead of needlessly duplicating them, allowing our text editor to be lightweight.

We reuse objects by first dividing the object into two parts: extrinsic and intrinsic state. Extrinsic refers to the part of the object that changes based on context, and therefore cannot be shared. For example, a character may be bolded, colored, or have a larger font size. This sort of data is not reusable, since we don’t want all instances of a given character to share these attributes.

On the other hand, intrinsic data represents what remains the same across characters. An example of intrinsic data would be the shape of a given character. All repeated characters are a rendered shape, and the shape does not change from one occurrence to the next. We can use the same “L” shape every time the character appears throughout our work, and apply the extrinsic properties to it afterwards.

To summarize:

Intrinsic data is immutable, identical, context-free, and as a result, reusable.

Extrinsic data is mutable and contextual, and as a result, not reusable across all cases.

It is through this separation of intrinsic and extrinsic data that we are able to identify what we can reuse in our objects. With that in mind, let’s jump into an example with code.

Implementation

We are going to simulate an army full of infantries in our code. We could also have archers, generals, and many other types of soldiers, and we want to reuse as much as we can since each one of these soldier-entities will exist in high volume.

Swift

1

2

3

protocolSoldier{

funcrender(from location:CGPoint,tonewLocation:CGPoint)

}

First we create a Soldier protocol with a function that takes in the original location of the soldier on a grid, and the new location the soldier will move to. The goal of our code is to render infantry units on a grid as the battle progresses.

Since each soldier has a unique location, locations are considered to be extrinsic state. Flyweight objects will not store locations, but they still need to work with extrinsic data through their function inputs. More on this shortly.

Flyweight

Let’s see what our Flyweight object looks like:

Swift

1

2

3

4

5

6

7

8

9

10

11

12

13

classInfantry: Soldier{

private letmodelData:Data

init(modelData:Data){

self.modelData=modelData

}

funcrender(from location:CGPoint,tonewLocation:CGPoint){

// Remove rendering from original location

// Graphically render at new location

}

}

Our Infantry conforms to Soldier and acts as our flyweight object. As our flyweight, it stores intrinsic data in the modelData property. This dummy property contains the graphics for rendering Infantries. Since all Infantries in our army look the same, we use one model to render all of them.

Two things to note about Flyweight objects:

They contain references exclusively to intrinsic state

They must interface with extrinsic state

Since Flyweights are meant to be reused, they can only contain intrinsic data. However, we still need to render soldiers at specific locations, so instead of storing the locations in our flyweight, we store them separately in a client object and pass them into the flyweight for temporary use in rendering. This way the Flyweight can interact with extrinsic data, but never store contextual information.

Client

We are still missing a client to store all our extrinsic data in.

Swift

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

classSoldierClient{

// 1

varcurrentLocation:CGPoint

// 2

letsoldier:Soldier

init(currentLocation:CGPoint,soldier:Soldier){

self.currentLocation=currentLocation

self.soldier=soldier

}

// 3

funcmoveSoldier(tonextLocation:CGPoint){

soldier.render(from:currentLocation,to:nextLocation)

currentLocation=nextLocation

}

}

The above code accomplishes the following:

Store a CGPoint representing a soldier’s current location.

Store a reference to the flyweight object.

Create a wrapper function that passes the new location to the soldier flyweight. The soldier flyweight removes the soldier from its initial location, and redraws it at the new location. Subsequently, currentLocation is updated to reflect the new location.

All the extrinsic data is stored in the client but used by the flyweight as needed. In other words, multiple clients can reuse the flyweight to render infantries at their uniquely desired locations.

Factory

Our client refers to an object conforming to Soldier, but how do we ensure multiple clients do not create duplicate references? If all Infantry types are identical and shareable, it would defeat the purpose of the pattern if multiple infantry objects were instantiated in our app. It is necessary to make sure all clients share only one infantry object.

This is where a factory object becomes helpful:

Swift

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

classSoldierFactory{

// 1

enumSoldierType{

caseinfantry

}

// 2

private varavailableSoldiers=[SoldierType:Soldier]()

// 3

staticletsharedInstance=SoldierFactory()

private init(){}

private funccreateSoldier(of type:SoldierType)-&gt;Soldier{

switchtype{

case.infantry:

letinfantry=Infantry(modelData:Data())

availableSoldiers[type]=infantry

returninfantry

}

}

// 4

funcgetSoldier(type:SoldierType)-&gt;Soldier{

ifletsoldier=availableSoldiers[type]{

returnsoldier

}else{

letsoldier=createSoldier(of:type)

returnsoldier

}

}

}

The factory object’s job is to ensure only one concrete soldier of a given type is instantiated. If the object does not exist, it is created, added to the availableSoldiers dictionary, and returned to the caller. Next time another client needs an infantry, it will simply reuse the existing one. Here is a step-by-step breakdown:

Create an enumeration of all possible concrete soldiers. We only have one, but you can imagine more being added over time (Archers, anyone?)

Store a private dictionary that contains all instantiated soldiers.

We make sure our factory is a singleton. We want all callers to refer to the same pool of objects, so they also share a factory. For a proper treatment of singletons, please read Swift Solutions: Singleton.

Anytime a client requests a soldier, we check if it already exists in availableSoldiers. If not, we instantiate and store it in the dictionary, and then return it. All future requests for infantries are reused instead of recreated.

We first create a reference to our SoldierFactory, then we get a reference to our infantry object. Since this is the first time we are requesting an infantry, a new instance is created and stored in the factory. We then create two infantries with different locations, and proceed to move our first soldier to a different location.

Final Thoughts

There are variations of this pattern that I should mention. Some versions store the factory inside the client, and have all extrinsic state is computed. There are benefits to this approach, but for the sake of simplicity, they are beyond the scope of this article.

Instead, I gave an alternative implementation for the sake of brevity. Even though the number of clients increases with each rendered soldier, the cost is dramatically reduced since the intrinsic state is shared. The core concept of flyweight has been demonstrated.

So in the future, any time you notice the need to instantiate a large number of objects that share what can be separated as intrinsic data, reach for this pattern. Performance is important to users, and this pattern surely delivers!