iOS开发篇-内存管理(中)

简介: 现在iOS开发已经是ARC甚至是swift的时代,但是内存管理仍是一个重点关注的问题,如果只知盲目开发而不知个中原理,踩坑就跳不出来了,理解好内存管理,能让我们写出更有质量的代码。

下面举例说明自动释放池的工作流程:


场景:现在xiaoming和xiaohong都想和小狗一起玩耍,但是他们的需求不一样,他们的玩耍时间不一样,流程如下:


方法一:

//
//  main.m
//  TextARC
//
//  Created by taobaichi on 2017/3/27.
//  Copyright © 2017年 MaChao. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "Dog.h"
int main(int argc, const char * argv[]) {
    //创建一个自动释放池
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc]init];
    //模拟:宠物中心派出小狗
    Dog * dog  = [[Dog alloc]init];
    //模拟:xiaoming需要和小狗玩耍,需要将其引用计数加1
    [dog retain];
    NSLog(@"xiaoming和小狗玩耍,小狗的引用计数为 %ld",dog.retainCount);
    //模拟:xiaohong需要和小狗玩耍,需要将其引用计数加1
    [dog retain];
    NSLog(@"xiaohong和小狗玩耍,小狗的引用技术为 %ld",dog.retainCount);
    //模拟:xiaoming确定不想和小狗玩耍了,需要将其引用计数减1
    [dog release];
    NSLog(@"xiaoming确定不想和小狗玩了,小狗的引用计数:%ld",dog.retainCount);
    //模拟:xiaohong不确定何时不想和小狗玩耍了,将其设置为自动释放
    [dog autorelease];
    NSLog(@"加入自动释放池,小狗的引用计数: %ld",dog.retainCount);
    [dog release];
    NSLog(@"释放池子");
    [pool release];
    return 0;
}


方法二:

//
//  main.m
//  TextARC
//
//  Created by taobaichi on 2017/3/27.
//  Copyright © 2017年 MaChao. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "Dog.h"
int main(int argc, const char * argv[]) {
        @autoreleasepool {
            //模拟:宠物中心派出小狗
            Dog * dog = [[Dog alloc]init];
            //模拟:xiaoming需要和小狗玩耍,需要将其引用计数加1
            [dog retain];
            NSLog(@"xiaoming需要和小狗玩耍,小狗的引用计数为 %ld",dog.retainCount);
            //模拟:xiaohong需要和小狗玩耍,需要将其引用计数加1
            [dog retain];
            NSLog(@"xiaohong需要和小狗玩耍,小狗的引用计数: %ld",dog.retainCount);
            //模拟:xiaoming确定不想和小狗玩耍了,需要将其引用技术减1
            [dog release];
            NSLog(@"xiaoming确定不想和小狗玩耍了,小狗的引用计数: %ld",dog.retainCount);
            //模拟:xiaohong不确定何时不想和小狗玩耍了,将其设置为自动释放
            [dog autorelease];
            NSLog(@"加入自动释放池,小狗的引用计数: %ld",dog.retainCount);
            //没人需要和小狗玩耍了,将其引用技术减1
            [dog release];
            NSLog(@"释放池子");
        }
    return 0;
}


输出结果如下(两种方法输出结果完全一致):

2017-03-27 14:10:40.808032 TextARC[3643:121392] 小狗被派出去啦! 初始引用计数为 1
2017-03-27 14:10:40.808204 TextARC[3643:121392] xiaoming和小狗玩耍,小狗的引用计数为 2
2017-03-27 14:10:40.808227 TextARC[3643:121392] xiaohong和小狗玩耍,小狗的引用技术为 3
2017-03-27 14:10:40.808246 TextARC[3643:121392] xiaoming不想和小狗玩了,小狗的引用计数:2
2017-03-27 14:10:40.808269 TextARC[3643:121392] 加入自动释放池,小狗的引用计数: 2
2017-03-27 14:10:40.808291 TextARC[3643:121392] 释放池子
2017-03-27 14:10:40.808339 TextARC[3643:121392] 小狗回到宠物中心


可以看到,当池子释放后,dog对象才被释放,因此在池子释放之前,xiaohong都可以尽情地和小狗玩耍


使用自动释放池需要注意:


  • 自动释放池实质上只是在释放的时候给池子中所有对象发送release消息,不保证对象一定会销毁,如果自动释放池向对象发送release消息后对象的引用计数仍大于1,对象就无法销毁。


  • 自动释放池中的对象会集中 在同一时间释放,如果操作需要生成的对象较多占用内存空间大,可以使用多个释放池来进行优化,比如在一个循环中需要创建大量的临时变量,可以创建内部的池子降低占用内存峰值。


-autorelease不会改变对象的引用计数


自动释放池的常见问题:


在管理对象释放的问题上,自动释放池帮助我们节省了大量的时间,但是有时候它却未必会达到我们期望的效果,比如在一个循环事件中,如果循环次数较大或者事件处理占用内存较大,就会导致内存占用不断增长,可能会导致不希望看到的后果。


示例代码:

for (int i = 0; i < 100000; i++) {
        NSString * log = [NSString stringWithFormat:@"%d",i];
        NSLog(@"%@",log);
    }


前面讲过,自动释放池的释放时间是确定的,这个例子中自动释放池会在循环事件结束时释放,那么问题来了:在这个十万次的循环中,每次都会生成一个字符串并打印,这些字符串对象都放在池子中直到循环结束才会释放,因此在循环期间内存不增长。


这类问题的解决方案是在循环中创建新的自动释放池,多少个就你和释放一次由我们自行决定。

for (int i = 0; i < 100000; i++) {
        @autoreleasepool {
            NSString * log = [NSString stringWithFormat:@"%d",i];
            NSLog(@"%@",log);
        }
    }


3. iOS的内存管理规则


3.1 基本原则


  • 当你通过newalloccopy方法创建一个对象时,它的引用计数为1,当不再使用该对象时应该向对象发送release或者autorelease消息释放对象。


  • 当你通过其他方法获得一个对象时,如果对象引用计数为1且被设置为autorelease,则不需要执行任何释放对象的操作


  • 如果你打算取得对象所有权,就需要保留对象并在操作完成之后释放,且必须保证retainrelease次数对等


应用到文章开头的例子中,小朋友每申请一个小狗(生成对象),最后都要归还到宠物中心(释放对象),如果只申请而不归还(对象创建了没有释放),那宠物中心的小狗就会越来越少(可用内存越来越少),到最后一个小狗都没有了(内存被耗尽),其他小朋友就再也没有小狗可申请了(无资源可使用),因此,必须要遵守规则:申请必须归还(规则1),申请几个必须归还几个(规则3),如果小狗被设定归还时间则不用小朋友主动归还(规则2)。


3.2 ARC


在MRC时代,必须严格遵守以上规则,否则内存问题将成为恶魔一样的存在,然而来到ARC时代,事情似乎变得轻松了,不用再写无止尽的retainrelease似乎让开发变得轻松,对初学者变得更友好。


ObjC2.0引入了垃圾回收机制,然而由于垃圾回收机制会对移动设备产生某些不好的影响(例如由于垃圾清理造成的卡顿),iOS并不支持这个机制,苹果的解决方案就是ARC(自动引用计数)。


iOS5 以后,我们可以开启ARC模式,ARC可以理解成一位管家,这个管家会帮我们向对象发送retainrelease语句,不再需要我们手动添加了,我们可以更舒心地创建或引用对象,简化内存管理步骤,节省大量的开发时间。


实际上,ARC并不是垃圾回收,也并不是不需要内存管理了,它是隐式的内存管理,编译器在编译的时候会在代码插入合适的retainrelease语句,相当于在背后帮我们完成了内存管理的工作。


注意:


  • 如果你的工程历史比较悠久,可以将其从MRC转换成ARC,跟上时代的步伐更好的维护


  • 如果你的工程引用了某些不支持ARC的库,可以在Build PhasesCompile Sources将对应的.m文件的编译器参数配置为-fno-objc-arc


  • ARC能帮我们简化内存管理,但不代表它是万能的,还是有它不能处理的情况,这些需要我们手动处理,比如循环引用、非ObjC对象、Core Foundation中的malloc()或者free()等等


思考:MRC有什么缺点,ARC有什么局限性?


3.3 ARC的修饰符


ARC提供四种修饰符,分别是strongweakautoreleasingunsafe_unretained


__strong:强引用,持有所指向对象的所有权,无修饰符情况下的默认值。如需强制释放,可置nil。


比如我们常用的定时器

NSTimer * timer = [NSTimer timerWit...];


相当于

NSTimer * __strong timer = [NSTimer timerWith...];


当不需要使用的时候,强制销毁定时器

[timer invalidate];
 timer = nil;


__weak:弱引用,不持有所指向对象的所有权,引用指向的对象内存被回收之后,引用本身会置nil,避免野指针.


比如避免循环引用的弱引用声明:

__weak typeof(self)weakSelf = self;


__autoreleasing:自动释放对象的引用,一般用于传递参数


比如一个读取数据的方法

-(void)loadData:(NSError **)error


当你调用的时候会发现这样的提示

NSError * error;
[self loadData:(NSError *__autoreleasing *)]


这是编译器自动帮我们插入以下代码

NSError * error;
NSError * __autoreleasing  temErr = error;
[self loadData:&tmpErr];


__unsafe_unretained:为兼容iOS5以下版本的产物,可以理解成MRC下的weak,现在基本用不到,这里不做描述.

相关文章
|
2月前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台策略
在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。
|
17天前
|
iOS开发 开发者 MacOS
深入探索iOS开发中的SwiftUI框架
【10月更文挑战第21天】 本文将带领读者深入了解Apple最新推出的SwiftUI框架,这一革命性的用户界面构建工具为iOS开发者提供了一种声明式、高效且直观的方式来创建复杂的用户界面。通过分析SwiftUI的核心概念、主要特性以及在实际项目中的应用示例,我们将展示如何利用SwiftUI简化UI代码,提高开发效率,并保持应用程序的高性能和响应性。无论你是iOS开发的新手还是有经验的开发者,本文都将为你提供宝贵的见解和实用的指导。
108 66
|
3天前
|
存储 监控 API
app开发之安卓Android+苹果ios打包所有权限对应解释列表【长期更新】-以及默认打包自动添加权限列表和简化后的基本打包权限列表以uniapp为例-优雅草央千澈
app开发之安卓Android+苹果ios打包所有权限对应解释列表【长期更新】-以及默认打包自动添加权限列表和简化后的基本打包权限列表以uniapp为例-优雅草央千澈
|
28天前
|
开发框架 Android开发 iOS开发
安卓与iOS开发中的跨平台策略:一次编码,多平台部署
在移动应用开发的广阔天地中,安卓和iOS两大阵营各占一方。随着技术的发展,跨平台开发框架应运而生,它们承诺着“一次编码,到处运行”的便捷。本文将深入探讨跨平台开发的现状、挑战以及未来趋势,同时通过代码示例揭示跨平台工具的实际运用。
|
1月前
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。
|
2月前
|
传感器 人工智能 物联网
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发,以及面临的挑战和未来趋势,旨在帮助读者深入了解并掌握这些关键技术。
52 6
|
2月前
|
存储 前端开发 Swift
探索iOS开发:从新手到专家的旅程
本文将带您领略iOS开发的奇妙之旅,从基础概念的理解到高级技巧的掌握,逐步深入iOS的世界。文章不仅分享技术知识,还鼓励读者在编程之路上保持好奇心和创新精神,实现个人成长与技术突破。
|
2月前
|
安全 IDE Swift
探索iOS开发之旅:从初学者到专家
在这篇文章中,我们将一起踏上iOS开发的旅程,从基础概念的理解到深入掌握核心技术。无论你是编程新手还是希望提升技能的开发者,这里都有你需要的指南和启示。我们将通过实际案例和代码示例,展示如何构建一个功能齐全的iOS应用。准备好了吗?让我们一起开始吧!
|
2月前
|
安全 Swift iOS开发
Swift 与 UIKit 在 iOS 应用界面开发中的关键技术和实践方法
本文深入探讨了 Swift 与 UIKit 在 iOS 应用界面开发中的关键技术和实践方法。Swift 以其简洁、高效和类型安全的特点,结合 UIKit 丰富的组件和功能,为开发者提供了强大的工具。文章从 Swift 的语法优势、类型安全、编程模型以及与 UIKit 的集成,到 UIKit 的主要组件和功能,再到构建界面的实践技巧和实际案例分析,全面介绍了如何利用这些技术创建高质量的用户界面。
34 2
|
2月前
|
安全 数据处理 Swift
深入探索iOS开发中的Swift语言特性
本文旨在为开发者提供对Swift语言在iOS平台开发的深度理解,涵盖从基础语法到高级特性的全面分析。通过具体案例和代码示例,揭示Swift如何简化编程过程、提高代码效率,并促进iOS应用的创新。文章不仅适合初学者作为入门指南,也适合有经验的开发者深化对Swift语言的认识。
57 9