iOS 面试总结

iOS 面试总结

UI相关面试题

系统的UI事件传递机制是怎样的?

hidtest 和hidpoint 内部实现
先判断自己是否隐藏或者支持用户响应或者是否具有透明度,然后从顶部倒序开始寻找相应视图

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    if (!self.userInteractionEnabled ||
        [self isHidden] ||
        self.alpha <= 0.01) {
        return nil;
    }
    
    if ([self pointInside:point withEvent:event]) {
        //遍历当前对象的子视图
        __block UIView *hit = nil;
        [self.subviews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            // 坐标转换
            CGPoint vonvertPoint = [self convertPoint:point toView:obj];
            //调用子视图的hittest方法
            hit = [obj hitTest:vonvertPoint withEvent:event];
            // 如果找到了接受事件的对象,则停止遍历
            if (hit) {
                *stop = YES;
            }
        }];
        
        if (hit) {
            return hit;
        }
        else{
            return self;
        }
    }
    else{
        return nil;
    }
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
    CGFloat x1 = point.x;
    CGFloat y1 = point.y;
    
    CGFloat x2 = self.frame.size.width / 2;
    CGFloat y2 = self.frame.size.height / 2;
    
    double dis = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
    // 67.923
    if (dis <= self.frame.size.width / 2) {
        return YES;
    }
    else{
        return NO;
    }
}

使UITableView滚动更流畅得方案和思路都有哪些?

cpu+GPU

cpu 采用子线程进行对象创建销毁,包括进行预排版,以及图片的解码,

Gpu 和异步绘制方案
kvo实现原理,kvc实现原理,通知实现原理

用分类做什么

1声明私有方法

2分解体积庞大的类文件

3把framework的私有方法进行公开

分类的特点

运行时决议

可以为系统类添加分类

分类可以添加哪些内容

实例方法

类方法

协议

属性

成员变量(使用关联对象)

扩展

编译时决议

只以声明的形式存在,多数情况下寄生于宿主类的.m中

不能为系统添加扩展

代理

代理是一种软件设计模式

代理是用代理模式实现

传递一对一

通知

使用观察者模式来实现的用于跨层传递消息的机制

传递方式为一对多

kvo

kvo是Objiective-C对观察者设计模式的又一种实现

Apple 使用了isa混写(is-swizzling)来实现kvo

runtime

image.png

blcok

对象、函数封装、执行上下文

不需要__blcok 的情况静态局部变量、全局变量、静态全局变量


image.png

image.png

image.png

image.png

该解决循环引用有一个问题,就是,如果不调用该方法,那么这个环会一直存在

什么是blcok

是一个对象、对函数封装、执行上下文

为什么block会产生循环引用

自循环引用,多循环引用
1、如果说当前blcok对当前某一成员变量进行截获,block会对对应变量,有一个强引用,当前block由于当前对象有一个强引用,产生一个自循环引用,可以通过声明为__weak 变量来进行循环引用的消除
2、如果说我们定义一个__blcok说明符,也会产生循环引用,但是是区分场景的,一种是在arc下面是会产生循环引用的,而在mrc下面不会.在arc下面可以通过断环的方式去解除对应的循环引用,但是有一个弊端、如果block一直得不到调用,这个循环引用是没有办法解除的

怎么理解截获变量的特性

1对于基本类型的局部变量,是对其值进行截获,
2对于对象类型的局部变量,是对其进行强引用,连通全所有修饰符共同进行截获
3对于静态的局部变量,是对其指针进行截获
4对于全局变量、局部、静态全局变量不产生截获

多线程

iOS 提供哪几种多线程

1 GCD
2 NSOperation 网络请求,图片下载 ,可以进行状态改变
3 NSThread 可以实现常驻线程

GCD

同步/异步 和串行/并发

dispatch_barrier_async 异步栅栏调用 解决多读单写的问题


image.png

dispathc_group


7A9BF7F0-EA1C-472B-8826-660F1DB9A360.png

image.png

image.png

performSelector 方法调用必须保证当前线程有runLoop,否则该方法会失效;

image.png

NSOperation 优势
添加任务依赖
任务执行状态控制
控制最大并发量


image.png

image.png

image.png

image.png

iOS 当中都有哪些锁

@synchronized 一般在创建单利对象的时候使用,保证在多线程环境中是唯一的

atomic 修饰属性的关键字,对被修饰的对象进行原子操作

OSSpinLock 自旋锁,循环等待询问,不释放当前资源,用于轻量级数据访问,简单的int值 +1/-1操作

NSRecursiveLock 递归锁 特点可以重入


image.png

NSLock 用于解决细密度的线程同步问题,用于解决线程互斥和进入自己的临界区


image.png

dispatch_semaphore_t 信号量

runLoop

什么是runloop
RunLoop是通过内部维护的事件循环来对事件/消息进行管理的对象

没有消息处理时,休眠以避免资源占用
有消息处理时立刻被唤醒

在main函数中


image.png

image.png

滑动tableview的时候我们的定时器还会生效吗?

kcfRunLoopDefaultModel 滑动tableView 会切换 UITrackingRunLoopMode 就不会生效了
解决方案,将Nstimer加入到CommonMode 中

RunLoop与线程关系
RunLoop与线程是一一对应的

实现常驻线程

为当前线程开启一个RunLoop
向该RunLoop中添加一个Port/Source等维持RunLoop的事件循环
启动该RunLoop

代码实现


image.png

怎样保证子线程数据来回更新UI的时候不打断用户滑动操作?
用户进行滑动的时候,runLoop在model 会被切换为 UITrackingRunLoopMode
网络请求时,是在子线程进行操作,请求完成之后回来在主线程更新UI
在主线程逻辑进行包装,将他包装在DefautModel下执行.

HTTP 协议

get用于获取资源,安全的,幂等的,可缓存

post 处理资源,非安全的,非幂等的,不可缓存

Http 无连接的,无状态的

UDP 复用、分用,差错检验

image.png

DNS
域名到IP地址的映射,DNS解析请求采用UDP数据报,且明文
DNS解析查询方式,递归查询,迭代查询

DNS解析存在哪些常见问题?

DNS劫持问题:
DNS解析转发问题

DNS劫持与HTTP的关系是怎么样的?
两者之间是没有关系的,第一解析是在客户端本地的,是发生在与HTTP建立连接之前进行的,2DNS解析请求使用UDP数据报,端口号为53

DNS解析转发

设计模式:6种

责任链:事件响应机制

侨界模式
适配器模式
对象适配器和被适配器,

单例模式:

命令模式:行为参数化,降低代码重合度

设计原则

image.png

单一职责原则:一个类只负责一件事,如:view 负责视图展示,clayer 负责动画

开关原则: 对修改关闭、对扩展开放

接口隔离原则:使用多个专门的协议、而不是一个庞大臃肿的协议.例如:tableView ,数据返回和事件处理

依赖倒置原则:抽象不应该依赖具体实现,具体实现依赖抽象

里氏替换原则:父类可以被子类无缝替换,切原有的功能不受任何影响 例如:kvo

迪米特法制:一个对象应当对其他对象尽可能少的了解
高内聚、低耦合

文件缓存:
淘汰原则:1先进先出,
LRU算法进行淘汰,时间访问优先选择,访问频率算法

网络设计

网络部分设计需要考虑哪些问题?
图片请求最大并发量,
请求超时策略:对一个数据进行2次或者3次请求,失败后,
请求优先级

image.png
image.png

image.png

image.png

算法

链表反转

//
//  ReversList.h
//  GCD
//
//  Created by 龚魁华 on 2020/4/23.
//  Copyright © 2020 龚魁华. All rights reserved.
//

#import 

NS_ASSUME_NONNULL_BEGIN
//定义一个链表
struct Node {
    int data;
    struct Node *next;
};

