ARC内存管理

深入学习内存管理。理解相关基础知识。

引用计数

内存管理的思考方式

1> 自己生成的对象,自己持有
alloc,new,copy,mutablecopy(copy方法利用NSCopying方法约定,由各类实现copyWithZone:方法生成并持有不可变对象的副本)
2> 非自己生成的对象,自己持有
NSMutableArray类的array方法。使用retain方法可以持有
id obj = [NSMutableArray array]; [obj retain];
3>不再需要自己持有的对象时释放
用alloc\new\copy\mutableCopy方法生成并持有的对象,或用retain方法持有的对象,一旦不再需要,务必要用release进行释放。
4>无法释放非自己持有的对象
程序崩溃情况

- 再度释放已经释放了的对象
- 访问已经释放的对象时。
- 释放了非自己持有的对象

alloc/retain/release/dealloc在GUNstep中的实现:

- 在objective-c的对象中存有引用计数这一整数值
- 调用alloc或是retain方法后,引用计数值加1.
- 调用release后,引用计数值减1.
- 引用计数值为0时,调用dealloc方法废弃对象。

苹果的实现

GNUstep将用内存块头部管理引用计数的好处:(GNUstep是与苹果Cocoa框架互换的框架)

- 少量代码即可完成
- 能够统一管理引用计数用内存块与对象用内存块。

苹果实现通过引用技术表来管理引用计数的好处:

- 对象用内存块的分配无需考虑内存块头部
- 引用计数表各记录中存有内存块地址,可以从各个记录追溯到各对象的内存块。(在调试的时候,只要内存技术表没有被破坏,就可以确认各内存块的位置;使用工具检测内存泄露时,引用计数表的各记录也有助于检测各对象的持有者是否存在)

autorelease简介

autorelease类似于C语言的局部变量。只是编程人员可以设定变量的作用域
具体使用:

  • 生成并持有NSAutoreleasePool对象
  • 调用已分配对象的autorelease实例方法
  • 废弃NSAutoreleasePool对象

类方法用于返回autorelease对象:
id array = [NAMutableArray arrayWithCapacity];
相当于:
id array = [[NAMutableArray arrayWithCapacity]autorelease];

autorelease实现

[obj autorelease];相当于

1
2
3
-(id) autorelease{
[NSAutoreleasePool addObject:self];
}

autorelease实例方法的本质就是调用NSAutoreleasePool对象的addObject类方法。

autoreleasepool底层是如何实现的?(苹果是如何实现autoreleasepool的)

autoreleasepool 以一个队列数组的形式实现,主要通过下列三个函数完成.

objc_autoreleasepoolPush
objc_autoreleasepoolPop
objc_autorelease

看函数名就可以知道,对 autorelease 分别执行 push,和 pop 操作。销毁对象时执行release操作。

ARC

ARC自动引用计数
Automatic Reference Counting,降低了程序崩溃、内存泄露的风险,很大程度上减少了开发程序的工作量。
使用条件:

  • 使用Xcode4.2或以上版本
  • 使用LLVM编译器3.0或以上版本
  • 编译器选项中设置ARC为有效1. “-fobjc-arc”

在LLVM编译器中设置ARC为有效状态,就无需再次键入retain或release代码

所有权的修饰符

在ARC环境下,id类型和对象类型和C语言其他类型不同,类型前必须加上所有权的修饰符。

__strong
__weak
__unsafe_unretained
__autoreleasing

__strong

ARC中, id及其他对象默认就是__strong修饰符修饰,持有强引用的变量超出其作用域时被废弃, 随着强引用的失效, 引用的对象会随之释放.

__weak

__strong修饰符修饰在两个对象持有和自己持有自己时很容易有循环引用,从而造成内存泄露。
避免循环引用问题。弱引用不能持有对象实例。 并且弱引用的对象被废弃时, 则此弱引用将自动失效并等于nil。

通过__weak变量访问对象实际上必定是访问注册到autoreleasepool的对象, 因为该修饰符只持有对象的弱引用, 在访问对象的过程中, 该对象可能被废弃, 如果把要访问的对象注册到autoreleasepool中, 那么在block结束之前都能确保该对象存在.

1
2
3
4
5
6
7
8
9
id __weak obj1 = nil;
{
id __strong obj0 = [[NSObject alloc] init];

obj1 = obj0;

NSLog(@"A:%@",obj1);
}
NSLog(@"B:%@",obj1);

输出:

1
2
A: <NSObject:ox753e180>
B: (null)

__weak 的几个使用场景:

  • 在 Delegate 关系中防止强引用循环。在 ARC 特性下,通常我们应该设置 Delegate 属性为 weak 的。但是这里有一个疑问,我们常用到的 UITableView 的 delegate 属性是这样定义的: @property (nonatomic, assign) id delegate;,为什么用的修饰符是 assign 而不是 weak?其实这个 assign 在 ARC 中意义等同于 __unsafe_unretaied(后面会讲到),它是为了在 ARC 特性下兼容 iOS4 及更低版本来实现弱引用机制。一般情况下,你应该尽量使用 weak。
  • 在 Block 中防止强引用循环。关于 Block 可以看这篇文章的详细探讨:Block
  • 用来修饰指向由 Interface Builder 创建的控件。比如:@property (weak, nonatomic) IBOutlet UIButton *testButton;。

__unsafe_unretained

unsafe_unretained 修饰的指针纯粹只是指向对象,没有任何额外的操作,不会去持有对象使得对象的 retainCount +1。而在指向的对象被释放时依然原原本本地指向原来的对象地址,不会被自动置为 nil,所以成为了野指针,非常不安全。
不安全的修饰符, 附有该修饰符的变量不属于编译器的内存管理对象. 该修饰符与
weak一样, 是弱引用, 并不能持有对象.并且访问该修饰符的变量时如果不能确保其确实存在, 则应用程序会崩溃!

1
2
3
4
5
6
7
8
9
id __unsafe_unretained obj1 = nil;
{
id __strong obj0 = [[NSObject alloc] init];

obj1 = obj0;

NSLog(@"A:%@",obj1);
}
NSLog(@"B:%@",obj1);

输出:

1
2
A: <NSObject:ox753e180>
B: <NSObject:ox753e180>

__unsafe_unretained 的应用场景:

在 ARC 环境下但是要兼容 iOS4.x 的版本,用 unsafe_unretained 替代 weak 解决强引用循环的问题。

__autoreleasing

在 ARC 模式下,我们不能显示的使用 autorelease 方法了,但是 autorelease 的机制还是有效的。

通过将对象赋给__autoreleasing修饰的变量就能达到在 MRC 模式下调用对象的 autorelease 方法同样的效果。
可理解为, ARC下用@autoreleasepool block代替NSAutoreleasePool类, 用__autoreleasing修饰符的变量代替autorelease方法.
但是, 显式使用autoreleasing修饰符跟strong一样罕见,

ps : id的指针或者对象的指针会被隐式附上__autoreleasing修饰符, 如 :

1
2
id *obj == id __autoreleasing *obj;
NSObject **obj == NSObject * __autoreleasing *obj;

在 ARC 模式下,显式的使用 __autoreleasing 的场景很少见,但是 autorelease 的机制却依然在很多地方默默起着作用。我们来看看这些场景:

  • 方法返回值。(保证返回时对象没被释放以便方法外的接收方能拿到有效的对象,否则你返回的是 nil,有何意义呢。所以就需要找一个合理的机制既能延长这个对象的生命周期,又能保证对其释放。这个机制就是 autorelease 机制。)
  • 访问 weak 修饰的变量。 ( weak 修饰的变量时,实际上必定会访问注册到 Autorelease Pool 的对象,因为 __weak 修饰符只持有对象的弱引用,而在访问对象的过程中,该对象有可能被废弃,如果把被访问的对象注册到 Autorelease Pool 中,就能保证 Autorelease Pool 被销毁前对象是存在的。)
  • id 的指针或对象的指针(id )。(所有这种指针的指针类型(id )的函数参数如果不加修饰符,编译器会默认将他们认定为 autoreleasing 类型。)【提醒】某些类的方法会隐式地使用自己的 Autorelease Pool,在这种时候使用 autoreleasing 类型要特别小心。NSDictionary 的 enumerateKeysAndObjectsUsingBlock 方法:所以要在Block外加 // 加 __block 保证可以在Block内被修改。

ARC规则

- 不能使用retain/release/retainCount/autorelease
- 不能使用NSAllocateObject/NSDeallocateObject(alloc实现实际上是通过直接调用NSAllocateObject函数来生成并持有对象, ARC下禁止使用NSAllocateObject函数与NSDeallocateObject函数)
- 需遵循内存管理的方法命名规则
- 不要显示调用dealloc(可以重写dealloc,但是不能调用[super dealloc])
- 使用@autoreleasepool块代替NSAutoreleasePool
- 不能使用区域(NSZone)
- 对象型变量不能作为C语言结构体(struct/union)的成员(可强制转换为void *或者是添加__unsafe__retained修饰符)
- 显示转换id和“void *”(通过__bridge可以id和void *相互转换,__bridge__retain可使要转换赋值的变量也持有所赋值的对象相当于retain,__bridge__transfer被转换的变量所持有的对象在该变量被赋值给转换目标变量后随之释放相当于release)这些转换也多数适用在OC对象和Core Foundation对象之间的相互转换。

属性

@property (nonatomic, strong) NSString *name;

在ARC下, 以下可作为这种属性声明中使用的属性来用.

从修饰符也可看出assign和weak的区别,和assign为什么不能修饰对象。

ARC的实现

ARC是由编译器+运行时库共同完成的.

__strong修饰符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
id __strong obj = [[NSObject alloc] init];
}
可转换为以下代码
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_release(obj);

{
id __strong obj = [NSMutableArray array];
}
可转换为以下代码
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);

objc_retainAutoreleasedReturnValue函数主要用于最优化程序运行. 它是用来持有返回注册在autoreleasepool中对象的方法.这个函数是成对的, 另外一个objc_autoreleaseReturnValue函数则用于alloc/new/copy/mutableCopy方法以外的类方法返回对象的实现上, 如下 :

1
2
3
4
5
6
7
8
9
10
11
+ (id)array
{
return [[NSMutableArray alloc] init];
}
可转换为以下代码
+ (id)array
{
id obj = objc_msgSend(NSMutableArray, @selector(alloc));
objc_msgSend(obj, @selector(init));
return objc_autoreleaseReturnValue(obj);
}

那么objc_autoreleaseReturnValue函数和objc_retainAutoreleasedReturnValue函数有什么用?

可以这样来总结 :

如果编译器检测到调用autorelease之后又紧接着调用retain的话, 就省略掉autorelease方法的调用. 通过objc_autoreleaseReturnValue函数和objc_retainAutoreleasedReturnValue函数将对象直接传递, 忽略掉多余的操作, 优化程序.

__weak修饰符

  • 若附有__weak修饰符的变量所引用的对象被废弃, 则将nil赋值给该变量.
  • 使用附有__weak修饰符的变量, 即是使用注册到autoreleasepool中的对象.
1
2
3
4
5
6
7
8
9
10
11
12
{
id __weak obj1 = obj;
}
可转换为以下代码
id obj1;
objc_initWeak(&obj1, obj);
objc_destroyWeak(&obj1);
也可转换为以下代码
id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
objc_storeWeak(&obj1, 0);

访问__weak变量时, 相当于访问注册到autoreleasepool的对象

1
2
3
4
5
6
7
8
9
10
11
12
{
id __weak obj1 = obj;
NSLog(@"%@", obj1);
}
可转换为以下代码
id obj1;
objc_initWeak(&obj1, obj);
id tmp = objc_loadWeakRetained(&obj1);
objc_autorelease(tmp);
NSLog(@"%@", tmp);
objc_destroyWeak(&obj1);
// objc_loadWeakRetained函数取出附有__weak修饰符变量所引用对象并retain

需要注意的是, 通过weak变量访问所引用的对象几次, 对象就被注册到autoreleasepool里几次. (将附有weak修饰符的变量赋值给附有__strong修饰符的变量后再使用可避免此问题)

__autoreleasing修饰符

将对象赋值给附有__autoreleasing修饰符的变量等同于MRC下调用对象的autorelease方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@autoreleasepool{
id __autoreleasing obj = [[NSObject alloc] init];
}
可转换为以下代码
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);

那么调用alloc/new/copy/mutableCopy以外的方法会怎样呢?

@autoreleasepool{
id __autoreleasing obj = [NSMutableArray array];
}
可转换为以下代码
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);

可见注册autorelease的方法没有改变, 仍是objc_autorelease函数

如何获取引用计数值

获取引用数值的函数
uinptr_t _objc_rootRetainCount(id obj)

- (NSUInteger)retainCount;
该方法返回的引用计数不一定准确, 因为有时系统会优化对象的释放行为, 在保留计数为1的时候就把它回收. 所以你用这个方法打印出来的引用计数可能永远不会出现0. 我们不应该根据retainCount来调试程序!!

总结

我们现在的工程几乎都运行在ARC下, 所以大部分内存管理代码都不需要我们自己写, 而由编译器帮我们搞定. 所以在ARC下我们只需要怎样不要去破坏这个生态即可

避免循环引用(使用__weak修饰符)
遵循ARC方法命名规则
适时清空指针(赋值nil即可, 避免野指针错误)
如用到Core Foundation对象, 则在dealloc方法中释放
在dealloc方法中只释放引用并移除监听(不能在dealloc中开启异步任务)
对于内存开销较大的资源, 如file descriptor, socket, 大块内存等应在不需要使用的时候调用close方法释放掉而不是在dealloc中处理.
适当使用@autoreleasepool block来降低内存峰值(之前我写的一篇文章中有demo)
必要时开启"僵尸对象"调试内存管理问题

属性与内存管理

@property实际上是getter和setter和ivar,@synthesize是合成这2个方法.
@property(),如果你里面什么都不写,那么系统会默认的把你的属性设置为:
@property(atomic, assign)….

关于nonatomic
指定 nonatomic特性,编译器合成访问器的时候不会去考虑线程安全问题。
如果你的多个线程在同一时间会访问到这个变量的话,可以将特性声明为 atomic(通过省略关键字nonatomic)。在这种特性的状态下,编辑器在合成访问器的时候就会在访问器里面加一个锁 (@synchronized),在同一时间只能有一个线程访问该变量。
但是使用锁是需要付出代价的,一个声明为atomic的属性,在设置和获取这个变量的时候都要比声明为nonatomic的慢。所以如果你不打算编写多线程代码,最好把变量的属性特性声明为nonatomic。

关于assign、retain和copy

assign:适用基本数据类型,(不适用于对象)简单赋值,不更改索引计数。因为assign对于在引用计数下的对象特性,只创建了一个弱引用(也就是平时说的浅复制)。这样使用变量会很危险。当你release了前一个对象的时候,被赋值的对象指针就成了无头指针了。因此在为对象类型的变量声明属性的时候,尽量不要使用assign。)
retain:适用于普通对象。释放旧的对象,将旧对象的值赋予输入对象,再提高输入对象的索引计数为1。
copy:适用于NSString与block。建立一个索引计数为1的对象,然后释放旧对象。

weak和strong的区别:

strong:先保留新值,再释放旧值,然后再将新值设置上去。weak:不保留新值,也不释放旧值,在属性所指对象遭到摧毁时,属性值也会清空。

copy与retain的区别:

copy其实是建立了一个相同的对象,而retain不是;
copy是内容拷贝,retain是指针拷贝;
copy是内容的拷贝 ,对于像NSString,的确是这样。如果拷贝的是NSArray这时只是copy了指向array中相对应元素的指针.这便是所谓的”浅复制”。

assign和weak的区别;

assign是指针赋值,不对引用计数操作,使用之后如果没有置为nil,可能就会产生野指针;
weak一旦不进行使用后,永远不会使用了,就不会产生野指针! 用weak避免循环引用.
assign 可以用非 OC 对象,而 weak 必须用于 OC 对象

Autorelease Pool

一般地,在新建一个iphone 项目的时候,xcode会自动地为你创建一个autorelease pool,这个pool就写在Main函数里面。
在NSAutoreleasePool中包含了一个可变数组,用来存储被声明为autorelease的对象。当NSAutoreleasePool自身被销毁的时候,它会遍历这个数组,release数组中的每一个成员(注意,这里只是release,并没有直接销毁对象)。若成员的retain count 大于1,那么对象没有被销毁,造成内存泄露。
默认的NSAutoreleasePool只有一个,你可以在你的程序中创建NSAutoreleasePool,被标记为autorelease的对象会跟最近的 NSAutoreleasePool匹配。
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//Create some objects
//do something…
[pool release];
你也可以嵌套使用NSAutoreleasePool ,就像你嵌套使用for一样。
即使NSAutoreleasePool 看起来没有手动release那么繁琐,但是使用NSAutoreleasePool 来管理内存的方法还是不推荐的。因为在一个NSAutoreleasePool 里面,如果有大量对象被标记为autorelease,在程序运行的时候,内存会剧增,直到NSAutoreleasePool 被销毁的时候才会释放。如果其中的对象足够的多,在运行过程中你可能会收到系统的低内存警告,或者直接crash。

  1. autorelease的具体使用方法:
    • 生成并持有NSAutoreleasePool对象。
    • 调用已分配对象的autorelease实例方法。
    • 废弃NSAutoreleasePool对象。

RunLoop开始 -> 创建autoreleasepool -> 线程处理事件循环 -> 废弃autoreleasepool -> RunLoop结束 -> 等待下一个Loop开始

Autorelease Pool 扩展
如果你极具好奇心,把Main函数中的NSAutoreleasePool代码删除掉,然后再自己的代码中把对象声明为autorelease,你会 发现系统并不会给你发出错误信息或者警告。用内存检测工具去检测内存的话,你可能会惊奇的发现你的对象仍然被销毁了。
其实在新生成一个Run Loop的时候,系统会自动的创建一个NSAutoreleasePool ,这个NSAutoreleasePool 无法被删除。
在做内存测试的时候,请不要用NSString。OC对字符串作了特殊处理
NSString *str =[[NSString alloc] stringWithString:@”123”];
在输出str的retain count 的时候,你会发现retain count 大于1。

面试题

  1. 内存管理,MRC,ARC的区别;
    内存管理机制:使用引用计数管理,分为ARC和MRC,MRC需要程序员自己管理内存,ARC则不需要.
    ARC相对于MRC,不是在编译时添加retain/release/autorelease这么简单。应该是编译期和运行期两部分共同帮助开发者管理内存。
    在编译期,ARC用的是更底层的C接口实现的retain/release/autorelease,这样做性能更好,也是为什么不能在ARC环境下手动retain/release/autorelease,同时对同一上下文的同一对象的成对retain/release操作进行优化(即忽略掉不必要的操作);ARC也包含运行期组件,这个地方做的优化比较复杂,但也不能被忽略。

  2. @property 后面可以有哪些修饰符?
    属性可以拥有的特质分为四类:

    原子性--- nonatomic 特质  
    读/写权限---readwrite(读写)、readonly (只读) 
    内存管理语义---assign、strong、 weak、unsafe_unretained、copy  
    方法名---getter=<name> 、setter=<name>
    不常用的:nonnull,null_resettable,nullable
    
  3. 什么情况使用 weak 关键字,相比 assign 有什么不同?assign为什么不能修饰对象?修饰了对象后会发生什么? weak为什么比assign安全,其实现原理是怎样的? weak在它指向的对象被释放后,会被置为nil,该机制是如何实现的;
    因为assign对于在引用计数下的对象特性,只创建了一个弱引用(也就是平时说的浅复制)。这样使用变量会很危险。当你release了前一个对象的时候,被赋值的对象指针就成了无头指针了。

  4. 怎么用 copy 关键字?NSString为什么用的copy,copy和strong有什么区别;
    NSString,NSArray,NSDictionary,Block使用copy。
    用 @property 声明 NSString、NSArray、NSDictionary 经常使用 copy 关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。
    另一篇:深拷贝和浅拷贝的详解。

  5. 这个写法会出什么问题: @property (copy) NSMutableArray *array;
    两个问题:1、添加,删除,修改数组内的元素的时候,程序会因为找不到对应的方法而崩溃.因为 copy 就是复制一个不可变 NSArray 的对象;
    2、使用了 atomic 属性会严重影响性能

  6. 如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?
    若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopying 与 NSMutableCopying 协议。

  7. @property 的本质是什么?
    @property = ivar + getter + setter;
    property在runtime中是objc_property_t定义如下:
    typedef struct objc_property *objc_property_t;
    而objc_property是一个结构体,包括name和attributes,定义如下:

1
2
3
4
struct property_t {
const char *name;
const char *attributes;
};

ivar、getter、setter 是如何生成并添加到这个类中的?
“自动合成”( autosynthesis).编译器会自动编写访问这些属性所需的方法,在编译期执行.生成了五个东西

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

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

  1. @protocol 和 category 中如何使用 @property
    在 protocol 中使用 property 只会生成 setter 和 getter 方法声明,我们使用属性的目的,是希望遵守我协议的对象能实现该属性.
    category 使用 @property 也是只会生成 setter 和 getter 方法的声明,如果我们真的需要给 category 增加属性的实现,需要借助于运行时的两个函数:
    objc_setAssociatedObject
    objc_getAssociatedObject

  2. Category和Extension区别;Category的缺点和优点;如何克服或者弥补这个缺点(不能添加实例变量,用Runtime实现);

Category;

- 用于给class及其subclass添加新的方法
- 有自己单独的 .h 和 .m 文件
- 用于添加新方法,而不能添加新属性(property)

Extension

- Extension常被称为是匿名的Category
- 用于给类添加新方法,但只作用于原始类,不作用于subclass
- 只能对有implementation源代码的类写Extension,对于没有implementation源代码的类,比如framework class,是不可以的
- **Extension可以给原始类添加新方法,以及新属性**

category 与extension不同在于后者可以添加属性,后者添加的方法也必须是要实现的。extension是私有的category。

Category的缺点和优点:
缺陷:一是无法向类中添加新的实例变量。
二是名称冲突,在自己的类别方法名中添加一个前缀,以确保不会发生冲突。
优势:将类的实现代码分散到多个不同文件或框架中,创建对私有方法的前向引用,以及向对象添加非正式协议。

  1. runtime 如何实现 weak 属性?(runtime 如何实现 weak 变量的自动置nil?)
    对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。

    1
    2
    3
    4
    5
    6
    7
     - (void)setObject:(NSObject *)object
    {
    objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN);
    [object cyl_runAtDealloc:^{
    _object = nil;
    }];
    }
  1. weak属性需要在dealloc中置nil么?

    不需要。
    在ARC环境无论是强指针还是弱指针都无需在 dealloc 设置为 nil , ARC 会自动帮我们处理

  2. @synthesize和@dynamic分别有什么作用?
    @synthesize 的语义是如果你没有手动实现 setter 方法和 getter 方法,那么编译器会自动为你加上这两个方法。
    @dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。

  3. [※※※]ARC下,不显式指定任何属性关键字时,默认的关键字都有哪些?
    对应基本数据类型默认关键字是:atomic,readwrite,assign
    对于普通的 Objective-C 对象:atomic,readwrite,strong

  1. [※※※]用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?
    使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本.
    如果我们使用是 strong ,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性.
  1. [※※※]@synthesize合成实例变量的规则是什么?假如property名为foo,存在一个名为_foo的实例变量,那么还会自动合成新变量么?不会

总结下 @synthesize 合成实例变量的规则,有以下几点:

