ARC 和 MRC 的区别?
MRC | ARC | |
---|---|---|
strong | 无 | ARC特有,在MRC时代没有,相当于MRC模式下的retain |
retain | MRC、ARC两种内存管理方式下相同 | MRC、ARC两种内存管理方式下相同 |
assign | 可以用来修饰对象类型,也可以用来修饰基本数据类型。修饰对象类型的时候,对象的引用计数不会随着引用次数的增加而增加,也就是说被释放之前,引用计数永远是1。 | 只能用来修饰基本数据类型,不能用来修饰对象类型。除此之外,还用来修饰代理对象。 |
weak | 无 | 相当于MRC模式下的assign |
copy | 1. block访问外部局部变量,block存放在栈里面 2. 只要block访问整个APP都存在的变量,肯定是在全局区 3. 不能使用retain引用block,因为block不在堆里面,只有使用copy才会把block放在堆区里面 | 1. 只要block访问外部局部变量,block就会存放在堆区 2. 可以使用strong去引用,因为本身就已经存放在堆区的 3. 也可以使用copy进行修饰,但是strong性能更好 |
strong:强引用,它是ARC特有。在MRC时代没有,相当于retain。由于MRC时代是靠引用计数器来管理对象什么时候被销毁所以用retain,而ARC时代管理对象的销毁是有系统自动判断,判断的依据就是该对象是否有强引用对象。如果对象没有被任何地方强引用就会被销毁。所以在ARC时代基本都用的strong来声明代替了retain。只能用于声明OC对象(ARC特有)
ARC
ARC默认属性修饰符
- ARC下对象类型属性:(atomic, readwrite, strong)
- ARC下非对象类型:(atomic, readwrite, unsafe_unretained)
ARC属性的所有权修饰符
属性声明的属性 | 所有权修饰符 |
---|---|
assign | _unsafe_unretained 修饰符 |
copy | _strong 修饰符 (但是赋值的是被复制的对象) |
retain | _strong 修饰符 |
strong | _strong 修饰符 |
_unsafe_unretained | _unsafe_unretained修饰符 |
weak | _weak 修饰符 |
_autoreleasing 修饰符 |
-
assign:
assign
只可以用来修饰基本数据类型,该方式会对象直接赋值而不会进行retain
操作。 -
copy:表⽰重新建立一个新的计数为1的对象,然后释放掉旧的值。
NSString、NSArray、NSDictionary
等经常使用copy
关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary
,为确保对象中的属性值不会无意间变动,应该在设置新属性值时拷贝一份。而NSMutableString、NSMutableArray、NSMutableDictionary
则往往使用strong
关键字更为妥当。浅copy
,类似strong
,持有原始对象的指针,会使retainCount
加一。深copy
,会创建一个新的对象,不会对原始对象的retainCount
变化。 -
retain和strong区别:
strong
修饰block
,相当于copy
,此时block
是放在堆上的,生命周期不会随函数周期结束而出栈,但是retain
修饰的block
是存放在栈上, 因此block
在函数调用结束时,对象会变成nil
,对象的指针会变成野指针,因此对象继续调用会产生异常。 -
_autoreleasing:
_autoreleasing
修饰符变量引用的对象,相当于在MRC
情况下调用对象的autorelease
方法
WWDC2011
和 iOS5
所引入自动管理机制——自动引用计数(ARC
),它不是垃圾回收机制而是编译器的一种特性。ARC管理机制
与 MRC手动机制
差不多,只是不再需要手动调用retain、release、autorelease
;当你使用ARC时,编译器会在在适当位置插入 release
和 autorelease
;ARC
时代引入了 strong强引用
来带代替 retain
,引入了weak弱引用
。
RunLoop:
看 apple
官方文档(多线程编程指南)描述: “runloop
是用来在线程上管理事件异步到达的基础设施……
runloop
在没有任何事件处理的时候会把它的线程置于休眠状态,它消除了消耗CPU
周期轮询,并防止处理器本身进入休眠状态并节省电源。” 看见没,消除CPU空转
才是它最大的用处。
图中第1步 Observer
监视的事件是 Entry
(即将进入Loop
),其回调内会调用 _objc_autoreleasePoolPush()
创建自动释放池。其 order
是-2147483647
,优先级最高,保证创建释放池发生在其他所有回调之前
图中第6步 Observer
监视了两个事件: BeforeWaiting
(准备进入休眠) 时调用_objc_autoreleasePoolPop()
和 _objc_autoreleasePoolPush()
释放旧的池并创建新池;Exit
(即将退出Loop
) 时调用 _objc_autoreleasePoolPop()
来释放自动释放池。这个 Observer
的 order
是 2147483647
,优先级最低,保证其释放池子发生在其他所有回调之后。
图中第10 Observer
监视事件是exit
(即讲退出runloop
),其回调内会调用 _objc_autoreleasePoolpop()
释放自动释放池。
从上面就能看出,Runloop
中系统自动创建的 @autoreleasepool
是在准备进入休眠状态才被销毁的。所以在 ARC
下,在线程中的临时对象是在当前线程的 Runloop
进入休眠或者退出 loop
或者退出线程时被执行 release
的。
AutoreleasePool
自动释放池是OC
中的一种内存自动回收机制,它可以将加入 AutoreleasePool
中的变量 release
的时机延迟,简单来说,就是当创建一个对象,在正常情况下,变量会在超出其作用域的时立即 release
。如果将对象加入到了自动释放池中,这个对象并不会立即释放,会等到 runloop
休眠/超出 autoreleasepool
作用域{}之后才会被释放。其机制如下图所示
-
从程序启动到加载完成,主线程对应的
runloop
会处于休眠状态,等待用户交互来唤醒runloop
-
用户的每一次交互都会启动一次
runloop
,用于处理用户的所有点击、触摸事件等 -
runloop
在监听到交互事件后,就会创建自动释放池,并将所有延迟释放的对象添加到自动释放池中 -
在一次完整的
runloop
结束之前,会向自动释放池中所有对象发送release
消息,然后销毁自动释放池
MRC
MRC默认属性修饰符:
MRC:(atomic, readwrite, assign)
MRC手动内存管理
引用计数器:在MRC时代,系统判定一个对象是否销毁是根据这个对象的引用计数器来判断的。
- 每个对象被创建时引用计数都为1
- 每当对象被其他指针引用时,需要手动使用[obj retain];让该对象引用计数+1。
- 当指针变量不在使用这个对象的时候,需要手动释放release这个对象。 让其的引用计数-1.
- 当一个对象的引用计数为0的时候,系统就会销毁这个对象。
- 在MRC模式下必须遵循谁创建,谁释放,谁引用,谁管理
内存泄漏
内存泄漏就是应当废弃的对象在超出其生存周期后继续存在。该被释放的时候未释放,一直被其内部的对象所持有,循环引用就属于内存泄漏。
block的属性修饰词为什么是copy?使用block有哪些使用注意?
block一旦没有进行copy操作,就不会在堆上
使用注意:循环引用问题
block在修改NSMutableArray,需不需要添加__block?
不需要
当变量是一个指针的时候,block里只是复制了一份这个指针,两个指针指向同一个地址。所以,在block里面对指针指向内容做的修改,在block外面也一样生效。
野指针和悬垂指针
野指针
是指向“垃圾”内存(不可用内存)的指针
产生原因:
指针创建时未初始化。指针变量刚被创建时不会自动成为NULL
指针,它会随机指向一个内存地址。
悬垂指针
:指针所指向的对象已经被释放或者回收了,但是指向该对象的指针没有作任何的修改,仍旧指向已经回收的内存地址。 此类指针称为垂悬指针。
@property 属性声明的关键字
:
weak
表示非持有特性,为属性设置新值的时候,设置方法既不会保留新值,也不会释放旧值。当属性所指的对象释放的时候,属性也会被置为 nil
。
assign
用来修饰基本数据类型和对象。当 assign
用来修饰对象的时候,和 weak
类似。唯一的区别就是当属性所指的对象释放的时候,属性指针不会被置为 nil
,这就会产生悬垂指针
。
unsafe_unretained
用来修饰属性的时候,和 assign
修饰对象的时候是一模一样的。为属性设置新值的时候,设置方法既不会保留新值,也不会释放旧值。唯一的区别就是当属性所指的对象释放的时候,属性不会被置为 nil
,这就会产生 悬垂指针
,所以是不安全的。
情形一:
weak和assign的区别
:
释放对象是否产生野指针,适用 OC
类型还是基本类型。
1. 修饰类型不同
- weak只能用于修饰OC对象类型;
- assign既可以修饰OC类型也可以修饰基本类型;
2. 释放是是否产生野指针
当 weak
对象被释放是,对象的指针会被设置为 nil
,因此再次去对该对象发送消息,不会崩溃,因此 weak
是安全的;
assign
如果修饰对象,会产生野指针问题;如果修饰基本数据类型则是安全的。 当对象被释放的时候,对象的指针不会置空,该对象会变成野指针,再次对该对象发送消息,会直接崩溃。
总结:
assign
:适用于基本数据类型(int
),因为基本数据类型存放在栈中,采用先进先出的原则,用系统自动分配释放管理内存。如果使用在对象类型,存放在堆中,需要考虑野指针的问题,则程序员要手动分配释放或者 ARC
下内存自动管理分配。
weak
:适用于 OC
对象类型,同时也适用于 delegate
,不会产生野指针,也不会循环引用,非常安全。
情形二:
block为什么用copy修饰 ?
默认情况下,block
是存放在栈中即 NSStackBlock
,因此 block
在函数调用结束时,对象会变成 nil
,但是对象的指针变成野指针,因此对象继续调用会产生异常。使用 copy
修饰之后,会将 block
对象保存到堆中 NSMallocBlock
,它的生命周期会随着对象的销毁而结束的。所以函数调用结束之后指针也会被设置为 nil
,再次调用该对象也不会产生异常。
解决循环引用常见的方式
-
weak-strong-dance
-
__block
修饰对象(需要注意的是在block内部需要置空对象,而且block
必须调用) -
传递对象
self
作为block
的参数,提供给block内部
使用 -
使用NSProxy
定时器 NSTimer 中的循环引用
首先控制器 self
强引用 timer
,然后timer
又强引用我们的控制器 self
,这就造成了循环引用,释放不掉造成内存泄露。
self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
复制代码
NSTimer循环引用解决方案:
方案一(失败):weak-strong-dance
一想到循环引用的解决方案,我们首先想到的肯定是 weak-strong-dance
,那么我们来尝试一下:
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer timerWithTimeInterval:1 target:weakSelf selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
复制代码
我们再次运行程序,进行 push-pop
跳转。发现问题还是存在,即定时器方法仍然在执行,并没有执行控制器 self
的 dealloc
方法,为什么呢?
我们使用 __weak
虽然打破了 self -> timer -> self
之前的循环引用,即引用链变成了self -> timer -> weakSelf -> self
。但是在这里我们的分析并不全面,此时还有一个 Runloop
对 timer
的强持有,因为 Runloop
的生命周期比控制器 self
界面更长,所以导致了 timer
无法释放,同时也导致了控制器 self
界面也无法释放。它们之间的引用链如下图所示:
我们的定时器 timer
捕获的是控制器 self
,是一个对象,其引用链关系为:NSRunLoop -> timer -> weakSelf -> self
。所以RunLoop对整个对象的空间有强持有,runloop没停,timer 和 weakSelf是无法释放的。
方案二(成功):pop时在其他方法中销毁timer
根据前面的解释,我们知道由于 Runloop
对 timer
的强持有,导致了 Runloop
间接的强持有了self
(因为 timer
中捕获的是 self
对象)。所以导致 dealloc
方法无法执行。需要查看在 pop
时,是否还有其他方法可以销毁 timer
。这个方法就是 didMoveToParentViewController
。
didMoveToParentViewController方法
,是用于当一个视图控制器中添加或者移除 viewController
后,必须调用的方法。目的是为了告诉iOS
,已经完成添加/删除子控制器的操作。
在控制器 self
中重写 didMoveToParentViewController方法
- (void)didMoveToParentViewController:(UIViewController *)parent{
// 无论push 进来 还是 pop 出去 正常跑
// 就算继续push 到下一层 pop 回去还是继续
if (parent == nil) {
[self.timer invalidate];
self.timer = nil;
NSLog(@"timer 走了");
}
}
复制代码
方案三(成功):中介者模式,即不使用self,依赖于其他对象
在 timer
模式中,我们重点关注的是 fireInTheHole
能执行,并不关心 timer
捕获的 target
是谁,由于这里不方便使用 self
(因为会有强持有问题),所以可以将 target
换成其他对象,例如将 target
换成NSObject
对象,将 fireInTheHole
交给 target
执行
将 timer
的 target
由 self
改成 objc
@property (nonatomic, strong) id target;
//**********1、修改target**********
self.target = [[NSObject alloc] init];
class_addMethod([NSObject class], @selector(fireInTheHole), (IMP)fireInTheHoleObjc, "v@:");
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.target selector:@selector(fireInTheHole) userInfo:nil repeats:YES];
//**********imp**********
void fireInTheHoleObjc(id obj){
NSLog(@"%s -- %@",__func__,obj);
}
- (void)fireInTheHole {
NSLog(@"fire in the hole");
}
复制代码
运行结果如下:
运行发现执行 dealloc
之后,timer
还是会继续执行。原因是解决了中介者的释放,但是没有解决中介者的回收,即 self.target
的回收。所以这种方式有缺陷
可以通过在 dealloc
方法中,取消定时器来解决,代码如下:
-(void)dealloc {
NSLog(@"%s",__func__);
[self.timer invalidate];
self.timer = nil;
}
复制代码
发现pop之后,timer释放,从而中介者也会进行回收释放,运行结果如下:
方案四(成功):NSProxy虚基类的方式
NSProxy
是一个虚基类,它的地位等同于 NSObject
。
command+shift+0
打开 Xcode
参考文档搜索 NSProxy
,说明如下:
NSProxy
An abstract superclass defining an API for objects that act as stand-ins for other objects or for objects that don’t exist yet.
Declaration
@interface NSProxy
Overview
Typically, a message to a proxy is forwarded to the real object or causes the proxy to load (or transform itself into) the real object. Subclasses of
NSProxy
can be used to implement transparent distributed messaging (for example,NSDistant<wbr>Object
) or for lazy instantiation of objects that are expensive to create.
NSProxy
implements the basic methods required of a root class, including those defined in theNSObject
protocol. However, as an abstract class it doesn’t provide an initialization method, and it raises an exception upon receiving any message it doesn’t respond to. A concrete subclass must therefore provide an initialization or creation method and override theforward<wbr>Invocation:
andmethod<wbr>Signature<wbr>For<wbr>Selector:
methods to handle messages that it doesn’t implement itself. A subclass’s implementation offorward<wbr>Invocation:
should do whatever is needed to process the invocation, such as forwarding the invocation over the network or loading the real object and passing it the invocation.method<wbr>Signature<wbr>For<wbr>Selector:
is required to provide argument type information for a given message; a subclass’s implementation should be able to determine the argument types for the messages it needs to forward and should construct anNSMethod<wbr>Signatureobject
accordingly. See theNSDistant<wbr>Object
,NSInvocation
, andNSMethod<wbr>Signature
class specifications for more information.
我们不用 self
来响应 timer
方法的 target
,而是用 NSProxy
来响应。
首先定义一个继承自 NSProxy
的子类
FXProxy.h
@interface FXProxy : NSProxy
+ (instancetype)proxyWithTransformObject:(id)object;
@end
复制代码
FXProxy.m
@interface FXProxy ()
@property (nonatomic, weak) id object;
@end
@implementation FXProxy
+ (instancetype)proxyWithTransformObject:(id)object{
FXProxy *proxy = [FXProxy alloc];
proxy.object = object;
return proxy;
}
-(id)forwardingTargetForSelector:(SEL)aSelector {
return self.object;
}
@end
复制代码
将 timer
中的 target
传入 NSProxy
子类对象,即 timer
持有 NSProxy
子类对象
//************解决timer强持有问题************
self.proxy = [FXProxy proxyWithTransformObject:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(fireInTheHole) userInfo:nil repeats:YES];
-(void)dealloc {
NSLog(@"%s",__func__);
[self.timer invalidate];
self.timer = nil;
}
复制代码
这样做的主要目的是将强引用的注意力转移成了消息转发。虚基类只负责消息转发,即使用 NSProxy
作为中间代理、中间者
这里有个疑问,定义的 proxy
对象,在 dealloc
释放时,还存在吗?
proxy
对象会正常释放,因为 self
正常释放了,所以可以释放其持有者,即 timer
和 proxy
,timer
的释放也打破了 runLoop
对 proxy
的强持有。完美的达到了两层释放,解释如下:
-
self
释放,导致了proxy
的释放 -
dealloc
方法中,timer
进行了释放,所以runloop
强引用也释放了
它们之间的引用链如下图所示: