Too Many Files Open while monitoring file changes - ios

I am developing a document-browser-based app for the iPad. I have been using SKQueue to monitor changes to the files in order to make sure their metadata remains current when the user performs actions in the document browser. The code for initiating monitoring:
// Set up the queue
let delegate = self
queue = SKQueue(delegate: delegate)!
// Remove all existing paths
queue?.removeAllPaths()
// Get the list of PDF URLs using a function that enumerates a folder's contents
let pdfFiles = getFolderContents(rootFolder: myDocumentsFolder, extensionWanted: "pdf")
for pdfFilePath in pdfFiles.filePaths {
queue?.addPath(pdfFilePath.path)
}
for pdfFolderPath in pdfFiles.folderPaths {
queue?.addPath(pdfFolderPath.path)
}
I developed my own logic to respond to notifications from this queue, but I do not remove any items from the queue during the app's runtime.
The problem - it seems that when the number of items watched is over 200 (files and folders) the system hits a wall and the console reports error 24: Too Many Files Open. After that, no reading/writing of any file can be performed.
From what I was able to gather from searching, it seems that iOS and iPadOS do not allow more than 256 files descriptors to be accessed at the same time, and that would mean that the GCD approach to monitoring file changes would suffer from the same limitation.
Is there any way to monitor files changes that is not subject to such a limitation? Any other suggestions?

After a lot of research and experimentation, I can finally verify that indeed, the default maximum number allowed of open file descriptors is 256 - for MacOS, iOS and iPadOS. This can be changed easily in MacOS - see article here. However, iOS and iPadOS being much more closed in nature, there is no reliable way of changing this limit on these platforms.
Good practices are therefore:
Try to avoid designing an app that would call for so many file descriptors open.
Monitor directories, and not individual files. With most tools available for monitoring the file system, you get notifications for any file change within a monitored directory. Just implement your logic from there, by enumerating the folder and comparing its new state with a saved state. You can find good code for that in the top two answers of this SO thread.
Note: I recommend enumeration rather than other methods of getting the file system state, because other methods tend to give incompatible results between the simulator and actual devices (different treatment of symlink resolution).
Make sure your method of choice for monitoring the file system can be queried for the number of items being watched, and issue an alert if the user approaches a set limit. Remember that the 256 open files must also include all files used by the app, including ones in the app's bundle and ones that are in actual use. So take a nice safety margin.
In my case, my app uses a UIDocumentBrowserViewController, or in other words - Apple's own Files app, to allow users to manage their files. I have to keep my metadata up-to-date with the file system state, and I have no control over the file management habits of my users. To complicate matters, the Files app itself can be used to modify the app's file system - while my app is not active.
Therefore, I do two things:
I save a detailed state of the file system from the App Delegate's applicationDidEnterBackground and applicationWillTerminate methods into a json file in Application Support, and on the app's launch I compare it to a fresh enumeration of the file system - and alert the user for any mismatch, suggesting to use the app's own file browser next time.
I created my own swift package, named SFSMonitor, for monitoring the file system. It is based on the wonderfully convenient SKQueue (which I highly recommend), but instead of monitoring kevent, it uses Dispatch Sources - a more modern approach, which Apple advocates. It is similar to Apple's own Directory Monitor (reference to which you can find here), but instead of monitoring one directory, it allows you to create and manage a whole queue of them. This class allows you to set a maximum number of monitored file descriptors, and be notified when that limit is reached.

Related

Programmatically reading iOS app .crash file?

As per my understanding, when an iOS app crashes, the system creates a .crash file that can be accessed through Xcode. Is it possible to read this file programmatically from within the app itself?
My intention is to send this crash log (if exists) to a server so that we can analyse the log without user intervention. There are several crash reporting services (e.g. bugsense, crashlytics) that are able to do this, so I assume a custom implementation should be possible?
It is not possible to read an iOS system generated crash report from within your app. The reason is that your app runs in a sandbox and has no access to anything outside its sandbox. So if you try to read a file or directory from a path that is not within your sandbox, you won't get any data.
The only option to get crash reports is adding a library into your code that is able to detect crashes, create reports and send them somehow. Most send it to a server since getting thousands of crash reports via email is not very efficient, also considering the required symbolication process to get the class, method and line number information.
A popular and safe to use open source library is PLCrashReporter. Most services use this one, a few write their own functionality to achieve the functionality. There is an open source app available to compare different crash reporting services and libraries named CrashProbe (Disclaimer: I am one of its developers). The project has a website at http://crashprobe.com with a list of services already compared.
It is also important to note, that having a specific crash reporting library is not enough for good crash reports, since the symbolication process needs to be able to translate information in the crash report to point you to the correct class name, method name, file name and line number for each frame in the stack trace.
I'd recommend to compare services that seem to fit your needs (also using CrashProbe) and then decide which one is best for your use case.
I won't recommend any services, since this is not allowed according to the stackoverflow guidelines.

Has Apple fixed the CoreData + iCloud sync issues? [closed]

There has been a lot of discussion lately about the issues with iCloud and Core Data and how Apple's APIs are currently broken in iOS 5 and possibly iOS 6.
Is it possible, given the current state of Apple's Core Data API, to reliably sync across multiple devices using iCloud?
If so, how would you do this? If not, please recommend an alternative approach.
This blog post will lead you to a chain of recent articles about the travails of developers attempting this approach.
From my own understanding and experience, I believe it is doable, but don't buy into the idea that you will get anything "for free". Depending on your data model, you may be better off syncing your whole persistent store as a document rather than using the documented core data / iCloud approach.
You may have better luck if you're already comfortable with Core Data. Just be sure you think through how to handle several important cases.
One is what to do if the user signs out of their iCloud account. When this happens, the local ubiquitous persistent store is deleted. If it makes sense for the user to still have access to their data, you'll need to manage a copy in local storage, and then manage resynchronizing when they sign back in.
Another is that changes can apparently be quite slow to propagate by default, so you may want to consider an alternative mechanism, such as the key value store, to quickly propagate sufficient information to avoid a bad user experience.
Conflict management is perhaps the most challenging (depending on your model). While the framework provides a mechanism to inform you of conflicts, you are on your own for providing a mechanism to resolve them, and there are reports that the conflict notifications may not be reliable (see linked articles), which seems strongly linked to the lag in updating.
In short, if you go into this understanding that the actual support is pretty bare bones and that you'll need to code very defensively, you may have a chance. There aren't any good recipes out there, so if you do make it work, please come back and tell us what works!
It depends on what you want to do. There are two types of Core Data-iCloud integration, as described here: http://developer.apple.com/library/ios/#releasenotes/DataManagement/RN-iCloudCoreData/_index.html
There are broadly speaking two types of Core Data-based application that integrate with iCloud:
Library-style applications, where the application usually has a single persistent store, and data from the store is used throughout the application.
Examples of this style of application are Music and Photos.
Document-based applications, where different documents may be opened at different times during the lifetime of the application.
Examples of this style of application are Keynote and Numbers.
If you're using the library-type, this article is the first of a series that goes into a lot of the problems that will come up: http://mentalfaculty.tumblr.com/post/23163747823/under-the-sheets-with-icloud-and-core-data-the-basics.
You can also check out sessions 218 (for document-based) or 227 (for library-style) of this year's wwdc.
As of iOS 7, the best solution is probably the Ensembles framework: https://github.com/drewmccormack/ensembles
Additionally, there is a promising project which will essentially allow you to do the same thing using a different cloud service.
Here is a link to the repository: https://github.com/nothirst/TICoreDataSync
Project description:
TICoreDataSync is a collection of classes to enable synchronization via the Cloud (including Dropbox) of Core Data-based applications (including document-based apps) between any number of clients running under Mac OS X or iOS. It's designed to be easy to extend if you need to synchronize via an option that isn't already supported.
Reasons for why iCloud is not currently reliable:
"Sometimes, iCloud simply fails to move data from one computer to another."
"Corrupted baselines are [a] common obstacle.... There is no recovery from a corrupted baseline, short of digging in to the innards of your local iCloud storage and scraping everything out, and there is no visible indication that corruption has occurred — syncing simply stops."
"Sometimes, when initializing the iCloud application subsystem, it will simply return an opaque internal error. When it fails, there’s no option to recover — all you can do is try again (and again…) until it finally works."
"[W]hen you turn off the “Documents & Data” syncing option in the iCloud system preferences, the iCloud system deletes all of your locally stored iCloud data[.]"
When you sign out of iCloud, the system moves your iCloud data to a location outside of your application’s sandbox container, and the application can no longer use it.
"In some circumstances (and we haven’t been able to figure out which, yet), iCloud actually changes the object class of an item when synchronizing it. Loosely described, the object class determines the type of the object in the database[.]"
"In some cases (again, not all the time), iCloud may do one of the following:
Owner relationships in an item’s data will point to the wrong owner;
Owner items get lost in synchronization and never appear on computers other than the one on which they were created. (This leads to the item never appearing in the UI on any other machine.) When this happens, bogus relationships get created between blob items and an arbitrary unrelated owner."
"Sometimes (without any apparent consistency or repeatability), the associated data for an object (for example, the PDF data for a PDF item, or the web archive data for a Web Archive item) would simply fail to show up on the destination machine. Sometimes it would arrive later (much later — minutes or hours)."
Quoted and paraphrased from these sources:
http://www.imore.com/debug-12-icloud-core-data-sync
http://rms2.tumblr.com/post/46505165521/the-gathering-storm-our-travails-with-icloud-sync
Note: I have seen one article where the author mentions getting it to work for iOS 6+, but they don't provide any examples: http://zaal.tumblr.com/post/46718877130/why-you-want-to-use-core-data-icloud-sync-if-only-it
As a reference, here are Apple's docs on iCloud + Core Data:
http://developer.apple.com/library/ios/#releasenotes/DataManagement/RN-iCloudCoreData/
http://developer.apple.com/library/ios/#documentation/General/Conceptual/iCloudDesignGuide/Chapters/DesignForCoreDataIniCloud.html
http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/CoreDataVersioning/vmCloud/vmCloud.html
And here is an example app:
http://developer.apple.com/library/ios/#DOCUMENTATION/General/Conceptual/iCloud101/Introduction/Introduction.html
The Apple developer tutorial on using the iCloud API to manipulate documents might be a good place to start.
Your Third iOS App introduces you to the iCloud document storage APIs. You use these APIs to store and manipulate files in a user’s iCloud storage.

Good way to manage the Documents/Inbox folder in an iOS app

When a file is passed into an iOS application by the document interaction system, a copy of the file is stored in the application bundle's Documents/Inbox folder. After the application has processed the file, it obviously needs to remove the file from Documents/Inbox, otherwise the folder will continue to grow and waste storage on the device.
I am uncomfortable with this simple solution (A), however, because my app needs to interact with the user before it can finish processing and removing the file. If the user suspends the app during this interaction period, and the app then gets killed while it is in the background, the stale file will not be removed when the app starts up the next time. Of course I can improve my app to cover this scenario, but I suspect that there will always be another border case that will leave me with an "unclean" Documents/Inbox folder.
A preferrable solution (B) therefore would be to remove the Documents/Inbox folder at an appropriate time (e.g. when the app launches normally, i.e. not via document interaction). I am still uncomfortable with this because I would be accessing a filesystem path whose location is not officially documented anywhere. For instance, my app would break if in a future version of iOS the document interaction system no longer places files in Document/Inbox.
So my questions are:
Would you recommend solution A or B?
Do you use a different approach and can you maybe give an outline of how your app manages Document/Inbox?
Last but not least: Do you know a piece of official Apple documentation that covers the topic and that I have overlooked?
Since I have asked this question, I have implemented the following solution:
I have redesigned my app so that it now immediately processes a file handed over to it via documentation interaction, without involving the user at all. Unless the app crashes, or is suspended and killed, in the middle of processing the file, this should always leave me with a clean Documents/Inbox.
To cover the (rare) case of a crash or suspend/kill, my app removes the Documents/Inbox folder when it is launched normallly (i.e. without the purpose of document interaction). To achieve this the Documents/Inbox folder path is necessarily hardcoded.
Here are the thoughts that went into the solution:
Redesigning the app
Initially it seemed like a good idea to offer the user a choice how she wanted a file to be processed - after all, offering a choice would make the app more flexible and provide the user with more freedom, right?
I then realized that I was trying to shift the responsibility for deciding how document interaction should be handled to the user. So I bit the bullet, made the hard decisions up-front, and then went happily on to implement a simple and straightforward document interaction system in my app.
As it turns out, no user interaction also means that the app is easier to use, so here's a win-win situation, both for me as a developer and for the users of my app.
Removing Documents/Inbox folder during app launch
Removing the Documents/Inbox folder during app launch is just an afterthought, not an essential part of how my app handles document interaction
Therefore I am quite willing to take the risk that Apple might change the filesystem path of the inbox folder at some point in the future. The worst thing that can happen is that my app will start to accumulate a few files that are leftovers from crash or suspend/kill scenarios.
And finally, a few thoughts for further development:
If it ever turns out that there really should be more than one way how the app can handle document interaction, I would add a user preference so that the user has to make a decision up-front, and the app does not need to stop its processing to ask the user for a choice.
If it ever turns out that user interaction is absolutely unavoidable in the middle of the document interaction handling process, I would look at this approach: 1) Before the user is allowed to interact, move the file from Documents/Inbox to some sort of "staging" folder; 2) Let user interaction take place; 3) Process the file in the "staging" folder, in whatever way the user chose. The important thing here is that the "staging" folder is in a known location and is completely managed by the app. Should the user suspend and then kill the app in the middle of the user interaction step, an appropriate action can simply be taken when the app is launched for the next time.
EDIT
In iOS 7 it is no longer possible to remove Documents/Inbox once it has been created. The NSFileManager method removeItemAtPath:error: returns Cocoa error 513 which resolves to NSFileWriteNoPermissionError (cf. this list of Foundation constants). The error does not seem to be related to POSIX permissions, however, it rather looks as if the system itself interferes with the attempt at deletion (possibly protection of the app bundle structure?).
Also noteworthy is that nowadays Apple explicitly names Documents/Inbox in the documentation of the UIApplicationDelegate method application:openURL:sourceApplication:annotation:. They also say that
[...] Your app has permission to read and delete files in this directory but does not have permission to write to them. If you want to modify a file, you must move it to a different directory first.
There is more stuff about possible encryption of the files in the docs, but you should read up on that yourself.
This problem has become much more complicated with the introduction of the Files app, and the "Open in place" feature.
If you do not have "Supports opening documents in place" switched on, in your info.plist, then things are pretty much the same, and files opened from any other app still appear in the Documents/Inbox directory. But files opened from the Files app appear in another inbox, currently at tmp/<bundle ID of app>-inbox. It is still recommended to delete the file once you are done with it, but there is less need to occasionally clean the directory, because the tmp directory gets cleaned by iOS once in a while anyways.
If you do have "Supports opening documents in place" switched on, then things change drastically. Files opened from the Files app and some other apps are no longer copied into an inbox, but they are passed to you at their original location. That is typically some location inside the Files app itself, inside another app referenced from the Files app, or even some general iCloud location. If you expose the files in your Documents folder, then it could even be one of your own app's files.
Needless to say, if you get such a file, you must not delete it. But if the file comes in an inbox, which will still happen a lot as well, then you must delete it. In order to determine this, the options of the application:openURL:options: call contains an UIApplicationOpenURLOptionsOpenInPlaceKey key. If that has value (NSNumber) NO, then the file is in an inbox, and it should be deleted. It it has value YES, then it is opened in-place, and must not be deleted.
Note that for files that are opened in place, you also need to gain permission to use them first. You do that but surrounding the access to the file by startAccessingSecurityScopedResource and stopAccessingSecurityScopedResource calls. See Apple documentation for details.
I am just now facing this same problem. Like you I don't have a good solution but in response to your questions I'm leaning towards option A over option B because I don't like the idea of potentially having an issue with future releases of the OS. Since, in my case, there is no user interaction once I display the preview of the document, I can go ahead and delete it when when I receive the –documentInteractionControllerDidEndPreview: delegate callback. This is theoretical in that I haven't coded this yet and won't get to it for a while as it is a low priority item. If it doesn't work or there are other issues, I will report back here. The Google search I entered in order to find documentation from Apple pointed to this StackOverflow post. I haven't seen any other useful information from Apple or anyone else on the subject.

