iOS极速入门教程(一):ObjectC基础知识及iOS mini工程实践

简介

本文介绍ObjectC基础知识,然后通过一个iOS mini工程实践iOS开发。本教程适合有一定编程经验但对iOS零基础的人,帮助你在最短时间内迈进iOS
开发大门。本文由yanzi原创,欢迎大家转载.

Object-C概述

Object-C(后面简称OC),是基于C语言的一种最小面向对象的语言,也即面向对象精髓它都有,但是又没有C++那么复杂。用它可以开发在macOS(桌面电脑后缀为app)和iOS(移动平台后缀为ipa)的程序。当然后面又出来了swift干同样的事就先不讨论了。为了对OC有个快速认识下面先介绍语法核心特点,后面再分而述之。

  1. 没有包名(命令空间)的概念,也即没有java里com.yanzi.XxxPerson和C++里的namespace如std::cout这种东西。但是没有包名就会遇到类重名的问题,所以就有了命名前缀的出现,如Java里字符串是String类,在iOS里是NSString, 这个NS就是前缀。

  2. 关键字都以@开头。原因是OC里可以混用C和C++,为了区分关键字所以OC里都以@开头。常见的关键字有:@public @protected @private @interface @implementation @end等。另外字符串也是以@开头,如@"Hello"是OC里的字符串,而"Hello"是C++里字符串。

  3. 数据类型:BOOL(类似java的boolean)其值是YES或NO,nil等于java里的null,self等于java里this,所有类的基类是NSObject等于java里的Object.

  4. 打印东西用NSLog,在C里是printf("Hello World!")在OC里是NSLog(@"Hello World!")

下面上国际惯例HelloWorld(macOS 控制台工程):
打开XCode点击新建工程:

我们选择macOS控制台工程:

然后输入工程名,语言选择OC:

之后就ok了,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//
// main.m
// OC-HelloWorld
//
// Created by yanzi on 2018/8/26.
// Copyright © 2018年 yanzi. All rights reserved.
//
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}

关于这个HelloWorld要点如下:

  1. OC的程序源文件以.m结尾,入口函数还是main
  2. OC里引入头文件是import,后面的<>表示系统头文件。import比C的include函数高级一些,会自动检测头文件是否导入,这意味着OC里的头文件不需要像C里面写ifndef ... def endef这种东西了.
  3. Foundation是macOS和iOS的基础底层框架,NSObject就在这里面定义.先盗一张iOS的框架构成图放在这
  4. @autoreleasepool既然是以@开头表示它是个关键字,和OC的内存管理有关.早期OC不支持内存自动回收,意味着写代码的人要非常严谨,但同时也要写一堆垃圾代码回收对象,所以后面有了autorerencecollect后面简称ARC就是自动回收机制。但实际上这个垃圾回收机制是半自动,并不是全自动后面再说.
  5. NSLog就是C里的printf,强大的地方在于能自动换行打印的东西带时间戳.
    OK关于HelloWorld解释完毕。

OC基础语法

第一个OC类

和C++类似,OC里一个类需要一个.h和一个.m文件才可以,前者是定义变量和方法,后者是实现.下面创建个Person类,成员变量有age和weight都是int的,然后分别实现set和get方法。创建方法如下:

Person.h文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//
// Person.h
// OC-HelloWorld
//
// Created by yanzi on 2018/8/26.
// Copyright © 2018年 yanzi. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface Person : NSObject{
int age;
int weight;
}
//age的动态get方法
- (int)age;
//age的set方法
- (void)setAge:(int)_age;
- (int)weight;
- (void)setAge:(int)_age andWeight:(int)_weight;
//+号表示静态方法,类似java里的static
+ (void)debug;
@end

Person.m文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//
// Person.m
// OC-HelloWorld
//
// Created by yanzi on 2018/8/26.
// Copyright © 2018年 yanzi. All rights reserved.
//
#import "Person.h"
@implementation Person
- (int)age{
return age;
}
- (int)weight{
return weight;
}
- (void)setAge:(int)_age{
age = _age;
}
- (void)setAge :(int)_age andWeight :(int)_weight{
age = _age;
weight = _weight;
}
+ (void)debug{
NSLog(@"I am only test for static method!");
}
@end

