Objective-C
iOS

Modern Objective-C ビフォーアフター

More than 3 years have passed since last update.

Adopting Modern Objective-Cより

instancetype

instancetype の方がコンパイラのチェックがきく。

before
@interface MyObject
- (id)myFactoryMethod;
@end

after
@interface MyObject
- (instancetype)myFactoryMethod;
@end

Enumeration Macros

iOS6 SDKから追加されたマクロ。

before
enum {
    UITableViewCellStyleDefault,
    UITableViewCellStyleValue1,
    UITableViewCellStyleValue2,
    UITableViewCellStyleSubtitle
};
typedef NSInteger UITableViewCellStyle;

after
typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
    UITableViewCellStyleDefault,
    UITableViewCellStyleValue1,
    UITableViewCellStyleValue2,
    UITableViewCellStyleSubtitle
};

bitmaskは NS_OPTIONS を使う

before
enum {
    UIViewAutoresizingNone                 = 0,
    UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
    UIViewAutoresizingFlexibleWidth        = 1 << 1,
    UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
    UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
    UIViewAutoresizingFlexibleHeight       = 1 << 4,
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};typedef NSUInteger UIViewAutoresizing;

after
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
    UIViewAutoresizingNone                 = 0,
    UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
    UIViewAutoresizingFlexibleWidth        = 1 << 1,
    UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
    UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
    UIViewAutoresizingFlexibleHeight       = 1 << 4,
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5};

Migrating to Modern Objective-Cより

Importing Headers

before
#import <Foundation/NSObject.h>
#import <Foundation/NSString.h> //or @class NSString;

after
#import <Foundation/Foundation.h>

--

Accessor Methods

常にアクセサメソッドを使おう。ただしinitializerやdealloc内は除く。

before
- (void)myMethod {
    // ...
    [self setTitle:[NSString stringWithFormat:@"Area: %1.2f", [self area]]];
    // ...
}

after
- (void)myMethod {
    // ...
    self.title = [NSString stringWithFormat:@"Area: %1.2f", self.area];
    // ...
}

--

Memory Management

ARCを使う。ARCはXcode 4.2(LLVM compiler 3.0)からサポート、一部の動作がコンパイラだけで解決できないためiOS5以上が必要になる。

before
NSMutableArray *array = [[NSMutableArray alloc] init];
// Use the array
[array release];

// or 

NSMutableArray *array = [[[NSMutableArray alloc] init] autoelease];
// Use the array

after
NSMutableArray *array = [NSMutableArray array];
// Use the array

--

before
id heisenObject = [[array objectAtIndex:n] retain];
[array removeObjectAtIndex:n];
// ...
[heisenObject doSomething];
[heisenObject release];
// ...

after
id heisenObject = [array objectAtIndex:n];
[array removeObjectAtIndex:n];
// ...
[heisenObject doSomething];
// ...

before
- (void)takeLastNameFrom:(Person *)person {
    NSString *oldLastname = [[self lastName] retain];
    [self setLastName:[person lastName]];
    NSLog(@"Lastname changed from %@ to %@", oldLastname, [self lastName]);
    [oldLastName release];
}

after
- (void)takeLastNameFrom:(Person *)person {
    NSString *oldLastname = [self lastName];
    [self setLastName:[person lastName]];
    NSLog(@"Lastname changed from %@ to %@", oldLastname, [self lastName]);
}

--

before
CFUUIDRef cfUUID = CFUUIDCreate(NULL);
NSString *noteUUID = (NSString *)CFUUIDCreateString(NULL, cfUUID);
CFRelease(cfUUID);

after
CFUUIDRef cfUUID = CFUUIDCreate(NULL);
NSString *noteUUID = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, cfUUID);
CFRelease(cfUUID);

--

Properties

property宣言、実装ではsynthesizeを使う。インスタンス変数に直接アクセスしたい場合は@synthesize title = _title;にして_title変数を使う。この時_titleは宣言しなくてもコンパイラが解決する。また@synthesize自体を省略することも可能(Xcode 4.4 - Apple LLVM 4.0 Compilerから)。

一つの考え方として変数 _title を参照用、プロパティ self.title = ... (setter) を更新用に使うという方法がある。(initializerやdeallocは除く)

before
@interface Thing : NSObject {
  NSString *title;
}
- (NSString *)title;
- (void)setTitle:(NSString *)newTitle;
@end

@implementation Thing
- (NSString *)title {
    return title;
}
- (void)setTitle:(NSString *)newTitle {
    if (title != newTitle) {
        [title release];
        title = [newTitle copy];
    }
}
@end

after
@interface Thing : NSObject 
@property (copy) NSString *title;
@end

@implementation Thing
@synthesize title;
@end

before
@interface Thing : NSObject {
    float radius;
}
- (float)radius;
- (void)setRadius:(float)newValue;
- (float)area;
@end

@implementation Thing
- (float)radius {
    return radius;
}
- (void)setRadius:(float)newValue {
    radius = newValue;
}
- (float)area {
    return M_PI * pow(radius, 2.0);
}
@end

after
@interface Thing : NSObject
@property float radius;
@property (readonly, nonatomic) float area;
@end

@implementation Thing
@synthesize radius;
- (float)area {
    return M_PI * pow(self.radius, 2.0)
;}
@end

before
@interface Thing : NSObject {
    id delegate;
}
- (id)delegate;
- (void)setDelegate:(id)newDelegate;
@end

@implementation Thing
- (id)delegate {
    return delegate;
}
- (void)setDelegate:(id)newDelegate {
    delegate = newDelegate;
}
@end

after
@interface Thing : NSObject
@property (weak) id delegate;
@end

@implementation Thing
@synthesize delegate;
@end

Private State

非公開な変数はクラスエクステンション内でpropertyとして宣言する。メソッドも同様にクラスエクステンションを使う。

before
@interface Thing {
   BOOL privateTest;
}

after
@interface Thing ()
@property BOOL privateTest;
@end
// ...

before
@interface Thing (PrivateMethods)
- (void)doSomethingPrivate;
- (void)doSomethingElsePrivate;
@end

after
@interface Thing ()
- (void)doSomethingPrivate;
- (void)doSomethingElsePrivate;
@end

Outlets

メモリ管理のためにstrongかweakかをproperty宣言する。

before
@interface MyViewController : MySuperclass {
    IBOutlet ElementClass *uiElement;
}
@end

after
@interface MyViewController : MySuperclass
@property (weak) IBOutlet ElementClass *uiElement;
@end

Initializer Methods and dealloc

初期化と解放はアクセサじゃなくて変数で。

before
- (id)init {
    if (self = [super init]) {
        [self setTitle:@"default"];
    }
    return self;
}

after
- (id)init {
    self = [super init];
    if (self) {
        _title = @"default";
    }
    return self;
}

before
- (void)dealloc {
    [self setTitle:nil];
    [super dealloc];
}

after
- (void)dealloc {
    [_title release];
    [super dealloc];
}

Protocols

必須ではない実装していないメソッドがあるためにidをそのまま使ったりせずにoptional宣言を使ってid型にもちゃんとprotocolを指定する。

before
@ButlerProtocol
- (void)makeTea;
- (void)serveSandwiches;
- (void)mowTheLawn;
@end

after
@protocol ButlerProtocol <NSObject>
- (void)makeTea;
- (void)serveSandwiches;
@optional
- (void)mowTheLawn;@end
before
- (id <ButlerProtocol>)butler;

after
@property id <ButlerProtocol> butler;

Collections and Literals

@ リテラルシンタックス と [] 添字シンタックスはXcode 4.4(Apple LLVM 4.0 Compiler)でサポートされたがiOSで使えるようになったのはXcode4.5から。

before
NSNumber *aNumber = [NSNumber numberWithFloat:2.3];

after
NSNumber *aNumber = @2.3f;

before
NSNumber *anotherNumber = [NSNumber numberWithFloat:x];

after
NSNumber *anotherNumber = @(x);

before
NSArray *anArray = [NSArray arrayWithObjects:aThing, @"A String",
                       [NSNumber numberWithFloat:3.14], nil];

after
NSArray *anArray = @[ aThing, @"A String", @3.14 ];

before
NSDictionary *aDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
                                value, @"Key",
                                [NSNumber numberWithBOOL:YES], @"OtherKey",                                nil];

objectivec:afterNSDictionary *aDictionary = @{ @"Key" : value, @"OtherKey" : @YES };

before
NSDictionary *distanceDict = [NSDictionary dictionaryWithObjectsAndKeys:            [NSNumber numberWithDouble:  0.0], kCIAttributeMin,
            [NSNumber numberWithDouble:  1.0], kCIAttributeMax,
            [NSNumber numberWithDouble:  0.0], kCIAttributeSliderMin,
            [NSNumber numberWithDouble:  0.7], kCIAttributeSliderMax,
            [NSNumber numberWithDouble:  0.2], kCIAttributeDefault,
            [NSNumber numberWithDouble:  0.0], kCIAttributeIdentity,
            kCIAttributeTypeScalar,            kCIAttributeType,
            nil];

