iOS面试题 - 内存管理

1、什么是ARC?

  • 为什么要使用内存管理

严格的内存管理,能够是我们的应用程在性能上有很大的提高。如果忽略内存管理,可能导致应用占用内存过高,导致程序崩溃

  • 引用计数工作原理

对象创建出来时,其保留计数至少为1。若想令其继续存活,则调用retain方法,这时对象引用计数+1。要是不再使用此对象,不想令其继续存活,那就调用releaseautorelease方法使对象引用计数减1。最终当保留计数归零时,对象就回收了(deallocated)

NSObject协议声明了下面三个方法用于操作计数器,以递增或递减其值:

  • retain递增保留计数。
  • release递减保留计数。
  • autorelease自动释放(即一个Runloop结束时),再递减保留计数。

注意:查看保留计数的方法叫做retainCount,此方法不太有用。

对象操作与Objective-C方法的对应

对象操作 Objective-C方法
生成并持有对象 alloc/new/copy/mutableCopy等方法
持有对象 retain方法
释放对象 release方法
废弃对象 dealloc 方法
  • MRC(手动引用计数)

MRC(手动引用计数)顾名思义就是自己管理对象的引用计数,经历过MRC时代,肯定知道需要使用手动的通过retain使对象引用计数加1,使对象不被释放,当不需要使用对象的时候使用release使对象的引用计数减1,当对象的引用计数为0使,对象被释放。所以代码中大量的retainrelease操作

  • ARC(自动引用计数)

ARC(Automatic Reference Couting)即自动引用计数,它是OC的内存管理机制,简单来说,就是代码中自动加入retain/release,原先需要手动添加用来处理内存管理的引用计数的代码可以自动由编译器完成了。

ARC的使用是为了解决对象retain/release匹配问题,以前因手动管理而造成内存泄露或者重复释放的问题将不复存在。ARC 管理对象生命期的办法基本上就是:在合适的地方插入 “保留” 及 “释放”操作。

  • 注意

ARC只负责管理Objective-C对象的内存。尤其要注意: CoreFoundation 对象不归 ARC管理,开发者必须适时调用 CFRetain/CFRelease。

2、什么情况下会出现循环引用?

循环引用是指两个或两个以上的对象相互强引用,导致所有对象无法被释放的现象。这是内存泄露的一种情况。看个例子:

@interface Father: NSObject
@property (nonatomic, strong) Son *son;
@end

@interface Son: NSObject
@property (nonatomic, strong) Father *father;
@end

可以看到上述代码有两个类分别是FatherSonFatherSon进行了强引用,Son又对Father进行了强引用,因而造成了循环引用。

解决办法是将Father中的Son对象属性由Strong改为weak,或者将Son中的father属性改为weak,一方为弱引用即可。

常用的循环引用情况:

  • 代理的循环引用
  • 定时器的循环引用
  • block的循环引用
  • OC对象内存处理

3、说明并比较关键词:Strongweakassigncopy

OC中,基本数据类型的默认关键字是atomic、readwrite、 assgin;普通属性的默认关键词是atomic、readwrite、strong

strong:表示指向并拥有该对象。其修饰的对象引用计数会增加1.该对象只要引用计数不为0,就不会被销毁。当然,强行将其设置为nil,也可以销毁它

weak:表示指向但不拥有该对象。其修饰的对象引用计数不会增加。无须手动设置,该对象会自行在内存中销毁。weak在对象释放后会置为nil

assgin:主要用于修饰基本数据类型,如NSIntegerCGFloat,这些数值主要存在于栈中

copycopystrong类似。不同之处是,strong的复制是多个指针指向同一个地址,而copy的复制是每次会在内存中复制一份对象,指针指向不同的地址。copy一般用在修饰有对应可变类型的不可变对象上,如:NSString、NSArray、NSDictionary

4、说明并比较关键词:atomicnonatomic

atomic:属性的默认行为。修饰的对象会保证settergetter的完整性,任何线程访问它都可以得到一个完整的初始化后的对象。因为要保证操作完成,所以速度比较慢。atomicnonatomic安全,但是也不是绝对的安全。

例如:当多个线程同时调用setget时,就会导致获得的对象值不一样。好比如三个线程A 、B 、C,如果线程A调了getter,与此同时线程B线程C都调了setter。那最后线程A 执行getter获得到的值有3种可能:可能是在线程B、线程C执行setter之前的值,也可能是线程B执行setter之后的值,也可能是线程C执行setter 的值。最终这个属性的值,是线程B线程C执行setter之后的值

