怎么理解Objective-C是动态运行时语言。
主要是将数据类型的确定由编译时,推迟到了运行时。这个问题其实浅涉及到两个概念,运行时和多态。
简单来说, 运行时机制使我们直到运行时才去决定一个对象的类别,以及调用该类别对象指定方法。
多态:不同对象以自己的方式响应相同的消息的能力叫做多态。
意思就是假设生物类(life)都拥有一个相同的方法-eat;那人类属于生物,猪也属于生物,都继承了life后,实现各自的eat,但是调用是我们只需调用各自的eat方法。
也就是不同的对象以自己的方式响应了相同的消 息(响应了eat这个选择器)。因此也可以说,运行时机制是多态的基础.
alloc 方法做了什么?
答: alloc 分配内存并将内存地址返回给指针,init对分配的内存初始化。
NSCache 与NSDictionary 区别?
答:当系统资源耗尽是NSCache可以自动删减缓存,字典需要自己编写删除程序,当系统通知时再手动进行删减,NSCache会先行删减最新缓存且未被使用的对象。
instancetype 与 id 区别?
1、相同点:都可以作为方法的返回类型
2、不同点
(1)instancetype可以返回和方法所在类相同类型的对象,id只能返回未知类型的对象;
(2)instancetype只能作为返回值,不能像id那样作为参数,比如下面的写法
结构体和类的区别
1、结构体只能封装属性,类却不仅可以封装属性也可以封装方法。如果一个封装的数据有属性也有行为,就只能用类了。
2、结构体变量分配在栈,而OC对象分配在堆,栈的空间相对于堆来说是比较小的,但是存储在栈中的数据访问效率相对于堆而言是比较高
3、堆的存储空间比较大,存储在堆中的数据访问效率相对于栈而言是比较低的
4、如果定义一个结构体,这个结构体中有很多属性,那么这个时候结构体变量在栈中会占据很多空间,这样的话就会降低效率
5、我们使用结构体的时候最好是属性比较少的结构体对象如果属性较多的话就要使用类了。
6、结构体赋值的话是直接赋值,对象的指针,赋值的是对象的地址。
NSObject的本质是什么?
NSObject的底层实现实质是一个结构体。而结构体中的成员isa是Class类型,通过源码typedef struct objc_class *Class可知它是一个指针。在64为环境下指针占8个字节,而在32位机下是占4个字节。因此该结构体占8个字节(因为该结构体只有一个成员)
NSObject对象占多少内存?
系统在alloc的时候,分配了16个字节给 NSObject 对象(malloc_size函数获得)
但是实际上 NSObject 只使用了 8个字节的存储空间(64bit系统下)
可以通过class_getInstanceSize()
}
一个Objective-C对象如何进行内存布局?(考虑有父类的情况)
所有父类的成员变量和自己的成员变量都会存放在该对象所对应的存储空间中
父类的方法和自己的方法都会缓存在类对象的方法缓存中,类方法是缓存在元类对象中
每一个对象内部都有一个isa指针,指向他的类对象,类对象中存放着本对象的如下信息
- 对象方法列表
- 成员变量的列表
- 属性列表
什么是MVC
MVC代表模型视图控制器。这是Apple选择作为iOS应用程序开发的主要方法的软件设计模式。模型代表应用程序数据;视图在屏幕上绘制事物;控制器管理模型和视图之间的数据流。模型和视图从不直接相互通信,而依赖于控制器来协调通信
iOS中事件的产生和传递
1.发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的队列事件中
2.UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常会先发送事件给应用程序的主窗口(keyWindow)
3.主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件
4.找到合适的视图控件后,就会调用视图控件的touches方法来作事件的具体处理:touchesBegin... touchesMoved...touchesEnded等
5.这些touches方法默认的做法是将事件顺着响应者链条向上传递,将事件叫个上一个相应者进行处理
属性关键字
- 读写类:
readwrite:属性同时具有 set 和 get 方法
readonly: 属性只有get 方法 - 原子性:
nonatomic: 非原子的,多线程执行效率高
atomic: 读写线程安全,但效率低,而且不是绝对的安全,比如修饰的是数组,那么对数组的读写是安全的,但如果是操作数组进行添加移除其中对象的还,就不保证安全了(默认采用自旋锁实现,保证绝对安全就需要采用互斥锁来实现)
引用计数:
assign: 主要用于修饰基本数据类型, 例如NSInteger,CGFloat,存储在栈中,内存不用程序员管理。assign是可以修饰对象的,修饰对象时不改变其引用计数,会产生悬垂指针。
reatin:
strong :修饰符表示指向并持有该对象,其修饰对象的引用计数会加1。该对象只要引用计数不为0就不会被销毁。当然可以通过将变量强制赋值 nil 来进行销毁。
weak: 只能修饰对象类型,不改变修饰对象的引用计数,对象释放后会被自动置为nil;
unsafe_unretained: 同时修饰基本数据类型和 NSObject 对象,__unsafe_unretain 类似于 weak ,但是当对象被释放后,指针依然保存着之前的地址,被释放后的地址变为僵尸对象,访问被释放的地址就会出问题,所以说他是不安全的。
copy: 分为深拷贝和浅拷贝 浅拷贝:对内存地址的复制,让目标对象指针和原对象指向同一片内存空间会增加引用计数 深拷贝:对对象内容的复制,开辟新的内存空间
可变对象的copy和mutablecopy都是深拷贝;
不可变对象的copy是浅拷贝,mutablecopy是深拷贝;
copy方法返回的都是不可变对象;
浅拷贝:浅拷贝只是对内存地址的复制,让目标对象的指针和源对象指向同一片内存空间。
深拷贝:深拷贝让目标对象指针和源对象指针分别指向两片内容相同的内存空间。
是否开辟了新的内存空间;
深拷贝不影响引用计数,浅拷贝会改变引用计数。
1:__weak 和 _Unsafe_Unretain 的区别?
weak: 修饰的指针变量,在指向的内存地址销毁后,会在 Runtime 的机制下,自动置为 nil。
_Unsafe_Unretain: 不会置为 nil,容易出现 悬垂指针,发生崩溃。但是 _Unsafe_Unretain 比 __weak 效率高。
2:@synthesize 与 @dynamic区别?
@synthesize:编译器自动生成setter和getter的方法,在你没有手动去实现这两个方法时。
@dynamic:告诉编译器你会动态生成setter和getter方法,不会要编译器帮你生成
3:@property的本质是什么?ivar、getter、setter是如何生成并添加到这个类中的?
@property 的本质是实例变量(ivar)+存取方法(access method = getter + setter),即 @property = ivar + getter + setter;
4.__weak 属性修饰的变量,如何实现在变量没有强引用后自动置为 nil ?
用的弱引用 - weak表。也是一张 哈希表。
被 weak 修饰的指针变量所指向的地址是 key ,所有指向这块内存地址的指针会被添加在一个数组里,这个数组是 Value。当内存地址销毁,数组里的所有对象被置为 nil。
5: 内存管理默认的关键字是什么?
ARC: @property (atomic ,readWrite,strong) UIView *view;
MRC: @Property (atomic,readWirte, retain) UIiView *view;
6: 是否了解 深拷贝 和 浅拷贝 的概念,集合类深拷贝如何实现?
1、对不可变的非集合对象,copy是指针拷贝,mutablecopy是内容拷贝
2、对于可变的非集合对象,copy,mutablecopy都是内容拷贝
3、对不可变的数组、字典、集合等集合类对象,copy是指针拷贝,mutablecopy是内容拷贝
4、对于可变的数组、字典、集合等集合类对象,copy,mutablecopy都是内容拷贝
但是,对于集合对象的内容复制仅仅是对对象本身,但是对象的里面的元素还是指针复制。要想复制整个集合对象,就要用集合深复制的方法,有两种:
(1)使用initWithArray:copyItems:方法,将第二个参数设置为YES即可
NSDictionary shallowCopyDict = [[NSDictionary alloc] initWithDictionary:someDictionary copyItems:YES];
(2)将集合对象进行归档(archive)然后解归档(unarchive)
NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
7:block为何要用copy修饰?
堆中的block 也就是copy修饰的block,它的生命周期是随着对象的销毁而结束,只要对象不销毁,我们就能调用到堆中的block。
UI视图相关:
卡顿
cpu:UI的计算,文本的布局、包括一些视图的绘制,图形的解码 》》 产生的位图提交给GPU
GPU:图层的合成、管理渲染
异步绘制:需要了解下
离屏渲染:GPU在当前缓存区外新开辟一个缓冲区进行渲染操作,
当我们指定了某些UI属性,标记它为在未愈合成之前,在当前屏幕上直接显示的时候就会触发离屏渲染 ,离屏渲染增加了GPU的工作量,
分类
能做的事情:
声明私有方法、 分解体积庞大的类文件、把frameWorkd的私有方法公开
能添加的内容:
实列方法、类方法、属性(只get,set方法,没有分配实列变量)、协议
分类为何不能直接声明属性?
category是表示一个指向分类的结构体指针,结构体没有属性列表.
属性=成员变量+get方法+set方法
分类实现的原理?
由运行时来决议,不同分类中含有同名方法,谁最终生效取决于谁最后进行编译分类当中的同名分类方法最终生效,添加的方法正好是系统类的同名方法,分类方法会覆盖系统方法,由于消息传递过程中优先查找数组靠前的方法;
分类底层原理探索
分类的实现原理是将分类中的方法,属性,协议信息放在 category_t结构体中,然后将结构体内的方法列表拷贝到类对象的方法列表中。
分类中可以添加属性,但是并不会自动生成成员变量及set、get方法。因为底层的category_t结构体中并不存在成员变量。通过之前对对象的分析我们知道成员变量是存放在实例对象中的,并且编译的那一刻就已经决定好了。而分类是在运行时才去加载的,那么我们就无法在程序运行时将分类的成员变量中添加到实例对象的结构体中。因此分类中不可以添加成员变量
扩展
作用:
声明私有属性、私有变量、
分类与扩展的区别:
1:分类运行时决议,扩展编译时决议
2:具有声明也有实现方法,扩展只有实现方法
3:能为系统类添加分类,扩展不能为系统类添加扩展
通知:
通知和代理的区别
1:通知是以观察者模式实现用于跨层传递的机制,代理是以代理模式来实现的
2: 一对多,一对一
通知的实现原理:
NSNotificationCent 可能存在一个 notificationMap 或者是字典,字典的key 就是我们的notificationName ,value就是 obServes,对于同一个notificationName
可能会添加多个observe ,所以这个observe是个list (列表),这个列表当中的每一个成员应当包含通知接收的观察者,同时也包含关于这个观察者调用的方法。
KVO
什么是kvo:
1:kvo是object-c对于观察者模式的又一实现;
每次当被观察对象的某个属性值发生改变时,注册的观察者便能获得通知。
2:苹果使用了isa 混写(isa- swizzling)来实现kvo
追问- isa—swizzling 的实现原理
其实KVO是通过isa-swizzling技术实现的,主要的操作如下:
1.当为某个对象添加观察者的时候,该对象的类将被继承生成一个中间类,并使该对象的isa指针指向中间类(所以,有时候发送消息需要明确指定类型)。注意:同一个类的其它实例对象并不受影响。
2.中间类在被观察的属性的setter方法中,在改变属性值的前后分别添加了willChangeValueForKey:和didChangeValueForKey:。使其在通过KVC标准改变属性值时可以被观察到,并向观察者发送消息。
3.当移除对某个对象的所有观察后,该对象的isa指针会重新指向原有的类。
kvo的实现原理?
kvo是系统关于观察者模式的又一实现;
kvo运用了isa混写技术来动态运行时来为某一个类添加一个子类,然后重写它的setter方法,同时把原有类的isa指针指向新创建的类;
在多人合作开发环境下,可能会有重复添加KVO的时候,或者重复移除KVO的时候,重复添加或删除KVO会怎么样?采取什么方案去解决?
重复添加或删除kvo会发生crash;一种就是手动实现一个KVO,KVO本质是苹果偷偷添加了一个派生类,将指针指向派生的子类,去监听子类的set方法从而实现监听的效果,手动实现一个KVO,并修改其添加和移除的方法,使用BOOL值作为属性在方法内部进行判断当前这个对象是否添加过或删除过即可;第二种方法,就是写一个类别,利用runtime去交换kvo的添加或移除方法到自定义的添加或移除方法,修改内部方法实现以达到该效果。
KVC
KVC(Key-value coding)键值编码,指iOS的开发中,可以允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值而不需要调用明确的存取方法。
KVC的实现原理
- KVC,键-值编码,使用字符串直接访问对象的属性。
- 底层实现,当一个对象调用setValue方法时,方法内部会做以下操作:
1、检查是否存在相应key的set方法,如果存在,就调用set方法
2、如果set方法不存在,就会查找与key相同名称并且带下划线的成员属性,如果有,则直接给成员属性赋值
3、如果没有找到_key,就会查找相同名称的属性key,如果有就直接赋值
4、如果还没找到,则调用valueForUndefinedKey:和setValue:forUndefinedKey:方法
内存管理
Objective-C 如何对内存管理的,说说你的看法和解决方法?
答:Objective-C的内存管理主要有三种方式ARC(自动内存计数)、手动内存计数、内存池。
1). 自动内存计数ARC:由Xcode自动在App编译阶段,在代码中添加内存管理代码。
2). 手动内存计数MRC:遵循内存谁申请、谁释放;谁添加,谁释放的原则。
3). 内存释放池Release Pool:把需要释放的内存统一放在一个池子中,当池子被抽干后(drain),池子中所有的内存空间也被自动释放掉。内存池的释放操作分为自动和手动。自动释放受runloop机制影响。
复制代码
内存泄漏检测方法:
- MLeaksFinder 第三方库
原理:MLeaksFinder一开始是从UIViewController入手的,UIViewController在POP或dismiss之后该控制器及其上的view,view的subviews都会被释放掉,MleaksFinder就是在控制器POP或dismiss之后去查看该控制器和其上的view是否都被释放掉。
具体的方法是,为基类 NSObject 添加一个方法 willDealloc 方法,该方法的作用是,先用一个弱指针指向 self,并在一小段时间(3秒)后,通过这个弱指针调用 -assertNotDealloc,而 -assertNotDealloc 主要作用是直接中断言。这样,当我们认为某个对象应该要被释放了,在释放前调用这个方法,如果3秒后它被释放成功,weakSelf 就指向 nil,不会调用到 -assertNotDealloc 方法,也就不会中断言,如果它没被释放(泄露了),-assertNotDealloc 就会被调用中断言。这样,当一个 UIViewController 被 pop 或 dismiss 时(我们认为它应该要被释放了),我们遍历该 UIViewController 上的所有 view,依次调 -willDealloc,若3秒后没被释放,就会中断言。
- 2.Memory Leaks
泄露的内存主要有以下两种:
Laek Memory 这种是忘记 Release 操作所泄露的内存。
Abandon Memory 这种是循环引用,无法释放掉的内存
block相关
截获变量相关知识点
-
局部变量
基本数据类型:对于基本数据类型的局部变量截获其值
对象类型:对象类型的局部变量连同其所有权修饰符一起截获 -
静态局部变量
以指针形式截获局部静态变量 -
全局变量 和静态全局变量
不对其进行截获
问题总结:
-
1.对变量进行赋值时;
需要__block修饰符:局部变量(基本数据类型、对象类型)
不需要__block修饰符:静态全局变量、全局变量、静态局部变量(通过指针形式来使用操作对应变量的) -
2.什么是block?
简单来说block是一个对象,是将函数及时执行上下文封装起来的对象。block是对象,它封装了一段代码,这段代码可以在任何时候执行。block可以作为函数参数或者函数的返回值,而其本身又可以带输入参数或返回值。与代理协议的功能一样,也可以用于事件通信。 -
3.为什么block会引起循环引用?
如果当前block对当前对象某个成员变量进行截获的话,那么这个block会对对应变量一个强引用,而当前block又由于对像有一个强引用就产生了一个自循环引用,我们可以通过声明其为__weak变量来进行循环引用的消除,如果我们定义了一个__block的声明符的话也会产生一个自循环引用,这是我们需要区分场景,一种是ARC中会产生循环引用,可以通过断环的方式去解除循环引用,但是有一个弊端就是说如果这个block没有得到调用,这个循环引用就一直得不到解除,而在MRC中是不存在循环引用的。 -
4.怎样理解blcok截获变量的特性?
对于基本数据类型的变量是对其值进行截获,而对于对象类型的局部变量是对其一个强引用,对于静态局部变量是以指针形式对其截获,全局变量和静态全局变量不对其进行截获。 -
5:关于block方面产生的循环引用,及解决方案?
比如block当中捕获的变量也是当前对象的一个成员变量,而这个block也是当前对象的一个成员变量,也就会造成自循式的循环环引用,我们可以避免自循环引用的方式加以__weak所有权修饰符的形式来解决,同时__block也会导致混合引用, - 使用block和使用delegate完成委托模式有什么优点?
使用block实现委托模式,其优点是回调的block代码块定义在委托对象函数内部,使代码更为紧凑;适配对象不再需要实现具体某个protocol,代码更为简洁。
- 使用block和使用delegate完成委托模式有什么优点?
- _block和__weak修饰符的区别?
__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。__weak只能在ARC模式下使用,也只能修饰对象,不能修饰基本数据类型。__block对象可以在block中被重新赋值,__weak不可以。
- _block和__weak修饰符的区别?
- Block不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针的内存地址。
__block 所起到的作用就是只要观察到该变量被 block 所持有,就将“外部变量”在栈中的内存地址放到了堆中。进而在block内部也可以修改外部变量的值。
- Block不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针的内存地址。
-
9.用copy修饰的原因
block创建时默认是创建在栈上的, 超过作用域后就会被销毁, 只有使用copy才会生成一个堆block, 在作用域外被访问 -
所有block都会造成循环引用吗?
UIView动画block不会造成循环引用是因为这是类方法,不可能强引用一个类,所以不会造成循环引用;
Masonry约束block不会造成循环引用是因为内部使用的是局部变量,block并没有持有self,在超出它的作用域后,就会被销毁;
AFN请求回调block不会造成循环引用是因为在内部做了处理。block先是被AFURLSessionManagerTaskDelegate对象持有。而AFURLSessionManagerTaskDelegate对象被mutableTaskDelegatesKeyedByTaskIdentifier字典持有,在block执行完成后,mutableTaskDelegatesKeyedByTaskIdentifier字典会移除AFURLSessionManagerTaskDelegate对象,这样对象就被释放了,所以不会造成循环引用。 -
10、block根据内存区间分为几种类型?
__NSMallocBlock (栈block) /__NSStackBlock (堆block) / __ NSGlobalBlock (全局block)
在ARC下,默认的block创建后都是GlobalBlock,当block内部引用到外部变量的值时,就会从GlobalBlock变到NSMallocBloc,注意即使你引用的是在栈区的局部变量也是NSMallocBloc,因为ARC下会将栈区的block copy到堆区,如果在MRC下需要手动去copy才会到堆区,所以关键在于block是否引用外部的变量,外部变量内存地址决定了block的内存地址,即NSStackBlock还是NSMallocBlock
RunLoop
相关指导文章: https://www.jianshu.com/p/5ef8f28025b9
什么是runloop?
runloop是通过内部维护事件循环来对事件、消息进行管理的一个对象。
1:没有消息需要处理时,休眠以避免资源浪费;
用户态 ————>内核态(状态切换)
2:有消息处理时,立刻被唤醒。
用户态 <—— 内核态
怎样做到有事做事,没事休息的?
当我们调用cfRunLoop的run 相关的方法之后,会调用系统的一个函数much__mag同时发生了用户态向核心态的切换然后当前线程处于休眠状态。
NSRunLoopCommonModes
1:commonMode 不是实际存在的一种mode
2:commonModel是同步source/Time/observer到多个model的一种技术
事件循环机制流程图:
描述如下:
首先,进入 RunLoop (顺便通知Observer我要进入了)
然后就去处理 Timer (顺便通知 Observer 我要处理 Timer)
然后处理 Source (顺便通知 Observer 我要处理 Source)
然后判断 Source1 还有没有没有分发的任务?
--- 有的话就去处理 Source1 收到的任务包括 Timer 和Source
--- 没有的话, 就要去睡觉了
任务都处理完了,进入休眠(顺便通知 Observer 我要休眠)
休眠的时候如果有新的任务进入, RunLoop被唤醒....
RunLoop 和多线程:
runloop和多线程是一一对应的,默认情况下是没有创建runloop
怎样实现一个常驻线程?
1:为当前线程开启一个runloop;
2:向该RunLoop添加一个Port/source/observer等维持RunLoop的事件循环;
3:启动该RunLoop(也就是调用cfRunLoop的run 方法).
RunLoop和多线程的关系?
每条线程都有唯一的一个与之对应的RunLoop对象,其关系是保存在一个全局的 Dictionary 里;主线程的RunLoop已经自动创建,子线程的RunLoop需要主动创建;RunLoop在第一次获取时创建,在线程结束时销毁
RunTime
1. runtime怎么添加属性、方法等
ivar表示成员变量
class_addIvar
class_addMethod
class_addProperty
class_addProtocol
class_replaceProperty
2. runtime 如何实现 weak 属性
-
首先要搞清楚weak属性的特点
weak策略表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似;然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out) -
那么runtime如何实现weak变量的自动置nil?
runtime对注册的类,会进行布局,会将 weak 对象放入一个 hash 表中。用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会调用对象的 dealloc 方法,假设 weak 指向的对象内存地址是a,那么就会以a为key,在这个 weak hash表中搜索,找到所有以a为key的 weak 对象,从而设置为 nil。
-
经典答案
Runtime:会维护ー个weak表,用于维护指向对象的所
有weak指针。Weak表是一个哈希表,其key为所指
对象的指针, value为weak指针的地址数组。
具体过程如下
1、初始化时: runtime会调用objc_ initweak函数,初始化一个新的weak指针指向对象的地址。
2、添加引用时: objc_ initweakl函数会调用objc_ storeweak()函数,更新指针指向,创建对应的弱引用表。
3、释放时,调用 cleardeallocatingl函数。cleardeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个 entry从weak表中删除,最后清理对象的记录。 -
weak属性需要在dealloc中置nil么
在ARC环境无论是强指针还是弱指针都无需在 dealloc 设置为 nil , ARC 会自动帮我们处理
即便是编译器不帮我们做这些,weak也不需要在dealloc中置nil
在属性所指的对象遭到摧毁时,属性值也会清空
objc// 模拟下weak的setter方法,大致如下- (void)setObject:(NSObject *)object{ objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN); [object cyl_runAtDealloc:^{ _object = nil; }];}
3. runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)
每一个类对象中都一个对象方法列表(对象方法缓存)
类方法列表是存放在类对象中isa指针指向的元类对象中(类方法缓存)
方法列表中每个方法结构体中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.
当我们发送一个消息给一个NSObject对象时,这条消息会在对象的类对象方法列表里查找
当我们发送一个消息给一个类时,这条消息会在类的Meta Class对象的方法列表里查找
4.使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?
无论在MRC下还是ARC下均不需要被关联的对象在生命周期内要比对象本身释放的晚很多,它们会在被 NSObject -dealloc 调用的object_dispose()方法中释放
补充:对象的内存销毁时间表,分四个步骤
1、调用 -release :引用计数变为零
- 对象正在被销毁,生命周期即将结束.
- 不能再有新的 __weak 弱引用,否则将指向 nil.
- 调用 [self dealloc]
2、 父类调用 -dealloc
- 继承关系中最直接继承的父类再调用 -dealloc
- 如果是 MRC 代码 则会手动释放实例变量们(iVars)
- 继承关系中每一层的父类 都再调用 -dealloc
3、NSObject 调 -dealloc
- 只做一件事:调用 Objective-C runtime 中object_dispose() 方法
- 调用 object_dispose()
- 为 C++ 的实例变量们(iVars)调用 destructors
- 为 ARC 状态下的 实例变量们(iVars) 调用 -release
- 解除所有使用 runtime Associate方法关联的对象
- 解除所有 __weak 引用
- 调用 free()
5._objc_msgForward函数是做什么的?直接调用它将会发生什么?
_objc_msgForward是IMP类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发
直接调用_objc_msgForward是非常危险
的事,这是把双刃刀,如果用不好会直接导致程序Crash,但是如果用得好,能做很多非常酷的事
JSPatch就是直接调用_objc_msgForward来实现其核心功能的
详细解说参见这里的第一个问题解答
6.能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
不能向编译后得到的类中增加实例变量
能向运行时创建的类中添加实例变
***分析如下: ***
因为编译后的类已经注册在runtime中,类结构体中的objc_ivar_list 实例变量的链表和instance_size实例变量的内存大小已经确定,同时runtime 会调用class_setIvarLayout 或 class_setWeakIvarLayout来处理strong weak引用,所以不能向存在的类中添加实例变量
运行时创建的类是可以添加实例变量,调用 class_addIvar函数,但是得在调用objc_allocateClassPair之后,objc_registerClassPair之前,原因同上。
7.简述下Objective-C中调用方法的过程(runtime)
Objective-C是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector),整个过程介绍如下:
- objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类
- 然后在该类中的方法列表以及其父类方法列表中寻找方法运行
- 如果,在最顶层的父类(一般也就NSObject)中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX
- 但是在这之前,objc的运行时会给出三次拯救程序崩溃的机会,这三次拯救程序奔溃的说明见问题《什么时候会报unrecognized selector的异常》中的说明
补充说明:Runtime 铸就了Objective-C 是动态语言的特性,使得C语言具备了面向对象的特性,在程序运行期创建,检查,修改类、对象及其对应的方法,这些操作都可以使用runtime中的对应方法实现。
8.什么是method swizzling(俗称黑魔法)
简单说就是进行方法交换
在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的
每个类都有一个方法列表,存放着方法的名字和方法实现的映射关系,selector的本质其实就是方法名,IMP有点类似函数指针,指向具体的Method实现,通过selector就可以找到对应的IMP
9 :runtime具体应用
利用关联对象(AssociatedObject)给分类添加属性
遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
交换方法实现(交换系统的方法)
利用消息转发机制解决方法找不到的异常问题
KVC 字典转模型
10:向nil对象发送消息会怎样?
在object中我们向对象发送消息时,runtime库会根据对象的isa指针查找该对象所属的类。然后在该类中的方法列表及其父类的方法列表中查找方法运行,然后再发送消息的时候,object_magsend方法不会返回值,所谓的返回值都是具体调用时执行的,然而当向一个nil对象发送消息时其对象的isa指针就返回0地址,所以不会发送任何错误。
网络相关
TCP/UDP
1:TCP建立流程:
1:为何要建立3次握手🤝?
当客服端发生请求发送了同步报文SYN到服务端,如果在传输过程中发生了超时逗留在网络路由中,发生超时后客户端会启用超时重传策略重新发送新的SYN同步报文到服务端,之后server端发送SYN同步报文、ACK确认报文到客户端,客户端再将ACK确认报文发送到服务端,这时在网络路由中逗留的超时报文传递到server端,server会以为是一次新的连接。,发送确认信息给发送端,但是当发送端没有理会的时候,接收端也就知道这是错误信息,就不会等待,也就没有资源浪费了。
DNS:
1:解释
DNS是 域名到IP地址的映射,DNS解析采用了UDP报文端口号为53且明文传输;
客户端通过DNS协议到DNS服务器请求对响应域名的一个解析,DNS把解析后的结果IP地址返回给客户端,再由客服端向IP server服务端发起请求
2:DNS解析的方式
递归查询、迭代查询
3:DNS常见的问题?
01:DNS劫持: 在向DNS服务器请求解析IP地址的过程中被钓鱼DNS服务窃听到返回一个错误的IP地址给客户端
02:DNS解析转发
4:如何防止DNS劫持呢?
01:httpDNS : 使用http协议向DNS服务器的80端口进行请求IP
02:长链接模式:
Session 、 cookie
对http无状态的特点的补充,
1:session和cookie的区别?
cookie:主要是记录用户状态,区分用户,状态保存在客户端中;
session: 记录用户状态,区分用户,状态存放在服务器端中;
2:怎么修改cookie?
-
新cookie覆盖旧的;
-
name,path,dimin等需要与原cookie的一致
3:怎么删除cookie? -
新cookie覆盖旧的;
-
name,path,dimin等需要与原cookie的一致
-
设置cookie的expires=过去的某个时间点;
4:如何保证cookie安全? -
对cookie进行加密
-
只在https上携带cookie;
-
设置cookie的httponly,防止跨站脚本攻击;
5:HTTP中 GET和POST有什么区别? -
GET是幂等的,可缓存的,不安全的
-
post是不幂等的,安全的;
6:https的建立流程是怎么样的?
客户端会发生到服务端一个支持的加密算法列表,TLS的版本号以及随机数C,服务端接收到后回给客户端一个服务的证书(商定的加密算法、随机数s),通过非对称加密进行对称加密的秘钥传输,之后http直接的请求就通过非对称加密保护的对称加密秘钥进行后续的网络访问。
7: TCP 和UDP的区别?
TCP是面向连接的并且可靠传输以及面向字节流,包括了流量的控制和拥塞的控制;
UDP只是简单的提供了复用、分用以及差错检错的一个传输层的基本功能,udp是无连接的;
8:网络请求返回HTTP状态码有哪些?
200 - 服务器成功返回网页 404 - 请求的网页不存在 503 - 服务不可用 如: 100 (继续)
1xx (临时响应)表示临时响应并需要请求者继续执行操作的状态代码。
2xx (成功):报文已经收到并成功被处理。如:200 (成功)
3xx (重定向):重定向,资源位置发生变动,需要客户端重新发送请求。如:300 (多种选择)
4xx(客户端错误):客户端错误,请求报文有误,服务端无法处理。(解决办法传参格式不正确)
5xx(服务器错误):服务端在处理请求时内部发生了错误。如:500 (服务器内部错误) -
推荐文章 网络相关精选文章
多线程 GCD
自旋锁、互斥锁 选择
自旋锁:等待状态处于忙等
互斥锁:等待状态处于休眠
浅谈dispatch_once实现原理:
+ (instancetype)sharedInstance
{
/*定义相应类实例的静态变量;
意义:函数内定义静态变量,无论该函数被调用多少次,
在内存中只初始化一次,并且能保存最后一次赋的值
*/
static ClassName *instance = nil;
/*定义一个dispatch_once_t(其实也就是整型)静态变量,
意义:作为标识下面dispatch_once的block是否已执行过。
static修饰会默认将其初始化为0,当值为0时才会执行block。
当block执行完成,底层会将onceToken设置为1,这也就是为什
么要传onceToken的地址(static修饰的变量可以通过地址修改
onceToken的值),同时底层会加锁来保证这个方法是线程安全的
*/
static dispatch_once_t onceToken;
/*只要当onceToken == 0时才会执行block,否则直接返回静态变量instance*/
dispatch_once(&onceToken, ^{
instance = [[ClassName alloc] init];
//...
});
return instance;
}
1、什么情况使用自旋锁比较划算?
预计线程等待锁的时间很短
加锁的代码(临界区)经常被调用,但竞争情况很少发生
CPU资源不紧张
多核处理器
2、什么情况使用互斥锁比较划算?
预计线程等待锁的时间较长
单核处理器
临界区有IO操作
临界区代码复杂或者循环量大
临界区竞争非常激烈
数据库相关操作
多线程操作共享资源的问题
- 在多线程的环境下,共享的资源可能会被多个线程共享,也就是多个线程可能会操作同一块资源.
- 当多个线程操作同一块资源时,很容易引发数据错乱和数据安全问题,数据有可能丢失,有可能增加,有可能错乱.
- 经典案例 : 火车站卖票,商品抢购
- 线程安全:同一块资源,被多个线程同时读写操作时,任然能够得到正确的结果,称之为线程是安全的.
方法交换
tableview 性能优化:
复用单元格
单元格中的视图尽量都使用不透明的,单元格中尽量少使用动画
图片加载使用异步加载
滑动时不加载图片,停止滑动时开始加载(需要处理快速滑动的空白)
单元格中的内容可以在自定义cell类中的drawRect方法内自己绘制
如非必要,减少reloadData全部cell,只reloadRowsAtIndexPaths
如果cell是动态行高,计算出高度后缓存
cell高度固定的话直接使用cell.rowHeight设置高度
https://www.jianshu.com/p/04457377b48d
webView
1.说一下 JS 和 OC 互相调用的几种方式?
- js调用oc的三种方式:(从iOS13开始苹果要求弃用UIWebView)
方式一:WKWebView拦截 URL
方式二:MessageHandler
通过替换js中的function(方法)
方式二:通过注入对象,直接调用对象方法
方式三:利用网页重定向,截取字符串.
方式四:
- oc调用js代码两种方式
1.通过webVIew调用 webView stringByEvaluatingJavaScriptFromString: 调用
2.通过JSContext调用[context evaluateScript:];
2.在使用 WKWedView 时遇到过哪些问题?
白屏问题,
Cookie 问题,
在WKWebView上直接使用NSURLProtocol无法拦截请求,
在WKWebView 上通过loadRequ发起的post请求body数据被丢失,截屏问题等
架构和框架相关
整理架构流程:
独立于APP的通用层(网络请求、时长统计框架、bug统计、第三方库等)
通用业务层(针对当下公司业务组件、有些UI的封装)
中间层 (协调和解耦,与业务之前紧密联系)
业务A、业务B、业务C、业务D
业务之间解耦的方式?
1: openUrl
2: 依赖注入
SDWebIMage基本流程
核心类 负责模块
SDWebImageManager 调度各个类
SDWebImageDownloader 下载
SDImageCache 缓存, 包括内存缓存和磁盘缓存
SDWebImageCodersManager 图片解码
1:图片通过什么方式进行读写,过程是怎么样的?
以图片URL的单项hash值作为key来存储到对于的框架中,读取是通过图片请求URL所对应的key进行查找,从内存中缓存,磁盘缓存中进行对应的查找都没有再进行下载操作,采取多级缓存的方式。
2:如果让你去设计SDWebImage的缓存机制,怎么去设计?
SDWebImage的缓存分为两种,一种是内存缓存(MemoryCache),另一种是硬盘缓存(DiskCache),我会自定义一个单例缓存类,其中会有对应的两种缓存作为属性,另外还会暴露一些自定义属性让用户可以去修改缓存的策略,例如最大缓存字节数,自动进行清理缓存还是不自动去清理等,还会暴露一些方法比如通过key去查找该缓存文件,当前key是否存在于沙盒或者内存中等。
3、 如何实现断点续传?
断点续传的理解可以分为两部分:一部分是断点,一部分是续传。断点的由来是在下载过程中,将一个下载文件分成了多个部分,同时进行多个部分一起的下载,当 某个时间点,任务被暂停了,此时下载暂停的位置就是断点了。续传就是当一个未完成的下载任务再次开始时,会从上次的断点继续传送。
使用多线程断点续传下载的时候,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个部分,每一个部分采用一个线程进行上传或下载,多个线程并发可以占用服务器端更多资源,从而加快下载速度。
在下载(或上传)过程中,如果网络故障、电量不足等原因导致下载中断,这就需要使用到断点续传功能。下次启动时,可以从记录位置(已经下载的部分)开始,继续下载以后未下载的部分,避免重复部分的下载。断点续传实质就是能记录上一次已下载完成的位置。
4、断点续传的过程
1.断点续传需要在下载过程中记录每条线程的下载进度;
2.每次下载开始之前先读取数据库,查询是否有未完成的记录,有就继续下载,没有则创建新记录插入数据库;
3.在每次向文件中写入数据之后,在数据库中更新下载进度;
4.下载完成之后删除数据库中下载记录。
5.如果让你去设计SDWebImage的缓存机制,怎么去设计?
SDWebImage的缓存分为两种,一种是内存缓存(MemoryCache),另一种是硬盘缓存(DiskCache),我会自定义一个单例缓存类,其中会有对应的两种缓存作为属性,另外还会暴露一些自定义属性让用户可以去修改缓存的策略,例如最大缓存字节数,自动进行清理缓存还是不自动去清理等,还会暴露一些方法比如通过key去查找该缓存文件,当前key是否存在于沙盒或者内存中等。
Swift
swift的优势有哪些?
1、Swift容易阅读,语法和文件结构简易化。
2、Swift更易于维护,文件分离后结构更清晰。
3、Swift更加安全,它是类型安全的语言。
4、Swift代码更少,简洁的语法,可以省去大量冗余代码
5、Swift速度更快,运算性能更高。
人事及领导问答面试题:
https://www.jianshu.com/p/7ca1737c65dc
提问环节
- 公司在 iOS 端主要使用了哪些技术框架?
- 公司内部有技术分享或者别的学习交流机会吗?
- 公司当前团队有多大,希望我进去参与哪方面的业务?
- 公司当前有没有什么大的竞争对手?
- 未来公司希望在哪些产品上重点发力?
网友评论