@weakOuter on anonymous classes (or lambdas)

Skip to first unread message

Jindrich Sarson

Jul 13, 2021, 8:45:07 AMJul 13
to j2objc-discuss

there were discussion few years ago about memory management in anonymous inner classes or lambdas. The problem is, that anonymous class strong references outer class and it is very easy to make retain cycle - especially when event handlers are members of this class.

There were discussion to make all anonymous inner classes / lambdas as __weak (see https://github.com/google/j2objc/issues/1029#issuecomment-462059013) or at least support @WeakOuter in anonymous class declaration, like:

Runnable r = new @Foo Runnable() { … };

I don't know, if anything was done regarding this already and what is best practice to write lanbdas, that will not make retain cycles.



Yegor Kurbachev

Jul 13, 2021, 8:58:39 AMJul 13
to j2objc-discuss

I don't know whether you've seen my question initially or not.

What I ended up doing is just locally fixing j2objc myself:
— package com.google.devtools.j2objc.util;
— class CaptureInfo, function addMethodReferenceReceiver(TypeElement type, TypeMirror receiverType)
— ADD TO capture.field BUILDER: .setIsWeak(true) at the end

This works PERFECTLY in our quite large application — https://apps.apple.com/il/app/air-doctor/id1208899318 — memory issues just disappeared completely!

I would LOVE to see it being a part of the official distribution (at least as a command line argument for the compiler).

P.S.: YES we do get exceptions here and there with event handler within event handler within event handler (in our case only occurs when a button opens a popup and its Ok handler opens another popup, which has an Ok handler, which is released too early — i.e. very rare case); found and fixed in no time (just create the inner-most lambda outside the inner lambda), as we find them in the very basic sanity checks of the system.

Tom Ball

Jul 13, 2021, 1:15:10 PMJul 13
to j2objc-...@googlegroups.com
Yegor: does your project generate ARC code? If it does, or if you compile with -fobjc-weak (as the j2objcc script does), then weak references are zeroed (made nil) when the object they refer to is deallocated. Since Objective-C nil references respond to messages, that means your program won't crash when a deallocated weak reference is invoked, but it still won't necessarily work correctly (nil references always return zero, so BOOL returns are false, double returns are 0.0, object returns are nil, etc.) 

I think the real issue is that the cycle_finder tool can't differentiate between a true reference cycle and a "cycle exists until state changes" condition. With capturing method references, the captured items have to be strongly assigned as long as the method reference is alive -- to free them, the method reference must go out of scope. With normal lambda use, the life of a lambda is short and then released (such as in a Promise or Future). However, method references are often retained indefinitely (maybe saved to a static variable, like regex patterns). Until those method references are freed, the variables they captured have to be alive, or else the reference won't behave correctly.

Jindrich: the reason j2objc's Weak annotation doesn't include the TYPE_USE target is because many Android projects build with Java 7, where it isn't defined. If we add a j2objc_annotations_java7.jar, we're sure to get lots of support complaints about the newer versions breaking builds (look how few developers found where the guava libraries went!), but perhaps it's time to encourage them to upgrade.

You received this message because you are subscribed to the Google Groups "j2objc-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to j2objc-discus...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/j2objc-discuss/76dc1807-7a7b-4f17-ba4c-0acd73eb62b5n%40googlegroups.com.

Yegor Kurbachev

Jul 13, 2021, 1:45:28 PMJul 13
to j2objc-discuss
Yes, we use ARC code. It fails in nil_chk macro.

The cycle is real — there is no issues with cycle_finder — and it won't solve itself when the state changes:
— Take Form class.
— It contains a button, so it references it.
— Button is being assigned a listener, which is an inner class of the Form. Button obviously calls the listener, so it references it.
— Listener is an inner class of the Form, so (default behaviour) it references the form.

Effectively default behaviour forces us to choose between the following options:
1. Define all listener classes to have a @WeakOuter annotation.
2. Remove all listeners, when form is ready to be released.
3. Don't hold reference to buttons, instead use "findById" (or similar) every time it's needed.
All of these choices are extremely easy to forget and are extremely inconvenient.

Most developers don't even develop for iOS, they develop generic code, which runs in Android and Web (GWT) and usually debug in Web — so they won't even notice that something is wrong...

I know that the default implementation is way safer — but its memory management is just unmanageable for us.   :(

Reply all
Reply to author
0 new messages