iOS - OC基础汇总

一、内存管理

1. 引用计数

  • OC类中实现了引用计数器,对象知道自己当前被引用的次数。
  • 对象初始化时计数器为1,每次操作对象都会引起相应的计数器变化。
    档引用计数器为0时,给对象发送dealloc消息销毁对象。

1.1 黄金法则

  • 凡是通过allocinitcopymutableCopyretain进行创建的对象,都要使用ReleaseAutorelease进行释放。
  • 自己生成的对象,自己持有。不是自己生成的对象,自己也能持有。
  • 不再需要持有的对象时释放,非自己持有的对象不需要释放。

1.2 引用计数存放

  • isa:

    • 从64bit开始,对象的引用计数存放在优化过的isa指针中,也可能存放在SideTable中。
    • 当优化过的isa指针中,引用计数过大存放不下时,就会将引用计数存放到SideTable中。
  • SideTable:

    • 是一个哈希表,Key为对象的指针,Value为对象内容具体存放的SideTable
    • SideTable包含自旋锁,引用计数表,弱引用表,由Runtime维护。
    • 查获或修改引用计数时是要加锁的,方便多个对象同时操作。


2. MRC

  • MRC(Manual Reference Counting)手动内存管理
  • iOS5之前,需要开发者手动去管理内存。
  • 需要引用对象时,发送retain消息,对象的引用计数+1
  • 不需要引用对象时,发送release消息,对象的引用计数-1
  • 当引用计数为0时,自动调用对象的dealloc方法销毁对象,释放内存,不能再使用release和其他方法。


3. ARC

  • ARC(Automatic Reference Counting)自动内存管理
  • ARC也是基于引用计数,只是编译器在编译时期自动在已有代码中插入适合的内存管理代码(retainreleasecopyautoreleaseautoreleasePool)以及在Runtime做一些优化。
  • 简单来说就是代码中自动加入了retainrelease,原先 MRC 中需要手动添加的用来管理引用计数的代码都由编译器帮我们完成了。


4. Strong

  • 强引用,指向一个对象时,对象引用计数+1;
  • 当对象没有一个强引用指向它时,他才会被释放。
  • 如果在声明引用时不加修饰符,默认为strong


5. Weak

  • 弱引用,不会引起对象的引用计数变化;
  • 当对象被释放时,所有指向它的弱引用指针会自动被置为nil,防止野指针。
  • 给nil发送消息时,会直接renturn,不会调用方法,也不会crash。

weak原理

  • 对象的SideTable中有一个weak表,以对象的内存地址为key,value则是所有指向该对象的弱引用指针的数组。
  • 当对象销毁时,通过对象内存地址找到所有指向它的弱引用指针,置为nil并删除。


6. Assign

  • assign指针纯粹指向对象,不会引起对象的引用计数发生变化。
  • 当对象被释放时,指针依然指向对象原来的内存地址,不会自动被置为nil,容易造成野指针。
  • 所以一般assign都用来修饰基本数据类型如intfloatstruct等值类型。
  • 值类型会被放入栈中,遵循先进后出的原则,由系统负责管理栈内存。
  • 引用类型会被放入堆中,需要我们自己手动管理内存(MRC)或通过 ARC 管理。


7. Copy、MutableCopy

浅拷贝
copy出来的对象与源对象地址一致,对拷贝对象做修改会影响源对象。

深拷贝
copy出来的对象与源对象地址不一致,开辟新的内存空间存放拷贝对象,对拷贝对象做修改不会影响源对象。

  • 对于不可变类型的对象,copy为浅拷贝,对象引用计数+1。
  • 对于可变类型的对象,copy为深拷贝,拷贝对象也变为不可变类型。
  • 对对象做mutableCopy操作,都为深拷贝,拷贝对象也会变为可变类型。
  • 对于容器类型(NSArrayNSDictionary等),深拷贝也仅是拷贝容器本身,对容器里面的元素只做浅拷贝。

声明NSString类型的属性,用copy还是strong修饰更好?
考虑多态的原因,NSString类型的属性,最终可能指向的是NSMutableString,为了防止源字符串的修改引起变化,最好是采用copy来修饰。

