面试题总结
xib 用weak修饰空间如果换成strong,removefromSuperView后不被释放,浪费内存
viewController中有一个强引用的view,view中有个强引用的数组的subviews,当xib新增一个控件时,会把控件以强引用的方式加入到subviews中;
所以,从xib添加一个控件的属性时,用weak 也不会释放掉,因为有其他的地方强引用这个控件。自定义的控件不能使用weak,在分配完内存后就变成了nil
TableView 复用机制:
刚开始会创建一个屏幕的cell,上划时,多创建三个,其他的cell复用已经创建的cell
网络请求,界面消失怎么取消请求
在UIViewController的分类中交换viewWillDisappear方法,添加NSURLSessionDataTask的属性task
在viewWillDisappear方法中调用task 的cancel方法
#import "UIViewController+Category.h"#import <objc/runtime.h>static void *dataTaskKey;@implementation UIViewController (Category)+ (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ FSSwizzleMethod(, @selector(viewWillDisappear:), @selector(fs_viewWillDisappear:)); });}- (void)fs_viewWillDisappear:(BOOL)animated { ; ;}- (NSURLSessionDataTask *)dataTask { return objc_getAssociatedObject(self, &dataTaskKey);}- (void)setDataTask:(NSURLSessionDataTask *)dataTask { objc_setAssociatedObject(self, &dataTaskKey, dataTask, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}void FSSwizzleMethod(Class cls, SEL originalSelector, SEL swizzledSelector) { Method originalMethod = class_getInstanceMethod(cls, originalSelector); Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector); BOOL didAddMethod = class_addMethod(cls, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(cls, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); }}@endcopy 修饰string,array,dictionary ; strong修饰mutableString,mutableArray, mutableDictionary
1)如果用strong修饰string,发生浅拷贝, 对mutableString 赋值给string, mutableString修改值会互相影响,出现脏数据; copy修饰string,mutableString复制给string,会开辟新的内存,将内容copy到内存中,发生深拷贝,互不影响
2)用copy修饰mutableString, 初始化mutableString是不可变类型,对它赋值会copy一份不可变的空间,对mutableString appending修改会崩溃,用strong不会,属性的类型是在运行时决定的,并不是@property申明什么类型就是什么类型
3)copy修饰词起作用是在setter方法中发生的,
copy,strong详解
@property (nonatomic, strong) NSString *str_strong;@property (nonatomic, copy) NSString *str_copy;@property (nonatomic, copy) NSMutableString *mutableStr_copy;@property (nonatomic, strong) NSMutableString *mutableStr_strong; NSMutableString *mutableStr1 = [@"222" mutableCopy]; self.str_strong = mutableStr1; self.str_copy = mutableStr1; ; NSLog(@"----str_copy====%@\n -----str_strong====%@",self.str_copy, self.str_strong);// 2022-11-18 10:00:35.256711+0800 OCProject ----str_copy====222// -----str_strong====222111 NSString *str1 = @"222"; self.str_strong = str1; // 和str1同地址 self.str_copy = str1;// 和str1同地址 str1 = @"1111"; // 重新分配一个空间// str_copy====222// -----str_strong====222 NSString *str1 = @"222"; self.mutableStr_copy = str1; self.mutableStr_strong = str1; ; // 崩溃self.mutableStr_copy不可变 ; // 崩溃self.mutableStr_strong不可变 self.mutableStr_copy = mutableStr1; self.mutableStr_strong =mutableStr1 ; // @"222bbb" 和mutableStr1同地址,任意一处改动其他也跟踪改动 ; // self.mutableStr_copy不可变,崩溃 self.mutableStr_copy = [@"222" copy]; self.mutableStr_strong = [@"222" copy]; ; // 崩溃self.mutableStr_copy不可变 ;// 崩溃self.mutableStr_strong不可 _str_strong = str1; _str_copy = str1; _str_strong = mutableStr1; _str_copy = mutableStr1; _mutableStr_strong = str1; _mutableStr_copy = str1; ; //崩溃 ;// 崩溃 _str_strong = mutableStr1; _str_copy = mutableStr1; _mutableStr_strong = mutableStr1; _mutableStr_copy = mutableStr1; ; ;// _str_strong、_str_copy、mutableStr、_mutableStr_copy、_mutableStr_strong都是 10x00006000006a6a30 @"222aaaaaa" 同地址,copy是发生在set方法中的,_下划线取值不调用set,get方法appdelegate过大怎么处理
1、使用Notification监听各种场景
2、使用ModuleManager,在ModuleManager实现遵守UIAppdelegate方法,转发到实现代理方法的各个模块
6、组件化
7、长连接
https://www.jianshu.com/writer#/notebooks/53203941/notes/107600812
拆包:一个head对应对一个body,head中存放body的数据长度
解析:protobuf
重连机制:
心跳包:
8、计时器的种类
NSTimer、CADisplayLink、dispatch_source_t
NSTimer
1、存在延迟
不管是一次性的还是周期性的timer的实际触发事件的时间,都会与所加入的RunLoop和RunLoop Mode有关,如果此RunLoop正在执行一个连续性的运算,timer就会被延时出发。重复性的timer遇到这种情况,如果延迟超过了一个周期,则会在延时结束后立刻执行,并按照之前指定的周期继续执行。
不适用是要置为nil,防止循环引用
dispatch_source_t
1、时间准确,需要将dispatch_source_t timer设置为成员变量,不然会立即释放
CADisplayLink
_displaylinkTimer = ; forMode:NSDefaultRunLoopMode];
屏幕刷新时调用CADisplayLink是一个能让我们以和屏幕刷新率同步的频率将特定的内容画到屏幕上的定时器类。CADisplayLink以特定模式注册到runloop后,每当屏幕显示内容刷新结束的时候,runloop就会向CADisplayLink指定的target发送一次指定的selector消息, CADisplayLink类对应的selector就会被调用一次。所以通常情况下,按照iOS设备屏幕的刷新率60次/秒
iOS设备的屏幕刷新频率是固定的,CADisplayLink在正常情况下会在每次刷新结束都被调用,精确度相当高。但如果调用的方法比较耗时,超过了屏幕刷新周期,就会导致跳过若干次回调调用机会。如果CPU过于繁忙,无法保证屏幕60次/秒的刷新率,就会导致跳过若干次调用回调方法的机会,跳过次数取决CPU的忙碌程度。
但是新设备屏幕刷新率是动态的,不再是固定的60fps
get post区别
https://www.cnblogs.com/wayaoyao/p/10960379.html
1、get请求参数再url中,长度限制2KB,可见,post在body中,不限制、不可见
2、get是请求服务器数据,post是发送数据到服务器,会修改服务器数据
3、get只支持ASCII字符,post没有字符类型限制
4、post的缺点:速度比get传输慢,get的效率更高
OC中使用字符串枚举使用:NS_STRING_ENUM
.h 文件中 -------------typedef NSString *KLTypeStr NS_STRING_ENUM;FOUNDATION_EXPORT KLTypeStr const KLTypeStringRed;FOUNDATION_EXPORT KLTypeStr const KLTypeStringGreen;FOUNDATION_EXPORT KLTypeStr const KLTypeStringOrange;.m 文件中 --------------NSString * const KLTypeStringRed = @"红色";NSString * const KLTypeStringGreen = @"绿色";NSString * const KLTypeStringOrange = @"橘色"; webSocket降频,重连机制
降频:在buffer中每次读取成功后,接着下一次读取
重连机制:
重连的时机:1、心跳包接收超时2、网络环境变化,当网络变化连接并不是立即的就快不可以用,所以切换网络后立即发送一个心跳包测试连接是否可用
如果安装2n次方的时间间隔重连,重连5次后仍然失败就放弃重连
登录后才能跳转一个界面实现
1、在UIViewController分类中添加block
//.htypedef BOOL(^NavigationControllerShouldForbiddenPushIntoCurrentVC)(UINavigationController *naviVC);@property (nonatomic, copy) NavigationControllerShouldForbiddenPushIntoCurrentVC navigationControllerShouldForbiddenPushIntoCurrentVC;//.m 在分类中添加属性YYSYNTH_DYNAMIC_PROPERTY_OBJECT(navigationControllerShouldForbiddenPushIntoCurrentVC, setNavigationControllerShouldForbiddenPushIntoCurrentVC, RETAIN_NONATOMIC, NavigationControllerShouldForbiddenPushIntoCurrentVC)
自定义UINavigationController子类:FSNavigationController.m
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated { if (viewController.navigationControllerShouldForbiddenPushIntoCurrentVC) { shouldForbidden = viewController.navigationControllerShouldForbiddenPushIntoCurrentVC(self); }shouldForbidden = true 就不跳转
使用的UIViewController种初始化方法实现block,并返回是禁止跳转,当禁止跳转时在block中执行登录方法,在登录成功的回到中push self
- (instancetype)init{ self = ; if (self) { @weakify(self); self.navigationControllerShouldForbiddenPushIntoCurrentVC = ^BOOL(UINavigationController *naviVC) { @strongify(self); BOOL shouldForbidden = !FSUser.user.information.isLogin; if (shouldForbidden) { ; }]; } return shouldForbidden; }; } return self;}避免button重复点击
使用runtime 解决按钮重复点击的问题
创建button的分类,在分类中交换- (void)sendAction:(SEL)action to:(nullable id)target forEvent:(nullable UIEvent *)event;
在自己实现的sendaction方法中判断是否要执行sendaction方法,如果执行,disable参数设置为YES,通过;调用disable的set方法,隔一段时间设置为NO,
WKWebView缓存
(https://juejin.cn/post/6844904153810993165#heading-4)
同一个请求连续发送两个,后发送的先到,获取的事先发送的数据,
1、 按钮防止重复点击,0.5s的间隔
2、只允许最新的请求发送,老的请求都取消
:后发送的请求和未返回的请求重复时,就取消之前的request
优点:多次请求,保证了最新的请求发出,用户拿到的一定是最新的数据,类似防抖。
缺点:
同时可能后台已经处理请求到一半了,然后请求被取消了,马上又进来一个请求,可能会造成效率不会太高。
3、只允许最老的请求发送,新的请求都取消
取消后来重复的请求,前面的请求没有返回,都不让发送,有多个重复请求,前端确保只发送一个。
优点:多次请求,保证了最老的请求发出,服务器压力小。
缺点:用户拿到的可能不是最新的数据。
4、允许多个请求发送,有一个请求成功,取消其他还在处理的请求
优点:能保证请求至少返回一个,尤其是当单个失败的可能性比较大,允许发出多个,那么用户频繁操作,成功的概率就更大。
缺点:
①. 用户拿到的数据不能保证是最新的。
②. 能否成功取消,由网络速度和这这部分代码执行速度决定,网络比代码快,比如网络 10 毫秒内返回,可能无法请求,原因是第一个网络成功了,第二个请求还没发出,即网络返回了,判断重复的函数还没执行。
当 url、method、参数一样,就是相同请求。
同一个类多个category同时交换一个方法,执行顺序如何?(包括交换后方法同名,交换后方法不同名)
例如有两个分类
结论:
1.如果交换后方法同名,最后只运行类中的方法,两次交换还原了SEL和IMP
2.如果交换后方法不同名,会倒叙执行文件的方法,如上:先执行2->1->宿主类
方法执行的顺序:后编译的先执行
APP启动时代理的执行方法
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: ? = nil) -> Bool { print("第一步"); return true }func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: ?) -> Bool { // Override point for customization after application launch. print("第二步"); return true }// 先WillEnterForeground 后DidBecomeActive func applicationWillEnterForeground(_ application: UIApplication) { print("第三步"); } func applicationDidBecomeActive(_ application: UIApplication) { print("第四步"); } // 先WillResignActive 后DidEnterBackground func applicationWillResignActive(_ application: UIApplication) { print("11111111"); } func applicationDidEnterBackground(_ application: UIApplication) { print("11111111"); } func applicationWillTerminate(_ application: UIApplication) { print("11111111"); }多个网络请求,UI只刷新一次
https://www.jianshu.com/writer#/notebooks/53203941/notes/107684334
利用dispatch_group,dispatch_group_enter和dispatch_group_leave控制group中任务的数量,当group任务都执行完毕时,执行dispatch_group_notify的任务
在dispatch_group_notify中在主线程中执行UI刷新
弱网环境长连接丢包
1、弱网优化:DNS query 是不用默认的,自己搭建httpDNS,查询IP,缓存IP,避免DNS劫持和故障
2、TCP,TLS预先建立连接和缓存,
3、检测弱网环境,使用QUIC协议
2、丢包重传:模仿TCP重传机制,设计应答机制,未收到确认就丢包了,重传,连续重传失败就等网络环境好了再尝试
数据持久化方案
https://zhuanlan.zhihu.com/p/468264281
kvo使用场景以及 影响上架
监听属性的变化
监听不存在的key会崩溃
H5登录自动登录状态
NSString *scriptCookie = ; WKUserContentController *userContentController = WKUserContentController.new; initWithSource:scriptCookie injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO]];webViewConfig.userContentController = userContentController; NSMutableURLRequest *request = ; request.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData; //JS注入的Cookie,比如PHP代码在Cookie容器中取是取不到的所以cookie种两次 .information.token ?: @""] forHTTPHeaderField:@"Cookie"];oom解决方案
头条抖音如何实现OOM崩溃率下降50%+
百度APP iOS端内存优化实践-内存管控方案
卡顿监控
1、子线程监控主线程runloop周期的时间
2、ping主线程,是否有回应
3、监控FPS
nonatomic线程安全问题
复现场景
@property (nonatomic, copy) NSString *testStr; dispatch_queue_t queue = dispatch_queue_create("11111", DISPATCH_QUEUE_CONCURRENT); for (int i = 0; i < 100000; i++) { dispatch_async(queue, ^{ self.testStr = ; NSLog(@"---%@",self.testStr); }); }
image.png
问题分析https://www.jianshu.com/p/a39b8c8ed3b1
若在MRC 下,属性name的set方法如下:-(void)setName:(NSString *)name{ if (_name != name) { ; ; _name = name; }}一个线程release时,其他线程已经把name,release了
点击home键做了什么http://www.manongjc.com/detail/53-bdwmccdgyqbaeio.html
如何深拷贝一个数组,数组里面是对象
viewDidLoad的调用时机
view加载到内存时
(https://blog.csdn.net/chabuduoxs/article/details/120333278)
loadView->viewDidLoad-> viewWillAppear -> viewWillLayoutSubviews -> viewDidLayoutSubviews -> viewDidAppear -> viewWillDisappear -> viewDidDisappear-> dealloc->
view的生命周期
init(frame: CGRect) ->willMove(toSuperview ->didMoveToSuperview ->willMove(toWindow -> didMoveToWindow-> layoutSubviews-> draw(_ rect
执行remove操作 ->willMove(toSuperview->willMove(toWindow->didMoveToWindow->didMoveToSuperview
->dealloc
如何监测启动时间
linkmap文件的作用
runloop防崩,设置的时间是用来干啥
vc的生命周期
如何设计一个日志系统
死锁的产生
1、互斥条件:同一份资源不能被多个线程同时持有,只能等持有的线程释放后,才能被其他线程访问
2、持有并等待:当线程A持有资源A后,等待访问资源B时,持有的资源A不会被释放
3、不可剥夺:线程A为释放资源A前,其他线程只能等待资源A被释放,不能剥夺线程A持有的资源
4、环路等待:线程A持有资源A并等待访问资源B,线程B持有资源B并等待资源A,出现了环路等待
打破这四个条件中的一个,即可避免死锁,一般破坏环路等待条件
断点续传
断点续传主要依赖于 HTTP 头部定义的 Range 来完成。有了 Range,应用可以通过 HTTP 请求获取失败的资源,从而来恢复下载该资源。当然并不是所有的服务器都支持 Range,但大多数服务器是可以的。Range 是以字节计算的,请求的时候不必给出结尾字节数,因为请求方并不一定知道资源的大小。
断点续传原理浅析
提升网络请求速率
国外网络被墙的原理
DNS污染:有意或无意进行的域名服务器分组,将域名指向错误的IP地址。
(http://www.jsxyy.com.cn/voddetail/199409.html)
VPN:(Virtual Private Network),学名虚拟专用网络
通过数据加密技术封装出来的一条虚拟数据通信隧道,实际上它借用的还是互联网上的公共链路。VPN会对你和公司之间传递的数据进行加密处理,加密后的数据会在一条专用的数据链路上进行安全传输,如同架设了一个专用网络一样。所以VPN称为虚拟专用网络。
当开启VPN后,你访问公司内网的办公网站时,不再直接访问公司内网的服务器,而是去访问VPN服务器,并给VPN服务器发一条指令“我要访问办公网站”。VPN服务器接到指令后,代替你去访问办公网站,收到公司办公网站的内容后,再通过“秘密隧道”将内容回传给你,这样你就通过VPN成功访问到你需要的内网资源啦
image.png
字典的底层数据结构
NSDictionary(字典)是使用hash表来实现key和value之间的映射和存储的
image.png
dictionary内部使用了两个指针数组分别来保存keys和value,已知的是dictionary采用的是连续存储的方式存储键值对,key哈希出来的数组下标地址,同样这个地址对应到values数组的下标,就是匹配到的值。因此keys和values这两个数组的长度一致才能保证匹配到数据。内部结构还有个_capacity表示当前通列表的扩充阀域 ,当count数量达到这个长度就扩容
NSDictionary设置的key和value,key值会根据特定的hash函数算出建立的空桶数组,keys和values同样多,然后存储数据的时候,根据hash函数算出来的值,找到对应的index下标,如果下标已有数据,开放定址法后移动插入,如果空桶数组到达数据阀值,这个时候就会把空桶数组扩容,然后重新哈希插入。这样把一些不连续的key-value值插入到了能建立起关系的hash表中,当我们查找的时候,key根据哈希值算出来index,直接index访问hash表keys和hash表values,这样查询速度就可以和连续线性存储的数据一样接近O(1)了,只是占用空间有点大,性能就很强悍。
crash的抓取
1、Xcode->windows->Devices and simulators->选择对应的手机,刷新view device logs ->找到对应时间的crash日志导出到本地
image.png
image.png
2、手机设置->隐私->分析与改进->分析数据
3、异常捕获和日志上传
NSMutableArray的数据结构
循环列表,从头或者尾做删除,添加的操作,消耗较小,有offset 偏移量来计算真实的下标,避免了从头或者尾部删除、添加操作造成的数组元素重新排列移动
NSMutableArray详解
image.png
循环中异步赋值
例子
tagged pointer没有release和retain操作 不会崩溃,数据大时会崩溃
base64
base64编码后会变大:转换到ASCII码后,会讲6位 一个单位,前面两位补0,作为1个字节
https://blog.csdn.net/JackieDYH/article/details/122558936
为什么使用base64编码:二进制不兼容。某些二进制值,在一些硬件上,比如在不同的路由器,老电脑上,表示的意义不一样,做的处理也不一样。同样,一些老的软件,网络协议也有类似的问题。但是万幸,Base64使用的64个字符,经ASCII/UTF-8编码后在大多数机器,软件上的行为是一样的。
页:
[1]