¿Clase ObjC / Cocoa para convertir el tamaño a una secuencia legible por humanos?

¿Hay una manera simple de hacer algo como …

[NSMagicDataConverter humanStringWithBytes:20000000] 

..que devolvería “19.1MB”?

Aquí está mi propia opinión sobre el problema:

 enum { kUnitStringBinaryUnits = 1 < < 0, kUnitStringOSNativeUnits = 1 << 1, kUnitStringLocalizedFormat = 1 << 2 }; NSString* unitStringFromBytes(double bytes, uint8_t flags){ static const char units[] = { '\0', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' }; static int maxUnits = sizeof units - 1; int multiplier = (flags & kUnitStringOSNativeUnits && !leopardOrGreater() || flags & kUnitStringBinaryUnits) ? 1024 : 1000; int exponent = 0; while (bytes >= multiplier && exponent < maxUnits) { bytes /= multiplier; exponent++; } NSNumberFormatter* formatter = [[[NSNumberFormatter alloc] init] autorelease]; [formatter setMaximumFractionDigits:2]; if (flags & kUnitStringLocalizedFormat) { [formatter setNumberStyle: NSNumberFormatterDecimalStyle]; } // Beware of reusing this format string. -[NSString stringWithFormat] ignores \0, *printf does not. return [NSString stringWithFormat:@"%@ %cB", [formatter stringFromNumber: [NSNumber numberWithDouble: bytes]], units[exponent]]; } 

Por defecto (si se pasa 0 para flags ), se generarán unidades SI (base diez). Puede configurar kUnitStringBinaryUnits para seleccionar unidades binarias (base dos) adecuadas para la memoria, o kUnitStringOSNativeUnits para que el tipo de unidad se seleccione automáticamente en función de la versión del sistema operativo (pre-Leopard obtiene la base dos, después-Leopard obtiene la base diez). El establecimiento de kUnitStringLocalizedFormat formatea la cadena en función de la configuración regional actual del usuario. Por ejemplo:

 unitStringFromBytes(1073741824, 0); // → "1.07 GB" unitStringFromBytes(1073741824, kUnitStringBinaryUnits); // → "1 GB" unitStringFromBytes(1073741824, kUnitStringOSNativeUnits | kUnitStringLocalizedFormat); // → "1.07 GB" (In Mac OS 10.6) unitStringFromBytes(12345678901234567890123456789, kUnitStringOSNativeUnits | kUnitStringLocalizedFormat); // → "12,345.68 YB" (In Mac OS 10.6, in the US) unitStringFromBytes(12345678901234567890123456789, kUnitStringOSNativeUnits | kUnitStringLocalizedFormat); // → "12.345,68 YB" (In Mac OS 10.6, in Spain) 

Aquí está la función de ayuda necesaria para las unidades nativas del sistema operativo:

 BOOL leopardOrGreater(){ static BOOL alreadyComputedOS = NO; static BOOL leopardOrGreater = NO; if (!alreadyComputedOS) { SInt32 majorVersion, minorVersion; Gestalt(gestaltSystemVersionMajor, &majorVersion); Gestalt(gestaltSystemVersionMinor, &minorVersion); leopardOrGreater = ((majorVersion == 10 && minorVersion >= 5) || majorVersion > 10); alreadyComputedOS = YES; } return leopardOrGreater; } 

A partir de OS X 10.8 e iOS 6, puede usar NSByteCountFormatter .

Tu ejemplo sería este:

 [NSByteCountFormatter stringFromByteCount:20000000 countStyle:NSByteCountFormatterCountStyleFile]; 

Me gustaría meter esto en una subclase NSFormatter.

 #import  @interface SOFileSizeFormatter : NSNumberFormatter { @private BOOL useBaseTenUnits; } /** Flag signaling whether to calculate file size in binary units (1024) or base ten units (1000). Default is binary units. */ @property (nonatomic, readwrite, assign, getter=isUsingBaseTenUnits) BOOL useBaseTenUnits; @end static const char sUnits[] = { '\0', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' }; static int sMaxUnits = sizeof sUnits - 1; @implementation SOFileSizeFormatter @synthesize useBaseTenUnits; - (NSString *) stringFromNumber:(NSNumber *)number { int multiplier = useBaseTenUnits ? 1000 : 1024; int exponent = 0; double bytes = [number doubleValue]; while ((bytes >= multiplier) && (exponent < sMaxUnits)) { bytes /= multiplier; exponent++; } return [NSString stringWithFormat:@"%@ %cB", [super stringFromNumber: [NSNumber numberWithDouble: bytes]], sUnits[exponent]]; } @end 

Uso:

 NSString *path = ...; // path to a file of 1,500,000 bytes NSString *sizeString = nil; NSNumber *sizeAttrib = [[[NSFileManager defaultManager] attributesOfItemAtPath:path error:NULL]objectForKey:NSFileSize]; SOFileSizeFormatter *sizeFormatter = [[[SOFileSizeFormatter alloc] init] autorelease]; [sizeFormatter setMaximumFractionDigits:2]; sizeString = [sizeFormatter stringFromNumber:sizeAttrib]; // sizeString ==> @"1.43 MB" [sizeFormatter setUseBaseTenUnits:YES]; sizeString = [sizeFormatter stringFromNumber:sizeAttrib]; // sizeString ==> @"1.5 MB" 
 NSString *stringFromFileSize(NSInteger theSize) { /* From http://snippets.dzone.com/posts/show/3038 with slight modification */ float floatSize = theSize; if (theSize<1023) return([NSString stringWithFormat:@"%i bytes",theSize]); floatSize = floatSize / 1024; if (floatSize<1023) return([NSString stringWithFormat:@"%1.1f KB",floatSize]); floatSize = floatSize / 1024; if (floatSize<1023) return([NSString stringWithFormat:@"%1.1f MB",floatSize]); floatSize = floatSize / 1024; return([NSString stringWithFormat:@"%1.1f GB",floatSize]); } 

Aquí hay una función similar a Objective C (usa NSNumber, NSArray, NSStirng, etc …) para hacer esta conversión.

Esto se basa en la respuesta de Sidnicious, así que muchas gracias por el trabajo inicial realizado allí. También basado en artículos de Wikipedia.

Úselo generalmente así: [HumanReadableDataSizeHelper humanReadableSizeFromBytes:[NSNumber numberWithDouble:doubleValue]] .

Pero, parece que quiere unidades SI con un multiplicador 1024, así que lo usaría así: [HumanReadableDataSizeHelper humanReadableSizeFromBytes:[NSNumber numberWithDouble:doubleValue] useSiPrefixes:YES useSiMultiplier:NO]

La razón por la que prefijo a los prefijos binarios (ki, Mi) es porque esos parecen ser el prefijo de unidad más apropiado configurado para usar para tamaños de datos en una computadora. Lo que solicitó fueron los prefijos de la unidad SI pero usando un multiplicador de 1024, técnicamente incorrecto. Aunque señalaré que los prefijos SI para múltiplos de 1024 son bastante comunes y los prefijos binarios no son bien aceptados (según Wikipedia).

HumanReadableDataSizeHelper.h

 @interface HumanReadableDataSizeHelper : NSObject /** @brief Produces a string containing the largest appropriate units and the new fractional value. @param sizeInBytes The value to convert in bytes. This function converts the bytes value to a value in the greatest units that produces a value >= 1 and returns the new value and units as a string. The magnitude multiplier used is 1024 and the prefixes used are the binary prefixes (ki, Mi, ...). */ + (NSString *)humanReadableSizeFromBytes:(NSNumber *)sizeInBytes; /** @brief Produces a string containing the largest appropriate units and the new fractional value. @param sizeInBytes The value to convert in bytes. @param useSiPrefixes Controls what prefix-set is used. @param useSiMultiplier Controls what magnitude multiplier is used. This function converts the bytes value to a value in the greatest units that produces a value >= 1 and returns the new value and units as a string. When useSiPrefixes is true, the prefixes used are the SI unit prefixes (k, M, ...). When useSiPrefixes is false, the prefixes used are the binary prefixes (ki, Mi, ...). When useSiMultiplier is true, the magnitude multiplier used is 1000 When useSiMultiplier is false, the magnitude multiplier used is 1024. */ + (NSString *)humanReadableSizeFromBytes:(NSNumber *)sizeInBytes useSiPrefixes:(BOOL)useSiPrefixes useSiMultiplier:(BOOL)useSiMultiplier; @end 

HumanReadableDataSizeHelper.m

 @implementation HumanReadableDataSizeHelper + (NSString *)humanReadableSizeFromBytes:(NSNumber *)sizeInBytes { return [self humanReadableSizeFromBytes:sizeInBytes useSiPrefixes:NO useSiMultiplier:NO]; } + (NSString *)humanReadableSizeFromBytes:(NSNumber *)sizeInBytes useSiPrefixes:(BOOL)useSiPrefixes useSiMultiplier:(BOOL)useSiMultiplier { NSString *unitSymbol = @"B"; NSInteger multiplier; NSArray *prefixes; if (useSiPrefixes) { /* SI prefixes http://en.wikipedia.org/wiki/Kilo- kilobyte (kB) 10^3 megabyte (MB) 10^6 gigabyte (GB) 10^9 terabyte (TB) 10^12 petabyte (PB) 10^15 exabyte (EB) 10^18 zettabyte (ZB) 10^21 yottabyte (YB) 10^24 */ prefixes = [NSArray arrayWithObjects: @"", @"k", @"M", @"G", @"T", @"P", @"E", @"Z", @"Y", nil]; } else { /* Binary prefixes http://en.wikipedia.org/wiki/Binary_prefix kibibyte (KiB) 2^10 = 1.024 * 10^3 mebibyte (MiB) 2^20 ≈ 1.049 * 10^6 gibibyte (GiB) 2^30 ≈ 1.074 * 10^9 tebibyte (TiB) 2^40 ≈ 1.100 * 10^12 pebibyte (PiB) 2^50 ≈ 1.126 * 10^15 exbibyte (EiB) 2^60 ≈ 1.153 * 10^18 zebibyte (ZiB) 2^70 ≈ 1.181 * 10^21 yobibyte (YiB) 2^80 ≈ 1.209 * 10^24 */ prefixes = [NSArray arrayWithObjects: @"", @"ki", @"Mi", @"Gi", @"Ti", @"Pi", @"Ei", @"Zi", @"Yi", nil]; } if (useSiMultiplier) { multiplier = 1000; } else { multiplier = 1024; } NSInteger exponent = 0; double size = [sizeInBytes doubleValue]; while ( (size >= multiplier) && (exponent < [prefixes count]) ) { size /= multiplier; exponent++; } NSNumberFormatter* formatter = [[[NSNumberFormatter alloc] init] autorelease]; [formatter setMaximumFractionDigits:2]; [formatter setNumberStyle:NSNumberFormatterDecimalStyle]; // Uses localized number formats. NSString *sizeInUnits = [formatter stringFromNumber:[NSNumber numberWithDouble:size]]; return [NSString stringWithFormat:@"%@ %@%@", sizeInUnits, [prefixes objectAtIndex:exponent], unitSymbol]; } @end 

Puede usar FormatterKit y su clase TTTUnitOfInformationFormatter :

https://github.com/mattt/FormatterKit

También está disponible a través de CocoaPods con:

 pod 'FormatterKit', '~> 1.1.1' 
 - (id)transformedValue:(id)value { double convertedValue = [value doubleValue]; int multiplyFactor = 0; NSArray *tokens = @[@"bytes",@"KB",@"MB",@"GB",@"TB"]; while (convertedValue > 1024) { convertedValue /= 1024; multiplyFactor++; } return [NSString stringWithFormat:@"%4.2f %@",convertedValue, tokens[multiplyFactor]]; } 

Sé que las preguntas son para Obj C pero si alguien está buscando una versión rápida:

  public static func fileSizeDisplay(fromBytes:Int) -> String { let display = ["bytes","KB","MB","GB","TB","PB"] var value:Double = Double(fromBytes) var type = 0 while (value > 1024){ value /= 1024 type = type + 1 } return "\(String(format:"%g", value)) \(display[type])" }