oc初识

介绍

Objective-Objective-C是C语言的严格超集--任何C语言程序不经修改就可以直接通过Objective-C编译器,在Objective-C中使用C语言代码也是完全合法的。Objective-C被描述为盖在C语言上的薄薄一层,因为Objective-C的原意就是在C语言主体上加入面向对象的特性。

oc文件类型如下:

扩展名 内容类型
.h 头文件。头文件包含类,类型,函数和常数的声明。
.m 源代码文件。这是典型的源代码文件扩展名,可以包含 Objective-C 和 C 代码。
.mm 源代码文件。带有这种扩展名的源代码文件,除了可以包含Objective-C和C代码以外还可以包含C++代码。仅在你的Objective-C代码中确实需要使用C++类或者特性的时候才用这种扩展名。

类的实现

  1. 类的声明,还没有实现
1
2
3
4
5
6
7
8
9
10
// Fanser.h文件
// 继承NSObject类
@interface Fanser : NSObject {
//成员变量
int age;
}
// 属性,方法声明
@property (nonatomic, copy) NSString* name;
- (void) sayHello;
@end
  1. 类的具体实现
1
2
3
4
5
6
7
8
9
10
11
// Fanser.m文件
#import "FansBoy.h"
@implementation Fanser
- (void) sayHello {
NSLog(@"My name is %@", self.name);
}
- (void) private_method {
// 只有当前区快能使用的本方法
}
@end

一个类就是由一个interface和一个implement组成的。

  • 对于interface中声明的方法,可以理解为public方法

  • 对于在implement中实现了而interface中没有声明的可以理解为private方法。只有implement的区块内可以调用到这个方法。

interface声明可以写在.m文件,也可以写在.h文件。implement文件只能写在.m文件中。

  • interface写在.h文件中,其他.m文件可以通过#import的方式引入这个.h文件来使用这个类。
  • interface写在.m文件中,则其他类无法导入该类。

类的创建

1
2
3
4
5
6
7
// main.m
#import "Fanser.h"
int main(int argc, const char * argv[]) {
// 可以使用[Fanser new],等价于[[Fanser alloc] init]
Fanser *fanser = [[Fanser alloc] init];
[fanser sayHello];
}
  • 通过alloc来给对象分配空间,通过init方法来初始化对象。

对于如果需要实现自定义的构造方法,只需要在implement中重写父类的构造方法即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@implementation Fanser
- (void) sayHello {
NSLog(@"My name is %@", self.name);
}
- (void) private_method {
// 只有当前区快能使用的本方法
}
- (instancetype) init {
self = [super init];
if (self) {
self.name = @"fzw";
}
NSLog(@"重写构造方法");
return self;
}
@end

函数方法

  1. 方法定义
1
2
3
// + 代表类方法 - 代表实例方法
// 这里的methodNameWith, paraLable为方法名标签,oc可以给每一个参数进行解释
+(int)methodNameWith:(int)para1 paraLabel:(int)paral
  1. 方法的调用
1
2
3
4
5
6
7
Fanser fanser = [[Fanser new]];
// 类方法调用
int answer = [Fanser methodNameWith:1 paramLable:2];
// 实例方法调用
int answer = [fanser mehtodNameWith:1 paramLable:2];
// 对于没有任何参数的方法可以直接使用.进行调用
int answer = fanser.answer; //相当于[fanswer answer]

oc的一大特色就是其消息模型。Objective-C里,与其说对象互相调用方法,不如说对象之间互相传递消息更为精确。此二种风格的主要差异在于调用方法/消息传递这个动作。C++里类别与方法的关系严格清楚,一个方法必定属于一个类别,而且在编译时(compile time)就已经紧密绑定,不可能调用一个不存在类别里的方法。但在Objective-C,类别与消息的关系比较松散,调用方法视为对对象发送消息,所有方法都被视为对消息的回应。所有消息处理直到运行时(runtime)才会动态决定,并交由类别自行决定如何处理收到的消息。也就是说,一个类别不保证一定会回应收到的消息,如果类别收到了一个无法处理的消息,程序只会抛出异常,不会出错或崩溃。

成员变量

oc不像其他语言一样,oc如果将成员变量在interface中进行声明,则可以被外部访问。如果不在interface中声明,而写在implement中,则为私有,不能被外部访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Fanser.h
@interface Fanser : NSObject {
// 公开变量
@public int public_int_variable;
@protected double protected_double_variable;
}
@end

// Fanser.m
@implementation Fanser {
// 私有成员变量
int _private_int_variable;
double _private_double_variable;
}
@end
1
2
3
4
5
6
7
8
9
//公开变量可以在外部进行访问
// main.m
#import "Fanser.h"
int main(int argc, const char * argv[]) {

Fanser *fanser = [[Fanser alloc] init];
fanser->public_int_variable = 111;
NSLog(@"%d",fanser->public_int_variable);
}

事实上,对于面向对象语言来说,我们并不喜欢使用公开的变量。而是将成员变量私有化,然后通过get/set方法访问变量。下面是访问成员变量正确的打开方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Fanser.h
@interface Fanser : NSObject
// get/set方法的声明
- (void)setName:(NSString *)name;
- (NSString *)name;
@end


// Fanser.m
@implementation Fanser {
//私有变量
NSString* _name;
}
// get/set方法的实现
- (void)setName:(NSString *)name {
self->_name = name;
}
- (void)name {
return _name;
}

属性

对于每个成员变量我们都需要去声明和实现它的get/set方法无疑是会增加程序员的开发成本,所以oc为我们引入的属性。

1
2
3
4
5
6
7
8
9
// fanser.h
@interface Fanser : NSObject
// 属性的声明
@property (nonatomic, copy) NSString* name;
@end

// fanser.m
@implementation Fanser
@end

通过@property可以声明属性。以上代码oc会自动生成_name的私有变量,以及name,setName方法。

当然,属性不止是有这些用处,属性还可以设置很多特性。

  • 访问原子性,默认:atomic
  • 存取特性,默认:readwrite
  • 内存管理,默认:strong
  • 重写get/set方法:getter=getName,setter=assignName
  • 是否可为null,默认:null_unspecified

协议

oc中的协议类似于其他语言等接口,可以声明一些协议,然后一些类可以去遵循这些协议。oc是显示声明。

  1. 协议的声明
1
2
3
4
5
6
7
8
9
10
// FanserProtocol.h
// 协议的定义,<NSObject>意味着继承制自NSObject协议
@protocol Fanser <NSObject>
// @required 要求要实现
@required
- (void) doCodeReview;
// optional 不一定要实现
@optional
- (void)writeDailyReportAt:(NSDate *)date;
@end
  1. 协议的遵守
1
2
3
4
//声明遵守了Fanser,和SeniorLevel协议
// 声明遵守了某些协议以后,就会声明这些协议所含有的方法
@interface Fanser : NSObject <Fanser,SeniorLevel>
@end
  1. 协议类型
1
2
3
4
5
@interface System : NSObject
// id<FanserProtocol> 的类型是遵循FanserProtocol协议的对象。
//一个对象如果遵循了FanserProtocol协议,就可以给fanser赋值。
@property (weak, nonatomic) id<FanserProtocol> fanser;
@end

有的时候,一个类声明要遵守某个协议,但是却不提供其实现。在编译期间会警告,但是是可以编译成功的。如果在程序的运行过程中,调用了这个没有实现的方法,则可能会导致程序出错。

我们可以使用respondsToSelector来判断某个对象是够实现了某个方法。

1
2
3
4
5
Myclass *myObject = [[MyClass alloc] init];
if ([myObject respondsToSelector:@selector(numberOfDatasFrom:)]) {
int dataCount = [myObject numberOfDatasFrom:@"main"];
NSLog(@"实现了该方法");
}

Frameworks

框架的分层

image-20230427195145838

  • Cocoa Touch(接触层):提供了很多ui相关的框架,与界面相关
  • Media(媒体层):提供了很多媒体相关技术支持的框架
  • Core Services(服务层):提供了很多基础能力,核心服务
  • Core OS(操作系统层):苹果提供的最底层的框架

处于上层的框架会依赖与下层框架,下层框架不会依赖于上层

Foundation

通过#import <Foundation/Foundation.h>即可引入Foundation框架

NSObject

所有类的根类(root Class),含有大量oc语言方法。

  1. 分配内存空间&初始化方法
1
2
3
4
5
6
7
8
@interface NSObject <NSObject>
// 为新对象分配内存空间
+(instancetype)alloc;
// 初始化对象
-(instancetype)init;
// 相当于[[NSObject alloc] init]
+(instancetype)new;
@end
  1. 发送消息
1
2
3
4
5
6
7
8
@protocol NSObject
// 发送消息给对象,返回消息执行结果
- (id)performSelector:(SEL)aSelector;
// 发送一个带参数消息给对象,返回消息执行结果
- (id)performSelector:(SEL)aSelector withObject:(id)object;
// 判断对象是否可以调用指定方法
- (BOOL)respondsToSelector:(SEL)aSelector;
@end
  1. 类关系判断
1
2
3
4
5
6
7
8
9
10
11
12
@interface NSObject <NSObject>
// 获取当前对象的类
- (Class)class;
// 获取当前对象的父类
- (Class)superclass;
// 判断对象是否是给定类或给定类的子类的实例
- (BOOL)isKindOfClass:(Class)aClass;
// 判断对象是否是给定类的实例
- (BOOL)isMemberOfClass:(Class)aClass;
// 判断对象是否遵循给定的协议
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
@end

NSString

NSString是一个专门用于处理字符串的类。提供了很多实用的方法

  1. NSString的创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 通过@""创建,常用
NSString *string = @"fzw";
// 通过c字符串创建NSString
NSSTring *string2 = [NSString stringWithUTF8Sring:"fzw"];

// 创建空字符串
NSString *string = [[NSString alloc] init];
// 通过一个NSString创建另一个NSString
NSString *string2 = [NSString stringWithString:@"hello"];
// 拼接字符串
NSString *string3 = [string2 stringByAppendingString:@"world"];

// 格式化创建
NSString *string1 = [[NSString alloc]initWithFormat:@"%d, %@", 123, @"456"];
  1. NSString转为其他类型
1
2
3
4
5
NSString *numberStr = @"456";
BOOL boolValue = [numberStr boolValue];
int intValue = [numberStr intValue];
float floatValue = [numberStr floatValue];
double doubleValue = [numberStr doubleValue];

NSArray

一个数组类,数组操作都习惯使用NSArray。

  1. NSArray的创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 创建空数组
NSArray *arr1 = [NSArray array];
NSArray *arr2 = [[NSArray alloc] init];

// 创建数组
NSArray *arr3 = @[@"a", @"b", @"c"];
NSArray *arr4 = [NSArray arrayWithObjects:@"a", @"b", @"c", nil];

// Array无法存放基本数据类型,如果需要存放基本数据类型,可以先转化为NSNumber
NSNumber *number = [[NSNumber alloc] initWithInt:666];
NSArray *arr5 = [NSArray arrayWithObjects:@(123), number, nil];

// 限制存放的对象的类型
NSArray<NSNumber *> *numberArr = @[@(1), @(2), @(0.3)];
  1. NSArray元素的访问

可以通过arr[index]的方式来访问元素,也可以通过[arr objectAtIndex:index]来访问元素,这两种写法效果一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 在NSArray访问元素的时候,由于无法确定原始类型,所以可以加一些判断。
// 1. 错误写法
NSArray *arr = @[@"abc", @(1)];
for (int i = 0; i < arr.count; i++) {
NSString *str = arr[i];
NSLog(@"%@", str);
}

// 2. 正确写法
NSArray *arr = @[@"abc", @(1)];
for (int i = 0; i < arr.count; i++) {
if ([arr[i] isKindOfClass:[NSString class]]) {
NSString *str = arr[i];
NSLog(@"%@", str);
} else if ([arr[i] isKindOfClass:[NSNumber class]]) {
NSNumber *num = arr[i];
NSLog(@"%@", num);
}
}
  1. 快速枚举(for-in)

oc提供了快速枚举的方式来简化数组的枚举,

1
2
3
4
NSArray<NSString*> *arr = @[@"a", @"b", @"c"];
for (NSString *str in arr) {
NSLog(@"%@", str);
}

注:对于NSArray,是无法改变数组的元素的。如果需要改变数组的元素,则需要使用NSMutableArray。

NSMutableArray

从名字上看,是一个可变数组。可以添加删除元素。

常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//创建一个数组,指定容量为size
+(id)arrayWithCapacity:size
// 初始化一个新分配的数组,指定容量为size
-(id)initWithCapacity:size
//将对象obj添加到数组末尾
-(void)addObject:obj
//将对象 obj 插入到索引为 i 的位置
-(void)insertObject:obj atIndex:i
//将数组中索引为 i 处的元素用obj 置换
-(void)replaceObject:obj atIndex:i
//从数组中删除所有是 obj 的对象
-(void)removeObject:obj
//从数组中删除索引为 i 的对像
-(void)removeObjectAtIndex:i
//用 selector 只是的比较方法将数组排序
-(void)sortUsingSelector:(SEL)selector

NSDictionary

数据字典,主要用于存放key-value键值对。

  1. map的创建
1
2
3
4
5
6
7
8
9
10
11
// 创建
NSDictionary *map = @{
@"key1":@"value1",
@"key2":@"value2"
};
NSDictionary *map2 = [[NSDictionary alloc] initWithObjects:@[@"value1", @"value2"] forKeys:@[@"key1",@"key2"]];
// 限制存放类型
NSDictionary<NSString*, NSNumber*> *map3 = @{
@"key1":@(0),
@"key2":@(1)
};
  1. map的遍历
1
2
3
4
5
6
7
8
9
10
NSDictionary<NSString*, NSString*> *map = @{
@"key1":@"value1",
@"key2":@"value2"
};
NSArray<NSString*> *keys = [map allKeys];
for (int i = 0; i < keys.count; i++) {
NSString *key = keys[i];
NSString *value = map[key];
NSLog(@"%@", value);
}
  1. 快速枚举
1
2
3
4
5
6
7
8
NSDictionary<NSString*, NSString*> *map = @{
@"key1":@"value1",
@"key2":@"value2"
};
for (NSString *key in map) {
NSString *value = map[key];
NSLog(@"%@", value);
}

注:对于NSDictionary,是不可变的。如果需要改变NSDictionary的元素,则需要使用NSMutableArray。

NSMutableDictionary

从名字上看,是一个可变字典。可以添加删除键值对。

常用方法:

1
2
3
4
5
6
// 根据初始容量创建字典
- (instancetype)initWithCapacity:(NSUInteger)numItems;
// 设置一个key-value
- (void)setObject:(ObjectType)anObject forKey:(id<NSCopying>)aKey;
// 根据key删除一个key-value
- (void)removeObjectForKey:(KeyType)aKey;