irpas技术客

iOS面试准备 - ios篇_smallcatlei_ios 面试 通知

未知 921

iOS面试准备 - ios篇 ios面试准备 - objective-c篇 ios面试准备 - 网络篇 IOS面试准备 - C++篇 iOS面试准备 - 其他篇

运行时

https://juejin.cn/post/6844903586216804359

Runtime消息发送机制 首先进行方法的查找: 1)iOS调用一个方法时,实际上会调用objc_msgSend(receiver, selector, arg1, arg2, …),该方法第一个参数是消息接收者,第二个参数是方法名,剩下的参数是方法参数; 2)iOS调用一个方法时,会先去该类的方法缓存列表里面查找是否有该方法,如果有直接调用,否则走第3)步; 3)去该类的方法列表里面找,找到直接调用,把方法加入缓存列表;否则走第4)步; 4)沿着该类的继承链继续查找,找到直接调用,把方法加入缓存列表;否则消息转发流程;

如果没有找到方法,开始消息的转发流程:

1)动态消息解析。检查是否重写了resolveInstanceMethod 方法,如果返回YES则可以通过class_addMethod 动态添加方法来处理消息,否则走第2)步; 2)消息target转发。forwardingTargetForSelector 用于指定哪个对象来响应消息。如果返回nil 则走第3)步; 3)消息转发。这步调用 methodSignatureForSelector 进行方法签名,这可以将函数的参数类型和返回值封装。如果返回 nil 执行第四步;否则返回 methodSignature,则进入 forwardInvocation ,在这里可以修改实现方法,修改响应对象等,如果方法调用成功,则结束。否则执行第4)步; 4)报错 unrecognized selector sent to instance。

load和initialize +load在main函数之前被Runtime调用,+initialize 方法是在类或它的子类收到第一条消息之前被调用的,这里所指的消息包括实例方法和类方法的调用。

load 当类被引用进项目的时候就会执行load函数(在main函数开始执行之前),与这个类是否被用到无关,每个类的load函数只会自动调用一次.由于load函数是系统自动加载的,因此不需要调用父类的load函数,否则父类的load函数会多次执行。

当父类和子类都实现load函数时,父类的load方法执行顺序要优先于子类 类中的load方法执行顺序要优先于类别(Category) 当有多个类别(Category)都实现了load方法,这几个load方法都会执行,但执行顺序不确定(其执行顺序与类别在Compile Sources中出现的顺序一致) 当然当有多个不同的类的时候,每个类load 执行顺序与其在Compile Sources出现的顺序一致

initialize initialize在类或者其子类的第一个方法被调用前调用。即使类文件被引用进项目,但是没有使用,initialize不会被调用。由于是系统自动调用,也不需要再调用 [super initialize] ,否则父类的initialize会被多次执行。假如这个类放到代码中,而这段代码并没有被执行,这个函数是不会被执行的。 1.父类的initialize方法会比子类先执行 2.当子类未实现initialize方法时,会调用父类initialize方法,子类实现initialize方法时,会覆盖父类initialize方法. 3.当有多个Category都实现了initialize方法,会覆盖类中的方法,只执行一个(会执行Compile Sources 列表中最后一个Category 的initialize方法)

怎么确保在load和initialize的调用只执行一次: 由于load和initialize可能会调用多次,所以在这两个方法里面做的初始化操作需要保证只初始化一次,用dispatch_once来控制

runtime应用 关联对象(Objective-C Associated Objects)给分类增加属性 方法魔法(Method Swizzling)方法添加和替换和KVO实现 消息转发(热更新)解决Bug(JSPatch) 实现NSCoding的自动归档和自动解档 实现字典和模型的转换(YYModel)

RunLooper

https://segmentfault.com/a/1190000023390697 线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。

系统默认注册了5个Mode: kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。 UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。 UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。 GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。 kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。

与GCD关系:当调用 dispatch_async(dispatch_get_main_queue(), block) 时,libDispatch 会向主线程的 RunLoop 发送消息,RunLoop会被唤醒,并从消息中取得这个 block,并在回调

解决NSTimer事件在列表滚动时不执行问题 因为定时器默认是运行在NSDefaultRunLoopMode,在列表滚动时候,主线程会切换到UITrackingRunLoopMode,导致定时器回调得不到执行。 有两种解决方案: ● 指定NSTimer运行于 NSRunLoopCommonModes下。 ● 在子线程创建和处理Timer事件,然后在主线程更新 UI。

GCD

GCD是核心XNU内核级实现的高效多线程编程功能。

dispatch_queue_create 创建队列

dispatch_get_main_queue() 获取主队列

dispatch_get_global_queue();并行队列,优先级依次为:ios7:高,默认,低,后台. ios7之后:用户交互,用户需要,默认,工具级,后台,没有指定。

dispatch_set_target_queue 变更优先级

dispatch_after 在一定时间之后执行

dispatch_time(DISPATCH_TIME_NOW, 200ull * NSEC_PER_SEC); 时间变量

dispatch_group_t group = dispatch_group_create(); 创建组 dispatch_group_async(group, queue, ^{NSLog(@“1”);}); 使用组 dispatch_group_notify(group, queue, ^{NSLog(@“4”);}); 在一组最后执行

dispatch_barrier_async 在这个调用之前的async先完成,再调用这个的代码块,在这个之调用后,在他之后的async才开始执行

dispatch_sync是等待处理执行结束后,再继续

dispatch_apply 自动适配线程,循环处理指定次数

dispatch_once 保证只执行一次,可以用在单例模式

+(instancetype)sharedInstance{ static MyClass *sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[MyClass alloc]init]; }); return _sharedInstance; }

dispatch_semaphore_create 创建信号量 自旋锁 dispatch_semaphore_signal 信号量加1 dispatch_semaphore_wait 信号量减1,如果信号量等于0就阻塞等待

NSOperation

NSOperation是基于GCD更高一层的封装。 一般使用 NSInvocationOperation 或者 NSBlockOperation ,也可以自己继承实现,需要实现main函数。

NSOperationQueue 是执行队列 添加到主队列的都会在主队列中执行 添加到其他队列的都会在子线程中执行

队列最大并发数设置 maxConcurrentOperationCount

函数: NSOperationQueue的函数: addOperation:把NSOperation 加到队列中 addExecutionBlock: 直接把代码块作为任务加到队列中 cancelAllOperations; NSOperationQueue提供的方法,可以取消队列的所有操作,所有任务取消,包括正在执行的,还未执行的。 setSuspended:(BOOL)b; 可设置任务的暂停和恢复,YES代表暂停队列,NO代表恢复队列

NSOperation的函数: cancel; ,可取消单个操作 .qualityOfService = NSQualityOfServiceBackground;设置优先级有:非常低,低,通常,高,非常高 [A addDependency:B] A在B执行完了过后开始执行

https://·/post/6844903805369188366 1.简述 CoreData是数据存储框架,采用了一种对象关系映射的存储关系。 CoreData一个比较大的优势在于在使用CoreData过程中不需要我们编写SQL语句,也就是将OC对象存储于数据库,也可以将数据库数据转为OC对象(数据库数据与OC对象相互转换)。 2.CoreData几个类 (1)NSManagedObjectContext 托管对象上下文,数据库的大多数操作是在这个类操作 (2)NSManagedObjectModel 托管对象模型,其中一个托管对象模型关联到一个模型文件,里面存储着数据库的数据结构。 (3)NSPersistentStoreCoordinator 持久化存储协调器,主要负责协调上下文玉存储的区域的关系。 (4)NSManagedObject 托管对象类,其中CoreData里面的托管对象都会继承此类。

FMDB

内部封装c语言 通过 sqlite3系列函数操作数据库。

有三个主要的类: 1.FMDatabase – 表示一个单独的SQLite数据库。 用来执行SQLite的命令。 2.FMResultSet – 表示FMDatabase执行查询后结果集 3.FMDatabaseQueue – 如果你想在多线程中执行多个查询或更新,你应该使用该类。这是线程安全的。

