美文网首页iOS 面试面试题iOS面试题合集(上)
iOS开发最新:各大厂面试题(二)

iOS开发最新:各大厂面试题(二)

作者: iOS猿_员 | 来源:发表于2020-08-31 15:36 被阅读0次

    一、iOS程序内存分为几个区

    iOS内存分为5大区域

    1. 栈区:编译器自动分配并释放,存放函数的参数值,局部变量等。栈是系统数据结构,对应线程/进程是唯一的。
    2. 堆区:由程序员分配和释放,如果程序员不释放,程序结束时,可能会由操作系统回收 ,比如在iOSalloc 都是存放在堆中。
    3. 全局区:全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量存放在一块区域,未初始化的全局变量和静态变量在相邻的另一块区域,程序结束后由系统释放。
    4. 文字常量区:存放常量字符串,程序结束后由系统释放程序结束释放。
    5. 代码区:存放函数的二进制代码

    二、iOS程序内存的每个分区怎么存储(举例说明)

    • 栈区:存放的局部变量、先进后出、一旦出了作用域就会被销毁;函数跳转地址,现场保护等,内存地址从高到低分配。
    • 堆区:堆区的地址是从低到高分配,通过程序员通过alloc手动分配。
    • 全局区:包含两个部分,未初始化区,初始化区域。全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域;

    代码区存放于低地址,栈区存放于高地址。区与区之间并不是连续的。堆区的内存是应用程序共享的,堆中的内存分配是系统负责的;当引用计数为0的时候,系统会回收该内存。

    三、block一般存在哪里(分ARC和MRC)

    • MRC 下,Block 默认是分配在栈上的,除非进行显式执行的copy方法,只要block没有引用外部的局部变量,block放在全局区里面
    • ARC的中,对象默认是用__strong修饰的,所以大部分情况下编译器都会将 block从栈自动复制到堆上。有一个特殊情况,如果仅仅定义了block没有赋值给变量的话,仍是在栈上。这种情况下随着作用域结束,block将会销毁回收。

    四、代码区存储的是什么?

    代码区存放的是程序中函数编译后的CPU指令

    五、进程和线程的理解(从资源分配进行理解)

    进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性

    1. 一个程序至少有一个进程,一个进程至少有一个线程。
    2. 线程的划分尺度小于进程,使得多线程程序的并发性高。

    线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

    六、进程线程的内存分配和管理

    当程序被运行时,需要将可执行文件加载到内存,在内存中的可执行文件形成进程,一个进程(文件)可以同时存在多个进程(内存)
    运行程序的时候,需要将可执行文件加载到内存中,形成进程。每个进程占据了一块独立的内存区域,这块内存区域又划分成不同的区域,从低地址到高地址依次为:代码区、只读常量区、全局区/数据区、BSS段、堆区、栈区 。

    7、多线程中哪些内存是共享哪些独占

    多线程中,线程之间有共享资源和独占资源。
    共享资源有:

    1. 进程申请的堆内存
    2. 进程打开的文件描述符
    3. 进程的全局数据
    4. 进程id,进程组id
    5. 进程目录
    6. 信号处理器

    独占资源有:

    1. 线程ID
    2. 寄存器组的值:每个线程有自己不同的运行线索,当从一个线 程切换到另一个线程上 时,必须将原有的线程的寄存器集合的状态保存,以便将来该线程在被重新切换到时能得以恢复。
    3. 线程堆栈
    4. 错误返回码
    5. 信号屏蔽码
    6. 线程的优先级

    八、实现多线程同步的方式

    同步方式有互斥锁(mutex),条件变量(condition variable)和读写锁(reader-writer lock)来同步资源
    iOS 互斥锁有@synchronized
    条件信号量dispatch_semaphore_t
    NSConditionLock
    读写锁 pthread_rwlock

    九、两个异步子线程输出字符串,主线程前后也输出一个字符串,顺序如何,为什么是这样的?

    先执行主线程种操作,在执行一步子线程操作,子线程在分配时遵循,新建,就续,运行,阻塞,死亡这个生命周期,而主线程已经运行状态,所以会先运行主线程的操作,操作遵循FIFO的模式进行。子线程如果没有特殊的优先级指定,默认处于同一优先级,所以也遵循FIFO的模式运行。

    十、任务A,B,C先执行A和B再执行C可以怎么实现(group,条件锁,barrier)

    1. group 通过创建信号量访问资源数量为1,然后通过waitsign顺序执行group内线程。

    2. 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()
                   }
    

    3. barrier 允许在一个并发队列中创建一个同步点。当在并发队列中遇到一个barrier, 他会延迟执行barrierblock,等待所有在barrier之前提交的blocks执行结束。 这时,barrier block自己开始执行。

    十一、属性的修饰关键词有哪些

    见上一篇 面试题集

    十二、atomicnonatomic的区别,如果是你觉得该怎么实现atomic一样的效果

    主要区别在于atomic保证 getset 操作的完整性。
    可以堆getset操作加锁,实现atomic一样的功能。

    十三、atomic 一定是线程安全的吗?什么情况下是不安全的?

    不一定是线程安全的,只保证了getset操作安全,但是不保证资源线程的安全。

    如果一个线程正在getter 或者 setter时,有另外一个线程同时对该属性进行release操作,如果release先完成,会造成crash

    十四、copy常用来修饰什么,为什么?

    常常用来修饰NSString,使用copy修饰之后,即使属性拷贝来自可变字符串,也会被深拷贝成不可变字符串,也就是源字符串修改之后不会影响到属性字符串,增强了代码的健壮性。

    十五、weakassign 的区别

    见上一篇 面试题集

    十六、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:也会被调用。

    十九、旋转数组找最小数(算法)

    思路:

    1. 从头到尾遍历数组一次,就能找出最小的元素,时间复杂度显然是O(n)
    2. 通过二分查找的方式,时间复杂度为O(logn)观察一下数组的特性,首先递增(称为递增a),然后突然下降到最小值,然后再递增(称为递增b)。当然还有一种特殊情况,就是数组递增,中间没有下降,即旋转元素个数为0。对于一般的情况,假设A为输入数组,leftright 为数组左右边界的坐标,考察中间位置的值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]
        }
    

    推荐文集

    收录:原文地址

    相关文章

      网友评论

        本文标题:iOS开发最新:各大厂面试题(二)

        本文链接:https://www.haomeiwen.com/subject/nwiusktx.html