Утечка памяти в Swift и / или ObjC при наличии свойства lazy по умолчанию в классе Swift с мостовым соединением - PullRequest
1 голос
/ 09 мая 2019

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

Отказ от ответственности:

  1. Я не могу опубликовать полную обратную трассировку, но если вы хотите увидеть ее, я сделаю все возможное, чтобы создать запутанную версию
  2. Я могу 't опубликовать полные определения классов, но я включаю части, которые были выполнены в этом сценарии, запутанные.

Что я надеюсь выучить

Я хочузнать, почему это происходит, а не получить решение.Или, может быть, когда я узнаю, что происходит, я узнаю, как лучше решить эту проблему, если есть лучший способ, чем сделать их lazy.

Проблема

Утечка этих значений по умолчанию

Цены:

@objcMembers
class Pricing: Mappable {
    var price: Money = Money.zero()
    var vat: Percent = Percent.zero()

    ...
}

Некоторая связанная с этим информация

Утечка этих двух свойств.Насколько я могу судить, эти нулевые экземпляры немедленно теряются, так как наш интерфейс ObjC Mappable имеет категорию под названием Mappable+JSONMappings, которая обеспечивает реализацию по умолчанию нашей инициализированной десериализации JSON, initWithJSON:, в Objective C для всех объектов, которые реализуютMappable.Это означает, что все, что вам нужно сделать, это заявить о его реализации, и тогда вы получите этот инициализатор, который затем вызывается нашим десериализатором, магией!

Однако мы получаем две отдельные утечки каждый раз, когда мы десериализуем эту Pricing объект.Один для класса Money и его внутреннего NSDecimalNumber и один для Percent.Исправление Money исправление NSDecimalNumber.Это согласуется, и они просачиваются точно так же, когда я смотрю на график памяти.

Mappable изначально использовался только нашими моделями ObjC, поскольку наши модели Swift до того момента были структурами, которые реализовывалиCodable для достижения того же результата, просто используя JSONDecoder.Эта модель должна была использоваться в устаревших частях нашей системы и, следовательно, должна также использовать Mappable, но мы использовали Swift для того, чтобы использовать функции Swift, недоступные в ObjC, такие как enum Enum: String и тому подобные, которые используютсявнутри по 1050 * по любым причинам.

Money.m:

@interface Money()

@property (nonatomic, strong) NSDecimalNumber *value;
@property (nonatomic, strong) NSString *currencyCode;

@end

@implementation Money

+ (instancetype)zero {
    return [Money initWithDecimal:[@(0) decimalValue]];
}

+ (instancetype)initWithDecimal:(NSDecimal)decimal {
    return [[self alloc] initWithDecimal:decimal];
}

- (instancetype)initWithDecimal:(NSDecimal)value {
    return [self initWithDecimal:value currency:[[CurrencySettings getInstance] getCurrency]];
}

- (instancetype)initWithDecimal:(NSDecimal)value currency:(NSString *)currencyCode {
    self = [super init];
    if (self) {
        _currencyCode = currencyCode;
        _value = [NSDecimalNumber decimalNumberWithDecimal:value];
    }
    return self;
}

@end

Я нашел метод, который часто вызывается в нашей внутренней реализации десериализации JSON.Он строит наши данные рекурсивно, используя этот метод NSObject:

[self setValue:value forKey:key]

TL; DR;

У нас есть внутренний слой десериализации ObjC JSON, который посредством магии реализует методы по умолчанию для вещей, реализующих интерфейс,Это часто вызывает следующий метод:

[self setValue:value forKey:key]

[NSObject setValue: forKey:]

В соответствии с документами говорится следующее :

Если ключ идентифицирует отношение «один к одному», передайте получателю объект, указанный в значении, без отношения к ранее связанному объекту, если таковой был.Учитывая объект коллекции и ключ, который идентифицирует отношение ко многим, связывают объекты, содержащиеся в коллекции, с получателем, не связывая ранее связанные объекты, если они были.Шаблон поиска, который setValue: forKey: использует, описан в Шаблонах поиска аксессора в Руководстве по программированию кодирования значения ключа. В среде с подсчетом ссылок, если доступ к переменной экземпляра осуществляется напрямую, значение сохраняется.

Отладка и трассировка

Имеется Автоматическая ссылкаПодсчет (ARC) включен на цели, и на данный момент это единственные утечки, которые мы имеем в настоящее время в довольно большом проекте, и использование Money.zero() также используется в других местах очень часто, указывая, что это на самом деле не проблемас самим классом Money.

График памяти для Money не очень информативен и отображает только это:

Leaked Money

График для NSDecimalNumber показывает, что Money виноват, хотя:

Leaked NSDecimalNumber

Проверка графика для Percent покажет то же самоерод отношений.

Создание свойств ленивых

Обходной путь должен сделать свойства lazy.Это не дает нам никакой информации о том, почему это произошло, поэтому я и публикую здесь.

@objcMembers
class Pricing: Mappable {
    lazy var price: Money = Money.zero()
    lazy var vat: Percent = Percent.zero()

    ...
}
...