[GeekBand]Objective-C编程语言第一周笔记

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已实现:

  1. 在堆上分配合适大小的内存。
  2. 将属性或者实例变量的内存置0。

init所做的事情——可以自定义:

  1. 调用父类初始化器 [super init] (前置调用)。
  2. 初始化当前对象实例变量(注意使用实例变量,不要使用属性)。

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;
}

你可能感兴趣的:([GeekBand]Objective-C编程语言第一周笔记)