1. Objective-C简介
Objective-C语言简介
- Objective-C在C语言基础上做了面向对象扩展。
- 1983年由Brad Cox和Tom Love发明,后成为NeXT的主力语言,后被苹果收购,成为苹果开发平台的主力语言。
- 与Cocoa和Cocoa Touch框架高度集成,支持开发Mac OS X、iOS应用。
- 在苹果开发平台上,通过LLVM编译器架构,支持与Swift语言双向互操作。
在iOS开发平台上支持的语言有Swift、Objective-C、C/C++,主要使用前两者。
如何掌握高级编程语言
底层思维:向下,如何把握机器底层从微观理解对象构造
- 语言转换
- 编译转换
- 内存模型
- 运行时机制
抽象思维:向上,如何将我们的周围世界抽象为程序代码
- 面向对象
- 组件封装
- 设计模式
- 架构模式
「时空人」三位一体分析法
- 对时间分析——发生在什么时候?
- Compile-time VS Run-time
- 对空间分析——变量放在哪里?
- Stack VS Heap
- 人物分析——代码哪里来的?
- Programmer VS Compiler/Runtime/Framework
两种开发方式
- Clang或GCC命令行
clang -fobjc-arc HelloWorld.m -o HelloWorld
- 或
gcc -fobjc-arc HelloWorld.m -o HelloWorld
,推荐用clang-fobjc-arc
:支持ARC(Automatic Reference Counting)- 适合调试、研究、微观探查
-
-o
:输出文件名 - 更多可以参考
-help
//引入头文件,可以避免多次引入重复头文件,推荐使用。
#import
int main(int argc, const char * argv[]) {
@autoreleasepool {
//类似于prinft,但多了日期时间等信息。
NSLog(@"Hello World~");
}
return 0;
}
- Objective-C的字符串前要加
@
- Xcode项目
- 构建正规工程项目
- 使用大型框架,追求设计质量与代码组织
Objective-C编译过程
- GCC: GCC Front End -> GCC Optimizer -> GCC Code Generator
- LLVM-GCC: GCC Front End -> LLVM Optimizer -> LLVM Code Generator
- LLVM-Clang: Clang Front End -> LLVM Optimizer -> LLVM Code Generator
- Objective-C直接生成机器码
- LLVM:Low Level Virtual Machine
- LLVM-Clang:目前iOS上一般使用的方式,针对iOS优化更好。
学习资源
- 苹果官方文档:https://developer.apple.com/library/
- Programming with Objective-C:https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/Introduction/Introduction.html
- WWDC
- https://developer.apple.com/videos/wwdc2014/
- https://developer.apple.com/videos/wwdc2015/
课程总结
- 了解Objective-C语言和编译架构
- 了解Objective-C开发工具
- 了解机器思维和抽象思维
- 驾驭工具,而不是被工具驾驭
- 掌握英文学习资源很重要
2. 类与对象
类型系统
- 引用类型 Reference Type
- 类 Class
- 指针 Pointer
- 块 Block
- 值类型 Value Type
- 基础数值类型
- 结构 Struct
- 枚举 Enum
- 类型装饰
- 协议 Protocol
- 类别 Category
- 扩展 Extension
- C语言中的数据类型均可用在Objective-C中
类 VS 结构
- 类型与实例
- 类与对象
- 结构与值
- 类——引用类型
- 位于栈上的指针(引用)
- 位于堆上的实体对象
- 结构——值类型
- 实例直接位于栈中
- 空间分析
- 运行时内存图——「胸中有沟壑」
类定义
RPoint.h
@interface RPoint: NSObject
@property int x; //属性,状态,这里默认初始化为0
@property int y;
- (void) print; //方法,行为
@end
-
- (void)print
:-
表示实例方法,+
表示类方法,其后的括号内为返回值类型。
RPoint.m
#import
#import "RPoint.h"
@implementation RPoint
- (void) print {
NSLog(@"[%d, %d]", self.x, self.y);
}
@end
SPoint.h
typedef struct {
int x;
int y;
}SPoint;
对象的空间分析
栈上存储指针(引用),堆上存储真正的对象,
值的空间分析
实例(值)内存直接存储在栈空间
栈 VS 堆
栈:存储值类型
- 无ARC负担,由系统自动管理,以执行函数为单位
- 空间大小编译时确定(参数+局部变量)
- 函数执行时,系统自动分配一个Stack
- 函数执行结束,系统立即自动回收Stack
- 函数之间通过拷贝值传递
- 具有局部性,大小有限额,超出会Stack Overflow
堆:存储引用类型对象
- 分配由程序员手动请求(创建对象时)
- 释放由运行时ARC机制自动释放(确定时)
- 函数之间通过拷贝引用(指针)传递
- 具有全局性,总体无大小限制(受制于系统内存整体大小)
main.m
#import
#import "RPoint.h"
#import "SPoint.h"
void process(RPoint* rp3, SPoint sp3);
int main(int argc, const char * argv[]) {
@autoreleasepool {
//Objective-C中所有的对象均以指针的形式存在,要加 * 号
//中括号为发送消息(方法调用)
//创建对象,alloc为向系统请求内存分配
RPoint* rp1 = [[RPoint alloc] init];
rp1.x = 10;
rp1.y = 20;
[rp1 print]; //[10, 20]
//
SPoint sp1;
sp1.x = 10;
sp2.y = 20;
NSLog(@"------拷贝------")
RPoint rp2 = rp1;
rp2.x++;
rp2.y++;
//引用传递
[rp1 print]; //[11, 21]
[rp2 print]; //[11, 21]
SPoint sp2 = sp1;
sp2.x++;
sp2.y++;
//值传递
NSLog(@"[%d, %d]", sp1.x, sp1.y); //[10, 20]
NSLog(@"[%d, %d]", sp2.x, sp2.y); //[11, 21]
NSLog(@"------传参------");
process(rp1, sp1);
[rp1 print]; //[12, 22]
NSLog(@"[%d, %d]", sp1.x, sp1.y); //[10, 20]
}
return 0;
}
//函数开始执行时自动创建一个新栈,与main函数的栈不同,结束时会自动销毁
void process(RPoint* rp3, SPoint sp3) {
rp3.x++;
rp3.y++;
sp3.x++;
sp3.y++;
[rp3 print]; //[12, 22]
NSLog(@"[%d, %d]", sp3.x, sp3.y); //[11, 21]
//函数执行结束后rp3和sp3将被销毁
//但rp3仅是指针,销毁后指针所指向的对象并不受影响。
}
3. 数据成员:属性与实例变量
类型成员 Type Member
数据成员 Data Member 描述对象状态
- 实例变量 Instance Variable
- 属性 Property
函数成员 Function Member 描述对象行为
- 方法 Method
- 初始化器 Init
- 析构器 Dealloc
认识属性
属性表达实例状态,描述类型对外接口。相比直接访问实例变量,属性可以做更多控制。
默认情况下,编译器会为属性定义propertyName自动合成:
- 一个getter访问器方法:propertyName
- 一个setter访问器方法:setPropertyName
- 一个实例变量:_propertyName
可自定义访问器方法,也可更改访问器方法名、或实例变量名。
可以使用静态全局变量(C语言)+ 类方法,模拟类型属性。
属性声明:
@property NSString* firstName;
//--上述代码将生成:--
NSString* _firstName;
- (NSString*) firstName {/**code**/}
- (void) setFirstName: (NSString*)newValue {/**code**/}
//----
//更改访问器方法名
@property (getter = GivenName, setter = setGivenName:) NSString* lastName;
//更改实例变量名
@synthesize firstName = givenName;
调用方法:
//访问器方法
[employee setFirstName: @"Tom"];//set
[employee firstName]; //get
//属性表达式,推荐。
employee.firstName = @"Tom"; //set
NSLog(@"First Name: %@", employee.firstName);//get
模拟静态(类)属性
在.m文件中定义一个静态变量
//静态变量
static int _max = 100;
@implementation Employee {
//...
}
在.h文件中定义方法
@interface Employee: NSObject
//...
+ (int) max;
+ (void) setMax: (int)newValue;
//...
@end
在.m文件中实现方法
@implementation Employee {
//...
//为静态变量提供访问器方法
+ (int) max {
return _max;
}
+ (void) setMax: (int)newValue {
_max = newValue;
}
//...
}
调用方法:
//Employee为类型,不是对象
[Employee setMax: 300];
Employee.max = 400;
NSLog(@"class variable is %d.", Employee.max);
实例变量
可以定义实例变量,而不定义属性。只有实例变量,没有类变量。
如果同时自定义了getter和setter访问器方法,或者针对只读属性定义了getter访问其方法,编译器将不再合成实例变量。
在类外一律使用属性来访问,类内大多也通过self使用属性访问。只有一下情况使用实例变量来访问:
- 初始化器 init
- 析构器 dealloc
- 自定义访问器方法
- 实例变量只能在类内访问,类外不可访问。
- 引用类型的属性使用实例变量访问时可能会�有内存管理的问题。
实例变量的生存周期
实例变量的存储:跟随对象实例存储在堆上。
值类型实例变量直「内嵌」在对象实例中。跟随对象实例内存释放而被释放。
引用类型实例变量通过指针「引用」堆上的引用类型实例,ARC针对引用进行计数管理,自动释放引用计数为0的对象。
属性的描述特性
属性描述特性(Attribute)可以指定属性不同环境下的不同功能。
- 读写特性
- 读写属性 readwrite (默认)
- 只读属性 readonly
- 多线程特性
- 原子性 atomic (默认)
- 非原子性 nonatomic
- 内存管理特性
- ARC环境
- 强引用 strong (默认)
- 弱引用 weak 阻止循环引用
- 拷贝属性 copy 为属性赋值时创建独立拷贝
- 其他情况
- retain
- assign
- unsafe_unretained
@property (readonly, nonatomic) NSString* firstName;
- 避免循环引用:使用weak属性
4. 函数成员:方法
函数成员 Function Member 描述对象行为
- 方法 Method
- 初始化器 Init
- 析构器 Dealloc
认识方法 Method
函数:代码段上的可执行指令序列
- 全局函数(C语言函数)
- 成员函数(Objective-C方法)
方法是类的成员函数,表达实例行为或类型行为。
所有方法默认为公有方法。没有private或protected方法。
动态消息分发:方法调用通过运行时动态消息分发实现,在对象上调用方法又称「向对象发送消息」。
实例方法或类型方法
实例方法——表达实例行为,可以访问
- 实例成员(实例属性、实例变量、实例方法)
- 类型方法、静态变量
类方法——表达类型行为,访问权限:
- 可以访问:类型方法、静态变量
- 不能访问:实例成员(实力属性、实例变量、实例方法)
了解编译器背后对实例方法和类方法的不同处理:self指针
//实例方法
- (void) print {
NSLog(@"[%d, %d]", self.x, self.y);
}
//编译器编译后:
void print(BLNPoint* self) {
NSLog(@"[%d, %d]", self.x, self.y);
}
//调用时:
[p1 print];
//编译器编译后:
print(p1);
//类型方法
+ (BLNPoint*) getOriginPoint {
//...
//在类型方法中,self 等同于类型,与实例方法中的self不同。
[self process];
//等同于
[BLNPoint process];
[self print];//错误
}
//编译器编译后:
BLNPoint* getOriginPoint() {
//...
}
//调用时:
BLNPoint* origin = [BLNPoint getOriginPoint];
//编译器编译后:
BLNPoint* origin = getOriginPoint();
方法参数
- 如果参数类型为值类型,则为传值方式;如果参数类型为引用类型,则为传指针方式。
- 方法可以没有参数,也可以没有返回值。
- 如果方法有参数,方法名约定包含第一个参数名,第二个参数开始需要显示提供外部参数名。
- 调用时,第一个参数名忽略,但后面的参数名必须显式标明。
- (BOOL) isEqualToPoint: (BLNPoint*) point;
- (void) moveToX: (int)x toY: (int)y;
//调用
[p1 isEqualToPoint: p2];
[p1 moveToX: 100 toY: 200];
-
id
可以表示所有的类型
id obj = [[BLNPoint alloc] init];
[obj moveToX: 50 toY: 60];
[obj print];
[obj setX: 70];
obj.x = 70;//obj声明为id时不可以这样用
- 理解动态方法调用机制——消息分发表
5. 初始化器与析构器
认识初始化器与析构器
初始化器用于初始化对象实例或者类型,是一个特殊的函数。
- 对象初始化器:- (id) init 可以重载多个
- 类型初始化器:+ (void) initialize 只能有一个
析构器用于释放对象拥有的资源,无返回值的函数。
- 对象析构器 - (void) dealloc 只能有一个
- 没有类型析构器
- (id) init;
- (id) initWithName: (NSString *)name;
- (id) initWithName: (NSString *)name WithPages: (int)pages;
- (void) dealloc;
+ (void) initialize;
对象初始化器
初始化对象实例时,init通常和alloc搭配使用。
alloc所做的事情——NSObject已实现:
- 在堆上分配合适大小的内存。
- 将属性或者实例变量的内存置0。
init所做的事情——可以自定义:
- 调用父类初始化器 [super init] (前置调用)。
- 初始化当前对象实例变量(注意使用实例变量,不要使用属性)。
new 相当于调用alloc/init的无参数版本。
- (id) init {
self = [super init];
if (self) {
//...
}
return self;
}
- (id) initWithName: (NSString *)name WithPages: (int)pages {
self = [super init];
if (self) {
//初始化实例对象,使用实例变量,不要使用属性self.name
_name = [name copy];
_pages = pages;
}
return self;
}
//调用:
Book* book = [Book new];
//相当于
Book* book = [[Book alloc] init];
类初始化器
类初始化器initialize负责类型级别的初始化。
initialize在每个类使用之前被系统自动调用,且每个进程周期中,只被调用一次。
子类的initialize会自动调用父类的initialize(前置调用)。
+ (void) initialize {
if (self == [Book class]) {
//...
}
}
对象析构器
对象析构器dealloc负责释放对象拥有的动态资源:
- 自动实现:1. ARC将对象属性引用计数减持
- 手动实现:2. 释放不受ARC管理的动态内存,如malloc分配的内存
- 手动实现:3. 关闭非内存资源,如文件句柄、网络端口...
dealloc由ARC根据对象引用计数规则,在释放对象内存前自动调用,无法手工调用。
子类的dealloc会自动调用父类的dealloc(后置调用)。
6. 继承与多态
认识面向对象
封装 Encapsulation
隐藏对象内部实现细节,对外仅提供公共接口访问。
继承 Inheritance
一个类型在另外类型基础上进行的扩展实现。
多态 Polymorphism
不同类型针对同一行为接口的不同实现方式。
认识继承 Inheritance
继承:每一个类只能有一个基类,子类自动继承基类的:
- 实例变量
- 属性
- 实例方法
- 类方法
了解所有类的根类:NSObject
继承的两层含义:
- 成员复用:子类复用基类成员
- 类型抽象:将子类当做父类来使用(IS-A关系准则Circle is a Shape)
Objective-C只支持单继承,一个类只能有一个基类
如果希望实例变量在类外可以访问,可以放在接口文件中,必须放在大括号内并加上@public,一般情况下不推荐
@interface Shape: NSObject {
@public int _data;
}
//调用时
shape->_data++;
认识运行时多态 Polymorphism
多态:子类在父类统一行为接口下,表现不同的实现方式。
对比重写与重载
- 子类重写父类同名同参数方法:子类只可以重写override父类方法
- 方法名相同、参数不同:Objective-C不支持方法的重载。
在子类的代码中,可以使用super来调用基类的实现。
- self具有多态性,可以指向不同子类
- super,没有多态性,仅指向当前父类
- 属性的setter和getter方法也可以被重写
理解self的多态性:
//Shape类
- (void) draw {
NSLog(@"Shape object draw");
}
- (void) move {
NSLog(@"Shape object move");
//这种情况下:self指代rectangle
//所以会调用rectangle的draw方法
[self draw];
}
//------------------------------
//Rectangle类继承自Shape
- (void) draw {
NSLog(@"Rectangle object draw");
}
/**
//可以理解为Rectangle有一个隐藏的move方法
//和Shape类中的一模一样
- (void) move {
NSLog(@"Shape object move");
//这种情况下:self指代rectangle
//所以会调用rectangle的draw方法
[self draw];
}
**/
//----------------------------
//调用时
//Shape是声明类型,Rectangle是实际类型
//所以这两行的执行结果是相同的
Shape* rectangle = [[Rectangle alloc] init];
//Rectangle* rectangle = [[Rectangle alloc] init];
[rectangle draw];//Rectangle object draw
//这里会输出:
//Shape object move
//Rectangle object draw
[rectangle move];
继承中的init和dealloc
初始化器 init
- 子类自动继承基类的初始化器
- 子类也可以重写基类初始化器,此时子类初始化器必须首先调用基类的一个初始化器(手工调用)。
析构器 dealloc
- 子类可以选择继承基类析构器,或者重写基类析构器。
- 子类析构器执行完毕后,会自动调用基类析构器(后置调用,且在开启ARC后不支持手工调用)
- 子类析构器自动具有多态性
Tips: 尽量避免在父类init和dealloc中调用子类重写的方法。
- (id) init {
self = [super init];
if (self) {
//...
}
return self;
}