ObjC / Cocoa class para converter tamanho em string legível por humanos?

Existe uma maneira simples de fazer algo como …

[NSMagicDataConverter humanStringWithBytes:20000000] 

..que retornaria “19.1MB”?

Aqui está minha opinião sobre o 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 padrão (se 0 for passado para flags ), irá gerar unidades SI (base dez). Você pode definir kUnitStringBinaryUnits para selecionar unidades binárias (base dois) adequadas para a memory, ou kUnitStringOSNativeUnits para ter o tipo de unidade selecionado automaticamente com base na versão do sistema operacional (o pré-Leopard obtém a base dois, o pós-Leopard obtém a base dez). Definir kUnitStringLocalizedFormat formata a string com base na localidade atual do usuário. Por exemplo:

 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) 

Aqui está a function auxiliar necessária para unidades nativas do sistema operacional:

 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 do OS X 10.8 e do iOS 6, você pode usar o NSByteCountFormatter .

Seu exemplo ficaria assim:

 [NSByteCountFormatter stringFromByteCount:20000000 countStyle:NSByteCountFormatterCountStyleFile]; 

Eu iria mush isso em uma subclass 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]); } 

Aqui está uma function mais Objective C-like (usa NSNumber, NSArray, NSStirng, etc …) para fazer esta conversão.

Isto é baseado na resposta de Sidnicious, então um grande obrigado pelo trabalho inicial feito lá. Também baseado em artigos da Wikipédia.

Use-o geralmente assim: [HumanReadableDataSizeHelper humanReadableSizeFromBytes:[NSNumber numberWithDouble:doubleValue]] .

Mas, parece que você quer unidades SI com um multiplicador de 1024 para que você possa usá-lo assim: [HumanReadableDataSizeHelper humanReadableSizeFromBytes:[NSNumber numberWithDouble:doubleValue] useSiPrefixes:YES useSiMultiplier:NO]

A razão pela qual eu padrão para prefixos binários (ki, Mi) é porque esses parecem ser o conjunto de prefixo de unidade mais apropriado para usar para tamanhos de dados em um computador. O que você pediu foram os prefixos da unidade SI, mas usando um multiplicador de 1024, tecnicamente incorreto. Embora eu note que prefixos SI para múltiplos de 1024 são bastante comuns e prefixos binários não são bem aceitos (de acordo com a 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 

Você pode usar o FormatterKit e sua class TTTUnitOfInformationFormatter :

https://github.com/mattt/FormatterKit

Também está disponível através do CocoaPods com:

 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]]; } 

Eu sei que as perguntas são para Obj C, mas se alguém está procurando uma versão 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])" }