美文网首页
iOS面试技术点总结

iOS面试技术点总结

作者: 苹果上的小豌豆 | 来源:发表于2019-07-11 10:46 被阅读0次

    1.category和extension。

    (分类(Category)是OC中的特有语法,它是表示一个指向分类的结构体的指针。原则上它只能增加方法,分类的指针结构体中,根本没有属性列表,不能增加成员(实例)变量。由于OC是动态语言,方法真正的实现是通过runtime完成的,虽然系统不给我们生成setter/getter,但我们可以通过runtime手动添加setter/getter方法)类扩展中添加的新方法,一定要实现。

    2.Struct (结构体) 和类(Class)

    类是引用类型,结构体是值类型。值类型在传递和赋值时将进行复制拷贝,而引用类型则只会使用引用对象的一个"指向"。

    内存中,引用类型诸如类是在堆(heap)上,而值类型诸如结构体实在栈(stack)上进行存储和操作。相比于栈上的操作,堆上的操作更加复杂耗时,所以苹果官方推荐使用结构体,这样可以提高 App 运行的效率。

    class优势:

    1.class可以继承,这样子类可以使用父类的特性和方法

    2.类型转换可以在runtime的时候检查和解释一个实例的类型

    3.可以用deinit来释放资源

    4.一个类可以被多次引用。

    Struct优势:结构较小,适用于复制操作,相比于一个class的实例被多次引用更加安全。无须担心内存memory leak或者多线程冲突问题。

    3.iOS与H5的交互【WKWebView】

    1.JS调用原生iOS代码(可以参考WebViewJavascriptBridge)

    self.webView.configuration.userContentController.add(self,name:"hidOpenInApp")

    2.iOS调用原生JS代码

    self.webView.evaluateJavaScript(doc,completionHandler: { (htmlStr, error)iniferror != nil { print(error!) }elseif(htmlStr != nil){ print(htmlStr!) } })


    4.多线程原理

    1.同步, 异步, 并发, 串行

    2.开线程异步执行完耗时代码,返回主线程刷新UI。

    3.等待异步执行多个任务后, 再执行下一个任务。

    4.延时提交任务

    5.信号量--控制最大并发数

    (信号量是一个整形值并且具有一个初始计数值,并且支持两个操作:信号通知和等待。当一个信号量被信号通知,其计数会被增加。当一个线程在一个信号量上等待时,线程会被阻塞(如果有必要的话),直至计数器大于零,然后线程会减少这个计数。)

    在GCD中有三个函数是semaphore的操作,分别是: 1、dispatch_semaphore_create   创建一个semaphore 2、dispatch_semaphore_signal   发送一个信号 3、dispatch_semaphore_wait    等待信号 

    //每十个任务并发执行 func GCDTest4() { let group = DispatchGroup.init() let queue = DispatchQueue.global() //剩余10个车位 let semaphore = DispatchSemaphore.init(value: 10) for i in 1...100 { //来了一辆车,信号量减1 let result = semaphore.wait(timeout: .distantFuture) if result == .success { queue.async(group: group, execute: { print("队列执行\(i)--\(Thread.current)") //模拟执行任务时间 sleep(3) //延迟3s后,走了一辆车,信号量+1 semaphore.signal() }) } } group.wait() }

    6.Group的用法

    (你想某个任务在其他任务执行之后再执行, 或者必须某个任务执行完,才能执行下面的任务, 可以使用DispatchGroup:)

     let group = DispatchGroup()         let queue = DispatchQueue(label: "uploadQueue", attributes: .concurrent)         queue.async(group: group){             //上传图片到七牛云上                }         group.notify(queue: queue){}


    GCD中有两个核心概念(任务block和队列queue)

    label: 队列的标识符,方便调试qos: 队列的quality of service。用来指明队列的“重要性”,后文会详细讲到。attributes: 队列的属性。类型是DispatchQueue.Attributes,是一个结构体,遵循了协议OptionSet。意味着你可以这样传入第一个参数[.option1,.option2]。默认:队列是串行的。.concurrent:队列为并行的。.initiallyInactive:则队列任务不会自动执行,需要开发者手动触发。autoreleaseFrequency: 顾名思义,自动释放频率。有些队列是会在执行完任务后自动释放的,有些比如Timer等是不会自动释放的,是需要手动释放。

    suspend / resume(挂起线程和继续执行线程)

    DispatchGroup用来管理一组任务的执行,然后监听任务都完成的事件。比如,多个网络请求同时发出去,等网络请求都完成后reload UI。

    Group.enter / Group.leave

    let group = DispatchGroup() group.enter()networkTask(label: "1", cost: 2, complete: { group.leave()}) group.enter()networkTask(label: "2", cost: 2, complete: { group.leave()}) group.wait(timeout: .now() + .seconds(4)) group.notify(queue: .main, execute:{ print("All network is done") 

    Group.wait

    (DispatchGroup支持阻塞当前线程,等待执行结果。)

    group.wait(timeout: .now() + .seconds(3))

    after(延迟执行)

    DispatchTime 的精度是纳秒DispatchWallTime 的精度是微秒

    DispatchQueue.global().asyncAfter(deadline: .now() + 2) {  }         DispatchQueue.main.asyncAfter(deadline: .now() + 2) { }

    DispatchQueue中main队列是串行队列而global()队列则是并行队列

    Barrier 线程阻断

    barrier任务提交后,等待前面所有的任务都完成了才执行自身。barrier任务执行完了后,再执行后续执行的任务。

    5.swift中的闭包

    1.尾随闭包(如果闭包是函数的最后一个参数,那么可以将闭包写在()后面,如果函数只有一个参数且为闭包,那么()可以不写)

    2.逃逸闭包(一个传入函数的闭包如果在函数执行结束之后才会被调用,那么这个闭包就叫做逃逸闭包 (通俗点讲,不在当前方法中使用闭包,而是在方法之外使用))

    定义函数的参数为逃逸闭包时,只需要在参数名之前标注 @escaping,用来指明这个闭包是允许“逃逸”出这个函数的

    将一个闭包标记为@escaping意味着你必须在闭包中显式地引用self

    3.自动闭包

    自动创建一个闭包用来包裹一个表达式,这种闭包不接受任何参数,当闭包被调用时,返回包裹在闭包中的表达式的值。自动闭包让你能够延迟求值,因为直到你调用这个闭包,代码段才会被执行,这种便利语法让你能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包。

    场景一:闭包应用之GCD线程间通信写法

    DispatchQueue.global().async { print("异步子线程") DispatchQueue.main.sync(execute: { print("回到主线程") }) }

    场景二:闭包(尾随闭包)值网络请求数据简单示例

    //关键字 变量 变量类型 () -> () //let callBack: ((_ result: [String : Any]?, _ error: Error?) -> ()) func loadData(callBack: @escaping ((_ result: [String: Any]?, _ error: Error?) -> ())) -> () { DispatchQueue.global().async { //异步获取json数据 let json = [ "name":"zhouyu", "age":16, "blog":"https://blog.csdn.net/kuangdacaikuang", "work":"iOS开发工程师" ] as [String : Any] DispatchQueue.main.sync(execute: { //主线程处理数据 var error: Error? callBack(json,error) }) } } 

    (实际上,就是在我们网络请求封装的回调里面就是一个尾随闭包)

    闭包重点 中 [unowned self] 和 [weak self]

    [unowned self] 不会置为nil

    [weak self]会置为nil

    @?block默认不使用

    所有在异步请求回调的block中需要使用 [weak self],如果使用 [unowned self],异步回调时self 可能已被释放,会引起EXC_BAD_ACCESS 野指针错误而crash。



    6.OC中的runtime机制和原理使用

    Objective-C是基于C语言加入面向对象特性和消息转发机制的动态语言,这就是说它不仅需要一个编译器,还需要Runtime系统动态的创建类和对象,进行消息发送和转发。

    runtime是一套比较底层的纯C语言API, 属于一个C语言库, 包含了很多底层的C语言API。在我们平时编写的OC代码中, 程序运行过程时, 其实最终都是转成了runtime的C语言代码, runtime算是OC的幕后工作者!

    [[Dog alloc] init]            runtime : objc_msgSend(objc_msgSend(“Dog” , “alloc”), “init”)

    相关函数

    objc_msgSend : 给对象发送消息

    class_copyMethodList : 遍历某个类所有的方法

    class_copyIvarList : 遍历某个类所有的成员变量

    使用场景:

    动态添加属性、动态添加方法、方法交换、字典模型转换

    // 获取image的系统方法imageWithName,代码1 Method imageWithName = class_getClassMethod([UIImage class], @selector(imageNamed:)); // 获取自定义的In_ImageName方法,代码2 Method In_imageName = class_getClassMethod([UIImage class], @selector(In_imageName:)); // 交换两个方法的指针地址,相当于交换两个方法的实现,代码3 method_exchangeImplementations(In_imageName, imageWithName); 

    添加属性

    - (NSString *)name{ return objc_getAssociatedObject(self, @"name"); } -(void)setName:(NSString *)name{ // objc_setAssociatedObject(将某个值跟某个对象关联起来,将某个值存储到某个对象中) // object:给哪个对象添加属性 // key:属性名称 // value:属性值 // policy:保存策略 objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_COPY_NONATOMIC); } 

    NSObject *objc = [[NSObject alloc] init]; objc.name = @"huhuhu"; NSLog(@"runtime动态添加属性:%@",objc.name);

    7.iOS底层原理总结 - RunLoop

    RunLoop 的概念:顾名思义,运行循环,在程序运行过程中循环做一些事情。

    我们平时开发中的很多东西都和RunLoop相关,比如:1.AutoreleasePool2.NSTimer3.消息通知4.perform函数5.网络请求6.dispatch调用7.block回调8.KVO.

    触摸事件以及各种硬件传感器

    runloop 整个的运行逻辑都是在于三个重要的对象如何运作:source (输入源)、timer (定时器)、observer (观察者)。

    苹果官方 RunLoop 实际应用

    1、自动释放池

    2、NSTimer (定时器)(NSTimer 其实就是 CFRunLoopTimerRef,他们之间是 toll-free bridged 的。)

    3、PerformSelecter...(当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。)(button事件的取消或者延后)

    4、事件响应

    5、手势识别(苹果注册了一个 Observer 监测 BeforeWaiting (Loop即将进入休眠) 事件,这个Observer的回调函数是 _UIGestureRecognizerUpdateObserver(),其内部会获取所有刚被标记为待处理的 GestureRecognizer,并执行GestureRecognizer的回调。)

    6.UI更新

    7.GCD

    8.AsyncDisplayKit(AsyncDisplayKit是 Facebook 推出的用于保持界面流畅性的框架,其原理大致如下:UI 线程中一旦出现繁重的任务就会导致界面卡顿,这类任务通常分为3类:排版,绘制,UI对象操作。排版通常包括计算视图大小、计算文本高度、重新计算子式图的排版等操作。绘制一般有文本绘制 (例如 CoreText)、图片绘制 (例如预先解压)、元素绘制 (Quartz)等操作。)

    9。UIImageView 延迟加载图片(给 UIImageView 设置图片可能耗时不少,如果此时要滑动 tableView 等则可能影响到界面的流畅。)(解决是:使用performSelector:withObject:afterDelay:inModes: 方法,将设置图片的方法放到 DefaultMode 中执行。为了流畅性,把图片加载延迟。)

    『ios』多个cell时间倒计时,最佳实现方法探索


    8.[HandyJSON] 设计思路简析

    HandyJSON的实现思路

    JSON转字典,同时处理自定义映射的内容 --> 获取对象/结构体属性的内存位置,然后将值写入~ 而其中最核心的内容就是 获取对象/结构体的属性列表与偏移量.

    HandyJSON是通过元数据的Nominal Type Descriptor来获取的~ 这是定义的结构体

    在WWDC2017大会上, Swift4.0的发布新增了一个重要的功能:Codable。Codable是一个协议,其作用类似于NSPropertyListSerialization 和 NSJSONSerialization,主要用于完成 JSON 和Model之间的转换。

    9.内存泄漏

    1.内存泄漏(memory leak):是指申请的内存空间使用完毕之后未回收

    2.内存溢出(out of memory):是指程序在申请内存时,没有足够的内存空间供其使用。

    第一种:静态分析方法(product ->Analyze)

    第二种:动态分析方法(Instrument工具库里的Leaks)。一般推荐使用第二种。

    内存泄漏的几个原因:1.NSTimer2.block3.delegate


    10.性能优化

    卡顿解决的主要思路

    卡顿解决的主要思路 尽可能减少CPU、GPU资源消耗 按照60FPS的刷帧率,每隔16ms就会有一次VSync信号 尽量用轻量级的对象,比如用不到事件处理的地方,可以考虑使用CALayer取代UIView 不要频繁地调用UIView的相关属性,比如frame、bounds、transform等属性,尽量减少不必要的修改 尽量提前计算好布局,在有需要时一次性调整对应的属性,不要多次修改属性 Autolayout会比直接设置frame消耗更多的CPU资源 图片的size最好刚好跟UIImageView的size保持一致 控制一下线程的最大并发数量 尽量把耗时的操作放到子线程 文本处理(尺寸计算、绘制) 图片处理(解码、绘制) 尽量避免短时间内大量图片的显示,尽可能将多张图片合成一张进行显示 GPU能处理的最大纹理尺寸是4096x4096,一旦超过这个尺寸,就会占用CPU资源进行处理,所以纹理尽量不要超过这个尺寸 尽量减少视图数量和层次 减少透明的视图(alpha<1),不透明的就设置opaque为YES 尽量避免出现离屏渲染  

    离屏渲染消耗性能的原因

    需要创建新的缓冲区 离屏渲染的整个过程,需要多次切换上下文环境,先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上,又需要将上下文环境从离屏切换到当前屏幕 #######哪些操作会触发离屏渲染? 光栅化,layer.shouldRasterize = YES 遮罩,layer.mask 圆角,同时设置layer.masksToBounds = YES、layer.cornerRadius大于0 考虑通过CoreGraphics绘制裁剪圆角,或者叫美工提供圆角图片 阴影,layer.shadowXXX 如果设置了layer.shadowPath就不会产生离屏渲染  

    APP的启动优化

    按照不同的阶段 dyld 减少动态库、合并一些动态库(定期清理不必要的动态库) 减少Objc类、分类的数量、减少Selector数量(定期清理不必要的类、分类) 减少C++虚函数数量 Swift尽量使用struct runtime 用+initialize方法和dispatch_once取代所有的attribute((constructor))、C++静态构造器、ObjC的+load main 在不影响用户体验的前提下,尽可能将一些操作延迟,不要全部都放在finishLaunching方法中 按需加载  

    11.集成第三方SDK,关于bitcode。

    关于bitcode, 知道这些就够了

    相关文章

      网友评论

          本文标题:iOS面试技术点总结

          本文链接:https://www.haomeiwen.com/subject/ojrxkctx.html