GitHub HomePage 只写了部分题目,完整版本参考GitHub
MrPeak的题目出处
谈下iOS开发中知道的哪些锁? 哪个性能最差?SD和AFN使用的哪个? 一般开发中你最常用哪个? 哪个锁apple存在问题又是什么问题?
-
我们在使用多线程的时候多个线程可能会访问同一块资源,这样就很容易引发数据错乱和数据安全等问题,这时候就需要我们保证每次只有一个线程访问这一块资源,锁 应运而生
-
OSSpinLock 自旋锁 ,存在的问题是, 优先级反转问题,破坏了spinlock
-
@synchronized
性能最差,但是SD和AFN等其他框架很多使用这个. -
dispatch_semaphore 信号量 一般开发中较为常用
dispatch_semaphore_t signal = dispatch_semaphore_create(1);
dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 1.0f * NSEC_PER_SEC);
//Thread1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"Thread1 waiting");
dispatch_semaphore_wait(signal, overTime); //signal 值 -1
NSLog(@"Thread1");
dispatch_semaphore_signal(signal); //signal 值 +1
NSLog(@"Thread1 send signal");
});
//Thread2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"Thread2 waiting");
dispatch_semaphore_wait(signal, overTime);
NSLog(@"Thread2");
dispatch_semaphore_signal(signal);
NSLog(@"Thread2 send signal");
});
dispatch_semaphore_create(1): 若传入为0(over time 失效) 则阻塞线程并等待timeout,时间到后会执行其后的语句,
dispatch_semaphore_wait(signal, overTime):可以理解为 lock,会使得 signal 值 -1
dispatch_semaphore_signal(signal):可以理解为 unlock,会使得 signal 值 +1
- tips 面试期间谈及信号量,一定要说具体项目使用场景.
如何使用runtime hook一个class的某个方法,又如何hook某个instance的方法?
这个问题,首先要考虑怎么回答才能不被套路
- 考虑 hook是否有公开头文件的类,有的话写一个Utility函数,再使用category,没有的话就建一个类作为新函数载体,
- 然后先为被hook的类增加函数,再替换。
- 如何hook某个instance的方法,应该可以定义一个函数指针变量(IMP要谈及吧),hook时将要调用的地址赋给这个变量,调用时把这个变量当作函数来用 (RAC框架 hook 谈及)
weak修饰的释放则自动被置为nil的实现原理
- Runtime维护着一个Weak表,用于存储指向某个对象的所有Weak指针
- Weak表是Hash表,Key是所指对象的地址,Value是Weak指针地址的数组
- 在对象被回收的时候,经过层层调用,会最终触发下面的方法将所有Weak指针的值设为nil。
- runtime源码,objc-weak.m 的 arr_clear_deallocating 函数
- weak指针的使用涉及到Hash表的增删改查,有一定的性能开销.
HTTPS的加密原理
- 服务器端用非对称加密(RSA)生成公钥和私钥
- 然后把公钥发给客户端, 服务器则保存私钥
- 客户端拿到公钥后, 会生成一个密钥, 这个密钥就是将来客户端和服务器用来通信的钥匙
- 然后客户端用公钥对密钥进行加密, 再发给服务器
- 服务器拿到客户端发来的加密后的密钥后, 再使用私钥解密密钥, 到此双方都获得通信的钥匙
##crash的收集和定位bug的方式谈下
- iTunes Connect(Manage Your Applications - View Details - Crash Reports),但是前提用户设置->隐私->诊断与用量->诊断与用量数据开启.一般不推荐
- 自己实现应用内崩溃收集,并上传服务器.(收集异常,存储到本地,下次用户打开程序时上传给我们)
- 在程序启动时加上一个异常捕获监听,用来处理程序崩溃时的回调动作UncaughtExceptionHandler是一个函数指针,该函数需要我们实现,可以取自己想要的名字。当程序发生异常崩溃时,该函数会得到调用,这跟C,C++中的回调函数的概念是一样的
NSSetUncaughtExceptionHandler (&UncaughtExceptionHandler)。 程序启动代理方法 //:collection crash info by DragonLi void UncaughtExceptionHandler(NSException *exception) { NSArray *callStack = [exception callStackSymbols]; NSString *reason = [exception reason]; NSString *name = [exception name]; NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; [formatter setDateFormat:@"YYYY-MM-dd HH:mm:ss"]; NSString * dateStr = [formatter stringFromDate:[NSDate date]]; NSString * iOS_Version = [[UIDevice currentDevice] systemVersion]; NSString * PhoneSize = NSStringFromCGSize([[UIScreen mainScreen] bounds].size); NSString * App_Version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]; NSString * iPhoneType = @"当前设备名字"; NSString *uploadString = @"所有拼接信息"; // 存储到本地沙盒.下次启动找寻 }
- 第三方收集crash (比如说集成友盟,使用dYSM分析定位代码)
SEL和Method和IMP分别说下再谈下对IMP的理解?
- SEL是“selector”的一个类型,表示一个方法的名字
- Method(我们常说的方法)表示一种类型,这种类型与selector和实现(implementation)相关
-
IMP定义为 id (*IMP) (id, SEL, …)。这样说来,IMP是一个指向函数的指针,这个被指向的函数包括id(“self”指针),调用的SEL(方法名),再加上一些其他参数.说白了IMP就是实现方法。
- 知名框架AFN源码涉及IMP的代码
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration];
NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
Class currentClass = [localDataTask class];
while (class_getInstanceMethod(currentClass, @selector(resume))) {
Class superClass = [currentClass superclass];
IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
if (classResumeIMP != superclassResumeIMP &&
originalAFResumeIMP != classResumeIMP) {
[self swizzleResumeAndSuspendMethodForClass:currentClass];
}
currentClass = [currentClass superclass];
}
[localDataTask cancel];
[session finishTasksAndInvalidate];
- 等待补充
Autorelease的原理 ?
- ARC下面,我们使用
@autoreleasepool{}
来使用一个Autoreleasepool,实际上UIKit 通过RunLoopObserver 在RunLoop二次Sleep间Autoreleasepool进行Pop和Push,将这次Loop产生的autorelease对象释放 对编译器会编译大致如下:
void *DragonLiContext = objc_ AutoreleasepoolPush();
// {} 的 code
objc_ AutoreleasepoolPop(DragonLiContext);
- 释放时机: 当前RunLoop迭代结束时候释放.
大文件离线下载怎么处理?会遇到哪些问题?又如何解决
- NSURLSessionDataTask 大文件离线断点下载 (AFN等框架,旧的connection类已经废弃)
- 内存飙升问题:(apple 默认实现机制导致),在下载文件的过程中,系统会先把文件保存在内存中,等到文件下载完毕之后再写入到磁盘! 在下载文件时,
一边下载一边写入到磁盘
,减小内存使用 具体实现方法: 1.NSFileHandle
文件句柄 2.NSOutputStream
输出流
// code copy from jianshu
///: 1. NSFileHandle
-(void)URLSession:(NSURLSession *)session dataTask:(nonnull NSURLSessionDataTask *)dataTask
didReceiveResponse:(nonnull NSURLResponse *)response
completionHandler:(nonnull void (^)(NSURLSessionResponseDisposition))completionHandler {
//接受到响应的时候 告诉系统如何处理服务器返回的数据
completionHandler(NSURLSessionResponseAllow);
//得到请求文件的数据大小
self.totalLength = response.expectedContentLength;
//拼接文件的全路径
NSString *fileName = response.suggestedFilename;
NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *fullPath = [cachePath stringByAppendingPathComponent:fileName];
//【1】在沙盒中创建一个空的文件
[[NSFileManager defaultManager] createFileAtPath:fullPath contents:nil attributes:nil];
//【2】创建一个文件句柄指针指向该文件(默认指向文件开头)
self.handle = [NSFileHandle fileHandleForWritingAtPath:fullPath];
}
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
//【3】使用文件句柄指针来写数据(边写边移动)
[self.handle writeData:data];
//累加已经下载的文件数据大小
self.currentLength += data.length;
//计算文件的下载进度 = 已经下载的 / 文件的总大小
self.progressView.progress = 1.0 * self.currentLength / self.totalLength;
}
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
//【4】关闭文件句柄
[self.handle closeFile];
}
///:NSOutputStream
-(void)URLSession:(NSURLSession *)session dataTask:(nonnull NSURLSessionDataTask *)dataTask
didReceiveResponse:(nonnull NSURLResponse *)response
completionHandler:(nonnull void (^)(NSURLSessionResponseDisposition))completionHandler {
//接受到响应的时候 告诉系统如何处理服务器返回的数据
completionHandler(NSURLSessionResponseAllow);
//得到请求文件的数据大小
self.totalLength = response.expectedContentLength;
//拼接文件的全路径
NSString *fileName = response.suggestedFilename;
NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *fullPath = [cachePath stringByAppendingPathComponent:fileName];
//(1)创建输出流,并打开
self.outStream = [[NSOutputStream alloc] initToFileAtPath:fullPath append:YES];
[self.outStream open];
}
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
//(2)使用输出流写数据
[self.outStream write:data.bytes maxLength:data.length];
//累加已经下载的文件数据大小
self.currentLength += data.length;
//计算文件的下载进度 = 已经下载的 / 文件的总大小
self.progressView.progress = 1.0 * self.currentLength / self.totalLength;
}
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
//(3)关闭输出流
[self.outStream close];
}
-
开始(resume) 暂停(suspend) 取消( 恢复等
[self.dataTask cancel];
//默认情况下取消下载不能进行恢复,若要取消之后还可以恢复,可以清空下载任务,再新建
self.dataTask = nil;
-
下载进度值发生跳跃错乱 :已经下载的数据 / 文件的总数据,在第一个代理方法中,我们得到的文件大小并不是真正的文件大小,而是剩余未下载的大小,所以在第一次开始下载时,可以得到正确的数据,但是在下载过程中执行其他操作,就会使得到的数据大小发生变化,从而导致下载进度值出现问题
解决方案
:文件总大小 = 已经下载的数据 + 剩余未下载的数据
-
优化性能(句柄和流一样) 只有第一次接收到响应的时候才需要创建空的文件(lazy load )
-
等待补充