iOS面试题小结2019

iOS面试题

runtime

参考文档链接

  • runtime介绍
  • runtime消息传递
  • runtime消息转发
  • runtime应用
介绍

OC是一门动态语言的根本是Runtime的存在,runtime可以提供得创建类和对象、进行消息传递和转发能力。

消息机制

一个对象的方法像这样[obj foo],编译器转成消息发送objc_msgSend(obj, foo),Runtime时执行的流程是这样的:

  • 首先,通过obj的isa指针找到它的 class;
  • 在 class的 method list找 foo;
  • 如果 class中没到 foo,继续往它的 superclass中找 ;
  • 一旦找到 foo这个函数,就去执行它的实现IMP。

但这种实现有个问题,效率低。但一个class往往只有 20%的函数会被经常调用,可能占总调用次数的 80%。每个消息都需要遍历一次objc_method_list并不合理。如果把经常被调用的函数缓存下来,那可以大大提高函数查询的效率。这也就是objc_class中另一个重要成员objc_cache做的事情 - 再找到foo之后,把foo的method_name作为key,method_imp作为value给存起来。当再次收到foo消息的时候,可以直接在cache里找到,避免去遍历objc_method_list。从前面的源代码可以看到objc_cache是存在objc_class结构体中的。

消息转发
  • 动态函数解析
  • 备用接收者
  • 完成消息转发:返回一个signature对象喝invovation对象

runloop的原理以及核心源代码

参考链接,文章讲解了详细的原理和源码分析。

  • 目的:省电提高响应速度,节省资源,避免琐碎线程开销。
  • 原理:线程(创建)–>runloop将进入–>最高优先级OB创建释放池–>runloop将睡–>最低优先级OB销毁旧池创建新池–>runloop将退出–>最低优先级OB销毁新池–>线程(销毁)
{
    /// 1. 通知Observers,即将进入RunLoop
    /// 此处有Observer会创建AutoreleasePool: _objc_autoreleasePoolPush();
    __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry);
    do {
 
        /// 2. 通知 Observers: 即将触发 Timer 回调。
        __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers);
        /// 3. 通知 Observers: 即将触发 Source (非基于port的,Source0) 回调。
        __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources);
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
 
        /// 4. 触发 Source0 (非基于port的) 回调。
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0);
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
 
        /// 6. 通知Observers,即将进入休眠
        /// 此处有Observer释放并新建AutoreleasePool: _objc_autoreleasePoolPop(); _objc_autoreleasePoolPush();
        __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting);
 
        /// 7. sleep to wait msg.
        mach_msg() -> mach_msg_trap();
        
 
        /// 8. 通知Observers,线程被唤醒
        __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting);
 
        /// 9. 如果是被Timer唤醒的,回调Timer
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);
 
        /// 9. 如果是被dispatch唤醒的,执行所有调用 dispatch_async 等方法放入main queue 的 block
        __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block);
 
        /// 9. 如果如果Runloop是被 Source1 (基于port的) 的事件唤醒了,处理这个事件
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1);
 
 
    } while (...);
 
    /// 10. 通知Observers,即将退出RunLoop
    /// 此处有Observer释放AutoreleasePool: _objc_autoreleasePoolPop();
    __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit);
}

autoreleasepool 原理

基本工作原理:

App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。
第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。

  • 目的:改变变量作用域,延迟释放
  • 作用:降低内存使用峰值,for()临时变量的内存回收;系统级优化,区别于java中的GC,定时内存回收,iOS使用pool可以做到及时内存回收,避免像java中时“卡”时“流畅”。
核心流程
  1. _objc_autoreleasePoolPush和_objc_autoreleasePoolPop 都是对AutoreleasePoolPage的封装;
void *objc_autoreleasePoolPush(void) {
    return AutoreleasePoolPage::push();
}

void objc_autoreleasePoolPop(void *ctxt) {
    AutoreleasePoolPage::pop(ctxt);
}
  1. AutoreleasePoolPage 是一个 C++ 中的类,它在 NSObject.mm 中的定义是这样的:
class AutoreleasePoolPage {
#   define EMPTY_POOL_PLACEHOLDER ((id*)1)

#   define POOL_BOUNDARY nil
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
    static size_t const SIZE = 
#if PROTECT_AUTORELEASEPOOL
        PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
        PAGE_MAX_SIZE;  // size and alignment, power of 2
#endif
    static size_t const COUNT = SIZE / sizeof(id);

    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
};
  • magic 检查校验完整性的变量
  • next 指向新加入的autorelease对象
  • thread page当前所在的线程,AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程)
  • parent 父节点 指向前一个page
  • child 子节点 指向下一个page
  • depth 链表的深度,节点个数
  • hiwat high water mark 数据容纳的一个上限
  • EMPTY_POOL_PLACEHOLDER 空池占位
  • POOL_BOUNDARY 是一个边界对象 nil,之前的源代码变量名是 POOL_SENTINEL哨兵对象,用来区别每个page即每个 AutoreleasePoolPage 边界
  • PAGE_MAX_SIZE = 4096, 为什么是4096呢?其实就是虚拟内存每个扇区4096个字节,4K对齐的说法。
  • COUNT 一个page里对象数
小结
  • 自动释放池是一个个 AutoreleasePoolPage 组成的一个page是4096字节大小,每个 AutoreleasePoolPage 以双向链表连接起来形成一个自动释放池
  • 当对象调用 autorelease 方法时,会将对象加入 AutoreleasePoolPage 的栈中
  • pop 时是传入边界对象,然后对page 中的对象发送release 的消息

KVO
  • 原理:
    1. isa swizzling方法,通过runtime动态创建一个中间类,继承自被监听的类。
    2. 使原有类的isa指针指向这个中间类,同时重写改类的Class方法,使得该类的Class方法返回自己而不是isa的指向。
    3. 复写中间类的对应被监听属性的setter方法,调用添加进来的方法,然后给当前中间类的父类也就是原类的发送setter消息。

NSCache
  1. 是线程安全的,Dictionary不是线程安全
  2. 系统资源将要耗尽时,它可以自动删减缓存。
  3. 可以设置最大缓存数量。
  4. 可以设置最大占用内存值。

load和initialize区别和源码

具体可参考链接

    • initialize 方法:苹果官方对这个方法有这样的一段描述:这个方法会在 第一次初始化这个类之前 被调用,我们用它来初始化静态变量。
  • load 方法会在加载类的时候就被调用,也就是应用启动的时候,在调用 main 函数之前会加载所有的类,也会调用每个类的 load 方法。
  • initialize 方法类似一个懒加载,如果没有使用这个类,那么系统默认不会去调用这个方法,且默认只加载一次,但存在一个initialize的实现被多次调用的情况,因为如果当前类没有实现initializie,则对当前class的initialize会转发调用super class的initialize;
  • initialize 的调用发生在 +init 方法之前,创建子类的时候,子类会去调用父类的 initialize 方法。

自定义对象的占用的空间

参考链接链接

  • 系统分配了16个字节给 NSObject 对象(通过 malloc_size 函数获得)
  • NSObject 对象内部只使用了8个字节的空间(64bit环境下,可以通过 class_getInstanceSize 函数获得)

GCD串行队列和并行队列

串行队列

2种获取方式:一个串行队列对应只有一个线程,因此同时只能执行一个操作,先追加的操作先执行。执行很多操作的时候就好像人们排队买东西一样,先来后到。

//1.手动创建串行队列
dispatch_queue_t mySerialQueue = dispatch_queue_create("com.gcd.queueCreate.mySerialQueue", NULL);
//2.主队列是串行队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();

并行队列

一个并行队列可以有多个线程,同时可以执行多个操作。在执行多个操作的时候,执行顺序会根据操作内容和系统状态发生变化。

