반응형

[최종 수정일 : 2017.06.15]

원문 : https://www.toptal.com/ios/top-ios-development-mistakes

iOS 개발자도 잘 모르는 가장 일반적인 실수 10가지(The 10 Most Common Mistackes iOS Developers Don’t know They’re Making)

앱 스토어에서 버그 있는 앱이 거부(rejected)당하는 것보다 더 나쁜(worse) 것은 무엇인가요? 리뷰 별점이 1개가 되면, 그것을 회복하기는 거의 불가능합니다. 이는 회사의 돈과 개발자의 작업이 필요합니다.

iOS는 세계에서 두번째로 큰 모바일 운영체제입니다. 또한 최신 버젼 채택률은 사용자의 85% 이상으로 매우 높습니다. 예상한것처럼, 참여도가 높은 사용자는 기대감 또한 높습니다 - 앱 또는 업데이트가 완벽하지 않으면, 그 사실을 알게 될 것입니다.

iOS 개발자에 대한 수요가 지속적으로 급증함에 따라, 많은 엔지니어들이 모바일 개발로 전환하였습니다(매일 1,000개가 넘는 새로운 앱이 Apple에 등록됩니다). 하지만 진정한 iOS 전문 지식은 기본 코딩을 훨씬 능가합니다. 아래에는 iOS 개발자가 자주하는 10가지 일반적인 실수와 그것들을 피하는 방법입니다.

iOS 사용자의 85%가 최신 OS 버젼을 사용합니다. 이는 앱이나 업데이트가 완벽할 것을 기대한다는 의미입니다.

일반적인 실수 1 : 비동기 프로세스를 이해하지 못함(Not Understanding Asynchronous Processes)

새 프로그래머에게서 흔히 볼수 있는 일반적인 실수는 비동기 코드를 부적절하게 처리하는 것입니다. 전형적인 시나리오를 생각해 봅시다:

사용자가 테이블뷰로 화면을 열고, 서버로 부터 일부 데이터를 가져오고 테이블뷰에 보여줍니다. 우리는 공식적으로 작성할 수 있습니다.

@property (nonatomic, strong) NSArray *dataFromServer;
- (void)viewDidLoad {
    __weak __typeof(self) weakSelf = self;
    [[ApiManager shared] latestDataWithCompletionBlock:^(NSArray *newData, NSError *error){
        weakSelf.dataFromServer = newData;     // 1
    }];
    [self.tableView reloadData];            // 2
}
// and other data source delegate methods
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.dataFromServer.count;
}

언뜻 보기에, 모든것이 정상으로 보입니다: 우리는 서버로부터 데이터를 가져오고 UI를 업데이트 합니다. 하지만, 가져오는 데이터가 비동기 프로세스이고, 새로운 데이터를 즉시 반환하지 않으며, reloadData 는 새로운 데이터를 받기 전에 호출됩니다. 이 실수를 수정하기 위해, #2번 줄을 블록 안의 #1번줄 바로 뒤로 이동해야 합니다.

@property (nonatomic, strong) NSArray *dataFromServer;
- (void)viewDidLoad {
    __weak __typeof(self) weakSelf = self;
    [[ApiManager shared] latestDataWithCompletionBlock:^(NSArray *newData, NSError *error){
        weakSelf.dataFromServer = newData;     // 1
        [weakSelf.tableView reloadData];    // 2
    }];
}
// and other data source delegate methods
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.dataFromServer.count;
}

하지만, 이 코드가 여전히 예상대로 동작하지 않는 상황일수 있습니다.

일반적인 실수 2 : 메인 큐가 아닌 스레드에서 UI 관련된 코드 실행하기(Running UI-Related Code on a Thread Other than Main Queue)

이전의 일반적인 실수로부터 수정된 예제 코드를 사용한다고 상상해 봅시다. 하지만 테이블 뷰는 비동기 프로세스가 성공적으로 완료된 후에도 새 데이터에 대해 여전히 업데이트 되지 않고 있습니다. 간단한 코드에 어떤 문제가 있는 것일까요? 이를 이해하려면, 우리는 블록 내부에 중단점(breakpoint)를 설정하고 블록이 호출되는 큐를 찾을수 있습니다. 우리가 호출한 것이 모든 UI관련된 코드가 실행되는 메인 큐에 없을때 설명했던 동작이 발생할 확률이 높습니다.

