学习目标
1.【了解】类的本质
2.【掌握】SEL数据类型
3.【掌握】点语法
4.【掌握】@property和@synthesize
5.【了解】动态类型和静态类型
6.【理解】id和instancetype
7.【理解】动态类型检测
8.【掌握】构造方法
一、类的本质
当程序执行的时候,程序中所有类都会自动加载到内存中的代码区(类加载)。并且一旦类加载到代码区,会直到程序结束才会被回收。
那么类以什么形式加载到代码区的呢?
系统首先会在代码区创建一个Class对象,将类的信息(类名、属性、方法)以Class对象的形式存储到这个对象之中,这个Class对象也叫做类对象。
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
|
//Person.h文件
#import <Foundation/Foundation.h>
@interface
Person
: NSObject
+
(
void
)
sayHi
;
@end
//Person.m文件
#import "Person.h"
@implementation
Person
+
(
void
)
sayHi
{
NSLog
(
@"sayHi"
)
;
}
@end
//main.m文件
#import <Foundation/Foundation.h>
#import "Person.h"
int
main
(
int
argc
,
const
char
*
argv
[
]
)
{
@
autoreleasepool
{
Person
*p
=
[
[
Person
alloc
]
init
]
;
//调用对象的class对象方法,就可以获取到存储这个对象所属类的类对象。
Class
c1
=
[
p
class
]
;
//调用这个类的class类方法,可以获取到这个类的Class对象的地址
Class
c2
=
[
Person
class
]
;
//调用类方法
[
c1
sayHi
]
;
//创建对象
Person
*p2
=
[
[
c2
alloc
]
init
]
;
}
return
0
;
}
|
二、SEL数据类型
SEL的全称是selector,译为选择器。SEL是一种用来存储类的方法的数据类型。系统会在Class对象中定义SEL类型的属性,并将类的方法包装为一个SEL对象,每个SEL对象只能包装一个方法。
类的方法以SEL对象的形式的存储在Class对象之中,一个SEL对象包装一个方法,这些方法以Class对象的属性的形式存储在Class对象里。
获取存储方法的SEL对象
1
2
|
//获取存储这个方法的SEL对象
SEL
s
=
@selector
(方法名
)
;
|
调用方法的本质,假如Person类有个eat对象方法
1
2
|
Person
*p
=
[
[
Person
alloc
]
init
]
;
[
p
eat
]
;
|
获取存储eat方法的SEL对象,将这个SEL对象发送给堆空间中的p对象,p对象接收到这个SEL消息后,就会根据isa指针找到存储类的Class对象。找到类对象以后,再根据SEL对象找到对应方法并执行,如果没有找到就去父类中找,直到基类还没有就报错。
调用方法的时候,其实是在为类或者对象发送SEL消息,将方法的SEL消息发送给对象,对象再根据isa指针找到类对象,查找是否有匹配的SEL对象。
手动向对象发送SEL消息,假如Person类有个eat对象方法和带参数est:andSleep:对象方法。
1
2
3
4
5
6
7
8
9
10
11
|
Person
*p
=
[
[
Person
alloc
]
init
]
;
//包装方法为SEL对象
SEL
s
=
@selector
(
eat
)
;
//将SEL对象发送给对象
[
p
performSelector
:s
]
;
//如果带多个参数
SEL
s1
=
@selector
(
est
:andSleep
:
)
;
//将SEL对象发送给对象,最多只能带两个参数
[
p
performSelector
:s1
withObject
:
@"参数1"
withObject
:
@"参数2"
]
;
|
三、点语法
访问OC对象的属性,需要使用对象调用对应属性的getter、setter方法来访问,让人感觉非常麻烦。苹果考虑到其他语言的程序员也会学习OC,就为Xcode编译器增加了一个编译器特性,可以使用点语法来替代对象调用setter和getter方法。编译器在编译的时候,会将使用点语法代码转换为调用对应的setter、getter方法。
语法:对象名.去掉下划线的属性名;
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
|
//Person.h文件
#import <Foundation/Foundation.h>
@interface
Person
: NSObject
{
NSString
*
_name
;
}
//_name的setter、getter方法声明
-
(
void
)
setName
:
(
NSString
*
)
name
;
-
(
NSString
*
)
name
;
@end
//Person.m文件
#import "Person.h"
@implementation
Person
//_name的setter、getter方法实现
-
(
void
)
setName
:
(
NSString
*
)
name
{
// self.age = name;
//等价于 [self setName] = name;
//会造成死循环
NSLog
(
@"调用了setter方法"
)
;
//如果调用了setter方法会输出这行
_name
=
name
;
}
-
(
NSString
*
)
name
{
NSLog
(
@"调用了getter方法"
)
;
//如果调用了getter方法会输出这行
return
_name
;
}
@end
//main.m文件
#import <Foundation/Foundation.h>
#import "Person.h"
int
main
(
int
argc
,
const
char
*
argv
[
]
)
{
@
autoreleasepool
{
Person
*p
=
[
[
Person
alloc
]
init
]
;
//当点语法在左边就是调用p的setter方法
p
.
name
=
@"六阿哥"
;
//为p对象的_name赋值并输出 调用了setter方法
//当点语法在右边就是调用p的getter方法
NSString
*name
=
p
.
name
;
//将p对象的_name赋值给name,并输出 调用了getter方法
NSLog
(
@"%@"
,
p
.
name
)
;
//输出 调用了getter方法
//输出 六阿哥
}
return
0
;
}
|
注意:
1.在setter、getter方法中慎用self,以免造成递归死循环。
四、@property和@synthesize
@property关键字
作用:程序在编译的时候,编译器会根据@property自动生成类的私有属性,并为属性自动生成对应setter、getter方法的声明。
单个属性语法:@property 数据类型 名称;
多个属性语法:@property 数据类型 名称1,名称2...;
@synthesize关键字
作用:程序在编译的时候,编译器会根据@synthesize自动生成对应属性setter、getter方法的实现。
语法:@synthesize @property的名称 = _名称;
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
59
60
|
//Person.h文件
#import <Foundation/Foundation.h>
@interface
Person
: NSObject
//这一行代码就相当于下面几行代码
@property
int
age
;
/* ps:这几行代码编译的时候自动生成
{
int _age;
}
- (void)setAge:(int)age;
- (int)age;
*/
@end
//Person.m文件
#import "Person.h"
@implementation
Person
//Xcode新版本里,@synthesize都不需要了,@property全部搞定。
@synthesize
age
=
_age
;
//为实例变量_age自动生成setter、getter方法的实现
/* ps:这几行代码编译的时候自动生成
- (void)setAge:(int)age {
_age = age;
}
- (int)age {
return _age;
}
*/
/*
@synthesize age;//如果只写名称,则会生成以下代码
{
int age;
}
- (void)setAge:(int)age {
self.age = age;
}
- (int)age {
return age;
}
*/
@end
//main.m文件
#import <Foundation/Foundation.h>
#import "Person.h"
int
main
(
int
argc
,
const
char
*
argv
[
]
)
{
@
autoreleasepool
{
Person
*p
=
[
[
Person
alloc
]
init
]
;
//使用点语法调用_age的setter、getter方法
p
.
age
=
18
;
int
age
=
p
.
age
;
}
return
0
;
}
|
注意:
1.@property的数据类型和属性的类型一致,名称和去掉下划线的实例变量名一致。
2.@synthesize需要指定对应属性,比如@synthesize age = _age;否则会自动生成一个跟名称同名的真私有属性。
3.@synthesize生成的setter、setter方法不做任何逻辑验证,如果我们希望在赋值、取值的时候需要逻辑验证,所以就得自己写。
4.@property批量定义属性的时候类型必须一致,@synthesize批量实现类型可以不一致。
5.@property关键字在Xcode4.4之后的版本里,已经替代了@synthesize的功能,所以我们以后写程序只需要写@property。
@property增强
1.如果属性已经存在,则不会自动生成属性,直接使用已经存在的属性。
2.@property生成的setter、getter方法也是没有任何逻辑验证的,所以我们可以自己重新写setter或者getter方法。
3.@property生成的属性和方法都可以被子类继承。
五、动态类型和静态类型
OC是一门弱类型语言,编译器在编译的时候在语法检测上没有强类型(比如java)语言那么严格,比如int num = 12.2;这是不会保错的。
动态类型:指针指向的对象不是本类对象,而是一个别的对象。这样的类型就叫做动态类型。比如:
1
|
Person
*p
=
[
[
Student
alloc
]
init
]
;
|
静态类型:指针指向的对象是本类对象,而不是一个别的对象。这样的类型就叫做静态类型。比如:
1
|
Person
*p
=
[
[
Person
alloc
]
init
]
;
|
编译检查:在程序编译的时候,根据指针的类型,在这个类里查找方法,如果有就编译通过。
运行检查:在程序运行的时候,根据对象检查是否有这个方法,如果有才会执行,没有就报错。
六、id和instancetype
id是一个万能指针,可以指向任意OC对象,可以作为方法的返回值,还有可以作为方法的参数。instancetype只能作为方法的返回值,代表返回当前类的对象,常用于构造方法。
1
2
3
|
id
d
=
[
[
NSObject
alloc
]
init
]
;
//指向任意OC对象
-
(
instancetype
)
init
;
//构造方法
|
通过NSObject指针去调用指向子类对象的方法,编译器会做编译检查,如果这个方法不是NSObject类中的方法,就报错。而通过id指针去调用对象的方法,编译不做检查。
id应用场景:
1
2
3
4
5
6
|
#import <Foundation/Foundation.h>
@interface
Person
: NSObject
@property
NSString
*name
;
@property
int
age
;
+
(
id
)
PersonWithName
:
(
NSString
*
)
name
andAge
:
(
int
)
age
;
//返回一个不确定类型的对象地址
@end
|
instancetype只能用在方法返回值中,代表返回当前类的对象。所以上面这个方法可以改为:
1
2
3
4
5
6
|
#import <Foundation/Foundation.h>
@interface
Person
: NSObject
@property
NSString
*name
;
@property
int
age
;
+
(
instancetype
)
PersonWithName
:
(
NSString
*
)
name
andAge
:
(
int
)
age
;
//返回当前类的对象的地址
@end
|
注意:
1.被id指向的对象能直接通过编译检查,不过只能使用id指针去调用方法,不能使用点语法。
2.instancetype只能作为方法的返回值。
七、动态类型检测
在程序运行的时候通过代码去检查方法是否可以调用,避免运行时报错。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#import "Person.h"
#import "Dog.h"
int
main
(
)
{
Person
*p
=
[
[
Person
alloc
]
init
]
;
//获取要判断的方法的SEL对象
SEL
s
=
@selector
(
shout
)
;
//判断这个对象中是否有这个方法
BOOL
result
=
[
p
respondsToSelector
:s
]
;
if
(
result
==
YES
)
{
//如果有就执行方法
[
(
Dog
*
)
p
shout
]
;
}
}
|
判断一个对象所属的类是不是指定的类或者指定的子类
1
2
3
4
5
6
7
|
#import "Person.h"
int
main
(
)
{
Person
*p
=
[
[
Person
alloc
]
init
]
;
//判断p指向的对象是不是Person类的对象或者Person类的子类对象
BOOL
result
=
[
p
isKindOfClass
:
[
Person
class
]
]
;
}
|
判断对象是不是特定类型的对象,不包括子类
1
2
3
4
5
6
7
|
#import "Person.h"
int
main
(
)
{
Person
*p
=
[
[
Person
alloc
]
init
]
;
//判断p指向的对象是不是Person类的对象
BOOL
result
=
[
p
isMemberOfClass
:
[
Person
class
]
]
;
}
|
判断一个类是不是另外一个类的子类
1
2
3
4
5
6
|
#import "Person.h"
#import "Student.h"
int
main
(
)
{
//判断Student类是不是Person的子类
BOOL
result
=
[
Student
isSubclassOfClass
:
[
Person
class
]
]
;
}
|
八、构造方法
new方法内部其实是先调用了alloc方法,在堆空间中创建对象并返回对象。再用这个对象调用init方法,初始化对象的属性,返回这个已经被初始化的对象。这个init方法是定义在NSObject中的构造方法,作用就是初始化对象。
1
2
3
|
Person
*p
=
[
Person
new
]
;
//上面表达式等价于
Person
*p
=
[
[
Person
alloc
]
init
]
;
|
重写构造方法 创建对象的时候直接为对象的属性赋自定义默认值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
//Person.h文件
#import <Foundation/Foundation.h>
@interface
Person
: NSObject
@property
NSString
*name
;
@property
int
age
;
@end
//Person.m文件
#import "Person.h"
@implementation
Person
-
(
instancetype
)
init
{
//先让父类初始化,返回已经初始化的对象
self
=
[
super
init
]
;
//调用init初始化对象有可能初始化失败,失败则返回nil。所以我们可以判断一下
if
(
self
)
{
self
.
name
=
@"无名"
;
self
.
age
=
18
;
}
return
self
;
}
@end
|
自定义构造方法 让调用者创建对象的时候可以为对象的属性自定义赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
//Person.h文件
#import <Foundation/Foundation.h>
@interface
Person
: NSObject
@property
NSString
*name
;
@property
int
age
;
-
(
instancetype
)
initWithName
:
(
NSString
*
)
name
andAge
:
(
int
)
age
;
@end
//Person.m文件
#import "Person.h"
@implementation
Person
-
(
instancetype
)
initWithName
:
(
NSString
*
)
name
andAge
:
(
int
)
age
{
//先让父类初始化,返回已经初始化的对象
self
=
[
super
init
]
;
//调用init初始化对象有可能初始化失败,失败则返回nil。所以我们可以判断一下
if
(
self
)
{
self
.
name
=
name
;
self
.
age
=
age
;
}
return
self
;
}
@end
|
注意:
1.重写构造方法时,必须先调用父类的init方法,来初始化父类的属性,再初始化子类的属性。
2.自定义构造方法名必须以initWith开头,必须有返回值(类型为instancetype),一定是一个对象方法。