要想线程决定的安全,需要使用锁,比如:@synchronized、NSLock、pthread

nonatomic:修饰的对象不保证settergetter的完整性,所以当多个线程访问它时,它可能会返回未初始化的对象。正因为如此,nonatomicatomic的速度快,但是线程也是不安全的。

但是实际的开发过程中,经常使用nonatomic

atomic and nonatomic

5、说明并比较关键词:__weak__block

  • __weak
__weak specifies a reference that does not keep the referenced object alive. 
A weak reference is set to nil when there are no strong references to the object.

__weakweak基本相同,只能在ARC模式下使用,修饰对象可以避免循环引用,不会增加引用。__weak只能修饰对象(比如NSString等),不能修饰基本数据类型(比如int等)

避免循环引用情况

__weak __typeof(self) weakSelf = self; 
self.testBlock = ^{
    [weakSelf doSomeThing];
});

弱引用不会影响对象的释放,但是当对象被释放时,所有指向它的弱引用都会自定被置为nil,这样可以防止野指针。

  • __block
A powerful feature of blocks is that they can modify variables in the same lexical scope. 
You signal that a block can modify a variable using the __block storage type modifier. 

At function level are __block variables. These are mutable within the block (and the enclosing scope) 
and are preserved if any referencing block is copied to the heap.

__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。

  • MRC下使用__block是可以避免循环引用的;
  • ARC下使用__block typeof(self)weakSelf = self;因为block是用过添加引用来访问实例变量的,所以self会被retain一次,block也是一个强引用,会引起循环引用,所以使用__weak

使用__block修饰变量使之在block内可被修改

在一个block里头如果使用了在block之外的变量,会将这份变量先复制一份再使用,也就是说对于当前的block来说,所有的外部的变数都是只可读的,不可改的。

如果我们想让某个block可以修改某个外部的变量,则需要使用__block修饰变量

__block int i = 1;
void (^block)(void) = ^{
    i = i + 1;
};

从另一个角度说,使用了__block关键字的变量会将变量从栈上复制到堆上。栈上那个变量会指向复制到堆上的变量。

__weak和__block对比

  • __block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。
  • __weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。
  • __block对象可以在block中被重新赋值,__weak不可以。
  • __block对象在ARC下可能会导致循环引用,非ARC下会避免循环引用,__weak只在ARC下使用,可以避免循环引用。

__weak and a __block

6、内存管理语法

请问下面的代码打印结果是什么?

NSString *firstString = @"hello";
NSString *secondString = @"hello";

if (firstString == secondString) {
    NSLog(@"Equal");
} else {
    NSLog(@"Not equal");
}

运行输出:Equal

  • 1、==这个符号判断的不是这两个值是否相等,而是这两个指针是否指向同一个对象。如果要判断两个NSString的值是否相等,那么应该用isEqualToString这个方法

  • 2、上面代码中,两个指针指向不同的对象,尽管它们的值是相等。但是iOS的编译器优化了内存分配,当两个指针指向两个值一样的NSString时,两者指向同一个内存地址,所以会指向判断为真,打印Equal

7、@property的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的?

  • @property 的本质

@property = ivar + getter + setter;

@property其实就是在编译阶段由编译器自动帮我们生成ivar成员变量,getter方法,setter方法

“属性” (property)有两大概念:ivar(实例变量)、存取方法(access method = getter + setter)。

“属性” (property)作为Objective-C的一项特性,主要的作用就在于封装对象中的数据。Objective-C对象通常会把其所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”(access method)来访问。其中,“获取方法” (getter)用于读取变量值,而“设置方法” (setter)用于写入变量值。

这个概念已经定型,并且经由“属性”这一特性而成为Objective-C 2.0的一部分。 而在正规的Objective-C编码风格中,存取方法有着严格的命名规范。 正因为有了这种严格的命名规范,所以Objective-C这门语言才能根据名称自动创建出存取方法。

其实也可以把属性当做一种关键字,其表示:编译器会自动写出一套存取方法,用以访问给定类型中具有给定名称的变量。 所以你也可以这么说:

@property = getter + setter;

例如下面这个类:

@interface Person : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end

上述代码写出来的类与下面这种写法等效:

@interface Person : NSObject
- (NSString *)firstName;
- (void)setFirstName:(NSString *)firstName;
- (NSString *)lastName;
- (void)setLastName:(NSString *)lastName;
@end

propertyruntime中是objc_property_t定义如下:

typedef struct objc_property *objc_property_t;

objc_property是一个结构体,包括nameattributes,定义如下:

struct property_t {
    const char *name;
    const char *attributes;
};

attributes本质是objc_property_attribute_t,定义了property的一些属性,定义如下:

/// Defines a property attribute
typedef struct {
    const char *name;           /**< The name of the attribute */
    const char *value;          /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;

attributes的具体内容是什么呢?其实,包括:类型,原子性,内存语义和对应的实例变量。

例如:我们定义一个string的属性@property (nonatomic, copy) NSString *string;,通过 property_getAttributes(property)获取到attributes并打印出来之后的结果为T@"NSString",C,N,V_string

其中T就代表类型,可参阅Type Encodings,C就代表CopyN代表nonatomicV就代表对于的实例变量。

  • ivar、getter、setter的生成

完成属性定义后,编译器会自动编写访问这些属性所需的方法,此过程叫做自动合成(autosynthesis)。需要强调的是,这个过程由编译器在编译期执行,所以编辑器里看不到这些合成方法的源代码。除了生成方法代码getter、setter之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字

在前例中,会生成两个实例变量,其名称分别为 _firstName_lastName。也可以在类的实现代码里通过 @synthesize 语法来指定实例变量的名字.

@implementation Person
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end

反编译相关的代码,大致生成了五个东西

  • OBJC_IVAR_$类名$属性名称 :该属性的“偏移量” (offset),这个偏移量是“硬编码” (hardcode),表示该变量距离存放对象的内存区域的起始地址有多远。
  • settergetter方法对应的实现函数
  • ivar_list :成员变量列表
  • method_list :方法列表
  • prop_list :属性列表

也就是说我们每次在增加一个属性,系统都会在 ivar_list中添加一个成员变量的描述,在 method_list中增加 settergetter方法的描述,在属性列表中增加一个属性的描述,然后计算该属性在对象中的偏移量,然后给出 settergetter方法对应的实现,在setter方法中从偏移量的位置开始赋值,在 getter方法中从偏移量开始取值,为了能够读取正确字节数,系统对象偏移量的指针类型进行了类型强转.

8、属性的关键字有哪些?

  • 默认使用关键字

对应基本数据类型默认关键字是:atomic、readwrite、assign
对于普通的Objective-C对象默认关键字是:atomic、readwrite、strong

  • 读写权限

readonly:属性只可读,表示仅拥有“获取方法”,利用此属性可以保证对外只读,但是在分类中,我们可以将其重新定义为读写属性

readwrite:属性可读可写,拥有“获取方法”与“设置方法”,若该属性由@synthesize实现,则编译器会自动生成这两个方法

  • 内存管理语义

strong:表示指向并拥有该对象,定义了一种其“拥有关系”,当为这种属性设置新值时,设置方法会先保留新值,并释放旧值,然后再将新值设置上去。修饰的对象引用计数会增加1。该对象只要引用计数不为0则不会被销毁。当然强行将其设为nil可以销毁它。

copycopy所表达的属性关系与strong类似,然而设置方法并不保留新值,而是将其拷贝(copy)。当属性类型为NSString *时,经常使用copy来保护其封装性,因为传递给设置方法的新值可能指向一个NSMutableString类的实例。该类是NSString的子类,表示一种可以被修改 其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值可能会在对象不知情况下遭若更改。所以,这时候就需要拷贝一份不可变的字符串。

weak:表示指向但不拥有该对象,定义了一种“非拥有关系”。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特性跟assign是一样的,然而在属性所指的对象被摧毁的时候,属性值会清空。其修饰的对象引用计数不会增加。无需手动设置,该对象会自行在内存中销毁。

assign:主要用于修饰基本数据类型,也可以说“纯量类型”,如NSInteger和CGFloat,这些数值主要存在于栈上

  • 原子性

atomicatomic修饰的对象会保证settergetter的完整性,任何线程对其访问都可以得到一个完整的初始化后的对象。因为要保证操作完成,所以速度慢。它比nonatomic安全,但也并不是绝对的线程安全,例如多个线程同时调用set和get就会导致获得的对象值不一样。绝对的线程安全就要用关键词synchronized

nonatomicnonatomic修饰的对象不保证setter和getter的完整性,所以多个线程对它进行访问,它可能会返回未初始化的对象。正因为如此,它比atomic快,但也是线程不安全的。

在默认情况下,由编译器所合成的方法会通过锁定机制确保其原子性,如果属性具备nonatomic特质,则不使用同步锁。

两者之间的区别在于:

具备atomic特质的获取方法会通过锁定机制来确保其操作的原子性,这也就是说,如果两个线程读写同一个属性,那么不论何时,总能看到有效的属性值,若是不加锁的话,那么当其中的一个线程正在改写某属性值时,另外一个线程突然闯入,把尚未修改好的属性值进行读取,那么线程可能读取不到对应的值。

但是注意:做过iOS开发的应该都明白,当我们使用属性的时候,都是nonatomic,这样做是因为,在iOS中使用同步锁开销较大,这样会带来性能问题,一般情况下并不是要求属性必须是原子的,因为这样并不能保证“线程安全”的操作,如果真的想保证线程安全,还需要使用更深层次的锁才行

  • 方法名---getter= 、setter=

getter=的样式:

  @property (nonatomic, getter=isOn) BOOL on;

setter=一般用在特殊的情境下,比如:

在数据反序列化、转模型的过程中,服务器返回的字段如果以init开头,所以你需要定义一个init开头的属性,但默认生成的settergetter方法也会以 init 开头,而编译器会把所有以 init 开头的方法当成初始化方法,而初始化方法只能返回 self 类型,因此编译器会报错。

这时你就可以使用下面的方式来避免编译器报错:

@property(nonatomic, strong, getter=p_initBy, setter=setP_initBy:)NSString *initBy;

另外也可以用关键字进行特殊说明,来避免编译器报错:

@property(nonatomic, readwrite, copy, null_resettable) NSString *initBy;
- (NSString *)initBy __attribute__((objc_method_family(none)));

9、怎么用 copy 关键字?

  • 1、 NSString、NSArray、NSDictionary 等等经常使用copy关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary;
  • 2、block 也经常使用 copy 关键字,具体原因见官方文档:Objects Use Properties to Keep Track of Blocks

copy 关键字常见的问题和思考

  • 1、这个写法会出什么问题:@property (copy) NSMutableArray *array;

两个问题:

1、添加,删除,修改数组内的元素的时候,程序会因为找不到对应的方法而崩溃。因为copy就是复制一个不可变NSArray的对象;
2、使用了atomic属性会严重影响性能 ;

比如下面的代码就会发生崩溃

// .h文件
// 下面的代码就会发生崩溃
@property (nonatomic, copy) NSMutableArray *mutableArray;

// .m文件
// 下面的代码就会发生崩溃
NSMutableArray *array = [NSMutableArray arrayWithObjects:@1,@2,nil];
self.mutableArray = array;
[self.mutableArray removeObjectAtIndex:0];

接下来就会奔溃:

 -[__NSArrayI removeObjectAtIndex:]: unrecognized selector sent to instance 0x7fcd1bc30460
  • 2、如何让自己的类使用copy修饰符?

若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopying 与 NSMutableCopying 协议。

具体步骤:

  • 需声明该类遵从NSCopying协议
  • 实现NSCopying协议,该协议只有一个方法:
- (id)copyWithZone:(NSZone *)zone;

注意:一提到让自己的类用copy修饰符,我们总是想覆写copy方法,其实真正需要实现的却是copyWithZone方法。

代码为例:

// .h文件
// 修改完的代码

typedef NS_ENUM(NSInteger, CYLSex) {
    CYLSexMan,
    CYLSexWoman
};

@interface CYLUser : NSObject

@property (nonatomic, readonly, copy) NSString *name;
@property (nonatomic, readonly, assign) NSUInteger age;
@property (nonatomic, readonly, assign) CYLSex sex;

- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;
+ (instancetype)userWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;

@end

然后实现协议中规定的方法:

- (id)copyWithZone:(NSZone *)zone {
    CYLUser *copy = [[[self class] allocWithZone:zone] 
                     initWithName:_name
                                  age:_age
                                  sex:_sex];
    return copy;
}

但在实际的项目中,不可能这么简单,遇到更复杂一点,比如类对象中的数据结构可能并未在初始化方法中设置好,需要另行设置。

  • 3、用@property声明的 NSString / NSArray / NSDictionary 经常使用 copy 关键字,为什么?如果改用strong关键字,可能造成什么问题?

@property声明 NSString、NSArray、NSDictionary经常使用 copy关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作(就是把可变的赋值给不可变的),为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。

因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本。

如果我们使用是strong ,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性。

总结:使用copy的目的是,防止把可变类型的对象赋值给不可变类型的对象时,可变类型对象的值发送变化会无意间篡改不可变类型对象原来的值。

10、浅拷贝和深拷贝的区别?

一般情况下我们说的拷贝分为两种:

  • 浅拷贝:只复制指向对象的指针,而不复制引用对象本身即指针拷贝
  • 深拷贝:复制引用对象本身,内存中存在了两份独立对象本身即对象拷贝

真正拷贝可以分为三层:

  • 浅拷贝(shallow copy):在浅拷贝操作时,对于被拷贝对象的每一层都是指针拷贝。
  • 深拷贝(one-level-deep copy):在深拷贝操作时,对于被拷贝对象,至少有一层是深拷贝
  • 完全拷贝(real-deep copy):在完全拷贝操作时,对于被拷贝对象的每一层都是对象拷贝

非集合类对象的copymutableCopy

[不可变对象 copy] // 浅拷贝
[不可变对象 mutableCopy] // 深拷贝
[可变对象 copy] //深拷贝
[可变对象 mutableCopy] //深拷贝

集合类对象的copymutableCopy

[不可变对象 copy] // 浅拷贝
[不可变对象 mutableCopy] //单层深拷贝
[可变对象 copy] //单层深拷贝
[可变对象 mutableCopy] //单层深拷贝

这里需要注意的是集合对象的内容复制仅限于对象本身,对象元素仍然是指针复制

iOS 集合的深复制与浅复制

11、苹果是如何实现Autorelease Pool的?

Autorelease Pool作用:缓存池,可以避免我们经常写relase的一种方式。其实就是延迟release,将创建的对象,添加到最近的Autorelease Pool中,等到Autorelease Pool作用域结束的时候,会将里面所有的对象的引用计数器-1.

Autorelease Pool 的实现原理

12、@synthesize和@dynamic

  • @synthesize

@synthesize的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法,这个过程由编译器在编译期间执行,所有编辑器里看不到这些“合成方法”。除了生成方法代码之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前加下划线,此次作为实例变量的名字。

  • @dynamic

@dynamic告诉编译器:属性的settergetter方法由用户自己实现,不自动生成(当然对于readonly的属性只需提供getter即可),也不需要实例变量,所以即使编译器没有发现存取方法,也不会报错,它相信这些方法在运行期能够找到。

假如一个属性被声明为@dynamic var,然后你没有提供@setter方法和@getter方法,编译的时候没问题,但是当程序运行到instance.var = someVar,由于缺 setter方法会导致程序崩溃;或者当运行到someVar = instance.var时,由于缺getter方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定

13、weak 的内部实现原理,weak 变量在引用计数为0时,会被自动设置成 nil,这个特性是如何实现的?

要实现 weak 属性,首先要搞清楚 weak 属性的特点:

weak 此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同 assign 类似, 然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。

那么runtime 如何实现 weak 变量的自动置nil

runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。

Friday QA上,有一期专门介绍 weak的实现原理。Zeroing Weak References in Objective-C

Friday QA上实现的核心代码,如下:

Class subclass = objc_allocateClassPair(class, newNameC, 0);

Method release = class_getInstanceMethod(class, @selector(release));

Method dealloc = class_getInstanceMethod(class, @selector(dealloc));

class_addMethod(subclass, @selector(release), (IMP)CustomSubclassRelease, method_getTypeEncoding(release));

class_addMethod(subclass, @selector(dealloc), (IMP)CustomSubclassDealloc, method_getTypeEncoding(dealloc));

objc_registerClassPair(subclass);

14、weak和assgin?

  • 为什么基本类型和C数据类型的修饰用assign?

因为基本数据类型不是对象,在内存中创建和使用后,在一个方法体结束后就被删除。基本数据类型不需要引用计数,而其他修饰词需要引用计数,所以使用assign

  • 什么情况使用 weak 关键字?

  • ARC中,在有可能出现循环引用的时候,往往要通过让其中一端使用weak来解决,比如:delegate代理属性

  • 自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,自定义 IBOutlet 控件属性一般也使用 weak;当然,也可以使用strong。在下文也有论述:《IBOutlet连出来的视图属性为什么可以被设置成weak?》

不同点:

weak 此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似, 然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。 而 assign 的“设置方法”只会执行针对“纯量类型” (scalar type,例如 CGFloat 或 NSlnteger 等)的简单赋值操作。

assign 可以用非 OC 对象,而 weak 必须用于 OC 对象。那么assign为什么不用来修饰对象呢?因为assign会导致下面这个问题:原因是assign修饰的对象被释放后,指针的地址依然存在,造成野指针,在堆上容易造成崩溃。而栈上的内存系统会自动处理,不会造成野指针。

15、Autorelease与AutoreleasePool?

Autorelease顾名思义就是自动释放,Autorelease机制是iOS开发者管理对象内存的好伙伴,MRC中,调用[obj autorelease]来延迟内存的释放是一件简单自然的事,ARC下,我们甚至可以完全不知道Autorelease就能管理好内存。

  • AutoreleasePool

自动释放池是用来存储多个对象类型的指针变量。当你确定要将对象放入到池中的时候,只需要调用对象的autorelease对象方法就可以把对象放入到自动释放池中。被存入到自动释放池内的对象,当自动释放池被销毁时,会对池内的对象全部做一次release操作

  • AutoreleasePool什么时候创建?

1、程序刚启动的时候,也会创建一个自动释放池
2、产生事件以后,运行循环开始处理事件,就会创建自动释放池

  • AutoreleasePool什么时候销毁?

1、程序运行结束之前销毁
2、事件处理结束以后,会销毁自动释放池
3、还有在池子满的时候,也会销毁

  • autorelease的对象是在什么时候被release的?

autorelease实际上只是把对release的调用延迟了,对于每一个autorelease,系统只是把该对象放入了当前的Autoreleasepool中,当该Autoreleasepool被释放时,该Autoreleasepool中的所有对象会被调用release。对于每一个Runloop,系统会隐式创建一个Autoreleasepool,这样所有的Autoreleasepool会构成一个象CallStack一样的一个栈式结构,在每一个 Runloop结束时,当前栈顶的Autoreleasepool会被销毁,这样这个Autoreleasepool里的每个对象都会被release

4010043-22266cb78facba1c.png

Runloop简单来说就是一个事件循环,有事情的时候启动,没事情的时候休闲。对于Runloop不了解,可以看深入理解RunLoop

16、怎么检查内存泄露?

  • 静态分析Analyze

1、不运行程序, 直接检测代码中是否有潜在的内存问题(不一定百分百准确, 仅仅是提供建议)
2、结合实际情况来分析, 是否真的有内存问题

  • 使用Instruments工具进行动态分析

1、运行程序, 通过使用APP,查看内存的分配情况(Allocations):可以查看做出了某个操作后(比如点击了某个按钮\显示了某个控制器),内存是否有暴增的情况(突然变化)
2、运行程序, 通过使用APP, 查看是否有内存泄漏(Leaks):红色区域代表内存泄漏出现的地方

memory-instruments-1.jpg

  • 借助第三方开源工具,如:MLeaksFinder

17、什么情况下会发生内存泄漏和内存溢出?

  • 内存泄漏:堆里不再使用的对象没有被销毁,依然占据着内存。
  • 内存溢出:一次内存泄露危害可以忽略,但内存泄露多了,内存迟早会被占光,最终会导致内存溢出!当程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如数据长度比较小的数据类型 存储了数据长度比较大的数据。

18、weak属性需要在dealloc中置nil么?

ARC环境无论是强指针还是弱指针都无需在dealloc 设置为nilARC会自动帮我们处理。即便是编译器不帮我们做这些,weak也不需要在dealloc中置nil,在属性所指的对象遭到摧毁时,属性值也会清空

19、什么时候在block中不需要使用weakSelf?

我们知道,在使用block的时候,为了避免产生循环引用,通常需要使用weakSelfstrongSelf,写下面这样的代码:

__weak typeof(self) weakSelf = self;
[self doSomeBlockJob:^{
    __strong typeof(weakSelf) strongSelf = weakSelf;
    if (strongSelf) {
        ...
    }
}];

block本身不被self持有,而被别的对象持有,同时不产生循环引用的时候,就不需要使用weakSelf了。

最常见的代码就是UIView的动画代码,我们在使用UIViewanimateWithDuration:animations方法做动画的时候,并不需要使用 weakself,因为引用持有关系是:

  • UIView的某个负责动画的对象持有了block
  • block 持有了 self

因为 self并不持有 block,所以就没有循环引用产生,因为就不需要使用weakSelf

[UIView animateWithDuration:0.2 animations:^{
    self.alpha = 1;
}];

当动画结束时,UIView 会结束持有这个block,如果没有别的对象持有block的话,block对象就会释放掉,从而block会释放掉对于self的持有。整个内存引用关系被解除。

参考

iOSInterviewQuestions
iOS内存管理详解

你可能感兴趣的:(iOS面试题 - 内存管理)