Failed to read the bundle file in iOS10

This problem is a bit strange. I read a JS file from Bunlde and perform calculations. The following is part of the code, there is no problem running on iOS12 and above. But when running on iOS 10.3.1, it prompts that the file couldn't be opened because there is no such file.
But I'm very sure the file is there, in the Bundle resource. And it can be used above iOS12. Before I thought it may be a problem with Chinese characters in the file path, so I used Bundle.main.url instead of Bundle.main.path to read the file. But the problem remains, can anyone give some hints? Thanks!

Code Block if let lunarPath = Bundle.main.url (forResource: "lunar", withExtension: "js", subdirectory: "天文算法") {
do {
let lunarContents = try String (contentsOf: lunarPath)
jsContext.evaluateScript(lunarContents)
} catch let error as NSError {
self.有效性 = false
self.問題文 = "初始化農曆算法異常,\(error.localizedDescription)"
}
}


Now that the if statement is entered, it means what the Bundle.main.url returned is not a nil value, so this file should be found. But in the try String function it returns that the file is not found. 
My general advice is that you not use non-ASCII characters for the names of files within your bundle. It should work but there are some gnarly edge cases. Admittedly these generally affect script systems, like Latin and Korean, where normalisation comes into play, and thus won’t affect Chinese.

The other thing to watch out for is case sensitivity. You have to make sure that the case of the string in your source code matches the case of the file. Again, this is more a Latin thing that a Chinese thing.

As to how you should debug this, I have three suggestions.



You wrote:

Before I thought it may be a problem with Chinese characters in the
file path, so I used Bundle.main.url instead of Bundle.main.path
to read the file.

To start, you should favour the URL APIs over the path APIs. This is because file URLs can carry extra payload, something that’s useful in various circumstances (for example, accessing files outside of your sandbox).

Also, you shouldn’t build your URL from Bundle.main.url. Rather, you should call url(forResource:withExtension:) or one of its friends.



The String(contentsOf:) API is tricky because of you don’t supply a text encoding and the default text encoding isn’t well specified. You should use one of the variants that allows you to supply a text encoding.

Also, just for debugging, try replacing this with Data(contentsOf:). That’ll confirm that the problem is with reading the file rather than the text encodings.



If none of the above helps, here’s what I recommend:
  1. Dump the UTF-8 representation of the path you’re using.

  2. Iterate the directory (using, say, contentsOfDirectory(at:includingPropertiesForKeys:options:)) and, for each item you get back, dump the UTF-8 representation of its path.

Step 2 will either return your file or not. If it doesn’t, there’s a problem with the way that the app was is structured. If it does, and Data(contentsOf:) continues to fail, you can compare the UTF-8 encoding of the paths to look for discrepancies.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Very detailed and instructive answer.

Regarding the first suggestion, I am sorry that I missed the if statement in the code block. The complete code is as follows.
Code Block
if let lunarPath = Bundle.main.url (forResource: "lunar", withExtension: "js", subdirectory: "天文算法") {
do {
let lunarContents = try String (contentsOf: lunarPath)
jsContext.evaluateScript(lunarContents)
} catch let error as NSError {
self.有效性 = false
self.問題文 = "初始化農曆算法異常,\(error.localizedDescription)"
}
}

So as you mentioned that I shouldn't construct URL from Bundle.main.url, I am not very clear about it.

Second, I did not supply a specific encoding for the String(contentsOf:). But in other parts of my project, I used the Data(contentsOf:) to read the file and mapped it to the Codable object. I found that the Data(contentsOf:) still reports that the file cannot be found. So I think this should have nothing to do with encoding.

Third, since the affirmative module is entered in the if judgment, then a valid URL should have been constructed. If it returns nil, it will not enter the catch block at all. That is to say, a valid URL is constructed through Bundle.main.url, but when String(contentsOf:) or Data(contentsOf:) tries to open, it reports an error that there is no file. "The file XXX couldn't be opened because there is no such file". This logic seems a little inappropriate, String(contentsOf:) or Data(contentsOf:) should handle some file format errors. Whether the file exists or not should have been processed during the URL creation stage. In fact, in iOS12 and above, it can be opened and read normally.

In addition, I can be sure that the case of the string is correct.

So as you mentioned that I shouldn't construct URL from
Bundle.main.url, I am not very clear about it.

Ah, confusion! I interpreted Bundle.main.url literally and thus assumed you were constructing a URL based of the bundle’s base URL (I forgot that this property is actually called Bundle.main.bundleURL). You can avoid this confusion in future by referring to methods via their full signature, for example, url(forResource:withExtension:).

I found that the Data(contentsOf:) still reports that the file
cannot be found.

OK, that’s an important point.

Third, since the affirmative module is entered in the if judgment,
then a valid URL should have been constructed.

Agreed. I didn’t realise you were constructing lunarPath via url(forResource:withExtension:).

Honestly, I’m struggling to think of what else might be causing this. To test my normalisation theory I ran the Chinese component of your URL through this code:

Code Block
func p(_ s: String) {
debugPrint(Data(s.utf8) as NSData)
}
let s = "天文算法" as NSString
p(s.precomposedStringWithCanonicalMapping)
p(s.precomposedStringWithCompatibilityMapping)
p(s.decomposedStringWithCanonicalMapping)
p(s.decomposedStringWithCompatibilityMapping)


This proved that normalisation isn’t an issue in that all three strings printed:

Code Block
<e5a4a9e6 9687e7ae 97e6b395>


I then put this code into a small test project:

Code Block
let lunarPath = Bundle.main.url (forResource: "lunar", withExtension: "js", subdirectory: "天文算法")!
let d = try! Data(contentsOf: lunarPath)
print(d.count)


along with a bundled text file called 天文算法/lunar.js. This code works on 14.4.2 but fails on my 10.3.3 test device.

Weird.

I then added this to the code to see what the path looks like:

Code Block
print(lunarPath.absoluteURL)


It printed this:

Code Block
file:///var/containers/Bundle/Application/EDF12AD5-8E5A-49D1-A6ED-00002718A1A4/Test678374.app/lunar.js


which is obviously broken because the 天文算法 path component is completely missing.

If I rename the 天文算法 directory to something ASCII (like tianwen-suanfa), and change the code to match, it works. So this problem is tied to the directory name, and probably to the characters in that name.

IMPORTANT I striped the diacriticals from the pinyin because they are subject to the normalisation issues I mentioned earlier.



So, what’s your next step? If I were in your shoes I’d simply rename the 天文算法 directory to something ASCII and then move on with your life. To quote my first post:

My general advice is that you not use non-ASCII characters for the
names of files within your bundle. It should work but there are some
gnarly edge cases.

Clearly you’ve found another one of these gnarly edge cases.

Alternatively, if you’d like me to dig deeper into this, you could open a DTS tech support incident. That’ll allow me to spend more time researching this. However, the result of this research might be that a) this is a Just a Bug™, and b) the best way to work around it is to rename your 天文算法 directory to something ASCII, which is why I’m recommending that up front.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Failed to read the bundle file in iOS10
 
 
Q