2020 iOS 面试题集 2
iOS程序内存分为几个区
iOS内存分为5大区域
- 栈区:编译器自动分配并释放,存放函数的参数值,局部变量等。栈是系统数据结构,对应线程/进程是唯一的。
- 堆区:由程序员分配和释放,如果程序员不释放,程序结束时,可能会由操作系统回收 ,比如在ios 中 alloc 都是存放在堆中。
- 全局区:全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量存放在一块区域,未初始化的全局变量和静态变量在相邻的另一块区域,程序结束后由系统释放。
- 文字常量区:存放常量字符串,程序结束后由系统释放程序结束释放。
- 代码区:存放函数的二进制代码
iOS程序内存的每个分区怎么存储(举例说明)
- 栈区:存放的局部变量、先进后出、一旦出了作用域就会被销毁;函数跳转地址,现场保护等,内存地址从高到低分配。
- 堆区:堆区的地址是从低到高分配,通过程序员通过alloc手动分配。
- 全局区:包含两个部分,未初始化区,初始化区域。全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域;
代码区存放于低地址,栈区存放于高地址。区与区之间并不是连续的。堆区的内存是应用程序共享的,堆中的内存分配是系统负责的;当引用计数为0的时候,系统会回收该内存。
block
一般存在哪里(分ARC
和MRC
)
- 在
MRC
下,Block
默认是分配在栈上的,除非进行显式执行的copy
方法,只要block没有引用外部的局部变量,block放在全局区里面 - 在
ARC
的中,对象默认是用__strong修饰的,所以大部分情况下编译器都会将 block从栈自动复制到堆上。有一个特殊情况,如果仅仅定义了block没有赋值给变量的话,仍是在栈上。这种情况下随着作用域结束,block将会销毁回收。
代码区存储的是什么?
代码区存放的是程序中函数编译后的CPU指令
进程和线程的理解(从资源分配进行理解)
进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性
- 一个程序至少有一个进程,一个进程至少有一个线程。
- 线程的划分尺度小于进程,使得多线程程序的并发性高。
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
进程线程的内存分配和管理
当程序被运行时,需要将可执行文件加载到内存,在内存中的可执行文件形成进程,一个进程(文件)可以同时存在多个进程(内存)
运行程序的时候,需要将可执行文件加载到内存中,形成进程。每个进程占据了一块独立的内存区域,这块内存区域又划分成不同的区域,从低地址到高地址依次为:代码区、只读常量区、全局区/数据区、BSS段、堆区、栈区 。
多线程中哪些内存是共享哪些独占
多线程中,线程之间有共享资源和独占资源。
共享资源有:
- 进程申请的堆内存
- 进程打开的文件描述符
- 进程的全局数据
- 进程id,进程组id
- 进程目录
- 信号处理器
独占资源有:
- 线程ID
- 寄存器组的值:每个线程有自己不同的运行线索,当从一个线 程切换到另一个线程上 时,必须将原有的线程的寄存器集合的状态保存,以便将来该线程在被重新切换到时能得以恢复。
- 线程堆栈
- 错误返回码
- 信号屏蔽码
- 线程的优先级
实现多线程同步的方式
同步方式有互斥锁(mutex),条件变量(condition variable)和读写锁(reader-writer lock)来同步资源
iOS 互斥锁有@synchronized
条件信号量dispatch_semaphore_t
NSConditionLock
读写锁 pthread_rwlock
两个异步子线程输出字符串,主线程前后也输出一个字符串,顺序如何,为什么是这样的?
先执行主线程种操作,在执行一步子线程操作,子线程在分配时遵循,新建,就续,运行,阻塞,死亡这个生命周期,而主线程已经运行状态,所以会先运行主线程的操作,操作遵循FIFO的模式进行。子线程如果没有特殊的优先级指定,默认处于同一优先级,所以也遵循FIFO的模式运行。
任务A,B,C先执行A和B再执行C可以怎么实现(group,条件锁,barrier)
-
group 通过创建信号量访问资源数量为1,然后通过wait和sign顺序执行group内线程。
-
NSConditionLock,通过控制创建条件和解锁条件,顺序执行线程。
let lock = NSConditionLock.init(condition: 3)
DispatchQueue.global().async {
lock.lock(whenCondition: 3)
print("A")
lock.unlock(withCondition: 2)
}
DispatchQueue.global().async {
lock.lock(whenCondition: 2)
print("B")
lock.unlock(withCondition: 1)
}
DispatchQueue.global().async {
lock.lock(whenCondition: 1)
print("C")
lock.unlock()
}
- barrier 允许在一个并发队列中创建一个同步点。当在并发队列中遇到一个barrier, 他会延迟执行barrier的block,等待所有在barrier之前提交的blocks执行结束。 这时,barrier block自己开始执行。
属性的修饰关键词有哪些
atomic
和nonatomic
的区别,如果是你觉得该怎么实现atomic
一样的效果
主要区别在于atomic
保证 get、set 操作的完整性。
可以堆get,set操作加锁,实现atomic一样的功能。
atomic
一定是线程安全的吗?什么情况下是不安全的?
不一定是线程安全的,只保证了get,set操作安全,但是不保证资源线程的安全。
如果一个线程正在getter 或者 setter时,有另外一
个线程同时对该属性进行release操作,如果release
先完成,会造成crash
copy
常用来修饰什么,为什么?
常常用来修饰NSString
,使用copy修饰之后,即使属性拷贝来自可变字符串,也会被深拷贝成不可变字符串,也就是源字符串修改之后不会影响到属性字符串,增强了代码的健壮性。
weak
和assign
的区别
delegate
你一般用什么修饰(回答weak
,为什么?可以用`assign吗)
循环引用(weak,用assign修饰block可以吗)
KVO
的实现原理(runtime)或者你要实现KVO你会怎么做
KVO
运用了一个isa-swizzling
技术,isa-swizzling
就是混合指针机制,将2个对象的isa指针互相调换。
当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的 setter
方法。派生类在被重写的setter
方法内实现真正的通知机制。
每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将isa
指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter
方法
键值观察通知依赖于NSObject
的两个方法: willChangeValueForKey:
和 didChangevlueForKey:
在一个被观察属性发生改变之前, willChangeValueForKey:
一定会被调用,这就 会记录旧的值。而当改变发生后,didChangeValueForKey:
会被调用,继而 observeValueForKey:ofObject:change:context:
也会被调用。
旋转数组找最小数(算法)
思路:
- 从头到尾遍历数组一次,就能找出最小的元素,时间复杂度显然是O(n)。
- 通过二分查找的方式,时间复杂度为O(logn)观察一下数组的特性,首先递增(称为递增a),然后突然下降到最小值,然后再递增(称为递增b)。当然还有一种特殊情况,就是数组递增,中间没有下降,即旋转元素个数为0。对于一般的情况,假设A为输入数组,left 和 right 为数组左右边界的坐标,考察中间位置的值A[mid] ,如果A[mid] <= A[right],表明处于递增b,调整右边界 right = mid;如果A[mid] >= A[left],表明处于递增a,因此调整左边界left = mid。当左右边界相邻时,较小的一个就是数组的最小值。其实,对于一般情况,右边界所指的元素为最小值。对于特殊情况,即旋转个数为0。按照上述算法,右边界会不断减少,直到与左边界相邻。这时左边界所指的元素为最小值。
//# Swift 实现
func findMin( pArray:[Int]) -> Int{
let len = pArray.count
if len <= 0 { return 0 };
var left:Int = 0
var right:Int = len - 1
var mid:Int = 0
while(right - left != 1)
{
mid = left + ((right - left)>>1);
if pArray[right] >= pArray[mid] {
right = mid;
} else if pArray[left] <= pArray[mid] {
left = mid
}
}
return pArray[right] > pArray[left] ? pArray[left] : pArray[right]
}