您的位置:首页 > 移动开发 > Objective-C

【Objective-C】类的属性、变量、前向声明

2015-08-08 16:16 337 查看
一、类

1. 类的实例方法:(类的成员变量用于保存每个对象的状态,而类的实例方法则用于向外界提供各种功能、或者执行某个动作)

1.1 选择器:

方法的声明部分就是方法的全名,包含作用范围(实例方法还是静态方法)、返回值类型。方法的名称及参数的类型和参数名。将方法全名中的的作用范围、返回值类型。参数类型和名称去掉,就构成了方法的签名

- (id)initWithName:(NSString *)newName andAge:(int)newAge // 全名

- initWithName:andAge: //这就是签名,其实就是方法名

每一个OC方法都对应一个选择器对象,即SEL类型的对象。OC运行时通过方法对应的选择器来定位其实现的代码。可以通过@selector指令,传入方法签名,即可获取该方法的选择器,如:

SEL initMethodSelector = @selector(initWithName:andAge:);
选择器通常用于指定回调方法。例如我们在触发某个事件后,希望执行某个方法的代码,此时就需要指定改方法的选择器。使用选择器时候要注意输入的方法签名是否准确(冒号一定要注意)。

2. 类的属性

2.1 声明属性

@property (属性的附加特性) 属性类型 属性名字;(OC提供的自动化的机制,节省用户重复写相同的垃圾代码)

//属性声明
@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) int age;     //要是@property int _age; 则会生成 -(void)set_age:xx; 编译器是比较死板的,给什么就生成什么。
属性的附加特性有几种类型:(参考网友)

1、nonatomic:编译器会默认生成额外的代码以保证访问属性是线程安全(使用互斥锁),这会牺牲一定的性能。如果在能够确保线程安全的情况下(例如只有一个线程会访问该属性),则使用nonatomic附加属性能够提升属性的读写速率。

2、readonly:说明属性是只读的,@synthesize指令只会生成get方法而没有set方法。

3、readwrite:说明属性是可读写的,默认值,@synthesize指令会同时生成get方法和set方法。

4、getter = get 方法名,setter = set方法名:当声明一个名为xxx的属性时,默认情况下(当编译器遇到@property时候,会自动展开生成getter和setter的声明)@synthesize指令会生成名为xxx的get方法和名为setXxx的set方法。

5、assign、retain或者copy:这三种附加特性设计内存管理,在后面内存管理中会提及。

6、要是在.h文件中没有声明_x成员变量,而且没有在.m文件中“实现属性”,那么编译器会自动为其生成一个带下划线的成员变量

//BlackCat.h
#import <Foundation/Foundation.h>
@interface BlackCat : NSObject {
    //int score;
}

@property int score;
- (void)test;

@end
//BlackCat.m
#import "BlackCat.h"
@implementation BlackCat
    
@synthesize num;

- (void)test {    
    //score = 12;会报错
    _score = 12;       //不会报错
    NSLog(@"score is %i", _score);
}

@end


2.2 实现属性

声明了属性后,还需要让编译器按照属性声明的要求,自动生成get和set方法,这是通过在类中实现使用@synthesize指令实现的。(要是用户自己实现了get、set方法的话,那么这句话就是作废,要是没有实现,那么synthesize会帮用户生成标准的set.get方法)

@synthesize 属性名称;(默认关联到与属性名相同的成员变量)如果属性对应的成员变量并没有显示定义在成员变量区中(就是系统找不到同名变量),则编译器会自动创建私有的该成员变量(同名)。这也说明了要是用户定义的变量知识私有的,不想让人访问的话,在头文件中可以不需要定义变量名。

#import <Foundation/Foundation.h>

@interface Person : NSObject 

@property int len;
@property int no;

- (void)test1;

@end

@synthesize 属性名称 = 成员变量名;(手动指定属性关联到哪个成员变量,自动生成的成员变量就消失了)

@synthesize name = _name, age = _age;
上面的代码编译器会自动生成如下代码:

- (NSString*)name;              //成员变量_name的get方法
- (void)setName:(NSString*)newName;     //成员变量_name的set方法
- (int)age;
- (void)setAge:(int)newAge;

验证:赋值给谁?

//BlackCat.h
#import <Foundation/Foundation.h>
@interface BlackCat : NSObject {
    int _num;
    
}

@property int num;
- (void)test;

@end
//BlackCat.m
#import "BlackCat.h"
@implementation BlackCat
    
@synthesize num;
- (void)test {
    //在外部接口中并没有num成员变量,下面没有报错,说明的是编译器自动产生的与属性名一样的成员变量
    NSLog(@"num is %i", num);
    NSLog(@"_num is %i", _num);
}

@end
//main.m 
BlackCat *blackCat = [[BlackCat alloc] init];
        blackCat.num = 21;
        [blackCat test];
结果是:


所以这就是需要我们告诉他给谁赋值,就是需要手动执行属性的关联。

@synthesize num = _num;  //自动生成的num就不在存在了

2.3 注意:要是在.m文件中可以省略@synthesize,系统会自动生成该文件,默认帮我们实现get方法以及set方法。(并且会默认去访问_xx成员变量 )而且要是系统发现我们并没有在.h文件中声明_xx,则会帮我们自动生成私有的_xx(在xcode以后具有的 )。

2.4 调用属性

