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

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

一、面向对象简介:

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

二、面向对象与面向过程

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

三、对象、类、方法

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

Alt text

四、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 中,一个类只能有一个父类。

Alt text

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

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

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

Alt text

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

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

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

#import "MyClass.h"
@implementation MyClass
- (id)initWithString:(NSString *)aName
{
    // code goes here
}
+ (MyClass *)myClassWithString:(NSString *)aName{
    // code goes here
}
@end

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

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

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

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

四、实例化对象

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

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

1、 Myclass * class = [[Myclass alloc] init];
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类添加两个动态方法:

- (int)age;
- (void)setAge:(int)age;

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

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

//使用[]语法,访问age的getter方法、setter方法
[class age];
[class setAge:28];
//使用.语法,访问age的getter方法
class.age
class.age = 28;

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

五、构造方法

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

- (id)init;

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

//声明
- (id)initWithMyClass:(int)age;
//实现
- (id)initWithMyClass:(int)age {
    self = [super init];
    if(self){
        \_age = age;
    }
    return self;
}

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

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

Myclass *class = [Myclass initWithMyClass:28];

六、便利构造器

便利构造器其实就是内方法,里面会把创建的对象进行autorelase,所以不用手动释放内存。

+ (id)myClassWithName:(NSString *)name age:(int)age {
    MyClass * myc = [[[MyClass alloc] init] autorelease];
    if (myc) {
        myc.name = name;
        myc.age = age;
    }
    return myc;
}

上面的IF是为了判断对象是否创建成功,防止为空的情况。

七、单例模式

使用单例模式多次调用此类方法创建对象时,指向的是同一块内存,因为方法中有判断语句,只要指针myc非空,就直接返回指针myc。

+ (id)shareInstance {
    static MyClass * myc = nil;
    @synchronized(self){ //关键字,同步,表示同一时间只能有一个线程在访问
        if (!myc) {
            myc = [[MyClass alloc] init];
        }
    }
    return myc;
}

使用dispatch_once_t:

+ (id)shareInstance {
    static MyClass * myc = nil;
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        MyClass = [[self alloc] init];
    });
    return myc;
}

1.这个方法可以在创建单例或者某些初始化动作时使用,以保证其唯一性。
2.该方法是线程安全的,所以请放心大胆的在子线程中使用。(前提是你的dispatch_once_t *predicate对象必须是全局或者静态对象。这一点很重要,如果不能保证这一点,也就不能保证该方法只会被执行一次。)

当然,如果我们为了保证单例的唯一性,那么我们可以进行重载如下几个系统方法:

1、allocWithZone

- (id)allocWithZone:(NSZone *)zone
{
    return [[self MyClass] retain];
}

2、copyWithZone

- (id)copyWithZone:(NSZone *)zone
{
    return self;
}

3、retain

- (id)retain
{
    return self;
}

4、release

- (void)release
{

}

5、retainCount

- (void)retainCount
{
    return UINT_MAX;
}

6、autorelease

- (void)autorelease
{
    return self;
}

不过作为程序员,应该去遵循某些原则,使用单例的时候尽量不要直接用alloc等方法。

八、Self关键字

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

- (void)test1(){
    [self test2];
}
+ (void)test2(){
    [Myclass test1];
    [self test1];
}

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

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

九、空指针与野指针

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

Student *s1 = NULL;
Student *s2 = nil;

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

Myclass *class = [[Myclass alloc] init];
class.age = 28;
class.release;
class.age = 20;

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

十、@property和@synthesize

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

- (int)age;
- (void)setAge:(int)age;

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

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

//声明方法
@property (nonatomic, copy) NSString *name;
//实现方法
@synthesize stuName = \_stuName;

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

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

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

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

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

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

-(void)setStuName:(NSString *)stuName{
    if (_stuName != stuName)
    {
        [_stuName release];
        \_stuName = [stuName retain];
    }
}

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是怎么回事,看看之前的第一段代码:

@synthesize stuName = \_stuName;

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

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

如果这里写成这样:

@synthesize stuName;
//等同于
@synthesize stuName = stuName;

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

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

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