Post

Replies

Boosts

Views

Activity

Reply to Can I use OSLogStore to access logs from earlier runs of my app?
@esikmo Do you know when the app starts logging the logs from scratch, i.e. when the previous logs are reset? For example, if the app is moved to the background and later is reopened (not suspended in-between), are the logs available from the first launch (along with logs after the second launch in OSLogStore(scope: .currentProcessIdentifier)? I think to implement a function that copies all the logs from OSLogStore when the app is closed (export to a file). It will allow to write a function to export logs from previous launches. I put that exporting in applicationWillTerminate, but it's never called. I tested it by killing the app, but the files not appear meaning applicationWillTerminate is not called. Putting the function into applicationWillResignActive or applicationDidEnterBackground might result in having a few files that overlay in time. Can you suggest how to implement this idea properly? func applicationWillTerminate(_ application: UIApplication) { // Export logs when the app is suspended if #available(iOS 15.0, *) { FileManager.exportLogsSinceLastLaunch() } } ... // global vars private var lastBgTaskIdLock = NSLock() private var lastBgTaskId: UIBackgroundTaskIdentifier? extension FileManager { public static func exportLogsSinceLastLaunch() { let logger = os.Logger(subsystem: Bundle.main.appId, category: "FileManager") logger.info("exportLogsSinceLastLaunch") let fileCoordinator = FileCoordinator() /// Register a task var cancel: (() -> Void)? let bgTask = UIApplication.shared.beginBackgroundTask(withName: "\(#function)") { cancel?() } let lock = lastBgTaskIdLock var bgTaskToCancel: UIBackgroundTaskIdentifier? do { lock.lock(); defer { lock.unlock() } if let id = lastBgTaskId, id != UIBackgroundTaskIdentifier.invalid { bgTaskToCancel = id } lastBgTaskId = bgTask } if let id = bgTaskToCancel { UIApplication.shared.endBackgroundTask(id) } /// Callback to end the task properly let endTaskCallback: () -> Void = { var needToEndTask = false do { lock.lock(); defer { lock.unlock() } if lastBgTaskId == bgTask { needToEndTask = true lastBgTaskId = UIBackgroundTaskIdentifier.invalid } } if needToEndTask { DispatchQueue.main.async { UIApplication.shared.endBackgroundTask(bgTask) } } } let task = Task.detached { [fileCoordinator] in do { // Export log into temporary file guard let tempUrl = try fileCoordinator.storeAndGetCurrentLogTemporaryUrl() else { return } // Copy file to a persistent storage try Task.checkCancellation() let filename = tempUrl.lastPathComponent let url = try FileUtil(contentDirectoryName: "logs").mkdir().getLocalFileURL(filename) try FileManager.default.copyItem(at: tempUrl, to: url) // Complete the task try Task.checkCancellation() } catch { logger.error(error: error) } /// End the task endTaskCallback() } cancel = { task.cancel() } } }
Nov ’23
Reply to EXC_BAD_ACCESS (code=10... when calling init on a struct
I hope my finding will help someone. We faced exactly the same crash in one of our unit tests after tweaking the library dependencies. And the problem was not in the Xcodeproject. May be the reason of the crash is still not clear, but here is the story: What happened: We have project A, which depends on library X, and we had library B. A->X - depends as subproject A->B - :path => dependency in Podfile. We decided to refactor B, and make the following: A -> B, X - both in Podfile (:path =>) B -> X - Podfile (:path =>) After that, one of the tests which has no differences with other tests, stated crashing with the issue above. The problem was in Generics: S<T: P>: NSObject - service class in A P - protocol in X F: P - fake class in TEST target of A (AT) R: P - class in A We are using this service in A as follows: S<R>, but in unit tests we need instance of type S<F>. After moving F: P from AT to A (in Target Membership), the crash disappear. Seems it's something related to : P in F: P - unit test bundle was not able to create S<F>. P.S. Before we found the solution we tried to change Podfile (:path => ) to normal versioned dependences (pod X, '0.1.0', pod B, '0.1.0') - no effect.
Feb ’23
Reply to com.apple.dt.deviceprocesscontrolservice Code=8 Error when running WidgetBundle Targets
In my case it was due to empty widget's families list: ... .supportedFamilies(families) ... var families: [WidgetFamily] {         if #available(iOSApplicationExtension 16.0, watchOS 9.0, *) {             return [.accessoryCircular, .accessoryInline, .systemSmall]         } else {             return [] // <------------------------- problem         }     } I've changed that to non-empty for <16.0 case: var families: [WidgetFamily] {         if #available(iOSApplicationExtension 16.0, watchOS 9.0, *) {             return [.accessoryCircular, .accessoryInline, .systemSmall]         } else {             return [.systemSmall]         }     }
Nov ’22
Reply to Swift 5.5 Concurrency: how to serialize async Tasks to replace an OperationQueue with maxConcurrentOperationCount = 1?
Very good question. To run a few async tasks one-by-one (each waits for previous task to be completed) check the following example I usually use: import UIKit import PlaygroundSupport /// Delays given callback invocation. (c) seriyvolk83 /// - Parameters: ///   - delay: the delay in seconds ///   - callback: the callback to invoke after 'delay' seconds public func delay(_ delay: TimeInterval, callback: @escaping ()->()) {     let delay = delay * Double(NSEC_PER_SEC)     let popTime = DispatchTime.now() + Double(Int64(delay)) / Double(NSEC_PER_SEC);     DispatchQueue.main.asyncAfter(deadline: popTime, execute: {         callback()     }) } extension Int {     /// Get uniform random value between 0 and maxValue     ///     /// - Parameter maxValue: the limit of the random values     /// - Returns: random Int     public static func random(_ maxValue: Int) -> Int {         return Int(arc4random_uniform(UInt32(maxValue)))     } } /// The shared utility storing operations and a semaphore class SyncTasksUtil {     static let queue = OperationQueue()     fileprivate static let semaphore = DispatchSemaphore(value: 1) } /// The operation that waits for all previous operations to be complete /// (c) seriyvolk83 class SyncJob: Operation {     typealias SyncJobFinished = ()->()     private var callback: ((@escaping SyncJobFinished)->())!     init(callback: @escaping ((@escaping SyncJobFinished))->()) {         self.callback = callback     }     override func main() {         if self.isCancelled {             return         }         // Waits for, or decrements, a semaphore.         SyncTasksUtil.semaphore.wait()         // Process the task in main queue         DispatchQueue.main.async {             self.callback() {                 // Once done, signal                 DispatchQueue.main.async {                     // Signals (increments) a semaphore (releases for other operations).                     SyncTasksUtil.semaphore.signal()                 }             }         }     }     /// Run a few asyncrounous task     static func test() {         /// Run 10 async tasks (one-by-one)         let n = 10         for i in 0..<n {             let k = i             // Define a task             let callback: (@escaping SyncJob.SyncJobFinished)->() = { (finished) in                 /// Simulate async task with a delay                 /// TODO YOUR TASK IS HERE                 delay(Double(Int.random(3)), callback: {                     print("\(k): \(Date())")                     /// Async task completed                     finished()                 })             }             /// Create an operation             let job = SyncJob(callback: { finished in                 callback(finished)             })             /// Add in a queue             SyncTasksUtil.queue.addOperation(job)         }     } } SyncJob.test() PlaygroundPage.current.needsIndefiniteExecution = true Note that if your tasks do not need to be launched in main queue, then modify SyncJob and remove DispatchQueue.main.async. If you put the example into a playground and run, then you will see the following output: 0: 2022-03-06 09:05:16 +0000 1: 2022-03-06 09:05:25 +0000 2: 2022-03-06 09:05:34 +0000 3: 2022-03-06 09:05:37 +0000 4: 2022-03-06 09:05:39 +0000 5: 2022-03-06 09:05:43 +0000 6: 2022-03-06 09:05:47 +0000 7: 2022-03-06 09:05:50 +0000 8: 2022-03-06 09:05:59 +0000 9: 2022-03-06 09:06:01 +0000 Note that all 10 tasks are going one after another despite on the random time used for the task.
Mar ’22