主程序main.m:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//
// main.m
// OC-HelloWorld
//
// Created by yanzi on 2018/8/26.
// Copyright © 2018年 yanzi. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Student.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
//创建一个类需要两步
//1.调用NSObject的静态方法分配内存
Person *per = [Person alloc];
//2.调用NSObject的动态方法初始化
per = [per init];
[per setAge:100];
int age = [per age];
NSLog(@"age = %i", age);
//上面两步可以精简为1步
Person *per2 = [[Person alloc] init];
[per2 setAge:101 andWeight:999];
NSLog(@"age = %i, weight = %i", [per2 age], [per2 weight]);
//调用静态方法
[Person debug];
}
return 0;
}

要点如下:

  1. OC里所有类都是NSObject的子类
  2. 申明一个类需要在头文件里用@interface,继承用:,类的成员变量写在@interface后的{}里,方法则写在{}外面,头文件结束用@end
  3. 类的实现写在.m文件里,且以关键字@impletation ClassXXX开始,然后就是各个方法的实现。最后以@end结尾.所有实现的外面不需要像Java那样用{}扩起来.
  4. OC里动态方法用-,静态方法用+表示(等价于java里用static修饰)。
  5. OC里类调用方法:[per setAge:10],使用中括号扩起来的,前面是对象,后面是方法,方法后面跟冒号,一个冒号后面传一个参数。
  6. 接下来重点说下OC里如何创建一个对象,在java里是这样的:
    1
    2
    Person per = new Person();
    per.setAge(10);

但在OC里要创建一个对象必须经过两步:第一步分配内存,调的是NSObject的静态方法alloc,然后调用NSObject的动态方法init去初始化。合二为一就是这么写的:

1
Person *per2 = [[Person alloc] init];

这里[Person alloc]执行完毕后返回的是一个id类型的指针,这个指针可以指向任何对象,类似C里的Void *和Java里的模版,然后调用init后返回的还是一个id类型的指针。上面这句代码的等号右边等于开辟了一块内存存Person,等号左右per2是个指针变量,它指向创建的Person对象。但是为了后面理解方便,一般可以认为per2就是个对象,把它当对象用就对了。

  1. 如果一个方法传两个参数,java里这样per.updateInfo(100, 89)就行了,但在OC里得这么定义:
1
- (void)setAge:(int)_age andWeight:(int)_weight;

即冒号后面是参数,参数类型必须用()扩起来,且:是方法名的一部分。这个函数名字可以分多段,中间用空格隔开。实现:

1
2
3
4
- (void)setAge :(int)_age andWeight :(int)_weight{
age = _age;
weight = _weight;
}

使用的话这样:

1
2
Person *per2 = [[Person alloc] init];
[per2 setAge:101 andWeight:999];

关于第一个类就解释这么多。

OC的点语法

在Java里可以通过per.age = 100修改Person这个对象的成员变量age的值,但是在OC里我们上面是这么写的:

1
2
[per setAge:100];
int age = [per age];

事实上上面两句可以直接简化为:

1
2
per2.age = 100; //等效于[per setAge:100]
int age = per2.age; //等效于int age = [per age]

但是和Java不同的是,OC这里并不是访问类的成员变量,而是编译器判断per2.age在等号左边,会把age变成Age然后前面加个set也就是会去找setAge这个方法。当编译器判断per2.age在等号右边也就是取值的时候,会自动找[per age]这个方法。可以通过在setAgeage这两个方法增加打印信息得到验证。如果你不实现setAge而代码里调用per.age=10则会直接编不过。
总而言之一句话:OC的点语法并不是访问的成员变量,而是编译器自动修改这条语句为setXxx,调用的是get方法或get方法。究竟是get和set取决于这个语句在等号的哪边。

为了不让这个点语法产生误解,OC建议所有的成员变量尽量以下划线开头,如_age,这样仍然通过per.age=10,免得让人误以为访问的是成员变量。另外,这个点语法也解释了为什么set方法实现时不能写成self.age = age。如果写成这样中间有点,这个函数会翻译成[self setAge],这样set方法就进入了死循环!!!

自定义构造函数和重写description函数

Person.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import <Foundation/Foundation.h>
@interface Person : NSObject{
int _age;
int _no;
}
//声明一个构造函数
- (id)initWidthAge:(int)age :(int)no;
- (void)setAge:(int)age;
- (int)age;
- (void)setNo:(int)no;
- (int)no;
@end

Person.m如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#import "Person.h"
@implementation Person
- (id)initWidthAge:(int)age :(int)no{
if(self = [super init]){
_age = age;
_no = no;
}
return self;
}
- (void)setAge:(int)age{
_age = age;
}
- (int)age{
return _age;
}
- (void)setNo: (int)no{
_no = no;
}
- (int)no{
return _no;
}
- (NSString *)description{
NSString *des = [NSString stringWithFormat:@"age = %i, no = %i", _age, _no];
return des;
}
@end

main.m如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//
// main.m
// OC-HelloWorld
//
// Created by yanzi on 2018/8/26.
// Copyright © 2018年 yanzi. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *per = [[Person alloc]initWidthAge :12 :23];
NSLog(@"%@", per);
}
return 0;
}

先重申三条OC的要点:

  1. 先重申一点:OC里在在定义函数或实现函数时的函数体,只要是带类型必然带括号括起来
  2. 入参用:params1,不需要带括号
  3. OC里只要用对象就少不了*

关于上面这段程序要点如下:

  1. 构造函数声明:
    1
    2
    //声明一个构造函数
    - (id)initWidthAge:(int)age :(int)no;

直接返回的是一个id类型对象,可以被转为任何对象的指针。一般构造函数用init开头,另外就是:是函数体的一部分,但空格不是,也就是说- (void)age- (void)age:是两个不同的函数。
构造函数实现:

1
2
3
4
5
6
7
- (id)initWidthAge:(int)age :(int)no{
if(self = [super init]){
_age = age;
_no = no;
}
return self;
}

这里先调用父类superinit方法给self,如果不为空则进行参数赋值,最后再将self返回出去。

  1. Java里的toString()函数在OC里就是description函数,该函数返回一个NSString类型的指针。
  2. OC里打印一个对象用%@格式化,如果不重写description函数将打印指针地址,否则打印description返回值。

    变量作用域和new关键字

    Java里作用域有四种:private、protected、public、包权限四种,但是因为OC里没有包的概念所以只有三种:@private@protected@public,且如果什么都不加则默认是@protected.
    • @private:只能在类内部访问
    • @protected:在类和子类里都可以访问
    • @public:全局都可以访问。
      如下面代码定义两个变量为private:
      1
      2
      3
      4
      5
      @interface Person : NSObject{
      @private
      int _age;
      int _no;
      }

另外,如果一个方法没有在头文件声明而直接在m文件实现了,则这个方法是私有方法。在Java里static方法是不能访问this变量的,但在OC里静态方法可以访问self,表示这个类。
new关键字用法:
Person *per = [Person new]这句代码等于Person *per = [[Person alloc]init],也即用new关键字即分配了内存也完成了初始化,返回一个可使用的对象。

@property@synthesize用法

首先@property表示属性一定是输出在声明文件里,@synthesize表示实现一定出现在m文件里。先来看为啥需要@property关键字。当写了两个变量都要写setXXXxxx,也即写个getter和setter,且这种都是垃圾代码意义不是很大,但要不可少。用这个关键字@property声明的变量自动为我们声明setter和getter方法:

1
2
//注意这个关键字不能在{}里
@property int age;

上面这句话等于下面三句话:

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

注意:@property出现在h文件里仅仅是帮我们声明函数,具体实现还要我们自己实现(xcode4.5之前)。
在m里自己实现setXXXxxx方法,但这其实也是一堆垃圾代码,所有有了关键字@synthesize(合成的意思):

1
2
//注意该关键字必须出现在m文件里
@synthesize age;

上面一句话可以完全代替下面好几句:

1
2
3
4
5
6
7
- (void)setAge:(int)age1{
age = age1;
}
- (void)age:{
return age;
}

注意:@synthesize xxx,这个xxx必须是通过@property声明的变量。

而且@synthesize xxx还有个特点,会自动检测头文件里有没有声明xxx变量,如果没有的话将自动给你声明一个xxx变量。前面说过OC里推荐类里的变量都用_xxx表示,所以可以这样写@synthesize age = _age,这样将自动检测头文件里有没有声明_age变量,没有的话则会自动创建。

也就是说要增加一个变量且自动实现getter和setter需要在头文件里写@property int age,且在m文件里写@synthesize age = _age这样两句话,这还是有点麻烦。所以xcode4.5之后只需要在m文件里写@property int age,m文件里什么都不需要写,xcode将会自动为你做以下几件事:

  1. 检测有没有声明_age变量,没有的话自动给你声明;
  2. 自动头文件给你声明setter方法setAge和getter方法age
  3. 自动在m文件里实现setter方法setAge和getter方法age
    为了防晕总结如下:
  4. @property出现在头文件里,(xcode4.5后)自动创建的变量是带下划线_的;
  5. @synthesize xxx后面跟的必须是头文件里用@property声明的变量,它自动创建的是不带下划线的变量,但是可以指定:@property xxx = _xxx
  6. xcode4.5后只用@property就可以自动完成变量声明和实现setter和getter,前提里m文件里不要再写@synthesize
  7. 无论是@property还是@synthesize准确说并不是OC的语法特性,而是xcode编辑器特性。

OC内存管理:@retain@release

任何继承自NSObject的对象都涉及到内存管理,因OC没有GC机制,每个对象内部都有一个引用计数器,当使用allocnewcopy创建一个对象时,引用计数器数值被置为1,给对象发retain消息则计数器加1,发release消息则减一。即便这个对象在局部函数里,也同样适用,如果不释放则会内存泄漏。当计数器为0则会被回收,OC会向这个对象发个dealloc消息。可以通过调用对象的retainCount得到其引用计数器的值。
注意:OC自带的类,调用其静态方法创建的对象默认有自动释放机制,不用手动释放。
OC内存管理的两个基本原则:

  1. 谁创建谁释放:谁调的allocnewcopy,谁就要releaeautorelease
  2. 谁污染谁治理:谁retain的谁负责release
    下面看一个经典例子,有个类Person,给每个Person都发一本书Book,这两个都是对象。为了手动管理其内存,我们需要先把XCode给我们打开的ARC(auto reference count)关掉:

    代码如下:
    Book.h代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //
    // Book.h
    // OC-HelloWorld
    //
    // Created by yanzi on 2018/9/8.
    // Copyright © 2018年 yanzi. All rights reserved.
    //
    #import <Foundation/Foundation.h>
    @interface Book : NSObject
    @property int price;
    - (id)initBookWithPrice:(int)price;
    @end

Book.m如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//
// Book.m
// OC-HelloWorld
//
// Created by yanzi on 2018/9/8.
// Copyright © 2018年 yanzi. All rights reserved.
//
#import "Book.h"
@implementation Book
- (id)initBookWithPrice:(int)price{
if(self = [super init]){
_price = price;
}
return self;
}
- (void)dealloc{
NSLog(@"price = %i的book被销毁", self.price);
[super dealloc];
}
@end

Person.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//
// Person.h
// OC-HelloWorld
//
// Created by yanzi on 2018/9/8.
// Copyright © 2018年 yanzi. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "Book.h"
@interface Person : NSObject
@property NSString* name;
@property Book* book;
- (id) initPersonWithName:(NSString*)name;
@end

Person.m如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//
// Person.m
// OC-HelloWorld
//
// Created by yanzi on 2018/9/8.
// Copyright © 2018年 yanzi. All rights reserved.
//
#import "Person.h"
@implementation Person
#pragma -mark 构造方法
- (id)initPersonWithName:(NSString *)name{
if(self = [super init]){
_name = name;
}
return self;
}
- (void)setBook:(Book *)book{
if(_book != book){
[_book release];
_book = [book retain];
}
}
- (void)dealloc{
[_book release];
NSLog(@"name = %@的对象释放了", _name);
[super dealloc];
}
@end

