3~5年开发经验iOS工程师 应该知道的常识点~本篇总结了以下内容

  • KVO
  • KVC
  • 多线程
  • runloop
  • 计时器

导航

iOS 进阶常识总结(一)

  • 目标
  • 类目标
  • 分类
  • runtime
  • 音讯与音讯转发

iOS 进阶常识总结(二)

  • KVO
  • KVC
  • 多线程
  • runloop
  • 计时器

iOS 进阶常识总结(三)

  • 视图渲染和离屏渲染
  • 事情传递和呼应链
  • crash处理和功能优化
  • 编译流程和发动流程

iOS 进阶常识总结(四)

  • 内存办理
  • 野指针处理
  • autoreleasePool
  • weak
  • 单例、告诉、block、承继和调集

iOS 进阶常识总结(五)

  • 网络基础
  • AFNetWorking
  • SDWebImage

KVO & KVC

KVO用法和底层原理

  • 运用办法:增加调查者并完成监听的代理办法
  • KVO底层运用了 isa-swizling 技能
  • OC中每个目标/类都有isa指针, isa 表明这个目标是哪个类的目标
  • 当给目标的某个特点注册了一个 observer,体系会创立一个新的中心类(intermediate class)承继自本来的class,把该目标的isa指针指向中心类。
  • 然后中心类会重写setter办法,赋值前调用willChangeValueForKey, 赋值后调用didChangeValueForKey,告诉一切调查者值产生了更改
  • 重写了 -class 办法伪装类没有产生改动

KVO的优缺陷

  • 长处
    • 1、能够方便快捷的完成两个目标的相关同步,例如view & model
    • 2、能够调查到新值和旧值的改动
    • 3、能够方便的调查到嵌套类型的数据改动
  • 缺陷
    • 1、调查目标经过string类型设置,假如写错或者变量名改动,编译时能够经过可是运转时会产生crash
    • 2、调查多个值需求在代理办法中多个if判别
    • 3、屡次注册会屡次触发代理办法
    • 4、增加和移除调查者有必要成对呈现,次数不匹配会crash

怎样手动触发KVO

  • 在调查目标改动前调用willChangeValueForKey:
  • 在调查目标改动后调用didChangeValueForKey:

KVO增加挑选条件

  • 重写automaticallyNotifiesObserversForKey,需求挑选的key回来NO
  • 手动触发KVO
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
   if ([key isEqualToString:@"age"]) {
     return NO;
   }
   return [super automaticallyNotifiesObserversForKey:key];
}
​
- (void)setAge:(NSInteger)age {
   if (age >= 18) {
     [self willChangeValueForKey:@"age"];
     _age = age;
     [self didChangeValueForKey:@"age"];
   }else {
     _age = age;
   }
}

运用KVC会触发KVO吗?

  • 会,只需accessInstanceVariablesDirectly回来YES,经过KVC修正成员变量的值会触发KVO
  • 这说明KVC内部调用了willChangeValueForKey:办法和didChangeValueForKey:办法

直接修正成员变量会触发KVO吗?

  • 不会

KVO的溃散与防护

  • 溃散原因:
    • KVO 增加和移除次数不相等,大部分是移除多于注册。
    • 被调查者dealloc时依然注册着KVO,导致溃散。
    • 增加了调查者,但未完成 observeValueForKeyPath:ofObject:change:context:
  • 防护计划1:
    • 直接运用facebook开源框架KVOController
  • 防护计划2:
    • 自定义一个哈希表,记载调查者和调查目标的联系。
    • 运用fishhook替换 addObserver:forKeyPath:options:context:,在增加前先判别是否现已存在相同调查者,不存在才增加,防止重复触发构成bug。
    • 运用fishhook替换removeObserver:forKeyPath:removeObserver:forKeyPath:context,移除之前判别是否存在对应联系,假如存在才开释。
    • 运用fishhook替换dealloc,履行dealloc前判别是否存在未移除的调查者,存在的话先移除。

KVC底层原理

setValue:forKey:的完成

  • 查找setKey:办法和_setKey:办法,只需找到就直接传递参数,调用办法;
  • 假如没有找到setKey:_setKey:办法,查看accessInstanceVariablesDirectly办法的回来值,假如回来NO(不允许直接拜访成员变量),调用setValue:forUndefineKey:并抛出反常NSUnknownKeyException
  • 假如accessInstanceVariablesDirectly办法回来YES(能够拜访其成员变量),就依照次序顺次查找 _key、_isKey、key、isKey 这四个成员变量,假如查找到了就直接赋值;假如没有查到,调用setValue:forUndefineKey:并抛出反常NSUnknownKeyException

valueForKey:的完成

  • 依照getKey,key,isKey的次序查找办法,只需找到就直接调用;
  • 假如没有找到,accessInstanceVariablesDirectly回来YES(能够拜访其成员变量),依照次序顺次查找_key、_isKey、key、isKey 这四个成员变量,找到就取值;假如没有找到成员变量,调用valueforUndefineKey并抛出反常NSUnknownKeyException
  • accessInstanceVariablesDirectly回来NO(不允许直接拜访成员变量),那么会调用valueforUndefineKey:办法,并抛出反常NSUnknownKeyException

多线程

进程和线程的差异

  • 进程:进程是指在体系中正在运转的一个应用程序,一个进程拥有多个线程。
  • 线程:线程是进程中的一个单位,一个进程想要履行使命, 有必要至少有一条线程。应程序发动默许敞开主线程。

进程都有什么状况

  • Not Running:未运转。
  • Inactive:前台非活动状况。处于前台,不能承受事情处理。
  • Active:前台活动状况。处于前台,能承受事情处理。
  • Background:后台状况。处于后台,假如有后台使命会继续履行代码,履行完后挂起程序。
  • Suspended:挂起状况。处于后台,不能履行代码,假如内存不足程序会被杀死。

什么是线程安全?

  • 多条线程一起拜访一段代码,不会构成数据紊乱的状况

怎样确保线程安全?

  • 经过线程加锁
  • pthread_mutex 互斥锁(C言语)
  • @synchronized
  • NSLock 目标锁
  • NSRecursiveLock 递归锁
  • NSCondition & NSConditionLock 条件锁
  • dispatch_semaphore GCD信号量完成加锁
  • OSSpinLock自旋锁(不主张运用)
  • os_unfair_lock自旋锁(IOS10今后代替OSSpinLock

你接触到的项目,哪些场景运用到了线程安全?

  • 在线列表的增员和减员,需求加锁坚持其线程安全。

iOS开发中有多少类型的线程?别离说说

  • 1、pthread
    • C言语完成的跨渠道通用的多线程API
    • 运用难度大,没有用过
  • 2、NSThread
    • OC面向目标的多线程API
    • 简略易用,能够直接操作线程目标。
    • 需求手动办理生命周期
  • 3、GCD
    • C言语完成的多核并行CPU计划,能更合理的运转多核CPU
    • 能够主动办理生命周期
  • 4、NSOperation
    • OC根据GCD的封装
    • 完全面向目标的多线程计划
    • 能够主动办理生命周期

GCD有什么行列,默许供给了哪些行列

  • 串行同步行列,使命按次序(串行),在当前线程履行(同步)
  • 串行异步行列,使命按次序(串行),开辟新的线程履行(异步)
  • 并行同步行列,使命按次序(无法表现并行),在当前线程履行(同步)
  • 并行异步行列,使命一起履行(并行),开辟新的线程履行(异步)
  • 默许供给了主行列和全局行列

GCD主线程 & 主行列的联系

  • 主行列使命只在主线程中被履行的
  • 主线程运转的是一个 runloop,除了主行列的使命,还有UI处理呼应处理

描述一下线程同步与异步的差异?

  • 线程同步是指当前有多个线程的话,有必要等一个线程履行完了才干履行下一个线程。
  • 线程异步指一个线程去履行,他的下一个线程不必等候他履行完就开始履行。

线程同步的办法

  • 嵌套调用
  • 线程加锁
  • GCD
    • 运用串行行列,使命都一个个按次序履行
    • 运用dispatch_semaphore信号量堵塞线程,直到使命完成再放行
    • dispatch_group也能够堵塞到一切使命完成才放行
  • NSOperationQueue
    • 设置maxConcurrentOperationCount = 1,同一时刻只有1个NSOperation被执

死锁的四个条件

  • 互斥条件,一个资源每次只能被一个线程持有
  • 恳求与坚持条件,需求非持有的资源完成使命,对已取得的资源坚持不放
  • 不掠夺条件,不能强行掠夺其他线程正在运用的资源
  • 循环等候条件,线程间构成循环等候资源联系

你遇到哪些死锁的状况

  • 串行行列,正在进行的使命A向串行行列增加一个同步使命B,会构成使命相互等候,构成死锁。
  • 优先级反转,OSSpinlock

dispatch_once完成原理

  • dispatch_once需求传入dispatch_once_t类型的参数,其实是个长整形
  • 处理block前会判别传入的dispatch_once_t是否为0,为0表明block 未履行
  • 履行后把token的值改为1,下次判别非0则不处理

performSelectorrunloop的联系

  • 调用 performSelecter:afterDelay:,内部会创立一个Timer并增加到当前线程的RunLoop
  • 假如当前线程Runloop没有跑起来,这个办法会失效。
  • 其他的performSelector系列办法是类似的

子线程履行 [p performSelector:@selector(func) withObject:nil afterDelay:4] 会产生什么?

  • 上面这个办法放在子线程,其实内部会创立一个NSTimer定时器。
  • 子线程不会默许敞开runloop,假如需求履行func函数得手动敞开runloop
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
       dispatch_async(queue, ^{
         // [[NSRunLoop currentRunLoop] run]; 放在上面无效
         // 只敞开runloop可是里边没有任何事情,敞开失败
         [self performSelector:@selector(test) withObject:nil afterDelay:2];
         [[NSRunLoop currentRunLoop] run];
    });
    

为什么只在主线程改写UI

  • UIKit是线程不安全的,用户操作涉及到渲染和拜访View的特点,异步操作会存在读写问题,加锁会耗费很多资源并拖慢运转速度。
  • UIApplication在主线程初始化,用户交互都在主线程传递,所以view在主线程上呼应事情最好。

一个行列负责刺进数据操作,一个行列负责读取操作,一起操作一个存储的行列,怎么确保顺利进行

  • 运用GCD栅门函数完成多度单写
  • 读取的时分运用 dispatch_sync 马上回来数据
  • 写入的时分运用 dispatch_barrier_async 堵塞其他操作后写入
  • 注意尽量不要运用全局行列,由于全局行列里还有其他操作

为什么需求锁?

  • 多线程编程会呈现线程相互干扰的状况,如多个线程拜访一个资源。
  • 需求一些同步工具,确保线程交互是安全的。

什么是互斥锁

  • 假如同享数据现已有了其他线程加锁了,线程会进行休眠状况等候锁
  • 一旦被拜访的资源被解锁,则等候资源的线程会被唤醒。
  • 使命复杂的时刻长的状况主张运用互斥锁
  • 长处
    • 获取不到资源时线程休眠,cpu能够调度其他的线程作业
  • 缺陷
    • 存在线程调度的开销
    • 假如使命时刻很短,线程调度下降了cpu的功率

什么是自旋锁

  • 假如同享数据现已有其他线程加锁了,线程会以死循环的办法等候锁
  • 一旦被拜访的资源被解锁,则等候资源的线程会当即履行
  • 适用于持有锁较短时刻
  • 长处:
    • 自旋锁不会引起线程休眠,不会进行线程调度和CPU时刻片轮转等耗时操作。
    • 假如能在很短的时刻内取得锁,自旋锁功率远高于互斥锁。
  • 缺陷:
    • 自旋锁一向占用CPU,未取得锁的状况下处于忙等状况。
    • 假如不能在很短的时刻内取得锁,使CPU功率下降。
    • 自旋锁不能完成递归调用。

读写锁

  • 读写锁又被称为 rw锁或者 readwrite锁
  • 不是最常用的,一般是数据库操作才会用到。
  • 具体操作为多读单写,写入操作只能串行履行,且写入时不能读取;读取需支持多线程操作,且读取时不能写入

说说你知道的锁有哪些

  • pthread_mutex 互斥锁(C言语)
  • @synchronized
  • NSLock 目标锁
  • NSRecursiveLock 递归锁
  • NSCondition & NSConditionLock 条件锁
  • dispatch_semaphore GCD信号量完成加锁
  • OSSpinLock自旋锁(暂不主张运用)
  • os_unfair_lock自旋锁(IOS10今后代替OSSpinLock

说说@synchronized

  • 原理
    • 底层是链表,存储节点SyncData,内部包括下列数据
    typedef struct SyncData {
        id object; // 传进来的obj
        recursive_mutex_t mutex; // 可重入锁
        struct SyncData* nextData; // 链表指向下一个Data
        int threadCount; // 记载拜访资源的线程数量
    }
    
    • 运用obj的地址作为hash传参经过id2data办法查找`SyncData
    • 经过objc_sync_enter(obj),objc_sync_exit(obj),加锁解锁。
    • 传入的obj被开释或为nil,会履行锁的开释
  • 长处
    • 不需求创立锁目标也能完成锁的功能
    • 运用简略方便,代码可读性强
  • 缺陷
    • 加锁的代码尽量少
    • 功能没有那么好
    • 注意锁的目标有必要是同一个OC目标

说说NSLock

  • 遵从NSLocking协议
  • 注意点
    • 同一线程lockunlock需求成对呈现
    • 同一线程接连lock两次会构成死锁

说说NSRecursiveLock

  • NSRecursiveLock是递归锁
  • 注意点
    • 同一个程lock屡次而不构成死锁
    • 同一线程当lock & unlock数量共同的时分才会开释锁,其他线程才干上锁

说说NSCondition & NSConditionLock

  • 条件锁:满足条件履行锁住的代码;不满足条件就堵塞线程,直到另一个线程发出解锁信号。
  • NSCondition目标实际上是一个锁和一个线程查看器
    • 锁用于保护数据源。
    • 线程查看器根据条件判别是否堵塞线程。
    • 需求手动敞开等候和手动发送信号解除等候
    • 一个wait有必要对应一个signal,一次唤醒全部需求运用broadcast
  • NSConditionLockNSCondition的封装
    • 经过condition主动判别堵塞线程仍是唤醒线程
    • 经过不同的condition值触发不同的操作
    • 解锁时经过unlockWithCondition 修正condition完成使命依靠

说说GCD信号量完成锁

  • dispatch_semaphore_creat(0)生成一个信号量semaphore = 0( 传入的值能够操控并行使命的数量)
  • dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER) 使semaphore - 1,当值小于0进入等候
  • dispatch_semaphore_signal(semaphore)发出信号,使semaphore + 1,当值大于等于0放行

说说OSSpinLock

  • OSSpinLock是自旋锁,忙等锁
  • 自旋锁存在优先级反转的问题,线程有优先级的时分或许导致下列状况。
    • 一个优先级低的线程先拜访某个数据,此刻运用自旋锁进行了加锁。
    • 一个优先级高的线程又去拜访这个数据,优先级高的线程会一向占着CPU资源忙等拜访
    • 成果导致优先级低的线程没有CPU资源完成使命,也无法开释锁。
  • 由于自旋锁自身存在的问题,所以苹果现已抛弃了OSSpinLock

说说 os_unfair_lock

  • iOS10今后代替OSSpinLock的锁,不再忙等
  • 获取不到资源时休眠,获取到资源时由内核唤醒线程
  • 没有加强公平性和次序,开释锁的线程或许当即再次加锁,之前等候锁的线程唤醒后或许也没能加锁成功。
  • 尽管处理了优先级反转,但也构成了饥饿(starvation
  • starvation 指贪婪线程占用资源事情太长,其他线程无法拜访同享资源。

5个线程读一个文件,怎么完成最多只有2个线程一起读这个文件

  • dispatch_semaphore信号量操控

Objective-C中的原子和非原子特点

  • OC在定义特点时有nonatomicatomic两种选择
  • atomic:原子特点,为setter/getter办法都加锁(默许就是atomic),线程安全,需求耗费很多的资源
  • nonatomic:非原子特点,不加锁,非线程安全
atomic加锁原理:
property (assign, atomic) int age;
 - (void)setAge:(int)age
{ 
   @synchronized(self) { 
    _age = age;
   }
}
​
- (int)age {
  int age1 = 0;
  @synchronized(self) {
   age1 = _age;
  }
}

atomic 润饰的特点 int a,在不同的线程履行 self.a = self.a + 1 履行一万次,这个特点的值会是一万吗?

  • 不会,左边的点语法调用的是setter,右边调用的是getter,这行句子并不是原子性的。

atomic就一定能确保线程安全么?

  • 不能,只能确保settergetter在当前线程的安全
  • 一个线程在接连屡次读取某条特点值的时分,其他线程一起在改值,最终无法得出期望值
  • 一个线程在获取当前特点的值, 另外一个线程把这个特点开释调了,有或许构成溃散

nonatomic是非原子操作符,为什么用nonatomic不必atomic

  • 假如该目标无需考虑多线程的状况,请参加这个特点润饰,这样会让编译器少生成一些互斥加锁代码,能够进步功率。
  • 运用atomic,编译器会在settergetter办法里边主动生成互斥锁代码,防止该变量读写不同步。

有人说能atomic耗内存,你觉得呢?

  • 由于会主动加锁,所以功能比nonatomic差。

atomic为什么会失效

  • atomic润饰的特点靠编译器主动生成的get/set办法完成原子操作,假如重写了恣意一个,atomic关键字的特性将失效

nonatomic完成

- (NSString *)userName {
   return _userName;
}
​
- (void)setUserName:(NSString *)userName {
   _userName = userName;
}

atomic 的完成

- (NSString *)userName {
   NSString *name;
   @synchronized (self) {
     name = _userName;
   }
   return name;
}
​
- (void)setUserName:(NSString *)userName {
   @synchronized (self) {
     _userName = userName;
   }
}

runloop

runloop是什么?

  • 体系内部存在办理事情的循环机制
  • runloop 是利用这个循环,办理音讯和事情的目标。

runloop 是否等于 while(1) { do something ... }

  • 不是
  • while(1) 是一个忙等的状况,需求一向占用资源。
  • runloop 没有音讯需求处理时进入休眠状况,音讯来了,需求处理时才被唤醒。

runloop的基本模式

  • iOS中有五种runLoop模式
  • UIInitializationRunLoopMode(发动后进入的第一个Mode,发动完成后就不再运用,切换到 kCFRunLoopDefaultMode
  • kCFRunLoopDefaultMode(App的默许Mode,一般主线程是在这个 Mode 下运转)
  • UITrackingRunLoopMode(界面盯梢 Mode,用于 ScrollView 追寻触摸滑动,确保界面滑动时不受其他 Mode 影响)
  • NSRunLoopCommonModes (这是一个伪Mode,等效于NSDefaultRunLoopModeNSEventTrackingRunLoopMode的结合 )
  • GSEventReceiveRunLoopMode(承受体系事情的内部 Mode,一般用不到)

runLoop的基本原理

  • 体系中的主线程会默许敞开runloop检测事情,没有事情需求处理的时分runloop会处于休眠状况。
  • 一旦有事情触发,例如用户点击屏幕,就会唤醒runloop使进入监听状况,然后处理事情。
  • 事情处理完成后又会从头进入休眠,等候下一次事情唤醒

runloop和线程的联系

  • runloop和线程一一对应。
  • 主线程的创立的时分默许敞开runloop,为了确保程序一向在跑。
  • 支线程的runloop是懒加载的,需求手动敞开。

runloop事情处理流程

  • 事情会触发runloop的入口函数CFRunLoopRunSpecific,函数内部首先会告诉observer把状况切换成kCFRunLoopEntry,然后经过__CFRunLoopRun发动runloop处理事情
  • __CFRunLoopRun的中心是是一个do - while循环,循环内容如下
    iOS 进阶知识总结(二)

runloop是怎样被唤醒的

  • 没有音讯需求处理时,休眠线程以防止资源占用。从用户态切换到内核态,等候音讯;
  • 有音讯需求处理时,马上唤醒线程,回到用户态处理音讯;
  • source0经过屏幕触发直接唤醒
  • source0经过调用mach_msg()函数来转移当前线程的操控权给内核态/用户态。

什么是用户态、中心态

  • 内核态:运转操作体系程序 ,表明一个应用进程履行体系调用后,或I/O 中止,时钟中止后,进程便处于内核履行
  • 用户态:运转用户程序 ,表明进程正处于用户状况中履行

runloop的状况

CFRunLoopObserverRef observerRef = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
     switch (activity) {
       case kCFRunLoopEntry: NSLog(@"runloop发动"); break;
       case kCFRunLoopBeforeTimers: NSLog(@"runloop即将处理timer事情"); break;
       case kCFRunLoopBeforeSources: NSLog(@"runloop即将处理sources事情"); break;
       case kCFRunLoopBeforeWaiting: NSLog(@"runloop即将进入休眠"); break;
       case kCFRunLoopAfterWaiting: NSLog(@"runloop被唤醒"); break;
       case kCFRunLoopExit: NSLog(@"runloop退出"); break;
       default: break;
     }
   });
   CFRunLoopAddObserver(CFRunLoopGetCurrent(), observerRef, kCFRunLoopDefaultMode);
}

runLoop 卡顿检测的办法

  • NSRunLoop 处理耗时主要下面两种状况
    • kCFRunLoopBeforeSourceskCFRunLoopBeforeWaiting 之间
    • kCFRunLoopAfterWaiting 之后
  • 上述两个时刻太长,能够判定此刻主线程卡顿
    • 生成全局可拜访的信号量
    • 增加Observer到主线程Runloop中,监听Runloop状况切换
    • 子线程增加do-while循环拜访dispatch_semaphore_wait 回来值, 非0 表明卡顿
    • 获取卡顿的堆栈传至后端,再剖析

怎样发动一个常驻线程

// 创立线程
NSThread *thread = [[NSThread alloc]  initWithTarget:self selector:@selector(play) object:nil];
[thread start];
​
// runloop保活
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
  
// 处理事情
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:NO];

计时器

NSTimer、CADisplayLink、dispatch_source_t 的优劣

  • NSTimer

    • 长处在于运用的是target-action模式,简略好用
    • 缺陷是简单不小心构成循环引证。需求依靠runlooprunloop假如被堵塞就要延迟到下一次runloop周期才履行,所以时刻精度上也略为不足
  • CADisplayLink

    • 长处是精度高,每次改写完毕后都调用,合适不断重绘的计时,例如视频
    • 缺陷简单不小心构成循环引证。selector循环间隔大于重绘每帧的间隔时刻,会导致越过若干次调用机会。不能够设置单次履行。
  • dispatch_source_t

    • 根据GCD,精度高,不依靠runloop,简略好使,最喜欢的计时器
    • 需求注意的点是运用的时分有必要持有计时器,不然就会提前开释。

NSTimer在子线程履行会怎样样?

  • NSTimer在子线程调用需求手动敞开子线程的runloop
  • [[NSRunLoop currentRunLoop] run];

NSTimer为什么禁绝?

  • 假如runloop正处在堵塞状况的时分NSTimer到达触发时刻,NSTimer的触发会被推迟到下一个runloop周期

NSTimer的循环引证?

timertarget相互强引证导致了循环引证。能够经过中心件持有timer & target处理

GCD计时器

NSTimeInterval interval = 1.0;
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), interval * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(_timer, ^{
   NSLog(@"GCD timer test");
});
dispatch_resume(_timer);