Flipswitchによる設定（PreferenceLoader）のリアルタイム更新

Flipswiitchを使ってTweakのON/OFFを行う機会があると思います。その時にFlipswitchと設定app（PreferenceLoader）間でリアルタイムに同期する方法を紹介します。
##基本的な書き方
<a href="https://github.com/ichitaso/UIRotation9/blob/master/UIRotationSwitch/Switch.x" target="_blank" rel="nofollow">https://github.com/ichitaso/UIRotation9/blob/master/UIRotationSwitch/Switch.x</a>

```objc:Switch.x
#define PREF_PATH @"/var/mobile/Library/Preferences/com.ichitaso.uirotation9.plist"
#define kPrefKey @"enabled"

@interface UIRotationSwitch : NSObject <FSSwitchDataSource>
@end

@implementation UIRotationSwitch

- (FSSwitchState)stateForSwitchIdentifier:(NSString *)switchIdentifier
{
	NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:PREF_PATH];
    
    id enable = [dict objectForKey:kPrefKey];
    BOOL isEnabled = enable ? [enable boolValue] : YES;
    
    return isEnabled ? FSSwitchStateOn : FSSwitchStateOff;
}
```
これで、「/var/mobile/Library/Preferences/com.ichitaso.uirotation9.plist」内の「enabled」keyのBOOL値によってFlipswitchのON/OFF時の動作が適用されます。

##Flipswitch側での設定のON/OFF
```objc:Switch.x
- (void)applyState:(FSSwitchState)newState forSwitchIdentifier:(NSString *)switchIdentifier
{
	NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:PREF_PATH];
    
    NSMutableDictionary *mutableDict = dict ? [[dict mutableCopy] autorelease] : [NSMutableDictionary dictionary];
    
    switch (newState) {
        case FSSwitchStateIndeterminate:
            return;
        case FSSwitchStateOn:
            [mutableDict setValue:@YES forKey:kPrefKey];
            CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), CFSTR("com.ichitaso.uirotation-switchOn"), NULL, NULL, true);
            break;
        case FSSwitchStateOff:
            [mutableDict setValue:@NO forKey:kPrefKey];
            CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), CFSTR("com.ichitaso.uirotation-switchOff"), NULL, NULL, true);
            break;
    }
    
    [mutableDict writeToFile:PREF_PATH atomically:YES];
    
    // Update Flipswitch state
    [[FSSwitchPanel sharedPanel] stateDidChangeForSwitchIdentifier:@"com.ichitaso.uirotationfs"];
}
```
ここがFlipswitchを動かした時に動作する部分です。

「NSMutableDictionary」によってplistファイルの書き込みと「CFNotificationCenterPostNotification」で通知を行います。

ONにするときは「FSSwitchStateOn」

```objc:Switch.x
[mutableDict setValue:@YES forKey:kPrefKey];
CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), CFSTR("com.ichitaso.uirotation-switchOn"), NULL, NULL, true);
```
OFFは「FSSwitchStateOff」

```objc:Switch.x
[mutableDict setValue:@NO forKey:kPrefKey];
CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(), CFSTR("com.ichitaso.uirotation-switchOff"), NULL, NULL, true);
```
最後にplistファイルに書き込みと、Switchの反映を行います。

```objc:Switch.x
[mutableDict writeToFile:PREF_PATH atomically:YES];

// Update Flipswitch state
[[FSSwitchPanel sharedPanel] stateDidChangeForSwitchIdentifier:@"com.ichitaso.uirotationfs"];
```

##通知の処理
このままでは通知が反映されないので、少し工夫します。Tweak側で通知を受けた時に、PreferenceLoaderでも反映されるようにします。

```objc:UIRController.h
@interface UIRController : NSObject
{
    NSUserDefaults *prefs;
}

@property (nonatomic) BOOL prefsChangedFromSettings;
@property (nonatomic) BOOL enable;
+ (UIRController *)sharedInstance;
- (void)updateSettings;
- (void)setEnabled:(BOOL)enable;

@end
```

