为什么我的视图控制器(通过故事板segue以模态形式呈现)在被解除后没有被释放?

3

我的视图控制器(如下代码)通过故事板连线(连接到UIButton)以模态方式呈现。然后,一旦单击动态生成的按钮之一,它将被解除。但由于某种原因,它并没有随后释放(保留计数为1)。

显然,首先需要关注的是两个对象(PPAPI和PPObjectCache),它们将此对象作为委托(在viewDidLoad中注册)。但是,如果我可以让其他任何保留该对象的东西释放它,这两个对象都使用弱引用,ARC会自动将它们置空。我已经验证了这些对象没有持有该视图控制器的强引用。

我使用Instruments(分配配置文件)检查了此对象的保留/释放,并显示了报告。如您所见,不平衡在UIKit或Foundation代码中,但很难看出具体位置或原因。

有人能发现UIKit或Foundation保留了我的视图控制器的原因吗?

#import "PPLoginViewController.h"
#import "PPAppDelegate.h"

@interface PPLoginViewController () {
    NSArray *employeeIDs;
}

@end

@implementation PPLoginViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    PPAPI *api = [PPAPI api];
    [api subscribeToViewsOfType:@"employees" delegate:self];
    [[PPObjectCache cache] subscribeToChangesToObjectsOfType:@"worker" delegate:self];
    [api callMethod:@"main.tasks.get_employees" withParameters:@{} callback:nil];
}

// PPAPI view subscription delegate method
- (void)receivedObjectIDs:(NSArray *)objectIDs forViewType:(NSString *)viewType {
    NSLog(@"%@: GOT VIEW", self);
    dispatch_async(dispatch_get_main_queue(), ^{
        employeeIDs = objectIDs;
        [self reload];
    });
}

// PPObjectCache delegate method
- (void)objectChanged:(id)object {
    dispatch_async(dispatch_get_main_queue(), ^{
        [self reload];
    });
}

- (void)reload {
    [[self.view.subviews filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
        return [evaluatedObject isKindOfClass:[UIButton class]];
    }]] makeObjectsPerformSelector:@selector(removeFromSuperview)];

    UIDevice *currentDevice = [UIDevice currentDevice];
    CGFloat y = 75.f;
    for (NSNumber *employeeID in employeeIDs) {
        NSDictionary *employee = [[PPObjectCache cache] objectOfType:@"worker" withID:employeeID];

        UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
        [button setTitle:employee[@"name"] forState:UIControlStateNormal];
        button.titleLabel.font = [UIFont boldSystemFontOfSize:18.f];
        button.tag = [employeeID integerValue];
        button.frame = CGRectMake((self.view.bounds.size.width - 240.f) / 2.f, y, 240.f, currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad ? 60.f : 45.f);
        [button addTarget:self action:@selector(login:) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:button];

        y += currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPad ? 75.f : 65.f;
    }
}

// UIButton action from above
- (void)login:(UIButton *)sender {
    PPAppDelegate *delegate = (PPAppDelegate *)[UIApplication sharedApplication].delegate;
    delegate.loggedInEmployee = [[PPObjectCache cache] objectOfType:@"worker" withID:@(sender.tag)];
    // self.logoutButton is an @property (weak) set from the presenting view controller
    self.logoutButton.title = [NSString stringWithFormat:@"Logout %@", delegate.loggedInEmployee[@"initials"]];

    [self dismissViewControllerAnimated:YES completion:nil];
}

@end

以下为分配报告(抱歉格式不佳)。您可以看到,我的应用程序代码负责的事件都是平衡的(显示为保留/释放组,或者紧随平衡调用之后)。UIKit或Foundation中的某些内容没有释放我的视图控制器!

