属性未保留(ARC)

4
我有一个以表单形式呈现的模态视图控制器,它具有委托协议。我正在实现该协议并在展示之前分配一个委托,但即使该属性是强引用,它仍然会丢失。欢迎提供任何可能的原因。谢谢!
以下是该属性声明,使用了 strong 关键字。
@protocol SDStoreViewDelegate <NSObject>
// Methods
@end

@interface SDStoreViewController : UIViewController

@property (strong, nonatomic) id <SDStoreViewDelegate> delegate;

@end

这里是创建对象并设置委托的地方。

SDStoreViewController *store = [[SDStoreViewController alloc] init];
[store setDelegate:self];

NSLog(@"1 %@",store.delegate); // Returns Object as expected

[self presentViewController:store animated:YES completion:^{
    NSLog(@"3 %@",store.delegate); // Returns Object as expected
}];

NSLog(@"4 %@",store.delegate); // Returns Object as expected

这是SDStoreViewController的viewDidLoad方法。它已经失去了它的代理。
- (void)viewDidLoad
{
    [super viewDidLoad];
    NSLog(@"3 %@",_delegate); //  Returns NULL
}

这是因为在调用委托时,如果它不是NULL,则会被解除。

- (void)dismiss:(id)sender
{
    NSLog(@"5 %@",_delegate); // Returns NULL

    [self.delegate aMethod];
}

更新

我已经使用了weakstrong引用。我也使用了self.delegate_delegate来访问属性。作为一个观察点,aMethod回调没有传递,这促使我进行侦查工作以找出它在哪里丢失。


这是你正在使用的确切代码吗?我可以想象,也许你无意中展示了一个不同的视图控制器,而不是你设置委托的那个。你能否在呈现视图控制器时记录下该视图控制器本身,并在 viewDidLoad 中查看它们是否相同? - omz
@omz所说的完全正确。-viewDidLoad不可能显示null,而演示完成块显示委托。-viewDidLoad将在调用该完成块之前被调用,因此发生了其他事情(可能是您代码中的掩码变量或其他任何事情)。您需要展示您正在使用的真实、精确的代码。 - Jason Coco
这就是确切的代码。viewDidload 中的其他内容不会影响委托。如果不是一个非常奇怪的问题,我就不会在这里发帖了。 - Ryan Poolos
我相信你这里没有展示所有的东西。正如@JasonCoco所说,viewDidLoad将在presentViewController:...的完成块之前被调用,这意味着委托首先是nil,但后来以某种方式设置为正确的值,而你并没有做任何事情,只是稍后又变成了nil。顺便说一下,你应该在日志中使用不同的数字,因为现在你无法区分两个“3”日志。 - omz
这种情况是不可能发生的。你要么有一个实际上正在显示的不同的控制器实例,要么你有掩盖了 ivar 的代码(这就是为什么我们要求你呈现所有代码的原因)。如果对象在完成块中正确地打印而不是在 -viewDidLoad 中,则表示您正在使用两个不同的视图控制器对象实例或已掩盖了 ivar。如果它在完成块中打印为 nil,则可以假设它被某种方式取消设置。在 -viewDidLoad 中打印 self 的地址和控制器的地址。它们是否不同? - Jason Coco
2个回答

2

首先,delegate 属性必须始终weak 或者 unsafe_unretained,否则 ARC 无法释放彼此之间存在强指针引用的类。

@property (weak, nonatomic) id <SDStoreViewDelegate> delegate;

或者

@property (unsafe_unretained, nonatomic) id <SDStoreViewDelegate> delegate;

为什么你没有尝试通过getter访问delegate属性?像这样:

NSLog(@"4 %@",self.delegate);

替代

NSLog(@"4 %@",_delegate);

而且还有一个奖励问题:你的方法在那一行被召回了吗?

[self.delegate aMethod];

如果答案是肯定的,那么你并没有失去你的delegate,如果答案是否定的,那么你需要在其他地方设置你的delegate


5
代理通常使用弱引用以避免保留循环,这是正确的。但并非总是如此。例如,NSURLConnection 的代理被保留,因为连接可以自行决定何时释放它(当连接完成加载时)。 - omz
我通常使用弱引用委托,但这个并没有被设置为弱引用或强引用。其次,我尝试像平常一样使用self.delegate进行引用,但在发布之前尝试了_delegate。 - Ryan Poolos
同时,方法没有被调用,这就是问题的起因。我开始使用 NSLog 来检查委托引用的位置,以确定它在哪里丢失。 - Ryan Poolos
1
@RyanPoolos,你在其他地方设置了delegate属性吗?因为属性不会在没有覆盖的情况下失去它们之前的内容。你能给我们展示这五行代码的确切日志吗?重要的是日志中数字的顺序。 - holex
那是委托设置的唯一位置。而且NSLog按照它们编号的顺序触发,以相应的顺序出现在控制台中。 - Ryan Poolos
@RyanPoolos,你的控制台上有两个#3行;其中一个显示了正确的指针,另一个显示了nil,在这些行之后,你看到了带有正确指针的#4,然后出现了nil的#5...我说得对吗?看起来不太一致...你能复制/粘贴来自你的控制台的正确输出、正确的数字和值,并更新你的问题吗? - holex

0
在实现文件中添加一个set和get方法,就在synthesize函数旁边。
@synthesize delegate = _delegate;
- (id)delegate{
    return _delegate; // Add a breakpoint here
}
- (void) setDelegate:(id) delegate {
    if (_delegate != delegate){
        _delegate = delegate; // Add a breakpoint here
    }
}

还可以在 initWithCoder: 方法中设置一个断点,看看它是否正常运行。

1
只有在使用Xcode 4.3或更早版本时才需要这样做。自从Xcode 4.4以后,属性被编译器自动合成。此外,默认情况下会有一个带有下划线前缀的ivar。 - DrummerB
2
实际上,如果您像这里一样提供自己的getter和setter,就不需要@synthesize语句。 - omz
getter和setter仅用于断点和了解每个属性的含义。@synthesize方法确保创建_delegate属性,并在您删除它们时接管这些方法。 - The Lazy Coder
自动合成是一个不错的功能,但保留@synthesize方法也很重要,因为它能让你查看、引用并给其添加注释。虽然这并非必须,但如果你手动添加getter或setter方法的话,自动生成的合成就可能会失效。如果属性总是自动合成,那你该如何自定义它们呢? - The Lazy Coder

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接