主程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//
// main.m
// OC-HelloWorld
//
// Created by yanzi on 2018/8/26.
// Copyright © 2018年 yanzi. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *per = [[Person alloc]initPersonWithName:@"张三"];
Book *book = [[Book alloc]initBookWithPrice:15];
per.book = book;
[book release];
[per release];
}
return 0;
}

要点如下:

  1. 如果使用@property Book* book;什么都不改的话是标准的gettersetter,如果仅重写了setter方法,它还是会默认生成_book变量,但是如果gettersetter都重写了,将不会再生成_book变量。一般情况下我们只需要重写getter方法;
  2. 对象之间setter方法的内存管理方法要点是:
    1
    2
    3
    4
    5
    6
    - (void)setBook:(Book *)book{
    if(_book != book){
    [_book release];
    _book = [book retain];
    }
    }

先释放老的,然后retain新的。最后本着谁污染谁治理的原则,在Person释放dealloc的时候释放自己持有的对象。

  1. 在OC里[nil release]是不会报错的。如果新创建的对象执行两次[per release]则会报野指针错误EXC_BAD_ACCESS,即访问不属于自己的内存。

@class关键字使用

接上面的例子,在Person.h里我们为了使用Book这个对象,需要#import "Book.h"这句代码。导入这句话意味着把Book.h东西都拷到Person.h里,有性能上的拖累。事实上只需要在Person.h里告诉Person存在一个类叫Book即可,不需要知道Book有多少方法或变量。
改良后的Person.h写法如下:

1
2
3
4
5
6
7
8
9
10
11
#import <Foundation/Foundation.h>
@class Book;
@interface Person : NSObject
@property NSString* name;
@property Book* book;
- (id) initPersonWithName:(NSString*)name;
@end

即把@import "Book.h"换成了@class Book。但是继承一个类的时候父类是不能用@class的,因为继承一个对象必须知道对象里有多少方法。还有一种场景非用@class不可,即A类里有个变量B, B这个类里有个变量A,如果通过@import导入对象头文件则会循环导入报错。
@import@class对比如下:

  1. @import方式会包含被引用类的所有信息,包括变量和方法;
  2. @class只是告诉编译器在A.h种有这么一个类B *b,这个类里有什么信息不需要知道,等实现文件要用到的时候才会去查看;
  3. 如果上百个头文件都@import同一个文件,当这个头文件稍有改动,则依赖它的所有文件都将重新编译一次。@class则不会有这个问题.
  4. 循环依赖只能使用@class

@property参数使用

接上面Person使用Book需要重写setter方法才能满足内存管理要求,还是太麻烦了,事实上可以通过下面一句话取代:

1
2
//retain关键字这里代表release旧值,retain新值。
@property(retain) Book* book;

上面一句话等于下面这一段:

1
2
3
4
5
6
- (void)setBook:(Book *)book{
if(_book != book){
[_book release];
_book = [book retain];
}
}

格式:@property(参数1,参数2) 类型 名字,其中的参数分为3类:

  1. 读写属性:readwrite readonly
  2. setter处理:assign retain copy
  3. 原子性:atomic nonatomic
    而默认的@property int age等于@property(assign, readwrite, atomic) int age:
  • assign:标准的setter和getter,不带retain
  • readwrite:既有setter也有getter
  • atomic:表示原子,即多线程访问安全,有性能消耗
    在很多情况下我们不需要多线程安全且只有getter不能setter,则用:@property(readonly, nonatomic) int age即可。另外,还可以指定getter和setter:
    1
    @property(getter=isRich) BOOL rich;

上述代码表示赋值的时候通过per.rich=YES即可,但是getter的时候通过per.isRich来进行。

autoreleasepoolautorelease使用及优化后的构造函数

新建的main函数里有这个玩意:

1
2
3
@autoreleasepool{
}

