oc语言学习之基础知识点介绍(五):OC进阶
一、点语法介绍
/* 以前封装后,要给属性赋值,必须调用方法 这样做,有两个缺点: 1.代码量多,调用方法要写的东西多。 2.看起来并不像是给属性赋值,也不像取值。 我们用点语法就可以更好的解决! 点语法的语法: 对象.属性; 注意:这里的属性不需要加_ 对象.属性 = 值; 点语法的本质: 其实就是调用getter或者setter方法。 点语法注意事项: 1.在类的方法里面如果用self.age 这个是调用方法,self->_age 是直接调用成员变量。 2.千万记得不要在setter或者getter方法里面用点语法,否则,死循环。 3.用点语法之前必须保证你的setter和getter方法的方法名符合命名的规范。 提示:点语法,也可以称之为 语法糖。 本质可以理解为是编译器的特性。 因为其他面向对象的语言都是用.来访问属性。 */ #import <Foundation/Foundation.h> #import "Person.h" int main(int argc, const char * argv[]) { @autoreleasepool { Person *p = [Person new]; // [p setName:@"刘德华"]; p.name = @"张学友"; //这一句就相当于上一句,编译后也是编译成 [p setName:@"张学友"]; // [p setAge:16]; p.age = 16; //这一句就相当于上一句 // [p setWeight:100]; p.weight = 100; //这一句就相当于上一句 // NSLog(@"名字=%@ 年龄:%d",[p name],[p age]); NSLog(@"名字=%@ 年龄:%d",p.name,p.age); } return 0; }
二、@property和synthesize关键字
/* @property 作用:帮我们生成setter和getter方法的声明。 语法: @property 类型 属性名; 注意:属性名不加_ 如果只用@property关键字的话,在实现类中,setter和getter方法需要自己实现。 既然有关键字可以生成setter和getter方法的声明,肯定就有关键字可以生成setter和getter方法的实现。 @synthesize: 作用:自动生成getter和setter方法的实现 语法: @synthesize 特性名(属性名) @synthesize做了多少事: 1.生成getter和setter方法的实现 2.还会生成一个私有的成员变量,成员变量名字就是特性名 3.实现的方法里是给自己生成的那个私有成员变量赋值 4.如果想指定给某个成员变量赋值,就在特性名后面加一个 = 成员变量名; 例:@synthesize name = _name; 5.如果你想过滤,就自己重写 批量生成方法的实现: 语法: @synthesize 特性名1,特性名2………………特性名n 批量生成方法的声明: 语法:@property 类型 特性名1,特性名2………………特性名n 例:@property NSString *name,*nickName; 注意:批量生成时,类型必须一致 */ #import <Foundation/Foundation.h> @interface Person : NSObject{ NSString *_name; int _age; } //这样就会给_name封装getter和setter的声明 @property NSString *name; /* -(void)setName:(NSString *)name; -(NSString *)name; */ //这样就会给_age封装getter和setter的声明 @property int age; /* -(void)setAge:(int)age; -(int)age; */ -(void)sayHi; @end // // Person.m // OC第五天 // // Created by iOS001 on 15/11/5. // // #import "Person.h" @implementation Person //@synthesize name;//自动生成了_name的getter和setter的实现 // //@synthesize age;//自动了_age的getter和setter的实现 //@synthesize name = _name;//自动生成了_name的getter和setter的实现 // //@synthesize age = _age;//自动了_age的getter和setter的实现 @synthesize name = _name,age=_age; //批量操作。 -(void)setAge:(int)age{ if (age < 0 || age > 120) { _age = 18; }else{ _age = age; } } /* -(void)setName:(NSString *)name{ _name = name; } -(NSString *)name{ return _name; } -(void)setAge:(int)age{ _age = age; } -(int)age{ return _age; } */ @end
上面的语法是在Xcode 4.4以前的用法,在现在的新版XCode中,可以不这么复杂。
/* @property增强使用: 用法跟之前的@property用法,完全一样 但是,它除了能生成声明以外,还能声明实现 而且,它还能帮我们生成私有的成员变量! 做的事:1.声明属性的setter和getter方法的声明 2.实现属性的setter和getter方法 3.生成一个以特性名加下划线的私有成员变量 注意:如果类的属性中已经存在同名的带下划线的成员变量,那么不会帮我们在生成了,操作的就是这个带下划线的成员变量。 @property封装对象类型 @property不能封装C语言数组 */ #import <Foundation/Foundation.h> @interface Person : NSObject{ NSString *_name; } @property NSString *name; @end #import "Person.h" @implementation Person @end /* 1.@property帮我们生成的实现,是没有任何逻辑过滤,如果我们要过滤,就重写。 2.如果getter方法和setter方法都有重写的话,那么@property不会帮我们生成私有成员变量了。 如果非要两个都重写的话,就自己定义一个成员变量。 */ #import <Foundation/Foundation.h> @interface Person : NSObject{ int _age; } @property NSString *name; @property int age; @end #import "Person.h" @implementation Person -(void)setAge:(int)age{ if(age<0 ||age>150){ _age = 16; }else{ _age = age; } } -(int)age{ return _age; } @end #import <Foundation/Foundation.h> #import "Person.h" int main(int argc, const char * argv[]) { @autoreleasepool { Person *p = [Person new]; p.name = @"草稚京"; p.age = -30; NSLog(@"p.name=%@ p.age=%d",p.name,p.age); } return 0; }
三、动态类型和静态类型的概念
/* 动态类型: 简单来说就是编译的时候,并不知道具体是什么类型的对象,直到运行的时候才知道是什么类型的对象 所以,运行的时候确定了对象的类型,然后才会去找到对应的类型里看有没有这个方法,如果有这个方法就执行,没有这个方法就报错 静态类型: 就是编译的时候就知道是什么类型了 比如说 int char float double int nums[10]; 一旦定义就不可能再调用char *数组,或者改成char *数组 动态类型的好处: 1.灵活 2.为了实现多态 */ //比如说我现在有3个类,Person、Student、Teacher,三个类中都有SayHi方法。Student和Teacher是继承并重写了Person类中的SayHi方法。 #import <Foundation/Foundation.h> #import "Person.h" #import "Student.h" #import "Teacher.h" int main(int argc, const char * argv[]) { @autoreleasepool { /* Person *p = [Person new]; //动态类型 // [p study];//因为编译阶段,它并不知道你是什么对象,所以不可能让你调用这个方法 [p performSelector:@selector(study)];//因为这是一个运行时才绑定的方法 */ /* printf("请输入序号--0--Person 1--Student 2--Teacher\n"); int num; scanf("%d",&num); Person *p; switch (num) { case 0: p = [Person new]; break; case 1: p = [Student new]; break; case 2: p = [Teacher new]; break; default: p = [Person new]; break; } [p sayHi];//现在编译器知道是哪个对象的sayHi吗???只能运行时才知道?? */ } return 0; }
四、id类型的使用
/* 动态类型: 在编译的时候并不知道具体是什么类型的对象,在运行的时候知道是什么类型的对象。 NSObject:根类。所有的人直接或者间接的继承自NSObject。 两个检查: 编译期检查: 因为OC是动态类型语言,那么也就是说类型只有在运行的时候才会被确定,所以在编译的时候没法检查真正指向的对象是类型,所以更加没法知道你指向的对象类型是否包含某个方法 检查范围:1.去检查它的指针类型里面是否有这个成员(属性、方法),如果有就让你编译通过,如果没有 2.就去父类的父类找,如果有,就编译通过,如果没有,再继续找 3.直到找到NSObject都还没有的话,就报错。 一句话概括:就是只会去赋值号左边的类型里找。 运行期检查: 就是运行的时候类型就确定了,所以就可以检查真正的那个对象里面包不包含这个成员了。 1.先去真正的对象类型里面找,如果找到就执行,如果没找到就继续往父类找。 2.去父类找,找到就执行,没找到就继续往父类的父类找,找到就执行,没找到继续往上找。 3.直到找到NSObject还没有,那么就程序运行的时候崩掉(闪退)。 我希望有一种类型的指针可以指向我任意的对象,并且,我用中括号的方式来调用方法,又不会经过编译期检查(编译时不会报错),有没有呢? id类型: 其实就是一个万能指针,所以任何对象都可以指向。 非常方便的体现什么叫动态类型。 任意对象都可以指向! 好处:灵活,任意类型都可以指向,哪怕上一步指向Student对象,下一步可以立即再指向NSString 缺点:因为编译器没有检查类型有没有方法,所以如果直接调用可能会报错 id是万能指针,任意类型的对象都能指向. 然后用id类型的变量可以调用方法,但是编译器不一定会报错,只能在运行时如果没有才报错。 id需要注意的地方: 1.用id类型指向的对象,不能用点语法 2.那能不能直接调用方法来赋值呢?可以!能不能用方法取值?也可以! id的作用: 1.指向任意类型 2.当做参数传递 注意:虽然任意类型的对象都可以传,但是运行时可能会报错 3.当做返回值返回 注意:任意类型都可以接收,那么很可能运行时会出问题 */ #import <Foundation/Foundation.h> #import "Person.h" #import "Student.h" int main(int argc, const char * argv[]) { @autoreleasepool { /* Person *p = [Student new]; //动态类型,只有运行的时候才直到指向的是Student对象 //[p sayHi];//编译器的时候也是去Person找,只不过正好这时候Person有 [p study]; //此时会报错,因为这时候是编译期检查,像这种中括号调用方法,都是编译期检查 */ /* id p = [Student new]; [p study]; //有没有经历运行期检查??肯定有! [p sayHi]; NSLog(@"%@",p); p = @"哈哈哈哈哈哈"; NSLog(@"%@",p); p = [Person new]; */ id p = [Person new]; [p study];//运行的时候才知道是Person类型,但是Person及其父类都没有study方法,所以运行报错 /* [p performSelector:@selector(test)]; //编译的时候没法检查 //[p sayHi];//而是先在真正指向的对象——student里面先找,找到了就执行 // NSLog(@"%@",p); // // [p study]; //Xcode编译的时候只会根据你的指针类型来检查,而并不会根据你具体指向的对象类型来检查 // // NSObject *obj = [Student new]; // // NSLog(@"%@",obj); // */ } return 0; }
五、动态类型和动态方法的检测以及响应方法的方法
1、态类型的检测
/* 动态类型检测:我们自己写代码来检测对象是否属于一个类。 因为我们之前用id类型是可以调用任意方法的,编译器不会报错,但是运行时报错。 所以我们就在想,能不能在调用方法之前,我们自己写代码判断一下id类型指向的对象是不是某一个类,如果是我才让你调用。 动态类型检测的方法: 1) 语法:[对象 isKindOfClass:Class]; 注意:这个方法是用来检测一个对象是不是右边这个类的对象,或者是这个类子类的对象,如果是则返回YES,如果不是返回NO 例: [p isKindOfClass:[Person class] ]; 解释:判断p这个对象是不是Person类的对象,或者是Person子类的对象,如果是返回YES,如果不是返回NO。 注意:包括子类的子类,子类的子类的子类………………………… 2) [对象 isMemberOfClass:Class]; 判断 对象 是不是某个类的对象,不包括子类。 跟上面的区别只在于,判断的时候不包括子类。 总结:isKindOfClass不能确切的确定是哪一个具体的类的对象(因为还能包括子类)。 isMemberOfClass 可以确切的知道是不是某一个类(因为不包括子类)。 3) 注意,接下来是类方法 [类名 isSubOfClass:Class]; 判断某一个类,是否是另外一个类的子类,包括子类的子类,以及自己类。 也就是说一个类如果是右边这个的类的本类,或者子类或者子类的子类都会返回YES,否则返回NO。 */ #import <Foundation/Foundation.h> #import "Person.h" #import "Student.h" #import "GoodStudent.h" int main(int argc, const char * argv[]) { @autoreleasepool { /* id obj = [Person new]; //我们能不能在调用这个方法之前,自己写代码判断一下,obj是不是Student类型,如果是,我就让你调用,不是,就 //[obj study];//编译不报错,运行报错 if([obj isKindOfClass:[Student class]]){//判断obj是不是student类型 //如果是就执行 [obj study]; }else{ NSLog(@"obj不是Student类型"); } */ //isKindOfClass的用法 /* // id obj = [Person new]; // BOOL res = [obj isKindOfClass:[Student class] ]; // // NSLog(@"%d",res); //0 // BOOL res = [obj isKindOfClass:[Person class] ]; // // NSLog(@"%d",res); //1 // BOOL res = [obj isKindOfClass:[NSObject class] ]; // // NSLog(@"%d",res);//1 id obj = [GoodStudent new]; BOOL res = [obj isKindOfClass:[Person class]]; NSLog(@"%d",res);//也是1 */ //isMemberOfClass的用法 /* id obj = [GoodStudent new]; BOOL res1 = [obj isKindOfClass:[Person class] ];//这种方法包括子类 BOOL res2 = [obj isMemberOfClass:[Person class] ];//这种方法不包括子类 NSLog(@"res1=%d res2=%d",res1,res2);//1 0 */ //isSubOfClass的用法(类方法) /* // BOOL res = [Student isSubclassOfClass:[Person class]]; // // NSLog(@"%d",res);//1 // BOOL res = [GoodStudent isSubclassOfClass:[Person class]]; // // NSLog(@"%d",res);//1 // BOOL res = [Person isSubclassOfClass:[Person class]]; // // NSLog(@"%d",res);//1 //这一句是问Person是Student子类吗? BOOL res = [Person isSubclassOfClass:[Student class]]; NSLog(@"%d",res);//0 */ } return 0; }
2、动态方法的检测
/* 因为之前用id类型调用方法,可能会出错,所以我们想先判断一下这个类型是不是拥有某个方法。 之前的做法是,判断一下某个对象是不是某个类的。那能不能具体一点,直接判断一下某个对象有没有某个方法呢? 动态方法检测! 1.(重点) [对象 respondsToSelector:SEL]; 例:[obj respondsToSelector:@selector(study)] 这是在判断obj有没有study这个方法,如果有返回YES,如果没有返回NO 注意:如果传入的是类方法,那么会返回0,因为类方法不属于对象 2.类方法(用的少) [类名 instancesRespondToSelector:SEL];//判断类方法的 判断某个类是否有某个对象方法,如果有则返回YES,否则返回NO 跟上面效果一样,只不过这是一个类方法用类名判断 */ #import <Foundation/Foundation.h> #import "Person.h" #import "Student.h" #import "GoodStudent.h" int main(int argc, const char * argv[]) { @autoreleasepool { /* id obj = [Student new]; //[obj study];//是不是要在这之前判断一下obj指向的对象有没有study这个方法,如果有才调用 if( [obj respondsToSelector:@selector(study)] ){ [obj study]; }else{ NSLog(@"没有study方法"); } */
//respondsToSelcetor的用法 /* id p = [Person new]; BOOL res = [p respondsToSelector:@selector(clsPerson)]; NSLog(@"%d",res);//0 */ // id obj = [Person new]; // BOOL res = [Person instancesRespondToSelector:@selector(sayHi)];//只能判断对象方法 // NSLog(@"%d",res); } return 0; }
3、响应方法的方法
/* 响应方法就叫调用方法。 以前我们用[] 现在我们用响应方法的方法: [对象 performSelector:SEL]; 调用某个方法,是没有参数的时候 例: [obj performSelector:@selector(sayHi)]; [对象 performSelector:SEL withObject:id]; 调用某个方法,是有1个参数的情况 例:[obj performSelector:@selector(sayName:) withObject:@"刘德华"]; //最多只有2个参数 [obj performSelector:(SEL) withObject:(id) withObject:(id) 这是有两个参数的情况 例:[obj performSelector:@selector(sayName:andNickName:) withObject:@"发哥" withObject:@"花卷"]; */ #import <Foundation/Foundation.h> #import "Person.h" int main(int argc, const char * argv[]) { @autoreleasepool { id obj = [Person new]; //这个是没参数的时候 // [obj performSelector:@selector(sayHi)]; // if([obj respondsToSelector:@selector(sayName:)]){ // //这个是有1个参数的时候 // [obj performSelector:@selector(sayName:) withObject:@"刘德华"]; // }else{ // NSLog(@"没有这个方法"); // } if([obj respondsToSelector:@selector(sayName:andNickName:)]){ //这个是有1个参数的时候 [obj performSelector:@selector(sayName:andNickName:) withObject:@"发哥" withObject:@"花卷"]; }else{ NSLog(@"没有这个方法"); } } return 0; }
六、构造和重写
1、构造
/* 创建一个对象: [类名 new]; new关键字做了三件事: 1.开辟堆空间(相当于创建对象) 2.初始化对象 3.返回对象(返回堆空间首地址) 其实new关键字它并不能开辟堆空间和初始化对象 而是调用了两个方法,一个是alloc,一个是init alloc开辟堆空间(创建对象),然后会返回这个对象 会调用这个alloc返回的对象的init方法,就是初始化对象的 init方法又会返回这个对象(堆空间地址) 构造方法是什么? 构造方法其实就是初始化对象的方法,也就是说init就是我们的构造方法 [Person new] 相当于 [ [Person alloc] init ]; */ #import <Foundation/Foundation.h> #import "Person.h" int main(int argc, const char * argv[]) { @autoreleasepool { Person *p = [Person new]; //创建了一个person对象并且初始化 //其实上面的语句类似于下面的步骤 // Person *p2 = [Person alloc];//创建对象 // Person *p3 = [p2 init];//初始化对象 //上面两句可以合并到一句 Person *p2 = [[Person alloc] init]; } return 0; }
2、重写
/* init方法会帮我们初始化对象为0,对象类型初始化为nil。 因为默认的只会帮我们初始化为0,我想以后创建对象,都是初始化为我指定的值。 那么就需要重写init方法,因为init方法,因为父类是默认初始化为0,我重写就自己重新赋值就好了。 重写init方法必须符合写法规范 方法体要遵从以下规则: if( self = [super init] ){ //做想初始化的事 } return self; 因为有些属性是父类里面提供初始化的,所以一定要加[super init],因为初始化的时候可能会未知原因出错,所以需要判断一下。 instancetype是什么? 之前可以用id作为返回值,但是不安全,因为我可以返回任意类型。 比如说:我其实想返回一个Person对象,但是你给我返回一个OC字符串。。。。 那么编译并不会报错,但是运行时可能出现严重的问题! 所以,问题来了:有没有一种东西,可以让我返回类型,并且安全:意思就是你返回的对象一定是我这个类的。 就要用instancetype instancetype还可以代表返回的是本类或者子类 注意:instancetype只能做方法的返回值,不能做参数,也不能做类型来定义 总结:instancetype主要是用来返回对象的,而且返回的是本类或子类的对象 */ #import <Foundation/Foundation.h> #import "Person.h" #import "Student.h" int main(int argc, const char * argv[]) { @autoreleasepool { /* Person *p = [Person new]; [p sayHi];//null 0 */ // Person *p = [Person dedaoPersonInstance]; // // [p sayHi]; // // //这里得到的究竟是Person呢还是Student?? // Student *s = [Student dedaoPersonInstance]; // //Person *p = [[Person alloc] init];//因为init要返回一个对象,所以用instancetype,因为子类也可能要通过int方法返回对象 Student *s = [[Student alloc] init]; [s sayHi]; } return 0; } //在Person类的实现类中 +(instancetype)dedaoPersonInstance{ Person *p = [self new]; //当然可以,谁调用就是谁,self就代表Person return p; // return @"哈哈哈哈"; } /** * 重写构造方法 * 返回初始化的对象 */ -(instancetype)init{ if(self = [super init]){ //现在初始化为以下的值了 _name = @"刘大同"; _age = 16; } return self; }
//在Student实现类中 #import "Student.h" @implementation Student -(instancetype)init{ if(self = [super init]){ _stuNo = 1; } return self; } -(void)sayHi{ NSLog(@"name=%@,age=%d,id=%d",_name,_age,_stuNo); } @end
3、自定义构造方法
/* 之前重写构造方法,初始化的值都已经固定写死了,不灵活了 如果我们想一个对象初始化的时候按照我给的值来初始化,怎么办? 就要自定义构造方法,自定义的构造方法里面要给参数 步骤: 1.是一个对象方法 2.以initWith开头,后面接XXXXX 3.有参数(几个参数,根据你想初始化几个值确定) 4.有返回值,类型是instancetype 注意:方法体里面也要遵从规范: if(self = [super init]){ //初始化的语句 } return self; */ #import <Foundation/Foundation.h> #import "Person.h" int main(int argc, const char * argv[]) { @autoreleasepool { // Person *p = [[Person alloc] init]; // // [p sayHi]; //一定要会用,以后也会经常用到 Person *p = [[Person alloc] initWithName:@"孙悟空" andAge:99]; [p sayHi]; } return 0; } #import "Person.h" @implementation Person -(instancetype)initWithName:(NSString *)name andAge:(int)age{ if(self = [super init]){ _name = name; _age = age; } return self; } +(instancetype)dedaoPersonInstance{ Person *p = [self new]; //当然可以,谁调用就是谁,self就代表Person return p; // return @"哈哈哈哈"; } /** * 重写构造方法 * 返回初始化的对象 */ -(instancetype)init{ if(self = [super init]){ //现在初始化为以下的值了 _name = @"刘大同"; _age = 16; } return self; } -(void)sayHi{ NSLog(@"name=%@ age=%d",_name,_age); } @end