实现属性之后,外部代码就可以通过属性来间接读写的对象中的成员变量了。属性的间接调用使用点操作符(点语法)。

3. 类的静态变量与静态方法

3.1 出了实例级别的成员变量和实例方法外,还有属于类级别的变量和方法,叫做类的静态变量与静态方法(类方法)。在一些类中,有些变量需要随处能进行访问,一般会设置成静态变量。类的静态变量使用static关键字表示,定义在类的实现部分中的所有实例方法外。

3.2 静态变量定义在类的实现部分中,不属于任何实例方法;如果将静态变量定义在某一实例方法内,则只用该实例方法有权访问这个共享变量。

#import "Cat.h"

@implementation Cat
//类的实现去,在这里添加属性以及方法的实现
static int score;     //全局的静态变量

- (void)sayHello {
    static int a;     //实例方法里的静态变量
    NSLog(@"miaomiao!!");
    score = 12;
    a = 22;
    NSLog(@"score is %i, a is %i", score, a);
}

- (void)setStage:(int)newStage {
    //a = 44;  会报错
    score = 15;
    NSLog(@"调用的等级的set方法: %i, %i", newStage, score);
    _stage = newStage;
}

@end
3.3 释放对象时,注意对类的静态变量的操作。

4. 类的继承与复合

4.1 OC类的继承不支持多继承,与java、c#一致,但是与c++有区别。也就是说每个类(除了NSobject之外)有且只能有一个基类,但是每个类可以有零个或者多个子类。(正是树的结构特征)

4.2 除了继承之外,类还允许进行复合。就是说类的成员变量指向另一个类的对象,反映出“类中有另一个类的概念”。如:猫子中有黑猫、白猫

#import <Foundation/Foundation.h>

@class BlackCat;

@interface Cat : NSObject {
    @private
    NSString *_name;
    int _age;
    
    //2
    @private
    int _lenth;
    int _stage;
}

//类的方法属性去,在这里添加类的方法属性
- (void)sayHello;
- (BOOL)likeFood:(NSString*)food;
//初始化一只新猫
- (id)initWithName:(NSString*)newName andAge:(int)newAge;


上面使用了@class 指令,这是类的一个前向声明。由于A类引用了B类,所以在编译器在编译这段代码的时候,需要知道B类代表着什么。以前我们是使用#import指令将B类的外部接口B.h导入到A.h中,但是这样会编译依赖和编译时间的浪费。这么说吧,假如A.h导入了B.h,有许多其他的类文件导入了A.h,那么这些类文件也会导入B.h,一旦B.h文件修改了,相当于所有导入B.h的文件都发生了变化,都是需要重新进行编译。使用类的前向引用可以有效的解除这种编译依赖。如果在A.h中改用了B类的前向引用,则当B.h修改后,对于那些导入了A.h的文件来说并没有发生什么变化,所以无需要编译。

对于A类来说,要是外部接口声明中没有调用B类的任何方法,根本没有必要将B.h导入,只需要让编译器知道B代表着一个类就行了。如果在A类的外部接口(A.h)中使用@class前向引用,则在A类的内部实现中还是要导入B.h,因为此时可能需要调用B类中的属性以及方法、属性,此时编译器需要知道B类的详细定义的。{也就是说真正用到的时候在导入}

除了能消除编译依赖外,类的前向声明还能解决循环引用的问题。下面例子:(#include也是一样的,不能相互递归)

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

@interface Chicken : NSObject {
    Egg* egg;

}

@end
#import <Foundation/Foundation.h>
#import "Chicken.h"

@interface Egg : NSObject {
    Chicken* chicken;

}

@end
编译时候报错:

未知类型。。。

这真是“鸡生蛋还是蛋生鸡”,这种典型的循环引用问题使用@class前向引用代替#import导入。

#import <Foundation/Foundation.h>
//#import "Egg.h"
@class Egg;

@interface Chicken : NSObject {
    Egg* egg;
    
}

@end
#import <Foundation/Foundation.h>
//#import "Chicken.h"
@class Chicken;

@interface Egg : NSObject {
    Chicken* chicken;
}

@end


**: 如果是继承某个类,就要导入类的头文件;

如果只是定义成员变量、属性、用@class;

5. 类的实例化与初始化

实例化的意思是:根据类的信息在内存中分配地址空间,用于创建新的类的对象;(要实例化一个新的对象,需要调用类的静态方法alloc)

初始化的意思是:将对象设置初始状态,比如说为其成员设置初始值

Cat* myCat = [Cat alloc]
alloc会查看Cat类及其所有基类,统计处这些类定义了那些成员变量,总共需要多大的内存空间,并向操作系统发出请求,如果成功alloc方法会将所有的成员变量设置为0(除了一个特殊的指向其类元对象的成员变量之外),并返回内存地址的首地址。

类在实例化后必须进行对象的初始化。在OC中并没有类的构造方法这一说,编译器不会自动生成默认的构造方法,需要显示声明和定义初始化方法。

类的初始化格式:

- (id)init {
    //由于类的一部分成员变量定义在基类中,所以如果类继承基类,初始化应该先显示调用基类的初始化方法,完成基类成员变量的初始化,然后在对当前类的成员变量进行初始化。
    self = [super init];
    if (self) {
        
    }
    return self;
}

*这也是说明,子类的初始化必须在对基类的初始化成功的基础上才有意义。

感谢互联
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: