使用Swift查询可用的iOS磁盘空间

41

我正在尝试使用Swift获取可用的iOS设备存储空间。 我在这里找到了这个函数here


        func deviceRemainingFreeSpaceInBytes() -> NSNumber {
          let documentDirectoryPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
          let systemAttributes = NSFileManager.defaultManager().attributesOfFileSystemForPath(documentDirectoryPath.last as String, error: nil)
          return systemAttributes[NSFileSystemFreeSize] as NSNumber
        }

但在编译时会出现这个错误:[NSObject : AnyObject]?没有名为 'subscript' 的成员。我认为这个错误来自于这里提到的问题,即attributesOfFileSystemForPath返回一个可选字典 (文档)。我大致了解这个问题,但由于建议的解决方案涉及嵌套 case,我不太清楚如何修复我感兴趣的函数 (这并没有帮助,因为我对 Swift 很新)。有人能建议如何让这个函数工作吗? 注意:我不确定原始函数是否由作者测试过或者它是否在 xcode 6 beta 下运行正常,但据我所见,在 GM 下它不起作用。

4个回答

74

iOS 11 更新

在 iOS 11 下,下面提供的答案不再提供准确结果。现在可以传递新的容量密钥 URL.resourceValues(forKeys:),这些密钥提供与设备设置中可用内容匹配的值。

  • static let volumeAvailableCapacityKey: URLResourceKey 卷的可用容量字节数(只读)的键。

  • static let volumeAvailableCapacityForImportantUsageKey: URLResourceKey 存储重要资源的卷的可用容量字节数(只读)的键。

  • static let volumeAvailableCapacityForOpportunisticUsageKey: URLResourceKey 用于存储非必需资源的卷的可用容量字节数(只读)的键。

  • static let volumeTotalCapacityKey: URLResourceKey 卷的总容量字节数(只读)的键。

来自Apple 文档

概述

在尝试本地存储大量数据之前,请先验证您是否具有足够的存储容量。要获取卷的存储容量,您构造一个 URL(使用 URL 的实例)来引用要查询的卷上的对象,然后查询该卷。

决定使用哪种查询类型

要使用的查询类型取决于正在存储的内容。如果您根据用户请求存储数据或应用程序需要正确运行的资源(例如,用户即将观看的视频或下一个游戏等级所需的资源),请针对 volumeAvailableCapacityForImportantUsageKey 进行查询。但是,如果您以更有预测性的方式下载数据(例如,下载用户最近观看过的电视系列的新可用剧集),请针对 volumeAvailableCapacityForOpportunisticUsageKey 进行查询。

构造查询

请使用此示例作为构建自己查询的指南:

let fileURL = URL(fileURLWithPath: NSHomeDirectory() as String)
do {
    let values = try fileURL.resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey])
    if let capacity = values.volumeAvailableCapacityForImportantUsage {
        print("Available capacity for important usage: \(capacity)")
    } else {
        print("Capacity is unavailable")
    }
} catch {
    print("Error retrieving capacity: \(error.localizedDescription)")
}

原始回答

使用if let的可选绑定在这里同样适用。

我建议该函数返回一个可选的Int64,这样它可以返回nil来表示失败:


func deviceRemainingFreeSpaceInBytes() -> Int64? {
    let documentDirectoryPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
    if let systemAttributes = NSFileManager.defaultManager().attributesOfFileSystemForPath(documentDirectoryPath.last as String, error: nil) {
        if let freeSize = systemAttributes[NSFileSystemFreeSize] as? NSNumber {
            return freeSize.longLongValue
        }
    }
    // something failed
    return nil
}

Swift 2.1 更新:

func deviceRemainingFreeSpaceInBytes() -> Int64? {
    let documentDirectory = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).last!
    guard
        let systemAttributes = try? NSFileManager.defaultManager().attributesOfFileSystemForPath(documentDirectory),
        let freeSize = systemAttributes[NSFileSystemFreeSize] as? NSNumber
    else {
        // something failed
        return nil
    }
    return freeSize.longLongValue
}

Swift 3.0 更新:

func deviceRemainingFreeSpaceInBytes() -> Int64? {
    let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).last!
    guard
        let systemAttributes = try? FileManager.default.attributesOfFileSystem(forPath: documentDirectory),
        let freeSize = systemAttributes[.systemFreeSize] as? NSNumber
    else {
        // something failed
        return nil
    }
    return freeSize.int64Value
}

用法:

if let bytes = deviceRemainingFreeSpaceInBytes() {
    print("free space: \(bytes)")
} else {
    print("failed")
}

3
为什么这些操作会返回大约200MB(200495104字节)的可用空间,但我的iOS设备不允许我拍照,也无法将7MB大小的视频保存到磁盘? - Sam Bing
2
正如@LucèBrùlè所提到的,当我运行上述代码时,得到的是相同的结果。显示大约215089152字节。但是当我在应用程序存储中检查时,只显示了一些字节。有没有办法找出确切的大小,与应用程序存储设置中显示的大小相同?提前致谢。 - Subin K Kuriakose
1
嘿@SubinKKuriakose,我进行了大量的研究,但实际上没有一种真正准确的方法来获取剩余的可用空间。我猜这与iOS在运行低时开始清理东西有关,这实际上总是在变化。 - Lukas
它显示的内存与设置中显示的不匹配,设置中显示为零KB,而通过此项显示还剩下49MB...我不明白为什么。 - Ribelyn Punk
1
我编辑了这个答案以在iOS 11上提供更准确的结果。 - pwc
显示剩余5条评论

61

好的,根据上述代码:

let usedSpace = totalDiskSpaceInBytes - freeDiskSpaceInBytes

你可能会发现 usedSpace 和iPhone设置页面上的值不相等。这是因为在iOS11中,苹果引入了“重要”资源的总可用容量(以字节为单位)

包括预计通过清除非必要和缓存资源来释放的空间的“重要”资源的总可用容量。 “重要”意味着用户或应用程序明确期望存在于本地系统中的某些内容,但最终可以替换。这将包括用户通过UI明确请求的项目,以及应用程序需要以提供功能的资源。

例如:用户明确请求观看但尚未完成观看的视频或用户请求下载的音频文件。

此值不应用于确定是否有空间存放不可替代的资源。对于不可替代的资源,始终尝试保存资源,而不管可用容量如何,并尽可能优雅地处理失败。

为了获得与iPhone设置页面上看到的完全相同的值,我们可以通过 volumeAvailableCapacityForImportantUsage 来获取可用空间。

if let space = try? URL(fileURLWithPath: NSHomeDirectory() as String).resourceValues(forKeys: [URLResourceKey.volumeAvailableCapacityForImportantUsageKey]).volumeAvailableCapacityForImportantUsage {
    return space ?? 0
}
你可以使用以下UIDevice扩展

Swift4

extension UIDevice {
    func MBFormatter(_ bytes: Int64) -> String {
        let formatter = ByteCountFormatter()
        formatter.allowedUnits = ByteCountFormatter.Units.useMB
        formatter.countStyle = ByteCountFormatter.CountStyle.decimal
        formatter.includesUnit = false
        return formatter.string(fromByteCount: bytes) as String
    }
    
    //MARK: Get String Value
    var totalDiskSpaceInGB:String {
       return ByteCountFormatter.string(fromByteCount: totalDiskSpaceInBytes, countStyle: ByteCountFormatter.CountStyle.decimal)
    }
    
    var freeDiskSpaceInGB:String {
        return ByteCountFormatter.string(fromByteCount: freeDiskSpaceInBytes, countStyle: ByteCountFormatter.CountStyle.decimal)
    }
    
    var usedDiskSpaceInGB:String {
        return ByteCountFormatter.string(fromByteCount: usedDiskSpaceInBytes, countStyle: ByteCountFormatter.CountStyle.decimal)
    }
    
    var totalDiskSpaceInMB:String {
        return MBFormatter(totalDiskSpaceInBytes)
    }
    
    var freeDiskSpaceInMB:String {
        return MBFormatter(freeDiskSpaceInBytes)
    }
    
    var usedDiskSpaceInMB:String {
        return MBFormatter(usedDiskSpaceInBytes)
    }
    
    //MARK: Get raw value
    var totalDiskSpaceInBytes:Int64 {
        guard let systemAttributes = try? FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String),
            let space = (systemAttributes[FileAttributeKey.systemSize] as? NSNumber)?.int64Value else { return 0 }
        return space
    }
    
    /*
     Total available capacity in bytes for "Important" resources, including space expected to be cleared by purging non-essential and cached resources. "Important" means something that the user or application clearly expects to be present on the local system, but is ultimately replaceable. This would include items that the user has explicitly requested via the UI, and resources that an application requires in order to provide functionality.
     Examples: A video that the user has explicitly requested to watch but has not yet finished watching or an audio file that the user has requested to download.
     This value should not be used in determining if there is room for an irreplaceable resource. In the case of irreplaceable resources, always attempt to save the resource regardless of available capacity and handle failure as gracefully as possible.
     */
    var freeDiskSpaceInBytes:Int64 {
        if #available(iOS 11.0, *) {
            if let space = try? URL(fileURLWithPath: NSHomeDirectory() as String).resourceValues(forKeys: [URLResourceKey.volumeAvailableCapacityForImportantUsageKey]).volumeAvailableCapacityForImportantUsage {
                return space ?? 0
            } else {
                return 0
            }
        } else {
            if let systemAttributes = try? FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String),
            let freeSpace = (systemAttributes[FileAttributeKey.systemFreeSize] as? NSNumber)?.int64Value {
                return freeSpace
            } else {
                return 0
            }
        }
    }
    
    var usedDiskSpaceInBytes:Int64 {
       return totalDiskSpaceInBytes - freeDiskSpaceInBytes
    }

}

使用方法:

print("totalDiskSpaceInBytes: \(UIDevice.current.totalDiskSpaceInBytes)")
print("freeDiskSpace: \(UIDevice.current.freeDiskSpaceInBytes)")
print("usedDiskSpace: \(UIDevice.current.usedDiskSpaceInBytes)")


    

输入图像描述


4
这应该是2018年最佳答案。 - anon_nerd
2022年仍然是最佳答案。 - Rexam
如果使用 systemFreeSize] as? Int64 等类型转换,就不需要进行 NSNumber 强制转换。 - IT Gypsy

21

我写了一个使用Swift获取可用/已用内存的类。

演示请参见:https://github.com/thanhcuong1990/swift-disk-status

升级以支持Swift 3。

import UIKit

class DiskStatus {

    //MARK: Formatter MB only
    class func MBFormatter(_ bytes: Int64) -> String {
        let formatter = ByteCountFormatter()
        formatter.allowedUnits = ByteCountFormatter.Units.useMB
        formatter.countStyle = ByteCountFormatter.CountStyle.decimal
        formatter.includesUnit = false
        return formatter.string(fromByteCount: bytes) as String
    }


    //MARK: Get String Value
    class var totalDiskSpace:String {
        get {
            return ByteCountFormatter.string(fromByteCount: totalDiskSpaceInBytes, countStyle: ByteCountFormatter.CountStyle.binary)
        }
    }

    class var freeDiskSpace:String {
        get {
            return ByteCountFormatter.string(fromByteCount: freeDiskSpaceInBytes, countStyle: ByteCountFormatter.CountStyle.binary)
        }
    }

    class var usedDiskSpace:String {
        get {
            return ByteCountFormatter.string(fromByteCount: usedDiskSpaceInBytes, countStyle: ByteCountFormatter.CountStyle.binary)
        }
    }


    //MARK: Get raw value
    class var totalDiskSpaceInBytes:Int64 {
        get {
            do {
                let systemAttributes = try FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String)
                let space = (systemAttributes[FileAttributeKey.systemSize] as? NSNumber)?.int64Value
                return space!
            } catch {
                return 0
            }
        }
    }

    class var freeDiskSpaceInBytes:Int64 {
        get {
            do {
                let systemAttributes = try FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String)
                let freeSpace = (systemAttributes[FileAttributeKey.systemFreeSize] as? NSNumber)?.int64Value
                return freeSpace!
            } catch {
                return 0
            }
        }
    }

    class var usedDiskSpaceInBytes:Int64 {
        get {
            let usedSpace = totalDiskSpaceInBytes - freeDiskSpaceInBytes
            return usedSpace
        }
    }

}

示例:

使用Swift获取磁盘空间状态


不错!我喜欢显示方面。 - Bryan Hanson
很好,帮了我很多。谢谢! - Ashok
Xcode 7.3: 使用未解决的标识符'ByteCountFormatter' - Vito Royeca
3
很遗憾,价值与iPhone“关于”页面上的不同。 - DàChún

2
这类似于Martin在Swift 3.1中的答案,但转换为UIDevice的扩展,以便更容易访问。
extension UIDevice {
    var systemSize: Int64? {
        guard let systemAttributes = try? FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String),
            let totalSize = (systemAttributes[.systemSize] as? NSNumber)?.int64Value else {
                return nil
        }

        return totalSize
    }

    var systemFreeSize: Int64? {
        guard let systemAttributes = try? FileManager.default.attributesOfFileSystem(forPath: NSHomeDirectory() as String),
            let freeSize = (systemAttributes[.systemFreeSize] as? NSNumber)?.int64Value else {
                return nil
        }

        return freeSize
    }
}

获取免费空间的方法:

UIDevice.current.systemFreeSize

获取总空间:

UIDevice.current.systemSize

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