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!