Code Monkey home page Code Monkey logo

reactiveviewmodel's Introduction

ReactiveViewModel Carthage compatible

ReactiveViewModel is a combination code/documentation project for building Cocoa applications using Model-View-ViewModel and ReactiveCocoa.

By explaining rationale, documenting best practices, and providing reusable library components, we want to make MVVM in Objective-C appealing and easy.

Model-View-ViewModel

Most Cocoa developers are familiar with the Model-View-Controller (MVC) pattern:

Model-View-Controller

Model-View-ViewModel (MVVM) is another architectural paradigm for GUI applications:

Model-View-ViewModel

Although it seems similar to MVC (except with a "view model" object in place of the controller), there's one major difference — the view owns the view model. Unlike a controller, a view model has no knowledge of the specific view that's using it.

This seemingly minor change offers huge benefits:

  1. View models are testable. Since they don't need a view to do their work, presentation behavior can be tested without any UI automation or stubbing.
  2. View models can be used like models. If desired, view models can be copied or serialized just like a domain model. This can be used to quickly implement UI restoration and similar behaviors.
  3. View models are (mostly) platform-agnostic. Since the actual UI code lives in the view, well-designed view models can be used on the iPhone, iPad, and Mac, with only minor tweaking for each platform.
  4. Views and view controllers are simpler. Once the important logic is moved elsewhere, views and VCs become dumb UI objects. This makes them easier to understand and redesign.

In short, replacing MVC with MVVM can lead to more versatile and rigorous UI code.

What's in a view model?

A view model is like an adapter for the model that makes it suitable for presentation. The view model is also where presentation behavior goes.

For example, a view model might handle:

  • Kicking off network or database requests
  • Determining when information should be hidden or shown
  • Date and number formatting
  • Localization

However, the view model is not responsible for actually presenting information or handling input — that's the sole domain of the view layer. When the view model needs to communicate something to the view, it does so through a system of data binding.

What about view controllers?

OS X and iOS both have view (or window) controllers, which may be confusing at first glance, since MVVM only refers to a view.

But upon closer inspection, it becomes apparent that view controllers are actually just part of the view layer, since they handle things like:

  • Layout
  • Animations
  • Device rotation
  • View and window transitions
  • Presenting loaded UI

So, "the view" actually means the view layer, which includes view controllers. There's no need to have a view and a view controller for the same section of the screen, though — just pick whichever class is easier for the use case.

No matter whether you decide to use a view or a view controller, you'll still have a view model.

ReactiveCocoa

MVVM is most successful with a powerful system of data binding. ReactiveCocoa is one such system.

By modeling changes as signals, the view model can communicate to the view without actually needing to know that it exists (similarly for model → view model communication). This decoupling is why view models can be tested without a view in place — the test simply needs to connect to the VM's signals and verify that the behavior is correct.

ReactiveCocoa also includes other conveniences that are hugely beneficial for MVVM, like commands, and built-in bindings for AppKit and UIKit.

Getting Started

To build ReactiveViewModel in isolation, open ReactiveViewModel.xcworkspace. To integrate it into your project, include ReactiveViewModel.xcodeproj and ReactiveCocoa.xcodeproj and link your target against the ReactiveViewModel and ReactiveCocoa targets for your platform.

More Resources

Model-View-ViewModel was originally developed by Microsoft, so many of the examples are specific to WPF or Silverlight, but there are still a few resources that may be useful:

Blog posts:

Presentations:

reactiveviewmodel's People

Contributors

bigboybad avatar esttorhe avatar joshaber avatar joshvera avatar jspahrsummers avatar kylef avatar mdiep avatar robrix avatar vox-humana avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

reactiveviewmodel's Issues

Using forwardSignalWhileActive to fix KVO performance on iOS 9.

Hi! I'm trying to work around #2383. We're using a few RACSignals in our UICollectionViewCells, so we are hitting the performance issues pretty quickly.

I saw some talk on #12 about using -forwardSignalWhileActive: to dispose upon deactivation, and resubscribe upon activation.

Will disposing on the signal in this manner trigger an 'un-observe' on KVO?

RVM Crash with take: on didBecomeActiveSignal when using OCMock

This isn't an RVM issue per se, but I wanted to document it in case anyone else had this issue and was google for an answer. Also, I'm looking for workaround (other than "don't use mocks", of course).

I have a very simple view model:

@implementation ASHViewModel

-(id)init
{
    self = [super init];
    if (self == nil) { return nil; }

    [[self.didBecomeActiveSignal take:1] subscribeNext:^(id x) {
        NSLog(@"Active");
    }];

    return self;
}

@end

Nothing special. Now, when I unit test it, I create a mock.

SpecBegin(ASHViewModel)

describe(@"in a describe block", ^{
    it(@"shouldn't crash", ^{
        ASHViewModel *viewModel = [[ASHViewModel alloc] init];
        id mockViewModel = [OCMockObject partialMockForObject:viewModel];

        [mockViewModel setActive:YES];
    });
});

SpecEnd

The problem is, due to the mocking, something funky is going on under the hood with take: being disposed of. It results in the following crash:

