ReactiveCocoa MVVM with UITableView - objective-c

I'm using ReactiveCocoa and am trying to apply MVVM. I have a fairly typical UITableView scenario with a refresh control for reloading data.
I've omitted the the UITableDataSource/Delegate methods as these are straight forward. The code below illustrates how I've designed the ViewModel and the ViewController to fit together.
ViewModel.h
#property (strong, nonatomic, readonly) RACCommand *getItemsCommand;
#property (strong, nonatomic, readonly) NSArray *items;
ViewModel.m
- (instancetype)init {
self = [super init];
if (!self) return nil;
#weakify(self);
self.getItemsCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
return [[ItemsDataSource getItems]
doNext:^(NSArray *items) {
#strongify(self);
// I actually do a little extra work here such as sorting
// the items appropriately for the view.
self.items = items;
}];
}];
return self;
}
ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableView addSubview:self.refreshControl];
RACSignal *refreshSignals = [RACSignal merge:#[
[self.refreshControl rac_signalForControlEvents:UIControlEventValueChanged],
[RACSignal return:nil]
]];
[refreshSignals
subscribeNext:^(id x) {
[self.viewModel.getItemsCommand execute:nil];
}];
[RACObserve(self.viewModel, items)
subscribeNext:^(NSArray *items) {
[self.tableView reloadData];
} completed:^{
[self.refreshControl endRefreshing];
}];
}
Questions/Problems
The completed block where I call endRefreshing never gets executed and for the life of me I can't figure out why.
Would it be better to use a public method - (RACSignal *)getItems instead of the getItems RACCommand?
Is my usage of doNext: in the ViewModel correct in order to apply side effects (i.e. the sorting of the items array) without causing an additional subscription?

I suggest making getItemsCommand use -map: to sort and process the items array. Leave any other side effect work to be done in a separate -doNext:. Once you have your command following this pattern (which is more compositional in RAC), then you can use the RAC() macro to assign the command's finished product, the sorted array, to the items property.
RAC(self, items) = [self.getItemsCommand.executionSignals concat];
RAC has a built-in command support for UIRefreshControl that will start/stop the refresh control along with the start/stop of the command. You should find that you can reduce your UIRefreshControl code to:
self.refreshControl.rac_command = self.getItemsCommand;
For table reloading, you can do:
[RACObserve(self, items) subscribeNext:^(id _) {
#strongify(self);
[self.tableView reloadData];
}];
Hope that helps.

1) Well, let's look at the signal:
RACObserve(self.viewModel, items)
When will that complete? Only when self.viewModel or self is deallocated, just like any other RACObserve. As long as those objects are around, it'll keep on nexting any time you set self.items.
It appears that you want it to endRefreshing once the getItemsCommand finishes executing, and you have this sort of implicit expectation that, since you know that command sets self.viewModel.items, that completion will somehow propagate -- but this isn't the case. To see when the command completes, you have to subscribe to the command's returned signal.
2) The advantage of RACCommand is the auto enabling/disabling behavior, which you aren't really taking advantage of here. The more canonical thing to do would be self.refreshControl.rac_command = self.viewModel.getItemsCommand;. That'll handle the endRefreshing stuff for you, saving you the headache from part 1.
3) ...sort of. The do* family of methods injects side effects for every subscription to the signal. So if you subscribe to a signal twice, and it sends a next, any doNext block it has will be invoked twice. An explicit subscription is more clear, since you want to execute this exactly once per next, regardless of how many times it's subscribed to.
#weakify(self);
self.getItemsCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
RACSignal *itemsSignal = [ItemsDataSource getItems];
[itemsSignal subscribeNext:^(NSArray *items) {
#strongify(self);
// Do stuff...
self.items = items;
}];
return itemsSignal;
}];

Related

Objective C - release blocks individually

