Objective-C 学习笔记(Day 3,上)

时间:2023-03-08 18:01:46

———————————————————————————————————————————

类方法





  ①类方法:

 

     + 开头的方法(定义的过程形式和对象方法一样,只不过 + 开头,这是唯一的区别)

 

  类方法的调用:

 

     [类名   方法名];

 

  ②对象方法:



     - 开头的方法



  对象方法的调用:



    [实例对象名   方法名];

 

  ★ 代码对比学习:

 

    有一个 Dog类,在里面有这么一个对象方法:

 

     -(void)run;  //对象方法

 

     想调用 run方法,

 

     Dog *dog = [Dog new];

     [dog run];

 

 

    在 Dog类 里面还存在另外一个类方法:

     

     +(void)run;  //类方法

     

     想调用 run 方法,



     [Dog run];





★★★ 在上面的描述中,我们可以看到,两个方法的名称都是  run ,但是只是前面的符号不同,一个是  +  ,另一个是  -  ,这样子的写法是可以的,他表示的是不同的方法,我们可以形象的看作两个方法的“性别”差异。对象方法需要创建实例对象进而调用,但是类方法却可以直接通过类名调用。类方法的好处就是不需要给他分配堆内存(因为类方法调用不需要创建空间),节省了内存空间。





———————————————————————————————————————————

类方法使用注意事项



①类方法 可以和 对象方法(实例方法) 同名,并不互相影响使用。

②类方法 可以从 父类 继承而来,子类可以重写类方法。

③类方法 和 对象方法 一样从 @interface…@end 里面声明,在 @implementation…@end 里面实现。

④类方法 只能类名调用,实例对象名调用是不可以的。 对象方法 也是这样,对象方法只能被 实例对象名调用,而不能被类名调用。

⑤在类方法里使用了self,这个self执行的是类对象(class object)而不是实例对象(instance object)



例子:(这里为了方便观察,我将头文件和源文件写在了一起)



#import <Foundation/Foundation.h>



@interface Caculator : NSObject

-(int)add:(int)num1 andNum2:(int)num2;

+(int)add:(int)num1 andNum2:(int)num2;

@end



@implementation Caculator

-(int)add:(int)num1 andNum2:(int)num2

{

    return num1+num2;

}

+(int)add:(int)num1 andNum2:(int)num2

{

    return num1+num2;

}

@end



int main(int argc, const char * argv[]) {

    @autoreleasepool {

        Caculator *caculator=[Caculator new];

        int result1=[caculator add:12 andNum2:13];

        

        int result2=[Caculator add:22 andNum2:15];

        

        NSLog(@"%d,%d",result1,result2);

    }

    return 0;

}





分析:上面是一个Caculator类,然后里面分别写了一个类方法和一个对象方法,显然除了方法名头上标识它们到底是什么方法的符号外,这两个方法是同名的(方法名都是 add: andNum2:),我们用一个实例对象和类名分别调用这两个方法,显然,是正确的,也没有互相影响。



然后我们再在上面的方法声明中添加一个对象方法(上面没有罗列,需要读者自己去写),如:    -(int)jian:(int)num1 andNum2:(int)num2;   这个我们认为是一个减法的对象方法。(实现部分自己写) 然后我们用类名去调用它:      [Caculator jian:14 andNum2:12];

显然这是错误的,系统报错: No known class method for selector ‘jian:andNum2:' 。这句话告诉我们,系统不知道有名为 ‘jian:andNum2:’ 的类方法(class method)。





———————————————————————————————————————————

类方法的调用和要点汇总



这一部分,设计并验证了类方法的调用这个知识点。

首先我们应该清楚,类方法是灵活的,我们设计了两个大的方面去研究类方法的调用。



(1)★对于同一个类而言★

①对象方法的内部可以调用类方法(对—>类)

②类方法的内部可以去调用同类的另外一个类方法(类—>类)

③类方法的内部可以调用对象方法(类—>对)



(2)★对于不同的类而言★(举例两个类:A类和B类)

①A的对象方法的内部可以调用B的类方法(对—>类)

②A的类方法的内部可以调用B的类方法(类—>类)

③A的类方法的内部可以调用B的对象方法(类—>对)



下面为了验证以上的6条,我设计并实现了下面的程序。(这个程序将两个类(Car和Dog)的声明和实现都写在了一起,方便观察)



#import <Foundation/Foundation.h>



@interface Car : NSObject

-(void)c1;

+(void)c2;

+(void)c3;

+(void)test:(Dog *)dog;

@end



@implementation Car

-(void)c1

{

    NSLog(@"这里是Car的对象方法c1。我们会在下面调用Car的其中一个类方法c2。");

    [Car c2];

}

+(void)c2

