DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
  1. DZone
  2. Coding
  3. Languages
  4. Stored Properties in Swift Extensions

Stored Properties in Swift Extensions

Normally you can't use stored properties in Swift Extensions, but this workaround will open up the possibilities. Learn how to achieve the dream in this walkthrough.

Marco Santarossa user avatar by
Marco Santarossa
·
Jun. 29, 17 · Tutorial
Like (1)
Save
Tweet
Share
16.68K Views

Join the DZone community and get the full member experience.

Join For Free

One of the main limitations of Swift Extensions is the impossibility to use stored properties. Let’s see a workaround to achieve our goals.

Overview

Swift Extensions allow us to add new functionality to an existing class, structure, enumeration, or protocol. We often use them in our projects, and there are moments where we would like having a way to keep the reference of some objects inside these extensions. Unfortunately, Swift doesn’t provide a straightforward way to do it. In this article, I will explain how to achieve it with the current API provided by the environment.

First of all, I will show you how I would like using the stored properties and then I will show you the workaround which we need to use. Happy reading!

The Dream

Let’s use, as an example, a protocol ToggleProtocol, which has a method toggle. Then, we let UIButton implement this protocol to change the background image depending on the toggle state:

protocol ToggleProtocol {
    func toggle()
}

enum ToggleState {
    case on
    case off
}

extension UIButton: ToggleProtocol {

    private(set) var toggleState = ToggleState.off

    func toggle() {
        toggleState = toggleState == .on ? .off : .on

        if toggleState == .on {
            // Shows background for status on
        } else {
            // Shows background for status off
        }
    }
}

Unfortunately, there is a problem. If we compile this code, the compiler will throw an error in the row of private(set) var toggleState = ToggleState.off:

error: extensions may not contain stored properties.

It means that Swift doesn’t support stored properties inside the extension. Therefore, we cannot use the toggleState property to keep the internal state of our toggle button. For this reason, we need a workaround.

The Workaround

The workaround to use stored properties inside the extensions is using the methods objc_getAssociatedObject and objc_setAssociatedObject which allow us to store an object associated with a key.

Objc_getassociatedobject

This returns the value associated with a given object for a given key.

You can find the documentation in this Apple web page.

This method wants two parameters:

  1. object: Any!: The source object for the association. We can pass self as the argument since the extension is the place where we want to manage the association.
  2. key: UnsafeRawPointer!: A pointer which is the key associated with the object to get.

Finally, this method returns the object which we want.

Objc_setassociatedobject

This sets an associated value for a given object using a given key and association policy.

You can find the documentation in this Apple web page.

This method wants four parameters:

  1. object: Any!: The source object for the association. We can pass self as the argument since the extension is the place where we want to manage the association.
  2. key: UnsafeRawPointer!: A pointer which is the key associated with the object to save.
  3. value: Any!: The object to save.
  4. policy: objc_AssociationPolicy: The policy used to save the object. It can be:
    • OBJC_ASSOCIATION_ASSIGN: It saves the object with a weak reference, in this way we don’t increase the retain count.
    • OBJC_ASSOCIATION_RETAIN_NONATOMIC: It saves the object not atomically with a strong reference.
    • OBJC_ASSOCIATION_COPY_NONATOMIC: It saves the object not atomically creating its copy.
    • OBJC_ASSOCIATION_RETAIN: It saves the object atomically with a strong reference.
    • OBJC_ASSOCIATION_COPY: It saves the object atomically creating its copy.

Note:

If you were wondering about the difference between atomic and not atomic:

  • Atomic: The object is thread-safe and always has a valid state even if a thread reads it while another one is changing its value.
  • Not Atomic: It’s the opposite of atomic. It’s not thread-safe. Of course, it’s faster since atomic has to manage a strategy to keep the object thread-safe. It should be the preferred way if you don’t have to use threads.

That’s all. Now, we can use these two methods to refactor our example used in “The Dream:”

struct AssociatedKeys {
    static var toggleState: UInt8 = 0
}

