ObjectiveC-04-类的创建以属性、方法定义详细

在本小节中,笔者会详细讲解下ObjC的类的相关内容,包括创建、构造、方法、属性以及属性读取等相关知识,先来看下类的组成:

在这里插入图片描述

类的创建

ObjC是在C语言基础上扩展的,在编写OS软件时可以混用两种语言。但它们之间是有区别的,C是面向过程的语言,而ObjC则是定位成一种OOP语言,所以本文档中基本是以ObjC语言为主,少量的会用C来编写(原因是C的性能会好一些)。

在objc中只要看到@符号,就可以把它看成是对C语言的扩展;

此处先讲一点ObjC面向对象的代码编写知识,后续会再详细描述OOP内容。OOP语言中的对象一般统称为类,ObjC也不例外,定义一个ObjC类时比java语言要麻烦一些,ObjcC类需要以下部分的组合;

在Xcode中新建类的方法如下,右键选择项目文件夹,然后选择 New File,比如创建一个名为Fraction的类。

在这里插入图片描述
创建完成生会生成两个源文件,这种源码格式需要适应,后面还有一种更简单的方式,会在本小节最后面介绍下。
在这里插入图片描述

类对象声明@interface

用关键字@interface来定义,这个关键字定义新类时首先需要告诉编译器类此类来自何处,这种设计方式是一个好的习惯,尤其是在一些更高级的语言中比如java不会限制源文件的名称必须一致,ObjC语言所有类的超类是NSObject,如果无特殊设计,一般自定义的类都会继承NSObject。然后在此接口中就可以定义属性和方法了,语法格式如下:

/*语法格式*/
#import <Foundation/Foundation.h>

@interface newClassName: parentClassName{ //一般为NSObject
     //propertyDeclarations
}
    //methodDeclarations
@end // newClassName

demo示例,下列代码中的NS_ASSUME_NONNULL_BEGIN是可有可无的,其它的符号会在后续详细说明。

//demo示例
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Fraction : NSObject
{//属性定义
@private
    int size;
    int length;
}

//方法定义,此处通常是顶格写的
- (void) print;
- (void) setNumerator: (int) n;
- (void) setDenominator: (int) d;

@end

NS_ASSUME_NONNULL_END

类对象实现@implementation

类实现用@implementation关键字来指定,它与interface是配套的。语法如下:

//语法格式
@implementation newClassName{
     memberDeclarations; //类变量
}
    methodDefinitions; //方法实现
@end

demo示例
@implementation Fraction {
    int numerator;
    int denominator;
}
- (void) print {
    NSLog(@"%i / %i", numerator, denominator);
}

- (void) setNumerator:(int) n {
    numerator = n;
}

- (void) setDenominator:(int) d {
    denominator = d;
}
@end

需要注意:

  1. 定义@implemention与@interface的文件名称相同;
  2. implementation可以不实现全部的interface中定义的函数声明,但未实现的方法调用在运行时会报异常;
  3. 如果在.m中选择性的实现在.h中定义的方法,在调用时可以使用respondsToSelector等函数来进行安全检查;

类对象调用

此示例中我们用main函数来调用上述自定义的类对象

#import "Fraction.h"
int main(int argc, char* argv[]){
    @autoreleasepool {
        Fraction *myFraction; //表明myFraction是一个对象的引用(指针)
        myFraction = [Fraction alloc]; //alloc表示要分配新的内存空间,返回存储数据的引用
        myFraction = [myFraction init]; //初始化类
        
        //上述三行代码可以合并为一行代码,即 Fraction *myFraction = [[Fraction alloc] init]

        [myFraction setNumerator:1]; //函数调用
        [myFraction setDenominator:3];

        [myFraction print];
    }
    return 0;
}

也可以改成如下的简单测试方法调用

#import "Fraction.h"

void fractionTest(){
    Fraction *fraction = [[Fraction alloc] init];
    [fraction setNumerator:1];
    [fraction print];
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        fractionTest();
    }
    return 0;
}

构造函数

类可以使用new和alloc init的方式来初始化对象,推荐后者, 在NSObject提供的init函数用于类对象的初始化,类似于java中的构造函数的概念,所以默认所有的自定义对象都有一个init方法(可以覆写)。

//alloc申请一块内存空间,同时也会设置一些属性的默认值保证类可用
//init可以有多个用于初始化类属性
Fraction *fraction = [[Fraction alloc] init];

覆写默认构造函数

(instancetype) init{  //instancetype也可换成id
    if(self = [super init] }{ //调用父类的初始化方法
        //个性化代码
        NSLog(@"FRACTION INIT");
    }
    return self; //返回实例对象
}

自定义多构造函数

同时也支持多构造函数,比如Foundation框架中提供的NSArray类。

- (instancetype)initWithString:(NSString *)aString;
- (instancetype)initWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2);
- (instancetype)initWithFormat:(NSString *)format arguments:(va_list)argList NS_FORMAT_FUNCTION(1,0);

//用myInitArrayValue变量初始化,即initWithArray是个有参数的方法
NSArray *myArray = [[NSArray alloc] initWithArray:myInitArrayValue];

自定义的类也可以仿写成NSArray那样类似initXXX的初始化方法,如下:

@interface Fraction:NSObject
- (instancetype) initWith: (int)n;
- (Fraction *) initWith: (int)n;
@end
//覆写超类的初始化代码,这块代码可不写
@implemention Fraction
(instancetype) init{
    if(self = [super init] }{ //调用父类的初始化方法
        //个性化代码
        NSLog(@"FRACTION INIT");
    }
    return self; //返回实例对象
}

//方式1:
- (Fraction *) initWith: (int)n {
    self = [super init]; //此处也可以调用其它的初始化方法
    if(self){
        [self setTo:n over:3]; //执行初始化方法
    }
    return self;
}

//方式2:
- (instancetype) initWith: (int)n {
    self = [super init]; //此处也可以调用其它的初始化方法
    if(self){
        [self setTo:n over:3]; //执行初始化方法
    }
    return self;
}
@end

//测试程序
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Fraction *a = [[Fraction alloc] initWith:3];
    }
    return 0;
}

上述代码中初始化方法的返回值一般会用(id)或(instancetype)来代替,id是一个关键字可简单认为它是一个通用的对象类型,类似泛型的概念,所以一般会用改成如下写法:

- (id) initWith: (int)n {
    self = [super init]; 
    if(self){
        [self setTo:n over:3]; //执行初始化方法
    }
    return self;
}

属性定义

全局变量 extern 关键字

定义在类声明和类实现部分都可以(这样本类和其它类全可以访问了),但一般会定义在类声明部分,下面是一个例子说明在Fraction.m中定义一个全局变量,需遵循两点规定:

  1. 在所有的方法和函数之外声明;
  2. 一般变量名为g开头,表示这是一个全局变量;
#import "Fraction.h"

//全局变量
int gGlobalVar = 5; 

int main(int argc, const char * argv[]) {
    NSLog(@"%i", gGlobalVar);
}

这种全局变量一般用于跨类使用,如果在其它文件中使用,不需要import引用就可以直接用,但需要用到extern关键字声明一下,它表示告诉编译器要访问其它文件中定义的全局变量,比如在另一个类Foo中访问

- (instancetype) init {
    self = [super init];
    if(self){
        NSLog(@"FRACTION INIT");
        extern int gGlobalVar;
        NSLog(@"FRACTION INIT %i", gGlobalVar); //5
    }
    return self;
} 

一个好的实践是把全局变量定义在一个单独的类中或定义的main.m函数中;

不可变变量 const 关键字

有此修饰的变量的值不能被改变,相当于java中的final的作用。

        const char *words[4] = {"java", "c", "objc"};
        for (int i=0; i<sizeof(words); i++){
            NSLog(@"%s is %lu length", words[i], strlen(words[i]));
        }

静态变量 static 关键字

ObjC中的static变量是和java不太一样,在ObjC中没有static只能在static方法中使用的限制。ObjC中的静态变量主要是为了弥补全局变量的安全缺陷而增加的,因为全局变量作用域太大,如果只希望在本类中使用全局变量,则可以在原基础上加上static来修饰。

另外一个用处就是用static修饰的变量的值不会被释放,其属于常驻内存的一个常量。

#import "Fraction.h"

//作用域只即于本类的全局变量
static int gGlobalVar = 5; 

int main(int argc, const char * argv[]) {
    NSLog(@"%i", gGlobalVar);
}

私有变量 @private 标识

除以使用上述private标识,还可以使用public, protected, package这三个,这些其实并不是作用域的概念,这些标识和变量的内存位置有关,因为类的成员变量是存储在结构体中的。这些标识指的是能否通过指针的方式直接操作内存的结构体。

@interface Fraction : NSObject
{//属性定义
@private
    int size;
    int length;
}

比如一个Fraction类定义了一个numerator成员,并用@public来修饰,则就可以通过下面的方式来访问这个变量

Fraction *myFract = [[Fraction alloc] init];
myFract->numerator;

方法中的临时局部变量

局部变量其实就是定义在方法体中的变量,因作用域只局限于此方法

- (void) setName{
    int i = 1; //此变量就是局部变量
}

属性存取

