xxfast / KStore

A tiny Kotlin multiplatform library that assists in saving and restoring objects to and from disk using kotlinx.coroutines, kotlinx.serialisation and kotlinx.io
https://xxfast.github.io/KStore/
Apache License 2.0
484 stars 15 forks source link

iOS crashes when storing (encode step) #44

Closed LiamDeGrey closed 9 months ago

LiamDeGrey commented 1 year ago

image

Dependencies image

Device details iPhone 14 Pro - 16.4.1 (a)

There is no issue when running on an iPhone 14 Pro simulator running 16.4

xxfast commented 1 year ago

Hi. Thanks for the issue. I think I also ran into this here #21, and related to an issue with okio

Can you share how the store was created? Is there a reproducer somewhere?

LiamDeGrey commented 1 year ago

Initially I thought this was a different issue to #21, but I think you're right - both crashes are getting caught up at okio.PosixFileSystem#sink

I'm happy for this discussion to be transferred to #21.

In reference to this potential Okio solution:

May be related to data protection caused by Passcode set on the Device's Settings. If the fopen is called when the device was locked and passcode is needed, "Operation not permitted" will be returned.

This was not the case for me - I can consistently reproduce the issue using my physical iPhone 14 Pro device while unlocked and in-use.

Related code:

Screenshot 2023-05-10 at 9 41 27 AM

FilePath for an iOS target is: "${NSHomeDirectory()}/$fileName.json"

xxfast commented 1 year ago

Thanks for the info. I think i'll keep this issue open and close my original one as there's more information here.

As for a fix, I'll follow up with okio - looks like there's a callback in AppDelegate that announces when applicationProtectedDataDidBecomeAvailable, whatever that means. With this, the store can handle this safely hopefully

ayodelekehinde commented 1 year ago

Is there a work around?

zacharee commented 1 year ago

The workaround I've found is to manually create the file with the correct empty JSON contents if it doesn't already exist.

Something like:

actual fun pathTo(subPath: String, startingTag: String): String {
    val fileManager = NSFileManager.defaultManager
    val directoryUrl = NSURL.fileURLWithPath(NSHomeDirectory()) // or NSURL.fileURLWithPath(NSSearchPathForDirectoriesInDomains(NS<Whatever>Directory, NSUserDomainMask, true).firstOrNull().toString())
    val fileUrl = directoryUrl.URLByAppendingPathComponent(subPath)!!
    val filePath = fileUrl.path!!

    if (!fileManager.fileExistsAtPath(filePath)) {
        fileManager.createFileAtPath(
            filePath,
            (startingTag as NSString).dataUsingEncoding(NSUTF8StringEncoding),
            null
        )
    }

    return filePath
}

If your store item is a single object, pass startingTag = "{}". If it's a list, pass startingTag = [].

xxfast commented 1 year ago

Hmm, this did not seem to work for me over here. @zacharee do you know which version of kstore you used?

zacharee commented 1 year ago

0.6.0

linde9821 commented 10 months ago

Hey I guess I ran into the same problem and wanted to let you know that @zacharee s workaround is not working for me either. Does anyone have any other suggestions?

linde9821 commented 10 months ago

I tried to find a solution and asked in the ios Channel of the Kotlin Slack Community. Konstantin Tskhovrebov suggested to use the NSCachesDirectory as the filePath which can be obtained like this:

private fun getCacheDirectoryPath(): Path {
    val cacheDir = NSSearchPathForDirectoriesInDomains(
        NSCachesDirectory,
        NSUserDomainMask,
        true
    ).first() as String
    return "$cacheDir/cache".toPath()
}

For me it worked this way. (Disclaimer: I switched to using okio directly, so I dont know if it works for KStore as well, but I guess because KStore uses okio it should)

xxfast commented 9 months ago

Hi @linde9821 Thanks for your findings! I'll add some convenience extension methods to address NSDocumentsDirectory via NSFileManager

xxfast commented 9 months ago

Fixed in 0.7.1!

[!WARNING] Avoid creating files at NSHomeDirectory! Though it works on the simulator, is not suitable for physical devices as the security policies on physical devices do not permit read/write to this directory

Use either

// for documents directory
storageDir = NSFileManager.defaultManager.DocumentDirectory?.relativePath

// or caches directory
storageDir = NSFileManager.defaultManager.CachesDirectory?.relativePath

Updated docs: https://xxfast.github.io/KStore/using-platform-paths.html#on-ios-other-apple-platforms