本文首发在:http://www.simman.cc/article/2107,转载请注明出处!
- Objective-c学习笔记01——简介
- Objective-c学习笔记02——类(面向对象)
- Objective-c学习笔记03——内存管理
- Objective-c学习笔记04——NSString
- Objective-c学习笔记05——NSArray(NSMutableArray)
- Objective-c学习笔记06——字典与集合
- Objective-c学习笔记07——异常处理(try catch)
一、面向对象简介:
面向对象程序设计(英语: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:
) 是包括冒号字符在内的所有签名关键词的串联。冒号字符表明有参数存在。在上述示例中,该方法采用两个参数。如果方法没有参数,则省略第一个(也是仅有的一个)签名关键词后面的冒号。
声明如下的方法:
- - (void)setName:(NSString *)name int)age width:(int)width;
则方法名为:setName::
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];
六、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;
则可以省略。