iOS 面试题-2019.下
- 简要说一下
autoreleasePool的数据结构
简单说是
双向链表,每张链表头尾相接,有parent、child指针,每创建一个池子,会在首部创建一个哨兵对象作为标记,最外层池子的顶端会有一个next指针。当链表容量满了,就会在链表的顶端,并指向下一张表
- 说一下
autoreleasePool的实现原理
autoreleasePool是一个延时release的机制,在自动释放池被销毁或耗尽时,会向池中的所有对象发送release消息,释放所有autorelease对象
autoreleasePool并没有单独的结构,而是由若干个autoreleasePoolPage作为结点以双向链表的形式组合而成
- 每一个指针代表一个加入到释放池的
对象或者是哨兵对象,哨兵对象是在@autoreleasepool{}构建的时候插入的- 当自动释放池
pop的时候,所有哨兵对象之后的对象都会release- 链表会在一个
Page空间占满时进行增加,一个autoreleasePoolPage的空间被占满时,会新建一个autoreleasePoolPage对象连接链表,后来的autorelease对象在新的page加入
- 解释一下三次握手和四次挥手
- 三次握手
- 由客户端向服务端发送
SYN同步报文- 当服务端收到
SYN同步报文之后,会返回给客户端SYN同步报文和ACK确认报文- 客户端会向服务端发送
ACK确认报文,此时客户端和服务端的连接正式建立
- 四次挥手
- 先由客户端向服务端发送
FIN结束报文- 服务端会返回给客户端
ACK确认报文。此时,由客户端发起的断开连接已经完成- 服务端会发送给客户端
FIN结束报文和ACK确认报文- 客户端会返回
ACK确认报文到服务端,至此,由服务端方向的断开连接已经完成
拓展:
SYN攻击
在三次握手过程中,Server发送SYN-ACK之后,收到Client的ACK之前的TCP连接称为半连接half-open connect,此时Server处于SYN_RCVD状态,当收到ACK后,Server转入ESTABLISHED状态。SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server回复确认包,并等待Client的确认,由于源地址是不存在的,因此,Server需要不断重发直至超时,这些伪造的SYN包占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络堵塞甚至系统瘫痪。SYN攻击时一种典型的DDOS攻击
一般有两种方式
1、客户端恶意不发送ACK
2、在发送给服务器的SYN报文段中提供虚假的IP地址,造成服务器永远收不到ACK
- 一个
NSObject对象占用多少内存
一个指针变量所占用的大小,
64bit占8个字节,32bit占4个字节
- 对象的
isa指针指向哪里
instance对象的isa指针指向class对象,class对象的isa指针指向meta-class对象,meta-class对象的isa指针指向基类的meta-class对象,基类自己的isa指针也指向自己
-
OC的类信息存放在哪里
成员变量的具体值存放在
instance对象,对象方法、协议、属性、成员变量信息存放在class对象,类方法信息存放在meta-class对象
- 为什么使用
runtime技术中的关联对象可以为类别添加属性
关联对象都由
AssociationsManager管理,AssociationsManager里面是由一个静态AssociationsHashMap来存储所有的关联对象的。这相当于把所有对象的关联对象都存在一个全局map里面。而map的key是这个对象的指针地址,而这个map的value又是另外一个AssociationsHashMap,里面保存了关联对象的kv对
- 用过哪些锁?哪些锁的性能比较高?谈下
Objective-C都有哪些锁机制,你一般用哪个
常用的锁有
NSLock、@synchronized代码块、信号量 dispatch_semaphore_t
os_unfair_lock(推荐)
dispatch_semaphore(推荐)
pthread_mutex(推荐)
dispatch_queue(DISPATCH_QUEUE_SERIAL)(推荐)
NSLock()
NSCondition()
@synchronized(最不推荐)
信号量性能最高
@synchronized代码块最方便
- 为什么一定要在主线程里面更新
UI
UIKit不是线程安全的,容易产生UI更新上的混乱
- 类和结构体的区别
类是引用类型,结构体是值类型。结构体变量分配在栈,
OC对象分配在堆。结构体只能封装属性,类不仅可以封装属性也可以封装方法
- 引用和指针的区别
指针的本质也就是变量,它不仅有自己的地址,也有它所存放的值,只不过这个值是地址而已
引用也就是指针常量,它是一个对象的别名,既然初始化了所指向的地址,那么它一定不为空,而且地址不可变
引用必须被初始化,指针不必
引用初始化以后不能被改变,指针可以改变所指的对象
不存在指向空值的引用,但存在指向空值的指针
-
id类型、nil、Nil、NULL和NSNULL的区别
id类型是一个独特的数据类型,可以转换为任何数据类型,id类型的变量可以存放任何数据类型的对象,id声明的对象具有运行时特性,可以指向任意类型的对象
nil是一个实例对象值,如果我们要把一个对象设置为空的时候就用nil
Nil是一个类对象的值,如果我们要把一个class的对象设置为空的时候就用Nil
NULL指向基本数据类型的空指针C语言的变量的指针为空
NSNull是一个对象,它用在不能使用nil的场合
-
weak的底层实现的原理是什么
weak表其实是一个hash哈希表,Key是所指对象的地址,Value是weak指针的地址数组
runtime维护了一个weak表,用于存储指向某个对象的所有weak指针,weak表其实是一个hash表,Key是所指对象的地址,value是weak指针的地址数组
为什么value是数组?因为一个对象可能被多个弱引用指针指向
-
weak原理实现步骤
weak的实现原理可概括三步
- 初始化时:
runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址- 添加引用时:
objc_initWeak函数会调用objc_storeWeak()函数,objc_storeWeak()的作用是更新指针指向,创建对应的弱引用表- 释放时,调用
clearDeallocating函数,clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录
- 编译过程做了哪些事情
Objective、Swift都是编译语言。编译语言在执行的时候,必须先通过编译器生成机器码,机器码可以直接在CPU上执行,所以执行效率较高
Objective、Swift二者的编译都是依赖于Clang + LLVM
不管是OC还是Swift,都是采用Clang作为编译器前端,LLVM(Low level vritual machine)作为编译器后端
- 编译器前端:编译器前端的任务是进行语法、语义分析,生成中间代码。在这个过程中会进行类型检查,如果发现错误或者警告会标注出来在哪一行
- 编译器后端:编译器后端会进行机器无关的代码优化,生成机器语言,并且进行机器相关的代码优化。
LLVM优化器会进行BitCode的生成,链接器优化等等,LLVM机器码生成器会针对不同的架构,比如arm64等生成不同的机器码
-
Linux命令之-Strings
strings命令是在对象文件或者二进制文件中查找可打印的字符串,常用来在二进制文件中查找字符串,与grep配合使用,例如一个用法就是在编译的so中定义字符串常量作为动态库的版本号,然后就可以使用strings+grep组合命令查看当前编译的so的版本号了。输入strings -h查看strings命令的用法,下面为具体用法实例
strings /lib/tls/libc.so.6 | grep GLIBC
strings /Users/y**ar/Desktop/Demo2/Libraries/libiPhone-lib.a | grep "UIWebView"
cat /Users/y**ar/Desktop/Demo2/Libraries/libiPhone-lib.a
-
OC格式化打印
%d整数
%02d表示不足2位补0
%u无符号整型
%f浮点(双字节)
%.2f精度浮点数,只保留两位小数
%x,%X十六进制整数
%o八进制整数
%p指针
%sC字符串
%c字符