[iPhone] Files and Networking

iPhone OS에서 실행되는 어플리케이션은 Core OS와 Core Services 프레임워크를 분류하여 로컬 파일시스템과 네트워크에 접근할 수 있습니다.

또한 사용자의 데이터를 저장하거나 다음 사용을 위한 어플리케이션의 상태를 저장하기 위해 파일을 읽고 쓰는 것이 가능합니다.

네트워크에 접근함으로써 네트워크상의 서버와 통신하거나 데이터를 주고 받는 등의 원격 작업을 수행할 수 있는 기능을 갖출 수 있습니다.

iPhone OS의 파일은 사용자들의 미디어파일과 개인정보와 같은 파일들과 함께 Flash 메모리를 공유하여 사용하고 있습니다.

보안상의 이유로 개발자가 만든 어플리케이션은 자신이 위치한 자신의 디렉토리에 국한하여 제한적인 데이터 읽기, 쓰기만을 할 수 있습니다.

Commonly Used Directories

보안을 위해 어플리케이션은 몇몇에 불과한 디렉토리에 접근하여 데이터와 환경 설정을 읽고 쓸수 있습니다.

어플리케이션이 장치에 설치될때 어플리케이션을 위한 홈 디렉토리가 생성됩니다.

다음은 그 내부의 중요한 서브디렉토리의 설명입니다.

[code]<Application_Home>/AppName.app[/code]
이 디렉토리는 어플리케이션 자신을 포함하는 Bundle 디렉토리입니다.
실행중에 이 디렉토리 내부의 파일을 변경할 수 없습니다.
iPhone OS 2.1 혹은 그 이후의 버젼은 iTunes에서 이 디렉토리를 백업하지 않습니다.
그러나 어플리케이션을 구매했을 당시의 초기 싱크 과정에서는 컨텐츠 내용이 복사됩니다.

[code]<Application_Home>/Documents/[/code]
이 디렉토리는 어플리케이션이 특정 데이터를 저장하기 위해 사용되는 공간입니다.
사용자의 데이터나 다른 정보를 정기적으로 저장할 수 있습니다.
이 디렉토리 내부의 컨텐츠는 iTunes에 의해서 백업 됩니다.

[code]<Application_Home>/Library/Preferences[/code]
이 디렉토리는 어플리케이션의 특정 설정 파일을 포함합니다.
하지만 환경 설정 파일을 직접 생성하여서는 안되고 NSUserDefaults 클래스나 CFPreferences API를 사용해야 합니다.
이 디렉토리 내부의 컨텐츠는 iTunes에 의해서 백업 됩니다.

[code]<Application_Home>/Library/Caches[/code]
이 디렉토리는 어플리케이션의 실행때마다 지속적으로 사용해야 하는 파일을 읽고 쓰기 위해 사용합니다.
개발한 어플리케이션은 일반적으로 이곳에 파일을 추가하고 삭제하여야 합니다.
iTunes는 전체 복원시에 이 디렉토리의 내용을 모두 제거 합니다.
그러므로 어플리케이션은 필요할때마다 이곳의 파일을 생성해 낼 수 있어야 합니다.
iPhone OS 2.2 혹은 그 이후의 버젼은 iTunes에서 이 디렉토리를 백업하지 않습니다.

[code]<Application_Home>/tmp/[/code]
이 디렉토리는 어플리케이션이 실행때마다 지속될 필요가 없는 임시적인 파일을 읽고 쓰기 위해 사용합니다.
어플리케이션이 실행중이 아닐때 시스템은 파일이 더 필요한가를 판단하여 더이상 필요하지 않다고 판단이 되면 삭제합니다.
iPhone OS 2.1 혹은 그 이후의 버젼에서는 iTunes에서 이 디렉토리를 백업하지 않습니다.

Backup and Restore

어플리케이션이 백업과 복원을 위해 따로 준비해야 하는 것은 없습니다.

iPhone OS 2.2 혹은 그 이후의 버젼은 장치가 컴퓨터에 연결되어 싱크 될때 다음을 제외한 나머지 모든 파일을 증분 백업 합니다.
[code]<Application_Home>/AppName.app
<Application_Home>/Library/Caches
<Application_Home>/tmp[/code]
어플리케이션이 매우 큰 파일을 생성하거나 매우 빈번하게 파일 엑세스를 한다면 /Documents가 아닌 /Library/Caches를 이용하는 것이 좋습니다. /Documents에 저장하게 되면 백업/복원 작업시에 시간이 더 걸리게 되는 문제점이 될 수도 있습니다.

Getting Path to Application Directories

다양한 레벨의 시스템에서 어플리케이션의 샌드박스의 위치를 알아내기 위한 다양한 프로그래밍 방식이 있어왔습니다.

하지만 Cocoa는 이러한 경로를 탐색하기 위해 다양한 프로그래밍 인터페이스를 제공합니다.

NSHomeDirectory(Foundation 프레임워크에 포함) 함수는 홈디렉토리의 경로뿐만 아니라 Documents, Library 그리고 tmp같은 디렉토리 경로를 손쉽게 얻어올수 있습니다.

또한 추가적으로 NSSearchPathForDirectoriesInDomains와 NSTemporaryDirectory함수를 사용하여 정확한 Document, Caches, tmp 디렉토리 경로를 얻어올 수 있습니다.

이 NSSearchPathForDirectoriesInDomains 함수를 사용하여 어플리케이션과 연관된 전체 경로를 알 수 있습니다.

iPhone OS에서 이 기능을 사용할려면 적절한 검색 경로를 첫번째 매개 변수로, NSUserDomainMask를 두번째 매개 변수로 지정합니다.

일반적으로 자주 사용되는 경로 상수는 다음과 같은 것들이 있습니다.

NSDocumentDirectory
[code]<Application_Home>/Documents[/code]
NSCachesDirectory
[code]<Application_Home>/Library/Caches[/code]
NSApplicationSupportDirectory
[code]<Application_Home>/Library/Application Support[/code]

어플리케이션의 Documents/ 디렉토리를 찾기 위해 다음과 같이 사용합니다.
[code]NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];[/code]
위의 결과값을 찍어보면 /var/mobile/Applications/해쉬코드/Documents 와 같은 값이 나옵니다. 해당 경로가 어플리케이션 Documents 디렉토리의 절대 경로가 되겠죠.

두번째 인자인 NSUserDomainMask말고 다른것을 사용할 수 있습니다.

NSSystemDomainMask와 첫번째 인자로 NSApplicationDirectory로 검색하여 보면 /Applications 라는 시스템 경로를 얻을 수 있습니다.

Reading and Writing File Data

iPhone OS는 파일을 읽고 쓰고 관리할 수 있도록 몇가지 방법을 제공하고 있습니다.

Foundation Framework :


  • 어플리케이션이 Property List를 사용한다면 NSPropertyListSerialization API를 사용하여 NSData로 변환이 가능합니다.  이렇게 변환 후에 NSData의 메서드를 이용하여 데이터를 쓸 수 있습니다.
  • 어플리케이션의 Model객체가 NSCoding 프로토콜을 채택하였다면 NSKeyedArchiver 클래스를 이용하여 객체를 저장할 수 있습니다.
  • Foundation 프레임워크는 컨텐츠 파일의 랜덤 엑세스를 위한 NSFileHandle 클래스를 제공합니다.
  • Foundation 프레임워크안의 NSFileManager는 시스템에 있는 파일을 생성하고 조작하는 메서드를 제공합니다.

Core OS calls :



  • fopen, fread, fwrite 를 호출하여 데이터를 엑덤 엑세스를 통해 읽고 쓸 수 있습니다.
  • mmap과 munmap을 호출하여 효과적으로 큰 파일을 메모리로 로드하거나 그안의 컨텐츠에 접근할 수 있습니다.

Reading and Writing Property List Data

Property List는 캡슐화 되어있는 데이터를 말합니다. 배열, 문자열, 날짜, 이진데이터, 숫자 및 Boolean 값을 포함합니다.

