Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Retain cycle #44

Closed
ZoltanKapi opened this issue Mar 2, 2023 · 6 comments
Closed

Retain cycle #44

ZoltanKapi opened this issue Mar 2, 2023 · 6 comments
Labels
enhancement New feature or request

Comments

@ZoltanKapi
Copy link

Probably, most of the users will not notice it, because the instance will be stored in a static var or something similar and will live while the app.

But this strong reference causes retain cycle.

var amplitude: Amplitude?

@ZoltanKapi
Copy link
Author

The problem is the same in EventPipeline.swift

@liuyang1520
Copy link
Collaborator

Thank you @ZoltanKapi for supporting Amplitude!

I think Timeline is obvious (will create a PR to update it), and I tried to use the debugging tool to generate the graph (https://stackoverflow.com/a/41661291), but I didn't find cycles for both Timeline and EventPipeline. I don't think we need weak for let in EventPipeline(https://krakendev.io/blog/weak-and-unowned-references-in-swift).

I am curious are you using any tool to detect this, would love to learn more.

Thank you!

@liuyang1520 liuyang1520 added the enhancement New feature or request label Mar 3, 2023
@ZoltanKapi
Copy link
Author

Thank you @liuyang1520 for the quick response!
Maybe I missed something, but if you have at least one strong reference for amplitude, that ref will prevents the deallocation.

Here is a dummy example:

class A {
    let b: B

    init(b: B) {
        self.b = b
    }

    deinit {
        print("deinit A")
    }
}

class B {
    var c: C

    init(c: C) {
        self.c = c
    }

    deinit {
        print("deinit B")
    }
}

class C {
    var a: A?

    deinit {
        print("deinit C")
    }
}

func factory() -> A {
    var c = C()
    var b = B(c: c)
    var a = A(b: b)
    c.a = a
    return a
}

var a: A? = factory()
a = nil

If you put this code snippet into a playground, you can't see any print in the console, because object C has a strong reference for object A, which will prevent the deallocation.
If you modify class C to store weak reference for A, it will solve the retain cycle.
This is a very simplified version, but the point is the same.

I think exactly for this reason it is not a good idea this design where the object graph is not clear tree without any loop. Apple's original solution for this problem is the delegate pattern. (you can use swiftlint to force that all delegate object should be stored in a weak property) Also with delegate protocols you can easily slice the code into smaller, more maintainable pieces.

@liuyang1520
Copy link
Collaborator

Thank you @ZoltanKapi for the detailed explanations!

As mentioned, I think Timeline is obvious, will update it shortly.

For EventPipeline, as Amplitude is not an optional parameter for it, I think we should keep it, references:

Thanks!

@Gray-Wind
Copy link

You still can have the init with non-optional parameter, but inside it would be weak.
If you need to re-create Amplitude, you end up with leftovers of the last one, which is not good in any situation.

The strong link to the Amplitude class must be hold in once place (user-space code).

@crleona
Copy link
Collaborator

crleona commented May 10, 2024

This should be fixed in 1.5.0

@crleona crleona closed this as completed May 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants