前言

如何掌握高级编程语言

  • 底层思维: 向下,如何把握机器底层从微观理解对象构造
    • 语言构造
    • 编译转换
    • 内存模型
    • 运行时机制
  • 抽象思维:向上,如何将我们的周围世界为程序代码
    • 面向对象
    • 组件封装
    • 设计模式
    • 架构模式

两种开发方式

  • Clang或GCC命令行
    • clang-fobjc-arc helloword.m
    • -fobjc-arc支持ARC内存管理
    • 适合调试、研究、微观探查
  • Xcode项目
    • 构建正规工程项目
    • 使用大型框架,追求设计质量与代码组织
  • 目前oc采用的LLVM-Clang的编译架构

类与对象

使用#import和#include的区别

  • import是oc语音的头文件导入,它能避免重复导入。确保头文件只会被导入一次。
  • include如果不注意很容易会重复导入,出现相互包含的编译错误。推荐使用import来导入头文件。

类和对象

  • 类是对某一事物的描述,是抽象的,而对象是一个实实在在的个体,是类的一个实例。
  • 类是对象的抽象,而对象是类的具体实例。

类型系统(不同的类型)

  • 引用类型(复制的指针)
    • 类 class
    • 指针 pointer
    • 块 block
  • 值类型(直接复制的数值)
    • 基础数据类型,int float等
    • 结构 struct
    • 枚举 enum
  • 类型装饰
    • 协议 protocol
    • 类别 category
    • 扩展 extension

类class和结构体struct的区别

  • 从类型和实例关系来看
    • class是类(类型)和对象(实例)
    • struct是结构(类型)和值(实例)
  • class是引用类型,位于栈上的是指针(引用),位于堆上的是实体对象
  • struct是值类型,实例(值)内存直接位于栈中

栈与堆的区别

内存管理的范围

  • 任何继承了NSObject的对象
  • 对其他非对象类型无效
  • 即只有oc对象需要进行内存管理,非oc对象类型比如基本的数据类型不需要进行内存管理

引入栈和堆的概念

栈(stack)
  • 栈区(stack)是由编译器自动分配并释放,存放函数的参数值,局部变量等。栈是系统数据结构,对应线程/进程是唯一的。优点是快速高效,缺点是有限制,数据不灵活(先进后出)。
  • 栈空间有两种分配方式:
    • 静态分配是由编译器完成,比如局部变量的分配
    • 动态分配由alloca函数完成,动态分配是无须释放的(自动释放)
堆(heap)
  • 堆(heap)是由程序员分配和释放的,如果不释放,程序结束时,可能会由操作系统回收 ,比如在ios 中 alloc 都是存放在堆中。优点是灵活方便,数据适应面广泛,但是效率有一定降低。
  • 堆空间的分配总是动态的。
  • 操作系统使用stack 段中的指针值访问heap 段中的对象。如果stack 对象的指针没有了,则heap 中的对象就不能访问。这也是内存泄露的原因。
只有OC对象才需要进行内存管理的原因
  • OC对象在内存中是以堆的方式分配空间的,并且堆内存是由程序员释放的,就是release。OC对象存放于堆里面(堆内存要程序员手动回收)。堆里面的内存是动态分配的,所以需要程序员手动的去添加内存、回收内存。
  • 非OC对象一般放在栈里面,栈内存会被系统自动回收。

stack 对象的优点主要有两点,一是创建速度快,二是管理简单,它有严格的生命周期。stack 对象的缺点是它不灵活。创建时长度是多大就一直是多 大,创建时是哪个函数创建的,它的owner 就一直是它。不像heap 对象那样有多个owner ,其实多个owner 等同于引用计数。只有 heap 对象才是采用“引用计数”方法管理它。

总结区别

  • 按管理方式分
    • 对于栈,是由系统编译器自动管理,不需要程序员手动管理
    • 对于堆,释放工作由程序员手动管理,不及时回收容易产生内存泄露
  • 按分配方式分
    • 堆是动态分配和回收内存的,没有静态分配的堆
    • 栈有两种分配方式:静态分配和动态分配
      • 静态分配由系统编译器完成的,比如局部变量的分配
      • 动态分配由alloc函数进行分配的,但是栈的动态分配和堆不同,它的动态分配也由系统编译器进行释放,不是程序员手动管理。

两个运行内存图

对象的空间分析
值的空间分析

数据成员:属性与实例变量

类型成员

  • 数据成员 data member 描述对象状态
    • 实例变量 instance variable
    • 属性 propety
  • 函数成员 function member 描述对象行为
    • 方法 method
    • 初始化器 init
    • 析构器 dealloc

实例变量和属性的区别

  • 大括号括起来的是instance variable(实例变量),只是简单的数值,不能绑定get/set方法,不能自动retain/copy/atomic。相当于一个简单的跟着instance走的局部变量
  • propety则是对get/set方法的语法封装,将两个方法的声明变成一个propety声明,并且通过标注各种attribute来完成许多基本任务。

