Question: memory management on Stack with Structs ?

Hi everyone,

I am a member of the RxSwiftCommunity, and I am facing an issue on a PR that I've made (https://github.com/RxSwiftCommunity/NSObject-Rx/pull/49\).

The big picture is:
I have a protocol that declares a computed var (get set) with a default implementation that uses the "objc_setAssociatedObject(self, &disposeBagContext, disposeObject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)" primitive to store and retrieve its value.
Here is the complete code:
public protocol HasDisposeBag {
    /// a unique RxSwift DisposeBag instance
    var disposeBag: DisposeBag { get set }
}
extension HasDisposeBag {
    func synchronizedBag<T>( _ action: () -> T) -> T {
        objc_sync_enter(self)
        let result = action()
        objc_sync_exit(self)
        return result
    }
    public var disposeBag: DisposeBag {
        get {
            return synchronizedBag {
                if let disposeObject = objc_getAssociatedObject(self, &disposeBagContext) as? DisposeBag {
                    return disposeObject
                }
                let disposeObject = DisposeBag()
                objc_setAssociatedObject(self, &disposeBagContext, disposeObject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
                return disposeObject
            }
        }
        set {
            synchronizedBag {
                objc_setAssociatedObject(self, &disposeBagContext, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            }
        }
    }
}

The question is: what happens when it is a struct that conforms to this protocol ?

In Swift, struct is a value type which means memory is handled in stack, not in heap, which indicate a specific life cycle and reusability (because of swift compiler optimizations). When compiler is going to reuse an instance of this Struct, what will happen with the DisposeBag computed property as it is an Object reference dynamically stored with this "objc_setAssociatedObject" primitive ?

Let's consider we create an instance of this Struct which has an associated DisposeBag. That bag is used in some RxSwift subscriptions. When the lifetime of the Struct finishes, Swift compiler can provide the same stack place to create another instance because of reusability feature. Will the already associated DisposeBag be persisted or will it be cleared for the new Struct ?

Thanks for you help.

Thibault Wittemberg.

Nothing good. It is unsafe to use objc_setAssociatedObject() with a Swift struct. For that matter you can't use objc_sync_enter/exit either.

···

On Oct 18, 2017, at 6:05 PM, Thibault Wittemberg via swift-dev <swift-dev@swift.org> wrote:

Hi everyone,

I am a member of the RxSwiftCommunity, and I am facing an issue on a PR that I've made (https://github.com/RxSwiftCommunity/NSObject-Rx/pull/49\).

The big picture is:
I have a protocol that declares a computed var (get set) with a default implementation that uses the "objc_setAssociatedObject(self, &disposeBagContext, disposeObject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)" primitive to store and retrieve its value.
Here is the complete code:
public protocol HasDisposeBag {
    /// a unique RxSwift DisposeBag instance
    var disposeBag: DisposeBag { get set }
}
extension HasDisposeBag {
    func synchronizedBag<T>( _ action: () -> T) -> T {
        objc_sync_enter(self)
        let result = action()
        objc_sync_exit(self)
        return result
    }
    public var disposeBag: DisposeBag {
        get {
            return synchronizedBag {
                if let disposeObject = objc_getAssociatedObject(self, &disposeBagContext) as? DisposeBag {
                    return disposeObject
                }
                let disposeObject = DisposeBag()
                objc_setAssociatedObject(self, &disposeBagContext, disposeObject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
                return disposeObject
            }
        }
        set {
            synchronizedBag {
                objc_setAssociatedObject(self, &disposeBagContext, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            }
        }
    }
}

The question is: what happens when it is a struct that conforms to this protocol ?

--
Greg Parker gparker@apple.com Runtime Wrangler