Доступный объем дискового пространства iOS с Swift

Я пытаюсь получить доступное хранилище устройств iOS с помощью Swift. Я нашел эту функцию здесь

        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]? does not have a member named 'subscript' Я считаю, что эта ошибка возникает из проблемы, упомянутой здесь , а именно: attributesOfFileSystemForPath возвращает необязательный словарь (documentation). Я понимаю проблему в общем смысле, но поскольку предлагаемое решение связано с вложенным случаем, я не совсем понимаю, как исправить интересующую меня функцию (это не помогает, что я довольно новичок в Swift). Может ли кто-нибудь предложить, как заставить функцию работать? ПРИМЕЧАНИЕ. Я не уверен, была ли оригинальная функция проверена автором или если она работала под бета-версией xcode 6, но она не работает под GM, насколько я могу видеть.

Ответ 1

Обновление 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:"/")
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")
}

Ответ 2

Я написал класс для получения/использования памяти с помощью 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
        }
    }

}

Демо:

get disk space status with Swift

Ответ 3

Ну, в соответствии с приведенными выше кодами:

let usedSpace = totalDiskSpaceInBytes - freeDiskSpaceInBytes

вы можете обнаружить, что usedSpace не соответствует значению страницы настроек iPhone. Это связано с тем, что в iOS11 Apple вводит Общая доступная емкость в байтах для "важных" ресурсов.

Общая доступная емкость в байтах для "важных" ресурсов, включая пространство должно быть очищено путем очистки несущественных и кэшированных Ресурсы. "Важно" означает что-то, что пользователь или приложение очевидно, ожидает присутствия в местной системе, но в конечном итоге заменяемый. Это будет включать элементы, которые пользователь явно запрашиваемых через пользовательский интерфейс, и ресурсов, которые требуется в приложении чтобы обеспечить функциональность.

Примеры: видео, которое пользователь явно попросил посмотреть, но еще не закончил смотреть или аудиофайл, который пользователь запросил загрузить.

Это значение не должны использоваться при определении того, есть ли место для незаменимый ресурс. В случае незаменимых ресурсов всегда попытайтесь сохранить ресурс независимо от имеющейся емкости и обрабатывать отказ как можно более изящно.

Чтобы получить то же значение, что и на странице настроек 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
    }

}

использование:

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

введите описание изображения здесь

Ответ 4

Это похоже на ответ Мартина для 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