长路漫漫,唯剑作伴--内存优化
一、iOS 内存优化那些事
1、ios release版本中去掉NSLog:NSLog是比较消耗内存的,特别是一些字符串拼接的打印。解决方法是可以再PCH文件中定义一个宏,在DEBUG版本中使用系统的NSLog,在RELEASE版本中使用自己定义的。如下:
#ifdef DEBUG //如果是调试状态 #define HITLog(...) NSLog(__VA_ARGS__) //把NSLog换成自己写的HITLog #else //如果是发布状态 #define HITLog(...) //把自己写的HITLog定义成空 #endif</span></span>
2、不要让主线程承担大量的数据处理工作,这样容易造成程序假死:例如我们可以把耗时的操作放到子线程中处理,之后将刷新UI的工作交给主线程。苹果提供了三种开辟子线程的方式--NSThread、NSOperation、GCD。
3、使用SDWebImage加载图片:SD会保证相同的图片只加载一次,而且永远不必担心阻塞主线程;
4、UITableview的cell重用机制:用户交互时,将看不见的cell放入缓存池中。创建新的cell是,先判断缓存池有没有相同的identifier可以使用,如果没有的话,再创建新的cell;如下:
//每当有一个cell进入视野范围的时候会调用此方法 -(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *ID = @"cellID"; //从缓存池中取出可以循环利用的cell UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID]; if (cell == nil) { cell = [[UITableViewCell alloc]initWithStyle:UITableViewStylePlain reuseIdentifier:ID]; } return cell; }
5、如果创建了大量的临时对象,最好加入autoreleasepool中,确保对象的及时释放。如下:
for (int i = 0; i < 100000; i ++) { @autoreleasepool { NSString * log = [NSString stringWithFormat:@"%d", i]; NSLog(@"%@", log); } }
6、使用Analyse和Instruments工具解决内存泄露问题:Analyse是静态分析工具,Instruments是动态分析工具;
7、优化UITableview:UITableview是使用很频繁的控件,可以使用以下方法保证UITableview的平滑滚动;
①、正确的使用reuseIdentifier重用cell;
②、使用不透明的视图,包括cell本身;
③、缓存行高,做法如下:
1️⃣、首先为UITableview添加一个category,并在分类中创建一个CellHeightCache类,用于缓存行高;
2️⃣、在声明三个方法和一个用于缓存行高的可变字典。如下:
#import "UITableView+CellHeightCache.h" #import <objc/runtime.h> @interface CellHeightCache () @property(nonatomic,strong) NSMutableDictionary<id<NSCopying>, NSNumber *> *mutableCellHeightCaches; @end @implementation CellHeightCache - (instancetype)init { self = [super init]; if (self) { _mutableCellHeightCaches = [NSMutableDictionary dictionary]; } return self; } // 是否已经缓存 - (BOOL)existsHeightForKey:(id<NSCopying>)key { NSNumber *number = self.mutableCellHeightCaches[key]; return number && ![number isEqualToNumber:@-1]; } // 缓存行高 - (void)cacheHeight:(CGFloat)height byKey:(id<NSCopying>)key { self.mutableCellHeightCaches[key] = @(height); } // 获得行高 - (CGFloat)heightForKey:(id<NSCopying>)key { #if CGFLOAT_IS_DOUBLE return [self.mutableCellHeightCaches[key] doubleValue]; #else return [self.mutableCellHeightCaches[key] floatValue]; #endif } @end
3️⃣、在UITableview的分类中关联cellHeigCache属性,并声明一个缓存行高和一个获取行高的方法,具体如下:
@implementation UITableView (CellHeightCache) // 关联CellHeightCache。如果缓存存在则返回,不则在则设置缓存 - (CellHeightCache *)cellHeightCache { /** * 获得关联对象 * * @param object 添加过关联的对象 * @param key 添加的唯一标识符 */ CellHeightCache *cache = objc_getAssociatedObject(self, _cmd); if (!cache) { cache = [CellHeightCache new]; /** * 设置关联对象 * * @param object 需要添加关联的对象 * @param key 添加的唯一标识符 * @param value 关联的对象 * @param policy 关联的策略,是个枚举 */ objc_setAssociatedObject(self, _cmd, cache, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } return cache; } // 获取行高 - (CGFloat)getCellHeightCacheWithCacheKey:(NSString *)cacheKey { if (!cacheKey) { return 0; } //如果已经存在cell height 则返回 if ([self.cellHeightCache existsHeightForKey:cacheKey]) { CGFloat cachedHeight = [self.cellHeightCache heightForKey:cacheKey]; return cachedHeight; } else { return 0; } } //缓存cell的高度 - (void)setCellHeightCacheWithCellHeight:(CGFloat)cellHeight CacheKey:(NSString *)cacheKey { [self.cellHeightCache cacheHeight:cellHeight byKey:cacheKey]; } @end
可以看到获取cell高度及缓存cell高度局调用了- (CellHeightCache *)cellHeightCache方法;
4️⃣、接下来,利用ViewModel计算好cell高度,及一个与之关联的id,做法如下:
#import <Foundation/Foundation.h> @class WeiBoData; @interface WeiBoFrame : NSObject @property (nonatomic, assign)CGRect iconF; @property (nonatomic, assign)CGRect nameF; @property (nonatomic, assign)CGRect vipF; @property (nonatomic, assign)CGRect contentF; @property (nonatomic, assign)CGFloat cellH; @property (nonatomic, strong)WeiBoData *weiBoData; //id @property(nonatomic,copy,readonly) NSString *identifier; @end
#import "WeiBoFrame.h" #import "WeiBoData.h" #define NameFontSize [UIFont boldSystemFontOfSize:15] #define ContentFontSize [UIFont boldSystemFontOfSize:16] @implementation WeiBoFrame - (instancetype)init { self = [super init]; if (self) { _identifier = [self setIdentifier]; } return self; } - (NSString *)setIdentifier { static NSInteger counter = 0; NSLog(@"%@",[NSString stringWithFormat:@"statusFrame_id_%@",@(counter++)]); return [NSString stringWithFormat:@"statusFrame_id_%@",@(counter++)]; }
5️⃣、最后只需要在方法里这样调用就可以了
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { WeiBoFrame *frame = self.dataSource[indexPath.row]; // NSLog(@"%f",frame.cellH); // return frame.cellH; CGFloat cellHeight = [tableView getCellHeightCacheWithCacheKey:frame.identifier]; NSLog(@"从缓存取出来的-----%f",cellHeight); if(!cellHeight){ cellHeight = frame.cellH; [tableView setCellHeightCacheWithCellHeight:cellHeight CacheKey:frame.identifier]; } return cellHeight; }