iOS客户端社招两年工程师面试

Minimax一面

  1. static是什么,和class有什么不同? 答:声明一个静态属性或者函数,我们常常使用值类型的static修饰符。下面就是一个结构体的例子: struct Sun { static func illuminate() {} } 对类来说,使用static 或者class修饰符,都是可以的。它们使用后的效果是一样的,但是本质上是不同的。能解释一下为什么不同吗? 答案: static修饰的属性或者修饰的函数都不可以重写。但是使用class修饰符,你可以重写属性或者函数。 当static在类中应用的时候,static就成为class final的一个别名。
  2. Class 和 Struct 的区别
  • 类是引用类型, 结构体为值类型
  • 结构体不可以继承
  • 值类型被赋予给一个变量、常量或者被传递给一个函数的时候,其值会被拷贝
  • 引用类型在被赋予到一个变量、常量或者被传递到一个函数时,其值不会被拷贝。因此,引用的是已存在的实例本身而不是其拷贝
  1. 三个线程异步执行,想要获取三个线程的结果再执行下一段代码,该怎么办? 用DISPATCHQUEUE
  2. 三种闭包
  3. 逃逸闭包,尾随闭包,自动闭包 当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 @escaping,用来指明这个闭包是允许“逃逸”出这个函数的。非逃逸闭包、逃逸闭包,一般都是当做参数传递给函数非逃逸闭包:闭包调用发生在函数结束前,闭包调用在函数作用域内逃逸闭包:闭包有可能在函数结束后调用,闭包调用逃离了函数的作用域,需要通过@escaping声明 尾随闭包是最后一个闭包,可以自动类型推断
  4. 如何解决循环引用? weak 和 unkowned 的引入是为了解决由 strong 带来的循环引用问题。简单来说,就是当两个对象互相有一个强指向去指向对方,这样导致两个对象在内存中无法释放。
  5. weak和unknowned的区别? weak 和 unowned 的使用场景有如下差别:
  • 当访问对象时该对象可能已经被释放了,则用 weak。比如 delegate 的修饰。
  • 当访问对象确定不可能被释放,则用 unowned。比如 self 的引用。
  • 实际上为了安全起见,很多公司规定任何时候都使用 weak 去修饰。 挂了,方向不符合,给了一道括号匹配代码题简单做做

收集iOS题8. Flutter iOS的app framework怎么瘦身,做了哪些(二进制瘦身,还是资源删减,是业务还是flutter层)?Flutter iOS的app framework瘦身可以从以下几个方面进行:

  1. 二进制瘦身:通过移除未使用的代码和库,减小二进制文件的大小。可以使用Xcode的Build Settings中的Strip Linked Product选项来开启二进制瘦身。
  2. 资源删减:检查项目中的图像、音频和其他资源文件,移除未使用或不必要的资源。可以使用Xcode的Images.xcassets来管理和优化图像资源。
  3. Dart和Flutter层瘦身:在Dart和Flutter代码中,移除未使用的类、函数和方法,以及不必要的依赖包。可以使用Dart的pubspec.yaml文件来管理和优化依赖包。
  4. 使用Flutter的Tree Shaking特性:在编译时,Flutter会自动移除未使用的代码,从而减小最终的二进制文件大小。确保在编译时开启Tree Shaking功能。
  5. 使用Flutter的Code Splitting特性:将应用程序拆分成多个模块,按需加载,从而减小初始安装包的大小。可以使用Flutter的--split-debug-info=<path>命令来实现代码拆分。
  6. 使用Flutter的懒加载特性:在需要时才加载某些资源,从而减小应用程序的内存占用。可以使用Flutter的Future.delayed方法来实现懒加载。
  7. 优化网络请求:减少不必要的网络请求,合并多个请求为一个请求,从而减小应用程序的数据流量。可以使用Flutter的http库来实现网络请求的优化。
  8. 使用Flutter的缓存策略:合理使用缓存,避免重复加载相同的资源,从而减小应用程序的数据流量。可以使用Flutter的ImageCache类来实现图像资源的缓存。 总之,Flutter iOS的app framework瘦身需要从多个方面进行优化,包括二进制瘦身、资源删减、代码优化等,以减小应用程序的大小和内存占用。
  9. Flutter渲染树(三个树)介绍和原理 Flutter的渲染树包括Widget、Element和RenderObject这三个树形结构,它们共同完成UI的构建和渲染工作。具体介绍如下:
  10. Widget树:
  • Widget是Flutter用户界面的不可变描述。它们是UI组件的配置数据结构,描述了UI的部分内容。
  • Widget树是由Widget对象构成的层次结构,它定义了应用程序的用户界面。
  • Flutter页面由各种Widget组合而成,每个Widget都是不可变的,即它的所有属性都必须为final类型。
  1. Element树:
  • Element是Widget的一个实例化对象,它通过Widget的createElement()方法创建,并保存了上下文信息。
  • Element作为Widget树和RenderObject树之间的桥梁,持有对应的Widget和RenderObject的引用,并负责配置Widget在视图树中的位置。
  1. RenderObject树:
  • RenderObject用于应用界面的布局和绘制,它包含了元素的大小、布局等信息。
  • RenderObject树是实际执行布局和绘制操作的结构,它根据Widget的属性进行layout和绘制。

三棵树的协同工作原理:

  • 当应用程序启动时,Flutter会首先构建Widget树,然后遍历这个树,为每个Widget创建对应的Element对象,形成Element树。
  • 接着,Flutter会调用每个Element的createRenderObject()方法来创建相应的RenderObject,从而形成RenderObject树。
  • 在UI更新过程中,如果Widget的配置发生变化,Flutter会比较新的Widget树与旧的Widget树,并通过Element树高效地确定哪些RenderObject需要更新,以最小化渲染工作量。 总的来说,这种设计使得Flutter能够有效地管理和更新UI,同时保持高性能的渲染过程。

腾讯一面10. 定义值类型和引用类型Swift有三种声明类型的方式:class,struct和enum。 它们可以分为值类型(struct和enum)和引用类型(class)。 它们在内存中的存储方式不同决定它们之间的区别:

  • 值类型存储在栈区。 每个值类型变量都有其自己的数据副本,并且对一个变量的操作不会影响另一个变量。
  • 引用类型存储在其他位置(堆区),我们在内存中有一个指向该位置的引用。 引用类型的变量可以指向相同类型的数据。 因此,对一个变量进行的操作会影响另一变量所指向的数据。
  1. Swift中字典的存储方式 有一个唯一的键值对,存储在哈希表当中,其中如果产生冲突,采用的是开放定址法(+1) https://juejin.cn/post/6932770154619076621
  2. http1.0和http2.0的区别 HTTP1.0和HTTP2.0在连接方式、多路复用、头部压缩以及服务器推送方面存在明显区别。具体来说: 支持长连接
  3. 连接方式:在HTTP1.0中,浏览器与服务器之间只保持短暂的连接,每次请求都需要建立一个新的TCP连接。而HTTP2.0基于SPDY协议,使用了二进制分帧机制,允许在一个单一的TCP连接上进行多路复用,即可以同时处理多个请求和响应,减少了因多次建立连接而产生的延迟和开销。
  4. 多路复用:HTTP1.0的请求是串行的,一个请求完成才能发送下一个请求,这导致了队头阻塞问题。而HTTP2.0通过多路复用技术解决了这一问题,可以并行地传输多个请求和响应,提高了页面加载速度。
  5. 头部压缩:HTTP2.0支持头部压缩,能够减少请求和响应头部的大小,进一步节省带宽,加快数据传输速度。而HTTP1.0不支持头部压缩。
  6. 服务器推送:HTTP2.0引入了服务器推送(Server Push)特性,使得服务器可以在客户端需要之前就主动发送数据,进一步提高了效率。HTTP1.0则没有这个功能。

总的来说,HTTP2.0相比HTTP1.0在连接方式等方面都有显著的提升。这些改进使得HTTP2.0在现代网络应用中更为高效和实用。13. HTTPS是通过TLS/SSL协议实现加密的。

首先,客户端和服务器进行握手,期间会交换支持的加密算法、协议版本等信息。然后,服务器会发送自己的数字证书给客户端,以证明自己的身份。这个证书通常由权威的证书颁发机构签发。

接下来是密钥交换阶段,客户端和服务器协商生成一个对称密钥,即"对话密钥"。这个密钥将用于后续的所有加密通信。在非对称加密中,服务器还会发送一个公钥给客户端,客户端使用这个公钥加密"预主秘钥"(Pre-Master Secret),然后发送给服务器。服务器使用私钥解密得到预主秘钥,双方基于此计算出相同的对话密钥。

最后,使用对话密钥对传输的数据进行加密和解密。因为对话密钥是对称的,所以加解密速度快,适合大量数据的加密传输。同时,TLS/SSL协议还保证了数据的完整性和防篡改性。