属性认识

  • 属性表达实例状态,描述类型的对外接口。相比直接访问实例变量,属性可以做更多的控制。
  • 默认情况下,编译器会为属性定义propetyName自动合成:
    • 一个getter访问器方法:propertyName
    • 一个setter访问器方法:setPropertyName
    • 一个实例变量_propertyName
  • 可以自定义访问器方法,也可以更改访问器方法名、或实例变量名
  • 可以使用静态全局变量(C语言)+类方法,模拟类型属性

getter和setter方法

面向对象的语言一般都会有setter和getter器。在oc中一般这样写:
在.h中声明

-(void)setAge:(int)newAge;
-(int)age;

在.m文件中具体实现

-(void)setAge:(int)newAge {
age = newAge;
}
-(int)age {
return age;
}
  • 调用方法,一般才用中括号[]和点.的方式调用。
  • 之后引用了@property来改进setter和getter。但是必须声明与之对应的实例变量。
    • 当编译器遇到@property的时候,会自动展开getter和setter的声明
    • 在m文件中,@synthesize会自动生成getter和setter的实现。@synthesize会去访问str同名的变量,如果没有找到就会错。(所以必须声明与之对应的实例变量)
  • 之后的版本中,不需要为属性声明实例变量,@synthesize如果找不到会自动生成一个同名的私有同名变量。
  • 再之后的版本中,直接省略@synthesize,因为编译器可以自动为属性生成setter和getter方法以及以下划线开头的实例变量_propertyName。
  • 目前,@property (nonatomic, copy) NSString *str;这句话完成了3个功能:
    1)生成_str成员变量的get和set方法的声明;
    2)生成_str成员变量set和get方法的实现;
    3)生成一个_str的成员变量。(注意:这种方式生成的成员变量是private的)
  • 设置访问方法的名字,默认的名字是setPropertyName和propertyName,可以通过设置@property的getter和setter属性来修改setter和getter器的方法名。
    @property (nonatomic,copy,getter = show1,setter = show2:) NSString *str

属性重写setter和getter方法

  • 如果只重写setter和getter其中之一,可以直接重写
-(void)setStr:(NSString *)str {
_str = @"只重写setter方法";
}
  • 如果要同时重写setter和getter。需要加上@synthesize propertyName = _propertyName;不然系统不认_str
    如果同时重写了setter和getter方法,系统就不会帮你自动生成这个_str
@synthesize str = _str;
-(void)setStr:(NSString *)str {
_str = @"同时重写setter和getter";
}
-(NSString *)str {
return _str;
}

实例变量

  • 可以定义实例变量,而不定义属性,只有实例变量没有类变量
  • 如果自定义了getter和setter访问器方法,或者针对只读属性定义了getter访问器方法,编译器将不再合成实例变量,需要自己声明。
  • 在类外一律使用属性来访问,类内大多也通过self使用属性访问,以下情况使用实例变量来访问:
    • 初始化器init
    • 析构器dealloc
    • 自定义访问器方法
  • 什么时候使用实例变量什么时候使用属性呢?参考这篇文章:iOS巩基之 不再纠结实例变量&属性

属性的描述特性

  • 读写特性
    • 读写属性 readwrite 默认
    • 只读属性 readonly
  • 多线程特性
    • 原子性atomic(默认)
    • 非原子性 nonatomic
  • 内存管理特性
    • ARC环境
      • 强引用strong
      • 弱引用 weak 阻止循环引用
      • 拷贝属性 copy 创建独立拷贝
    • 其他情况
      • retain
      • assign
      • unsafe_unretained

参考这篇文章关于属性的特性总结:
1.OC属性总结笔记
2.OC 属性特性assign,retain

函数成员:方法

  • 方法是类的成员函数,表达实例行为或类型行为
  • 所有方法默认为公有方法,没有private和protected方法
  • 动态消息分发:方法调用通过运行时动态消息分发实现,在对象上调用方法又称为"向对象发送消息"
  • OC 的方法调用就是编译器通过 objc_msgSend()进行消息分发。下面两行代码是等价的:
[array insertObject:foo atIndex:0];
objc_msgSend(array, @selector(insertObject:atIndex:), foo, 0);
  • 动态消息分发属于oc runtime机制,比较复杂。单独在分析。

runtime

  • objc语言是一门动态语言,它将很多静态语言在编译和链接时做的事放到了运行时来处理。
  • 这种特性意味着OC不仅需要一个编译器,还需要一个运行时系统来执行编译的代码。对于OC来说,这个运行时系统就像一个操作系统一样。这个运行时系统即Runtime,是用C和汇编写的,这个库使得C语言有了面向对象的能力。其中最主要的是消息机制。对于C语言,函数的调用在编译的会决定调用哪个函数,编译完成之后顺序执行,无任何二义性。OC的函数调用称为消息发送,属于动态调用过程。在编译的时候并不能决定真正的调用哪个函数(事实证明,在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。而C语言在编译阶段就会报错。)只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
  • Runtime库主要做下面几件事:
    1.封装:在这个库中,对象可以用C语言中的结构体表示,而方法可以用C函数来实现,另外再加上一些额外的特性。这些结构体和函数被Runtime函数封装后,我们就可以在程序运行时创建、检查、修改类、对象和他们的方法了。
    2.找出方法的最终执行代码:当程序执行[object doSomething]时,会向消息接收者(object)发送一条消息(doSomething),Runtime会根据消息接收者是否能响应该消息而做出不同的反应。

