iOS 读取存储在Container的文件问题
问题描述
在使用KMM实现iOS平台文件存储时,通过导出container文件可以查看到文件确实存在目录下,但是在调用NSData.dataWithContentsOfFile
时总是返回空,根据错误信息得知找不到该文件或目录No such File or Directory
。其中代码如下:
val errorPtr: ObjCObjectVar<NSError?> = alloc()
NSData.dataWithContentsOfFile(fullPath, options = 0, error = errorPtr.ptr)?.let { bytes ->
val array = ByteArray(bytes.length.toInt())
bytes.getBytes(array.refTo(0).getPointer(this), bytes.length)
return@withContext array
}
println(errorPtr.value?.description.orEmpty())
return@withContext null
errorPtr是为了查看具体错误,所以加上这个为了更好定位问题
问题定位
- 通过上面的error日志中得知,大概率是因为fullPath这个目录是一个错误的目录了,而我又从device中导出了
xxx.xcappdata
,
在这个目录下确实能找到对应名字的文件,而且文件是没有问题的,这个可以确定问题是文件是存在的,但是目录又不一致 - 既然文件存在,但是目录上却找不到该文件,问题出在哪呢?于是想通过代码打印出fullPath和目录下所有文件的路径对比是否一致,上代码:
这一块是Swift的代码,然后获取document Directory的url(因为我存储文件的目录就是这个),然后通过遍历将所有文件url打印并对比let fileManager = FileManager.default let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0] do { let fileURLs = try fileManager.contentsOfDirectory(at: documentsURL, includingPropertiesForKeys: nil) print(fileURLs) // process files } catch { print("Error while enumerating files \(documentsURL.path): \(error.localizedDescription)") }
- fullPath路径是这样的
/var/mobile/Containers/Data/Application/3FC1CCE9-A788-47AB-902A-FA133FAA3D30/Documents/60F570BE-2A16-4EFB-96B3-9203C0A0ABCE.jpg
- 而文件url则是
file:///private/var/mobile/Containers/Data/Application/C5AC5178-6887-40F4-9EE5-8D56CB830CB3/Documents/60F570BE-2A16-4EFB-96B3-9203C0A0ABCE.jpg
果然,Application后面的UUID并不一致,那全路径肯定也是不对的咯
通过查阅资料,得知在iOS8.0
之后每次debug运行或者更新,这个UUID都会改变,而我又为了方便,一开始就存储了全路径,所以每次重新运行代码就会出现找不到这个文件,因为UUID的改变导致了整个目录都变了,自然找不到。
解决问题
既然知道了问题所在,解决问题就简单了
- 遍历目录,通过fileName过滤拿到对应的文件
刚开始,由于对iOS这边的api之类的还不是很了解,解决思路也很直接,既然Swift上能拿到目录下所有文件的url了,在kotlin native层肯定有对应的API,于是开搞,上代码:
再次运行,成功拿到bytes(✌️)// 拿到目录的url val url = fileManager.URLForDirectory( directory = NSDocumentDirectory, inDomain = NSUserDomainMask, appropriateForURL = null, create = false, error = null )!! // 拿到目录下所有文件的url val fileUrls = fileManager.contentsOfDirectoryAtURL(url = url, includingPropertiesForKeys = null, options = 0, error = errorPtr.ptr) // 通过全路径拿到文件名,然后通过文件名过滤得到唯一的文件url val currentFileUrl = fileUrls?.first { it.toString().contains(fileName) } as? NSURL // 读取到NSData中 currentFileUrl?.let { NSData.dataWithContentsOfURL(currentFileUrl) ?.let { bytes -> val array = ByteArray(bytes.length.toInt()) bytes.getBytes(array.refTo(0).getPointer(this), bytes.length) return@withContext array } }
但是这样存在一个问题,随着时间,这个目录的文件肯定会越来越多,到时候遍历肯定是一个耗时任务。那有没有可以直接通过filename拿到对应文件的url呢?
看了下代码,既然能拿到document目录的url,那是不是也有API可以直接拿到文件的url?一开始想通过url + filename组装一下
他们类型不一致,相对麻烦,还好找到了,代码:
路径正确,直接调用val documentDirectory = NSSearchPathForDirectoriesInDomains( directory = NSDocumentDirectory, domainMask = NSUserDomainMask, expandTilde = true ).first() as NSString val fullPath = documentDirectory.stringByAppendingPathComponent(fileName) println(fullPath)
NSData.dataWithContentsOfFile
传入文件路径即可
总结
在 iOS8.0 之后,由于Application(Bundle identifier)的UUID会随机改变,所以不能直接存全路径,可以直接存名字
而可以通过NSSearchPathForDirectoriesInDomains
拿到对应的路径,再加上文件名得到当前文件准确的全路径,这时候读取即可。