简体   繁体   中英

Memory leak with UITableView and NSFetchedResultsController

I have a UITableView with its contents managed by a NSFetchedResultsController fetching CoreData. I have two types of table view cells, one with an image and one without an image. The images are handled by UIDocument and are kept in the iCloud ubiquitous documents container and are referenced by client name.

As the cells are generated and reused when there are many user generated images on the screen, the memory usage of my program creeps higher and higher. Around 110 mb, I get a low memory warning.

I suspect my prepareForReuse() method in my tableView cell isn't doing its job correctly.

Here's what my UITableViewCell looks like now:

class ClientTableViewCell: UITableViewCell {

@IBOutlet weak var clientName: UILabel!

@IBOutlet weak var clientModifiedDate: UILabel!

@IBOutlet weak var profileImage: UIImageView!

let dateFormatter = NSDateFormatter()

var document: MyDocument?
var documentURL: NSURL?
var ubiquityURL: NSURL?

var metaDataQuery: NSMetadataQuery?

var myClient : Client?
{
    didSet
    {
        updateClientInfo()
    }
}

override func prepareForReuse() {
    super.prepareForReuse()

    clientName.text = nil
    clientModifiedDate.text = nil

    if let mc = myClient
    {
        if mc.imageurl != ""
        {
            if let p = profileImage
            {
                p.image = nil
            } else
            {
                NSLog("nil document")
            }
        }
    } else
    {
         NSLog("nil client")
    }
}


func updateClientInfo()
{
    if myClient != nil
    {
        clientName.text = myClient!.name

        dateFormatter.dateStyle = .ShortStyle

        let dispDate = dateFormatter.stringFromDate(NSDate(timeIntervalSinceReferenceDate: myClient!.dateModified))

        clientModifiedDate.text = dispDate

        if let imageName = myClient?.imageurl  {

            if myClient?.imageurl != "" {

                var myClientName : String!

                myClientName = myClient!.name

                metaDataQuery = NSMetadataQuery()

                metaDataQuery?.predicate = NSPredicate(format: "%K like '\(myClientName).png'", NSMetadataItemFSNameKey)

                metaDataQuery?.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]

                NSNotificationCenter.defaultCenter().addObserver(self,
                    selector: "metadataQueryDidFinishGathering:",
                    name: NSMetadataQueryDidFinishGatheringNotification,
                    object: metaDataQuery!)

                metaDataQuery!.startQuery()

            }
        }
    }

}


func metadataQueryDidFinishGathering(notification: NSNotification) -> Void {

        let query: NSMetadataQuery = notification.object as! NSMetadataQuery

        query.disableUpdates()

        NSNotificationCenter.defaultCenter().removeObserver(self, name: NSMetadataQueryDidFinishGatheringNotification, object: query)

        query.stopQuery()

        let results = query.results

        if query.resultCount == 1 {
            let resultURL = results[0].valueForAttribute(NSMetadataItemURLKey) as! NSURL
                document = MyDocument(fileURL: resultURL)
                document?.openWithCompletionHandler({(success: Bool) -> Void in
            if success {

                if let pi = self.profileImage
                {
                    pi.image = self.document?.image
                }


                } else {
                    println("iCloud file open failed")
                }
            })

        }  else {
            NSLog("Could not find profile image, creating blank document")
        }
}

}

If you're not familiar with iCloud and UIDocument, you might be wondering why I'm querying the metadata to get at these images/documents. If you know of a reliable way of fetching these images/documents in the ubiquitous container, I'm all ears. It causes this pop in effect during the scrolling of the table view cells.

And here's my function that populates the cells:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    //set the client
    let client = clients.fetchedObjects![indexPath.row] as! Client

    //set the correct identifier
    if let imageurl = client.imageurl as String?
    {
        if imageurl == ""
        {
            var cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifierNoImage, forIndexPath: indexPath) as? ClientTableViewCell
            cell!.myClient = client
            return cell!

        } else
        {
            var cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as? ClientTableViewCell
            cell!.myClient = client
            return cell!
        }
    }
    var cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifierNoImage, forIndexPath: indexPath) as? ClientTableViewCell
    cell!.myClient = client
    return cell!
}

I originally was going to use the attribute imageurl to store the name and url of the client profile image, but since the ubiquitous container url is unique for every device, I now only use it to detect if there is an image with this client or not.

I've been having this issue for weeks now and cannot seem to nail it down. Any guidance would be greatly appreciated!

Workbench: Xcode 6.3 and Swift 1.2.

Solved my own memory leak! With the help of this answer here: [ UIDocument never calling dealloc

I was querying UIDocuments and never closing them. That's why the ARC wasn't cleaning up my unused UIDocuments. The link above has the Object C version, but for folks using Swift, try these lines to close your documents after using them:

document?.updateChangeCount(UIDocumentChangeKind.Done)
document?.closeWithCompletionHandler(nil)

Whew! Glad that mess was over. Hope this helps someone!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM