iOS大厂面试查漏补缺

一、UI方面

1. UI卡顿问题
  1. 原因
    • 屏幕垂直信号之间是一帧率,16.7ms
    • CPU计算,GPU渲染,这两步要在一帧的时间完成。
    • 如果没完成,就会出现卡顿,掉帧情况
  2. 优化方案
    • CPU层面:
    • 尽量用更加轻量级的对象,比如不需要处理事件的视图,可以用CALayer代替UIView
    • 图片的解码,可以在子线程用CGBitmapContext绘制,然后从Bitmap中创建图片
    • 尽量不要对UIView频繁调用属性修改,frame,bounds,transform等修改
    • 尽量提前计算好布局,在有需要的时候一次性调整对应的属性
    • Autolayout会比直接设置frame消耗更多的CPU资源,所以尽量直接设置frame
    • 图片的size最好刚好跟UIImageView的size保持一致
    • 控制线程的最大并发数量
    • 对象创建、及时销毁
    • 文本等预排版(boudingRectWithSize && drawWithRect 这两个方法)
    • GPU层面:
    • 避免离屏渲染(避免设置圆角带来离屏渲染:贝塞尔曲线绘制)
    • 减少视图层级(Texture/ASDK 就是通过pre-composing技术,提前把多个sub-layer合成渲染为一张图片,减少视图层级,大大提高渲染效率)
2. 聊聊WebView?
  • 使用
    1. 设置uiDelegate和navigationDelegate
    1. 设置config
    • 其中config里面,设置userContentController的时候,注意添加JS脚本监听的时候,防止循环引用
    • 通过创建中间类弱引用,让WKScripMessageHandler类设置为弱类型。
    1. 实现代理事件
    • uiDelegate: 处理JS脚本,确认框,警告等
    • navigationDelegate: 处理跳转,加载页面等
    • decisionHandler方法一定要实现,否则点击跳转链接就会崩溃
3. 事件响应链是如何传递的?

事件传递 & 事件响应

  • 事件传递: 从UIApplication到UIWindow,再顺着子视图往下找,根据两个方法
    • pointInSide: withEvent
    • hitTest: withEvent
  • 事件响应
    - 从传递最后确认的视图开始,验证是否能处理响应事件,如果不能就顺着视图往上找父视图,如果可以处理,就处理了,和事件传递大致相反,根据判断方法
    • touchesBegan: withEvent
    • touchesMoved: withEvent
4. layoutsubviews是在什么时机调用的?
  • init初始化是不会触发
  • addSubview会触发
  • 设置frame且前后值发生变化,frame为zero且不添加到指定视图,不会触发
  • 旋转screen会触发父视图的layoutSubviews
  • 滚动UIScrollView引起View重新布局时候会触发layoutSubviews
5. sizeThatFits 和 sizeToFit的区别
  • UIView如果设置过autolayout约束,则该sizeToFit方法失效
  • 调用sizeToFit会自动调用sizeThatFits
  • 自定义View的时候,不应该重写sizeToFit方法,应该重写sizeThatFits,在合适的地方只需要调用sizeToFit方法,就会自动触发sizeThatFits方法
  • sizeToFit可以改变UIView的frame,sizeThatFit不会改变UIView的frame
  • 它们都没有递归,不管subviews,只管自己

二、网络方面

  1. 网络请求缓存问题?
    • 空间:URLCache是苹果提供的网络请求缓存类,主要用于GET方法,请求资源用的。默认内存是512K,磁盘10M,也可以自己修改
    • 策略:6种,使用缓存,不使用缓存,实现,不实现等等。
    • 更新缓存内容:
    • Last-Modified:服务器会在资源后面有个标记Last-Modified,表示最后修改时间。第一次请求会缓存这个时间,后面请求时候,发送一个If-Modified-Since报头。如果返回304就不会传输资源数据,用本地缓存的即可。否则会传输资源。
    • Etag:服务器对资源实体用Etag来标记,就是对实体进行散列计算,得到唯一值。第二次请求,对这个值进行比较。如果返回304就数据为空,和上述情况一样。

三、安全方面

  1. Mach-O
    • Mach Object的缩写,是mac及iOS上可执行文件的格式,不一定是可执行,只是一种文件格式。
    • 包含类型:
      1. OBJECT:.o.a 文件(目标文件)
      1. EXECUTE:指的是IPA拆包后的文件(可执行文件)
      1. DYLIB:指的是.dylib.framework (动态库文件)
      1. DYLINKER:指的是动态连接器(动态库连接器文件)
      1. DSYM:(符号表),指的是有保存符号信息用于分析闪退信息的文件(符号表文件)
    • 加载过程
      1. 把可执行文件加载到内存中
      1. 从可执行文件中分析dyld的路径
      1. dyld 加载到内存中
      1. dyld 递归加载所有的动态连接库dylib
  2. ldid重签名
      1. ldid -e 命令导出可执行文件的签名文件entitlements
      1. 用越狱了的手机,导出SpringBoard的签名文件entitlements
      1. SpringBoard的签名文件覆盖自己的APP的签名文件entitlements

四、底层原理

  1. OC为什么要设计Metaclass?

    • 这个是要从面向对象设计的哲学去考虑这个问题
    • 第一:提取出某一种群体的特性,对这类群体进行划分,从而有基本的属性成员信息方法信息
      第二:可以复用消息传递,在OC的消息传递过程中,只要是这个群体的实体,就不需要关心自身的类型是什么,只需要关心这个接口。在传递消息的时候就可以复用了。
      第三:对于描述这个类的类,即元类,对于类的类方法而言,就类似实例的实例方法,也需要这么一个复用消息传递机制,所以也就产生了针对类的元类,本质上来说,我认为也是消息传递的复用工具
  2. 类可以调用对象方法吗?

    • 可以,因为基类的super-class指针,指向的是类。
  3. isa的指针指向?

      1. 实例对象的isa指向类对象,类对象的isa指向元类
      1. isa指向,在不同平台(arm64 或 x86)通过掩码计算,来得到真实的地址值。
  4. KVO? 键值监听

      1. 使用:
        [self.person addObserver:self forKeyPath:@"age" options:options context:nil];
      1. 本质:
        第一:NSKVONotifying_Person 继承 Person
        第二:在set方法里,增加了willChangedidChange方法
        第三:联想到在swift 里,我们可以写属性监听器
        第四:从runtime的角度也可以验证,通过调用methodForSelector方法,查看实际上调用的方法是什么
        第五:从runtime调用class_copyMethodList方法,也可以获取添加监听后的类的所有的方法列表,打印就会发现与添加之前不一样了。
        第六:从runtime调用object_getClass获取类名,打印也会发现与添加之前不一样
      1. 手动触发KVO?
        第一:只需要手动在合适的地方添加willChangedidChange方法
        第二:通过-> 方式赋值,不会调用set方法,不会触发KVO
  5. KVC 键值编码

      1. 使用
        [self.person setValue:@10 forKey:@"age"];
        [self.person setValue:@20 forKeyPath:@"cat.weight"];
      1. 原理
        第一:赋值
        setKey
        _setKey
        _key
        _isKey
        key
        isKey
        第二:取值
        getKey
        Key
        isKey
        _key
      1. 所以,KVC是可以触发KVO的,因为会调用set方法
  6. Category的理解?

    1. 分类的使用场景:一般业务比较复杂,需要将一部分功能或业务逻辑分模块出来,就可以用分类
    2. Category的本质
    • 通过runtime加载某个类的所有Category数据
    • 把所有的Category的方法、属性、协议,的数据,合并到一个大数组中
      • 后编译的Category数据,会在数组的前面,所以后编译的Category会被先调用
    • 如果分类中含有和原类的类方法同名的,就会先调用分类的方法
    • 通过runtime关联属性
      • objc_setAssociatedObject
      • objc_getAssociatedObject
      • 关联对象并不是存储在被关联的对象中,而是存储在全局统一的AssociationsManager中
      • 设置关联对象为nil,就是相当于移除关联对象
  7. load、initialize方法

    • 调用顺序
    • load
      • 先调用类的load(先编译的先调用),再调用分类的load(也是先编译的先调用)。
      • 先调用父类,再调用子类。
    • initialize
      • 先调用父类,再调用子类。
    • 调用方式
      • load是在系统加载的时候,根据函数地址直接调用的
      • initialize是通过消息发送调用
    • 调用时间
      • load是runtime加载类、分类的时候调用,只会调用一次(在APP冷启动的第二阶段就会调用)
      • initialize是类在第一次接收到消息的时候调用,每个类只会initialize一次。但是父类的initialize方法可能会被调用多次。
	// JHPerson 和 JHTeacher 都是 Person 的子类,并且只有Person这个父类实现了+initialize方法
- (void)testInit {
   NSLog(@"--------");
    [JHPerson alloc];
    [JHPerson alloc];
    [JHTeacher alloc];
    [JHTeacher alloc];
}

	// 伪代码逻辑
    if (JHPerson没有初始化) {
 	if (JHPerson的父类Person没有初始化) {
			objc_msgSend([Person class], @selector(initialize)); // 第一次调用父类
		}
		objc_msgSend([JHPerson class],@selector(initialize)); // 调用子类,子类没实现,去找到父类调用,第二次调用父类
    }
 	
    if (JHTeacher没有初始化) {
 	if (JHTeacher的父类Person没有初始化) { // 父类已经初始化了,不会进来
		   objc_msgSend([Person class], @selector(initialize));
		}
		objc_msgSend([JHTeacher class],@selector(initialize)); // 调用子类,子类没实现,去找到父类调用,第三次调用父类
     }
  1. 聊聊block
    1. block的本质是一个OC对象
      NSGloabalBlock --> NSBlock --> NSObject
    1. block类型问题
    • GloabalBlock – 没有访问auto变量 – 程序的数据区域
    • NSStackBlock – 访问了auto变量 – 栈里面
    • NSMallocBlock – NSStackBlock复制 – 堆里面
    • NSMallockBlock复制,引用计数+1
      iOS大厂面试查漏补缺_第1张图片
    1. 变量捕获
      1. 局部变量会被捕获,全局变量不会被捕获
      1. static修饰的或者是__block修饰的,是指针传递
      1. 默认没有修饰,(即auto)是值传递
    1. 下划线_block修饰后的对象,是一个结构体
    struct __Block_byref_age_0 {
    	void *__isa;
    	__Block_byref_age_0 *__forwarding; // 这个指针指向自己
    	int __flags;
    	int __size;
    	int age;
    };
    
    • forwarding为什么指向自己?如图,保证当从栈复制到堆时候,也能正确指向属性的指针地址
      iOS大厂面试查漏补缺_第2张图片
    1. 循环引用
      1. ARC下
      • 用__weak一般是最佳方案,因为安全,对象销毁指针指向nil
      • 用_unsafe_unretain,不安全,对象销毁,指针地址不变
      • __block 方案,个人取个名字叫过河拆桥法,方便记忆。
        1. 首先__block修饰后,变为一个结构体。
        1. 结构体里面有强指针指向实例对象__block实例对象产生了强引用
        1. 实例对象调用block对象实例对象block产生了强引用
        1. block再访问了__block对象的内容,block__block产生了强引用,如图:
// 1. __block修饰的那部分:__block Person *person = [[Person alloc] init];
    struct __Block_byref_person_0 {
  	void *__isa;
		__Block_byref_person_0 *__forwarding;
 	int __flags;
 	int __size;
 	void (*__Block_byref_id_object_copy)(void*, void*);
		void (*__Block_byref_id_object_dispose)(void*);
 	Person *__strong person; // 后面使用的person指针,都是这个指针
	};

iOS大厂面试查漏补缺_第3张图片
- 5. 现在我要过河拆桥,我调用block的时候,通过__block访问到了我想要访问的实例对象的属性(比如,这里我的目的就是访问person.age),那么我的目的已经完成了,所以,我不再需要让__block对象再去强引用实例对象了。

// 1. __block修饰的那部分:__block Person *person = [[Person alloc] init];
    struct __Block_byref_person_0 {
  	void *__isa;
		__Block_byref_person_0 *__forwarding;
 	int __flags;
 	int __size;
 	void (*__Block_byref_id_object_copy)(void*, void*);
		void (*__Block_byref_id_object_dispose)(void*);
 	Person *__strong person = nil; // 在这里来过河拆桥,把它变为nil
 	
	};

iOS大厂面试查漏补缺_第4张图片

9. 聊聊Runloop
10. 聊聊Runtime
11. 聊聊锁
12. 聊聊多线程
    1. MRC
    • 1.因为MRC不支持弱引用的情况,所以可以使用__unsafe_unretained
    • 2.使用__block解决
      因为__block不会对对象产生强引用

五、聊聊数据持久化

  • 本地存储的几种方式
  • 数据库(sqlite、coredate、FMDB、WCDB)

六、聊聊路由

  • Beehive
  • CTMeditor

七、聊聊Swift和OC的区别?

7.1 struct & class
  • struct是值引用,更轻量,存放于栈,不可继承
  • class是指针引用(或称类型引用),存放于堆区,可以继承
7.2 swift的派发机制
  • 直接派发
    • 是在struct、enum中,速度快,但是不能继承
  • 函数表派发
    • swift里函数表叫Witness Table ,类会有一个数组存储里面的函数指针。
    • 一个对象的内存地址前8位存储类的类型信息,类型信息就包括了函数指针。
    • 后8位信息是引用计数
  • 消息机制派发
    • 消息派发是在运行时,可以改变函数的行为,KVO和CoreData都是对这种机制的运用。OC默认使用消息派发,C语言使用的是直接派发。
      • 当一个消息被派发,程序运行时就会按照继承关系向上查找被调用的函数,但是效率不高,所以会通过缓存来提高效率,性能和函数表派发差不多。
  • 场景
    • 直接派发
      • 值类型
      • final
      • @inline
      • class和协议的extension
    • 函数表派发:
      • class和协议的初始化声明
    • 消息机制派发:
      • class和@objc extension
      • dynamic:可以让类里面的函数使用消息机制派发,可以重载extension里的函数
      • @nonobjc: 来禁止消息机制派发这个函数
7.3 对协议的理解?
  • 解决面向对象菱形继承问题

你可能感兴趣的:(葵花宝典,ios,面试,ui)