1. 面试问题整理

24.8.5 更新 24.8.6 更新

1.1. 消息转发

消息发送的本质

objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)

消息发送步骤

  1. 首先检查这个Selector是不是要忽略。比如Mac OS X开发,有了垃圾回收就不会理会retainrelease这些函数。
  2. 检测这个Selector的target是不是nil,OC允许我们对一个nil对象执行任何方法不会Crash,因为运行时会被忽略掉。
  3. 如果上面两步都通过了,就开始查找这个类的实现IMP,先从cache里查找,如果找到了就运行对应的函数去执行相应的代码。
  4. 如果cache中没有找到就找类的方法列表中是否有对应的方法。
  5. 如果类的方法列表中找不到就到父类的方法列表中查找,一直找到NSObject类为止。
  6. 如果没有找到,Runtime 会发送 +resolveInstanceMethod: 或者 +resolveClassMethod:尝试去 resolve 这个消息。
  7. 如果 resolve 方法返回 NO,Runtime 就发送 -forwardingTargetForSelector: 允许你把这个消息转发给另一个对象。
  8. 如果没有新的目标对象返回,Runtime就会发送-methodSignatureForSelector:-forwardInvocation: 消息。你可以发送 -invokeWithTarget: 消息来手动转发消息或者发送-doesNotRecognizeSelector:抛出异常。

消息转发应用

  • 预防没有方法实现而会导致的崩溃。
  • 预防苹果系统API迭代造成API不兼容的崩溃。
  • 模拟多继承。

参考

1.2. KVO的底层实现?

当一个对象使用了KVO监听,iOS系统会修改这个对象的isa指针,改为指向一个全新的通过Runtime动态创建的子类,子类拥有自己的set方法实现,set方法实现内部会顺序调用willChangeValueForKey方法、原来的setter方法实现、didChangeValueForKey方法,而didChangeValueForKey方法内部又会调用监听器的observeValueForKeyPath:ofObject:change:context:监听方法。

手动触发:

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
    BOOL automatic = YES;
    if ([key isEqualToString:@"name"]) {
        automatic = NO;
    } else {
        automatic = [super automaticallyNotifiesObserversForKey:key];
    }
    return automatic;
}

- (void)setName:(NSString *)name
{
    if (![_name isEqualToString:name]) {
        [self willChangeValueForKey:@"name"];
        _name = name;
        [self didChangeValueForKey:@"name"];
    } 
}

参考

1.3. isa是什么?

isa是结构体指针,只要是OC对象都有isa,作用如下:

  • 实例对象instanceisa指向类对象class
  • 类对象classisa指向元类对象meta-class
  • classsuperclass指向父类的class,如果没有父类,superclass指针为nil
  • meta-classsuperclass指向父类的meta-class,基类的meta-classsuperclass指向基类的class
  • instance调用对象方法的轨迹:isa找到class,方法不存在,就通过superclass找父类。
  • class调用类方法的轨迹:isameta-class,方法不存在,就通过superclass找父类。

参考

1.4. 自动释放池

@autoreleasepoolmain函数中的c++代码:

