Objective-C语言基础学习总结
目录
这几天通过观看B站视频《iOS开发工程师 零基础到APP上线》,对OC的基础语言部分进行了系统学习,对OC语言的一些特性有了初步了解。在学习过程中,通过与C++中class对比,来加深自己的理解。接下来我会阅读官方文档《Programming with Objective-C》继续巩固语言,然后通过观看视频《iOS 7 应用开发》学习如何开发APP。
下面是对近期学习的一个总结,巩固知识的同时,也能方便以后查阅(如有不正确的地方,欢迎大家批评指正)。
1.学习环境和资源
首先介绍一下我目前的学习环境、工具和几个学习iOS开发经常会访问到的一些网站以及相关书籍。
环境:因为我的电脑是Windows系统,所以只能安装虚拟机了,我安装的版本是:VMware Workstation Pro 15,macOS 13
工具:Xcode 是运行在操作系统Mac OS X上的集成开发工具(IDE),可以直接在Apple Store下载,但是由于Apple Store中Xcode最新版本不支持macOS 13,所以我去官网http://developer.apple.com下载了Xcode 9(Xcode版本只能向上适配macOS版本,而不能向下)。
苹果社区:http://developer.apple.com,在这里可以找到官方文档,各个版本的系统软件等资源。
第三方网站:stackoverflow,Ourcoders, Cocoa China,Github。
书籍:苹果官方文档《Programming with Objective-C》,《Programming iOS 7》等。
2.Objective-C概览
(1)Objective-C
Objective-C是编写基于OS X和iOS系统软件的主要语言,它在C语言的基础上,实现了面向对象编程,具有封装、继承和多态特性。Objective-C是C语言的严格超集:任何C语言程序不经修改可以直接通过Objective-C编译器,Objective-C中使用C语言代码也是合法的。
Objetive-C代码的文件扩展名
扩展名 | 内容类型 |
.h | 头文件。头文件包含类、类型、函数和常数的声明 |
.m | 源代码文件。典型的源代码文件扩展名,可以包含Objective-C和C代码 |
.mm | 源代码文件。带有这种扩展名的源代码文件,除了Objective-C和C代码,还可以包含C++代码。较少使用,除非代码中确实需要C++类或者特性时才用这种扩展名 |
(2)头文件
C++中使用#include添加头文件,而Objective-C中通过#import添加头文件。例如:
#import <Foundation/Foundation.h>
(3)main函数
Objective-C程序同样以main函数为入口。例如:
#import <Foundation/Foundation.h>
int main(int argc, const char *argv[]) {
@autoreleasepool {
//insert code here....
}
return 0;
}
@autoreleasepool是自动释放池,整个块中内容支持ARC(Auto Reference Count自动引用计数),系统在资源引用计数为0时,自动释放资源,不需要手动释放(ARC有些类似C++11中的smarter pointer)。
3.类
(1)类的声明和实现
类的声明
通常会在头文件.h中声明一个类。与C++使用class来声明类不同,Objective-C中类的声明以@interface开头,以@end结尾,中间是数据成员和方法的声明。例如声明类RPoint:
@interface RPoint:NSObject
//添加数据和方法
@end
上述例子中SimpleClass是类名,该类继承自类NSObject。Objective-C中类名一般遵循首字母大写的原则,且程序员声明的类都应该继承自根类NSObject。因为NSObject中已经包含了类所需的分配器alloc、初始化器init、析构器dealloc等成员方法,继承后可直接调用。
类的实现
通常在与声明类的头文件同名的.m文件中实现类。C++中通过作用域符号来实现类中函数,而Objective-C中实现方法如下:
#import "PRoint.h"
@implementation RPoint:NSObject
//添加方法定义
@end
Objective-C只要声明了类,那么一定要实现它,即使实现代码块中不需要做什么。
(2)类的数据成员
属性Property
- 属性表达实例状态,描述类型对外接口。相比直接访问实例变量,属性可以做更多控制。例如向上述RPoint类中添加属性:
@interface RPoint:NSObject
@property int x;
@property int y;
@end
- 默认情况下,编译器会为属性定义的propertyName自动合成getter、setter访问器方法以及一个实例变量。类外可以使用属性访问,类内也可以通过self访问(self类似C++中this指针):
#import <Foundation/Foundation.h>
#import "RPoint.h"
int main(int argc, const char *argv[]) {
@autoreleasepool {
//Objective-C中为对象动态申请内存,后面会细说
RPoint *rp1 = [[RPoint alloc] init];
rp1.x = 10; //通过"."访问属性
rp1.y = 20;
}
return 0;
}
实例变量Instance Variable
- 系统会为属性propertyName自动合成一个实例变量_propertyName,而getter和setter方法中访问的是_propertyName.。
- 可以定义实例变量,而不定义属性。
- 如果同时自定义了setter和getter访问器方法,或者针对只读属性定义了getter方法,编译器将不再合成实例变量。
注意:类外一律使用属性访问,类内通过self进行属性访问。只有以下情况使用实例变量访问:初始化器init,析构器dealloc,自定义访问器方法。
(3)方法
全局函数
- 类以外定义的函数,NSLog就是一个全局函数。
成员函数
现在声明一个类BLNPoint,向其中添加实例方法和类型方法:
@interface BLNPoint:NSObject
@property int x;
@property int y;
//实例方法,无参数,无返回值
-(void) print;
//实例方法,有两个参数,无返回值
-(void) moveToX:(int) x ToY:(int) y;
//类型方法,无参数,返回类型为BLNPoint
+(BLNPoint *) getOriginPoint;
@end
以"-"开头的方法称为实例方法Instance method,通过类对象对其进行调用;而以"+"开头的方法称为类型方法Class method,类型方法类似于C++中的静态方法,直接通过类来调用。Objective-C类中方法默认是public。(21号从函数参数开始写起。。。)
消息发送
方法调用,即动态消息发送。例如:
#import <Foundation/Foundation.h>
#import "BLNPoint.h"
int main(int argc, const char *argv[]) {
@autoreleasepool {
BLNPoint *p1 = [[BLNPoint alloc] init];
//向对象p1发送print消息
[p1 print];
//向对象p1发送moveToX: ToY:消息,该消息包含两个参数
[p1 moveToX:100 ToY:200];
//向类发送getOriginPoint消息
[BLNPoint getOriginPoint];
}
return 0;
}
上述例子中的-(void) print 方法是不带参数的,而-(void) moveToX:(int) x ToY:(int) y 函数是带有两个参数的方法,发送消息需要给参数赋值 [p1 moveToX:100 ToY:200]。如果方法有参数,方法名约定包含第一个参数名,如方法名moveToX:(int) x就包含了第一个参数,从第二个参数开始需要提供外部参数名 ,ToY:(int) y表示第二个参数,ToY是外部参数名。Objective-C中参数名是函数名的一部分,Objective-C不支持重载,如果两个函数名字相同,则出现错误。
上面两个方法都是实例方法,+(BLNPoint *) getOriginPoint是类型方法,只能通过向类发送该消息。Objective-C方法的定义以及调用/消息发送与C++的函数有较大差别,这里需要注意。
访问权限
Objective-C的实例方法可以访问的成员有:实例成员(属性,实例变量,实例方法)、类型方法、静态变量(实例方法我将其类比C++中class的普通成员函数,它可以访问所属类的所有成员)。
Objective-C的类型方法可以访问的成员有:类型方法,静态变量;不可以访问实例成员(类型方法将其类比为C++中静态成员函数,这种函数只能通过类名调用,并且函数内部只能访问静态变量,不能访问非静态变量)。
初始化器和析构器
- 初始化对象实例时,使用alloc和init,如:BLNPoint *p1 = [[BLNPoint alloc] init],alloc分配内存,init初始化 。
- 系统提供init方法,也可以自定义。如果要自定义初始化器,必须采用以下形式:
首先在类中声明:
@interface Book : NSObject
-(id) init; //默认初始化器
-(id) initWithName : (NSString *) name;
-(id) initWithName : (NSString *) name WithPage:(int) pages;
-(id) initWithName : (NSString *) name WithPage:(int) pages WithCategory:(NSString *) category;
@end
然后在implementation中定义初始化器:
#import "Book.h"
@implementation Book
-(id)init
{
self = [super init];
if(self)
NSLog(@"Book Object init");
return self;
}
-(id)initWithName:(NSString*)name
{
return [self initWithName:name WithPages:0 WithCategory:@"General"];
}
-(id)initWithName:(NSString*)name WithPages:(int)pages
{
return [self initWithName:name WithPages:pages WithCategory:@"General"];
}
//对参数最多的初始化器真正实现它,其他的初始化器则调用该函数
-(id)initWithName:(NSString*)name WithPages:(int)pages WithCategory:(NSString*)category
{
self = [super init];
//判断指针是否为nil,不是nil才能够初始化
if(self)
{
NSLog(@"Book Object init");
_name = [name copy]; //对于指针变量要深拷贝
_pages = pages;
_category = [category copy];
}
return self;
}
自定义初始化器的格式通常如下:调用父类的初始化器--初始化实例变量,即用传入参数赋值实例变量,对于指针变量必须调用copy方法(copy方法类似于C++类中的深拷贝,既拷贝了指针,又拷贝了相应的内存,并让拷贝指针指向拷贝内存)。如果不调用copy方法,那么当传入变量变化时,类中相应实例变量也会发生变化,这不是我们希望发生的现象。
当有多个初始化器,每个初始化器参数个数不同时,通常真正实现参数最多的初始化器,其他的初始化器则可以直接调用该初始化器。
- 类初始化器initialize:负责类型级别的初始化,initialize在每个类使用之前被系统自动调用,子类的initialize会自动调用initialize。类初始化器一般不需要自定义。
- 对象析构器dealloc:由系统自动释放ARC管理的资源,需要手动释放不受ARC管理的动态内存:如malloc分配的内存,以及非内存资源:如文件句柄,网络端口。
3.字符串NSString
(1)字符串NSString
初始化
字符串的初始化方法有:直接初始化,初始化器,工厂方法。例如:
//直接初始化
NSString *str1 = @"Hello World!";
//初始化器
NSString *str2 = [[NSString alloc] initWith:"Hello World!" encoding:NSUTF8StringEncoding];
//工厂方法
NSString *str3 = [NSString stringWithCString:"Hello World" encoding:NSUTF8StringEncoding];
常量性
字符串NSString恒定,不能更改。例如:
[str1 stringByAppendingString:@"Yes!"];
//错误,str依然是"Hello World!"
如果要更改指针所指向的内容,需要重新返回新值,如下:
str1 = [str1 stringByAppendingString:@"Yes!"];
//此时,指针str指向新的内容"Hello World!Yes!"
共享机制
NSString有共享机制,直接初始化的字符串,值相等的对象指向同一地址。
值相等和地址相等
if([str1 isEqualToString:str2])
//判断str1和str2值是否相等
if(str1==str2)
//判断str1和str2指向地址是否相等
NSMutableString
NSMutableString是NSString的子类,具有可变性,不具有共享机制。例如:
NSMutableString *mustr1 = [NSMutableString stringWithString:@"Hello World!"];
[mustr1 appendString:@"Mutable!"];
//正确,此时mustr1为"Hello World!Mutable!"
3.集合Collection
集合类型主要有三种:NSArray、NSSet、NSDictionary,都具有常量性,它们都有可变子类:NSMutableArray、NSMutableSet、NSMutableDictionary。主要介绍数组NSArray,其余两种类型的使用基本类似。
NSArray
初始化
NSArray是一个类,初始化同样有三种方法:直接初始化,初始化器,工厂方法。例如:
NSArray *a1 = @[@"Shanghai",@"Beijing",@"New York",@"Paris"];
NSArray *a2 = [[NSArray alloc] initWithObjects:@"Shanghai",@"Beijing",@"New York",@"Paris",nil];
NSArray *a3 = [NSArray arrayWithObjects:@"Shanghai",@"Beijing",@"New York",@"Paris",nil];
其中使用初始化器和工厂方法初始化时必须用nil进行结束,nil不算在数组内。数组内元素必须是对象objects,即NSObjects的子类对象。如果是非对象类型,必须封装为对象。例如:
//基本数值类型变量,非对象
NSInteger number = 100;
//使用NSNumber封装为对象
NSNumber *numberObject1 = [NSNumber numberWithInteger: number];
//使用@初始化数值对象
NSNumber *numberObject2 = @300u;
Point point; //结构类型
point.h = 100;
point.v = 200;
//使用NSValue封装为对象
NSValue *pointObject = [NSValue value:&point withObjectType:@encode(Point)];
NSArray a4 = @[numberObject1,numberObject2,pointObject ];
常量性
NSArray具有常量性,不能更改元素长度和指针,但是指针指向的内容可以更改。根据常量性可知,对数组进行排序,要将排序结果赋给新NSArray。
遍历方法
数组的遍历有三种方式:快速枚举、迭代器模式、for循环。其中快速枚举方式速度快,最常用。
补充一个很重要的集合遍历方式:通过方法
[arr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop)
BLNPoint *p1 = [[BLNPoint alloc] initWithX:10 WithY:20];
BLNPoint *p2 = [[BLNPoint alloc] initWithX:20 WithY:40];
BLNPoint *p3 = [[BLNPoint alloc] initWithX:30 WithY:60];
BLNPoint *p4 = [[BLNPoint alloc] initWithX:40 WithY:80];
NSArray *a5 = @[p1,p2,p3,p4];
//快速枚举
for(BLNPoint *point in a5){};
//迭代器模式
NSEnumerator *enumerator = [a5 objectEnumerator];
BLNPoint *item;
while(item = [enumerator nextObject]){};
//for循环
for(int i=0;i<4;i++){};
数组查找
BLNPoint *target = [[BLNPoint alloc] initWithX:20 WithY:40];
NSUInteger index1 = [a5 indexOfObject:target];
NSUInteger index2 = [a5 indexOfObjectIdenticalTo:p3];