Just started programming on objective-c and now i have issue with which can't deal by myself. I'm receiving data from asynchronous request and try to delver it to singleton, but it's not changed.
This is where i'm trying to store my data
Data.h
#import <Foundation/Foundation.h>
#interface Data : NSObject
#property (nonatomic, strong) NSDictionary *products;
-(void)setProducts:(NSDictionary *)value;
#end
Data.m
#import "Data.h"
#implementation Data
+(Data *)sharedInstance
{
static Data *_sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[Data alloc] init];
});
return _sharedInstance;
}
- (id)init {
self = [super init];
if ( self )
{
_products = [[NSDictionary alloc] init];
}
return self;
}
#end
This is the class, where i'm receiving data from server:
ConnectionService.m
- (void)getProductsWithCompletion:(void (^)(NSDictionary *products))completion
{
NSString *urlString = [NSString stringWithFormat:#"serverurl", [[AppDelegate instance]getUrl]];
NSURL *url = [NSURL URLWithString:urlString];
NSURLSessionDataTask *getData = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
NSString *rawJson = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSDictionary *value = [rawJson JSONValue];
completion(value);
}];
[getData resume];
}
This is the class where i'm calling request and try to deliver it to singleton:
viewController.m
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:YES];
[[ConnectionService instance] getProductsWithCompletion:^(NSDictionary *products) {
[Data sharedInstance].products = products;
NSLog(#"products: %#", [[Data sharedInstance] products]);//all is working, products contains data
}];
// checking received data
NSDictionary *tmp = [[Data sharedInstance] products];
NSLog(#"tmp: %#", tmp); //now it's null
}
The issue is the fact that the request is asynchronous and things aren't happening in the order you expect:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:YES];
[[ConnectionService instance] getProductsWithCompletion:^(NSDictionary *products) {
// (2)
[Data sharedInstance].products = products;
NSLog(#"products: %#", [[Data sharedInstance]products]);//all is working, products contains data
}];
// (1)
NSDictionary *tmp = [[Data sharedInstance]products];
NSLog(#"tmp: %#", tmp); //now it's null
}
In the code you posted, (1) will happen before (2). That's because (2) is part of the completion block and is set to run once the network request has completed and all the data has been parsed and is ready to use. While that asynchronous request is prepared and run in a background thread, the main thread ((1)) continues and executes before the request has taken place.
To resolve the issue, move your logging into the completion routine, or simply remove (1).
Another way is to use protocol, to notify your completion block is finished.So that you can simply do:
[[ConnectionService instance] getProductsWithCompletion:^(NSDictionary *products) {
if(self.delegate){
[self.delegate myNotifyMethod:products];
}
}];
and your protocol method:
-(void)myNotifyMethod:(NSDictionary *)items{
[Data sharedInstance].products = products;
NSLog(#"products: %#", [[Data sharedInstance]products]);
}
You can declare the protocol as:
#protocol MyProtocol <NSObject>
- (void)myNotifyMethod: (NSDictionary *)items;
#end
and set the delegate property as:
#property (nonatomic, weak) id<MyProtocol> delegate;
Related
I am working on using NSURLSession and JSON serialization to fetch content from my site. The async calls and getting the JSON data work perfectly. My issue is, when it comes to displaying the data in the TableviewController, I put an NSLog statement to see if there is data and there is, but that cell.textlable.text never updates. I'm guessing the issue is the threads but I can't figure it out. Can you help?
#interface MainTableViewController :
UITableViewController<LokalModelProtocol>
#property (strong,nonatomic) NSMutableArray* arr;
#end
#implementation MainTableViewController
#synthesize arr;
- (void)viewDidLoad {
[super viewDidLoad];
arr = [[NSMutableArray alloc]init];
LokalModel *lokal = [[LokalModel alloc]init];
lokal.delegate=self;
[lokal downloadItems];
}
-(void)itemsDownloaded:(NSMutableArray *)items
{
arr=items;
//NSLog(#"%#", items);
[self.tableView reloadData];
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
#warning Incomplete implementation, return the number of sections
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection: (NSInteger)section {
#warning Incomplete implementation, return the number of rows
// return 1;
return [arr count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"mainCell" forIndexPath:indexPath];
PostModel *post = [[PostModel alloc]init];
post =[arr objectAtIndex:indexPath.row];
NSLog(#"%#", post.postTitle); ////this outputs the correct strings///////
cell.textLabel.text =[NSString stringWithFormat:#"%#", post.postTitle];
cell.detailTextLabel.text = post.postTitle;///neither of these do//////
return cell;
}
#end
#protocol LokalModelProtocol <NSObject,NSURLSessionDelegate>
+(void)itemsDownloaded:(NSMutableArray*)items;
#end
#interface LokalModel : NSObject
-(void)downloadItems;
#property (strong, nonatomic) NSMutableData* thedata;
#property (strong, nonatomic) NSString* urlString;
#property (strong, nonatomic) NSURL* theUrl;
#property (strong,nonatomic) id<LokalModelProtocol>delegate;
+(void)parseJson:(NSData*)data;
#end
id<LokalModelProtocol>delegate;
#implementation LokalModel;
#synthesize thedata,urlString,theUrl,delegate;
-(void)downloadItems{
NSURL *theUrl = nil;
static NSString* urlString = #"https://balalatet.com/wp-json/wp/v2/posts";
theUrl=[NSURL URLWithString:urlString];
NSURLSession *currentSession= [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionDataTask *task = [currentSession dataTaskWithURL:theUrl completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error){
[NSException raise:#"error" format:#"%#",error.localizedDescription];
NSLog(#"error1");
}
else{
NSLog(#"success");
[LokalModel parseJson:data];
}
}];
[task resume];
}
+(void)parseJson:(NSData*)data{
NSArray *jsonResults = [[NSArray alloc]init];
NSError *jsonerror;
jsonResults =[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&jsonerror];
if (jsonerror)
[NSException raise:#"json error" format:#"%#",jsonerror.localizedDescription];
NSMutableArray *posts = [[NSMutableArray alloc] init];
NSMutableDictionary *jsonElenent =[NSMutableDictionary dictionary];
for (NSMutableDictionary *d in jsonResults)
{
jsonElenent=d;
PostModel *thePost=[[PostModel alloc]init];
thePost.postId= jsonElenent[#"id"];
thePost.postDate= jsonElenent[#"date"];
thePost.postDategmt= jsonElenent[#"date_gmt"];
thePost.postGuid= jsonElenent[#"guid"];
thePost.postSlug= jsonElenent[#"slug"];
thePost.postStatus= jsonElenent[#"status"];
thePost.postSticky= jsonElenent[#"sticky"];
thePost.postPingStatus= jsonElenent[#"ping_status"];
thePost.postType= jsonElenent[#"type"];
thePost.postCommentStatus= jsonElenent[#"comment_status"];
thePost.postTags= jsonElenent[#"tags"];
thePost.postTitle= jsonElenent[#"title"];
thePost.postTemplate= jsonElenent[#"template"];
thePost.postLink= jsonElenent[#"link"];
thePost.postMeta= jsonElenent[#"meta"];
thePost.postModified= jsonElenent[#"modified"];
thePost.postModifiedgmt= jsonElenent[#"modified_gmt"];
thePost.postFeaturedMedia= jsonElenent[#"featured_media"];
thePost.postFormat= jsonElenent[#"format"];
thePost.postLinks= jsonElenent[#"links"];
thePost.postAuthor= jsonElenent[#"author"];
thePost.postContent= jsonElenent[#"content"];
thePost.postCategory= jsonElenent[#"category"];
thePost.postExcerpt= jsonElenent[#"excerpt"];
NSLog(#"%#", thePost.postTitle);
[posts addObject:thePost];
}
dispatch_async(dispatch_get_main_queue(), ^{
[delegate itemsDownloaded:posts];
});
}
#end
Update
my apologies as part of my debugging info is incorrect. the nslog output did not come from the cellForRowAtIndexPath method. in fact the arr array remains empty because the
(void)itemsDownloaded:(NSMutableArray *)items
is never called. im sure i setup the protocol correctly. any thoughts on why the MainTableViewControllerClass cant get the data?
update
so i realized that i forgot to remove the line
id<LokalModelProtocol>delegate;
that i put right before the #implementation in LokalModel. but now doing so causes an error "unrecognized selector sent to instance" at the line
[delegate itemsDownloaded:posts];
I aslo tried
[self.delegate itemsDownloaded:posts];
but it throws the same exception.
Solved
My protocol method had to be an instance method, and i had it set as a class method.
Before return your cell add try to add this code in cellForRowIndexPath
[cell layoutIfneeded];
I believe you have to add a registerNib:forCellReuseIdentifier: or registerClass:forCellReuseIdentifier: prior to using dequeueReusableCellWithIdentifier:forIndexPath: (in viewDidLoad for example)
From the documentation: https://developer.apple.com/documentation/uikit/uitableview/1614878-dequeuereusablecellwithidentifie?language=objc
Important
You must register a class or nib file using the registerNib:forCellReuseIdentifier: or registerClass:forCellReuseIdentifier: method before calling this method.
I have always been nervous when it comes to blocks and GCD because my mind tells me that it looks very complex!
I am getting a crash inside a block which ideally looks alright to me:
#pragma mark -
-(void)fetchOrdersListWithInfoDict:(NSDictionary*)infoDict completionBlock:(CompletionBlock)completionBlock errorBlock:(ErrorBlock)errorBlock
{
__weak VTVehicleServiceNetworkManager *weakSelf = self;
TaskBlock fetchOrdersListTaskBlock = ^()
{
__block __strong HLOrdersDataProvider *ordersDataProvider = nil;
NSBlockOperation *fetchOrdersOperation = [NSBlockOperation blockOperationWithBlock:[^{
ordersDataProvider = [[HLOrdersDataProvider alloc] init];
[ordersDataProvider performFetchOrdersListWithInfoDict:infoDict
completionBlock:completionBlock
errorBlock:errorBlock];
} copy]];
[weakSelf.dataOperationQueue addOperation:fetchOrdersOperation];
};
[self fetchDataWithTaskBlock:[fetchOrdersListTaskBlock copy]
errorBlock:^(NSError *error) {
errorBlock(error);
}];
}
I was able to trace out the zombie object but I am not able to figure out why is this object turning out to be a zombie. Here is the snapshot from profile:
I have gone through the following guides (1, 2) to see if I can find out what I am doing wrong but I was no where near to find out what is going wrong.
Any help and reference text to what I am doing wrong will help.
Edit:
I have tried what #Jerimy has suggested and in fact my code which I have posted earlier was exactly the same as required: Declaring and initializing ordersDataProvider inside the block operation itself. But since it was crashing at the same point I tried to declare it outside the block just to see if it addresses the crash.
Below is the new code I tested:
#pragma mark -
-(void)fetchOrdersListWithInfoDict:(NSDictionary*)infoDict completionBlock:(CompletionBlock)completionBlock errorBlock:(ErrorBlock)errorBlock
{
__weak VTVehicleServiceNetworkManager *weakSelf = self;
completionBlock = [completionBlock copy];
errorBlock = [errorBlock copy];
TaskBlock fetchOrdersListTaskBlock = ^()
{
NSBlockOperation *fetchOrdersOperation = [NSBlockOperation blockOperationWithBlock:^{
HLOrdersDataProvider *ordersDataProvider = [[HLOrdersDataProvider alloc] init];
[ordersDataProvider performFetchOrdersListWithInfoDict:infoDict
completionBlock:completionBlock
errorBlock:errorBlock];
}];
[weakSelf.dataOperationQueue addOperation:fetchOrdersOperation];
};
[self fetchDataWithTaskBlock:[fetchOrdersListTaskBlock copy]
errorBlock:^(NSError *error) {
errorBlock(error);
}];
}
The crash from Profile:
There is not much from the stack trace as well, SDMHTTPRequest is a library and am very sure there is nothing wrong there, and the HLOrdersDataProvider is the zombie object which I was able to trace out in Instruments app:
EDIT 2
Adding the interface and implementation of HLOrdersDataProvider for more details:
#interface HLOrdersDataProvider : HLDataProvider
-(void)performFetchOrdersListWithInfoDict:(NSDictionary*)infoDict completionBlock:(CompletionBlock)completionBlock errorBlock:(ErrorBlock)errorBlock;
#end
#implementation HLOrdersDataProvider
-(void)performFetchOrdersListWithInfoDict:(NSDictionary*)infoDict completionBlock:(CompletionBlock)completionBlock errorBlock:(ErrorBlock)errorBlock
{
// Using SDMConnectivity
NSString *queryString = [infoDict valueForKey:#"$filter"];
NSString *appendStringForEndpoint = [kRowsetsKeyword stringByAppendingFormat:#"?%#", queryString];
[self fetchDataFromServerWithEndPointAppendString:appendStringForEndpoint
completionBlock:completionBlock
errorBlock:errorBlock];
}
#pragma mark - Service Agent related
-(NSString*)collectionName
{
return [NSString stringWithString:kRowsetsKeyword];
}
-(void)requestFinished:(SDMHttpRequest *)request
{
NSError *error = nil;
// Let's parse the response and send the results back to the caller
NSString *collectionName = [self collectionName];
NSData *responseData = [request responseData];
NSArray *entitiesArray = [self parseODataEntriesWithData:responseData
withCollectionName:collectionName
error:&error];
if (error)
[self triggerFailureBlockWithArgument:error];
else
[self triggerCompletionBlockWithArgument:entitiesArray];
}
#end
Also, HLOrdersDataProvider is inherited from HLDataProvider so below is the interface and implementation of this class too:
#import <Foundation/Foundation.h>
//#import "SDMHttpRequestDelegate.h"
#import "SDMRequestBuilder.h"
#import "SDMHttpRequest.h"
#import "SDMParser.h"
#import "HLConstant.h"
#import "HLConnectionData.h"
#interface HLDataProvider : NSObject <SDMHttpRequestDelegate>
#property (copy, atomic) CompletionBlock completionBlock;
#property (copy , atomic) ErrorBlock errorBlock;
#property (copy, atomic) CompletionBlockWithDataFetchStatus completionBlockWithDataFetchStatus;
+ (id)sharedInstance;
- (NSMutableArray*)parseODataEntriesWithData:(NSData*)data withCollectionName:(NSString*)collectionName;
- (NSMutableArray*)parseODataEntriesWithData:(NSData*)data withCollectionName:(NSString*)collectionName error:(NSError**)outError;
- (NSMutableArray*)parseJSONEntriesWithData:(NSData*)data;
-(NSArray*)fetchEntriesFromDatabaseWithEntityName:(NSString*)entityName relationshipObjects:(NSMutableArray*)relationships;
-(void)updateDatabaseWithEntries:(NSMutableArray*)scanEntries;
-(void)updateDatabaseWithJSONEntries:(NSMutableArray*)scanEntries;
-(id)parsedOdataResultFromEntries:(NSMutableArray*)entries;
-(void)fetchDataFromServerWithEndPointAppendString:(NSString*)appendStr completionBlock:(CompletionBlock)inCompletionBlock errorBlock:(ErrorBlock)inErrorBlock;
-(NSString*)collectionName;
-(void)triggerCompletionBlockWithArgument:(id)inArg;
-(void)triggerFailureBlockWithArgument:(NSError*)inArg;
#end
#implementation HLDataProvider
+ (id)sharedInstance
{
//Subclassess will override this method
return nil;
}
-(NSMutableArray*)parseODataEntriesWithData:(NSData*)data withCollectionName:(NSString*)collectionName
{
return [self parseODataEntriesWithData:data
withCollectionName:collectionName
error:NULL];
}
-(NSMutableArray*)parseODataEntriesWithData:(NSData*)data withCollectionName:(NSString*)collectionName error:(NSError**)outError
{
NSMutableArray *entriesArray = nil;
#try {
entriesArray = sdmParseODataEntriesXML(data,
[[[HLConnectionData metaDataDocument] getCollectionByName:collectionName] getEntitySchema],
[HLConnectionData serviceDocument]);
}
#catch (NSException *exception) {
NSLog(#"Got exception: %#", exception);
if (outError)
{
*outError = [NSError errorWithDomain:#"Vehicle Service"
code:-1001
userInfo:[NSDictionary dictionaryWithObject:exception forKey:NSLocalizedDescriptionKey]];
}
}
#finally {
}
return entriesArray;
}
- (NSMutableArray*)parseJSONEntriesWithData:(NSData*)data
{
NSError *error = nil;
id object = [NSJSONSerialization
JSONObjectWithData:data
options:0
error:&error];
NSMutableArray *resultArray = nil;
if(error) { /* JSON was malformed, act appropriately here */ }
if([object isKindOfClass:[NSDictionary class]])
{
resultArray = [NSMutableArray arrayWithObject:object];
}
else if ([object isKindOfClass:[NSArray class]])
{
resultArray = [NSMutableArray arrayWithArray:object];
}
return resultArray;
}
#pragma mark -
#pragma mark - Data Fetch - Server - SDMConnectivity
-(void)fetchDataFromServerWithEndPointAppendString:(NSString*)appendStr completionBlock:(CompletionBlock)inCompletionBlock errorBlock:(ErrorBlock)inErrorBlock;
{
self.errorBlock = inErrorBlock;
self.completionBlock = inCompletionBlock;
id<SDMRequesting> request = nil;
//NSString *clientStr = #"&sap-client=320&sap-language=EN";
NSString *urlStr =[NSString stringWithFormat:#"%#/%#",[HLConnectionData applicationEndPoint], appendStr];
urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
[SDMRequestBuilder setRequestType:HTTPRequestType];
request=[SDMRequestBuilder requestWithURL:[NSURL URLWithString:urlStr]];
[request setUsername:kUserName];
/*Set Password in SDMRequesting object*/
[request setPassword:kPassword];
[request setRequestMethod:#"GET"];
[request setTimeOutSeconds:kTimeoutInterval];
/*set the Delegate. This class must adhere to SDMHttpRequestDelegate to get the callback*/
[request setDelegate:self];
/*Call startAsynchronous API to request object to retreive Data asynchrnously in the call backs */
[request startSynchronous];
}
-(void)updateDatabaseWithEntries:(NSMutableArray*)scanEntries
{
//Subclasses will override this
}
-(void)updateDatabaseWithJSONEntries:(NSMutableArray*)scanEntries
{
//Subclasses will override this
}
-(id)parsedOdataResultFromEntries:(NSMutableArray*)entries
{
//Subclasses will override this
return nil;
}
-(void)deleteExistingEntriesFromCoredata
{
//Subclasses will override this
}
-(NSArray*)fetchEntriesFromDatabaseWithEntityName:(NSString*)entityName relationshipObjects:(NSMutableArray*)array
{
//Subclasses will overide this method
return nil;
}
#pragma mark - SDMHTTPRequestDelegate methods
- (void)requestStarted:(SDMHttpRequest*) request
{
}
- (void)requestFinished:(SDMHttpRequest*) request
{
// For service doc and metadata we instantiate HLDataProvider, so we send this raw SDMHTTPRequest object as-is. For other service agents like HLOrdersDataProvider we send the parsed information, check the subclass' implementation of -requestFinished: method
[self triggerCompletionBlockWithArgument:request];
}
-(void)triggerCompletionBlockWithArgument:(id)inArg
{
self.completionBlock(inArg);
}
- (void)requestFailed:(SDMHttpRequest*) request
{
[self triggerFailureBlockWithArgument:request.error];
}
-(void)triggerFailureBlockWithArgument:(NSError*)inArg
{
self.errorBlock(inArg);
}
- (void)requestRedirected:(SDMHttpRequest*) request
{
}
#pragma mark - Service Agent related
-(NSString*)collectionName
{
// Should be overridden by the subclasses
return nil;
}
The referenced code is very convoluted (you're doing things in a very complicated way).
First off, you should not be creating the copy of your blocks in the caller. Make the copy in the callee if necessary (ie: to copy it to heap instead of using the stack-allocated block if you are going to call it after the stack has been popped). In almost all APIs using blocks, it is not the caller's responsibility to ensure that a block is on heap.
There is no reason for your ordersDataProvider variable to be declared in the scope of fetchOrdersListTaskBlock because it is only ever used inside of fetchOrdersOperation's block.
I don't immediately see the cause of your crash, but I suspect that simplifying your code will help reveal the problem. Perhaps the issue is in HLOrdersDataProvider's initializer.
Try something like:
-(void)fetchOrdersListWithInfoDict:(NSDictionary*)infoDict completionBlock:(CompletionBlock)completionBlock errorBlock:(ErrorBlock)errorBlock
{
completionBlock = [completionBlock copy];
errorBlock = [errorBlock copy];
__weak VTVehicleServiceNetworkManager *weakSelf = self;
TaskBlock fetchOrdersListTaskBlock = ^{
NSBlockOperation *fetchOrdersOperation = [NSBlockOperation blockOperationWithBlock:^{
HLOrdersDataProvider *ordersDataProvider = [[HLOrdersDataProvider alloc] init];
[ordersDataProvider performFetchOrdersListWithInfoDict:infoDict
completionBlock:completionBlock
errorBlock:errorBlock];
}];
[weakSelf.dataOperationQueue addOperation:fetchOrdersOperation];
};
[self fetchDataWithTaskBlock:fetchOrdersListTaskBlock
errorBlock:errorBlock];
}
Or better yet, re-design your class to work like this (I don't see a need for NSBlockOperation in your example):
-(void)fetchOrdersListWithInfoDict:(NSDictionary*)infoDict completionBlock:(CompletionBlock)completionBlock errorBlock:(ErrorBlock)errorBlock
{
completionBlock = [completionBlock copy];
errorBlock = [errorBlock copy];
TaskBlock fetchOrdersListTaskBlock = ^{
HLOrdersDataProvider *ordersDataProvider = [[HLOrdersDataProvider alloc] init];
[ordersDataProvider performFetchOrdersListWithInfoDict:infoDict
completionBlock:completionBlock
errorBlock:errorBlock];
};
[self fetchDataWithTaskBlock:fetchOrdersListTaskBlock
errorBlock:errorBlock];
}
I'm sorry to ask this question again, but I'm still stuck.
I have a city object trying to fetch weather from a weather fetcher object
#interface WeatherFetcher : NSObject {
}
#property (nonatomic, strong) NSMutableDictionary *weatherData;
- (void)fetchWeather:(NSString *)cityName;
- (void)handleNetworkErorr:(NSError *)error;
- (void)handleNetworkResponse:(NSData *)myData;
#end
This is were I assign the value to weatherData
#import "WeatherFetcher.h"
#implementation WeatherFetcher
- (void)fetchWeather:(NSString *)cityName
{
NSString *urlString = #"http://api.openweathermap.org/data/2.5/weather?q=";
urlString = [urlString stringByAppendingString:cityName];
urlString = [urlString stringByAppendingString:#",Aus"];
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError)
{
[self handleNetworkErorr:connectionError];
}
else
{
[self handleNetworkResponse:data];
}
}];
}
#pragma mark - Private Failure Methods
- (void)handleNetworkErorr:(NSError *)error
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Network Error" message:#"Please try again later" delegate:nil cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
[alert show];
}
#pragma mark - Private Success Methods
- (void)handleNetworkResponse:(NSData *)myData
{
//NSMutableDictionary *data = [NSMutableDictionary dictionary];
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
// now we'll parse our data using NSJSONSerialization
id myJSON = [NSJSONSerialization JSONObjectWithData:myData options:NSJSONReadingMutableContainers error:nil];
// typecast an array and list its contents
NSDictionary *jsonArray = (NSDictionary *)myJSON;
//NSLog([jsonArray description]);
// take a look at all elements in the array
for (id element in jsonArray) {
id key = [element description];
id innerArr = [jsonArray objectForKey:key];
NSDictionary *inner = (NSDictionary *)innerArr;
if ([inner conformsToProtocol:#protocol(NSFastEnumeration)]) {
for(id ele in inner) {
if ([ele conformsToProtocol:#protocol(NSFastEnumeration)]) {
NSDictionary *innerInner = (NSDictionary *)ele;
for(id eleEle in innerInner) {
id innerInnerKey = [eleEle description];
[data setObject:[[inner valueForKey:innerInnerKey] description] forKey:[eleEle description]];
}
}
else {
id innerKey = [ele description];
[data setObject:[[inner valueForKey:innerKey] description] forKey:[ele description]];
}
}
}
else {
[data setObject:[inner description] forKey:[element description]];
}
}
self.weatherData = data;
NSLog([self.weatherData description]) **//there is data**
}
#end
However every time I call this from by city object I get nothing back at all.
#import <Foundation/Foundation.h>
#import "WeatherFetcher.h"
#interface City : NSObject {
}
#property (nonatomic, strong) NSString *cityName;
#property (nonatomic, strong) NSString *stateName;
#property (nonatomic, strong) UIImage *cityPicture;
#property (nonatomic, strong) NSString *weather;
#property (nonatomic, strong) NSMutableDictionary *weatherData;
-(NSString *)getWeather;
#end
UI calls getWeather by a button press to get the string value to be displayed on screen
#implementation City {
}
-(NSString *)getWeather {
//return self.weather;
NSString *info = #"";
WeatherFetcher *weatherFetcher = [[WeatherFetcher alloc] init];
[weatherFetcher fetchWeather:self.cityName];
self.weatherData = [weatherFetcher weatherData];
for (id element in self.weatherData) {
info = [info stringByAppendingString:[element description]];
info = [info stringByAppendingString:#"-->"];
info = [info stringByAppendingString:[self.weatherData valueForKey:[element description]]];
info = [info stringByAppendingString:#"\n"];
}
return info;
}
#end
What am I doing wrong here?
getWeather method in the city class gets called when a button is pressed and I'm trying to display this string in a text area. I don't have much experience with Objective C and this is my first app other than Hello World app.
Thank you!
Your WeatherFetcher is asynchronous (sendAsynchronousRequest:) - it sets a task to obtain the data and then returns (usually) before that data has been obtained. So when you try to access the weatherData immediately after the call to fetchWeather: it is not there yet.
You need to redesign your model to handle asynchronicity - getWeather cannot be synchronous. For example you could make fetchWeather: take a completion block to invoke when the data is available and have getWeather pass in a suitable block.
I'm developing an iOS app using Core Data. And I have a Log entity with one-to-many relationships with Audio, Photo entities, and one-to-one relationship with Status entity. The log also has text, longitude, latitude properties. I can create the log, change its properties, add status entity, these changes would display right, until I quit the App. All the changes would disappear, and I was looking at the sqlite database, all these changes were never persisted in the database. In the database, the status object will just be created, but not linked to the log object.
But if I add an audio or photo object into the log.audioSet or log.photoSet, the changes I made to log, including the changes to text or status, will suddenly be saved into the database.
So it seems the changes are only maintained in the NSManagedObjectContext, until a related one_to_many entity is added and the [[LTLogStore sharedStore] saveChanges] will suddenly start to work.
I am using a singleton to manage the NSManagedObjectContext. Any ideas?
I would post some code if it's relevant. Thanks.
UPDATE: I'm not sure these code is enough. But basically everything works, and displays, it just doesn't save to the database. I'm using the mogenerator to set the text and latitude, but since everything is in the context. I am not sure this is the code you might need.
CODE:
#interface LTLogStore : NSObject{
}
+ (LTLogStore *)sharedStore;
- (void)removeItem:(Log *)p;
- (Log *)createItem;
- (BOOL)saveChanges;
#property(nonatomic, strong) NSFetchedResultsController *resultsController;
#property(nonatomic, strong) NSManagedObjectModel *model;
#property(nonatomic, strong) NSManagedObjectContext *context;
#end
#implementation LTLogStore
#synthesize resultsController;
#synthesize context, model;
+ (LTLogStore *)sharedStore
{
static LTLogStore *sharedStore = nil;
if(!sharedStore){
sharedStore = [[super allocWithZone:nil] init];
}
return sharedStore;
}
+ (id)allocWithZone:(NSZone *)zone
{
return [self sharedStore];
}
- (id)init
{
self = [super init];
if(self) {
model = [NSManagedObjectModel mergedModelFromBundles:nil];
NSPersistentStoreCoordinator *psc =
[[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
// Where does the SQLite file go?
NSString *path = [self itemArchivePath];
NSURL *storeURL = [NSURL fileURLWithPath:path];
NSError *error = nil;
if (![psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:nil
error:&error]) {
[NSException raise:#"Open failed"
format:#"Reason: %#", [error localizedDescription]];
}
// Create the managed object context
context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:psc];
// The managed object context can manage undo, but we don't need it
[context setUndoManager:nil];
}
return self;
}
- (NSFetchedResultsController *)resultsController {
if (resultsController !=nil) {
return resultsController;
}
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *e = [[model entitiesByName] objectForKey:#"Log"];
[request setEntity:e];
NSSortDescriptor *sd = [NSSortDescriptor
sortDescriptorWithKey:#"created_at"
ascending:NO];
[request setSortDescriptors:[NSArray arrayWithObject:sd]];
[request setReturnsObjectsAsFaults:NO];
NSFetchedResultsController *fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:request
managedObjectContext:context
sectionNameKeyPath:nil cacheName:#"Root"];
NSError *error;
BOOL success = [fetchedResultsController performFetch:&error];
if (!success) {
//handle the error
}
return fetchedResultsController;
}
- (NSString *)itemArchivePath
{
NSArray *documentDirectories =
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
// Get one and only document directory from that list
NSString *documentDirectory = [documentDirectories objectAtIndex:0];
NSString *storePath = [documentDirectory stringByAppendingPathComponent:#"store.data"];
return storePath;
}
- (BOOL)saveChanges
{
NSError *err = nil;
BOOL successful = [context save:&err];
NSLog(#"Saving changes to the database");
if (!successful) {
NSLog(#"Error saving: %#", [err localizedDescription]);
}
return successful;
}
- (void)removeItem:(Log *)l
{
[context deleteObject:l];
[self saveChanges];
}
- (Log *)createItem
{
Log *p = [NSEntityDescription insertNewObjectForEntityForName:#"Log"
inManagedObjectContext:context];
[self saveChanges];
return p;
}
#end
#interface Log : _Log {
}
//these two are some custom convenience methods for location attributes, but it does the work of setting the longitude and latitude value in the log object, but calling the [[LTLogStore sharedStore] saveChanges] still won't save it into the database.
-(CLLocation*)location;
-(void)setLocation:(CLLocation*)location;
//this all works
-(Audio*)newAudio;
-(Audio*)newAudioWithPath:(NSString*)audioPath;
//after calling this method, even the log.text changes will be saved to the database.
-(void)addAudioWithPath:(NSString*)audioPath;
-(void)removeAudio:(Audio*)audio;
#end
#import "Log.h"
#import "Audio.h"
#import "LTLogStore.h"
#implementation Log
-(CLLocation*)location{
if (!self.longitude || !self.latitude) {
return nil;
}
CLLocation *l = [[CLLocation alloc] initWithLatitude:[self.latitude doubleValue] longitude:[self.longitude doubleValue]];
return l;
}
-(void)setLocation:(CLLocation*)location{
if (location==nil) {
self.latitude = nil;
self.longitude = nil;
}
self.latitude = [NSNumber numberWithDouble: location.coordinate.latitude];
self.longitude = [NSNumber numberWithDouble:location.coordinate.longitude];
[[LTLogStore sharedStore] saveChanges];
}
-(Audio*)newAudio{
Audio *a = [Audio new];
a.log = self;
return a;
}
-(Audio*)newAudioWithPath:(NSString*)audioPath{
Audio *new = [self newAudio];
[new setKey:audioPath];
return new;
}
-(void)addAudioWithPath:(NSString*)audioPath{
Audio *new = [self newAudio];
[new setKey:audioPath];
[[LTLogStore sharedStore] saveChanges];
}
-(void)removeAudio:(Audio*)audio{
[self.audiosSet removeObject:audio];
[[[LTLogStore sharedStore] context] deleteObject:audio];
[[LTLogStore sharedStore] saveChanges];
}
#end
UPDATE:
Problem solved, see answer.
UPDATE QUESTION: Why is my overriding causing the problem? Can someone explain the cause behind the magic of Core Data or maybe KVO behind scene?
Problem solved, I overrode the willChangeValueForKey method in the Log class, which caused the problem, I thought the code is irrelevant. But it IS:
- (void)willChangeValueForKey:(NSString *)key{
//I added the following line to fix my problem
[super willChangeValueForKey:key];
//this is the original line, I want to have this
//because I want to have a isBlank property
//so I can see if the user modified the log
_isBlank = false;
//I tried to also add the following line to be safe.
//turns out this line is not needed, and it will make the problem occur again
//[super didChangeValueForKey:key];
}
I'm facing a little bit of trouble finding whats wrong with my code, because I'm trying to download several images from different urls and the requests are always failing.
Could you guys give me a little help?
Here is my code:
//
// Chapters.h
//
//
// Created by Nuno Martins on 11/07/18.
// Copyright 2011 WeTouch. All rights reserved.
//
#import <Foundation/Foundation.h>
//#import <GHUnit/GHUnit.h>
#class ASINetworkQueue;
#interface Chapters : NSObject {
NSString * chaptersBaseUrl;
NSMutableArray * chaptersList;
ASINetworkQueue *networkQueue;
}
#property (retain) ASINetworkQueue *networkQueue;
#property (nonatomic, retain) NSString *chaptersBaseUrl;
#property (nonatomic, retain) NSMutableArray *chaptersList;
-(void)downloadChaptersIconsFromUrlArrayToFile:(NSMutableArray *)iconUrls;
#end
//
// Chapters.m
//
//
// Created by Nuno Martins on 11/07/18.
// Copyright 2011 WeTouch. All rights reserved.
//
#import "Chapters.h"
#import "Chapter.h"
#import "PDFDataAgregator.h"
#import "ASIHTTPRequest.h"
#import "ASINetworkQueue.h"
#implementation Chapters
#synthesize chaptersBaseUrl;
#synthesize chaptersList;
#synthesize networkQueue;
- (void)dealloc
{
[networkQueue release];
[super dealloc];
}
-(void)downloadChaptersIconsFromUrlArrayToFile:(NSMutableArray *)iconUrls
{
networkQueue = [[ASINetworkQueue alloc] init];
// Stop anything already in the queue before removing it
[networkQueue cancelAllOperations];
// Creating a new queue each time we use it means we don't have to worry about clearing delegates or resetting progress tracking
[networkQueue setDelegate:self];
[networkQueue setRequestDidFinishSelector:#selector(requestFinished:)];
[networkQueue setRequestDidFailSelector:#selector(requestFailed:)];
[networkQueue setQueueDidFinishSelector:#selector(queueFinished:)];
NSLog(#"Array-> %d", [iconUrls count]);
NSMutableArray *myIcons = [[NSMutableArray alloc] initWithArray:iconUrls];
//Create Chapters Folder
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *docDirectory = [paths objectAtIndex:0];
NSString *newDir = [docDirectory stringByAppendingPathComponent:#"Chapters"];
[[NSFileManager defaultManager] createDirectoryAtPath:newDir withIntermediateDirectories:YES attributes:nil error: NULL];
for(unsigned i = 0; i < [myIcons count]; i++)
{
NSURL *url = [NSURL URLWithString:[myIcons objectAtIndex:i]];
NSString *fileName = [url lastPathComponent];
NSString *filePath = [newDir stringByAppendingPathComponent:fileName];
NSLog(#"Icon File Path: %#",filePath);
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:[iconUrls objectAtIndex:i]]];
[request setDownloadDestinationPath:filePath];
//[request setUserInfo:[NSDictionary dictionaryWithObject:#"request1" forKey:#"name"]];
[request setTemporaryFileDownloadPath:[filePath stringByAppendingPathExtension:#"download"]];
[request setAllowResumeForFileDownloads:YES];
[networkQueue addOperation:request];
}
[networkQueue go];
}
- (void)requestFinished:(ASIHTTPRequest *)request
{
// You could release the queue here if you wanted
if ([networkQueue requestsCount] == 0) {
// Since this is a retained property, setting it to nil will release it
// This is the safest way to handle releasing things - most of the time you only ever need to release in your accessors
// And if you an Objective-C 2.0 property for the queue (as in this example) the accessor is generated automatically for you
[self setNetworkQueue:nil];
}
//... Handle success
NSLog(#"Request finished");
}
- (void)requestFailed:(ASIHTTPRequest *)request
{
// You could release the queue here if you wanted
NSLog(#"Number of requests in Queue %d", [networkQueue requestsCount]);
if ([networkQueue requestsCount] == 0) {
[self setNetworkQueue:nil];
}
//... Handle failure
NSLog(#"Request failed");
}
- (void)queueFinished:(ASINetworkQueue *)queue
{
// You could release the queue here if you wanted
if ([networkQueue requestsCount] == 0) {
[self setNetworkQueue:nil];
}
NSLog(#"Queue finished");
}
Well this was a problem related with Bad url format.
I was passing http:/somesite.com/someimage.png instead of passing http://somesite.com/someimage.png
I was missing the / because when I append a BaseUrl string to the filename using stringByAppending path Component it removes one slash of the HTTP://.
Solved now!