afnetworking async background task handle response when viewcontroller changes - objective-c

My application allows a user to create an item for listing on an eCommerce site. The user goes through a number of screens adding images and information until they need to create the item on the store.
The final upload screen has an UIViewController using AFNetworking has two services to call that:
1) Calls an image upload webservice and returns some ID's. On success it calls (2).
2) Calls another service using these returned ID's as part of the request.
This process is started when the user hits the Submit button.
What I would like to happen is the following:
The users clicks Submit and the process starts in the background
The current storyboard scene returns to the start screen to allow the user to create another item whilst the previous is still running.
As the code for the service calls and handling the responses from them are in the UIViewController once the scene changes the UIViewController will no longer be running on the stack so what will happen to the service response etc?
If I create a separate class to do the work I'll loose the object reference when the scene changes. If the method is still processing would it be garbage collected?
Should I be sticking this on a background thread using Grand Central Dispatch?

For more detail, here's an example.
I usually have a class named NetWrapper which manages whole network related thing.
.h
#interface NetWrapper : NSObject
+ (instancetype)shared;
#pragma mark - APIs
- (void)requestVersion;
#end
.m
static NetWrapper *_netWrapper;
#implementation NetWrapper
+ (instancetype)shared
{
if(_netWrapper == nil)
{
_netWrapper = [[NetWrapper alloc] init];
}
return _netWrapper;
}
#pragma mark - APIs
- (void)requestVersion
{
// do something
}
If you have a singleton class like this, you can alway have the same instance with
[NetWrapper shared]
and invoke instance method like below.
[[NetWrapper shared] requestVersion];

Related

"[GCController controllers]" does not contain any controllers that were connected prior to application launch

"[GCController controllers]" does not contain any controllers that were connected prior to application launch
TLDR;
I am trying to implement gamepad input on macOS using the Game Controller Framework. When invoked in my code, [GameController controllers] always returns an empty list until new controllers are connected. It never reflects gamepads connected to macOS prior to application launch, except if you disconnect them and reconnect them while the app is running. Does anyone know what I need to do to make controllers populate with pre-launch connections?
Full question
Now that Apple has added support for Xbox and Playstation controllers to the GameController framework, I'm trying to use it for gamepad input on a C++ game engine I'm developing. I'm using the framework instead of IOKit in order to "future-proof" my games to support additional controller types in the future, as well as to simplify my own input handling code.
Like many other game engines, I've foregone using NSApplicationMain() and nib files in favor of implementing my own event loop and setting up my game window programmatically. While my "Windows style" event loop appears to be working correctly, I've discovered that [GCController controllers] does not. The array it returns is always empty at launch, and will only ever reflect controllers that are connected while the game is running. Disconnecting a pre-connected controller does not trigger my GCControllerDidDisconnectNotification callback.
Here is a simplified version of my event loop:
int main(int argc, const char * argv[])
{
#autoreleasepool
{
// Create application
[NSApplication sharedApplication];
// Set up custom app delegate
CustomAppDelegate * delegate = [[CustomAppDelegate alloc] init];
[NSApp setDelegate:delegate];
// Activate and launch app
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
[NSApp setPresentationOptions:NSApplicationPresentationDefault];
[NSApp activateIgnoringOtherApps:YES]; // Strictly speaking, not necessary
[NSApp finishLaunching]; // NSMenu is set up at this point in applicationWillFinishLaunching:.
// Initialize game engine (window is created here)
GenericEngineCode_Init(); // <-- Where I want to call [GCController controllers]
NSEvent *e;
do
{
do
{
// Pump messages
e = [NSApp nextEventMatchingMask: NSEventMaskAny
untilDate: nil
inMode: NSDefaultRunLoopMode
dequeue: YES];
if (e)
{
[NSApp sendEvent: e];
[NSApp updateWindows];
}
} while (e);
} while (GenericEngineCode_Run()); // Steps the engine, returns false when quitting.
GenericEngineCode_Cleanup();
}
return 0;
}
I've confirmed that even when using [NSApp run] instead of [NSApp finishLaunching], the behavior is the same. As best as I can tell, the problem is that there's something NSApplicationMain() does that I'm not doing, but that function is a black box -- I can't identify what I need to do to get controllers to populate correctly. Does anyone know what I'm missing?
The closest thing I could find to an explanation of this problem is this answer, which suggests that my app isn't getting didBecomeActive notifications, or that at the least, the private _GCControllerManager isn't getting a CBApplicationDidBecomeActive message. I'm not a professional macOS developer, though: I don't know if this actually applies to my situation, or how I'd go about correcting the problem if it does.
After a huge amount of time searching, I found the answer on my own. It turns out that my code wasn't the problem -- the problem was that my Info.plist file was having its CFBundleIdentifier value stripped out due to a problem with my build system. It appears that the Game Controller Framework needs the bundle identifier to correctly populate [GCController controllers] at launch. While a missing CFBundleIdentifier would have been a problem anyway, as a Windows person it didn't occur to me that the identifier might be used for things besides the App Store, so I let it slide until now.
If someone else has this problem, make sure that CFBundleIdentifier isn't missing or empty in Info.plist in your assembled app bundle. In my case with Premake, I had to manually set PRODUCT_BUNDLE_IDENTIFIER with xcodebuildsettings so that $(PRODUCT_BUNDLE_IDENTIFIER) would get properly replaced in Info.plist.

Dropbox not calling delegate methods on iPad only

I'm using dropbox to upload files to a user's Dropbox.
On iPhone it works flawlessly, but on iPad the delegate methods for the DBRestClientDelegate are not being called.
I am still using v1.
The code I'm using is
- (DBRestClient *)restClient {
if (!_restClient) {
_restClient =
[[DBRestClient alloc] initWithSession:[DBSession sharedSession]];
_restClient.delegate = self;
}
return _restClient;
}
[[self restClient] uploadFile:[NSString stringWithFormat:#"%#.jpeg",fileName]
toPath:#"/"
withParentRev:nil
fromPath:imagePath];
After calling this on the iPhone the delegate method
- (void)restClient:(DBRestClient*)client uploadedFile:(NSString*)destPath from:(NSString*)srcPath
is successfully called, allowing me to handle that. On the iPad, however, that, along with the other delegate methods, are not called, does not upload the file and does not throw any errors.
Any thoughts are appreciated.
Thanks,
Luke
There are a few things that might cause your delegate methods to not be called:
Your rest client is nil or is being released (e.g., by ARC) prematurely.
You're making the call in a background thread that doesn't have a run loop.
Your delegate method that should be called back has a typo in it. Unfortunately the SDK doesn't warn you if it can't find a delegate method to call; it just completes without telling anyone.

Where does the main-loop go when creating an iOS app?

I am writing an iOS app for the iPhone in Xcode and I have created some classes as well as their methods inside their respective .h and .m files (that's two classes so basically I have two pairs of .h & .m files)
I now I want to start writing my main loop that will be executed whenever the user hits on the play button, but where exactly do I do that ?
Do I do that in ViewController.m ? e.g. inside this method :
- (IBAction)playPressed:(UIButton *)sender
{
// main loop executed in here ?
// or simply message to the main loop to start executing is *sent* from here ?
}
I read about a similar question in here and someone was suggesting AppDelegate. Now would that be AppDelegate.m or AppDelegate.h ? And if that's the case do I just start writing code or do I include everything inside something like :
int main(int argc, char **argv)
{
....
}
in the Appdelegate file?
I tried to simply start instantiating classes and declaring generic methods (not belonging to any particular class that is..) in a game.m file I created and I get a initializer element is not a compile-time constant warning as soon as I try instantiating anything
Any help? Coming from c++ it would really help me to clarify once and for all in which file exactly to write my main loop and whether I should wrap it in some kind of an int main() function..
thanks!
PS :
Just in case it makes any difference, my ViewController will only consist of a play button that would start the execution of my main loop whenever its pressed, and a stop button that would terminate the execution of the main loop
I have created their respective methods in ViewController.m :
- (IBAction)playPressed:(UIButton *)sender
{
//
}
- (IBAction)stopPressed:(UIButton *)sender
{
// ??
}
which are for the time being empty :)
The programming methodoly on iOS is different from the C++ methodoly.
In C++ , indeed , you would have to make an infinite loop and get the touches , draw everything , etc at each frame.
Until the player presses "exit" and you break the loop.
On iOS , things are done differently:
You already have a main.m file in which you have a main function.
That starts up the app delegate. That app delegate tells you when the app finished launching , goes to background , comes in foreground , etc.
When the app finished launching , you go to your first actual screen.
There , you ADD subviews. You don't draw them at each frame. That is done automatically for you once you have added the view to a parent view.
The programming on iOS is based on events. You don't have to check for touches and see if the
touch location is on a button and then call the method of that button.
Instead , you set a callback method for the button and it's called automatically for you once the button is pressed.
Of course , you first need to alloc the button and add it to a parent view.
Once you get used to this event based programming model , you will for sure like it.
At the start it may seam very different and maybe it doesn't make sense to you , but don't worry.
Comming from a C++ background is surely a good start.
Cheers,
George
EDIT: In that case , I can give more specific info:
So , you go from the AppDelegate in your first screen. Let's call it MainAppScreen.
Now , you need to add those 2 buttons and set selectors ( callback methods ) for them. I can see you already did that.
Now :
- (IBAction)playPressed:(UIButton *)sender
{
running = TRUE;
[self performSelectorInBackground:#selector(myLoop) withObject:nil];
}
- (IBAction)stopPressed:(UIButton *)sender
{
running = FALSE;
}
- (void) myLoop
{
while(running)
{
//this is your loop. You can code in here.
}
}
Where running is an instance variable in the MainAppScreen class.
Hope this helps.
Cheers!
Every iOS app, as well as every executable file has an entry point - this is the main(). You can't have more than one entry points of an executable.And if you look closely into the project you will see that there is an automatically generated main.m file in the Supporting Files group in Xcode's navigator, which looks like this:
#import <UIKit/UIKit.h>
#import "MyAppDelegate.h"
int main(int argc, char *argv[])
{
#autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([MyAppDelegate class]));
}
}
What you want to do is not clear enough, but it's a good start reading about the structure and the lifecycle of iOS apps, objective-c syntax, get familiar with the UIKit and at least some of the frameworks Apple provide.
You don't have a main in iOS apps (well, technically you do have a main, but you don't have to worry about writing it). That's all handled for you. The runloop is all done for you too. All you have to do is create your button and then tell it (via addTarget method) which method to run when it gets pressed.
Update:
This is pseudo(ish) code for what you'd need to do....
[startButton addTarget:#selector(startPressed:)];
[stopButton addTarget:#selector(stopPressed:)];
-(void)startPressed {
backgroundThread = [[NSThread alloc] initWithWhateverYouWantToRun];
[backgroundThread start];
}
-(void)stopPressed {
[backgroundThread stop];
}
In your background thread, if you want to update the UI, you would call sendMessageOnMainThread (or something similar - can't remember the exact details at the moment!)

Update UILabel in a "while loop"

I'm trying to update a UILabel within a "while loop" but it is not changing the UILabel text. I know that it is shown at the end of the current run loop cycle at the main thread as usual for iOS. But how can solve this problem ( the ftp.AsyncFinished function is provided by external chilkat ftp module) :
The data is updated every second. I searched this forum and in Apple's documentation, but I could not figure out the correct way to update a UILabel within a "while loop" on the main thread. What can I use instead of a while loop, that allows the main thread to update the UILabel.
while (ftp.AsyncFinished != YES) {
// here is the code getting data from the cilkat FTP module stored in PercentOfFile and MinutesRemaining
NSString *content =[[NSString alloc] initWithFormat:#"%.0f%% done%.0fmin left",PercentOfFile,MinutesRemaining ];
[LabelProgress setText:content];
}
In order for the UI to update at all, the main thread's run loop has to complete. That means your method (if called on the main thread), must return before any UI updates occur. All updates are then coalesced and the UI is redrawn.
See What is the most robust way to force a UIView to redraw? for more background.
Your while loop is incorrectly designed for an asynchronous update. You should not be fetching async data in the middle of a while loop. You should be registering for a callback of some kind (generally by setting yourself as delegate) and then just waiting for the system to call you.
You will need to call your upload progress in another thread and register for call backs. Create a custom class that inherits from CkoFTP2Progress which has this method:
- (void)PercentDone: (NSNumber *)pctDone abort:(BOOL *)abort;
Assuming you have a CkoFTp2 object named "ftp" and your custom class named "UploadProgress", register for callbacks:
[self.ftp setEventCallbackObject:self.uploadProgress];
You could move the while loop to a background thread and call setText from there with performSelectorOnMainThread.
// update your UILabel here
// ...
// Delay execution of long-running task so that update of UILabel can be performed
dispatch_after( dispatch_time( DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC ), dispatch_get_main_queue(), ^{
// long-running task
// ..
});

Exception/Error handling in Objective-C (iPhone app)

I actually have two questions regarding exception/error handling in the iPhone app that I am making:
The app uses Internet, but when there's no connection, the app just dies (during launch). How can I handle this to print some infomsg to the user, instead of just getting thrown back to the springboard?
Can someone show me an example of how to handle for instance a "page not found" or "no contact with server" error, so I can give some sort of info to the user in the same way as above?
For crashes, the first step is to use error messages and the debugger to figure out what call is causing the problem. If the problem is caused by an uncaught exception, read this Apple article on exception handling. The specific answer really depends on your code and exactly what is causing the crash, so I won't speculate about a particular solution.
As far as detecting server error response codes (such as 404), that's more specific to WebKit. I assume you're using UIWebView on iPhone, and you've probably noticed that none of the primary methods return errors. This is because it uses a delegate model to report progress or errors asynchronously. (It makes sense because you don't want your UI code to be at the mercy of a slow-loading (or non-existent) webpage. To be notified of such errors, there are a few steps.
Adopt the UIWebViewDelegate protocol, usually in the same class that will start the webpage load for convenience.
Set that object as the delegate of the UIWebView instance. (It has a delegate property, so you can use something like either uiView.delegate = self or [uiView setDelegate:self] based on what you prefer.)
Implement the webView:didFailLoadWithError: method in that class. (You can be notified when the load finishing by implementing webViewDidFinishLoad: as well.) This is where you include the logic of what should happen when an error occurs.
I didn't see any detailed documentation on the content of any particular errors handed back via this delegate method, but it's a standard NSError object, and I recommend checking out the contents by calling its methods, such as -localizedDescription and -userInfo.
Here is some sample code with #import statements excluded for brevity.
MyClass.h
#interface MyClass : NSObject <UIWebViewDelegate> {
IBOutlet UIWebView* myWebView;
}
-(void)webView:(UIWebView*)webView didFailLoadWithError:(NSError *)error;
#end
MyClass.m
#implementation MyClass
- (id) init {
if ((self = [super init]) == nil)
return nil;
// initialize myWebView
myWebView.delegate = self;
return self;
}
- (void) webView:(UIWebView*)webView didFailLoadWithError:(NSError*)error {
...
}
#end
Testing for a connection is pretty easy...
NSString * test = [NSString stringWithContentsOfURL:[NSURL URLWithString:#"http://www.stackoverflow.com"]];
if (test == nil) {
//display an alertview saying you don't have an internet connection
}
Using a URL to test for a connection is not a good idea, it is not robust enough to determine if the internet connection is down, the website is down or some other network issue etc and above all it adds an overhead to the call as far as network traffic.
Look at the Reachability demo on the Apple site, it uses the correct way to determine connectivity, including whether you are on wifi etc.