OC

作者:南泽1
链接:https://zhuanlan.zhihu.com/p/146761745
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

1. OC语法

1. OC中对象的结构(腾讯一面)

  1. Instance对象

  2. 如果是NSObject对象,对象中只有一个isa指针,在64位中占16个字节(可以通过malloc_size函数获得),但实际只用到了8个字节(可以通过class_getInstanceSize函数获得)。

  3. 这个isa指针指向了class类对象,通过isa指针&isa_mask可以获得NSObject类的地址。

  4. 如果自定义的类,如果增加了属性或成员变量,对象中会有成员变量值得存储地址。

  5. 对象方法、属性、成员变量、协议信息存放在类对象中,类方法存放在元类对象(可以通过get_class获得)中

  6. class对象

[图片上传失败...(image-609f4d-1592965813716)]

  1. meta-class对象

  2. isa指针指向了基类的meta-class对象,superClass指针指向了父类的meta-class对象。基类的meta-class对象的superClass指针指向了基类的class对象
    2.结构与class对象一致,只是只存储了类方法信息

4 isa指针

  1. 在arm64架构之前isa指针直接存储了class、meta-class对象的地址
  2. 在arm64之后isa指针经过优化,使用了公用体(union)结构

[图片上传失败...(image-9414c0-1592965813716)]

2. assign、strong、weak、copy、unsafe_unretained

  1. strong:一般用于id类型和对象的修饰符,是使用__strong修饰符,instance对象会持有该对象,setter方法中会调用retain,并且retainCount+1
  2. assign:一般用于基本数据类型,是使用__unsafe_unretained修饰符,setter时会直接赋值
  3. weak:是使用__weak修饰符,如果retainCount为0,对象会被自动释放,并设置成nil
  4. copy:是使用__strong修饰符,但是赋值的是被赋值的对象
  5. unsafe_unretained:是使用__unsafe_unretained修饰符,

3. assing可以使用在对象中吗(头条一面)
可以,但会造成坏内存访问的错误
4. weak如何实现自动赋nil(头条一面)
runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc, 在这个 weak 表中搜索,找到所有以key为键的 weak 对象,从而设置为 nil。
5. 可变数组的实现原理(头条二面)
__NSArrayM 用了环形缓冲区 (circular buffer),环形缓冲区有一些非常酷的属性。尤其是,除非缓冲区满了,否则在任意一端插入或删除均不会要求移动任何内存。我们来分析这个类如何充分利用环形缓冲区来使得自身比 C 数组强大得多。在任意一端插入或者删除,只是修改offset参数,不需要移动内存,我们访问的时候只是不和普通的数组一样index多少就是多少,这里会计算加上offset之后处理的值取数据,而不是插入头和尾巴的时候,环形结构会根据最少移动内存指针的方式插入,例如要在A和B之间插入,按照C的数组,我们需要把B到E的元素移动内存,但是环形缓冲区的设计,我们只要把A的值向前移动一个单位内存,即可,同时修改offset偏移量,就能保证最小的移动单元来完成中间插入。
往中部插入对象有非常相似的结果。合理的解释就是,__NSArrayM 试着去最小化内存的移动,因此会移动最少的一边元素。
NSMutableArray 是一个高级抽象数组,解决了 C 风格数组对应的缺点。(C数组插入的时候都会移动内存,不是O(1),用到了环形缓冲区数据结构来处理内存移动的损耗)
但是可变数组任意一端插入或删除能有固定时间的性能。而且在中间插入和删除的时候都会试着去移动最小化内存。
环形缓冲区的数据结构如果是连续数组结构,在扩容的时候难免会移动大量内存,因此用链表实现环形缓冲会更好
6. KVO的实现原理(美团一面、阿里一面)

  1. 通过runtime API动态生成一个子类,让instance对象的isa指针只想这个子类,
  2. 当修改instance对象属性的时候,会调用Fondation框架里的NSSetXXXValueAndNotify方函数
    -willChangeValueForKey
    调用父类原有的set方法
    -didChangeValueForKey
    内部触发器会调用监听方法-observeValueForKeyPath:ofObject:change:context:

7. Block的循环引用、如何解决、原理(阿里一面)
block的本质是封装了函数调用和环境的代码块,但我感觉可以把它看成是一个对象,因为block内部也有isa指针

  1. 三种block:globalBlock、stackBlock、mallocBlock
  2. block在修饰auto变量是值传递,在修饰静态变量是指针传递,在修饰全局变量不需要传递参数
  3. 如果block被拷贝到堆上,内部会调用_block_object_assign,如果auto对象是被__strong或__copy修饰会形成强引用
  4. 可以用__weak或__unsafe_unretained

