[Objective-C] Swift를 먼저 배운 사람이 Objective-C를 학습할 때

잡담

안녕하세요! 오랜만에 글을 작성하네요 ㅎㅎ

최근에 취직을 해서 필요에 의해 Objective - C(이하 옵젝씨)를 배우게 되었습니다.

솔직히 옵젝씨를 한 번도 안해봐서 당황스럽고, 부담스러운데요 ㅜㅜ 학습을 진행하던 와중에 이런 생각이 들었습니다.

“처음부터 끝까지 학습한다고해서 과연 학습한 내용들이 장기기억으로 넘어갈까?”

솔직히 저는 단순 암기는 잘 못하는 편이라서 이런 순차적인 학습 방식으로는 장기기억으로 넘어가지 않는 편입니다. (그냥 공부를 못하는 걸수도..)

자! 그러면 어떻게 해야할까요?

결론적으로는 내가 Swift를 지금까지 공부해오면서 이건 꼭 알아야 개발할 수 있다고 생각한 중요한 부분만 Swift와 비교하면서 학습해보려고 합니다.

사실 이게 조삼모사 느낌이고, 어떤 사람들은 그래도 순차적으로 배워야 탄탄하게 배우는 거다라고 할 수 있습니다.

하지만 iOS 개발이 처음이 아니고 Swift로 이미 개발을 해봤기 때문에 어떤 부분에 대해 학습이 필요한지 정확하게 알고 있다고 생각합니다. 물론 옵젝씨를 처음하는 사람은 순차적으로 처음부터 배우는게 맞다고 생각합니다.

이 게시글은 학습할 때마다 업데이트 될 예정입니다.

학습 내용

  • Class 생성 방법
@interface SimpleClass : NSObject

@end

class를 생성할 때, @interface라는 키워드로 생성됩니다. 솔직히 이게 Swift에서는 음.. 이 부분을 protocol과 매우 비슷한데, 구현은 하지않고 선언만하면 되는 부분이라고 생각하면 됩니다.

음.. interface와 implementation이 하나의 세트라고 생각하면 되고, @protocol 키워드로 protocol을 생성하면 implementation을 구현할 필요가 없습니다.

구체적인 구현을 할꺼면 @implementation이라는 키워드가 필요합니다.

// class
@interface SimpleClass : NSObject {
    // member variable
    int wheels;
    int seats;
}

// member method
-(void)setWheels:(int)w;
-(void)setSeats:(int)s;

@end

보는 바와 같이, 중괄호 안에 있는 부분이 변수를 선언할 수 있는 영역이고, 닫는 중괄호부터 @end까지의 영역이 메서드를 정의하는 영역입니다.

메서드를 정의하는 부분에서 파라미터를 설정하는게 혼동이 있을 것이라고 생각합니다. 콜론 뒤에 (int)w 방식으로 타입을 정해주고 이름을 정의 해줍니다.

이 부분에서 @property라는 키워드를 통해 변수를 생성한다면, set과 get에 대한 메서드가 자동으로 생성됩니다.

@property int wheels;
@property int seats;
  • var , let은 없다..

아.. 이것도 정말 당황스러웠던 부분인데, 선언이 완전 C언어랑 똑같습니다. var과 let이 없어요! int, double 등..

  • mutable, immutable

Array랑 Dictionary가 mutable한게 있고, immutable한게 있습니다. 음.. String도 따로 구분지어놨더라구요.. (옵젝씨에서는 NSString입니다.)

mutable이라는 키워드가 들어가 있지 않으면, immutable 타입입니다. 즉, 수정이 불가능하다는 거죠!

  • 다중 파라미터 메서드
-(void)drawCircleX:(int)x :(int)y;

아 달라도 너무 달라요… 제가 뭐.. 언어를 많이 써보진 않았는데 이런 형식 처음 봅니다.

콜론이 기준이기 때문에 파라미터가 추가 될 경우 이전 파라미터에서 한 칸을 띄고 콜론을 써준 후 정의를 합니다.

  • new vs alloc

참고링크 : https://pk09.tistory.com/entry/Objective-C-New와-Alloc의-차이

  • viewController에서 protocol 채택
@interface ViewController : UIViewController <UITextFieldDelegate>

@end

아.. 그냥 콤마하고 옆에다가 더 쓰면 되는 줄 알았는데, 이런 것까지 다르다니…

Swift 그리워요.. 😭

  • extension vs category

소오오올직히 당연히 extension은 있다고 생각했어요… 그런데 없다고 하더라구요

category가 비슷한 기능을 한다고 합니다. extension 자체가 없는 건 아니고, 프로토콜 채택이 불가능한 extension은 있어요.. 뭔가 개념이 좀 애매모호 하네요.

// 예시
@interface Fraction (MathOps)
-void add;
@end

소괄호 안에 있는 내용은 카테고리를 저 이름으로 정의한다고 생각하면 됩니다. extension과 마찬가지로 카테고리에는 프로퍼티 추가가 불가능합니다. 음.. 여기서 살짝 아쉬운 부분은 프로토콜을 채택할 수 없다는 점?

  • automic

쓰레드에 lock을 건다는 것은 데이터를 변경할 때, 데이터 변경 작업 이외의 다른 모든 작업이 멈추도록 만듭니다.(좀 더 정확히는 SpinLock을 걸게 되면, 크리티컬 섹션의 동작이 모두 끝날 때까지 쓰레드가 루프를 돌면서 busy waiting하게 됩니다.)