关键字@autoreleasepool表示这是一个自动释放池,这是OC里内存自动回收机制,一般可以把一些临时变量添加到自动释放池里(调用这个变量的autorelease方法)。当池子销毁时,池子里所有变量都会调用各自的release方法一次。要点如下:

  1. 一个iOS程序可以有多个自动释放池,写一个大括号就表示新建个池子.
  2. OC对象只要发送一条autorelease消息,就会把这个变量添加到最近的(栈顶)自动释放池中
  3. autorelease只是把release延迟了,等待该pool销毁的时候里面的所有对象做一次release操作。
    示例如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #import <Foundation/Foundation.h>
    #import "Person.h"
    #import "Book.h"
    int main(int argc, const char * argv[]) {
    @autoreleasepool {
    Person *per = [[Person alloc]initPersonWithName:@"张三"];
    Book *book = [[Book alloc]initBookWithPrice:15];
    [book autorelease];
    [per autorelease];
    }
    return 0;
    }

事实上autorelease返回的是对象本身,为了简便我们可以这么创建变量:

1
2
3
4
5
6
7
8
9
10
11
12
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Book.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *per = [[[Person alloc]initPersonWithName:@"张三"]autorelease];
Book *book = [[[Book alloc]initBookWithPrice:15]autorelease];
}
return 0;
}

即创建的时候就把放到自动释放池里。为了更好看,我们通常把allocinitautorelease都放到构造函数里:
头文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property NSString* name;
@property(getter=isRich) BOOL rich;
- (id) initPersonWithName:(NSString*)name;
//下面两个静态构造函数我们新增的
+ (id)person;
+ (id)personWithName:(NSString*)name;
@end

m文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#import "Person.h"
@implementation Person
#pragma -mark 构造方法
- (id)initPersonWithName:(NSString *)name{
if(self = [super init]){
_name = name;
}
return self;
}
+ (id)person{
Person *per = [[[Person alloc]init]autorelease];
return per;
}
+ (id)personWithName:(NSString *)name{
Person *per = [[[Person alloc]init]autorelease];
per.name = name;
return per;
}

主文件测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
#import <Foundation/Foundation.h>
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person* per = [Person personWithName:@"李四"];
NSLog(@"per.name = %@", per.name);
}
return 0;
}

要点如下:

  1. 构造函数是静态的;
  2. 创造后直接autorelease
    此外关于自动释放池还应注意以下几点:
  3. 不要把大量循环操作放在一个自动释放池里,会造成内存峰值上升;
  4. 尽量不要对大内存使用自动释放,而应该精准手动管理

Category使用

可扩展性是面向对象的基本特征,在Java里想扩展的话大家会想到继承。但在OC里有一种与众不同的方法,可以为已经存在的类添加新的行为(方法),这种方式就叫Category(分类)。通过Category可以不用创建子类就达到扩展的目的,保证原始类的设计规模较小。
在上面例子基础上假设我们要为Person这个类添加一个Category叫Good,则Person+Good.h如下:

1
2
3
4
5
6
7
#import <Foundation/Foundation.h>
#import "Person.h"
@interface Person(Good)
- (void)study;
@end

Person+Good.m内容如下:

1
2
3
4
5
6
7
8
#import "Person+Good.h"
@implementation Person(Good)
- (void)study{
NSLog(@"I am good person, this is study method!");
}
@end

在main.m里测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Person+Good.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person* per = [Person personWithName:@"李四"];
NSLog(@"per.name = %@", per.name);
[per study];
}
return 0;
}

要点如下:

  1. 如果我们给类Person添加的Category叫Good,则头文件和实现名字一般取为Person+Good,这表示给类Person添加了一个叫Good的分类.
  2. 在分类的头文件里是这么写的:
    1
    2
    3
    4
    5
    6
    7
    #import <Foundation/Foundation.h>
    #import "Person.h"
    @interface Person(Good)
    - (void)study;
    @end

注意不管你给哪个类添加分类,@interface后面跟的都是类名,@interface Person:NSObject这里冒号表示继承。而分类是@interface Person(Good)括号里就是分类的名字。分类的实现在m文件里:

1
2
3
4
5
6
7
8
#import "Person+Good.h"
@implementation Person(Good)
- (void)study{
NSLog(@"I am good person, this is study method!");
}
@end

和申明一样也是类名后面跟括号。

  1. 如何使用分类,只需要在main.m里 把Person+Good.h头文件引过来就可以用分类新增加的方法了。
  2. 注意:分类智能新增加方法,而不能新增成员变量。
  3. 理论上分类的文件名可以随便写的,一般建议用+号连接类和分类。
    不仅可以给自定义类写分类,也可以对系统已经存在的类写分类。再举个例子,比如我们给系统自带的NSString扩充个JSON的分类,增加一个json()的函数,实现如下。
    新增NSString+JSON.h:
    1
    2
    3
    4
    5
    6
    7
    #import <Foundation/Foundation.h>
    @interface NSString(JSON)
    - (NSString*)json;
    @end

新增NSString+JSON.m:

1
2
3
4
5
6
7
8
9
#import "NSString+JSON.h"
@implementation NSString(JSON)
- (NSString*)json{
return [NSString stringWithUTF8String:"{'code':0,'data':{}}"];
}
@end

测试文件main.m:

1
2
3
4
5
6
7
8
9
10
11
12
#import <Foundation/Foundation.h>
#import "NSString+JSON.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSString *str = @"tet";
NSString *strJson = [str json];
NSLog(@"%@", strJson);
}
return 0;
}

最后补充一点,分类并不一定要写在一个单独的文件里,完全可以和类的声明写到一起。Person.h如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property NSString* name;
@property(getter=isRich) BOOL rich;
- (id) initPersonWithName:(NSString*)name;
//下面两个静态构造函数我们新增的
+ (id)person;
+ (id)personWithName:(NSString*)name;
@end
//下面表示基于类Person新增一个叫Bad的分类
@interface Person(Bad)
//下面是个分类新增的方法
- (void)test;
@end

Protocol使用

Category是OC特有的,而Protocol类似于Java里的interface,字面意思就是协议,也就是一系列方法的列表,其中声明的方法可以被任意类实现,通常在OC里管这种模式叫delegation模式。
新建Button.h定义一个按钮和按钮监听:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#import <Foundation/Foundation.h>
@class Button;
@protocol ButtonDelegate <NSObject>
- (void)onClick:(Button*)button;
@end
@interface Button : NSObject
@property (nonatomic, retain) id<ButtonDelegate> delegate;
- (void)click;
@end

Button.m实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#import "Button.h"
@implementation Button
//按钮被点击通知监听器
- (void)click{
if([_delegate respondsToSelector:@selector(onClick:)]){
[_delegate onClick:self];
}else{
NSLog(@"_delegate未实现对应方法");
//[_delegate onClick];
}
}
- (void)dealloc{
[_delegate release];
[super dealloc];
}
@end

这里的click方法模拟点击按钮动作。
ButtonListener.h:

1
2
3
4
5
6
7
#import <Foundation/Foundation.h>
//#import "Button.h"
@protocol ButtonDelegate;
@interface ButtonListener : NSObject <ButtonDelegate>
@end

ButtonListener.m:

1
2
3
4
5
6
7
8
9
10
11
#import "ButtonListener.h"
#import "Button.h"
@implementation ButtonListener
- (void)onClick:(Button *)button{
NSLog(@"%@被点击了", button);
}
@end

测试main.m文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#import <Foundation/Foundation.h>
#import "Button.h"
#import "ButtonListener.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Button *btn = [[[Button alloc]init]autorelease];
ButtonListener *listener = [[[ButtonListener alloc]init]autorelease];
btn.delegate = listener;
[btn click];
}
return 0;
}

要点如下:

  1. 定义协议用关键字@protocol, 用<xxx>表示实现什么接口。在定义类的时候NSObject表示所有类的基类,定义接口的时候是所有接口的基接口,源码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    @protocol NSObject
    - (BOOL)isEqual:(id)object;
    @property (readonly) NSUInteger hash;
    @property (readonly) Class superclass;
    - (Class)class OBJC_SWIFT_UNAVAILABLE("use 'type(of: anObject)' instead");
    - (instancetype)self;
    - (id)performSelector:(SEL)aSelector;
    - (id)performSelector:(SEL)aSelector withObject:(id)object;
    - (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
    - (BOOL)isProxy;
    - (BOOL)isKindOfClass:(Class)aClass;
    - (BOOL)isMemberOfClass:(Class)aClass;
    - (BOOL)conformsToProtocol:(Protocol *)aProtocol;
    - (BOOL)respondsToSelector:(SEL)aSelector;
    - (instancetype)retain OBJC_ARC_UNAVAILABLE;
    - (oneway void)release OBJC_ARC_UNAVAILABLE;
    - (instancetype)autorelease OBJC_ARC_UNAVAILABLE;
    - (NSUInteger)retainCount OBJC_ARC_UNAVAILABLE;
    - (struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;
    @property (readonly, copy) NSString *description;
    @optional
    @property (readonly, copy) NSString *debugDescription;
    @end
  2. @property (nonatomic, retain) id<ButtonDelegate> delegate;这表示delegate变量是任意实现ButtonDelegate接口的类;

  3. @interface ButtonListener : NSObject <ButtonDelegate>这表示ButtonListener继承自NSObject同时实现了ButtonDelegate
  4. 如果判断一个delegate是否实现了某个方法,用这句[_delegate respondsToSelector:@selector(onClick:)],其中respondsToSelector就是NSObject协议里的。

OC的block语法

OC的block语法类似Java的匿名类,所谓匿名类就是没有名字。先来看一段OC程序:

1
2
3
4
5
6
7
8
9
10
11
int main(int argc, const char * argv[]) {
@autoreleasepool {
int (^SUM)(int , int) = ^(int a, int b){
return a + b;
};
int c = SUM(10, 12);
NSLog(@"c = %i", c);
}
return 0;
}

这里定义的SUM函数实现就是一个block,类似java里的:

1
2
3
4
5
btn.setOnClickListener(new View.OnClickListener{
void onClick(){
}
});

当然也可以写成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#import <Foundation/Foundation.h>
typedef int (^SUM)(int, int);
int main(int argc, const char * argv[]) {
@autoreleasepool {
SUM sum = ^(int a, int b){
return a + b;
};
int c = sum(10, 12);
NSLog(@"c = %i", c);
}
return 0;
}

block语法要点如下:

  1. block使用符号^表示:int (^SUM)(int, int),这里的int是这个函数的返回值,SUM就是block的名字,括号里是形参;
  2. 注意block使用时是用block的名字,后面带括号输入参数,而不是OC里的对象[per age]这种方式调用;
  3. 至此可以总结下,^表示block,:表示继承,<>表示实现某个protocol,()表示分类。
  4. 在block里可以访问外面的变量,但仅仅是读取而不能修改,如果要修改需要前面加__,如下所示:
    1
    2
    3
    4
    5
    6
    __block int cc = 10;
    SUM sum = ^(int a, int b){
    cc += 1;
    return cc;
    };
    int c = sum(10, 12);

下面通过block实现按钮点击监听效果:
Button.h:

1
2
3
4
5
6
7
8
9
10
#import <Foundation/Foundation.h>
@class Button;
typedef void (^ButtonBlock)(Button*);
@interface Button : NSObject
@property (nonatomic, assign) ButtonBlock block;
- (void)click;
@end

Button.m:

1
2
3
4
5
6
7
8
#import "Button.h"
@implementation Button
- (void)click{
_block(self);
}
@end

main.m:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#import <Foundation/Foundation.h>
#include "Button.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Button *btn = [[[Button alloc]init]autorelease];
btn.block = ^(Button *btn){
NSLog(@"%@被点击了", btn);
};
[btn click];
}
return 0;
}

至此OC基础语法就介绍到这。

Foundation框架

OC里的Foundation框架类似于Java里java.lang.***包名下的东西,是iOS开发中的基础,定义了大量基础数据类型。

NSRangeNSPoint

显示 Gitment 评论