int main(int argc, const char * argv[]) {
    {
        void * atautoreleasepoolobj = objc_autoreleasePoolPush();

        // do whatever you want

        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    return 0;
}

AutoreleasePoolPage的结构:

class AutoreleasePoolPage {
    magic_t const magic;// 用于对当前 AutoreleasePoolPage 完整性的校验
    id *next;// 指向了下一个为空的内存地址
    pthread_t const thread;// 保存了当前页所在的线程
    AutoreleasePoolPage * const parent;// 实现双向链表的parent节点
    AutoreleasePoolPage *child;// 实现双向链表的child节点
    uint32_t const depth;
    uint32_t hiwat;
};

AutoreleasePoolPage 的大小都是 4096 字节,其中56bit用于存储成员变量,剩下的从哨兵的位置开始都是用来存对象地址的。

objc_autoreleasePoolPush方法:

static inline void *push() {
   return autoreleaseFast(POOL_SENTINEL);
}

static inline id *autoreleaseFast(id obj)
{
   AutoreleasePoolPage *page = hotPage();
   if (page && !page->full()) {// 有hotpage,且不满
       return page->add(obj);
   } else if (page) {// 有hotpage,且已满
       return autoreleaseFullPage(obj, page);
   } else {// 没有hotpage
       return autoreleaseNoPage(obj);
   }
}
Objective-C

objc_autoreleasePoolPop方法:

void objc_autoreleasePoolPop(void *ctxt) {
    AutoreleasePoolPage::pop(ctxt);
}

static inline void pop(void *token) {
    AutoreleasePoolPage *page = pageForPointer(token);
    id *stop = (id *)token;// 获取当前 token 所在的 AutoreleasePoolPage

    page->releaseUntil(stop);// 方法释放栈中的对象,直到 stop

    /**
     * 当前page一半都没满,说明剩余的page空间已经暂时够了,把多余的child page就可以全kill掉,释放空间;
     * 如果超过一半,就认为下一页page还有存在的必要,说不定添加的对象太多就能用的到,所以kill掉孙子page,有个儿子page就暂时够了。
     */
    if (page->child) {
        if (page->lessThanHalfFull()) {
            page->child->kill();
        } else if (page->child->child) {
            page->child->child->kill();
        }
    }
}

autorelease方法:调用上面提到的autoreleaseFast方法,将当前对象加到AutoreleasePoolPage中。

RunLoopautoreleasePool

  1. 第1个Observer监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()
  2. 第2个Observer:
    • 监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()objc_autoreleasePoolPush()
    • 监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()

参考

1.5. Runloop

  • 顾名思义,运行循环,线程与Runloop是一一对应,Runloop运行可以是线程不退出。
  • Runloop默认注册5个Mode
    • kCFRunLoopDefaultMode:App的默认 Mode,通常主线程是在这个 Mode 下运行的。
    • UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
    • UIInitializationRunLoopMode:在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。
    • GSEventReceiveRunLoopMode:接受系统事件的内部 Mode,通常用不到。
    • kCFRunLoopCommonModes:这是一个占位的 Mode,没有实际作用。
  • Runloop包含多个Mode ,每个Mode包含Source数组,Observer数组,Timer数组。
  • Runloop状态:即将进入Loop、即将处理 Timer、即将处理 Source、即将进入休眠、即将退出Loop。
  • Runloop的核心就是一个mach_msg()RunLoop调用这个函数去接收消息,如果没有别人发送port消息过来,内核会将线程置于等待状态。
  • RunloopAutoreleasePool
    • Runloop进入循环时刻,创建自动释放池;
    • Runloop进入休眠,释放旧的池,创建新的池;
    • Runloop退出,释放自动释放池。
  • Runloop与事件响应:苹果注册了一个 Source1 (基于 mach port 的) 用来接收系统事件,其回调函数为__IOHIDEventSystemClientQueueCallback()
  • Runloop与界面更新:苹果注册了一个 Observer 监听 BeforeWaiting(即将进入休眠) 和 Exit (即将退出Loop) 事件。
  • Runloop应用:
    • AFNetworking单独创建了一个线程,并在这个线程中启动了一个RunLoop,执行后台请求任务。
    • AsyncDisplayKit在主线程的RunLoop中添加一个Observer,监听了kCFRunLoopBeforeWaitingkCFRunLoopExit事件,在收到回调时,处理队列里的UI渲染任务。

参考

1.6. category实现方式

  • category的作用:a)分散功能,b)声明私有方法;
  • categoryextension:
    • category是运行期决议的,不可以添加属性。
    • extension是编译期决议的,可以添加属性,一般为了私有化。
  • category和原来类中存在相同方法,category的方法更加靠前,运行期优先被查找和调用。
  • category可以使用runtime关联对象来实现添加实例变量。

参考

1.7. 方法交换

  1. Swizzling需要在+(void)load中添加。
  2. Swizzling应该总是在dispatch_once中执行。
  3. Swizzling+load中执行时,不要调用[super load]。如果多次调用了[super load],可能会出现“Swizzle无效”的假象。

场景

  1. 统计VC加载次数。
  2. 防止UI控件短时间多次激活事件。
  3. 防奔溃处理:数组越界问题。
  4. 适配iOS13的模态的的样式问题。

参考

1.8. weak实现

weakRuntime维护了一个hash(哈希)表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。

参考

1.9. 监控卡顿

