Objective-C编程基础
Objective-C简称OC或ObjC 。是一种通用、高级、面向对象的编程语言。它扩展了标准的ANSI C编程语言,将Smalltalk式的消息传递机制加入到ANSI C中。目前主要支持的编译器有GCC和Clang(采用LLVM作为后端)。
Objective-C的商标权属于苹果公司,苹果公司也是这个编程语言的主要开发者。苹果在开发NeXTSTEP操作系统时使用了Objective-C,之后被OS X和iOS继承下来。现在Objective-C与Swift是OS X和iOS操作系统、及与其相关的API、Cocoa和Cocoa Touch的主要编程语言。
对于OC语言的学习,可以查看苹果关于OC的官方文档。我这里再提供一个官方的 OC文档中文版翻译.
环境
建议使用Mac OS系统环境。若无Mac电脑,也可以先在Windows系统下使用GNUstep搭建一个简易编译环境用于学习ObjC 编程语言。但是需要注意,Windows下的GNUstep目前仅支持ObjC 1.0,而不支持2.0的语法。详细见GNUstep官网
需下载三个文件:
- GNUstep MSYS System
- GNUstep Core
- GNUstep Devel
依次安装上述文件,注意后两个要与第一个安装在同一目录下

安装完成后,创建hello.m源文件用于测试:
#import <Foundation/Foundation.h>
int main(int argc,const char *argv[]){
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog (@"hello world");
[pool drain];
return 0;
}
ObjC 的手工编译命令较为繁琐:
gcc -o hello hello.m -I/GNUstep/System/Library/Headers -fconstant-string-class=NSConstantString -L/GNUstep/System/Library/Libraries -lobjc -lgnustep-base
Mac电脑下搭建环境,仅需要在应用商店下载安装Xcode即可。
基础语法
Objective-C可以视作C语言的超集,它完全兼容C(C99)语言的语法。因此,关于OC编程的基础语法部分,请参见C语言。这里可以学习我的《程序员的C》视频课程。
注意,OC程序的源文件后缀名是.m,m表示mssage,代表OC中最重要的一个机制,消息机制。而C语言源文件后缀名是.c。OC中,main函数仍然是OC程序的入口和出口。
数据类型

与C语言相比,OC新增了一些类型
BOOL:取值为YES/NO的布尔类型NSObject:OC中的对象类型id:动态对象类型,相当于万能指针,于Dart中的dynamic类型相似SEL:选择器数据类型- Block:代码块数据类型,相当于Dart中的闭包
与C语言相比的一些变化
- OC语言使用
#import来包含头文件。它与#include的类似,都是把头文件拷贝到该指令所在的地方,但是#import可以自动防止重复导入,相当于C语言通过定义宏来实现头文件保护 - 增加了一个关键字
@class在头文件中引用一个类。注意,仅仅是引用,并不会包含这个类的所有内容,因此你仍需要在.m文件中使用#import包含这个类的头文件。该关键字的主要意义是提升编译效率 - 使用
NSLog函数 替代printf来打印日志。NSLog是Foundation框架提供的日志输出函数,也支持格式化输出。但要注意,该函数传递的字符串参数是NSString对象,而不是char *类型的字符串。
整型占位符:
| 占位符 | 描述 |
|---|---|
| %d | 十进制整数,正数无符号,负数有“-”符号 |
| %i | 输出时,与%d相同 |
| %o | 八进制无符号整数,没有0前缀 |
| %x | 十六进制无符号整数,没有0x前缀 |
| %u | 十进制无符号整数 |
| %zd | NSInteger型专用输出 |
| %tu | 无符号NSUInteger的输出 |
浮点数占位符:
| 占位符 | 描述 |
|---|---|
| %f | 以小数形式输出浮点数,默认6位小数 |
| %e | 以指数形式输出浮点数,默认6位小数 |
| %g | 自动选择%e或者%f格式 |
其他占位符:
| 占位符 | 描述 |
|---|---|
| %c | 输出C语言单个字符 |
| %s | 输出C语言字符串 |
| %p | 输出十六进制形式的指针地址 |
| %@ | 输出OC对象,在iOS中非常常用,应牢记 |
OC语言提供了两种类型注释,分别是简单注释和文档注释。文档注释可以被appledoc识别提取成文档。
简单注释
// 这是单行注释
/*
* 这是多行注释
*/
文档注释
/// 这是单行文档注释
/**
* 这是多行文档注释
*
* @param state 状态值
* @return 返回状态描述
*/
面向对象编程
类的声明和定义
OC 中类的声明和定义是不在一起的。类声明的文件叫做接口文件,也就是C里面的.h头文件。类定义文件叫做实现文件,以.m为文件后缀名。
创建头文件Phone.h,声明一个类Phone
#import <Foundation/Foundation.h>
@interface Phone : NSObject
@end
创建Phone.m文件来实现类,完成类的定义
#import "Phone.h"
@implementation Phone
@end
实例化对象
为类创建对象这一过程被称为实例化。
Phone *phone = [[Phone alloc] init];
相当于Java中的如下语法:
Phone phone = Phone.alloc().init();
OC中类的实例化有三个步骤:
在堆内存中开辟一块新的存储空间
[Phone alloc]初始化成员变量(也叫实例变量)
[Phone init]返回对象的指针地址
有时alloc和init可以省去,用new代替:
Phone *phone = [Phone new];

