알림
이 글은 Qiita에 게시된 “Modern Objective-C ビフォーアフター”(http://qiita.com/makoto_kw/items/d86fada0e38e9245912a , 2014-04-16 수정본 기준), makoto_kw님이 작성한 글을 번역한 것입니다.
Objective-C 언어는 2017년 현재도 여전히 많은 수의 사용자가 사용하고 있는 언어이다[1]. 최근 Objective-C의 관심이 Swift의 영향으로 근래의 웹에서 최근 자료를 찾기 힘든 것이 사실이다. 이 자료는 꽤 오래된 것이지만 회사에서 코드 리팩토링 업무를 하면서 modern objective-c 형식으로 변경이 필요하여 참조한 자료이다. 이를 사용하면 기존 방식에 비해 코드의 길이를 많이 줄일 수 있으며 불필요한 선언 코드를 줄일 수 있다는 것을 알게될 것이다. 물론 이 방법이 나온지 꽤 오래되었기 때문에 아마 대부분의 Objective-C 개발자들은 알고 있을 것으로 예상되지만 아직 이 사실에 대하여 알지 못하는 분들을 위해 도움이 될 수 있었으면 좋겠다.
Adopting Modern Objective-C
instancetype
instancetype
를 사용하면 컴파일러가 타입 체크가 가능함
@interface MyObject
- (id)myFactoryMethod;
@end
↓
@interface MyObject
- (instancetype)myFactoryMethod;
@end
Enumeration Macros
“iOS6 SDK”부터 추가된 매크로
enum {
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
};
typedef NSInteger UITableViewCellStyle;
↓
typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
};
bitmask는 NS_OPTIONS
를 사용
enum {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};typedef NSUInteger UIViewAutoresizing;
↓
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
#import <Foundation/NSObject.h>
#import <Foundation/NSString.h> //or @class NSString;
↓
#import <Foundation/Foundation.h>
Accessor Methods
항상 엑세서 메소드를 사용하라. 단, initializer랑 dealloc안은 피한다.
- (void)myMethod {
// ...
[self setTitle:[NSString stringWithFormat:@"Area: %1.2f", [self area]]];
// ...
}
↓
- (void)myMethod {
// ...
self.title = [NSString stringWithFormat:@"Area: %1.2f", self.area];
// ...
}
Memory Management
ARC를 사용한다. ARC은 Xcode 4.2(LLVM compiler 3.0)부터 지원한다. 일부기능은 컴파일러 만으로 해결이 안되기 때문에 iOS5 이상을 지원해야 한다.
NSMutableArray *array = [[NSMutableArray alloc] init];
// Use the array
[array release];
// or
NSMutableArray *array = [[[NSMutableArray alloc] init] autoelease];
// Use the array
↓
NSMutableArray *array = [NSMutableArray array];
// Use the array
id heisenObject = [[array objectAtIndex:n] retain];
[array removeObjectAtIndex:n];
// ...
[heisenObject doSomething];
[heisenObject release];
// ...
↓
id heisenObject = [array objectAtIndex:n];
[array removeObjectAtIndex:n];
// ...
[heisenObject doSomething];
// ...
- (void)takeLastNameFrom:(Person *)person {
NSString *oldLastname = [[self lastName] retain];
[self setLastName:[person lastName]];
NSLog(@"Lastname changed from %@ to %@", oldLastname, [self lastName]);
[oldLastName release];
}
↓
- (void)takeLastNameFrom:(Person *)person {
NSString *oldLastname = [self lastName];
[self setLastName:[person lastName]];
NSLog(@"Lastname changed from %@ to %@", oldLastname, [self lastName]);
}
CFUUIDRef cfUUID = CFUUIDCreate(NULL);
NSString *noteUUID = (NSString *)CFUUIDCreateString(NULL, cfUUID);
CFRelease(cfUUID);
↓
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는 피함)
@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
↓
@interface Thing : NSObject
@property (copy) NSString *title;
@end
@implementation Thing
@synthesize title;
@end
@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
↓
@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
@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
↓
@interface Thing : NSObject
@property (weak) id delegate;
@end
@implementation Thing
@synthesize delegate;
@end
Private State
Private 변수는 클래스 익스텐션 내에 property로 선언한다. 메소드도 마찬가지로 클래스 익스텐션을 사용한다.
@interface Thing {
BOOL privateTest;
}
↓
@interface Thing ()
@property BOOL privateTest;
@end
// ...
@interface Thing (PrivateMethods)
- (void)doSomethingPrivate;
- (void)doSomethingElsePrivate;
@end
↓
@interface Thing ()
- (void)doSomethingPrivate;
- (void)doSomethingElsePrivate;
@end
Outlets
메모리 관리를 위해 strong, weak로 property를 선언한다.
@interface MyViewController : MySuperclass {
IBOutlet ElementClass *uiElement;
}
@end
↓
@interface MyViewController : MySuperclass
@property (weak) IBOutlet ElementClass *uiElement;
@end
Initializer Methods and dealloc
초기화와 해제는 액세서를 쓰지 않고 변수로 한다.
- (id)init {
if (self = [super init]) {
[self setTitle:@"default"];
}
return self;
}
↓
- (id)init {
self = [super init];
if (self) {
_title = @"default";
}
return self;
}
- (void)dealloc {
[self setTitle:nil];
[super dealloc];
}
↓
- (void)dealloc {
[_title release];
[super dealloc];
}
Protocols
필수는 아닌 구현하지 않은 메소드가 있기 때문에 id를 그대로 사용하지 말고 optional선언을 사용해서 id형에 제대로 protocol을 지정한다.
@ButlerProtocol
- (void)makeTea;
- (void)serveSandwiches;
- (void)mowTheLawn;
@end
↓
@protocol ButlerProtocol <NSObject>
- (void)makeTea;
- (void)serveSandwiches;
@optional
- (void)mowTheLawn;
@end
- (id <ButlerProtocol>)butler;
↓
@property id <ButlerProtocol> butler;
Collections and Literals
@
리터럴 구문과 []
참조구문은 Xcode 4.4(Apple LLVM 4.0 Compiler)부터 지원했으나 iOS에 사용할 수 있게 된건 Xcode4.5 부터이다.
NSNumber *aNumber = [NSNumber numberWithFloat:2.3];
↓
NSNumber *aNumber = @2.3f;
NSNumber *anotherNumber = [NSNumber numberWithFloat:x];
↓
NSNumber *anotherNumber = @(x);
NSArray *anArray = [NSArray arrayWithObjects:aThing, @"A String",
[NSNumber numberWithFloat:3.14], nil];
↓
NSArray *anArray = @[ aThing, @"A String", @3.14 ];
NSDictionary *aDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
value, @"Key",
[NSNumber numberWithBOOL:YES], @"OtherKey", nil];
↓
objectivec:afterNSDictionary *aDictionary = @{ @"Key" : value, @"OtherKey" : @YES };
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];
↓
NSDictionary *distanceDict = @{
kCIAttributeMin : @0.0,
kCIAttributeMax : @1.0,
kCIAttributeSliderMin : @0.0,
kCIAttributeSliderMax : @0.7,
kCIAttributeDefault : @0.2,
kCIAttributeIdentity : @0.0,
kCIAttributeType : kCIAttributeTypeScalar
};
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];
↓
NSDictionary *slopeDict = @{
kCIAttributeSliderMin : @-0.01,
kCIAttributeSliderMax : @0.01,
kCIAttributeDefault : @0.00,
kCIAttributeIdentity : @0.00,
kCIAttributeType : kCIAttributeTypeScalar };
id firstElement = [anArray objectAtIndex:0];
[anArray replaceObjectAtIndex:0 withObject:newValue];
↓
id firstElement = anArray[0];
anArray[0] = newValue;
id value = [aDictionary objectForKey:@"key"];
[aDictionary setObject:newValue forKey:@"key"];
↓
id value = aDictionary[@"key"];
aDictionary[@"key"] = newValue;
NSArray *array = ...;
int i;
for (i = 0; i < [array count]; i++) {
id element = [array objectAtIndex:i];
// ...
}
↓
NSArray *array = ...;
for (id element in array) {
// ...
}
Blocks
NSArray
정렬은 (NSArray *)sortedArrayUsingComparator:(NSComparator)cmptr
를 사용할 수 있다.
NSArray *array = ...;
NSArray *sortedArray;
sortedArray = [array sortedArrayUsingFunction:MySort context:NULL];
NSInteger MySort(id num1, id num2, void *context) {
NSComparisonResult result;
// Do comparison
return result;
}
↓
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
을 사용할 수 있다.
NSArray *array = ...;
for (id element in array) {
// ...
}
↓
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
을 사용할 수 있다.
NSDictionary *dictionary = ...;
for (NSString *key in dictionary) {
id object = [dictionary objectForKey:key];
// Do things with key and object.
}
↓
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
을 사용할 수 있다.
- (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.
}
↓
- (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
으로 변환해서 사용해야 한다.
기타
- 원래 자료는 Mac Developer Library modern으로 검색
- 사용하고 싶은 메소드가 블럭에 대응되지 않는 경우 → BlocksKit의 이용을 검토
- Xcode(컴파일러) 업데이트 정보는 What’s New in Xcode를 참고
- Xcode에 modern Objective-C로 변환하는 기능을 제공함 (Edit > Covert > To Modern Objective-C Syntax… 사용. Xcode8.2.1 기준)
참고자료
[1] TIOBE Index for December 2016(2017-01-08 04:12 KST 확인), http://www.tiobe.com/tiobe-index/