类属性的存取就是属性的getter和setter方法,一般不建议直接通过属性名称的方式来设置和读取属性的值,最好封装到一个方法中,这样能获取最大的便利。在ObjC中其有一个特定的规范,比如:

int tire //属性名
- (void) setTire:(int)tire; //设置方法,加set前缀
- (int) tire; //读取方法,不加get前缀

不加get前缀,这是一个不成文的规定,因为Cocoa框架中的API带get前缀的函数表示返回传入参数的指针值。

通用实现

这种实现方式比较通用,但会写大量的代码。在现代的编程中可以使用新的特性,如果是比较老的应用只能用这种方法。

@interface AllWeatherRadial : NSObject
{
	float pressure;
	float treadDepth
}

- (float) pressure;
- (void) setPressure: (float *) pressure;

- (float) treadDepth;
- (void) setTreadDepth: (float *) treadDepth;

@end // AllWeatherRadial end
@implementation AllWeatherRadial

- (float) pressure{
    return pressusre
}
- (void) setPressure: (float *) pressure{
    pressusre = pressusre;
}

- (float) treadDepth{
    return treadDepth;
}
- (void) setTreadDepth: (float *) treadDepth{
    treadDepth = treadDepth;
}
@end // AllWeatherRadial end

使用注解@property

@property是一种新的编译器功能,它意味着声明了一个新对象的属性,编译器还会自己生成getter和setter方法,用这种特性简化上面的代码如下:

#import <Foundation/Foundation.h>

@interface AllWeatherRadial : NSObject

@property float rainHandling;
@property float snowHandling;

@end
#import "AllWeatherRadial.h"

@implementation AllWeatherRadial

/*这两行代码不是必须的,它只是一种get/set预生成器指令,但在Xcode4.5版本后可以不写*/
//@synthesize rainHandling;
//@synthesize snowHandling;

- (NSString *) description{
    NSString *desc;
    desc = [[NSString alloc] initWithFormat: @"AllWeatherRadial: / %.1f / %.1f",
               [self rainHandling],
               [self snowHandling]];

    return (desc);
}
@end

测试代码

#import <Foundation/Foundation.h>
#import "AllWeatherRadial.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        AllWeatherRadial *item = [[AllWeatherRadial alloc] init];
        [item rainHandling];
        [item setRainHandling:2.2];
        NSLog(@"%@", item);
    }
    return 0;
}

@property指令详细

因为@property是一种自动生成代码的指令,所以也需要有一些灵活的配置。比如添加一个内存回收机制,是否允许生成代码等,以下是这引指令常用的修饰符,可多个同时使用。

@property (readonly, nonatomic) NSString *name;
  • weak/strong:引用类型,常用
  • nonatomic:线程不安全,常用
  • atomic:线程安全,系统会生成互斥的getter和setter方法,常用
  • readonly:只读属性,不会生成setter方法
  • readwrite:默认
  • assign:默认编译器
  • nullable:是否可为空
  • retain:保留和内容释放有关
  • null_resettable、null_unspecified:老版本遗留的不太使用了,变量引用相关
  • setter/getter:自定义方法名,如@property (setter=isHidden) BOOL hidden;这样编译器就会自动生成一个名为isHidden的方法做为setter方法;

如果使用了@property指令定义属性,则在实现类中可用以下方式访问

@interface AllWeatherRadial : NSObject
@property float rainHandling;
@end

@implementation AllWeatherRadial

- (void) propertyGetter{
    [self rainHandling]; //调用rainHandling()方法
    self.rainHandling;  //调用rainHandling()方法
    _rainHandling;  //直接读取属性
}
@end

@synthesize指令详细

如果不想用上面带下划线(直接读取属性)的方式调用,就需要用到 @synthesize 关键字重新声明一次。

  1. 有一点需要注意的是,使用这个指令时,属性的名称不要以new、alloc、copy、init开头;
  2. 使用了@property指令后,省略了@synthesize时,则编译器会默认把property声明的属性名称最前面加上下划线,比如name变成_name;
@interface AllWeatherRadial : NSObject
@property float rainHandling;
@end

@implementation AllWeatherRadial
@synthesize rainHandling
- (void) propertyGetter{
    rainHandling;  //直接读取属性,这样就不用前面加_了
}
@end

@dynamic指令

这个指令比较简单,就是阻止自动生成getter/setter方法

#import <Foundation/Foundation.h>

@interface AllWeatherRadial : NSObject

@property float rainHandling;
@property float snowHandling;

@end

//---------------------
@implementation AllWeatherRadial

@dynamic rainHandling;

