iOS核心知识点
针对iOS开发过程中常见的内存管理、多线程、UI渲染、运行时、性能优化等问题进行系统探讨,帮助广大iOS开发者完成从知其然到知其所以然的进阶。 需要参加IOS面试的同学也以此为参考,做到有的放矢。
猴叻鳢
海阔凭鱼跃,天高任鸟飞
展开
-
类的load方法和initialize方法对比
load方法是用户代码能够最早被访问到的方法,并且类只有有load方法,就一定会在应用程序main方法调前被调用;load方法和initialize方法的这种差异, 决定了如果我们要对类的方法进行swizzle操作,比较靠谱的方式是将swizzle相关代码写在load方法中,确保相关逻辑一定会执行。原创 2024-09-08 10:34:45 · 285 阅读 · 0 评论 -
block对变量捕获的方式
block的捕获方式其实并不是对对象调用copy方法,而是进行值copy。对于基础类型和对象类型, 在block中进行赋值操作, 需要添加__block关键字,对于静态局部变量和全局变量, 就无需添加__block关键字。 对于非赋值操作,也无需添加__block关键字。原创 2024-09-07 10:33:00 · 305 阅读 · 0 评论 -
weak的实现原理
对象销毁时,在哈希表中查找到所有指向此对象的 weak 指针,并将其全部置为空 nil,即通过执行*referrer1 = NULL和*referrer2 = NULL,实现将p1和p2置为NULL。iOS 在运行时维护着一个全局的弱引用表,该表是一个 hash 表,hash表的 key 是 weak 对象的地址,value 是指向该对象的所有 weak 指针的地址数组。以上述代码为例,hash表的key为obj, value为[referrer1, referrer2];原创 2024-02-29 18:43:08 · 694 阅读 · 0 评论 -
ARC实现原理及副作用
ARC的原理是在编译时,由编译器在代码中自动插入retain、release、autorelease等代码来管理对象的引用计数。在程序运行时,系统会根据对象的引用情况来自动添加或删除retain、release等代码,从而自动管理对象的内存。(3) 当前类启动了NSTimer,但试图在当前类的dealloc方法中停止timer(如果timer未停掉,当前类由于被timer持有,dealloc方法根本不会被调用)。ARC情况下仍然可能存在内存泄漏。导致内存泄漏的主要原因是循环引用。原创 2024-02-29 19:48:34 · 386 阅读 · 0 评论 -
atomic修饰的属性是线程安全的吗
由于多线程操作会导致线程不安全的操作通常是调用 + [NSMutableArray addObject:]方法, 而atomic标识符并未对调用addObject加锁,因此是线程不安全的。atomic的作用只是在属性setter方法和getter方法中对变量的赋值或访问操作进行了加锁,无法保证其他操作,如添加元素、alloc、dealloc是线程安全的。原创 2024-02-29 17:31:59 · 447 阅读 · 0 评论 -
iOS中的浅拷贝与深拷贝
在iOS中分为浅拷贝和深拷贝。浅拷贝是指针复制,与源对象指向同一内存地址,源对象的引用计数器会+1。深拷贝是内容拷贝,两个对象内容相同,新的对象 retain 为 1 ,与旧有对象的引用计数无关,旧有对象没有变化。copy 会减少对象对上下文的依赖。retain 属性表示两个对象地址相同,内容也相同,这个对象的引用计数器+1。2. 什么时候是深拷贝,什么时候是浅拷贝系统的非容器类对象指的是 NSString,NSNumber 这些不能包含其他对象的对象。在非容器类对象中。原创 2024-02-17 11:44:39 · 502 阅读 · 0 评论 -
iOS中block的底层实现
_block的作用是,记录外部变量的真实地址,使得其可以传入block内部。在block中对__block修饰的对象的操作,等同于直接对对象本身进行访问和修改。4. block包含variables字段, 用来记录引用的外部变量,所有这些变量都会被拷贝,并存放于此。5. block中包含flags字段, 用来记录block的一些附加信息。1. oc中万物皆对象,block也是一个对象,底层用struct来描述。3. block本质是一个函数, 因此包含一个FuncPtr。字段,用来记录函数地址。原创 2024-02-20 16:13:29 · 522 阅读 · 0 评论 -
block与delegate比较
(1)delegate相对于block比较安全,因为 delegate 方法的声明和实现是分离的,不会引用上下文,也就不容易出现循环引用。方法实现和调用可以放到一起,代码变得连贯,使用也简单,一般不需要存储临时数据,能够直接访问上下文。(3)一个delegate可以包含多个通知事件,面向协议编程,代码更容易理解。(2)block效率低,block出栈需要将使用的数据从栈内存拷贝到堆内存。delegate的方法的声明和实现分离,代码的连贯性不好,不易阅读。(1)使用 block 时稍微不注意就形成循环引用,原创 2024-02-27 22:16:40 · 405 阅读 · 0 评论 -
iOS消息发送流程
Objc的方法调用基于消息发送机制。即Objc中的方法调用,在底层实际都是通过调用objc_msgSend方法向对象消息发送消息来实现的。在iOS中, 实例对象的方法主要存储在类的方法列表中,类方法则是主要存储在原类中。 向对象发送消息,核心就是找到对应的方法,然后传入参数执行。 其具体过程是:确定class已经加载 从cache中通过hash查找方法 从类的方法列表中查找 从父类中查找 若方法列表是排序过的,则采用二分法查找;若方法列表未排序,则原创 2024-03-01 10:12:50 · 852 阅读 · 0 评论 -
iOS消息转发流程
当向Objc对象发送消息时,如果找到对象对应的方法,就会进入消息转发流程,给开发者提供一些最后的机会处理消息无法发送问题,以免出现程序崩溃。1. 回调对象的resolveInstanceMethod方法,在这个方法中,允许开发者在运行时为指定对象添加一个方法,然后返回YES。forwardingtargetForSelector方法,该方法允许用户将消息转发到一个可以接收该消息的其他对象。3. 若上一步仍然未能正确处理, 对象的。, 或者未能在重写方法中正确处理,则将会调用对象的。原创 2024-03-01 10:30:37 · 877 阅读 · 0 评论 -
Meta class的作用是什么
RootMetaclass继承于RootClass,而RootClass中的方法都是实例方法,若在RootClass中存在签名相同的方法, 方法是可以正确调用的。再加一个类方法指针。但将实例方法和类方法都存储一个类中,在消息传递时又需要对两种类型的消息进行不同的处理,可能会导致传递过程的复杂化。有一个比较有意思的点需要注意,我们调用类方法时,有可能消息会被转发到根类的实例方法。在往上根元类(root metaclass)指向自己,形成了一个闭环,一个完备的设计。形成了一个闭环,形成了一个完备的设计。原创 2024-03-01 10:50:06 · 450 阅读 · 0 评论 -
KVO实现原理
当某个类的属性对象第一次被观察时,系统就会在运行时动态地创立该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter方法。派生类在被重写的setter方法内实现真正的通知机制。当一个某个属性第一次被观察,那么系统会偷偷将其isa指针从原来的类,改为指向动态生成的派生类,从而使得属性被赋值时执行的是派生类的setter方法。因为kvo是重写的setter方法,成员变量赋值只是修改指针的引用,并不会调用setter方法。在派生类重写的settter方法中,通过给成员变量直接赋值,而不是通过属性。原创 2024-02-20 18:11:50 · 373 阅读 · 0 评论 -
GCD 串行队列、并发队列以及同步执行、异步执行
dispatch_async 如果调度到同一个串行队列上,第一次异步调度时会开启一个新线程,后续异步调度会在第一次创建的线程上运行,但肯定会顺序执行。dispatch_sync 任务将在当前线程执行,若同步调度任务到并发队列上,则所有任务在当前线程顺序执行。并行队列:调度任务到并行队列, 将开启多个线程,任务执行结束的顺序无法确定。dispatch_async 如果调度到同一个并行队列上, 将开多个新线程执行,任务乱序执行。串行队列: 调度任务到串行队列,任务将在同一个线程执行,并且顺序执行。原创 2024-02-19 16:14:20 · 537 阅读 · 0 评论 -
dispath_group_async与dispatch_barrier_async对比
dispatch_barrier用来在并行队列中的部分任务进行阻塞,确保该任务不会与其他任务并行,进而在实现线程安全的同时,达到更高的效率。读的时候由于需要立刻返回结果, 因此应该用同步调度。这样在不同线程中调用读方法,就可以实现多线程并发。dispath_group用来实现依赖, 等多个任务完成后, 在执行下一步。写的时候由于有栅栏的保护, 在开始执行写任务时,并发对列中的其他任务都不会执行。原创 2024-02-19 09:21:47 · 438 阅读 · 0 评论 -
NSOperation Queue相对于GCD的优势
NSOperation Queue中任务开始顺序(即main或start方法执行顺序)是顺序执行的,但由于任务在不同线程中执行, 结束顺序无法确定。NSOperationQueue本质上是对GCD的封装,但提供了更强的任务依赖关系管理能力,允许更加灵活的定义任务间的执行顺序。系统默认通过KVO监控对应属性的值的变化,在状态为Finished后,对相关内存进行回收。若重写了start方法,则需要自定义任务状态。(gcd也可通过dispatch_group设置依赖,但没有移除依赖的方法)。原创 2024-02-17 10:46:49 · 476 阅读 · 0 评论 -
Runloop的概念及典型应用
我们触摸屏幕,先摸到硬件(屏幕),屏幕表面的事件会被IOKit先包装成Event,通过mach_Port传给正在活跃的APP , Event先告诉source1(mach_port),source1唤醒RunLoop, 然后将事件Event分发给source0,然后由source0来处理。runloop只能同时运行在一个mode上,如果此时收到另外一个mode上的事件,是无法处理的。互相切换,进而实现无消息时休息,有消息时立刻唤醒,进而在及时响应任务的同时,最大限度降低资源开销。原创 2024-02-19 17:25:44 · 1218 阅读 · 1 评论 -
将代码同步到主线程的三种方法
【代码】将代码同步到主线程的三种方法。原创 2024-02-19 17:04:15 · 419 阅读 · 1 评论 -
UIView的layoutSubview方法什么时候会被调用
大部分iOS设备的vSync信号产生的周期是16ms一次,这是layoutSubView会被异步调用的底层原因。很多人习惯重写UIView的layoutSubview方法,在里面写View的布局代码,期望视图在数据发生变化,或系统主题改变时,页面的布局能够执行同样的代码逻辑,保证最终展示结果符合预期。layoutSubView方法在当前消息循环中被立刻调用,对layoutSubView方法的调用是会延迟到下一个或更靠后的消息循环中去。2. 调用UIView的addSubview方法。原创 2024-02-28 10:58:45 · 383 阅读 · 0 评论 -
iOS中图片上屏过程
在回答一张图片在iOS中上屏的过程这一问题之前, 首先了解下iOS的UI渲染的主要过程。Display 阶段主要进行视图绘制,生成的位图存放在将CALayer包含 contents 属性指向称为 backing store的一块缓存区。Commit 阶段主要将图层进行打包,并将它们发送至 Render Server。Layout 阶段主要进行视图构建,包括:LayoutSubviews 方法的重载,addSubview: 方法填充子视图等。Prepare 阶段属于附加步骤,一般处理图像的解码和转换等操作。原创 2024-02-28 11:14:48 · 476 阅读 · 0 评论 -
iOS中卡顿产生的主要原因及优化思路
卡顿本质上是一个UI上的体验问题,而UI的渲染及显示,主要涉及CPU和GPU两个层面。若CPU+GPU渲染耗时超过16.7ms,就会在屏幕vsync信号到来时无法更新屏幕内容,进而导致卡顿。iOS中UI渲染主要包含Layout->Display->Prepare->Commit四个阶段。其中前三个阶段主要是CPU在处理, 第四个阶段主要是GPU处理。可行的优化手段,在不同阶段也会有所不同。原创 2024-02-28 12:32:37 · 2755 阅读 · 0 评论 -
load方法和initialize方法对比
2. 如果类和类Category都实现了initialize方法,调用Category的initialize方法,会覆盖类中的方法,只执行一个,如果多个category,则调用编译顺序最后的initialize方法。4. 如果子类没有实现initialize方法,父类实现了initialize方法,调用子类的时候,会先调用父类的initialize方法,再调用子类的实例方法。2. load的调用顺序为:父类load ---> 子类load ---> Category的load。原创 2024-02-21 10:03:02 · 500 阅读 · 1 评论 -
iOS中符号Rebase和Bind相关概念
由于镜像会在随机的地址上加载,和之前指针指向的地址(preferred_address)会有一个偏差(slide),dyld需要修正这个偏差,来指向正确的地址。在Debug场景下, 符号以dwarf格式存在于.o文件中;而在Release场景下,App中的符号可以被完全裁剪,符号信息存储在dsym文件。Bind:是设置镜像依赖的外部符号的指针,主要。镜像读入内存过程中完成,性能消耗主要是文件IO。Rebase在前,Bind在后。性能消耗主要在CPU计算。是工作是查询全局符号表,原创 2024-02-23 13:14:58 · 645 阅读 · 0 评论 -
iOS安装包Mach-O文件格式
分为__TEXT和__DATA两种类型以及其他补充字段如符号表、字符串表等等。__TEXT中主要是代码信息,比如__objc_classname即某个类的信息,__DATA中主要是常量、全局变量等信息。1. header:主要包含mach-o的类型(静态库, 动态库, exe)、体系结构、加载命令的个数和长度等信息。有很多个命令,分别用于加载动态库、加载数据段等。命令中包及要加载的段的名称、基地址、地址偏移量,大小等信息。原创 2024-02-23 13:27:16 · 387 阅读 · 0 评论 -
iOS中的懒加载类与非懒加载类
比如一个项目中有10000个类,没有必要在启动的时候就全部加载出来,因为这个时候根本不需要用到它。非懒加载符号会在dyld加载时就在符号表中绑定真实的符号地址,绑定后的符号地址存储在nl_symbol_ptr。这主要是因为load方法会在App启动时被调用,而要调用其load方法,显然需要先对类进行加载。后续再调用对应符号时, 则会直接访问修正后的真实的函数地址。懒加载的符号第一次被访问时,会先跳转到__stub_helper。懒加载的符号地址存储在__la_symbol_ptr。, 然后在该位置调用。原创 2024-02-23 13:20:02 · 505 阅读 · 0 评论 -
iOS App冷启动优化:Before Main阶段
8.Initalizers:执行OC类的load方法,以及执行C++类的构造方法,C++初始化全局、静态对象。initialize方法,当前类的initialize会被覆盖,即可能不会执行,进而可以导致意料之外的严重问题。5. Rebase:根据动态库加载的随机地址+符号偏移,为镜像中的符号生成真实的指针地址。7. Objc: 即注册OC类、协议, 以及将category中的方法添加到方法列表。4. Dyld加载其他动态库,分析动态库的依赖,并递归加载其他动态库。2. ObjC类,方法越多,启动越慢。原创 2024-02-25 12:02:00 · 817 阅读 · 0 评论 -
iOS App冷启动优化:二进制重排
如果将BeforeMain阶段用到的方法都找出来出来,让他们在同一或相近的内存页中顺序提前加载,就可以大大减少应用启动时缺页异常出现的概率。二进制文件中方法的加载顺序, 取决于方法在代码文件中的书写顺序,而不是调用顺序。应用程序启动时会调用到的方法是有限的,但可能分散在很多个。这就导致很多完全还用不到的方法,会在应用启动时就会被加载到内存,这需要开辟大量内存页,进而增加大量启动耗时。枚举出应用启动时会调用到的方法,将其保存到.order文件中。由于内存是分页管理的,要加载就要。设置应用在链接时, 根据。原创 2024-02-26 10:25:23 · 787 阅读 · 1 评论 -
iOS中几种hook代码的方法
iOS中Object-C代码的主要hook方式。其原理是利用OC的Runtime特性,动态改变SEL(方法编号)和IMP(方法实现)的对应关系,达到OC方法调用流程改变的目的。比如, 我们可以通过hook字典NSMutableDictionary的 setObject:forKey和removeObjectForKey:方法,帮助其实现空安全。2.dobby是一个开源的第三方框架,主要用于C、C++等静态代码的hook。其原理是,直接修改目标函数的头部代码(修改的是内存中MachO的代码段。原创 2024-02-22 16:49:26 · 1456 阅读 · 1 评论 -
iOS WebKit中的多进程
KVO是基于runtime机制实现的。当某个类的属性对象第一次被观察时,系统就会在运行期动态地创立该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter方法。派生类在被重写的setter方法内实现真正的通知机制。当某个属性第一次被观察,那么系统会偷偷将其isa指针从原来的类,改为指向动态生成的派生类,从而使得属性被赋值时执行的是派生类的setter方法。因为kvo是重写的setter方法,成员变量赋值只是修改指针的引用,并不会调用setter方法。原创 2024-02-27 21:26:09 · 605 阅读 · 0 评论