面试题-UI相关

539 阅读23分钟

1.TableViewCell如何根据UILabel内容长度自动调整高度?

  • UILabel的Line设置为0。Line代表UILabel能显示的最多行数;

* 重写estimatedHeightForRowAtIndexPath方法

* 重写heightForRowAtIndexPath,真实高度返回值为UITableViewAutomaticDimension

* 或者在viewDidLoad中直接用


self.tableView.estimatedRowHeight = 10

self.tableView.rowHeight = UITableViewAutomaticDimension

2.LoadView方法了解吗?

  • LoadView作用,用来创建控制器的View;

* 当控制器的View,第一次使用的时候调用;

* loadView底层原理:

  • 先判断当前控制器是不是从storyBoard当中加载的,如果是从storyBoard加载的控制器.那么它就会把从storyBoard当中加载的控制器的View,设置成当前控制器的view.
  • 当前控制器是不是从xib当中加载的,如果是从xib当中加载的话,把xib当中指定的View,设置为当前控制器的View.
  • 如果也不是从xib加载的,它会创建空白的view.

* 一但重写了loadView方法,就说明要自己定义View.

* 一般使用的场景:当控制器的View一显示时,就是一张图片,或者UIWebView. 节省内存

3.IBOutlet连出来的视图属性为什么可以被设置成weak?

* 官方文档里面说:一般的IBOutlet直接关联到viewcontroller。但是跟其关联的控件并不是添加在controller上,而是添加到controller的view上,比如[self.view addSubView:xxx]; 这个时候self.view已经对xxx 强引用过了,self.view才是持有xxx的对象。这样子才符合引用计数的规则。所以直接IBOutlet顶级view的时候肯定是strong的;

* 其实质是:使用storyboard创建的viewController,那么会有一个叫 _topLevelObjectsToKeepAliveFromStoryboard的私有数组强引用所有top level的对象,同时top level对象强引用所有子对象,那么vc没必要再强引用top level对象的子对象。

* 以UIButton为例:UIViewController->UIView->UIButton

* 他们之间的引用关系用图表示如下:

  • viewController强引用view对象,同时view强引用button对象,那么你声明属性的时候使用weak就可以了。(觉得Strong也可以,但是完全没必要)

* 释放过程:其实不管声明的属性是强引用还是弱引用,在控制器消失的时候,这个属性消失,View消失,subViews消失,控件也就消失了。

4.IB中User Defined Runtime Attributes如何使用?

* 用户定义的运行时属性

* 它能够通过KVC的方式配置一些你在interface builder 中不能配置的属性。当你希望在IB中作尽可能多得事情,这个特性能够帮助你编写更加轻量级的viewcontrolle

* 当你使用IB(Storyboard或者Xib)编辑视图的时候,有时可能会遇到诸如 圆角、边框、边框颜色、控件背景颜色等等难以通过IB直接设置的属性。这时你不得不借助代码实现。其实当出现这类情况时,我们其实可以借助Runtime Attribute在IB中实现。

* 在IB中,点击任意一个控件切换到identity inspector

图片展示.png

  • 属性类型

    * 下面是一些可用的Attribute Types与其相对应的数据类型:

  • Boolean – BOOL
  • Number – NSNumber * String – NSString * Point – CGPoint * Size – CGSize * Rect – CGRect * Range – NSRange * Color – UIColor * Image - UIImage imageNamed

* 注意:在user defined runtime attributes(用户定义的运行时属性)是没有报错机制的,我们必须保障每一个属性都要写正确(尤其是大小写)和每个Type的数据类型选择正确

5.单个VC的生命周期

  • LoadView: 加载view * viewDidLoad: view加载完毕 * viewWillAppear: 控制器的view将要显示 * viewWillLayoutSubviews:控制器的view将要布局子控件 * viewDidLayoutSubviews:控制器的view布局子控件完成 * viewDidAppear: 控制器的view完全显示 * viewWillDisappear: 控制器的view即将消失的时候 * viewDidDisappear: 控制器的view完全消失的时候

6.controller1 push controller2后调用顺序

当我们点击push的时候首先会加载下一个界面然后才会调用当前界面消失的方法

  • loadView:ViewController2
  • viewDidLoad:ViewController2
  • viewWillDisappear:ViewController1 将要消失
  • viewWillAppear:ViewController2 将要出现
  • viewWillLayoutSubviews ViewController2
  • viewDidLayoutSubviews ViewController2
  • viewWillLayoutSubviews:ViewController1
  • viewDidLayoutSubviews:ViewController1
  • viewDidDisappear:ViewController1 完全消失
  • viewDidAppear:ViewController2 完全出现
  • 如果a控制器push到b控制器,那么a和b的View都不会被销毁,因为它的控制器还存在,有一个强引用引用着它(除非内存警告会销毁a的View)如果b控制器pop到a控制器,那么b的View会被销毁a和b都在导航控制器的栈里被管理,就是个数组
  • 此时如果你打印会发现方法的调用顺序是:
  • 控制器b的View的viewDidLoad
  • 控制器b的View的viewWillAppear
  • 控制器a的View的viewDidDisappear

7.内存警告处理

  • 内存警告调用顺序
    • 当application接收到内存警告的时候,会先通知它的代理,代理在接收到内存警告的时候会调用applicationDidReceiveMemoryWarning方法
    • 之后代理会通知它的window,window会通知它的根控制器,根控制器会通知它的子控制器,内存警告是由上往下一层一层往下传的,最后传给控制器View,控制器View会调用它的didReceiveMemoryWarning方法
  • 内存警告处理
    • 如果view可以release,则调用 viewWillUnLoad方法,release the view,然后再调用viewDidUnLoad方法。 ###8.说一下AppDelegate代理的几个方法?
  • 1.-(void)applicationWillResignActive:(UIApplication )application.

说明:当应用程序将要入非活动状态执行,在此期间,应用程序不接收消息或事件,比如来电.

  • 2.-(void)applicationDidBecomeActive:(UIApplication )application

说明:当应用程序入活动状态执行,这个刚好跟上面那个方法相反.

  • 3.-(void)applicationDidEnterBackground:(UIApplication )application

说明:当程序被推送到后台的时候调用。所以要设置后台继续运行,则在这个函数里面设置即可.

  • 4.-(void)applicationWillEnterForeground:(UIApplication )application

说明:当程序从后台将要重新回到前台时候调用,这个刚好跟上面的那个方法相反.

  • 5.-(void)applicationWillTerminate:(UIApplication )application.

说明:当程序将要退出是被调用,通常是用来保存数据和一些退出前的清理工作。这个需要要,设置UIApplicationExitsOnSuspend的键值.

  • 6.-(void)applicationDidReceiveMemoryWarning:(UIApplication )application

说明: iPhone设备只有有限的内存,如果为应用程序分配了太多内存操作系统会终止应用程!序的运行,在终止前会执行这个方法,通常可以在这里进行内存清理工作防止程序被终止.

  • 7.-(void)applicationSignificantTimeChange:(UIApplication )application

说明:当系统时间发生改变时执行.

  • 8.-(void)applicationDidFinishLaunching:(UIApplication)application

说明:当程序载入后执行.

  • 9.- (void)application:(UApplication)application willChangeStatusBarFrame:(CGRectinewStatusBarFrame

说明:当StatusBar框将要变化时执行.

  • 10.- (void)application:(UIApplication )application willChangeStatusBarOrientation:(UlinterfaceOrientation)newStatusBarOrientation duration(NSTimelinterval)duration

说明:当StatusBar框方向将要变化时执行.

  • 11.-(BOOL)application:(UIApplication )application handleOpenURL:(NSURL )url

说明:当通过url执行.

  • 12.- (void)application:(UIApplication )application didChangeStatusBarOrientation(unterfaceOrientation)oldStatusBarOrientation

说明:当StatusBar框方向变化完成后执行.

  • 13.-(void)application:(UIApplication )application didChangeSetStatusBarFrame:(CGRect)oldStatusBarFrame

说明:当StatusBar框变化完成后执行. ###9.如何通过方法找到当前显示的viewController?

  • 场景:在处理URL Router跳转的时候,经常需要得到当前最上层的控制器进行视图跳转。
  • 方法:Podfile添加pod 'CJBaseHelper/UIViewControllerCJHelper',并pod update或者pod install,库引入成功后,直接使用如下方法即可,即:
UIViewController *vc = [UIViewControllerCJHelper findCurrentShowingViewController];
或
UIViewController *vc = [UIViewControllerCJHelper findCurrentShowingViewControllerFrom:self];

10.如何在多次presentViewController后直接返回到指定层

  • 场景:如果多个控制器都通过present方式跳转,比如从A跳到B,从B跳到C,从C跳到D,那么如何从D直接返回A呢?
  • 方法:可以通过presentingViewController一直找到A控制器,然后调用A控制器的 dismissViewControllerAnimated 方法。方法如下:
然后调用A控制器的 dismissViewControllerAnimated 方法。方法如下:
  • 补充:presentedViewControllerpresentingViewController 假设从A控制器通过present的方式跳转到了B控制器,那么 A.presentedViewController 就是B控制器B.presentingViewController 就是A控制器

11.如何通过视图view获取当前视图所在的控制器

+ (nullable UIViewController *)findBelongViewControllerForView:(UIView *)view {
    UIResponder *responder = view;
    while ((responder = [responder nextResponder]))
        if ([responder isKindOfClass: [UIViewController class]]) {
            return (UIViewController *)responder;
        }
    return nil;
}

12.setNeedsDisplay 和 layoutIfNeeded 两者是什么关系?

  • setNeedsDisplay会调用drawRect方法重画页面
  • setNeedsLayout会调用layoutSubviews,页面才会发生变化。
  • 调用layoutIfNeed不会触发VC中viewDidLayoutSubviews 和 viewWillLayoutSubviews 里面重写 views的布局

13.什么情况下会调用layoutSubviews ?

  • 调用setNeedsLayout layoutIfNeed,直接调用setLayoutSubviews
  • addsubview时触发layoutSubviews
  • 改变一个view的frame会触发layoutSubviews
  • 改变view的size会触发父view的layoutSubviews
  • 滚动会触发layoutSubviews
  • 旋转Screen会触发父UIView上的layoutSubviews事件

14.什么情况会调用draw rect方法

  • controller的loadView、viewdidLoad方法调用之后,view即将出现在屏幕之前系统调用drawRect.
  • sizeToFit方法调用之后
  • 设置contetMode为UIViewCOntentModelRedraw,之后每次更改frame的时候调用redraw方法。
  • 调用setNeedsDisplay方法

15.多个类型的cell如何优雅加载?

  • 方法一: modle,根据model来对应cell,cell面向model开发,一般都是一个类型的数据(model)对应一种类型的cell,所以类型是固定的,所以我们用一个枚举来定义所有类型的cell,cellType或者model就知道如何去处理相应的cell了;但是会有重复的操作和数据处理。
  • 方法二:
  • Model:对 Cell 类型进行更高层次的抽象,将model抽出相同的数据,定义BaseModel,通过继承的方式,分为数据类型 DataModel 和非数据类型 SpecialModel 两种,进行定义,通过多层继承可进一步避免重复定义变量。将非数据类型也定义为类型的好处是,将这部分 UI 控制逻辑下沉到 Model 创建之处:网络/持久化数据 Entity -> UIModel,在这个过程中,创建额外的非数据型UIModel,只要数据创建好,后期就不用再理相关逻辑了。
  • Cell:定义BaseCell, Cell子类型通过运行时动态创建,UI显示通过CardBaseView作为容器,加载到Cell 的ContentView上。
  • 通过一系列解耦,将变化分散到两端:Model 和 View;
  • View:因为是View放置在Cell的ContentView上,因此,View的Delegate是Cell,Cell通过消息转发实现回调,避免Cell实现中手写回调中转

16.控件的点击事件和添加在上边的手势谁先响应,并说明原因

  • 只会响应手势
  • UIGestureRecognizer有个属性cancelsTouchesInView,这个属性默认值为YES,即当手势识别成功后,会发送touchesCancelled消息为view来结束view的响应。如果cancelsTouchesInView为NO,那么gestureRecognizer和view都可以响应。 UIKit内置了6中手势识别器:
    • UITapGestureRecognizer:点击(单击、双击、三连击等)手势
    • UIPinchGestureRecognizer:缩放手势
    • UIPanGestureRecognizer:拖拽手势
    • UISwipeGestureRecognizer:滑动手势
    • UIRotationGestureRecognizer:旋转手势
    • UILongPressGestureRecognizer:长按手势

17.iOS滑块验证

拼图验证
  • 思路:
    • 1.创建一个背景图片和一个滑块,需要注意的是我们需要压缩背景的显示图片到指定的尺寸目的是为了从这图片上截取某一块的时候frame对的上
    • 2.创建一个从可移动的图片,从背景图的随机位置截取某一部分,并绘制贝塞尔曲线
    • 3.创建一个遮罩层UIView (上面加入一个CAShapeLayer CAShapeLayer的贝塞尔曲线和截取图片的一样)
  • 4.拉动滑块移动让图片位置随着Slider的value值改变而改变frame
    • 5.判断可移动图片与遮罩层的frame的x值相差是否在误差范围内
文本按顺序点击验证
  • 思路:
      1. 创建普通的背景图和滑块
    • 2.获取多个随机汉字并打乱顺序
    • 3.创建随机位置的按钮文本(x值固定,y值随机)

18.CALayer 和 UIView的区别?

  • UIView 和 CALayer都是 UI 操作的对象
  • UIView是 CALayer用于交互的对象,UIView是CALayer的delegate ,UIView是UIResponder的子类,其中提供了很多CALayer所没有的交互接口,主要负责处理用户触发的各种操作
  • CALayer主要负责绘制,在图像和动画上渲染性能更好
  • iOS提供 UIView 和CAlayer 两个个平行的层级结构主要是用于职责分离,实现视图的绘制,显示,布局解耦,避免重复代码

19.解释一下这几个名词:UIView 的frame,bounds,center,CALayer的frame,bounds,anchorPoint,position

UIView
  • frame: 描述当前界面元素在其父界面元素中的位置和大小
  • bounds: 描述当前界面元素在其自身坐标系统中的位置和大小
  • center: 描述当前界面元素的中心点在其父界面元素中的位置
CALayer
  • frame:与view中的frame概念相同,(x,y)subLayer左上角相对于supLayer坐标系的位置关系;width, height表示subLayer的宽度和高度
  • bounds:与view中的bounds概念相同,(x,y)subLayer左上角相对于自身坐标系的关系;width, height表示subLayer的宽度和高
  • anchorPoint(锚点):锚点在自身坐标系中的相对位置,默认值为(0.5,0.5),左上角为(0,0),右下角为(1,1),其他位置以此类推;锚点都是对于自身来讲的. 确定自身的锚点,通常用于做相对的tranform变换.当然也可以用来确定位置
  • position:锚点在supLayer坐标系中的位置

20.iOS 为什么必须在主线程中操作UI

  • UIKit不是线程安全的(多个线程访问修改,可能一个线程已经释放了,另一个线程会访问,以及资源抢夺问题等)
  • 主线程上默认是开始 runloop 的,子线程没有 runloop 也无法监听一些事件,手势刷新UI等操作
  • 在子线程更新UI可能会无效,也可能会崩溃

21.layoutIfNeeded , layoutSubViews和 setNeedsLayout区别?

  • layoutIfNeeded 方法一点被调用,主线程会立即强制重新布局,它会从当前视图开始,一直到完成所有子视图的布局
  • layoutSubViews 用来自定义视图尺寸,他是系统自动调用的,开发者不能手动调用,可以重写改方法,让系统在调整布局时候按照我们希望的方式进行布局.这个方法在旋转屏幕,滑动或者触摸屏幕,修改子视图时候被触发.
  • setNeedsLayout 和 layoutIfNeeded相似,唯一不同的是他不会立即强制视图重新布局,而是在下一个布局周期才会触发更新.他主要用于多个视图布局先后更新的场景;

22.图片png与jpg的区别是什么?

  • png: 优点:无损格式,不论保存多少次,理论上图片质量都不会受任何影响;支持透明 缺点:尺寸过大;打开速度与保存速度和jpg没法比
  • jpg: 优点:尺寸较小,节省空间;打开速度快 缺点:有损格式,在修图时不断保存会导致图片质量不断降低;不支持透明

23.UIScrollView 的 contentView, contentInset, contentSize, contentOffset 关键字比较?

  • contentView 是指 UIScrollView上显示内容的区域,用户 addSubView 都是在 contentView上进行的;
  • contentInset 是指 contentView与 UIScrollView的边界;
  • contentSize 是指 contentView 的大小,表示可以滑动范围;
  • contentOffset 是当前 contentView 浏览位置左上角点的坐标;

24.SafeArea, SafeAreaLayoutGuide, SafeAreaInsets ?

  • SafeArea 是指App 显示内容的区域,不包括StatusBar,Navigationbar,tabbar,和 toolbar, 在 iPhoneX 中一般是指扣除了statusBar(44像素),和底部home indicator(高度为34像素)的区域.这样操作不会被刘海或者底部手势影响了.
  • SafeAreaLayoutGuide 是指 Safe Area 的区域范围和限制, 在布局设置中,可以取得他的上下左右4个边界位置进行相应的布局
  • SafeAreaInsets 限定了Safe Area区域与整个屏幕之间的布局关系,一般用上下左右4个值来获取 SafeArea 与屏幕边缘之间的距离;

25.AutoLayout 和 Frame在UI布局和渲染上有什么区别?

  • AutoLayout 是针对多尺寸屏幕的设计,其本质是通过线性不等式设置UI控件的相对位置,从而适配多种屏幕设备
  • Frame 是基于XY坐标轴系统布局机制,它从数学上限定了UI 控件的具体位置,是 iOS'开发中最低层,最基本的界面布局方式
  • AutoLayout性能比 Frame 差很多,AutoLayout布局过程是首先求解线性不等式,然后在转化为Frame进行布局,其中求解计算量非常大,很损耗性能

26.Storyboard/xib 和 纯代码UI相比,有哪些优缺点

优点:

  • 简单直接快速, 通过拖拽和点选即可配置UI,界面所见即所得

  • 在 Storybord可以清楚的区分ViewController 界面之间的跳转关系 缺点:

  • 协作冲突,多人编辑时,容易发生冲突,很难解决冲突

  • 很难做到界面继承和重用

  • 不便于进行模块化管理

  • 占用内存,造成卡顿,影响性能

  • 排除BAD_EXCUSE错误不说,单单是有提示的错误,就足以让人在代码和Storyboard之间来回摸索,却无法找到解决方案。

相同点:

  • 采用Storyboard开发或采用纯代码开发的App,在真机的运行效率上,并没有太大的区别

27.UIButton 和UITableView的层级结构

  • 继承结构
    • UIButton -> UIControl -> UIView -> UIResponder -> NSObject
    • UITableView -> UIScrollView -> UIView -> UIResponder -> NSObject
  • 内部子控件结构
    • UIButton内部子控件结构: 默认有两个, 一个UIImageView, 一个UILable, 分别可以设置图片和文字, button设置属性基本都是set方法
    • UITableView内部子控件结构: UITableView中每一行数据都是UITableViewCell, UITableViewCell内部有一个UIView控件 (contentView, 作为其它元素的父控件) , 两个UILable 控件 (textLable, detailTextLable) , 一个UIImageView控件 (imageView) , 分别用于容器, 显示内容, 详情和图片

28.什么是隐式动画和显示动画

  • 隐式动画
    • 系统框架自动完成,Core Animation在每个runloop周期中自动开始一次新的事务,即使你不显式的用[CATransaction begin]开始一次事务,任何在一次runloop循环中属性的改变都会被集中起来,然后做一次0.25秒的动画。在iOS4中,苹果对UIView添加了一种基于block的动画方法:+animateWithDuration:animations:。这样写对做一堆的属性动画在语法上会更加简单,但实质上它们都是在做同样的事情。CATransaction的+begin和+commit方法在+animateWithDuration:animations:内部自动调用,这样block中所有属性的改变都会被事务所包含,多用于简单动画效果
    [UIView animateWithDuration:1 animations:^{
        view.center = self.view.center;
    }];

  • 显式动画,Core Animation提供的显式动画类型,既可以直接对layer层属性做动画,也可以覆盖默认的图层行为。我们经常使用的CABasicAnimationCAKeyframeAnimationCATransitionAnimationCAAnimationGroup等都是显式动画类型,这些CAAnimation类型可以直接提交到CALayer上。显式动画可用于实现更为复杂的动画效果.
CABasicAnimation *positionAnima = [CABasicAnimation animationWithKeyPath:@"position.y"];
positionAnima.duration = 0.8;
positionAnima.fromValue = @(self.imageView.center.y);
positionAnima.toValue = @(self.imageView.center.y-30);
positionAnima.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
positionAnima.repeatCount = HUGE_VALF;
positionAnima.repeatDuration = 2;
positionAnima.removedOnCompletion = NO;
positionAnima.fillMode = kCAFillModeForwards;
[self.imageView.layer addAnimation:positionAnima forKey:@"AnimationMoveY"];

29.UIWindow是什么,有什么特点?

  • UIWindow 继承自 UIView, 作为根视图来装置 View元素, UIWindow提供一个区域用于显示UIView,并且将事件分发给 UIView,一般一个应用只有一个 UIWindow;

30.View 和 View 传值方式有哪些

  • 通过视图类对外提供的属性直接传值
  • 通过Delegate传值
  • 通过通知传值
  • 通过 Block 传值

31.imageNamed 和 imageWithContentsOfFile区别

  • imageNamed 会自动缓存新加载的图片,并切重复利用缓存图片,一般用于App 内经常使用的尺寸不大的图片
  • imageWithContentsOfFile 根据路径加载没有 取缓存和缓存的过程,用于一些大图,使用完毕会释放内存

32.tintColor 是什么?

  • tintColor 是 ios7以后 UIView类添加的一个新属性,用于改变应用的主色调,默认是 nil

33.masksToBounds 和clipsToBounds

  • masksToBounds 是指子 layer 在超出父 layer时是否被裁剪,YES表示参见,NO 表示不裁剪, 默认是NO
  • clipsToBounds 是指子 View 在超出父 View时是否被裁剪

34.一个 TableView 是否可以关联2个不同的dataSource?

  • 可以关联多个数据源,重点只要处理好数据源和 tableView 的对接工作即可

35.能否在一个控制器 嵌入2个 TableViewController 控制器

  • 可以, 控制器可以添加子控制器

36.使用 drawRect 有什么影响?

  • drawRect 方法依赖 Core Graphics 框架来进行自定义的绘制 缺点:
  • 它处理 touch 事件时每次按钮被点击后,都会用 setNeddsDisplay 进行强制 重绘;而且不止一次,每次单点事件触发两次执行。这样的话从性能的角度来 说,对 CPU 和内存来说都是欠佳的。特别是如果在我们的界面上有多个这样的 UIButton 实例,那就会很糟糕了
  • 这个方法的调用机制也是非常特别. 当你调用 setNeedsDisplay 方法时, UIKit 将会 把当前图层标记为 dirty,但还是会显示原来的内容,直到下一次的视图渲染周期,才会 将标记为 dirty 的图层重新建立 Core Graphics 上下文,然后将内存中的数据恢复出 来, 再使用 CGContextRef 进行绘制

37.动画相关有哪几种方式?

  • UIView animation --- 可以实现基于 UIView 的简单动画,他是CALayer Animation封装,可以实现移动,旋转,变色,缩放等基本操作,他实现的动画无法回撤,暂停,与手势交互,常用方法如下
    [UIView animateWithDuration: 10 animations:^{
        // 动画操作
    }];

  • UIViewPropertyAnimator --- 是 iOS10 中引入的处理交互式动画接口,他是基于 UIView 实现的, 用法同 UIView animation比较类似,增加了一些新的属性以及方法;

  • CALayer Animation --- 是在更底层CALayer 上的动画接口, 他可以实现各种复杂的动画效果, 实现的动画可以回撤,暂停与手势交互等,常用的类有以下几个:

      1. CABasicAnimation——基本动画
      1. CAKeyframeAnimation——关键帧动画

    与CABasicAnimation的区别是:CABasicAnimation:只能从一个数值(fromValue)变到另一个数值(toValue)CAKeyframeAnimation:会使用一个NSArray保存这些数值

      1. CAAnimationGroup——动画组

    动画组,是CAAnimation的子类,可以保存一组动画对象,将CAAnimationGroup对象加入层后,组中所有动画对象可以同时并发运行

      1. 转场动画——CATransition

    CATransition是CAAnimation的子类,用于做转场动画,能够为层提供移出屏幕和移入屏幕的动画效果。iOS比Mac OS X的转场动画效果少一点。

38.手机适配方案

  • 使用宏,针对不同的设备抽取导航,状态栏,以及 tabbar 高度信息
  • 宽高等比适配(X 的特殊处理)
  • 图片美工需要提供@2x,@3x进行适配
  • 字体根据屏幕大小适配
  • 权限针对不同系统进行适配
  • api 适配

39.屏幕成像原理是什么?

  • 每一个UIView都有一个layer,每一个layer都有个content,这个content指向的是一块缓存,叫做backing store。
  • UIView的绘制和渲染是两个过程,当UIView被绘制时,CPU执行drawRect,通过context将数据写入backing store。
  • 当backing store写完后,通过render server交给GPU去渲染,将backing store中的bitmap数据显示在屏幕上。
  • 说到底CPU就是做绘制的操作把内容放到缓存里,GPU负责从缓存里读取数据然后渲染到屏幕上。

40.离屏渲染是什么?

  • 离屏渲染,指的是 GPU (图形处理器)在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。为什么离屏这么耗时?原因主要有创建缓冲区和上下文切换。创建新的缓冲区代价都不算大,付出最大代价的是上下文切换。
  • GPU屏幕渲染有两种方式:
    • On-Screen Rendering (当前屏幕渲染) 指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区进行。
    • Off-Screen Rendering (离屏渲染) 指的是在GPU在当前屏幕缓冲区以外开辟一个缓冲区进行渲染操作

41. 哪些情况会造成离屏渲染?

  • 为图层设置遮罩(layer.mask)
  • 将图层的layer.masksToBounds / view.clipsToBounds属性设置为true
  • 将图层layer.allowsGroupOpacity属性设置为YES和layer.opacity小于1.0
  • 为图层设置阴影(layer.shadow *)。
  • 为图层设置layer.shouldRasterize=true
  • 具有layer.cornerRadius,layer.edgeAntialiasingMask,layer.allowsEdgeAntialiasing的图层
  • 文本(任何种类,包括UILabel,CATextLayer,Core Text等)

42.假如Controller太臃肿,如何优化?

  • 将网络请求抽象到单独的类中,方便在基类中处理公共逻辑;方便在基类中处理缓存逻辑,以及其它一些公共逻辑;方便做对象的持久化。
  • 将界面的封装抽象到专门的类中, 构造专门的 UIView 的子类,来负责这些控件的拼装。这是最彻底和优雅的方式,不过稍微麻烦一些的是,你需要把这些控件的事件回调先接管,再都一一暴露回 Controller。
  • 构造 ViewModel, 借鉴MVVM。具体做法就是将 ViewController 给 View 传递数据这个过程,抽象成构造 ViewModel 的过程。
  • 专门构造存储类,专门来处理本地数据的存取。
  • 整合常量

43.什么是响应者链?

  • 响应者链是用于确定事件响应的一种机制, 事件主要是指触摸事件(touch Event),该机制与UIKIT中的UIResponder类密切相关,响应触摸事件的必须是继承自UIResponder的类,比如UIView 和UIViewController

  • 一个事件响应者的完成主要分为2个过程: hitTest方法命中视图和响应者链确定响应者

  • hitTest的调用顺序是从UIWindow开始,对视图的每个子视图依次调用,也可以说是从显示最上面到最下面,直到找命中者; 然后命中者视图沿着响应者链往上传递寻找真正的响应者.

事件传递过程
  • 当我们触控手机屏幕时系统便会将这一操作封装成一个UIEvent放到事件队列里面,然后Application从事件队列取出这个事件,接着需要找到命中者, 所以开始的第一步应该是找到命中者, 那么又是如何找到的呢?那就不得不引出UIView的2个方法:
// 返回能够相应该事件的视图
-(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event  
// 查看点击的位置是否在这个视图上
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event 

  • 寻找事件的命中视图是通过对视图调用hitTest和pointInside完成的 hitTest的调用顺序是从UIWindow开始,对视图的每个子视图依次调用,也可以说是从显示最上面到最下面 遍历直到找到命中视图;
响应链传递
  • 找到命中者,任务并未完成,因为命中者不一定是事件的响应者,所谓响应就是开发中为事件绑定一个触发函数,事件发生后,执行响应函数里的代码
  • 找到命中视图后事件会从此视图开始沿着响应链nextResponder传递,直到找到处理事件的响应视图,如果没有处理的事件会被丢弃。
  • 如果视图有父视图则nextResponder指向父视图,如果是根视图则指向控制器,最终指向AppDelegate, 他们都是通过重写nextResponder来实现。
  • 自下往上查找
无法响应的事件情况
  • Alpha=0、
  • 子视图超出父视图的情况、
  • userInteractionEnabled=NO、
  • hidden=YES
声明
本文面试题答案是参考各路大神收集得来,答案不一定是最全面,最合适的,如果哪里有问题,欢迎大家积极讨论。
参考文章:

www.jianshu.com/p/ca1ff3749… github.com/ReginaVicky…