Objective-C 学习笔记

可能比较杂乱,可以按照关键字来搜索快速查找定位。

可能会遇到的问题:

  1. 如果是复制进去的文件,Linker、编译的时候报错,说文件没有找到,可能是文件没有添加到 Compile Sources 列表里面,选择 项目根 -> Build Phases -> Compile Sources

.h 文件只是用来声明一个类有哪些成员变量和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#import <Foundation/Foundation.h>

@interface StudentNSObject {
// 成员变量要定义在大括号中
int _age;
}
// 在这里声明的所有方法都是公共的
// _age 的 get 方法
// - 代表动态方法 + 代表静态方法
- (int)getAge;
// _age 的 set 方法
- (void)setAge:(int)age;// 一个 : 代表一个参数

@end

@interface 代表声明一个类,最后接 @end
“:” 代表继承

.m 文件是用来实现类的一些方法的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#import "Student.h"

@implementation Student

- (int)getAge {
return _age;
}

- (void)setAge:(int)age {
_age = age;
// 错误写法,会导致死循环,无限调用 set 方法
// self.age = newAge; // [self setAge:newAge];
}

@end

创建一个 Student 对象:
1、调用一个静态方法 alloc 来分配内存

1
Student *stu = [Student alloc];

2、调用一个动态方法 init 进行初始化

1
stu = [stu init];

相当于:

1
2
3
4
5
Student *stu = [[Student alloc] init];

Person *person = [[Person alloc] init];
person.age = 10;// 等效于 [peson setAgeL:10]; 调用的 set 方法
int age = person.age;// 等效于 [person getAge]; 调用的 get 方法

在 OC 当中,使用 %@ 来打印一个对象(内存地址),重写 description 方法

1
NSLog(@"%@", stu);

在 OC 当中格式化字符串:

1
NSString *string = [NSString stringWithFormat:@"age is %i and no is %i", _age, _no];

如果直接把方法写在 .m 文件中,没有在 .h 文件中进行声明,那么这个方法就是私有的

.h 文件中,当编译器遇到 @property 时,会自动展开成 getter 和 setter 的声明,用在 @interface 里面

1
2
3
@property int age;
- (int)age;
- (void)setAge:(int)newAge;

.m 文件中,@synthesize age; 相当于:

1
2
3
4
5
6
7
- (void)setAge:(int)newAge {
age = newAge;
}

- (int)age {
return age;
}

还可以写成:@synthesize age, no, height; 用在 @implementation 里面
@synthesize 会自动生成 getter 和 setter 的实现方法。
@synthesize 默认会去访问跟 age 同名的变量,如果找不到,会在类的内部自动生成一个同名的变量,所以在 .h 文件中可以不用再声明该变量。
age = _age 代表 getter 和 setter 会去访问 _age 这个成员变量,自动生成的 age 就不会再存在了

1
@synthesize age = _age;

在 Xcode 4.5 之后,如果在 .h 文件中使用 @property 声明了变量 age 或者 _age,在 .m 文件中没有使用 @synthesize 实现这个变量,编译器会自动帮我们创建一个变量 _age。

如果在 getter 或者 setter 方法里面需要做一些操作的话,就不能依赖于 @synthesize 了。
如果你实现了 @synthesize 和 getter/setter 方法,@synthesize 就作废,不会再帮你创建 getter/setter 方法了。

内存管理

  1. 范围:任何继承了 NSObject 的对象,对基本数据类型无效
  2. 原理:
    • 每个对象内部都保存了一个与之相关联的整数,称为引用计数器
    • 当使用 alloc、new 或者 copy 创建一个对象时,对象的引用计数器被设置为 1
    • 给对象发送一条 retain 消息,可以使引用计数器 +1
    • 给对象发送一条 release 消息,可以使引用计数器 -1
    • 当一个对象的引用计数器值为 0 时,那么它将被销毁,其占用的内存被系统回收,OC 也会自动向对象发送一条 dealloc 消息。一般会重写 dealloc 方法,在这里释放相关资源。一定不要直接调用 dealloc 方法
    • 可以给对象发送 retainCount 消息获得当前的引用计数器值

每个OC对象都有自己的引用计数器,是一个整数,表示“对象被引用的此数”,即有多少人正在使用这个OC对象
每个OC对象内部都专门有4个字节的存储空间来存储引用计数器

重写dealloc方法:

1
2
3
4
5
- (void)dealloc {
// 自己要做的一些事情
// 一定要调用super的dealloc方法,而且最好放在最后面调用
[super dealloc];
}

内存管理法则

  1. 谁创建,谁释放(“谁污染,谁治理”)。如果你通过 alloc、new 或 (mutable)copy 来创建一个对象,那么你必须调用 release 或 autorelease。换句话说,不是你创建的,就不用你去释放
  2. 一般来说,除了 alloc、new 或 copy 之外的方法创建的对象都被声明了 autorelease
  3. 谁 retain,谁 release。只要你调用了 retain,无论这个对象时如何生成的,你都要调用 release

方法注释:

  • 给方法分组:
1
#pragma mark - 生命周期方法
  • 给方法注释:
1
#pragma mark 方法单独注释

@class 关键字:

@class 用来声明一个类,在 .h 文件中,可以不需要 import 进来某个类的 .h 文件,用@class 类名 代替,在 .m 文件中,因为要实现方法,需要用到类的一些东西,所以需要 import 进来这个类的 .h 文件

1
@property (retain) Book *book;

这里的 retain 代表:在 setter 方法中,release 旧值,retain 新值
@property 后面加上 (retain) ,在实现文件 .m 中,会自动生成管理内存的方法:

1
2
3
4
5
6
- (void)setBook:(Book *)book {
if(_book != book) {
[_book release];
_book = [book retain];
}
}
1
2
3
4
@property int age; // 默认是 @property (readwrite) int age;
@property (readonly) int age; // 代表只生成 getter 方法

@property (assign) int age; // 代表直接赋值

@property 属性默认为 atomic,提供多线程安全
在多线程环境下,原子操作时必要的,否则有可能引起错误的结果
加了 atomic,setter/getter 是一个原子操作,如果有多个线程同时调用 setter 的话,不会出现某一个线程执行 setter 全部语句之前,另一个线程开始执行 setter 的情况,相当于函数头尾加了锁一样

如果是 nonatomic,代表方法不需要考虑线程安全问题
禁止多线程,变量保护,提高性能
atomic 是 OC 使用的一种线程保护技术,防止在写入未完成的时候被另外一个线程读取,造成数据错误。而这种机制是耗费系统资源的,所以在 iPhone 这种小型设备上,如果没有使用多线程间的通讯,那么 nonatomic 是一个非常好的选择
如果不需要多线程支持的话,用 nonatomic 就够了,另外由于不涉及锁操作,所以它执行相对快点

1
@property (nonatomic, getter = isRich) Bool rich;

getter 是用来指定 get 方法的方法名

1
2
3
4
5
6
// @autoreleasepool 代表创建一个自动释放池
@autoreleasepool {
Student *stu = [[[Student alloc] init] autorelease];// 最好写成这样,下面的方式可能会忘记写 [stu autorelease];
// Student *stu = [[Student alloc] init];
// [stu autorelease]; // 代表将 stu 放入上面创建的自动释放池当中
}

当自动释放池被销毁了,池子里面的所有对象都会调用释放方法

使用静态方法快速创建一个 autorelease 的对象:

  • 在 Student.m 文件中创建一个创建对象的静态方法:
1
2
3
4
5
+ (id)student {
Student *stu = [[[Student alloc] init] autorelease];
return stu;
// 也可以这样写:return [[[Student alloc] init] autorelease]; 省略了中间变量 stu
}

使用:

1
Student *stu = [Student student];

在静态方法中,不能访问成员变量

autoreleasepool 注意事项:

  1. 在 ARC 下,不能使用 [[NSAutoreleasePool alloc] init],而应当使用 @autoreleasepool
  2. 不要把大量循环操作放到同一个 NSAutoreleasePool 之间,这样会造成内存峰值的上升
  3. 尽量避免对大内存使用该方法,对于这种延迟释放机制,还是尽量少用
  4. SDK 中一般利用静态方法创建并返回的对象都是已经 autorelease 的,不需要再进行 release 操作。如 [NSNumber numberWithInt:10]; 返回的对象是不需要再 release 的。但是通过 [[NSNumber alloc] initWithInt:10] 创建的对象需要 release

Category:

  1. OC 提供了一种与众不同的方式—— Category,可以动态地为已经存在的类添加新的行为(方法)
  2. 这样可以保证类的原始设计规模较小,功能增加时再逐步扩展
  3. 使用 Category 对类进行扩展时,不需要创建子类
  4. Category 使用简单的方式,实现了类的相关方法的模块化,把不同的类方法分配到不同的分类文件中

创建一个 Category 也很简单, File -> New -> File… ,选择 Source 下的 Objective-C File,点击 Next,File 取名为你要分类的名称,比如写 Test,File Type 选择为 Category,Class 输入你要扩展的类,然后就一直点击 Next,创建好了 Category,文件名为 Student+Test.h 和 Student+Test.m,Student+Test.h 内容大致如下:

1
2
3
@interface Student (Test)

@end

Student+Test.m 内容大致如下:

1
2
3
@implementation Student (Test)

@end

() 代表这是一个分类,括号中的 Test 代表分类的名称,在 Student+Test.h 中添加一个方法:

1
- (void)test2;

这样就将 Student 类扩展了一个 test2 的方法了,test2 的实现写在 Student+Test.m 中

Category 的作用:在不改变原来类内容的基础上,可以为类增加一些方法
Category 只能扩展方法,不能增加成员变量
Category 方法实现中可以访问原来类中声明的成员变量
Category 也能给系统的类扩展方法
Category 可以重新实现原来类中的方法,但是会覆盖掉原来的方法,导致原来的方法不能再使用
方法调用的优先级: 分类(最后编译的优先) -> 原来类 -> 父类
如果多个 Category 重复定义了同名的方法,会执行最后编译的 Category 的方法

Protocol:

  1. 简单来说就是一系列方法的列表,其中声明的方法可以被任何类实现。这种模式一般称为代理 (delegation) 模式
  2. 在 iOS 和 OS X 开发中,Apple 采用了大量的代理模式来实现 MVC 中的 View(UI 控件) 和 Conroller(控制器) 的解耦
  • 协议的定义
1
2
3
@protocol 协议名称 <NSObject>
// 方法声明列表
@end
  • 如何遵守协议
    1. 类遵守协议
      @interface 类名 : 父类名 <协议名称1, 协议名称2>
      @end
    2. 协议遵守协议
      @protocol 协议名称 <其他协议名称1, 其他协议名称2>
      @end
    3. 协议中方法声明的关键字
      1. @required(默认),要求时限,如果没有实现,会发出警告
      2. @optional,不要求实现,不会有警告
    4. 定义一个变量的时候,限制这个变量保存的对象遵守某个协议
      类名<协议名称> 变量名;
      id<协议名称> 变量名;
      NSObject
      obj;
      id obj2;
      如果没有遵守对应的协议,编译器会警告
    5. @property 中声明的属性也可用做一个遵守协议的限制
      @property (nonatomic, strong) 类名<协议名称> 属性名;
      @property (nonatomic, strong) id<协议名称> 属性名;
      @property (nonatomic, strong) Dog
      dog;
      @property (nonatomic, strong) id dog2;
    6. 协议可以定义在单独的 .h 文件中,也可以定义在某个类中
      1. 如果这个协议只用在某个类中,应该把这个协议定义在该类中
      2. 如果这个协议用在很多类中,应该把这个协议定义在单独的文件中
    7. 分类可以定义在单独 .h 文件和 .m 文件中,也可以定义在原来的类中
      1. 一般情况下,都是定义在单独 .h 文件中
      2. 定义在原来的类中的分类,只要求能看懂语法

Button.h 主要代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
// <>代表实现某个协议
@protocol ButtonDelegate <NSObject>
- (void)onClick;
@end

@interface Button : NSObject

// delegate 就是按钮的监听器
@portocol (nonatomic, retain) id<ButtonDelegate> delagate;

- (void)click;
@end

Button.m:

1
2
3
4
5
6
7
8
9
10
11
@implementation Button
- (void)dealloc {
[_delegate release];
[super dealloc];
}

- (void)click {
// 按钮被点击了,就应该通知监听器
[_delegate onclick];
}
@end

ButtonListener.h:

1
2
3
4
5
#import "Button.h"

@interface ButtonListener : NSObject <ButtonDelegate>

@end

ButtonListener.m:

1
2
3
4
5
@implementation ButtonListener
- (void)onClick{
NSLog(@"按钮被点击了");
}
@end

main.m:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import "Button.h"
#import "ButtonListener.h"

int main(int argc, const char * argv[]) {
@autoreleasepool {
// 初始化一个按钮
Button *button = [[[Button alloc] init] autorelease];
// 初始化一个按钮的监听器
ButtonListener *listener = [[[ButtonListener alloc] init] autorelease];
// 设置按钮的监听器
button.delegate = listener;
// 点击按钮
[button click];
}
return 0;
}
1
2
3
4
5
6
7
8
9
10
@protocol Study <NSObject>
// @required 表示此方法是必须实现的
// 虽然字面上说是必须实现,但是编译器并不强求某个类实现
// 默认就是 @required
@required
- (void)test;
// @optional 表示此方法是可选实现的
@optional
- (void)test2;
@end

判断某个对象是否有遵守某个 protocol:

1
2
3
if ([stu confirmsToProtocl:@protocol(protocol name)]) {

}

判断是否实现了某个方法:

1
2
3
if ([_delegate respondsToSelector:@selector(onClick:)]) {

}

Block:

  1. Block 封装了一段代码,可以在任何时候执行
  2. Block 可以作为函数参数或者函数的返回值,而其本身又可以带输入参数或返回值。它和传统的函数指针很类似,但是有区别:block 是 inline(内联函数) 的,并且默认情况下它对局部变量时只读的
  3. 苹果官方建议尽量多用 block。在多线程、异步任务、集合遍历、集合排序、动画转场用的很多

例如定义一个 a + b 的 Block:

1
2
3
int (^Sum) (int, int) = ^(int a, int b) {
return a + b;
}

调用:

1
int s = Sum(10, 11);

或者这样定义也行:

1
2
3
4
5
6
7
typedef int (^MySum) (int, int);

void test() {
MySum sum = ^(int a, int b) {
return a + b;
}
}

调用也和之前一样:

1
int s = sum(10, 10);

Block 可以访问外部定义的局部变量,但是不能修改:

1
2
3
4
5
6
7
void test() {
int c = 15;
MySum sum = ^(int a, int b) {
NSLog(@"c = %i", c);
return a + b;
}
}

要修改 c的值的话,将 c 声明为: __block int c = 15; 前面有两个下划线 _

直接访问成员变量,该变量的声明必须是 @public:

1
2
Student *stu = [[[Student alloc] init] autorelease];
stu->age = 10;

指向函数的指针

1
2
3
4
5
6
int sum(int a, int b) {
return a + b;
}

int (*p)(int, int) = sum;
int d = p(10, 20);

方法和函数的区别:
方法:

  1. 对象方法都是以减号 - 开头,类方法都是以 + 开头
  2. 对象方法的声明必须写在 @interface 和 @end 之间,对象方法的实现必须写在 @implementation 和 @end 之间
  3. 对象方法只能由对象来调用,类方法只能由类来调用
  4. 对象方法归类/对象所有
  5. 对象方法中能访问当前对象的成员变量(实例变量);类方法中不能访问成员变量(实例变量)

函数:

  1. 函数能写在文件中的任意位置(@interface 和 @end 之间),函数归文件所有
  2. 函数调用不依赖于对象
  3. 函数内部不能直接通过成员变量名访问某个对象的成员变量

继承的使用场合:

  1. 当两个类拥有相同的属性和方法,就可以将相同的东西抽取到一个父类中
  2. 当 A 类完全拥有和 B 类中的部分属性和方法时,可以考虑让 B 类继承 A 类

多态:

  1. 没有继承就没有多态
  2. 代码的体现:父类类型的指针指向子类对象
  3. 好处:如果函数/方法参数中使用的是父类类型,可以传入父类、子类对象
  4. 局限性:
    1. 父类类型的变量不能调用子类特有的方法,必须强制类型转换为子类类型的变量之后才能调用
      强制类型转换
1
2
Animal *a = [Dog new];
Dog *d = (Dog *)a;

成员变量的作用域:

  • @public:在任何地方都能直接访问对象的成员变量
  • @private:只能在当前类的对象中直接访问(@implementation 中默认就是 @private)
  • @protected:可以再当前类及子类的对象方法中直接访问(@interface 中默认就是 @protected)
  • @package:只要处在同一个框架中,就能直接访问对象的成员变量

@implementation 中不能定义和 @interface 中同名的成员变量

NSString 是不可变的

  • NSString 的创建:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 这种方式创建出来的字符串是不需要释放的
NSString *str1 = @"A String!";

NSString *str2 = [[NSString alloc] init];
str2 = @"A String!";
[str2 release];

NSString *str3 = [[NSString alloc] initWithString:@"A String!"];
[str3 release];
// 不需要管理内存
str3 = [NSString stringWithString:@"A String!"];

NSString *str4 = [[NSString alloc] initWithUTF8String:@"A String!"];
[str4 release];

NSString *str5 = [[NSString alloc] initWithFormat:@"My nameis %s and age is %i", "mlb", 21];
// 这行代码放在中间会造成2个错误
// 1、前面创建的字符串没有被释放
// 2、后面创建的字符串会释放2次,造成野指针错误
// *str5 = [[NSString alloc] stringWithFormat:@"My nameis %s and age is %i", "mlb", 21];
[str5 release];
*str5 = [[NSString alloc] stringWithFormat:@"My nameis %s and age is %i", "mlb", 21];

// 从文件中读取文本
NSString *path = @"/Users/HelloWorld/Desktop/test.txt";
// 这个方法已经过期,不能解析中文
// NSString *str1 = [NSString stringWithContentsOfFile:path];
// 定义一个 NSError 变量
NSError *error;
// 指定字符串编码为 UTF-8:NSUTF8StringEncoding
NSString *str1 = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
NSLog(@"%@", str1);
if (error == nil) {
NSLog(@"读取文件成功:%@", str1);
} else {
NSLog(@"读取文件失败:@error", error);
}
  • 字符串的导出:
1
2
3
4
5
6
7
8
9
10
11
12
NSString *str = @"123456";
NSString *path = @"/Users/HelloWorld/Desktop/test.txt";

NSError *error;
// 编码指定错误也会报错
// YES 代表要进行原子性操作,也就是会创建一个中间的临时文件
[str writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:&error];
if (error) {
NSLog(@"写入失败:%@", error); // error 写成 [error localizedDescription] 可以返回主要的报错信息
} else {
NSLog(@"写入成功");
}

NSString 的常用方法:

  • 字符串的大小写处理:
1
2
3
4
5
6
NSString *str = @"HelloWorld";
// 转成大写
NSLog(@"大写:%@", [str uppercaseString]);
// 转成小写
NSLog(@"小写:%@", [str lowercaseString]);
NSLog(@"helloWorld 首字母变大写:%@", [@"helloWorld" capitalizedString]);
  • 字符串的比较:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 检测字符串的内容是否相同
BOOL result = [@"abc" isEqualToString:@"abc"];
NSLog(@"%i", result);

// NSOrderedAscending 右边的字符串比左边的大
// NSOrderedSame 两个字符串的内容相同
// NSOrderDescending 左边的字符串比右边的大
// 忽略大小写比较:[@"abc" caseInsensitiveCompare:@"Abc"];
NSComparisonResult result2 = [@"abc" compare:@"Abc"];
if (result2 == NSOrderedSame) {
NSLog(@"两个字符串的内容");
} else if (result2 == NSOrderedAscending) {
NSLog(@"右边 > 左边");
} else if (result2 == NSOrderDescending) {
NSLog(@"右边 < 左边");
}
  • 字符串的搜索:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
NSString *str = @"123456456.txt";
NSLog(@"是否以12开头:%i", [str hasPrefix:@"12"]);
NSLog(@"是否以txt结尾:%i", [str hasSuffix:@"txt"]);
// 从头开始搜索
NSRange range = [str rangeOfString:@"345"];
// range.length == 0
if (range.location == NSNotFound) {
NSLog(@"没有找到");
} else {
NSLog(@"找到,范围是:%@", NSStringFromRange(range));
}
// 从尾部开始搜索
range = [str rangeOfString:@"456" options:NSBackwardsSearch];
NSLog(@"从尾部开始搜索,找到,范围是:%@", NSStringFromRange(range));

// 从指定范围开始搜索字符串方法
[str rangeOfString:@"456" options:NSBackwardsSearch range:(NSRange)];
  • 字符串的截取:
1
2
3
4
5
6
7
8
NSString *str = @"123456";
// 从索引3开始截取到尾部(包括3)
NSLog(@"%@", [str substringFromIndex:3]);
// 从头部开始截取到索引3之前(不包括3)
NSLog(@"%@", [str substringToIndex:3]);
// 指定范围进行截取
NSRange range = NSMakeRange(2, 3);
NSLog(@"%@", [str substringWithRange:range]);
  • 字符串的拆分:
1
2
3
NSString *str2 = @"1,2,3,4,5,6";
NSArray *array = [str2 componentsSeparatedByString:@","];
NSLog(@"%@", array);
  • 字符串与路径:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 快速创建一个自动释放的数组
NSMutableArray *components = [NSMutableArray array];
[components addObject:@"Users"];
[components addObject:@"HelloWorld"];
[components addObject:@"Desktop"];
// 将数组中的所有字符串拼接成一个路径
NSString *path = [NSString pathWithComponents:components];
NSLog(@"%@", path);
// 将路径分解成数组
NSArray *cmps = [path pathConponents];
NSLog(@"%@", cmps);
// 判断是否为绝对路径
path = @"/Users/HelloWorld/test/test.txt";
NSLog(@"%i", [path isAbsolutePath]);
// 返回路径的最后一个文件夹
NSLog(@"最后一个目录:%@", [path lastPathComponent]);
// 删除最后一个文件夹目录并返回一个新的路径
NSLog(@"%@", [path stringByDeletingLastPathComponent]);
// 在最后面拼接一个目录
NSLog(@"%@", [path stringByAppandingPathComponent:@"abc"]);
  • 扩展名处理:
1
2
3
4
5
6
7
NSString *path = @"/Users/HelloWorld/test/test.txt";
// 获取扩展名
NSLog(@"扩展名:%@", [path pathExtension]);
// 删除扩展名
NSLog(@"%@", [path stringByDeletingPathExtension]);
// 拼接扩展名,只是在尾部添加了扩展名字符串,并不会检测原先是否有扩展名
NSLog(@"%@", [@"abc" stringByAppendingPathExtension:@"mp3"]);
  • 其他用法:
1
2
3
4
5
6
7
8
9
10
11
12
// 将字符串转成int类型
NSString *str = @"12";
int a = [str intValue];
NSLog(@"%i", a);
// 求字符串的长度,是字数而不是字符数
NSLog(@"length = %zi", [@"我是字符串", length]);
// 取出指定下标的字符
unichar c = [@"abcd" characterAtIndex:0];
NSLog(@"%c", c);
// 将 OC 字符串转成 C 语言中的字符串(char *)
const char *s = [@"abc", UTF8String];
NSLog(@"%s", s);

NSMutableString

  1. NSString 是不可变的,不能删除字符或者添加字符。NSString 有一个子类 NSMutableString,称为“可变字符串”
  2. 创建可变字符串的常用方法:
    1. - (id)initWithCapacity:(NSUInteger)capacity
    2. + (id)stringWithCapacity:(NSUInteger)capacity
  3. capacity 只是一个最优值,字符串的大小并不仅限于所提供的容量,设置了 capacity,可以预分配一块内存来存储它,操作速度会快很多
  4. 当然,也可以使用创建 NSString 的方法来创建 NSMutableString,因为 NSMutableString 是 NSString 的子类,NSString 能用的方法,NSMutableString 都能使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 可变字符串的创建
// 预先分配10个字数的空间
NSMutableString *str = [[NSMutableString alloc] initWithCapacity:10];
// 设置字符串内容
[str setString:@"1234"];
// 拼接字符串
[str appendString:@"567"];
[str appendFormat:@"age is %i and name is %s", 21, "mlb"];
// 替换字符串
// 先找到要替换的字符串的范围
NSRange range = [str rangeOfString:@"age"];
[str replaceCharactersinRange:range withString:@"years"];
// 插入字符串
[str insertString:@"abc" atIndex:2];
// 删除字符串
range = [str rangeOfString:@"567"];
[str deleteCharactersInRange:range];
NSLog(@"%@", str);
[str release];

NSArray

  1. 用来存储对象的有序列表,它是不可变的
  2. 不能存储C语言中的基本数据类型,如 int、float、enum、struct,也不能存储 nil
1
2
3
4
5
6
7
8
9
10
// 创建一个空的数组
NSArray *array = [NSArray array];
// 创建一个有一个元素的数组
array = [NSArray arrayWithObject:@"123"];
// 创建有多个元素的数组,nil代表创建结束
array = [NSArray arrayWithObjects:@"a", @"b", @"c", nil];
// 获取数组长度
unsigned int count = [array count];
// count = array.count;
NSLog(@"%zi", count);
  • 数组的简单使用:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
NSArray *array = [NSArray arrayWithObjects:@"a", @"b", @"c", nil];
// 判断是否包含了某个元素
if ([array containsObject:@"bc"]) {
NSLog(@"包含了字符串bc");
}
// 返回数组最后一个元素
NSString *last = [array lastObject];
NSLog(@"last = %@", last);
// 返回指定下标的元素
NSString *str = [array objectAtIndex:1];
NSLog(@"str = %@", str);
// 查询某个元素的下标
int index = [array indexOfObject:@"c"];
NSLog(@"index = %i", index);
  • 数组的内存管理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Student *stu1 = [[Student alloc] init];
Student *stu2 = [[Student alloc] init];
Student *stu3 = [[Student alloc] init];

NSLog(@"stu1:%zi", [stu1 retainCount]);
// 当把一个对象添加进数组中时,这个对象的引用计数器会加1,也就是说数组会对它做一个 retain 操作
NSArray *array = [[NSArray alloc] initWithObjects:stu1, stu2, stu3, nil];
NSLog(@"stu1:%zi", [stu1 retainCount]);
NSLog(@"count = %zi", array.count);
// 数组被销毁的时候,会对内部的所有元素做一次 release 操作
[array release];
[stu1 release];
[stu2 release];
[stu3 release];

NSArray 的比较:

  1. 比较两个集合内容是否相同
    - (BOOL)isEqualToArray:(NSArray *)otherArray
  2. 返回两个集合中第一个相同的对象元素
    - (id)firstObjectCommonWithArray:(NSArray *)otherArray
  • 给数组里面的元素发送消息:
1
2
3
4
// 让数组里面的所有对象都调用某个方法
[array makeObjectsPerformSelector:@selector(function name)];
// 也可以传入参数
[array makeObjectsPerformSelector:@selector(function name:) withObject:@"123"];
  • 遍历数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
NSArray *array = [NSArray arrayWithObjects:@"a", @"b", @"c", nil];
unsigned int count = array.count;
// 第一种方法:
for (int i = 0; i < count; i++) {
// id == void *
id obj = [array objectAtIndex:i];
NSlog(@"%i - %@", i, obj);
}
// 第二种方法:
// 快速遍历
int i = 0;
for (id obj in array) {
NSLog(@"%i - %@", i, obj);
i++;
}
// 第三种方法:
[array enumerateObjectsUsingBlock:
^(id obj, NSUInteger idx, BOOL *stop) {
NSLog(@"%zi - %@", idx, obj);
if (idx == 1) {
// 利用指针修改外部 BOOL 变量的值
*stop = YES;
}
}];
// 第四种方法:
// 获取数组的迭代器
NSEnumerator *enumerator = [array objectEnumerator];
// 获取下一个需要遍历的元素
id obj = nil;
while (obj = [enumerator nextObject]) {
NSLog(@"obj = %@", obj);
}
// 反序迭代器
NSEnumerator *reverseEnumerator = [array reverseObjectEnumerator];

// allObjects 是取出没有被遍历过的对象
NSArray *arr = [enumerator allObjects];
NSLog(@"arr: %@", arr);
  • 派生出新的数组
1
2
3
4
5
6
7
8
9
10
11
12
NSArray *array = [NSArray arrayWithObjects:@"a", @"b", @"c", nil];
NSArray *array2 = [array arrayByAddingObject:@"d"];
NSArray *array3 = [array arrayByAddingObjectsFromArray:[NSArray arrayWithObjects:@"e", @"f", nil]];
NSLog(@"array = %@", array);
NSLog(@"array2 = %@", array2);
NSLog(@"array3 = %@", array3);

NSArray *array4 = [NSArray arrayWithObjects:@"1", @"2", @"3", nil];
// 截取某个数组中的元素并返回一个新的数组
NSRange range = NSMakeRange(1, 2);
NSArray *array5 = [array4 subarrayWithRange:range];
NSLog(@"array5 = %@", array5);
  • NSArray其他用法:
1
2
3
4
5
6
7
8
9
10
11
NSArray *array = [NSArray arrayWithObjects:@"a", @"b", @"c", nil];
// 利用 separator 作为拼接符将数组元素拼接成一个字符串
NSString *str = [array componentsJoinedByString:@"-"];
NSLog(@"str = %@", str);
// 把数组写入文件中,生成的是 XML 格式的文件
NSString *path = @"/Users/HelloWorld/test/array.xml";
[array writeToFile:path atomically:YES];

// 从文件中读取数组内容(文件内容有严格的格式要求)
NSArray *array = [NSArray arrayWithContentsOfFile:path];
NSLog(@"array = %@", array);
  • NSArray排序
1
2
3
4
5
6
7
NSArray *array = [NSArray arrayWithObjects:@"2", @"3", @"1", @"4", nil];
// 没有排序输出
NSLog(@"array = %@", array);
// 返回一个排好序的数组,原来的数组的元素顺序不会改变
// 指定元素的比较方法,默认从小到大排序
NSArray *sortArray = [array sortedArrayUsingSelector:@selector(compare:)];
NSLog(@"sortArray = %@", sortArray);
  • 对象的比较排序:

Book.h:

1
2
3
4
5
@interface Book : NSObject
@property (nonatomic, retain) NSString *name;

+ (id)bookWithName:(NSString *)name;
@end

Book.m:

1
2
3
4
5
6
7
8
9
10
11
12
@implementation Book
+ (id)bookWithName:(NSString *)name {
Book *book = [[[Book alloc] init] autorelease];
book.name = name;
return book;
}

- (void)dealloc {
[_name release];
[super release];
}
@end

Student.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@class Book;

@interface Student : NSObject
@property (nonatomic, retain) NSString *firstName;
@property (nonatomic, retain) NSString *lastName;
@property (nonatomic, retain) Book *book;

+ (id)studentWithFirstName:(NSString *)firstname lastname:(NSString *)lastname;

+ (id)studentWithFirstName:(NSString *)firstname lastname:(NSString *)lastname bookName:(NSString *)bookName;

// 返回值类型为 NSComparisonResult
- (NSComparisonResult)compareStudent:(Student *)stu;
@end

Student.m:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@implementation

+ (id)studentWithFirstName:(NSString *)firstname lastname:(NSString *)lastname {
Student *stu = [[[Student alloc] init] autorelease];
stu.firstName = firstname;
stu.lastName = lastname;

return stu;
}

+ (id)studentWithFirstName:(NSString *)firstname lastname:(NSString *)lastname bookName:(NSString *)bookName {
Student stu = [Student studentWithFirstName:firstname lastName:lastname];
stu.book = [Book bookWithName:bookName];
return stu;
}

- (NSComparisonResult)compareStudent:(Student *)stu {
// 先按照姓排序
NSComparisonResult result = [self.lastName compare:stu.lastName]
// 如果有相同的姓,就比较名字
if (result == NSOrderedSame) {
result = [self.firstName compare:stu.firstName];
}
return result;
}

- (void)dealloc {
[_firstName release];
[_lastName release];
[_book release];
[super release];
}

- (NSString *) description {
return [NSString stringWithFormat:@"[%@ %@ - %@]", self.lastName, self.firstName, self.book.name];
}
@end

main.m:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
Student *s1 = [Student studentWithFirstName:@"LeBin" lastName:@"Mei"];
Student *s2 = [Student studentWithFirstName:@"JiBin" lastName:@"Chen"];
Student *s3 = [Student studentWithFirstName:@"BinBin" lastName:@"Chi"];
Student *s4 = [Student studentWithFirstName:@"Bi" lastName:@"Xue"];
Student *s5 = [Student studentWithFirstName:@"Bin" lastName:@"Xu"];

// 第一种方法
NSArray *array = [NSArray arrayWithObjects:s1, s2, s3, s4, s5, nil];
// 指定排序的比较方法
NSArray *array2 = [array sortedArrayUsingSelector:@selector(compareStudent:)];
NSLog(@"array2 = %@", array2);

// 第二种方法
// 利用Block进行排序
NSArray *array3 = [array sortedArrayUsingComparator:
^NSComparisonResult(Student *obj1, Student *obj2) {
// 先按照姓排序
NSComparisonResult result = [self.lastName compare:stu.lastName]
// 如果有相同的姓,就比较名字
if (result == NSOrderedSame) {
result = [self.firstName compare:stu.firstName];
}
return result;
}];
NSLog(@"array3 = %@", array3);

// 另外一种排序,多属性
Student *s1 = [Student studentWithFirstName:@"LeBin" lastName:@"Mei" bookName:@"book1"];
Student *s2 = [Student studentWithFirstName:@"JiBin" lastName:@"Chen" bookName:@"book2"];
Student *s3 = [Student studentWithFirstName:@"BinBin" lastName:@"Chi" bookName:@"book1"];
Student *s4 = [Student studentWithFirstName:@"Bi" lastName:@"Xue" bookName:@"book2"];
Student *s5 = [Student studentWithFirstName:@"Bin" lastName:@"Xu" bookName:@"book3"];

NSArray *array = [NSArray arrayWithObjects:s1, s2, s3, s4, s5, nil];

// 1.先按照书名进行排序
NSSortDescriptor *bookNameDesc = [NSSortDescriptor sortDescriptorWithKey:@"book.name" ascending:YES];
// 2.再按照姓进行排序
NSSortDescriptor *lastNameDesc = [NSSortDescriptor sortDescriptorWithKey:@"lastName" ascending:YES];
// 3.最后按照名进行排序
NSSortDescriptor *firstNameDesc = [NSSortDescriptor sortDescriptorWithKey:@"firstName" ascending:YES];
// 按顺序添加排序描述器
NSArray *descs = [NSArray arrayWithObjects:bookNameDesc, lastNameDesc , firstNameDesc , nil];
NSArray *array2 = [array sortedArrayUsingDescriptors:descs];
NSLog(@"array2 = %@", array2);

NSMutableArray

  1. 可变的 NSArray,NSArray 的子类,可以随意的添加或者删除元素
  2. 创建 NSMutableArray 的方法:
    1. + (id)arrayWithCapacity:(NSUInteger)numItems
    2. - (id)initWithCapacity:(NSUInteger)numItems
  3. 也可以使用创建 NSArray 的方法来创建 NSMutableArray
  4. 当一个元素被加到集合中时,会执行一次 retain 操作;当一个元素从集合中移除时,会执行一次 release 操作;当集合被销毁时(调用了 dealloc),集合里的所有元素都会执行一次 release 操作(这个原则还适用于其他集合:NSDictionary/NSSet 等)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 创建 NSMutableArray
NSMutableArray *array = [NSMutableArray arrayWithObject:@"1"];
// 添加元素,对被添加的元素会做一次 retain 操作,引用计数器加 1
[array addObject:@"2"];
[array addObject:@"3"];
NSLog(@"%@", array);
// 删除元素,对被删除的元素做一次 release 操作,引用计数器减 1
// 删除指定元素
[array removeObject:@"2"];
// 删除最后一个元素
[array removeLastObject];
// 删除所有元素
[array removeAllObject];
// 当数组被释放的时候,会对所有的元素都做一次 release 操作

// 替换指定下标的元素
NSMutableArray *array = [NSMutableArray arrayWithObject:@"1", @"2", @"3"];
[array relaceObjectAtIndex:1 withObject:@"4"];
NSLog(@"array = %@", array);

// 排序
NSMutableArray *array = [NSMutableArray arrayWithObject:@"1", @"3", @"2"];
// 因为是可变数组,所以不需要返回新的数组
[array sortUsingSelector:@selector(compare:)];
NSLog(@"array = %@", array);

NSDictionary
通过唯一的 key 找到对应的 value,类似于 Java 的 Map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 字典的初始化
// NSDiationary 是不可变的
NSDiationary *dict = [NSDiationary dictionaryWithObject:@"v" forKey:@"k"];
dict = [NSDiationary dictionaryWithObjectsAndKeys:
@"v1", @"k1",
@"v2", @"k2",
@"v3", @"k3", nil];
NSArray *objects = [NSArray arrayWithObjects:@"v1", @"v2", @"v3", nil];
NSArray *keys = [NSArray arrayWithObjects:@"k1", @"k2", @"k3", nil];
dict = [NSDiationary dictionaryWithObjects:objects forKeys:keys];
NSLog(@"%@", dict);

// NSDiationary 常用方法
// count 是计算有几个键值对 (key-value)
NSLog(@"count = %zi", dict.count);
// 由于 NSDiationary 是不可变的,所以只能取值,而不能修改值
id obj = [dict objectForKey];
NSLog(@"obj = %@", obj);
// 将字典写入文件中,写入的格式是 XML 的
NSString *path = @"/Users/HelloWorld/Desktop/dict.xml";
[dict writeToFile:path atomically:YES];
// 也可以从文件中读取,文件内容必须是 XML 格式
dict = [NSDiationary dictionalryWithContentsOfFile:path];
NSLog(@"dict = %@", dict);
// 返回所有的 key
NSArray *keys = [dict allKeys];
NSLog(@"keys = %@", keys);
// 返回所有的 value
NSArray *object = [dict allValues];
NSLog(@"objects = %@", objects);
// 根据多个 key 取出对应的多个 value
// notFoundMarker 不能为 nil
objects = [dict objectsForKeys:[NSArray arrayWithObjects:@"k1", @"k2", nil] notFoundMarker:@"not_found"];
NSLog(@"objects = %@", objects);

// 遍历字典
NSDiationary *dict = [NSDiationary dictionaryWithObjectsAndKeys:
@"v1", @"k1",
@"v2", @"k2",
@"v3", @"k3", nil];
// 第一种方法
for (id key in dict) {
id value = [dict objectForKey:key];
NSLog(@"%@ = %@", key, value);
}
// 第二种方法
NSEnumerator *enumer = [dict keyEnumerator];
id key = nil;
while (key = [enumer nextObject]) {
id value = [dict objectForKey:key];
NSLog(@"%@ = %@", key, value);
}
// 对象迭代器:[dict objectEnumerator]
// 第三种方法
[dict enumerateKeysAndObjectsUsingBlock:
^(id key, id obj, BOOL *stop) {
NSLog(@"%@ = %@", key, obj);
}];

NSDiationary 的内存管理

Student.h:

1
2
3
4
@interface Student : NSObject
@property (nonatomic, retain) NSString *name;
+ (id)studentWithName:(NSString *)name;
@end

Student.m:

1
2
3
4
5
6
7
8
9
10
11
12
13
@implementation Student
+ (id)studentWithName:(NSString *)name {
Student *stu = [[[Student alloc] init] autorelease];
stu.name = name;
return stu;
}

- (void)dealloc {
NSLog(@"%@被销毁了", _name);
[_name release];
[super dealloc];
}
@end

main.m:

1
2
3
4
5
6
7
8
9
Student *s1 = [Student studentWithName:@"student1"];
Student *s2 = [Student studentWithName:@"student2"];
Student *s3 = [Student studentWithName:@"student3"];
// 一个对象称为字典的 key 或者 value 时,会做一次 retain 操作,也就是引用计数器 +1
NSDiationary *dict = [NSDiationary dictionaryWithObjectsAndKeys:
s1, @"k1",
s2, @"k2",
s3, @"k3", nil];
// 当字典被销毁时,里面的所有 key 和 value 都会做一次 release 操作,也就是引用计数器 -1

NSMutableDiationary

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 可变的字典的使用
// 创建一个空的字典
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
Student *s1 = [Student studentWithName:@"student1"];
Student *s2 = [Student studentWithName:@"student2"];
// 添加元素
// s1 的引用计数器会 +1
[dict setObject:s1 forKey:@"k1"];
NSLog(@"s1: %zi", [s1 retainCount]);
// 添加其他字典的键值对到一个字典中
NSDiationary *others = [NSDiationary dictionaryWithObjectsAndKeys:
@"v1", @"k1",
@"v2", @"k2",
@"v3", @"k3", nil];
[dict addEntriesFromDictionary:other];
// 删除所有的键值对
[dict removeAllObjects];
// 删除key对应的键值对
[dict removeObjectForKey:@"k1"];
// 删除多个 key 对应的键值对
[dict removeObjectsForKeys:[NSArray arrayWithObject:@"k1"]];
// 字典被销毁时,内部的所有 key 和 value 的引用计数器都会 -1,也就是说,s1 会被 release 一次

NSNumber
NSNumber 可以将基本数据类型包装成对象,这样就可以间接将基本数据类型存进 NSArray、NSDictionary 等集合中

1
2
3
4
5
6
7
8
9
10
11
// 将 int 类型的数据包装成一个 NSNumber 对象
NSNumber *num = [NSNumber numberWithInt:10];
NSLog(@"num = %@", num);
NSMutableArray *array = [NSMutableArray array];
// 添加数值到数组中
[array addObject:number];
// 取出来还是一个 NSNumber 对象,不支持自动解包(也就是不会自动转化为 int 类型)
NSNumber *num1 = [array lastObject];
// 将 NSNumber 对象转化为 int 类型
int n = [num1 intValue];
NSLog(@"n = %i", n);

NSNumber 的常用方法

  1. - (char)charValue
  2. - (int)intValue
  3. - (double)doubleValue
  4. - (BOOL)boolValue
  5. - (NSString *)stringValue
  6. - (NSComparisonResult)compare:(NSNumber *)otherNumber
  7. - (BOOL)isEqualToNumber:(NSNumber *)number

NSValue
NSNumber 是 NSValue 的子类,但 NSNumber 只能包装数字类型,NSValue 可以包装任意值。也就可以用 NSValue 包装结构体后加入 NSArray、NSDictionary 等集合中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
CGPoint point = CGPointMake(10, 10);
// 将结构体变量包装成一个对象
NSValue *value = [NSValue valueWithPoint:point];
NSMutableArray *array = [NSMutableArray array];
// 添加 value
[array addObject:value];
//取出当时放进去的 value
NSValue *value1 = [array lastObject];
CGPoint point1 = [value1 pointValue];
BOOL result = CGPointEqualToPoint(point1, point);
NSLog(@"result = %i", result);
// 包装结构体变量
typedef struct {
int year;
int month;
int day;
} Date;

Date date = {2014, 11, 27};
// 这里要传结构体的地址 &date
// 根据结构体类型生成对应的结构描述字符串
char *type = @encode(Date);
[NSValue value:&date, withObjCType:type];

// 定义一个结构体变量
Date date1;
// 取出包装好的结构体
[value getValue:&date1];
NSLog(@"year = %i, month = %i, day = %i", date1.year, date1.month, date1.day);

NSNull

  1. 集合中是不能存放 nil 值的,因为 nil 在集合中有特殊含义,但有时确实需要存储一个表示“什么都没有”的值,那么就可以使用 NSNull,它也是 NSObject 的一个子类
  2. 创建和获取 NSNull 的方法:
    + (NSNull *)null
1
2
// 这个null只有一个,也就是说多个NSNull对象,他们的地址是相同的
NSNUll *n = [NSNull null];

NSDate
NSDate 的静态初始化
TimeInterval 是秒

  1. 返回当前时间:
    + (id)date
  2. 返回以当前时间为基准,然后过了 secs 秒的时间
    + (id)dateWithTimeIntervalSinceNow:(NSTimeInterval)secs
  3. 返回以 2001/01/01 GMT 为基准,然后过了 secs 秒的时间
    + (id)dateWithTimeIntervalSinceReferenceDate:(NSTimeInterval)secs
  4. 返回以 1970/01/01 GMT 为基准,然后过了 secs 秒的时间
    + (id)dateWithTimeIntervalSince1970:(NSTimeInterval)secs
  5. 返回很多年以后的未来的某一天
    + (id)distantFuture
  6. 返回很多年以前的某一天
    + (id)distantPast
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 日期创建
// date 方法返回的就是当前时间
NSDate *date = [NSDate date];
// 日期使用
// now: 11:12:40
// date: 11:12:50
date = [NSDate dateWithTimeIntervalSinceNow:10];
// 从 1970-01-01 00:00:00 开始
date = [NSDate dateWithTimeIntervalSince1970:10];
// 随机返回一个比较遥远的未来时间
date = [NSDate distantFuture];
NSLog(@"future date = %@", date);
// 随机返回一个比较遥远的过去时间
date = [NSDate distantPast];
NSLog(@"future date = %@", date);
// 返回 1970-01-01 00:00:00 开始到现在的毫秒数
NSTimeInterval interval = [date timeIntervalSince1970];
// 日期比较
// 返回两个时间中比较早的时间
NSDate *date2 = [NSDate date];
[date earlierDate:date2];
// 返回两个时间中比较晚的时间
[date laterDate:date2];
// 日期格式化
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
// 设置格式,hh 是12小时进制,HH 是24小时进制
formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss";
// 设置时区
// formatter.locale = [[[NSLocale alloc] initWithLocaleIdentifier:@""] autorelease];
// 把 NSDate 转换为 NSString
NSString *str = [formatter stringFromDate:date];
NSLog(@"str = %@", str);
// 把 NSString 转换为 NSDate,如果没有设置时区,默认是格林尼治时区
NSDate *date2 = [formatter dateFromString:@"2014-11-27 16:02:50"];
NSLog(@"date2 = %@", date2);
[formatter release];

