疫情期间比较火的《阿里、字节:一套高效的iOS面试题
》,以下是自己在2020年3月份做了一遍的结果,和盲点总结,欢迎大家交流.
以下题目有些比较简单的就没有作答.部分题目需要篇幅较长的,在《另外一篇博客》中有纯手写作答.
1,isa为Object中的唯一成员变量,指向对象所属的类
2,isa的类型为isa_t 是后来的runtime出现的,
metaclass是元类,可以理解为根类(object类)的类
3,class中存储的主要内容包含:
isa_t superclass
cache_t cache
class_rw_t *data()
因为类也是对象,动态创建的时候object类(根类)的类就是meatclass
class_copyIvarList
就是class_ro_t
中的ivars
, 成员列表,在编译期确定,不能动态添加,
class_copyPropertyList
是属性列表,对应 class_rw_t
的property_array_t
,它的内容是property_t
结构体,内容为name
和attribute
,对应属性,是成员变量的声明,在编译期前已经存在的属性会在编译期生成以_
为开头的同名成员变量,attribute
中是对成员变量的一些访问权限控制描述
class_rw_t 是runtime 时动态添加方法的操作对象,其中没有ivar相关的内容,在运行时,objc_class中是持有的class_rw_t,class_rw_t又引用的了class_ro_t,其中的方法列表,协议列表,属性列表等都是复制class_ro_t的内容,并且在运行时可以更改,它也是oc作为动态语言的一大特点
class_ro_t 是编译期,编译器对类文件编译产生的,其中包含了类的方法,属性,协议等信息,它是不能被改变的,因为已经确定了大小
class_rw_t
中的信息中,如方法合并到method_list中等,这也是我们一直说类别方法是不是覆盖类中重名方法,而是在方法调用的时候会在method_list的中从后往前找,自然是category的先被找到,category:分类
给类添加新的方法
不能给类添加成员变量
通过@property定义的变量,只能生成对应的getter和setter的方法声明,但是不能实现getter和setter方法,同时也不能生成带下划线的成员属性
是运行期决定的
注意:为什么不能添加属性,原因就是category是运行期决定的,在运行期类的内存布局已经确定,如果添加实例变量会破坏类的内存布局,会产生意想不到的错误。
extension:扩展
可以给类添加成员变量,但是是私有的
可以給类添加方法,但是是私有的
添加的属性和方法是类的一部分,在编译期就决定的。在编译器和头文件的@interface和实现文件里的@implement一起形成了一个完整的类。
伴随着类的产生而产生,也随着类的消失而消失
必须有类的源码才可以给类添加extension,所以对于系统一些类,如nsstring,就无法添加类扩展
不能给NSObject添加Extension,因为在extension中添加的方法或属性必须在源类的文件的.m文件中实现才可以,即:你必须有一个类的源码才能添加一个类的extension。
消息调用流程
load:
当类被装载的时候被调用,只调用一次
调用方式并不是采用runtime的objc_msgSend方式调用的,而是直接采用函数的内存地址直接调用的
多个类的load调用顺序,是依赖于compile sources中的文件顺序决定的,根据文件从上到下的顺序调用
子类和父类同时实现load的方法时,父类的方法先被调用
本类与category的调用顺序是,优先调用本类的(注意:category是在最后被装载的)
多个category,每个load都会被调用(这也是load的调用方式不是采用objc_msgSend的方式调用的),同样按照compile sources中的顺序调用的
load是被动调用的,在类装载时调用的,不需要手动触发调用
initialize:
当类或子类第一次收到消息时被调用(即:静态方法或实例方法第一次被调用,也就是这个类第一次被用到的时候),只调用一次
调用方式是通过runtime的objc_msgSend的方式调用的,此时所有的类都已经装载完毕
子类和父类同时实现initialize,父类的先被调用,然后调用子类的
本类与category同时实现initialize,category会覆盖本类的方法,只调用category的initialize一次(这也说明initialize的调用方式采用objc_msgSend的方式调用的)
initialize是主动调用的,只有当类第一次被用到的时候才会触发
优势: 让语言具有动态性,更加灵活,具有更多让程序员在运行时做动作的可能性.
劣势: 高度的动态话导致安全性降低,多层机制势必降低语言执行效率
关于ARC下,不显示指定属性关键字时,默认关键字:
1.基本数据类型:atomic readwrite assign
2.普通OC对象: atomic readwrite strong
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fFxRpwzu-1593760800175)(https://github.com/imwangxuesen/Blog/blob/master/Private/temp/sidetable.png?raw=true)]
SideTables, SideTable, weak_table, weak_entry_t
全局associatedMap , 每一个对象的disguised_ptr_t结构体为key,对应着一个ObjectAssociationMap, ObjectAssociationMap中存储着本对象所有的key及其对应的关联对象(ObjectAssociation)
关联对象实现原理
1, 关联对象的ObjectAssociation中有两个属性(uintptr_t _policy, id value),
_policy 包含 retain, assgin copy, 会对应的对对象进行和普通对象一样的内存管理操作.
2 ,实现weak,用__weak修饰对象,并将其用block包裹,关联时,关联block对象
-(void)setWeakvalue:(NSObject *)weakvalue {
__weak typeof(weakvalue) weakObj = weakvalue;
typeof(weakvalue) (^block)() = ^(){
return weakObj;
};
objc_setAssociatedObject(self, weakValueKey, block, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSObject *)weakvalue {
id (^block)() = objc_getAssociatedObject(self, weakValueKey);
return block();
}
双向链表,编译后,autoreleasepool是一个全局变量,每一个线程,在runtime启动时都会准备一个autorelasepool,
主要两个方法, push, pop
push就是在page中插入一个哨兵对象,代表这些属于要一起release的对象,
如果page满了,则创建新的page,并合老的page关联起来,对象指针压栈
pop就是从传入哨兵对象往后,所有对象,依次执行release
Objective-C Autorelease Pool 的实现原理
自动释放池的前世今生
理解ARC实现原理
block中的循环引用
NSTimer的循环引用
addObserver的循环引用
delegate的强引用
大次数循环内存爆涨
非OC对象的内存处理(需手动释放)
其他
1,只交换一次
2,获取方法(class_getInstanceMethod/class_getClassMethod)会沿着继承者链向上寻找,所以防止交换时,交换的方法实现时本类的,绝对不能是父类的
Method swizzling的正确姿势
不能,它只管setter和getter方法的原子性,如果两个线程循环做属性的累加操作,依旧不行
实现内省的方法包括:
isKindOfClass:Class
isMemberOfClass:Class
respondToSelector:selector
conformsToProtocol:protocol
实现原理:以上方法的实现原理都是运用runtime的相关函数实现的。
NSNotification原理解析
Observation {
block_t block;
thread_t thread;
id observer;
SEL selector;
}
NotificationCenter中有三个存储属性,
1.1,wildcard:链表,存储没有name没有observer的Observation存储在这里
1.2,named:map 有名字,有observer的存储在这里
{key(name):value(Map{key(observer):value(Observation对象)})}
1.3,nameless: map 没有名字,但是有observer的存储在这里
{key(observer):value(Observation(链表))}
同步
3.1是在一个线程
3.2
让通知的执行方法异步执行即可
通过NSNotificationQueue,将通知添加到队列当中,立即将控制权返回给调用者,在合适的实际发送通知,从而不会阻塞当前的调用
NSPostingStyle为NSPostNow是同步,
NSPostWhenIdle和NSPostASAP:异步发送
NSNotificationQueue将通知添加到队列中时,其中postringStyle参数就是定义通知调用和runloop状态之间关系。
该参数的三个可选参数:
NSPostWhenIdle:runloop空闲的时候回调通知方法
NSPostASAP:runloop在执行timer事件或sources事件的时候回调通知方法
NSPostNow:runloop立即回调通知方法
6.1 发送时使用传入主线程参数
6.2 使用machport在主线程runloop注册,在notification回调中使用machport进行线程间通信,给主线程发送消息(注意在自线程中暂存通知消息,用machport回到主线程后再执行)
iOS9之前不行,因为notificationcenter对观察者的引用是unsafe_unretained,当观察者释放的时候,观察者的指针值并不为nil,出现也指针
iOS9之后可以,因为notificationcenter对观察者的引用是weak
会调用多次observer的action
多次移除没有任何影响
// 发送通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@“TestNotification” object:@1];
// 接收通知
[NSNotificationCenter.defaultCenter postNotificationName:@“TestNotification” object:nil];
答案解析
收不到,因为有名字,有observer,在查找对应的notification的时候是在named表中找,找到的不是同一个.
Runloop & KVO
runloop
runloop对于一个标准的iOS开发来说都不陌生,应该说熟悉runloop是标配,下面就随便列几个典型问题吧
1, 硬件处理相应,封装UIEvent发送到Application(通过注册的machport)
2, 寻找响应者,先发给手势集合,然后仔是从离屏幕最近的开始到离屏幕最远的,从父视图到子视图,通过 hitTest:withEvent
main函数中调用UIApplicationMain,处理UI,为了让程序可以一直运行,所以开启一个常驻线程
1,UIKit不是线程安全的,如果多线程操作UI会产生很多渲染顺序,资源浪费等不可控情况,甚至会引起页面渲染错误导致的用户流失问题.
2,如果把UIKit设计成安全的,也并不是好的,因为UIkit设计成线程安全的,并不能提高渲染的效率,首先,渲染的过程是
core animation 先构建视图布局(layout),重载drawrect进行实时绘制(disply),图像解码和格式转换(prepare), 将layer递归打包发送到render server (commit)
render server收到后,将信息反序列化成渲染树,根据layer属性生成绘制指令,并在下一次VSync信号来到时调用OpenGL进行渲染.
GPU 会等待显示器的VSync信号发出后才进行OpenGL渲染管线,将3D几何数据转换成2D的像素图像和光栅处理,随后进行新的一帧渲染,并将其输出到缓冲区
Display, 从缓冲区中取出画面,并输出到屏幕
这里会出现渲染卡帧的问题,原因是因为在一个VSync周期内,GPU没有完成这一帧的渲染任务,导致display过程在缓冲区中拿不到画面,显示器丢帧
如果线程安全的UIKit在子线程中并发大量的提交到core animation处理页面数据,本质上,知识提升了第一步的速度和效率,但是核心的瓶颈在与GPU的处理速度和VSync的同步问题,所以,这样不但没有提升效率和体验,反而提高了风险和耗损,降低了用户体验
PerformSelector会在线程的runloop中添加一个Timer,到时去发送指定的消息,
如果某一个线程的runloop没有开启,那么performSelector将不会生效,因为Timer依赖runloop,如果runloop没有启动,那么timer并不能执行,进而方法无法调用
1,在线程中加入MachPort
2,在线程中开启where(YES)的循环,优化一点就是加入信号量,模拟runloop eg:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0)
线程内 {
while(YES) {
dispatch_semaphore_wait(semaphore);
if (退出条件满足) {
break;
}
}
}
dispatch_semaphore_signal(semaphore);
同runloop一样,这也是标配的知识点了,同样列出几个典型问题
实现原理
比如有一个教 fullName 的属性,依赖于 firstName 和 lastName,当 firstName 或者 lastName 改变时,这个 fullName 属性需要被通知到。
(NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@“fullName”]) {
NSArray *affectingKeys = @[@“lastName”, @“firstName”];
keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
}
return keyPaths;
}
你也可以通过实现 keyPathsForValuesAffecting 方法来达到前面同样的效果,这里的就是属性名,不过第一个字母要大写,用前面的例子来说就是这样:
(NSSet *)keyPathsForValuesAffectingFullName {
return [NSSet setWithObjects:@“lastName”, @“firstName”, nil];
}
重写 automaticallyNotifiesObserversForKey 方法
会,
kvc的原理就是先去调用setKey方法、_setKey 方法
找不到set方法直接设置属性 _key key isKey内部会间听到值的改变
block是类吗,有哪些类型
一个int变量被 __block 修饰与否的区别?block的变量截获
block在修改NSMutableArray,需不需要添加__block
怎么进行内存管理的
block可以用strong修饰吗
解决循环引用时为什么要用__strong、__weak修饰
block发生copy时机
Block访问对象类型的auto变量时,在ARC和MRC下有什么区别
多线程
主要以GCD为主
线程框架 | 描述 | 语言 | 生命周期管理 |
---|---|---|---|
pthread | 跨平台、Unix/Linux/Windows、使用难度大 | C | 程序员管理 |
NSThread | 面向对象,可操作线程对象,简单易用 | OC | 程序员管理 |
GCD | 简单易用,API使用block,代码集中 | C | 自动管理 |
NSOperation | 基于GCD,多了一些简单实用的功能,比如最大并发数,使其更加面向对象 | OC | 自动管理 |
两大类
serial dispatch queue (串行队列) main dispatch queue
concurrent dispatch queue (并行队列) dispatch_global_queue
GCD有哪些方法api
dispatch_sync
dispatch_async
dispatch_once
dispatch_queue_create
dispatch_semaphore_create
dispatch_semaphore_wait
dispatch_semaphore_signal
dispatch_group_create
dispatch_group_enter
dispatch_group_leave
dispatch_set_target_queue
dispatch_barrier_sync
dispatch_barrier_async
dispatch_after
dispatch_apply
dispatch_source_t
1,穿行队列,并发为1
2,dispatch_group_enter dispatch_group_leave
3,dispatch_semaphore_wait dispatch_semaphore_signal
4,dispatch_barrier
5,dispatch_group_notify
6,dispatch_block_wait
7,dispatch_block_notify
void dispatch_once_f(dispatch_once_t *val, void *ctxt, void (*func)(void *)){
// val == 0
volatile long *vval = val;
// 第一个线程进来
// dispatch_atomic_cmpxchg(p,l,n) 方法的功能是原子操作
// 方法含义为如果p==l 则 将n赋值给p, 并且返回true
// 如果p != l 则返回 false
// 所以第一次进来 val == 0 , val被赋值为1 , 并且返回ture
if (dispatch_atomic_cmpxchg(val, 0l, 1l)) {
func(ctxt); // block真正执行
// dispatch_atomic_barrier 是一个编译器操作,意思为前后指令的顺序不能颠倒.这里是防止编译器优化将原本的指令语义破坏
dispatch_atomic_barrier();
// 将vval赋值为非零
*val = ~0l;
}
else
{
// 如果在第一个线程进来后执行上边代码块的同时,有其他的线程进来执行
// 则进入空循环,等待vval被赋值为非零.
do
{
// 这有助于提高性能和节省CPU耗电,延迟空等
_dispatch_hardware_pause();
} while (*vval != ~0l);
dispatch_atomic_barrier();
}
}
死锁发生的条件
比如:
eg1:主线程调用主线程同步任务
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"发生死锁");
});
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"hhaah");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"dkjfks");
});
});
https://blog.csdn.net/deft_mkjing/article/details/79513500
自旋锁: 线程反复检查所变量是否可用,线程这一过程中保持执行,因此是一种忙等状态,一旦获取了,线程一直保持,直到显示的释放,自旋锁避免了进程上下文切换的开销,因此,对于线程只阻塞很短的场合是有效的.
互斥锁: 避免两个线程同时对某一变量进行读写,通过将代码切片成一个个临界区而达成
信号量:
条件锁:
读写锁
OSSpinLock已经被弃用,因为会出现低优先级线程持有锁,然后高优先级去获取锁的时候,spin lock会进入忙等状态,占用大量cpu时间,这时低优先级队列无法争夺cpu时间片,从而导致任务无法完成,无法释放lock
这个志向了一个静态变量,值为-1,是根据系统情况去分配他的数值.
视图&图像相关
定时器都是依赖runloop运行的,
NSTimer是最见的定时器,注意在使用的时候在滑动的时候不会被触发,需要加入到runloop commonmode 中,在没有runloop运行的子线程,也不会执行
有内存泄漏问题,注意invalied.提供了比较多的初始化方法.比较方便易用
CADisplaylink 是根据屏幕刷新频率处罚的定时器,苹果目前的屏幕刷新频率为60HZ,华为刚出了一个90HZ的以后可能这需要注意下改动,在不同的手机上是不同的频率,他比较准确,在没有什么耗时任务卡住runloop的情况下,很稳定,但是不灵活,适合做UI刷新,添加进runloop即刻启动
dispatch_source_t 是基于gcd 给runloop添加source而实现的定时任务.更加精确,可以设置冗余时间,可以放到子线程
自动布局 Auto Layout (原理篇)
1,UIView是CAlayer的外壳,用来处理响应,包装Layer,继承自UIResponder
2,Layer是实际渲染层,用来处理视图属性,他的代理是UIView,view的frame、bounds等属性实际都是来自layer
3,Layer维护着逻辑树(这里是代码可以操作的),动画树(中间层,系统在此层修改属性,进行渲染相关的操作),显示树(其内容就是现在正被显示在屏幕上的内容)
4,坐标系统:layer比view多了anchorPoint,是各种图形变换的坐标原点,同时会更改layer的position的位置,缺省值为「0.5,0.5」,即layer中央
UIView & CALayer的区别
1,手指点击屏幕,硬件将响应消息转化为UIEvent传入正在活跃的应用时间队列
2,发给手势系统,看看是否可以响应,如果不能,则进入应用
2,第一层是UIWindow响应,调用pointInside:withEvent 检查点是否在该视图内.如果是,通过hitTest:withEvent 调用所有子视图的pointInside:withEvent,和hitTest:withEvent
规则
1,pointInside:withEvent 返回NO,什么都不做
2,pointInside:withEvent 返回YES, 调用对应的hitTest:withEvent,递归检查子视图是否是可以响应事件.
3,pointInside:withEvent 返回YES,并且没有子视图,hitTest:withEvent返回自己,并逐层翻上去.
至此便找到了响应者.
这时响应者链,事件传递链就是一层层翻上去,看看谁可以响应事件.
1,drawrect用来拿到上下文绘制视图内容,在UIView的子类中
2,drawrect调用时机:
2.1:loadview之后
2.2:待用setNeedsDisplay或者setNeedsDisplayInRect:出发drawrect,前提条件为view必须有rect
2.3 该方法在调用sizeThatFits后被调用,所以可以先调用sizeToFit计算出size。然后系统自动调用drawRect:方法。
这里简单说一下sizeToFit和sizeThatFit:
sizeToFit:会计算出最优的 size 而且会改变自己的size
sizeThatFits:会计算出最优的 size 但是不会改变 自己的 size
3, layoutsubviews是用来重新布局,(比如改变子视图位置)默认方法中什么都不干,开发者可以自己重写
4, 调用时机:
4.1, setNeedLayout标记,在下一轮runloop时调用layoutsubviews
4.2, 如果在setNeedLayout后立即调用layoutIfNeed会立即出发layoutsubviews
系统调用时机:
1, init不会触发
2, addsubviews会触发layoutSubviews (前提为frame有值)
3, 改变view的frame会触发
4, 滚动srollview
5, 旋转会触发父视图的layoutsubviews
6, 直接调用layoutsubviews
layoutsubviews早于drawrect调用. 前者布局相关,后者绘制相关
1,我们创建的View,设置视图相关的属性,比如frame,backgroundcolor,textcolor等,背后实际体现在CALayer上
2,Core Animation将layer解析成图层树,每个节点存储着layer相关的frame,bounds,transform等属性,图层数还能表达各个图层之间的关系.
3,CoreAnimation 依赖于OpenGL ES或者Metal做GPU渲染,Core Graphics做CPU渲染.如上图所示,在屏幕上显示视图,CPU和GPU要写作,一部分数据通过CoreGraphics、CoreImage由CPU预处理,最终由OpenGL ES或者metal将数据传送到GPU,最终显示到屏幕
3.1,CoreGraphic基于Quartz高级绘画引擎,主要用于运行时图像绘制,开发者可以用来处理基于路径的绘图,转换,颜色管理,离屏渲染,图案,渐变和阴影,图像数据管理,图像创建和图像遮照以及PDF文档创建、显示和分析
3.2,CoreImage用于运行前创建的图像,大部分情况下CoreImage在GPU中完成工作,如果GPU忙,会使用CPU进行处理.支持两种处理模式
1,CoreAnimation提交会话(包括自己和子树的layout状态等)
2,RenderServer解析提交的子树状态,生成绘制指令
3,CPU执行绘制指令
4,显示渲染后的数据
在CoreAnimation流水线中,App调用Render Server前的最后一步,Commit Transaciton分为四个步骤
_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv
进行打包,并发送至Render ServerOpenGL/Metal的作用是在CPU上生成GPU可以理解的一系列指令,并提交给GPU
图形渲染管线,实际上指的是一堆原始图形数据经过一个输送管道,期间经过各种变化处理最终出现在屏幕的过程,通常情况下,渲染管线可以描述成vertices(顶点)到pixels(像素)的过程
如图:
关于图中详细解析,看链接内
CPU计算显示内容–>GPU渲染–>渲染结果放到帧缓冲区(iOS是双缓冲)–>视频控制器按照VSync信号逐行读取帧缓冲区数据,传递给显示器显示
imageName:
加载后会由系统管理内存,内存占用比较小,对象释放,内存不会,多次加载相同图片不会重复加载.
适合小图片,频繁重复加载的场景
imageWithContentsOfFile:
内存占用小,相同图片会重复加载,重复占用内存,对象释放,对应的内存也会释放.
适合大图片,不频繁重复加载的场景
解码:png,jpeg这种都是压缩格式,解码就是解压缩的过程,
图片解码需要大量计算,耗时长,iOS创建UIImage 或者 CGImageSource的时候并不会立即解码,图片设置到UIImageView或者CALayer.contents中的时候,并且CALayer被提交到GPU前,CGImage中的数据才会解码,并且是在主线程.不可避免.
优化方案:
后台线程把图片会知道CGBitmapContext中,然后从Bitmap直接创建图片,
图片渲染怎么优化
1,下载图片
2,图片处理(裁剪,边框等)
3,写入磁盘
4,从磁盘读取数据到内核缓冲区
5,从内核缓冲区复制到用户空间(内存级别拷贝)
6,解压缩为位图(耗cpu较高)
7,如果位图数据不是字节对齐的,CoreAnimation会copy一份位图数据并进行字节对齐
8,CoreAnimation渲染解压缩过的位图
以上4,5,6,7,8步是在UIImageView的setImage时进行的,所以默认在主线程进行(iOS UI操作必须在主线程执行)。
1,GPU处理完后会放入帧缓冲区,视频控制器定时读取缓冲区内容,给屏幕显示
2,如果缓冲区允许覆盖,那么就会产生丢帧,或者和面断层.不允许覆盖,就会丢帧
可以用双缓冲区,或者加大缓冲区的方案解决
App启动过程
精准计算启动时间
XCode中有debug工具:
对于如何测试启动时间,Xcode 提供了一个很赞的方法,只需要在 Edit scheme -> Run -> Arguments 中将环境变量 DYLD_PRINT_STATISTICS 设为 1,就可以看到 main 之前各个阶段的时间消耗
冷启动,热启动
慢的原因是因为主线程的工作量大,比如IO,大量计算
从启动周期每一步分析,优化
main执行之前
main执行之后
首屏渲染之后
可优化方案:
这个阶段一般是从AppDelegate的applicationDidFinishLaunching的方法到首屏渲染,展示欢迎页这个阶段
一般包含的动作有:
可优化方案:
监控方案:
1,定时任务获取主队列的调用栈,0.002获取一次,记录每次获取的方法内容,对应方法时间累加
2,hook objcMsgSend方法 获取每个方法前后时间,记录时间,需要汇编,难度大
3,使用XCode工具 Timer Profiler 观察方法耗时
卡顿原因:
在一个VSync内GPU和CPU的协作,未能将渲染任务完成放入到帧缓冲区,视频控制器去缓冲区拿数据的时候是空的,所以卡帧
CADisplayLink 监控,结合子线程和信号量,两次事件触发时间间隔超过一个VSync的时长,上报调用栈
在runloop中添加监听,如果kCFRunLoopBeforeSources和kCFRunLoopBeforeWaiting中间的耗时超过VSync的时间,那么就是卡帧了,然后这个时候拿到线程调用栈,看看,那个部分耗时长即可.
使用定时器,每隔一段时间获取一次电量,并上报
+ (float)getBatteryLevel {
[UIDevice currentDevice].batteryMonitoringEnabled = YES;
return [UIDevice currentDevice].batteryLevel;
}
网络优化分为,提速,节流,安全,选择合理网络协议处理针对的业务(比如聊天的,用socket)
为了防止开发者的应用随意安装在手机上,用证书控制,只有经过苹果允许的应用才可以安装上,防止盗版什么的
这么做的目的是为了整个应用生态的体验着想,当然也有安全因素在里边.
AppStore安装app时的认证流程
架构设计
典型源码的学习
只是列出一些iOS比较核心的开源库,这些库包含了很多高质量的思想,源码学习的时候一定要关注每个框架解决的核心问题是什么,还有它们的优缺点,这样才能算真正理解和吸收
AFN3.0解析
AFN2.0解析
SDWebImage
JSPatch、Aspects(虽然一个不可用、另一个不维护,但是这两个库都很精炼巧妙,很适合学习)
Weex/RN, 笔者认为这种前端和客户端紧密联系的库是必须要知道其原理的
CTMediator、其他router库,这些都是常见的路由库,开发中基本上都会用到
请圈友们在评论下面补充吧
架构设计
手动埋点:代码侵入,不好维护,无法线上动态,但是灵活
自动化埋点:全量获取,自定义程度低,灵活性差,效率低.好处是容错高
技术原理:hook关键方法,一类事件的统一入口或者出口,比如统计PV用viewcontroller的viewdidload.统计点击事件用UIControl的sendAction:to:forEvent等
可视化买点:开发代价大,自定义程度低,灵活性差.好处就是的运营人员上手快.对业务发展由好处.
MVC/MVP/MVVM 三种设计模式简介附demo
单例,工厂,MVC、MVP、MVVM、观察者,代理,装饰模式
如果保证项目的稳定性
设计一个图片缓存框架(LRU)
如何设计一个git diff
设计一个线程池?画出你的架构图
你的app架构是什么,有什么优缺点、为什么这么做、怎么改进
其他问题
PerformSelector & NSInvocation优劣对比
oc怎么实现多继承?怎么面向切面(可以参考Aspects深度解析-iOS面向切面编程)
哪些bug会导致崩溃,如何防护崩溃
怎么监控崩溃
app的启动过程(考察LLVM编译过程、静态链接、动态链接、runtime初始化)
沙盒目录的每个文件夹划分的作用
简述下match-o文件结构
系统基础知识
进程和线程的区别
HTTPS的握手过程
什么是中间人攻击?怎么预防
TCP的握手过程?为什么进行三次握手,四次挥手
栈由编译器申请空间大小,是固定的,连续的,对应线程是唯一的,快速高效,缺点是有限制,先入后出不灵活
堆区是通过alloc分配的,它的内存空间是动态的,由一个空闲内存空间指针链表维护,内存空间不连续,可以很大,但是容易内存碎片化.好处就是查询快.
###加密算法:对称加密算法和非对称加密算法区别
对称加密:
非对称加密:
对称加密:
DES 3DES AES
非对称加密:
RSA DSA
MD5 输出128位
SHA1 输出160位
SHA256 输出256位
charles抓包过程?不使用charles,4G网络如何抓包
数据结构与算法
对于移动开发者来说,一般不会遇到非常难的算法,大多以数据结构为主,笔者列出一些必会的算法,当然有时间了可以去LeetCode上刷刷题
八大排序算法
栈&队列
字符串处理
链表
二叉树相关操作
深搜广搜
基本的动态规划题、贪心算法、二分查找
直击2020
神经病院objc runtime入院考试
靠谱的 iOS
Objc的底层并发API
NSURLCache
1, GCD的源码分析
相关文档:
iOS主线程和主队列的区别
多线程-奇怪的GCD
被遗弃的线程
dispatch_async与dispatch_sync区别
GCD容易让人迷惑的几个小问题
深入理解gcd
深入理解GCD之dispatch_queue
Objective-C runtime机制(5)——iOS 内存管理
Https单向认证和双向认证
Flutter安装