IOS面试专题iOS面试iOS面试题

面试题知识点梳理

2019-03-20  本文已影响0人  Hengry

重点

  • KVC、KVO
  • GCD
  • Runtime iOS开发之Runtime——面试解析
  • runloop
  • Block iOS开发之Block
  • 内存管理、堆栈
  • 通知、代理delegate
  • Net、Https双向认证
  • 数据持久化、数据库
  • MVVM、MVP、MVC
  • 第三方库
  • 性能优化: TableView
  • 响应式编程 RxSwift
  • 与Js交互、WKWebView

拓展知识点

面试要求

1、熟悉iOS开发常用设计模式多线程、数据持久化、网络通信动画效果、界面布局、json解析、自定义控件等

2、对runtime、GCD、KVO、Block等有一定了解,熟悉iOS内存管理机制,对程序性能优化、内存优化有一定经验。

1.分类(category)的作用

130、多线程

1、多线程概念

多条线程是同步完成多项任务,提高资源的使用效率。多核的CPU运行多线程更为出色;在iOS应用中,对多线程最初的理解为并发。

2、多线程的作用

实现负载均衡问题,提高cpu利用效率。

3、使用场景

数据请求框架、多张图片下载、定时器,视频图像的采集、处理、保存等耗时操作的方法。

133、进程和线程的区别与联系是什么?

一个程序至少有个一进程,一个进程至少有一个线程:

进程:拥有独立的内存单元,而多个线程共享一块内存

线程:线程是进程内的一个执行单元

联系:线程是进程的基本组成单位。

136、对比iOS中的多线程技术

  1. NSThread

    NSThread需要手动管理线程生命周期

  2. GCD

    • GCD仅仅支持FIFO队列,只可以设置队列的优先级。而NSOperationQueue中的每个任务都可以被重新设置优先级(setQueuePriority:),从而实现不同操作的执行顺序调整。
    • GCD的执行速度比NSOperationQueue快
    • GCD不支持异步操作之间的依赖关系设置。如果某个操作依赖另一个操作的数据,使用NSOperationQueue能够设置依赖按照正确的顺序执行操作(addDependency:)。
  3. NSOperationQueue

    • 方便停止队列中的任务(cancelAllOpeations, suspended);GCD不方便停止队列中的任务
    • 支持KVO,可以监测operation是否正在执行(isExecuted)、是否结束(isFinished)、是否取消(isCanceld)
    • NSOperationQueue可设置最大并发数量(节电), GCD具有dispath_one(只执行一次、单例)和dispatch_after(延时执行)功能
  4. NSObject分类

    NSObject分类(perform)和NSThread遇到对象分配需要手动管理内存和线程生命周期

    NSObject分类线程通信

137、多线程优缺点

优点:

缺点:

134、 异步执行两个耗时操作,等两次耗时操作都执行完毕后,再回到主线程执行操作。


    dispatch_queue_t queue = dispatch_get_global_queue(0, 0); // 全局并发队列
    
    dispatch_queue_t group = dispatch_queue_create("并行队列", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_group_async(group, queue, ^{ // 异步执行操作1
        // longTime1
    });
    dispatch_group_async(group, queue, ^{ // 异步执行操作2
        // longTime2
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // 主线程刷新UI
        // reload Data
    });

面试题陷阱

    NSLog(@"1");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2");
    });
    
    NSLog(@"3");
    
    //奔溃原因:
    // 同步线程不会去创建新的线程。
    // 在同步线程里面执行dispatch_get_main_queue()时会发送线程卡死的现象
    
    /* 正确做法:异步切换主线程
     NSLog(@"1");
     dispatch_async(dispatch_get_main_queue(), ^{
     NSLog(@"2");
     });
     
     NSLog(@"3");
     */

GCD

1、创建队列

//OBJECTIVE-C
  //串行队列
 dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", NULL);
 dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL);
  //并行队列
 dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT);

//SWIFT
  //串行队列
  let queue = dispatch_queue_create("tk.bourne.testQueue", nil);
  let queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL)
  //并行队列
  let queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT)

2、全局队列

//OBJECTIVE-C
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//SWIFT
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

3、创建任务

// 创建同步任务
// OBJECTIVE-C
dispatch_sync(<#queue#>, ^{
      //code here
      NSLog(@"%@", [NSThread currentThread]);
  });

// SWIFT
 dispatch_sync(<#queue#>, { () -> Void in
      //code here
      println(NSThread.currentThread())
  })
// 创建异步任务
// OBJECTIVE-C
 dispatch_async(<#queue#>, ^{
      //code here
      NSLog(@"%@", [NSThread currentThread]);
  });

// SWIFT
 dispatch_async(<#queue#>, { () -> Void in
      //code here
      println(NSThread.currentThread())
  })

4、延时

// 创建队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 设置延时,单位秒
double delay = 3; 

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), queue, ^{
  // 3秒后需要执行的任务
});

NSOprationQueue

NSOperation 有一个非常实用的功能,那就是添加依赖。比如有 3 个任务:A: 从服务器上下载一张图片,B:给这张图片加个水印,C:把图片返回给服务器。这时就可以用到依赖了:

//1.任务一:下载图片
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"下载图片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//2.任务二:打水印
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"打水印   - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//3.任务三:上传图片
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"上传图片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//4.设置依赖
[operation2 addDependency:operation1];      //任务二依赖任务一
[operation3 addDependency:operation2];      //任务三依赖任务二

//5.创建队列并加入任务
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];

面试题:NSoperationQueue处理A,B,C三个线程,要求执行完A,B后才能执行C。

参考地址:https://blog.csdn.net/qq_30932479/article/details/79790646

  1. NSOperation添加依赖关系实现
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    NSBlockOperation *A = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"A----%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *B = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"B---%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *C = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"C---%@",[NSThread currentThread]);
    }];
    
    [C addDependency:A];
    [C addDependency:B];
    
    [queue addOperation:A];
    [queue addOperation:B];
    [queue addOperation:C];
  1. 使用GCD的栅栏函数或者队列组
    /*
     1,开启两个线程处理A和B
     2,通过任务组执行A,B之后执行C
     */
    dispatch_queue_t queue = dispatch_queue_create("dealWith", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"A----%@",[NSThread currentThread]);
    });
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"B---%@",[NSThread currentThread]);
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"C---%@",[NSThread currentThread]);
    });
  1. GCD添加栅栏函数
    //栅栏函数(栅栏函数不能用全局并发队列)
    //等执行完栅栏函数中的代码才继续执行下面的代码
    
    dispatch_queue_t queue = dispatch_queue_create("dealWith", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"A----%@",[NSThread currentThread]);
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"B---%@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"C---%@",[NSThread currentThread]);
    });

142、Core Data

Core Data是iOS 5之后才出现的一个基于Sqlite进行封装的数据持久化框架。它提供了对象-关系映射(ORM)的功能,即能够将OC对象转换为数据,保存到sqlite数据库文件中,也能够将保存在数据库中的数据还原为OC对象。在数据操作期间,不需要编写任何SQL语句。

如何解决Core Data线程数据同步问题?

监听通知NSManagedObjectContextDidSaveNotification,在耗时操作处理完之后告诉主上下文哪些改变了。我们可以通过主线程执行合并操作来实现。

