How to bind Realm objects changes? - ios

In my project im trying to work via MVVM,
so in VM in .h file
#property (nonatomic, strong) NSArray *cities;
in .m file
- (NSArray *)cities {
return [[GPCity allObjects] valueForKey:#"name"];
}
GPCity is a RLMObject subclass
How to bind this via ReactiveCocoa (i mean see all cities updates/adds/removes) ?
Something like:
RAC(self, cities) = [[GPCity allObjects] map:(GPCity *)city {return city.name;}];

You can wrap Realm change notifications in a RAC signal:
#interface RLMResults (RACSupport)
- (RACSignal *)gp_signal;
#end
#implementation RLMResults (RACSupport)
- (RACSignal *)gp_signal {
return [RACSignal createSignal:^(id<RACSubscriber> subscriber) {
id token = [self.realm addNotificationBlock:^(NSString *notification, RLMRealm *realm) {
if (notification == RLMRealmDidChangeNotification) {
[subscriber sendNext:self];
}
}];
return [RACDisposable disposableWithBlock:^{
[self.realm removeNotification:token];
}];
}];
}
#end
and then do:
RAC(self, cities) = [[[RLMObject allObjects] gp_signal]
map:^(RLMResults<GPCity *> *cities) { return [cities valueForKey:#"name"]; }];
This will unfortunately update the signal after every write transaction, and not just ones which modify cities. Once Realm 0.98 is released with support for per-RLMResults notifications, you'll be able to do the following, which will only update when a GPCity object is updated:
#interface RLMResults (RACSupport)
- (RACSignal *)gp_signal;
#end
#implementation RLMResults (RACSupport)
- (RACSignal *)gp_signal {
return [RACSignal createSignal:^(id<RACSubscriber> subscriber) {
id token = [self addNotificationBlock:^(RLMResults *results, NSError *error) {
if (error) {
[subscriber sendError:error];
}
else {
[subscriber sendNext:results];
}
}];
return [RACDisposable disposableWithBlock:^{
[token stop];
}];
}];
}
#end

Related

Don't trigger RACSignal side effects until it has completed

I'm trying to create signal that will trigger it side effects on first subscription, and replay events for any further subscriptions. Once the signal sends complete or error I would like to re trigger side effect for next subscription. I've come up with this as solution, but I'm wondering if there is more elegant way to solve this.
#interface ViewController ()
#property (nonatomic, assign) NSUInteger counter;
#property (nonatomic, strong) RACSignal *defferedIncrement;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[[self defferedIncrement] subscribeNext:^(id x) {
NSLog(#"Count: %#", x);
}];
[[self defferedIncrement] subscribeNext:^(id x) {
NSLog(#"Count: %#", x);
}];
}
- (IBAction)buttonDidTap:(id)sender {
[[self defferedIncrement] subscribeNext:^(id x) {
NSLog(#"Count: %#", x);
}];
}
- (RACSignal *)realIncrement
{
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
RACDisposable *disposable = [RACDisposable new];
if (disposable.disposed) { return disposable; }
++self.counter;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:#(self.counter)];
[subscriber sendCompleted];
});
return disposable;
}];
}
- (RACSignal *)defferedIncrement
{
if (!_defferedIncrement) {
_defferedIncrement = [[RACSignal defer:^RACSignal *{
return [[self realIncrement] finally:^{
_defferedIncrement = nil;
}];
}] replayLazily];
}
return _defferedIncrement;
}
#end

Filter function is not working in this case?

I am try to follow tutorial regarding to ReactiveCocoa from Ray, but somehow the filter function is working since it always goes down to the subscribeNext although I debugged that filter function does go with the return #NO branch.
#import <Accounts/Accounts.h>
#import <Social/Social.h>
#import <ReactiveCocoa/ReactiveCocoa.h>
#import "SearchViewController.h"
typedef NS_ENUM(NSInteger, RWTwitterInstantError) {
RWTwitterInstantErrorAccessDenied,
RWTwitterInstantErrorNoTwitterAccounts,
RWTwitterInstantErrorInvalidResponse
};
static NSString * const RWTwitterInstantDomain = #"TwitterInstant";
#interface SearchViewController ()
{
RACDisposable *requestTwiiterSubscription;
}
#property (strong, nonatomic) ACAccountStore *accountStore;
#property (strong, nonatomic) ACAccountType *twitterAccountType;
#property (weak, nonatomic) IBOutlet UISearchBar *searchBar;
#property (strong, nonatomic) UITextField *searchBarTextField;
#end
#implementation SearchViewController
- (UITextField *)searchBarTextField {
if (!_searchBarTextField) {
for (UIView *view in self.searchBar.subviews) {
for (id deeperView in view.subviews) {
if ([deeperView isKindOfClass:[UITextField class]]) {
_searchBarTextField = deeperView;
}
}
}
}
return _searchBarTextField;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.searchBar.text = #"Co";
__weak SearchViewController *weakSelf = self;
self.accountStore = [[ACAccountStore alloc] init];
self.twitterAccountType = [self.accountStore
accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
/**
* The then method waits until a completed event is emitted, then subscribes to the signal returned by its block parameter.
* This effectively passes control from one signal to the next.
*/
requestTwiiterSubscription = [[[[self requestAccessToTwitterSignal]
then:^RACSignal *{
return weakSelf.searchBarTextField.rac_textSignal;
}]
filter:^BOOL(NSString *textString) {
if (textString.length >= 3) {
return #YES;
}
return #NO;
}]
subscribeNext:^(id x) {
NSLog(#"%#", x);
} error:^(NSError *error) {
NSLog(#"An error occurred: %#", error);
}];
}
- (void)dealloc {
[requestTwiiterSubscription dispose];
}
- (RACSignal *)requestAccessToTwitterSignal {
// 1 - define an error
NSError *accessError = [NSError errorWithDomain:RWTwitterInstantDomain
code:RWTwitterInstantErrorAccessDenied
userInfo:nil];
// 2 - create the signal
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 3 - request access to twitter
[self.accountStore
requestAccessToAccountsWithType:self.twitterAccountType
options:nil
completion:^(BOOL granted, NSError *error) {
// 4 - handle the response
if (!granted) {
[subscriber sendError:accessError];
} else {
[subscriber sendNext:nil];
[subscriber sendCompleted];
}
}];
return nil;
}];
}
#end
You have the wrong return values in the filter. You want:
requestTwiiterSubscription = [[[[self requestAccessToTwitterSignal]
then:^RACSignal *{
return weakSelf.searchBarTextField.rac_textSignal;
}]
filter:^BOOL(NSString *textString) {
if (textString.length >= 3) {
return YES;
}
return NO;
}]
subscribeNext:^(id x) {
NSLog(#"%#", x);
} error:^(NSError *error) {
NSLog(#"An error occurred: %#", error);
}];
You were returning NSNumber objects instead of BOOL values.

iOS Azure Mobile Service Completion Method

i try to get some data from Azure, all works fine.
My problem is to populate the Data in a table view.
Here is my Implementaion of the TableViewController (only the important):
#interface tableview ()
#property (strong, nonatomic) AzureService *ClientService;
#end
#implementation overview
#synthesize tableViewObject,tabledata,ClientService;
- (void)viewDidLoad {
[super viewDidLoad];
[self.tableViewObject setDelegate:self];
[self.tableViewObject setDataSource:self];
self.ClientService = [[AzureService alloc]init];
[self.ClientService DatamyWay:^
{
[self.tableViewObject reloadData];
NSLog(#"Reload Table after complete Request");
}];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.ClientService.loadedItems count];
}
#end
Here´s the Implementation of the method form the Service
- (void) DatamyWay:(completionBlock)completion
{
[self.table readWithCompletion:^(MSQueryResult *result, NSError *error) {
if(error) { // error is nil if no error occured
NSLog(#"ERROR %#", error);
} else {
[self.loadedItems addObjectsFromArray:result.items];
for(NSDictionary *item in result.items) { // items is NSArray of records that match query
NSLog(#"Location Name: %#", [item objectForKey:#"name"]);
}
}
}];
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}
Here the debugger output
2015-06-17 22:24:02.848 type2[3041:301640] Reload Table after complete Request
2015-06-17 22:24:03.601 type2[3041:301640] Location Name: abc
2015-06-17 22:24:03.601 type2[3041:301640] Location Name: def
2015-06-17 22:24:03.602 type2[3041:301640] Location Name: ghj
2015-06-17 22:24:03.602 type2[3041:301640] Location Name: klm
so i´ve leraned the call is asynchron. i want to check when the Completion code is called. So i decided to write the "Reload" NS Log Message. As you can see in the Debugger output, the NSlog message from the Completion block writes before the NSlog message in the Service Method. so the numbersinRowSection can´t count and nothing will happen. I hope the problem is described clearly.
Regards
i know, i shouldn´t post my own answer. I hope someone can help this one.
Put the Completion block in the Request.
code that works in my Service Implementation
- (void) DatamyWay:(completionBlock)completion
{
AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
[self.table readWithCompletion:^(MSQueryResult *result, NSError *error) {
if(error) { // error is nil if no error occured
NSLog(#"ERROR %#", error);
} else {
[delegate.loadedItems addObjectsFromArray:result.items];
for(NSDictionary *item in result.items) { // items is NSArray of records that match query
NSLog(#"Location Name: %#", [item objectForKey:#"name"]);
}
completion();
}
}];
}

core data: background context ios

I already posted this problem and no one tell me what is the problem, please help.
I have problem when I use Core Data to save data in my iOS app.
My application can receive many and many text message in a second from a server.
So i need to save those message in my data base, now i use NSOperations inside queue to save messages (so you can imagine the number of the NSOperation in the NSOperationQueue).
The problem is if the i have a normal flow of messages then it works fine and if have an important flow of messages , the app freeze indefinitely or the NSOperations increase without limit in the queue.
PS: if remove the observer of merge the messages are saved without any problem
This my code of the NSOperation:
#interface SaveRecievedMessageOperation()
#property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, strong) AppDelegate *appdelegate;
#property (nonatomic, assign) BOOL executing;
#property (nonatomic, assign) BOOL finished;
#end
#implementation SaveRecievedMessageOperation
#synthesize message;
#synthesize managedObjectContext;
#synthesize appdelegate;
#synthesize executing;
#synthesize finished;
- (id)initWithMessage:(SipMessage *)messageToSave
{
if (self = [super init]) {
self.message = messageToSave;
}
return self;
}
- (void)main
{
#autoreleasepool
{
self.appdelegate = [[UIApplication sharedApplication] delegate];
[self managedObjectContext];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(mergeChanges:)
name:NSManagedObjectContextDidSaveNotification
object:self.managedObjectContext];
[self saveMessage];
}
}
- (NSManagedObjectContext *)managedObjectContext
{
if (managedObjectContext != nil)
{
return managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self.appdelegate persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[managedObjectContext setPersistentStoreCoordinator:coordinator];
[managedObjectContext setMergePolicy:NSOverwriteMergePolicy];
}
return managedObjectContext;
}
- (void)saveMessage
{
NSDictionary *header = self.message.headers;
NSArray *bodies = self.message.bodies;
SipBody *sipBody;
NSDictionary* body;
NSData *ContentData;
if ([[header valueForKey:#"Content-Type"] rangeOfString:#"application/json"].location != NSNotFound)
{
sipBody = [bodies objectAtIndex:0];
body = [NSJSONSerialization
JSONObjectWithData:sipBody.content
options:NSJSONReadingAllowFragments
error:nil];
}
else if ([[header valueForKey:#"Content-Type"] rangeOfString:#"multipart/mixed"].location != NSNotFound)
{
for (SipBody *sipB in bodies) {
if ([[sipB.headers valueForKey:#"Content-Type"] rangeOfString:#"application/json"].location != NSNotFound)
{
body = [NSJSONSerialization
JSONObjectWithData:sipB.content
options:NSJSONReadingAllowFragments
error:nil];
}
else
{
ContentData = [NSData dataWithData:sipB.content];
}
}
}
else
{
return;
}
MGMPhone *sender;
NSArray *senders = [self updatePhonesFromMSISDNsList:[[header valueForKey:#"swfrom"] componentsSeparatedByString:MGMseparator]];
sender = senders[0];
NSError *error;
MGMMessage *aMesage = [MGMMessage createInContext:self.managedObjectContext];
[aMesage setBoxType:[NSNumber numberWithInteger:BoxTypeIncomingMessage]];
[aMesage setContent:[body valueForKey:#"Content"]];
[aMesage setContentType:[header valueForKey:#"Content-Type"]];
[aMesage setGroupMessage:( [[header valueForKey:#"groupmessage"] isEqualToString:#"true"]
?
[self saveContext];
}
#pragma mark core data
- (void)mergeChanges:(NSNotification *)notification
{
dispatch_async(dispatch_get_main_queue(), ^{
NSManagedObjectContext *mainContext = [appdelegate managedObjectContext];
[mainContext performSelector:#selector(mergeChangesFromContextDidSaveNotification:) withObject:notification];
[self setManagedObjectContext:nil];
}
- (void)saveContext
{
NSError *error = nil;
if (self.managedObjectContext != nil)
{
if ([self.managedObjectContext hasChanges]) {
BOOL isSaved = [self.managedObjectContext save:&error];
if(!isSaved){
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
else if (![self.managedObjectContext hasChanges]){
[self setManagedObjectContext:nil];
}
}
}
#end
I think everything is fine, except the merge operation. I'm using very similar CoreData operations in background myself.
Typically you want to merge updates from background operations with the 'main' managed object context. So basically in the background operation you should only perform the save: method. (So don't register for save notifications nor initiate merge actions).
Unless you really need to merge managed object contexts in background threads, the save notification should be registered only once in the main thread (typically in a singleton e.g. appDelegate or your own singleton). The save notification will be sent by CoreData after any save operation in other contexts, allowing you to merge these changes with the main context.

KVO on a collection

I added a observer to my collection, and observe the count on it
[[[JHTaskSave defaults] tasks] addObserver:self forKeyPath:#"count" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
JHTaskSave is a singleton object and tasks is a JHTaskCollection KVC compliant, when I add an object to my collection:
[[[JHTaskSave defaults] tasks] addTask:newTask]
The count of tasks changes but the observeValueForKeyPath is not called, I don't understand why
Here is my collection class:
#interface JHTaskCollection : NSObject <NSFastEnumeration>
{
NSMutableArray *_tasks;
}
#property (nonatomic) NSUInteger count;
- (id)taskAtIndex:(NSUInteger)index;
- (void)addTask:(JHTask *)task;
- (void)removeTask:(JHTask *)task;
- (void)insertObject:(id)key inTasksAtIndex:(NSUInteger)index;
- (void)removeObjectFromTasksAtIndex:(NSUInteger)index;
- (void)removeTaskAtIndexes:(NSIndexSet *)indexes;
- (NSArray *)taskAtIndexes:(NSIndexSet *)indexes;
#end
#implementation JHTaskCollection
- (id)init
{
if(self = [super init]) {
_tasks = [[NSMutableArray alloc] init];
}
return self;
}
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained *)stackbuf count:(NSUInteger)len
{
return [_tasks countByEnumeratingWithState:state objects:stackbuf count:len];
}
- (NSArray *)taskAtIndexes:(NSIndexSet *)indexes
{
return [_tasks objectsAtIndexes:indexes];
}
- (void)insertObject:(id)key inTasksAtIndex:(NSUInteger)index
{
[_tasks insertObject:key atIndex:index];
}
- (void)removeObjectFromTasksAtIndex:(NSUInteger)index
{
[_tasks removeObjectAtIndex:index];
}
- (void)removeTaskAtIndexes:(NSIndexSet *)indexes
{
[_tasks removeObjectsAtIndexes:indexes];
}
- (JHTask *)taskAtIndex:(NSUInteger)index
{
return [_tasks objectAtIndex:index];
}
- (NSUInteger)count
{
return _tasks.count;
}
- (void)addTask:(JHTask *)task
{
[_tasks addObject:task];
}
- (void)removeTask:(JHTask *)task
{
[_tasks removeObject:task];
}
#end
Based on the code you posted, if you want count to be Key-Value Observable, you need to send willChangeValueForKey and didChangeValueForKey notifications any time you mutate the collection in a way that changes the count. Using your code:
#implementation JHTaskCollection
- (id)init
{
if(self = [super init]) {
_tasks = [[NSMutableArray alloc] init];
}
return self;
}
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained *)stackbuf count:(NSUInteger)len
{
return [_tasks countByEnumeratingWithState:state objects:stackbuf count:len];
}
- (NSArray *)taskAtIndexes:(NSIndexSet *)indexes
{
return [_tasks objectsAtIndexes:indexes];
}
- (void)insertObject:(id)key inTasksAtIndex:(NSUInteger)index
{
[self willChangeValueForKey: #"count"];
[_tasks insertObject:key atIndex:index];
[self didChangeValueForKey: #"count"];
}
- (void)removeObjectFromTasksAtIndex:(NSUInteger)index
{
[self willChangeValueForKey: #"count"];
[_tasks removeObjectAtIndex:index];
[self didChangeValueForKey: #"count"];
}
- (void)removeTaskAtIndexes:(NSIndexSet *)indexes
{
[self willChangeValueForKey: #"count"];
[_tasks removeObjectsAtIndexes:indexes];
[self didChangeValueForKey: #"count"];
}
- (JHTask *)taskAtIndex:(NSUInteger)index
{
return [_tasks objectAtIndex:index];
}
- (NSUInteger)count
{
return _tasks.count;
}
- (void)addTask:(JHTask *)task
{
[self willChangeValueForKey: #"count"];
[_tasks addObject:task];
[self didChangeValueForKey: #"count"];
}
- (void)removeTask:(JHTask *)task
{
[self willChangeValueForKey: #"count"];
[_tasks removeObject:task];
[self didChangeValueForKey: #"count"];
}
#end
There's a couple of reasons why the notification for count isn't firing, both having to do with Key-Value Coding.
The first reason is because the count property is not updated when calling addTask:. There isn't a direct link between the count property and the count of the _tasks array. Any code manipulating the array has to be wrapped between -willChangeValueForKey: and -didChangeValueForKey: calls. However, the KVO protocol provides the ability to set dependent keys. Since the count property is affected by the array, you can set a dependency between these two keys by using
+ (NSSet*) keyPathsForValuesAffectingCount {
return [NSSet setWithObject:#"tasks"];
}
Or the more generic + keyPathsForValuesAffectingValueForKey:(NSString *)key. Implementing either will fire notifications for count when tasks is modified.
The second reason why this won't work, is because you're not updating the tasks array in a Key-Value compliant way as defined in the Key-Value Coding Programming Guide. Basically, objects in ordered collections have to be added with either
-insertObject:in<Key>AtIndex:
-insert<Key>:atIndexes:
Or be wrapped with the will|didChangeValueForKey: calls.
In your example, if you provide the following implementation after setting the key dependency, you should observe a change.
- (void)addTask:(JHTask *)task
{
[self insertObject:task inTasksAtIndex:[_tasks count]];
}
-(NSInteger) count is a method, and as such, you cannot use KVO to monitor it - you could use AOP though I would think this is not the direction you would like to go.
Perhaps instead, you could directly monitor the collection, rather than it's count, though I'm not sure if you can do this with KVO (I don't think you can) - you would need to add an addObject: method and do your magic in there?
Edit:
You could use 'method swizzling', as laid out in this blog, though is it worth the added obscurity?

Resources