(接(iOS-内存管理(二、理论实践1))
四、非OC对象,例如CF,CoreGraphic框架,C/C++语言混编等内存管理
通过以前的学习我们知道ARC模式下,只有继承于NSObject的对象,系统才能对其内存进行自动管理,下面我们针对常用非NSObject的对象内存管理进行实践
1、类似CoreGraphic也需要自己管理部分内存
例如我们需要对一张图片进行处理,修改其对应像素:
CGImageRef imageRef = self.CGImage; //1、获取图片宽高 NSUInteger width = CGImageGetWidth(imageRef); NSUInteger height = CGImageGetHeight(imageRef); //2、创建颜色空间 CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB(); //3、根据像素点个数创建一个所需要的空间 UInt32 *imagePiexl = (UInt32 *)calloc(width*height, sizeof(UInt32)); CGContextRef contextRef = CGBitmapContextCreate(imagePiexl, width, height, 8, 4*width, colorSpaceRef, kCGImageAlphaNoneSkipLast); //4、根据图片数据源绘制上下文 CGContextDrawImage(contextRef, CGRectMake(0, 0, width, height), imageRef);
在内部像素数据处理完成之后我们需要管理开启的内存:
//根据上下文绘制 CGImageRef finalRef = CGBitmapContextCreateImage(contextRef); //释放用过的内存 CGContextRelease(contextRef); CGColorSpaceRelease(colorSpaceRef); free(imagePiexl); return [UIImage imageWithCGImage:finalRef scale:self.scale orientation:UIImageOrientationUp];
2、类似CF框架在使用时的内存管理
底层的 Core Foundation 对象,很多都是C写的,在创建时大多以 XxxCreateWithXxx 这样的方式创建,例如:
// 创建一个 CFStringRef 对象 CFStringRef str= CFStringCreateWithCString(kCFAllocatorDefault, “hello world", kCFStringEncodingUTF8); // 创建一个 CTFontRef 对象 CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL);
对于这些对象的引用计数的修改,要相应的使用 CFRetain 和 CFRelease 方法。对于 CFRetain 和 CFRelease 两个方法,读者可以直观地认为,这与 Objective-C 对象的 retain 和 release 方法等价。如下所示:
// 创建一个 CTFontRef 对象 CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL); // 引用计数加 1 CFRetain(fontRef); // 引用计数减 1 CFRelease(fontRef);
除此之外,这里添加一个bridge扩展知识。在 ARC 下,我们有时需要将一个 Core Foundation 对象转换成一个 Objective-C 对象,这个时候我们需要告诉编译器,转换过程中的引用计数需要做如何的调整。这就引入了bridge相关的关键字,以下是这些关键字的说明:
- __bridge: 只做类型转换,不修改相关对象的引用计数,原来的 Core Foundation 对象在不用时,需要调用 CFRelease 方法。
- __bridge_retained:类型转换后,将相关对象的引用计数加 1,原来的 Core Foundation 对象在不用时,需要调用 CFRelease 方法。 -__bridge_transfer:类型转换后,将该对象的引用计数交给 ARC 管理,Core Foundation 对象在不用时,不再需要调用 CFRelease 方法。
3、OC中使用C/C++编写代码模块,C/C++ 代码也需要自行进行内存管理:
一段简单的C代码,C++类似:
char *p = (char *) malloc(100); strcpy(p, “hello”); free(p);
所有在OC中混编的C/C++代码模块,都需要自行在其代码块中完成对应内存处理。
五、计时器、通知、KVO等内存管理
1、NSTimer 在使用完成后,记得移除,特别是在VC中。处理timer内存泄露的方法有很多,可以通过runloop,block等等都可以,这里篇幅有限使用最简单最常见的方式:
// 在viewDidLoad 初始化
- (void)viewDidLoad {
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1
target:self
selector:@selector(p_doSomeThing)
userInfo:nil
repeats:YES];
}
// 在viewWillDisappear 中手动终止销毁
[self.timer invalidate];
self.timer = nil;
2、通知、和KVO差不多,记得添加通知,使用结束后一定要在合适的地方,进行移除,以KVO为例:
// 初始化 person 对象,并创建对 age 值的监听
self.person = [[Person alloc] init];
self.person.age = 10;
[self.person addObserver:self forKeyPath:@"age" options: option context:nil];
// 注意需要移除监听
// 也可以放在在viewWillDisappear中
- (void)dealloc {
[self.person removeObserver:self forKeyPath:@"age"];
}
六、属性的内存管理(只针对ARC)
1、属性的基本特性
2、属性一般是我们修饰引用对象的时候使用,不同对象使用的属性值不同,对应的内存管理也存在差别,下面了解一些简单属性使用实践:
- assign和weak
weak与assign都表示了一种“非持有关系”(nonowning relationship),也称弱引用,在使用时不会增加被引用对象的引用计数。weak在引用的对象被销毁后会被指向nil。而assign不会被置nil。
assign 跟 unsafe_unretained其实差不多,如果用assign 来修饰对象,当assign指向的对象被释放时,assign就成了一个野指针,它会指向一块无效内存,这时你给这个assign修饰的属性发送消息或赋值时就会发生崩溃(也有可能不崩溃,取决于你发送消息的时候那块内存是否被释放)。 所以我们一般不用assign修饰对象,因为用assign修饰的指针会成为野指针导致错误。
那为什么用assign来修饰基本数据类型呢?因为基本数据类型是被分配到栈上的,栈的内存会由系统自己自动处理,不会造成野指针。
总结:assign用于修饰基础类型的数据(NSInteger)和C语言类型数据(int,float,double,char,bool) ,而weak只能用于修饰对象。
- strong和copy
使用strong和copy时都会使引用对象引用计数+1。但是使用copy修饰的属性在某些情况下赋值的时候会创建对象的副本,也就是深拷贝。
这时候我们又引入一另一组常见概念深拷贝/浅拷贝:
我们根据一个NSString例子(NSArry,NSDictory等类似)来理解深拷贝浅拷贝:
场景1:
@interface TestStringClass () @property (nonatomic, strong) NSString *strongString; @property (nonatomic, copy) NSString *copyedString; @end - (void)test { NSString *originString = [NSString stringWithFormat:@"abc"]; self.strongString = originString; self.copyedString = originString; NSLog(@"origin string: %p, %p", originString, &originString); NSLog(@"strong string: %p, %p", _strongString, &_strongString); NSLog(@"copy string: %p, %p", _copyedString, &_copyedString); }
场景1现象:
打印的3个内存地址是一样的,这说明我们用一个不可变NSString对象给目标对象赋值时,无论 使用strong或copy 修饰目标对象都是一样的。此时strong和copy 都是浅拷贝;
场景1结论:
这种场景,目标对象使用strong/copy修饰都可以;
场景2:
下面我们把NSString换成NSMutableString看看,将
NSString *originString = [NSString stringWithFormat:@"abc"];
改为:
NSMutableString *originString = [NSMutableString stringWithFormat:@"abc"];
场景2现象:
我们看到originString跟_strongString内存是一样的,_copyedString内存地址是不一样的:这说明我们用一个可变对象给目标对象赋值时,strong修饰的目标对象 依然是浅拷贝,copy修饰的目标对象变成了深拷贝。
场景2结论:
这种场景,strong修饰的目标对象strongString,就会引发一个潜在的问。我们定义的strongString 是一个不可变的NSString对象,可是此种赋值,已经将其变成了NSMutableString类型,和我们的初衷相悖,如果再通过其对其他代码块传值,他的源数据修改就会引发一系列的数据问题。
总结场景1和场景2,我们得出一个结论:
在目标对象为不可变对象例如NSSring时,避免可变字符串的修改导致的一些非预期问题,使用copy来修饰。在目标对象为可变对象例如NSMutableString时,使用strong来修饰。(NSArry,NSDictory等同理)
可能最后你又回又一个疑问,如果我要用copy来修饰NSMutableString的对象呢?
答案是会崩溃,因为copy就是深拷贝了一个不可变的NSString对象。这时如果对这个对象进行可变操作,会产生崩溃。 例如:
@property(nonatomic, copy)NSMutableString *copyString; //copy 属性修饰的NSMutableString 底层会进行深拷贝,此时copyString实际上是一个不可变对象 [copyString appendString:@"badguy"];
或这样写
//初始化一个可变NSMutableString NSMutableString *mutableString = [[NSMutableString alloc] initWithFormat:@"哈哈"]; //[mutableString copy] 会深拷贝一个不可变NSString 赋值给copyString,此时copyString已经变成不可变NSString NSMutableString *copyString = [mutableString copy]; //对不可变 copyString 修改会发生崩溃 [copyString appendString:@"不可变"];
内存学习部分暂时完结
- 理论部分请移步 iOS-内存管理 (一、理论篇)