Fix crash when sort and filter change at the same time.

Fix edit table cell during reload
This commit is contained in:
relikd
2020-06-20 12:52:17 +02:00
parent 778f377e42
commit 26f6ea1a9a
3 changed files with 68 additions and 77 deletions

View File

@@ -1111,7 +1111,7 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = main/main.entitlements; CODE_SIGN_ENTITLEMENTS = main/main.entitlements;
CURRENT_PROJECT_VERSION = 19; CURRENT_PROJECT_VERSION = 20;
INFOPLIST_FILE = main/Info.plist; INFOPLIST_FILE = main/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@@ -1130,7 +1130,7 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = main/main.entitlements; CODE_SIGN_ENTITLEMENTS = main/main.entitlements;
CURRENT_PROJECT_VERSION = 19; CURRENT_PROJECT_VERSION = 20;
INFOPLIST_FILE = main/Info.plist; INFOPLIST_FILE = main/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@@ -1149,7 +1149,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = GlassVPN/GlassVPN.entitlements; CODE_SIGN_ENTITLEMENTS = GlassVPN/GlassVPN.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 19; CURRENT_PROJECT_VERSION = 20;
INFOPLIST_FILE = GlassVPN/Info.plist; INFOPLIST_FILE = GlassVPN/Info.plist;
MARKETING_VERSION = 1.0.0; MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = "de.uni-bamberg.psi.AppCheck.VPN"; PRODUCT_BUNDLE_IDENTIFIER = "de.uni-bamberg.psi.AppCheck.VPN";
@@ -1167,7 +1167,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = GlassVPN/GlassVPN.entitlements; CODE_SIGN_ENTITLEMENTS = GlassVPN/GlassVPN.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 19; CURRENT_PROJECT_VERSION = 20;
INFOPLIST_FILE = GlassVPN/Info.plist; INFOPLIST_FILE = GlassVPN/Info.plist;
MARKETING_VERSION = 1.0.0; MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = "de.uni-bamberg.psi.AppCheck.VPN"; PRODUCT_BUNDLE_IDENTIFIER = "de.uni-bamberg.psi.AppCheck.VPN";

View File

@@ -1,24 +1,15 @@
import UIKit import UIKit
protocol FilterPipelineDelegate: AnyObject { protocol FilterPipelineDelegate: AnyObject {
/// Call `reloadData()` on main thread. /// Call `reloadData()`
/// - Warning: This function may be called from a background thread.
func filterPipelineDidReset() func filterPipelineDidReset()
/// Call `safeDeleteRows()`
/// Call `safeDeleteRows()` on main thread.
/// - Warning: This function may be called from a background thread.
func filterPipeline(delete rows: [Int]) func filterPipeline(delete rows: [Int])
/// Call `safeInsertRow()`
/// Call `safeInsertRow()` on main thread.
/// - Warning: This function may be called from a background thread.
func filterPipeline(insert row: Int) func filterPipeline(insert row: Int)
/// Call `safeReloadRow()`
/// Call `safeReloadRow()` on main thread.
/// - Warning: This function may be called from a background thread.
func filterPipeline(update row: Int) func filterPipeline(update row: Int)
/// Call `safeMoveRow()`
/// Call `safeMoveRow()` on main thread.
/// - Warning: This function may be called from a background thread.
func filterPipeline(move oldRow: Int, to newRow: Int) func filterPipeline(move oldRow: Int, to newRow: Int)
} }

View File

