1.为什么说Objective-C是一门动态的语言?

数据类型的确定由编译时推迟到运行时!objective-C 可以通过Runtime 这个运行时机制,在运行时动态的添加和修改变量、方法、类等,所以说Objective-C 是一门动态的语言。

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

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

3.frame 和 bounds 有什么不同?

frame 指的是:该view在父view坐标系统中的位置(X,Y)和大小(W,H)。(参照点是父view的坐标系统)
bounds 指的是:该view在本身坐标系统中的位置(X,Y)和(W,H)大小。(参照点是本身坐标系统,所以X Y 永远都是 0 )

4.@property (属性) 的本质是什么?

@property = ivar(实例变量)+ getter + setter(存取方法);

5.@property (属性) 中有哪些属性关键字?各个作用是什么?

属性可以拥有的特质属性分为四类:
1.原子性: nonatomic 和 atomic
2.读/写权限: readwrite(读写) 和 readonly (只读)
3.内存管理语义: assign、weak、strong 和 copy
4.方法名:getter=<name> 和 setter=<name>
5.不常用的:nonnull,null_resettable,nullable

各个属性关键字的作用:
1.nonatomic: 非原子操作,编译器生成的setter和getter方法都不会加锁,禁止多线程,提高性能。atomic: 原子操作,自动加锁(创建lock),锁定变量,确保多线程安全。虽然系统默认是atomic, 但是实际开发中一般使用nonatomic,效率高。
2.readwrite: 可读可写,会 getter方法 和 setter方法。readonly: 只读,只会生成getter方法,不会生成setter方法,不希望属性在类外改变。
3.assign: 是赋值特性,用于基本数据类型(Bool Int Float Double Struct (结构体) )。weak:弱引用, 一般用于 delegate 和 block 等引用类型。strong: 强引用,对象不易释放掉。copy: 表示拷贝特性,setter方法将传入对象复制一份,生成不可变对象。

6.weak 和 assign 有什么不同?什么情况下使用 weak 关键字?

不同点:
assign 可以用非 OC 对象(基本数据类型),而 weak 必须用于 OC 对象。
weak 表明该属性定义了一种“非拥有关系”。在属性所指的对象销毁时,属性值会自动清空(nil)。

使用 weak 关键字的情况
1.用 delegate(代理属性) 进行一对一操作的时候。
2.自身已经对它进行一次强引用,没有必要再强引用一次,也使用 weak 。

所以XIB的IBOutlet连出来的视图属性被设置成weak,因为父控件的subViews数组已经对它有一个强引用。

7.Objective-C 的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方式用继承好还是分类好?为什么?

Objective-C 的类不可以多重继承;可以实现多个接口( @protocol 协议)!Category 是类别;一般情况用分类好,用Category去重写类的方法,仅对本Category有效,不会影响到其他类与原有类的关系。

8.什么情况下使用 copy 关键字?如果改用strong关键字,可能造成什么问题?

copy 使用情景:
 1.NSString、NSArray、NSDictionary 等经常使用 copy 关键字修饰。
 2.block 也经常使用 copy 关键字。

使用 strong 的话,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性。因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作(就是把可变的赋值给不可变的)。为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份(所以使用copy)。

一句话:使用copy的目的是,防止把可变类型的对象赋值给不可变类型的对象时,可变类型对象的值发送变化会无意间篡改不可变类型对象原来的值。

9.浅拷贝(浅复制) 和 深拷贝(深复制) 的区别?

浅拷贝:只复制指向对象的指针,而不复制引用对象本身。
深拷贝:复制引用对象本身。

10.使用系统对象的 copy 与 mutableCopy 方法

数据的可变对象和不可变对象

不管是集合类对象(NSArray、NSDictionary、NSSet ...之类的对象),还是非集合类对象(NSString, NSNumber ...之类的对象),接收到 copy 和 mutableCopy 消息时,都遵循以下准则:
1.copy 返回的是不可变对象(immutableObject);如果用 copy 返回值调用可变对象(mutable)的方法就会 crash(奔溃) 。
2.mutableCopy 返回的是可变对象(mutableObject)。


数据的浅复制和深复制

在非集合类对象中,对不可变对象进行copy操作,是指针复制,mutableCopy操作是内容复制;对可变对象进行copy和mutableCopy都是内容复制。

在集合类对象中,对不可变对象进行copy操作,是指针复制,mutableCopy操作是内容复制;对可变对象进行copy 和 mutableCopy 都是内容复制。但是:集合对象的内容复制仅限于对象本身,对集合内的对象元素仍然是指针复制。(即单层内容复制)

总结一句话: 只有对不可变对象进行 copy 操作是指针复制(浅复制),其它情况都是内容复制(深复制)!

11.这个写法会出什么问题:@property (nonatomic, copy) NSMutableArray *arr;

出现的问题:添加,删除,修改数组内的元素的时候,程序会因为找不到对应的方法而崩溃。
//如:-[__NSArrayI removeObjectAtIndex:]: unrecognized selector sent to instance 0x7fcd1bc30460

原因:copy 后返回的是不可变对象(即 arr 是 NSArray 类型,NSArray 类型对象不能调用 NSMutableArray 类型对象的方法),不能对 NSArray 对象进行 添加/修改/删除 等。

当修饰可变类型的属性时,如NSMutableArray、NSMutableDictionary、NSMutableString,用strong。  
当修饰不可变类型的属性时,如NSArray、NSDictionary、NSString,用copy。
  
一般声明NSString和NSArray类型的属性时候,都不希望它会被修改,最好用copy修饰,如果用strong修饰的话,一旦它指向一个可变类型的对象的时候,那么它指向的内容是可以随意被修改的
如果是NSMutableArray类型的属性,最好用strong修饰,用copy修饰的话,返回的数组是不可变的,一旦对其进行增删改操作就会崩溃

12.如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?

若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopying 与 NSMutableCopying 协议。
具体步骤:

    1.需声明该类遵从 NSCopying 协议
    
    2.实现 NSCopying 协议的方法。
    // 该协议只有一个方法: 
    - (id)copyWithZone:(NSZone *)zone;
  
    // 注意:使用 copy 修饰符,调用的是copy方法,其实真正需要实现的是 “copyWithZone”方法。

13.@synthesize 和 @dynamic 分别有什么作用?

@synthesize : 编译器自动生成属性的 setter方法 和 getter方法 。
@dynamic : 属性的 setter 与 getter方法 由用户自己实现,不自动生成。

系统默认的就是 @synthesize

14.常见的 Objective-C 的数据类型有那些,和C的基本数据类型有什么区别?如:NSInteger和int

Objective-C 的数据类型有 NSString,NSNumber,NSArray,NSMutableArray,NSData 等等,这些都是class,创建后便是对象。而C语言的基本数据类型只是一定字节的内存空间,用于存放数值;NSInteger是基本数据类型,并不是NSNumber的子类,当然也不是NSObject的子类。NSInteger是基本数据类型Int或者Long的别名,它的区别在于,NSInteger会根据系统是32位还是64位来决定是本身是int还是long。

15.id 声明的对象有什么特性?

id 是指向任意类型的 Objcetive-C 的对象,声明的对象具有运行时的特性。

16.Objective-C 如何对内存管理的,说说你的看法和解决方法?

Objective-C的内存管理主要有三种方式 ARC(自动内存计数)、手动内存计数、内存池。
1.自动内存计数ARC:由Xcode自动在App编译阶段,在代码中添加内存管理代码。
2.手动内存计数MRC:遵循内存谁申请、谁释放;谁添加,谁释放的原则。
3.内存释放池Release Pool:把需要释放的内存统一放在一个池子中,当池子被抽干后(drain),池子中所有的内存空间也被自动释放掉。内存池的释放操作分为自动和手动。自动释放受runloop机制影响。

17.Category(类别)、 Extension(扩展)和继承的区别

区别:
1.分类(Category)有名字。类扩展(Extension)没有名字,类扩展是一种特殊的分类。
2.分类只能扩展方法(属性仅仅是声明,并没真正实现)。类扩展可以扩展属性、成员变量和方法。
3.继承可以增加,修改或者删除方法,并且可以增加属性。

18.为什么常见的delegate属性都用是 week 而不是 strong ?

是为了防止 delegate 两端产生不必要的循环引用。

@property (nonatomic, weak) id<UITableViewDelegate> delegate;

19.delegate 和 Notification 的区别?

delegate: 一对一反向传值和通知,但是需要遵守协议(@protocol)。
Notification: 一对多的反向传值和通知,需要提前注册通知,只负责消息发送出去,并不关心谁是接收者。

20.方法和选择器有何不同?

selector 只是一个方法的名字。方法却是一个组合体,包含了名字和实现。

21.你是否接触过OC中的反射机制?简单聊一下概念和使用

1.class反射
    通过类名的字符串形式实例化对象。
        Class class = NSClassFromString(@"student"); 
        Student *stu = [[class alloc] init];
    将类名变为字符串。
        Class class =[Student class];
        NSString *className = NSStringFromClass(class);  
        
2.SEL的反射
    通过方法的字符串形式实例化方法。
        SEL selector = NSSelectorFromString(@"setName");  
        [stu performSelector:selector withObject:@"Mike"];
    将方法变成字符串。
        NSStringFromSelector(@selector*(setName:));

22.什么是block?用来做什么?使用的时候应该注意什么?

block: 闭包(代码块),获取其它函数局部变量的匿名函数。
作用: 用来反向传值的。

使用注意点: 
1.在block内部使用外部指针且会造成循环引用情况下,需要用__week修饰外部指针:
    __weak typeof(self) weakSelf = self; 
2.在block内部如果调用了延时函数还使用弱指针会取不到该指针,因为已经被销毁了,需要在block内部再将弱指针重新强引用一下。
    __strong typeof(self) strongSelf = weakSelf;
3.如果需要在block内部改变外部栈区变量的话,需要在用 __block 修饰外部变量。

23.BAD_ACCESS在什么情况下出现?

访问了野指针,比如访问已经释放对象的成员变量或者发消息、死循环等。

24.如何访问并修改一个类的私有属性?

1.通过KVC获取和修改。
2.通过runtime访问并修改私有属性。

25.一个objc对象的isa的指针指向什么?有什么作用?

指向他的类对象,可以找到对象上的方法。

26.什么是懒加载?

懒加载就是只在用到的时候才去初始化。也可以理解成延时加载。
最简单的一个例子就是tableView中图片的加载显示了, 一个延时加载, 避免内存过高,一个异步加载,避免线程堵塞提高用户体验。

27.APP启动的顺序

1.先执行main()函数
2.mian()函数中执行UIApplicationMain()函数 
3.加载info.plist文件 
4.创建RunLoop()回调函数
5.创建UIApplication对象,设置其代理UIApplicationDelegate 
6.程序加载完毕后调用delegate对象的application:didFinishLaunchingWithOptions:方法
7.在上诉6.方法中创建UIWindow(窗口)和设置rootViewController(根视图控制器)
8.最终设置显示窗口(makeKeyAndVisible)
9.最后程序变成活跃状态(BecomeActive),Runloop 时刻监听各种事件的发生

28.View的加载顺序

1.initWithCoder(如果没有storyboard就会调用initWithFrame,这里两种方法视为一种)
2.awakeFromNib
3.layoutSubviews
4.drawRect

29.ViewController生命周期

按照执行顺序排列:
1.initWithCoder:通过nib文件初始化时触发。
2.awakeFromNib:nib文件被加载的时候,会发生一个awakeFromNib的消息到nib文件中的每对象。      
3.loadView:开始加载视图控制器自带的view。
4.viewDidLoad:视图控制器的view被加载完成。  
5.viewWillAppear:视图控制器的view将要显示在window上。
6.updateViewConstraints:视图控制器的view开始更新AutoLayout约束。
7.viewWillLayoutSubviews:视图控制器的view将要更新内容视图的位置。
8.viewDidLayoutSubviews:视图控制器的view已经更新视图的位置。
9.viewDidAppear:视图控制器的view已经展示到window上。 
10.viewWillDisappear:视图控制器的view将要从window上消失。
11.viewDidDisappear:视图控制器的view已经从window上消失。

30.如何对App进行性能测试

 Profile-> Instruments ->Time Profiler

31.如何优化App的性能

一.入门级
1.用ARC管理内存 (避免忘记释放内存所造成内存泄露)
2.正确的使用重用标识符 reuseIdentifier (给TableViewCell CollectionViewCell HeaderFooterView等添加重用标志)
3.尽量把view设置为完全不透明
4.避免庞大的XIB
5.不要阻塞主线程(耗时操作放在异步中执行,再回到主线程)
6.在ImageView中直接设置图片大小(运行中缩放图片是很耗费资源的,尤其是嵌套在UIScrollView中的情况下)
7.选择正确的Collection
8.打开gzip压缩

二.中级
1.重用和延迟加载(懒加载)View
2.重要数据使用Cache(缓存)
3.权衡渲染方法
4.处理内存警告
5.重用大开销的对象
6.使用Sprite Sheets
7.避免反复处理数据
8.选择正确的数据格式(一般用JSON,因为解析JSON会比XML更快一些,JSON也通常更小更便于传输)
9.正确地设定Background Images
10.减少使用Web特性
11.设定Shadow Path
12.优化你的TableView
13.选择正确的数据存储选项

三.高级
1.加速启动时间
2.使用Autorelease Pool
3.选择是否缓存图片
4.尽量避免日期格式转换

32.如何优化UITableView

1.注册重用标识符(reuseIdentifier),给UITableViewCell 和 HeaderFooterView 
2.缓存行高
3.尽量少在cellForRowAtIndexPath中设置数据,可以在willDisplayCell里进行数据的设置

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
//不要去设置cell的数据
}
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
//当cell要显示时才去设置需要显示的cell对应的数据
}
4.避免主线程阻塞(获取数据、数据处理等耗时操作,放在子线程异步处理)
5.避免在Cell中频繁的创建对象
6.减少对象的属性赋值操作(UIView的frame/bounds等属性的赋值操作,会产生比较大的CPU消耗)
7.异步绘制
8.简化视图结构,减少Subviews的数量(减少Cell中控件的数量)
9.减少离屏渲染
10.尽量使用rowHeight,sectionFooterHeight 和 sectionHeaderHeight来设定固定的高,不要请求delegate
11.尽量设置Cell中的view的颜色为完全不透明
12.尽量少设置颜色渐变,图片的缩放
13.如果cell内现实的内容来自web,使用异步加载,缓存请求结果
14.使用正确的数据结构来存储数据

33.内存泄漏和内存溢出。 如何检查内存泄漏?

内存泄漏: 申请的内存空间使用完毕之后未回收。
一次内存泄露危害可以忽略,但若一直泄漏,无论有多少内存,迟早都会被占用光,最终导致程序crash。(因此,开发中我们要尽量避免内存泄漏)

内存溢出: 程序在申请内存时,没有足够的内存空间供其使用(就是内存不够用了),导致机器重启或者程序crash。

检查内存泄露方法:
1.静态分析 analyze。
2.instruments 工具里面有个 leak 可以动态分析。

34.导致内存泄漏的原因

ARC环境下导致内存泄漏的根本原因是: 存在循环引用。导致一些内存无法释放,最终dealloc()方法无法被调用。
情况如下:
1.VC(控制器)中存在NSTimer(定时器) , 没有及时设置其为失效([timer invalidate])和置空(self.timer=nil)
2.VC(控制器)中的delegate使用了Strong修饰,没有使用Weak修饰
3.VC(控制器)中的Block被当前VC(self)持有,又在block内部调用VC中的属性或者方法。解决方法: 在block外部弱化VC(self)。

__weak typeof(self) weakSelf = self;   // block外部弱化
[self.operationQueue addOperationWithBlock:^{
    __strong typeof(weakSelf) strongSelf = weakSelf;  // 子线程中再次强引用获取self
}];

4.大次数循环,内存暴涨
for (int i = 0; i < 1000000; i++) {
    NSString *string = @"Abc";
    string = [string lowercaseString];
    string = [string stringByAppendingString:@"xyz"];
    NSLog(@"%@", string);
}
解决方法:在循环中创建自己的autoReleasePool,及时释放占用内存大的临时变量,减少内存占用峰值。
 for (int i = 0; i < 1000000; i++) {
     @autoreleasepool {
          NSString *string = @"Abc";
          string = [string lowercaseString];
          tring = [string stringByAppendingString:@"xyz"];
          NSLog(@"%@", string);
      }
  }

35.iOS中常用的数据存储方式有哪些?

1.Plist存储。 (是一种XML格式的属性列表文件,不能存储自定义对象,只能存储NSString,NSArray,NSNumber,Bool等类型,通过NSBundle获取)

2.NSUserDefaults(偏好设置存储)。不需要关心文件名,可以快速进行键值对存储,能够直接存储基本数据类型。
   // 通过key(fieldName)存储
   NSUserDefaults *defaus=[NSUserDefaults standardUserDefaults];
   [defaus setObject:value forKey:fieldName];
   [defaus synchronize];  // 即时写入 
   
   // 通过key(fieldName)获取
   NSUserDefaults *defaus=[NSUserDefaults standardUserDefaults];
   return [defaus objectForKey:fieldName];
   
3.NSKeyedArchiver 归档 (专门用来做自定义对象归档)。 需要遵守NSCoding协议中的两个方法。encodeWithCoder:(归档对象时,要存储哪些对象的哪些属性)  initWithCoder:(解析文件中的数据)

4.SQLite及FMDB(中小型数据库)。写SQL语句建库、建表、建约束...
在FMDB中,除查询以外的所有操作,都称为"更新", create、drop、insert、update、delete等,使用executeUpdate:方法执行更新。 
FMDB有三个主要的类:1.FMDataBase:代表一个单独的DataBase数据库。 2.FMResultSet:执行查询后的结果集。3.FMDataBaseQueue: 用于多线程执行多个查询或者更新,它是线程安全的。

5.CoreData (对象-关系映射,能够将OC对象转化成数据,也能够将数据库中的数据还原成OC对象。是对SQLite3的封装,更加面向对象,效率没有SQLite3高,在此数据操作期间,不需要编写任何SQL语句)。

36.iOS中的沙盒目录结构是怎样的?

沙盒结构:
1.Application:存放程序源文件,上架前经过数字签名,上架后不可修改。
2.Documents:常用目录,iCloud备份目录,存放数据。(这里不能存缓存文件,否则上架不被通过)
3.Library:
      Caches:存放体积大又不需要备份的数据。(常用的缓存路径)
      Preference:设置目录,iCloud会备份设置信息。
4.tmp:存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能。

37.你一般是怎么用Instruments的?

Instruments里面工具很多,常用:
1.Time Profiler: 性能分析
2.Leaks:检查内存,看是否有内存泄露
3.Allocations:用来检查内存,写算法的那批人也用这个来检查
4.Zombies:检查是否访问了僵尸对象,但是这个工具只能从上往下检查,不智能。

38.isKindOfClass isSubclassOfClass 和 isMemberOfClass 的区别

1.isKindOfClass

// 添加测试函数
-(void)addTestFun{

    // 父类
    TestModel *modelClass=[[TestModel alloc]init];
    
    // 子类(继承TestModel类)
    TestModelSonClass *sonClass=[[TestModelSonClass alloc]init];
    
    // 使用 isKindOfClass 判断
    BOOL isSuperClass=[modelClass isKindOfClass:[TestModel class]];
    BOOL isSonClass=[sonClass isKinkdOfClass:[TestModel class]];
    
    NSLog(@"One: %d  Two: %d",isSuperClass,isSonClass);
    
}

最终运行效果:

TestModel[87850:13575604] One: 1  Two: 1
2.isSubclassOfClass

// 添加测试函数
-(void)addTestFun{
    
    // 父类
    TestModel *modelClass=[[TestModel alloc]init];
    
    // 子类(继承TestModel类)
    TestModelSonClass *sonClass=[[TestModelSonClass alloc]init];
    
    // 使用 isSubclassOfClass 判断
    BOOL isSuperClass=[[modelClass class] isSubclassOfClass:[TestModel class]];
    BOOL isSonClass=[[sonClass class] isSubclassOfClass:[TestModel class]];
    
    NSLog(@"One: %d  Two: %d",isSuperClass,isSonClass);
    
}

最终运行效果:

TestModel[87850:13575604] One: 1  Two: 1
3.isMemberOfClass

// 添加测试函数
-(void)addTestFun{
    
    // 父类
    TestModel *modelClass=[[TestModel alloc]init];
    
    // 子类(继承TestModel类)
    TestModelSonClass *sonClass=[[TestModelSonClass alloc]init];
    
    // 使用 isMemberOfClass 判断
    BOOL isSuperClass=[modelClass isMemberOfClass:[TestModel class]];
    BOOL isSonClass=[sonClass isMemberOfClass:[TestModel class]];
    
    NSLog(@"One: %d  Two: %d",isSuperClass,isSonClass);
    
}

最终运行效果:

TestModel[87850:13575604] One: 1  Two: 0
4.最后总结

isKindOfClass 和 isSubclassOfClass 的作用是: 用来判断是否是某个类 或其 子类 的实例。
isMemberOfClass的作用是: 用来判断是否是某个类的实例(要完全匹配)。 
39.respondsToSelector 和 conformsToProtocol 的区别

respondsToSelector : 用来判断是否有以某个名字命名的方法 
conformsToProtocol : 用来判断对象是否实现了指定协议类的方法  


1.堆和栈的区别?

OC语言是C语言的超集。C语言的内存模型分为5个区:栈区、堆区、静态区、常量区、代码区。每个区存储的内容如下:

1、堆区:就是通过new、malloc、realloc分配的内存块,编译器不会负责它们的释放工作,需要用程序区释放。分配方式类似于数据结构中的链表。在iOS开发中所说的“内存泄漏”说的就是堆区的内存。

2、栈区:存放函数的参数值、局部变量等,由编译器自动分配和释放,通常在函数执行完后就释放了,其操作方式类似于数据结构中的栈。栈内存分配运算内置于CPU的指令集,效率很高,但是分配的内存量有限,比如iOS中栈区的大小是2M。

3、静态区:全局变量和静态变量(在iOS中就是用static修饰的局部变量或者是全局全局变量)的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后,由系统释放。
4、常量区:常量存储在这里,不允许修改。
5、代码区:存放函数体的二进制代码。

堆和栈的区别:

1.1、堆空间的内存是动态分配的(一般程序员分配释放),一般存放对象,并且需要手动释放内存。当然了,iOS引入了ARC(自动引用计数)之后,就不就不需要用代码管理对象的内存了。

1.2、栈空间的内存是由系统自动分配,一般存放局部变量。比如对象的地址等值,不需要程序员对这块内存进行管理,比如,函数中的局部变量的作用范围(生命周期)就是在调完这个函数之后就结束了。

2、堆空间比较大,栈空间比较小。
3、堆空间一般存放对象本身,block的copy等。栈空间中一般存储基本数据类型,对象的地址。
4、堆(数据结构):堆可以被看成是一棵树,如:堆排序。栈(数据结构):一种先进后出的数据结构。

2.什么是程序、进程、线程? 说一下进程和线程的关系以及区别?

1.1 程序(Application):
由源代码生成的可执行应用。(例如:QQ 微信)

1.2 进程:
进程: 是指在操作系统(OS)中正在运行的一个应用程序(一般一个App就一个进程)。程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行。
所以一个正在运行的程序可以看做一个进程。(例如: 正在运行的QQ就是一个进程)

1.3 线程:
线程: 程序(进程)中独立运行的代码段。(例如: 接收QQ消息的代码)
线程是进程的基本执行单元,一个进程的所有任务(操作)都是在线程中执行
进程要想执行任务,必须至少有一条线程(程序启动会默认开始一条线程,这条线程被称为主线程(UI线程))

大概说一下:
每个进程之间是独立的,每个进程都运行在其专用的且受保护的内存中(每个进程拥有独立运行所需的全部资源)。
一个程序至少包含一个进程(一般一个App就一个进程),一个进程至少包含一个线程,一个进程中的所有线程共享当前进程所拥有的资源。
进程有独立的地址空间,一个进程崩溃后,在保护模式的影响下不会对其他进程产生影响。而线程只是一个进程中的不同执行路径,线程有自己的堆栈和局部变量,线程之间没有单独的地址空间。


进程与线程的区别:
1.同一进程内的线程共享本进程的资源(如内存,I/O,CPU等),但进程之间的资源是独立的。
2.一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
3.进程切换时,消耗的资源大,效率低。所以频繁的切换时,使用线程要高于进程。
4.每个进程都有一个运行的入口,顺序执行。但是线程不能独立执行,必须依存在进程中,由应用程序提供多个线程操作。
5.线程是处理器调度(执行任务)的基本单元,但是进程不是。

3.进程的状态有哪些?

1.新建
2.就绪 : 线程对象加入线程池中等待 CPU 调度
3.运行 : CPU负责调度线程中线程的执行, 线程执行完成前, 状态可能在就绪和运行之间来回切换
4.阻塞 : 满足某个预定条件, 使用休眠或锁, 阻塞线程执行
5.死亡 : 线程执行完毕, 或者内部中止执行线程对象

4.什么是线程安全?分别有哪些锁?

线程安全: 多个线程同时访问一块资源, 容易引发数据错乱和数据安全。

1.互斥锁(加锁): 新线程访问时, 发现其他线程正在执行锁定的代码, 新线程会进入休眠
   1.1 NSLock
   1.2 pthread_mutex
   1.3 @synchronized
2.自旋锁: 忙等的锁, 新线程会用死循环的方式, 一直等待锁定的代码执行完成, 数据量少的时候用
3.条件锁: 不满足就休眠, 资源分配到了, 条件锁打开, 进程继续运行, 例如:NSConditionLock
4.读写锁: 用于解决多线程对公共资源读写问题。 读操作可以并发重入, 写操作是互斥的
5.信号量: 

5.iOS开启多线程的方法有哪些?

1.pthread  2.NSThread  3.GCD  4.NSOperation

6.简单的说一下NSThread的使用 具体使用详情

1.NSThread是基于Objective-C的,更加面向对象。
2.它可以通过三种方法开启子线程,分别是: initWithTarget:  detachNewThreadSelector:(detachNewThreadWithBlock: 至少iOS10系统) 和 performSelectorInBackground: 。 其中 initWithTarget: 需要调用 start() 方法才能开启子线程。
3.NSThread可以通过类方法 currentThread() 获取当前线程,通过类方法 mainThread()获得主线程,还可以退出线程(exit)和睡眠线程(sleepForTimeInterval)。
4.在实际开发中一般很少用其开始子线程,常用的也就[NSThread currentThread];(获取当前线程)和[NSThread sleepForTimeInterval:3.0];(睡眠线程)

7.简单的说一下GCD的使用 具体使用详情

1.GCD是基于C语言的,更加偏向于底层。
2.使用GCD就两个步骤:
   2.1 创建一个队列(串行队列或并发队列)
   2.2 将任务追加到队列中,系统就会根据任务类型执行任务(同步执行或异步执行)
3.队列的创建和获取
   3.1 队列可以通过 dispatch_queue_create 来创建,通过设置第二个参数用来识别是串行队列还是并发队列(串行队列: DISPATCH_QUEUE_SERIAL; 并发队列: DISPATCH_QUEUE_CONCURRENT),
   3.2 队列也可以通过 dispatch_get_global_queue 获取全局的并发队列 和 dispatch_get_main_queue 获取特殊的串行队列(主队列)。
4.任务都是放在Block中执行的,执行任务分为同步和异步,同步是:dispatch_sync(queue, ^{}); 异步是:dispatch_async(queue, ^{});异步(async)具备开启子线程的能力,但是在主队列中不会开启子线程。同步(sync)不具备开启子线程的能力,在主队列中会造成死锁(线程相互等待)
5.组合使用情况如下: 
   5.1 同步(sync)  + 并发队列 (没有开启子线程,串行执行任务,在主线程)
   5.2 同步(sync)  + 串行队列 (没有开启子线程,串行执行任务,在主线程)
   5.3 同步(sync)  + 主线程   (造成死锁,主线程和同步任务相互等待)
   5.4 异步(async) + 并发队列 (有开启子线程 ,并发执行任务,在子线程)
   5.5 异步(async) + 串行队列 (只开启一条子线程,串行执行任务)
   5.6 异步(async) + 主线程   (没有开启子线程,串行执行任务,在主线程)
6.GCD的其他函数(方法)的使用
   6.1 栅栏函数:dispatch_barrier_sync (同步) 和 dispatch_barrier_async (异步)
   6.2 延时执行函数:dispatch_after  一定是将任务追加到主队列中(主线程)
   6.3 一次性函数:dispatch_once
   6.4 队列组函数: dispatch_group_async  通过: dispatch_group_notify 监听
   6.5 暂停当前线程函数: dispatch_group_wait

8.简单的说一下NSOperation的使用 具体使用详情

1.NSOperation是基于GCD更高一层的封装,完全面向对象。
2.NSOperation的使用三个步骤: 
   2.1 创建操作:先将需要执行的操作封装到 NSOperation 的子类中。
   2.2 创建队列:创建 NSOperationQueue 对象。
   2.3 将操作加入到队列中:将 NSOperation 子类添加到 NSOperationQueue(队列) 对象中。
3.创建操作。在不使用NSOperationQueue的情况下,单独使用 NSOperation 的子类封装操作, 不会开启子线程(同步执行操作)。
   3.1 NSOperation的子类有 NSInvocationOperation  NSBlockOperation 和自定义继承自NSOperation(通过实现方法(main)来封装操作)。
4.创建队列。NSOperationQueue 一共有两种队列:主队列、自定义队列。其中自定义队列同时包含了串行、并发功能。
   4.1 通过 [NSOperationQueue mainQueue]; 获取主队列
   4.2 通过 [[NSOperationQueue alloc] init]; 创建自定义队列(非主队列,包含了串行、并发功能)。
5.将操作加入到队列中。NSOperation 需要配合 NSOperationQueue 来实现多线程,我们需要将创建好的操作加入到队列中去。总共有两种方法:
   5.1 通过 addOperation: 添加操作到队列中 [queueObj addOperation:operationObj];
   5.2 通过 addOperationWithBlock: 直接创建操作 [queueObj addOperationWithBlock:^{}];
6.NSOperationQueue 创建的自定义队列同时具有串行、并发功能。通过设置属性 maxConcurrentOperationCount (最大并发操作数) 的个数 决定队列类型,默认情况下为 -1,表示不进行限制,可进行并发执行;为 1 时,队列为串行队列。只能串行执行;大于 1 时,队列为并发队列。
7.NSOperation其他操作
  7.1 操作之间添加依赖 addDependency:
  7.2 操作完成时回调  completionBlock = ^{};
  7.3 取消队列的所有操作 cancelAllOperations
  7.4 判断队列是否处于暂停状态  isSuspended
  7.5 向队列中添加操作数组 addOperations
  7.6 可取消操作 cancel (实质是标记 isCancelled 状态)
  7.7 判断操作状态 isFinished(操作是否已经完成)  isCancelled(操作是否已经取消) isExecuting(操作是否正在运行)  isReady(操作是否处于准备就绪状态)

9.NSThread GCD 和 NSOperation 之间的优缺点

1.NSThread
   优点:比其他两种更加轻量级,使用简单。
   缺点:线程之间的通信比较麻烦(最大缺点)。需要自己管理线程的生命周期([thread cancel];取消子线程)、线程同步、加锁、睡眠以及唤醒等。

2.GCD 
   优点:性能最高效(更接近底层)。不需要管理线程的生命周期,数据同步的事情。
   缺点: 基于C语言实现,不好理解。添加异步操作之间的事务性,顺序性和依赖关系 需要写更多的代码来实现。
     
3.Operation:
   优点:是对GCD的封装,更加面向对象。也是不需要管理线程的生命周期,数据同步的事情。
        可以使用自定义继承 NSOperation 的类,重写 main 方法,在其里面添加操作(任务)。
        可以更好的添加操作之间依赖和查看操作的状态,还可以设置操作的优先级和更好的取消正在执行中的操作。
   缺点:开启操作一般需要用到 NSOperation的两个子类 NSInvocationOperation 和 NSBlockOperation
        开启的操作需要添加到 NSOperationQueue 中才开启子线程。

10.iOS 代码加锁的几种方式

1.@synchronized(self)
2.NSConditionLock
3.NSCondition
4.NSLock
5.dispatch_semaphore_t (GCD中的函数)
6.OSSpinLock

这几种锁都可以带来原子性,性能的损耗从上至下依次更小。

1、使用 @synchronized(self) 给代码加锁

@interface ViewController ()

@property (nonatomic,assign) NSInteger ticketNumber;

@end
  
#pragma mark -使用GCD创建线程
-(void)startGCDAction{
    
    self.ticketNumber=50;  // 50张票
    
    // 1.创建并发队列
    dispatch_queue_t queueObj=dispatch_queue_create("nameValue",DISPATCH_QUEUE_CONCURRENT);
    
    // 2.开启多条异步线程
    for (NSInteger i=1; i<=5; i++) {
        dispatch_async(queueObj, ^{
            NSLog(@"做任务(%zi),当前线程:%@",i,[NSThread currentThread]);
            [self startSaleTicket]; // 开始卖票
        });
    }
}

// 通过@synchronized()加锁
-(void)startSaleTicket{
    do {
        [NSThread sleepForTimeInterval:0.3];
        // 下面是加锁的代码内容
        @synchronized (self) {
            if (self.ticketNumber<=0) break;
            NSLog(@"余票是: %zi 当前线程是: %@",self.ticketNumber,[NSThread currentThread]);
            self.ticketNumber-=1;
        }
        
    }
    while (self.ticketNumber);
}

最终运行结果:

2019-08-12 14:15:58.189393+0800 TestModel[87804:13566450] 做任务(2),当前线程:<NSThread: 0x283a16cc0>{number = 3, name = (null)}
2019-08-12 14:15:58.189548+0800 TestModel[87804:13566452] 做任务(1),当前线程:<NSThread: 0x283a39fc0>{number = 4, name = (null)}
2019-08-12 14:15:58.189868+0800 TestModel[87804:13566451] 做任务(3),当前线程:<NSThread: 0x283a026c0>{number = 5, name = (null)}
2019-08-12 14:15:58.189917+0800 TestModel[87804:13566453] 做任务(4),当前线程:<NSThread: 0x283a02040>{number = 6, name = (null)}
2019-08-12 14:15:58.190395+0800 TestModel[87804:13566459] 做任务(5),当前线程:<NSThread: 0x283a01c80>{number = 7, name = (null)}
2019-08-12 14:15:58.490183+0800 TestModel[87804:13566450] 余票是: 50 当前线程是: <NSThread: 0x283a16cc0>{number = 3, name = (null)}
2019-08-12 14:15:58.490588+0800 TestModel[87804:13566452] 余票是: 49 当前线程是: <NSThread: 0x283a39fc0>{number = 4, name = (null)}
2019-08-12 14:15:58.495170+0800 TestModel[87804:13566451] 余票是: 48 当前线程是: <NSThread: 0x283a026c0>{number = 5, name = (null)}
2019-08-12 14:15:58.495440+0800 TestModel[87804:13566453] 余票是: 47 当前线程是: <NSThread: 0x283a02040>{number = 6, name = (null)}
2019-08-12 14:15:58.495664+0800 TestModel[87804:13566459] 余票是: 46 当前线程是: <NSThread: 0x283a01c80>{number = 7, name = (null)}
2019-08-12 14:15:58.795679+0800 TestModel[87804:13566450] 余票是: 45 当前线程是: <NSThread: 0x283a16cc0>{number = 3, name = (null)}
2019-08-12 14:15:58.796089+0800 TestModel[87804:13566452] 余票是: 44 当前线程是: <NSThread: 0x283a39fc0>{number = 4, name = (null)}
2019-08-12 14:15:58.800381+0800 TestModel[87804:13566451] 余票是: 43 当前线程是: <NSThread: 0x283a026c0>{number = 5, name = (null)}
2019-08-12 14:15:58.800643+0800 TestModel[87804:13566453] 余票是: 42 当前线程是: <NSThread: 0x283a02040>{number = 6, name = (null)}
2019-08-12 14:15:58.801037+0800 TestModel[87804:13566459] 余票是: 41 当前线程是: <NSThread: 0x283a01c80>{number = 7, name = (null)}
2019-08-12 14:15:59.097379+0800 TestModel[87804:13566450] 余票是: 40 当前线程是: <NSThread: 0x283a16cc0>{number = 3, name = (null)}
2019-08-12 14:15:59.098778+0800 TestModel[87804:13566452] 余票是: 39 当前线程是: <NSThread: 0x283a39fc0>{number = 4, name = (null)}
2019-08-12 14:15:59.104982+0800 TestModel[87804:13566451] 余票是: 38 当前线程是: <NSThread: 0x283a026c0>{number = 5, name = (null)}
2019-08-12 14:15:59.105416+0800 TestModel[87804:13566453] 余票是: 37 当前线程是: <NSThread: 0x283a02040>{number = 6, name = (null)}
2019-08-12 14:15:59.105727+0800 TestModel[87804:13566459] 余票是: 36 当前线程是: <NSThread: 0x283a01c80>{number = 7, name = (null)}
2019-08-12 14:15:59.401558+0800 TestModel[87804:13566450] 余票是: 35 当前线程是: <NSThread: 0x283a16cc0>{number = 3, name = (null)}
2019-08-12 14:15:59.404078+0800 TestModel[87804:13566452] 余票是: 34 当前线程是: <NSThread: 0x283a39fc0>{number = 4, name = (null)}
2019-08-12 14:15:59.406779+0800 TestModel[87804:13566451] 余票是: 33 当前线程是: <NSThread: 0x283a026c0>{number = 5, name = (null)}
2019-08-12 14:15:59.407160+0800 TestModel[87804:13566453] 余票是: 32 当前线程是: <NSThread: 0x283a02040>{number = 6, name = (null)}
2019-08-12 14:15:59.407764+0800 TestModel[87804:13566459] 余票是: 31 当前线程是: <NSThread: 0x283a01c80>{number = 7, name = (null)}
2019-08-12 14:15:59.706818+0800 TestModel[87804:13566450] 余票是: 30 当前线程是: <NSThread: 0x283a16cc0>{number = 3, name = (null)}
2019-08-12 14:15:59.707735+0800 TestModel[87804:13566451] 余票是: 29 当前线程是: <NSThread: 0x283a026c0>{number = 5, name = (null)}
2019-08-12 14:15:59.709405+0800 TestModel[87804:13566452] 余票是: 28 当前线程是: <NSThread: 0x283a39fc0>{number = 4, name = (null)}
2019-08-12 14:15:59.712616+0800 TestModel[87804:13566453] 余票是: 27 当前线程是: <NSThread: 0x283a02040>{number = 6, name = (null)}
2019-08-12 14:15:59.713001+0800 TestModel[87804:13566459] 余票是: 26 当前线程是: <NSThread: 0x283a01c80>{number = 7, name = (null)}
2019-08-12 14:16:00.016941+0800 TestModel[87804:13566450] 余票是: 25 当前线程是: <NSThread: 0x283a16cc0>{number = 3, name = (null)}
2019-08-12 14:16:00.017259+0800 TestModel[87804:13566451] 余票是: 24 当前线程是: <NSThread: 0x283a026c0>{number = 5, name = (null)}
2019-08-12 14:16:00.017491+0800 TestModel[87804:13566452] 余票是: 23 当前线程是: <NSThread: 0x283a39fc0>{number = 4, name = (null)}
2019-08-12 14:16:00.018511+0800 TestModel[87804:13566453] 余票是: 22 当前线程是: <NSThread: 0x283a02040>{number = 6, name = (null)}
2019-08-12 14:16:00.018640+0800 TestModel[87804:13566459] 余票是: 21 当前线程是: <NSThread: 0x283a01c80>{number = 7, name = (null)}
2019-08-12 14:16:00.317675+0800 TestModel[87804:13566450] 余票是: 20 当前线程是: <NSThread: 0x283a16cc0>{number = 3, name = (null)}
2019-08-12 14:16:00.318162+0800 TestModel[87804:13566451] 余票是: 19 当前线程是: <NSThread: 0x283a026c0>{number = 5, name = (null)}
2019-08-12 14:16:00.322806+0800 TestModel[87804:13566452] 余票是: 18 当前线程是: <NSThread: 0x283a39fc0>{number = 4, name = (null)}
2019-08-12 14:16:00.323192+0800 TestModel[87804:13566453] 余票是: 17 当前线程是: <NSThread: 0x283a02040>{number = 6, name = (null)}
2019-08-12 14:16:00.323446+0800 TestModel[87804:13566459] 余票是: 16 当前线程是: <NSThread: 0x283a01c80>{number = 7, name = (null)}
2019-08-12 14:16:00.623299+0800 TestModel[87804:13566450] 余票是: 15 当前线程是: <NSThread: 0x283a16cc0>{number = 3, name = (null)}
2019-08-12 14:16:00.623643+0800 TestModel[87804:13566451] 余票是: 14 当前线程是: <NSThread: 0x283a026c0>{number = 5, name = (null)}
2019-08-12 14:16:00.628154+0800 TestModel[87804:13566452] 余票是: 13 当前线程是: <NSThread: 0x283a39fc0>{number = 4, name = (null)}
2019-08-12 14:16:00.628523+0800 TestModel[87804:13566453] 余票是: 12 当前线程是: <NSThread: 0x283a02040>{number = 6, name = (null)}
2019-08-12 14:16:00.628825+0800 TestModel[87804:13566459] 余票是: 11 当前线程是: <NSThread: 0x283a01c80>{number = 7, name = (null)}
2019-08-12 14:16:00.928590+0800 TestModel[87804:13566450] 余票是: 10 当前线程是: <NSThread: 0x283a16cc0>{number = 3, name = (null)}
2019-08-12 14:16:00.928799+0800 TestModel[87804:13566451] 余票是: 9 当前线程是: <NSThread: 0x283a026c0>{number = 5, name = (null)}
2019-08-12 14:16:00.933344+0800 TestModel[87804:13566452] 余票是: 8 当前线程是: <NSThread: 0x283a39fc0>{number = 4, name = (null)}
2019-08-12 14:16:00.933492+0800 TestModel[87804:13566459] 余票是: 7 当前线程是: <NSThread: 0x283a01c80>{number = 7, name = (null)}
2019-08-12 14:16:00.933836+0800 TestModel[87804:13566453] 余票是: 6 当前线程是: <NSThread: 0x283a02040>{number = 6, name = (null)}
2019-08-12 14:16:01.231961+0800 TestModel[87804:13566450] 余票是: 5 当前线程是: <NSThread: 0x283a16cc0>{number = 3, name = (null)}
2019-08-12 14:16:01.232049+0800 TestModel[87804:13566451] 余票是: 4 当前线程是: <NSThread: 0x283a026c0>{number = 5, name = (null)}
2019-08-12 14:16:01.233846+0800 TestModel[87804:13566452] 余票是: 3 当前线程是: <NSThread: 0x283a39fc0>{number = 4, name = (null)}
2019-08-12 14:16:01.234564+0800 TestModel[87804:13566453] 余票是: 2 当前线程是: <NSThread: 0x283a02040>{number = 6, name = (null)}
2019-08-12 14:16:01.238114+0800 TestModel[87804:13566459] 余票是: 1 当前线程是: <NSThread: 0x283a01c80>{number = 7, name = (null)}

