直接初始化的委托生成ARC警告和EXC_BAD_ACCESS崩溃

时间:2012-05-31 16:02:40

标签: objective-c delegates automatic-ref-counting

我创建了一个委托对象,在其自己的类NumericTextFieldDelegate中实现了UITextFieldDelegate,然后我以这种方式在我的控制器中初始化了委托:

textFieldName.delegate = [NumericTextFieldDelegate new];

我从编译器那里得到了这个警告:

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

这意味着该对象将在分配后释放,实际上当我运行应用程序时,我关注UITextField,我得到EXC_BAD_ACCESS并且应用程序崩溃......

使我发现的唯一方法是使用工厂方法创建一个静态变量,该方法调度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

然后当我以这种方式分配代表时:

textFieldName.delegate = [NumericTextFieldDelegate getDelegate];

一切运作良好,但我的问题是:

为什么我不能简单地分配一个匿名的新类实例? 为什么在分配后自动释放对象?

为什么我需要这种解决方法?

感谢。

4 个答案:

答案 0 :(得分:2)

我同意@Inaziger的分析。 UITextField实例的委托是一种弱引用。它不会保留分配给它的代理人。根据ARC,代表将是零,没有人持有它的参考。因此,由分配者保留它以便调用委托。您之前的代码解决方法是这样的:

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

textFieldName的实例获得了对在somethod中本地创建的委托的引用。在方法调用之后,ARC将temDelegate设置为nil。但是,文本字段的委托仍然保存指向分配给的内存的指针,然后由ARC释放。这就是为什么你的内存访问崩溃了。

通过将del保持为类中的静态var,只要您没有将其设置为nil,它就会在您的应用程序运行周期中保留。我认为最好将static del保持为类级别成员并提供一个setter,以便您应该记住释放它。类似的东西:

// 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;
}

顺便说一下,您还可以保留以前的变通方法代码。您可以将委托作为类成员变量或属性保存在文本字段的类中。

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

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

上述策略的好处是,当您的视图控制器消失时,您不必担心释放代理。

答案 1 :(得分:1)

问题是,Cocoa(Touch)中的代表通常不会被取消。这可以防止保留周期。但这也意味着其他东西需要保持对对象的引用,以便在完成它时释放它 - 否则对象就会被泄露。这就是委托关系在这种模式下的工作方式。

您的getDelegate方法工作的原因是因为对委托的引用存储在静态变量del中,这使ARC无法释放对象。

答案 2 :(得分:1)

  

为什么我不能简单地分配一个匿名的新类实例?为什么在分配后自动释放对象?

     

为什么我需要这种解决方法?

您可以指定该班级的新实例。但是它会立即释放,因为它没有强大的引用 - 只有textfield.delegate中的弱引用(不安全的未保留),这是为了防止已经提到的保留周期。这正是警告告诉你的。 但是,我不会使用这种类似单身的模式。只需为您的委托对象添加一个强大的属性,并将该属性值指定为文本字段的委托。

@property (nonatomic,strong) MyDelegateObject delegateObject;

Self.delegateObject = [MyDelegateObject new];
Textfield.delegate = self.delegateObject;

答案 3 :(得分:0)

嗯,“为什么”是因为UITextField属性delegate被声明为:

@property(nonatomic, assign) id<UITextFieldDelegate> delegate

(参见class reference。)

声明的属性assign表示“setter使用简单赋值”,因此不实现任何内存管理功能,例如retain(或取消分配时释放)。 (见The Objective-C Programming Language, Declared Properties