卡顿原因

  • 复杂UI、图文混排的绘制量过大;
  • 在主线程上做网络同步请求;
  • 在主线程做大量的IO操作;
  • 运算量过大,CPU持续高占用;
  • 死锁和主子线程抢锁。

监控卡顿

  1. 创建一个CFRunLoopObserverContext观察者;
  2. 监听kCFRunLoopBeforeSourceskCFRunLoopBeforeWaiting再到kCFRunLoopAfterWaiting的状态。
  3. 开启子线程,利用信号量计算这几个状态切换的时间,间隔时间如果超过50ms,卡顿计数+1。
  4. 如果卡顿计数超过大于等于5次,触发卡顿上报线程调用栈。

参考

1.10. block实现

block是个结构图,拥有isa指针。

捕获机制

  • 全局变量,捕获指针,修改可以生效。
  • 局部变量,捕获值,修改不生效。

block类型

  • 全局block
  • block
  • block

__block

依然是个结构体,拥有isa指针,局部变量被包装成了一个对象,里面有个成员变量,指向了局部变量,所以block修改这个变量是有效果的。

参考

1.11. 五大区

  • 栈区:运行期分配,例如:局部变量、函数参数等。
  • 堆区:运行期分配,例如:alloc、new的对象。
  • 全局区:编译期分配,例如:static修饰变量。
  • 常量区:编译期分配,例如:string的引用,但是string的指针是堆区
  • 代码区:编译期分配,存放程序的代码。

参考

1.12. 性能优化(启动等)

  • 启动优化
  • 卡顿优化
  • 耗电优化
  • 包体积优化

1.13. TCP三次握手,四次挥手

三次握手

  1. 客户端发送[SYN] Seq=x
  2. 服务端发送[SYN, ACK] Seq=y Ack=x+1
  3. 客户端发送[ACK] Seq=y+1;

四次挥手

  1. 客户端发送[FIN] Seq=x
  2. 服务端发送[FIN, ACK] Ack=x+1
  3. 服务端发送[FIN] Seq=y;
  4. 客户端发送[ACK] Ack=y+1;
  5. 等待2MSL,服务端无响应,说明服务端已关闭,这时,客户端也关闭。

参考

1.14. 通知

本地通知

  1. 请求权限;
  2. 创建通知内容;
  3. 创建通知触发
  4. 创建请求;
  5. 将请求添加到通知中心。

远程通知

  1. 请求权限;
  2. 上传token至业务服务器;
  3. 业务服务器使用token向苹果APNS服务器提交请求;

  4. 活久见的重构 - iOS 10 UserNotifications 框架解析

1.15. 对象在什么时候释放?

  • Runloop状态为休眠或退出。
  • weak引用的对象在dealloc中释放。
  • 离开自动释放池作用域。

1.16. ipa文件结构

  • Frameworks:App引入的动态库;
  • Assets.car;
  • 可执行文件:MachO文件;
  • 资源文件:xx.bundle;
  • 签名文件:_CodeSignature

参考

1.17. 动态库和静态库

静态库:链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝。利用静态函数库编译成的文件比较大,因为整个 函数库的所有数据都会被整合进目标代码中,他的优点就显而易见了,即编译后的执行程序不需要外部的函数库支持,因为所有使用的函数都已经被编译进去了。当然这也会成为他的缺点,因为如果静态函数库改变了,那么你的程序必须重新编译。

动态库:链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存。由于函数库没有被整合进你的程序,而是程序运行时动态的申请并调用,所以程序的运行环境中必须提供相应的库。动态函数库的改变并不影响你的程序,所以动态函数库的升级比较方便。

参考

1.18. 事件传递

  1. 点击一个UIView或产生一个触摸事件A,这个触摸事件A会被添加到由UIApplication管理的事件队列中(即,首先接收到事件的是UIApplication)。
  2. UIApplication会从事件对列中取出最前面的事件(此处假设为触摸事件A),把事件A传递给应用程序的主窗口(keyWindow)。
  3. 窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件。

参考

1.19. 面向对象编程特征有哪些?

  • “抽象”,把现实世界中的某一类东西,提取出来,用程序代码表示;
  • “封装”,把过程和数据包围起来,对数据的访问只能通过已定义的界面;
  • “继承”,一种联结类的层次模型;
  • “多态”,允许不同类的对象对同一消息做出响应。

