[关闭]
@qidiandasheng 2022-08-23T01:27:10.000000Z 字数 16597 阅读 2581

内存优化(😁)

性能优化


常见内存问题

FOOM

内存泄露

WKWebview白屏

解决办法:

  1. // 此方法适用iOS9.0以上
  2. - (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView NS_AVAILABLE(10_11, 9_0){
  3. //reload
  4. }

野指针问题

iOS 9以后NSNotificationCenter不需要手动移除观察者

在iOS9之前,通知中心使用 unsafe_unretained修饰引用观察者,如果观察者被回收时,若不手动移除,指针会指向被回收的内存区域,变为野指针,如果再发送消息会造成Crash;

而iOS 9之后,通知中心使用weak修饰引用观察者 ,即使不手动移除观察者,weak指针也会在观察者被回收后自动置nil。之后发送消息,是不会有问题的。

iOS 10 nano_free Crash

针对内存类型的优化

IOKit

这一部分主要是图片、OpenGL纹理、CVPixelBuffer等,比如通常是OpenGL的纹理,glTexImage2d调用产生的。iOS系统有相关释放接口。但可能释放不及时。

CPU和GPU的都算在VM中。Allocations不包含GL纹理,创建一定数量纹理后,到达极限值,则之后创建纹理就会失败,App可能不会崩溃,但是出现异常,花屏,或者拍后页白屏。

显存可能被映射到某块虚拟内存,因此可以通过IOKit来查看纹理增长情况。手机的显存就是内存,而Mac才区分显存和内存。

纹理是在内核态分配的,不计算到Allocations里边。如包含OpenGL的纹理,是Dirty Size,需要降下来。

若GL分配纹理不释放,则IOKit的Virtual Size不断增长;如果纹理正确释放,则Virtual Size比较稳定。

所以,通常情况下,开发者已经正确调用了释放内存的操作,但是OpenGL自己做的优化,使得内存并未真正地及时释放掉,仅仅是为了重用。

glDeleteTextures函数,并非一定会立即释放掉纹理,而是表明该纹理可以再次在glGenTextures的时候被复用。

CVOpenGLESTextureCacheFlush()

GPUImage里面有一个释放帧缓冲的方法,当收到内存警告的时候就会调用这个方法,里面就有对纹理缓存进行了释放。

  1. - (void)purgeAllUnassignedFramebuffers
  2. {
  3. runAsynchronouslyOnVideoProcessingQueue(^{
  4. [framebufferCache removeAllObjects];
  5. [framebufferTypeCounts removeAllObjects];
  6. #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
  7. CVOpenGLESTextureCacheFlush([[GPUImageContext sharedImageProcessingContext] coreVideoTextureCache], 0);
  8. #else
  9. #endif
  10. });
  11. }

这里有一点需要格外注意:CVOpenGLESTextureCacheFlush调用后,内存可能依然不会立即释放。假设延迟5s执行,则可能释放(而延迟1s,则可能只释放部分内存)。
这与CVPixelBuffer以及CVOpenGLESTextureCacheFlush的自身机制有关系。

  1. 如默认情况下纹理会延迟1s进行page out操作;
  2. CVOpenGLESTextureCacheFlush的方法注释中刻意添加了周期性调用(This call must be made periodically)的提示,以保证纹理释放操作的执行。
  1. //
  2. // cacheAttributes
  3. //
  4. // By default, textures will age out after one second. Setting a maximum
  5. // texture age of zero will disable the age-out mechanism completely.
  6. // CVOpenGLESTextureCacheFlush() can be used to force eviction in either case.
  7. CV_EXPORT const CFStringRef CV_NONNULL kCVOpenGLESTextureCacheMaximumTextureAgeKey COREVIDEO_GL_DEPRECATED(ios, 5.0, 12.0) COREVIDEO_GL_DEPRECATED(tvos, 9.0, 12.0) API_UNAVAILABLE(macosx) __WATCHOS_PROHIBITED;
  8. /*!
  9. @function CVOpenGLESTextureCacheFlush
  10. @abstract Performs internal housekeeping/recycling operations
  11. @discussion This call must be made periodically to give the texture cache a chance to make OpenGLES calls
  12. on the OpenGLES context used to create it in order to do housekeeping operations. The EAGLContext
  13. associated with the cache may be used to delete or unbind textures.
  14. @param textureCache The texture cache object to flush
  15. @param options Currently unused, set to 0.
  16. */
  17. CV_EXPORT void CVOpenGLESTextureCacheFlush( CVOpenGLESTextureCacheRef CV_NONNULL textureCache, CVOptionFlags options ) COREVIDEO_GL_DEPRECATED(ios, 5.0, 12.0) COREVIDEO_GL_DEPRECATED(tvos, 9.0, 12.0) API_UNAVAILABLE(macosx) __WATCHOS_PROHIBITED;

注意,这里的periodically肯定是有坑的。如果遇到内存未立即释放的情况,试一下延迟几秒钟执行CVOpenGLESTextureCacheFlush操作。

CGImageRef转CVPixelBufferRef

  1. - (void)dealloc {
  2. if (_pixelBufferPool) {
  3. CVPixelBufferPoolFlush(_pixelBufferPool, kCVPixelBufferPoolFlushExcessBuffers);
  4. CVPixelBufferPoolRelease(_pixelBufferPool);
  5. _pixelBufferPool = nil;
  6. }
  7. }
  8. - (CVPixelBufferRef)createPixelBufferFromCGImage:(CGImageRef )image {
  9. size_t height = CGImageGetHeight(image);
  10. size_t width = CGImageGetWidth(image);
  11. if (!_pixelBufferPool || !CGSizeEqualToSize(_pixelPoolSize, CGSizeMake(width, height))) {
  12. if (_pixelBufferPool) {
  13. CVPixelBufferPoolFlush(_pixelBufferPool, kCVPixelBufferPoolFlushExcessBuffers);
  14. CVPixelBufferPoolRelease(_pixelBufferPool);
  15. _pixelBufferPool = nil;
  16. }
  17. NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
  18. [attributes setObject:[NSNumber numberWithInt:kCVPixelFormatType_32BGRA] forKey:(NSString *)kCVPixelBufferPixelFormatTypeKey];
  19. [attributes setObject:@(width) forKey:(NSString *)kCVPixelBufferWidthKey];
  20. [attributes setObject:@(height) forKey:(NSString *)kCVPixelBufferHeightKey];
  21. [attributes setObject:@(32) forKey:(NSString *)kCVPixelBufferBytesPerRowAlignmentKey];
  22. [attributes setObject:[NSDictionary dictionary] forKey:(NSString *)kCVPixelBufferIOSurfacePropertiesKey];
  23. CVPixelBufferPoolCreate(kCFAllocatorDefault, NULL, (__bridge CFDictionaryRef _Nullable)(attributes), &_pixelBufferPool);
  24. _pixelPoolSize = CGSizeMake(width, height);
  25. }
  26. CVPixelBufferRef pxbuffer = NULL;
  27. CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, _pixelBufferPool,&pxbuffer);
  28. NSParameterAssert(pxbuffer != NULL);
  29. CIImage *ciimage = [[CIImage alloc] initWithCGImage:image];
  30. [_ciContext render:ciimage toCVPixelBuffer:pxbuffer];
  31. return pxbuffer;
  32. }

如果PixelBuffer重用,则使用Pool,释放操作需要调用Pool的flush函数。而iOS系统中实际的内存释放时机会有延迟,且这里拍照的pixelBuffer并不会频繁复用,因此直接使用create方法来替代Pool更合理。用完就释放。

修改为:

  1. - (CVPixelBufferRef)createPixelBufferFromCGImage:(CGImageRef )image {
  2. size_t height = CGImageGetHeight(image);
  3. size_t width = CGImageGetWidth(image);
  4. CVPixelBufferRef pxbuffer = NULL;
  5. CFDictionaryRef empty; // empty value for attr value.
  6. CFMutableDictionaryRef attrs;
  7. empty = CFDictionaryCreate(kCFAllocatorDefault, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); // our empty IOSurface properties dictionary
  8. attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
  9. CFDictionarySetValue(attrs, kCVPixelBufferIOSurfacePropertiesKey, empty);
  10. CVPixelBufferCreate(kCFAllocatorDefault, width, height, kCVPixelFormatType_32BGRA, attrs, &pxbuffer);
  11. CFRelease(attrs);
  12. CFRelease(empty);
  13. NSParameterAssert(pxbuffer != NULL);
  14. CIImage *ciimage = [[CIImage alloc] initWithCGImage:image];
  15. [_ciContext render:ciimage toCVPixelBuffer:pxbuffer];
  16. return pxbuffer;
  17. }

VM:ImageIO_PNG_Data

UIImage的imageNamed:方法会将图片数据缓存在内存中。而imageWithContentsOfFile:方法则不会进行缓存,用完立即释放掉了。优化建议:

如果对于多图的滚动视图,渲染到imageView中后,可以使用autoreleasepool来尽早释放:

  1. for (int i=0;i<10;i++) {
  2. UIImageView *imageView = xxx;
  3. NSString *imageFile = xxx;
  4. @autoreleasepool {
  5. imageView.image = [UIImage imageWithContentsOfFile:imageFile];
  6. }
  7. [self.scrollView addSubview:imageView];
  8. }

优化措施:适当地使用imageNamed:imageWithContentsOfFile:方法。对于比较老的项目,可以在调试环境对imageNamed:方法进行hook,检测UIImage的size大小,以筛选出尺寸过大的图片。

VM:CG raster data

光栅数据,即为UIImage的解码数据。SDWebImage将解码数据做了缓存,避免渲染时候在主线程解码而造成阻塞。

优化措施:

  1. [[SDImageCache sharedImageCache] config].shouldDecompressImages = NO;
  2. [[SDImageCache sharedImageCache] config].shouldCacheImagesInMemory = NO;
  3. [[SDWebImageDownloader sharedDownloader] setShouldDecompressImages:NO];

VM:CoreAnimation

一般是UIView,CALayer。如有个5.78MB的,无法看出是哪个View,只知道是一个很大的View。

  1. CA::Render::Shmem::new_bitmap xxxxx
  2. CABackingStorePrepareUpdate_(CABackingStore*,xxxxxxx)
  3. CABackingStoreUpdate_
  4. invocation function for block in CA::Layer::display_()

优化措施:不要用太大的UIViewCALayer

代码段__TEXT

优化措施:缩小包体积。

图片

生成图片(UIGraphicsImageRenderer)

UIGraphicsImageRenderer 代替 UIGraphicsBeginImageContextWithOptions

使用 UIGraphicsBeginImageContextWithOptions 生成的图片,每个像素需要 4 个字节表示。建议使用 UIGraphicsImageRenderer,这个方法是从 iOS 10 引入,在 iOS 12 上会自动选择最佳的图像格式,可以减少很多内存。

导出图片Fri Jul 24 2020 19_51_36 GMT+0800 (中国标准时间).png-188kB

导出图片Fri Jul 24 2020 19_51_54 GMT+0800 (中国标准时间).png-196kB

Downsampling(降低采样):ImageIO

在视图比较小,图片比较大的场景下,直接展示原图片会造成不必要的内存和CPU消耗,这里就可以使用ImageIO的接口,它会按照取平均值的办法把多个像素点变成一个像素点,这个过程称为 Downsampling,也就是生成缩略图。

直接使用UIImage的方式:

ImageIO的方式:

导出图片Fri Jul 24 2020 19_56_32 GMT+0800 (中国标准时间).png-166.9kB

导出图片Fri Jul 24 2020 19_56_50 GMT+0800 (中国标准时间).png-190.7kB

这里options有不同的选项来控制ImageIO的处理方式:

这样的缩略图方式可以省去大量的内存和CPU消耗,官方Case给出的前后内存对比:

导出图片Fri Jul 24 2020 20_34_36 GMT+0800 (中国标准时间).png-245.4kB

缩放大图片

这里的缩放大图片也就是用的上面的ImageIO的方式。

以往图片缩放接口是这样写的:

  1. - (UIImage *)scaleImage:(UIImage *)image newSize:(CGSize)newSize{
  2. UIGraphicsBeginImageContextWithOptions(newSize, NO, 0);
  3. [image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
  4. UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
  5. UIGraphicsEndImageContext();
  6. return newImage;
  7. }

处理大分辨率图片时,往往容易出现OOM,原因是-[UIImage drawInRect:]在绘制时,先解码图片,再生成原始分辨率大小的bitmap,这是很耗内存的。解决方法是使用更低层的ImageIO接口,避免中间bitmap产生:

  1. - (UIImage *)scaleImageWithData:(NSData *)data newSize:(CGSize)newSize scale:(CGFloat)scale orientation:(UIImageOrientation)orientation{
  2. CGFloat maxPixelSize = MAX(newSize.width, newSize.height);
  3. CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, nil);
  4. NSDictionary *options = @{(__bridge id)kCGImageSourceCreateThumbnailFromImageAlways:(__bridge id)kCFBooleanTrue,(__bridge id)kCGImageSourceThumbnailMaxPixelSize:[NSNumber numberWithFloat:maxPixelSize]};
  5. CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(sourceRef, 0, (__bridge CFDictionaryRef)options);
  6. UIImage *resultImage = [UIImage imageWithCGImage:imageRef scale:scale orientation:orientation];
  7. CGImageRelease(imageRef);
  8. CFRelease(sourceRef);
  9. return resultImage;
  10. }

imageNamed和imageWithContentsOfFile

  1. UIImage的imageNamed:方法会将图片数据缓存在内存中,缓存使用的时NSCache,收到内存警告会释放。
  2. imageWithContentsOfFile:方法则不会进行缓存,不需要的时候就立即释放掉了。

所以

  1. 对于频繁使用的小图,可以放到Assets.xcassets中,使用imageNamed:方法。
  2. 对于不经常使用的大图,不要放到Assets.xcassets中,且使用imageWithContentsOfFile:方法。

UIImage的异步解码和渲染

UIImage只有在屏幕上渲染(self.imageView.image = image)的时候,才去解码的,解码操作在主线程执行。所以,如果有非常多(如滑动界面下载大量网络图片)或者较大图片的解码渲染操作,则会阻塞主线程。可以添加异步解码的一些使用技巧。
可以通过如下方式,避免图片使用时候的一些阻塞、资源消耗过大、频繁解码等的情况。

  1. 异步下载网络图片,进行内存和磁盘缓存
  2. 对图片进行异步解码,将解码后的数据放到内存缓存
  3. 主线程进行图片的渲染

具体可参考SDWebimage的实现。

缓存优化

对于缓存数据或可重建数据,尽量使用NSCache或NSPurableData,具有以下优点。

  1. NSCache是线程安全的,NSMutableDictionary线程不安全
  2. 当内存不足收到内存警告时,NSCache会自动释放内存(所以从缓存中取数据的时候总要判断是否为空)
  3. NSCache可以指定缓存的限额,当缓存超出限额自动释放内存

下边代码是SDWebImage的cache, SDMemoryCache继承自NSCache

  1. // A memory cache which auto purge the cache on memory warning and support weak cache.
  2. @interface SDMemoryCache <KeyType, ObjectType> : NSCache <KeyType, ObjectType>
  3. @end
  4. // Private
  5. @interface SDMemoryCache <KeyType, ObjectType> ()
  6. @property (nonatomic, strong, nonnull) SDImageCacheConfig *config;
  7. @property (nonatomic, strong, nonnull) NSMapTable<KeyType, ObjectType> *weakCache; // strong-weak cache
  8. @property (nonatomic, strong, nonnull) dispatch_semaphore_t weakCacheLock; // a lock to keep the access to `weakCache` thread-safe
  9. - (instancetype)init NS_UNAVAILABLE;
  10. - (instancetype)initWithConfig:(nonnull SDImageCacheConfig *)config;
  11. @end
  12. @implementation SDMemoryCache
  13. // Current this seems no use on macOS (macOS use virtual memory and do not clear cache when memory warning). So we only override on iOS/tvOS platform.
  14. // But in the future there may be more options and features for this subclass.
  15. #if SD_UIKIT
  16. - (void)dealloc {
  17. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  18. }
  19. - (instancetype)initWithConfig:(SDImageCacheConfig *)config {
  20. self = [super init];
  21. if (self) {
  22. // Use a strong-weak maptable storing the secondary cache. Follow the doc that NSCache does not copy keys
  23. // This is useful when the memory warning, the cache was purged. However, the image instance can be retained by other instance such as imageViews and alive.
  24. // At this case, we can sync weak cache back and do not need to load from disk cache
  25. self.weakCache = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
  26. self.weakCacheLock = dispatch_semaphore_create(1);
  27. self.config = config;
  28. [[NSNotificationCenter defaultCenter] addObserver:self
  29. selector:@selector(didReceiveMemoryWarning:)
  30. name:UIApplicationDidReceiveMemoryWarningNotification
  31. object:nil];
  32. }
  33. return self;
  34. }
  35. - (void)didReceiveMemoryWarning:(NSNotification *)notification {
  36. // Only remove cache, but keep weak cache
  37. [super removeAllObjects];
  38. }
  39. // `setObject:forKey:` just call this with 0 cost. Override this is enough
  40. - (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g {
  41. [super setObject:obj forKey:key cost:g];
  42. if (!self.config.shouldUseWeakMemoryCache) {
  43. return;
  44. }
  45. if (key && obj) {
  46. // Store weak cache
  47. LOCK(self.weakCacheLock);
  48. [self.weakCache setObject:obj forKey:key];
  49. UNLOCK(self.weakCacheLock);
  50. }
  51. }
  52. - (id)objectForKey:(id)key {
  53. id obj = [super objectForKey:key];
  54. if (!self.config.shouldUseWeakMemoryCache) {
  55. return obj;
  56. }
  57. /// 内存缓存中若没有,则从weakCache中找,找到了,再缓存到内存中?
  58. if (key && !obj) {
  59. // Check weak cache
  60. LOCK(self.weakCacheLock);
  61. obj = [self.weakCache objectForKey:key];
  62. UNLOCK(self.weakCacheLock);
  63. if (obj) {
  64. // Sync cache
  65. NSUInteger cost = 0;
  66. if ([obj isKindOfClass:[UIImage class]]) {
  67. cost = SDCacheCostForImage(obj);
  68. }
  69. [super setObject:obj forKey:key cost:cost];
  70. }
  71. }
  72. return obj;
  73. }
  74. - (void)removeObjectForKey:(id)key {
  75. [super removeObjectForKey:key];
  76. if (!self.config.shouldUseWeakMemoryCache) {
  77. return;
  78. }
  79. if (key) {
  80. // Remove weak cache
  81. LOCK(self.weakCacheLock);
  82. [self.weakCache removeObjectForKey:key];
  83. UNLOCK(self.weakCacheLock);
  84. }
  85. }
  86. - (void)removeAllObjects {
  87. [super removeAllObjects];
  88. if (!self.config.shouldUseWeakMemoryCache) {
  89. return;
  90. }
  91. // Manually remove should also remove weak cache
  92. LOCK(self.weakCacheLock);
  93. [self.weakCache removeAllObjects];
  94. UNLOCK(self.weakCacheLock);
  95. }

使用NSMapTable来存储strong-weak cache(key是strong,value是weak的)。

shouldUseWeakMemoryCache为YES,则将图片数据缓存到内存的同时,使用一个weak maptable存储该image,如image key(strong)->image(weak)

若内存警告,则缓存的image被清除,一些image可以恢复,则该weak maptable就不受影响。否则,image被清除,则SD就要重新处理该内存缓存,如从disk查询或网络请求。

如App进入后台,释放掉内存,再进入前台时,view的cell中的image可以重建,然后放到weak maptable中,而不需要再从disk读取。

加载超大图片的正确姿势

对于一些微信长图/微博长图之类的,或者一些需要展示全图,然后拖动来查看细节的场景,可以使用CATiledLayer来进行分片加载,避免直接对图片的所有部分进行解码和渲染,以节省资源。在滑动时,指定目标位置,映射原图指定位置的部分图片进行解码和渲染。

SDWebImage是否使用解压缩的区别

SDWebImage默认是会提前子线程上对图片进行解码的,使用代码如下:

  1. - (nullable UIImage *)sd_decompressedImageWithImage:(nullable UIImage *)image {
  2. if (![[self class] shouldDecodeImage:image]) {
  3. return image;
  4. }
  5. // autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
  6. // on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
  7. @autoreleasepool{
  8. CGImageRef imageRef = image.CGImage;
  9. CGColorSpaceRef colorspaceRef = [[self class] colorSpaceForImageRef:imageRef];
  10. size_t width = CGImageGetWidth(imageRef);
  11. size_t height = CGImageGetHeight(imageRef);
  12. // kCGImageAlphaNone is not supported in CGBitmapContextCreate.
  13. // Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
  14. // to create bitmap graphics contexts without alpha info.
  15. CGContextRef context = CGBitmapContextCreate(NULL,
  16. width,
  17. height,
  18. kBitsPerComponent,
  19. 0,
  20. colorspaceRef,
  21. kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
  22. if (context == NULL) {
  23. return image;
  24. }
  25. // Draw the image into the context and retrieve the new bitmap image without alpha
  26. CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
  27. CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
  28. UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha
  29. scale:image.scale
  30. orientation:image.imageOrientation];
  31. CGContextRelease(context);
  32. CGImageRelease(imageRefWithoutAlpha);
  33. return imageWithoutAlpha;
  34. }
  35. }

我们调用SDWebimage来显示一张1240 × 983的图片,用instruments来查看内存的变化。如下图所示,在创建位图的瞬间会有一个内存高峰,由方法CGBitmapContextCreate创建的位图上下文产生,当位图绘制完成之后,我们看到释放了上下文CGContextRelease(context);,内存又降了下来。

截屏2020-08-04 下午7.02.02.png-459.1kB


  1. [[SDImageCache sharedImageCache] config].shouldDecompressImages = NO;
  2. [[SDWebImageDownloader sharedDownloader] setShouldDecompressImages:NO];

以上代码表示不提前解码,同样用instruments来查看内存的变化。如下图所示,我们看到并没有产生一个内存的高峰,这是由于系统在渲染时进行解码,并不产生我们自己解码绘制时创建的上下文:

截屏2020-08-04 下午7.10.01.png-376.7kB

其他

超大UIView相关的优化

如果UIView的size过大,如果全部绘制,则会消耗大量内存,以及阻塞主线程。

常见的场景如微信消息的超长文本,则可将其分割成多个UIView,然后放到UITableView中,利用cell的复用机制,减少不必要的渲染和内存占用。

离屏渲染

我们经常会需要预先渲染文字/图片以提高性能,此时需要尽可能保证这块 context 的大小与屏幕上的实际尺寸一致,避免浪费内存。可以通过 View Hierarchy 调试工具,打印一个 layer 的 contents 属性来查看其中的 CGImage(backing image)以及其大小。layer的contents属性即可看到其CGImage(backing store)的大小。

离屏渲染未必会导致性能降低,而是会额外加重GPU的负担,可能导致一个V-sync信号周期内,GPU的任务未能完成,最终结果就是可能导致卡顿。

启动优化

App启动时,加载相应的二进制文件或者dylib到内存中。当进程访问一个虚拟内存page,但该page未与物理内存形成映射关系,则会触发缺页中断,然后再分配物理内存。过多的缺页中断会导致一定的耗时。

二进制重排的启动优化方案,是通过减少App启动时候的缺页中断次数,来加速App启动。

字节对齐

当定义object的时候,尽量使得内存页对齐也会有帮助。小内存属性放一起,大内存属性放一起。

参考

iOS APP内存优化记录
关于iOS内存的深入排查和优化
iOS内存二三事

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注