iOS

iOS senior engineer interview

2019-07-11  本文已影响2人  踏云小子

中级

一、Block

1.1 block的实质是什么?一共有几种block?都是什么情况下生成的?

block对象就是一个结构体,里面有isa指针指向自己的类(global malloc stack),有desc结构体描述block的信息,__forwarding指向自己或堆上自己的地址,如果block对象截获变量,这些变量也会出现在block结构体中。最重要的block结构体有一个函数指针,指向block代码块。block结构体的构造函数的参数,包括函数指针,描述block的结构体,自动截获的变量(全局变量不用截获),引用到的__block变量。(__block对象也会转变成结构体)


image.png
int main() {
    int (^blk)(int i) = ^(int i){
        int result =  i + 1;
        return result;
    };
    blk(3);
    return 0;
}
//==============上面是反编译以前的代码==================
struct __block_impl {
    void *isa;//isa表明结构体类型。
    int Flags;
    int Reserved;
    void *FuncPtr;//指向函数指针
};
//这个结构体及时Block反编译以后生成的主要结构。
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
    //初始化函数
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;//表示这个Block是存储于栈上。
    impl.Flags = flags;
    impl.FuncPtr = fp;//函数指针赋值
    Desc = desc;
  }
};
//这个函数就是Block的具体实现,并且添加了一个默认实现。
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int i) {
    int result = i + 1;
    return result;
}
//Block的描述信息
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
};
//__main_block_desc_0的一个实例,其中Block_size初始化为__main_block_impl_0结构体的大小。
struct __main_block_desc_0 __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

int main() {
    //int (*blk)(int i) = ((int (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    //上面一行转换为下面两行等价
    struct __main_block_impl_0 tmp = __main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
    struct __main_block_impl_0 *blk = &tmp;
    
    //int blkRerurn = ((int (*)(__block_impl *, int))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, 3);
    //下面一行是上面一行的简化版
    (*blk->impl.FuncPtr)(blk,3);
    return 0;
}

1.2 为什么在默认情况下无法修改被block捕获的变量? __block都做了什么?

__block变量i在转换为c语言后直接转换为一个__Block_byref_i_0类型的结构体
通过i->__forwarding->i获取外部变量并修改,然后将__main_block_impl_0copy下,再释放下,具体代码

1.3 模拟一下循环引用的一个情况?block实现界面反向传值如何实现?

二、Runtime

2.1 objc在向一个对象发送消息时,发生了什么?

runtime会根据对象的isa指针找到所对应的类,然后在类的方法列表、父类的方法列表里找对应的方法运行,但是在发送消息时,objc_msgSend不会返回值,只会在程序实际运行时决定

附注:c语言,调用一个方法其实就是跳到内存中的某一点并开始执行一段代码。没有任何动态的特性,因为这在编译时就决定好了。而在 Objective-C 中,[object foo] 语法并不会立即执行 foo 这个方法的代码。它是在运行时给 object 发送一条叫 foo 的消息。这个消息,也许会由 object 来处理,也许会被转发给另一个对象,或者不予理睬假装没收到这个消息。多条不同的消息也可以对应同一个方法实现。这些都是在程序运行的时候决定的。

[array insertObject:foo atIndex:5];
//编译时转化成
objc_msgSend(array, @selector(insertObject:atIndex:), foo, 5);

2.2 objc中向一个nil对象发送消息将会发生什么?

向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了

2.3 什么时候会报unrecognized selector错误?iOS有哪些机制来避免走到这一步?

2.4 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?能否添加成员变量?能否添加属性变量?原理是什么?

附注:成员变量、实例变量、属性变量之间的区别?成员变量就是用{}括起来的对象,外界无法使用;属性变量就是@property修饰的,外界可以拿到;实例变量本质上就是成员变量,只是实例对象是针对类,定义了属性变量myButton会自动生成_myButton,并写好setter/getter方法


QQ图片20180310223645.jpg

2.5 runtime如何实现weak变量的自动置nil?

runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。

2.6 给类添加一个属性后,在类结构体里哪些元素会发生变化?

objc_ivar_list(实例变量列表)和instance_size(实例内存大小)会变化

2.7 runtime的运用

2.7.1 kvo、kvc实现原理

removeObserver就是将isa指针指向原来的类
具体demo看这里

2.7.2 category的实现

category内部是如何实现的?与该类原有方法的名称相同的时候,为什么原有方法会失效?

1. 将Category和它的元类注册到哈希表
2. 如果元类已经实现,则重建它的方法列表
2.7.3 添加属性
objc_setAssociatedObject
2.7.4 添加实例变量
2.7.5 获取私有方法
2.7.6 替换方法
2.8 + (void)load; 和 + (void)initialize;区别是什么
image.png

Runloop

苹果是如何实现Autorelease Pool的?

其实就是指针组成的堆栈,当要释放时候,调用objc_autoreleasePoolPush将边界对象放进AutoreleasePoolPage栈顶,返回返回边界对象内存地址,接着就是pop操作,对晚于边界对象的对象发送release消息,并移动next指针到正确位置