1.20. struct和class的区别?

https://juejin.cn/post/6844903799413276685

  1. 类属于引用类型,结构体属于值类型
  2. 类允许被继承,结构体不允许被继承
  3. 类中的每一个成员变量都必须被初始化,否则编译器会报错,而结构体不需要,编译器会自动帮我们生成init函数,给变量赋一个默认值

1.21. Swift设置访问权限如何设置?

https://juejin.cn/post/7012087397765054494

  • open
  • public
  • internal
  • fileprivate
  • private

1.22. Swift中的逃逸闭包(@escaping )与非逃逸闭包(@noescaping)

https://juejin.cn/post/6844903951519727629

概念:一个接受闭包作为参数的函数,该闭包可能在函数返回后才被调用,也就是说这个闭包逃离了函数的作用域,这种闭包称为逃逸闭包。当你声明一个接受闭包作为形式参数的函数时,你可以在形式参数前写@escaping来明确闭包是允许逃逸。

概念:一个接受闭包作为参数的函数, 闭包是在这个函数结束前内被调用。

1.23. 常见设计模式有哪些?

https://www.cnblogs.com/newsouls/archive/2011/07/28/DesignTemplage.html

  • 单例模式
  • 工厂模式

1.24. 如何用GCD同步若干个异步调用?

https://cloud.tencent.com/developer/article/1521135

  • 将几个线程加入到group中, 然后利用group_notify来执行最后要做的动作
  • 利用GCD信号量dispatch_semaphore_t来实现

1.25. 创建线程有几种方式?

  • pthread 实现多线程操作
  • NSThread实现多线程
  • GCD 实现多线程
  • NSOperation

https://youle.zhipin.com/questions/4c09e5e18447d9dbtnV809W7FlQ~.html

引用计数小于1的时候释放的。在ARC环境下我们不能直接去操作引用计数的值,但是我们可以跟踪是否有strong指针指向、如果没有strong指针指向、则立即销毁。 这里有一个地方值得关注的事自动缓存池,他会延迟销毁时机,但是实际上也是延迟执行re lease而已。

1.26. 异步上传实现

  • 分块上传;
  • Socket监听;

参考

1.27. https是什么?

  1. 客户端向服务器发送 HTTPS 请求。
  2. 服务器将公钥证书发送给客户端。
  3. 客户端验证服务器的证书。
  4. 如果验证通过,客户端生成一个用于会话的对称密钥。
  5. 客户端使用服务器的公钥对对称密钥进行加密,并将加密后的密钥发送给服务器。
  6. 服务器使用私钥对客户端发送的加密密钥进行解密,得到对称密钥。
  7. 服务器和客户端使用对称密钥进行加密和解密数据传输。

参考

1.28. 对称加密和非对称加密的区别?

  • 对称加密:加密解密都是用相同规则。
  • 非对称加密:公钥加密的信息只有私钥解得开,那么只要私钥不泄漏,通信就是安全的。

苹果证书校验

  1. Mac生成c公钥、c私钥;
  2. 苹果服务器使用s私钥对c公钥加密,生成证书文件给Mac。
  3. Mac使用c私钥加密可执行文件(带证书),通过可执行文件在手机安装。
  4. 手机内置苹果服务器s公钥,解密证书,等到c公钥。
  5. 手机通过c公钥匙验证可执行文件,通过,则可以安装。

参考

1.29. https证书校验

  1. 创建NSURLSession对象。
  2. 创建NSURLSessionDataTask对象。
  3. 设置NSURLSessionDelegate代理。
  4. 实现NSURLSessionDelegate代理方法
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {

    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {

        // 验证证书

        SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;

        SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, 0);

        NSData *remoteCertificateData = CFBridgingRelease(CFDataCreateCopy(NULL, SecCertificateCopyData(certificate)));

        NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"server" ofType:@"cer"];

        NSData *localCertData = [NSData dataWithContentsOfFile:cerPath];

        if ([remoteCertificateData isEqualToData:localCertData]) {

            // 验证通过

            NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];

            completionHandler(NSURLSessionAuthChallengeUseCredential, credential);

        } else {

            // 验证失败

            completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);

        }

    } else {

        completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);

    }

}

参考

1.30. CocoaPods

1.31. 组件化开发方式有哪些?

1.32. 轮播图实现原理

1.33. 如何存放敏感信息?

1.34. 如何使用Git?

1.35. OC和Swift如何互相调用?

1.36. RESTful

1.37. NoSql

1.38. JAVA面试题大全(200+道题目)

1.39. Java容器有哪些?

  • Collection
    • List
      • ArrayList
      • LinkedList
      • Vector
    • Set
      • HashSet
        • LinkedHashSet
      • TreeSet
  • Map
    • HashMap
      • LinkedHashMap
    • HashTable
    • TreeMap

1.40. List、Set、Map 之间的区别是什么?

  • List:有序集合、元素可重复。
  • Set:元素不可重复,HashSet无序,LinkedHashSet按照插入排序,SortedSet可排序。
  • Map:键值对集合,存储键、值之间的映射。key无序,唯一,value可重复。

  • HashMap和HashTable到底哪不同?

1.41. HashMap 和 Hashtable 有什么区别?

1.42. ArrayList 和 LinkedList 的区别是什么?

  • ArrayList的数据结构是动态数组;LinkedList的数据结构是双向链表。
  • ArrayList比LinkedList在随机访问的时候效率要高,因为LinkedList是线性的数据结构,需要依次往后查找。
  • 在非首尾的增删操作,LinkedList要比ArrayList的效率要高,因为ArrayList在操作增删时要影响其他元素的下标。

总结:需要频繁读取集合中的元素时,推荐使用ArrayList;插入和删除操作较多时,推荐使用LinkedList。

1.43. ArrayList 和 Vector 的区别是什么?

  • 相同点:都实现了List接口,都是有序集合;
  • 区别:Vector是线程安全的,ArrayList不是线程安全的;
  • 当Vector或ArrayList中的元素超过它的初始大小时,Vector会将容量翻倍,而ArrayList只会将容量扩大50%。

1.44. Array 和 ArrayList 有何区别?

  • Array类型的变量在声明时必须实例化;ArrayList可以只是先声明;
  • Array大小是固定的,而ArrayList的大小是动态变化的;
  • Array可以包含基本类型和对象类型,ArrayList只能包含对象类型;

1.45. 说一下 spring 的事务隔离?

  • 脏读:当前事务读取了其他事物没有提交成功的数据。
    • 使用“读提交”
  • 不可重复度:当前事务读了同一条记录,数据不一样,因为其他事务在两次读取期间修改了这条记录。
    • 使用行级锁
  • 幻读:当前事务两次统计表行数不一样,因为其他事务新增或者删除表记录。
    • 使用表级锁
  • 事务隔离级别:

    • 读未提交
    • 读提交
    • 重复度
    • 序列化
  • 快速理解脏读、不可重复读、幻读和MVCC

1.46. 说一下 spring mvc 运行流程?

  1. 请求被DispatcherServlet处理
  2. DispatcherServlet通过HandlerMapping找到Controlller
  3. DispatcherServlet调用HandlerAdapter执行Controller方法
  4. 返回ModelAndView对象
  5. ViewResolver渲染

1.47. 数据库的三范式是什么?

1.48. 说一下 jvm 的主要组成部分?及其作用?

  • 类加载器(ClassLoader)
  • 运行时数据区(Runtime Data Area)
  • 执行引擎(Execution Engine)
  • 本地库接口(Native Interface)

组件的作用: 首先通过类加载器(ClassLoader)会把 Java 代码转换成字节码,运行时数据区(Runtime Data Area)再把字节码加载到内存中,而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。

1.49. 说一下 jvm 运行时数据区域?

  • 程序计数器
  • 虚拟机栈
  • 本地方法栈
  • 堆(创建的对象)
  • 方法区(类的属性、成员变量、构造函数等)

1.50. 说一下类加载的执行过程?

  • 加载:根据查找路径找到相应的 class 文件然后导入
  • 检查:检查加载的 class 文件的正确性
  • 准备:给类中的静态变量分配内存空间
  • 解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标识,而在直接引用直接指向内存中的地址;
  • 初始化:对静态变量和静态代码块执行初始化工作。

results matching ""

    No results matching ""