2014-07-30 13:26:22.644 Bug[15566:60b] Active
<unknown>:0: error: -[ASHViewModelSpec in_a_describe_block_shouldn_t_crash] : Cannot remove an observer <RACKVOTrampoline 0xd73b7f0> for the key path "active" from <ASHViewModel 0x913ee70> because it is not registered as an observer.
(
    0   CoreFoundation                      0x019081e4 __exceptionPreprocess + 180
    1   libobjc.A.dylib                     0x016878e5 objc_exception_throw + 44
    2   CoreFoundation                      0x01907fbb +[NSException raise:format:] + 139
    3   Foundation                          0x012d546d -[NSObject(NSKeyValueObserverRegistration) _removeObserver:forProperty:] + 538
    4   Foundation                          0x012d51f4 -[NSObject(NSKeyValueObserverRegistration) removeObserver:forKeyPath:] + 105
    5   Foundation                          0x012d5118 -[NSObject(NSKeyValueObserverRegistration) removeObserver:forKeyPath:context:] + 172
    6   Bug                                 0x0002b7a0 -[RACKVOTrampoline dispose] + 528
    7   Bug                                 0x0001fe43 -[RACCompoundDisposable dispose] + 355
    8   Bug                                 0x0000a994 __69-[NSObject(RACKVOWrapper) rac_observeKeyPath:options:observer:block:]_block_invoke96 + 52
    9   Bug                                 0x00020c79 -[RACDisposable dispose] + 201
    10  Bug                                 0x0001ffc1 disposeEach + 81
    11  CoreFoundation                      0x018a9c69 CFArrayApplyFunction + 57
    12  Bug                                 0x0001fed3 -[RACCompoundDisposable dispose] + 499
    13  Bug                                 0x0001fe43 -[RACCompoundDisposable dispose] + 355
    14  Bug                                 0x0001ffc1 disposeEach + 81
    15  CoreFoundation                      0x018a9c69 CFArrayApplyFunction + 57
    16  Bug                                 0x0001fed3 -[RACCompoundDisposable dispose] + 499
    17  Bug                                 0x000388e4 -[RACSerialDisposable dispose] + 196
    18  Bug                                 0x0001fe43 -[RACCompoundDisposable dispose] + 355
    19  Bug                                 0x0001ffc1 disposeEach + 81
    20  CoreFoundation                      0x018a9c69 CFArrayApplyFunction + 57
    21  Bug                                 0x0001fed3 -[RACCompoundDisposable dispose] + 499
    22  Bug                                 0x000388e4 -[RACSerialDisposable dispose] + 196
    23  Bug                                 0x0001fe43 -[RACCompoundDisposable dispose] + 355
    24  Bug                                 0x0001ffc1 disposeEach + 81
    25  CoreFoundation                      0x018a9c69 CFArrayApplyFunction + 57
    26  Bug                                 0x0001fed3 -[RACCompoundDisposable dispose] + 499
    27  Bug                                 0x000388e4 -[RACSerialDisposable dispose] + 196
    28  Bug                                 0x0001fe43 -[RACCompoundDisposable dispose] + 355
    29  Bug                                 0x0001ffc1 disposeEach + 81
    30  CoreFoundation                      0x018a9c69 CFArrayApplyFunction + 57
    31  Bug                                 0x0001fed3 -[RACCompoundDisposable dispose] + 499
    32  Bug                                 0x000388e4 -[RACSerialDisposable dispose] + 196
    33  Bug                                 0x0005f1e7 __29-[RACSignal(RACStream) bind:]_block_invoke125 + 247
    34  Bug                                 0x0006f69b -[RACSubscriber sendNext:] + 251
    35  Bug                                 0x0002cf57 -[RACPassthroughSubscriber sendNext:] + 487
    36  Bug                                 0x0005ec0a __29-[RACSignal(RACStream) bind:]_block_invoke_298 + 106
    37  Bug                                 0x0006f69b -[RACSubscriber sendNext:] + 251
    38  Bug                                 0x00030b91 __29-[RACReturnSignal subscribe:]_block_invoke + 97
    39  Bug                                 0x00070898 -[RACSubscriptionScheduler schedule:] + 488
    40  Bug                                 0x00030a9f -[RACReturnSignal subscribe:] + 479
    41  Bug                                 0x00062e6b -[RACSignal(Subscription) subscribeNext:error:completed:] + 1003
    42  Bug                                 0x0005ea11 __29-[RACSignal(RACStream) bind:]_block_invoke88 + 833
    43  Bug                                 0x0005f1b0 __29-[RACSignal(RACStream) bind:]_block_invoke125 + 192
    44  Bug                                 0x0006f69b -[RACSubscriber sendNext:] + 251
    45  Bug                                 0x0002cf57 -[RACPassthroughSubscriber sendNext:] + 487
    46  Bug                                 0x0005ec0a __29-[RACSignal(RACStream) bind:]_block_invoke_298 + 106
    47  Bug                                 0x0006f69b -[RACSubscriber sendNext:] + 251
    48  Bug                                 0x00030b91 __29-[RACReturnSignal subscribe:]_block_invoke + 97
    49  Bug                                 0x00070898 -[RACSubscriptionScheduler schedule:] + 488
    50  Bug                                 0x00030a9f -[RACReturnSignal subscribe:] + 479
    51  Bug                                 0x00062e6b -[RACSignal(Subscription) subscribeNext:error:completed:] + 1003
    52  Bug                                 0x0005ea11 __29-[RACSignal(RACStream) bind:]_block_invoke88 + 833
    53  Bug                                 0x0005f1b0 __29-[RACSignal(RACStream) bind:]_block_invoke125 + 192
    54  Bug                                 0x0006f69b -[RACSubscriber sendNext:] + 251
    55  Bug                                 0x0002cf57 -[RACPassthroughSubscriber sendNext:] + 487
    56  Bug                                 0x0005ec0a __29-[RACSignal(RACStream) bind:]_block_invoke_298 + 106
    57  Bug                                 0x0006f69b -[RACSubscriber sendNext:] + 251
    58  Bug                                 0x00030b91 __29-[RACReturnSignal subscribe:]_block_invoke + 97
    59  Bug                                 0x00070898 -[RACSubscriptionScheduler schedule:] + 488
    60  Bug                                 0x00030a9f -[RACReturnSignal subscribe:] + 479
    61  Bug                                 0x00062e6b -[RACSignal(Subscription) subscribeNext:error:completed:] + 1003
    62  Bug                                 0x0005ea11 __29-[RACSignal(RACStream) bind:]_block_invoke88 + 833
    63  Bug                                 0x0005f1b0 __29-[RACSignal(RACStream) bind:]_block_invoke125 + 192
    64  Bug                                 0x0006f69b -[RACSubscriber sendNext:] + 251
    65  Bug                                 0x0002cf57 -[RACPassthroughSubscriber sendNext:] + 487
    66  Bug                                 0x0004a74a __35-[RACSignal(Operations) takeUntil:]_block_invoke576 + 106
    67  Bug                                 0x0006f69b -[RACSubscriber sendNext:] + 251
    68  Bug                                 0x0002cf57 -[RACPassthroughSubscriber sendNext:] + 487
    69  Bug                                 0x0000d99c __84-[NSObject(RACPropertySubscribing) rac_valuesAndChangesForKeyPath:options:observer:]_block_invoke48 + 476
    70  Bug                                 0x0000a591 __69-[NSObject(RACKVOWrapper) rac_observeKeyPath:options:observer:block:]_block_invoke77 + 769
    71  Bug                                 0x0002ba55 -[RACKVOTrampoline observeValueForKeyPath:ofObject:change:context:] + 581
    72  Foundation                          0x012d5d77 NSKeyValueNotifyObserver + 362
    73  Foundation                          0x012d7686 NSKeyValueDidChange + 458
    74  Foundation                          0x01293dcd -[NSObject(NSKeyValueObserverNotification) didChangeValueForKey:] + 120
    75  Bug                                 0x0007c83c -[RVMViewModel setActive:] + 332
    76  CoreFoundation                      0x018fc91d __invoking___ + 29
    77  CoreFoundation                      0x018fc82a -[NSInvocation invoke] + 362
    78  CoreFoundation                      0x018fc9aa -[NSInvocation invokeWithTarget:] + 74
    79  BugTests                            0x093aa624 -[OCPartialMockObject handleUnRecordedInvocation:] + 68
    80  BugTests                            0x093a782c -[OCMockObject forwardInvocation:] + 108
    81  CoreFoundation                      0x018f82da ___forwarding___ + 458
    82  CoreFoundation                      0x018f80ee _CF_forwarding_prep_0 + 14
    83  BugTests                            0x093821ee __34-[ASHViewModelSpec spt_defineSpec]_block_invoke_2 + 174
    84  BugTests                            0x093ad76c runExampleBlock + 1500
    85  BugTests                            0x093aedb2 __48-[SPTExampleGroup compileExamplesWithNameStack:]_block_invoke + 258
    86  BugTests                            0x093b413c -[SPTXCTestCase spt_runExampleAtIndex:] + 556
    87  CoreFoundation                      0x018fc91d __invoking___ + 29
    88  CoreFoundation                      0x018fc82a -[NSInvocation invoke] + 362
    89  XCTest                              0x20103c6c -[XCTestCase invokeTest] + 221
    90  XCTest                              0x20103d7b -[XCTestCase performTest:] + 111
    91  BugTests                            0x093b4b28 -[SPTXCTestCase performTest:] + 152
    92  XCTest                              0x20104c48 -[XCTest run] + 82
    93  XCTest                              0x201033e8 -[XCTestSuite performTest:] + 139
    94  XCTest                              0x20104c48 -[XCTest run] + 82
    95  XCTest                              0x201033e8 -[XCTestSuite performTest:] + 139
    96  XCTest                              0x20104c48 -[XCTest run] + 82
    97  XCTest                              0x201033e8 -[XCTestSuite performTest:] + 139
    98  XCTest                              0x20104c48 -[XCTest run] + 82
    99  XCTest                              0x201066ba +[XCTestProbe runTests:] + 183
    100 Foundation                          0x012bd5ec __NSFireDelayedPerform + 372
    101 CoreFoundation                      0x018c6ac6 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 22
    102 CoreFoundation                      0x018c64ad __CFRunLoopDoTimer + 1181
    103 CoreFoundation                      0x018ae538 __CFRunLoopRun + 1816
    104 CoreFoundation                      0x018ad9d3 CFRunLoopRunSpecific + 467
    105 CoreFoundation                      0x018ad7eb CFRunLoopRunInMode + 123
    106 GraphicsServices                    0x038fc5ee GSEventRunModal + 192
    107 GraphicsServices                    0x038fc42b GSEventRun + 104
    108 UIKit                               0x00347f9b UIApplicationMain + 1225
    109 Bug                                 0x000023cd main + 141
    110 libdyld.dylib                       0x01f4f701 start + 1
)