NSObject 常用方法

  1. 判断是否为 @class 或者 @class 的子类的实例
    - (BOOL)isKindOfClass:(Class)@class
  2. 判断是否为 @class 的实例(不包括 @class 的子类)
    - (BOOL)isMemberOfClass:(Class)@class
  3. 判断对象是否实现了 @protocol 协议
    - (BOOL)conformsToProtocol:(Protocol)@protocol
  4. 判断这个类的对象是否拥有参数提供的方法
    + (BOOL)instancesRespondToSelector:(SEL)@selector
  5. 判断对象是否拥有参数提供的方法
    - (BOOL)respondsToSelector:(SEL)@selector
  6. 延迟调用参数提供的方法,方法所需参数用 withObject 传入
    - (void)performSelector:(SEL)@selector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay

Person.h:

1
2
3
@interface Person : NSObject
- (void)test;
@end

Person.m

1
2
3
4
5
@implementation Person
- (void)test {
NSLog(@"Person 调用了 test 方法");
}
@end

Student.h:

1
2
3
4
5
6
@interface Student : Person

- (void)test;
- (void)test2:(NSString *)str;

@end

Student.m:

1
2
3
4
5
6
7
8
9
10
@implementation Student
- (void)test {
NSLog(@"Student 调用了 test 方法");
}

- (void)test2:(NSString *)str {
NSLog(@"Student 调用了 test2 方法,str = %@", str);
}

@end

main.m:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Student *stu = [[[Student alloc] init] autorelease];
// isKindOfClass 判断对象是否属于某个类或者其子类
if ([stu isKindOfClass:[Person class]]) {
NSLog(@"stu 属于 Person 或者属于继承自 Person 的类");
}
// isMemberOfClass 判断对象是否属于某个类(不包括子类)
BOOL result = [stu isMemberOfClass:[Person class]];
NSLog(@"result = %i", result);
// 直接调用方法
// 1. 把 test 方法包装成 SEL 类型的数据
// 2. 根据 SEL 数据找到对应的方法地址
// 3. 根据方法地址调用对应的方法
[stu test];
// 间接调用方法
[stu performSelector:@selector(test)];
// 传入参数
[stu performSelector:@selector(test2:) withObject:@"123"];
// 延迟调用
[stu performSelector:@selector(test2:) withObject:@"123" afterDelay:2];

反射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 类的反射
NSString *str = @"Person";
Class class = NSClassFromString(str);
Person *person = [[class alloc] init];
NSLog(@"%@", person);
[person release];
// Class 变成字符串
NSString *name = NSStringFromClass([Person class]);
// 方法的反射
NSString *method = @"test";
SEL selector = NSSelectorFromString(method);
[person performSelector:selector];
// 将 SEL 转换为字符串
NSString *selectorName = NSStringFromSelector(selector);
[person release];

SEL
SEL 其实是对方法的一种包装,将方法包装成一个 SEL 类型的数据,去找对应的方法地址,找到方法地址就可以调用方法。其实消息(常说的发送消息)就是 SEL。

  • 方法的存储位置
    1. 每个类的方法列表都存储在类对象中
    2. 每个方法都有一个与之对应的 SEL 类型的对象
    3. 根据一个 SEL 对象就可以找到方法的地址,进而调用方法
    4. SEL 类型的定义
      typedef struct objc_selector *SEL;
  • SEL 对象的创建
1
2
SEL s = @selector(test);
SEL s2 = NSSelectorFromString(@"test");
  • SEL 对象的其他用法
1
2
3
4
5
6
// 将 SEL 对象转为 NSString 对象
NSString *str = NSStringFromSelector(@selector(test));

Person *p = [Person new];
// 调用对象 p 的 test方法
[p performSelector:@selector(test)];

每个方法的内部都有一个隐藏的 SEL 对象:_cmd,代表当前方法

1
2
3
4
5
6
- (void)test {
NSString *str = NSStringFromSelector(_cmd);
NSLog(@"_cmd = %@", str);
// 会引发死循环
// [self performSelector:_cmd];
}

copy 和 mutableCopy

  1. 一个对象使用 copy 或 mutableCopy 方法可以创建对象的副本
  2. copy - 需要先实现 NSCopying 协议,创建的是不可变得副本(如 NSString、NSArray、NSDictionary)
  3. mutableCopy - 需要先实现 NSMutableCopying 协议,,创建的是可变副本(如 NSMutableString、NSMutableArray、NSMutableDictionary)
  4. 深复制:内容拷贝,源对象和副本指向不同的两个对象。源对象引用计时器不变,副本计数器设置为 1
  5. 浅复制:指针拷贝,源对象和副本指向的是同一个对象。对象的引用计数器+1,其实相当于做了一次 retain 操作
  6. 只有不可变对象创建不可变副本(copy)才是浅复制,其他都是深复制

copy 语法的目的:改变副本的时候,不会影响到源对象

为自定义类添加复制功能

  1. 如果想自定义 copy,那么就必须遵守 NSCopying,并且实现 copyWithZone 方法
  2. 如果想自定义 mutableCopy,那么就必须遵守 NSMutableCopying,并且实现 mutableCopyWithZone:方法
  3. 以 copy 为例,建议用 [self class] 代替直接类名