注意,在OC中调用对象的方法使用的是消息机制,这与Java这类语言是不同的,调用某个对象的方法,就是向这个对象发消息。其消息发送格式为:[类名或对象名 方法名];

实例变量
成员变量也称为实例变量。实例变量需要在接口文件(即.h文件)中定义。
@interface Phone : NSObject
{
// 声明两个实例变量
@public int num;
NSString* name;
}
@end
使用实例变量
Phone *phone = [[Phone alloc] init];
phone->num = 10;
NSLog(@"phone num is %d",phone->num);
可以通过对象->实例变量的方式访问对象中声明的这些成员(在外部访问,则声明时需要加@public修饰) 。
实例变量、局部变量、全局变量的区别:

方法的声明与实现
方法的声明是在类的.h文件中进行的,格式如下:

实现方法是指在方法的定义中编写该方法的具体代码,格式如下:

方法声明:
@interface Phone : NSObject
{
// 声明两个实例变量
@public int num;
NSString* name;
}
// 声明一个方法
-(void)print;
@end
方法实现:
@implementation Phone
-(void)print
{
NSLog(@"%i",num);
}
@end
方法调用:
Phone *phone = [[Phone alloc] init];
phone->num = 10;
[phone print];
小结:
-修饰表示实例方法,+修饰表示类方法- 方法的返回值类型必须要用括号括起来,在声明完方法后必须加上分号结束
- 实例方法法只能由实例对象来调用,并且可以访问当前对象的成员变量
- 方法实现,即声明原型的后面加上
{},并在花括号中编写具体代码表示方法实现 - 实例方法调用格式为:
[实例对象 方法名];
实例方法和类方法的区别
- 实例方法是属于实例对象,以
-开头,能访问实例变量 - 类方法是属于类的,以
+开头,只能用类名调用,实例对象不能调用,且类方法中不能直接访问实例变量,亦不能直接调用实例方法 - 类方法和实例方法可以同名
OC中的所谓的类方法,即相当于Java类中的静态方法(static修饰的方法),通常用于无需成员变量,编写相关工具方法的场景下。
方法的参数
一个参数的方法

方法声明:
-(BOOL)call:(NSString*) number;
方法定义:
-(BOOL)call:(NSString*) number
{
NSLog(@"call number is %@",number);
return YES;
}
方法调用:
[phone call:@"911"];
多个参数的方法
多个参数的方法原型有两种声明方式,首先来看最简单的,其原型格式如下:
-(返回值类型)方法名:(参数类型)形参1 :(参数类型)形参2 :(参数类型)形参3
{
方法实现的代码;
}
多参数方法的声明:
// 三个整数的加法
-(int)add:(int)n1 :(int)n2 :(int)n3;
多参数方法的定义:
-(int)add:(int)n1 :(int)n2 :(int)n3
{
return n1 + n2 + n3;
}
多参数方法的调用:
int sum = [test add:3 :4 :5];
NSLog(@"sum is %d",sum);
OC语言是一种语义表达明确,可读性极高的编程语言。其可读性就体现在第二种多参数方法原型的声明上,这种方法原型与第一种相比,多了一种对参数的描述标签:
-(返回值类型)方法名:(参数类型)形参1 标签1:(参数类型)形参2 标签2:(参数类型)形参3
{
方法实现的代码;
}
使用第二种方式对上例进行改造:
// 多参数带参数标签的声明
-(int)addWith:(int)n1 andNum:(int)n2 andNum:(int)n3;
实现:
-(int)addWith:(int)n1 andNum:(int)n2 andNum:(int)n3
{
return n1 + n2 + n3;
}
调用:
int sum = [test addWith:1 andNum:3 andNum:5];
NSLog(@"sum is %d",sum);

类的属性
在OC中,可以使用属性来提高代码编写的速度和直观性。属性提供了便捷的设置和获取实例变量的方式。
属性,是指由@property修饰的成员变量。在于类以外的代码访问时,可以通过属性来访问内部变量,而不能直接访问类的成员。
简单说,OC的所谓属性,就相当于Java中的setter、getter方法,Dart中的set、get方法。
TSPerson.h文件:
#import <Foundation/Foundation.h>
@interface TSPerson : NSObject
{
int _age;
NSString *_name;
}
@property int age;
@property NSString *name;
@end
TSPerson.m文件:
#import "TSPerson.h"
@implementation TSPerson
@synthesize age=_age;
@synthesize name=_name;
@end
使用:
TSPerson *person = [[TSPerson alloc] init];
person.age = 18;
[person setName:@"张三"];
NSLog(@"person age=%d, name=%@",person.age,person.name);
以上代码作用,等同于以下Dart代码:
class TSPerson{
int _age;
String _name;
get age=>_age;
set age(int age){
_age = age;
}
// ......
}
小结:
- 头文件中使用
@property修饰声明一个属性 - 定义文件中使用
@synthesize修饰来实现一个属性,则会自动生成该属性对应的setter方法,形如setAge,以及getter方法,形如age。上例中我们将属性绑定到了一个对应的_开头的成员变量上,这是苹果推荐的编码风格,内部成员变量以下划线开头,然后通过属性绑定对外暴露。如果成员变量与属性同名,则无需绑定的步骤。 - 定义属性之后,有两种方式可以访问,一种通过消息机制调用对象的方法
[person setName:@"张三"];,一种是通过语法糖.访问person.age = 18;。
注意,以上是完整的写法,在Xcode4.4之后,已经简化了属性的使用,只需要在头文件使用@property声明属性即可,而无需使用@synthesize,亦无需声明一个下划线开头的成员变量,工具会自动生成一个以下划线开头,与属性名相同的成员变量。
由于自动生成的setter、getter方法过于简单,很多时候我们可能需要做一些处理,对参数进行过滤等,这时候我们只需要自行重写一个setter、getter方法即可。
属性的修饰符
- 修饰是否生成getter方法
readonly只生成getter方法,不生成setter方法readwrite既生成getter 又生成setter方法(默认)
@property (readonly) int age;
- 指定所生成的方法的方法名称
- getter=定义的getter方法名称
- setter=定义的setter方法名称(setter方法必须要有
:)
// 通常BOOL类型的属性的getter方法以is开头
@property (getter=isMarried) BOOL married;
// 指定的方法名后面跟":"
@property(setter=myWeight:) double weight;
类的构造方法
在OC的类中,以init方法作为构造方法。构造方法的定义有一个固定的模版,必须按照此模版定义:
@implementation Person
- (instancetype)init
{
self = [super init];
if (self) {
// 完成初始化
_age = 18;
}
return self;
}
@end
重写构造方法,需要注意:
- 必须先初始化父类, 再初始化子类
- 必须判断父类是否初始化成功, 只有父类初始化成功才能继续初始化子类
- 构造方法完成初始化后,必须返回当前对象的指针
- 构造方法的返回值使用
instancetype(在更早的一些版本可能会使用id类型做返回值,现在一律使用instancetype) self相当于其他语言的this指针,不同的是,self在类方法中使用, 那么self就代表当前类,可以调用其他类方法;如果self在实例方法中使用,则self代表当前实例,可以调用实例方法。
自定义构造方法
默认的构造方法往往无法满足我们的需求,很多时候我们需要在创建对象的时候传参,这时候就需要自定义构造方法。
- (instancetype)initWithName:(NSString *)name
{
if (self =[super init]) {
_name = name;
}
return self;
}
注意,自定义的构造方法通常initWith开头,其多个参数的声明,与普通方法的一致。
类的继承
OC中的类通常会继承NSObject类,当我们用Xcode工具创建类时,直接会帮我们自动继承。这里NSObject就是超类,语法如下:
@interface Phone : NSObject
@end
当我们需要继承自定义的基类时,亦使用:继承语法。一个子类继承了某个父类,那么子类就拥有了父类的所有属性和方法(同Java一样,可以继承实例方法和类方法)。需要注意的是, 在继承中方法可以重写, 但是属性(成员变量)不能重写。
OC中也有super用于调用父类中的方法,但和self不同,它不是一关键字,而是一个编译器的指令符号。并且需要注意,当super在实例方法中时,那么就调用父类的实例方法,而当super在类方法中,那么就会调用父类的类方法。
在Java中,我们直接打印一个对象实例时,可能会输出hash地址,通常需要重写toString方法实现对象打印,OC中也有对应的机制,只不过是重写NSObject的description方法
-(NSString *) description
{
return [NSString stringWithFormat:@"num=%d,name=%d\n",_num,_name];
}
多态
OC中的多态和Java中基本相同(实例化时,父类指向子类):
Animal *animal = nil;
animal = [Cat new];
[animal eat];
animal = [Dog new];
[animal eat];
注意,存在多态时,父类想要调用子类特有的方法,需要进行强制类型转换:
// 子类有一个特有的方法
[dog bark];
Animal *an2 = [Dog new];
//把父类的指针进行强制类型转换
[(Dog*)an2 bark];
封装
面向对象编程中,除了类、方法的封装,通常还会对成员变量进行访问权限控制,以封装数据。OC中提供了四种访问权限修饰符:
| 修饰符 | 类内部 | 同一包中 | 子类 | 全局范围内 |
|---|---|---|---|---|
@private |
√ | |||
@package |
√ | √ | ||
@protected |
√ | √ | ||
@public |
√ | √ | √ | √ |
需要注意,@package修饰,如果是在其它包中访问那么就相当于@private,如果是在当前代码所在的包种访问就是@public的。OC中默认情况下的实例变量都是@protected修饰的。
@interface Person : NSObject
{
@private
int _age;
NSString *_name;
@public
NSString *address;
int uid;
}
Category
一般翻译为分类或类别。可以在不修改原来类的基础上, 为这个类扩展一些方法,可以让一个庞大的类分模块多人开发。其功能相当于Dart语言中的扩展方法。
如下例:
// Dart示例
class Person{}
/// 在不修改Person类前提下,为其增加一个toStr方法
extension PersonEx on Person {
String toStr(){
return "这是Person类的扩展方法!";
}
}
OC代码实现:
Person+PersonEx.h
#import "Person.h"
@interface Person (PersonEx)
-(NSString*)toStr;
@end
Person+PersonEx.m
#import "Person+PersonEx.h"
@implementation Person (PersonEx)
-(NSString*)toStr
{
return @"OC的分类方法";
}
@end
Category的声明格式:
@interface 原类名 (分类名)
//在类别中添加方法
//但不允许在类别中添加变量
@end
小结:
分类只能增加方法, 不能增加成员变量
分类可以访问原类中的成员变量
如果分类和原类存在同名方法, 则优先调用分类中的, 忽略原类中的方法
如果省略分类名,则称为匿名分类(或Class Extension)。它可以为某个类扩充一些私有成员变量和方法
#import "Person.h" // 匿名分类写在.m文件中 @interface Person () { int _age; } - (void)say; @end @implementation Person - (void)say { NSLog(@"age = %i", _age); } @end可以在匿名分类中遵守协议
协议
协议(Protocol)主要用来声明一些方法。OC中的协议,相当于Java语言中的接口。 一个Protocol是由一系列的方法声明组成的。参见协议文档
声明一个协议:
@protocol MyProtocol <NSObject>
- (void)play;
@end
让一个类遵守协议(头文件):
@interface Studnet : NSObject<MyProtocol> // 遵守协议
@end
实现协议:
@implementation Student
// 实现方法
- (void)play
{
NSLog(@"%s", __func__);
}
@end
协议中声明方法时可以使用两个关键字修饰,@required和@optional,其中默认就是@required的,表示遵循协议的类必须实现该方法。如果某些方法不一定非要实现,则用@optional修饰,表示可选的:
@protocol MyProtocol <NSObject>
@required
- (void)play;
@optional
- (void)greet;
@end
在声明一个自定义类时,我们知道必须继承一个基类NSObject,那么这里还有一个基协议也叫NSObject,我们自定义的协议遵守NSObject基协议。
注意,一个协议也可以遵守另一些协议,这类似于Java中的接口继承:
@protocol A <NSObject>
-(void)methodA;
@end
// B协议遵守A协议
@protocol B <A>
-(void)methodB;
@end
@interface Student : NSObject <B>
// 同时拥有A、B协议中的方法声明
@end
小结:
协议只有声明没有实现
任何类只要遵守了某个协议,就相当于拥有了协议中的所有方法声明
父类遵守了某个协议,那么子类也遵守了
一个协议可以遵守其他的协议
OC类只能单继承,协议可以遵守多个:
#import "SportProtocol.h" #import "StudyProtocol.h" @interface Student : NSObject <SportProtocol, StudyProtocol> @endOC中的协议可以类似于Java中泛型一样,进行类型限制(格式:
数据类型<协议名称> 变量名):// 如果Student没有遵守协议则会报警告 id<SportProtocol> student = [[Student alloc] init];
公众号“编程之路从0到1”