Good Code
Objective-C is a fairly simple language extension. In this file all available Objective-C keywords and concepts will be discussed. Absent will be library features introduced by the Foundation.
The code will be regular Objective-C, unless specifically noted to be an Objective-C extension.
Forward Declaration and Import
You can #import
and forward declare code:
#import <Foundation/Foundation.h>
// forward declarations of a class and a protocol
@protocol SomeProtocol;
@class Foreign;
Protocol
You can declare a protocol with @optional and @required methods and properties:
// declare a protocol with a required (default)
// and an optional method
@protocol MethodProtocol
@required
- (Foreign *) someMethod;
@optional
- (Foreign *) otherMethod;
// properties can be declared in protocols, yet the
// class must redeclare them
@property( assign) NSUInteger value;
@end
You can extend forward declared classes with your protocol, but this is rarely useful:
// extend Foreign class with this method, so
// we know we can message it with that protocol
@class Foreign< MethodProtocol>;
Classes
#pragma clang diagnostic ignored "-Wobjc-root-class"
@interface Foo : NSObject < MethodProtocol>
{
@public // The usual visibility modifiers except `@package`
@protected
@private
NSUInteger _value; // will be used as property "value" ivar
}
// properties and all supported property attributes
@property( readwrite, assign) NSUInteger value; // reimplement Protocol
@property( nonatomic, retain) Foreign *foreign; // creates own ivar
@property( readonly, nonnull) NSString *backedByIvar; // creates own ivar
@end
The implementation can @synthesize a property but it is superflous. The name
of the instance variable is fixed by the compiler to be '_'<name>
.
Foo
implements the required method from MethodProtocol
.
@implementation Foo
@synthesize foreign = _foreign;
- (Foreign *) someMethod;
{
return( _foreign);
}
@end
@defs is available and great for writing fast accessors, when you don’t want your instance variables to be @public:
static inline Foreign *FooGetForeign( Foo *self)
{
return( ((struct{ @defs( Foo); } *) self)->_foreign);
}
@compatibility_alias
@compatibility_alias will work as expected.
// use Foo under alias Foobar
@compatibility_alias Foobar Foo;
Protocolclasses
You can declare protocolclasses. This is a mulle-objc extension to the Objective-C language. Protocolclasses will not work on other runtimes (though they will compile):
// protocolclass Description with default implementation of -description
@class Description;
@protocol Description
- (NSString *) description;
// properties can not be declared in protocolclases yet
// @property( assign) NSUInteger value;
@end
#pragma clang diagnostic ignored "-Wobjc-root-class"
// protocolclass must implement Protocol of same name
@interface Description< Description>
@end
// implementation will serve the default implementation of description
@implementation Description
- (NSString *) description
{
return( @"VfL Bochum 1848");
}
@end
Your classes can now inherit the description protocol, but it doesn’t have to
implement -description
. It can override it though if desired:
@interface Bar : Foo < Description>
@end
@implementation Bar
// call super on protocolclass (not superclass)
- (NSString *) description
{
return( [super description]); // unvoidable warning for now
}
Keywords
PROTOCOL
PROTOCOL is a compiler keyword. Objective-C’s Protocol *
does not work, as
PROTOCOL is a kind of @selector in mulle-objc:
- (BOOL) knowsThisProtocol:(PROTOCOL) proto
{
return( @protocol( Description) == proto);
}
instancetype
instancetype behaves normally:
// instancetype keyword
- (instancetype) init
{
return( self);
}
@encode
@encode behaves normally and is 90% compatible with the Apple runtime:
// @encode keyword
+ (char *) type
{
return( @encode( Bar));
}
@autoreleasepool
You can use @autoreleasepool:
// keyword autoreleasepool
- (void) autoreleasepool
{
@autoreleasepool
{
}
}
@try …
Exception handling can be done with @try,@catch,@finally:
- (void) trycatchfinally
{
@try
{
}
@catch( NSException *e)
{
}
@finally
{
}
}
NS_DURING …
Or with old-skool NS_DURING
,NS_HANDLER
,NS_ENDHANDLER
:
// exception handling with nsduring
- (void) nsduring
{
NS_DURING
NS_HANDLER
[localException raise];
NS_ENDHANDLER
}
inout …
These @encode adornments should still work but they are not encoded and decoded:
- in
- out
- inout
- bycopy
- byref
- oneway
See: Stack Overflow
Literals
Literals are supported except @YES and @NO, but boxed versions of those are fine.
@1848,@18.48,@’A’,@(YES)
- (NSNumber *) literalInteger
{
return( @1848);
}
- (NSNumber *) literalDouble
{
return( @18.48);
}
- (NSNumber *) literalCharacter
{
return( @'A');
}
- (NSNumber *) literalBOOL
{
return( @(YES));
}
@{} and @[]
- (NSDictionary *) literalDictionary
{
return( @{ @"VfL Bochum" : @1848 });
}
- (NSArray *) literalArray
{
return( @[ @1848, @"VfL Bochum" ]);
}
@protocol
- (PROTOCOL) literalProtocol
{
return( @protocol( Description));
}
@selector
- (SEL) literalSelector
{
return( @selector( whatever:));
}
Fast Enumeration
Fast Enumeration is supported:
// fast enumeration
- (void) fastEnumerate:(NSArray *) array
{
for( id p in array)
{
}
}
@end
Checked against the list of NSHipster’s compiler directive
Putting all the pieces together
Run the main
function, to verify the truth of what’s been written (good-code.m).
int main()
{
Bar *bar;
// don't need an enclosing @autoreleasepool in mulle-objc
bar = [Bar new];
[bar fastEnumerate:[NSArray arrayWithObject:@"foo"]];
NSLog( @"description: %@", [bar description]);
NSLog( @"literalInteger: %@", [bar literalInteger]);
NSLog( @"literalDouble: %@", [bar literalDouble]);
NSLog( @"literalCharacter: %@", [bar literalCharacter]);
NSLog( @"literalArray: %@", [bar literalArray]);
NSLog( @"literalDictionary: %@", [bar literalDictionary]);
// NSStringFromProtocol is a 8.0.0 feature
#if MULLE_FOUNDATION_VERSION >= ((0 << 20) | (15 << 8) | 0)
NSLog( @"literalProtocol: %@", NSStringFromProtocol( [bar literalProtocol]));
#endif
NSLog( @"literalSelector: %@", NSStringFromSelector( [bar literalSelector]));
return( 0);
}
Expected output
description: VfL Bochum 1848
literalInteger: 1848
literalDouble: 18.480000
literalCharacter: 65
literalArray: (
1848,
VfL Bochum
)
literalDictionary: {
VfL Bochum = 1848;
}
literalSelector: <invalid selector>
Next
The followup to “good code” is of course bad code