Search code examples
iosxcodeswiftmemory-leaksinstruments

Unexplainable memory leak swift iOS


I have a super simple basic application,

just 1 viewcontroller with a collectionView

It seams to leak my collectionViewFlowLayout, and i don't know why

Leaked Object   #   Address Size    Responsible Library Responsible Frame
__NSDictionaryM 1   0x7fcc4ca31340  48 Bytes    UIKit   UICollectionViewLayoutCommonInit
__NSDictionaryM 1   0x7fcc4ca313a0  48 Bytes    UIKit   UICollectionViewLayoutCommonInit
__NSDictionaryI 1   0x7fcc4ca31570  64 Bytes    UIKit   UICollectionViewFlowLayoutCommonInit
__NSDictionaryM 1   0x7fcc4ca313d0  48 Bytes    UIKit   UICollectionViewLayoutCommonInit
UICollectionViewFlowLayout  1   0x7fcc4ca310c0  512 Bytes   leak-application    ObjectiveC.UICollectionViewFlowLayout.__allocating_init (ObjectiveC.UICollectionViewFlowLayout.Type)() -> ObjectiveC.UICollectionViewFlowLayout
__NSDictionaryM 1   0x7fcc4ca31370  48 Bytes    UIKit   UICollectionViewLayoutCommonInit
__NSDictionaryM 1   0x7fcc4ca31400  48 Bytes    UIKit   UICollectionViewLayoutCommonInit
NSMutableIndexSet   1   0x7fcc4ca314e0  48 Bytes    UIKit   UICollectionViewLayoutCommonInit
NSMutableIndexSet   1   0x7fcc4ca31470  48 Bytes    UIKit   UICollectionViewLayoutCommonInit
__NSDictionaryM 1   0x7fcc4ca2f290  48 Bytes    UIKit   UICollectionViewLayoutCommonInit

this is my code

class ParallaxCollectionViewCell: UICollectionViewCell {

    let titleLabel = UILabel(frame: CGRectMake(0, 0, 100, 100))

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.contentView.addSubview(titleLabel)
    }

    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

}

class ViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {

    var collectionView: UICollectionView?
    let collectionViewLayout = UICollectionViewFlowLayout()

    override func viewDidLoad() {
        super.viewDidLoad()
        setup()
    }

    func setup() {

        self.collectionView = UICollectionView(frame: CGRectZero, collectionViewLayout: self.collectionViewLayout)
        self.view.addSubview(self.collectionView!)

        self.collectionView?.delegate = self;
        self.collectionView?.dataSource = self;
        self.collectionView?.backgroundColor = UIColor.whiteColor()
        self.collectionView?.registerClass(ParallaxCollectionViewCell.self, forCellWithReuseIdentifier: "identfifier")

        self.collectionView?.reloadData()
    }

    override func viewDidLayoutSubviews() {
        self.collectionView?.frame = self.view.bounds
    }

    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
        return CGSizeMake(CGRectGetWidth(self.view.bounds), 100)
    }

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let parallaxCell = collectionView.dequeueReusableCellWithReuseIdentifier("identfifier", forIndexPath: indexPath)  as ParallaxCollectionViewCell
        parallaxCell.titleLabel.text = "test"
        return parallaxCell
    }

    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 10
    }
}

If i make the collectionViewLayout property an optional var and assign it in setup

var collectionViewLayout : UICollectionViewFlowLayout?
func setup() {
    self.collectionViewLayout = UICollectionViewFlowLayout()
    self.collectionView = UICollectionView(frame: CGRectZero, collectionViewLayout: self.collectionViewLayout!)
    ...

it doesn't leak .. but that cannot be the solution ?


Solution

  • I've previously encountered a Swift bug related to NIB loading that can cause properties to be initialized twice. If I recall correctly, in some cases two different designated initializers could be called on the same object instance, causing the object's instance properties to be initialized twice and leaking anything was instantiated during the first initialization. (That was in an early Swift beta using NSWindowController in a Cocoa application. I don't know if the bug still exists in the current Swift version.)

    That would explain the leak you're seeing with your collectionViewLayout property, and why it doesn't leak when you assign the property in setup().

    You can use Instruments to see how many UICollectionViewFlowLayout instances are created, or you can set a symbolic breakpoint in -[UICollectionViewFlowLayout init] to see if it's called twice during your ViewController initialization.

    Otherwise, it could just be a false positive in the leak detector.

    To work around the problem, you can try overriding init(coder:) and initialize your collectionViewLayout property in that method:

    required init(coder aDecoder: NSCoder) {
        self.collectionViewLayout = UICollectionViewFlowLayout()
        super.init(coder:aDecoder)
    }
    

    However, you'll want to confirm that this initializer is not called twice.