Objective-C(OC)运行时具有以下一些重要特性:
一、动态特性
OC 的运行时具有高度的动态性。这意味着很多操作可以在程序运行过程中动态地进行改变和调整。例如,方法调用是在运行时根据接收消息的对象的实际类型来确定的。可以在运行时向一个对象发送未知的消息,运行时系统会尝试查找相应的方法实现。如果找不到,还可以通过动态方法解析和消息转发机制来处理这种情况,使得程序在面对未知情况时具有更大的灵活性。
二、面向对象增强
- 继承和多态在 OC 的运行时中得到了充分体现。子类可以继承父类的属性和方法,并在运行时根据实际对象的类型来决定调用哪个方法实现,实现了多态性。
- 类别(Categories)允许在不修改原始类的情况下为已有类添加新方法,这种扩展在运行时生效,为程序的功能扩展提供了便利。
- 协议(Protocols)定义了方法的规范,运行时可以检查对象是否遵循特定协议,使得不同的类可以以一种统一的方式实现某些行为。
三、内存管理
OC 的运行时采用引用计数的内存管理方式。运行时系统跟踪对象的引用计数,当引用计数变为零时自动释放对象占用的内存。同时,自动释放池机制可以管理临时对象的生命周期,减少手动管理内存的负担。
四、反射能力
OC 的运行时提供了一定的反射能力。可以在运行时获取类、对象和方法的信息,比如获取类的方法列表、对象的类名以及方法的名称等。还可以根据类名在运行时动态地创建对象,为程序的动态性和灵活性提供了更多可能。
应用程序的生命周期,主要是什么:
application:willFinishLaunchingWithOptions: 程序启动
application:didFinishLaunchingWithOptions: 入口,只执行一次,启动完成准备开始运行
applicationWillResignActive: 切换到非活动状态,如按下home键、切换程序
applicationDidBecomeActive: 切换到激活状态
applicationDidEnterBackground: 应用程序进入后台
applicationWillEnterForeground: 应用程序将要被激活
applicationWillTerminate: 应用程序将要退出
App的启动过程:
打开程序——执行main函数——UIAPPlicationMain函数——初始化UIAPPlicationMain函数(设置代理,开启runloop)——监听系统事件,通知AppDelegate——程序结束
总结:面试官问的是应用程序的生命周期,而我答的是Viewcontroller的生命周期,面试官主要想听到的关键词应该是:main函数、UIApplicationMain、AppDelegate、runloop、监听
App 的生命周期主要包括以下几个阶段:
一、未运行阶段
当 App 未被启动时,它处于未运行状态,存在于设备的存储中但没有在内存中运行。
二、启动阶段
- 当用户点击 App 图标或通过其他方式启动 App 时,系统首先为 App 分配资源并创建进程。
- 加载基本的可执行文件和资源,然后进入
didFinishLaunchingWithOptions
方法,在这个方法中可以进行一些初始化操作,如设置根视图控制器、配置服务等。
三、活跃阶段
- App 显示在前台,用户可以与 App 进行交互。
- 此时 App 会不断接收事件并响应,如触摸事件、传感器事件等。
四、后台阶段
- 当用户切换到其他 App 或者按下 Home 键时,App 进入后台状态。
- 在这个阶段,App 可以执行一些有限的任务,如继续播放音乐、完成未完成的网络请求等。
- 可以在
applicationDidEnterBackground
方法中进行一些后台任务的准备工作。
五、挂起阶段
- 如果 App 在后台一段时间没有活动,系统可能会将 App 挂起,以节省资源。
- 挂起的 App 处于一种冻结状态,不会执行任何代码,但它的内存仍然被保留。
六、重新激活阶段
- 如果用户再次切换回该 App,App 会从挂起状态重新激活。
- 系统会调用
applicationWillEnterForeground
方法,然后再次进入活跃状态,App 可以恢复之前的状态并继续响应用户交互。
七、终止阶段
- 在某些情况下,系统可能会终止 App,例如内存不足或者用户强制关闭 App。
- 当 App 被终止时,系统会调用
applicationWillTerminate
方法,可以在这个方法中进行一些清理工作,保存数据等。
介绍一下 iOS App 的内存管理机制
iOS App 的内存管理机制通过自动引用计数和自动释放池等技术,使得开发者能够更方便地管理内存,同时也需要开发者注意避免循环引用和及时释放不再使用的对象,以确保应用的内存使用高效且不会出现内存泄漏。在处理内存警告时,应采取适当的措施来释放内存,以提高应用的稳定性和性能。
WKWebView 和 UIWebView 都是用于在 iOS 平台上显示网页内容的组件,但它们有以下主要区别:
一、性能方面
- 加载速度:WKWebView 通常比 UIWebView 加载网页更快。WKWebView 采用了更先进的渲染引擎和多进程架构,能够更高效地处理网页内容的加载和显示。
- 资源占用:WKWebView 在内存和 CPU 资源的使用上更加高效。它能够更好地管理资源,减少内存泄漏和卡顿现象的发生。
二、功能特性
- JavaScript 支持:WKWebView 对 JavaScript 的执行速度更快,并且支持更多的 JavaScript 特性。它还提供了更好的与 JavaScript 交互的方式,使得开发人员能够更方便地实现网页与原生应用的交互。
- 导航控制:WKWebView 提供了更强大的导航控制功能,包括前进、后退、刷新等操作。它还支持自定义导航栏和手势操作,提供更好的用户体验。
- 插件支持:UIWebView 支持一些插件,如 Adobe Flash,但这些插件在现代网页开发中逐渐被淘汰。WKWebView 不支持 Flash 等插件,但它支持 HTML5 视频和音频播放,以及其他现代网页技术。
三、安全与隐私
- 安全沙箱:WKWebView 运行在更严格的安全沙箱中,与原生应用的其他部分隔离开来。这提高了应用的安全性,减少了恶意网页攻击的风险。
- 隐私保护:WKWebView 提供了更好的隐私保护功能,例如限制网页对设备信息的访问、阻止第三方 Cookie 跟踪等。
四、开发体验
- API 设计:WKWebView 的 API 设计更加现代化和简洁,易于使用和理解。它提供了更多的异步操作和回调方法,使得开发人员能够更方便地处理网页加载过程中的各种情况。
- 兼容性:由于 WKWebView 是较新的技术,它在一些旧版本的 iOS 系统上可能存在兼容性问题。而 UIWebView 在旧版本系统上的兼容性更好,但随着时间的推移,苹果逐渐鼓励开发人员使用 WKWebView。
总之,WKWebView 在性能、功能、安全和开发体验等方面都优于 UIWebView。虽然在一些旧版本系统上可能存在兼容性问题,但随着 iOS 系统的不断更新和发展,WKWebView 已经成为 iOS 开发中显示网页内容的首选组件。
KVC和KVO
以下是对 Objective-C 中键值编码(Key-Value Coding,KVC)和键值观察(Key-Value Observing,KVO)的简单介绍:
一、键值编码(KVC)
- 定义与作用:KVC 是一种机制,允许通过字符串形式的键(key)来访问和操作对象的属性。它提供了一种灵活的方式来设置和获取对象的属性值,而不需要直接访问实例变量。
- 使用方法:
- 通过键来设置属性值,例如使用
setValue:forKey:
方法。比如[obj setValue:@"newValue" forKey:@"someProperty"]
,可以设置对象obj
的名为someProperty
的属性值为"newValue"
。 - 通过键来获取属性值,如使用
valueForKey:
方法。像NSString *value = [obj valueForKey:@"someProperty"]
可以获取对象obj
的someProperty
属性的值。 - 可以处理集合属性,如使用
setValue:forKeyPath:
来设置对象的嵌套属性或集合中的元素。
- 通过键来设置属性值,例如使用
二、键值观察(KVO)
- 定义与作用:KVO 允许对象观察另一个对象特定属性的变化。当被观察的属性值发生变化时,观察者会收到通知,从而可以做出相应的反应。
- 使用方法:
- 首先,需要注册成为被观察对象属性的观察者。使用
addObserver:forKeyPath:options:context:
方法来注册观察者。例如[observedObject addObserver:self forKeyPath:@"someProperty" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]
,表示当前对象(self
)观察observedObject
的someProperty
属性变化。 - 当被观察的属性值发生变化时,观察者会收到
observeValueForKeyPath:ofObject:change:context:
方法的调用。在这个方法中,可以处理属性变化的情况,比如更新界面或执行其他逻辑。 - 最后,当不再需要观察时,使用
removeObserver:forKeyPath:
方法来移除观察者,以避免内存泄漏。
- 首先,需要注册成为被观察对象属性的观察者。使用
总的来说,KVC 和 KVO 在 Objective-C 中提供了强大的机制来实现动态的属性访问和属性变化的通知,有助于构建更加灵活和响应式的应用程序。
runtime和runloop
一、运行时(Runtime)
- 定义与作用:Objective-C 运行时是一个在运行时执行的库,它允许程序在运行时动态地执行各种操作,如动态地创建类和对象、动态地调用方法、进行消息转发等。它为 Objective-C 语言提供了强大的动态特性。
- 主要功能:
- 动态方法调用:可以在运行时根据对象的类型来确定要调用的方法。例如,可以向一个未知类型的对象发送消息,运行时系统会在对象所属的类以及其继承体系中查找对应的方法实现并执行。
- 动态创建类和对象:可以在运行时动态地创建新的类和对象。可以通过修改类的定义、添加新的方法和属性等方式来扩展已有类的功能。
- 消息转发:当一个对象接收到一个无法响应的消息时,运行时系统可以通过消息转发机制将消息转发给其他对象,以尝试找到能够处理该消息的对象。
- 内存管理:运行时系统负责管理对象的内存分配和释放,通过引用计数等机制来确保对象在不再被使用时能够被正确地回收内存。
二、运行循环(RunLoop)
- 定义与作用:RunLoop 是一个事件处理循环,它负责接收输入源的事件并将其分发给相应的处理程序。在 iOS 和 macOS 中,每个线程都有一个与之关联的 RunLoop,它可以让线程在没有任务时进入休眠状态,以节省系统资源,当有事件发生时再唤醒线程进行处理。
- 主要功能:
- 事件处理:RunLoop 会不断地从输入源(如触摸事件、定时器、网络事件等)中获取事件,并将其分发给相应的处理程序。例如,当用户触摸屏幕时,触摸事件会被添加到 RunLoop 的输入源中,RunLoop 会将该事件分发给相应的视图进行处理。
- 保持线程活跃:RunLoop 可以让线程在没有任务时进入休眠状态,当有事件发生时再唤醒线程进行处理。这样可以避免线程在没有任务时一直占用 CPU 资源,提高系统的性能和响应速度。
- 定时器管理:可以使用 RunLoop 来管理定时器,确保定时器在正确的时间触发。RunLoop 会在合适的时候唤醒线程,执行定时器的回调方法。
- 网络连接管理:在进行网络连接时,可以使用 RunLoop 来等待网络响应。RunLoop 会在网络连接有数据可读或可写时唤醒线程,进行相应的处理。
总之,运行时和运行循环在 Objective-C 中扮演着重要的角色。运行时提供了强大的动态特性,使得程序可以在运行时进行各种灵活的操作;而运行循环则负责处理事件,保持线程活跃,提高系统的性能和响应速度。
RunLoop 的运行步骤:
-
通知 Observers 进入 RunLoop。
- RunLoop 即将开始运行时,会通知注册的观察者,让它们知道 RunLoop 即将启动。
-
通知 Observers 即将处理 Timer 事件。
- 如果有定时器(Timer)注册到当前 RunLoop,在准备处理定时器事件之前,通知观察者。
-
通知 Observers 即将处理 Source0(非基于端口的输入源)事件。
- Source0 通常是由 AppKit 或 UIKit 内部自动触发的事件,比如触摸事件等。在处理这些事件之前通知观察者。
-
处理 Source0 事件。
- 如果有 Source0 事件等待处理,RunLoop 会执行相应的处理函数。
-
如果有 Source1(基于端口的输入源)事件到来且处于等待状态,立即处理这个事件。
- Source1 通常是与系统内核或其他底层机制相关的事件,比如网络连接事件等。如果有这样的事件等待处理,优先处理 Source1。
-
通知 Observers 线程即将进入休眠状态等待新的事件。
- 如果没有更多的事件需要处理,RunLoop 准备进入休眠状态等待新的事件到来时,通知观察者。
-
线程进入休眠状态,等待被唤醒。
- RunLoop 使当前线程进入休眠状态,直到有新的事件到来或者超时时间到达。
-
当有事件到来时,唤醒线程并处理事件。
- 如果有新的事件到来,比如定时器触发、外部输入源有数据可读等,RunLoop 会唤醒线程并处理相应的事件。
-
处理唤醒时收到的事件。
- 根据唤醒的原因处理相应的事件,可能是定时器事件、输入源事件等。
-
通知 Observers RunLoop 结束。
- RunLoop 运行结束时,通知观察者 RunLoop 已经完成了一次循环。
OC的事件响应流程
在 Objective-C(OC)中,事件响应流程主要涉及以下几个关键步骤:
一、事件产生
事件可以由用户操作(如触摸屏幕、点击按钮等)、系统通知或其他外部因素触发。例如,用户在屏幕上点击一个按钮,就产生了一个触摸事件。
二、事件传递
- 从窗口开始,事件被传递到当前活跃的视图层次结构中。窗口会将事件传递给最顶层的视图,然后视图按照层次结构依次向下传递。
- 每个视图都有机会响应事件。如果一个视图不能处理事件,它会将事件传递给它的父视图,直到找到一个能够处理事件的视图。
三、响应者对象
- 在视图层次结构中,能够响应事件的对象被称为响应者对象。通常,
UIView
及其子类、UIViewController
等都是响应者对象。 - 响应者对象通过实现特定的方法来处理事件。例如,
touchesBegan:withEvent:
方法用于处理触摸开始事件。
四、响应链
- 响应者对象组成了一个响应链。如果一个视图不能处理事件,它会沿着响应链向上传递事件,直到找到一个能够处理事件的对象。
- 响应链通常从具体的视图开始,经过视图的父视图、视图控制器,一直到窗口或应用程序的委托对象。
五、事件处理
- 当一个响应者对象接收到事件时,它可以决定是否处理事件。如果响应者对象处理事件,它会执行相应的事件处理代码。
- 例如,如果一个按钮接收到触摸事件,它可能会触发一个动作方法,执行特定的业务逻辑。
总之,OC 的事件响应流程是一个从事件产生到事件传递、再到响应者对象处理事件的过程。通过响应链的机制,确保事件能够被正确地处理,从而实现用户与应用程序的交互。
UIViewController
的生命周期主要包括以下几个阶段:
一、创建阶段
init
方法:初始化控制器,可以在这里进行一些基本的初始化设置,但此时视图还未加载。initWithNibName:bundle:
:如果使用 nib 文件进行初始化,会调用这个方法。可以在此方法中传入特定的 nib 文件名和资源束来进行更定制化的初始化。
二、加载视图阶段
loadView
:当需要加载视图时被调用。默认情况下,如果没有通过 nib 文件或 storyboard 创建视图,会在这里手动创建视图。一般情况下不建议直接调用这个方法,而是让系统在需要的时候自动调用。viewDidLoad
:在视图加载完成后调用。这是进行进一步视图初始化和设置的主要地方,可以在这里设置子视图、配置布局、加载数据等。
三、出现阶段
viewWillAppear
:在视图即将显示在屏幕上时调用。可以在这里进行一些准备工作,如更新界面状态、设置动画初始值等。这个方法可能会被多次调用,例如当控制器的视图因为导航控制器的切换等原因即将再次显示时。viewDidAppear
:在视图完全显示在屏幕上之后调用。可以在这里开始一些需要在视图显示后进行的操作,如启动动画、开始定时器等。
四、消失阶段
viewWillDisappear
:在视图即将从屏幕上消失时调用。可以在这里进行一些清理工作,如停止动画、保存用户输入的数据等。这个方法也可能会被多次调用。viewDidDisappear
:在视图完全从屏幕上消失后调用。可以在这里进行一些后续的清理工作,如停止定时器、释放占用的资源等。
五、内存警告阶段
didReceiveMemoryWarning
:当系统发出内存警告时调用。可以在这里释放一些不必要的内存占用,如缓存的数据、未使用的视图等,以避免被系统强制终止。
六、销毁阶段
dealloc
:在控制器被释放时调用。可以在这里释放控制器持有的强引用资源,如移除观察者、关闭网络连接等,以确保没有内存泄漏。
设计模式是什么? 你知道哪些设计模式,并简要叙述?
MVC是最普遍认知的设计模式,MVC模式将页面的逻辑分为3块:Model(模型数据业务)、View(UI展示业务)、Controller(协调者-控制器)
这样的划分很好理解,维护时,只要找到对应的那一块进行修改就好了。
在iOS开发中,UIKIt框架是将控制器Controller与View进行绑定了的,每个控制器都有View对象,代码添加UI子控件细节或者在xib与storyboard中子视图可以直接与controller进行关联,都会导致控制器中难以避免很多本该View去负责的UI子控件细节处理放在了控制器Controller里面;而在Controller里面本身要处理的请求、控制器生命周期函数要处理的事情比较多的情况下,控制器就变得很臃肿。实际上这个设计模式在iOS中为:M-VC
MVVM设计模式介绍
M=Model, V=V+C, VM = ViewModel. 为什么会叫ViewModel?
先看这样划分后的分工:
View :UI界面的创建及根据传递的Model来更新视图的逻辑 。
Controller :负责控制器本身的生命周期,协调各个部分的绑定关系以及必要的逻辑处理。
ViewModel :网络请求、返回数据逻辑和缓存读写。
Model :用来将数据模型化,是数据查看更清晰,使用更方便。
总结来说就是:MVVM由MVP和WPF结合演变过来的,MVVM模式是将业务分为3块M-V-新对象,由于这个新对象是为View量身定制的(即它是视图的模型),被称为ViewModel。MVVM的核心是双向绑定。
MVVM的双向绑定
绑定的意思就是讲两个对象建立关联,其中一个改变另一个自动跟着变。假设Model与View绑定就意味着Model改变时View自动跟着变,不需要手动赋值那一步—即响应式
单向绑定:一般指模型数据变化触发对应的视图数据变化。
双向绑定:指模型数据,视图数据任意一方变化,都会触发另一方的同步变化。
双向绑定如何实现?
通信图
-
实际开发中的做法:让Controller拥有View和ViewModel属性,VM拥有Model属性;Controller或者View来接收ViewModel发送的Model改变的通知
-
用户的操作点击或者Controller的视图生命周期里面让ViewModel去执行请求,请求完成后ViewModel将返回数据模型化并保存,从而更新了Model;Controller和View是属于V部分,即实现V改变M(V绑定M)。如果不需要请求,这直接修改Model就是了。
-
第2步中的Model的改变,VM是知道的(因为持有关系),只需要Model改变后发一个通知;Controller或View接收到通知后(一般是Controller先接收再赋值给View),根据这个新Model去改变视图就完成了M改变V(M绑定V)
使用RAC(RactiveCocoa)框架实现绑定可以简单到一句话概括:
ViewModel中创建好请求的信号RACSignal, Controller中订阅这个信号,在ViewModel完成请求后订阅者调用sendNext:方法,Controller里面订阅时写的block就收到回调了。
结论:主体使用MVC,局部看情况使用MVVM设计模式,这样比较适用于当前的iOS开发。
MVVM 的优势
低耦合:View 可以独立于Model变化和修改,一个 viewModel 可以绑定到不同的 View 上
可重用性:可以把一些视图逻辑放在一个 viewModel里面,让很多 view 重用这段视图逻辑
独立开发:开发人员可以专注于业务逻辑和数据的开发 viewModel,设计人员可以专注于页面设计
可测试:通常界面是比较难于测试的,而 MVVM 模式可以针对 viewModel来进行测试
MVVM 的弊端
数据绑定使得Bug 很难被调试。你看到界面异常了,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题。数据绑定使得一个位置的 Bug 被快速传递到别的位置,要定位原始出问题的地方就变得不那么容易了。
对于过大的项目,数据绑定和数据转化需要花费更多的内存(成本)。主要成本在于:
数组内容的转化成本较高:数组里面每项都要转化成Item对象,如果Item对象中还有类似数组,就很头疼。
转化之后的数据在大部分情况是不能直接被展示的,为了能够被展示,还需要第二次转化。
只有在API返回的数据高度标准化时,这些对象原型(Item)的可复用程度才高,否则容易出现类型爆炸,提高维护成本。
调试时通过对象原型查看数据内容不如直接通过NSDictionary/NSArray直观。
同一API的数据被不同View展示时,难以控制数据转化的代码,它们有可能会散落在任何需要的地方。
delete Notification KVO 区别
一.delegate的优势:
1.非常严格的语法。所有将听到的事件必须是在delegate协议中有清晰的定义。
2.如果delegate中的一个方法没有实现那么就会出现编译警告/错误
3.协议必须在controller的作用域范围内定义
4.在一个应用中的控制流程是可跟踪的并且是可识别的;
5.在一个控制器中可以定义定义多个不同的协议,每个协议有不同的delegates
6.没有第三方对象要求保持/监视通信过程。
7.能够接收调用的协议方法的返回值。这意味着delegate能够提供反馈信息给controller
缺点:
1.需要定义很多代码:1.协议定义;2.controller的delegate属性;3.在delegate本身中实现delegate方法定义
2.在释放代理对象时,需要小心的将delegate改为nil。一旦设定失败,那么调用释放对象的方法将会出现内存crash
3.在一个controller中有多个delegate对象,并且delegate是遵守同一个协议,但还是很难告诉多个对象同一个事件,不过有可能。
二.notification
优势:
1.不需要编写多少代码,实现比较简单;
2.对于一个发出的通知,多个对象能够做出反应,即1对多的方式实现简单
3.controller能够传递context对象(dictionary),context对象携带了关于发送通知的自定义的信息
缺点:
1.在编译期不会检查通知是否能够被观察者正确的处理;
2.在释放注册的对象时,需要在通知中心取消注册;
3.在调试的时候应用的工作以及控制过程难跟踪;
4.需要第三方对喜爱那个来管理controller与观察者对象之间的联系;
5.controller和观察者需要提前知道通知名称、UserInfo dictionary keys。如果这些没有在工作区间定义,那么会出现不同步的情况;
6.通知发出后,controller不能从观察者获得任何的反馈信息。
三.KVO
优点:
1.能够提供一种简单的方法实现两个对象间的同步。例如:model和view之间同步;
2.能够对非我们创建的对象,即内部对象的状态改变作出响应,而且不需要改变内部对象(SKD对象)的实现;
3.能够提供观察的属性的最新值以及先前值;
4.用key paths来观察属性,因此也可以观察嵌套对象;
5.完成了对观察对象的抽象,因为不需要额外的代码来允许观察值能够被观察
缺点:
1.我们观察的属性必须使用strings来定义。因此在编译器不会出现警告以及检查;
2.对属性重构将导致我们的观察代码不再可用;
3.复杂的“IF”语句要求对象正在观察多个值。这是因为所有的观察代码通过一个方法来指向;
4.当释放观察者时不需要移除观察者。
block的类型
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,
最终都是继承自NSBlock类型(NSBlock 继承 NSObject)
副本源的配置存储域 复制效果
NSGlobalBlock ( _NSConcreteGlobalBlock ) 程序的数据区域 什么也不做
NSStackBlock ( _NSConcreteStackBlock ) 栈区 从栈复制到堆
NSMallocBlock ( _NSConcreteMallocBlock ) 堆 引用计数增加
以下是对 pthread、NSThread、GCD(Grand Central Dispatch)和 NSOperation 的简单介绍:
一、pthread
- 定义与特点:pthread 是 POSIX 线程标准,是 C 语言级别的多线程编程接口。它提供了底层的线程创建、管理和同步功能。
- 使用场景:适用于需要对线程进行精细控制的场景,例如对线程的属性进行复杂的设置。但由于是底层接口,使用起来相对复杂。
二、NSThread
- 定义与特点:Objective-C 层面的线程类。可以直接创建和管理线程对象,相对容易使用。可以设置线程的名称、优先级等属性。
- 使用场景:当需要直接操作线程对象并且对线程的生命周期有明确控制需求时可以使用。但相比 GCD 和 NSOperation,功能相对简单。
三、GCD
- 定义与特点:Grand Central Dispatch 是苹果提供的强大的并发编程框架。它基于任务和队列的概念,自动管理线程的创建和销毁,充分利用多核处理器的优势。
- 使用场景:
- 异步执行任务:可以方便地将耗时任务放到后台队列执行,不阻塞主线程。例如,加载网络数据、进行大量计算等。
- 并发执行多个任务:可以同时执行多个任务,提高程序的效率。
- 任务依赖管理:可以设置任务之间的依赖关系,确保某些任务在其他任务完成后执行。
四、NSOperation
- 定义与特点:是基于 Objective-C 的面向对象的抽象,用于表示任务。NSOperation 和 NSOperationQueue 一起使用,可以实现更高级的任务管理。
- 使用场景:
- 任务取消:可以方便地取消正在执行的任务。
- 任务依赖:与 GCD 类似,可以设置任务之间的依赖关系。
- 自定义操作:可以通过继承 NSOperation 类来创建自定义的任务类型,实现更复杂的任务逻辑。
总体而言,GCD 和 NSOperation 通常是在 iOS 和 macOS 开发中更常用的多线程编程方式,因为它们提供了更高级的功能和更方便的接口,而 pthread 和 NSThread 在特定情况下可能会被使用,例如需要与其他 C 语言库集成或者对线程进行非常底层的控制。
SDWebImage 和 AFNetworking 的底层原理介绍:
一、SDWebImage
-
异步加载图片:
- SDWebImage 主要通过异步的方式从网络或本地加载图片。当请求一个图片时,它不会阻塞主线程,确保应用的流畅性。
- 使用后台线程进行网络请求,一旦获取到图片数据,再切换回主线程进行图片的显示。
-
缓存机制:
- 内存缓存:使用 NSCache 来存储最近使用的图片,以便快速访问。当内存紧张时,系统会自动清理缓存。
- 磁盘缓存:将图片存储到磁盘上,以便下次使用时可以直接从磁盘读取,而无需再次进行网络请求。磁盘缓存使用文件系统来存储图片,并通过计算图片的 URL 的哈希值来确定存储路径。
-
图片解码和显示:
- 当获取到图片数据后,SDWebImage 会根据图片的格式进行解码。对于常见的图片格式如 JPEG、PNG 等,它会使用系统提供的图像解码函数进行解码。
- 解码后的图片会被显示在相应的 UI 组件上,通常是 UIImageView。SDWebImage 会自动处理图片的缩放和适配,以确保图片在不同尺寸的 UI 组件上显示正常。
二、AFNetworking
-
网络请求封装:
- AFNetworking 封装了 iOS 和 macOS 系统的底层网络请求 API,如 NSURLSession 和 NSURLConnection。它提供了一个简洁、易用的接口,让开发者可以方便地进行网络请求。
- 支持多种 HTTP 方法,如 GET、POST、PUT、DELETE 等。可以设置请求的头部信息、参数、超时时间等。
-
异步执行和回调:
- 网络请求在后台线程中执行,不会阻塞主线程。当请求完成后,通过回调函数将结果返回给主线程进行处理。
- 支持使用 block、delegate 或 notification 等方式来接收请求的结果。开发者可以根据自己的喜好选择合适的方式来处理请求的响应。
-
网络状态监测:
- AFNetworking 可以监测网络状态的变化,当网络状态发生改变时,可以自动调整网络请求的行为。
- 例如,当网络连接中断时,可以暂停正在进行的请求,当网络连接恢复时,可以自动恢复请求。
-
安全连接和证书验证:
- 支持 HTTPS 安全连接,可以进行证书验证和信任管理。确保网络请求的安全性。
总之,SDWebImage 和 AFNetworking 都是非常强大的开源库,它们的底层原理涉及到多线程、缓存管理、网络请求封装等技术,为开发者提供了方便、高效的图片加载和网络请求功能。
性能优化及内存管理在实际开发中的应运。
1.UITableView的数据条数太多时会消耗内存,可以给UITableViewCell、UICollectionViewCell、UITableViewHeaderFooterView设置正确的复用ID,充分复用。
2.有透明度的View(alpha值在0到1之间),opaque的值应该设置为YES,可以优化渲染系统、提高性能。(当alpha值为0或1时,opaque的值对性能没有影响)
3.避免过于庞大的XIB/StoryBord文件,当加载XIB文件的时候,所有的内容都会被加到内存里,如果有一个不会立刻用到的View,就是在浪费内存资源。
4.不要让主线程承担过多的任务,否则会阻塞主线程,使app失去反应。
5.加载本地图片时,应该保证UIImageView的大小和图片的大小相同,因为缩放图片是很消耗资源的,特别是在UIImageView嵌套在UIScrollView中的情况下。如果是从网络加载的图片,可以等图片加载完成以后,开启一个子线程缩放图片,然后再放到UIImageView中。
6.在合适的场景下选择合适的数据类型,对于数组:使用索引查询很快,使用值查询很慢,插入删除很慢,对于字典:用键来查找很快,对于集合:用值来查找很快,插入删除很快。
7.网络下载文件时压缩(目前AFNetworking已经默认压缩)
8.当UIScrollView嵌套大量UIView时会消耗内存,可以模仿UITableView的重用方式解决,当网络请求的时候可以使用延迟加载来显示请求错误的页面,因为网络请求错误的页面不会马上用到,如果每次都先加载出来会消耗内存。
9.不大可能改变但经常使用的东西可以使用缓存,比如cell的行高可以缓存起来,这样reloaddata的时候效率会很高。还有一些网络数据,不需要每次都请求的,应该缓存起来,可以写入数据库,也可以写入plist文件。
10.在appDelegate和UIViewController中都有处理内存警告的方法,注册并接受内存警告的通知,一旦收到通知就移除缓存,释放不需要的内存空间。
11.一些对象的初始化很慢,比如NSDateFormatter和NSCalendar,但你又必须要使用它,这时可以重用它们,有两种方式,第一种是添加属性到你的类,第二种是创建静态变量(类似于单例)
12.服务器端和客户端使用相同的数据结构,避免反复处理数据,UIWebView中尽可能少的使用框架,用原声js最好,因为UIView的加载比较慢。
13.在循环创建变量处理数据的时候,使用自动释放池可以及时的释放内存。
14.加载本地图片的时候,如果只使用一次使用imageWithContentOfFile方法,因为imageNamed方法会缓存图片,消耗内存。