다른 예로 모든 Cocoa/iPhone 어플리케이션의 설정이 들어있는 Info.plist 파일을 들 수 있습니다.

코드상에서는 NSDictionary, NSArray, NSString, NSDate, NSData, NSNumber를 사용할 수 있습니다.

다음은 Property List를 NSData로 변환하여 저장하는 방법입니다.
[code]- (BOOL)writeApplicationPlist:(id)plist toFile:(NSString *)fileName {
    NSString *error;
    NSData *pData = [NSPropertyListSerialization dataFromPropertyList:plist
                              format:NSPropertyListBinaryFormat_v1_0 errorDescription:&error];
    if (!pData) {
        NSLog(@”%@”, error);
        return NO;
    }
    return ([self writeApplicationData:pData toFile:(NSString *)fileName]);
}
– (BOOL)writeApplicationData:(NSData *)data toFile:(NSString *)fileName {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    if (!documentsDirectory) {
        NSLog(@”Documents directory not found!”);
        return NO;
    }
    NSString *appFile = [documentsDirectorystringByAppendingPathComponent:fileName];
    return ([data writeToFile:appFile atomically:YES]);
}[/code]
그러면 읽어와야 겠죠. 다음의 코드를 참고하세요.
[code]- (id)applicationPlistFromFile:(NSString *)fileName {
    NSData *retData;
    NSString *error;
    id retPlist;
    NSPropertyListFormat format;
    retData = [self applicationDataFromFile:fileName];
    if (!retData) {
        NSLog(@”Data file not returned.”);
        return nil;
    }
    retPlist = [NSPropertyListSerialization propertyListFromData:retData
                  mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&error];
    if (!retPlist){
        NSLog(@”Plist not returned, error: %@”, error);
    }
    return retPlist;
}
– (NSData *)applicationDataFromFile:(NSString *)fileName {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *appFile = [documentsDirectory stringByAppendingPathComponent:fileName];
    NSData *myData = [[[NSData alloc] initWithContentsOfFile:appFile] autorelease];
    return myData;
}[/code]
id는 객체 자신을 가리키는 타입입니다. 무엇이든 가리킬 수 있는 자바의 Object라고 생각하시면 이해가 쉬울까요.

Using Archivers to Read and Write Data

실제로 프로그램이 실행할때 메모리에 수많은 객체를 생성합니다. 그리고 각각의 객체는 서로 연결되어있습니다.

이러한 객체를 파일로 저장후에 다시 읽었을때 연결된 다른 객체가 존재하지 않는다면 큰 문제가 되겠죠.

아카이브라 불리는 이 과정은 실행중에 생성된 객체들의 복잡한 상속관계를 나타내는 그래프를 모두 바이트 단위로 변환하는 과정입니다.

이것을 사용하기 위해서는 클래스가 NSCoding을 구현해야 하고 그에 따른 encodeWithCode와 initWithCoder 메서드를 구현해야 합니다.

자세한 내용은 관련 [문서]를 참고하세요. 데이터를 저장하기 위해서는 다음과 같은 방식으로 합니다.
[code]NSData *data = [NSKeyedArchiver archivedDataWithRootObject:[_myDataSource representation]];
[data writeToFile:myFilePath atomically:YES];[/code]
데이터를 읽어 들여 복원할때는 다음과 같이 하면 됩니다.
[code]NSData* data = [NSData dataWithContentsOfFile:myFilePath];
id rootObject = [NSKeyedUnarchiver unarchiveObjectWithData:data];[/code]
File Acess Guidelines

파일을 생성하고 데이터를 쓸때 다음의 가이드 라인을 지키도록 합시다.

파일을 쓰는 횟수를 최소화 해야 합니다. 파일을 엑세스 하는 작업은 느리고 제한적인 수명을 가진 플래시 메모리에 하는 행위입니다.



  • 파일의 일부분이 변경되었을때 바뀐 부분만을 쓸 수 있습니다. 적은 바이트의 변경을 위해 전체 파일을 쓰는 행위를 피하도록 합시다.
  • 파일 포맷을 정의할때, 자주쓰이는 데이터를 최대한 하나의 그룹으로 묶어 저장하면 디스크 엑세스 횟수를 최소화 할 수 있습니다.
  • 랜덤한 엑세스가 필요한 데이터 구조로 되어있는 경우 SQLite 데이터베이스에 데이터를 저장할 수 있습니다.