8. Block和delegate的比较

  1. 从源头上理解和区别block和delegate
    delegate运行成本低,block的运行成本高。
    block出栈需要将使用的数据从栈内存拷贝到堆内存,当然对象的话就是加计数,使用完或者block置nil后才消除。delegate只是保存了一个对象指针,直接回调,没有额外消耗。就像C的函数指针,只多做了一个查表动作。
  2. 从使用场景区别block和delegate
    有多个相关方法。假如每个方法都设置一个 block, 这样会更麻烦。而 delegate 让多个方法分成一组,只需要设置一次,就可以多次回调。当多于 3 个方法时就应该优先采用 delegate。当1,2个回调时,则使用block。
  3. delegate更安全些,比如: 避免循环引用。使用 block 时稍微不注意就形成循环引用,导致对象释放不了。这种循环引用,一旦出现就比较难检查出来。而 delegate 的方法是分离开的,并不会引用上下文,因此会更安全些。
  4. delegate回调返回的参数被限制在了 NS 类的范围内,数量也很有限(当然可以用直接调用方法的形式在绕过,并不推荐;也可以用 Array 套着传, 不过这样需要有文档支持,不然不够清晰,回调方法也需要独立的验证,故也不推荐)。

如果这个回调是一个不定期触发,或者会多次触发的,那么 Delegation 应该更适合;如果这个回调是一个一次性的,并且和调用方法是单线性关系的,那么 Block 应该更适合。在不同的执行线(不是线程),不同的执行次数、执行数量上的区别,是鉴别使用哪一种回调的最好判断方法。
9. 讲一讲响应链(美团二面)

  1. 事件的每个类型,UIKit指定一个第一响应者,然后最先发送事件到这个对象。
  2. App中包含一个UILable,UITextField,UIButton,以及2个backgroundView,如果UITextField不能响应事件,UIKit发送事件到UITextField的父视图(UIView)对象,随后是UIWindow的根视图(UIView)。从根视图,响应者链在事件传递到UIWindow之前,先转移到所拥有的UIViewController。如果UIWindow不能处理事件,UIKit传递事件到UIApplication对象,也可能到app delegate如果对象是UIResponder的实例并且不是响应链的一部分。

[图片上传失败...(image-69c704-1592965813715)]

10. 如何通过一个view查找它所在的viewController(美团二面)

UIResponser *responser = self.nextResponser;
do  {
    if ([responser isKindOfClass:[UIVC class]])
        return (UIVC *)responser;
    responser = response.nextResponser;
} while (responser);

11. 持久化(阿里一面)

  1. NSUserDefaults:用于存放小数据,只能存储系统自带的数据类型,自定义的对象无法存储

  2. 不需要关心文件名(不需要设置路径)

  3. 键值对存储(账号相关信息) 对象存储

NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    //放到缓存里,并不会马上放到文件里面
[userDefaults setObject:@"123" forKey:@"account"]; //对象
[userDefaults setObject:@"123456" forKey:@"pwd"];
//BOOL类型
[userDefaults setBool:YES forKey:@"status"];
//在ios7 默认不会马上跟硬盘同步  同步操作 起到立即存储的作用
[userDefaults synchronize];

  1. 3属性列表property list:只能存储系统自带的数据类型,一般实际开发中存储字典、数组,自定义的模型无法进行存储
 NSString *kUserPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"latestQuery.plist"];
NSArray  *array = @[];
[arr writeToFile:kUserPath atomically:YES];

4.归档NSKeyedArchiver:可以存储自定义对象,自定义对象想要归档,则自定义对象必须遵守NSCoding协议,实现协议方法

//归档
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
//编码
[archiver encodeObject:self.afterSelectedArray forKey:GMEncodeKey];
//结束编码
[archiver finishEncoding];

//解档
NSData *undata = [[NSData alloc] initWithContentsOfFile:toFileName];
//解档辅助类
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:undata];
//解码并解档出model
NSArray *tempArray = [unarchiver decodeObjectForKey:GMEncodeKey];
//关闭解档
[unarchiver finishDecoding];

  1. 数据库:用于存放一条条的数据
  2. 文件:用于存放图片、文件、音视频等数据

12. 多态(腾讯一面) iOS的多态基于了runtime,可以动态绑定数据类型,添加方法,属性等

13. layoutIfNeeded和setNeedsLayout的区别(头条二面)
更新布局会调用layoutIfNeeded

  • setNeedsLayout
    会标记为需要重新布局,异步调用layoutIfNeeded刷新布局,但不是立刻执行,是在RunLoop下一轮循环结束前刷新。对于这一轮runloop之内的所有布局和UI上的更新只会刷新一次,layoutSubviews一定会被调用。
  • layoutIfNeeded
    如果有需要刷新标记,会立刻调用layoutSubviews进行刷新操作,如果没有则不调用

14. isEquel和hash的关系(头条二面)
两个对象isEquel相等,hash一定相等,但hash相等,isEque不一定相等
15. 有哪些编码方式(头条一面)

NSASCIIStringEncoding = 1,      /* 0..127 only */
NSNEXTSTEPStringEncoding = 2,
NSJapaneseEUCStringEncoding = 3,
NSUTF8StringEncoding = 4,
NSSymbolStringEncoding = 6,

2. Runtime
1. 消息调用的过程(美团一面、阿里一面)

  1. 首先会在reveiceClasscache中查找方法
  2. 如果没有找到,会到reveiceClassclass_rw_t的方法列表中查找
  3. 如果找不到,会到superClasscache中查找
  4. 如果找不到,会到superClassclass_rw_t的方法列表中查找
  5. 如果找不到,会递归3操作,直到superClassnil
  6. 如果superClassnil,会进入消息动态解析
  7. 如果有动态解析,进入消息转发
  8. 如果没有动态解析,会调用+resolveInstanceMethod:+resolveClassMethod:
  9. 动态解析过后,会重新走消息发送机制
  10. 如果没有动态解析,会调用forwardingTargetForSeletor
  11. 如果返回不为nil,调用objc_msgSend(返回值,SEL)
  12. 如果返回nil,会调用methodSignatureForSeletor
  13. 如果返回值不为nil,调用forwardingInvocation
  14. 返回为nil,报错doesNotRecognizeSelector

2. 如何hook一个对象的方法,而不影响其它对象(头条二面)

  1. 可以在分类的load方法里,或是initialaze里使用dispatch_once,
  2. 调用class_addMethod判断是否可以添加方法,可以的话调用class_replaceMethod函数
  3. 或者调用method_exchangeImplementations函数

3. runtime如何通过selector找到对应的IMP地址(阿里一面)

  1. IMP class_getMethodImplementation(Class cls, SEL name); 参考2.1 消息调用的过程
  2. IMP method_getImplementation(Method m) 直接取method_t中的imp指针

3. RunLoop
1. runloop内部实现逻辑?(阿里一面)

  1. source0:触摸事件处理、performSelector:onThread
  2. source1:基于port之间的线程通信、系统时间捕捉
  3. Timers:NSTimer、performSelector:withObject:afterDelay:
  4. Observers:用于监听RunLoop的状态、刷新UI(BeforeWaiting)、AutoreleasePool(BeforeWaiting)

[图片上传失败...(image-92e0a3-1592965813715)]

4. 多线程
1. 队列、进程和线程的区别(阿里一面、头条一面、百度一面)

  1. 进程:进程是操作系统资源分配的基本单位。
  2. 线程:线程是任务调度和执行的基本单位。一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。

地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的资源是独立的。

  1. 队列:队列是一种特殊的线性表

2. 进程间通信的方式(百度一面)

  1. 管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
  2. 命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
  3. 消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  4. 共享存储SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
  5. 信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
  6. 套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
  7. 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

3. 一个进程有哪些区(百度一面)
参考5.1
4. 多线程的方式和它们的区别(头条一面、阿里一面)

[图片上传失败...(image-8a9f22-1592965813715)]

5. 线程死锁的四个条件(阿里一面)

  1. 互斥条件:进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源
  2. 请求和保持条件:进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此事请求阻塞,但又对自己获得的资源保持不放
  3. 不可剥夺条件:是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释放
  4. 环路等待条件:是指进程发生死锁后,必然存在一个进程--资源之间的环形链

6. 线程安全的题(头条一面)

  1. 资源共享:一块资源可能被多个线程共享,比如多个线程同时访问同一个对象、同一个变量、同一个文件
  2. 当多个线程同时访问一块资源时,可能发生数据错乱或线程安全问题

7. iOS中有哪些锁(头条一面、腾讯一面)

  1. OSSpinLock
  2. os_unfiar_lock
  3. ptheard_metux_lock
  4. dispatch_semaphore
  5. NSLock
  6. NSRecursiveLock
  7. NSCondition
  8. NSConditionLock

8. 自旋锁和互斥锁的区别(头条二面)
1. 自旋锁适用于: 1. 预计线程等待锁的时间很短 2. 临界区经常被调用,但竞争较少 3. 多核处理 4. CPU不紧张 2. 互斥锁适用于: 1. 预计线程等待锁的时间较长 2. 单核处理器 3. 临界区有IO操作 4. 临界区代码复杂或循环量大 5. 临界区竞争激烈
9. 线程同步的方式(腾讯一面)

  1. 可以利用加锁的方式来保证线程同步
  2. 可以利用dispat_queue串行队列
  3. 可以用dispatch_semaphore信号量
  4. atomic,对象的原子性,默认在setter方法内部加锁
  5. 针对读写操作可以使用多读单写的操作来保证线程同步,例如dispatch_barrier_async和ptheard_rolock

10. GCD执行原理?(阿里一面)
11. 事务的特征(阿里一面)
事务是并发控制的基本单位。所谓的事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位。例如,银行转账工作:从一个账号扣款并使另一个账号增款,这两个操作要么都执行,要么都不执行。所以,应该把它们看成一个事务。事务是数据库维护数据一致性的单位,在每个事务结束时,都能保持数据一致性。 针对上面的描述可以看出,事务的提出主要是为了解决并发情况下保持数据一致性的问题。

事务具有以下4个基本特征。

  1. Atomic(原子性):事务中包含的操作被看做一个逻辑单元,这个逻单元中的操作要么全部成功,要么全部失败。
  2. Consistency(一致性):只有合法的数据可以被写入数据库,否则事务应该将其回滚到最初状态。
  3. Isolation(隔离性):事务允许多个用户对同一个数据进行并发访问,而不破坏数据的正确性和完整性。同时,并行事务的修改必须与其他并行事务的修改相互独立。
  4. Durability(持久性):事务结束后,事务处理的结果必须能够得到固化。

5. 内存管理
1. 介绍下内存的几大区域?(阿里一面)

  1. 代码段:用于存放编译之后的代码
  2. 数据段:
  3. 堆段:通过malloc、calloc、alloc动态分配的空间,分配的内存地址越来越大,需要程序员手动管理内存
  4. 栈段:用于函数调用开销,例如局部变量,分配的内存空间地址越来越小,系统自动分配内存
  5. 内核区:

2. 浅拷贝和深拷贝的区别(阿里一面、头条三面)
浅拷贝拷贝地址,深拷贝拷贝内存
一般不可变量调用copy,为浅拷贝;
可变变量调用copy,或调用MutableCopy为深拷贝
3. 数组copy后里面的元素会复制一份新的吗(头条三面)
不会
4. 对于字符串使用strong会有什么问题?对于可变数组使用copy这样会有什么问题?

  1. 如果赋值的是不可变字符串,没有问题,如果赋值的和被赋值是可变字符串,则如果修改赋值对象,用strong修饰的被赋值对象也会被修改
  2. 会新创建一个新的不可变数组,在调用添加删除会报错

5. Autorelease pool的实现原理(阿里一面)

  1. 内部结构主要有__atAutoreleasePool、autoreleasePoolPage
  2. 调用了autorelease的对象都由autoreleasePoolPage来管理,
  3. 调用了push操作后会有一个POOL_BOUNDARY入栈,
  4. 然后调用了autorelease的对象都会加到autoreleasePoolPage,
  5. 如果autoreleasePoolPage内存不够他会创建一个新的autoreleasePoolPage,原先的child指向新的autoreleasePoolPage,新的perent指向原先的,
  6. 在调用pop以后,会从最后一个入栈的对象开始往前release,知道遇到POOL_BOUNDARY为止