如果指定了成员变量的名称,会生成一个指定的名称的成员变量,
如果这个成员已经存在了就不再生成了.
如果是 @synthesize foo; 还会生成一个名称为foo的成员变量,也就是说:
如果没有指定成员变量的名称会自动生成一个属性同名的成员变量,
如果是 @synthesize foo = _foo; 就不会生成成员变量了.
  1. [※※※※※]在有了自动合成属性实例变量之后,@synthesize还有哪些使用场景?
    什么情况下不会autosynthesis(自动合成)?就需要使用@synthesize 来手动合成ivar。

    同时重写了 setter 和 getter 时
    重写了只读属性的 getter 时
    使用了 @dynamic 时
    在 @protocol 中定义的所有属性
    在 category 中定义的所有属性
    重载的属性
    
  2. releasepool被释放了,里面的对象都会被释放吗?过程是怎样的。

  1. 自动释放池底层怎么实现?
    自动释放池以队列数组的形式实现:当你创建一个新的自动释放池时,它将被添加到栈顶。当一个对象收到发送autorelease消息时,它被添加到当前线程的处于栈顶的自动释放池中,当自动释放池被回收时,它们从栈中被删除, 并且会给池子里面所有的对象都会做一次release操作.

  2. 什么情况下会发生内存泄漏和内存溢出? 如何查看或者检测内存泄露;
    内存泄露:如果失去了对一个对象的访问权,而又没有将retain count减至0,就会造成内存泄露。不再需要的对象没有释放,导致内存泄露,内存泄露会导致应用闪退。循环引用造成内存不可回收。
    1>block中使用strong self,如果不注意很容易造成内存泄露
    2>delegate如果设置为strong也很大可能造成内存泄露,不过具体还要看有没有循环引用.

  1. ARC是依靠什么实现的?ARC是一种编译时特性还是运行时特性;
    编译期和运行期两部分共同帮助开发者管理内存。
    简单来说,编译器在编译代码时,会自动生成实例的引用计数代码,帮助我们完成之前MRC需要完成的工作,不过据说除此之外,编译器也会执行某些优化。

  2. 如果自己实现一个setter方法,该如何写代码?

  3. 讲讲Foundation和CoreFoundation之间的区别;CoreFoundation是不支持ARC的,如何与Foundation之间进行转化。

Core Foundation框架 (CoreFoundation.framework) 是一组C语言接口,它们为iOS应用程序提供基本数据管理和服务功能。
Foundation框架提供Objective-C接口。

CocoaFoundation指针与CoreFoundation指针转换,需要考虑的是所指向对象所有权的归属。ARC提供了3个修饰符来管理。
  1. bridge,什么也不做,仅仅是转换。此种情况下:
    i). 从Cocoa转换到Core,需要人工CFRetain,否则,Cocoa指针释放后, 传出去的指针则无效。
    ii). 从Core转换到Cocoa,需要人工CFRelease,否则,Cocoa指针释放后,对象引用计数仍为1,不会被销毁。
  2.
bridge_retained,转换后自动调用CFRetain,即帮助自动解决上述i的情形。
  3. __bridge_transfer,转换后自动调用CFRelease,即帮助自动解决上述ii的情形。
参考:http://www.jianshu.com/p/5c98ac2dab58

  1. release如何实现对象释放?autorelease如何实现?两者的区别与联系?release能做的事情autorelease都能做,那么release可不可以舍弃?为什么?

    release和autorelease区别

    release立即释放,autorelease不立即释放,先放到autoreleasepool中,当pool结束后自动调用release。
    一般来说,除了alloc、new或copy之外的方法创建的对象都被声明了autorelease.

  1. dealloc里做哪些操作?

  2. dealloc不remove通知会怎么样?

  3. atomic一定是线程安全的吗?

  1. BAD_ACCESS这个bug是如何出现的?
    野指针。正在使用的对象被释放了,导致野指针,访问野指针导致程序崩溃。

  2. [※]objc使用什么机制管理对象内存?—-引用计数
    通过 retainCount 的机制来决定对象是否需要释放。 每次 runloop 的时候,都会检查对象的 retainCount,如果retainCount 为 0,说明该对象没有地方需要继续使用了,可以释放掉了。

  3. [※※※※]ARC通过什么方式帮助开发者管理内存?
    ARC的实现。

  4. [※※※※]不手动指定autoreleasepool的前提下,一个autorealese对象在什么时刻释放?(比如在一个vc的viewDidLoad中创建)
    所有 autorelease 的对象,在出了作用域之后,会被自动添加到最近创建的自动释放池中。并会在当前的 runloop 迭代结束时释放。

