将来跳槽用面试题面试

以大厂面试真题总结,构建自己的iOS开发体系(中)

2021-04-08  本文已影响0人  iOS弗森科

在百度三面被挂掉之后,沉下心来,整理构建自己的开发体系,方便以后查看。 金三银四已经降临,为此提供了找了不少学习方向给大家,也是一些进价方向,希望能帮大家快速提升自己的短板!

持续更新,敬请关注

本章节:

标:不要浪费美好的年华,做自己觉得对的事情!

目录


感谢观看下载资料:2021年【最新iOS面试题】附答案

目录


下载地址:

地址:https://docs.qq.com/doc/DVU5LY1Bsb3dSZ3Nn

四、Block

17、block相关
1、block本质是什么?
- block是将函数及其执行上下文封装起来的对象

2、关于block的截获特性,你是否有了解?block的截获变量特性是怎样的?
变量捕获机制分析:
- 对于“基本数据类型”的“局部变量”截获其值
- 对于“对象”类型的局部变量“连同所有权修饰符”一起截获
- 以“指针形式”截获局部静态变量(指针传递)
- 不截获全局变量、静态全局变量(直接访问)

改外部变量必要条件
- 将auto从栈copy到堆
原因:栈中内存管理是由系统管理,出了作用域就会被回收,堆中才是可以由程序员管理

3、对栈上的block进行copy之后,假如在mrc环境下,内存是否回泄漏?
- copy操作之后,堆上的block没有额外的成员变量指向它,正如我们alloc对象后,没有进行release,造成内存泄漏

4、面试题:请思考,这段代码有问题么?
{
    __block MCBlock *blockSelf = self;
    _blk = ^int(int num){
        return num * blockSelf.var;
    }
    _blk(3);
}
- 在MRC下,不会产生循环引用
- 在ARC下,会产生循环引用,造成内存泄漏

5、为什么block会产生循环引用?
- 如果当前block对当前对象的某一成员变量进行截获,block会对当前对象有一个强引用
- 而当前block由于当前对象对其有一个强引用,产生了一个自循环引用的一个循环引用的问题

6、Block不允许修改外部变量的值
原因:
- block 本质上是一个对象,block 的花括号区域是对象内部的一个函数,变量进入 花括号,实际就是已经进入了另一个函数区域---改变了作用域。
- 在几个作用域之间进行切换时,如果不加上这样的限制,变量的可维护性将大大降低。
- 比如想在block内声明了一个与外部同名的变量,此时是允许呢还是不允许呢?只有加上了这样的限制,这样的情景才能实现。

- 所以 Apple 在编译器层面做了限制,如果在 block 内部试图修改 auto 变量(无修饰符),那么直接编译报错。
- 可以把编译器的这种行为理解为:对 block 内部捕获到的 auto 变量设置为只读属性---不允许直接修改。

7、如何实现对外部变量的捕获?
- 将变量设置为全局变量。原理:block内外可直接访问全局变量
- 加 static (放在静态存储区/全局初始化区)。原理是block内部对外部auto变量进行指针捕获
- 最优解:使用__block 关键字

8、__block
- 将auto变量封装为结构体(对象),在结构体内部新建一个同名的auto变量
- block内截获该结构体的指针
- 在block中使用自动变量时,使用指针指向的结构体中的自动变量

__block int var = 10;
void(^blk)(void) = ^{
    var = 20;
};
blk();

转换后的代码:

struct __Block_byref_var_0 {
    void *__isa;
    __Block_byref_var_0 *__forwarding;
    int __flags;
    int __size;
    int var; // 10 => 20 该结构体持有相当于原来自动变量的成员变量
};

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __Block_byref_var_0 *var; // by ref
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_var_0 *_var, int flags=0) : var(_var->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

9、block在修改NSMutableArray需不需要添加__block
- 不需要
- 当变量是一个指针的时候,block里只是复制了一份这个指针,两个指针指向同一个地址。
- 所以,在block里面对指针指向内容做的修改,在block外面也一样生效。

10、block是如何捕获局部变量的?
- block捕获外界变量时,在内部会自动生成同一个属性来保存

11、UIView动画中block回调里self要不要弱引用?
- 不需要,它不会造成循环引用,因为它是类方法。
- 之所以需要弱引用本身,是因为怕对象之间产生循环引用,当前控制器不可能强引用一个类,所以循环无法形成。

12、block里面会不会存在self为空的情况(weak strong的原理)?

__weak typeof(self) weakself = self;
[self wj_refresh_addRefreshHeader:^{
    __strong typeof(weakself) strongself = weakself;
    [strongself.dataSource reloadDataWithCompletion:nil];
}];

- 有时候weakSelf在block里在执行reloadDataWithCompletion还存在
- 但在执行reloadDataWithCompletion前,可能会被释放了
- 为了保证self在block执行过程里一直存在,对他强引用strongSelf

13、__block与__weak的区别
- _block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型
- __weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)
- __block对象可以在block中被重新赋值,__weak不可以。 

