iOS: 无法将文件保存到“应用支持”文件夹,但可以保存到“文档”文件夹。

35

我可以轻松下载并保存一个二进制文件到“文档”文件夹,并使用自定义名称。

但是,如果我将URL更改为“应用程序支持”文件夹而不是“文档”文件夹,则会写入失败,并显示该URL不存在。

以下是URL构建代码:

- ( NSURL * ) getSaveFolder
{
    NSURL * appSupportDir    = nil;
    NSURL * appDirectory     = nil;
    NSArray * possibleURLs   = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSAllDomainsMask];
    
    if ( [possibleURLs count] >= 1 )
    {
        appSupportDir = [possibleURLs objectAtIndex:0];
    }

    if ( appSupportDir != nil)
    {
        NSString * appBundleID = [[NSBundle mainBundle] bundleIdentifier];
        appDirectory           = [appSupportDir URLByAppendingPathComponent:appBundleID];
    }

    return appSupportDir;
}

这是节省的代码:

- ( void ) writeOutDataToFile:( NSData * )data
{
    NSURL * finalURL = [self.rootPathURL URLByAppendingPathComponent:self.aFileName];

    [data writeToURL:finalURL atomically:YES];
}
如果我把NSArray改为:
NSArray * possibleURLs   = [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];

然后它就保存成功了。

我已经阅读了苹果关于文件相关内容的文档,但是无法解决这个问题 - 我错过了什么?


3
这段代码在iOS中没有意义。但在OS X中可以使用。在iOS中,“Application Support”目录已经包含在应用程序的沙盒中。而在OS X中则不是这样。 - rmaddy
1
@rmaddy,我非常尊重您作为iOS专家的地位,所以我想向您再次确认一下。iOS文档中明确多次表示要将bundle id附加到路径末尾。如果他们不是这个意思或者这不是必要的,为什么他们还在强调这一点呢? - SAHM
1
使用应用程序支持目录常量NSApplicationSupportDirectory,附加您的<bundle_ID>,用于:您的应用程序为用户创建和管理的资源和数据文件。您可以使用此目录存储应用程序状态信息、计算或下载的数据,甚至是由您代表用户管理的用户创建的数据。 - SAHM
3
@SAHM,你的应用程序可能使用的其他库也可能会写入到你的应用程序的“Application Support”文件夹中。因此,如果你的代码附加了你的Bundle ID,它可以防止可能的命名冲突。 - rmaddy
1
@rmaddy,所以这样做可能是个好主意,即使它可能不是完全必要的,对吧? - SAHM
显示剩余4条评论
6个回答

52

如果有人不确定如何执行rmaddy所描述的操作:

NSString *appSupportDir = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) lastObject];
//If there isn't an App Support Directory yet ... 
if (![[NSFileManager defaultManager] fileExistsAtPath:appSupportDir isDirectory:NULL]) {
    NSError *error = nil;
//Create one 
    if (![[NSFileManager defaultManager] createDirectoryAtPath:appSupportDir withIntermediateDirectories:YES attributes:nil error:&error]) {
        NSLog(@"%@", error.localizedDescription);
    }
    else {
// *** OPTIONAL *** Mark the directory as excluded from iCloud backups 
        NSURL *url = [NSURL fileURLWithPath:appSupportDir];
        if (![url setResourceValue:@YES
                            forKey:NSURLIsExcludedFromBackupKey
                             error:&error])
        {
            NSLog(@"Error excluding %@ from backup %@", url.lastPathComponent, error.localizedDescription);
        }
        else {
            NSLog(@"Yay");
        }
    }
}

1
感谢您抽出时间分享示例! - Doug
@chrysallwood,能分享一下Swift版本吗? - Juan Boero
1
从技术问答QA1719:“应用程序应该创建自己的目录以进行排除,而不是排除系统定义的目录”。我认为这是一个好的做法。 - matm

51

Documents 目录不同,Application Support 目录在应用程序沙盒中默认不存在。您需要在使用它之前创建它。

获取目录引用的更简单方式是:

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSString *appSupportDirectory = paths.firstObject;

