1、查找两个子视图的共同父视图
通过super找到两个子视图各自的父视图,然后倒序遍历,找到相等的,就是一个公共父视图。
时间复杂度O(n)?
2、runLoop
理解:对象,内部存有线程名称,与线程有关,有一套事件循环机制,可使线程有事处理、无事休眠。
使用场景:NSTimer 定时器、事件响应、页面卡顿监测。
timer加入run loop注意事项:精确度不高,受 run loop 处理情况影响。
timer 加入 run loop 模式区别:默认加到 default mode,滑动列表有定时器时会暂停,一般加到 common mode
3、dictionary 在 oc和swift中的区别:
在 OC 里,字典是不可变的NSDictionary和可变的NSMutableDictionary类的实例。定义字典时,需要明确指定键和值的类型, OC 字典可以存储任意类型的对象,所以 oc中的字典不是类型安全。oc中字典不能存储值为nil,可以存储[NSNull nu]
Swift 的字典使用泛型来定义,语法更加简洁,并且类型安全。你可以直接定义字典的键和值的类型,编译器会在编译时检查类型是否匹配,避免了运行时的类型错误。借助可选类型,可以存储nil
3、autoreleasepool
实现原理:AutoreleasePoolPage,以栈为数据结构,双向链表,有 parent、child指针,next用于存放下一个autorelease对象,如果next指向栈顶,则创建新的page。
释放时机:作用域结束 或 runloop 即将进入休眠状态时,对释放池对象进行 pop 和 release 操作。
嵌套逻辑:不同的 @autoreleasepool 以 POOL_BOUNDARY 做分割。
4、OOM、内存泄漏,野指针
7、锁
自旋锁:一直处于忙等状态,直到满足条件
互斥锁:非忙等状态,会操作系统内核,将自己的状态改为阻塞挂起,从待执行队列中移除,直到被其他线程唤醒
NSLock:底层是对pthread_mutex的封装,互斥锁。有种场景会引起死锁,连续锁定。
如何实现多读单写
- GCD
func read() -> T {
queue.sync { data } // 同步读取,支持并发
}
func write(_ newValue: T) {
queue.async(flags: .barrier) { // 异步屏障写入,独占队列
self.data = newValue
}
}
- pthread_rwlock读写锁
private var rwLock = pthread_rwlock_t()
func read() -> T {
pthread_rwlock_rdlock(&rwLock)
defer {
pthread_rwlock_unlock(&rwLock)
}
return data
}
func write(_ newValue: T) {
pthread_rwlock_wrlock(&rwLock)
defer {
pthread_rwlock_unlock(&rwLock)
}
data = newValue
}
7、flutter相关
StatefulWidget中 createState,在创建state时,可以用 带有参数的构造函数吗? 不可以。产生的问题:参数无法动态更新。
核心原因:
Flutter 框架机制:State 的生命周期由框架管理(如热重载时可能复用 State),若通过构造函数传参,会导致参数无法动态更新(因为 State实例可能被复用)。
职责分离:StatefulWidget 是不可变的配置,State 是可变的状态。参数应通过 Widget 的属性(final 字段)传递给 State,而非 State 自身的构造函数。
flutter最新的渲染引擎 Impeller 和 skia:skia是通用的2D图形引擎平台,所以需要平衡多平台的需求,会有一些性能瓶颈。Impeller专门为 Flutter优化的渲染引擎,聚焦 Flutter 的 UI 渲染性能,提升渲染效率和跨平台体验,尤其是低端设备
widget参数,为什么用final
widget本质是无状态的,不可变的配置对象。
保障element的复用机制
flutter决定是否需要更新UI的判断:
Element.canUpdate
bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType &&
oldWidget.key == newWidget.key;
// ✅ 不比较参数值,仅依赖类型和Key
}
flutter中组件的复用,key
在 Widget 树的复用机制中,Key 主要用于标识 Widget,帮助框架判断是否复用现有的 Element。
无状态组件---封装独立 Widget,通过参数配置
有状态组件---GlobalKey+StatefulWidget
列表项复用---ValueKey + 稳定 ID:flutter4.0后,自动使用ObjectKey(index),减少重建,业务方最好定义key
逻辑共享---Mixin 或组合模式
性能敏感场景---AutomaticKeepAlive+RenderObject 复用
遵循Key最小化
不需要key的:静态、无状态、不涉及复用的组件
必须使用key的:列表、条件渲染、动态组件(避免状态丢失)
column中存放Listview,会有什么问题,怎么解决
Column 是一个垂直排列的容器,它的大小默认是包裹内容的,而 ListView 是一个可滚动的列表,默认会尽可能扩展以填充可用空间。当把 ListView 放在 Column 里时,Column 会尝试根据 ListView 的大小调整自己,但 ListView 在未指定高度的情况下会无限扩展,导致 Column 的高度也无限,这会触发溢出错误,因为 Column 在没有足够约束的情况下无法确定高度。
解决方案:1-限制 ListView 的高度;2-SingleChildScrollView包裹 Column,Expaned+ListView;3-CustomScrollView+SliverList
怎么获取组件的大小?
组件的大小,是在布局完成后的渲染阶段
1--GlobalKey + RenderBox;2--WidgetsBinding的addPostFrameCallback;3--LayoutBuilder(获取的是父容器的约束)
优先使用 Flutter 4.0 的RenderBox.onSizeChanged,在StatefulWidget中通过GlobalKey绑定,实现精准的尺寸监听。对于静态组件,addPostFrameCallback是最简单的方案;对于动态变化场景,结合AnimatedRenderObjectWidget和onSizeChanged实现高效更新。
LayoutBuilder可靠吗?
通过父容器的Constrains来传递可用空间,因此跟父容器有关。
固定宽度的父容器、Expanded的父容器---都可靠
可滚动的父容器--不一定
无约束的父容器--不可靠
skia的渲染后端(backends)有两种,一种使用CPU进行软件渲染,也称software rendering,一种是利用GPU进行硬件加速渲染,具体使用哪种方式取决于运行环境和配置。
CPU 渲染
将图形绘制到位图(Bitmap)中。SkBitmap bitmap;SkCanvas canvas(bitmap);
特点:不依赖 GPU,适用于没有 GPU 或 GPU 不可用的环境;灵活性高,可以在所有平台上运行;性能较低,尤其是在处理复杂图形或高分辨率时。
使用场景:低端设备或嵌入式系统(平台不支持GPU);离屏渲染(如生成图片、位图 或 PDF);测试和调试;低功耗模式下为了节省电量。
GPU 渲染
使用 GPU 进行硬件加速渲染,通过图形 API(如 OpenGL、Vulkan、Metal)将图形直接绘制到屏幕上。
特点:性能高,适合处理复杂图形和高分辨率渲染;依赖 GPU 和图形驱动,需要平台支持;需要额外的配置和管理(如上下文创建、资源释放)。
使用场景:高性能应用(如 Flutter、Chrome);实时渲染(如游戏、动画)。
Flutter 使用 Skia 作为其渲染引擎,默认情况下会优先使用 GPU 进行硬件加速渲染。
Widget 树 -> RenderObject 树 -> 通过 Skia 渲染为像素【如果支持 GPU,Skia 使用 GPU 后端(如 OpenGL 或 Metal)进行渲染】【如果不支持 GPU,Skia 回退到 CPU 渲染】
如何判断skia是否使用GPU?
运行flutter项目,查看日志输出
flutter: GPU rendering is enabled. --- 使用 GPU 渲染
flutter: Software rendering is enabled. --- 使用 CPU 渲染
9、swift值类型、引用类型
值类型:如结构体、枚举,String等,实例分配在栈中,赋值和传递过程中,会创建一份新数据,修改互相不影响,内存管理相对简单,超过作用域就会自动销毁
引用类型:类,栈上的指针+堆上的对象,赋值和传递过程中,传递的是地址,修改会互相有影响,内存管理稍微复杂,使用引用计数进行内存管理
10、swift 函数的执行方式
静态调用:编译时就可以确定要调用的方法,直接通过函数指针调用,函数指针存放在__text段,eg:值类型(结构体属于值类型,内部是不存放方法的)
动态调用:
- 函数表sil_vtable,每个类有一张虚函数表。子类的虚函数表中有 子类的方法+父类的方法,extension的方法,子类是不会继承的。
- class中,final修饰的方法,不会放在函数表中,直接静态调用
protocol呢?sil文件中,sil_witness_table,协议见证表,引用文章一篇 iOS-Swift 独孤九剑:八、协议的本质
11、swift
final修饰类时,这个类是不可以被继承的,防止开发者创建其子类,从而保证类的设计和行为不会被意外修改;修饰方法和属性时,代表不能被子类重写
@objc,代表要将方法暴露给oc,调度方式是 函数表
@dynamic,代表属性或方法的实现,将由运行时提供,修饰属性时,编译器不会自动生成存取方法。单独使用,调度方式是 函数表
@objc+@dynamic,调度方式是消息机制
12、dispatch_group_wait dispatch_group_notify
都是用于监控dispatch_group中任务的完成状态
不同点
dispatch_group_wait是一个同步方法,会阻塞当前线程,直到所有任务完成或到达超时时间
// 等待组内任务完成,这里设置为一直等待
long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
if (result == 0) {
NSLog(@"All completed.");
} else {
NSLog(@"Timed out");
}
dispatch_group_notify是一个异步方法,不会阻塞当前线程,所有任务完成后,会将代码块放在指定队列中执行
13、DNS HTTPDNS
传统DNS解析,从本地DNS服务器-根域名服务器-顶级域名服务器逐级查找,可能涉及多个环节,存在被劫持的风险。
HTTPDNS,基于http协议的域名解析技术,直接向httpdns服务器发送请求,httpdns利用自身的ip地址库,智能调度算法,结合用户的位置、运营商信息等,提供最优质的ip地址。
14、设计模式6大原则
单一职责原则:一个类只做一件事,eg:UIView和CALayer
开闭原则:对修改关闭,对扩展开放,考虑后续扩展性,尽量不在原有基础上修改
接口隔离原则:多个专门的协议,eg:UITableviewDelegate + UITableViewDataSource
依赖倒置原则:抽象不依赖具体的实现,具体实现可以依赖于抽象
里氏替换原则:父类可以被子类无缝替换,且原有的功能不受任何影响 如:KVO
迪米特法则:一个对象应当对其他对象尽可能少的了解,实现高聚合、低耦合
15、离屏渲染
画家算法
原因是:涉及到多个图层,需要中间状态去做最后的合成操作。
计算机是不能多张图片叠加后一起圆角的。
bgcolor + border + image =》 是不会造成离屏渲染的,不涉及到最后的合成,只需要逐层渲染
光栅化:采用缓存机制,bitmap形式保存,一定会触发离屏渲染,至少一次,只能缓存100ms
遮罩mask:单独一层layer,需要混合计算
阴影:shadowOffset,根据画家算法,需要先绘制阴影,再画本体,然而阴影的位置,依赖本体,所以涉及离屏渲染。如果是通过shadowPath,则不会触发,因为预先告诉了CoreAnimation依赖的几何形状,不需要依赖本体
圆角:ios9.0之后,cornerradius需要配合maskToBounds
只有imgView4会触发,多图层+maskToBounds
16、以前写过投屏,被问起了。
投屏功能,有多种方案,如 苹果的airplay,dlna等,还有现成成熟的乐播投屏。airplay有封闭圈,所以当时使用的是dlna。dlna是一个组织,定义了一套由基础协议(eg:http、udp、UPnP、ssdp等)组成的标准,应用范围广。
服务描述文件:每个设备通过 XML 文件来描述自身提供的服务、服务的操作方法以及相关参数
upnp协议:通用即插即用协议,加入网络后,可自动被发现。重要组成 ssdp,SSDP 主要负责设备与服务的发现,让网络中的设备能自动发现彼此及所提供的服务。它在 UPnP 框架里就像 “侦查兵”,为设备之间后续的通信与交互打基础。比如家庭网络中,智能电视和投屏设备间,就是通过 SSDP 来找到对方,为后续投屏功能的实现创造条件。
工作原理
设备宣告:新设备接入网络时,会向特定的多播地址(如 239.255.255.250:1900)发送含有自身信息和提供服务详情的消息,即 “NOTIFY” 消息。这些信息包含设备类型、唯一标识、设备描述 URL 等,好比设备在网络里 “大声宣告” 自己的存在和特点。
搜索请求与响应:其他设备若要查找特定类型设备或服务,会发送 “M - SEARCH” 消息。网络中符合条件的设备收到后,用 “HTTP/1.1 200 OK” 响应消息回复,告知请求方自身的详细信息,方便请求方了解网络中可用的设备和服务。
设备间的控制和数据交互,是通过发送 SOAP(简单对象访问协议)实现。
投屏流程
- 手机端搜索设备
启动GCDAsyncUdpSocket(udp端口发送请求),发送数据,
M-SEARCH * HTTP/1.1
HOST: 239.255.255.250:1900 //IPv4下的多播地址和端口号
MAN: "ssdp:discover"
MX: 3
ST: urn:schemas-upnp-org:service:AVTransport:1
USER-AGENT: iOS UPnP/1.1 mccree/1.0
2. 设备们响应
HTTP/1.1 200 OK
Cache-control: max-age=1800
Date: Thu, 16 Feb 2017 09:09:45 GMT
EXT:
LOCATION: http://10.2.9.152:49152/TxMediaRenderer_desc.xml //设备描述文档地址
Server: search target
USN: uuid:3c970e3c0c0d0000_MR::upnp:rootdevice //composite identifier for the advertisment
BOOTID.UPNP.ORG: 1487062102 //number increased each time device sends an initial announce or an update message
CONFIGID.UPNP.ORG: 499354 //number used for caching description information
SEARCHPORT.UPNP.ORG: number identifies port on which device responds to unicast M-SEARCH
ST: upnp:rootdevice //device type
这份数据仅仅是发现的设备,具体设备提供了哪些服务,需要 启动http,去 LOCATION 中 获取数据。
NSURL *URL = [NSURL URLWithString:location];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:5.0];
request.HTTPMethod = @"GET";
[[[NSURLSession sharedSession] dataTaskWithRequest:request
结果是按照xml的格式返回,xml中有详细的信息,设备名称,服务列表等,我们需要解析xml。
- 动作
手机端 发送动作
NSURLSession *session = [NSURLSession sharedSession];
NSURL *url = [NSURL URLWithString:[action getPostUrlStrWith:_model]];
NSString *postXML = [action getPostXMLFile];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
[request addValue:@"text/xml" forHTTPHeaderField:@"Content-Type"];
[request addValue:[action getSOAPAction] forHTTPHeaderField:@"SOAPAction"];
request.HTTPBody = [postXML dataUsingEncoding:NSUTF8StringEncoding];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request
17、oc中的 enum 和swift 中的 enum的区别
oc:本质都是整型
typedef NS_ENUM(NSInteger, Day) {
Monday,
Tuesday
}
swift :枚举值可以是很多类型,如string,浮点类型、整型等,还可以有方法,计算属性,关联值,原始值,也可以遵守协议等。
关联值:可以为成员 关联其他类型的数据,需要根据类型动态分配
enum Shape {
case rectangle(width: Double, height: Double)
case circle(radius: Double)
}
原始值:枚举的实例对象直接存储值,不需要再额外分配空间
enum Code: Int {
case one = 1
case two = 2
}
18、swift中如何解析json?使用Codable+JSONDecoder,无需三方
Codable协议包括Decodable和Encodable,如果仅解析,可以只遵循Decodable,如果 需要自定义 属性名,需要 使用 CodingKeys枚举 自定义 映射关系
struct User: Codable {
let name: String?
}
let json = "*****"
let decoder = JSONDecoder()
do {
let user = try decoder.decode(User.self, from: json)
} catch {
}
19、flutter中,如果异步10个请求,最后如何统一?使用Future.wait
// 定义10个URL
List<String> urls = [
'https://example.com/api1',
'https://example.com/api2',
// ......
'https://example.com/api10'
];
// 创建10个异步请求的Future列表
List<Future<http.Response>> futures = urls.map((url) => http.get(Uri.parse(url))).toList();
// 等待所有异步请求完成
List<http.Response> responses = await Future.wait(futures);