When running an iOS app in the simulator, there's an environment variable NSObjCMessageLoggingEnabled that causes all objc_msgSend calls to be logged out to a file. (Detailed explanation).
I'm trying to trace out all the messages that are being sent in my app, but I can't run it in the simulator. It has to be on a physical device because I'm interacting with hardware through the lightning connector. For the same reason, I can't attach a debugger to the process.
It would be helpful to write a log to disk like the one created with NSObjCMessageLoggingEnabled, but write it locally in my app's Documents directory, or similar.
Is this possible?
All NSObjCMessageLoggingEnabled does is cause CoreFoundation to automatically call instrumentObjcMessageSends at startup and shutdown.
The problem is that Apple has intentionally hidden that function in the native iOS SDK, so you can't just call it.
But it still exists in the dylib at runtime, so you can always do this:
#include <dlfcn.h>
typedef void functype(BOOL);
void *libobjc = dlopen("/usr/lib/libobjc.dylib", RTLD_LAZY);
functype *instrumentObjcMessageSends = dlsym(libobjc, "instrumentObjcMessageSends");
if (!instrumentObjcMessageSends) {
NSLog(#"Couldn't get instrumentObjcMessageSends");
exit(1);
}
instrumentObjcMessageSends(YES);
NSLog(#"Did it");
I have no idea where, if anywhere, the logs are being written to. I assume you're going to want to call logObjcMessageSends to register your own ObjCLogProc, as explained in the post you linked to.
Related
I tested:
UIControl().sendAction(#selector(URLSessionTask.suspend), to: UIApplication.shared, for: nil)
which is for putting app on background and it works.
How do I put app back on foreground?
I tried:
UIControl().sendAction(#selector(URLSessionTask.resume), to: UIApplication.shared, for: nil)
But eventually it crashes...
Thank you
Update:
Since you've indicated that you're looking for any technical solution, even those not compatible with the App Store or Apple's terms, this should be possible using the Private API LSApplicationWorkspace: openApplicationWithBundleID. Try something like this:
Create a .h file and set up an interface to the LSApplicationWorkspace class and list the required method. You will need to #import "PrivateHeaders.h" in your bridging header.
//
// PrivateHeaders.h
//
#ifndef PrivateHeaders_h
#define PrivateHeaders_h
#interface LSApplicationWorkspace : NSObject
- (bool)openApplicationWithBundleID:(id)arg1;
#end
#endif /* PrivateHeaders_h */
You should then be able to call this function and pass in the Bundle Identifier of your app as an string.
//
// SomeClass.swift
//
import MobileCoreServices
let workspace = LSApplicationWorkspace()
/**
Launch an App given its bundle identifier
- parameter bundleIdentifier: The bundle identifier of the app to launch
- returns: True if app is launched, otherwise false
*/
func openApp(withBundleIdentifier bundleIdentifier: String) -> Bool {
// Call the Private API LSApplicationWorkspace method
return workspace.openApplication(withBundleID: bundleIdentifier)
}
Original:
What you are doing is likely a violation of the iOS Human Interface Guidelines (although the "Don’t Quit Programmatically" is no longer specifically defined), so as the comments have said, it is not suited to the App Store. Regardless, once your app is suspended in this way, I don't expect that there is a way to resume it programmatically, unless you can hook into a Background Operation to run URLSessionTask.resume, but I have not tested it and am unsure whether it can work.
Apps can be launched (and hence brought into the foreground) programmatically from another app or today extension by using a Custom URL Scheme, or via a Push Notification. It isn't possible to launch the app from the Background Operation via a URL Scheme, since it is part of the UIKit framework, which must be run in the main thread.
In summary, I think your best option is to try to use a Notification. This just means that the user will need to click on the notification to bring your app back into the foreground.
Closing/opening the app should be done explicitly by the user. Any other way of closing or opening the app is not supported by Apple and will be rejected when uploaded to app store. iOS Human Interface Guideline states:
Don’t Quit Programmatically
Never quit an iOS application
programmatically because people tend to interpret this as a crash.
However, if external circumstances prevent your application from
functioning as intended, you need to tell your users about the
situation and explain what they can do about it. Depending on how
severe the application malfunction is, you have two choices.
*Display
an attractive screen that describes the problem and suggests a
correction. A screen provides feedback that reassures usersthat
there’s nothing wrong with your application. It puts usersin control,
letting them decide whether they want to take corrective action and
continue using your application or press the Home button and open a
different application
*If only some of your application's features are
not working, display either a screen or an alert when people activate
the feature. Display the alert only when people try to accessthe
feature that isn’t functioning
Just as a follow up to Jordan's excellent answer I want to give an explanation for why your code works in the first place and why that alone will get your app rejected, even without any functionality to make it active again and bring it to the foreground.
As maddy pointed out in a comment, you're basically calling a method from UIApplication's private API. This works due to the Objective-C runtime's dynamic linking. You might wonder "But I am using Swift, what does that have to do with Objective-C?" The answer lies in #selector mechanism. A Selector is basically just a symbol that the Objective-C runtime looks up in a table to get a method it invokes (for you). This is why it's technically not correct to say you "call a method" when you do something like myObjectInstance.someMethod(). The correct way to phrase that would be to "send a message" to the object, because that's what is happening in the runtime. The target-action mechanism is build around that. The sendAction(_: Selector?, to: Any?) method does the same thing. So in effect your code does the following:
Get the symbol that corresponds to URLSessionTask's suspend() method.
Tell the shared instance of UIApplication to invoke the method that it has for that symbol.
Now usually that would result in a crash with the typical "unknown selector sent to instance..." error message. But here, by sure coincidence UIApplication also has a method for that instance (or rather, the runtime also has one of its methods listed in its table for that symbol). You kind of "found" a method that is not declared in its public header. You successfully circumvented a compile-time check for this and invoke a method that is part of a private API. This is explicitly forbidden in the Apple Developer Program License Agreement
Besides all that, I would strongly advise against trying to design an app that way in the first place. As maddy pointed out it's also likely considered to violate the HIGs. Even if you're not trying to do anything malicious and properly explain the feature in your app's description, that won't make Apple let it slide (I assume). Personally, as a user, I'd also find it annoying if the app did something the system already has a specific mechanic for in a different manner, at least in terms of app's coming to background and foreground.
I don't think it can be done without user interaction
The option is you can generate a push notification to tell the user to bring the application to foreground
When the operating system delivers push notification and the target application is not running in the foreground, it presents the notification.
If there is a notification alert and the user taps or clicks the action button (or moves the action slider), the application launches and calls a method to pass in the local-notification object or remote-notification payload.
I tested:
UIControl().sendAction(#selector(URLSessionTask.suspend), to: UIApplication.shared, for: nil)
which is for putting app on background and it works.
How do I put app back on foreground?
I tried:
UIControl().sendAction(#selector(URLSessionTask.resume), to: UIApplication.shared, for: nil)
But eventually it crashes...
Thank you
Update:
Since you've indicated that you're looking for any technical solution, even those not compatible with the App Store or Apple's terms, this should be possible using the Private API LSApplicationWorkspace: openApplicationWithBundleID. Try something like this:
Create a .h file and set up an interface to the LSApplicationWorkspace class and list the required method. You will need to #import "PrivateHeaders.h" in your bridging header.
//
// PrivateHeaders.h
//
#ifndef PrivateHeaders_h
#define PrivateHeaders_h
#interface LSApplicationWorkspace : NSObject
- (bool)openApplicationWithBundleID:(id)arg1;
#end
#endif /* PrivateHeaders_h */
You should then be able to call this function and pass in the Bundle Identifier of your app as an string.
//
// SomeClass.swift
//
import MobileCoreServices
let workspace = LSApplicationWorkspace()
/**
Launch an App given its bundle identifier
- parameter bundleIdentifier: The bundle identifier of the app to launch
- returns: True if app is launched, otherwise false
*/
func openApp(withBundleIdentifier bundleIdentifier: String) -> Bool {
// Call the Private API LSApplicationWorkspace method
return workspace.openApplication(withBundleID: bundleIdentifier)
}
Original:
What you are doing is likely a violation of the iOS Human Interface Guidelines (although the "Don’t Quit Programmatically" is no longer specifically defined), so as the comments have said, it is not suited to the App Store. Regardless, once your app is suspended in this way, I don't expect that there is a way to resume it programmatically, unless you can hook into a Background Operation to run URLSessionTask.resume, but I have not tested it and am unsure whether it can work.
Apps can be launched (and hence brought into the foreground) programmatically from another app or today extension by using a Custom URL Scheme, or via a Push Notification. It isn't possible to launch the app from the Background Operation via a URL Scheme, since it is part of the UIKit framework, which must be run in the main thread.
In summary, I think your best option is to try to use a Notification. This just means that the user will need to click on the notification to bring your app back into the foreground.
Closing/opening the app should be done explicitly by the user. Any other way of closing or opening the app is not supported by Apple and will be rejected when uploaded to app store. iOS Human Interface Guideline states:
Don’t Quit Programmatically
Never quit an iOS application
programmatically because people tend to interpret this as a crash.
However, if external circumstances prevent your application from
functioning as intended, you need to tell your users about the
situation and explain what they can do about it. Depending on how
severe the application malfunction is, you have two choices.
*Display
an attractive screen that describes the problem and suggests a
correction. A screen provides feedback that reassures usersthat
there’s nothing wrong with your application. It puts usersin control,
letting them decide whether they want to take corrective action and
continue using your application or press the Home button and open a
different application
*If only some of your application's features are
not working, display either a screen or an alert when people activate
the feature. Display the alert only when people try to accessthe
feature that isn’t functioning
Just as a follow up to Jordan's excellent answer I want to give an explanation for why your code works in the first place and why that alone will get your app rejected, even without any functionality to make it active again and bring it to the foreground.
As maddy pointed out in a comment, you're basically calling a method from UIApplication's private API. This works due to the Objective-C runtime's dynamic linking. You might wonder "But I am using Swift, what does that have to do with Objective-C?" The answer lies in #selector mechanism. A Selector is basically just a symbol that the Objective-C runtime looks up in a table to get a method it invokes (for you). This is why it's technically not correct to say you "call a method" when you do something like myObjectInstance.someMethod(). The correct way to phrase that would be to "send a message" to the object, because that's what is happening in the runtime. The target-action mechanism is build around that. The sendAction(_: Selector?, to: Any?) method does the same thing. So in effect your code does the following:
Get the symbol that corresponds to URLSessionTask's suspend() method.
Tell the shared instance of UIApplication to invoke the method that it has for that symbol.
Now usually that would result in a crash with the typical "unknown selector sent to instance..." error message. But here, by sure coincidence UIApplication also has a method for that instance (or rather, the runtime also has one of its methods listed in its table for that symbol). You kind of "found" a method that is not declared in its public header. You successfully circumvented a compile-time check for this and invoke a method that is part of a private API. This is explicitly forbidden in the Apple Developer Program License Agreement
Besides all that, I would strongly advise against trying to design an app that way in the first place. As maddy pointed out it's also likely considered to violate the HIGs. Even if you're not trying to do anything malicious and properly explain the feature in your app's description, that won't make Apple let it slide (I assume). Personally, as a user, I'd also find it annoying if the app did something the system already has a specific mechanic for in a different manner, at least in terms of app's coming to background and foreground.
I don't think it can be done without user interaction
The option is you can generate a push notification to tell the user to bring the application to foreground
When the operating system delivers push notification and the target application is not running in the foreground, it presents the notification.
If there is a notification alert and the user taps or clicks the action button (or moves the action slider), the application launches and calls a method to pass in the local-notification object or remote-notification payload.
I am writing codes for a project where i can not use several crash reporting tools due to some privacy issue. So i am searching to manage a send email having crash report if crash occurs without the involvement of third party reporting tool.
In your application delegate declare API like:
void uncaughtExceptionHandler(NSException * exception)
{
// Here you can:
// 1. Set some boolean in user defaults that app crashed.
// 2. Dump this data (below) in some file in documents directory.
NSLog(#"Uncaught Exception: %#", exception.reason);
NSLog(#"CrashSymbols: %#", exception.callStackSymbols);
}
Then set in "application:didFinishLaunchingWithOptions:":
NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler);
Then when your application next launches, if boolean (1) is set in user defaults, read this data (2) and email.
You can use PLCrashReporter framework in your iOS application, whenever the application is started, it should search for saved crash logs then send email using MFMailViewComposer.
https://www.plcrashreporter.org/
In the WWDC 2013 talk on processing app store receipts, it is suggested that for iOS apps, receipt validation code should be called as soon as possible. Even before application:didFinishLaunchingWithOptions: - i.e. in the main() function. I suppose that the way this would work is as follows:
int main(int argc, char *argv[]) {
#autoreleasepool {
validateReceiptMethod(); // <---- HERE
int retVal = UIApplicationMain(argc, argv, nil, nil);
return retVal;
}
}
The idea is that the UIApplicationMain() method is what launches your app and calls application:didFinishLaunchingWithOptions:. If you put the validateReceiptMethod() after UIApplciationMain(), it would never run.
Anyway, this is working great. But what if there is no receipt? Then you need to call SKReceiptRefreshRequest to get a new one from the app store, which is fine. But if you run this code before UIApplciationMain(), it will also run before any of your UI is displayed. So what would happen in terms of showing the user the apple ID login dialog? Is it even possible to call SKReceiptRefreshRequest from the main() method?
So what would happen in terms of showing the user the apple ID login dialog?
Store Kit alerts appear in windows that are not owned by your application, so can be shown when your application is not active — even before it launches. But this is not really relevant.
Is it even possible to call SKReceiptRefreshRequest from the main() method?
This may be possible if you set up your own event loop then stop it when the receipt request finishes, but you should not do this. Don’t delay launching your application waiting on a network request; it might never complete. If the receipt is invalid, I would recommend entering UIApplicationMain() and requesting another receipt when launching finishes.
Edit: Since you can not do anything about not having a valid receipt before entering UIApplicationMain(), I don’t understand why Apple recommends checking at this point. This makes sense on OS X as the application should terminate, but not on iOS where application should keep running and it is acceptable to ignore invalid receipts. You could check early, store the state in a global variable, then respond later; but why not check only when you’re ready to respond.
I've found that my iOS 5 app is sometimes unexpectedly quited and it does not seem to be due to an uncaught exception, since I've implemented uncaughtExceptionHandler in the app delegate class and I get nothing from there.
If it is because the system is terminating it, it looks like you can only be aware of that if it is in background state: I've read the following line in Apple's documentation.
The applicationWillTerminate: method is not called if your app is currently suspended.
So, if I'm not wrong, you can get the reason why your app is terminated by the system in these cases:
App was in background state
Low-memory event was triggered
Can I detect more causes of why the app is being terminated, in order to report the issue? Or is it possible that it is not currently being terminated, but moved to background without user interaction?
Thanks
NSSetUncaughtExceptionHandler() installs a handler for Objective-C exceptions (e.g. trying to access an NSArray item that does not exist). It does not catch the lower level signals like segmentation fault, bus error, illegal instruction, ..., things that happen when your app for example tries to access an invalid pointer address.
You can also install handlers for those:
#include <signal.h>
void signalHandler(int signal)
{
}
// Somewhere in your start-up code.
signal(SIGSEGV, signalHandler);