# iOS基础 # iOS面试题二

如果你不知道你接下来需要补充学习哪一部分知识,做点面试题吧。

接上文

61、NStimer准吗?谈谈你的看法?如果不准该怎样实现一个精确的NSTimer?

1.不准

2.不准的原因如下:

1、NSTimer加在main runloop中,模式是NSDefaultRunLoopMode,main负责所有主线程事件,例如UI界面的操作,复杂的运算,这样在同一个runloop中timer就会产生阻塞。

2、模式的改变。主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。

当你创建一个 Timer 并加到 DefaultMode 时,Timer 会得到重复回调,但此时滑动一个ScrollView时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 Timer 就不会被回调,并且也不会影响到滑动操作。所以就会影响到NSTimer不准的情况。

PS:DefaultMode 是 App 平时所处的状态,rackingRunLoopMode 是追踪 ScrollView 滑动时的状态。

方法一:

1、在主线程中进行NSTimer操作,但是将NSTimer实例加到main runloop的特定mode(模式)中。避免被复杂运算操作或者UI界面刷新所干扰。

self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(showTime) userInfo:nil repeats:YES];

[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

2、在子线程中进行NSTimer的操作,再在主线程中修改UI界面显示操作结果;

- (void)timerMethod2 {
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(newThread) object:nil];
[thread start];
}
- (void)newThread
{
@autoreleasepool
{
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(showTime) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] run];
}
}
总结:

一开始的时候系统就为我们将主线程的main runloop隐式的启动了。

在创建线程的时候,可以主动获取当前线程的runloop。每个子线程对应一个runloop

方法二:

使用示例

使用mach内核级的函数可以使用mach_absolute_time()获取到CPU的tickcount的计数值,可以通过”mach_timebase_info”函数获取到纳秒级的精确度 。然后使用mach_wait_until(uint64_t deadline)函数,直到指定的时间之后,就可以执行指定任务了。

关于数据结构mach_timebase_info的定义如下:

struct mach_timebase_info {uint32_t numer;uint32_t denom;};
#include
#include
static const uint64_t NANOS_PER_USEC = 1000ULL;
static const uint64_t NANOS_PER_MILLISEC = 1000ULL * NANOS_PER_USEC;
static const uint64_t NANOS_PER_SEC = 1000ULL * NANOS_PER_MILLISEC;
static mach_timebase_info_data_t timebase_info;
static uint64_t nanos_to_abs(uint64_t nanos) {
return nanos * timebase_info.denom / timebase_info.numer;
}
void example_mach_wait_until(int seconds)
{
mach_timebase_info(&timebase_info);
uint64_t time_to_wait = nanos_to_abs(seconds * NANOS_PER_SEC);
uint64_t now = mach_absolute_time();
mach_wait_until(now + time_to_wait);
}
方法三:直接使用GCD替代!
 
 cgd timer
 

self.gcdTime = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
// 开始时间支持纳秒级别
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)2 * NSEC_PER_SEC);
// 2秒执行一次
uint64_t dur = (uint64_t)(2.0 * NSEC_PER_SEC);
// 最后一个参数是允许的误差,即使设为零,系统也会有默认的误差
dispatch_source_set_timer(self.gcdTime, start, dur, 0);
// 设置回调
dispatch_source_set_event_handler(self.gcdTime, ^{
    NSLog(@"---%@---%@",[NSThread currentThread],self);
});


取消定时器:dispatch_cancel(self.gcdTimer);

62、你知道哪些设计模式,并简要叙述?

1). MVC模式:Model View Control,把模型 视图 控制器 层进行解耦合编写。
2). MVVM模式:Model View ViewModel 把模型 视图 业务逻辑 层进行解耦和编写。
3). 单例模式:通过static关键词,声明全局变量。在整个进程运行期间只会被赋值一次。
4). 观察者模式:KVO是典型的通知模式,观察某个属性的状态,状态发生变化时通知观察者。
5). 委托模式:代理+协议的组合。实现1对1的反向传值操作。
6). 工厂模式:通过一个类方法,批量的根据已有模板生产对象。

63、import 跟 #include 有什么区别,@class呢,#import<> 跟 #import”” 有什么区别?

1. #import是Objective-C导入头文件的关键字,#include是C/C++导入头文件的关键字,使用#import头文件会自动只导入一次,不会重复导入。
2. @class告诉编译器某个类的声明,当执行时,才去查看类的实现文件,可以解决头文件的相互包含。
3. #import<> 用来包含系统的头文件,#import””用来包含用户头文件。

64、tableView的重用机制能简单说一下么?

cell池
visibleCells 当前显示的cells

reusableTableCells 保存重用的cells

dequeueReusableCellWithIdentifier 获取重用cell

超出屏幕的时候 更新 reusableTableCells
reload的时候  更新 reusableTableCells

reusableTableCells为空的话  reloadRowsAtIndex 也会更新

65、写一个线程安全的单例模式 - 保证线程安全的方式

加锁和GCD栅栏,队列组相关知识

66、在手势对象基础类UIGestureRecognizer的常用子类手势类型中哪两个手势发生后,响应只会执行一次?

UITapGestureRecognizer,UISwipeGestureRecognizer是一次性手势,手势发生后,响应只会执行一次。

67、自定义库要注意些什么

抽象和封装,方便使用。

首先是对问题有充分的了解,比如构建一个文件解压压缩框架,从使用者的角度出发,只需关注发送给框架一个解压请求,框架完成复杂文件的解压操作,

并且在适当的时候通知给使用者,如解压完成、解压出错等。

在框架内部去构建对象的关系,通过抽象让其更为健壮、便于更改。其次是API的说明文档。

68、对于Objective-C,你认为它最大的优点和最大的不足是什么

最大的优点是它的运行时特性,不足是没有命名空间,对于命名冲突,可以使用长命名法或特殊前缀解决,如果是引入的第三方库之间的命名冲突,可以

对项目buildsetting里的other linker flags进行修改(第三方静态库引用者修改)

Bulding Setting里设置的other linker flags添加的有-Objc,而-Objc得作用就是将加载的静态库中的分类一并加载到程序的可执行文件,如果不添加这个参数,很有可能会出现selector not recognized问题,主要是找不到分类定义的方法。
-Objc添加后就会出现多个静态库定义同样的方法、全局变量等,然后就会出现上面的问题duplicate symbol。

69、NSOperationQueue

你用过NSOperationQueue么?如果用过或者了解的话,你为什么要使用NSOperationQueue,实现了什么?请描述它和G.C.D的区别和类似的地方(提示:可以从两者的实现机制和适用范围来描述)。

使用NSOperationQueue用来管理子类化的NSOperation对象,控制其线程并发数目。GCD和NSOperation都可以实现对线程的管理,区别是 NSOperation和NSOperationQueue是多线程的面向对象抽象。

项目中使用NSOperation的优点是NSOperation是对线程的高度抽象,在项目中使用它,会使项目的程序结构更好,子类化NSOperation的设计思路,是具有面向对象的优点(复用、封装),使得实现是多线程支持,而接口简单,建议在复杂项目中使用。

项目中使用GCD的优点是GCD本身非常简单、易用,对于不复杂的多线程操作,会节省代码量,而Block参数的使用,会是代码更为易读,建议在简单项目中使用。

70、程序内存分区

程序内存分区
以下是比较常用的五分区方式,当然也不排除网上有其他的分区方式。


栈
栈的大小在编译时就已经确定了,一般是2M;栈是一块从高到低地址的连续区域,存放临时变量和执行函数时的内存等。栈内存分配分为动态和静态,静态如自动变量(局部变量)等,动态如alloc等。

堆
堆是从低到高地址的不连续区域,类似链表;用来存放malloc或new申请的内存。

全局/静态
存放静态/全局变量;全局区细分为未初始化/初始化区。

常量
存放常量;程序中使用的常量会到常量区获取。
可以看看这个例子来理解一下。

...int a;//a在全局未初始化区int a = 10;//a在全局初始化区 10在常量区static int a = 10;//a在静态区 10在常量区//程序入口int main(...) {   int a = 10;//a在栈区 10在常量区
   static int a = 10;//a在静态区 10在常量区
   char *a = (char *)malloc(10); //a在栈区 malloc后的内存在堆区
   ...
}
代码
存放二进制代码,运行程序就是执行代码,代码要执行就要加载进内存(RAM运行内存)。

71、指针函数 / 函数指针 / Block

指针函数

C语言的概念;本质是函数,返回指针。

char *fun() {    char *p = "";    return p;
}

函数指针

C语言的概念;本质是指针,指向函数。

int fun(int a,int b) {    return a + b;
}int (*func)(int,int);
func = fun;
func(1,2);//3

Block

OC语言的概念;表示一个代码块,OC中视为对象;挺像C函数指针的。

//typedeftypedef int (^SumBlock)(int a,int b);
SumBlock sumBlock = ^(int a,int b) {    return a + b;
};
sumBlock(1,2);//3//普通
 int (^sumBlock)(int a,int b) = ^(int a,int b) {    return a + b;
};
sumBlock(1,2);//3

72、iOS类和结构体有什么区别

1:类指针赋值时只是复制了地址,结构体是复制内容;
2:类不能有同名同参数个数的方法,结构体可以;
3:结构体方法实现编译时就确定了,类方法实现可动态改变;
4:内存分配不一样,结构体在栈,类在堆;
5:结构体可以多重继承,类只能单继承。

73、线程安全方法

线程安全:多线程环境下保证数据的完整性。

队列

把操作放入队列线性执行,可用GCD和NSOperationQueue。

锁/信号量

用锁/信号量形成操作互斥。

让操作原子化

让操作原子执行,系统提供了一些原子执行的方法。

了解更多

iOS-线程安全

74、NSOperationQueue和GCD区别联系

区别

NSOperationQueue没有串行/并发队列,但可以设置最大并发数;
NSOperationQueue支持方法和block,GCD只支持block;
NSOperationQueue可以暂停/取消操作;
NSOperationQueue支持更多的功能,比如KVO和自定义操作;
NSOperationQueue可以设置操作的优先级,GCD只能设置队列的优先级。

联系

提供的功能是相似的;
NSOperationQueue是GCD的封装。

75、对程序性能的优化你有什么建议?

1.使用复用机制
2.尽可能设置 View 为不透明
3.避免臃肿的 XIB 文件
4.不要阻塞主线程
5.图片尺寸匹配 UIImageView,避免巨大图片
6.选择合适的容器
8.View 的复用和懒加载机制
9.缓存  服务器的响应信息(response)、 图片、计算值。比如:UITableView 的 row heights。
10.关于图形绘制、减少离屏渲染(设置圆角和阴影的时候可以选用绘制的方法)
11.处理 Memory Warnings
在 AppDelegate 中实现 - [AppDelegate applicationDidReceiveMemoryWarning:] 代理方法。
在 UIViewController 中重载 didReceiveMemoryWarning 方法。
监听 UIApplicationDidReceiveMemoryWarningNotification 通知。
12.复用高开销的对象
14.优化 UITableView
通过正确的设置 reuseIdentifier 来重用 Cell。
尽量减少不必要的透明 View。
尽量避免渐变效果、图片拉伸和离屏渲染。
当不同的行的高度不一样时,尽量缓存它们的高度值。
如果 Cell 展示的内容来自网络,确保用异步加载的方式来获取数据,并且缓存服务器的 response。
使用 shadowPath 来设置阴影效果。
尽量减少 subview 的数量,对于 subview 较多并且样式多变的 Cell,可以考虑用异步绘制或重写 drawRect。
尽量优化 - [UITableView tableView:cellForRowAtIndexPath:] 方法中的处理逻辑,如果确实要做一些处理,可以考虑做一次,缓存结果。
选择合适的数据结构来承载数据,不同的数据结构对不同操作的开销是存在差异的。
对于 rowHeight、sectionFooterHeight、sectionHeaderHeight 尽量使用常量。
15.选择合适的数据存储方式
在 iOS 中可以用来进行数据持有化的方案包括:
NSUserDefaults。只适合用来存小数据。
XML、JSON、Plist 等文件。JSON 和 XML 文件的差异在「选择正确的数据格式」已经说过了。
使用 NSCoding 来存档。NSCoding 同样是对文件进行读写,所以它也会面临必须加载整个文件才能继续的问题。
使用 SQLite 数据库。可以配合 FMDB 使用。数据的相对文件来说还是好处很多的,比如可以按需取数据、不用暴力查找等等。
使用 CoreData。也是数据库技术,跟 SQLite 的性能差异比较小。但是 CoreData 是一个对象图谱模型,显得更面向对象;SQLite 就是常规的 DBMS。
16.减少应用启动时间
快速启动应用对于用户来说可以留下很好的印象。尤其是第一次使用时。
保证应用快速启动的指导原则:
尽量将启动过程中的处理分拆成各个异步处理流,比如:网络请求、数据库访问、数据解析等等。
避免臃肿的 XIB 文件,因为它们会在你的主线程中进行加载。重申:Storyboard 没这个问题,放心使用。
注意:在测试程序启动性能的时候,最好用与 Xcode 断开连接的设备进行测试。因为 watchdog 在使用 Xcode 进行调试的时候是不会启动的。
17.使用 Autorelease Pool (内存释放池)
18.imageNamed 和 imageWithContentsOfFile,imageName会缓存图片

76、NSURLConnection 和NSURLSession 的区别是 么? NSURLProtocol是做什么的?

1.下载
NSURLConnection下载文件时,先是将整个文件下载到内存,然后再写入到沙盒,如果文件比较大,就会出现内存暴涨的情况。
而使用NSURLSessionDownloadTask下载文件,会默认下载到沙盒中的tem文件中,不会出现内存暴涨的情况,但是在下载完成后会把tem中的临时文件删除,需要在初始化任务方法时,在completionHandler回调中增加保存文件的代码
2.请求方法的控制
NSURLConnection实例化对象,实例化开始,默认请求就发送(同步发送),不需要调用start方法。而cancel可以停止请求的发送,停止后不能继续访问,需要创建新的请求。
NSURLSession有三个控制方法,取消(cancel)、暂停(suspend)、继续(resume),暂停以后可以通过继续恢复当前的请求任务。
使用NSURLSession进行断点下载更加便捷.
NSURLSession的构造方法(sessionWithConfiguration:delegate:delegateQueue)中有一个NSURLSessionConfiguration类的参数可以设置配置信息,其决定了cookie,安全和高速缓存策略,最大主机连接数,资源管理,网络超时等配置。NSURLConnection不能进行这个配置,相比较与NSURLConnection依赖与一个全局的配置对象,缺乏灵活性而言,NSURLSession有很大的改进

77、如果项目开始容错处理没做?如何防止拦截潜在的崩溃?

例:

1、category给类添加方法用来替换掉原本存在潜在崩溃的方法。

2、利用runtime方法交换技术,将系统方法替换成类添加的新方法。

3、利用异常的捕获来防止程序的崩溃,并且进行相应的处理。

总结:

1、不要过分相信服务器返回的数据会永远的正确。

2、在对数据处理上,要进行容错处理,进行相应判断之后再处理数据,这是一个良好的编程习惯。

78、容错处理你们一般是注意哪些?

在团队协作开发当中,由于每个团队成员的水平不一,很难控制代码的质量,保证代码的健壮性,经常会发生由于后台返回异常数据造成app崩溃闪退的情况,为了避免这样的情况项目中做一些容错处理,显得格外重要,极大程度上降低了因为数据容错不到位产生崩溃闪退的概率。

例如:

1.字典

2.数组;

3.野指针;

4.NSNull   @"{\"value\": null}";  这种json解析出来的时候,NSNull如果去执行方法,就会 unrecognized selector

等~

79、内存泄漏可能会出现的几种原因,聊聊你的看法?

第一种可能:第三方框架不当使用;

第二种可能:block循环引用;

第三种可能:delegate循环引用;

第四种可能:NSTimer循环引用 如 和 VC的循环引用

第五种可能:非OC对象内存处理

第六种可能:地图类处理

第七种可能:大次数循环内存暴涨



追问一:非OC对象如何处理?

非OC对象,其需要手动执行释放操作例:CGImageRelease(ref),否则会造成大量的内存泄漏导致程序崩溃。

其他的对于CoreFoundation框架下的某些对象或变量需要手动释放、C语言代码中的malloc等需要对应free。

80、常用锁有以下几种:

1.@synchronized 关键字加锁

2. NSLock 对象锁

3. NSCondition

4. NSConditionLock 条件锁

5. NSRecursiveLock 递归锁

6. pthread_mutex 互斥锁(C语言)

7. dispatch_semaphore 信号量实现加锁(GCD)

8. OSSpinLock

9.pthread_rwlock

10.POSIX Conditions

11.os_unfair_lock

追问一:自旋和互斥对比?

自旋锁和互斥锁

相同点:都能保证同一时间只有一个线程访问共享资源。都能保证线程安全。

不同点:

互斥锁:如果共享数据已经有其他线程加锁了,线程会进入休眠状态等待锁。一旦被访问的资源被解锁,则等待资源的线程会被唤醒。

自旋锁:如果共享数据已经有其他线程加锁了,线程会以死循环的方式等待锁,一旦被访问的资源被解锁,则等待资源的线程会立即执行。

自旋锁的效率高于互斥锁。

使用自旋锁时要注意:

由于自旋时不释放CPU,因而持有自旋锁的线程应该尽快释放自旋锁,否则等待该自旋锁的线程会一直在哪里自旋,这就会浪费CPU时间。

持有自旋锁的线程在sleep之前应该释放自旋锁以便其他可以获得该自旋锁。内核编程中,如果持有自旋锁的代码sleep了就可能导致整个系统挂起。

使用任何锁都需要消耗系统资源(内存资源和CPU时间),这种资源消耗可以分为两类:

1.建立锁所需要的资源

2.当线程被阻塞时所需要的资源

追问二:用C/OC/C++,任选其一,实现自旋或互斥?口述即可!

cpp实现:


两种锁的加锁原理:

互斥锁:线程会从sleep(加锁)——>running(解锁),过程中有上下文的切换,cpu的抢占,信号的发送等开销。

自旋锁:线程一直是running(加锁——>解锁),死循环检测锁的标志位,机制不复杂。

81、优化你是从哪几方面着手

一、首页启动速度

启动过程中做的事情越少越好(尽可能将多个接口合并)

不在UI线程上作耗时的操作(数据的处理在子线程进行,处理完通知主线程刷新节目)

在合适的时机开始后台任务(例如在用户指引节目就可以开始准备加载的数据)

尽量减小包的大小

优化方法:

量化启动时间

启动速度模块化

辅助工具(友盟,听云,Flurry)

二、页面浏览速度

json的处理(iOS 自带的NSJSONSerialization,Jsonkit,SBJson)

数据的分页(后端数据多的话,就要分页返回,例如网易新闻,或者 微博记录)

数据压缩(大数据也可以压缩返回,减少流量,加快反应速度)

内容缓存(例如网易新闻的最新新闻列表都是要缓存到本地,从本地加载,可以缓存到内存,或者数据库,根据情况而定)

延时加载tab(比如app有5个tab,可以先加载第一个要显示的tab,其他的在显示时候加载,按需加载)

算法的优化(核心算法的优化,例如有些app 有个 联系人姓名用汉语拼音的首字母排序)

三、操作流畅度优化:

Tableview 优化(tableview cell的加载优化)

ViewController加载优化(不同view之间的跳转,可以提前准备好数据)

四、数据库的优化:

数据库设计上面的重构

查询语句的优化

分库分表(数据太多的时候,可以分不同的表或者库)

五、服务器端和客户端的交互优化:

客户端尽量减少请求

服务端尽量做多的逻辑处理

服务器端和客户端采取推拉结合的方式(可以利用一些同步机制)

通信协议的优化。(减少报文的大小)

电量使用优化(尽量不要使用后台运行)

82、怎么防止别人动态在你程序生成代码?怎么防止反编译?

(这题是听错了面试官的意思)

面试官意思是怎么防止别人反编译你的app?

1.本地数据加密

iOS应用防反编译加密技术之一:对NSUserDefaults,sqlite存储文件数据加密,保护帐号和关键信息

2.URL编码加密

iOS应用防反编译加密技术之二:对程序中出现的URL进行编码加密,防止URL被静态分析

3.网络传输数据加密

iOS应用防反编译加密技术之三:对客户端传输数据提供加密方案,有效防止通过网络接口的拦截获取数据

4.方法体,方法名高级混淆

iOS应用防反编译加密技术之四:对应用程序的方法名和方法体进行混淆,保证源码被逆向后无法解析代码

5.程序结构混排加密

iOS应用防反编译加密技术之五:对应用程序逻辑结构进行打乱混排,保证源码可读性降到最低

84、你理解的多线程?

1.可能会追问,每种多线程基于什么语言?

2.生命周期是如何管理?

3.你更倾向于哪种?追问至现在常用的两种你的看法是?

第一种:pthread

.特点:

1)一套通用的多线程API

2)适用于Unix\Linux\Windows等系统

3)跨平台\可移植

4)使用难度大

b.使用语言:c语言

c.使用频率:几乎不用

d.线程生命周期:由程序员进行管理

第二种:NSThread

a.特点:

1)使用更加面向对象

2)简单易用,可直接操作线程对象

b.使用语言:OC语言

c.使用频率:偶尔使用

d.线程生命周期:由程序员进行管理

第三种:GCD

a.特点:

1)旨在替代NSThread等线程技术

2)充分利用设备的多核(自动)

b.使用语言:C语言

c.使用频率:经常使用

d.线程生命周期:自动管理

第四种:NSOperation

a.特点:

1)基于GCD(底层是GCD)

2)比GCD多了一些更简单实用的功能

3)使用更加面向对象

b.使用语言:OC语言

c.使用频率:经常使用

d.线程生命周期:自动管理

多线程的原理

同一时间,CPU只能处理1条线程,只有1条线程在工作(执行)

多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换)

如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象

思考:如果线程非常非常多,会发生什么情况?

CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源

每条线程被调度执行的频次会降低(线程的执行效率降低)

多线程的优点

能适当提高程序的执行效率

能适当提高资源利用率(CPU、内存利用率)

多线程的缺点

开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能

线程越多,CPU在调度线程上的开销就越大

程序设计更加复杂:比如线程之间的通信、多线程的数据共享

你更倾向于哪一种?

倾向于GCD:

GCD

技术是一个轻量的,底层实现隐藏的神奇技术,我们能够通过GCD和block轻松实现多线程编程,有时候,GCD相比其他系统提供的多线程方法更加有效,当然,有时候GCD不是最佳选择,另一个多线程编程的技术

NSOprationQueue 让我们能够将后台线程以队列方式依序执行,并提供更多操作的入口,这和 GCD 的实现有些类似。

这种类似不是一个巧合,在早期,MacOX

与 iOS 的程序都普遍采用Operation

Queue来进行编写后台线程代码,而之后出现的GCD技术大体是依照前者的原则来实现的,而随着GCD的普及,在iOS 4 与 MacOS X

10.6以后,Operation Queue的底层实现都是用GCD来实现的。

那这两者直接有什么区别呢?

1.    GCD是底层的C语言构成的API,而NSOperationQueue及相关对象是Objc的对象。在GCD中,在队列中执行的是由block构成的任务,这是一个轻量级的数据结构;而Operation作为一个对象,为我们提供了更多的选择;

2.    在NSOperationQueue中,我们可以随时取消已经设定要准备执行的任务(当然,已经开始的任务就无法阻止了),而GCD没法停止已经加入queue的block(其实是有的,但需要许多复杂的代码);

3.    NSOperation能够方便地设置依赖关系,我们可以让一个Operation依赖于另一个Operation,这样的话尽管两个Operation处于同一个并行队列中,但前者会直到后者执行完毕后再执行;

4.    我们能将KVO应用在NSOperation中,可以监听一个Operation是否完成或取消,这样子能比GCD更加有效地掌控我们执行的后台任务;

5.    在NSOperation中,我们能够设置NSOperation的priority优先级,能够使同一个并行队列中的任务区分先后地执行,而在GCD中,我们只能区分不同任务队列的优先级,如果要区分block任务的优先级,也需要大量的复杂代码;

6.    我们能够对NSOperation进行继承,在这之上添加成员变量与成员方法,提高整个代码的复用度,这比简单地将block任务排入执行队列更有自由度,能够在其之上添加更多自定制的功能。

总的来说,Operation

queue

提供了更多你在编写多线程程序时需要的功能,并隐藏了许多线程调度,线程取消与线程优先级的复杂代码,为我们提供简单的API入口。从编程原则来说,一般我们需要尽可能的使用高等级、封装完美的API,在必须时才使用底层API。但是我认为当我们的需求能够以更简单的底层代码完成的时候,简洁的GCD或许是个更好的选择,而Operation

queue 为我们提供能更多的选择。

倾向于:NSOperation

NSOperation相对于GCD:

1,NSOperation拥有更多的函数可用,具体查看api。NSOperationQueue 是在GCD基础上实现的,只不过是GCD更高一层的抽象。

2,在NSOperationQueue中,可以建立各个NSOperation之间的依赖关系。

3,NSOperationQueue支持KVO。可以监测operation是否正在执行(isExecuted)、是否结束(isFinished),是否取消(isCanceld)

4,GCD 只支持FIFO 的队列,而NSOperationQueue可以调整队列的执行顺序(通过调整权重)。NSOperationQueue可以方便的管理并发、NSOperation之间的优先级。

使用NSOperation的情况:各个操作之间有依赖关系、操作需要取消暂停、并发管理、控制操作之间优先级,限制同时能执行的线程数量.让线程在某时刻停止/继续等。

使用GCD的情况:一般的需求很简单的多线程操作,用GCD都可以了,简单高效。

从编程原则来说,一般我们需要尽可能的使用高等级、封装完美的API,在必须时才使用底层API。

当需求简单,简洁的GCD或许是个更好的选择,而Operation queue 为我们提供能更多的选择。

85、SD原理以及最大支持多少个下载数?

6

86、runtime动态创建一个类,需要注意什么?

了解一些如何动态构建类

87、有一个很长字符串,你用什么算法搜索到abc的位置?

1、暴力匹配

2、KMP查找

3、后缀树

88、代码文件编译生成过程,编译和链接有什么区别,链接做了什么事情

将预处理生成的文件,经过词法分析、语法分析、语义分析以及优化后编译成若干个目标模块。可以理解为将高级语言翻译为计算机可以理解的二进制代码,即机器语言。

由链接程序将编译后形成的一组目标模块以及它们所需要的库函数链接在一起,形成一个完整的载入模型。链接主要解决模块间的相互引用问题。分为地址和空间分配,符号解析和重定位几个步骤。

在编译阶段生成目标文件时,会暂时搁置那些外部引用,而这些外部引用就是在链接时进行确定的,链接器在链接时,会根据符号名称去相应模块中寻找对应符号。待符号确定之后,链接器会重写之前那些未确定的符号的地址,这个过程就是重定位。链接一般分为静态链接、载入时动态链接以及运行时动态链接三种。

89、用C语言实现一个通知流程,说思路即可!


90、A B 线程执行到一半去执行C线程,用OC和C各自怎么实现!

wait notify


dispatch_wait

dispatch_notify

91、快排的原理

选定一个基准数,左右根据基准数进行调控,小的往基准数左边放,小的往右边放,递归执行

92、C语言中strlen和sizeof的区别

sizeof是求数据类型所占的空间大小,而strlen是求字符串的长度

93、推送的原理

1、 注册:为应用程序申请消息推送服务。此时你的设备会向APNs服务器发送注册请求。
2、 APNs服务器接受请求,并将deviceToken返给你设备上的应用程序
3、客户端应用程序将deviceToken发送给后台服务器程序,后台接收并储存。
4、 后台服务器向APNs服务器发送推送消息
5、 APNs服务器将消息发给deviceToken对应设备上的应用程序

94、一个页面可以由几个控制器共同管理吗


95、项目上线或者版本迭代,有过被拒吗?是什么原因?怎么解决?


99、请说明并比较以下关键词:atomic, nonatomic

atomic修饰的对象会保证setter和getter的完整性,任何线程对其访问都可以得到一个完整的初始化后的对象。

因为要保证操作完成,所以速度慢。它比nonatomic安全,但也并不是绝对的线程安全,例如多个线程同时调用set和get就会导致获得的对象值不一样。绝对的线程安全就要用关键词synchronized。

atomic只是get set线程安全,不代表就一定线程安全了

nonatomic修饰的对象不保证setter和getter的完整性,所以多个线程对它进行访问,它可能会返回未初始化的对象。正因为如此,它比atomic快,但也是线程不安全的。

100、你平时做过什么有技术难点的东西,然后怎么解决的

异步绘制的问题

性能优化

项目优化

代码优化

自动化打包

地图、视频


写一套对应的VC内存泄露检查机制,控件子线程调用的检查机制

101、聊一聊你之前公司的项目


MVC架构,分为,基础库(基础组件、网络请求、基础category)层、中间层(路由层、网络扩展层)、业务层、展示层

除开独立性高的业务,其他的业务层和展示层在主工程,基础层和中间层在pod资源中


Swift、OC混编项目,需要兼顾Swift和OC带来的编译问题,加入Swift后,新业务都使用Swift开发,尽量少耦合老代码。

使用pod为项目管理工具、fastlane为自动打包工具

分发系统式 基于ruby和bash脚本写的内部自动化打包分发系统

102、Swift和OC混编遇到了什么问题

1、编译速度问题 5分钟变为 10分钟

2、经常丢失断点、丢失提示、llbd打印信息错误等

这种情况,请仔细检查你的桥接文件:项目名-Bridging-Header,是否导入了第三方库。若导入了第三方库,则该库是否是以Cocoapods来管理的,比如AFNetWorking是通过 Cocoapods 管理的,那么在桥接文件中,你应该

@import AFNetWorking;

而不是 import "AFNetWorking.h",或者以这种#import导入该三方的其他文件


3、命名空间的问题

103、Swift 引入后 编译优化的一些思考

1. 尽可能的移除pch中的文件、XX-Swift.h文件千万不要图方便放到pch文件中,不然每次编译都需要全局编译

2. 尽可能的减少Objective-C与Swift的混编,减小bridge文件的大小、通过模块化,实现OC与Swift之间的隔离 通过路由的方式进行模块通信,降低耦合度,路由中间件也可以减少业务中头文件的频繁交叉、繁复引用,降低耦合性

3. 模块头文件引用以@import or import <> 库引用的方式

4. 通过中间件减少业务中头文件的交叉、繁复引用,降低耦合性

5. 通过将第三方库、基础组件二进制化减少编译时间,把很少改动或者基本不会改的库编译成二进制framework

6. 组件化的时候注意组件之间的依赖关系

- 业务组件尽可能不依赖业务组件,如果依赖关系过强,就需要考虑是否业务拆分有问题
- 业务组件只依赖基础组件与第三方库
- 基础组件不依赖业务组件
- 基础组件尽量不依赖基础组件 

104、项目优化的一些思考

项目的优化分为编译优化 和 运行优化


编译优化指的是 优化方向是优化我们的开发过程,例如编译速度,代码效率,迭代效率,扩展性等

运行优化指的是 优化方向是app的运行,例如,列表流畅度、交互体验、网络延时等等


编译优化:

Swift编译速度慢的原因:

1、本身方法实现耗时太多:在xcode中开启编译时长检查,方法编译时间超出设置值就会有黄色警告

2、过多的编译器类型检查:例如过多的optation 类型

3、头文件的引用问题,尽可能的移除pch中的文件、XX-Swift.h文件千万不要图方便放到pch文件中,不然每次编译都需要全局编译

