Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Laziness-Driven Development in iOS (CocoaHeads ...

Ellen Shapiro
September 09, 2014

Laziness-Driven Development in iOS (CocoaHeads Chicago, September 2014)

Initial version of my talk about how best practices secretly enable laziness. Delivered at CocoaHeads Chicago, September 2014

Ellen Shapiro

September 09, 2014
Tweet

More Decks by Ellen Shapiro

Other Decks in Technology

Transcript

  1. Laziness-Driven Development in iOS CocoaHeads Chicago | September 2014 |

    by Ellen Shapiro vokalinteractive.com | justhum.com | designatednerd.com | @designatednerd
  2. What is lazy, but is NOT Laziness-Driven Development? » Copying

    and pasting code » Code without tests » Code which was fast to write, but impossible to read » Using libraries for every possible task.
  3. What is Laziness-Driven Development? » Smart reuse of code »

    Clear, Readable code » Removal of Stringly-typed code » Making smart decisions today to save yourself time and heartache tomorrow
  4. Eventually, You're going to screw something up. Mistakes are absolutely

    inevitable with code as complex as that in modern iOS applications.
  5. if you know you're going to screw something up, only

    screw it up once. Then, you only have to fix it once.
  6. MVC helps keep code separated by its purpose. » It's

    a hell of a lot easier to fix a bug when your networking code and your view layout code are not all mixed together. » Note that MVC can easily be replaced by MVVM - the goal of both patterns is to separate your model code from your view code from the code which updates both.
  7. Inheritance helps keep code with the same purpose centralized. »

    Inheritance is best used when you have many things of the same type that do the same thing. » For example, if all your UIViewControllers have a logout button in the UINavigationBar, use a superclass to add that button and handle logout. » If there's a bug with logout, there's only one place to look.
  8. YES

  9. Examples of Stringly-typed code [tableView dequeueReusableCellWithIdentifier:@"MyTableViewCell" forIndexPath:indexPath]; if ([segue.identifier isEqualToString:@"SomeSegue"])

    { ... [self willChangeValueForKey:@"aProperty"]; self.aProperty = NO; [self didChangeValueForKey:@"aProperty"]; [UIImage imageNamed:@"Logo"]; self.titleLabel.text = @"The Title Text"; [myArray valueForKeyPath:@"avg.self"];
  10. String Constants: The Cure For Spelling Before: [self performSegueWithIdentifier:@"Login Segeu"];

    [self performSegueWithIdentifier:@"LoginSegue"]; After: static NSString *const kLoginSegue = @"Login Segue"; [self performSegueWithIdentifier:kLoginSegue];
  11. constants: Not just for strings » If you're using consistent

    padding or sizes for UI elements, use CGFloat constants to enforce that. » Makes it way easier to read your code: self.viewWidth = 74; vs self.viewWidth = kProfileViewWidth; » If design changes up your padding, you change it in one place, and you're done.
  12. Enums: Even lazier constants typedef NS_ENUM(NSInteger, TableViewSection) { TableViewSectionName, //0

    TableViewSectionAddress, //1 TableViewSectionCount //2 } - (NSInteger)numberOfSectionsInTableView { return TableViewSectionCount; }
  13. Enums: Even lazier constants typedef NS_ENUM(NSInteger, TableViewSection) { TableViewSectionName, //0

    TableViewSectionAddress, //1 TableViewSectionPhoneNumber, //2 TableViewSectionCount //3 } - (NSInteger)numberOfSectionsInTableView { return TableViewSectionCount; }
  14. Non-Integer Enums in Swift If you mark your enum as

    a type convertible from a literal, you can assign those values directly:
  15. What about macros? » In Objective-C Macros can be a

    great way to handle operations that are a little too convoluted to handle with methods. #define CDSELECTOR(selectorSymbol) NSStringFromSelector(@selector(selectorSymbol)) » Macros can easily be abused by being used where declared, typed Constants would make more sense. #define kStringConstant @"A String That Should Be A Constant"
  16. Macros: Not gonna happen in Swift » Apple's official recommendation

    for macro migration to Swift boils down to "don't": "Declare simple macros as global constants, and translate complex macros into functions." » You can use certain runtime definitions in Swift (#ifdef DEBUG), but there are a ton of restrictions. » If you're moving a project to Swift, find a way to use functions instead wherever possible.
  17. readability » Someone who knows iOS development but knows little

    about your project should be able to easily read your code. » Remember: Sometimes that person will be you in the future. » Fixing bugs is a lot easier to do when you can figure out what the hell you were trying to do in the first place just by reading code and comments.
  18. Variable naming » Keep your variables descriptive. » BAD: UIView

    *v, CGPoint pt » BETTER: UIView *profileView, CGPoint profileOrigin » This is especially important in Swift because of type inference - you want to be able to have someone reading your code tell what type a variable would be without having to scroll back up to where it was declared.
  19. Method Naming » Objective-C is very good about encouraging extremelyLongAndDescriptiveMethodNames:withParamet

    er:andCompletionBlock: » Swift can still doWhatItSaysOnTheLabel(). » The biggest difference is that in Swift, often you can leave off the parameter names in code, which can make the code harder to read. » Particularly for single-parameter methods, name your methods so that the type of the first parameter is very clear.
  20. Benefits of clear naming! » Easier to read code (duh)

    » Naming things clearly helps facilitate searching » By the name of a file » By the name of a property/method or by typing in the drop-down menu » Reading a stack trace is easier when you can follow names of methods. » Makes it easy to get a summary of what a file does from the drop-down menu.
  21. //TODO: Write better comments » That said, make sure that

    you're letting the code do most of the talking for you in terms of what you're doing, and that you're using comments to clarify why you're taking a certain approach. » If you're using Some Code You Found On The Internet, throw in a link - sometimes old posts will get updated with links to new approaches. » If something is a workaround for an older version of iOS, note that in your comments so it can be removed once support for that version is dropped.
  22. Remember, the goal of good comments and clear naming is

    the same: Future laziness for you.
  23. Testing stuff manually is a pain. » Build » Run

    » Reset everything » Poke » Reset everything again » Poke again » (Repeat ad nauseam until it's fixed)
  24. Unit testing benefits » Tests your under-the-hood logic independently of

    your user interface. » Really, really fast. Typical unit tests take < 1 minute to run an entire suite of dozens of tests. » Mean that you can focus on getting your logic to work before you figure out how to make the results look good.
  25. Unit Testing Protips » Reset any NSUserDefaults that you're using

    to maintain state in the setUp or tearDown methods of your tests. » Make sure you're always testing on a clean database. NSPersistentStoreCoordinator's NSInMemoryStoreType is your friend. » When using asynchronous methods, whether with delegates or blocks, XCTestExpectation is your friend in Xcode 6.
  26. Adding functionality to existing objects » Great for code you

    want to centralize, but for which a subclass seems a bit too specific. » Makes it easy to combine various pieces of added functionality without having a crazy nest of subclasses. » Add functionality not just to one class, but to all of its subclasses.
  27. Objective-C: Categories Let's say you want to easily make any

    subclass of UIView into a circle. In Objective-C: UIView+XYZApp: - (void)xyz_circleify { self.layer.cornerRadius = CGRectGetWidth(self.frame) / 2; self.clipsToBounds = YES; } would be available anywhere you #import UIView +XYZApp.h.
  28. Swift: Extensions MyViewExtension: extension UIView { public func xyz_circleify() {

    layer.cornerRadius = CGRectGetWidth(frame) / 2 clipsToBounds = true } } Since Swift doesn't require importing, this is available anywhere in your module.
  29. Watch for method name collsions » Standard practice is to

    prefix your category methods with a three letter prefix and an underscore like xyz_circleify » This prevents your method from colliding with one of Apple's if they ever decide to add their own circleify method » Also makes it easy to pick out which methods you wrote vs. the ones created by Apple at a glance.
  30. Watch for method name collsions even in Swift Swift takes

    away the need for prefixing classes, but not for prefixing extensions.
  31. Plugins Before Alcatraz » Download project » Build, run, install,

    » Restart Xcode, » Do the hokey pokey and turn yourself about. » Uninstall by hunting down obscure folder and then forgetting what the hell you were doing in there.
  32. Plugins AFTER Alcatraz » Push button, install plugin. » Push

    other button to see plugin page. » Search plugins easily. » Push button, uninstall plugin. » Live your best life.
  33. Laziness-Enhancing Plugins » Lin - localization » VVDocumenter - e-z

    comments » XToDo - To-Do list from your //TODOs » BBFullIssueNavigator - see the whole issue » FuzzyAutocomplete - Way better autocomplete » SCXcodeMiniMap - A Sublime-Text style overview in the left gutter of your code view.
  34. Cocoa Controls: A place for ideas » A centralized catalog

    dozens and dozens of open- source UI controls. » If you've got a vague idea of what you want to do, this can help you narrow down what works and what doesn't from a conceptual standpoint » Don't reinvent the wheel if you don't have to » If you do have to, get an idea of where to start
  35. Get an idea of what is simple and what is

    not » As you look through controls, you can start to spot patterns of what controls are easy to design and implement, and which ones take monstrous amounts of work. » Evaluate which path will actually cause you less grief: Adding yet another dependency, or rewriting something yourself and being able to customize it exactly to your needs.
  36. Edit NSManagedObjects Like A Human Being » MOGenerator goes through

    through and creates private files (which are continually regenerated whenever you change your model) and public subclass files (which you can edit) make it easier to change your Core Data model during development. » No more NSManagedObject+Human.h categories to add additional functionality to your NSManagedObjects!
  37. MOGenerator Kills Boilerplate just to watch it die » Creates

    all the boilerplate code you'll need for accessing values like normal properties instead of constantly using valueForKey. » Creates structs which point to strings representing all values and relationships on a managed object, so you don't have to use stringly- typed code to create NSPredicates or NSSortDescriptors:
  38. Some Examples of Lazier Code with MOGenerator NSSortDescriptor *ordinalAscending =

    [NSSortDescriptor sortDescriptorWithKey:HUMSongAttributes.songOrdinalNumber ascending:YES]; NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:[HUMSong entityName]]; [NSPredicate predicateWithFormat:@"%K ==[c] %@", HUMSongAttributes.capoFret, capoFret]
  39. What is a Struct? » In Objective-C, structs are C

    Structs. » In Swift, a struct is any non-object data structure, and it is always passed by value. » Structs are cheaper to create than Objects, but cannot take advantage of Automatic Reference Counting. » NSRange, CGRect, and CGSize are some examples of Apple-provided structs.
  40. Structs: Towards More Readable Constants Constants.h extern const struct HUMUserDefaultKeys

    { __unsafe_unretained NSString *hasPermissionForUsageStatistics; __unsafe_unretained NSString *hasRunBefore } HUMUserDefaultKeys; Constants.m: const struct HUMUserDefaultKeys HUMUserDefaultKeys = { .hasPermissionForUsageStatistics = @"__kHasPermissionForUsageStatistics", .hasRunBefore = @"__kHasRunBefore" };
  41. Quick Tangent: __Unsafe_unretained » Strings in a C struct need

    to be marked with __unsafe_unretained so they'll compile using ARC. » Since ARC can't really see into structs, putting something in a struct implies that you will handle its memory management. » If you're declaring const structs, you don't actually need to worry about memory management since they're constant and will never get deallocated.
  42. Structs: More common in Swift » Structs are far more

    common in Swift. Anything which does not require inheritance is defined as a Struct - including String. » You can create your own structs similarly, but without the noise of having to declare members __unsafe_unretained struct HUMUserDefaultKeys { let hasRunBefore = "__kHasRunBefore" let hasPermissionForUsageStatistics = "__kHasPermissionForUsageStatistics" } var before = HUMUserDefaultKeys().hasRunBefore
  43. KIF

  44. KIF

  45. What is KIF? » Short for Keep It Functional »

    Unlike Apple's built-in UI testing tools, which use JavaScript, you can write KIF tests in Obj-C or Swift (thought Swift requires extra setup). » Uses accessibility labels to drill through the view hierarchy and find the view you want to tap or inspect. » A great way to justify adding accessibility to your app to bean-counters.
  46. KIF Protips » Use known data sets to test your

    user interface - remember that you should try to keep these tests focused on your user interface and information flow, NOT whether your server is working. » Be aware that if your app uses CoreData or has complex animations, you may occasionally need to add some [tester waitForTimeInterval:] calls to allow saves or animations to complete before moving to the next step.
  47. KIF Gotchas » Make sure you're localizing your accessibilityLabel strings

    since they will be read out loud to your visually impaired users. » Running a suite of KIF tests takes longer than standard unit tests (minutes vs. seconds), since it's tapping through your user interface. » Sometimes, KIFTypist can get ahead of itself. This has gotten better recently, but when a test fails, check to make sure the right string was input.
  48. Spend some time working with both new and old languages

    before doing this. » Having translated a couple apps for both iOS and Android, the biggest thing I prefer on Android is string resource management. » All user-facing strings are kept in a single file per language, and are given unique IDs. » mTextView.setText(R.string.my_string_name) will set a string with the ID my_string_name from a resources file, which will be accessed by language.
  49. Figure out how to take the theory and put it

    into action in an iOS-friendly way » My solution: An NSString+AppName category with all of the user-facing strings in the application: + (NSString *)xyz_continue { return NSLocalizedString(@"Continue", @"Continue text"); } » Centralizes and makes it possible to organize localized strings, helps prevent duplicate keys and duplicate values, and makes strings auto- completeable.
  50. When Should You write Your own library? » When Apple

    has made something you need to do over and over again harder than it needs to be. » When nobody else has already written a library for this task. » When taking the time to write the library will save you time in the end.
  51. My Experience Writing A Library » Asset catalogs solved a

    lot of problems with accessing multiple dimensions of image without having to use multiple strings to access the images. » You still have to call the image using a string: [UIImage imageNamed:@"AnImage"] » Did I mention that I hate stringly-typed code? » KSImageNamed Xcode plugin gave you auto-complete on strings, but still left strings scattered all over the code.
  52. Cat2Cat: Asset Catalog To Category » Walks through an AssetCatalog

    and spits out a category on UIImage with the names of every image as a method so instead of writing: [UIImage imageNamed:@"My Image Name"]; you can write: [UIImage ac_My_Image_Name];
  53. Why do it this way? » Centralizes all of your

    image calls » Auto-complete on method names » Can be easily run with a script any time you update your code (or even every time you build your code) » The script rewrites the file every time it runs, so any image called programmatically is guaranteed to be there. » Throws obvious errors when you remove an image.
  54. How is this lazier? » Find out you broke your

    images at compile time instead of runtime. » Auto-complete, especially with FuzzyAutocomplete is way faster than trying to remember how to spell something. » By taking the time to write and test a library, I have something I know will work every time, without having to re-test it.
  55. Note: Don't be afraid to point people to Competing libraries.

    About a month after Cat2Cat went public, Square released objc codegenutils: » objc-assetgen does the same thing as Cat2Cat. » objc-colordump outputs a file of UIColors from your Interace Builder color palette. » objc-identifierconstants dumps segue identifiers and VC identifiers out into files or each
  56. in conclusion » Centralize your code, make it easy to

    read, and keep it separated by purpose, so it's easy to fix. » Get rid of stringly-typed code so it's harder to break in the first place. » Use the tools other people have built to enhance your laziness, or build new tools yourself. » Best practices are secretly the lazy way to code.
  57. Links » Alcatraz: http://alcatraz.io/ » MOGenerator: https://github.com/rentzsch/ mogenerator » Cocoa

    Controls: https://www.cocoacontrols.com/ » Cat2Cat: https://github.com/vokalinteractive/ Cat2Cat » objc codegenutils: https://github.com/square/objc- codegenutils/
  58. Made with Deckset I rather enjoyed working with it, you

    should try it: http://decksetapp.com/