总的来说,HTTPS通过TLS/SSL协议实现了数据传输的加密,确保了网络通信的安全性。

  1. Swift的编译流程Swift的编译流程主要包括以下几个步骤:
  2. 源代码编写:开发者使用Swift语言编写源代码,这些源代码文件通常以.swift为扩展名。
  3. 编译成中间表示(IR):Swift编译器首先将Swift源代码编译成一种称为中间表示(Intermediate Representation,简称IR)的形式。IR是一种与平台无关的表示形式,它包含了源代码的抽象语法树(AST)和一些优化信息。
  4. 优化:在生成IR之后,编译器会进行一系列的优化工作,以提高代码的执行效率。这些优化可能包括去除未使用的代码、内联函数调用、循环展开等。
  5. 生成目标代码:优化后的IR会被进一步编译成目标平台的机器代码。这个过程可能会涉及到不同的后端编译器,取决于目标平台是iOS、macOS、Linux还是其他系统。
  6. 链接:如果项目中包含了多个编译单元或者使用了第三方库,编译器还需要将这些部分链接起来,形成最终的可执行文件。
  7. 调试信息生成:为了能够在后续的调试过程中使用,编译器还会生成调试信息。这通常需要在编译时加上特定的参数,如-g参数,以便在目标文件中包含必要的调试信息。
  8. 运行或打包:最后,编译完成的可执行文件可以直接运行,或者根据需要进行打包,以便于分发或部署。

总的来说,整个编译流程确保了Swift代码能够被高效地转换成可以在特定平台上运行的机器代码,同时提供了必要的调试支持。Swift的设计还注重内存安全性和原生错误处理,以提高应用的稳定性和安全性。15. 在Swift编程语言中,线程锁的实现方式是什么?请详细描述其工作原理和使用方法。在Swift编程语言中,线程锁可以通过多种方式实现,包括互斥锁(Mutex)、递归锁、读写锁以及使用DispatchQueue等。 线程锁的工作原理主要是通过控制对共享资源的访问来实现线程同步**。当一个线程获得锁时,它会阻止其他线程访问被锁定的代码区域或资源,直到该线程释放锁为止。这样可以防止多个线程同时修改同一份数据,从而避免数据竞争和不一致的问题。

要在Swift中使用线程锁,首先需要选择合适的锁类型。例如,如果需要保护的代码区域允许递归调用,那么应该选择递归锁。如果多个线程只需要读取共享数据而不进行写入,那么可以使用读写锁来提高效率。具体操作如下:

  • 互斥锁(Mutex):可以使用pthread_mutex_t类型的互斥锁,它在锁定时会阻塞当前线程,直到锁被释放。这种锁适用于保护临界区的代码,确保同一时间只有一个线程可以执行这部分代码。
  • 递归锁:Swift中的NSLock类可以实现简单的互斥锁,但它不是递归的。如果需要递归锁,可以考虑使用NSRecursiveLock类。
  • 读写锁:可以使用pthread_rwlock_t类型的读写锁,它允许多个读线程同时访问,但在写线程获取锁时会阻止其他所有线程的访问。
  • DispatchQueue:可以使用GCD提供的DispatchQueue来实现同步。通过创建串行队列,可以确保任务按顺序执行,从而避免竞争条件。此外,还可以使用dispatch_barrier_async函数在并行队列中创建屏障,以实现读写锁的功能。 总的来说,在使用线程锁时,需要注意正确配对加锁和解锁操作,并且确保加锁和解锁操作在同一个线程中进行。错误的使用线程锁可能导致死锁或线程阻塞等问题,因此在使用时应谨慎并遵循最佳实践。
  1. guard和if的区别 综上所述,虽然guard和if在功能上相似,但guard提供了一种更为优雅和简洁的方式来处理条件判断,尤其是在需要早期退出和处理Optional类型时。使用guard可以帮助编写更加清晰、易于维护的代码。
  2. 怎么检测野指针?
  3. 优先级反转的情况遇到过吗,怎么解决 挂了,考的排列组合题和环大小找到环题

小红书一面17. 主线程里加入三个串行线程会有什么影响?18. 串行队列和并行队列的区别19. 并发和并行的区别并发和并行是两种不同的任务执行方式,它们在概念上有所区别。以下是具体分析:

  • 并行(Parallelism):并行是指多个任务在同一时刻被多个执行单元同时处理。这通常需要多核处理器或多处理器系统来实现真正的同步执行。在并行处理中,每个任务都独立于其他任务运行,并且可以同时利用不同的CPU核心或处理器资源。
  • 并发(Concurrency):并发是指多个任务交替执行,但给人的感觉好像是同时进行的。并发主要是通过任务调度来实现的,即操作系统快速地在不同任务之间切换,使得每个任务都有机会执行。并发可以在单核处理器上实现,因为虽然任一时刻只有一个任务在执行,但通过快速切换,可以有效地模拟出多个任务同时运行的效果。 总的来说,并行关注的是任务的物理执行,而并发关注的是任务的逻辑调度。两者都是操作系统中重要的概念,用于提高系统资源的利用率和提升用户体验。
  1. block、代理和闭包的特性,区别
  2. ARC具体会在什么时候运行 编译和运行的时候
  3. 串行队列和同步队列会创造新的线程吗 队列(Queue)本身通常不直接创建线程,而是依赖于派发队列(Dispatch Queue)或线程池来管理和调度任务的执行。当将任务添加到队列中时,底层的派发机制会决定是否创建新的线程来处理这些任务。

在并行队列(Parallel Queue)中,系统会根据可用资源和负载情况自动创建和管理线程。当你将任务添加到并行队列时,如果当前有可用的线程,系统会立即分配一个线程来执行该任务。如果没有可用的线程,系统可能会创建新的线程来处理任务。

在串行队列(Serial Queue)中,由于任务是按顺序逐个执行的,因此通常不需要创建多个线程。系统会按照任务添加到队列的顺序,逐一在单个线程上执行这些任务。

在某些情况下,你可以自己创建和管理线程,然后将任务分派到特定的队列中。但这通常需要更细致的控制,并需要手动处理线程生命周期和同步问题。

总的来说,队列本身并不直接创建线程,而是由底层的派发机制根据任务的性质和队列的类型来决定何时以及如何创建线程来处理任务。23. Property warpperhttps://juejin.cn/post/6844904018121064456#%E6%A6%82%E8%A7%8824. OC/Swift的宏区别25. 函数的具体存储和定义和使用(内存上)26. Swift的类型安全主要体现在哪些方面?27. 尽管Swift是类型安全,但线程仍然存在崩溃现象,请描述线程可能崩溃的场景尽管Swift是类型安全的语言,但线程崩溃仍然可能发生。以下是一些可能导致线程崩溃的场景:

  • 死锁:当两个或多个线程相互等待对方释放资源,导致彼此都无法继续执行时,就会发生死锁。这通常发生在多个线程访问共享资源时,没有正确的同步机制。
  • 数据竞争:当多个线程同时访问和修改同一个数据时,可能会导致数据不一致和其他未定义的行为。Swift的内存模型并不能完全保证原子操作的线程安全性,因此需要使用适当的同步机制来避免数据竞争。
  • 栈溢出:每个线程都有自己独立的栈空间,用于存储局部变量和函数调用信息。如果栈空间不足,会导致栈溢出,进而引发线程崩溃。
  • 信号处理:当线程接收到某些特定的信号(如段错误、浮点异常等)时,如果没有正确处理这些信号,就可能导致线程崩溃。
  • 内存泄漏:虽然Swift使用自动引用计数(ARC)进行内存管理,但在某些情况下,循环强引用或不正确的使用unowned和weak关键字可能导致内存泄漏,最终导致线程崩溃。
  • 非法指令:如果线程执行了非法指令(如除以零、访问无效的内存地址等),会导致线程崩溃。
  • 资源耗尽:当线程消耗的资源超过系统限制(如打开的文件描述符数量、虚拟内存大小等)时,也可能导致线程崩溃。 总的来说,线程崩溃通常是由于多线程编程中的同步问题、资源管理不当或非法操作等原因导致的。为了避免线程崩溃,需要在编程时充分考虑线程安全性、正确使用同步机制、合理管理资源以及处理异常情况。
  1. Swift是静态语言,在编译的时候就能指定指向函数的指针了吗? 考的encodeString字符串转码:ABC3[d] -> ABCddd 二面 工程题目:一个疯子和很多乘客一起选择飞机的座位,座位优先级为靠门靠前靠窗,疯子可以随便选座位,提供飞机的座位图和乘客的长度和疯子在乘客的位置,请设计一个工程,返回最后选完的座位图

require和便捷哪个是横向是上向可选类型是引用传递还是值传递离屏渲染iosproperty wrapper(属性包装器)是一种用于扩展类、结构体和枚举类型的功能。它允许您为属性添加额外的行为,例如计算属性、观察者、键值编码等。要使用 property wrapper,您需要创建一个带有 @propertyWrapper 注解的结构体。这个结构体需要实现一个名为 wrappedValue 的可变或不可变属性,以及一些可选的初始化方法和属性观察者。下面是一个简单的 property wrapper 示例:

Omi顶对科技面试28. Swift的类型安全主要体现在哪些方面?闭包是如何实现的。内存上是如何传递的

阿里三面大数用链表形式相加

#牛客在线求职答疑中心#
全部评论

相关推荐

2 23 评论
分享
牛客网
牛客企业服务