Syncing Core Data across multiple devices using iCloud

There has been a lot of discussion lately about the issues with iCloud and Core Data and how Apple's APIs are currently broken in iOS 5 and possibly iOS 6.
Is it possible, given the current state of Apple's Core Data API, to reliably sync across multiple devices using iCloud?
If so, how would you do this? If not, please recommend an alternative approach.
This blog post will lead you to a chain of recent articles about the travails of developers attempting this approach.
From my own understanding and experience, I believe it is doable, but don't buy into the idea that you will get anything "for free". Depending on your data model, you may be better off syncing your whole persistent store as a document rather than using the documented core data / iCloud approach.
You may have better luck if you're already comfortable with Core Data. Just be sure you think through how to handle several important cases.
One is what to do if the user signs out of their iCloud account. When this happens, the local ubiquitous persistent store is deleted. If it makes sense for the user to still have access to their data, you'll need to manage a copy in local storage, and then manage resynchronizing when they sign back in.
Another is that changes can apparently be quite slow to propagate by default, so you may want to consider an alternative mechanism, such as the key value store, to quickly propagate sufficient information to avoid a bad user experience.
Conflict management is perhaps the most challenging (depending on your model). While the framework provides a mechanism to inform you of conflicts, you are on your own for providing a mechanism to resolve them, and there are reports that the conflict notifications may not be reliable (see linked articles), which seems strongly linked to the lag in updating.
In short, if you go into this understanding that the actual support is pretty bare bones and that you'll need to code very defensively, you may have a chance. There aren't any good recipes out there, so if you do make it work, please come back and tell us what works!
It depends on what you want to do. There are two types of Core Data-iCloud integration, as described here: http://developer.apple.com/library/ios/#releasenotes/DataManagement/RN-iCloudCoreData/_index.html
There are broadly speaking two types of Core Data-based application that integrate with iCloud:
Library-style applications, where the application usually has a single persistent store, and data from the store is used throughout the application.
Examples of this style of application are Music and Photos.
Document-based applications, where different documents may be opened at different times during the lifetime of the application.
Examples of this style of application are Keynote and Numbers.
If you're using the library-type, this article is the first of a series that goes into a lot of the problems that will come up: http://mentalfaculty.tumblr.com/post/23163747823/under-the-sheets-with-icloud-and-core-data-the-basics.
You can also check out sessions 218 (for document-based) or 227 (for library-style) of this year's wwdc.
As of iOS 7, the best solution is probably the Ensembles framework: https://github.com/drewmccormack/ensembles
Additionally, there is a promising project which will essentially allow you to do the same thing using a different cloud service.
Here is a link to the repository: https://github.com/nothirst/TICoreDataSync
Project description:
TICoreDataSync is a collection of classes to enable synchronization via the Cloud (including Dropbox) of Core Data-based applications (including document-based apps) between any number of clients running under Mac OS X or iOS. It's designed to be easy to extend if you need to synchronize via an option that isn't already supported.
Reasons for why iCloud is not currently reliable:
"Sometimes, iCloud simply fails to move data from one computer to another."
"Corrupted baselines are [a] common obstacle.... There is no recovery from a corrupted baseline, short of digging in to the innards of your local iCloud storage and scraping everything out, and there is no visible indication that corruption has occurred — syncing simply stops."
"Sometimes, when initializing the iCloud application subsystem, it will simply return an opaque internal error. When it fails, there’s no option to recover — all you can do is try again (and again…) until it finally works."
"[W]hen you turn off the “Documents & Data” syncing option in the iCloud system preferences, the iCloud system deletes all of your locally stored iCloud data[.]"
When you sign out of iCloud, the system moves your iCloud data to a location outside of your application’s sandbox container, and the application can no longer use it.
"In some circumstances (and we haven’t been able to figure out which, yet), iCloud actually changes the object class of an item when synchronizing it. Loosely described, the object class determines the type of the object in the database[.]"
"In some cases (again, not all the time), iCloud may do one of the following:
Owner relationships in an item’s data will point to the wrong owner;
Owner items get lost in synchronization and never appear on computers other than the one on which they were created. (This leads to the item never appearing in the UI on any other machine.) When this happens, bogus relationships get created between blob items and an arbitrary unrelated owner."
"Sometimes (without any apparent consistency or repeatability), the associated data for an object (for example, the PDF data for a PDF item, or the web archive data for a Web Archive item) would simply fail to show up on the destination machine. Sometimes it would arrive later (much later — minutes or hours)."
Quoted and paraphrased from these sources:
http://www.imore.com/debug-12-icloud-core-data-sync
http://rms2.tumblr.com/post/46505165521/the-gathering-storm-our-travails-with-icloud-sync
Note: I have seen one article where the author mentions getting it to work for iOS 6+, but they don't provide any examples: http://zaal.tumblr.com/post/46718877130/why-you-want-to-use-core-data-icloud-sync-if-only-it
As a reference, here are Apple's docs on iCloud + Core Data:
http://developer.apple.com/library/ios/#releasenotes/DataManagement/RN-iCloudCoreData/
http://developer.apple.com/library/ios/#documentation/General/Conceptual/iCloudDesignGuide/Chapters/DesignForCoreDataIniCloud.html
http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/CoreDataVersioning/vmCloud/vmCloud.html
And here is an example app:
http://developer.apple.com/library/ios/#DOCUMENTATION/General/Conceptual/iCloud101/Introduction/Introduction.html
The Apple developer tutorial on using the iCloud API to manipulate documents might be a good place to start.
Your Third iOS App introduces you to the iCloud document storage APIs. You use these APIs to store and manipulate files in a user’s iCloud storage.