类指针

类方法和实例方法有什么区别?

高级

1.UITableview的优化方法(缓存高度,异步绘制,减少层级,hide,避免离屏渲染)

2.有没有用过runtime,用它都能做什么?(交换方法,创建类,给新创建的类增加方法,改变isa指针)

3.看过哪些第三方框架的源码?都是如何实现的?(如果没有,问一下多图下载的设计)

4.SDWebImage的缓存策略?

5.AFN为什么添加一条常驻线程?

6. AsyncDisplayKit原理是什么

内部封装了UIView、CALayer,是得他们这些属性都可以在后台线程设置,从而完成了排版、绘制在后台线程的实现

造成UI卡顿主要包括:
- 排版:计算视图大小,计算文本高度,重新计算子视图的排版
- 绘制:文本绘制、图片绘制(预先解压)、元素绘制(Quartz)
- UI对象操作:UIView、CALayer等UI对象的创建、销毁、属性设置

8. 关于网络编程

1.iOS中socket使用
Socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。

http协议 对应于应用层
tcp协议 对应于传输层
ip协议 对应于网络层
三者本质上没有可比性。 何况HTTP协议是基于TCP连接的。

TCP/IP是传输层协议,主要解决数据如何在网络中传输;而HTTP是应用层协议,主要解决如何包装数据。

我 们在传输数据时,可以只使用传输层(TCP/IP),但是那样的话,由于没有应用层,便无法识别数据内容,如果想要使传输的数据有意义,则必须使用应用层 协议,应用层协议很多,有HTTP、FTP、TELNET等等,也可以自己定义应用层协议。WEB使用HTTP作传输层协议,以封装HTTP文本信息,然 后使用TCP/IP做传输层协议将它发送到网络上。

SOCKET原理

套接字(socket)概念套接字(socket)是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。
应 用层通过传输层进行数据通信时,TCP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个 TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了套接字(Socket)接口。应 用层可以和传输层通过Socket接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。
建立socket连接建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket,另一个运行于服务器端,称为ServerSocket。
套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。
服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
连 接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户 端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
SOCKET连接与TCP连接创建Socket连接时,可以指定使用的传输层协议,Socket可以支持不同的传输层协议(TCP或UDP),当使用TCP协议进行连接时,该Socket连接就是一个TCP连接。
Socket连接与HTTP连接由 于通常情况下Socket连接就是TCP连接,因此Socket连接一旦建立,通信双方即可开始相互发送数据内容,直到双方连接断开。但在实际网络应用 中,客户端到服务器之间的通信往往需要穿越多个中间节点,例如路由器、网关、防火墙等,大部分防火墙默认会关闭长时间处于非活跃状态的连接而导致 Socket 连接断连,因此需要通过轮询告诉网络,该连接处于活跃状态。
而HTTP连接使用的是“请求—响应”的方式,不仅在请求时需要先建立连接,而且需要客户端向服务器发出请求后,服务器端才能回复数据。
很 多情况下,需要服务器端主动向客户端推送数据,保持客户端与服务器数据的实时与同步。此时若双方建立的是Socket连接,服务器就可以直接将数据传送给 客户端;若双方建立的是HTTP连接,则服务器需要等到客户端发送一次请求后才能将数据传回给客户端,因此,客户端定时向服务器端发送连接请求,不仅可以 保持在线,同时也是在“询问”服务器是否有新的数据,如果有就将数据传给客户端。

TCP与UDP
TCP和UDP都是传输层的协议:

TCP是传输控制层协议,是面向连接、可靠的,点对点的;

UDP是用户数据报协议,是不需要连接、不可靠的,点对多点的;

TCP侧重于安全传输,UDP侧重于快速传输。

TCP的三次握手:a、用户端向服务器发送syn包,用户端进入send状态,服务器等待接收;b、服务器接收到syn包并确认后,发送一个syn+ack包给用户端,服务器进入recv状态;c、用户端收到服务器的返回的syn+ack包后,再发送一个确认包ack给服务器,此时用户端和服务器都进入established状态,连接成功。

HTTP与HTTPS
http是超文本传输协议,是短连接,用户端向服务器请求数据后,服务器响应,连接断开。http是应用层面向对象的协议,它由请求报文和响应报文组成:

请求报文:请求行、请求头、空行和请求体;

响应报文:响应行、响应头、响应体。

http有GET和POST两种请求方式:

GET:请求内容拼接在url的后面,可以通过截取url‘?’后面的内容得到,多个参数由'&'分隔,账号和密码会被明文显示在url的后面,是不安全的,而且传输数据较少,不超过1024字节;

POST:请求参数写在请求数据里面,相对GET是比较安全的,提交数据放在http包的包体中,传输数据较多,理论上没有上限。

http协议是基于socket的,http的底层就是socket通信。

https是安全超文本传输协议,是基于http协议开发的,s指的是secure安全,它通过是安全套接字层来完成用户端和服务器端的通信,可以说是http的安全版协议。

Socket与其他
socket是通信的基石,是支持TCP/IP协议的基本通信单元。socket是基于TCP/IP的协议的封装,它本身并不是一个协议,而是一个接口API,它包含了传输协议,主机IP、主机进程端口号及服务器IP、服务器进程端口号五大基础部分组成。

在实际应用中,应用层通过传输层通信时,TCP常遇到为多个程序提供并发服务的问题,这时就会有多个TCP或者多个程序需要用同一个TCP协议端口传输数据,但是服务器无法区分是具体是哪个程序,这时就有了socket,应用层和传输层可以通过socket抽象层来判断具体为哪个程序进行通信,提供并发服务。

socket一般成对出现,一个是客户端ClientScoket,一个是服务端ServiceSocket。socket连接分三步:

服务器监听:ServiceSocket不会规定客户端的IP与端口号,服务器是处于等待连接的状态,监听网络访问,等待客户端连接;

客户端请求:ClientScoket不仅包含自己的主机IP和进程端口号,还包含服务端的IP和进程端口号,通过服务端的IP和端口号找寻对应的ServiceSocket发送连接请求;

连接确认:当服务器监听或者说接收到ClientScoket的连接请求时,就响应ClientScoket的请求,并新建一个线程,发送给客户端完整的ServiceSocket,一旦客户端确认此ServiceSocket后,连接完成,ServiceSocket继续处于等待状态,等待其他客户端进程的连接。

ios中创建socket连接五步走:创建socket、连接服务器、用户发送数据到服务器、服务器响应数据返回用户、关闭socket。

socket与TCP:

socket是可以支持TCP和UDP协议的,如果是使用TCP协议进行连接,那么就是一个TCP连接。

socket与http:

socket一般都是基于TCP的,所以是一个长连接,可以进行通信。但实际应用中应用层向传输层通信还需要穿越多个中间节点,例如路由器、网关、防火墙等,而防火墙一般是默认关闭处于不活跃的连接的,所以需要轮询服务器,保证连接不被关闭。

http是短连接,服务器响应数据后就断开。而实际应用中,经常需要服务端与客户端保持数据实时与同步,这就需要服务器发送数据给客户端,但http只能让客户端先建立连接并请求数据,服务器才能响应数据。而采用socket长连接就不需如此,服务器可以直接将数据发送给客户端。

9. ReactNative原理

面向对象

三大特征

Obj-C部分

内存管理
image.png
自动释放池

当一个自动释放池子被销毁,会对池子里对象发送一条release消息

  1. 与线程关系
    每个线程对应一个NSAutoreleasePool,当新池子被创建,push进栈,当池子被释放内存,pop出栈
类与对象

b) 实例方法
实例方法属于实例,不常驻内存,存在栈上

Runtime

Obj-C不同于c是一个动态语言,当程序执行[object doSomething]时,会向消息接收者(object)发送一条消息(doSomething),runtime会根据消息接收者是否能响应该消息而做出不同的反应。

UIApplication
image.png
atomic与nonatomic
//设置属性name为nonatomic
@property (nonatomic, copy) NSString *name;

//创建两个线程同时操作属性name
- (IBAction)onclickAtomic:(id)sender {
    WS(weakSelf);
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        weakSelf.name = [weakSelf.name stringByAppendingString:@" will "];
        NSLog(@"name:%@", weakSelf.name);
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        weakSelf.name = [weakSelf.name stringByAppendingString:@" javion "];
        NSLog(@"name:%@", weakSelf.name);
    }];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue setName:@"MyQueue"];
    [queue addOperations:@[op1, op2] waitUntilFinished:NO];
}

结果果然出现了奇葩情况

image.png

于是我把属性改成了atomic,然后就正常了

image.png
通俗的例子

A想要从自己的帐户中转1000块钱到B的帐户里。那个从A开始转帐,到转帐结束的这一个过程,称之为一个事务。在这个事务里,要做如下操作:

  1. 从A的帐户中减去1000块钱。如果A的帐户原来有3000块钱,现在就变成2000块钱了。
  2. 在B的帐户里加1000块钱。如果B的帐户如果原来有2000块钱,现在则变成3000块钱了。
    如果在A的帐户已经减去了1000块钱的时候,忽然发生了意外,比如停电什么的,导致转帐事务意外终止了,而此时B的帐户里还没有增加1000块钱。那么,我们称这个操作失败了,要进行回滚。回滚就是回到事务开始之前的状态,也就是回到A的帐户还没减1000块的状态,B的帐户的原来的状态。此时A的帐户仍然有3000块,B的帐户仍然有2000块。

我们把这种要么一起成功(A帐户成功减少1000,同时B帐户成功增加1000),要么一起失败(A帐户回到原来状态,B帐户也回到原来状态)的操作叫原子性操作。

如果把一个事务可看作是一个程序,它要么完整的被执行,要么完全不执行。这种特性就叫原子性。

附其他网站的面试题

王隆帅-iOS常见面试题汇总

上一篇下一篇

猜你喜欢

热点阅读