Away0x's Blog

Coding blogging for hackers.

ObjectiveC - SEL, Id

选择器类型 SEL

  • SEL 类型代表着方法的签名,在类对象的方法列表中存储着该签名与方法代码的对应关系
  • 每个类的方法列表都存储在类对象中
  • 每个方法都有一个与之对应的 SEL 类型的对象
  • 根据一个 SEL 对象就可以找到方法的地址,进而调用方法
  • SEL 类型的定义: typedef struct obj_selector *SEL;
1
2
3
4
5
6
7
8
9
10
[p test];
/*
1. 首先把 test 这个方法名包装成 SEL 类型的数据
2. 根据 SEL 数据到该类的类对象上去找对应的方法的代码,找到就执行该代码
3. 没找到则根据类对象上父类的类对象指针,去父类的类对象中查找,找到则执行父类的代码
4. 如还没找到,一直向上找,直到基类 NSObject
5. 如都没找到就报错

**这个操作过程中有缓存,第一次找是一个个的找,很耗性能,之后再用,直接从缓存中取用
*/

SEL 的作用 1: 配合对象/类来检查对象/类中有没有实现某一个方法

1
2
3
4
5
6
7
8
9
10
SEL sel = @selector(setAge:);
Person *p = [Person new];
// 判断对象 p 中有没实现实例方法 "setAge:"
BOOL flag = [p respondsToSelector:sel];

// respondsToSelector 注意点
// 1. 如果是通过一个对象来调用该方法那么会判断该对象有没实现该实例方法
// 2. 如果是通过类来调用,那么会判断该类有没实现这个类方法

flag = [Person respondsToSelector:sel];

SEL 的作用 2: 配合对象/类来调用某一个 SEL 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 调用无参方法
SEL sel = @selector(demo);
Person *p = [Person new];
// 调用 p 对象中 sel 类型对应的方法,即 demo 方法
[p performSelector:sel]; // 相当于 [p demo]

// 调用单参方法
SEL sel2 = @selector(demo:);
[p performSelector:sel2 withObject:@"123"]; // withObject 就是要传递的参数
// 如用 performSelector 调用有参方法,那么参数必须是对象类型
//    即方法的形参必须是一个对象,因为 withObject 只能传递一个对象

// 调用多参方法 (performSelector 最多只能传递 2 个参数)
SEL sel3 = @selector(demo:andOther:);
[p performSelector:sel3 withObject:@"123" withObject:@"456"];

SEL 的作用 3: 配合对象将 SEL 类型作为方法的形参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@interface Demo : NSObject
// 调用传入对象的指定方法
- (void)useSel:(id)obj andSel:(SEL)sel;
@end

@implementation Demo
- (void)useSel:(id)obj andSel:(SEL)sel {
    [obj performSelector:sel];
}
@end

// 使用
Demo *demo = [Demo new];
//     调用 obj1 的 test 方法
[demo useSel:obj1 andSel:@selector(test)];

id

  • id 是一个数据类型,并且是一个动态数据类型 (万能指针)

静态类型

将一个指针变量定义为特定类的对象时,使用的是静态类型,在编译时就知道这个指针变量所属的类。这个变量总是存储特定类的对象

编译时已经知道类中有哪些属性和方法,如访问了不属于该静态类型的属性和方法,编译器会报错

1
Person *p = [Person new];

动态类型

这一特性是程序直到执行时才确定对象所属的类

编译时不知道其真实类型,在运行时才知道其真实类型。并且通过动态类型定义变量,如访问了不属于该动态类型的属性和方法,编译器不会报错 (躲过了编译器的检查)

1
2
3
4
5
// id 的定义中,已经包好了 *,所以自己不用写
// id 指针只能指向 OC 中的对象
// 当 id 指向的变量调用了本项目中所有类都没有的方法,编译器会报错
// id 类型不能使用 . 语法。因为 . 语法是编译时特性,id 是运行时特性
id obj = [Person new];
  • id == NSObject *,但是数据类型不一样
    1. id 是动态类型
    2. NSObject * 是静态类型

动态类型的作用

  1. 调用子类方法而不用强转
    1. 通过静态类型定义变量,不能调用子类特有的方法(除非强转成子类) objectivec id obj = [Father new]; [obj child_function]; // 可调用子类特有的方法
    2. 通过动态类型定义变量,可以调用子类特有的方法(不用强转)
  2. 可通过动态数据类型调用私有方法(即只有实现没有声明的方法)

动态类型的缺点

  • 由于动态类型可调用任意方法,所以有可能调用到不属于自己的方法,而编译不会报错,所以可能导致运行时报错
  • 虽说 id 类型可存储任何类型的对象,但不要养成滥用这种通用类型的习惯
    1. 如没使用多态尽量使用静态类型
      • 用于多态,可减少代码量,避免调用子类特有的方法需要强制类型转换
    2. 静态类型可以更早的发现错误 (在编译阶段而不是运行阶段)】
    3. 静态类型可提高程序的可读性
    4. 使用动态类型前最好判断其真实类型 (避免运行时错误)

动态类型判断真实类型

1
2
3
4
5
6
7
// 
// 方法1: isKindOfClass:classObj 判断实例对象是否是这个类或者这个类的子类的实例
Person *p = [Person new];
Student *stu = [Student new];

BOOL res = [p isKindOfClass:[Person class]]; // YES
res = [stu isKindOfClass:[Person class]];    // YES
1
2
3
4
5
6
// isMemberOfClass:classObj 判断是否是这个类的实例
Person *p = [Person new];
Student *stu = [Student new];

BOOL res = [p isMemberOfClass:[Person class]]; // YES
res = [stu isMemberOfClass:[Person class]];    // NO (是子类不是实例)
1
2
3
4
5
// 使用
id obj = [Person new];
if ([obj isKindOfClass:[Person class]]) {
    [obj person_function];
}