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

Intermediate Objective-C Workshop

Intermediate Objective-C Workshop

Curtis Herbert

August 23, 2014
Tweet

More Decks by Curtis Herbert

Other Decks in Programming

Transcript

  1. HISTORY OF OBJ-C • 1983 - C + Smalltalk come

    together as a C pre-compiler • 1988 - NeXT licensed the language • 1996 - Apple buys NeXT as foundation for OS X (“Rhapsody”) • 2006 - Objective-C 2.0 • 2008 - iPhone SDK
  2. int myAge = 6; myAge = myAge + 6; int

    alsoMyAge = myAge; C PRIMITIVES
  3. [tableView reloadData]; Send the message “reloadData” to the object “tableView”

    objc_msgsend(tableview, @selector(reloadData)); MESSAGE SENDING
  4. “Objective-C decides which method implementation to call right before doing

    so (during runtime). The idea is that the connection between the name of a method and the implementation is dynamic. C++ for example does this during compile time.”
  5. AN EXAMPLE OF THIS VOODOO UIView *myNewView = [[UIView alloc]

    init]; object_setClass(myNewView, [MyOtherClass class]); (this isn’t a common thing to do, don’t worry)
  6. -[NSArray objectForKey:]: unrecognized selector sent to instance 0x102301c40 
 ***

    Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSArray objectForKey:]: unrecognized selector sent to instance 0x102301c40'
  7. OBJ-C PRIMITIVES SEL CLASS ID SEL mySelector = @selector(update); if

    ([self respondsToSelector:mySelector]) { } NIL
  8. OBJ-C PRIMITIVES SEL CLASS ID SEL mySelector = @selector(updateName:); [self

    performSelector:mySelector withObject:@“Joe”]; NIL
  9. OBJ-C PRIMITIVES SEL CLASS ID Class targetClass = [NSDate class];

    if ([myObject isKindOfClass:targetClass]) … NIL
  10. OBJ-C DIRECTIVES • Instructions to the compiler • @IBOutlet, @property,

    @class, etc • @“string”, @[@“string1”], @{@“key” : @“value”}
  11. OBJ-C CLASSES UIVIEWCONTROLLER NSSTRING NSNUMBER UITABLEVIEW NSARRAY UIVIEW NSDATE NSDICTIONARY

    NSURL UILABEL UIIMAGEVIEW NSFORMATTER CLLOCTIONMANAGER UISWITCH UICOLLECTIONVIEW
  12. STANDARD OO STUFF NSObject UIView : UIResponder UIResponder : NSObject

    Inherits from subclass Inherits from subclass
  13. COMPONENTS OF A CLASSES HEADER Properties, method signatures Exposed to

    the outside world IMPLEMENTATION The executable code
  14. #import “PCHActivitySharingProvider.h” #import <AFNetworking/AFNetworking.h> @implementation PCHActivitySharingProvider - (void)shareActivity:(PCHActivity *)activity {

    //pre-process the activity, do weird stuff [self showShareUIWithActivity:activity]; //do something else } - (void)showShareUIWithActivity:(PCHActivity *)activity { //do awesome stuff with activity } @end
  15. @implementation PCHActivitySharingProvider PCHActivity *_activity; - (void)shareActivity:(PCHActivity *)activity { _activity =

    activity; [self showShareUIWithActivity:activity]; } ... INTERNAL VARIABLES (IVARS)
  16. @implementation PCHActivitySharingProvider PCHActivity *_activity; - (void)setActivity:(PCHActivity *)activity { if (activity

    != _activity) { _activity = activity; } } - (PCHActivity *)activity { return _activity; } ...
  17. @import Foundation; @class CBCActivity; @interface CBCActivitySharingProvider : NSObject <NSCoder> @property

    CBCActivity *activity; - (void)shareActivity:(CBCActivity *)activity; @end PROPERTIES
  18. myObject.activity myObject.activity = blah; self.activity = blah; return self.activity; “DOT

    SYNTAX” VS “OLD SCHOOL” [myObject activity]; [myObject setActivity:blah]; [self setActivity:blah]; return [self activity];
  19. CONVENTIONS - CLARITY Code Commentary insertObject:atIndex: Good. insert:at: Not clear;

    what is being inserted? what does “at” signify? removeObjectAtIndex: Good. removeObject: Good, because it removes object referred to in argument. remove: Not clear; what is being removed? https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/ NamingBasics.html#//apple_ref/doc/uid/20001281-BBCHBFAH
  20. CONVENTIONS - DESCRIPTIVE Code Commentary destinationSelection Good. destSel Not clear.

    setBackgroundColor: Good. setBkgdColor: Not clear. https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CodingGuidelines/ Articles/NamingBasics.html#//apple_ref/doc/uid/20001281-BBCHBFAH
  21. CONVENTIONS - PREFIXING • Class Prefix — UIColor, NSString, CLLocation,

    etc • Apple uses 2-letter prefix • Prefix your classes with your initials Prefix Cocoa Framework NS Foundation NS Application Kit AB Address Book IB Interface Builder UI UIKit
  22. - (void)myMethod { int a = 10; } C primitives

    only exist in memory as long as they’re in scope. The memory for a is gone after the method returns.
  23. int a = 10; int b = a; b =

    20; C primitives are passed by making copies. 10 ➡️
  24. int a = 10; int b = a; b =

    20; C primitives are passed by making copies. 10 10 ➡️
  25. int a = 10; int b = a; b =

    20; C primitives are passed by making copies. 10 20 ➡️
  26. int a = 10; [self myAdder:a]; a = 20; C

    primitives are passed by making copies. 10 - (void)myAddder:(int)c { adder += 40; } ➡️
  27. int a = 10; [self myAdder:a]; a = 20; C

    primitives are passed by making copies. 10 10 - (void)myAddder:(int)c { adder += 40; } ➡️
  28. int a = 10; [self myAdder:a]; a = 20; C

    primitives are passed by making copies. 10 50 - (void)myAddder:(int)c { adder += 40; } ➡️
  29. int a = 10; [self myAdder:a]; a = 20; C

    primitives are passed by making copies. 20 - (void)myAddder:(int)c { adder += 40; } ➡️
  30. NSNumber *myNumber = [[NSNumber alloc] init]; NSNumber A pointer is

    a variable whose value is the address of another variable.
  31. NSNumber *myNumber = [[NSNumber alloc] init]; A pointer is a

    variable whose value is the address of another variable. NSNumber *
  32. NSNumber *myNumber = [[NSNumber alloc] init]; NSNumber *5 A pointer

    is a variable whose value is the address of another variable.
  33. *5 *12 NSNumber NSNumber NSNumber *myNumber = [[NSNumber alloc] init];

    NSNumber *myNumber2 = [[NSNumber alloc] init];
  34. NSNumber *myNumber = [[NSNumber alloc] init]; NSNumber *myNumber2 = [[NSNumber

    alloc] init]; myNumber = myNumber2; *5 *5 NSNumber NSNumber
  35. *5 NSMutableString *5 NSMutableString *myString = [[NSMutableString alloc] init]; [self

    appendCompany:myString]; - (void)appendCompany:(NSMutableString *)string { NSLog(string); }
  36. *5 NSMutableString *5 NSMutableString *myString = [[NSMutableString alloc] init]; [self

    appendCompany:myString]; - (void)appendCompany:(NSMutableString *)string { [string appendString:@“Test”]; }
  37. C primitives only exist in memory as long as they’re

    in scope. How do we clean up after objects which work differently?
  38. *5 *12 NSNumber NSNumber NSNumber *myNumber = [[NSNumber alloc] init];

    NSNumber *myNumber2 = [[NSNumber alloc] init]; myNumber = myNumber2;
  39. Reference counting is a way to express ownership of an

    object. NSString 1 image: http://cocoadevcentral.com alloc
  40. A BALANCING ACT • Alloc or copy variants: starts with

    retain count of 1 • Retain: +1 • Release: -1 • Autorelease: -1 (later)
  41. IF YOU CREATE, YOU RELEASE - (void)shareActivity { NSDate *today

    = [[NSDate alloc] init]; //do some stuff with the date [today release]; }
  42. AUTORELEASE TO TRANSFER OWNERSHIP - (NSDate *)todaysDate { NSDate *today

    = [[NSDate alloc] init]; //do some stuff with the date return [today autorelease]; }
  43. TAKING OWNERSHIP PCHActivity *_activity; - (void)shareActivity:(PCHActivity *)activity { [_activity release];

    _activity = [activity retain]; } - (void)dealloc { [_activity release]; [super dealloc]; }
  44. TAKING OWNERSHIP (COPY) PCHActivity *_activity; - (void)shareActivity:(PCHActivity *)activity { [_activity

    release]; _activity = [activity copy]; } - (void)dealloc { [_activity release]; [super dealloc]; }
  45. AUTOMATIC REFERENCE COUNTING • Added in 2011 • Compiler automatically

    adds these retain/release calls • Different than garbage collection (which is at runtime) • Didn’t solve all the retain-related issues…
  46. weak - does not increase the reference count atomic -

    creates a lock in setters/getters readonly - only creates a getter for the property copy - creates a copy instead of retaining original object strong - increases the reference count nonatomic - does not lock, better performance readwrite - creates setter and getter assign - similar to weak, used for primitives
  47. @import Foundation; @class PCHActivityMonitor; @interface PCHActivity : NSObject @property (strong,

    atomic, readwrite) PCHActivityMonitor *monitor; @property (assign, atomic, readwrite) BOOL isValid; @end PROPERTY DEFAULT ATTRIBUTES
  48. @property PCHActivityMonitor *monitor; _monitor @property PCHActivity *activity; _activity SYNTHESIZED IVARS

    Property definition ivar name In general though, avoid accessing ivars, always use self.property. Use ivars in custom setters and gettings.
  49. int (^myBlock)(int) = ^(int a) { return a * 15;

    }; int result = myBlock(10); COMPONENTS OF A BLOCK
  50. int (^myBlock)(int) = ^(int a) { return a * 15;

    }; int result = myBlock(10); DEFINITION Return type
  51. int (^myBlock)(int) = ^(int a) { return a * 15;

    }; int result = myBlock(10); DEFINITION Name
  52. int (^myBlock)(int) = ^(int a) { return a * 15;

    }; int result = myBlock(10); DEFINITION Parameter types
  53. int (^myBlock)(int) = ^(int a) { return a * 15;

    }; int result = myBlock(10); IMPLEMENTATION Parameters
  54. int (^myBlock)(int) = ^(int a) { return a * 15;

    }; int result = myBlock(10); IMPLEMENTATION Body
  55. - (void)upload:(NSData *)data failure:(void (^)(NSError *))errorCallback; YOUR OWN BLOCKS AS

    PARAMS [server upload:myData failure:^(NSError *error) { NSLog(@“Error!?! %@“, error); }];
  56. - (void)upload:(NSData *)data failure:(void (^)(NSError *))errorCallback { //perform upload NSError

    *error = //get the upload error if (errorCallback) { errorCallback(error); } } YOUR OWN BLOCKS AS PARAMS
  57. typedef void (^CBCErrorHandler)(NSError *); @interface PCHServerAPI - (void)upload:(NSData *)data failure:(PCHErrorHandler)callback;

    - (void)synchronizeWithFailure:(PCHErrorHandler)callback; @end REUSING BLOCK DEFINITIONS
  58. GOTCHA #1: __BLOCK NSString *test = @“Hello!; void (^myBlock)() =

    ^(){ test = @“Hello 2!”; }; myBlock(); NSLog(test);
  59. GOTCHA #1: __BLOCK __block NSString *test = @“Hello!; void (^myBlock)()

    = ^(){ test = @“Hello 2!”; }; myBlock(); NSLog(test);
  60. GOTCHA #2: RETAIN CYCLES @property (nonatomic, copy) void (^errorHandler)(NSError *);

    ... self.errorHandler = ^(){ [self displayAlert:@“Oh No!”]; };
  61. _weak typeof(self) weakSelf = self; self.errorHandler = ^(){ __strong typeof(weakSelf)

    strongSelf = weakSelf; if (strongSelf) { [strongSelf displayAlert:@“Oh No!”]; } }; GOTCHA #2: RETAIN CYCLES
  62. HANDS-ON #6 You’ll need to make sure reloadData happens on

    the main thread dispatch_async(dispatch_get_main_queue(), ^{ //main thread code });
  63. @implementation PCHServerAPI static CBCServerAPI *PCHServerAPISharedInstance = nil; + (instancetype)sharedServer {

    static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ PCHServerAPISharedInstance = [[PCHServerAPI alloc] init]; }); return PCHServerAPISharedInstance; }
  64. @implementation PCHServerAPI static dispatch_queue_t PCHServerAPISharedQueue = nil; - (instancetype)init {

    if (self = [super init]) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ PCHServerAPISharedQueue = dispatch_queue_create(...); }); } return self; }
  65. DATA SOURCE / DELEGATE (PROTOCOLS) UITableView • What data should

    be shown? • What happens during key events (like tapping a row)?
  66. - (void)awakeFromNib { [super awakeFromNib]; self.tableView.dataSource = self; } ...

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return 0; } ... CBCViewController.m
  67. @protocol UITableViewDataSource <NSObject> @required - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section; - (UITableViewCell

    *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath; @optional - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; ... UITableViewController.h
  68. @interface UITableView : UIScrollView <NSCoding> ... @property (nonatomic, weak) id

    <UITableViewDataSource> dataSource; ... UITableViewController.h
  69. NSUInteger rows = 0; if ([self.delegate respondsToSelector:@selector(tableView:numberOfRowsInSection:)]) { rows =

    [self.delegate tableView:self numberOfRowsInSection:section]; } UITableViewController.m
  70. @interface UITableView : UIScrollView <NSCoding> ... @property (nonatomic, weak) id

    <UITableViewDataSource> dataSource; @property (nonatomic, weak) id <UITableViewDelegate> delegate; ...
  71. Use this when you want two-way communications between classes, but

    you don’t want to have to hard-code references. Great for reusability / being able to swap out implementations.
  72. @interface PCHServerAPI : NSObject @property (nonatomic) NSUInteger downloadCount; @property (nonatomic)

    BOOL downloading; @end WHAT IF? “Hey, tell me if these change.”
  73. KEY-VALUE OBSERVING (KVO) • Listen for changes for a given

    key-path • One-way: doesn’t need to know about subscribers • Ad-hoc: no formal contract (protocol) • Automatic • Limitless subscribers
  74. Step 1 - Register - (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options

    context:(void *)context Step 2 - Listen for Callbacks - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
  75. [self.serverAPI addObserver:self forKeyPath:@“downloadCount” options:0 context:nil]; ... - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object

    change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:@"downloadCount"]) { //do something with the change } }
  76. A FEW IMPROVEMENTS static void * MyContext = &MyContext; ...

    - (void)observeValueForKeyPath:(NSString *)keyPath... { if (context == MyContext) { } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } }
  77. UNSUBSCRIBING - A PAIN @try { [self.server removeObserver:self forKeyPath:NSStringFromSelector(@selector(downloadCount)) context:MyContext];

    } @catch (NSException * __unused exception) {} • Can’t check if you’re a subscriber • Will crash if you try to unsubscribe when not already subscribed
  78. @interface PCHServerAPI : NSObject @property (nonatomic) NSUInteger downloadCount; @property (nonatomic,

    readonly) BOOL downloading; @end - (BOOL)downloading { return self.downloadCount > 0; } LETS GET TRICKY
  79. - (void)setDownloadCount:(NSUInteger)downloadCount { BOOL shouldUpdateDownloading = //custom logic if (shouldUpdateDownloading)

    { [self willChangeValueForKey:@"downloading"]; } _downloadCount = downloadCount; if (shouldUpdateDownloading) { [self didChangeValueForKey:@"downloading"]; } } DERIVED PROPERTIES MANUAL NOTIFICATIONS
  80. Use this when you want to allow multiple objects to

    listen to changes without the baggage of a protocol.
  81. HANDS-ON #9 • viewWillAppear is a great spot to hook

    up KVO in this example • it’s opposite, viewDidDisappear, is a great place to unsubscribe
  82. extern NSString * const PCHServerDownloadStartNotification; extern NSString * const PCHServerDownloadCountKey;

    NSString * const PCHServerDownloadStartNotification = @“org.phillycocoa.downloadStartNotification”; NSString * const PCHServerDownloadCountKey = @"PCHServerDownloadCountKey"; CLEANUP - USE CONSTANTS
  83. Use this when you want to allow multiple objects to

    be notified when something happens without needing any link between the objects.
  84. HANDS-ON #10 • setDownloads is a great place to send

    a notification (if conditions are met) • you’ll need to use [NSNumber numberForBOOL:] to send bool values in the user info dictionary, and [number boolValue] to get it on the other side
  85. • Exceptions exist in Objective-C, but are expensive • Instead

    return a successful BOOL if no return type needed, or nil if an object is expected. • Use an NSError parameter with information about the error (same as you would use an exception to bubble up information).
  86. *0 *0 NSError *error = nil; BOOL success = [myContext

    save:error]; -(BOOL)save:(NSError*)error;
  87. *0 *5 NSError NSError *error = nil; BOOL success =

    [myContext save:error]; -(BOOL)save:(NSError*)error { error = [[NSError alloc] init]; }
  88. ** pointer to a pointer & location in memory A

    POINTER TO A POINTER? * pointer to an object
  89. NSError *error = nil; A POINTER TO A POINTER? *5

    NSError if (error) { *error = [NSError errorWithDomain:@“...” code:1 userInfo:nil]; } } -(BOOL)save:(NSError**)error { BOOL success = [myContext save:&error];
  90. A LITTLE MORE ON NSERROR [NSError errorWithDomain:domain code:code userInfo:userInfo]; Domain

    - reverse DNS, most likely the same as your app ID (“com.consumedbycode.slopes”, for example) Code - you pick these and define their meanings. Internal to app logic. UserInfo - can use to pass back a dictionary of values to the handler. Usually a description using NSLocalizedDescriptionKey.
  91. extern NSString * const PCHSlopesErrorDomain; extern NSString * const PCHSlopesErrorUnknownTypeDescription;

    extern int const PCHSlopesErrorUnknownTypeCode; NSString * const PCHSlopesErrorDomain = @“org.phillycocoa.slopes”; NSString * const PCHSlopesErrorUnknownTypeDescription = @"File type for export unknown.”; int const PCHSlopesErrorUnknownTypeCode = 100; CLEANUP - USE CONSTANTS
  92. CATEGORIES • A category allows you to add methods to

    an existing class without subclassing it or needing to know any of the details of how it's implemented. • Effects all instances of that class within the application.
  93. @interface NSArray (Utils) - (id)randomObject; @end @implementation NSArray (Utils) -(id)randomObject

    { if ([self count]) { return [self objectAtIndex:arc4random_uniform([self count])]; } else { return nil; } } @end CATEGORIES NSArray+utils.h NSArray+utils.m
  94. @interface NSArray (CBCUtils) - (id)pch_randomObject; @end @implementation NSArray (CBCUtils) -(id)pch_randomObject

    { if ([self count]) { return [self objectAtIndex:arc4random_uniform([self count])]; } else { return nil; } } @end BEST PRACTICE: NAMESPACE NSArray+utils.h NSArray+utils.m
  95. - (void)build; - (void)destroy; CATEGORIES FOR EXPOSING “PROTECTED” METHODS PCHActivity.h

    @interface CBCActivity - (void)build; @end PCHActivity+Protected.h @interface CBCActivity(Protected) -(void)destroy; @end
  96. INSTANCETYPE Remember: id means “this could be any type” instancetype

    lets the compiler infer the type and can only be used for return types
  97. INSTANCETYPE - (id)init { self = [super init]; return self;

    } - (bool)testMethod { ... } bool test = [[[AClass alloc] init] testMethod]; //error!
  98. INSTANCETYPE - (id)init { self = [super init]; return self;

    } - (bool)testMethod { ... } bool test = [((AClass *)[[AClass alloc] init]) testMethod]; //yay!
  99. INSTANCETYPE - (instancetype)init { self = [super init]; return self;

    } - (bool)testMethod { ... } bool test = [[[AClass alloc] init] testMethod]; //yay!