Use DatabaseDictionary instead of NSDictionary. Work around a Swift memory leak with NSDictionary.

This commit is contained in:
Brent Simmons 2019-03-02 16:17:06 -08:00
parent 797c7cb0fb
commit e04250f1b3
6 changed files with 64 additions and 92 deletions

View File

@ -40,31 +40,30 @@ extension Article {
self.init(accountID: accountID, articleID: parsedItem.syncServiceID, feedID: feedID, uniqueID: parsedItem.uniqueID, title: parsedItem.title, contentHTML: parsedItem.contentHTML, contentText: parsedItem.contentText, url: parsedItem.url, externalURL: parsedItem.externalURL, summary: parsedItem.summary, imageURL: parsedItem.imageURL, bannerImageURL: parsedItem.bannerImageURL, datePublished: datePublished, dateModified: dateModified, authors: authors, attachments: attachments, status: status) self.init(accountID: accountID, articleID: parsedItem.syncServiceID, feedID: feedID, uniqueID: parsedItem.uniqueID, title: parsedItem.title, contentHTML: parsedItem.contentHTML, contentText: parsedItem.contentText, url: parsedItem.url, externalURL: parsedItem.externalURL, summary: parsedItem.summary, imageURL: parsedItem.imageURL, bannerImageURL: parsedItem.bannerImageURL, datePublished: datePublished, dateModified: dateModified, authors: authors, attachments: attachments, status: status)
} }
private func addPossibleStringChangeWithKeyPath(_ comparisonKeyPath: KeyPath<Article,String?>, _ otherArticle: Article, _ key: String, _ dictionary: NSMutableDictionary) { private func addPossibleStringChangeWithKeyPath(_ comparisonKeyPath: KeyPath<Article,String?>, _ otherArticle: Article, _ key: String, _ dictionary: inout DatabaseDictionary) {
if self[keyPath: comparisonKeyPath] != otherArticle[keyPath: comparisonKeyPath] { if self[keyPath: comparisonKeyPath] != otherArticle[keyPath: comparisonKeyPath] {
dictionary.addOptionalStringDefaultingEmpty(self[keyPath: comparisonKeyPath], key) dictionary[key] = self[keyPath: comparisonKeyPath] ?? ""
} }
} }
func changesFrom(_ existingArticle: Article) -> NSDictionary? { func changesFrom(_ existingArticle: Article) -> DatabaseDictionary? {
if self == existingArticle { if self == existingArticle {
return nil return nil
} }
let d = NSMutableDictionary() var d = DatabaseDictionary()
if uniqueID != existingArticle.uniqueID { if uniqueID != existingArticle.uniqueID {
d[DatabaseKey.uniqueID] = uniqueID d[DatabaseKey.uniqueID] = uniqueID
} }
addPossibleStringChangeWithKeyPath(\Article.title, existingArticle, DatabaseKey.title, d) addPossibleStringChangeWithKeyPath(\Article.title, existingArticle, DatabaseKey.title, &d)
addPossibleStringChangeWithKeyPath(\Article.contentHTML, existingArticle, DatabaseKey.contentHTML, d) addPossibleStringChangeWithKeyPath(\Article.contentHTML, existingArticle, DatabaseKey.contentHTML, &d)
addPossibleStringChangeWithKeyPath(\Article.contentText, existingArticle, DatabaseKey.contentText, d) addPossibleStringChangeWithKeyPath(\Article.contentText, existingArticle, DatabaseKey.contentText, &d)
addPossibleStringChangeWithKeyPath(\Article.url, existingArticle, DatabaseKey.url, d) addPossibleStringChangeWithKeyPath(\Article.url, existingArticle, DatabaseKey.url, &d)
addPossibleStringChangeWithKeyPath(\Article.externalURL, existingArticle, DatabaseKey.externalURL, d) addPossibleStringChangeWithKeyPath(\Article.externalURL, existingArticle, DatabaseKey.externalURL, &d)
addPossibleStringChangeWithKeyPath(\Article.summary, existingArticle, DatabaseKey.summary, d) addPossibleStringChangeWithKeyPath(\Article.summary, existingArticle, DatabaseKey.summary, &d)
addPossibleStringChangeWithKeyPath(\Article.imageURL, existingArticle, DatabaseKey.imageURL, d) addPossibleStringChangeWithKeyPath(\Article.imageURL, existingArticle, DatabaseKey.imageURL, &d)
addPossibleStringChangeWithKeyPath(\Article.bannerImageURL, existingArticle, DatabaseKey.bannerImageURL, d) addPossibleStringChangeWithKeyPath(\Article.bannerImageURL, existingArticle, DatabaseKey.bannerImageURL, &d)
// If updated versions of dates are nil, and we have existing dates, keep the existing dates. // If updated versions of dates are nil, and we have existing dates, keep the existing dates.
// This is data thats good to have, and its likely that a feed removing dates is doing so in error. // This is data thats good to have, and its likely that a feed removing dates is doing so in error.
@ -91,27 +90,44 @@ extension Article {
extension Article: DatabaseObject { extension Article: DatabaseObject {
public func databaseDictionary() -> NSDictionary? { public func databaseDictionary() -> DatabaseDictionary? {
var d = DatabaseDictionary()
let d = NSMutableDictionary()
d[DatabaseKey.articleID] = articleID d[DatabaseKey.articleID] = articleID
d[DatabaseKey.feedID] = feedID d[DatabaseKey.feedID] = feedID
d[DatabaseKey.uniqueID] = uniqueID d[DatabaseKey.uniqueID] = uniqueID
d.addOptionalString(title, DatabaseKey.title) if let title = title {
d.addOptionalString(contentHTML, DatabaseKey.contentHTML) d[DatabaseKey.title] = title
d.addOptionalString(contentText, DatabaseKey.contentText) }
d.addOptionalString(url, DatabaseKey.url) if let contentHTML = contentHTML {
d.addOptionalString(externalURL, DatabaseKey.externalURL) d[DatabaseKey.contentHTML] = contentHTML
d.addOptionalString(summary, DatabaseKey.summary) }
d.addOptionalString(imageURL, DatabaseKey.imageURL) if let contentText = contentText {
d.addOptionalString(bannerImageURL, DatabaseKey.bannerImageURL) d[DatabaseKey.contentText] = contentText
}
d.addOptionalDate(datePublished, DatabaseKey.datePublished) if let url = url {
d.addOptionalDate(dateModified, DatabaseKey.dateModified) d[DatabaseKey.url] = url
}
return (d.copy() as! NSDictionary) if let externalURL = externalURL {
d[DatabaseKey.externalURL] = externalURL
}
if let summary = summary {
d[DatabaseKey.summary] = summary
}
if let imageURL = imageURL {
d[DatabaseKey.imageURL] = imageURL
}
if let bannerImageURL = bannerImageURL {
d[DatabaseKey.bannerImageURL] = bannerImageURL
}
if let datePublished = datePublished {
d[DatabaseKey.datePublished] = datePublished
}
if let dateModified = dateModified {
d[DatabaseKey.dateModified] = dateModified
}
return d
} }
public var databaseID: String { public var databaseID: String {
@ -160,35 +176,8 @@ extension Set where Element == Article {
return self.map{ $0 as DatabaseObject } return self.map{ $0 as DatabaseObject }
} }
func databaseDictionaries() -> [NSDictionary]? { func databaseDictionaries() -> [DatabaseDictionary]? {
return self.compactMap { $0.databaseDictionary() } return self.compactMap { $0.databaseDictionary() }
} }
} }
private extension NSMutableDictionary {
func addOptionalString(_ value: String?, _ key: String) {
if let value = value {
self[key] = value
}
}
func addOptionalStringDefaultingEmpty(_ value: String?, _ key: String) {
if let value = value {
self[key] = value
}
else {
self[key] = ""
}
}
func addOptionalDate(_ date: Date?, _ key: String) {
if let date = date {
self[key] = date as NSDate
}
}
}

View File

@ -29,17 +29,8 @@ extension ArticleStatus: DatabaseObject {
return articleID return articleID
} }
public func databaseDictionary() -> NSDictionary? { public func databaseDictionary() -> DatabaseDictionary? {
return [DatabaseKey.articleID: articleID, DatabaseKey.read: read, DatabaseKey.starred: starred, DatabaseKey.userDeleted: userDeleted, DatabaseKey.dateArrived: dateArrived]
let d = NSMutableDictionary()
d[DatabaseKey.articleID] = articleID
d[DatabaseKey.read] = read
d[DatabaseKey.starred] = starred
d[DatabaseKey.userDeleted] = userDeleted
d[DatabaseKey.dateArrived] = dateArrived
return (d.copy() as! NSDictionary)
} }
} }

View File

@ -60,13 +60,8 @@ extension Attachment: DatabaseObject {
return attachmentID return attachmentID
} }
public func databaseDictionary() -> NSDictionary? { public func databaseDictionary() -> DatabaseDictionary? {
var d: DatabaseDictionary = [DatabaseKey.attachmentID: attachmentID, DatabaseKey.url: url]
let d = NSMutableDictionary()
d[DatabaseKey.attachmentID] = attachmentID
d[DatabaseKey.url] = url
if let mimeType = mimeType { if let mimeType = mimeType {
d[DatabaseKey.mimeType] = mimeType d[DatabaseKey.mimeType] = mimeType
} }
@ -79,15 +74,14 @@ extension Attachment: DatabaseObject {
if let durationInSeconds = durationInSeconds { if let durationInSeconds = durationInSeconds {
d[DatabaseKey.durationInSeconds] = NSNumber(value: durationInSeconds) d[DatabaseKey.durationInSeconds] = NSNumber(value: durationInSeconds)
} }
return d
return (d.copy() as! NSDictionary)
} }
} }
extension Set where Element == Attachment { extension Set where Element == Attachment {
func databaseDictionaries() -> [NSDictionary] { func databaseDictionaries() -> [DatabaseDictionary] {
return self.compactMap { $0.databaseDictionary() } return self.compactMap { $0.databaseDictionary() }
} }

View File

@ -48,12 +48,9 @@ extension Author: DatabaseObject {
return authorID return authorID
} }
public func databaseDictionary() -> NSDictionary? { public func databaseDictionary() -> DatabaseDictionary? {
let d = NSMutableDictionary()
d[DatabaseKey.authorID] = authorID
var d: DatabaseDictionary = [DatabaseKey.authorID: authorID]
if let name = name { if let name = name {
d[DatabaseKey.name] = name d[DatabaseKey.name] = name
} }
@ -66,8 +63,7 @@ extension Author: DatabaseObject {
if let emailAddress = emailAddress { if let emailAddress = emailAddress {
d[DatabaseKey.emailAddress] = emailAddress d[DatabaseKey.emailAddress] = emailAddress
} }
return d
return (d.copy() as! NSDictionary)
} }
} }

View File

@ -108,9 +108,11 @@ private extension SearchTable {
} }
func insert(_ article: ArticleSearchInfo, _ database: FMDatabase) -> Int { func insert(_ article: ArticleSearchInfo, _ database: FMDatabase) -> Int {
let rowDictionary = NSMutableDictionary() let rowDictionary: DatabaseDictionary = [DatabaseKey.body: article.bodyForIndex, DatabaseKey.title: article.title ?? ""]
rowDictionary.setObject(article.title ?? "", forKey: DatabaseKey.title as NSString) // rowDictionary[DatabaseKey.title] = article.title ?? ""
rowDictionary.setObject(article.bodyForIndex, forKey: DatabaseKey.body as NSString) // rowDictionary[DatabaseKey.body] = article.bodyForIndex
// rowDictionary.setObject(article.title ?? "", forKey: DatabaseKey.title as NSString)
// rowDictionary.setObject(article.bodyForIndex, forKey: DatabaseKey.body as NSString)
insertRow(rowDictionary, insertType: .normal, in: database) insertRow(rowDictionary, insertType: .normal, in: database)
return Int(database.lastInsertRowId()) return Int(database.lastInsertRowId())
} }
@ -168,12 +170,12 @@ private extension SearchTable {
return return
} }
let updateDictionary = NSMutableDictionary() var updateDictionary = DatabaseDictionary()
if title != searchInfo.title { if title != searchInfo.title {
updateDictionary.setObject(title, forKey: DatabaseKey.title as NSString) updateDictionary[DatabaseKey.title] = title
} }
if article.bodyForIndex != searchInfo.body { if article.bodyForIndex != searchInfo.body {
updateDictionary.setObject(article.bodyForIndex, forKey: DatabaseKey.body as NSString) updateDictionary[DatabaseKey.body] = article.bodyForIndex
} }
updateRowsWithDictionary(updateDictionary, whereKey: DatabaseKey.rowID, matches: searchInfo.rowID, database: database) updateRowsWithDictionary(updateDictionary, whereKey: DatabaseKey.rowID, matches: searchInfo.rowID, database: database)
} }

@ -1 +1 @@
Subproject commit 3d57e08e9d905c432e86a2544ccfb1390257aec2 Subproject commit 92f72ec2c179a5cb5c1b69df8ab4d428d598c243