Object-C 类
Classes 类
像其他的面向对象的语言一样,Object-C也提供创建对象的蓝本,即类。首先我们在类中定义一些可以重复使用的属性和方法。然后,我们实例化类,即对象,之后就可以使用属性和访问。
Object-C和C++一样,从类的实现中抽象出了类的接口。接口中定义了类的公开的方法和属性,相应的实现代码,定义方法和属性的具体的实现。这和我们看到的函数,分离关注点是一样的。
vcyBpbnRlcmZhY2UgYW5kIGltcGxlbWVudGF0aW9u" src="http://img.2cto.com/2016/0418/20160418085358906.png" title="\" />
这张我们将学习关于类接口,实现、属性、方法已经实例化的基本语法。我们也会介绍Object-C的内省和反射的功能。
Creating Classes 创建对象
我们将定义一个叫Car的类,他的接口在Car.h文件中,他的实现在Car.m 文件中。这些是标准的Object-C的文件扩展名。当其他的类想使用这个类的功能时,需要使用这个类的头文件,而类的实现文件是给编译器使用的。
xcode 提供方便的创建类的模板。我们可以从 File > New > File …或者Cmd + N 快捷键创建类。在模板中从iOS > Cocoa Touch中选择Object-C类模板。之后,提示您配置一些信息:
在Class 域中填写Car。在subclass of 中选择NSobject。NSObject 类是其他所有Object-C类的父类。点击下一步,提示选择文件存储的位置,选择存储在工程根目录下。在对话框的下面,注意选择您的工程为目标工程。这样是保证文件会被编译。
点击next后,您会在Xcode的工程导航中看到Car.h文件和Car.m文件。如果您选择工程名称,在编译资源模块中,您会发现在Car.m文件在构建路径中。任何您想让编译器编译的文件都必须在这个类表中。
Interfaces 接口
Car.h 文件中包含一些样板代码,从现在开始,我们修改如下,声明一个叫model的变量和一个叫drive的方法。
// Car.h #import @interface Car : NSObject { // Protected instance variables (not recommended) } @property (copy) NSString *model; - (void)drive; @end
通过@interface指令创建接口,后面接着类的名字和父类的名字,中间用冒号隔开。保护类型的变量可以定时在花括号内。大多数开发者喜欢讲实例变量放在.m文件中,而不是头文件中。
@property 声明一个public 类型的变量,而且通过copy定义内存管理行为。这种情况下,赋值给model变量的值,是一个副本,而不是直接指向值。在属性章节我们将会讨论更多的细节。接下来是属性的数据类型和名字,就像普通的变量声明一样。
以- (void)开头的行,定义了一个叫drive的函数,没有参数, (void) 部分定义函数的返回值类型。在方法前面的减号,表示一个实例方法,与之相对应的是类方法。
Implementations 实现
任何类的实现,第一件事是引入相对应的头文件。 @implementation 和@interface是一样的,除了不需要父类。私有的变量可以定义在类名之后的括号中。
// Car.m #import "Car.h" @implementation Car { // Private instance variables double _odometer; } @synthesize model = _model; // Optional for Xcode 4.4+ - (void)drive { NSLog(@"Driving a %@. Vrooooom!", self.model); } @end
@synthesize 帮助我们为属性产生存取方法(getter和setter方法)。默认,getter方式是属性名(model),setter方法是set前缀加属性名首字符大写,这样比手动写每一个存储器的特征方便。_model 部分定义了属性的私有实例变量名。
xcode 4.4 之后,用@property声明的属性变量,自动产生存取方法。如果您认为默认的实例变量命名规则可以,您可以省略@synthesize行。
drive方法的实现和接口中有相同的方法签名,但是在签名之后,有方法调用时执行的代码。我们是通过self.model 获取model的值,而不是通过_model。这是最佳实践,利用了属性的存储方法。只有在init方法和dealloc方法中您才需要直接访问实例变量。
self 关键是指向方法调用的实例。除了获取属性值,还可以调用方法。在教程中您将看到很多这样的例子。
Instantiation and Usage 实例化和用法
任何想访问类的文件,都要引入类的头文件,我们绝对不能直接访问类的实现。这样将违反了,分离实现和接口的目的。所以,为了让我们类生效,如下修改main.m.
// main.m #import #import "Car.h" int main(int argc, const char * argv[]) { @autoreleasepool { Car *toyota = [[Car alloc] init]; [toyota setModel:@"Toyota Corolla"]; NSLog(@"Created a %@", [toyota model]); toyota.model = @"Toyota Camry"; NSLog(@"Changed the car to a %@", toyota.model); [toyota drive]; } return 0; }
在使用#import执行引入头文件之后,您可以使用alloc/init模式实例化类。正如您所见,实例化分成两个步骤:首先使用alloc方法为对象分配内存,接着初始化对象。你绝对不能使用未初始化的对象。
再次强调,所有对象都作为指针存储。这也就是为什么我们使用Car *toyota,而不是 Car toyota。
为了调用Object-C对象的方法,我们将实例名和方法名,放在方括号内,用空格分开。在方法名之后是参数,之前是冒号。所以您有C++、java、Python编程背景,[toyota setModel:@”Toyota Corolla”]可以转化成toyota.setModel(“Toyota Corolla”)。
toyota.setModel("Toyota Corolla");
方括号的语法可能是您不太习惯,但是我确信,在您阅读网方法章节后,您会非常习惯这种写法。
这个例子为您展示了两种获取属性的方法。您可以使用存取方法,或者点语法。
Class Methods and Variables 类方法和变量
上面的例子定义的实例方法和属性,我们也可以定义类的方法和属性,在其他的语言中称为static方法或者属性。
类方法的声明和实例方法是一样的,除了在方法之前是加号而不是减号。添加如下的class方法在Car.h文件中:
// Car.h + (void)setDefaultModel:(NSString *)aModel;
同样,在方法的实现之前也添加加号,但是没有类变量,您可以在实现文件中定义static变量模拟。
// Car.m #import "Car.h" static NSString *_defaultModel; @implementation Car { ... + (void)setDefaultModel:(NSString *)aModel { _defaultModel = [aModel copy]; } @end
[aModel copy] 创建了一个参数的副本,而不是直接指向参数。这就是为什么我们在声明model变量时,使用(copy)属性。
类方法使用和实例方法一样的方括号语法,但是他必须直接从类调动,不能在实例上调用。[toyota setDefaultModel:@”Model T”]将会报错。
// main.m [Car setDefaultModel:@"Nissan Versa"];
“Constructor” Methods 构造方法
在Object-C中没有构造方法。然而,对象的初始化是通过init方法,在对象内存分配完成之后。这也就是为什么对象实例化分两步:分配内存和初始化。有一个类的初始化方法,等会我们会讨论。
init是默认的初始化方法。您也可以自定义您自己的初始化方法。自定义的初始化方法,没有什么特殊的,和其他的实例方法一样。
// Car.h - (id)initWithModel:(NSString *)aModel;
为了实现这个方法,我们要遵守初始化方法的命名规范,像initWithModel。super关键字指向它的父类,self指向方法调用的对象。添加如下代码到Car.m
// Car.m - (id)initWithModel:(NSString *)aModel { self = [super init]; if (self) { // Any custom setup work goes here _model = [aModel copy]; _odometer = 0; } return self; } - (id)init { // Forward to the "designated" initialization method return [self initWithModel:_defaultModel]; }
初始化方法总是返回对象自己的引用。如果不能初始化,返回nil.这也就是为什么我们在使用之前,总是检测他是否存在。但是总是只有一个初始化方法做这件事。其他的初始化方法,调用这个指定的初始化方法。这样当你定义多个初始化方法的时候,就省去了一些模板代码。
注意,我们在initWithModel方法中直接给_model和_odometer赋值,也只有这一个地方可以这样,其他的地方,需要使用self.model和self.odometer.
Class-Level Initialization 类-初始化方法
类级别的初始化方法和init方法一样。我们可以在这里初始化类,在其他地方使用它之前。例如我们赋值_defaultModel。例如:
// Car.m + (void)initialize { if (self == [Car class]) { // Makes sure this isn't executed more than once _defaultModel = @"Nissan Versa"; } }
类的初始化方法,在类使用之前,只能被调用一次。这就包括他所有的子类。最佳实践是我们使用self == [Car class]确认初始化方法已经执行了。注意,在类方法中,self关键字,指向类自己,而不是实例。
Object-C不强制您标示重写方法。
接下来我们看看自定义初始化方法的执行情况。在类第一次被使用的时候,会执行[Car initialize]方法,即将_defaultModel赋值@”Nissan Versa”。这个可以在第一个NSLog中看到。第二个自定义方法initWithModel输出结果可以在第二个输出中看到。
// main.m #import #import "Car.h" int main(int argc, const char * argv[]) { @autoreleasepool { // Instantiating objects Car *nissan = [[Car alloc] init]; NSLog(@"Created a %@", [nissan model]); Car *chevy = [[Car alloc] initWithModel:@"Chevy Corvette"]; NSLog(@"Created a %@, too.", chevy.model); } return 0; }
Dynamic Typing 动态类型
类可以被一个对象表示,即类对象,这样我们就可以方法他们的属性。而且可以在执行时改变类的行为,增加或者修改方法。这是非常厉害的动态类型能力。这样您可以在不知道当前对象是什么类型,也可以调用方法和设置属性。
最简单的获取类方法的方式是调用类方法。例如,[Car class]返回一个表示类的对象。您可以将此对象传递给isMemberOfClass: 和 isKindOfClass:方法获取其他对象的信息。
// main.m #import #import "Car.h" int main(int argc, const char * argv[]) { @autoreleasepool { Car *delorean = [[Car alloc] initWithModel:@"DeLorean"]; // Get the class of an object NSLog(@"%@ is an instance of the %@ class", [delorean model], [delorean class]); // Check an object against a class and all subclasses if ([delorean isKindOfClass:[NSObject class]]) { NSLog(@"%@ is an instance of NSObject or one " "of its subclasses", [delorean model]); } else { NSLog(@"%@ is not an instance of NSObject or " "one of its subclasses", [delorean model]); } // Check an object against a class, but not its subclasses if ([delorean isMemberOfClass:[NSObject class]]) { NSLog(@"%@ is a instance of NSObject", [delorean model]); } else { NSLog(@"%@ is not an instance of NSObject", [delorean model]); } // Convert between strings and classes if (NSClassFromString(@"Car") == [Car class]) { NSLog(@"I can convert between strings and classes!"); } } return 0; }
NSClassFromString() 是另一种获取类对象的方法。这样可以使您在运行时获取类对象。然而不是高效的。所以最好使用类方法获取类对象。
如果您对动态类型感兴趣,您可以查看Slectors和id类型的相关内容
Summary
这个模块我们学习了创建类、初始化对象、定义初始化方法、调动类方法和变量。了解了一点关于动态类型的知识。
Object不支持命名空间,所以Cocoa中的函数有NS、CA、AV这样的前缀,避免冲突。这个对类也是一样的。我me你建议对于特定项目的类,命名使用三个字母的前缀,例如,XYZCar。
上一篇: python实现-明明的随机数
下一篇: php正则表达式的基本语法总结