面试题

285 阅读20分钟

OC基础

说OC本质?

我们平时编写的oc代码,底层都是c和c++.所以oc中的对象最终都转换成了c和c++中的结构体,oc中调用方法,实质是转换成了发送消息objc_msgSend

NSObject本质?

NSObject实际是一个objc_class类型的结构体,继承于objc_object,它里面的数据结构要分arm64之前和之后,之前是objc_object里面有一个isa指针,之后对这个isa指针进行了优化,是一个共用体,利用位域来存储类更多的数据,除了这个isa以外,还有cache方法缓存列表,父类指针superclass,还有个class_rw_t里面存储着方法列表,协议列表,属性列表,都是二维数组可进行读写操作

优化过的isa共用体一共占64个字节,里面包含了9个参数,比如是否有被弱引用指向referenced,是否有关联对象has_ass,引用计数的存储extra_t.has_sidetabble,等等

class_rw_t和class_ro_t区别?

里面都装载着类的属性方法和协议,ro_t还装载了成员变量,

class_rw_t是程序运行时确定的,class_ro_t是程序编译时确定的,所以当在编译器 class_data指向的是ro_t,在运行时指向的是rw_t,运行时会拷贝一份ro_t,然后将类的分类数据以倒数形式插入到方法列表等中

什么时候weak关键字和assign有什么不同?

Weak关键字表明的是一种非持有关系,被weak修饰的对象不会引起引用计数的增加,一般在遇到循环引问题上会使用weak修饰其中一方,断掉互相引用的循环链,weak是用来修饰oc对象的,assign是用来修饰基本计数类型,如果用assgin修饰oc对象会产生悬浮指针

深拷贝和浅拷贝?

深拷贝是指 新开辟了一份内存并将值拷贝过去,是两个不同的对象

浅拷贝是指拷贝一份指针,相当于2个指针指向同一块内存

协议和分类中如何使用属性?

协议中的属性是要遵循协议的类去实现setter方法getter方法 ,生成成员变量

分类中使用属性要用关联对象

属性的本质是什么,是如何生成ivar getter和setter方法的?

属性的本质 = 成员变量+setter+getter方法

属性用来封装对象中的数据的,对象中用成员变量来保存各种数据,用setter方法来进行写入变量值,gette方法读取变量值

当完成属性定义后,编译器会帮我们生成成员变量,生命并实现setter和getter方法

如何自己实现,用@synsizeed 生命成员变量,实现setter方法和getter方法 

load和initialized方法的区别?

  • load函数 load方法:在加载类的时候调用,在main函数之前调用,每个类都会调用一次,而且只调用一次不会调用父类的load函数

执行顺序是:

类优先于分类执行

有继承关系的类是先执行父类在执行子类,,无继承关系的类按照参与编译的顺序执行

分类是按照参与编译的顺序执行

编译顺序可查看Compile source文件

  • initialized函数

initialized是类第一次使用之前调用,可能会掉多次,如果子类initialized未实现 会调用父类initialized

执行顺序是

父类优先于子类执行

分类优先于类,多个分类执行后编译的分类

判断对象是否相等?

isEqual  判断的是对象的类型是否相等 对象的值是否相等

== 是判断指针是否相等

isEquaString 是判断字符串是否相等

什么是单例?

在全局中只会初始化一次的实例变量,会一直存储在内存中,直到app程序退出 系统会回收这部分内存 可以用dispatch once 函数来创建一个单例,单例一般都会在类中提供一个类方法来获取创建

weak和strong的区别?

 weak 是一种非拥有关系 ,不会对对象的引用计数+1  

strong 会对对象的引用计数+1

举例说明分别修饰字符串时,对引用计数的影响和指向对象赋值发送改变后对自身的影响

多线程

进程和线程的理解?区别和关系?

进程:可以举个例子,程序中运行的程序 就是一个进程,比如 微信,QQ,进程之间是独立的,互补影响的,他们有一块专门且受保护的内存

