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

依次安装上述文件,注意后两个要与第一个安装在同一目录下

2021-04-09-001

安装完成后,创建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来打印日志。NSLogFoundation框架提供的日志输出函数,也支持格式化输出。但要注意,该函数传递的字符串参数是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中类的实例化有三个步骤:

  1. 在堆内存中开辟一块新的存储空间

    [Phone alloc]
    
  2. 初始化成员变量(也叫实例变量)

    [Phone init]
    
  3. 返回对象的指针地址

有时allocinit可以省去,用new代替:

Phone *phone = [Phone new];

cjygl

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

xxjz

实例变量

成员变量也称为实例变量。实例变量需要在接口文件(即.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修饰) 。

实例变量、局部变量、全局变量的区别:

20210410215916

方法的声明与实现

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

20210410220515

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

20210410220848

方法声明:

@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修饰的方法),通常用于无需成员变量,编写相关工具方法的场景下。

方法的参数

一个参数的方法

20210410230419

方法声明:

-(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);

xxffsm

类的属性

在OC中,可以使用属性来提高代码编写的速度和直观性。属性提供了便捷的设置和获取实例变量的方式。

属性,是指由@property修饰的成员变量。在于类以外的代码访问时,可以通过属性来访问内部变量,而不能直接访问类的成员。

简单说,OC的所谓属性,就相当于Java中的setter、getter方法,Dart中的setget方法。

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

重写构造方法,需要注意:

  1. 必须先初始化父类, 再初始化子类
  2. 必须判断父类是否初始化成功, 只有父类初始化成功才能继续初始化子类
  3. 构造方法完成初始化后,必须返回当前对象的指针
  4. 构造方法的返回值使用instancetype(在更早的一些版本可能会使用id类型做返回值,现在一律使用instancetype
  5. 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中也有对应的机制,只不过是重写NSObjectdescription方法

-(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

小结:

  1. 分类只能增加方法, 不能增加成员变量

  2. 分类可以访问原类中的成员变量

  3. 如果分类和原类存在同名方法, 则优先调用分类中的, 忽略原类中的方法

  4. 如果省略分类名,则称为匿名分类(或Class Extension)。它可以为某个类扩充一些私有成员变量和方法

    #import "Person.h"
    // 匿名分类写在.m文件中
    @interface Person ()
    {
        int _age;
    }
    
    - (void)say;
    @end
    
    @implementation Person
    - (void)say
    {
        NSLog(@"age = %i", _age);
    }
    @end
    
  5. 可以在匿名分类中遵守协议

协议

协议(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>
    
    @end
    
  • OC中的协议可以类似于Java中泛型一样,进行类型限制(格式:数据类型<协议名称> 变量名):

    // 如果Student没有遵守协议则会报警告
    id<SportProtocol> student = [[Student alloc] init];
    

公众号“编程之路从0到1”

20190301102949549

Copyright © Arcticfox 2021 all right reserved,powered by Gitbook文档修订于: 2022-05-01 12:20:20

results matching ""

    No results matching ""