一、类的本质
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