9
你按照我说的创建了目录吗? - rmaddy
1
虽然来晚了,但原始问题使用的是NSAllDomainsMask而不是NSUserDomainMask。我怀疑这就是问题所在。 - Jeremy Wiebe

9
我遇到了同样的问题,决定采用更简洁的方法:

我遇到了同样的问题,决定采用更简洁的方法:

let fileManager = NSFileManager.defaultManager()
let urls = fileManager.URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask) as! [NSURL]
if let applicationSupportURL = urls.last {
    fileManager.createDirectoryAtURL(applicationSupportURL, withIntermediateDirectories: true, attributes: nil, error: nil)
}

这种方法有效是因为createDirectoryAtURL使用withIntermediateDirectories:true,只有在文件夹不存在时才会创建。

6

一行代码 - 如果需要也会创建:

[[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:nil]

4

以下是一些针对iOS平台的Swift代码,可以将二进制数据文件写入应用程序支持目录。部分灵感来自chrysAllwood提供的答案。

   /// Method to write a file containing binary data to the "application support" directory.
   ///
   /// - Parameters:
   ///   - fileName: Name of the file to be written.
   ///   - dataBytes: File contents as a byte array.
   ///   - optionalSubfolder: Subfolder to contain the file, in addition to the bundle ID subfolder.
   ///                        If this is omitted no extra subfolder is created/used.
   ///   - iCloudBackupForFolder: Specify false to opt out from iCloud backup for whole folder or
   ///                            subfolder. This is only relevant if this method call results in
   ///                            creation of the folder or subfolder, otherwise it is ignored.
   /// - Returns: Nil if all OK, otherwise text for a couple of non-Error errors.
   /// - Throws: Various errors possible, probably of type NSError.
   public func writeBytesToApplicationSupportFile(_ fileName : String,
                                                  _ dataBytes : [UInt8],
                                                  optionalSubfolder : String? = nil,
                                                  iCloudBackupForFolder : Bool = true)
                                                 throws -> String? {

      let fileManager = FileManager.default

      // Get iOS directory for "application support" files
      let appSupportDirectory =
                  fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first
      if appSupportDirectory == nil {
         return "Unable to determine iOS application support directory for this app."
      }

      // Add "bundle ID" as subfolder. This is recommended by Apple, although it is probably not
      // necessary.
      if Bundle.main.bundleIdentifier == nil {
         return "Unable to determine bundle ID for the app."
      }
      var mySupportDirectory =
                  appSupportDirectory!.appendingPathComponent(Bundle.main.bundleIdentifier!)

      // Add an additional subfolder if that option was specified
      if optionalSubfolder != nil {
         mySupportDirectory = appSupportDirectory!.appendingPathComponent(optionalSubfolder!)
      }

      // Create the folder and subfolder(s) as needed
      if !fileManager.fileExists(atPath: mySupportDirectory.path) {
         try fileManager.createDirectory(atPath: mySupportDirectory.path,
                                         withIntermediateDirectories: true, attributes: nil)

         // Opt out from iCloud backup for this subfolder if requested
         if !iCloudBackupForFolder {
            var resourceValues : URLResourceValues = URLResourceValues()
            resourceValues.isExcludedFromBackup = true
            try mySupportDirectory.setResourceValues(resourceValues)
         }
      }

      // Create the file if necessary
      let mySupportFile = mySupportDirectory.appendingPathComponent(fileName)
      if !fileManager.fileExists(atPath: mySupportFile.path) {
         if !fileManager.createFile(atPath: mySupportFile.path, contents: nil, attributes: nil) {
            return "File creation failed."
         }
      }

      // Write the file (finally)
      let fileHandle = try FileHandle(forWritingTo: mySupportFile)
      fileHandle.write(NSData(bytes: UnsafePointer(dataBytes), length: dataBytes.count) as Data)
      fileHandle.closeFile()

      return nil
   }

1

Swift 4.2 版本

    let fileManager = FileManager.default
    let urls = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask)
    if let applicationSupportURL = urls.last {
        do{
            try fileManager.createDirectory(at: applicationSupportURL, withIntermediateDirectories: true, attributes: nil)
        }
        catch{
            print(error)
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接