{

    NSLog(@"这里是Car的类方法c2。我们会在下面调用Dog的类方法d1");

    [Dog d1];

}



+(void)c3

{

    NSLog(@"这里是Car的类方法c3。我们会在下面调用Dog的对象方法d4");

    Dog *dog11=[Dog new];

    [dog11 d4];

}



+(void)test:(Dog *)dog

{

    [dog d2];

    

    Dog *dd=[Dog new];

    [dd d2];

}

@end



@interface Dog : NSObject

+(void)d1;

-(void)d2;

+(void)d3;

-(void)d4;

@end



@implementation Dog

+(void)d1

{

    NSLog(@"这是Dog的类方法d1。我们会在下面调用Dog的类方法d3");

    [Dog d3];

}

-(void)d2

{

    NSLog(@"这是Dog的对象方法d2。我们会在下面调用Car的类方法c3");

    [Car c3];

}

+(void)d3

{

    NSLog(@"这是Dog的类方法d3。我们会在下面调用Dog的对象方法d2");

    Dog *dd1=[Dog new];

    [dd1 d2];

}

-(void)d4

{

    NSLog(@"这里是Dog的对象方法d4。");

}

@end



int main(int argc, const char * argv[]) {

    @autoreleasepool {

        Car *car=[Car new];

        [car c1];   //★★★断点设置处★★★

    }

    return 0;

}





上面的程序读者可以拷贝去验证一下,整个流程是这样的:



调用Car的对象方法(c1)    在方法内      调用Car的类方法(c2)

调用Car的类方法(c2)    在方法内      调用Dog的类方法(d1)

调用Dog的类方法(d1)    在方法内      调用Dog的类方法(d3)

调用Dog的类方法(d3)    在方法内      调用Dog的对象方法(d2)

调用Dog的对象方法(d2)    在方法内      调用Car的类方法(c3)

调用Car的类方法(c3)    在方法内      调用Dog的对象方法(d4)



我们可以将断点设置在main函数中合适位置,然后Step into去一步一步的来看整个程序的流程,观察跳跃的过程。便于理解。





再说一点,因为我们这里学习的是类方法的调用,所以关于“在A的对象方法 内部调用 A的另一个对象方法”(对—>对)和“在A的对象方法 内部调用 B的对象方法”(对—>对)这两个情况我们就不做验证了,当然结果也是一定的,同样可以互相调用。有兴趣的同学可以去验证一下。





★★★另外说明一下这些情况:



★//对象方法中可以调用其他的对象方法

    // (1)在当前对象方法内部创建对象(可以是当前类实例对象,也可以是其他类的实例对象),使用新创建的对象调用对象方法

    // (2)可以使用self

    // (3)对象作为方法的参数传递过来,可以使用传递过来的对象调用方法



★//在类方法中可以调用其他类方法

    //(1)可以直接使用本类类名(或者其他类名)调用类方法

    //(2)可以使用self

(另外,在类方法中不允许访问实例变量(也就是不允许出现类的声明中属性成员),如下图)

Objective-C 学习笔记(Day 3,上)

_speed是Car类声明的一个属性成员,这是不允许出现在类方法中的。因为调用类方法没有分配存储空间,堆区是空的,所以没法存储值,所以不能访问。



★//在类方法中可以调用对象方法

    //(1)对象作为方法的参数传递过来

    //(2)可以创建一个对象再调用

(如下图)

Objective-C 学习笔记(Day 3,上)

最后一点需要说明的,那就是类方法不能调用自身,否则就会出现死循环(无法靠自身控制终止循环)。(如下图,在类方法 t1 的内部再次调用了 t1 ,那么一旦在main函数中 [Car t1];   ,那么循环就不会终止)

Objective-C 学习笔记(Day 3,上)

我还去验证了一下对象方法是否能在内部去调用自身,会出错,这个很无聊,大家不用去验证了~

———————————————————————————————————————————

类方法的一个应用(IPhone 颜色)



#import <Foundation/Foundation.h>

typedef enum {kColorWhite,kColorBlack,kColorTHJ} Color;



@interface IPhone : NSObject

{

    @public

    Color _color;

}



+(NSString *)toGetIPhoneColor:(Color)color;

//返回值类型需要是 字符串类型



@end



@implementation IPhone

+(NSString *)toGetIPhoneColor:(Color)color

{

    NSString *str;//我们需要一个字符串变量来接收返回的值

    //这里用了 switch语句 用来分别判断代码表示的什么颜色,然后转化为汉字字符串返回

    switch (color) {

        case kColorWhite:

            str=@"白色";

            break;

        case kColorBlack:

            str=@"黑色";

            break;

        case kColorTHJ:

            str=@"土豪金";

            break;

        default:

            break;

    }

    return str;

}

@end



