Search
Rich's Mad Rants
Powered by Squarespace
Friday
Oct052012

FMSAlertManager added to GitHub

Ok, I've tried to create my first GitHub repository. I think I did something wrong, however. It lists the project as Javascript, not Objective-C. If you know how to fix this--or if you see anything else I screwed up--please let me know.

Still, this is an updated version of the block-based UIAlertView API I described in this post.

After using the code in my own projects, I made a number of small improvements. For example, it can now auto-close alert views when the app goes into the background, and you can get access to the alert view instance so you can programatically press buttons or dismiss it. I've also created Docset documentation.

Check it out here: https://github.com/rikiwarren/FMSAlertManager.

Monday
Sep242012

Advanced Storyboard Techniques

I'm not afraid to admit it. I love Storyboards. They were one of the new features in iOS 5 that greatly changed how I develop applications. I must say, it's nice to see how they continue to evolve and mature in iOS 6.

Not surprisingly, I heavily emphasized Storyboards in my book, Creating iOS 5 Apps: Develop and Design. However, it turns out they are a lot more flexible and powerful than I originally gave them credit for. So, without further ado, here's a shotgun blast of advanced Storyboard tips and techniques. Some of it applies to iOS 5; however, much of it is new for iOS 6. 

Segue Timing

Understanding the timing behind segues is important to using them successfully. When a new segue fires, the following steps occur:

  1. The destination view controller is instantiated (initWithCoder: is called)
  2. The source view controller's prepareForSegue:sender: method is called
  3. The destination view is loaded
    1. The destination's viewDidLoad is called
  4. The destination view appears
    1. The source's viewWillDisappear: is called
    2. The destination's viewWillAppear: is called
    3. The source's viewDidDisappear: is called
    4. The destination's viewDidAppear: is called.

When a view controller is disposed (dismissing a modal view or popping a navigation view), we go through the following sequence:

  1. The source's viewWillDisappear: is called
    1. The destination's viewWillDisappear: is called
    2. The source's viewWillAppear: is called
    3. The destination's viewDidDisappear: is called
    4. The source's viewDidAppear: is called.
  2. The destination view is deallocated

There are a few key point worth noting. Every time we fire off a segue, we will instantiate (and then later deallocate) a new copy of the destination's view controller. If you need to preload or cache your view controllers, you will want to manage the view controllers programmatically.

Also, when the prepareForSegue:sender: method is called, the destination controller's view has not yet loaded. You cannot access any of that controller's outlets. The best practice is to just pass whatever data you wish to the destination controller--storing the data in properties as necessary, and then let the destination use its properties in its viewDidLoad method to configure its own outlets.

Finally, our code can alter the order--sometimes in unexpected ways. For example, if we touch the destination's view property before its view has loaded, we will force the view to load. This can cause the destination controller's viewDidLoad method to fire earlier than we expected. Just another reason to leave the view (and everything inside the view) alone. 

Loading New Views Programmatically

Let's say we want to draw a segue to a view controller that builds its views programatically (by overriding its loadView method). Originally, I thought this was not possible; that we would have to manage the transition to this view controller entirely in code. Turns out, we can have the Storyboard automatically instantiate and deallocate our view controller--even though we're not designing its view in the Storyboard. There's just one small trick to make it all work:

  1. Make a UIViewController subclass.
  2. Implement its loadView method.
  3. In the Storyboard, drag out a view controller as normal.
  4. Set the view controller's class to your subclass.
  5. Set the segue to new view controller.
  6. Now, comes the secret sauce--delete the view from the view controller's scene.

ProgramaticViewController

When the segue fires, the view will be created using the slightly modified sequence:

  1. The destination view controller is instantiated (initWithCoder: is called)
  2. The source view controller's prepareForSegue:sender: method is called
  3. The destination view is loaded
    1. The destination's custom loadView method is called
    2. The destination's viewDidLoad method is called
  4. The destination view appears
    1. The source's viewWillDisappear: is called
    2. The destination's viewWillAppear: is called
    3. The source's viewDidDisappear: is called
    4. The destination's viewDidAppear: is called.