如何自定义cpoy操作?

  • 遵循copy协议<NSCopying, NSMutableCopying>
  • 重写copyWithZonemutableCopyWithZone方法。


8. Atomic、Nonatomic

  • 对atomic修饰的属性的setter、getter方法添加了原子锁,保证set、get操作的完整性。也就是下一次的setter、getter操作必须等到上一次的set、get操作完成之后才能执行。

  • 因为atomic添加了原子锁,会增加开销,运行速度更慢,在不需要保证setter、getter操作的完整的情况下,所以一般都使用nonatomic。

  • atomic不能完全保证线程安全

    • 只能保证setter、getter操作的完整性,当开启多个线程执行多个setter、getter操作时,无法保证执行的顺序。
    • 另外如数组除了setter、getter操作外,还有remove的操作。


9. 内存泄漏

是指申请的内存空间用完之后未释放,在ARC下根本原因是循环引用(在ViewController中没有正确的使用NStimerdelegateblock)引起的。


10. 循环饮用

  • 两个对象之间相互强引用,引用计数都依赖于对方,导致对象无法释放。
  • 最容易产生循环引用的两种情况就是delegateblock,所以才引入了弱引用。
  • 持有对象,但不增加引用计数,这样就避免了循环引用的产生。


11. 内存溢出

通俗的讲就是内存不够用了,程序申请内存空间时,没有足够的内存空间可供使用。




二、Runtime

1. Runtime:

  • Runtime(运行时)是一个C语言的库,提供API创建类、添加方法、删除方法、交换方法等。

  • 运行时:用户可以运行编译过的程序,程序运行的过程。

  • 编译时:源代码被编译成机器可以识别的代码的过程。

  • OC是一门动态语言:

    • OC的动态性由Runtime支持的
    • 动态语言是指程序可以在运行时可以改变其结构:添加新的函数、属性,删除已有的函数、属性等结构上的变化,在运行时做类型的检查。


2. id、instanceType

  • id

    • id 是任意类型
    • 使用id修饰的对象是动态类型,只是简单的声明了指向对象的指针。
    • 编译时不做类型检查,可以发送任何信息给id类型的对象。
  • instanceType

    • 表示某个方法返回未知类型的OC对象,非关联类型的方法返回所在类的类型。
    • instancetype可以返回和方法所在类相同类型的对象,id只能返回未知类型的对象。
    • instancetype只能作为返回值,不能像id一样作为参数。
  • 关联返回类型

    • 1.类方法中,以allocnew开头
    • 2.实例方法中,以autoreleaseinitretain、或self开头
    • 当方法的返回值为id类型,方法不会返回一个类型不明的对象,会返回一个方法所在类类型的对象。
  • 非关联返回类型

    • 1.类方法中,不以allocnew开头
    • 2.实例方法中,不以autoreleaseinitretain、或self开头
    • 当方法的返回值为id,方法会返回一个类型不明的对象;
    • 可以用instancetype作为方法的返回值的类型,返回一个方法所在类类型的对象。
  • NSObject *:

    • 声明类指向NSObject类型对象的指针,编译时要做类型检查。
    • NSObject是OC中的基类,绝大多数类都继承与NSObject
  • id <NSObject>:

    • 也是一个指针,要求指向的类型要实现NSObject protocol
    • NSObjectNSProxy类实现了NSObject接口,id<NSObject>可以指向它们
  • OC对象的本质:

    • OC对象本身是一个结构体,这个结构体只有一个isa指针
    • 任何数据结构,只要在恰当的位置有个指针指向一个class,那么它就可以被认为是一个对象。
  • NSObject对象内存大小:

    • 64bit下boolsigned charunsigned char占1个字节;
    • shortunsigned short 占2个字节;
    • intunsigned intfloat占4个字节;
    • longunsigned longlong longdouble占8个字节。
    • NSObject占8个字节;
    • 结构体内成员按自身长度自对齐;
    • 对象内存申请的时候按8字节对齐,开辟内存时按16字节对齐。


