試著把iOS儲(chǔ)存空間填滿&iOS13UserDefaults上限

北海道海邊雪景

做iOS開發(fā)做到文件儲(chǔ)存這一塊的時(shí)候,面對(duì)各種各樣的try語句,總會(huì)想如果真的儲(chǔ)存滿了會(huì)發(fā)生什么情況。
于是腦洞大開,就做個(gè)把iOS儲(chǔ)存空間塞滿的app,以滿足測試場景。

填滿實(shí)現(xiàn)方案

實(shí)現(xiàn)方案非常簡單,就是用各種空數(shù)據(jù)堆成文件,然后用FileManager儲(chǔ)存進(jìn)sandbox

  1. 利用volumeAvailableCapacityForImportantUsageKey可以查詢到可以使用的儲(chǔ)存空間(單位為字節(jié))
    ??蘋果官方示例代碼
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)")
}
  1. 把字節(jié)轉(zhuǎn)換為GB, MB, KB
    • Swift可以把函數(shù)作為參數(shù)傳遞,所以用一個(gè)包含了運(yùn)算符和乘數(shù)的元組作為單位轉(zhuǎn)換函數(shù)的返回值
    • enum的類型取為Int,從bit開始rawValue遞增,每升一級(jí)乘以1024,然后以此為基礎(chǔ)設(shè)計(jì)獲得乘數(shù)的遞歸函數(shù)
    • 兩個(gè)assertionFailure的地方,從這個(gè)程序設(shè)計(jì)的角度來看是不可能進(jìn)入的,參照喵神的文章
      關(guān)于 Swift Error 的分類
      這里應(yīng)為logic failure, 用assertionFailure讓程序在debug時(shí)進(jìn)入后直接崩潰
struct DataAllocator {

    ...

    enum Unit: Int, CustomStringConvertible {
        case bits = 0
        case bytes
        case kilobytes
        case megabytes
        case gigabytes

        var description: String {
            switch self {
            case .bits:
                return "bits"
            case .bytes:
                return "bytes"
            case .kilobytes:
                return "kilobytes"
            case .megabytes:
                return "megabytes"
            case .gigabytes:
                return "gigabytes"
            @unknown default:
                return "unknown"
            }
        }
    }

    static func size(of size: Int, from fromUnit: Unit, to toUnit: Unit) -> Int {
        let (oprt, multiplier) = conversion(from: fromUnit, to: toUnit)
        return oprt(size, multiplier)
    }

    static func conversion(from fromUnit: Unit, to toUnit: Unit) -> (operator: (Int, Int) -> Int, multiplier: Int) {
        switch fromUnit.rawValue {
        case 0...toUnit.rawValue:
            return (/, multiplier(from: toUnit, to: fromUnit))
        case (toUnit.rawValue + 1)...:
            return (*, multiplier(from: fromUnit, to: toUnit))
        default:
            assertionFailure("unable to convert from \(fromUnit) to \(toUnit)")
            return (*, 1)
        }
    }

    private static func multiplier(from fromUnit: Unit, to toUnit: Unit) -> Int {
        if fromUnit == toUnit { return 1 }
        guard fromUnit.rawValue > toUnit.rawValue,
            let lowerUnit = Unit(rawValue: fromUnit.rawValue - 1) else {
                assertionFailure("unable to get multiplier from \(fromUnit) to \(toUnit)")
                return 1
        }
        return 1024 * multiplier(from: lowerUnit, to: toUnit)
    }
}

private func display(capacity: Int64) {
    let total = Int(capacity)
    var remainder = total
    let gigas = DataAllocator.size(of: remainder, from: .bytes, to: .gigabytes)
    remainder -= DataAllocator.size(of: gigas, from: .gigabytes, to: .bytes)
    let megas = DataAllocator.size(of: remainder, from: .bytes, to: .megabytes)
    remainder -= DataAllocator.size(of: megas, from: .megabytes, to: .bytes)
    let kilos = DataAllocator.size(of: remainder, from: .bytes, to: .kilobytes)
    infos = [("Total", total), ("GB", gigas), ("MB", megas), ("KB", kilos)]
    // refreshInfos()
}
  1. 申請(qǐng)?zhí)囟ù笮〉膬?nèi)存,然后用寫入文件,用FileManager存入sandbox中,已知UInt8大小為一字節(jié)...
    • 由于iOS設(shè)備內(nèi)存基本都在3G以下,還有很多1G,所以單次文件儲(chǔ)存控制在512M以下
    • 使用for語句需加上autoreleasepool,否則for語句內(nèi)不會(huì)自動(dòng)釋放內(nèi)存,內(nèi)存爆炸
