UIAlertView с предоставленным пользователем контекстом и [self autorelease]


Я просмотрел некоторые идеи о том, как предоставить контекст для UIAlertView. Общие ответы-сохранить его в словаре или подклассе UIAlertView. Мне не нравится идея сохранения контекста в словаре, это неподходящее место для данных. Подклассы UIAlertView не поддерживаются Apple, поэтому по моему стандарту это не очень хорошее решение.

Мне пришла в голову одна идея, но я не знаю, что с ней делать. Создайте экземпляр объекта контекста, который является делегатом UIAlertView. То контекст вида оповещения, в свою очередь, имеет свой собственный делегат, который является контроллером вида.

Проблема заключается в освобождении памяти. Я поставил alertView.делегируйте nil и вызовите [self autorelease], чтобы освободить объект контекста in-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
2 2

2 ответа:

Вы можете использовать понятие ассоциированных объектов. Используя функции objc_setAssociatedObject() и objc_getAssociatedObject(). Вы можете использовать эти свойства для существенного добавления нового свойства, в вашем случае для хранения NSDictionary, к объекту через категорию.

Вот пример категории UIAlertView. Эти файлы должны быть скомпилированы без ARC, -fno-objc-arc флаг устанавливается, если проект использует ДУГА.

UIAlertView+WithContext.h:

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

UIAlertView+WithContext.м:

#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: a 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);
}

Когда вы нажмете кнопку OK alertview, вы увидите:

userInfo:{
    Greeting = Hello;
}

Пара заметок:

1) Убедитесь, что тип ассоциации соответствует объявлению свойства, чтобы все вело себя так, как ожидалось.

2) вы, вероятно, не следует использовать userInfo Для свойства / ассоциации, так как Apple вполне может решить добавить свойство userInfo в UIAlertView в будущем.

Править чтобы решить ваши проблемы по поводу вашего [self autorelease];

Крайне важно, чтобы вы уравновесили свое неявное alloc удержание из этой строки: delegate:[[WantAlertViewContext alloc] initWithDelegate:self context:userInfo]. Вы достигаете этого баланса, вызывая [self autorelease]; в конечном методе делегирования UIAlertView.

Конечно, это кажется неправильным. В основном потому, что нет никакого способа, когда смотришь на это, что это на первый взгляд это не похоже на неправильное управление памятью. Но есть один простой способ избежать этой" контролируемой утечки " 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];
Я думаю, что этот конкретный шаблон дизайна таит в себе опасность. Если вы в конечном итоге используете его, внимательно следите за ним.

Я придумал более простое решение, которое может подойти в некоторых обстоятельствах. Поскольку вы получаете контекст 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". Надеюсь, это поможет.