子线程默认是没有 RunLoop 的,如果需要在子线程开启 RunLoop ,则需要调用 [NSRunLoop CurrentRunLoop] 方法,它内部实现是先检查线程,如果发现是子线程,以懒加载的形式 创建一个子线程的 RunLoop。并存储在一个全局的 可变字典里。编程人员在调用 [NSRunLoop CurrentRunLoop] 时,是自动创建 RunLoop 的,而没法手动创建。

自定义的 NSOperation 和 NSThread 需要手动创建自动释放池。比如: 自定义的 NSOperation 类中的 main 方法里就必须添加自动释放池。否则出了作用域后,自动释放对象会因为没有自动释放池去处理它,而造成内存泄露。

但对于 blockOperation 和 invocationOperation 这种默认的Operation ,系统已经帮我们封装好了,不需要手动创建自动释放池。

@autoreleasepool 当自动释放池被销毁或者耗尽时,会向自动释放池中的所有对象发送 release 消息,释放自动释放池中的所有对象。

如果在一个vc的viewDidLoad中创建一个 Autorelease对象,那么该对象会在 viewDidAppear 方法执行前就被销毁了。

  1. 内存不足的产生的例子?如何解决?
    在for循环中读入大量图片的同时,改变其尺寸。图像文件读入到NSData对象,并从中产生UIImage对象,改变该尺寸后生成新的UIImage对象,这种情况就会产生大量的autorelease的对象。不废弃NSAutoreleasePool对象,那么生成的对象就不能释放,就会产生内存不足。
    解决:在适当的地方生成、持有或废弃NSAutoreleasePool对象;
    返回NSMutableArray类的arrayWithCapcity方法。来返回autorelease对象。

4.weak是怎么实现自动设置为nil的?
答:当把一个强引用的变量赋值给一个弱引用的变量时, 实际上编译器做了以下几步:

1、把__weak变量初始化为0
2、使用__strong变量的地址作为键值,把__weak变量的地址注册到weak表中,weak表是一个散列表
3、如果__strong变量的地址为0,就把对应__weak变量的地址从weak表中删除

使用weak表,使用废弃变量的地址作为键值进行检索,就能高速获取对应的附有__weak修饰符的变量的地址。
废弃谁都不持有的对象时,编译器进行了如下几步操作:

1、从weak表中获取废弃对象的地址作为键值的记录
2、将记录中所有weak变量的地址赋值为nil
3、将记录从weak表中删除
4、将废弃对象的地址作为键值的记录从引用计数表中删除

因此,如果大量的使用附有weak修饰符的变量,会消耗相应的CPU资源,所以应该只在为了避免循环引用时再使用weak修饰符。


参考:

iOS ARC 内存管理要点http://www.samirchen.com/ios-arc/

文章目录
  1. 1. 引用计数
    1. 1.0.1. 内存管理的思考方式
    2. 1.0.2. alloc/retain/release/dealloc在GUNstep中的实现:
    3. 1.0.3. 苹果的实现
    4. 1.0.4. autorelease简介
    5. 1.0.5. autorelease实现
    6. 1.0.6. autoreleasepool底层是如何实现的?(苹果是如何实现autoreleasepool的)
  • 2. ARC
    1. 2.0.1. 所有权的修饰符
    2. 2.0.2. ARC规则
    3. 2.0.3. 属性
  • 3. ARC的实现
    1. 3.0.1. __strong修饰符
    2. 3.0.2. __weak修饰符
    3. 3.0.3. __autoreleasing修饰符
    4. 3.0.4. 如何获取引用计数值
    5. 3.0.5. 总结
  • 3.1. 属性与内存管理
  • 3.2. Autorelease Pool
  • 4. 面试题
  • 本站总访问量 本站访客数人次 ,