4、swift工程引用OC pod库  不需要通过桥文件 直接import对应的库名称,也尽量少在bridge文件中对OC库的头文件进行引用,影响Xcode编译效率,同时会导致lldb调试问题和断点问题

5、 pod库太多:把一些稳定的 不需要变更的库,打成二进制包


懒加载的使用

105、iOS系统框架介绍

image

107、如何拦截AFNetworking,我希望在请求发出去之前添加一些头部信息

runtime覆盖request方法,但是很蠢

108、日志上报,错误日志上报,业界有哪些方法 案例

exception catch

信号异常 catch

109、一个app进入后台之后如何唤起

app没死可以通过通知

app已死可以通过VoIP

110、如果没有API 没有百度,你要怎么解决一个问题,有实际处理过类似情况么,比如把一个1M的文件尽可能的压缩


110、100个随机数字,想要找到最大值,时间复杂度是?

O(n)

111、数据库都有哪些类型

112、iOS虚拟内存的使用

115、什么是ABI?

应用程序二进制接口,应用程序与操作系统之间,一个应用和它的库之间或者应用的组成部分之间的低接口。

API 表示的时源代码和库之间的接口,ABI允许编译好的目标代码在使用贱人ABI的系统中无需改动就能运行

116、REST、HTTP、JSON是什么?

RESTful api Representational State Transfer 资源表现层状态转化架构

(1)每一个URI代表一种资源;
(2)客户端和服务器之间,传递这种资源的某种表现层;
(3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。

117、delegate解决了什么问题,Notification与它有什么不同?

delegate  一对一的通信原理,完成消息回调

Notification 是一对多,与对象之间无需建立直接关系

118、LLVM与Clang的区别?

Clang 是编译器前端

Clang 的作用是 语法、语义分析器,生成中间代码

LLVM是编译器后端

LLVM的作用是代码优化器和后端生成目标程序

从宏观上来说,LLVM包含了Clang

119、Class、objc的区别是什么?

objc为实例对象,表示的时通过类构建的一个实例本身,实例对象是一个objc_object 类型的结构体,包含一个 Class类型的isa属性 用于表明其所属的类

Class为类对象,表示的是类本身

120、不通过继承,代码复用(共享)的方式有哪些

protocol 协议

extension 扩展 

runtime

122、在一个app中间有一个button,在你手触摸屏幕点击后,到这个button收到点击事件,中间发生了什么

响应链大概有以下几个步骤

1. 设备将touch到的UITouch和UIEvent对象打包, 放到当前活动的Application的事件队列中
2. 单例的UIApplication会从事件队列中取出触摸事件并传递给单例UIWindow
3. UIWindow使用hitTest:withEvent:方法查找touch操作的所在的视图view

RunLoop这边我大概讲一下

1. 主线程的RunLoop被唤醒
2. 通知Observer,处理Timer和Source 0
3. Springboard接受touch event之后转给App进程中
4. RunLoop处理Source 1,Source1 就会触发回调,并调用_UIApplicationHandleEventQueue() 进行应用内部的分发。
5. RunLoop处理完毕进入睡眠,此前会释放旧的autorelease pool并新建一个autorelease pool

123、main()之前的过程有哪些?

1、系统先读取App的可执行文件(Mach-O文件),获取到dyld的路径,并加载dyld(动态库链接程序)。

2、dyld去初始化运行环境、开启缓存策略(冷热启动)、加载依赖库(读取文件、验证、注册到系统核心)、我们的可执行文件、链接依赖库,并调用每个依赖库的初始化方法。

3、在上一步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会缓存上一次把信息加载内存的缓存,所以第二次比第一次启动快一点

124、block为什么防止引用循环为什么要外部weak 内部 strong

因为block截获self之后self属于block结构体中的一个由__strong修饰的属性会强引用self, 所以需要使用__weak修饰的weakSelf防止循环引用。
block使用的__strong修饰的weakSelf是为了在block(可以理解为函数)生命周期中self不会提前释放。strongSelf实质是一个局部变量(在block这个“函数”里面的局部变量),当block执行完毕就会释放自动变量strongSelf,不会对self进行一直进行强引用。

125、iOS 中内省的几个方法?class方法和objc_getClass方法有什么区别?

内省方法
判断对象类型:
-(BOOL) isKindOfClass: 判断是否是这个类或者这个类的子类的实例
-(BOOL) isMemberOfClass: 判断是否是这个类的实例
判断对象or类是否有这个方法
-(BOOL) respondsToSelector: 判读实例是否有这样方法
+(BOOL) instancesRespondToSelector: 判断类是否有这个方法

object_getClass(obj)返回的是obj中的isa指针;而[obj class]则分两种情况:一是当obj为实例对象时,[obj  class]中class是实例方法:- (Class)class,返回的obj对象中的isa指针;二是当obj为类对象(包括元类和根类以及根元类)时,调用的是类方法:+ (Class)class,返回的结果为其本身。

126、在运行时创建类的方法objc_allocateClassPair的方法名尾部为什么是pair(成对的意思)?

另一半就是meta-class

127、一个int变量被__block修饰与否的区别?

没有修饰,被block捕获,是值拷贝。
使用__block修饰,会生成一个结构体,复制int的引用地址。达到修改数据。

128、如何取消系统默认的KVO并手动触发(给KVO的触发设定条件:改变的值符合某个条件时再触发KVO)?

前面我们已经领教了KVO的实现,那如何取消系统的自动KVO呢

实现NSObject的方法,过滤我们需要过滤的key
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    if ([key isEqualToString:@"name"]) {
        return NO;
    }
    return [super automaticallyNotifiesObserversForKey:key];
}

实现自己的KVO逻辑
- (void)setName:(NSString *)name {
    [self willChangeValueForKey:@"name"];
    _name = name;
    [self didChangeValueForKey:@"name"];
}

130、什么是中间人攻击

攻击者在请求和响应传输途中,拦截并篡改内容。

SSL 证书欺骗攻击流程大概如下:

截获客户端与服务器通信的通道
然后在 SSL 建立连接的时候,进行中间人攻击
将自己伪装成客户端,获取到服务器真实有效的 CA 证书(非对称加密的公钥)
将自己伪装成服务器,获取到客服端的之后通信的密钥(对称加密的密钥)
有了证书和密钥就可以监听之后通信的内容了

133、了解内联函数么?

OC中使用inline,主要是为了提高函数调用的效率
使用例子:
static inline NSString * imageURLKeyForState(UIControlState state) {
    return [NSString stringWithFormat:@"image_%lu", (unsigned long)state];
}


我们通常会发现,inline 会有 static来修饰,表示在当前文件中应用,如 static b, 在其它文件中也可以出现static b.不会导致重名的错误.
inline和函数
1.inline函数避免了普通函数的,在汇编时必须调用call的缺点:取消了函数的参数压栈,减少了调用的开销,提高效率.所以执行速度确比一般函数的执行速度要快.
2.集成了宏的优点,使用时直接用代码替换(像宏一样)
inline和宏
1.避免了宏的缺点:需要预编译.因为inline内联函数也是函数,不需要预编译.
2.编译器在调用一个内联函数时,会首先检查它的参数的类型,保证调用正确。然后进行一系列的相关检查,就像对待任何一个真正的函数一样。这样就消除了它的隐患和局限性。
3.可以使用所在类的保护成员及私有成员。
inline 说明
1.内联函数只是我们向编译器提供的申请,编译器不一定采取inline形式调用函数.
2.内联函数不能承载大量的代码.如果内联函数的函数体过大,编译器会自动放弃内联.
3.内联函数内不允许使用循环语句或开关语句
4.内联函数的定义须在调用之前。

135、iOS SDK中的那些设计模式的使用


KVO 观察者模式

单例模式 NSFileManager 、 UIApplication

代理委托模式:UITableViewDelegate

适配器模式:类似中间件的构建,当接入SDK的时候,适配器模式可以起到隔离并适配的作用

装饰模式: 比如 category

抽象工厂模式:让外部无需知道工厂内部的任何变动

137、链表和数组的区别是什么?插入和查询的时间复杂度分别是多少?

数组是一段连续的内存地址,链表是无序的,根据指针来指向下一个内存地址

数组查询指定位置的元素很方便,但是插入和删除需要移动,不如链表方便,查找O(1) 添加删除 O(n)

链表的插入删除元素相对数组较为简单,不需要移动元素,且较为容易实现长度扩充,但是寻找某个元素较为困难; 查找O(n), 添加删除O(1)

138、哈希表是如何实现的?如何解决地址冲突?

散列表(Hash table,也叫哈希表),是根据关键码值(Key-Value)而直接进行访问的数据结构。


通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。查询时间复杂度O(1)


地址冲突处理:

地址冲突是什么? 
实际上是由数组和链表的方式组合实现的。我们哈希出来的key会放置在数组的某个位置,这个时候如果有其他元素哈希结果也是这个位置的时候,且两个key并不一样。这就是地址冲突。

1、再哈希,重新进行一次全部的哈希,常见于需要扩容的时候,翻倍扩容并重新哈希

2、当key相同的时候,会再数组后面链接一个链表并把值存入

136、面向对象的设计原则

面向对象设计的六个设计原则:

缩写 英文名称 中文名称
SRP Single Responsibility Principle 单一职责原则
OCP Open Close Principle 开闭原则
LSP Liskov Substitution Principle 里氏替换原则
LoD Law of Demeter ( Least Knowledge Principle) 迪米特法则(最少知道原则)
ISP Interface Segregation Principle 接口分离原则
DIP Dependency Inversion Principle 依赖倒置原则

通常所说的SOLID(上方表格缩写的首字母,从上到下)设计原则没有包含本篇介绍的迪米特法则

141、NSCache 和 NSDictionary

如何选择存缓存数据结构

当系统资源将要耗尽时,它可以自动删减缓存。
NSCache还会先行删减“最久未使用的”(lease recently used)对象。
NSCache 并不会“拷贝”键,而是会“保留”它。NSCache对象不拷贝键的原因在于:很多时候,键都是有不支持拷贝操作的对象来充当的。因此,NSCache 不会自动拷贝键,所以说,在健不支持拷贝操作的情况下,该类用起来比字典更方便。
NSCache是线程安全的。而NSDictionary则绝不具备此优势,意思就是:在开发者自己不编写加锁代码的前提下,多个线程便可以同时访问NSCache.

142、iOS图片内存占用由什么决定

图片显示占用内存大小 = 图片的宽度 乘以 图片的高度 乘以 颜色 RGBA 占用的4个字节;

还有色域等会影响

143、索引的作用、索引的优缺点

作用优点

因为索引可以大大提高的性能。

第1通过唯一性索引可以保证数据库表中每一行数据的唯一性。

第2可以大大加快数据的检索速度。这也是创建索引最主要的原因。

缺点

第1 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。

第2 实际上索引也是一张表,该表保存了主键与索引字段.索引需要占物理空间,

144、NSMutableArray的实现原理

https://www.jianshu.com/p/3c77756a86ab

__NSArrayM 用了环形缓冲区 (circular buffer)。这个数据结构相当简单,只是比常规数组或缓冲区复杂点。环形缓冲区的内容能在到达任意一端时绕向另一端。环形缓冲区有一些非常酷的属性。尤其是,除非缓冲区满了,否则在任意一端插入或删除均不会要求移动任何内存。

__NSArrayM 从不减少它的缓冲区大小

145、编译链接

预处理、编译(词法分析、语法分析、静态分析、中间代码生成)、汇编、链接(生成 Executable 可执行文件)

146、NSProxy & NSObject

NSProxy是一个虚类,实现了NSObject协议,可以用于处理weakProxy问题

146、继续了解

大数字符串相加

合并链表

反转数组、 旋转数组

非递归二叉树

快速排序

反转二叉树

147、 Oc和Swift的消息派发的差异

动态派发都是一样的,注意 动态派发是不支持值类型的

1、4种派发机制,而不是两种(静态和动态):

1. 内联(inline) (最快)    内联是指在编译期把每一处方法调用替换为直接执行方法内部的代码,可以帮助你避免运行时方法调用的时间开销。
2. 静态派发 (Static Dispatch)
3. 函数表派发 (Virtual Dispatch)
4. 动态派发 (Dynamic Dispatch)(最慢)


OC默认支持动态派发,多态的形式为开发人员提供灵活性,例如重写,但是同样动态的查找需要运行时的开销

2、消息派发,KVO,runtime方法查找

swift 要通过 @objc 、 dynamic 桥接OC 的runtime,间接实现,可以通过SIL中间文件判断派发方式

Swift 中的动态派发和 OC 中的动态派发类似,在运行时程序会根据被调用的方法的名字去内存中的方法表中查表,找到方法的实现并执行。


3、swift的静态派发

