Flutter相关
如何局部刷新某个控件而不重走build方法?
如何减少安装包的大小
状态管理的选择
桥接 与原生的交互
Flutter 定义了三种不同类型的 Channel,它们分别是?
- BasicMessageChannel:
这是最常用的一种 Channel 类型。它允许 Flutter 与宿主平台之间传递简单的消息。它的消息可以是任意类型的数据,但必须是序列化的,例如字符串、数字、布尔值等。通过 BasicMessageChannel,Flutter 和宿主平台可以相互发送消息,并进行双向通信。 - MethodChannel:
MethodChannel 用于在 Flutter 和宿主平台之间进行方法调用。它允许 Flutter 调用宿主平台上的原生方法,并获取返回结果。方法调用的参数和返回值必须是序列化的,例如字符串、数字、布尔值等。通过 MethodChannel,Flutter 可以与宿主平台进行复杂的交互,执行原生功能。 - EventChannel:
EventChannel 用于在 Flutter 和宿主平台之间进行事件的传递。它支持从宿主平台向 Flutter 发送事件通知,Flutter 可以监听这些事件并做出相应的处理。EventChannel 主要用于实时数据流的传输,例如传感器数据、网络状态变化等。通过 EventChannel,Flutter 可以与宿主平台进行实时数据交换。这些 Channel 类型是 Flutter 与宿主平台进行通信的重要工具,它们使得 Flutter 应用程序能够与原生功能进行无缝集成,并实现更复杂的交互和数据传输。
三棵树
信号量?
APP崩溃率? 野指针错误如何定位排查?
APP日活量大概多少?
网络优化?
如何优化内存
死锁有什么优化?递归锁了解么?
@sythesize底层实现
崩溃有哪些?,线上如何处理崩溃
char int 如何实现字节对齐
App闪退的过程
二进制重排的原理是什么?二进制重排优化了什么?为什么耗时?哪一步最耗时?
页面秒卡的时间点怎么确定的
组件化有什么优势?大概什么过程?你在里面充当什么角色?保证组件能正常运行你们采取了什么策略。
找一个可能有环链表进入环的首个链表
编译的整个过程
网络请求的整个过程是什么样子的
HTTPS了解多少?
缓存
OOM
hook_objc崩溃如何处理
短视频秒播优化
gif图内存优化
Texture内部原理
开屏广告
如何绕过内购?
SDWebImage相关知识点
SDWebImage内部实现原理步骤
SDWebImage源码解析
SDWebImage源码解读
SDWebImage知识点
用队列实现栈的功能
APPStore拒绝上架的原因大概有哪些?
- 安全性问题
苹果非常重视应用的安全性,因此在审核应用时,会特别关注应用的安全性问题。如果应用存在安全隐患,就会被拒绝上架。这些安全隐患包括但不限于:未经授权的访问用户数据、未经授权的网络访问、使用过期的API等等。
- 功能性问题
应用的功能性问题也是被拒绝上架的常见原因。如果应用存在功能性问题,比如崩溃、卡顿、无响应等等,就会被拒绝上架。这些问题可能会导致用户无法正常使用应用,影响用户体验。
- 用户体验问题
应用的用户体验也是苹果审核的重点之一。如果应用的用户体验不好,比如界面设计不合理、操作不便捷、用户反馈不及时等等,就会被拒绝上架。这些问题可能会导致用户不愿意使用应用,影响应用的下载量和用户满意度。
- 版权问题
应用存在版权问题也是被拒绝上架的常见原因。如果应用包含未经授权的版权内容,比如图片、音乐、视频等等,就会被拒绝上架。这些问题可能会导致开发者面临法律诉讼,影响应用的声誉和用户信任度。
- 推广问题
应用的推广问题也是被拒绝上架的常见原因。如果应用的推广方式不合法或者存在欺骗用户的嫌疑,就会被拒绝上架。这些问题可能会导致开发者面临法律诉讼,影响应用的声誉和用户信任度。
在提交应用到App Store上架时,开发者需要仔细检查应用的安全性、功能性、用户体验、版权情况和推广方式等方面,确保应用符合苹果的要求。如果应用被拒绝上架,开发者需要及时查找问题并进行修复,以便重新提交审核。同时,开发者还需要保持对苹果审核规定的了解,并及时更新应用以符合最新的审核要求。
静态库和动态库的区别
动态库(Dynamic Link Library)和静态库(Static Link Library)都是可重用的代码库,它们之间的主要区别在于:
- 编译方式不同:
静态库是在编译时将库的代码打包到可执行程序中,因此生成的可执行程序包含了所有用到的库函数的代码。这样,当程序被调用时,需要使用哪些库函数就直接从可执行文件中取出来使用。因为代码打包进了可执行程序中,因此静态库的生成通常需要在代码的编译阶段进行。
动态库则是在运行时动态加载到程序中的,因此生成的可执行文件并不包含库函数的实现代码,而只是引用了动态库的接口。当程序调用到该库函数时,操作系统会将该函数从动态库文件中加载到内存中供程序运行使用。这样一来,程序的可执行文件会比静态库生成的可执行文件小很多。因为代码加载是在程序运行时进行的,所以动态库的链接通常是在程序运行之前进行。
- 内存使用方式不同:
由于静态库的代码被打包进了可执行程序中,所以在程序运行时,静态库中的代码被复制到了程序使用的内存中,并一直驻留在内存中使用,因此不需要占用额外的内存空间。
而动态库的代码在程序运行时才会被加载到内存中,因此动态库的代码实现被复制进内存,会占用额外的内存空间。但是与静态库相比,动态库的内存使用方式具有更好的空间和性能优势,因为多个程序可以共享同一个动态库,而不需要重复加载相同的库文件,从而减少了系统的内存占用。
- 更新和维护方式不同:
静态库的代码被打包成可执行程序的一部分,因此静态库的更新和维护需要重新进行编译和部署,才能让所有使用了该静态库的程序都能够得到更新的代码。
动态库可以独立于程序进行更新,因为动态库作为一个单独的文件存在于系统中,可以被多个程序共享。因此,当需要更新动态库时,只需要替换掉旧的动态库文件,不需要重新编译和部署所有使用了该动态库的程序。
因此,如果需要多个程序共享同一个库,或者需要较少的内存占用,则使用动态库可能更为合适。如果需要保持部署和更新的稳定性,则静态库可能更为适合。
class_ro_t 和 class_rw_t区别
Category 为什么不能添加实例变量
ARC模式下,如何获取对象的引用计数
1.使用CFGetRetainCount
CFGetRetainCount((__bridge CFTypeRef)(obj))
2.使用KVC
[obj valueForKey:@"retainCount"]
优先级反转
优先级反转(Priority Inversion)是指在多线程中,由于共享资源的访问导致低优先级任务拥有高优先级任务所需的资源,从而使高优先级任务被迫等待低优先级任务执行完毕,使得高优先级任务的性能降低。
简单来说,就是在多线程的情况下,高优先级任务被低优先级任务“挟持”,因为低优先级任务在占用高优先级任务所需的资源。
优先级反转的危害:
- 系统响应时间变慢,高优先级任务因为等待低优先级任务占用资源而被延迟;
- 系统实时性能受到影响,高优先级任务无法在预期的时间内得到执行,会影响系统的实时性,使系统难以满足对时间要求严格的实时应用场景;
- 死锁,即多个任务之间发生相互等待,导致程序无法继续往下执行。
属性关键字
TCP与UDP区别?
1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接。TCP连接是通过三次握手建立的,握手是启动和确认连接的过程。 建立连接后,即可开始数据传输。 传输后,通过关闭所有已建立的虚拟电路来终止连接。
而UDP使用一种简单的传输模型,而不使用隐式的握手对话来保证可靠性,排序或数据完整性。因此,UDP提供了不可靠的服务,数据报可能会乱序到达,出现重复或丢失而不会发出通知。 UDP假定不需要在应用程序中执行错误检查和纠正,从而避免了在网络接口级别进行此类处理的开销。与TCP不同,UDP与包广播(发送到本地网络上的所有包)和多播(发送到所有订户)兼容。
2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保 证可靠交付
3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的
UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
5、TCP标头大小20字节;UDP的标头大小,只有8个字节
6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道
7、TCP传输数据稳定可靠,适用于对网络通讯质量较高的场景,需要准确无误的传输给对方。比如常用于网页浏览,电子邮件和文件传输等。 而UDP传输速度较快,但是可能产生丢包,所以适用于对实时性要求较高,但是对于少量丢包没有太大要求的场景,通常用于域名查询,IP语音通话,视频直播等。
为什么要在主线程操作UI?
UIKit不是线程安全的: UIKit这样一个庞大的框架,将其所有属性都设计为线程安全是不现实的,这可不仅仅是简单的将nonatomic改成atomic或者是加锁解锁的操作,还涉及到很多的方面:
1、假设能够异步设置view的属性,那我们究竟是希望这些改动能够同时生效,还是按照各自runloop的进度去改变这个view的属性呢?
2、假设UITableView在其他线程去移除了一个cell,而在另一个线程却对这个cell所在的index进行一些操作,这时候可能就会引发crash。
3、如果在后台线程移除了一个view,这个时候runloop周期还没有完结,用户在主线程点击了这个“将要”消失的view,那么究竟该不该响应事件?在哪条线程进行响应?
为什么要在主线程操作UI?可以概括为以下三点:
1、 UIKit并不是一个线程安全的类,UI操作会涉及到渲染以及访问各种View对象的属性,异步操作下会存在读写问题,而为其加锁则会耗费大量资源并拖慢运行速度。
2、因为整个程序的起点UIApplication是在主线程进行初始化,所有的用户事件都是在主线程上进行传递(如点击、拖动),所以view只能在主线程上才能对事件进行响应。
3、在渲染方面由于图像的渲染需要以60帧的刷新率在屏幕上 同时更新,在非主线程异步化的情况下无法确定这个处理过程能够实现同步更新。
仔细思考,似乎能够了解多线程处理UI并没有给我们开发带来更多的便利,很容易得出一个结论: “我在一个串行队列对这些事件进行处理就可以了。” 苹果也是这样想的,所以UIKit的所有操作都要放到主线程串行执行。
事件传递以及响应过程?
iOS事件传递以及响应综合分析
事件传递以及响应链
iOS | 响应链及手势识别
什么是异步渲染?
在iOS开发中,异步渲染是指在绘制和显示用户界面时使用异步的方式进行操作,以提高界面的响应性能和流畅度。iOS中的界面渲染是在主线程上进行的,而主线程还负责处理用户交互、动画和其他任务,如果在主线程上执行耗时的渲染操作,会导致界面卡顿和不流畅的用户体验。
为了解决这个问题,iOS提供了一些异步渲染的技术和机制:
异步绘制(Async Drawing):在绘制复杂的视图或图层时,可以将绘制操作放在后台线程上进行,以避免阻塞主线程。通常使用Core Graphics或Core Animation的异步绘制API来实现。这样可以确保主线程保持响应,并且在绘制完成后,将绘制结果显示在屏幕上。
异步布局(Async Layout):视图的布局计算通常是在界面更新周期中的一个重要步骤。当视图层次较为复杂时,布局计算可能会消耗大量的时间。为了避免阻塞主线程,可以将布局计算放在后台线程上进行,然后在主线程上应用布局结果。这样可以提高界面的响应速度和流畅度。
异步加载图片(Async Image Loading):在加载和显示图片时,可以使用异步加载的方式,以避免阻塞主线程。可以使用诸如NSURLSession、SDWebImage、Alamofire等网络库来异步下载图片,并在下载完成后将其显示在界面上。这样可以确保界面的响应性,并避免因网络请求而导致的界面卡顿。
异步绘制文本(Async Text Rendering):在绘制大量文本时,文本渲染可能会成为性能瓶颈。为了提高性能,可以将文本渲染操作放在后台线程上进行,并在渲染完成后将结果显示在界面上。可以使用Core Text或TextKit等技术来实现异步文本渲染。
通过使用异步渲染技术,可以将耗时的操作转移到后台线程上进行,从而保持主线程的响应性和流畅度。这对于处理复杂的界面、大量的数据或者网络请求等场景非常有用,可以提高iOS应用程序的性能和用户体验。
Swift中的方法调用有哪些形式?
在Swift中,方法调用有以下几种形式:直接派发(Direct Dispatch)、函数表派发(Table Dispatch)、消息机制派发(Message Dispatch)以及动态派发(Dynamic Dispatch)。
这么多不同的调用方式存在的原因是为了在不同的编程场景中提供灵活性和性能优化。
直接派发(Direct Dispatch):直接派发是在编译时确定方法的调用目标,然后直接调用目标方法的机制。这种方式的优势是效率高,因为它不需要运行时的查找或动态决策。直接派发适用于非虚方法(non-virtual method)和值类型,因为它们不涉及继承和多态的特性。
函数表派发(Table Dispatch):函数表派发是通过在对象中维护一个函数表(或虚函数表)来确定方法的调用目标的机制。函数表是一个包含了对象的虚方法的指针数组。当调用方法时,编译器会根据对象的类型查找函数表中相应的方法,并进行调用。函数表派发适用于类的实例方法,因为类支持继承和多态。它可以实现动态的方法查找,允许子类重写父类的方法。
消息机制派发(Message Dispatch):消息机制派发是一种动态派发机制,它通过在运行时发送消息并根据接收者的实际类型来确定方法的调用目标。消息机制派发适用于与Objective-C运行时特性交互的情况,例如在Objective-C类中调用Swift方法或使用@objc关键字标记的方法。
动态派发(Dynamic Dispatch):动态派发是一种特殊的动态派发形式,它根据对象的实际类型来确定方法的调用目标。它支持继承和多态,允许子类重写父类的方法,并根据实际类型进行调用。在Swift中,类的实例方法默认情况下是动态派发的。
这些不同的方法调用形式在性能、灵活性和语言特性上有所区别。直接派发是最高效的,适用于非虚方法和值类型。函数表派发支持继承和多态,适用于类的实例方法。消息机制派发适用于Objective-C运行时特性和与Objective-C代码的交互。动态派发支持继承和多态,通过在运行时根据实际类型确定方法调用目标。
在选择方法调用形式时,应根据具体需求和场景进行考虑。通常情况下,直接派发是最佳选择,因为它具有最佳的性能。函数表派发和消息机制派发适用于需要继承、多态或与Objective-C代码的交互的情况。动态派发适用于需要在运行时根据实际类型确定方法调用目标的情况。
需要注意的是,Swift编译器会根据上下文和代码结构自动选择最佳的方法调用形式,以提供最佳的性能和行为。
Swift中struct和class有什么区别?
在Swift中,struct(结构体)和class(类)都是用于创建自定义类型的关键字。它们具有以下区别:
值类型 vs 引用类型:结构体是值类型,而类是引用类型。当你创建一个结构体实例并将其赋值给一个变量或常量,实际上是将该值复制到新的变量或常量中。而当你创建一个类的实例并将其赋值给一个变量或常量,实际上是将一个引用指向该实例。这意味着结构体的赋值是对值的拷贝,而类的赋值是对引用的拷贝。
内存管理:由于结构体是值类型,它们在栈上分配内存,并在超出其作用域时被自动释放。而类是引用类型,它们在堆上分配内存,并使用引用计数(reference counting)来管理内存。当没有任何引用指向一个类实例时,内存会自动被释放。
继承:类支持继承,可以通过派生子类来继承父类的特性。而结构体不支持继承,不能派生出子结构体。
类型转换:类之间可以进行类型转换,包括向上转型(upcasting)和向下转型(downcasting)。而结构体之间没有继承关系,所以没有类型转换的需求。
可变性:结构体实例是值类型,即它们的属性在实例化后不能被修改,除非该实例被声明为可变(使用var关键字)。而类实例是引用类型,它们的属性可以在任何时候被修改,即使该实例被声明为常量(使用let关键字)。
使用场景:结构体适合表示相对简单的数据结构,例如坐标点、时间日期等。它们通常用于传递和复制数据。类适合表示更复杂的对象,具有状态和行为,并且可能在多个地方共享和修改。
总结起来,结构体和类在Swift中具有不同的特性和用途。选择使用哪种类型取决于你的需求和设计考虑。如果你只需要简单的数据封装和传递,或者希望避免引用类型的特殊行为(如共享和修改),则可以选择结构体。如果你需要更复杂的对象、继承、类型转换和可变性等特性,则应选择类。
保证线程安全的方式?
dispatch_barrier_async 和 dispatch_group
读写锁(多读单写)
多读单写的意思就是:可以多个读者同时读取数据,而在读的时候,不能去写入数据。并且,在写的过程中,不能有其他写者去写。即读者之间是并发的,写者与读者或其他写者是互斥的。
iOS 的多读单写指的是多个线程可以同时读取共享的数据,但是只有一个线程能够写入数据。这是为了保证数据的一致性和避免竞争条件的出现。
方式一:
可以使用 GCD 的并发队列来实现多读单写。具体实现步骤如下:
1.定义一个并发队列和一个串行队列,用于处理读操作和写操作,分别为 readQueue 和 writeQueue。
dispatch_queue_t readQueue = dispatch_queue_create("com.example.readQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t writeQueue = dispatch_queue_create("com.example.writeQueue", DISPATCH_QUEUE_SERIAL);
2.定义一个全局变量,用于存储共享数据。
NSMutableArray *sharedData = [NSMutableArray array];
3.实现读操作,使用 readQueue 中的异步方法来执行读取操作,这样多个读操作可以同时进行。
- (void)readDataWithCompletion:(void (^)(NSArray *))completion {
dispatch_async(readQueue, ^{
NSArray *dataArray = [NSArray arrayWithArray:sharedData];
dispatch_async(dispatch_get_main_queue(), ^{
completion(dataArray);
});
});
}
4.实现写操作,使用 writeQueue 中的同步方法来执行写入操作,这样保证只有一个线程能够写入数据
- (void)writeData:(id)data {
dispatch_sync(writeQueue, ^{
[sharedData addObject:data];
});
}
通过以上步骤,我们实现了多读单写的功能,多个线程可以同时读取共享数据,但是只有一个线程能够写入数据。这样可以保证数据的一致性和避免竞争条件的出现。注意在 Objective-C 中需要使用 block 来传递回调函数,以及使用 dispatch_get_main_queue() 来将结果回调到主线程。
方式二:
用dispatch_barrier_sync(栅栏函数)去实现。
1、读并发执行,写同步执行
2、读和写是互斥的,读是需要等待结果返回的
3、写之前要等所有读操作完成之后开始,写不用等结果
上面的条件要求中:
1、写之前要等所有读操作完成之后开始":这个就是栅栏函数,"不用等结果":async
2、读并发执行":就是并发队列,"读要等结果":就是同步sync
3、栅栏函数起作用的条件就是在同一线程
@interface TKReadWhiteSafeDic() {
// 定义一个并发队列
dispatch_queue_t concurrent_queue;
// 用户数据中心, 可能多个线程需要数据访问
NSMutableDictionary *userCenterDic;
}
@end
// 多读单写模型
@implementation TKReadWhiteSafeDic
- (id)init {
self = [super init];
if (self) {
// 通过宏定义 DISPATCH_QUEUE_CONCURRENT 创建一个并发队列
concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
// 创建数据容器
userCenterDic = [NSMutableDictionary dictionary];
}
return self;
}
- (id)objectForKey:(NSString *)key {
__block id obj;
// 同步读取指定数据
dispatch_sync(concurrent_queue, ^{
obj = [userCenterDic objectForKey:key];
});
return obj;
}
- (void)setObject:(id)obj forKey:(NSString *)key {
// 异步栅栏调用设置数据
dispatch_barrier_async(concurrent_queue, ^{
[userCenterDic setObject:obj forKey:key];
});
}