关于OC 底层想聊聊

OC 作为一种经典的运行时动态语言, 底层的动态运行时、消息机制另无数iOS开发者痴迷.

Runtime:

结构模型
  1. 我们先来看下类的定义:
typedef struct objc_class *Class;

struct objc_class {
  Class  *isa;
  Class  superClass;
  const char* name;
  long  version;
  long  info;
  long  instance_size;
  struct objc_ivar_list *ivar;
  struct objc_method_list *methodLists;
  struct objc_cache *cache;
  struct objc_protocol_list *protocol;
}

2.对象的定义:

typedef struct objc_object *id;
3.对象的isa指针:
struct objc_object {
   Class isa;
}

4.方法定义:

typedef struct objc_method *Method;
// objc_method 结构体定义
struct objc_method {
SEL method_name; // 函数方法(选择器)
char* method_types ; // 参数types是一个描述传递给方法的参数类型的字符数组,这涉及到类型编码。
IMP method_imp; // 函数指针, 函数方法通过IMP 指针,找到对应的函数实现
}

5.SEL 的定义:

typedef struct objc_selector *SEL;(函数方法)
// IMP 的定义(函数指针)
typedef id(*IMP)(id, SEL,...)

6.ivar (成员变量的定义)

typedef struct objc_ivar *ivar;
struct objc_ivar {
  char* objc_ivar;
  char* ivar_type;
  int  ivar_offset;
  int space;
}

7.属性的定义

typedef struct objc_property  *objc_property_t;

8.分类的定义:

typedef struct objc_category *Category;
struct objc_category {
char* category_name;
char* class_name;
struct objc_method_list *instance_methods;
struct objc_method_list *class_methods;
struct objc_protocol_list *protocols
}

9.protocol 协议的定义:

typedef struct objc_object *Protocol;

runtime 的一张经典的关系图:


isa和superClass闭环图

为什么要设计metaClass?
Objective-C这种借鉴Smalltalk的,更注重的是消息传递,是动态响应消息。
消息传递对于面向对象的设计,其实在于给出一种对消息的解决方案。而面向对象优点之一的复用,在这种设计里,更多在于复用解决方案,而不是单纯的类本身。这种思想就如设计组件一般,关心接口,关心组合而非类本身。其实之所以有MetaClass这种设计,我的理解并不是先有MetaClass,而是在万物都是对象的Smalltalk里,向对象发送消息的基本解决方案是统一的,希望复用的。而实例和类之间用的这一套通过isa指针指向的Class单例中存储方法列表和查询方法的解决方案的流程,是应该在类上复用的,而MetaClass就顺理成章出现罢了。

class_copyIvarList()& class_copyPropertyList()区别?
1). class_copyIvarList()返回的仅仅是对象类的属性.
2). class_copyPropertyList()返回类的所有属性和变量.(包括在@interface大括号中声明的变量)

  1. class_rw_t 和 class_ro_t 的区别?
struct objc_object {
    isa_t isa;
};
struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    class_rw_t *data() { 
        return bits.data();
    }
};
struct class_rw_t {
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    Class firstSubclass;
};

class_rw_t: 读写, OC的 属性 方法 协议信息都保在class_rw_t中
class_ro_t: 只读, 在编译期class_ro_t 的结构体就已经确定不能更改.

category如何被加载的,两个category的load方法的加载顺序,两个category的同名方法的加载顺序?

1).类的初始化也是动态的,根类NSObject 的+load 和+initilize两个方法,用于类的初始化.
2). 加载顺序是父类先+load,然后子类+load,然后分类+load,那么如果分类重写子类方法:首先子类+load,将方法添加到类的方法列表methodLists,然后分类+load,将重写的方法添加到方法列表中.
4). 实际调用时,调用的是后添加的方法,即后添加的方法在方法列表methodLists的这个数组的顶部,后+load的类的方法,后添加到方法列表,而这时的添加方式又是插入顶部添加,即[methodLists insertObject:category_method atIndex:0]; 所以objc_msgSend遍历方法列表查找SEL 对应的IMP时,会先找到分类重写的那个,调用执行。然后添加到缓存列表中,这样主类方法实现永远也不会调到。
3). 类的加载顺序,决定方法的添加顺序,调用的时候,后添加的方法会先被找到,所以调用的始终是后加载的类的方法实现,类似于入栈出栈.

category & extension区别,能给NSObject添加Extension吗,结果如何?
category: 分类,有名字, 属性不实现setter getter 方法.
extension: 扩展,无名字, 可扩展 方法 属性 成员变量.

消息转发机制,消息转发机制和其他语言的消息机制优劣对比?
1).消息(传递)机制
RunTime简称运行时。就是系统在运行的时候的一些机制,其中最主要的是消息机制。
对于C语言,函数的调用在编译的时候会决定调用哪个函数。编译完成之后直接顺序执行,无任何二义性。OC的函数调用称为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪个函数(也就是说,在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错。而C语言在编译阶段就会报错)。只有在真正运行的时候才会根据函数的名称找 到对应的函数来调用。
来看 这个函数是怎么调用的[obj makeText];

首先,编译器将代码[obj makeText]转化为objc_msgSend(obj, @selector (makeText)),在objc_msgSend函数中。首先通过obj的isa指针找到obj对应的Class。在Class中先去cache中通过SEL查找对应函数method,若 cache中未找到。再去methodList中查找,若methodlist中未找到,则取superClass中查找。若能找到,则将method加入到cache中,以方便下次查找,并通过method中的函数指针跳转到对应的函数中去执行。

2).消息转发机制(可以间接实现多重继承)

在方法调用的时候,方法查询-> 动态解析-> 消息转发 之前做了什么?

IMP、SEL、Method的区别和使用场景?

+(void)load、+(void)initialize方法的区别什么?在继承关系中他们有什么区别.
+(void)load程序刚启动就会调用这个方法.
在外部初始化本类的时候,就会马上调用+(void)initialize方法.
程序启动 -> load(自动调用) -> [外部初始化本类方法] ->initialize(自动调用) ->调用本类方法

  1. 说说消息转发机制的优劣.
内存管理
  1. weak的实现原理?SideTable的结构是什么样的?
  2. 关联对象的应用?系统如何实现关联对象的?
  3. 关联对象的如何进行内存管理的?关联对象如何实现weak属性?
  4. Autoreleasepool的原理?所使用的的数据结构是什么?
  5. ARC的实现原理?ARC下对retain & release做了哪些优化?
  6. ARC下哪些情况会造成内存泄漏?
其他
  1. Method Swizzle注意事项?
  2. 属性修饰符atomic的内部实现是怎么样的?能保证线程安全吗?
-(void)setName:(NSString *)name {
@synchronizaed(self) {
if(_name = name) {
[_name release];
[_name retain];
       }
    }
}
-(*NSString)name {
@synchronizaed(self) {
return _name;
   }
}

原子属性只能保证内部set get 的完整性不受外部影响,但是不能保证线程的绝对安全, 线程安全还需要线程锁.

  1. iOS 中内省的几个方法有哪些?内部实现原理是什么?
    内省是(对象)在(运行时)获取其(类型)的能力;
-(BOOL)isKindOfClass:
-(BOOL)isMemberOfClass:
-(BOOL)respondsToSelector:
+(BOOL)instanceRespondsToSelector:
  1. class、objc_getClass、object_getclass 方法有什么区别?

NSNotification相关

苹果并没有开源相关代码,但是可以读下GNUStep的源码,基本上实现方式很具有参考性

  1. 实现原理(结构设计、通知如何存储的、name&observer&SEL之间的关系等)
  2. 通知的发送时同步的,还是异步的
  3. NSNotificationCenter接受消息和发送消息是在一个线程里吗?如何异步发送消息
  4. NSNotificationQueue是异步还是同步发送?在哪个线程响应
  5. NSNotificationQueuerunloop的关系
  6. 如何保证通知接收的线程在主线程
  7. 页面销毁时不移除通知会崩溃吗
  8. 多次添加同一个通知会是什么结果?多次移除通知呢

Runloop & KVO

runloop
  1. app如何接收到触摸事件的?

  2. 为什么只有主线程的runloop是开启的?
    1).因为APP启动以后就开启了主线程,主线程中需要接受各种的用户操作, 如果不开启主线程的runLoop 那主线程在执行一次任务后就不是保活状态, 无法工作.
    2). 大多数的子线程创建其实只处理了一件事情,并不需要保活, 节省CPU的资源,如果需要保活,那就手动开启runloop,也保证了runloop 的灵活性.

  3. 为什么只在主线程刷新UI?
    1). 整个程序的启点UIApplication 是在主线程初始化的, 所有的用户事件都在主线程完成.
    2). 而在渲染方面由于图像的渲染需要以60帧的刷新率在屏幕上 同时更新,在非主线程异步化的情况下无法确定这个处理过程能够实现同步更新。
    3).如果Main RunLoop 中的事件需要跨线程进行传输,就会导致显示和用户事件不能同步.

  4. performSelector和runloop的关系?

1). 使用performSelector的话一定是在运行时候才能发现,如果此方法不存在就会crash.
2). 在子线程中无法使用performSelector 的afterDelay 方法,因为afterDelay的方式是使用当前线程的runloop 根据afterDelay参数创建一个Timer定时器后调用SEL, 无afterDelay的方式是直接调用SEL, 所以需要在子线程创建runloop.
// 第一种, 子线程创建runloop

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [self performSelector:@selector(delayMethod) withObject:nil afterDelay:0];
    [[NSRunLoop currentRunLoop] run];
    NSLog(@"调用方法==开始");
    sleep(5);
    NSLog(@"调用方法==结束");
});

// 第二种,其实是GCD内部已经创建好了runloop

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
        if ([self respondsToSelector:@selector(delayMethod)]) {
            [self performSelector:@selector(delayMethod) withObject:nil];
        }
});
    
NSLog(@"调用方法==开始");
sleep(5);
NSLog(@"调用方法==结束");
  1. 如何使线程保活?
    在线程中开启一个runloop
KVO

1.实现原理?
KVO 是基于强大的runtime
当一个A类的属性发生改变时, 通过runtime会创建A的派生类B,B继承于A.
通过runtime 的(isa-swapping)将A 的isa 指向B.
当B的属性发生改变后, 就会重写A的属性setter 方法,通知observer发生改变.

2.如何手动关闭KVO?

  1. 通过KVC修改属性会触发KVO么?
    会的, 修改属性,会调用属性的set 方法, 那么就会启动KVO的observer.
  2. 哪些情况下使用KVO会崩溃,怎么预防崩溃?
    KVO的添加次数与移出次数不匹配.
    被观察者提前释放.(被观察者在 dealloc 时仍然注册着 KVO)
    添加了观察者, 但是未实现监听方法.
    添加或者移出时,keyPath 为空.
  1. kvo的优缺点?
    优点:能够提供一种简单的方法实现两个对象间的同步.
    用key paths来观察属性,因此也可以观察嵌套对象
    完成了对观察对象的抽象,因为不需要额外的代码来允许观察值能够被观察.

缺点:
我们观察的属性必须使用strings来定义.
对属性重构将导致我们的观察代码不再可用.

Block
  1. block的内部实现,结构体是什么样的?
  2. block是类吗,有哪些类型?
  3. 一个int变量被 __block 修饰与否的区别?block的变量截获?
  4. block在修改NSMutableArray,需不需要添加__block?
  5. 怎么进行内存管理的?
  6. block可以用strong修饰吗?
  7. 解决循环引用时为什么要用__strong、__weak修饰?
  8. block发生copy时机?
  9. Block访问对象类型的auto变量时,在ARC和MRC下有什么区别?
多线程
  1. iOS开发中有多少类型的线程?分别对比

  2. GCD有哪些队列,默认提供哪些队列

  3. GCD有哪些方法api

  4. GCD主线程 & 主队列的关系

  5. 如何实现同步,有多少方式就说多少

  6. dispatch_once实现原理

  7. 什么情况下会死锁?
    串行队列中执行同步线程的任务.

  8. 有哪些类型的线程锁,分别介绍下作用和使用场景

  9. NSOperationQueue中的maxConcurrentOperationCount默认值

maxConcurrentOperationCount代表队列同一时间允许执行的最多的任务数。或者理解为同一时间允许执行的最多线程数。

maxConcurrentOperationCount默认为-1,代表不限制。

maxConcurrentOperationCount 必须要提前设置,如果队列中添加了操作再设置maxConcurrentOperationCount就无效了。

  1. NSTimer、CADisplayLink、dispatch_source_t 的优劣
视图&图像相关
  1. AutoLayout的原理,性能如何?
    背景:
    AutoLayout本质就是一个线性方程解析Engine.

NSLayoutConstraint,本质上是表示两个视图之间布局关系的一个线性方程,该方程可以是线性等式、也可以是线性不等式。
多个约束对象组成是一个约束集合,本质上是表示某个界面上多个视图之间布局关系的线性方程组。方程组中的多个线性方程,以数字标识的优先级进行排序(UILayoutPriority,本质上是浮点型float.


Auto Layout 基本原理
  1. UIView & CALayer的区别?

  2. 事件响应者链?
    把Event事件加入到UIApplication队列中, UIResponser ->UIApplication-> UIWindow ->UIView

  3. drawrect & layoutsubviews调用时机?

drawRect调用时机:

如果在UIView初始化时没有设置frame,会导致drawRect不被自动调用.
sizeToFit后会调用。这时候可以先用sizeToFit中计算出size,然后系统自动调用drawRect方法.
通过设置contentMode为.redraw时,那么在每次设置或更改frame的时候自动调用drawRect.
直接调用setNeedsDisplay,或者setNeedsDisplayInRect会触发drawRect.

layoutSubViews调用时机:

init初始化不会调用layoutSubviews方法.

addSubview时会调用.
改变一个UIView的frame时会调用.
(滚动一个UIScrollView导致UIView重新布局时会调用.
旋转Screen会触发父UIView上的事件.
手动调用setNeedsLayout或者layoutIfNeeded.)

  1. UI的刷新原理?
    当在操作 UI 时,比如改变了 Frame、更新了 UIView/CALayer 的层次时,或者手动调用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,这个 UIView/CALayer 就被标记为待处理,并被提交到一个全局的容器去。
    RunLoop的 Observer会监听 BeforeWaiting(即将进入休眠) 和 Exit (即将退出Loop) 事件,回调去执行一个很长的函数:
    _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()。这个函数里会遍历所有待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面。

  2. 隐式动画 & 显示动画区别?
    显式动画是指用户自己通过beginAnimations:context:和commitAnimations创建的动画。
    隐式动画是指通过UIView的animateWithDuration:animations:方法创建的动画。
    隐式动画是ios4之后引入sdk的,之前只有显式动画。从官方的介绍来看,两者并没有什么差别,甚至苹果还推荐使用隐式 动画,但是这里面有一个问题,就是使用隐式动画后,View会暂时不能接收用户的触摸、滑动等手势。
    这就造成了当一个列表滚动时,如果对其中的view使用了隐式动画,就会感觉滚动无法主动停止下来,必须等动画结束了才能停止。

  3. 什么是离屏渲染?
    GPU在当前显示缓存区之外又开辟新的缓存区,就是离屏渲染.

  4. imageName & imageWithContentsOfFile区别

  5. 多个相同的图片,会重复加载吗?

  6. 图片是什么时候解码的,如何优化?
    一般我们使用的图像是JPEG/PNG,这些图像数据不是位图,而是是经过编码压缩后的数据,需要线将它解码转成位图数据,然后才能把位图渲染到屏幕上。

当你用 UIImage 或 CGImageSource 的那几个方法创建图片时,图片数据并不会立刻解码。图片设置到 UIImageView 或者 CALayer.contents 中去,并且 CALayer 被提交到 GPU 前,CGImage 中的数据才会得到解码。这一步是发生在主线程的,并且不可避免。

图片加载的工作流

概括来说,从磁盘中加载一张图片,并将它显示到屏幕上,中间的主要工作流如下:

  1. 假设我们使用 +imageWithContentsOfFile: 方法从磁盘中加载一张图片,这个时候的图片并没有解压缩;
  2. 然后将生成的 UIImage 赋值给 UIImageView
  3. 接着一个隐式的 CATransaction 捕获到了 UIImageView 图层树的变化;
  4. 在主线程的下一个 run loop 到来时,Core Animation 提交了这个隐式的 transaction ,这个过程可能会对图片进行 copy 操作,而受图片是否字节对齐等因素的影响,这个 copy 操作可能会涉及以下部分或全部步骤:
    1. 分配内存缓冲区用于管理文件 IO 和解压缩操作;
    2. 将文件数据从磁盘读到内存中;
    3. 将压缩的图片数据解码成未压缩的位图形式,这是一个非常耗时的 CPU 操作;
    4. 最后 Core Animation 使用未压缩的位图数据渲染 UIImageView 的图层。

在上面的步骤中,我们提到了图片的解压缩是一个非常耗时的 CPU 操作,并且它默认是在主线程中执行的。那么当需要加载的图片比较多时,就会对我们应用的响应性造成严重的影响,尤其是在快速滑动的列表上,这个问题会表现得更加突出。

图像的解码
解码操作是比较耗时的,并且没有GPU硬解码,只能通过CPU,iOS默认会在主线程对图像进行解码。解码过程是一个相当复杂的任务,需要消耗非常长的时间。60FPS ≈ 0.01666s per frame = 16.7ms per frame,这意味着在主线程超过16.7ms的任务都会引起掉帧。很多库都解决了图像解码的问题,不过由于解码后的图像太大,一般不会缓存到磁盘,SDWebImage的做法是把解码操作从主线程移到子线程,让耗时的解码操作不占用主线程的时间。

对于PNG图片来说,因为文件可能更大,所以加载会比JPEG更长,但是解码会相对较快,而且Xcode会把PNG图片进行解码优化之后引入工程。JPEG图片更小,加载更快,但是解压的步骤要消耗更长的时间,因为JPEG解压算法比基于zip的PNG算法更加复杂。
当加载图片的时候,iOS通常会延迟解压图片的时间,直到加载到内存之后。因为需要在绘制之前进行解压,这就会在准备绘制图片的时候影响性能。
iOS通常会延时解压图片,等到图片在屏幕上显示的时候解压图片。解压图片是非常耗时的操作。

图像解码的核心方法CGBitmapContextCreate

CGContextRef CGBitmapContextCreate(
void * data,
size_t width,
size_t height,
size_t bitsPerComponent,
size_t bytesPerRow,
CGColorSpaceRef _Nullable space,
uint32_t bitmapInfo)

  1. 图片渲染怎么优化?
    图片的显示分为三步:加载、解码、渲染。
    通常,我们操作的只有加载,解码和渲染是由UIKit进行。

什么是解码?
以UIImageView为例。当其显示在屏幕上时,需要UIImage作为数据源。
UIImage持有的数据是未解码的压缩数据,能节省较多的内存和加快存储。
解码是一个计算量较大的任务,且需要CPU来执行;并且解码出来的图片体积与图片的宽高有关系,而与图片原来的体积无关。
其体积大小可简单描述为:宽 * 高 * 每个像素点的大小 = width * height * 4bytes。

解码原理

图像解码操作会造成什么问题?
以我们常见的UITableView和UICollectionView为例,假如我们在使用一个多图片显示的功能:
在上下滑动显示图片的过程中,我们会在cellFor的方法加载UIImage图片、赋值给UIImageView,相当于在主线程同时进行IO操作、解码操作等,会造成内存迅速增长和CPU负载瞬间提升。
并且内存的迅速增加会触发系统的内存回收机制,尝试回收其他后台进程的内存,增加CPU的工作量。如果系统无法提供足够的内存,则会先结束其他后台进程,最终无法满足的话会结束当前进程。

那么如何对这种情况进行优化 ?

优化1:降采样

在滑动显示的过程中,图片显示的宽高远比真实图片要小,我们可以采用加载缩略图的方式减少图片的占用内存。
如下图所示:


截屏2021-03-02 下午9.28.38.png

我们加载jpeg的图片,然后进行相关设置,解码后根据设置生成CGImage缩略图,最后包装成UIImage,最终传递给UIImageView渲染。
思考:这里的解码步骤为何不是上文提到的imageView.image=image时机?

func downsample(imageAt imageURL: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage {
let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL, imageSourceOptions)!
let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale
let downsampleOptions = [kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceShouldCacheImmediately: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels] as CFDictionary
let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions)!
return UIImage(cgImage: downsampledImage)
}

我的理解:正常的UIImage加载是从APP本地读取,或者从网络下载图片,此时不涉及图片内容相关的操作,并不需要解码;当图片被赋值给UIImageView时,CALayer读取图片内容进行渲染,所以需要对图片进行解码;
而上文的缩略图生成过程中,已经对图片进行解码操作,此时的UIImage只是一个CGImage的封装,所以当UIImage赋值给UIImageView时,CALayer可以直接使用CGImage所持有的图像数据。

优化2:异步处理

从用户的体验来分析,滑动的操作往往是间断性触发,在滑动的瞬间有较大的工作量,而且由于都是在主线程进行操作无法进行任务分配,CPU 2处于闲置。由此引申出两种优化手段:Prefetching(预处理)和
Background decoding/downsampling(子线程解码和降采样)。综合起来,可以在Prefetching的时候把降采样放到子线程进行处理,因为降采样过程就包括解码操作。

Prefetching回调中,把降采样的操作放到同步队列serialQueue中,处理完毕之后抛给主线程进行update操作。
需要特别注意,此处不能是并发队列,否则会造成线程爆炸,原因见总结部分。

截屏2021-03-02 下午10.05.07.png

优化3:使用Image Asset Catalogs

Apple推荐的图片资源管理工具,压缩效率更高,在iOS 12的机器上有10~20%的空间节约,并且每个版本Apple都会持续对其进行优化。
内容较多,详细可点Session

总结

应用上述的优化策略,已经能对图片加载有比较好的优化。

原文如下:
Thread Explosion(线程爆炸)
More images to decode than available CPUs(解码图像数量大于CPU数量)
GCD continues creating threads as new work is enqueued(GCD创建新线程处理新的任务)
Each thread gets less time to actually decode images(每个线程获得很少的时间解码图像)

从这个案例我们学习到如何避免图像解码的线程爆炸,但还能扩散思维:

我们分析苹果工程师的逻辑:
原因(解码任务过多)==> 过程(GCD开启更多线程) ==> 结果( 每个线程获得更少的时间)
延伸出来的问题有:
GCD是如何处理并发队列?为何会启动多个线程处理?
多少的线程数量是合适的?线程的cpu时间分配和切换代价如何?
...
举一反三,类似的问题太多。但是这样的思考稍显混乱,仍有优化的空间。

把脑海关于GCD的认知提炼出来:
1、GCD是用来处理一系列任务的同步和异步执行,队列有串行和并发两种,与线程的关系只有主线程和非主线程的区别;
2、串行队列是执行完当前的任务,才会执行下一个block任务;并行队列是多个block任务并行执行,GCD会根据任务的执行情况分配线程,原则是尽快完成所有任务;

接下来的表现是操作系统相关的知识:
1、iOS系统中进程和线程的关联,每个启动的APP都是一个进程,其中有多个线程;
2、cpu的时间是分为多个时间片,每个线程轮询执行;
3、线程切换执行有代价,但比进程切换小得多;
4、每个cpu核心在同一时刻只能执行一个线程;

至此我们可以结合操作系统和GCD的知识,猜测底层GCD的实现思路和线程爆炸情况下的表现:
主线程把多个任务block放到并发队列,GCD先启动一个线程处理解码任务,线程执行过程中遇到耗时操作时(IO等待、大量CPU计算),短时间内无法完成,为了不阻塞后续任务的执行,GCD启动新的线程处理新的任务。

