本篇文章是承接上篇文章 iOS 进阶+面试(二)
二十一、可变数组与不可变数组用什么修饰:原因?
地址https://www.jianshu.com/p/27305b08b0f2
二十二、nsstring 使用什么关键字修饰?
地址https://www.jianshu.com/p/9b77d61d76fe
二十三、线上项目崩溃,日志处理?
友盟, 集成需要在
-(void)viewWillAppear:(BOOL)animated
-(void)viewWillDisappear:(BOOL)animated
详解 : 用友盟详细说明一下
- 报表中心中下载错误
- 将 友盟Crash分析工具与下载的错误报表放同一文件夹中, 打开 终端 , 先拖入友盟Crash分析工具** 再拖入 错误报表 , 按回车
- 进入友盟 个人中心 -> 错误分析 -> 错误列表 -> 点击列表中错误进入界面
2. 点击 右上角 进入 [报表中心] 下载该错误. 将 友盟Crash分析工具与下载的错误报表放同一文件夹中, 打开 终端 , 先拖入友盟Crash分析工具 再拖入 错误报表 , 按回车.
- 终端 运行完成后 , 会显示错误的位置与行数
二十四、谈谈多线程的理解?
- 使用线程可以把程序中占据时间长的任务放到后台去处理,如图片、视频的下载
- 充分利用系统的多核 发挥多核处理器的优势,并发执行让系统运行的更快、更流畅,用户体验更好
缺点:
更多的线程需要更多的内存空间
当多个线程对同一个资源出现争夺的时候要注意线程安全的问题。
二十五、iPhone x max这个你们是怎样适配的?
iOS11之前导航栏默认高度为64pt(这里高度指statusBar + NavigationBar),iOS11之后如果设置了prefersLargeTitles = YES则为96pt,默认情况下还是64pt,但在iPhoneX上由于刘海的出现statusBar由以前的20pt变成了44pt,所以iPhoneX上高度变为88pt,如果项目里隐藏了导航栏加了自定义按钮之类的,这里需要注意适配一下。
之前是按照定义宏变量适配的,这个感觉每次新出手机都要重新适配新手机,iOS11 新出安全区域的概念,这是我们可以根据安全区域来适配,
问题:
- 导航栏图层及对titleView布局的影响
iOS11之前导航栏的title是添加在UINavigationItemView上面,而navigationBarButton则直接添加在UINavigationBar上面,如果设置了titleView,则titleView也是直接添加在UINavigationBar上面。iOS11之后,大概因为largeTitle的原因,视图层级发生了变化,如果没有给titleView赋值,则titleView会直接添加在_UINavigationBarContentView上面,如果赋值了titleView,则会把titleView添加在_UITAMICAdaptorView上,而navigationBarButton被加在了_UIButtonBarStackView上,然后他们都被加在了_UINavigationBarContentView上,如图:
所以如果你的项目是自定义的navigationBar,那么在iOS11上运行就可能出现布局错乱的bug,解决办法是重写UINavigationBar的layoutSubviews方法,调整布局,上代码:
- (void)layoutSubviews {
[super layoutSubviews];
//注意导航栏及状态栏高度适配
self.frame = CGRectMake(0, 0, CGRectGetWidth(self.frame), naviBarHeight);
for (UIView *view in self.subviews) {
if([NSStringFromClass([view class]) containsString:@"Background"]) {
view.frame = self.bounds;
}
else if ([NSStringFromClass([view class]) containsString:@"ContentView"]) {
CGRect frame = view.frame;
frame.origin.y = statusBarHeight;
frame.size.height = self.bounds.size.height - frame.origin.y;
view.frame = frame;
}
}
}
- UIScrollView、UITableView、UICollectionView
大家在iOS11设备上运行出现最多问题应该就是tableview莫名奇妙的偏移20pt或者64pt了。。原因是iOS11弃用了automaticallyAdjustsScrollViewInsets属性,取而代之的是UIScrollView新增了contentInsetAdjustmentBehavior属性,这一切的罪魁祸首都是新引入的safeArea,关于safeArea适配这篇文章iOS 11 安全区域适配总结讲的很详细,感兴趣的可以看下,我直接贴适配代码,因为低版本直接用contentInsetAdjustmentBehavior会报警告,所有定义了如下的宏(感谢@炒鸡范的指正,之前的宏犯了个低级错误...现改为)
#define adjustsScrollViewInsets(scrollView)\
do {\
_Pragma("clang diagnostic push")\
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"")\
if ([scrollView respondsToSelector:NSSelectorFromString(@"setContentInsetAdjustmentBehavior:")]) {\
NSMethodSignature *signature = [UIScrollView instanceMethodSignatureForSelector:@selector(setContentInsetAdjustmentBehavior:)];\
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];\
NSInteger argument = 2;\
invocation.target = scrollView;\
invocation.selector = @selector(setContentInsetAdjustmentBehavior:);\
[invocation setArgument:&argument atIndex:2];\
[invocation retainArguments];\
[invocation invoke];\
}\
_Pragma("clang diagnostic pop")\
}
还有的发现某些界面tableView的sectionHeader、sectionFooter高度与设置不符的问题,在iOS11中如果不实现 -tableView: viewForHeaderInSection:和-tableView: viewForFooterInSection: ,则-tableView: heightForHeaderInSection:和- tableView: heightForFooterInSection:不会被调用,导致它们都变成了默认高度,这是因为tableView在iOS11默认使用Self-Sizing,tableView的estimatedRowHeight、estimatedSectionHeaderHeight、 estimatedSectionFooterHeight三个高度估算属性由默认的0变成了UITableViewAutomaticDimension,解决办法简单粗暴,就是实现对应方法或把这三个属性设为0。
如果你使用了Masonry,那么你需要适配safeArea
if (@available(iOS 11.0, *)) {
make.edges.equalTo()(self.view.safeAreaInsets)
} else {
make.edges.equalTo()(self.view)
}
- TabBarController
在viewWillAppear时 tabbar高度是49(默认高度?)
在viewDidAppear时 tabbar高度是83(真实准确高度)
可我在viewDidLoad时候就开始绘制界面了 如何适配?
具体的实现逻辑就是写一个继承UITabBarController的类然后修改类里面的方法
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
if (@available(iOS 11.0, *)){
for (UIView *view in self.view.subviews) {
if ([view isKindOfClass:[UITabBar class]]) {
//x的位置不变 y的位置你自己调调到UI满意 宽不变 高也不能变 最终只是改变一下y的相对位置
view.frame = CGRectMake(view.frame.origin.x, self.view.bounds.size.height-64, view.frame.size.width, 83);
}
}
}
}
之前这么写有一个问题,就是会出现tabbar在push和pop的时候有上移下移的问题,后来查了一些资料解决了这个问题,在需要展示tabbar的控制器中添加下面的代码就可以了
- (void)viewWillDisappear:(BOOL)animated{
if (iPhoneX) {
if (@available(iOS 11.0, *)){
// 修改tabBra的frame
CGRect frame = self.tabBarController.tabBar.frame;
frame.origin.y = [UIScreen mainScreen].bounds.size.height -64;
self.navigationController.tabBarController.tabBar.frame = frame;
}
}
}
- (void)viewWillAppear:(BOOL)animated{
if (iPhoneX) {
if (@available(iOS 11.0, *)){
// 修改tabBra的frame
CGRect frame = self.tabBarController.tabBar.frame;
frame.origin.y = [UIScreen mainScreen].bounds.size.height -64;
self.navigationController.tabBarController.tabBar.frame = frame;
}
}
}
二十六、git的常见命令操作
git pull 拉下项目代码
git status 查看状态
git checkout --readme.txt 把readme.txt文件在工作去的修改全部撤销
git rm 用于删除一个文件
git branch dev 创建dev分支
git checkout dev 跳转到dev分支 //注意当前的要先提交然后再跳转
git checkout -b dev 创建并切换到dev分支
git branch 查看当前分支
二十七、http中的三次握手和四次挥手?
1:客户端向服务器发出连接请求报文
2:TCP服务器收到请求报文后,如果同意连接,则发出确认报文
3:TCP客户进程收到确认后,还要向服务器给出确认
思考:为什么要三次握手呢,有人说两次握手就好了?
举例:已失效的连接请求报文段。
client发送了第一个连接的请求报文,但是由于网络不好,这个请求没有立即到达服务端,而是在某个网络节点中滞留了,直到某个时间才到达server,本来这已经是一个失效
的报文,但是server端接收到这个请求报文后,还是会想client发出确认的报文,表示同意连接。假如不采用三次握手,那么只要server发出确认,新的建立就连接了,但其实这个
请求是失效的请求,client是不会理睬server的确认信息,也不会向服务端发送确认的请求,但是server认为新的连接已经建立起来了,并一直等待client发来数据,这样,server的
很多资源就没白白浪费掉了,采用三次握手就是为了防止这种情况的发生,server会因为收不到确认的报文,就知道client并没有建立连接。这就是三次握手的作用。
- TCP的四次挥手 ?
1、TCP发送一个FIN(结束),用来关闭客户到服务端的连接。
2、服务端收到这个FIN,他发回一个ACK(确认),确认收到序号为收到序号+1,和SYN一样,一个FIN将占用一个序号
3、 服务端发送一个FIN(结束)到客户端,服务端关闭客户端的连接。
4、客户端发送ACK(确认)报文确认,并将确认的序号+1,这样关闭完成
思考:那么为什么是4次挥手呢?
为了确保数据能够完成传输。
关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可以未必会马上会关闭SOCKET,也
即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。
可能有人会有疑问,tcp我握手的时候为何ACK(确认)和SYN(建立连接)是一起发送。挥手的时候为什么是分开的时候发送呢 ????
因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,
思考:客户端突然挂掉了怎么办?
正常连接时,客户端突然挂掉了,如果没有措施处理这种情况,那么就会出现客户端和服务器端出现长时期的空闲。解决办法是在服务器端设置保活计时器,每当服务器收到
客户端的消息,就将计时器复位。超时时间通常设置为2小时。若服务器超过2小时没收到客户的信息,他就发送探测报文段。若发送了10个探测报文段,每一个相隔75秒,
还没有响应就认为客户端出了故障,因而终止该连接。
- 四、TCP和UDP的区别
我这里简单列举几个,因为我还没有研究UDP这个协议。
1、基于连接与无连接;UDP是无连接的,即发送数据之前不需要建立连接
2、TCP保证数据正确性
,UDP可能丢包,TCP保证数据顺序,UDP不保证。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付
,即不保证可靠交付Tcp通过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输。如丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。
3、UDP具有较好的实时性,工作效率比TCP高
,适用于对高速传输和实时性有较高的通信或广播通信。
4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信。
5、TCP对系统资源要求较多,UDP对系统资源要求较少。
二十八、IOS 保证线程同步方式&性能对比
- @synchronized
- NSLock
- NSRecursiveLock
- dispatch_semaphore
- NSCondition
- pthread_mutex
- OSSpinLock。
文章 https://www.jianshu.com/p/4edf98a61483
二十九、数据库
文章 https://www.jianshu.com/p/d8b980b41de4
三十、iOS中UITableViewCell的重用机制原理?
- 重用实现分析
查看UITableView头文件,会找到NSMutableArray* visiableCells
,和NSMutableDictnery* reusableTableCells
两个结构。visiableCells内保存当前显示的cells,reusableTableCells保存可重 用的cells
TableView显示之初,reusableTableCells为空,那么tableView dequeueReusableCellWithIdentifier:CellIdentifier返回nil。开始的cell都是通过 [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]来创建,而且cellForRowAtIndexPath只是调用最大显示cell数的 次数
比如:有100条数据,iPhone一屏最多显示10个cell。程序最开始显示TableView的情况是:
1. 用[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]创建10次cell,并给cell指定同样的重用标识(当然,可以为不同显示类型的 cell指定不同的标识)。并且10个cell全部都加入到visiableCells数组,reusableTableCells为空。
2. 向下拖动tableView,当cell1完全移出屏幕,并且cell11(它也是alloc出来的,原因同上)完全显示出来的时候。cell11加入到 visiableCells,cell1移出visiableCells,cell1加入到reusableTableCells。
3. 接着向下拖动tableView,因为reusableTableCells中已经有值,所以,当需要显示新的 cell,cellForRowAtIndexPath再次被调用的时候,tableView dequeueReusableCellWithIdentifier:CellIdentifier,返回cell1。cell1加入到 visiableCells,cell1移出reusableTableCells;cell2移出visiableCells,cell2加入到 reusableTableCells。之后再需要显示的Cell就可以正常重用了。
所以整个过程并不难理解,但需要注意正是因为这样的原因:配置Cell的时候一定要注意,对取出的重用的cell做重新赋值,不要遗留老数据
。
- 一些情况
使用过程中,我注意到,并不是只有拖动超出屏幕的时候才会更新reusableTableCells表,还有:
1. reloadData,这种情况比较特殊。一般是部分数据发生变化,需要重新刷新cell显示的内容时调用。在 cellForRowAtIndexPath调用中,所有cell都是重用的。我估计reloadData调用后,把visiableCells中所有 cell移入reusableTableCells,visiableCells清空。cellForRowAtIndexPath调用后,再把 reuse的cell从reusableTableCells取出来,放入到visiableCells。
2. reloadRowsAtIndex,刷新指定的IndexPath。如果调用时reusableTableCells为空,那么 cellForRowAtIndexPath调用后,是新创建cell,新的cell加入到visiableCells。老的cell移出 visiableCells,加入到reusableTableCells。于是,之后的刷新就有cell做reuse了。
三十一、遇到tableView卡顿嘛?会造成卡顿的原因大致有哪些?
1.最常用的就是cell的重用, 注册重用标识符
如果不重用cell时,每当一个cell显示到屏幕上时,就会重新创建一个新的cell
如果有很多数据的时候,就会堆积很多cell。
如果重用cell,为cell创建一个ID,每当需要显示cell 的时候,都会先去缓冲池中寻找可循环利用的cell,如果没有再重新创建cell
2.避免cell的重新布局
cell的布局填充等操作 比较耗时,一般创建时就布局好
如可以将cell单独放到一个自定义类,初始化时就布局好
3.提前计算并缓存cell的属性及内容
当我们创建cell的数据源方法时,编译器并不是先创建cell 再定cell的高度
而是先根据内容一次确定每一个cell的高度,高度确定后,再创建要显示的cell,滚动时,每当cell进入凭虚都会计算高度,提前估算高度告诉编译器,编译器知道高度后,紧接着就会创建cell,这时再调用高度的具体计算方法,这样可以方式浪费时间去计算显示以外的cell
4.减少cell中控件的数量
尽量使cell得布局大致相同,不同风格的cell可以使用不用的重用标识符,初始化时添加控件,
不适用的可以先隐藏
5.不要使用ClearColor,无背景色,透明度也不要设置为0
渲染耗时比较长
6.使用局部更新
如果只是更新某组的话,使用reloadSection进行局部更
7.加载网络数据,下载图片,使用异步加载,并缓存
8.少使用addView 给cell动态添加view
9.按需加载cell,cell滚动很快时,只加载范围内的cell
10.不要实现无用的代理方法,tableView只遵守两个协议
11.缓存行高:estimatedHeightForRow不能和HeightForRow里面的layoutIfNeed同时存在,这两者同时存在才会出现“窜动”的bug。所以我的建议是:只要是固定行高就写预估行高来减少行高调用次数提升性能。如果是动态行高就不要写预估方法了,用一个行高的缓存字典来减少代码的调用次数即可
12.不要做多余的绘制工作。在实现drawRect:的时候,它的rect参数就是需要绘制的区域,这个区域之外的不需要进行绘制。例如上例中,就可以用CGRectIntersectsRect、CGRectIntersection或CGRectContainsRect判断是否需要绘制image和text,然后再调用绘制方法。
13.预渲染图像。当新的图像出现时,仍然会有短暂的停顿现象。解决的办法就是在bitmap context里先将其画一遍,导出成UIImage对象,然后再绘制到屏幕;
14.使用正确的数据结构来存储数据。
三十二、runloop 使用
文章
概念:
运行循环,保持程序的运行,处理应用中的各种事件,有事就做,没事休息,可以节省CPU的资源 ,提高程序性能
1、讲讲 RunLoop,项目中有用到吗?
2、RunLoop内部实现逻辑?
3、Runloop和线程的关系?
每条线程都有唯一的一个与之对应的RunLoop对象
RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建
RunLoop在第一次获取时创建,在线程结束时销毁
4、timer 与 Runloop 的关系?
5、程序中添加每3秒响应一次的NSTimer,当拖动tableview时timer可能无法响应要怎么解决?
6、Runloop 是怎么响应用户操作的, 具体流程是什么样的?
7、说说RunLoop的几种状态?
8、Runloop的mode作用是什么?
1、Runloop 的应用:
NSTimer 、 imageview 显示、performselector、常驻线程、自动释放池
2、Runloop 相关类
//
CFRunLoopRef
// 代表运行模式:每次启动,只能制定一个模式,如果需要切换,只能退出当前roop,这样做的目的是为了区分开不同的 timer/source/observer
CFRunLoopModeRef
// 事件输入源
// 1. Source1 : 基于Port的线程间通信 2. Source0 : 触摸事件,PerformSelectors
CFRunLoopSourceRef
// 基于时间的触发器,但是会受到 mode 的影响,但是GCD定时器不会受到影响
CFRunLoopTimerRef
// 观察者 监听roop 的状态
// 可以监听的时间点:
// 1、即将进入kcfRunLoopEntry
// 2、即将处理timer kcfRunLoopBeforeTimers(timer)
// 3、即将处理source kcfRunLoopBeforeSources(source)
// 4、即将进入休眠 kcfRunLoopBeforeWaiting()
// 5、刚从休眠中唤醒 kcfRunLoopAfterWaiting()
// 6、退出 kcfRunLoopExit(loop)
CFRunLoopObserverRef
3、Runloop 的实现机制 以及在多线程中的使用
实现机制:保持程序的运行,处理应用中的各种事件,有事就做,没事休息,可以节省CPU的资源 ,提高程序性能
处理逻辑:
(1)、通知observer ,即将进入 runloop
(2)、通知observer ,即将处理 timer
(3)、通知observer ,即将处理 source0
(4)、如果有 source0,处理
(5)、通知observer ,即将休眠
(6)、通知observer ,即将唤醒
(7)、处理未处理的事件
(8)、线程退出
4、autorelease 对象什么时候被释放?
分两种情况:
1、手动干预释放:就是制定autoreleasepool ,超出当前作用预就释放
2、系统自动释放:当前runloop退出,就释放
5、mode 的作用是什么?
主要是用来制定优先级的
6、nstimer 的使用注意事项:
(1)、将 nstimer 实例添加到runloop 的时候,应该注意 类型mode
(2)、不用的时候 一定要调用 invalidate 方法 ,不调用就会引起内存泄漏 而且用 xcode 找不到
7、UITableViewCell 上有一个 UILable ,用于显示时间,滑动的过程中是否刷新时间呢?
如果在创建定时器的过程中使用的是 NSDefaultRunLoopMode 模式,在滑动过程中是不会调的,因为 该模式是运行在 空闲状态下的。默认模式的优先级比较低。
8、子线程中performSelector afterDelay
performSelector
今天用几个例子来记录一下performSelector的各种用法和注意事项
performSelector:withObject
此方法同步阻塞当前线程 它走完再走后面的方法
performSelectorOnMainThread:withObject:waitUntilDone
此方法可以在主线程或者子线程去调 但selector方法运行在主线程
waitUntilDone:YES 同步阻塞 自己走完再走后面方法
waitUntilDone:NO 异步非阻塞
performSelector:withObject:afterDelay
此方法是异步非阻塞!! 不能在没有runloop的子线程直接调 直接调的话不会生效
如果想要在子线程中生效可以:
给这个子线程加runloop
让这个方法在一个你创建的新的带有runloop的子线程中perform
不用performSelector:withObject:afterDelay改用dispatch_after
1. afterDelay 方式是使用当前线程的定时器在一定时间后调用SEL,NO AfterDelay方式是直接调用SEL.
2. 主线程的runloop默认开启,子线程的runloop默认不开启,所以timer在子线程中是不会执行的,需要手动开启runloop。
三十三、weak 与 assign 的区别
1、区别
1.修饰变量类型的区别
weak 只可以修饰对象。如果修饰基本数据类型,编译器会报错-“Property with ‘weak’ attribute must be of object type”。
assign 可修饰对象,和基本数据类型。当需要修饰对象类型时,MRC时代使用unsafe_unretained。当然,unsafe_unretained也可能产生野指针,所以它名字是"unsafe_”。
2.是否产生野指针的区别
weak 不会产生野指针问题。因为weak修饰的对象释放后(引用计数器值为0),指针会自动被置nil,之后再向该对象发消息也不会崩溃。 weak是安全的。
assign 如果修饰对象,会产生野指针问题;如果修饰基本数据类型则是安全的。修饰的对象释放后,指针不会自动被置空,此时向对象发消息会崩溃。
二、相似
都可以修饰对象类型,但是assign修饰对象会存在问题。
三、总结
assign 适用于基本数据类型如int,float,struct等值类型,不适用于引用类型。因为值类型会被放入栈中,遵循先进后出原则,由系统负责管理栈内存。而引用类型会被放入堆中,需要我们自己手动管理内存或通过ARC管理。
weak 适用于delegate和block等引用类型,不会导致野指针问题,也不会循环引用,非常安全。
三十四、常见的审核 反馈问题有哪些?
文章 https://www.jianshu.com/p/0de01db729c8