前面已经简单介绍过objc的基础知识,让大家对objc有个大致的印象,今天将重点解释objc面向对象的特性。objc相对于c语言多了面向对象特性,但是objc又没有其他面向对象语言那么多语法特性,objc本身对面向对象进行了精简。当然这并不代表今天的内容就会少,今天的内容还是相当多的:
<a href="http://www.cnblogs.com/kenshincui/p/3861302.html#class">类定义</a>
<a href="http://www.cnblogs.com/kenshincui/p/3861302.html#field">成员变量</a>
<a href="http://www.cnblogs.com/kenshincui/p/3861302.html#method">方法和属性</a>
<a href="http://www.cnblogs.com/kenshincui/p/3861302.html#self">self关键字</a>
<a href="http://www.cnblogs.com/kenshincui/p/3861302.html#constructor">构造方法</a>
<a href="http://www.cnblogs.com/kenshincui/p/3861302.html#description">description方法</a>
<a href="http://www.cnblogs.com/kenshincui/p/3861302.html#inherit">继承</a>
在c#、java等其他高级语言中定义一个类是相当简单点的,直接一个关键字class加一对大括号基本就完成了,但是在objc中类的定义相对变化比较大。现在假设需要定义一个person类
在xcode中添加文件,选择cocoa class 或者cocoa touch class

输入类名person,并选择父类为nsobject
person.h
person.m
在objc中定义一个类需要两个文件.h和.m:
.h文件:放类的声明,包括成员变量、属性和方法声明(事实上.h文件不参与编译过程);关键字@interface声明一个类,同时它必须以@end结束,在这两个关键字中间声明相关成员;在声明person类的同时可以看到它继承于nsobject,这是objc的基类,所有的类最终都继承于这个类(但是需要注意objc中的基类或者根类并不只有一个,例如nsproxy也是objc的基类),由于这个类在foundation框架中定义,所以导入了<foundation/foundaton.h>(这么描述的意思是导入foundation框架中的foundation.h声明文件);
.m文件:放属性、方法的具体实现;关键字@implementation用于实现某个类,同时必须以@end结尾,在这两个关键字中间实现具体的属性、方法;由于.m中使用了person类,所以需要导入声明文件“person.h”;
假设在person类中包含人员姓名(name)、年龄(age)、民族(nation)、身高(height)四个成员变量,同时姓名和年龄两个成员变量是私有的,身高是公开的,民族则限制为只有子类可以访问。
成员变量定义在.h文件中,同时必须定义在类后面的{}内。成员的可访问性通过下面三个关键字声明:
@private 私有成员,只有当前类可以访问;
@protected 受保护成员,只有当前类或子类可以访问(如果没有添加任何修饰则默认为@protected);
@public 公共成员,所有类均可访问;
在objc中可访问性修饰符除了这三种,还有一个@package不太常用,它类似于c#中的internal在框架内是公共的,但是框架外是私有的(也就是只能在一个框架内可以访问)。那么既然身高是公共的,外界怎么访问呢?
这里需要注意几点:
objc中所有的对象类型的变量都必须加上“*”,在objc中对象其实就是一个指针(例如之前看到的nsstring也是如此,但是基本类型不用加”*”);
objc中使用[]进行方法调用,在objc中方法调用的本质就是给这个对象或类发送一个消息;
在objc中类的实例化需要两个步骤:分配内存、初始化;
类的初始化调用了父类的init方法,如果使用默认初始化方法进行初始化(没有参数),内存分配和初始化可以简写成[person new];
公共成员的调用使用“->”操作符;
既然有了上面成员变量,假设现在需要一个对象方法去设置用户姓名,还需一个类方法打印一些信息。
在objc中方法分为静态方法和动态方法两种,动态方法就是对象的方法,静态方法就是类方法,这一点跟其他高级语言没有区别。在objc中使用“-”定义动态方法,使用“+”定义静态方法。如果一个方法在.h中有声明则该方法是公共方法,如果没有在.h中声明直接在.m中定义则该方法是私有方法,外部无法访问。
在objc中方法的参数类型、返回值类型需要放到()中,而且参数前必须使用冒号,并且此时冒号是方法名的一部分。当然,上面的方法只有一个参数,假设现在有一个方法可以同时设置年龄和籍贯,可以写成如下形式:
其中andheight可以省略不写,当然为了保证方法名更有意义建议书写时加上。
大家都知道在其他语言中还会经常提到属性的概念,通常一个成员的访问不会直接通过成员变量而是通过属性暴漏给外界。在objc中属性的实现方式其实类似于java中属性定义,通过对应的setter和getter方法进行实现。没错,上面setname其实就是属性的setter方法,但是在objc中gettter方法通常使用变量名,而不加“get”。下面就看一下年龄属性的实现
接下来看一下具体的调用
关于方法的调用在这里不着重介绍了,我们可以看到p.age的调用方式,是不是类似于c#、java中属性的调用方式,这就是objc中的点语法。其实这种方式调用的本质还是调用对应的方法进行处理,这么做的目的只是为了开发人员书写方便而已(这就是语法糖的目的)。至于p.age是调用get方法还是调用set方法完全取决于当前操作是赋值操作还是读取操作。
通过上面的程序我们可以看到如果要定义一个属性,首先需要在.h中声明其次还要在.m中实现,而定义属性的代码基本都是类似的,那么有没有简单的方法呢,其实在objc中可以通过声明@property,同时通过@synthesize自动生成getter、setter方法(在新版本中甚至甚至都不用通过@synthesize只声明就可以使用)。我们通过一段代码来说明这个问题(为了方便大家查看代码,在下面的代码中暂时去掉前面定义的成员变量、属性等)
main.m
上面的代码虽然简单,但是几乎涵盖所有属性生成规则。通过上面的代码我们可以看到最简单的方法就是直接通过@property就可以声明一个变量(例如weight属性),不需要进行实现即可直接使用;还可以使用@property声明再用@synthesize去实现(例如上面的birthday属性),不仅如此在实现的时候还可以指定实现此属性时使用哪个成员变量(例如degress属性)。在上面的代码中我们还看到weight属性自动生成了一个_weight成员变量,而education生成了一个education属性,那么它们生成的规则是什么呢,这里总结如下:
如果只声明一个属性a,不使用@synthesize实现:编译器会使用_a作为属性的成员变量(如果没有定义成员变量_a则会自动生成一个私有的成员变量_a;如果已经定义了成员变量_a则使用自定义的成员变量_a。注意:如果此时定义的成员变量不是_a而是a则此时会自动生成一个成员变量_a,它跟自定义成员变量a没有任何关系);
如果声明了一个属性a,使用@synthesize a进行实现,但是实现过程中没有指定使用的成员变量(例如上面birthday):则此时编译器会使用a作为属性的成员变量(如果定义了成员变量a,则使用自定义成员变量;如果此时没有定义则会自动生成一个私有的成员变量a,注意如果此时定义的是_a则它跟生成的a成员变量没有任何关系);
如果声明了一个属性a,使用@synthesize a=_a进行实现,这个过程已经指定了使用的成员变量:此时会使用指定的成员变量作为属性变量;
有了上面的总结,相信理解上面的代码并不难,通常在实际开发过程中我们要么直接在@property中声明不使用@synthesize;要么使用过程中指定具体的成员变量。
此外再次强调一下,通过上面的方式定义变量的本质还是生成对应的gettter、setter方法(只是这个步骤编译器帮你完成了),如果通过@property定义了属性,同时在.m中又自定义实现了对应方法,则会使用自定义方法。
在c#、java中都有一个关键字this用于表示当前对象,其实在objc中也有一个类似的关键字self,只是self不仅可以表示当前对象还可以表示类本身,也就是说它既可以用在静态方法中又可以用在动态方法中。
perosn.h
在上面代码中可以看到setname: andage:方法是一个动态方法,此时self就代表调用对象;而在showmessage方法中self调用了类的静态方法printinfo,此时self代表调用的类;因此可以总结出在objc中self就代表当前方法的调用者。
先看一段代码
如果运行上面的代码将会发生死循环,原因很简单,self.name=name本身就会调用person的setname方法,如此反复就会造成循环操作,所有一般如果需要重写setter方法,可以直接写成_name=name,由此我们也可以看到为什么之前即使没有使用@property生成对应的属性方法,在定义成员变量时也都加上了下划线(这是一好的编码习惯)。
在前面的代码中我们已经看到如果要初始化一个类需要调用init方法,那么下面看一下如何自定义构造方法
在objc中super代表父类,通过调用父类的方法给当前对象赋值,然后判断这个对象是否为nil,如果不为空则依次给name、age属性赋值。
通过自定义构造方法固然可以简化代码,但是在使用时还要手动申请内存,在objc中一般我们通过定义一个静态方法来解决这个问题
在c#中每个类都有一个tostring()方法(java中叫做tostring())用于打印一个对象的信息,在objc中这个方法叫description,例如在前面的person类中我们可以重写这个方法用于打印调试
注意上面nslog中的格式符是%@,当使用%@输出一个对象时,objc会调用个对象的description返回对应的信息进行输出,默认情况下如果我们不重写description方法,输出内容是类名和地址,例如person则输出“<person: 0x100202310>”。
需要强调的是千万不要在description中打印输出self,因为当输出self时会调用该对象的description方法,如此一来就会造成死循环。
继承是面向对象三大特征之一,既然objc是面向对象语言,当然同样支持继承。事实上前面定义的person类本身就继承于nsobject,下面再简单看一个例子,这里部分假设我们还有一个student类继承于person类,而且这个类有一个分数(score)属性。
student.h
student.m
继承知识比较简单,通过上面的代码基本上就可以了解,这里不做详细论述。