I'm new to iOS development and wrestling with UITableViews.
My problem is that I'm populating my UITableView with data from an external server, but due to multithreading it's not waiting until the data arrives before loading the table view.
My current idea is to reload the table view when the data loads.
Earlier in same class DailyBreakdown.c, I reload the table view with this code:
-(void)viewWillAppear:(BOOL)animated
{
[[self class] getAllActivities];
[super viewWillAppear:animated];
[self makeObjects];
[self.tableView reloadData];
}
So on the callback when my data loads (using Restkit), I try to call [self.tableView reloadData] again, but I get the errors:
Definition of 'struct objc_class' must be imported from module
'ObjectiveC.runtime' before it is required
Implicit conversion of Objective-C pointer type 'Class' to C pointer
type 'struct objc_class *' requires a bridged cast
Here's the method where I return the Activity objects from the API:
+(NSArray *)getAllActivities{
if (allActivities == nil) {
// Load the object model via RestKit
//allActivities = [[NSMutableArray alloc] initWithObjects:#"Workout", #"Baked cake", #"Read for HR", nil];
allActivities = [[NSMutableArray alloc] init];
RKObjectManager *objectManager = [RKObjectManager sharedManager];
[objectManager getObjectsAtPath:#"/activities"
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
//allActivities = [mappingResult array];
allActivities = [NSMutableArray arrayWithArray: [mappingResult array]];
[[self class] makeObjects];
/*** THIS LINE IS THE PROBLEM **/
[self.tableView reloadData];
}
failure:^(RKObjectRequestOperation *operation, NSError *error) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
NSLog(#"Hit error: %#", error);
}];
}
return allActivities;
}
So, why can't I call [self.tableView reloadData] as before? How would I do this from inside my class method?
Also, is there a better way to accomplish this besides reloading the tableview? Maybe threadlocking so that allActivities doesn't return nil when the view is loaded? Any ideas are welcome.
Thanks!
getAllActivities is a class method, so you can't access properties and instance methods from it. The simplest solution would be to make it an instance method. In my opinion, given your situation (trying to access a tableView property) this change would be the right thing to do.
Another solutions to this problem:
you may add a block argument to getAllActivities that will be called when the call to external service completes successfully; in this block you would reload your table
you may pass your instance's self to getAllActivities and use it to access tableView
One more thing I've noticed - return allActivities; won't contain results from the last RestKit call, because it'll be executed before the call completes.
Your getAllActivities method is defined as a class level method (because it's prefixed with a plus). What you probably meant to do was define an instance level method (with a minus in place of the plus).
-(NSArray *)getAllActivities
The next thing I notice is that you're neither doing anything with the result from the call, nor is the result what you'd expect. Your RKObjectManager's getObjectsAtPath is asynchronous, and will return immediately. Meaning that the value for allActivities will almost always be an empty array. Therefore you can further re-define your method as:
-(void)getAllActivities{
//...
//No return here!
}
And, finally, since your method isn't really "getting" activities at all, you might consider naming it to something like:
-(void)reloadAllActivities
Related
In my iOS app, I am using the forecast.io API to get a weather forecast for 3 specific days. Once I get the array from all 3, I want to create an NSMutableArray and add all of those objects to it. The problem I am getting is that it is trying to create the NSMutableArray before the forecast data is retrieved. Here is what I have so far:
typedef void(^myCompletion)(BOOL);
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:YES];
[self myMethod:^(BOOL finished) {
if(finished){
NSMutableArray *allOfIt = [[NSMutableArray alloc] initWithObjects:self.weatherSaturday, self.weatherSunday, self.weatherMonday, nil];
NSLog(#"%#", allOfIt);
}
}];
}
-(void) myMethod:(myCompletion) compblock{
//do stuff
ForecastKit *forecast = [[ForecastKit alloc] initWithAPIKey:#"MY-API-KEY"];
// Request the forecast for a location at a specified time
[forecast getDailyForcastForLatitude:37.438905 longitude:-106.886051 time:1467475200 success:^(NSArray *saturday) {
// NSLog(#"%#", saturday);
self.weatherSaturday = saturday;
} failure:^(NSError *error){
NSLog(#"Daily w/ time %#", error.description);
}];
[forecast getDailyForcastForLatitude:37.438905 longitude:-106.886051 time:1467561600 success:^(NSArray *sunday) {
// NSLog(#"%#", sunday);
self.weatherSunday = sunday;
} failure:^(NSError *error){
NSLog(#"Daily w/ time %#", error.description);
}];
[forecast getDailyForcastForLatitude:37.438905 longitude:-106.886051 time:1467648000 success:^(NSArray *monday) {
// NSLog(#"%#", monday);
self.weatherMonday = monday;
} failure:^(NSError *error){
NSLog(#"Daily w/ time %#", error.description);
}];
compblock(YES);
}
When the code is ran, it fires the NSLog for allOfIt, which shows as null, before it gets any of the forecast data. What am I missing?
The problem I am getting is that it is trying to create the NSMutableArray before the forecast data is retrieved
Yup, exactly. The problem is simply that you don't understand what "asynchronous" means. Networking takes time, and it all happens in the background. Meanwhile, your main code does not pause; it is all executed instantly.
Things, therefore, do not happen in the order in which your code is written. All three getDailyForcastForLatitude calls fire off immediately and the whole method ends. Then, slowly, one by one, in no particular order, the server calls back and the three completion handlers (the stuff in curly braces) are called.
If you want the completion handlers to be called in order, you need each getDailyForcastForLatitude call to be made in the completion handler of the getDailyForcastForLatitude call that precedes it. Or, write your code in such a way that it doesn't matter when and in what order the completion handlers come back to you.
I'm a junior level developer and I'm stuck at this very simple thing which I'm unable to figure out.
I've a class which is AFHTTPRequestOperationManager extended. Means it's #interface apiClient : AFHTTPRequestOperationManager in this class I've all my code which fetch images from Imgur API and using AFNetworking I've parsed the data upto a level where I start getting only the link of the images.Now the rootViewController which is a UICollectionViewController extended. In it's viewDidLoad I send a call to my APIClient and it start moving from methods to methods and finally give me an NSMutableArray which contains the images link I'll use in the UIImageView of my CollectionViewCell.
The Question is I'm using this to send the final links back to my rootViewController i.e. GalleryCollectionViewController
GalleryCollectionViewController *gcvc = [[GalleryCollectionViewController alloc] init];
[gcvc recieveGalleryImagesLinks:galleryImgLinkArr];
The problem is that in gcvc it calls numberOfItemsInSection before it can get any response from the API. So that means the count goes out zero and hence it is not displaying the data. So how can I get it to get the API call from apiClient class first and then make the viewController. I hope I've clearly stated my problem and if there is a need of sharing any more code I'll do it.
UPDATE
After authorization this methods gets the gallery images:
- (void)galleryAlbum:(NSString *)ID {
NSLog(#"ID is %#", ID);
[self GET:[NSString stringWithFormat:#"%#3/gallery/album/%#", self.baseURL,ID] parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
self.galleryData = (NSDictionary *)responseObject;
galleryDataArr = [[NSMutableArray alloc] init];
galleryDataArr = [self.galleryData valueForKey:#"data"];
galleryImagesArr = [[NSMutableArray alloc] init];
galleryImagesArr = [galleryDataArr valueForKey:#"images"];
galleryImgLinkArr = [[NSMutableArray alloc] init];
galleryImgLinkArr = [galleryImagesArr valueForKey:#"link"];
GalleryCollectionViewController *gcvc = [[GalleryCollectionViewController alloc] init];
[gcvc recieveGalleryImagesLinks:galleryImgLinkArr];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Failed #2");
}];
}
Here gcvc is receiving the array with images link and then reloading the collection view as:
-(void)recieveGalleryImagesLinks:(NSMutableArray *)array
{
self.imageLinks = [[NSMutableArray alloc] initWithArray:array];
NSLog(#"Array: %#",self.imageLinks);
[self.collectionView reloadData];
}
But it's giving "UICollectionView: must be initialized with a non-nil layout parameter" although the array is not empty.
This line of code below is incorrect:
GalleryCollectionViewController *gcvc = [[GalleryCollectionViewController alloc] init];
[gcvc recieveGalleryImagesLinks:galleryImgLinkArr];
You need to use a blocks or delegate approach here which will pass back the array back to your collectionViewController and reload the collecrionView.
Even your method call from API class seems to be correct for delegate approach but make it a protocal method. This way you need not have to change much. It will be something like below:
[self.delegate recieveGalleryImagesLinks:galleryImgLinkArr
And when are calling API and is waiting for response show activityIndicator for loading data.
I have a lot of blocks in my code. I have a process for initialising a user upon login, I am using Parse.com as my backend:
PFQuery *messageBankQuery = [PFQuery queryWithClassName:#"messageBank"];
[messageBankQuery whereKey:#"username" equalTo:[PFUser currentUser].username];
[messageBankQuery getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if(!error){
[self setupUserWithMessageBank:object];
}//end no error if
else{
NSLog:(#"error");
}
}];
The messageBank is a parse object that holds references to all the messages the user has. If that object is found setupUserWithMessageBank is called in the block. setupUserWithMessageBank also does more block work:
-(void)setupUserWithMessageBank: (PFObject *)object{
__weak FriendsViewController *weakSelf = self;
//2.)Init the user
weakSelf.currentUser = [[appUser alloc] initWithParseUser:[PFUser currentUser] andMessageBank:object];
//3.) Setup that message array
[weakSelf.currentUser setupMessagedTodayWithHandler:^(BOOL successful) {
if(successful){
//4.)Add friends to the array
[weakSelf.currentUser populateFriendsArrayWithCompletionHandler:^(BOOL successful, NSError *error, BOOL addSelf, BOOL alreadyFriends) {
if(successful){
[self.indicator stopAnimating];
[self.indicator removeFromSuperview];
[self.tableView reloadData];
__weak FriendsViewController *weakSelf = self;
[weakSelf.currentUser refreshMessagesArrayWithCompletionHandler:^(BOOL successful, BOOL newMessages) {
if(successful) {
//set the button
[self.navigationItem.rightBarButtonItem setAction:#selector(showMessages)];
}
else{
[weakSelf displayGeneralError];
}
}];//end fill messages
}
else{
[weakSelf displayGeneralError];
}
}];//end populate method call
}
else{
[weakSelf displayGeneralError];
}
}];
}
I am a little confused over the use of weakSelf. Is it okay to declare weakSelf inside the start of the setupUserWithMessageBank method? Because his method is called inside another block so technically it's being created inside a block. Do I need to pass weakSelf inside the method instead?
I'm also not completely sure where I should be using weakSelf. Do I need to use it to turn off activity indicators ? Any pointers about my usage of this would be really appreciated. Thanks!
you will probably only need to use a weakSelf if you actually keep a reference to the block within self (or maybe transitively, a block being kept in an object that is kept within self), which in this case, doesnt look like you are doing. the only reason really to use a weakSelf within a block is to avoid retain cycles.
if both blocks have short life cycles then its probably safe to just use self within the block. (if you have any control over the life cycle of the blocks, make sure they are set to nil after executing or cleaned up if they dont get executed because of some failure so they dont hang around)
I have created a class NetCalculator which I am calling when a button is pressed. The method calculate network it gets 2 NSStrings and returns an id object (either "Network" object or "UIAlertView". Then I am checking which object is and I present the data. When I am using the UIAlertView the app is crashing after showing 2-3 alerts.
Any ides why this happens? On terminal it doesnt show any error just some random hexadecimal.
-(IBAction)calculate:(id)sender {
id result;
Network *network = [[Network alloc]init];
NetCalculator *netCalculated = [[NetCalculator alloc] init];
result = [netCalculated calculateNetworkWithIP:ipLabel.text andSubnet:subnetLabel.text];
if([result isKindOfClass:[Network class]]){
network = result;
NSLog(#"network %#",network.networkIP);
}
else if([result isKindOfClass:[UIAlertView class]]) {
UIAlertView *alert;
alert = result;
[alert show];
}
};
Your code is quite strange to me. Your method calculateNetworkWithIP could return a Network result or a UIAlertView result. I wouldn't follow such an approach.
If the problem relies on memory you should show us hot that method is implemented.
Anyway, I would propose some changes (The following code does not take into account ARC or non ARC code). In particular, I would modify the calculateNetworkWithIP to return a Network result. An error will populated if a problem arises and it is passed as an argument.
- (Network*) calculateNetworkWithIP:(NSString *)ip subnet:(NSString*)subnet error:(NSError**)error
If all is ok, the result would be be a Network and so print it or reused it somewhere. Otherwise an NSError would be returned and based on that create and show an alert view.
So, here pseudo code to do it.
NetCalculator *netCalculated = [[NetCalculator alloc] init];
NSError* error = nil;
Network* networkResult = [netCalculated calculateNetworkWithIP:ipLabel.text subnet:subnetLabel.text error:&error];
if(error != nil) {
// create and show an alert view with the error you received
} else {
// all ok so, for example, save the result in a instance variable
}
To follow a similar approach you can take a look at why is "error:&error" used here (objective-c).
I'm not sure if i cause a leak here, is it ok to return allocated NSError back to
the calling method by perform selector?
Is it OK to create the NSMutableArray and store it in the same object i got for the callback? and later pass it to the delegate?
The code works fine, but because i'm new to arc i have the fear of doing something wrong.
(i'm using perform selector because my selector is dynamic. just for the example i wrote it statically).
AFHTTPRequestOperation *operation = [self.client HTTPRequestOperationWithRequest:request
success:^(AFHTTPRequestOperation *operation, id responseObject) {
//-----------------Callback--------------------
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
SEL callback = #selector(getOperationCallback:);
NSError *error = [self performSelector:callback withObject:operation];
//------------------Delegate Call---------------
if(operation.delegate)
[operation.delegate onFinish:operation.requestIdentifier error:error
data:operation.parsedObject];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//------------------Delegate Call---------------
if(operation.delegate)
[operation.delegate onFinish:operation.requestIdentifier error:error data:nil];
}];
- (NSError *)getOperationCallback:(AFHTTPRequestOperation *)operation{
NSArray *rawJson = (NSArray *)operation.jsonObject;
NSError *error;
NSMutableArray *array = [[NSMutableArray alloc] init];
for(id json in rawJson){
MyObject *object = [[MyObject alloc] initWithJson:json];
if(object){
[array addObject:object];
}else{
error = [NSError errorWithDomain:#"myErrors" code:1000 userInfo:nil];
break;
}
}
operation.parsedObject = array;
return error;
}
Generally, performSelector: in ARC could only cause a leak, if the selector you pass to it starts with alloc, new, retain, copy, or mutableCopy.
is it ok to return allocated NSError back to the calling method by perform selector?
Is it OK to create the NSMutableArray and store it in the same object
i got for the callback?
and later pass it to the delegate?
Answer to all questions are OK, nothing wrong with anything. All objects are created in autorelease way.
As long as the return value of the method your are invoking via performSelector:withObject: is an object, it's perfectly ok to do that.
It won't leak since ARC will take care of releasing array and error is autoreleased.
In general, steer clear of performSelector:. The reason being that ARC cannot help you. Even if you think your app works and you've tested that it does, it might break later down the line when you change something.
Sure, if the selector you're calling does not start with alloc, new, copy, mutableCopy, etc then it won't be a problem. But there's cases, such as using __attribute__((ns_returns_retained)) that make it non-obvious that a method might return something retained. In any case, having code that ARC cannot help you out with, is a bad thing.
There's always a way to make it such that you don't have to use performSelector:. Why not make use of a callback block for example?