Farooq Mulla
Tech Blog
Friday, July 5, 2013
Thursday, June 27, 2013
iOS 7 Updates:
Some really useful iOS 7 design and transition documentation together in one place for easy access and redistribution. These docs are very much in flux with the rest of the new OS, but they should give you a good sense of the new design patterns in iOS 7, and may provide some good tips for creating an app which successfully bridges iOS 6 to iOS 7 while we need to support both.
Wednesday, June 12, 2013
Unit Testing BLOCK's using OCMock in Objective C
Hi,
Please find below for "Unit Testing of BLOCK's using OCMock in Objective C".
Example Class:
MyClass.h
//Here, we have a class with a delegate property and methods.
@protocol MyClassDelegate <NSObject>
-(void)DidEndWithErrorMessage:(NSString *)message;
@end
@interface MyClass : NSObject
@property (assign) id <MyClassDelegate> delegate;
//This is the method, which will return the block
- (void (^)(NSError *, id))handleRequestResponse;
- (void)getRequestDetails;
@end
MyClass.m
@implementation MyClass
@synthesize delegate = _delegate;
//Implement the method for unit testing
- (void (^)(NSError *, id))handleRequestResponse
{
void (^completionBlock)(NSError *, id) = [^(NSError *error, id response) {
if(error)
{
[[self delegate] DidEndWithErrorMessage:[error localizedDescription]];
}
} copy];
return [completionBlock autorelease];
}
- (void)getRequestDetails
{
[RequestManager requestForDetailsUsingBlock:[self handleRequestResponse]];
}
@end
And Unit Testing Classes:
MyClassTests.h
#import <SenTestingKit/SenTestingKit.h>
@interface MyClassTests : SenTestCase
@end
MyClassTests.m
#import <OCMock/OCMock.h>
#import "MyClass.h"
@interface MyClassTests ()
{
MyClass *_instance;
}
@end
@implementation MyClassTests
-(void)setUp
{
[super setUp];
_instance = [[MyClass alloc] init];
}
-(void)tearDown
{
[_instance release];
[super tearDown];
}
-(void)testCreditFacilityAccountDetailsErrorReponse
{
//set expected error string into separate local variable
NSString *errorMessage = @"Testing Blocks using OCMock";
//An error object to pass it as an argument to block
NSError *error = [[NSError alloc] initWithDomain:nil code:0 userInfo:[NSDictionary dictionaryWithObject:errorMessage forKey:NSLocalizedDescriptionKey]];
//mock the response object, which is also one of the argument to block
id mockResponse = [OCMockObject mockForClass:[NSDictionary class]];
//You can stub/expect methods here
//mock the delegate so that we can verify the error message
id mockDelegate = [OCMockObject mockForProtocol:@protocol(MyClassDelegate)];
[[mockDelegate expect] DidEndWithErrorMessage:[OCMArg checkWithBlock:^BOOL(id message)
{
STAssertEqualObjects(errorMessage, message, @"It should match the expected error message");
}]];
[_instance setDelegate:mockDelegate];
//Set the mocked delegate
//Get the response Block
void (^responseBlock)(NSError *, id) = [_instance handleRequestResponse];
//Invoking a block
responseBlock(error,mockResponse);
//Verify the mocke delegate instance
[mockDelegate verify];
[error release];
}
@end
Sunday, November 25, 2012
Revised iOS Application Life Cycle
Changes to main()
Let’s have a look at the changes to the
main()
function, which still is the starting point for our app. The function now looks like this:int main(int argc, char *argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
main()
uses the new @autoreleasepool { }
syntax introduced with LLVM 3.0, but that change doesn’t concern the app’s launch sequence. More importantly, the call to UIApplicationMain()
changed from UIApplicationMain(argc, argv, nil, nil);
to the one shown above. Note the change in the fourth argument.
Looking at the documentation, we learn that the fourth argument to
UIApplicationMain()
specifiesThe name of the class from which the application delegate is instantiated. … Specifynil
if you load the delegate object from your application’s main nib file.
So apparently our app delegate, which was previously created with Interface Builder inside
MainWindow.xib
, is now created directly by the UIApplicationMain()
function.1 In fact, there isn’t even a MainWindow.xib
file anymore in our project!No MainWindow.xib
Before Xcode 4.2, all default project templates did create a
MainWindow.xib
file. Because this file was specified in the app’s Info.plist
as the main NIB file, the UIApplicationMain()
function would load it automatically right after it had created the UIApplication
instance for the app.
It was then inside the NIB file that both the application delegate object and the app’s main window were created and wired together via outlets. More often than not,
MainWindow.xib
would also include the app’s root view controller, already connected to a custom outlet in the application delegate class and the window’s rootViewController
outlet.
With so much work done inside the main NIB file, the default
application:didFinishLaunchingWithOptions:
method could be kept almost empty. All it had to do was bring the window on screen:- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
[self.window makeKeyAndVisible];
return YES;
}
For Xcode 4.2 and later, Apple has decided to remove the main NIB file from the iOS project templates. The immediate reason for this change was most likely the introduction of storyboarding.
Storyboards are based around view controllers, not views or windows. Sticking to the concept of a main NIB file for a storyboard-based app would make no sense (and is impossible since the
Info.plist
keys NSMainNibFile
and UIMainStoryboardFile
are mutually exclusive). And abandoning the main NIB file only for storyboard-based apps but keeping it for others would lead to project templates differ from each other more than necessary.
More Work To Do in application:didFinishLaunchingWithOptions:
With the main NIB file gone, the tasks that were performed in the NIB file must now move elsewhere. We have already seen that
UIApplicationMain()
takes over one of them: the creation of our application delegate. Since the function now has control over both the UIApplication
instance and its delegate, we can also safely assume that it will assign the delegate to the application instance.
At this stage, the
application:didFinishLaunchingWithOptions:
message will be sent. Since nobody has created a window or view controller yet, the method now performs these tasks in code, at least for an app that is not storyboard-based:- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}
If your app uses storyboarding (by specifying a
UIMainStoryboardFile
in Info.plist
), the app will automatically load the main storyboard file at launch, just like the main NIB file. It will also create a window and place the storyboard’s initial view controller inside it, and bring the window on screen. Therefore, there is nothing left to do for us in application:didFinishLaunchingWithOptions:
:- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
return YES;
}
iOS Application Life Cycle
I noticed that many beginning iOS developers see the launch process of an iOS app as a bit of a mystery. Somehow, someone sends our application delegate an
The execution of every C program starts with a function called
Looking at the documentation for
Let’s take this apart step by step:
application:didFinishLaunchingWithOptions:
message, seemingly the first place where we have chance to inject code of our own. But how does our app get there? main()
The execution of every C program starts with a function called
main()
, and since Objective-C is a strict superset of C, the same must be true for an Objective-C program. If you create a new iOS project from one of the default templates, Xcode places this function in a separate file called main.m
in theSupporting Files group. Usually, you never have to look at that file but let’s do. This is the entire code ofmain()
:int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, nil);
[pool release];
return retVal;
}
The function’s arguments
argc
and argv
contain info about the command-line arguments passed to the executable on launch. We can safely ignore them for this discussion. Let’s have a look at what the function does, which seems to be very litte:- It creates an autorelease pool because in every Cocoa app one must exist at all times (otherwise, anautorelease call would fail).
- It calls a function named UIApplicationMain(). We will take a deeper look at it below.
- It drains the autorelease pool it just created.
- It returns the return value of UIApplicationMain() to its caller (which is the shell that launched the executable).
main()
, it ends. So this looks like a very short program indeed. Nevertheless, this is how all iOS apps work, so the secret must be theUIApplicationMain()
function. Should it ever return, our program would end immediately. UIApplicationMain()
Looking at the documentation for
UIApplicationMain()
, we find this:
This function instantiates the application object from the principal class and and instantiates the delegate (if any) from the given class and sets the delegate for the application. It also sets up the main event loop, including the application’s run loop, and begins processing events. If the application’s Info.plist file specifies a main nib file to be loaded, by including theNSMainNibFile key and a valid nib file name for the value, this function loads that nib file.
Despite the declared return type, this function never returns.
- First, the function creates the main application object (step 3 in the flowchart). If you specify
nil
as the third argument toUIApplicationMain()
(the default), it will create an instance ofUIApplication
in this step. This is usually what you want. However, if you need to subclassUIApplication
(for example, to override its event handling insendEvent:
), you have to pass a string with the name of your subclass toUIApplicationMain()
. - The function then looks at its fourth argument. If it is non-nil, it interprets it as the name of the class for the application delegate, instantiates an object of this class and assigns it as the application object’s
delegate
. The default for the fourth argument isnil
, though, which signifies that the app delegate will be created in the main NIB file. - Next,
UIApplicationMain()
loads and parses your app’sInfo.plist
(step 4). If it contains a key named “Main nib file base name” (NSMainNibFile
), the function will also load the NIB file specified there (step 5). - By default, the main NIB file is called
MainWindow.nib
. It contains at least an object representing the application delegate, connected to the File’s Owner’sdelegate
outlet (step 6), and aUIWindow
object that will be used as the app’s main window, connected to an outlet of the app delegate. If you used a view-controller-based app template, the NIB file will also contain your app’s root view controller and possibly one or more view child controllers.It is worth mentioning that this is the only step where the UIKit-based app templates (Window-based, View-based, Navigation-based, Tab-based, etc.) differ significantly from each other. If you started out with a view-based app and later want to introduce a navigation controller, there is no need to start a new project: simply replace the root view controller in the main NIB file and adjust one or two lines of code in the app delegate. I noticed that many newbies to the iOS platform struggle with this problem and assume a huge difference between the different project templates. There isn’t. - Now,
UIApplicationMain()
creates the application’s run loop that is used by theUIApplication
instance to process events such as touches or network events (step 7). The run loop is basically an infinite loop that causesUIApplicationMain()
to never return. - Before the application object processes the first event, it finally sends the well-known
application:didFinishLaunchingWithOptions:
message to its delegate, giving us the chance to do our own setup (step 8). The least we have to do here is put our main window on the screen by sending it amakeKeyAndVisible
message.
Entry points
You see, there is no magic here. Besides
application:didFinishLaunchingWithOptions:
, there are several more entry points for custom code during the launch sequence (none of which are usually needed):- Directly in
main()
beforeUIApplicationMain()
is called. - The
init
method of a customUIApplication
subclass. - The
initWithCoder:
orawakeFromNib
methods of our application delegate if it is created from a NIB file (the default). - The
+initialize
methods of our application delegate class or a customUIApplication
subclass. Any class receives an+initialize
message before it is sent its first message from within the program.
Note that this sequence only happens at the actual launch of an app. If the app is already running and simply brought back from the background, none of this occurs.
Saturday, November 24, 2012
Basics of Objective C
Setting up the environment
- Linux/FreeBSD: Install GNUStep
- In order to build GNUstep applications one must first execute the GNUstep.sh file in /usr/GNUstep/System/Makefiles/GNUstep.sh. This path depends on your system. Some put it in /usr, some /usr/lib, some /usr/local. If your shell is a csh/tcsh based shell, you'll want to execute GNUStep.csh instead. It's recommended that you put this script in your .bashrc or .cshrc.
- Mac OS X: Install XCode
- Windows NT 5.X: Install cygwin or mingw and then install GNUStep
Preamble
- This tutorial assumes you have some basic C knowledge, including C data types, what a function is, what a return value is, knowledge of pointers and basic memory management in C. If you haven't gotten this far, I highly suggest you pick up K and R's book, The C Language. This is the book on C written by the writers of C.
- Objective-C, being a C derivative, inherits all of C's features. There are a few exceptions but they don't really deviate from what C offers as a language.
- nil: In C/C++ you're probably used to NULL. In Objective-C it is nil. The difference is you can pass messages to nil (such as [nil message];) and this is perfectly legal. You cannot however do this with NULL.
- BOOL: C doesn't have an official boolean type, and in reality neither does Objective-C. It's however built into the Foundation classes (Namely from importing NSObject.h). nil is also included in this header file. BOOL in Objective-C has two modes, YES and NO rather than TRUE and FALSE.
- #import vs #include: As you will notice in the hello world example, #import was used. #import is supported by the gcc compiler, however it is deprecated in favor of #include. #import is basically the same thing as #ifndef #define #endif at the top and bottom of every .h file you make. I find this to be retarded, as many other programmers will most likely agree. For all purposes, just use #import. It's less hassle, and if gcc ever does remove it chances are enough Objective-C developers exist to either keep it from getting removed or getting added back in. As an aside, Apple officially uses #import in all their code so if this ever did happen, you can be certain that Apple would conviently ship a forked version of gcc to add this back in.
- The word method and message are used interchangably in Objective-C, although messages have special properties. A message can be dynamically forwarded to another object. Calling a message on an object in Objective-C doesn't mean that the object implements that message, just that it knows how to respond to it somehow via directly implementing it or forwarding the message to an object that does know how to.
Making Hello World!
- hello.m
#import <stdio.h> int main( int argc, const char *argv[] ) { printf( "hello world\n" ); return 0; }
- output
hello world
- You use #import instead of #include in Objective-C
- The default file extention for Objective-C is .m
Creating Classes
@interface
- Based on an example in "Programming in Objective-C," Copyright © 2004 by Sams Publishing. Used with permission
- Fraction.h
#import <Foundation/NSObject.h> @interface Fraction: NSObject { int numerator; int denominator; } -(void) print; -(void) setNumerator: (int) n; -(void) setDenominator: (int) d; -(int) numerator; -(int) denominator; @end
- NSObject: Short for NeXTStep Object. Although this is less meaningful today since it's really OpenStep.
- Inheritance is specified as Class: Parent, as seen with Fraction: NSObject.
- Instance variables go between @interface Class: Parent { .... }
- No access is set (protected, public, private). Default is protected. Setting the access will be shown later
- Instance methods follow after the member variables. The format is: scope (returnType) methodName: (parameter1Type) parameter1Name;
- scope refers to class or instance. instance methods begin with - class level methods begin with +
- Interface ends with @end
@implementation
- Based on an example in "Programming in Objective-C," Copyright © 2004 by Sams Publishing. Used with permission
- Fraction.m
#import "Fraction.h" #import <stdio.h> @implementation Fraction -(void) print { printf( "%i/%i", numerator, denominator ); } -(void) setNumerator: (int) n { numerator = n; } -(void) setDenominator: (int) d { denominator = d; } -(int) denominator { return denominator; } -(int) numerator { return numerator; } @end
- @implementation ClassName starts the implementation @end ends it
- All the defined methods are implemented very simlar to how they are declared in the interface
- Piecing it together
- Based on an example in "Programming in Objective-C," Copyright © 2004 by Sams Publishing. Used with permission
- main.m
#import <stdio.h> #import "Fraction.h" int main( int argc, const char *argv[] ) { // create a new instance Fraction *frac = [[Fraction alloc] init]; // set the values [frac setNumerator: 1]; [frac setDenominator: 3]; // print it printf( "The fraction is: " ); [frac print]; printf( "\n" ); // free memory [frac release]; return 0; }
- output
The fraction is: 1/3
- Fraction *frac = [[Fraction alloc] init];
- There are several important things in this one line.
- The way methods in Objective-C are called is [object method], which is similar to object->method() in C++
- Objective-C doesn't have value types, so there is nothing similar to C++'s: Fraction frac; frac.print();. You always deal with objects as pointers in Objective-C.
- What this line is really doing is two things: [Fraction alloc] is calling the alloc method on the Fraction class. This is similar to mallocing memory, because that is all that is done in this operation.
- [object init] is the constructor call, which initializes any variables in the object. This method is called on the instance returned from [Fraction alloc]. This operation is so common it's usually just done in one line as Object *var = [[Object alloc] init];
- [frac setNumerator: 1] is quite simple. It's calling the setNumerator method on frac, and passing it the parameter 1.
- Like every c variant, there's a construct for freeing memory. This is done via release, which is inherited from NSObject. This method will be explainted in greater detail later.
The details...
Multiple Parameters
- Up until this point I haven't showed any way to specify multiple parameters. It's not as intuitive at first, but it's syntax is a welcome addition from Smalltalk
- Based on an example in "Programming in Objective-C," Copyright © 2004 by Sams Publishing. Used with permission
- Fraction.h
... -(void) setNumerator: (int) n andDenominator: (int) d; ...
- Fraction.m
... -(void) setNumerator: (int) n andDenominator: (int) d { numerator = n; denominator = d; } ...
- main.m
#import <stdio.h> #import "Fraction.h" int main( int argc, const char *argv[] ) { // create a new instance Fraction *frac = [[Fraction alloc] init]; Fraction *frac2 = [[Fraction alloc] init]; // set the values [frac setNumerator: 1]; [frac setDenominator: 3]; // combined set [frac2 setNumerator: 1 andDenominator: 5]; // print it printf( "The fraction is: " ); [frac print]; printf( "\n" ); // print it printf( "Fraction 2 is: " ); [frac2 print]; printf( "\n" ); // free memory [frac release]; [frac2 release]; return 0; }
- output
The fraction is: 1/3 Fraction 2 is: 1/5
- The method is actually called setNumerator:andDenominator:
- Additional parameters are added the same was as the 2nd, such that you'd have method:label1:label2:label3: and you'd call it with [obj method: param1 label1: param2 label2: param3 label3: param4]
- Labels are optional. It's possible to have a method named method:::. This is done by simply not specifing label names, but just a : to separate the parameters. This is however not advised.
Constructors
- Based on an example in "Programming in Objective-C," Copyright © 2004 by Sams Publishing. Used with permission
- Fraction.h
... -(Fraction*) initWithNumerator: (int) n denominator: (int) d; ...
- Fraction.m
... -(Fraction*) initWithNumerator: (int) n denominator: (int) d { self = [super init]; if ( self ) { [self setNumerator: n andDenominator: d]; } return self; } ...
- main.m
#import <stdio.h> #import "Fraction.h" int main( int argc, const char *argv[] ) { // create a new instance Fraction *frac = [[Fraction alloc] init]; Fraction *frac2 = [[Fraction alloc] init]; Fraction *frac3 = [[Fraction alloc] initWithNumerator: 3 denominator: 10]; // set the values [frac setNumerator: 1]; [frac setDenominator: 3]; // combined set [frac2 setNumerator: 1 andDenominator: 5]; // print it printf( "The fraction is: " ); [frac print]; printf( "\n" ); printf( "Fraction 2 is: " ); [frac2 print]; printf( "\n" ); printf( "Fraction 3 is: " ); [frac3 print]; printf( "\n" ); // free memory [frac release]; [frac2 release]; [frac3 release]; return 0; }
- output
The fraction is: 1/3 Fraction 2 is: 1/5 Fraction 3 is: 3/10
- @interface declaration is identical to a regular function
- @implementation shows a new keyword: super
- Similar to Java, Objective-C only has one parent class.
- Accessing it's super constructor is done through [super init] and this is required for proper inheritance.
- This returns an instance which you assign to another new keyword, self. Self is similar to this in Java and C++.
- if ( self ) is the same as if ( self != nil ) to make sure that the super constructor successfully returned a new object. nil is Objective-C's form of NULL from C/C++. This is gotten from including NSObject.
- After you've initialized the varialbes, you return yourself with return self;
- The deafult constructor is -(id) init;
- Constructors in Objective-C are technically just "init" methods, they aren't a special construct like they are in C++ and Java.
Access Privledges
- The default access is @protected
- Java implements this with public/private/protected modifiers infront of methods and variables. Objective-C's approach is much more similar to C++'s for instance variables
- Access.h
#import <Foundation/NSObject.h> @interface Access: NSObject { @public int publicVar; @private int privateVar; int privateVar2; @protected int protectedVar; } @end
- Access.m
#import "Access.h" @implementation Access @end
- main.m
#import "Access.h" #import <stdio.h> int main( int argc, const char *argv[] ) { Access *a = [[Access alloc] init]; // works a->publicVar = 5; printf( "public var: %i\n", a->publicVar ); // doesn't compile //a->privateVar = 10; //printf( "private var: %i\n", a->privateVar ); [a release]; return 0; }
- output
public var: 5
- As you an see, instead of private: [list of vars] public: [list of vars] like in C++, it's just @private, @protected, etc.
Class level access
- Often it's nice to have class level variables and functions, for instance when keeping track of the # of times an object has been instanciated.
- ClassA.h
#import <Foundation/NSObject.h> static int count; @interface ClassA: NSObject +(int) initCount; +(void) initialize; @end
- ClassA.m
#import "ClassA.h" @implementation ClassA -(id) init { self = [super init]; count++; return self; } +(int) initCount { return count; } +(void) initialize { count = 0; } @end
- main.m
#import "ClassA.h" #import <stdio.h> int main( int argc, const char *argv[] ) { ClassA *c1 = [[ClassA alloc] init]; ClassA *c2 = [[ClassA alloc] init]; // print count printf( "ClassA count: %i\n", [ClassA initCount] ); ClassA *c3 = [[ClassA alloc] init]; // print count again printf( "ClassA count: %i\n", [ClassA initCount] ); [c1 release]; [c2 release]; [c3 release]; return 0; }
- output
ClassA count: 2 ClassA count: 3
- static int count = 0; This is how the class variable is declared. This is not the ideal place for such a variable. A nicer solution would have been like Java's implementation of static class variables. However this works
- +(int) initCount; This is the actual method that returns the count. Notice the subtle difference. Instead of using a - infront of the type, a + is used. The + denotes a class level function.
- Accessing the variable is no different than member variables, as seen by count++ in the constructor of ClassA.
- The +(void) initialize method is called when Objective-C starts your program, and it's called for every class. This is a good place to initialize class level variables like our count.
Exceptions
- NOTE: Exception handling is only supported in Mac OS X 10.3
- Based on an example in "Programming in Objective-C," Copyright © 2004 by Sams Publishing. Used with permission
- CupWarningException.h
#import <Foundation/NSException.h> @interface CupWarningException: NSException @end
- CupWarningException.m
#import "CupWarningException.h" @implementation CupWarningException @end
- CupOverflowException.h
#import <Foundation/NSException.h> @interface CupOverflowException: NSException @end
- CupOverflowException.m
#import "CupOverflowException.h" @implementation CupOverflowException @end
- Cup.h
#import <Foundation/NSObject.h> @interface Cup: NSObject { int level; } -(int) level; -(void) setLevel: (int) l; -(void) fill; -(void) empty; -(void) print; @end
- Cup.m
#import "Cup.h" #import "CupOverflowException.h" #import "CupWarningException.h" #import <Foundation/NSException.h> #import <Foundation/NSString.h> @implementation Cup -(id) init { self = [super init]; if ( self ) { [self setLevel: 0]; } return self; } -(int) level { return level; } -(void) setLevel: (int) l { level = l; if ( level > 100 ) { // throw overflow NSException *e = [CupOverflowException exceptionWithName: @"CupOverflowException" reason: @"The level is above 100" userInfo: nil]; @throw e; } else if ( level >= 50 ) { // throw warning NSException *e = [CupWarningException exceptionWithName: @"CupWarningException" reason: @"The level is above or at 50" userInfo: nil]; @throw e; } else if ( level < 0 ) { // throw exception NSException *e = [NSException exceptionWithName: @"CupUnderflowException" reason: @"The level is below 0" userInfo: nil]; @throw e; } } -(void) fill { [self setLevel: level + 10]; } -(void) empty { [self setLevel: level - 10]; } -(void) print { printf( "Cup level is: %i\n", level ); } @end
- main.m
#import "Cup.h" #import "CupOverflowException.h" #import "CupWarningException.h" #import <Foundation/NSString.h> #import <Foundation/NSException.h> #import <Foundation/NSAutoreleasePool.h> #import <stdio.h> int main( int argc, const char *argv[] ) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; Cup *cup = [[Cup alloc] init]; int i; // this will work for ( i = 0; i < 4; i++ ) { [cup fill]; [cup print]; } // this will throw exceptions for ( i = 0; i < 7; i++ ) { @try { [cup fill]; } @catch ( CupWarningException *e ) { printf( "%s: ", [[e name] cString] ); } @catch ( CupOverflowException *e ) { printf( "%s: ", [[e name] cString] ); } @finally { [cup print]; } } // throw a generic exception @try { [cup setLevel: -1]; } @catch ( NSException *e ) { printf( "%s: %s\n", [[e name] cString], [[e reason] cString] ); } // free memory [cup release]; [pool release]; }
- output
Cup level is: 10 Cup level is: 20 Cup level is: 30 Cup level is: 40 CupWarningException: Cup level is: 50 CupWarningException: Cup level is: 60 CupWarningException: Cup level is: 70 CupWarningException: Cup level is: 80 CupWarningException: Cup level is: 90 CupWarningException: Cup level is: 100 CupOverflowException: Cup level is: 110 CupUnderflowException: The level is below 0
- NSAutoreleasePool is a memory management class. Don't worry about what this does right now.
- Exceptions that are thrown don't have to extend NSException. You can just as easily use an id as well: @catch ( id e ) { ... }
- There is also a finally block, which behaves just like Java's. The contents of a finally block are guaranteed to be called.
- The string as show in Cup.m, @"CupOverflowException", is a constant NSString object. The @ sign is used often in Objective-C to denote extentions to the language. A C string is just like C and C++, "String constant", and is of type char *.
Inheritance, Polymorphism, and other OOP features
The id type
- Objective-C has a type called id, that acts in some ways like a void*, though it's meant strictly for objects. Objective-C differs from Java and C++ in that when you call a method on an object, it doesn't need to know the type. That method simply just has to exist. This is refered to as message pasing in Objective-C.
- Based on an example in "Programming in Objective-C," Copyright © 2004 by Sams Publishing. Used with permission
- Fraction.h
#import <Foundation/NSObject.h> @interface Fraction: NSObject { int numerator; int denominator; } -(Fraction*) initWithNumerator: (int) n denominator: (int) d; -(void) print; -(void) setNumerator: (int) d; -(void) setDenominator: (int) d; -(void) setNumerator: (int) n andDenominator: (int) d; -(int) numerator; -(int) denominator; @end
- Fraction.m
#import "Fraction.h" #import <stdio.h> @implementation Fraction -(Fraction*) initWithNumerator: (int) n denominator: (int) d { self = [super init]; if ( self ) { [self setNumerator: n andDenominator: d]; } return self; } -(void) print { printf( "%i / %i", numerator, denominator ); } -(void) setNumerator: (int) n { numerator = n; } -(void) setDenominator: (int) d { denominator = d; } -(void) setNumerator: (int) n andDenominator: (int) d { numerator = n; denominator = d; } -(int) denominator { return denominator; } -(int) numerator { return numerator; } @end
- Complex.h
#import <Foundation/NSObject.h> @interface Complex: NSObject { double real; double imaginary; } -(Complex*) initWithReal: (double) r andImaginary: (double) i; -(void) setReal: (double) r; -(void) setImaginary: (double) i; -(void) setReal: (double) r andImaginary: (double) i; -(double) real; -(double) imaginary; -(void) print; @end
- Complex.m
#import "Complex.h" #import <stdio.h> @implementation Complex -(Complex*) initWithReal: (double) r andImaginary: (double) i { self = [super init]; if ( self ) { [self setReal: r andImaginary: i]; } return self; } -(void) setReal: (double) r { real = r; } -(void) setImaginary: (double) i { imaginary = i; } -(void) setReal: (double) r andImaginary: (double) i { real = r; imaginary = i; } -(double) real { return real; } -(double) imaginary { return imaginary; } -(void) print { printf( "%_f + %_fi", real, imaginary ); } @end
- main.m
#import <stdio.h> #import "Fraction.h" #import "Complex.h" int main( int argc, const char *argv[] ) { // create a new instance Fraction *frac = [[Fraction alloc] initWithNumerator: 1 denominator: 10]; Complex *comp = [[Complex alloc] initWithReal: 10 andImaginary: 15]; id number; // print fraction number = frac; printf( "The fraction is: " ); [number print]; printf( "\n" ); // print complex number = comp; printf( "The complex number is: " ); [number print]; printf( "\n" ); // free memory [frac release]; [comp release]; return 0; }
- output
The fraction is: 1 / 10 The complex number is: 10.000000 + 15.000000i
- There are obvious benefits to this type of dynamic binding. You don't have to know the type of something to call a method on it. If the object responds to a message, it will invoke that method. Lots of nasty casting isn't involved in this either, such as in Java to call .intValue() on an integer object would involve casting first, then calling the method.
Inheritance
- Based on an example in "Programming in Objective-C," Copyright © 2004 by Sams Publishing. Used with permission
- Rectangle.h
#import <Foundation/NSObject.h> @interface Rectangle: NSObject { int width; int height; } -(Rectangle*) initWithWidth: (int) w height: (int) h; -(void) setWidth: (int) w; -(void) setHeight: (int) h; -(void) setWidth: (int) w height: (int) h; -(int) width; -(int) height; -(void) print; @end
- Rectangle.m
#import "Rectangle.h" #import <stdio.h> @implementation Rectangle -(Rectangle*) initWithWidth: (int) w height: (int) h { self = [super init]; if ( self ) { [self setWidth: w height: h]; } return self; } -(void) setWidth: (int) w { width = w; } -(void) setHeight: (int) h { height = h; } -(void) setWidth: (int) w height: (int) h { width = w; height = h; } -(int) width { return width; } -(int) height { return height; } -(void) print { printf( "width = %i, height = %i", width, height ); } @end
- Square.h
#import "Rectangle.h" @interface Square: Rectangle -(Square*) initWithSize: (int) s; -(void) setSize: (int) s; -(int) size; @end
- Square.m
#import "Square.h" @implementation Square -(Square*) initWithSize: (int) s { self = [super init]; if ( self ) { [self setSize: s]; } return self; } -(void) setSize: (int) s { width = s; height = s; } -(int) size { return width; } -(void) setWidth: (int) w { [self setSize: w]; } -(void) setHeight: (int) h { [self setSize: h]; } @end
- main.m
#import "Square.h" #import "Rectangle.h" #import <stdio.h> int main( int argc, const char *argv[] ) { Rectangle *rec = [[Rectangle alloc] initWithWidth: 10 height: 20]; Square *sq = [[Square alloc] initWithSize: 15]; // print em printf( "Rectangle: " ); [rec print]; printf( "\n" ); printf( "Square: " ); [sq print]; printf( "\n" ); // update square [sq setWidth: 20]; printf( "Square after change: " ); [sq print]; printf( "\n" ); // free memory [rec release]; [sq release]; return 0; }
- output
Rectangle: width = 10, height = 20 Square: width = 15, height = 15 Square after change: width = 20, height = 20
- Inheritance in Objective-C is similar to Java. When you extend your super class (of which you can only have one parent) you can override the methods of your super class by simply putting the new implementations in the child classes implementation. No fooling with virtual tables like C++.
- One thing left out here that is worth nothing is what would happen if you attempted to call the constructor for rectangle like: Square *sq = [[Square alloc] initWithWidth: 10 height: 15]. The answer is it will throw a compile error. Since the return type of the rectangle constructor is Rectangle*, not Square* this would not work. In such a case if you want this to occur, that's what the id variable is good for. Just change the Rectangle* return type to id if you wish to use your parent's constructors in a subclass.
Dynamic types
- There are several methods for working with dynamic types in Objective-C
- Every object inherited from NSObject has a class method that returns a class object. This is very similar to Java's getClass() method. This class object is used in the methods above.
- Selectors are used to represent a message in Objective-C. The syntax for creating a selector is shown in the next example
- Based on an example in "Programming in Objective-C," Copyright © 2004 by Sams Publishing. Used with permission
- main.m
#import "Square.h" #import "Rectangle.h" #import <stdio.h> int main( int argc, const char *argv[] ) { Rectangle *rec = [[Rectangle alloc] initWithWidth: 10 height: 20]; Square *sq = [[Square alloc] initWithSize: 15]; // isMemberOfClass // true if ( [sq isMemberOfClass: [Square class]] == YES ) { printf( "square is a member of square class\n" ); } // false if ( [sq isMemberOfClass: [Rectangle class]] == YES ) { printf( "square is a member of rectangle class\n" ); } // false if ( [sq isMemberOfClass: [NSObject class]] == YES ) { printf( "square is a member of object class\n" ); } // isKindOfClass // true if ( [sq isKindOfClass: [Square class]] == YES ) { printf( "square is a kind of square class\n" ); } // true if ( [sq isKindOfClass: [Rectangle class]] == YES ) { printf( "square is a kind of rectangle class\n" ); } // true if ( [sq isKindOfClass: [NSObject class]] == YES ) { printf( "square is a kind of object class\n" ); } // respondsToSelector // true if ( [sq respondsToSelector: @selector( setSize: )] == YES ) { printf( "square responds to setSize: method\n" ); } // false if ( [sq respondsToSelector: @selector( nonExistant )] == YES ) { printf( "square responds to nonExistant method\n" ); } // true if ( [Square respondsToSelector: @selector( alloc )] == YES ) { printf( "square class responds to alloc method\n" ); } // instancesRespondToSelector // false if ( [Rectangle instancesRespondToSelector: @selector( setSize: )] == YES ) { printf( "rectangle instance responds to setSize: method\n" ); } // true if ( [Square instancesRespondToSelector: @selector( setSize: )] == YES ) { printf( "square instance responds to setSize: method\n" ); } // free memory [rec release]; [sq release]; return 0; }
- output
square is a member of square class square is a kind of square class square is a kind of rectangle class square is a kind of object class square responds to setSize: method square class responds to alloc method square instance responds to setSize: method
Categories
- When you want to add methods to a class, you typically extend it. However this solution isn't always perfect, especially if you want to rewrite the functionality of a class that you don't have the source code to. Categories allow you to add functionality to already existing classes without extending them. Ruby also has similar functionality to this.
- Based on an example in "Programming in Objective-C," Copyright © 2004 by Sams Publishing. Used with permission
- FractionMath.h
#import "Fraction.h" @interface Fraction (Math) -(Fraction*) add: (Fraction*) f; -(Fraction*) mul: (Fraction*) f; -(Fraction*) div: (Fraction*) f; -(Fraction*) sub: (Fraction*) f; @end
- FractionMath.m
#import "FractionMath.h" @implementation Fraction (Math) -(Fraction*) add: (Fraction*) f { return [[Fraction alloc] initWithNumerator: numerator * [f denominator] + denominator * [f numerator] denominator: denominator * [f denominator]]; } -(Fraction*) mul: (Fraction*) f { return [[Fraction alloc] initWithNumerator: numerator * [f numerator] denominator: denominator * [f denominator]]; } -(Fraction*) div: (Fraction*) f { return [[Fraction alloc] initWithNumerator: numerator * [f denominator] denominator: denominator * [f numerator]]; } -(Fraction*) sub: (Fraction*) f { return [[Fraction alloc] initWithNumerator: numerator * [f denominator] - denominator * [f numerator] denominator: denominator * [f denominator]]; } @end
- main.m
#import <stdio.h> #import "Fraction.h" #import "FractionMath.h" int main( int argc, const char *argv[] ) { // create a new instance Fraction *frac1 = [[Fraction alloc] initWithNumerator: 1 denominator: 3]; Fraction *frac2 = [[Fraction alloc] initWithNumerator: 2 denominator: 5]; Fraction *frac3 = [frac1 mul: frac2]; // print it [frac1 print]; printf( " * " ); [frac2 print]; printf( " = " ); [frac3 print]; printf( "\n" ); // free memory [frac1 release]; [frac2 release]; [frac3 release]; return 0; }
- output
1/3 * 2/5 = 2/15
- The magic here is the two @implementation and @interface lines: @interface Fraction (Math) and @implementation Fraction (Math).
- There can only be one category with the same name. Additional cateogies may be added on with different but unqiue names.
- Categories can't add instance variables.
- Categories are useful for creating private methods. Since Objective-C has no notion of private/protected/public methods like java does, one has to create categories that hide such functionality. The way this is done is by moving the private methods from your class's header (.h) file to the implementation file (.m). The following is a very brief example of what I mean.
- MyClass.h
#import <Foundation/NSObject.h> @interface MyClass: NSObject -(void) publicMethod; @end
- MyClass.m
#import "MyClass.h" #import <stdio.h> @implementation MyClass -(void) publicMethod { printf( "public method\n" ); } @end // private methods @interface MyClass (Private) -(void) privateMethod; @end @implementation MyClass (Private) -(void) privateMethod { printf( "private method\n" ); } @end
- main.m
#import "MyClass.h" int main( int argc, const char *argv[] ) { MyClass *obj = [[MyClass alloc] init]; // this compiles [obj publicMethod]; // this throws errors when compiling //[obj privateMethod]; // free memory [obj release]; return 0; }
- output
public method
Posing
- Posing is similar to categories, but with a twist. It allows you to extend a class, and make your subclass pose (in place of) the super class globally. For instance: Say you have NSArrayChild that extends NSArray. If you made NSArrayChild pose for NSArray all your code would begin using the NSArrayChild instead of NSArray automatically.
- Based on an example in "Programming in Objective-C," Copyright © 2004 by Sams Publishing. Used with permission
- FractionB.h
#import "Fraction.h" @interface FractionB: Fraction -(void) print; @end
- FractionB.m
#import "FractionB.h" #import <stdio.h> @implementation FractionB -(void) print { printf( "(%i/%i)", numerator, denominator ); } @end
- main.m
#import <stdio.h> #import "Fraction.h" #import "FractionB.h" int main( int argc, const char *argv[] ) { Fraction *frac = [[Fraction alloc] initWithNumerator: 3 denominator: 10]; // print it printf( "The fraction is: " ); [frac print]; printf( "\n" ); // make FractionB pose as Fraction [FractionB poseAsClass: [Fraction class]]; Fraction *frac2 = [[Fraction alloc] initWithNumerator: 3 denominator: 10]; // print it printf( "The fraction is: " ); [frac2 print]; printf( "\n" ); // free memory [frac release]; [frac2 release]; return 0; }
- output
The fraction is: 3/10 The fraction is: (3/10)
- The output from this program would print the first fraction s 3/10. The second would output (3/10), which is implemented by FractionB.
- The method poseAsClass is part of NSObject. This allows a subclass to pose as a superclass.
Protocols
- A Protocol in Objective-C is identical in functionality to an interface in Java, or a purely virtual class in C++.
- Based on an example in "Programming in Objective-C," Copyright © 2004 by Sams Publishing. Used with permission
- Printing.h
@protocol Printing -(void) print; @end
- Fraction.h
#import <Foundation/NSObject.h> #import "Printing.h" @interface Fraction: NSObject <Printing, NSCopying> { int numerator; int denominator; } -(Fraction*) initWithNumerator: (int) n denominator: (int) d; -(void) setNumerator: (int) d; -(void) setDenominator: (int) d; -(void) setNumerator: (int) n andDenominator: (int) d; -(int) numerator; -(int) denominator; @end
- Fraction.m
#import "Fraction.h" #import <stdio.h> @implementation Fraction -(Fraction*) initWithNumerator: (int) n denominator: (int) d { self = [super init]; if ( self ) { [self setNumerator: n andDenominator: d]; } return self; } -(void) print { printf( "%i/%i", numerator, denominator ); } -(void) setNumerator: (int) n { numerator = n; } -(void) setDenominator: (int) d { denominator = d; } -(void) setNumerator: (int) n andDenominator: (int) d { numerator = n; denominator = d; } -(int) denominator { return denominator; } -(int) numerator { return numerator; } -(Fraction*) copyWithZone: (NSZone*) zone { return [[Fraction allocWithZone: zone] initWithNumerator: numerator denominator: denominator]; } @end
- Complex.h
#import <Foundation/NSObject.h> #import "Printing.h" @interface Complex: NSObject <Printing> { double real; double imaginary; } -(Complex*) initWithReal: (double) r andImaginary: (double) i; -(void) setReal: (double) r; -(void) setImaginary: (double) i; -(void) setReal: (double) r andImaginary: (double) i; -(double) real; -(double) imaginary; @end
- Complex.m
#import "Complex.h" #import <stdio.h> @implementation Complex -(Complex*) initWithReal: (double) r andImaginary: (double) i { self = [super init]; if ( self ) { [self setReal: r andImaginary: i]; } return self; } -(void) setReal: (double) r { real = r; } -(void) setImaginary: (double) i { imaginary = i; } -(void) setReal: (double) r andImaginary: (double) i { real = r; imaginary = i; } -(double) real { return real; } -(double) imaginary { return imaginary; } -(void) print { printf( "%_f + %_fi", real, imaginary ); } @end
- main.m
#import <stdio.h> #import "Fraction.h" #import "Complex.h" int main( int argc, const char *argv[] ) { // create a new instance Fraction *frac = [[Fraction alloc] initWithNumerator: 3 denominator: 10]; Complex *comp = [[Complex alloc] initWithReal: 5 andImaginary: 15]; id <Printing> printable; id <NSCopying, Printing> copyPrintable; // print it printable = frac; printf( "The fraction is: " ); [printable print]; printf( "\n" ); // print complex printable = comp; printf( "The complex number is: " ); [printable print]; printf( "\n" ); // this compiles because Fraction comforms to both Printing and NSCopyable copyPrintable = frac; // this doesn't compile because Complex only conforms to Printing //copyPrintable = comp; // test conformance // true if ( [frac conformsToProtocol: @protocol( NSCopying )] == YES ) { printf( "Fraction conforms to NSCopying\n" ); } // false if ( [comp conformsToProtocol: @protocol( NSCopying )] == YES ) { printf( "Complex conforms to NSCopying\n" ); } // free memory [frac release]; [comp release]; return 0; }
- output
The fraction is: 3/10 The complex number is: 5.000000 + 15.000000i Fraction conforms to NSCopying
- The protocol specification is quite simple. it is basically @protocol ProtocolName (methods you must implement) @end.
- To conform to a protocol, you put the protocols you're conforming to in <>'s, and comma separate them. Example: @interface SomeClass <Protocol1, Protocol2, Protocol3>
- The methods that the protocol requires to be implemented are not required to be in the list of methods for the header file. As you can see, Complex.h doesn't have a definition for -(void) print, but it still implements it since it conforms to the protocol.
- One unique aspect of Objective-C's interface system is how you specify types. Rather than specifying it like Java or C++ as: Printing *someVar = ( Printing * ) frac; for example, you use the id type with a restricted protocol: id <Printing> var = frac; This allows you to dynamically specify a type that requires multiple protocols, all with one variable. Such as: id <Printing, NSCopying> var = frac;
- Much like using @selector for testing an object's inheritance, you can use @protocol to test for conformance of interfaces. [object conformsToProtocol: @protocol( SomeProtocol )] returns a BOOL if the object conforms to that protocol. This works the same for classes as well: [SomeClass conformsToProtocol: @protocol( SomeProtocol )].
Memory Management
- Up until now I've kind of dodged memory management in Objective-C. Sure you can call dealloc on an object, but what happens if the object contains pointers to other objects? One has to be concerned about freeing the memory of those objects as well. Also how does the Foundation framework manage memory when you create classes from it? This will all be explained.
- Note: everything up until this point has been properly memory managed, incase you're wondering.
Retain and Release
- Retain and release are two methods inherited from any object that has NSObject as a parent. Each object has an internal counter that can be used to keep track of the number references an object has. So if you have 3 referneces, you don't want to dealloc yourself. However once you reach 0, you should dealloc yourself. [object retain] increments the counter by 1 (which starts at 1) and [object release] decrements it by 1. If the [object release] invocation causes the count to reach 0, dealloc is then called.
- Fraction.m
... -(void) dealloc { printf( "Deallocing fraction\n" ); [super dealloc]; } ...
- Based on an example in "Programming in Objective-C," Copyright © 2004 by Sams Publishing. Used with permission
- main.m
#import "Fraction.h" #import <stdio.h> int main( int argc, const char *argv[] ) { Fraction *frac1 = [[Fraction alloc] init]; Fraction *frac2 = [[Fraction alloc] init]; // print current counts printf( "Fraction 1 retain count: %i\n", [frac1 retainCount] ); printf( "Fraction 2 retain count: %i\n", [frac2 retainCount] ); // increment them [frac1 retain]; // 2 [frac1 retain]; // 3 [frac2 retain]; // 2 // print current counts printf( "Fraction 1 retain count: %i\n", [frac1 retainCount] ); printf( "Fraction 2 retain count: %i\n", [frac2 retainCount] ); // decrement [frac1 release]; // 2 [frac2 release]; // 1 // print current counts printf( "Fraction 1 retain count: %i\n", [frac1 retainCount] ); printf( "Fraction 2 retain count: %i\n", [frac2 retainCount] ); // release them until they dealloc themselves [frac1 release]; // 1 [frac1 release]; // 0 [frac2 release]; // 0 }
- output
Fraction 1 retain count: 1 Fraction 2 retain count: 1 Fraction 1 retain count: 3 Fraction 2 retain count: 2 Fraction 1 retain count: 2 Fraction 2 retain count: 1 Deallocing fraction Deallocing fraction
- The retain calls increment the counter. The release calls decrement it. One can get the count as an int by calling [obj retainCount]. Once the retainCount reaches 0, both objects dealloc themselves and you can see this when both print out "Deallocing fraction."
Dealloc
- When your object contains other objects, you must free them whenever you yourself dealloc. One of the nice advantages to Objective-C is you can pass messages to nil, so there isn't a lot of error checking to release an object.
- Based on an example in "Programming in Objective-C," Copyright © 2004 by Sams Publishing. Used with permission
- AddressCard.h
#import <Foundation/NSObject.h> #import <Foundation/NSString.h> @interface AddressCard: NSObject { NSString *first; NSString *last; NSString *email; } -(AddressCard*) initWithFirst: (NSString*) f last: (NSString*) l email: (NSString*) e; -(NSString*) first; -(NSString*) last; -(NSString*) email; -(void) setFirst: (NSString*) f; -(void) setLast: (NSString*) l; -(void) setEmail: (NSString*) e; -(void) setFirst: (NSString*) f last: (NSString*) l email: (NSString*) e; -(void) setFirst: (NSString*) f last: (NSString*) l; -(void) print; @end
- AddressCard.m
#import "AddressCard.h" #import <stdio.h> @implementation AddressCard -(AddressCard*) initWithFirst: (NSString*) f last: (NSString*) l email: (NSString*) e { self = [super init]; if ( self ) { [self setFirst: f last: l email: e]; } return self; } -(NSString*) first { return first; } -(NSString*) last { return last; } -(NSString*) email { return email; } -(void) setFirst: (NSString*) f { [f retain]; [first release]; first = f; } -(void) setLast: (NSString*) l { [l retain]; [last release]; last = l; } -(void) setEmail: (NSString*) e { [e retain]; [email release]; email = e; } -(void) setFirst: (NSString*) f last: (NSString*) l email: (NSString*) e { [self setFirst: f]; [self setLast: l]; [self setEmail: e]; } -(void) setFirst: (NSString*) f last: (NSString*) l { [self setFirst: f]; [self setLast: l]; } -(void) print { printf( "%s %s <%s>", [first cString], [last cString], [email cString] ); } -(void) dealloc { [first release]; [last release]; [email release]; [super dealloc]; } @end
- main.m
#import "AddressCard.h" #import <Foundation/NSString.h> #import <stdio.h> int main( int argc, const char *argv[] ) { NSString *first =[[NSString alloc] initWithCString: "Tom"]; NSString *last = [[NSString alloc] initWithCString: "Jones"]; NSString *email = [[NSString alloc] initWithCString: "tom@jones.com"]; AddressCard *tom = [[AddressCard alloc] initWithFirst: first last: last email: email]; // we're done with the strings, so we must dealloc them [first release]; [last release]; [email release]; // print to show the retain count printf( "Retain count: %i\n", [[tom first] retainCount] ); [tom print]; printf( "\n" ); // free memory [tom release]; return 0; }
- output
Retain count: 1 Tom Jones <tom@jones.com>
- This example shows not only how to make a dealloc method, as shown in AddressCard.m, but one way to do member variables.
- The order of the 3 operations in each set method is very important. Lets say you'return passing a parameter of yourself to one of your methods (a bit of an odd example, but this can happen). If you release first, THEN retain you will destruct yourself! That's why you should always 1) retain 2) release 3) set the value.
- Normally one wouldn't initialize variables with C strings because they don't support unicode. The next example, with NSAutoreleasePool shows the proper way to do strings and initializing.
- This is just one way of handling member variable memory management. One way to handle this is to create copies inside your set methods.
Autorelease Pool
- When you want to start doing more programming using NSString and other Foundation framework classes you need a more flexible system. This system is using Autorelease pools.
- When developing Mac Cocoa applications, the auto release pool is setup automatically for you.
- Based on an example in "Programming in Objective-C," Copyright © 2004 by Sams Publishing. Used with permission
- main.m
#import <Foundation/NSString.h> #import <Foundation/NSAutoreleasePool.h> #import <stdio.h> int main( int argc, const char *argv[] ) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSString *str1 = @"constant string"; NSString *str2 = [NSString stringWithString: @"string managed by the pool"]; NSString *str3 = [[NSString alloc] initWithString: @"self managed string"]; // print the strings printf( "%s retain count: %x\n", [str1 cString], [str1 retainCount] ); printf( "%s retain count: %x\n", [str2 cString], [str2 retainCount] ); printf( "%s retain count: %x\n", [str3 cString], [str3 retainCount] ); // free memory [str3 release]; // free pool [pool release]; return 0; }
- output
constant string retain count: ffffffff string managed by the pool retain count: 1 self managed string retain count: 1
- If you run this you'll notice a few things. One is that the retainCount of str1 is ffffffff.
- The other is, I only release str3, yet this program is memory management perfect. The reason is the first constant string is added to the autorelease pool automatically. The other string is made using stringWithString. This method creates a string that is owned by NSString class, which also puts it in the auto release pool.
- It's important to remember, for proper memory management, that convience methods like [NSString stringWithString: @"String"] use autorelease pools, but alloc methods like [[NSString alloc] initWithString: @"String"] do not use autorelease pools for managing memory.
- There are two ways to manage memory in Objective-C: 1) retain and release or 2) retain and release/autorelease.
- For each retain, there must be one release OR one autorelease.
- The following example shows what I mean by this
- Based on an example in "Programming in Objective-C," Copyright © 2004 by Sams Publishing. Used with permission
- Fraction.h
... +(Fraction*) fractionWithNumerator: (int) n denominator: (int) d; ...
- Fraction.m
... +(Fraction*) fractionWithNumerator: (int) n denominator: (int) d { Fraction *ret = [[Fraction alloc] initWithNumerator: n denominator: d]; [ret autorelease]; return ret; } ...
- main.m
#import <Foundation/NSAutoreleasePool.h> #import "Fraction.h" #import <stdio.h> int main( int argc, const char *argv[] ) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; Fraction *frac1 = [Fraction fractionWithNumerator: 2 denominator: 5]; Fraction *frac2 = [Fraction fractionWithNumerator: 1 denominator: 3]; // print frac 1 printf( "Fraction 1: " ); [frac1 print]; printf( "\n" ); // print frac 2 printf( "Fraction 2: " ); [frac2 print]; printf( "\n" ); // this causes a segmentation fault //[frac1 release]; // release the pool and all objects in it [pool release]; return 0; }
- output
Fraction 1: 2/5 Fraction 2: 1/3
- In this example, the method is a class level method. After the object is created, autorelease is called on it. Inside the body of the main method, I never call release on the object.
- The reason this works is because: for every retain, one release or autorelease must be called. The object's retain count starts out as 1, and I called autorelease on it once. This means 1 - 1 = 0. Once the autorelease pool is released, it counts the autorelease calls on all objects and decrements them with [obj release] with the same number of times autorelease was called per object.
- As the comment says, uncommenting that line causes a segment fault. Since autorelease was already called on the object, calling release on it, and then releasing the autorelease pool would attempt to call dealloc on an object that is nil, which is not valid. The end math is 1 (creation) - 1 (release) - 1 (autorelease) = -1.
- Auto release pools can be dynamically created for large amounts of temporary objects. All one must do is create a pool, perform any large chunk of code that creates lots of temporary objects, then release the pool. As you may wonder, it this means it is possible to have more than one auto release pool at a time.
Foundation framework classes
- The Foundation framework is similar to C++'s Standard Template Library. Although since Objective-C has real dynamic types, the horrible cludge that is C++'s templating is not necessary. This framework contains collections, networking, threading, and much more.
NSArray
- Based on an example in "Programming in Objective-C," Copyright © 2004 by Sams Publishing. Used with permission
- main.m
#import <Foundation/NSArray.h> #import <Foundation/NSString.h> #import <Foundation/NSAutoreleasePool.h> #import <Foundation/NSEnumerator.h> #import <stdio.h> void print( NSArray *array ) { NSEnumerator *enumerator = [array objectEnumerator]; id obj; while ( obj = [enumerator nextObject] ) { printf( "%s\n", [[obj description] cString] ); } } int main( int argc, const char *argv[] ) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSArray *arr = [[NSArray alloc] initWithObjects: @"Me", @"Myself", @"I", nil]; NSMutableArray *mutable = [[NSMutableArray alloc] init]; // enumerate over items printf( "----static array\n" ); print( arr ); // add stuff [mutable addObject: @"One"]; [mutable addObject: @"Two"]; [mutable addObjectsFromArray: arr]; [mutable addObject: @"Three"]; // print em printf( "----mutable array\n" ); print( mutable ); // sort then print printf( "----sorted mutable array\n" ); [mutable sortUsingSelector: @selector( caseInsensitiveCompare: )]; print( mutable ); // free memory [arr release]; [mutable release]; [pool release]; return 0; }
- output
----static array Me Myself I ----mutable array One Two Me Myself I Three ----sorted mutable array I Me Myself One Three Two
- There are two kinds of arrays (and of usually most data oriented Foundation classes) NSArray and NSMutableArray. As the name suggests, Mutable is changable, NSArray then is not. This means you can make an NSArray but once you have you can't change the length.
- You initialize an array via the constructor using Obj, Obj, Obj, ..., nil. The nil is an ending delimiter.
- The sorting shows how to sort an object using a selector. The selector tells the array to sort using NSString's case insensitive compare. If your object has several sort methods, you can choose anyone you want using this selector.
- In the print method, I used the method description. This is similar to Java's toString. It returns an NSString representation of an object.
- NSEnumerator is similar to Java's enumerator system. The reason why while ( obj = [array objectEnumerator] ) works is because objectEnumerator returns nil on the last object. Since in C nil is usually 0, this is the same as false. ( ( obj = [array objectEnumerator] ) != nil ) might be preferable
NSDictionary
- Based on an example in "Programming in Objective-C," Copyright © 2004 by Sams Publishing. Used with permission
- main.m
#import <Foundation/NSString.h> #import <Foundation/NSAutoreleasePool.h> #import <Foundation/NSDictionary.h> #import <Foundation/NSEnumerator.h> #import <Foundation/Foundation.h> #import <stdio.h> void print( NSDictionary *map ) { NSEnumerator *enumerator = [map keyEnumerator]; id key; while ( key = [enumerator nextObject] ) { printf( "%s => %s\n", [[key description] cString], [[[map objectForKey: key] description] cString] ); } } int main( int argc, const char *argv[] ) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSDictionary *dictionary = [[NSDictionary alloc] initWithObjectsAndKeys: @"one", [NSNumber numberWithInt: 1], @"two", [NSNumber numberWithInt: 2], @"three", [NSNumber numberWithInt: 3], nil]; NSMutableDictionary *mutable = [[NSMutableDictionary alloc] init]; // print dictionary printf( "----static dictionary\n" ); print( dictionary ); // add objects [mutable setObject: @"Tom" forKey: @"tom@jones.com"]; [mutable setObject: @"Bob" forKey: @"bob@dole.com" ]; // print mutable dictionary printf( "----mutable dictionary\n" ); print( mutable ); // free memory [dictionary release]; [mutable release]; [pool release]; return 0; }
- output
----static dictionary 1 => one 2 => two 3 => three ----mutable dictionary bob@dole.com => Bob tom@jones.com => Tom
-(BOOL) isKindOfClass: classObj | is object a descendent or member of classObj |
-(BOOL) isMemberOfClass: classObj | is object a member of classObj |
-(BOOL) respondsToSelector: selector | does the object have a method named specifiec by the selector |
+(BOOL) instancesRespondToSelector: selector | does an object created by this class have the ability to respond to the specified selector |
-(id) performSelector: selector | invoke the specified selector on the object |
Subscribe to:
Posts (Atom)