164
votes

I'm working on an ios game that's targeting as a minimum the 3gs. We are using HD assets for retina display devices (iphone 4, ipod touch 4th gen).

Memory wise, Ipod Touch 4th gen seems to be the most constraint device for us since it has the same amount of RAM (256 compared to Iphone 4's 512) as 3gs but we're using HD assets on it. The app used to crash when trying to load 100-110mb of ram but now that we're down to 70MB, we've never had loading crash.

After lots of searching around, there seems to be no official hard limit so how should we go about knowing what memory budget to use to be safe? We want to be able to give the artists a budget they can use without memory worries for each map.

10
Not sure how this question can be a duplicate of something which was asked at a later point of time.Jasper

10 Answers

45
votes

I think you've answered your own question: try not to go beyond the 70 Mb limit, however it really depends on many things: what iOS version you're using (not SDK), how many applications running in background, what exact memory you're using etc.

Just avoid the instant memory splashes (e.g. you're using 40 Mb of RAM, and then allocating 80 Mb's more for some short computation). In this case iOS would kill your application immediately.

You should also consider lazy loading of assets (load them only when you really need and not beforehand).

454
votes

Results of testing with the utility Split wrote (link is in his answer):

device: (crash amount/total amount/percentage of total)

  • iPad1: 127MB/256MB/49%
  • iPad2: 275MB/512MB/53%
  • iPad3: 645MB/1024MB/62%
  • iPad4: 585MB/1024MB/57% (iOS 8.1)
  • iPad Mini 1st Generation: 297MB/512MB/58%
  • iPad Mini retina: 696MB/1024MB/68% (iOS 7.1)
  • iPad Air: 697MB/1024MB/68%
  • iPad Air 2: 1383MB/2048MB/68% (iOS 10.2.1)
  • iPad Pro 9.7": 1395MB/1971MB/71% (iOS 10.0.2 (14A456))
  • iPad Pro 10.5”: 3057/4000/76% (iOS 11 beta4)
  • iPad Pro 12.9” (2015): 3058/3999/76% (iOS 11.2.1)
  • iPad Pro 12.9” (2017): 3057/3974/77% (iOS 11 beta4)
  • iPad Pro 11.0” (2018): 2858/3769/76% (iOS 12.1)
  • iPad Pro 12.9” (2018, 1TB): 4598/5650/81% (iOS 12.1)
  • iPad 10.2: 1844/2998/62% (iOS 13.2.3)
  • iPod touch 4th gen: 130MB/256MB/51% (iOS 6.1.1)
  • iPod touch 5th gen: 286MB/512MB/56% (iOS 7.0)
  • iPhone4: 325MB/512MB/63%
  • iPhone4s: 286MB/512MB/56%
  • iPhone5: 645MB/1024MB/62%
  • iPhone5s: 646MB/1024MB/63%
  • iPhone6: 645MB/1024MB/62% (iOS 8.x)
  • iPhone6+: 645MB/1024MB/62% (iOS 8.x)
  • iPhone6s: 1396MB/2048MB/68% (iOS 9.2)
  • iPhone6s+: 1392MB/2048MB/68% (iOS 10.2.1)
  • iPhoneSE: 1395MB/2048MB/69% (iOS 9.3)
  • iPhone7: 1395/2048MB/68% (iOS 10.2)
  • iPhone7+: 2040MB/3072MB/66% (iOS 10.2.1)
  • iPhone8: 1364/1990MB/70% (iOS 12.1)
  • iPhone X: 1392/2785/50% (iOS 11.2.1)
  • iPhone XS: 2040/3754/54% (iOS 12.1)
  • iPhone XS Max: 2039/3735/55% (iOS 12.1)
  • iPhone XR: 1792/2813/63% (iOS 12.1)
  • iPhone 11: 2068/3844/54% (iOS 13.1.3)
  • iPhone 11 Pro Max: 2067/3740/55% (iOS 13.2.3)
139
votes

I created small utility which tries to allocate as much memory as possible to crash and it records when memory warnings and crash happened. This helps to find out what's the memory budget for any iOS device.

https://github.com/Split82/iOSMemoryBudgetTest

20
votes

In my app, user experience is better if more memory is used, so I have to decide if I really should free all the memory I can in didReceiveMemoryWarning. Based on Split's and Jasper Pol's answer, using a maximum of 45% of the total device memory appears to be a safe threshold (thanks guys).

In case someone wants to look at my actual implementation:

#import "mach/mach.h"

- (void)didReceiveMemoryWarning
{
    // Remember to call super
    [super didReceiveMemoryWarning];

    // If we are using more than 45% of the memory, free even important resources,
    // because the app might be killed by the OS if we don't
    if ([self __getMemoryUsedPer1] > 0.45)
    {
        // Free important resources here
    }

    // Free regular unimportant resources always here
}

- (float)__getMemoryUsedPer1
{
    struct mach_task_basic_info info;
    mach_msg_type_number_t size = sizeof(info);
    kern_return_t kerr = task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &size);
    if (kerr == KERN_SUCCESS)
    {
        float used_bytes = info.resident_size;
        float total_bytes = [NSProcessInfo processInfo].physicalMemory;
        //NSLog(@"Used: %f MB out of %f MB (%f%%)", used_bytes / 1024.0f / 1024.0f, total_bytes / 1024.0f / 1024.0f, used_bytes * 100.0f / total_bytes);
        return used_bytes / total_bytes;
    }
    return 1;
}

Swift (based on this answer):

func __getMemoryUsedPer1() -> Float
{
    let MACH_TASK_BASIC_INFO_COUNT = (sizeof(mach_task_basic_info_data_t) / sizeof(natural_t))
    let name = mach_task_self_
    let flavor = task_flavor_t(MACH_TASK_BASIC_INFO)
    var size = mach_msg_type_number_t(MACH_TASK_BASIC_INFO_COUNT)
    var infoPointer = UnsafeMutablePointer<mach_task_basic_info>.alloc(1)
    let kerr = task_info(name, flavor, UnsafeMutablePointer(infoPointer), &size)
    let info = infoPointer.move()
    infoPointer.dealloc(1)
    if kerr == KERN_SUCCESS
    {
        var used_bytes: Float = Float(info.resident_size)
        var total_bytes: Float = Float(NSProcessInfo.processInfo().physicalMemory)
        println("Used: \(used_bytes / 1024.0 / 1024.0) MB out of \(total_bytes / 1024.0 / 1024.0) MB (\(used_bytes * 100.0 / total_bytes)%%)")
        return used_bytes / total_bytes
    }
    return 1
}
8
votes

By forking SPLITS repo, I built one to test iOS memory that can be allocated to the Today's Extension

iOSMemoryBudgetTestForExtension

Following is the result that i got in iPhone 5s

Memory Warning at 10 MB

App Crashed at 12 MB

By this means Apple is merely allowing any extensions to work with their full potential.

7
votes

You should watch session 147 from the WWDC 2010 Session videos. It is "Advanced Performance Optimization on iPhone OS, part 2".
There is a lot of good advice on memory optimizations.

Some of the tips are:

  • Use nested NSAutoReleasePools to make sure your memory usage does not spike.
  • Use CGImageSource when creating thumbnails from large images.
  • Respond to low memory warnings.
7
votes

Starting with iOS13, there is an Apple-supported way of querying this by using

#include <os/proc.h>

size_t os_proc_available_memory(void)

Introduced here: https://developer.apple.com/videos/play/wwdc2019/606/

Around min 29-ish.

Edit: Adding link to documentation https://developer.apple.com/documentation/os/3191911-os_proc_available_memory?language=objc

3
votes
- (float)__getMemoryUsedPer1
{
    struct mach_task_basic_info info;
    mach_msg_type_number_t size = MACH_TASK_BASIC_INFO;
    kern_return_t kerr = task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &size);
    if (kerr == KERN_SUCCESS)
    {
        float used_bytes = info.resident_size;
        float total_bytes = [NSProcessInfo processInfo].physicalMemory;
        //NSLog(@"Used: %f MB out of %f MB (%f%%)", used_bytes / 1024.0f / 1024.0f, total_bytes / 1024.0f / 1024.0f, used_bytes * 100.0f / total_bytes);
        return used_bytes / total_bytes;
    }
    return 1;
}

If one will use TASK_BASIC_INFO_COUNT instead of MACH_TASK_BASIC_INFO, you will get

kerr == KERN_INVALID_ARGUMENT (4)

2
votes

I created one more list by sorting Jaspers list by device RAM (I made my own tests with Split's tool and fixed some results - check my comments in Jaspers thread).

device RAM: percent range to crash

  • 256MB: 49% - 51%
  • 512MB: 53% - 63%
  • 1024MB: 57% - 68%
  • 2048MB: 68% - 69%
  • 3072MB: 63% - 66%
  • 4096MB: 77%
  • 6144MB: 81%

Special cases:

  • iPhone X (3072MB): 50%
  • iPhone XS/XS Max (4096MB): 55%
  • iPhone XR (3072MB): 63%
  • iPhone 11/11 Pro Max (4096MB): 54% - 55%

Device RAM can be read easily:

[NSProcessInfo processInfo].physicalMemory

From my experience it is safe to use 45% for 1GB devices, 50% for 2/3GB devices and 55% for 4GB devices. Percent for macOS can be a bit bigger.

1
votes

Working with the many answers above, I have implemented Apples new method os_proc_available_memory() for iOS 13+ coupled with NSByteCountFormatter which offers a number of useful formatting options for nicer output of the memory:

#include <os/proc.h>

....

- (NSString *)memoryStringForBytes:(unsigned long long)memoryBytes {
    NSByteCountFormatter *byteFormatter = [[NSByteCountFormatter alloc] init];
    byteFormatter.allowedUnits = NSByteCountFormatterUseGB;
    byteFormatter.countStyle = NSByteCountFormatterCountStyleMemory;
    NSString *memoryString = [byteFormatter stringFromByteCount:memoryBytes];
    return memoryString;
}

- (void)memoryLoggingOutput {
    if (@available(iOS 13.0, *)) {
        NSLog(@"Physical memory available: %@", [self memoryStringForBytes:[NSProcessInfo processInfo].physicalMemory]);
        NSLog(@"Memory A (brackets): %@", [self memoryStringForBytes:(long)os_proc_available_memory()]);
        NSLog(@"Memory B (no brackets): %@", [self memoryStringForBytes:(long)os_proc_available_memory]);
    }
}

Important note: Do not forget the () at the end. I have included both NSLog options in in the memoryLoggingOutput method because it does not warn you that they are missing and failure to include the brackets returns an unexpected yet constant result.

The string returned from the method memoryStringForBytes outputs values like so:

NSLog(@"%@", [self memoryStringForBytes:(long)os_proc_available_memory()]); // 1.93 GB
// 2 seconds later
NSLog(@"%@", [self memoryStringForBytes:(long)os_proc_available_memory()]); // 1.84 GB