cocoa bindings to parameter values in UI

So I'm working on an instrument that has close to 100 parameters, and following the patterns in the examples I could find got quickly unwieldy. Wondering why one could not simply bind to key paths in the parameterTree, I eventually went down an unpleasant rabbit hole in finding out these KVO notifications do not come back in the application's preferred thread.


Any way we go, this seems a really un-idiomatic way to write UI code in Cocoa.


So.. I came up with this for my controller, which I don't like for several reasons, although I like it much more than dealing with outlets and actions for every control in the view.


Am I doing something unnecessarily the hard way?


Also, I'd think two things in XCode/AudioToolkit would be extremely valuable: 1) The ability to define a parameterTree as part of your xib/storyboard like you can with CoreData schemas, and 2) some sort of official object controller class provided which does this bridging.



@synthesize parameters = _parameters;
@synthesize parameterTree = _parameterTree;

- (void)dealloc {
    if (observer != nil) {
        [_parameterTree removeParameterObserver:observer];
        observer = nil;
    }
}

- (p2fmAudioUnit *)getAudioUnit {
    return _audioUnit;
}

- (void)setAudioUnit:(p2fmAudioUnit *)audioUnit {
    _audioUnit = audioUnit;
    dispatch_async(dispatch_get_main_queue(), ^{
        if ([self isViewLoaded]) {
            [self connectView];
        }
    });
}

- (void) viewDidLoad {
    [super viewDidLoad];
    self.preferredContentSize = self.view.frame.size;

    if (_audioUnit) {
        [self connectView];
    }
}

- (void)connectView {
    AUParameterTree *tree = _audioUnit.parameterTree;
    if (tree == nil) {
        return;
    }
   
    self.parameterTree = tree;
    [self refresh];
   
    [_audioUnit addObserver:self
                 forKeyPath:@"allParameterValues"
                    options:NSKeyValueObservingOptionNew
                    context:NULL];
   
    __weak AudioUnitViewController *weakSelf = self;
    observer = [_parameterTree tokenByAddingParameterObserver:^(AUParameterAddress address, AUValue value) {
        __strong AudioUnitViewController *strongSelf = weakSelf;
        AUParameter *param = [strongSelf->_parameterTree parameterWithAddress:address];
        if (param != nil) {
            dispatch_async(dispatch_get_main_queue()
                           , ^{
                               [strongSelf refreshParam:param];
                           });
        }
    }];
}

- (void)refresh {
    for (AUParameter *param in [_parameterTree allParameters]) {
        [self refreshParam:param];
    }
}

- (void)refreshParam:(AUParameter *)param {
    NSArray *keys = [[param keyPath] componentsSeparatedByString:@"."];
    NSUInteger len = [keys count];
    if (len < 1) {
        return;
    }
    NSMutableDictionary *dict = _parameters;
    if (dict == nil) {
        self.parameters = [[NSMutableDictionary alloc] init];
        dict = _parameters;
    }
    for (NSUInteger i = 0; i < (len-1); i++) {
        NSString *key = [keys objectAtIndex:i];
        NSMutableDictionary *node = [dict objectForKey:key];
        if (node == nil) {
            node = [[NSMutableDictionary alloc] init];
            [dict setValue:node forKey:key];
        }
        dict = node;
    }
    NSString *key = [keys lastObject];
    BOOL addObserver = NO;
    if ([dict objectForKey:key] == nil) {
        addObserver = YES;
    }
    [dict setValue:@(param.value) forKey:key];
    if (addObserver) {
        [dict addObserver:self
               forKeyPath:key
                  options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
                  context:(void *)param];
    }
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary<nskeyvaluechangekey,id> *)change
                       context:(void *)context {
    if (object == _audioUnit && [keyPath isEqualToString:@"allParameterValues"]) {
        dispatch_async(dispatch_get_main_queue()
                       , ^{
                           [self refresh];
                       });
    } else if (context != NULL) {
        id p = (__bridge id)context;
        if ([p isKindOfClass:[AUParameter class]]) {
            AUParameter *param = p;
            AUValue new = [[change valueForKey:NSKeyValueChangeNewKey] floatValue];
            AUValue old = [[change valueForKey:NSKeyValueChangeOldKey] floatValue];
            if (new != old) {
                [param setValue:new originator:observer];
            }
        }
    }
}