问题:项目中我们经常遇到一种情况,因为某个功能需要创建很多临时变量,而且这些变量比较耗内存,还会造成崩溃。比如下面是渲染视频每一帧的代码,很耗内存,而且上限不可控,乃至崩溃:
//写入时的逻辑:将数组中的每一张图片多次写入到buffer中,
while([writerInput isReadyForMoreMediaData]){
CVPixelBufferRef buffer =NULL;
if (frame < frameCount) {
UIImage *frameImage = [export getImageWithCurrentFrame:frame];
CGImageRef imageRef = frameImage.CGImage;
// 裁剪
CGImageRef subImageRef = CGImageCreateWithImageInRect(imageRef, self.svgaCropRect);
// 将图片转成buffer
buffer = (CVPixelBufferRef)[self pixelBufferFromCGImage:subImageRef withBottomCGImage:bottomCGImage];
CGImageRelease(subImageRef);
if(buffer){
//添加buffer并设置每个buffer出现的时间,每个buffer的出现时间为第n张除以60(20是一秒20张图片,帧率,也可以自己设置其他值)所以为frame/60,即CMTimeMake(frame,60)为每一个buffer出现的时间点
CLog(@"frame = %d", frame);
if(frame >=0&&![adaptor appendPixelBuffer:buffer withPresentationTime:CMTimeMake(frame, DEVideoFrame)]){//设置每秒钟播放图片的个数
// NSLog(@"FAIL");
}else{
// NSLog(@"OK");
}
CFRelease(buffer);
}
分析下原因:
采用ARC内存管理,frameImage
对象占用内存没有及时释放,循环创建,飙升过高造成崩溃。
解决方案:
虽然我们不能直接对frameImage
进行release
操作,但我们可以引入自动释放池(AutoreleasePool
),
@autoreleasepool {
// Code benefitting from a local autorelease pool.
}
循环内引入,
@autoreleasepool {
UIImage *frameImage = [export getImageWithCurrentFrame:frame];
CGImageRef imageRef = frameImage.CGImage;
// 裁剪
CGImageRef subImageRef = CGImageCreateWithImageInRect(imageRef, self.svgaCropRect);
// 将图片转成buffer
buffer = (CVPixelBufferRef)[self pixelBufferFromCGImage:subImageRef withBottomCGImage:bottomCGImage];
CGImageRelease(subImageRef);
增加@autoreleasepool
后,自动释放了临时创建的对象内存,内存没有没有明显的上涨。
拓展:
看下入口main函数,
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
源码如图:
@autoreleasepool
的底层代码__AtAutoreleasePool __autoreleasepool
;__AtAutoreleasePool
是一个结构体。
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
__AtAutoreleasePool __autoreleasepool
;相当于执行了__AtAutoreleasePool
的构造函数和析构函数
atautoreleasepoolobj = objc_autoreleasePoolPush();
objc_autoreleasePoolPop(atautoreleasepoolobj);
push是Page执行,pop同理
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
Page继承自PageData
PageData如下:
结论:分析PageData结构体,看出autoreleasepool是一个对Page进行分页管理的双向链表。分析push和pop方法(引文在下方)得出,每一个autoreleasepool对象只有一个哨兵,哨兵放在第一页中;每一页的大小为4096字节;每一页的前56个字节存储页的AutoreleasePoolPageData结构体数据;第一页的第56往后8个字节存储哨兵,后面存储autorelease对象,总共可以存储504个;从第二页开始,每页可以存储505个对象;objc_autoreleasepoolpush是一个查找child,递增next,创建新页的过程;objc_autoreleasepoolpop是一个查找parent,递减next,释放对象,销毁page的过程,遇到哨兵对象即停止。