1、代码段:编译之后的代码
2、数据段2.1、字符串常量:比如NSString *str = @“123”
2.2、已初始化数据:已初始化的全局变量、静态变量等
2.3、未初始化数据:未初始化的全局变量、静态变量等3、堆:通过alloc、malloc、calloc等动态分配的空间,分配的内存空间地址越来越大
4、栈:函数调用开销,比如局部变量。分配的内存空间地址越来越小
1、从64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象的存储.
2、在没有使用Tagged Pointer之前, NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值。使用Tagged Pointer之后,NSNumber指针里面存储的数据变成了:Tag + Data,也就是将数据直接存储在了指针中。(Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要 malloc 和 free。)
3、当指针不够存储数据时,才会使用动态分配内存的方式来存储数据。
4、objc_msgSend能识别Tagged Pointer,比如NSNumber的intValue方法,直接从指针提取数据,节省了以前的调用开销。
5、在内存读取上有着 3 倍的效率,创建时比以前快 106 倍。
优点:
1、减少了 64 位机器下程序的内存占用,还提高了运行效率。完美地解决了小内存对象在存储和访问效率上的问题。
弊病:
1、因为并不是真正的对象,而是一个伪对象,是没有isa指针的。
2、因为不是真正的对象,所以如果你直接访问Tagged Pointer的isa成员的话,在编译时将会得到警告。这时我们只要避免在代码中直接访问对象的 isa 变量,即可避免这个问题。
判断是否为Tagged Pointer:
1、iOS平台,最高有效位是1(第64bit)
2、Mac平台,最低有效位是1
#if TARGET_OS_OSX & __x86_64__
//64-bit Mac tag bit is LSB
# define OBJC_MSB_TAGGED_POINTERS 0
#else
//Everything else tag bit is MSB
# define OBJC_MSB_TAGGED_POINTERS 1
#endif
#if OBJC_MSB_TAGGED_POINTERS
# define _OBJC_TAG_MASK (1UL<<63)
#else
# define _OBJC_TAG_MASK 1UL
#endif
static inline bool
_objc_isTaggedPointer(const void *_Nullable ptr) {
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
思考以下2段代码能发生什么事?有什么区别?
1、第一段代码会导致闪退,因为第一段代码每次赋值都是给地址赋值,每次setter之前都会执行release操作。因为是异步操作,所以可能会导致多次release,导致引用计数已经小于等于0,对象已经没销毁。最终抛出坏内存异常。
2、第二段代码属于指针赋值,直接赋值,没有引用计数操作,所以没有问题。
已知iOS中常用的定时器有三种:NSTimer、CADisplayLink、GCD。
它们有各自的特性和应用场景:
1、NSTimer
1.1、存在延迟:不管是一次性的还是周期性的timer的实际触发事件的时间,都会与所加入的RunLoop和RunLoop Mode有关,如果此RunLoop正在执行一个连续性的运算,timer就会被延时出发。重复性的timer遇到这种情况,如果延迟超过了一个周期,则会在延时结束后立刻执行,并按照之前指定的周期继续执行。
1.2、必须加入Runloop:注意与uiscrollview的使用,要切换RunLoopMode状态。
2、CADisplayLink
1、保证调用频率和屏幕的刷帧频率一致,60FPS。
2、如果调用的方法比较耗时,超过了屏幕刷新周期,就会导致跳过若干次回调调用机会,跳过次数取决CPU的忙碌程度。
3、CADisplayLink适合做界面的不停重绘,比如视频播放的时候需要不停地获取下一帧用于界面渲染。
3、GCD
1、时间准确
2、可以使用子线程,解决定时间跑在主线程上卡UI问题。
查看以下代码能否正常编译运行?会出现什么问题?需要怎么实现?
@interface ViewController ()
@property (strong, nonatomic) CADisplayLink *link;
@property (strong, nonatomic) NSTimer *timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(linkTest)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
}
- (void)timerTest {
NSLog(@"%s", __func__);
}
- (void)linkTest {
NSLog(@"%s", __func__);
}
- (void)dealloc {
NSLog(@"%s", __func__);
[self.link invalidate];
[self.timer invalidate];
}
@end
以上代码可以正常编译运行,但是离开这个页面时,因为它们之间互相强引用,导致内存无法正常释放(内存泄漏)。以timer为例,见下图:
解决方案,可以在两者之间添加一个弱引用,见下图:
这样当ViewController销毁时,发现没有其他对象强引用它,那么整个相关链条就可以正常销毁。
代码实现:
方法一、使用block
timer可以使用block弱引用实现
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf timerTest];
}];
}
- (void)timerTest {
NSLog(@"%s", __func__);
}
方法二、使用代理对象(NSProxy)
1、NSProxy是专为代理而生,当调用没有实现的方法时,直接触发消息转发。
2、如果继承的事NSObject时,会先触发消息发送机制,如果没有时才会进入消息转发阶段,更耗时耗性能。
ZMProxy.h:
#import <Foundation/Foundation.h>
@interface ZMProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
ZMProxy.m:
#import "ZMProxy.h"
@implementation ZMProxy
+ (instancetype)proxyWithTarget:(id)target {
ZMProxy *proxy = [[ZMProxy alloc] init];
proxy.target = target;
return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
@end
ZMProxy1.h:
#import <Foundation/Foundation.h>
@interface ZMProxy1 : NSObject
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
ZMProxy1.m:
#import "ZMProxy1.h"
@implementation ZMProxy1
+ (instancetype)proxyWithTarget:(id)target {
ZMProxy1 *proxy = [[ZMProxy1 alloc] init];
proxy.target = target;
return proxy;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
return self.target;
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
self.link = [CADisplayLink displayLinkWithTarget:[ZMProxy proxyWithTarget:self] selector:@selector(linkTest)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[ZMProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
}
- (void)timerTest {
NSLog(@"%s", __func__);
}
- (void)linkTest {
NSLog(@"%s", __func__);
}
- (void)dealloc {
NSLog(@"%s", __func__);
[self.link invalidate];
[self.timer invalidate];
}
ZMTimer.h:
#import <Foundation/Foundation.h>
@interface ZMTimer : NSObject
/// 开启定时器【代码块回调】
/// @param task 任务
/// @param start 开始时间
/// @param interval 时间间隔
/// @param repeats 是否重复
/// @param async 是否异步
/// @return 返回定时器标记
+ (NSString *)execTask:(void(^)(void))task
start:(NSTimeInterval)start
interval:(NSTimeInterval)interval
repeats:(BOOL)repeats
async:(BOOL)async;
/// 开启定时器【SEL实现】
/// @param target 添加对象
/// @param selector 方法实现
/// @param start 开始时间
/// @param interval 时间间隔
/// @param repeats 是否重复
/// @param async 是否异步
/// @return 返回定时器标记
+ (NSString *)execTask:(id)target
selector:(SEL)selector
start:(NSTimeInterval)start
interval:(NSTimeInterval)interval
repeats:(BOOL)repeats
async:(BOOL)async;
/// 取消定时器任务
/// @param name 定时器标记
+ (void)cancelTask:(NSString *)name;
@end
ZMTimer.m:
#import "ZMTimer.h"
@implementation ZMTimer
static NSMutableDictionary *timers_;
dispatch_semaphore_t semaphore_;
+ (void)initialize {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
timers_ = [NSMutableDictionary dictionary];
semaphore_ = dispatch_semaphore_create(1);
});
}
/// 开启定时器【代码块回调】
/// @param task 任务
/// @param start 开始时间
/// @param interval 时间间隔
/// @param repeats 是否重复
/// @param async 是否异步
/// @return 返回定时器标记
+ (NSString *)execTask:(void (^)(void))task
start:(NSTimeInterval)start
interval:(NSTimeInterval)interval
repeats:(BOOL)repeats
async:(BOOL)async {
if (!task || start < 0 || (interval <= 0 && repeats)) return nil;
// 1、创建队列
dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : dispatch_get_main_queue();
// 2、创建定时器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 3、设置时间
dispatch_source_set_timer(timer,
dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC),
interval * NSEC_PER_SEC, 0);
// 加锁
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
// 定时器的唯一标识
NSString *name = [NSString stringWithFormat:@"%zd", timers_.count];
// 4、存放到字典中
timers_[name] = timer;
// 解锁
dispatch_semaphore_signal(semaphore_);
// 5、设置回调
dispatch_source_set_event_handler(timer, ^{
task();
if (!repeats) { // 不重复的任务
[self cancelTask:name];
}
});
// 6、启动定时器
dispatch_resume(timer);
return name;
}
/// 开启定时器【SEL实现】
/// @param target 添加对象
/// @param selector 方法实现
/// @param start 开始时间
/// @param interval 时间间隔
/// @param repeats 是否重复
/// @param async 是否异步
/// @return 返回定时器标记
+ (NSString *)execTask:(id)target
selector:(SEL)selector
start:(NSTimeInterval)start
interval:(NSTimeInterval)interval
repeats:(BOOL)repeats
async:(BOOL)async {
if (!target || !selector) return nil;
return [self execTask:^{
if ([target respondsToSelector:selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[target performSelector:selector];
#pragma clang diagnostic pop
}
} start:start interval:interval repeats:repeats async:async];
}
/// 取消定时器任务
/// @param name 定时器标记
+ (void)cancelTask:(NSString *)name {
if (name.length == 0) return;
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
dispatch_source_t timer = timers_[name];
if (timer) {
// 7、取消定时器
dispatch_source_cancel(timer);
[timers_ removeObjectForKey:name];
}
dispatch_semaphore_signal(semaphore_);
}
@end
实现:
#import "ViewController.h"
#import "ZMTimer.h"
@interface ViewController ()
@property (copy, nonatomic) NSString *task;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.task = [ZMTimer execTask:self
selector:@selector(doTask)
start:2.0
interval:1.0
repeats:YES
async:NO];
// self.task = [MJTimer execTask:^{
// NSLog(@"111111");
// } start:2.0 interval:-10 repeats:NO async:NO];
}
- (void)doTask {
NSLog(@"222222");
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[ZMTimer cancelTask:self.task];
}
@end
1、在iOS中,使用
引用计数
来管理OC对象的内存。
2、一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间。
2、调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1
3、内存管理的经验总结:3.1、当调用alloc、new、copy、mutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease来释放它。
3.2、想拥有某个对象,就让它的引用计数+1;不想再拥有某个对象,就让它的引用计数-1。4、可以通过以下私有函数来查看自动释放池的情况
extern void _objc_autoreleasePoolPrint(void);
1、在64bit中,引用计数可以直接存储在优化过的isa指针中,也可能存储在SideTable类中。
2、refcnts是一个存放着对象引用计数的散列表。
当一个对象要释放时,会自动调用dealloc,接下的调用轨迹是:
dealloc
_objc_rootDealloc
rootDealloc
object_dispose
objc_destructInstance、free
1、自动释放池的主要底层数据结构是:__AtAutoreleasePool
、AutoreleasePoolPage
。
2、调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的。
3、源码分析:
clang重写
@autoreleasepool
objc4源码:NSObject.mm
1、每个AutoreleasePoolPage
对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址
2、所有的AutoreleasePoolPage
对象通过双向链表的形式连接在一起。
1、调用push方法会将一个POOL_BOUNDARY
入栈,并且返回其存放的内存地址。
2、调用pop方法时传入一个POOL_BOUNDARY
的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY
。
3、id *next
指向了下一个能存放autorelease对象地址的区域。
iOS在主线程的Runloop中注册了2个Observer
1、第1个Observer监听了
kCFRunLoopEntry
事件,会调用objc_autoreleasePoolPush()
。
2、第2个Observer:2.1、监听了
kCFRunLoopBeforeWaiting
事件,会调用objc_autoreleasePoolPop()
、objc_autoreleasePoolPush()
。
2.2、监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()
。
1、使用CADisplayLink、NSTimer有什么注意点?
2、介绍下内存的几大区域
3、讲一下你对 iOS 内存管理的理解
4、ARC 都帮我们做了什么?
LLVM(编译器) + Runtime(运行时)
编译器:如自动释放池的实现逻辑
运行时:比如弱引用的实现逻辑
总的来说:ARC是LLVM编译器和Runtime系统相互协作的结果。
5、weak指针的实现原理
当一个对象要释放时,会自动调用dealloc。底层实现:
obj->clearDeallocating();//将指向当前对象的指针置为nil
6、autorelease对象在什么时机会被调用release
什么时候调用release是由RunLoop来控制的,它可能是在某次RunLoop循环中,RunLoop休眠之前调用了release。
7、方法里有局部对象, 出了方法后会立即释放吗?
1、如果这个对象是autorelease对象,什么时候调用release是由RunLoop来控制的,它可能是在某次RunLoop循环中,RunLoop休眠之前调用了release。
2、如果不是autorelease对象,在方法结束之前,底层会调用release方法,释放这个局部对象。