Сохранить ссылку на объект локальной переменной в 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 будет доступен до тех пор, пока распознаватель жестов может вызвать его.

Другие вопросы по тегам