线程:线程是进程的最小执行单元,要想在进程中执行任务,是要再线程中执行的,每个进程至少有一条线程,程序启动时,默认开启了一条线程,称为主线程,

关系:

可以把整个应用程序比做商场,进程可以比作商场里的门店,线程可以比作门店里的员工

每个进程是独立的,想在进程执行任务要在开启线程在线程中执行,线程不能单独运行他要依附进程

区别:

一条线程崩溃会导致整个线程也挂掉

当前进程挂掉不会影响其他进程

说说队列?

队列是一种线性数据结构,它遵循先进先出的原则,在尾部追加数据,在头部取出数据

队列和线程的关系?

他们没有什么直接的关系,

队列是装载任务的,线程是执行任务的

多线程里的队列分同步队列和并发队列

同步队列:就是任务要一个接着一个执行

并发队列:任务可以同时执行

线程与runloop的关系?

首先每个runloop都有一条且唯一的一条与之对应的线程**

程序启动时开启了runloop会默认开启一条线程 也就是主线程

在子线程,runloop是懒加载模式,在获取runloop时 进行创建runloop

Run;oop是用来管理线程的,runloop会唤醒app去执行线程中的任务,线程中的任务执行完毕,runloop会进入休眠状态

影响执行任务速率的因素?

cpu调度的速度

线程执行的速度

任务的复杂度

任务的优先级

队列的种类

说说你对多线程的理解?多线程的原理?

同一时间程序只能运行一条线程.多线程就是cpu快速的在多条线程之间进行切换,给人造成一种错觉,是在同时执行多个任务

多线程的优点和缺点?及多线程的意义?

先说说优点:

适当的开启多个线程可以提高程序的性能和cpu的利用率

线程执行完任务会进行销毁

缺点:

开启线程会占用内存空间,如果开启大量的线程势必会有大量的内存开销,降低程序的性能,多线程出现资争夺同一块内存资源的情况 

说下多线程的生命周期?

新建:新建一个线程对象

就绪:给线程发送start消息,将线程放入可调度的线程池

运行:在可调度的运行池中调度线程执行任务,

阻塞:当线程满足一定情况的时候,线程可能会进行休眠进行阻塞,这时会将线程从可调度的线程池中将线程取出来,等待阻塞结束会在将线程放入可调度的线程池

死亡:正常死亡,任务执行完毕,线程销毁,非正常死亡,当线程满足某个条件时,可在线程内部进行终止执行任务

说下线程池的原理?

当线程池的大小 小于核心线程池的大小时,会创建新的线程

当线程池的大小大于或等于核心线程池的大小时,

会判断线程中的队列是否饱和,如果未饱和,push任务到队列中

如果未饱和并且,线程池的大小等于核心线程池的大小会按照线程饱和策略来处理任务

如果线程池的大小大于核心线程池的大小,会新建线程来执行任务

说说多线程的方案?NSOperation和GCD的区别?

pThread 基于c语言, 要手动管理线程的生命周期,几乎用不到

NSThread 基于oc语言,要手动管理线程的生命周期,可直接操作线程的开启和销毁

GCD一般用来取代NSthread基于c语言, 自动管理线程的生命周期,任务执行完毕,线程销毁

NSOperation 是基于GCD实现的,在GCD层面上又封装了一些方法,也是自动管理线程的生命周期

区别:

NSOperation的功能更多,他是基于GCD又封装了一层,他可以对队列的任务 进行顺序调整和 彼此之间的依赖,可以观察线程执行任务的进度

GCD更接近底层实现,所以他的执行效率更高,并发速度更快,如果在异步执行任务中,没有对任务的顺序性和依赖性有要求的话,建议使用GCD更好一点

线程间的通信?

说几个比较熟悉的吧

可以用performSelector 某个线程指定某个线程发送消息

