UIAlertView 具有用户提供的上下文和 [self autorelease]

Posted

技术标签:

【中文标题】UIAlertView 具有用户提供的上下文和 [self autorelease]【英文标题】:UIAlertView with a user supplied context and [self autorelease] 【发布时间】:2012-04-24 18:55:39 【问题描述】:

我已经查看了一些关于如何为 UIAlertView 提供上下文的想法。常见的答案是将其保存在字典或 UIAlertView 的子类中。我不喜欢将上下文保存在字典中的想法,这是数据的错误位置。 Apple 不支持子类化 UIAlertView,因此按照我的标准,这不是一个好的解决方案。

我想出了一个主意,但我不知道该怎么做。创建一个上下文对象的实例,它是 UIAlertView 的委托。反过来,警报视图上下文有它自己的委托,即视图控制器。

问题在于释放内存。我将 alertView.delegate 设置为 nil 并调用 [self autorelease] 以释放 -alertView:didDismissWithButtonIndex: 中的上下文对象。

问题是:我给自己造成了什么问题?我怀疑我正在为一个微妙的记忆错误做好准备。

这里是简单版本,只支持 -alertView:clickedButtonAtIndex:

使用

- (void)askUserIfTheyWantToSeeRemoteNotification:(NSDictionary *)userInfo

    [[[[UIAlertView alloc] initWithTitle:[userInfo valueForKey:@"action"]
                                 message:[userInfo valueForKeyPath:@"aps.alert"]
                                delegate:[[WantAlertViewContext alloc] initWithDelegate:self context:userInfo]
                       cancelButtonTitle:@"Dismiss"
                       otherButtonTitles:@"View", nil] autorelease] show];

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex withContext:(id)context

    if (buttonIndex != alertView.cancelButtonIndex)
        [self presentViewForRemoteNotification:context];

界面

@protocol WantAlertViewContextDelegate <NSObject>
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex withContext:(id)context;
@end

@interface WantAlertViewContext : NSObject <UIAlertViewDelegate>
- (id)initWithDelegate:(id<WantAlertViewContextDelegate>)delegate context:(id)context;
@property (assign, nonatomic) id<WantAlertViewContextDelegate> delegate;
@property (retain, nonatomic) id context;
@end

实施

@implementation WantAlertViewContext
- (id)initWithDelegate:(id<WantAlertViewContextDelegate>)delegate context:(id)context

    self = [super init];
    if (self) 
        _delegate = delegate;
        _context  = [context retain];
    
    return self;

- (void)dealloc

    [_context release];
    [super dealloc];

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex

    [self.delegate alertView:alertView clickedButtonAtIndex:buttonIndex withContext:self.context];

- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex

    alertView.delegate = nil;
    [self autorelease];

@synthesize delegate = _delegate;
@synthesize context  = _context;
@end

【问题讨论】:

【参考方案1】:

您可以使用关联对象的概念。使用函数objc_setAssociatedObject()objc_getAssociatedObject()。您可以使用这些属性实质上通过类别向对象添加一个新属性,在您的情况下保存NSDictionary

这是UIAlertView 类别的示例。如果项目使用 ARC,则这些文件应在不设置 ARC-fno-objc-arc 标志的情况下编译。

UIAlertView+WithContext.h:

#import <UIKit/UIKit.h>
@interface UIAlertView (Context)
@property (nonatomic, copy) NSDictionary *userInfo;
@end

UIAlertView+WithContext.m:

#import "UIAlertView+WithContext.h"
// This enum is actually declared elseware
enum 
    OBJC_ASSOCIATION_ASSIGN = 0,
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
    OBJC_ASSOCIATION_RETAIN = 01401,
    OBJC_ASSOCIATION_COPY = 01403
;
@implementation UIAlertView (Context) 
static char ContextPrivateKey;
-(void)setUserInfo:(NSDictionary *)userInfo
    objc_setAssociatedObject(self, &ContextPrivateKey, userInfo, 3);

-(NSDictionary *)userInfo
    return objc_getAssociatedObject(self, &ContextPrivateKey);

@end

这个类别很容易使用。

SomeViewController.m:UIAlertViewDelegate 是否使用 ARC。

-(void)viewDidAppear:(BOOL)animated
    [super viewDidAppear:animated];
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Title" message:@"Message" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
    alert.userInfo = [NSDictionary dictionaryWithObject:@"Hello" forKey:@"Greeting"];// autorelease if MRC
    [alert show]; // release if MRC


-(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
    NSLog(@"userInfo:%@",alertView.userInfo);

当你按下 alertview 的 OK 按钮时,你会看到:

userInfo:
    Greeting = Hello;

几点说明:

1) 确保关联类型与属性声明相匹配,以便按预期运行。

2) 您可能不应该将userInfo 用于属性/关联,因为Apple 很可能会决定在未来将userInfo 属性添加到UIAlertView

编辑 解决您对[self autorelease];的疑虑

你必须平衡你隐含的alloc 保留从这一行:delegate:[[WantAlertViewContext alloc] initWithDelegate:self context:userInfo]。您可以通过在最终的 UIAlertView 委托方法中调用 [self autorelease]; 来实现这种平衡。

当然,这确实感觉不对。主要是因为在看这个的时候没有办法乍一看看起来不像是内存管理不善。但是有一种简单的方法可以避免您正在创建的这种“受控泄漏”API;让WantAlertViewContext 的实例显式保留自身。例如:

-(id)initWithDelegate:(id<WantAlertViewContextDelegate>)delegate context:(id)context
    self = [super init];
    if (self) 
        _delegate = delegate;
        _context  = [context retain];
    
    return [self retain]; // Explicitly retain self


-(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
    alertView.delegate = nil;
    [self autorelease]; // Or just [self release]; doesn't make much difference at this point

现在你的班级有了一些内在的和谐。我说一些是因为这仍然不完美。例如,如果一个实例从不是警报视图委托,它将永远不会被释放。它仍然只是一个“半可控”的内存泄漏。

无论如何,现在您的实例化调用看起来更合乎逻辑:

delegate:[[[WantAlertViewContext alloc] initWithDelegate:self context:userInfo] autorelease];

我认为这种特殊的设计模式充满了危险。如果您最终使用它,请密切关注它。

【讨论】:

我喜欢这个想法,可能会改用它,但它并不能完全回答我的 [self autorelease] 问题。我想如果我摆脱这个 [self autorelease],我会感觉好多了。 是的,感谢您的回复和详细说明。我也在想同样的事情。我只是想确保我没有自欺欺人。我太聪明了,导致了很多棘手的记忆问题。我认为这有点争议,因为我正在切换到关联的对象。很高兴知道 [self release] 和 [self autorelease] 是有用但危险的工具。【参考方案2】:

我想出了一个更简单的解决方案,可能适合某些情况。因为您在调用委托时获得了 NSAlertView 上下文,所以我使用对象的实际地址来创建一个标记 (NSString*),然后我使用该标记将自定义值存储在全局或对象特定的 NSDictionary 中。这是一个例子:

+(NSString*)GetTag:(id)ObjectIn

    return [NSString stringWithFormat:@"Tag-%i",(int)ObjectIn];

在委托中:

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex

    NSString* MyID = [CommandManager GetTag:alertView];
    [CurrentActiveAlerts removeObjectForKey:MyID];

调用:

UIAlertView *myAlert = [[UIAlertView alloc] initWithTitle:title_text
                                                          message:@""
                                                     delegate:self
                                            cancelButtonTitle:nil
                                        otherButtonTitles:button_text ,nil];

    CurrentActiveAlerts[[CommandManager GetTag:myAlert]] = CommandToRun;        // Querky way to link NSDict to UIAlert, but the best I could think of
    [myAlert show];
    [myAlert release];

这些键最终看起来像“Tag-226811776”。希望这会有所帮助。

【讨论】:

以上是关于UIAlertView 具有用户提供的上下文和 [self autorelease]的主要内容,如果未能解决你的问题,请参考以下文章

iPhone中真正的模态UIAlertView?

UIAlertView 按钮操作

具有多个文本输入的 UIAlertView?

appdelegate.m 中的 UIAlertView 不起作用

UIAlertView 输入 Apple ID 和密码 IAP

UIAlertView 滚动问题