after
NSDictionary *distanceDict = @{
        kCIAttributeMin       : @0.0,
        kCIAttributeMax       : @1.0,
        kCIAttributeSliderMin : @0.0,
        kCIAttributeSliderMax : @0.7,
        kCIAttributeDefault   : @0.2,
        kCIAttributeIdentity  : @0.0,
        kCIAttributeType      : kCIAttributeTypeScalar
};

before
NSDictionary *slopeDict = [NSDictionary dictionaryWithObjectsAndKeys:            [NSNumber numberWithDouble: -0.01], kCIAttributeSliderMin,
            [NSNumber numberWithDouble:  0.01], kCIAttributeSliderMax,
            [NSNumber numberWithDouble:  0.00], kCIAttributeDefault,
            [NSNumber numberWithDouble:  0.00], kCIAttributeIdentity,
            kCIAttributeTypeScalar,             kCIAttributeType,
            nil];

after
NSDictionary *slopeDict = @{
        kCIAttributeSliderMin : @-0.01,
        kCIAttributeSliderMax : @0.01,
        kCIAttributeDefault   : @0.00,
        kCIAttributeIdentity  : @0.00,
        kCIAttributeType      : kCIAttributeTypeScalar };

before
id firstElement = [anArray objectAtIndex:0];
[anArray replaceObjectAtIndex:0 withObject:newValue];

after
id firstElement = anArray[0];
anArray[0] = newValue;

before
id value = [aDictionary objectForKey:@"key"];
[aDictionary setObject:newValue forKey:@"key"];

after
id value = aDictionary[@"key"];
aDictionary[@"key"] = newValue;

before
NSArray *array = ...;
int i;
for (i = 0; i < [array count]; i++) {
    id element = [array objectAtIndex:i];
    // ... 
}

after
NSArray *array = ...;
for (id element in array) {
     // ...
}

Blocks

NSArray

ソートは(NSArray *)sortedArrayUsingComparator:(NSComparator)cmptrが使える。

before
NSArray *array = ...;
NSArray *sortedArray;
sortedArray = [array sortedArrayUsingFunction:MySort context:NULL];

NSInteger MySort(id num1, id num2, void *context) {
    NSComparisonResult result;
    // Do comparison
    return result;
}

after
NSArray *array = ...;
BOOL reverse = ...;
NSArray *sortedArray;
sortedArray = [array sortedArrayUsingComparator:^(id num1, id num2) {
    NSComparisonResult result;
    // Do comparison
    return result;
}];

--

eachには(void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))blockが使える。

before
NSArray *array = ...;
for (id element in array) {
    // ... 
}

after
NSArray *array = ...;
[array enumerateObjectsUsingBlock:
      ^(id obj, NSUInteger idx, BOOL *stop) {
      // ...
      NSLog(@"Processing %@ at index %d”, obj, idx);
      // ...
}];

--

NSDictionary

(void)enumerateKeysAndObjectsUsingBlock:(void (^)(id key, id obj, BOOL *stop))blockが使える。

before
NSDictionary *dictionary = ...;
for (NSString *key in dictionary) {
    id object = [dictionary objectForKey:key];
    // Do things with key and object.
}

after
NSDictionary *dictionary = ...;
[dictionary enumerateKeysAndObjectsUsingBlock:^(id key, id object, BOOL *stop) {
    // Do things with key and object.
}];

--

Notifications

(id)addObserverForName:(NSString *)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))blockが使える。

before
- (void)registerForNotifications {
    NSNotificationCenter *center = ...
    [center addObserver:self
        selector:@selector(windowBecameKey:)
        name:NSWindowDidBecomeKeyNotification
        object:self.window];
}
// Different context
// No queue information
- (void)windowBecameKey:(NSNotification *)notification {
    // Get contextual information.
}

after
- (void)registerForNotifications {
    NSNotificationCenter *center = ...
    MyClass *__weak weakSelf = self;
    [center addObserverForName:NSWindowDidBecomeKeyNotification
        object:self.window
        queue:[NSOperationQueue mainQueue]
        usingBlock:^(NSNotification *) {
            // ...
            [weakSelf doSomething];
            // ...
    }];
}

Blosks内でselfは直接参照せず、弱参照 MyClass *__weakに変換して使わないといけない。

--

その他