6. 性能优化
1. 性能优化(阿里二面)

  1. CPU

  2. 尽量使用轻量级对象,比如用不到事件处理的地方,可以考虑使用CALayer取代UIView

  3. 不要频繁调用UIView的相关属性,比如frame、bounds、transform等属性,尽量减少不必要的修改

  4. 尽量提前计算好布局,在有需要时一次调整,不要多次修改属性

  5. autolayout比直接设置frame小号更多CPU资源

  6. 图片的size最好刚好跟UIImageView的size一样

  7. 控制一下线程最大并发数

  8. 尽量把耗时操作放到子线程

  9. 文本处理(尺寸计算、绘制)

  10. 图片处理(解码、绘制)

  11. GPU

  12. 尽量减少短时间内的图片大量显示,尽可能的合成一张图片显示

  13. GPU最大能处理的纹理是4096 x 4096,一但超过这个尺寸,就需要占用CPU资源处理,所以纹理最好不要超过这个尺寸

  14. 尽量减少视图的数量和层次

  15. 尽量不要使用透明度

  16. 尽量不要出现离屏渲染(在当前屏幕缓冲区以外,开出一片新的缓冲区进行渲染操作)

  17. 需要创建新的缓冲区

  18. 离屏渲染需要多次切换上下文,先从当前屏幕,切换到离屏屏幕,等到离屏渲染完成之后,有需要将缓冲区渲染到当前屏幕,有需要将上下文环境从离屏屏幕切换到当前屏幕

  19. 那些操作会造成离屏渲染

  20. 光栅化,layer.shouldRasterize = YES;

  21. 遮罩,layer.mask

  22. 圆角,同时设置layer.masksToBounds = YES 和 layer.corner > 0,可以考虑通过CoreGraphics绘制剪裁圆角,或直接使用圆角图片

  23. 阴影,layer.shadowXXX,如果设置了layer.shadowPath就不会产生离屏渲染

2. 你是如何组件化解耦的?(阿里一面)
项目前期一般不需要组件化解耦,而且前期开发人员少,业务发展的不是特别快,不使用组件化可以保证开发进度。但如果项目发展起来会出现很多弊端,如耦合度比较严重、容易出现冲突、业务方的开发效率不够高(因为他只关心自己的组件,却需要编译整个项目)

  1. 优点:

  2. 加快编译速度

  3. 提高开发效率

  4. 自由选择开发设计模式

  5. 方便OA有针对性测试

  6. 缺点:

  7. 资源的重复性:以前放在一起,知道哪些资源是公共资源,而组件化之后,因为各个组件都相对独立了,所以一些资源需要重复加载,导致包变大了

  8. 页面样式:因为独立开来,可能导致页面样式有差异。可以使用公共组件化解决。

3. 优化你是从哪几方面着手?(阿里一面)

  1. 耗电优化

  2. CPU处理:尽可能的降低CPU和GPU的功耗

  3. 少用NSTimer

  4. 优化IO操作

  5. 尽量不要频繁的写入小数据,最好批量一次性写入

  6. 读写大量数据时,考虑用dispatch_io,其提供了基于GCD的异步操作IO文件的API。用dispatch_io系统会优化磁盘访问

  7. 数据量比较大的,建议使用数据库(SQLite、CoreData)

  8. 网络:

  9. 减少、压缩网络数据

  10. 如果多次请求数据相同,尽量使用缓存

  11. 如果大文件下载,使用断点续传

  12. 网络不可用时,不要尝试执行网络请求

  13. 让用户可以手动取消长时间运行、速度很慢的网络请求,设置合适的超时时间

  14. 批量传输,比如下载视频流时,不要传输很小的数据包,直接下载整个文件或一大块一大块的下载。如果下载广告,一次性下载多一些,然后慢慢展示,如果下载电子邮件,一次下载多封,不要一封一封下载

  15. 定位:

  16. 如果需要快速定位使用CLLocationManagerrequestLocation方法,定位完成后,会自动让定位硬件断电。

  17. 如果不是导航应用,不要实时刷新位置,定位完毕就关掉定位服务

  18. 尽量降低定位精度,比如尽量不要使用精度最高的kCLLocationAccuracyBest

  19. 需要后台定位时,尽量设置pauseLocationUpdatesAutomatically = YES,如果用户不大可能移动的时候会自动暂停位置更新

  20. 尽量不要使用startMonitoringSignificantLocationChanges,优先考虑startMonitoringForRegion

  21. 硬件检测优化:用户移动、摇晃、倾斜设备时,可能触发动作事件,这些事件由加速器、陀螺仪、磁力计等硬件检测,在不使用的时候,应关闭这些设备

  22. 启动优化:主要优化冷启动

  23. dyld

  24. 减少动态库,合并一些动态库,清理一些不用的动态库

  25. 减少Objc类、分类、Seletor的数量,清理一些不用的类、分类

  26. 减少c++虚函数数量

  27. Swift尽量使用struct

  28. runtime
    initialize+dispatch_once取代load、c++静态构造器等

  29. main
    在不影响用户体验的前提下,尽量延迟加载,尽量不放到finishLaunching方法中

  30. 安装包优化

  31. 资源(图片、音视频等)

  32. 采用无损压缩

  33. 删除没有用到的资源

  34. 编辑器

  35. Strip Linked Product、Make Strings Read-Only、Symbols Hidden by Default设置为YES

  36. 去掉异常支持,Enable C++ Exceptions、Enable Objective-C Exceptions设置为NO, Other C Flags添加-fno-exceptions

  37. 利用APPCode检测未使用的代码

  38. 利用LLVM插件检测出重复代码、为被调用代码

  39. 卡顿优化:参考6.1

7. 第三方库
1. Masonry的约束应该写在哪里(阿里一面)

  1. 在addSubView之后添加约束
  2. 相关约束对象添加之后添加约束

2. YYModel和AF源码(阿里二面)
3. Pod update和pod install的区别(头条二面)
在pod多少版本之前,pod update只是更新需要更新的代码,而Pod install是更新全部代码
不想升级specs库,可以增加忽略参数--no-repo-update
4. SD的源码(头条二面)
5. 看过哪些源码,讲讲思路(腾讯一面)
6. YYAsyncLayer如何异步绘制?(阿里一面)
8. 设计模式及架构
1. MVC的一些缺点(头条一面)
2. 介绍一下MVVM(阿里三面)
3. MVC和MVVM的区别(腾讯一面)
4. 架构(阿里二面)
5. 如何自己设计json转model(阿里二面)
6. 知道哪些设计模式(阿里三面、腾讯一面)
7. 存一个通讯录,包括增删改查,用什么数据结构(腾讯一面)
8. 讲一下我最满意的一个项目(百度二面)
9. 介绍项目,主要介绍自己强项一点的地方(阿里二面、头条三面)
10. 自我介绍 介绍一些项目难点(阿里三面)
11. 怎么防止别人反编译你的app?(阿里一面)
9. 网络
1. 七层模型(美团一面、百度一面)
应用层、表示层、会话层、网络层、传输层、数据链路层、物理层
应用层 网络服务与最终用户的一个接口。 协议有:HTTP FTP TFTP SMTP SNMP DNS TELNET HTTPS POP3 DHCP 表示层 数据的表示、安全、压缩。(在五层模型里面已经合并到了应用层) 格式有,JPEG、ASCll、EBCDIC、加密格式等 会话层 建立、管理、终止会话。(在五层模型里面已经合并到了应用层) 对应主机进程,指本地主机与远程主机正在进行的会话 传输层 定义传输数据的协议端口号,以及流控和差错校验。 协议有:TCP UDP,数据包一旦离开网卡即进入网络传输层 网络层 进行逻辑地址寻址,实现不同网络之间的路径选择。 协议有:ICMP IGMP IP(IPV4 IPV6) 数据链路层 建立逻辑连接、进行硬件地址寻址、差错校验等功能。(由底层网络定义协议) 将比特组合成字节进而组合成帧,用MAC地址访问介质,错误发现但不能纠正。 物理层 建立、维护、断开物理连接。(由底层网络定义协议) TCP/IP 层级模型结构,应用层之间的协议通过逐级调用传输层(Transport layer)、网络层(Network Layer)和物理数据链路层(Physical Data Link)而可以实现应用层的应用程序通信互联。 应用层需要关心应用程序的逻辑细节,而不是数据在网络中的传输活动。应用层其下三层则处理真正的通信细节。在 Internet 整个发展过程中的所有思想和着重点都以一种称为 RFC(Request For Comments)的文档格式存在。针对每一种特定的 TCP/IP 应用,有相应的 RFC文档。
2.传输层和网络层分别是做什么的(百度一面)
3. http有哪些部分(美团一面、腾讯一面、头条一面)
http协议报文 1.请求报文(请求行/请求头/请求数据/空行) 请求行 求方法字段、URL字段和HTTP协议版本 例如:GET /index.html HTTP/1.1 get方法将数据拼接在url后面,传递参数受限 请求方法: GET、POST、HEAD、PUT、DELETE、OPTIONS、TRACE、CONNECT 请求头(key value形式) User-Agent:产生请求的浏览器类型。 Accept:客户端可识别的内容类型列表。 Host:主机地址 请求数据 post方法中,会把数据以key value形式发送请求 发送回车符和换行符,通知服务器以下不再有请求头 2.响应报文(状态行、消息报头、响应正文) 状态行 消息报头 响应正文
4. Http2.0如1.x的区别(百度一面)
5.发送一个HTTP请求的过程(百度二面)
6. tcp和udp的区别(美团一面、百度一面)

  1. TCP:是面向链接的协议,经过三次握手建立链接,链接成功传输协议、四次放手,
  2. UDP:是非链接协议,所以TCP比UDP安全,但因为需要建立链接,所以传输速度慢

7. get和post的区别(美团一面)

  1. get一般用于从服务器上获取数据,post一般用于向服务器传输数据
  2. get采用的地址传参,post采用报文传参
  3. get传输的数据量小,一般2K,post可传输的数据量很大,一般默认无限传输,但实际有上限(4G)
  4. get安全性非常低,post安全性较高
  5. get不能上传文件,post可以上传文件

8. TCP头部多长,IP呢(腾讯一面)
IP头部范围为20B(不含options)~60B
UDP头部长度是固定的,为12B(假头部)+8B(真头部) = 20B
TCP跟IP头长度一样,20B~60B,取决于有没有options.
9. UDP可以实现一对多??(百度一面)
10. TCP是如何保证可靠的(百度二面)
1、确认和重传:接收方收到报文就会确认,发送方发送一段时间后没有收到确认就重传。
2、数据校验
3、数据合理分片和排序:
  UDP:IP数据报大于1500字节,大于MTU.这个时候发送方IP层就需要分片(fragmentation).把数据报分成若干片,使每一片都小于MTU.而接收方IP层则需要进行数据报的重组.这样就会多做许多事情,而更严重的是,由于UDP的特性,当某一片数据传送中丢失时,接收方便无法重组数据报.将导致丢弃整个UDP数据报.
  tcp会按MTU合理分片,接收方会缓存未按序到达的数据,重新排序后再交给应用层。
4、流量控制:当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。
5、拥塞控制:当网络拥塞时,减少数据的发送。
11. TCP为什么是三次握手和四次挥手(头条三面)
12. 知道MTU吗(腾讯一面)
最大输出单元,如果在IP层要传输一个数据报比链路层的MTU还大,那么IP层就会对这个数据报进行分片。一个数据报会被分为若干片,每个分片的大小都小于或者等于链路层的MTU值。当同一网络上的主机互相进行通信时,该网络的MTU对通信双方非常重要。
13. HTTPS通信的步骤
① 客户端发送报文进行SSL通信。报文中包含客户端支持的SSL的指定版本、加密组件列表(加密算法及密钥长度等)。
② 服务器应答,并在应答报文中包含SSL版本以及加密组件。服务器的加密组件内容是从接受到的客户端加密组件内筛选出来的。
③ 服务器发送报文,报文中包含公开密钥证书。
④ 服务器发送报文通知客户端,最初阶段SSL握手协商部分结束。
⑤ SSL第一次握手结束之后,客户端发送一个报文作为回应。报文中包含通信加密中使用的一种被称Pre-master secret的随机密码串。该密码串已经使用服务器的公钥加密。
⑥ 客户端发送报文,并提示服务器,此后的报文通信会采用Pre-master secret密钥加密。
⑦ 客户端发送Finished报文。该报文包含连接至今全部报文的整体校验值。这次握手协商是否能够完成成功,要以服务器是否能够正确解密该报文作为判定标准。
⑧ 服务器同样发送Change Cipher Spec报文。
⑨ 服务器同样发送Finished报文。
⑩ 服务器和客户端的Finished报文交换完毕之后,SSL连接就算建立完成。
⑪ 应用层协议通信,即发送HTTP响应。
⑫ 最后由客户端断开链接。断开链接时,发送close_nofify报文
14. HTTP和HTTPS区别?
① https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用;http一般是免费的。
② http是超文本传输协议,信息是明文传输;https则是具有安全性的ssl加密传输协议。
③ http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
④ http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
TTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全,为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了7a64e78988e69d8331333366306533HTTPS。

