Trouble using subclasses of PFObject in blocks such as fetchIfNeededInBackgroundWithBlock - ios

I'm having trouble understanding how to use subclassed objects with blocks.
Here is an example of what I'm trying. PFItem is a subclass of PFObject.
- (void) handleItem:(PFItem *)item{
[item fetchIfNeededInBackgroundWithBlock:^(PFItem *item, NSError *error) {
if (!error) {
if ([item.name isEqualToString:#"Antidote"]) {
NSLog(#"Applied %#", item.name);
NSMutableArray *discardItems = [NSMutableArray array];
for (PFItem *item in self.pfButtonCharacter.itemsApplied) {
if (item.malicious) {
[discardItems addObject:item];
NSLog(#"Removing %#", item.name);
}
}
[PFObject deleteAll:discardItems];
}
}
}];
}
However, xcode flags this as a semantic error:
Incompatible block pointer types sending 'void (^)(PFItem *__strong, NSError *__strong)' to parameter of type 'PFObjectResultBlock' (aka 'void (^)(PFObject *__strong, NSError *__strong)')
If I change from PFItem to PFObject in fetchIfNeededInBackgroundWithBlock, it works, but then I can no longer access the properties of item. Instead of item.name I need to do item[#"name"].

If the method specifies you must use a block that takes a PFObject argument rather than a PFItem argument, then you must use a block that matches that for the method.
If you know the object being sent is actually a PFItem, you can always cast it within the block:
[item fetchIfNeededInBackgroundWithBlock:^(PFObject *obj, NSError *error) {
PFItem *item;
if ([obj isKindOfClass:[PFItem class]]) {
item = (PFItem *)obj;
} else {
return;
}
if (!error) {
if ([item.name isEqualToString:#"Antidote"]) {
NSLog(#"Applied %#", item.name);
NSMutableArray *discardItems = [NSMutableArray array];
for (PFItem *item in self.pfButtonCharacter.itemsApplied) {
if (item.malicious) {
[discardItems addObject:item];
NSLog(#"Removing %#", item.name);
}
}
[PFObject deleteAll:discardItems];
}
}
}];

- (void) handleItem:(PFItem *)item{
[item fetchIfNeededInBackgroundWithBlock:^(PFObject *pfObj, NSError *error) {
PFItem *item = (PFItem *)pfObj;
if (!error) {
if ([item.name isEqualToString:#"Antidote"]) {
NSLog(#"Applied %#", item.name);
NSMutableArray *discardItems = [NSMutableArray array];
for (PFItem *item in self.pfButtonCharacter.itemsApplied) {
if (item.malicious) {
[discardItems addObject:item];
NSLog(#"Removing %#", item.name);
}
}
[PFObject deleteAll:discardItems];
}
}
}];
}
Cast the PFObject to a PFItem and you're done. This is assuming that the PFObject is actually a PFItem.

Related

Obj-c - NSArray property appears NULL in numberOfRowsInSection, but not in viewDidLoad?

I'm trying to create a new array by filtering self.neighbourData into self.closeByNeighbours (a new array), and everything works great - the data appears as it should in viewDidLoad. However, when trying to return the number of cells for self.closeByNeighbours, the NSArray count returns NULL? Any idea as to why this is?
ViewController.h
#property (strong, nonatomic) NSArray *closeByNeighbours;
#property (strong, nonatomic) NSMutableArray *neighbourData;
ViewController.m
-(void)viewDidLoad {
NSMutableDictionary *viewParams = [NSMutableDictionary new];
[viewParams setValue:#"u000" forKey:#"view_name"];
[DIOSView viewGet:viewParams success:^(AFHTTPRequestOperation *operation, id responseObject) {
self.neighbourData = [responseObject mutableCopy];
[self.neighboursView reloadData];
NSDictionary *userDictInfo = (NSDictionary*) [NSKeyedUnarchiver unarchiveObjectWithData:[[NSUserDefaults standardUserDefaults] objectForKey:#"diosSession"]];
DIOSSession *session = [DIOSSession sharedSession];
[session setUser:userDictInfo];
[session user];
NSString *myData = [session user][#"user"][#"field_province"][#"und"][0][#"safe_value"];
self.closeByNeighbours = [self.neighbourData filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"(province contains[c] %#)", myData]];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Failure: %#", [error localizedDescription]);
}];
}
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (tableView == self.neighboursView) {
return [self.closeByNeighbours count];
}
}
The framework reloads the table view implicitly after the view did load.
At that moment closeByNeighbours is declared but not initialized therefore it's nil.
The completion block in viewGet is called much later.
A solution to avoid nil is to initialize the array at the beginning of viedDidLoad
-(void)viewDidLoad {
[super viewDidLoad];
self.closeByNeighbours = [NSArray array];
...
And you have to reload the table view on the main thread in the completion handler
...
self.closeByNeighbours = [self.neighbourData filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"(province contains[c] %#)", myData]];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
I think you are forgetting to init closeByNeighbours. Try adding.
NSString *myData = [session user][#"user"][#"field_province"][#"und"][0][#"safe_value"];
self.closeByNeighbours = [NSArray array: filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"(province contains[c] %#)", myData]];
Or however you want, you just need to initialize the closeByNeighbours property.

How to integrate NSUrlsession in ios

Hi I am very new to ios and in my app I am using NSUrlSession for integrating services.
Here my main problem is when I get a response from the server, I can't handle them properly.
When I get a correct response, then see the below json stucture:-
responseObject = {
{
Name = Broad;
DeptNo = A00;
BatchNo = 23;
DeptId = 120;
},
{
Name = James;
DeptNo = B00;
BatchNo = 23;
DeptId = 123;
},
}
when I get a wrong response, see the below json stucture:-
responseObject = {
error = 1;
message = "Invalid Code";
}
when I get a correct response from the server, I am getting an exception in my below if block(like __NSCFArray objectForKey:]: unrecognized selector sent to instance 0x1611c200') and when I get a wrong response then T get exception in my else block
Please help me how to handle them
my code:-
(void) GetCallService1: (id)MainResponse{
dispatch_async(dispatch_get_main_queue(), ^{
NameArray = [[NSMutableArray alloc]init];
IdArray = [[NSMutableArray alloc]init];
if([MainResponse objectForKey:#"error"] != nil)
{
NSLog(#"No data available");
}
else{
for (NSDictionary *obj in MainResponse) {
if([obj objectForKey:#"Name"] && [obj objectForKey:#"DeptNo"]) {
NSString * Name = [obj objectForKey:#"Name"];
[NameArray addObject:Name];
NSString * Id = [obj objectForKey:#"id"];
[IdArray addObject:Id];
}
}
}
});
}
1)Change Your implementation like below
2)I checked is it dictionary type & error key has some value
3)Earlier you were calling objectForKey on Array, therefore it was crashing
-(void) GetCallService1: (id)MainResponse{
dispatch_async(dispatch_get_main_queue(), ^{
NameArray = [[NSMutableArray alloc]init];
IdArray = [[NSMutableArray alloc]init];
//here I checked is it dictionary type & error key has some value
if ([MainResponse isKindOfClass:[NSDictionary class ]] &&[MainResponse objectForKey:#"error"])
{
NSLog(#"No data available");
}
else{
for (NSDictionary *obj in MainResponse) {
if([obj objectForKey:#"Name"] && [obj objectForKey:#"DeptNo"]) {
NSString * Name = [obj objectForKey:#"Name"];
[NameArray addObject:Name];
NSString * Id = [obj objectForKey:#"id"];
[IdArray addObject:Id];
}
}
}
});
}
Try this:
//Result Block
typedef void (^ResultBlock)(id, NSError*);
//URL request
-(void)requestURL:(NSURLRequest *)request withResult:(ResultBlock)resultHandler{
//URLSession
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * data, NSURLResponse * response, NSError * error) {
if(!error){
NSError *jsonError = nil;
id result = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&jsonError];
if([result isKindOfClass:[NSArray class]]){
//Success
resultHandler(result,nil);
}
else if([result isKindOfClass:[NSDictionary class]]){
if([[result objectForKey:#"error"] integerValue]){
//Failure.
NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary];
[errorDetail setValue:[result objectForKey:#"message"] forKey:NSLocalizedDescriptionKey];
NSError *error = [NSError errorWithDomain:#"Error" code:100 userInfo:errorDetail];
resultHandler(nil, errorDetail);
}
}
}
}];
[task resume];
}
//Call your requestURL method:
[self requestURL:request withResult:^(id result, NSError *error){
if(!error){
//Success, Read & update your list
}
else{
//Error
// NSLog(error.localizedDescription());
}
}];

Dynamically reference variables/objects/arrays etc

This seems such an easy task, at least it is in VB.net. I simply need to reference an array based on a string that is passed to a method. When a view controller loads a method is called and a string is passed. A URL will be created based on this string and JSON will be fetched from it. What I want is for the method to populate an appropriate array based on this passed string.
Here we see the method "goGetData" being called in class "getData" with one of three string parameters "workshop/speaker/exhibitor":
- (void)viewDidLoad
{
[getData goGetData:#"workshop"];
[getData goGetData:#"speaker"];
[getData goGetData:#"exhibitor"];
getData *getDataInstance = [[getData alloc] init];
NSArray *newTablesArray = getDataInstance.jsonAllTables;
NSLog(#"Json currently = %#", newTablesArray);
[super viewDidLoad];
[[self myTableView]setDelegate:self];
[[self myTableView]setDataSource:self];
arrayTable =[[NSMutableArray alloc]init];
}
For example if "goGetDate" is fired with "speaker" I would need the speaker data to be fetched and then the "_jsonSpeaker" array to be populated. Here is my attempt so far to reference and populate the arrays based on what string was passed in the method call:
#import "getData.h"
#implementation getData
+(void)goGetData:(NSString *)requestedTable
{
getData *getDataInstance = [[getData alloc] init];
[getDataInstance buildArray];
[getDataInstance fetchData:requestedTable];
}
-(void)buildArray{
// I tried putting the arrays in an array but still do no know how to reference them
_jsonAllTables = [[NSMutableArray alloc] initWithObjects:_jsonExhibitor, _jsonSpeaker, _jsonWorkshop, nil];
}
-(void)fetchData:(NSString *)requestedTable{
NSString *varCurrentTable;
varCurrentTable = [NSString stringWithFormat:#"_json%#", requestedTable];
NSString *requestedURL;
requestedURL = [NSString stringWithFormat:#"http://testapi.website.com/api/%#", requestedTable];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:requestedURL]];
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if (response){
NSHTTPURLResponse *newResp = (NSHTTPURLResponse*)response;
if (newResp.statusCode == 200) {
// STUFF FOR TESTING NSLog(#"Response to request: %# is: %i GOOD", requestedURL, newResp.statusCode);
if ([data length] >0 && error == nil)
{
// STUFF FOR TESTING NSUInteger indexOfArray = [_jsonAllTables indexOfObject:varCurrentTable];
// STUFF FOR TESTING NSString *objectAtIndexOfArray = [_jsonAllTables objectAtIndex:indexOfArray];
// This is the part I think I am stuck on:
// "CURRENT TABLE TO BE POPULATED" = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
}
else if ([data length] == 0 && error == nil)
{
NSLog(#"Nothing was downloaded");
}
else if (error != nil)
{
NSLog(#"Error: %#", error);
}
} else if (newResp.statusCode == 404){
NSLog(#"Response to request: %# is: %i BAD - URL incorrect", requestedURL, newResp.statusCode);
} else {
// add more returned status error handling here
}
}else{
NSLog(#"No response received");
}
}];
}
#end
Thanks,
Added for clarification on what I am trying to achieve: To save a LOT of writing out the same thing over and over is the following possibly in Obj-c (please excuse the mish-mash of languages)
NSArray *ListOfTables = [NSArray arrayWithObjects:#"Speaker", #"Exhibitor", #"Workshop", nil];
For i as int = 0 to ListOfTables.count{
[self fetchData:(ListOfTables.objectAtIndex = i) withCompletion:^(NSArray* objects, NSError*error){
dispatch_async(dispatch_get_main_queue(), ^{
if (objects) {
self.(ListOfTables.objectAtIndex = i) = objects;
}
else {
NSLog(#"Error: %error", error);
}
});
}];
i++;
Next
};
Notice i don't call a separate method for each table, instead I call the same method but with different table name parameter each time. I can't seem to find a working example with such placeholders in Xcode.
You probably want a method which is asynchronous and returns the result via a completion handler:
typedef void(^completion_t)(NSArray* objects, NSError*error);
-(void)fetchData:(NSString *)tableName
withCompletion:(completion_t)completionHandler;
Usage:
- (void) foo {
[self fetchData:tableName1 withCompletion:^(NSArray* objects, NSError*error){
dispatch_async(dispatch_get_main_queue(), ^{
if (objects) {
self.table1 = objects;
}
else {
NSLog(#"Error: %error", error);
}
});
}];
[self fetchData:tableName2 withCompletion:^(NSArray* objects, NSError*error){
dispatch_async(dispatch_get_main_queue(), ^{
if (objects) {
self.table2 = objects;
}
else {
NSLog(#"Error: %error", error);
}
});
}];
[self fetchData:tableName3 withCompletion:^(NSArray* objects, NSError*error){
dispatch_async(dispatch_get_main_queue(), ^{
if (objects) {
self.table3 = objects;
}
else {
NSLog(#"Error: %error", error);
}
});
}];
}
Implementation:
typedef void(^completion_t)(NSArray* objects, NSError* error);
-(void)fetchData:(NSString *)tableName
withCompletion:(completion_t)completionHandler
{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:tableName]];
// Setup HTTP headers, e.g. "Accept: application/json", etc.
...
[NSURLConnection sendAsynchronousRequest:request
queue:[[NSOperationQueue alloc] init]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSError* err = error;
NSArray* objects; // final result array as a representation of JSON Array
if (response) {
NSHTTPURLResponse *newResp = (NSHTTPURLResponse*)response;
if (newResp.statusCode == 200) {
if ([data length] >0 && error == nil)
{
NSError* localError;
objects = ... // Use NSJSONSerialization to obtain a representation
if (objects) {
if (completionHandler) {
completionHandler(object, nil);
}
return;
}
else {
err = localError;
}
}
else {
err = ...
}
}
}
if (objects == nil) {
assert(err);
if (completionHandler) {
completionHandler(nil, err);
}
}
}];
}
Asynchronous Loop
Another example, for loading a bunch of data:
First, implemented a method which is an "asynchronous loop":
typedef void(^completion_t)(id result, NSError* error);
- (void) fetchObjectsWithTableNames:(NSMutableArray*)tableNames
completion:(completion_t)completionHandler;
This method is, itself asynchronous, thus the completion handler.
Usage:
- (void) foo
{
NSArray* tableNames = #[#"A", #"B", #"C"]; // possibly 1000
[self fetchObjectsWithTableNames:[tableNames mutableCopy]:^(id result, NSError*error){
if (error) {
NSLog(#"Error: %#", error);
}
else {
// finished fetching a bunch of datas with result:
NSLog(#"Result: %#", result);
}
}];
}
Implementation
- (void) fetchObjectsWithTableNames:(NSMutableArray*)tableNames
completion:(completion_t)completionHandler;
{
if ([tableNames count] > 0) {
NSString* name = [tableNames firstObject];
[tableNames removeObjectAtIndex:0];
[self fetchData:name withCompletion:^(NSArray* objects, NSError*error){
if (objects) {
[self.tables addObject:objects];
[self fetchObjectsWithTableNames:tableNames completion:completionHandler];
}
else {
// handle error
}
}];
}
else {
// finished
if (completionHandler) {
completionHandler(#"finished", nil);
}
}
}

pulling a value from NSMutableDictionary

Here's my code for returning a unique value for identical keys in a dictionary. Right now, in my log, my "objects array:" is 6 (3 sets of (2 objects with identical keys)), and my "dictionary:" returns values for 1 object from each set (3 unique values). In my 'for' statement:
for (id key in dict)
{
self.titlesArray = [NSMutableArray arrayWithObject:dict];
NSLog(#"titles: %#", self.titlesArray);
self.titlesArray = [[NSMutableArray alloc] initWithObjects:[dict valueForKey:key] ,nil];
NSLog(#"titles: %#", self.titlesArray);
}
The first log prints out the three unique values AND keys. The second prints only a single value for a single key (which is what I want.. but I need all three key values) So my problem now is that I am unable to pull a key for each unique value from the dictionary and add it to my titlesArray.
for (id key in dict)
{
self.titlesArray = [NSMutableArray arrayWithObject:dict];
self.titlesArray = [[NSMutableArray alloc] initWithObjects:[dict valueForKey:key] ,nil];
code isn't quite right.
PFQuery *query = [PFQuery queryWithClassName:#"Images"];
[query whereKey:#"recipientIds" equalTo:[[PFUser currentUser] objectId]];
[query orderByDescending:#"createdAt"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error) {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
else {
// found messages!
self.objectsArray = objects;
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
for(id obj in self.objectsArray){
PFObject *key = [obj valueForKey:#"titleLabel"];
if(![dict objectForKey:key]){
[dict setValue:key forKey:[obj valueForKey:#"titleLabel"]];
}
}
for (id key in dict) {
self.titlesArray = [NSMutableArray arrayWithObject:dict];
NSLog(#"titles: %#", self.titlesArray);
self.titlesArray = [[NSMutableArray alloc] initWithObjects:[dict valueForKey:key] ,nil];
NSLog(#"titles: %#", self.titlesArray);
}
NSLog(#"dictionary: %#", dict);
NSLog(#"Objects array is %d", [self.objectsArray count]);
[self.pickerView reloadComponent:0];
it looks like there is some type error in line
PFObject *key = [self.objectsArray valueForKey:#"titleLabel"];
it should be
PFObject *key = [obj valueForKey:#"titleLabel"];
It's happening in this line, isn't it:
if(![dict objectForKey:#"titleLabel"]){
[dict setValue:obj forKey:key];
}
}
You are setting "obj" as a value, no problem there, but then you are using "key" which is a PFObject, but NSDictionary requires a NSString for the key.
If PFObject contains a NSString property that you want to use, you can pass that in. For example, if PFObject has an NSString property called "name" you could call this:
if(![dict objectForKey:#"titleLabel"]) {
[dict setValue:obj forKey:key.name];
}
}
The relevant thing to notice is the types of the parameters when NSMutableDictionary defines this method, namely the (NSString*):
- (void)setValue:(id)value forKey:(NSString *)key
How does your PFObject look like. Does it have strings in it?. According to your question you already know that you can't pass a PFObject as key for dictionary. If your object is some what like this
interface PFObject : NSObject
{
NSString *keyString;
......
.Some other variables
}
Then you should be using it like this to set it as key
PFObject *key = [self.objectsArray valueForKey:#"titleLabel"];
if(![dict objectForKey:#"titleLabel"]){
[dict setValue:obj forKey:[key valueForKey#"titleLabel"]];
}
Here is the code I found to work. It takes the array and sorts through the keys to return only unique values for a specific key:
PFQuery *query = [PFQuery queryWithClassName:#"Images"];
[query whereKey:#"recipientIds" equalTo:[[PFUser currentUser] objectId]];
[query orderByDescending:#"createdAt"];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (error) {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
else {
// found messages!
self.objectsArray = objects;
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
for(id obj in self.objectsArray){
PFObject *key = [obj valueForKey:#"titleLabel"];
if(![dict objectForKey:key]){
[dict setValue:key forKey:[obj valueForKey:#"titleLabel"]];
}
}
for (id key in dict) {
self.titlesArray = [NSMutableArray arrayWithObject:dict];
[self.titlesArray addObject:dict.allKeys];
self.titlesArray = [self.titlesArray objectAtIndex:1];
}
NSLog(#"titles: %#", self.titlesArray);
[self.pickerView reloadComponent:0];

remove table rows before reload

I want to remove all rows in my table view before reloading the data, but can't seem to get it to work.
In my viewcontroller I get my array from this AFNetworking request.
- (void)viewDidLoad
[[LocationApiClient sharedInstance] getPath:#"locations.json"
parameters:nil
success:
^(AFHTTPRequestOperation *operation, id response) {
NSLog(#"Response: %#", response);
NSMutableArray *location_results = [NSMutableArray array];
for (id locationDictionary in response) {
Location *location = [[Location alloc] initWithDictionary:locationDictionary];
[location_results addObject:location];
}
self.location_results = location_results;
[self.tableView reloadData];
}
failure:
^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error fetching locations!");
NSLog(#"%#", error);
}];
}
And I want to remove data then reload it with this button
- (IBAction)locationPressed:(id)sender {
[[Location sharedSingleton].locationManager startUpdatingLocation];
[self viewDidLoad];
NSMutableArray *location_results = [NSMutableArray array];
[location_results removeAllObjects];
[self.tableView reloadData];
}
But it's not removing the rows. I see the reload happening over the top of the rows that are already there. Any suggestions?
Please DON'T EVER call viewDidLoad manually.
Create a method like
- (void)reloadDataWithCompletion:(void(^)(NSArray *locations))completion
failure:(void(^)(NSError *error))failure {
[[LocationApiClient sharedInstance] getPath:#"locations.json"
parameters:nil
success:
^(AFHTTPRequestOperation *operation, id response) {
NSLog(#"Response: %#", response);
NSMutableArray *location_results = [NSMutableArray array];
for (id locationDictionary in response) {
Location *location = [[Location alloc] initWithDictionary:locationDictionary];
[location_results addObject:location];
}
if(completion)
completion(location_results);
}
failure:
^(AFHTTPRequestOperation *operation, NSError *error) {
if(failure)
failure(error);
}];
}
And call it whenever you need to reload the data
- (void)viewDidLoad {
[super viewDidLoad]; // Don't forget the call to super!
[self reloadDataWithCompletion:^(NSArray *locations) {
self.location_results = locations;
[self.tableView reloadData];
} failure:^(NSError *error) {
NSLog(#"Error fetching locations!");
NSLog(#"%#", error);
}];
}
- (IBAction)locationPressed:(id)sender {
[[Location sharedSingleton].locationManager startUpdatingLocation];
[self reloadDataWithCompletion:^(NSArray *locations) {
self.location_results = locations;
[self.tableView reloadData];
} failure:^(NSError *error) {
NSLog(#"Error fetching locations!");
NSLog(#"%#", error);
}];
}
In order to achieve a graphical effect for reloading the table you can do (assuming that you have only one section), substitute
[self.tableView reloadData];
with
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];

Resources