Objective-c学习笔记02——类(面向对象)01

本文首发在:http://www.simman.cc/article/2107,转载请注明出处!

Objective-C可以开发apple家族系列产品的软件,这里记录成笔记方便日后复习:

一、面向对象简介:

面向对象程序设计(英语:Object-oriented programming,缩写:OOP),指一种程序设计范型,同时也是一种程序开发的方法。对象指的是类的集合。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性。[1]

二、面向对象与面向过程

面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

三、对象、类、方法

请查阅:面向对象程序设计
也可以看下图简单理解一下:

四、Objective-c中的类

1 OC类的结构

扩展名 源类型
.h 头文件。头文件包含类、类型、函数和常量声明,类的声明使用关键字@interface@end
.m 实现文件。具有此扩展名的文件可以同时包含 Objective-C 代码和 C 代码。有时也称为源文件,类的实现使用关键字@implementation@end
.mm 实现文件。具有此扩展名的实现文件,除了包含 Objective-C 代码和 C 代码以外,还可以包含 C++ 代码。仅当您实际引用您的 Objective-C 代码中的 C++ 类或功能时,才使用此扩展名。

注意:.h中的方法只是做一个声明,并不对方法进行实现。也就是说,只是说明一下方法名、方法的返回值类型、方法接收的参数类型而已,并不会编写方法内部的代码。

2 方法

方法的声明和实现,都必须以 + 或者 – 开头

* + 表示类方法(静态方法)
* – 表示对象方法(动态方法)
在.h中声明的所有方法作用域都是public类型,不能更改。

类方法和实例方法的区别在于,类方法不能使用实例变量

使用类方法主要原因有:

1.类方法的使用不依赖于实例化一个对象,也就是说如果一个功能的实现不需要实例化对象,就可以用类方法来实现。
2.类方法可以隐藏单例,将类方法和单例结合,可以在应用程序的任何地方访问静态实例,而无需使用指向对象的指针或保存他的实例变量。
3.类方法和内存管理相关,分配一个NSArray,可以【NSArray alloc】init,也可以【NSArray array】,但是前者必须释放,而后者返回一个随时准备好自动释放的数组对象,并不需要你进行release操作

四、创建一个类

1 声明
下图中的语法声明名为 MyClass 的类,它是从基础类(或根类)NSObject 继承而来的。(根类是供其他类直接或间接继承的类。)类声明以编译器指令 @interface 开始,以 @end 指令结束。类名称后面(以冒号分隔),是父类的名称。在 Objective-C 中,一个类只能有一个父类。

在 @interface 指令和 @end 指令之间,编写属性和方法的声明。这些声明组成了类的公共接口。分号标记每个属性和方法声明的结尾。如果类具有与其公共接口相关的自定函数、常量或数据类型,请将它们的声明放在@interface …@end 块之外。

注意:上面的成员变量命名有所不规范,我们通常会在前面加上 _ (下划线)以和其他变量或者方法区分,这里的成员变量作用域有三种类型,分别是@public,@protected,@private,如果没有指定类型,则默认为@protected.

方法声明包含方法类型标识符、返回类型、一个或多个签名关键词,以及参数类型和名称信息。以下是 insertObject:atIndex: 实例方法的声明。

一个方法的实际名称 (insertObject:atIndex:) 是包括冒号字符在内的所有签名关键词的串联。冒号字符表明有参数存在。在上述示例中,该方法采用两个参数。如果方法没有参数,则省略第一个(也是仅有的一个)签名关键词后面的冒号。
声明如下的方法:

  1. - (void)setName:(NSString *)name :( int)age width:(int)width;

则方法名为:setName::width:
2 实现
类实现的语法与类接口文件类似。它以 @implementation 编译器指令开始(接着是该类的名称),以 @end 指令结束。中间是方法实现。(函数实现应在 @implementation …@end 块之外。)一个实现应该总是将导入它的接口文件作为代码的第一行。

  1. #import "MyClass.h"
  2. @implementation MyClass
  3. - (id)initWithString:(NSString *)aName
  4. {
  5. // code goes here
  6. }
  7. + (MyClass *)myClassWithString:(NSString *)aName{
  8. // code goes here
  9. }
  10. @end

对于包含对象的变量,Objective-C 既支持动态类型化,也支持静态类型化。静态类型化的变量,要在变量类型声明中包括类名称。动态类型化的变量,则要给对象使用类型 id。您会发现在某些情况下,会需要使用动态类型化的变量。例如,集 (collection) 对象,如数组,在它包含对象的类型未知的情况下,可能会使用动态类型化的变量。此类变量提供了极大的灵活性,也让 Objective-C 程序拥有了更强大的活力。

下面的例子,展示了静态类型化和动态类型化的变量声明:

  1. MyClass *myObject1; // Static typing
  2. id myObject2; // Dynamic typing
  3. NSString *userName; // From Your First iOS App (static typing)

请注意第一个声明中的星号 (*)。在 Objective-C 中,执行对象引用的只能是指针。如果您还不能完全理解这个要求,不用担心。并非一定要成为指针专家才能开始 Objective-C 编程。只需要记住,在静态类型化的对象的声明中,变量的名称前面应放置一个星号。id 类型意味着一个指针。

四、实例化对象

  1. #import <Foundation/Foundation.h>
  2. #import "MyClass.h"
  3. int main(int argc, const char * argv[])
  4. {
  5. @autoreleasepool {
  6. MyClass * class = [MyClass alloc]; //调用Myclass类的静态方法alloc分配存储空间
  7. class = [class init]; //调用对象class的动态方法init进行初始化
  8. }
  9. return 0;
  10. }

alloc:会返回分配好内存的Myclass对象,在等号左边用了一个指向Myclass类型的指针变量class来接收这个对象,注意class左边的*号。所有OC对象都是用指针变量来接收的,如果你不了解指针,你记住下面这点就行了:利用类名定义一个变量时,类名后面一定要带个*号。
init:由于init是动态方法,所以这里使用stu变量来调用,并不是使用类名来调用。init会返回已经初始化完毕的对象,再次赋值给了stu变量。这时候的Student对象stu才能正常使用。
当然你也可以写成如下两种方式:

  1. 1 Myclass * class = [[Myclass alloc] init];
  2. 2 Myclass * class = [Myclass new];

还有一点需要明确一下,如果你创建一个新对象(入用alloc),就会在内存中为它保留足够的空间用于存储对象数据,这包括它的实例变量的空间,另外再多一些记录其他信息,还有一点就是使用init后示例变量会进行初始化,通常都会为0.

五、释放对象(销毁对象)

在Xcode4.2之前需要程序员进行手动释放对象,那么就要使用如下的代码进行:
[class release] ,class是我们上面实例化的对象。

当然现在Xcode已经更新到了5.0版本,则默认为启用ARC(全称:Automatic Reference Counting)机制,此时释放对象的任务就交由系统进行处理,如果你使用的是5.0版本,并且想手动释放对象的话,那么可以在Build Settings 里面找到Apple LLVM 5.0 – Language – Objective C的下面把Objective-C Automatic ReferenceCounting 的值设置为NO。

否则你使用 release 的时候Xcode就会报如下错误:
‘release’ is unavailable: not available in automatic reference counting mode ARC forbids explicit message send of ‘release’

四、”.”语法

在说.语法之前,让我们来看看getter和setter方法,下面我们给MyClass类添加两个动态方法:

  1. - (int)age;
  2. - (void)setAge:(int)age;

1、上面的age为getter方法,oc习惯把getter方法命名和实例变量名一样。
2、上面的setAge为setter方法,同样习惯于使用set开头,并且后面也与实例变量名相同,而且首字母要大写。

除了上面的方法,OC同时提供了使用.语法进行访问类方法,请看下面的代码

  1. //使用[]语法,访问age的getter方法、setter方法
  2. [class age];
  3. [class setAge:28];
  4. //使用.语法,访问age的getter方法
  5. class.age
  6. class.age = 28;

这里要说明的是 class.age 等于 [class.age],class.age = 28 等于 [class setAge:28],.语法其实就是访问对象方法的getter和setter方法。至于到底使用哪个方法,取决于你怎么用。

五、构造方法

在Objective-c中,init就是默认的构造方法,它不接受任何参数。我们可以看到在Foundation框架里对init的如下声明:

  1. - (id)init;

一般来说,我们编写的类可能会在初始化的时候进行对象变量的赋值,所以我们要重构构造方法,可以使用如下的方式:

  1. //声明
  2. - (id)initWithMyClass:(int)age;
  3. //实现
  4. - (id)initWithMyClass:(int)age {
  5. self = [super init];
  6. if(self){
  7. _age = age;
  8. }
  9. return self;
  10. }

通过上面的代码我们可以看出,构造方法的返回值类型为id,现在可以把id理解为任何类型的类型。
上面使用了 [super init],这是调用父类的init方法进行初始化,并返回赋值给当前对象self,接下来判断是否为真,如果是则对_age赋值,否则不做任何操作,最后返回当前对象self;

如果我们重构了构造方法,那我们就可以使用如下方式进行创建对象,并初始化了。

  1. Myclass *class = [Myclass initWithMyClass:28];

六、Self关键字

Objective-c中的self代表当前方法的调用者,并且可以用在动态方法静态方法中。

  1. - (void)test1(){
  2. [self test2];
  3. }
  4. + (void)test2(){
  5. [Myclass test1];
  6. [self test1];
  7. }

通过上面的代码我们可以看到不管是在动态方法和静态方法中,都可以使用self关键字调用其他方法,并且等于 [Myclass test1]这种方式。

如果细分一下的话,可以理解为:
静态方法:self代表这个类。
动态方法:self代表这个对象。

六、空指针与野指针

1、空指针
没有存储任何内存地址的指针就称为空指针(NULL指针)
空指针就是被赋值为0的指针,在没有被具体初始化之前,其值为0。
下面两个都是空指针:

  1. Student *s1 = NULL;
  2. Student *s2 = nil;

2、野指针
“野指针”不是NULL指针,是指向”垃圾”内存(不可用内存)的指针
3、示例

  1. Myclass *class = [[Myclass alloc] init];
  2. class.age = 28;
  3. class.release;
  4. class.age = 20;

如果我们run如上代码会报 class.age = 20;这行的错误,因为上一行已经relase了,这是对象指向的内存地址不可用,也就成了野指针。
当然,如果我们给空指针发消息,则不会报错。

七、@property和@synthesize

在说@property和@synthesize之前我们先写一段代码:

  1. - (int)age;
  2. - (void)setAge:(int)age;

上面的代码是age的setter和getter方法声明的简单访问器,其中的setter中还涉及到一定的内存管理,既然这个技术这么重要,那么有没有一种更方便的方法去做呢?答案就是@property和@synthesize。它们是Objective-C 2.0加入的指令,前者用于声明,后者用于合成访问器,结合使用就可以自动生成访问器了。

下面我们使用@property和@synthesize来新建一个访问器:

  1. //声明方法
  2. @property (nonatomic, copy) NSString *name;
  3. //实现方法
  4. @synthesize stuName = _stuName;

使用@property和@synthesize很方便,但又给我们带来了很多疑问比如在上面的代码中又出现了nonatomic和copy,是什么意 思?在@property中还有其他几个关键字,它们都是有特殊作用的,我把它们分为三类分别是:原子性,访问器控制,内存管理。

原子性
atomic(默认):atomic意为操作是原子的,意味着只有一个线程访问实例变量。atomic是线程安全的至少在当前的访器上我是安全的。它是一个默认的,但是很少使用。它的比较慢,这跟ARM平台和内部锁机制有关。
nonatomic: nonatomic跟atomic刚好相反。表示非原子的,可以被多个线程访问。它的速度比atomic快。但不能保证在多线程环境下的安全性,在单线程和明确只有一个线程访问的情况下广泛使用。
访问器控制
readwrite(默认):readwrite是默认的,表示同时拥有setter和getter。
readonly:readonly 表示只有getter没有setter。
有时候为了语意更明确可能需要自定义访问器的名字:

  1. @property (nonatomic, setter = mySetter:,getter = myGetter ) NSString *name;

最常见的是BOOL类型,比如标识View是否隐藏的属性hidden。可以这样声明

  1. @property (nonatomic,getter = isHidden ) BOOL hidden;

要注意修改setter或者getter的名字是存在副作用的,可能会使KVC和KVO无法正常工作。
内存管理
retain:使用了retain意味着实例变量要获取传入参数的所有权。具体表现在setter中对实例变量先release然后将参数 retain之后传给它。下面这段代码展示了retain类似的行为:

  1. -(void)setStuName:(NSString *)stuName{
  2. if (_stuName != stuName)
  3. {
  4. [_stuName release];
  5. _stuName = [stuName retain];
  6. }
  7. }

assign(默认):用于值类型,如int、float、double和NSInteger,CGFloat等表示单纯的复制。还包括不存在所有权关系的对象,比如常见的delegate。
strong:是在ARC伴随IOS引入的时候引入的关键字是retain的一个可选的替代。表示实例变量对传入的参数要有所有权关系即强引用。strong跟retain的意思相同并产生相同的代码,但是语意上更好更能体现对象的关系。
weak: weak跟assign的效果相似,不同的是weak在对象被回收之后自动设置为nil。而且weak智能用在iOS 5或以后的版本,对于之前的版本,使用unsafe_unretained。
unsafe_unretained:weak的低版本替代。
copy:copy是为是实例变量保留一个自己的副本。
现在明白了@property是怎么回事了,但是@synthesize是怎么回事,看看之前的第一段代码:

  1. @synthesize stuName = _stuName;

这里的stuName = _stuName是什么意思?stuName是propertyName跟@property声明的名字一样。而后面的_stuName 是实例变量名。生成的访问器就是来访问的 _stuName的。代码的样子就和最开始那setter和getter代码所描述的一样。

注意一个问题,我们并没有声明_stuName这个变量,这是编译器自动帮我们创建的。 如果这段指令我换个写法:@synthesize stuName = a; 并且我们没有在interface里面声明这个变量,那么会自动创建一个变量a。

如果这里写成这样:

  1. @synthesize stuName;
  2. //等同于
  3. @synthesize stuName = stuName;

在Xcode4.4中,Xcode添加的一些新的编译特性。其中一个就是默认合成(Default Synthesis)。默认合成就不再需要显示的使用@synthesize指令了,这很方便但是要注意的是,默认合成遵守的约定,这里的也就是命名规则是propertyName = _propertyName。

  1. /对于下面的@propety
  2. @property (nonatomic, copy) NSString *stuName;
  3. //默认合成的规则是这样:
  4. @synthesize stuName = _stuName;

因为@synthesize是默认合成,所以 @synthesize stuName = _stuName;则可以省略。

此条目发表在objective-c分类目录,贴了objective-c标签。将固定链接加入收藏夹
posted @ 2013-10-19 16:11  simman  阅读(509)  评论(0编辑  收藏  举报