#   Event Type   RefCt RefCt   Timestamp   Responsible Library Responsible Caller
0   Malloc  +1  1   00:19.062.502   UIKit   -[UIClassSwapper initWithCoder:]
1   Retain  +1  2   00:19.062.991   UIKit   -[UIRuntimeConnection initWithCoder:]
2   Retain  +1  3   00:19.063.036   UIKit   -[UIRuntimeConnection initWithCoder:]
3   Retain  +1  4   00:19.063.230   UIKit   UINibDecoderDecodeObjectForValue
4   Retain  +1  5   00:19.063.274   UIKit   UINibDecoderDecodeObjectForValue
5   Retain  +1  6   00:19.063.315   Foundation  -[NSObject(NSKeyValueCoding) setValue:forKey:]
6   Retain  +1  7   00:19.063.388   UIKit   -[UINib instantiateWithOwner:options:]
    Release (2) -2      00:19.063.568   UIKit   -[UINibDecoder finishDecoding]
8   Release -1  5   00:19.063.599   UIKit   -[UINibDecoder finishDecoding]
10  Release -1  3   00:19.063.722   UIKit   -[UIRuntimeConnection dealloc]
11  Release -1  2   00:19.063.755   UIKit   -[UIRuntimeConnection dealloc]
12  Retain  +1  3   00:19.063.920   UIKit   -[UIStoryboardSegue initWithIdentifier:source:destination:]
    Retain/Release (2)          00:19.067.387   Purchase    -[PPMasterViewController prepareForSegue:sender:]
15  Retain  +1  4   00:19.072.460   UIKit   -[UIViewController setChildModalViewController:]
16  Retain  +1  5   00:19.076.305   UIKit   -[UINib instantiateWithOwner:options:]
17  Retain  +1  6   00:19.076.366   UIKit   -[UINib instantiateWithOwner:options:]
18  Retain  +1  7   00:19.076.582   UIKit   -[UIProxyObject initWithCoder:]
19  Retain  +1  8   00:19.076.587   UIKit   -[UIRuntimeConnection initWithCoder:]
20  Retain  +1  9   00:19.080.167   UIKit   UINibDecoderDecodeObjectForValue
21  Retain  +1  10  00:19.080.230   UIKit   UINibDecoderDecodeObjectForValue
22  Release -1  9   00:19.080.449   UIKit   -[UINib instantiateWithOwner:options:]
23  Release -1  8   00:19.080.507   UIKit   -[UINib instantiateWithOwner:options:]
24  Release -1  7   00:19.080.578   UIKit   -[UINibDecoder finishDecoding]
    Release (2) -2      00:19.080.602   UIKit   -[UINibDecoder finishDecoding]
26  Release -1  5   00:19.080.716   UIKit   -[UIRuntimeConnection dealloc]
    Retain/Release (2)          00:19.081.788   Purchase    -[PPAPI subscribeToViewsOfType:delegate:]
    Retain/Release (2)          00:19.081.866   Purchase    -[PPObjectCache subscribeToChangesToObjectsOfType:delegate:]
32  Retain  +1  5   00:19.089.975   UIKit   -[UIWindow _setRotatableClient:toOrientation:updateStatusBar:duration:force:isRotating:]
33  Retain  +1  6   00:19.091.352   UIKit   __91-[UIWindowController transition:fromViewController:toViewController:target:didEndSelector:]_block_invoke_0238
    Retain/Release (2)          00:19.105.379   UIKit   -[UIResponder becomeFirstResponder]
36  Retain  +1  7   00:19.106.122   UIKit   -[UIViewController presentViewController:withTransition:completion:]
37  Retain  +1  8   00:19.106.142   UIKit   -[UIViewController presentViewController:withTransition:completion:]
38  Release -1  7   00:19.108.717   UIKit   _UIApplicationHandleEvent
39  Release -1  6   00:19.109.517   UIKit   -[UIStoryboardSegue dealloc]
40  Release -1  5   00:19.109.534   UIKit   _UIApplicationHandleEvent
41  Release -1  4   00:19.109.581   UIKit   -[UIStoryboardScene dealloc]
    Retain/Autorelease/Release (5)  +1      00:19.145.293   Foundation  -[NSConcreteHashTable countByEnumeratingWithState:objects:count:]
    Retain (2)  +2      00:19.151.847   Purchase    -[PPLoginViewController receivedObjectIDs:forViewType:]
    Retain (2)  +2      00:19.151.874   Purchase    __copy_helper_block_
    Release (2) -2      00:19.151.888   Purchase    -[PPLoginViewController receivedObjectIDs:forViewType:]
    Release (2) -2      00:19.278.813   Purchase    __destroy_helper_block_
48  Release -1  4   00:19.541.189   UIKit   -[UIWindowController transitionViewDidComplete:fromView:toView:removeFromView:]
    Retain/Release (2)          00:19.996.260   UIKit   -[UIViewController _dismissViewControllerWithTransition:from:completion:]
51  Release -1  3   00:19.996.269   UIKit   -[UIViewController _dismissViewControllerWithTransition:from:completion:]
52  Release -1  2   00:19.996.302   UIKit   -[UIPeripheralHost(UIKitInternal) _stopPinningInputViewsOnBehalfOfResponder:]
53  Retain  +1  3   00:19.996.776   UIKit   -[UIViewController _dismissViewControllerWithTransition:from:completion:]
54  Retain  +1  4   00:20.001.379   UIKit   __91-[UIWindowController transition:fromViewController:toViewController:target:didEndSelector:]_block_invoke_0238
55  Release -1  3   00:20.002.196   UIKit   -[UIViewController _dismissViewControllerWithTransition:from:completion:]
56  Retain  +1  4   00:20.432.653   UIKit   -[UIViewController _didFinishDismissTransition]
57  Release -1  3   00:20.432.658   UIKit   -[UIViewController setChildModalViewController:]
58  Release -1  2   00:20.432.662   UIKit   -[UIViewController _didFinishDismissTransition]
59  Release -1  1   00:20.432.706   UIKit   -[UIWindowController transitionViewDidComplete:fromView:toView:removeFromView:]
    Retain/Autorelease (2)  +1      00:20.663.794   Foundation  hashProbe

尝试在您的块中使用__block引用来引用自身。__block PPLoginViewController *bself = self; - Warren Burton
在每个块之前添加上述代码行,并将块更改为引用“bself”而不是“self”。感谢您的建议。我认为,如果像那样的东西导致了不平衡的保留(我无法想象它会如何),它将显示在上面的分配报告中。块保留已显示,但通过释放来平衡。 - jbg
是的,由于Foundation或UIKit中的某些内容保留了我的视图控制器(如Allocations报告所示),因此dealloc未被调用。我尝试从代码中删除所有块(改为使用[self performSelectorOnMainThread:@selector(reload) withObject:nil waitUntilDone:NO]),但问题仍然存在。即使在使用块时添加弱引用/强引用也没有任何区别。无论如何都没有存储块(GCD执行然后释放它们),因此在块中保留自己不会导致循环引用。请参见https://dev59.com/hlvUa4cB1Zd3GeqPvqvl - jbg
1个回答

4
这是我的愚蠢错误,正如我所预料的那样,但我会在这里回答,以防对任何人有用!
PPAPI维护了一个带有弱引用的NSHashTable,用于视图订阅,如果被订阅对象释放,它们将自动被ARC清零。当从NSHashTable获取对象时,它们似乎被Foundation保留然后自动释放。
PPAPI在后台GCD队列上的while(1)循环中访问NSHashTable(这可能是糟糕的设计)。由于while(1)循环使队列忙碌,autorelease池从未被清空,结果PPLoginViewController被Foundation保留并永远不会被释放。
解决方法是把while(1)循环的内容放到@autoreleasepool {}块中。
线索是Allocation报告的最后一行,显示Retain/Autorelease而不是Retain/Autorelease/Release。

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