We can then instantiate and layout our views and subviews in the loadView method, as normal. We can even draw segues from this view controller to other view controllers in the Storyboard--though, since we don't have any controls to link the segue to, we will need to trigger these segues in code (draw the segue from the controller itself, then have the controller call performSegueWithIdentifier:sender: to trigger the desired segue).

Loading New Views Using Nibs

Being able to include programatically-designed view controllers is cool--but it would be even better if we could load the view controller from another nib file. This is basically the same as the previous example, except instead of implementing the loadView method, we provide a properly named nib file. 

Note: There's no way to specify the nib file for our view controller. However, whenever no name is provided, UIViewController searches for a nib whose name matches the view controller's class name. So, if we have a view controller called MyCoolViewController, it would look for MyCoolViewController.xib. As long as the names match, the nib will load as expected.

Loading New Views From Other Storyboards

For me, this is the holy grail for Storyboards. I'd love to be able to create a Storyboard that is a composite of other Storyboards. Ironically, there doesn't appear to be any way to do this from within Interface Builder. The "add the view controller, then delete the view" trick won't work. Don't get me wrong, we can still string together Storyboards, but we have to stitch everything together in code.

To segue to another Storyboard:

  1. Get the Storyboard by calling UIStoryboard's storyboardWithName:bundle: method.
  2. Get the view controller by calling instantiateInitialViewController (to get the initial view controller) or instantiateViewControllerWithIdentifier: (to get an arbitrary controller from within the Storyboard).
  3. Present the view controller (e.g. presenting it modally or pushing it onto a navigation controller).

It's a bit annoying that we have to do all this in code--but it's still a nice trick to master. There are often good reasons to split a Storyboard into separate, independent modules. For example, you might have different people working on different parts of the UI. Or, you might just want to prevent a single Storyboard from becoming overly complex.

On the other hand, many of the benefits of using Storyboards (e.g. drawing segues or unwinding segues between scenes) won't work between Storyboards. So, there are also good reasons to keep everything together. I guess the bottom line is, don't split things up just for the sake of splitting things up. But, don't be afraid to split them up either.

Unwinding Segues

In iOS 5, we could easily pass data forward along the segues--but we didn't have an automated way of passing data back. To get around this, we typically used a delegate pattern. The destination view would declare a delegate protocol. The sending view would then implement this protocol. In the prepareforSegue:sender: method, the sender would assign itself as the destination's delegate. The destination controller could then call any of the delegate methods--allowing us to send arbitrary messages back upstream.

While this is still a powerful design pattern, iOS 6 has partially automated the procedure--letting us more easily create a wider range transitions. Basically, we can define how the Storyboard will unwind any given segue.

In the controller you wish to return to, implement a method that returns an IBAction, and that takes a single UIStoryboardSegue argument:

- (IBAction)returnActionForSegue:(UIStoryboardSegue *)returnSegue {

// do useful actions here.

}

Now, in the scene we wish to unwind, control-drag from the UI element that will trigger the return to the scene's exit icon (the green icon in the dock). In the popup menu, select the previously defined return item.

DragToExitIcon

Note1: We cannot simply create a segue from the button back to the desired scene, since that will instantiate an entire new copy of the scene. Instead, we want to return to the existing copy of the scene.

Note2: We don't necessarily need to return to the previous scene. This is particularly handy if we are pushing a number of scenes into a navigation controller, and want to pop back to an arbitrary point in the middle--or if we're mixing modal and push segues, and want to unwind to any point in the chain.

The Details

The system will search for the return destination by calling canPerformUnwindSegueAction:fromViewController:withSender: on the current view controller. By default, this method simply checks to see if you have a method whose selector matches the return action. If it does not find a match, it will search the parent view controller, and will continue searching up the chain of parent view controllers until it finds a match.

We can override canPerformUnwindSegueAction:fromViewController:withSender: to add more control to the search--for example, if we define the same return action in multiple view controllers, and then override this method to make sure we return to the correct one.

