问题
I have looked over some ideas for how to supply a context to a UIAlertView. The common answers are save it in a dictionary or subclass UIAlertView. I don't like the idea of saving the context in a dictionary, it's the wrong place for the data. Subclassing UIAlertView is not supported by Apple, so by my standard, is not a good solution.
I came up with an idea, but I'm not sure what to make of it. Create an instance of a context object that is the delegate of UIAlertView. The alert view context, in turn, has it's own delegate which is the view controller.
The trouble is releasing memory. I set alertView.delegate to nil and call [self autorelease] to free the context object in -alertView:didDismissWithButtonIndex:.
THE QUESTION IS: What problems am I causing myself? I have a suspicion that I'm setting myself up for a subtle memory error.
Here is the simple version which only supports -alertView:clickedButtonAtIndex:
Use
- (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];
}
Interface
@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
@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:
You can use the concept of associated objects. Using the functions objc_setAssociatedObject()
and objc_getAssociatedObject()
. You can use these properties to essentially add a new property, in your case to hold an NSDictionary
, to an object through a category.
Here is an example of a UIAlertView
category. These files should be compiled without ARC
, -fno-objc-arc
flag set if the project is using 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
This category is easily used.
SomeViewController.m: a UIAlertViewDelegate
using ARC or not.
-(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);
}
When you press the alertview's OK button you will see:
userInfo:{
Greeting = Hello;
}
A couple of notes:
1) Make sure the association type matches the property declaration so things behave as expected.
2) You probably shouldn't use userInfo
for the property/association since Apple may well decide to add a userInfo
property to UIAlertView
in the future.
Edit To address your concerns about your [self autorelease];
It is imperative that you balance your implicit alloc
retain from this line: delegate:[[WantAlertViewContext alloc] initWithDelegate:self context:userInfo]
. You achieve this balance by calling [self autorelease];
in the final UIAlertView
delegate method.
Granted, this does feel wrong. Mostly because there is no way when looking at this that it doesn't at first blush look like memory mis-management. But there is one simple way to avoid this "controlled leak" API you are creating; Have the instance of WantAlertViewContext
explicitly retain itself. For example:
-(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
}
Now your class has some internal harmony. I say some because this is still not perfect. For example, if an instance is never an alert-view delegate it will never be released. It is still just a "semi-controlled" memory leak.
Anyway, now your instantiation call can look more logical:
delegate:[[[WantAlertViewContext alloc] initWithDelegate:self context:userInfo] autorelease];
I think that this particular design pattern is fraught with danger. If you do end up using it keep a close eye on it.
回答2:
I've come up with a simpler solution that may fit in some circumstances. Because you get the NSAlertView context when the delegate gets called, I use the actual address of the object to make a tag (NSString*) which I then use to store custom values in a global or object specific NSDictionary. Here is an example:
+(NSString*)GetTag:(id)ObjectIn
{
return [NSString stringWithFormat:@"Tag-%i",(int)ObjectIn];
}
In the Delegate:
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
NSString* MyID = [CommandManager GetTag:alertView];
[CurrentActiveAlerts removeObjectForKey:MyID];
}
Calling:
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];
The keys will end up looking like "Tag-226811776". Hope this helps.
来源:https://stackoverflow.com/questions/10304273/uialertview-with-a-user-supplied-context-and-self-autorelease