0

Update :

Had nothing to do with Core Data or the CollectionView. I never dismissed the ViewController holding the CollectionView. More in the answer below!


I am having a memory leak in Swift iOS. First I thought it was located in my fetch function, but then I tried something different. I broke the connection between my UICollectionViewCell and my Managed Object at various places. I am fetching images. So I replaced the result from my fetch with a random image asset that was added to an array for as many times as there were results. This always fixed my leak. So now I knew it wasn't a problem in my fetch function, any other function on the way to the Cell.

After some googling I found that Core Data has some tendency to create strong reference cycles on it's own. But I don't think that that is it. If it was, it shouldn't matter that I don't use the resulting array of images in a Cell. I should still have a leak, but when I don't connect the images from Core Data to the Cell I have no leak.

What I don't understand is why I have a leak in the first place. I thought that arrays work like values, not references. So putting images loaded from core data in an array should work like a copy and there shouldn't be any strong reference cycle...

I also found that refreshing the Managed Object that has the Binary Data atribute for the images doesn't fix the problem.

So what do I do now? Do I need to delete all the cells? Do I need to make UIImages out of the NSData from Core Data (glanced over something that said that Strings work like values but NSStrings don't, so maybe an NSData thingy works like a reference)? Do I need to find a way to refresh the attribute of an object?...

Thanks!

Fetch(also tried setting things as weak, doesn't work):

    import UIKit
    import CoreData


    func getFilteredThumbImages (albumIdentifier: String, moc : NSManagedObjectContext?) -> [NSData]? {

        var error: NSError?

        let resultPredicate = NSPredicate(format: "iD = %@", albumIdentifier)
        let albumfetchRequest = NSFetchRequest(entityName: "Album")
        albumfetchRequest.predicate = resultPredicate
        var results = moc!.executeFetchRequest(albumfetchRequest, error:&error)!

        if error == nil {

            weak var tempFThumbs = results.last!.mutableSetValueForKey("filteredThumbs")

            weak var testFoundImages = tempFThumbs!.mutableSetValueForKey("imageData")

            var foundImages = testFoundImages!.allObjects as [NSData]

            moc!.refreshObject(results.last! as NSManagedObject, mergeChanges: false)

            moc!.reset()

           return foundImages
        }
        else {
            moc!.refreshObject(results.last! as NSManagedObject, mergeChanges: false)
            //appDel?.managedObjectContext?.reset()

            moc!.reset()

            return nil
        }

    }

UICollectionViewCell :

import UIKit

class CollectionViewCell: UICollectionViewCell {


    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    let textLabel: UILabel!
    let imageView: UIImageView!

    override init(frame: CGRect) {
        NSLog("MyObject init")
        super.init(frame: frame)

        imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height))
        imageView.backgroundColor = UIColor(red:1, green:0.992, blue:0.965, alpha:1)
        imageView.contentMode = UIViewContentMode.ScaleAspectFit
        imageView.clipsToBounds = true
        contentView.backgroundColor = UIColor(red:1, green:0.992, blue:0.965, alpha:1)
        contentView.addSubview(imageView)

    }
}

Input images in Cell(thumbs is a variable array => [NSData]):

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {

        println("loading...")
        weak var cell = collectionView.dequeueReusableCellWithReuseIdentifier("CollectionViewCell", forIndexPath: indexPath) as? CollectionViewCell

        //cell!.imageView?.image = UIImage(named: "test")


        if thumbs != nil {
            cell!.imageView?.image = UIImage(data: thumbs![indexPath.row])
            println("loading \(thumbs!.count) cells")
        }




        return cell!
    }
R Menke
  • 8,183
  • 4
  • 35
  • 63
  • "What I don't understand is why I have a leak in the first place" How do you know there's a leak? If you know there's a leak, you know why there's a leak and what is leaking. Use Instruments. Use logging on `deinit` to see whether objects are going out of existence in good order. – matt Jan 17 '15 at 06:10
  • I know there is a leak because each time I load the collectionview, it loads the images (around 30mb) and I don't get that memory back. So after 5-6 loads it crashes. Instruments doesn't report any leaks... Even if instruments did report it (99% sure that I know what is wrong), I still don't know how to fix it. – R Menke Jan 17 '15 at 06:12
  • 1
    So it sounds like the collection view controller itself is leaking each time. What does "each time" mean? Does it appear as a pushed or presented view controller, and then get popped or dismissed? Think about your view controller hierarchy. How is it configured? – matt Jan 17 '15 at 06:16
  • Oh, and add `deinit` with logging to your collection view controller. Show it and then unshow it (whatever that means). Does the logging appear in the console? If not, your collection view controller is leaking. – matt Jan 17 '15 at 06:18
  • I didn't think about that. I thought it had more to do with how I had my properties set up. The collectionview is done programatically in a view is embedded in a navigation controller. Segues to that navigation controller are "Show" (old push). And I segue back with another "Show" I realise now, that that should be dismiss.... – R Menke Jan 17 '15 at 06:19
  • Well, let's think about it! Sure, I could be wrong, but let's be safe. – matt Jan 17 '15 at 06:19
  • New Personal Hero: @matt Sometimes the problem is something so stupid and in this case also code from 4 weeks ago that came back to bite me in the ass! Thx p.s. if you put this in some kind of answer, I can accept it – R Menke Jan 17 '15 at 06:41
  • Check my answer - I am assuming this is a navigation controller, but if it isn't, I can rewrite it. Just want to make sure posterity sees the right thing. – matt Jan 17 '15 at 06:44
  • Oh, and this explains why you didn't get a leak in Instruments. It isn't a leak. It's just a s**tload of view controllers and images; no law against that. :) – matt Jan 17 '15 at 06:46
  • Stupid things is, that I know this. Weeks ago I made a prepare for segue and for some reason I thought it would be a great idea to also make a reverse and I just added another "Show" without thinking about it. At that time there was also no data yet being loaded, just an interface. So I never noticed the increase in memory. – R Menke Jan 17 '15 at 06:53

1 Answers1

2

The problem is that you've got a cycle in your storyboard. You are pushing from view controller A to view controller B, and then pushing from view controller B "back" to view controller A. And so on, every time. Thus, your navigation controller just piles up numerous copies of the same view controllers. And so you wind up with numerous copies of all those images.

It always surprises me that you don't get a notice when you do this sort of thing. It's a classic mistake, and all too easy to make. Surely Interface Builder could detect this kind of cycle and warn you. But it doesn't...

matt
  • 515,959
  • 87
  • 875
  • 1,141