Избегайте освобождения объекта без добавления свойства/iVar в UIViewController

У меня есть собственный класс/объект, который обрабатывает жесты и выполняет анимацию для данного вида с помощью CADisplayLink. В простейшей форме мой класс выглядит примерно так:

@interface SomeClass : NSObject

@property (strong) UIView *someView;

@end

Когда я добавляю следующий код в свой контроллер представления....

- (void)viewDidLoad
{
    [super viewDidLoad];

    SomeClass *someClass = [[SomeClass alloc] init];
    someClass.someView = someView;
}

... Я ожидал, что мой объект someClass будет сохранен на всю жизнь контроллера представления, поскольку я использую сильную ссылку на someView.

Однако someClass немедленно освобождается.

Я уже знаю, что могу преодолеть освобождение, просто добавив someClass в качестве свойства (или действительно iVar) контроллера представления, однако в идеале я хотел бы избежать этой дополнительной работы...

так что в любом случае я могу сохранить свой класс до тех пор, пока представление или контроллер представления, с которым он связан, не будут освобождены?

ИЗМЕНИТЬ

Объекты UIGestureRecognizer являются примером класса, который не освобождается, когда я связываю их с представлением...

- (void)viewDidLoad
{
    UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] init];
    [someView addGestureRecognizer:gestureRecognizer];
}
// tapGestureRecognizer still lives

Предположительно это связано с тем, что UIView становится владельцем объекта UIGestureRecognizer. Можно ли как-то добиться этого с помощью моего класса и категории UIView? То есть....

 - (void)viewDidLoad
{
    [super viewDidLoad];

    SomeClass *someClass = [[SomeClass alloc] init];
    [someView addSomeClass:someClass];
}

person Ollie-Mobile    schedule 09.06.2014    source источник


Ответы (3)


Если вы хотите связать объект с UIView так же, как это делает UIGestureRecognizer, то это технически возможно с использованием связанных объектов следующим образом (но я не уверен, что буду поддерживать этот подход, поскольку связанные объекты часто заморочился)...

SomeClass.h

@class SomeClass;


@interface UIView (SomeClass)

- (void)addSomeClass:(SomeClass *)someClass;
- (void)removeSomeClass:(SomeClass *)someClass;

@end


@interface SomeClass : NSObject

@property (strong) UIView *someView;

@end

SomeClass.m

#import "SomeClass.h"
#import <objc/runtime.h>


@implementation UIView (AssociatedObject)

- (void)addSomeClass:(SomeClass *)someClass
{
    NSMutableArray *someClasses = [self someClasses];
    if (someClasses == nil) {
        someClasses = [[NSMutableArray alloc] init];
        [self setSomeClasses:someClasses];
    }
    [someClasses addObject:someClass];
}

- (void)removeSomeClass:(SomeClass *)someClass
{
    NSMutableArray *someClasses = [self someClasses];
    if (someClasses != nil) {
        [someClasses removeObject:someClass];
        if (someClasses.count == 0) {
            [self setSomeClasses:nil];
        }
    }
}

#pragma mark - Private Methods

- (NSMutableArray *)someClasses
{
    return (NSMutableArray *)objc_getAssociatedObject(self, @selector(someClasses));
}

- (void)setSomeClasses:(NSMutableArray *)someClasses
{
    objc_setAssociatedObject(self, @selector(someClasses), someClasses, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end


@implementation SomeClass

@end

Реализация

- (void)viewDidLoad
{
    [super viewDidLoad];

    SomeClass *someClass = [[SomeClass alloc] init];
    someClass.someView = someView;
    [someView addSomeClass:someClass];
}

Некоторое дополнительное чтение об ассоциированных объектах от NSHipster...

http://nshipster.com/associated-objects/

person Oliver Pearmain    schedule 10.06.2014
comment
Спасибо, что действительно ответили на вопрос. - person Ollie-Mobile; 10.06.2014

Но вы можете объявить экземпляр SomeClass вместо свойства следующим образом:

@implementation ViewController
{
    SomeClass* _someClass;
}

...


- (void)viewDidLoad
{
    [super viewDidLoad];

    _someClass = [[SomeClass alloc] init];
    _someClass.someView = someView;
}
person stosha    schedule 09.06.2014
comment
Извиняюсь, нужно было конкретно указать свойство или iVar (я думал, что это очевидно). Это именно то, чего я хотел бы избежать. - person Ollie-Mobile; 10.06.2014

Ваш экземпляр SomeClass содержит сильную ссылку на экземпляр someView, но ничто не содержит ссылку на экземпляр SomeClass, кроме локальной переменной внутри вашего сообщения viewDidLoad, поэтому, как только метод завершится, этот экземпляр может быть освобожден. Поскольку это был объект, содержащий единственную ссылку на ваш UIView, представление также может быть освобождено.

Ваши единственные варианты - сохранить ссылку на объект SomeClass в переменной экземпляра (или iVar), как предложено stosha, или в свойстве. Свойства являются предпочтительным методом, и при автоматическом синтезе они требуют не больше усилий, чем объявление локальной переменной.

Вы можете объявить свойство внутри файла .m, чтобы оно не было видно другим классам, которые ссылаются на ваш класс ViewController -

В вашем файле ViewController.m -

@interface ViewController ()

@property (strong, nonatomic) SomeClass *someClass;

@end

@implementation ViewController

...

 (void)viewDidLoad
{
    [super viewDidLoad];

    self.someClass = [[SomeClass alloc] init];
    self.someClass.someView = someView;
}
person Paulw11    schedule 09.06.2014
comment
Спасибо за информативный комментарий, однако это не отвечает на мой вопрос. - person Ollie-Mobile; 10.06.2014
comment
Хорошо, тогда короткий ответ - нет. Да, вы можете изменить представление для хранения ссылки, но оно кажется излишне сложным по сравнению с простым свойством. - person Paulw11; 10.06.2014