How to log a warning that shows up as a runtime issue in Xcode?
Asked Answered
V

7

25

Xcode 8 or 9 started displaying runtime issues. You see a purple icon at the top of the window, and a list in Issue Navigator, next to buildtime issues like compilation warnings and errors.

enter image description here

enter image description here

The runtime issues I've seen are created by the system libraries. Is there a way for my own application code to generate these?

Vehemence answered 22/9, 2017 at 20:12 Comment(2)
I think you can't so far...Chignon
Getting precisely what you're asking for isn't possible, but if you elaborate on the goal you're trying to achieve or problem you're trying to solve, we may be able to provide an alternative solution.Viminal
B
5

Yes! You'll see these if you do something that a sanitizer catches, like performing certain UI operations a background thread with Thread Sanitizer enabled. Having an ambiguous layout and pausing in the view debugger is also a way to get this to occur. Either way, seeing this occur in Apple's libraries isn't a good thing…

UPDATE: I looked into how these warnings are hit, and LLDB essentially sets a breakpoint on a set of magic functions (__asan::AsanDie(), __tsan_on_report, __ubsan_on_report, __main_thread_checker_on_report) inside of a sanitizer dynamic library that are called when issues occur. If you define your own version of these functions and call them, you'll get the warning to show up. First, create the symbol somewhere in your project:

void __main_thread_checker_on_report(char *message) {
}

Then you can trigger it at will, just by putting this code anywhere in your application (you probably want to hide it behind a macro):

((void (*)(char *))dlsym(dlopen(NULL, RTLD_LAZY), "__main_thread_checker_on_report")))("This will show up as a warning")

Needless to say, this will almost certainly not play nicely with the actual sanitizer if you choose to use it. You should probably compile the above conditionally based on whether you are using a sanitizer or not.

Bloomer answered 18/1, 2018 at 12:18 Comment(10)
I think, the point of the question is an ability to generate custom runtime issues "by hand", at will.Hass
Can you create these at will? Yes, if you do anything that's listed above. Can you do it as a sort of logging or debugging facility? No, not really. That's not what it's supposed to be used for.Bloomer
Well, to my mind it actually kind of is what it's supposed to be used for :) Not for general logging, but for addressing outstanding runtime issues quickly, with an ability to see additional metadata and to jump quickly to the place in code that caused it. It would be quite awesome. And based on the interest to this question, looks like quite a lot of people agree. But yes, most likely it's not possible. It would be cool if somebody showed up and told us that it actually is though.Hass
Well, you can always kill your program with assertionFailure or the like to get a similar effect, but it has the side effect of stopping your program. I see how what you want is useful, so I'd suggest filing a bug with Apple and seeing if they expose this to users.Bloomer
That's definitely something we need to do if this question doesn't take us anywhere... Although since Apple can't even manage colours in the console, or proper tabs, I don't really think it'd get us anywhere. Still, that's better than doing nothing and complaining ¯\_(ツ)_/¯Hass
Not sure why this answer was downvoted - it is correct. The question was "Is there a way for my own application code to generate these?" and the answer is no.Viminal
@JacekLampart I'm not the one who downvoted, but maybe the reason is that the answer actually starts with "Yes" and doesn't mention that it is not actually possible anywhere? :) Comments are not a part of an answer. I don't think the answer should be downvoted, since it's just that the question was misinterpreted, but the answer doesn't... well... answer the question, in this particular case. Your comment does. This answer doesn't.Hass
I started the answer with a "Yes" because it is possible to generate one of these warnings by doing something that is caught by a sanitizer. Would you want to do something like this on purpose? Probably not.Bloomer
@Hass upon further inspection, the answer to your question is in fact "yes", if you jump through some hoops. See my update.Bloomer
@saagarjha, that's super interesting! Thanks!Hass
C
2

Swift Composable Architecture folks dug into this and found a solution

You should read about it here

And you can get the code here

which you can then use as

runtimeWarning("This is neat")

full code because SO likes code rather than references:

#if DEBUG
  import os
  import XCTestDynamicOverlay

  // NB: Xcode runtime warnings offer a much better experience than traditional assertions and
  //     breakpoints, but Apple provides no means of creating custom runtime warnings ourselves.
  //     To work around this, we hook into SwiftUI's runtime issue delivery mechanism, instead.
  //
  // Feedback filed: https://gist.github.com/stephencelis/a8d06383ed6ccde3e5ef5d1b3ad52bbc
  private let rw = (
    dso: { () -> UnsafeMutableRawPointer in
      let count = _dyld_image_count()
      for i in 0..<count {
        if let name = _dyld_get_image_name(i) {
          let swiftString = String(cString: name)
          if swiftString.hasSuffix("/SwiftUI") {
            if let header = _dyld_get_image_header(i) {
              return UnsafeMutableRawPointer(mutating: UnsafeRawPointer(header))
            }
          }
        }
      }
      return UnsafeMutableRawPointer(mutating: #dsohandle)
    }(),
    log: OSLog(subsystem: "com.apple.runtime-issues", category: "ComposableArchitecture")
  )
#endif

@_transparent
@inline(__always)
func runtimeWarning(
  _ message: @autoclosure () -> StaticString,
  _ args: @autoclosure () -> [CVarArg] = []
) {
  #if DEBUG
    let message = message()
    unsafeBitCast(
      os_log as (OSLogType, UnsafeRawPointer, OSLog, StaticString, CVarArg...) -> Void,
      to: ((OSLogType, UnsafeRawPointer, OSLog, StaticString, [CVarArg]) -> Void).self
    )(.fault, rw.dso, rw.log, message, args())
    XCTFail(String(format: "\(message)", arguments: args()))
  #endif
}

code is MIT licenced

Coben answered 27/5, 2022 at 14:48 Comment(0)
O
1

Just wanted to add a Swift implementation of a runtimeWarning method, for anyone curious:

func runtimeWarning(_ message: String) {
    // Load the dynamic library.
    guard let handle = dlopen(nil, RTLD_NOW) else {
        fatalError("Couldn't find dynamic library for runtime warning.")
    }

    // Get the "__main_thread_checker_on_report" symbol from the handle.
    guard let sym = dlsym(handle, "__main_thread_checker_on_report") else {
        fatalError("Couldn't find function for runtime warning reporting.")
    }

    // Cast the symbol to a callable Swift function type.
    typealias ReporterFunction = @convention(c) (UnsafePointer<Int8>) -> Void
    let reporter = unsafeBitCast(sym, to: ReporterFunction.self)

    // Convert the message to a pointer
    message.withCString { messagePointer in
        // Call the reporter with the acquired messagePointer
        reporter(messagePointer)
    }
}
Ol answered 5/6, 2020 at 12:25 Comment(0)
L
0

In XCode 8.3 and earlier you can use set breakpoint into any method of UIKit class like setNeedsDisplay() like below.

Also there is library in objective-c steipete class in which #import <objc/runtime.h> is used.

enter image description here

But in Xcode 9 below library Xcode.app/Contenets/Developer/usr/lib/libMainThreadChecker.dylib is available, Which handle for any relevant issues potentially performed out-of-main thread at runtime.

Louis answered 25/1, 2018 at 9:53 Comment(0)
C
0

Well, that's a hack, far from ideal, but can by useful to get runtime warnings without having game-over behavior like in assertions or fatal errors:

func runtimeWarning(_ message: String, file: String = #file, line: Int = #line) {
    #if DEBUG
    DispatchQueue.global(qos: .userInteractive).async {
        // If you got here, please check console for more info
        _ = UIApplication.shared.windows
        print("Runtime warning: \(message): file \(file.fileName), line \(line)")
    }
    #endif
}

fileprivate extension String {
    var fileName: String { URL(fileURLWithPath: self).lastPathComponent }
}

Unfortunately this leads to quite a big print to console from Main Thread Checker, also does not show on correct line, but at least you have all info needed in console (including line of code you should check).

Usage and result is very similar to assertionFailure or fatalError:

runtimeWarning("Hi, I'm purple")

leads to message in console:

Runtime warning: Hi, I'm purple: file MyFile.swift, line 10

and this, so you won't miss it:

Runtime issue

Collinsworth answered 26/3, 2020 at 21:26 Comment(1)
No wait, this is such a dirty... trick, hah, I see what you did there, the cornerstone is just the access to the UIApplication.shared.windows, wooow...Indivertible
H
-1

CocoaLumberjack framework can be used to capture Run times console logs as well as App's Background wakeup logs.

https://github.com/CocoaLumberjack/CocoaLumberjack

https://github.com/phonegap/phonegap-plugin-push/issues/1988

This way you can capture purple warnings displayed in Xcode9 like below in a file that is maintained inside App Container:-

=================================================================

Main Thread Checker: UI API called on a background thread: -[UIApplication registerUserNotificationSettings:] PID: 2897, TID: 1262426, Thread name: (none), Queue name: com.apple.root.default-qos, QoS: 21

Homeomorphism answered 19/1, 2018 at 12:50 Comment(0)
A
-1

It depends on you if you are doing any UI related stuff on other then main thread so system will generate for you else you can not manually generate it.

All UI manipulations should be done in the Main Thread.

If you are not doing this so, in XCode 9 have feature called Main thread checker.

For more info, you can visit below url: https://developer.apple.com/documentation/code_diagnostics/main_thread_checker

It is basically used to checks whether any UIKit related stuff is happening on a main thread or not ?, If failed to do so, it will produce issues at Runtime. So wrap your code in Main Thread block like below to avoid glitches and runtime warnings.

You can Enable - Disable using this steps Edit Scheme... -> (Select your scheme) -> Diagnostics -> Disable 'Main thread checker'

Aggrandize answered 24/1, 2018 at 10:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.