3. isa(is a what?)

  • 实例对象isa指向类对象;
  • 类对象isa指向元类;
  • 类对象superClass指向父类指向的类对象;
  • 所有元类isa指向NSObject对象的元类(根元类);
  • 根元类isa指向自己;
  • 根元类的superClass指向NSObject的类对象;
  • 元类的superClass指向对应父类的元类;


4. 消息发送机制

在OC中,对象调用方法其实是对象接收消息,消息的发送采用“动态绑定”的机制,具体调用哪个方法直到运行时才能确定,确定后才会去执行绑定的代码

OC对象调用方法在运行时会被转化为 void objc_msgSend(id self, SEL cmd...)

  • SEL:方法名

  • IMP:指向方法实现的函数指针

  • 消息发送流程

    • 1.根据消息接收者的isa确定自己所属的类,先在类的 _x001D_cache 和 MethodLists 中从上向下查找IMP;
    • 2.如果本类中没有找到,则会根据本类的superClass指针,沿着继承体系继续向上查找(向父类查找)
    • 3.如果向父类查找都没有找到,则会进入「消息转发流程」
  • 消息转发流程

    • 1.动态解析:

      • 类方法未找到时调起,可于此添加类方法实现
        + (BOOL)resolveInstanceMethod:(SEL)selector;
      • 实例方法未找到时调起,可于此添加实例方法实现
        + (BOOL)resolveIClassMethod:(SEL)selector;
    • 2.备用接收者:

      • 消息接收者重定向(返回一个实例对象、或类)
        - (id)forwardingTargetForSelector:(SEL)selector;
    • 3.消息重定向:

    - (void)forwardInvocation:(NSInvocation *)anInvocation;
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
    
    • 4.抛出异常:
      最后消息未能处理的时候,还会调用 - (void)doesNotRecognizeSelector:(SEL)aSelector 抛出异常,程序也就崩溃了。


5. Merhod Swizzing(方法交换)

  • Method Swizzing是发生在运行时的,主要用于在运行时将两个Method进行交换。

  • 先给要替换的方法的类添加一个Category,然后在Category中的+(void)load方法中添加Method Swizzling方法,我们用来替换的方法也写在这个Category中。

  • Swizzling应该总在+load中执行,load类方法是程序运行时这个类被加载到内存中就调用的一个方法,执行比较早,并且不需要我们手动调用。

  • Swizzling应该总是在dispatch_once中执行,避免被多次执行。

  • Method Swizzing应用:

    • 页面统计:交换Controller View的生命周期方法
    • 事件统计、防止按钮短时间内重复点击:交换UIControl sendAction:to:forEvent方法
    • 交换delegate的方法:hook setDelegate方法,最好加上是否实现了delegate的方法的判断
    • 防止数组越界崩溃:hook objectAtIndex,需要获取到类簇的真身


6. Category、Extension

  • Category
    在程序运行时实例方法整合到主类中、类方法整合到元类中、协议同时整合到主类和元类中

  • 在类的+laod方法中可以调用category里声明的方法吗?
    可以,因为附加Category到类的工作优先于+load方法的执行

  • 类和category的+load方法调用的顺序
    先类,后category。而各个category的+load方法按照编译的顺序执行

  • 关联对象存在哪?
    所有的关联对象都由AssociationsManager管理,AssociationsManager里面由一个静态AssociationsHashMap来存储所有的关联对象的

  • 在category里可以添加属性吗?

    • category中只能添加方法,不能添加实例变量。类的内存大小是在编译时确定的,而category是在运行时被添加的,此时再添加实例变量会破坏内存结构。
    • 在category中添加属性,通过关联对象实现setter、getter方法。
  • 类和category的同名方法调用的顺序

    • category并不是完全替换掉主类的同名方法,只是类的方法列表中会出现名字一样的方法且category的方法会排在前面,多个category中的同名方法按编译的顺序排。runtime查找方法按照顺序,一旦找到就return
    • 遍历类的方法列表,列表里最后一个同名的方法,即是原方法。
  • category与extension区别:

    • category

      • 运行时决议
      • 有单独的.h.m文件
      • 可以为系统类添加分类
      • 看不到源码的类可以添加分类
      • 只能添加方法,不能添加实例变量
    • extension

      • 编译时决议
      • 以声明的方式存在,寄生于主类.m文件
      • 不可以为系统类添加 extension
      • 没有.m源码的类不可以 extension
      • 可以添加方法,可添加实例变量,默认为@private


