【问题标题】:iOS: Can't save file to 'Application Support' folder, but can to 'Documents'iOS:无法将文件保存到“应用程序支持”文件夹,但可以保存到“文档”
【发布时间】:2013-04-18 18:30:47
【问题描述】:

我可以使用自定义名称下载二进制文件并将其保存到“文档”文件夹中。

如果我只是将 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];

然后它保存得很好。

我已阅读 Apple Docs on File 内容,但无法解决这个问题 - 我错过了什么?

【问题讨论】:

  • 该代码对 iOS 毫无意义。不过,这对 OS X 来说很好。在 iOS 中,Application Support 目录已经在您应用的沙箱中。在 OS X 中,它不是。
  • @rmaddy 我真的很尊重您作为 iOS 权威,所以我想与您再次确认这一点。它在 iOS 文档中多次明确表示将捆绑 ID 附加到路径的末尾。如果他们不是故意的/没有必要,为什么还要一直这么说?
  • developer.apple.com/library/ios/documentation/FileManagement/… : "使用应用程序支持目录常量 NSApplicationSupportDirectory,将您的 附加到:您的应用程序为用户创建和管理的资源和数据文件。您可以使用此目录来存储应用程序状态信息、计算或下载的数据,甚至是您代表用户管理的用户创建的数据。”
  • @SAHM 您的应用使用的其他库也可能写入您应用的应用程序支持文件夹。因此,如果您自己的代码附加了您的包 ID,它可以防止可能的命名冲突。
  • @rmaddy 所以这样做可能是个好主意,即使它可能完全没有必要,对吧?

标签: ios file save directory


【解决方案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)
        }
    }

【讨论】:

    【解决方案2】:

    一个班轮 - 如有必要也会创建:

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

    【讨论】:

      【解决方案3】:

      这里有一些适用于 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
         }
      

      【讨论】:

        【解决方案4】:

        Documents 目录不同,Application Support 目录默认不存在于应用的沙箱中。您需要先创建它,然后才能使用它。

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

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

        【讨论】:

        • 您是否像我说的那样创建了目录?
        • 这个聚会晚了,但最初的问题是使用NSAllDomainsMask 而不是NSUserDomainMask。我怀疑这就是问题所在。
        【解决方案5】:

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

        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】:

          如果有人不确定如何执行 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");
                  }
              }
          }
          

          【讨论】:

          • 感谢您花时间分享示例!
          • @chrysallwood ,愿意分享 swift 版本吗?
          • 来自技术问答 QA1719:“应用程序应创建自己的排除目录,而不是排除系统定义的目录”。我认为这是一个很好的做法。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-10-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-10-05
          相关资源
          最近更新 更多