//1.手动创建并行队列
dispatch_queue_t myConcurrentQueue = dispatch_queue_create("com.gcd.queueCreate.myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
//2.全局队列是并行队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dealloc 调用时机
  1. 这个类被release的时候会被调用;
  2. 这个对象的retain count为0的时候会被调用,或者说一个对象或者类被置为nil的时候;
  3. 父类会在子类调用完后自动调用
1.Dealloc 调用流程
  • 1.首先调用 _objc_rootDealloc()
  • 2.接下来调用 rootDealloc()
  • 3.这时候会判断是否可以被释放,判断的依据主要有5个,判断是否有以上五种情况
    • NONPointer_ISA
    • weakly_reference
    • has_assoc
    • has_cxx_dtor
    • has_sidetable_rc
  • 4-1.如果有以上五中任意一种,将会调用 object_dispose()方法,做下一步的处理。
  • 4-2.如果没有之前五种情况的任意一种,则可以执行释放操作,C函数的 free()。
  • 5.执行完毕。

2.object_dispose() 调用流程。
  • 1.直接调用 objc_destructInstance()。
  • 2.之后调用 C函数的 free()。

3.objc_destructInstance() 调用流程
  • 1.先判断 hasCxxDtor,如果有 C++ 的相关内容,要调用 object_cxxDestruct() ,销毁 C++ 相关的内容。
  • 2.再判断 hasAssocitatedObjects,如果有的话,要调用 object_remove_associations(),销毁关联对象的一系列操作。
  • 3.然后调用 clearDeallocating()。
  • 4.执行完毕。

4.clearDeallocating() 调用流程。
  • 1.先执行 sideTable_clearDellocating()。
  • 2.再执行 weak_clear_no_lock,在这一步骤中,会将指向该对象的弱引用指针置为 nil。
  • 3.接下来执行 table.refcnts.eraser(),从引用计数表中擦除该对象的引用计数。
  • 4.至此为止,Dealloc 的执行流程结束。

无重复字符的最长子串

定义:字串:连续无重复的字符集
例如:一个字符串  awbcdewgh
他的子串:  awbc、awbcd、awbcde   …很多个子串 ,但是都是连续在一起 。
他的子序列: abc 、abcd、 abcde  …  很多个子序列 ,但是子序列中的字符在字符串中不一定是连在一起的,而是删除其中若干个, 但是子序列一定是单调的(即字符之间ASCII单调递增或单调递减,相对顺序不能改变)

- (void)lengthOfLongestSubstring:(NSString *)tmpString{
    NSString *str = tmpString;
    NSMutableSet *set = [NSMutableSet new];
    long n = str.length;
    //length:长度,index:起始位置
    int length = 0, i = 0, j = 0 ,index = 0;
    while (i < n && j < n) {
        //截取单个字符判断
        if (![set containsObject:[str substringWithRange:NSMakeRange(j,1)]]) {
            //添加当前字符
            [set addObject:[str substringWithRange:NSMakeRange(j++, 1)]];
            if (j - i > length) {
                length = j - i;
                index = i;
                NSLog(@"lenght:%d---j:%d---i:%d",length,j,i);
            }
        }else{
            [set removeObject:[str substringWithRange:NSMakeRange(i++, 1)]];
        }
    }
    NSString * result = [str substringWithRange:NSMakeRange(index, length)];
    NSLog(@"%@",result);
}

内存循环引用,代码检测

产生的几种情况:

  • 相互持有:你中有我,我中有你,无法释放,strong–weak
  • block在copy时都会对block内部用到的对象进行强引用的
  • delegate:weak 修饰属性代理变量
  • NSTimer:可以强制invalidate

检测内存泄露的方法

  • 工具检测:Instruments
  • 三方:MLeaksFinder(Tencent)链接和FBRetainCycleDetector(Facebook)链接

block的内存管理

Block 的内存管理分两种情况,如果是 MRC 其实可以使用 __Block,这样 Block 内部截取的变量的引用计数不会 +1,也就无需程序员动手管理了。block不能修改局部变量的值,若修改需要加__block 修饰。
参考链接

  • 1.堆 Block:__NSConcreteMallocBlock ————————堆中
  • 2.栈 Block:__NSConcreteStackBlock ————————栈中
  • 全局 Block:__NSConcreteGlobalBlock ————————数据区域中

为什么更新UI的操作必须放在主线程

参考链接
因为UIKit框架是线程不安全的。把UIKit设计成线程安全并不会带来太多的便利,也不会提升太多的性能表现,甚至会因为加锁解锁而耗费大量的时间。事实上并发编程也没有因为UIKit是线程不安全而变得困难,我们所需要做的只是要确保UI操作在主线程进行就可以了。所有UI操作在串行队列中就可以了,非主线程异步绘制UI无法满足60fps刷新率

autolayout与frame的性能比较,原理
  • Auto Layout 是针对多尺寸屏幕的设计。其本质是通过线性不等式设置UI控件的相对位置,从而适配多种iPhone/iPad 屏幕的尺寸。
  • Frame 是基于 XY 坐标轴系统的布局。它从数学上限定了 UI 控件的具体位置,是 iOS 开发中最底层、最基本的界面布局机制。
  • Auto Layout 的性能比 Frame 差很多。Auto Layout 的布局过程是首先求解线性不等式,然后再转化为 Frame 进行布局。其中求解的计算量非常大,通常 Auto Layout 的性能损耗是 Frame 布局的10倍左右。
    参考链接

NSCopy协议:

NSCopying是一个与对象拷贝有关的协议。如果想让一个类的对象支持拷贝,就需要让该类实现NSCopying协议。NSCopying协议中的声明的方法只有一个- (id)copyWithZone:(NSZone *)zone。当我们的类实现了NSCopying协议,通过类的对象调用copy方法时,copy方法就会去调用我们实现的- (id)copyWithZone:(NSZone *)zone方法,实现拷贝功能

Masonry 布局与基础的内存管理

基于XXX

riblet

参考链接
这个方案是Uber 骑手App模块化开发的一个方案。

swift和OC比较有哪些方面的优势
  • Swift代码更好写:类型推断,多返回值,全面的ARC 去掉臃肿的API实现,结构体可以实现扩展和协议
  • Swift更安全
  • Swift更快
  • Swift开源
  • Swift跨平台

swift中struct、enum和类的区别
  • 枚举、结构体、类的共同点:
    1,定义属性和方法;
    2,下标语法访问值;
    3,初始化器;
    4,支持扩展增加功能;
    5,可以遵循协议;
  • 类特有的功能:
    1,继承;
    2,允许类型转换;
    3,析构方法释放资源;
    4,引用计数;
  • 类是引用类型
    引用类型(reference types,通常是类)被复制的时候其实复制的是一份引用,两份引用指向同一个对象。所以在修改一个实例的数据时副本的数据也被修改了(s1、s2)。
  • 枚举,结构体是值类型
    值类型(value types)的每一个实例都有一份属于自己的数据,在复制时修改一个实例的数据并不影响副本的数据(p1、p2)。值类型和引用类型是这三兄弟最本质的区别。

RAC中有哪些操作符

throttle的作用是什么

参考链接
iOS中有Disk I/O Throttle,Memory I/O Throttle,和Network I/O Throttle
Global Queue有哪几种优先级:Default,Low,High,BACKGROUND(I/O Throttle)
应用场景:

  • 网络限流:弱网情况下,减小
  • 用户连续点击button,防止误操作连续pushVC,可以设置timestamp间隔解决,也可以采用RxSwift方法实现

button.rx_tap
   .throttle(0.5, MainScheduler.instance)
   .subscribeNext { _ in 
      print("Hello World")
   }
   .addDisposableTo(disposeBag)

自定义TabBar的高度遇到过哪些坑

可自行添加…

childController.tabBarItem.selectedImage = [[UIImage imageNamed:selectedImage] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];

离屏渲染,有哪些方式会触发离屏渲染

Off-Screen Rendering意为离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。
触发方式:

  • shouldRasterize(光栅化)
  • masks(遮罩)
  • shadows(阴影)
  • edge antialiasing(抗锯齿)
  • group opacity(不透明)
  • 复杂形状设置圆角等

两个子试图最近公共父视图。

这个问的其实是数据结构中的二叉树,查找一个普通二叉树中两个节点最近的公共祖先问题

  1. 找出所有父类
  2. contain/emrate 集合,

App的签名原理

async非对称加密

哈希表的底层实现原理

响应式编程,RXSwift

app编译过程

lldb
  • breakpoint 设置断点定位到某一个函数
  • tb 输出详细线程信息
  • po 打印
  • n 断点指针下一步

RAC

MVVM与VIPER的区别


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值