가장 인기 있는 라이브러리-Alamofire, AFNetwork, Haneke-들은 비동기 작업을 수행한 후에 메인 큐에서 completionBlock을 호출하도록 설계 되어 있습니다. 하지만, 여러분은 언제나 이런것들에 의존할수는 없고 여러분의 코드를 올바른 큐로 전달하는 것을 잊어버리기 쉽습니다.

모든 UI관련된 코드가 메인 큐에 있도록 하려면, 그 큐에 전달하는 것을 잊지 마세요:

dispatch_async(dispatch_get_main_queue(), ^{
    [self.tableView reloadData];
});

일반적인 실수 3 : 동시성과 멀티 스레딩을 잘못이해하기(Misunderstanding Concurrency & Multithreading)

동시성(concurrency)는 날카로운 칼에 비교할 수 있습니다: 조심하지 않거나 경험이 풍부하지 않다면 스스로를 다치기 쉽지만, 적절하고 안전하게 사용하면 매우 유용하고 효율적입니다.

동시성(concurrency) 사용을 피할수 있지만, 어떤 앱을 만들던지간에, 동시성 없이는 만들수 없는 확률이 높습니다. 동시성은 앱에 대해서 중요한 장점을 가실수 있습니다. 특히:

  • 대부분의 모든 앱은 웹 서비스 호출을 가지고 있습니다(예를 들어, 무거운 계산이나 데이터베이스로 부터 데이터 읽는 것). 메인 큐에서 이런 작업이 실행되는 경우, 앱은 가끔식 멈추며(freeze), 반응이 없을 것입니다. 게다가, 이것이 너무 오래 걸리면, iOS는 앱을 완전히 종료시킬 것입니다. 이런 작업을 다른 큐로 이동하면 앱이 멈춘(freeze) 것처럼 보이지 않고 사용자는 작업이 진행되는 동안 계속 앱을 사용할 수 있습니다.
  • 최신 iOS 기기들은 하나 이상의 코어를 가지므로, 사용자가 병렬로 수행할 수 있는 작업을 순차적으로 완료될때까지 왜 기다려야 할까요?

하지만, 동시성의 장점은 정말 재현하기 어려운 진행 조건 처럼, 복잡하지 않고 버그 발생 가능성 없이는 얻을수 없습니다.

실제 예제를 살펴봅시다(일부 코드는 간략화하기 위해 생략됨을 주의 합니다)

Case 1

final class SpinLock {
    private var lock = OS_SPINLOCK_INIT

    func withLock<Return>(@noescape body: () -> Return) -> Return {
        OSSpinLockLock(&lock)
        defer { OSSpinLockUnlock(&lock) }
        return body()
    }
}

class ThreadSafeVar<Value> {

    private let lock: ReadWriteLock
    private var _value: Value
    var value: Value {
        get {
            return lock.withReadLock {
                return _value
            }
        }
        set {
            lock.withWriteLock {
                _value = newValue
            }
        }
    }
}

멀티 스레드 코드 :

let counter = ThreadSafeVar<Int>(value: 0)

// this code might be called from several threads 

counter.value += 1

if (counter.value == someValue) {
    // do something
}

언뜻 보기에, 모든것이 동기화되고 예상대로 동작하는 경우 ThreadSaveVar를 counter로 래핑하고 스레드에 안전한 것처럼 보입니다. 불행하게도 이것은 두 스레드가 동시에 증가하는 줄(line)에 도달할 수 있고 counter.value == someValue 결과가 결코 옳지 않기 때문에, 사실이 아닙니다. 이를 해결하기 위해, 우리는 증가한 후의 값을 반환하는 ThreadSafeCounter를 만들 수 있습니다.

class ThreadSafeCounter {

    private var value: Int32 = 0

    func increment() -> Int {
        return Int(OSAtomicIncrement32(&value))
    }
}

Case 2

struct SynchronizedDataArray {

    private let synchronizationQueue = dispatch_queue_create("queue_name", nil)
    private var _data = [DataType]()
    var data: [DataType] {
        var dataInternal = [DataType]()
        dispatch_sync(self.synchronizationQueue) {
            dataInternal = self._data
        }

        return dataInternal
    }

    mutating func append(item: DataType) {
        appendItems([item])
    }

    mutating func appendItems(items: [DataType]) {
        dispatch_barrier_sync(synchronizationQueue) {
            self._data += items
        }
    }
}

이 경우에, dispatch_barrier_sync 는 배열에 대한 접근을 동기화 하는데 사용되었습니다. 이것은 접근 동기화를 보장하는데 즐겨찾는 패턴입니다. 불행하게도, 이 코드는 항목을 추가할때마다 구조체 복사본을 만드는 것을 고려하지 않았기에, 매번 새로운 동기화 큐를 가집니다.

여기에서, 처음 봤을때 옳게 보이더라도 예상대로 작동하지 않을수 있습니다. 또한, 테스트와 디버깅에 많은 작업이 필요하지만, 결국에는 앱의 속도와 응답속도를 향상시킬 수 있습니다.

일반적인 실수 4 : 변경 가능한 객체의 위험성을 모른다(Not Knowing Pitfalls of Mutable Objects)

Swift는 값 타입으로 하는 실수를 피하기에 매우 유용하지만, 여전히 많은 개발자들은 Objective-C를 사용합니다. 변경가능한(Mutable) 객체들은 매우 위험하고 숨겨진 문제를 야기할 수 있습니다. 함수로부터 불변(immutable) 객체가 반환되어야 한다는 것은 알려진 규칙이지만, 대부분의 개발자들은 이유를 모릅니다. 아래 코드를 살펴 봅시다:

// Box.h
@interface Box: NSObject
@property (nonatomic, readonly, strong) NSArray <Box *> *boxes;
@end

// Box.m
@interface Box()
@property (nonatomic, strong) NSMutableArray <Box *> *m_boxes;
- (void)addBox:(Box *)box;
@end

@implementation Box
- (instancetype)init {
    self = [super init];
    if (self) {
        _m_boxes = [NSMutableArray array];
    }
    return self;
}
- (void)addBox:(Box *)box {
    [self.m_boxes addObject:box];
}
- (NSArray *)boxes {
    return self.m_boxes;
}
@end

NSMutableArray가 NSArray의 서브클래스이기 때문에 위 코드는 정확합니다. 이 코드에서 무엇이 잘못될수 있을까요?

첫번째 그리고 가장 분명한 것은 다른 개발자가 다음과 같이 할수 있습니다.

NSArray<Box *> *childBoxes = [box boxes];
if ([childBoxes isKindOfClass:[NSMutableArray class]]) {
    // add more boxes to childBoxes
}

이 코드는 여러분의 클래스를 망가뜨릴것이지만, 이 경우에, 코드는 작고, 일부분을 처리하기 위해 개발자에게 맡겼습니다.

여기에서 사실, 훨씬 더 나쁘고 예기치 않는 행동을 보여줍니다.

Box *box = [[Box alloc] init];
NSArray<Box *> *childBoxes = [box boxes];

[box addBox:[[Box alloc] init]];
NSArray<Box *> *newChildBoxes = [box boxes];

여기에서 기대하는 것은 [newChildBoxes count] > [childBoxes count] 이지만, 그렇지 않는 경우는 무엇인가요? 그런 다음 클래스는 이미 반환된 값을 변경하기 때문에 설계한데로 되지 않습니다. 부등식이 true가 아닐것이라 믿는 경우, UIView 와 [view subviews]로 시험해 보세요.

운이 좋게도, 우리는 우리 코드를 첫번째 예제에서 getter을 다시 작성해서 쉽게 수정할 수 있습니다.

- (NSArray *)boxes {
    return [self.m_boxes copy];
}

일반적인 실수 5 : iOS NSDictionary의 내부 동작을 이해하지 못한다(Not Understanding How iOS NSDictionary Works Internally

사용자 정의 클래스와 NSDictionary로 작업한 적이 있는 경우, 딕셔너리 키(key)로 NSCopying을 준수하지 않으면 클래스를 사용할수 없다는 것을 알게 될 것입니다. 대부분의 개발자들은 Apple이 왜 그러한 제한을 추가 했는지 물어보지 않습니다. 왜 Apple은 키(Key)를 복사하고 원래 객체 대신에 복사본을 사용할까요?

이를 이해하기 위해서는 NSDictionary 내부적으로 어떻게 동작하는지가 핵심(key) 입니다. 엄밀히 말하면, 그것은 해쉬(hash) 테이블일 뿐입니다. 키(key)에 대한 객체를 추가하는 동안 상위 레벨에서 어떻게 동작하는지 간단히 요약해 봅시다(테이블 크기조절과 성능 최적화는 단순화를 위해 여기에서 생략됨).

1단계 : hash(Key) 계산하기.

2단계 : 해쉬(hash)를 기반으로, 객체를 넣을 곳을 찾습니다. 일반적으로, 이것은 딕셔너리 길이로 해쉬 값의 절대값을 가져서 마무리합니다. 결과 인덱스는 Key/Value쌍을 저장하는데 사용됩니다.

3단계 : 그 위치에 객체가 없으면, 연결 목록을 만들고 레코드(Object와 Key)를 저장합니다. 그렇지 않으면 목록의 끝에 레코드를 추가합니다.

이제, 딕셔너리에서 레코드를 가져오는 방법을 설명합니다.

1단계 : hash(Key)계산하기.

2단계 : 해시로 키(Key)를 검색합니다. 그곳에 데이터가 없으면 nil이 반환됩니다.

3단계 : 연결 목록에 있는 경우, [stored_key isEqual:Key] 될때까지 그 객체(Object)를 반복합니다.

안쪽에서 일어나는게 무엇인지 이해를 통해 두가지 결론에 이를수 있습니다.

  1. 키(Key)의 해쉬가 변경된 경우, 레코드는 다른 연결 목록으로 이동해야 합니다.
  2. 키는 고유해야 합니다.

간단한 클래스에서 예제를 봅시다.

@interface Person
@property NSMutableString *name;
@end

@implementation Person

- (BOOL)isEqual:(id)object {
  if (self == object) {
    return YES;
  }

  if (![object isKindOfClass:[Person class]]) {
    return NO;
  }

  return [self.name isEqualToSting:((Person *)object).name];
}

- (NSUInteger)hash {
  return [self.name hash];
}

@end

이제 NSDIctionary 가 키(Keys)를 복사하지 않는다고 상상해 보세요:

NSMutableDictionary *gotCharactersRating = [[NSMutableDictionary alloc] init];
Person *p = [[Person alloc] init];
p.name = @"Job Snow";

gotCharactersRating[p] = @10;

오! 거기에 오타가 있습니다! 수정합시다!

p.name = @"Jon Snow";

딕셔너리에 무슨 일이 발생되나요? 이름이 변경되면 다른 해쉬를 가집니다. 이제 우리 객체(Object)는 잘못된 위치에 있고(여전히 예전 해쉬값을 가지며, 딕셔너리는 데이터가 변경된것에 관해 알지 못합니다), 딕셔너리에서 데이터를 검색하는데 사용할 해시가 무엇인지 명확하지 않습니다. 더 나쁜 경우가 있을수 있습니다. 이미 5점으로 딕셔너리에서 Jon Snow을 가지고 있다고 상상해 보세요. 디겨너리는 동일한 키에 대해 두개의 다른 값으로 끝날것입니다.

보다시피, NSDictionary에서 변경가능한(mutable) 키(keys)를 사용하면 많은 문제가 발생할 수 있습니다. 이런 문제를 피하는 가장 좋은 방법은 객체를 저장하기 전에 복사하는 것이고, copy로 프로퍼티를 표시하는 것입니다. 이 방법은 여러분의 클래스를 일관되게 유지하는데 도움이 될 것입니다.

일반적인 실수 6 : XIB 대신에 스토리보드 사용하기(Using Storyboards Instead of XIBs)

대부분의 새로운 iOS개발자들은 Apple의 제안을 따라 기본적(default) 으로 UI에 스토리 보드(Storyboards)를 사용합니다. 하지만, 많은 단점이 있고 스토리 보드 사용시 장점은 몇 가지만(논쟁의 여지가 있음) 있습니다.

스토리 보드의 단점 :

  1. 여러명의 팀 구성원에 대해 스토리보드를 수정하는 것은 정말 어렵습니다.

    엄밀히 말해, 많은 스토리보드를 사용할 수 있지만, 이 경우에 유일한 장점은, 스토리보드의 컨트롤러 간에 연속성(segues)을 가질수 있습니다.

  2. 스토리 보드의 컨트롤러(controllers)와 연속성(segues) 이름은 문자열이며, 코드 전체에서 이러한 모든 문자열(String)을 다시 입력(re-enter)해야(언젠가 문제가 될수있음) 하거나 스토리 보드 상수의 큰 목록을 유지해야 합니다. SBConstants를 사용할 수 있지만, 스토리보드에서 이름을 바꾸는 것은 여전히 쉬운 작업이 아닙니다.

  3. 스토리보드는 여러분을 비-모듈(non-modular) 설계를 하도록 만듭니다. 스토리보드로 작업하는 동안 View들을 재사용해야할 동기(incentive)가 거의 없습니다. 이것은 MVP(minimum viable product) 또는 빠른 UI 프로토타입에 대해서 적용가능하지만, 실제 앱에서는 동일한 View를 여러번 사용해야할 수도 있습니다.

스토리 보드의 장점(논란의 여지가 있음) :

  1. 앱 전체 탐색을 눈에 볼수 있습니다. 그러나, 실제 앱은 서로 다른 방향으로 연결된, 10개 이상의 컨트롤러(controllers)가 있을수 있습니다. 스토리보드 연결은 실의 볼처럼 보이고 데이터 흐름을 이해하는데 높은 수준을 제공하지 않습니다.
  2. 정적인 테이블. 이것은 유일한 실제 장점이라 생각할수 있습니다. 문제는 앱을 개선하는 동안 정적인 테이블의 90%가 동적인 테이블로 변경되기 쉽고 동적인 테이블은 XIB에서 더 쉽게 처리할 수 있습니다.

일반적인 실수 7 : 객체와 포인터 비교의 혼란(Confusing Object & Pointer Comparison)

두 객체를 비교하는 동안, 두가지가 같은지 비교할수 있습니다: 포인터와 객체 비교.

포인터가 같다는 것은 두 포인터가 같은 객체를 가리 가리키는 상황입니다. Objective-C에서, 두개의 포인터를 비교하기 위해서 ==연산자를 사용합니다. 객체가 같다는 것은 데이터베이스에서 사용자가 같은것 처럼, 두 객체가 논리적으로 같은 상황입니다. Objective-C에서, 두 객체를 비교하기 위해 isEqual 또는 타입이 지정된 isEqualToStringisEqualToDate, 등등 더 나은 것을 사용합니다.

다음 코드를 검토하세요 :

NSString *a = @"a";                         // 1
NSString *b = @"a";                         // 2
if (a == b) {                               // 3
    NSLog(@"%@ is equal to %@", a, b);
} else {
    NSLog(@"%@ is NOT equal to %@", a, b);
}

이 코드를 실행할때 콘솔에 무엇이 출력되나요? 두 객체 a와 b 모두 메모리에서 동일한 객체를 가리키는 것처럼 a is equal to b 를을 얻을 것입니다.

하지만 이제 #2번 줄을 다음처럼 변경해 보세요:

NSString *b = [[@"a" mutableCopy] copy];

이제 동일한 값을 가지더라도 다른 객체를 가리키고 있으므로 a is NOT equal to b를 얻을 것입니다.

이 문제는 isEqual또는 타입 지정 함수로 교체하여 피할수 있습니다. 코드 예제에서 항상 제대로 동작하도록 #3번 줄을 다음 코드로 교체해야 합니다.

if ([a isEqual:b]) {

일반적인 실수 8 : 하드코딩된 값 사용하기(Using Hardcoded Values)

하드코딩된 값 사용은 두가지 중요한 문제가 있습니다.

  1. 무엇을 나타내는지 명확하지 않을때가 있습니다.
  2. 코드에서 여러군데 사용해야 할때 다시 입력(또는 복사 붙여넣기)해야 합니다.

다음 예제를 검토하세요.

if ([[NSDate date] timeIntervalSinceDate:self.lastAppLaunch] < 172800) {
    // do something
}
or
    [self.tableView registerNib:nib forCellReuseIdentifier:@"SimpleCell"];
    ...
    [self.tableView dequeueReusableCellWithIdentifier:@"SimpleCell"];

172800은 무엇을 나타내나요? 왜 사용하나요? 그것은 아마도 2일에 대한 초(seconds)와 상응한다는 것도 분명하지 않습니다 (하루에 , 24 x 60 x 60 또는 86,400 초).

하드코딩된 값을 사용하는 대신에, #define문으로 정의된 값을 사용할 수 있습니다. 예를 들어:

#define SECONDS_PER_DAY 86400
#define SIMPLE_CELL_IDENTIFIER @"SimpleCell"

#define은 코드에서 정의된 이름을 값으로 교체해주는 전처리기 매크로 입니다. 따라서 #define을 헤더파일에서 가지고 있고 다른곳에서 가져오면(import), 파일에 정의된 모든 값들은 교체될것입니다.

한가지 문제를 제외하고는 잘 동작합니다. 남은 문제를 설명하기 위해, 다음 코드를 검토하세요.

#define X = 3 
...
CGFloat y = X / 2;  

이 코드가 실행된 뒤에 y값은 무엇이 될까요? 1.5라고 말하면 잘못된 것입니다. 코드가 실행되고 난 후에 y는 1(1.5가 아님)과 같을 것입니다. 왜 그럴까요? 답은 #define이 타입에 대한 정보가 없다는 것입니다. 따라서 우리의 경우에, 두개의 Int값(3과 2)을 나누며, 그 결과 Int가(1) 되고나서 Float로 형변환 됩니다.

상수를 대신 사용하면 피할수 있습니다. 정에 다음을 입력 :

static const CGFloat X = 3;
...
CGFloat y = X / 2;  // y will now equal 1.5, as expected

일반적인 실수 9 : Switch 문에서 Default 키워드 사용하기(Using Default Keyword in a Switch Statement)

Switch문에서 default 키워드를 사용하면, 버그나 예치지 않는 동작이 발생 할 수 있습니다. Objective-C 에서 다음 코드를 검토하세요.

typedef NS_ENUM(NSUInteger, UserType) {
    UserTypeAdmin,
    UserTypeRegular
};

- (BOOL)canEditUserWithType:(UserType)userType {

    switch (userType) {
        case UserTypeAdmin:
            return YES;
        default:
            return NO;
    }

}

Swift로 작성된 동일한 코드

enum UserType {
    case Admin, Regular
}

func canEditUserWithType(type: UserType) -> Bool {
    switch(type) {
        case .Admin: return true
        default: return false
    }
}

이 코드는 의도된대로 동작하며, Admin 사용자만 다른 레코드를 변경할 수 있습니다. 하지만, 레코드를 편집 할 수 있는 다른 사용자 타입 Manager를 추가 할수 있나요? switch문을 업데이트해는 것을 잊은 경우, 코드는 컴파일 될것이지만, 예상대로 동작하지 않을 것입니다. 하지만 개발자가 처음부터 default 키워드 대신 enum 값을 사용한 경우, 컴파일 할때 확인될것이고, 테스트나 출 전에 수정할 수 있습니다. Objective-C에서 이를 처리하는 좋은 당법은 다음과 같습니다.

typedef NS_ENUM(NSUInteger, UserType) {
    UserTypeAdmin,
    UserTypeRegular,
    UserTypeManager
};

- (BOOL)canEditUserWithType:(UserType)userType {

    switch (userType) {
        case UserTypeAdmin:
        case UserTypeManager:
            return YES;
        case UserTypeRegular:
            return NO;
    }

}

Swift로 작성된 동일한 코드

enum UserType {
    case Admin, Regular, Manager
}

func canEditUserWithType(type: UserType) -> Bool {
    switch(type) {
        case .Manager: fallthrough
        case .Admin: return true
        case .Regular: return false
    }
}

일반적인 실수 10 : 로그를 위해 NSLog 사용하기(Using NSLog for Logging)

많은 iOS 개발자가 앱에 로그를 위해 NSLog를 사용하지만, 대부분 끔찍한 실수입니다. NSLog 함수 설명에 대한 Apple 문서를 확인한 경우, 매우 간단하다는 것을 알수 있습니다.

void NSLog(NSString *format, ...);

무엇이 잘못될 가능성이 있나요? 사실상 없습니다. 하지만, 디바이스를 Xcode Organizer에 연결하면, 모든 디버그 메시지가 표시됩니다. 이러한 이유만으로 NSLog를 절대(naver) 사용하지 않아야 합니다: 원치 않는 내부 데이터가 쉽게 보이고, 추가적으로 전문가가 아닌 것으로 보입니다.

더 나은 방법은 NSLog를 설정가능한 CocoaLumberjack 또는 다른 로깅 프레임워크로 교체하는 것입니다.

마무리(Wrap Up)

iOS는 매우 강하고 빠르게 진화하는 플랫폼입니다. Apple은 Swift 언어를 지속적으로 확장하는 동안에, iOS에 새로운 하드웨어 및 기능을 도입하기 위해 지속적으로 많은 노력을 하고 있습니다.

Objective-C와 Swift 기술을 향상시키면 훌륭한 iOS 개발자가 될것이고, 첨단 기술을 사용하여 도전적인 프로젝트를 수행 할 수 있는 기회를 제공할 것입니다.

관련 : iOS 개발자 가이드 : Objective-C에서 Swift 배우기


반응형
Posted by 까칠코더
,