前言
忙忙碌碌的几年,等到准备换工作时才想起来整理一些基础知识,有些比较基础的题目但是不常用也都忘记了,现在静下心来整理一番,题目多数是从其他前人的博客中摘录的,也有一些是自己觉得比较容易忘记的内容,希望自己能够在忙碌中经常回头看看,也希望能够帮助到其他在找工作的同学。同时题目也会在以后的工作中持续更新...
下面是面试常见的题目汇总
Git 工作流程
- 使用中央服务器辅助协作;
- 每人在服务器拥有一个以自己 id 为名称的分支;
- 各人只许推送更新到自己的分支,不允许推送到别人的分支或者 master;
- master 由专人管理,在合适时 merge 其它分支(开发初期每日自动 merge 各人分支,生产化后则由人工 merge 经过 review 的分支);
- 代码修改 merge 到 master 后,将同步到所有终端。
关于instrument
- Time Profiler:性能分析
- Zombies:检查是否访问了僵尸对象,但是这个工具只能从上往下检查,不智能
- Allocations:用来检查内存,写算法的那批人也用这个来检查
- Leaks:检查内存,看是否有内存泄露
如何调试BAD_ACCESS错误
- 重写object的respondsToSelector方法,现实出现EXEC_BAD_ACCESS前访问的最后一个object。
- EXC_BAD_ACCESS 这个错误,可以这么说,90%的错误来源在于对一个已经释放的对象进行release操作。
您开发常用的工具有哪些?
- 友盟统计
- 青花瓷charles:这个软件还是蛮不错的,可以用来过滤网络请求,模拟低速网路,还可以修改网络请求内容这些
- Reveal:调试页面不错,还有用来学习别人的demo时候可以拿来看UI层次结构,还可以用来标记可以设为透明的控件,用来优化性能
- 马克鳗
- Photoshop
- Frank DeLoupe: 支持 Retina 的屏幕拾色器,吐血推荐 Frank DeLoupe
- xScope: 每个前端工程师心中都有一把尺子 xScope
- Synergy: 在多台电脑间共享键盘和鼠标,只有一个系统一台显示器的前端工程师不是好厨子 Synergy
- Kaleidoscope: 最好用的 Diff 工具,没有之一 Kaleidoscope
- iA Writer: Markdown 写文档的工具,开始写文档的那天就是一个前端工程师走向成熟的标志 iA Writer for Mac
- Tower: Git 的 GUI 工具,支持 GitHub,而且比 GitHub 自己的客户端功能要强大,推荐使用 Git 又对 terminal 命令没有强迫症的人使用 Tower - The most powerful Git client for Mac
- Dash: 各种语言和工具、框架的文档 https://itunes.apple.com/us/app/dash-docs-snippets/id458034879?mt=12
iOS 的签名机制大概是怎样的?
签名机制:
- 先将应用内容通过摘要算法,得到摘要
- 再用私钥对摘要进行加密得到密文
- 将源文本、密文、和私钥对应的公钥一并发布
验证流程:
- 查看公钥是否是私钥方的
- 然后用公钥对密文进行解密得到摘要
- 将APP用同样的摘要算法得到摘要,两个摘要进行比对,如果相等那么一切正常
以上过程只要有一步出问题就视为无效。
#include与#import的区别、#import与@class 的区别
#include和#import 其效果相同,都是导入类中定义的行为(方法);
#import 不会引起交叉编译,确保头文件只会被导入一次;
@class 表明只定义了类的名称,而具体类的行为是未知的,一般用于.h 文件
@class比#import编译效率更高。此外@class和#import的主要区别在于解决引用死锁的问题。
请分别说明@public、@protected、@private的含义与作用
@public:对象的实例变量的作用域在任意地方都可以被访问 ;
@protected:对象的实例变量作用域在本类和子类都可以被访问 ;
@private:实例变量的作用域只能在本类(自身)中访问 .
请解释self = [super init]方法
容错处理,当父类初始化失败,会返回一个nil,表示初始化失败。由于继承的关系,子类是需要拥有父类的实例和行为,因此,我们必须先初始化父类,然后再初始化子类
什么情况使用 weak 关键字,相比 assign 有什么不同?
什么情况使用 weak 关键字
- 在 ARC 中,在有可能出现循环引用的时候,往往要通过让其中一端使用 weak 来解决,比如: delegate 代理属性
- 自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,自定义 IBOutlet 控件属性一般也使用 weak;当然,也可以使用strong。
不同点: - weak: 此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似, 然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。 而 assign
的“设置方法”只会执行针对“纯量类型” (scalar type,例如 CGFloat 或 NSlnteger 等)的简单赋值操作。 - assigin 可以用非 OC 对象,而 weak 必须用于 OC 对象
@protocol 和 category 中如何使用 @property
● 在 protocol 中使用 property 只会生成 setter 和 getter 方法声明,我们使用属性的目的,是希望遵守我协议的对象能实现该属性
● category 使用 @property 也是只会生成 setter 和 getter 方法的声明,如果我们真的需要给 category 增加属性的实现,需要借助于运行时的两个函数:
objc_setAssociatedObject
objc_getAssociatedObject
用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?
- 因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本.
- 如果我们使用是 strong ,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性.
copy 此特质所表达的所属关系与 strong 类似。然而设置方法并不保留新值,而是将其“拷贝” (copy)。 当属性类型为 NSString 时,经常用此特质来保护其封装性,因为传递给设置方法的新值有可能指向一个 NSMutableString 类的实例。这个类是 NSString 的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以,这时就要拷贝一份“不可变” (immutable)的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是“可变的” (mutable),就应该在设置新属性值时拷贝一份。
这个写法会出什么问题: @property (copy) NSMutableArray *array;
两个问题:
- 添加,删除,修改数组内的元素的时候,程序会因为找不到对应的方法而崩溃.因为 copy 就是复制一个不可变 NSArray 的对象;
- 使用了 atomic 属性会严重影响性能 ;
如何为 Class 定义一个对外只读对内可读写的属性?
在头文件中将属性定义为readonly, 在.m文件中将属性重新定义为readwrite
为什么很多内置类如UITableViewController的delegate属性都是assign而不是retain的?
所有的引用计数系统,都存在循环应用的问题。例如下面的引用关系:
• 对象a创建并引用到了对象b.
• 对象b创建并引用到了对象c.
• 对象c创建并引用到了对象b.这时候b和c的引用计数分别是2和1。
当a不再使用b,调用release释放对b的所有权,因为c还引用了b,所以b的引用计数为1,b不会被释放。b不释放,c的引用计数就是1,c也不会被释放。从此,b和c永远留在内存中。这种情况,必须打断循环引用,通过其他规则来维护引用关系。我们常见的delegate往往是assign方式的属性而不是retain方式 的属性,赋值不会增加引用计数,就是为了防止delegation两端产生不必要的循环引用。如果一个UITableViewController 对象a通过retain获取了UITableView对象b的所有权,这个UITableView对象b的delegate又是a, 如果这个delegate是retain方式的,那基本上就没有机会释放这两个对象了。自己在设计使用delegate模式时,也要注意这点。
使用block有什么好处?请使用NSTimer
写出一个使用block显示(在UILabel
上)秒表的代码
代码紧凑,传值、回调都很方便,省去了写代理的很多代码。
NSTimer封装成的block,实现方法:
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES callback:^() {
weakSelf.secondsLabel.text = ...
}
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
block和代理的区别,哪个更好?
代理回调更面向过程,block更面向结果。
如果需要在执行的不同步骤时被通知,你就要使用代理。
如果只需要请求的消息或者失败的详情,应该使用block。
block更适合与状态无关的操作,比如被告知某些结果.
block之间是不会相互影响的。
但是代理更像一个生产流水线,每个回调方法是生产线上的一个处理步骤,一个回调的变动可能会引起另一个回调的变动。要是一个对象有超过一个的不同事件,应该使用代理。
一个对象只有一个代理,要是某个对象是个单例对象,就不能使用代理。要是一个对象调用方法需要返回一些额外的信息,就可能需要使用代理。
在block内如何修改block外部变量?
默认情况下,在block中访问的外部变量是复制过去的,即:写操作不对原变量生效。但是你可以加上__block来让其写操作生效,示例代码如下:
__block int a = 0;
void (^foo)(void) = ^{
a = 1;
}
f00();
类别和类扩展的区别
- category和extensions的不同在于后者可以添加属性。另外后者添加的方法是必须要实现的。
- extensions可以认为是一个私有的Category。
分类的作用?分类和继承的区别?
- 分类可以在不获悉,不改变原来代码的情况下往里面添加新的方法,只能添加,不能删除修改,并且如果分类和原来类中的方法产生名称冲突,则分类将覆盖原来的方法,因为分类具有更高的优先级。
- 继承可以增加,修改或者删除方法,并且可以增加属性;但是分类只能添加方法,不能删除修改,也不能增加属性。
重写一个类的方法用继承好还是分类好? 为什么?
一般情况用分类好,用Category去重写类的方法,仅对本Category有效,不会影响到其他类与原有类的关系。
addObserver:forKeyPath:options:context:各个参数的作用分别是什么,observer中需要实现哪个方法才能获得KVO回调?
// 添加键值观察
/*
1 观察者,负责处理监听事件的对象
2 观察的属性
3 观察的选项
4 上下文
*/
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"Person Name"];
observer中需要实现一下方法:
// 所有的 kvo 监听到事件,都会调用此方法
/*
1. 观察的属性
2. 观察的对象
3. change 属性变化字典(新/旧)
4. 上下文,与监听的时候传递的一致
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
若一个类有实例变量NSString *_foo,调用setValue:forKey:时,可以以foo还是_foo作为key?
都可以。
KVC的keyPath中的集合运算符如何使用?
- 必须用在集合对象上或普通对象的集合属性上
- 简单集合运算符有@avg, @count , @max , @min ,@sum,
- 格式 @"@sum.age"或 @"集合属性.@max.age"
KVC和KVO的keyPath一定是属性么?
KVO支持实例变量
我们说的oc是动态运行时语言是什么意思?
OC的动态运行时,是指OC具有动态类型和动态绑定的特性。动态类型能使程序直到执行时才确定对象的所属类, 其具体引用的对象在运行时才能确定。 动态绑定能使程序直到运行时才确定调用对象的实际方法。
多态。 主要是将数据类型的确定由编译时,推迟到了运行时。
这个问题其实浅涉及到两个概念,运行时和多态。
简单来说,运行时机制使我们直到运行时才去决定一个对象的类别,以及调用该类别对象指定方法。
多态:不同对象以自己的方式响应相同的消息的能力叫做多态。意思就是假设生物类(life)都用有一个相同的方法-eat;
那人类属于生物,猪也属于生物,都继承了life后,实现各自的eat,但是调用是我们只需调用各自的eat方法。
也就是不同的对象以自己的方式响应了相同的消息(响应了eat这个选择器)。
因此也可以说,运行时机制是多态的基础?~~~
对于语句NSString*obj = [[NSData alloc] init]; obj在编译时和运行时分别时什么类型的对象?
编译时是NSString的类型;
运行时是NSData类型的对象;
isMemberOfClass 和 isKindOfClass 联系与区别
- 联系:两者都能检测一个对象是否是某个类的成员
- 区别:isKindOfClass不仅用来确定一个对象是否是一个类的成员,也可以用来确定一个对象是否派生自该类的类的成员 ,而isMemberOfClass 只能做到第一点。
举例:如 ClassA派 生 自NSObject 类 , ClassA *a = [ClassA alloc] init];,[a isKindOfClass:[NSObject class]] 可以检查出 a 是否是 NSObject派生类 的成员,但 isMemberOfClass 做不到。
一个objc对象的isa的指针指向什么?有什么作用?
指向他的类对象,从而可以找到对象上的方法
objc中的类方法和实例方法有什么本质区别和联系?
类方法:
- 类方法是属于类对象的
- 类方法只能通过类对象调用
- 类方法中的self是类对象
- 类方法可以调用其他的类方法
- 类方法中不能访问成员变量
- 类方法中不定直接调用对象方法
实例方法:
- 实例方法是属于实例对象的
- 实例方法只能通过实例对象调用
- 实例方法中的self是实例对象
- 实例方法中可以访问成员变量
- 实例方法中直接调用实例方法
- 实例方法中也可以调用类方法(通过类名)
请描述一个你所遇到retain cycle例子
block中的循环引用一个ViewController
请谈谈内存的使用和优化的注意事项
● 重用问题:如UITableViewCells、UICollectionViewCells、UITableViewHeaderFooterViews设置正确的reuseIdentifier,充分重用;
● 尽量把views设置为不透明:当opque为NO的时候,图层的半透明取决于图片和其本身合成的图层为结果,可提高性能;
● 不要使用太复杂的XIB/Storyboard:载入时就会将XIB/storyboard需要的所有资源,包括图片全部载入内存,即使未来很久才会使用。那些相比纯代码写的延迟加载,性能及内存就差了很多;
● 选择正确的数据结构:学会选择对业务场景最合适的数组结构是写出高效代码的基础。比如,数组: 有序的一组值。使用索引来查询很快,使用值查询很慢,插入/删除很慢。字典: 存储键值对,用键来查找比较快。集合: 无序的一组值,用值来查找很快,插入/删除很快。gzip/zip压缩:当从服务端下载相关附件时,可以通过gzip/zip压缩后再下载,使得内存更小,下载速度也更快。
● 延迟加载:对于不应该使用的数据,使用延迟加载方式。对于不需要马上显示的视图,使用延迟加载方式。比如,网络请求失败时显示的提示界面,可能一直都不会使用到,因此应该使用延迟加载。
● 数据缓存:对于cell的行高要缓存起来,使得reload数据时,效率也极高。而对于那些网络数据,不需要每次都请求的,应该缓存起来,可以写入数据库,也可以通过plist文件存储。
● 处理内存警告:一般在基类统一处理内存警告,将相关不用资源立即释放掉重用大开销对象:一些objects的初始化很慢,比如NSDateFormatter
和NSCalendar
,但又不可避免地需要使用它们。通常是作为属性存储起来,防止反复创建。
● 避免反复处理数据:许多应用需要从服务器加载功能所需的常为JSON或者XML格式的数据。在服务器端和客户端使用相同的数据结构很重要;
● 使用Autorelease Pool:在某些循环创建临时变量处理数据时,自动释放池以保证能及时释放内存;
● 正确选择图片加载方式:详情阅读UIImage加载方式
线程与进程的区别和联系?
- 一个程序至少要有进程,一个进程至少要有一个线程.
- 进程:资源分配的最小独立单元,进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
- 线程:进程下的一个分支,是进程的实体,是CPU调度和分派的基本单元,它是比进程更小的能独立运行的基本单位,线程自己基本不拥有系统资源,只拥有一点在运行中必不可少的资源(程序计数器、一组寄存器、栈),但是它可与同属一个进程的其他线程共享进程所拥有的全部资源。
- 进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性。
- 进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。
- 但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
请简单介绍下iOS的多线程
iOS中的多线程,是Cocoa框架下的多线程,通过Cocoa的封装,可以让我们更为方便的使用线程,做过C++的同学可能会对线程有更多的理解,比如线程的创立,信号量、共享变量有认识,Cocoa框架下会方便很多,它对线程做了封装,有些封装,可以让我们创建的对象,本身便拥有线程,也就是线程的对象化抽象,从而减少我们的工程,提供程序的健壮性。
GCD
GCD是(Grand Central Dispatch)的缩写 ,从系统级别提供的一个易用地多线程类库,具有运行时的特点,能充分利用多核心硬件。GCD的API接口为C语言的函数,函数参数中多数有Block, 为我们提供强大的“接口”。
NSOperation与Queue
NSOperation是一个抽象类,它封装了线程的细节实现,我们可以通过子类化该对象,加上NSQueue来同面向对象的思维,管理多线程程序。
NSThread
NSThread是一个控制线程执行的对象,它不如NSOperation抽象,通过它我们可以方便的得到一个线程,并控制它。但NSThread的线程之间的并发控制,是需要我们自己来控制的,可以通过NSCondition实现。
什么时候选择NSOperation? NSOperation 相比于 GCD 有哪些优势?
项目中使用NSOperation的优点是NSOperation是对线程的高度抽象,在项目中使用它,会使项目的程序结构更好,子类化NSOperation的设计思路,是具有面向对象的优点(复用、封装),使得实现是多线程支持,而接口简单,建议在复杂项目中使用。
● 提供了在 GCD 中不那么容易复制的有用特性。
● 提供了任务的状态:isExecuteing, isFinished, 可以很方便的取消一个NSOperation的执行
● 可以更容易的添加任务的依赖关系
项目中使用GCD的优点是GCD本身非常简单、易用,对于不复杂的多线程操作,会节省代码量,而Block参数的使用,会是代码更为易读,建议在简单项目中使用。
使用atomic一定是线程安全的吗?
不是,atomic的本意是指属性的存取方法是线程安全的(thread safe),并不保证整个对象是线程安全的。比如,声明一个NSMutableArray的原子属性stuff,此时self.stuff 和self.stuff = othersulf都是线程安全的。但是,使用[self.stuff objectAtIndex:index]就不是线程安全的,需要用锁来保证线程安全性。
GCD的队列(dispatch_queue_t)分哪两种类型?
- 串行队列Serial Dispatch Queue
- 并行队列Concurrent Dispatch Queue
dispatch_barrier_async的作用是什么?
在并行队列中,为了保持某些任务的顺序,需要等待一些任务完成后才能继续进行,使用 barrier 来等待之前任务完成,避免数据竞争等问题。
dispatch_barrier_async 函数会等待追加到Concurrent Dispatch Queue并行队列中的操作全部执行完之后,然后再执行 dispatch_barrier_async 函数追加的处理,等 dispatch_barrier_async 追加的处理执行结束之后,Concurrent Dispatch Queue才恢复之前的动作继续执行。
打个比方:比如你们公司周末跟团旅游,高速休息站上,司机说:大家都去上厕所,速战速决,上完厕所就上高速。超大的公共厕所,大家同时去,程序猿很快就结束了,但程序媛就可能会慢一些,即使你第一个回来,司机也不会出发,司机要等待所有人都回来后,才能出发。 dispatch_barrier_async 函数追加的内容就如同 “上完厕所就上高速”这个动作。
(注意:使用 dispatch_barrier_async ,该函数只能搭配自定义并行队列 dispatch_queue_t 使用。不能使用: dispatch_get_global_queue ,否则 dispatch_barrier_async 的作用会和 dispatch_async 的作用一模一样。 )
UITableViewCell上有个UILabel,显示NSTimer实现的秒表时间,手指滚动cell过程中,label是否刷新,为什么?
这是否刷新取决于timer加入到Run Loop中的Mode是什么。Mode主要是用来指定事件在运行循环中的优先级的,分为:
● NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认,空闲状态
● UITrackingRunLoopMode:ScrollView滑动时会切换到该Mode
● UIInitializationRunLoopMode:run loop启动时,会切换到该Mode
● NSRunLoopCommonModes(kCFRunLoopCommonModes):
苹果公开提供的Mode有两个:
NSDefaultRunLoopMode(kCFRunLoopDefaultMode)
NSRunLoopCommonModes(kCFRunLoopCommonModes)
在编程中:如果我们把一个NSTimer对象以NSDefaultRunLoopMode添加到主运行循环中的时候, ScrollView滚动过程中会因为Mode的切换,而导致NSTimer将不再被调度。当我们滚动的时候,也希望不调度,那就应该使用默认模式。但是,如果希望在滚动时,定时器也要回调,那就应该使用NSRunLoopCommonModes
runtime 如何实现 weak 属性
runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。
runtime如何通过selector找到对应的IMP地址?
每一个类对象中都一个方法列表,方法列表中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.
+(void)load; +(void)initialize;有什么用处?
在Objective-C中,runtime会自动调用每个类的两个方法。+load会在类初始加载时调用,+initialize会在第一次调用类的类方法或实例方法之前被调用。这两个方法是可选的,且只有在实现了它们时才会被调用。
如何访问并修改一个类的私有属性?
方法一:KVC(键值编码)
Person *p = [Person new];
//修改私有属性的值
[p setValue:@"yyMae" forKey:@"name"];
//访问私有属性的值
NSString *name = [p valueForKey:@"name"];
方法二:通过runtime获取或修改一个类私有属性的值
Person *p = [Person new];
// IVar是runtime声明的一个宏
unsigned int count = 0; //count记录变量的数量
// 获取类的所有属性变量
Ivar *members = class_copyIvarList([Person class], &count);
for (int i = 0; i < count; i++) {
Ivar ivar = members[i];
// 取得属性名并转成字符串类型
const char *memberName = ivar_getName(ivar);
NSLog(@"%s",memberName);
Ivar name = members[0];
// 修改属性值
object_setIvar(Person, name, @"yyMae");
}
Objective-C 如何对已有的方法,添加自己的功能代码以实现类似记录日志这样的功能?
这题目主要考察的是runtime如何交换方法
先在分类中添加一个方法,注意不能重写系统方法,会覆盖先在分类中添加一个方法,注意不能重写系统方法,会覆盖
+ (NSString *)myLog
{
// 这里写打印行号,什么方法,哪个类调用等等
}
然后交换方法
// 加载分类到内存的时候调用
+ (void)load
{
// 获取description方法地址
Method description = class_getClassMethod(self, @selector(description));
// 获取myLog方法地址
Method myLog = class_getClassMethod(self, @selector(myLog));
// 交换方法地址,相当于交换实现方式
method_exchangeImplementations(description, myLog);
}
下回调用description方法时,实际上调用的是myLog的方法。
Splitview Controller是什么?
UISplitViewController适合用于主从界面的情况(Master view→Detail view),Detail view跟随Master view进行更新。
AppDelegate扮演着什么样的角色?
创建应用程序之后之后,默认有AppDelegate.h文件与AppDelegate.m文件。
AppDelegate为整个应用的一个代理,提供程序启动、退出等类似监控的接口,控制着应用的生命周期。
请谈谈应用的生命周期
- 启动程序
- willFinishLaunchingWithOptions
- didFinishLaunchingWithOptions
- applicationDidBecomeActive
- 按下home键
- applicationWillResignActive
- applicationDidEnterBackground
- 双击home键,再打开程序
- applicationWillEnterForeground
- applicationDidBecomeActive
- 当程序将要退出是被调用,通常是用来保存数据
- applicationWillTerminate
当系统出现内存警告时会发生什么?
- 会将不在当前窗口上的view暂时移除
- 如果放任内存警告,最终会导致软件强制被系统关闭
网友评论