Mar 192017
 

이번에 어떤 이슈를 확인해야할 일이 있어서 macOS의 콘솔 기능을 이용할 일이 있었다.
콘솔 기능은 앱이나 시스템으로부터 수집된 로그 데이터(통합 로그, unified logging)를 보기 쉽게 보여주는 프로그램이다.
하지만 분석을 하다보면 <private>로 되어 분석에 한계가 있는 로그들이 존재한다.

콘솔 기능에 들어가면 로 숨겨져 있는 것을 확인 가능

이는 macOS 내부에 제작된 프라이버시 프로텍션 기능이 동작해서 그렇다.
하지만 이를 풀어서 확인해야할 필요성이 있는 경우가 있는데 이 때 터미널을 켜서 아래의 명령어를 실행하면 된다.

sudo log config --mode "private_data:on"

그럼 아래 그림과 같이 <private>가 없어진 상태로 출력된다.

private로 숨겨지지 않은 형태로 출력

작업이 끝나고 다시 이 기능을 활성화 해야할 경우에는 반대로 실행하면 된다.

sudo log config --mode "private_data:off"

 

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/

Sep 142016
 

VPN은 외부로부터 격리된 인트라넷을 구성하면 외부에서 접속이 불가능한데 이를 가능하게 해주는 서비스이다. 물리적으로 서로 떨어져 있는 회사 네트워크를 외부에 공개하지 않으면서 통신하기 위해 만들어 졌다. 물론 회사 네트워크에서 사용하면 이런용도로 사용하게 되겠지만, 집에서 공유기 뒤에 NAS를 쓰거나, 개인 서버를 사용하는 경우에도 동일한 방법으로 활용할 수 있다. 만약 NAS나 개인서버를 SMB(네트워크 공유 기능)통해 사용하기 위해서는 같은 네트워크에 있어야 한다. 하지만 외부에서는 이 방법을 사용할 수 없는데, 이는 ISP(네트워크 제공사)의 방화벽 문제가 있기 때문이며, 따라서 WebDAV등을 이용하여 구현하는 방법이 일반적이다. 이 경우에 외부에는 내부 서버를 공개하지 않으면서 안전(암호화된)하게 내부 네트워크에 접속된 것과 같은 상태를 만들 수 있는 방법이 바로 VPN인 것이다.

일반적인 공유기는 VPN 프로토콜 중에 하나인 PPTP를 지원하도록 구현되어 있다. 하지만 PPTP의 문제는 인증시스템이나 암호화 방식이 매우 오래되었으며 보안에 취약하다는 점이다[1]. 그래서인지 이번에 업데이트 되는 iOS 10, macOS Sierra부터는 보안상의 이유로 더 이상 PPTP프로토콜 기반의 VPN서비스를 이용할 수 없게 되었다[2]. 하지만 필자가 가지고 있는 공유기에서 PPTP를 지원하지 않기 때문에 다른 프로토콜을 사용하는 VPN이 필요하게 되었다. 하지만 굳이 공유기에서 PPTP가 아닌 다른 프로토콜을 지원하게 하려면 공유기의 펌웨어를 커스터마이징해야 한다는 것인데 그건 너무 시간이 많이 걸리며 공유기 공급사에서 소스코드를 공개하지 않는다는 점에서 어려움이 있다. 따라서 VPN서버를 공유기에서 내부 서버로 변경하면 여러종류의 VPN프로토콜이 사용 가능해 지기 때문에 홈 서버에 VPN서버를 설치 하게 되었다. 좀 더 큰 CPU파워를 사용할 수 있게 됨으로써 최신 VPN 프로토콜과 고급 암호화 기능도 사용할 수 있게 되었다.

자료 조사를 해보면서 여러 종류의 VPN 프로토콜이 존재함을 알 수 있었으며, VPN 프로토콜을 선택 기준이 필요해 졌는데 모바일이나 OS에서 추가적인 프로그램 설치 없이 사용할 수 있어야 한다는 조건을 걸었다. 이 조건을 만족하면 모바일로 외부에서 집에 있는 서버에 있는 동영상을 스트리밍으로 볼 수 있기 때문에다. 이에 추가 클라이언트 환경 설치가 필요한 OpenVPN은 제외되었다. 그렇게 되면 가능한 프로토콜이 L2TP/IPSec, IPSec, IKEv2 를 사용할 수 있음을 확인 가능하였다. 최초에는 L2TP/IPSec을 이용할 예정이였으나, 필자가 사용하는 OS에서는 기본 지원하며, 속도도 빠르고 높은 보안을 제공하는 IKEv2 프로토콜을 이용하여 구현하기로 하였다[3].

IKEv2 프로토콜을 사용할 때 ESP를 통하여 암호화된 패킷을 전송한다. 원래 ESP는 IP프로토콜에 바로 들어가기 때문에 공유기(NAT)뒤에 VPN서버가 있는 경우에는 DMZ설정 같은 것이 필요하며 공유기가 이 기능을 지원해야할 수도 있다. DMZ로 설정하면 VPN서버의 모든 부분이 인터넷으로 공개되기 때문에 내부를 숨기겠다는 목적이 달성되지 않는다. 하지만 선배 개발자들은 이런 문제를 해결하기 위해 NAT Passthrough라는 것을 제공하며 UDP프로토콜에 ESP를 담아서 보내도록 구현이 된다. 이 문제는 이를 설정함으로써 해결 된다.

준비물은 MITM(Man in the middle attack)방지를 위한 서버 인증을 위한 인증서가 필요하다. 이 글에서는 self-signed 인증서를 사용하여 IKEv2서버 구축하는 방법을 알려주지 않는다. 만약 self-signed 인증서를 사용하게 되면 클라이언트에서 추가적인 설정이 필요할 수도 있다.

이 글은 공유기의 IP주소로 DNS/DDNS 등의 방법으로 도메인이 할당되어 있으며, 이 도메인으로 정상적인 SSL 인증서를 발급받았다는 전제로 작성되어 있다.

SSL 인증서는 일반적으로 유료이지만 StartSSL이나 Let’s Encrypt 등을 통하여 무료로 발급 가능하다.

IKEv2를 동작시키기 위해서는 UDP/500과 UDP/4500을 열어 두어야 한다. UDP/500은 IKE프로토콜을 위해 필요하며, UDP/4500은 IPSec을 이용하기위해 필요하다. 이 두가지 포트를 공유기에서 포트포워딩 설정을 해두어야 한다.

IKEv2를 사용하기 위해서는 strongswan이라는 패키지 설치와 암호화 기능 등의 기능을 사용하기 위해다.

1. 패키지 설치
apt-get install strongswan libcharon-extra-plugins

2. 인증서 설정

2.1. root 인증서 복사
발급받은 인증서의 루트 인증서 및 채인 인증서 복사
복사시 한 파일당 하나의 인증서만 포함 시킬 것
/etc/ipsec.d/cacerts

2.2. 인증서 복사
발급받은 인증서 파일 복사(pem)
/etc/ipsec.d/certs

2.3 인증서의 비밀키 복사
발급받을 때 사용한 인증서 비밀키 복사 (키 패스워드 제거할 것)
/etc/ipsec.d/private

2.4 권한 설정
chmod 740 /etc/ipsec.d/cacerts
chmod 740 /etc/ipsec.d/certs
chmod 700 /etc/ipsec.d/private

3. /etc/ipsec.conf 파일 수정

config setup
  strictcrlpolicy=yes
  uniqueids = no

