2012/07/08

Objective-C在Xcode 4.4版裡的新增功能

2012年Apple年度大會WWDC(Apple Worldwide Developers Conference)的影片可以下載囉。

底下記錄一些未來的Objective-C將會擁有的新功能與新特色,在Xcode版本4.4以後的編譯器才有這些新功能,編譯出來的程式可以相容於先前版本的系統。

參考影片:
Session 405 - Modern Objective-C
Session 413 - Migrating to Modern Objective-C






首先,跟類別裡方法的宣告順序有關。譬如說,在類別裡有個私有的方法,該怎麼辦?

// SongPlayer.h
@interface SongPlayer : NSObject
{
}
- (void)playSong:(Song *)song;
@end

// SongPlayer.m
@implementation SongPlayer
- (void)playSong:(Song *)song
{
  NSError *error;
  [self startAudio:&error];
  //...
}

- (void)startAudio:(NSError **)error
{
  //...
}
@end

其中playSong是公開的介面,而startAudio是內部私用的方法。上面這段程式碼會有編譯錯誤,因為playSong在startAudio宣告之前就呼叫了。

如果能將startAudio搬到前面去就可解決,但有時候做不到或不想這麼做,一般的解法是運用class extension,修改如下:

// SongPlayer.h
@interface SongPlayer : NSObject
{
}
- (void)playSong:(Song *)song;

@end

// SongPlayer.m
@interface SongPlayer ()
- (void)startAudio:(NSError **)error;
@end

@implementation SongPlayer
- (void)playSong:(Song *)song
{
  NSError *error;
  [self startAudio:&error];

  //...

}

- (void)startAudio:(NSError **)error
{
  //...

}
@end

我們可以把私用方法宣告在class extension(@interface SongPlayer () ... @end)裡,就可解決此問題。

但新的Objective-C更強,它直接幫我們處理好了,編譯器會先解析各方法的宣告(方法名、回傳型別、參數型別),然後再解析方法的內容,這麼一來,一開始的程式碼就不會有錯誤了。


新的Objective-C的enum有了不錯的進步,新型的enum可如下定義:

enum FruitEnum : NSUInteger
{
  FruitEnumApple,
  FruitEnumBanana,
  FruitEnumCherry,
  FruitEnumBlueberry,
  //...
} FruitEnum;

其名為fixed underlying type enum。

有了這種enum後,Xcode在補足程式碼(code completion)時就更聰明了,而且,編譯器也能作檢查型別,譬如說把某enum值指定給另一個enum型別的變數,就會出現錯誤訊息(編譯器參數為-Wcovnersion),還有,以switch根據某enum型別作判斷的話,若少寫某enum值時編譯器也會提醒你(編譯器參數為-Wswitch)。


對於property也有很多新東西。

原本會這麼寫:

//Person.h
@interface Person : NSObject
{
  NSString *_name;

}

@property (strong) NSString *name;
@end

//Person.m
@implementation Person

@synthesize name = _name;

@end

可以把instance variable的宣告改放在@implementation裡:
//Person.h
@interface Person : NSObject
{
  NSString *_name;

}

@property (strong) NSString *name;
@end

//Person.m
@implementation Person
{
  NSString *_name;
}

@synthesize name = _name;

@end

而且,既然用了@synthesize,根本就不需要自己宣告instance variable了:
//Person.h
@interface Person : NSObject
{
  NSString *_name;

}

@property (strong) NSString *name;
@end

//Person.m
@implementation Person
{
  NSString *_name;
}


@synthesize name = _name;

@end

以上是本來就有的,現在,在Xcode 4.4(與之後的版本),連@synthesize都可以省略了:
//Person.h
@interface Person : NSObject
{
  NSString *_name;

}

@property (strong) NSString *name;
@end

//Person.m
@implementation Person
{
  NSString *_name;
}


@synthesize name = _name;

@end

也就是說,synthesize是"預設"的行為了,原本property要寫好幾行程式,現在只需一行即可,編譯器會自動加入:

@synthesize name = _name;

預設會在property名字前面加上底線「_」作為instance variable的名字。

注意,為了相容於舊版,若你寫了
@synthesize name;
則等同於
@synthesize name = name;


object literal(這是我覺得早就應該加入的功能)。

首先是NSNumber,原本要這麼寫:
NSNumber *n1 = [NSNumber numberWithInt:1234];
NSNumber *n2 = [NSNumber numberWithFloat:1234.56f];
NSNumber *n3 = [NSNumber numberWithBool:YES];
等等...

新版的只需寫:
NSNumber *n1 = @1234;
NSNumber *n2 = @1234.56f;
NSNumber *n3 = @(YES); // iOS 5寫@YES不行
等等...

不僅如此,以下的運算式都會變成NSNumber:
@( M_PI / 6 )
@( "0123456789ABCDEF"[x % 16] )
@( NSWriteDirectionLeftToRight )

@( getenv("PATH") ),這個會變成NSString

同樣的,NSArray,原本要寫很多程式碼,要擔心有沒有在末端加上nil,現在可改成:

NSArray *array1 = @[]; // 空的陣列
NSArray *array2 = @[a]; // 只含有a物件的陣列
NSArray *array3 = @[@"a", @"b", @"c"]; // 含有三個字串的陣列

既然NSArray出現了,沒道理NSDictionary會缺席啊,同理,NSDictionary也有了類似的新語法:

NSDictionary *dict1 = @{};
NSDictionary *dict2 = @{k1:o1};
NSDictionary *dict3 = @{k1:o1, k2:o2, k3:o3};

id value = aDictionary[@"key"]; // 讀取
aMutableDictionary[@"key"] = newValue; // 寫入


subscripting

NSArray的物件也可以用[]來存取其內的東西了,

NSArray *songs = @[@"song1", @"song2", @"song3"];

NSString *s1 = songs[0]; //讀取

songs[0] = @"newSong"; //寫入

不只NSArray可以利用[]語法,你自己寫的類別也可以喔,只需實作底下的方法即可:

-(元素型別)objectAtIndexedSubscript:(索引型別)idx;
-(void)setObject:(元素型別)object atIndexedSubscript:(索引型別)idx;

嗯,還不錯,程式碼可以大幅簡化囉。

2 comments:

  1. 布林的新寫法
    NSNumber *n3 = @YES;
    會跳出
    Unexpected type name BOOL expected expression

    WWDC也寫錯XD

    要寫NSNumber *n3 = @(YES);

    ReplyDelete
  2. 感謝提醒,在iOS 5寫@YES會有錯誤,但iOS 6就可以了。

    ReplyDelete