[BalancingLibra] Application.persistentDataPath, Path.Combine()에 대해
유저데이터를 저장할 때 해당 함수를 처음 이용해보아서 해당 함수의 작동 과정 및 궁금함을 해소한 과정을 기록해 두겠다.
1. Application.persistentDataPath
2. Path.Combine()
3. 후기
1. Application.persistentDataPath
해당 명령어는 OS 관계없이 저장 데이터를 저장할 기기 내 안전한 장소를 찾아준다.
나는 현재 맥북을 쓰고 있지만, 일반 PC나 안드로이드는 경우가 다르다.
PC의 경우, 일단 C드라이브에 저장하려고 하면 관리자 권한이 없다고 한다.
모바일은 앱마다 샌드박스라는, 각 OS만의 격리된 방이 아니면 파일 접근 자체가 금지되기 때문이다.
해당 명령어를 사용하면, 아래와 같이 이 앱이 자유롭게 파일을 다룰 수 있는 안전한 폴더 위치를 받아온다.
mac : ~/Library/Application Support/
window : %userprofile%\AppData\LocalLow\
android : /storage/emulated/0/Android/Data...
그러면 여기서 어떻게 안전한 장소를 찾을까?
그건 유니티의 cpp native가 OS의 API를 직접 호출해서 받아오는 값으로 결정된다. OS가 내어준 샌드박스 경로를 받아오는 것이다.
하지만 각 플랫폼의 구현 형식이 다르므로 유니티는 각 플랫폼별로 다르게 샌드박스를 경로를 받도록 구성되어 있다.
Win32 API : window의 SHGetKnownFolderPath같은 API를 호출해 FOLDERID_LocalAppData를 요청한다.
그러면 %APPDATA$\..\LocalLow\CompanyName\ProductName을 받아오는데, 윈도우 정책상 프로그램이 권한 없이 쓸 수 있는 표준 경로이기 때문이다.
android : 유니티의 cpp 백엔드가 JNI(Java Native Interface)를 통해 Android Java API인 Context.getExternalFilesDir(null)을 호출한다.
그 결과, /storage/emulated/0/Android/data/com.Company.Product/files를 받아오는데, android OS가 앱 삭제시 데이터를 날려주면서 앱 실행중에는 읽기쓰기 권한을 보장하는 경로이기 때문이다.
ios/mac : NSSearchPathForDirectoriesInDomains 함수를 호출해 NSDocumentDirectory 또는 NSLibraryDirectory를 찾는다.
그 결과, /var/mobile/Containers/Data/Application/<GUID>/Documents를 받아오는데, IOS 정책 안에서 유일하게 영구 데이터를 보존가능하도록 허용된 Documents 폴더이기 때문이다.
2. Path.Combine()
경고를 설정할 때, 현재 실행 중인 OS를 감지해서 알아서 맞는 구분 기호를 넣어준다.
window : \
mac/linux/mobile : /
그러면 Path.Combine이 실행될 때마다 OS를 스캔하는걸까? 그러면 자원 낭비이므로 해당 방법은 사용하지 않을 것이다.
.net 런타임(유니티 에디터에서 실행할 때는 Mono(mac/window 차이를 처리), 폰으로 빌드할 때는 IL2CPP)이 초기화될 때 이미 결정된 상수를 가져온다. (mac : /, window : \) 이 상수를 DirectorySeparatorChar로 정의한다.
Path.Combine은 문자열을 합칠 때, 이 DirectorySeparatorChar을 중간에 넣어 코드는 하나지만 실행되는 환경에 따라 문자열이 달라진다.
이후에 Path.Combine("A", "B")를 호출하면 다음 순서로 작동한다.
1) 유효성 검사 : 문자열이 null or 유효하지 않은 문자 포함되었는지 검사.
2) 루트 판별 : B가 절대 경로인지 확인한다. (B가 절대 경로라면 A를 무시하고 B만 반환한다. 이 부분은 추후 정리할 것이다.)
3) 구분자 처리 : A의 마지막 글자가 이미 구분자인지 아닌지 검사. (Yes : A + B, No : A + DirectorySeparatorChar + B) 수행.
위 3)처럼 +으로 문자열을 합치면 중간 단계의 가비지 메모리가 생성된다. 하지만 최신 .net은 Path.Combine은 내부적으로 StringBuilder과 유사한 버퍼 처리로 불필요한 메모리할당을 최소화하도록 최적화되어 있다.
Update()에서 경로 계산을 매번 한다면, +보다 Path.Combine이 GC 부하가 적다.
그러나 두 번째 매개변수가 / or \로 시작하면 앞 경로가 사라진다.
그 이유는 Path.combine은 두 번째 인자가 구분자로 시작하면 그건 루트부터 시작하는 절대 경로라고 판단하기 때문이다.
추가로 AssetDatabase.LoadAssetAtPath같은 유니티 에디터 전용 API는 윈도우에서도 반드시 /만 인식하는 경우가 있으므로 결과를 맹신하면 안된다.
즉, Path.Combine은 런타임이 미리 정해둔 상수를 끼워 넣는 방법이다.
3. 후기
처음에는 어떻게 사용하는지 가볍게 쓰려고 했는데 궁금한 게 많고, 글로 써야 정리가 잘 되어서 글로 많이 써버리게 되었다. 그래도 유니티 내 런타임도 알게 되고, Path.Combine팁도 알게 되어 좋았다. 꼭 프로젝트에서 써먹고 싶다.