要求方法内部代码对编译器透明,运行时不允许更改,这样编译器才可以保证我们运行的时候不需要查表可以直接跳转到方法代码执行。值类型满足静态派发的要求

148、内存碎片、 内存对齐

内存碎片产生的原因:

内部碎片:当一个进程不能完全使用分给他的固定内存区域时产生内存碎片

比如多次内存分配后释放了 2  + 1 内存,但是两个内存块不连续,所以你没法申请一个 3 的内存



处理方案,内存页,比如:每两个连续内存组成一个内存页,进程申请的最小单位为页面 多页面一起申请组合使用,但是问题就是内存页会存在资源浪费



外部碎片:某些未分配的连续内存区域太小,不能满足进程的内存分配需求而不能被利用的内存区域

149、缓存淘汰算法

FIFO  LRU(最近最少使用,最近一段时间最少被访问的数据淘汰掉)   LFU(最不经常使用、基于最近访问频率来进行淘汰)   都适用于什么情况

LRU  这个缓存算法将最近使用的条目存放到靠近缓存顶部的位置。当一个新条目被访问时,LRU将它放置到缓存的顶部。当缓存达到极限时,较早之前访问的条目将从缓存底部开始被移除。这里会使用到昂贵的算法,而且它需要记录“年龄位”来精确显示条目是何时被访问的。此外,当一个LRU缓存算法删除某个条目后,“年龄位”将随其他条目发生改变。

LFU   最不经常使用、基于最近访问频率来进行淘汰

命中率高一些

150、 iOS渲染机制

render server

https://blog.csdn.net/u011342466/article/details/50918035

151、 卡顿原理

动画中的presationLayer

152、

iOS IPC https://segmentfault.com/a/1190000002400329

153、互斥量、信号量

互斥量用于线程的互斥,信号量用于线程的同步。

这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。

互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源

154、class_ro_t与class_rw_t的区别

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() const {
        return bits.data();
    }
    
    
类结构中包含了class_data_bits_t结构,这个结构实际上上是由一个指针(class_rw_t *)和一些关于类初始化状态的的标识组成


而class_rw_t结构中又包含了一个const class_ro_t *类型的指针实现

对比变量定义发现,class_ro_t结构中与class_rw_t对应的的主要成员变量都使用了base做区分,说明class_ro_t的结构更加贴近类本身的结构,class_rw_t像是类拓展出来的.


在关于类的实现中,几乎所有引用到class_ro_t变量的地方都是使用了const关键字做修饰,更像是一个静态不愿意被外界修改的属性;而引用到class_rw_t变量就没有这样的限制.

所以可以理解为class_ro_t存储的是类在编译期就已经确定的特性,而class_rw_t则是提供在运行时进行类延展的能力.






155、- Category的实现原理,以及Category为什么只能加方法不能加成员变量。

struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods;
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
    
    protocol_list_t *protocolsForMeta(bool isMeta) {
        if (isMeta) return nullptr;
        else return protocols;
    }
};

分类的结构体中没有成员变量,所以分类是不允许添加成员变量的。分类中添加的属性,并不会生成成员变量,只会有get set 方法声明,需要自己实现。 Category可以添加属性,编译并不会报错,但是并不会自动生成成员变量及set/get方法。

成员变量是存放在实例对象中的,并且编译的那一刻就已经决定好了。而分类是在运行时才去加载的。那么我们就无法再程序运行时将分类的成员变量中添加到实例对象的结构体中。因此分类中不可以添加成员变量。

  • 分类的方法,协议,属性等是存放在categroy结构体里面的,那么他又是如何存储在类对象中的呢?

分类的实现原理是将category中的方法,属性,协议数据放在category_t结构体中,然后将结构体内的方法列表拷贝到类对象的方法列表中。(runtime方法 attachLists memmove,memcpy)

分类的对象方法存储在哪? 类方法呢?

分类中的对象方法依然是存储在类对象中的,同本类对象方法在同一个地方,调用步骤也同调用对象方法一样。如果是类方法的话,也同样是存储在元类对象中。

rw->methods.attachLists(mlists, mcount),
rw->properties.attachLists(proplists, propcount),
rw->protocols.attachLists(protolists, protocount),
  • 分类中load方法不会覆盖本类的load方法,load方法是直接方法地址调用,没有通过消息机制,本类更早调用:

call_load_methods 里面在进行的 call_class_loads() 然后再是 call_category_loads()

(*load_method)(cls, @selector(load)); 就是使用指针方式直接调用load方法,不走 objc_msgSend方法

分类和本类都实现load方法,都会调用,但是本类更早。当子类没有实现load方法时,不会去调用父类的load方法。

  • 多个分类时方法调用顺序,按编译顺序,最后编译的最先调用。

initialize就是如此,分类的initialize方法会覆盖本类的initialize方法

父类的initialize方法比子类先执行,子类没有实现initialize方法时,会调用父类的initialize方法;放子类实现initialize方法时会覆盖父类的initialize方法。

分类方法是运行时期间拷贝到类对象方法列表,isa指针找到方法列表先找到的就是分类的方法。所以分类会覆盖本类的方法。

load 源码调用路径

_objc_init   初始化

load_images

prepare_load_methods
- schedule_class_load
- add_class_load
- add_category_to_loadable_list

call_load_methods
- call_class_loads
- call_category_loads

(*load_method)(cls, **@selector**(load)); 
initialize 源码调用路径

objc-msg-arm64.s
objc_msgSend

objc-runtime-new.mm
class_getInstanceMethod
lookUpImpOrNil
lookUpImpOrForward
_class_initialize
callInitialize
objc_msgSend(cls,SEL_initialize)

class结构体:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

分类的信息最后是添加到本类信息的前面的.所以如果分类中有和本类相同的方法,会优先调用分类的方法实现

拷贝分类方法到类对象,相同的方法怎么办

156、OC向一个nil对象发送消息

objc_msgSend 第一个参数数self

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

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,042评论 1 32
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,044评论 29 470
  • OC语言基础 1.类与对象 类方法 OC的类方法只有2种:静态方法和实例方法两种 在OC中,只要方法声明在@int...
    奇异果好补阅读 4,193评论 0 11
  • iOS面试小贴士 ———————————————回答好下面的足够了------------------------...
    不言不爱阅读 1,878评论 0 7
  • 九小时 五百四十分钟 它们一样 却也不一样 时针还没浏览到全貌 分针已经转得很无聊 同一方天地中生长 却注定了各自...
    云端没有地平线阅读 148评论 0 0