152、UITableView

  1. UITableView最核心的思想

    Cell的重用机制。简单理解:UITableView只会创建一屏幕(或者一屏幕多一点)Cell,其他都是从中取出来重用的。每当Cell滑出屏幕时,就会收到一个Cell集合(复用池)中。当要显示某一位置的Cell时,会先从复用集合中取,如果有则直接拿来显示;如何没有,才会创建新的Cell。这样极大减少了内存的开销。

    tableView:cellForRowAtIndexPath: 方法只负责赋值

    tableView:heightForRowAtIndexPath: 方法只负责计算高度

  2. 自定义高度

  3. UITableView性能优化

    • 缓存行高
    • 异步绘制
    • 异步加载图片以及缓存
    • 滑动时按需加载,特别是加载大量的图片的列表
    • 不要动态创建子视图:所有子视图都预先创建,如果不需要显示设置隐藏
    • 所有子视图都应该添加到contentView上
    • 尽量少用或者不用透明图层
    • cell栅格化
  4. 离屏渲染的问题

    下面的情况或操作会引发离屏渲染问题:

    • 为图层设置遮罩(layer.mask)
    • 将图层的layer.masksToBounds/vew.clipsToBounds属性设置为true
    • 将图层的layer.allowsGroupOpacity属性设置为true、layer.opacity小于1.0
    • 设置阴影layer.shadow
    • layar.shouldRasterize属性为true
    • layar.cornerRadius,
    • 使用CGContext在drawRect:方法中绘制大部分都会导致离屏渲染
  5. 离屏渲染优化方案

    • 圆角优化

      渲染机制是GPU在当前屏幕缓冲区外新开辟一个渲染缓冲区进行工作,也就是离屏渲染,这会给我们带来额外的性能损耗。如果圆角操作达到一定数量,会触发缓冲区的频繁合并和上下文的频繁切换,性能的代价会宏观地表现在用户体验上—掉帧。

方案一:使用贝塞尔曲线UIBezierPath和Core Graphics框架画出一个圆角图片

 UIImageView *imageView = [[UIImageView alloc] initWithFrame: CGRectMake(100, 100, 100, 100)];
 imageView.image = [UIImage imageNamed: @"myImg"];
     
 // 开始对imagewView进行画图
 UIGraphicsBeginImageContextWith±Options(imageView.bounds.size, NO, 1.0);
 
 // 使用贝塞尔曲线画出一个圆形路径
 [UIBezierPath bezier±PathWithRoundedRect:imageView.bounds cornerRadius: imageView.frame.size.width] addClip];
 
 [imageView drawRect: imageView.bounds];
 
 // 重新设置圆角图片
 imageView.image = UIGraphicsGetImageFromCurrentImageContext();
  
 // 结束画图
 UIGraphicsEndImageContext±();
 [self.view addSubview: imageView];
        

方案二:使用CAShapeLayer和UIBezierPath设置圆角遮罩层

 UIImageView *imageView = [[UIImageView alloc] initWithFrame: CGRectMake(100, 100, 100, 100)];
 imageView.image = [UIImage imageNamed: @"myImg"];
 
 // 使用贝塞尔曲线画出一个圆形路径
 UIBezierPath *maskPath = UIBezierPath bezierPathWithRoundRect: imagewView.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii: imageView.bounds.size];
 
 // 创建CAShapeLayer
 CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
 maskLayer.frame = imageView.bounds;
 
 // 设置绘制路径
 maskLayer.path = maskPath.CGPath;
 imageView.layer.mask = maskLayer;
 [self.view addSubView: imageView];
 

162、Objective-C堆和栈的区别?

管理方式:栈是由编译器自动管理,无需我们手动控制;堆释放工作由程序员控制,容易产生memory leak。

分配方式:堆只有动态分配。栈分为静态分配和动态分配。

分配效率:栈分配效率高。堆的分配效率相对低。

栈:

堆:

145、说说关于UDP/TCP的区别?

UDP

TCP

TCP与UDP的区别:

