Share
Embed
Transcript
180 slides
-
INTERMEDIATE OBJECTIVE-C CocoaShops Curtis Herbert Carl Leiby Kotaro Fujita Mike
Zornek @parrots @carlism @wild37 @zorn -
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 -
int myAge = 6; myAge = myAge + 6; int
alsoMyAge = myAge; C PRIMITIVES -
WHERE THINGS GET FUN: OBJ-C IS A DYNAMIC RUNTIME A
quick detour to the deep end -
[tableView reloadData]; Send the message “reloadData” to the object “tableView”
objc_msgsend(tableview, @selector(reloadData)); MESSAGE SENDING -
“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.” -
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) -
-[NSArray objectForKey:]: unrecognized selector sent to instance 0x102301c40 ***
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSArray objectForKey:]: unrecognized selector sent to instance 0x102301c40' -
OBJ-C PRIMITIVES SEL CLASS ID id name = @“Curtis”; id
date = [NSDate new]; NIL -
OBJ-C PRIMITIVES SEL CLASS ID SEL mySelector = @selector(update); [self
performSelector:mySelector]; NIL -
OBJ-C PRIMITIVES SEL CLASS ID SEL mySelector = @selector(update); if
([self respondsToSelector:mySelector]) { } NIL -
OBJ-C PRIMITIVES SEL CLASS ID SEL mySelector = @selector(updateName:); [self
performSelector:mySelector withObject:@“Joe”]; NIL -
OBJ-C PRIMITIVES SEL CLASS ID SEL mySelector = NSSelectorFromString(@“myMethod”); [self
performSelector:mySelector]; NIL -
OBJ-C PRIMITIVES SEL CLASS ID Class targetClass = [NSDate class];
if ([myObject isKindOfClass:targetClass]) … NIL -
OBJ-C PRIMITIVES SEL CLASS ID myObject = nil; NSString *name
= [myObject showName]; NIL -
OBJ-C DIRECTIVES • Instructions to the compiler • @IBOutlet, @property,
@class, etc • @“string”, @[@“string1”], @{@“key” : @“value”} -
OBJ-C CLASSES UIVIEWCONTROLLER NSSTRING NSNUMBER UITABLEVIEW NSARRAY UIVIEW NSDATE NSDICTIONARY
NSURL UILABEL UIIMAGEVIEW NSFORMATTER CLLOCTIONMANAGER UISWITCH UICOLLECTIONVIEW -
STANDARD OO STUFF NSObject UIView : UIResponder UIResponder : NSObject
Inherits from subclass Inherits from subclass -
COMPONENTS OF A CLASSES HEADER Properties, method signatures Exposed to
the outside world IMPLEMENTATION The executable code -
@import Foundation; @class PCHActivity; @interface PCHActivitySharingProvider : NSObject <NSCoder> -
(void)shareActivity:(PCHActivity *)activity; @end -
#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 -
- (void)share; - (void)shareActivity:(PCHActivity *)activity; - (void)shareActivity:(PCHActivity *)activity withName:(NSString *)name
forUser:(PCHUser *)user; + (instancetype)sharedInstance; METHODS -
@implementation PCHActivitySharingProvider PCHActivity *_activity; - (void)shareActivity:(PCHActivity *)activity { _activity =
activity; [self showShareUIWithActivity:activity]; } ... INTERNAL VARIABLES (IVARS) -
@implementation PCHActivitySharingProvider PCHActivity *_activity; - (void)setActivity:(PCHActivity *)activity { _activity =
activity; } - (PCHActivity *)activity { return _activity; } ... -
@import Foundation; @class CBCActivity; @interface CBCActivitySharingProvider : NSObject <NSCoder> @property
CBCActivity *activity; - (void)shareActivity:(CBCActivity *)activity; @end PROPERTIES -
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]; -
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 -
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 -
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 -
- (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. -
int a = 10; int b = a; b =
20; C primitives are passed by making copies. 10 ➡️ -
int a = 10; int b = a; b =
20; C primitives are passed by making copies. 10 10 ➡️ -
int a = 10; int b = a; b =
20; C primitives are passed by making copies. 10 20 ➡️ -
int a = 10; [self myAdder:a]; a = 20; C
primitives are passed by making copies. 10 - (void)myAddder:(int)c { adder += 40; } ➡️ -
int a = 10; [self myAdder:a]; a = 20; C
primitives are passed by making copies. 10 10 - (void)myAddder:(int)c { adder += 40; } ➡️ -
int a = 10; [self myAdder:a]; a = 20; C
primitives are passed by making copies. 10 50 - (void)myAddder:(int)c { adder += 40; } ➡️ -
int a = 10; [self myAdder:a]; a = 20; C
primitives are passed by making copies. 20 - (void)myAddder:(int)c { adder += 40; } ➡️ -
NSNumber *myNumber = [[NSNumber alloc] init]; NSNumber A pointer is
a variable whose value is the address of another variable. -
NSNumber *myNumber = [[NSNumber alloc] init]; A pointer is a
variable whose value is the address of another variable. NSNumber * -
NSNumber *myNumber = [[NSNumber alloc] init]; NSNumber *5 A pointer
is a variable whose value is the address of another variable. -
*0 NSNumber *myNumber; The nil value means “this instance reference
isn’t pointing to any instance.” -
*0 NSNumber *myNumber = nil; if (myNumber) { //do something
if not nil } nil is equivalent to 0 -
*5 *12 NSNumber NSNumber NSNumber *myNumber = [[NSNumber alloc] init];
NSNumber *myNumber2 = [[NSNumber alloc] init]; -
NSNumber *myNumber = [[NSNumber alloc] init]; NSNumber *myNumber2 = [[NSNumber
alloc] init]; myNumber = myNumber2; *5 *5 NSNumber NSNumber -
*5 NSMutableString *5 NSMutableString *myString = [[NSMutableString alloc] init]; [self
appendCompany:myString]; - (void)appendCompany:(NSMutableString *)string { NSLog(string); } -
*5 NSMutableString *5 NSMutableString *myString = [[NSMutableString alloc] init]; [self
appendCompany:myString]; - (void)appendCompany:(NSMutableString *)string { [string appendString:@“Test”]; } -
MEMORY MANAGEMENT Reference Counting: A trip back to the dark
ages (pre 2011) -
C primitives only exist in memory as long as they’re
in scope. How do we clean up after objects which work differently? -
*5 *12 NSNumber NSNumber NSNumber *myNumber = [[NSNumber alloc] init];
NSNumber *myNumber2 = [[NSNumber alloc] init]; myNumber = myNumber2; -
Reference counting is a way to express ownership of an
object. NSString 1 image: http://cocoadevcentral.com alloc -
Reference counting is a way to express ownership of an
object. NSString 2 -
Reference counting is a way to express ownership of an
object. NSString 1 -
Reference counting is a way to express ownership of an
object. NSString 0 -
Reference counting is a way to express ownership of an
object. NSString dealloc -
A BALANCING ACT • Alloc or copy variants: starts with
retain count of 1 • Retain: +1 • Release: -1 • Autorelease: -1 (later) -
IF YOU CREATE, YOU RELEASE - (void)shareActivity { NSDate *today
= [[NSDate alloc] init]; //do some stuff with the date [today release]; } -
AUTORELEASE TO TRANSFER OWNERSHIP - (NSDate *)todaysDate { NSDate *today
= [[NSDate alloc] init]; //do some stuff with the date return [today autorelease]; } -
TAKING OWNERSHIP PCHActivity *_activity; - (void)shareActivity:(PCHActivity *)activity { [_activity release];
_activity = [activity retain]; } - (void)dealloc { [_activity release]; [super dealloc]; } -
TAKING OWNERSHIP (COPY) PCHActivity *_activity; - (void)shareActivity:(PCHActivity *)activity { [_activity
release]; _activity = [activity copy]; } - (void)dealloc { [_activity release]; [super dealloc]; } -
2014-03-30 02:30:36.172 tradeRumors[3997:20b] *** -[GameLayer update]: message sent to deallocated
instance 0x59bf670 EXEC_BAD_ACCESS -
HANDS-ON #4 Use NSLog(@"%lu", (unsigned long)[self retainCount]) to print an
object’s current reference count -
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… -
@implementation PCHActivitySharingProvider __weak PCHActivity *_activity; - (void)shareActivity:(PCHActivity *)activity { _activity
= activity; } ... WEAK VARIABLES -
@import Foundation; @class PCHActivityMonitor; @interface PCHActivity : NSObject @property (weak)
PCHActivityMonitor *monitor; @end PROPERTY ATTRIBUTES -
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 -
@import Foundation; @class PCHActivityMonitor; @interface PCHActivity : NSObject @property (strong,
atomic, readwrite) PCHActivityMonitor *monitor; @property (assign, atomic, readwrite) BOOL isValid; @end PROPERTY DEFAULT ATTRIBUTES -
@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. -
[UIView animateWithDuration:1 animations:^{ self.view.alpha = 0.0; }]; [myDictionary enumerateKeysAndObjectsUsingBlock:^(id k,
id v, BOOL *stop) { NSLog(@"%@ => %@", k, v); }]; PASS THEM AROUND -
int (^myBlock)(int) = ^(int a) { return a * 15;
}; int result = myBlock(10); COMPONENTS OF A BLOCK -
int (^myBlock)(int) = ^(int a) { return a * 15;
}; int result = myBlock(10); DEFINITION Return type -
int (^myBlock)(int) = ^(int a) { return a * 15;
}; int result = myBlock(10); DEFINITION Name -
int (^myBlock)(int) = ^(int a) { return a * 15;
}; int result = myBlock(10); DEFINITION Parameter types -
int (^myBlock)(int) = ^(int a) { return a * 15;
}; int result = myBlock(10); IMPLEMENTATION Parameters -
int (^myBlock)(int) = ^(int a) { return a * 15;
}; int result = myBlock(10); IMPLEMENTATION Body -
- (void)upload:(NSData *)data failure:(void (^)(NSError *))errorCallback; YOUR OWN BLOCKS AS
PARAMS [server upload:myData failure:^(NSError *error) { NSLog(@“Error!?! %@“, error); }]; -
- (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 -
typedef void (^CBCErrorHandler)(NSError *); @interface PCHServerAPI - (void)upload:(NSData *)data failure:(PCHErrorHandler)callback;
- (void)synchronizeWithFailure:(PCHErrorHandler)callback; @end REUSING BLOCK DEFINITIONS -
GOTCHA #1: __BLOCK NSString *test = @“Hello!; void (^myBlock)() =
^(){ test = @“Hello 2!”; }; myBlock(); NSLog(test); -
GOTCHA #1: __BLOCK __block NSString *test = @“Hello!; void (^myBlock)()
= ^(){ test = @“Hello 2!”; }; myBlock(); NSLog(test); -
GOTCHA #2: RETAIN CYCLES @property (nonatomic, copy) void (^errorHandler)(NSError *);
... self.errorHandler = ^(){ [self displayAlert:@“Oh No!”]; }; -
_weak typeof(self) weakSelf = self; self.errorHandler = ^(){ __strong typeof(weakSelf)
strongSelf = weakSelf; if (strongSelf) { [strongSelf displayAlert:@“Oh No!”]; } }; GOTCHA #2: RETAIN CYCLES -
HANDS-ON #6 You’ll need to make sure reloadData happens on
the main thread dispatch_async(dispatch_get_main_queue(), ^{ //main thread code }); -
@implementation PCHServerAPI static CBCServerAPI *PCHServerAPISharedInstance = nil; + (instancetype)sharedServer {
static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ PCHServerAPISharedInstance = [[PCHServerAPI alloc] init]; }); return PCHServerAPISharedInstance; } -
@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; } -
DATA SOURCE / DELEGATE (PROTOCOLS) UITableView • What data should
be shown? • What happens during key events (like tapping a row)? -
- (void)awakeFromNib { [super awakeFromNib]; self.tableView.dataSource = self; } ...
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return 0; } ... CBCViewController.m -
@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 -
@interface UITableView : UIScrollView <NSCoding> ... @property (nonatomic, weak) id
<UITableViewDataSource> dataSource; ... UITableViewController.h -
NSUInteger rows = 0; if ([self.delegate respondsToSelector:@selector(tableView:numberOfRowsInSection:)]) { rows =
[self.delegate tableView:self numberOfRowsInSection:section]; } UITableViewController.m -
@interface UITableView : UIScrollView <NSCoding> ... @property (nonatomic, weak) id
<UITableViewDataSource> dataSource; @property (nonatomic, weak) id <UITableViewDelegate> delegate; ... -
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. -
@protocol PCHServerAPIDelegate <NSObject> @required - (void)downloadCountChangedTo:(NSUInteger)count; - (void)isDownloading:(BOOL)downloading; @end @interface
PCHServerAPI : NSObject @property (nonatomic, weak) id <PCHServerAPIDelegate> delegate; @end -
@interface PCHServerAPI : NSObject @property (nonatomic) NSUInteger downloadCount; @property (nonatomic)
BOOL downloading; @end WHAT IF? “Hey, tell me if these change.” -
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 -
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 -
[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 } } -
A FEW IMPROVEMENTS static void * MyContext = &MyContext; ...
- (void)observeValueForKeyPath:(NSString *)keyPath... { if (context == MyContext) { } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } -
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 -
@interface PCHServerAPI : NSObject @property (nonatomic) NSUInteger downloadCount; @property (nonatomic,
readonly) BOOL downloading; @end - (BOOL)downloading { return self.downloadCount > 0; } LETS GET TRICKY -
+ (NSSet *)keyPathsForValuesAffectingDownloading { return [NSSet setWithObjects: NSStringFromSelector(@selector(downloadCount)), nil]; }
DERIVED PROPERTIES KEYPATH RELATIONSHIPS -
- (void)setDownloadCount:(NSUInteger)downloadCount { BOOL shouldUpdateDownloading = //custom logic if (shouldUpdateDownloading)
{ [self willChangeValueForKey:@"downloading"]; } _downloadCount = downloadCount; if (shouldUpdateDownloading) { [self didChangeValueForKey:@"downloading"]; } } DERIVED PROPERTIES MANUAL NOTIFICATIONS -
Use this when you want to allow multiple objects to
listen to changes without the baggage of a protocol. -
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 -
REGISTERING id subscriptionToken; subscriptionToken = [[NSNotificationCenter defaultCenter] removeObserverForName:@“PCHDownloadStart” object:nil queue:nil
usingBlock:^(NSNotification *notification) { //do something with the notification }]; -
extern NSString * const PCHServerDownloadStartNotification; extern NSString * const PCHServerDownloadCountKey;
NSString * const PCHServerDownloadStartNotification = @“org.phillycocoa.downloadStartNotification”; NSString * const PCHServerDownloadCountKey = @"PCHServerDownloadCountKey"; CLEANUP - USE CONSTANTS -
Use this when you want to allow multiple objects to
be notified when something happens without needing any link between the objects. -
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 -
• 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). -
*0 *0 NSError *error = nil; BOOL success = [myContext
save:error]; -(BOOL)save:(NSError*)error; -
*0 *5 NSError NSError *error = nil; BOOL success =
[myContext save:error]; -(BOOL)save:(NSError*)error { error = [[NSError alloc] init]; } -
** pointer to a pointer & location in memory A
POINTER TO A POINTER? * pointer to an object -
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]; -
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. -
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 -
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. -
@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 -
@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 -
- (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 -
#import “PCHServerAPI.h” @interface PCHServerAPI() @property (nonatomic, readwrite) BOOL downloading; @end
@implementaion ... EXTENSIONS -
INSTANCETYPE Remember: id means “this could be any type” instancetype
lets the compiler infer the type and can only be used for return types -
INSTANCETYPE - (id)init { self = [super init]; return self;
} - (bool)testMethod { ... } bool test = [[AClass alloc] init] testMethod]; //error! -
INSTANCETYPE - (id)init { self = [super init]; return self;
} - (bool)testMethod { ... } bool test = [((AClass *)[AClass alloc] init]) testMethod]; //yay! -
INSTANCETYPE - (instancetype)init { self = [super init]; return self;
} - (bool)testMethod { ... } bool test = [[AClass alloc] init] testMethod]; //yay!