```objc:UIRController.m
#import "UIRController.h"

#define PREF_PATH @"/var/mobile/Library/Preferences/com.ichitaso.uirotation9.plist"
#define Preferences @"com.ichitaso.uirotation9"

@implementation UIRController
+ (UIRController *)sharedInstance {
	static dispatch_once_t p = 0;
    __strong static id _sharedObject = nil;
    dispatch_once(&p, ^{
        _sharedObject = [[self alloc] init];
    });
    return _sharedObject;
}

- (id)init {
	if (self = [super init]) {
        NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:PREF_PATH];
        id isEnabled = [dict objectForKey:@"enabled"];
        BOOL enabled = isEnabled ? [isEnabled boolValue] : YES;
        
		prefs = [[NSUserDefaults alloc] initWithSuiteName:Preferences];
		
        [prefs registerDefaults:@{
			@"enable": @YES
            }];
        
		[prefs setBool:enabled forKey:@"enabled"];
        
		_prefsChangedFromSettings = NO;
		_enable = enabled;
        
	}
	return self;
}

- (void)updateSettings
{
    [self setEnabled:[prefs boolForKey:@"enabled"]];
    _prefsChangedFromSettings = NO;
}

- (void)setEnabled:(BOOL)enable
{
	if (enable == _enable)
		return;

	_enable = enable;

	if (!_prefsChangedFromSettings)
		[prefs setBool:_enable forKey:@"enabled"];
}

@end
```
このようなファイルを用意してTweak.xmと一緒にcompileするようにします。

```objc:Tweak.xm
// Called by the flipswitch toggle
//==============================================================================

#import "UIRController.h"

void prefsChanged() {
    [UIRController sharedInstance].prefsChangedFromSettings = YES;
    [[UIRController sharedInstance] updateSettings];
    
}

void switchToggleOn() {
    [[UIRController sharedInstance] setEnabled:YES];
}

void switchToggleOff() {
    [[UIRController sharedInstance] setEnabled:NO];
}

//==============================================================================

%ctor
{
    @autoreleasepool {
...
       CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
                                        NULL,
                                        (CFNotificationCallback)switchToggleOn,
                                        CFSTR("com.ichitaso.uirotation-switchOn"),
                                        NULL,
                                        CFNotificationSuspensionBehaviorDeliverImmediately);
        
        CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
                                        NULL,
                                        (CFNotificationCallback)switchToggleOff,
                                        CFSTR("com.ichitaso.uirotation-switchOff"),
                                        NULL,
                                        CFNotificationSuspensionBehaviorDeliverImmediately);
...
```

これでdylib側とPreferenceLoaderで設定がリンクされるようになりました。

##設定（PreferenceLoader）
<a href="https://github.com/ichitaso/UIRotation9/blob/master/Preference.m" target="_blank" rel="nofollow">https://github.com/ichitaso/UIRotation9/blob/master/Preference.m</a>

```objc:Preference.m
@interface UIRPreferenceController : PSListController <UIActionSheetDelegate, UIAlertViewDelegate>
{
}
- (void)reloadPrefs:(NSNotification *)notification;
@end
```
NSNotificationクラスを使用します。

```objc:Preference.m
- (instancetype)init
{
    self = [super init];
    
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"reloadPrefs" object:nil];
    
    CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
                                    NULL,
                                    (CFNotificationCallback)reloadPrefsCallBack,
                                    CFSTR("com.ichitaso.uirotation-switchOn"),
                                    NULL,
                                    CFNotificationSuspensionBehaviorDeliverImmediately);
    
    CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
                                    NULL,
                                    (CFNotificationCallback)reloadPrefsCallBack,
                                    CFSTR("com.ichitaso.uirotation-switchOff"),
                                    NULL,
                                    CFNotificationSuspensionBehaviorDeliverImmediately);
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadPrefs:) name:@"reloadPrefs" object:nil];
    
    return self;
}
```
「- (instancetype)init」で、通知処理を追加します。

```objc:Preference.m
void reloadPrefsCallBack() {
    [[NSNotificationCenter defaultCenter] postNotificationName:@"reloadPrefs" object:nil];
}

- (void)reloadPrefs:(NSNotification *)notification
{
    [self reloadSpecifiers];
}
```
これで通知を受けた際に[self reloadSpecifiers];で設定の更新を行うようにすると、Flipswitch側で操作した時に設定app側でもリアルタイムでスイッチがON/OFFされるようになりました。

細かい部分ですが、ちょっとしたこだわりですね！

それではまた。