参与实际开发已经有了一段时间,其中有段时间还尝试学习了swift3.0,但是学到最后的感觉是:虽然对iOS开发有了更深层次的了解,但越来越感觉到基础的重要性。并且,这几天在阅读一些开源框架的时候,发现对于有些代码中的写法会有些陌生,甚至怀疑自己当初有没有学过OC。查了查网上的资料发现,感到陌生是因为有些语法(写法)是之前OC中的规范,现在有了新的写法(例如变量的声明是直接用@property,还是写在interface的大括号中)。但是我觉得还是因为自己的基础不扎实,没有了解到整个语言规范的发展过程,导致无法理解前辈们写的某些句子。
所以复习一遍OC基础,是非常有必要的。我将用记笔记的形式,系统的复习一下OC的基础知识,希望前辈们看到了也能多多批评指正。
目录
一、OC相对于C语言在语法上的不同之处
二、OC中的数据类型
三、面向过程和面向对象
四、类与对象
五、方法的声明和实现
六、其他注意事项
一、OC相对于C语言在语法上的不同之处
概括
OC在C的击基础之上新增了1小部分面向对象的语法.
OC将C的复杂的、繁琐的、可恶的语法封装的更为简单.
OC完全兼容C语言.
后缀
- OC程序的源文件的后缀名是.m m代表message 代表OC中最重要的1个机制 消息机制。C程序的源文件的后缀名.c
main函数
- main函数仍然是OC程序的入口和出口. int类型的返回值 代表程序的结束状态.
import指令.
1).以#号开头,是1个预处理指令.
2).作用: 是#inlcude指令的增强版. 将文件的内容在预编译的时候拷贝写指令的地方.
3).增强: 同1个文件无论#import多少次,只会包含1次.
如果#include指令要实现这个效果 就必须要配合条件编译指令来实现.
而#import指令只需要直接包含就可以 其他什么都不用作.
4).简要原理: #import指令在包含文件的时候,底层会先判断这个文件是否被包含 如果被包含就会略过 否则才会包含.
框架
1). 是1个功能集 苹果或者第三方事先将一些程序在开发程序的时候经常要用到的功能事先写好.把这些功能封装在1个1个的类或者函数之中.
这些函数和类的集合就叫做框架.
有点像C语言的函数库.
2). Foundation
框架.
Foundation
: 基础 基本. 这个框架中提供了一些最基础的功能 输入和输出. 一些数据类型.
Foundation.h
的路径:
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/System/Library/Frameworks/Foundation.framework/Versions/C/Headers/Foundation.h
Foundation.h
这个文件中包含了Foundation框架中的其他的所有的头文件.
所以,我们只要包含Foundation.h
就相当于包含了Foundation
框架中所有的头文件.
那么Foundation
框架中的所有的函数和类就可以直接使用.
@autoreleasepool
是自动释放池.(现在开发基本上用不到,不做叙述)
NSLog函数.
1). 作用: 是printf函数的增强版. 向控制台输出信息.
2). 语法:
NSLog(@"格式控制字符串",变量列表);
最简单的语法:
NSLog(@"要输出的信息");
3). 增强:
a. 输出一些调试相关信息.
2020-07-01 12:40:52.628 Day01-OC基础语法 [784:210576] Hello,World!
执行这段代码的时间.
程序的名称.
进程编号.
线程编号.
输出的信息.
b. 会自动换行.在输出完信息之后 会自动换行.
c. OC中其实新增了一些数据类型. NSLog函数不仅仅可以输出C数据类型变量的值还可以输出OC新增的数据类型的变的值.
4).用法和printf函数差不多. 一样可以输出变的值 并且占位符和用法都一样
5).使用注意:
a. NSLog函数的第1个参数前面必须要加1个@符号.
b. 如果手贱在字符串的末尾加了1个'\n'代表换行 那么函数的自动换行功能就会失效.
字符串
1). C语言的字符串的存储方式
a. 使用字符数组存储
b. 使用字符指针
2). OC中设计了1个更为好用的用来存储字符串的1个类型. NSString
NSString 类型的指针变量 专门用来存储OC字符串的地址.
3). OC的字符串常量必须要使用1个前缀@符号.
"jack" 这是1个C语言的字符串.
@"jack" 这是1个OC的字符串常量.
NSString类型的指针变量,只能存储OC字符串的地址.
NSString *str = @"jack";
4).总结
a. 在OC中专门设计了1个NSString类型来存储字符串.
b. 字符串分为C字符串和OC字符串.
字符串如果没有@前缀 那么这个字符串常量就是1个C字符串.
字符串如果有@前缀 那么这个字符串常量就是OC字符串.
@"jack"
"rose"
所以,OC字符串常量的前面必须要加1个@符号.
c. NSString类型的指针变量 只能存储 OC字符串.
NSString *str = @"jack";
5). 注意
1). NSLog函数的第1个参数是1个OC字符串,所以NSLog函数的第1个实参应该以@符号开头.
NSLog(NSString * format...)
2). 如果要使用NSLog函数输出OC字符串的值,那么使用占位符%@
NS前缀.
NextStep ---> Cocoa ---> Foundation框架之中.
@符号
1). 将C字符串转换为OC字符串.
"jack" @"jack"
2). OC中的绝大部分的关键字都是以@符号开头.
注释:
和C语言的注释一模一样.分为单行注释和多行注释.
函数的定义和调用.
与C语言的函数的定义和调用是一样的.
OC程序的编译、连接、执行.
1). 在.m文件中写上符合OC语法规范的源代码.
2). 使用编译器将源代码编译为目标文件.'
cc -c xx.m
a. 预处理
b. 检查语法
c. 编译.
3).链接
cc xx.o
如果程序中使用到了框架中的函数或者类.那么在链接的时候,就必须要告诉编译器
去那1个框架中找这个函数或者类.
cc xx.o -framework
框架名称
.
cc main.o -
framework Foundation
程序中用到了那1个框架中的功能 那么就再这个地方告诉编译器.
4) 链接成功以后 就会生成1个a.out可执行文件 执行就可以了.
OC程序和C程序各个阶段的后缀名对比
源文件 | 目标文件 | 可执行文件 | |
---|---|---|---|
C | .c | .o | .out |
OC | .m | .o | .out |
二、OC中的数据类型
OC中支持C语言中的所有的数据类型.
a. 基本数据类型
int double float char
b. 构造类型
数组 结构体 枚举
c. 指针类型
int *p1;
d. 空类型
void
e. typedef自定义类型.
BOOL类型.
1). 可以存储YES或者NO中的任意1个数据.
2). 一般情况下BOOL类型的变量用来存储条件表达式的结果.如果条件表达式成立 那么结果就是YES
如果条件表达式不成立 结果过就是NO
3). BOOL的本质.
typedef signed char BOOL;
实际上BOOL类型的变量 是1个有符号的char变量.
#define YES ((BOOL)1)
#define NO ((BOOL)0)
YES 实际上就是 1
NO 实际上就是 0
3). Boolean(我从来没用过)
a.Boolean类型的变量可以存储true或者flase
b.一般情况下Boolean类型的变量用来存储条件表达式的结果.如果条件表达式成立 那么结果就是true
如果条件表达式不成立 结果过就是false
c. 本质
typedef unsigned char Boolean;
#define true 1
#define false 0
class 类型. 类.
id类型 万能指针.
nil 与NULL差不多.
SEL 方法选择器.
block 代码段.
三、面向过程和面向对象
学这节课之前我是这么理解的
也是这么理解的
究竟是什么呢?
OC是在C的基础之上.
a. 将C复杂的繁琐的恶心的语法封装的更为简单.
#import
NSLog
NSString
.......
b. 在C语言的基础之上新增了一小部分的面向对象的语法.
实现需求之一:
要把大象放进冰箱应该怎么办?
a. 打开冰箱门.
b. 把大象放进去
c. 把冰箱门关上.
有没有更好的方式:
找1个冰箱 自己开门 自己把大象拉进去 自己关门.
同1件事情我们有两种不同的解决思路.
如果解决1件事情的时候,每1件事情都是我们亲自去一步步实现 那么这种解决问题的思路叫做面向过程的解决思路.
如果解决1件事情的时候,自己不去亲自做,而是找1个专门做这件事情的人来帮助我们做.这样解决问题的思路我们就叫做面向对象的解决思路.
面向过程与面向对象是解决同1个问题的不同的思路.
代码世界的面向过程与面向对象.
1). 面向过程.
在遇到1个需求的时候,实现这个需求的每1个步骤 都是自己写代码亲自的去一步步的实现. 这样的解决问题的方式我们就叫做面向过程的方式.
2). 面向对象.
在遇到1个需求的时候,不要亲自去实现 而是找1个专门做这件事情的人来帮助我们搞定. 这样的解决问题的方式就叫做面向对象的解决方式.
面向对象和面向过程优缺点分析.
C语言是1门面向过程的语言. 有功能的概念 但是没有人的概念.
OC语言是1门面向对象的语言.
面向过程的解决问题的缺点: 后期的维护和修改不方便.
面向对象的解决问题的优点: 后期的维护和修改十分方便.
使用面向对象设计我们的程序,可以让我们的程序在后期的维护和修改当中更加的方便和快捷.
如何使用面向对象来设计程序呢?
当你遇到1个需求的时候. 不要亲自去实现.
1). 先看看有没有现成的人是专门做这件事情的. 框架. 如果有直接使用
2). 如果没有.就自己造出1个拥有这样的功能的人. 那么造出来的这个人可以多次使用的.
四、类与对象
什么是对象?
对象是现实生活中的1个具体存在. 看得见、摸的着.拿过来就可以直接使用.
什么是类.
物以类聚 人以群分.
类是对一群具有相同特征或者行为的事物的1个统称. 抽象的. 不能直接使用.
如果非要使用类的话呢?只能去找到这1类事物中的1个具体存在,然后使用这个具体存在.
类和对象之间的关系.
类是模板.类的对象是根据这个模板创建出来的.
类模板中有什么,对象中就有什么 绝不可能多 也绝不可能少.
类是制造月饼的模子. 模子不可以吃 所以类不能直接使用.
对象是根据模子制造出来的月饼, 模子上有什么 月饼上就有什么 不会多也不会少.
月饼可以吃 所以对象可以使用.
如何设计1个类.
设计类的三要素.
-> 类的名字. 你要描述的这类事物叫什么名字.
-> 这类事物具有的相同的特征. 这类事物拥有什么
-> 这类事物具有的共同的行为. 这类事物会做什么.
如何找到类.
名词提炼法: 分析整个业务流程,分析出现了哪些名词. 这些名词就是你要找到的类.
小明在公交车上牵着1只叼着热狗的狗.;
人类:
姓名 性别....
搭公交. 遛狗.
公交车
品牌 价格 颜色..
行驶.
热狗
大小 颜色 味道 温度.
散发味道.
狗
颜色 品质......
吃热狗....
请问是先有类还是先有对象?
从现实的角度 一定是先有对象再有类.
从代码的角度 一定是先有类再有对象.
因为类是一群具有相同特征和行为的集合。
如何定义类.
1).类的三要素.
2).定义类的语法
a. 位置. 直接写在源文件之中 不要写在main函数之中.
b. 类的定义分为两个部分.
-> 类的声明
@inteface 类名 : NSObject
{
这类事物具有的共同的特征.将他们定义为变量.
}
功能就是1个方法.将方法的声明写在这里.
@end
-> 类的实现
@implementation 类名
将方法的实现写在这里.
@end
3).几点注意
a. 类必须要有声明和实现
b. 类名用你描述的事物的名称来命名就可以了.
类名的每1个单词的首字母必须要以大写开头.
c. 用来表示着类事物的共同的特征的变量必须要定义在@interface的大括弧之中.
d. 定义在大括弧之中用来表示着类事物的共同的特征的变量我们叫做.
属性
成员变量
实例变量
字段
.......
e. 为类定义属性的时候,属性的名词必须要以_开头 下划线开头.
语法是固定的.
@interface 类名 : NSObect
{
这类事物具有的共同的特征定义为变量.
数据类型 变量名1;
数据类型 变量名2;
.........
}
@end
@implementation 类名
@end
类是无法直接使用的. 如果非要使用这个类的话,就必须要先找到这个类中的1个具体存在.
再使用这个对象
类和对象的关系: 类中有的东西 这个类的对象也有 不会多少也不会少. 对象就可以使用.
这个时候我们就可以使用对象里面东西..
如何创建1个类的对象呢?
语法: 类名 *对象名 = [类名 new];
Person *p1 = [Person new];
根据Person这个类的模板,创建了1个对象名字叫做p1.
p1对象的特点:
-> 可以直接使用.
-> 类中定义的东西 这个对象中也有 不会多也不会少.
如何使用对象;
如何访问对象的属性:
1). 默认情况下,对象的属性是不允许被外界直接访问的.
如果允许对象的属性可以被外界访问,那么就再声明属性的是加1个@public关键字.
2). 访问对象的属性的方式(这是最初的方式,后面会用点语法)
对象名->属性名 = 值;
对象名->属性名;
五、方法的声明和实现
定义1个类.
分为类的声明和实现
@interface 类名 : NSObject
{
属性 属性表示类的特征.
}
方法的声明; 方法表示类的功能.
@end
@implementation 类名
方法的实现;
@end
类事物不仅具有相同的特征还具有相同的行为.
行为就是1个功能. C语言中使用函数来表示1个功能.
OC的类具有的行为,我们使用方法来表示。
方法和函数都表示1个功能.
无参数的方法.
1). 声明
a. 位置: 在@interface的大括弧的外面.
b. 语法:
- (返回值类型)方法名称;
- (void)run;
表示声明了1个无返回值并且无参数的方法 方法名字叫做run
2). 实现
a.位置: 在@implementation之中实现
b.实现的语法:
将方法的声明拷贝到@implementation之中.去掉分号 追加大括弧1对 将方法实现的代码写在大括弧之中.
3).调用
a. 方法是无法直接调用的.因为类是不能直接使用的.必须要先创建对象.
那么这个对象中就有类中的属性和方法了 就可以调用对象的方法了.
b. 调用对象的方法.
[对象名 方法名];
带1个参数的方法.
1). 声明
a.位置: 在@interface的大括弧的外面.
b.语法:
- (返回值类型)方法名称:(参数类型) 形参名称;
- (void)eat:(NSString *)foodName;
定义了1个方法 这个方法没有返回值.
这个方法的名字叫做eat:
这个方法有1个参数,类型是NSString *类型的 参数的名字叫做foodName
(void)eat:(NSString *)foodName;
void eat(NSString *foodName);
2). 实现
a.位置: 在@implementation之中实现
b.语法: 将方法的声明拷贝到@implementation之中.去掉分号 追加大括弧1对 将方法实现的代码写在大括弧之中.
3). 调用
a. 方法是无法直接调用的.因为类是不能直接使用的.必须要先创建对象.
那么这个对象中就有类中的属性和方法了 就可以调用对象的方法了.
b. 调用语法:
[对象名 方法名:实参];
方法头中的数据类型都要用1个小括弧括起来.
- (返回值类型)方法名称:(参数类型)参数名称;
带多个参数的方法.
b. 语法:
- (返回值类型)方法名称:(参数类型)形参名称1 :(参数类型)参数名称2 :(参数类型)参数名称3;
- (int)sum:(int)num1 :(int)num2;
表示声明了1个方法 这个方法的返回值类型是int类型的.
方法的名称叫做 sum: :
有两个参数 参数类型都是int类型 参数名称叫做num1 num2
2).实现.
b. 实现的语法: 将方法的声明拷贝到@implementation之中.去掉分号 追加大括弧1对 将方法实现的代码写在大括弧之中.
3).调用:
a. 方法是无法直接调用的.因为类是不能直接使用的.必须要先创建对象.
那么这个对象中就有类中的属性和方法了 就可以调用对象的方法了.
b. 调用带多个参数的语法
[对象名 方法名:实参1 :实参2 :实参3];
带参数的方法声明的规范:
1). 如果方法只有1个参数. 要求最好这个方法的名字叫做 xxxWith:
xxxWithxxx
eatWith:
eatWithFood:
这样写的话,那么调用方法的时候看起来就像是1条完整的语句. 提高了我们代码的阅读性.
遵守的规范: 就是让我们的方法调用的时候看起来像1条完整的语句.
2).如果方法有多个参数 建议这个方法名称:
方法名With:(参数类型)参数名称 and:(参数类型)参数名称 and:(参数类型)参数名称;
- (int)sumWith:(int)num1 and:(int)num2;
sumWith: and:
更详细的写法
方法名With参数1:(参数类型)参数名称 and参数2:(参数类型)参数名称 and参数3:(参数类型)参数名称;
六、其他注意事项
- 同1个类可以创建无数个对象.
- 同1个类的多个对象之间毫无关系.
虽然他们拥有相同的属性和方法.
属性的值是不会相互影响的
- 在方法的实现中,可以直接访问属性.
在方法中访问的属性是谁的属性呢?
这个方法是通过那1个对象来调用的.那么方法中的直接访问的属性就是那1个对象的.
其他基础内容
目录
分组导航标记
异常处理
多文件开发
Xcode文档的安装
static关键字
self关键字
分组导航标记:
1). #pragma mark 分组名
就会在导航条对应的位置显示1个标题.
2). #pragma mark -
就会在导航条对应的位置显示1条水平分隔线.
3). #pragma mark - 分组名
就会在导航条对应的位置先产生1条水平分割线.再显示标题.
异常处理
-
什么是错误?
一般情况下,错误指的是我们写的源代码不符合语法规范. 然后编译报错.
后果: 程序无法编译.
解决: 将不符合语法规范的代码改为符合语法规范的代码不就可以了吗.
-
什么是Bug?
程序可以编译 链接 执行.程序执行的结果并不是我们所预想的那样.
解决: 通过调试寻找发生Bug的原因.
-
什么是异常?
程序可以编译 链接 执行.
当程序在执行的时候 处于某种特定条件下 程序的执行就会终止.
异常的后果: 程序会立即崩溃.程序立即终止运行.并且后面的代码不会执行了 -
如何处理异常.
1). 目的: 为了让程序在执行的时候如果发生了异常而不崩溃 而是继续往下执行.
2). 语法:
@try { } @catch(NSException *ex) { } 将有可能发生异常的代码放在@try中. 当@try中的代码在执行的时候.如果发生了异常.不会崩溃,而是会立即跳转到@catch中去执行里面的代码. 当@catch的代码执行完毕之后 结束@try...@catch往下执行. 如果@try中的代码在执行的时候 没有发生异常. 就会略过@catch往下执行.
3). 当@try中的代码在执行的时候发生了异常.@try块发生异常的后面的代码不会执行.而是立即转到@catch
-
@catch中的代码只有在@try的代码发生异常的时候才会执行.
所以,@catch中我们一般情况下写处理异常的代码.
发生这个异常以后 要做神马事情.
-
@catch的参数NSException *ex 通过%@打印出ex指向的对象的值.
可以拿到发生异常的原因
-
@try...@catch后面还可以跟1个@finally
@finally中的代码.无论@try中是否发生了异常都会被执行.
-
@try..@catch并不是万能的.不是所有的运行时错误都可以处理的.
C语言的异常是无法处理的。
在实际的开发过程中 使用@try用的相对比较少的.
避免异常我们最常用的方式还是逻辑判断.
多文件开发
-
所有的类都写在main.m这个源文件之中的.
后果: 后期的维护就非常的不方便.也不利于团队开发
-
推荐的方式
把1个类写在1个模块之中. 而1个模块至少包含两个文件.
.h 头文件
写的类声明 因为要用到Foundation框架中的类 NSObject 所以在这个头文件中要引入Foundation框架的头文件. 然后将类的声明的部分写在.h文件中
.m 实现文件
先引入模块的头文件 这样才会有类的声明 再写上类的实现. 如果要用到类. 只需要引入这个了模块的头文件就可以直接使用了.
-
添加类模块的更简洁的方式
NewFile Cocoa Class 自动生成模块文件 .h .m
自动的将类的声明和实现写好.填写的名称是决定模块文件的名称. 类名是可以自己再改的
但是建议模块的文件名和模块中的类名保持一致.这样方便代码的管理. 当我们要使用这个类的时候,需要先将这个类的头文件先引进来.才可以使用.
Xcode文档的安装
-
苹果提供了很多很多的框架.框架中有很多类 很多的函数 提供了一些数据类型.
遇到的问题:
a. 你如何知道到底有哪些框架
b. 框架中有哪些类.
c. 类只又有哪些牛X的方法.
d. 如何调用这些类.所有的这一切.Xode文档.详细的描述了这些信息.
-
Xcode文档需要单独的安装.
1). 在线安装.
2). 离线安装.
/Applications/Xcode.app/Contents/Developer/Documentation/DocSets
static关键字
-
C语言中的static
a. 修饰局部变量.
b. 修饰全局变量.
c. 修饰函数.
-
OC中的static关键字.
a. static不能修饰属性 也不能修饰方法.
b. static可以修饰方法中的局部变量.
c. static 也可以用来修饰全局变量(比如cell的重用ID会经常用到)
如果方法中的局部变量被static修饰,那么这个变量就会被变成静态变量.
存储在常量区 当方法执行完毕之后 不会回收 下次再执行这个方法的时候 直接使用 而不会再声明了.
- 如果方法的返回值是当前类的对象,那么方法的返回值就写instancetype
self关键字
-
在方法的内部可以定义1个和属性名相同的局部变量.
这个时候 如果在方法中访问这个同名的变量,访问的是局部变量.
问题1: 如果这个时候我就是要访问那个同名的属性,怎么办?
问题2: 在1个对象方法中要调用当前对象的另外1个对象方法怎么办?
-
self:自己的.
和Java、C#中的this关键字有1点点像.可以在对象方法和类方法中使用.
self是1个指针. 在对象方法中self指向当前对象. 在类方法中self指向当前类.
-
self
用在对象方法中.-
self在对象方法中,指向当前对象.
当前对象: 谁调用方法谁就是当前对象.
-
作用:
a. 可以使用self显示的访问当前对象的属性.
self->属性. 代表访问的是当前对象的这个属性.
b. 可以使用self来调用当前对象的其他的对象方法.
-
对象方法中使用self的场景.
a. 必须使用self的场景.
-> 如果在方法中存在和属性同名的局部变量你如果想要访问同名的局部变量,直接写就可以了.
你如果想要访问当前对象的同名属性 必须使用self
-> 在对象方法中,如果要调用当前对象的其他的对象方法 必须使用self.
b.选用self的场景.
在方法中不存在和属性同名的局部变量 如果这个时候想要访问当前对象的属性
用布用self效果都是一样的.都是访问的当前对象的属性.属性要求以下划线开头 局部变量不要求以下划线开头 按照这个规范来,实际上是不会重名的.
-
-
把
self
用在类方法中.-
类加载. 当类第1次被访问的时候 会将类的代码存储在代码区.
代码区中用来存储类的空间也有1个地址.
-
在类方法中 self也是1个指针.执行当前这个类在代码段中的地址.
self 在类方法中 就相当于是当前这个类.
-
总结一下取到类在代码段中的地址的方式.
a. 调试查看对象的isa指针的值.
b. 在类方法中查看self的值.
c. 调用对象的对象方法class 就会返回这个对象所属的类在代码段中的地址.
d. 调用类的类方法class 就会返回这个类在代码段中的地址
-
有神马用?
可以在类方法中使用self来显示地调用本类的其他的类方法.
使用建议,如果要在当前类方法中调用本类的其他的类方法 虽然可以直接使用类名 但是建议使用self
-
-
对象方法可以声明多次.但是只会认为有1次.
对象方法如果有多次声明只能实现1次 否则就会报错.
对象方法、类方法之间是不能重名的.
但是,对象方法和类方法是可以重名的.
通过类名来调用 调用的就是类方法,反之是对象方法
-
注意
在对象方法中 不能使用self调用本类的类方法.
在类方法中 不能使用self访问对象的成员. 不能去直接访问属性和调用对象方法.