내가 아직 부산에서 일하는 이유?

난 부산에서 iOS 개발자를 하면서 살고있는데 아주 가끔씩 괜찮은 오퍼로 이직 제의가 들어 올 때가 있다.

그럴때마다 계속 거절을 하는데 이유는 딱히 없다. 제시하는 연봉이 낮은 것도 아니지만 난 서울에 거처가 없어서 내 생활권을 이동하려면 돈이 적지 않게 나간다. 20대의 나라면 패기있게 갔겠지만 지금은 글쎄..?

근데 이제 iOS 개발쪽으로 기술스택이 오지게 쌓여 가면서 부산에서는 내 연차의 연봉을 맞춰 줄 수 있는 기업이 사실상 없는 것 같다.

기업만 있어도 부산에 계속 있을 텐데 말이지…

주거환경이 제공되고 연봉이 1억 2천쯤되면 이직을 생각해 볼 까하는 발직한 상상을 해본다.

창업은 또 옛날에 해봤기 때문에 귀찮다…

[센디] 이사모아 앱 업데이트

주식회사 센디로 이직하고 처음으로 처리한 작업, 이사모아 앱 리뉴얼…

현재 우리회사는 센디를 주력으로 사업을 진행하고 있으나, 회사 온보딩겸 기존에 있던 제품인 이사모아를 리뉴얼 하는 업무를 받았음

기존 버전

문제점

  1. 디자인 리소스가 없음
  2. 아키텍처가 명확하지 않음
  3. API 문서가 없음

내가 입사하기 전까지 명확한 iOS 개발자가 없었던거 같음. 안드로이드 버전을 기준으로 마이그레이션 하면서 작성한 느낌.

안드로이드 개발자 또는 학생 인턴이 기능 구현을 해서 뷰 마다 코드가 다름 어떤 부분은 안드로이드 코드를 그대로 복사한 듯한 느낌이 들기도 하고 어떤부분은 미묘한 VIP를 따르는 것 같으면서도 어딘가는 MVC이다.

다행히 Objective-C와 Swift의 혼종은 아닌듯 하나 메인터넌스가 매우 오랫동안되지 않아 엄청 구버전의 라이브러리들을 사용하고 있는 듯했다.

앱 리뉴얼

입사 후 몇 주간은 기존 코드 리딩 및 부분 개선을 진행했고 이후에 iOS 디자인 리소스가 나와서 넘겨 받았다.

디자인을 넘겨 받고 기존 프로젝트의 코드 리딩을 한 결과 점진적 기능 개선은 매우 매우 험난한 길을 갈 것 같아서 그냥 새로 만들기로 했다.

개선사항

  1. MVVM 아키텍처로 변경
  2. GitHub Action을 사용하여 CI / CD 환경 구성
  3. 유닛테스트 작성
  4. 레거시 라이브러리 제거

아키텍처 변경은 80% 정도 진행했으며 추후 개선점이 보여서 리팩토링이 필요한 상황이다.

CI / CD 환경구성은 기존에도 잘 쓰고 있어서 간단하게 설정 할 수 있었음.

테스트 코드 작성은 적극적으로 진행하지 않아서 현재 커버리지가 한 자릿수이다. 개선이 필요함…

레거시 라이브러리는 성공적으로 모두 제거했다.

일정이 부족하여 몇 가지 기능은 빠진채로 출시하게 되었는데 센디 iOS 버전이 마무리 되는대로 작업할 예정이다.

이사할 때 한 번쯤은 써보시길 바란다. -끝-

https://apps.apple.com/us/app/%EC%9D%B4%EC%82%AC%EB%AA%A8%EC%95%84-%EC%9D%B4%EC%82%AC%EB%B9%84%EC%9A%A9%EA%B3%84%EC%82%B0%EA%B9%8C%EC%A7%80-1%EB%B6%84/id1136075052

MRR(Manual Retain-Release) 붐은 온다. a.k.a 메모리 관리

https://github.com/jaemyeong/MRR

응용프로그램에서 메모리 관리란 프로그램이 실행되는 동안 메모리를 할당하고, 사용하고, 작업이 끝나면 메모리를 해제하는 과정을 의미한다. Objective-C에서는 소유권 개념으로 메모리를 관리하고 있다.

Objective-C는 두가지 방법으로 메모리 관리를 할 수 있는데, 런타임 환경에서 NSObject의 참조 카운팅(Reference Counting)을 명시적으로 추적 관리하는 MRR 방법과 컴파일할 때 미리 정해진 규칙으로 메모리 관리 코드를 삽입해주는 ARC 방법이 있다.

Xcode로 프로젝트를 생성하면 기본적으로 ARC 환경으로 설정된다. 왜냐하면 ARC 개념이 2011년도에 발표되었으므로…ㅋㅋㅋ

하지만 여기서는 MRR을 사용하여 프로젝트를 생성해 볼 것이다. 이유는 없다 그냥 다시 한 번 향수를 느끼고 싶달까..

프로젝트 생성

일단 프로젝트 언어를 Objective-C로 설정하여 생성한다.

이제 프로젝트의 빌드 설정으로 가서 ARC 설정을 NO로 변경한다.

이제 해당 프로젝트의 메모리 관리는 메뉴얼로 진행해야한다.

해당 프로젝트를 실행하면 바로 앱이 충돌이 나며 종료될 것이다. 이유는 빌드 설정을 MRR에 맞도록 변경했으나 아직 템플릿 코드는 ARC에 맞추어져 있어서 메모리 관리가 제대로 되지 않아 충돌이 발생하는 것이다.

먼저 앱의 엔트리포인트인 main.m 파일을 편집한다.

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

코드를 해석하면

  1. strong 변수 appDelegateClassName을 선언한다.
  2. autoreleasepool을 생성.
  3. AppDelegate의 클래스 타입으로 NSString 문자열 생성 (참조 카운팅 1, autorelease 대기)
  4. appDelegateClassName 변수에 값 대입 (참조 카운팅 1 증가)
  5. autoreleasepool 종료 되면서 autorelease 대기중이던 객체들 모두 릴리즈 (참조 카운팅 1 감소)
  6. UIApplicationMain 실행
  7. UIApplicationMain 함수가 종료되고 값을 반환 하면서 main 함수의 컨텍스트 종료
  8. strong 변수 appDelegateClassName 에 할당된 자원 회수 (참조 카운팅 1 감소)

순서로 진행되는데 MRR 환경에서는 strong 변수 개념이 없으므로 appDelegateClassName 변수에 값을 대입 할 때 자동으로 참조 카운팅을 증가 시켜주는 코드가 들어가지 않는다. 따라서 UIApplicationMain을 실행하는 시점에 appDelegateClassName 변수가 가지고 있는 객체의 참조 카운트가 0이 되면서 해당 값으로 UIApplicationMain 함수를 실행 하려고 하기 때문에 충돌이 나고 종료 되는 것이다.

이제 위 코드를 MRR에 맞게 변경하면 다음과 같다.

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

그냥 모든 코드를 autoreleasepool 블럭에 넣고 실행하면 끝이다. 애초에 현재 버전의 Xcode에서 Objective-C 템플릿이 왜 저렇게 변경되었는지 모르겠지만 옜날에는 위와 같은 모습이였다.

NSStringFromClass 함수로 반환된 값이 autorelease 대기 상태이고 해당 값을 사용하여 UIApplicationMain 함수를 실행하고 함수가 종료되면 해당 값을 리턴 하면서 autoreleasepool 블럭이 종료되면서 autorelease 대기중이던 자원이 모두 회수된다. 깔끔하다.

iOS 바닥에서 고이다 못해 썩은 분들은 아래와 같은 모양의 main 함수도 많이 보았을 것이다. ㅋㅋㅋ

int main(int argc, char * argv[]) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    
    int ret = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    
    [pool drain];
    
    return ret;
}

이제 이 프로젝트로 무엇을 만들어본다?…🤔

에 발행했습니다
iOS(으)로 분류되었습니다 , , , 에 태그되었습니다

Xcode Project 버전 관리

Xcode 프로젝트는 일반적으로 사용자들이 인식하는 버전과 빌드 두가지가 존재한다. 여기서 버전은 유의적 버전 표기법으로 표현 가능하고 빌드는 단순하게 증가하는 정수 값이다.

테스트 플라이트에서 인식하는 버전은 유의적 버전과 빌드는 상호 의존적이다.

0.1.0 버전의 1번 빌드와 0.2.0 버전의 1번 빌드는 독립적인 바이너리로 판단한다.

Xcode는 타겟의 버전을 Info.plist 또는 Build Settings에 기록하는데 버전을 변경할 때마다 개발자가 직접 관리하면 타겟 별로 버전 관리를 잊을 수 있으므로 버전을 관리하는 도구를 애플에서 제공하고 있다.

