#1607 (widget is slow to reload)

- Added logging to help check performance (look in Console, filter on "🚧")
This commit is contained in:
David Sinclair 2022-02-01 17:05:03 -08:00
parent 742ae95501
commit 64d1e83af7
8 changed files with 153 additions and 25 deletions

View file

@ -32,7 +32,7 @@
173CB31126BCE94700BA872A /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 173CB31026BCE94700BA872A /* SwiftUI.framework */; };
173CB31426BCE94700BA872A /* WidgetExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173CB31326BCE94700BA872A /* WidgetExtension.swift */; };
173CB31626BCE94A00BA872A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 173CB31526BCE94A00BA872A /* Assets.xcassets */; };
173CB31A26BCE94A00BA872A /* Widget Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 173CB30D26BCE94700BA872A /* Widget Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
173CB31A26BCE94A00BA872A /* NewsBlur Widget.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 173CB30D26BCE94700BA872A /* NewsBlur Widget.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
1740C6881C10FD75005EA453 /* theme_color_dark.png in Resources */ = {isa = PBXBuildFile; fileRef = 1740C6841C10FD75005EA453 /* theme_color_dark.png */; };
1740C6891C10FD75005EA453 /* theme_color_light.png in Resources */ = {isa = PBXBuildFile; fileRef = 1740C6851C10FD75005EA453 /* theme_color_light.png */; };
1740C68A1C10FD75005EA453 /* theme_color_medium.png in Resources */ = {isa = PBXBuildFile; fileRef = 1740C6861C10FD75005EA453 /* theme_color_medium.png */; };
@ -90,6 +90,7 @@
17876BA51C99137B0055DD15 /* accessory_disclosure@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 17876BA31C99137B0055DD15 /* accessory_disclosure@2x.png */; };
1788939D249332E6004CBA4E /* g_icn_search.png in Resources */ = {isa = PBXBuildFile; fileRef = 1788939C249332E6004CBA4E /* g_icn_search.png */; };
1791C21526C4C7BC00D815AA /* WidgetStoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1791C21426C4C7BC00D815AA /* WidgetStoryView.swift */; };
17997C5827A8FDD100483E69 /* WidgetDebugTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17997C5727A8FDD100483E69 /* WidgetDebugTimer.swift */; };
179DD9CF23DFDD51007BFD21 /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 179DD9CE23DFDD51007BFD21 /* CloudKit.framework */; };
17A396D924F86A8F0023C9E2 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 17A396D824F86A8F0023C9E2 /* MainInterface.storyboard */; };
17AACFE122279A3C00DE6EA4 /* autoscroll_resume@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 17AACFD722279A3900DE6EA4 /* autoscroll_resume@2x.png */; };
@ -702,7 +703,7 @@
files = (
177551DF238E228A00E27818 /* Old NewsBlur Latest.appex in Embed App Extensions */,
1749391B1C251BFE003D98AA /* Share Extension.appex in Embed App Extensions */,
173CB31A26BCE94A00BA872A /* Widget Extension.appex in Embed App Extensions */,
173CB31A26BCE94A00BA872A /* NewsBlur Widget.appex in Embed App Extensions */,
);
name = "Embed App Extensions";
runOnlyForDeploymentPostprocessing = 0;
@ -734,7 +735,7 @@
172AD273251D9F40000BB264 /* Storyboards.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storyboards.swift; sourceTree = "<group>"; };
17362ADB23639B4E00A0FCCC /* OfflineFetchText.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = OfflineFetchText.h; path = offline/OfflineFetchText.h; sourceTree = "<group>"; };
17362ADC23639B4E00A0FCCC /* OfflineFetchText.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = OfflineFetchText.m; path = offline/OfflineFetchText.m; sourceTree = "<group>"; };
173CB30D26BCE94700BA872A /* Widget Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Widget Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
173CB30D26BCE94700BA872A /* NewsBlur Widget.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "NewsBlur Widget.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
173CB30E26BCE94700BA872A /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };
173CB31026BCE94700BA872A /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
173CB31326BCE94700BA872A /* WidgetExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetExtension.swift; sourceTree = "<group>"; };
@ -805,6 +806,7 @@
17876BA31C99137B0055DD15 /* accessory_disclosure@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "accessory_disclosure@2x.png"; sourceTree = "<group>"; };
1788939C249332E6004CBA4E /* g_icn_search.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = g_icn_search.png; sourceTree = "<group>"; };
1791C21426C4C7BC00D815AA /* WidgetStoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetStoryView.swift; sourceTree = "<group>"; };
17997C5727A8FDD100483E69 /* WidgetDebugTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetDebugTimer.swift; sourceTree = "<group>"; };
179DD9CC23DFD20E007BFD21 /* BridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BridgingHeader.h; path = "Other Sources/BridgingHeader.h"; sourceTree = "<group>"; };
179DD9CE23DFDD51007BFD21 /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; };
17A396D824F86A8F0023C9E2 /* MainInterface.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = MainInterface.storyboard; sourceTree = "<group>"; };
@ -1657,6 +1659,7 @@
1723388D26BE440400610784 /* WidgetFeed.swift */,
1723388C26BE440400610784 /* WidgetStory.swift */,
1723388A26BE43EB00610784 /* WidgetLoader.swift */,
17997C5727A8FDD100483E69 /* WidgetDebugTimer.swift */,
173CB31526BCE94A00BA872A /* Assets.xcassets */,
173CB31726BCE94A00BA872A /* Info.plist */,
173CB31B26BCE94A00BA872A /* WidgetExtension.entitlements */,
@ -1756,7 +1759,7 @@
174939101C251BFE003D98AA /* Share Extension.appex */,
FF8A94971DE3BB77000A4C31 /* Story Notification Service Extension.appex */,
177551D3238E228A00E27818 /* Old NewsBlur Latest.appex */,
173CB30D26BCE94700BA872A /* Widget Extension.appex */,
173CB30D26BCE94700BA872A /* NewsBlur Widget.appex */,
);
name = Products;
sourceTree = "<group>";
@ -2867,9 +2870,9 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
173CB30C26BCE94700BA872A /* Widget Extension */ = {
173CB30C26BCE94700BA872A /* NewsBlur Widget */ = {
isa = PBXNativeTarget;
buildConfigurationList = 173CB31C26BCE94A00BA872A /* Build configuration list for PBXNativeTarget "Widget Extension" */;
buildConfigurationList = 173CB31C26BCE94A00BA872A /* Build configuration list for PBXNativeTarget "NewsBlur Widget" */;
buildPhases = (
173CB30926BCE94700BA872A /* Sources */,
173CB30A26BCE94700BA872A /* Frameworks */,
@ -2879,9 +2882,9 @@
);
dependencies = (
);
name = "Widget Extension";
name = "NewsBlur Widget";
productName = WidgetExtension;
productReference = 173CB30D26BCE94700BA872A /* Widget Extension.appex */;
productReference = 173CB30D26BCE94700BA872A /* NewsBlur Widget.appex */;
productType = "com.apple.product-type.app-extension";
};
1749390F1C251BFE003D98AA /* Share Extension */ = {
@ -3051,7 +3054,7 @@
1749390F1C251BFE003D98AA /* Share Extension */,
FF8A94961DE3BB77000A4C31 /* Story Notification Service Extension */,
177551D2238E228A00E27818 /* Old Widget Extension */,
173CB30C26BCE94700BA872A /* Widget Extension */,
173CB30C26BCE94700BA872A /* NewsBlur Widget */,
);
};
/* End PBXProject section */
@ -3540,6 +3543,7 @@
173CB31426BCE94700BA872A /* WidgetExtension.swift in Sources */,
1723388E26BE440400610784 /* WidgetStory.swift in Sources */,
1723389426C3775B00610784 /* WidgetBarView.swift in Sources */,
17997C5827A8FDD100483E69 /* WidgetDebugTimer.swift in Sources */,
1723388F26BE440400610784 /* WidgetFeed.swift in Sources */,
1791C21526C4C7BC00D815AA /* WidgetStoryView.swift in Sources */,
);
@ -3745,7 +3749,7 @@
/* Begin PBXTargetDependency section */
173CB31926BCE94A00BA872A /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 173CB30C26BCE94700BA872A /* Widget Extension */;
target = 173CB30C26BCE94700BA872A /* NewsBlur Widget */;
targetProxy = 173CB31826BCE94A00BA872A /* PBXContainerItemProxy */;
};
1749391A1C251BFE003D98AA /* PBXTargetDependency */ = {
@ -4415,7 +4419,7 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
173CB31C26BCE94A00BA872A /* Build configuration list for PBXNativeTarget "Widget Extension" */ = {
173CB31C26BCE94A00BA872A /* Build configuration list for PBXNativeTarget "NewsBlur Widget" */ = {
isa = XCConfigurationList;
buildConfigurations = (
173CB31D26BCE94A00BA872A /* Debug */,

View file

@ -16,8 +16,8 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "173CB30C26BCE94700BA872A"
BuildableName = "Widget Extension.appex"
BlueprintName = "Widget Extension"
BuildableName = "NewsBlur Widget.appex"
BlueprintName = "NewsBlur Widget"
ReferencedContainer = "container:NewsBlur.xcodeproj">
</BuildableReference>
</BuildActionEntry>

View file

@ -14,7 +14,7 @@
<objects>
<viewController storyboardIdentifier="DetailViewController" useStoryboardIdentifierAsRestorationIdentifier="YES" id="djW-7k-haK" customClass="DetailViewController" customModule="NewsBlur" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jTZ-4O-xyT">
<rect key="frame" x="0.0" y="0.0" width="818.5" height="834"/>
<rect key="frame" x="0.0" y="0.0" width="1194" height="834"/>
<subviews>
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="bPa-u1-Aml">
<rect key="frame" x="0.0" y="74" width="1194" height="580"/>

View file

@ -248,7 +248,7 @@ class WidgetCache {
stories = try decoder.decode([Story].self, from: json)
} catch {
print("Error \(error)")
NSLog("Error \(error)")
}
}
@ -269,7 +269,7 @@ class WidgetCache {
try json.write(to: url)
} catch {
print("Error \(error)")
NSLog("Error \(error)")
}
}
@ -295,7 +295,7 @@ class WidgetCache {
try image.pngData()?.write(to: imageURL)
} catch {
print("Image error: \(error)")
NSLog("Image saving error: \(error)")
}
}
@ -319,7 +319,7 @@ class WidgetCache {
storyImageCache[identifier] = nil
}
} catch {
print("Flush story images error: \(error)")
NSLog("Flush story images error: \(error)")
}
}
@ -446,7 +446,7 @@ class WidgetCache {
return image
} catch {
print("Image error: \(error)")
NSLog("Cached image loading error: \(error)")
}
return nil

