一、点语法
点语法的本质是方法调用,就是调用的setter和getter方法。
#import <Foundation/Foundation.h>
#import "Person.h"
int main()
{
Person *p=[Person new];
p.age=23; // 这就是点语法,xcode会自动将这个代码转为[p setAge:23]
NSLog(@"%d",p.age); // xcode会自动将点语法转为[p age]
return 0;
}
//编译器根据取值还是赋值来判断转为setter还是getter。
@interface Person : NSObject
{
int _age;
}
- (void)setAge:(int)age;
- (int)age;
@end
@implementation Person
- (void)setAge:(int)age
{
_age=age;
// self.age=age; 这会死循环
NSLog(@"use-setter");//为了验证点语法是否调用setter方法
}
- (int)age
{
NSLog(@"use-getter");//为了验证点语法是否覅用getter方法
//NSLog(@"%d",self.age);这会死循环,get方法不停地调用自己
return _age;
}
@end
@private:只能在当前类的对象方法中直接访问。类的实现中的变量默认是private
@protected:可以在当前类以及子类的对象方法中直接访问。类的声明中的变量默认是protect。
@public:任何地方都可以直接访问
@package:同一个“体系内”(框架)可以访问,介于@private和@public之间
OC只支持单继承。
#import <Foundation/Foundation.h>
//Person类
@interface Person : NSObject
{
@protected
int _age;
@private
NSString *_name;
}
- (void)setAge:(int)age;
- (int)age;
- (void)setName:(NSString *)name;
- (NSString *)name;
@end
@implementation Person
{
int hight; // 在类的实现中定义了一个变量,这个变量是private,其他类均不能访问。
}
//_age的set和get
- (void)setAge:(int)age
{
NSLog(@"%d",self->hight); //本类可以直接访问hight
_age=age;
// self.age=age; 这会死循环
NSLog(@"use-setter");//为了验证点语法是否调用setter方法
}
- (int)age
{
NSLog(@"use-getter");//为了验证点语法是否覅用getter方法
//NSLog(@"%d",self.age);这会死循环,get方法不停地调用自己
return _age;
}
//_name的set和get
- (void)setName:(NSString *)name
{
_name=name;
}
- (NSString *)name
{
return _name;
}
@end
//Student类
@interface Student : Person
- (void)said;
@end
@implementation Student
- (void)said
{
/*
NSLog(@"my name is %@",self->_name);
这样是错误的,因为_name是Person中被private修饰的,子类不能直接访问.
但是Student类中有_name这个变量
*/
NSLog(@"my name is %@,age is %d",self.name,self->_age); // _age可以被子类直接访问
// NSLog(@"%d",self->hight); 这是错误的,因为Person.h中根本没有hight这个变量
}
@end
int main()
{
Person *p=[Person new];
p.age=23; // 这就是点语法,xcode会自动将这个代码转为[p setAge:23]
NSLog(@"%d",p.age); // xcode会自动将点语法转为[p age]
//private修饰的变量虽然子类不能直接访问,但是在子类内部这些变量都有
Student *student=[Student new];
student.age=12;
student.name=@"xiaoming";
[student said];
return 0;
}
三、@property和@synthesize
@property:自动生成某个成员变量的setter和getter方法的声明
如:@property int age;
编译器会自动将这句自动生成 -(void)setAge:(int)age;
和 -(int)age;
#import <Foundation/Foundation.h>
#import "Person.h"
int main()
{
Person *p=[Person new];
p.age=23; // 这就是点语法,xcode会自动将这个代码转为[p setAge:23]
NSLog(@"%d",p.age); // xcode会自动将点语法转为[p age]
[p setName:@"liming"];
NSLog(@"%@",[p name]);
return 0;
}
@interface Person : NSObject
{
int _age;
NSString *_name;
}
@property int age;
/*
int 就是set方法的参数类型和get方法的返回值类型,
age 直接影响到set和get方法的名称
假如 @property int _age;
那么生成的set和get方法将分别是
-(void)set_age:(int)_age;
- (int)_age;
*/
@property NSString * name;
@end
@implementation Person
//_age的set和get
- (void)setAge:(int)age
{
_age=age;;
}
- (int)age
{
return _age;
}
//_name的set和get
- (void)setName:(NSString *)name
{
_name=name;
}
- (NSString *)name
{
return _name;
}
@end
@synthesize:自动生成某个成员变量的set和get方法实现
如:@synthesize age=_age;(age:@property里写的是啥,这里就写啥。_age:这个变量写成什么,set和 get里面就会访问什么变量)
这句话意味着会访问_age这个变量。
因此上述代码中的Person类的实现可以写成:
@implementation Person
//_age的set和get
@synthesize age=_age;
//_name的set和get
@synthesize name=_name;
// @synthesize age=_age,name=_name;这可以连着写
//@property 也可以连着写 @proerty int age,hight; (age和hight必须都是int类型)
@end
@synthesize age=_age;这句代码访问_age这个变量,如果不存在,那就会自动生成一个@private类型的_age变量。
所以上述代码可以简化为:
#import <Foundation/Foundation.h>
@interface Person : NSObject //类的声明
//自动生成相应的set和get方法声明
@property int age;
@property NSString * name;
@end
@implementation Person // 类的实现
//自动生成变量_age和_name.且自动生成相应的get和set方法的实现
@synthesize age=_age,name=_name;
@end
随着xcode不断升级,@synthesize语句也可以简化不写
因此代码能进一步简化到:
#import <Foundation/Foundation.h>
//类的声明
@interface Person : NSObject
//自动生成相应的set和get方法声明,同时也实现了这些方法,而且还自动生成了私有的_age和_name
@property int age;
@property NSString * name;
@end
//类的实现
@implementation Person
@end
注意:1.@synthesize age;这里没有注明访问哪个变量,那么就会默认访问跟age一样的变量,也就是会访问成员变 量age而不是_age。假如age这个变量不存在,就会自动生成age。
2.代码中没有相应的set和get方法时,Xcode才会自动生成。有,就不生成,同时变量也不会生成(因为生成 变量的目的就是为了在set和get中使用)。
若手动实现了setter方法,编译器就只会自动生成getter方法
若手动实现了getter方法,编译器就只会自动生成setter方法
若同时手动实现了setter和getter方法,编译器就不会自动生成不存在的成员变量
四、id类型
万能指针,能指向任何OC对象,相当于NSObject *注意:id后面不要加上*
id p = [Person new];
五、构造方法
构造方法是用来初始化对象的方法。
重写构造方法
完整地创建一个可用的对象,分为两步:
1>分配存储空间 (类方法:+(id)alloc,返回一个分配好内存空间的对象,但是没有初始化)
2>初始化 (对象方法:-(id)init,返回初始化好的对象)
new就是包含了alloc和init两个方法。
init就是构造方法。
重写init方法分三步:
1>调用父类init方法,且赋值给self,初始化父类中声明的一些成员变量和其他属性。
2>判断self是否为空。不为空才算初始化成功,才有必要进行自定义的初始化内容。
3>返回self。
#import <Foundation/Foundation.h>
@interface Person : NSObject
//自动生成相应的set和get方法声明,同时也实现了这些方法
@property int age;
@property NSString * name;
@end
@implementation Person
//重写init方法
- (id)init
{
if(self=[super init])
{
_age=23;
NSLog(@"Person 初始化完毕,age=%d",_age);
}
return self;
}
@end
int main()
{
Person *p=[[Person alloc]init];
p=[Person new]; // 可见直接调用new是一样的,因为new本来就是包含了alloc和init
return 0;
}
//程序结果:
//2014-12-12 19:18:31.558 核心语法[1137:303] Person 初始化完毕,age=23
//2014-12-12 19:18:31.561 核心语法[1137:303] Person 初始化完毕,age=23
自定义构造方法
规范:1>一定是对象方法,一定是以-开头
2>返回值一般是id类型
3>方法名一般以initWith开头
#import <Foundation/Foundation.h>
//Persdon类
@interface Person : NSObject
//自动生成_age和_name,且自动生成他们的set和get方法声明及实现
@property int age;
@property NSString *name;
//自定义构造方法
- (id)initWithAge:(int)age andName:(NSString *)name;
@end
@implementation Person
//自定义构造方法的实现
- (id)initWithAge:(int)age andName:(NSString *)name
{
if(self=[super init])
{
_age=age;
_name=name;
}
return self;
}
@end
//Student类
@interface Student : Person
@property int number;
//自定义构造方法
- (id)initWithAge:(int)age andName:(NSString *)name andNumber:(int)number;
@end
@implementation Student
//实现自定义构造方法
- (id)initWithAge:(int)age andName:(NSString *)name andNumber:(int)number
{
//父类内部的成员变量,尽量用父类自己的方法访问
if(self=[super initWithAge:age andName:name])
{
_number=number;
}
return self;
}
@end
int main()
{
//用自定义的构造方法初始化对象
Person *p=[[Person alloc] initWithAge:23 andName:@"LIMING"];
NSLog(@"age--%d,name--%@",p.age,p.name);
Student *s=[[Student alloc]initWithAge:34 andName:@"haha" andNumber:1234];
NSLog(@"age=%d,name=%@,number=%d",s.age,s.name,s.number);
return 0;
}
六、分类(category)
可以给某个类扩充一些方法,在不修改原来类的代码的情况下。
格式:
//分类声明
@interface 类名(分类名称)
@end
//分类实现
@implementation 类名(分类名称)
@end
注意:
1.分类只能加方法,不能加成员变量。
2.分类方法实现中可以访问原来类中声明的成员变量。
3.分类可以重新实现原来类中的方法,但是会覆盖掉原来的方法,会导致原来的方法没法再使用。
4.方法调用的优先级:分类(最后参与编译的分类优先)>原来类>父类
(Xcode中调整.m文件编译顺序:点击项目名称->Build Phases->Compile Sources,在这里上下拖动可以调换顺序)
#import <Foundation/Foundation.h>
//NSString类的分类
@interface NSString (numCount)
//声明方法计算字符串中数字的个数,一个类方法,一个对象方法
+ (int)countOfString:(NSString *)str;
- (int)count;
@end
@implementation NSString (numCount)
+ (int)countOfString:(NSString *)str
{
int count=0;
for(int i=0;i<[str length];i++)
{
//判断每个位置上德字符是不是在0-9之间
if([str characterAtIndex:i]>='0'&&[str characterAtIndex:i]<='9')
count++;
}
return count;
}
- (int)count
{
//直接调用类方法
return [NSString countOfString:self];
}
@end
//Persdon类
@interface Person : NSObject
//自动生成_age和_name,且自动生成他们的set和get方法声明及实现
@property int age;
@property NSString *name;
@end
//preson类的实现
@implementation Person
@end
//Person的分类
@interface Person (Beijing)
- (void)test;
@end
@implementation Person (Beijing)
- (void)test
{
NSLog(@"%@",@"beijing");
}
@end
//Person的分类
@interface Person (Nanjing)
- (void)test;
@end
@implementation Person (Nanjing)
- (void)test
{
NSLog(@"%@",@"nanjing");
}
@end
int main()
{
Person *p=[[Person alloc] init];
[p test];//调整Person两个分类的编译顺序时,结果是不同的
NSLog(@"%d",[NSString countOfString:@"123"]);//调用自定义的类方法
NSLog(@"%d",[@"s1d" count]); // 调用自定义的对象方法
return 0;
}
八、 类的本质
1. 类也是个对象
其实类也是一个对象,是Class类型的对象,简称“类对象”。
类名就代表着类对象,每个类只有一个类对象。
Class类型的定义:typedef struct objc_class *Class;(也就是本质是指针)
2. +load和+initialize
+load
在程序启动的时候会加载所有的类和分类,并调用所有类和分类的+load方法
先加载父类,再加载子类;也就是先调用父类的+load,再调用子类的+load
先加载元原始类,再加载分类
不管程序运行过程有没有用到这个类,都会调用+load加载
+initialize
在第一次使用某个类时(比如创建对象等),就会调用一次+initialize方法
一个类只会调用一次+initialize方法,先调用父类的,再调用子类的(要是分类重写了initialize方法,就直接调用分类的,原始类就不调用了)
3. 获取类对象的2种方式
Class c = [Person class]; // 类方法
或者
Person *p = [Person new];
Class c2 = [p class]; // 对象方法
4. 类对象调用类方法
Class c = [Person class];
Person *p2 = [c new];
#import <Foundation/Foundation.h>
//Persdon类
@interface Person : NSObject
+ (void)test;
@end
//preson类的实现
@implementation Person
+ (void)test
{
NSLog(@"Person的类方法-test");
}
+(void)load
{
NSLog(@"Person----------load");
}
+ (void)initialize
{
NSLog(@"Person-----initialize");
}
@end
//Person的分类
@interface Person (Beijing)
@end
@implementation Person (Beijing)
+(void)load
{
NSLog(@"Person (Beijing)----------load");
}
+ (void)initialize
{
NSLog(@"Person (Beijing)-----initialize");
}
@end
//Student类
@interface Student : Person
@end
@implementation Student
+(void)load
{
NSLog(@"Student----------load");
}
+ (void)initialize
{
NSLog(@"Student-----initialize");
}
@end
int main()
{
Person *p=[[Person alloc] init];
Class c=[Person class]; // 通过类方法获取Person的类对象
Class c1=[p class]; // 通过对象方法获取Person的类对象
NSLog(@"%p---%p",c,c1); //地址相同,即二者都是指向Person类对象,这个类对象只有一个
//类对象调用类方法
[c test];
Person *p1=[c new];
return 0;
}
//执行结果:
//2014-12-13 00:04:37.832 核心语法[2141:303] Person----------load
//2014-12-13 00:04:37.834 核心语法[2141:303] Student----------load
//2014-12-13 00:04:37.835 核心语法[2141:303] Person (Beijing)----------load
//2014-12-13 00:04:37.836 核心语法[2141:303] Person (Beijing)-----initialize
//2014-12-13 00:04:37.837 核心语法[2141:303] 0x1000012c0---0x1000012c0
//2014-12-13 00:04:37.837 核心语法[2141:303] Person的类方法-test
九、descrition方法
1. -description方法
使用NSLog和%@输出某个对象时(默认输出的是“类名+内存地址”),会调用对象的-description方法,并拿到返回值进行输出
2. + description方法
使用NSLog和%@输出某个类对象时(默认输出类名),会调用类对象+description方法,并拿到返回值进行输出
3. 死循环陷阱
如果在-description方法中使用NSLog打印self
注意:NSLog输出C语言字符串时,字符串内不能含有中文
#import <Foundation/Foundation.h>
//Persdon类
@interface Person : NSObject
@property int age;
@property NSString *name;
@end
//preson类的实现
@implementation Person
//重写类方法
+ (NSString *)description
{
return @"=======";
}
//重写对象方法
- (NSString *)description
{
return [NSString stringWithFormat:@"Persion-age=%d,name=%@",self.age,self.name];
}
@end
int main()
{
Person *p=[[Person alloc] init];
p.age=12;
p.name=@"xiaoming";
NSLog(@"%@",p); // 会调用对象的description方法
Class c=[Person class];
NSLog(@"%@",c); //会调用类的description方法
return 0;
}
//运行结果:
//2014-12-13 01:05:00.812 核心语法[2397:303] Persion-age=12,name=xiaoming
//2014-12-13 01:05:00.815 核心语法[2397:303] =======
十、SEL
1. 方法的存储位置
每个类的方法列表都存储在类对象中每个方法都有一个与之对应的SEL类型的对象
根据一个SEL对象就可以找到方法的地址,进而调用方法
SEL类型的定义:typedef struct objc_selector *SEL;
#import <Foundation/Foundation.h>
//Persdon类
@interface Person : NSObject
- (void)test;
- (void)test1:(NSString *)name;
- (void)name:(int)num;
@end
//preson类的实现
@implementation Person
- (void)test
{
NSLog(@"---------");
}
- (void)test1:(NSString *)name
{
NSLog(@"name=%@",name);
}
/*
每个方法都有一个隐藏的SEL数据类型:_cmd,这个数据代表方法本身
同样的,知道一个方法的SEL类型,也可以变成字符串
*/
- (void)name:(int)num
{
NSString *name1=NSStringFromSelector(_cmd);
NSLog(@"%@--%d",name1,num);
}
@end
int main()
{
Person *p=[[Person alloc] init];
[p test];//直接调用对象方法test
[p performSelector:@selector(test)]; // 间接调用
/*
直接调用对象方法的过程:
1.把该方法包装成SEL类型的数据
2.根据SEL数据找到对应的方法地址
3.根据方法地址调用对应的方法
*/
//创建SEL对象
SEL s=@selector(test1:);
//间接调用带参数的方法
[p performSelector:s withObject:@"lixiao"];
//假如知道一个方法名得字符串形式,也可以转换为SEL
NSString *name=@"test1:";
SEL s1=NSSelectorFromString(name);// 调用了一个函数,将字符串转换为SEL
[p performSelector:s1 withObject:@"hah"];
[p name:23];//打印函数名称
return 0;
}
SEL其实是对方法的一种包装,将方法包装成SEL类型的数据,去找对应方法的地址,找到方法地址就可以调用该方法。消息机制的消息就是SEL