Jan 132017
 

이 글은 qiita에 작성된 http://qiita.com/motokiee/items/b30514204a819a09425b(작성자 motokiee님, 2016-02-29 투고)을 번역한 글입니다.

Objective-C에서 Swift로 이전하는 과도기

수년간 개발되어온 앱에 슬슬 Swift를 도입하기 시작하지 않았나요? 당연히 Objective-C에서 만들어진 재산을 그대로 둔 상태로 개발하게 될 것이라고 생각합니다.

이 때 Optional을 다루는 것에 대해 곤란해하고있지 않을까 생각이 듭니다. Objective-C에서는 리시버가 nil인 상태로 메시지를 보내더라도 크래시가 발하지 않았지만, Optional이 있는 Swift로 부터 Objective-C의 코드를 호출하는 경우에는 좀 곤란해 집니다.

Swift와 Objective-C의 호환성을 강화하기 위해서 nullable, nonnull이 Objective-C에 추가되었습니다.

Swift의 코드를 작성할 때, 이러한 형수식자(Type qualifier)를 사용해서 Objective-C 쪽도 개선하여 Swift 도입을 좀 더 쉽게 할 수 있을 것이라고 생각합니다.

nullable

nullable은 Optional에 있는 nil을 허용한다는 것을 명시하기 위한 형수식자입니다.

예를 들면, NSDatainitWithContentsOfURL: 이니셜라이져는 아래와 같이 정의되어 있어서 리턴값이 nil이 될 수있다는 것을 명시하고 있습니다.

- (nullable instancetype)initWithContentsOfURL:(NSURL *)url;

이것을 스위프트에서 보면 아래와 같이 failable initializer로 변환되어 실패할 가능성이 있는 이니셜라이져가 되는 것을 알 수 있습니다.

public init?(contentsOfURL url: NSURL)

이와 같이 인스턴스 생성이랑 파라미터, 리턴값이 nil이 될 수 있는 경우에는 Objective-C쪽에 nullable을 지정해 두는 것으로 Swift에서 Optional으로 다루는 것이 가능해집니다.

Objective-C에서는 nullable을 지정하지 않는 경우에는 “Implicitly unwrapped optional”이 됩니다. 아래와 같은 Objective-C의 메소드에서 고려해보겠습니다.

- (UIImage*)createImage;

nullable을 붙이지 않고 Swift에서 사용하려고 하면 변환시에 “Implicitly unwrapped optional”이 되어 버립니다.

func createImage() -> UIImage!

이 메소드의 리턴 값을 사용하려고 할 때 nil일 경우 크래시가 발생해버려서 Swift부터 이 Objective-C의 코드를 사용할 때에 불안한 부분이 따라다니게 됩니다. 혹시 이러한 처리를 보게 된다면 nullable을 지정하여 Swift쪽에서 Optional으로 이용가능하도록 하면 Swift에서도 안심하고 이용할 수 있게 됩니다.

- (nullable UIImage*)createImage;

아래의 Objective-C의 코드를 수정하면 Swift에서는

func createImage() -> UIImage?

과 같이 변환됩니다.

nonnull

nonnull은 Optional이 아니라는 것을 명시하기 위한 형수식자로 nonnull을 함수랑 메소드의 파라미터, 리턴 값에 지정하는 경우에는 Optional한 변수를 지정하는 것이 불가능해집니다.

NS_ASSUME_NONNULL_BEGIN, NS_ASSUME_NONNULL_END

하지만 UIKit의 소스코드를 확인해보면 nonnull을 찾을 수 없습니다.

예를 들면 UIVisualEffectView는 이하와 같이 정의 되어 있습니다만 nonnull은 어디에도 쓰이고 있지 않습니다. nullable은 제대로 쓰이고 있습니다.

NS_CLASS_AVAILABLE_IOS(8_0) @interface UIVisualEffectView : UIView <NSSecureCoding>
@property (nonatomic, strong, readonly) UIView *contentView; // Do not add subviews directly to UIVisualEffectView, use this view instead.
@property (nonatomic, copy, nullable) UIVisualEffect *effect;
- (instancetype)initWithEffect:(nullable UIVisualEffect *)effect NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
@end

하지만 Swift의 변환은 제대로 Optional이 아니도록 되어 있습니다.

@available(iOS 8.0, *)
public class UIVisualEffectView : UIView, NSSecureCoding {
    public var contentView: UIView { get } // Do not add subviews directly to UIVisualEffectView, use this view instead.
    @NSCopying public var effect: UIVisualEffect?
    public init(effect: UIVisualEffect?)
    public init?(coder aDecoder: NSCoder)
}

nonnull의 지정은 제대로 되어 있다는 것입니다. 왜 nonnull이 지정되지 않은것과 상관없이 변환이 가능한 걸까요?

알아보니 NS_ASSUME_NONNULL_BEGINNS_ASSUME_NONNULL_END 매크로가 사용되어 있었습니다.

