Skip to content

Commit

Permalink
Improve performance in cases of small or no diff updates
Browse files Browse the repository at this point in the history
  • Loading branch information
kyleve committed Jul 1, 2020
1 parent 34c00d4 commit db0a1cc
Show file tree
Hide file tree
Showing 22 changed files with 590 additions and 208 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

### Fixed

- [Significant performance improvements](https://github.com/kyleve/Listable/pull/179) for list updates which contain either no changes, or only in-place updates to existing items and sections. In many cases, these updates will now be 80% faster. This change also improves performance for other types of changes such as insertions, removals, or moves, but not to the same degree.

### Added

- [Added additional layout configuration options](https://github.com/kyleve/Listable/pull/173/): `headerToFirstSectionSpacing` and `lastSectionToFooterSpacing` now let you control the spacing between the list header and content, and the content and the list footer.
Expand Down
22 changes: 22 additions & 0 deletions Debugging and Logging/SignpostLoggerTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// SignpostLoggerTests.swift
// Listable-Unit-Tests
//
// Created by Kyle Van Essen on 6/30/20.
//

import XCTest
@testable import Listable


class SignpostLoggerTests : XCTestCase {

func test_isLoggingEnabled_true_in_debug()
{
/// Test to validate that no one has accidentally committed disabling `isLoggingEnabled` in `DEBUG`.

#if DEBUG
XCTAssertEqual(true, SignpostLogger.isLoggingEnabled)
#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ final class AutoScrollingViewController : UIViewController

@objc private func addItem()
{
let last = items.last?.identifier.toAny
let last = items.last?.identifier

items.append(BottomPinnedItem(text: "Item \(items.count)"))

Expand Down
4 changes: 4 additions & 0 deletions Demo/Sources/Demos/XcodePreviewDemo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import SwiftUI
import BlueprintUICommonControls
import BlueprintLists
import Listable


fileprivate struct XcodePreviewDemoContent : BlueprintItemContent, Equatable
Expand Down Expand Up @@ -83,6 +84,7 @@ fileprivate struct XcodePreviewDemoContent : BlueprintItemContent, Equatable
}
}

#if DEBUG && canImport(SwiftUI) && !arch(i386) && !arch(arm)

@available(iOS 13.0, *)
struct ElementPreview : PreviewProvider {
Expand All @@ -94,3 +96,5 @@ struct ElementPreview : PreviewProvider {
)
}
}

#endif
24 changes: 22 additions & 2 deletions Listable/Sources/Debugging and Logging/SignpostLogger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,29 @@ struct SignpostLoggingInfo {
///
/// Resources
/// ---------
/// WWDC: https://developer.apple.com/videos/play/wwdc2018/405/
/// WWDC 2018: https://developer.apple.com/videos/play/wwdc2018/405/
/// WWDC 2019: https://developer.apple.com/wwdc20/10168
/// Swift By Sundell: https://www.swiftbysundell.com/wwdc2018/getting-started-with-signposts/
///
struct SignpostLogger {


#if DEBUG
/// You may temporarily set this param to `false` to disable os_signpost logging,
/// for example if debugging performance in Instruments.app.
///
/// Note that tests will fail while this is set to `false` in `DEBUG`, to ensure
/// this is not accidentally commited as `false`.
static let isLoggingEnabled = true
#else
static let isLoggingEnabled = false
#endif

static func log<Output>(log : OSLog, name: StaticString, for loggable : SignpostLoggable? = nil, _ output : () -> Output) -> Output
{
guard self.isLoggingEnabled else {
return output()
}

self.log(.begin, log: log, name: name, for: loggable)

let output = output()
Expand All @@ -67,6 +83,10 @@ struct SignpostLogger {

static func log(_ type : EventType, log : OSLog, name: StaticString, for loggable : SignpostLoggable? = nil)
{
guard self.isLoggingEnabled else {
return
}

if #available(iOS 12.0, *) {
if let loggable = loggable {
os_signpost(
Expand Down
56 changes: 17 additions & 39 deletions Listable/Sources/Identifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,21 @@
import Foundation


public final class AnyIdentifier : Hashable, CustomDebugStringConvertible
public class AnyIdentifier : Hashable, CustomDebugStringConvertible
{
private let value : AnyHashable
private let representedType : ObjectIdentifier

fileprivate let value : AnyHashable

private let hash : Int

public init<Represented>(_ value : Identifier<Represented>)
fileprivate init(type : ObjectIdentifier, value : AnyHashable)
{
self.value = AnyHashable(value)
self.representedType = type
self.value = value

var hasher = Hasher()
hasher.combine(self.representedType)
hasher.combine(self.value)
self.hash = hasher.finalize()
}
Expand All @@ -27,7 +31,7 @@ public final class AnyIdentifier : Hashable, CustomDebugStringConvertible

public static func == (lhs: AnyIdentifier, rhs: AnyIdentifier) -> Bool
{
return lhs.hash == rhs.hash && lhs.value == rhs.value
return lhs.hash == rhs.hash && lhs.representedType == rhs.representedType && lhs.value == rhs.value
}

// MARK: Hashable
Expand All @@ -39,19 +43,14 @@ public final class AnyIdentifier : Hashable, CustomDebugStringConvertible

// MARK: CustomDebugStringConvertible

public var debugDescription: String {
self.value.identifierContentString
public var debugDescription : String {
fatalError()
}
}


public final class Identifier<Represented> : Hashable, CustomDebugStringConvertible
public final class Identifier<Represented> : AnyIdentifier
{
private let type : ObjectIdentifier
private let value : AnyHashable

private let hash : Int

/// Identifier which identifies by the type of `Represented` only.
/// If you have multiple of `Represented` within a list, it is recommended that
/// you use `init(_ value:)` to provide a unique inner value.
Expand All @@ -62,36 +61,15 @@ public final class Identifier<Represented> : Hashable, CustomDebugStringConverti

public init<Value:Hashable>(_ value : Value)
{
self.value = AnyHashable(value)
self.type = ObjectIdentifier(Represented.self)

var hasher = Hasher()
hasher.combine(self.type)
hasher.combine(self.value)
self.hash = hasher.finalize()
}

public var toAny : AnyIdentifier {
AnyIdentifier(self)
}

// MARK: Equatable

public static func == (lhs: Identifier<Represented>, rhs: Identifier<Represented>) -> Bool
{
return lhs.hash == rhs.hash && lhs.type == rhs.type && lhs.value == rhs.value
}

// MARK: Hashable

public func hash(into hasher: inout Hasher)
{
hasher.combine(self.hash)
super.init(
type: ObjectIdentifier(Represented.self),
value: AnyHashable(value)
)
}

// MARK: CustomDebugStringConvertible

public var debugDescription: String {
public override var debugDescription : String {
"Identifier<\(String(describing: Represented.self))>: \(self.value.identifierContentString)"
}
}
Expand Down
Loading

0 comments on commit db0a1cc

Please sign in to comment.