360iDev Presentation this year:
As a contract iOS programmer, I spend about 80% of my time working with other people's iOS code - either working as a part of existing teams or taking over incomplete projects from developers who are no longer around. Along the way, I've gathered a list of the common mistakes I've seen people make, the open-source libraries I've seen people misuse the most, and the really simple code changes that can make huge differences in the reliability and performance of your apps. For each mistake or anti-pattern, I'll have an explanation of the issue with it, and at least one potential remedy or remediation that could be taken.
This talk will have a lot of specific code examples on a number of different topics and technologies, so hopefully everyone will learn something. And hopefully at least something will save you some time.
Note that this will be a very opinionated talk, and I'm quite likely to step on someone's pet pattern, so there may be fireworks.
Beyond Boundaries: Leveraging No-Code Solutions for Industry Innovation
360iDev iOS AntiPatterns
1. "Stop it! That
Hurts!"
Common iOS Anti-Patterns
@CarlBrwn Carl Brown
Missing final vowel (blame Flickr) 2012
2. ObBio
Carl Brown (@CarlBrwn)
iOS Contractor
Teach Intro to iOS classes
Co-Organizer CocoaCoder.org
Started Mobile Programming 2004 (Palm)
Half of team (Nov. '08) that wrote
LIVESTRONG.com Calorie Tracker
2012
3. ObBio
Carl Brown (@CarlBrwn)
iOS Contractor
Teach Intro to iOS classes
Co-Organizer CocoaCoder.org
Started Mobile Programming 2004 (Palm)
Half of team (Nov. '08) that wrote
LIVESTRONG.com Calorie Tracker
2012
4. Why So Much Bad Code?
I live in Austin Texas
2012
5. Why So Much Bad Code?
"Looks like any idiot can make an App"
2012
7. Why So Much Bad Code?
Working with clients in Austin
Where SxSW implies that
any idiot can found App startup
oDesk/Elance/etc are full of idiots
2008 NDA gave idiots a running start
2012
8. Not "Bad Code"?
At least for purposes of this talk...
Braces for 'if' on next line
2012
9. Not "Bad Code"?
At least for purposes of this talk...
Braces for 'if' on next line
Singletons ([Class sharedInstance])
2012
10. Not "Bad Code"?
At least for purposes of this talk...
Braces for 'if' on next line
Singletons ([Class sharedInstance])
Dot Notation (self.foo vs [self foo])
2012
11. Not "Bad Code"?
At least for purposes of this talk...
Braces for 'if' on next line
Singletons ([Class sharedInstance])
Dot Notation (self.foo vs [self foo])
Max nested square brackets ([[[[ ]]]])
2012
12. Not "Bad Code"?
At least for purposes of this talk...
Braces for 'if' on next line
Singletons ([Class sharedInstance])
Dot Notation (self.foo vs [self foo])
Max nested square brackets ([[[[ ]]]])
ARC-less-ness (at least not yet)
2012
13. Not "Bad Code"?
At least for purposes of this talk...
Braces for 'if' on next line
Singletons ([Class sharedInstance])
Dot Notation (self.foo vs [self foo])
Max nested square brackets ([[[[ ]]]])
ARC-less-ness (at least not yet)
Lack of Unit Tests (Unfortunately)
2012
14. "Out Of Scope"
Bad Code
At least for purposes of this talk...
Wrong in any Language
Methods way too long
(Including AppDelegate)
"Stringly Typed" code
Using strings instead of types
or Classes or enums
General Programming 101 Stuff
2012
15. Notice:
All of the code you are about to see
is real.
All of it was either released to users,
sold for real money or posted online.
Variable names have been changed to
protect the guilty.
Some has been edited for brevity.
2012
16. Bad Code
while (self && [self retainCount] >= 0) {
[self release];
}
///Carl's Note: retainCount is NSUInteger
"Tier 1" Bad:
if (--retainCount == 0) [self dealloc];
- (id)initWithTarget:(id)theTarget
{
retainCount = 1;
// We do not retain
2012
17. Bad Code
while (self && [self retainCount] >= 0) {
[self release];
}
///Carl's Note: retainCount is NSUInteger
"Tier 1" Bad:
if (--retainCount == 0) [self dealloc];
- (id)initWithTarget:(id)theTarget
{
retainCount = 1;
// We do not retain
2012
18. Bad Code
while (self && [self retainCount] >= 0) {
[self release];
}
///Carl's Note: retainCount is NSUInteger
"Tier 1" Bad:
if (--retainCount == 0) [self dealloc];
- (id)initWithTarget:(id)theTarget
{
retainCount = 1;
// We do not retain
2012
19. Bad Code
while (self && [self retainCount] >= 0) {
[self release];
}
///Carl's Note: retainCount is NSUInteger
"Tier 1" Bad:
if (--retainCount == 0) [self dealloc];
- (id)initWithTarget:(id)theTarget
{
retainCount = 1;
// We do not retain
2012
20. Bad Code
while (self && [self retainCount] >= 0) {
[self release];
}
///Carl's Note: retainCount is NSUInteger
"Tier 1" Bad:
if (--retainCount == 0) [self dealloc];
- (id)initWithTarget:(id)theTarget
{
retainCount = 1;
// We do not retain
2012
31. Read Good Code
We don't expect people to write in
English before they spend a lot of time
reading it
Programming Languages should be no
different
Read Good Books and Blogs
WWDC videos and Apple Sample Code
2012
32. Read Good Code
We don't expect people to write in
English before they spend a lot of time
reading it
Programming Languages should be no
different
Read Good Books and Blogs
WWDC videos and Apple Sample Code
2012
33. Coding Style
if ([UIImagePickerController
isSourceTypeAvailable:
UIImagePickerControllerSourceTypeCamera])
if ([[UIImagePickerController
availableMediaTypesForSourceType:
picker.sourceType] containsObject:
kUTTypeMovie])
self.cameraButton.hidden = NO;
2012
48. Operation vs Dispatch
I tend to use NSOperations for:
things I'm going to do several times
things that have non-trivial complexity
2012
49. Operation vs Dispatch
I tend to use NSOperations for:
things I'm going to do several times
things that have non-trivial complexity
I tend to use dispatch_async() for things:
with less than 10 or so lines of code
done only once in the App
that won't need to change when spec changes
2012
50. Waiting
[NSThread sleepForTimeInterval:0.5];
! // wait until the operation is completed
!
[(NSConditionLock *) operation.doneLock
lockWhenCondition:kXmlOperationComplete
beforeDate:[NSDate dateWithTimeIntervalSinceNow:
kGiveUpInterval]];
2012
57. Careful with Caching
fetcher = [ nil objectForKey:@"type"];
if (!fetcher) {
fetcher = [[Fetcher alloc] init];
[ nil setValue:fetcher
forKey:@"type"];
[fetcher release];
}
[ fetcher fetchPicturesForDelegate:self
callbackMethod:@selector(picsFound:error:)];
Cache turned out to be nil 2012
58. Careful with Caching
fetcher = [ nil objectForKey:@"type"];
if (!fetcher) {
fetcher = [[Fetcher alloc] init];
[ nil setValue:fetcher
forKey:@"type"];
[fetcher release];
}
[NSZombie fetchPicturesForDelegate:self
callbackMethod:@selector(picsFound:error:)];
So fetcher could Zombify anytime 2012
59. Don't Cache That Way
That's a paradigm from web
programming, not mobile
Good only when you have tons of
memory
and need to serve >1K req/sec
Mobile needs to be Responsive,
but that kind of memory/time tradeoff not
helpful
2012
63. Tags should be a Last Resort
Primarily used for:
Multiple Instances of the same Class
With the same Delegate method
2012
64. Tags should be a Last Resort
Primarily used for:
Multiple Instances of the same Class
With the same Delegate method
Like UIAlertView or UIActionSheet
2012
65. Tags should be a Last Resort
Primarily used for:
Multiple Instances of the same Class
With the same Delegate method
Like UIAlertView or UIActionSheet
also -[MKMapViewDelegate
mapView:viewForAnnotation:]
2012
68. Don't mix read-only and
read-write data
Imagine an App that downloads bulk
data like phonebooks or store
locations or SKUs
2012
69. Don't mix read-only and
read-write data
Imagine an App that downloads bulk
data like phonebooks or store
locations or SKUs
Now imagine that App also lets the
local user comment/favorite/rate/
review those entries
2012
70. Don't mix read-only and
read-write data
Imagine an App that downloads bulk
data like phonebooks or store
locations or SKUs
Now imagine that App also lets the
local user comment/favorite/rate/
review those entries
Now imagine that every time you
updated 400K records, you had to
preserve local modifications...
2012
78. Three20 cont.
the new Facebook for iOS marks our first
release in years without the Three20
framework.
https://www.facebook.com/notes/facebook-engineering/
under-the-hood-rebuilding-facebook-for-ios/10151036091753920 2012
79. asi-http-request
After giving it a lot of thought over the
last few weeks, I’ve decided that I’m not
going to continue working on
ASIHTTPRequest in future.
http://allseeing-i.com/%5Brequest_release%5D; 2012
80. Async != Background
- (void)viewDidLoad {
! ///Snip...
! [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSString *jsonString = [[NSString alloc]
initWithData:jsonResponse
encoding:NSUTF8StringEncoding];
for (NSDictionary *jsonDict in [jsonString JSONValue]) {
! ! //Create Object
! ! [NSEntityDescription insertNewObjectForEntityForName:@"Event"];
! }
}
AFNetworking can have this confusion 2012
82. "Rocket Engine" for
Responsive Code
NSAssert(![NSThread isMainThread], @"BOOM");
2012
83. "Rocket Engine" for
Responsive Code
NSAssert(![NSThread isMainThread], @"BOOM");
WARNING: Rocket Engines
can EXPLODE in testing.
2012
84. Updating from Network
- (void)eventModelDidLoad:(NSArray *)events
{
! for (UIImageView *poster in
self.scrollView.contentView.subViews) {
[poster removeFromSuperview];
}
for (Event *event in events)
{
UIImageView *poster = [[UIImageView alloc]
initWithImage:event.image];
[eventPoster setFrame:event.frameRect];
[self.scrollView.contentView addSubview:poster];
}
}
Typical of a RESTKit Project 2012
85. Refresh Incrementally
Only Init the Subviews Where
User Might Scroll Soon
Think tableView and
dequeueReusableCellWithIdentifier
Don't Delete & Recreate
Subviews that Already Exist
2012
88. NMVC
Network Model (or at least "Disk")
Controller
View
2012
89. NMVC
Network Model (or at least "Disk")
Controller
View
2012
90. Networking Advice
Always* load the UI from local storage
WITHOUT WAITING or BLOCKING
Core Data or local file or something
Put up a placeholder if no data
*Except with live web pages or HTTP streaming or the like 2012
91. Networking Advice
Always* load the UI from local storage
WITHOUT WAITING or BLOCKING
Core Data or local file or something
Put up a placeholder if no data
Then start async download
*Except with live web pages or HTTP streaming or the like 2012
92. Networking Advice
Always* load the UI from local storage
WITHOUT WAITING or BLOCKING
Core Data or local file or something
Put up a placeholder if no data
Then start async download
Then put network data in local storage
*Except with live web pages or HTTP streaming or the like 2012
93. Networking Advice
Always* load the UI from local storage
WITHOUT WAITING or BLOCKING
Core Data or local file or something
Put up a placeholder if no data
Then start async download
Then put network data in local storage
Then notify the UI to refresh itself
*Except with live web pages or HTTP streaming or the like 2012
94. In Summary
Program Sober and Rested, If Possible
Leave Other Languages' Patterns
at the Door
Program Idiomatically
Don't Fight the Frameworks
Read the Fine Print
Look for Hidden Bugs
2012
Often, this is just code written by stupid people\n
Developing Under the Influence...\n
But there's not a lot we can learn from looking at Tier1 Badness, so on to...\n
\n
Useful Somewhere, but not Here\n
This is from one of my favorite public APIs to pick on: Evernote\n
Please don't name your ViewControllers "SomethingView"\n
\n
\n
This was a fun bug I had to chase down.\nI'm pretty sure the original file looked like this\n
And then I think this happened\n
Which resulted in this file, which would sometimes present the camera button on devices without the camera, which would then crash if clicked.\n
\n
\n
So Imagine there's a framework for saving a file\n
\n
\n
\n
So Imagine there's a framework for saving a file\n
\n
\n
iOS app for company with multi-billion dollar market cap.\n
\n
But, it could be worse. This was a Flash programmer on a video uploading app.\n\nApparently, Flash doesn't do threads...\n
\n
\n
\n
\n
\n
\n
\n
\n
In this case, fetcherMap was nil, so it would occasionally crash\n
In this case, fetcherMap was nil, so it would occasionally crash\n
In this case, fetcherMap was nil, so it would occasionally crash\n
\n
Apparently Useful, but not practically so\n
So much for type safety\n
MKMapViewDelegate Needed when user taps button on Callout on MapView \n
MKMapViewDelegate Needed when user taps button on Callout on MapView \n
MKMapViewDelegate Needed when user taps button on Callout on MapView \n
MKMapViewDelegate Needed when user taps button on Callout on MapView \n
Because if something in the background changes the data, the object in your array is no longer valid. Usually something like: "CoreData could not fulfill a fault"\n
\n
\n
\n
\n
\n
\n
\n
No matter what you think about this spider...\n
\n
\n
\n
\n
\n
\n
\n
\n
So here, every time an update comes from the network, every poster is deleted from the scrollview, and then a new poster is made and added for everything that the network had. Even if it was already there. Even if it's pages away from where the user might scroll\n
\n
Network talks to the model, not the controller\n
Network talks to the model, not the controller\n
Network talks to the model, not the controller\n
Network talks to the model, not the controller\n
Network talks to the model, not the controller\n
Network talks to the model, not the controller\n
Network talks to the model, not the controller\n
Network talks to the model, not the controller\n
\n
\n
\n
\n
Stay away from things that make you Stupid\nRemember what language you're writing in\nPay attention, think things through, and educate yourself\n