From 5d3e534b1c474b4b6ab7a2868a571bdd932b83da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=88=B4=E9=93=AD?= Date: Tue, 12 Nov 2024 01:30:44 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=AF=E5=8A=A8=E6=97=B6=E9=97=B4=E6=A3=80?= =?UTF-8?q?=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SwiftPamphletApp.xcodeproj/project.pbxproj | 31 +++++++ .../App/SwiftPamphletAppApp.swift | 80 +++++++++++++++- SwiftPamphletApp/HomeUI/HomeView.swift | 30 ------ SwiftPamphletApp/Info.plist | 8 ++ .../Performance/MetricManager.swift | 5 + .../Performance/NotificationPreheat.swift | 93 +++++++++++++++++++ SwiftPamphletApp/Performance/Perf.swift | 33 +++++++ .../Performance/TaskManager.swift | 11 +++ 8 files changed, 258 insertions(+), 33 deletions(-) create mode 100644 SwiftPamphletApp/Performance/NotificationPreheat.swift create mode 100644 SwiftPamphletApp/Performance/Perf.swift create mode 100644 SwiftPamphletApp/Performance/TaskManager.swift diff --git a/SwiftPamphletApp.xcodeproj/project.pbxproj b/SwiftPamphletApp.xcodeproj/project.pbxproj index 009164f0f..ec23ce97f 100644 --- a/SwiftPamphletApp.xcodeproj/project.pbxproj +++ b/SwiftPamphletApp.xcodeproj/project.pbxproj @@ -480,8 +480,12 @@ 3A3168932CE0FD53004DFC5C /* HomeiOSView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3168922CE0FD53004DFC5C /* HomeiOSView.swift */; }; 3A3168952CE0FFCE004DFC5C /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 3A3168942CE0FFCE004DFC5C /* Launch Screen.storyboard */; platformFilter = ios; }; 3A46DEF52BD7AAB2008AD993 /* SMDate in Frameworks */ = {isa = PBXBuildFile; productRef = 3A46DEF42BD7AAB2008AD993 /* SMDate */; }; + 3AA462362CE228CA00774B59 /* NotificationPreheat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA462352CE228CA00774B59 /* NotificationPreheat.swift */; }; + 3AA462382CE261CC00774B59 /* TaskManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA462372CE261CC00774B59 /* TaskManager.swift */; }; + 3AA4623D2CE2734E00774B59 /* AsyncAlgorithms in Frameworks */ = {isa = PBXBuildFile; productRef = 3AA4623C2CE2734E00774B59 /* AsyncAlgorithms */; }; 3ACA74FB2BD7964A0024B18E /* SMFile in Frameworks */ = {isa = PBXBuildFile; productRef = 3ACA74FA2BD7964A0024B18E /* SMFile */; }; 3ADBA84F2CE209FB00B0050B /* MetricManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ADBA84E2CE209FB00B0050B /* MetricManager.swift */; }; + 3ADBA8512CE216E900B0050B /* Perf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3ADBA8502CE216E900B0050B /* Perf.swift */; }; 3AE0D59A2BAB0A0600D6D925 /* DeveloperListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE0D5992BAB0A0600D6D925 /* DeveloperListView.swift */; }; 3AE0D59E2BAB183100D6D925 /* EditDeveloper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE0D59D2BAB183100D6D925 /* EditDeveloper.swift */; }; 3AE4CA382BD7813D005BEF2C /* InfoOrganizer in Frameworks */ = {isa = PBXBuildFile; productRef = 3AE4CA372BD7813D005BEF2C /* InfoOrganizer */; }; @@ -959,7 +963,10 @@ 3A30EFD52CDA818B0029CB2F /* WeatherKit(ap).md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "WeatherKit(ap).md"; sourceTree = ""; }; 3A3168922CE0FD53004DFC5C /* HomeiOSView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeiOSView.swift; sourceTree = ""; }; 3A3168942CE0FFCE004DFC5C /* Launch Screen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = ""; }; + 3AA462352CE228CA00774B59 /* NotificationPreheat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPreheat.swift; sourceTree = ""; }; + 3AA462372CE261CC00774B59 /* TaskManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskManager.swift; sourceTree = ""; }; 3ADBA84E2CE209FB00B0050B /* MetricManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetricManager.swift; sourceTree = ""; }; + 3ADBA8502CE216E900B0050B /* Perf.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Perf.swift; sourceTree = ""; }; 3AE0D5992BAB0A0600D6D925 /* DeveloperListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperListView.swift; sourceTree = ""; }; 3AE0D59D2BAB183100D6D925 /* EditDeveloper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditDeveloper.swift; sourceTree = ""; }; 3AF2A2DE2BE22A8C00F3BE1B /* UnCategoryInfoListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnCategoryInfoListView.swift; sourceTree = ""; }; @@ -982,6 +989,7 @@ 08F14B402BBDA3EA005B46CC /* NukeUI in Frameworks */, 08F14B422BBDA3EA005B46CC /* NukeVideo in Frameworks */, 08F14B3E2BBDA3EA005B46CC /* NukeExtensions in Frameworks */, + 3AA4623D2CE2734E00774B59 /* AsyncAlgorithms in Frameworks */, 3A46DEF52BD7AAB2008AD993 /* SMDate in Frameworks */, 08BF26D32768A5B40064DDAC /* MarkdownUI in Frameworks */, 08397E322B9F39C100DFDD02 /* SwiftSoup in Frameworks */, @@ -2268,6 +2276,9 @@ isa = PBXGroup; children = ( 3ADBA84E2CE209FB00B0050B /* MetricManager.swift */, + 3ADBA8502CE216E900B0050B /* Perf.swift */, + 3AA462352CE228CA00774B59 /* NotificationPreheat.swift */, + 3AA462372CE261CC00774B59 /* TaskManager.swift */, ); path = Performance; sourceTree = ""; @@ -2339,6 +2350,7 @@ 3A46DEF42BD7AAB2008AD993 /* SMDate */, 0868D00A2BDD37280023C871 /* SMGitHub */, 082036792C02EEA7002FB5E3 /* SMUI */, + 3AA4623C2CE2734E00774B59 /* AsyncAlgorithms */, ); productName = SwiftPamphletApp; productReference = 086A5F032744E88E00FECE02 /* 戴铭的开发小册子.app */; @@ -2381,6 +2393,7 @@ 3A46DEF32BD7AAB2008AD993 /* XCLocalSwiftPackageReference "SwiftPamphletApp/SharePackage/SMDate" */, 0868D0092BDD37280023C871 /* XCLocalSwiftPackageReference "SwiftPamphletApp/SharePackage/SMGitHub" */, 082036782C02EEA7002FB5E3 /* XCLocalSwiftPackageReference "SwiftPamphletApp/SharePackage/SMUI" */, + 3AA4623B2CE2734E00774B59 /* XCRemoteSwiftPackageReference "swift-async-algorithms" */, ); productRefGroup = 086A5F042744E88E00FECE02 /* Products */; projectDirPath = ""; @@ -2845,6 +2858,7 @@ 3AE0D59A2BAB0A0600D6D925 /* DeveloperListView.swift in Sources */, 3A3168932CE0FD53004DFC5C /* HomeiOSView.swift in Sources */, 084417772B99BA3F0049297D /* SidebarView.swift in Sources */, + 3AA462362CE228CA00774B59 /* NotificationPreheat.swift in Sources */, 08ED801C2B9D1EEC0069B7EC /* SettingView.swift in Sources */, 0825E4872BC6596F00332378 /* EditCustomSearchView.swift in Sources */, 3AF2A2E92BE239BA00F3BE1B /* ArchivedInfosView.swift in Sources */, @@ -2870,10 +2884,12 @@ 08A7FF312BEB02EA00E12E5A /* GithubAccessTokenView.swift in Sources */, 086A5F372744ED9600FECE02 /* UserView.swift in Sources */, 086A5F522744EF4C00FECE02 /* ViewComponent.swift in Sources */, + 3ADBA8512CE216E900B0050B /* Perf.swift in Sources */, 08CD61FF27758B8A008C0935 /* Token.swift in Sources */, 3AE0D59E2BAB183100D6D925 /* EditDeveloper.swift in Sources */, 08EF35D22BECFDA80098E2D4 /* BookmarkModel.swift in Sources */, 086A5F462744EEB900FECE02 /* FundationFunction.swift in Sources */, + 3AA462382CE261CC00774B59 /* TaskManager.swift in Sources */, 084E1A6527B51EDB0072BBB6 /* AutoTask.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3037,6 +3053,7 @@ ); MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 6.6.4; + MERGED_BINARY_TYPE = none; OTHER_LDFLAGS = ( "-Xlinker", "-interposable", @@ -3090,6 +3107,7 @@ ); MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 6.6.4; + MERGED_BINARY_TYPE = none; PRODUCT_BUNDLE_IDENTIFIER = com.starming.SwiftPamphletAppByMing; PRODUCT_NAME = "戴铭的开发小册子"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -3194,6 +3212,14 @@ minimumVersion = 12.5.0; }; }; + 3AA4623B2CE2734E00774B59 /* XCRemoteSwiftPackageReference "swift-async-algorithms" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-async-algorithms.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.2; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -3253,6 +3279,11 @@ isa = XCSwiftPackageProductDependency; productName = SMDate; }; + 3AA4623C2CE2734E00774B59 /* AsyncAlgorithms */ = { + isa = XCSwiftPackageProductDependency; + package = 3AA4623B2CE2734E00774B59 /* XCRemoteSwiftPackageReference "swift-async-algorithms" */; + productName = AsyncAlgorithms; + }; 3ACA74FA2BD7964A0024B18E /* SMFile */ = { isa = XCSwiftPackageProductDependency; productName = SMFile; diff --git a/SwiftPamphletApp/App/SwiftPamphletAppApp.swift b/SwiftPamphletApp/App/SwiftPamphletAppApp.swift index c60b2a2ed..2cfd33ffa 100644 --- a/SwiftPamphletApp/App/SwiftPamphletAppApp.swift +++ b/SwiftPamphletApp/App/SwiftPamphletAppApp.swift @@ -11,16 +11,25 @@ import SwiftData import InfoOrganizer import SMFile import SMGitHub - +import os.signpost +import BackgroundTasks @main struct SwiftPamphletAppApp: App { + // 启动时间打点 + private let launchStartTime = DispatchTime.now() + private let signpostID = OSSignpostID(log: OSLog.default) + private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Launch") + #if os(macOS) @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate #elseif os(iOS) @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate #endif + init() { + os_signpost(.begin, log: log, name: "Launch", signpostID: signpostID) + let gr = GitHubReq.shared if SPC.gitHubAccessToken.isEmpty == true { gr.githubat = SPC.githubAccessToken() @@ -28,6 +37,9 @@ struct SwiftPamphletAppApp: App { gr.githubat = SPC.gitHubAccessToken } } + + @Environment(\.scenePhase) private var phase + var body: some Scene { WindowGroup { #if os(macOS) @@ -35,9 +47,45 @@ struct SwiftPamphletAppApp: App { .modelContainer(for: [IOInfo.self, DeveloperModel.self, BookmarkModel.self], isUndoEnabled: true) #elseif os(iOS) HomeiOSView() + .onAppear { + // background fetch + BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.starming.fetch", using: nil) { task in + self.handleAppRefresh(task: task as! BGAppRefreshTask) + } + scheduleAppRefresh() + + // 任务管理器 + // 使用示例 + + + #if DEBUG + // 主界面加载完成,记录终点 + let launchEndTime = DispatchTime.now() + let launchTime = Double(launchEndTime.uptimeNanoseconds - launchStartTime.uptimeNanoseconds) / 1_000_000_000 + print("手动打点 Post-main : \(launchTime) 秒") + + // 查看整体从进程创建到主界面加载完成时间,只在开发环境下执行 + if let processStartTime = Perf.getProcessRunningTime() { + print("进程创建到主界面加载完成时间: \(String(format: "%.2f", processStartTime)) 秒") + } else { + print("无法获取进程创建时间") + } + #endif + + // 记录启动结束 + os_signpost(.end, log: log, name: "Launch", signpostID: signpostID) + } + .onChange(of: phase) { oldValue, newValue in + switch newValue { + case .background: + print("background") + default: + break + } + } + // .modelContainer(for: [IOInfo.self, DeveloperModel.self, BookmarkModel.self], isUndoEnabled: true) #endif - } #if os(macOS) .windowToolbarStyle(UnifiedWindowToolbarStyle(showsTitle: true)) // 用来控制是否展示标题 @@ -49,8 +97,33 @@ struct SwiftPamphletAppApp: App { #endif } + + #if os(iOS) + // MARK: - Background Task + func scheduleAppRefresh() { + let request = BGAppRefreshTaskRequest(identifier: "com.starming.fetch") + request.earliestBeginDate = Date(timeIntervalSinceNow: 60 * 15) // 最早15分钟后运行 + do { + try BGTaskScheduler.shared.submit(request) + } catch { + print("后台任务请求失败: \(error)") + } + } + func handleAppRefresh(task: BGAppRefreshTask) { + // 确保任务在有限的时间内完成 + task.expirationHandler = { + // 如果任务时间即将耗尽,取消任务 + task.setTaskCompleted(success: false) + } + + // 模拟数据获取 + print("后台任务开始,获取数据") + } + #endif } + + // MARK: - UnCat #if os(macOS) @@ -73,9 +146,10 @@ class AppDelegate: NSObject, NSApplicationDelegate { #elseif os(iOS) class AppDelegate: NSObject, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { - _ = MetricKitManager.shared + _ = MetricKitManager.shared // 上报启动时间 print("didFinishLaunchingWithOptions") return true } + } #endif diff --git a/SwiftPamphletApp/HomeUI/HomeView.swift b/SwiftPamphletApp/HomeUI/HomeView.swift index 0bc06579b..614f20fe8 100644 --- a/SwiftPamphletApp/HomeUI/HomeView.swift +++ b/SwiftPamphletApp/HomeUI/HomeView.swift @@ -66,14 +66,6 @@ struct HomeView: View { selectedDataLinkString = sdLinkStr _ = WWDCViewModel() - // 性能用,只在开发环境下执行 - #if DEBUG - if let processStartTime = getProcessStartTime() { - print("进程创建时间: \(processStartTime) 秒") - } else { - print("无法获取进程创建时间") - } - #endif }) .onChange(of: selectedDataLinkString, { sdLinkStr = selectedDataLinkString @@ -96,26 +88,4 @@ struct HomeView: View { } -// 性能用 -// 通过 sysctl 获取进程创建时间 -func getProcessStartTime() -> Double? { - var kinfo = kinfo_proc() - var size = MemoryLayout.stride - var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()] - - let result = mib.withUnsafeMutableBufferPointer { mibPtr -> Int32 in - sysctl(mibPtr.baseAddress, 4, &kinfo, &size, nil, 0) - } - - guard result == 0 else { - print("sysctl 调用失败,错误码: \(result)") - return nil - } - let startTimeSec = kinfo.kp_proc.p_starttime.tv_sec - let startTimeUsec = kinfo.kp_proc.p_starttime.tv_usec - let startTime = TimeInterval(startTimeSec) + TimeInterval(startTimeUsec) / 1_000_000 - let currentTime = Date().timeIntervalSince1970 - return currentTime - startTime - -} diff --git a/SwiftPamphletApp/Info.plist b/SwiftPamphletApp/Info.plist index 870284daf..dcd8b884d 100644 --- a/SwiftPamphletApp/Info.plist +++ b/SwiftPamphletApp/Info.plist @@ -2,9 +2,17 @@ + BGTaskSchedulerPermittedIdentifiers + + com.starming.fetch + CFBundleLocalizations zh + UIBackgroundModes + + fetch + diff --git a/SwiftPamphletApp/Performance/MetricManager.swift b/SwiftPamphletApp/Performance/MetricManager.swift index f3d056026..1abef16ef 100644 --- a/SwiftPamphletApp/Performance/MetricManager.swift +++ b/SwiftPamphletApp/Performance/MetricManager.swift @@ -19,12 +19,17 @@ import MetricKit let metricManager = MXMetricManager.shared metricManager.add(self) } + + deinit { + MXMetricManager.shared.remove(self) + } } extension MetricKitManager { #if os(iOS) @available(iOS 13.0, *) func didReceive(_ payloads: [MXMetricPayload]) { + // 获取启动时间数据 guard let firstPayload = payloads.first else { return } print("Launch Time Data: \(firstPayload.dictionaryRepresentation())") } diff --git a/SwiftPamphletApp/Performance/NotificationPreheat.swift b/SwiftPamphletApp/Performance/NotificationPreheat.swift new file mode 100644 index 000000000..35ba1ca23 --- /dev/null +++ b/SwiftPamphletApp/Performance/NotificationPreheat.swift @@ -0,0 +1,93 @@ +// +// NotificationService.swift +// SwiftPamphletApp +// +// Created by Ming on 2024/11/11. +// + +import Foundation +import UserNotifications +import SwiftUI + +struct NotificationPreheatDemoView: View { + var body: some View { + VStack { + Button("请求通知授权") { + NotificationService.shared.requestAuthorization { granted in + if granted { + print("授权成功") + } else { + print("授权失败") + } + } + } + + Button("安排通知") { + NotificationService.shared.scheduleNotification(title: "提醒", body: "这是一个通知示例", timeInterval: 5) + } + } + .padding() + } +} + +class NotificationService: NSObject, UNUserNotificationCenterDelegate { + @MainActor static let shared = NotificationService() + + private override init() { + super.init() + UNUserNotificationCenter.current().delegate = self + } + + func requestAuthorization(completion: @escaping (Bool) -> Void) { + UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in + if let error = error { + print("Authorization error: \(error.localizedDescription)") + completion(false) + return + } + } + } + + func scheduleNotification(title: String, body: String, timeInterval: TimeInterval) { + let content = UNMutableNotificationContent() + content.title = title + content.body = body + content.sound = .default + + let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeInterval, repeats: false) + let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger) + + UNUserNotificationCenter.current().add(request) { error in + if let error = error { + print("Notification scheduling error: \(error.localizedDescription)") + } + } + } + + // 处理接收到的通知 + func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + // 在应用程序前台时处理通知 + print("接收到通知:\(notification.request.content.title)") + preloadSystemLibraries() + completionHandler([.sound]) + } + + func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { + // 处理用户点击通知的行为 + print("用户点击了通知:\(response.notification.request.content.userInfo)") + completionHandler() + } + + private func preloadSystemLibraries() { + // 使用 dlopen 提前加载主 App 常用的系统动态库 + let libraries = [ + "/usr/lib/libobjc.A.dylib", + "/System/Library/Frameworks/UIKit.framework/UIKit", + "/System/Library/Frameworks/Foundation.framework/Foundation" + ] + + for library in libraries { + dlopen(library, RTLD_NOW) + } + } +} diff --git a/SwiftPamphletApp/Performance/Perf.swift b/SwiftPamphletApp/Performance/Perf.swift new file mode 100644 index 000000000..47b6b90c5 --- /dev/null +++ b/SwiftPamphletApp/Performance/Perf.swift @@ -0,0 +1,33 @@ +// +// Perf.swift +// SwiftPamphletApp +// +// Created by Ming on 2024/11/11. +// + +import Foundation + +// 性能工具 +struct Perf { + // 通过 sysctl 获取进程创建开始到当前的时间 + static func getProcessRunningTime() -> Double? { + var kinfo = kinfo_proc() + var size = MemoryLayout.stride + var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()] + + let result = mib.withUnsafeMutableBufferPointer { mibPtr -> Int32 in + sysctl(mibPtr.baseAddress, 4, &kinfo, &size, nil, 0) + } + + guard result == 0 else { + print("sysctl 调用失败,错误码: \(result)") + return nil + } + + let startTimeSec = kinfo.kp_proc.p_starttime.tv_sec + let startTimeUsec = kinfo.kp_proc.p_starttime.tv_usec + let startTime = TimeInterval(startTimeSec) + TimeInterval(startTimeUsec) / 1_000_000 + let currentTime = Date().timeIntervalSince1970 + return currentTime - startTime + } +} diff --git a/SwiftPamphletApp/Performance/TaskManager.swift b/SwiftPamphletApp/Performance/TaskManager.swift new file mode 100644 index 000000000..b115d2450 --- /dev/null +++ b/SwiftPamphletApp/Performance/TaskManager.swift @@ -0,0 +1,11 @@ +// +// TaskManager.swift +// SwiftPamphletApp +// +// Created by Ming on 2024/11/12. +// + +import Foundation + + +