# Deinit and MainActor

**URL:** <https://forums.swift.org/t/deinit-and-mainactor/50132>\
**Category:** Discussion\
**Tags:** concurrency, actors\
**Created:** [June 30, 2021, 12:10pm UTC](https://forums.swift.org/t/deinit-and-mainactor/50132 "2021-06-30T12:10:10Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![moreindirection](https://avatars.discourse-cdn.com/v4/letter/m/858c86/32.png) [@moreindirection](https://forums.swift.org/u/moreindirection)\
**Post date:** [June 30, 2021, 12:10pm UTC](https://forums.swift.org/t/deinit-and-mainactor/50132/1 "2021-06-30T12:10:10Z")

</div>

I've annotated some of my classes that should be run only on the UI thread with `@MainActor`. However, I'm running into a compiler error when I try to clean up resources in `deinit`.

Here's a stripped-down example of the error, running in Xcode 13.0 beta 2 (13A5155e):

 ![image](https://global.discourse-cdn.com/swift/original/3X/d/e/deac8bc7bc3904f82fb0b9d49e027aed64e8e5fa.png)

Is it possible to clean up resources in the `deinit` of a `@MainActor`-annotated class?

I did notice that if I inline the cleanup, then it compiles, e.g.:

```
  deinit {
    self.inputStream?.close()
    self.outputStream?.close()
}

```

But it's definitely cleaner to have one named `closeAll` method that I can use anywhere in the class, including `deinit`.

---

<div class="post-metadata">

**Author:** ![benlings](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/benlings/32/13011_2.png) [@benlings](https://forums.swift.org/u/benlings)\
**Post date:** [June 30, 2021, 10:28pm UTC](https://forums.swift.org/t/deinit-and-mainactor/50132/2 "2021-06-30T22:28:36Z")

</div>

In a class annotated with a global actor, `deinit` isn’t isolated to an actor. It can’t be because the last reference to the actor could go out of scope on any thread/task. This is noted in the [Global Actors proposal](https://github.com/apple/swift-evolution/blob/main/proposals/0316-global-actors.md).

It’s possible to use `async`/`Task.detached` to switch to the main actor, but [this is unsafe](https://bugs.swift.org/plugins/servlet/mobile#issue/SR-6942).

---

<div class="post-metadata">

**Author:** ![JJJ](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jjj/32/12193_2.png) [@JJJ](https://forums.swift.org/u/JJJ)\
**Post date:** [June 30, 2021, 11:10pm UTC](https://forums.swift.org/t/deinit-and-mainactor/50132/3 "2021-06-30T23:10:15Z")

</div>

I'm wondering whether the code invoking `deinit` could do something like this (imaginary pseudocode):

```nohighlight
guard refCnt > 0 else {
  if isSync {
    deinit()
    destroy()
    return
  } else {
    Task.detached {
      await deinit()
      destroy()
    }
    return
  }
}

```

---

<div class="post-metadata">

**Author:** ![Joe\_Groff](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/joe_groff/32/1929_2.png) [@Joe\_Groff](https://forums.swift.org/u/Joe_Groff)\
**Post date:** [July 1, 2021, 5:06pm UTC](https://forums.swift.org/t/deinit-and-mainactor/50132/4 "2021-07-01T17:06:07Z")

</div>

Keep in mind that deinit can't extend the lifetime of `self`; the object will be deallocated once the deinit completes. So if you need to trigger async work to run on a different actor from a deinit, you'll need to copy that data out of the object to pass to the new task.

---

<div class="post-metadata">

**Author:** ![moreindirection](https://avatars.discourse-cdn.com/v4/letter/m/858c86/32.png) [@moreindirection](https://forums.swift.org/u/moreindirection)\
**Post date:** [July 1, 2021, 7:15pm UTC](https://forums.swift.org/t/deinit-and-mainactor/50132/5 "2021-07-01T19:15:29Z")

</div>

I decided to abandon the idea of doing last-resort cleanup in `deinit`. Instead, I'll just count on clients of my class close any resources they don't need.

I thought I could then use `deinit` to assert that this cleanup had been done, but that leads to the same compiler error:

 ![image](https://global.discourse-cdn.com/swift/original/3X/0/9/0940d673a7dd3b716ca079a11d22c3d9c76e177a.png)

---

<div class="post-metadata">

**Author:** ![epam-gor-gyolchanyan](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/epam-gor-gyolchanyan/32/13031_2.png) [@epam-gor-gyolchanyan](https://forums.swift.org/u/epam-gor-gyolchanyan)\
**Post date:** [July 13, 2021, 4:40pm UTC](https://forums.swift.org/t/deinit-and-mainactor/50132/6 "2021-07-13T16:40:02Z")

</div>

I've hit this problem when playing with actors. A possible solution would be to allow declaring `deinit async`. I haven't though it out exhaustively, but intuitively, here's how I think it would work:  
if a type has a `deinit async` implemented, then when the reference count reaches zero an implicit call to something like `object.deinit()` is replaced by `async { await object.deinit() }`. I thought about the possibility of `await`-ing on the object if it went away in an `async` scope, but that would be inconsistent with the behavior from inside a sync scope. Regarding the "deterministic lifetime" principle: If a type defines `deinit async`, then by definition, the author realizes that there actual deinitialization happens outside of the normal control flow.

---

<div class="post-metadata">

**Author:** ![Nickolas\_Pohilets](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/nickolas_pohilets/32/10659_2.png) [@Nickolas\_Pohilets](https://forums.swift.org/u/Nickolas_Pohilets)\
**Post date:** [July 13, 2021, 5:49pm UTC](https://forums.swift.org/t/deinit-and-mainactor/50132/7 "2021-07-13T17:49:45Z")

</div>

IIRC, some UIKit classes (`UIViewController` at least) have some hackery that dispatches dealloc to main thread if last release happened from the background queue.

---

<div class="post-metadata">

**Author:** ![adamkemp](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/adamkemp/32/3017_2.png) [@adamkemp](https://forums.swift.org/u/adamkemp)\
**Post date:** [July 13, 2021, 6:47pm UTC](https://forums.swift.org/t/deinit-and-mainactor/50132/8 "2021-07-13T18:47:31Z")

</div>

> [@Nickolas\_Pohilets](#):
>
> IIRC, some UIKit classes ( `UIViewController` at least) have some hackery that dispatches dealloc to main thread if last release happened from the background queue.

That wouldn't actually work because you can't keep an object alive in `dealloc` (at least with ARC). Once `dealloc` returns the object's memory is going to be reclaimed and possibly reused. If you tried to dispatch to another thread and then use `self` you would almost certainly crash.

You could potentially dispatch to another thread and do other cleanup things as long as you don't reference `self` and retain anything else you need to use, but it's generally not a good idea, and I don't believe UIViewController does any such thing.

The general guidance for things that require asynchronous cleanup is that they should have explicit lifetime management, i.e., an explicit `stop` or `cleanup` method that the client has to call. If the object is deallocated before that method is called then that's a programmer error and may crash (some APIs deliberately crash in this situation to make the behavior more predictable).

Relying on `dealloc` or `deinit` to to do cleanup that has to be asynchronous or has to be on a particular thread is just not a safe pattern.

---

<div class="post-metadata">

**Author:** ![moreindirection](https://avatars.discourse-cdn.com/v4/letter/m/858c86/32.png) [@moreindirection](https://forums.swift.org/u/moreindirection)\
**Post date:** [July 13, 2021, 7:03pm UTC](https://forums.swift.org/t/deinit-and-mainactor/50132/9 "2021-07-13T19:03:26Z")

</div>

I guess the question I still can't resolve is: why does this compile when I inline the contents of `closeAll` into `deinit`, but raise an error if I abstract my cleanup calls into a method and then call it in `deinit`?

---

<div class="post-metadata">

**Author:** ![Nickolas\_Pohilets](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/nickolas_pohilets/32/10659_2.png) [@Nickolas\_Pohilets](https://forums.swift.org/u/Nickolas_Pohilets)\
**Post date:** [July 13, 2021, 7:58pm UTC](https://forums.swift.org/t/deinit-and-mainactor/50132/10 "2021-07-13T19:58:27Z")

</div>

Object is kept alive not in dealloc, but rather in `release`. `UIViewController` overrides `release` method. Overridden implementation is implemented in non-ARC code, and its logic can be described by the following preudocode:

```nohighlight
- (void)release {
    atomic decrement retain count
    if it was the last reference {
        if it is the main thread {
            [self dealloc];
        } else {
           // No retain, no resurrection
           // self is passed as non-retained context
           dispatch_barrier_async_f(dispatch_get_main_queue(), self, _objc_deallocOnMainThreadHelper);
        }
    }
}
void _objc_deallocOnMainThreadHelper(void *context) {
    [(id)context dealloc];
}

```

Implementation ensures that after atomic decrement reading weak references returns nil, even if `dealloc` was not called yet.

---

<div class="post-metadata">

**Author:** ![adamkemp](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/adamkemp/32/3017_2.png) [@adamkemp](https://forums.swift.org/u/adamkemp)\
**Post date:** [July 13, 2021, 8:55pm UTC](https://forums.swift.org/t/deinit-and-mainactor/50132/11 "2021-07-13T20:55:48Z")

</div>

As far as I can tell that is no longer the case and hasn't been for a while. Regardless, you can't do that _with ARC_. In Objective-C you're allowed to not use ARC if you want to hack around things, but in Swift that's not an option so there's no getting around this.

---

<div class="post-metadata">

**Author:** ![Nickolas\_Pohilets](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/nickolas_pohilets/32/10659_2.png) [@Nickolas\_Pohilets](https://forums.swift.org/u/Nickolas_Pohilets)\
**Post date:** [July 14, 2021, 10:59am UTC](https://forums.swift.org/t/deinit-and-mainactor/50132/12 "2021-07-14T10:59:41Z")

</div>

> [@adamkemp](#):
>
> As far as I can tell that is no longer the case and hasn't been for a while.

I wrote my previous message staring into assembly of the UIKitCore from iOS 14.5 Simulator. Didn’t check on device, TBH, but I think it is reasonable to expect the same behavior on device as well.

> [@adamkemp](#):
>
> Regardless, you can't do that _with ARC_

I’m not sure what you mean here. I’m trying to make a point that technically it is possible to make deinit isolated on the actor. But that would be a feature of the Swift runtime, [implemented in C++](https://github.com/apple/swift/blob/17358e4d98f1cf0c5e8b2e8fd7bb740f663e6149/stdlib/public/runtime/HeapObject.cpp#L393) (so, no ARC), supported by the compiler feature.

I’m not suggesting that @moreindirection should try to override the release method, that is indeed impossible, I’m questioning if `deinit` need to be non-isolated.

---

<div class="post-metadata">

**Author:** ![adamkemp](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/adamkemp/32/3017_2.png) [@adamkemp](https://forums.swift.org/u/adamkemp)\
**Post date:** [July 14, 2021, 4:22pm UTC](https://forums.swift.org/t/deinit-and-mainactor/50132/13 "2021-07-14T16:22:56Z")

</div>

Oh, I do see some usages of that pattern in UIKit (very well hidden), but not in UIViewController. Maybe it's somehow even more buried there. Oh well.

I'm still not convinced that's comparable to what's being proposed here. The code surrounding the usage of `_objc_deallocOnMainThreadHelper` uses manual refcounting logic such that it knows (synchronously) whether we're actually going to deallocate the object or not _before_ dispatching `dealloc` to the main queue. By the time `dealloc` is called on the main queue we've already committed to deallocating it, and the normal ARC semantics apply (i.e., you can't resurrect the object at that point, and you can't do any further asynchronous cleanup from within `dealloc`).

If you allowed `dealloc` (in ObjC) or `deinit` (in Swift) to _itself_ be asynchronous then that would require the ability to hold that object in memory for some indeterminate amount of time as it hops potentially from one thread to another. That would require further refcounting to track when it's really safe to deallocate that object. The semantics of these languages don't allow for that kind of resurrection. I've seen that in garbage-collected languages via their finalizers, but even those run _before_ the object is officially committed to being collected, and the finalizer can't be asynchronous (it has to commit synchronously to either resurrecting or not).

---

<div class="post-metadata">

**Author:** ![Nickolas\_Pohilets](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/nickolas_pohilets/32/10659_2.png) [@Nickolas\_Pohilets](https://forums.swift.org/u/Nickolas_Pohilets)\
**Post date:** [July 14, 2021, 5:21pm UTC](https://forums.swift.org/t/deinit-and-mainactor/50132/14 "2021-07-14T17:21:00Z")

</div>

I was talking about synchronous `deinit`, but isolated in the actor. This is sufficient to solve the original problem of this thread.

Asynchronous `deinit` is a different topic, but it is also possible. Resurrection is a problem, but it is completely orthogonal to asyncronousity.

It is a problem for synchronous `deinit` as well. The following code compiles without errors, but crashes in runtime:

```swift
var objects: [AnyObject] = []

class Inner {
    var name: String

    init(name: String) {
        self.name = name
    }

    deinit {
        print("Inner.deinit(): name=\(name)")
    }
}

class Test: CustomStringConvertible {
    var inner: Inner?

    init() {
        inner = Inner(name: "\(ObjectIdentifier(self))")
    }

    var description: String {
        return "Test(\(inner?.name ?? "nil"))"
    }

    deinit {
        print("Test.deinit()")
        objects.append(self)
    }
}

func foo() {
    let t = Test()
    print(t)
}

func bar() {
    foo()
    print("Now in bar()")
    for x in objects {
        print(x)
    }
    objects.removeAll()
}

bar()

```

Some options to solve this problem, both for sync and async `deinit`:

- No nothing, crash only if resurrected object is accessed (observed behaviour)
- Run `deinit` with refcount=1 and assert that it is still 1 after `deinit`
- Implement escape analysis for values other than closures and catch this in compile time.

Note the for the last solution, `closeAll()` from the original problem would need to be marked somehow to indicate that self is non-escaping. Or vice versa - assume that self is non-escaping in methods by default, and annotate escaping methods explicitly.

---

<div class="post-metadata">

**Author:** ![adamkemp](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/adamkemp/32/3017_2.png) [@adamkemp](https://forums.swift.org/u/adamkemp)\
**Post date:** [July 14, 2021, 5:42pm UTC](https://forums.swift.org/t/deinit-and-mainactor/50132/15 "2021-07-14T17:42:22Z")

</div>

> [@Nickolas\_Pohilets](#):
>
> I was talking about synchronous `deinit` , but isolated in the actor. This is sufficient to solve the original problem of this thread.

Oooh, I see. So as long as `deinit` starts on the right actor context and completes synchronously then it should be safe, and that _is_ analogous to the example with UIKit that you mentioned. You're right, I don't see any obvious flaws in that approach. That said, I'm definitely not an expert on the actor proposals.

Sorry for the derail. I saw the talk about async stuff above and thought that was what was being proposed.

---

<div class="post-metadata">

**Author:** ![Nickolas\_Pohilets](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/nickolas_pohilets/32/10659_2.png) [@Nickolas\_Pohilets](https://forums.swift.org/u/Nickolas_Pohilets)\
**Post date:** [July 15, 2021, 12:15pm UTC](https://forums.swift.org/t/deinit-and-mainactor/50132/16 "2021-07-15T12:15:01Z")

</div>

I still would be interested to hear from the core team on the subject of isolated `deinit`. After quick searching I don't see this topic being raised before (or did I miss it?). @Douglas_Gregor, @Joe_Groff, wdyt?

Would it be possible to make `deinit` isolated for actors and classes isolated in global actors? I guess implementation would involve adding a slot for the function getting a scheduler for the `deinit` to class metadata, but if wrapped into a flag new runtime still would be binary compatible with existing binaries, right?

---

<div class="post-metadata">

**Author:** ![Douglas\_Gregor](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/douglas_gregor/32/1362_2.png) [@Douglas\_Gregor](https://forums.swift.org/u/Douglas_Gregor)\
**Post date:** [July 19, 2021, 6:34pm UTC](https://forums.swift.org/t/deinit-and-mainactor/50132/17 "2021-07-19T18:34:44Z")

</div>

> [@Nickolas\_Pohilets](#):
>
> Would it be possible to make `deinit` isolated for actors and classes isolated in global actors?

As @benlings [notes](https://forums.swift.org/t/deinit-and-mainactor/50132/2), the last reference to an actor can go away anywhere---in any task---so you are not guaranteed to be on the actor. You might be in synchronous code, so you cannot "hop" over to the actor. For global-actor-qualified classes, that's the end of the story: there is no way to get to the global actor without escaping `self`, which you aren't permitted to do.

For `actor` instances, you do know that you have a unique reference to the instance. For the native actors, we could call this isolated because we know that each actor instance is independently synchronized. However, this falls apart immediately when an actor has a custom executor for the same reasons that global actors don't work: the custom executor might be shared with other actors, and you cannot synchronize appropriately with them. Similar issues issues [exist for initializers](https://forums.swift.org/t/on-actor-initializers/49001), which have a similar property of having a unique `self` (unless they escape it).

Doug

---

<div class="post-metadata">

**Author:** ![Nickolas\_Pohilets](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/nickolas_pohilets/32/10659_2.png) [@Nickolas\_Pohilets](https://forums.swift.org/u/Nickolas_Pohilets)\
**Post date:** [July 22, 2021, 11:24am UTC](https://forums.swift.org/t/deinit-and-mainactor/50132/18 "2021-07-22T11:24:06Z")

</div>

> [@Douglas\_Gregor](#):
>
> You might be in synchronous code, so you cannot "hop" over to the actor.

Would it be possible to wrap `deinit` into a top-level task when last release happens from synchronous code?

In asynchronous code, it may be possible to run `deinit` as a child task, but this would turn every release into an implicit suspension point. So, instead even for asynchronous code `release` should remain a synchronous operation which launches `deinit` in a top-level task. "Detached" task and "top-level" task are the same thing, right?

> [@Douglas\_Gregor](#):
>
> there is no way to get to the global actor without escaping `self`

Could you elaborate on this? Where would references to `self` remain after `deinit` is executed on the executor? Is it possible to create from C++ code of the runtime a job that stores `self` without retaining it?

---

<div class="post-metadata">

**Author:** ![Douglas\_Gregor](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/douglas_gregor/32/1362_2.png) [@Douglas\_Gregor](https://forums.swift.org/u/Douglas_Gregor)\
**Post date:** [July 22, 2021, 4:23pm UTC](https://forums.swift.org/t/deinit-and-mainactor/50132/19 "2021-07-22T16:23:46Z")

</div>

> [@Nickolas\_Pohilets](#):
>
> > [@Douglas\_Gregor](#):
> >
> > You might be in synchronous code, so you cannot "hop" over to the actor.
> 
> Would it be possible to wrap `deinit` into a top-level task when last release happens from synchronous code?

This is effectively escaping `self`. Remember, `deinit` is the teardown after we've already done the last release. It's reference count hit zero and must not increase again, and we can't block on this other task.

If the right semantics for your actor is to schedule a task to run later to complete teardown, capture the stored property values you need into that new task and let the `deinit` finish synchronously. But the actor instance itself has to go away.

Doug

---

<div class="post-metadata">

**Author:** ![Nickolas\_Pohilets](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/nickolas_pohilets/32/10659_2.png) [@Nickolas\_Pohilets](https://forums.swift.org/u/Nickolas_Pohilets)\
**Post date:** [July 26, 2021, 9:06am UTC](https://forums.swift.org/t/deinit-and-mainactor/50132/20 "2021-07-26T09:06:28Z")

</div>

> [@Douglas\_Gregor](#):
>
> This is effectively escaping `self`.

Escaping from what boundary?

Calling `deinit` on the scheduler de-facto makes `release` asynchronous. And `self` does not escape this asynchronous `release()`. But as I argued above, `release` should not await on `deinit`, so `deinit` must be detached and de-jure `release` remains synchronous. IMO, escaping `self` from synchronous `release` is not a problem, as a long as it does not escape from logical asynchronous `release`. Do I miss something?

> [@Douglas\_Gregor](#):
>
> Remember, `deinit` is the teardown after we've already done the last release. It's reference count hit zero and must not increase again, and we can't block on this other task.

I don't see how reference counting is related to blocking. If we can create a job that captures `self` as `unowned(unsafe)`, it should not matter if we block, await or detach. But as I argued before, it needs to be detaching. Can job capture `self` as `unowned(unsafe)` or is there something in the concurrency design that prevents this?

[Next page](https://forums.swift.org/t/deinit-and-mainactor/50132.md?page=2)
