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

一、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]
    }

推荐文集

收录:原文地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值