简单来说,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。
15. HTTPS 中的 SSL 握手建立过程
1、首先,客户端 A 访问服务器 B ,比如我们用浏览器打开一个网页 https://www.baidu.com ,这时,浏览器就是客户端 A ,百度的服务器就是服务器 B 了。这时候客户端 A 会生成一个随机数1,把随机数1 、自己支持的 SSL 版本号以及加密算法等这些信息告诉服务器 B 。
2、服务器 B 知道这些信息后,确认一下双方的加密算法,然后服务端也生成一个随机数 B ,并将随机数 B 和 CA 颁发给自己的证书一同返回给客户端 A 。
3、 客户端 A 得到 CA 证书后,会去校验该 CA 证书的有效性,校验方法在上面已经说过了。校验通过后,客户端生成一个随机数3 ,然后用证书中的公钥加密随机数3 并传输给服务端 B 。服务端 B 得到加密后的随机数3,然后利用私钥进行解密,得到真正的随机数3。
4、最后,客户端 A 和服务端 B 都有随机数1、随机数2、随机数3,然后双方利用这三个随机数生成一个对话密钥。之后传输内容就是利用对话密钥来进行加解密了。这时就是利用了对称加密,一般用的都是 AES 算法。
5、 客户端 A 通知服务端 B ,指明后面的通讯用对话密钥来完成,同时通知服务器 B 客户端 A 的握手过程结束。
6、服务端 B 通知客户端 A,指明后面的通讯用对话密钥来完成,同时通知客户端 A 服务器 B 的握手过程结束。SSL 的握手部分结束,SSL 安全通道的数据通讯开始,客户端 A 和服务器 B 开始使用相同的对话密钥进行数据通讯。
到此,SSL 握手过程就讲完了。可能上面的流程太过于复杂,我们简单地来讲:
客户端和服务端建立 SSL 握手:客户端通过 CA 证书来确认服务端的身份;互相传递三个随机数,之后通过这随机数来生成一个密钥;互相确认密钥,然后握手结束;数据通讯开始,都使用同一个对话密钥来加解密;
我们可以发现,在 HTTPS 加密原理的过程中把对称加密和非对称加密都利用了起来。即利用了非对称加密安全性高的特点,又利用了对称加密速度快,效率高的好处。
16. 中间人攻击
中间人攻击(英语:Man-in-the-middle attack,缩写:MITM),指攻击者与通讯的两端分别创建独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。

中间人攻击过程

流程如下:
1、截获客户端与服务器通信的通道
2、然后在 SSL 建立连接的时候,进行中间人攻击
3、将自己伪装成客户端,获取到服务器真实有效的 CA 证书(非对称加密的公钥)
4、将自己伪装成服务器,获取到客服端的之后通信的密钥(对称加密的密钥)
5、有了证书和密钥就可以监听之后通信的内容了
17. charles抓包原理
抓取https包的时候,青花瓷会要求使用者 对抓包的设备(手机或其他设备)
,安装一个证书,安装这个证书的时候,其实是安装了一个根证书(允许颁发CA的一个证书机构的根证书),当你安装了该根证书之后,该证书机构颁发的其他证书,默认都会被你的系统所信任,这个就是青花瓷完成https抓包的一个重要前提!!

[图片上传失败...(image-a62461-1592965813713)]

中间人攻击示意图

1、当客户端Client对服务器Server发送请求(带着随机数和加密算法),由于青花瓷做了代理,请求被青花瓷拦截,处理(青花瓷的角色现在对于Client来说是服务器),青花瓷将客户端带的随机数和加密算法处理,然后返回自己的证书通过客户端校验,获取到客户端提交的请求参数等数据,
2、青花瓷作为客户端(自己去产生随机数和携带支持的加密算法)去请求刚刚Client想要请求的Server,然后,Server会和青花瓷完成上面讲的那个完整的校验,并且读取青花瓷带错来的具体请求,返回正常的数据结果.
3、青花瓷得到服务器数据的返回结果之后,开始继续和过程1中的Client以服务器的身份,去做处理,首先收到客户端的随机数和加密算法,自己生成一个随机数和选择一个客户端的加密算法,然后*********重要********** 青花瓷会返回一个伪造的CA证书(公钥和真实的server不一样,但是域名是一样的,或者说,除了域名是一致的,其他的都不是一致的,而且这个签发机构是青花瓷之前让你安装的根证书 签发的,所以,当返回这个证书的时候,你的客户端的信任链是可以完成的,会被系统信任),然后Client在这个伪造的证书(对于青花瓷和Client是真实证书(验证信任链和证书信息都通过了),但是和真实的域名对应的证书来看,是伪造证书)的基础上,和青花瓷通信,然后青花瓷再和Server通信,成了一个中间人的角色,这样,整个过程的数据传输,都被青花瓷给监听到了
在此,中间人攻击的过程 就完成了
10. 算法
1. 算法字符串翻转(头条二面、腾讯一面)
2. 两个链表找第一个相同结点(腾讯一面)
3. 找链表的倒数第k个结点(腾讯一面)
4. 把一个链表比某个值大的放在左边,比它小的放在右边(腾讯一面)
5. 二叉树的中序遍历,非递归(腾讯一面)
6. 求数组的最长子数组(百度一面)
7. 在一个10G的数据里面找出最大的100个数(百度二面)