环境与工具:MacOSx + Xcode
前言:
- Objective-C 完全兼容 C 语言,其源文件后缀为
.m
,是 message 的意思,代表其消息机制; main.m
中的main
函数仍然是程序的入口;- 框架:是一个功能集合,官方或第三方开发好的程序功能的集合,把这些功能封装在类或函数中,这些类和函数的集合就叫框架
- Foundation 框架:Objective-C 中最基本的框架,提供了一些最基础的功能和数据类型;
- OC 中大量出的
NS
表示乔布斯当初建立的NeXTSTEP公司,后来被引用到 OC 中; - OC 中大量出现的
@
符号表示:- 将 C 字符串转换为 OC 字符串;
- 也应用于 OC 中绝大部分的关键字中;
- OC 中的注释方式与 C 相同;
- OC 中函数的定义与调用方式与 C 相同;
- OC 支持 C 中所有的运算符;
- OC 支持 C 中所有的控制语句语法;
- OC 支持 C 中所有的关键字;
文章目录
💦开始
Hello world!
main.m
// #import 与 C 的 #include 一样,是预处理指令,引入头文件,在程序编译时执行,前者是后者的增强版
// Foundation.h 的位置:Finder - 应用层序 - 右击 Xcode 显示包内容 - Contents - Developer - Platforms - MacOSx.platform - Developer - SDKs - MacOSx10.xx.sdk - System - Library - Frameworks - Foundation.framework - Headers - Foundation.h,打开该文件可见,该文件包含了 Foundation 框架中的所有头文件
// 下句的意思是把 Foundation 框架中的 Foundation.h 头文件包含进来
#import <Foundation/Foundation.h>
// main 函数返回值依然是整型 int,代表程序的结束状态,main 函数的输入参数用于接收传递给该程序的参数,与C的main函数传入参数一样,可以为空
int main(int argc, char *argv[]) {
@autoreleasepool { // @autoreleasepool - 自动释放池
NSLog(@"Hello World!"); // NSLog() 是个函数,于 Foundation.h 中,是 C 中 printf() 的增强版
// 语法:
// NSLog(@"内容");
// NSLog(@"格式控制字符串",变量列表);
// 该函数打印的格式为:当前代码执行时间 + 当前程序工程名称 + 进程编号 + 内容 + 自动换行
}
return 0;
}
编译、链接
见教程第 13 集:
iOS开发基础班+就业班(100天完整版)之基础班2:Objective-C学习(10天)
面向过程与面向对象
它们是解决同一个问题的两种不同思路:
- 面向过程:解决一件事通过自己逐步实现;
- 面向对象:解决一件事找一个专业的方法或人帮忙实现;
面向对象程序设计思想:
- 遇到需求先找有没有专门解决这件事的方法或人,如果有就直接用;
- 如果没有,直接造出拥有这种功能的对象;
类与对象
- 类是具有相同特征的事物的统称,是抽象的,不可直接使用;
- 对象是具体的客观存在的一个实体;
- 类与对象之间的关系:
- 类是对象的模板,根据这类实例化的对象,类有什么特征对象就有什么特征,不可多也不可少;
- 类的本质是程序员自定义的一个数据类型,既然类是数据类型,那么他跟其他数据类型一样,也可以作为方法的传入参数;
main.m
// 声明一个类
@interface Person : NSObject{
@public // 允许实例化的类访问类属性
// 类的属性代表这类对象具有的共同特征
NSString *_name;
int _age;
float _height;
float _weight;
}
// 声明类方法
- (void)run; // 无参数方法
- (void)eatWithFood:(NSString *)foodName; // 带单个输入参数无返回值的方法规范
- (int)sumWithNum1:(int)num1 andNum2:(int)num2; // 带多个输入参数有返回值的方法
-
@end
// 实现一个类
@implementation Person
// 实现类方法
- (void)run{
NSLog(@"i am running.");
// 在类方法的实现中可以访问类属性
_name = @"Yuen";
// 方法中直接访问的属性是属于实例化对象的
NSLog(@"my height is %@",_height)
_weight -= 0.5f;
NSLog(@"after running, my weight is %@",_weight )
}
- (void)eatWithFood:(NSString *)foodName{
NSLog(@"i am eatting %@",foodName);
}
- (int)sumWithNum1:(int)num1 andNum2:(int)num2{
int num3 = num1 andNum2 + num2;
return num3;
}
@end
// 注意:类的声明和实现必须都存在
int main(int argc, char *argv[]) {
// 实例化一个类:类名 *对象名 = [类名 new]
Person *p1 = [Person new];
// 访问对象的属性
p1->_name = @"Yuen";
p1->_age = 18;
p1->_height= 2.26f;
p1->_weight = 200.8f;
(*p1)._name = @"Yuen";
NSLog(@"your name is %@",p1->_name);
// 调用类方法
[p1 run];
[p1 eatWithFood:@"apple"];
int sum = [p1 sumWithNum1:10 andNum2:20];
NSLog(@"the sum is %d",sum );
return 0;
}
😏以上是为了方便阅读把类的声明、实现、调用都放到了main.m文件中,编程中为了方便代码管理,应该分发到不同的文件中;
【例程】类作为方法的传入参数
Person.h
#import <Foundation/Foundation.h>
#import "Dog.h"
@interface Person:NSobject{
@public
NSString *_name;
int _age;
}
-(void)pet:(Dog *)dog; // 把类作为方法的传入参数
@end
Person.m
#import "Person.h"
@implementation Person
- (void)pet:(Dog *)dog{
[dog shout];
}
@end
Dog.h
#import <Foundation/Foundation.h>
@interface Dog:NSobject{
@public
NSString *_name;
int _age;
}
-(void)pet:(Dog *)dog; // 把类作为方法的传入参数
@end
Dog.m
#import "Dog.h"
@implementation Dog
- (void)shout{
NSLog(@"wowowowowowwo...");
}
@end
main.m
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, char *argv[]) {
Person *p1 = [Person new];
Dog *d1 = [Dog new];
[p1 pet:d1]
return 0;
}
【例程】对象作为类的传入参数
Person.h
#import <Foundation/Foundation.h>
@interface Person:NSobject{
@public
NSString *_name;
int _age;
}
- (BOOL)compareAgeWithOtherPerson:(Person *)otherPerson; // 对象作为类的传入参数
@end
Person.m
#import "Person.h"
@implementation Person
- (BOOL)compareAgeWithOtherPerson:(Person *)otherPerson{
return (_age > otherPerson->_age);
}
@end
main.m
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, char *argv[]) {
Person *p1 = [Person new];
Person *p2 = [Person new];
BOOL res = [p1 compareAgeWithOtherPerson:p2];
NSLog(@"res = %d",res);
return 0;
}
【例程】对象作为方法的返回值
Person.h
#import <Foundation/Foundation.h>
#import "Dog.h"
@interface Person:NSobject{
@public
NSString *_name;
int _age;
}
- (Dog *)pet; // 对象作为方法返回值
@end
Person.m
#import "Person.h"
@implementation Person
- (Dog *)pet{
Dog *d1 = [Dog new];
return d1; // 返回对象的指针
}
@end
Dog.h
#import <Foundation/Foundation.h>
@interface Dog:NSobject{
@public
NSString *_name;
int _age;
}
- (void)shout;
@end
Dog.m
#import "Dog.h"
@implementation Dog
- (void)shout{
NSLog(@"wowowowowowwo...");
}
@end
main.m
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, char *argv[]) {
Person *p1 = [Person new];
Dog *d1 = [p1 pet];
return 0;
}
【例程】对象作为类的属性
Person.h
#import <Foundation/Foundation.h>
#import "Dog.h"
@interface Person:NSobject{
@public
NSString *_name;
int _age;
Dog *_dog; // 对象作为类的属性
}
- (Dog *)pet; // 对象作为方法返回值
@end
Person.m
#import "Person.h"
@implementation Person
- (Dog *)pet{
Dog *d1 = [Dog new];
return d1; // 返回对象的指针
}
@end
Dog.h
#import <Foundation/Foundation.h>
#import "Necklace.h"
@interface Dog:NSobject{
@public
NSString *_name;
int _age;
Necklace *_necklace; // 对象作为类的属性
}
- (void)shout;
@end
Dog.m
#import "Dog.h"
@implementation Dog
- (void)shout{
NSLog(@"wowowowowowwo...");
}
@end
Necklace.h
#import <Foundation/Foundation.h>
@interface Necklace:NSobject{
@public
NSString *_colour;
}
- (void)shinning;
@end
Necklace.m
#import "Necklace.h"
@implementation Necklace
- (void)shinning{
NSLog(@"the dog's necklace is %@ and it's shinning...",_colour);
}
@end
main.m
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, char *argv[]) {
Person *p1 = [Person new];
Dog *d1 = [Dog new];
d1->_name = @"旺财";
p1->*_dog = d1; // 对象作为类的属性
p1->_dog->_name = @"大黄"; //修改对象的属性
p1->_dog->_necklace = [Necklace new]; // 对象作为类的属性
p1->_dog->_necklace->_colour = "golden";
[p1->_dog->_nacklace shinning];
return 0;
}
类方法与对象方法
- 类方法:类方法的调用不依赖于对象,即可在不创建对象下调用类方法,直接通过类名来调用;
😀类方法的优点:
- 节约内存空间:因为不用额外创建对象;
- 提高效率:调用类方法时指针直接指向类所在内存地址,而不是间接通过对象来指向内存地址;
😮 注意:
- 类方法中不能直接访问类属性,因为类属性只在对象被创建时才随着在对象中被创建,而类方法在执行时如果没有对象,那么就不能访问类属性;
虽然不能直接访问类属性,但仍可在类方法中创建对象,通过这个对象来访问类属性;- 在类方法中不能通过
self
直接调用当前类的其他对象方法,因为对象方法只能通过对象来调用;
当然也可以跟上面一样,在类方法中创建对象,通过这个对象来访问当前类的其他对象方法;- 综上,如果方法不需要直接访问属性,也不需要直接调用其他同类对象方法,则应该使用类方法,因为他可以降低内存占用和提高效率;
- 类方法的规范:每创建一个类,都应该为这个类提供一个和该类名同名的类方法,并提供一个对象返回值,其作用是在可用一句话创建一个完整的对象,提高代码可读性(你会发现,苹果在底层封装了很多个这样的类方法);
- 对象方法/实例方法:对象方法的调用必须先创建对象,通过对象名来调用;
Person.h
#import <Foundation/Foundation.h>
@interface Person:NSobject{
@public
NSString *_name;
int _age;
}
- (void)sayHi; // 对象方法声明
+ (void)saySorry; // 类方法声明
+ (Person *)PersonWithName:(NSString *)name andAge:(int)age; // 提供一个和该类名同名的类方法,并提供一个对象返回值
@end
Person.m
#import "Person.h"
@implementation Person
- (void)sayHi{ // 对象方法实现
_name = @"Yuen";
NSLog(@"hello,i'm %@",_name);
}
+ (void)saySorry{ // 类方法实现
NSLog(@"Sorry...");
Person *p1 = [Person new]; // 类方法中创建对象
p1->_name = "Yuen"; // 类方法中通过对象访问类属性
[p1 sayHi]; // 类方法中调用当前类的其他对象方法
}
+ (Person *)PersonWithName:(NSString *)name andAge:(int)age{
Person * p1 = [Person new];
p1->_name = name;
p1->_age = age;
return p1;
}
@end
main.m
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, char *argv[]) {
Person *p1 = [Person new];
[p1 sayHi]; // 对象方法调用
[Person saySorry]; // 类方法调用
Person *p2 = [Person PersonWithName:@"Yuen" andAge:18]; // 一句话创建一个完整的对象
return 0;
}
类加载
内存中的五大区域:
- 栈:存储局部变量;
- 堆:程序员手动申请的字节空间,如 malloc、calloc、relaloc函数;
- BSS 段:存储未被初始化的全局变量、静态变量;
- 数据段:(常量区)存储已被初始化的全局静态变量、常量数据;
- 代码段:存储代码;
类加载:在程序运行期间,当某个类被第一次访问的时候,会将这个类存储到内存中的代码段区域,这个过程就叫类加载,直到程序结束时,代码区才会被释放;
💥注意:
- 对象中只有属性,没有方法,因为对象中的方法都是从类而来,是一样的,在内存代码段中占用一小块内存空间即可,没必要浪费更多的内存空间,而属性却可以是自定义的;(见下图)
- 访问对象的属性:
指针名->属性名;
,根据指针找到其指向的对象,再找到对象中的属性来访问; - 调用方法:
[指针名 方法名];
,先根据指针名找到其指向的对象,再根据对象的 isa 指针找到类,然后在类中找到方法;
main.m
@interface Person : NSObject{
@public
NSString *_name;
int _age;
float _height;
float _weight;
}
- (void)sayHi;
@end
@implementation Person
- (void)sayHi{
NSLog(@"hello,my name is %@,i am %d years old.",_name,_age);
}
@end
int main(int argc, char *argv[]) {
// 这种类的实例化方式是合法的,他的意义是在栈内存中声明一个 Person 类型的指针变量 p1,但这个指针变量没有指向任何地址值,这就是**类加载**
Person *p1;
// 当然最标准的类实例化方式还是下面这个,上面那个是合法但没意义的,真正创建对象的是 [Person new]
Person *p1 = [Person new];
/*
new 的作用:
1. 在堆内存中申请一块合适大小的空间;
2. 在这个空间中根据类的模板创建对象,类模板中定义了什么属性,就把这些属性依次声明在对象中;
> 注意:在每一个对象中都有一个隐藏的属性: isa 指针,指向对象所属的类在代码段中的地址值,也即说,不管有多少个对象,如果他们所属的类是同一个,那么他们的 isa 指针中存储的地址值的一样的;
3. 初始化对象的属性;
> 1. 如果属性的类型是基本数据类型,则赋值为 0;
> 2. 如果属性的类型是 C 语言的指针类型,则赋值为 NULL;
> 3. 如果属性的类型是 OC 的类指针类型,则赋值为 nil;
> 其实 NULL 和 nil 都是一个宏,值都是 0,表示指针不指向任何内存空间;
4. 返回对象的地址;
*/
return 0;
}
分组导航标记
- 分组导航标记:方便快速找到对应类;
#pragma mark 分组名
,在导航条的位置显示一个自定义标题;#prama mark -
,在导航条对应位置显示一个水平分隔线;#pragma mark - 分组名
,以上两者的结合;
#pragma mark 人类
@interface Person : NSObject{
}
@end
@implementation Person
@end
#pragma mark - 狗类
@interface Dog : NSObject{
}
@end
@implementation Dog
@end
函数与方法的同异
- 函数:实现某种功能的代码封装,如 C 中的
void test(){}
- 方法:在 OC 类中写的函数,就叫方法,如其中的
- (void)sayHi;
- 同异:本质是一样的:
- 定义的语法不同;
- 定义的位置不同:
- OC 方法的声明只能在
@interface
花括号外边和@end
之间,实现只能在@implementation
和@end
之间; - 函数除了不能在
@interface
花括号外边和@end
之间和函数里面,其他地方都可以(当然为了代码规范,函数就不要写在类里面);
- OC 方法的声明只能在
- 调用方法不同:
- 函数可以直接调用;
- 方法必须先创建对象,通过对象来调用;
main.m
#import <Foundation/Foundation.h>
void test(); // 函数声明
void test(){ // 函数实现
NSLog(@"2333")
}
@interface Person : NSObject{
}
- (void)hello; // 方法声明
@end
@implementation Person
- (void)hello{ //方法实现
NSLog(@"hello");
}
@end
int main(int argc, char *argv[]) {
test(); // 调用函数
Person *p1 = [Person new];
[p1 hello]; // 调用方法
return 0;
}
bug 与 NSException 及其处理
- bug:程序可以编译、链接、执行,但程序执行的结果不是预期效果,这种情况称为 bug;
- Exception:程序可以编译、链接、执行,当程序执行到某一步时,程序执行发生崩溃,程序执行终止,这种情况称为 Exception;
异常处理
Person.h
#import <Foundation/Foundation.h>
@interface Person:NSobject{
@public
NSString *_name;
int _age;
}
- (void)sayHi;
@end
Person.m
#import "Person.h"
@implementation Person
- (void)sayHi{
NSLog(@"hello.");
}
@end
main.m
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, char *argv[]) {
/*
将有可能发生异常的代码放在 @try 中;
当 @try 中的代码执行时发生异常,程序不会崩溃,而是跳转到 @catch 中执行里面的代码,然后结束 @try @catch 继续往下执行;
如果 @try 中代码没有发生异常,就略 @catch 往下执行;
@try @catch 后还可以跟一个 @finally,里面的代码无论 @try 中是否发生异常都会被执行;
*/
/*
值得注意的是, @try @catch 并不是所有的异常都是可以处理的,C 语言中大部分的异常它都无法处理;
*/
@try{
Person *p1 = [Person new];
[p1 sayHi];
}
@catch(NSException *ex){
NSLog(@"throw exception:%@",ex); // 查看异常发生原因
}
@finally{
NSLog(@"coding...")
}
return 0;
}
代码规范
- 在头文件中声明类,在源文件中实现类;
📏数据类型
- OC 支持 C 中的所有数据类型:
- 基本数据类型:
int double float char
; - 构造类型:
数组 结构体 枚举
; - 指针类型:
int *p1
; - 空类型:
void
; - typedef 自定义类型;
- 基本数据类型:
BOOL
类型:存储YES
或NO
,BOOL 类型变量一般用于存储条件表达式的结果;Boolean
类型:存储ture
或false
,Boolean 类型变量一般用于存储条件表达式的结果;class
类型;id
类型:万能指针;nil
类型:与 NULL 差不多;SEL
:方法选择器;block
:代码块;
数据类型是在内存中开辟空间的一个模板;
NSString 字符串
NSString
是OC 中专门用来存储OC 字符串地址的数据类型;- 其实
NSString
是Foundation 框架中的一个类,作用是存储OC 字符串,本质上是用了NSString
对象来存储; NSSting
中最常用的方法:+ (instancetype)stringWithUTF8String:(const char *)nullTerminatedCString;
- 该类方法的作用是将C 的字符串转换为OC 字符串对象;
- 其中
instancetype
为返回值返回这个类的对象;
+ (instancetype)stringWithFormat:(NSString *)format;
- 该类方法的作用是拼接一个字符串对象,实现把不同数据类型的变量拼接成一个OC 字符串;
length
方法没返回值为NSUInteger
,即unsigned long
,得到字符串的个数,支持处理中文;- (unichar)characterAtIndex:(NSUInteger)index;
- 返回值是
unichar
,即unsigned short
,占用内存 2字节;
- 返回值是
isEqualToString:(NSString *)
判断两个字符串的内容是否相同;- (NSComparisonResult)compare:(NSString *)string;
- 作用:比较两个字符串大小;
- 返回值(int):
- 前者比后者小: -1;
- 前者与后者相等:0;
- 前者比后者大: 1;
main.m
int main() {
NSString *str = @"Yuen"; // 定义一个 OC 字符串
// 打印字符串的两种形式
NSLog(@str);
NSLog(@"%@",str);
// 知道NSString 是一个类之后,我们也可以用类实例化的方式来创建字符串,但不推荐
NSString *str1 = [NSString new]; // 创建空字符串@""
NSString *str2 = [NSString string]; // 创建空字符串@""
NSString *str3 = [NSString stringWithFormat:@"Yuen"]; // @"Yuen" 本质上就是NSString 类的对象,*str3 是这个对象的地址
NSLog(@"string is %@,and its memory address is %p.",str3,str3) // 查看字符串内容及其内存地址值
// 将C 字符串转换为OC 字符串对象
char *str4 = "Yuen";
NSString *str5 = [NSString stringWithUTF8String:str4];
NSLog(@"NSString is %@.",str5)
// 拼接OC 字符串
int age = 18;
NSString *str6 = @"Yuen";
NSSting *str8 = NSString *str7 = [NSString stringWithFormat:@"hello,i'm %@ and i'm %d years old.",str6,age]; // 拼接字符串
// 查看字符串长度
NSString *str9 = @"Yuenjunnin";
NSUInteger len = [str9 lenth];
NSLog(@"string lenth is %lu",len);
// 得到字符串中指定下标的字符
unichar ch = [str9 characterAtIndex:2];
NSLog(@"found the specified character:%C",ch); // 查看字符串中指定下表的字符,用%C(大写C),因为%c 对应的是char,而unichar 对应的是%C
// 判断两个字符串的内容是否相同
NSString *str10 = @"dog";
NSString *str11 = @"cat";
BOOL yesOrNo = [str10 isEqualToString:str11];
NSLog(@"Are they two equal? %d",yesOrNo);
// 比较两个字符串大小
NSString *str12 = @"dog";
NSString *str13 = @"cat";
NSComparisonResult res = [str12 compare:str13]; // 该方法的返回值本质是一个枚举,其包含int 类型的-1、0、1,所以也可以定义一个int 类型来接收方法返回值,即 int res = [str12 compare:str13];
NSLog(@"which is bigger of them? %d",res);
return 0;
}
BOOL & Boolean
- 查看源码可见,BOOL 实际上是一个有符号的
char
变量,目的是提高代码可读性;
typedef signed char BOOL
YES
和 NO
实际上是1
和 0
;
#define YES((BOOL)1)
#define NO((BOOL)0)
- 同理,Boolean实际上是一个无符号的
char
变量,目的也是提高代码可读性;
typedef unsigned char Boolean
true
和 false
实际上是1
和 0
;
#define true 1
#define false 0
- 同时使用 BOOL 和 Boolean,是为了提高多种语言的兼容性;
main.m
int main() {
int num1 = 10;
int num2 = 20;
BOOL re1 = num1 > num2
NSLog(@"%@",re1)
return 0;
}
💻Xcode
- xcode 中新建文件时,选
cocoa class
,可自动生成同名的头文件和源文件;
新建工程
Xcode 新建一个新工程 - 选 OSX - Application - Command line Tool - Language 选 Objective-c- 生成main.m
源代码文件;
Source Control不勾选,不用Git管理代码
从 finder 中添加代码文件
从 finder 中直接拉取代码文件到工程中,会弹出以下窗,记得勾选Copy items if needed
和Create groups
;