RACObserve subscribeNext didn't execute when value of the observed item changed? - objective-c

hello everybody I have an weird issue , I putted this code in my bind method :
[RACObserve(self.viewModel,contacts) subscribeNext:^(id x) {
[self.contactsTableView reloadData];
}];
but when the contacts in my viewModel changed , the subscribeNext didn't execute !! , I checked if the value changed by debugging and it changed normally !!
this is where the value is changed in my view Model (Simplified) :
I initilized it here :
- (instancetype)init {
self.contacts = [[NSMutableArray <Contact *> alloc]init];
}
and changed here
#pragma mark - load and filter methods
- (RACCommand *)loadContactsCommand {
ContactsNetworkManager *contactNetworkManager = [ContactsNetworkManager sharedManager];
return [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
return [[contactNetworkManager getAllContactsSignal] map:^id(NSMutableArray<Contact *> * value) {
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:#"firstName" ascending:YES];
[value sortUsingDescriptors:[NSArray arrayWithObject:descriptor]];
[_contacts removeAllObjects];
[_contacts arrayByAddingObjectsFromArray:value];
return value;
}];
}];
}
#pragma mark - Actions
- (void)loadContacts {
[self.loadContactsCommand execute:nil];
}
#end

find it , actually the solution is too simple , I had to use self rather than on an independent reference to the NSMutableArray object like _ . In other words, it won't work if you do that , because the KVO established by RACObserve() is relative to the object you pass in as its first parameter (self, in this case), so only KVC-compliant mutations that pass through the observed object will trigger the observation notifications.

Related

Retrieving Entities from Core Data: Printing properties gives me incomplete/unwanted results?

I've recently decided to move all my application data from plists to Core Data. This was pretty straightforward. I had a class with a couple properties I would read all my plist info to using a dictionary. This was mimicked by creating an entity with the same exact attributes. I then loaded the plist information into Arrays, and parsed them all, writing and save new entities to core data.
Sounds good no? Well, I thought to so. However, now that I'm reading it all back in, I've noticed some problems.
For one, when I go ahead and try to print off the properties of my object as follows:
for (CXStellarObject *obj in self.starsArray)
{
NSLog(#"Type: %#\n Language: %#\n Name: %#\n ImageName: %#\n Description: %#\n",obj.type,obj.language.label,obj.name,obj.imageName,obj.description);
}
I get this:
Type: star
Language: (null)
Name: Sirius
ImageName:
Description: <CXStellarObject: 0x7fbb0a58d5d0> (entity: CXStellarObject; id: 0xd000000000140000 <x-coredata://9E3F584C-3214-4A6A-B55A-B63D066A152B/CXStellarObject/p5> ; data: {
descriptor = "The brighest star visible from Earth, Sirius (Also known as Sirius A, or the Dog Star), is a bright white dwarf. It is part of the Canis Major Constellation. It is approximately 8.6 light years from o";
imageName = "";
language = "0xd000000000040002 <x-coredata://9E3F584C-3214-4A6A-B55A-B63D066A152B/CXLocalizationAsset/p1>";
name = Sirius;
type = star;
})
For one, my description isn't even complete. It cuts off at "from o", when it actually goes on longer in the plist. Next, name and type are repeated at the bottom of the printed results. I don't know why they're there.
Finally, what are all these addresses and other junk flanking the resulting data? I don't want that there, how did it get there?
You're probably going to need to see what I've got going on here, so I'll show you the Entity that is involved:
Here's how I wrote all these objects to Core Data:
-(void)writeAllArraysToCoreData
{
NSArray *starsArray = [self starsArray];
NSArray *planetsArray = [self planetsArray];
NSArray *moonsArray = [self moonsArray];
NSArray *allData = #[starsArray,planetsArray,moonsArray];
for (NSArray *array in allData)
{
for (NSDictionary *dict in array)
{
[self archieveCXStellarObjectWithLanguage:[dict valueForKey:#"Language"] Type:[dict valueForKey:#"Type"] Name:[dict valueForKey:#"Name"] ImageName:[dict valueForKey:#"ImageName"] Descriptor:[dict valueForKey:#"Description"]];
}
}
}
And finally, the method called in here that I defined:
-(void)archieveCXStellarObjectWithLanguage:(NSString *)languageCode Type:(NSString *)type Name:(NSString *)name ImageName:(NSString *)imageName Descriptor:(NSString *)descriptor
{
CXStellarObject *object = [NSEntityDescription insertNewObjectForEntityForName:#"CXStellarObject" inManagedObjectContext:self.managedObjectContext];
[object setLanguage:[self.supportedLanguageAssets valueForKey:languageCode]];
[object setType:type];
[object setName:name];
[object setImageName:imageName];
[object setDescriptor:descriptor];
// Add New Object to All Objects Array
[self.allStoredObjects addObject:object];
// Save Changes
[self saveContext];
}
And finally how I extracted them, and where this is going a bit awry (Don't worry, it's not a complicated method)
-(void)loadData
{
if (!self.allStoredObjects)
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"CXStellarObject" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSError *error;
NSArray *fetchResults = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (!fetchResults)
{
[NSException raise:#"Fetch Failed!" format:#"Reason: %#",[error localizedDescription]];
}
[self setAllStoredObjects:[NSMutableArray arrayWithArray:fetchResults]];
// Filter Arrays
NSPredicate *starPredicate = [NSPredicate predicateWithFormat:#"SELF.type == 'star'"];
[self setStarsArray:[fetchResults filteredArrayUsingPredicate:starPredicate]];
for (CXStellarObject *obj in self.starsArray)
{
NSLog(#"Type: %#\n Language: %#\n Name: %#\n ImageName: %#\n Description: %#\n",obj.type,nil,obj.name,obj.imageName,obj.description);
}
}
}
And that's it, thanks for taking the time to get this far. If you need any more information from me, just comment and I'll put it in ASAP.
Assuming your CXStellarObject object is a custom subclass of NSManagedObject, you need to override the description method for that object and write it to return formatted info about your managed object.
Something like this:
-(NSString *) description;
{
NSMutableString *result = [[NSMutableString alloc] init];
[result appendFormat: #" descriptor = %#\n", self.descriptor];
[result appendFormat: #" imageName = %#\n", imageName];
//And so on...
}
I suggest investigating Core Data "faults". Whenever you see references like that in your descriptions that is because the object is not in memory yet, only a stub of the object is. As soon as you touch the object Core Data will pull it into memory automatically and then the full values will be visible.
Further, it is common for long description calls to be clipped. If you want to see the full value, capture it in the debugger and call it via po.

Return User's State as an NSString with reverseGeocodeLocation

I am trying to simply return a user's state. I understand that I need to use reverseGeocodeLocation. I would like to return the state as an NSString in the same way that I am returning the user latitude below:
- (NSString *)getUserLatitude
{
NSString *userLatitude = [NSString stringWithFormat:#"%f",
locationManager.location.coordinate.latitude];
return userLatitude;
}
I currently have this code, but I cannot get it to work. It may be because I am using (void). I am just a bit lost.
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation
*)newLocation fromLocation:(CLLocation *)oldLocation {
CLGeocoder * geoCoder = [[CLGeocoder alloc] init];
[geoCoder reverseGeocodeLocation:newLocation completionHandler:^(NSArray *placemarks,
NSError *error) {
for (CLPlacemark * placemark in placemarks) {
NSString *userState = [placemark locality];
return userState;
}
}];
}
Anyone have any ideas? Thank you!
You have to do something with the retrieved locality in the completion block. This code is executed asynchronously long after the method (with void return) has returned itself.
Usually you would call some sort of method on your own view controller or model class that passes the retrieved information.
Replace the return userState, it does not match the return type of the block.
Instead put something like:
[myViewController didFinishGettingState:userState];
You should look into block and GCD basics so that you can appreciate how this asynchnonicity works.
You are probably not understanding the way your completionHandler is working. The reverseGeocodeLocation:completionHandler: takes an handler, which is a function that will be executed when the lookup is completed and invoked with the placemarks and error as parameters.
What you have to do is to perform something meaningful in that block.
I would start checking whether any error occurred, then I would call a method for failing or success as follows
[geoCoder reverseGeocodeLocation:newLocation completionHandler:^(NSArray *placemarks, NSError *error) {
if (error != nil) {
// Something bad happened...
[self didFailRetrievingUserState:error];
} else {
// Check whether the placemark retrieved is unique
if (placemarks.count > 1) {
NSMutableArray * states = [NSMutableArray array];
for (CLPlacemark * placemark in placemarks) {
NSString * userState = [placemark locality];
[states addObject:userState];
}
[self didFinishRetrievingUserStates:states];
} else {
[self didFinishRetrievingUserState:[placemarks[0] locality]];
}
}
}];
Then of course you need to implement the three methods we are calling in the block above
- (void)didFailRetrievingUserState:(NSError *)error {
// Show error
}
- (void)didFinishRetrievingUserStates:(NSArray *)userStates {
// Do something reasonable with the multiple possible values
}
- (void)didFinishRetrievingUserState:(NSString *)userState {
// Do something reasonable with the only result you have
}
Clearly the above code is meant as a suggestion. You can make different decisions, like for instance handling all the logic inside the handler block, or not discriminating between the unique/not unique cases.
In general it's just important that you understand that the handler block is not supposed to return anything since it's a void function. It's just supposed to do something, and this something may be invoking your "delegate" methods as defined in the example.

Why isn't multithreading working in this implementation?

Q1: Can I call a method and have it execute on a background thread from inside another method that is currently executing on the main thread?
Q2: As an extension of the above, can I call a method and have it execute on a background thread from inside another method that is currently executing on some other background thread itself?
Q3: And one final question given the above : if I initialize an instance of some object X on some thread (main/background) and then have a method Y, of that object X, executing on some other background thread, can this method Y send messages and update an int property (e.g. of that Object X, or is such communication not possible ?
The reason I'm asking this last question is because I've been going over and over it again and I can't figure what is wrong here:
The following code returns zero acceleration and zero degrees values :
MotionHandler.m
#implementation MotionHandler
#synthesize currentAccelerationOnYaxis; // this is a double
-(void)startCompassUpdates
{
locationManager=[[CLLocationManager alloc] init];
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.delegate=self;
[locationManager startUpdatingHeading];
NSLog(#"compass updates initialized");
}
-(int) currentDegrees
{
return (int)locationManager.heading.magneticHeading;
}
-(void) startAccelerationUpdates
{
CMMotionManager *motionManager = [[CMMotionManager alloc] init];
motionManager.deviceMotionUpdateInterval = 0.01;
[motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue currentQueue]
withHandler:^(CMDeviceMotion *motion, NSError *error)
{
self.currentAccelerationOnYaxis = motion.userAcceleration.y;
}
];
}
#end
Tester.m
#implementation Tester
-(void)test
{
MotionHandler *currentMotionHandler = [[MotionHandler alloc] init];
[currentMotionHandler performSelectorInBackground:#selector(startCompassUpdates) withObject:nil];
[currentMotionHandler performSelectorInBackground:#selector(startAccelerationUpdates) withObject:nil];
while(1==1)
{
NSLog(#"current acceleration is %f", currentMotionHandler.currentAccelerationOnYaxis);
NSLog(#"current degrees are %i", [currentMotionHandler currentDegrees]);
}
SomeViewController.m
#implementation SomeViewController
-(void) viewDidLoad
{
[myTester performSelectorInBackground:#selector(test) withObject:nil];
}
#end
However, the following code returns those values normally :
Tester.m
#interface Tester()
{
CLLocationManager *locationManager;
double accelerationOnYaxis;
// more code..
}
#end
#implementation Tester
- (id) init
{
locationManager=[[CLLocationManager alloc] init];
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.delegate=self;
[locationManager startUpdatingHeading];
// more code..
}
-(void) test
{
CMMotionManager *motionManager = [[CMMotionManager alloc] init];
motionManager.deviceMotionUpdateInterval = 0.01;
[motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue]
withHandler:^(CMDeviceMotion *motion, NSError *error)
{
accelerationOnYaxis = motion.userAcceleration.y;
}
];
while(1==1)
{
NSLog(#"current acceleration is %f", accelerationOnYaxis);
NSLog(#"current degrees are %i", locationManager.heading.magneticHeading);
}
}
SomeViewController.m
#implementation SomeViewController
-(void) viewDidLoad
{
[myTester performSelectorInBackground:#selector(test) withObject:nil];
}
What's wrong with the first version? I really want to use that first one because it seems much better design-wise.. Thank you for any help!
Calling performSelectorInBackground:withObject: is the same as if you called the detachNewThreadSelector:toTarget:withObject: method of NSThread with the current object, selector, and parameter object as parameters (Threading Programming Guide). No matter where you call it, a new thread will be created to perform that selector. So to answer your first two questions: yes and yes.
For your final question, as long as this Object X is the same object in both methods, any of X's properties can be updated. But, beware that this can yield unexpected results (ie. see Concurrency Programming Guide). If multiple methods are updating X's property, values can be overwritten or disregarded. But, if you are only updating it from method Y and reading it from all other methods, such problems shouldn't occur.
You should take a look at the Grand Central Dispatch documentation from Apple. It allows you to use multiple threads in a block-based structure.
2 importants function are dispatch_sync() and dispatch_async().
Some examples:
To execute a certain block of code on a background thread and wait until it is finished:
__block id someVariable = nil;
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// do some heavy work in the background
someVariable = [[NSObject alloc] init];
});
NSLog(#"Variable: %#", someVariable);
This function modifies the variable someVariable which you can use later on. Please note that the main thread will be paused to wait for the background thread. If that is not what you want, you can use dispatch_async() as follows:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// do some heavy work in the background
NSObject *someVariable = [[NSObject alloc] init];
// notify main thread that the work is done
dispatch_async(dispatch_get_main_queue(), ^{
// call some function and pass someVariable to it, it will be called on the main thread
NSLog(#"Variable: %#", someVariable);
});
});

Objective-C: No objects in array after adding them. Out of scope!

I have a NSMutableArray in an object.
In an object-method, I do something like this:
/* ... */
[[LRResty client] get:connectURL withBlock:^(LRRestyResponse *r) {
SBJsonParser *jsonParser = [SBJsonParser new];
NSDictionary *jsonResponse = [jsonParser objectWithString:[r asString]];
NSDictionary *permittedBases= [jsonResponse objectForKey:#"permittedBases"];
Database *database = [[Database alloc] init];
for (id key in permittedBases) {
/* ... */
[workingDatabases addObject:database];
}
}];
return workingDatabases;
At the return line, there are no objects in my array (anymore). I am aware of the fact, that the 'database'-objects are going out of scope. But I am saving them in the array.
Am I overseeing something?
If it is of any help, here is the header file:
#class Database;
#interface CommunicationHelper : NSObject {
NSMutableArray *workingDatabases;
}
// The function where the problem appears:
- (NSMutableArray *)getDatabasesForWebsite:(Website *)websiteIn;
#property(nonatomic,copy) NSMutableArray *workingDatabases;
#end
just allocate your workingDatabases (Mutable array) somewhere before using that array.
Once you allocate it,It will work fine.
I assume it's because [LRResty client] get: is asynchronous. The block is called when the connection is finished, i.e. after the call to return.
//Called first
[[LRResty client] get:connectURL
//Called second
return workingDatabases;
//Called later when the connection is finished
SBJsonParser *jsonParser = [SBJsonParser new];
NSDictionary *jsonResponse = [jsonParser objectWithString:[r asString]];
NSDictionary *permittedBases= [jsonResponse objectForKey:#"permittedBases"];
Database *database = [[Database alloc] init];
for (id key in permittedBases) {
/* ... */
[workingDatabases addObject:database];
}
Edit
Ajeet has a valid point too, ensure your array is initialized.
I used the LRResty framework for accessing a RESTful webservice. It was an odd thing anyways, so I switched to a way more rich-featured framework, called "ASIHTTP". I would recommend that to anyone who wants to use RESTful services (and more) on iOS

iOS Singleton Variables Not Keeping Their Values

So I'm still kind of new to Objective-C, and this was my first app that I'm now updating. The idea is this: The whole app is basically various lists of stuff. It asks the API for 15 posts, shows those with a Load More button. Click Load More, it loads 15 more, etc. The API that it loads these from has a token system with a timeout built in. Too long between requests, and you have to get a new token. So I want to have a singleton to use anywhere in my app so I can just do [APIMachine getToken] and behind the scenes, it checks if the time since the last request was too long (or this is the first request), if so, gets a new token, otherwise returns the one we already have. I'm following the singleton pattern I've found in so many places, but every time the Load More button uses [APIMachine getToken]it gets either nothing or something completely random. I had it print this stuff in the logs, and one time I even got a UITableViewCell as my token. Looks like variables are being overwritten somehow. But I really can't figure it out.
So here it is:
static PoorAPI2 *_instance;
#implementation PoorAPI2
#synthesize apiToken, timeOpened, tokenTTL;
+ (PoorAPI2*)sharedAPI
{
#synchronized(self) {
if (_instance == nil) {
_instance = [[super allocWithZone:NULL] init];
}
}
return _instance;
}
-(NSString *)API_open{
//boring code to get api token redacted
if ([doneness isEqualToString:#"success"]) {
NSDictionary *data = [json objectForKey:#"data"];
apiToken = [data objectForKey:#"api_token"];
tokenTTL = [data objectForKey:#"ttl"];
timeOpened = [NSDate date];
}else{
NSLog(#"FFFFFFFUUUUUUUUUUUU this error should be handled better.");
}
return apiToken;
}
-(BOOL)isConnectionOpen{
return ([timeOpened timeIntervalSinceNow] > tokenTTL);
}
-(NSString *)getToken{
if([self isConnectionOpen]){
return apiToken;
}else{
return [_instance API_open];
}
}
-(id)init{
if(self = [super init]){
apiToken = [[NSString alloc] initWithString:#""];
timeOpened = [[NSDate alloc] initWithTimeIntervalSinceNow:0];
tokenTTL = 0;
}
return self;
}
+ (id)allocWithZone:(NSZone *)zone
{
return [[self sharedAPI]retain];
}
- (id)copyWithZone:(NSZone *)zone
{
return self;
}
- (id)retain
{
return self;
}
- (unsigned)retainCount
{
return NSUIntegerMax; //denotes an object that cannot be released
}
- (void)release
{
//do nothing
}
- (id)autorelease
{
return self;
}
#end
I can only hope I'm doing something seriously foolish and this will be a hilarious point-and-laugh-at-that-guy thread. Then at least my app will work.
In API_open, you store three objects in instance variables, but they're not objects you own, so they'll probably be gone by the time you need them and replaced by something unpredictable. You need to retain them or use proper setters.
You problem is:
static PoorAPI2 *_instance;
C, and by inheritance Objective-C, do not initialize variables. Just change to:
static PoorAPI2 *_instance = nil;
Also I am of the school that adding extra code to try to prevent the singleton from being used as a single is a total waste of time, and only give you more code with more possibilities for bugs.
So if I was you then I would remove every method from +[PoorApi2 allocWithZone:] and down. Objective-C is a dynamic language and if a client wanted to instantiate a second instance of your singleton then it would be able to do so despite all your wasted extra lines of code. At the most I would add a log like this:
-(id)init{
if (_instance) NSLog(#"WARNING: PoorAPI2 already has a shared instance.");
if(self = [super init]){
apiToken = [[NSString alloc] initWithString:#""];
timeOpened = [[NSDate alloc] initWithTimeIntervalSinceNow:0];
tokenTTL = 0;
}
return self;
}
Creating a second instance of a singleton is a programming error and should be caught in development. Not a problem you should add extra lines of code to hide.