Approach to Reactifying delegate methods with side effects

前端 未结 1 1527
离开以前
离开以前 2021-02-04 17:32

Just trying to wrap my head around the ReactiveCocoa approach to certain situations.

I have a situation where a segment controller swaps out children view controllers. I

1条回答
  •  说谎
    说谎 (楼主)
    2021-02-04 17:38

    Here's my answer, as copied from the GitHub issue:

    I haven't used ReactiveCocoaLayout, but I suspect you may find some of this code could be improved by using RCL, in addition to RAC. I'm sure someone else will provide more detail about that.

    The first thing I'd suggest is reading up on -rac_signalForSelector:. It's enormously valuable for bridging between delegate callbacks and RAC signal.

    For example, here's how you get signals that represent your desired callbacks:

    RACSignal *movedToParentController = [[self
        rac_signalForSelector:@selector(didMoveToParentViewController:)]
        filter:^(RACTuple *arguments) {
            return arguments.first != nil; // Ignores when parent is `nil`
        }];
    
    RACSignal *beginSearch = [self rac_signalForSelector:@selector(searchBarShouldBeginEditing:)];
    RACSignal *endSearch = [self rac_signalForSelector:@selector(searchBarShouldEndEditing:)];
    

    Now, let's say you have a method that updates the view:

    - (void)updateViewInsets:(UIEdgeInsets)insets navigationBarAlpha:(CGFloat)alpha animated:(BOOL)animated {
        void (^updates)(void) = ^{
            if (self.tableView.contentInset.top != insets.top) {
                self.tableView.contentInset = insets;
                self.tableView.scrollIndicatorInsets = insets;
            }
            self.navigationController.navigationBar.alpha = alpha;
        };
    
        animated ? [UIView animateWithDuration:0.25 animations:updates] : updates();
    }
    

    Now, you can use start to put a few things together.

    First, since -searchBarShouldBeginEditing: is the shortest:

    [beginSearch subscribeNext:^(id _) {
        UIEdgeInsets insets = UIEdgeInsetsMake(UIApplication.sharedApplication.statusBarFrame.size.height, 0, 0, 0);
        [self updateViewInsets:insets navigationBarAlpha:0 animated:YES];
    }];
    

    Now, for the more complicated piece. This signal composition starts by +mergeing two signals, the signal for -didMoveToParentViewController: and the signal for searchBarShouldEndEditing:. Each of these signals is mapped to the appropriate parent view controller, and paired with a boolean indicating whether to perform animation. This pair of values is packed into a RACTuple.

    Next, using -reduceEach:, the (UIViewController *, BOOL) tuple is mapped into a (UIEdgeInsets, BOOL) tuple. This mapping calculates the edge insets from the parent view controller, but doesn't change animated flag.

    Finally, this signal composition is subscribed to, wherein the helper method is called.

    [[[RACSignal
        merge:@[
            [movedToParentController reduceEach:^(UIViewController *parent) {
                return RACTuplePack(parent, @NO); // Unanimated
            }],
            [endSearch reduceEach:^(id _) {
                return RACTuplePack(self.parentViewController, @YES); // Animated
            }]
        ]]
        reduceEach:^(UIViewController *parent, NSNumber *animated) {
            CGFloat top = parent.topLayoutGuide.length;
            CGFloat bottom = parent.bottomLayoutGuide.length;
            UIEdgeInsets newInsets = UIEdgeInsetsMake(top, 0, bottom, 0);
            return RACTuplePack(([NSValue valueWithUIEdgeInsets:newInsets]), animated);
        }]
        subscribeNext:^(RACTuple *tuple) {
            RACTupleUnpack(NSValue *insets, NSNumber *animated) = tuple;
            [self updateViewInsets:insets.UIEdgeInsetsValue navigationBarAlpha:1 animated:animated.boolValue];
        }];
    

    You'll find with RAC there are often alternative approaches that you can take. The more you experiment, the more you'll discover what works, what doesn't work, the nuances, etc.

    PS. Appropriate @weakify/@strongify is left as an exercise.

    FOLLOW UP ANSWER

    Another approach is to use -rac_liftSelector:. Here's how it could be used for the code you've provided. It's very similar to the code above, except you extract the animated flag into its own signal, instead of nesting it into the signal that calculates the insets.

    RACSignal *insets = [RACSignal
        merge:@[
            [movedToParentController reduceEach:^(UIViewController *parent) {
                return parent;
            }],
            [endSearch reduceEach:^(id _) {
                return self.parentViewController;
            }]
        ]]
        map:^(UIViewController *parent) {
            CGFloat top = parent.topLayoutGuide.length;
            CGFloat bottom = parent.bottomLayoutGuide.length;
            UIEdgeInsets newInsets = UIEdgeInsetsMake(top, 0, bottom, 0);
            return [NSValue valueWithUIEdgeInsets:newInsets];
        }];
    
    RACSignal *animated = [RACSignal merge:@[
        [movedToParentController mapReplace:@NO],
        [endSearch mapReplace:@YES],
    ];
    
    RACSignal *alpha = [RACSignal return:@1];
    
    [self rac_liftSelector:@selector(updateViewInsets:navigationBarAlpha:animated:) withSignals:insets, alpha, animated, nil];
    

    IMO, neither approach is a clear winner over the other. The guidelines however do recommend avoiding explicit subscription.

    0 讨论(0)
提交回复
热议问题