I have a question/issue with the new native memory...
# multiplatform
w
I have a question/issue with the new native memory manager. In the old implementation and with Swift native memory management (reference counting) if an object is never used in any thread but the main thread it will also be deallocated in the main thread. With the new memory manager this does not seem to be the case because the garbage collector runs in a separate thread and it causes issues for custom implementations of deinit which require the code to be executed on the main thread (such as UIView/UIViewController instances). What is the best practice in this regard?
h
What about @MainActor? Would this help?
w
It will result in a compile error in combination with code in deinit:
Copy code
Call to main actor-isolated instance method 'xxx' in a synchronous nonisolated context
Also deinit will still be called in a background thread
p
AFAIU, Swift itself doesn't provide any guarantees that deinit will be called on the main thread for any object, @MainActor or not. This is because you cannot know from which thread the last reference to an object will go away. So it would seem to be on you to ensure that your deinit is safe to be called on other threads than the main one.
w
You can! I always take care to never perform code casually in a background thread and certainly not capture any UI related objects there so in practice the code will always run on the main thread. I even have assertions for that in the code. Suddenly with the kotlin memory manager classes that are only used from main are suddenly deallocated in a background thread.
As I read that thread you sent though UIKit even does some hacking itself to ensure dealloc gets called on main, so they are aware of such problem themselves! It does not make sense to me to support @MainActor but not solve the dealloc problem. It would be possible to solve such a thing by retaining a @MainActor an additional time from the main thread, overriding the release method and when the retain count would be 1 (which is the reference retained from the main thread) in the next runloop on main remove that reference such that the object is deallocated on main.
Just to answer my own question, I have made critical objects extend this non-arc objective c class which ensures that dealloc will always take place on the main thread:
Copy code
@implementation BMMainThreadConfinedObject {
    volatile BOOL _deallocating;
    volatile BOOL _retained;
}

- (id)init {
    if ((self = [super init])) {
        BMEnsureMainThread();
        // retain one additional time, this will be released when the retainCount == 1
        [self retain];
        _retained = YES;
    }
    return self;
}

- (void)dealloc {
    BMEnsureMainThread();
    [super dealloc];
}

- (oneway void)release {
    NSUInteger retainCount = [self retainCount];
    [super release];
    retainCount--;
    if (retainCount == 1 && _retained && !_deallocating) {
        _deallocating = YES;
        if ([NSThread isMainThread]) {
            [super release];
        } else {
            // Create a weak reference to avoid retain being triggered
            __typeof(self) __block __self = self;
            dispatch_async(dispatch_get_main_queue(), ^{
                [__self release];
            });
        }
    }
}

- (id)retain {
    if (_deallocating) {
        return self;
    } else {
        return [super retain];
    }
}

@end