conn roadwarrior
  auto=add
  compress=no
  type=tunnel     # tunnel: network 계층(ip)부터 암호화, transport: 전송 계층(transport layer; TCP/UDP)부터 암호화
  keyexchange=ikev2
  rekey=no
  reauth=no
  fragmentation=yes
  forceencaps=yes
  dpdaction=clear
  dpddelay=35s
  dpdtimeout=2000s
  left=%any
  leftid=@example.com   # 아래 ipsec.secrets의 이름과 통일, 인증서에 도메인 포함되어 있을 것
  leftcert=example_com.pem   # 상기 certs 디렉토리에 복사한 인증서 파일명으로 변경 
  leftsendcert=always
  leftsubnet=0.0.0.0/0
  leftauth=pubkey
  right=%any
  rightid=%any
  rightauth=eap-mschapv2
  eap_identity=%identity
  rightdns=192.168.1.1           # 여기서 rightdns 의 주소를 VPN사용시 쓸 DNS서버 주소로 변경 필요하다. (예: 8.8.8.8)
  rightsourceip=10.8.10.0/24
  rightsendcert=never

4. /etc/ipsec.secrets

# 서버 도메인과 키 설정

# 예제 - 아래 도메인은 클라이언트 설정시 사용, 인증서에 아래 도메인이 포함되어 있어야 함, 상기 복사한 비밀키 파일명을 사용함
example.com : RSA “example_com.key"

{{VPN접속ID}} : EAP “{{VPN접속PASSWORD}}"
# 예제
admin : EAP “password"

5. 서버 재시작
ipsec restart

6. 공유기(NAT) 포트 포워딩
UDP 500 과 UDP 4500을 내부에 설치된 VPN서버로 포워딩

7. 테스트
iOS기기나 macOS에서 아래의 설정으로 만듦
내부망과 외부망에서 각각 수행하여 정상적으로 접속 되는지 확인한다.

  • VPN Type : IKEv2
  • Server Address(서버 주소) : VPN의 도메인 주소 (예: example.com)
  • Remote ID (리모트 ID) : 상기 적은 도메인 주소(예: example.com)
  • Local ID (로컬 ID) : 비워둘 것
  • Authentication Settings…(인증 설정) : 사용자 이름
    사용자 이름 : ipsec.secrets 에 설정한 아이디
    패스워드 : ipsec.secrets 에 설정한 패스워드
VPN 설정 예시

VPN 설정 예시

 

connected to VPN

VPN 접속 성공

8. 관련 접속 로그 확인
tail -f /var/log/syslog
tail -f /var/log/auth.log

이 글을 통해 우분투 서버에서 IKEv2를 사용한 서버 구축이 가능하였으며 이를 통해 어디에서나 모바일 디바이스, 데스크톱을 통하여 내부 네트워크에 접속할 수 있게 되었으며, 암호화가 되어 안전한 환경에서 내부서버의 데이터를 사용할 수 있게 되었다.

참고문헌

[1] B. Schneier, Mudge, “Cryptanalysis of Microsoft’s PPTP Authentication Extensions (MS-CHAPv2)”, CQRE ’99, Springer-Verlag, 1999, pp. 192-203.
[2] “Prepare for removal of PPTP VPN before you upgrade to iOS 10 and macOS Sierra”, Apple, https://support.apple.com/en-us/HT206844
[3]  https://hide.me/en/blog/2015/03/whats-the-difference-considering-pptp-vs-l2tp-vs-sstp-vs-ikev2/
[4] https://hub.zhovner.com/geek/universal-ikev2-server-configuration/

Oct 302013
 

메버릭스에서 지도가 잘 나오지 않는다면 DNS 설정을 의심해 보는 것이 좋다. 일단 확인한 것으로는 구글의 DNS(예: 8.8.8.8, 4.4.4.4)로 설정되어 있으면 잘 되지 않는 다는 것이였다. 해결방법은 구글의 DNS가 아닌 통신회사(IDS)에서 제공해 주는 DNS서버를 이용하는 것만으로도 충분히 해결이 된다. DNS 설정 방법은 운영체제 별로 다르기 때문에 관련 사용 설명서를 참조하면 된다.