TCP传输原理

  1. TCP如何防止乱序和丢包

  2. 描述一下三次握手

    第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;

    第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

    第三次握手:客户端收到服务器的SYN+ACK包,并向服务器发送确认标包ACK(ack=k+1),此时发送完毕,客户端和服务器端进入ESTABLISHED状态,完成三次握手。完成三次握手,客户端与服务器端开始传输数据。

  3. 三次握手过程

    第一次握手:建立连接时,客户端发送同步序列编号到服务器,并进入发送状态,等待服务器确认。

    第二次握手:服务器收到同步序列编号,并确认同时自己也发送一个同步序列编号+确认标志,此时服务器进入接收状态

    第三次握手:客户端收到服务器发送的包,并向服务器发送确认标志,随后连接成功。

148、Block

  1. Block定义格式:

    typedef void(^completion)(BOOL finnished)
    
  2. 使用block时什么情况会发生循环引用,如何解决?

    一个对象中强引用了block,在block中又使用了该对象,就会发生循环引用。解决方法:将该对象使用__weak或者__block修饰符修饰之后再在block中使用。

    __weak typeof(self) weakSelf = self;
    
    __weak typeof(self) weakSelf = self;
    [self doSomeBlockJob:^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        if (strongSelf) {
            ...
        }
    }];
    

    iOS中block的详解weakSelf、strongSelf https://blog.csdn.net/xgb742951920/article/details/69258372

  3. 在block内如何修改block外部变量?

    在block中访问的外部变量是复制过去的,即:写操作不对原变量生效。

    // __block修饰局部变量,这个变量在block内外属于同一个地址上的变量,可以被block内部修改。
    __block int a = 0;
    void (^foo)(void) = ^ {
        a = 1;
    }
    
    foo()
    

120、KVO、NSNotification、delegate以及block区别?

122、runtime/消息转发机制

  1. runtime原理

    1.1、runtime基本概念

    runtime是一套OC底层纯C语言编写的库。我们平时编写的OC代码中,程序运行过程,其实最终都是转成了runtime的C语言代码,runtime是OC的幕后工作者。

    1.2、runtime工作原理

    在程序运行过程中,动态创建类:

    objc_allocateClassPair,class_addIvar,objc_registerClassPair.

    动态为某个类添加属性/方法,修改属性/方法(修改封装的框架)

    objc_setAssociatedObjectobjc_setIvar

    遍历一个类的所有成员变量(属性)/方法(字典转模型,归档解析)

    class_copyIvarList,class_copyPropertyList,class_copyMethodList

  2. 消息机制

    2.1 消息转发的原理

    当向一个对象发送消息时,objc_msgSend方法根据对象的isa指针找到对象的类,然后在类型的调度表查(dispath table)找selector方法。一旦找到selector,objc_msgSend根据调度表的内存地址调用改实现方法。

消息转发调用方法顺序

objc_msgSend-->CacheLookup-->objc_msgSend_uncache-->±MethodTableLookup-->class_lookupMethodAnd±LoadCache3-->lookUpImpOrForward

  1. 动态绑定

    动态绑定—在运行时确定要调用的方法

链表

1、删除单链表节点

r = p->pNext; // p后面的结点
p->pNext = r->pNext; // 修改p的Next结点
free(r); // 释放内存

2、插入单链表结点

r = p->pNext;
p->pNext = q; // p的Next结点指向新结点q
q->pNext = r; // 新插入结点的Next结点指向r

3、创建单链表

#include <stdio.h>
#include <malloc.h>

typedef struct Node
{
    int data;
    struct Node *pNext;
}NODE, *PNODE; // NODE等价于struct Node、 PNODE等价于struct NODE *

// 函数声明
PNODE create_list(void);
// 遍历链表
void traverse_list(PNODE pHead);
// 判断链表是否为空
bool is_empty(PNODE pHead);
// 链表的长度
int length_list(PNODE);
// 在某个位置上插入结点 
bool insert_list(PNODE, int, int *);
// 删除链表
bool delete_list(PNODE, int, int *);
// 链表排序
void sort_list(PNODE);

int main(id)
{
    
    PNODE pHead = NUll;
    
    pHead = create_list();
    traverse_list(pHead);
    sort_list(pHead);
    
    return 0;
}

// 创建链表
PNODE create_list(void)
{
    int len; // 链表长度
    int i;
    int val; // 临时存放用户输入的结点的值
    
    PNODE pHead = (PNODE)malloc(sizeof(NODE));
    
    printf("请输入您需要生产的链表结点个数: len = ");
    scanf("%d",&len);
    
    for (i =0; i<len; ++i)
    {
        printf("请输入第%d个结点的值:", i+1);
        scanf("%d", &val);
        
        PNODE pNew = (PNODE)malloc(sizeof(NODE));
        if (NULL != pNew)
        {
            printf("分配失败,程序终止!")    
            exit(-1);
        }
        
        // 将pNew挂到链表尾部
        pNex->data = val;
        pHead->pNext = pNew;
        pNew->pNext = NULL:
    }
    
    return pHead;
    
}

// 判断链表是否为空
bool is_empty(PNODE pHead)
{
    if (NULL == pHead->pNext)
    {
        return true;  
    }else{
        return false;
    }
}

// 链表长度
int length_list(PNODE pHead)
{
    PNODE p = pHead->pNext;
    int len = 0;
    while(NULL ! = p)
    {
        ++len;
        p = p->pNext;
    }
    
    return len;
}

// 遍历
void traverse_list(PNODE pHead)
{
    PNODE p = pHead->pNext;
    while(NULL != p)
    {
        printf("%d ", p->data);
        p = p->pNext;
    }
}

// 链表排序
void sort_list(PNODE pHead)
{
    int i, j, t;
    PNODE p, q;
    int len = length_list(pHead);
    
    // 冒泡排序
    for (i=0,p=pHead->pNext; i< len - 1; ++i, p  p->pNext)
    {
        
        for(j=i+1, q=p->pNext; j < len; ++j,q=q->pNext)
        {
            // 后面的结点比前面的大,则交换位置
            if (p->data > q->data) 
            {
                t = p->data;
                p->data = q->data;
                q->data = t;
            }
        }
    }
    
    return;
}


// 在某个位置上插入结点 
bool insert_list(PNODE pHead, int pos, int val)
{
    int i = 0;
    PNODE p = pHead;
    
    while(NULL ! = p && i < pos -1)
    {
        p = p->pNext;
        ++i;
    }
    
    if (i > pos - 1 || NULL == p)
        return false;
    
    PNODE pNew = (PNODE)malloc(sizeof(NODE));
    if (NULL == pNew)
    {
        printf("动态分配内存失败!")
    }
    
    pNew->data = val;
    PNODE q = p->pNext;
    p->pNext = pNew;
    pNew->pNext = q;
    return true;
    
}

// 删除链表中某个下标的结点并返回删除元素pVal 
bool delete_list(PNODE pHead, int pos, int *pVal)
{
    int i = 0;
    PNODE p = pHead;
    
    while(NULL ! = p->pNext && i < pos -1)
    {
        p = p->pNext;
        ++i;
    }
    
    if (i > pos - 1 || NULL == p->pNext)
        return false;
    

    PNODE q = p->pNext;
    // 删除的值
    *pVal = q->data;
    
    // 删除结点后面的结点
    p->pNext = p->pNext->pNext;
    free(q);
    q = NULL:
    return true;
    
}

数据结构概念

狭义:

  • 数据结构是专门研究数据存储的问题
  • 数据的存储包含两方面:个体的存储+个体关系的存储

广义:

  • 数据结构既包含数据的存储也包含数据的操作
  • 对存储数据的操作就是算法

算法概念

狭义:算法是和数据的存储方式密切相关

广义:算法和数据的存储方式无关

数据的存储结构:

队列

170、UIKit、CoreAnimation和CoreGraphics的关系是什么?在开发过程中是否使用到CoreAnimation 和CoreGraphics?

178、动画

CAAnimation 动画基类

上一篇下一篇

猜你喜欢

热点阅读