OC语法之-核心语法
多文件开发
- C语言中我们知道,多文件开发是将函数的实现全部写在.c文件(one.c),函数的声明全部写在.h文件(one.h),然后只要将.h文件导入(#include”one.h”)带有主函数的.c(two.c)文件即可,然后分别编译one.c/two.c文件得到one.o/two.o文件,然后一起连接这两个.o文件生成可执行文件a.out,然后运行即可.
- 如果我们在two.c文件中导入的不是.h文件而是one.c文件,当同时进行连接one.o/two.o时,就会报错:说我们重复定义,因为#include相当于copy,two.c中已经有了one.c的全部内容,因此如果在把他们一起连接那么实际上就会有两份one.c了
- 对比C语言来看,OC也同理,为了方便,我们也进行多文件开发,分为.h/.m文件,因此也不能导入.m文件,否者也会报错:重复定义,XCode编译连接时会把所有的.m一起编译连接
- 同C语言对比一样,我们把每一个类单独用一对.m/.h文件来写,通常这个.m/.h文件的名字为该类的名字;.h中放类的声明,.m文件中放类的实现
- 与C语言中不同的是,.m文件中一定要导入对应的.h文件,因为.m文件中用到了.h文件类的声明的成员变量跟方法
- 如果哪个文件用到了该类,直接将该类的.h文件导入即可,千万不可以导入.m文件,错误跟C语言一样,重复定义
- 从上面我们可以看出.h跟.m没有任何必然联系系,不用一样的名字也可以
- XCode编译是把所有的.m文件编译成.o(目标文件),然后连接是把所有的.o文件连接在一起形成可执行文件a.out
点语法
#import <Foundation/Foundation.h>
@interface Person : NSObject
{
int _age;
NSString *_name;
}
- (void)setAge:(int)age;
- (int)age;
@end
#import "Person.h"
@implementation Person
- (void)setAge:(int)age {
//_age = age;
NSLog(@"setAge:");
// 会引发死循环
//self.age = age; // [self setAge:age];
}
- (int)age {
NSLog(@"age");
return _age;
// 会引发死循环
//return self.age;// [self age];
}
@end
//点语法调用:
Person *p = [Person new];
//点语法的赋值就是调用set方法
p.age = 10; // [p setAge:10];
//点语法的取值就是调用get方法
int a = p.age; // [p age];
- 点语法跟set方法/get方法有关的
- 这个点不是访问这个对象的成员变量的(不是age也不是_age)(这个最容易误解!!!),java中的点语法是访问成员变量的
- 点语法的本质是方法调用
- 编译器遇到点语法时,会自动转化为方法调用,因此点语法是编译器特性(编译器特性)
-
点语法的赋值就是调用set方法
p.age = 10; <=>[p setAge:10];
-
点语法的取值就是调用get方法
int a = p.age; <=> [p age];
- 满足点语法的条件:
- 有set/get方法的声明
- 有set/get方法的实现
- set/get方法实现中,可以没有成员变量操作
-
比如:
1. 声明 - (void)setAge:(int)age; - (int)age; 2.实现: - (void)setAge:(int)age { NSLog(@"这里随便写"); } - (int)age { NSLog(@"这里随便写"); //return 1; }
- 没有set方法,就只能使用点语法的取值操作
- 没有get方法,就只能使用点语法的赋值操作
- 注意: 同样适用于类方法
Person * p = Person.new
成员变量的作用域
- 局部变量/全局变量都有他的作用域,而成员变量既不属于局部变量,也不属于全局变量,那么他的作用域是什么呢?
- 局部变量与全局变量的作用域相对的是代码块,即在代码块内有用还是在代码块外有用;那么成员变量的作用域就是相对于这个类了,即在类中有用,还是在类外有用
- 相对于类内还是类外分为以下4种:
- @private: 只能在当前类的对象方法中直接访问(@implementation中设置成员变量默认是
@private
,类扩展中设置也是!!!)- 注意了,在 @implementation中或者类扩展中声明的成员变量虽然默认是@private,但是也只能是@private,即使加上@public也没有用,因为别人包含或者继承这个类的时候,只会包含你的.h文件!!!
- @protected: 可以在当前类及其子类的对象方法中直接访问(在@interface中默认就是
@protected
) - @public: 在任何地方(类内部、外部)都能直接访问对象的成员变量
- @package: 同一个体系(框架)内可以访问,介于
@private
与@public
之间
- @private: 只能在当前类的对象方法中直接访问(@implementation中设置成员变量默认是
- 注意:
@interface
和@implementation
中不能声明同名的成员变量 -
代码举例:
//Person.h文件 #import <Foundation/Foundation.h> @interface Person : NSObject { int _no; // 在任何地方都能直接访问对象的成员变量 @public int _age; // 只能在当前类的对象方法中直接访问 @private int _height; // 能在当前类和子类的对象方法中直接访问 @protected int _weight; int _money; } - (void)setHeight:(int)height; - (int)height; - (void)test; @end //Person.m文件 #import "Person.h" @interface Person() { // 类扩展中:默认就是私有@private //说明:类扩展中声明成员变量<=>@implementatiom中声明成员变量 int _klkl; @public//没有用仍然是@private类型 int _lkk; } @end @implementation Person { int _aaa;// 默认就是私有@private @public//写上这个也没有用,因为别人包含或者继承这个类的时候,只会包含你的.h文件!!! int _bbb; // @implementation中不能定义和@interface中同名的成员变量 // int _no; } - (void)test { //只能在当前类实现中访问 _age = 19; _height = 20; _weight = 50; _aaa = 10; } - (void)setHeight:(int)height { _height = height; } - (int)height { return _height; } @end //Student.h文件 #import "Person.h" @interface Student : Person - (void)study; @end //Student.m文件 #import "Student.h" @implementation Student - (void)study { //不能在子类中直接访问 // _height = 10; //用set/get方法设置,方法是可以继承的 [self setHeight:10]; int h = [self height]; //内在子类中直接访问 _weight = 100; //不能在子类中直接访问 //_klkl = 10; } @end //main.m文件 int main(int argc, const char * argv[]) { @autoreleasepool { Student *stu = [Student new]; [stu setHeight:100]; NSLog(@"%d", [stu height]); Person *p = [Person new]; p->_age = 100; //@public没用!!! //p->_bbb = 10; //只能在当前类中访问 //p->_height = 20; //前类和子类 //p->_weight = 10; } return 0; }
@property和@synthesize
- 这两个关键字帮助我们自动生成get/set方法,跟点语法一样,也是编译器特性
-
Xcode4.4之前
#import <Foundation/Foundation.h> @interface Person : NSObject { //下面这些成员变量也可以不写 int _age; double _weight; NSString *_name; } // @property:可以自动生成某个成员变量的setter和getter声明 @property int age; //- (void)setAge:(int)age; //- (int)age; /* @property int _age; //- (void)set_age:(int)_age; //- (int)_age; */ @property int height; - (void)test; @property double weight; @property NSString *name; @end #import "Person.h" @implementation Person /* 1.这句话的作用: @synthesize自动生成age的setter和getter实现,并且会访问_age这个成员变量,如果不存在,就会自动生成@private类型的_age成员变量 2. @synthesize age生成怎样的get方法/set方法 3. = _age 这个set/get方法中访问哪个成员变量 4. 注意:如果只写@synthesize age,默认会访问age这个成员变量,如果没有age,就会自动生成@private类型的age变量 5. 等价如下: -(void)setAge:(int)age{ _age = age; } -(int)age{ return _age; } 6. 手动实现: 若手动实现了setter方法,编译器就只会自动生成getter方法 若手动实现了getter方法,编译器就只会自动生成setter方法 若手动实现了setter方法/getter方法,编译器就不会自动生成不存在的成员变量 */ @synthesize age = _age; @synthesize height = _height; @synthesize weight = _weight, name = _name; -(void)test{ NSLog(@"%zd",_height); } @end
- Xcode4.4之后
只需要写@property int age;
就可以了,会做以下几件事:- 生成get/set方法的声明
- 生成带下划线的成员变量_age(注意这变量是@private)
- 生成get/set方法的实现
- @dynamic age;
- 告诉编译器,属性age的setter与getter方法由用户自己实现,不需要自动生成,或者,getter和setter方法在别处生成了(比如父类中),并没有在本类生成。
- 存取方法在运行时动态创建与绑定:主要使用在CoreData的实现NSManagerObject子类时使用
id
- id是一种数据类型
- 万能指针,能指向\操作任何OC对象
id == NSObject *
- id是没有
*
的!!! - id类型不能使用点语法!!!
构造方法
- +new方法
- 该方法是一个类方法
- 通过该方法可以创建一个对象
- 该方法实际上做了两件事
- 调用+alloc分配存储空间
- 在调用-init进行初始化
- 完整地创建一个可用的对象
- 分配存储空间 +alloc
- 初始化 -init
// Person *p = [Person new]; // 1.调用+alloc分配存储空间 // Person *p1 = [Person alloc]; // 2.调用-init进行初始化 // Person *p2 = [p1 init];
- 构造方法
- 构造方法就是这个-init方法
- 作用:用来初始化对象,是个对象方法,-开头
- 重写构造方法
- 对象初始化完毕,默认成员变量都是0
- 重写构造方法的目的:为了让对象创建出来,成员变量就会有一些固定的值
- 在对象相应的类中,重写父类的构造方法(-init)
- 重写构造方法的注意点:
- 先调用父类的构造方法(
[super init]
) - 再进行子类内部成员变量的初始化
#import "Person.h" @implementation Person // 重写-init方法 //- (id)init //{ // // 1.一定要调用回super的init方法:初始化父类中声明的一些成员变量和其他属性 // 只有父类先初始化成功,才有必要进行下一步的初始化 // self = [super init]; // 当前对象 self //此时的super是NSObject,那NSObject的init做了什么事情呢? /* -(id)init{ isa = [Person class]; return self; } */ // // 2.如果对象初始化成功,才有必要进行接下来的初始化 // if (self != nil) // { // 初始化成功 // _age = 10; // } // // // 3.返回一个已经初始化完毕的对象 // return self; //} //简写如下: - (id)init { //注意之这里是=不是==!!!! if ( self = [super init] ) { // 初始化成功 _age = 10; } // 3.返回一个已经初始化完毕的对象 return self; } @end
- 先调用父类的构造方法(
-
构造方法的执行过程
- 自定义构造方法
- 自定义构造方法的规范
- 一定是对象方法,一定以 - 开头
- 返回值一般是id类型
- 方法名一般以initWith开头(注意With必须首字母大写!!!)
-
代码举例:
#import <Foundation/Foundation.h> @interface Person : NSObject @property NSString *name; @property int age; - (id)initWithName:(NSString *)name andAge:(int)age; @end #import "Person.h" @implementation Person - (id)initWithName:(NSString *)name andAge:(int)age { if ( self = [super init] ) { _name = name; _age = age; } return self; } @end //创建一个Student子类 #import "Person.h" @interface Student : Person @property int no; - (id)initWithName:(NSString *)name andAge:(int)age andNo:(int)no; @end #import "Student.h" @implementation Student // 父类的属性交给父类方法去处理,子类方法处理子类自己的属性 - (id)initWithName:(NSString *)name andAge:(int)age andNo:(int)no { // 将name、age传递到父类方法中进行初始化 if ( self = [super initWithName:name andAge:age]) { _no = no; } return self; } @end //调用: Student *p = [[Student alloc] initWithName:@"Jim" andAge:29 andNo:10];
- 自定义构造方法的规范
分类(Category)
- 有一种要求:不改变原来的类,不创建新的类(继承),为原来的类添加方法.
- 分类的作用:在不改变原来类内容的基础上,可以为类增加一些方法
-
格式:
//通常写在.h文件,但也可以都写在.m文件 @interface Person (分类名称) //给原来类扩充的方法 @end //.m文件 @implementation Person (分类名称) //相应方法的实现 //在这里可以访问原来类的成员变量 //也可以重写原来类的方法 @end
- 使用注意:
- 分类只能增加方法,不能增加成员变量。就算通过其他方式添加也并非本质的成员变量
- 分类方法实现中可以访问原来类中声明的成员变量(但注意这个成员变量一定不能是@private,即不是@property修饰的!!!)
- 分类可以重新实现原来类中的方法,但是会覆盖掉原来的方法,会导致原来的方法没法再使用
- 方法调用的优先级:分类(最后参与编译的分类优先) –> 原来类 –> 父类
- 分类不一定要有.h文件,如果仅仅是重写、修改原来类中的方法,只需要在.m文件中重写就行了
-
代码举例:
//Person.h文件 #import <Foundation/Foundation.h> @interface Person : NSObject { //这样做事为了能够让这个成员变量在分类中也能够访问 int _age; } @property int age; - (void)test; @end //Person.m文件 #import "Person.h" @implementation Person - (void)test { NSLog(@"Person-test"); } @end //Person+JJ.h文件 #import "Person.h" @interface Person (JJ) - (void)test2; @end //Person+JJ.m文件 #import "Person+JJ.h" @implementation Person (JJ) - (void)test2 { NSLog(@"-----test2"); } - (void)test { NSLog(@"Person (JJ)-test"); } @end //Person+MJ.h文件 #import "Person.h" @interface Person (MJ) - (void)study; @end //Person+MJ.m文件 #import "Person+MJ.h" @implementation Person (MJ) - (void)study { //访问原类的成员变量 //_age一定不能是@private,即用@property修饰的不行 NSLog(@"学习-----%d", _age); } //重写原类的方法 - (void)test { NSLog(@"Person (MJ)-test"); } @end //main.m文件 #import <Foundation/Foundation.h> #import "Person.h" //先加载 #import "Person+MJ.h" //后加载 #import "Person+JJ.h" int main() { Person *p = [[Person alloc] init]; //p.age = 10; // 优先去分类中查找,然后再去原来类中找,最后再去父类中找 /* //先调用后加载的 打印结果: Person (JJ)-test */ [p test]; [p study]; return 0; }
类的本质
类
- 每个类在存储空间中只有一份,所有同类型的对象都指向这个类
- 类本身也是一个对象,是个Class(结构体指针,已经包含*了)类型的对象,简称类对象
- 实例对象:用一个类(类对象)创建的对象 类对象: 用一个类调用class创建出类对象
-
代码举例:
void test() { // 利用Person这个类创建了2个Person类型的对象 Person *p = [[Person alloc] init]; Person *p2 = [[Person alloc] init]; // 获取内存中的类对象 //方法一:用实例对象获取,-class方法本质就是拿到该对象的isa指针(每个实例对象的isa指针指向他的类) Class c = [p class]; Class c2 = [p2 class]; // 方法二:利用类的Class,+class获取本身这个结构体指针 Class c3 = [Person class]; NSLog(@"c=%p, c2=%p, c3=%p", c, c2, c3); }
+load与+initialize
- +load
- 当程序启动时,就会加载项目中所有的类和分类,而且加载后会调用每个类和分类的+load方法(分类中最后掉调用)。只会调用一次。
- 先加载父类,再加载子类(先调用父类的+load方法,再调用子类的+load方法),先加载元原始类,再加载分类;
- 不管程序运行过程有没有用到这个类,都会调用+load加载
- +initialize
- 当第一次使用某个类时,就会调用当前类的+initialize方法,只会调用一次
- 先初始化父类,再初始化子类(先调用父类的+initialize方法,再调用子类的+initialize方法)
- 分类中的+initialize方法会覆盖原类中的+initialize方法,而+load不会
- 在+initialize/+load方法中就没有必要在写[super …]了,因为他们一定会先调用父类的
-
代码举例:
//父类person #import <Foundation/Foundation.h> @interface Person : NSObject @property int age; + (void)test; @end #import "Person.h" @implementation Person + (void)test { NSLog(@"调用了test方法"); } // 当程序启动的时候,就会加载一次项目中所有的类。类加载完毕后就会调用+load方法 + (void)load { NSLog(@"Person---load"); } // 当第一次使用这个类的时候,就会调用一次+initialize方法 + (void)initialize { NSLog(@"Person-initialize"); } @end //子类Student #import <Foundation/Foundation.h> #import "Person.h" @interface Student : Person @end #import "Student.h" @implementation Student // 在类被加载的时候调用 + (void)load { NSLog(@"Student---load"); } + (void)initialize { NSLog(@"Student-initialize"); } @end //分类 #import "Person.h" @interface Person (MJ) @end #import "Person+MJ.h" @implementation Person (MJ) //分类最后加载 + (void)load { NSLog(@"Person(MJ)---load"); } //分类中的+initialize方法会覆盖原类中的+initialize方法 + (void)initialize { NSLog(@"Person(MJ)-initialize"); } @end //main.m #import <Foundation/Foundation.h> #import "Person.h" #import "Student.h" int main() { [[Student alloc] init]; return 0; }
-
打印结果:
2017-12-07 09:40:37.116124+0800 06-类的本质[18927:757753] Person---load 2017-12-07 09:40:37.116692+0800 06-类的本质[18927:757753] Student---load 2017-12-07 09:40:37.116760+0800 06-类的本质[18927:757753] Person(MJ)---load 2017-12-07 09:40:37.116851+0800 06-类的本质[18927:757753] Person(MJ)-initialize 2017-12-07 09:40:37.116967+0800 06-类的本质[18927:757753] Student-initialize
description
- description方法
默认情况下,利用NSLog和%@输出对象时,结果是:<类名:内存地址>,实际操作如下:
- 会调用对象p的-description方法
- 拿到-description方法的返回值(NSString *)显示到屏幕上
- -description方法默认返回的是“类名+内存地址”
+ description方法
默认情况下,利用NSLog和%@输出类对象时,结果是:<类名>,实际操作如下:类名>
- 会调用类的+description方法
- 拿到+description方法的返回值(NSString *)显示到屏幕上
- +description方法默认返回的是“类名”
代码举例:
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property int age;
@property NSString *name;
@end
#import "Person.h"
@implementation Person
// 决定了实例对象的输出结果
- (NSString *)description
{
// 下面代码会引发死循环
// NSLog(@"%@", self);
return [NSString stringWithFormat:@"age=%d, name=%@", _age, _name];
//return @"3424324";
}
// 决定了类对象的输出结果
+ (NSString *)description
{
return @"Abc";
}
@end
调用:
Person *p = [[Person alloc] init];
p.age = 20;
p.name = @"Jack";
NSLog(@"%@", p);
Class c = [Person class];
NSLog(@"%@", c);
NSLog
// 输出当前函数名
NSLog(@"%NSLog(@"%s\n", __func__);
// 输出行号
NSLog(@"%d", __LINE__);
// NSLog输出C语言字符串的时候,不能有中文
// NSLog(@"%s", __FILE__);
// 输出源文件的名称
printf("%s\n", __FILE__);
SEL
- 方法的存储位置
- 每个类的方法列表都存储在类对象中
- 每个方法都有一个与之对应的SEL类型的对象
- 根据一个SEL对象就可以找到方法的地址,进而调用方法
- SEL类型的定义
typedef struct objc_selector *SEL;
- SEL对象的创建
SEL s = @selector(test);
SEL s2 = NSSelectorFromString(@"test");
- SEL其实是对方法的一种包装,将方法包装成一个SEL类型的数据,去找对应的方法地址。找到方法地址就可以调用方法
[p test2];
调用过程- 把test2包装成SEL类型的数据
- 根据SEL数据找到对应的方法地址
- 根据方法地址调用对应的方法
- _cmd
- 每个方法内部都有个隐藏的参数_cmd
- _cmd代表着当前方法的SEL
- _cmd == @selecter(test2);
- 将SEL对象转为NSString对象
NSString *str = NSStringFromSelector(@selector(test));
代码举例:
#import <Foundation/Foundation.h>
@interface Person : NSObject
- (void)test2;
- (void)test3:(NSString *)abc;
@end
#import "Person.h"
@implementation Person
- (void)test2
{
//每个方法内部都有个隐藏的参数_cmd
// _cmd代表着当前方法的SEL
//_cmd == @selecter(test2);
NSString *str = NSStringFromSelector(_cmd);
// 会引发死循环
// [self performSelector:_cmd];
NSLog(@"调用了test2方法-----%@", str);
}
- (void)test3:(NSString *)abc
{
NSLog(@"test3-----%@", abc);
}
@end
//调用:
Person *p = [[Person alloc] init];
//1. 间接调用test2方法
//直接调用
[p test2];
//简介调用
[p performSelector:@selector(test2)];
//2.带参数的直接调用
[p test3:@"123"];
//间接调用
SEL ss = @selector(test3:);
[p performSelector:ss withObject:@"456"];
//3.把一个字符串转化为sel
NSString *name = @"test2";
SEL s = NSSelectorFromString(name);
[p performSelector:s];