Having the app running in simulator I've connected to webView via a web inspector. May be that is related.
Post
Replies
Boosts
Views
Activity
@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()
}
}
}
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.
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]
}
}
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.
Disable Metal validation.
Just disable Metal validation in Run settings.
ARCapture framework allows to record video from ARKit. It's lightweight, well documented and can be extended if needed. Moreover, it has no memory leaks.