实例方法和类型方法

  • 实例方法的self指针代表当前实例对象,实例指针
  • 类方法的self指针代表当前类

方法参数

  • 注意值类型和引用类型的不同参数类型对外部的影响。
  • 注意方法参数名,内部参数和外部参数,第二个参数名开始需要显示的提供外部参数名。
  • 调用时,第一个参数名忽略,后面的参数名必须显示标明。

初始化器和析构器

认识初始化器和析构器

  • 初始化器用于初始化对象实例或类型,是一个特殊的函数
    • 对象初始化器 -(id) init可以重载多个
    • 类型初始化器 +(void)initialize只能有一个
  • 析构器用于释放对象拥有的资源,无返回值的函数
    • 对象析构器-(void)dealloc只能有一个
    • 没有类型析构器

对象初始化器

  • 初始化对象实例时,init通常和alloc搭配使用

  • alloc所做的事情-NSObject已实现

    • 在堆上分配合适大小的内存
    • 将属性或者实例变量的内存置0
  • init所做的事情 - 可以自定义

    • 调用父类初始化器[super init](前置调用)
    • 初始化当前对象实例变量(注意使用实例变量,不要使用属性)
  • new相等于调用alloc/init的无参数版本。

  • 初始化器内部使用实例变量不使用属性。

  • id或者instancetype类型的返回值。

  • 指定初始化器(真正实现的初始化),便捷初始化器(会调用指定初始化器)?

指定初始化方法和便利构造器

指定初始化方法

  • 无论调用哪一个初始化方法都会调用的初始化方法,称为指定初始化方法。
  • 一般会写一个传入参数最多的init方法,然后其他方法调用这个指定初始化方法即可。
- (id)initWithName:(NSString *)name
{
//调用指定初始化方法
return [self initWithName:name age:0 sex:'0' height:0];
}

//指定初始化方法
- (id)initWithName:(NSString *)name age:(NSInteger)age sex:(char)sex height:(CGFloat)height
{
self = [super init];
if (self) {
_name = name;
_age = age;
_sex = sex;
_height = height;
}
return self;
}

便利构造器

  • 便利构造器就是一个类方法(+号方法),内部实现是封装了alloc和初始化操作,创建对象更加方便快捷。
  • 定义便利构造器有以下规则:
    • 便利构造器是“+”方法。
    • 返回本类型的实例。
    • ⽅法名以类名开头。
    • 可以有0到多个参数。
+(id)PersonWithName:(NSString *)name age:(NSInteger)age sex:(char)sex height:(CGFloat)height
{
Person \*p = [[Person alloc] initWithName:name age:age sex:sex height:height];
return p;
}

类初始化器

  • 类初始化器initialize负责类型级别的初始化
  • initialize在每个类使用之前被系统自动调用,且每个进程周期中,只被调用一次。
  • 子类的initialize会自动调用父类的initialize(前置调用)

对象析构器

  • 对象析构器dealloc负责释放对象拥有的动态资源
    • 自动实现:ARC将对象属性引用计数减持
    • 手动实现:释放不受ARC管理的动态内存,如malloc分配的内存
    • 手动实现:关闭非内存资源,如文件句柄,网络端口
  • dealloc由ARC根据对象引用计数规则,在释放对象内存前自动调用,无法手工调用
  • 子类的dealloc会自动调用父类的dealloc(后置调用)

继承与多态

认识面向对象

  • 封装 encapsulation
  • 继承 inheritance
  • 多态 polymorphism

继承

  • 继承:每一个类只能有一个基类,子类自动继承基类
    • 实例变量
    • 属性
    • 实例方法
    • 类方法
  • 所有类的根类:NSObject
  • 继承的两层含义:
    • 成员复用:子类复用基类成员
    • 类型抽象:将子类当做父类来使用

运行时多态 polymorphism

  • 多态:子类在父类统一行为接口下,表现不同的实现方式
  • 对比重写和重载
    • 子类重写父类同名同参数方法
    • 方法名相同、参数不同,OC不支持方法的重载
  • 在子类的代码中,可以使用super来调用基类的实现
    • self具有多态性,可以指向不同的子类
    • super没有多态性,仅指向当前父类

继承中的init和dealloc

  • 初始化器init
    • 子类自动继续基类的初始化器
    • 子类也可以重写基类初始化器,此时子类初始化器必须首先调用基类的一个初始化器(手工调用)
  • 析构器dealloc
    • 子类可以选择继承基类析构器,或者重写基类析构器
    • 子类析构器执行完毕后,会自动调用基类析构器(后置调用,且不支持手工调用)
    • 子类析构器自动具有多态性
  • Tips:尽量避免在父类init和dealloc调用子类重写的方法。