Сохранить ссылку на объект локальной переменной в ARC
Я создал класс под ARC с некоторыми методами, которые принимают блоки. Проблема в том, что приложение продолжает падать, и я думаю, что причина сбоя в том, что объект освобождается ARC. Мой вопрос, как я могу это исправить, то есть как я могу сохранить ссылку на объект, чтобы объект не был освобожден, пока блок не был обработан.
Вот класс.h
#if NS_BLOCKS_AVAILABLE
typedef void (^KelaMagicalControlCompletionBlock)();
#endif
@interface KelaMagicalControl : NSObject
+(KelaMagicalControl *)controlWithTitle:(NSString *)title message:(NSString *)message;
-(id)initWithTitle:(NSString *)title message:(NSString *)message;
-(void)showWithTouchCompletionBlock:(KelaMagicalControlCompletionBlock)completionBlock;
@end
Вот класс.m
#import "KelaMagicalControl.h"
@interface KelaMagicalControl()
@property (nonatomic, strong) NSString * title;
@property (nonatomic, strong) NSString * message;
@property (copy) KelaMagicalControlCompletionBlock completionBlock;
@end
@implementation KelaMagicalControl
-(void)dealloc
{
NSLog(@"deallocated");
}
+ (KelaMagicalControl *)toastWithTitle:(NSString *)title message:(NSString *)message
{
KelaMagicalControl * magicalControl = [[KelaMagicalControl alloc] initWithTitle:title message:message];
return magicalControl;
}
-(id)initWithTitle:(NSString *)title message:(NSString *)message
{
if(self = [super init])
{
_title = title;
_message = message;
}
return self;
}
-(void)showWithTouchCompletionBlock:(void (^)())completionBlock
{
UIWindow * mainWindow = [[UIApplication sharedApplication]keyWindow];
UIView * viewTemp = [[UIView alloc] initWithFrame:CGRectMake(10, 10, 300, 100)];
[viewTemp setTag:10001];
[viewTemp setBackgroundColor:[UIColor redColor]];
[mainWindow addSubview:viewTemp];
UITapGestureRecognizer * tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(mainViewTapped)];
[viewTemp addGestureRecognizer:tapGestureRecognizer];
self.completionBlock = completionBlock;
}
-(void)mainViewTapped
{
if(self.completionBlock)
{
self.completionBlock();
self.completionBlock = nil;
}
}
Из класса контроллера я отправляю сообщение в методы пользовательского класса, например так:
-(IBAction)showMagicalControl:(id)sender
{
NSString * title = @"Title";
NSString * message = @"This is a very long message";
KelaMagicalControl * magicalControl = [[KelaMagicalControl alloc] initWithTitle:title message:message];
[magicalControl showWithTouchCompletionBlock:^{
NSLog(@"control tapped");
}];
}
Таким образом, он показывает, что элемент управления в порядке, но когда я нажимаю на него, вместо выполнения блока, он вылетает с ошибкой "obj_msgsend". Он даже не достигает метода showMagicalControl. Я думаю, что, поскольку я использую ARC, он освобождается автоматически, я вижу, что dealloc вызывается немедленно (перед выполнением блока). Он не падает, если я создаю свойство magicalRecord и использую его, но согласно моему требованию я не хочу использовать глобальный iVar или свойство просто для вызова этого кода блока.
Любые предложения, пожалуйста?
3 ответа
Проблема в том, что ваш KelaMagicalControl освобождается в конце метода showMagicalControl: он нигде не сохраняется. Только UIView, который вы создали внутри showWithTouchCompletionBlock: сохраняется, потому что вы добавили его в суперпредставление, в нашем случае это окно. Вот почему всплывающее окно отображается правильно. Но цель всегда unsafe_unretained, поэтому, когда вы нажимаете на это представление, gestRecognizer попытается вызвать ваш KelaMagicalControl, который уже был выпущен, поэтому вы получите сбой.
Вы можете легко решить эту проблему, сделав свой KelaMagicalControl подклассом UIView. Я быстро напечатал изменения, которые вы должны сделать:
.h файл
#import <UIKit/UIKit.h>
#if NS_BLOCKS_AVAILABLE
typedef void (^KelaMagicalControlCompletionBlock)();
#endif
@interface KelaMagicalControl : UIView
{
NSString* _title;
NSString* _message;
}
-(id)initWithTitle:(NSString *)title message:(NSString *)message;
-(void)showWithTouchCompletionBlock:(KelaMagicalControlCompletionBlock)completionBlock;
@end
.m файл
#import "KelaMagicalControl.h"
@interface KelaMagicalControl()
@property (nonatomic, strong) NSString * title;
@property (nonatomic, strong) NSString * message;
@property (copy) KelaMagicalControlCompletionBlock completionBlock;
@end
@implementation KelaMagicalControl
-(void)dealloc
{
NSLog(@"deallocated");
}
+ (KelaMagicalControl *)toastWithTitle:(NSString *)title message:(NSString *)message
{
KelaMagicalControl * magicalControl = [[KelaMagicalControl alloc] initWithTitle:title message:message];
return magicalControl;
}
-(id)initWithTitle:(NSString *)title message:(NSString *)message
{
self = [super initWithFrame:CGRectMake(10, 10, 300, 300)];
if (self)
{
_title = title;
_message = message;
}
return self;
}
-(void)showWithTouchCompletionBlock:(void (^)())completionBlock
{
UIWindow * mainWindow = [[UIApplication sharedApplication]keyWindow];
[self setTag:10001];
[self setBackgroundColor:[UIColor redColor]];
[mainWindow addSubview:self];
UITapGestureRecognizer * tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(mainViewTapped)];
[self addGestureRecognizer:tapGestureRecognizer];
self.completionBlock = completionBlock;
}
-(void)mainViewTapped
{
if(self.completionBlock)
{
self.completionBlock();
self.completionBlock = nil;
}
}
@end
Поскольку ваш KelaMagicalControl теперь является отображаемым UIView, он будет автоматически сохранен, поскольку у него есть суперпредставление. При нажатии на вид блок завершения теперь будет выполняться так, как вы хотите. Убедитесь, что в конце блока завершения вы удалите его из суперпредставления, иначе он никогда не будет выпущен.
Вы правы, magicalControl освобождается, потому что это ограничивает его возможности. Я не проверял следующее, но это должно работать.
KelaMagicalControl * magicalControl = [[KelaMagicalControl alloc] initWithTitle:title message:message];
[magicalControl showWithTouchCompletionBlock:^{
KelaMagicalControl *retainedVar = magicalControl;
NSLog(@"control tapped");
}];
объявление сильной ссылки внутри блока сохранит magicalControl.
Одним из решений является использование блочного API для UIGestureRecognizer
(есть много версий этого плавающего в Интернете), а затем в блоке, позвоните [self mainViewTapped]
, Это сохраняет ваши KelaMagicalControl
и будет гарантировать, что KelaMagicalControl
будет доступен до тех пор, пока распознаватель жестов может вызвать его.