How can I calculate the size of a folder?

IosIphoneNsfilemanager

Ios Problem Overview


I'm creating a folder to cache images inside Documents with my iPhone App. I want to be able to keep the size of this folder down to 1MB, so I need to to check the size in bytes of my folder.

I have code to calculate the size of file, but I need the size of the folder.

What would be the best way to do this?

Ios Solutions


Solution 1 - Ios

tl;dr

All the other answers are off :)

Problem

I'd like to add my two cents to this old question as there seem to be many answers that are all very similar but yield results that are in some cases very unprecise.

To understand why we first have to define what the size of a folder is. In my understanding (and probably the one of the OP) it is the amount of bytes that the directory including all of its contents uses on the volume. Or, put in another way:

It is the space becoming available if the directory would be completely removed.

I'm aware that this definition is not the only valid way to interpret the question but I do think it's what most use cases boil down to.

Error

The existing answers all take a very simple approach: Traverse the directory contents, adding up the sizes of (regular) files. This does not take a couple of subtleties into account.

  • The space used on the volume increments in blocks, not in bytes. Even a one byte file uses at least one block.
  • Files carry around meta data (like any number of extended attributes). This data must go somewhere.
  • HFS deploys file system compression to actually store the file using less bytes then its real length.
Solution

All of these reasons make the existing answers produce unprecise results. So I'm proposing this extension on NSFileManager (code on github due to length: Swift 4, Objective C) to remedy the problem. It's also quite a bit faster, especially with directories containing a lot of files.

The core of the solution is to use NSURL's NSURLTotalFileAllocatedSizeKey or NSURLFileAllocatedSizeKey properies to retrieve file sizes.

Test

I've also set up a simple iOS test project, demonstrating the differences between the solutions. It shows how utterly wrong the results can be in some scenarios.

In the test I create a directory containing 100 small files (ranging from 0 to 800 bytes). The folderSize: method copied from some other answer calculates a total of 21 kB while my allocatedSize method yields 401 kB.

Proof

I made sure that the results of allocatedSize are closer to the correct value by calculating the difference of the available bytes on the volume before and after deleting the test directory. In my tests the difference was always exactly equal to the result of allocatedSize.

Please see Rob Napier's comment to understand that there's still room for improvement.

Performance

But there's another advantage: When calculating the size of a directory with 1000 files, on my iPhone 6 the folderSize: method takes about 250 ms while allocatedSize traverses the same hierarchy in 35 ms.

This is probably due to using NSFileManager's new(ish) enumeratorAtURL:includingPropertiesForKeys:options:errorHandler: API to traverse the hierachy. This method let's you specify prefetched properties for the items to be iterated, resulting in less io.

Results
Test `folderSize` (100 test files)
	size: 21 KB (21.368 bytes)
	time: 0.055 s
	actual bytes: 401 KB (401.408 bytes)

Test `allocatedSize` (100 test files)
	size: 401 KB (401.408 bytes)
	time: 0.048 s
	actual bytes: 401 KB (401.408 bytes)

Test `folderSize` (1000 test files)
    size: 2 MB (2.013.068 bytes)
    time: 0.263 s
    actual bytes: 4,1 MB (4.087.808 bytes)

Test `allocatedSize` (1000 test files)
    size: 4,1 MB (4.087.808 bytes)
    time: 0.034 s
    actual bytes: 4,1 MB (4.087.808 bytes)

Solution 2 - Ios

Cheers for that Alex, you helped a lot, have now written the following function which does the trick...

- (unsigned long long int)folderSize:(NSString *)folderPath {
	NSArray *filesArray = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:folderPath error:nil];
	NSEnumerator *filesEnumerator = [filesArray objectEnumerator];
	NSString *fileName;
	unsigned long long int fileSize = 0;

	while (fileName = [filesEnumerator nextObject]) {
		NSDictionary *fileDictionary = [[NSFileManager defaultManager] fileAttributesAtPath:[folderPath stringByAppendingPathComponent:fileName] traverseLink:YES];
		fileSize += [fileDictionary fileSize];
	}

	return fileSize;
}

It is coming up with the exact number of bytes as Finder does.

As an aside, Finder returns two numbers. One is the size on the disk and the other is the actual number of bytes.

For example, when I run this code on one of my folders, it comes back in the code with a 'fileSize' of 130398. When I check in Finder, it says the size is 201KB on disk (130,398 bytes).

Am a little unsure of what to go with here (201KB or 130,398 bytes) as the actual size. For now, I'll go on the safe side and cut my limit in half until I find out what this means exactly...

If anyone can add any more information to these differing numbers I'd appreciate it.

Cheers,

Solution 3 - Ios

This is how to get folder and file size in MB, KB and GB ---

1. Folder Size -

-(NSString *)sizeOfFolder:(NSString *)folderPath
{
    NSArray *contents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:folderPath error:nil];
    NSEnumerator *contentsEnumurator = [contents objectEnumerator];

    NSString *file;
    unsigned long long int folderSize = 0;

    while (file = [contentsEnumurator nextObject]) {
        NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[folderPath stringByAppendingPathComponent:file] error:nil];
        folderSize += [[fileAttributes objectForKey:NSFileSize] intValue];
    }

    //This line will give you formatted size from bytes ....
    NSString *folderSizeStr = [NSByteCountFormatter stringFromByteCount:folderSize countStyle:NSByteCountFormatterCountStyleFile];
    return folderSizeStr;
}

Note: In case of sub folders please use subpathsOfDirectoryAtPath: instead of contentsOfDirectoryAtPath:

2. File Size -

-(NSString *)sizeOfFile:(NSString *)filePath
{
    NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
    NSInteger fileSize = [[fileAttributes objectForKey:NSFileSize] integerValue];
    NSString *fileSizeStr = [NSByteCountFormatter stringFromByteCount:fileSize countStyle:NSByteCountFormatterCountStyleFile];
    return fileSizeStr;
}

---------- Swift 4.0 ----------

1. Folder Size -

func sizeOfFolder(_ folderPath: String) -> String? {
    do {
        let contents = try FileManager.default.contentsOfDirectory(atPath: folderPath)
        var folderSize: Int64 = 0
        for content in contents {
            do {
                let fullContentPath = folderPath + "/" + content
                let fileAttributes = try FileManager.default.attributesOfItem(atPath: fullContentPath)
                folderSize += fileAttributes[FileAttributeKey.size] as? Int64 ?? 0
            } catch _ {
                continue
            }
        }
        
        /// This line will give you formatted size from bytes ....
        let fileSizeStr = ByteCountFormatter.string(fromByteCount: folderSize, countStyle: ByteCountFormatter.CountStyle.file)
        return fileSizeStr
        
    } catch let error {
        print(error.localizedDescription)
        return nil
    }
}

2. File Size -

func sizeOfFile(_ filePath: String) -> String? {
    do {
        let fileAttributes = try FileManager.default.attributesOfItem(atPath: filePath)
        let folderSize = fileAttributes[FileAttributeKey.size] as? Int64 ?? 0
        let fileSizeStr = ByteCountFormatter.string(fromByteCount: folderSize, countStyle: ByteCountFormatter.CountStyle.file)
        return fileSizeStr
    } catch {
        print(error)
    }
    return nil
}

Solution 4 - Ios

In iOS 5 the method -filesAttributesAtPath: is deprecated. Here is the version of the first code posted with the new method:

- (unsigned long long int)folderSize:(NSString *)folderPath {
    NSArray *filesArray = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:folderPath error:nil];
    NSEnumerator *filesEnumerator = [filesArray objectEnumerator];
    NSString *fileName;
    unsigned long long int fileSize = 0;
    
    while (fileName = [filesEnumerator nextObject]) {
        NSDictionary *fileDictionary = [[NSFileManager defaultManager] attributesOfItemAtPath:[folderPath stringByAppendingPathComponent:fileName] error:nil];
        fileSize += [fileDictionary fileSize];
    }
    
    return fileSize;
}

Solution 5 - Ios

Something like the following should help get you started. You'll need to modify _documentsDirectory to your specific folder, though:

- (unsigned long long int) documentsFolderSize {
    NSFileManager *_manager = [NSFileManager defaultManager];
    NSArray *_documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *_documentsDirectory = [_documentPaths objectAtIndex:0];	
    NSArray *_documentsFileList;
    NSEnumerator *_documentsEnumerator;
    NSString *_documentFilePath;
    unsigned long long int _documentsFolderSize = 0;

    _documentsFileList = [_manager subpathsAtPath:_documentsDirectory];
    _documentsEnumerator = [_documentsFileList objectEnumerator];
    while (_documentFilePath = [_documentsEnumerator nextObject]) {
        NSDictionary *_documentFileAttributes = [_manager fileAttributesAtPath:[_documentsDirectory stringByAppendingPathComponent:_documentFilePath] traverseLink:YES];
        _documentsFolderSize += [_documentFileAttributes fileSize];
    }

    return _documentsFolderSize;
}

Solution 6 - Ios

I used this code to get the directory size of 2 directories, if one directory didnt exist, it would show Zero KB. Otherwise, the second half of the code will display the folder size along with the KB, MB, GB, respectively, and it will also display it in a clean format: 10.02 MB.

Try this something like this:

- (unsigned long long int)folderSize:(NSString *)folderPath {
	NSArray *filesArray = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:folderPath error:nil];
	NSEnumerator *filesEnumerator = [filesArray objectEnumerator];
	NSString *fileName;
	unsigned long long int fileSize = 0;
	
	while (fileName = [filesEnumerator nextObject]) {
		NSDictionary *fileDictionary = [[NSFileManager defaultManager] fileAttributesAtPath:[folderPath stringByAppendingPathComponent:fileName] traverseLink:YES];
		fileSize += [fileDictionary fileSize];
	} 
	
	return fileSize;
}

-(NSString *)getMPSize
{
	NSString*sizeTypeW = @"bytes";
	int app = [self folderSize:@"/PathToTheFolderYouWantTheSizeOf/"];
	NSFileManager *manager = [NSFileManager defaultManager];
	if([manager fileExistsAtPath:@"/AnotherFolder/"] == YES){
		
		int working = [self folderSize:@"/AnotherFolder/"];
		if(working<1){
			return @"Size: Zero KB";
		}else{
			if (working > 1024)
			{
				//Kilobytes
				working = working / 1024;
				
				sizeTypeW = @" KB";
			}
			
			if (working > 1024)
			{
				//Megabytes
				working = working / 1024;
				
				sizeTypeW = @" MB";
			}
			
			if (working > 1024)
			{
				//Gigabytes
				working = working / 1024;
				
				sizeTypeW = @" GB";
			}
			
			return [NSString stringWithFormat:@"App: %i MB, Working: %i %@ ",app/1024/1024, working,sizeTypeW];
		}
		
	}else{
		return [NSString stringWithFormat:@"App: %i MB, Working: Zero KB",app/1024/1024];
	}
	[manager release];
}

Solution 7 - Ios

Here's a swift 2.1/2.2 answer using extensions and building off of Rok's answer:

extension NSFileManager {
    func fileSizeAtPath(path: String) -> Int64 {
        do {
            let fileAttributes = try attributesOfItemAtPath(path)
            let fileSizeNumber = fileAttributes[NSFileSize]
            let fileSize = fileSizeNumber?.longLongValue
            return fileSize!
        } catch {
            print("error reading filesize, NSFileManager extension fileSizeAtPath")
            return 0
        }
    }
    
    func folderSizeAtPath(path: String) -> Int64 {
        var size : Int64 = 0
        do {
            let files = try subpathsOfDirectoryAtPath(path)
            for i in 0 ..< files.count {
                size += fileSizeAtPath((path as NSString).stringByAppendingPathComponent(files[i]) as String)
            }
        } catch {
            print("error reading directory, NSFileManager extension folderSizeAtPath")
        }
        return size
    }

    func format(size: Int64) -> String {
       let folderSizeStr = NSByteCountFormatter.stringFromByteCount(size, countStyle: NSByteCountFormatterCountStyle.File)
       return folderSizeStr
    }

}

Usage example:

let fileManager = NSFileManager.defaultManager()
let documentsDirPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
let dirSize: String = fileManager.format(fileManager.folderSizeAtPath(documentsDirPath))

Solution 8 - Ios

Updated Method using enumeration block

Calculate Folder Size with only files

- (NSString *)sizeOfFolder:(NSString *)folderPath {
    NSArray *folderContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:folderPath error:nil];
    __block unsigned long long int folderSize = 0;
    
    [folderContents enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[folderPath stringByAppendingPathComponent:obj] error:nil];
        folderSize += [[fileAttributes objectForKey:NSFileSize] intValue];
    }];
    NSString *folderSizeStr = [NSByteCountFormatter stringFromByteCount:folderSize countStyle:NSByteCountFormatterCountStyleFile];
    return folderSizeStr;
}

Calculate Folder Size with other sub directories in the folder

 NSArray *folderContents = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:folderPath error:nil];

Get File Size

- (NSString *)sizeOfFile:(NSString *)filePath {
    NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
    NSInteger fileSize = [[fileAttributes objectForKey:NSFileSize] integerValue];
    NSString *fileSizeString = [NSByteCountFormatter stringFromByteCount:fileSize countStyle:NSByteCountFormatterCountStyleFile];
    return fileSizeString;
}

Solution 9 - Ios

Here is the Swift 3 equivalent of a FileManager extension based off of @vitalii extension:

extension FileManager {

func fileSizeAtPath(path: String) -> Int64 {
    do {
        let fileAttributes = try attributesOfItem(atPath: path)
        let fileSizeNumber = fileAttributes[FileAttributeKey.size] as? NSNumber
        let fileSize = fileSizeNumber?.int64Value
        return fileSize!
    } catch {
        print("error reading filesize, NSFileManager extension fileSizeAtPath")
        return 0
    }
}

func folderSizeAtPath(path: String) -> Int64 {
    var size : Int64 = 0
    do {
        let files = try subpathsOfDirectory(atPath: path)
        for i in 0 ..< files.count {
            size += fileSizeAtPath(path:path.appending("/"+files[i]))
        }
    } catch {
        print("error reading directory, NSFileManager extension folderSizeAtPath")
    }
    return size
}

func format(size: Int64) -> String {
    let folderSizeStr = ByteCountFormatter.string(fromByteCount: size, countStyle: ByteCountFormatter.CountStyle.file)
    return folderSizeStr
}}

Solution 10 - Ios

I think use Unix C method is better for performance.

+ (long long) folderSizeAtPath: (const char*)folderPath {
  long long folderSize = 0;
  DIR* dir = opendir(folderPath);
  if (dir == NULL) return 0;
  struct dirent* child;
  while ((child = readdir(dir))!=NULL) {
    if (child->d_type == DT_DIR
        && child->d_name[0] == '.'
        && (child->d_name[1] == 0 // ignore .
            ||
            (child->d_name[1] == '.' && child->d_name[2] == 0) // ignore dir ..
           ))
      continue;
    
    int folderPathLength = strlen(folderPath);
    char childPath[1024]; // child 
    stpcpy(childPath, folderPath);
    if (folderPath[folderPathLength-1] != '/'){
      childPath[folderPathLength] = '/';
      folderPathLength++;
    }
    stpcpy(childPath+folderPathLength, child->d_name);
    childPath[folderPathLength + child->d_namlen] = 0;
    if (child->d_type == DT_DIR){ // directory
      folderSize += [self _folderSizeAtPath:childPath]; // 
      // add folder size
      struct stat st;
      if (lstat(childPath, &st) == 0)
        folderSize += st.st_size;
    } else if (child->d_type == DT_REG || child->d_type == DT_LNK){ // file or link
      struct stat st;
      if (lstat(childPath, &st) == 0)
        folderSize += st.st_size;
    }
  }
  return folderSize;
}

Solution 11 - Ios

if we want to get the size of any file then here is a method, where we only need to pass path of that file.

- (unsigned long long int) fileSizeAt:(NSString *)path {
    NSFileManager *_manager = [NSFileManager defaultManager];
    return [[_manager fileAttributesAtPath:path traverseLink:YES] fileSize];
}

Solution 12 - Ios

I cleaned up a bit the first answer's implementation before using it, so it no longer throws deprecated warnings + using fast enumeration.

/**
 *	Calculates the size of a folder.
 *
 *	@param	folderPath	The path of the folder
 *
 *	@return	folder size in bytes
 */