通过全局变量,多个线程访问一块内存,但是要加入锁的操作,否则会进行资源争夺

条件锁,当线程满足某个条件时进行执行

Runloop中的source,source是事件驱动,要有任务响应时,会唤醒线程执行任务,任务执行完毕会进入休眠状态

队列组

异步执行任务,等任务都执行完毕,可获取通知,在主队列上可进行刷新数据,队列组可设置等待的时间

有2种方式,

使用异步全局队列

使用group_enter和group_levae 这个要成对出现,否则会崩溃

应用场景:多个接口请求后,刷新页面

栅栏函数,异步栅栏函数和同步栅栏函数?

在多异步并发队列执行任务时,可以栅栏函数来分割任务的执行,在栅栏函数前面的队列任务先执行,后面的要等栅栏函数执行完毕在执行,栅栏函数要配合自己创建的并发队列,使用系统的全局队列是无效的

异步栅栏函数:可以开启子线程执行任务,且执行的任务是无序的

同步栅栏函数:在当前线程执行任务,执行的任务是有序的

线程同步方案

首先:多线程运行中,有可能会导致并发执行任务在同一时间访问都一块数据资源,这样会导致数据错乱

解决方案:

1:锁

自旋锁:等待锁的线程会一直处于忙等的状态,比较消耗资源,比较适合等待时间较短的线程任务

  • dispatch_semaphore_t  信号量,设置并发的线程数,当超过这个设置的线程数时会进行等待,执行完任务会释放信号量

     

2:互斥锁:等待锁的线程会进入休眠状态,这个比较适合等待时间较长的线程任务

  • pthread_mutex_t;

  • NSLock; //是对上面的封装

  • os_unfair_lock;

  • @synchronized:性能价低

  • 读写锁

  • 递归锁和条件锁

     

  3:GCD的异步串行队列

线程中的死锁遇到过吗,有哪几种?

出现死锁这种情况主要是因为队列之间的任务互相等待

  • 比如在主队列上同步执行任务

  • 同步串行队列嵌套一个同步串行队列

因为同步执行任务,是要将当前的任务执行完才能去执行队列里的任务,而当前任务执行完是要将队列里的任务执行完,他们之间会互相等待对方执行完毕,所以造成了死锁

Runloop

说说runloop是怎样响应用户操作的,具体流程是什么?

程序启动时会开启一个runloop,runloop中会注册一个souce1,这个source1是基于mach-port实现的,是用来接收系统进程级别的事件,当屏幕接收到一个触摸或者其他操作时,mach-port会分发给需要处理的app,此时app中的souce1会收到回调,唤醒线程,并且将事件包装成UIEvent事件传递给runlooop中的souce0来处理,进行往下层分发,找到第一响应者

内存管理

说下oc内存管理机制吧?

在arm64架构之后提出了一个叫TagPoiner方法 是用来管理 nsnumer nsstring nsdate等小数据对象的内存的 

其他的oc对象的内存管理采用的是 动态分配内存 维护内部引用计数和加入自动释放池 的方式

对象以alllco new copy初始化的对象采用引用计数机制

维护引用计数有mrc 手动引用计数 arc自动引用计数机制 

想持有某个对象就让这个对象引用计数+1,不想持有就-1,当引用计数为0的时候,创建一个对象引用计数默认为1,retain会让引用计数+1 releaseh会让引用计数-1

自动释放池 :用遍历构造器创建的对象,会想对象放到自动释放池中管理内存生命周期,当池子销毁的时候里面的对象被释放

自动释放池了解吗 ,说下数据结构,如何进行管理对象内存的 何时释放的?

自动释放池的数据结构,他底层是一个结构体,结构体里有一个构造函数和一个析构函数,这两个函数最终操作的是autorealsepage,而这个page在自动释放池中是以双联变形式串联起来的,可以说自动释放池就是一page为节点,双链表形式像一整串page串联起来的数据结构

