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 from RACAbleSelf #40

Closed
ctsimmonds opened this issue Aug 24, 2012 · 3 comments
Closed

Retain cycle from RACAbleSelf #40

ctsimmonds opened this issue Aug 24, 2012 · 3 comments

Comments

@ctsimmonds
Copy link

Investigating some memory leaks in my application, I've found that whenever I use RACAbleSelf to subscribe for changes to a property on an object, that object is never freed. If I use a weak pointer inside the block, my object is freed, but about 60 KB of RAC infrastructure is retained somehow even though nothing is actively referencing it.

I've reproduced this with the following simple code:

//
//  main.m
//

#import <Foundation/Foundation.h>

#import <ReactiveCocoa/ReactiveCocoa.h>

@interface MyClass : NSObject

@property (nonatomic) NSString *value1;
@property (nonatomic) NSString *value2;

@end

@implementation MyClass

@synthesize value1, value2;

- (MyClass *)initWithWeak:(BOOL)isWeak
{
  self = [super init];
  if (self) {
    if (isWeak) {
      __weak __block id weakSelf = self;
      [[RACAbleSelf(value1) distinctUntilChanged] subscribeNext:^(id x) {
        NSLog(@"Using weak: subscribeNext called with x=%@", x);
        MyClass *strongSelf = weakSelf;
        strongSelf.value2 = strongSelf.value1;
      }];
    } else {
      [[RACAbleSelf(value1) distinctUntilChanged] subscribeNext:^(id x) {
        NSLog(@"Not using weak: subscribeNext called with x=%@", x);
        self.value2 = self.value1;
      }];
    }
  }
  return self;
}

@end

int main(int argc, const char * argv[])
{
  @autoreleasepool {
    // Pause for a while to give time to connect with Instruments.app
    [NSThread sleepForTimeInterval:30];

    MyClass* mc = [[MyClass alloc] initWithWeak:YES];
    mc.value2 = @"foo";
    mc.value1 = @"bar";
    mc.value1 = @"bar"; // Repeat a value to ensure distinctUntilChanged works
    mc.value1 = @"baz";

    NSLog(@"value1 = %@", mc.value1);
    NSLog(@"value2 = %@", mc.value2);

    mc = nil; // Should cause all allocated objects to be freed

    // Give time to mark another heap snapshot before exiting
    [NSThread sleepForTimeInterval:30];
  }

  return 0;
}

If I pass YES into the initializer for MyClass, its instance is freed, but it's not if I pass in NO instead. Either way, after running this, in Instruments.app I see about 60 KB of memory still allocated, including several RAC objects: 1 RACReplaySubject, 1 RACSubscribable, 1 RACSubscriber, and 2 RACDisposables.

My expectation is that when nothing references the object being subscribed to anymore, it and all of the objects created by RAC for the subscription would be cleaned up.

@joshaber
Copy link
Member

You should absolutely be using a weak/unsafe_unretained pointer to self in those blocks. Otherwise it's a text-book retain cycle.

The other objects hanging around sounds odd. I'll take a look.

@ctsimmonds
Copy link
Author

Ok, thanks.

Is the need for a weak or unsafe_unretained pointer to self documented anywhere? I only thought to try it after poking around in the list of issues and noticing it mentioned there.

Colin

On 2012-08-24, at 11:01, Josh Abernathy wrote:

You should absolutely be using a weak/unsafe_unretained pointer to self in those blocks. Otherwise it's a text-book retain cycle.

The other objects hanging around sounds odd. I'll take a look.


Reply to this email directly or view it on GitHub.

@joshaber
Copy link
Member

@ctsimmonds That PR resolves it. Thanks for bringing it up and especially for including the sample code!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants