目录
属性访问权限关键词
-
open
最开放的权限,不仅可以在模块外访问,还可以在模块外继承和重写。常用于框架中对外公开的接口。
-
public
公开权限,在模块外也可以访问,但不能在模块外继承或重写。
-
internal
默认权限,只在当前模块(target)内可见,模块外无法访问。
-
fileprivate
文件级权限,限定在同一个 Swift 文件内可见。
-
private
最严格的权限,仅在当前声明的作用域内可见(例如在同一个类型的扩展中也不可访问)
weak和strong
• strong(强引用):
• 当你持有一个对象的 strong 引用时,这个引用会增加对象的引用计数,确保该对象一直存活,直到所有 strong 引用都被释放。
• weak(弱引用):
• 弱引用不会增加对象的引用计数,也就是说它不“拥有”这个对象。当没有 strong 引用时,对象会被销毁,所有指向该对象的 weak 引用会自动变为 nil。
视图控制器的生命周期
1. 初始化阶段
• init(nibName:bundle:)
当你创建视图控制器对象时,系统会调用初始化方法。此时,你可以传入 nib 名称或者使用代码创建视图控制器。
• loadView()
当视图控制器的 view 属性被第一次访问时,系统会调用 loadView() 方法。默认情况下,loadView() 会从 storyboard 或 nib 加载视图。如果你选择完全手写界面,可以重写 loadView(),在里面创建并赋值 self.view。
2. 视图加载阶段
• viewDidLoad()
当视图控制器的视图加载完成后(但尚未显示),系统会调用 viewDidLoad()。这是配置视图、初始化数据、绑定事件的好地方。注意,这个方法只会调用一次。
3. 视图即将显示阶段
• viewWillAppear(_:)
在视图即将加入到窗口前,系统会调用 viewWillAppear(_:)。在这个方法里,你可以更新界面数据、调整状态(如隐藏导航栏)等。这时视图仍未出现在屏幕上。
• viewDidAppear(_:)
当视图完全显示在屏幕上后,会调用 viewDidAppear(_:)。常用于启动动画、网络请求或者开始某些需要用户交互的任务。
4. 视图即将消失阶段
• viewWillDisappear(_:)
当视图即将从屏幕上消失时(比如用户返回或切换到其他界面),系统会调用 viewWillDisappear(_:)。在这里你可以停止定时器、取消网络请求、保存状态等。
• viewDidDisappear(_:)
当视图完全离开屏幕后,会调用 viewDidDisappear(_:)。这时可以进行一些清理工作,确保资源释放。
5. 视图控制器的销毁
• deinit
当视图控制器不再被使用时,系统会调用 deinit 进行销毁。这个阶段适合释放资源、取消观察者等操作。
应用程序的生命周期
1. 启动阶段
• 应用启动
当用户点击图标启动 App 时,系统会创建应用进程,并调用 AppDelegate 中的方法:
• application(_:willFinishLaunchingWithOptions:)(可选):最初的初始化工作,准备启动环境。
• application(_:didFinishLaunchingWithOptions:):完成启动设置,比如初始化 window、配置全局状态等。
此时,应用还未进入活动状态,UI 还在加载中。
2. 活跃状态
• 应用进入前台并成为活动状态
当应用的 UI 完全呈现并准备好与用户交互时,系统会调用:
• applicationDidBecomeActive(_:)
此时,应用处于活跃状态,用户可以操作 App。
3. 不活跃状态
• 应用即将中断
当出现临时中断(例如来电、短信通知)时,系统会调用:
• applicationWillResignActive(_:)
应用此时会暂停某些任务或动画,以便尽可能快地响应中断。
4. 后台状态
• 应用进入后台
当用户按下 Home 键或切换到其他 App 时,系统会调用:
• applicationDidEnterBackground(_:)
在后台状态下,应用有一定时间完成任务(比如保存数据、释放资源),然后被挂起;部分后台任务可以申请延长执行时间,但总体上应用不再活跃。
5. 前台状态(从后台恢复)
• 应用从后台恢复
当用户重新打开 App 时,系统会调用:
• applicationWillEnterForeground(_:):应用将要从后台恢复,此时可以做一些准备工作。
• applicationDidBecomeActive(_:):应用完全进入活动状态。
6. 终止阶段
• 应用终止
当系统需要释放资源或者用户手动关闭 App 时,系统会调用:
• applicationWillTerminate(_:)
注意:如果应用处于后台挂起状态时被系统杀掉,此方法可能不会被调用,所以在 applicationDidEnterBackground(_:) 中保存重要数据更为保险。
点击屏幕后的流程
1. 硬件捕捉触摸
• 触摸硬件:当你触摸屏幕时,设备的触摸传感器检测到这一动作,并将信号传递给操作系统。
2. 系统封装事件
• 内核处理:iOS 内核接收到硬件信号后,将触摸数据封装成一个 UIEvent 对象。
• 事件队列:这个事件会被放入主线程的 RunLoop 中等待处理。
3. 主线程 RunLoop 分发事件
• RunLoop 轮询:主线程的 RunLoop 不断检查事件队列,当有新的事件时,它会开始处理。
• 事件分发到 UIWindow:RunLoop 将 UIEvent 传递给应用程序的 UIWindow。
4. UIWindow 的 Hit-Testing
• Hit-Testing 算法:UIWindow 根据触摸点的位置,通过 hit-testing 遍历整个视图层级,找到最合适的视图(也就是响应链中的第一个 UIResponder 对象)。
• 确定目标视图:例如,一个按钮或一个自定义的视图会被确定为触摸事件的接收者。
5. 事件传递到 UIResponder
• 触摸事件传递:找到目标视图后,系统会调用该视图的 UIResponder 方法,如 touchesBegan(_:with:)。
• 连续触摸处理:如果你继续滑动手指,则会依次调用 touchesMoved(:with:);当你松开手指时,调用 touchesEnded(:with:)。如果触摸被系统或手势识别器中断,则会调用 touchesCancelled(_:with:)。
6. 手势识别
• Gesture Recognizers:如果目标视图上附加了手势识别器(如点击、长按、滑动等),它们会同时接收这些触摸事件,判断是否符合相应的手势。
• 协调响应:手势识别器与 UIResponder 链共同工作,决定最终哪个控件响应这一触摸事件。
7. 执行响应与界面更新
• 响应用户操作:目标视图在收到事件后,根据业务逻辑执行相应的操作(例如改变 UI 状态、触发动画、调用回调等)。
• 界面更新:根据事件处理结果,界面可能会更新,或者触发其他的异步操作。
Runloop
RunLoop 是 iOS/macOS 应用中用于保持线程持续运行并处理事件的一个核心机制,可以把它看作一个“事件循环”。简单来说,RunLoop 就像一个一直在“等电话”的人,不断地等待、接收并处理各种事件(例如触摸、定时器、网络数据、输入等)。
主要特点
1. 持续运行(线程保活)
• 主线程的 RunLoop 自动启动,并不断循环,确保应用界面一直响应用户操作。
• 子线程默认没有 RunLoop,如果需要保持长时间运行(比如后台任务),必须手动启动。
2. 事件处理
• RunLoop 会等待事件的到来,比如用户点击、定时器触发、网络请求返回等。
• 一旦有事件发生,RunLoop 会唤醒线程,处理事件,然后再次进入等待状态。
3. 模式(Modes)
• RunLoop 可以运行在不同的模式下,不同模式会包含不同的事件源。
• 常见的模式有默认模式(kCFRunLoopDefaultMode)和追踪模式(UITrackingRunLoopMode,通常用于滚动时)。
4. 自动退出与唤醒
• 如果在某个模式下没有事件源,RunLoop 会自动进入休眠状态,等待事件发生后被唤醒。
• 当线程内所有任务完成后,RunLoop 有时会退出循环,结束线程的执行。
RunTime
KVC(键值编码)的实现机制
当你调用 setValue:forKey: 或 valueForKey: 时,系统会根据键(一个字符串)按照一定规则查找与之对应的访问方法或成员变量,主要步骤如下:
-
方法查找顺序
-
Setter: 对于 setValue:forKey:,首先查找是否存在形如 - set<Key>: 的方法(例如 key 为 “name” 会查找 - setName:)。如果找不到,系统会查找 _set<Key>:。
-
Getter: 对于 valueForKey:,会先查找 - <key>、- get<Key>、- is<Key> 等方法。
-
-
直接访问实例变量
如果没有找到合适的访问方法,KVC 会借助 runtime 函数直接访问对象的实例变量。它会按照一定顺序查找(通常先查找 _key,再查找 key 等),从而实现对变量的读写。
-
错误处理
如果既没有方法也找不到实例变量,则会调用 valueForUndefinedKey: 或 setValue:forUndefinedKey:,默认情况下这会抛出异常。你可以在子类中重写这两个方法来定制错误处理行为。
底层关键点:
-
依赖于 runtime 的方法查找机制(通过 objc_msgSend 调用方法)。
-
利用 runtime 的 introspection 能力(例如 class_copyIvarList)来直接访问实例变量。
KVO(键值观察)的实现机制
KVO 通过 runtime 动态创建子类和方法替换的方式,实现了对对象属性变化的观察,主要过程如下:
-
动态子类化
当你对某个对象添加观察者时,系统会动态创建一个新的类(通常命名为 NSKVONotifying_<OriginalClassName>),该类继承自原始类。
-
这个动态创建的子类会重写被观察属性对应的 setter 方法。
-
-
修改 isa 指针
添加观察后,被观察对象的 isa 指针会被修改,指向这个动态生成的子类。这样,当外界调用 setter 方法时,实际执行的是动态子类中的方法。
-
重写的 setter 方法
在动态子类中重写的 setter 方法通常的执行顺序为:
-
通知即将改变: 调用 willChangeValueForKey:,触发 KVO 前通知。
-
调用原实现: 更新属性值,通常通过调用父类的实现(或者直接操作 ivar)。
-
通知已经改变: 调用 didChangeValueForKey:,触发 KVO 后通知,将变化信息传递给观察者。
这种方式确保了每次属性修改时,观察者能够接收到正确的通知。
-
-
移除观察者与恢复
当观察者移除后,系统会相应地清理这些动态创建的结构,并恢复对象原有的 isa 指针。
底层关键点:
-
动态生成类: 利用 runtime API(如 objc_allocateClassPair、class_addMethod 和 objc_registerClassPair)创建新的子类。
-
方法交换与重写: 使用 runtime 函数重写 setter 方法,从而在修改属性前后插入通知逻辑。
-
isa 指针修改: 改变对象的 isa 指针,使其指向动态生成的子类,达到“劫持”方法调用的效果。
消息转发
-
当一个对象无法响应某个方法时,除了动态方法解析外,还可以通过消息转发机制将该消息转发给其他对象处理。
-
消息转发主要涉及两个方法:-forwardingTargetForSelector:(第一阶段转发,可直接指定替代对象)和 -forwardInvocation:(第二阶段转发,允许你完全自定义消息转发逻辑)。
iOS的锁
1. NSLock
• 最基本的锁,使用简单,适用于简单的互斥保护。
2. NSRecursiveLock
• 递归锁,允许同一线程对同一锁重复加锁,适用于递归调用或者同一线程需要多次加锁的场景。
3. dispatch_semaphore
• GCD 提供的信号量,可以用作锁来控制资源的并发访问。常用于同步任务或者做简单的互斥保护。
4. Serial Dispatch Queue
• 利用串行队列来保证同一时间只有一个任务在执行,也可以达到类似互斥锁的效果,且常常更简单高效。
死锁
产生死锁的必要条件有四个:
-
互斥条件 : 指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
-
请求和保持条件 : 指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
-
不可剥夺条件 : 指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
-
环路等待条件 : 指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
持久化
1. UserDefaults(NSUserDefaults)
• 特点:
• 用于存储少量数据,如用户偏好、设置、简单状态等。
• 数据存储在 plist 文件中,操作简单。
• 适用场景:
• 保存 App 配置、用户设置、简单标识等不需要复杂查询的数据。
2. 文件存储
• 特点:
• 可以直接读写应用沙盒内的文件(如文本文件、JSON、Plist、二进制数据等)。
• 使用 FileManager 对文件进行管理。
• 适用场景:
• 保存图片、音频、缓存数据、日志等较大、非结构化的数据。
3. Core Data
• 特点:
• 苹果提供的对象图管理和持久化框架。
• 支持复杂的数据模型、关系查询、数据版本迁移等。
• 内置缓存、数据断层加载和自动更新功能。
• 适用场景:
• 处理大量结构化数据和复杂关系的应用,如社交 App、任务管理 App 等。
4. Keychain
• 特点:
• 专门用于存储敏感信息,比如密码、令牌、证书等。
• 提供安全加密存储机制。
• 适用场景:
• 保存用户登录凭证、密钥、敏感个人数据,确保安全性。
5. CloudKit
• 特点:
• 苹果官方的云数据存储解决方案。
• 支持 iCloud 数据同步,可以实现数据在多设备间共享。
• 适用场景:
• 数据需要在多个设备上同步,或者希望利用云端存储进行备份和共享。
Block
-
全局 Block(Global Block)
-
特点:如果 Block 没有捕获任何外部变量(或者只捕获了静态变量),它会被分配在全局内存中。
-
生命周期:全局 Block 在整个应用程序运行期间都存在,不需要额外的内存管理。
-
内部标识:通常是 __NSGlobalBlock__。
-
-
栈 Block(Stack Block)
-
特点:如果 Block 捕获了局部变量,则默认情况下会在栈上创建。
-
生命周期:栈 Block 的生命周期受限于所在的函数调用,一旦函数返回,栈 Block 也会随之销毁。
-
注意:如果需要在函数返回后使用这个 Block,就必须将它复制到堆上,否则会出现野指针问题。
-
内部标识:通常是 __NSConcreteStackBlock__。
-
-
堆 Block(Heap Block,也称为 Malloc Block)
-
特点:当你显式调用 copy 方法(或者通过声明属性时使用 copy 修饰符)将栈 Block 复制到堆上,就会生成堆 Block。
-
生命周期:堆 Block 的生命周期由引用计数管理,可以在函数返回后安全使用。
-
内部标识:通常是 __NSConcreteMallocBlock__。
-
Extension和Category
1. 支持的类型范围
-
Objective-C 的 Category
-
只能扩展继承自 Objective-C 的类(例如 NSObject 及其子类)。
-
不能扩展结构体、枚举等基本类型。
-
-
Swift 的 Extension
-
可以扩展所有类型,包括类、结构体、枚举和协议。这使得扩展的适用范围更广泛。
-
2. 添加成员的种类
-
Objective-C 的 Category
-
只能添加方法(实例方法和类方法),以及属性的声明(只能通过方法实现计算属性效果,无法添加存储属性)。
-
虽然可以利用关联对象“模拟”存储属性,但并非语言本身支持。
-
-
Swift 的 Extension
-
可以添加方法、计算属性、下标、嵌套类型和协议一致性(使类型符合新的协议)。
-
同样,Swift extension 不允许添加存储属性或属性观察器。
-
3. 重写与方法覆盖
-
Objective-C 的 Category
-
由于运行时的动态性,Category 有时可以“覆盖”原有方法的实现,但这种做法不被推荐,因为可能引发冲突和难以预料的行为。
-
-
Swift 的 Extension
-
不能在扩展中重写已有的方法。扩展只能添加新功能,任何与原有方法名冲突的定义都会导致编译错误。
-
4. 编译时与运行时
-
Objective-C 的 Category
-
是基于运行时机制,方法解析和合并在程序加载时动态完成。这种动态特性虽然灵活,但也容易引发方法冲突或覆盖问题。
-
-
Swift 的 Extension
-
在编译时生效,与主类型合并在一起,提供更强的类型安全性和编译时检查,减少了运行时潜在的风险。
-
5. 语法和可见性控制
-
Objective-C 的 Category
-
语法上以 @interface ClassName (CategoryName) 的形式定义,所有方法默认都是公开的,缺乏细粒度的访问控制。
-
-
Swift 的 Extension
-
使用 extension 关键字定义扩展,可以通过访问控制修饰符(如 public、internal、private)来控制扩展中添加成员的可见性,更符合模块化和封装的设计理念。
-