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: 是赋值特性,用于基本数据类型,如果用来修饰对象属性 , 那么当对象被销毁后指针是不会指向 nil 的 . 所以会出现野指针错误 ( 与weak的区别 )默认关键字     
weak:弱引用, 表明该属性定义了一种非拥有关系,在属性所指的对象销毁时,属性值自动清空(nil)。  
strong: 强引用,setter方法将传入参数先保留,再赋值,传入retaincount会+1。  
copy: 表示拷贝特性,setter方法将传入对象复制一份,生成不可变对象。
setter=<name>和getter=<name>一般用在特殊的情境下,当需要定义一个 init 开头的属性,但默认生成的 setter 与 getter 方法也会以 init 开头,而编译器会把所有以 init 开头的方法当成初始化方法,而初始化方法只能返回 self 类型,因此编译器会报错。

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?
1)、让自己的类可以使用copy修饰符的步骤:

a)、让自己的类遵循NSCopying协议
b)、实现NSCopying协议中必须实现的代理方法:
    - (id)copyWithZone:(NSZone *)zone;

示例代码:

#import <Foundation/Foundation.h>

@interface Person : NSObject <NSCopying, NSMutableCopying>

@property (nonatomic, copy) NSString *name;

@end


#import "Person.h"

@implementation Person

- (id)copyWithZone:(NSZone *)zone {
    Person *person = [Person allocWithZone:zone];
    person.name = self.name;
    return person;
}

- (id)mutableCopyWithZone:(NSZone *)zone {
    Person *person = [Person allocWithZone:zone];
    person.name = self.name;
    return person;
}

@end

2)、如何重写带copy关键字的setter:

- (void)setName:(NSString *)name {
    _name = [name copy];
}

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机制影响。
ARC下,系统会帮助我们释放该对象所包含的实例变量,但是有些对象还是需要们自己去释放的(比如Core Foundation框架下的一些对象),另外通知中观察者的移除,代理置空,停止timer等

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 修饰外部变量。
没有通过__block修饰则是将值传进去,而通过__block修饰之后是将变量的内存地址传进去,这样就可以修改变量的值。
  
  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 时刻监听各种事件的发生

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.避免庞大的XIB
4.不要阻塞主线程(耗时操作放在异步中执行,再回到主线程)
5.重用和延迟加载(懒加载)View

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);
      }
  }
5.没有移除通知的观察者
6.多个对象相互强引用

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 : 用来判断对象是否实现了指定协议类的方法