Подтвердить что ты не робот

Objective-C self → _ ivar access с явным vs неявным self->

Общая проблема

До сих пор я всегда думал, что self->_ivar эквивалентно _ivar. Сегодня я узнал, что это не совсем так.

См., например, следующий фрагмент кода:

@interface TestClass : NSObject {
    NSString *_testIVar;
}
@end

@implementation TestClass

- (instancetype)init
{
    if ((self = [super init])) {
        _testIVar = @"Testing Only";
    }
    return self;
}

- (void)test
{
    {
        NSInteger self = 42;
        NSLog(@"without arrow: %@", _testIVar);        /* OK              */
        NSLog(@"with    arrow: %@", self->_testIVar);  /* COMPILER ERROR! */
    }
}

@end

Несмотря на то, что я спрятал исходный self с некоторым NSInteger, также названным self, неявный синтаксис ivar _testIVar все еще находит "оригинальное", тогда как self->_testIVar, очевидно, этого не делает. В последнем случае компилятор правильно жалуется на

Тип ссылки ссылки "NSInteger" (иначе "long" ) не является указателем

В первом случае, однако, он просто работает.

Проблема реального мира

Этот пример может показаться довольно искусственным, но это совсем не так. Например, проект ExtObjC (используется ReactiveCocoa) определяет очень удобные @weakify(var) и @strongify(var), которые помогают избежать захвата self (и других объектов) в блоках, определяя действительно удобный синтаксис (нет необходимости писать нечетные и громоздкие записи __weak typeof(self) weakSelf = self; [...] ^{ __strong typeof(self) strongSelf = weakSelf; [...] } больше). Например:

- (void)someMethod
{
    @weakify(self);
    dispatch_async(self.someQueue, ^{
        @strongify(self);
        NSLog(@"self @ %p", self);
    }
}

Без @weakify и @strongify блок захватит сильную ссылку на self. С @weakify и @strongify это не так. Таким образом, освобождение self не будет отложено до тех пор, пока блок не будет запущен. Главное преимущество состоит в том, что вам не нужно забывать использовать weakSelf или strongSelf вместо self, потому что скрывается "оригинал" self.

Это очень удобно, ExtObjC реализует @weakify/@strongify, создавая с макросами нечто похожее на следующее:

- (void)someMethod
{
    __weak typeof(self) _weakSelf = self;
    dispatch_async(self.someQueue, ^{
        __strong typeof(self) self = _weakSelf;
        NSLog(@"self @ %p", self);
    }
}

Достаточно справедливо, что еще лучше, потому что мы можем просто продолжать использовать self, не заимствуя при этом сильную ссылку на self. Однако, как только мы будем использовать неявный синтаксис синтаксиса, сильная ссылка на "оригинал" self будет сохранена!

- (void)someMethod
{
    @weakify(self);
    dispatch_async(self.someQueue, ^{
        @strongify(self);  /* compiler warning: Unused variable self here!!! */
        NSLog(@"self->_testIVar: %@", _testIVar);
    }
}

Разное

При использовании ivars в блоках мы определенно захватываем self. См. Например, этот снимок экрана: Unused and captured self.

Еще одна забавная вещь о скриншоте заключается в том, что предупреждающие сообщения

Неиспользуемая переменная 'self'

и в строке ниже

Захват "я" сильно в этом блоке, вероятно, приведет к циклу сохранения

Вот почему я думаю, что есть две версии self: -)

Вопрос

Реальный вопрос: что именно означает _testIVar? Как найти "оригинальный" self указатель?

Чтобы уточнить (также см. мой снимок экрана): Как отметил @MartinR (что и я думаю), существует некоторая специальная версия self, которая не может быть изменена и используется только для неявного self-ivar -доступ. Это где-то документировано? В основном, где определено, к чему относится неявный self? Кажется, он ведет себя так же, как, например, Java (с this), но с той разницей, что this является зарезервированным ключевым словом, которое вы не можете переопределить.

Вопрос также не в том, как "исправить" его, просто написать self->_testIVar будет то, что я хочу в примере @weakify/@strongify. Это больше, чем я думал, используя @weakify/@strongify, вы не можете совершить ошибку, неявно сильно захватывая self, но это, похоже, не так.

4b9b3361

Ответ 1

Все методы Objective-C вызываются с двумя скрытыми аргументами (из "Objective-C Руководство по программированию времени выполнения" ):

  • Получающий объект
  • Селектор метода

и метод может ссылаться на принимающий объект как self (и на свой собственный селектор как _cmd).

Теперь _ivar эквивалентно self->_ivar, где self - это неявное первое параметр функции. Пока вы не определяете новую переменную self во внутренней области, _ivar == self->_ivar имеет значение true.

Если вы определяете новую переменную self во внутренней области, то у вас есть

  • Локально определенный self,
  • "неявное я", которое является первым параметром функции,

и _ivar все еще относится к "неявному я"! Это объясняет предупреждения компилятора в вашем блоке, которые, по-видимому, противоречат друг другу:

  • "Неиспользованная переменная" self "относится к локально определенному self,
  • "Захват" я "в этом блоке..." относится к "неявному я" функции.

Следующий код также демонстрирует это:

@interface MyClass : NSObject
{
    NSString *_ivar;
}
@end

@implementation MyClass

- (void)test
{
    _ivar = @"foo"; // Set instance variable of receiver
    {
        MyClass *self = [MyClass new]; // Redefine self in inner scope
        self->_ivar = @"bar"; // Set instance variable of redefined self
        NSLog(@"%@ - %@", self->_ivar, _ivar);
        // Output: bar - foo
    }
}

@end