Furthermore, container view controllers are responsible for searching their child view controllers. Apple's built-in container views all do the right thing. For example, a UINavigationViewController will search through the view controller stack from the top down, starting just below the exiting controller.

If we want our own custom container controller to search through its child view controllers, we need to override viewControllerForUnwindSegueAction:fromViewController:withSender:. In this method, we should iterate over our child view containers, calling canPerformUnwind… on them. If we find a match, we should return it. If not, we should call the superclass's implementation and return the result.

Once it finds a match, the system then calls segueForUnwindingToViewController:fromViewController:identifier: on the destination controller. Again, we need to override this method for our custom container controllers, returning a segue that performs the animation and other necessary steps to unwind the view controllers.

Then, the system calls prepareForSegue:sender: on the exiting view controller, followed by calling the exit action on the destination view controller. We can use either of these steps to pass data back to the return point.  Finally the system triggers the segue.

This is a bit complicated, so let's walk through a concrete example. Let's say I have a multi-page form that I want users to fill out. To use the form, I modally present a navigation controller. The navigation controller's root view controller is the first page of my form. As the user fills out each page, I push a new one on the navigation controller. On the final page I have a done and a clear buttons. 

The done button should take the user back to my original view (the navigation controller's presenting view controller). The clear button should just take them back to the beginning of the form.

To set this up, I need to implement two methods. formCompleted: should be implemented on the view controller that will present the form, while clearForm:  should be implemented on the first page's view controller. I then draw connections from the done button to the formCompleted:  exit action, and from the clear button to the clearForm:  exit action.

When the clear button is pressed, the system searches the final page's view controller for the clearForm:  method. It doesn't find a match, so it searches the parent view controller--our navigation controller. The navigation view controller manages the search of its children. Basically, it starts at the top of the stack, just below the last page controller, and works its way down.  Eventually it finds the method on its root view controller--so it returns that controller as the destination.

The navigation controller then creates a segue where it pops to the destination with animation, and passes this back to the final page controller. The final page's prepareForSegue:sender: is called. The root controller's clearForm: method is called. Then the segue is triggered.

If I press the done button, it will search the last page, then search the navigation controller. This time, the navigation controller doesn't find anything, so the system will search its presenting controller. Here a match is found. The presenting controller automatically creates a segue to dismiss the modal view controller with the correct animation. Again, the final page's prepareForSegue:sender: is called. The root controller's formComplete:  method is called, and the segue is triggered.

As you can see, the ability to unwind segues allows us to easily return to any existing view controller in our view controller hierarchy. in iOS 5, these transitions required a considerable amount of custom code--the more complex the transition, the more complicated the code. Now, these transitions can largely be automated, saving us time and effort, and hopefully avoiding bugs.

Container Views

The iOS SDK has always had two different types of view controllers. The most basic type are the content view controllers. These are the view controllers that we create to display our content. However, the system also had a number of pre-build container view controllers: UINavigationControllerUITabBarController and others. These controllers usually provided a minimal user interface of their own (the navigation or tab bar itself). Instead, they manage a number of content view controllers for us.

With iOS 5, Apple gave us the ability to create our own content view controllers. This is a surprisingly powerful technique, letting us easily break a complex user interface into several independent parts. However, all of the management of these sub view controllers and their views had to be done in code.

Now, with iOS 6, we can add a container view to our Storyboards (though, not our nibs). Simply drag a Container View object from the library and place it on one of your scenes. Interface Builder will automatically add a content view controller for the container view, with an embed segue--though you can change the segue to embed any view controller in the Storyboard.

AddingAContainerView

When the parent view controller is loaded, this segue will fire, and the child view will also load. Timing-wise, the child view will finish loading before the parent's viewDidLoad method is called, so you can safely configure the child from within the parent's viewDidLoad.

Unfortunately, this only gets us about half way to our goal. Don't get me wrong. It's a cool new feature, and I love the fact that I can graphically lay out my container views. However, I typically use container views because I'm going to be dynamically swapping the content within those views. And this may or may not be possible to set up in the Storyboard.

If the child views are responsible for swapping themselves around, then you can simply draw segues from one child to the next as usual. There are no problems, and everything just works (though, see the note on unwinding segues above).

However, there's no way to have the parent view controller drive these segues. To really let the parent freely drive arbitrary transitions between all the possible child controllers, you'll have to manage everything in code. Here's a common work flow:

  1. Instantiate the new child view controller.
  2. Set the frame of the new child controller's view to the existing container view.
  3. Animate the transition between the views.
  4. Remove the old child controller from the parent.
  5. Add the new child controller to the parent.

Assuming you only have a single container view--and thus only a single child controller--you could implement this as shown below. Obviously, if you want to preload or cache the view controllers, or if you have multiple container views, your implementation may become much more complex. 

// Assumes we have a MyChildViewController.xib, or that 
// MyChildViewController programmatically creates its views.
MyChildViewController *controller = [[MyChildViewController alloc] init];

// Assumes we only have 1 child view controller
UIViewController *currentChild = [self.childViewControllers lastObject];
controller.view.frame = self.containerView.frame;

[UIView
 transitionFromView:self.containerView
 toView:controller.view
 duration:0.25f
 options:UIViewAnimationOptionTransitionCrossDissolve
 completion:^(BOOL finished) {
    [currentChild removeFromParentViewController];
    [self addChildViewController:controller];
}];

 

If you want to load another controller from within the Storyboard, you would modify the code as shown below:


UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil];