protocol ToggleProtocol {
    func toggle()
}

enum ToggleState {
    case on
    case off
}

extension UIButton: ToggleProtocol {

    private(set) var toggleState: ToggleState {
        get {
            guard let value = objc_getAssociatedObject(self, &AssociatedKeys.toggleState) as? ToggleState else {
                return .off
            }
            return value
        }
        set(newValue) {
            objc_setAssociatedObject(self, &AssociatedKeys.toggleState, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

    func toggle() {
        toggleState = toggleState == .on ? .off : .on

        if toggleState == .on {
            // Shows background for status on
        } else {
            // Shows background for status off
        }
    }
}

AssociatedKeys contains the associated keys. It means that if we had two associated keys, we may have used it like this:

struct AssociatedKeys {
    static var toggleState: UInt8 = 0
    static var anotherState: UInt8 = 0
}

extension UIButton: ToggleProtocol {
    // ...

    private(set) var anotherState: ToggleState {
        get {
            guard let value = objc_getAssociatedObject(self, &AssociatedKeys.anotherState) as? ToggleState else {
                return .off
            }
            return value
        }
        set(newValue) {
            objc_setAssociatedObject(self, &AssociatedKeys.anotherState, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

    // ...
}

Since the key must be a pointer (UnsafeRawPointer), we send the address of AssociatedKeys.toggleState with &.

We can refactor a little bit objc_getAssociatedObject using a new function with a generic object type and a default value:

func objc_getAssociatedObject<T>(_ object: Any!, _ key: UnsafeRawPointer!, defaultValue: T) -> T {
    guard let value = objc_getAssociatedObject(object, key) as? T else {
        return defaultValue
    }
    return value
}

Thanks to this method, we can change our stored properties in the example with:

private(set) var toggleState: ToggleState {
    get {
        return objc_getAssociatedObject(self, &AssociatedKeys.toggleState, defaultValue: .off)
    }
    set(newValue) {
        objc_setAssociatedObject(self, &AssociatedKeys.toggleState, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }
}

Conclusion

This workaround may seem a not-so-clean solution, but unfortunately, it’s one of the cleanest ways to manage a stored property inside an extension, since Swift doesn’t provide a more “Swifty” way to do it.

Update 6/19:

Pablo Roca provided in the comment a better approach for having a clean objc_getAssociatedObject method:

protocol PropertyStoring {

    associatedtype T

    func getAssociatedObject(_ key: UnsafeRawPointer!, defaultValue: T) -> T
}

extension PropertyStoring {
    func getAssociatedObject(_ key: UnsafeRawPointer!, defaultValue: T) -> T {
        guard let value = objc_getAssociatedObject(self, key) as? T else {
            return defaultValue
        }
        return value
    }
}

protocol ToggleProtocol {
    func toggle()
}

enum ToggleState {
    case on
    case off
}

extension UIButton: ToggleProtocol, PropertyStoring {

    typealias T = ToggleState

    private struct CustomProperties {
        static var toggleState = ToggleState.off
    }

    var toggleState: ToggleState {
        get {
            return getAssociatedObject(&CustomProperties.toggleState, defaultValue: CustomProperties.toggleState)
        }
        set {
            return objc_setAssociatedObject(self, &CustomProperties.toggleState, newValue, .OBJC_ASSOCIATION_RETAIN)
        }
    }

    func toggle() {
        toggleState = toggleState == .on ? .off : .on

        if toggleState == .on {
            // Shows background for status on
        } else {
            // Shows background for status off
        }
    }
}

let a = UIButton()
print(a.toggleState)
a.toggleState = .on
print(a.toggleState)

Thank you very much for sharing your approach.

Property (programming) Swift (programming language) Object (computer science)

Published at DZone with permission of Marco Santarossa, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Microservices 101: Transactional Outbox and Inbox
  • Shift-Left: A Developer's Pipe(line) Dream?
  • Best Navicat Alternative for Windows
  • Integrate AWS Secrets Manager in Spring Boot Application

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: