I'm trying to set value after mapping data using Reactive Cocoa. Here is my code:
- (RACSignal *)getNews{
RACSignal *sign = [self.manager rac_GET:MAIN_URL_NEWS parameters:self.parameters];
return sign;
}
Then i do:
#weakify(self);
[[[self getNews] map:^id(NSDictionary *response) {
// Mapping
NSArray * array = [response valueForKey:#"data"];
NSMutableArray *localArray = [NSMutableArray new];
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NewsParseer *news = [[NewsParseer alloc]init];
[news mts_setValuesForKeysWithDictionary:[[response valueForKey:#"data"]objectAtIndex:idx]];
NSLog(#"Object memory %#", news);
NSLog(#"Newnew %#", news.title);
[localArray addObject:news];
}];
return localArray;
}] subscribeNext:^(NSArray* x) {
NSLog(#"Data is %#", x);
}];
NSLog(#"data array %#", self.dataArray);
#"Data is %#" is output correct value, therefore, in subscribeNext we got correct value.
But, #"data array" output is
data array (
)
Why is that happening?
Of course i did initialize array:
self.dataArray = [NSMutableArray new];
Even provide getter method:
-(NSMutableArray*)dataArray{
if(!_dataArray){
_dataArray = [[NSMutableArray alloc] init];
}
return _dataArray;
}
It doesn't look like you're ever setting self.dataArray to equal to result of x in your subscribeNext block.
Try:
self.dataArray = x; after your log statement in subscribeNext.
Also, the signal is asynchronous - it's highly unlikely that your logging of self.dataArray will occur after your signal completes. It will probably happen prior to the completion of the signals work.
Related
Now I have a textview ,and I receive content message , I will do
NSString *speakText=[[NSString alloc]initWithFormat:#"%#\n%#\n%#",fromWho,content,_cheatView.text];
to show it .
But now I want to create a array to save all message ,
In a array like sample
> [0] (first message 3pair key/value)
>[0]
>[1]
>[2]
>[1] (second message 3pair key/value)
>[2]
>[x]
>[y]
I tried use
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage: (id)message;
{
NSString *toMessageJson = [[NSString alloc]initWithString:message];
[toMessageJson dataUsingEncoding:NSUTF8StringEncoding];
NSData *toMessageData =[toMessageJson dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
NSDictionary *messageDic = [NSJSONSerialization JSONObjectWithData:toMessageData options:NSJSONReadingAllowFragments error:&error];
NSMutableArray *logDic =[NSMutableArray alloc];
[messageDic enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL * stop) {
if ([key isEqualToString:#"content"]) {
[logDic addObject:messageDic];
}
}];
But the logArray always only have 1 Object .
I think that logDic is confusing. At first I thought logDic is a dictionary, proper naming should also take place.
Main issue I can see is that you are always creating logDic every time you receive a message.
So if you make logDic as a property or an iVar.
example:
//WhatEverClassname
#interface WhatEverClassname(){
NSMutableArray *logDic;
}
#end
#implementation WhatEverClassname
-(id)init{
if(!(self = [super init])){
return nil;
}
logDic = [NSMutableArray new];
return self;
}
#end
then in your didReceived method you don't need to initialise it anymore.
This enumeration isn't actually needed.
[message enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL * stop) ]
it should be like:
if(message[#"content"] != nil)
[logDic addObject:message[#"content"]]; // no point in looping if you are only expecting a specific key.
I've got this parsing operation that currently works fine, but I've started to notice that it is freezing up my UI slightly so I'm trying to refactor and get this done asynchronously. I'm having some issues however and was hoping someone could point me in the right direction. Here's my current (synchronous) code:
- (NSArray *)eventsFromJSON:(NSString *)objectNotation
{
NSParameterAssert(objectNotation != nil);
NSData *unicodeNotation = [objectNotation dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
NSDictionary *eventsData = [NSJSONSerialization JSONObjectWithData:unicodeNotation options:0 error:&error];
if (eventsData == nil) {
//invalid JSON
return nil;
}
NSArray *events = [eventsData valueForKeyPath:#"resultsPage.results"];
if (events == nil) {
//parsing error
return nil;
}
NSLog(#"events looks like %#", events);
NSMutableArray *formattedEvents = [NSMutableArray arrayWithCapacity:events.count];
for (id object in [events valueForKeyPath:#"event"]) {
Event *event = [[Event alloc] init];
event.latitude = [object valueForKeyPath:#"location.lat"];
event.longitude = [object valueForKeyPath:#"location.lng"];
event.title = [object valueForKeyPath:#"displayName"];
event.venue = [object valueForKeyPath:#"venue.displayName"];
event.ticketsLink = [NSURL URLWithString:[object valueForKeyPath:#"uri"]];
event.artist = [object valueForKeyPath:#"performance.artist.displayName"];
event.date = [object valueForKeyPath:#"start.datetime"];
[formattedEvents addObject:event];
}
return [NSArray arrayWithArray:formattedEvents];
}
I've been looking into NSOperationQueue's and I'm struggling to find a solution as I'd like to return an array from this method and operation queues are not meant to have return values. I'm also looking at GCD and i've got somethinbg like this:
- (NSArray *)eventsFromJSON:(NSString *)objectNotation
{
dispatch_queue_t backgroundQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
__block NSMutableArray *mutable = [NSMutableArray array];
dispatch_async(backgroundQueue, ^{
NSParameterAssert(objectNotation != nil);
NSData *unicodeNotation = [objectNotation dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
NSDictionary *eventsData = [NSJSONSerialization JSONObjectWithData:unicodeNotation options:0 error:&error];
if (eventsData == nil) {
//invalid JSON
mutable = nil;
}
NSArray *events = [eventsData valueForKeyPath:#"resultsPage.results"];
if (events == nil) {
//parsing error
mutable = nil;
}
NSLog(#"events looks like %#", events);
NSMutableArray *formattedEvents = [NSMutableArray arrayWithCapacity:events.count];
for (id object in [events valueForKeyPath:#"event"]) {
Event *event = [[Event alloc] init];
event.latitude = [object valueForKeyPath:#"location.lat"];
event.longitude = [object valueForKeyPath:#"location.lng"];
event.title = [object valueForKeyPath:#"displayName"];
event.venue = [object valueForKeyPath:#"venue.displayName"];
event.ticketsLink = [NSURL URLWithString:[object valueForKeyPath:#"uri"]];
event.artist = [object valueForKeyPath:#"performance.artist.displayName"];
event.date = [object valueForKeyPath:#"start.datetime"];
[formattedEvents addObject:event];
}
mutable = [NSMutableArray arrayWithArray:formattedEvents];
});
return [mutable copy];
}
For some reason, this seems to be returning the object before the parsing has finished however, as I'm gettting no data out of that mutable object, but I'm noticing that the parsing is indeed occurring (i'm logging out the results). can anyone give me an idea about how to get this asynch stuff going?
Thanks!!
You primary problem is that by their very nature asynchronous operations can't synchronously return a result. Instead of returning an array from -eventsFromJSON:, you should provide a way for the caller to receive a callback when the results are finished. There are two common approaches to this in Cocoa.
You can create a delegate with an associated delegate protocol including a method like -parser:(Parser *)parser didFinishParsingEvents:(NSArray *)events, then have your parser call this method on its delegate when parsing is finished.
Another solution is to allow the caller to provide a completion block to be executed when parsing is complete. So, you might do something like this:
- (void)eventsFromJSON:(NSString *)objectNotation completionHandler:(void (^)(NSArray *events))completionHandler)
{
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(backgroundQueue, ^{
NSMutableArray *mutable = [NSMutableArray array];
NSParameterAssert(objectNotation != nil);
NSData *unicodeNotation = [objectNotation dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
// Snip...
mutable = [NSMutableArray arrayWithArray:formattedEvents];
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler([mutable copy]);
});
});
}
Then you can call this code some thing like this:
- (void)parseJSONAndUpdateUI // Or whatever you're doing
{
NSString *jsonString = ...;
Parser *parser = [[Parser alloc] init];
[parser parseEventsFromJSON:jsonString completionHandler:^(NSArray *events){
// Update UI with parsed events here
}];
}
I like the second, block-based approach better. It makes for less code in most cases. The code also reads closer to the synchronous approach where the method just returns an array, since the code that uses the resultant array simply follows the method call (albeit indented since it's in the completion block's scope).
I would recommend using a completion block that you pass into your parse method. This way you don't have to return a value, but can do what you need to with the information once it is parsed. You just have to make sure you use GCD again to put the completion block on the main thread.
You could also post a notification on the main thread once the operation is complete that contains the array in userInfo.
Returning a value will not work however for asynchronous operations.
You are getting a returned object before the parsing has finished because your return [mutable copy] is outside of the dispatch_async block. Since dispatch_async functions asynchronously, it will return immediately, and then calls your return [mutable copy] (which is empty because it's not done parsing).
I'm a developer from Python world used to using exceptions. I found in many places that using exceptions is not so wise here, and did my best to convert to NSErrors when needed. but then I encounter this:
NSMutableArray *results;
for (NSDictionary *dict in dicts)
{
// Memory management code omitted
SomeModel *model = [[SomeModel alloc] init];
model.attr1 = [[dict objectForKey:#"key1"] integerValue];
model.attr2 = [[dict objectForKey:#"key2"] integerValue];
model.attr3 = [[dict objectForKey:#"key3"] integerValue];
model.attr4 = [[dict objectForKey:#"key4"] integerValue];
[results addObject:model];
}
with some of the objects in dict containing NSNull, which would result an "unrecognized selector" exception. In that case, I want to drop that datum completely. My first instinct is to wrap the whole content of the for block into a #try-#catch block:
NSMutableArray *results;
for (NSDictionary *dict in dicts)
{
#try
{
SomeModel *model = [[SomeModel alloc] init];
model.attr1 = [[dict objectForKey:#"key1"] integerValue];
model.attr2 = [[dict objectForKey:#"key2"] integerValue];
model.attr3 = [[dict objectForKey:#"key3"] integerValue];
model.attr4 = [[dict objectForKey:#"key4"] integerValue];
[results addObject:model];
}
#catch(NSException *exception)
{
// Do something
}
}
But is this a good approach? I can't come up with a solution without repeating checks on each variable, which is really ugly IMO. Hopefully there are alternatives to this that haven't occur to me. Thanks in advance.
The proper Objective-C way to do this would be:
for (NSDictionary *dict in dicts)
{
if (! [dict isKindOfClass:[NSDictionary class]])
continue;
// ...
}
Testing if a receiver can respond to a message before sending it is a typical pattern in Objective-C.
Also, take note that exceptions in Objective-C are always a programmer error and are not used for normal execution flow.
Many people use a category on NSDictionary for these cases:
- (id)safeObjectForKey:(id)aKey
{
id obj = [self objectForKey:aKey];
if ([obj isKindOfClass:[NSNull class]])
{
return nil;
}
return obj;
}
You still need to make sure, that your dict is an actual dictionary instance.
In the end I decided to solve the problem using KVC. Something like this:
- (id)initWithPropertyDictionary:(NSDictionary *)dict
lookUpTable:(NSDictionary *)keyToProperty
{
self = [self init];
for (NSString *key in dict)
{
NSString *propertyName;
if ([keyToProperty objectForKey:key])
propertyName = [keyToProperty objectForKey:key];
else
propertyName = key;
if ([[dict objectForKey:key] isKindOfClass:[NSNull class]])
{
[self release];
return nil;
}
else
{
[self setValue:[dict objectForKey:key] forKey:propertyName];
}
}
}
The setback of this resolution is that I'll have to use NSNumber for my properties, but for JSON data there is really no distinction between floating numbers and integers, so this is fine.
And if you really want primitive types, you can couple this method with custom setters that converts those NSNumbers into appropriate types.
With this, all you need to do is check for nil before adding the object into the array. Much cleaner everywhere except the model class.
Thanks to jaydee3 for inspiring me to focus on changing the model class.
hi at all ,I've this code :
+(NSArray *)splatterUrls
{
NSString *jsonString = [ ApiMethod jsonOfStores];
NSDictionary *results =[jsonString objectFromJSONString];
NSArray *movieArray = [results objectForKey:#"Seasons"];
//int i=0;
// Search for year to match
for (NSDictionary *movie in movieArray)
{
NSNumber *idSplatterMovie = [movie objectForKey:#"Id"];
// NSLog(#" %#", idSplatterMovie );
NSArray *try = [movie objectForKey:#"Episodes"];
// NSLog(#"%#", try);
for (NSDictionary *op in try)
{
if([idSplatterMovie integerValue] == 46)
{
//i++;
NSArray *movieArrayString = [op objectForKey:#"Url"];
// NSLog(#" %#", movieArrayString);
return movieArrayString;
}
}
}
}
I want to return movieArrayString with all his objects and how many object contains in it. I think that I should use this method : + (id)arrayWithObjects:(const id *)objects count:(NSUInteger)count. It's possible? If yes, can you tell me how can use it?
Thank you so much!
by the way , i have to call splatterUrls method and implement in home.m that it is :
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray *urlSplatter= [GetSplatterUrlsMovie splatterUrls];
NSLog(#" %#", urlSplatter);
}
Looks good as it is to me.
Do this to return your movies array, array will be equal to your movies array:
NSArray *array = [self splatterUrls];
Then to get the count/number of objects in your array do this, i is equal to the number of objects in the array:
int i = [array count];
What is the problem ??
You return a NSarray ... call the method count on your NSarray object!
I am relatively new to Objective-C and now I have a problem in my iPhone app that I don't fully understand.
I try to use a NSMutableDictionary, this does not seem to work as i expect for some reason. When I run the debugger and do po numberToCallerMap to see the dictionary, I get an exception. I have read the documentation for NSMutableDictionary on how to initialize it, but I can not see what I am doing wrong. Help and advice are appreciated. The variable causing me problem is numberToCallerMap, here is the relevant function:
- (void)setData:(NSString*)value{
[list release];
list = [[NSMutableArray alloc] init];
SBJSON *json = [[[SBJSON alloc] init] autorelease];
NSMutableDictionary* numberToCallerMap;
CallerInfo* caller;
NSDictionary* callerInfo;
#try {
NSArray *array = (NSArray*)[json objectWithString:value];
// reading all the items in the array one by one
numberToCallerMap = [NSMutableDictionary dictionary];
for (id *item in array) {
// if the item is NSDictionary (in this case ... different json file will probably have a different class)
NSDictionary *dict2 = (NSDictionary *) item;
CallInfo *data = [CallInfo alloc];
[data initFromDictionary:dict2];
callerInfo = (NSDictionary*)[dict2 valueForKey:#"caller"] ;
//Here, we want the phonenumber to be part of the CallerInfo object instead.
// It is sent from the server as part of the Call-object
NSString* number = (NSString*)[dict2 valueForKey:#"phoneNumber"];
[callerInfo setValue:number forKey:#"phoneNumber"];
caller = (CallerInfo*)[numberToCallerMap valueForKey:number];
if(caller == nil || [caller isKindOfClass:[NSNull class]]){
caller = [CallerInfo alloc];
[caller initFromDictionary:callerInfo];
[numberToCallerMap setValue:caller forKey:number];
[list insertObject:caller atIndex:0];
}
[caller addRecentCall:data];
}
}
#catch (NSException * e) {
[list release];
list = [[NSMutableArray alloc] init];
}
#finally {
[numberToCallerMap release];
}
}
This is probably not the only problem, but you are not alloc-ing your numberToCallerMap dictionary, you are getting it from a convenience class method -- [NSMutableDictionary dictionary] -- that returns it autoreleased. So you should not call release on it yourself.