Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix KVO crashes #1038

Merged
merged 2 commits into from
Nov 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 6 additions & 13 deletions app/AboutAppearanceViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#import "AboutAppearanceViewController.h"
#import "FontPickerViewController.h"
#import "UserPreferences.h"
#import "NSObject+SaneKVO.h"

static NSString *const ThemeNameCellIdentifier = @"Theme Name";
static NSString *const FontSizeCellIdentifier = @"Font Size";
Expand All @@ -21,19 +22,11 @@ @implementation AboutAppearanceViewController

- (void)viewDidLoad {
[super viewDidLoad];
[[UserPreferences shared] addObserver:self forKeyPath:@"theme" options:NSKeyValueObservingOptionNew context:nil];
[[UserPreferences shared] addObserver:self forKeyPath:@"fontSize" options:NSKeyValueObservingOptionNew context:nil];
[[UserPreferences shared] addObserver:self forKeyPath:@"fontFamily" options:NSKeyValueObservingOptionNew context:nil];
}
- (void)dealloc {
[[UserPreferences shared] removeObserver:self forKeyPath:@"theme"];
[[UserPreferences shared] removeObserver:self forKeyPath:@"fontSize"];
[[UserPreferences shared] removeObserver:self forKeyPath:@"fontFamily"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
[self.tableView reloadData];
[self setNeedsStatusBarAppearanceUpdate];
[UserPreferences.shared observe:@[@"theme", @"fontSize", @"fontFamily"]
options:0 owner:self usingBlock:^(typeof(self) self) {
[self.tableView reloadData];
[self setNeedsStatusBarAppearanceUpdate];
}];
}

#pragma mark - Table view data source
Expand Down
16 changes: 5 additions & 11 deletions app/AboutExternalKeyboardViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#import "AboutExternalKeyboardViewController.h"
#import "UserPreferences.h"
#import "NSObject+SaneKVO.h"

const int kCapsLockMappingSection = 0;

Expand All @@ -23,20 +24,13 @@ @implementation AboutExternalKeyboardViewController

- (void)viewDidLoad {
[super viewDidLoad];
[UserPreferences.shared addObserver:self forKeyPath:@"capsLockMapping" options:NSKeyValueObservingOptionNew context:nil];
[UserPreferences.shared addObserver:self forKeyPath:@"optionMapping" options:NSKeyValueObservingOptionNew context:nil];
[UserPreferences.shared observe:@[@"capsLockMapping", @"optionMapping"]
options:0 owner:self usingBlock:^(typeof(self) self) {
[self.tableView reloadData];
}];
[self _update];
}

- (void)dealloc {
[UserPreferences.shared removeObserver:self forKeyPath:@"capsLockMapping"];
[UserPreferences.shared removeObserver:self forKeyPath:@"optionMapping"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
[self.tableView reloadData];
}

- (void)_update {
self.optionMetaSwitch.on = UserPreferences.shared.optionMapping == OptionMapEsc;
self.backtickEscapeSwitch.on = UserPreferences.shared.backtickMapEscape;
Expand Down
15 changes: 5 additions & 10 deletions app/AboutNavigationController.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#import "AboutNavigationController.h"
#import "UserPreferences.h"
#import "NSObject+SaneKVO.h"

@interface AboutNavigationController ()

Expand All @@ -16,23 +17,17 @@ @implementation AboutNavigationController

- (void)viewDidLoad {
[super viewDidLoad];
[[UserPreferences shared] addObserver:self forKeyPath:@"theme" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionInitial context:nil];
}
- (void)dealloc {
[[UserPreferences shared] removeObserver:self forKeyPath:@"theme"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if (@available(iOS 13, *)) {
if ([keyPath isEqualToString:@"theme"]) {
[UserPreferences.shared observe:@[@"theme"] options:NSKeyValueObservingOptionInitial
owner:self usingBlock:^(typeof(self) self) {
if (@available(iOS 13, *)) {
UIKeyboardAppearance appearance = UserPreferences.shared.theme.keyboardAppearance;
if (appearance == UIKeyboardAppearanceDark) {
self.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
} else {
self.overrideUserInterfaceStyle = UIUserInterfaceStyleLight;
}
}
}
}];
}

@end
29 changes: 6 additions & 23 deletions app/AboutViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#import "AboutViewController.h"
#import "UserPreferences.h"
#import "AppGroup.h"
#import "NSObject+SaneKVO.h"

@interface AboutViewController ()
@property (weak, nonatomic) IBOutlet UITableViewCell *capsLockMappingCell;
Expand Down Expand Up @@ -46,39 +47,21 @@ - (void)viewDidLoad {
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"],
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]];

UserPreferences *prefs = [UserPreferences shared];
NSKeyValueObservingOptions opts = NSKeyValueObservingOptionNew;
[prefs addObserver:self forKeyPath:@"capsLockMapping" options:opts context:nil];
[prefs addObserver:self forKeyPath:@"fontSize" options:opts context:nil];
[prefs addObserver:self forKeyPath:@"launchCommand" options:opts context:nil];
[prefs addObserver:self forKeyPath:@"bootCommand" options:opts context:nil];
}

- (void)dealloc {
UserPreferences *prefs = [UserPreferences shared];
[prefs removeObserver:self forKeyPath:@"capsLockMapping"];
[prefs removeObserver:self forKeyPath:@"fontSize"];
[prefs removeObserver:self forKeyPath:@"launchCommand"];
[prefs removeObserver:self forKeyPath:@"bootCommand"];
[UserPreferences.shared observe:@[@"capsLockMapping", @"fontSize", @"launchCommand", @"bootCommand"]
options:0 owner:self usingBlock:^(typeof(self) self) {
[self _updatePreferenceUI];
}];
}

- (IBAction)dismiss:(id)sender {
[self dismissViewControllerAnimated:self completion:nil];
[self dismissViewControllerAnimated:self completion:nil];
}

- (void)exitRecovery:(id)sender {
[NSUserDefaults.standardUserDefaults setBool:NO forKey:@"recovery"];
exit(0);
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([object isKindOfClass:[UserPreferences class]]) {
[self _updatePreferenceUI];
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}

- (void)_updatePreferenceUI {
UserPreferences *prefs = UserPreferences.shared;
self.themeCell.detailTextLabel.text = prefs.theme.presetName;
Expand Down
10 changes: 5 additions & 5 deletions app/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#import "SceneDelegate.h"
#import "PasteboardDevice.h"
#import "LocationDevice.h"
#import "NSObject+SaneKVO.h"
#import "Roots.h"
#import "TerminalViewController.h"
#import "UserPreferences.h"
Expand Down Expand Up @@ -208,7 +209,10 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
extern const char *uname_version;
uname_version = self.unameVersion.UTF8String;

[UserPreferences.shared addObserver:self forKeyPath:@"shouldDisableDimming" options:NSKeyValueObservingOptionInitial context:nil];
[UserPreferences.shared observe:@[@"shouldDisableDimming"] options:NSKeyValueObservingOptionInitial
owner:self usingBlock:^(typeof(self) self) {
UIApplication.sharedApplication.idleTimerDisabled = UserPreferences.shared.shouldDisableDimming;
}];

struct sockaddr_in6 address = {
.sin6_len = sizeof(address),
Expand Down Expand Up @@ -237,10 +241,6 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
return YES;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
UIApplication.sharedApplication.idleTimerDisabled = UserPreferences.shared.shouldDisableDimming;
}

- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions API_AVAILABLE(ios(13.0)) {
for (UISceneSession *sceneSession in sceneSessions) {
NSString *terminalUUID = sceneSession.stateRestorationActivity.userInfo[@"TerminalUUID"];
Expand Down
33 changes: 33 additions & 0 deletions app/NSObject+SaneKVO.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// NSObject+SaneKVO.h
// iSH
//
// Created by Theodore Dubois on 11/10/20.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface KVOObservation : NSObject {
BOOL _enabled;
tbodt marked this conversation as resolved.
Show resolved Hide resolved
__weak id _object;
tbodt marked this conversation as resolved.
Show resolved Hide resolved
NSString *_keyPath;
void (^_block)(void);
}
- (void)disable;
@end

@interface NSObject (SaneKVO)

- (KVOObservation *)observe:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
usingBlock:(void (^)(void))block;
- (void)observe:(NSArray<NSString *> *)keyPaths
options:(NSKeyValueObservingOptions)options
owner:(id)owner
usingBlock:(void (^)(id))block;

@end

NS_ASSUME_NONNULL_END
72 changes: 72 additions & 0 deletions app/NSObject+SaneKVO.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//
// NSObject+SaneKVO.m
// iSH
//
// Created by Theodore Dubois on 11/10/20.
//

#import <objc/runtime.h>
#import "NSObject+SaneKVO.h"

static void *kKVOObservations = &kKVOObservations;

@interface KVOObservation ()
- (instancetype)initWithKeyPath:(NSString *)keyPath object:(id)object block:(void (^)(void))block;
@end

@implementation NSObject (SaneKVO)

- (KVOObservation *)observe:(NSString *)keyPath options:(NSKeyValueObservingOptions)options usingBlock:(void (^)(void))block {
KVOObservation *observation = [[KVOObservation alloc] initWithKeyPath:keyPath object:self block:block];
[self addObserver:observation forKeyPath:keyPath options:options context:NULL];
return observation;
}

- (void)observe:(NSArray<NSString *> *)keyPaths options:(NSKeyValueObservingOptions)options owner:(id)owner usingBlock:(void (^)(id self))block {
__weak id weakOwner = owner;
void (^newBlock)(void) = ^{
id owner = weakOwner;
NSAssert(owner, @"kvo notification shouldn't come to dead object");
block(owner);
};
@synchronized (owner) {
for (NSString *keyPath in keyPaths) {
NSMutableSet *observations = objc_getAssociatedObject(owner, kKVOObservations);
if (observations == nil) {
tbodt marked this conversation as resolved.
Show resolved Hide resolved
observations = [NSMutableSet new];
objc_setAssociatedObject(owner, kKVOObservations, observations, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[observations addObject:[self observe:keyPath options:options usingBlock:newBlock]];
}
}
}

@end

@implementation KVOObservation

- (instancetype)initWithKeyPath:(NSString *)keyPath object:(id)object block:(void (^)(void))block {
if (self = [super init]) {
_keyPath = keyPath;
_object = object;
_block = block;
_enabled = YES;
}
return self;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
_block();
}

- (void)disable {
if (_enabled) {
[_object removeObserver:self forKeyPath:_keyPath context:NULL];
_enabled = NO;
}
}
- (void)dealloc {
[self disable];
}

@end
22 changes: 11 additions & 11 deletions app/Roots.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#import <FileProvider/FileProvider.h>
#import "Roots.h"
#import "AppGroup.h"
#import "NSObject+SaneKVO.h"
#include "tools/fakefs.h"

static NSURL *RootsDir() {
Expand Down Expand Up @@ -50,25 +51,24 @@ - (instancetype)init {
NSAssert(NO, @"failed to import alpine, error %@", error);
}
}
[self addObserver:self forKeyPath:@"roots" options:0 context:nil];
[self observe:@[@"roots"] options:0 owner:self usingBlock:^(typeof(self) self) {
if (self.defaultRoot == nil && self.roots.count)
self.defaultRoot = self.roots[0];
[self syncFileProviderDomains];
}];
[self syncFileProviderDomains];

self.defaultRoot = [NSUserDefaults.standardUserDefaults stringForKey:kDefaultRoot];
[self addObserver:self forKeyPath:@"defaultRoot" options:0 context:nil];
if ((!self.defaultRoot || ![self.roots containsObject:self.defaultRoot]) && self.roots.count)
self.defaultRoot = self.roots.firstObject;
}
return self;
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"defaultRoot"]) {
[NSUserDefaults.standardUserDefaults setObject:self.defaultRoot forKey:kDefaultRoot];
} else if ([keyPath isEqualToString:@"roots"]) {
if (self.defaultRoot == nil && self.roots.count)
self.defaultRoot = self.roots[0];
[self syncFileProviderDomains];
}
- (NSString *)defaultRoot {
return [NSUserDefaults.standardUserDefaults stringForKey:kDefaultRoot];
}
- (void)setDefaultRoot:(NSString *)defaultRoot {
[NSUserDefaults.standardUserDefaults setObject:defaultRoot forKey:kDefaultRoot];
}

- (NSURL *)rootUrl:(NSString *)name {
Expand Down
14 changes: 5 additions & 9 deletions app/RootsTableViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#import "ProgressReportViewController.h"
#import "UIApplication+OpenURL.h"
#import "UIViewController+Extras.h"
#import "NSObject+SaneKVO.h"

@interface RootsTableViewController ()
@end
Expand All @@ -29,15 +30,10 @@ @implementation RootsTableViewController

- (void)viewDidLoad {
[super viewDidLoad];
[Roots.instance addObserver:self forKeyPath:@"roots" options:0 context:nil];
[Roots.instance addObserver:self forKeyPath:@"defaultRoot" options:0 context:nil];
}
- (void)dealloc {
[Roots.instance removeObserver:self forKeyPath:@"roots"];
[Roots.instance removeObserver:self forKeyPath:@"defaultRoot"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
[self.tableView reloadData];
[Roots.instance observe:@[@"roots", @"defaultRoot"]
options:0 owner:self usingBlock:^(typeof(self) self) {
[self.tableView reloadData];
}];
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
Expand Down
Loading