그래서 이는 달리 말하면, 데이터가 시간 소모 없이 바로 변경된 것처럼 보이게 합니다. 데이터 변경시에 데이터 업데이트 이외에는 아무런 작업이 일어나지 않았기 때문입니다. 이런 방식으로 atomic 데이터는 멀티 쓰레드 환경에서 데이터가 반드시 변경 전, 변경 후의 상황에서만 접근되도록 하는 것을 보장합니다. 즉, 데이터의 변경 중에는 해당 데이터에 접근이 불가능(lock이 걸려 있으므로)합니다.

ObjectiveC Property는 기본적으로 atomic으로 선언됩니다. 다만, atomic property는 위에서 본 것처럼 atomic 데이터가 변경 도중에 접근하지 못 하도록 lock이 걸리기 때문에 property 접근의 성능이 느려집니다. 그래서 멀티 쓰레드에서 접근될 이유가 없는 많은 ObjectiveC Property(반드시 메인스레드에서 업데이트 해야하는 View Property 같은 것들)에는 nonatomic annotation을 설정하는 것이 좋습니다.

참고 링크 - https://hcn1519.github.io/articles/2019-03/atomic

  • Dispatch Queue

진심으로 이건 꼭 알아야하는 부분입니다. 결국 Closure(Block)를 쓰면 쓸 수 밖에 없는 GCD..

Swift랑은 사용하는 방식이 좀 다른데, Swift는 할당을 해도 상관없지만 할당 없이도 그냥 클로저 열어서 사용하면 되는데 옵젝씨는 방식이 좀 다르네요..

// 1) Dispatch Queue를 생성합니다.
// 1-1) serial dispatch queue를 생성합니다.
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
// 1-2) concurrent dispatch queue를 생성합니다.
dispatch_queue_t concurrentQueue = dispatch_queue_create(""test", DISPATCH_QUEUE_CONCURRENT);

// 2) Main Dispatch Queue를 얻어옵니다.
dispatch_queue_t mainQueue = dispatch_get_main_queue();

// 3) Global Dispatch Queue를 얻어옵니다.
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

위와 같은 방식으로 변수를 할당할 수 있고, 이제 저 큐에 task를 추가할려면

dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{ NSLog(@"1"); });
dispatch_sync(queue, ^{ NSLog(@"2"); });
dispatch_sync(queue, ^{ NSLog(@"3"); });

이와 같은 방식으로 block을 열어서 추가하시면 됩니다. 음.. sync나 async는 상황에 따라 변경 가능합니다.

  • 블럭 안에서 순환참조(weak self)
__weak __typeof(self)weakSelf = self;

순환 참조 해결을 위해서 꼭 써야하는 건데, 이것도.. 방식이 참 다르네요 😥

  • Objective - C 에서의 escaping

이것도 혼란스러웠던 부분입니다. Swift에서는 메서드 안에서 Closure를 호출할 때, Closure에서의 결과값을 메서드의 파라미터로 넘겨주기 위해선 탈출 클로저라고 해서 escaping 키워드를 꼭 적어줘야 했습니다.

하지만 옵젝씨에서는 escaping을 적어주지 않는게 default라고 합니다.. 후.. 역으로 추적하지 않을 땐, NS_NOESCAPE 라는 키워드를 적어줘야 합니다. 하.. 왜 이런 것까지 다른걸까요 ^^ 매우 불편하네요 😠

  • NSArray랑 NSDictionary 초기화

음.. 여러가지 방식이 있지만 여기서 말하고 싶은 것은.. 진짜 그냥 값 바로 넣어서 초기화하는 방법을 말하고 싶었어요! 매번 alloc init해주니까 괄호열고 괄호 닫고 너무 귀찮아서 아.. 그냥 대충 바로 값을 넣어줄 수 없나 싶어서 찾아봤습니다.

어떻게 보면 당연히 알아야하는 것인데, 설마.. 이거까지 문법이 다르겠어라고 생각하고 그냥 Swift 식으로 해봤는데 역시나 다릅니다 ㅎㅎ 뭐이리 다른게 많은지..

NSArray *arr = @[@"123", @"1"];
NSDictionary *dic = @{@"key": @"value"};

아.. 그냥 딕셔너리도 대괄호 쓰지… 정말 매우 혼란스러운 언어입니다.

  • assign, weak, retain, strong, copy

assign : 객체의 retain count를 증가시키지 않는다. 외부에서 retain count를 감소시켜 객체가 소멸될수 있기 때문에 int와 같은 primitive type에 적합하다.

weak : assign과 거의 동일하지만 차이가 있다. assign은 객체가 소멸되어도 포인터값이 변하지 않는데, weak는 객체가 해제되는 시점에 포인터값이 nil이 된다. assign의 문제점은 객체가 해제되어도 포인터값이 남아있어 접근하려다 죽는경우가 생긴다. Objective C는 기본적으로 nil에 접근할때는 오류가 발생하지 않는다.

retain : retain count를 증가시킨다. 현재 Scope에서 객체가 유지되는것을 보장받기위해서는 retain후에 필요가 없을때 release 하도록한다.

strong : strong은 retain attribute과 동일하다. ARC를 사용한다면 strong을 사용한다.

copy : 새로운 객체를 생성하고 해당 객체의 retain count를 증가시킨다.

참고 링크 - https://mongyang.tistory.com/43

이전에 ARC가 없을 때, weak 대신 assign, strong 대신 retain을 썼다고 생각하시면 될 것 같습니다. ARC가 나오면서 그냥 weak, strong을 사용해도 상관없지만, 이전 코드를 이해하기 위해선 알아둬야 한다고 생각합니다.