7. 归档解档(NSCoding)

  • 归档与解档是iOS中一种「序列化」与「反序列化」的方式。

  • 对象要实现 序列化 需要遵循NSCoding协议。

  • 通过 class_copyIvarList 获得对象的属性列表

  • 通过 ivar_getName(ivar) 获取到属性的C字符串名称

  • 转成对应的OC名称NSString *key = [NSString stringWithUTF8String:name];

  • 利用KVC进行归档[[corder encodeObject: [self valueForKey:key] forKey: key];

  • 解档id value = [coder decodeObjectForKey];

  • 利用KVC进行赋值[self setValue:value ForKey:key];


8. KVC、KVO

8.1 KVC

是一种可以通过key来访问类属性的机制。而不是通过调用Setter、Getter方法访问。可以在运行时动态访问和修改对象的属性。

  • setValue:ForKey:

    • 按照setKey、_setKey的顺序查找方法,找到了就传递参数,调用方法
    • 如果没找到,则查看accessInstanceVariableDirectly方法的返回值,如果为NO(默认是YES)就不再继续往下执行,直接调用setValue:forUndefinedKey抛出NSUnknownKeyException异常
    • 如果返回值为YES,则按照_key、_isKey、key、isKey的顺序查找成员变量,找到了就直接赋值
    • 如果没找到,则调用setValue:forUndefinedKey抛出异常
  • valueForKey:

    • 按照getKey、_getKey的顺序查找方法,找到了就直接调用方法
    • 如果没找到,则查看accessInstanceVariableDirectly方法的返回值,如果为NO(默认是YES)就不再继续往下执行,直接调用value:forUndefinedKey抛出NSUnknownKeyException异常
    • 如果返回值为YES,则按照_key、_isKey、key、isKey的顺序查找成员变量,找到了就直接取值
    • 如果没找到,则调用value:forUndefinedKey抛出异常


8.2 KVO

KVO(Key Value Observing):键值监听,可以监听对象某个属性值的变化

  • 给对象添加监听
  • 通过runtime动态创建一个子类,修改对象的isa指向子类
  • 子类重写set方法,内部执行顺序
    • willChangeValueForKey
    • [super setKey]
    • didChangeValueForKey
    • didChangeValueForKey中调用KVO的回调方法:observeValueForKeyPath:ofObject:change:context:




三、Block

1. Block原理

  • block是一个结构体,通常包含两个成员变量:__block_impl、__block_desc和一个构造函数。
  • block本质上也是一个OC对象,因为它内部有isa指针,block封装了函数调用以及函数调用环境的OC对象
  • block实际上就是OC对于闭包的实现,闭包是一个函数(或指向函数的指针),再加上该函数执行的外部的上下文变量


2. Block类型

  • 全局Block(NSGlobalBlock)
    没有访问外部auto变量(我们平时写的局部变量,默认就有自动变量(Auto),离开作用域就销毁),访问外部static或者全局变量还是全局Block

  • 栈Block(NSStackBlock)
    访问了外部auto变量(在ARC下没有强引用指向这个block,而是直接打印出来)

  • 堆Block(NSMallocBlock)

    • 栈block调用了copy(在ARC下访问了auto变量且有强引用指向该block或作为函数的返回值,就会自动将栈block copy到堆上)
    • 全局block调用copy还是全局block,堆block调用copy引用计数+1


3. 变量捕获

  • 局部变量为什么要捕获?
    考虑作用域的问题,在block里属于跨函数来访问局部变量,所以需要捕获

  • auto变量值传递,static变量指针传递

    • auto变量可能会销毁,内存可能会消失,不采用指针访问;
    • static变量一直保存在内存中,采用指针访问
  • 全局变量不需要捕获,直接访问

    • block里访问self会捕获self
    • OC函数转成C++函数时,self和_cmd作为函数默认传的参数,是局部变量,所以要捕获。
    • block里访问成员变量是先捕获self,然后通过self访问成员变量


4. __block、__weak

  • __block不管是MRC、还是ARC下都可以使用,可以修饰对象,也可以修饰基本数据类型;

  • __weak只能在ARC下使用,只能修饰对象,不能修饰基本数据类型;

  • __block对象可以在block中被重新赋值,__weak不可以

  • 解决循环引用:在ARC下使用__weak,在MRC下使用__block

  • 修改变量
    • __block修饰的变量在block中保存的是变量的地址,使用__block修饰之后的变量类似于static变量
    • __block不能修饰全局变量、静态变量(static


5. weakSelf

  • 如果block在栈空间,不管外部变量是强引用还是弱引用,block都会弱引用访问对象;

  • 如果block在堆空间,如果外部强引用,block内部也是强引用,如果外部弱引用,block内部也是弱引用

__weak typedof(self) weakSelf = self;
  • block是controller的属性,如果内部没有使用weakSelf会造成内存泄漏
  • block不是controller的属性,内部使用self不会造成内存泄漏
  • 当使用类方法有block作为参数使用时,block内部使用self不会造成内存泄漏




四、多线程

1. 多线程

  • 什么是多线程?

    • 多线程是指实现多个线程并发执行的技术,进而提升整体处理性能。
    • 同一时间,CPU 只能处理一条线程,多线程并发执行,其实是 CPU 快速的在多条线程之间调度(切换),如果 CPU 调度线程的时间足够快, 就造成了多线程并发执行的假象
  • 进程
    当一个程序进入内存运行,即变成一个进程。进程是处于运行过程中的程序,并且具有一定独立功能。

  • 线程

    • 线程是进程中的一个执行单元,负责当前进程中程序的之心,一个进程至少有一个线程,一个进程中可以有多个线程;
    • 对于CPU单一个核心而言,某个时刻只能执行一个线程,而CPU在多个线程之间切换的速度相对我们的感觉要快,看上去就是在同一时刻运行。
    • 多线程并不能提高程序的运行速度,但能提高运行效率
    • 单线程程序:若有多个任务只能一次执行
    • 多线程程序:若有多个任务,可以同时执行


2. 队列(dispatch queue)

执行任务的等待队列,即用来存在任务的队列。队列是一种特殊的线性表,采用FIFO(first in first out)的原则。新的任务总是被插到队列的末尾,读取任务总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。

  • 串行队列(serial dispatch queue)
    只开启一个线程,每次只能执行一个任务,一个任务执行完毕后才能执行下一个任务。

  • 并发队列(concurrent dispatch queue)
    可以让多个任务并发(同时)执行,可以开启多个线程,并同时执行任务。并行队列的并发功能只能在异步下才有效

  • 主队列:是一个特殊的串行队列,添加到主队列的任务只能在主线程中执行

  • 全局队列:也是并发队列


3. 任务

执行操作的意思,就是要在线程中执行的代码

  • 同步执行(sync)

    • 同步添加任务到队列中,队列在任务结束之前会一直等待,直到任务完成之后再继续执行
    • 只能在当前线程中执行任务,不具备开启新线程的能力
  • 异步执行(async)

    • 异步添加任务到队列中,队列不会等待,可以继续执行其他任务。
    • 可以在新的线程中执行任务,具备开启线程的能力,但不一定开启新线程。


4. 线程安全

在多线程中运行得到的结果与在单线程中运行得到的结果一致,即为线程安全

  • GCD信号量

    • 保持线程同步,将异步执行转换为同步执行
    • 保证线程安全,为线程加锁
  • 自旋锁

    • 如果资源被占用,等待的线程以死循环的方式一直处于忙等状态,一旦资源释放,立马执行
  • 互斥锁

    • 如果资源被占用,等待的线程会进入休眠状态,直到等待的资源被解锁才被唤醒
  • iOS八大锁

    • NSLock互斥锁
    • NSCondition互斥锁(条件锁)
    • NSConditionLock互斥锁(条件锁)
    • pthread_mutex互斥锁
    • NSRecursiveLock递归锁
    • @synchronized递归锁
    • OSSpinLock自旋锁
    • dispatch_semaphore:是一种更高级的同步机制,互斥锁可以说是 semaphore 在仅取值0/1时的特例。信号量可以有更多的取值空间,用来实现更加复杂的同步,而不单单是线程间互斥


5. GCD

  • 什么是GCD?

    • GCD(Grand Central Dispatch)是 Apple 开发的一个多核编程的较新的解决方法
    • 它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统
    • 它是一个在线程池模式的基础上执行的并发任务
  • 为什么要使用GCD?

    • GCD 可用于多核的并行运算
    • GCD 会自动利用更多的 CPU 内核(比如双核、四核)
    • GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
    • 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码
  • 队列(Dispatch Queue

    • 串行队列(Serial Dispatch Queue)
    • 并发队列(Concurrent Dispatch Queue)
  • 任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。

    • 同步执行(sync)
    • 异步执行(async)
  • 常用函数:

    • dispatch_barrier_async
      栅栏函数,等待前面加到并发队列中的任务全部执行完毕之后,再将指定的任务追加到该异步队列中

    • dispatch_after
      延时执行,不是在指定时间之后才开始执行任务,而是在指定时间之后将任务追加到主队列中。严格来讲,这个时间并不是绝对准确的

    • dispatch_once
      只执行一次,常用于创建单例,在多线程的环境下,也能保证线程安全

    • dispatch_apply
      快速迭代,可以在多线程中同时(异步)遍历

    • dispatch_group_notify
      监听group中任务的完成状态,当所有任务都执行完后,追加任务到group中并执行

    • dispatch_group_wait
      阻塞当前线程,等待指定的group中的任务执行完成后,才继续往下执行

    • dispatch_group_enter
      表示一个任务追加到group中

    • dispatch_group_leave
      表示一个任务离开group

    • dispatch_semaphore
      信号量

    • dispatch_semaphore_create
      创建信号量并初始化信号总量

    • dispatch_semaphore_signal
      信号量+1

    • dispatch_semaphore_wait

      • 信号量-1
      • 信号量<0时会一直阻塞所在线程,反之就可以正常执行


6. NSOperation、NSOperationQueue

  • NSOperation 操作(任务)
    即GCD中的任务,将要在线程中执行的代码片段

  • NSOperationQueue 操作队列

    • 不同于GCD的队列先进先出(FIFO)的原则。
    • 对于添加到队列中的操作(任务),首先进入准备就绪的状态(就绪状态取决于操作(任务)之间的依赖关系)
    • 然后就绪状态的操作(任务)的开始执行顺序由操作(任务)之间的优先级决定
  • 没有依赖关系的操作(任务)先进入就绪状态,根据优先级决定执行顺序;当前操作(任务)依赖的操作(任务)执行完毕,当前操作(任务)进入就绪状态。

  • 通过设置最大并发操作(任务)数来控制并发和串行,默认为 -1,不做限制,可进行并发执行;==1是串行队列,串行执行;>1是并发队列,并发执行。

  • 被添加到队列的操作(任务),默认是异步执行的;主队列运行在主线程;自定义队列同时包含串行、并发的功能,运行在其他线程。

  • 通过设置队列的 isSuspended 属性,可实现队列的暂停与继续的效果,正在执行的操作(任务)不受影响;可以取消队列中所有的操作(任务),也可取消单个操作(任务),只对未执行的操作(任务)有效;操作(任务)已经在执行中,系统不会强制停止这个操作(任务),只会标记 cancelled 属性为 true。


7. NSThread

  • NSThread是苹果官方提供的,使用起来比PThread更加面向对象,简单易用,可以直接操作线程对象。

  • NSThread的对象就代表一条线程,轻量级的线程操作,生命周期需要程序员控制,当任务执行完毕之后被释放掉。




五、RunLoop

1. 什么是RunLoop?

  • 顾名思义,RunLoop就是一直运行着的循环;
  • RunLoop实际上是一个对象,这个对象在循环中用来处理程序运行过程中出现的各种事件(比如说touch事件、UI刷新事件、定时器事件、Selector事件),从而保持程序的持续运行;
  • Runloop在没有事件处理时,会让线程进入休眠,只有在接收到事件时才会被唤醒,然后再做出相应的处理;
  • 一条线程对应一个RunLoop对象,主线程的RunLoop对象由系统自动创建


2. RunLoop Mode

  • NSDefaultRunLoopMode
    app默认Mode,通常主线程就在该Mode下运行

  • NSTrackingRunLoopMode
    界面跟踪mode,用于scrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响

  • UIInitializationRunLoopMode
    在刚启动app时进入的第一个Mode,启动完成后就不再使用

  • GSEventReceiveRunLoopMode
    接收系统事件的内部Mode,通常用不到

  • NSRunLoopCommonModes
    这是一个占位Mode,不是一个真正的Mode,会同时处理默认模式和UI模式中的事件


3. RunLoop Source

  • 定时源,基于时间的触发器,基本上就是 NSTimer;
  • NSTimer 默认被加入RunLoop的Default Mode下;
  • source0:非基于Port的源,基本就是应用层事件,比如点击事件
  • source1:基于Port的源,通过内核和其他线程通信,接收、分发系统事件
  • 我们点击app内的某个按钮,先触摸到屏幕(硬件),屏幕表面的事件会先封装成Event传递给source1(mach_port),source1唤醒runloop,让后将Event分发给source0处理


4. RunLoop Observer

  • CFRunLoopObserverRef,监听RunLoop的状态改变
  • 即将进入Loop:1
  • 即将处理Timer:2
  • 即将进入source:4
  • 即将进入休眠:3、2
  • 即将从休眠中唤醒:6、4
  • 即将从Loop中退出:1、2、8


5. RunLoop运行逻辑

  • 1.通知观察者即将进入RunLoop。
  • 2.通知观察者即将处理Timer。
  • 3.通知观察者即将处理source0。
  • 4.处理source0。
  • 5.如果有source1,跳至第9步。
  • 6通知观察者线程即将进入休眠状态。
  • 7.线程进入休眠,等待直到以下任一事件发生唤醒线程。
    • (1)某一事件到达基于端口的源;
    • (2)定时器启动;
    • (3)RunLoop设置的时间超时;
    • (4)RunLoop被显式唤醒;
  • 8.通知观察者线程即将被唤醒
  • 9.处理唤醒时收到的事件。
    • 1.如果用户定义的定时器启动,处理定时器事件并重启RunLoop,跳回步骤2;
    • 2.如果输入源启动,传递相应消息;
    • 3.如果RunLoop被显式唤醒且时间未超时,重启RunLoop,跳回步骤2;
  • 10.通知观察者,即将退出RunLoop。


6. 如何实现常驻线程

如果经常会在子线程中做一些耗时操作(比如下载文件、后台播放音乐等),最好能让这条线程常驻内存。

  • 为当前线程添加一个RunLoop
  • 向该RunLoop中添加一个Port/Source等维持RunLoop的事件循环
  • 启动该RunLoop




六、性能优化

1. 卡顿优化

解决卡顿的主要思路就是尽可能的减少CPU与GPU资源的消耗

1.1针对CPU

  • 尽量用轻量级的对象,如:不用处理事件的UI控件可以考虑使用CALayer;
  • 不要频繁的调用UIView的相关属性,如:frame、bounds、transfor等;
  • 尽量提前计算好布局,在有需要的时候一次性调整对应属性,不要多次修改;
  • AutoLayout会比直接设置frame消耗更多的CPU资源;
  • 图片size和UIImageView的size保持一致;
  • 控制线程的最大并发数;
  • 耗时操作放入子线程,如:文本的尺寸计算、绘制,图片的解码、绘制等。

1.2 针对GPU

  • 尽量避免短时间内显示大量的图片
  • GPU能处理的尺寸最大纹理尺寸为4096*4096,超过这个尺寸就会占用CPU资源,所以纹理不能超过这个尺寸
  • 尽量减少透明视图的数量与层次
  • 减少透明的视图(alpha<1),不透明就设置opaque为YES视
  • 尽量避免离屏渲染,以下操作会导致离屏渲染
    • 光栅化,layer.shouldRasterize = YES
    • layer.mask
    • layer.maskToBounds = YES, layer.cornerRadius > 0
    • 阴影未设置layer.shadowPath


2. 耗电优化

  • 尽可能降低CPU、GPU功耗;
  • 少用定时器;
  • 定位优化:
    • 如果只是需要快速确定用户位置,用CLLocationManager的requestLocation方法定位,定位硬件会自动断电;
    • 若不是导航应用,尽量不要实时更新位置,定位结束就关掉定位服务尽量降低定位精度,如不使用精度最高的KCLLocationAccuracyBest;
    • 需要后台定位时,尽量设置pauseLocationUpdatesAutomatically为YES,若用户不怎么移动的时候,系统会自动暂停位置更新。


3. 启动优化

3.1 热启动

app进程还在系统中,无需开启新进程的启动过程

3.2 冷启动

  • app不在系统进程中,用户再点击启动app的过程,这时需要创建一个新进程分配给app;
  • app启动最佳速度是400ms内,因为从点击app图标启动,然后Launch Screen出现再消失的时间就是400ms;
  • app启动最慢不能>20s,否则app进程会被系统杀死;
  • 冷启动的整个过程是指从用户唤起app开始到AppDelegate中的;didFinishLaunchWithOptions方法执行完毕,并以执行main()函数的时机为分界点,分为pre-main和main()阶段。

3.3 Mach-O

  • Mach-O文件

    • Mach Object File Format:是一种用于记录可执行文件、共享库、动态加载代码和内存转储的文件格式。是OSX和iOS上主要的可执行文件格式,类似于Windows系统上的exe。
    • app编译生成的二进制可执行文件就是Mach-O格式的,iOS工程所有的类编译后会生成对应的目标文件.o文件,而这个可执行文件就是这些.o文件的集合。
  • .ipa(iPhone Application)
    实际上只是一种变相的zip压缩包;

  • dylib
    动态链接库,是运行时加载的,可以被多个app进程共用。分为系统dylib和内嵌dylib(即开发者手动引入的动态库);

  • dyld
    动态链接器,一个专门用来加载dylib的库;

  • dyld shared cache
    动态库共享缓存,当需要加载的动态库非常多时,相互依赖的符号也更多了,为了节省解析处理符号的时间,OSX和iOS上动态链接器使用了共享缓存;

  • images
    镜像,每个app都是以images为单位进行加载的;

  • executable
    应用的二进制可执行文件;

  • bundle
    资源文件,属于不能被链接的dylib,只能在运行时通过dlopen()加载;

  • framework
    可以是动态库,也可以是静态库,是一个包含dylib、bundle和头文件的文件夹。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • 1.关于方法的执行顺序问题,代码示例如下: dispatch_after 第二个参数为0,可以理解为在当前时刻向主...
    oc123阅读 821评论 0 0
  • 前言 收录的iOS面试技术点,可能没那么多时间来总结答案,有什么需要讨论的地方欢迎大家底部留言。主要记录一下大厂技...
    iOS猿_员阅读 7,668评论 2 19
  • 1. #import 跟#include、@class有什么区别?#import<> 跟 #import""又什么...
    superxjhw阅读 434评论 0 0
  • 1.网络 1.网络七层协议有哪些? 物理层:主要功能:传输比特流;典型设备:集线器、中继器;典型协议标准和应用:V...
    _我和你一样阅读 3,367评论 1 38
  • 1.weak和assign区别 修饰变量类型的区别: weak 只可以修饰对象。如果修饰基本数据类型,编译器会报错...
    coderjon阅读 1,005评论 0 1