
做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
- 利用
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)")
}
- 把字節(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()
}
- 申請(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里存那么多東西就是了