【Objective-C】13-类和方法的本质

本文详细解析Objective-C中的类本质、类的加载与初始化、描述符方法以及SEL机制,通过实例代码展示如何操作类与对象,包括获取类对象、类方法的调用与重写、描述符方法的使用以及SEL机制的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >



一、类的本质

1.1 类的本质是对象,是Class类型的对象,简称类对象。

1.2 类的创建过程:Person *p = [[Person alloc] init];

     1>利用Class创建Person类对象

     2>利用Person类对象,创建Person类型的对象。

1.3 获取内存中类对象的两种方式

     1> Class c = [p class];  //调用对象的class对象方法

     2> Class c1 = [Person class];  //调用类的class类方法

1.4 利用上面两行代码获得的Class对象是同一个对象,指向的是同一个内存地址。可通过打印对象的内存地址验证。

1.5 类对象和类是同一个对象,所以可以通过类对象创建类的对象。

#import <Foundation/Foundation.h>

@interface Person : NSObject
+ (void)test;
@end

@implementation Person
+ (void)test
{
	NSLog(@"调用了test类方法");
}
@end

int main() {
	//利用Person这个类创建了两个Person类型的对象
	Person *p = [[Person alloc] init];

	Person *p2 = [[Person alloc] init];

	Class c = [p class]; // 通过对象方法class获取内存中的类对象
	Class c2 = [Person class]; // 通过类方法class获取内存中的类对象
	NSLog(@"c=%p,c2=%p", c, c2); //打印两个类对象的内存地址,验证是否同一个对象。

	Class c3 = [p class];
	[c3 test]; //利用类对象调用类方法
	Person *p3 = [[c3 new] init]; // 利用类对象创建对象
	return 0;
}



、类的加载和初始化

2.1 当程序启动的时候,就会加载一次项目中所有的类和分类(不管程序运行过程中有没有用到这个类)。类被加载完毕的时候就会调用每个类和分类的+load方法。只会调用一次。

2.2 当第一次使用某个类的时候,就会调用一次当前类的+initialize方法。之后不管调用这个类多少次,整个项目生命周期中只会调用一次。

2.3 加载顺序:先加载父类,再加载子类(先调用父类的+load方法,再调用子类的+load方法)。

      先初始化父类,再初始化子类(先调用父类的+initialize方法,再调用子类的+initialize方法)。

      分类和类都实现+initialize方法,类的+initialize方法会被分类覆盖。

2.4 +initialize方法可以用来监听类,如果想在类第一次被使用时执行某些行为,可以把代码放在+initialize方法中。

#import <Foundation/Foundation.h>

@interface Person : NSObject

@end

@implementation Person
//当程序启动的时候,就会加载一次项目中所有的类。类被加载完毕的时候就会调用+load方法
+ (void)load
{
	NSLog(@"Person--load");
}
//当第一次使用这个类的时候,就会调用一次+initialize方法。整个项目生命周期中只会调用一次
+(void)initialize
{
	NSLog(@"Person--initialize");
}
@end

@interface Student : Person

@end

@implementation Student
//在类被加载的时候调用
+ (void)load
{
	NSLog(@"Student--load");
}
+(void)initialize
{
	NSLog(@"Student--initialize");
}
@end

@interface Person(MM)

@end

@implementation Person(MM)
//在类被加载的时候调用
+ (void)load
{
	NSLog(@"Person (MM)--load");
}
// 使用类时会分类会覆盖原来类的+initialize方法
+(void)initialize
{
	NSLog(@"Person (MM)--initialize");
}
@end

int main() {
	Person *p = [[Person alloc] init];
	Person *p2 = [[Person alloc] init];
	Student *s = [[Student alloc] init];
	return 0;
}
输出结果:

2015 - 02 - 08 05:28 : 31.400 04 - 类的加载[1465:46788] Person--load
2015 - 02 - 08 05 : 28 : 31.401 04 - 类的加载[1465:46788] Student--load
2015 - 02 - 08 05 : 28 : 31.401 04 - 类的加载[1465:46788] Person(MM)--load
2015 - 02 - 08 05 : 28 : 31.402 04 - 类的加载[1465:46788] Person(MM)--initialize
2015 - 02 - 08 05 : 28 : 31.402 04 - 类的加载[1465:46788] Student--initialize

三、description方法

description方法分对象方法和类方法。默认使用NSLog和%@打印对象时,直接就输出对象的类名和内存地址。默认使用NSLog和%@打印类时,直接就输出类名。当需要显示自定义信息时,需要重写description方法。

3.1 -description

     默认情况下,利用NSLog和%@(NSLog(@"%@",p);)输出对象时,结果是:<类名:内存地址>
    1>会调用对象p的-description方法
    2>拿到-description方法的返回值(NSString *)显示到屏幕上
    3>-description方法默认返回的是“类名+内存地址”
    4>决定了实例对象的输出结果

    5>-description的重写
    - (NSString *)description
    {
    //下面代码会引发死循环
    // NSLog(@"%@",self);

    return [NSString stringWithFormat:@"age=%d,name=%@",_age,_name];
    }


3.2 +description

Class c = [Person class];

默认情况下,利用NSLog和%@(NSLog(@"%@",c);)输出类对象时,结果是:类名
1>会调用类的+description方法
2>拿到+description方法的返回值(NSString *)显示到屏幕上
3>决定了类对象的输出结果
NSLog(@"%@",c);
4>+description的重写

+(NSString *)description
{
    return @"ABC";
}

#import <Foundation/Foundation.h>

@interface Person : NSObject
@property int age;
@end

@implementation Person
// 重写-description方法
- (NSString *)description
{
	return[NSString stringWithFormat : @"age=%d", _age];
}
// 重写+descrition方法
+(NSString *)description
{
	return @"调用+description";
}
@end

int main() {
	Person *p = [[Person alloc] init];
	NSLog(@"%@", p);//调用-description

	Class c = [Person class];
	NSLog(@"%@", c);
	return 0;
}
输出结果:

2015 - 02 - 08 06:06 : 49.832 05 - description[1582:51889] age = 0
2015 - 02 - 08 06 : 06 : 49.834 05 - description[1582:51889] 调用+description

四、NSLog方法补充:
NSLog(@"%p", &p);  // 指针变量的地址
NSLog(@"%p", p);     // 对象的地址
NSLog(@"%@", p);    // <类名:对象地址>


NSLog(@"%d", __LINE__);  // 打印行号
//NSLog(@"%s",__FILE__);   //NSLog输出C语言字符串的时候,不能有中文
printf("%s\n", __FILE__);
NSLog(@"%s", __func__);  //输出当前函数名


五、SEL

5.1 SEL是一种数据类型,全称Selector,表示方法的存储位置。

5.2 方法调用的本质:SEL其实是对方法的一种包装,将方法包装成一个SEL类型的数据,去找对应的方法地址。找到方法地址就可以调用方法。其实消息就是SEL。

Person *p = [[Person alloc] init];

[p test];

1.把test包装成SEL类型的数据
2.根据SEL数据找到对应的方法地址
3.根据方法地址调用对应的方法

4.以上三个步骤会有缓存机制,当第一次通过SEL找到相对应的方法时,会把该查询结果缓存起来,便于下次的查找。

5.3 通过SEL间接调用方法

间接调用test方法
1>无参数
[p performSelector : @selector(test2)];
2>有参数
[p test : @"123"];
[p performSelector : @selector(test:) withObject:@"456"];

5.4 方法的存储位置

1>每个类的方法列表都存储在类对象中

2>每个方法都有一个与之对应的SEL类型的对象

3>根据一个SEL对象就可以找到方法的地址,进而调用方法

4>SEL类型的定义

typedef struct objc_selector *SEL;

5.5 SEL对象的创建

SEL s = @selector(test);

SEL s2 = NSSelectorFromString(@"test");

5.5  知道方法名的字符串,调用方法
NSString *name = @"test2";
SEL s = NSSelectorFromString(name);
[p performSelector: s];

5.6 _cmd

每个方法内部都包含着一个_cmd,代表着当前方法。如果要打印出来的话,需要调用NSStringFromSelector(_cmd);
NSString *str = NSStringFromSelector(_cmd);
NSLog(@"%@", str);

5.7 注意

在方法内部调用下面语句会引发死循环
[self performSelector : _cmd];


#import <Foundation/Foundation.h>

@interface Person : NSObject
+ (void)test;
-(void)test2;
-(void)test3:(NSString *)str;
@end

@implementation Person
+ (void)test
{
	NSLog(@"+test");
}
-(void)test2
{
	NSString *str = NSStringFromSelector(_cmd);
	NSLog(@"调用了test2方法---%@", str);

	//会引发死循环
	//[self performSelector:_cmd];
}
-(void)test3:(NSString *)str
{
	NSLog(@"test3---%@", str);
}
@end

int main() {
	Person *p = [[Person alloc] init];

	NSString *name = @"test2";
	SEL s = NSSelectorFromString(name);
	[p performSelector : s];

	[p performSelector : @selector(test2)];

	SEL s2 = @selector(test3:);//间接调用带参数的方法,别忘了方法后面的冒号,因为冒号也是属于方法名的一部分
	[p performSelector : s2 withObject : @"hhh"];

	return 0;
}

输出结果:

2015-02-08 07:42:12.070 SEL[1725:61095] 调用了test2方法---test2
2015-02-08 07:42:12.072 SEL[1725:61095] 调用了test2方法---test2
2015-02-08 07:42:12.072 SEL[1725:61095] test3---hhh


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值