autorealsepage 的数据结构可以说下吗?

它一共占4096个字节,它里面的参数占56个字节,其中包括父节点 子节点,当前线程 next指针等

主线程runloop注册了2个abserver

一个监听即将进入runloop状态

会调用push 会将一个哨兵对象入栈并返回这个指针地址

另一个observer监听即将进入休眠和退出事件

会调用pop操作,并传入push操作返回的哨兵对象指针,依次给自动释放池里的page中存放的指针对象发送realease消息,直到遇到哨兵对象 停止

释放的时机是在runloop的某次循环中 要进入休眠状态之前会调用pop操作发送realease消息

说下循环引用?

程序中常遇到的循环引用,

  • Delegate,通常代理的关键字用weak来修饰,比如a类中有协议需要b类来遵循,b类已经对a类进行强引用了,这时如果b类对设置的代理也是强引用,就会造成循环引用

  • Block

Self将block作为自己的属性变量,而block方法里又retain 了self,不管block内部是直接访问self还是访问self的属性,都会retain self,因为arcblock进行copy的时候会对外部对象进行强引用

  • 定时器

Nstimer创建的时候 会对传入的target进行强引用,即使传入的weak修饰的也是强引用,解决办法:创建的使用使用block方式

用NSProcy 做代理转发

说说OC的几大内存区?

代码区:存放程序的二进制文件

常量区:存放的字符串常量和const常量

全局变量区:存放的是未初始化的全局变量,全局静态变态,初始化的全局变量 静态全局变量

栈区:存放的是局部变量和函数调用开销,内存地址从高指向低,内存是系统分配和释放

堆区:存在的是 以alloc new等方法创建的对象,内存地址从低指向高,内存分配从手动分配和释放

方法里有局部对象,是出了方法就会释放吗?

这个要看对象是怎么创建的 如果通过alloc new创建的 除了方法就释放

如果是通过遍历构造器创建的  内存是受autoreleasepool管理,在当前runloop的某次循环,进入休眠之前进行释放

ARC帮我们做了什么?

ARC是编译器和runloop共同协作的成果,在程序编译期间帮我们插入retain release ,autorelease等操作

网络

GET请求参数一定是放在URL中的么?GET和POST的区别?

GET是HTTP请求的一种方法,HTTP协议定义了get请求方法的传输规则,将请求参数拼接在URL后面,POST是将请求参数放在body中,但只是一种规则, GET请求也是可以携带bocy的可以将请求参数放在body里,但是一般服务器会直接忽略这部分数据,可以和服务器协商处理

GET和POST是HTTP常用的请求方法

GET请求是将请求参数拼接在url后面的,所以请求参数会明文暴露出去,安全性低

一些浏览器和服务器会对URL有长度限制,导致get请求的参数会有大小限制

GET是来获取资源的,会有缓存

发送GET请求的时候会产生一个数据包,直接将url和请求参数发给服务器

POST是将请求参数放在body里的,发送post请求会产生2个数据包,首先会把自己的header发过去,服务器响应成功后会将自己的请求参数发送过去.一般用来更新服务器数据

GET请求参数一定是放在URL中的么?GET和POST的区别?

GET是HTTP请求的一种方法,HTTP协议定义了get请求方法的传输规则,将请求参数拼接在URL后面,POST是将请求参数放在body中,但只是一种规则, GET请求也是可以携带bocy的可以将请求参数放在body里,但是一般服务器会直接忽略这部分数据,可以和服务器协商处理

GET和POST是HTTP常用的请求方法

GET请求是将请求参数拼接在url后面的,所以请求参数会明文暴露出去,安全性低

一些浏览器和服务器会对URL有长度限制,导致get请求的参数会有大小限制

GET是来获取资源的,会有缓存

发送GET请求的时候会产生一个数据包,直接将url和请求参数发给服务器

