1. 如何理解RunLoop
Runloop(运行循环)是iOS和macOS中的一个焦点概念,它负责管理事件和计时器,以确保应用步调可以或许在精确的时间相应用户的输入,并在不占用过多资源的情况下保持活动状态。
在一个iOS或macOS应用步调中,有一个主线程,所有的用户界面更新、网络哀求、定时器、事件处置处罚等都是在该线程中执行的。而 Runloop 就是这个线程中的一个对象,它不断地监听体系事件,如触摸事件、定时器事件等,一旦有事件发生,就会关照相应的处置处罚方法来处置处罚该事件。
Runloop 监听的事件重要分为两种:输入源(Input Source)和定时源(Timer Source)。输入源包括触摸事件、键盘事件、鼠标事件等,定时源则是基于时间的触发器,如 NSTimer、CADisplayLink 等。当事件发生时,Runloop 会将事件分发给相应的处置处罚方法,并在事件处置处罚完成后继续监听事件。
Runloop 的好处在于它可以让应用步调在等待事件时保持活动状态,同时也可以或许在没有事件时降低资源占用。因此,Runloop 是 iOS 和 macOS 应用步调可以或许保持流通和相应的关键之一。
2. 如何理解RunTime
Runtime(运行时)是指步调在运行期间的行为,即代码被编译成二进制文件之后,在运行时期间的行为。在编译时,我们可以推测代码的行为,但是在运行时,步调的行为每每会有一些意外情况,比如动态创建对象、动态绑定方法等。
在Objective-C中,Runtime是一个重要的概念,它为Objective-C提供了一些高级特性,如消息传递、动态绑定和反射等。Objective-C的对象都是在运行时创建的,它们的属性和方法也是在运行时添加的。因此,Objective-C步调的运行时环境黑白常动态的。
使用Runtime可以做很多有用的事情,例如:
- 动态创建类、对象和方法;
- 拦截方法调用并修改方法实现;
- 动态交换方法的实现;
- 实现 KVO(键值观察)功能;
- 实现消息转发;
总的来说,Runtime是一种强大的工具,它可以帮助我们更好地理解Objective-C的内部机制,并为我们提供了一些有用的功能,让我们可以或许更加灵活地编写高效的代码。
3. KVO与KVC有什么接洽
KVC(键值编码)和KVO(键值观察)是Objective-C中的两个重要机制,它们之间有一定的接洽,同时也有一定的区别。
- KVC是一种通过字符串访问对象属性的机制,它允许我们使用字符串来获取和设置对象的属性,而不必要显式地调用对象的getter和setter方法。KVC的焦点是通过key来访问对象的属性,即通过字符串访问对象的属性。
- KVO则是一种观察者模式的实现,它允许一个对象监听另一个对象的某个属性的变化,并在该属性发生变化时自动得到关照。在iOS开辟中,我们通常使用KVO来实现对于UI控件的相应、数据模子的观察等场景。
KVC和KVO之间的接洽在于,KVO的实现必要使用到KVC。当我们对一个对象的属性进行观察时,KVO会自动为被观察的对象动态生成一个子类,并在该子类中重写被观察属性的setter方法。在该setter方法中,KVO会添加一些关照的逻辑,以便在属性值发生变化时发送关照。
因此,当我们使用KVO时,可以通过KVC来访问被观察对象的属性值。同时,KVC也可以在不使用KVO的情况下,直接访问对象的属性,从而使我们的代码更加简便和易读。
必要注意的是,KVC和KVO固然在一些场景下会同时使用,但是它们是两个不同的机制,应该根据具体的需求来选择是否使用它们。
4. iOS的事件传递过程
事件从UIApplication开始,按照视图层次结构自顶向下地传递。在这个过程中,体系会根据事件范例和相应者链来决定将事件发送给哪个视图或控件。当事件传递到某个视图或控件时,该视图或控件会实验处置处罚该事件。如果该视图或控件不能处置处罚该事件,则会将该事件转发给下一个相应者。这个过程会不停持续到某个相应者处置处罚了该事件,大概事件传递到UIApplication时结束。
当事件处置处罚完成后,体系会对事件做出终极的相应。例如,在触摸事件中,体系会根据手指的状态来判定该事件是否是一个单击、双击、长按等手势。同时,体系还会在事件结束时对相关的UI元素进行更新和重绘。
在事件传递过程中,可以通过重写视图或控件的相应方法来改变事件的传递和处置处罚行为。例如,可以重写hitTest:withEvent:方法来指定事件的相应对象;重写pointInside:withEvent:方法来指定相应区域;重写touchesBegan:withEvent:方法来处置处罚触摸事件等。
总之,在iOS中,事件传递过程是一个复杂而又精细的机制。了解事件传递过程对于优化应用步调的相应速度和用户体验有着重要的意义。
5. CALayer 与 UIView 的关系
CALayer是UIView的底层实现,可以看作是UIView的图层。每个UIView都有一个对应的CALayer对象,它负责管理UIView的内容和显示。UIView提供了一些高级的界面控制功能,例如相应用户交互事件、布局管理、自动绘制等,而CALayer则负责渲染UIView的内容,包括绘制、变换、动画等。
UIView和CALayer的关系可以用以下两种方式来理解:
- UIView是CALayer的外观:UIView提供了许多高级的界面控制功能,例如用户交互、布局管理等,这些功能都创建在CALayer的底子之上。因此,可以将UIView看作是CALayer的外观,它通过对CALayer的属性进行设置,来控制图层的显示和行为。
- UIView是CALayer的管理者:UIView负责管理CALayer对象的创建、烧毁、布局等,它维护了一个CALayer的层级结构,并负责对图层的属性进行设置。UIView还提供了一些高级的功能,例如自动绘制、动画效果等,这些功能都是基于CALayer实现的。
因此,可以说CALayer是UIView的底层实现,UIView提供了对CALayer的封装,使得开辟者可以更加方便地使用和管理图层。在开辟中,如果必要实现一些高级的界面效果,例如复杂的动画、图层蒙版等,就必要直接使用CALayer来实现。
6. iOS中为什么代理必要用 weak 修饰
制止循环引用:如果代理对象使用strong来修饰,那么代理对象就会对委托对象进行强引用,而委托对象又会对代理对象进行引用,从而形成循环引用。这样就会导致对象无法释放,造成内存走漏。因此,使用weak来修饰代理对象可以制止循环引用问题。
被代理对象通常具有更长的生命周期:在大多数情况下,代理对象是由被代理对象持有的,因此被代理对象通常具有更长的生命周期。如果使用strong来修饰代理对象,那么代理对象就会不停存在于内存中,即使被代理对象已经不存在了。这样就会造成内存浪费。
总之,使用weak来修饰代理对象可以制止循环引用问题,同时也更符合代理对象与被代理对象之间的实际关系。
7. Block 为什么要用 copy 修饰
Block在界说时会捕获作用域中的变量和对象,并将其保存在一个数据结构中,以便在后续的调用中使用。
由于Block大概在界说时保存了局部变量或对象的引用,如果在Block在界说后耽误执行时,这些局部变量或对象已经被烧毁,那么在Block执行时就会访问到已经释放的内存空间,导致步调崩溃。因此,为了制止这种问题,Block必要使用copy修饰。
使用copy修饰Block会将其从栈区复制到堆区,这样就能保证在Block执行时,其所依靠的对象和变量的引用不会因为作用域的改变而失效。具体来说,当Block被复制到堆区时,其所依靠的对象和变量也会被一并复制到堆区,这样就能保证在Block执行时,其所依靠的对象和变量的引用是有用的。
必要注意的是,在ARC(Automatic Reference Counting)环境下,如果Block没有捕获任何对象,则可以使用strong来修饰,因为这种情况下Block不会持有任何对象的引用。但是如果Block捕获了对象或变量,则仍然必要使用copy来修饰,以确保Block的精确性。
block 使用 copy 是从 MRC 遗留下来的“传统”,在 MRC 中,方法内部的 block 是在栈区的。使用 copy 可以把它放到堆区;在 ARC 中写不写都行。
对于 block 使用 copy 照旧 strong 效果是一样的,但写上 copy 也无伤风雅,还能时刻提醒我们:编译器自动对 block 进行了 copy 操纵。如果不写 copy ,该类的调用者有大概会忘记大概根本不知道“编译器会自动对 block 进行了 copy 操纵”,他们有大概会在调用之前自行拷贝属性值。这种操纵多余而低效。
8. 什么是 Block
Block是一种用于封装一段代码的数据范例。Block实际上是一个匿名函数,它可以捕获一些变量和常量,并将它们封装在一起,形成一个可以在必要时执行的代码块。Block可以被当作一个对象来使用,它可以作为方法参数、成员变量、局部变量、数组元素等等。Block中捕获的变量和常量被保存在Block中,可以在Block执行时使用。
在使用Block时,必要注意以下几点:
- Block中捕获的变量必要在Block执行时仍然存在。如果Block中捕获的变量是局部变量,那么必要使用__block修饰符来使其在Block执行时仍然存在。
- Block中使用的变量必要精确地被引用。如果Block中使用了对象,那么必要使用strong或weak修饰符来指定对该对象的引用方式。如果Block中使用了基本数据范例的变量,则不必要使用修饰符。
- Block可以作为参数传递给方法或函数,也可以在方法或函数中界说。如果Block作为参数传递给方法或函数,则必要使用^符号界说。
总之,Block是一种非常强大和灵活的数据范例,它可以用于封装一段代码,并将其当做一个对象来使用。Block可以捕获变量和常量,并且可以作为参数传递给方法或函数,在实际开辟中非常常用。
9. iOS 是如何实现 APNs 的
APNs(Apple Push Notification service)是苹果提供的推送服务,它允许开辟者向用户装备发送推送关照。iOS体系实现APNs的过程如下:
- iOS应用步调必要使用APNs推送服务,必要首先向APNs服务器注册。注册过程中,iOS应用步调会生成一个装备令牌(Device Token),并将该令牌发送给APNs服务器。装备令牌是一个唯一标识符,用于标识该装备的APNs推送服务。
- APNs服务器会将装备令牌保存在其数据库中,并为该装备生成一个注册ID(Registration ID),用于标识该装备在APNs服务器上的注册信息。
- 当开辟者必要向某个装备发送推送关照时,必要将推送关照发送给APNs服务器。
- APNs服务器会根据装备令牌和注册ID,将推送关照发送到对应的装备上。
- 接收到推送关照的装备会显示关照内容,并执行相应的操纵。
在实际开辟中,iOS应用步调可以通过使用APNs SDK,来向APNs服务器注册、发送推送关照等操纵。具体来说,可以使用APNs SDK中提供的API,通过创建毗连、发送哀求等方式,来实现对APNs服务器的访问和操纵。同时,iOS应用步调也必要提供一些必要的配置信息,如证书、证书暗码、装备令牌等等,以便与APNs服务器进行通讯
10. 谈谈对内存管理的理解
在iOS中,重要采用了自动引用计数(Automatic Reference Counting,ARC)的机制来进行内存管理。
ARC是一种编译时自动插入内存管理代码的技术,它可以自动地分析对象的引用关系,并在对象不再被使用时自动释放对象所占用的内存。ARC机制可以极大地简化内存管理的过程,淘汰了手动释放内存的工作量,提高了应用步调的性能和稳定性。
在使用ARC时,必要注意以下几点:
- 对象的引用关系必要精确地维护。如果对象被多个变量引用,那么必要确保所有引用都精确地被处置处罚。
- 对象的循环引用必要制止。循环引用会导致对象无法被精确地释放,从而导致内存走漏。
- 对象的生命周期必要精确地管理。对象在不再被使用时应该及时释放,以便及时采取内存资源。
- 除了ARC机制外,iOS还提供了一些手动管理内存的方式,如MRC(Manual Reference Counting)和内存池技术等。在使用这些技术时,必要更加谨慎地管理对象的引用关系和生命周期,以免出现内存走漏等问题。
11. 什么是内存池
内存池是一种优化内存分配和管理的技术,它可以提高内存分配和释放的效率,淘汰因频繁申请和释放内存而带来的性能损失。
内存池的基本思想是在步调初始化时预先分配一定命目的内存块,并把它们存放在一个池子里面。当必要申请内存时,不再使用体系提供的malloc()或new运算符来动态申请内存,而是直接从内存池中取出一个内存块进行使用。当不再必要这个内存块时,也不用立即将其释放,而是将其放回内存池中,以备后续使用。
内存池的长处重要有:
- 淘汰内存分配和释放的次数,降低内存碎片的产生,提高内存分配和释放的效率。
- 步调初始化时可以预先分配一定命目的内存,制止在步调运行过程中频繁地进行内存分配和释放,提高步调的性能。
- 内存池可以淘汰内存申请和释放带来的锁竞争,从而提高步调的并发性能。
在iOS开辟中,内存池的应用比较广泛,例如在网络编程中使用的Socket毗连池、数据库毗连池、图片缓存池等。内存池技术可以有用地优化内存管理,提高步调的性能和稳定性,但必要注意合理地计划内存池的大小和对象的生命周期,以制止因内存池过大或过小而造成的资源浪费或性能损失。
12. 为什么Block 使用了 __block,就可以修改block内部的变量
__block修饰符标记后,block就会访问标记变量本身内存地址,而未标记对象则访问截获拷贝后的变量内存地址、
13. iOS的启动优化都有哪些
14. 叙述一下APP的启动流程
- 体系先读取App的可执行文件(Mach-O文件),获取到dyld(动态库)的路径,并加载dyld(动态库链接步调)。
- dyld去初始化运行环境、开启缓存策略(冷热启动)、加载依靠库(读取文件、验证、注册到体系焦点)、可执行文件、链接依靠库,并调用每个依靠库的初始化方法。
- 在上一步runtime被初始化,当所有的依靠库初始化后,步调可执行文件进行初始化,这个时候runtime会对项目中的所有类进行类结构初始化,然后调用所有类的+load方法。
1、runtime初始化方法 _objc_init 中最后注册了两个关照:
map_images: 重要是在镜像加载进内容后对其二进制内容进行分析,初始化里面的类结构等
load_images: 重要是调用call_load_methods 按照继承层次依次调用Class的 +load方法 然后是Category的+ load方法。(call_load_methods 调用load 是通过方法地址直接调用的load方法,并不是通过消息机制,这就是为什么分类中的load方法并不会覆盖主类以及其他同主类的分类里的load 方法实现了。)
2、runtime 调用项目中所有的load方法时,所有的类的结构已经初始化了,此时在load方法中可以使用任何类创建实例并给他们发送消息。
4、最后dyld返回main函数地址,main函数被调用。dyld会缓存上一次把信息加载内存的缓存,所以第二次比第一次启动快一点。
pre-main
初始化空间,加载镜像,载入动态库链接器,链接动态库,objcsetup,callloadmethod,
动态库越多,启动越慢,+load方法多也会影响
优化方向:静态库,cocoapods :linkage => :static指令 将所有的动态库转为静态库。 要注意资源获取的方式问题。
合并动态库:cocoapods-pod-merge 支持合并动态库,要注意import方式会改变,作用域前缀要加上
main
main函数以后就是只管淘汰第一个页面展示前的工作,有些组件库自己的工作可以派发到库自己处置处罚,大概异步处置处罚
启动项热拔插
main函数之前优化
启动的第一步是加载动态库,加载体系的动态库使很快的,因为可以缓存,而加载内嵌的动态库速度较慢。所以,提高这一步的效率的关键是:淘汰动态库的数目。
Rebase和Bind都是为了办理指针引用的问题。对于Objective C开辟来说,重要的时间斲丧在Class/Method的符号加载上,所以常见的优化方案是:
合并Category和功能雷同的类。比如:UIView+Frame,UIView+AutoLayout…合并为一个
删除无用的方法和类。
15. KVC 原理
KVC是基于runtime机制实现的。可以访问私有成员变量、可以间接修改私有变量的值。
KVC键值查找原理
setValue:forKey:搜索方式
1、首先搜索setKey:方法.(key指成员变量名, 首字母大写)
2、上面的setter方法没找到, 如果类方法accessInstanceVariablesDirectly返回YES. 那么按 _key, _isKey,key, iskey的顺序搜索成员名。(这个类方法是NSKeyValueCodingCatogery中实现的类方法, 默认实现为返回YES)
3、如果没有找到成员变量, 调用setValue:forUnderfinedKey:
valueForKey:的搜索方式
1、首先按getKey, key, isKey的顺序查找getter方法, 找到直接调用. 如果是BOOL、int等内建值范例, 会做NSNumber的转换.
2、上面的getter没找到, 查找countOfKey, objectInKeyAtindex, KeyAtindexes格式的方法. 如果countOfKey和另外两个方法中的一个找到, 那么就会返回一个可以相应NSArray所有方法的代理集合的NSArray消息方法.
3、还没找到, 查找countOfKey, enumeratorOfKey, memberOfKey格式的方法. 如果这三个方法都找到, 那么就返回一个可以相应NSSet所有方法的代理集合.
4、照旧没找到, 如果类方法accessInstanceVariablesDirectly返回YES. 那么按 _key, _isKey, key, iskey的顺序搜索成员名.
5、再没找到, 调用valueForUndefinedKey.
16. KVO 原理
1.KVO是基于runtime机制实现的
2.当某个类的属性对象第一次被观察时,体系就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter 方法。派生类在被重写的setter方法内实现真正的关照机制
3.如果原类为Person,那么生成的派生类名为NSKVONotifying_Person
4.每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么体系会偷偷将isa指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter方法
5.键值观察关照依靠于NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey:;在一个被观察属性发生改变之前, willChangeValueForKey:一定会被调用,这就 会记载旧的值。而当改变发生后,didChangeValueForKey:会被调用,继而 observeValueForKeyfObject:change:context: 也会被调用。
深入
1.Apple 使用了 isa 混写(isa-swizzling)来实现 KVO 。当观察对象A时,KVO机制动态创建一个新的名为:?NSKVONotifying_A的新类,该类继承自对象A的本类,且KVO为NSKVONotifying_A重写观察属性的setter?方法,setter?方法会负责在调用原?setter?方法之前和之后,关照所有观察对象属性值的更改情况。
2.NSKVONotifying_A类分析:在这个过程,被观察对象的 isa 指针从指向原来的A类,被KVO机制修改为指向体系新创建的子类 NSKVONotifying_A类,来实现当前类属性值改变的监听;
3.所以当我们从应用层面上看来,完全没故意识到有新的类出现,这是体系“遮盖”了对KVO的底层实现过程,让我们误以为照旧原来的类。但是此时如果我们创建一个新的名为“NSKVONotifying_A”的类(),就会发现体系运行到注册KVO的那段代码时步调就崩溃,因为体系在注册监听的时候动态创建了名为NSKVONotifying_A的中间类,并指向这个中间类了。
4.(isa 指针的作用:每个对象都有isa 指针,指向该对象的类,它告诉 Runtime 体系这个对象的类是什么。所以对象注册为观察者时,isa指针指向新子类,那么这个被观察的对象就神奇地变成新子类的对象(或实例)了。)?因而在该对象上对 setter 的调用就会调用已重写的 setter,从而激活键值关照机制。
5.子类setter方法分析:KVO的键值观察关照依靠于 NSObject 的两个方法:willChangeValueForKey:和 didChangevlueForKey:,在存取数值的前后分别调用2个方法: 被观察属性发生改变之前,willChangeValueForKey:被调用,关照体系该 keyPath?的属性值即将变更;当改变发生后, didChangeValueForKey: 被调用,关照体系该 keyPath?的属性值已经变更;之后,?observeValueForKeyfObject:change:context: 也会被调用。且重写观察属性的setter?方法这种继承方式的注入是在运行时而不是编译时实现的。
17. category 为什么不能添加属性?
category 它是在运行期决定的,因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来说是劫难性的。
extension看起来很像一个匿名的category,但是extension和有名字的category几乎完全是两个东西。 extension在编译期决定,它就是类的一部门,在编译期和头文件里的@interface以及实现文件里的@implement一起形成一个完整的类,它陪同类的产生而产生,亦随之一起灭亡。extension一般用来隐藏类的私有信息,你必须有一个类的源码才气为一个类添加extension,所以你无法为体系的类比如NSString添加extension。
但是category则完全不一样,它是在运行期决定的。
就category和extension的区别来看,我们可以推导出一个明显的事实,extension可以添加实例变量,而category是无法添加实例变量的。
那为什么 使用Runtime技术中的关联对象可以为类别添加属性。
其原因是:关联对象都由AssociationsManager管理,AssociationsManager里面是由一个静态AssociationsHashMap来存储所有的关联对象的。这相称于把所有对象的关联对象都存在一个全局map里面。而map的的key是这个对象的指针地址(任意两个不同对象的指针地址一定是不同的),而这个map的value又是另外一个AssociationsHashMap,里面保存了关联对象的kv对。
如合清算关联对象?
runtime的烧毁对象函数objc_destructInstance里面会判定这个对象有没有关联对象,如果有,会调用_object_remove_assocations做关联对象的清算工作。
18. 函数界说时 - 和 + 的含义及区别
19. 类扩展和分类的区别
分类不能添加成员变量【固然可以添加属性 但是一旦调用就会报方法找不到的错误】 (可以通过runtime给分类间接添加成员变量),而类扩展可以添加成员变量;
分类中的属性不会自动实现set方法和get方法,而类扩展中的属性再转为底层时是可以自动实现set、get方法
类扩展中添加的新方法,不实现会报警告。categorygory中界说了方法不实现则没有这个问题
类扩展可以界说在.m文件中,这种扩展方式中界说的变量都是私有的,也可以界说在.h文件中,这样界说的代码就是共有的,类扩展在.m文件中声明私有方法黑白常好的方式。
类扩展不能像分类那样拥有独立的实现部门(@implementation部门),也就是说,类扩展所声明的方法必须依托对应类的实现部门来实现。
20. App的启动流程
- main函数之前
体系将App的可执行文件(Mach-O文件)和dyld加载到内存,由dyld进行动态链接。
- 设置相关环境变量
根据环境变量设置相应的值以及获取当前运行架构。例如配置环境变量打印启动流程耗时: DYLD_PRINT_STATISTICS和DYLD_PRINT_STATISTICS_DETAILS。
- 加载共享缓存库
加载动态共享缓存库到动态库共享缓存区,例如UIKit、CoreFoundation等官方库。
- 加载动态库
把所有的可执行文件所依靠的动态库递归加载到内存中。
- rebase和binding
iOS采用ASLR技术(地址空间布局随机化),加载App的内存地址是随机的,rebase会根据随机的偏移量对原来的地址做重定向。
binding进行符号绑定。指向image外部动态库的指针被符号(symbol)绑定。dyld必要去符号表里查找,找到对应的实现。
- Objc setup
(1) 注册ObjC类
(2) 把category的界说插入方法列表
(3) selector唯一性检查
- initializer
(1)调用所有类、分类的+load方法
(2)调用attribute((constructor))修饰的函数
(3)非基本范例的C++静态全局变量的创建(通常是类或结构体)
- map_images与load_images
map_images : dyld 将 image 加载进内存时 , 会触发该函数。
load_images : dyld 初始化 image 会触发该方法. ( 我们所熟知的 load 方法也是在此处调用 ) 。
dyld在初始化其他动态库之前,会最先初始化体系库libsystem,运行Runtime。体系库libsystem初始化完成后,就会初始化其他动态库,然后由Runtime调用map_images来读取类、方法、协议以及分类并存储到对应的表中(注意:分类并不是直接存,而是通过attachLists方法把分类的数据添加到类里面),然后Runtime会继续调用load_images调用所有类的load方法以及分类的load方法,这些都做完之后,通过dyld提供的回调_dyld_objc_notify_register,告诉dyld加载完毕,然后dyld就开始找主步调的入口main函数,最后进入步调的main函数。
- load方法的调用顺序
+load方法是在load_images中调用的。
load方法调用顺序为:先处置处罚类,后处置处罚分类;处置处罚类的顺序是先父类,后子类。
在调用类的load方法时,做了递归处置处罚,会先调用父类的load,然后再调用子类的load,所有类的load方法调用完成后,才会开始处置处罚所有类的分类,分类的处置处罚顺序取决于Mach-O头文件,和类的顺序没有直接关系。先后顺序即:父类->子类->所有类的分类。
- main函数阶段
main() 会调用 UIApplicationMain(),直至application:didFinishLaunchingWithOptions:执行完毕,整个启动流程就完成了。
21. 谈谈对单例模式的理解
单例模式是一种计划模式,它确保一个类只有一个实例,并提供一个全局访问点来访问该实例。单例模式通常用于必要全局唯一对象的场景,比如配置管理、资源管理、日志记载等。
- 唯一实例: 单例模式确保一个类只有一个实例存在,无论何时何地,对该类的实例访问都只会返回同一个对象。这样可以制止创建多个对象造成资源的浪费,同时保证对象的唯一性。
- 全局访问点: 单例模式提供了一个全局访问点来访问唯一的实例,使得其他对象可以通过该访问点获取单例对象的引用。这样可以方便地在步调的任何地方使用单例对象,而不必要显式地创建对象或传递对象的引用。
- 耽误实例化: 单例模式通常采用耽误实例化的方式来创建单例对象,即在第一次访问单例对象时才创建对象。这样可以节省资源,制止在步调启动时就创建对象,提高步调的启动速度。
- 线程安全性: 在多线程环境下,单例模式必要考虑线程安全性的问题,确保在多线程环境下也能精确地创建和访问单例对象。常见的线程安全实现方式包括使用同步锁、静态变量初始化、耽误初始化等。
- 生命周期管理: 单例模式通常是全局存在的,因此必要考虑对象的生命周期管理,确保单例对象在符合的机遇被精确地释放和清算。这样可以制止内存走漏和资源走漏问题。
- 全局状态共享: 单例模式可以用来实现全局状态共享,多个对象可以共享同一个单例对象的状态,从而实现数据共享和通讯。
总的来说,单例模式是一种简朴而又实用的计划模式,它提供了一种方便的方式来管理全局唯一对象,并确保对象的唯一性、耽误实例化、线程安全性等特性,实用于许多必要全局唯一对象的场景。但是在使用单例模式时必要注意线程安全性和生命周期管理等问题,以确保单例对象的精确使用和管理。
22. 谈一谈对runtime的理解
- 动态性: Runtime 是 Objective-C 的运行时体系,它允许在步调运行时动态地创建类、修改类的结构、调用方法等。这种动态性使得 Objective-C 具有很强的灵活性,可以实现一些在静态语言中难以实现的功能,比如动态派发、消息转发等。
- 元数据: Runtime 中存储了关于类、对象、方法等的元数据信息,包括类的名称、父类、实例变量、方法列表等。通过这些元数据信息,可以在运行时对类和对象进行操纵,比如动态创建类和对象、动态添加方法等。
- 消息传递: 在 Objective-C 中,方法调用是通过消息传递的方式实现的,即发送一个消息给对象,对象根据消息选择符合的方法来执行。Runtime 提供了消息传递的机制,包括消息发送、方法分析、消息转发等,使得 Objective-C 具有动态派发的特性。
- 方法调用: Runtime 提供了一系列方法来实现方法的调用,包括实例方法调用、类方法调用、动态方法分析、方法交换等。通过这些方法,可以在运行时动态地调用方法,甚至可以修改方法的实现。
- 内存管理: Runtime 还提供了一些内存管理的功能,包括自动引用计数(ARC)和自动释放池(Autorelease Pool)。通过这些功能,可以在运行时对对象的内存进行管理,确保对象的引用计数精确,制止内存走漏和野指针问题。
综上所述,Runtime 是 Objective-C 的运行时体系,它提供了一系列功能,包括动态性、元数据、消息传递、方法调用、内存管理等,使得 Objective-C 具有很强的灵活性和动态性。深入理解 Runtime 可以帮助开辟者更好地理解 Objective-C 语言的运行机制,从而编写出更加灵活和高效的代码。
23. 如何查看手机中的崩溃日志?
(1)点击xcode中的window;
(2)选择必要查看日志的装备;
(3)选择view of device log;
24. App启动优化
- main之前:
- 重要工作:加载dylib和可执行文件
- 优化方法:
- 淘汰不必要的framework,因为动态链接比较耗时;
- 移除不必要的类;
- 合并雷同的类和拓展;
- 压缩资源图片;
- 将不必须在+load方法中做的事情耽误到+initialize中;
- 删减没有被调用到大概已经废弃的方法
- main之后:
- 重要工作:从main到applicationWillFinishLaunching执行完成的时间 显示首页 ;
- 优化方法:
- 只管使用纯代码实现UI;
- 对某些可以耽误加载的业务进行耽误加载;
- 淘汰启动时的网络哀求;
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |