Development Quickies

From macwrench


In diesem Artikel findet sich eine Sammlung diverser Tips und Tricks zum Thema macOS-/iOS-Softwareentwicklung mit Xcode, die im Alltag immer wieder nützlich sein können. Falls bereits aus einem dieser Quickies ein eigener Artikel entstand, ist dieser jeweils am Ende der Beschreibung verlinkt.

Xcode

reset all iOS Simulator devices

... by running this command:

xcrun simctl erase all

This also helps when you're seeing an error message like "Unable to boot device because it cannot be located on disk", e.g. when the folder "~/Library/Developer/CoreSimulator/Devices" has been deleted for some reason.

validate .strings files

The following terminal command only indicates whether or not the file is syntactically valid:

 plutil -lint /path/to/Localizable.strings

Batch check of all .strings files within the current working directory:

 find . -name \*.strings -exec plutil -lint {} +

Xcode debugging fails

If the Xcode debugger fails to start with an error messsage like "Could not attach to pid ..." try this terminal command as this is often just an authorization issue:

sudo DevToolsSecurity -enable

app extensions

Things learned while writing WidgetKit app extensions

Multiple instances (sizes) of the same widget ...
... apparently run within the same runtime environment so avoid writing data from the configuration intent to some kind of singleton or static class since it will be overwritten by a second instance with a different configuration. But on the other hand don't rely on this data on being shared across widget instances. This seems to be an undocumented bejavior and may not always apply, and it might change in the future.
Self-Updating widgets/High frequency updates
Some people suggest you should reload the timeline from within getTimeline() by calling WidgetCenter.shared.reloadAllTimelines() in order to achieve a higher update frequency of your extension. But doing this will drive up the cpu load, inevitably drain your battery and shorten the devices' life span in the process. So better avoid such dirty hacks and stick to the original plan for updating your widgets as laid out in Apple's documentation. Get used to the fact that high frequency updates of WidgetKit app extensions are simply not possible any more. Rather file a complaint with Apple, maybe they listen sooner or later and provide a proper mechanism for this specific use case.
When your extensions write shared data to a shared resource
Make use of semaphores when writing shared data to a shared resource such as a keychain or a user defaults store. You may run into race conditions when multiple instances of the same app extension can process or show different data, especially when they are configurable (i.e. have a configuration intent). They may have to write something in there simultaneously as they might receive an update trigger at the very same time and write operations on a shared keychain aren't always blazingly fast.

check if we're running within an extension

at compile-time

Objective-C

#ifdef TARGET_IS_EXTENSION
     // this piece of code will only be compiled when building for app extension targets
#endif
at runtime

Objective-C

if ([[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]) {
    // this is an app extension
}

Swift

if Bundle.main.bundlePath.hasSuffix(".appex") {
    // this is an app extension
}
or
if Bundle.main.bundleURL.pathExtension == "appex" {
    // this is an app extension
}

Things to keep in mind

Foundation Framework

NSArray:indexOfObject: returns 0 and NOT NSNotFound as you might have expected when the array is nil, so you better check that first:

NSArray *array = nil;
NSInteger index = [array indexOfObject:@"test"];

Tested on macOS Version 12.6

Important Resources

Interfaces

Guidelines for Authentication Services