View file

@ -0,0 +1,110 @@
//
// WidgetDebugTimer.swift
// Widget Extension
//
// Created by David Sinclair on 2022-01-31.
// Based on Dejal code.
//
import Foundation
/// Timer for debugging performance.
class WidgetDebugTimer {
/// Private singleton shared instance. Access via the class functions.
private static let shared = WidgetDebugTimer()
/// Private initializer to prevent others constructing a new instance.
private init() {
formatter = NumberFormatter()
formatter.minimumIntegerDigits = 1
formatter.minimumFractionDigits = 6
formatter.maximumFractionDigits = 6
}
/// Information about each timer operation.
private struct Info {
/// The date the operation was started.
var start: Date
/// The date this step was started.
var step: Date
/// The indentation level.
var level: Int
// If I ever add the closure-based long-running timers, add those properties.
}
/// A dictionary of operation info, keyed on the operation string.
private typealias InfoDictionary = [String : Info]
/// A dictionary of operation info, keyed on the operation string.
private var info = InfoDictionary()
/// A number formatter for the number of seconds.
private var formatter: NumberFormatter
/// Given an operation name, starts a debug timer. Use `print(_:step:)` after the code to time.
///
/// - Parameter operation: The name of the operation to time (used as both a key to group timers, and a debug label).
/// - Parameter level: How much to indent the operation, for nested timers. Defaults to zero (no indentation).
/// - Returns: The operation name, so it can be assigned to a variable instead of typing it again. Discardable.
@discardableResult
class func start(_ operation: String, level: Int = 0) -> String {
let date = Date()
shared.info[operation] = Info(start: date, step: date, level: level)
return operation
}
/// Given an operation name, that must have been previously started via `start(_:)`, prints the total time so far and (if a step is provided) the time since that this step took, i.e. since the start or the previous step.
///
/// - Parameter operation: The name of the operation to time.
/// - Parameter step: The name of the step of the operation. May be omitted if there's only one interesting step.
class func print(_ operation: String, step: String? = nil) {
let date = Date()
guard let currentInfo = shared.info[operation] else {
NSLog("\(operation): forgot to call start(_:) first!")
return
}
let totalDuration = date.timeIntervalSince(currentInfo.start)
guard let step = step else {
NSLog("\(String(repeating: " ", count: currentInfo.level * 2))\(operation) took \(shared.formatter.string(from: NSNumber(value: totalDuration)) ?? "?") seconds")
return
}
let stepDuration = date.timeIntervalSince(currentInfo.step)
var newInfo = currentInfo
let alert = stepDuration < 0.001 ? "" : stepDuration < 0.01 ? " 🚨" : stepDuration < 0.1 ? " 🚨🚨" : stepDuration < 1.0 ? " 🚨🚨🚨" : " 🚨🚨🚨🚨"
NSLog("\(String(repeating: " ", count: currentInfo.level * 2))\(operation): \(step) took \(shared.formatter.string(from: NSNumber(value: stepDuration)) ?? "?") seconds (total \(shared.formatter.string(from: NSNumber(value: totalDuration)) ?? "?") seconds)\(alert)")
newInfo.step = date
shared.info[operation] = newInfo
}
}
/// Convenience timer for debugging performance of a code scope; automatically prints the info when exiting the scope.
class DebugScopeTimer {
/// The current operation.
let operation: String
/// Initializer. Assign this to a variable to establish the scope, e.g. `let debug = DebugScopeTimer("Thing")` (this will result in a warning, but that can be useful to remind me to remove the timer; can't assign to underscore, as that is immediately released).
///
/// - Parameter operation: The name of the operation to time.
init(_ operation: String) {
self.operation = operation
WidgetDebugTimer.start(operation)
}
/// Deinitializer. Prints the info when exiting the scope.
deinit {
WidgetDebugTimer.print(operation)
}
}