Like I said, not strictly a RVM issue, but open to suggestions. Feel free to close if it's too off-topic.

didBecomeInactiveSignal should skip:1

Discussing with @kylef, it's not intuitive that didBecomeInactiveSignal immediately sends a value of NO in most cases. Complicating matters is the fact that didBecomeInactiveSignal and its corresponding didBecomeActiveSignal are lazily-loaded.

I understand that the header includes the following comment:

// If the receiver is currently active, this signal will send once immediately
// upon subscription.

However, to quote @joshaber, "If we're expecting people to read the docs, we're gonna have a bad time. :hurtrealbad:"

I would suggest we consider that when they are created, if the would immediately send a value due to the current state of active, then they skip:1. Thoughts?

What about ViewModel based routing?

Hi,

Maybe it's not best suitable place for that question, sorry then, but I am just wondering is there something like ReactiveUI Routing concept implementation in ObjC? Kind of ViewModel-based routing, to be able to navigate between screens with ViewModel? And what you think about that?

Thanks

code inside [self.didBecomeActiveSignal subscribeNext:^(id x) {}] not called.

I'm learning to use ReactiveViewModel by following some examples:

but when I used didBecomeActiveSignal, code in subscribeNext not called

Here is my code:

MuseumsListViewModel.m

#import "MuseumsListViewModel.h"
#import "Museum.h"
#import "MuseumItemViewModel.h"
#import "JSONServices.h"
#import "Constants.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
#import <LinqToObjectiveC/NSArray+LinqExtensions.h>

@implementation MuseumsListViewModel

- (instancetype)init {
    self = [super init];
    if (self) {
        @weakify(self)
        [self.didBecomeActiveSignal subscribeNext:^(id x) {
            NSLog(@"didBecomeActiveSignal");
            @strongify(self);
            [self getMuseumsSignal];
        }];
    }
    return self;
}

- (RACSignal *)getMuseumsSignal {
    return
    [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        [JSONServices loadJSONFromURL:MUSEUMS_LIST_URL parseToClass:[MuseumsList class] completion:^(id object) {
            MuseumsList *museumsList = (MuseumsList *)object;
            if (self.museums == nil) {
                self.museums = [[NSMutableArray alloc] init];
            }
            [self.museums addObjectsFromArray:[museumsList.data linq_select:^id(Museum *museum) {
                return [[MuseumItemViewModel alloc] initWithMuseum:museum];
            }]];
            self.meta = museumsList.meta;
        } failure:^(NSError *error) {
            NSLog(@"Error: %@", error);
        }];
        return nil;
    }];
}

@end

MuseumsListViewController.m

#import "MuseumsListViewController.h"

@interface MuseumsListViewController ()

@end

@implementation MuseumsListViewController

- (instancetype)initWithViewModel:(MuseumsListViewModel *)viewModel {
    self = [super init];
    if (self ) {
        self.viewModel = viewModel;
    }
    return self;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

@end

AppDelegate.m

#import "AppDelegate.h"
#import "MuseumsListViewModel.h"
#import "MuseumsListViewController.h"

@interface AppDelegate ()

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

    MuseumsListViewModel *museumsListViewModel = [[MuseumsListViewModel alloc] init];
    MuseumsListViewController *museumsListViewController = [[MuseumsListViewController alloc] initWithViewModel:museumsListViewModel];
    self.window.rootViewController = museumsListViewController;
    [self.window makeKeyAndVisible];
    return YES;
}

@end

Did I miss something? Or I did something wrong? Can you help me? Thank you.

Data Context Interaction pattern on top of MVVM.

Hi, this rather a question or ask for advice, but definitely not an issue.

I spend some time today playing with architecture described in README.md
So at first some of my assumptions.

  1. View layer = View Controller + custom UIView + layouts and other view related stuff
  2. ViewModel = Subclass of some basic ViewModel class, with link to actual model, actual this is an adapter of model for View Controller consumption, values transforming. Other ViewModel responsibilities not clear for me. Who manages UINavigationController stack?
  3. Model = Some DTO class (like Mantle for example)

If all above assumptions are correct, then ViewModel can act as a Role in DCI pattern.
In this pattern Roles collected in Contexts, or in another words - Use cases.
Each Use case can also have some output signals or commands which will trigger start of another use cases.

And finally, Use cases can be connected with each other in some Workflow or Application entity.

This system will allow to test and isolate application not just by parts, but also by task, user stories, and other logical chunks.

Open questions.

  1. Common Shared state between ViewModels (network clients, core data contexts)
  2. Container View Controllers responsibility.

Binding asynchronously loaded images from a view-model

I'm tinkering with an MVVM implementation, and I'm curious to hear others' input.

We need to present a table view UI of people, showing a single label in each cell for the person's name, and a single UIImageView for the person's avatar. We need to fetch each person's avatar from some web service.

The standard Cocoa Touch MVC solution usually involves queueing up some background requests and coordinating their returned images with the appropriate UITableViewCell subviews that are on screen. This is done asynchronously and kicked off via delegate or data source methods, usually handled within the view controller.

It seems like an MVVM solution would push the avatar image retrieval into a view-model.

So, we have a very simple model:

@interface Person : NSObject

@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@property (nonatomic, strong) NSURL *avatarURL;

@end

And I suppose we have the following view-model:

@interface PersonEntryViewModel : NSObject

/// A UI-ready combination of a `Person` `firstName` and `lastName`
@property (nonatomic, copy) NSString *name;

@property (nonatomic, strong) UIImage *avatar;

- (instancetype)initWithPerson:(Person *)person;

@end

Then we could have, say, a UITableViewCell implementation:

@interface PersonTableCell : UITableViewCell
@property (nonatomic, strong) PersonEntryViewModel *viewModel;
@end
@implementation PersonTableCell

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self == nil) return nil;

    RAC(self.textLabel, attributedText) = RACObserve(self, viewModel.name);
    RAC(self.imageView, image) = RACObserve(self, viewModel.avatar);

    return self;
}

@end

This seems like a good strategy, but I'm wrestling with how the PersonEntryViewModel provides a UIImage for the avatar. It seems like +[NSURLConnection rac_sendAsynchronousRequest:] could be the answer:

@implementation PersonEntryViewModel

- (instancetype)initWithPerson:(Person *)person
{
    self = [super init];
    if (!self) return nil;

    // omitted: binding self.name based on person.firstName & person.lastName

    NSURLRequest *request = [NSURLRequest requestWithURL:person.avatarURL];
    RAC(self, avatar) = [[[NSURLConnection rac_sendAsynchronousRequest:request]
        reduceEach:^id(NSURLResponse *response, NSData *data){
            return [[UIImage alloc] initWithData:data];
        }]
        deliverOn:[RACScheduler mainThreadScheduler]];

    return self;
}

@end

…but this will kick off the request on initialization of the view-model as opposed to, say, when the view-model is set on the UITableViewCell within -tableView:cellForRowAtIndexPath:. There also isn't any cancellation of the request when the cell scrolls out of view.

Should the view-model actually expose a RACSignal as opposed to a simple image property? Should the avatar binding between the cell and the view-model occur somewhere else other than the cell's initializer?

Thanks in advance for any input anyone has! If this is too generic of a problem, or poorly outlined, please close it out and I'll try to reevaluate.

GitHub API request failed: NetworkError(Error Domain=NSURLErrorDomain Code=-1001 "The request timed out."

*** Checking out ios-snapshot-test-case at "da629211c17a4c507e2e866e8a19ed3122af770b"
*** Downloading realm-cocoa.framework binary at "v1.1.0"
GitHub API request failed: NetworkError(Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." UserInfo={NSURLSessionDownloadTaskResumeData=<CFData 0x7fd736535570 [0x7fffc5786bd0]>{length = 7061, capacity = 16384, bytes = 0x3c3f786d6c2076657273696f6e3d2231 ... 2f706c6973743e0a}, NSErrorFailingURLKey=https://github-cloud.s3.amazonaws.com/releases/4044891/decf6870-7c73-11e6-8998-f439aca6d4db.zip?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAISTNZFOVBIJMK3TQ%2F20161201%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20161201T043448Z&X-Amz-Expires=300&X-Amz-Signature=e8a82b4001c68e4afa49f4c42692a42e1f8411efe63fe6d888f75676090922cd&X-Amz-SignedHeaders=host&actor_id=0&response-content-disposition=attachment%3B%20filename%3DCarthage.framework.zip&response-content-type=application%2Foctet-stream, _kCFStreamErrorDomainKey=4, NSLocalizedDescription=The request timed out., NSErrorFailingURLStringKey=https://github-cloud.s3.amazonaws.com/releases/4044891/decf6870-7c73-11e6-8998-f439aca6d4db.zip?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAISTNZFOVBIJMK3TQ%2F20161201%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20161201T043448Z&X-Amz-Expires=300&X-Amz-Signature=e8a82b4001c68e4afa49f4c42692a42e1f8411efe63fe6d888f75676090922cd&X-Amz-SignedHeaders=host&actor_id=0&response-content-disposition=attachment%3B%20filename%3DCarthage.framework.zip&response-content-type=application%2Foctet-stream, NSUnderlyingError=0x7fd736462ed0 {Error Domain=kCFErrorDomainCFNetwork Code=-1001 "(null)" UserInfo={_kCFStreamErrorCodeKey=-2102, _kCFStreamErrorDomainKey=4}}, _kCFStreamErrorCodeKey=-2102})

https://github.com/danielgindi/Charts/issues/1886

你好 更新框架文件为啥老是报错了?

hhbdeMacBook-Air:ReactiveViewModel-master huabinhu$ carthage update
*** Cloning Nimble
*** Cloning Quick
*** Cloning xcconfigs
*** Cloning ReactiveCocoa
*** Checking out Nimble at "v0.2.0"
*** Checking out Quick at "v0.2.3"
*** Downloading ReactiveCocoa.framework binary at "v2.5"
GitHub API request failed: NetworkError(Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." UserInfo={NSURLSessionDownloadTaskResumeData=<CFData 0x7faeeb813ae0 [0x7fffc843bbd0]>{length = 7085, capacity = 16384, bytes = 0x3c3f786d6c2076657273696f6e3d2231 ... 2f706c6973743e0a}, NSErrorFailingURLKey=https://github-cloud.s3.amazonaws.com/releases/3606624/32544950-ffb3-11e4-9089-020ae2920dfd.zip?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAISTNZFOVBIJMK3TQ%2F20161016%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20161016T031029Z&X-Amz-Expires=300&X-Amz-Signature=b08e6b7b6ded8c129c177cc16d05fdc6245dd28a8eef9ccff9864dd9191aa29d&X-Amz-SignedHeaders=host&actor_id=0&response-content-disposition=attachment%3B%20filename%3DReactiveCocoa.framework.zip&response-content-type=application%2Foctet-stream, _kCFStreamErrorDomainKey=4, NSLocalizedDescription=The request timed out., NSErrorFailingURLStringKey=https://github-cloud.s3.amazonaws.com/releases/3606624/32544950-ffb3-11e4-9089-020ae2920dfd.zip?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAISTNZFOVBIJMK3TQ%2F20161016%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20161016T031029Z&X-Amz-Expires=300&X-Amz-Signature=b08e6b7b6ded8c129c177cc16d05fdc6245dd28a8eef9ccff9864dd9191aa29d&X-Amz-SignedHeaders=host&actor_id=0&response-content-disposition=attachment%3B%20filename%3DReactiveCocoa.framework.zip&response-content-type=application%2Foctet-stream, NSUnderlyingError=0x7faee967fb60 {Error Domain=kCFErrorDomainCFNetwork Code=-1001 "(null)" UserInfo={_kCFStreamErrorCodeKey=-2102, _kCFStreamErrorDomainKey=4}}, _kCFStreamErrorCodeKey=-2102})

forwardSignalWhileActive as signal operator

A category method on signal would increase readability a lot. If you wanna keep the API footprint small I get that, but anyone who uses this a lot will end up implementing it.

-(RACSignal *)rvm_forwardWhileViewModelActive:(RVMViewModel *)viewModel

Error Handling in ViewModel

This is just a quick question:

I have noticed that in the GroceryList, the GCYViewModel class inherits from RVMViewModel and adds the errors signal.

Is there a specific reason why this kind of error handling is not part of RVMViewModel directly?

Maybe its because the assumption, that all errors are handled equally (e.g. just display the error message on the screen) is too specific and its best practice to just subscribe to the error event of each Action of a viewModel individually and handle the error of that Action individually?

retain cycle when using didBecomeActiveSignal

Hello,

I have a retain cycle in my code caused by the following setup in viewDidLoad

   [[[[RACSignal
    combineLatest:@[RACObserve(self.viewModel,index),
/*A*/                   self.viewModel.didBecomeActiveSignal]]
/*B                          RACObserve(self.viewModel,active)]]*/
    deliverOnMainThread]
    distinctUntilChanged]
    subscribeNext:^(RACTuple* x) {

The option A is causing a retain cycle so the view model is never released. B works fine. Do you see something wrong with the code or it might be a bug ?

Thank you,
Adrian

Communicating up the chain from subviews in ReactiveCocoa MVVM world

Curious when/if you guys leverage the responder chain. I've been playing with different ways of communicating events and status amongst view controllers and subviews like cells. Of most interest to me is communicating back up that chain, which is where I'm wondering if the responder chain would be a good solution.

For propagating view information down the chain, I've tried different combinations of the the cell just having access to the view controller view model (since it's technically representing some of the data coming from that view model), or subviews/cells having their own view model. The later is certainly more modular, but more work.

My main question lies with some event occurring in a subview/cell (a uibutton tap or a uiswitch flipping) somehow being propagated up to the main view model, ultimately so it can affect other views/cells in the main view (think of flipping a switch and hiding some cells). When the cell shares the view controller's view model, it's easy enough because you just call a method on that view model. When the cell has it's own view model, there are a lot of options, none of which feel quite ideal. You could call a method on the cell view model, which in turn would call a method on it's delegate (the view controller view model). You could expose a signal on the cell view model, which the parent view model observes. There are a few other possible solutions too.

So I've been wondering if it would be easier to not try and send messages back up that chain, but instead use the responder chain to call a method on the view controller, which in turn can update the view model appropriately, which would propagate any changes back down the view model hierarchy.

// cc @ashfurrow

ViewModel creation boilerplate in table/collectionviews

One problem I've yet to find a solution to using MVVM is where to neatly take care of Model > ViewModel conversion/wrapping.

I tend to have a ViewModel class per each TableViewCell/CollectionViewCell, and hence when I create a datasource that initializes itself with a set of Model objects, I need to do something like the following:

+ (NSArray *)viewModelsFromModels:(NSArray *)models {
  NSMutableArray *array       = [NSMutableArray array];
  for (Model *model in models) {
    ViewModel *viewModel = [[ViewModel alloc] initWithModel:model];
    [array addObject:viewModel];
  }
  return array;
}

This is....kind of gross, and also leads to creeping complexity and difficulty of use whenever you have to unwrap viewmodels to get to their underlying models (eg, when handing off a viewmodel to another class that wants to create it's own viewmodel).

I've thought of a few solutions to this but none really work:

  • Create categories on the underlying model that creates a viewmodel for whatever our current context is. This a: gets messy and b: doesn't let us do any caching/lazy computation of viewmodel properties (which is a nice bonus), it also could have performance hits since we'd be creating/destroying these objects frequently.
  • Have every cell store a viewModel and have the datasource update the underlying model property of the viewModel. This also means we don't get any nice caching of our viewModel properties, and limits us to functions that expose/transform the underlying models properties in realtime as they are called.
  • Tucking away this code into a generic macro like MVVM_ADAPT(collection, targetClass). This is sort of okay.

I still haven't come up with a nice way around this, it's my only gripe with MVVM right now. What do you do in your projects?

Feedback on MVVM-ification of an app

I've been playing around with MVVM and RAC for a little while now and have been updating an old project to MVVM (RAC will come later). I want to make sure I'm understanding how all the pieces fit together. Does anybody have any feedback on that pull?

Thanks in advance.

How to properly select content os a model based on a filter selected by a UISegmentedControl?

I'm just starting a project and using ReactiveCocoa/ReactiveViewModel for the first time.

I have a view with a segmented control and a table view. The segment has 3 options: recent, all and search.

I created a view model as data source of table view, and now I want that when the user select recent or all, the table view show something like 15 more recent or all data from Core Data, and when the user select filter, a search filed should appear and the content should be filtered based on the text of the search field.

How is the best approach to the view model know what is the selection of the UISegmentedControl?

Should I just create a integer value in the view model, set it via RACCommand when the user changes the selection, and watch it on the view model?

View model observing itself

In order to do validation, I'm creating an isValid signal property on the view model. The signal is created the usual way with -combineLatest:reduce: with the input signals being RACObserve(self, propertyNameX). It seems strange to be observing self, but it also seems to be a necessary approach. Is this the way to do it, or is there a better way?

EDIT: Related to #2.

What happened to initWithModel: and what did it do?

I'm reviewing @ashfurrow's C-41 MVVC project, and notice he's using - (instancetype)initWithModel:(id)model; which is included with his version of RVM. However the method is missing from the latest version. So:

  1. What happened to the method?
  2. What did it do (i.e., what was the model)?

The relationship of the view-model and data-model in MVVM, especially with regard to collections

I know this isn't necessarily a RAC question, so please close it if you don't like these types of questions here.

I was having a conversation with @ashfurrow exploring the relationship of the view model and the (data) model. Namely I was wondering if in a situation where you were on an 'edit' screen, whether the view model was always a proxy between the view and the model.

He does proxy/shadow his non-collection properties with RACChannel which makes sense for validation, etc. The more complicated situation in my mind is any collection properties on the model. Is it worth the headache of maintaining an entirely separate collection on the view model? If not, do you expose the model itself? Some of it's properties? Ash solves for this by using methods that internally access the set on the model. I'm thinking that exposing the set as an immutable read-only property on the view-model might make sense.

Trying to read about this from the .Net world. Some people seem to say you can expose the model or some of it's properties where doing otherwise would just be overhead and no transformation is necessary when accessing.

Would love to hear thoughts on this.

didBecomeInactiveSignal send value before viewDidLoad,is this right?

- (RACSignal *)didBecomeInactiveSignal {
    if (_didBecomeInactiveSignal == nil) {
        @weakify(self);

        _didBecomeInactiveSignal = [[[RACObserve(self, active)
            filter:^ BOOL (NSNumber *active) {
                NSLog(@"active:%@",active);
                return !active.boolValue;
            }]
            map:^(id _) {
                @strongify(self);
                return self;
            }]
            setNameWithFormat:@"%@ -didBecomeInactiveSignal", self];
    }

    return _didBecomeInactiveSignal;
}
  //in  myviewmodel
-(id)init{
//---
    [self.didBecomeInactiveSignal subscribeNext:^(id x){
        NSLog(@"didBecomeInactiveSignal");
    }];
//---
//in myviewcontroller that related with myviewmodel
- (void)viewDidLoad
{
   NSLog(@"did load");
}

output:
2014-11-04 23:03:18.714 [5256:60b] active:0
2014-11-04 23:03:18.793 [5256:60b] didBecomeInactiveSignal
2014-11-04 23:03:18.911 [5256:60b] active:0
2014-11-04 23:03:18.913 [5256:60b] active:0
2014-11-04 23:03:19.051 [5256:60b] active:0
2014-11-04 23:03:19.052 [5256:60b] active:0
2014-11-04 23:03:19.082 [5256:60b] view did load

MVVM and animation callbacks, executing one view update after another

I'm trying to determine a clean way to do the following with MVVM, with the approach that changes to the view should always be triggered by changes to the viewModel. Basically it's an action that provides information that I need to use in the second of two chained animations.

  1. Looking at a table list of items.
  2. Tap one to go into a carousel-like modal where you can page through full screen versions horizontally.
  3. Tap a button on the card indicating you would like to select that one
  4. Close the carousel with animation and then ->
  5. Animate the row you selected in the cards up to the top.

So right now I catch the tap, hide the modal with an animation, and in the completion handler for that animation I update the viewmodel, which causes the next animation step to happen (animating the row to the top).

[[RACObserve(self, optionDetailContainerViewController.cardButtonTappedSignal) switchToLatest]
    subscribeNext:^(RACTuple *values) {
        @strongify(self);
        NSNumber *index = values.first;
        NSNumber *type = values.second;
        if (type.integerValue == TCOptionDetailTypeUse) {
            id object = self.viewModel.searchResults[index.integerValue];
            if ([self.viewModel.selectedOptions containsObject:object]) {
                return;
            }
        }
        [self removeOptionDetailCardsWithCompletionHandler:^{
           @strongify(self);
            if (type.integerValue == TCOptionDetailTypeDelete) {
                [self.viewModel deleteSelectedOption:index.integerValue];
            } else {
                [self.viewModel useThirdPartyObject:self.viewModel.searchResults[index.integerValue]];
            }
        }];
    }];

Really what should happen is tapping the button updates the view model to indicate that the modal should close, and then somehow at the end of that animation, the view model is updated again to reflect the next step with the information (index, type) that gathered from the button tap.

I know this probably isn't spelled out really well, but my brain is a little foggy right now.

How-to MVVM with container controllers?

Hi. I have a question about using MVVM approach with container controllers.
For example. We have container that show ListViewController or MapViewController. Simple tab-bat approach. Does this container view controller require standalone ViewModel?
If yes, does this view model correct:

@property RACCommand* activateMapPresentation;
@property RACCommand* activateListPresentation;
@property BOOL mapIsActive;
@property BOOL listIsActive;

Static strings, images etc. where do they belong?

I am still new at the MVVM playground but so far I like it a lot, but where do static strings, images etc. belong?

So which one is preferable:
self.title = NSLocalizedString(@"SomeKey", @"....");
or
self.title = self.viewModel.title; (add RAC+RACObserve)

I am leaning towards the last one because future change would not involve the ViewController.

Another example could be TablieView section header titles, Ash Furrow does the following in his C-41 code:

-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    if (section == ASHEditRecipeViewControllerMetadataSection) {
        return nil;
    } else if (section == ASHEditRecipeViewControllerFilmTypeSection) {
        return NSLocalizedString(@"Film Type", @"Edit View Controller section title");
    } else if (section == ASHEditRecipeViewControllerStepsSection) {
        return NSLocalizedString(@"Steps", @"Edit View Controller section title");
    } else {
        return nil;
    }
}

But would it not be better to ask the ViewModel like:

-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    return [self.viewModel headerTitleForSection:section];
}

-Morten

run error

Hi, I'm a Chinese Programmer,after I clone the project from github,what should I do next.
11111

can`t pod install 0.3.3

I cant pod install 0.3.3, even if repo update. When run pod search ReactiveViewModel`.

-> ReactiveViewModel (0.3)
   Model-View-ViewModel, using ReactiveCocoa.
   pod 'ReactiveViewModel', '~> 0.3'
   - Homepage: https://github.com/ReactiveCocoa/ReactiveViewModel
   - Source:   https://github.com/ReactiveCocoa/ReactiveViewModel.git
   - Versions: 0.3, 0.2, 0.1.1 [master repo]

But ReactiveCoccoa exist the newest release when run pod search ReactiveCocoa.

-> ReactiveCocoa (7.2.0)
   Streams of values over time
   pod 'ReactiveCocoa', '~> 7.2.0'
   - Homepage: https://github.com/ReactiveCocoa/ReactiveCocoa
   - Source:   https://github.com/ReactiveCocoa/ReactiveCocoa.git
   - Versions: 7.2.0, 7.1.0, 7.1.0-rc.2, 7.1.0-rc.1, 7.0.1, 7.0.0, 7.0.0-rc.1, 7.0.0-alpha.2, 7.0.0-alpha.1, 6.1.0-alpha.2, 6.1.0-alpha.1, 6.0.2, 6.0.1, 6.0.0,
   6.0.0-rc.3, 6.0.0-rc.2, 6.0.0-rc.1, 6.0.0-alpha.1, 5.0.4, 5.0.3, 5.0.2, 5.0.1, 5.0.0, 5.0.0-rc.1, 5.0.0-alpha.6, 5.0.0-alpha.5, 5.0.0-alpha.3, 5.0.0-alpha.2, 4.2.2,
   4.2.1, 4.1.0, 4.0.4-alpha-4, 4.0.4-alpha-1, 4.0.3-alpha-3, 4.0.3-alpha-1, 4.0.2-alpha-3, 4.0.2-alpha-1, 4.0.1, 4.0.1-alpha-3, 4.0.1-alpha-1, 4.0.0, 4.0.0-alpha-3,
   4.0.0-alpha-2, 4.0.0-alpha-1, 4.0.0-RC.2, 4.0.0-RC.1, 3.0.0, 3.0.0-swift2, 3.0-beta.9, 3.0-beta.6, 3.0-alpha.3, 3.0.0-alpha.1, 3.0-RC.1, 2.5, 2.4.7, 2.4.6, 2.4.5,
   2.4.4, 2.4.2, 2.3.1, 2.3, 2.2.4, 2.2.3, 2.2.2, 2.2, 2.1.8, 2.1.7, 2.1.6, 2.1.5, 2.1.4, 2.1.3, 2.1.2, 2.1.1, 2.1, 2.0, 1.9.7, 1.9.6, 1.9.5, 1.9.4, 1.8.1, 1.8.0, 1.7.2,
   1.7.1, 1.7.0, 1.6.0, 1.5.0, 1.4.0, 1.3.1, 1.0.0, 0.17.1, 0.16.1, 0.13.1, 0.12.0, 0.10.0, 0.9.0, 0.8.0, 0.6.0, 0.5.0, 0.0.1 [master repo]

Will not support pod anymore?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.