struct DataAllocator {

    typealias Byte = [UInt8]

    private(set) var unit: Unit

    init(unit: Unit) {
        self.unit = unit
    }

    func allocateMemory(of size: Int) -> Byte {
        let buffer = Byte(repeating: 0,
                          count: DataAllocator.size(of: size,
                                                    from: unit,
                                                    to: .bytes))
        return buffer
    }

    static func allocateMemory(of size: Int, unit: Unit) -> Byte {
        let allocator = DataAllocator(unit: unit)
        return allocator.allocateMemory(of: size)
    }

    ...
}
static func fileStorage(data: [UInt8], namePrefix: String, nameSuffix: String) throws -> String {
    let content = Data(bytes: data, count: data.count)
    let fileManager = FileManager.default
    let documentDirectory = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor:nil, create:false)
    let fileName = namePrefix + "_filestorage_" + nameSuffix
    let fileURL = documentDirectory.appendingPathComponent(fileName)
    try content.write(to: fileURL)
    return fileName
}
private func fulfillStorage() {
    let gigas = infos[1].detail
    let megas = infos[2].detail
    let kilos = infos[3].detail
    let alertTitle = "Store"

    func fileStorage(unit: DataAllocator.Unit, size: Int, frenquency: Int) {
        do {
            try FileStorage.fileStorage(unit: unit, size: size, frequency: frenquency)
        } catch {
            print(error.localizedDescription)
        }
    }

    DispatchQueue.global().async {
        if gigas > 0 {
            for idx in 1...(gigas * 2) {
                autoreleasepool {
                    fileStorage(unit: .megabytes, size: 512, frenquency: idx)
                }
            }
        }
        if megas > 0 {
            for idx in 1...megas {
                autoreleasepool {
                    fileStorage(unit: .megabytes, size: 1, frenquency: idx)
                }
            }
        }
        if kilos > 0 {
            for idx in 1...kilos {
                autoreleasepool {
                    fileStorage(unit: .kilobytes, size: 1, frenquency: idx)
                }
            }
        }
    }
}

填滿iOS儲(chǔ)存空間app完整代碼

https://github.com/itsuhi-shu/FulfillStorage

遇到的問題

很顯然申請(qǐng)到的內(nèi)存存入文件后所占用的空間并不完全等于內(nèi)存大小...雖然能夠想到這問題,但是本菜鳥非科班出身無法搞清個(gè)所以然來,而且差別并不大,所以忽略不計(jì)

iOS13的UserDefaults儲(chǔ)存上限警告(4MB)

(雖然和以上內(nèi)容并無太大關(guān)聯(lián),但是正好同時(shí)發(fā)現(xiàn)所以一起貼出來)

iOS的UserDefaults理論上可以儲(chǔ)存和可用空間等大的內(nèi)容,然而從iOS13開始,UserDefaults使用超過4M后每次嘗試儲(chǔ)存都會(huì)出現(xiàn)警告

2019-11-14 13:40:35.383876+0900 TestUserDefaults[21230:7116172] [User Defaults] CFPrefsPlistSource<0x600002bc0580> (Domain: com.yifei-zhou.TestUserDefaults, User: kCFPreferencesCurrentUser, ByHost: No, Container: (null), Contents Need Refresh: Yes): Attempting to store >= 4194304 bytes of data in CFPreferences/NSUserDefaults on this platform is invalid. This is a bug in TestUserDefaults or a library it uses

2019-11-14 13:40:35.384214+0900 TestUserDefaults[21230:7116172] [User Defaults] CFPrefsPlistSource<0x600002bc0580> (Domain: com.yifei-zhou.TestUserDefaults, User: kCFPreferencesCurrentUser, ByHost: No, Container: (null), Contents Need Refresh: No): Transitioning into direct mode

嘛,一般也不會(huì)往UserDefaults里存那么多東西就是了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容