一、类的深入研究
(一)类的本质
类本身也是一个对象,是class类型的对象,简称“类对象”。
Class类型的定义:
Typedef struct obj class *class;
类名就代表着类对象,每个类只有一个类对象。
利用class 创建 Person类
利用Person 创建Person类型的对象
Person *p=[[Person alloc] init];
获取内存中的类对象有两种方法:
(1)class c=[p claa];//指向类的对象的指针调用class方法
(2)Class c1=[Person class];//使用类名调用class方法
注意:c和c1打印出来的地址相同,class c2=[p claa];可以证明所有的对象共用一个类方法。
(二)类的加载和初始化
#import <Foundation/Foundation.h>
//Person类的声明和实现
@interface Person : NSObject
@end
@implementation Person
//重写类的加载和初始化方法
+(void)load {
NSLog(@"person + load");}
+(void)initialize {
NSLog(@"person + initialize");}
@end
//Student类的声明和实现,继承自Person类
@interface Student : Person
@end
@implementation Student
//重写类的加载和初始化方法
+(void)load {
NSLog(@"Student + load");}
+(void)initialize {
NSLog(@"Student + initialize");}
@end
int main(int argc, const char * argv[]) {
//创建一个Person类
//第一次使用Person类
Person *p1 = [[Person alloc] init];
Person *p2 = [[Person alloc] init];
//第一次使用Student类
Student *s = [[Student alloc] init];
return 0;
}
2016-03-06 23:18:35.878 类[9783:96717] person + load
2016-03-06 23:18:35.879 类[9783:96717] Student + load
2016-03-06 23:18:35.879 类[9783:96717] person + initialize
2016-03-06 23:18:35.879 类[9783:96717] Student + initialize
Program ended with exit code: 0
1.当程序启动时,就会加载项目中所有的类和分类,而且加载后会调用每个类和分类的+load方法,只会调用一次;
2.当第一次使用某个类时,就会调用当前类的+initialize方法;
3.先加载父类,再加载子类(先调用父类的+load方法,再调用子类的+load方法,最后调用分类的+load方法),先初始化父类,再初始化子类(先调用父类的+initialize方法,再调用子类的+initialize方法)。
4.注意:在初始化的时候,如果在分类中重写了+initialize方法,则会覆盖掉父类的。
5.重写+initialize方法可以监听类的使用情况。
二、description方法
description方法包括类方法和对象方法。(NSObject类所包含)
(一)基本知识
-description(对象方法)
使用NSLog和@%输出某个对象时,会调用对象的description方法,并拿到返回值进行输出。
+description(类方法)
使用NSLog和@%输出某个对象时,会调用类对象的description方法,并拿到返回值进行输出,把整个对象一次性打印出来,打印对象使用%@。
使用@%打印对象如(“@%”,P)默认打印输出为<类名:内存地址>,虽然字符串也是对象,但字符串在使用@%打印时情况特殊。
#import <Foundation/Foundation.h>
//Person类的声明和实现
@interface Person : NSObject
//两个成员变量
@property int age;
@property NSString *name;
@end
@implementation Person
@end
int main(int argc, const char * argv[]) {
Person *p = [[Person alloc] init];
//点语法的本质是调用setterr和getter方法
p.age = 18;
p.name = @"lvyuping";
//使用NSLog和%@打印对象
NSLog(@"%@",p);
return 0;
}
2016-03-06 23:28:23.571 类[10376:103772] <Person: 0x100114a60>
Program ended with exit code: 0
(二)实现打印对象的所有属性
#import <Foundation/Foundation.h>
//Person类的声明和实现
@interface Person : NSObject
@property int age;
@property NSString *name;
@end
@implementation Person
//重写-description方法,实现打印对象的所有成员变量
-(NSString *)description {
return [NSString stringWithFormat:@"age = %d,name = %@",_age,_name];
}
@end
int main(int argc, const char * argv[]) {
Person *p = [[Person alloc] init];
//点语法的本质是调用setterr和getter方法
p.age = 18;
p.name = @"lvyuping";
//使用NSLog和%@打印对象
NSLog(@"%@",p);
//获取Person的类对象
Class c = [Person class];
NSLog(@"%@",c);
return 0;
}
2016-03-06 23:36:00.657 类[10853:111222] age = 18,name = lvyuping
2016-03-06 23:36:00.658 类[10853:111222] Person
Program ended with exit code: 0
(三)区别
+description方法决定了类对象的输出结果,即类本身
-description方法决定了实例对象的输出结果,即Person创建的对象。
(四)打印相关补充
int main(int argc, const char * argv[]) {
Person *p = [[Person alloc] init];
//1.打印对象地址
NSLog(@"%p",p);
//2.打印当前代码行号
NSLog(@"%d",__LINE__);
//3.打印当前文件路径
NSLog(@"%s",__FILE__);
//4.打印所属的方法或函数名
NSLog(@"%s",__func__);
return 0;
}
三、SEL
SEL:全称Selector 表示方法的存储位置。
方法在内存中是怎么存储的?
Person *p=[[Person alloc] init];
[p test];
寻找方法的过程:
(1)首先把test这个方法名包装成sel类型的数据;
(2)根据SEL数据找到对应的方法地址;
(3)根据方法地址调用相应的方法。
(4)注意:在这个操作过程中有缓存,第一次找的时候是一个一个的找,非常耗性能,之后再用到的时候就直接使用。
关于_cmd:每个方法的内部都有一个-cmd,代表着当前方法。
注意:SEL其实是对方法的一种包装,将方法包装成一个SEL类型的数据,去寻找对应的方法地址,找到方法地址后就可以调用方法。这些都是运行时特性,发消息就是发送SEL,然后根据SEL找到地址,调用方法。