iOS-内存管理(三、理论实践2)

1,301 阅读7分钟

(接(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:@"不可变"];

内存学习部分暂时完结