안녕하세요 문씨입니다
* 이 강좌는 먼저 올렸던 IB있든말든 SDK버전 문제 해결하기(http://cafe.naver.com/mcbugi/61663)편을 수정하여 내용을 보강 한것입니다.
이번 강좌에서는 매번 SDK가 버전업 하면 가지는 문제를 해결하기위한 해법을 제시합니다.
최신 SDK를 설치하게 되면 기존 버전들은 다 지워집니다
*3.x대의 SDK경우 계속 추가되는 방식이었으나 4.0부터 필요한 버전 외에는 제거됩니다.
그리고 실제로 빌드 가능한 버전은 버전으로 기기를 구별하는 좀 이상한 경우라서 아이패드용인 3.2와 아이폰용인 4.0이렇게 두개가 됩니다.
*나중에 아이패드가 4.0으로 나오면 버전 통합이 있을것으로 보입니다.
4.0 SDK 업데이트를 통해 애플은 xCode에 있었던 버전 선택 부분을 없에 버렸습니다.
이제는 최신 버전으로만 빌드하도록 유도하는 애플입니다.
당연히 최신 SDK를 설치한 상태에서 하위 버전을 사용하려고 해도 SDK가 없다고 뜹니다.
기존의 개발하던 프로젝트를 열면 Missing SDK가 뜹니다.
그렇다면 할수있는 방향은 네가지입니다.
- 최신 버전 전용으로 앱을 수정한다.
- 개발자 편의용입니다. 빌드를 최신버전에 맞추고 그냥 맞추어 사용하는겁니다. 가장 편하고 몇가지 버려진 API만 수정해 주면 됩니다.
- 최신 SDK의 사용을 포기하고 현재 개발중인 버전을 유지한다.
- 최신 버전을 포기하는 방법인데 별로 추천하지 못합니다. 최신 버전이 설치된 기기에서 테스트 해봐야 하는데 이 방법으로는 xCode에서 기기를 인식하지 못하고 기기에서 앱에 어떤 에러를 보여줄지 테스트 해보지 않고는 모릅니다. 그리고 계속 바뀌어 나가는 애플에 열심히 배우고 따라가는 취지에는 맞지 않습니다.
- 최신 SDK를 설치하고 직접 구 SDK에서 SDK데이터를 가져온다.
- /Developer/Platforms/iPhoneOS.platform/Developer/SDKs 위치에 있는 폴더들이 SDK 버전별로 있는것인데 구 SDK를 따로 설치한다음 위 폴더로 폴더들을 복사해 넣고 따로 설치한 구 SDK 는 지우는겁니다. (용량낭비니) 이 방법은 좀 변칙적인 방식입니다. 애플에서 구버전을 제공하지 않으니 직접 넣어주는 방법인데 시뮬레이터에서 실행 할수가 없습니다.시뮬레이터용 SDK를 복사해 넣어도 문제의 시뮬레이터가 SDK 정보가 없어서 에러납니다. 2번과 같은이유로 별로 추천하지는 않습니다.
- 최신 SDK를 지원하면서 구 버전에도 설치할수 있도록 만든다.
- 애플에서 유도하고 있는 방법입니다. 이미 해외 앱들은 당연한듯 개발하고 있고 이방법을 사용하는게 최고인듯 하지만 위 세가지 방법보다 제일 어렵고 번거롭습니다.
* 4.0기기에 설치하면 멀티테스킹(이라 쓰고 FAS라 읽는다)도 지원되고 3.0기기에 설치하면 지원 안되지만 실행은 됩니다.
버전 호환 문제가 있는 이유는 호환시키는 방법을 잘 모르기 때문입니다.
구버전으로 빌드하면 별다른 설정 없이도 최신 버전에서 작동합니다. 문제는 그 반대 입니다. 최신 버전으로 빌드한 앱을 올리면 그 버전을 사용하는 유저만 설치가 가능해집니다. 그래서 이전부터 모르고 최신으로 올렸다가 유저들의 원성으로 다시 하위 버전으로 빌드해서 올리는 경우가 허다했습니다.
최신버전으로 빌드하면서 구버전도 지원하려면 두가지가 필요합니다.
프로젝트 설정과 코딩 규칙입니다.
- 설정
프로젝트 설정
*이 스샷은 SDK4.0입니다 내용을 보완하는 시점에는 4.1 베타 3까지 나와있습니다. 그리고 선택가능한 버전은 아이패드용인 3.2와 아이폰용인 4.1입니다.
프로젝트 설정에서 위 스샷 처럼 Base SDK는 최신 버전의 Device를 선택합니다
그리고 Deployment Target을 지원할수 있는 최하 버전까지 선택합니다
프리뷰로 나와있는 xCode 4.0은 (알아)보기좋게 바뀌어 있습니다
슬라이더로 구성되서 어느 버전부터 어느 버전까지 설치가능한지 한눈에 보입니다. 프로젝트 설정은 이정도입니다. 기존의 Missing SDK를 표시하는 프로젝트가 있다면 위와같이 수정하면 됩니다. 그리고 이렇게 수정하면 구버전의 기기 테스트에서도 특별히 바뀐 API가 없다면 잘 돌아갑니다. 다음은 프레임워크 설정입니다. 프레임워크 중에는 3.x부터 추가된것도 있고 4.x부터 추가된것도 있습니다 4.0부터 추가된 프레임워크의 예로 iAd가 있습니다. 문제는 이 프레임워크를 넣어두면 3.x의 기기에서 튕긴다는 겁니다. 프레임워크를 못찾았다는 이유입니다. 앱은 로드될때 연결된 프레임워크를 확인합니다. 그래서 로그도 못찍어보고 바로 튕기는 겁니다. 앱에서 프레임워크에 대해 반드시 필요하다고 설정해 놓았기 때문에 일어나는 문제인겁니다. Target의 항목의 정보를 엽니다 연결된 라이브러리로 프레임워크들이 있습니다 종류(Type)을 보면 Required가 보입니다. 이걸 있어도 그만 없어도 그만이라는 뜻의 Week으로 바꿔줍니다. 이제 설정은 끝났습니다. 2. 코딩 컴파일러는 #if문을 사용하는 방법으로 빌드하는 SDK버전을 인식해서 상황에 맞는 코드를 사용하도록 구분하는것입니다. 보통 컴파일러 조건문인 #if는 여러 버전에서 같이 사용되는 프레임워크나 직접 만든 공용 클래스에서 많이 사용됩니다. 예) 같은 프레임워크도 여러 버전에서 빌드되는 일이 있기때문에 #if 문을 사용하여 빌드 당시 버전을 체크합니다 위 스샷의 경우 #if __IPHONE_4_0 <= __IPHONE_OS_VERSION_MAX_ALLOWED 를 사용했는데 __IPHONE_4_0의 경우 상수라 보면 됩니다. 고정된 값입니다. __IPHONE_OS_VERSION_MAX_ALLOWED는 빌드하는 버전에 맞춘 수치로 빌드버전마다 선언되는 버전이 다릅니다. 아이패드용 3.2의 경우 아이폰용 4.1의 경우 즉 해당 빌드 버전을 구별해서 사용할 코드는 사용하고 사용해서 안되는 코드는 빌드에 사용되지 않도록 할수 있습니다. 즉 위 예시는 빌드가 4.0 이상 일때만 저 #if안에 들어있는 코드를 컴파일 할때 사용하도록 한것입니다. 제가 사용중인 공용 클래스중 하나가 멀티다운로더인데 3.x대의 빌드의 경우 NSThread를 사용하였고 4.x이상의 경우 GCD를 사용하도록 구성되어 있습니다. 이 컴파일 조건문은 빌드시 적용되는 것으로 앱 실행중에 버전에 맞추어서 작동하는 것과는 다른겁니다. 주의하세요 컴파일러에서 조건문을 사용하는 경우는 빌드할때 조건에 맞는 코드가 생성되는겁니다.. 예를 들어 NSLog는 디버깅할때만 사용하는것이기에 시뮬레이터에서 돌릴때는 사용하고 기기에서 돌릴때는 사용하지 않도록 하는 경우도 있습니다. 즉 컴파일시 분간하는 것은 실제 앱이 모든 기기에서 모든 버전에서 호환되도록 하는것과는 거리가 있습니다. 즉 코드로 기기의 상태를 확인해서 대응할 필요가 있습니다. 보통 버전별로 다르기 때문에 시스템의 버전을 찾아다 비교하는 방식을 사용하기도 합니다만 하지만 이 방법은 별로 추천하지는 않습니다. 같은 버전이라도 상황에 따라 지원안하는 경우도 있기 때문이죠. 그저 이 버전부터는 지원되니 그냥 사용한다는 사고방식은 문제가 있습니다. 추천하는 방식은 기능별로 나누어서 확인 하는 방법입니다. 단순히 iOS버전별로 구분하는 것이 아니라 기기 종류에 따라 구분도 해야 하기 때문이죠. 기기는 점점 종류가 많아지고 있습니다. 애플조차 처음에 앱의 기능 구분을 전화와 GPS, 카메라가 있는 아이폰과 없는 터치로 구분했었습니다만, 나중에는 이 앱이 어디까지 기능이 필요한지 정의 하도록 하였습니다. 그러면 스토어에서 해당앱이 어느 기기까지 설치 가능한지 분석하여 설치 유무를 알려주었습니다. 그럼 어떻게 iOS버전에 맞추는지 보겠습니다. 아무리 Objective-C만 한다 하더라도 나중에 좀더 심도있는 기능을 구현하려면 C함수의 사용은 필연적입니다. 그리고 C 함수는 프레임워크에 따라 아직 추가가 안된경우도 있을수 있습니다. 정의되지 않은 C함수를 사용하면 당연히 심볼에러를 내면서 튕기게 됩니다. 그 예중 하나가 GCD의 Dispatch함수입니다. 이 함수들은 iOS4.0부터 추가된것이라 그냥 하위버전에서 사용되면 당연히 문제가 됩니다. 심볼체크하는 방법은 간단합니다 그 함수명을 쓰는 겁니다. 단 실행이 아닙니다. 예를 들어서 queue = dispatch_queue_create(“test”, NULL); 함수가 있으면 이 코드는 실행한겁니다 심볼 체크하려면 if (dispatch_queue_create) 를 합니다. C함수는 기본적으로 끝에 인자가 있던 없던 ()가 붙습니다. 그래서 foo()는 실행하는 코드고 foo면 함수의 포인터 주소를 돌려주기 때문에 그 함수가 존재하는지 확인할수 있습니다. 실행 -> foo(); 심볼 체크 -> if (foo != NULL) { ... Objective-C의 객체의 경우 respondsToSelector함수로 확인합니다. 객체에 해당함수가 있으면 YES가 돌려집니다. 이 함수는 보통 델리게이트 선언중 @optional을 사용하는 객체에서 단골 코드입니다. 그 예를 들면 4.0부터 로컬푸시 기능이 제공됩니다. 그렇다면 시스템의 버전이 4.0이상인지 확인하는 것보다는 함수가 있으면 지원한다고 판단하고 기능을 사용하는 것입니다. 보통 이런식으로 판단하지만 무작정 함수가 있다고 사용하는 것도 금물입니다. 그 기능에 대해 확실히 조건을 알아두어야 한다는 겁니다. 그 예로 백그라운드의 경우 UIApplication에 백그라운드 관련 함수가 있다고 해도 기기에 따라 백그라운드를 지원안하는 경우가 있기 때문에 UIDevice에 multitaskingSupported속성이 YES인지 확인해봐야 합니다. 1, 2까지 이해를 했어도 이 함수의 존재 없이는 호환성 성립 못하는 것이 있습니다. 이 NSClassFromString함수는 문자열을 그대로 클래스 명으로 인식해서 클래스를 돌려주는 역활을 합니다. 이것이 없어서는 안되는 이유가 있습니다. 먼저 예문을 보겠습니다. 로컬푸시를 하기 위해 처음 객체를 만드는 부분입니다. 이 로컬푸시는 iOS4.0부터 추가되었는데 이 코드 한줄 때문에 하위버전 기기에서는 로그 한번 못 찍어보고 튕겨버립니다. 원인은 프레임워크 존재 체크랑 비슷한 이유인데 한마디로 UILocalNotification을 못찾아서 그런겁니다. 그렇다면 2번 방법으로 기능 유무를 확인 하더라도 코드 실행도 안했는데 단지 코드가 있다는 이유로 튕긴다면 방법이 없다고 볼수 있습니다. 심볼에러가 나는 부분은 정확히 보자면 alloc앞의 클래스 부분입니다. 그래서 코드를 이렇게 바꾸면 튕기지 않습니다. 클래스라는것은 알지만 어떤 클래스인지는 모르기 때문이죠. 하지만 Class로 선언한 className은 NULL입니다. 이대로 사용해도 문제가 됩니다. 생성이 안되기 때문이죠. 그렇다고 Class className = UILocalNotification;으로 바꿀수도 없는 노릇입니다. 그래서 사용하는 것이 NSClassFromString입니다. 문자열을 넣으면 Class로 돌려주지만 그 클래스가 없으면 NULL이 돌려집니다. 물론 위 코드 방법도 안전한 방법은 아닙니다. 확실히 하자면 이렇게 하는게 정답일겁니다.
그렇다면 UILocalNotification *push부분은 어떤것일까요?
클래스로 사용한 부분은 문제가 되고 변수 부분은 문제가 안된다는 것은 이상하게 보일수도 있습니다.
당연히 문제 없습니다. 객체 타입 선언은 어디까지나 컴파일러에서 인식하기 위한 것일뿐 빌드후에는 id push로 인식됩니다. 어떤 Objective-C객체든 그것을 잡고있는 변수는 그저 메모리 주소를 가지는 변수일 뿐입니다. 실제로 다른 객체 선언을 해놓고 선언되지 않은 함수를 사용하면 경고는 뜨지만 객체에 함수만 제대로 있다면 별 문제 없이 실행됩니다. 이렇게 버전업을 할때마다 추가되는것이 있고 제거되는 것도 있습니다. 호환테크닉에 정말 중요한것은 이해입니다. 사용하고자 하는 기능을 이해하고 버전의 변화를 거치면서 무엇이 바뀌었는지 확실히 알아둘 필요가 있습니다. 여기까지 버전별 호환 테크닉이었습니다. 감사합니다.
이렇게 UIApplication객체를 불러와서 respondsToSelector함수로 scheduleLocalNotification:함수가 있는지 확인하는 겁니다.
직접 선언한 부분을 Class변수로 교체했기 때문에 심볼 체크가 일어나지 않습니다.
'강좌' 카테고리의 다른 글
| [문씨의 강좌] iOS 버전별 호환 테크닉 (2) | 2011/03/07 |
|---|---|
| [문씨의 강좌] 멀티스레딩3 <Grand Central Dispatch> (2) | 2011/03/07 |
| [문씨의 강좌] 멀티스레딩2 <NSOperation> (0) | 2011/03/07 |
| [문씨의 강좌] 멀티스레딩 <NSThread> (2) | 2011/03/07 |
| [문씨의 강좌] 메모리 관리3 <Singleton> (0) | 2011/03/07 |
| [문씨의 강좌] 메모리 관리2 <Property> (1) | 2011/03/07 |


