这是我参与「第四届青训营」笔记创作活动的第1天,记录了7月23日iOS端青训营第二课的课堂内容。
课堂重点
本次课程介绍了Xcode工具的项目结构和基础功能,并且讲解了Objective-C语言的基本语法特征,用OC实现了简单的命令行程序。
Xcode
集合了编译、测试、Git,可以将app直接提交到AppStore
工程体系
工厂(Workspace&Project)里有多条生产线(Target),它们可以共用同样的原料和机器(Project中包含的源代码、资源文件、第三方库等)
- Workspace:一个工作空间,可以包含多个Project,组合成一个庞大复杂的工程
- Project:工程的核心,可以包含多个Target,管理源代码、资源文件、第三方库等
- 后缀为.pbxproj的文件,Old-Style ASCII Property Lists的格式,记录了:
- 对文件的引用
- 源代码
- 资源文件
- Framework, Library
- 虚拟文件夹,不影响真实项目目录结构
- 应包括的Target
- 构建配置
- 对文件的引用
- 后缀为.pbxproj的文件,Old-Style ASCII Property Lists的格式,记录了:
- Target:特定的构建目标(App主体、命令行工具、代码二进制库……),可以利用所在Project中的(部分或者全部)源代码和资源文件,定义了系统的所有输入和文件的处理配置(Build Phases, Build Setting),其对应的构建产物是Product,即运行最终结果
课堂例图
Objective-C
面向对象的程序语言,是标准C语言的扩充,所以C语言代码在这里可以合法通过编译。
OC的类由Interface和Implementation两部分组成
- interface可以声明属性、变量和函数方法,但是不实现,声明内容包含在@interface和@end中间。
- 一个类可以有多个interface
// 类的Interface开始 @interface ByteDancer : NSObject @property (nonatomic, copy) NSString* name; - (void)printHello; @end // 类的Interface结束 - implementation主要实现在interface中声明的方法,实现内容包含在@implementation和@end之间。
- 每一个声明的方法都需要实现,否则编译不通过
- 如果实现了interface中没有声明的方法或变量,那么将被视作private,只能在这个implementation中被访问
// 类implementation 开始 @implementation ByteDancer - (void)printHello { // 实现已经声明的方法 NSLog(@"My name is %@", self.name); } - (void)private_method { // 实现没有声明的方法 NSLog(@"hello"); } @end // 类implementation 结束
.h文件和.m文件
.h文件可以被其他.h和.m文件import,而.m文件不可以被import
- OC类的@interface部分可以写在.h或者.m文件中
- 在.h文件中,该声明可以被其他的.h或者.m引入和调用
- 在.m文件中,只能被当前文件的其他类调用
- OC类的@implementation部分只能写在.m文件中
- .m引入了对应的.h文件,才能调用外部方法的实现 <-> 必须声明在.h中,才能被其他.m调用
对象与构造函数
- 创建对象:创建的是对象指针,必须带
*号#import "ByteDancer.h" ByteDancer *byteDancer1 = [[ByteDancer alloc] init]; [byteDancer1 printHello]; int staticReturn = [ByteDancer staticFunction]; - 构造函数:
- 重写父类默认声明的构造函数init
@implementation ByteDancer // 重写父类NSObject的init - (instancetype)init { // 需要先调用父类的init self = [super init]; if(self) { self.name = @"James"; } NSLog(@"Joined in!"); return self; } - 通过interface声明一个需要参数的构造函数
//在ByteDancer.h里声明 @interface ByteDancer: NSObject // 构造函数 - (instancetype)initWithName:(NSString *)name; @end // 在ByteDancer.m里实现 @implementation ByteDancer - (instancetype)initWithName:(NSString *)name { // 需要先调用父类的init self = [super init]; if (self) { self.name = name; } NSLog(@"A ByteDancer, %@ Joined!", name); return self; } @end
- 重写父类默认声明的构造函数init
函数方法
- 无论是在interface还是complementation,方法均以+/-符号开头
- 加号表示类方法,无实例化,相当于cpp和Java中的static
- 减号表示实例方法
// 在ByteDancer.h中声明 @interface ByteDancer: NSObject // (回传值)方法名:(参数1类型)参数1 标签:(参数2类型)参数2 - (void)sayHello; - (void)groupWith:(NSString *)name; - (ByteDancer *)groupWith:(NSString *)name andWith:(NSString *)name2; // 类方法(静态函数) + (int)staticFunction; @end // 在ByteDancer.m中实现 @implementation ByteDancer // 与 Interface一一对应,但完成函数主体实现 - (void)sayHello { NSLog(@"Hi"); } - (void)groupWith:(NSString *)name { NSLog(@"Hi, I'm group with %@", name); } - (ByteDancer *)groupWith:(NSString *)name andWith:(NSString *):name2 { ByteDancer *group = [[ByteDancer alloc] initWithName: [NSString stringWithFormat:@"%@,%@,%@", self.name, name, name2]]; return group; } + (int)staticFunction { return 0; } @end - 函数调用:用方括号包起来。如果是类方法,用类名+方法名;如果是实例方法,一般用对象名+方法名
#import "ByteDancer.h"
// 创建对象
ByteDancer *byteDancer2 = [[ByteDancer alloc] initWithName:@"James"];
// 实例方法,没有参数
[byteDancer2 sayHello];
// 实例方法,多参数、返回对象
//第一个函数groupWith返回对象group后,再调用它的sayHello
[[byteDancer2 groupWith:@"Andy" andWith:@"Amy"] sayHello];
// 类方法 (静态函数)
int staticReturn = [ByteDancer staticFunction];
成员变量
- 只有放在.h文件的interface里才可以是公开变量,可以被外部调用访问;其余都是私有变量,只允许在相同.m文件里的方法访问。
- public:类内类外均可访问
- protected:类及其子类可以访问
- private:仅类内可访问
(类内:@implementation和@end之间)
// ByteDancer.h
@interface ByteDancer: NSObject{
//公开变量
@public int public_int_variable;
@protected double protected_double_variable;
}
// ByteDancer.m
@implementation ByteDancer {
// 私有
int _private_int_variable;
double _private_double_variable;
}
@end
语言特性
- @property属性
- 用该关键字代替在@interface中声明变量
// ByteDancer.h @interface ByteDancer: NSObject @property (nonatomic, copy) NSString* name; @end- 在类外也可以通过.(点符号)来访问
// 外部 // 属性设置(name为同名变量):相当于[byteDancer3 setName:@"James"] byteDancer3.name = @"James"; // 访问属性(name为同名变量):相当于[byteDancer3 name] NSString *yourName = byteDancer3.name; //内部 self.name = @"James"; self->_name = @"James";- 声明属性(name) = 声明私有变量(_name,自动生成的下划线开头) + 声明并实现对应的get/set方法(getName/setName)
注意⚠️
变量和属性的区别!为属性重写get/set方法时,更改的是私有变量,而不是属性
// 正确案例 @implementation ByteDancer /* 覆盖 Set 方法 * 更改私有属性_name */ - (void)setName:(NSString *)name { _name = [NSString stringWithFormat:@"%@ %@", name, @"!"]; } @end// 错误案例 @implementation ByteDancer /* 覆盖 Set 方法 * .name的调用相当于使用getName/setName, * 而我们重写的本身就是get/set方法,却又在使用旧的方法, * 这样程序就陷入了一种悖论般的死循环 */ - (void)setName:(NSString *)name { self.name = [NSString stringWithFormat:@"%@ %@", name, @"!"]; } @end
协议
- 与父类在目的上有共同点:多个类有共用的方法(即共用的interface),无需各自重新声明方法
- 协议声明
@protocol SeniorLevel <NSObject>
- (void)doCodeReview;
@end
@protocol JuniorLevel <NSObject>
// id<SeniorLevel> 代表任何遵守SeniorLevel的类
- (void)assignMentor:(id<SeniorLevel>)mentor;
@end
// 协议可以继承多个协议
@protocol Developer <NSObject>
- (bool)doCoding;
@end
@protocol ClientDeveloper <Developer>
@end
- 类遵守协议
# import "ByteDancerProtocol.h"
@interface SenioriOSDeveloper: ByteDancer <SeniorLevel, ClientDeveloper>
@end
@interface JunioriOSDeveloper: ByteDancer <JuniorLevel, ClientDeveloper>
// myMentor 类型是任何遵守 SeniorLevel 的类
@property (strong, nonatomic) id<SeniorLevel> myMentor;
@end
- 类实现协议方法
@implementation SenioriOSDeveloper
- (bool)doCoding{
return YES;
}
- (void)doCodeReview {
NSLog(@"Code Review Done");
}
@end
@implementation JunioriOSDeveloper
- (bool)doCoding {
return YES;
}
- (void)assignMentor:(id<SeniorLevel>)mentor {
self.myMentor = mentor;
}
@end
- 创建和调用对象
// 任何遵守 SeniorLevel 的对象
id <SeniorLevel> seniorEmployee = [[SenioriOSDeveloper alloc] initWithName:@"Bob"];
// 指定的对象
JunioriOSDeveloper *junioriOSDeveloper = [[JunioriOSDeveloper alloc] initWithName:@"James"];
// 调用
[junioriOSDeveloper assignMentor:seniorEmployee];
[junioriOSDeveloper doCoding];
[seniorEmployee doCodeReview];
方法调用 = 发送信息
C++函数在编译时确定a调用b的行为(呈现在汇编代码阶段),OC将调用作为信息传递的行为,在运行时才确定回应方式。出于这样的特性:
- 存在Runtime框架:在程序运行时,动态替换方法的实现指针、新增或删除方法。
- 使用respondsToSelector进行方法调用(传入方法名):如果对象响应了,表明方法存在可以直接调用;如果没有响应,说明不存在该方法。
MyClass *myObject = [[MyClass alloc] init];
if ([myObject respondsToSelector: @selector(numerOfDatasFrom:)]) {
int dataCount = [myObject numerOfDatasFrom:@"main"];
NSLog(@"%d", dataCount);
} else {
int dataCount = -1;
NSLog(@"%d", dataCount);
}
总结
快速学习了Xcode和Objective-C的基本知识,是学校和自学速度的100000倍😢消化了好久好久诶。