Objective-C
1. 基础语法
-
OC相对于C
- 在C的基础之上新增了一小部分面向对象的语法
- 将C的复杂的、繁琐的、可恶的的语法封装的更为简单
- OC完全兼容C语言
-
OC程序的源文件的后缀名是
.m
m
代表message
它代表OC中最重要的一个机制,消息机制 -
main
函数仍然是OC程序的入口和出口int
类型的返回值 代表程序的结束状态main
函数的参数:仍然可以接收用户在运行程序的时候传递数据给程序。参数也可以不要。
-
#import
指令- 以
#
号开头,是一个预处理指令。 - 作用:是
#include
执行的增强版,将文件的内容在预编译的时候拷贝到写指令的地方。 - 增强:同一个文件无论
#import
多少次,只会包含一次。- 如果
#include
指令要实现这个效果,就必须要配合条件来编译指令来实现。 - 而
#import
指令只需要直接包含就可以,其他什么都不用做。
- 如果
- 简要原理:
#import
指令在包含文件的时候,底层会先判断这个文件是否被包含,如果被包含就会略过,否则才会包含。
- 以
-
框架
- 是一个功能集,苹果或第三方实现将一些程序在开发程序的时候经常需要用到的功能事先写好,把这些功能封装在一个一个的类或函数之中。这些函数和类的集合就叫做框架。
Foundation
框架,这个框架中提供了一些最基础的功能,输入和输出,一些数据类型。Foundation.h
这个文件中包含了Foundation
框架中的其他的所有的头文件,所以,我们只要包含了Foundation.h
就相当于包含了Foundation
框架中所有的头文件,那么Foundation
框架中的所有的函数和类就可以直接使用。
-
NSLog
函数#import <Foundation/Foundation.h> int main() { float f1 = 12.12f; NSLog(@"jack f1=%f",f1); NSLog(@"Hello!"); return 0; }
- 作用:是
printf
函数的增强版,向控制台输出信息。 - 语法:
NSLog(@"格式控制字符串",变量列表);
- 增强
- 输出一些调试相关信息
2020-07-01 12:40:52.628 Day01-OC基础语法 [784:210576] Hello,World
执行代码的时间、程序的名称、进程编号、线程编号、输出的信息 - 会自动换行,在输出完信息之后,会自动换行
- OC中其实新增了一些数据类型,NSLog函数不仅仅可以输出C数据类型变量的值还可以输出OC新增的数据类型的变量的值。
- 输出一些调试相关信息
- 用法和
printf
函数差不多,一样可以输出变量的值,并且占位符和用法一样。 - 使用注意:
NSLog
函数的第一个参数前面必须要加一个@
符号。- 如果在字符串的末尾加了一个
\n
代表换行,那么函数的自动换行功能就会失效。
NSLog
实质上是打印类型的实例方法- (NSString *)description
,可以通过重写此方法实现自定义输出格式
- 作用:是
-
字符串
- C语言的字符串的存储方式
- 使用字符数组存储
- 使用字符指针
- OC中设计了一个更为好用的用来存储字符串的一个类型。
NSString
它是一个指针变量,专门用来存储OC字符串的地址。 - OC的字符串常量必须要使用1个前缀
@
符号。"jack"
这是一个C语言的字符串@"jack"
这是一个OC的字符串常量NSString
类型的指针变量,只能存储OC字符串的地址。NSString *str = @"jack";
- 如果要使用
NSLog
函数输出OC字符串的值,那么使用占位符%@
- C语言的字符串的存储方式
-
OC中的数据类型
- OC中支持C语言中的所有的数据类型
- 基本数据类型 int double float char
- 构造类型 数组、结构体、枚举
- 指针类型 int *p1
- 空类型 void
- typedef 自定义类型
- BOOL类型
- 可以存储YES或者NO中的任意一个数据。
- 一般情况下BOOL类型的变量用来存储条件表达式的结果,如果条件表达式成立,那么结果就是YES。
- BOOL的本质
typedef signed char BOOL;
实际上BOOL类型的变量是一个有符号的char变量#define YES ((BOOL)1)
YES实际上就是1#define NO ((BOOL)0)
NO实际上就是0
- Boolean类型
- Boolean类型的变量可以存储
true
或者false
- 一般情况下
Boolean
类型的变量用来存储条件表达式的结果,如果条件表达式成立,那么结果就是true,如果条件表达式不成立,结果就是false。 - 本质
typedef unsigned char Boolean
#define true 1
#define false 0
- Boolean类型的变量可以存储
- class类型 类
- id类型 万能指针
- nil 与NULL差不多
- SEL 方法选择器
- block 代码段
- OC中支持C语言中的所有的数据类型
2. 面向对象
-
面向过程与面向对象是解决同一个问题的不同的思路
- 如果解决一件事情的时候,每一件事情都是我们亲自去一步步实现,那么这种解决问题的思路叫做面向过程的解决思路
- 如果解决一件事情的时候,自己不去亲自做,而是找一个专门做这件事情的人来帮助我们做,这样解决问题的思路我们叫做面向对象的解决思路。
-
如何使用面向对象来设计程序呢?
- 先看看有没有现成的人专门做这件事情的框架,如果有直接使用。
- 如果没有,就自己造出一个拥有这样的功能的人,那么造出来的这个人就可以多次使用。
-
面向对象的三大特征
-
封装
- 类就是更高级别的封装,类将数据和行为封装为了一个整体。
- 好处
- 屏蔽内部的实现,外界不需要知道内部是如何实现的,只需要知道这个对象有什么用
- 方便操作
- 后期的维护十分的便利
- 属性的封装
-
setter封装
- 将属性的
@public
去掉,因为一旦写上@public
就意味着外界可以直接访问对象的这个属性,外界一旦可以直接访问这个属性,那么外界就可以任意的为这个属性赋值。 - 为类提供一个方法,这个方法专门为这个属性赋值。
- 这个方法我们叫做
setter
,这个方法一定是一个对象方法,没有返回值。这个方法的名称必须以set开头,跟上去掉下划线首字母大写的属性名。这个方法一定是有参数的,并且参数类型和属性的类型一致。参数的名称和属性去掉下划线一致。
- 将属性的
-
getter封装
- 这里写一个
getter
方法来取属性的值 - 方法名为去掉下划线的属性名
- 返回值的类型和属性的类型一致。
- 这里写一个
-
案例
#import <Foundation/Foundation.h> // 类的声明 @interface Person1 : NSObject { NSString *_name; // 为姓名赋值的时候,要求姓名的长度不能小于2,否则赋值为@"无名" int _age; // 为age赋值的时,区间为1-99 } // 声明两个setter - (void)setName:(NSString *)name; - (void)setAge:(int)age; // 声明两个getter - (NSString *) name; - (int) age; @end @implementation Person1 // 实现两个setter - (void)setName:(NSString *)name{ if([name length] < 2) { _name = @"无名"; return; } _name = name; // _name = [name length] < 2 ? @"无名" : name; } - (void)setAge:(int)age { if (age > 0 && age < 100) { _age = age; return; } _age = 1; } - (NSString *)name { return _name; } - (int)age { return _age; } @end int main() { Person1 *p1 = [Person1 new]; [p1 setName:@"zzz"]; [p1 setAge:23]; NSString *p1Name = [p1 name]; NSLog(@"创建一个Person1对象,获取name: %@",p1Name); return 0; }
-
-
继承
-
目的:儿子类想拥有父亲类中的所有的成员,但是不想自己去定义,而是想凭空拥有。
-
语法
@interface 类名 : 父类 @end
-
效果:子类一旦从父类去继承,子类就拥有父类的所有成员,无需定义,包括属性和方法
-
特点
- 单根性:单继承,一个类有且只能有一个父类
- 传递性
-
NSObject类
- 是Foundation框架中的类,在这个类中有一个类方法new,这个方法是用来创建对象的,方法的返回值是创建的这个对象的指针。
- NSObject中有一个new方法、一个isa属性。
- NSObject类是OC中所有类的祖宗,因为OC中的类全部都是直接的或间接的从它继承。
-
子类中不能存在和父类同名的属性。因为子类从父类继承,就意味着子类拥有了父类的所有的成员,包括属性和方法,如果子类再定义一个同名的属性,就冲突了
-
-
多态
- 指的是同一行为,对于不同的事物具有完全不同的表现形式。
-
-
什么是对象?
对象是现实生活中的一个具体存在,看得见,摸得着,拿过来就可以直接使用。
-
什么是类?
类是对一群具有相同特征或者行为的事物的一个统称,抽象的,不能直接使用。
-
类和对象之间的关系
类是模板,类的对象是模板创建出来的,类模板中有什么,对象中就有什么,绝不可能多,也绝不可能少。 -
如何设计一个类
-
类的作用:用来描述一群具有相同特征和行为的事物的。
-
设计类的三要素
- 类的名字:你要描述这个类事物叫什么名字
- 这类事物具有的相同的特征
- 这类事物具有的共同的行为
-
语法
-
位置,直接写在源文件中,不要写在main函数之中。
-
类的定义分为两个部分
-
类的声明
@interface 类名 : NSObject { 这类事物具有的共同的特征,将他们定义为变量 属性不允许声明的时候初始化 } 功能就是一个方法,将方法的声明写在这里 @end
-
类的实现
@implementation 类名 将方法的实现写在这里 @end
-
-
-
注意
- 类必须要有声明和实现
- 类名用你描述的事物的名称来命名就可以了
- 类名的每一个单词的首字母必须以大写开头
NSObject
照写就可以了- 用来标识这类事物的共同的特征的变量必须要定义在
@interface
的大括号之中 - 定义在大括号之中的用来标识这类事物的共同特征的变量叫做属性、成员变量
- 为类定义属性的时候,属性的名词必须要以
_
开头
-
类是无法直接使用的,如果非要使用这个类的话,就必须要先找到这个类中的一个具体存在,而这个就是对象
-
-
如何创建一个类的对象? 语法
类名 *对象名 = [类名 new]
; -
如何访问对象的属性?
- 默认情况下,对象的属性时不允许被外界直接访问的
- 如果允许对象的属性可以被外界访问,那么就在声明属性的时候加上
@public
关键字 - 访问方式
对象名->属性名 = 值;
或(*对象名).属性名;
一般用第一种
-
类中的行为使用方法来表示
- OC中的方法分为两种,对象方法和类方法,先学习对象方法
- 方法的声明写在类的声明中,实现写在类的实现中
- 无参数的方法
- 声明
- 位置:在
@interface
的大括号后面 - 语法:
- (返回值类型)方法名称;
- 位置:在
- 实现
- 位置:在
@implementation
之中实现 - 语法:
- (返回值类型) 方法名称 {}
将方法的声明拷贝到@implementation
之中,去掉分号,追加大括号,将方法实现的代码写在大括号之中。
- 位置:在
- 调用
- 方法是无法直接调用的,因为类是不能直接使用的,必须要先创建对象,那么这个对象中就有类中的属性和方法了,就可以调用对象的方法了。
- 调用
[对象名 方法名];
- 声明
- 带一个参数的方法
- 声明
- 位置:在
@interface
的大括号的外面 - 语法:
- (返回值类型) 方法名称:(参数类型)形参名称;
- 位置:在
- 实现
- 位置:在
@implementation
之中实现 - 语法:
- (返回值类型) 方法名称 {}
将方法的声明拷贝到@implementation
之中,去掉分号,追加大括号,将方法实现的代码写在大括号之中。
- 位置:在
- 调用
- 方法是无法直接调用的,因为类是不能直接使用的,必须要先创建对象,那么这个对象中就有类中的属性和方法了,就可以调用对象的方法了。
- 调用
[对象名 方法名:实参];
- 声明
- 带多个参数的方法
- 声明
- 位置:在
@interface
的大括号的外面 - 语法:
- (返回值类型) 方法名称:(参数类型)形参名称1 :(参数类型)形参名称2 :(参数类型)形参名称3
- 位置:在
- 实现
- 位置:在
@implementation
之中实现 - 语法:将方法的声明拷贝到
@implementation
之中,去掉分号,追加大括号,将方法实现的代码写在大括号之中。
- 位置:在
- 调用
- 方法是无法直接调用的,因为类是不能直接使用的,必须要先创建对象,那么这个对象中就有类中的属性和方法了,就可以调用对象的方法了。
- 调用
[对象名 方法名:实参1 :实参2 :实参3]
- 声明
- 带参数的方法声明的规范
- 如果方法只有一个参数,建议这个方法的名字叫做
xxxWith
- 如果方法有多个参数,建议这个方法名称为
xxxWith:(参数类型)参数名称 and:(参数类型)参数名称 and:(参数类型)参数名称;
- 如果方法只有一个参数,建议这个方法的名字叫做
-
函数和方法的异同
- 相同点
- 都是用来封装一段代码,表示一个相对独立的功能
- 函数或者方法只要被调用,那么封装在其中的代码就会被自动执行
- 不同点
- 语法不同
- 定义的位置不一样 OC方法的声明只能写在
@interface
的大括号外面,实现只能写在@implementation
之中。函数除了在函数内部和@interface
的大括号之中,其他的地方都可以写。 - 调用方式不同
- 相同点
-
内存中的五大区域
- 栈:存储局部变量
- 堆:程序员手动申请的字节空间
- BSS段:存储未被初始化的全局变量 静态变量
- 数据段 (常量区):存储已经被初始化的全局变量、静态变量、常量数据
- 代码段:存储代码
-
类的加载
- 在创建对象的时候,肯定是需要访问类的
- 声明一个类的指针变量也会访问类
- 在程序运行期间,当某一个类第一次被访问的时候,会将这个类存储到内存中的代码段区域,这个过程叫做类加载。一旦类被加载到代码段以后,直到程序结束的时候才会被释放。
-
对象在内存中如何存储的?
Persion *p1 = [Person new];
Person *p1
会在栈内存中申请一块空间;在栈内存中声明一个Person类型的指针变量p1.p1
是一个指针变量,那么只能存储地址[Person new];
真正在内存中创建对象的其实是这段代码。new
做的事情- 在堆内存中申请一块合适大小的空间。
- 在这个空间中根据类的模板创建对象,类模板中定义了什么属性,就把这些属性依次的声明在对象之中。对象中还有另外一个属性,叫做
isa
,是一个指针。指向对象所属的类在代码段中的地址。 - 初始化对象的属性。如果属性的类型是基本数据类型,那么就赋值为0;如果属性的类型是C语言的指针类型,那么就赋值为NULL。如果属性的类型是OC的类指针类型,那么就赋值为nil。
- 返回对象的地址。
- 注意
- 对象中只有属性,而没有方法,自己类的属性外加一个isa指针指向代码段中的类。
- 在调用方法的时候,根据指针名找到对象,对象发现要调用方法,再根据对象的isa指针找到类,然后再调用类里的方法。
-
nil和NULL
- NULL 只能作为指针变量的值。可以作为指针变量的值,如果一个指针变量的值是NULL,代表这个指针不指向内存中的任何一块空间。NULL其实等价于0
- nil 只能作为指针变量的值。代表这个指针变量不指向内存中的任何空间。
- NULL和nil是一样的。
- 使用建议:虽然使用NULL的地方可以使nil,使用nil的地方可以使用NULL,但是不建议随便使用。C指针使用NULL,OC的类指针使用nil
-
分组导航标记
#pragma mark 分组名
就会在导航条对应的位置显示1个标题#pragma mark -
就会在导航条对应的位置显示一条水平分割线#pragma mark - 分组名
水平线和标题都有
-
对象和方法
- 对象作为方法的参数
- 类的本质是我们自定义的一个数据类型,因为对象在内存中的大小是由我们自己决定的。既然类是一个数据类型,那么类就可以作为方法的参数。
- (void)test:(Dog *) dog
- 在调用的时候,实参也必须是一个符号要求的对象。
- 当对象作为方法的参数传递的时候,传递的是地址,所以在方法内部通过形参去修改对象的时候,会影响到实参变量指向的对象的值。
- 对象作为方法的返回值
- 什么时候方法的返回值是一个对象呢?当方法执行完毕后,如果有一个对象方法的内容不知道如何处理,并且这个对象是调用者需要的。
- (Dog *) test;
- 对象作为方法的参数
-
类方法
- 类方法的调用不依赖于对象,如果要调用类方法,不需要去创建对象,而是直接通过类名来调用
- 声明
- 类方法声明使用
+
号 + (返回值类型)方法名
- 类方法声明使用
- 调用
- 类方法的调用不需要通过对象名来调用,直接使用类名来调用
[类名 类方法名];
- 类方法的规范
-
如果我们写一个类,那么就要求为这个类提供一个和类名同名的类方法,这个方法创建一个最纯洁的对象返回。
// 实现一个用于创建对象的类方法 + (DemoClass *) demoClass { return [DemoClass new]; } + (DemoClass *) demoClassWidthName:(NSString *)name andAge:(int)age andSex:(char)sex { DemoClass *p1 = [DemoClass new]; p1->_name = name; p1->_age = age; p1->_sex = sex; return p1; }
-
如果希望创建的对象的值由调用者指定,那么就为这个类方法带参数
-
-
NSString
-
概述
- 是一个数据类型,用来保存OC字符串的。
- 作用:存储OC字符串的
- OC中的字符串本质上是用NSString对象来存储的
-
常用的类方法
-
+ (instancetype)stringWithUTF8String:(const char *)nullTerminatedCSrting;
instancetype作为返回值,代表返回的是当前这个类的对象
作用:将C语言的字符串转换为OC字符串对象char *str0 = "rose"; NSString *str1 = [NSString stringWithUTF8String:str0];
-
+ (instancetype)stringWithFormat:(NSSrting *)format,...
作用:拼接一个字符串对象,使用变量或者其他数据拼接成OC字符串。int age = 19; NSString *name = @"小明"; NSString *str = [NSString stringWithFormat:@"大家好,我叫%@,我今年%d岁",name,age];
-
-
常用的对象方法
-
length
方法 返回类型为NSUInteger 获取字符串的字符个数 -
characterAtIndex
方法 返回类型是unichar 获取指定下标的字符。如果要输出unichar变量的值,使用%C
-
isEqualToString
方法,返回类型是BOOL,判断两个字符串内容是否相等。 -
compare
方法,比较字符串的大小。使用int类型接收结果,因为返回值时1个枚举(返回的如果是-1说明小于,0表示相等,1表示大于)-
返回值
- 小于,返回
-1
- 相等,返回
0
- 大于,返回
1
- 小于,返回
-
参数
NSCaseInsensitiveSearch = 1
不区分大小写比较NSLiteralSearch = 2
区分大小写比较NSBackwardsSearch = 4
从字符串末尾开始搜索NSAnchoredSearch = 8
搜索限制范围的字符串NSNumbericSearch = 64
按照字符串里的数字为依据,算出顺序。例如 Foo2.txt < Foo7.txt < Foo25.txtNSDiacriticInsensitiveSearch = 128
忽略 “-” 符号的比较NSWidthInsensitiveSearch = 256
忽略字符串的长度,比较出结果NSForcedOrderingSearch = 512
忽略不区分大小写比较的选项,并强制返回NSOrderedAscending
或者NSOrderedDescending
NSRegularExpressionSearch = 1024
只能应用于rangeOfString:...,`` stringByReplacingOccurrencesOfString:...
和replaceOccurrencesOfString:...
方法。使用通用兼容的比较方法,如果设置此项,可以去掉NSCaseInsensitiveSearch
和NSAnchoredSearc
-
举例
// main.m #import <Foundation/Foundation.h> int main() { NSString *str1 = @"a"; NSString *str2 = @"b"; NSLog(@"result = %d",[str1 compare:str2]); // -1 NSString *str3 = @"A"; NSString *str4 = @"a"; NSLog(@"result = %d",[str3 compare:str4 options:1]); // 0 说明相等 NSString *str3 = @"A"; NSString *str4 = @"a"; NSLog(@"result = %d",[str3 compare:str4 options:1]); // 0 说明相等 }
-
-
-
-
匿名对象
- 没有名字的对象,如果我们创建一个对象,没有用一个指针存储这个对象的地址,也没有任何指针指向这个对象,那么这个对象就叫做匿名对象。
- 如何使用一个匿名对象?在new一个对象的时候,会返回这个对象的指针,我们可以直接使用
- 注意点
- 匿名对象只能使用一次
- 每次创建匿名对象都是不同的对象。
- 有什么用?
- 如果某个对象的成员只会被我们使用一次,用完之后这个对象再也不需要了,那么就可以使用匿名对象。
- 如果方法的参数是一个对象,而调用者为这个参数赋值的对象就是专门来给这个方法传递的,并且这个对象调用者不会使用,那么这个时候就可以直接为方法传递一个匿名对象
-
对象与对象之间的关系
- 组合关系 一个类是由其他的几个类联合起来组合而成,那么他们之间的关系就叫做组合关系。
- 依赖关系 一个对象的方法的参数是另外一个对象,那么我们就说他们直接的关系是依赖关系
- 耦合度:当修改一个对象的时候,对另外一个对象的影响程度。
- 低耦合:当修改一个对象的时候,对另外一个对象的影响较小甚至没有影响。
- 低内聚:一个对象仅仅做自己的事情。
- 关联关系 一个类作为另一个类的属性,但是他们不是组合关系,而是一个拥有的关系。
- 继承关系
3. 多文件开发
-
推荐开发方式
- 把一个类写在一个模块中,而一个模块至少包含两个文件
.h
头文件 写类的声明 在这个头文件中要引入Foundation框架的头文件.m
实现文件 写类的实现 先 引入模块的头文件,这样才会有类的声明,再写类的实现- 最后,如果要使用这个类,在
main.m
中引入这个模块的有文件就可以了 - 注意:只能引入
.h
文件,不能引入.m
文件。
-
案例
-
创建一个
Person.h
文件#import <Foundation/Foundation.h> @interface Person: NSObject { @public NSString *_name; int _age; } - (void)sayHi; @end
-
创建一个
Person.m
文件#import "Person.h" @implementation Person - (void) sayHi { NSLog(@"大家好,我是人..."); } @end
-
main.m
#import <Foundation/Foundation.h> #import "Person.h" int main(int argc, const char * argv[]) { Person *p1 = [Person new]; [p1 sayHi]; }
-
-
添加类模块的更简洁的方式
NewFile Cocoa Class
自动生成模块文件
4. 异常
-
什么是错误?
- 一般情况下,错误指的是我们写的源代码不符合语法规范,然后编译报错
- 后果:程序无法编译
- 解决:将不符合语法规范的代码改为符合语法规范的代码
-
什么是BUG?
- 程序可以编译、链接、执行,程序执行的结果并不是我们预想的那样。
- 解决:通过调试寻找发生Bug的原因
-
什么是异常?
- 程序可以编译、连接、执行
- 当程序在执行的时候,处于某种特定条件下,程序的执行就会终止。
-
如何处理异常?
-
目的:为了让程序在执行的时候如果发生了异常而不崩溃,而是继续往下执行
-
语法
@try { // 将有可能发生异常的代码放在@try中 // 当@try中的代码在执行的时候,如果发生了异常,不会崩溃,而是会立即跳转到@catch中去执行里面的代码 // 当@catch的代码执行完毕后,结束@try...@catch往下执行 // 如果@try中的代码在执行的时候,没有发生异常,就会略过@catch往下执行 } @catch(NSException *ex) { }
-
@catch中的代码只有在@try的代码发生异常的时候才会执行,所以@catch中我们一般情况下写处理异常的代码
-
@catch中的参数
NSException *ex
通过%@
打印出ex指向的对象的值,可以拿到异常的原因 -
最后还可以跟一个
@finally
。无论try中是否发生异常都会执行 -
注意,C语言中的异常无法处理
-
5. 关键字
- static
- C语言中的static
- 修饰局部变量
- 修饰全局变量
- 修饰函数
- OC语言中的static
- static既不能修饰属性,也不能修饰方法
- static可以修饰方法中的局部变量
如果方法中的局部变量被static修饰,那么这个变量就会变成静态变量,存储在常量区中。当方法执行完毕之后,不会回收,下次再执行这个方法的时候直接使用,而不会再声明了。
- C语言中的static
- self
- 在方法内部可以定义一个和属性名相同的局部变量,这个时候,如果在方法中访问这个同名的变量,访问的是局部变量。
- self可以用在对象方法和类方法中,self就是一个指针,在对象方法中,self指向当前对象,在类方法中self指向当前类。
- self用在对象方法中
- self在对象方法中,指向当前对象(谁调用方法谁就是当前对象)。
- 可以使用self显示的访问当前对象的属性。self->属性,代表访问的是当前对象的这个属性。
- 可以使用self来调用当前对象的其他的对象方法。
- 对象方法中必须使用self的场景
- 如果在方法中存在和属性同名的局部变量
- 在对象方法中,如果要调用当前对象的其他的对象方法
- self用在类方法中
- 类加载,当类第一次被访问的时候,会将类的代码存储在代码区中。代码区中用来存储类的空间也有一个地址。
- 在类方法中,self也是一个指针,执行当前这个类在代码段中的地址。self在类方法中,就相当于这个类。
- super关键字
- 可以用在类方法和对象方法中
- 在对象方法中可以使用super关键字调用当前对象从父类继承过来的对象方法。
- 在类方法中,super关键字可以调用当前类从父类继承过来的类方法。
- 类方法也能被子类继承,父类中的类方法可以使用父类名来调用,也可以使用子类名调用
- 在子类的类方法中,可以使用super关键字调用父类的类方法
- super只能来调用父类的对象方法或者类方法,不能来访问属性
- 访问修饰符:用来修饰属性,可以限定对象的属性在那一段范围之中访问
@private
私有,被@private修饰的属性只能在本类的方法实现中访问。@protected
受保护的,被@protected
修饰的属性只能在本类和本类的子类中的方法实现中访问。@package
被@package
修饰的属性,可以在当前框架中访问。@public
公共的,被@public
修饰的属性,可以在任意的地方访问。- 默认是
@protected
- 子类可以继承父类的私有属性,只不过,在子类中无法去直接访问从父类继承过来的私有属性。但是如果父类中有一个方法再为属性赋值或者取值,那么子类可以调用这个方法间接的访问父类的私有属性。
- 访问修饰符的作用域:从写访问修饰符的地方开始往下,直到遇到另外一个访问修饰符或者结束大括号为止,中间的所有的属性都应用这个访问修饰符。
- 访问修饰符只能用来修饰属性,不能用来修饰方法。
%p
打印的是指针变量的值,也就是地址。%@
打印的是指针指向的对象 输出格式:<对象所属的类名:对象的地址>
6. 方法
-
私有方法:方法不写声明,只写实现,那么这个方法就是一个私有方法。只能在本类的其他方法中调用,不能在外界调用。
-
里氏替换原则—LSP
-
子类可以替换父类的位置,并且程序的功能不受影响。
Person *p1 = [Student new];
-
为什么?
- 父类指针迫切的需求要一个父类对象,而我们给了一个子类对象,这里完全没有问题,因为子类就是一个父类。
- 因为父类中拥有的成员,子类都有,所以不会影响程序的功能。
-
里氏替换原则的表现形式
当一个父类指针指向一个子类对象的时候,这里就有了里氏替换原则。
-
LSP的作用
- 一个指针中不仅可以存储本类对象的地址,还可以存储子类对象的地址。
- 如果一个指针的类型是NSObject类型的,那么这个指针可以存储任意的OC对象的地址。
- 如果一个数组的元素的类型是一个OC指针类型,那么这个数组中不仅可以存储本类对象,还可以存储子类对象。
- 如果一个数组的元素是NSObject指针类型,那么意味着任意的OC对象都可以存储到这个数组之中。
- 如果一个方法的参数是一个对象,那么我们在为这个参数传递的时候,对方法中的代码不会有丝毫的影响。
- 当一个父类指针指向一个子类对象的时候,通过这个父类指针只能去调用子类对象中的父类成员。子类独有的成员无法访问。
-
-
方法重写
- 子类从父类继承,子类就继承了父类的方法,子类继承了父类的方法,就意味着子类拥有了这个功能。
- 有的时候虽然子类拥有父类的这个行为,但是具体的实现和父类的实现不一样,那么子类就自己按照自己的方式在实现中重写这个方法。
- 当一个父类指针指向一个子类对象的时候,通过这个父类指针调用的方法,如果在子类对象中重写了,调用的就是子类重写的方法。
7. 特有语法
-
SEL
-
全称selector 选择器
-
SEL是一个数据类型,所以要在内存中申请空间存储数据
-
SEL其实是一个类,SEL对象是用来存储一个方法的。
-
方法的本质就是一个SEL对象。
-
如何将方法存储在类对象中的?
- 先创建一个SEL对象
- 将方法的信息存储在这个SEL对象之中。
- 再将这个SEL对象作为类对象的属性。
-
如何拿到存储方法的SEL对象?
- 因为SEL是一个typedef类型的,在自定义的时候已经加了
*
了。所以我们在声明SEL指针的时候,不需要加*
- 取到存储方法的SEL对象
SEL sl = @selector(方法名);
- 因为SEL是一个typedef类型的,在自定义的时候已经加了
-
调用方法的本质
[p1 sayHi];
- 先拿到存储sayHi方法的SEL对象,也就是拿到存储sayHi方法的SEL数据,SEL消息。
- 将这个SEL消息发送给p1对象。
- 这个时候,p1对象接收到这个SEL消息以后,就知道要调用方法
- 根据对象的
isa
指针找到存储类的类对象 - 找到这个类对象以后,在这个类对象中搜寻是否有和传入的SEL数据相匹配的。如果有就执行,如果没有再找父类。
-
OC最重要的一个机制就是消息机制。调用方法的本质其实就是为对象发送SEL消息。
[p1 sayHi];
为p1对象发送一条sayHi消息。 -
手动的为对象发送SEL消息
-
先得到方法的SEL数据
-
将这个SEL消息发送给p1对象
-
Person *p1 [Person new]; //先得到方法的SEL数据 SEL s1 = @selector(sayHi); // 将这个SEL消息发送给p1对象 [p1 performSelector:s1]; // 如果方法有参数,就调用 [p1 performSelector:s1 withObject:@"红烧肉"]
-
-
-
点语法
- 使用点语法来访问对象的属性
对象名.去掉下划线的属性名;
- 原理
- 点语法在编译器编译的时候,其实会将点语法转换为调用setter、getter的代码
- 当赋值或取值的时候,编译器会将点语法转换为调用setter或者getter方法的代码。
- 如果属性没有封装getter或setter ,是无法使用点语法的。
- 使用点语法来访问对象的属性
-
@property
- 作用:自动生成getter、setter方法的声明,写在
@interface
类的声明中 - 语法
@property 数据类型 名称; //名称要去掉_
- 原理
- 编译器在编译的时候,会根据
@property
生成getter
和setter
方法的实现 - 生成
-(void) set首字母大写的名称:(数据类型)名称;
- 生成
-(数据类型)名称;
- 编译器在编译的时候,会根据
- 注意
@property
的类型和属性的类型一致。@property
的名称和属性的名称一致(去掉下划线)@property
的名称决定了生成getter
个setter
方法的名称@property
的数据类型决定了生成setter
方法的参数类型和getter
方法的返回值类型。@property
只是生成了getter
和setter
方法的声明,实现还是要自己来,属性还是要自己定义。
- 作用:自动生成getter、setter方法的声明,写在
-
@synthesize
-
作用:自动生成
getter
和setter
方法的实现。应该写在类的实现中 -
语法 :
@synthesize @property的名称;
@interface Person:NSObject { int _age; } @property int age; @end // ----------------------- @implmentation Person @synthesize age; @end
-
@synthesize
做的事情@implmentation Person @synthesize age; @end @implementation Person { int age; } - (void)setAge:(int) age { self->age = age; } - (int) age { return age; } @end
- 生成一个真私有的属性,属性的类型和
@synthesize
对应的@property
类型一致。属性的名字和@synthesize
对应的@property
名字一致。 - 自动生成setter方法的实现。将参数直接赋值给自动生成的那个私有属性。
- 自定生成getter方法的实现。将生成的私有属性的值返回。
- 生成一个真私有的属性,属性的类型和
-
希望
@synthesize
不要去自动生成私有属性,直接操作我们写好的属性- 语法
@synthesize @property的名称 = 已经存在的属性名;
- 语法
-
批量声明
- 如果多个
@property
的类型一致,可以批量声明@property float height,weight;
@synthesize
也可以批量声明@synthesize name = _name,age = _age;
- 如果多个
-
-
@property
增强- 只需要写一个
@property
编译器就会自动- 生成私有属性
- 生成getter setter的声明
- 生成getter setter的实现
@property NSString *name;
- 自动的生成一个私有属性,属性的类型和
@property
类型一致,属性的名称和@property
的名称一致。属性的名称自动的加一个下划线。 - 自动的生成这个属性的getter setter方法的声明
- 自动的生成这个属性的getter setter方法的实现
- setter的实现:直接将参数的值赋值给自动生成的私有属性
- getter的实现:直接返回生成的私有属性的值
- 自动的生成一个私有属性,属性的类型和
- 使用注意
@property
的类型一定要和属性的类型一致。名称要和属性的名称一致,只是去掉下划线。- 也可以批量声明相同类型的
@property
@property
生成的方法实现没有做任何逻辑验证- 如果我们可以重写
setter
来自定义验证逻辑,如果重写了setter
,还会自动生成getter
。如果同时重写了setter
和getter
,就不会自动生成私有属性了。
- 继承 父类的
@property
一样可以被子类继承,@property
生成的属性是私有的,在子类的内部无法直接访问生成的私有属性。但是可以用过setter
和getter
来访问。
- 只需要写一个
-
静态类型与动态类型
- 静态类型:指的是一个指针指向的对象是一个本类对象
- 动态类型:指的是一个指针指向的对象不是本类对象
- 编译检查:编译器在编译的时候,能不能通过一个指针去调用指针指向的对象的方法。判断原则:看指针所属的类型之中是否有这个方法,如果有就认为可以调用,编译通过。如果这个类中没有,那么编译报错。(看的是指针类型)
- 运行检查:编译检查只是骗过了编译器,但是这个方法究竟能不能执行,在运行时会去检查对象中是否真的有这个方法,如果有就执行,如果没有就报错。
-
NSObject与id指针
- NSObject是OC中所有类的基类,根据LSP,NSObject指针就可以指向任意的OC对象。所以NSObject指针是一个万能指针。但是要调用指向的子类对象的独有方法,就必须进行类型转换。
- id指针:是一个万能指针,可以指向任意的OC对象。
- id是一个typedef自定义类型,在定义的时候已经加了
*
,所以在声明id指针的时候不需要再加*
了。 - id指针是一个万能指针,任意的OC对象都可以指。
- id是一个typedef自定义类型,在定义的时候已经加了
- NSObject与id指针的异同
- 相同:万能指针,都可以执行任意的OC对象。
- 不同:通过NSObject指针去调用对象的方法的时候,编译器会做编译检查,通过id类型的指针去调用对象的方法的时候,编译器直接通过,无论你调用什么方法。
- 注意点:id指针只能调用方法,不能访问属性。
- 如果方法的返回值是
instancetype
,代表方法的返回值是当前这个类的对象。 instancetype
智能作为方法的返回值,不能在别的地方使用。
-
动态类型监测
- 判断对象中是否有这个方法可以执行
-(BOOL)respondsToSelector:(SEL)aSelector
- 判断类中是否有指定的类方法
+(BOOL)instancesRespondToSelector:(SEL)aSelector
- 判断指定的对象是否为指定类的对象或子类对象
-(BOOL)isKindOfClass:(Class)aClass
- 判断对象是否为指定类的对象,不包含子类
-(BOOL)isMemberOfClass:(Class)aClass
- 判断类是否为另一个类的子类
+(BOOL)isSubclassOfClass:(Class)aClass
- 判断对象中是否有这个方法可以执行
-
构造方法
-
new方法的作用
- 创建对象
- 初始化对象
- 把对象的地址返回
-
new方法的内部,其实是先调用
alloc
方法创建对象,再调用init
方法初始化,这时对象才可以使用。alloc
方法是一个类方法,作用:哪一个类调用这个方法,就创建哪个类的对象,并把对象返回。[Person alloc]
init
方法是一个对象方法,作用:初始化对象。
-
init方法
- 作用:初始化对象,为对象的属性赋初始值,这个init方法我们叫做构造方法。
- 所以,我们创建一个对象,如果没有为这个对象的属性赋值,这个对象的属性是有默认值的。
-
重写
init
方法规范- 只需要实现,不需要声明。
- 必须要先调用父类的
init
方法,然后将方法的返回值赋值给self
。 - 调用
init
方法初始化对象有可能会失败,如果初始化失败,返回的就是nil
。 - 判断父类是否初始化成功,判断
self
的值是否为nil
,如果不为nil
说明初始化成功。 - 如果上一步成功,就初始化当前对象的属性。
- 最后,返回self的值。
#import "Person4.h" @implementation Person4 - (instancetype)init { if (self = [super init]) { self.name = @"jack"; } return self; } @end
-
为什么要调用父类的
init
方法?因为父类的init
方法会初始化父类的属性,所以必须要保证当前对象中父类属性也同时被初始化。 -
为什么要赋值给
self
?因为调用父类的init
方法,会返回初始化成功的对象。实际上返回的就是当前对象,但是我们要判断下是否初始化成功,所以要赋值给self
。(self
只能在构造方法中赋值) -
什么时候需要重写
init
方法?如果希望创建出来的对象的属性的默认值不是nil
、NULL
、0
,而是我们指定的值,就需要重写init
方法。 -
自定义构造方法规范
- 自定义构造方法的返回值必须是
instancetype
(非必需)。 - 自定义构造方法的名称必须以
initWith
开头 (init
必需,With
非必需)。 - 方法的实现和
init
要求一样。 - 先声明后,再实现。
// 声明 @interface Person4 : NSObject { NSString *_name; int age; } @property NSString *name; @property int age; - (instancetype)initWithName:(NSString *)name andAge:(int)age; @end // 实现 @implementation Person4 - (instancetype)init { if (self = [super init]) { self.name = @"jack"; } return self; } - (instancetype)initWithName:(NSString *)name andAge:(int)age { if (self = [super init]) { self.name = name; self.age = age; } return self; } // 使用 #import <Foundation/Foundation.h> int main() { /* 自定义构造方法 */ Person4 *p41 = [[Person4 alloc] initWithName:@"zzz" andAge:2]; return 0; }
- 自定义构造方法的返回值必须是
-
8. 内存管理
-
概述
- 内存的作用:存储数据。
- 如何将数据存储到内存之中。(声明一个变量,然后将数据存储进去)
- 当数据不再被使用的时候,占用的内存空间如何被释放。
-
内存中的五大区域的内存管理
- 栈:局部变量。当局部变量的作用域被执行完毕后,这个局部变量就会被系统立即回收。
- 堆:OC对象,使用C函数申请的空间。分配在堆区中的OC对象,需要回收,但是存储在堆中的OC对象,系统不会自动回收,直到程序结束的时候才会被回收。
- BSS段:未初始化的全局变量、静态变量。一旦初始化就回收,并转存到数据段之中。
- 数据段:已经初始化的全局变量、静态变量。直到程序结束的时候才会被回收。
- 代码段:代码。程序结束的时候,系统会自动回收存储在代码段中的数据。
- 栈、BSS段、数据段、代码段存储在它们中的数据的回收,是由西永自动完成的,不需要我们干涉。
-
引用计数器
- 每一个对象都有一个属性,叫做
retainCount
,叫做引用计数器,类型是unsigned long
,占用8个字节。 - 引用计数器的作用:用来记录当前这个对象有多少个人在使用它。
- 默认情况下,创建一个对象出来,这个对象的引用计数器的默认值是1。
- 当多个人使用这个对象的时候,应该先让这个对象的引用计数器的值+1,代表这个对象多一个人使用。
- 当这个对象少一个人使用的时候,应该先让这个对象的引用计数器的值-1,代表这个对象少一个人使用。
- 当这个对象的引用计数器变为0的时候,代表这个对象无人使用,这个时候系统就会自动回收这个对象。
- 当对象被回收的时候,会自动调用对象的
dealloc
方法。 - 如何操作引用计数器?
- 为对象发送一条
retain
消息,对象的引用计数器就会+1
。 - 为对象发送一条
release
消息,对象的引用计数器就会-1
。 - 为对象发送一条
retainCount
消息,就可以去到对象的引用计数器的值。
- 为对象发送一条
- 每一个对象都有一个属性,叫做
-
内存管理的分类
MRC(Manual Reference Counting)
手动引用计数,手动内存管理。 要求程序员手动的发送retain
和release
消息。ARC(Automatic Reference Counting)
自动引用计数,自动内存管理。系统自动在合适的地方发送retain
和release
消息。
-
ARC
- 判断准则:只要没有强指针指向对象,就会释放对象。
- 特点
- 不允许调用
release
、retain
、retainCount
- 允许重写
dealloc
,但是不允许调用[super dealloc]
property
的参数strong
:成员变量是强指针(适用于OC对象)weak
:成员变量是弱指针
- 不允许调用
- 指针分为强指针和弱指针
- 形象解释
把对象想象成一条狗,它要跑 (be deallocated)。强指针就像一条拴在狗脖子上的狗链;只要攥在手里,狗就跑不了;如果5个人攥着5条狗链都拴着狗 (5个强指针指向对象),除非5条狗链都撒开,狗就跑不了。弱指针就像是孩子指着狗喊“看!狗!”;只要狗链还拴着狗,孩子就能指着狗喊。当所有狗链都撒开,不管有多少孩子指着狗喊,狗都跑了。当最后一个强指针不再指向对象,对象就会被释放,所有弱指针清零。我们什么时候使用弱指针呢?只有当你想避免保留循环 (retain cycles,) 时,我们才使用它。
__weak
和__strong
会出现在声明中,默认情况下,一个指针都会使用__strong
属性,表明这是一个强引用。这意味着,只要引用存在,对象就不会被销毁。当所有(强)引用都去除时,对象才会被收集和释放。- 但是一些集合类不应该增加其元素的引用,因为这会引起对象无法释放。在这种情况下,我们需要使用弱引用。当被引用的对象消失时,弱引用会自动设置为
nil
。 - 当一个对象不再有
strong
类型的指针指向它的时候,它就会被释放,即使这个对象还有__week
类型的指针指向它。当对象被释放后,如果还有__weak
指针指向对象,则会清除掉所有剩余的__weak
指针。 - 在OC中
strong
就相当于retain
属性,而weak
相当于assign
。只有一种情况你需要使用weak
(默认是strong
),就是为了避免retain cycles
(就是父类中含有子类{父类retain
了子类},子类中又调用了父类{子类又retain
了父类},这样都无法release
)
- 形象解释
-
MRC程序
-
IOS5开始,Xcode4.2开始就支持ARC。XCode7默认支持ARC开发。
-
这里需要关闭ARC,开启MRC。因为ARC开启的时候,无法调用
dealloc
。 -
重写
dealloc
方法的规范:必须要调用父类的dealloc
方法,并且要放到最后一句代码。 -
测试引用计数器
- 新创建一个对象,这个对象的引用计数器的值默认为1。
- 当对象的引用计数器变为0的时候,对象就会被系统立即回收,并自动调用
dealloc
方法。 - 为对象发送
retain
消息,对象的引用计时器就会+1
。
#import <Foundation/Foundation.h> // 对象的声明 @interface Person5 : NSObject @property NSString *name; @property int age; - (void) sayHi; @end // 对象的实现 @implementation Person5 - (void)sayHi { NSLog(@"你好"); } - (void)dealloc { // 只要这个方法被执行,就代表当前这个对象被回收了 NSLog(@"%@被回收了",_name); [super dealloc]; } @end int main() { /* MRC */ Person5 *p51 = [[Person5 alloc] init]; p51.name = @"小明"; // 获取引用计数器的值 NSUInteger count = [p51 retainCount]; NSLog(@"count=%lu", count); // 1 [p51 release]; return 0; }
-
内存的管理原则
- 有对象的创建。就要匹配一个
release
。 retain
的次数要和release
的次数匹配。- 谁用谁
retaim
,谁不用谁release
。 - 只有在多一个人用的时候,才
retain
,少一个人使用的时候才release
。
- 有对象的创建。就要匹配一个
-
野指针
- C语言中的野指针:定义一个指针变量,没有初始化,这个指针变量的值是一个垃圾直,指向一块随机的空间,这个指针叫做野指针。
- OC中的野指针:指针指向的对象已经被回收了,这样的指针就叫做野指针。
- 内存回收的本质:申请一个变量,实际上就是向系统申请指定字节数的空间,这些空间系统就不会再分配给别人使用了。当变量被回收的时候,代表变量占用的字节空间从此以后系统可以分配给别人使用了。但是字节空间中存储的数据还在。
-
僵尸对象
一个已经被释放的对象,但是这个对象所占的空间还没有分配给别人,这样的对象叫做僵尸对象。我们通过野指针去访问僵尸对象的时候,有可能没有问题,也有可能有问题。当僵尸对象占用的空间还没有分配给别人的时候,这事可以的。当僵尸对象占用的空间分配给别人使用的时候,就不可以了。僵尸对象的实时检查机制,可以将这个机制打开,打开以后,只要访问的是僵尸对象,无论空间是否分配,就会报错,但是打开后,每访问一个对象,都会先检查这个对象是否为一个僵尸对象,比较消耗性能。当一个指针成为野指针后,将其设置为
nil
。 -
内存泄漏
- 指的是一个对象没有被及时的回收,在该回收的时候而没有被回收。一直驻留在内存中,直到程序结束的时候才回收。
- 单个对象的内存泄漏的情况
- 有对象的创建,而没有对应的
release
。 retain
的次数和release
的次数不匹配。- 在不适当的时候,为指针赋值为
nil
。 - 在方法中为传入的对象进行不适当的
retain
。
- 有对象的创建,而没有对应的
-
setter方法内存管理
在MRC的开发模式下,一个类的属性如果是一个OC对象类型的,那么这个属性的
setter
方法就赢按照下面的格式写。- (void)setCar: (Car *)car { if (_car != car) { [_car release]; _car = [car retain]; } } - (void)dealloc { [_car release]; [super delloc]; }
如果属性的类型不是OC对象类型的,不需要像上面那样写。
-
-
@property
-
作用
- 自动生成私有属性
- 自动生成这个属性的
getter
setter
方法的声明。 - 自动生成这个属性的
getter
、setter
方法的实现。
-
参数
@property(参数1,参数2,参数3...)数据类型 名称;
- 与多线程相关的两个参数
atomic
:默认值,这个时候生成的setter
方法的代码就会被加上一把线程安全锁。特点:安全、效率低nonatomic
:这个时候生成的setter
方法的代码就不会加线程安全锁。特点:不安全、效率高- 建议:要效率
- 与生成的
setter
方法的实现相关的参数assign
:默认值,生成的setter方法的实现就是直接赋值。retain
:生成的setter
方法的实现就是标准的MRC内存管理代码。也就是先判断新旧是否为同一个对象,如果不是release
旧的,retain
新的。改为strong
- 当属性的类型是OC对象类型的时候,那么就使用
retain
。当属性的类型是非OC对象类型的时候,用assign
。但是生成标准的setter
方法为标准的MRC
内存管理代码,不会自动的在dealloc
中生成release
代码。所以还需要手动的在dealloc
中release
。
- 与生成只读、读写相关的参数
readwrite
:默认值,同时生成getter
和setter
readonly
:只会生成getter
,不会生成setter
- 与生成的
getter
、setter
方法的名字相关的参数getter
:getter = getter
方法名字 用来指定@property
生成的getter
方法的名字。setter
:setter = setter
方法名字 用来指定@property
生成的setter方法的名字,注意,setter方法是带参数的,所以要加一个冒号。- 如果使用
getter
和setter
修改了生成的方法的名字,在使用点语法的时候,编译器会转换为调用修改后的名字的代码。
-
-
Ar
9. 分类
- 概述
- 如果将一个类中的所有方法都写在一起,程序显得很臃肿,后期难以维护和管理。
- 默认情况下,一个类独占一个模块。这个将所有的成员都写在一个模块中,就很难管理。
- 所以,让一个类占多个模块,将功能相似的方法定义在同一个模块中。这样的好处是,方便管理。
- 分类:将一个类分为多个模块。
- 实现
- 在项目下新建一个文件,选择
Objective-C File
- 文件类型选择
Category
Class
选择要分的类- 添加成功后,会生成一个文件名为
本类名+分类名
的.m
和.h
文件
- 在项目下新建一个文件,选择
- 说明
- 添加的分类也分为声明和实现
@interface 本类名(分类名) @end
@implementation 本类名(分类名) @end
- 使用:如果要访问分类中定义的成员,就要把分类的头文件引进来。
- 作用
- 减少类的臃肿
- 为一个已经存在的类添加方法(系统库中的也可以)
- 注意点
- 分类只能增加方法,不能增加属性。(可以声明属性,用
Runtime
动态绑定) - 在分类中可以写
@property
,但是不会自动生成私有属性,也不会自动生成getter
和setter
的实现,只会生成getter
和setter
的声明。 - 在分类的方法实现中,不可以直接访问本类的私有属性。但是可以调用本类的
getter
和setter
来访问属性。 - 当分类中有和本类中同名的方法的时候,优先调用分类的方法,哪怕没有引入分类的头文件。如果多个分类中有相同的方法,优先调用最后编译的分类。
- 分类只能增加方法,不能增加属性。(可以声明属性,用
- 非正式协议(为系统自带的类写分类,这个就叫做非正式协议)
10. 延展
-
概述
- 是一个特殊的分类。所以延展也是类的一部分。
- 特殊之处:
- 延展这个特殊的分类没有名字。
- 只有声明,没有实现。和本类共享一个实现。
- 只会生成一个
.h
文件,文件名称为本类名_延展文件名
。
-
语法
@interface 本类名 () @end // 没有实现
-
延展和分类的区别
- 分类有名字,延展没有名字,是一个匿名的分类。
- 每一个分类都有单独的声明和实现,而延展只有声明,没有单独的实现,和本类共享一个实现。
- 分类中只能新增方法,而延展中任意的成员都可以写。
- 分类中写
@property
,只会生成getter
和setter
的声明,而延展中写@property
会自动生成私有属性,也会生成getter
和setter
的声明和实现。
-
延展的使用场景
- 延展一般都不会专门创建一个延展文件,都是将延展直接写在本类的实现文件中。这个时候,写在延展中的成员,就相当于这个类的私有成员。只能在本类的实现中访问。外部不能访问。这样,就可以为类写一个私有的
@property
,生成的setter
和getter
只能在类的内部访问,不能在外部访问。 - 所以,如果想要为类写一个真私有属性,虽然我们可以定义在
@implementation
中,但是不要这样写,不规范。写一个延展,将这个私有属性定义在延展中。 - 如果要为类写一个私有方法,建议将声明写在延展中,实现写在本类的实现中。
- 延展天生就是来私有化类的成员的。
- 延展一般都不会专门创建一个延展文件,都是将延展直接写在本类的实现文件中。这个时候,写在延展中的成员,就相当于这个类的私有成员。只能在本类的实现中访问。外部不能访问。这样,就可以为类写一个私有的
-
举例
// // Person.m #import "Person.h" /* 延展 */ @interface Person () { NSString *_name; } @property(nonatomic,assign)int age; -(void)study; @end @implementation Person - (void) study { NSLog(@"学习"); } @end
11. Block
-
block是一个数据类型,专门存储一段代码,这段代码可以有参数,可以有返回值。
-
block变量的声明
-
block变量中并不是任意的一段代码都可以存进入的,而是有限定的。
-
在声明block变量的时候,必须要指定这个block变量存储的代码段是否有参数,是否有返回值。
-
指定后,只能存储指定的这种代码段。
-
语法
返回值类型 (^block变量的名称)(参数列表); void (^myBlock1)(); // 声明了一个block类型的变量叫做myBlock1,这个变量只能存储没有返回值,没有参数的代码段 int (^myBlock2)(int num1, int num2);
-
-
初始化
block
变量-
原理:写一个符合
block
要求的代码段,存储到block
中 -
代码段格式
^返回值类型(参数列表){ 代码段; }; myBlock = ^void() { NSLog("hh"); }; myBlock2 = ^int(int num1, int num2); int (^myBlock3)(int num1, int num2) = ^int(int num1, int num2) { int num3 = num1 + num2; return num3; };
-
-
如何执行存储在block变量中的代码段?
语法格式:block变量名();
-
block简写
- 如果写的代码段没有返回值,那么代码段的
void
可省略。(注意声明不可以省略) - 如果写的代码段没有参数,那么代码段的小括号可以省略。(注意声明变量不可以省)
- 都没有,只写
^
。 - 声明block变量时,如果有指定参数,只写参数的类型,不用写参数的名称。(注意代码段都要写)
- 无论代码段是否有返回值,在写代码段的时候都可以省略(系统会自动的确定)。
- 建议:按照最标准的写法来写block变量和block代码段。因为这样可以提高代码的阅读性。
- 如果写的代码段没有返回值,那么代码段的
-
可以使用
typedef
来简化block变量-
typedef 返回值类型 (^新类型)(参数列表);
代表重新定义了一个类型,后面创建这个类型的变量就可以直接使用新类型创建。 -
typedef void (^NewType)(); // 代表重新定义了一个类型叫做NewType NewType block1; NewType block2; typedef int (^NewType2)(int num1,int num2); NewType2 t1 = ^int(int num1,int num2) { int num3 = num1 + num2; return num3; };
-
-
block块访问外部变量的问题
- 在
block
代码块的内部,可以取定义在外部的局部变量和全局变量。 - 在
block
代码块的内部,可以修改全局变量,但是不能修改定义在外部的局部变量。 - 如果希望允许修改外部的局部变量,就在这个局部变量加一个
__block
的修饰符。
- 在
-
block作为函数的参数
- 就是在小括号中声明一个指定格式的
block
变量就可以了。 - 可以使用
typedef
来进行简化。 - 调用这个函数的时候,传入一个与定义的
block
变量格式一样的代码段。 - 效果:可以将调用者自己写的代码段,传递到函数的内部去执行。
- 就是在小括号中声明一个指定格式的
-
block作为函数的返回值
-
block与函数的异同
- 相同:都是封装一段代码
- 不同
- block是一个数据类型,函数是一个函数。
- 可以声明block类型的变量,函数就只是函数。
block
可以作为函数的参数,而函数不能作为函数的参数(可以用指向函数的指针)
12. Protocol
-
作用:专门用来声明一大堆方法。(不能声明属性,也不能实现方法,只能用来写方法的声明)。只要某个类遵守了这个协议,就相当于拥有这个协议中的所有的方法的声明。(类似Java中的接口)
-
协议的声明
@protocol 协议名称 <NSObject> 方法的声明; @end
-
创建
NewFile OC-File protocol
- 只会生成一个
.h
文件
-
类遵守协议
-
协议就是用来写方法声明的,就是用来被类遵守的。
-
如果想要一个类拥有协议中定义的所有的方法声明,那么就让这个类遵守这个协议。
-
类只要遵守一个协议,那么这个类就拥有了这些协议中定义的所有的方法的声明了。
-
#import "协议" @interface 类名 : 父类 <协议名> @end
-
这个类只拥有了这个协议中的方法的声明,没有实现,所以,这个类就应该实现协议中的方法。
-
如果类没有实现协议中的方法,不会报错,只会警告。但是当创建对象,调用没有实现的协议的方法时,就会报错。
-
// SportProtocol.h #import <Foundation/Foundation.h> @protocol SportProtocol @required -(void) paShan; -(void) swim; @end // Person.h #import <Foundation/Foundation.h> #import "SportProtocol.h" @interface Person : NSObject <SportProtocol> @end // Person.m #import "Person.h" @implementation Person - (void)paShan{ NSLog(@"爬山"); } - (void)swim{ NSLog(@"游泳"); } @end
-
-
类是单继承,协议可以多遵守。(类似Java中的单继承,多实现)
-
@interface 类名 : 父类名 <协议1,协议2,协议3> @end
-
当一个遵守了多个协议后,相当于这个类拥有了所有协议中定义的方法的声明,那么这个类应该熟悉实现所有协议中的方法。
-
-
@required
与@optional
- 这两个修饰符专门用来修饰协议中方法。
- 在协议中,如果方法的声明被
@required
修饰,那么遵守这个协议的类必须要实现这个方法,否则编译器会发出警告。 - 在协议中,如果方法的声明被
@optional
修饰,那么遵守这个协议的类可以实现这个方法,也可以不实现这个方法,不实现编译器也不会报出警告。 - 这两个关键字的作用是,告诉遵守协议的类,哪些方法是必须要实现的,因为会调用。
- 默认是
@required
。
-
协议可以继承
- 协议可以从另外一个协议中继承,并且可以多继承。
- 语法:
@protocol 协议名称 <父协议的名称>
- 效果
- 子协议中不仅有自己的方法的声明,还有父协议中的所有的方法的声明。
- 如果一个类遵守了某份协议,那么这个类就拥有了这个协议和这个协议的父协议的所有的方法的声明。
- 在
Foundation
框架中,有一个类,叫做NSObject
是所有OC类的基类;有一个协议,叫做NSObject
。类的名称可以和协议一样。 NSObject
协议被NSObject
类遵守,所以,NSObject
协议中的所有的方法,全部的OC
类都拥有了。- 要求所有的协议都间接或直接的继承NSObject协议。
-
协议的类型限制
-
NSObject<协议名称> *指针名
:这个指针要指向遵守了这个协议的任意对象,否则就会一个警告。 -
<>
可以写多个协议。 -
Why???遵守了某个协议的类,就相当于这个类拥有了这个协议所定义的行为。
-
// LearnProtocol.h #import <Foundation/Foundation.h> @protocol LearnProtocol <NSObject> @end // PlayProtocol.h #import <Foundation/Foundation.h> @protocol PlayProtocol <NSObject> @end // Student.h #import <Foundation/Foundation.h> #import "LearnProtocol.h" #import "PlayProtocol.h" @interface Student : NSObject <LearnProtocol,PlayProtocol> @end // Student.m #import "Student.h" @implementation Student @end // main.m #import <Foundation/Foundation.h> #import "Student.h" #import "LearnProtocol.h" #import "PlayProtocol.h" int main() { NSObject<LearnProtocol,PlayProtocol> *student1 = [Student new]; }
-
13. 深拷贝和浅拷贝
- 概述
- 浅拷贝:没有开辟新的指针地址,只是将新对象指向原来的指针,并没有进行真正的复制。
- 深拷贝:深拷贝分为不完全深拷贝、完全深拷贝
- 不完全深拷贝:开辟了新的指针地址,但是如果
copy
的对象是容器类(NSArray)
等,容器内的元素还是原来的元素(容器内的元素指针地址指向同一块内存)。不完全深拷贝拷贝的只是一个容器对象,只是外壳,容器内的对象只保存一份引用。 - 完全深拷贝:开辟了新的指针地址,而且如果拷贝的对象是容器类,容器内的元素同样也进行深拷贝,开辟了新的内存空间,完全深拷贝就是完完全全的进行拷贝。(了解下自定义类深拷贝怎么实现)
- 浅拷贝、深拷贝
- 对于不可变得对象来说,由于对象本身不可进行改变,即不能对它产生影响,所以不可变对象
copy
都是浅拷贝。 - 不可变对象的
mutableCopy
是深拷贝(不完全深拷贝)。 - 可变对象的
copy
是深拷贝(不完全深拷贝)
- 对于不可变得对象来说,由于对象本身不可进行改变,即不能对它产生影响,所以不可变对象