14、多层block嵌套如何使用weakSelf?
__weak typeof(self) weakself = self;
[self wj_refresh_addRefreshHeader:^{
    __strong typeof(weakself) strongself = weakself;
    __weak typeof(self) weakSelf2 = strongself;
    [strongself.dataSource reloadDataWithCompletion:^(BOOL result) {
        __strong typeof(self) strongSelf2 = weakSelf2;
    }];
}];

15、Masonry对于block内部引用self会不会造成循环引用?
- 不会
- 这个block没有copy,是在栈上,使用完直接释放了,

- (NSArray *)mas_makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *))block {
    self.translatesAutoresizingMaskIntoConstraints = NO;
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    block(constraintMaker);
    return [constraintMaker install];
}

18、 代理、Block利弊
- 与委托代理模式的代码相比,用block写出的代码更为整洁

代理优点:
- 代理语法清晰,可读性高,易于维护
- 它减少代码耦合性,使事件监听与事件处理分离
- 一个控制器可以实现多个代理,满足自定义开发需求,灵活性较高

代理缺点:
- 实现代理的过程较繁琐
- 跨层传值时加大代码的耦合性,并且程序的层次结构也变得混乱
- 当多个对象同时传值时不易区分,导致代理易用性大大降低

block优点:
- 语法简洁,代码可读性和维护性较高
- 配合GCD优秀的解决多线程问题
block缺点:
- Block中得代码将自动进行一次retain操作,容易造成内存泄漏
- Block内默认引用为强引用,容易造成循环应用

运行成本:
delegate运行成本低,block的运行成本高
- block出栈需要将使用的数据从栈内存拷贝到堆内存,当然对象的话就是假引用技术,使用完block置nil才会消除
- delegate只是保存了一个对象的指针,直接回调,没有额外的消耗。就像c的函数指针,只多了一个查表动作

19、有哪些情况会出现内存泄漏。
- block循环引用
- delegate循环引用问题
- NSTimer循环引用
- 地图类处理

- 线程保活target:self

20、__weak来解决block中的循环引用,还有别的方法吗。
- __block
- 将对象传进入修改


五、Runtime

21、以下方法打印什么
@implementation Son : Father

- (id)init {
    self = [super init];
    if (self) {
        NSLog(@"%@", NSStringFromClass([self class]));
        NSLog(@"%@", NSStringFromClass([super class]));
    }
    return self;
}

@end

这两个都打印出来的是:Son.

- [self class] 会先在当前类的方法列表中查找class这个方法
- [super class] 会先到父类中去查找class方法
- 两者在找不到的时候,都会继续向祖先类查询class方法,最终到NSObject类

- NSObject中class的实现
- (Class)class {
    return object_getClass(self);
}

22、Runtime如何通过selector找到对应的IMP地址?IMP和SEL关系是?
- SEL:类方法的指针,相当于一种编号,区别与IMP!
- IMP:函数指针,保存了方法的地址!

关系:SEL是通过表取对应关系的IMP,进行方法的调用!

struct objc_method {
    SEL method_name                                      
    char *method_types                                       
    IMP method_imp                                           
}

23、Runtime的相关术语
SEL、id、Class、Method、IMP、Cache、Property

- 介绍下runtime的内存模型(isa、对象、类、metaclass、结构体的存储信息等)
- 为什么要设计metaclass
- class_copyIvarList & class_copyPropertyList区别
- class_rw_t 和 class_ro_t 的区别

23、交互两个方法的现实有什么风险?
- class_replaceMethod
- method_exchangeImplementations
- class_getInstanceMethod

个人经验总结:
- 当我们写的类没有继承的关系的时候,俩种方法都没什么问题
- 当有继承关系又不确定方法实现没实现,最好用class_replaceMethod方法

补充:在美图秀秀面试时,一个面试官问到方法交互,我说就是交换两个放的IMP指针指向,
他问还有么?不知道还有什么,现在想起来,他应该是想问从isa指针到方法查找,再到根据SEL查找IMP过程吧

- 多次hook方法会存在什么问题?

TODO(待填充);⌛️⌛️⌛️⌛️⌛️

24、对象关联底层数据结构
通过 runtime 的源码得知:
- 关联属性并没有添加到 category_t(分类)里边
- 运行时也不会合并到元类对象里边
- 而是存储在一个全局的AssociationsManager里边

#import <objc/runtime.h>
// 添加
objc_setAssociatedObject(<#id  _Nonnull object#>, <#const void * _Nonnull key#>, <#id  _Nullable value#>, <#objc_AssociationPolicy policy#>)
// 获取
objc_getAssociatedObject(<#id  _Nonnull object#>, <#const void * _Nonnull key#>)
// 移除
objc_removeAssociatedObjects(<#id  _Nonnull object#>)

- 关联对象的应用?系统如何实现关联对象的
- 关联对象的如何进行内存管理的?关联对象如何实现weak属性
- 关联的对象,需要在主对象dealloc的时候释放么?

被关联的对象的生命周期内要比对象本身释放晚很多, 它们会在被 NSObject -dealloc 调用的 object_dispose() 方法中释放。

image
25、消息转发流程,向一个nil对象发送消息会怎样
转发过程(一定要回答出从缓存中查找)
- 消息发送
- 动态方法解析
- 消息转发

1、消息发送过程 objc_msgSend(receiver, selector)
- 向一个对象发送消息时,runtime会根据对象的isa指针找到所属类
- 在该类的方法列表及父类方法列表中寻找方法(缓存)
- 如果在最顶层父类中依然找不到对应方法,会报 unrecognized selector send to xxx

2、向一个nil对象发送消息会怎样?
- objc_msgSend会通过判断self来决定是否发送消息
- 如果self为nil,那么selector也会为空,直接返回,不会出现问题
- 但对于[NSNull null]对象发送消息时,是会crash的,因为NSNull类只有一个null方法

在崩溃前有三次拯救程序崩溃的机会,就是接下来的消息转发

3、消息转发流程
TODO(待填充);⌛️⌛️⌛️⌛️⌛️

26、performSelector:withObject:afterDelay: 内部大概是怎么实现的,有什么注意事项么?
内部实现:
- 创建一个定时器, 时间结束后系统会使用runtime通过方法名称去方法列表中找到对应的方法实现并调用方法
- Selector本质就是方法名称

注意事项:
- 调用performSelector:withObject:afterDelay:方法时,先判断希望调用的方法是否存在respondsToSelector:
- 这个方法是异步方法,必须在主线程调用,在子线程调用永远不会调用到想调用的方法


六、Runloop

27、RunLoop相关
什么是RunLoop?
- RunLoop 实际上是一个对象
- 这个对象在循环中用来处理程序运行过程中出现的各种事件(比如说触摸事件、UI刷新事件、定时器事件、Selector事件)
- 从而保持程序的持续运行
- 在没有事件处理的时候,会使线程进入睡眠模式,从而节省 CPU 资源,提高程序性能

// 简单的理解为如下代码
int main(int argc, char * argv[]) {        
    BOOL running = YES;
    do {
        // 执行各种任务,处理各种事件
        // ......
    } while (running);  // 判断是否需要退出

    return 0;
}

- 讲讲runloop,项目中有用到么?
- runloop内部实现逻辑?
- timer与runloop的关系?
- 程序中添加每3秒响应一次的NSTimer,当拖动tableview时timer可能无法响应要怎么解决?
- runloop是怎么响应用户操作的,具体流程是什么样的?
- 说说runloop的几种状态?
- runloop的mode作用是什么

int main(int argc, char * argv[]) {
    @autoreleasepool {
        int retVal = 0;
        do {
            // 睡眠中等待消息
            int message = sleep_and_wait;
            // 处理消息
            retVal = process_message(message);
        } while (retVal == 0)
        return 0;
    }
}

- 定时器(Timer)、PerformSelect
- GCD Async Main Queue
- 事件响应、手势识别、界面刷新
- 网络请求
- AutoreleasePool

- 保持程序的持续运行
- 处理App中的各种事件(比如触摸事件、定时器事件等)
- 节省CPU资源,提高程序性能:该做事时做事,该休息时休息

- 每条线程都有唯一的一个与之对应的RunLoop对象
- RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
- 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
- RunLoop会在线程结束时销毁
- 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop

/*
 * 从字典中获取,如果没有则直接创建
 */
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFSpinUnlock(&loopsLock);
if (!loop) {
    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
    __CFSpinLock(&loopsLock);
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));

    if (!loop) {
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
        loop = newLoop;
    }

    // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
    __CFSpinUnlock(&loopsLock);
    CFRelease(newLoop);
}

28、 NSTimer相关
1、NSTimer准吗?如果不准的话原因是什么?如何解决?
原因:
- NSTimer的触发时间到的时候,会在RunLoop中被检测一次;
- 如果在这一次的RunLoop中做了耗时的操作,会处于阻塞状态
- 时间超过了定时器的间隔时间,触发时间就会推迟到下一个runloop周期

解决方法:
- 在子线程中创建timer,在子线程中进行定时任务的操作,需要UI操作时切换回主线程进行操作
- 使用CADisplayLink(时钟???)
- 使用GCD定时器

2、使用NSTimer是如何处理循环引用的?
- 使用类方法
TODO(待填充);⌛️⌛️⌛️⌛️⌛️


拓展、如何利用runloop监控卡顿

七、KVO

29、KVO相关

KVO 的 全称Key-Value Observing,俗称“键值监听”,可以用于某个对象属性值的改变

1、iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么)
- 利用runtimeAPI动态生成一个子类(NSKVONotifying_XXXX),并且让instance对象的isa指向这个全新的子类
- 当修改instance对象的属性时,会动用Foundation的_NSSetXXXValueAndNotify函数
- willChangeValueForKey
- 父类原来的setter方法
- didChangeValueForKey
- 内部触发监听器(ObserveValueForKeyPath:ofObject:change:context)