- (unsigned long long int)folderSize:(NSString *)folderPath {
    NSFileManager *fm = [NSFileManager defaultManager];
    NSArray *filesArray = [fm subpathsOfDirectoryAtPath:folderPath error:nil];
    unsigned long long int fileSize = 0;
    
    NSError *error;
    for(NSString *fileName in filesArray) {
        error = nil;
        NSDictionary *fileDictionary = [fm attributesOfItemAtPath:[folderPath     stringByAppendingPathComponent:fileName] error:&error];
        if (!error) {
            fileSize += [fileDictionary fileSize];
        }else{
            NSLog(@"ERROR: %@", error);
        }
    }

    return fileSize;
}

Solution 13 - Ios

Swift Implementation

class func folderSize(folderPath:String) -> UInt{
    
    // @see http://stackoverflow.com/questions/2188469/calculate-the-size-of-a-folder
    
    let filesArray:[String] = NSFileManager.defaultManager().subpathsOfDirectoryAtPath(folderPath, error: nil)! as [String]
    var fileSize:UInt = 0
    
    for fileName in filesArray{
        let filePath = folderPath.stringByAppendingPathComponent(fileName)
        let fileDictionary:NSDictionary = NSFileManager.defaultManager().attributesOfItemAtPath(filePath, error: nil)!
        fileSize += UInt(fileDictionary.fileSize())

    }
    
    return fileSize
}

Solution 14 - Ios

Not sure if this helps anyone, but I wanted to relate some of my findings (some inspired by @zneak's comment above).

  1. I could not find any shortcuts using NSDirectoryEnumerator to avoid enumerating through files to get the total contained size of a directory.

  2. For my tests, using -[NSFileManager subpathsOfDirectoryAtPath:path error:nil] was faster than using -[NSFileManager enumeratorAtPath:path]. This looks to me like it might be a classic time/space tradeoff, as subPaths... creates an NSArray on which it then iterates, where enumerator... might not.

Some background on #1. Assuming:

NSFileManager *fileMan = [NSFileManager defaultManager];
NSString *dirPath = @"/"; // references some directory

Then

[fileMan enumeratorAtPath:dirPath] fileAttributes]

returns nil. The correct attribute accessor is directoryAttributes, but

[fileMan enumeratorAtPath:dirPath] directoryAttributes] fileSize]

returns the size of the directory information, not the recursive sum of the sizes of all contained files (a lá ⌘-I in Finder).

Solution 15 - Ios

I've created a simple NSFileManager extension:

extension NSFileManager {
  func fileSizeAtPath(path: String) -> Int {
    return attributesOfItemAtPath(path, error: nil)?[NSFileSize] as? Int ?? 0
  }

  func folderSizeAtPath(path: String) -> Int {
    var size = 0
    for file in subpathsOfDirectoryAtPath(path, error: nil) as? [String] ?? [] {
      size += fileSizeAtPath(path.stringByAppendingPathComponent(file))
    }
    return size
  }
}

You can get the file size:

NSFileManager.defaultManager().fileSizeAtPath("file path")

and the folder size:

NSFileManager.defaultManager().folderSizeAtPath("folder path")

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionjoseph_carneyView Question on Stackoverflow
Solution 1 - IosNikolai RuheView Answer on Stackoverflow
Solution 2 - Iosjoseph_carneyView Answer on Stackoverflow
Solution 3 - IosTheTigerView Answer on Stackoverflow
Solution 4 - IosndocView Answer on Stackoverflow
Solution 5 - IosAlex ReynoldsView Answer on Stackoverflow
Solution 6 - IosWrightsCSView Answer on Stackoverflow
Solution 7 - IosKrtkoView Answer on Stackoverflow
Solution 8 - IosicodebusterView Answer on Stackoverflow
Solution 9 - IosLarry MickieView Answer on Stackoverflow
Solution 10 - IosZYiOSView Answer on Stackoverflow
Solution 11 - IosPrakash RajView Answer on Stackoverflow
Solution 12 - IosStotoView Answer on Stackoverflow
Solution 13 - IosLuyu ZhangView Answer on Stackoverflow
Solution 14 - IosClay BridgesView Answer on Stackoverflow
Solution 15 - IosRok GregoričView Answer on Stackoverflow