1、weak属性如何自动置nil的?runtime会对weak属性进行内存布局,构建hash表。以weak属性对象内存地址为key,weak属性值为value,当对象引用计数为0时,dealloc方法调用时,会将weak属性值自动置nil。2、hash表?...
1、weak属性如何自动置nil的?
runtime会对weak属性进行内存布局,构建hash表。以weak属性对象内存地址为key,weak属性值为value,当对象引用计数为0时,dealloc方法调用时,会将weak属性值自动置nil。
2、hash表?
哈希表是一种根据关键码去寻找值的数据映射结构。f(key)找到要找的值,key就是关键码,f()是哈希函数。
3、遇到的问题1
画面上有UIwebview,他的delegate是控制器,在webview加载完成后需要做某些事情,比如加载某些js方法。
如果在webview载入完成之前,将控制器关闭,控制器就会被释放掉。但是由于webview正在载入页面而不会被立马释放掉,等到页面加载完毕,回调delegate里面的方法,由于此时控制器已经释放掉了,所以会崩溃。
解决办法是在dealloc中把webview的delegate释放掉。
-(void)dealloc
{
self.webview.delegate = nil;
}
4、Block的循环应用、内部修改外部变量、三种block
block强引用self,self强引用block。
block不允许修改外部变量的值,这里的外部变量指的是栈中指针的内存地址。__block的作用是只要观察到变量被block使用,就将外部变量在栈中的内存地址存放到堆中。block捕获的外部变量可以改变值的是静态变量、静态全局变量、全局变量。
栈block,只用到外部局部变量、成员属性变量,没有强指针引用,由系统控制生命周期,一旦返回之后,就会被系统销毁,是不持有对象的;
堆block,有强指针引用或copy修饰的成员属性引用的block会被复制到堆中成为堆block;
全局block,只用到全局变量、静态变量,生命周期从创建到应用程序结束,也不持有对象。
5、全局变量和局部变量在内存中是否有区别?如果有,是什么区别?
有区别,全局变量(和静态变量)保存在内存的全局存储区中,占用静态的存储单元;局部变量保存在栈中,只有在所在函数被调用时才动态的为变量分配存储单元。
6、KVO底层实现原理?手动触发KVO?
当观察一个对象时,runtime会动态创建继承自该对象的类,并重写被观察对象的setter方法,重写的setter方法会负责在调用原setter方法前后通知所有观察对象值的更改,最后会把该对象的isa指针指向这个创建的子类,对象就变成子类的实例;
在setter方法中,手动实现NSObject两个方法:willChangeValueForKey、didChangeValueForKey;
7、category为什么不能添加属性?怎么实现添加?与Extension的区别?category覆盖原类方法?多个category调用顺序?
runtime初始化时category的内存布局已经确定,没有ivar,所以默认不能添加属性。
使用runtime的关联对象(objc_getAssociatedObject),并重写setter和getter方法。
#import "UIButton+ClickBlock.h"
#import <objc/runtime.h>
static const void *associatedKey = "associatedKey";
@implementation UIButton (ClickBlock)
-(void)setClick:(ClickBlock)click
{
objc_setAssociatedObject(self, associatedKey, click, OBJC_ASSOCIATION_COPY_NONATOMIC);
[self removeTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
if (click)
{
[self addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];
}
}
-(ClickBlock)click
{
return objc_getAssociatedObject(self, associatedKey);
}
-(void)buttonClick
{
if (self.click)
{
self.click();
}
}
@end
Extension编译期创建,可以添加成员变量。必须要有类的源码才可以添加。
category方法会在runtime初始化的时候copy到原来方法的前面,调用分类的方法的时候直接返回,不再调用原类的方法。
多个category的调用顺序将按照编译的顺序来。
8、load方法和initialize方法的异同。主要说一下执行时间,各自用途,没实现子类的方法会不会调用父类的?
load方法,如果类自身没有定义,并不会调用其父类的load方法。initialize方法,如果类自身没有定义,就会调用父类的initialize方法。
load只要类所在的文件被引用,就会执行。initialize方法在类或者其子类的第一个方法被调用之前才会执行,load方法不视为类的第一个方法。
方法都只会执行一次。load方法执行顺序:父类>子类>,类方法>类别中方法。initialize只会执行最近的一次调用。
load的一般应用场景:1、hook方法的时候;2、涉及到组件化开发中不同组件间通信,在load中注册相关协议等。
initialize方法主要用来对一些不方便在编译期初始化的对象进行赋值。比如NSMutableArray这种类型的实例化依赖于runtime的消息发送,所以显然无法在编译期初始化。
9、iOS中的hook方法
改变程序运行流程的一种技术。
1、Method Swizzle,利用OC的Runtime特性,动态改变SEL(方法编号)和IMP(方法实现)的对应关系,达到OC方法调用流程改变的目的。
2、fishhook。
3、Cydia Substrate。
10、对runtime的理解。
每个实例对象都有一个isa指针,当调用方法的时候,runtime库会在类的方法列表及父类的方法列表中去寻找与消息对应的selector指向的方法,找到后运行这个方法。
调用类方法时,会在这个类的meta-class的方法列表中查找。
消息发送步骤:
1、检测这个selector是不是要忽略的;
2、检测target是不是nil,是nil会被忽略掉;
3、根据SEL去cache里面去找IMP,找到就执行,找不到去类的方法列表中去找,还找不到就去父类中找,直到NSObject类为止;
4、如果还找不到就要开始进入动态方法解析。
@dynamic表示我们会为属性动态提供存取方法。动态方法解析就是,runtime会调用resolveInstanceMethod或resolveClassMethod来给程序员一次动态添加方法实现的机会。
- (void)myInstanceMethod:(NSString *)string
{
NSLog(@"myInstanceMethod = %@", string);
}
+(BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(resolveThisMethodDynamically))
{
class_addMethod([self class], sel, class_getMethodImplementation([self class], @selector(myInstanceMethod:)), "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
resolveInstanceMethod(YES完成NO继续)、forwardingTargetForSelector(备用的接收者)、forwardInvocation(消息转发)。
11、autoreleasepool的原理和使用场景?
AutoreleasePoolPage以双向链表组合构成栈结构,实现autoreleasepool。
runloop和线程是一一对应的,对应的方式是以key-value的方式保存在一个全局字典中。子线程的runloop会在第一次获取的时候创建,如果不获取的话就一直不会创建。runloop会在线程销毁时销毁。
iOS应用启动后会注册两个Observer管理和维护autoreleasepool。(_wrapRunLoopWithAutoreleasePoolHandler)。第一个监听runloop进入,调用objc_autoreleasePoolPush()向当前的AutoreleasePoolPage增加一个哨兵对象标志创建自动释放池。这个observer是优先级最高的。
第二个Observer会监听RunLoop的进入休眠和即将退出RunLoop。在即将休眠时调用objc_autoreleasePoolPop()和objc_autoreleasePoolPush()根据情况从最新加入的对象一直往前清理直到遇到哨兵对象并创建新的池子。在即将退出RunLoop时会调用objc_autoreleasePoolPop()释放自动释放池内对象。优先级最低,确保发生在所有回调操作之后。
在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些都是在主线程的autoreleasepool包裹下的。
有两种情况需要手动创建自动释放池:
1、在子线程中,使用便利构造器创建的对象,这些对象是autorelease的,需要自动释放池才能释放。
2、一段代码里面,大量使用便利构造器创建的对象,需要手动添加自动释放池。
12、iOS中使用的锁、死锁的发生与避免
@synchronized、信号量、NSLock、NSRecursiveLock是递归锁、NSConditionLock条件锁、NSCondition可以对线程进行挂起和唤醒、dispatch_semaphore、pthread_mutex和pthread_mutext(recursive)、OSSpinLock、os_unfair_lock。
死锁:就是有多个线程都卡住了,多个线程在等待对方完成后执行,结果都执行不了了。
GCD使用异步线程、并行队列。
13、NSOperation和GCD的区别
GCD使用C语言编写高效,NSOperation是对GCD的面向对象的封装。对于特殊需求,如取消任务、设置任务优先级、任务状态监听,NSOperation使用起来更加方便。
可以设置依赖关系,GCD只能通过dispatch_barrier_async实现。
可以通过KVO观察当前operation执行状态(执行或取消)。
NSOperation可以设置自身优先级,GCD只能设置队列的优先级,不能对block设置优先级。
NSOperation可以自定义如NSInvationOperation/NSBlockOperation。
GCD高效,NSOperation开销相对高。
14、OC与JS交互
15、iOS crash防护
1、unrecognized selector,方法在类和父类中都找不到后,会走消息转发机制。
resolveInstanceMethod、forwardingTargetForSelector、forwardInvocation。
我们就在forwardingTargetForSelector里面做处理。因为resolveInstanceMethod需要在类本身动态添加不存在的方法。forwardInvocation开销较大,而且forwardInvocation经常被使用者调用,不适合多次重写。forwardingTargetForSelector将消息转发给一个对象,开销小,被重写的概率低。
创建一个继承自NSObject的类,实现resolveInstanceMethod,在里面动态的添加以转发消息为SEL的IMP,在里面打印,并且返回0。在frowardingTargetForSelector中返回该类的实例。
2、KVO crash,导致crash的两种情形:被观察者dealloc时仍然注册着KVO;KVO注册观察者与移除观察者不匹配。被观察对象的KVO关系图混乱导致。
可以用代理模式来管理被观察者的KVO关系图。在dealloc的时候,可以通过方法交换,将其对应的KVODelegate所有和KVO相关的数据清空,然后将KVODelegate置空。
3、NSNotification crash,当一个对象添加了notification之后,如果dealloc的时候,仍然持有notification,就会出现NSNotification类型的crash。
苹果在iOS9之后专门针对这种情况做了处理,开发者没有移除也不会产生crash了。针对iOS9之前的用户,需要做防护。
hook NSNotificationCenter的添加观察者的方法,在观察者身上添加标记。交换观察者原有的dealloc函数,在对象真正dealloc之前判断是否有标记,如果有的话先移除观察者再dealloc。
4、NSTimer crash,有两个问题:一是NSTimer会强引用target实例,target强引用timer,造成循环引用。二是NSTimer无限重复执行一个任务,导致target的selector一直被重复调用且处于无效状态。
定义一个抽象类(继承自NSProxy),抽象类中弱引用target。创建NSTimer的category,交换系统方法,实现NSTimer强引用抽象类。
5、container 类型crash,指的是容器类的crash,数组、字典、NSCache。
在一些常用的会导致崩溃的API中进行方法交换,然后在新的方法中加入一些条件限制和判断,从而让API变得安全。
6、NSString类型crash防护,在一些常用的会导致崩溃的API中进行方法交换,然后在新的方法中加入一些条件限制和判断,从而让API变的安全。
7、野指针crash防护,hook NSObject的dealloc方法,运行时动态生成新类,用_zoombie_做前缀拼接原始类名,将僵尸对象的isa指针指向_zoombile_新类,给新类添加forwardingTargetForSelector方法,在该方法中去掉类名的前缀,打印出来哪个类调用了哪个方法,终止程序。
16、架构与设计模式
1、MVC设计模式介绍
2、MVVM介绍、MVC与MVVM的区别
MVVM是在MVC的基础上演化而来,MVVM想要解决的问题是尽可能地减少Controller的任务。
View(ViewController)不再是UIView的子类,而变成了UIViewController的子类,不再负责数据的请求以及处理逻辑,因此不再臃肿。
ViewModel代替了MVC中的Controller成为了协调者的角色,ViewModel被View(ViewController)持有,同时持有Model。数据请求以及处理逻辑都放在ViewModel中。
如果Controller的代码量很多,并且View的计算很复杂,维护成本很高,改动一个小点还可能会导致蝴蝶效应,测试也要回归当前页面所有的用例。
3、ReactiveCocoa的热信号与冷信号
冷信号:只有有订阅者的时候才会发送信号,一对一,如果有其他的订阅者订阅会重新完整的发送信号,给订阅者发送消息则一定会收到。
热信号:不在乎是否有订阅者,一对多,如果发送当时有订阅者,就会同时接收到信号,如果没有就不会接收到消息,给订阅者发送消息不一定会收到。
4、缓存架构设计LRU方案(最久最少使用的数据删除)
缓存是本地数据存储,存储方式主要包含两种:内存存储和磁盘存储。
磁盘存储方式主要有文件管理和数据库,读取慢、空间大、可持久。
在程序中声明的容器(数组、字典)都可看作内存中的存储,读取快、空间小、不可持久。
iOS主要提供四种磁盘存储方式:
NSKeyedArchiver,归档,需要遵守NSCoding协议,并提供encode和initWithCoder方法。只能一次性归档保存以及一次性解压,针对小量数据。
NSUserDefaults,用来保存应用程序设置和属性、用户保存的数据。
Write写入方式,写入到文件中。
SQLite。
磁盘保守测量低于内存读取几十倍。
先去找内存中的资源,如果没有去找磁盘中的,找到后存储到内存中,再从内存中读取内容。
内存优化,提高内存命中率:
用双向链表实现LRU,每个数据是这个串上的一个节点,经常访问的数据移动到头部,等数据超出容量之后从链表后面的一些节点销毁。
磁盘优化,数据分类存储:
数据比较大的时候使用文件存储,数据较小的时候使用sqlite,一般在20KB左右。
5、SDWebImage源码,如何实现解码
UIImage有两种加载图片方法,imageNamed:和imageWithContentsOfFile:。
imageNamed方法的特点在于可以缓存已经加载的图片,使用时,先根据文件名在系统缓存中寻找图片,如果没有,从Bundle中找到该文件,在渲染到屏幕的时候才解码图片,并将解码结果保留到缓存中,当收到内存警告时,缓存会被清空。当频繁加载同一张图片时,使用imageNamed效果比较好,imageWithContentsOfFile仅加载图片,不缓存图像数据。
imageNamed第一次加载图片时,只在渲染的时候才在主线程解码,性能并不高效。
图像分为矢量图和位图,显示到屏幕中的图像是位图图像。我们经常使用的图片是JPG或PNG格式的图片,他们是经过编码压缩后的图片格式,在显示到屏幕之前,需要解码成位图图像。解码比较耗时,不能使用GPU硬解码,只能通过CPU软解码实现。
//位图大小的计算公式,其中bytesPerPixel = 48
bitmap_size = imageSize.width * imageSize.height * bytesPerPixel;
将耗时的解码工作放在子线程中实现。
显示之前还有图片重采样,图片放大和缩小都会引
6、AFNetWorking源码分析
17、iOS事件传递机制
UIResponder
事件分发机制:
用户点击屏幕产生事件,UIApplication事件分发,UIWindow,hitTest:withEvent:,pointInside:withEvent:,subviews(在里面的UIView上走同样的point范围校验,如果在某一个UIView中返回了YES,说明触摸点在这个UIView的范围内,则遍历它的子视图,看触摸点最终落在哪个子视图上,如果返回NO,说明触摸点不在这个UIView范围内,那么就遍历跟他同级的下一个UIView)
找到合适的view后进行处理,如果这个view不进行处理,就按照分发顺序回传。
视图响应:是和事件传递过程相反的。
18、离屏渲染
油画算法:计算机图层的叠加绘制大概遵循油画算法,会按照层级绘制,先绘制距离较远的场景,再用绘制距离较近的场景覆盖较远的部分,但是无法修改前面的图层。
离屏渲染:油画算法无法满足我们的需求,我们可以再另开辟一个空间,用于临时渲染,这个临时渲染就是离屏渲染。开辟临时缓存空间,上下文切换,内存拷贝,额外的渲染。
离屏渲染是GPU无法按油画算法一次性渲染完我们的视图才会触发。
优化圆角问题:尽量避免使用裁切(masksToBounds),如果要用到可以放到子view中,离屏渲染需要的空间就会变小。提前切好需要的圆角,避免渲染的时候再切。
19、iOS常见三种加密
哈希hash:
1、md5加密,不可逆运算,定长32位字符,相同数据加密结果一致,不同数据结果不一致。可以用作一致性验证。是不安全的。加盐,一串比较复杂的字符串。
2、SHA加密,
3、HMAC加密,
对称加密:
非对称加密RSA:公钥和私钥,HTTPS,
本文标题为:iOS面试
- 详解flutter engine 那些没被释放的东西 2022-12-04
- SurfaceView播放视频发送弹幕并实现滚动歌词 2023-01-02
- Android实现轮询的三种方式 2023-02-17
- 作为iOS开发,这道面试题你能答出来,说明你基础很OK! 2023-09-14
- Android实现监听音量的变化 2023-03-30
- Flutter实现底部和顶部导航栏 2022-08-31
- 最好用的ios数据恢复软件:PhoneRescue for Mac 2023-09-14
- Android studio实现动态背景页面 2023-05-23
- Android MaterialButton使用实例详解(告别shape、selector) 2023-06-16
- iOS 对当前webView进行截屏的方法 2023-03-01