Это сильный эталонный цикл или вообще утечка памяти?

Примеры циклов с сильными ссылками, которые я обычно вижу, включают два класса со свойствами, указывающими друг на друга. Однако что, если только один из классов имеет свойство, указывающее на другой экземпляр, например:

class ClassA {
    var classB: ClassB? = nil
}

class ClassB {

}

Затем я создаю свои экземпляры следующим образом:

var myClassA = ClassA()
var myClassB = ClassB() //Reference count 1
myClassA.classB = myClassB //Reference count 2

// Now deallocate
myClassB = nil  //Reference count 1
myClassA = nil

Поскольку я освободил myClassB, счетчик ссылок равен 1. Что случилось со счетчиком ссылок myClassA.classB? Он никогда не достигал нуля, так как я никогда не делал myClassA.classB = nil и не использовал deinit для этого. Это делается неявно, так как я сделал myClassA = nil?

Это то, что можно отнести к категории сильных эталонных циклов? Я бы предположил, что это как минимум утечка памяти, правда ли это?


person TruMan1    schedule 22.05.2016    source источник
comment
В вашем коде нет ни утечки, ни циклической ссылки. Когда вы устанавливаете myClassA на nil, ARC автоматически прекращает владение ClassB, поэтому они оба освобождаются.   -  person ozgur    schedule 22.05.2016
comment
Чтобы расширить это — цикла нет, так как ваш граф имеет только одно ребро: myClassA.classB -> myClassB.   -  person jtbandes    schedule 22.05.2016
comment
Если бы этот код вызывал ссылку, это сделало бы ARC лишь незначительно полезным. Все ссылки, хранящиеся на уровне экземпляра, освобождаются при освобождении экземпляра.   -  person Avi    schedule 22.05.2016
comment
Все вышеперечисленные комментарии совершенно справедливы. Кстати, если вы добавите метод deinit к обоим классам, которые что-то печатают, вы можете эмпирически убедиться, что экземпляр ClassA и экземпляр ClassB освобождаются. Кроме того, вероятно, нет нужды говорить, что myClassA и myClassB должны быть определены как необязательные, чтобы установить их в nil.   -  person Rob    schedule 22.05.2016
comment
Я добавил пример, основанный на комментарии @Rob, для демонстрации на игровой площадке: 1) исходный код не имеет строгого эталонного цикла, 2) что потребуется для создания строгого эталонного цикла, 3) как сломать эту сильную ссылку цикл.   -  person vacawama    schedule 22.05.2016
comment
Это облегчение, спасибо всем! Я предполагаю, что это из-за ARC, иначе вам пришлось бы установить myClassA.classB на ноль в deinit ClassA.   -  person TruMan1    schedule 22.05.2016


Ответы (1)


Как объяснили в комментариях @ozgur, @jtbandes, @Avi и @Rob, не существует строгого эталонного цикла или утечки.

Вот пример, основанный на комментарии @Rob, который вы можете запустить на игровой площадке:

class ClassA {
    var classB: ClassB?

    deinit {
        print("ClassA deallocated")
    }
}

class ClassB {
    deinit {
        print("ClassB deallocated")
    }
}

class Tester {
    func test() {
        var myClassA: ClassA! = ClassA()
        var myClassB: ClassB! = ClassB() //Reference count 1
        myClassA.classB = myClassB //Reference count 2

        // Now deallocate
        print("setting myClassB to nil")
        myClassB = nil  //Reference count 1
        print("setting myClassA to nil")
        myClassA = nil
        print("all done")
    }
}

// Create `Tester` object and call `test`:

Tester().test()

Выход:

setting myClassB to nil
setting myClassA to nil
ClassA deallocated
ClassB deallocated
all done

Следует отметить, что даже если вы сначала установите myClassB в nil, myClassA освобождается первым. Когда myClassA освобождается, последняя ссылка на myClassB освобождается ARC, а затем освобождается myClassB.


Чтобы продемонстрировать цикл строгой ссылки, пусть ClassB сохранит сильную ссылку на ClassA:

class ClassA {
    var classB: ClassB?

    deinit {
        print("ClassA deallocated")
    }
}

class ClassB {
    var classA: ClassA?

    deinit {
        print("ClassB deallocated")
    }
}

class Tester {
    func test() {
        var myClassA:ClassA! = ClassA()
        var myClassB:ClassB! = ClassB() //Reference count 1
        myClassA.classB = myClassB //Reference count 2
        myClassB.classA = myClassA

        // Now deallocate
        print("setting myClassB to nil")
        myClassB = nil  //Reference count 1
        print("setting myClassA to nil")
        myClassA = nil
        print("all done")
    }
}

Tester().test()

Выход:

setting myClassB to nil
setting myClassA to nil
all done

Ни один из объектов не освобождается, если они оба содержат сильную ссылку на другой. Чтобы разорвать этот цикл сильных ссылок, объявите одно из свойств classB или classA равным weak. Какой из них вы выберете, повлияет на порядок освобождения объектов:

Если вы объявите weak var classB: ClassB в ClassA:

Выход:

setting myClassB to nil
ClassB deallocated
setting myClassA to nil
ClassA deallocated
all done

Если вместо этого вы объявите weak var classA: ClassA in ClassB:

Выход:

setting myClassB to nil
setting myClassA to nil
ClassA deallocated
ClassB deallocated
all done
person vacawama    schedule 22.05.2016