캐쉬 파일을 디스크에 쓰는것을 피하도록 합니다. 어플리케이션이 종료될때 다음 실행시에 동일한 상태로 시작하기 위해 저장하는 것은 예외로 합니다.

Saving State Information

사용자가 Home 버튼을 누르게 되면 iPhone OS는 당신의 어플리케이션을 종료하고 홈스크린 화면으로 돌아갑니다.

마찬가지로 URL을 여는 경우 어플리케이션이 종료하고 다른 어플리케이션의 URI가 열리게 됩니다.

이 과정은 멀티테스트 환경과 달리 현재 실행되는 어플리케이션이 종료되고 다른 어플리케이션이 수행되는 것을 뜻합니다.

이러한 작업이 자주 일어난다면 어플리케이션의 상태를 관리하는 방법을 변경할 필요가 있습니다.

사용자가 수동으로 디스크에 저장하는 작업과 달리 주요 지점에서 자동으로 저장하도록 프로그램이 변경되어야 합니다.

어플리케이션이 종료될 때 임시 캐시 파일이나 데이터베이스 환경을 이용하여 상태를 저장해야 합니다.

다음에 어플리케이션이 실행될때 저장되었던 정보를 가지고 이전 상태로 복원하는 과정을 수행하여야 합니다.

저장하는 횟수를 최소화 하여야 하지만 최대한 적절한 시점에 저장하여 이 상태를 그대로 복원하여 보여주어야 합니다.

예를 들어 연락처를 수정하고 있다가 어플리케이션을 종료하고 다시 실행했을 때 연락처의 가장 첫화면보다 수정하던 창을 그대로 보여주는 것이 좋습니다.

Networking

iPhone OS의 네트워킹 스택은 아이폰과 아이팟 터치에 있는 무선 하드웨어 장치의 여러 인터페이스를 포함합니다.

주요 프로그래밍 인터페이스로는 BSD소켓으로 만들어진 CFNetwork 프레임워크가 있습니다.

이 문서에서는 구체적인 조언만을 담고 있고 NSStream 클래스를 사용하는 방법의 정보를 원하시면 Foundation Framework 문서를 참고하시기 바랍니다.

Tip for Efficient Networking
네트워크를 통해 데이터를 주고받는 행위는 장치의 많은 전력을 소비하는 행위라는것을 기억하셔야 합니다.
데이터를 주고받는 시간을 최소화 하는 것이 배터리 수명에 도움이 됩니다.



  • 통신 프로토콜을 정의할때 데이터 포맷은 가능한한 작게 만들어야 합니다.
  • 채팅과 같은 무수히 많은 통신이 오가는 방식은 피하도록 합니다.
  • 데이터는 하나의 덩어리로 묶어 보낼 수 있습니다.

Cellular와 Wi-Fi를 이용한 통신은 아무런 행동이 없을때 파워가 꺼지도록 만들어져 있습니다. 하지만 계속해서 작은 용량의 패킷을 주고 받게 되면 이것은 지속적으로 파워가 켜져있게 되는 것을 의미하게 되며 배터리가 소모된다는 것을 뜻합니다.

그러므로 작은 데이터를 자주 주고 받는것보다는 큰 데이터를 긴 시간차를 두고 주고 받는 것이 좋습니다.

네트워크를 이용한 통신 프로그램을 작성할때는 패킷의 손실이 일어날 수 있다는 것을 기억해 두어야 합니다.

코드를 작성할때 통신 실패에 대한 핸들링을 충분히 하여야 합니다.

그 예로 네트워크가 갑자기 사라지게 될 경우 사용자에게 알리는 시스템을 갖추는 등의 노력이 필요합니다.