Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

directly initialized delegate generates ARC warning and EXC_BAD_ACCESS crash

I have created a delegate object implementing the UITextFieldDelegate in its own class named NumericTextFieldDelegate then i have initialized the delegate in my controller in this way :

textFieldName.delegate = [NumericTextFieldDelegate new];

And i got this warning from the compiler :

Assigning retained object to unsafe property; object will be released after assignment

That means that the object will be released after the assignment and in fact when i run the application and i focus the UITextField i get an EXC_BAD_ACCESS and the app crash ...

The only way to make it work that i found is creating a static variable with a factory method that dispatch the instance of the NumericTextFieldDelegate :

@interface NumericTextFieldDelegate : NSObject <UITextFieldDelegate>

+(NumericTextFieldDelegate *) getDelegate;

@end

@implementation NumericTextFieldDelegate

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {

    NSString *resultingString = [textField.text stringByReplacingCharactersInRange: range withString: string];

    // This allows backspace
    if ([resultingString length] == 0) {
        return true;
    }

    NSInteger holder;
    NSScanner *scan = [NSScanner scannerWithString: resultingString];

    return [scan scanInteger: &holder] && [scan isAtEnd];
}

+(NumericTextFieldDelegate *) getDelegate {
    static NumericTextFieldDelegate *del;
    @synchronized(del) {
        if(del == nil)
            del = [NumericTextFieldDelegate new];
    }
    return del;
}

@end

And then when i assign the delegate in this way :

textFieldName.delegate = [NumericTextFieldDelegate getDelegate];

everything works well, but my question is :

Why can't i simply assign an anonymous new instance of the class ? Why the object is automatically released after the assignment ?

Why do i need this workaround ?

Thanks.

like image 518
aleroot Avatar asked May 31 '12 16:05

aleroot


3 Answers

I agree with @Inaziger analysis. The delegate of UITextField instance is a kind of weak reference. It does not hold the delegate assigned to it. According ARC, the delegate will be nil is no one hold a reference to it. Therefore, it will be up to assigner to keep it so the delegate will be called. You code prior workaround is something like this:

- (void) somemethod {
...
id<UITextFieldDelegate> tempDelegate = [NumericTextFieldDelegate new];
textFieldName.delegate = tempDelegate;
...
}

the instance of textFieldName got a reference to a delegate created locally in somethod. ARC will set temDelegate to nil after the method call. However, the text field's delegate still holds the a pointer to memory assigned to, which is released by ARC afterwards. That's why you got the bad memory access crash.

By keeping the del as static var in your class, it will be kept during your app run cycle as long as you have not set it to nil. I think it is better to keep the static del as a class level member and to provide a setter so that you should remember to release it. Something like:

// in interface definition
+(NumericTextFieldDelegate *) getDelegate;
+(void) setDelegate:(id)newDel;

// in implementation
static NumericTextFieldDelegate* del;

+(NumericTextFieldDelegate *) getDelegate {
  @synchronized(del) {
    if(del == nil)
      del = [NumericTextFieldDelegate new];
    }
  return del;
}

+(void) setDelegate:(id)newDel {
   del = newDel;
}

By the way, you can also keep your prior workaround codes as they are. You can keep the delegate in the class of text field as a class member variable or property.

@interface myTextFieldContainer () {
@proerpty (strong) id<UITextFieldDelegate> delHolder;
...
}

@implementaion myTextFieldContainer {
@sythysis delHolder = _delHodler;
...
self.delHolder = [NumericTextFieldDelegate new];
textFieldName.delegate = self.delHolder;

The benefit of above strategy is that you would not worry about release the delegate when your view controller is gone.

like image 107
David.Chu.ca Avatar answered Nov 03 '22 03:11

David.Chu.ca


The thing is, delegates in Cocoa (Touch) are normally unretained. This prevents retain cycles. But this also means that something else needs to keep a reference to the object in order to release it when you are done with it — otherwise the object is just leaked. That's just the way the delegate relationship works in this pattern.

The reason your getDelegate method works is because a reference to the delegate is stored in the static variable del, which keeps ARC from releasing the object.

like image 1
Chuck Avatar answered Nov 03 '22 03:11

Chuck


Why can't i simply assign an anonymous new instance of the class ? Why the object is automatically released after the assignment ?

Why do i need this workaround ?

You can assign a new instance of the class. But it gets immediately released because it has no strong reference - only a weak one (unsafe unretained) by textfield.delegate which is to prevent retain cycles as already mentioned. And thats exactly what the warning is telling you. I wouldn't use this singleton-like pattern however. Simply add a strong property for your delegate object and assign that property value as delegate of your textfield.

@property (nonatomic,strong) MyDelegateObject delegateObject;

Self.delegateObject = [MyDelegateObject new];
Textfield.delegate = self.delegateObject;
like image 1
Mario Avatar answered Nov 03 '22 02:11

Mario