iOS面试总结(2)

511 阅读40分钟

一个 iOS App 会处于哪些状态?

一个iOS App可以处于以下几种状态:

  1. Not Running(未运行):App尚未启动或已被系统终止。

  2. Inactive(非活动):App正在前台运行,但无法接收用户输入事件,比如正在执行系统任务或被其他应用程序覆盖。

  3. Active(活动):App正在前台运行,并且可以接收和处理用户输入事件。

  4. Background(后台):App在后台运行,没有用户界面,但仍在执行某些任务,如音乐播放、定位更新等。在后台状态下,App的资源和执行时间受到限制。

  5. Suspended(挂起):App仍在后台,但不再执行任何代码。系统可能会在内存不足时终止挂起的App,以释放资源。

除了这些基本状态,iOS App还可以根据具体的功能和需求进入其他特定的状态,例如:

  • Background Fetch(后台获取):App可以在后台定期获取新数据,以便在用户打开App时更新内容。

  • Background Transfer(后台传输):App可以在后台继续进行网络传输,如下载文件或上传数据。

  • Background Location Updates(后台定位更新):App可以在后台持续接收位置更新,以便提供位置相关的服务。

  • VoIP(网络电话):App可以在后台接收网络电话的呼叫和事件。

  • Push Notifications(推送通知):App可以在后台接收和处理推送通知。

这些状态和功能可以通过在App的Info.plist文件中配置相应的后台模式来实现。根据App的需求和功能,可以选择适当的后台模式来实现所需的后台行为。

总结来说,iOS App可以处于未运行、非活动、活动、后台和挂起等状态。根据具体的功能和需求,App还可以进入其他特定的后台状态,以实现后台任务和功能。

Push Notification 是如何工作的?

推送通知是一种通过移动设备上的操作系统发送的消息,用于向用户传递重要信息、提醒或更新。在iOS上,推送通知是通过苹果的推送通知服务(Apple Push Notification Service,简称APNS)来实现的。

推送通知的工作原理如下:

  1. 用户授权:在使用App之前,用户需要明确地授权App发送推送通知。当App首次请求发送推送通知时,系统会弹出一个授权提示框,用户可以选择允许或拒绝。

  2. 设备标识:一旦用户授权,设备会生成一个唯一的设备标识符(Device Token)。设备标识符是一个与设备和App相关联的令牌,用于标识设备。

  3. App与APNS通信:当App需要发送推送通知时,它会与APNS建立连接,并将设备标识符和推送通知的内容发送给APNS。

  4. APNS发送通知:APNS接收到App发送的推送通知请求后,会根据设备标识符找到对应的设备,并将推送通知发送到该设备。

  5. 设备接收通知:一旦设备收到推送通知,操作系统会在设备上显示通知,包括标题、内容和其他自定义的信息。用户可以点击通知来打开App或执行其他操作。

需要注意的是,推送通知的发送是由APNS负责的,App只需要将推送通知的内容发送给APNS即可。APNS负责将推送通知传递给目标设备,并在设备上显示通知。

推送通知可以用于各种用途,如消息提醒、新闻更新、社交互动等。它可以帮助App与用户保持连接,并向用户提供及时的信息。


Learn more:

  1. iOS Push Notifications Explained - Airship
  2. How does iOS push notification work? | by Achsuthan Mahendran | Medium
  3. Notifications Overview - Apple Developer

什么是 Runloop?

RunLoop是iOS和OSX开发中的一个重要概念,它是一种高级的循环机制,用于让程序持续运行并处理各种事件。它可以让线程在需要做事情时忙起来,而在没有任务时进入休眠状态[1]

RunLoop与线程的关系

RunLoop与线程是密不可分的。每条线程都有一个与之对应的RunLoop对象。主线程的RunLoop对象是由系统自动创建和启动的,而子线程中的RunLoop对象需要手动获取并启动[1]

RunLoop的结构

RunLoop对象包含一个线程、若干个mode和若干个commonMode。其中,mode是CFRunLoopMode类型的对象,用于处理特定的事件。commonMode是多个mode的集合,可以同时处理多个mode下的事件[1]

CFRunLoopModeRef

CFRunLoopMode是RunLoop的工作模式,苹果提供了几个常用的模式,如NSDefaultRunLoopMode和NSRunLoopCommonModes。每个RunLoop对象都在某个特定的CFRunLoopMode下运行,这个特定的mode称为_currentMode[1]

CFRunLoopSourceRef-事件源

CFRunLoopSource是RunLoop的事件源,用于接收和处理事件。它可以是输入源(input source)或定时源(timer source),用于触发相应的事件处理[2]

RunLoop的底层实现

RunLoop的底层实现是基于CFRunLoop的源码。它使用了线程锁、端口、集合等数据结构来管理和调度事件的处理。RunLoop会不断检测事件源,接收事件并通知线程进行处理[2]

苹果用RunLoop实现的功能

RunLoop在iOS开发中有多种应用场景,包括自动释放池、延迟回调、触摸事件、屏幕刷新等功能。它可以帮助开发者管理和调度这些功能的执行[2]


Learn more:

  1. iOS RunLoop详解 - 掘金
  2. 深入理解RunLoop | Garan no dou
  3. Runloop · 笔试面试知识整理
  4. iOS Run Loop, What Why When - Prafulla Singh - Medium
  5. Run Loops
  6. What is a RunLoop Anyway? Swift and iOS Guide | by Steven Curtis | Medium

Toll-Free Bridging 是什么?什么情况下会使用?

Toll-Free Bridging是一种在Objective-C和Core Foundation之间进行数据类型转换的机制。它允许我们在这两个框架中的某些数据类型之间进行无缝的转换和交互使用。

Toll-Free Bridging的使用场景包括但不限于以下情况:

  1. 在Objective-C和Core Foundation之间进行数据类型转换:Toll-Free Bridging允许我们将Objective-C对象转换为对应的Core Foundation对象,或者将Core Foundation对象转换为对应的Objective-C对象。这样可以在两个框架之间无缝地传递数据,方便开发和使用[1]
  2. 在使用Core Foundation框架的API时,需要将Objective-C对象转换为对应的Core Foundation对象:有些Core Foundation的API只接受Core Foundation对象作为参数,这时我们可以使用Toll-Free Bridging将Objective-C对象转换为对应的Core Foundation对象,以便调用这些API[1]
  3. 在使用Foundation框架的API时,需要将Core Foundation对象转换为对应的Objective-C对象:有些Foundation的API只接受Objective-C对象作为参数,这时我们可以使用Toll-Free Bridging将Core Foundation对象转换为对应的Objective-C对象,以便调用这些API[1]

总结一下,Toll-Free Bridging是一种方便的机制,可以在Objective-C和Core Foundation之间进行数据类型转换,使得两个框架之间的数据交互更加灵活和便捷。


Learn more:

  1. 深入理解Toll-Free Bridging-CSDN博客
  2. Blogs/iOS/深入理解Toll-Free Bridging.md at master
  3. 什么是Toll-free bridging - 麦克煎蛋 - 博客园

当系统出现内存警告时会发生什么?

当系统出现内存警告时,操作系统会发送内存警告通知给应用程序。这通常发生在设备的可用内存不足时,操作系统需要回收一些内存资源以保证系统的正常运行。

当应用程序接收到内存警告通知时,它可以采取一些措施来释放内存,以减少内存使用量。以下是一些常见的处理方式:

  1. 清理不必要的内存:应用程序可以释放一些不再使用的对象或资源,以减少内存占用。这可以通过手动释放对象、取消不必要的缓存或清理临时文件等方式来实现。

  2. 优化内存管理:应用程序可以优化内存管理策略,减少内存的分配和释放次数。例如,使用对象池来重用对象,避免频繁的内存分配和释放操作。

  3. 降低内存使用量:应用程序可以通过减少内存使用量来应对内存警告。例如,降低图片的分辨率或压缩率,减少内存占用。

  4. 延迟加载:应用程序可以延迟加载一些资源,只在需要时才进行加载。这样可以减少初始加载时的内存占用。

  5. 释放不必要的缓存:应用程序可以释放一些不必要的缓存数据,以减少内存占用。例如,清理图片缓存、网络请求缓存等。

总之,当系统出现内存警告时,应用程序应该采取相应的措施来释放内存,以避免因内存不足而导致应用程序崩溃或性能下降。这样可以提高应用程序的稳定性和用户体验。

什么是 Protocol,Delegate 一般是怎么用的?

Protocol是一种定义了一组方法和属性的接口,用于规定类或结构体应该实现的行为。它类似于一份合同或协议,定义了一组规则,以确保不同的类型可以相互通信和交互。

Delegate是一种设计模式,用于实现对象之间的通信和交互。在这种模式中,一个对象(委托方)将某些任务委托给另一个对象(委托代理),并在特定事件发生时调用委托代理的方法来通知和获取结果。

一般来说,使用Delegate的步骤如下:

  1. 定义协议(Protocol):首先,定义一个协议,其中包含委托方需要实现的方法和属性。协议通常会声明一些可选的方法,以便委托方可以选择性地实现。

  2. 声明委托代理(Delegate)属性:在委托方的类中声明一个属性,用于保存委托代理对象。通常使用weak修饰符来避免循环引用。

  3. 委托方调用委托代理方法:在委托方的适当时机,调用委托代理对象的方法来通知和传递相关的数据。委托方可以将自身作为参数传递给委托代理方法,以便委托代理可以回调委托方。

  4. 委托代理实现协议方法:在委托代理对象中实现协议中定义的方法。委托代理根据需要处理委托方传递的数据,并返回结果(如果需要)。

通过使用Delegate模式,委托方和委托代理之间可以实现解耦,委托方只需要知道委托代理遵循了特定的协议,而不需要了解具体的实现细节。这样可以提高代码的可维护性和灵活性。

总结:Protocol是一种定义接口的机制,用于规定类或结构体的行为。Delegate是一种设计模式,用于实现对象之间的通信和交互。通过定义协议和使用委托代理,可以实现对象之间的解耦和灵活的交互方式。

autorelease 对象在什么情况下会被释放?

autorelease对象在以下情况下会被释放:

  1. 当前的自动释放池结束:在Objective-C中,我们可以使用自动释放池(Autorelease Pool)来管理autorelease对象的生命周期。当自动释放池结束时,其中的所有autorelease对象会被释放。通常,在每个事件循环(event loop)或每个迭代循环(iteration loop)中都会创建一个自动释放池。

  2. 手动释放:我们也可以通过手动调用release方法来释放autorelease对象。但是,这种情况下需要注意,如果我们在对象的生命周期内多次调用了autorelease方法,那么在最后一次调用autorelease之后,对象会被添加到当前的自动释放池中,并在自动释放池结束时被释放。

  3. 引用计数为0:autorelease对象的引用计数为0时,它会被释放。引用计数是一种用于跟踪对象被引用的次数的机制。当没有任何对象持有该autorelease对象时,它的引用计数会减少到0,从而触发释放操作。

需要注意的是,autorelease对象的释放是在未来某个时间点进行的,而不是在对象不再被使用的具体时刻立即释放。这是因为autorelease对象会被添加到当前的自动释放池中,而自动释放池的释放时机是由系统控制的。

总结:autorelease对象在自动释放池结束、手动释放或引用计数为0时会被释放。释放操作是在未来某个时间点进行的,由系统控制。

为什么 NotificationCenter 要 removeObserver? 如何实现自动 remove?

NSNotificationCenter的removeObserver方法用于移除之前通过addObserver方法注册的观察者对象。这是为了避免潜在的内存泄漏和不必要的通知处理。

当我们在一个对象中注册为观察者时,通常是为了在特定事件发生时接收通知并执行相应的操作。然而,如果在不再需要接收通知的情况下,没有及时移除观察者,就可能导致以下问题:

  1. 内存泄漏:观察者对象会保持对通知中心的强引用,如果不及时移除观察者,就会导致观察者对象无法释放,从而造成内存泄漏。

  2. 无效的通知处理:如果观察者对象已经被释放,但仍然保留在通知中心的观察者列表中,当相应的通知发生时,通知中心会尝试调用已经释放的观察者对象的方法,导致无效的通知处理。

为了避免以上问题,我们需要在适当的时机调用removeObserver方法,将观察者对象从通知中心中移除。通常,在观察者对象被释放之前,或者在不再需要接收通知的时候,都应该调用removeObserver方法。

为了实现自动移除观察者,可以考虑以下两种方式:

  1. 在合适的时机手动调用removeObserver方法:在观察者对象即将被释放或不再需要接收通知时,手动调用removeObserver方法来移除观察者。例如,在对象的dealloc方法中调用removeObserver方法,确保在对象被释放时移除观察者。

  2. 使用block-based的观察者方式:在iOS 9及以上版本,我们可以使用block-based的观察者方式来注册观察者。这种方式不需要手动调用removeObserver方法,观察者会在其关联的对象被释放时自动移除。使用block-based观察者可以简化代码,并且避免了手动调用removeObserver的繁琐。

总结:NSNotificationCenter的removeObserver方法用于移除之前通过addObserver方法注册的观察者对象,以避免内存泄漏和无效的通知处理。为了实现自动移除观察者,可以在合适的时机手动调用removeObserver方法,或者使用block-based的观察者方式来注册观察者。

Learn more:

  1. 理解KVO - 用Swift在WKWebView中添加进度条
  2. iOS 9+ NotificationCenter 还需要手动移除观察者吗? - 知乎
  3. 观察者模式-将消息通知给观察者 - 码农充电站 - 博客园

当 TableView 的 Cell 改变时,如何让这些改变以动画的形式呈现?

在 UITableView 中,当 Cell 改变时,可以通过添加动画效果来使这些改变以更加生动的方式呈现。以下是几种常用的方法:

  1. 使用 UITableView 的 reloadRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation) 方法 [1]。这个方法可以在指定的 indexPaths 处刷新 Cell,并且可以指定动画效果。你可以将需要改变的 Cell 的 indexPaths 传入该方法,然后选择合适的动画效果,例如 .fade.right.left 等。这样在刷新 Cell 时就会有相应的动画效果。

  2. 使用 UITableView 的 beginUpdates()endUpdates() 方法 [1]。这对方法可以在改变 Cell 时触发 UITableView 的更新过程,并且可以自动计算 Cell 的高度变化。你可以在 beginUpdates()endUpdates() 之间进行 Cell 的高度变化操作,然后 UITableView 会自动计算并刷新 Cell 的布局,从而实现动画效果。

  3. 在 iOS 11 及以上版本中,你还可以使用 performBatchUpdates(_:completion:) 方法来代替 beginUpdates()endUpdates() 方法 [1]。这个方法可以在一个闭包中执行多个更新操作,并且可以指定动画效果。你可以在闭包中进行 Cell 的高度变化操作,然后选择合适的动画效果,最后在闭包结束时调用 completion 闭包来完成更新。

综上所述,你可以根据具体的需求选择合适的方法来实现 Cell 的动画效果。使用这些方法可以让 Cell 的改变以动画的形式呈现,提升用户体验。


Learn more:

  1. 为 UITableViewCell 高度变化添加动画 | 闪耀旅途
  2. [UITableView] UITableViewCell渲染动画效果 - 简书
  3. iOS实现TableView中Cell出现时弹出动画-腾讯云开发者社区-腾讯云

什么是 Method Swizzle,什么情况下会使用?

Method Swizzle是一种在运行时改变方法实现的技术。通过Method Swizzle,我们可以在运行时修改类的方法分发表,从而达到Hook的目的。这种技术在Objective-C中非常常见,可以用于实现方法的增强、替换或者跟踪等功能。

Method Swizzle的原理是利用Objective-C的动态特性,在运行时交换方法的实现。在Objective-C中,方法调用是通过向对象发送消息来实现的,而消息的唯一依据是方法的选择器(selector)。每个类都有一个方法列表,存放着选择器和方法实现的映射关系。通过修改方法列表中选择器对应的方法实现,就可以实现方法的交换。

使用Method Swizzle的情况有很多,以下是一些常见的应用场景:

  1. 方法增强:通过Method Swizzle,我们可以在不修改原始代码的情况下,在方法的前后插入自定义的代码,实现方法的增强功能。比如,在每个ViewController的加载时插入跟踪日志信息[1]

  2. 方法替换:有时候我们希望替换某个类的方法实现,以改变原有的行为。通过Method Swizzle,我们可以将原始方法的实现替换为自定义的方法实现,从而改变方法的行为。

  3. 调试和跟踪:Method Swizzle可以用于在方法调用前后打印日志信息,以便调试和跟踪代码的执行流程。

  4. AOP编程:面向切面编程(AOP)是一种编程范式,可以通过Method Swizzle来实现。通过在方法调用前后插入切面逻辑,可以实现横切关注点的统一处理,比如日志记录、性能监控等。

需要注意的是,Method Swizzle涉及到修改全局状态,因此在使用时需要采取一些保护措施。常见的做法是使用GCD的dispatch_once来确保代码只被执行一次,以避免多线程环境下的竞态条件。


Learn more:

  1. Method Swizzling-阿里云开发者社区
  2. 深入浅出理解Method Swizzle | 马德茂的博客
  3. iOS 黑魔法Method Swizzling如何正确使用 - 掘金

为什么 UIScrollView 的滚动会导致 NSTimer 失效?

UIScrollView的滚动会导致NSTimer失效的原因是RunLoop的模式切换。

在iOS中,UIScrollView的滚动是在主线程的Tracking模式下进行的,而NSTimer默认是在Default模式下运行。当UIScrollView开始滚动时,RunLoop会将模式切换为Tracking模式,这会导致Default模式下的NSTimer失效,因为RunLoop只会在当前模式下执行对应模式的任务。

解决这个问题的一种常见方法是将NSTimer添加到RunLoop的Common模式中,这样即使RunLoop切换到Tracking模式,NSTimer仍然可以继续执行。可以使用NSRunLoop的addTimer(_:forMode:)方法将NSTimer添加到Common模式中。

另外,还可以考虑使用CADisplayLink来替代NSTimer,CADisplayLink是一种与屏幕刷新率同步的定时器,它会在每次屏幕刷新时触发回调。由于CADisplayLink是与屏幕刷新率同步的,所以在UIScrollView滚动时仍然可以正常工作。

需要注意的是,在使用NSTimer或CADisplayLink时,要确保在适当的时候将其从RunLoop中移除,以避免内存泄漏和不必要的资源消耗。

为什么当 Core Animation 完成时,layer 又会恢复到原先的状态?

当 Core Animation 完成时,layer 又会恢复到原先的状态的原因是因为 Core Animation 的动画过程实际上只是修改了呈现树(Presentation Tree),并没有对图层的属性进行实际改变[1]

下面是对这个问题的详细解释:

  1. 图层树结构:Layer 和 View 都存在着一个层级树状结构,称之为图层树(Layer Tree)。直接创建的或者通过 UIView 获得的图层树用于显示,称之为模型树(Model Tree)。模型树的背后还存在两份图层树的拷贝,一个是呈现树(Presentation Tree),一个是渲染树(Render Tree)[1]

  2. 模型树和呈现树:模型树是我们通过代码直接操控的部分,当我们修改模型树的属性时,属性的值会立即变为新的值。而呈现树是模型树的拷贝,它可以通过普通图层的 presentationLayer 属性获得。呈现树的属性值和动画运行过程中界面上看到的是一致的[1]

  3. 动画过程:当我们使用 Core Animation 创建动画时,实际上是在修改呈现树的属性值,而不是直接修改图层的属性。动画的过程只是修改了呈现树,没有对图层的属性进行实际改变。因此,当动画完成时,图层会恢复到原先的状态,即模型树的状态[1]

为了保留动画效果,我们可以在添加动画之外再去手动设置图层的属性,以使其保持在动画结束后的状态。例如,可以在添加动画后手动设置图层的属性值,使其与动画结束后的状态一致[2]


Learn more:

  1. collamark
  2. ios,_为什么当 Core Animation 完成时,layer 又会恢复到原先的状态?,ios

你会如何存储用户的一些敏感信息,如登录的 token。

在iOS APP开发过程中,存储用户的敏感信息(如登录的token)是一个关键的安全问题。以下是一些iOS存储敏感信息的最佳实践:

  1. 使用Keychain存储敏感信息:Keychain是iOS提供的安全存储机制,可以用于存储敏感信息,如用户的登录token。Keychain提供了加密和安全的存储,可以防止敏感信息被未经授权的访问。可以使用KeychainWrapper等第三方库来简化Keychain的使用。

  2. 使用加密算法加密敏感信息:在存储敏感信息之前,可以使用加密算法对其进行加密。常见的加密算法包括AES和RSA。使用加密算法可以增加敏感信息的安全性,即使数据被访问,也无法解密获得原始数据。

  3. 避免明文存储敏感信息:绝对不要将敏感信息以明文形式存储在设备的文件系统或数据库中。明文存储会增加敏感信息被攻击者获取的风险。

  4. 使用安全的传输协议:在传输敏感信息时,应使用安全的传输协议,如HTTPS。HTTPS可以加密数据传输,防止数据在传输过程中被窃取或篡改。

  5. 及时清除敏感信息:在不再需要敏感信息时,应及时将其从内存中清除,以防止被其他应用或攻击者获取。

  6. 限制敏感信息的访问权限:只有必要的组件和功能才能访问敏感信息。可以使用权限控制和访问控制列表来限制对敏感信息的访问。

  7. 定期更新敏感信息:对于一些敏感信息,如登录的token,应定期更新,以减少被攻击者利用的风险。

请注意,以上是一些常见的最佳实践,但具体的实现方式可能因应用的需求和安全要求而有所不同。在实际开发中,建议参考相关的安全规范和文档,以确保敏感信息的安全存储和传输。


Learn more:

  1. iOS安全:【敏感信息的脱敏规范】(数据类型包括日志相关、账户订单、个人信息、账户认证、持卡数据)-阿里云开发者社区
  2. 3.1 实现安全数据存储 · Secure Mobile Development Best Practices | NowSecure
  3. 2.5 在内存中安全的存储敏感数据 · Secure Mobile Development Best Practices | NowSecure

iOS开发过程中有用过一些开源组件吧,能简单说几个么,大概说说它们的使用场景实现。

在iOS开发中,常常会使用一些开源组件来加快开发速度、提高代码质量和增加功能。以下是一些常用的开源组件及其使用场景实现:

  1. AFNetworking [1]: 用于网络请求和数据交换。它提供了简单易用的API,支持各种网络请求操作,如GET、POST等,还支持网络状态监测和文件上传等功能。

  2. SDWebImage [1]: 用于异步加载和缓存网络图片。它可以帮助开发者快速加载远程图片,并自动处理图片的缓存和内存管理,提高图片加载的性能和用户体验。

  3. Masonry [2]: 用于自动布局。它是对AutoLayout的封装,提供了简洁的语法和链式调用,使得布局代码更加清晰和易于维护。

  4. MJRefresh [2]: 用于实现下拉刷新和上拉加载更多功能。它提供了简单易用的API,可以方便地添加下拉刷新和上拉加载更多的效果,并支持自定义刷新样式。

  5. MJExtension [3]: 用于JSON数据解析和模型转换。它可以将JSON数据自动解析为模型对象,简化了数据解析的过程,提高了开发效率。

  6. MBProgressHUD [2]: 用于显示进度指示器。它可以在界面上显示加载中、成功或失败等状态,并提供了多种样式和自定义选项,方便开发者在需要时显示进度指示器。

这些开源组件在iOS开发中被广泛使用,可以帮助开发者快速实现常见的功能和提高开发效率。


Learn more:

  1. GitHub - Tim9Liu9/TimLiu-iOS: iOS开发常用三方库、插件、知名博客等等
  2. 网易新闻iOS版使用的18个开源组件 - 掘金
  3. iOS开发常用第三方开源框架和优秀开发者博客合集 - 掘金

什么时候会发生 EXC BAD ACCESS 异常?

EXC_BAD_ACCESS异常是iOS开发中常见的异常,它发生在应用程序尝试访问没有权限的内存时。这可能由多种原因引起,例如访问已释放的对象、访问已释放的内存块或访问已损坏的内存。

以下是一些常见的EXC_BAD_ACCESS异常发生的情况:

  1. 访问已释放的对象:当您尝试访问已从内存中释放的对象时会发生此异常。这可能是因为忘记保留对象或过早释放对象。要解决此问题,请确保正确管理对象的内存生命周期,并避免访问已释放的对象。

  2. 访问已释放的内存块:当尝试访问已释放的内存块时会发生此异常。这可能是因为错误地调用了free()或delete来释放已释放的内存块。为避免此问题,请仔细检查内存管理代码,并确保只访问有效的内存块。

  3. 访问已损坏的内存:当应用程序尝试访问已损坏的内存(例如缓冲区溢出或内存泄漏)时会发生此异常。调试和修复此问题可能会有一定挑战,但使用工具如Address Sanitizer或通过内存分析器运行应用程序可以帮助识别和修复内存损坏问题。

要解决和调试EXC_BAD_ACCESS异常,可以按照以下步骤进行操作:

  1. 启用NSZombieEnabled:此设置可以将已释放的实例转换为“僵尸”对象,当访问时可以记录消息,有助于识别发送给已释放实例的消息。可以在Xcode中启用NSZombieEnabled,方法是转到Product -> Scheme -> Edit Scheme -> Run -> Diagnostics -> Enable Zombie Objects。

  2. 启用MallocStackLogging:此设置允许跟踪内存分配和释放的历史记录,有助于识别与内存相关的问题的来源。您可以在Xcode中启用MallocStackLogging,方法是转到Product -> Scheme -> Edit Scheme -> Run -> Arguments -> Environment Variables -> 添加MallocStackLogging并将其值设置为YES。

  3. 使用info malloc-history命令:如果您已启用MallocStackLogging,可以在调试器中使用info malloc-history命令获取特定内存地址的内存分配和释放的详细历史记录。这可以帮助您追溯EXC_BAD_ACCESS异常的来源。

  4. 分析您的代码:仔细检查您的代码,特别注意正确的内存管理实践,如正确保留和释放对象。使用Xcode的静态分析器或Instruments等工具来识别潜在的内存问题。

什么时候会使用 Core Graphics,有什么注意事项么?

在iOS开发中,Core Graphics(又称为Quartz 2D)是一个强大的绘图框架,用于处理图形和图像的绘制、变换和渲染。以下是一些常见的使用场景和注意事项:

使用场景:

  1. 自定义绘图:当您需要在iOS应用程序中进行自定义绘图时,可以使用Core Graphics。它提供了一系列的绘图函数和API,可以绘制线条、形状、文本和图像等。

  2. 图形处理和变换:Core Graphics提供了丰富的图形处理和变换功能,如缩放、旋转、裁剪和混合等。您可以使用这些功能来实现图像的变换、合成和特效等。

  3. PDF和图像的渲染:Core Graphics可以用于将PDF文档和图像渲染到屏幕上或绘制到图形上下文中。您可以使用它来显示PDF文档的内容或将图像绘制到自定义的图形上下文中。

注意事项:

  1. 性能考虑:由于Core Graphics是一个底层的绘图框架,使用不当可能会对性能产生负面影响。在处理大量图形或图像时,应注意性能优化,避免频繁的绘制操作和不必要的图形变换。

  2. 内存管理:在使用Core Graphics时,需要注意内存管理。特别是在处理大型图像或进行频繁的绘制操作时,及时释放不再使用的图形上下文或图像资源,以避免内存泄漏和性能问题。

  3. 坐标系转换:Core Graphics使用的坐标系与UIKit的坐标系有所不同。在进行绘制操作时,需要注意坐标系的转换,以确保绘制的内容正确显示在屏幕上。

  4. 线程安全:Core Graphics并非线程安全的,因此在多线程环境下使用时需要注意同步和线程安全性。通常情况下,应将绘制操作限制在主线程中进行。

总之,Core Graphics是iOS开发中强大而灵活的绘图框架,可以用于实现自定义绘图、图形处理和渲染等功能。在使用时需要注意性能、内存管理、坐标系转换和线程安全等方面的考虑。

NSNotification 和 KVO 的使用场景?

NSNotification和KVO(Key-Value Observing)是iOS开发中用于实现观察者模式的两种机制,它们的使用场景如下:

NSNotification的使用场景:

  1. 发送广播通知:NSNotification可以用于在应用程序内部或不同组件之间发送广播通知。例如,当某个事件发生时,您可以使用NSNotification发送通知,然后其他对象可以注册为观察者来接收并响应该通知。

  2. 解耦组件之间的通信:NSNotification可以帮助解耦应用程序中的不同组件。通过发送和接收通知,组件之间可以进行松散的耦合,不需要直接引用彼此的实例。

  3. 监听系统级别的通知:NSNotification可以用于监听系统级别的通知,例如设备旋转、键盘弹出等。通过注册为观察者,您可以在这些系统事件发生时执行相应的操作。

KVO的使用场景:

  1. 监听属性的变化:KVO允许您监听对象属性的变化。当被观察的属性发生变化时,观察者会收到通知并可以执行相应的操作。这对于实现数据绑定、响应式编程或在模型-视图之间保持同步非常有用。

  2. 监听集合的变化:KVO还可以用于监听集合类型(如NSArray、NSSet)的变化。当集合中的对象添加、删除或替换时,观察者可以收到通知并做出相应的响应。

  3. 自定义属性的观察:除了监听对象的属性,KVO还可以用于自定义属性的观察。通过重写key>willChangeValueForKey:key>didChangeValueForKey:方法,您可以手动触发属性的变化通知。

需要注意的是,NSNotification和KVO都是用于实现观察者模式的机制,但它们的使用场景略有不同。NSNotification更适用于广播通知和解耦组件之间的通信,而KVO更适用于监听属性和集合的变化。根据具体的需求和设计模式,选择合适的机制来实现观察者模式。

使用 Block 时需要注意哪些问题?

在使用Block时,需要注意以下几个问题:

  1. 循环引用:Block中使用了外部的对象时,需要注意可能引起的循环引用问题。当Block持有了外部对象,并且外部对象也持有了Block时,可能会导致内存泄漏。为避免循环引用,可以使用弱引用(weak reference)或者使用强弱引用组合(strong-weak reference pattern)来解决。

  2. Block内部变量的修改:默认情况下,Block内部无法修改外部变量的值,因为Block会对外部变量进行值拷贝。如果需要在Block内部修改外部变量的值,可以使用__block修饰符来声明变量,使其在Block内部可变。

  3. Block的生命周期管理:当Block被捕获并在其他地方使用时,需要注意Block的生命周期管理。如果Block被存储在实例变量或全局变量中,需要注意在适当的时机释放Block,避免引起内存泄漏。

  4. Block的线程安全性:Block在多线程环境下的使用需要考虑线程安全性。如果多个线程同时访问同一个Block对象,需要确保对Block内部的共享资源进行适当的同步操作,以避免竞态条件和数据不一致的问题。

  5. Block的循环使用:在某些情况下,Block可能会被多次调用,例如在循环中使用Block。在这种情况下,需要注意Block内部对外部变量的引用是否正确,以及是否会导致意外的行为或内存泄漏。

总之,使用Block时需要注意循环引用、变量修改、生命周期管理、线程安全性和循环使用等问题。合理地处理这些问题,可以确保Block的正确使用和避免潜在的问题。

performSelector:withObject:afterDelay: 内部大概是怎么实现的,有什么注意事项么?

performSelector:withObject:afterDelay: 是 NSObject 类的一个方法,用于在指定延迟后执行指定的方法,并可以传递一个参数给该方法。下面是大致的实现方式和一些注意事项:

实现方式:

  1. performSelector:withObject:afterDelay: 方法会创建一个 NSInvocation 对象,该对象包含了要执行的方法和参数。
  2. 使用 NSInvocation 对象的 invokeWithTarget: 方法来执行方法,并传递参数。

注意事项:

  1. performSelector:withObject:afterDelay: 方法只能在主线程中使用,因为它依赖于主线程的 Run Loop 来实现延迟执行。
  2. 如果需要在其他线程中延迟执行方法,可以使用 GCD 的 dispatch_after 函数来实现。
  3. 如果延迟时间为0,方法会立即执行,不会有延迟。
  4. 如果在延迟执行之前调用了 cancelPreviousPerformRequestsWithTarget: 方法取消了之前的延迟执行请求,那么该方法将不会被执行。
  5. 如果在延迟执行之前对象被释放了,那么该方法也将不会被执行。

Learn more:

  1. objective c - How to use performSelector:withObject:afterDelay: with primitive types in Cocoa? - Stack Overflow
  2. Coalescing - Taking Notes
  3. [objective-c] How to use performSelector:withObject:afterDelay: with primitive types in Cocoa? - SyntaxFix

如何播放 GIF 图片,有什么优化方案么?

在iOS开发中,可以使用第三方库或原生方法来播放GIF图片。以下是一些常用的方法和优化方案:

  1. 使用第三方库:可以使用一些第三方库来方便地加载和播放GIF图片,例如SDWebImage、FLAnimatedImage等。这些库提供了简单的API来加载和显示GIF图片,并且通常具有缓存和性能优化的功能。

  2. 使用原生方法:在iOS 13及以上版本,可以使用UIImage的animatedImageWithImages:duration:方法来创建并播放GIF图片。这个方法可以将一系列的静态图片帧组合成一个动画,并指定每帧的持续时间。

  3. 优化GIF图片大小:GIF图片通常比其他图片格式(如JPEG、PNG)的文件大小要大,因此在使用GIF图片时,可以考虑对其进行优化以减小文件大小。可以使用一些在线工具或图像处理软件来压缩GIF图片,减少帧数或降低颜色深度等。

  4. 控制动画播放:如果GIF图片的帧数较多或动画较长,可以考虑控制动画的播放,以避免过多的资源消耗。可以通过设置动画的播放次数、播放速度或手动触发播放等方式来控制动画的展示。

  5. 内存管理:由于GIF图片可能占用较多的内存,特别是帧数较多或尺寸较大的GIF图片,需要注意内存管理。可以使用缓存来避免重复加载和释放GIF图片,以及在不需要时及时释放相关资源。

  6. 考虑性能影响:播放GIF图片可能会对性能产生一定的影响,特别是在同时播放多个GIF图片或在较低性能的设备上。因此,在使用GIF图片时,需要评估其对性能的影响,并根据实际情况进行优化或限制。

总之,播放GIF图片可以使用第三方库或原生方法,并可以通过优化文件大小、控制播放、内存管理和考虑性能影响等方式来提升用户体验和性能。根据具体需求和场景选择合适的方法和优化策略。

使用 NSUserDefaults 时,如何处理布尔的默认值?(比如返回 NO,不知道是真的 NO 还是没有设置过)

当使用NSUserDefaults存储布尔值时,如果没有设置过该键对应的值,NSUserDefaults会返回一个默认值。对于布尔类型,默认值是NO。因此,当从NSUserDefaults中获取布尔值时,如果返回NO,无法确定是真正的NO还是没有设置过。

为了解决这个问题,可以在设置布尔值时,使用一个特定的值来表示未设置的状态,而不是使用默认值NO。例如,可以使用NSNumber对象来表示布尔值,并使用nil来表示未设置的状态。

示例代码如下:

// 存储布尔值

  • (void)setBoolValue:(BOOL)value forKey:(NSString *)key { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; if (value) { [defaults setObject:@(YES) forKey:key]; } else { [defaults removeObjectForKey:key]; } [defaults synchronize]; }

// 获取布尔值

  • (BOOL)getBoolValueForKey:(NSString *)key { NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; NSNumber *value = [defaults objectForKey:key]; if (value != nil) { return [value boolValue]; } else { // 未设置过该键对应的值 return NO; // 或者根据需求返回其他默认值 } }

通过使用NSNumber对象,并使用nil来表示未设置的状态,可以区分真正的NO和未设置的情况。这样可以更准确地处理布尔值的默认值。

有哪几种方式可以对图片进行缩放,使用 CoreGraphics 缩放时有什么注意事项?

在iOS开发中,有几种方式可以对图片进行缩放,包括使用UIKit、Core Graphics和第三方库。以下是常用的几种方式:

  1. 使用UIKit:

    • 使用UIImageView的contentMode属性来控制图片的缩放行为。设置为UIViewContentModeScaleAspectFit可以保持图片的纵横比并适应视图的大小。
    • 使用UIImage的resizableImageWithCapInsets:方法来创建可拉伸的图片,可以在保持图片比例的同时进行缩放。
  2. 使用Core Graphics:

    • 使用Core Graphics框架中的函数和方法来进行图像的缩放。可以使用CGContextDrawImage函数将图像绘制到指定的图形上下文中,并使用CGContextDrawImage函数的rect参数来指定缩放后的尺寸。
    • 注意在进行缩放时保持图像的纵横比,避免图像变形。
  3. 使用第三方库:

    • 一些第三方库提供了更高级的图像处理功能,包括图像缩放。例如,SDWebImage、Kingfisher等库可以方便地加载、缓存和显示缩放后的图像。

使用Core Graphics进行图像缩放时,需要注意以下事项:

  1. 确保保持图像的纵横比:在缩放图像时,应保持图像的纵横比,以避免图像变形。可以通过在缩放时同时调整宽度和高度来保持纵横比。

  2. 考虑图像的分辨率:缩放图像可能会影响图像的分辨率。如果需要保持图像的清晰度,可以选择使用更高分辨率的图像进行缩放。

  3. 注意图像的质量损失:在进行大幅度缩放时,图像可能会出现质量损失,导致图像变得模糊或失真。要尽量避免这种情况,可以选择使用高质量的缩放算法或尝试不同的缩放比例。

总之,iOS开发中可以使用UIKit、Core Graphics和第三方库来对图片进行缩放。在使用Core Graphics进行缩放时,需要注意保持图像的纵横比、图像的分辨率以及可能出现的质量损失。根据具体需求选择合适的方式进行图像缩放。

哪些途径可以让 ViewController 瘦下来?

有几种方法可以让 ViewController 瘦下来,以下是一些常见的途径:

  1. 使用 Model-View-ViewModel (MVVM) 模式:MVVM 模式将视图控制器的责任进行分离,引入了一个名为 view model 的中间层。视图控制器主要负责用户交互和将数据展示到视图上,而具体的数据处理和格式化则由 view model 负责。这样可以减轻视图控制器的负担,使其更加瘦身。[2]

  2. 使用多个视图控制器:将复杂的用户界面拆分成多个部分,每个部分由一个独立的视图控制器管理。这种方式被称为视图控制器容器化,可以将视图控制器的责任分散到多个子视图控制器中,使其更加精简和可复用。[2]

  3. 将特定功能委托给其他对象:将一些与展示和用户交互相关的任务委托给其他对象,而不是将其全部放在视图控制器中。例如,对于表格视图的数据源和委托方法,可以将其放在独立的对象中,以减轻视图控制器的负担。[2]

  4. 使用协议和委托模式:通过使用协议和委托模式,可以将视图控制器与其他对象进行解耦,使其更加独立和可测试。通过定义协议并将其实现委托给其他对象,可以将一些功能和逻辑从视图控制器中移出,使其更加瘦身。[1]

  5. 使用组件化和模块化的设计:将应用程序拆分成多个独立的组件和模块,每个模块负责特定的功能。这样可以将视图控制器的责任限制在特定的模块内,使其更加专注和瘦身。

这些方法可以帮助你使视图控制器更加瘦身,提高代码的可维护性和可测试性。


Learn more:

  1. How To Fix Your Fat ViewController | by Mohd Hafiz | Geek Culture | Medium
  2. Three Strategies to Keep View Controllers Skinny
  3. cocoa - How to make ViewController First responder? - Stack Overflow

iOS开发有哪些常见的 Crash 场景?

在iOS开发中,常见的Crash场景包括:

  1. 找不到方法的实现(unrecognized selector sent to instance)[1]

    • 场景:当调用一个对象的方法,但该方法未被实现时,会导致Crash。
    • 解决方案:可以通过消息转发机制来处理未实现的方法,或者使用respondsToSelector方法进行判断。
  2. KVC(Key-Value Coding)造成的Crash [1]

    • 场景:当使用KVC给一个对象设置不存在的key、key为nil或者key不是对象的属性时,会导致Crash。
    • 解决方案:可以通过重写setValue:forUndefinedKey:和valueForUndefinedKey:方法来处理未定义的key。
  3. EXC_BAD_ACCESS [1]

    • 场景:EXC_BAD_ACCESS错误通常是由于访问已释放的对象、访问越界的内存等引起的。
    • 解决方案:需要仔细检查代码,确保对象的生命周期正确管理,避免访问已释放的对象或越界访问。
  4. 多线程中的崩溃 [2]

    • 场景:在多线程开发中,如果不正确地处理线程同步和资源访问,可能会导致崩溃。
    • 解决方案:使用线程同步机制(如锁、信号量等)来保证多线程访问的安全性,避免竞态条件和资源冲突。

Learn more:

  1. iOS中常见Crash总结 - 掘金
  2. iOS常见Crash案例总结 - 简书
  3. iOS常见crash场景及解决方案 - 简书