iOS - how to structure database to conform to iCloud backup rules

I've been having trouble getting an app submitted to the App Store. This is due to the fact that that database, which is updatable, is too large for the iCloud backup limitations. Most of the data in the db is static, but one table records the user's schedule for reviewing words (this is a vocabulary quiz).
As far as I can tell, I have two or three realistic options. The first is to put the whole database into the Library/Cache directory. This should be accepted, because it's not backed up to iCloud. However, there's no guarantee that it will be maintained during app updates, per this entry in "Make App Backups More Efficient" at this url:
http://developer.apple.com/library/IOs/#documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/PerformanceTuning/PerformanceTuning.html
Files Saved During App Updates
When a user downloads an app update, iTunes installs the update in a new app directory. It then moves the user’s data files from the old installation over to the new app directory before deleting the old installation. Files in the following directories are guaranteed to be preserved during the update process:
<Application_Home>/Documents
<Application_Home>/Library
Although files in other user directories may also be moved over, you should not rely on them being present after an update.
The second option is to put the data into the NSDocuments or NSLibrary directory, as mark it with the skipBackupFlag. However, one problem is this flag doesn't work for iOS 5.0 and previous per this entry in "How do I prevent files from being backed up to iCloud and iTunes?" at
https://developer.apple.com/library/ios/#qa/qa1719/_index.html
Important The new "do not back up" attribute will only be used by iOS 5.0.1 or later. On iOS 5.0 and earlier, applications will need to store their data in <Application_Home>/Library/Caches to avoid having it backed up. Since this attribute is ignored on older systems, you will need to insure your app complies with the iOS Data Storage Guidelines on all versions of iOS that your application supports
This means that even if I use the "skipBackupFlag", I'll still have the problem that the database is getting backed up to the cloud, I think.
So, the third option, which is pretty much of an ugly hack, is to split the database into two. Put the updatable part into the NSLibrary or NSDocuments directory, and leave the rest in application resources. This would have the small, updatable part stored on the cloud, and leave the rest in the app resources directory. The problem is that this splits the db for no good reason, and introduces possible performance issues with having two databases open at once.
So, my question is, is my interpretation of the rules correct? Am I going to have to go with option 3?
p.s. I noticed in my last post cited urls were edited to links without the url showing. How do I do this?
Have you considered using external file references as described in https://developer.apple.com/library/IOS/#releasenotes/DataManagement/RN-CoreData/_index.html . Specifically, refer to "setAllowsExternalBinaryDataStorage:" https://developer.apple.com/library/IOS/documentation/Cocoa/Reference/CoreDataFramework/Classes/NSAttributeDescription_Class/reference.html#//apple_ref/occ/instm/NSAttributeDescription/setAllowsExternalBinaryDataStorage: . Pushing out large data into a separate file can help reduce database size .

Resources