1
2
3
4
5
- (id)copyWithZone:(NSZone *)zone {
id copy = [[[self class] allocWithZone:zone] init];
// 做一些属性的初始化
return copy;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 演示字符串的拷贝
NSString *string = [[NSString alloc] initWithFormat:@"age is %i", 21];// 1
// 产生了一个新的对象,引用计数器为 1.源对象的引用计数器不变
NSMutableString *str = [string mutableCopy];// 1
NSLog(@"str: &zi", [str retainCount]); // 1
NSLog(@"string: %zi", [string retainCount]);// 1
// str 和 string 不是相同对象
NSLog(@"%i", str == string);
// 修改 str 看 string 是否有被修改
[str appendString@"123456"];
NSLog(@"str = %@", str);
NSLog(@"string = %@", string);
[str release];// 0
[string release];// 0
// 如果一个对象是不可变的,copy 的对象也是不可变的,系统会直接返回被 copy 的对象本身
// copy 产生的是不可变副本,由于源对象本身就不可变,所以为了性能着想,copy 会直接返回源对象本身,相当于源对象的 retain 操作,引用计数器 +1
NSString *s1 = [[NSString alloc] initWithFormat:@"age is %i", 21];
NSLog(@"s1: %zi", [s1 retainCount]);// 1
NSString *s2 = [s1 copy];
NSLog(@"s1: %zi", [s1 retainCount]);// 2
NSLog(@"%i", s2 == s1);

Student.h:

1
2
3
4
5
6
7
8
@interface Student : NSObject <NSCopying>
// copy 代表 setter 方法会 release 旧对象,copy 新对象
// 修改外面的变量,并不会影响到内部的成员变量
// 建议:NSString 一般用 copy 策略,其他对象一般用 retain
@property (nonatomic, cop)NSString *name;

+ (id)studentWithName:(NSString *)name;
@end

Student.m:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@implementation Student
+ (id)studentWithName:(NSString *)name {
Student *stu = [[[Student alloc] init] autorelease];
stu.name = name;
return stu;
}
// 这里创建的副本对象不要求释放
- (id)copyWithZone:(NSZone *)zone {
Student *copy = [[Student allocWithZone:zone] init];
copy.name = self.name;
return copy;
}

- (void)description {
return [NSString stringWithFormat:@"[name = %@]", _name];
}

- (void)dealloc {
[_name release];
[super release];
}
@end

main.m:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 这里写 self class 是为了防止子类在创建的过程中导致类型错误
Student *stu = [[[[self class] alloc] init] autorelease];
NSMutableString *string = [NSMutableString stringWithFormat@"age is %i", 21];
stu.name = string;
[string addendString:@"abcd"];
// 如果 Student.h 中的 *name 是 retain,那么修改了 string,stu.name 也被修改了
// 如果 Student.h 中的 *name 是 copy,那么修改了 string,stu.name 就不会被修改
NSLog(@"name = %@", stu.name);
NSLog(@"string = %@", string);

// Student 的 copy
Student *stu1 = [Student studentWithName:@"student1"];
// 如果 Student 没有实现 NSCopying 协议,那么会报错:unrecognized selector sent to instance....
Student *stu2 = [stu1 copy];
stu2.name = @"stu2";
NSLog(@"stu1 = %@", stu1);
NSLog(@"stu2 = %@", stu2);
[stu2 release];

类的本质
类本身也是一个对象,是一个 Class 类型的对象,简称类对象
Class 类型的定义:
typedef struct objc_class *Class;
类名就代表着类对象,每个类只有一个类对象
例如:
利用 Class 创建 Person 类对象
利用 Person 类对象创建 Person 类型的对象

获取内存中的类对象:

1
2
3
4
5
6
7
8
Person *p1 = [[Person alloc] init];
Person *p2 = [[Person alloc] init];
// 第一种方法
Class c1 = [p1 class];
Class c2 = [p2 class];
// 第二种方法
Class c3 = [Person class];
NSLog(@"c1 = %p, c2 = %p, c3 = %p", c1, c2, c3);

获取的类对象(Class)可以调用类方法,比如 Person.h 中有一个名为 test 的类方法:

1
+ (void)texst;

Person.m 中有实现该方法:

1
2
3
+ (void)test {
NSLog(@"调用了类方法 test");
}

测试:

1
2
Class c = [p1 class];
[c test];

类的加载和初始化:
+load

  1. 在程序启动的时候会加载所有的类和分类,并调用所有类和分类的 +load 方法
  2. 先加载父类,再加载子类:也就是先调用父类的 +load 方法,再调用子类的 +load 方法
  3. 先加载原始类,再加载分类
  4. 不管程序运行过程有没有用到这个类,都会调用 +load 加载

+initialize

  1. 在第一次使用某个类时(比如创建对象等),就会调用一次 +initialize 方法
  2. 一个类只会调用一次 +initialize 方法,先调用父类的,再调用子类的

+ (void)load 方法在程序启动的时候,就会加载一次项目中所有的类和分类。类加载完毕后就会调用每个类和分类的 load 方法。只会调用一次

load 方法会从父类开始调用,再是子类,包括 Category 也会调用 load 方法
+ (void)initialize 方法在第一次使用某个类的时候调用

initialize 方法也是先初始化父类,再是子类

description 方法

  1. -description 方法:使用 NSLog 和 %@ 输出某个对象时,会调用对象的 -description 方法,并拿到返回值进行输出
  2. +description 方法:使用 NSLog 和 %@ 输出某个对象时,会调用类对象的 +description 方法,并拿到返回值进行输

死循环陷阱:如果在 -description 方法中使用 NSLog 打印 self

构造方法
作用:用来初始化对象的方法,是一个对象方法,- 开头
重写构造方法的目的:为了让对象创建出来,成员变量就已经有一些固定的值
重写构造方法的注意点:

  1. 先调用父类的构造方法([super init])
  2. 再进行子类的内部成员变量的初始化

例如:
重写 -init 方法:

1
2
3
4
5
6
7
8
9
10
11
- (id)init {
// 1. 一定要调用回 super 的 init 方法:初始化父类中声明的一些成员变量和其他属性
self = [super init];// 当前对象 self
// 2. 如果对象初始化成功,才有必要进行接下来的初始化操作
if (self != nil) {
// 初始化成功
_age = 10;
}
// 3. 返回一个已经初始化完毕的对象
return self;
}

父类的属性交给父类方法去处理,子类方法处理子类自己的属性
自定义构造方法的规范:

  1. 一定是对象方法,一定以 - 开头
  2. 返回值一般是 id 类型
  3. 方法名一般以 init 开头

NSLog 输出的一些补充(都是两个下划线 _ ):

1
2
3
4
5
6
7
// 输出当前函数名
NSLog(@"%s\n", __func__);
// 输出行号
NSLog(@"%d", __LINE__);
// NSLog 输出 C 语言字符串的时候,不能有中文,可以使用 printf 函数输出
// NSLog(@"%s", __FILE__);
printf("%s\n", __FILE__);

ARC
ARC 的判断准则:只要没有强指针指向对象,就会释放对象
ARC 的特点:

  1. 不允许调用 release、retain、retainCount
  2. 允许重写 dealloc,但是不允许调用 [super deallo]
  3. @property 的参数:
    • strong:成员变量时强指针,相当于原来的 retain(适用于OC对象类型)
    • weak:成员变量时弱指针,相当于原来的 assign(适用于OC对象类型)
    • assign:适用于非 OC 对象类型

指针分两种:

  1. 强指针:默认情况下,所有的指针都是强指针 __strong
  2. 弱指针:__weak

Xcode 是默认使用 ARC 的,如果某个 .m 文件真的不想使用 ARC,可以通过以下步骤来不适用 ARC:
选择 Xcode 右侧项目树的根,然后是 TARGETS -> Build Phases -> Compile Sources ,下拉,选择目标 .m 文件,回车或者双击,弹出输入框,输入 -fno-objc-arc 回车,就可以了,如下图所示:

如果开发环境是非 ARC 的,想要使用 ARC 的,将上面的 -fno-objc-arc 改成 -f-objc-arc 就可以了。

ARC 循环引用
当两端循环引用的时候,解决方案如下:

  1. ARC
    1端用 strong,另一端用 weak
  2. 非ARC
    1端用 retain,另一端用 assign

例如:
在使用 ARC 下,有两个类:Person、Dog,如下:
Person.h:

1
2
3
4
5
6
@class Dog;

@interface Person : NSObject
// 人有一只狗,strong,强指针
@property (nonatomic, strong) Dog *dog;
@end

Person.m:

1
2
3
4
5
@implementation Person
- (void)dealloc {
NSLog(@"Person ---- dealloc");
}
@end

Dog.h:

1
2
3
4
5
6
@class Dog;

@interface Dog: NSObject
// 狗有一个主人,strong,强指针
@property (nonatomic, strong) Person *person;
@end

Dog.m:

1
2
3
4
5
@implementation Dog
- (void)dealloc {
NSLog(@"Dog ---- dealloc");
}
@end

main.m:

1
2
3
4
5
6
Person *p = [[Person alloc] init];
Dog *d = [[Dog alloc] init];
// 第一种情况:
// 当两个互指之后,会出现内存泄露,两个对象的 dealloc 没有被调用,也就是两个对象的内存没有被释放
p.dog = d;
d.person = p;

如图所示:

1
2
3
4
5
// 第二种情况:
// 如果注释上面两行代码中的任意一行,两个对象都可以被释放
// 因为当 main 函数执行完毕之后,对象 p 和 d 都被回收,但是两个内存中的对象有强指针,不会被回收,所以会造成内存泄露
p.dog = d;
// d.person = p;

如图所示:

如果将 Dog 中的 person 属性改成 weak:

1
@property (nonatomic, weak) Person *person;

那么,上面第二种情况就变成了如下图所示:

这样的话,当程序运行结束,被回收的就是 Person 对象,既然 Person 对象被回收了,那么 Dog 对象就没有了强指针,也会被回收了。