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
");
        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
原文地址:https://www.cnblogs.com/bobo-pcb/p/5054851.html