前言
iOS中是怎么对内存进行管理的?
iOS中有哪些多线程?
Block是什么?有哪些?
iOS中如何保存数据?有哪些持久化存储机制?
什么是浅拷贝?什么是深拷贝?
1、iOS中是怎么对内存进行管理的?
为什么要进行内存管理
?
程序在运行的过程中,往往涉及到创建对象
、定义变量
、调用函数或方法
,而这些行为都会增加程序的内存占用。
一个移动设备
的内存是有限
的,每个软件所能占用的内存也是有限的。当程序所占用的内存较多时,系统就会发出内存警告
,这时就得回收
一些不需要再使用的内存空间
。比如不再使用的对象
、变量
等。
如果程序占用内存过大
,系统可能会强制关闭
程序,造成崩溃
、闪退
现象,影响用户体验
。所以我们需要对内存进行合理的分配
、清除内存
、回收不再使用的对象
,从而保证程序的稳定性
。
内存组成是什么
?
-
代码区
:用于存放程序的代码
,即CPU执行的机器指令
,是只读
的。代码区通常也位于代码段
。 -
常量区
:存储常量数据
,如字符串常量。用于存储已经初始化的常量,这些数据在程序运行期间不可被修改
,程序结束后由系统释放。常量区通常位于代码段
。 -
全局(静态)区
:用于存储全局变量
和静态变量
,其生命周期与整个应用程序的运行周期相同
。全局变量
存储在数据段
,静态变量
存储在BSS段(未初始化数据段
)。 -
堆区
:是用于动态
分配内存的区域,用于存放进程运行中被动态分配
的内存段。在堆中分配的内存由程序员负责管理
,通常通过malloc
、free
或new
、delete
等操作。堆的大小通常受操作系统
和硬件
限制,且动态增长
。 -
栈区
:用于存放程序临时创建的变量
、存放函数的参数值
、局部变量
等。由编译器
自动分配释放。
怎么对内存进行管理
?
在iOS中,内存管理是一个重要
而且必要
的任务,以确保应用运行时不会出现内存泄露
或内存过度使用
问题。以下是一些iOS对内存进行管理的关键概念和方法:
- 使用ARC的情况下
1、自动管理引用计数:
在ARC环境下,引用计数是自动管理的。你无需手动调用retain
、 release
和autorelese
。
// 示例:使用强引用
NSString *strongReference = [[NSString alloc] initWithFormat:@"Hello, World!"];
// 示例:使用弱引用
__weak NSString *weakReference = strongReference;
2、避免循环引用:
在Block中使用__weak
来引用对象,以免循环引用(说明下,并非所有的Block都能产生循环引用
)。
__weak typeof(self) weakSelf = self;
someBlock = ^{
[weakSelf doSomething];
};
- 在非ARC环境下
1、手动管理引用计数:
在非ARC环境下,需要手动管理引用计数,手动调用retain
和release
。
// 示例:手动管理引用计数
NSString *manualReference = [[NSString alloc] initWithFormat:@"Hello, World!"];
[manualReference retain]; // 增加引用计数
[manualReference release]; // 减少引用计数
2、使用自动释放池:
在非ARC环境下,可以使用自动释放池来延迟释放对象
// 示例:使用自动释放池
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *autoreleaseReference = [[[NSString alloc] initWithFormat:@"Hello, World!"] autorelease];
[pool drain]; // 手动释放池中的对象
2、iOS中有哪些多线程?有什么区别?
在iOS中,多线程是一种并发编程
的技术,允许应用程序同时
执行多个任务
。以下是iOS中常用的多线程技术,以及它们之间的一些区别:
-
NSThread
:是Objective-C中的一个轻量级多线程类。你可以使用NSThread
来创建和管理线程。但需要注意的是,NSThread
的使用相对底层(提供了较为基础的线程管理接口
),需要手动管理线程的生命周期和线程执行。
a、线程的创建和启动
需要手动
创建线程对象、设置线程的执行方法、启动线程,并在适当的时机手动管理线程的生命周期
。
// 创建线程对象
NSThread *myThread = [[NSThread alloc] initWithTarget:self selector:@selector(myThreadMainMethod) object:nil];
// 启动线程
[myThread start];
b、线程同步和通信
提供了基础的线程同步和通信机制,例如使用performSelector:onThread:withObject:waitUntilDone:
方法来在不同线程中执行方法(UI更新(主线程上)
、后台任务通知主线程
、线程之间的通信
)。但相较于其他更高级的多线程技术,NSThread
对线程同步较为有限
。
1)、UI更新
[self performSelector:@selector(updateUI) onThread:[NSThread mainThread] withObject:nil waitUntilDone:NO];
2)、后台任务通知主线程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 后台任务完成
// 通知主线程执行操作
[self performSelector:@selector(taskCompleted) onThread:[NSThread mainThread] withObject:nil waitUntilDone:NO];
});
3)、线程之间的通信
// 在后台线程中执行任务
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self performSelector:@selector(taskCompleted) onThread:myThread withObject:nil waitUntilDone:NO];
});
-
GCD
:由苹果提供的高级多线程技术,用于管理并发任务。GCD提供了一个简单而强大的API,通过队列来管理任务的执行。GCD负责调度任务,开发者无需手动创建线程。GCD包括了串行队列、并发队列和全局队列等概念。 -
NSOperation/NSOperationQueue
:建立在GCD之上的更高级别的抽象。NSOperation
定义了一个操作,而NSOperationQueue
管理操作的执行。这提供了更多的控制和额外的功能,例如操作之间的依赖关系
、取消和暂停
等。
区别
:
-
抽象程度
-
NSThread
:较为底层,需要手动管理线程的创建、启动和销毁。 -
GCD
:提供了更高级别的抽象,通过队列管理任务。 -
NSOperationQueue
:提供了更高级别的操作管理
-
-
手动与自动管理
-
NSThread
:手动管理线程的生命周期和执行 -
GCD
/NSOperationQueue
:自动化的方式来管理并发任务,开发者只需关注任务的执行逻辑。
-
-
队列类型
-
GCD/NSOperationQueue
:包含了串行队列和并发队列,允许开发者轻松实现并发任务
-
-
依赖关系
:串行队列和并发队列,同时支持设置最大并发数。-
GCD/NSOperationQueue
:支持任务之间的依赖关系,可以指定一个任务在另一个任务完成后才执行。使得任务之间的协作变得更为灵活。
-
3、Block是什么?有哪些?
Block是一种在C语言基础
上构建的Objective-C特性,允许你创建一个封装了一段代码的对象
,可以在程序中传递
和执行
。Block也被称为闭包,它捕获了其定义时的环境
,使得在执行时可以访问
这个环境。
语法如下
:
returnType (^blockName)(parameterTypes) = ^returnType(parameters) {
// block 体
};
1、无参数无返回值的Block
// 声明并定义一个无参数无返回值的 Block
void (^simpleBlock)(void) = ^{
NSLog(@"This is a simple block.");
};
// 调用 Block
simpleBlock();
2、带参数的Block
// 声明并定义一个带参数的 Block
void (^parameterBlock)(NSString *) = ^(NSString *name) {
NSLog(@"Hello, %@", name);
};
// 调用 Block
parameterBlock(@"John");
3、带返回值的Block
:
// 声明并定义一个带返回值的 Block
NSInteger (^addBlock)(NSInteger, NSInteger) = ^(NSInteger a, NSInteger b) {
return a + b;
};
// 调用 Block
NSInteger result = addBlock(3, 5);
NSLog(@"Result: %ld", result);
4、Block作为方法参数
:
// 声明一个方法,参数为 Block
- (void)performOperationWithBlock:(void (^)(void))operationBlock {
NSLog(@"Before Block");
// 执行传入的 Block
operationBlock();
NSLog(@"After Block");
}
// 调用方法,传入 Block
[self performOperationWithBlock:^{
NSLog(@"Inside the Block");
}];
Block在异步线程
、回调函数
、动画
和集合
操作等场景中被广泛使用。
4、iOS中如何保存数据?有哪些持久化存储机制?
1、UserDefaults
:存储小量数据
的轻量级
存储方式,适用于简单的配置信息
、用户偏好
设置等。它基于键值对
的方式进行数据存储。
// 保存数据到UserDefaults
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:@"John" forKey:@"UserName"];
[userDefaults synchronize]; // 可选的,手动同步UserDefaults
// 从UserDefaults读取数据
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSString *userName = [userDefaults objectForKey:@"UserName"];
2、文件存储
:可以使用NSData
、NSString
等进行数据的读写。
// 保存数据到文件
NSString *dataString = @"Hello, World!";
NSString *filePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"data.txt"];
[dataString writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
// 从文件中读取数据
NSString *filePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"data.txt"];
NSString *dataString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
3、Core Data
:一个面向对象的数据持久化框架,适用于处理大量结构化
的数据。提供了对象关系映射
和查询
功能。
// Core Data 的基本使用
NSManagedObjectContext *context = // 获取上下文对象
NSManagedObject *newObject = [NSEntityDescription insertNewObjectForEntityForName:@"EntityName" inManagedObjectContext:context];
[newObject setValue:@"John" forKey:@"name"];
NSError *error = nil;
if (![context save:&error]) {
NSLog(@"Save failed: %@", [error localizedDescription]);
}
4、SQLite数据库
:可以直接使用SQLite API
或者使用封装库(FMDB
)来实现
// 使用 FMDB 操作 SQLite 数据库
FMDatabase *db = [FMDatabase databaseWithPath:databasePath];
if (![db open]) {
NSLog(@"Could not open database.");
return;
}
NSString *insertSQL = [NSString stringWithFormat:@"INSERT INTO TableName (name, age) VALUES ('%@', %ld)", name, age];
[db executeUpdate:insertSQL];
[db close];
这些是iOS中常用的一些持久化存储机制,选择适当的方式取决于应用程序的需求和数据的性质。
-
UserDefaults
适用于小量数据
; -
文件存储
适用于简单文本或二进制数据
; -
Core Data
适用于复杂的数据模型
; -
SQLite
则适用于需要关系型数据库
的情况。
5、什么是浅拷贝?什么是深拷贝?
浅拷贝
和深拷贝
是在编程中经常遇到的两个概念,它们描述了在复制对象或数据结构时所采用的不同策略。
-
浅拷贝
:创建一个新的对象
,但不复制
对象中的所有内容
。只复制
对象的引用
,而不复制
引用指向的内容
。因此,原始对象和浅拷贝后的对象共享相同的子对象
。如果修改
了原始对象中的共享子对象,浅拷贝后的对象也会受到影响
。
#import <Foundation/Foundation.h>
@interface Person : NSObject <NSCopying>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSMutableArray *hobbies;
@end
@implementation Person
- (id)copyWithZone:(NSZone *)zone {
Person *copy = [[[self class] allocWithZone:zone] init];
copy.name = [self.name copy];
copy.hobbies = [self.hobbies mutableCopy];
return copy;
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person1 = [[Person alloc] init];
person1.name = @"John";
person1.hobbies = [NSMutableArray arrayWithObjects:@"Reading", @"Swimming", nil];
// 浅拷贝
Person *person2 = [person1 copy];
// 修改原始对象的属性
[person1.hobbies addObject:@"Traveling"];
// 浅拷贝后的对象也受到影响
NSLog(@"Person 1 Hobbies: %@", person1.hobbies); // 输出: Reading, Swimming, Traveling
NSLog(@"Person 2 Hobbies: %@", person2.hobbies); // 输出: Reading, Swimming, Traveling
}
return 0;
}
Person
类实现了NSCopying
协议,并重写了copyWithZone:
方法进行浅拷贝。通过copy
方法创建的person2
对象和person1
对象共享相同的hobbies
数组,因此修改一个对象的hobbies
数组会影响到另一个对象。
-
深拷贝
:创建一个新的对象
,并递归地复制对象中的所有内容
,包括对象所引用的其他对象。深拷贝生成的新对象与原始对象完全独立
,对一个对象的修改不会影响影响到另一个对象
。
#import <Foundation/Foundation.h>
@interface Person : NSObject <NSCopying>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSMutableArray *hobbies;
@end
@implementation Person
- (id)copyWithZone:(NSZone *)zone {
Person *copy = [[[self class] allocWithZone:zone] init];
copy.name = [self.name copy];
copy.hobbies = [[NSMutableArray alloc] initWithArray:self.hobbies copyItems:YES];
return copy;
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person1 = [[Person alloc] init];
person1.name = @"John";
person1.hobbies = [NSMutableArray arrayWithObjects:@"Reading", @"Swimming", nil];
// 深拷贝
Person *person2 = [person1 copy];
// 修改原始对象的属性
[person1.hobbies addObject:@"Traveling"];
// 深拷贝后的对象不受影响
NSLog(@"Person 1 Hobbies: %@", person1.hobbies); // 输出: Reading, Swimming, Traveling
NSLog(@"Person 2 Hobbies: %@", person2.hobbies); // 输出: Reading, Swimming
}
return 0;
}
Person
类的copyWithZone:
方法中使用了initWithArray:copyItems:
方法,这会递归地复制数组中的每个元素,从而创建了一个全新的hobbies
数组,使得修改一个对象的hobbies
不会影响到另一个对象。
让我们通过一个生活中的比喻来理解
:
假设你制作了一个购物清单
,上面列有你需要购买的物品。现在你和你朋友决定共享
这个购物清单,以便两人都知道需要购买什么。
浅拷贝
:你和朋友共用
这一份购物清单
。如果你修改了清单上的某个物品,这个变化也会影响到你朋友,因为你们就这一份,按着上面的来购物。
深拷贝
:你制作了一份购物清单的副本,你们两个各自拿着一张清单。这个时候,你修改了自己的清单,朋友那是不受到影响的。