I've been asked to reduce the startup time of an iOS application. I am very familiar with the platform/tools in general but I haven't focussed upon application startup time before. I'm wondering if there are known patterns for attacking this problem?
I realize that I can simply measure the time it takes to go from main() through the completion of application:didFinishLaunchingWithOptions: (which includes any background loading tasks), but again, I am hoping there might be a more standardized way to do this.
Any suggestions would be greatly appreciated!
-M
from WWDC 2012 session 235
set the start point at the first line of code in main.m
#import <UIKit/UIKit.h>
CFAbsoluteTime StartTime;
int main(int argc, char *argv[])
{
StartTime = CFAbsoluteTimeGetCurrent();
#autoreleasepool {
...
set the end point somewhere in AppDelegate's application:didFinishLaunchingWithOptions:
extern CFAbsoluteTime StartTime;
...
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Launched in %f sec", CFAbsoluteTimeGetCurrent() - StartTime);
});
Your method sounds like the correct one (I recommend using CFAbsoluteTime for your measurements).
One thing which may help you reduce the launch time is to avoid having View Controllers loaded from nibs on application launch. If I am not mistaken this forces them to be loaded into memory even before your app launches. Instead, alloc and init your view controllers dynamically when you need them. Note that you can still have the Views you'd like to be loaded by the view controllers stored in Nibs, you don't have to stop using IB. Just don't use IB to set static outlets for your app delegate.
Related
I'll try to keep it short. I want to create a 3D FPS game, just for myself, that can run on multiple platforms, but I figured that to keep it simple, perhaps it is best to start off with something that is exclusively for macOS. I opted for Objective-C because
(a) Window Application projects in Xcode can only be coded either in Obj-C or Swift (since we are dealing with Cocoa API) and
(b) Obj-C is closer to old-school then Swift.
But before I learn to draw/render 2D-shapes on the window's canvas by writing code, I have to learn to invoke an application window with its properties set to my liking. I've spent hours doing research and experimenting with chunks of code. This is what I've tried: I open with
#import <Cocoa/Cocoa.h>
int main(int argc, const char * argv[]) {
#autoreleasepool {
Then I go with ...
1)
NSWindow *window = [[[NSApplication sharedApplication] windows] firstObject];
NSRect frame = [window frame];
frame.origin.x = 100;
frame.origin.y = 200;
frame.size.width = 100;
frame.size.height = 500;
[window setFrame: frame display: YES];
... and close with ...
NSApplicationMain(argc, argv); // runs the win display function.
}
return (0) ;
}
But no visible changes. Nothing really gets reset. So instead of (1) I tried ...
2)
NSWindow *window = [[[NSApplication sharedApplication] windows] firstObject];
NSPoint newOrigin;
newOrigin.x = 400;
newOrigin.y = 100;
[window setFrameOrigin : newOrigin];
Still nothing. Then instead of (2) I tried:
3)
NSWindowController* controller = [[NSWindowController alloc]
initWithWindowNibName:#"MainMenu"];
[controller showWindow:nil];
Great. Now it's spitting out something I don't understand, especially since I'm new to Obj-C:
2020-02-08 21:53:49.782197-0800
tryout_macApp2[14333:939233] [Nib Loading] Failed
to connect (delegate) outlet from
(NSWindowController) to (AppDelegate): missing
setter or instance variable
I remember dicing around with an ApplicationDelegate, with CGSizeMake(), etc., but it just made the experience really inundating and frustrating. Nothing happened. Then there are NSView, NSViewController, and other classes, which is really mindboggling and begs the question: why are there so many classes when all I want to do is override the preset origin of the window and the dimensions preset by the MainMenu.xib file? (By the way, this project is derived from a Window Application project provided by Xcode.)
I really can't think of anything else to add to give you the entire picture of my predicament, so if you feel that something is missing, please chime in.
[Edit:] Moving forward to phase 2 of my project here: How do I paint/draw/render a dot or color a pixel on the canvas of my window with only a few lines in Obj-C on Mac OS X using Xcode?.
The short answer is that main() is too early to be trying to do this. Instead, implement -applicationDidFinishLaunching: on your app delegate class, and do it there. Leave main() as it was originally created by Xcode's template.
After that, I would say to obtain the window (if there's only going to be one main one), it's better to add an outlet to your app delegate and then, in the NIB, connect that outlet to the window. Then, you can use that outlet whenever you want to refer to the window.
Also, make sure that Visible at Launch is disabled for the window in the NIB. That's so you configure it as you want before showing it.
For a more complex app, it's probably better to not put a window into the Main Menu NIB. Instead, make a separate NIB for the window. Then, load it using a window controller object and ask that for its window.
I love Objective-C but also feel your pain, it has this testy ability to frustrate you endlessly.
I have not really developed a game but let me try and point you in the right direction. I think you need a UIViewController.
Now each UIViewController has a built in UIView that sort of represents the visible portion of it. You can use this or add a UIView and use that, whichever depends on your implementation. For now I'd suggest add a separate UIView and use that rather. Once you're comfortable you can then move the implementation to the UIViewController's view if you need to.
Anyhow, for now, create a UIView subclass, say MyGame or something, as for now all your code will end up there.
To do all of the above is not easy, especially if its the first time. If you can follow some tutorial it will be great. Even if the tutorial just adds a button, you can use it and replace the button with your view.
Anyhow, now that you've got that running and the view you've added shows up in green or some other neon colour just to verify that you can indeed change its properties, you're good to go.
Now you start. In MyGame, implement the
-(void)drawRect:(CGRect)rect
message, grab the context through
UIGraphicsGetCurrentContext
and start drawing lines and stuff on it, basically the stuff I understand you are interested in doing. You can also, through the same context, change the origin of what you are doing.
Hope this helps.
I'm looking at an Objective-C sample project at:
https://developer.apple.com/library/mac/samplecode/FunkyOverlayWindow/Introduction/Intro.html
I don't understand how main.m passes control to the other classes/objects.
Let me explain what I'm trying to do. I'm trying to build that app, step by step. My first step was to get main.m to compile. I believe this is the starting point for most applications. I don't know what to add/compile next, because main.m does't mention/refer to any of the classes in that project.
Any ideas?
The entry point in Objective-C programs is the function called main(). See the following code from main.m
int main(int argc, char *argv[])
{
return NSApplicationMain(argc, (const char **)argue);
}
The main() function begins by calling a function named NSApplicationMain() that is cocoa framework function and not shown to user, which is functionally similar to the following:
void NSApplicationMain(int argc, char *argv[])
{
[NSApplication sharedApplication];
[NSBundle loadNibNamed:#"myMain" owner:NSApp];
[NSApp run];
}
Thus, [NSBundle loadNibNamed:#"myMain" owner:NSApp] is called by NSApplicationMain function and in this time, #"myMain" is identifier for Main Interface(as MainMenu.xib within sample source)
finally MainMenu.xib is run, and then OverlayWindow that is main window of MainMenu.xib will be run.
I appreciate, and understand perfectly, how you could not want to use Xcode - especially if, like me, you come from a Unix/GCC/Makefile environment. However, there are some things that are really quite hard to do without the Xcode GUI and linking buttons and widgets to your code is one of those things.
In Xcode, you click the button in your user interface and drag a line to the code attached to it - these are called IBOutlets and IBActions. So, my first suggestion is to Google those two terms.
Secondly, I recommend you watch this video on YouTube to understand what the XIB/NIB is and how to link it to your code and then you can try and do it without Xcode once you get the concept... video showing linking buttons to code.
There is a gap in my understanding of blocks and ARC that I would like some help with. I have received a crash report from a tester which originates from an animation completion block. The crash is as follows:
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0xf0000010
This is happening inside the following animation completion block (using animateWithDuration:animations:completion:). I am swapping two view controllers using a temporary variable:
{
[current wasMovedOffScreen];
PlayerViewController *temp = current;
current = next;
next = temp;
}
next = temp; is the line on which the crash occurs. The duration of the animation is 0.3 seconds.
This code is executed inside a view controller which acts as a container for two other view controllers, used to simulate the user passing through a list of objects, like a navigation controller, but with a number of customisations that meant a navigation controller was not appropriate for me.
The two child view controllers are called "current" and "next" - as you can guess the current controller displays the current content, and the next one is used to animate in the new item when the user moves through the list.
If I change the block so that temp is declared in the implementation (along with current and next) the crash stops. However it seems unnecessary to me to have to hold a class ivar for something that is by definition local and temporary.
So,
Why is temp apparently released by ARC during this process?
What would be the correct way to implement this? Is there some lifetime qualifier I should add to the block implementation?
Why could I not reproduce the crash on my own device or in the simulator? Both devices were iPhone 4 running the same version of iOS (5.0.1).
I don't think the problem is actually ARC in this instance. Variables (including local variables) are __strong by default, so current should be retained in the first assignment.
http://developer.apple.com/library/mac/#releasenotes/ObjectiveC/RN-TransitioningToARC/_index.html - see section 'Variable Qualifiers'
If you've got a crash you can't reproduce yourself, it's always going to be difficult to fix. Have you tried running the app outside of the debugger? Having the debugger loaded can sometimes change behaviour. Also, try debugging with NSZombieEnabled, or Instruments.app's zombie tool.
And if it really keeps you up, since you're only alternating between two views you don't actually need all this current / next business. Well, you can keep track of current if you want... but here's the idea.
If you have properties:
#property (nonatomic, strong) UIViewController *controller1;
#property (nonatomic, strong) UIViewController *controller2;
#property (nonatomic, weak) UIViewController *currentController;
Then you can have a method:
- (id)swapControllers {
// use the current controller to figure out what the next controller is, so
// you don't have to do the two way swap. do in the completion, if you like.
UIViewController *nextController = ([self.currentController isEqual:self.controller1]) ? self.controller1 : self.controller2;
[UIView animateWithDuration:.25
animations:^{
// TODO: some animations on currentController and nextController
} completion:^(BOOL finished) {
[self.currentController wasMovedOffScreen];
self.currentController = nextController;
}];
}
As for your original issue, ARC knows how to handle blocks pretty well - if you weren't using it, I might ask if you'd forgotten to copy a block declared elsewhere, then stored, then passed in as the completion handler. Since that's not the case, and since you haven't been able to get a repro... I'm guessing that you might be running a beta sdk - there have been some ARC bugs in beta releases of late. If that's so, you might try building with the last stable release, and see if you get better results.
I persist to be a little confused about when to put something in a viewController and when to put it inside an AppDelegate, and if it is ok to reference viewController methods from the AppDelegate (i know you can but that doesn't mean it is encouraged).
Really, I wouldn't be confused if it weren't for all this multi-tasking stuff that seems to complicate the concepts for me. First, if anyone knows of a good, thorough and easy to read overview of how to deal with multitasking, I'd love to know.
Here's the deal: there are things I need my app to do when it loads, whether loading fresh or loading from the background. Stuff like perform a network reachability test, setup the interface based on data received from the internet, and this or that.
One of my main questions relates to how the viewcontroller's view interacts with background states. If the app resumes from the background and the view is immediately present without loading, then I assume it is still in memory and I have verified that viewDidLoad was not called with a basic NSLog. So, is it safe to say that any and all objects retained by my viewcontroller (like the data models and all subviews) are thus still in memory? If not, what's the best practice discovering which objects need to be re-loaded, what are still there, etc?
I think it's safe to assume that the standard memory management rules apply, even in a multi-tasking environment. That means that your controller, and anything you've got a reference to in your controller should still be valid until either:
You explicitly deallocate your controller/objects
Your app terminates
It seems like your assumption is that the system is going to mess with your objects behind your back, which (I hope) can't happen. Those methods are there in the app delegate in case you want to explicitly do anything when those particular events occur.
Regarding the viewDidLoad question, you could implement viewDidUnload or check the isViewLoaded method to make sure your view wasn't unloaded due to a low memory condition or otherwise. More on this in the UIViewController documentation.
One way to approach this problem is with lazily-loaded properties. In your .h file:
#interface YourViewController : NSObject
#property (nonatomic, retain) NSArray *exampleObject;
#end
And in your .m file:
#implementation YourViewController
#synthesize exampleObject = _exampleObject;
- (NSArray *)exampleObject {
// reload only if necessary
if (!_exampleObject) {
_exampleObject = [[NSArray alloc] init];
// do whatever other setup you need to
}
return _exampleObject;
}
#end
In an Xxode project that has a lot of .h and .m files, how do you determine which file gets executed first?
The file that contains int main(int argc, char * argv[]); will get run first, since the main() function is the first function to get run. In pretty much every Xcode template project, that file is called "main.m".
edit
Usually, the main() function of a Cocoa Touch app is this:
int main(int argc, char *argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, nil);
[pool release];
return retVal;
}
(Substitute NSApplicationMain() for UIApplicationMain() and remove the autorelease pool if you're writing a Mac app)
edit #2
I'm only interested in the file that gets run first from the classes folder
The simple answer is "The application delegate", then everything else.
The technical answer to that is that any objects in your MainMenu.xib (Mac) or MainWindow.xib (iOS) file will be instantiated first. Usually the objects in that file will be instantiated in the order in which they appear, but I don't think that's guaranteed.
So if you 3 have custom top-level objects in your MainWindow.xib file, then they'll all be instantiated at the same time (within reason). First their initWithCoder: methods will execute, then some time later their awakeFromNib methods will execute (which usually the safest "starting point" for that object).
The application delegate launch methods will happen somewhere along in there too (I believe in between initWithCoder: and awakeFromNib).
Cocoa and Cocoa-Touch apps are completely event-driven. It is not that the order the methods are executed can be understood by reading the source code files.
As Dave explained, the entry of the program is at the main function in main.m. It immediately calls UI/NSApplicationMain.
NS/UIApplicationMain is a function provided by Cocoa(-Touch). It watches the user interaction, and fires events accordingly.
For example, when the user clicks a button, the system calls automatically what you provided as the action method specified in the xib file.
Another example is the drawRect: method you provide: it's called when the system decides to draw an object onto the screen. It's very important that you don't actively draw to the screen. The system asks you to draw the screen, and you respond.
One important set of events are the ones which are called at the beginning of the program, like applicationDidFinishLaunching: or the ones which are called when a view is loaded from the xib file, viewDidLoad.
Aaron Hillegass made a great analogy of the situation: you're KITT and respond to Michael Knight's order, not the other way around. Well you need to be over certain age to understand this analogy...
Another thing that should get run first is in an App Delegate (Defined like so: NSObject <UIApplicationDelegate> and setup in the nib) the method applicationDidFinishLaunching
This is obviously not technically the first thing to be run. The main loop and whatever is in UIApplication will be executed first, but in general this is good starting point for iOS applications and the first place you really have control.
You can also create a class called like "Controller" and drag an NSObject into your nib and set the class to Controller. Then the method awakeFromNib will be called.
Either of these should be fine to set up your app.