앞에서 이야기 했던 UIVisualEffectView.h에도 제대로 이 매크로가 사용되어 있었습니다.

//
//  UIVisualEffectView.h
//  UIKit
//
//  Copyright (c) 2014-2015 Apple Inc. All rights reserved.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

// ...중략

NS_CLASS_AVAILABLE_IOS(8_0) @interface UIVisualEffectView : UIView <NSSecureCoding>
@property (nonatomic, strong, readonly) UIView *contentView; // Do not add subviews directly to UIVisualEffectView, use this view instead.
@property (nonatomic, copy, nullable) UIVisualEffect *effect;
- (instancetype)initWithEffect:(nullable UIVisualEffect *)effect NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
@end

NS_ASSUME_NONNULL_END

Foundation과 UIKit의 소스를 찾아보면 이 매크로가 사용되어 있었습니다.

확실히 nonnull, nullable을 하나하나 다 쓰고 있는 것은 큰일이겠지요. Objective-C에서 nonnull, nullable을 붙일 필요가 있는 경우는 적극적으로 NSASSUME_NONNULL_BEGINNS_ASSUME_NONNULL_END을 사용하고 nullable만을 쓰는 것이 좋을지도 모르겠습니다.

주의

같은 파일내에 메소드와 프로퍼티에 하나라도 nonnull, nullable을 쓰는 경우, 파일 내의 보든 메소드의 파라미터, 리턴 값, 프로퍼티에 형수식자를 붙이지 않으면 안됩니다. warning이 발생합니다.

 

Lightweight Generics

Swift로부터 Objective-C의 코드를 사용하려고 하는 때, NSArray로부터 변환과 NSDictionary로부터 Dictionary의 변환에서는 곤란할 부분이 없습니다만 배열 요소의 형이 AnyObject가 되어버려 곤란해 질 수 있을 것이라 생각합니다.

이와 같은 경우에 guard랑 Optional Binding같은 것을 사용해서 안전하게 형변환하여 구현할 것이라 생각하지만 Objective-C의 코드를 Generics를 사용해서 수정하는 편이 더 좋아보입니다.

UIView는 subviews라 불리우는 NSArray의 프로퍼티를 가지고 있습니다만, Generics를 사용해서 UIView의 배열이라는 것을 명시하고 있습니다.

@property(nonatomic,readonly,copy) NSArray<__kindof UIView *> *subviews;

__kindof는 서브클래스(자식 클래스)도 허용하기 위한 어노테이션입니다. subviews는 UIView의 서브클래스도 허용하는 것을 명시합니다.

Objective-C에 Nullability와 Generics을 지정해 가는 공정

Objective-C에서 이하와 같이 정의되어있는 오브젝트를 보겠습니다.

@interface MNPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic) NSUInteger age;
@property (nonatomic, copy) NSArray *items;
- (NSString*)hey;
- (instancetype)initWithName:(NSString*)name age:(NSUInteger)age;
@end

이것을 Swift로부터도 사용하기 쉽도록 Nullability와 Generics를 지정해보겠습니다.

아무 손도 대지 않은 경우, Swift에서는 이렇게 보입니다. 이니셜라이져와 프로퍼티에 !가 붙어서 “Implicitly unwrapped optional”이 된 것을 알 수 있습니다.

public class MNPerson : NSObject {
    public var name: String!
    public var age: UInt
    public var items: [AnyObject]!
    public func hey() -> String!
    public init!(name: String!, age: UInt)
}

Objective-C의 헤더가 Swift에서 어떤식으로 표시되는지를 확인하는 방법

Objective-C에서 Nullability와 Generics를 지정할 때, jump bar의 좌측 끝에 있는 버튼을 클릭하면 나타나는 “Generated Interface”를 사용해서 Objective-C의 헤더파일이 Swift에 어떤 인터페이스가 되는지 확인하는 것이 가능합니다.

 

nullable의 설정

먼저 nullable을 붙여보도록 합시다. 아래와 같이 됩니다.

@interface MNPerson : NSObject
@property (nullable, nonatomic, copy) NSString *name;
@property (nonatomic) NSUInteger age;
@property (nullable, nonatomic, copy) NSArray *items;
- (nullable NSString*)hey;
- (nullable instancetype)initWithName:(nullable NSString*)name age:(NSUInteger)age;
@end

Nullability는 포인터형만 지정하는 것이므로 primitive한 값, NSUInteger같은 것에는 nullable을 붙일 필요가 없습니다.

이것을 Generated Interface에서 보면 아래와 같이 됩니다.

public class MNPerson : NSObject {
    public var name: String?
    public var age: UInt
    public var items: [AnyObject]?
    public func hey() -> String?
    public init?(name: String?, age: UInt)
}

nonnull의 설정

우선 Optional으로 취급되고 있도록 되었습니다. 하지만 모든 것이 Optional이라면 하나하나 Optional Binding으로 값을 끄집어내지 않으면 안되기 때문에 좀 귀찮습니다.

nonnull으로 취급하는 장소가 없나 구현을 확인해봅시다.

- (instancetype)initWithName:(NSString*)name age:(NSUInteger)age {
    self = [super init];

    if (self) {
        _name = name;
        _age = age;
    }
    return self;
}


- (NSString*)hey {
    return @"hey";
}

이니셜라이져에서 인스턴스 변수인 _name_age에 값이 설정되어 있습니다. hey 메소드도 실패 가능성은 없기 때문에 여기에 해당하는 프로퍼티랑 메소드의 파라미터를 nonnull으로 해봅시다.

@interface MNPerson : NSObject
@property (nonnull, nonatomic, copy) NSString *name;
@property (nonatomic) NSUInteger age;
@property (nullable, nonatomic, copy) NSArray *items;
- (nonnull NSString*)hey;
- (nonnull instancetype)initWithName:(nonnull NSString*)name age:(NSUInteger)age;
@end

Swift에서 봐보면 이렇게 됩니다.

public class MNPerson : NSObject {
    public var name: String
    public var age: UInt
    public var items: [AnyObject]?
    public func hey() -> String?
    public init(name: String, age: UInt)

NS_ASSUME_NONNULL_BEGIN,NS_ASSUME_NONNULL_END 매크로를 사용하면 아래와 같이 쓸 수 있습니다.

NS_ASSUME_NONNULL_BEGIN

@interface MNPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic) NSUInteger age;
@property (nullable, nonatomic, copy) NSArray *items;
- (NSString*)hey;
- (instancetype)initWithName:(NSString*)name age:(NSUInteger)age;
@end
NS_ASSUME_NONNULL_END

nil이 될 가능성이 있는 곳에만 nullable을 지정할 필요가 있습니다만 nonnull인 프로퍼티에 대하여는 지정이 불필요해집니다.

결과는 앞과 같은 형태, 아래와 같이 됩니다.

public class MNPerson : NSObject {
    public var name: String
    public var age: UInt
    public var items: [AnyObject]?
    public func hey() -> String
    public init(name: String, age: UInt)
}

Generics의 설정

이것으로 Optional의 설정은 완료했습니다만 Swift로부터 사용할 때에 귀찮은 점이 한 부분 남아 있습니다. Generated Interface를 봐 봅시다.

public class MNPerson : NSObject {
    public var name: String
    public var age: UInt
    public var items: [AnyObject]?
    public func hey() -> String
    public init(name: String, age: UInt)
}

items 프로퍼티가 AnyObject의 배열이 되어 있습니다. 하나하나 캐스트 하는 것도 귀찮습니다. 여기서는 items에 쌓여있는 것은 문자열이라 한정해서 Generics의 설정을 하겠습니다.

NS_ASSUME_NONNULL_BEGIN

@interface MNPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic) NSUInteger age;
@property (nullable, nonatomic, copy) NSArray<NSString*> *items;
- (NSString*)hey;
- (instancetype)initWithName:(NSString*)name age:(NSUInteger)age;
@end
NS_ASSUME_NONNULL_END

items 프로퍼티에 대하여 NSString을 지정했습니다. Generated Interface를 봐 봅시다.

public class MNPerson : NSObject {
    public var name: String
    public var age: UInt
    public var items: [String]?
    public func hey() -> String
    public init(name: String, age: UInt)
}

위와 같이 [String]으로 되어 있는 것을 알 수 있습니다.

이렇듯 구현을 확인하면서 Swift로부터 이용하기 쉽도록 해 가는 것이 가능합니다.

정리

코드양적으로 봤을 때 그렇게까지 많이 재작성하지 않더라도 Swift에서 사용하기 쉽게 인터페이스를 수정하는 것이 가능합니다.

어떻게 구현되었는지 파악되어 있는 경우에는 이와 같은 Nullability와 Generics를 지정하는 것이 기존의 Objective-C의 코드를 Swift부터 사용하기 쉽게 할 수 있다는 것에 틀린 부분은 없다고 생각합니다.

단지, 이것들을 바꾸는 것은 이외로 간단하게 되는 것은 아닌 것 같은 인상을 줍니다. 이유라고 한다면 한 부분에만 nullablenonnull을 지정하는 것이 불가능하다거나 나름대로 큰 클래스가 된다면 nullable이라거나 nonnull인 것을 간단하게 판단할 수 없는 경우가 많아진다는 인상을 주기 때문입니다.

Objective-C의 경우 기본적으로는 nullable이 된다고 생각하지만, Swift에서 이용할 때에는 Optional로써 취급되지 않으면 안되기 떄문에 단순하게 nullable로 바꾸는 것에는 큰 강점을 느낄 수 없습니다.

그래도 Optional로써 취급되어지는 것이 강점이라고 생각하며 AnyObject의 캐스팅이 줄어든다는 것은 Swift코드를 작성해나가는데 큰 강점이 될 것이라 생각합니다.

참 고

Jan 082017
 

알림
이 글은 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/