一、OC 是动态类型语言吗?OC 是强类型语言吗?为什么?
Objective-C (OC) 是一种动态类型语言,同时也是一种强类型语言。
-
动态类型语言:
在动态类型语言中,变量的类型是在运行时确定的,而不是在编译时确定的。在 Objective-C 中,你可以在运行时动态地创建类、修改类的行为、发送消息给对象等,这些特性使得 Objective-C 被归类为动态类型语言。
-
强类型语言:
强类型语言是指在编译时会进行严格的类型检查的语言。在 Objective-C 中,变量必须被明确定义为某种类型,而且在编译时会进行类型检查,以确保类型的正确性。这使得 Objective-C 也被归类为强类型语言。
为什么 Objective-C 同时被归类为动态类型语言和强类型语言呢?这是因为动态类型和强类型是两个不同的概念,它们并不互斥。一种语言可以同时具备动态类型和强类型的特性。在 Objective-C 中,动态类型主要体现在运行时的特性,比如动态消息传递、动态方法解析等;而强类型主要体现在编译时的特性,比如变量的类型必须在声明时确定,不同类型之间不能直接进行运算等。
总的来说,Objective-C 是一种既具有动态类型特性又具有强类型特性的语言。这些特性使得 Objective-C 在开发过程中具有灵活性和安全性,并且允许开发者在运行时进行许多高级的操作。
1.1 动态类型的编程语言有哪些?
动态类型的编程语言是指在运行时才确定变量类型的编程语言。这些语言通常允许变量的类型在运行时进行改变,具有灵活性和动态性。以下是一些常见的动态类型编程语言:
-
JavaScript: JavaScript 是一种动态类型的脚本语言,常用于前端开发和服务器端开发。变量的类型在运行时由赋值来确定,可以随时改变变量的类型。
-
Python: Python 也是一种动态类型的脚本语言,变量的类型在运行时由赋值来确定,可以随时改变变量的类型。
-
Ruby: Ruby 是一种动态类型的面向对象编程语言,变量的类型在运行时由赋值来确定,可以随时改变变量的类型。
-
Objective-C: Objective-C 是一种动态类型的面向对象编程语言,常用于 macOS 和 iOS 应用开发。它允许在运行时动态创建类、修改类的行为等。
-
PHP: PHP 是一种动态类型的服务器端脚本语言,变量的类型在运行时由赋值来确定,可以随时改变变量的类型。
-
Lua: Lua 是一种轻量级的动态类型脚本语言,常用于游戏开发和嵌入式系统。
这些语言都具有动态类型的特性,允许在运行时进行灵活的类型操作,但也因此需要更多的运行时检查和处理,开发者需要在使用这些语言时注意动态类型带来的潜在问题。
1.2 静态类型的编程语言有哪些?
静态类型的编程语言是指在编译时就确定了变量的类型,变量的类型在声明时就需要指定,并且在编译时进行类型检查。以下是一些常见的静态类型编程语言:
-
C: C 是一种静态类型的过程式编程语言,变量在声明时需要指定类型,并且在编译时进行类型检查。
-
C++: C++ 是在 C 语言基础上发展而来的一种静态类型的面向对象编程语言,同样需要在声明时指定变量的类型,并且在编译时进行类型检查。
-
Java: Java 是一种静态类型的面向对象编程语言,变量在声明时需要指定类型,并且在编译时进行类型检查。
-
C#: C# 也是一种静态类型的面向对象编程语言,变量在声明时需要指定类型,并且在编译时进行类型检查。
-
Swift: Swift 是一种苹果公司推出的静态类型的编程语言,用于 macOS 和 iOS 应用开发,变量在声明时需要指定类型,并且在编译时进行类型检查。
-
Kotlin: Kotlin 是一种静态类型的编程语言,可以与 Java 互操作,变量在声明时需要指定类型,并且在编译时进行类型检查。
这些语言都具有静态类型的特性,因此在编译时就能够进行类型检查,可以在一定程度上减少类型相关的错误,并且通常具有较好的性能。
1.3 强类型的编程语言有哪些?
强类型语言是指在编译时或运行时对数据类型进行严格的检查和限制的编程语言。以下是一些常见的强类型语言:
-
Java: Java 是一种静态类型的面向对象编程语言,它要求变量的类型在编译时就要确定,并且在运行时对类型进行严格的检查。
-
C++: C++ 也是一种强类型的编程语言,它要求在编译时对类型进行严格的检查。
-
C#: C# 是微软公司推出的一种强类型的面向对象编程语言,它要求在编译时对类型进行严格的检查。
-
Swift: Swift 是苹果公司推出的一种强类型的编程语言,用于 macOS 和 iOS 应用开发,它要求在编译时对类型进行严格的检查。
-
Kotlin: Kotlin 是一种静态类型的编程语言,它也要求在编译时对类型进行严格的检查。
-
Haskell: Haskell 是一种强类型的函数式编程语言,它在类型系统方面非常严格。
这些语言都具有强类型的特性,要求在编译时或运行时对类型进行严格的检查和限制,从而提高了程序的安全性和稳定性。
1.4 弱类型的编程语言有哪些?
弱类型语言是指在编程时对数据类型的限制比较宽松,允许隐式类型转换或者在运行时进行自动类型转换的编程语言。以下是一些常见的弱类型语言:
-
JavaScript: JavaScript 是一种动态类型的弱类型语言,它允许在运行时进行隐式类型转换,例如将字符串和数字相加时会自动进行类型转换。
-
PHP: PHP 也是一种动态类型的弱类型语言,它在类型转换方面比较宽松,允许在运行时进行自动类型转换。
-
Python: Python 是一种动态类型的弱类型语言,它允许在运行时进行隐式类型转换,例如在列表中同时存储不同类型的元素。
-
Ruby: Ruby 也是一种动态类型的弱类型语言,它在类型转换方面相对宽松,允许在运行时进行自动类型转换。
-
Perl: Perl 是一种弱类型的解释型语言,它在类型转换方面比较宽松,允许在运行时进行自动类型转换。
这些语言都具有弱类型的特性,允许在运行时进行隐式类型转换,这样可以提供更大的灵活性,但也可能增加代码的不确定性和出错的概率。
二、OC语言,把一个B类型对象赋值给A类型可以吗?为什么?
在 Objective-C 中,可以将一个 B 类型的对象赋值给 A 类型,因为 Objective-C 是一种动态类型的语言,它使用的是指针来引用对象,而不是直接操作对象本身。因此,在 Objective-C 中,对象的类型信息是存储在指针中的,而不是对象本身。
这意味着,即使两个类之间没有继承关系,也可以将一个类的对象指针赋值给另一个类的对象指针,编译器不会报错。但是在运行时,如果实际对象的类型与指针所声明的类型不匹配,就会导致类型不匹配的异常。
在改变指针类型以后可以继续调用原来类型的方法,编译期没问题,运行时如果在新类型没有对应方法会crash;也可以通过强转来调用新类型的方法,编译和运行都没有问题,但是必须要强转才能调用,否则编译不过,因为编译期并不知道类型改变了。
这种动态类型的特性使得 Objective-C 具有更大的灵活性,但也增加了在运行时出现类型相关的错误的潜在风险。
三、OC 对象占用内存大小
isa 指针大小 + 实例变量大小
最小是 16 字节,一个 isa 指针占用 8 字节,还需要内存对齐,所以就是最少 16 字节。
Objective-C 对象占用的内存大小取决于对象的实例变量、指针大小和一些额外的内存开销。具体来说,Objective-C 对象占用的内存大小由以下几个部分组成:
-
实例变量: Objective-C 对象中包含了实例变量,其占用的内存大小取决于实例变量的类型和数量。
-
isa 指针: 每个 Objective-C 对象都包含一个 isa 指针,用于指向对象的类。isa 指针的大小取决于系统架构,通常在 64 位系统中占用 8 个字节,32 位系统中占用 4 个字节。
-
额外内存开销: Objective-C 运行时需要一些额外的内存来存储对象的引用计数、锁信息等。
总的来说,Objective-C 对象的内存大小是动态的,取决于实例变量的大小和数量,以及额外的内存开销。如果需要精确计算一个特定对象占用的内存大小,可以使用 sizeof
运算符来获取对象的大小。
需要注意的是,Objective-C 2.0 引入了自动引用计数(ARC),这会影响对象的内存管理方式,但不会显著改变对象本身占用的内存大小。
四、OC 编译过程
Objective-C 的编译过程可以分为预处理、编译、汇编和链接四个阶段:
-
预处理(Preprocessing): 在这个阶段,预处理器会处理源文件,包括展开宏定义、处理条件编译指令(如
#ifdef
、#endif
等)、包含头文件等。预处理器会生成一个经过预处理的中间文件。 -
编译(Compilation): 经过预处理的中间文件会被编译器处理,编译器会将源代码翻译成汇编代码。在 Objective-C 中,编译器会将 Objective-C 代码翻译成相应的 C 代码。
-
汇编(Assembly): 汇编器将汇编代码翻译成机器码,生成目标文件(通常是以 .o 或 .obj 为扩展名的文件)。
-
链接(Linking): 链接器将目标文件与库文件进行链接,生成可执行文件。在 Objective-C 中,链接器会将 Objective-C 运行时库和其他必要的库文件链接到可执行文件中。
需要注意的是,Objective-C 的编译过程中还涉及到 Objective-C 运行时库的支持,因为 Objective-C 是一种动态语言,它需要在运行时进行方法调度、消息传递等操作,这些都依赖于 Objective-C 运行时库的支持。因此,在编译过程中,链接器会链接 Objective-C 运行时库以支持 Objective-C 代码的运行。
总的来说,Objective-C 的编译过程与传统的编译过程类似,但在链接阶段会涉及到 Objective-C 运行时库的支持。
4.1 程序在链接过程都做了什么?
在程序的链接过程中,链接器会执行以下主要任务:
-
符号解析(Symbol Resolution): 链接器会解析程序中引用的函数、全局变量以及其他符号。它会在可执行文件中查找这些符号的定义,或者在库文件中查找对应的符号,然后建立符号引用和符号定义之间的关联。
-
重定位(Relocation): 当符号解析完成后,链接器会对代码和数据的地址进行重定位,以确保它们能够正确地访问到符号的定义。这包括调整代码中的跳转指令和数据中的地址引用,使得它们指向正确的内存位置。
-
符号合并(Symbol Merging): 如果多个目标文件中定义了相同的全局符号(函数或变量),链接器会进行符号合并,保留其中一个定义,并移除其他重复的定义。
-
库文件链接(Library Linking): 链接器会将程序所需的库文件链接到可执行文件中,以便在程序运行时能够访问库中定义的函数和变量。
-
生成可执行文件(Executable Generation): 最终阶段,链接器将经过符号解析、重定位和库文件链接处理后的目标文件组合成最终的可执行文件,其中包含了程序的代码和数据,以及所需的库函数和运行时支持。
总的来说,链接器的主要任务是将各个目标文件和库文件组合成一个可执行文件,并确保其中的符号引用能够正确地与符号定义关联起来。这样,程序在运行时才能够正确地执行各种函数调用和数据访问操作。
五、OC 支持函数重载吗
Objective-C 不支持像 C++ 或 Swift 那样的函数重载。在 Objective-C 中,函数名是唯一的,不能根据参数的不同来重载同名函数。如果定义了两个同名的方法,编译器会认为它们是相同的方法,而不是重载关系。
在 Objective-C 中,如果需要实现类似函数重载的功能,可以通过使用不同的方法名或者参数来区分不同的方法。例如,可以在方法名后面添加参数信息来区分不同的方法,或者在方法内部根据参数的类型或个数来实现不同的逻辑。
另外,在 Objective-C 中还可以使用多参数的方法,通过使用 ...
来接受可变参数列表。这样可以实现一个方法接受不定数量的参数,类似于函数重载的效果。
总的来说,虽然 Objective-C 不支持函数重载,但可以通过其他方式来实现类似的功能。
六、OC中,== isEqual hash 的用法
在 Objective-C 中,==
、isEqual:
和 hash
是用于比较对象的方法,它们各自有不同的作用:
==
==
用于比较两个对象的地址是否相同,即判断两个对象是否是同一个对象的实例。- 通常情况下,使用
==
来比较两个对象是否相等,实际上是在比较它们的内存地址是否相同。
isEqual:
isEqual:
是 NSObject 类的方法,用于比较两个对象的内容是否相等。- 在默认情况下,
isEqual:
方法会比较两个对象的内存地址,即和==
的作用类似。 - 但是,可以在自定义类中重写
isEqual:
方法,来实现自定义的对象比较逻辑。在重写isEqual:
方法时,通常需要同时重写hash
方法。
hash
hash
是 NSObject 类的方法,用于返回对象的哈希值。- 在默认情况下,
hash
方法会返回对象的内存地址的哈希值。 - 通常情况下,如果两个对象通过
isEqual:
方法比较相等,那么它们的hash
值也应该相等。因此,在重写isEqual:
方法时,通常也需要同时重写hash
方法来保持一致性。
在自定义类中,如果需要进行对象的比较,通常会同时重写 isEqual:
和 hash
方法,以确保对象的比较逻辑正确。当然,对于一些简单的比较,直接使用 ==
比较对象的地址也是可以的。
七、可变数组的实现原理
在 Objective-C 中,可变数组(NSMutableArray
)的实现原理主要是基于动态数组(Dynamic Array)和动态内存管理的概念。可变数组是一种动态数据结构,可以根据需要动态增加或删除元素,而不需要事先指定数组的大小。
下面是可变数组的一般实现原理:
-
动态数组:
- 可变数组通常使用动态数组来存储元素。动态数组在内部使用一个固定大小的数组来存储元素,当数组空间不足时,会自动扩展数组的大小,以容纳更多的元素。
- 当向可变数组添加元素时,会检查当前数组的容量是否足够,如果不够,则会重新分配一块更大的内存空间,并将原数组中的元素复制到新的内存空间中。
-
动态内存管理:
- 可变数组使用动态内存管理来管理内部的数组空间。在需要扩展数组空间时,会动态分配新的内存空间,并将原有元素复制到新的内存空间中。
- 当可变数组不再需要某个元素时,会释放该元素所占用的内存空间,以便回收内存。
-
自动调整容量:
- 可变数组通常会预留一定的额外容量,以避免频繁地扩展数组空间。当数组空间不足时,会根据一定的策略(如倍增策略)扩展数组的容量,以提高性能。
总的来说,可变数组通过动态数组和动态内存管理的方式实现了动态增加和删除元素的功能,同时也会根据需要自动调整数组的容量,以提高效率和性能。
八、OC中,怎么 hook 方法?怎么才能在不影响其他对象的条件下 hook 方法?
在 Objective-C 中,可以使用 Runtime(运行时)机制来实现方法的 Hook。方法的 Hook 是指在方法调用前后插入自定义的逻辑,从而可以修改方法的行为或监控方法的调用情况。
下面是一种常见的方法 Hook 实现方式:
-
使用 Runtime 进行方法交换:
- 可以使用 Runtime 中的
class_replaceMethod
函数来交换方法的实现。具体步骤如下:
a. 获取需要 Hook 的类和方法。
b. 动态创建一个新的方法,用于替换原方法的实现。
c. 使用class_replaceMethod
函数将原方法的实现替换为新方法的实现。
d. 在新方法中调用原方法的实现,并在需要的地方插入自定义逻辑。
- 可以使用 Runtime 中的
-
保证不影响其他对象的条件下 Hook 方法:
- 为了确保在 Hook 方法时不影响其他对象,可以采取以下措施:
a. 在 Hook 方法前先保存原方法的实现,以便在需要时调用原方法。
b. 在新方法中调用原方法的实现,以保证原方法的功能不受影响。
c. 尽量避免对其他对象的方法进行 Hook,仅对需要的类和方法进行 Hook。
- 为了确保在 Hook 方法时不影响其他对象,可以采取以下措施:
下面是一个简单的示例代码,演示如何使用 Runtime 进行方法的 Hook:
objective-c
#import <objc/runtime.h>
@interface MyClass : NSObject
- (void)originalMethod;
@end
@implementation MyClass
- (void)originalMethod {
NSLog(@"Original method is called");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyClass *myObject = [[MyClass alloc] init];
Method originalMethod = class_getInstanceMethod([MyClass class], @selector(originalMethod));
IMP newMethodImplementation = imp_implementationWithBlock(^{
NSLog(@"Hooked method is called");
// Call original method implementation
((void (*)(id, SEL))method_getImplementation(originalMethod))(myObject, @selector(originalMethod));
});
class_replaceMethod([MyClass class], @selector(originalMethod), newMethodImplementation, method_getTypeEncoding(originalMethod));
[myObject originalMethod]; // This will call the hooked method
}
return 0;
}
在上述示例中,我们通过 Runtime 的函数实现了对 MyClass
类中 originalMethod
方法的 Hook。在新方法中,我们插入了自定义的逻辑,并在需要时调用了原方法的实现,以确保原方法的功能不受影响。
九、OC中,类别(Category)和扩展(Extension)的区别
在 Objective-C 中,类别(Category)和扩展(Extension)都是用来对类进行扩展的机制,但它们有一些区别:
-
类别(Category):
- 类别是一种在不修改原始类的情况下,为现有类添加新方法的方式。
- 类别可以为现有的类添加实例方法、类方法、甚至可以添加属性。
- 类别的方法在运行时会被合并到原始类中,因此可以在不修改原始类的情况下扩展类的功能。
- 类别适用于在不方便修改原始类的情况下,为类添加新的方法或功能。
-
扩展(Extension):
- 扩展是一种特殊的类别,它在实现文件(.m 文件)中使用,用于声明私有的方法和属性。
- 扩展可以在类的内部声明私有的方法和属性,这些方法和属性只能在当前类的实现文件中访问,对外部是不可见的。
- 扩展通常用于将类的私有方法和属性放在一个独立的扩展中,以便更好地组织和管理代码。
总的来说,类别和扩展都可以用来对类进行扩展,但类别更多用于为类添加新的方法或功能,而扩展更多用于声明私有的方法和属性。类别的方法会被合并到原始类中,而扩展的方法和属性只能在当前类的实现文件中访问。
十、OC中,main 函数之前做的事
在 Objective-C 程序中,main 函数之前通常会做一些初始化工作,以确保程序能够正常运行。主要的初始化工作包括:
-
导入头文件:
- 在 main 函数之前,通常会导入需要使用的头文件,包括系统框架的头文件和自定义类的头文件。这样可以确保在后续代码中可以正确地引用和调用相应的类和方法。
-
设置自动释放池:
- 在 main 函数之前,一般会创建一个自动释放池(@autoreleasepool),用于管理内存的释放。自动释放池可以确保在作用域结束时自动释放对象,避免内存泄漏。
-
初始化应用程序:
- 在 main 函数之前,可能会进行应用程序的初始化工作,包括设置应用程序的一些全局配置、初始化一些全局变量或单例对象等。
-
注册类和方法:
- 在 main 函数之前,可能会注册一些类和方法,以便后续可以通过类名或方法名来获取相应的类或方法。
-
执行其他初始化操作:
- 在 main 函数之前,还可能会执行其他一些初始化操作,例如配置日志系统、设置全局变量、初始化第三方库等。
总的来说,main 函数之前的工作主要是为程序的正常运行做准备,包括导入头文件、设置自动释放池、初始化应用程序、注册类和方法等。这些初始化工作可以确保程序在启动时能够顺利执行。
十一、iOS动态库和静态库
在 iOS 开发中,动态库(Dynamic Library)和静态库(Static Library)都是用来存储可重用代码的库文件,但它们有一些区别:
-
静态库:
- 静态库是在编译时被链接到应用程序中的库文件,它的代码会被完整地复制到应用程序的可执行文件中。
- 静态库的扩展名通常为
.a
,例如libMyStaticLibrary.a
。 - 静态库的优点是简单易用,集成到应用程序中后不会受到外部环境的影响,可以避免因为库的更新导致应用程序出现问题。
- 静态库的缺点是会增加应用程序的体积,并且如果多个应用程序使用同一个静态库,会导致代码的重复。
-
动态库:
- 动态库是在运行时被加载到内存中的库文件,应用程序在运行时动态链接到动态库。
- 动态库的扩展名通常为
.dylib
或.framework
,例如libMyDynamicLibrary.dylib
或MyFramework.framework
。 - 动态库的优点是可以减小应用程序的体积,多个应用程序可以共享同一个动态库,避免代码的重复。
- 动态库的缺点是需要确保设备上有相应的动态库文件,否则应用程序无法正常运行;另外,动态库的加载会稍微影响应用程序的启动速度。
总的来说,静态库在编译时被链接到应用程序中,而动态库在运行时被加载到内存中。选择使用静态库还是动态库取决于具体的需求,如体积、可维护性、共享性等因素。在 iOS 开发中,通常使用 Cocoa Touch Framework 来创建动态库,以便在多个应用程序之间共享代码。
十二、iOS,如何优化应用启动时间
优化应用启动时间在 iOS 开发中非常重要,用户体验受到启动速度的直接影响。以下是一些优化应用启动时间的方法:
-
延迟加载:
- 尽量延迟加载不必要的资源和模块,只在需要时再进行加载。可以使用懒加载(Lazy Loading)的方式,避免在应用启动时加载过多的资源。
-
精简启动过程:
- 优化应用启动过程,尽量减少启动时需要执行的代码和初始化操作。可以通过分析启动过程中的瓶颈,去除不必要的操作,提高启动速度。
-
启动图片优化:
- 确保启动图片(Launch Screen)的设计简单明了,避免过多复杂的动画和效果,以减少启动时间。
-
代码优化:
- 优化应用代码,避免过多的循环和复杂的逻辑,提高代码执行效率。可以使用工具检测和优化性能瓶颈,减少不必要的计算和内存消耗。
-
使用异步加载:
- 尽量使用异步加载数据和资源,避免阻塞主线程,提高应用的响应速度。可以使用 GCD(Grand Central Dispatch)或 OperationQueue 等技术来实现异步加载。
-
预加载数据:
- 在应用启动时预加载一部分数据,以提前准备好应用所需的数据,避免在应用运行时再去加载数据,从而减少启动时间。
-
优化第三方库:
- 使用轻量级的第三方库,避免加载过多的不必要功能和代码,以减少应用的启动时间。
-
启动时间监控:
- 使用 Xcode 的 Instruments 工具进行启动时间的监控和分析,找出应用启动过程中的性能瓶颈,并进行相应的优化。
通过以上方法,可以有效地优化应用的启动时间,提升用户体验,让应用在启动时更加快速流畅。
十三、KVO 实现原理
KVO(Key-Value Observing)是 Cocoa 框架中的一种机制,用于实现对象的属性监听。当被监听的对象的属性发生变化时,注册的观察者会收到通知。KVO 的实现原理主要涉及以下几个步骤:
-
注册观察者:
- 被观察的对象调用
addObserver:forKeyPath:options:context:
方法,向系统注册观察者。观察者需要实现observeValueForKeyPath:ofObject:change:context:
方法来接收属性变化通知。
- 被观察的对象调用
-
动态子类:
- 在注册观察者时,系统会动态地为被观察的对象生成一个中间类(dynamic subclass),这个中间类会重写被观察属性的 setter 方法。这样,当属性值发生变化时,会调用重写的 setter 方法。
-
属性变化通知:
- 当被观察属性的值发生变化时,系统会调用动态子类的 setter 方法,而不是直接调用原始对象的 setter 方法。在动态子类的 setter 方法中,会发送属性变化通知给注册的观察者。
-
观察者接收通知:
- 观察者实现的
observeValueForKeyPath:ofObject:change:context:
方法会接收到属性变化的通知。在这个方法中,观察者可以处理属性变化的情况,例如更新 UI 或执行其他操作。
- 观察者实现的
-
移除观察者:
- 当不再需要监听属性变化时,观察者需要调用
removeObserver:forKeyPath:
方法来取消注册。这样可以确保避免潜在的内存泄漏和不必要的通知。
- 当不再需要监听属性变化时,观察者需要调用
总的来说,KVO 的实现原理是通过动态子类来重写被观察属性的 setter 方法,从而实现属性变化的通知机制。开发者在使用 KVO 时需要注意内存管理和线程安全等问题,避免潜在的风险。
十四、OC中,load 与 initialize
在 Objective-C 中,load
方法和 initialize
方法都是类方法,用于在类加载时执行特定的操作。它们之间有一些区别,下面对它们进行简要的比较:
-
load 方法:
load
方法是在程序启动时,类加载到内存时调用的方法。每个类的load
方法只会被调用一次,无论是否有子类。如果子类没有实现load
方法,会调用父类的load
方法。load
方法是在所有类和分类加载到内存之后,main 函数执行之前调用的。因此,load
方法中可以进行全局的初始化操作,如方法交换、注册通知等。- 注意:不应该在
load
方法中调用实例方法,因为此时类还没有完全初始化,可能会导致未定义的行为。
-
initialize 方法:
initialize
方法是在类或其子类的第一个方法被调用之前调用的方法。每个类的initialize
方法只会被调用一次,且是在第一次使用该类时调用。- 如果子类没有实现
initialize
方法,会调用父类的initialize
方法。并且,如果子类实现了initialize
方法,但没有调用父类的initialize
方法,则父类的initialize
方法也会被调用。 initialize
方法可以用于进行类的初始化操作,例如设置静态变量的初始值、注册通知等。- 注意:
initialize
方法可能会被多线程同时调用,因此需要注意线程安全性。
总的来说,load
方法是在类加载到内存时调用的全局初始化方法,而 initialize
方法是在类或其子类的第一个方法被调用前调用的类初始化方法。开发者可以根据需求选择适合的方法来进行类的初始化操作。
十五、Block
15.1 Block的用法
在 iOS 开发中,Block 是 Objective-C 中的一种语法特性,用于封装一段代码并在需要时执行。Block 可以捕获其定义时的上下文信息,包括变量、常量和函数等,形成一个闭包,可以在其定义的范围外执行。以下是关于 iOS 中 Block 的一些重要信息:
-
Block 的语法:
- Block 的语法类似于函数指针,使用
^
符号定义,例如:^returnType (parameters) {...}
。 - Block 可以被赋值给变量或作为参数传递给方法。
- Block 可以在定义时捕获外部变量,并在执行时访问这些变量的值。
- Block 的语法类似于函数指针,使用
-
Block 的类型:
- Block 可以根据其捕获的上下文信息分为三种类型:
- 全局 Block:不捕获任何外部变量的 Block,类似于普通函数。
- 堆上的 Block:捕获外部变量并在堆上分配内存的 Block,可以在定义范围外执行。
- 栈上的 Block:只在定义范围内有效的 Block,一旦超出定义范围就会失效。
- Block 可以根据其捕获的上下文信息分为三种类型:
-
Block 的使用场景:
- Block 可以用于实现回调函数、异步操作、动画效果等。
- 在 GCD(Grand Central Dispatch)中,常用 Block 来执行并发任务。
- 在网络请求、动画处理、数据处理等场景下,Block 也经常被使用。
-
Block 的内存管理:
- 在使用 Block 时需要注意避免循环引用(retain cycle)问题,可以使用
__weak
或__block
来避免循环引用。 - 当 Block 捕获外部变量时,会对这些变量进行强引用,因此需要注意内存管理。
- 在使用 Block 时需要注意避免循环引用(retain cycle)问题,可以使用
总的来说,Block 是 iOS 开发中非常有用的语法特性,可以方便地封装一段代码并在需要时执行。熟练掌握 Block 的语法和使用方法可以提高代码的可读性和灵活性。
15.2 谈谈对Block的理解
在 iOS 开发中,Block 是一种闭包(Closure)的概念,它允许我们将一段代码封装起来,并在需要时进行调用。Block 在 Objective-C 和 Swift 中都有广泛的应用,可以用于实现回调、异步操作、事件处理等功能。以下是我对 iOS Block 的理解:
-
闭包特性:
- Block 是一种闭包,它可以捕获其定义时的上下文信息,包括变量、常量和函数等。这意味着在定义 Block 时可以访问外部变量,并在执行时使用这些变量的值。
- 闭包还可以作为参数传递给函数或方法,从而实现回调函数的功能。这种特性使得 Block 在异步编程中非常有用,可以方便地处理异步任务的结果。
-
简洁的语法:
- Block 的语法相对简洁,使用
^
符号定义,可以直接在代码中定义匿名函数。这种语法特性使得代码更加紧凑和易读,尤其在需要传递一小段代码的情况下非常方便。
- Block 的语法相对简洁,使用
-
方便的异步操作:
- 在 iOS 中,Block 与 GCD(Grand Central Dispatch)结合使用,可以方便地实现异步操作。通过将任务封装在 Block 中,可以在后台线程执行任务,并在完成后回到主线程更新 UI。
-
内存管理:
- 在使用 Block 时需要注意内存管理,特别是避免循环引用问题。可以使用
__weak
或__block
来避免循环引用,确保在合适的时机释放 Block 对象。
- 在使用 Block 时需要注意内存管理,特别是避免循环引用问题。可以使用
总的来说,iOS Block 是一种强大的语法特性,能够简化代码、实现异步操作并提高代码的可读性。通过深入理解 Block 的特性和使用方法,开发者可以更好地利用它来处理各种场景下的编程需求。
十六、Notification 通知
16.1 谈谈你对iOS Notification的理解
在 iOS 开发中,Notification(通知)是一种用于在应用内部或应用之间进行消息传递的机制。通知中心(NotificationCenter)负责管理通知的发布和订阅,开发者可以使用通知来实现模块之间的解耦、消息传递和事件响应等功能。以下是我对 iOS Notification 的理解:
-
发布-订阅模式:
- iOS Notification 使用了发布-订阅(Publish-Subscribe)模式,其中一个模块发布通知,而其他模块可以订阅并接收通知。这种模式可以实现模块之间的解耦,让不同模块之间的通信更加灵活。
-
通知中心:
- NotificationCenter 是 iOS 中负责管理通知的类,开发者可以通过 NotificationCenter 来发布和订阅通知。通知中心采用单例模式,可以在整个应用中共享通知。
-
通知的类型:
- 在 iOS 中,通知分为两种类型:普通通知和用户通知。
- 普通通知(NSNotification):用于在应用内部传递消息,不涉及用户交互。
- 用户通知(User Notifications):用于处理用户与应用之间的交互,例如推送通知、本地通知等。
- 在 iOS 中,通知分为两种类型:普通通知和用户通知。
-
通知的使用场景:
- 通知可以用于模块之间的通信,例如当某个模块的状态发生变化时,可以通过通知通知其他模块。
- 通知也可以用于处理用户事件,例如在收到推送通知时触发相应的操作。
- 通知还可以用于监听系统事件,例如应用进入后台、网络状态变化等。
-
通知的解除:
- 在使用通知时,需要注意及时解除订阅,避免内存泄漏和不必要的通知传递。可以在合适的时机调用
removeObserver
方法解除通知的订阅。
- 在使用通知时,需要注意及时解除订阅,避免内存泄漏和不必要的通知传递。可以在合适的时机调用
总的来说,iOS Notification 是一种非常有用的消息传递机制,可以帮助开发者实现模块之间的解耦、事件响应和用户交互等功能。通过合理地使用通知,开发者可以更好地组织代码、提高应用的灵活性和可维护性。
16.2 IOS中,页面销毁时不移除通知会崩溃吗
iOS 9 之后就不会崩溃了,系统会自己移除。iOS9.0之前,会crash。
在 iOS 中,如果在页面销毁时没有移除通知,可能会导致一些问题,但不一定会导致应用崩溃。以下是一些可能出现的问题:
-
内存泄漏:
- 如果在页面销毁时没有移除通知的观察者,通知中心会继续保留对观察者的引用,导致观察者无法被释放,从而造成内存泄漏。长时间的内存泄漏可能会导致应用占用过多内存而被系统终止。
-
重复接收通知:
- 如果在页面销毁时没有移除通知的观察者,下次再次创建相同页面时,之前注册的通知观察者仍然存在,可能会导致重复接收通知的情况,从而引发逻辑错误。
-
野指针访问:
- 如果在页面销毁时没有移除通知的观察者,当通知发送时,观察者已经被释放,通知中心会尝试调用已经释放的观察者对象,可能导致野指针访问而引发崩溃。
虽然不移除通知可能会导致上述问题,但并不是一定会导致应用崩溃。系统会在一定程度上容忍这种行为,但是为了确保应用的健壮性和稳定性,建议在页面销毁时及时移除通知的观察者。可以在 deinit
方法中调用 removeObserver
方法来移除通知的观察者,确保不再接收通知。
16.3 iOS,多次添加同一个通知会是什么结果?多次移除通知呢
在 iOS 中,多次添加同一个通知(即同一个观察者对同一个通知进行多次注册)和多次移除同一个通知会有以下结果:
-
多次添加同一个通知:
- 如果同一个观察者对同一个通知进行多次注册,通知中心会记录每一次注册,但只会收到一次通知。也就是说,即使多次注册,观察者只会收到一次通知。
-
多次移除同一个通知:
- 如果同一个观察者对同一个通知进行多次移除,通知中心会根据每次移除的请求进行处理。通知中心会尝试移除与每次移除请求匹配的通知观察者,如果成功移除则返回 true,否则返回 false。
- 如果观察者多次移除同一个通知,但实际上只注册了一次,那么第一次移除会成功,后续的移除操作会返回 false,因为通知中心已经移除了观察者,无法再次移除。
总的来说,多次添加同一个通知并不会导致重复接收通知,而多次移除同一个通知会根据实际情况进行处理。在实际开发中,为了避免不必要的操作,建议在添加通知和移除通知时保持一致,确保每次添加通知都对应一次移除通知操作。