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 ответа:
Вы можете использовать понятие ассоциированных объектов. Используя функции
Вот пример категории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". Надеюсь, это поможет.