Ali
Ali

Reputation: 19682

On iOS "monitor" NSUserDefaults read and writes

In my iOS project, I am using a 3rd party library (Google VR), which reads and writes stuff to the NSUserDefaults.

I know I can read and print all the user default (by something like

NSArray *keys = [[[NSUserDefaults standardUserDefaults] dictionaryRepresentation] allKeys];

for(NSString* key in keys){
    // your code here
    NSLog(@"value: %@ forKey: %@",[[NSUserDefaults standardUserDefaults] valueForKey:key],key);
}

What I need is to see what the library is looking for and not not finding.
Basically, the library checks if it has done configuration before (pairing with a cardboard headset), by checking a userdefaults key. I want to know when it happens and what ket it is.

I understand that one way is to do a diff, print all user defaults before and after pairing and see what changed, but I want to know if there is a general way to monitor NSUserDefaults read calls.

Method swizzling, sounds like a possible solution, but I am not quite sure how that would work.

EDIT: for the record, I used the answer by @BlackM below and found out that Google VR for Unity, on iOS looks for com.google.cardboard.sdk.DeviceParamsAndTime to check if it has configured the headset or not.

Upvotes: 1

Views: 275

Answers (1)

BlackM
BlackM

Reputation: 4065

I manage to implement it with method swizzling:

#import <objc/runtime.h>

@implementation NSUserDefaults (Read)
+(void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        //Swizzling objectForKey
        SEL originalSelectorVWA = @selector(objectForKey:);
        SEL swizzledSelectorVWA = @selector(swizzled_objectForKey:);

        Method originalMethodVWA = class_getInstanceMethod(class, originalSelectorVWA);
        Method swizzledMethodVWA = class_getInstanceMethod(class, swizzledSelectorVWA);

        BOOL didAddMethodVWA =
        class_addMethod(class,
                        originalSelectorVWA,
                        method_getImplementation(swizzledMethodVWA),
                        method_getTypeEncoding(swizzledMethodVWA));

        if (didAddMethodVWA) {
            class_replaceMethod(class,
                                swizzledSelectorVWA,
                                method_getImplementation(originalMethodVWA),
                                method_getTypeEncoding(originalMethodVWA));
        } else {
            method_exchangeImplementations(originalMethodVWA, swizzledMethodVWA);
        }

    });
}

#pragma mark - Method Swizzling
- (id)swizzled_objectForKey:(NSString *)defaultName {

    id data = [self swizzled_objectForKey:defaultName];

    if (!data) {
         NSDictionary *infoDict = [NSDictionary dictionaryWithObject:defaultName forKey:@"key"];
        [[NSNotificationCenter defaultCenter] postNotificationName:@"readNSUserDefaults" object:nil userInfo:infoDict];
    }

    return data;
}
@end

In your controller:

- (void)viewDidLoad {

    [super viewDidLoad];

    NSString *valueToSave = @"someValue";

    [[NSUserDefaults standardUserDefaults] setObject:valueToSave forKey:@"someKey"];

    [[NSUserDefaults standardUserDefaults] synchronize];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(readNSUSerDefaults:) name:@"readNSUserDefaults" object:nil];

      [[NSUserDefaults standardUserDefaults] stringForKey:@"someKey"];
[[NSUserDefaults standardUserDefaults] stringForKey:@"thisKeyDoesntExistKey"];
}

-(void)readNSUSerDefaults:(NSNotification*)notification {

    NSString *key = [notification.userInfo objectForKey:@"key"];
    NSLog(@"Read key %@ returned nil NSUserDefaults",key);
}

Upvotes: 2

Related Questions