集合此案例,我们能回答相关问题:
1、现在有一个很复杂的计算任务,例如是统计一个5000x5000图片中像素点的RGB颜色通道,如果用分为25个任务放到GCD并发队列,把大图切分成25个1000x1000小图分别统计,是否会速度的提升?
2、GCD的串行队列和并发队列的应用场景有何不同?

  1. 如果GPU的刷新率超过了iOS屏幕60Hz刷新率是什么现象,怎么解决?
    画面撕裂
    降低GPU刷新频率为60Hz
    GPU每秒钟刷新屏幕60次,这每刷新一次就是一帧frame,每一帧大概在1/60 = 16.67ms画面最佳.

性能优化

  1. 如何做启动优化,如何监控
  2. 如何做卡顿优化,如何监控
  3. 如何做耗电优化,如何监控
  4. 如何做网络优化,如何监控

开发证书

  1. 苹果使用证书的目的是什么
  2. AppStore安装app时的认证流程
  3. 开发者怎么在debug模式下把app安装到设备呢

架构设计

典型源码的学习

  1. AFNetWorking
  2. SDWebImage
  3. JSPatch、Aspects(虽然一个不可用、另一个不维护,但是这两个库都很精炼巧妙,很适合学习)
  4. Weex/RN, 笔者认为这种前端和客户端紧密联系的库是必须要知道其原理的
  5. CTMediator、其他router库,这些都是常见的路由库,开发中基本上都会用到

架构设计

  1. 手动埋点、自动化埋点、可视化埋点

  2. MVC、MVP、MVVM设计模式

  3. 常见的设计模式
    单利 工厂 门面 观察者 通知

  4. 单例的弊端

  5. 常见的路由方案,以及优缺点对比

  6. 如果保证项目的稳定性

  7. 设计一个图片缓存框架(LRU)

  8. 如何设计一个git diff

  9. 设计一个线程池?画出你的架构图

  10. 你的app架构是什么,有什么优缺点、为什么这么做、怎么改进

其他问题

  1. PerformSelector & NSInvocation优劣对比
  2. oc怎么实现多继承?怎么面向切面(可以参考Aspects深度解析-iOS面向切面编程
  3. 哪些bug会导致崩溃,如何防护崩溃
  4. 怎么监控崩溃
  5. app的启动过程(考察LLVM编译过程、静态链接、动态链接、runtime初始化)
  6. 沙盒目录的每个文件夹划分的作用
  7. 简述下match-o文件结构

系统基础知识

  1. 进程和线程的区别?
    进程是app 启动市创建的,一个app 只能有一个进程.
    线程是在进程中的, 一个进程可以有多个线程.

  2. HTTPS的握手过程?
    client发送url 请求.
    server端收到请求,并且配置好数字证书(公钥),并返回给client.
    client验证数字证书的有效性,并生成随机数.
    client 打包数字证书 + 随机数发送给server.
    server端解析.
    server 返回信息给client.
    client解锁返回信息.
    ps: server发送的签名证书属于非对称加密, client 与server 通讯使用的是对称加密.

  3. 什么是中间人攻击?怎么预防
    就是第三方伪装成client盗走了数字签名证书.

  4. TCP的握手过程?为什么进行三次握手,四次挥手?

  5. 堆和栈区的区别?谁的占用内存空间大?

  6. 加密算法:对称加密算法和非对称加密算法区别?

  7. 常见的对称加密和非对称加密算法有哪些?
    非对称加密算法:RSA,DSA/DSS
    对称加密算法:DES,RC4,3DES

  8. MD5、Sha1、Sha256区别?

  9. charles抓包过程?不使用charles4G网络如何抓包?

数据结构与算法

LeetCode

  1. 八大排序算法
  2. 栈&队列
  3. 字符串处理
  4. 链表
  5. 二叉树相关操作
  6. 深搜广搜
  7. 基本的动态规划题、贪心算法、二分查找
    [原文章] (https://www.jianshu.com/p/e87e0be2281f)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,335评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,895评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,766评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,918评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,042评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,169评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,219评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,976评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,393评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,711评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,876评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,562评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,193评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,903评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,699评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,764评论 2 351

推荐阅读更多精彩内容