OC 基础面试题 I

引言:

# 1. define 和 const 有什么区别?

#define 定义的宏,程序在预处理阶段将用#define 定义的宏的内容进行了替换。因此程序运行时,在常量表中并没有使用#define 定义的宏,不会分配内存,在编译时不会检查数据类型, 出错的概率会大一些。定义表达式时要注意边缘效应。

const定义的常量,在程序运行时是存放在常量表中,系统会为其分配内存,并且会检查数据类型。 define在预处理阶段进行替换,const常量在编译阶段使用

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

# 2. const 定义常量的方式?

  1. const int a; a 是指向整数的指针(整数不可变,指针可以变)。
  2. int * const a; a 是指向整数的指针(指针指向的整数是可变的,但是指针是不可变的)。
  3. int const *const a; a是指向常整数型的指针(指针指向的整数和指针本身都是不可改变的)。

# 3. 数组和指针的区别?

  1. 数组可以申请在栈区和静态数据区;指针可以指向任意类型的内存块。
  2. 数组名表示的是数组首地址,是常量指针,不可改变;普通指针的值可以改变。
  3. 用字符串初始化字符数组是将字符串的内容拷贝到字符数组中;用字符串初始化字符指针,是将字符串的首地址赋值给指针,也就是指针指向了该字符串。

# 4. 方法和选择器有什么不同?

  1. 选择器(selector):是一个方法的名字。
  2. 方法:是一个组合体, 包含了名字和实现。

# 5. malloc 和 new 的区别?

  1. new 是C++的操作符,malloc 是 C 中的函数。
  2. new 不只分配内存空间,还会调用类的构造函数;malloc 只会分配内存空间,不会进行初始化类成员的工作。
  3. 内存泄露时,都可以检测出来;但是 new 会指出哪个文件的哪一行,malloc 则不会。
  4. new 可以认为是 malloc+构造函数的执行。
  5. new 出来的指针是直接带类型信息的。

# 6. 简述一下 OC 中的反射机制?

class 反射:

通过类名的字符串实例化对象:

Class class NSClassFromString(@"View");
View *view = [[class alloc] init];

将类名转换成字符串:

Class class = [View class];
NSString *className = NSStringFromClass(class);

SEL 反射:

通过方法名的字符串实例化:

SEL selector = NSSelectorFromClass(@"setName");
[stu performSelector:selector withObject:@"Mike"];

将方法变成字符串:

NSStringFomrSelector(@selector*(setName:))

# 7. 什么是 SEL?如何使用?

SEL 就是对方法的一种包装。包装的 SEL 类型数据对应的是相应方法的地址,找到方法地址就可以调用方法。 在内存中每个类方法都存在类对象中,每个方法都有一个与之对应的 SEL 类型的数据,根据一个 SEL 数据就可以找到对应方法的地址,进而调用方法。

**包装方法: **

SEL s1 = @selector(test);

调用方法:

直接通过方法名调用: [person text]

间接的通过 SEL 数据来调用:SEL aaa=@selector(text); [person performSelector:aaa];

# 8. NS/CF/CG/CA/UI这些前缀分别代表什么含义?

  1. NS: 函数归属于 cocoa Fundation 框架;
  2. CF: 函数归属于 core Fundation 框架;
  3. CG: 函数归属于 CoreGraphics.frameworks 框架;
  4. CA: 函数归属于 CoreAnimation.frameworks 框架;
  5. UI: 函数归属于 UIKit 框架。

# 9. 面向对象有哪些特征?谈谈自己的理解?

继承:

  1. 继承是从已有类得到继承信息创建新类的过程。
  2. 提供继承信息的类被称为父类;得到继承信息的类被称为子类。
  3. 继承让系统有一定的延续性,也是封装程序的重要手段。

封装:

  1. 就是隐藏一切能够隐藏的东西,向外界提供最简单的调用接口。
  2. 把数据和数据操作的方法绑定起来,外界对数据的访问只能通过定义好的接口来访问。
  3. 日常开发中定义方法,就是封装的一种体现。

多态:

  1. 允许不同子类型的对象对统一消息作出不同的响应。
  2. 用同样的对象调用相同的方法,却实现了不同的事情。
  3. 编译时多态: 方法的重载(前绑定)。
  4. 运行时多态:方法的重写(后绑定)。

方法重写:

  1. 子类继承父类并重写父类已有或抽象的方法。

对象造型:

  1. 举例 生物类都有一个相同的方法 set。人属于生物类,狗也属于生物类,都继承自生物类,分别实现了各自的 eat 方法,但是调用时只需要调用各自的 eat,就能都响应了 eat 的x消息。

抽象:

  1. 将一类对象的共同特性总结出来构造类的过程。
  2. 包括数据抽象(属性)和行为抽象(方法)。
  3. 抽象只关注对象有哪些属性和行为, 不关心行为的细节是什么。

# 10. 为什么说 OC 是运行时语言?

  1. 主要是将数据类型的确认由编译时,推迟到了运行时。
  2. 运行时机制,使我们直到运行时才去决定一个对象的类别,以及对象调用的方法。

# 11. readwrite, readonly, assign, weak, retain, strong, copy, nonatomic 属性的作用?

readwrite:

可读可写特性。需要生成 getter 和 setter 方法时使用。

readonly:

只读特性。 只会生成 getter 方法,不会生成setter方法;不希望属性在类外被改变。

assgin:

是 mrc/arc中, 赋值属性,setter 方法将传入的值设置给实例变量;在设置数值的时候,不会做任何附加操作 arc 中,通常用来保存基本数据类型;mrc 中,如果不需要引用,就是用 assgin。 是赋值特性,setter方法将传入参数赋值给实例变量;仅设置变量时;

weak:

在 arc 中,用来保存不需要强引用的对象. weak 修饰的属性,如果没有其他对象对其强引用,就会被立即释放。

retain:

mrc中的,持有属性,setter 方法传入的值先保留,再赋值,传入的值的引用计数retainCount会 + 1。

strong:

arc 中的,与 retain 等效。

copy:

赋值特性,setter 方法将传入的值完全复制一份;需要完全一份新的变量时使用。

nonatominc:

非原子性,决定编译器生成的getter,setter 是否是原子操作。在自己管理的内存环境中,访问器只是简单的返回这个值。

natominc:

原子性;多线程安全,防止读写未完成时被另一个线程读写,一般使用 nonatomic。

weak 和 assgin 的区别:

  1. assgin 指向的对象如果被释放,地址不会有任何变化
  2. weak 指向的对象如果被释放,地址会立即变化为 nil(指向0的空对象)
  3. assgin 的效率高,风险大(野指针)

如何一个值,在属性中,本质上保存的都是一个“数字”:

  1. 如果是对象, 保存的是堆中的地址
  2. 如果是基本数据类型,保存的是基本数据类型的数值

# 12. 懒加载?

在用到的时候才初始化。也可以理解为延时加载。

最好的例子就是 tableView中图片的加载显示,一个延时加载,避免内存过高;一个异步加载,避免线程堵塞提高用户体验。

# 13. OC 有多继承么?

多继承是指一个子类继承多个父类,继承多个父类的特性。OC 中没有多继承,只支持单继承。可以通过类别和协议来实现。

**协议:**可以实现多个接口,通过实现多个接口来完成多继承。 **类别:**一般使用分类,去重写类的方法,仅对当前分类有效,不会影响其他分类和原有类。

# 14. OC有私有方法,私有变量么?如果没有如何代替?

没有私有方法, OC中的方法只有两个,实例方法(-),类方法(+)。可以通过把方法的声明和定义都放到.m 文件中来实现表面上的私有方法。

OC 中有私有变量,可以通过@private 来修饰,也可以把声明放在.m 文件中。 OC 中所有的实例变量都是默认私有的,实例方法默认都是共有的。

# 15. 描述类别(Category)和延展(extensions)的区别?

类别:

在没有原来类的.m 文件的基础上,来添加实例方法。

延展:

特殊形式的类别,在.m 文件中声明和实现延展的作用,给某个类添加私有方法或私有变量。

区别:

  1. 延展可以添加属性,并且它的方法是必须要实现的。可以认为是一个私有的类目。
  2. 类别可以在不知道不改变原来代码的情况下,来添加新的方法,但是不能修改删除原来的方法。
  3. 类别优先级更高。如果类别中和原有类中的类名冲突会优先调用类别中的方法。
  4. Category 只能添加方法,不能添加成员变量:原因是 如果可以添加成员变量,添加的成员变量没法初始化。

# 16. include 和 #import 的区别?

import 是 OC 对 include 的改进版本,确保引用的文件只会被引用一次,bu会陷入递归包含的问题。

# 17. #import、#include 和 @class 的区别?#import<> 跟 #import””又什么区别?

#import是Objective-C导入头文件的关键字, 会引入该头文件中的全部信息,包括实例变量和方法等等;
#include是C/C++导入头文件的关键字; @class只是告诉编译器,声明的是类的名称,至于这些类是如何定义的暂时不考虑,当执行时,才去查看类的实现文件,可以解决头文件的相互包含;这种写法叫做类的前置声明。

使用#import头文件会自动只导入一次,不会重复导入,相当于#include#pragma once

一般在.h文件中使用 @calss,不需要知道引入类中的内部实现。 一般在.m 文件中会用到引入类的实例变量或方法,所以使用 #import 引入。

效率方面,import 效率低,任意一个类发生改变,所有引用的类都需要重新编译;@class 则不会。 如果有循环依赖关系,A->B, B->A这样的相互依赖的关系,如果使用的是 import 来包含,会出现编译错误;如果使用@class 在两个类头文件中相互声明,编译不会出现错误。

#import<>用来包含系统的头文件,#import ""来包含用户自定义头文件。

# 18. 深拷贝(深赋值)和浅拷贝的区别?

浅拷贝(copy):只复制对象的指针,而不复制引用对象的本身。 深拷贝(mutableCopy):复制引用对象本身。内存中会存在两个本身独立的对象,当修改 A 时,A _copy 不会发生改变。

# 19. 类变量中@protected、@private、@public、@package声明有什么含义?

表示变量的不同的作用域。

@proteected: 受保护,在该类和子类中可以访问,默认的; @private:私有的,只能在该类中访问; @public:共有的,在任何地方都能访问; @package: 本包内可以访问,不能跨包访问。

# 20. OC 的消息机制?

OC 的函数调用被称为消息发送,属于动态调用过程。 在编译期间并不能决定真正调用哪个函数,只有在真正运行时才会根据函数的名称找到函数调用。 OC 在编译阶段可以调用任何函数,即使函数并未实现,只要声明了就不会报错。

# 21. 常见的 OC 数据类型有哪些?与 C 有什么区别?

常见的有: NSInteger、CGFloat、 NSString、 NSNumber、 NSArray、 NSDate

NSInteger:根据系统32位还是64位来决定本身是 int 还是 long。 CGFloat:根据系统是32位还是64位来决定本身是 float 还是 double。

NSString、 NSNumber、 NSArray、 NSDate 都是指针类型的对象,在堆中分配内存;C 中的 char、int 都是在栈中分配内存。

# 22. id 和 nil 代表什么?nil 和 null 有什么区别?

id :类型的指针可以指向任意类型的 OC 对象。 nil :代表空值(空指针的值,0)。即对象的引用为空。 null :表示指向基本数据类型变量,即C语言变量的指针为空。

在非 ARC 中nil 和 null 可以互换;在 ARC 中,指针和对象的引用要求严格不可以互换。

# 23. 向一个 nil 对象发送消息会发生什么?

OC 中 nil 对象发送消息是有效的, 但是在运行时没有任何作用,也不会崩溃。

如果一个方法返回的是一个对象,那么发送给 nil 的消息返回0 。如果方法返回值为结构体,那么发送给 nil 对象的消息返回0 。

# 24. 类方法和实例方法有什么区别?

类方法:

  1. 属于类对象,只能类对象来调用。
  2. 类方法可以调用任何其他类方法。
  3. 不能访问成员变量,不能直接调用对象方法。

实例方法:

  1. 属于实例对象,只能实例对象来调用。
  2. 实例方法可以调用实例方法。
  3. 能访问实例变量,能调用类方法。

# 25. _block 和 _weak 修饰符有什么区别?

_block 在 arc 和 mrc 下都能使用,可以修饰对象,也可以修饰基本数据类型。 _weak只能在 arc 下使用,只能修饰对象,不能修饰基本数据类型。 _block修饰的对象可以在 block 中进行重新赋值,_weak 不能。

# 26. @property 的本质是什么?成员变量、getter、setter 方法是如何生存并添加到类中的?

@property 的创建属性的本质是:ivar(实例变量)+ getter + setter 方法 。

# 27. Category是什么?重写一个类的方式用继承好还是分类好?为什么?

Category是类别,又叫分类;一般情况用分类好,用Category去重写类的方法,仅对本Category有效,不会影响到其他类与原有类的关系

# 28. 什么情况使用 weak 关键字,相比 assign 有什么不同?

首先明白什么情况使用weak关键字?

在ARC中,在有可能出现循环引用的时候,往往要通过让其中一端使用weak来解决,比如:delegate代理属性,代理属性也可使用assign 自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用weak,自定义IBOutlet控件属性一般也使用weak;当然,也可以使用strong,但是建议使用weak

weak 和 assign的不同点:

weak策略在属性所指的对象遭到摧毁时,系统会将weak修饰的属性对象的指针指向nil,在OC给nil发消息是不会有什么问题的; 如果使用assign策略在属性所指的对象遭到摧毁时,属性对象指针还指向原来的对象,由于对象已经被销毁,这时候就产生了野指针,如果这时候在给此对象发送消息,很容造成程序奔溃; assigin 可以用于修饰非OC对象, 而weak必须用于OC对象;

# 29. 怎么用 copy 关键字?

一般使用retain或者strong修饰属性时,是使引用对象的指针指向同一个对象,即为同一块内存地址。只要其中有一个指针变量被修改时所有其他引用该对象的变量都会被改变。

而使用copy关键字修饰在赋值时是释放旧对象,拷贝新对象内容。重新分配了内存地址。以后该指针变量被修改时就不会影响旧对象的内容了。 copy只有实现NSCopying协议的对象类型才有效。 常用于NSStringBlock

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

该类必须要实现NSCopying协议。实现 - (id)copyWithZone:(NSZone *)zone;方法。
重写copy关键字的setter时,需要调用一下传入对象的copy方法。然后赋值给该setter的方法对应的成员变量。

# 31. 这个写法会出什么问题:@@property (copy) NSMutableArray *array;?

当一个NSMutableArray对象使用initWithArray:初始化方法创建时,并将该对象赋值给了array属性。

那么之后array执行可变数组的方法,比如: removeObjectAtIndex:时会出现unrecognized selector sent to instance的崩溃。 原因在于array属性在被赋值(setter)的时候默认执行了copy方法后变为了不可变NSArray对象。

# 32. 对于语句 NSString*obj = [[NSData alloc] init]; obj在编译时和运行时分别时什么类型的对象?

编译时是NSString的类型;运行时是NSData类型的对象

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

object-c的数据类型有NSStringNSNumberNSArrayNSMutableArrayNSData等等,这些都是class,创建后便是对象。 C语言的基本数据类型int,只是一定字节的内存空间,用于存放数值;

NSInteger是基本数据类型,并不是NSNumber的子类,当然也不是NSObject的子类。 NSInteger是基本数据类型Int或者Long的别名(NSInteger的定义typedef long NSInteger),它的区别在于,NSInteger会根据系统是32位还是64位来决定是本身是int还是Long。

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

id 声明的对象具有运行时的特性,即可以指向任意类型的objcetive-c的对象;

# 35. 内存管理的几条原则时什么?按照默认法则, 哪些关键字生成的对象需要手动释放?在和property结合的时候怎样有效的避免内存泄露?

Objective-C的内存管理主要有三种方式ARC(自动内存计数)手动内存计数内存池

原则: 谁申请,谁释放。遵循Cocoa Touch的使用原则;

内存管理主要要避免“过早释放”和“内存泄漏”,对于“过早释放”需要注意@property设置特性时,一定要用对特性关键字,对于“内存泄漏”,一定要申请了要负责释放,要细心。 关键字alloc 或 new 生成的对象需要手动释放; 设置正确的property属性,对于retain需要在合适的地方释放


# 36. 浅复制和深复制的区别?

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

意思就是说我有个A对象,复制一份后得到A_copy对象后,对于浅复制来说,A和A_copy指向的是同一个内存资源,复制的只不过是是一个指针,对象本身资源还是只有一份,那如果我们对A_copy执行了修改操作,那么发现A引用的对象同样被修改,这其实违背了我们复制拷贝的一个思想。深复制就好理解了,内存中存在了两份独立对象本身。

**用网上一哥们通俗的话将就是: ** 浅复制好比你和你的影子,你完蛋,你的影子也完蛋 深复制好比你和你的克隆人,你完蛋,你的克隆人还活着。


# 37. 类别的作用?继承和类别在实现中有何区别?

类别:

  1. 将类的实现分散到多个不同文件或多个不同框架中。
  2. 创建对私有方法的前向引用。
  3. 向对象添加非正式协议。

category:

  1. 可以在不获悉,不改变原来代码的情况下往里面添加新的方法,只能添加,不能删除修改。
  2. 并且如果类别和原来类中的方法产生名称冲突,则类别将覆盖原来的方法,因为类别具有更高的优先级。

继承: 可以增加、扩展父类方法,并且可以增加属性。

类别和类扩展的区别:

  1. category 和 extensions 的不同在于 extensions 可以添加属性。另外 extensions 添加的方法是必须要实现的。extensions 可以认为是一个私有的 Category 。
  2. 分类有名字,类扩展没有分类名字,是一种特殊的分类
  3. 分类只能扩展方法(属性仅仅是声明,并没真正实现),类扩展可以扩展属性、成员变量和方法

# 38. 代理的作用?

代理的目的是改变或传递控制链。允许一个类在某些特定时刻通知到其他类,而不需要获取到那些类的指针。可以减少框架复杂度。另外一点,代理可以理解为java中的回调监听机制的一种类似。


# 39. 什么时候使用NSMutableArray,什么时候使用NSArray?

当数组在程序运行时,需要不断变化的,使用 NSMutableArray ; 当数组在初始化后,便不再改变的,使用 NSArray 。

需要指出的是,使用 NSArray 只表明的是该数组在运行时不发生改变,即不能往 NSAarry 的数组里新增和删除元素,但不表明其数组內的元素的内容不能发生改变。 NSArray 是线程安全的,NSMutableArray不是线程安全的,多线程使用到 NSMutableArray 需要注意。


# 40. 谈谈Object-C的内存管理方式及过程?

当你使用new,alloccopy方法创建一个对象时,该对象的保留计数器值为1.当你不再使用该对象时,你要负责向该对象发送一条releaseautorelease消息.这样,该对象将在使用寿命结束时被销毁.

当你通过任何其他方法获得一个对象时,则假设该对象的保留计数器值为1,而且已经被设置为自动释放,你不需要执行任何操作来确保该对象被清理.如果你打算在一段时间内拥有该对象,则需要保留它并确保在操作完成时释放它.

如果你保留了某个对象,你需要(最终)释放或自动释放该对象.必须保持retain方法和release方法的使用次数相等.