iOS知识点汇总

UI试图

UITableView相关

1.cell的复用
2.数据源同步:

  • 并发访问、数据拷贝(缺点:拷贝数据产生内存开销)


    image.png
  • 串行访问 (缺点:子线程操作耗时时,删除会有延迟)


    image.png
事件传递&视图响应

UIView提供内容,以及负责处理触摸等事件,参与响应链
CALayer负责显示内容的contents

  • 事件传递:
    主要方法
    -(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
    -(BOOL)pointInside(CGPoint)point withEvent:(UIEvent *)event
    流程


    image.png
  • 响应链:
    主要方法
    -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
    -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
    流程


    image.png
图像显示原理

CPU和GPU是通过总线连接起来的,CPU输出的是位图,再经由总线在合适的时机上传给GPU,GPU拿到位图后会做图层的渲染、纹理的合成,之后把结果放到帧缓冲区中,由视频控制器根据VSync信号在指定时间之前去提取帧缓冲中的屏幕显示内容,最终显示到屏幕上。


image.png

image.png
  • CPU工作
    Layout UI布局、文本计算
    Display 绘制
    Prepare 图片编解码
    Commit 提交位图
  • GPU渲染管线
    顶点着色
    图元装配
    光栅化
    片段着色
    片段处理
UI卡顿、掉帧

原因:1s完成60帧的刷新,在视觉上是流畅的,也就是每个VSync之间有16.7ms,在这16.7ms之间,要CPU和GPU共同完成下一帧画面的合成,不能完成则会导致卡顿(掉帧)


image.png

界面滑动优化方案:

  • CPU
    对象的创建、调整、销毁放到子线程中操作
    预排版(布局计算、文本计算)
    预渲染(文本等异步绘制,图片编解码等)
  • GPU
    纹理渲染(给layer设置圆角、阴影等会触发离屏渲染)
    视图混合(减少层级)
UI绘制原理/异步绘制

调用UIView的setNeedsDisplay方法,并不会立刻发生当前视图的绘制工作,而是先调用layer的setNeedDisplay方法,相当于在当前layer上打上一个脏标记,然后在当前RunLoop将要结束前调用CALayer的display方法,去绘制试图


绘制原理.png

异步绘制:
layer.delegate实现了displayLayer:方法,进入异步绘制流程中

  • 代理负责生成对应的bitmap
  • 设置该bitmap作为layer.contents属性的值


    异步绘制.png
离屏渲染

在屏渲染:GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行的
离屏渲染:GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。当我们设置UI视图的图层属性时(圆角,遮罩,阴影),如果指定为在未预合之前(无法把渲染结果直接写入帧缓存区,而是暂存在另外的内存区域之后再写入frame buffer)不能用于直接显示的时候,就会触发离屏渲染。
为何要避免:会创建新的渲染缓冲区,GPU会产生额外的开销,很有可能导致CPU和GPU绘制一帧的时间超过16.7ms,会造成卡顿和掉帧

OC语言

分类Category
  • 特点:
    1.运行时决议(运行时通过Runtime把分类中添加的内容添加到原类中)
    2.可以为系统类添加分类

  • 可以添加哪些内容
    实例方法、类方法、协议、属性

  • 结构体


    分类结构体.png
  • 程序启动加载调用栈(image为镜像)
    _objc_init(runtime初始化)
    map_2_images(程序镜像的处理)
    map_images_nolock
    _read_images(加载可执行文件到内存中)
    remethodizeClass(分类加载内容在此)

  • 原理
    倒叙遍历原类的所有分类(最先访问最后编译的分类),把实例方法、类方法、协议、属性放入不同的二维数组中,获取原类当中的rw数据,其中包含原类的方法列表信息,根据拼接之后的元素总数重新分配内存,通过memmove把原类方法进行内存移动,移动到最后面,memcopy把分类中的方法copy到原类的内存中。所以原类的方法还存在,只是调用顺序问题。

关联对象

可以为分类添加实例变量
objc_getAssociatedObject(id object, const void *key)
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
objc_removeAssociatedObjects(id object)

  • 本质
    关联对象由AssociationsManager管理并在AssociationsHashMap中存储。
    所有对象的关联内容都在同一个全局容器中。
    @selector(text)-key COPY/RETAIN-Policy


    image.png
扩展

扩展Extension

  • 格式
@interface XXX ()
//私有属性
//私有方法(如果不实现,编译时会报警,Method definition for 'XXX' not found)
@end
  • 使用
    声明私有属性
    声明私有方法
    声明私有成员变量
  • 特点
    编译时决议
    只以声明的形式存在,多数情况下寄生于宿主类的.m中
    不能为系统类添加扩展
代理

是一种软件设计模式
传递方式是一对一的

NSNotification
  • 特点
    是使用观察者设计模式来实现的用于跨层传递消息的机制
    传递方式是一对多的
  • 如何实现通知机制
    NSNotificationCenter内部会维护一个Notification_Map表, notificationName为key,Observers_List为value


    image.png
KVO
  • KVO:key-value observing
    是OC对观察者设计模式的又一实现
    Apple使用了isa混写(isa-swizzling)来实现KVO
  • isa-swizzling
    A在运行时会动态生成一个NSKVONotifying_A类(A的子类),然后将A类的实例对象的isa指针指向NSKVONotifying_A类,
    观察A的name属性:
    1、重写set方法,并在设置操作的前后分别调用 willChangeValueForKey: 和 didChangeValueForKey方法,这两个方法用于通知系统该 key 的属性值即将和已经变更了
    2、重写class方法,返回原来的A类对象
    3、重写dealloc方法,再合适的时候销毁这个运行时创建的类


    image.png
  • 通过kvc设置name kvo能否生效
    可以。和kvc的实现有关
  • 通过成员变量直接赋值name kvo是否生效
    不能,直接赋值不会走set方法,可以在赋值前后加上willChangeValueForKey: 和 didChangeValueForKey
KVC

如果没有找到Set方法的话,会先根据accessInstanceVariablesDirectly返回实例变量是否存在,YES会按照_key,_iskey,key,iskey的顺序搜索成员并进行赋值操作,都没有会调用valueForUndefinedKey:

属性关键字
  • 读写权限
    readonly
    readwrite(系统默认)
  • 原子性
    1.atomic(系统默认):可以保证赋值和获取是线程安全的,并不代表操作(如:数组的增加和移除)和访问
    2.nonatomic
  • 引用计数
    retain(MAC)/strong(ARC)
    assign(基本数据类型、对象类型)/unsafe_assign(MRC中使用频繁)
    weak/copy
    1、assign和weak的区别:
    assign:修饰基本数据类型;修饰对象时,不改变其引用计数;会产生野指针。
    weak:不改变被修饰对象的引用计数;所指对象在被释放后会自动置为nil。
    2、copy和mutableCopy/深拷贝和浅拷贝

Runtime

对象、类对象、元类对象

对象,指向类对象
类对象,指向元类对象

  • 整体数据结构


    数据结构.png

    image.png
  • 类对象(objc_class类型数据结构)
    存储实例方法列表等信息

  • 元类对象(objc_class类型数据结构)
    存储类方法列表等信息,元类对象的isa指针都指向根元类,根元类对象的isa指针也指向根元类。根元类对象的superClass指针指向根类对象

  • 如果类方法没有实现,在根类对象里面有同名实例方法,才能调用

消息传递机制
消息传递.png
  • 缓存查找
    根据给定的方法选择器SEL,找到对应的方法实现(通过哈希查找找到对应的bucket_t)


    image.png
  • 当前类中查找
    对于已排序好的方法列表。采用二分查找算法查找方法对应执行函数
    对于没有排序的方法列表,采用一般遍历查找
  • 父类逐级查找


    image.png
消息转发流程
  • resolveInstanceMethod:/resolveClassMethod:
  • forwadingTargetForSelector:
  • methodSignatureForSelector:
    forwardInvocation:
Method-Swizzling
  • method_exchangeImplementations()
动态添加方法
class_addMethod(Class cls, SEL name, IMP imp, const char * types)

内存管理

内存布局

stack:方法调用
heap:通过alloc等分配的对象

  • 内存管理方案
    1、TaggedPointer (一些小对象,如NSNumber)
    2、NONPOINTER_ISA (64位架构下的ios应用程序。在64位架构下,isa指针占64个比特位,但只需要32位就够了,为了提高内存利用率,在剩余的比特位中存储了一些关于内存管理的数据内容)
    3、散列表 (SideTables,引用计数、弱引用表)


    SideTable结构
ARC

自动引用计数管理内存
ARC是编译器(自动插入retain、release)和Runtime共同协作
ARC中禁止手动调用retain/release/retainCount/dealloc(ARC下可以重写dealloc方法,但是不能显示调用super dealloc)
新增weak、strong属性关键字

MRC

手动引用计数(斜体方法在ARC中调用会编译报错)
alloc:分配对象的内存空间
retain:引用计数+1
release:引用计数-1
retainCount:当前对象的引用计数值
autorelease:autoreleasePool结束时调用release
dealloc:显示调用[super dealloc]

引用计数机制
  • alloc实现
    经过一系列调用,最终调用C函数的calloc
    此时并没有设置引用计数为1。(调用retainCount返回1,和retainCount的实现有关)
  • retain实现
//通过哈希查找对象引用计数相关的sidetable,从sidetables中找到sidetable
SideTable& table = SidTables()[this];
//获取引用计数map,通过哈希查找从中获取引用计数值
size_t& refcntStorage = table.refcnts[this];
//引用计数+1,实际是加偏移量4,反应出的结果是+1
refcntStorage += SIDE_TABLE_RC_ONE;
  • release实现
SideTable& table = SidTables()[this];
RefcountMap::iterator it = table.refcnts.find(this);
//引用计数-1
it->sencond -= SIDE_TABLE_RC_ONE;
  • retainCount
SideTable& table = SidTables()[this];
size_t refcnt_result = 1;
RefcountMap::iterator it = table.refcnts.find(this);
//alloc没有给对象进行+1,由于refcnt_result为1,所以调用retainCount为1
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
  • dealloc


    image.png
弱引用表
  • 添加weak变量
    在weak_register_no_lock()函数中进行弱引用变量的添加,通过哈希算法进行位置查找,如果查找到的位置中已经有对应的弱引用数组,把弱引用指针添加到数组中,如果没有会创建弱引用数组,第0个位置上添加最新的weak指针,后面都初始化为nil
objc_initWeak() -> storeWeak() -> weak_register_no_lock()
  • 清除weak变量,同时设置指向nil
    weak_clear_no_lock()根据当前对象指针查找弱引用表,遍历当前对象的弱引用数组,把弱引用指针置为nil
dealloc() -> clearDeallocating() -> weak_clear_no_lock()
AutoReleasePool

编译器会将@autorealsepool{}改为

void *ctx = objc_autoreleasePoolPush(); (-> AutoreleasePoolPage::push)
{}代码块
//一次pop相当于一次批量的pop操作
objc_autoreleasePoolPop(ctx);(-> AutoreleasePoolPage::pop)
  • 结构
    是以栈为结点,通过双向链表的形式组合而成的数据结构(双向链表:每个结点都有两个指针,ParentPtr、ChildPtr)
    和线程是一一对应的
  • AutoreleasePoolPage
    id* next;
    AutoreleasePoolPage* const parent;
    AutoreleasePoolPage* child;
    pthread_t const thread;
  • 自动释放池
    在当次runloop将要结束的时候调用AutoreleasePoolPage::pop()
    多层嵌套就是多次插入哨兵对象
    在for循环中alloc图片数据等内存消耗较大的场景手动插入autoreleasePool(每次循环都会完成一次内存的释放)
循环引用

自循环引用
相互循环引用
多循环引用

  • 破除循环引用
    避免产生循环应用;在合适的时机手动断环
    (__weak;
    __block—在MRC下,__block修饰对象不会增加其引用计数,避免了循环引用;在ARC下,__block修饰对象会被强引用,无法避免循环引用,需手动解环;
    __unsafe_unretained—和__weak等效,但是被修饰的对象被释放后会产生野指针问题)
  • NSTimer循环引用问题
    调用invalidate,从runloop中移除,设为nil 释放对target对象的强引用
    不直接持有,引入中间对象持有两个弱引用,NSTimer和原对象

Block

Block本质-对象

Block是将函数及其执行上下文封装起来的对象

截获变量特性
  • 局部变量
    基本数据类型:截获其值
    对象类型:连同所有权修饰符一起截获(和循环引用有关联)
  • 静态局部变量:指针形式
  • 全局变量:不截获
  • 静态全局变量:不截获
__block修饰符的本质

一般情况下,对被截获变量进行赋值操作需要添加__block修饰符(赋值 != 操作)
__block修饰的变量变成了对象

__block int xxx;
struct _Block_byref_XXX_0 {
    void *isa;
    //栈上的__forwarding指针指向自身
    //栈上的block进行copy操作后,栈上的__forwarding指向堆上的block,堆上的__forwarding     指向自身
    _Block_byref_XXX_0 *__forwarding;  
    int __flags;
    int __size;
    int XXX;
}
Block的内存管理

GlobalBlock(放在已初始化数据区中)
StackBlock(被销毁时,__block修饰的对象和block都会被销毁)
MallocBlock(堆上的block)

  • Block的copy操作


    image.png
循环引用

__weak修饰

多线程

GCD
  • 同步/异步 和 串行/并发
    死锁:队列引起的循环等待
  • dispatch_barrier_async
    解决多读单写的问题
  • dispatch_group
NSOperation/NSOperationQueue
  • 添加/移除任务依赖
  • 任务执行状态的控制
    isReady
    isExecuting
    isFinished(是通过KVO机制实现的)
    isCancelled
    如果只重写了main方法,底层控制变更任务执行完成状态,以及任务退出
    如果重写了start方法,自行控制任务状态
  • 最大并发量
NSThread
  • 启动流程
start() -> 创建pthread -> main() -> [target performSelector:selector] -> exit()
  • 结合RunLoop实现常驻线程
互斥锁、自旋锁、递归锁等
  • @synchronized
    一般在创建单例对象的时候使用
  • atomic
    修饰属性的关键字;
    对被修饰对象进行原子操作(不负责使用,对mutableArr.addObject操作不能保证线程安全)
  • OSSpinLock
    自旋锁;循环等待询问,并不释放当前资源
    用于轻量级数据访问,简单的int值+1/-1操作
  • NSLock
    获取到锁,再获取会导致死锁(可使用递归锁进行解决)


    image.png
  • NSRecursiveLock
    递归锁;可以重入


    image.png
  • dispatch_semaphore_t
    信号量
  • dispatch_semaphore_create(0);
struct semaphore {
  int value;
  List <thread>;
}
  • dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
{
  S.value = S.value - 1;
  //阻塞是一个主动行为
  if S.value < 0 then Block(S.List)
}
  • dispatch_semaphore_signal(semaphore); //发送信号量
{
  S.value = S.value + 1;
  //唤醒是一个被动行为
  if S.value >= 0 then wakeup(S.List);
}

RunLoop

什么是RunLoop

RunLoop是通过内部维护的事件循环来对事件/消息进行管理的一个对象
没有消息处理时,休眠以避免资源占用(用户态,通过系统调用进入内核态)
有消息处理时,立刻被唤醒(内核态 -> 用户态)
不是个简单的while循环,主要是发生了状态切换

数据结构

NSRunLoop是对CFRunLoop的封装,提供了面向对象的API

  • CFRunLoop
struct __CFRunLoop {    
    pthread_t _pthread;  //对应的线程  RunLoop和线程一一对应
    CFMutableSetRef _commonModes; //多个commonModes <String>
    CFMutableSetRef _commonModeItems;   //多个Observer,多个Timer,多个Source
    CFRunLoopModeRef _currentMode;  //当前运行的mode
    CFMutableSetRef _modes; //多个modes <CFRunLoopMode>
};
  • CFRunLoopMode
struct __CFRunLoopMode {
    CFMutableSetRef _sources0;  //需要手动唤醒当前线程
    CFMutableSetRef _sources1;  //具备唤醒线程的能力
    CFMutableArrayRef _observers;   //
    CFMutableArrayRef _timers;   //
};
  • Source/Timer/Observer
    CFRunLoopObserverRef监听runloop的状态
各数据结构间的关系
事件循环机制

有事做事,没事做时休息


image.png
RunLoop和NSTimer

用户滑动ScrollView时,RunLoop会转到UITrackingRunLoopMode,Mode发生切换,timer不会再生效了

  • 解决方案
    通过CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFRunLoopMode mode )方法将timer添加到NSRunLoopCommonModes中
RunLoop与线程之间的关系

线程与RunLoop是一一对应的
自己创建的线程默认是没有RunLoop的

常驻线程

1、为当前线程开启一个RunLoop
2、向该RunLoop中添加一个Port/Source等维持RunLoop的时间循环
3、启动该RunLoop

保证子线程数据回来更新UI的时候不打断用户的滑动操作

可以把更新UI放到DefaultMode下,这样不会在TrackingMode下更新UI

网络

HTTP协议

超文本传输协议

  • 请求/响应报文
  • 连接建立流程
  • HTTP特点
HTTPS与网络安全
TCP/UDP
DNS解析

域名到IP地址的映射,DNS解析请求采用UDP数据报,且明文

  • 递归查询
    我去给你问一下


    image.png
  • 迭代查询
    我告诉你谁可能知道


    image.png
  • DNS解析中常见的问题
    1、DNS劫持问题


    image.png

    2、DNS解析转发问题

  • DNS劫持怎么解决
    1、httpDNS:
    使用DNS协议向DNS服务器的53端口进行请求(也就是使用HTTP协议向服务器的80端口进行请求,通过ip像服务端进行请求,返回给域名的ip地址)
    2、长连接


    image.png
Session/Cookie

对HTTP协议无状态特点的补偿
Cookie:保存在客户端,用来记录用户状态

设计模式

六大设计原则
  • 单一职责原则
    一个类只负责一件事(UIView和CALayer)
  • 依赖倒置原则
    抽象不应该依赖具体实现,具体实现可以依赖于抽象(上层业务只依赖于提供的接口,并不关心内部实现)
  • 开闭原则
    对修改关闭,对扩展开放
  • 里氏替换原则
    父类可以被子类无缝替换,且原有功能不受影响(KVO)
  • 接口隔离原则
    使用多个专门的协议,而不是一个庞大臃肿的协议(UITableViewDelegate和UITableViewDataSource)
    协议中的方法应该尽量少
  • 迪米特法则
    一个对象对其他对象有尽可能少的了解,高内聚低耦合
责任链

实际需求:对于业务A -> 业务B -> 业务C 变更为 业务C -> 业务B -> 业务A
业务C的nextBusinessObject为业务B,业务B的nextBusinessObject为业务C


类构成
桥接
适配器
单例
命令

架构/框架

模块化
分层
解耦
降低代码重合度

图片缓存框架
image.png
  • 图片读写
    以图片URL的单向Hash值作为key
    内存方面淘汰策略:
    1、队列先进先出
    2、LRU算法,如30分钟内未使用过(触发时机每次进行图片查找时,后台切换到前台时)
    磁盘设计:
    1、存储方式
    2、大小限制
    3、淘汰策略(7天以上的删除)
    网络设计:
    1、图片请求的最大并发量
    2、请求的超时策略(超时重试)
    3、请求优先级
    图片解码:
    1、应用策略模式对不同图片格式进行解码
阅读时长统计框架
image.png
复杂页面架构
  • MVVM框架思想

  • ReactNative的数据流思想
    任何一个子节点是没有权利做自己的变化更新的(打一个标记),必须把自己变化更新的消息传递给根结点,由根结点自顶向下的方式询问哪些结点需要更新


    RN数据流思想
  • 系统UIView更新机制的思想

  • FaceBook的开源框架AsyncDisplayKit关于预排班的设计思想

客户端整体架构
image.png
  • 业务之间的解耦通信方式
    OpenURL
    依赖注入(通过中间层解耦)

算法

字符串反转
链表反转
有序数组合并
Hash算法
查找两个子视图的共同父试图
求无序数组当中的中位数

三方库

AFNetworking
框架图
SDWebImageView
架构简图
ReactiveCocoa
  • 信号
    核心类RACSignal
  • 订阅
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容