2、使用 NSLock 给代码加锁

@interface ViewController ()

@property (nonatomic,strong) NSLock *addLock;

@end

-(NSLock *)addLock{
    if (_addLock == nil ) {
        _addLock = [[NSLock alloc]init];
    }
    return _addLock;
}
 
#pragma mark -使用GCD创建线程
-(void)startGCDAction{
    
    self.ticketNumber=50;  // 50张票
    
    // 1.创建并发队列
    dispatch_queue_t queueObj=dispatch_queue_create("nameValue",DISPATCH_QUEUE_CONCURRENT);
    
    // 2.开启多条异步线程
    for (NSInteger i=1; i<=5; i++) {
        dispatch_async(queueObj, ^{
            NSLog(@"做任务(%zi),当前线程:%@",i,[NSThread currentThread]);
            [self startSaleTicket]; // 开始卖票
        });
    }
}

// 通过 NSLock 加锁
-(void)startSaleTicket{

    do {
        
        [NSThread sleepForTimeInterval:0.3];
        
        [self.addLock lock];   // 加锁
        if (self.ticketNumber<=0) break;
        NSLog(@"余票是: %zi 当前线程是: %@",self.ticketNumber,[NSThread currentThread]);
        self.ticketNumber-=1;
        [self.addLock unlock]; // 解锁
    }
    while (self.ticketNumber);

}

最终运行结果:

2019-08-12 14:31:28.968327+0800 TestModel[87809:13567989] 做任务(2),当前线程:<NSThread: 0x281fcc6c0>{number = 4, name = (null)}
2019-08-12 14:31:28.968620+0800 TestModel[87809:13567988] 做任务(1),当前线程:<NSThread: 0x281fe1400>{number = 3, name = (null)}
2019-08-12 14:31:28.969657+0800 TestModel[87809:13567986] 做任务(3),当前线程:<NSThread: 0x281fcc880>{number = 5, name = (null)}
2019-08-12 14:31:28.969713+0800 TestModel[87809:13567987] 做任务(4),当前线程:<NSThread: 0x281fc1200>{number = 6, name = (null)}
2019-08-12 14:31:28.970159+0800 TestModel[87809:13567992] 做任务(5),当前线程:<NSThread: 0x281fc1100>{number = 7, name = (null)}
2019-08-12 14:31:29.273489+0800 TestModel[87809:13567989] 余票是: 50 当前线程是: <NSThread: 0x281fcc6c0>{number = 4, name = (null)}
2019-08-12 14:31:29.273594+0800 TestModel[87809:13567988] 余票是: 49 当前线程是: <NSThread: 0x281fe1400>{number = 3, name = (null)}
2019-08-12 14:31:29.273889+0800 TestModel[87809:13567992] 余票是: 48 当前线程是: <NSThread: 0x281fc1100>{number = 7, name = (null)}
2019-08-12 14:31:29.273965+0800 TestModel[87809:13567987] 余票是: 47 当前线程是: <NSThread: 0x281fc1200>{number = 6, name = (null)}
2019-08-12 14:31:29.274026+0800 TestModel[87809:13567986] 余票是: 46 当前线程是: <NSThread: 0x281fcc880>{number = 5, name = (null)}
2019-08-12 14:31:29.577287+0800 TestModel[87809:13567989] 余票是: 45 当前线程是: <NSThread: 0x281fcc6c0>{number = 4, name = (null)}
2019-08-12 14:31:29.577437+0800 TestModel[87809:13567988] 余票是: 44 当前线程是: <NSThread: 0x281fe1400>{number = 3, name = (null)}
2019-08-12 14:31:29.577606+0800 TestModel[87809:13567992] 余票是: 43 当前线程是: <NSThread: 0x281fc1100>{number = 7, name = (null)}
2019-08-12 14:31:29.577718+0800 TestModel[87809:13567987] 余票是: 42 当前线程是: <NSThread: 0x281fc1200>{number = 6, name = (null)}
2019-08-12 14:31:29.577993+0800 TestModel[87809:13567986] 余票是: 41 当前线程是: <NSThread: 0x281fcc880>{number = 5, name = (null)}
2019-08-12 14:31:29.882881+0800 TestModel[87809:13567989] 余票是: 40 当前线程是: <NSThread: 0x281fcc6c0>{number = 4, name = (null)}
2019-08-12 14:31:29.883291+0800 TestModel[87809:13567988] 余票是: 39 当前线程是: <NSThread: 0x281fe1400>{number = 3, name = (null)}
2019-08-12 14:31:29.883545+0800 TestModel[87809:13567987] 余票是: 38 当前线程是: <NSThread: 0x281fc1200>{number = 6, name = (null)}
2019-08-12 14:31:29.883949+0800 TestModel[87809:13567986] 余票是: 37 当前线程是: <NSThread: 0x281fcc880>{number = 5, name = (null)}
2019-08-12 14:31:29.884300+0800 TestModel[87809:13567992] 余票是: 36 当前线程是: <NSThread: 0x281fc1100>{number = 7, name = (null)}
2019-08-12 14:31:30.188301+0800 TestModel[87809:13567989] 余票是: 35 当前线程是: <NSThread: 0x281fcc6c0>{number = 4, name = (null)}
2019-08-12 14:31:30.188728+0800 TestModel[87809:13567988] 余票是: 34 当前线程是: <NSThread: 0x281fe1400>{number = 3, name = (null)}
2019-08-12 14:31:30.189026+0800 TestModel[87809:13567987] 余票是: 33 当前线程是: <NSThread: 0x281fc1200>{number = 6, name = (null)}
2019-08-12 14:31:30.189603+0800 TestModel[87809:13567986] 余票是: 32 当前线程是: <NSThread: 0x281fcc880>{number = 5, name = (null)}
2019-08-12 14:31:30.190006+0800 TestModel[87809:13567992] 余票是: 31 当前线程是: <NSThread: 0x281fc1100>{number = 7, name = (null)}
2019-08-12 14:31:30.489850+0800 TestModel[87809:13567988] 余票是: 30 当前线程是: <NSThread: 0x281fe1400>{number = 3, name = (null)}
2019-08-12 14:31:30.490175+0800 TestModel[87809:13567987] 余票是: 29 当前线程是: <NSThread: 0x281fc1200>{number = 6, name = (null)}
2019-08-12 14:31:30.493747+0800 TestModel[87809:13567989] 余票是: 28 当前线程是: <NSThread: 0x281fcc6c0>{number = 4, name = (null)}
2019-08-12 14:31:30.494048+0800 TestModel[87809:13567986] 余票是: 27 当前线程是: <NSThread: 0x281fcc880>{number = 5, name = (null)}
2019-08-12 14:31:30.494877+0800 TestModel[87809:13567992] 余票是: 26 当前线程是: <NSThread: 0x281fc1100>{number = 7, name = (null)}
2019-08-12 14:31:30.795292+0800 TestModel[87809:13567988] 余票是: 25 当前线程是: <NSThread: 0x281fe1400>{number = 3, name = (null)}
2019-08-12 14:31:30.795687+0800 TestModel[87809:13567987] 余票是: 24 当前线程是: <NSThread: 0x281fc1200>{number = 6, name = (null)}
2019-08-12 14:31:30.795958+0800 TestModel[87809:13567989] 余票是: 23 当前线程是: <NSThread: 0x281fcc6c0>{number = 4, name = (null)}
2019-08-12 14:31:30.796489+0800 TestModel[87809:13567986] 余票是: 22 当前线程是: <NSThread: 0x281fcc880>{number = 5, name = (null)}
2019-08-12 14:31:30.800184+0800 TestModel[87809:13567992] 余票是: 21 当前线程是: <NSThread: 0x281fc1100>{number = 7, name = (null)}
2019-08-12 14:31:31.099967+0800 TestModel[87809:13567986] 余票是: 20 当前线程是: <NSThread: 0x281fcc880>{number = 5, name = (null)}
2019-08-12 14:31:31.100050+0800 TestModel[87809:13567988] 余票是: 19 当前线程是: <NSThread: 0x281fe1400>{number = 3, name = (null)}
2019-08-12 14:31:31.100090+0800 TestModel[87809:13567987] 余票是: 18 当前线程是: <NSThread: 0x281fc1200>{number = 6, name = (null)}
2019-08-12 14:31:31.100198+0800 TestModel[87809:13567989] 余票是: 17 当前线程是: <NSThread: 0x281fcc6c0>{number = 4, name = (null)}
2019-08-12 14:31:31.102069+0800 TestModel[87809:13567992] 余票是: 16 当前线程是: <NSThread: 0x281fc1100>{number = 7, name = (null)}
2019-08-12 14:31:31.405114+0800 TestModel[87809:13567986] 余票是: 15 当前线程是: <NSThread: 0x281fcc880>{number = 5, name = (null)}
2019-08-12 14:31:31.405197+0800 TestModel[87809:13567988] 余票是: 14 当前线程是: <NSThread: 0x281fe1400>{number = 3, name = (null)}
2019-08-12 14:31:31.405238+0800 TestModel[87809:13567987] 余票是: 13 当前线程是: <NSThread: 0x281fc1200>{number = 6, name = (null)}
2019-08-12 14:31:31.405293+0800 TestModel[87809:13567989] 余票是: 12 当前线程是: <NSThread: 0x281fcc6c0>{number = 4, name = (null)}
2019-08-12 14:31:31.407180+0800 TestModel[87809:13567992] 余票是: 11 当前线程是: <NSThread: 0x281fc1100>{number = 7, name = (null)}
2019-08-12 14:31:31.705607+0800 TestModel[87809:13567986] 余票是: 10 当前线程是: <NSThread: 0x281fcc880>{number = 5, name = (null)}
2019-08-12 14:31:31.705747+0800 TestModel[87809:13567988] 余票是: 9 当前线程是: <NSThread: 0x281fe1400>{number = 3, name = (null)}
2019-08-12 14:31:31.705857+0800 TestModel[87809:13567987] 余票是: 8 当前线程是: <NSThread: 0x281fc1200>{number = 6, name = (null)}
2019-08-12 14:31:31.705953+0800 TestModel[87809:13567989] 余票是: 7 当前线程是: <NSThread: 0x281fcc6c0>{number = 4, name = (null)}
2019-08-12 14:31:31.707719+0800 TestModel[87809:13567992] 余票是: 6 当前线程是: <NSThread: 0x281fc1100>{number = 7, name = (null)}
2019-08-12 14:31:32.007136+0800 TestModel[87809:13567986] 余票是: 5 当前线程是: <NSThread: 0x281fcc880>{number = 5, name = (null)}
2019-08-12 14:31:32.007306+0800 TestModel[87809:13567988] 余票是: 4 当前线程是: <NSThread: 0x281fe1400>{number = 3, name = (null)}
2019-08-12 14:31:32.007378+0800 TestModel[87809:13567987] 余票是: 3 当前线程是: <NSThread: 0x281fc1200>{number = 6, name = (null)}
2019-08-12 14:31:32.007499+0800 TestModel[87809:13567989] 余票是: 2 当前线程是: <NSThread: 0x281fcc6c0>{number = 4, name = (null)}
2019-08-12 14:31:32.012931+0800 TestModel[87809:13567992] 余票是: 1 当前线程是: <NSThread: 0x281fc1100>{number = 7, name = (null)}

11.说一下你对Runtime的认识 具体认识

题外话:计算机唯一能识别的语言是机器语言,高级编程语言不能被直接识别,需要先编译为汇编语言,再由汇编语言编译为机器语言才能被计算机识别。而 Objective-C语言不能被直接编译为汇编语言,它必须先编译为C语言,然后再编译为汇编语言,最后再由汇编语言编译为机器语言才能被计算机识别。 从OC到C语言的过渡就是由runtime来实现的。我们使用OC进行面向对象开发,但是C语言更多的是面向过程开发,这就需要将面向对象的类转变为面向过程的结构体。

1.Runtime 是iOS中的一个运行时系统,是苹果用C语言和汇编语言编写的一套底层纯C语言API。
2.Runtime正是 Objective-C这门动态语言的核心(数据类型的确定由编译时推迟到运行时)。从OC到C语言的过渡就是由Runtime来实现的。
3.OC代码最终都会被编译器转化为运行时代码,通过消息机制决定函数调用方式(objc_msgSend)。
4.OC类、对象和方法等都会被Runtime转化成C语言中的结构体。
5.在Runtime中,id 是一个指向 objc_object 结构体的指针;class 是一个指向 objc_class 结构体的指针
6.SEL 是一个指向 objc_selector 结构体的指针; Ivar 是一个指向 objc_ivar 的结构体的指针
7.Method 是一个指向 objc_method 的结构体的指针;IMP 是一个函数指针

12.说一下Runtime的使用情景 具体使用

1.动态方法交换  (method_exchangeImplementations)
2.给分类添加属性 (objc_setAssociatedObject 和 objc_getAssociatedObject)
3.获取类的详细信息
   3.1 属性列表(class_copyPropertyList)
   3.2 获取成员变量(class_copyIvarList)
   3.3 获取所有方法(class_copyMethodList)
   3.4 获取当前遵循的所有协议(class_copyProtocolList)
4.解决同一方法高频率调用的效率问题
5.方法动态解析与消息转发
   5.1 动态添加方法
   5.2 解决方法无响应崩溃问题
6.动态操作属性
  6.1 动态修改属性变量
  6.2 实现 NSCoding 的自动归档和解档
  6.3 实现字典与模型的转换