@interface ReversList : NSObject

//链表反转
struct Node* reverseList(struct Node *head);
//构造一个链表
struct Node* contructList(void);
//打印链表中的数据
void printList(struct Node *head);
@end

NS_ASSUME_NONNULL_END

//
//  ReversList.m
//  GCD
//
//  Created by 龚魁华 on 2020/4/23.
//  Copyright © 2020 龚魁华. All rights reserved.
//

#import "ReversList.h"

@implementation ReversList

//链表反转
struct Node* reverseList(struct Node *head){
    //定义遍历指针,初始化为头节点
    struct Node *p = head;
    //反转后的链表头部
    struct Node *newH = NULL;
    
    //遍历链表
    while (p != NULL) {
        //记录下一个结点
        struct Node *temp = p->next;
        //当前结点的next指向链表头部
        p->next = newH;
        //更改新链表头部为当前结点
        newH = p;
        //移动p指针
        p = temp;
    }
    
    //返回反转后的链表头结点
    return newH;
}

//构造一个链表
struct Node* contructList(void){
    //头结点定义
    struct Node *head = NULL;
    //记录当前尾结点
    struct Node *cur = NULL;
    
    for (int i = 1; i < 5; i++) {
        struct Node *node = malloc(sizeof(struct Node));
        node->data = i;
        //头结点为空,新结点即为头结点
        if (head == NULL) {
            head = node;
        }
        //当前结点的next为新结点
        else{
            cur->next = node;
        }
        //设置当前结点为新结点
        cur = node;
    }
    return head;
}

//打印链表中的数据
void printList(struct Node *head){
    struct Node *temp = head;
    while (temp != NULL) {
        printf("node is %d\n",temp->data);
        temp = temp->next;
    }
}
@end

字符串交换

//字符串交换
void char_reverse(char *cha){
    //指向第一个字符
    char *begin = cha;
    char *end = cha + strlen(cha) -1;
    
    while (begin

有序数组合并

/// 有序数组合并
/// @param a 第一个数组
/// @param aLen 数组长度
/// @param b 第二个数组
/// @param bLen <#bLen description#>
/// @param result 合并后结果
void mergeList(int a[],int aLen,int b[],int bLen,int result[]){
    int p = 0;//遍历数组a的指针
    int q = 0;//遍历数组b的指针
    int i = 0;//记录当前存储位置
    //任意数组没有达到边界则进行遍历
    while (p

寻找2个view的共同父视图

    - (NSArray  *)finCommonSuperView:(UIView *)viewOne other:(UIView *)viewOther{
    NSMutableArray *result = [NSMutableArray array];
    //查找第一个父视图的所有父视图
    NSArray *arrayOne = [self findSuperViews:viewOne];
    //查找第二个父视图的所有父视图
    NSArray *arrayOther = [self findSuperViews:viewOther];
    int i = 0;
    //越界限制条件
    while (i *)findSuperViews:(UIView *)view{
    //初始化为第一父视图
    UIView *temp = view.superview;
    //保存结果数组
    NSMutableArray *result = [NSMutableArray array];
    while (temp) {
        [result addObject:temp];
        //顺着superview指针一直向上查找
        temp = temp.superview;
    }
    return result;
}

无序数组中位数查找

int findMedian(int a[],int aLen){
    int low = 0;
    int high = aLen -1;
    int mid = (aLen - 1)/2;
    int div = PartSort(a,low,high);
    while (div != mid) {
        if (mid < div) {
            //左半区间查找
            div = PartSort(a,low,div - 1);
        }else{
            //右半区间查找
             div = PartSort(a,div+1,high);
        }
    }
    //找到了
    return a[mid];
}

int PartSort(int a[], int start,int end){
    int low = start;
    int high = end;
    //选取关键字
    int key = a[end];
    while (low=key) {
            --high;
        }
        if (low

你可能感兴趣的:(iOS 面试总结)