터미널에서 버전을 지정할 프로젝트의 경로로 이동한다음 해당 프로젝트의 모든 타겟의 빌드 번호를 변경하기 위해서는 다음 명령어를 실행한다.

$ agvtool new-version -all 1

모든 타겟의 버전을 변경하기 위해서는 다음 명령어를 실행하면된다.

$ agvtool new-marketing-version 0.1.0

업데이트 마다 버전 변경을 개발자가 직접하지 않고 도구를 사용하여 버전 기록이 유실되는 실수를 하지 않는 버릇을 들이는게 중요하다.

CI / CD 환경에서 빌드 번호 자동 증가와 같은 자동화를 할 때 사용하면 좋은 도구이기도 하다.

에 발행했습니다
iOS, macOS(으)로 분류되었습니다 , , 에 태그되었습니다

CocoaPods 설치하기

Cocoa 또는 Cocoa Touch 환경에서 개발을 할 때 외부 라이브러리 사용을 쉽게 도와주는 패키지 관리 도구는 여러가지가 있지만 이 글을 작성하는 기준으로 가장 많이 사용하는 도구는 아마도 CocoaPods 일 것이다.

내가 CocoaPods을 사용하는 방법을 정리한 글이다.

CocoaPods은 Ruby를 사용하여 개발된 의존성 관리 도구 이다. 따라서 CocoaPods을 사용하기 위해서는 Ruby가 필요한데 macOS에 기본적으로 내장되었는 Ruby를 사용하여도 충분하다.

하지만 나의 경우 macOS 버전 별로 내장되어 있는 Ruby 버전이 다르고 RubyGems를 사용하여 gem을 설치할 때에도 내장된 Ruby의 경우 sudo 권한을 요구할 수도 있다.

이러한 단점을 보완하기 위해 나는 시스템 별로 rbenv를 사용하여 Ruby 환경을 관리하고, Bundler를 사용하여 설치되는 RubyGems를 관리하고 있다. CocoaPods을 제외하더라도 Ruby로 작성된 도구를 사용할 일이 아주 가끔있는데 로컬 머신과 빌드 머신과의 Ruby 환경의 차이를 최소화 하기 위해 rbenv를 사용하는 것을 권장한다. 귀찮으면 안써도 된다.

rbenv 설치

rbenv를 설치하는 방법은 Homebrew를 사용하면 매우 쉽다.

$ brew install rbenv

rbenv를 설치한 다음 적당히 설정을 마치고(여기서는 생략) Ruby 2.6.6 버전을 설치한다. 작성일 기준으로 Ruby의 안정 버전은 3.0.0이지만 경고 없이 실행되는 CocoaPods의 버전이 2.6.6인 것 같아서 나는 2.6.6버전의 Ruby를 사용하고 있다.

$ rbenv install 2.6.6

다음으로 rbenv 명령어 또는 .ruby-version 파일을 사용하여 우리가 사용할 프로젝트의 Ruby 버전을 일치시킨다.

Bundler 설치

이제 rbenv를 사용하여 우리가 사용할 Ruby 버전을 설치했으면 Bundler를 사용하여 RubyGems의 버전을 관리하면 된다.

우리가 사용할 CocoaPods은 Ruby로 개발된 일종의 RubyGems이다. rbenv를 사용하여 우리가 사용할 Ruby 환경을 구성했으므로 이제 Bundler를 사용하여 RubyGems를 관리하면 된다. Bundler의 최신 버전 설치는 터미널에서 다음과 같이 진행하면 된다.

$ gem install bundler

이제 프로젝트 별로 Bundler를 사용하여 RubyGems를 관리하려면 우리가 작성한 프로젝트 경로로 이동하고 다음 명령어를 실행한다.

$ bundle init

이제 해당 경로에 Gemfile이 추가되었을 것이다. Gemfile에 우리가 사용할 RubyGems와 버전을 명시하면 되는데 우리가 사용할 RubyGems는 CocoaPods 최신 버전이므로 Gemfile에 다음 내용을 추가하고 저장한다.

gem "cocoapods"

이제 우리 프로젝트에 의존적인 패키지를 정의했으므로 다음 명령어로 해당 경로에 RubyGems를 설치하면 된다.

$ bundle install

CocoaPods 사용

일반적으로 로컬에 설치하는 CocoaPods RubyGems와 달리 Bundler를 사용하여 설치한 CocoaPods을 실행할 때는 다음과 같다.

$ bundle exec pod <명령어>

이제 시스템과 독림적으로 운용가능한 CocoaPods 환경이 구성되었다.