int main(int argc, const char * argv[]) {

    @autoreleasepool {

        

        //我们需要显示手机颜色的一个方法,所以这个方法只需要往里面传值就可以了,我们需要把我们定义的枚举类型的值转化为能让人理解的汉字去表示颜色。这个地方不需要创建对象,所以要用类方法。我们调用类方法去接收并处理一个颜色,最后返回一个字符串类型的值,然后用一个字符串变量去接收这个值,然后输出这个值,这就是整个思路

        NSString *str=[IPhone toGetIPhoneColor:kColorTHJ];

        NSLog(@"%@",str);

    }

    return 0;

}





———————————————————————————————————————————

 匿名类的概念及使用



#import <Foundation/Foundation.h>

#import "Car.h"

int main(int argc, const char * argv[]) {

    @autoreleasepool {

        //①使用匿名类可以调用方法

        [[Car new] start];//使用匿名类可以调用方法(其实这里理解起来可能有些别扭,这里为什么叫做匿名类而不是别的。我们可以暂时这样理解:之前我们见到new的时候是创建实例对象的时候,而现在我们申请内存空间了也初始化了,但是却没有实例对象,所以不能叫匿名对象,那么这个东西在类里面出现的,就暂且叫为匿名类吧。new是一个方法)

        

        [[[Car alloc]init]start];//这句话等价于上面的 [[Car new] start];  ,我们知道new做了两个事情,一个是分配存储空间(alloc),另外一个就是初始化(init)。new是alloc和init的结合,平时因为方便,我们一直用new,其实两种写法都一样。

        [[[Car alloc]init]stop];

        

        //②使用匿名类可以访问实例变量(成员变量)

        [Car new]->_speed=100;

        NSLog(@"speed:%d",[Car new]->_speed);

        //输出 speed:0

        //对于用匿名类访问实例变量(成员属性),★只能访问一次★,我们第一次访问设置_speed值为 100 ,但是输出的时候相当于又访问了一次,此时没有设置值,所以就输出系统自动初始化的值,也就是 0

    }

    return 0;

}



//  匿名类是不能有名字的类,它们不能被引用,只能在创建时用New语句来声明它们。匿名类的声明是在编译时进行的,实例化在运行时进行。这意味着for循环中的一个new语句会创建相同匿名类的几个实例,而不是创建几个不同匿名类的一个实例。

//  ★上面这句话说的很好,总结为:编译时声明,运行时实例化!





———————————————————————————————————————————

new 和 alloc/init 的区别



在上面一小节我们对new有了新的认识,现在我重点去解释一下这两个的相同于不同。



1.在实际开发中很少会用到new,一般创建对象看到的全是[[className alloc] init]

但是并不意味着你不会接触到new,在一些代码中还是会看到[className new],

还有去面试的时候,也很可能被问到这个问题。

2.那么,他们两者之间到底有什么区别呢

我们看源码:



+ new

{

    id newObject = (*_alloc)((Class)self, 0);

    Class metaClass = self->isa;

    if (class_getVersion(metaClass) > 1)

        return [newObject init];

    else

        return newObject;

}



而 alloc/init 像这样:

+ alloc

{

    return (*_zoneAlloc)((Class)self, 0, malloc_default_zone());

}

- init

{

    return self;

}



通过源码中我们发现,[className new]基本等同于[[className alloc] init];

区别只在于alloc分配内存的时候使用了zone.

它(zone)是给对象分配内存的时候,把关联的对象分配到一个相邻的内存区域内,以便于调用时消耗很少的代价,提升了程序处理速度;



3.而为什么不推荐使用new?

不知大家发现了没有:如果使用new的话,初始化方法被固定死只能调用init.

而你想调用initXXX怎么办?不可能!据说最初的设计是完全借鉴Smalltalk语法来的。

传说那个时候已经有allocFromZone:这个方法,

但是这个方法需要传个参数id myCompanion = [[TheClass allocFromZone:[self zone]] init];

这个方法像下面这样:



+ allocFromZone:(void *) z

{

    return (*_zoneAlloc)((Class)self, 0, z);

}



//后来简化为下面这个:

+ alloc

{

    return (*_zoneAlloc)((Class)self, 0, malloc_default_zone());

}



但是,出现个问题:这个方法只是给对象分配了内存,并没有初始化实例变量。

是不是又回到new那样的处理方式:在方法内部隐式调用init方法呢?

后来发现“显示调用总比隐式调用要好”,所以后来就把两个方法分开了。



★★★概括来说,new和alloc/init在功能上几乎是一致的,分配内存并完成初始化。

差别在于,采用new的方式只能采用默认的init方法完成初始化,

采用alloc的方式可以用其他定制的初始化方法★★★





———————————————————————————————————————————

版权声明:本文为博主原创文章,未经博主允许不得转载。