iOS中MVC、MVVM
MVC
在 iOS 开发中,MVC(Model View Controller)是构建iOS App的标准模式,M(Model)是指业务数据;V(View)指用户界面,即UIView;C(Controller)是指控制器,即UIViewController。Controller可以直接与 Model、View对话,控制数据传递与视图切换;Model 通过 Notification 和 KVO 机制与 Controller 间接通信;View 通过关联action、代理(delegate)与 Controller 间接通信;Model 和 View 不能直接通信,需通过 Controller 传递。
优点
- 耦合性低
- 重用性高
- 可维护性高
缺点
1.臃肿的 ViewController
Controller协调模型和视图之间的所有交互。比如: 业务逻辑、数据转化、视图状态与切换等
2.遗失(无处安放)的网络逻辑
在MVC中并没有用来负责网络逻辑的层,你可能试着把它放在Model对象里,但是也会很棘手,因为网络调用应该使用异步,比持有它的model生命周期更长,事情将变的复杂。显然View里面做网络请求那就更格格不入了,因此只剩下Controller了, 但无疑增加了Controller工作量。若不这样,何处才是网络逻辑的家呢
3.太过于轻量级的 Model
4.较差的可测试性
由于View Controller混合了视图处理逻辑和业务逻辑,分离这些成分的单元测试成了一个艰巨的任务。
MVVM
MVVM就是在MVC的基础上分离出业务处理的逻辑到VM(ViewModel)层,减少Controller的工作量,M(Model)是指业务数据;V(View)指用户界面,即UIView;C(Controller)是指控制器,隐式存在,即UIViewController;VM(ViewModel)是指视图模型,负责业务处理和数据转化。在MVVM中核心是ViewModel,Controller 和 View 都不能直接和 Model 通信,必须通过 ViewModel;ViewModel 直接和 Model 通信,不能直接与 View 通信;而 Controller 负责常规的UI逻辑处理、View和ViewModel之间的通信,不涉及业务逻辑,只需要知道结果,而这个过程由ViewModel去做。
优点
1.低耦合
视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的"View"上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变
2.可重用性
你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑
3.独立开发
开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计
4.可测试
界面素来是比较难于测试的,而现在测试可以针对ViewModel来写
目的
MVVM模式和MVC模式一样,主要目的是分离视图(View)和模型(Model)。
iOS怎么检查内存问题
- 通过Xcode -> Product -> Analyze 来分析代码是否潜在内存泄露问题
使用Xcode自带工具Instrument
Swift中guard、defer
guard
可以解决 if 嵌套问题。在保证条件满足情况下,才会让你通过, 否则在else中返回
defer
用于推迟执行,适合用来做清理、资源回收工作。在一个作用域中的defer语句自下而上调用。
iOS中UITableView的性能优化
1.对cell进行复用,不要重复创建Cell的实例
2.使用不透明视图
不透明的视图可以极大地提高渲染的速度。可以将cell及其子视图的opaque(不透明)属性设为YES(默认值)
3.避免使用图形特效
在UIImage中使用复杂的图形特效(例如渐变), Layer添加阴影
4.避免动态UI排版
用户滑动时动态添加子/移除视图或修改视图属性
5.减少预加载时间
缓存图片数据,提前计算Cell的高度并缓存起来,减少初始化的过程
6.不要阻塞主线程
耗时操作放在子线程进行,比如网络请求
7.预渲染图像
如果有图片
8.不要做多余的绘制工作
在实现drawRect:的时候,它的rect参数就是需要绘制的区域,这个区域之外的不需要进行绘制
9.滑动时按需加载对应的内容
iOS代理(Delegate)、通知(Notification)、协议(Protocol)的应用场景
Delegate
注重的是过程,是一对一的,对于一个协议(Protocol)就只能用一个代理,所以单例不能用代理。
Notification
一个消息通知机制,类似广播, 一对多的。观察者只需要向消息中心注册感兴趣的东西,当有地方发出这个消息的时候,通知中心会发送给注册这个消息的对象。
Protocol
一个自定义方法的集合,由Delegate去实现这些方法
通知(Notification)与代理(Delegate)的优缺点
- 通知可以一对多通信,代理只能一对一。
- 代理的执行效率比较高。
- 通知的使用比较简单, 但是需要注意通知的移除、重复注册问题。
通知太多的情况下,代码比较难维护,建议选择性使用。
iOS中的KVC、KVO
KVC
键值编码,是一种间接访问实例变量的方法。允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值,而不需要调用明确的存取方法,在运行时可动态地访问和修改对象的属性。
KVO
键值观察,基于KVC实现的,它能够观察一个对象的某个属性值的变化。其基本思想是:对目标对象的某属性添加观察,当该属性发生变化时,通过触发观察者对象实现的KVO接口方法,来自动的通知观察者。
注意
纯Swift类和结构体不支持KVC、KVO
实现一个单列
Objective-C
(MyClass *)shared {
static MyClass *sharedInstance = nil;
static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{
sharedInstance = [[MyClass alloc] init];
});
return sharedInstance;
}Swift
class MyClass { static let shared = MyClass() private init() { } }
iOS实现多线程技术
NSThread
Objective-C的线程对象,一个NSThread对象就是一条线程.
创建线程的方式有:
1. 创建线程不启动,需调用start方法启动
initWithTarget:selector:object:
2、创建并启动线程
detachNewThreadSelector:toTarget:withObject:
3、隐式创建并启动线程
performSelectorInBackground:withObject:
GCD
Apple 开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并发任务。
有以下优点:
1. GCD 可用于多核的并行运算
2. GCD 会自动利用更多的 CPU 内核(比如双核、四核)
3. GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
4. 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码
NSOperation/NSOperationQueue
NSOperation/NSOperationQueue是基于GCD更高一层的封装,完全面向对象。NSOperation是个抽象类,并不具备封装操作的能力,需要子类化或者使用系统提供子类:NSInvocationOperation、NSBlockOperation
有以下优点:
1.可添加完成的代码块,在操作完成后执行
2.添加操作之间的依赖关系,方便控制执行顺序
3.设定操作的优先级
4.可以很方便的取消一个操作的执行
5.使用KVO观察对操作执行状态的更改:isExecuteing,isFinished,isCancelled
NSThread、GCD、NSOperation比较
- NSThread 轻量级,使用简单,难以管理多个线程,另外开发者需要管理线程的生命周期、线程同步等
- GCD不需要开发者管理线程(创建线程、调度任务、销毁线程),重心放在功能实现,与Block配合,使用起来非常灵活,代码简洁
NSOperation 完全面向对象,不需要关心线程管理,数据同步的事情,可控制最大并发数量,可控制任务优先级,可设置任务间的依赖关系,可观察任务状态变化
栈和堆的区别
堆:动态分配内存,需要程序员自己申请,程序员自己管理
栈:自动分配内存,自动销毁,先入后出,栈上的内容存在自动销毁的情况
OC的类可以多继承吗?如果没有,有什么办法可以实现
OC不支持多继承(Swift也不支持多继承),但可以通过实现多协议来间接实现多继承。比如:在A类中定义协议A,B类中定义协议B;然后在C类中实现协议A、协议B。
strong,weak,retain,assign,copy,nonatomic,atomic的作用与区别
- 作用
strong:强引用,引用计数增加1,当retainCount=0时,该对象才会被销毁
weak:弱引用,不改变引用计数, 指针指向的地址一旦被释放,这些指针都将被赋值为ni(这样的好处能有效的防止野指针)
retain:对参数进行release旧值再retain新值,引用计数增加1(对其他NSObject和其子类)
copy:建立一个引用计数为1的对象,然后释放旧对象(NSString)
assign:简单赋值,不改变引用计数,主要用于非对象类型
readwrite:属性可读可写
readonly:属性是只读
nonatomic:禁止多线程,变量保护,提高性能
atomic:提供多线程安全,避免变量的读写不同步的问题 区别
1.copy与retain的区别copy其实是建立了一个相同的对象,而retain不是;
copy是内容拷贝,retain是指针拷贝;
copy是内容的拷贝 ,对于像NSString,的确是这样,如果拷贝的是NSArray这时只是copy了指向array中相对应元素的指针.这便是所谓的"浅复制"。
2.weak和strong的区别
weak和strong不同的是: 当一个对象不再有strong类型的指针指向它的时候,它会被释放,即使还有weak型指针指向它。一旦最后一个strong型指针离去 ,这个对象将被释放,所有剩余的weak型指针都将被清除。
3.copy与strong区别
当源字符串是NSString时,由于字符串是不可变的,所以,不管是strong还是copy属性的对象,都是指向源对象,copy操作只是做了次浅拷贝。当源字符串是NSMutableString时,strong属性只是增加了源字符串的引用计数,而copy属性则是对源字符串做了次深拷贝,产生一个新的对象,且copy属性对象指向这个新的对象。另外需要注意的是,这个copy属性对象的类型始终是NSString,而不是NSMutableString,因此其是不可变的。
Objective-C中类别(Category)与扩展(Extension)的区别
Extension能为某个类附加额外的属性,成员变量,方法声明
Category只能扩充方法,不能扩展属性和成员变量
iOS本地存储数据有哪些方式
- NSUserDefaults
- plist存储
- 归档(NSKeyedArchiver/NSKeyedUnarchiver,实现NSCoding协议)
- SQLite
CoreData
Swfit类和结构体的区别
相同点:
- 可以定义属性用于储值
- 可以定义方法实现功能
- 可以定义构造器(构造函数)用于生成初始化值
- 可以通过扩展增加默认实现的功能
- 可以继承协议以提供某种标准功能
不同点:
- 类有析构函数,结构体没有析构函数
- 类的对象是引用类型,而结构体是值类型(Copy)
- 类可以继承某个类,结构体不可以
类中引用计数允许对一个类的多次引用, 结构体不使用引用计数
用Swfit实现一个泛类型函数用于交换2个变量的值
func valueSwitch<Base>(a: inout Base, b: inout Base) {
let t = a
a = b
b = t
}
用Swfit定义一个block
let myBlock: (() -> Void) = {
// do something
}
__block和__weak的区别
__weak: 使用block时为了避免循环引用,用来弱引用某个对象
__block: 可以改变变量的作用域,使得变量的作用域扩展到block内部,在block内部就可以对外部的变量进行操作了
block是什么
Block对象是一个C级别的语法和运行机制, 与标准的C函数类似,但它包含了与堆、栈内存绑定的变量。因此,Block对象包含着一组状态数据, 这些数据在程序执行时用于对行为产生影响。
实现冒泡排序(从大到小排序)
C++
int nums[] = {8, 5, 1, 6, 9, 100, 2, 88, 77}; int len = sizeof(nums) / sizeof(nums[0]); for (int index = 0; index<len; index++) { int current = nums[index]; for (int i = index+1; i<len; i++) { int next = nums[i]; if (current < next) { nums[index] = next; nums[i] = current; current = next; } } }
Swift
var nums: [Int] = [8, 5, 1, 6, 9, 100, 2, 88, 77] let len = nums.count for index in 0..<len { var current = nums[index] for i in index+1..<len { let next = nums[i] if current < next { nums[index] = next nums[i] = current current = next } } }
Objective-C
NSMutableArray<NSNumber *> *nums = [NSMutableArray arrayWithArray:@[@8, @5, @1, @6, @9, @100, @2, @88, @77]]; NSInteger len = nums.count; for (int index = 0; index<len; index++) { NSNumber *current = nums[index]; for (int i = index+1; i<len; i++) { NSNumber *next = nums[i]; if (current.intValue < next.intValue) { nums[index] = next; nums[i] = current; current = next; } } }
SD_WebImage 原理
原理
1、显示placeholderImage 2、SDImageCache从缓存中查找图片是否已经下载 3、先从内存图片缓存查找是否有图片 4、如果内存中有图片缓存,显示图片 5、如果内存中没有,生成NSInvocationOperation添加到执行队列开始从硬盘查找图片缓存 6、如果硬盘中有,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存),显示图片 7、如果硬盘中没有,说明该图片没有缓存,需要下载图片,共享或重新生成一个下载器SDWeb- -ImageDownLoader开始下载图片 8、开始图片网络请求,下载数据 9、数据下载完成后交给SDWebImageDecoder做图片解码 10、回调展示图片 11、图片保存到硬盘缓存和内存缓存 12、SDImageCache初始化会注册一些通知,在内存警告或退到后台的时候清理内存图片缓存, -应用结束的时候清理过期图片
优点
- 通过runtime动态绑定key,防止重复下载
- url失效处理等
- 图片过期清理
异步下载和保存,不阻塞主线程
### NSRunloop 1、“消息”循环,等待消息(会休眠)->接收消息->处理消息。通过上面的代码,runloop本质就是提供了一种消息处理模式。 2、NSRunLoop有不同的模式(5种),对应不现的应用场景,比如:触摸事情、键盘鼠标输入输出,设备连接等,我们一般默认模式,其实也比较少用
autorelease
1、autorelease就是自动释放,会像C语言的自动变量那样来对待对象实例。当超出作用域时,对象实例的release方法被调用。
2、NSAutoreleasePool对象的生命周期相当于C语言变量的作用域。对于所有调用过autorelease实例方法的对象,在废弃NSAutoreleasePool对象时,都将调用release实例方法
Swift Copy-On-Write
Copy-On-Write故名思议就是写时复制,当我们对变量进行写操作的时候会触发拷贝操作。但是我们也不能在每一次写入的时候都拷贝,思考一下,如果该变量的引用计数只有1,那就没有任何拷贝的必要。所以在拷贝前我们需要检测变量的引用计数是否唯一。在swift中提供了isKnownUniquelyReferenced,它能检查一个类的实例是不是唯一的引用。
swift String 与 NSString的不同
String 是结构体,NSString 是类,这是它们的根本区别。
String 是值类型,NSString 引用类型
swift协议 OC中的协议
1、OC的协议必须由deleagate去实现。
2、Swift的协议可以通过extension实现功能,类只要继承这个协议,不一定需要实现,也能具备协议中的特性,swift 协议可以定义属性。
为什么类别不能添加属性
分类里使用@property声明属性,只是将该属性添加到该类的属性列表,并声明了setter和getter方法,但是没有生成相应的成员变量,也没有实现setter和getter方法。所以说分类不能添加属性。
什么时候使用GCD,什么时候使用NSOperation
1、GCD使用情况较多,简单高效,有更高的并发和执行能力;复杂的任务通过NSOperation实现。(简单任务使用GCD,复杂任务使用NSOperation)。
2、如果需要观察任务状态变化,使用NSOperation,否则使用 GCD。
3、多任务情况下,如果任务之间存在依赖关系,或者需要设定任务的优先级,使用NSOperation,否则使用GCD。
gcd 造成死锁的原因和解决办法
原因:线程同步、相互等待
解决:使用异步线程
block 与代理 应用场景
1、block注重结果的传输,比如一个网络请求我只需要知道成功或者失败了,这时应该使用Block;代理更注重过程信息的传输,比如一个网络请求有成功、失败、缓存、https 验证,网络进度等,这时候就要使用代理了
2、有多个相关方法。假如每个方法都设置一个 block, 这样会更麻烦。而 delegate 让多个方法分成一组,只需要设置一次,就可以多次回调。当多于 3 个方法时就应该优先采用 delegate。
NSTimer 为什么不精确,怎么解决
1、主线程堵塞,定时器也堵塞。
1、在run loops循环过程中,被NSTimer触发事件阻塞了,导致循环不能及时进行下去,延误之后NSTimer触发时间。
2、在run loops循环过程中,在某一时刻主线程发生了阻塞情况,导致循环不能及时进行下去,厌恶NSTimer触发时间。
3、在run loops循环过程中,发生了模式的转换,(比如UIScrollView的滑动) 导致原有模式下的NSTimer不会正常触发。
解决办法
1、不要把定时器放入主线程中(可以放到子线程中)
2、不要把耗时操作放到主线程(耗时操作使用异步线程)
3、定时器触发的函数中不要进行耗时操作(如不能避免,请使用移到其他线程进行)
iOS 有哪些锁
iOS有互斥锁,递归锁,读写锁,信号量,条件锁,自旋锁等
还不快抢沙发