앱을 만들다보면 아주 가끔이지만 텍스트 파일을 다뤄야 할 때가 있다.




1. 파일 생성 및 저장


다음은 Documents Directory에 파일을 저장하는 방법이다.


// 경로 

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

    NSString *documentsDirectory = [paths objectAtIndex:0];

    NSString *filePath = [documentsDirectory stringByAppendingPathComponent:@"test.txt"];


// 컨텐츠

    NSString *content = @"테스트로 저장해요.";

    NSError *err = NULL;

    

 // 파일 저장

    [content writeToFile:filePath

              atomically:NO

                encoding:NSUTF8StringEncoding

                   error:&err];





2. 파일 읽기


파일을 읽을 때에는 이렇게 읽는다.


// 경로 

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

    NSString *documentsDirectory = [paths objectAtIndex:0];

    NSString *filePath = [documentsDirectory stringByAppendingPathComponent:@"test.txt"];


// 컨텐츠 읽기 

    NSString *content = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error: NULL];





3. 파일명 변경


파일명을 변경하려면 다음과 같이 변경할 수 있다.



// 경로 

    NSFileManager *fileManager = [NSFileManager defaultManager];

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

    NSString *documentsDirectory = [paths objectAtIndex:0];


// 해당 파일 경로

    NSString *oldPath = [documentsDirectory stringByAppendingPathComponent:@"test.txt"];

    NSString *newPath = [documentsDirectory stringByAppendingPathComponent:@"renamed.txt"];

    

// 파일명 변경

 NSError *err = NULL;

    [fileManager moveItemAtPath:oldPath toPath:newPath error:&err];




4. 파일 삭제

특정 파일을 지울 때는 다음과 같이 경로를 지정하여 삭제할 수 있다.

// 경로 

    NSFileManager *fileManager = [NSFileManager defaultManager];

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

    NSString *documentsDirectory = [paths objectAtIndex:0];


// 파일 삭제

[fileManager removeItemAtPath:[documentsDirectory stringByAppendingPathComponent:@"renamed.txt"] error:NULL];





네트워크 통신하다 보면 서버 시스템이 UTF-8로 데이터를 넘기지 않는 경우가 있다.


서버에서 EUC-KR로 데이터를 넘겨주면 이를 그냥 NSString으로 넘기면 글씨가 깨지기 마련이다.


다음과 같이 인코딩을 지정하여 const char 로 변환 후 NSString으로 데이터를 생성하면 정상적으로 텍스트를 읽을 수 있다.



NSUInteger encoding = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingEUC_KR);

const char * eucKRString = [original cStringUsingEncoding:encoding];

NSString *encodedString = [NSString stringWithUTF8String:eucKRString];



혹은 다음과 같이 사용해도 된다. 

NSUInteger encoding = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingEUC_KR);

NSString * encodedString = [NSString stringWithCString:[data bytes] encoding:encoding];



출처 - http://stackoverflow.com/questions/13883638/how-to-encode-nsstring-to-euc-kr

네트워크 통신하다보면 응답 데이터를  텍스트로 받을 경우가 있다.


전문도 그렇게 오는데, 공백과 줄바꿈 항목을 trim하고 싶을 때 아래와 같이 사용하면 된다.



NSString *resStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

resStr = [resStr stringByReplacingOccurrencesOfString:@" " withString:@""];     // 공백 제거

resStr = [[resStr componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]] componentsJoinedByString:@""];     // 바꿈 제거




제목은 거창하지만 답답할 사람들을 위해서 적어 본다.


음...실제 iOS 앱을 마켓 배포한지가 꽤 되어서 오랫만에 마켓 배포를 하려다 보니 좀 이상한 에러 메시지가 보인다. 


이상하게 배포 인증서도 정상 발급한 상태이고, 프로비저닝 프로파일도 제대로 발급한 상태인데도 불구하고 프로비저닝 프로파일이 매칭이 안된다니?


No matching provisioning profile found


일단 여행이 시작된다.


CSR과 배포용 인증서를 새로 발급하고, 프로비저닝 프로파일도 새로 생성하고 실행해 보았다. -> 동일 증상

Xcode를 종료하고 다시 2~3 차례 실행해보았다. -> 동일 증상

키체인 앱을 실행하여 기존 인증서와 키를 삭제하고 다시 다운로드 후 실행해보았다. -> 동일 증상




일단 Xcode > Preferences > Accounts 에서 기존 파일들을 모두 삭제한 뒤 개발자 사이트 접속해서 새로 관련 파일들을 받아서 실행해 보았다.


Archiving은 된다. 오거나이저에서 새로운 바이너리가 등록되면서 일단 아카이빙에는 성공했다.

문제는 이 바이너리의 유효성 체크를 위해서 Validate를 진행하면 다음과 같은 에러메시지가 뜬다.


Missing ios distribution signing identity


이게 뭔 소린가?


해답은 다음과 같다.


From Apple - 

Thanks for bringing this to the attention of the community and apologies for the issues you’ve been having. This issue stems from having a copy of the expired WWDR Intermediate certificate in both your System and Login keychains. To resolve the issue, you should first download and install the new WWDR intermediate certificate (by double-clicking on the file). Next, in the Keychain Access application, select the System keychain. Make sure to select “Show Expired Certificates” in the View menu and then delete the expired version of the Apple Worldwide Developer Relations Certificate Authority Intermediate certificate (expired on February 14, 2016). Your certificates should now appear as valid in Keychain Access and be available to Xcode for submissions to the App Store.


애플의 인증서 이슈였던 것.

일단 애플의 답변대로 위의 링크에서 새로 받은 것을 설치한 뒤, 키체인 관리에서 시스템 키체인 항목에서 만료된 인증서 보기를 선택 후 해당 인증서 삭제한다.

Xcode 재실행해서 아카이빙한 뒤 오거나이저를 통해서 마켓에 올리면 된다.


혹시나 안된다면 맥을 한 번 더 리부팅한 뒤 Xcode를 재실행해보자!


아래는 해당 내용을 찾은 원문...


http://stackoverflow.com/questions/32821189/xcode-7-error-missing-ios-distribution-signing-identity-for



게으른 내가 2~3시간 헛손질하다가 함 써본다. 킁!


한 2년 가까이 iOS 프로그램을 안만들었더니...세월이 넘 빠르게 흘러버렸는지 도데체 감이 안온다.

그 사이에 swift도 나왔고, iWatch도 나오고, 그리고 얼마전에는 Metal도 공개되었다.

이것저것 바뀐게 꽤 많은데, 계속 follow-up을 하지 않으니 뭐가 나왔다더라 이상은 모르는 게 사실.


대부분의 국내 기업들이 안드로이드나 하이브리드 방식으로 앱을 개발하는 관계로 꽤 오랫동안 본의아니게 등안 시 했었는데, 이러다가 iOS 다 잊어버리겠다는 생각이 들어서 하루에 조금씩 다시 관련 내용들을 살펴보기로 했다.


아무튼, 오픈 소스 컨트롤러 몇 개를 검색해보다보니 대부분의 소스들이 CocoaPod 를 통해서 개별 프로젝트에 라이브러리를 따로따로 파편화되는 것은 방지하고, repository를 통해서 관리하고, 팀 간 협업을 지원할 수 있으며, 간편하게 새로운 버전 또는 공동 작업 버전의 오프소스를 관리 및 업데이트할 수 있다고 한다.


CocoaPod 홈 - https://cocoapods.org



Ant, Maven이나 Gradle과 같은 빌드자동화 툴인지는 아니지만, 일부 기능이 일맥상통하는 부분이 있다.


Apache Ant는 빌드자동화 툴로 많이 알려졌고, xml 기반의 빌드 스크립트가 커지고 dependency 이슈로 인해서 이를 관리하기 힘들어져 나중에 Apache IVY를 추가하게 되었다고 한다.


Maven의 경우는 Ant의 이러한 문제점을 해결하기 위해서 나왔지만 여전히 xml에 사양을 기재하는 방식이다. 하지만 기존의 Ant가 빌드에 필요한 모든 커맨드들을 모두 기재해야 했다면, Maven은 규약들과 invoke 해야할 타겟을 지정하는 형태로 사용하기 때문에 Ant와는 큰 차이가 있다고 한다. 더 가볍고, 라이브러리의 버전 및 프로젝트 관리가 더 편한 Maven도 라이브러리의 dependency 관리에 이슈가 있다고 한다.(Ant는 라이브러리의 dependency를 Apache IVY 추가를 통해서 보완했다)


아무튼 이 와중에 Gradle이 2012년에 나왔고, 구글이 이를 안드로이드 배포 패키지 관리를 위한 빌드자동화툴로 선택하며서 많이 퍼지게 되었다. 아주 빠른 속도로 이 툴은 선택되었다.


Gradle 역시 Maven과 같이 필드와 라이브러리 관리를 모두 지원하며, Ant의 강력함과 유연성 그리고 Maven의 라이프사이클 관리와 사용 편리성을 모두 갖췄다고 한다. 기존의 XML을 통한 방식보다는 Groovy 언어 기반의 DSL(Domain Specific Language)을 이용한다. 그 결과 Ant나 Maven 보다 더 읽기 쉽고, 간단한 빌드 사양을 작성할 수 있다. 처음에 Apache IVY를 dependency 관리 툴로 사용했지만 나중에 자체 관리기능으로 변경했다고 한다.


*. 참조해서 읽은 글 - http://technologyconversations.com/2014/06/18/build-tools/


- Ant: https://en.wikipedia.org/wiki/Apache_Ant

- Maven: https://en.wikipedia.org/wiki/Apache_Maven

- Gradle: https://en.wikipedia.org/wiki/Gradle

- DSL: https://en.wikipedia.org/wiki/Domain-specific_language



아무튼, 또 쓰잘데기 없이 '이게 뭔가?'해서 문서들을 이것저것 읽어댔는데...


이런 빌드자동화 툴보다는 사용법은 NPM과 같은 비슷하면서 Mac 개발환경 하의 라이브러리 dependency 관리 및 프로젝트 관리에  도움을 주는 일종의 툴로 보면 될 것 같다. 


지금 떠오르는 장점을 좀 생각해보자면, 이 정도일 것 같다.


1) 라이브러리의 중복을 제거한다.

2) 라이브러리의 버전을 관리한다.

3) 팀 프로젝트의 형상관리 시 동일한 버전의 라이브러리를 운영한다.


그럼, 한 번 설치해보자.



1. CocoaPod의 설치


CocoaPod는 루비(Ruby)로 만들어졌다고 한다. OS X의 기본 설치된 루비를 이용하면 된다고 하는데, 루비 버전은 그냥 신경쓰지말라고 그냥 아래의 명령어를 입력하여 설치하면 된다고 한다.


설치 가이드 - https://guides.cocoapods.org/using/getting-started.html#getting-started



$ sudo gem install cocoapods



관리자 권한으로 명령어를 입력하면, 비밀번호 확인하고 설치가 한참있다가 시작된다. 

잠시 뭔가 이상이 생긴것이 아닌가라는 생각이 들정도로 딜레이가 있다.



내 경우에는 1분이 넘어서 아래와 같이 설치 및 완료를 볼 수 있었다.



설치 가이드 밑에 보면 sudo 권한을 통하지 않고, 설치하는 방법도 나와있으므로 참고해서 설치하고 싶은면 따라하면 될 것 같다.


설치할 때 가끔 이슈가 발생하기도 하나 보다. 그럴 경우에는 다음의 페이지를 참조해서 해당 이슈를 해결해보자.


트러블슈팅 - https://guides.cocoapods.org/using/troubleshooting#installing-cocoapods



2. CocoaPod를 통한 프로젝트 설정


간단하게 프로젝트 설정을 다음의 오픈소스를 가지고 진행해보았다.


https://github.com/jdg/MBProgressHUD




맥에서 백그라운드로 실행되는 데몬과 에이전트를 lauchd 라는 launchctl 제품이 있다고 한다.


아무튼 기본 사용법은 간단하다.


FTP 서비스 시작 >

sudo -s launchctl load -w /System/Library/LaunchDaemons/ftp.plist


FTP 서비스 종료 > 

sudo -s launchctl unload -w /System/Library/LaunchDaemons/ftp.plist


이렇게 시작하면 일단 사용자 계정의 루트로 접근하므로, 실제로 사용하려면 몇 가지 설정을 선택하여야 할 것 같다.


우선 테스트를 위해서 한 번 실행해봄.


출   처 - http://igerry.com/desktop/apple-os/enabling-ftp-server-os-x-mavericks.html

사용법 - https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man1/launchctl.1.html


제이슨킷을 포함해서 Xcode 4.4 이상에서 컴파일하면 다음과 같은 경고들이 뜬다.


1. Format specified type 'unsigned long' but the argument has type 'NSUInteger' (aka 'unsigned int')


http://stackoverflow.com/questions/4998722/why-does-only-nslog-warn-me-about-using-the-lu-string-format-specifier-for-nsui



2. Direct access to objective-c's isa is deprecated in favor of object_setClass() and object_getClass()


http://stackoverflow.com/questions/11735460/multiple-format-string-issue-warnings-in-jsonkit-m-after-upgrading-to-phonegap



대충 읽어보니...현재 버전에서 문제는 없지만 노란 경고가 줄줄이 뜨니 기분이 영 마뜩찮다.


추가한 광고 패키지에 들어있는 제이슨킷 메서드 관련된 부분인데, 직접 수정해도 되지만 요거 수정해달라고 해야 할 듯...


아무튼 해결책은 다음과 같다.


https://issues.apache.org/jira/browse/CB-1164



[GADBannerView private]: unrecognized selector sent to instance 0x9159e50 2012-08-20 


애드몹 새로운 SDK 연결했더니 이런 에러 뜨면서 앱이 죽는다...메모리 참조 문제로...


검색해보니...다음과 같은 해답이 있다. 으...음...


https://developers.google.com/mobile-ads-sdk/docs/#incorporating


ㅋㅋㅋ...SDK 다운로드 페이지 옆에 안내문 써있네...문서에도 적혀있고.

https://developer.apple.com/icloud/documentation/data-storage/


오늘 리젝을 하나 당했는데...사유가 '사용자가 생성한 데이터가 아닌 파일은 도큐멘트 디렉토리에 저장하면 안되용~' 이네.


당신이 사용자의 Document Directory에 2메가 이상의 파일들을 저장하고 있으니 위의 가이드를 잘 살펴보라고 한다.


iCloud가 나오면서 체크해야 할 부분이 몇 가지가 있는데, 데이터 저장과 관련한 몇 가지 부분을 수정해주어야 한다.

예전의 클라우드 서비스가 없을 시절에는 그다지 체크하지 않던 부분이었지만 글을 읽어보니 그 말도 맞다.


아무튼 수정해서 다시 올리면서 관련 사항을 한 번 정리해본다.


아이폰 개별 앱의 샌드박스 내에는 일반적으로 저장 공간이 세 부분으로 나뉘어진다.



1. 아이폰의 세 가지 저장 공간


1) Documents Directory:  <Application_Home>/Documents

이 디렉토리는 마치 영구 저장소같이 사용할 수 있고, 아이클라우드 백업 시 자동으로 백업이 함께 된다.

보통은 사용자가 앱을 사용하면서 필요한 내용을 저장하거나 사용자가 생성한 데이터를 저장하게 된다.


2) Library내의 Caches Directory: <Application_Home>/Library/Caches

이 디렉토리는 사용자가 선별(아이템 구매 또는 잡지 구매 같이)적으로 저장할 수 있고, 클라우드 백업 역시 선별적으로 가능하다.

보통은 추가 구매 콘텐츠나 웹 등에서 임시적으로 이미지, 동영상, pdf 등의 파일을 저장하기도 하고 기간을 두고 해제하기도 한다.


3) tmp Directory: <Application_Home>/tmp

이 디렉토리는 일시적으로 사용하게 되는 곳으로,  클라우드 백업과는 무관한 폴더다.

잠시 보여주기 위해서 저장한 파일이나 일회성으로 소비되는 리소스를 제공한다.



그냥 끝내면 재미없으니까...유익하지도 않고, 사용법을 간략하게 적어본다.

아래의 예제는 화면 스샷을 만들어서 해당 스크린샷을 각각의 폴더에 저장해본 예제임. (파일 저장 및 읽기 부분만)



2. 활용하기


1) Documents Directory:  

도큐멘트디렉토리를 지정하여 저장하려는 파일명을 패스에 추가한 뒤에 데이터를 그 지정한 패스로 저장하면 된다.


1) 쓰기

    NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString * documentsDirectory = [paths objectAtIndex:0];
    NSString * filePath = [documentsDirectory stringByAppendingPathComponent:@"screenshot.png"];
    UIImage * image = [self screenshot];
    NSData * imageData = UIImagePNGRepresentation(image);
    [imageData writeToFile:filePath atomically:NO];


2) 읽기

     NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString * documentsDirectory = [paths objectAtIndex:0]; NSString * path = [documentsDirectory stringByAppendingPathComponent:@"screenshot.png"]; NSData * imageData = [NSData dataWithContentsOfFile:path];



2) Library내의 Caches Directory: 

캐쉬디렉토리를 지정하면된다. 사용법은 동일하니 참고.


1) 쓰기

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString * cachesDirectory = [paths objectAtIndex:0];
    NSString * filePath = [cachesDirectory stringByAppendingPathComponent:@"screenshot.png"];
    UIImage * image = [self screenshot];
    NSData * imageData = UIImagePNGRepresentation(image);
    [imageData writeToFile:filePath atomically:NO];


2) 읽기 

   NSArray * paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
   NSString * documentsDirectory = [paths objectAtIndex:0];
   NSString * path = [documentsDirectory stringByAppendingPathComponent:@"screenshot.png"];
   NSData * imageData = [NSData dataWithContentsOfFile:path];



3) tmp Directory: <Application_Home>/tmp

템프 디렉토리에 사용하는 법은 조금 다르지만 아주 간단하다.


1) 쓰기

    NSString * tempFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"screenshot.png"];
    UIImage * image = [self screenshot];
    NSData * imageData = UIImagePNGRepresentation(image);
    [imageData writeToFile:tempFilePath atomically:NO];


2) 읽기

NSString * tempFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"screenshot.png"];
        NSData * imageData = [NSData dataWithContentsOfFile:tempFilePath];


아무튼 해당 내용을 잘 파악해서 목적에 맞게 저장하고 읽어야지...리젝을 안 당함...-_-;;


아참...파일이 잘 저장되었나 확인하는 방법은 시뮬레이터의 앱 안의 각각의 폴더를 확인하면 된다. 단지 템프 폴더의 경우에는 시뮬레이터 작동 시에는 해당 폴더에 저장이 안된다. 단말로 체크.



+ Recent posts