1、简述__kindof关键字
如果想保证数组中只能存在某一类元素,这就需要添加泛型,比如
@property (nonatomic, strong) NSArray
但是呢,这样声明的数组它只能包含UIView类型的元素,如果元素被赋值为UIWebView或UIButton这样的子类型,编译器就会报警告⚠️。
为了解决这个问题,__kindof就应运而生。
@property (nonatomic, strong) NSArray<__kindof UIView *> *viewCollection;
用这种结构声明,这个数组就可以包含UIView以及UIView的子类型,例如UIWebView或UIButton。这样会使代码更严谨
2、什么是内连函数?内连函数与普通函数区别?
内联函数是指用 inline 关键字修饰的函数。在类内定义的函数被默认成内联函数。内联函数从源代码
层看,有函数的结构
,而在编译后
,却不具备函数的性质
。内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处。编译时类似于宏替换
,使用函数体替换调用处的函数名
。一般在代码中用 inline 修饰,但是能否形成内联函数,需要看编译器对该函数定义的具体处理。
inline内联函数的优点
- 内连函数是内存地址的调用,取消了普通函数call的操作,不需要将函数体做压栈操作,减少了调用的开销,所以要比一般函数执行的快。
- 可以像宏定义一样直接替换,但是不需要宏定义一样预编译
- 编译器在调用内联函数时会像普通函数一样,检查函数的参数类型,保证调用正确
注意事项 - 内连函数是我们向编译器提供的申请,但是编译器不一定会调用
- 内连函数体不应该过大,且最好不要有循环,否则函数体过大,编译器会放弃内连函数
- 内连函数的定义需要在调用之前
3、简述Swift中,Struct与Class的区别。
Class是引用类型 Struct是值类型
Class允许被继承 Struct不允许被继承
Class中的每一个成员变量都必须初始化,否则编译器会报错
Struct不需要初始化成员变量,编译器会自动生成init函数,给变量赋一个默认值
4、简述App main()函数执行前启动流程?
app在启动后,系统首先加载App本身的.o文件的几个(可执行文件)
然后加载动态链接库(dyld)
main()调用之前的加载过程
App开始启动后,系统首先加载可执行文件(自身App的所有.o文件的集合),然后加载动态链接库dyld。
dyld是一个专门用来加载动态链接库的库。
dyld源码链接
执行从dyld开始,dyld从可执行文件的依赖开始, 递归加载所有的依赖动态链接库。
动态链接库包括:
iOS 中用到的所有系统 framework
加载OC runtime方法的libobjc,
系统级别的libSystem,例如libdispatch(GCD)和libsystem_blocks (Block)。
其实无论对于系统的动态链接库还是对于App本身的可执行文件而言,他们都算是image(镜像),而每个App都是以image(镜像)为单位进行加载的。
生命周期
load-->viewdidload-->viewwillappear-->viewwilllayout-->viewdidlayout-->viewdidappear-->viewwilldisappear-->viewdiddisapperar-->dealloc
事件的响应链
uiview-->UIResponer-->UIwindow-->UIApplication
组件化开发:
方案(本地化组件、上传到git,用cocoapod管理)
通信方式
URL路由:组件自己想module manager注册URL,然后组件间通过URL通信,参数会拼接到URL后面,
targ-action,
protocol
继承和重写的区别
继承就是子类直接使用父类的方法,而重写,就是在子类实现一个与父类方法名和参数都相同的方法
继承
:当多个类具有相同的特征(属性)和行为(方法),为了实现代码复用,需要子类继承父类的特征和行为。使得子类具有父类的各种属性和方法,除了具有父类的特征和行为,还具有一些自己特殊的特征和行为。
多态
:当一个类需要表现出多种形态,具有多种实现方式。避免了在父类里大量重载引起代码臃肿且难于维护,增强程序的可扩展性及可维护性,使代码更加简洁。子类重写父类的方法。使子类具有不同的方法实现。
可以简单理解为:继承是子类使用父类的方法,而多态则是父类使用子类的方法。
重写
:如果在子类中定义的一个方法,其名称、返回类型及参数列表正好与父类中某个方法的名称、返回类型及参数列表相匹配,那么可以说,子类的方法重写了父类的方法。
重载
:多个同名而不同参数的方法之间,互相称之为重载方法。
接口
:接口把方法的特征和实现分割开来。它本身没有任何实现,不涉及表象,只描述public行为,所以接口比抽象类更抽象化。但是接口不是类,所以不能被实例化。
通知能被多次移除吗
循环饮用的解决办法
如何确定双向链表是不是环
block里面有什么,block和delegate的区别
1.从源头上理解和区别block和delegate
delegate运行成本低,block的运行成本高。
block出栈需要将使用的数据从栈内存拷贝到堆内存,当然对象的话就是加计数,使用完或者block置nil后才消除。delegate只是保存了一个对象指针,直接回调,没有额外消耗。就像C的函数指针,只多做了一个查表动作。
2.从使用场景区别block和delegate
有多个相关方法。假如每个方法都设置一个 block, 这样会更麻烦。而 delegate 让多个方法分成一组,只需要设置一次,就可以多次回调。当多于 3 个方法时就应该优先采用 delegate。当1,2个回调时,则使用block。
delegate更安全些,比如: 避免循环引用。使用 block 时稍微不注意就形成循环引用,导致对象释放不了。这种循环引用,一旦出现就比较难检查出来。而 delegate 的方法是分离开的,并不会引用上下文,因此会更安全些。
筒子算法
8、给定5亿个整数,输出所有出现过的整数,重复整数只输出一次。注:整数为32位整数。最大可使用内存为1G,不足以容纳这5亿个整数。(写出大体思路即可)
线程锁
自旋锁 atomic
互斥锁 条件锁、信号量
读写锁
多线程操作可变数组(删除和加载更多同时进行)
删除是在主线程进行,加载更多在子线程
方案一:放在串行队列,依此进行
方案二:将删除的数据copy到子线程,待加载更多之后,将加载后到数据去掉删除的数据,然后回到主线程刷新UI
copy会浪费内存资源
为什么刷新UI必须在主线程中完成
1、在子线程中刷新UI会存在线程安全性问题,UIviewkit不是线程安全,异步操作会存在读写问题
2、UIApplication是在主线程中初始化,view的回应也只能在主线程中刷新
栈实现队列的效果
创建两个栈,入栈和出栈,任务直接压栈道入栈里面,当需要pop的时候,先判断出栈里面是否有,如果没有则把入栈里的内容执行pop操作然后push进出栈里面,然后出栈执行pop操作
7、多线程同步,一个线程加锁一个互斥量,解锁前,第二线程试图锁定此互斥量,简述此时互斥锁和自旋锁处理流程?
互斥锁:第二个线程会休眠,直至互斥量被解锁
自旋锁:第二个线程会一直循环,知道互斥量被解锁
正背面剔除
根据顶点数据排序,实现观察者正面的渲染,忽略掉观察者背面
cell复用机制
当前页面能显示几个cell,则会初始化几个cell,当上滑或者下滑时,则是在复用池里根据identify去查找,如果查不到,继续初始化
6、简述SOLID原则
单一原则
接口分离原则
里氏替换原则
开放封闭原则
依赖倒置原则:
解决线程执行顺序的方法
dispatch_group,栅栏函数,nsoperation 添加依赖
7、指针和饮用的区别
指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;
引用:跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。int &b=a;b是a的引用
区别:
引用不可以为空,当被创建的时候,必须初始化,而指针可以是空值,可以在任何时候被初始化。