1

I would like to use dynamic properties with generic getter/setter, but it seems that in some cases ARC releases an object when it should not. I give here the simplest code to show the problem.

Code that works:

I have the class Person with one dynamic property 'name'

@interface Person : NSObject
@property (strong, nonatomic) NSString *name;
@end

In the implementation, the 2 methods have the minimal code to be able to set the person's name. I removed the getter part of the code.

@implementation Person

@dynamic name;

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
    // Setter signature for a NSString
    return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}

- (void)forwardInvocation:(NSInvocation *)invocation
{
    // Get the NSString to set
    NSString *s;
    [invocation getArgument:&s atIndex:2];

    NSLog(@"The string is: %@",s);
}

@end

In my UIViewController, I have one button:

- (IBAction)OnTestClicked:(id)sender
{
    Person *p = [[Person alloc] init];
    p.name = @"John Doe";
}

and the test works just fine. I got the Log

The string is: John Doe

If I click the button 10 times, I have 10 times the correct Log

The Problem

If the value @"John Doe" is not a string literal, the app will crash when I click the test button a second time.

Here is an example using a private property 'defaultName' that does not use a string literal.

@interface MyViewController ()
@property (strong, nonatomic) NSString *defaultName;
@end

@implementation MyViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.defaultName = [[NSString alloc] initWithFormat:@"John Doe"];
}

- (IBAction)OnTestClicked:(id)sender
{
    Person *p = [[Person alloc] init];
    p.name = self.defaultName;
}

If a click the test button more than once, I got the messages

malloc: *** error for object 0xa8257b0: pointer being freed was not allocated

or

malloc: *** error for object 0x8f68430: double free

It seems that ARC is releasing the strong property 'DefaultName'. But because I am using ARC, I cannot add a retain in the setter....

How do I solve this problem?

Thank you

PatrickV
  • 1,090
  • 2
  • 9
  • 23
  • 2
    The simple solution would be *use synthesize* with `NSObject` subclasses. (See http://stackoverflow.com/questions/1160498/synthesize-vs-dynamic-what-are-the-differences) However, I think you might be trying to do something special here? Why are you wanting to use dynamic here at all? The only purposeful use case I've seen for `dynamic` is with `NSManagedObject` subclasses. – JRG-Developer Dec 17 '13 at 22:14
  • 1
    What happens if you do `p.name = [self.defaultName copy];` – nhgrif Dec 17 '13 at 22:22
  • Yes JRG-Developer, I try to implement my own managed objects. And nhgrif, for the copy, it is not the best solution for big objects. – PatrickV Dec 17 '13 at 22:58

1 Answers1

3

Using __unsafe_unretained should solve the problem:

- (void)forwardInvocation:(NSInvocation *)invocation
{
    // Get the NSString to set
    __unsafe_unretained NSString *s;
    [invocation getArgument:&s atIndex:2];

    NSLog(@"The string is: %@",s);
}

because getArgument: just copies the argument into the given buffer, without retaining it.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382