iOS面试题学习
2018-10-27 » iOS关键词复习法如果你不知道你接下来需要补充学习哪一部分知识,做点面试题吧。
常见算法套路
1、数组
- 双指针(左右指针、快慢指针)
- 二分查找类型的题
- 排序数组查找固定值、固定值的最开始最后出现位置,旋转排序数组查找固定值、最小值
- 二分查找关键是开闭区间,退出循环闭区间,right的赋值决定,如果是count - 1 就是闭区间,如果是count 就应该是开区间
- int mid = left + (right - left) / 2
- 寻找左侧边界的二分查找:
- 寻找右侧边界的二分查找:最后收紧的是左侧边界 while (left < right) left = mid + 1,return left - 1
- 二维数组的二分查找:二维拉成一维处理,left、right按n*n折算xy的位置
```
var m = matrix.count, n = matrix[0].count
var left = 0, right = m * n - 1
while left <= right {
let mid = left + (right - left) / 2
let mid_value = matrix[mid/n][mid%n]
let left_value = matrix[left/n][left%n]
let right_value = matrix[right/n][right%n]
if mid_value > target {
right = mid-1
} else if mid_value < target {
left = mid+1
} else {
return true
}
}
```
- 回文串判定、最长回文子串
- 反转数组
- 有序数组两数之和
- 二分查找类型的题
- 滑动窗口,窗口的移动、收缩
- 子串问题(无重复的最长子串、字符串所有的字母异位词、最小覆盖子串)
- 前缀和
- 求和为N的组合数
- 二维数组求指定区间和(预处理每一个位置到0.0的总和)
-
差分数组(新增一个数组存放 n[i] - n[i-1])
- 花式遍历
- 旋转矩阵 (对角线翻转整个矩阵,然后再倒序行,旋转二维矩阵的难点在于将「行」变成「列」,将「列」变成「行」,而只有按照对角线的对称操作是可以轻松完成这一点的,对称操作之后就很容易发现规律了)
- 矩阵对角线 (对角线位置 x+y始终相等)
- 矩阵旋转遍历、构建n*n旋转矩阵 (上下左右边界问题处理清楚)
- 排序
- 冒泡
- 外层是轮次,内层是两两比较,最大的会冒到最后,然后进行下一轮
for(int i = 0;i<len-1;i++){ //需要len-1轮 for(int j = 0;j<len-i-1;j++){ //每次只用考察unsorted部分即可 if(arr[j+1]<arr[j]){ //逆序 swap(arr[j+1],arr[j]);//调整逆序对
- 外层是轮次,内层是两两比较,最大的会冒到最后,然后进行下一轮
- 插入
- 外层是轮次 i,内层每轮次(j = i-1)和前面已经排好的部分一一比较(右边往左边比较),比较过程中j 赋值给 j+1 (整体就是i取出,j区间的大于i的往后挪一位)
for(int i = 1;i<len;i++){ //从第二个元素开始作为key int key = arr[i]; int j = i-1; //从右向左扫描 while(key<arr[j]){ //如果一直都比key大 arr[j+1] = arr[j]; j--; } //直到找到<key的值,其后面必是空位 arr[j+1] = key;
- 外层是轮次 i,内层每轮次(j = i-1)和前面已经排好的部分一一比较(右边往左边比较),比较过程中j 赋值给 j+1 (整体就是i取出,j区间的大于i的往后挪一位)
- 选择
- 两层循环 i的每一个值 和后面的值一一对比swap
for(int i=0;i<a.length;i++) { //内层循环j=i+1,外层循环控制着循环次数。即每趟中a[i]这个值就是本趟的最小值。i位置上是最小值 for(int j=i+1;j<a.length;j++) { if(a[i]>a[j]) { temp=a[i]; a[i]=a[j]; a[j]=temp; }
- 两层循环 i的每一个值 和后面的值一一对比swap
- 快速
- 寻找中心轴、一般从左边第一个取值做中心轴,左右游标分别对比中心轴,右游标开始,找到小于中心的往左放 ,然后左游标找到大于中心就往右边放 ``` if(low >= high) return; //单元素向量必定有序 int mid = find_partition(arr, low,high); //构造轴点,此时左边都<轴点,右边都>轴点 quick_sort(arr, low,mid - 1); //排序左边 quick_sort(arr, mid+1,high); //排序右边
中心轴寻找过程,选了左边做中心轴就要右边开始找小于中心轴的值。找到left >= right重合了,就把中心轴的值复位,接着就沿着左右继续排序操作 // 外循环在左右指针重合或者交错的时候结束 while (right > left) { // right指针从右向左进行比较 while (right > left) { if (arr[right] < pivot) { arr[left] = arr[right]; index = right; left++; break; } right–; } // left指针从左向右进行比较 while (right > left) { if (arr[left] > pivot) { arr[right] = arr[left]; index = left; right–; break; } left++; } } arr[index] = pivot; ```
- 拼凑最大数(通过字符串化数字来比较大小 a=43 b = 425 a+b > b+a “43425” > “42543”)
- 摆动排序,开个新空间排好顺序 然后奇数偶数分别赋值前后半部分
- 归并排序
- 冒泡
-
单调队列
- 动态规划
-
是否能分解成递推的子问题,比如最长子序列、最大整数子集这种,dp[i] = max(dp[i], dp[i - 1] + 1), 如果要得具体结果,要根据dp数组重新遍历原数组倒推
-
贪心算法
-
打家劫舍
-
########### 数组
########### 环形
区分从 0 开始 到 n - 1 和 从 1 开始 到 n
########### 矩阵 for i in 0..<grid.count { for j in 0..<grid[0].count { if i > 0 && j > 0 { dp[i][j] = min(dp[i-1][j],dp[i][j-1]) + grid[i][j] } else if i > 0 { dp[i][j] = dp[i-1][j] + grid[i][j] } else if j > 0{ dp[i][j] = dp[i][j-1] + grid[i][j] } } }
########### 树形 func rob(_ root: TreeNode?) -> Int {
//偷、不偷
let res = robDeep(root)
return max(res.0, res.1) }
func robDeep(_ root: TreeNode?) -> (robRoot:Int,robNode:Int) { if root == nil { return (0,0) } var left = robDeep(root!.left) var right = robDeep(root!.right)
return (left.1 + right.1 + root!.val, max(left.0, left.1) + max(right.0, right.1)) }
- 递归回溯、剪枝(递归能不能解决,不能试试回溯,回溯多了,试试剪枝(加访问控制过滤、判断重复使用跳过))
func permuteUnique(_ nums: [Int]) -> [[Int]] { var result = [[Int]]() var visited = [Int:Bool]() func permuteDeep(_ tmpArray: inout [Int], _ startIndex:Int) { if tmpArray.count == nums.count { result.append(tmpArray) } for i in 0..<nums.count { if visited[i] == true { continue } if i > 0, nums[i] == nums[i - 1], visited[i-1] == false { continue } visited[i] = true tmpArray.append(nums[i]) permuteDeep(&tmpArray, i + 1) tmpArray.popLast() visited[i] = false } } var tmp = [Int]() permuteDeep(&tmp, 0) return result }
2、链表
链表注意点:
1、节点都是指针指向,返回完整指针的题都要有dumpNode 2、交换场景 tmp临时指针的使用
- 双指针、快慢指针
- 分割链表
- 合并有序链表
- 找环、找环入口
- 查找、删除倒数第N个节点
- 链表的中间点
- 两个链表是否相交,和找环类似
- 回文链表
- 递归
- 反转链表 (从最后一个节点往前看,递归思想)
递归思想 let last = reverseDeep(head?.next) head?.next?.next = head head?.next = nil 遍历 + tmp while cur != nil { tmp = cur?.next cur?.next = pre pre = cur cur = tmp }
- 反转链表 (从最后一个节点往前看,递归思想)
3、二叉树
- 遍历
- 前序 中左右 递归 ``` add root deep left deep right
- 后序 左右中 递归
deep left deep right add root
- 中序 左中右 递归
deep left add root deep right
- 层序 入栈出栈
```
二维数组
1、最大矩形 、正方形
2、被围绕的区域
3、
底层知识
2、Tagged Pointer
NSString 相关类说明表格
类名 | 存储区域 | 初始化的引用计数(retainCount) | 作用描述 |
---|---|---|---|
NSString | 堆区 | 1 | 开发者常用的不可变字符串类,编译期间会转换到其他类型 |
NSMutableString | 堆区 | 1 | 开发者常用的可变字符串类,编译期间会转换到其他类型 |
__NSCFString | 堆区 | 1 | 可变字符串 NSMutableString 类,编译期间会转换到该类型 |
__NSCFConstantString | 堆区 | 2^64-1 | 不可变字符串 NSString 类,编译期间会转换到该类型 |
NSTaggedPointerString | 栈区 | 2^64-1 | Tagged Pointer对象,并不是真的对象 |
https://www.jianshu.com/p/dcbf48a733f9
3、isa是什么
isa
成员变量在64位
CPU架构下是8字节,且排在objc_class
结构体的前8字节。
isa_t
- has_sidetable_rc 是否用sidetable存放引用计数
- bits 存储对象使用过程中的其他信息
- Class cls 指向类对象
- extra_rc 不用sidetable的时候存放引用计数的地方(10以内)
对象的isa指针 指向 对象的所属类(如person对象的isa指向Person类)
类的isa指针 指向 类的元类(如Person类的isa指向Person元类)
元类的isa指针 指向 根元类(如Person元类的isa指向NSObject元类)
根元类的isa指针 指向自身(是个圆圈)
元类的继承关系向上传递(如Teacher元类 继承自 Person元类)
4、方法缓存原理
1、cache_t 能缓存调用过的方法
2、cache_t 的三个成员变量
_buckets : struct bucket_t * , 也就是指针数组,表示一系列的哈希桶(已调用的方法的 SEL 和 IMP 就缓存在这),一个桶可以存一个方法。
_mask : 侧面反映哈希桶的总数
_occupied : 代表当前已经缓存的方法数
3、当缓存的方法达到临界点(桶总数的3/4)时,下次再缓存新的方法时,首先会丢弃旧的桶,同时开辟新的内存,也就是扩容(扩容后就全是新桶,每个方法都需要重新缓存),_occupied此时为1。
4、当多个线程同时调用一个方法时
多线程读缓存 : 读缓存由汇编实现,无锁且高效,优于并没有改变 _buckets 和 _mask, 所以并无安全隐患
多线程写缓存 : OC 用一个全局的互斥锁来保证不会出现写两次缓存的情况
多线程读写缓存 : OC使用了ldp汇编指令、编译内存屏障技术、内存垃圾回收技术等多种手段来解决多线程读写的无锁处理方案,既保证了安全,又提升了系统的性能。
5、了解一个OC对象
一个NSObject对象占用多少内存
new一个NSObject对象解析出来其实就是一个结构体,里面有个isa指针
struct NSObject_IMP {
Class isa;
};
isa 指针在64位架构占用8个字节
扩展:
一个指针占用多少个字节:64位计算器 8个字节,32位计算器 4个字节,地址就是指针,指针指向地址,而地址是内存单元的编号,一个指针占用几个字节,等于是一个地址的内存单元编号有多长。
那一个对象占的就是 8 字节
7、KVO本质
person在呗添加KVO监听之后发生了什么:
person -> NSKVONotifying_Person
isa指针会指向 person 子类 NSKVONotifying_Person
NSKVONotifying_Person 里的get、set方法进行了重写,class方法也重写了
KVO的本质是什么:
当一个对象使用了KVO监听,iOS系统会修改这个对象的isa指针,改为指向一个全新的通过Runtime动态创建的子类,子类拥有自己的set方法实现,set方法实现内部会顺序调用willChangeValueForKey方法、原来的setter方法实现、didChangeValueForKey方法,而didChangeValueForKey方法内部又会调用监听器的observeValueForKeyPath:ofObject:change:context:监听方法。
如何手动触发KVO
答. 被监听的属性的值被修改时,就会自动触发KVO。如果想要手动触发KVO,则需要我们自己调用willChangeValueForKey和didChangeValueForKey方法即可在不改变属性值的情况下手动触发KVO,并且这两个方法缺一不可。
8、iOS常见崩溃
信号可捕捉崩溃
数组越界、字典存nil
多线程操作对象重复retain等
野指针
通知在错误线程发生接收
KVO错误移除监听者等
找不到方法
操作字符串
NSTimer crash
对象的释放和操作不在一个线程,导致释放的时候执行操作
信号不可捕捉崩溃
后台超时
内存OOM
多线程死锁超时崩溃
主线程卡顿
崩溃排查入门
Appdelegate中注册SIGABRT, SIGBUS, SIGSEGV等信号发生时的处理函数,处理Signal层面的crash
SIGKILL:用来立即结束程序的运行的信号。
SIGSEGV:试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据。
SIGABRT:调用abort函数生成的信号。
SIGTRAP:由断点指令或其它trap指令产生。
SIGBUS:非法地址, 包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数, 但其地址不是4的倍数。它与SIGSEGV的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或只读存储空间)。
针对野指针的处理机制
模仿Xcode的zombie机制:
1.Swizzle原有allocWithZone方法,添加野指针防护标记。
2.Swizzle原有dealloc方法,如果有野指针防护标记,调用 objc_destructInstance方法,修改实例isa使其指向zombieObject,保存原始 类名,以便上报使用。
3.Swizzle消息转发机制forwardingTargetForSelector方法,处理所 有原始类originObject的方法,收集错误信息并上报。
4.及时释放zombieObject。
13、几个线程问题
1、NSLog([NSThread currentThread]) 直接调用会崩溃,打印线程打印他的描述就行 直接打印线程会崩溃,错误:**XPC connection interrupted**
2、串行队列 不管是async、sync 你要执行 及时是async中等待 也得等着走完,因为就一个队列在进行
3、并行队列,如果async sleep等待,其他的可以同步进行 不需要等他,但是如果是sync等待在队列前面, 那还是乖乖等着。毕竟队列同步的话顺序执行,即使是同步并行,只要是sync 同步 ,就在一个线程里跑 就是串行执行的,如果是异步就会开启线程可以利用并行队列并行执行。
4、iOS 几种锁的性能问题
https://www.jianshu.com/p/b1edc6b0937a
@synchronized 性能最差
自旋锁最好,dispatch_semaphore信号量 较好,然后是 NSLock 性能较好
os_unfair_lock 互斥锁 替换自旋锁
苹果通过这个处理了优先级反转的问题
**临界区:**指的是一块对公共资源进行访问的代码,并非一种机制或是算法。
**自旋锁:**是用于多线程同步的一种锁,线程反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种`忙等待`。一旦获取了自旋锁,线程会一直保持该锁,直至显式释放自旋锁。 自旋锁避免了进程上下文的调度开销,因此对于线程只会阻塞很短时间的场合是有效的。
**互斥锁(Mutex):**是一种用于多线程编程中,防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。该目的通过将代码切片成一个一个的临界区而达成。
**读写锁:**是计算机程序的并发控制的一种同步机制,也称“共享-互斥锁”、多读者-单写者锁) 用于解决多线程对公共资源读写问题。读操作可并发重入,写操作是互斥的。 读写锁通常用互斥锁、条件变量、信号量实现。
**信号量(semaphore):**是一种更高级的同步机制,`互斥锁`可以说是semaphore在仅取值0/1时的特例。`信号量`可以有更多的取值空间,用来实现更加复杂的同步,而不单单是线程间互斥。
**条件锁:**就是条件变量,当进程的某些资源要求不满足时就进入休眠,也就是锁住了。当资源被分配到了,条件锁打开,进程继续运行。
14、iOS通过MachPort向特定线程发送通知
mach kernel属于苹果内核,RunLoop依靠它实现了休眠和唤醒而避免了CPU的空转: mach_port
Runloop是基于pthread进行管理的,pthread是基于c的跨平台多线程操作底层API。它是mach thread的上层封装
14、事件响应
苹果注册了一个 Source1 (基于 mach port 的) 用来接收系统事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()。
当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收。SpringBoard 只接收按键(锁屏/静音等),触摸,加速,接近传感器等几种 Event,随后用 mach port 转发给需要的App进程。
触摸事件其实是Source1接收系统事件后在回调 __IOHIDEventSystemClientQueueCallback()内触发的 Source0,Source0 再触发的 _UIApplicationHandleEventQueue()。source0一定是要唤醒runloop及时响应并执行的,如果runloop此时在休眠等待系统的 mach_msg事件,那么就会通过source1来唤醒runloop执行。
_UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。
15、通知
1、通知是分线程的,如果在子线程发出的通知,接受者接到之后的方法执行会在相应的线程完成
2、同时注册多个监听者,要注意顺序,可以同时给一个对象加多个接受者
3、要监听所有通知,那么 name设置为nil , 包括系统通知在内都会监听
4、iOS 9 以后不移除对应的监听者也没事
16、app如何接收到触摸事件的
1. 首先,手机中处理`触摸事件`的是硬件系统进程 ,当硬件系统进程识别到触摸事件后,会将这个事件`进行封装`,并通过`machPort`,将封装的事件发送给当前活跃的APP进程。
2. 由于APP的主线程中runloop注册了这个machPort端口,就是用于接收处理这个事件的,所以这里APP收到这个消息后,开始寻找`响应链`。
3. 寻找到响应链后,开始`分发事件`,它会优先发送给`手势集合`,来过滤这个事件,一旦手势集合中其中一个手势识别了这个事件,那么这个事件将不会发送给响应链对象。
4. 手势没有识别到这个事件,事件将会发送给响应链对象`UIResponser`。
17、利用 runloop
解释一下页面的渲染的过程?
1、当我们调用 [UIView setNeedsDisplay]
时,这时会调用当前 View.layer
的 [view.layer setNeedsDisplay]
方法。
这等于给当前的 layer
打上了一个脏标记,而此时并没有直接进行绘制工作。而是会到当前的 Runloop
即将休眠,也就是 beforeWaiting
时才会进行绘制工作。
紧接着会调用 [CALayer display]
,进入到真正绘制的工作。CALayer
层会判断自己的 delegate
有没有实现异步绘制的代理方法 displayer:
,这个代理方法是异步绘制的入口,如果没有实现这个方法,那么会继续进行系统绘制的流程,然后绘制结束。
CALayer
内部会创建一个 Backing Store
,用来获取图形上下文。接下来会判断这个 layer
是否有 delegate。
如果有的话,会调用 [layer.delegate drawLayer:inContext:]
,并且会返回给我们 [UIView DrawRect:]
的回调,让我们在系统绘制的基础之上再做一些事情。
如果没有 delegate
,那么会调用 [CALayer drawInContext:]
。
以上两个分支,最终 CALayer
都会将位图提交到 Backing Store
,最后提交给 GPU
。
至此绘制的过程结束。
2、UI在什么时候渲染?
因为UI不是立即渲染的,CoreAnimation在runloop中注册了一个即将进入休眠的observer,在休眠之前对已提交的请求进行集中渲染。
3、卡顿问题,一次runloop绘制任务太多仍会存在卡顿问题,放到下个Runloop,以减少单次runloop绘制任务。(其实重用UI、子线程处理耗时的非UI操作基本上能处理大多数常见卡顿)。runloop中将一个任务放到第二次runloop中执行。最简单使用[self performSelector:@selector(xxxxx) withObject:nil afterDelay:0],
而GCD的话,是不一定的。
4、Runloop支持线程唤醒的事件类型
- 基于端口的事件 如触摸事件,系统将触摸事件封装,并且通过进程之间端口通讯传递到我们的进程。
[[NSRunLoop currentRunLoop] addPort:port forMode:NSRunLoopCommonModes];
- 自定义事件
[self performSelector:@selector(taskDo) onThread:thread withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
- 基于时间的定时事件
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
18、几个数据结构问题
1、NSArray与NSSet的区别?
- NSArray内存中存储地址连续,而NSSet不连续
- NSSet效率高,内部使用hash查找;NSArray查找需要遍历
- NSSet通过anyObject访问元素,NSArray通过下标访问
2、NSHashTable与NSMapTable?
- NSHashTable是NSSet的通用版本,对元素弱引用,可变类型;可以在访问成员时copy
- NSMapTable是NSDictionary的通用版本,对元素弱引用,可变类型;可以在访问成员时copy
(注:NSHashTable与NSSet的区别:NSHashTable可以通过option设置元素弱引用/copyin,只有可变类型。但是添加对象的时候NSHashTable耗费时间是NSSet的两倍。 NSMapTable与NSDictionary的区别:同上)
基础部分
1、线程和进程有什么区别
进程是一个程序执行的实例,是资源分配的最小单位
线程是进程中的一个实例,是操作系统可以识别的最小执行和调度单位
那么,线程和进程与堆、栈之间的关系?
栈是线程独有的,保存其运行状态和局部自动变量,栈空间是线程安全的,栈被自动分配到进程的内存空间,栈内存无需开发管理,系统自动管理
堆在操作系统初始化进程的时候分配,运行过程可以要求更多额外的堆内存,但是需要返回,不然呢就是内存泄露
2、说一下线程之间的通信
例如在多线程并发条件下,为了让线程之间可以更方便的共同完成一个任务,需要一些协调通信,采取的通信方式就是 等待、唤起。
也就是 wait() 和 notify()、 notifyAll()
运行 就绪 阻塞 创建 终止
3、当用一个不存在的key来查找两个不同长度的字典,那么哪个效率会高?
表面上看可能是一样快,因为字典底层都用了哈希表,查找的时间复杂度为 O(1),(最差的时候是O(n))都是一样的,但是可能会由于两个哈希表的负载因子不同,倒是查找的时间也是不同的。
4、什么是指针常量和常量指针
指针常量是 常量,指针修饰它,这个常量的值是一个指针 int a; int *const b = &a;
常量指针本质是指针,常量修饰它 const int *p;
5、不借用第三个变量,如何交换两个变量的值?
算术运算
int a,b;
a=10;b=12;
a=b-a; //a=2;b=12
b=b-a; //a=2;b=10
a=b+a; //a=12;b=10
位运算 异或
int a=10,b=12; //a=1010^b=1100;
a=a^b; //a=0110^b=1100;
b=a^b; //a=0110^b=1010;
a=a^b; //a=1100=12;b=1010;
栈实现
int exchange(int x,int y)
{
stack S;
push(S,x);
push(S,y);
x=pop(S);
y=pop(S);
}
8、http 的 POST 和 GET 啥区别?
最直观的区别就是GET是获取数据、POST是提交数据
GET请求的数据会附在URL之后
POST把提交的数据则放置在是HTTP包的包体中
GET请求URL受浏览器影响 所以有长度限制
POST没有,一般服务器会做POST数据长度的限制
POST的数据传输不是直接拼接URL 所以相对安全一些
9、http和https的区别,说一下http和https的请求过程?
http + ssl/tls = https
主要介绍一下,ssl的验证过程 保证安全和数据完整性
10、如何用HTTP实现长连接?
web端:
Connection:keep-alive
服务器在闲置时候会向客户端发生侦测包,默认闲置时间是2个小时
移动端:
基于tcp的长连接,socket编程技术
11、HTTP2.0针对同一个域名的多个请求,会建立多少个tcp连接?
https://www.cnblogs.com/zlingh/p/5887143.html
12、聊下HTTP post的body体使用form-urlencoded和multipart/form-data的区别。
application/x-www-form-urlencoded:窗体数据被编码为名称/值对。这是标准的编码格式。
multipart/form-data:窗体数据被编码为一条消息,页上的每个控件对应消息中的一个部分
13、通信底层原理
OSI采用了分层的结构化技术,共分七层:
物理层:为设备间的数据通信提供传输媒体和互连设备,光纤、无线信道等等
数据链路层:为网络层提供数据传送服务的,包括链路连接的建立、拆除和分离;对帧的收发顺序控制
网络层:数据传送的单位是分组或者包,网络层在给两个不同地理位置的主机之间提供
传输层:定义了一些传输数据的协议和端口号,TCP, UDP;主要从下层接收的数据进行分段和传输,到达目的地后再重组
会话层:通过传输层建立数据传输通道,主要在你的系统之间发起会话或者接受会话请求(IP、MAC、主机名称)
表示层:可确保一个系统的应用层所发送的信息可以被另一个系统的应用层读取,主要做的就是把应用层提供的信息变换为能够共同理解的形式,提供字符代码,数据格式,控制信息格式,加密等的统一表示。
应用层:为用户的应用程序提供网络服务
TCP/IP 采用四层结构:
网络接口层:硬件、帧头帧尾的添加
网络互联层:确定目标计算机的IP地址
传输层:TCP,确定如何传输
应用层:app
14、介绍一下XMPP?
XMPP是一种以XML为基础的开放式实时通信协议。
XMPP 是一种很类似于http协议的一种数据传输协议,它的过程就如同“解包装–〉包装”的过程,用户只需要明白它接受的类型,并理解它返回的类型,就可以很好的利用xmpp来进行数据通讯。基于可扩展标记语言(XML)的协议
XMPP基本结构:客户端 服务器 网关
通信能够在这三者的任意两个之间双向发生。服务器同时承担了客户端信息记录,连接管理和信息的路由功能。网关承担着与异构即时通信系统的互联互通,异构系统可以包括SMS(短信),MSN,ICQ等。基本的网络形式是单客户端通过TCP/IP连接到单服务器,然后在之上传输XML。
XMPP核心协议通信的基本模式就是先建立一个stream,然后协商一堆安全之类的东西,中间通信过程就是客户端发送XML Stanza,一个接一个的。服务器根据客户端发送的信息以及程序的逻辑,发送XML Stanza给客户端。但是这个过程并不是一问一答的,任何时候都有可能从一方发信给另外一方。通信的最后阶段是关闭流,关闭TCP/IP连接。
客户端1 <--> XMPP服务器 <--> 客户端2
两个客户端可以分别和服务器通信,但是客户端之间的通信必须经过服务器
用于一些即时通信
15、ssl / tls证书 作用
保障通信双方的可靠性,通信的安全和数据的完整性
https和ssl在握手方向有什么区别?
一个是连接握手,一个是安全校验握手
16、socket连接和 http 连接区别
Http是基于Tcp的,而Socket是一套编程接口让我们更方便的使用Tcp/Ip协议;
Http是应用层协议,在Tcp/Udp上一层。
1、Http是基于"请求-响应"的,服务器不能主动向客户端推送数据,只能借助客户端请求到后向客户端推送数据,而Sokcet双方随时可以互发数据;
2、Http不是持久连接的,Socket用Tcp是持久连接;
3、Http基于Tcp,Socket可以基于Tcp/Udp;
4、Http连接是通过Socket实现的;
5、Http连接后发送的数据必须满足Http协议规定的格式:请求头、请求头和请求体,而Socket连接后发送的数据没有格式要求。
Socket的实现原理及 Socket之间是如何通信的
网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。
建立网络通信连接至少要一对端口号(socket)。
socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;
HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。
socket分为客户端和服务端,客户端发送连接请求,服务端等待连接请求
当服务端socket监听到客户端socket的请求时,就响应客户端套接字的请求,建立一个新的线程,把服务端套接字描述发送给客户端,一旦客户端确认了此描述,双方正式建立连接,而服务端socket继续处于监听状态,等待其他连接请求
17、说一下HTTP协议以及经常使用的code码的含义。
一些常见的状态代码为:
200 - 服务器成功返回网页
300 - 重定向之类
404 - 请求的网页不存在
503 - 服务器暂时不可用
18、网络拥塞控制、tcp的慢启动
不要一开始就发送大量的数据,先探测一下网络的拥塞程度,也就是说由小到大逐渐增加拥塞窗口的大小。
简单来说 拥塞控制就是防止过多的数据注入网络中,这样可以使网络中的路由器或链路不致过载。
原理:
请求发送,每次按窗口数发送数据,收到一个确认就把窗口值加一,逐渐递增,这就是慢开始算法
当网络拥塞,窗口重新回 1 最大慢开始门限变为出现问题的网络拥塞窗口值的一半 这就是拥塞避免算法
然后再次循环。
19、TCP 三次握手、四次挥手,为什么 断开连接是4次挥手呢
因为TCP连接的时候,最后一次握手表示收到服务器确认的请求可以携带需要发给服务器的数据,三次是最短可能
四次挥手是确保客户端 没有消息要发给服务端,服务端也没有消息要发给客户端了,也可以不用四次,但是就会增加空等待的资源浪费
extern的作用
告诉编译器,这个全局变量在本文件找不到就去其他文件去找。如有必要需要使用#import “x.h”这样编译器才知道到哪里去找。使用extern前要保证对应变量被编译过,想要访问全局变量可以使用extern关键字(全局变量定义不能有static修饰)。
比如 A文件中 我声明的全局变量 NSInteger age = 10; 但是属性也不能直接获取。 如下在B文件中可以获取到 :
extern NSInteger age;
age ++;
NSLog(@"%d",age); // 11
如果不想让age被找到,声明为static
const的作用
常量定义,修饰一个常量
int a = 1;
int b = 2;
int const *p = &a
// 如果const修饰的是*p,那么*p的值是不能改变的,也就是p中存放的a的地址中的值无法改变,但是p的值是可以改变的(也就是p此时可以改变指向)
p = &b;
printf("---");
printf("%p",&b);
printf("---");
printf("%p",p);
printf("---");
printf("%d",*p);
//输出 ---0x7ffeea7e89f8---0x7ffeea7e89f8---2
int *const p = &a;
// 如果const修饰的是p,那么p的值是不能改变的,也就是p中存放的a的地址无法改变(p是int类型的指针变量)。但是*p是可以变化的,我们并没有用const去修饰*p,所以可以通过*p去改变a的值
*p = b;
static的作用
static NSInteger staticValue = 0;
static关键字修饰局部变量:
当static关键字修饰局部变量时,只会初始化一次且在程序中只有一份内存
关键字static不可以改变局部变量的作用域,但可延长局部变量的生命周期(直到程序结束才销毁)
static关键字修饰全局变量:
当static关键字修饰全局变量时,作用域仅限于当前文件,外部类是不可以访问到该全局变量的(即使在外部使用extern关键字也无法访问)
如果需要直接访问 需要引用头文件
宏定义
宏定义属于预编译指令,在程序运行之前已经编译好了的,编译过程替换
#define M_PI 3.14159265358979323846264338327950288
#define SELF(x) x //NSLog(@"Hello %@",SELF(name));
#define PLUS(x,y) x + y //printf("%d",PLUS(3,2));
#define MIN(A,B) A < B ? A : B // int a = MIN(1,2);
#define NSLog(format, ...) do { \ fprintf(stderr, "<%s : %d> %s\n", \
[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], \
__LINE__, __func__); \
(NSLog)((format), ##__VA_ARGS__); \
fprintf(stderr, "-------\n"); \
} while (0)
4、@property 的本质是什么?
@property = ivar + getter + setter;
“属性” (property)有两大概念:ivar(实例变量)、getter+setter(存取方法)
“属性” (property)作为 Objective-C 的一项特性,主要的作用就在于封装对象中的数据。 Objective-C 对象通常会把其所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”(access method)来访问。其中,“获取方法” (getter)用于读取变量值,而“设置方法” (setter)用于写入变量值。
5、ivar、getter、setter 是如何生成并添加到类中的
引申一个问题:@synthesize 和 @dynamic 分别有什么作用?
完成属性(@property)定义后,编译器会自动编写访问这些属性所需的方法,此过程叫做“自动合成”(autosynthesis)。
我们也可以在类的实现代码里通过 @synthesize 语法来指定实例变量的名字。
@synthesize lastName = _myLastName;
或者通过 @dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。
@property有两个对应的词,
一个是@synthesize(合成实例变量),一个是@dynamic。
如果@synthesize和@dynamic都没有写,那么默认的就是 @synthesize var = _var;
// 在类的实现代码里通过 @synthesize 语法可以来指定实例变量的名字。(@synthesize var = _newVar;)
1. @synthesize 的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法。
2. @dynamic 告诉编译器,属性的setter与getter方法由用户自己实现,不自动生成(如,@dynamic var)。
5、用@property声明的 NSString / NSArray / NSDictionary 经常使用 copy 关键字,为什么?如果改用strong关键字,可能造成什么问题?
用 @property 声明 NSString、NSArray、NSDictionary 经常使用 copy 关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作(就是把可变的赋值给不可变的),为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。
1. 因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本。
2. 如果我们使用是 strong ,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性。
//总结:使用copy的目的是,防止把可变类型的对象赋值给不可变类型的对象时,可变类型对象的值发送变化会无意间篡改不可变类型对象原来的值。
这里还有一个引申问题:
NSMutableArray 如果用 copy修饰了会出现什么问题?
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArray0 addObject:]: unrecognized selector sent to instance 0x600000a100c0'
由于使用的是copy属性,本身的可变属性默认有一个不可变的拷贝 NSArray ,所以我们用这个可变数组去添加元素的时候,找不到对应方法而发生crash。
6、浅拷贝和深拷贝的区别?
浅拷贝(copy):只复制指向对象的指针,而不复制引用对象本身。
深拷贝(mutableCopy):复制引用对象本身。内存中存在了两份独立对象本身,当修改A时,A_copy不变。
只有对不可变对象进行copy操作是指针复制(浅复制),其它情况都是内容复制(深复制)
8、如何让自己的类用copy修饰符
若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopying 与 NSMutableCopying 协议。
具体步骤:
1. 需声明该类遵从 NSCopying 协议
2. 实现 NSCopying 协议的方法。
// 该协议只有一个方法:
- (id)copyWithZone:(NSZone *)zone;
// 注意:使用 copy 修饰符,调用的是copy方法,其实真正需要实现的是 “copyWithZone” 方法。
9、ViewController生命周期
按照执行顺序排列:
1. initWithCoder:通过nib文件初始化时触发。
2. awakeFromNib:nib文件被加载的时候,会发生一个awakeFromNib的消息到nib文件中的每个对象。
//如果不是nib初始化 上面两个换成 initWithNibName:bundle:
3. loadView:开始加载视图控制器自带的view。
4. viewDidLoad:视图控制器的view被加载完成。
5. viewWillAppear:视图控制器的view将要显示在window上。
6. updateViewConstraints:视图控制器的view开始更新AutoLayout约束。
7. viewWillLayoutSubviews:视图控制器的view将要更新内容视图的位置。
8. viewDidLayoutSubviews:视图控制器的view已经更新视图的位置。
9. viewDidAppear:视图控制器的view已经展示到window上。
10. viewWillDisappear:视图控制器的view将要从window上消失。
11. viewDidDisappear:视图控制器的view已经从window上消失。
10、OC的反射机制
1). class反射
通过类名的字符串形式实例化对象。
Class class = NSClassFromString(@"student");
Student *stu = [[class alloc] init];
将类名变为字符串。
Class class =[Student class];
NSString *className = NSStringFromClass(class);
2). SEL的反射
通过方法的字符串形式实例化方法。
SEL selector = NSSelectorFromString(@"setName");
[stu performSelector:selector withObject:@"Mike"];
将方法变成字符串。
NSStringFromSelector(@selector*(setName:));
11、self 和 super
self 是类的隐藏参数,指向当前调用方法的这个类的实例。
super是一个Magic Keyword,它本质是一个编译器标示符,和self是指向的同一个消息接收者。
不同的是:super会告诉编译器,调用class这个方法时,要去父类的方法,而不是本类里的。
12、id 和 NSObject*的区别
id是一个 objc_object 结构体指针,定义是
typedef struct objc_object *id
id可以理解为指向对象的指针。所有oc的对象 id都可以指向,编译器不会做类型检查,id调用任何存在的方法都不会在编译阶段报错,当然如果这个id指向的对象没有这个方法,该崩溃还是会崩溃的。
NSObject *指向的必须是NSObject的子类,调用的也只能是NSObjec里面的方法否则就要做强制类型转换。
不是所有的OC对象都是NSObject的子类,还有一些继承自NSProxy。NSObject *可指向的类型是id的子集。
引申: id 和 instancetype 的区别
instancetype的作用,就是使那些非关联返回类型的方法返回所在类的类型!
相同点:
都可以作为方法的返回类型
不同点:
instancetype可以返回和方法所在类相同类型的对象,id只能返回未知类型的对象
instancetype只能作为返回值,不能像id那样作为参数
13、NSDictionary的实现原理是什么?hash的问题
一:字典原理
NSDictionary(字典)是使用hash表来实现key和value之间的映射和存储的
方法:- (void)setObject:(id)anObject forKey:(id)aKey;
Objective-C中的字典NSDictionary底层其实是一个哈希表
位图算法:
给定10亿个不重复的正int的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那10亿个数当中。
解法:遍历40个亿数字,映射到BitMap中,然后对于给出的数,直接判断指定的位上存在不存在即可。
14、你们的App是如何处理本地数据安全的(比如用户名的密码)?
本地尽量不存储用户隐私数据、敏感信息
使用如AES256加密算法对数据进行安全加密后再存入SQLite中
或者数据库整体加密
存放在keychain里面
向Keychain中存储数据时,不要使用kSecAttrAccessibleAlways,而是使用更安全的kSecAttrAccessibleWhenUnlocked或kSecAttrAccessibleWhenUnlockedThisDeviceOnly选项。
AES DES
15、遇到过BAD_ACCESS的错误吗?你是怎样调试的?
90%的错误来源在于对一个已经释放的对象进行release操作, 或者说对一个访问不到的地址进行访问,可能是由于些变量已经被回收了,亦可能是由于使用栈内存的基本类型的数据赋值给了id类型的变量。
例如:
id x_id = [self performSelector:@selector(returnInt)];
- (int)returnInt { return 5; }
上面通过id去接受int返回值,int是存放在栈里面的,堆内存地址如何找得到,自然就是 EXC_BAD_ACCESS。
处理方法
1、xcode可以用僵尸模式打印出对象 然后通过对象查找对应的代码位置
1、Edit Scheme - Diagnositics - Memory Management 勾选 Zombie Objects 和 Malloc Stack
2、会打印出
cyuyan[7756:17601127] *** -[UIViewController respondsToSelector:]: message sent to deallocated instance 0x7fe71240d390
这句开启僵尸模式后打出来的输出,包含了我们需要的 进程pid、崩溃地址,终端通过下面命令查看堆栈日志来找到崩溃代码
3、查找日志
sudo malloc_history 7756 0x7fe71240d390
2、在 other c flags中加入-D FOR_DEBUG(记住请只在Debug Configuration下加入此标记)。这样当你程序崩溃时,Xcode的console上就会准确地记录了最后运行的object的方法。重写一个object的respondsToSelector方法,打印报错前的
#ifdef _FOR_DEBUG_
-(BOOL) respondsToSelector:(SEL)aSelector {
printf("SELECTOR: %s\n", [NSStringFromSelector(aSelector) UTF8String]);
return [super respondsToSelector:aSelector];
}
#endif
3、通过instruments的Zombies
引申:怎么定位到野指针的地方。如果还没定位到,这个对象被提前释放了,怎么知道该对象在什么地方释放的
一种是多线程,一种是野指针。这两种Crash都带随机性,我们要让随机crash变成不随机
把这一随机的过程变成不随机的过程。对象释放后在内存上填上不可访问的数据,其实这种技术其实一直都有,xcode的Enable Scribble就是这个作用。
1、Edit Scheme - Diagnositics - Memory Management 勾选 Malloc Scribble
16、如何设计一个通知中心
单例设计一个NotificationCenter,
NSPointerArray 保存 observer,对象销毁 observer自动变null
17、KVO、KVC的实现原理
KVC( 键值编码 )实现
1.KVC是基于runtime机制实现的
2、可以访问私有成员变量、可以间接修改私有变量的值
[object setValue:@"134567" forKey:@"uid"];
就会被编译器处理成:
// 首先找到对应sel
SEL sel = sel_get_uid("setValue:forKey:");
// 根据object->isa找到sel对应的IMP实现指针
IMP method = objc_msg_lookup (object->isa,sel);
// 调用指针完成KVC赋值
method(object, sel, @"134567", @"uid");
KVC键值查找原理
setValue:forKey:搜索方式
1、首先搜索setKey:方法.(key指成员变量名, 首字母大写)
2、上面的setter方法没找到, 如果类方法accessInstanceVariablesDirectly返回YES. 那么按 _key, _isKey,key, iskey的顺序搜索成员名。(这个类方法是NSKeyValueCodingCatogery中实现的类方法, 默认实现为返回YES)
3、如果没有找到成员变量, 调用setValue:forUnderfinedKey:
valueForKey:的搜索方式
1、首先按getKey, key, isKey的顺序查找getter方法, 找到直接调用. 如果是BOOL、int等内建值类型, 会做NSNumber的转换.
2、上面的getter没找到, 查找countOfKey, objectInKeyAtindex, KeyAtindexes格式的方法. 如果countOfKey和另外两个方法中的一个找到, 那么就会返回一个可以响应NSArray所有方法的代理集合的NSArray消息方法.
3、还没找到, 查找countOfKey, enumeratorOfKey, memberOfKey格式的方法. 如果这三个方法都找到, 那么就返回一个可以响应NSSet所有方法的代理集合.
4、还是没找到, 如果类方法accessInstanceVariablesDirectly返回YES. 那么按 _key, _isKey, key, iskey的顺序搜索成员名.
5、再没找到, 调用valueForUndefinedKey.
KVO实现 键值观察、观察者模式的一种应用
简答
1.KVO是基于runtime机制实现的
2.当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter 方法。派生类在被重写的setter方法内实现真正的通知机制
3.如果原类为Person,那么生成的派生类名为NSKVONotifying_Person
4.每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将isa指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter方法
5.键值观察通知依赖于NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey:;在一个被观察属性发生改变之前, willChangeValueForKey:一定会被调用,这就 会记录旧的值。而当改变发生后,didChangeValueForKey:会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。
深入
1.Apple 使用了 isa 混写(isa-swizzling)来实现 KVO 。当观察对象A时,KVO机制动态创建一个新的名为:?NSKVONotifying_A的新类,该类继承自对象A的本类,且KVO为NSKVONotifying_A重写观察属性的setter?方法,setter?方法会负责在调用原?setter?方法之前和之后,通知所有观察对象属性值的更改情况。
2.NSKVONotifying_A类剖析:在这个过程,被观察对象的 isa 指针从指向原来的A类,被KVO机制修改为指向系统新创建的子类 NSKVONotifying_A类,来实现当前类属性值改变的监听;
3.所以当我们从应用层面上看来,完全没有意识到有新的类出现,这是系统“隐瞒”了对KVO的底层实现过程,让我们误以为还是原来的类。但是此时如果我们创建一个新的名为“NSKVONotifying_A”的类(),就会发现系统运行到注册KVO的那段代码时程序就崩溃,因为系统在注册监听的时候动态创建了名为NSKVONotifying_A的中间类,并指向这个中间类了。
4.(isa 指针的作用:每个对象都有isa 指针,指向该对象的类,它告诉 Runtime 系统这个对象的类是什么。所以对象注册为观察者时,isa指针指向新子类,那么这个被观察的对象就神奇地变成新子类的对象(或实例)了。)?因而在该对象上对 setter 的调用就会调用已重写的 setter,从而激活键值通知机制。
5.子类setter方法剖析:KVO的键值观察通知依赖于 NSObject 的两个方法:willChangeValueForKey:和 didChangevlueForKey:,在存取数值的前后分别调用2个方法: 被观察属性发生改变之前,willChangeValueForKey:被调用,通知系统该 keyPath?的属性值即将变更;当改变发生后, didChangeValueForKey: 被调用,通知系统该 keyPath?的属性值已经变更;之后,?observeValueForKey:ofObject:change:context: 也会被调用。且重写观察属性的setter?方法这种继承方式的注入是在运行时而不是编译时实现的。
22、说一下简单工厂模式,工厂模式以及抽象工厂模式?
简单工厂模式:根据外部信息就可以决定创建对象,所有产品都通过工厂判断就创建,体系结构很明显,缺点就是集中了所有的产品创建逻辑,耦合太重。
工厂模式:产品的各自创建逻辑下发到各自的工厂类中,一定程度达到解耦合。 多态性,产品构建逻辑可以具体到对应的产品工厂类中,更加清晰。 当我需要新产品的时候,只需要添加一个新的产品工厂,实现抽象工厂的产品产出方法,产出对应的产品。不影响客户逻辑。
抽象工厂模式:当有多个产品线,需要多个工厂分别生产不同的产品线产品,这个时候我们抽象出工厂逻辑,产品也抽象出产品类型,工厂抽象类只需要构建返回抽象产品的方法即可,更深程度的解耦。具体的什么工厂产什么产品逻辑下发到实际工厂实现。 即使添加新产品也不影响抽象工厂和抽象产品的逻辑。
23、如何设计一个网络请求库
网络请求库需要的功能:
1、在任意位置发起请求
2、请求表单的创建 (url拼接、参数填充、http请求方法确认)
3、UI-Loading
4、数据解析
5、异常处理
6、结果提示
自己分装的 一个 API 网络请求库
24、说一下多线程,你平常是怎么用的?
常用的有 GCD 和 NSOperation 、NSThread
NSThread 用于获取当前线程等操作
GCD 和 NSOperation 实现多线程操作不需要自己管理线程,操作简单
GCD block的使用方式比NSOperation 适合简单操作,NSOperation 对象级操作方法更多,更复杂操作适用
25、说一下UITableViewCell的卡顿你是怎么优化的?
一般简单的UITableViewCell都不会卡顿,TableView本身有Cell重用机制,但一些复杂的自适应高度的cell比较容易产生卡顿。
1、避免cell的过多重新布局,差别太大的cell之间不要选择重用。
2、提前计算并缓存cell的高度,内容
3、尽量减少动态添加View的操作
4、减少所有对主线程有影响的无意义操作
5、cell中的图片加载用异步加载,缓存等
6、局部更新cell
7、减少不必要的渲染时间,比如少用透明色之类的
29、请解释以下keywords的区别: assign vs weak, _block vs _weak
weak和assign都是引用计数不变,两个的差别在于,weak用于object type,就是指针类型,而assign用于简单的数据类型,如int BOOL 等。
assign看起来跟weak一样,其实不能混用的,assign的变量在释放后并不设置为nil(和weak不同),当你再去引用时候就会发生错误,崩溃,EXC_BAD_ACCESS.
assign 可以修饰对象么? 可以修饰,编译器不会报错,但是访问过程中对象容易野指针
__block 用于标记需要在block内部修改的变量,__weak 用于防止引用循环
其他关键字
assign,weak,unsafe_unretained,strong,retain ,copy,readonly,readwrite , nonatomic,natomic及 __weak,__block ,@synthesize 和 @dynamic
30、使用atomic一定是线程安全的吗?
atomic只能保证操作也就是存取属性的时候的存取方法是线程安全的,并不能保证整个对象就是线程安全的。
比如NSMutableArray 设置值得时候是线程安全的,但是通过objectAtIndex访问的时候就不再是线程安全的了。还是需要锁来保证线程的安全。
31、描述一个你遇到过的retain cycle例子
VC中一个强引用block里面使用self
代理使用强引用
sqllite多线程抢写入操作
32、+(void)load; +(void)initialize; 有什么用处?方法分别在什么时候调用的?
在 Objective-C 中,runtime 会自动调用每个类的这两个方法
1.+load 会在类初始加载时调用
2.+initialize 会在第一次调用类的类方法或实例方法之前被调用
这两个方法是可选的,且只有在实现了它们时才会被调用
两者的共同点:两个方法都只会被调用一次
34、如何高性能的给UIImageView加个圆角?
如何高性能的给 UIImageView 加个圆角?
不好的解决方案:使用下面的方式会强制Core Animation提前渲染屏幕的离屏绘制, 而离屏绘制就会给性能带来负面影响,会有卡顿的现象出现。
self.view.layer.cornerRadius = 5.0f;
self.view.layer.masksToBounds = YES;
正确的解决方案:使用绘图技术
- (UIImage *)circleImage {
// NO代表透明
UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0);
// 获得上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 添加一个圆
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
CGContextAddEllipseInRect(ctx, rect);
// 裁剪
CGContextClip(ctx);
// 将图片画上去
[self drawInRect:rect];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
// 关闭上下文
UIGraphicsEndImageContext();
return image;
}
还有一种方案:使用了贝塞尔曲线"切割"个这个图片, 给UIImageView 添加了的圆角,其实也是通过绘图技术来实现的。
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
imageView.center = CGPointMake(200, 300);
UIImage *anotherImage = [UIImage imageNamed:@"image"];
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds
cornerRadius:50] addClip];
[anotherImage drawInRect:imageView.bounds];
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.view addSubview:imageView];
36、设计一个检测主线程卡顿的方案
runloop 监听
37、说几个你在工作中使用到的线程安全的例子
多线程同时操作同一个数据源的时候
AFNetworking 对于session的构建等都是线程安全的
38、用过哪些锁?哪些锁的性能比较高?谈下Objective C都有哪些锁机制,你一般用哪个?
常用的锁有 NSLock、@synchronized代码块、信号量 dispatch_semaphore_t
信号量性能最高
@synchronized代码块 最方便
32、说一下静态库和动态库之间的区别
静态库
.a 、.framework 结尾
是一个已经编译好了的集合,使用的时候连接器会把静态库合并到可执行文件中。
动态库
.tbd 或 .framework结尾
编译过程不会被链接到目标代码中, 只会将动态库头文件添加到目标app的可执行文件,程序运行的时候被添加在独立于app的内存区域。
36、说一下你对架构的理解? 技术架构如何搭建?
[参考文章一 ](https://link.jianshu.com/?t=https://casatwy.com/iosying-yong-jia-gou-tan-viewceng-de-zu-zhi-he-diao-yong-fang-an.html)
[参考文章二](https://link.jianshu.com/?t=https://casatwy.com/iosying-yong-jia-gou-tan-wang-luo-ceng-she-ji-fang-an.html)
37、为什么一定要在主线程里面更新UI?
UIKit 不是线程安全的,容易产生UI更新上的混乱
40、loadView是干嘛用的?
self.view的初始化,根据xib初始化或者init初始化
41、viewWillLayoutSubView
controller layout触发的时候,开发者有机会去重新layout自己的各个subview。说UI熟悉的一定要知道。
当子View发生frame的变动的时候会触发layoutsubView,我们可以在这个方法中提前做一些预处理
42、GCD线程池原理
43、用过coredata或者sqlite吗?读写是分线程的吗?遇到过死锁没?咋解决的?
sqlite 一个线程A操作写入、一个线程B操作读取,在第一个线程等待写入的过程中也发起写入,写入操作在普通的事务操作 begin trancaction commit transaction ,这种情况就会死锁
两个线程都争取写入操作,因为在A线程等待变成排他锁的过程中处于待定锁状态,并不会拒绝B线程的保留锁的获取,导致B线程一直不释放共享锁,A就一直得不到排他锁,造成死锁。
单个线程可以死锁(main thread里dispatch_sync到main queue),
多个线程直接也可以死锁(A,B线程互相持有对方需要的资源且互相等待)。
45、PKI体系(其实就是CA证书验证体系)当中加密和签名有什么区别?
签名密钥对用于数据的完整性检测,保证防伪造与防抵赖,签名私钥的遗失,并不会影响对以前签名数据的验证,因此,签名私钥无须备份,因此,签名密钥不需要也不应该需要第三方来管理,完全由持有者自己产生;
加密密钥对用于数据的加密保护,若加密私钥遗失,将导致以前的加密数据无法解密,这在实际应用中是无法接受的,加密私钥应该由可信的第三方(即通常所说的CA)来备份,以保证加密数据的可用性,因此,加密密钥对可以由第三方来产生,并备份。
一个加密 一个保证完整性
49、iOS下如何实现指定线程数目的线程池?
使用信号量
GCD的信号量机制(dispatch_semaphore)
信号量是一个整型值,有初始计数值;可以接收通知信号和等待信号。当信号量收到通知信号时,计数+1;当信号量收到等待信号时,计数-1;如果信号量为0,线程会阻塞,直到线程信号量大于0,才会继续下去。
使用信号量机制可以实现线程的同步,也可以控制最大并发数。以下是控制最大并发数的代码。
dispatch_queue_t workConcurrentQueue = dispatch_queue_create("cccccccc", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t serialQueue = dispatch_queue_create("sssssssss",DISPATCH_QUEUE_SERIAL);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
for (NSInteger i = 0; i < 10; i++) {
dispatch_async(serialQueue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(workConcurrentQueue, ^{
NSLog(@"thread-info:%@开始执行任务%d",[NSThread currentThread],(int)i);
sleep(1);
NSLog(@"thread-info:%@结束执行任务%d",[NSThread currentThread],(int)i);
dispatch_semaphore_signal(semaphore);});
});
}
NSLog(@"主线程...!");
说明:从执行结果中可以看出,虽然将10个任务都异步加入了并发队列,但信号量机制控制了最大线程并发数,始终是3个线程在执行任务。此外,这些线程也没有阻塞线程。
50、函数式编程当中的 first-class function是什么意思呢?
函数是一等公民
函数能像参数那样被传递到另一个函数、从另一个函数那像值一样被返回出来、函数可以赋值给变量或者存在数据结构中。
51.遇到tableView卡顿嘛?会造成卡顿的原因大致有哪些?
可能造成tableView卡顿的原因有:
1.最常用的就是cell的重用, 注册重用标识符
如果不重用cell时,每当一个cell显示到屏幕上时,就会重新创建一个新的cell
如果有很多数据的时候,就会堆积很多cell。
如果重用cell,为cell创建一个ID,每当需要显示cell 的时候,都会先去缓冲池中寻找可循环利用的cell,如果没有再重新创建cell
2.避免cell的重新布局
cell的布局填充等操作 比较耗时,一般创建时就布局好
如可以将cell单独放到一个自定义类,初始化时就布局好
3.提前计算并缓存cell的属性及内容
当我们创建cell的数据源方法时,编译器并不是先创建cell 再定cell的高度
而是先根据内容一次确定每一个cell的高度,高度确定后,再创建要显示的cell,滚动时,每当cell进入凭虚都会计算高度,提前估算高度告诉编译器,编译器知道高度后,紧接着就会创建cell,这时再调用高度的具体计算方法,这样可以方式浪费时间去计算显示以外的cell
4.减少cell中控件的数量
尽量使cell得布局大致相同,不同风格的cell可以使用不用的重用标识符,初始化时添加控件,
不适用的可以先隐藏
5.不要使用ClearColor,无背景色,透明度也不要设置为0
渲染耗时比较长
6.使用局部更新
如果只是更新某组的话,使用reloadSection进行局部更
7.加载网络数据,下载图片,使用异步加载,并缓存
8.少使用addView 给cell动态添加view
9.按需加载cell,cell滚动很快时,只加载范围内的cell
10.不要实现无用的代理方法,tableView只遵守两个协议
11.缓存行高:estimatedHeightForRow不能和HeightForRow里面的layoutIfNeed同时存在,这两者同时存在才会出现“窜动”的bug。所以我的建议是:只要是固定行高就写预估行高来减少行高调用次数提升性能。如果是动态行高就不要写预估方法了,用一个行高的缓存字典来减少代码的调用次数即可
12.不要做多余的绘制工作。在实现drawRect:的时候,它的rect参数就是需要绘制的区域,这个区域之外的不需要进行绘制。例如上例中,就可以用CGRectIntersectsRect、CGRectIntersection或CGRectContainsRect判断是否需要绘制image和text,然后再调用绘制方法。
13.预渲染图像。当新的图像出现时,仍然会有短暂的停顿现象。解决的办法就是在bitmap context里先将其画一遍,导出成UIImage对象,然后再绘制到屏幕;
14.使用正确的数据结构来存储数据。
53、让你设计一种机制检测UIViewController的内存泄漏,你会怎么做?Instrument是如何检测内存泄漏的
swizzle NavigationController 的 push 和 pop方法
pop了控制器后过几秒钟进行一遍判断,如果为nil表示已销毁,没有则表示内存泄露
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf assertNotDealloc];
});
54、通过[UIImage imageNamed:]生成的对象什么时候被释放?
这种图片加载方式带有图片缓存的功能,使用这种方式加载图片后,图片会自动加入系统缓存中,并不会立即释放到内存。一些资源使程序中经常使用的图片资源,
使用这种方式会加快程序的运行减少IO操作,但对于项目中只用到一次的图片,如果采用这种方案加载,会增导致程序的内存使用增加。
非缓存的加载方式
(UIImage *)imageWithContentsOfFile:(NSString *)path
(UIImage *)
:(NSData *)data
55、applicationWillEnterForeground和applicationDidBecomeActive都会在哪些场景下被调用?举例越多越好。
后台进入前台
通知中心回来
正常启动app
56、如何终止正在运行的工作线程?
block 中 return;
[thread cancle]
57、穷举iOS下所有的本地持久化方案。
plist
preference NSUserDefault
NSKeyedArchiver
SQLite3
coreData
沙盒
58、项目中网络层如何做安全处理
1、尽量使用https
https可以过滤掉大部分的安全问题。https在证书申请,服务器配置,性能优化,客户端配置上都需要投入精力,所以缺乏安全意识的开发人员容易跳过https,或者拖到以后遇到问题再优化。https除了性能优化麻烦一些以外其他都比想象中的简单,如果没精力优化性能,至少在注册登录模块需要启用https,这部分业务对性能要求比较低。
2、不要传输明文密码
不知道现在还有多少app后台是明文存储密码的。无论客户端,server还是网络传输都要避免明文密码,要使用hash值。客户端不要做任何密码相关的存储,hash值也不行。存储token进行下一次的认证,而且token需要设置有效期,使用refresh token去申请新的token。
3、Post并不比Get安全
事实上,Post和Get一样不安全,都是明文。参数放在QueryString或者Body没任何安全上的差别。在Http的环境下,使用Post或者Get都需要做加密和签名处理。
4、不要使用301跳转
301跳转很容易被Http劫持攻击。移动端http使用301比桌面端更危险,用户看不到浏览器地址,无法察觉到被重定向到了其他地址。如果一定要使用,确保跳转发生在https的环境下,而且https做了证书绑定校验。
5、http请求都带上MAC
所有客户端发出的请求,无论是查询还是写操作,都带上MAC(Message Authentication
Code)。MAC不但能保证请求没有被篡改(Integrity),还能保证请求确实来自你的合法客户端(Signing)。当然前提是你客户端的key没有被泄漏,如何保证客户端key的安全是另一个话题。MAC值的计算可以简单的处理为hash(request
params+key)。带上MAC之后,服务器就可以过滤掉绝大部分的非法请求。MAC虽然带有签名的功能,和RSA证书的电子签名方式却不一样,原因是MAC签名和签名验证使用的是同一个key,而RSA是使用私钥签名,公钥验证,MAC的签名并不具备法律效应。
6、http请求使用临时密钥
高延迟的网络环境下,不经优化https的体验确实会明显不如http。在不具备https条件或对网络性能要求较高且缺乏https优化经验的场景下,http的流量也应该使用AES进行加密。AES的密钥可以由客户端来临时生成,不过这个临时的AES
key需要使用服务器的公钥进行加密,确保只有自己的服务器才能解开这个请求的信息,当然服务器的response也需要使用同样的AES
key进行加密。由于http的应用场景都是由客户端发起,服务器响应,所以这种由客户端单方生成密钥的方式可以一定程度上便捷的保证通信安全。
7、AES使用CBC模式
不要使用ECB模式,记得设置初始化向量,每个block加密之前要和上个block的秘文进行运算。
60、M、V、C相互通讯规则你知道的有哪些?
MVC 是一种设计思想,一种框架模式,是一种把应用中所有类组织起来的策略,它把你的程序分为三块,分别是:
M(Model):实际上考虑的是“什么”问题,你的程序本质上是什么,独立于 UI 工作。是程序中用于处理应用程序逻辑的部分,通常负责存取数据。
C(Controller):控制你 Model 如何呈现在屏幕上,当它需要数据的时候就告诉 Model,你帮我获取某某数据;当它需要 UI 展示和更新的时候就告诉 View,你帮我生成一个 UI 显示某某数据,是 Model 和 View 沟通的桥梁。
V(View):Controller 的手下,是 Controller 要使用的类,用于构建视图,通常是根据 Model 来创建视图的。
要了解 MVC 如何工作,首先需要了解这三个模块间如何通信。
MVC通信规则
Controller to Model
可以直接单向通信。Controller 需要将 Model 呈现给用户,因此需要知道模型的一切,还需要有同 Model 完全通信的能力,并且能任意使用 Model 的公共 API。
Controller to View
可以直接单向通信。Controller 通过 View 来布局用户界面。
Model to View
永远不要直接通信。Model 是独立于 UI 的,并不需要和 View 直接通信,View 通过 Controller 获取 Model 数据
View to Controller
View 不能对 Controller 知道的太多,因此要通过间接的方式通信。
Target
action。首先 Controller 会给自己留一个 target,再把配套的 action 交给 View 作为联系方式。那么 View
接收到某些变化时,View 就会发送 action 给 target 从而达到通知的目的。这里 View 只需要发送
action,并不需要知道 Controller 如何去执行方法。
代理。有时候 View 没有足够的逻辑去判断用户操作是否符合规范,他会把判断这些问题的权力委托给其他对象,他只需获得答案就行了,并不会管是谁给的答案。
DataSoure。View 没有拥有他们所显示数据的权力,View 只能向 Controller 请求数据进行显示,Controller 则获取 Model 的数据整理排版后提供给 View。
Model 访问 Controller
同样的 Model 是独立于 UI 存在的,因此无法直接与 Controller 通信,但是当 Model 本身信息发生了改变的时候,会通过下面的方式进行间接通信。
Notification & KVO一种类似电台的方法,Model 信息改变时会广播消息给感兴趣的人 ,只要 Controller 接收到了这个广播的时候就会主动联系 Model,获取新的数据并提供给 View。
从上面的简单介绍中我们来简单概括一下 MVC 模式的优点。
1.低耦合性
2.有利于开发分工
3.有利于组件重用
4.可维护性
60、什么是MVVM,请设计View model需要考虑哪些?MVC缺点是什么
M + V + VM , VM的作用主要用于简化Controller的负担,但是VM的设计中不可以没有C,其实应该是 M + V + C +VM , C 作为 关联 V 和 VM 的纽带,V 最好不要直接关联VM。
MVC缺点
胖VC ,VC 和V、和M耦合很重
MVP
Controller/Presenter负责逻辑的处理 View 不和 Model通信
61、NStimer准吗?谈谈你的看法?如果不准该怎样实现一个精确的NSTimer?
1.不准
2.不准的原因如下:
1、NSTimer加在main runloop中,模式是NSDefaultRunLoopMode,main负责所有主线程事件,例如UI界面的操作,复杂的运算,这样在同一个runloop中timer就会产生阻塞。
2、模式的改变。主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。
当你创建一个 Timer 并加到 DefaultMode 时,Timer 会得到重复回调,但此时滑动一个ScrollView时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 Timer 就不会被回调,并且也不会影响到滑动操作。所以就会影响到NSTimer不准的情况。
PS:DefaultMode 是 App 平时所处的状态,rackingRunLoopMode 是追踪 ScrollView 滑动时的状态。
方法一:
1、在主线程中进行NSTimer操作,但是将NSTimer实例加到main runloop的特定mode(模式)中。避免被复杂运算操作或者UI界面刷新所干扰。
self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(showTime) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
2、在子线程中进行NSTimer的操作,再在主线程中修改UI界面显示操作结果;
- (void)timerMethod2 {
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(newThread) object:nil];
[thread start];
}
- (void)newThread
{
@autoreleasepool
{
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(showTime) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] run];
}
}
总结:
一开始的时候系统就为我们将主线程的main runloop隐式的启动了。
在创建线程的时候,可以主动获取当前线程的runloop。每个子线程对应一个runloop
方法二:
使用示例
使用mach内核级的函数可以使用mach_absolute_time()获取到CPU的tickcount的计数值,可以通过”mach_timebase_info”函数获取到纳秒级的精确度 。然后使用mach_wait_until(uint64_t deadline)函数,直到指定的时间之后,就可以执行指定任务了。
关于数据结构mach_timebase_info的定义如下:
struct mach_timebase_info {uint32_t numer;uint32_t denom;};
#include
#include
static const uint64_t NANOS_PER_USEC = 1000ULL;
static const uint64_t NANOS_PER_MILLISEC = 1000ULL * NANOS_PER_USEC;
static const uint64_t NANOS_PER_SEC = 1000ULL * NANOS_PER_MILLISEC;
static mach_timebase_info_data_t timebase_info;
static uint64_t nanos_to_abs(uint64_t nanos) {
return nanos * timebase_info.denom / timebase_info.numer;
}
void example_mach_wait_until(int seconds)
{
mach_timebase_info(&timebase_info);
uint64_t time_to_wait = nanos_to_abs(seconds * NANOS_PER_SEC);
uint64_t now = mach_absolute_time();
mach_wait_until(now + time_to_wait);
}
方法三:直接使用GCD替代!
cgd timer
self.gcdTime = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
// 开始时间支持纳秒级别
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)2 * NSEC_PER_SEC);
// 2秒执行一次
uint64_t dur = (uint64_t)(2.0 * NSEC_PER_SEC);
// 最后一个参数是允许的误差,即使设为零,系统也会有默认的误差
dispatch_source_set_timer(self.gcdTime, start, dur, 0);
// 设置回调
dispatch_source_set_event_handler(self.gcdTime, ^{
NSLog(@"---%@---%@",[NSThread currentThread],self);
});
取消定时器:dispatch_cancel(self.gcdTimer);
62、你知道哪些设计模式,并简要叙述?
1). MVC模式:Model View Control,把模型 视图 控制器 层进行解耦合编写。
2). MVVM模式:Model View ViewModel 把模型 视图 业务逻辑 层进行解耦和编写。
3). 单例模式:通过static关键词,声明全局变量。在整个进程运行期间只会被赋值一次。
4). 观察者模式:KVO是典型的通知模式,观察某个属性的状态,状态发生变化时通知观察者。
5). 委托模式:代理+协议的组合。实现1对1的反向传值操作。
6). 工厂模式:通过一个类方法,批量的根据已有模板生产对象。
63、import 跟 #include 有什么区别,@class呢,#import<> 跟 #import”” 有什么区别?
1. #import是Objective-C导入头文件的关键字,#include是C/C++导入头文件的关键字,使用#import头文件会自动只导入一次,不会重复导入。
2. @class告诉编译器某个类的声明,当执行时,才去查看类的实现文件,可以解决头文件的相互包含。
3. #import<> 用来包含系统的头文件,#import””用来包含用户头文件。
64、tableView的重用机制能简单说一下么?
cell池
visibleCells 当前显示的cells
reusableTableCells 保存重用的cells
dequeueReusableCellWithIdentifier 获取重用cell
超出屏幕的时候 更新 reusableTableCells
reload的时候 更新 reusableTableCells
reusableTableCells为空的话 reloadRowsAtIndex 也会更新
65、写一个线程安全的单例模式 - 保证线程安全的方式(加锁和GCD栅栏,队列组相关知识)
1、
private static MySingleton instance = new MySingleton();
private MySingleton(){}
public static MySingleton getInstance() {
return instance;
}
2、
private static MySingleton instance = null;
private MySingleton(){}
public synchronized static MySingleton getInstance() {
try {
if(instance == null){//懒汉式
//创建实例之前可能会有一些准备性的耗时工作
Thread.sleep(300);
instance = new MySingleton();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return instance;
}
66、在手势对象基础类UIGestureRecognizer的常用子类手势类型中哪两个手势发生后,响应只会执行一次?
UITapGestureRecognizer,UISwipeGestureRecognizer是一次性手势,手势发生后,响应只会执行一次。
68、对于Objective-C,你认为它最大的优点和最大的不足是什么
最大的优点是它的运行时特性,不足是没有命名空间,对于命名冲突,可以使用长命名法或特殊前缀解决,如果是引入的第三方库之间的命名冲突,可以
对项目buildsetting里的other linker flags进行修改(第三方静态库引用者修改)
Bulding Setting里设置的other linker flags添加的有-Objc,而-Objc得作用就是将加载的静态库中的分类一并加载到程序的可执行文件,如果不添加这个参数,很有可能会出现selector not recognized问题,主要是找不到分类定义的方法。
-Objc添加后就会出现多个静态库定义同样的方法、全局变量等,然后就会出现上面的问题duplicate symbol。
69、NSOperationQueue
你用过NSOperationQueue么?如果用过或者了解的话,你为什么要使用NSOperationQueue,实现了什么?请描述它和G.C.D的区别和类似的地方(提示:可以从两者的实现机制和适用范围来描述)。
使用NSOperationQueue用来管理子类化的NSOperation对象,控制其线程并发数目。GCD和NSOperation都可以实现对线程的管理,区别是 NSOperation和NSOperationQueue是多线程的面向对象抽象。
项目中使用NSOperation的优点是NSOperation是对线程的高度抽象,在项目中使用它,会使项目的程序结构更好,子类化NSOperation的设计思路,是具有面向对象的优点(复用、封装),使得实现是多线程支持,而接口简单,建议在复杂项目中使用。
项目中使用GCD的优点是GCD本身非常简单、易用,对于不复杂的多线程操作,会节省代码量,而Block参数的使用,会是代码更为易读,建议在简单项目中使用。
70、程序内存分区
程序内存分区 以下是比较常用的五分区方式,当然也不排除网上有其他的分区方式。
栈
栈的大小在编译时就已经确定了,一般是2M;栈是一块从高到低地址的连续区域,存放临时变量和执行函数时的内存等。栈内存分配分为动态和静态,静态如自动变量(局部变量)等,动态如alloc等。
堆
堆是从低到高地址的不连续区域,类似链表;用来存放malloc或new申请的内存。
全局/静态
存放静态/全局变量;全局区细分为未初始化/初始化区。
常量
存放常量;程序中使用的常量会到常量区获取。
可以看看这个例子来理解一下。
...int a;//a在全局未初始化区int a = 10;//a在全局初始化区 10在常量区static int a = 10;//a在静态区 10在常量区//程序入口int main(...) { int a = 10;//a在栈区 10在常量区
static int a = 10;//a在静态区 10在常量区
char *a = (char *)malloc(10); //a在栈区 malloc后的内存在堆区
...
}
代码
存放二进制代码,运行程序就是执行代码,代码要执行就要加载进内存(RAM运行内存)。
71、指针函数 / 函数指针 / Block
指针函数
C语言的概念;本质是函数,返回指针。
char *fun() { char *p = ""; return p;
}
函数指针
C语言的概念;本质是指针,指向函数。
int fun(int a,int b) { return a + b;
}int (*func)(int,int);
func = fun;
func(1,2);//3
Block
OC语言的概念;表示一个代码块,OC中视为对象;挺像C函数指针的。
//typedeftypedef int (^SumBlock)(int a,int b);
SumBlock sumBlock = ^(int a,int b) { return a + b;
};
sumBlock(1,2);//3//普通
int (^sumBlock)(int a,int b) = ^(int a,int b) { return a + b;
};
sumBlock(1,2);//3
72、iOS类和结构体有什么区别
1:类指针赋值时只是复制了地址,结构体是复制内容;
2:类不能有同名同参数个数的方法,结构体可以;
3:结构体方法实现编译时就确定了,类方法实现可动态改变;
4:内存分配不一样,结构体在栈,类在堆;
5:结构体可以多重继承,类只能单继承。
73、线程安全方法
线程安全:多线程环境下保证数据的完整性。
队列
把操作放入队列线性执行,可用GCD和NSOperationQueue。
锁/信号量
用锁/信号量形成操作互斥。
让操作原子化
让操作原子执行,系统提供了一些原子执行的方法。
了解更多
74、NSOperationQueue和GCD区别联系
区别
NSOperationQueue没有串行/并发队列,但可以设置最大并发数;
NSOperationQueue支持方法和block,GCD只支持block;
NSOperationQueue可以暂停/取消操作;
NSOperationQueue支持更多的功能,比如KVO和自定义操作;
NSOperationQueue可以设置操作的优先级,GCD只能设置队列的优先级。
联系
提供的功能是相似的;
NSOperationQueue是GCD的封装。
75、对程序性能的优化你有什么建议?
1.使用复用机制
2.尽可能设置 View 为不透明
3.避免臃肿的 XIB 文件
4.不要阻塞主线程
5.图片尺寸匹配 UIImageView,避免巨大图片
6.选择合适的容器
8.View 的复用和懒加载机制
9.缓存 服务器的响应信息(response)、 图片、计算值。比如:UITableView 的 row heights。
10.关于图形绘制、减少离屏渲染(设置圆角和阴影的时候可以选用绘制的方法)
11.处理 Memory Warnings
在 AppDelegate 中实现 - [AppDelegate applicationDidReceiveMemoryWarning:] 代理方法。
在 UIViewController 中重载 didReceiveMemoryWarning 方法。
监听 UIApplicationDidReceiveMemoryWarningNotification 通知。
12.复用高开销的对象
14.优化 UITableView
通过正确的设置 reuseIdentifier 来重用 Cell。
尽量减少不必要的透明 View。
尽量避免渐变效果、图片拉伸和离屏渲染。
当不同的行的高度不一样时,尽量缓存它们的高度值。
如果 Cell 展示的内容来自网络,确保用异步加载的方式来获取数据,并且缓存服务器的 response。
使用 shadowPath 来设置阴影效果。
尽量减少 subview 的数量,对于 subview 较多并且样式多变的 Cell,可以考虑用异步绘制或重写 drawRect。
尽量优化 - [UITableView tableView:cellForRowAtIndexPath:] 方法中的处理逻辑,如果确实要做一些处理,可以考虑做一次,缓存结果。
选择合适的数据结构来承载数据,不同的数据结构对不同操作的开销是存在差异的。
对于 rowHeight、sectionFooterHeight、sectionHeaderHeight 尽量使用常量。
15.选择合适的数据存储方式
在 iOS 中可以用来进行数据持有化的方案包括:
NSUserDefaults。只适合用来存小数据。
XML、JSON、Plist 等文件。JSON 和 XML 文件的差异在「选择正确的数据格式」已经说过了。
使用 NSCoding 来存档。NSCoding 同样是对文件进行读写,所以它也会面临必须加载整个文件才能继续的问题。
使用 SQLite 数据库。可以配合 FMDB 使用。数据的相对文件来说还是好处很多的,比如可以按需取数据、不用暴力查找等等。
使用 CoreData。也是数据库技术,跟 SQLite 的性能差异比较小。但是 CoreData 是一个对象图谱模型,显得更面向对象;SQLite 就是常规的 DBMS。
16.减少应用启动时间
快速启动应用对于用户来说可以留下很好的印象。尤其是第一次使用时。
保证应用快速启动的指导原则:
尽量将启动过程中的处理分拆成各个异步处理流,比如:网络请求、数据库访问、数据解析等等。
避免臃肿的 XIB 文件,因为它们会在你的主线程中进行加载。重申:Storyboard 没这个问题,放心使用。
注意:在测试程序启动性能的时候,最好用与 Xcode 断开连接的设备进行测试。因为 watchdog 在使用 Xcode 进行调试的时候是不会启动的。
17.使用 Autorelease Pool (内存释放池)
18.imageNamed 和 imageWithContentsOfFile,imageName会缓存图片
76、NSURLConnection 和NSURLSession 的区别是 么? NSURLProtocol是做什么的?
1.下载
NSURLConnection下载文件时,先是将整个文件下载到内存,然后再写入到沙盒,如果文件比较大,就会出现内存暴涨的情况。
而使用NSURLSessionDownloadTask下载文件,会默认下载到沙盒中的tem文件中,不会出现内存暴涨的情况,但是在下载完成后会把tem中的临时文件删除,需要在初始化任务方法时,在completionHandler回调中增加保存文件的代码
2.请求方法的控制
NSURLConnection实例化对象,实例化开始,默认请求就发送(同步发送),不需要调用start方法。而cancel可以停止请求的发送,停止后不能继续访问,需要创建新的请求。
NSURLSession有三个控制方法,取消(cancel)、暂停(suspend)、继续(resume),暂停以后可以通过继续恢复当前的请求任务。
使用NSURLSession进行断点下载更加便捷.
NSURLSession的构造方法(sessionWithConfiguration:delegate:delegateQueue)中有一个NSURLSessionConfiguration类的参数可以设置配置信息,其决定了cookie,安全和高速缓存策略,最大主机连接数,资源管理,网络超时等配置。NSURLConnection不能进行这个配置,相比较与NSURLConnection依赖与一个全局的配置对象,缺乏灵活性而言,NSURLSession有很大的改进
77、如果项目开始容错处理没做?如何防止拦截潜在的崩溃?
例:
1、category给类添加方法用来替换掉原本存在潜在崩溃的方法。
2、利用runtime方法交换技术,将系统方法替换成类添加的新方法。
3、利用异常的捕获来防止程序的崩溃,并且进行相应的处理。
总结:
1、不要过分相信服务器返回的数据会永远的正确。
2、在对数据处理上,要进行容错处理,进行相应判断之后再处理数据,这是一个良好的编程习惯。
78、容错处理你们一般是注意哪些?
在团队协作开发当中,由于每个团队成员的水平不一,很难控制代码的质量,保证代码的健壮性,经常会发生由于后台返回异常数据造成app崩溃闪退的情况,为了避免这样的情况项目中做一些容错处理,显得格外重要,极大程度上降低了因为数据容错不到位产生崩溃闪退的概率。
例如:
1.字典
2.数组;
3.野指针;
4.NSNull @"{\"value\": null}"; 这种json解析出来的时候,NSNull如果去执行方法,就会 unrecognized selector
等~
79、内存泄漏可能会出现的几种原因,聊聊你的看法?
第一种可能:第三方框架不当使用;
第二种可能:block循环引用;
第三种可能:delegate循环引用;
第四种可能:NSTimer循环引用 如 和 VC的循环引用
第五种可能:非OC对象内存处理
第六种可能:地图类处理
第七种可能:大次数循环内存暴涨
追问一:非OC对象如何处理?
非OC对象,其需要手动执行释放操作例:CGImageRelease(ref),否则会造成大量的内存泄漏导致程序崩溃。
其他的对于CoreFoundation框架下的某些对象或变量需要手动释放、C语言代码中的malloc等需要对应free。
80、常用锁有以下几种:
1.@synchronized 关键字加锁
2. NSLock 对象锁
3. NSCondition
4. NSConditionLock 条件锁
5. NSRecursiveLock 递归锁
6. pthread_mutex 互斥锁(C语言)
7. dispatch_semaphore 信号量实现加锁(GCD)
8. OSSpinLock
9.pthread_rwlock
10.POSIX Conditions
11.os_unfair_lock
追问一:自旋和互斥对比?
自旋锁和互斥锁
相同点:都能保证同一时间只有一个线程访问共享资源。都能保证线程安全。
不同点:
互斥锁:如果共享数据已经有其他线程加锁了,线程会进入休眠状态等待锁。一旦被访问的资源被解锁,则等待资源的线程会被唤醒。
自旋锁:如果共享数据已经有其他线程加锁了,线程会以死循环的方式等待锁,一旦被访问的资源被解锁,则等待资源的线程会立即执行。
自旋锁的效率高于互斥锁。
使用自旋锁时要注意:
由于自旋时不释放CPU,因而持有自旋锁的线程应该尽快释放自旋锁,否则等待该自旋锁的线程会一直在哪里自旋,这就会浪费CPU时间。
持有自旋锁的线程在sleep之前应该释放自旋锁以便其他可以获得该自旋锁。内核编程中,如果持有自旋锁的代码sleep了就可能导致整个系统挂起。
使用任何锁都需要消耗系统资源(内存资源和CPU时间),这种资源消耗可以分为两类:
1.建立锁所需要的资源
2.当线程被阻塞时所需要的资源
追问二:用C/OC/C++,任选其一,实现自旋或互斥?口述即可!
cpp实现:
两种锁的加锁原理:
互斥锁:线程会从sleep(加锁)——>running(解锁),过程中有上下文的切换,cpu的抢占,信号的发送等开销。
自旋锁:线程一直是running(加锁——>解锁),死循环检测锁的标志位,机制不复杂。
82、怎么防止别人动态在你程序生成代码?怎么防止反编译?
(这题是听错了面试官的意思)
面试官意思是怎么防止别人反编译你的app?
1.本地数据加密
iOS应用防反编译加密技术之一:对NSUserDefaults,sqlite存储文件数据加密,保护帐号和关键信息
2.URL编码加密
iOS应用防反编译加密技术之二:对程序中出现的URL进行编码加密,防止URL被静态分析
3.网络传输数据加密
iOS应用防反编译加密技术之三:对客户端传输数据提供加密方案,有效防止通过网络接口的拦截获取数据
4.方法体,方法名高级混淆
iOS应用防反编译加密技术之四:对应用程序的方法名和方法体进行混淆,保证源码被逆向后无法解析代码
5.程序结构混排加密
iOS应用防反编译加密技术之五:对应用程序逻辑结构进行打乱混排,保证源码可读性降到最低
84、你理解的多线程?
1.可能会追问,每种多线程基于什么语言?
2.生命周期是如何管理?
3.你更倾向于哪种?追问至现在常用的两种你的看法是?
第一种:pthread
.特点:
1)一套通用的多线程API
2)适用于Unix\Linux\Windows等系统
3)跨平台\可移植
4)使用难度大
b.使用语言:c语言
c.使用频率:几乎不用
d.线程生命周期:由程序员进行管理
第二种:NSThread
a.特点:
1)使用更加面向对象
2)简单易用,可直接操作线程对象
b.使用语言:OC语言
c.使用频率:偶尔使用
d.线程生命周期:由程序员进行管理
第三种:GCD
a.特点:
1)旨在替代NSThread等线程技术
2)充分利用设备的多核(自动)
b.使用语言:C语言
c.使用频率:经常使用
d.线程生命周期:自动管理
第四种:NSOperation
a.特点:
1)基于GCD(底层是GCD)
2)比GCD多了一些更简单实用的功能
3)使用更加面向对象
b.使用语言:OC语言
c.使用频率:经常使用
d.线程生命周期:自动管理
多线程的原理
同一时间,CPU只能处理1条线程,只有1条线程在工作(执行)
多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换)
如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象
思考:如果线程非常非常多,会发生什么情况?
CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源
每条线程被调度执行的频次会降低(线程的执行效率降低)
多线程的优点
能适当提高程序的执行效率
能适当提高资源利用率(CPU、内存利用率)
多线程的缺点
开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
线程越多,CPU在调度线程上的开销就越大
程序设计更加复杂:比如线程之间的通信、多线程的数据共享
你更倾向于哪一种?
倾向于GCD:
GCD
技术是一个轻量的,底层实现隐藏的神奇技术,我们能够通过GCD和block轻松实现多线程编程,有时候,GCD相比其他系统提供的多线程方法更加有效,当然,有时候GCD不是最佳选择,另一个多线程编程的技术
NSOprationQueue 让我们能够将后台线程以队列方式依序执行,并提供更多操作的入口,这和 GCD 的实现有些类似。
这种类似不是一个巧合,在早期,MacOX
与 iOS 的程序都普遍采用Operation
Queue来进行编写后台线程代码,而之后出现的GCD技术大体是依照前者的原则来实现的,而随着GCD的普及,在iOS 4 与 MacOS X
10.6以后,Operation Queue的底层实现都是用GCD来实现的。
那这两者直接有什么区别呢?
1. GCD是底层的C语言构成的API,而NSOperationQueue及相关对象是Objc的对象。在GCD中,在队列中执行的是由block构成的任务,这是一个轻量级的数据结构;而Operation作为一个对象,为我们提供了更多的选择;
2. 在NSOperationQueue中,我们可以随时取消已经设定要准备执行的任务(当然,已经开始的任务就无法阻止了),而GCD没法停止已经加入queue的block(其实是有的,但需要许多复杂的代码);
3. NSOperation能够方便地设置依赖关系,我们可以让一个Operation依赖于另一个Operation,这样的话尽管两个Operation处于同一个并行队列中,但前者会直到后者执行完毕后再执行;
4. 我们能将KVO应用在NSOperation中,可以监听一个Operation是否完成或取消,这样子能比GCD更加有效地掌控我们执行的后台任务;
5. 在NSOperation中,我们能够设置NSOperation的priority优先级,能够使同一个并行队列中的任务区分先后地执行,而在GCD中,我们只能区分不同任务队列的优先级,如果要区分block任务的优先级,也需要大量的复杂代码;
6. 我们能够对NSOperation进行继承,在这之上添加成员变量与成员方法,提高整个代码的复用度,这比简单地将block任务排入执行队列更有自由度,能够在其之上添加更多自定制的功能。
总的来说,Operation
queue
提供了更多你在编写多线程程序时需要的功能,并隐藏了许多线程调度,线程取消与线程优先级的复杂代码,为我们提供简单的API入口。从编程原则来说,一般我们需要尽可能的使用高等级、封装完美的API,在必须时才使用底层API。但是我认为当我们的需求能够以更简单的底层代码完成的时候,简洁的GCD或许是个更好的选择,而Operation
queue 为我们提供能更多的选择。
倾向于:NSOperation
NSOperation相对于GCD:
1,NSOperation拥有更多的函数可用,具体查看api。NSOperationQueue 是在GCD基础上实现的,只不过是GCD更高一层的抽象。
2,在NSOperationQueue中,可以建立各个NSOperation之间的依赖关系。
3,NSOperationQueue支持KVO。可以监测operation是否正在执行(isExecuted)、是否结束(isFinished),是否取消(isCanceld)
4,GCD 只支持FIFO 的队列,而NSOperationQueue可以调整队列的执行顺序(通过调整权重)。NSOperationQueue可以方便的管理并发、NSOperation之间的优先级。
使用NSOperation的情况:各个操作之间有依赖关系、操作需要取消暂停、并发管理、控制操作之间优先级,限制同时能执行的线程数量.让线程在某时刻停止/继续等。
使用GCD的情况:一般的需求很简单的多线程操作,用GCD都可以了,简单高效。
从编程原则来说,一般我们需要尽可能的使用高等级、封装完美的API,在必须时才使用底层API。
当需求简单,简洁的GCD或许是个更好的选择,而Operation queue 为我们提供能更多的选择。
86、runtime动态创建一个类,需要注意什么?
1、objc_allocateClassPair
2、class_addIvar 添加属性(在注册之前)
3、objc_registerClassPair 注册类
4、class_addMethod 从其他类构建方法,然后获取SEL和IMP
-
- method_getImplementation IMP
- method_getTypeEncoding 参数类型
- @selector(newInsMethod) SEL
5、objc_disposeClassPair 销毁
87、有一个很长字符串,你用什么算法搜索到abc的位置?
1、暴力匹配
BF BM
2、KMP查找
let text = "ABC12124335234562345623456"
let pat = "243"
let array = [Int].init(repeating: 0, count: text.count)
let next = getNext(pat: pat as NSString, next: array)
print(KMPSearch(text: text as NSString, pat: pat as NSString, next: next))
func KMPSearch(text: NSString, pat: NSString, next: [Int]) -> Int {
let m = text.length
let n = pat.length
var i = 0
var j = 0
while i<m && j<n {
if j == -1 || text.character(at: i) == pat.character(at: j) {
i = i + 1
j = j + 1
} else {
j = next[j]
}
if j == n {
return i - j
}
}
return -1
}
func getNext(pat: NSString, next: [Int]) -> [Int] {
var nextArray = next
let n = pat.length
nextArray[0] = -1
var k = -1
var j = 0
while j<n-1 {
if k == -1 || pat.character(at: j) == pat.character(at: k) {
k = k+1
j = j+1
nextArray[j] = k
} else{
k = nextArray[k]
}
}
return nextArray
}
88、代码文件编译生成过程,编译和链接有什么区别,链接做了什么事情
将预处理生成的文件,经过词法分析、语法分析、语义分析以及优化后编译成若干个目标模块。可以理解为将高级语言翻译为计算机可以理解的二进制代码,即机器语言。
由链接程序将编译后形成的一组目标模块以及它们所需要的库函数链接在一起,形成一个完整的载入模型。链接主要解决模块间的相互引用问题。分为地址和空间分配,符号解析和重定位几个步骤。
在编译阶段生成目标文件时,会暂时搁置那些外部引用,而这些外部引用就是在链接时进行确定的,链接器在链接时,会根据符号名称去相应模块中寻找对应符号。待符号确定之后,链接器会重写之前那些未确定的符号的地址,这个过程就是重定位。链接一般分为静态链接、载入时动态链接以及运行时动态链接三种。
90、A B 线程执行到一半去执行C线程,用OC和C各自怎么实现!
wait notify
dispatch_wait
dispatch_notify
92、C语言中strlen和sizeof的区别
sizeof是求数据类型所占的空间大小,而strlen是求字符串的长度
93、推送的原理
1、 注册:为应用程序申请消息推送服务。此时你的设备会向APNs服务器发送注册请求。
2、 APNs服务器接受请求,并将deviceToken返给你设备上的应用程序
3、客户端应用程序将deviceToken发送给后台服务器程序,后台接收并储存。
4、 后台服务器向APNs服务器发送推送消息
5、 APNs服务器将消息发给deviceToken对应设备上的应用程序
https://www.jianshu.com/p/2595dfc5e7cd
99、请说明并比较以下关键词:atomic, nonatomic
atomic修饰的对象会保证setter和getter的完整性,任何线程对其访问都可以得到一个完整的初始化后的对象。
因为要保证操作完成,所以速度慢。它比nonatomic安全,但也并不是绝对的线程安全,例如多个线程同时调用set和get就会导致获得的对象值不一样。绝对的线程安全就要用关键词synchronized。
atomic只是get set线程安全,不代表就一定线程安全了
nonatomic修饰的对象不保证setter和getter的完整性,所以多个线程对它进行访问,它可能会返回未初始化的对象。正因为如此,它比atomic快,但也是线程不安全的。
102、Swift和OC混编遇到了什么问题
1、编译速度问题 5分钟变为 10分钟
2、经常丢失断点、丢失提示、llbd打印信息错误等
这种情况,请仔细检查你的桥接文件:项目名-Bridging-Header,是否导入了第三方库。若导入了第三方库,则该库是否是以Cocoapods来管理的,比如AFNetWorking是通过 Cocoapods 管理的,那么在桥接文件中,你应该
@import AFNetWorking;
而不是 import "AFNetWorking.h",或者以这种#import导入该三方的其他文件
3、命名空间的问题
103、Swift 引入后 编译优化的一些思考
1. 尽可能的移除pch中的文件、XX-Swift.h文件千万不要图方便放到pch文件中,不然每次编译都需要全局编译
2. 尽可能的减少Objective-C与Swift的混编,减小bridge文件的大小、通过模块化,实现OC与Swift之间的隔离 通过路由的方式进行模块通信,降低耦合度,路由中间件也可以减少业务中头文件的频繁交叉、繁复引用,降低耦合性
3. 模块头文件引用以@import or import <> 库引用的方式
4. 通过中间件减少业务中头文件的交叉、繁复引用,降低耦合性
5. 通过将第三方库、基础组件二进制化减少编译时间,把很少改动或者基本不会改的库编译成二进制framework
6. 组件化的时候注意组件之间的依赖关系
- 业务组件尽可能不依赖业务组件,如果依赖关系过强,就需要考虑是否业务拆分有问题
- 业务组件只依赖基础组件与第三方库
- 基础组件不依赖业务组件
- 基础组件尽量不依赖基础组件
104、项目优化的一些思考
项目的优化分为编译优化 和 运行优化
编译优化指的是 优化方向是优化我们的开发过程,例如编译速度,代码效率,迭代效率,扩展性等
运行优化指的是 优化方向是app的运行,例如,列表流畅度、交互体验、网络延时等等
编译优化:
Swift编译速度慢的原因:
1、本身方法实现耗时太多:在xcode中开启编译时长检查,方法编译时间超出设置值就会有黄色警告
2、过多的编译器类型检查:例如过多的optation 类型
3、头文件的引用问题,尽可能的移除pch中的文件、XX-Swift.h文件千万不要图方便放到pch文件中,不然每次编译都需要全局编译
4、swift工程引用OC pod库 不需要通过桥文件 直接import对应的库名称,也尽量少在bridge文件中对OC库的头文件进行引用,影响Xcode编译效率,同时会导致lldb调试问题和断点问题
5、
pod库太多:把一些稳定的 不需要变更的库,打成二进制包
懒加载的使用
105、iOS系统框架介绍
107、如何拦截AFNetworking,我希望在请求发出去之前添加一些头部信息
https://www.jianshu.com/p/ae5e8f9988d8
NSURLProtocol 拦截
108、日志上报,错误日志上报,业界有哪些方法 案例
exception catch
信号异常 catch
109、一个app进入后台之后如何唤起
app没死可以通过通知
app已死可以通过VoIP
后台保活策略
114、swift 和 OC 都是动态强类型语言
静态类型语言:是一种编译器无法自动检测类型的语言。 java
动态类型语言:是一种编译器能够自动检测类型的语言。
强类型语言:强类型语言也称为强类型定义语言。是一种总是强制类型定义的语言,要求变量的使用要严格符合定义,所有变量都必须先定义后使用。
弱类型语言:弱类型语言也称为弱类型定义语言。与强类型定义相反。像vb,php,js等就属于弱类型语言。
116、REST、HTTP、JSON是什么?
RESTful api Representational State Transfer 资源表现层状态转化架构
(1)每一个URI代表一种资源;
(2)客户端和服务器之间,传递这种资源的某种表现层;
(3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。
117、delegate解决了什么问题,Notification与它有什么不同?
delegate 一对一的通信原理,完成消息回调
Notification 是一对多,与对象之间无需建立直接关系
118、LLVM与Clang的区别?
Clang 是编译器前端
Clang 的作用是 语法、语义分析器,生成中间代码
LLVM是编译器后端
LLVM的作用是代码优化器和后端生成目标程序
从宏观上来说,LLVM包含了Clang
119、Class、objc的区别是什么?
objc为实例对象,表示的是通过类构建的一个实例本身,实例对象是一个objc_object 类型的结构体,包含一个 Class类型的isa属性 用于表明其所属的类
Class为类对象,表示的是类本身
120、不通过继承,代码复用(共享)的方式有哪些
protocol 协议
extension 扩展
runtime
121、编译选项 whole module optmization 优化了什么
编译器对你的代码进行分析的时候,再也不会局限于一个文件当中了,而是整个module。有什么用呢,有了这一特性,编译器可以对你的代码了解得更多,能更好的做好编译工作。
122、在一个app中间有一个button,在你手触摸屏幕点击后,到这个button收到点击事件,中间发生了什么
响应链大概有以下几个步骤
1. 设备将touch到的UITouch和UIEvent对象打包, 放到当前活动的Application的事件队列中
2. 单例的UIApplication会从事件队列中取出触摸事件并传递给单例UIWindow
这中间有 touchbegin、touchend sendaction to等
3. UIWindow使用hitTest:withEvent:方法查找touch操作的所在的视图view
RunLoop这边:
1. 主线程的RunLoop被唤醒
2. 通知Observer,处理Timer和Source 0
3. Springboard接受touch event之后转给App进程中
4. RunLoop处理Source 1,Source1 就会触发回调,并调用_UIApplicationHandleEventQueue() 进行应用内部的分发。
5. RunLoop处理完毕进入睡眠,此前会释放旧的autorelease pool并新建一个autorelease pool
123、main()之前的过程有哪些?
1、系统先读取App的可执行文件(Mach-O文件),获取到dyld的路径,并加载dyld(动态库链接程序)。
2、dyld去初始化运行环境、开启缓存策略(冷热启动)、加载依赖库(读取文件、验证、注册到系统核心)、我们的可执行文件、链接依赖库,并调用每个依赖库的初始化方法。
3、在上一步runtime被初始化,当所有的依赖库初始化后,程序可执行文件进行初始化,这个时候runtime会对项目中的所有类进行类结构初始化,然后调用所有类的+load方法。
1、runtime初始化方法 _objc_init 中最后注册了两个通知:
map_images: 主要是在镜像加载进内容后对其二进制内容进行解析,初始化里面的类结构等
load_images: 主要是调用call_load_methods 按照继承层次依次调用Class的 +load方法 然后是Category的+ load方法。(call_load_methods 调用load 是通过方法地址直接调用的load方法,并不是通过消息机制,这就是为什么分类中的load方法并不会覆盖主类以及其他同主类的分类里的load 方法实现了。)
2、runtime 调用项目中所有的load方法时,所有的类的结构已经初始化了,此时在load方法中可以使用任何类创建实例并给他们发送消息。
4、最后dyld返回main函数地址,main函数被调用。dyld会缓存上一次把信息加载内存的缓存,所以第二次比第一次启动快一点
124、block为什么防止引用循环为什么要外部weak 内部 strong
因为block截获self之后self属于block结构体中的一个由__strong修饰的属性会强引用self, 所以需要使用__weak修饰的weakSelf防止循环引用。
block使用的__strong修饰的weakSelf是为了在block(可以理解为函数)生命周期中self不会提前释放。
strongSelf实质是一个局部变量(在block这个“函数”里面的局部变量),当block执行完毕就会释放自动变量strongSelf,不会对self进行一直进行强引用。
目的是为了让block执行完之前 self不释放
125、iOS 中内省的几个方法?class方法和objc_getClass方法有什么区别?
内省方法
判断对象类型:
-(BOOL) isKindOfClass: 判断是否是这个类或者这个类的子类的实例
-(BOOL) isMemberOfClass: 判断是否是这个类的实例
判断对象or类是否有这个方法
-(BOOL) respondsToSelector: 判读实例是否有这样方法
+(BOOL) instancesRespondToSelector: 判断类是否有这个方法
object_getClass(obj)返回的是obj中的isa指针;而[obj class]则分两种情况:一是当obj为实例对象时,[obj class]中class是实例方法:- (Class)class,返回的obj对象中的isa指针;二是当obj为类对象(包括元类和根类以及根元类)时,调用的是类方法:+ (Class)class,返回的结果为其本身。
126、在运行时创建类的方法objc_allocateClassPair
的方法名尾部为什么是pair(成对的意思)?
另一半就是meta-class,类和元类的创建
127、一个int变量被__block
修饰与否的区别?
没有修饰,被block捕获,是值拷贝。
使用__block修饰,会生成一个结构体,复制int的引用地址。达到修改数据。
129、如何取消系统默认的KVO并手动触发(给KVO的触发设定条件:改变的值符合某个条件时再触发KVO)?
前面我们已经领教了KVO的实现,那如何取消系统的自动KVO呢
实现NSObject的方法,过滤我们需要过滤的key
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([key isEqualToString:@"name"]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
实现自己的KVO逻辑
- (void)setName:(NSString *)name {
[self willChangeValueForKey:@"name"];
_name = name;
[self didChangeValueForKey:@"name"];
}
130、什么是中间人攻击
攻击者在请求和响应传输途中,拦截并篡改内容。
SSL 证书欺骗攻击流程大概如下:
截获客户端与服务器通信的通道
然后在 SSL 建立连接的时候,进行中间人攻击
将自己伪装成客户端,获取到服务器真实有效的 CA 证书(非对称加密的公钥)
将自己伪装成服务器,获取到客服端的之后通信的密钥(对称加密的密钥)
有了证书和密钥就可以监听之后通信的内容了
131、如何用Charles抓HTTPS的包?
前面说过,HTTPS 可以有效防止中间人攻击,那 Charles 是如何抓取 HTTPS 包的呢?
Charles 作为一个“中间人代理”,当浏览器和服务器通信时,Charles接收服务器的证书,但动态生成一张证书发送给浏览器,也就是说Charles作为中间代理在浏览器和服务器之间通信,所以通信的数据可以被Charles拦截并解密。由于Charles更改了证书,浏览器校验不通过会给出安全警告,必须安装Charles的证书后才能进行正常访问。
这里有个大前提,就是客户端信任了 Charles 自己制作的证书,然后导致 Charles 拿到 CA 证书和对称加密的公开密钥;这里我们简单总结下:
1、首先 Charles 假冒了客户端,拿到服务器的 CA 证书
2、然后 Charles 假冒了服务器,给客户端发送了一张自己制作的证书,客户端信任该证书
3、Charles 再次假冒服务器,拿到客户端的对称密钥
4、Charles 再次假冒客户端,将对称密钥加密发送给服务器,让服务器认为这次通信是没问题的,服务器发送成功响应
5、最后 Charles 假冒服务器将成功响应发给客户端
133、了解内联函数么?
OC中使用inline,主要是为了提高函数调用的效率
使用例子:
static inline NSString * imageURLKeyForState(UIControlState state) {
return [NSString stringWithFormat:@"image_%lu", (unsigned long)state];
}
我们通常会发现,inline 会有 static来修饰,表示在当前文件中应用,如 static b, 在其它文件中也可以出现static b.不会导致重名的错误.
inline和函数
1.inline函数避免了普通函数的,在汇编时必须调用call的缺点:取消了函数的参数压栈,减少了调用的开销,提高效率.所以执行速度确比一般函数的执行速度要快.
2.集成了宏的优点,使用时直接用代码替换(像宏一样)
inline和宏
1.避免了宏的缺点:需要预编译.因为inline内联函数也是函数,不需要预编译.
2.编译器在调用一个内联函数时,会首先检查它的参数的类型,保证调用正确。然后进行一系列的相关检查,就像对待任何一个真正的函数一样。这样就消除了它的隐患和局限性。
3.可以使用所在类的保护成员及私有成员。
inline 说明
1.内联函数只是我们向编译器提供的申请,编译器不一定采取inline形式调用函数.
2.内联函数不能承载大量的代码.如果内联函数的函数体过大,编译器会自动放弃内联.
3.内联函数内不允许使用循环语句或开关语句
4.内联函数的定义须在调用之前。
134、列举你知道的线程同步策略?
wait notify 、synchronized代码块
135、iOS SDK中的那些设计模式的使用
KVO 观察者模式
单例模式 NSFileManager 、 UIApplication
代理委托模式:UITableViewDelegate
适配器模式:类似中间件的构建,当接入SDK的时候,适配器模式可以起到隔离并适配的作用
装饰模式: 比如 category
抽象工厂模式:让外部无需知道工厂内部的任何变动
136、面向对象的设计原则
面向对象设计的六个设计原则:
缩写 | 英文名称 | 中文名称 |
---|---|---|
SRP | Single Responsibility Principle | 单一职责原则 |
OCP | Open Close Principle | 开闭原则 |
LSP | Liskov Substitution Principle | 里氏替换原则 |
LoD | Law of Demeter ( Least Knowledge Principle) | 迪米特法则(最少知道原则) |
ISP | Interface Segregation Principle | 接口分离原则 |
DIP | Dependency Inversion Principle | 依赖倒置原则 |
通常所说的SOLID
(上方表格缩写的首字母,从上到下)设计原则没有包含本篇介绍的迪米特法则
137、链表和数组的区别是什么?插入和查询的时间复杂度分别是多少?
数组是一段连续的内存地址,链表是无序的,根据指针来指向下一个内存地址
数组查询指定位置的元素很方便,但是插入和删除需要移动,不如链表方便,查找O(1) 添加删除 O(n)
链表的插入删除元素相对数组较为简单,不需要移动元素,且较为容易实现长度扩充,但是寻找某个元素较为困难; 查找O(n), 添加删除O(1)
138、哈希表是如何实现的?如何解决地址冲突?
散列表(Hash table,也叫哈希表),是根据关键码值(Key-Value)而直接进行访问的数据结构。
通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。查询时间复杂度O(1)
地址冲突处理:
地址冲突是什么?
实际上是由数组和链表的方式组合实现的。我们哈希出来的key会放置在数组的某个位置,这个时候如果有其他元素哈希结果也是这个位置的时候,且两个key并不一样。这就是地址冲突。
1、再哈希,重新进行一次全部的哈希,常见于需要扩容的时候,翻倍扩容并重新哈希
2、当key相同的时候,会再数组后面链接一个链表并把值存入
141、NSCache 和 NSDictionary
如何选择存缓存数据结构
当系统资源将要耗尽时,它可以自动删减缓存。
NSCache还会先行删减“最久未使用的”(lease recently used)对象。
NSCache 并不会“拷贝”键,而是会“保留”它。NSCache对象不拷贝键的原因在于:很多时候,键都是有不支持拷贝操作的对象来充当的。因此,NSCache 不会自动拷贝键,所以说,在健不支持拷贝操作的情况下,该类用起来比字典更方便。
NSCache是线程安全的。而NSDictionary则绝不具备此优势,意思就是:在开发者自己不编写加锁代码的前提下,多个线程便可以同时访问NSCache.
142、iOS图片内存占用由什么决定
图片显示占用内存大小 = 图片的宽度 乘以 图片的高度 乘以 颜色 RGBA 占用的4个字节;
还有色域等会影响
143、索引的作用、索引的优缺点
作用优点
因为索引可以大大提高的性能。
第1通过唯一性索引可以保证数据库表中每一行数据的唯一性。
第2可以大大加快数据的检索速度。这也是创建索引最主要的原因。
缺点
第1 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。
第2 实际上索引也是一张表,该表保存了主键与索引字段.索引需要占物理空间,
144、NSMutableArray的实现原理
https://www.jianshu.com/p/3c77756a86ab
__NSArrayM 用了环形缓冲区 (circular buffer)。这个数据结构相当简单,只是比常规数组或缓冲区复杂点。环形缓冲区的内容能在到达任意一端时绕向另一端。环形缓冲区有一些非常酷的属性。尤其是,除非缓冲区满了,否则在任意一端插入或删除均不会要求移动任何内存。
__NSArrayM 从不减少它的缓冲区大小
145、编译链接
预处理、编译(词法分析、语法分析、静态分析、中间代码生成)、汇编、链接(生成 Executable 可执行文件)
146、NSProxy & NSObject
NSProxy是一个虚类,实现了NSObject协议,可以用于处理weakProxy问题
4种派发机制,而不是两种(静态和动态):
- 内联(inline) (最快) 内联是指在编译期把每一处方法调用替换为直接执行方法内部的代码,可以帮助你避免运行时方法调用的时间开销。
- 静态派发 (Static Dispatch)
- 函数表派发 (Virtual Dispatch)
- 动态派发 (Dynamic Dispatch)(最慢)
OC默认支持动态派发,多态的形式为开发人员提供灵活性,例如重写,但是同样动态的查找需要运行时的开销
消息派发,KVO,runtime方法查找
- swift 要通过 @objc 、 dynamic 桥接OC 的runtime,间接实现,可以通过SIL中间文件判断派发方式
Swift 中的动态派发和 OC 中的动态派发类似,在运行时程序会根据被调用的方法的名字去内存中的方法表中查表,找到方法的实现并执行。
- swift的静态派发
要求方法内部代码对编译器透明,运行时不允许更改,这样编译器才可以保证我们运行的时候不需要查表可以直接跳转到方法代码执行。值类型满足静态派发的要求
内存碎片、 内存对齐
内存碎片产生的原因:
内部碎片:当一个进程不能完全使用分给他的固定内存区域时产生内存碎片
比如多次内存分配后释放了 2 + 1 内存,但是两个内存块不连续,所以你没法申请一个 3 的内存
处理方案,内存页,比如:每两个连续内存组成一个内存页,进程申请的最小单位为页面 多页面一起申请组合使用,但是问题就是内存页会存在资源浪费
外部碎片:某些未分配的连续内存区域太小,不能满足进程的内存分配需求而不能被利用的内存区域
常见的Crash 原因
- KVO问题
- NSNotification线程问题
- 数组越界
- 野指针
- 后台任务超时
- 内存爆出
- 主线程卡顿超阀值
- 死锁
分类的对象方法存储在哪? 类方法呢?
分类中的对象方法依然是存储在类对象中的,同本类对象方法在同一个地方,调用步骤也同调用对象方法一样。如果是类方法的话,也同样是存储在元类对象中。
class_rw_ext
分类的结构体中没有成员变量,所以分类是不允许添加成员变量的。分类中添加的属性,并不会生成成员变量,只会有get set 方法声明,需要自己实现。 Category可以添加属性,编译并不会报错,但是并不会自动生成成员变量及set/get方法。
成员变量是存放在实例对象中的,并且编译的那一刻就已经决定好了。而分类是在运行时才去加载的。那么我们就无法再程序运行时将分类的成员变量中添加到实例对象的结构体中。因此分类中不可以添加成员变量。
拷贝分类方法到类对象,相同的方法怎么办
都会存在方法列表里,但是会调用到分类里的这个方法
Swift部分
1.什么是函数式编程?
函数式编程其实是一种编程思想, 代码写出来只是它的表现形式
在面向对象的编程思想中, 我们将要解决的一个个问题, 抽象成一个个类, 通过给类定义属性和方法, 让类帮助我们解决需要处理的问题.(其实面向对象也叫命令式编程, 就像给对象下一个个命令)
而在函数式编程中, 我们则通过函数描述我们要解决的问题, 以及解决问题需要怎样的方案.
RxSwift
2.swift相对于OC有哪些优点?
1、swift语法简单易读、代码更少,更加清晰、易于维护
2、更加安全,optional的使用更加考验程序员对代码安全的掌控
3、泛型、结构体、枚举都很强大
4、函数为一等公民,便捷的函数式编程
5、有命名空间 基于module
6、类型判断
oc的优点、运行时
3.什么是泛型,swift在哪些地方使用了泛型?
泛型(generic)可以使我们在程序代码中定义一些可变的部分,在运行的时候指定。使用泛型可以最大限度地重用代码、保护类型的安全以及提高性能。
例如 optional 中的 map、flatMap 、?? (泛型加逃逸闭包的方式,做三目运算)
4.defer、guard的作用?
defer 包体中的内容一定会在离开作用域的时候执行
guard 过滤器,拦截器
5.swift语法糖 ?!的本质(实现原理)
? 为optional的语法糖
Optional< T > 是一个包含了 nil 和 普通类型的枚举,确保使用者在变量为nil的情况下的处理
! 为optional 强制解包 的语法糖
6.举例swift中模式匹配的作用?
模式匹配: 在switch中体现最明显
通配符模式: _
标识符模式:let i = 1
值绑定模式:case .Student(let name) 或者 case let .Student(name)
元祖模式:case (let code, _)
可选模式:if case let x? = someOptional { }
类型转换模式:case is Int: 或者 case let n as String:
表达式模式:范围匹配 case (0..<2) case(0...2, 2...4)
条件句中使用where: case (let age) where age > 30
if case let:if case let .Student(name) = xiaoming { }
for case let: for case let x in array where x > 10 {} 或者 for x in array where x > 10
7.swift中closure与OC中block的区别?
1、closure是匿名函数、block是一个结构体对象
2、都能捕获变量
3、closure通过逃逸闭包来在block内部修改变量,block 通过 __block 修饰符
8.什么是capture list,举例说明用处?
捕获列表
weak unowned
9.swift中private与fileprivate的区别?
private的作用域被约束与被定义的当前类作用域,fileprivate作用域是整个文件
10.Set 独有的方法有哪些?
intersect(_:)// 根据两个集合中都包含的值创建的一个新的集合
exclusiveOr(_:) // 根据只在一个集合中但不在两个集合中的值创建一个新的集合
union(_:) // 根据两个集合的值创建一个新的集合
subtract(_:) //根据不在该集合中的值创建一个新的集合
isSubsetOf(_:) //判断一个集合中的值是否也被包含在另外一个集合中
isSupersetOf(_:) //判断一个集合中包含的值是否含有另一个集合中所有的值
isStrictSubsetOf(:) isStrictSupersetOf(:) //判断一个集合是否是另外一个集合的子集合或者父集合并且和特定集合不相等
isDisjointWith(_:) //判断两个集合是否不含有相同的值
11.实现一个 min 函数,返回两个元素较小的元素
func minNum<T: Comparable>(a: T, b: T) -> T {
return a > b ? a : b
}
12.map、filter、reduce 的作用
1、map 是Array类的一个方法,我们可以使用它来对数组的每个元素进行转换
let intArray = [1, 3, 5]
let stringArr = intArray.map {
return "\($0)"
}
// ["1", "3", "5"]
2、filter 用于选择数组元素中满足某种条件的元素
let filterArr = intArray.filter {
return $0 > 1
}
//[3, 5]
3、reduce 把数组元素组合计算为一个值
let result = intArray.reduce(0) {
return $0 + $1
}
//9
13.map 与 flatmap 的区别
1、map 可以对一个集合类型的所有元素做一个映射操作
2、和map 不同,flatmap 在之前版本有两个定义,分别是:
func flatMap(transform: (Self.Generator.Element) throws -> T?) -> [T]
func flatMap(transform: (Self.Generator.Element) -> S) -> [S.Generator.Element]
swift 4.1 废弃后改为
func flatMap(transform: (Self.Generator.Element) throws -> Sequence) -> [Sequence.Element]
func flatMap<U>(_ transform: (Wrapped) throws -> U?) rethrows -> U?
1) flatMap的第一个作用和map一样,对一个集合类型的所有元素做一个映射操作,但是可以过滤为nil的情况
例如:
let array = [1,2,5,6,7,nil]
let array_map = array.map { $0 }
//[Optional(1), Optional(2), Optional(5), Optional(6), Optional(7), nil]
let array_flatmap = array_map.flatMap { $0 }
//[1, 2, 5, 6, 7]
2) 第二种情况可以进行“降维”操作
let array = [["1", "2"],["3", "4"]]
let array_map = array.map { $0 }
//[["1", "2"], ["3", "4"]]
let array_flatmap = array_map.flatMap { $0 }
//["1", "2", "3", "4"]
14.什么是 copy on write
copy on write, 写时复制,简称COW,它通过浅拷贝(shallow copy)只复制引用而避免复制值;当的确需要进行写入操作时,首先进行值拷贝,在对拷贝后的值执行写入操作,这样减少了无谓的复制耗时。
15.如何获取当前代码的函数名和行号
#function
#line
#file
#column
16.如何声明一个只能被类 conform 的 protocol
protocol:class
17.String 与 NSString 的关系与区别
两者可以随意转换
String为值类型,拷贝赋值需要值拷贝
NSString 传递指针
20.如何截取 String 的某段字符串
substring 已废弃
let star = str.index(str.startIndex, offsetBy: 0)
let end = str.index(str.startIndex, offsetBy: 4)
let substr = str[star..<end]
21.throws 和 rethrows 的用法与作用
当闭包参数会抛出异常时 使用throws
同时外部方法体返回结果需要 rethrows 异常
rethrows 可以用 throws 替换, 反过来不行
22.try? 和 try!是什么意思
不处理错误,抛出异常函数时, 如果函数抛出异常, 则返回 nil, 否则返回函数返回值的可选值,
保证不会出现错误 强制解,抛出异常的时候崩溃, 否则则返会函数返回值
23.associatedtype 的作用
关联类型,关联类型为协议中的某个类型提供了一个别名,其代表的真实类型在实现者中定义
//协议,使用关联类型
protocol TableViewCell {
associatedtype T
func updateCell(_ data: T)
}
//遵守TableViewCell
class MyTableViewCell: UITableViewCell, TableViewCell {
typealias T = Model
func updateCell(_ data: Model) {
// do something ...
}
}
24.public 和 open 的区别
是否可以外部继承
25.声明一个只有一个参数没有返回值闭包的别名
typealias MyBlock = (Int) -> (Void)
26.Self 的使用场景
例如:协议定义的时候,如果需要使用到实现者的上下文怎么办? 我们并不知道谁会实现自己
这个时候可以使用Self进行指代
27.dynamic 的作用
swift中的函数是静态调用,静态调用的方式会更快,但是静态调用的时候没救不能从字符串查找到对于的方法地址,这样 与OC交互的时候,OC动态查找方法就会找不到,这个时候就可以通过使用 dynamic 标记来告诉编译器,这个方法要被动态调用的
swift中如果KVO监听属性,那么属性就需要 dynamic 来标记
28.什么时候使用 @objc
与OC 的交互部分
KOV 监听、动态方法查找等都需要
协议可选方法等
29.Optional(可选型) 是用什么实现的
枚举 一个 为nil,一个为属性值
30.如何自定义下标获取
extension Demo {
subscript(index: Int) -> Int {
get {
// 返回一个适当的 Int 类型的值
}
set(newValue) {
// 执行适当的赋值操作
}
}
}
31.inout 的作用
让输入参数可变 类似__block 的作用
32.Error 如果要兼容 NSError 需要做什么操作
Error是一个协议, swift中的Error 都是enum, 可以转 NSError
如果需要Error有NSError的功能,实现 LocalizedError CustomNSError 协议
33.下面的代码都用了哪些语法糖
[1, 2, 3].map{ $0 * 2 }
array语法糖
尾部闭包语法糖
$0
34.什么是高阶函数
map、flatMap、filter、reduce?
35.下面的代码会不会崩溃,说出原因
var mutableArray = [1,2,3]
for _ in mutableArray {
mutableArray.removeLast()
}
不会,值类型
36.给集合中元素是字符串的类型增加一个扩展方法,应该怎么声明
extension Array where Element == String { }
37.定义静态方法时关键字 static 和 class 有什么区别
非class类型 一般 统一用 static 例如 枚举 结构体
protocol中 使用 static ,实现协议的 枚举 结构体 用 static
class 中使用 class static 都可以
class可以被子类复写 static不行
38.一个 Sequence 的索引是不是一定从 0 开始?
不是。
ArraySlice是Sequence的子类,ArraySlice就不是
39.数组都实现了哪些协议
Decodable Encodable Equatable Hashable CustomStringConvertible, CustomDebugStringConvertible RandomAccessCollection, MutableCollection RangeReplaceableCollection CustomReflectable ExpressibleByArrayLiteral
40.如何自定义模式匹配
infix operator =~
func =~ (str: String, pattern: String) -> Bool {
}
infix、 prefix、 postfix 用于自定义表达式的声明, 分别表示 中缀、前缀、后缀
41.autoclosure 的作用
自动闭包,将参数自动封装为闭包参数
42.下面代码中 mutating 的作用是什么
struct Person {
var name: String {
mutating get {
return store
}
}
}
结构体中的 属性可能发生改变
43.如何让自定义对象支持字面量初始化
ExpressibleByArrayLiteral
ExpressibleByStringLiteral
44.为什么数组索引越界会崩溃,而字典用下标取值时 key 没有对应值的话返回的是 nil 不会崩溃。
struct Array<Element> {
subscript(index: Int) -> Element
}
struct Dictionary<Key: Hashable, Value> {
subscript(key: Key) -> Value?
}
字典是无序的,数组是有序的,所以字典查询做的 键值对查询返回的是一个可选值
45.一个函数的参数类型只要是数字(Int、Float)都可以,要怎么表示。
Int、Float 都有一个协议
func myMethod<T>(_ value: T) where T: Numeric {
print(value + 1)
}
或者 ExpressibleByIntegerLiteral 协议也行
46.Swift的静态派发
很显然静态派发是一种更高效的方法,因为静态派发免去了查表操作。
不过静态派发是有条件的,方法内部的代码必须对编译器透明,并且在运行时不能被更改,这样编译器才能帮助我们。
1、Swift 中的值类型不能被继承,也就是说值类型的方法实现不能被修改或者被复写,因此值类型的方法满足静态派发的要求。
2、默认静态派发,如果需要满足动态派发,需要 dynamic修饰
3、OC中的消息机制就是动态派发
动态派发
在运行时程序会根据被调用的方法的名字去内存中的方法表中查表,找到方法的实现并执行
47.Swift有哪些修饰符
open、public 、internal、fileprivate、private
open 跨模块可见,可修改可继承
public 跨模块可见,不可修改
internal 模块内可见级别 默认
fileprivate 文件内私有
private 类中私有
48、实现一个函数,输入是任一整数,输出要返回输入的整数 + 2
func plusTwo(one: Int) -> (Int) -> Int {
return { (two: Int) in return two + one }
}
plusTwo(one: 4)(2)
49、Swift 到底是面向对象还是函数式的编程语言?
Swift 既是面向对象的,又是函数式的编程语言。
说 Swift 是 Object-oriented,是因为 Swift 支持类的封装、继承、和多态,从这点上来看与 Java 这类纯面向对象的语言几乎毫无差别。
说 Swift 是函数式编程语言,是因为 Swift 支持 map, reduce, filter, flatmap 这类去除中间状态、数学函数式的方法,更加强调运算结果而不是中间过程。
50、class 和 struct 的区别
class 引用类型,可以继承、多态,通过引用计数来管理
struct 是值类型,不通过引用计数来管理