创建FMDatabase对象时参数为SQLite数据库文件路径。该路径可以是以下三种之一: 1…文件路径。该文件路径无需真实存,如果不存在会自动创建。 2…空字符串(@”")。表示会在临时目录创建一个空的数据库,当FMDatabase 链接关闭时,文件也被删除。 3.NULL. 将创建一个内在数据库。同样的,当FMDatabase连接关闭时,数据会被销毁。

打开数据库 在和数据库交互 之前,数据库必须是打开的。如果资源或权限不足无法打开或创建数据库,都会导致打开失败

常用命令 SELECT、CREATE, UPDATE, INSERT,ALTER,COMMIT, BEGIN, DETACH, DELETE, DROP, END, EXPLAIN, VACUUM, and REPLACE (等) 只要不是以SELECT开头的命令都是UPDATE命令。执行更新返回一个BOOL值。YES表示执行成功,否则表示有那些错误 。你可以调用 -lastErrorMessage 和 -lastErrorCode方法来得到更多信息。

执行查询 SELECT命令就是查询,执行查询的方法是以 -excuteQuery开头的。执行查询时,如果成功返回FMResultSet对象, 错误返回nil. 与执行更新相当,支持使用 NSError**参数。同时,你也可以使用 -lastErrorCode和-lastErrorMessage获知错误信息。

添加表字段的sql语句怎么写 1、判断表是否打开 2、判断表中是否存在当前的一个或者多个字段 3、如果不存在添加字段

性能优化

应用启动优化:合并动态库;删除无用类;删除无用静态变量;将不必须在+load方法中做的事情延迟到+initialize中;尽量使用纯代码编写,减少xib的使用;耗时操作异步执行;不用AutoLayout;优化代码降低包大小

懒加载和复用对象

是否透明属性opaque YES表示不透明。

列表性能提升: reuseIdentifier避免每次渲染cell都重建;尽量非透明opaque 为YES;缓存行高;耗时如网络请求,异步加载缓存结果;Shadow Path替代直接用阴影;减少层次结构。

不要阻塞主线程,耗时操作异步到子线程

正确选择Collection:Arrays: 有序的一组值。使用index来lookup很快,使用value lookup很慢, 插入/删除很慢;Dictionaries: 存储键值对。 用键来查找比较快;Sets: 无序的一组值。用值来查找很快,插入/删除很快。

使用缓存,比如NSCache,缓存文件等

重用大开销对象,恰当使用单例模式

webview重用

数据存储

使用NSUserDefaults 存沙盒文件,小文件 plist 文件储存(xcode配置里面就有这个文件) 归档保存,将字典数组或者实现了(NSCoding)协议的对象转换成字节流NSData,再写入文件。使用NSKeyedArchiver和NSKeyedUnarchiver SQLite数据库 (开源库 FMDB、Realm等) 使用 Core Data

autoreleasepool

自动释放池块提供了一种机制,您可以通过该机制放弃对象的所有权,但避免立即释放它的可能性(例如当您从方法返回对象时) Cocoa 总是希望代码在自动释放池块中执行,否则自动释放的对象不会被释放并且您的应用程序会泄漏内存。

使用自己的自动释放池块:

1.如果您正在编写不基于 UI 框架的程序,例如命令行工具。 2.如果您编写一个创建许多临时对象的循环。 您可以在循环内使用自动释放池块在下一次迭代之前处理这些对象。在循环中使用自动释放池块有助于减少应用程序的最大内存占用。 3.如果你产生一个辅助线程。 您必须在线程开始执行后立即创建自己的自动释放池块;否则,您的应用程序将泄漏对象。(Cocoa 应用程序中的每个线程都维护自己的自动释放池块堆栈。如果您正在编写仅 Foundation 程序或分离线程,则需要创建自己的自动释放池块。)

main函数的autoreleasepool :autorelease对象的释放时机是由RunLoop控制的,会在当前RunLoop每次循环结束时释放。

iOS 自动释放池ARC与MRC

内存分布

内存泄漏

内存泄漏的原因 在用C/C++时,创建对象后未销毁,比如调用malloc后不free、调用new后不delete; 调用CoreFoundation里面的C方法后创建对对象后不释放。比如调用CGImageCreate不调用CGImageRelease; 循环引用。 NSTimer 有些注册未解开注册 子线程未关闭runlooper或者有死循环,死锁等让该关闭的线程关闭不了

CFRunLoopStop([NSRunLoop currentRunLoop].getCFRunLoop);

检测循环引用 静态代码分析。 通过Xcode->Product->Anaylze分析结果来处理; 动态分析。用MLeaksFinder(只能检测OC泄露)或者Instrument或者OOMDetector(能检测OC与C++泄露)。

其他

对于视图或者图层来说,frame是一个虚拟属性,是根据bounds,position和transform计算而来,所以当其中任何一个值发生改变,frame都会变化。相反,改变frame的值同样会影响到他们当中的值。 https://zsisme.gitbooks.io/ios-/content/chapter3/layout.html

为什么圆角和透明触发离屏渲染:

当App需要进行额外的渲染和合并时,例如按钮设置圆角,我们是需要对UIButton这个控件中的所有图层都进行圆角+裁剪,然后再将合并后的结果存入帧缓存区,再从帧缓存中取出交由屏幕显示,这时,在正常的渲染流程中,我们是无法做到对所有图层进行圆角裁剪的,因为它是用一个丢一个。所以我们需要提前将处理好的结果放入离屏缓冲区,最后将几个图层进行叠加合并,存放到站缓冲区,最后屏幕上就是我们想实现的效果。 super class是怎么调用的 super 不同于 self,它不是个对象,而是个 flag 用于 objc_msgSendSuper 的结构体 objc_super 是编译时确定的,里面包含了当前类的父类信息 [super someMethod] 会去利用结构体中的父类信息,从这个父类开始顺着继承链向上查找,直到找到第一个实现 - someMethod 这个方法的类 找到方法后,利用结构体中的 receiver,也就是一开始触发这个方法调用的实例,调用这个方法实现。 所以说,[super class] 经过这么一大圈的转换,实际上变成了 [self class] 了。

weak的实现 Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。

1、初始化时:runtime初始化一个新的weak指针指向对象的地址。 2、添加引用时:更新指针指向,创建对应的弱引用表。 3、释放时,首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除。 静态链接 静态链接:每一个.c的代码源文件可以被理解成一个模块,每一个模块独立编译,再把所有编译完的文件链接起来,这个过程就是我们所说的静态链接。 静态库: 例如.a和.framework。静态库链接时,会被完整地复制到可执行文件中,被使用到了多次,就会复制多份,这样就有多份拷贝很冗余,链接时间长了,还浪费了内存空间。

静态链接流程: 1.空间与地址分配 每个单独的文件编译后都会生成一个符号表,静态链接后这些表会被合并成一个全局符号表。合并的规则是相似段合并、数据段与数据段合并、代码段与代码段合并。 合并后每一个符号的的地址被确定,并写入全局符号表中。 2.重定位符号 经过空间与地址分配之后代码段中指令用到的符号地址还没有更新,想要确定符号的地址需要用到重定位表。编译后.o文件中需要重定位的符号的相关信息会存入重定位表中。

动态链接 程序编译时并不会链接到目标程序中,目标程序只会存储指向动态库的引用,在程序运行时才被载入。: 共享内存,节省资源,同一份库可以被多个程序使用;减少打包之后的 app 的大小;

CoreGraphics,和CoreAnimation

为什么必须在主线程操作UI UIKit并不是一个线程安全的类,UI操作涉及到渲染访问各种View对象的属性,如果异步操作下会存在读写问题, 而为其加锁则会耗费大量资源并拖慢运行速度。 另一方面因为整个程序的起点UIApplication是在主线程进行初始化,所有的用户事件都是在主线程上进行传递(如点击、拖动),所以view只能在主线程上才能对事件进行响应。 而在渲染方面由于图像的渲染需要以60帧的刷新率在屏幕上 同时 更新,在非主线程异步化的情况下无法确定这个处理过程能够实现同步更新。

省电 测试工具:xcode自带工具:Energy Impact。可以图形直观展示CPU,GPU,定位,网络,后台,前台等耗电占比。

省电的方案: 识别:想清楚你需要app在特定时刻需要完成哪些工作,如果是不必要的工作,考虑延后执行或者省去。 优化:优化app的功能实现,尽可能以更有效率的方式去完成功能。 合并:不需要立刻获取,可以延后合并执行,比如合并网络 减少:在满足需求的基础上,尽量减少做重复工作的频率。

耗电大户: 网络:应减少数据传输,合并网络请求,适当的网络延时等 定位:精度越高,定位时间越长,越耗电 CPU: GPU:UI不可见时,应该避免更新其内容 传感器和蓝牙: 传感器数据应按需获取,用完即停;蓝牙应该尽量分批、减少数据轮询等操作

帧率FPS 代码中检测:CADisplayLink Xcode检测工具:Core Animation

主线程卡顿监测: 通过开辟一个子线程来监测主线程的RunLoop,当两个状态区域的耗时大于设定的阈值时,即为一次卡顿。

判断是否实现协议 conformsToProtocol

判断有没有实现dalegate的某个方法 respondsToSelector

通知 postNotification是同步方法。当调用addObserver方法监听通知,然后调用postNotification抛通知,postNotification会在当前线程遍历所有的观察者,然后依次调用观察者的监听方法,调用完成后才会去执行postNotification后面的代码。

实现异步的通知使用:addObserverForName:object:queue:usingBlock来实现异步通知。

监测野指针 xcode Run配置打开 内存涂鸦(Malloc Scribble),将释放的内存进行涂鸦成固定值,导致使用野指针的时候一定crash xcode Run配置打开 僵尸对象,僵尸对象替换对象的dealloc方法,如果调用已经dealloc过后的对象抛出异常

iOS常见崩溃以及总结 常见崩溃 非法参数 数组越界 KVO 重复一处观察者,没有实现observeValueForKeyPath: kvc 对象接收到未知的消息 signal 信号量崩溃

捕获crash 捕获oc崩溃:NSSetUncaughtExceptionHandler 。如下

NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);

注册SIGABRT, SIGBUS, SIGSEGV等信号发生时的处理函数,处理Signal层面的crash。 例如如下

signal(SIGHUP, SignalExceptionHandler); signal(SIGINT, SignalExceptionHandler); signal(SIGQUIT, SignalExceptionHandler);

crash保护 hook相关的方法,用trycatch保护,增加保护机制。

调用方法前,判断 respondsToSelector

崩溃分析方法 用xcode查看崩溃 xcode->Window->Organizer->Crashes

在Archive的时候会生成.xcarchive文件,然后显示包内容就能够在里面找到.dsYM文件。用友盟.dsYM分析,选中UUID,输入崩溃地址。

** 路由方案 ** url 路由

target-action方案: 给组件封装一层target对象来对外提供服务,然后调用者通过依赖中间件来使用服务;其中,中间件是通过runtime来调用组件的服务,是真正意义上的解耦,也是该方案最核心的地方。不会对原来组件造成入侵;然后,通过实现中间件的category来提供服务给调用者,这样使用者只需要依赖中间件,而组件则不需要依赖中间件。每个category对应一个Target,Categroy中的方法对应Action场景

protocol-class 就是通过protocol定义服务接口,组件通过实现该接口来提供接口定义的服务,具体实现就是把protocol和class做一个映射,同时在内存中保存一张映射表,使用的时候,就通过protocol找到对应的class来获取需要的服务。

ios的导航设计模式 平铺导航( UITabbarController ) 标签导航( UINavigationController ) 树形导航(UIPageViewController)

属性修饰符 可以用strong和retain修饰同一个属性 但是不能用strong和copy修饰同一属性,运行会报错

viewDidUnload在这里插入代码片 是ios6后舍弃的 vc销毁时回调的生命周期

WKWebview ios和js通信 ios调用js,在ios中:

[self.webView evaluateJavaScript:@"document.title" completionHandler:^(id _Nullable title, NSError * _Nullable error) { NSLog(@"Hello, %@", title); }];

js调用ios 在ios中注册回调

KWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; config.userContentController = [[WKUserContentController alloc] init]; //声明 hello message handler 协议 [config.userContentController addScriptMessageHandler:self name:@"hello"]; //定义 Message Handler 处理方法 - (void)userContentController:(WKUserContentController *)userContentController{}

在js中调用:

window.webkit.messageHandlers.hello.postMessage();

真机调试前的准备

创建登录开发者账号 在本地用钥匙串创建csr文件 进入证书管理页面,创建证书,上传csr文件,生成cer证书,下载这个证书 双击下载证书,安装到电脑的钥匙串 创建appid 添加允许调试的设备的UDID 创建PP文件:选择真机调试配置文件,填入appid,真机调试证书,最后生成并下载mobileprovision文件 双击pp文件,安装到xcode上 xcode中Bundle identifier设置成appid,配置选择pp文件,配置cer证书


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #iOS #面试 #通知 #ios工程师面试 #基础知识 #iOS篇