POST是将请求参数放在body里的,发送post请求会产生2个数据包,首先会把自己的header发过去,服务器响应成功后会将自己的请求参数发送过去.一般用来更新服务器数据

http header 和 body

请求行,请求头,请求实体

请求行:请求方法 URI HTTP/1.0

请求头:对客户端的环境描述、客户端请求信息

请求实体:是客户端想给服务器发送的数据

响应行,响应头,响应实体

响应行:HTTP/1.1 200 OK

响应头:包含了对服务器的描述、对返回数据的描述,

响应体是服务器给客户端的返回数据

HTTPS和HTTP的区别?

HTTP是超文本传输协议,是明文发送的,https是在http基础上将发送的数据用tls协议来加密,更安全,是超文本安全协议,端口号一个是80一个是443

如何建立连接 过程是怎么样的?

客服端发送同步连接请求,报文syn=1,服务器返回确定连接报文,SYN=1,ACK=1,客户端再次发送确认报文,SYN=0,ACK=1 ;与服务器建立连接,

如何释放连接,过程是怎样的?

客户端像服务器发送断开连接报文,FIN=1,ACK=1,服务器返回确认报文,ACK=1,服务器根据自己是否有数据还发送,如果没有发送断开连接请求FIN=1,ACK=1,客户端发送确认报文,ACK=1,与此服务器与客户端释放连接

HTTPS是如何进行报文加密的,经历了哪些过程?

1服务端发送client hello报文 里面包含tls版本号,一个随机数后续生成密钥使用,还有客户端支持的加密组件

2:服务器发送server hello报文,里面包含tls版本号,选择的加密组件,和一个随机数

3 :服务端下发证书给客户端,发送Client Key Exchange报文和一个用公钥加密的server param

4:服务端发送sever hello done报文,告知客户端协商结束

5:客户端发送client key change报文,发送一个client param,client param ,server param生成一个随机密钥串,用client random和server random 和随机密钥串生成主密钥,用主密钥延伸出以后客户端和服务端加密用的密钥

6:客户端发送Change Cipher Spec报文,告知服务器之后用密钥传输数据

7:客户端发送FInished报文,将之前所有报文整体摘要值用密钥加密发给服务器

8:服务器发送Change Cipher Spec报文,告知客户端之后用密钥传输数据

9:服务器发送Finished报文,将之前所有的报文整体摘要值使用密钥加密发给客户端。服务端和客户端都能正确解密,握手正确结束

架构设计

开发中遇到的crash,怎么处理这些crash的,有什么好的解决方案么

  • 集合类:   插入空值 NSInvalidArgumentException 非法参数异常   数组越界 NSRangeException
  • 循环引用
  • kvo,通知没有移除
  • .h声明和.m方法实现没有一一对应

埋点方案具体实现?

目前主要涉及了4大事件:

用户登录注册流程性事件

页面浏览时长事件

收益率搜索事件

App启动和退出事件

是基于runtime的交换方法实现的

json表格式:整体是一个字典,嵌套多个字典,里面的字典key是时间名字:value是对应的事件

 Gesture:当前页面/方法名字

 action:当前页面/方法名字/空间tag

 Network: 当前页面/URL路径

将事件与页面/页面元素/点击事件进行绑定

目前在本地维护了一个表,等这个表成熟后会通过服务器下发通过本地绑定的控件ID来动态响应事件参数

架构是怎么搭建的?组件化开发用到哪些?私有库有了解吗?

分为Service服务层 涉及分类,工具类

UI公用层 统一风格的控件封装

组件层:

功能性的组件 如地图管理,ocr扫描等。

支持业务开发的组件。

网络层:

业务层:调用组件用router方式通信,不同业务之间也用router方式通信。

Router是MGJRouter:

优点:方案成熟,可以通过添加scheme来区分外部url还是内部url,可多端统一路由规则

缺点:需要注册和维护绑定的url,无法保证使用的模块一定注册过。传递的参数有限,只能传字符串,和nsdata等。