点语法:
点语法的本质还是方法调用
当使用点语法时,编译器会自动展开成相应的方法
Person *p = [Person new];
p.age=10; //编译器编译的时候会自动将这一行代码转换成 [p setAge:10];
int a=p.age; //编译器编译的时候会自动将这一行代码转换成 int a = [p age];
使用点语法的时候要注意不要形成死循环:
- (void)setAge:(int)age
{
//下面的代码将引发死循环,相当于[self setAge:age]
self.age = age;
}
- (int)age
{
//下面的代码将引发死循环,相当于[self age]
return self.age;}
成员变量的4种作用域:
@private:只能在当前类的对象方法中直接访问(@implementation中不写默认是@private)
@protected:可以再当前类及其子类的对象方法中直接访问(@interface中不写默认是@protected)
@public:任何地方都可以访问对象的成员变量
@package:只要处在同一个框架中,就能直接访问对象的成员变量,介于@private和@public之间
什么都不写的时候默认的访问级别是protected
成员变量也可以写在实现部分,也就是.m文件中,访问级别默认是@private
在 .m文件中声明的成员变量不能和 .h文件中声明的成员变量重名(也就是@implementation和@interface文件)
OC中只能单继承
父类\超类 superclass
子类 subclass\subclasses
@property和@synthesize
这两个关键字是编译器特性
@property:可以自动生成某个成员变量的set和get方法声明
成员变量:
int _age;
int _height;
NSString *_name;
get和set方法的声明:
@property int age;就相当于下面的两句声明代码
- (void)setAge:(int)age;
- (int)age;
@property NSString *name;
当多个成员变量的类型一致的时候,可以使用@property 类型 成员1,成员2,成员3;
get和set方法的实现:
@synthesize age=_age; //相当于下面两个方法(@synthesize自动生成age的set和get方法,并且访问_age这个成员变量)
如果是@synthesize age;//这样的写法的话默认访问的就不是成员变量_age了,而是成员变量age
- (void)setAge:(int)age
{
_age=age;
}
- (int)age
{
return _age;
}
不写成员变量,但是写@property的话,那么在@implementation中使用@synthesize的话,访问成员变量,如果不存在的话,就会自动生成@private的成员变量,数据类型根据@property中的数据类型。(这个变量是生成在@implementation中的)
最简单的写法:
直接写一个@property int age;
这句话将会完成的任务:
1、生成age的get和set方法声明
2、生成一个int类型的成员变量 int _age;(因为生成的变量是@private,所以如果子类需要访问的话,还是需要我们自己写成员变量,这样就不会生成私有的成员变量了)
3、生成age的get和set方法的实现
@synthesize的细节
@synthesize age=_age
1、setter和getter实现中会访问成员变量_age;
2、如果成员变量不存在,就会自动生成一个@private的成员变量_age
@synthesize age
1、setter和getter实现中会访问age变量
2、如果成员变量age不存在,就会自动生成一个@private的成员变量age
手动实现
1、若手动实现setter方法,编译器就只会自动生成getter方法
2、若手动实现getter方法,编译器就只会自动生成setter方法
3、若手动同时实现了setter和getter方法,编译器就不会自动生成不存在的成员变量
id
id是万能指针,能指向\操作任何OC对象
id内部包含*,所以声明变量的时候不需要带 *
NSString * (字符串)也是OC对象,所以id也可以指向字符串
也就是说 id 相当于 NSObject *
id类型的定义:
typedef struct objc object{
Class isa;
} *id;
局限性:
调用一个不存在的方法的时候,编译器会马上报错
构造方法:对象方法
构造方法:用来初始化对象的方法,是个对象方法,以 -号开头
重写构造方法的目的:为了让对象创建出来,成员变量就会有一些固定的值
Person *p = [Person new];
new方法完整的创建一个对象(分别调用两个方法完成下面的工作):
1、分配存储空间(+alloc)
2、初始化(-init)
new方法内部进行的工作:
//调用+alloc方法分配存储空间
Person *p1 = [Person alloc];
//调用-init方法进行初始化
Person *p2 = [p1 init];
init方法就是构造方法:
等价于:Person *p = [[Person alloc] init];
对象初始化之后,对象的成员变量默认为0
当要求每个Person对象创建出来的时候,他的成员变量_age都是10
构造方法的注意点:
1、先调用父类的构造方法 ([super init])
2、在进行子类内部成员变量的初始化
//重写-init方法
- (id)init
{
//1、一定要调用super的init方法:初始化父类中声明的一些成员变量和其他属性
//2、如果对象初始化成功,才有必要进行接下来的初始化
if (self = [super init])
{
//初始化成功
_age = 10;
}
//3、返回一个已经初始化完毕的对象
return self;
}
自定义构造函数:
上面的重写init方法,只能在内部给成员变量赋初始值,声明的对象都一样
自定义构造函数分两步:
第一步:声明
第二步:实现
自定义构造方法的规范:
1、一定是对象方法,一定以 - 开头
2、返回值一般是id类型
3、方法名一般以initWith开头
声明:
- (id)initWithName:(NSString *)name;
实现:
- (id)initWithName:(NSString *)name
{
if(self = [super init])
{
_name=name;
}
return self;
}
当子类中的初始化方法需要使用父类的成员变量的时候,原则是:子类中的成员变量在子类中初始化,父类的成员变量在父类中初始化。(父类中提供初始化方法,然后子类将值传递过来)
这样做的好处就是:当父类中的成员变量发生变化的时候,子类不需要更改不会报错。直接改父类就行了
Category:分类
分类:可以给某一个类扩充一些方法(不修改原来类的代码)-》相当于C#中的partial
//声明
@interface 类名 (分类名称)
@end
//实现
@implementaion 类名 (分类名称)
@end
使用注意:
1、分类只能增加方法,不能增加成员变量
2、分类方法实现中可以访问原来类中声明的成员变量
3、如果分类中的方法与原来类中的方法重名,那么优先调用的是分类中的方法。(这样就会覆盖原来类中的方法,原来类中的方法将不能使用)
4、方法调用的优先级:分类(最后参与编译的分类优先)-->原来类-->父类
characterAtIndex:<#(NSUInteger)#>:字符串的这个方法是获取指定位置上的字符(位置序号从0开始)
类的本质:
1、类也是个对象。简称“类对象”
->其实类也是一个对象,是一个Class类型的对象。
Class关键字内部包含 * ,使用的时候后面不需要加 *
利用Person类对象,创建Person类型的对象
//获取内存中的类对象(获取到的类对象可以调用类方法)
Person *p = [[Person alloc] init];
Class c = [p class];
或者:Class c = [Person class];
类的加载过程:
+ (void)load:方法,在类被加载的时候调用
程序启动的时候会加载所有的类和分类,并调用所有类和分类的+load方法
先加载父类,再加载子类,也就是先调用父类的+load,再调用子类的+load
先加载原始类,再加载分类
不管程序运行过程中有没有用到这个类,都会调用+load加载
+initialize
在第一次使用某个类时(比如创建对象等),就会调用一次+initialize方法
一个类只会调用一次+initialize方法,先调用父类的,在调用子类的
当程序启动时,就会加载项目中所有的类和分类,而且加载后会调用每个类和分类的+load方法,先调用原始类的load方法,后调用分类的load方法
当第一次使用某个类时,就会调用当前类的+initialize方法
先加载父类,再加载子类(先调用父类的+load方法,在调用子类的+load方法)
先初始化父类,再初始化子类(先调用父类的+initialize方法,再调用子类的+initialize方法)
description方法:
-description(决定实例对象的输出结果)
Person *p = [[Person alloc] init];
//默认情况下,利用NSLog和%@输出对象时,结果是: <类名:内存地址>
NSLog(@"%@",p);
打印OC对象使用的占位符是%@
打印OC对象的话(除了NSString *),显示的是类名加上类在内存中的地址
执行NSLog(@"%@",p);这句代码的时候:
1、首先会调用对象p的-description方法
2、拿到-description方法的返回值(NSString *)显示到屏幕上
3、-description方法默认返回的是“类名:内存地址”
当想输出对象的内容的时候,可以在类的实现中重写-description方法:
- (NSString *)description
{
return [NSString stringWithFormat:@"age=%d,name=%@",_age,_name];
}
不要在description中尝试输出self:NSLog(@"%@",self),这样会引发死循环
+description(决定类对象的输出结果)
Class c = [Person class];
//会调用类的+description方法
//拿到+description方法的返回值(NSString *)显示到屏幕上(默认返回的是类名)
NSLog(@"%@",c);
NSLog():
打印指针中存储的对象的内存地址:
NSLog(@"%p",p);
打印指针变量自己的内存地址:
NSLog(@"%p", &p);
NSLog()输出C语言字符串的时候,不能有中文
NSLog(@"%s",_func_);//输出当前方法名称
NSLog(@"%d",_LINE_);//输出当前行号
NSLog(@"%s",_FILE_);//输出当前源文件的完整路径
NSLog(@"%s",__PRETTY_FUNCTION__ );//返回当前方法或函数的完整的函数名(包括返回值和参数)
SEL类型
是一种类型
一个类中都有SEL类型数据,一个SEL数据对应一个方法的地址
Person *p = [[Person alloc] init];
[p test2];
1、把test2包装成SEL类型的数据
2、根据SEL数据找到对应的方法地址
3、根据方法地址调用对应的方法(这里使用的缓存技术,第一次会查找,以后就会使用第一次查找的结果)
间接调用test2方法
[p performSelector:@selector(test2)];
方法的存储位置:
每个类的方法列表都存储在类对象中
每个方法都有一个与之对应的SEL类型的数据
根据一个SEL对象就可以找到方法的地址,进而调用
SEL类型的定义
typedef struct objc_selector *SEL;
SEL对象的创建
SEL s=@selector(test);
SEL s2=NSSelectorFormString(@"test");
NSSelectorFormString(@"test");//将一个字符串类型的数据传进去转换成SEL数据
每个方法内部都有一个SEL类型的数据_cmd,指向当前方法
在方法中不能使用[self performSelector:_cmd];
SEL其实是对方法的一种包装,将方法包装成一个SEL类型的数据,去找对应的方法地址,找到方法地址就可以调用方法了。
其实消息就是SEL