View file

@ -23,13 +23,19 @@ struct Provider: TimelineProvider {
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
let operation = WidgetDebugTimer.start("🚧 getTimeline")
cache.loadCachedStories()
WidgetDebugTimer.print(operation, step: "loadCachedStories")
if context.isPreview && !cache.stories.isEmpty {
return
}
cache.load {
WidgetDebugTimer.print(operation, step: "cache.load()")
var entries: [SimpleEntry] = []
// Generate a timeline consisting of five entries an hour apart, starting from the current date.
@ -47,6 +53,8 @@ struct Provider: TimelineProvider {
let timeline = Timeline(entries: entries, policy: .atEnd)
WidgetDebugTimer.print(operation, step: "making timeline")
let imageRequestGroup = DispatchGroup()
for feed in cache.feeds {
@ -66,6 +74,8 @@ struct Provider: TimelineProvider {
}
imageRequestGroup.notify(queue: .main) {
WidgetDebugTimer.print(operation, step: "requesting images")
completion(timeline)
}
}

View file

@ -37,6 +37,8 @@ struct Feed: Identifiable {
///
/// - Parameter dictionary: Dictionary representation.
init(from dictionary: Dictionary) {
let operation = WidgetDebugTimer.start("reading feed")
id = dictionary[DictionaryKeys.id] as? String ?? ""
title = dictionary[DictionaryKeys.title] as? String ?? ""
@ -51,6 +53,8 @@ struct Feed: Identifiable {
} else {
rightColor = Self.from(hexString: "505050")
}
WidgetDebugTimer.print(operation, step: "title: \(title)")
}
/// Initializer for a sample.
@ -93,7 +97,7 @@ struct Feed: Identifiable {
blue = Double( hex & 0x0000FF) / 255
}
print("Reading color from '\(hexString)': red: \(red), green: \(green), blue: \(blue), alpha: \(alpha)")
NSLog("Reading color from '\(hexString)': red: \(red), green: \(green), blue: \(blue), alpha: \(alpha)")
return Color(.sRGB, red: red, green: green, blue: blue, opacity: alpha)
}

View file

@ -46,17 +46,17 @@ class Loader: NSObject, URLSessionDataDelegate {
completionHandler(.allow)
}
func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
print("error: \(error.debugDescription)")
NSLog("error: \(error.debugDescription)")
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
print("data: \(data)")
NSLog("🚧 \(dataTask.currentRequest?.url?.path ?? "?") data: \(data)")
receivedData.append(data)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
completion(.failure(error))