OC学习记录
记录一些函数和语法。。。
# 一些基础 C 函数
# sleep
用于等待指定的秒数,在unistd.h
中声明
# atoi
获取一个字符串后将它转换成整数,如果不能转换,就返回 0。
int num = atoi("23");
# modf
返回浮点数的整数部分和小数部分,整数部分通过引用传递。
double pi = 3.14;
double integerPart;
double fractionPart = modf(pi, &integerPart);
printf("%.0f, %.2f\n", integerPart, fractionPart);
2
3
4
# time 和 localtime_r
time
函数返回英国格林尼治时间从 1970 到当前累计时间。localtime_r
函数可以读取当前秒数。localtime_r
有两个形式参数,分别是 time_t 类型变量的地址和 struct tm 类型变量的地址。
long t = time(NULL);
printf("%ld\n", t);
struct tm now;
localtime_r(&t, &now);
printf("The time is %d:%d:%d\n", now.tm_hour, now.tm_min, now.tm_sec);
2
3
4
5
# OC 部分
# import 指令
这是一个预处理指令,是 include 的增强版,将文件的内容在预编译的时候拷贝写指令的地方。同一个文件无论 import 多少次,只会包含一次。
# NSLog
printf 增强版,用于输出一些调试信息。
- 执行这段代码的时间
- 程序的名称
- 进程编号
- 线程编号
- 输出的信息
# OC 程序编译
使用编译器将源代码编译成目标文件
cc -cxx.m
- 预处理
- 检查语法
- 编译
链接
cc xx.o
如果程序中使用了框架中的函数或者类,那么在链接的时候,就必须要告诉编译器,去框架中寻找那个类。
cc xx.o -framework 框架名称
链接成功之后,会生成一个 a.out 可执行文件。
# 内存中的五大区域
- 栈 存储局部变量
- 堆 手动申请的字节空间 malloc、calloc、realloc
- BSS 段 存储未被初始化或初始化为 0 的全局变量 静态变量
- Data 段(常量段) 存储已经被初始化的全局、静态变量 常量数据
- 代码段 存储代码
# id
当声明指向对象的指针时,通常都会明确地写出相应对象的类:
NSDate *expiration;
当声明的指针不知道所指对象的准确类型时,可以使用 id 类型,id 类型的含义是:可以指向任意类型 Objective-C 对象的指针。id 已经隐含了星号的作用。
id delegate;
# NSObject 和 id 的区别
相同点:万能指针 都可以指向任意的 oc 对象
不同点: 通过 NSObject 指针去调用对象方法的时候,编译器会做编译检查。通过 id 类型去调用对象方法的时候,编译器会直接通过。
注意:id 指针只能调用对象的方法,不能使用点语法,如果使用点语法就会直接编译错误。
# NSString
# 创建字符串实例
NSString *lament = @"Why me?";
@"..."
是 OC 中的一个缩写,代表根据给定的字符串创建一个NSString
对象。这种缩写为字面量语法。
# 创建动态字符串
NSString *dateStr = [NSString stringWithFormat: @"The date is %@", now];
# NSString 方法
NSString *str = @"Hello World";
// 获取长度
NSUInteger count = [str length];
NSLog(@"%lu", count);
NSString *temp = @"123";
// 判断字符串是否相同
if([temp isEqualToString: str]) {
NSLog(@"equal");
}
// 返回原字符串大写版本
NSString *upper = [str uppercaseString];
NSLog(@"%@", upper);
NSString* str = @"test\n123\n";
NSArray *array = [str componentsSeparatedByString:@"\n"];
for (NSString* str in array) {
NSLog(@"%@", str);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# NSArray/NSMutableArray
创建 NSArray,使用字面量语法。
NSDate *now = [NSDate date];
NSDate *tomorrow = [now dateByAddingTimeInterval:24.0*60.0*60.0];
NSDate *yesterday = [now dateByAddingTimeInterval:-24.0*60.0*60.0];
NSArray *dateList = @[now, tomorrow, yesterday];
NSLog(@"%@", dateList[0]);
// 获取数组中的元素个数
NSLog(@"%@", [dateList count]);
2
3
4
5
6
7
8
NSArray 实例是无法改变的,一旦被创建,就无法添加或者删除。
创建 NSMutableArray
NSMutableArray *dateList = [NSMutableArray array];
[dateList addObject: now];
[dateList addObject: tomorrow];
[dateList insertObject: yesterday atIndex: 0];
[dateList removeObjectAtIndex: 0];
2
3
4
5
# 快速枚举
for (NSDate *d in dateList) {
NSLog(@"%@", d);
}
2
3
# 类加载
当某个类第一次被访问时,会被加载到代码段,这个过程叫类加载
执行方法的 4 步:
- 将方法加载到代码段
- 声明方法的参数到栈
- 把实参的值赋值给形参
类一旦被加载到代码段之后,是不会被回收的,除非程序结束。
# 对象在内存中的存储
Person *p1 = [Person new];
- Person *p1; 会在栈中申请一块空间。在栈内存中声明一个 Person 类型的指针变量 p1。
- [Person new]; 真正在内存中创建对象的是这句代码。new 做了下面几件事情。
- 在堆内存中申请一块合适大小的空间。
- 在这个空间中根据类的模版创建对象。类模版中定义了哪些属性,就把这些属性依次声明在对象之中。对象中还有另外一个属性,叫做
isa
,是一个指针,它指向对象所属的类在代码段中的地址。- 初始化对象的属性,如果属性的类型是基本数据类型,那么就赋值为 0,如果属性的类型是 C 语言的指针类型,就赋值为 NULL,如果属性的类型是 OC 的类指针类型,那么就赋值为 nil。
- 返回对象的地址
注意事项
- 对象中只有属性,而没有方法,自己类的属性外加 1 个 isa 指针指向代码段中的类。
- 如何访问对象的属性 指针名 -> 属性名 根据指针,找到指针指向的对象,再找到对象中的属性类访问。
- 如何调用方法 [指针名 方法名]; 先根据指针名找到对象,对象发现要调用方法,再根据对象的 isa 指针找到类,然后调用类里的方法。
# self
- 当类第一次被访问时,会将类的代码存储在代码区,代码区中用来存储类的空间也有 1 个地址
- 在类方法中,self 也是一个指针,指向这个类在代码段中的地址,self 在类方法中就相当于这个类
- 可以查看 isa 指针的值,或者在类方法中查看 self 的值,调用对象的
class
方法,就会返回这个对象所属的类在代码段中的地址。调用类的class
方法,也会返回这个类在代码段中的地址。 - 可以在类中的类方法里通过 self 调用其他的类方法
# 子类的存储
- 子类对象中有自己的属性和所有父类的属性
- 代码段中的每一个类都有一个
isa
指针,这个指针指向它的父类,一直指向NSObject
。
# 类的本质
在代码段中存储类的步骤:
- 先在代码段中创建 1 个 Class 对象,Class 是 Foundation 框架中的 1 个类。这个 Class 对象就是用来存储类信息的。
- 将类的信息存储在这个 Class 对象之中。这个 Class 对象至少有 3 个属性,类名:存储的这个类的名称,属性:存储的这个类具有哪些属性,方法:存储的这个类具有哪些方法。
所以。类是以 Class 对象的形式存储在代码段的。存储类的这个 Class 对象我们也叫做类对象。用来存储类的 1 个对象。所以,存储类的类对象也有 1 个叫做 isa 指针的属性这个指针指向存储父类的类对象.
Class c1 = [Person class];
NSLog(@"c1 = %p", c1);
Person *p = [Person new];
Class c2 = [p class];
NSLog(@"c2 = %p", c2);
2
3
4
5
6
- 调用类的类方法
class
,可以得到存储类的类对象的地址。 - 调用对象的对象方法
class
,就可以得到存储这个对象所属的类的 Class 对象的地址。 - 对象中的 isa 指针的值其实就是代码段中存储类的类对象的地址。
Class 指针在 typedef 的时候已经加了*,不需要在定义的时候加*。
# 类对象的使用
使用类对象调用类的方法
只能调用类方法,因为类对象就等价于存在其中的类。
// c1就代表Person类
Class c1 = [Person class];
[Person sayHi];
[c1 sayHi];
2
3
4
使用类对象来调用new
方法
Person *p1 = [Person new];
Class c1 = [Person class];
Person *p2 = [c1 new];
2
3
# 方法的本质
SEL
全程叫做selector
选择器,SEL
是一个数据类型,需要在内存中申请空间存储数据。SEL 其实是一个类,SEL 对象用来存储一个方法的。
方法的存储:
- 先创建 1 个 SEL 对象
- 将方法的信息存储在这个 SEL 对象之中
- 再将这个 SEL 对象作为类对象的属性
SEL fun = @selector(sayHi);
NSLog(@"sel = %p", fun);
2
方法的调用
[Person sayHi];
- 先拿到存储 sayHi 方法的 SEL 对象,也就是拿到存储 sayHi 方法的 SEL 数据,SEL 消息。
- 将这个 SEL 消息发送给 p1 对象
- 这个时候,p1 对象接收到这个 SEL 消息以后就要调用的方法。
- 根据对象的 isa 指针找到存储类的类对象
- 找到这个类以后,在这个类对象中去搜索是否有和传入的 SEL 数据相匹配的。如果有就执行,如果没有再找父类,直到 NSObject。
OC 最重要的机制,消息机制,调用方法的本质其实是为对象发送 SEL 对象。
# 手动为对象发送消息
- 先得到 SEL 数据
- 将这个 SEL 消息发送给 p1 对象
- 调用对象的
performSelector
方法,结果和[p sayHi]
一致。
Person *p = [Person new];
[p sayHi];
[p performSelector:fun];
2
3
带参数函数调用
SEL fun = @selector(setName:);
Person *p = [Person new];
NSString *name = @"jack";
[p performSelector:fun withObject: name];
NSLog(@"name = %@", [p name]);
2
3
4
5
# instancetype
+ (instancetype) person {
return [self new];
}
2
3
instancetype 只能作为方法的返回值,不能在别的地方使用。如果写成具体的类型,那么子类集成时,返回的就是父类的指针。
# isKindOfClass / isMemberOfClass
+ (BOOL)respondsToSelector:(SEL)aSelector; // 判断对象中是否有这个方法可以执行
+ (BOOL)instancesRespondToSelector:(SEL)aSelector; // 判断类中是否有指定的类方法
- (BOOL)isKindOfClass:(Class)aClass; // 判断是否是某类的对象或子类对象
- (BOOL)isMemberOfClass:(Class)aClass; // 判断是否是某类的对象,不包括子类对象
2
3
4
# 构造方法
new
方法的内部,其实是先调用的alloc
方法,再调用init
方法。
Person *p1 = [Person new];
Person *p2 = [[Person alloc] init];
2
init
方法:
初始化对象,为对象的属性赋初始值,这个init
方法我们叫做构造方法。
# 重写init
方法的规范
- 必须先调用父类的 init 方法,然后将方法的返回值赋值给 self
- 调用 init 方法可能会失败,如果初始化失败,返回的是 nil
- 如果初始化成功,基于初始化当前对象的属性
- 最后返回 self 的值
- (instancetype) init
{
self = [super init];
if (self != nil) {
self.name = @"jack";
}
return self;
}
2
3
4
5
6
7
8
# 自定义构造方法
- (instancetype) initWithName: (NSString *)name andAge:(int) age
{
if (self = [super init]) {
self.name = name;
self.age = age;
}
}
2
3
4
5
6
7
# 内存管理
栈:局部变量,当局部变量的作用域被执行完毕之后,这个局部变量就会被系统立即回收。
堆:内存管理
BSS 段:未初始化的全局变量、静态变量,一旦初始化就回收,并转存到数据段中。
数据段:已经初始化得到全局变量、静态变量,直到程序结束才会被回收
代码段:代码、程序结束的时候,系统会自动回收存储在代码段中的数据。
栈、BSS 段、数据段、代码段中的数据,都是由系统自动完成回收。
# 引用计数
每一个对象都有 1 个属性,叫做retainCount
,类型是 unsigned long 占 8 个字节。引用计数器的作用,用来记录当前这个对象有多少个人在使用。默认情况下,创建一个对象出来,它的计数器默认为 1。当这个对象的引用计数器变为 0 时,系统会自动回收这个对象。
Person *p = [[Person alloc] init];
NSLog(@"count = %lu", [p retainCount]); // count = 1
2
具体操作:
- 当对象发送一条 retain 消息,对象的引用计数器就会加 1
- 当对象发送 1 条 release 消息时,对象的引用计数器减 1
- 当对象的引用计数器变为 0 的时候,对象会被系统立即回收,在被回收的时候,会被调用对象的 dealloc 方法
内存管理分为MRC
和ARC
。
MRC:Manual Referen Counting 手动引用计数
ARC:Automatic Reference Counting 自动引用计数
# MRC
默认开启 ARC,需要将 Build Setting 中的 ARC 关闭之后,才能使用 MRC。具体操作见下图。
实现一下类的dealloc
方法。
- (void)dealloc {
NSLog(@"dealloc");
[super dealloc];
}
2
3
4
在主函数中调用相关方法。
Person *p = [[Person alloc] init];
NSLog(@"count = %lu", [p retainCount]); // count = 1
[p retain];
NSLog(@"count = %lu", p.retainCount); // count = 2
[p release];
[p release];
// dealloc
2
3
4
5
6
7
release
消息并不是回收对象,而是让对象的引用计数器-1,当引用计数器为 0 的时候,才会回收对象。回收只是意味着对象所占用的空间可以分配给别人,但是空间里存储的数据还在。
Person *p = [[Person alloc] init];
[p release]; // retainCount = 0, dealloc
[p sayHi]; // 可能还可以正常运行
2
3
# 野指针和僵尸对象
OC 中的野指针:指针指向的对象已经回收了,这样的指针叫做野指针。
僵尸对象:1 个已经释放的对象,但是这个对象所占的空间还没有分配给别人,这样的对象叫做僵尸对象。
当我们通过野指针去访问僵尸对象的时候,有可能有问题,也有可能没有问题。
当僵尸对象占用的空间分配给了别人使用的时候,就不可以。
# 内存泄漏
指的是 1 个对象没有被及时的回收,在该回收的时候而没有被回收,一直驻留在内存中,直到程序结束的时候才回收。
单个对象内存泄漏的情况:
- 有对象创建,没有对应的 release
- retain 和 release 的次数不匹配
- 在不适当的时候,为指针赋值为 nil
- 在方法当中为传入的对象进行不适当的 retain
如何确保单个对象可以被回收:
- 有对象的创建,就必须要匹配 1 个 release
- retain 次数和 release 次数一定要匹配
- 只有在指针称为野指针的时候才能赋值为 nil
- 在方法当中不要随意为传入的对象 retain
# OC 属性
@property (nonatomic, retain) NSString *name;
@property (nonatomic, assign) int age;
2
- assign: 默认值,生成的 setter 方法的实现就是直接赋值
- retain:生成的 setter 方法的实现是标准的 MRC 内存管理代码,也就是,先判断新旧对象是否为同 1 个对象,如果不是,release 旧的,retain 新的。
当属性的类型是 oc 对象类型的时候,使用 retain,否则用 assign
注意:
retain 参数,只是生成标准的 setter 方法为标准的 MRC 内存管理代码,不会自动的在 dealloc 中生成 release 方法,所以我们还要自己手动在 dealloc 中 release。
readwrite 和 readonly
readwrite 是默认值,代表同时生成 getter、setter
readonly:只会生成 getter
@property (nonatomic, assign, getter=xxx, setter=xyz) int age; // 指定getter setter方法名
# 自动释放池
存入自动释放池中的对象,在自动释放池被销毁的时候,会自动调用存储在该自动释放池中的所有对象的 release。
# ARC
在 ARC 机制下,当没有任何类型的强指针指向对象的时候,这个对象就会被立即回收。
- 指向对象的强指针被回收
- 指向对象的强指针被赋值为 nil
在 ARC 下,property 不可以添加 retain 参数,属性默认是 strong。
# 分类
在分类中可以写@property,但是不会自动生成私有属性,也不会自动生成 getter、setter 的实现,只会生成 getter、setter 的声明,需要自行实现
在分类的方法实现中不可以直接访问本类的真私有属性(定义在 implementation 中),但是可以调用本类的 getter、setter 来访问属性
当分类中有和本类同名的方法的时候,优先调用分类的方法,哪怕没有引入分类的文件,如果多个分类中有同名的方法,那么会调用最后编译的。
@interface Person (grow)
@end
2
3
# 延展
@interface Person ()
@end
2
3
本质上是一个分类,作为本类的一部分,只有声明,没有单独的实现,和本类共享一个对象。
延展和分类的区别:
- 分类有名字,延展没有,是一个匿名的分类
- 每一个分类都有单独的声明和实现,而延展只有声明,没有单独的实现,和本类共享一个实现
- 分类中只能新增方法,而延展中任意的成员都可以写
- 分类中可以写@property,但是只会生成 getter、setter 的声明,延展中写@property 会自动生成私有属性,也会生成 getter、setter
使用场景:
- 要为类写一个私有的@property,生成的 getter、setter 方法只有在类的内部访问,不能在外部访问
- 延展 100%不会独占 1 个文件,都是将延展写在本类的实现文件中。这个时候,写在延展中的成员,就相当于这个类的私有成员,只能在本类的实现中访问。
- 将私有方法声明在延展中,在本类中实现,提高代码的阅读性
- 如果类的成员只希望在类内部访问,定义在延展中,希望在外部访问的话,定义在@interface 中
# block
block 是一个数据类型,block 类型的变量中专门存储 1 段代码,这段代码可以有参数,也可以有返回值
void (^myblock) (void) = ^{
NSLog(@"myblock");
};
int (^myblock1) (void) = ^int {
NSLog(@"myblock1");
return 1;
};
int (^myblock2) (int, int) = ^int(int num1, int num2) {
return num1 + num2;
};
2
3
4
5
6
7
8
9
10
可以使用 typedef 来简化 block 的声明。
typedef void (^VoidBlock) (void);
VoidBlock v = ^void {
NSLog(@"myblock");
};
2
3
4
获取/修改外部变量
- 在 block 中可以获取外部局部变量和全局变量的值
- 在 block 代码内部可以修改全局变量的值,但是不能外部局部变量的值
- 给局部变量添加
__block
修饰符,就可以在 block 中修改
__block int num = 0;
int (^myblock2) (int num1, int num2) = ^int(int num1, int num2) {
num++;
return num1 + num2;
};
2
3
4
5
block 作为函数参数
typedef void (^VoidBlock) (void);
void test(VoidBlock testBlock) {
testBlock();
}
test(^{
NSLog(@"Hello");
});
2
3
4
5
6
7
8
block 作为函数返回值
VoidBlock fun() {
VoidBlock res = ^{
NSLog(@"Hello World");
}
return res;
}
2
3
4
5
6