2、如何手动触发KVO
- 手动调用willChangeValueForKey
- 修改成员变量值
- 手动调用didChangeValueForKey

3、直接修改成员变量会触发KVO么
- 不会触发KVO(原因看KVO的本质)

4、object_getClass(self.person) 和 [self.person class];分别打印什么?为什么?
- object_getClass(self.person); -> NSKVONotifying_MJPerson
- [self.person class];          -> MJPerson

- 原因:NSKVONotifying_MJPerson重写底层实现,目的:隐藏动态创建的类,不让用户感知
- (Class)class {
    return [MJPerson class];
}

// 伪代码 Function框架
void _NSSetIntValueForKey(){
    [self willChangeValueForKey:@"age"];
    [self setAge:age];
    [self didChangeValueForKey:@"age"];
}

// 通知监听器
- (void)didChangeValueForKey:(NSString *)key {
   [obser observeValueForKeyPath:key ofObject:self change:nil content:nil];
}

其他:
根据地址打印方法:p (IMP)0X1065....
类对象:  object_getClass(self.person);
原类对象:object_getClass(object_getClass(self.person));


八、KVC

30、KVC相关

KVC的全称是Key-Value Coding,俗称“键值编码”,可以通过一个key来访问某个属性

1、通过KVC修改属性会出发KVO么?
- 能触发KVO()
- KVC在修改属性时,会调用willChangeValueForKey:和didChangeValueForKey:方法;

2、KVC的赋值和取值过程是怎样的?原理是什么?
- 见下图

3、使用场景
- 单层字典模型转化:[self.model setValuesForKeysWithDictionary:dict];

- 通过KVC修改未暴露的属性:
UILabel *placeholderLabel=[self.userTextField valueForKeyPath:@"placeholderLabel"];
placeholderLabel.textColor = [UIColor redColor];

- 使用valueForKeyPath可以获取数组中的最小值、最大值、平均值、求和
CGFloat sum = [[array valueForKeyPath:@"@sum.floatValue"] floatValue];
CGFloat avg = [[array valueForKeyPath:@"@avg.floatValue"] floatValue];
CGFloat max =[[array valueForKeyPath:@"@max.floatValue"] floatValue];
CGFloat min =[[array valueForKeyPath:@"@min.floatValue"] floatValue];

- 数组内部去重
[dataArray valueForKeyPath:@"@distinctUnionOfObjects.self"]

- 数组合并(去重合并:distinctUnionOfArrays.self、直接合并:unionOfArrays.self)
NSArray *temp1 = @[@3, @2, @2, @1];
NSArray *temp2 = @[@3, @4, @5];

NSLog(@"\n%@",[@[temp1, temp2] valueForKeyPath:@"@distinctUnionOfArrays.self"]);
NSLog(@"\n%@",[@[temp1, temp2] valueForKeyPath:@"@unionOfArrays.self"]);

输出两个数组:( 5, 1, 2, 3, 4 ), ( 3, 2, 2, 1, 3, 4, 5 )。

- 大小写转换(uppercaseString)及 打印字符串长度同样适用(length)
NSArray *array = @[@"name", @"w", @"aa", @"jimsa"];
NSLog(@"%@", [array valueForKeyPath:@"uppercaseString"]);
打印:
(NAME,W,AA,JIMSA)

image
- 首先会按照setKey、_setKey的顺序查找方法,找到方法,直接调用方法并赋值;
- 未找到方法,则调用+ (BOOL)accessInstanceVariablesDirectly;
- 若accessInstanceVariablesDirectly方法返回YES,则按照_key、_isKey、key、isKey的顺序查找成员变量,找到直接赋值,找不到则抛出异常;
- 若accessInstanceVariablesDirectly方法返回NO,则直接抛出异常;

image
- 首先会按照getKey、key、isKey、_key的顺序查找方法,找到直接调用取值
- 若未找到,则查看+ (BOOL)accessInstanceVariablesDirectly的返回值,若返回NO,则直接抛出异常;
- 若返回的YES,则按照_key、_isKey、key、isKey的顺序查找成员变量,找到则取值;
- 找不到则抛出异常;


九、Category

31、Category相关
1、Category的使用场合是什么?
- 将一个类拆成很多模块(其实就是解耦,将相关的功能放到一起)

2、说说Category的实现原理
- 通过runtime动态将分类的方法合并到类对象、元类对象中
- Category编译之后的底层结构是 struct_category_t , 里面存储着分类的对象方法、类方法、属性、协议信息
- 在程序运行的时候,runtime会将 Category 的数据,合并到类信息中(类对象、元类对象)

3、category和extension区别
- Extension在编译时,就把信息合并到类信息中
- Category是在运行时,才会将分类信息合并到类信息中
- 分类声明的属性,只会生成getter/setter方法声明,不会自动生成成员变量和getter/setter方法实现,而扩展会
- 分类不可用为类添加实例变量,而扩展可以

分类的局限性:
- 无法为类添加实例变量,但可通过关联对象进行实现
- 分类的方法如果和类重名,会覆盖原来方法的实现
- 多个分类的方法重名,会调用最后编译的那个分类的实现

4、为什么category不能添加属性?使用Runtime就可以了?
- 分类没有自己的isa指针
- 类最开始生成了很多基本属性,比如IvarList,MethodList
- 分类只会将自己的method attach到主类,并不会影响到主类的IvarList
- 实例变量没有setter和getter方法。也没有自己的isa指针

- 关联对象都由AssociationsManager管理
- AssociationsManager里面是由一个静态AssociationsHashMap来存储所有的关联对象的。
- 相当于把所有对象的关联对象都存在一个全局map里面。而map的的key是这个对象的指针地址
- 而这个map的value又是另外一个AssAssociationsHashMap,里面保存了关联对象的kv对

5、Category中有load方法么?load方法什么时候调用的?load方法能继承么?
- 有
- +load方法会在runtime加载类、分类时调用;
- 每个类、分类的+load,在程序运行过程中只调用一次

- 调用顺序
- 先调用类的+load,(按照编译先后顺序,先编译,先调用),调用子类的+load之前会调用父类的+load
- 再调用分类的+load按照编译先后顺序调用(先编译,先调用)

6、test方法和load方法的本质区别?(+load方法为什么不会被覆盖)
- test方法是通过消息机制调用 objc_msgSend([MJPerson class], @selector(test))
- +load方法调用,直接找到内存中的地址,进行方法调用

7、load调用顺序
- +load方法会在runtime加载类、分类时调用
- 每个类、分类的+load,在程序运行过程中只调用一次

调用顺序
- 先调用类的+load方法,之后按照编译先后顺序调用(先编译,先调用,调用子类的+load之前会先调用父类的+load)
- 再调用分类的+load,之后按照编译先后顺序调用(先编译,先调用)

8、不同Category中存在同一个方法,会执行哪个方法?如果是两个都执行,执行顺序是什么样的?
- 根据Build Phases->Compile Sources中添加的文件顺序,后面的会覆盖前面的

9、load、initialize方法的区别是什么?它们在category中的调用顺序?以及出现继承时他们之间的调用过程?
区别:
调用方式不同
- load是根据函数地址直接调用
- initialize是荣光objc_msgSend调用

调用时刻
- load是runtime加载 类/分类 的时候调用(只会调用1次)
- initialize是类第一次接收消息时调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)

调用顺序
- load:先调用类的load。先编译的类,优先调用load(调用子类的load之前,会先调用父类的load)
- 再调用分类的load(先编译的分类,优先调用load)
- initialize:先初始化父类,  再初始化子类(可能最终调用的是父类的initialize方法)

10、⚠️:
- category的方法没有“完全替换掉”原来类已经有的方法,也就是说如果category和原来类都有methodA,那么category附加完成之后,类的方法列表里会有两个methodA
- category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面
- 这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会罢休_,殊不知后面可能还有一样名字的方法。

11、为什么不能动态添加成员变量?
- 方法和属性并不“属于”类实例,而成员变量“属于”类实例
- “类实例”概念,指的是一块内存区域,包含了isa指针和所有的成员变量。
- 假如允许动态修改类成员变量布局,已经创建出的类实例就不符合类定义了,变成了无效对象。但方法定义是在objc_class中管理的,不管如何增删类方法,都不影响类实例的内存布局,已经创建出的类实例仍然可正常使用


十、网络

32、TCP、UDP各自的优缺点及区别
TCP优点:( 可靠,稳定)
- 在传递数据之前,会有三次握手来建立连接,
- 在数据传递时,有确认、窗口、重传、拥塞控制机制,
- 在数据传完后,还会断开连接用来节约系统资源

TCP缺点:(慢,效率低,占用系统资源高)
- TCP在传递数据之前,要先建连接,这会消耗时间
- 在数据传递时(确认机制、重传机制、拥塞控制机制)等都会消耗大量的时间
- 因为TCP有确认机制、三次握手机制,这些也导致TCP容易被人利用,实现DOS、DDOS、CC等攻击

UDP的优点:(快)
- UDP没有TCP的握手、确认、窗口、重传、拥塞控制等机制
- UDP是一个无状态的传输协议,所以它在传递数据时非常快

UDP的缺点:(不可靠,不稳定)
- 因为UDP没有TCP那些可靠的机制,在数据传递时,如果网络质量不好,就会很容易丢包

小结TCP与UDP的区别:
- TCP面向连接(如打电话要先拨号建立连接); UDP是无连接的,即发送数据
- TCP提供可靠的服务。通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
- TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流; UDP是面向报文的
- 每一条TCP连接只能是点到点的; UDP支持一对一,一对多,多对一和多对多的交互通信

33、Scoket连接和HTTP连接的区别
- HTTP协议是基于TCP连接的,是应用层协议,主要解决如何包装数据。Socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API),通过Socket,我们才能使用TCP/IP协议。
- HTTP连接:短连接,客户端向服务器发送一次请求,服务器响应后连接断开,节省资源。服务器不能主动给客户端响应,iPhone主要使用类NSURLConnection
- Socket连接:长连接,客户端跟服务器端直接使用Socket进行连接,没有规定连接后断开,因此客户端和服务器段保持连接通道,双方可以主动发送数据

34、HTTP协议的特点,关于HTTP请求GET和POST的区别
特点:
- HTTP超文本传输协议,是短连接,是客户端主动发送请求,服务器做出响应,服务器响应之后,链接断开
- HTTP是一个属于应用层面向对象的协议,HTTP有两类报文:请求报文和响应报文
- HTTP请求报文:一个HTTP请求报文由请求行、请求头部、空行和请求数据4部分组成
- HTTP响应报文:由三部分组成:状态行、消息报头、响应正文

GET请求
- 参数在地址后拼接,不安全(因为所有参数都拼接在地址后面)
- 不适合传输大量数据(长度有限制,为1024个字节)

POST请求
- 参数在请求数据区放着,相对GET请求更安全
- 数据大小理论上没有限制
- 提交的数据放置在HTTP包的包体中

35、断点续传怎么实现的?
- 断点续传主要依赖于 HTTP 头部定义的 Range 来完成
- 有了 Range,应用可以通过 HTTP 请求获取失败的资源,从而来恢复下载该资源
- 当然并不是所有的服务器都支持 Range,但大多数服务器是可以的。Range 是以字节计算的,请求的时候不必给出结尾字节数,因为请求方并不一定知道资源的大小

36、网络层相关面试

- 网络七层协议

- Charles原理

- HTTP和HTTPS的区别?Https为什么更加安全?

- HTTPS的连接建立流程

- 解释一下三次握手和四次挥手

- TCP分片 和 IP分片

- Cookie和Session
[网络相关之Cookie和Session](https://www.jianshu.com/p/5f250c621e81?utm_campaign=hugo)

37、DNS是什么?DNS解析过程

域名系统(Domain Name System,DNS)
因特网上的主机,可以使用多种方式标识:

1、区别:
- 主机名:方便人们记忆和接受,但长度不一、没有规律的字符串,路由器并不方便处理
- IP地址:路由器方便处理,不便于人们记忆

为了折衷这两种方式,需要一种能进行主机名到IP地址转换的目录服务,就是 域名系统(Domain Name System,DNS)

2、作用:
- 将用户提供的主机名解析为IP地址

3、DNS解析过程(以www.163.com为例:)
- 打开浏览器,输入一个域名(www.163.com)。客户端会发出一个DNS请求到本地DNS服务器(本地DNS服务器一般都是你的网络接入服务器商提供,比如中国电信,中国移动)
- 本地DNS服务器会首先查询它的缓存记录,如果缓存中有此条记录,直接返回结果。如果没有,向DNS根服务器进行查询。
- 根DNS服务器没有记录具体的域名和IP地址的对应关系,而是给出域服务器的地址,告诉他可以到域服务器上去继续查询
- 本地DNS服务器继续向域服务器发出请求,在这个例子中,请求的对象是.com域服务器。
- .com域服务器收到请求之后,也不会直接返回域名和IP地址的对应关系,而是告诉本地DNS服务器,你的域名的解析服务器的地址。
- 最后,本地DNS服务器向域名的解析服务器发出请求,这时就能收到一个域名和IP地址对应关系,
- 本地DNS服务器不仅要把IP地址返回给用户电脑,还要把这个对应关系保存在缓存中,以备下次别的用户查询时,可以直接返回结果,加快网络访问。

过程:本地服务器->根服务器->域服务器->域名解析服务器
- 整合成流程图
TODO(待填充);⌛️⌛️⌛️⌛️⌛️

十一、UI

41、Storyboard/Xib和纯代码UI相比,有哪些优缺点?
storyboard/xib优点
- 简单直接。直接通过拖拽和点选即可完成配置。
- 跳转关系清楚

缺点:
- 协作冲突(多人提交代码)
- 很难做到页面继承和重用
- 不便于进行模块化管理
- 影响性能(多图层渲染)

42、自动布局AutoLayout原理,性能如何
戴明-iOS开发高手课 - 03

43、说明比较方法:layoutifNeeded、layoutSubviews、setNeedsLayout
44、如果页面 A 跳转到 页面 B,A 的 viewDidDisappear 方法和 B 的 viewDidAppear 方法哪个先调用?
- A -->viewWillDisappear
- B-->viewWillAppear
- A-->viewDidDisappear
- B-->viewDidAppear

45、离屏渲染,隐式动画和显式动画相关
⚠️经常看到,圆角会触发离屏渲染。但其实这个说法是不准确的,因为圆角触发离屏渲染也是有条件的!

1、离屏渲染触发条件:
- 背景色、边框、背景色+边框,再加上圆角+裁剪,因为 contents = nil 没有需要裁剪处理的内容,所以不会造成离屏渲染。
- 一旦为contents设置了内容,无论是图片、绘制内容、有图像信息的子视图等,再加上圆角+裁剪,就会触发离屏渲染。

2、在一个表内有很多cell,每个cell上有很多个视图,如何解决卡顿问题?
3、切圆角一定会触发离屏渲染吗?

4、iOS 9及之后的系统版本,苹果进行了一些优化
- 只设置contents或者UIImageView的image,并加上圆角+裁剪,是不会产生离屏渲染的。
- 但如果加上了背景色、边框或其他有图像内容的图层,还是会产生离屏渲染。
- 使用类似于UIButton的视图的时候需要注意

46、frame和bouns的区别。什么时候frame和bouns的高宽不相等
旋转后怎么样

47、事件响应过程(响应链)
1、事件的传递 (寻找最合适的view的过程)
- 当一个事件发生后,事件会从父控件传给子控件 (UIApplication->UIWindow->UIView->initial view)

2、事件的响应
- 首先看initial view能否处理这个事件,如果不能则会将事件传递给其上级视图(inital view的superView)
- 如果上级视图仍然无法处理则会继续往上传递;一直传递到视图控制器view controller,首先判断视图控制器的根视图view是否能处理此事件
- 如果不能则接着判断该视图控制器能否处理此事件,如果还是不能则继续向上传递
- 一直到window,如果window还是不能处理此事件则继续交给application处理,如果最后application还是不能处理此事件则将其丢弃

3、⚠️注意
- 事件的传递是从上到下(父控件到子控件)
- 事件的响应是从下到上(顺着响应者链条向上传递:子控件到父控件)

4、重要方法:
4.1、hitTest:withEvent:
- 只要事件一传递给一个控件,这个控件就会调用他自己的hitTest:withEvent:方法
- 寻找并返回最合适的view(能够响应事件的那个最合适的view)

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    // 1.判断自己能否接收触摸事件
    if (self.userInteractionEnabled == NO
        || self.hidden == YES
        || self.alpha <= 0.01) {
        return nil;
    }
    // 2.判断触摸点在不在自己范围内
    if (![self pointInside:point withEvent:event]) {
        return nil;
    }
    // 3.从后往前遍历自己的子控件,看是否有子控件更适合响应此事件
    for(NSInteger i = self.subviews.count; i >= 0; i --) {
        UIView *childView = self.subviews[i];
        CGPoint childPoint = [self convertPoint:point toView:childView];
        UIView *fitView = [childView hitTest:childPoint withEvent:event];
        if (fitView) {
            return fitView;
        }
    }
    // 没有找到比自己更合适的view
    return self;
}

4.2、pointInside:withEvent:
- 判断点在不在当前view上(方法调用者的坐标系上)
- 如果返回YES,代表点在方法调用者的坐标系上;
- 返回NO代表点不在方法调用者的坐标系上,那么方法调用者也就不能处理事件。

5、穿透
- 假设有一个黄色控件和白色控件,白色空间覆盖在黄色控件上
- 点击白色view想要黄色view来响应该事件,就是所谓的穿透

方法一、
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    CGPoint yellowPoint = [self convertPoint:point toView:_yellowView];
    if ([_yellowView pointInside:yellowPoint withEvent:event]) {
        return _yellowView;
    }
    return [super hitTest:point withEvent:event];
}

方法二、
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    CGPoint yellowPoint =[_yellowView convertPoint:point fromView:self];
    if ([_yellowView pointInside:yellowPoint withEvent:event]){
        return NO;
    } else {
        return [super pointInside:point withEvent:event];
    }
}

下一章:百度三面被挂掉之后,沉下心来总结,构建自己的iOS开发体系(下)


下载地址:

地址:https://docs.qq.com/doc/DVU5LY1Bsb3dSZ3Nn

小编文章请观看合集

文章来源作者:强子ly

上一篇下一篇

猜你喜欢

热点阅读