Adeline Adeline - 19 days ago 6
Swift Question

How to know if user can read the content of a directory in swift

In order to avoid a known bug, I need to kwnow before enumerating a directory, if the user can read it.

How can I do that in swift (most recent version, using Xcode and MAC OS most recent version)?

More info:
Trying to enumerate a directory in swift can lead to an application crash if the user can't access it. There is no way to catch an error to prevent the crash but it only happen if the user can't access the directory. So, knowing beforehand if the directory can be read (thus enumerated) would help in preventing the crash.

Cashable code:

There is this example : https://bugs.swift.org/browse/SR-2872 reported by Timothy Wood

And this example : http://prod.lists.apple.com/archives/cocoa-dev/2016/Oct/msg00166.html reported by Jean Suisse

Answer

A workaround for this bug is provided in the release notes for Xcode 8.1. To quote the release notes:

Some Objective-C methods in the SDKs may incorrectly annotate or assume a type to be nonnull rather than nullable. A type that Swift treats as a struct, such as NSURL (Foundation.URL) or NSDate (Foundation.Date), results in a run-time crash in a method with a name like bridgeFromObjectiveC; in other cases, it can lead to crashes or undefined behavior in user code. If you identify such a method, please file a report at bugreport.apple.com. As a workaround, add a trampoline function in Objective-C with the correct nullability annotations. For example, the following function will allow you to call FileManager's enumerator(at:includingPropertiesForKeys:options:errorHandler:) method with an error handler that accepts nil URLs:

static inline NSDirectoryEnumerator<NSURL *> * _Nullable
fileManagerEnumeratorAtURL(NSFileManager *fileManager, NSURL * _Nonnull url, NSArray<NSURLResourceKey> * _Nullable keys, NSDirectoryEnumerationOptions options, BOOL (^ _Nullable errorHandler)(NSURL * _Nullable errorURL, NSError * _Nonnull error)) {
     return [fileManager enumeratorAtURL:url includingPropertiesForKeys:keys options:options errorHandler:^(NSURL * _Nonnull errorURL, NSError * _Nonnull error) {
      return errorHandler(errorURL, error);
   }];
}

This function can be included in your bridging header (for an app or test target) or umbrella header (for a framework target). (27749845)

The steps to take:

1) Add a new .m file to your project called DirectoryEnumeratorGlue.m. When you add it Xcode will ask you whether you want it to create a bridging header for you, say yes. Put the following in DirectoryEnumeratorGlue.m:

NSDirectoryEnumerator<NSURL *> * _Nullable
fileManagerEnumeratorAtURL(NSFileManager * _Nonnull fileManager, NSURL * _Nonnull url, NSArray<NSURLResourceKey> * _Nullable keys, NSDirectoryEnumerationOptions options, BOOL (^ _Nullable errorHandler)(NSURL * _Nullable errorURL, NSError * _Nonnull error)) {
     return [fileManager enumeratorAtURL:url includingPropertiesForKeys:keys options:options errorHandler:^(NSURL * _Nonnull errorURL, NSError * _Nonnull error) {
      return errorHandler(errorURL, error);
   }];
}

2) Add a new .h file to your project, named DirectoryEnumeratorGlue.h. Put the following in DirectoryEnumeratorGlue.h:

extern NSDirectoryEnumerator<NSURL *> * _Nullable
fileManagerEnumeratorAtURL(NSFileManager * _Nonnull fileManager, NSURL * _Nonnull url, NSArray<NSURLResourceKey> * _Nullable keys, NSDirectoryEnumerationOptions options, BOOL (^ _Nullable errorHandler)(NSURL * _Nullable errorURL, NSError * _Nonnull error));

3) Add the following to your bridging header:

#import <Foundation/Foundation.h>
#import "DirectoryEnumeratorGlue.h"

4) Replace the call to FileManager.default.enumerator in your Swift call with something like this:

    let enumerator = fileManagerEnumeratorAtURL(FileManager.default, url, [],  [], {
        (errorURL, error) in
        return true
    })
Comments