PSUserDefaults

Deliberately shaped like NSUserDefaults, so it reads the way you expect. It is a different store: values go to /Library/TweakInject/Preferences/Defaults/, where a settings pane in another process can see them.

Include
#import "PSUserDefaults.h"

Creating one

Signatures
- (nullable instancetype)initWithSuiteName:(NSString *)domain;
@property (readonly, copy) NSString *suiteName;

There is no shared singleton and init is unavailable: every instance names its domain, because a tweak writing to the wrong domain fails silently and is tedious to find. Pass your package identifier.

Defaults

Signature
- (void)registerDefaults:(NSDictionary<NSString *, id> *)defaults;

Values used when a key has never been written. They are kept in memory and never saved, so call this each time your tweak loads, before reading, exactly as with NSUserDefaults.

Every PSUserDefaults for the same domain in a process sees them, so you can register once and read from another instance. Unlike NSUserDefaults, they do not carry over to other domains: several tweaks load into the same app, and one tweak's fallbacks must not answer for another's keys.

Typed accessors

Signatures
- (BOOL)boolForKey:(NSString *)key;
- (NSInteger)integerForKey:(NSString *)key;
- (double)doubleForKey:(NSString *)key;
- (float)floatForKey:(NSString *)key;
- (nullable NSString *)stringForKey:(NSString *)key;
- (nullable NSArray *)arrayForKey:(NSString *)key;
- (nullable NSDictionary *)dictionaryForKey:(NSString *)key;
- (nullable NSData *)dataForKey:(NSString *)key;
- (nullable NSArray<NSString *> *)stringArrayForKey:(NSString *)key;
- (nullable NSURL *)URLForKey:(NSString *)key;

- (void)setBool:(BOOL)value forKey:(NSString *)key;
- (void)setInteger:(NSInteger)value forKey:(NSString *)key;
- (void)setDouble:(double)value forKey:(NSString *)key;
- (void)setFloat:(float)value forKey:(NSString *)key;
- (void)setURL:(nullable NSURL *)url forKey:(NSString *)key;

A URL is stored as a plain string, a path for a file URL, so the plist stays readable.

Objects and subscripting

Signatures
- (nullable id)objectForKey:(NSString *)key;
- (void)setObject:(nullable id)value forKey:(NSString *)key;
- (void)removeObjectForKey:(NSString *)key;

- (nullable id)objectForKeyedSubscript:(NSString *)key;
- (void)setObject:(nullable id)value forKeyedSubscript:(NSString *)key;

The subscript pair means defaults[@"key"] works for both reading and writing.

Everything at once

Signature
- (NSDictionary<NSString *, id> *)dictionaryRepresentation;

Every key with a value, your registered defaults included.

Reading and replacing a whole domain

Signatures
- (NSDictionary<NSString *, id> *)persistentDomainForName:(NSString *)domainName;
- (void)setPersistentDomain:(NSDictionary<NSString *, id> *)domain forName:(NSString *)domainName;
- (void)removePersistentDomainForName:(NSString *)domainName;

These three work on a domain's whole plist at once, rather than a key at a time. "Persistent domain" is the NSUserDefaults name for that plist.

Values you pass to registerDefaults: are not part of it. They are fallbacks kept in memory and never written to disk, so these methods leave them out, while dictionaryRepresentation above includes them.

Reading takes a domain name rather than using the instance's own, so a settings pane can read another tweak's values. Writing replaces everything in the domain in one go, and removing deletes every stored key, which leaves your registered defaults in force. That last one is how you offer a "reset to defaults" button.

Objective-C
// Reset to defaults
[prefs removePersistentDomainForName:prefs.suiteName];

// Save and restore a preset
NSDictionary *preset = [prefs persistentDomainForName:prefs.suiteName];
[prefs setPersistentDomain:preset forName:prefs.suiteName];

Reacting to changes

Signature
extern NSNotificationName const PSUserDefaultsDidChangeNotification;

Posted on the main thread, with the PSUserDefaults as its object, whenever its domain changes, including when your settings pane saves. Keep the instance around for as long as you want to hear about it.

Objective-C
[NSNotificationCenter.defaultCenter addObserverForName:PSUserDefaultsDidChangeNotification
                                                object:prefs
                                                 queue:nil
                                            usingBlock:^(NSNotification *note) {
    reloadSettings();
}];

Synchronizing

Signature
- (BOOL)synchronize;

Writes are saved as soon as they return, so you never need to call this.

Example

Objective-C
PSUserDefaults *prefs =
    [[PSUserDefaults alloc] initWithSuiteName:@"com.example.mytweak"];

[prefs registerDefaults:@{ @"enabled": @YES, @"intensity": @0.5 }];

if ([prefs boolForKey:@"enabled"]) {
    double intensity = [prefs doubleForKey:@"intensity"];
    apply(intensity);
}

prefs[@"lastRun"] = [NSDate date];