@@ -101,14 +101,17 @@ extension GroupedDomainDataSource {
for (i, val) in logs.enumerated() { for (i, val) in logs.enumerated() {
logs[i].options = DomainFilter[val.domain] logs[i].options = DomainFilter[val.domain]
} }
DispatchQueue.main.sync {
pipeline.reset(dataSource: logs) pipeline.reset(dataSource: logs)
} }
}
func syncUpdate(_: SyncUpdate, insert rows: SQLiteRowRange, affects: SyncUpdateEnd) { func syncUpdate(_: SyncUpdate, insert rows: SQLiteRowRange, affects: SyncUpdateEnd) {
guard let latest = AppDB?.dnsLogsGrouped(range: rows, parentDomain: parent) else { guard let latest = AppDB?.dnsLogsGrouped(range: rows, parentDomain: parent) else {
assertionFailure("NotifySyncInsert fired with empty range") assertionFailure("NotifySyncInsert fired with empty range")
return return
} }
DispatchQueue.main.sync {
cellAnimationsGroup(if: latest.count > 14) cellAnimationsGroup(if: latest.count > 14)
for x in latest { for x in latest {
if let (i, obj) = pipeline.dataSourceGet(where: { $0.domain == x.domain }) { if let (i, obj) = pipeline.dataSourceGet(where: { $0.domain == x.domain }) {
@@ -121,6 +124,7 @@ extension GroupedDomainDataSource {
} }
cellAnimationsCommit() cellAnimationsCommit()
} }
}
func syncUpdate(_ sender: SyncUpdate, remove rows: SQLiteRowRange, affects: SyncUpdateEnd) { func syncUpdate(_ sender: SyncUpdate, remove rows: SQLiteRowRange, affects: SyncUpdateEnd) {
if affects == .Latest { if affects == .Latest {
@@ -132,6 +136,7 @@ extension GroupedDomainDataSource {
outdated.count > 0 else { outdated.count > 0 else {
return return
} }
DispatchQueue.main.sync {
cellAnimationsGroup(if: outdated.count > 14) cellAnimationsGroup(if: outdated.count > 14)
var listOfDeletes: [Int] = [] var listOfDeletes: [Int] = []
for x in outdated { for x in outdated {
@@ -148,6 +153,7 @@ extension GroupedDomainDataSource {
pipeline.remove(indices: listOfDeletes.sorted()) pipeline.remove(indices: listOfDeletes.sorted())
cellAnimationsCommit() cellAnimationsCommit()
} }
}
func syncUpdate(_ sender: SyncUpdate, partialRemove affectedFQDN: String) { func syncUpdate(_ sender: SyncUpdate, partialRemove affectedFQDN: String) {
let affectedParent = affectedFQDN.extractDomain() let affectedParent = affectedFQDN.extractDomain()
@@ -155,12 +161,13 @@ extension GroupedDomainDataSource {
return // does not affect current table return // does not affect current table
} }
let affected = (parent == nil ? affectedParent : affectedFQDN) let affected = (parent == nil ? affectedParent : affectedFQDN)
let updated = AppDB?.dnsLogsGrouped(range: sender.rows, matchingDomain: affected, parentDomain: parent)?.first
DispatchQueue.main.sync {
guard let old = pipeline.dataSourceGet(where: { $0.domain == affected }) else { guard let old = pipeline.dataSourceGet(where: { $0.domain == affected }) else {
// can only happen if delete sheet is open while background sync removed the element // can only happen if delete sheet is open while background sync removed the element
return return
} }
if var updated = AppDB?.dnsLogsGrouped(range: sender.rows, matchingDomain: affected, if var updated = updated {
parentDomain: parent)?.first {
assert(old.object.domain == updated.domain) assert(old.object.domain == updated.domain)
updated.options = DomainFilter[updated.domain] updated.options = DomainFilter[updated.domain]
pipeline.update(updated, at: old.index) pipeline.update(updated, at: old.index)
@@ -169,6 +176,7 @@ extension GroupedDomainDataSource {
} }
} }
} }
}
// ################################# // #################################
@@ -180,9 +188,8 @@ extension GroupedDomainDataSource {
extension GroupedDomainDataSource { extension GroupedDomainDataSource {
/// Sets `pipeline.delegate = nil` to disable individual cell animations (update, insert, delete & move). /// Sets `pipeline.delegate = nil` to disable individual cell animations (update, insert, delete & move).
private func cellAnimationsGroup(if condition: Bool = true) { private func cellAnimationsGroup(if condition: Bool = true) {
if condition { pipeline.delegate = nil } if condition || delegate?.tableView.isFrontmost == false {
if pipeline.delegate != nil { pipeline.delegate = nil
onMain { if !$0.isFrontmost { self.pipeline.delegate = nil } }
} }
} }
/// No-Op if cell animations are enabled already. /// No-Op if cell animations are enabled already.
@@ -190,34 +197,27 @@ extension GroupedDomainDataSource {
private func cellAnimationsCommit() { private func cellAnimationsCommit() {
if pipeline.delegate == nil { if pipeline.delegate == nil {
pipeline.delegate = self pipeline.delegate = self
onMain { $0.reloadData() } delegate?.tableView.reloadData()
}
}
/// Perform table view manipulations on main thread.
/// Set `delegate = nil` to disable `tableView` animations.
private func onMain(_ closure: (UITableView) -> Void) {
if Thread.isMainThread {
if let tv = delegate?.tableView { closure(tv) }
} else {
DispatchQueue.main.sync {
if let tv = delegate?.tableView { closure(tv) }
}
} }
} }
// TODO: Collect animations and post them in a single animations block. // TODO: Collect animations and post them in a single animations block.
// This will require enormous work to translate then into a final set. // This will require enormous work to translate them into a final set.
func filterPipelineDidReset() { onMain { $0.reloadData() } } func filterPipelineDidReset() { delegate?.tableView.reloadData() }
func filterPipeline(delete rows: [Int]) { onMain { $0.safeDeleteRows(rows) } } func filterPipeline(delete rows: [Int]) { delegate?.tableView.safeDeleteRows(rows) }
func filterPipeline(insert row: Int) { onMain { $0.safeInsertRow(row, with: .left) } } func filterPipeline(insert row: Int) { delegate?.tableView.safeInsertRow(row, with: .left) }
func filterPipeline(update row: Int) { onMain { $0.safeReloadRow(row) } } func filterPipeline(update row: Int) {
func filterPipeline(move oldRow: Int, to newRow: Int) { guard let tv = delegate?.tableView else { return }
onMain { if !tv.isEditing { tv.safeReloadRow(row) }
$0.safeMoveRow(oldRow, to: newRow) else if tv.isFrontmost == true {
if $0.isFrontmost { // onMain ensures delegate is set delegate?.groupedDomainDataSource(needsUpdate: row)
delegate!.groupedDomainDataSource(needsUpdate: newRow)
} }
} }
func filterPipeline(move oldRow: Int, to newRow: Int) {
delegate?.tableView.safeMoveRow(oldRow, to: newRow)
if delegate?.tableView.isFrontmost == true {
delegate?.groupedDomainDataSource(needsUpdate: newRow)
}
} }
} }