可变不可变基础对象
- 在
Objective-C
中,有很多基础对象都有可变和不可变两种版本: - 不可变:
NSString
: 代表不可变的文本字符串。NSArray
: 代表不可变的对象数组。NSDictionary
: 代表不可变的键值对集合。NSData
: 代表不可变的字节缓冲区。NSSet
: 代表不可变的无序对象集合。NSNumber
: 这是一个特例,它只有不可变版本,用于包装基础数据类型如int
,float
,bool
等。NSValue
: 用于包装C
语言的结构体,例如CGRect
,CGPoint
,CGSize
等。只有不可变版本NSCharacterSet
: 用于搜索和匹配字符集合。NSIndexSet
: 是一个用于存储唯一整数的集合。
- 可变:
NSMutableString
: 代表可变的文本字符串。NSMutableArray
: 代表可变的对象数组。NSMutableDictionary
: 代表可变的键值对集合。NSMutableData
: 代表可变的字节缓冲区。NSMutableSet
: 代表可变的无序对象集合。NSMutableCharacterSet
: 是NSCharacterSet
的可变版本。NSMutableIndexSet
: 是NSIndexSet
的可变版本。
NSString
- 在
Objective-C
中,NSString
是不可变的,意味着你不能修改其实例所持有的字符串内容。 - 但你可以改变
NSString
的指针来指向一个新的NSString
对象。 - 在
ARC
环境中,NSString
指针指向的原来的对象会被自动释放,具体是:编译器会自动插入内存管理相关的代码,从而帮助你自动管理对象的生命周期。 - 关于
ARC
机制,见下文
- 在
类的继承
必须继承自NSObject
吗
- 在
Objective-C
中,一个类不是必须要继承自NSObject
,但在实际使用中,大多数情况下都建议继承自它,原因如下:- 基础方法和功能:
NSObject
为派生类提供了一系列基础方法和功能,如-isEqual:
,-hash
,-description
, 和-performSelector:
等。 - 与Objective-C运行时交互:
NSObject
协议和类提供了与Objective-C
运行时系统交互的基础能力。这使得其派生类能够使用Objective-C
的动态特性,如动态方法解析、消息转发等。 - 内存管理: 如果你在非
ARC
(Automatic Reference Counting
) 的环境下编写代码,NSObject
提供了-retain
,-release
, 和-autorelease
方法来帮助内存管理。 - 与Foundation框架协同工作: 很多
Foundation
框架的API
预期它们与NSObject
的子类一起使用。
- 基础方法和功能:
子类重写父类方法
- 当子类重写(
override
)父类的方法时,可以认为它是“覆盖”了父类的那个方法。 - 具体地说,当你尝试调用那个方法的时候,运行时系统会先在子类中查找。如果子类有重写的版本,那么运行时系统会调用子类的方法而不是父类的。
- 需要注意的是,父类的方法并没有被真正“删除”或“替换”,它仍然存在于父类中,只是在子类的上下文中,子类的版本会被优先考虑。
类
定义
1 2 3 4 5 6 7 8 |
@interface SimpleClass : NSObject // 公共属性 @property NSString *firstName; @property NSString *lastName; @property int yearOfBirth; @end |
发消息
- 在
Objective-C
术语中,一个对象通过调用另一个对象的方法来向该对象发送消息。 - 对象可以向自己发送消息
1 2 3 4 5 6 7 8 |
@implementation XYZPerson - (void)sayHello { [self saySomething:@"Hello, world!"]; } - (void)saySomething:(NSString *)greeting { NSLog(@"%@", greeting); } @end |
实例方法
- 方法名称前面的减号表示它是实例方法,可以在类的任何实例上调用。
- 这与类方法不同,类方法可以在类本身上调用。
- 实例方法是与类的实例关联的方法。要调用实例方法,您需要先创建该类的实例(对象),然后通过该实例调用方法。
1 2 3 |
@interface MyClass : NSObject - (void)myInstanceMethod; @end |
1 2 |
MyClass *obj = [[MyClass alloc] init]; [obj myInstanceMethod]; |
类方法
- 类方法与类本身关联,而不是与类的任何特定实例关联。您不需要创建类的实例来调用类方法。
- 在接口定义中,类方法以加号
+
开头。
1 2 3 |
@interface MyClass : NSObject + (void)myClassMethod; @end |
1 |
[MyClass myClassMethod]; |
方法参数
1 2 3 4 5 6 |
void SomeFunction(SomeType value); - (void)someMethodWithValue:(SomeType)value; - (void)someMethodWithFirstValue:(SomeType)value1 secondValue:(AnotherType)value2; |
类的实现
- 头文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// // test_class.h // oc_learn // // Created by lif on 2023/9/26. // #ifndef test_class_h #define test_class_h @interface SimpleClass : NSObject { int value; } - (void)test_instance_func; + (void)test_class_func; @end #endif /* test_class_h */ |
- 源文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// // test_class.m // oc_learn // // Created by lif on 2023/9/26. // #import <Foundation/Foundation.h> #import "test_class.h" @implementation SimpleClass - (void)test_instance_func { NSLog(@"this is instance func"); } + (void)test_class_func { NSLog(@"this is class func"); } @end |
- 输出
1 2 3 4 |
Hello, World! this is instance func this is class func Program ended with exit code: 0 |
其他
- 每个类的名称在应用程序中必须是唯一的,即使在包含的库或框架中也是如此。
ARC
ARC工作的简要概述:
- 引用计数:每个
Objective-C
对象都有一个关联的引用计数。当一个对象的引用计数变为0
时,该对象会被销毁并释放其占用的内存。 - 增加引用计数:
- 当你创建一个新对象(如通过
alloc
、new
、copy
或mutableCopy
方法),其引用计数默认为1
。 - 当你给某个对象发送
retain
消息(在非ARC
环境下),或者在ARC
环境下有新的强引用(strong reference
)指向该对象时,该对象的引用计数增加。
- 当你创建一个新对象(如通过
- 减少引用计数:
- 在非
ARC
环境下,发送release
或autorelease
消息会减少对象的引用计数。 - 在
ARC
环境下,当一个强引用(strong reference
)不再指向某对象时(例如,指向了其他对象或被设置为nil
),编译器会自动为你插入代码来减少该对象的引用计数。
- 在非
- 对象销毁:当对象的引用计数变为
0
,该对象的dealloc
方法会被调用,然后对象占用的内存被释放。
属性修饰符
ARC
背后的机制实际上相对复杂,涉及到多个属性修饰符(如strong
,weak
,assign
,copy
等)和其他细节- strong (默认的修饰符)
- 这意味着属性持有对象的一个强引用。当对象的引用计数为
0
时,对象会被销毁。 - 当你设置一个新的对象到这样的属性上时,新对象的引用计数增加。
- 当属性不再持有该对象或被设置为
nil
时,原对象的引用计数减少。 - 常用于对象关联的大多数情况。
- 这意味着属性持有对象的一个强引用。当对象的引用计数为
- weak
- 属性持有对象的一个弱引用,不增加对象的引用计数。
- 弱引用用于避免引用循环。当对象被销毁时,所有指向它的弱引用都自动变为
nil
。 - 常见的用途是委托(
delegates
)和IBOutlets
(Interface Builder outlets
)。
- assign
- 通常用于基本数据类型(如
int
,float
等)。 - 对于对象类型,它持有对象的一个非保持引用。这意味着即使对象被销毁,该引用也不会自动置为
nil
(这可能会导致“悬挂指针”问题)。 - 使用
assign
对于对象类型并不是推荐的做法,除非你非常确信对象的生命周期。
- 通常用于基本数据类型(如
- copy
- 当对象被设置到属性上时,属性会持有该对象的一个复制版本。
- 对于字符串属性,通常建议使用
copy
修饰符,因为这样可以确保该属性持有一个不可变的版本,即使外部赋给它一个可变字符串。 - 对于其他需要保持不可变状态的对象(如
NSArray
或NSDictionary
),也可以使用此修饰符。
- unsafe_unretained
- 类似于
assign
,但适用于所有对象类型。 - 它持有对象的一个非保持引用,即使对象被销毁,该引用也不会自动置为
nil
。 - 使用此修饰符有风险,因为可能导致访问已被释放的内存的问题。
- 类似于
- readonly
- 此修饰符表示属性只可读,不能通过属性来修改它的值。
- 它不是直接与内存管理相关的,但是常与上述修饰符一起使用。
- readwrite (默认的修饰符)
- 此修饰符表示属性是可读可写的。
- 同样,它不是直接与内存管理相关的,但是常与上述修饰符一起使用。
1 2 3 4 5 6 7 |
// 默认情况 // 实际上只需要写@property (strong) NSString *name;,因为readwrite是默认的。 @property (strong, readwrite) NSString *name; @property (copy, readonly) NSArray *items; @property (weak, readwrite) id<SomeDelegate> delegate; |
使用ARC
- 在
Xcode
中,打开你的项目。 - 选择你的项目目标在
Targets
列表中。 - 在
Build Settings
选项卡中,搜索“Automatic Reference Counting
”。 - 设置“
Objective-C Automatic Reference Counting
”为“YES
”。
关闭ARC
- 设置“
Objective-C Automatic Reference Counting
”为“NO
”。
特定文件禁用ARC
- 选择你的项目目标。
- 选择
Build Phases
选项卡。 - 在
Compile Sources
部分,找到你想禁用ARC
的文件。 - 为该文件添加
-fno-objc-arc
编译器标志。
使用ARC需要注意:
- 不要在你的代码中使用
retain
,release
, 和autorelease
。 - 使用新的
ARC
属性修饰符,如strong
,weak
,unsafe_unretained
, 和copy
。 - 注意循环引用,特别是使用闭包(
block
)时。使用weak
引用可以避免大多数循环引用。
ARC和autoreleasepool
Autorelease pools
提供了一种延迟释放对象的机制,允许对象在稍后的时间点被释放。- 在非
ARC
环境下,你可能会使用autorelease
方法来避免立即释放一个对象。这个对象随后会在autoreleasepool
中被释放。 - 在
ARC
环境下,虽然你不会显式地调用autorelease
,但ARC
仍然可能在内部使用它。因此,autoreleasepool
仍然是有用的,特别是在大量创建临时对象的循环中。
autorelease
- 在非
ARC
环境中,autorelease
是一个常见的内存管理方法,它允许你将对象的所有权交给最近的autorelease pool
,该对象在稍后的时间点被释放。 - 这意味着对象在当前方法或作用域结束后不会立即被销毁,而是在外部的
autorelease pool
被释放时销毁。
1 |
NSString *string = [[[NSString alloc] initWithString:@"Hello, World!"] autorelease]; |
1 2 3 4 5 |
- (NSString *)createString { NSString *string = [[NSString alloc] initWithString:@"Sample String"]; return [string autorelease]; } |
autoreleasepool
- 在非
ARC
环境下,当你发送一个autorelease
消息给一个对象,那个对象会被添加到当前活跃的(最近创建的)autorelease pool
中。 - 然后,当该
autorelease pool
被drain
或释放时,所有在该pool
中的对象都会接收到一个release
消息。 - 在
ARC
环境下,你实际上不能(也不需要)显式地发送autorelease
消息给一个对象。ARC
会为你自动插入这些消息,以确保内存被正确地管理。- 也是需要放到
autoreleasepool
中,但是不需要发什么消息。 - 原理就是
ARC
保证了在编译器自动插入一些代码从而能正确释放对象。
- 也是需要放到
1 2 3 4 5 |
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // ... Code that creates and uses autoreleased objects ... [pool drain]; |
1 2 3 |
@autoreleasepool { // ... Code that creates and uses autoreleased objects ... } |
关于ARC的理解
- 编译期与运行期:在编译期间,
ARC
会通过静态分析来确定何时自动插入retain
、release
和autorelease
调用。然后,当程序在运行时执行到这些插入的代码时,才会实际执行相应的内存管理操作。 - 例如
NSString
对象:如果一个NSString
指针变量(比如,一个属性)从一个NSString
对象切换到另一个,那么确实如此:当新对象被赋值给该指针时,新对象会被retain
(增加引用计数),而原对象会被release
(减少引用计数)。如果原对象的引用计数变为0
,那么它将被立即释放。 - 不是"优化",而是"管理":虽然可以将ARC看作是某种优化,但更准确地说,它是一种内存管理机制。它确实优化了编写内存管理代码的过程,但其主要目的是确保对象在不再需要时被正确释放,并且在需要时持续存在。
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ 包管理器:各平台安装卸载相关记述09/17
- ♥ Chromium 进程线程05/08
- ♥ lua学习记述二06/13
- ♥ 包管理器:设计与实现09/18
- ♥ 实用操作二06/15
- ♥ 【AcWing 语法基础课 第二讲】02/17