I have the following dummy architecture: a singleton class that will receive some data, and, at some point(when returnCallback function is called), will return the data using a callback.
#interface Helper: NSObject
{
void (^_completionHandler)(int someParameter);
}
+(Helper *)getInstance;
- (void) doSomethingWithCompletionHandler:(void(^)(int))handler;
#end
#implementation Helper
+(Helper *)getInstance {
static Helper *instance = nil;
#synchronized(self) {
if (instance == nil)
instance = [[self alloc] init];
}
return instance;
}
- (void) doSomethingWithCompletionHandler:(void(^)(int))handler
{
//do things
_completionHandler = [handler copy];
//do things
}
-(void) returnCallback
{
int result;
//do things with result
_completionHandler(result);
//nothing to follow, it just returned the result.
}
#end
Untill now I was calling the helper a single time and everything worked ok.
E.g.
[[Helper getInstance] doSomethingWithCompletionHandler:^(int result){
NSLog(#"I received %d", result);
}];
But now I need to call the helper 2 times, the second one being inside of the first one.
E.g.
[[Helper getInstance] doSomethingWithCompletionHandler:^(int result){
[[Helper getInstance] doSomethingWithCompletionHandler:^(int result){
NSLog(#" Yay, I'm good %d", result);
}];
NSLog(#"They stopped retaining me:( %d", result);
}];
The problem is(as displayed in the log) that the first function callback is released from memory and I cannot access the result variable. A way to resolve that is to keep 2 variables of the callbacks(one with the current one, one with the old one), but what if I'll need the 3rd one? I tried to build an NSMutableArray with the blocks references. But I had to remove them aswell, and I didn't figure out how.(they get copied inside Helper class, so I don't have a reference to that copied object inside the "Testing" class, do I?)
The above code isn't tested as this is more of an architecture-based question. I will however test it and edit the message asap if there are any errors.
Due to the way you have it designed, you can only have one active operation. If you ever try to execute more operations than one at the time, unexpected stuff happens (as in your example).
There is an established pattern for doing stuff like this - take a look at NSOperation and NSOperationQueue, e.g. here

How to name Undo menu entries for Core Data add/remove items via bindings and NSArrayController?

I have a NSTableView populated by a Core Data entity and Add Item / Remove Item buttons all wired with a NSArrayController and bindings in Interface Builder.
The Undo/Redo menu items can undo or redo the add / remove item actions.
But the menu entries are called only „Undo“ resp. „Redo“.
How can i name them like „Undo Add Item“, „Undo Remove Item“, etc.
(I am aware, something similar was asked before, but the accepted answers are either a single, now rotten link or the advice to subclass NSManagedObject and override a method that Apples documentation says about: "Important: You must not override this method.“)
Add a subclass of NSArrayController as a file in your project. In the xib, in the Identity Inspector of the array controller, change the Class from NSArrayController to your new subclass.
Override the - newObject method.
- (id)newObject
{
id newObj = [super newObject];
NSUndoManager *undoManager = [[[NSApp delegate] window] undoManager];
[undoManager setActionName:#"Add Item"];
return newObj;
}
Also the - remove:sender method.
- (void)remove:(id)sender
{
[super remove:sender];
NSUndoManager *undoManager = [[[NSApp delegate] window] undoManager];
[undoManager setActionName:#"Remove Item"];
}
Register for NSManagedObjectContextObjectsDidChangeNotification:
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(mocDidChangeNotification:)
name:NSManagedObjectContextObjectsDidChangeNotification
object: nil];
And parse the userInfo dictionary in the corresponding method:
- (void)mocDidChangeNotification:(NSNotification *)notification
{
NSManagedObjectContext* savedContext = [notification object];
// Ignore change notifications for anything but the mainQueue MOC
if (savedContext != self.managedObjectContext) {
return;
}
// Ignore updates -- lots of noise from maintaining user-irrelevant data
// Set actionName for insertion
for (NSManagedObject* insertedObject in
[notification.userInfo valueForKeyPath:NSInsertedObjectsKey])
{
NSString* objectClass = NSStringFromClass([insertedObject class]);
savedContext.undoManager.actionName = savedContext.undoManager.isUndoing ?
[NSString stringWithFormat:#"Delete %#", objectClass] :
[NSString stringWithFormat:#"Insert %#", objectClass];
}
// Set actionName for deletion
for (NSManagedObject* deletedObject in
[notification.userInfo valueForKeyPath:NSDeletedObjectsKey])
{
NSString* objectClass = NSStringFromClass([deletedObject class]);
savedContext.undoManager.actionName = savedContext.undoManager.isUndoing ?
[NSString stringWithFormat:#"Insert %#", objectClass] :
[NSString stringWithFormat:#"Delete %#", objectClass];
}
}
I've tested this in my own code-- it's rough. Can spend a lot more time making the actionName nicer. I deleted parsing of updates because: 1) insertions and deletions of objects in to-many relationships generate updates of other objects 2) I don't care to figure out how to discover what properties changed at this time
I also have class names that aren't user-friendly, so this is a great time to implement the description function for all entities, and use that rather than the class name.
But this at least works for all object controllers in a project, and easily enough for insert and delete.
[edit] Updated with mikeD's suggestion to cover redo having an inverse name. Thanks!

Using willChangeValueForKey/didChangeValueForKey with OSSpinLockLock

I want to issue a notification when all 4 different threads have finished their work. I'm keeping count of the total threads and have a listener that does some work when the threads have finished.
Is the following a safe way to do this?
// ivars:
NSMutableArray *list;
OSSpinLock lock;
#define MAX_ALLOWED 4
- (void)someThreadedWork
{
// Iterate thru 4 different items using gcd and update
for (int x = 0; x < MAX_ALLOWED; ++x)
{
dispatch_async(some_queue, ^{
// Do some work.. once done,
[self updateCount:ix];
});
}
}
- (void)updateCount:(NSInteger)newCount
OSSpinLockLock(&lock);
{
[list addObject:[NSNumber numberWithInt:newCount]];
if ([list count] == MAX_ALLOWED)
{
_allValuesUpdatedAt = [NSDate date];
}
}
OSSpinLockUnlock(&lock);
}
- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object
change:(NSDictionary*)change context:(void*)context
{
// When I get the 'allItemsUpdatedAt' event, I will perform some other work
}
- (id)init {
if (self = [super init])
{
// there is a corresponding removeObserver in the dealloc
list = [[NSMutableArray alloc] init];
[anInstance addObserver:self
forKeyPath:#"allItemsUpdatedAt"
options:NSKeyValueObservingOptionNew
context:NULL];
}
return self;
}
HachiEthan's comment about dispatch_async is exactly correct. If you use dispatch_group_async instead, then you don't need any locking at all, nor do you have to keep track of the threads that are currently working. It'll do all of this for you.
See "Waiting on Groups of Queued Tasks" in the Concurrency Programming Guide for a faster, simpler, more robust, and less energy intensive approach to this problem. See also "Migrating Away from Threads" in the same document to learn how to convert thread-based systems to queue-based systems.
Heres a few things:
Red Flag: You change the value of _allValuesUpdatedAt inside the lock, but are potentially "reading" its value elsewhere... (e.g. whomever is observing the property allValuesUpdatedAt) so unless your allValuesUpdatedAt property is also protected by that same &lock instance, you've got a problem there. (Edit: also, your above code is not retaining the NSDate, so it will be autoreleased on you at some point.)
If you use dispatch_async and pass it a serial queue then you don't need that spin-lock for updating the values in your list, provided that the updateCount: method is only accessed by code running from that serial queue. (Note: You'd still need to protect access to allValuesUpdatedAt)
If updateCount: does need to be accessed from other code, say, the main thread, then yes you will need some sort of lock there, like you have.
EDIT: keeping most of what you have, but addressing the red-flag
//header:
#property (atomic, retain) NSDate *allValuesUpdatedAt;
//impl:
- (void)updateCount:(NSInteger)newCount
OSSpinLockLock(&lock);
{
[list addObject:[NSNumber numberWithInt:newCount]];
if ([list count] == MAX_ALLOWED)
{
//use atomic property for read/write thread-safety:
self.allValuesUpdatedAt = [NSDate date];
}
}
OSSpinLockUnlock(&lock);
}

Objective-C setter is never called

I'm trying to make an NSMutableArray usable in multiple classes. I'm having an issue with defining and using a custom setter, for some reason, even though I call my setter, it is never executed (I have an NSLog set up in the method). Here is all of the relevant code:
AppDelegate.h
#interface TouchTrackerAppDelegate : NSObject <UIApplicationDelegate> {
NSMutableArray *completeLines;
}
#property (nonatomic, retain, setter = setCompleteLines:, getter = getCompleteLines) NSMutableArray *completeLines;
-(NSMutableArray*) getCompleteLines;
-(void) setCompleteLines:(NSMutableArray *) newLines;
AppDelegate.m
#implementation TouchTrackerAppDelegate
-(NSMutableArray*) getCompleteLines {
return self.completeLines;
}
-(void) setCompleteLines:(NSMutableArray *)newLines {
NSLog(#"gets here");
if (completeLines != newLines) {
[completeLines release];
completeLines = [newLines retain];
}
NSLog(#"completeLines global count: %i",[completeLines count]);
}
View.h
#import "TouchTrackerAppDelegate.h"
#interface TouchDrawView : UIView {
NSMutableDictionary *linesInProcess;
NSMutableArray *completeLines;
TouchTrackerAppDelegate *navigationDelegate;
}
#end
View.m*
#import "TouchTrackerAppDelegate.h"
- (id)initWithCoder:(NSCoder *)c
{
[super initWithCoder:c];
linesInProcess = [[NSMutableDictionary alloc] init];
completeLines = [[NSMutableArray alloc] init];
return self;
}
- (void)viewDidLoad {
navigationDelegate = (TouchTrackerAppDelegate *)[[UIApplication sharedApplication] delegate];
}
-(void)endTouches:(NSSet *)touches
{
if([EditModeSingleton isEditMode]){
for(UITouch *t in touches){
NSValue *key = [NSValue valueWithPointer:t];
Line *line = [linesInProcess objectForKey:key];
if(line){
[completeLines addObject:line];
[linesInProcess removeObjectForKey:key];
[navigationDelegate setCompleteLines:completeLines];
NSLog(#"completeLines count: %i", [completeLines count]);
}
}
[self setNeedsDisplay];
}
else {NSLog(#"in Play mode");}
}
The problem arises in my View.m when I call '[navigationDelegate setCompleteLines:completeLines];'. As far as I can tell, this never executes. I'm also not sure if my setter method is correct in the way I'm trying to pass the array from my view to the app delegate for use in other classes. If there is a better way of doing that, I'd appreciate some help.
Thank you!
If you're not entering that function, there's really only one solid possibility:
navigationDelegate is nil. Verify this by logging or asserting it just before sending the message to it in endTouches and then figure out why.
Cnage:
[linesInProcess removeObjectForKey:key];
[navigationDelegate setCompleteLines:completeLines];
To:
[linesInProcess removeObjectForKey:key];
NSAssert(navigationDelegate != nil, #"navigationDelegate is nil");
[navigationDelegate setCompleteLines:completeLines];
For future reference/help (and to answer your question in comments) -
Breakpoint basics in brief:
Set breakpoints at or before the line where you suspect your code breaks/fails/behaves-unexpectedly. Run your program in debug...
If the breakpoint gets hit: Examine both the call-stack and variable-values in the various Debugging panes in Xcode for clues.
Or if the breakpoint is never hit: Go back up a step in your function calls and set a breakpoint there.
If nothing else, breakpoints can narrow your issue down by process of elimination and help you ask better questions that get answered faster. =)
Although StackOverflow helped you track down this problem pretty fast, you can save yourself a lot of time and frustration in the future if you make use of breakpoints.
In this case, setting a breakpoint at or before the line: [navigationDelegate setCompleteLines:completeLines]; would have revealed navigationDelegate was nil. Then you repeat: set a breakpoint at or before navigationDelegate is assigned and re-run it. When this breakpoint didn't get hit, you would then realize your problem is something other than your setter! =)
You might still have had to ask "why isn't viewDidLoad being called?" but with part of the confusion already solved by you, your answer would have arrived much faster! Hope that helps you in the future~

Cocoa bindings between NSTableView and NSMutableArray refuse to update

Ok, I'm very new to Obj-C and Cocoa, but I'm sure my bindings here are correct. I've been googling, searching stack overflow and have checked my values again and again.
So, here are my bindings:
They connect to this class:
#interface TMMaddMangaWindowDelegate : NSWindowController {
...
}
...
#property (copy) NSMutableArray* mangaList;
...
#end
#implementation TMMaddMangaWindowDelegate
...
#synthesize mangaList;
// - (NSMutableArray*) mangaList {
// NSLog(#"mangaList was called!");
// return mangaList;
//}
//- (void) setMangaList:(NSMutableArray *) input{
// NSLog(#"setMangaList was called!");
// [mangaList autorelease];
// mangaList = [input retain];
//}
...
-(void) populateList:(NSArray*)list{
NSMutableArray* newArray = [[NSMutableArray alloc] initWithArray:list];
NSLog(#"Populating List.");
for(NSXMLNode* node in list){
[newArray addObject:node.description];
//[[self mutableArrayValueForKey:#"mangaList"] addObject:node.description];
//NSLog(#"%#", node.description);
}
[self setMangaList:newArray];
[[self chapterListDownloadIndicator] stopAnimation:self];
}
As you can see, I also tried the mutableArrayValueForKey approach, which yielded nothing. I know for a fact mangaList is gaining items.
I've been working on this for a while, and probably made a stupid mistake.
Thanks in advance.
It looks like you are changing mangaList behind the array controller's back. Whenever you are making a change to mangaList you should first call [self willChangeValueForKey:#"mangaList"]; and then [self didChangeValueForKey:#"mangaList"]; once you are done with the change. this will let the array controller know it needs to take a look at what changed.
It turns out that the problem was that the window did not have the class identity of Files Owner set to my window controller/delegate. The moment I set this the window sprang to life.
That problem was also preventing my NSProgressIndicator from working.