1.一个NSObject对象占用多少内存?
64bit: sizeof 也是以8字节对齐,是个运算符直接传类型计算
class_getInstanceSize函数 传类,得最终创建得至少占用多少内存空间
系统分配了16个字节给NSObject对象(通过malloc_size函数获得以16字节对齐)
但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得以8字节对齐)
2.对象的isa指针指向哪里?
instance对象的isa指向class对象
class对象的isa指向meta-class对象
meta-class对象的isa指向基类的meta-class对象
3.OC的类信息存放在哪里?
instance对象的isa指向class对象 64位开始需要 &ISA_MASK获取
class对象的isa指向meta-class对象 64位开始需要 &ISA_MASK获取
meta-class对象的isa指向基类的meta-class对象
class的superclass指向父类的class
如果没有父类,superclass指针为nil
meta-class的superclass指向父类的meta-class
基类的meta-class的superclass指向基类的class
instance调用对象方法的轨迹
isa找到class,方法不存在,就通过superclass找父类
class调用类方法的轨迹
isa找meta-class,方法不存在,就通过superclass找父类
4.iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)
利用RuntimeAPI动态生成一个子类NSKVONotifying_xxx,并且让instance对象的isa指向这个全新的子类
当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数
willChangeValueForKey:
父类原来的setter
didChangeValueForKey:
内部会触发监听器(Oberser)的监听方法( observeValueForKeyPath:ofObject:change:context:)
5.如何手动触发KVO?
手动调用willChangeValueForKey:和didChangeValueForKey:
6.直接修改成员变量会触发KVO么?
不会触发KVO
7.通过KVC修改属性会触发KVO么?
会触发KVO 原因请参照8.KVC的setvalueforkey的赋值过程
8.KVC的赋值和取值过程是怎样的?原理是什么?
KVC的全称是Key-Value Coding,俗称“键值编码”,可以通过一个key来访问某个属性
2.valueForKey:的原理
9.Category的使用场合是什么?
Category的实现原理
Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息
在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)
struct _category_t {
const char *name; // 类名称
struct _class_t *cls; //
const struct _method_list_t *instance_methods; //实例方法
const struct _method_list_t *class_methods; //类方法
const struct _protocol_list_t *protocols; // 协议
const struct _prop_list_t *properties; // 属性
};
10.Category和Class Extension的区别是什么?
Class Extension在编译的时候,它的数据就已经包含在类信息中
Category是在运行时,才会将数据合并到类信息中
11.Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?
有load方法
load方法在runtime加载类、分类的时候调用
load方法可以继承,但是一般情况下不会主动去调用load方法,都是让系统自动调用
12.Category的加载处理过程
1.通过Runtime加载某个类的所有Category数据
2.把所有Category的方法、属性、协议数据,合并到一个大数组中
后面参与编译的Category数据,会在数组的前面
3.将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面
13.load、initialize方法的区别什么?它们在category中的调用的顺序?以及出现继承时他们之间的调用过程?
load与initialize区别:
1.调用方式
1>load是根据函数地址直接调用
2>initialize是通过objc_msgSend调用
2.调用时刻
1>load是runtime加载类,分类的时候调用(只会调用一次)
2>initialize是类第一次接收到消息的时候调用,每一个类只会被initialize一次(父类的initialize可能会被调用多次)
三个加载顺序 load -> c++ > main
load、initialize的调用顺序
1.load
先调用类的+load
按照编译先后顺序调用(先编译,先调用)
调用子类的+load之前会先调用父类的+load
再调用分类的+load
按照编译先后顺序调用(先编译,先调用)
2.initialize
a>先初始化父类
b>在初始化子类(可能最终调用的也是父类的初始化方法)
+initialize和+load的很大区别是,+initialize是通过objc_msgSend进行调用的,所以有以下特点
如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
如果分类实现了+initialize,就覆盖类本身的+initialize调用
14.Category能否添加成员变量?如果可以,如何给Category添加成员变量?
不能直接给Category添加成员变量,但是可以间接实现Category有成员变量的效果
添加关联对象
void objc_setAssociatedObject(id object, const void * key,
id value, objc_AssociationPolicy policy)
获得关联对象
id objc_getAssociatedObject(id object, const void * key)
移除所有的关联对象
void objc_removeAssociatedObjects(id object)
内部实现逻辑可参考此图
15.block的原理是怎样的?本质是什么?
block本质上也是一个OC对象,它内部也有个isa指针
block是封装了函数调用以及函数调用环境的OC对象
block的底层结构如下图所示
为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
16.如何在block内部修改外部的局部变量
1.使用static 修饰局部变量
2.把局部变量改为全局变量
3.使用__block修饰局部变量
其本质是:
编译器会将__block变量包装成一个对象,内部结构如下
16.解决循环引用问题
ARC
1.__weak、__unsafe_unretained
2.用__block解决(必须要调用block)
MRC
__unsafe_unretained、__block(无需调用block)
17.block的属性修饰词为什么是copy?使用block有哪些使用注意?
block一旦没有进行copy操作,就不会在堆上
使用注意:循环引用问题
18.block在修改NSMutableArray,需不需要添加__block?
NSMutableArray *array = [NSMutableArray array];
void (^block)(void) = ^{
[array addObject:@10];
};
这种情况是不会报错的,因为没有修改array的指向,只是使用了array而已。所以不需要添加__block。
19.任务的执行速度的影响因素
1.cpu的调度能力
2.任务的复杂度
3.优先级
4.线程状态
20.优先级翻转 (IO VS CPU 优先级提升)
1.IO 密集型 频繁等待
2.CPU 密集 很少等待
3.饿死
4.调度
优先级影响因素
1.用户指定
2.等待的频繁程度
3.长时间不执行
21.方法的本质,SEL是什么?IMP是什么?两者之间的关系是什么?
方法的本质:发送消息,消息会有以下几个流程
1.快速查找(objc_msgSend) ~cache_t 缓存消息
2.慢速查找~ 递归自己|父类 !lookUpimpOrForward
3.查找不到消息:动态方法解析 ~resolveInstanceMethod
4.消息快速转发~ forwardingTargetForSelector
5.消息慢速转发 ~methodSignatureForSelector & forwardInvocation
sel是方法编号 ~ 在read_image期间就编译入了内存
imp就是我们函数实现指针,找imp就是找函数的过程
sel就相当于书本的目录title
imp就是书本的页码
查找具体的函数就是想看这本书里面具体篇章的内容
1.我们首先知道想看什么 ~title(sel)
2.根据目录对应的页码(imp)
3.翻看到具体的内容
22.能否向编译后的得到的类中增加还实例变量?能否向运行时创建的类中添加实例变量?
1.不能向编译后的得到的类中增加实例变量
2.只要类没有注册到内存中还是可以添加的
原因:我们编译好的示例变量存储的位置在ro,一旦编译完成,内存结构就完全确定无法修改
可以添加属性 + 方法
23.main函数加载之前做了什么
代码经过预处理阶段-> 编译阶段->后端LLVM生成汇编码->生成目标文件.o->链接静动态库->绑定架构
1.动态库链接库
2.ImageLoader加载可执行文件, 里边是被编译过的符号,代码等
3.runtime与+load
https://www.imooc.com/article/38338
24.runtime方法交换 有哪些弊端?
第一个风险是,需要在 +load 方法中进行方法交换。因为如果在其他时候进行方法交换,难以保证另外一个线程中不会同时调用被交换的方法,从而导致程序不能按预期执行。
第二个风险是,被交换的方法必须是当前类的方法,不能是父类的方法,直接把父类的实现拷贝过来不会起作用。父类的方法必须在调用的时候使用,而不是方法交换时使用。
第三个风险是,交换的方法如果依赖了 cmd,那么交换后,如果 cmd 发生了变化,就会出现各种奇怪问题,而且这些问题还很难排查。特别是交换了系统方法,你无法保证系统方法内部是否依赖了 cmd。
第四个风险是,方法交换命名冲突。如果出现冲突,可能会导致方法交换失败。
25.JSBridge的底层原理
JSBridge是什么?JSBridge是一种桥接器,通过JS引擎或Webview容器为媒介 ,约定协议进行通信,实现Native端和Web端双向通信的一种机制
将Native端的接口封装成js接口
将Web端js接口封装成原生接口
WebView是Native中加载网页的一个控件,该组件提供一个evaluateJavascript()方法运行JS代码。我们要做的是在Native端执行一个js方法,在Web端进行监听。
当Web端要请求Native端的方法时,我们首先要自定义一个URL Schema,向Native端发起一个请求,最后在Native端的WebView进行监听,下面我们看看具体实现
26.OC的协议与swift协议的区别:
OC中的协议:
1、受限于委托代理的含义,多⽤于不同类之间的传值与回调。
Swift的协议:
1、可以通过协议 (extension) 扩展,实现协议的⽅法(OC不⾏)
2、定义属性⽅法
3、通过抽取不同类中的相同⽅法和属性,实现模块化减少耦合。使面向协议编程成为可能
4、不需要单独声明协议对象和指定代理
5、协议可以继承其他协议
27.讲一下 OC 的消息机制
OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方法调用者)发送了一条消息(selector方法名)
objc_msgSend底层有3大阶段
消息发送(当前类、父类中查找)、动态方法解析、消息转发
28.消息转发机制流程
1.objc_msgSend执行流程01-消息发送
2.objc_msgSend执行流程02-动态方法解析
3.objc_msgSend的执行流程03-消息转发
29.什么是Runtime?平时项目中有用过么?
OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行
OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数
平时编写的OC代码,底层都是转换成了Runtime API进行调用
具体应用:
利用关联对象(AssociatedObject)给分类添加属性
遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
交换方法实现(交换系统的方法)
利用消息转发机制解决方法找不到的异常问题
......
30.打印结果分别是什么?
@interface MJPerson:NSObject
@end
@implementation MJPerson
@end
@interface MJStudent:MJPerson
@end
@implementation MJStudent
- (instancetype)init {
if (self = [super init]) {
NSLog(@"[self class] = %@", [self class]);
NSLog(@"[super class] = %@", [super class]);
NSLog(@"[self superclass] = %@", [self superclass]);
NSLog(@"[super superclass] = %@", [super superclass]);
/*
[self class] = MJStudent
[super class] = MJStudent
[self superclass] = MJPerson
[super superclass] = MJPerson
**/
}
return self;
}
@end
BOOL res1 = [[NSObject class] isKindOfClass:[NSObject class]];
BOOL res2 = [[NSObject class] isMemberOfClass:[NSObject class]];
BOOL res3 = [[MJPerson class] isKindOfClass:[MJPerson class]];
BOOL res4 = [[MJPerson class] isMemberOfClass:[MJPerson class]];
NSLog(@"%d,%d,%d,%d",res1,res2,res3,res4);
// 1 0 0 0
isKindOfClass
1.类方法
当前类的元类(父元类,根元类,根类)是否与cls相等
2.实例方法
判断当前类(父类、根类)是否与cls相等
isMemberOfClass
1.类方法
判断当前的元类是否与cls相等
2.示例方法
判断当前类是否与cls相等
31.OC 与 swift的区别
1.swift是静态语言,有类型推断,OC是动态语言。
2.swift面向协议编程,OC面向对象编程
3.swift注重值类型,OC注重引用类型。
4.swift支持泛型,OC只支持轻量泛型
5.swift支持静态派发(效率高)、动态派发(函数表派发、消息派发)方式,OC支持动态派发(消息派发)方式。
6.swift支持函数式编程
7.swift的协议不仅可以被类实现,也可以被struct和enum实现
8.swift有元组类型、支持运算符重载
9.swift支持命名空间
10.swift支持默认参数
11.swift比oc代码更加简洁
32.super的本质
super调用,底层会转换为objc_msgSendSuper2函数的调用,接收2个参数
struct objc_super2
SEL
struct objc_super2 {
id receiver; //receiver是消息接收者
Class current_class; //current_class是receiver的Class对象
}
33.讲讲 RunLoop,项目中有用到吗?
runloop是事件接收和分发机制的一种实现。
runloop是线程的基础组成部分,一个runloop就是一个事件处理循环,用来不断的处理输入事件和调配工作,使用runloop的目的是为了让线程在有工作的时候工作,没有工作的时候休眠,从而节约资源。runloop单从其工作内容来看和快递的中转站有很大的相似之处。
iOS中有2套API来访问和使用RunLoop
Foundation:NSRunLoop
Core Foundation:CFRunLoopRef
应用范畴
定时器(Timer)、PerformSelector
GCD Async Main Queue
事件响应、手势识别、界面刷新
网络请求
AutoreleasePool
34.runloop内部实现逻辑?
35.runloop与线程的关系
每条线程都有唯一的一个与之对应的RunLoop对象
RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
RunLoop会在线程结束时销毁
主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
36.timer 与 runloop 的关系?
我们创建timer时,之所以timer能运行,是因为创建timer时,一般情况下,是在主线程中创建,这时会默认将timer以defaultRunloopModel的类型加入主线程,而主线程的runloop对象默认是打开的,从而timer可以运行。
37.程序中添加每3秒响应一次的NSTimer,当拖动tableview时timer可能无法响应要怎么解决?
方法1:创建一条新的线程,然后将timer加入到新创建线程对应的runloop对象中即可。
方法2:依旧是将timer注册在主线程的runloop中,不过注册的model类型为commonModels,之所以加入到commonModels中就行,是因为commonModels是一种公用的类型,每当runloop内容发生改变时,runloop会将commonModels类型下所有注册的内容同步到对应的model中。
38.说说runLoop的几种状态
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), //即将进入runloop
kCFRunLoopBeforeTimers = (1UL << 1), //即将处理Timer
kCFRunLoopBeforeSources = (1UL << 2), //即将处理Source
kCFRunLoopBeforeWaiting = (1UL << 5),//即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6),//刚从休眠中醒来
kCFRunLoopExit = (1UL << 7), //刚从休眠中唤醒
kCFRunLoopAllActivities = 0x0FFFFFFFU //即将退出runloop
};
39.runloop的mode作用是什么?
1.kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
2. UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
3. UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用,会切换到kCFRunLoopDefaultMode
4. GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
5. kCFRunLoopCommonModes: 这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode
40.你理解的多线程?
在iOS中每个进程启动后都会建立一个主线程(UI线程),这个线程是其他线程的父线程。由于在iOS中除了主线程,其他子线程是独立于Cocoa Touch的,所以只有主线程可以更新UI界面
1.进程:进程是指在系统中正在运行的一个应用程序,每一个程序都是一个进程,并且进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内
2.线程(thread)一个进程要想执行任务,必须得有线程。线程中任务的执行是串行的,要在一个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务,也就是说,在同一时间内,一个线程只能执行一个任务,由此可以理解线程是进程中的一条执行路径。一个进程中至少包含一条线程,即主线程,创建线程的目的就是为了开启一条新的执行路径,运行指定的代码,与主线程中的代码实现同时运行。
3.主线程:处理UI,所有更新UI的操作都必须在主线程上执行。不要把耗时操作放在主线程,会卡界面。
4.多线程:是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。在同一时刻,一个CPU只能处理一条线程,但CPU可以在多条线程之间快速的切换,只要切换的足够快,就造成了多线程一同执行的假象。
5.同步(sync):只能在当前线程按先后顺序依次执行,不开启新线程。
6.异步(async):可以在当前线程开启多个新线程执行,可不按顺序执行
7.并发:多个任务并发(同时)执行
8.串行:一个任务执行完毕后,再执行下一个任务
41.iOS的多线程方案有哪几种?你更倾向于哪一种?
42.线程的状态与生命周期
线程的生命周期是:新建 - 就绪 - 运行 - 阻塞 - 死亡
新建:实例化线程对象。
就绪:向线程对象发送start消息,线程对象被加入可调度线程池等待CPU调度。
运行:CPU 负责调度可调度线程池中线程的执行。线程执行完成之前,状态可能会在就绪和运行之间来回切换。就绪和运行之间的状态变化由CPU负责,程序员不能干预。
阻塞:当满足某个预定条件时,可以使用休眠或锁,阻塞线程执行。sleepForTimeInterval(休眠指定时长),sleepUntilDate(休眠到指定日期),@synchronized(self):(互斥锁)。
死亡:正常死亡,线程执行完毕。非正常死亡,当满足某个条件后,在线程内部中止执行或在主线程中止线程对象。[NSThread exit]一旦强行终止线程,后续的所有代码都不会被执行。线程外死亡,[thread cancel] 并不会直接取消线程,只是给线程对象添加isCancelled标记。死亡后线程对象的 isFinished属性为YES;如果是发送calcel消息,线程对象的isCancelled 属性为YES;死亡后stackSize == 0,内存空间被释放。
https://blog.csdn.net/qq_27247497/article/details/120047237
43.GCD 的队列类型
并发队列(Concurrent Dispatch Queue)
可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
并发功能只有在异步(dispatch_async)函数下才有效
串行队列(Serial Dispatch Queue)
让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
44.说一下 OperationQueue 和 GCD 的区别,以及各自的优势
1.GCD的核心是C语言写的系统服务,执行和操作简单高效,因此NSOperation底层也通过GCD实现,换个说法就是NSOperation是对GCD更高层次的抽象,这是他们之间最本质的区别。因此如果希望自定义任务,建议使用NSOperation
2.依赖关系,NSOperation可以设置两个NSOperation之间的依赖,第二个任务依赖于第一个任务完成执行,GCD无法设置依赖关系,不过可以通过dispatch_barrier_async来实现这种效果;
3.KVO(键值对观察),NSOperation和容易判断Operation当前的状态(是否执行,是否取消),对此GCD无法通过KVO进行判断;
4.优先级,NSOperation可以设置自身的优先级,但是优先级高的不一定先执行,GCD只能设置队列的优先级,无法在执行的block设置优先级;
5.继承,NSOperation是一个抽象类,实际开发中常用的两个类是NSInvocationOperation和NSBlockOperation,同样我们可以自定义NSOperation,GCD执行任务可以自由组装,没有继承那么高的代码复用度;
6.效率,直接使用GCD效率确实会更高效,NSOperation会多一点开销,但是通过NSOperation可以获得依赖,优先级,继承,键值对观察这些优势,相对于多的那么一点开销确实很划算,鱼和熊掌不可得兼,取舍在于开发者自己;
根据实际开发中来说,GCD使用情况较多,简单高效,从变成原则上来看,应该是使用高层次的抽象,避免使用低层次的抽象,那么无疑我们应该选择NSOperation,因为复杂的任务可以自己通过NSOperation实现,日常还是GCD的天下,毕竟GCD有更高的并发和执行能力。
45.线程安全的处理手段有哪些?
1、互斥锁(synchronized):多条线程抢夺同一块资源时,当一个线程获得这个锁之后,其他想要获得此锁的线程将会被阻塞,直到该锁被释放
缺点:需要消耗大量的CPU资源
2、自旋锁(OS_SPINLOCK_INIT):获取了自旋锁后,线程会反复检查锁变量是否可用。避免了进程上下文的调度开销,适用于线程阻塞时间短的场合
3、信号量(DispatchSemaphore):一个计数信号,可用来资源访问的并发控制
let semaphore = DispatchSemaphore(value: 1)// 初始化信号量
semaphore.wait() //等待信号,信号量-1,当信号总量为0时会一直等待
semaphore.signal() //发送信号,信号量+1
46.OC你了解的锁有哪些?
OSSpinLock //自旋锁 IOS10后已废弃
//os_unfair_lock用于取代不安全的OSSpinLock ,从iOS10开始才支持
os_unfair_lock //互斥锁
//pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) 递归锁
//pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT); 互斥锁
pthread_mutex //互斥锁 / 递归锁
dispatch_semaphore //信号量
dispatch_queue(DISPATCH_QUEUE_SERIAL) //串行队列
NSLock //互斥锁 对pthread_mutex的封装
NSRecursiveLock //递归锁 对mutex递归锁的封装
NSCondition // NSCondition是对mutex和cond的封装
//NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值
NSConditionLock
@synchronized //@synchronized是对mutex递归锁的封装
47.多线程的安全隐患,解决方式
资源共享
1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源
比如多个线程访问同一个对象、同一个变量、同一个文件
当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题
解决方案:使用线程同步技术(同步,就是协同步调,按预定的先后次序进行)
常见的线程同步技术是:加锁
48.下面输出结果
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"1");
[self performSelector:@selector(test) withObject:nil afterDelay:0];
NSLog(@"3");
// [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
//[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
});
- (void)test{
NSLog(@"2:%s", __func__);
}
注释后:
打印结果是:1、3
原因
performSelector:withObject:afterDelay:的本质是往Runloop中添加定时器
子线程默认没有启动Runloop
打开注释:1 3 2
- (void)interview{
//performSelector 会崩溃
//因为 thread 开启后执行完会被释放,在执行方法会崩溃
NSThread * thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"1");
}];
[thread start];
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}
49.使用CADisplayLink、NSTimer有什么注意点?
1.CADisplayLink、NSTimer会对target产生强引用,如果target又对它们产生强引用,那么就会引发循环引用
2.不准时,CADisplayLink和Timer都需要依赖RunLoop,如果RunLoop的任务过于繁重,那么就会造成不准时,所以想准时的话,我们应该使用GCD的定时器
循环引用解决方案:
50.介绍下内存的几大区域
代码段:编译之后的代码
数据段:
字符串常量:比如NSString *str = @"123"
已初始化数据:已初始化的全局变量、静态变量等
未初始化数据:未初始化的全局变量、静态变量等
栈:函数调用开销,比如局部变量。分配的内存空间地址越来越小
堆:通过alloc、malloc、calloc等动态分配的空间,分配的内存空间地址越来越大
51.讲一下你对 iOS 内存管理的理解
在iOS中,使用引用计数来管理OC对象的内存
一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间
调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1
内存管理的经验总结
当调用alloc、new、copy、mutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease来释放它
想拥有某个对象,就让它的引用计数+1;不想再拥有某个对象,就让它的引用计数-1
可以通过以下私有函数来查看自动释放池的情况
extern void _objc_autoreleasePoolPrint(void);
52.ARC 都帮我们做了什么?
ARC是LLVM编译器和RunLoop相互协作的一个结果,帮我们自动进行内存管理(如下所示),并且处理了弱引用(对象销毁时,指向这个对象的弱引用,都会被置为nil)
1.使用assign修饰普通数据类型时,ARC会帮我们自动生成get、set方法,如下所示
@property (nonatomic,assign) NSInteger age;
- (void)setAge:(NSInteger)age{
_age = age;
}
- (NSInteger)age{
return _age;
}
2.使用retain、strong修饰对象类型,ARC会帮我们自动生成get、set方法,并且在set方法里帮我们retain、release对象,如下所示:
@property (nonatomic,retain) NSObject *status;
- (void)setStatus:(NSObject *)status{
if (_status != status){
//如果新对象和旧对象不相同,就先让旧对象的引用计数-1
[_status release];
//然后把新对象的引用计数+1,在赋值给_status
_status = [status retain];
}
}
- (NSObject *)status{
return _status;
}
3.使用copy修饰对象类型,ARC会帮我们自动生成get、set方法,并且在set方法里帮我们copy、release对象,如下所示:
@property (nonatomic,copy) NSString *name;
- (void)setName:(NSString *)name{
if (_name != name){
//如果新对象和旧对象不相同,就先让旧对象的引用计数-1
[_name release];
//然后把新对象的引用计数+1,在赋值给_status
此处会使用copy产生一个不可变的对象,这就是为什么NSMutableArray等可变类型不能使用copy的根本原因!!!
_name = [name copy];
}
}
- (NSString *)name{
return _name;
}
53.weak指针的实现原理
当一个对象obj被weak指针指向时,这个weak指针会以obj作为key,被存储到sideTable类的weak_table这个散列表上对应的一个weak指针数组里面。
当一个对象obj的dealloc方法被调用时,Runtime会以obj为key,从sideTable的weak_table散列表中,找出对应的weak指针列表,然后将里面的weak指针逐个置为nil。
54.autorelease对象在什么时机会被调用release
在所处的runloop的休眠之前释放
自动释放池的主要底层数据结构是:__AtAutoreleasePool、AutoreleasePoolPage
调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的
每个AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址
所有的AutoreleasePoolPage对象通过双向链表的形式连接在一起
55.Runloop和Autorelease
iOS在主线程的Runloop中注册了2个Observer
第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()
第2个Observer
监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()
56.方法里有局部对象, 出了方法后会立即释放吗
如果是普通的 局部对象 会立即释放
如果是放在了 autoreleasePool 自动释放池,在 runloop 迭代结束的时候释放
57.Tagged Pointer
1.从64bit开始,iOS引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象的存储
2.在没有使用Tagged Pointer之前, NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值
3.使用Tagged Pointer之后,NSNumber指针里面存储的数据变成了:Tag + Data,也就是将数据直接存储在了指针中
4.当指针不够存储数据时,才会使用动态分配内存的方式来存储数据
objc_msgSend能识别Tagged Pointer,比如NSNumber的intValue方法,直接从指针提取数据,节省了以前的调用开销
5.如何判断一个指针是否为Tagged Pointer?
iOS平台,最高有效位是1(第64bit)
Mac平台,最低有效位是1
58.你在项目中是怎么优化内存的?
- 把views设置为透明,透明的Views你应该设置它们的opaque属性为YES
2.对于仅使用一次或是使用频率很低的大图片资源不要使用UIImage(named: "")而使用UIImage(contentsOfFile: "")
3.在自定义的UIView子类中,利用drawRect:方法进行绘制改为CAShapeLayer
4.基于颜色创建纯色的图片时,尺寸过大
5.在Image Views中调整图片大小
6.高清大图片的处理
7.第三方库的缓存机制,适时缓存,适时清除
8.重用和延迟加载(lazy load) Views
59.OC与Swift的区别
1.swift是静态语言,有类型推断,OC是动态语言。
2.swift面向协议编程,OC面向对象编程
3.swift注重值类型,OC注重引用类型。
4.swift支持泛型,OC只支持轻量泛型
5.swift支持静态派发(效率高)、动态派发(函数表派发、消息派发)方式,OC支持动态派发(消息派发)方式。
6.swift支持函数式编程
7.swift的协议不仅可以被类实现,也可以被struct和enum实现
8.swift有元组类型、支持运算符重载
9.swift支持命名空间
10.swift支持默认参数
11.swift比oc代码更加简洁
60.内存对齐规则(IOS 64位环境下)
1.Founction 框架对象最小要求内存占用16
2.结构体内存对齐原则:内存必须是最大成员大小的倍数
3.IOS 操作系统内存对齐原则: 内存是以16的倍数对齐
4.64位系统下数据类型占用字节数
char 1
char * 8
int 4
short 2
BOOL 1
unsigned int 4
NSInterger 8
long 8
long long 8
float 4
double 8
CGFloat 8
CGSize 16
CGRect 32
61.堆、栈、队列
栈(stack):又名堆栈,存放的是:在函数中定义的一些基本数据类型的变量和对象的引用;当超过作用域后释放
栈(heap):存储对象,所有new出来的对象和数组
堆(二叉树):从根节点开始,每个节点都可以有左、右两个节点,元素优先将每一层按从左到右的方式填满,即父节点最多有2个子节点,有右子节点就一定有左子节点,同一层的某个节点左边一定是填满的。堆分为最大堆,最小堆,最小堆就是根节点元素值是所有元素中最小的,最大堆则相反,左右节点的大小则没有规定谁大谁小。
堆和栈的区别:
1、堆可以动态的分配内存大小(由程序员分配释放,如果程序员不释放,程序结束时可能有OS释放,分配方式类似于链表),生存周期不需要事先告诉编译器,程序运行时申请;栈通常比堆小,栈中存放的数据的大小和生存周期都必须是确定的(由编译器自动分配释放)
2、栈的存储速度比堆快,栈数据可共享
3、栈使用的是一级缓存,通常是被调用时处于存储空间中,调用完毕后立即释放;堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法决定
4、栈是运行时单位,堆是存储的单位,栈解决程序运行时的问题,即程序如何执行,或者说如何处理数据,堆解决的是数据的存储问题,即数据怎么放?放在那?
62.UIView和CALayer是什么关系?
创建UIView对象时,UIView内部会自动创建一个层(CALayer对象),通过UIView的layer属性可以访问这个层。当UIView需要显示到屏幕上时,会调用drawRect:方法进行绘图渲染,并且会将所有内容绘制在自己的层上,绘图完毕后,系统会将层拷贝到屏幕上,于是就完成了UIView的显示
UIView相比CALayer最大区别是UIView继承自UIResponder,可以响应用户事件,而CALayer不可以;UIView侧重于对显示内容的管理,CALayer侧重于对内容的绘制。
UIView本身,更像是一个CALayer的管理器,访问它的和绘图、坐标相关的属性,如frame,bounds等,实际上内部都是访问它所在CALayer的相关属性
UIView和CALayer是相互依赖的关系。UIView依赖CALayer提供的内容,CALayer依赖UIView提供的容器来显示绘制的内容。归根到底CALayer是这一切的基础,如果没有CALayer,UIView自身也不会存在,UIView是一个特殊的CALayer实现,添加了响应事件的能力。
63.说一下 JS 和 OC 互相调用的几种方式?
js调用oc的三种方式:
根据网页重定向截取字符串通过url scheme判断
替换方法.context[@"copyText"]
注入对象:遵守协议NSExport,设置context[@
oc调用js代码两种方式
通过webVIew调用 webView stringByEvaluatingJavaScriptFromString: 调用
通过JSContext调用[context evaluateScript:];
64.Http 和 Https 的区别?Https为什么更加安全?
区别
1.HTTPS 需要向机构申请 CA 证书,极少免费。
2.HTTP 属于明文传输,HTTPS基于 SSL 进行加密传输。
3.HTTP 端口号为 80,HTTPS 端口号为 443 。
4.HTTPS 是加密传输,有身份验证的环节,更加安全。
安全
SSL(安全套接层) TLS(传输层安全)
以上两者在传输层之上,对网络连接进行加密处理,保障数据的完整性,更加的安全。
65.编程中的六大设计原则?
1.单一职责原则
通俗地讲就是一个类只做一件事
CALayer:动画和视图的显示。
UIView:只负责事件传递、事件响应。
2.开闭原则
对修改关闭,对扩展开放。 要考虑到后续的扩展性,而不是在原有的基础上来回修改
3.接口隔离原则
使用多个专门的协议、而不是一个庞大臃肿的协议,如 UITableviewDelegate + UITableViewDataSource
4.依赖倒置原则
抽象不应该依赖于具体实现、具体实现可以依赖于抽象。 调用接口感觉不到内部是如何操作的
5.里氏替换原则
父类可以被子类无缝替换,且原有的功能不受任何影响 如:KVO
6.迪米特法则
一个对象应当对其他对象尽可能少的了解,实现高聚合、低耦合
66.沙盒目录结构是怎样的?各自用于那些场景?
Application:存放程序源文件,上架前经过数字签名,上架后不可修改
Documents:常用目录,iCloud备份目录,存放数据
tmp:存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能
Library包含以下目录:
Caches:存放体积大又不需要备份的数据
Preference:设置目录,iCloud会备份设置信息
Application Support:建议用来存储除用户数据相关以外的所有文件,如游戏的新关卡。在iTunes和iCloud备份时会备份该目录。
Frameworks:用来保存动态库的文件夹,在iOS系统中已不能使用,该目录可以忽略。
67.AFNetworking 底层原理分析
AFNetworking是封装的NSURLSession的网络请求,由五个模块组成:分别由NSURLSession,Security,Reachability,Serialization,UIKit五部分组成
NSURLSession:网络通信模块(核心模块) 对应 AFNetworking中的 AFURLSessionManager和对HTTP协议进行特化处理的AFHTTPSessionManager,AFHTTPSessionManager是继承于AFURLSessionmanager的
Security:网络通讯安全策略模块 对应 AFSecurityPolicy
Reachability:网络状态监听模块 对应AFNetworkReachabilityManager
Seriaalization:网络通信信息序列化、反序列化模块 对应 AFURLResponseSerialization
UIKit:对于iOS UIKit的扩展库
68.说一下工作中你怎么做性能优化的
一般都是说关于tableView的优化处理,
造成tableView卡顿的原因
1.没有使用cell的重用标识符,导致一直创建新的cell
2.cell的重新布局
3.没有提前计算并缓存cell的属性及内容
4.cell中控件的数量过多
5.使用了ClearColor,无背景色,透明度为0
6.更新只使用tableView.reloadData()(如果只是更新某组的话,使用reloadSection进行局部更新)
7.加载网络数据,下载图片,没有使用异步加载,并缓存
8.使用addView 给cell动态添加view
9.没有按需加载cell(cell滚动很快时,只加载范围内的cell)
10.实现无用的代理方法(tableView只遵守两个协议)
11.没有做缓存行高(estimatedHeightForRow不能和HeightForRow里面的layoutIfNeed同时存在,这两者同时存在才会出现“窜动”的bug。
建议是:只要是固定行高就写预估行高来减少行高调用次数提升性能。如果是动态行高就不要写预估方法了,用一个行高的缓存字典来减少代码的调用次数即可)
12.做了多余的绘制工作(在实现drawRect:的时候,它的rect参数就是需要绘制的区域,这个区域之外的不需要进行绘制)
13.没有预渲染图像。(当新的图像出现时,仍然会有短暂的停顿现象。解决的办法就是在bitmap context里先将其画一遍,导出成UIImage对象,然后再绘制到屏幕)
提升tableView的流畅度
*本质上是降低 CPU、GPU 的工作,从这两个大的方面去提升性能。
1.CPU:对象的创建和销毁、对象属性的调整、布局计算、文本的计算和排版、图片的格式转换和解码、图像的绘制
2.GPU:纹理的渲染
卡顿优化在 CPU 层面
1.尽量用轻量级的对象,比如用不到事件处理的地方,可以考虑使用 CALayer 取代 UIView
2.不要频繁地调用 UIView 的相关属性,比如 frame、bounds、transform 等属性,尽量减少不必要的修改
3.尽量提前计算好布局,在有需要时一次性调整对应的属性,不要多次修改属性
4.Autolayout 会比直接设置 frame 消耗更多的 CPU 资源
5.图片的 size 最好刚好跟 UIImageView 的 size 保持一致
6.控制一下线程的最大并发数量
7.尽量把耗时的操作放到子线程
8.文本处理(尺寸计算、绘制)
9.图片处理(解码、绘制)
卡顿优化在 GPU层面
1.尽量避免短时间内大量图片的显示,尽可能将多张图片合成一张进行显示
2.GPU能处理的最大纹理尺寸是 4096x4096,一旦超过这个尺寸,就会占用 CPU 资源进行处理,所以纹理尽量不要超过这个尺寸
3.尽量减少视图数量和层次
4.减少透明的视图(alpha<1),不透明的就设置 opaque 为 YES
5.尽量避免出现离屏渲染
69.iOS图片设置圆角性能问题
1.直接使用setCornerRadius
这样设置会触发离屏渲染,比较消耗性能。比如当一个页面上有十几头像这样设置了圆角会明显感觉到卡顿。
注意:png图片UIImageView处理圆角是不会产生离屏渲染的。(ios9.0之后不会离屏渲染,ios9.0之前还是会离屏渲染)
2.setCornerRadius设置圆角之后,shouldRasterize=YES光栅化
avatarImageView.layer.shouldRasterize = YES;
avatarImageViewUrl.layer.rasterizationScale=[UIScreen mainScreen].scale; //UIImageView不加这句会产生一点模糊
shouldRasterize=YES设置光栅化,可以使离屏渲染的结果缓存到内存中存为位图,
使用的时候直接使用缓存,节省了一直离屏渲染损耗的性能。
但是如果layer及sublayers常常改变的话,它就会一直不停的渲染及删除缓存重新
创建缓存,所以这种情况下建议不要使用光栅化,这样也是比较损耗性能的。
3.直接覆盖一张中间为圆形透明的图片(推荐使用)
4.UIImage drawInRect绘制圆角
这种方式GPU损耗低内存占用大,而且UIButton上不知道怎么绘制,可以用
UIimageView添加个点击手势当做UIButton使用。
5.SDWebImage处理图片时Core Graphics绘制圆角(暂时感觉是最优方法)
70.以scheduledTimerWithTimeInterval的方式触发的timer,在滑动页面上的列表时,timer会暂停,为什么?该如何解决?
原因在于滑动时当前线程的runloop切换了mode用于列表滑动,导致timer暂停。
runloop中的mode主要用来指定事件在runloop中的优先级,有以下几种:
- Default(NSDefaultRunLoopMode):默认,一般情况下使用;
- Connection(NSConnectionReplyMode):一般系统用来处理NSConnection相关事件,开发者一般用不到;
- Modal(NSModalPanelRunLoopMode):处理modal panels事件;
- Event Tracking(NSEventTrackingRunLoopMode):用于处理拖拽和用户交互的模式。
- Common(NSRunloopCommonModes):模式合集。默认包括Default,Modal,Event >Tracking三大模式,可以处理几乎所有事件。
回到题中的情境。滑动列表时,runloop的mode由原来的Default模式切换到了Event Tracking模式,timer原来好好的运行在Default模式中,被关闭后自然就停止工作了。
解决方法其一是将timer加入到NSRunloopCommonModes中。其二是将timer放到另一个线程中,然后开启另一个线程的runloop,这样可以保证与主线程互不干扰,而现在主线程正在处理页面滑动。
//方法1
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
//方法2
dispatch_async(dispatch_get_global_queue(0, 0), ^{
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(repeat:) userInfo:nil repeats:true];
[[NSRunLoop currentRunLoop] run];
});
71.对称加密和非对称加密的区别?
1、对称加密又称公开密钥加密,加密和解密都会用到同一个密钥,如果密钥被攻击者获得,此时加密就失去了意义。常见的对称加密算法有DES、3DES、AES、Blowfish、IDEA、RC5、RC6。
2、非对称加密又称共享密钥加密,使用一对非对称的密钥,一把叫做私有密钥,另一把叫做公有密钥;公钥加密只能用私钥来解密,私钥加密只能用公钥来解密。常见的公钥加密算法有:RSA、ElGamal、背包算法、Rabin(RSA的特例)、迪菲-赫尔曼密钥交换协议中的公钥加密算法、椭圆曲线加密算法)。
72.组件化有什么好处?
业务分层、解耦,使代码变得可维护;
有效的拆分、组织日益庞大的工程代码,使工程目录变得可维护;
便于各业务功能拆分、抽离,实现真正的功能复用;
业务隔离,跨团队开发代码控制和版本风险控制的实现;
模块化对代码的封装性、合理性都有一定的要求,提升开发同学的设计能力;
在维护好各级组件的情况下,随意组合满足不同客户需求;(只需要将之前的多个业务组件模块在新的主App中进行组装即可快速迭代出下一个全新App)
73.你是如何组件化解耦的?
分层
基础功能组件:按功能分库,不涉及产品业务需求,跟库Library类似,通过良好的接口拱上层业务组件调用;不写入产品定制逻辑,通过扩展接口完成定制;
基础UI组件:各个业务模块依赖使用,但需要保持好定制扩展的设计
业务组件:业务功能间相对独立,相互间没有Model共享的依赖;业务之间的页面调用只能通过UIBus进行跳转;业务之间的逻辑Action调用只能通过服务提供;
中间件:target-action,url-block,protocol-class
74.IOS 支持多继承吗?如何实现
Objective-C 不支持多继承,但我们可以间接的实现(协议、分类、消息转发)
具体实现可参考https://www.jianshu.com/p/020f8541f77b
75.APP启动时间应从哪些方面优化?
App启动时间可以通过xcode提供的工具来度量,在Xcode的Product->Scheme-->Edit Scheme->Run->Auguments中,将环境变量DYLD_PRINT_STATISTICS设为YES,优化需以下方面入手
dylib loading time
核心思想是减少dylibs的引用
合并现有的dylibs(最好是6个以内)
使用静态库
rebase/binding time
核心思想是减少DATA块内的指针
减少Object C元数据量,减少Objc类数量,减少实例变量和函数(与面向对象设计思想冲突)
减少c++虚函数
多使用Swift结构体(推荐使用swift)
ObjC setup time
核心思想同上,这部分内容基本上在上一阶段优化过后就不会太过耗时
initializer time
使用initialize替代load方法
减少使用c/c++的attribute((constructor));推荐使用dispatch_once() pthread_once() std:once()等方法
推荐使用swift
不要在初始化中调用dlopen()方法,因为加载过程是单线程,无锁,如果调用dlopen则会变成多线程,会开启锁的消耗,同时有可能死锁
不要在初始化中创建线程
75.imageName和ImageWithContextOfFile的区别?哪个性能高
用imageNamed的方式加载时,图片使用完毕后缓存到内存中,内存消耗多,加载速度快。即使生成的对象被 autoReleasePool释放了,这份缓存也不释放,如果图像比较大,或者图像比较多,用这种方式会消耗很大的内存。
imageNamed采用了缓存机制,如果缓存中已加载了图片,直接从缓存读就行了,每次就不用再去读文件了,效率会更高。
ImageWithContextOfFile加载,图片是不会缓存的,加载速度慢。
大量使用imageNamed方式会在不需要缓存的地方额外增加开销CPU的时间.当应用程序需要加载一张比较大的图片并且使用一次性,那么其实是没有必要去缓存这个图片的,用imageWithContentsOfFile是最为经济的方式,这样不会因为UIImage元素较多情况下,CPU会被逐个分散在不必要缓存上浪费过多时间.
76.Category 与 Extension本质区别
1)Extension在编译期间就已经决定了,它就是类的一部分,在编译器和头文件里的@interface以及实现文件里的@implement一起形成一个完整的类,它伴随类的产生而产生,亦随之一起消灭。extension一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加extension,所以无法为系统的类,比如NSString添加extension。
2)Category则是在运行期间决定的。extension可以添加实例变量,而category是无法添加实例变量的(因为在运行期间,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这对于编译型语言来说是灾难性的)。
77.三种架构模式——MVC、MVP、MVVM
1.MVC(Model-View-Controller)
Model:模型层,数据模型及其业务逻辑,是针对业务模型建立的数据结构,Model与View无关,而与业务有关。
View:视图层,用于与用户实现交互的页面,通常实现数据的输入和输出功能。
Controller:控制器,用于连接Model层和View层,完成Model层和View层的交互。还可以处理页面业务逻辑,它接收并处理来自用户的请求,并将Model返回给用户。
2.MVP(Model-View-Presenter)
Model:模型层,用于数据存储以及业务逻辑。
View:视图层,用于展示与用户实现交互的页面,通常实现数据的输入和输出功能。
Presenter:表示器,用于连接M层、V层,完成Model层与View层的交互,还可以进行业务逻辑的处理。
3.MVVM(Model-View-ViewModel)
Model:数据模型(数据处理业务),指的是后端传递的数据。
View:视图,将Model的数据以某种方式展示出来。
ViewModel:视图模型,数据的双向绑定(当Model中的数据发生改变时View就感知到,当View中的数据发生变化时Model也能感知到),是MVVM模式的核心。ViewModel 层把 Model 层和 View 层的数据同步自动化了,解决了 MVP 框架中数据同步比较麻烦的问题,不仅减轻了 ViewModel 层的压力,同时使得数据处理更加方便——只需告诉 View 层展示的数据是 Model 层中的哪一部分即可。
4.Viper(View、Interactor、Presenter、Entity、Route)
V - ViewView,组织对内部的布局,对外暴露用户更新UI的接口,自己不主动更新UI。
I - Interactor,实现和封装各种业务的Use Case,可以获取各种Manager或者Service用于实现业务逻辑。
P - Presenter,调用Interactor提供的UseCase执行业务逻辑。Presenter作为业务和View的中转站,不包含业务实现代码。而是调用各种Use Case。
E - Entity,这里是业务的Model层,是业务逻辑的实体。
R - Router,路由工具,用于页面之间的跳转,实现页面之间的解耦。
78.iOS中常用的几种设计模式
1.代理模式
代理模式完成委托方交给的任务,委托方有一些任务自己不想完成,但是还需要要实现,则将该任务存放到协议中,由代理完成.但是代理并不会主动的执行任务,需要委托方通知代理。
应用场景:当一个类的某些功能需要由别的类来实现,但是又不确定具体会是哪个类实现。
优势:解耦合
敏捷原则:开放-封闭原则
实例:tableview的 数据源delegate,通过和protocol的配合,完成委托诉求。列表row个数delegate,自定义的delegate。
2.观察者模式(通知机制,KVO机制)
观察者模式本质上是一种发布-订阅模型,用以消除具有不同行为的对象之间的耦合,通过这一模式,不同对象可以协同工作,同时它们也可以被复用于其他地方Observer从Subject订阅通知,ConcreteObserver实现重现ObServer并将其重载其update方法。
应用场景:一般为model层对,controller和view进行的通知方式,不关心谁去接收,只负责发布信息。
优势:解耦合
敏捷原则:接口隔离原则,开放-封闭原则
实例:Notification通知中心,注册通知中心,任何位置可以发送消息,注册观察者的对象可以接收。
3.单例模式
单例模式可以保证App在程序运行中,一个类只有唯一个实例,从而做到节约内存。
在整个App程序中,这一份资源是共享的。
提供一个固定的实例创建方法。
应用场景:确保程序运行期某个类,只有一份实例,用于进行资源共享控制。
优势:使用简单,延时求值,易于跨模块
敏捷原则:单一职责原则
实例:[UIApplication sharedApplication]。
4.策略模式
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。
应用场景:定义算法族,封装起来,使他们之间可以相互替换。
优势:使算法的变化独立于使用算法的用户
敏捷原则:接口隔离原则;
多用组合,少用继承;
针对接口编程,而非实现
实例:排序算法,NSArray的sortedArrayUsingSelector;经典的鸭子会叫,会飞案例。
79.IOS内存管理
Objective-C提供了两种种内存管理方式:manual reference counting(MRC,手动引用计数器),automatic reference counting(ARC,自动引用计数)。ARC作为苹果新提供的技术,苹果推荐开发者使用ARC技术来管理内存;
1.引用计数器
采用引用计数进行管理
每个对象都有一个关联的整数,称为引用计数器
当代码需要使用该对象时,则将对象的引用计数加1
当代码结束使用该对象时,则将对象的引用计数减1
当引用计数的值变为0时,此时对象将被释放。
与之对应的消息发送方法
当对象被创建(alloc、new或copy等方法)时,其引用计数初始值为1
给对象发送retain消息,其引用计数加
给对象发送release消息,其引用计数减1
当对象引用计数归0时,ObjC給对象发送dealloc消息销毁对象
2.自动释放池
AutoreleasePool的原理
自动释放池,系统有一个现成的自动内存管理池,他会随着每一个mainRunloop的结束而释放其中的对像;自动释放池也可以手动创建,他可以让pool中的对象在执行完代码后马上被释放,可以起到优化内存,防止内存溢出的效果(如视频针图片的切换时、创建大量临时对象时等)。
autorelease:自动释放,使对象在超出指定的生存范围时能够自动并正确地释放 (release 即是立即释放)。
参考以下链接
https://blog.csdn.net/z119901214/article/details/82417153
80.iOS性能优化之内存篇
https://blog.csdn.net/hanhailong18/article/details/111350050
http://events.jianshu.io/p/235eae2e820c
81.事件响应链
用户点击屏幕时,首先UIApplication对象先收到该点击事件,再依次传递给它上面的所有子view,直到传递到最上层,即UIApplication ——> UIWindow ——> RootViewController ——> View ——> Button,即传递链。
而反之Button ——> View ——> RootViewController ——> UIWindow ——> UIApplication则称为响应链。
简单总结,事件链包含传递链和响应链,事件通过传递链传递上去,通过响应链找到相应的UIResponse。
响应链的具体流程:
1、若view的 viewcontroller 存在,则将该事件传递给其viewcontroller响应;如若不存在,则传递给其父视图;
2、若view的最顶层不能处理事件,则传递给UIWindow进行处理;
3、若UIWindow不能处理,则传递给UIApplication;
4、若UIApplication不能处理,则将该事件丢弃。
传递链的具体流程:
1、用户在点击屏幕。
2、系统将点击事件加入到UIApplication管理的消息队列中。
3、UIApplication会从消息队列中取出该事件传递给UIWindow对象;
4、在UIWindow中调用方法hitTest:withEvent:返回最终相应的view;
82.bugly的卡顿监控原理
Runloop的两次source[kCFRunLoopBeforeSources 和 kCFRunLoopAfterWaiting]的监控 创建信号量的方式
渲染界面的频率来监控帧率
https://www.jianshu.com/p/2c7b13bfd82f
83.组件化解耦方案
https://www.jianshu.com/p/1274062f38c5
84.xcode Instrument工具
https://www.jianshu.com/p/6e3530cd375a
https://blog.csdn.net/m0_46110288/article/details/115698448
Swift系列面试题总结
https://www.jianshu.com/p/431665354c65
1.class 和 struct 的区别
struct是值类型,class是引用类型。
值类型的变量直接包含它们的数据,对于值类型都有它们自己的数据副本,因此对一个变量操作不可能影响另一个变量。
引用类型的变量存储对他们的数据引用,因此后者称为对象,因此对一个变量操作可能影响另一个变量所引用的对象。
二者的本质区别:struct是深拷贝,拷贝的是内容;class是浅拷贝,拷贝的是指针。
property的初始化不同:class 在初始化时不能直接把 property 放在 默认的constructor 的参数里,而是需要自己创建一个带参数的constructor;而struct可以,把属性放在默认的constructor 的参数里。
变量赋值方式不同:struct是值拷贝;class是引用拷贝。
immutable变量:swift的可变内容和不可变内容用var和let来甄别,如果初始为let的变量再去修改会发生编译错误。struct遵循这一特性;class不存在这样的问题。
mutating function: struct 和 class 的差別是 struct 的 function 要去改变 property 的值的时候要加上 mutating,而 class 不用。
继承: struct不可以继承,class可以继承。
struct比class更轻量:struct分配在栈中,class分配在堆中。
2.Swift 是面向对象还是函数式的编程语言?
Swift 既是面向对象的,又是函数式的编程语言。
说 Swift 是面向对象的语言,是因为 Swift 支持类的封装、继承、和多态,从这点上来看与 Java 这类纯面向对象的语言几乎毫无差别。
说 Swift 是函数式编程语言,是因为 Swift 支持 map, reduce, filter, flatmap 这类去除中间状态、数学函数式的方法,更加强调运算结果而不是中间过程。
3.什么是泛型,swift哪些地方使用了泛型?
泛型(generic)可以使我们在程序代码中定义一些可变的部分,在运行的时候指定。使用泛型可以最大限度地重用代码、保护类型的安全以及提高性能。
例如 optional 中的 map、flatMap 、?? (泛型加逃逸闭包的方式,做三目运算)
4.swift 语法糖 ? !的本质(实现原理)
?为optional的语法糖
optional是一个包含了nil 和普通类型的枚举,确保使用者在变量为nil的情况下处理
!为optional 强制解包的语法糖
5.Optional(可选型) 是用什么实现的
Optional 是一个泛型枚举
大致定义如下:
enum Optional{
case none
case some(Wrapped)
}
除了使用 let someValue: Int? = nil 之外, 还可以使用let optional1: Optional= nil 来定义
6.什么是高阶函数
一个函数如果可以以某一个函数作为参数, 或者是返回值, 那么这个函数就称之为高阶函数, 如 map, reduce, filter
7.如何解决引用循环
转换为值类型, 只有类会存在引用循环, 所以如果能不用类, 是可以解引用循环的,
delegate 使用 weak 属性.
闭包中, 对有可能发生循环引用的对象, 使用 weak 或者 unowned, 修饰
8.定义静态方法时关键字 static 和 class 有什么区别
static 定义的方法不可以被子类继承, class 则可以
class AnotherClass {
static func staticMethod(){}
class func classMethod(){}
}
class ChildOfAnotherClass: AnotherClass {
override class func classMethod(){}
//override static func staticMethod(){}// error
}
9.请说明并比较以下关键词:Open, Public, Internal, File-private, Private
Swift 有五个级别的访问控制权限,从高到底依次为比如 Open, Public, Internal, File-private, Private。
他们遵循的基本原则是:高级别的变量不允许被定义为低级别变量的成员变量。比如一个 private 的 class 中不能含有 public 的 String。反之,低级别的变量却可以定义在高级别的变量中。比如 public 的 class 中可以含有 private 的 Int。
Open 具备最高的访问权限。其修饰的类和方法可以在任意 Module 中被访问和重写;它是 Swift 3 中新添加的访问权限。
Public 的权限仅次于 Open。与 Open 唯一的区别在于它修饰的对象可以在任意 Module 中被访问,但不能重写。
Internal 是默认的权限。它表示只能在当前定义的 Module 中访问和重写,它可以被一个 Module 中的多个文件访问,但不可以被其他的 Module 中被访问。
File-private 也是 Swift 3 新添加的权限。其被修饰的对象只能在当前文件中被使用。例如它可以被一个文件中的 class,extension,struct 共同使用。
Private 是最低的访问权限。它的对象只能在定义的作用域内使用。离开了这个作用域,即使是同一个文件中的其他作用域,也无法访问。
10.swift中,关键字 guard 和 defer 的用法 guard也是基于一个表达式的布尔值去判断一段代码是否该被执行。与if语句不同的是,guard只有在条件不满足的时候才会执行这段代码。
guard let name = self.text else { return }
defer的用法是,这条语句并不会马上执行,而是被推入栈中,直到函数结束时才再次被调用。
defer {
//函数结束才调用
}
算法题:
1.用递归写一个算法,计算从1到100的和
func sum(value: Int) -> Int {
if value <= 0 {
return 0
}
var number = value
return value + sum(value: number - 1)
}
// 计算过程
let result = sum(value: 100)
print(result)
2.给定一个Int型数组,用里面的元素组成一个最大数,因为数字可能非常大,用字符串输出。
func largestNumber(_ nums: [Int]) -> String {
let sort = nums.map {"\($0)"}.sorted { (lStr, rStr) -> Bool in
return lStr + rStr > rStr + lStr
}
let result = sort.joined()
if result.prefix(1) == "0" {
return "0"
} else {
return result
}
}