学习笔记
( 摘自《iOS开发:从零基础到精通》)
-
id 和 instancetype
- id 类型:通用对象类型,类似于 C 语言中的 void *,可以指向任意一个继承了 NSObject 类的对象(id 本身就是一个指针,所以使用时不需要加星号),是 Objective-C 动态绑定的基础
// 示例:一个数组中存储了 NSNumber 和 NSString 两种类型的对象,因为不知道每个对象的类型,所以可以用 // id 这种通用对象类型 int main(int argc, const char *argv[]) { @autoreleasepool { NSString *array = @[@123, @"456"]; id obj = array[1]; NSLog(@"%@", obj); } return 0; }
- instancetype 类型:在类方法中,例如,以 alloc、new 开头的方法,以及实例方法中 autorelease、init、retain 等方法的返回值类型就是 instancetype 类型,这些就称为关联返回类型的方法,即返回值是一个以方法所在的类为类型的对象,使用 instancetype 作为方法返回值的好处是可以确定对象类型,以便帮助编译器更好地定位代码问题
- id 和 instancetype 的异同:
- 相同点:都可以作为方法的返回值类型
- 不同点:
- id 可以作为方法或函数的参数的类型,也可以单独使用这种类型定义变量,但是 instancetype 不行
- 如果方法的返回值类型是 instancetype,那么它返回的一定是这个类型的对象;如果方法的返回值类型是 id,那么它返回的是未知类型的对象
-
懒加载:
- 当需要获取某个属性的值时,再对该属性对象的实例变量进行初始化,从而提升内存的使用效率
- 懒加载实际上就是对属性的 getter 方法进行重写,可以对属性进行一些初始化的操作
// 使用懒加载初始化 name 属性,在初始化时给其赋值 -(NSString *)name { if (_name == nil) { // _name 是 name 属性对应的实例变量 _name = [NSString stringWithFormat:@"hello"]; } return _name; }
-
公有属性和私有属性:
- 公共属性:在 .h 文件中声明的属性,可供外部调用
- 专有属性:在 .m 文件中声明,只能在该类内部使用
-
属性关键字:
-
原子性:
- atomic(默认):
- 意味着多线程中只有一个线程能访问实例变量
- 是线程安全的,但是会影响访问速度
- 在非 ARC(自动引用计数)编译环境下,需要设置访问锁来保证对该变量进行正确的 getter/setter
- nonatomic:
- 多个线程可以同时对其进行访问
- 非线程安全的,访问速度快
- 当两个不同的线程对其访问时,容易失控
- atomic(默认):
-
存取方法:
-
readwrite(默认)
-
readonly
-
在声明属性时,可以指定存取方法的自定义名称,通常用于 BOOL 类型属性的 getter 方法,例如
@property(nonatomic, getter=isHidden) BOOL hidden;
-
-
内存管理:
- strong(默认):强引用,表示实例变量对传入的对象要有所有权关系,引用计数加1
- weak:弱引用,在 setter 方法中,对传入的对象不进行引用计数加1的操作,当该对象引用计数为0时,该对象被释放,用 weak 声明的实例变量指向 nil,即实例变量的值为0
- assign:简单复制,不改变索引计数,适用于简单数据类型,如 int、float、double、NSInteger、CGFloat等
- copy:用于在内存中保留一份传入值的复制,而不是值自身的情况,即把原来的对象完整地复制到另外一个新的内存区,当副本改变时,原对象并不同时改变,同样当原对象发送改变时,其副本也不会发生改变,因为原对象和复制对象存储在两个独立的内存区域中,copy 和 strong 的区别在于实例变量是对传入对象的副本拥有所有权,而非对象本身
-
-
方法名称:
- 一个方法的实际名称是所有签名关键词的串联,包括冒号,例如
-(void)insertString:(NSString *)aString atIndex:(NSUInteger)loc;
方法名是 insertString:atIndex:
-
消息处理机制:
- 在 Objective-C 中,消息是直到运行时才和方法进行绑定关联的,关键在于编译器为类和对象生成的结构,其中类的结构中包含两个基本元素:一个指向父类的指针和一个类的方法列表;而当对象被创建时,对象的第一个实例变量是一个指向该对象的“类结构”的指针(isa 指针),通过该指针,就可以访问到该类及其父类的方法列表
- 当向某个对象发送消息时:
- 首先根据 isa 指针,找到该对象对应的类结构的方法列表,继而即可找到具体的方法实现;当在本类的方法列表中找不到对应的方法时,会根据类结构中父类的指针去查找父类的方法列表,直至 NSObject 根类
- 将对象以及参数传递给找到的方法实现
- 执行方法中的代码,获取方法的返回值
-
对象操作:
- 判断对象的类型:isKindOfClass
- 判断对象是否响应消息:respondsToSelector
- 对象间的比较:isEqual
- 对象复制:copy
-
const 关键字:
- const 修饰离其最近的变量
- const 和 宏 #define 的对比:
- 涉及常量的定义都建议用 const(并且都放到一个类中),其处理性能比宏定义高,因为当多次使用该常量时,只要在内存中创建一个对象即可,而如果使用宏定义,在程序编译时会把所有的宏都替换成常量,当程序运行时,会创建多个对象,占用多个内存区域,执行效率低一点
-
数组的复制:
- 直接赋值:浅复制,在内存中只有一个对象
- arrayWithArray:深复制
-
int、NSInteger 和 NSNumber 的对比:
- int 类型的大小是由系统的架构决定的
- NSInteger 是一种动态定义的类型,会根据系统使用最大的 int
- NSInteger 是基础类型,NSNumber 是一个类,如果需要在数组中存储一个数值,不能直接使用 NSInteger ,因为 Objective-C 的集合中存储的数据必须是对象
-
Block:
-
Block 块是封装工作单元的对象,是可以在任何时间执行的代码段,其本质是可移植的匿名函数
-
可以作为方法和函数的参数传入,也可以从方法和函数中返回
-
可以把 Block 赋给一个变量,这个变量就是指向 Block 的指针,调用已定义的 Block 时类似于函数调用
// 定义 int (^printBlock)(int) = ^(int inputNum) { NSLog(@"printBlock Called!"); return inputNum; } // 调用 int i = printBlock(100);
-
可以把 Block 声明为类的属性(需要使用 copy 关键字),可以使用点语法来对属性进行取值和赋值
-
操作 Block 外部的变量:
- 访问 block 外的变量
int main(int argc, const char *argv[]) { @autoreleasepool { int i = 100; void (^beginBlock)(void) = ^(void) { NSLog(@"i: %d", i); // 如果 i 定义在 Block 的定义之前则可以访问 // i = 200; // 报错,此时 Block 是不能修改 Block 外定义的变量的 }; beginBlock(); // 输出100 i = 200; beginBlock(); // 输出100,说明 Block 只有在定义时能捕获一次 i,后面 i 改变 Block 是无法捕获的 NSLog(@"i: %d", i); // 输出200 } return 0; }
- 修改 block 外的变量:要在变量声明时加上 __block 关键字
int main(int argc, const char *argv[]) { @autoreleasepool { __block int i = 100; void (^beginBlock)(void) = ^(void) { NSLog(@"i: %d", i); // 如果 i 定义在 Block 的定义之前则可以访问 // i = 200; // 此时 Block 可以修改 Block 外定义的变量 }; beginBlock(); // 输出100 i = 200; beginBlock(); // 输出200,说明此时 Block 能捕获到 i 的变化 NSLog(@"i: %d", i); // 输出200 } return 0; }
-
Block 回调
- 定义带 Block 参数的方法
- 设置 Block 的回调时机
- 定义 Block 中需要执行的操作
-
-
分类 Category:
- 当需要对一个类新增一些新方法时使用,特别是针对系统自定义的类,如 UIView、 UIImageView 等
- 注意:
- 不要用分类去重写已存在的方法
- 通过使用分类添加的方法,不仅针对该类有效,对其子类也有效,例如:给 UIView 添加了分类,UIImageView、UIButton 等子类也能用
- 类 + 分类名称必须唯一
-
KVC:
-
Key-value coding 键值编码
// 赋值 -(void)setValue:(nullable id)value forKey:(NSString *)key; // 简易路径赋值 -(void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; // 复合路径赋值 // 取值 -(nullable id)valueForKey:(NSString *)key; // 简易路径取值 -(nullable id)valueForKeyPath:(NSString *)keyPath; // 复合路径取值
-
修改 readonly 的属性以及私有属性:KVC 和点语法都可以用于对象属性的赋值,但是点语法只能给未被标记为 readonly 的属性赋值,而 KVC 是更加底层的技术,可以给 readonly 的属性赋值;此外,通过 runtime 遍历出来的私有属性/私有变量也能通过 KVC 进行赋值和取值(不推荐)
-
-
KVO:
- Key Value Observing 观察者模式,当数据模型的属性值改变后,作为监视器的视图组件就会触发特定的方法,在该方法中可以获取数据模型变更的数值,从而更新界面 UI
-
对象复制:
-
深复制和浅复制
-
可变对象复制和不可变对象复制:
对象 对象类型 copy mutableCopy NSString 非容器类不可变对象 浅复制 深复制 NSMutableString 非容器类可变对象 深复制 深复制 NSArray 容器类不可变对象 浅复制 深复制 NSMutableArray 容器类可变对象 深复制 深复制 NSDictionary 容器类不可变对象 浅复制 深复制 NSMutableDictionary 容器类不可变对象 深复制 深复制 (注意:容器类对象内的对象都是浅复制)
-
自定义对象复制:实现 copyWithZone 方法(遵守 NSCopying 协议)
-
-
宏定义:
-
无参宏
-
有参宏:
-
注意参数要加上括号,因为宏体中传入的参数可以是一个表达式
// 期望的是 ((3+4)*(3+4)),如果不加括号,则会变成 (3+4*3+4) # define SQUARE(a) ((a)*(a)) int a = 3, b = 4; NSLog(@"square = %d\n", SQUARE(a+b))
-
运算符 “#” 把跟在其后的参数转换成一个字符串
# define STRING(n) #n NSLog(@"%s", STRING(test)) // 输出 test
-
运算符 “##” 用于把多个参数连接到一起(很少用)
# define CONNECT(a,b,c) (a##b##c) NSLog(@"%f", CONNECT(13.6, 2, 3)) // 输出 13.623000
-
-
-
预处理指令 #include、#import、@class 对比:
- #include:在指令处展开被包含的文件,标准 C 编译器至少支持八重嵌套包含,重复引用会报错
- #import:大部分功能和 #include 一样,但是解决了重复引用的问题
- @class:引入一个类时效率更高,但是不能使用引入类中的属性和方法
-
常用的占位符:
- %@:对象
- %d %i:整数
- %u:无符号整形
- %f:浮点/双字
- %x %X:二进制整数
- %o:八进制整数
- %zu:size_t
- %p:指针
- %e:浮点/双字(科学计算)
- %g:浮点/双字
- %s:C 字符串
- %.*s:Pascal 字符串
- %c:字符
- %C:unichar
- %lld:64位长整数
- %llu:无符号64位长整数
- %Lf:64位双字