# 1. Objective-C堆和栈的区别?

管理方式: 对于栈来讲,是由编译器自动管理,无需我们手工控制; 对于堆来说,释放工作由程序员控制,容易产生memory leak

申请大小: 栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 Windows 下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。

堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出

分配方式: 都是动态分配的,没有静态分配的堆。

有2种分配方式:静态分配和动态分配。 a. 静态分配是编译器完成的,比如局部变量的分配。 b. 动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。

分配效率: 是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。 则是C/C++函数库提供的,它的机制是很复杂的。


# 2. static 关键字的作用?

函数体内 static 变量的作用范围为该函数体,不同于 auto 变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值; 在模块内的 static 全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问; 在模块内的 static 函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内; 在函数或方法范围外定义的变量以及用static修饰的变量都是静态变量。静态变量的生命周期是从程序开始到程序结束。 在类中的 static 成员变量属于整个类所拥有, 但是无论生成多少该类的对象, 都只有一个静态变量, 也就是说多个对象共享一个静态变量。 在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因而只能访问类的static 成员变量。

一个对象对静态变量赋值后, 在使用这个变量之前, 如果有另外一个对象修改了该静态变量的值, 如果不进行同步的话就会发生错误。 利用静态变量的这种性质可以实现对象间的信息共享和消息传递等。


# 3. 线程与进程的区别和联系?

进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性 进程和线程的主要差别在于它们是不同的操作系统资源管理方式。 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。 线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉。所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。 但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。


# 4. super 和 self 的区别?

super: 并不确定指向某个对象。super 只能被用于调用父类的方法, 不能通过 super 完成赋值, 也不能把方法的返回值指定为 super 。

self: 是指收到当前消息的实例变量, 例如:B类继承A类, B类方法中调用 [self a] ; B类中没有a方法, 是继承自A类中的, A类中a方法调用了b方法 [self b] ; , 此时这个self 指的是B类的对象。


# 5. 动态绑定?

动态绑定: 在程序执行时才确定对象的属性和需要响应的消息。

OC中的消息是执行时才去绑定的。运行时系统首先会确定接受者的类型(动态类型识别), 然后根据消息名在类的方法列表里选择响应的方法执行,如果没有就到父类中继续寻找, 如果一直到 NSObject 也没有找到要调用的方法, 就会包不能识别消息的错误。


# 6. 静态类型检查的总结?

OC的静态类型检查是在编译期完成的。

  1. 对于id类型的变量, 调用任何方法都会通过编译(当然调用不恰当的方法会出现运行时错误)
  2. id类型的变量和被静态类型的变量之间是可以相互赋值的
  3. 被定义为静态类型对象的变量, 如果调用了类或父类中未定义的方法, 编译器就会提示警告
  4. 如果是静态类型的变量, 父类类型的实例变量不可以赋值给子类类型的实例变量
  5. 如果是静态类型的变量, 子类类型的实例变量可以赋值给父类类型的实例变量
  6. 若要判断到底是哪一个类的方法被执行了, 不要看变量所声明的类型, 而要看司机执行时这个变量的类型
  7. id类型并不是(NSObject *)类型

# 7. 重载?

重载: 指的是一个函数、运算符或者方法定义有多种功能, 并根据情况来选择合适的功能。

OC 可以通过动态绑定让同一个消息选择器执行不同的功能来实现重载。


# 8. 为什么不可以直接访问属性, 而是要通过getter 和 setter方法来实现?

为了封装。

如果允许直接访问类的实例变量, 那么当类的实现发生了变化, 实例变量被删除或者作用发生了变化时, 所有调用这个类的外部模块都需要修改。 如果使用类getter/setter的形式, 当类的实现发生了变化时则只需要修改getter/setter接口, 外部调用部分不需要做任何修改。 虽然子类可以直接访问父类的实例变量, 但是要尽量用getter/setter方法来访问父类中的实例变量, 这样可以使程序尽可能的低耦合。


# 9. 什么情况下会发生内存泄漏和内存溢出例如程序闪退的原因和处理方法?

当程序在申请内存后, 无法释放已经申请的内存空间(例如一个对象或者变量使用完成后没有释放,这个对对象一直占用着内存),一次内存泄漏的危害可以忽略,但是内存泄露堆积的后果很严重,无论多少内存,迟早会被占光,内存泄露最终会导致内存溢出

当程序申请内存时, 没有足够的内存空间供其使用,出现了out of memory;比如申请了一个int,但给他存放了long才能存下的数,就是内存溢出.


# 10. +load和+initialize区别是什么?

initialize 和 load 的区别在于: load是只要类所在文件被引用就会被调用,而initialize是在类或者其子类的第一个方法被调用前调用。

所以如果类没有被引用进项目,就不会有load调用;但即使类文件被引用进来,但是没有使用,那么initialize也不会被调用。


# 11. KVC,KVO和代理 通知中心block?

KVC: (键值编码)是一种简介访问对象实例变量的机制,该机制可以不通过存取方法就可以访问对象的实例变量

KVO: (键值观察)是一种能使得对象获取到其他对象属性变化的通知机制 实现KVO键值观察模式,被观察的对象必须使用KVC键值编码来修改它的实例变量,这样才能被观察着观察到。因此,KVC是KVO的基础或者说KVO的实现是建立在KVC的基础之上的

代理: Delegate与block一般适用于两个对象1对1之间的通信交互,Delegate需要定义协议方法,代理对象实现协议方法,并且需要建立代理关系才可以实现通信。Block更加简洁


# 12. @property 后面可以有哪些修饰符?

线程安全:nonatomic, atomic 内存管理:strong, weak ,copy,retain,unsafe_unretained,assign 访问权限:readonly, readwrite 指定方法名:getter=, setter= 不常用的:nonnull,null_resettable,nullable


# 13. 沙盒目录结构是怎样的?各自用于那些场景?

Application: 存放程序源文件,上架前经过数字签名,上架后不可修改

Documents: 常用目录,iCloud备份目录,存放数据

tmp: 存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能

Library:

  • Caches:存放体积大又不需要备份的数据
  • Preference:设置目录,iCloud会备份设置信息

# 14. Objective-C使用什么机制管理对象内存?

MRC 手动引用计数 ARC 自动引用计数,现在通常ARC

通过 retainCount 的机制来决定对象是否需要释放。 每次 runloop 的时候,都会检查对象的 retainCount,如果retainCount 为 0,说明该对象没有地方需要继续使用了,可以释放掉了


# 15. ARC是为了解决什么问题诞生的?

首先解释ARC: automatic reference counting自动引用计数

了解MRC的缺点:

  1. 在MRC时代当我们要释放一个堆内存时,首先要确定指向这个堆空间的指针都被release了
  2. 释放指针指向的堆空间,首先要确定哪些指针指向同一个堆,这些指针只能释放一次(MRC下即谁创建,谁释放,避免重复释放)
  3. 模块化操作时,对象可能被多个模块创建和使用,不能确定最后由谁去释放
  4. 多线程操作时,不确定哪个线程最后使用完毕

综上所述,MRC有诸多缺点,很容易造成内存泄露和坏内存的问题,这时苹果为尽量解决这个问题,从而诞生了ARC


# 16. ARC下还会存在内存泄露吗?

循环引用会导致内存泄露

Objective-C对象与CoreFoundation对象进行桥接的时候如果管理不当也会造成内存泄露 CoreFoundation中的对象不受ARC管理,需要开发者手动释放


# 17. 使用atomic一定是线程安全的吗?

不是,atomic的本意是指属性的存取方法是线程安全的,并不保证整个对象是线程安全的。

举例:声明一个NSMutableArray的原子属性stuff,此时self.stuff 和self.stuff = othersulf都是线程安全的。但是,使用[self.stuff objectAtIndex:index]就不是线程安全的,需要用互斥锁来保证线程安全性


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

@property 有两个对应的词,一个是@synthesize ,一个是 @dynamic。如果@synthesize和@dynamic都没写,那么默认的就是@syntheszie var = _var;

@synthesize: 语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法

@dynamic 告诉编译器:属性的setter与getter方法由用户自己实现,不自动生成(当然对于readonly的属性只需提供getter即可)

假如一个属性被声明为@dynamic var,然后你没有提供@setter方法和@getter方法,编译的时候没问题,但是当程序运行到instance.var = someVar,由于缺setter方法会导致程序崩溃;或者当运行到 someVar = instance.var时,由于缺getter方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定


# 19. 怎么用 copy 关键字?

NSString、NSArray、NSDictionary等等经常使用copy关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,为确保对象中的属性值不会无意间变动,应该在设置新属性值时拷贝一份,保护其封装性

block也经常使用copy关键字: block 使用 copy 是从 MRC 遗留下来的“传统”,在 MRC 中,方法内部的 block 是在栈区的,使用 copy 可以把它放到堆区. 在ARC中写不写都行:对于 block 使用 copy 还是 strong 效果是一样的,但是建议写上copy,因为这样显示告知调用者“编译器会自动对 block 进行了 copy 操作


# 20. NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?

因为父类指针可以指向子类对象,使用copy的目的是为了让本对象的属性不受外界影响,使用copy无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本.

如果我们使用是strong,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性.


# 21. KVO内部实现原理?

KVO是基于runtime机制实现的:

  1. 当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter 方法。派生类在被重写的setter方法内实现真正的通知机制
  2. 如果原类为Person,那么生成的派生类名为NSKVONotifying_Person
  3. 每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将isa指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter方法
  4. 键值观察通知依赖于NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey:;在一个被观察属性发生改变之前, willChangeValueForKey: 一定会被调用,这就 会记录旧的值。而当改变发生后,didChangeValueForKey: 会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。

补充:KVO的这套实现机制中苹果还偷偷重写了class方法,让我们误认为还是使用的当前类,从而达到隐藏生成的派生类


# 22. SQLite 和 Core Data的区别

SQLite: mac os x中sqlite库,可以在多个平台使用,sqlite是一个轻量级的嵌入式sql数据库编程。与core data框架不同的是,sqlite是使用程序式的,sql的主要的API来直接操作数据表。跨平台

**CoreData: ** 基于sqlite的.是以图形界面的方式快速的定义数据模型. Core Data 基于mvc模式下, 为创建分解的cocoa应用程序提供了一个灵活和强大的数据模型框架. Core Data是苹果自己的框架, 本质上还是和SQLite进行交互, 只不过在交互或者处理数据的时候进行了许多的优化, 减少了许多代码量, core data是苹果自己弄的框架,它其实还是和sqlite进行交互的,只是在交互的时候或者处理数据的时候进行了很多的优化,core data可以缩小你的代码量,而且core data已经优化过很多个版本,还提供了出色的安全性和错误处理之外,还提供了对任何竞争性方案的最好的内存可扩展性。换句话说就是,你可能花费了很长时间为某个问题进行优化精心制作了一个方案,但是在性能上的优势和core data相比,还是相差深远的。


# 23. category 和 extension 的区别

分类有名字,类扩展没有分类名字,是一种特殊的分类 分类只能扩展方法(属性仅仅是声明,并没真正实现),类扩展可以扩展属性、成员变量和方法


# 24. define 和 const常量有什么区别?

define在预处理阶段进行替换,const常量在编译阶段使用 宏不做类型检查,仅仅进行替换,const常量有数据类型,会执行类型检查 define不能调试,const常量可以调试 define定义的常量在替换后运行过程中会不断地占用内存,而const定义的常量存储在数据段只有一份copy,效率更高 define可以定义一些简单的函数,const不可以


# 25. block和weak修饰符的区别?

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

__weak只能在ARC模式下使用,只能修饰对象(NSString),不能修饰基本数据类型

__block修饰的对象可以在block中被重新赋值,weak修饰的对象不可以


# 26. @property 的本质是什么?

@property其实就是在编译阶段由编译器自动帮我们生成ivar成员变量,getter方法,setter方法


####27. @protocol 和 category 中如何使用 @property

  • protocol中使用property只会生成setter和getter方法声明,我们使用属性的目的,是希望遵守我协议的对象能实现该属性
  • category 使用 @property也是只会生成setter和getter方法声明,如果我们真的需要给category增加属性的实现,需要借助于运行时的两个函数

objc_setAssociatedObject objc_getAssociatedObject


# 28.如何手动触发一个value的KVO

自动触发的场景: 在注册KVO之前设置一个初始值,注册之后,设置一个不一样的值,就可以触发了

手动触发演示:

@property (nonatomic, strong) NSDate *now;

- (void)viewDidLoad
{
    [super viewDidLoad];

    // “手动触发self.now的KVO”,必写。
    [self willChangeValueForKey:@"now"];

    // “手动触发self.now的KVO”,必写。
    [self didChangeValueForKey:@"now"];
}

# 29. KVC的keyPath中的集合运算符如何使用?

  • 必须用在集合对象上或普通对象的集合属性上
  • 简单集合运算符有@avg, @count , @max , @min ,@sum
  • 格式 @"@sum.age" 或 @"集合属性.@max.age"???

# 30. runtime怎么添加属性、方法等

  • class_addIvar ivar表示成员变量
  • class_addMethod
  • class_addProperty
  • class_addProtocol
  • class_replaceProperty

# 31. 是否可以把比较耗时的操作放在NSNotificationCenter中

首先必须明确通知在哪个线程中发出,那么处理接受到通知的方法也在这个线程中调用

如果在异步线程发的通知,那么可以执行比较耗时的操作; 如果在主线程发的通知,那么就不可以执行比较耗时的操作


# 32. runtime 如何实现 weak 属性

首先要搞清楚weak属性的特点: weak策略表明该属性定义了一种“非拥有关系” (nonowning relationship)。 为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似; 然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)

那么runtime如何实现weak变量的自动置nil? runtime 对注册的类,会进行布局,会将 weak 对象放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会调用对象的 dealloc 方法, 假设 weak 指向的对象内存地址是a,那么就会以a为key,在这个 weak hash表中搜索,找到所有以a为key的 weak 对象,从而设置为 nil。


# 33. weak属性需要在dealloc中置nil么

  • 在ARC环境无论是强指针还是弱指针都无需在 dealloc 设置为 nil , ARC 会自动帮我们处理
  • 即便是编译器不帮我们做这些,weak也不需要在dealloc中置nil
  • 在属性所指的对象遭到摧毁时,属性值也会清空

模拟下weak的setter方法,大致如下:

- (void)setObject:(NSObject *)object
{
    objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN);
    [object cyl_runAtDealloc:^{
        _object = nil;
    }];
}

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

  • 每一个对象内部都有一个isa指针,这个指针是指向它的真实类型
  • 根据这个指针就能知道将来调用哪个类的方法

# 35. runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)

  • 每一个类对象中都一个对象方法列表(对象方法缓存)
  • 类方法列表是存放在类对象中isa指针指向的元类对象中(类方法缓存)
  • 方法列表中每个方法结构体中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.
  • 当我们发送一个消息给一个NSObject对象时,这条消息会在对象的类对象方法列表里查找
  • 当我们发送一个消息给一个类时,这条消息会在类的Meta Class对象的方法列表里查找

# 36. _objc_msgForward函数是做什么的?直接调用它将会发生什么?

_objc_msgForward 是IMP类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发 直接调用_objc_msgForward是非常危险的事,这是把双刃刀,如果用不好会直接导致程序Crash,但是如果用得好,能做很多非常酷的事 JSPatch就是直接调用_objc_msgForward来实现其核心功能的


# 37. dispatch_barrier_async的作用是什么?

函数定义: dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

必须是并发队列,要是串行队列,这个函数就没啥意义了

注意:这个函数的第一个参数queue不能是全局的并发队列 作用:在它前面的任务执行结束后它才执行,在它后面的任务等它执行完成后才会执


# 38. 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?

不能向编译后得到的类中增加实例变量; 能向运行时创建的类中添加实例变量;

分析如下: 因为编译后的类已经注册在runtime中,类结构体中的objc_ivar_list 实例变量的链表和instance_size实例变量的内存大小已经确定,同时runtime 会调用class_setIvarLayout 或 class_setWeakIvarLayout来处理strong weak引用,所以不能向存在的类中添加实例变量

运行时创建的类是可以添加实例变量,调用 class_addIvar函数,但是得在调用objc_allocateClassPair之后,objc_registerClassPair之前,原因同上。


# 39. runloop和线程有什么关系?

每条线程都有唯一的一个RunLoop对象与之对应的 主线程的RunLoop是自动创建并启动, 子线程的RunLoop需要手动创建;

子线程的RunLoop创建步骤如下:

  • 在子线程中调用[NSRunLoop currentRunLoop]创建RunLoop对象(懒加载,只创建一次)
  • 获得RunLoop对象后要调用run方法来启动一个运行循环
// 启动RunLoop
[[NSRunLoop currentRunLoop] run];

    * RunLoop的其他启动方法

// 第一个参数:指定运行模式
// 第二个参数:指定RunLoop的过期时间,即:到了这个时间后RunLoop就失效了
[[NSRunLoop currentRunLoop] runMode:kCFRunLoopDefaultMode beforeDate:[NSDate distantFuture]];

# 40. runloop的mode作用是什么?

用来控制一些特殊操作只能在指定模式下运行,一般可以通过指定操作的运行mode来控制执行时机,以提高用户体验

系统默认注册了5个Model kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行,对应OC中的:NSDefaultRunLoopMode UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他Mode影响 kCFRunLoopCommonModes:这是一个标记Mode,不是一种真正的Mode,事件可以运行在所有标有common modes标记的模式中,对应OC中的NSRunLoopCommonModes,带有common modes标记的模式有:UITrackingRunLoopMode和kCFRunLoopDefaultMode UIInitializationRunLoopMode:在启动 App时进入的第一个 Mode,启动完成后就不再使用 GSEventReceiveRunLoopMode:接受系统事件的内部Mode,通常用不到