虽然已经有了 Swift,但是还是有很多 Objective-C 的项目,这门语言还是有必要学一学。如果已经有 C 语言的基础,相信你能很快上手。OC 是完全兼容 C99 的,所以也会先回顾一下 C 语言的重要内容。
如果还完全不会用 Xcode 创建项目和文件,可以看看 Xcode 使用笔记 0 新司机上路
C 语言基础回顾
布尔值
Xcode 支持 C99 标准
|
|
注意在 Objective-C 中,BOOL
类型使用 8 位存储空间,如果不小心将一个大于 1 字节的整型值(比如short
或int
)付给一个BOOL
变量,那么只有低位字节会用作BOOL
值,如果该低位字节刚好为 0(比如说 8960,写成十六进制为0x2300),BOOL
值将会被认作是 0 ,即 NO
值,故不要混用数字和BOOL
类型。
函数
如果变量是“与某块数据相关联的名称”,那么函数就是“与某块代码相关联的名称”。
发现代码中的重复,提取具有共通性的部分并将其用独立的函数处理,接着添加参数处理差异部分。
局部变量、帧和栈
函数可以有很多局部变量,这些局部变量都保存在函数的帧 (frame) 中。
使用栈 (stack) 来描述帧在内存中存储的地点。执行函数时,函数的帧会在栈的顶部被创建出来。函数执行结束时,我们会说函数返回了。也就是说,其帧会退出栈,等待下一个调用它的函数继续执行。
全局变量与静态变量
在函数外声明的变量称为全局变量。复杂的程序会涉及大量的文件,包含不同的函数。这些文件中的代码都能访问全局变量。在不同的文件中共享变量不一定都是好事,有时候会引起很严重的混淆问题。
为此,C 语言引入了静态变量 (static variable)概念。只有那些声明了静态变量的文件才可以访问静态变量。详见下一小节。
存储类、链接和内存管理
C 语言中有 5 个作为存储类说明符的关键字,它们是auto
register
static
extern
以及typedef
。
typedef
与内存存储无关,由于语法原因被归入此类。不可以在一个声明中使用一个以上存储类说明符,意味着不能将其他任一存储类说明符作为typedef
的一部分。
C 使用作用域、链接和存储时期来定义 5 种存储类,见下表:
存储类 | 存储时期 | 作用域 | 链接 | 声明方式 |
---|---|---|---|---|
自动 | 自动 | 代码块 | 空 | 代码块内 |
寄存器 | 自动 | 代码块 | 空 | 代码块内,使用 关键字 register |
空链接的静态 | 静态 | 代码块 | 空 | 代码块内,使用关键字 static |
具有外部链接的静态 | 静态 | 文件 | 外部 | 所有函数之外 |
具有内部链接的静态 | 静态 | 文件 | 内部 | 所有函数之外,使用关键字 static |
详见:C 语言存储类详解
指针
通过*
运算符,可以访问保存在某个地址中的数据。
不同数据类型的大小
sizeof()
会返回一个类型为size_t
的数,与之对应的格式是%zu
,较少用到。示例:
|
|
运行程序,如果指针长度是 4 个字节,那么该程序就是在 32 位环境下运行,8——64 位环境。
NULL
NULL
就是 0(地址也是数字)。在if
的条件中,很方便判断指针是否为空:
|
|
或者用较简洁的三元运算符
|
|
通过引用传递
通过一个实例来学习,其中调用modf()
函数并传入一个double
类型数,可以得到该浮点数的整数部分和小数部分。
|
|
调用函数时传入某个地址,然后由函数将数据传入该地址指向的内存,这种参数传入方式称为通过引用传递 (pass-by-refrence)。
以下是一个很实用,很简单的通过引用传递参数的例子。
|
|
Objective-C 基础
学习自《Objective-C 编程》第二版,《Objective-C 基础教程》第二版,iTunes 上 Stanford iOS7 的视频。
类和方法初体验
方法和消息
|
|
alloc 和 init
唯一必须以嵌套的形式连续发送的消息是alloc
和init
。
|
|
向 nil 发送信息
nil
是不指向任何对象的指针,多数面向对象的语言不允许向nil
发送消息,但是在 Objective-C 中,可以向nil
中发送消息,什么事情也不会发生。
重点1:
如果程序向某个对象发送了消息,但是没有得到预期的结果,请检查消息接收方是否为nil
。重点2:
向nil
发送消息,得到的返回值没有意义。
id
id 是可以指向任意类型对象的指针
id 已经隐含了星号的作用。
获取从出生到当前时刻的时间间隔
(单位:秒)
|
|
对象和内存
内存管理
栈是由有序的帧构成的。函数的帧在函数结束后会自动释放。而堆里的对象不会自动释放。管理堆很重要,因为程序占用的堆大小是有限的。而每个对象被创建后都会占用一顶的堆内存。如果系统内存不足,Mac 应用的运行效果就会变差,而 iOS 应用则会崩溃。因此,必须释放不需要的对象,将内存交还给系统,继续重新使用。
|
|
ARC
第一个NSDate
实例对象怎么样了?
我们已经丢失了这个对象以及其包含的信息。因为我们丢失了指向该对象的指针,即使它仍然在堆上,我们也再不能获得这个对象的信息。如果要保留第一个 NSDate
实例,需要在currentTime
指向第一个实例时,创建新指针从currentTime
那里获取第一个实例对象的地址,此时第一个实例对象的引用数为 2(该实例被 2 个指针所指向)。
将指向实例对象的指针设置为nil
,实例对象就会失去一个引用,如果开启了 ARC,当一个实例对象的引用数为 0 时,就会自动销毁该实例对象,释放内存。
ARC automatic reference counting 自动引用计数
如果指针变量被销毁,它所指向的对象会失去一个引用。当某个对象的实例变量指向其他对象时,事情会变得更复杂—— todo
NSString
创建 NSString 实例
|
|
创建动态字符串,字符串的内容要等到程序运行时才能知道,可以使用stringWithFormat:
类方法来创建动态字符串:
|
|
使用官方文档
打开 Xcode 的 “Help -> Documentation and API Reference”,或者用快捷键 “Command + Shift + 0” ,搜索想了解的资料,注意语言的切换,可以先在顶部的搜索框输入某个类,再用 “Command + f” 在页面内搜索这个类的某个方法。
NSArray
创建数组和遍历
|
|
可变数组 NSMutableArray
|
|
旧式数组方法
|
|
结束处的nil
告诉方法停止运行,所以,这个NSDate
数列拥有三个指针对象。如果没有
练习
购物清单
|
|
里程碑 1:学会从问题出发查找 OC 文档
在专有名词和常见名词表单中找到含”aa”的专有名词(即首字母大写)。
在给出的程序基础上,思考:
如何判断首字母是否是大写,在NSString
的界面搜索capital
,发现没有相关方法,于是搜索index
,想着应该有用index
获取某个字符的方法,搜索得
|
|
于是将获取的字符与'A'
和'Z'
比较一下就好了。
读入文件并将数据保存在字符串中的方法
|
|
然后贴一下完整的源码,打开的文件是在任意一台有 Mac OS 系统的人都有的文件,不必修改任何代码就可以运行。
|
|
里程碑2:第一个自定义类
创建新类的步骤,Command + n,选择 macOS 区域中的 Cocoa class
点击 Next,填入类名,确定 Group 是存放源代码的文件夹,而不是整个项目,如下图
快捷键
“Ctrl-Command-上箭头”,在 .m 文件及其对应的 .h 文件中切换。
在头文件中声明方法
|
|
在 .m 文件中实现方法
|
|
在 main()添加使用 BNRPerson 类的代码
首先记得在 main.m 中导入头文件,当你打出 BNR… 还没有自动补全的提示,那么很可能是忘记了导入头文件。
|
|
存取方法及命名
getter 和 setter 方法被统称为存取方法 accessor method 或 accessor。
实例变量的命名方法是:开头是下划线,然后是小写开始的驼峰命名法。
方法不以下划线开头,存方法以 set 开头,取方法名和实例变量名相同(只是没有下划线)。
特别需要注意的是,Objective-C 中没有命名空间,所以如果你写了一个称为 Person 的类,而它连接到另一个库,其中有其他人声明的 Person 类,编译器无法区别这两个类,就会出现编译器错误。
为了避免这样的冲突,苹果公司推荐使用三个或三个以上字母作为类的前缀,让类的名字独一无二,就不会和其他人的类名起冲突。
self
Objective-C 的方法都包含一个隐含的局部变量self
,它是一个指向当前方法的对象的指针。当某个对象要向自己发送消息的时候,就需要使用self
。有些人坚持不直接存取实例变量(个人认为是为了避免不小心修改实例变量)的信条,他们会在类的方法中调用对象自身的存取方法。改写一些上文中的bodyMassIndex
方法:
|
|
Properties
可简化存取方法的过程。
Declaring properties
在 BNRPerson.h 中删除实例变量,然后用下面两个属性heightInMeters
和weightInKilos
重写存取方法的声明。
|
|
声明属性的时候,编译器还会帮你声明存取方法。不过有一些例外情况,需要自己调整属性声明或实现存取方法。
Property attributes
- nonatomic
- atomic (default)
- readonly
- readwrite
例如:
|
|
Dot notation
使用 dot notation 的时候是在发送消息。
|
|
Inheritance
创建一个新类,命名为 BNREmployee,还记得怎么创建新类吗?如果不记得,查看左边的目录,点击“里程碑2:第一个自定义类”再复习一下(・ω・)ノ
在 BNREmployee.h,导入 BNRPerson.h,将父类从 NSObject
改为 NSRPerson
|
|
在 BNREmployee.m 文件中,实现 yearOfEmployment
方法:
|
|
在 main.m 中导入 BNREmployee.h,创建一个 BNREmployee 的实例。让 mikey 变量指向一个 BNRPerson
对象。
|
|
Overriding methods
通常,子类会与父类有所不同。假设,员工的 BMI 值都是 19.在这种情况下,可以覆盖BNREmployee
的bodyMassIndex
方法。
可以编写一个新的实现来覆盖继承下来的方法。在BNREmployee.m
方法中,覆盖bodyMassIndex
:
|
|
Inheritance hierarchy
NSObject