【问题标题】:How to set an ivar in a block without creating a retain cycle如何在不创建保留周期的情况下在块中设置 ivar
【发布时间】:2014-01-19 09:14:39
【问题描述】:

我正在尝试在 JavascriptCore 块中启动一个名为 FormObjectNSObject 子类。 FormObject 应该是 nil,直到我将它设置在 JavascriptCore 块中。我需要在这个块中设置它,因为在我显示UIActionSheet 之后,我将这个FormObject 保存在UIActionSheetDelegate 方法- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex

代码如下:

- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
    if (actionSheet.tag == kSaveFormConfirmationActionSheetTag) {
        if ([ButtonTitle isEqualToString:NSLocalizedString(@"Yes", @"Yes")]) {
            NSLog(@"Saving form data");
            NSLog(@"%@",formData);
            NSLog(@"%@",formData.dictionary);
            if (formData) {
                [[AutoFillManager defaultManager] saveFormData:formData];
                formData = nil;
            }
        }
        else if ([ButtonTitle isEqualToString:NSLocalizedString(@"Never for this Website", @"Never for this Website")]) {
            NSString *formURLString = [formData.urlString copy];
            formData = nil;
            formData = [[FormObject alloc] initWithDisabledSite:formURLString];
            [[AutoFillManager defaultManager] saveFormData:formData];
            formData = nil;

        }
        else {
            self.formData = nil;
        }
    }
}



- (JSContext *)getCurrentJavascriptContext {
    JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

    NSString *scriptPath = [[NSBundle mainBundle] pathForResource:@"JSTools" ofType:@"js"];
    NSString *scriptString = [NSString stringWithContentsOfFile:scriptPath encoding:NSUTF8StringEncoding error:nil];
    [context evaluateScript:scriptString];
    context[@"print"] = ^(NSString *string) {
        NSLog(@"%@",string);
    };

    __weak AutoFillManager *afm = [AutoFillManager defaultManager];
    __weak UIToolbar *weakToolbar = toolbar;
    __weak typeof(self) weakSelf = self;

    context[@"receiveForm"] = ^(NSString *urlString, NSString *username, NSString *usernameFieldID, NSString *usernameFieldName, NSString *password, NSString *passwordFieldID, NSString *passwordFieldName) {

        NSURL *url = [NSURL URLWithString:urlString];

        for (NSDictionary *savedForm in [afm savedForms]) {
            FormObject *form = [[FormObject alloc] initWithFormDictionary:savedForm];
            if ([[url host] isEqualToString:form.urlString] && [username isEqualToString:form.username] && form.neverForThisSite == NO) {
#ifdef DEBUG
                NSLog(@"Site already saved");
#endif
                return;
            }

            if ([[url host] isEqualToString:form.urlString] && form.neverForThisSite == YES) {
#ifdef DEBUG
                NSLog(@"Never for this site");
#endif
                return;
            }
        }

        formData = [[FormObject alloc] initWithUsername:username withUsernameID:usernameFieldID withUsernameName:usernameFieldName withPassword:password withPasswordID:passwordFieldID withPasswordName:passwordFieldName withURLString:[url host]];
        UIActionSheet *saveFormConfirmationActionSheet = [[UIActionSheet alloc] initWithTitle:[NSString stringWithFormat:NSLocalizedString(@"Would you like to save this password?", @"Would you like to save this password?")] delegate:weakSelf cancelButtonTitle:NSLocalizedString(@"Not Now", @"Not Now") destructiveButtonTitle:nil otherButtonTitles:NSLocalizedString(@"Yes", @"Yes"), NSLocalizedString(@"Never for this Website", @"Never for this Website"), nil];
        saveFormConfirmationActionSheet.tag = kSaveFormConfirmationActionSheetTag;
        [saveFormConfirmationActionSheet showFromToolbar:weakToolbar];
    };

    return context;
}

我知道问题是因为

formData = [[FormObject alloc] initWithUsername:username withUsernameID:usernameFieldID withUsernameName:usernameFieldName withPassword:password withPasswordID:passwordFieldID withPasswordName:passwordFieldName withURLString:[url host]];

如果我添加说明符__weak,Xcode 会警告我将其更改为__block。如果我将其更改为__block,那么之后formData 仍将是nil

如何正确设置此块中的 ivar?

解决方案

解决办法是把ivar变成一个属性,用weak self来设置属性。

- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
    if ([ButtonTitle isEqualToString:NSLocalizedString(@"Never for this Website", @"Never for this Website")]) {
            NSString *formURLString = [self.formData.urlString copy];
            self.formData = nil;
            self.formData = [[FormObject alloc] initWithDisabledSite:formURLString];
            [[AutoFillManager defaultManager] saveFormData:self.formData];
            self.formData = nil;

        }
        else {
            self.formData = nil;
        }
    }
}


- (JSContext *)getCurrentJavascriptContext {
    JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

    NSString *scriptPath = [[NSBundle mainBundle] pathForResource:@"JSTools" ofType:@"js"];
    NSString *scriptString = [NSString stringWithContentsOfFile:scriptPath encoding:NSUTF8StringEncoding error:nil];
    [context evaluateScript:scriptString];
    context[@"print"] = ^(NSString *string) {
        NSLog(@"%@",string);
    };

    __weak AutoFillManager *afm = [AutoFillManager defaultManager];
    __weak UIToolbar *weakToolbar = toolbar;
    __weak typeof(self) weakSelf = self;

    context[@"receiveForm"] = ^(NSString *urlString, NSString *username, NSString *usernameFieldID, NSString *usernameFieldName, NSString *password, NSString *passwordFieldID, NSString *passwordFieldName) {

        NSURL *url = [NSURL URLWithString:urlString];

        for (NSDictionary *savedForm in [afm savedForms]) {
            FormObject *form = [[FormObject alloc] initWithFormDictionary:savedForm];
            if ([[url host] isEqualToString:form.urlString] && [username isEqualToString:form.username] && form.neverForThisSite == NO) {
#ifdef DEBUG
                NSLog(@"Site already saved");
#endif
                return;
            }

            if ([[url host] isEqualToString:form.urlString] && form.neverForThisSite == YES) {
#ifdef DEBUG
                NSLog(@"Never for this site");
#endif
                return;
            }
        }

        weakSelf.formData = [[FormObject alloc] initWithUsername:username withUsernameID:usernameFieldID withUsernameName:usernameFieldName withPassword:password withPasswordID:passwordFieldID withPasswordName:passwordFieldName withURLString:[url host]];
        UIActionSheet *saveFormConfirmationActionSheet = [[UIActionSheet alloc] initWithTitle:[NSString stringWithFormat:NSLocalizedString(@"Would you like to save this password?", @"Would you like to save this password?")] delegate:weakSelf cancelButtonTitle:NSLocalizedString(@"Not Now", @"Not Now") destructiveButtonTitle:nil otherButtonTitles:NSLocalizedString(@"Yes", @"Yes"), NSLocalizedString(@"Never for this Website", @"Never for this Website"), nil];
        saveFormConfirmationActionSheet.tag = kSaveFormConfirmationActionSheetTag;
        [saveFormConfirmationActionSheet showFromToolbar:weakToolbar];
    };

    return context;
}

【问题讨论】:

  • 只有在块内使用self 时才会创建保留周期。为什么每个人都害怕在块中保留循环作为火?
  • 没有。我使用了工具,并将这个 ivar 设置为 nil 会导致保留周期。
  • @Cy-4AH 不是真的,保留周期会使您的应用崩溃,因此我们必须认真处理。
  • 我认为您必须显示更多代码。
  • 您确实需要像 Martin 所说的那样显示更多代码。你说过如果你尝试一些东西,Xcode 会给你各种警告/错误 - 显示你尝试过的代码和 Xcode 给出的消息。您的问题的标准解决方案是使用弱引用,您说 Xcode 不会让您这样做 - 人们需要查看您的代码以找出原因。

标签: ios objective-c objective-c-blocks ivar


【解决方案1】:

访问块内的实例变量将导致块捕获对拥有该实例变量的对象的引用 - self

您可以通过将实例变量转换为属性、创建对 self 的弱引用并将属性设置在弱 self 上来避免这种情况。

您也可以使用-> 直接访问ivar,同样是在弱自我上,但我更喜欢前一种解决方案。

【讨论】:

  • 似乎我们还必须保持对弱引用的本地强引用,这样它就不会被随机删除。我们应该在哪里这样做?在块的第一行?阻止之前,到主要对象__strong __typeof(_self)strongSelf = weakSelf;
  • 只有在您认为有可能发生或您关心的情况下,您才需要这样做。在大多数用例中,如果有问题的对象已被释放,那么您就不会被打扰 - 它通常是一个视图控制器被删除或其他东西。在这种情况下,做一个强有力的参考是很混乱的。
  • ...而且在任何情况下,永远不要先于阻止!这将重新引入保留周期。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-08
相关资源
最近更新 更多