面试题
1.使用CADisplayLink、NStimer有什么注意点?
- 注意循环引用
- 会造成时间不准确的问题(NStimer依赖于RunLoop,如果RunLoop的任务过于繁重,可能会导致NSTimer不准时)
- 要求比较准时的时候,还是需要用GCD来实现定时器
2.介绍下内存的几大区域?
地址从低到高:保留区/代码区/数据段/堆/栈/内核区
3.讲一下你对ios内存管理的理解?
TaggedPointer、NONPOINTER_ISA(64位系统下)、散列表(引用计数表、weak表)
4.ARC帮我们做了什么?
LLVM+Runtime
LLVM(编译器在编译的时候,会自动帮我们在合适的地方添加retain,release等操作)+runtime(在运行时的比如弱指针会帮我们去管理清除)
总的来说:是LLVM和Runtime共同作用的结果
5.weak指针实现的原理?
将弱引用存储到一个哈希表里,当对象要销毁时,就会取出当前对象的弱引用表,将该表存储的弱引用都给清除掉
【具体的实现原理可参考-- https://www.jianshu.com/p/9c52b6f9d01c】
6.autorelease对象在什么时机会被调用release?
如果有被autoreleasepool包裹的,是出大括号就开始被释放(调用了pop的方法时候),如不是 :它可能是在某次RunLoop循环中,RunLoop休眠之前调用了release
7.方法里有局部对象,出了方法后会被立即释放?
MRC环境下:不一定,是在当前runloop循环中,即将进入休眠时释放
ARC环境下:马上释放,只要出了作用域就释放
在下面我们会逐步来说明
Demo代码可见MemoryManagement
分析下面的情况
-(void)test1{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for(int i=0;i<1000;i++){
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abcdefghijk"];
});
}
}
================
-(void)test2{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for(int i=0;i<1000;i++){
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abc"];
});
}
}
解决办法:加锁
@property (assign, nonatomic) os_unfair_lock lock;
@property (nonatomic,strong)NSString *name;
-(void)test1{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for(int i=0;i<1000;i++){
dispatch_async(queue, ^{
os_unfair_lock_lock(&_lock);
self.name = [NSString stringWithFormat:@"abcdefghijk"];
os_unfair_lock_unlock(&_lock);
});
}
}
第二种:没有崩溃(正常运行)
分析:taggedPointer
abc直接存储在指针里面了
何为:taggedPointer 下面我们会具体的分析
CADisplayLink、NSTimer使用注意
CADisplayLink、NSTimer会对target产生强引用,如果target又对他们产生强引用,那么必然会造成循环强引用
如:
self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(linkTest)];
self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector: @selector(timerTest) userInfo:nil repeats:YES];
- 解决办法
- 使用block
- 使用代理对象(NSProxy)
timer解决:
方法一:
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
[weakSelf timerTest];
}];
- (void)dealloc{
NSLog(@"%s", __func__);
[self.timer invalidate];
}
方法二:
YDProxy.h
#import <Foundation/Foundation.h>
@interface YDProxy : NSObject
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
YDProxy.m
#import "YDProxy.h"
@implementation YDProxy
+ (instancetype)proxyWithTarget:(id)target{
YDProxy *proxy = [[YDProxy alloc] init];
proxy.target = target;
return proxy;
}
- (id)forwardingTargetForSelector:(SEL)aSelector{
return self.target;
}
-(void)dealloc{
NSLog(@"%s", __func__);
}
@end
使用:
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[YDProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
-(void)timerTest{
NSLog(@"%s",__FUNCTION__);
}
-(void)dealloc{
NSLog(@"%s", __func__);
[self.timer invalidate];
}
=====================
CADisplayLink解决:
//保证调用频率和屏幕的刷帧频率一致,60FPS
self.link = [CADisplayLink displayLinkWithTarget:[MJProxy proxyWithTarget:self] selector:@selector(linkTest)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
-(void)dealloc{
[self.link invalidate];
}
NSProxy
- 1.专门用来做消息转发的(和NSObject平级)
- 2.和继承自NSobject比较,效率高(本类方法没有,不需要 从父类去找了,直接进入消息转发)
YDProxy2.h
#import <UIKit/UIKit.h>
@interface YDProxy2 : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end
YDProxy2.m
#import "YDProxy2.h"
@implementation YDProxy2
+ (instancetype)proxyWithTarget:(id)target{
// NSProxy对象不需要调用init,因为它本来就没有init方法
YDProxy2 *proxy = [YDProxy2 alloc];
proxy.target = target;
return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
return [self.target methodSignatureForSelector:sel];
}
//重定向消息
- (void)forwardInvocation:(NSInvocation *)invocation{
[invocation invokeWithTarget:self.target];
}
@end
ViewController_3.m
#import "ViewController_3.h"
#import "YDProxy2.h"
@interface ViewController_3()
@property (strong, nonatomic) NSTimer *timer;
@end
@implementation ViewController_3
-(void)viewDidLoad{
[super viewDidLoad];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[YDProxy2 proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
//分析:
//继承了NSProxy ,那么大部分就会进入消息转发阶段。
//在调用isKindOfClass 的时候,进入了消息转发。进入到 methodSignatureForSelector 和 forwardInvocation 这两个方法。
//调用顺序就改成了 target 。也就是说先拿到[YDProxy2 proxyWithtarget: vc] 中的 vc
//然后变成了[vc isKindOfClass:[ViewController class]];
NSLog(@"%d",[[YDProxy2 proxyWithTarget:self] isKindOfClass:[ViewController_3 class]]);
}
- (void)timerTest{
NSLog(@"%s", __func__);
}
- (void)dealloc{
NSLog(@"%s", __func__);
[self.timer invalidate];
}
@end
GCD定时器
#import "ViewController_4.h"
@interface ViewController_4()
@property (nonatomic,strong)dispatch_source_t timer;
@end
@implementation ViewController_4
-(void)viewDidLoad{
[super viewDidLoad];
[self test1];
}
-(void)test1{
// 队列
dispatch_queue_t queue = dispatch_queue_create("timer", DISPATCH_QUEUE_SERIAL);
// 创建定时器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 设置时间
uint64_t start = 2.0; // 2秒后开始执行
uint64_t interval = 1.0; // 每隔1秒执行
dispatch_source_set_timer(timer,
dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC),interval * NSEC_PER_SEC, 0);
// 设置回调
dispatch_source_set_event_handler(timer, ^{
NSLog(@"1111");
});
// OR: dispatch_source_set_event_handler_f(timer, timerFire);
// 启动定时器
dispatch_resume(timer);
self.timer = timer; //得使用强引用引用下
}
void timerFire(void *param){
NSLog(@"2222 - %@", [NSThread currentThread]);
}
- (void)dealloc{
NSLog(@"%s", __func__);
}
@end
接下来我们封装下:
YDTimer.h
#import <Foundation/Foundation.h>
@interface YDTimer : NSObject
+ (NSString *)execTask:(void(^)(void))task
start:(NSTimeInterval)start
interval:(NSTimeInterval)interval
repeats:(BOOL)repeats
async:(BOOL)async;
+ (NSString *)execTask:(id)target
selector:(SEL)selector
start:(NSTimeInterval)start
interval:(NSTimeInterval)interval
repeats:(BOOL)repeats
async:(BOOL)async;
+ (void)cancelTask:(NSString *)name;
@end
YDTimer.m
#import "YDTimer.h"
@implementation YDTimer
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);
});
}
+ (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;
// 队列
dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : dispatch_get_main_queue();
// 创建定时器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 设置时间
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];
// 存放到字典中
timers_[name] = timer;
dispatch_semaphore_signal(semaphore_);
// 设置回调
dispatch_source_set_event_handler(timer, ^{
task();
if (!repeats) { // 不重复的任务
[self cancelTask:name];
}
});
// 启动定时器
dispatch_resume(timer);
return name;
}
+ (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];
}
+ (void)cancelTask:(NSString *)name{
if (name.length == 0) return;
dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
dispatch_source_t timer = timers_[name];
if (timer) {
dispatch_source_cancel(timer);
[timers_ removeObjectForKey:name];
}
dispatch_semaphore_signal(semaphore_);
}
@end
调用:
#import "ViewController_5.h"
#import "YDTimer.h"
@interface ViewController_5()
@property (copy, nonatomic) NSString *task;
@end
@implementation ViewController_5
- (void)viewDidLoad {
[super viewDidLoad];
// 接口设计
self.task = [YDTimer execTask:self
selector:@selector(doTask)
start:2.0
interval:1.0
repeats:YES
async:NO];
}
- (void)doTask{
NSLog(@"doTask - %@", [NSThread currentThread]);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[YDTimer cancelTask:self.task];
}
- (void)dealloc{
NSLog(@"%s", __func__);
[YDTimer cancelTask:self.task];
}
@end
iOS程序的内存布局
static int A = 10 ;
int B;
@implementation ViewController_6
-(void)viewDidLoad{
[super viewDidLoad];
//数据段
NSLog(@"%p",&A);
NSLog(@"%p",&B);
NSString *str = @"字符串常量";
NSLog(@"%p",str);
//堆
NSObject *obj1 = [[NSObject alloc]init];
NSObject *obj2 = [[NSObject alloc]init];
NSLog(@"%p",obj1);
NSLog(@"%p",obj2);
//栈
int age=10;
float height = 180.0f;
NSLog(@"%p",&age);
NSLog(@"%p",&height);
}
打印:
MemoryManagement[10795:942733] 0x105488190
MemoryManagement[10795:942733] 0x105488210
MemoryManagement[10795:942733] 0x105485450
MemoryManagement[10795:942733] 0x600002aab7e0
MemoryManagement[10795:942733] 0x600002aaba60
MemoryManagement[10795:942733] 0x7ffeea77c3c4
MemoryManagement[10795:942733] 0x7ffeea77c3c0
Tagged Pointer
#import "ViewController_7.h"
#import <objc/runtime.h>
@implementation ViewController_7
-(void)viewDidLoad{
[super viewDidLoad];
NSNumber *a = @(1);
NSLog(@"%p",a);
NSNumber *b = @(2);
NSLog(@"%p",b);
NSNumber *c = @(3.1415926);
NSLog(@"%p",c);
NSLog(@"=======Start======");
for(int i=0;i<10;i++){
NSNumber *d = @((int)(arc4random() % 520));
NSLog(@"%p--a=%@",d,d);
}
NSLog(@"=======End======");
NSString *str = @"abc"; // __NSCFConstantString(常量字符串)
NSLog(@"%p -- %@",str,[str class]);
NSString *str1 = [NSString stringWithFormat:@"%@",str]; // NSTaggedPointerString
NSLog(@"%p -- %@",str1,[str1 class]);
NSString *str2 = [str copy]; // __NSCFConstantString(常量字符串)
NSLog(@"%p -- %@",str2,[str2 class]);
NSMutableString *str3 = [str mutableCopy]; // __NSCFString(字符串)
NSLog(@"%p -- %@",str3,[str3 class]);
}
@end
打印:
MemoryManagement[30107:1510547] 0x982ce8502b222809
MemoryManagement[30107:1510547] 0x982ce8502b222839
MemoryManagement[30107:1510547] 0x6000029efa00
MemoryManagement[30107:1510547] =======Start======
MemoryManagement[30107:1510547] 0x982ce8502b222d49--a=85
MemoryManagement[30107:1510547] 0x982ce8502b222009--a=129
MemoryManagement[30107:1510547] 0x982ce8502b222239--a=162
MemoryManagement[30107:1510547] 0x982ce8502b223959--a=276
MemoryManagement[30107:1510547] 0x982ce8502b222899--a=8
MemoryManagement[30107:1510547] 0x982ce8502b223f99--a=376
MemoryManagement[30107:1510547] 0x982ce8502b222ee9--a=111
MemoryManagement[30107:1510547] 0x982ce8502b223429--a=451
MemoryManagement[30107:1510547] 0x982ce8502b223969--a=279
MemoryManagement[30107:1510547] 0x982ce8502b222c29--a=67
MemoryManagement[30107:1510547] =======End======
MemoryManagement[30107:1510547] 0x10f0fe2d8 -- __NSCFConstantString
MemoryManagement[30107:1510547] 0x882ce8502d140e08 -- NSTaggedPointerString
MemoryManagement[30107:1510547] 0x10f0fe2d8 -- __NSCFConstantString
发现:变量a和变量b不是一个对象(伪对象)、而变量c是一个真正的oc对象
(如果:是一个比较长的数字,或者小数等(占位较大的),就是一个真正的oc对象)
发现:变量str1不是一个对象(伪对象,isa的指向0x0),而变量str,str2,str3是oc对象
(如果:是一个比较长的字符串,那么就是一个真正的OC对象)
我们在来看看上面打印的内存地址:
对于NSNumber:是taggerPointer的末尾 是一样的(现在的重新运行时9)(再次运行可能结果不一样)
并且:他们的前面的几位都有一样的
对于string的 是NSTaggedPointerString的,他的地址和真正的字符串地址是有很大不一样的
具体的:可以参考Objective-C 的 Tagged Pointer 实现、Objective-C对象的TaggedPointer特性
【这里只是简单的瞅瞅】
判断是否为Tagged Pointer
objc4-750里面
objc_internal.h
#if (TARGET_OS_OSX || TARGET_OS_IOSMAC) && __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
# 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;
}
即:(需要讲16进制的地址转为2进制的)
ios平台(64bit):最高有效位为1
Mac平台:最低有效位是1
我们用上面的例子:
对于:NSNumber *a = @(1); a的地址是:0x982ce8502b222809 转为二进制
1001/1000/0010/1100/1110/1000/0101/0000/0010/1011/0010/0010/0010/1000/0000/1001
发现:最高位为1(是Tagged Pointer)
对于:NSNumber *c = @(3.1415926); c的地址是:0x6000029efa00转为二进制
0000/0000/0000/0000/0110/0000/0000/0000/0000/0010/1001/1110/1111/1010/0000/0000
发现:最高位为0(不是Tagged Pointer)
这里我们可以发现:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for(int i=0;i<1000;i++){
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abc"];
});
}
没有崩溃:不会出现资源争夺的问题(它的值是直接存储在指针地址里面的,不是一个真正的oc对象)
OC对象的内存管理
MRC:
Person *person1 = [[[Person alloc] init] autorelease];
在自动释放池里面调用的autorelease会被释放掉
============
// 自动生成成员变量和属性的setter、getter实现
@synthesize age = _age;
@property (nonatomic, retain) Dog *dog;
@synthesize dog= _dog;
/*
- (void)setDog:(Dog *)dog{
if (_dog != dog) {
[_dog release];
_dog = [dog retain];
}
}
- (Dog *)dog{
return _dog;
}
*/
- (void)dealloc{
self.dog = nil;
[super dealloc];
}
==========
@property (retain, nonatomic) NSMutableArray *data;
1.
self.data = [[[NSMutableArray alloc] init] autorelease];
2.
self.data = [[NSMutableArray alloc] init];
[self.data release];
3.
NSMutableArray *data = [[NSMutableArray alloc] init];
self.data = data;
[data release];
4.self.data = [NSMutableArray array];
- (void)dealloc {
self.data = nil;
[super dealloc];
}
copy
拷贝的目的:产生一个副本对象,跟源对象互不影响
修改了源对象,不会影响副本对象
修改了副本对象,不会影响源对象
iOS提供了2个拷贝方法
1.copy,不可变拷贝,产生不可变副本
2.mutableCopy,可变拷贝,产生可变副本
深拷贝和浅拷贝
1.深拷贝:内容拷贝,产生新的对象
2.浅拷贝:指针拷贝,没有产生新的对象
NSString *str1 = [NSString stringWithFormat:@"test"];
NSLog(@"%p -- %@",str1,[str1 class]);//0xf7f20831888a5de3 -- NSTaggedPointerString
NSString *str2 = [str1 copy];
NSLog(@"%p -- %@",str2,[str2 class]);//0xf7f20831888a5de3 -- NSTaggedPointerString
NSMutableString *str3 = [str1 mutableCopy];
NSLog(@"%p -- %@",str3,[str3 class]);//0x600002a13480 -- __NSCFString
[str3 release];
[str2 release];
[str1 release];
打印:
MemoryManagement[30966:1563960] 0x8bbfbc3f19412f76 -- NSTaggedPointerString
MemoryManagement[30966:1563960] 0x8bbfbc3f19412f76 -- NSTaggedPointerString
MemoryManagement[30966:1563960] 0x6000016a03c0 -- __NSCFString
发现:str1和str2指向同一个地方,而str3不是
NSMutableString *str1 = [[NSMutableString alloc]initWithString:@"test"];
NSLog(@"%p -- %@",str1,[str1 class]);//0x600002d16dc0 -- __NSCFString
NSString *str2 = [str1 copy];
NSLog(@"%p -- %@",str2,[str2 class]);//0xef964e52ba42afec -- NSTaggedPointerString
NSString *str3 = [str2 mutableCopy];
NSLog(@"%p -- %@",str3,[str3 class]);//0x600002d16cd0 -- __NSCFString
[str3 release];
[str2 release];
[str1 release];
打印:
MemoryManagement[31096:1571416] 0x600002d16dc0 -- __NSCFString
MemoryManagement[31096:1571416] 0xef964e52ba42afec -- NSTaggedPointerString
MemoryManagement[31096:1571416] 0x600002d16cd0 -- __NSCFString
copy和mutableCopy
@property (copy, nonatomic) NSMutableArray *data; //有问题,讲一个可变的数组copy成了不可变的了
友情链接: