面試題
1.使用CADisplayLink、NStimer有什么注意點(diǎn)?
- 注意循環(huán)引用
- 會(huì)造成時(shí)間不準(zhǔn)確的問題(NStimer依賴于RunLoop,如果RunLoop的任務(wù)過于繁重,可能會(huì)導(dǎo)致NSTimer不準(zhǔn)時(shí))
- 要求比較準(zhǔn)時(shí)的時(shí)候,還是需要用GCD來實(shí)現(xiàn)定時(shí)器
2.介紹下內(nèi)存的幾大區(qū)域?
地址從低到高:保留區(qū)/代碼區(qū)/數(shù)據(jù)段/堆/棧/內(nèi)核區(qū)
3.講一下你對(duì)ios內(nèi)存管理的理解?
TaggedPointer、NONPOINTER_ISA(64位系統(tǒng)下)、散列表(引用計(jì)數(shù)表、weak表)
4.ARC幫我們做了什么?
LLVM+Runtime
LLVM(編譯器在編譯的時(shí)候,會(huì)自動(dòng)幫我們?cè)诤线m的地方添加retain,release等操作)+runtime(在運(yùn)行時(shí)的比如弱指針會(huì)幫我們?nèi)ス芾砬宄?
總的來說:是LLVM和Runtime共同作用的結(jié)果
5.weak指針實(shí)現(xiàn)的原理?
將弱引用存儲(chǔ)到一個(gè)哈希表里,當(dāng)對(duì)象要銷毀時(shí),就會(huì)取出當(dāng)前對(duì)象的弱引用表,將該表存儲(chǔ)的弱引用都給清除掉
【具體的實(shí)現(xiàn)原理可參考-- http://www.lxweimin.com/p/9c52b6f9d01c】
6.autorelease對(duì)象在什么時(shí)機(jī)會(huì)被調(diào)用release?
如果有被autoreleasepool包裹的,是出大括號(hào)就開始被釋放(調(diào)用了pop的方法時(shí)候),如不是 :它可能是在某次RunLoop循環(huán)中,RunLoop休眠之前調(diào)用了release
7.方法里有局部對(duì)象,出了方法后會(huì)被立即釋放?
MRC環(huán)境下:不一定,是在當(dāng)前runloop循環(huán)中,即將進(jìn)入休眠時(shí)釋放
ARC環(huán)境下:馬上釋放,只要出了作用域就釋放
在下面我們會(huì)逐步來說明
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);
});
}
}
第二種:沒有崩潰(正常運(yùn)行)
分析:taggedPointer
abc直接存儲(chǔ)在指針里面了
何為:taggedPointer 下面我們會(huì)具體的分析
CADisplayLink、NSTimer使用注意
CADisplayLink、NSTimer會(huì)對(duì)target產(chǎn)生強(qiáng)引用,如果target又對(duì)他們產(chǎn)生強(qiáng)引用,那么必然會(huì)造成循環(huán)強(qiáng)引用
如:
self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(linkTest)];
self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector: @selector(timerTest) userInfo:nil repeats:YES];
- 解決辦法
- 使用block
- 使用代理對(duì)象(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解決:
//保證調(diào)用頻率和屏幕的刷幀頻率一致,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.專門用來做消息轉(zhuǎn)發(fā)的(和NSObject平級(jí))
- 2.和繼承自NSobject比較,效率高(本類方法沒有,不需要 從父類去找了,直接進(jìn)入消息轉(zhuǎn)發(fā))
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對(duì)象不需要調(diào)用init,因?yàn)樗緛砭蜎]有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 ,那么大部分就會(huì)進(jìn)入消息轉(zhuǎn)發(fā)階段。
//在調(diào)用isKindOfClass 的時(shí)候,進(jìn)入了消息轉(zhuǎn)發(fā)。進(jìn)入到 methodSignatureForSelector 和 forwardInvocation 這兩個(gè)方法。
//調(diào)用順序就改成了 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定時(shí)器
#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{
// 隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("timer", DISPATCH_QUEUE_SERIAL);
// 創(chuàng)建定時(shí)器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 設(shè)置時(shí)間
uint64_t start = 2.0; // 2秒后開始執(zhí)行
uint64_t interval = 1.0; // 每隔1秒執(zhí)行
dispatch_source_set_timer(timer,
dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC),interval * NSEC_PER_SEC, 0);
// 設(shè)置回調(diào)
dispatch_source_set_event_handler(timer, ^{
NSLog(@"1111");
});
// OR: dispatch_source_set_event_handler_f(timer, timerFire);
// 啟動(dòng)定時(shí)器
dispatch_resume(timer);
self.timer = timer; //得使用強(qiáng)引用引用下
}
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;
// 隊(duì)列
dispatch_queue_t queue = async ? dispatch_get_global_queue(0, 0) : dispatch_get_main_queue();
// 創(chuàng)建定時(shí)器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 設(shè)置時(shí)間
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);
// 定時(shí)器的唯一標(biāo)識(shí)
NSString *name = [NSString stringWithFormat:@"%zd", timers_.count];
// 存放到字典中
timers_[name] = timer;
dispatch_semaphore_signal(semaphore_);
// 設(shè)置回調(diào)
dispatch_source_set_event_handler(timer, ^{
task();
if (!repeats) { // 不重復(fù)的任務(wù)
[self cancelTask:name];
}
});
// 啟動(dòng)定時(shí)器
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
調(diào)用:
#import "ViewController_5.h"
#import "YDTimer.h"
@interface ViewController_5()
@property (copy, nonatomic) NSString *task;
@end
@implementation ViewController_5
- (void)viewDidLoad {
[super viewDidLoad];
// 接口設(shè)計(jì)
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程序的內(nèi)存布局
static int A = 10 ;
int B;
@implementation ViewController_6
-(void)viewDidLoad{
[super viewDidLoad];
//數(shù)據(jù)段
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
發(fā)現(xiàn):變量a和變量b不是一個(gè)對(duì)象(偽對(duì)象)、而變量c是一個(gè)真正的oc對(duì)象
(如果:是一個(gè)比較長的數(shù)字,或者小數(shù)等(占位較大的),就是一個(gè)真正的oc對(duì)象)
發(fā)現(xiàn):變量str1不是一個(gè)對(duì)象(偽對(duì)象,isa的指向0x0),而變量str,str2,str3是oc對(duì)象
(如果:是一個(gè)比較長的字符串,那么就是一個(gè)真正的OC對(duì)象)
我們?cè)趤砜纯瓷厦娲蛴〉膬?nèi)存地址:
對(duì)于NSNumber:是taggerPointer的末尾 是一樣的(現(xiàn)在的重新運(yùn)行時(shí)9)(再次運(yùn)行可能結(jié)果不一樣)
并且:他們的前面的幾位都有一樣的
對(duì)于string的 是NSTaggedPointerString的,他的地址和真正的字符串地址是有很大不一樣的
具體的:可以參考Objective-C 的 Tagged Pointer 實(shí)現(xiàn)、Objective-C對(duì)象的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進(jìn)制的地址轉(zhuǎn)為2進(jìn)制的)
ios平臺(tái)(64bit):最高有效位為1
Mac平臺(tái):最低有效位是1
我們用上面的例子:
對(duì)于:NSNumber *a = @(1); a的地址是:0x982ce8502b222809 轉(zhuǎn)為二進(jìn)制
1001/1000/0010/1100/1110/1000/0101/0000/0010/1011/0010/0010/0010/1000/0000/1001
發(fā)現(xiàn):最高位為1(是Tagged Pointer)
對(duì)于:NSNumber *c = @(3.1415926); c的地址是:0x6000029efa00轉(zhuǎn)為二進(jìn)制
0000/0000/0000/0000/0110/0000/0000/0000/0000/0010/1001/1110/1111/1010/0000/0000
發(fā)現(xiàn):最高位為0(不是Tagged Pointer)
這里我們可以發(fā)現(xiàn):
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"];
});
}
沒有崩潰:不會(huì)出現(xiàn)資源爭奪的問題(它的值是直接存儲(chǔ)在指針地址里面的,不是一個(gè)真正的oc對(duì)象)
OC對(duì)象的內(nèi)存管理
MRC:
Person *person1 = [[[Person alloc] init] autorelease];
在自動(dòng)釋放池里面調(diào)用的autorelease會(huì)被釋放掉
============
// 自動(dòng)生成成員變量和屬性的setter、getter實(shí)現(xiàn)
@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
拷貝的目的:產(chǎn)生一個(gè)副本對(duì)象,跟源對(duì)象互不影響
修改了源對(duì)象,不會(huì)影響副本對(duì)象
修改了副本對(duì)象,不會(huì)影響源對(duì)象
iOS提供了2個(gè)拷貝方法
1.copy,不可變拷貝,產(chǎn)生不可變副本
2.mutableCopy,可變拷貝,產(chǎn)生可變副本
深拷貝和淺拷貝
1.深拷貝:內(nèi)容拷貝,產(chǎn)生新的對(duì)象
2.淺拷貝:指針拷貝,沒有產(chǎn)生新的對(duì)象
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
發(fā)現(xiàn):str1和str2指向同一個(gè)地方,而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; //有問題,講一個(gè)可變的數(shù)組copy成了不可變的了
友情鏈接: