2023最新初级难度Objective-C面试题,包含答案。刷题必备!记录一下。
好记性不如烂笔头
问: Objective-C和C++有什么区别?
Objective-C 和 C++ 都是编程语言,它们都是从 C 语言演化而来的,并且都支持面向对象的特性。然而,尽管它们有共同之处,但两者在设计和使用上还是存在一些显著的区别:
-
动态性:
- Objective-C 是一种完全动态的语言,这意味着它的方法调用是在运行时解析的,通过消息传递机制实现。
- C++ 则是一种部分动态的语言,它允许静态类型检查和函数重载等特性,这些功能在编译时就可以确定。
-
继承机制:
- Objective-C 不支持多继承,但它提供了分类(Categories)和协议(Protocols)来模拟类似的功能,以及扩展(Extensions),这是一种匿名分类。
- C++ 支持多继承,即一个类可以同时继承多个基类的属性和行为。
-
消息发送与函数调用:
- 在 Objective-C 中,方法调用是通过向对象发送消息来完成的,语法形式为
[receiver message]
。 - 而在 C++ 中,方法调用是直接进行的,通常采用
receiver.method()
的形式。
- 在 Objective-C 中,方法调用是通过向对象发送消息来完成的,语法形式为
-
兼容性:
- Objective-C 完全兼容 C,因此可以直接包含 C 代码,并在 Objective-C 程序中使用 C 函数和数据结构。
- C++ 也兼容 C,但与 Objective-C 相比,C++ 更多地保留了 C 的原始风格,使得 C++ 程序可能更接近于传统的 C 程序。
-
标准库和第三方支持:
- C++ 提供了一个庞大的标准库,包括容器、算法、输入/输出操作等功能,以及广泛的第三方库支持。
- Objective-C 标准库相对较小,但它利用了 Cocoa 和 Cocoa Touch 框架的强大功能,这些框架是 macOS 和 iOS 开发的核心。
-
社区和生态系统:
- C++ 社区庞大且活跃,应用领域广泛,包括游戏开发、高性能计算、金融软件等。
- Objective-C 主要用于苹果平台的开发,尤其是在 macOS 和 iOS 上。
-
开发工具和环境:
- C++ 可以使用多种集成开发环境(IDEs)和编译器,如 Visual Studio、GCC 和 Clang。
- Objective-C 开发通常使用 Xcode,这是一个由 Apple 提供的 IDE,专为 macOS 和 iOS 开发而设计。
总的来说,Objective-C 更适合于开发 Apple 平台上的应用程序,而 C++ 则适用于各种操作系统和应用场景。选择哪种语言取决于项目需求和技术栈。
问: 什么是Objective-C中的消息传递?
在Objective-C中,消息传递是一种基本的编程机制,用于向对象发送消息以执行其特定的方法。它允许程序员在运行时动态地调用对象的方法,而不是像其他静态类型语言那样在编译时就确定了所有的方法调用。
消息传递的基本概念是:一个对象接收到一个带有选择子(selector)的消息后,会根据该选择子去查找对应的方法实现并执行。这个过程可以分为以下几个步骤:
-
编码阶段:
- 在源代码中编写方法调用,例如
[receiver message]
,其中receiver
是接收消息的对象,message
是要执行的方法名。 - 编译器会将这个方法调用转换为一条消息发送指令,并将选择子存储在程序中。
- 在源代码中编写方法调用,例如
-
运行时阶段:
- 当程序运行到这条消息发送指令时,会创建一个消息结构体,包含接收者、选择子以及可能的参数。
- 然后通过 Objective-C 的 Runtime 库来处理这个消息结构体,查找对应的方法实现(也称为 IMP,即 "implementation")。
-
消息转发阶段:
- 如果在接收者类和其超类中找不到与选择子匹配的方法实现,Runtime 会尝试进行消息转发。
- 消息转发是一个两步过程:首先尝试动态添加方法(如果实现了
+resolveInstanceMethod:
或+resolveClassMethod:
),然后若还是失败则使用-forwardInvocation:
方法来进行更灵活的转发处理。
由于 Objective-C 的动态特性,消息传递可以在运行时改变对象的行为,使得开发更加灵活。这种机制对于实现多态性、反射、动态代理等功能至关重要。
问: 请解释一下Objective-C中的继承。
Objective-C中的继承是一种面向对象编程(OOP)的特性,它允许一个类(子类或派生类)从另一个类(基类或父类)中获取和扩展属性与行为。在Objective-C中,所有类都直接或间接地继承自NSObject
,这是Objective-C的基础类。
继承的基本概念:
- 单一继承:Objective-C只支持单一继承,即每个类只有一个直接父类。
- 多级继承:尽管不允许多重继承(一个类同时继承多个类),但可以通过多级继承来模拟这种效果,即一个子类可以继承另一个子类。
继承的优点:
- 代码重用:通过继承,子类可以复用父类已经实现的功能,避免了重复编写相同的代码。
- 易于维护和扩展:当需要对系统进行修改或添加新功能时,只需修改或添加新的子类,而不需要更改现有的代码。
- 封装性:子类只能访问父类的公共接口,保持了数据的安全性和隐藏性。
继承的过程:
-
定义一个子类时,使用冒号
:
来指定其基类,例如:@interface DerivedClass : BaseClass
-
子类会自动拥有父类的所有实例变量、属性、方法以及协议等。
-
子类可以定义自己的实例变量、属性、方法以及协议,也可以重写(override)父类的方法以提供不同的实现。
继承和重写(Overriding):
- 子类可以重写父类的方法,提供特定于子类的行为。
- 重写的方法必须具有相同的名字、返回类型和参数列表。
- 在子类中调用被重写的方法,可以使用
super
关键字来调用父类的版本。
保护级别和继承:
- Objective-C中有三种保护级别:
@public
、@protected
和@private
。 - 默认情况下,实例变量是
@protected
的,这意味着子类可以访问它们。 @private
实例变量不能被子类访问。@public
实例变量可以在任何地方访问。
总之,Objective-C中的继承是一个强大的工具,用于组织代码、共享和扩展功能。然而,过度依赖继承可能会导致设计过于复杂,因此在实践中应谨慎使用,并考虑其他替代方案,如组合(composition)。
问: Objective-C中的类别(category)有什么作用?
Objective-C中的类别(Category)是一种扩展已有类功能的机制,它允许开发者向现有的类添加新的方法,而无需继承该类或修改原始类的源代码。类别有以下作用:
-
方法分组:
- 类别可以将一个大类的众多方法进行逻辑上的分类和组织,使得代码更易于理解和维护。
-
增加功能:
- 通过定义一个新的类别,可以在不修改原类的前提下为已存在的类添加新的方法。
- 这种方式特别适用于那些你没有权限或者不想修改其源代码的系统框架类。
-
私有化实现:
- 类别可以被用来隐藏实现细节,比如在同一个类的不同类别中分别实现公共接口和私有辅助方法。
-
避免命名冲突:
- 如果多个类别的名字相同,它们会被合并成一个类,这有助于避免不同模块之间的命名冲突。
-
重载系统方法:
- 类别可以用来覆盖系统的方法,但需要注意的是,如果两个类别都为同一个类提供了同名的方法,那么运行时会选择最后加载的那个类别的方法来执行。
-
匿名类别(Extension):
- Extension 是一种特殊的类别,它没有名称,通常用于声明私有方法和实例变量。
使用类别时需要注意一些限制:
- 类别不能添加新的实例变量,只能添加方法。
- 虽然类别可以访问原始类的实例变量,但不推荐直接这样做,因为这破坏了封装性。
- 如果需要添加实例变量或改变原有方法的行为,应考虑使用子类。
总之,Objective-C 中的类别是一种强大的特性,它允许程序员以非侵入的方式扩展类的功能,并且有助于提高代码的可读性和可维护性。
问: 请解释一下Objective-C中的委托模式。
Objective-C中的委托模式是一种设计模式,它允许一个对象(称为代理或委托对象)代表另一个对象(称为目标对象)执行特定的任务。这种模式通常用于实现松散耦合的系统,其中不同的组件可以通过定义明确的接口进行交互。
在Objective-C中,委托模式主要通过以下方式实现:
-
协议(Protocols):在Objective-C中,协议是一组方法声明的集合,它们描述了类需要实现的一组功能。协议本身并不包含任何实现代码,只是规定了实现某些方法的责任。协议通常以
.h
文件的形式定义,并使用@protocol
关键字。 -
代理属性:目标对象通常有一个代理属性,类型是遵循相关协议的对象。目标对象会将消息发送给这个代理对象,要求它执行某个任务。
-
设置代理:在运行时,目标对象可以设置其代理属性为任意遵守相应协议的对象。这通常是通过实例变量或者属性访问器方法完成的。
-
调用代理方法:当发生特定事件或需要执行特定操作时,目标对象会向它的代理发送消息,调用代理方法。这些方法都是在协议中声明过的。
-
响应代理方法:代理对象实现了协议中声明的方法,并根据需求处理这些消息。这可能是更新用户界面、处理数据等。
例如,考虑一个简单的应用,该应用具有一个视图控制器和一个网络请求类。网络请求类可能有一个代理属性,类型是一个遵循自定义协议的类。视图控制器可以作为这个网络请求类的代理,并实现协议中的方法来处理请求的结果。
下面是委托模式在Objective-C中的基本步骤:
-
定义一个协议,如:
@protocol MyDelegate <NSObject> - (void)requestDidFinish:(NSData *)data; - (void)requestDidFailWithError:(NSError *)error; @end
-
在目标对象(比如网络请求类)中定义一个代理属性,如:
@interface NetworkRequest : NSObject @property (nonatomic, weak) id<MyDelegate> delegate; // 其他方法... @end
-
设置代理,如:
ViewController *viewController = [[ViewController alloc] init]; NetworkRequest *request = [[NetworkRequest alloc] init]; request.delegate = viewController;
-
调用代理方法,如:
if ([self.delegate respondsToSelector:@selector(requestDidFinish:)]) { [self.delegate requestDidFinish:data]; }
-
实现协议方法,在代理对象(比如视图控制器)中,如:
- (void)requestDidFinish:(NSData *)data { // 更新UI或者处理数据... }
总的来说,委托模式在Objective-C中广泛应用于各种场景,特别是在用户界面编程中,比如UITableView的数据源和代理就是典型的例子。通过委托模式,我们可以将责任分散到多个类中,使得代码更加模块化和易于维护。
问: 如何使用Objective-C中的属性(property)?
Objective-C中的属性(property)是一种语法特性,用于声明和管理对象实例变量。属性定义了对类的实例变量的访问方式,包括读取(getter)、设置(setter)以及内存管理策略。以下是如何使用Objective-C中属性的详细步骤:
- 声明属性:
- 在类的接口(
.h
文件)中,使用@property
关键字声明一个或多个属性。 - 属性可以具有不同的修饰符来控制其行为,如
nonatomic
、readonly
、readwrite
、strong
、weak
、assign
和copy
等。
- 在类的接口(
@interface MyClass : NSObject
@property (nonatomic, strong) NSString *myString;
@property (nonatomic, assign) NSInteger myNumber;
@end
-
生成访问器方法:
- 当编译器看到
@property
声明时,会自动生成对应的访问器方法(getter 和 setter)。 - 如果没有显式地提供存取方法实现,编译器会自动合成这些方法。
- 当编译器看到
-
自定义存取方法:
- 有时可能需要定制存取方法的行为,例如添加额外的逻辑或者改变默认的内存管理策略。
- 在这种情况下,可以在类的实现(
.m
文件)中提供存取方法的实现,并在前面加上@synthesize
或者直接使用@dynamic
来告诉编译器是否应该生成默认的存取方法。
@implementation MyClass
@synthesize myString = _myString; // 如果不加这个,就隐含使用下划线后缀的实例变量名
- (NSString *)myString {
return [self.myString uppercaseString]; // 自定义 getter 行为
}
- (void)setMyString:(NSString *)myString {
if (![_myString isEqualToString:myString]) { // 自定义 setter 行为
self.myString = [myString copy];
NSLog(@"myString was set to %@", myString);
}
}
@end
-
内存管理策略:
- Objective-C 的属性可以指定内存管理策略,这取决于使用的修饰符。
- 对于非ARC环境,有
retain
(等同于ARC下的strong
)、assign
、copy
和unsafe_unretained
(等同于ARC下的weak
)等选项。 - 对于ARC环境,常用的属性修饰符是
strong
、weak
、unowned
(不安全的弱引用)和copy
。
-
多态性支持:
- 属性可以通过 KVC(Key-Value Coding)和 KVO(Key-Value Observing)机制支持多态性。
- 可以通过
[object valueForKey:@"propertyName"]
来获取属性值,即使该属性未被公开。
总的来说,Objective-C 中的属性提供了便捷的方式来管理对象的实例变量,并且能够根据需求进行定制。它们有助于提高代码的可读性和维护性,同时减少了重复代码的编写。
问: 请解释一下Objective-C中的ARC(自动引用计数)。
Objective-C中的ARC(Automatic Reference Counting)是一种内存管理机制,它自动为对象的生命周期进行管理。在使用ARC的情况下,编译器会在代码中插入适当的retain、release和autorelease消息,以确保对象在需要时被正确地创建、保留和销毁。
在Objective-C中,每个对象都有一个引用计数,表示有多少个指针指向该对象。当对象的引用计数为0时,系统会自动释放该对象占用的内存。在MRC(手动引用计数)模式下,程序员需要手动调用retain
和release
方法来增加或减少对象的引用计数。而在ARC模式下,这些操作由编译器自动完成。
以下是ARC的一些关键特性:
-
内存管理自动化:在ARC模式下,程序员不再需要手动调用
retain
、release
和autorelease
方法。编译器负责分析代码并插入必要的内存管理指令。 -
编译时检查:ARC通过静态分析来检测可能的内存管理错误,比如悬垂指针(dangling pointer)、循环引用等,并在编译阶段报告这些问题。
-
更强的类型安全:ARC引入了新的关键字,如
__strong
、__weak
、__unsafe_unretained
和__autoreleasing
,这些关键字可以更精确地控制变量的内存管理行为。 -
优化性能:ARC允许编译器进行更高级别的优化,因为它知道对象的生命周期和使用情况。
-
易于维护:由于内存管理是自动化的,所以ARC代码通常比MRC代码更容易理解和维护。
-
与MRC兼容:即使在一个项目中启用了ARC,也可以在某些文件或代码块中禁用ARC,以便处理一些特殊的情况。
要启用ARC,只需在Xcode项目的构建设置中将“Objective-C Automatic Reference Counting”选项设置为“YES”。一旦启用ARC,编译器就会在生成的可执行文件中包含所需的内存管理代码。
虽然ARC减轻了程序员对内存管理的负担,但并不意味着完全不需要理解内存管理。了解如何避免循环引用和其他潜在问题仍然是至关重要的。此外,对于低级别的内存管理操作,例如分配原始内存或者直接操作引用计数,仍然需要使用其他技术,如Core Foundation框架的CFRetain和CFRelease函数。
问: 什么是Objective-C中的协议(protocol)?
在Objective-C中,协议(protocol)是一种用于定义对象之间交互的一种规范。它是一组方法的声明,这些方法不必实现任何功能,但它们指定了遵循该协议的对象必须提供的接口。简而言之,协议是Objective-C中多态性的关键机制之一,允许类或类别(category)提供一组通用的方法签名,而无需指定具体的实现。
协议可以被看作是一个蓝图或者合同,规定了哪些方法应该被实现。一个类可以通过遵循(conform to)一个或多个协议来表明它可以响应那些由协议所定义的消息。这为编程中的解耦和模块化提供了便利,因为协议可以独立于具体实现进行设计和使用。
协议可以在其方法前使用@required
和@optional
关键字来标记方法是否必须实现。@required
表示遵循协议的类必须实现这个方法,否则编译器会报错;而@optional
则表示这个方法是可选的,类可以选择性地实现。
以下是一个简单的Objective-C协议的例子:
@protocol MyProtocol <NSObject>
@required
- (void)mandatoryMethod;
@optional
- (void)optionalMethod;
@end
在这个例子中,MyProtocol
定义了一个强制实现的方法mandatoryMethod
和一个可选实现的方法optionalMethod
。当一个类遵循这个协议时,它必须实现mandatoryMethod
,而optionalMethod
则是可选的。
需要注意的是,尽管协议类似于Java中的接口,但在Objective-C中,一个类可以遵循多个协议,这提供了更灵活的设计模式。
问: Objective-C中的代理(delegate)和协议(protocol)有什么区别?
在Objective-C中,代理(delegate)和协议(protocol)是两个紧密相关的概念,它们共同构成了委托模式的基础。然而,它们之间有明确的定义和用途:
代理(Delegate):
- 代理是一个设计模式,用于将一个对象的部分责任委托给另一个对象处理。
- 在Objective-C中,代理通常是一个指向遵循特定协议的对象的指针。
- 目标对象通过这个代理指针调用协议中声明的方法来执行某些任务。
协议(Protocol):
- 协议是一组方法的集合,这些方法由一个类或者对象来实现。
- 它们可以看作是一种接口定义,描述了某个类或对象应该具有的功能,但不提供具体的实现代码。
- 协议可以通过
@protocol
关键字进行定义,并使用< >
包含在一个类的接口声明中,表明该类遵循此协议。
简而言之,代理是指向遵循协议的对象的指针,而协议则是对一组方法的抽象描述。两者结合在一起,使得目标对象能够将部分工作委派给代理对象执行,从而实现松耦合的设计。这种模式在许多iOS和macOS的应用程序中都有广泛的应用,比如UITableView、UIScrollView等UIKit组件都使用了代理和协议来进行数据和行为的交互。
问: Objective-C中的“id”关键词有什么作用?
在Objective-C中,“id”是一个特殊的类型关键词,它表示任意一个Objective-C对象的指针。以下是“id”的一些主要作用:
-
通用性:
- “id”类型的变量可以保存任何Objective-C类的对象的引用,这使得它具有很高的通用性。
- 在编译时,编译器并不知道“id”变量所指向的具体类型,因此所有的方法调用都是动态执行的。
-
消息发送:
- 使用“id”类型的变量发送消息时,需要使用
objc_msgSend()
函数来完成。 - 由于编译器无法在编译时验证消息的有效性,所以如果向一个“id”变量发送了它不能响应的方法,程序可能会崩溃。
- 使用“id”类型的变量发送消息时,需要使用
-
多态性:
- “id”是实现多态性的关键,因为一个“id”变量可以在运行时指向不同的类实例,从而实现多种行为。
- 这种灵活性使得“id”成为Objective-C中处理未知类型对象的理想选择。
-
泛型编程:
- 由于Objective-C不支持像C++那样的模板或Java那样的泛型,因此“id”经常被用来模拟泛型功能。
- 它允许编写不受特定类型限制的代码,例如创建和操作数组、字典等容器。
-
接口设计:
- 在定义方法参数或者返回类型时,可以使用“id”来表明该方法可以接受或返回任何类型的对象。
- 这种设计方式可以提高代码的灵活性,但可能需要更多的运行时检查来确保正确性。
-
协议遵循:
- 当声明一个类遵循某个协议时,可以将协议名称放在尖括号(<>)内,并与“id”一起使用,如:“id
”。
- 当声明一个类遵循某个协议时,可以将协议名称放在尖括号(<>)内,并与“id”一起使用,如:“id
需要注意的是,尽管“id”提供了很大的灵活性,但也可能导致一些问题,比如丧失类型安全性和性能损失。因此,在实际编程中,应尽量减少对“id”的依赖,除非确实有必要处理未知类型的对象。