Integrating NSUserDefaults plugin to iOS (#291)

Summary:
I have a few details left, but its almost done. This PR addresses #145

- The NSUserDefaults plugin uses the SharedPreferences Desktop Part since we can reuse all of it.
- The NSUserDefaults plugin uses swizzling in order to be notified of what specific event changed at runtime.
- Added Test harness in both Sample Swift and Sample apps for iOS in order to test the plugin.
- Updated the documentation in `docs/shared-preferences-plugin.md` and` README.md`

I am open to suggestions since the desktop sharedPreferences version doesn't support deletion of preferences. Most likely I would have to modify the UI, and for that matter, I might as well build a user defaults desktop version

I wanted to add xiphirx in this MR since he developed the shared preferences plugin for Android and Desktop. I don't see a way to remove preferences from the flipper desktop app so I was wondering if you would be OK with me adding that.
Pull Request resolved: https://github.com/facebook/flipper/pull/291

Reviewed By: passy

Differential Revision: D10334685

Pulled By: priteshrnandgaonkar

fbshipit-source-id: d798c01a46df7ddecf713924799f046b560ea922
This commit is contained in:
Marc Terns
2018-10-12 04:07:39 -07:00
committed by Facebook Github Bot
parent 233b7bcd3c
commit c7ad49a9eb
21 changed files with 557 additions and 53 deletions

View File

@@ -0,0 +1,20 @@
//
// FKUserDefaultsPlugin.h
// Sample
//
// Created by Marc Terns on 9/30/18.
// Copyright © 2018 Facebook. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <FlipperKit/FlipperPlugin.h>
NS_ASSUME_NONNULL_BEGIN
@interface FKUserDefaultsPlugin : NSObject <FlipperPlugin>
- (instancetype)initWithSuiteName:(nullable NSString *)suiteName;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,80 @@
//
// FKUserDefaultsPlugin.m
// Sample
//
// Created by Marc Terns on 9/30/18.
// Copyright © 2018 Facebook. All rights reserved.
//
#import "FKUserDefaultsPlugin.h"
#import <FlipperKit/FlipperConnection.h>
#import <FlipperKit/FlipperResponder.h>
#import "FKUserDefaultsSwizzleUtility.h"
@interface FKUserDefaultsPlugin ()
@property (nonatomic, strong) id<FlipperConnection> flipperConnection;
@property (nonatomic, strong) NSUserDefaults *userDefaults;
@property (nonatomic, copy) NSString *key;
@property (nonatomic, copy) NSString *suiteName;
@end
@implementation FKUserDefaultsPlugin
- (instancetype)initWithSuiteName:(NSString *)suiteName {
if (self = [super init]) {
_userDefaults = [NSUserDefaults standardUserDefaults];
_suiteName = suiteName;
__weak typeof(self) weakSelf = self;
[FKUserDefaultsSwizzleUtility swizzleSelector:@selector(setObject:forKey:) class:[NSUserDefaults class] block:^(NSInvocation * _Nonnull invocation) {
__unsafe_unretained id firstArg = nil;
__unsafe_unretained id secondArg = nil;
[invocation getArgument:&firstArg atIndex:2];
[invocation getArgument:&secondArg atIndex:3];
[invocation invoke];
[weakSelf userDefaultsChangedWithValue:firstArg key:secondArg];
}];
}
return self;
}
- (void)didConnect:(id<FlipperConnection>)connection {
self.flipperConnection = connection;
[connection receive:@"getSharedPreferences" withBlock:^(NSDictionary *params, id<FlipperResponder> responder) {
[responder success:[self.userDefaults dictionaryRepresentation]];
}];
[connection receive:@"setSharedPreference" withBlock:^(NSDictionary *params , id<FlipperResponder> responder) {
NSString *preferenceName = params[@"preferenceName"];
[self.userDefaults setObject:params[@"preferenceValue"] forKey:preferenceName];
[responder success:[self.userDefaults dictionaryRepresentation]];
}];
}
- (void)didDisconnect {
self.flipperConnection = nil;
}
- (NSString *)identifier {
return @"Preferences";
}
#pragma mark - Private methods
- (void)userDefaultsChangedWithValue:(id)value key:(NSString *)key {
NSTimeInterval interval = [[NSDate date] timeIntervalSince1970] * 1000;
NSString *intervalStr = [NSString stringWithFormat:@"%f", interval];
NSMutableDictionary *params = [@{@"name":key,
@"time":intervalStr
} mutableCopy];
if (!value) {
[params setObject:@"YES" forKey:@"deleted"];
} else {
[params setObject:value forKey:@"value"];
}
[self.flipperConnection send:@"sharedPreferencesChange" withParams:[params copy]];
}
@end

View File

@@ -0,0 +1,18 @@
//
// FKUserDefaultsSwizzleUtility.h
// FlipperKit
//
// Created by Marc Terns on 10/6/18.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface FKUserDefaultsSwizzleUtility : NSObject
+ (void)swizzleSelector:(SEL)selector class:(Class)aClass block:(void(^)(NSInvocation *invocation))block;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,76 @@
//
// FKUserDefaultsSwizzleUtility.m
// FlipperKit
//
// Created by Marc Terns on 10/6/18.
//
#import "FKUserDefaultsSwizzleUtility.h"
#import <objc/runtime.h>
@interface FKUserDefaultsSwizzleUtility ()
@property (nonatomic, strong) NSMutableSet *swizzledClasses;
@property (nonatomic, strong) NSMutableDictionary *swizzledBlocks;
@property (nonatomic) IMP forwardingIMP;
@end
@implementation FKUserDefaultsSwizzleUtility
- (instancetype)init {
if (self = [super init]) {
_swizzledClasses = [NSMutableSet set];
_swizzledBlocks = [NSMutableDictionary dictionary];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
_forwardingIMP = class_getMethodImplementation([NSObject class], @selector(flipperKitThisMethodShouldNotExist));
#pragma clang diagnostic pop
}
return self;
}
+ (instancetype)sharedInstance {
static FKUserDefaultsSwizzleUtility *sharedInstance = nil;
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
+ (void)swizzleSelector:(SEL)selector class:(Class)aClass block:(void (^)(NSInvocation * _Nonnull))block {
[[self sharedInstance] swizzleSelector:selector class:aClass block:block];
}
- (void)swizzleSelector:(SEL)selector class:(Class)aClass block:(void (^)(NSInvocation * _Nonnull))block {
if (![self.swizzledClasses containsObject:aClass]) {
SEL fwdSel = @selector(forwardInvocation:);
Method m = class_getInstanceMethod(aClass, fwdSel);
__block IMP orig;
__weak typeof(self) weakSelf = self;
IMP imp = imp_implementationWithBlock(^(id self, NSInvocation *invocation) {
NSString * selStr = NSStringFromSelector([invocation selector]);
void (^block)(NSInvocation *) = weakSelf.swizzledBlocks[aClass][selStr];
if (block != nil) {
NSString *originalStr = [@"comfacebookFlipperKit_" stringByAppendingString:selStr];
[invocation setSelector:NSSelectorFromString(originalStr)];
block(invocation);
} else {
((void (*)(id, SEL, NSInvocation *))orig)(self, fwdSel, invocation);
}
});
orig = method_setImplementation(m, imp);
[self.swizzledClasses addObject:aClass];
}
NSMutableDictionary *classDict = self.swizzledBlocks[aClass];
if (classDict == nil) {
classDict = [NSMutableDictionary dictionary];
self.swizzledBlocks[(id)aClass] = classDict;
}
classDict[NSStringFromSelector(selector)] = block;
Method m = class_getInstanceMethod(aClass, selector);
NSString *newSelStr = [@"comfacebookFlipperKit_" stringByAppendingString:NSStringFromSelector(selector)];
SEL newSel = NSSelectorFromString(newSelStr);
class_addMethod(aClass, newSel, method_getImplementation(m), method_getTypeEncoding(m));
method_setImplementation(m, self.forwardingIMP);
}
@end