- (NSString *) description{
    NSString *desc;
    desc = [[NSString alloc] initWithFormat: @"AllWeatherRadial: / %.1f / %.1f",
               [self rainHandling], //这处会报错,因为上面阻止生成了存取方法
               [self snowHandling]];

    return (desc);
}
@end

方法定义

在objc中会区分类方法和实例方法,比如alloc就是一个类方法,而init就是一个实例方法。

  1. -:减号定义实例方法,必须实例化类后才能使用;
  2. +:加号用来定义类方法,相当于static方法,可以不实例化类直接使用;

消息:接收者+方法名+参数三部分组成,在ObjC中方法调用有时称为消息发送,消息的格式其实就是[NSObject alloc]这样的格式,前面是接收者,后面是方法名

语法格式

注意参数的定义格式,参数名称这个东西这点和python比较类似。

无参函数
在这里插入图片描述

有参函数:建议的是第二种格式。
在这里插入图片描述

  1. 有参数时需要在方法名称后加一个冒号:,否则直接以分号或{}结尾即可;多个参数以冒号:相连;
  2. (int) n,必须要指定其类型,参数名称任意,例如:- (int) getNumerator;
  3. ObjC语言中如果参数或变量是一个引用类型,则需要加一个*号,例如:- (NSString *) getNumerator: (NSString *) val;

返回类型:如果有返回值,需要在方法实现的最后加一个return语句;
在这里插入图片描述

返回值

  1. 返回值为基本数据,则直接用 () 包装即可,比如 (int);
  2. 返回值为指针(对象),则需要用( *)包装,比如(NSString *);

示例程序

// 无返回值,无参数
- (void) createLabel{
}

//有返回值 ,无参数
- (BOOL) createLabel{
    return YES;
}

- (NSTextField *) createLabel{
    NSTextField *label = [[NSTextField alloc] initWithFrame:NSMakeRect(10, 10, 280, 20)];
    return label;
}

//无返回值,有一个参数
- (void) formRichLabel:(NSTextField *) richTextLabel{

}

//无返回值,有多个参数,参数名+类型,第一个参数不需要指定参数名
- (void)addAttribute: (NSAttributedStringKey)name 
                      value:(id)value 
                      range:(NSRange)range{
}

//用const关键字,定义为不可变参数
- (void) setTo: (const int) n over:(int) d

事实上,参数名是可选的,如下实现与上述等价,但是如果定义了名称则所有的实现和调用都需要带名称。

//方法定义
- (void) setTo: (int) n :(int) d

私有方法

严格来讲这并不是私有方法,因为ObjC没有私有这一定义,只是为了一些编程方便,设置在实现类中的一些私有方法。多数充当测试和工具类使用。

#import <Foundation/Foundation.h>

NSString *bool2Str(BOOL y){
    if (y == YES){
        return @"yes";
    }else{
        return @"no";
    }
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!\n");
        printf("Hello, World!\n");
        
        NSLog(@"%d = %@", 1, bool2Str(1));
        NSLog(@"%d = %@", YES, bool2Str(YES));
    }
    return 0;
}

类的描述

在上面的程序中,如果通过NSLog()函数可以打印一个类的实例,一般只会打印一个内存地址出来,如果想变成一个可读性更强的文案就需要覆写NSObject对象提供的description方法(此方法和java的toString类似)。

descritpion 方法

在ObjC中NSLog(@"%@)调用的就是对象的description方法,此方法直接在实现中写即可,不需要在接口中定义,因为它是在NSObject中实现的,下面例子演示了自定义类的描述信息的方法,注意下面两种方式实现的不同

+ (NSString *)description{
    return @"aaa";
}
NSLog(@"%@", s);
NSLog(@"%@", [Rectangle description]);

/*
<Square: 0x10110d950>
aaa
*/
- (NSString *)description{
    return @"aaa";
}

Rectangle *s = [[Square alloc] init];
NSLog(@"%@", s);
NSLog(@"%@", [Rectangle description]);

/*
aaa
Rectangle
*/

推荐的方式是声明为 - ,因为它会打印出更多信息

- (NSString *)description{
    NSString *str = [[NSString alloc] initWithFormat:@"size=%d length=%d", size, length];
    return str;
}

/*
size=5 length=6
Rectangle
*/

class 方法

在ObjC中也有和java一样的一个class方法,比如得到当前类的类型:

 [Complex class] //Complex是一个自定义的类
 if([ob1 class] == [ob2 class]) //比较两个类是否是同一类型

sizeof 内存大小

这个方法可以打印当前对象、变量等占用的内存大小,单位为byte。

//基本数据大小
int a[3] = {1,2,4};
sizeof(a);//~~12

//对象大小
sizeof(*myFract); 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

korgs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值