// Must match the controller's Storyboard ID
SecondChildViewController *controller =
[storyboard instantiateViewControllerWithIdentifier:@"SecondChild"];


// Assumes we only have 1 child view controller
UIViewController *currentChild = [self.childViewControllers lastObject];
controller.view.frame = self.containerView.frame;

[UIView
 transitionFromView:self.containerView
 toView:controller.view
 duration:0.25f
 options:UIViewAnimationOptionTransitionCrossDissolve
 completion:^(BOOL finished) {
    [currentChild removeFromParentViewController];
    [self addChildViewController:controller];
}];

 

Be sure to set the matching Storyboard ID in the Identity Inspector for the child view controller--otherwise this won't work.

And that's it for now. I hope you found at least some useful information in here. Also, please let me know if you find any other cool Storyboard tricks.

Thursday
Sep202012

Notes about Xcode 4.5

Here are a few things that may not be immediately obvious about Xcode 4.5

Retina 4 support

So, you have an existing app, and you want to get it ready for the soon-to-be-released iPhone 5. You've already gone through all the work to make sure your views auto-resize properly (using either the old autoresize mask or iOS 6's autolayout features). So, you bring up your project in Xcode. Switch the simulator over to the 4-inch device (in the iOS Simulator's menu: Hardware > Device > iPhone (Retina 4-inch)), and run your app.

Unfortunately, nothing happens. Your app is still displayed with the black bars at the top and bottom.

WithBars

Ok, the problem is, you don't know the secret handshake. Simply add a retina-4 launch image to the project. This will (magically) enable the full 4-inch mode. You can either add it through the project's Target > Summary settings, or simply add a file named Default-568h.png to the project (remember, the iPhone 5's screen is 568 points tall--which translates into 1136 pixels, hence the name). In either case, the launch image needs to be 640 x 1136 pixels.

WithoutBars

Note: Xcode 4.5 will automatically add black launch images to new projects. This means the full 4-inch mode will be enabled by default on any new projects you create.

I'm not a huge fan of this as a design choice. While it's a slick way to automatically check for 4-inch support in legacy projects--there's a little too much magic going on in the background, with no obvious connection between cause and effect. I'd rather have an explicit plist setting that we explicitly needed to toggle on or off. But, that's just me.

Literal Syntax Support

I'll admit it, I'm loving the new literal syntax. Here's a mini review, in case you're not familiar with it.

We've always had literal syntax for strings. To create a new string, we'd simply append the at-sign before a C-style string, as shown below:

NSString *myString = @"this is a literal string"

Now, we have support for literal NSNumbers, NSArrays and NSDictionaries.

NSNumber *aNumber = @42;

NSNumber *anotherNumber = @3.14;

NSNumber *alsoANumber = @YES;

NSNumber *thisEvenWorks = @([self aMethodThatReturnsANumber] + 2.0);

NSArray *array = @[@"First", @"Second", @"Third"];

NSDictionary *dict = @{@"FirstKey":@"I'm the first value",

                      @"SecondKey":@"I'm the second value"};

Apple's also added syntax to support subscript notation for accessing the values in arrays and dictionaries:

NSString *second = array[1];

array[1] = @"I'm the new second element";

NSString *secondValue = dict[@"SecondKey"];

dict[@"SecondKey"] = @"I'm the new second value";

We can even add subscript support to our own objects. We just need to implement any or all of the following methods:

// integer-based getter -- like NSArray

- (id)objectAtIndexedSubscript:(NSUInteger)index

// Object-based getter -- like NSDictionary

- (id)objectForKeyedSubscript:(id)key

// integer-based setter -- like NSArray

- (void)setObject:(id)anObject atIndexedSubscript:(NSUInteger)index

// Object-based setter -- like NSDictionary

- (void)setObject:(id)object forKeyedSubscript:(id < NSCopying >)aKey

Note: We are implementing these methods, not overriding them. This means that a) Xcode's autocomplete will not help us get the method names correct, and b) if you don't declare these methods in your header file, you'll get compilation errors.

We've been able to use the literal syntax for iOS development since Xcode 4.4--but we couldn't use the subscript notation, since the support had not yet been added to the objects themselves. However, now that Xcode 4.5 is out, we can use both. But here's the scary bit. As long as you use the iOS 6.0 SDK (which is the default), you can deploy the project to earlier versions of the OS, and subscript notation still works.

I've tested this on an iOS 5.1 phone. I was able to use subscript notation to get and set values into an NSMutableArray. I was also able to add subscript support to my own objects, and use it without any problems. That's some scary dark voodoo shit, let me tell you.

However, I'm just glad I can use literal syntax and subscripts without worrying about my deployment targets.

Deployment Targets

And one last note about deployment targets. With Xcode 4.5, Apple's dropped support for anything older than iOS 4.3. 

All I can say is, about damn time!

Tuesday
Sep182012

Advanced Profiling with the DTPerformanceSession Framework

I spent part of the weekend playing with Instruments and the DTPerformanceSession framework. This framework offers a number of really cool features for fine-tuning performance testing on both iOS and OS X applications. Unfortunately, there's not a lot of documentation out there. In fact, the only thing I could find is the "New Features in Instruments 4.0" user guide.

DTPerformanceSession offers a rich set of features for gathering and recording information from a restricted set of instruments (currently limited to Time Profiler, Allocations, leaks and the Activity Monitor). We can then save the data as a .dtps bundle, which can be opened by instruments. While this opens up possibilities of creating customized test harnesses--it's honestly too much work for most situations. Fortunately, there's an easier option.

DTPerformanceSession also lets us add custom flags to our app during our regular performance testing. We can then access these flags from within instruments, using them to focus in on a very specific portion of our code.

To add signal flags, first we need to add the DTPerformanceSession.framework to our application. We then need to import the DTSignalFlag header file, as shown below:

#import <DTPerformanceSession/DTSignalFlag.h>

Note: for OS X, I also needed to manually set the Runtime Search Path. I'm not sure why this wasn't set automatically--it may be because I was using a version of Xcode installed in a non-standard location. If you run into strange errors, simply copy the Framework Search Path location over to the Runtime Search Path in the target's Build Settings.

We can then add signal flags to our app by calling DTSendSignalFlag(). This method takes three arguments. Honestly, I'm not 100% sure what these arguments do. There's no real documentation--not even in the header file. However, based on the header file and Apple's sample code,  The first appears to be a user id string. This string shows up in Instrument's flag table, and can be used to identify particular signals. Apple's sample code uses a unique C string, beginning with the developer's reverse domain name notation.

The second argument defines the type of signal. Here, we can use DT_POINT_SIGNAL to flag a single location in time. Alternatively, we can use DT_START_SIGNAL and DT_END_SIGNAL to mark off a block of code.

The third argument determines whether or not it adds a backtrace. All of Apple's samples simply passed in YES, so I did the same. 

So, to mark off a section of code, I simply added the following code to my project:

DTSendSignalFlag("com.freelancemadscience.MyProject.importantBlockStart", DT_START_SIGNAL, TRUE);

// The code we're testing goes here...

DTSendSignalFlag("com.freelancemadscience.MyProject.importantBlockEnd", DT_END_SIGNAL, TRUE);

Now, whenever I run instruments, these flags will be added to my project. Unfortunately, they do not show up by default. Instead, we must open the flag table, by selecting Window > Manage Flags….

Opening Flag Table

This will bring up a list of all the flags in this profiling run. The table shows the flag's name (the user id string we used), a timestamp, and for the start/end flags, it will list a duration. We can also expand the start/end flags, if we wish. 

To display the flags in Instrument's timeline, click on the Displayed Flags button, and select Symbol Flags

Turning On Signal Flags

Even more importantly, we can set Instrument's range filter to the start and end flags by selecting the start flag in the table, then clicking on the cock icon at the bottom of the table.

Time Range

This will highlight the range between the selected start flag and its corresponding end flag, limiting the profiling information to the method calls and allocations that actually occurred within this range. For example, below, I am viewing the Time Profiler data for the code between the selected start and end flags. During this range, my app spent 92% of its time in the test2() method.

As you can see, this gives us a lot of fine control over exactly what we're looking at, letting us focus in on a known problem areas.

Filtered Time Profile

 

 

Friday
Aug312012

To Declare or Not To Declare

Between teaching an intro to iOS class last week, and Colin Wheeler's blog post "Cover up those ivars", I've been thinking a lot about what we declare in Objective-C, and where we should declare them.

Background

Back in the day--and by that I mean just over the last few years--we had to explicitly declare almost everything. For example, we had to declare our iVars in the header file. The compiler needed to know how much memory our class used when it went to make a subclass.

Now, I don't know about you, but I like to keep my header files as clean as possible. Ideally, they should only contain the properties and methods that outside classes are allowed to call. Everything else should be hidden away. Unfortunately, having the iVars in the header file exposed some of my class's implementation details. Worse yet, if developers could see them, they were likely to try and use them. This made it harder to modify my implementations without also breaking their code.

Over time, however, things changed. If we're using a modern OS (iOS, or a 64-bit version of OS X), we don't need to have our iVars in the header file anymore. We don't even need to explicitly declare them. Xcode can auto-generate appropriate iVars from our property declarations.

Even better, with Xcode 4.4, we no longer need to explicitly call @synthesize for our properties. Again, Xcode will handle it (if necessary). The rules on when Xcode auto generates iVars gets a little more complicated--but generally it does the right thing. If it's not synthesizing methods (for example, because I provided custom getters and setters), it won't generate the iVar.

Class extensions also help us move things out of our header files. We can declare private properties, iVars and methods there. Of course, Objective-C doesn't really offer support for private methods, so developers just make methods private by hiding them. If it's not declared in the header file, it should be considered private.

Now, nothing will stop you from calling the method, if you somehow discover its name--but given Objective-C's dynamic and reflexive nature, there's no way anyone could really stop you anyway. Instead, the public/private divide is really about clearly expressing developer intent. If it's not in the header file, you shouldn't use it, period.

Additionally, like C, we don't actually need to declare our methods. We could simply create a "private" method by just defining them in the @implementation block. As long as our methods are defined before they are called, everything works. Unfortunately, this isn't always possible (e.g. mutually recursive methods).

However, with Xcode 4.4, Apple added considerable support for undeclared private methods. Specifically, the compiler is now smart enough to find the method regardless of its position in the implementation file. So, we no longer need to worry about ordering.

What I Declare

Or, more appropriately, what do I leave undeclared.

I never declare iVars directly. Instead, I will create public or private properties and let them generate the iVars for me. If I need to let a subclass access a private property, I can always re-declare it in the subclass and then use the @dynamic property directive in my @implementation block.

Of course, I get a little worried when I do this. It seems like code smell--an indication that something might be wrong with my design. Why does my subclass need access to that property? Should the property actually be a public property? Should I redesign my class hierarchy? Is there maybe a better way to solve this problem (e.g. using aggregation instead of inheritance)? Most of the time, I find that redeclaring the property is not the correct solution--still, it's nice to know that it's there.

I'm actually a little bit surprised by how many developers continue to manually declare their iVars. In many cases, it looks like they're doing it out of habit…like they're afraid to trust their compiler.

Oddly enough, explicitly declaring iVars can actually introduce bugs. If the iVar and property names don't match up correctly, Xcode will happily create a second iVar for you. The property will connect to the auto-generated iVar, not the one you explicitly declared. While the additional memory usage is unlikely to cause any serious problems, it's at least somewhat sloppy. Besides, worse things happen if you start accessing the iVar directly. Then the code that accesses the iVar and the code that uses the property might end up modifying two different variables. These bugs can be very confusing and hard to find.

I've also stopped using @synthesize. It was always somewhat tedious--especially since I always gave my iVars names that began with an underscore. Now, Xcode does that by default, so it saves me a bit of typing.

Oddly, I still declare private methods in my class extension. This used to be rational. I didn't have to worry about how I ordered my method definitions--which gave me more freedom when it came to organizing my code. Also, Xcode seemed to respond better to explicitly declared methods. Code completion seemed to work better, and I seemed to get better warnings and errors.

However, with Xcode 4.4 none of that seems to matter anymore. I ran a few tests, and Xcode seems to handle undeclared and declared private methods equally well. So, I'm starting to rethink this. I still like the explicit declarations, but I'm worried that I may be clinging to them out of inertia.

Where I Declare it

Basically, whenever possible I try to keep things out of the header file. If other classes need access to it, I put it in the header. If not, I hide it away. If in doubt, I err on the side of hiding things. I can always expose them later.

For properties, it gets a little more complex. For example, many times outside classes may need to read data from my class, but won't actually need to change it. In that case, I typically create a readonly property in the header file, and a readwrite property in the class extension. Inside my class, I can read and write to the property as normal--but other classes can only read the data.

Note, the only attribute you can change when redeclaring properties is the readwrite/readonly attribute. This means we need to include a storage attribute (strong, weak, copy, assign) in our readonly property declarations. Yes, that leaks implementation details--but its not worth losing sleep over.

However, I get a bit extreme when it comes to pulling things from my header files. When I'm using a view controller combined with a storyboard/nib, I will also hide my IBOutlets and IBActions in my class extension.

Now, this gets a bit odd, conceptually. Technically, yes. These are elements that--by definition--need to be accessed outside my class. However, in general, they should only be accessed by the system as it loads the nib (remember, storyboards compile down into nib files). And the system can find it just as easily in my class extension. No one else needs to know about it.

Our view controllers manage one or more views to display data and to respond to user interactions. We shouldn't be concerned with how the view controller handles those tasks. Instead, we should just pass it our data, and let it display the data however it wishes.

Or, think about it this way, if I put outlets in the header file, developers (including my future self, when I'm tired or up against a tight deadline) will be tempted to access and modify the outlets directly. This is almost always a mistake. Again, we can pass the data to the controller or call its public methods--but we should let the controller decide when and how it will display the information.

So, what do you declare, and where do you declare it? More importantly, why?

Page 1 ... 3 4 5 6 7 ... 21 Next 5 Entries »