Swift中struct和class的区别
struct是值引用,存放于栈区,更轻量,class是类型引用,存放于堆区。struct无法继承,class可以继承。
Swift中的方法调用
直接派发、函数表派发、消息机制派发。派发方式受声明位置,引用类型,特定行为的影响。
Swift和OC的区别
swift:静态语言、更精简、存在命名空间、方法调用方式多、存在泛型、元组、高阶函数、性能更高、速度更快。
OC:动态语言、消息转发、面向对象
Swift面向协议编程
面向协议则是用协议的方式组织各个类的关系,Swift底层几乎所有类都构建在协议之上。
面向协议能够解决面向对象的菱形继承,横切关注点和动态派发的安全性等问题。
OC中的block
block本质是一个对象,底层用struct实现。
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};
isa 指针,所有对象都有该指针,用于实现对象相关的功能。
flags,用于按 bit 位表示一些 block 的附加信息,本文后面介绍 block copy 的实现代码可以看到对该变量的使用。
reserved,保留变量。invoke,函数指针,指向具体的 block 实现的函数调用地址。
descriptor, 表示该 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函数的指针。
variables,capture 过来的变量,block 能够访问它外部的局部变量,就是因为将这些变量(或变量的地址)复制到了结构体中。
__block的作用是可以获取对应变量的指针,使其可以在block内部被修改。通过反编译的代码我们可以看到该对象是这样的:
struct __Block_byref_i_0 {
void *__isa;
__Block_byref_i_0 *__forwarding;
int __flags;
int __size;
int val; //变量名
};
GCD中的Block是在堆上。
NSCoding协议
一种编码协议,归档时和解档时需要依赖该协议定义的编码和解码方法。Foundation和Cocoa Touch中的大部分类都遵循了这个协议,一般被NSKeyedArchiver做自定义对象持久化时使用。
KVO的实现原理
利用Runtime生成一个中间对象,让原对象的isa指针指向它,然后重写setter方法,插入willChangeValueForKey和didChangeValueForKey方法。当属性变化时会调用,会调用这两个方法通知到外界属性变化。
NSOperation的特性
NSOperation是对GCD的封装,具有面向对象的特点,可以更方便的进行封装,可以设置依赖关系。
NSNotificaiton是同步,如果发通知时在子线程,接收在子线程。
事件响应链
手势的点击会发生两个重要事情,事件传递和事件响应。
事件传递:从UIApplication开始,到window,再逐步往下层(子视图)找,直到找到最深层的子视图,其为first responder。用到的判断方法是pointInside:withEvent和hitTest:withEvent。
事件响应:从识别到的视图(first responder)开始验证能否响应事件,如果不能就交给其上层(父视图)视图,如果能相应将不再往下传递,如果直到找到UIApplication层还没有相应,那就忽略该次点击。用到的判断方法是touchesBegan:withEvent、touchesMoved:withEvent等。
异步渲染
异步渲染就是在子线程进行绘制,然后拿到主线程显示。UIView的显示是通过CALayer实现的,CALayer的显示则是通过contents进行的。异步渲染的实现原理是当我们改变UIView的frame时,会调用layer的setNeedsDisplay,然后调用layer的display方法。我们不能在非主线程将内容绘制到layer的context上,但我们单独开一个子线程通过CGBitmapContextCreateImage()绘制内容,绘制完成之后切回主线程,将内容赋值到contents上。
layoutsubviews调用时机
init初始化不会触发。
addSubview时。
设置frame且前后值变化,frame为zero且不添加到指定视图不会触发。
旋转Screen会触发父视图的layoutSubviews。
滚动UIScrollView引起View重新布局时会触发layoutSubviews。
离屏渲染
如果要在显示屏上显示内容,我们至少需要一块与屏幕像素数据量一样大的frame buffer,作为像素数据存储区域。
如果有时因为面临一些限制,无法把渲染结果直接写入frame buffer,而是先暂存在另外的内存区域,之后再写入frame buffer,那么这个过程被称之为离屏渲染。
以阴影为例,为什么它会导致离屏渲染。因为GPU的渲染是遵循“画家算法”,一层一层绘制的,但阴影很特殊,它需要全部内容绘制完成,再根据外轮廓进行绘制。
这就导致了,阴影这一层要一直占据一块内存区域,这就导致了离屏渲染。
类似导致离屏渲染的情况还有:cornerRadius+clipsToBoundsgroup opacity 组透明度mask 遮罩UIBlurEffect 毛玻璃效果。
CoreAnimation & UIKit
CoreAnimation虽然直译是核心动画,但它其实是一个图像渲染框架,动画实现只是它的一部分功能。是UIKit和AppKit的底层实现,位于Metal、Core Graphics和GPU之上之上。
ARC方案的原理
ARC(Automatic Reference Cunting)自动引用计数,意即通过LLVM编译器自动管理对应的引用计数状态。ARC开启时无需再次键入retain或者release代码。
它是在编译阶段添加retain或者release代码的。
避免循环引用
循环引用及两个及以上对象出现引用环,导致对象无法释放的情况。一般在block,delegate,NSTimer时容易出现这个问题。
解决方案就是让环的其中一环节实现弱引用。
为什么当我们在使用block时外面是weak 声明一个weakSelf,还要在block内部使用strong再持有一下?
block外界声明weak是为了实现block对对象的弱持有,而里面的作用是为了保证在进到block时不会发生释放。
Autoreleasepool是实现机制是什么?它是什么时候释放内部的对象的?它内部的数据结构是什么样的?当我提到哨兵对象时,会继续问哨兵对象的作用是什么,为什么要设计它?
Autoreleasepool的原理是一个双向列表,它会对加入其中的对象实现延迟释放。当Autoreleasepool调用drain方法时会释放内部标记为autorelease的对象。
class AutoreleasePoolPage {
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
};
哨兵对象类似一个指针,指向自动释放池的栈顶位置,它的作用就是用于标记当前自动释放池需要释放内部对象时,释放到那个地方结束,每次入栈时它用于确定添加的位置,然后再次移动到栈顶。
哪些对象会放入到Autoreleasepool中?
有两种情况生成的对象会加入到autoreleasepool中:
非alloc/new/copy/mutablecopy 开始的方式初始化时。
id的指针或对象的指针在没有显示指定时
weak的实现原理是什么?当引用对象销毁是它是如何管理内部的Hash表的
runTime会把对weak修饰的对象放到一个全局的哈希表中,用weak修饰的对象的内存地址为key,weak指针为值,在对象进行销毁时,用通过自身地址去哈希表中查找到所有指向此对象的weak指针,并把所有的weak指针置位nil。
Runtime
消息发送的流程
OC中的方法调用会转化成给对象发送消息,发送消息会调用这个方法:objc_msgSend(receiver, @selector(message))
该过程有以下关键步骤:
先确定调用方法的类已经都加载完毕,如果没加载完毕的话进行加载
从cache中查找方法
cache中没有找到对应的方法,则到方法列表中查,查到则缓存
如果本类中查询到没有结果,则遍历所有父类重复上面的查找过程,直到NSObject
关联对象时什么情况下会导致内存泄露
关联对象可以理解就是持有了一个对象,如果是retain等方式的持有,而该对象也持有了本类,那就是导致了循环引用。
消息转发的流程
消息转发是发生在接收者(receiver)没有找到对应的方法(method)的时候,该步骤有如下几个关键步骤:
消息转发的时候,如果是实例方法会走resolveInstanceMethod:,如果是类方法会走resolveClassMethod:,它们的返回值都是Bool,需要我们确定是否进行转发。
如果第一步返回YES,确定转发就会进到下个方法forwardingTargetForSelector,这个方法需要我们指定一个被用receiver。
methodSignatureForSelector用于指定方法签名,forwardInvocation用于处理Invocation,进行完整转发。
如果消息转发也没有处理即为无法处理,会调用doesNotRecognizeSelector,引发崩溃。
category能否添加属性,为什么?能否添加实例变量,为什么?
可以添加属性,这里的属性指@property,但跟类里的@property又不一样。正常的@property为:实例变量Ivar + Setter + Getter 方法,分类里的@property这三者都没有,需要我们手动实现。
分类是运行时被编译的,这时类的结构已经固定了,所以我们无法添加实例变量。
对于分类自定义Setter和Getter方法,我们可以通过关联对象(Associated Object)进行实现。
元类的作用
元类的作用是存储类方法,同时它也是为了让OC的类结构能够形成闭环。
对于为甚设计元类有以下原因;
在OC的世界里一切皆对象(借鉴于Smalltalk),metaclass的设计就是要为满足这一点。
在OC中Class也是一种对象,它对应的类就是metaclass,metaclass也是一种对象,它的类是root metaclass,在往上根元类(root metaclass)指向自己,形成了一个闭环,一个完备的设计。
如果不要metaclass可不可以?也是可以的,在objc_class再加一个类方法指针。但是这样的设计会将消息传递的过程复杂化,所以为了消息传递流程的复用,为了一切皆对象的思想,就有了metaclass。
类方法、类属性的存储地
类方法和类属性都是存储到元类中的。
类属性在Swift用的多些,OC中很少有人用到,但其实它也是有的,写法如下:
@interface Person : NSObject
// 在属性类别中加上class
@property (class, nonatomic, copy) NSString *name;
@end
// 调用方式
NSString *temp = Person.name;
需要注意的是跟实例属性不一样,类属性不会自动生成实例变量和setter,getter方法,需要我们手动实现。
runtime的应用场景
hook系统方法进行方法交换。
了解一个类(闭源)的私有属性和方法。
关联对象,实现添加分类属性的功能。
修改isa指针,自定义KVO。
Runloop
检测卡顿
线程保活
性能优化,将一些耗时操作放到runloop wait的情况处理。
对TableView进行性能优化
缓存高度
异步渲染
减少离屏渲染
缩小包体积
图片压缩,无用图片删除
一些大图可以动态下发
删除无用类,无用方法
减少三方库的依赖
项目编译的流程
编译流程:
预处理:处理宏定义,删除注释,展开头文件。
词法分析:把代码切成一个个token,比如大小括号等于号还有字符串
语法分析:验证语法是否正确,合成抽象语法树AST
静态分析:查找代码错误
类型检查:动态和静态
目标代码的生成与优化,包括删除多余指令,选择合适的寻址方式,如果开启了bitcode,会做进一步的优化
汇编:由汇编器生成汇编语言
机器码:由汇编语言转成机器码,生成.o文件
应用启动的流程:
启动的前提是完成编译,运行程序即运行编译过后的目标程序,它分为main函数前和main函数后:
main前
加载可执行文件(App的.o文件集合)
加载动态链接库(系统和应用的动态链接库),进行rebase指针调整和bind符号绑定
Objc运行时的初始处理,包括Objc相关类的注册,category注册,selector唯一性检查
初始化,包括执行+load()、attribute(constructor)修饰的函数的调用、创建C++静态全局变量
main后
首页初始化所需要配置文件的读写操作
首页界面渲染
对于基本数据类型,一般是存储到栈中的,它有没有可能存在堆上,什么情况下会存储到堆上?
栈和堆都是同属一块内存,只不过一个是高地址往低地址存储,一个从低地址往高地址存储,他们并没有严格的界限说一个值只能放在堆上或者栈上。所以基本数据类型也是可以存储到堆上的。
当该基础类型变量被__block捕获时,该变量连同block都会被copy到堆上。
数据库中的事务是什么意思?
事务就是访问并操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行。如果其中一个步骤出错就要撤销整个操作,回滚到进入事务之前的状态。
LRU算法
LRU(Least recently used 最近最少使用)算法是一个缓存淘汰算法,其作用就是当缓存很多时,该淘汰哪些内容,见名知意,它的核心思想是淘汰最近使用最少的内容。实现它的关键步骤是:
新数据插入到链表的头部
每当缓存命中时,则将数据移动到链表头部
链表满时,将尾部数据清除
这个算法在SDWebImage和Kingfisher等需要处理缓存的库中都有实现。
设计模式
工厂模式、观察者模式、中介者模式、单例模式。
如果有1000万个Int类型的数字,如何对他们排序?
这里的隐藏含义是,内存不够用时如何排序,还有一个隐藏含义是硬盘足够大。这是可以采用分而治之的方法,将数据分成若干块,使每一小块满足当前内容大小,然后对每块内容单独排序,最后采用归并排序对所有块进行排序,就得到了一个有序序列。
设计一套数据库方案,实现类似微信的搜索关键词能快速检索出包含该字符串的聊天信息,并展示对应数量(聊天记录的数据量较大)
可以对聊天记录的文本值加上索引。正常情况下数据库搜索都是全量检索的,加上索引之后只会检索满足条件的记录,大大降低检索量。
Lottie实现动画效果的原理
iOS里的动画基本都是基于CoreAnimation里的API实现的,Lottie也是如此。在AE上实现动画效果,通过插件导出对应的json文件,Lottie的库解析该json,转成对应的系统API方法。图片的引用可以使用Base64编到json里,也可以通过项目集成,通过路径引用。
静态库和动态库的区别
静态库:链接时被完整复制到可执行文件中,多次使用就多份拷贝。
动态库:链接时不复制,而是由系统动态加载到内存,内存中只会有一份该动态库。
二进制重排的核心依据
修改链接顺序,减少启动时的缺页中断。
网友评论