前言
本文阅读耗时15分钟,主要是介绍OC的一些语法,类如何写以及内存管理。不懂OC的看完也能看懂实际含义了。
文章目的方向其实还是研究Rust在端上的应用,下一篇会记录Swift的语法,再是IOS开发,Rust 在IOS上的表现。
目录
一、OC概述
Next
(Next
也是上任苹果计算机CEO
,Steve Jobs
在1985
年离开苹果计算机后所创立的公司)研发的一种操作系统NextStep
。
后来,在1996
年,苹果公司出于战略考虑收购了Next
公司,自然乔帮主又回到了苹果公司。基于NextStep code library
,苹果公司把它集成到了自己的系统中去,也就是现在的MAC OS X
,可以说NextStep
为苹果mac
系统的发展奠定了基础。
NextStep
核心code
就是Object-C
写的, 所以你能看到OC
中命名都是NS
开头。
那OC
和C
有啥区别?
OC
基于C
,新增了一小部分面向对象语法- 将
C
中复杂的语法封装的更简单 OC
里面直接写C
语言,也是没有问题的OC
程序的源文件后缀名是.m
,C
程序是.c
main
函数依旧是两者的入口和出口OC
中#import
类似C
语言的#include
,但是#import
只会导入一次头文件,再也不用像C
一样,要对导头文件进行处理
二、OC的一些基本语法
OC编译
cc -c xxx.m //编译源文件得到.o文件
cc xxx.o // 或者cc xxx.o -framework(框架名称) 和c一样,生成.out文件,执行即可运行
OC数据类型
该有的都有,如基本类型(int
、double
、float
、char
)、构造类型(数组、结构体、枚举)、指针类型、空类型(void)
、typedef
自定义类型、BOOL
类型、Boolean
类型、class
类型、id
类型、指针、nil
、SEL
、block
代码段
typedef
将长类型定义为短类型
typedef unsigned long long int bigdata;
bigdata test = 10;
BOOL、Boolean类型
BOOL b = YES;
BOOL c = NO;
Boolean d = true;
具体的定义如下
#define YES ((BOOL)1)
#define NO ((BOOL)0)
typedef unsigned char Boolean
#define true 1
#define false 0
所以YES
就是1
,Boolean
其实就是 char
class
说到class
,就要体OC
中的类以什么形式存储在代码段中了
任何存储在内存中的数据都有一个数据类型
先在代码段中创建一个Class
对象,Class
是Foundation
框架的1
个类。将类的信息存储在这个Class
对象之中。
包含基本的类名、属性、方法、isa
。这个Class
对象,我们称为类对象。
如何拿到类对象?
Class c1 = [Person class];
或者
Person *p1 = [Person new];
Class c2 = [p1 class];
id
Animal *obj = [Animal new];
[obj sayHi];
id id2 = [Animal new];//id是 typedef自定义类型
[id2 sayHi];
nil和NULL
NULL
就是0
, nil
只能作为指针变量的值,代表指针变量不指向内存的任何空间。((void *)0)
也等价于0
。
一般C
使用NULL
,OC
使用nil
,并且在OC
中,Person *p1 = nil
, 通过p
1访问属性会报错,但是访问方法不会报错,就是不会执行而已。
SEL
这个主要是用来找这个类中是否有这个方法,方法的本质就是selector
选择器,主要通过selector
来查找。
SEL s1 = @selector(syaHi); //拿到SEL对象
block
block
是一个数据类型,专门存储一段代码,这段代码可以有参数和返回值
返回类型(^block变量名称)(参数列表);
1.声明
void (^myBlock1)();
int (^myBlock2)(int num1,int num2);
代码段格式
^返回值类型(参数列表){
};
2.实现
myBlock1 = ^void(){
NSLog(@"hello");
};
和变量一样定义就行
myBlock1();//执行
3.简单的定义和使用
int (^myBlock3)(int a, int b) = ^int(int a, int b){
return a + b;
};
int result = myBlock3(2, 4);
4.block简写
4.1 代码段没有返回值可以省略void,没有参数可以省略()
void (^block)() = ^{};
代码段没有参数
int (^block2)() = ^int{
return 1;
};
4.2声明block的时候,参数可以不写名字
int (^block3)(int, int) = ^int(int a, int b){
return 1;
};
4.3无论代码段是否有返回值,都可以不写返回值
int (^block3)(int, int) = ^(int a, int b){
return 1;
};
static
C
语言中的static
可以修饰局部变量,全局变量、函数
OC
中,static不能修饰属性和方法,可以修饰方法中的局部变量(存储在常量区,不会回收,下次执行这个方法的时候会直接使用,不会声明)
这里可以看看后面类的单例章节怎么写的。因为它存储在常量区,不会被回收,所以下次的时候直接用。
异常
OC
的异常处理不是万能的,C
语言的异常是无法处理,以及一些nil
对象的属性调用也无法处理。
@try{
}
@catch(NSException *ex){
NSLog(@"catch的错误是 %@", ex);
}@finally{
}
三、类与方法
类与方法实现
@interface Person:NSObject
{
NString *_name;
@public
int _age;
}
- (void) run; //对象方法
-(void)run:(int)name;
- (int)sum:(int)num1 :(int)num2;
+ (instancetype)person;//类方法,就比如java的静态方法
@end
//类的实现
@implementation Person
- (void) run{
}
- (void) run:(int)name{
}
- (int)sum:(int)num1 :(int)num2
{
return num1 + num2;
}
+ (instancetype)person
{
return [self new];
}
@end
//调用
Person *p1 = [Person new];
[p1 run];//方法调用
[p1 run:@"one-thread"];//有参数的方法调用
p1-> _age;//方法属性,_name是私有的,访问不到
[p1 sum:1:2];
Person *p2 = [Person person];//类方法
匿名对象
[[Person new] run]; //和其他语言一样
修饰符
修饰符有四种类型,分别是@private
、@protected
、@public
、 @package
(当前框架中访问)。 默认是私有的,且修饰符只能修饰属性。
下面两个属性a
、b
都是公有的,c
是私有的
@interface Person : NSObject{
@public
int a;
int b;
@private
int c;
}
@end
你肯定会问,既然只能修饰属性,那方法私有怎么处理啊?我们直接在类的声明中把方法声明去掉就可以了,这就代表只能本类中使用。
//类的声明
@interface Person:NSObject
@end
//类的实现
@implementation Person
-(void) run{
}
@end
可能你还会问,如何实现类的打印?
Person *p1 = [Person new];
NSLog(@"%@", p1); //这样打印出来的不是我们想要的对象中的属性数据
只需要将Person
实现NSObject
的description
方法即可,类似重写java
的toString
方法
- (NSString *)description
{
return [NSString stringWithFormat:@"姓名:%@ 年龄%d", _name, _age];
}
你可能又会问NSLog
是干嘛的?
NSLog
NSLog
是Foudation
框架里的,是printf
函数的增强版,会引入一些调试相关的信息,就像Android
的Log
一样,有时间、TAG
信息,并且在输出后会自动换行,针对新增的数据类型,需要使用NSLog
。
它与C
的字符串相比,OC
需要加前缀@
,NSString *str = @"jack";
包括字符串输出,NSLog(@"hello, %@", s);
它的定义是 @interface NSString:NSObject<NSCoping, NSMutableCoping,NSSecureCoding>
。所以本质上字符串是用NSString对象来存储的。也可以通过NSString *str =[NSString new]
,内容是@""
,或者 NSString *str1 = [NSString string];
常用的类方法有
(instancetype)stringWithUTF8String(const char *) //将c语言的字符串转换成OC字符串对象
(instancetype)stringWithFormat:(NSString *)format, ... //拼接OC字符串
了解了NSLog
,你可能会比较好奇,OC
中类是怎么存储的。
类以什么形式存储在代码段中
任何存储在内存中的数据都有一个数据类型
先在代码段中创建一个Class
对象,Class
是Foundation
框架的1
个类。将类的信息存储在这个Class
对象之中。
包含基本的类名、属性、方法、isa
。这个Class
对象,我们称为类对象。
Class c1 = [Person class];
或者
Person *p1 = [Person new];
Class c2 = [p1 class];
其实对象中isa
就是Class
对象地址。并且c1
c2
就代表Person
类,类方法完全可以通过c1
、c2
调用。
方法的本质
Person *p1 = [Person new];
[p1 sayHi];
先拿到sayHi
方法的SEL
对象。再将SEL
消息发送给p1
对象,p1
接收到SEL
消息后,就知道要调用方法。
然后根据对象的isa
指针找到存储类的类对象
找到类对象后,在这个类对象中去查找是否有和传入的SEL
数据匹配的,有就执行,没有就从父类去找,直到NSObject
。
set、get方法
我们会使用点语法来调用get
和set
方法
@interface Person: NSObject
{
NSString *_name;
}
- (void)setName(NSString *)name;
- (NSString *)name;
@end
@implementation Person
- (void) setName:(NSString *)name{
_name = name;
}
- (NSString *)name
{
return name;
}
@end
我们就可以使用点语法来调用,来读取属性了,它的本质就是调用get
、set
方法
Person *p = [Person new];
p.name = @"张三";
NSString * testName = p.name;
那有没有办法让代码自动生成get
、set
方法呢?
@property
@interface Person : NSObject
{
NSString *_name;
int _age;
int _sex;
}
- (void)setName:(NSString *)name;
- (NSString *)name;
@property int age;
@property int sex;//标记了,会对应有一个_sex属性
@end
@implementation Person
{
- (void)setName:(NSString *)name
{
_name = name;
}
- (NSString *)name
{
return name;
}
@synthesize age;//不用定义age也可以,它也会自动生成一个私有属性
@synthesize sex = _sex; //这样写没有私有的属性
}
@end
我们可以利用 @property(相当于对get、set方法的定义) + @synthesize(类视线中,对属性的get、set方法的实现)
还是太麻烦了,声明和实现中都要写一遍。
怎么办?XCode 4.4
后,只需要写一个@property
就会自动生成私有属性以及get、set
方法声明和实现了。
@interface Person:NSObject
@property NSString *name; //自动生成的属性是_name
@end
对象的其他创建方式
Person *p1 = [Person new];
等价于
Person *p2 = [[Person alloc]init];
类如何继承
@interface Father:NSObject
@end
@interface Son:Father
@end
之前了解过了类如何存储的,并且有一个isa
指针,那么子类也有的,子类的isa
指针指向了Son Class
代码段,Son Class
中也有一个isa
指针,指向了 Father Class
。
和其他语言一样,子类当中拥有父类的所有属性及方法。
协议
协议也可以理解为接口,专门声明一大堆方法的,不要认为是泛型,因为加了这个,子类要遵守协议,要实现协议里面的方法。
@protocol MyProtocol <NSObject>
//方法声明
-(void)run;
- (void)sleep; //想要子类一定要实现,可以强制编译器报警告,可以使用@required -(void) sleep;另一个@optional 可以不报警告
@end
@interface Dog:NSObject<MyProtocol>
@end
//不实现也不会报错
@implementation Dog
- (void)run{
}
- (void)sleep{
}
@end
单例
单例,顾名思义,只能有一个对象,在OC里也是一样的。
非安全写法
@implementation Person
+ (instancetype)allocWithZone(struct _NSZone *)zone
{
static id instance = nil;
if(instances == nil){
instance = [super allocWithZone:zone];
}
return instance;
}
+ (instancetype)defaultPerson{
return [self new];
}
+ (instancetype)sharedPerson{
return [self new];
}
@end
Person *p = [Person new];
Person *p1 = [Person defaultPerson];
安全写法
+ (instancetype)allocWithZone(struct _NSZone *)zone
{
static id instance = nil;
@synchronized(self){
if(instances == nil){
instance = [super allocWithZone:zone];
}
}
return instance;
}
推荐GCD安全写法
对象的代码在整个程序运行期间只被执行一次
+ (instancetype)sharedPerson()
{
static id instance = nil;
static dispatch_once_t onceToken;//声明一个静态的gcd单次任务
dispatch_once(&onceToken, ^{
if(instances == nil){
instance = [[Perosn alloc]init];
}
});
return instance;
}
也可以直接仿照系统[NSFileManager defaultManager]
单例实现。
四、内存管理
针对类的介绍,基本可以让你看懂OC代码了,但是其中的一些细节原理,还是要多写写代码。肯定要了解一下内存管理
内存的五大区域
- 栈: 局部变量,出了作用域后会被立马被系统回收
- 堆:
OC
对象,使用C函数申请的动态空间。也是不会自动回收,直到程序结束的时候才会回收 - BSS段:未初始化的全局变量和静态变量。一旦初始化就会回收,并转存到数据段之中。
- 数据段:已经初始化的全局变量、静态变量。直到程序结束才会被回收
- 代码段:代码。程序结束的时候,系统会自动回收存储在代码段中的数据。
引用计数
对象内部持有retainCount
属性,即引用计数 unsigned long
8
个字节,代表有多少人使用这个对象。这个引用计数器也是可以操作的,为对象发送retain
消息,引用计数加1
,为对象发送release
消息,引用计数减1
。当为0的时候,对象就会被系统立即回收,自动调用对象的dealloc
方法。为对象发送retainCount
消息,就可以获取到值。
MRC
Manual Reference Couting
手动内存管理,这个在XCode
上,可以打开关闭这个功能,现在基本都是ARC
了吧。ARC也是从XCode 4.2
后支持的。
内存管理原则
- 有对象的创建,就要匹配一个
release
retain
次数和release
要匹配
对象内存释放的时候
//给Person加个打印
@implementation Person
-(void)dealloc
{
NSLog(@"回收Person自己的内存方法重写");
[super dealloc];//这行一定要放在最后一行
}
@end
Person *p1 = [Person new];//创建的时候自己会reatin
[p1 release];//释放的时候,发现引用计数器为0了,就会调用dealloc
野指针
C
语言:定义一个指针变量,没有初始化,指向1
块随机的空间,这个就叫野指针。OC
: 指针指向的对象已经被回收了,这样的指针叫做野指针。
对象回收本质
申请一个变量,实际上就是向系统申请指定字节数量的空间。这些空间系统不再分配给别人了
当变量被回收时候,代表变量占用的字节空间从此以后系统就可以分配给别人使用了。
但是字节空间中存储的数据还在。
回收对象:所谓对象的回收,指的是对象占用的空间可以分配给别人。当这个对象没有分配给别人之前,其实对象数据还在。
僵尸对象
一个已经被释放的对象,但是这个对象所占的空间还没有分配给别人,这样的对象叫做僵尸对象。我们通过野指针去访问僵尸对象的时候,有可能没问题,有可能有问题,并且无法复活僵尸对象。
僵尸对象的实时检查机制,可以将这个机制打开(XCode
配置中),打开之后,只要访问的是僵尸对象,无论空间是否分配,就会报错。但是打开后,每访问一个对象的时候都会检查这个对象是否是1
个僵尸对象,非常消耗性能的。
内存泄漏
内存没有被及时的回收,主要表现在有对象创建而没有对象的release
。或者MRC
的时候,retain
次数和relase
次数不匹配。并且在没有调用release
的时候,直接赋值后,也会导致内存泄漏。
例如给Person
加个Car
- (void)setCar(Car *)car{
if(_car != car){//将之前的对象先release后,再reatin
[_car release];
_car = [car retain];
}
}
//重写dealloc
- (void)dealloc
{
[_car release];
[super delloc];
}
Person *p = [Person new];
Car *car = [Car new];
p.car = car;
[car release];
[p release];
针对上面示例代码的写法(属于标准的MRC
内存管理写法),@property
也为我们准备了自动实现
property
@interface Person : NSObject{
Car *_car;
}
@propperty car;
@end
上面这种写法默认的set方法就是直接将 _car = car;
,所以@property
提供了参数给我们实现标准的MRC
管理。它有四组参数,分别是
atomic、nonatomic
默认值atomic
,生成的set
方法会被加上线程安全锁,当然对应的右nonatomic
(不加锁)
@interface Person : NSObject
@property(nonatomic) Car * car;
@property(atomic) Car * car2;
@end
assign、retain
默认值assign
,就是直接赋值, retain
生成的实现,就符合标准的MRC
内存管理
@interface Person : NSObject
@property(nonatomic, retain) Car * car;
@end
//自己还要实现相关的销毁策略
@implementation Person
- (void)dealloc
{
[_car release];
}
@end
readonly、readwrite
默认值readwrite
,对应的get
、set
方法都会生成,readonly
就只会生成get
方法
@interface Person : NSObject
@property(nonatomic, retain, readonly) Car * car;
@end
get、set
默认情况下,生成的get
、set
方法的名字都是标准的。
@interface Person : NSObject
@property(nonatomic, retain, readonly, getter=xxx, setter=yyy:) Car * car; //对应的get方法的方法名就是xxx, setter方法名也是一样的。
@end
循环引用
当两个类相互包含的时候,就会出现循环引用,造成无限递归问题。
比如下面的Book
持有Person
,Person
持有Book
解决方法第一步,先避免编译失败问题,就是不要import.h
头文件,直接使用@class
定义。
//book.h 文件
//#import "person.h"
@interface Book : NSObject
@property(nonatomic,retain)Person *person;
@end
//person.h 文件
//#import "book.h" 在person.m实现中再引入 book.h,这样方法也能有提示
@class Book
@interface Person : NSObject
@property(nonatomic,retain)Book *book;
@end
import
是会将指定文件的内容拷贝到写指令的地方,而@class
并不会拷贝内容,只是告诉编译器这是一个类。
解决第二步就是,将其中一个改为assign
Person的dealloc方法
{
[_book relase];
[super dealloc];
}
Book的dealloc方法
{
[_person release];
[super dealloc];
}
//下面是都是retain的示例过程
Person *p = [Person new];
Book *book1 = [Book new];
//这时候book和 p 引用计数都是1
p.book = book1; // book1的引用计数变成了2
book1.person = p;//这时候p的引用计数也变成了2
[book1 release]; //book1 计数变成1
[p release]; // p的计数变成1
//最后对象都没销毁,都没有执行到dealloc方法
两个对象相互引用的时候,只要一端使用retain
,另一端使用assign
,就不会循环引用。
autorelase
存入到自动释放池中的对象,在自动释放池被销毁的时候,会自动调用存储在该自动释放池中的所有对象的release
方法。
注意每次对象只能加入一次自动释放池中。
@autoreleasepool{
Person *p = [Person new];
[p autorelease];//将p对象存储到当前的自动释放池中
Person *p1 = [[Person new] autorelease];//自动释放池中调用1个对象的autorelease多少次,就会将这个对象存储到自动释放池多少次。并发多少次的release消息
}
当然不想写autorelease
的话,可以为类,写一个同名的类方法,用来快速生成一个类对象,在类方法中实现autorelase
,则通过类方法创建的对象自动会被加入到autorelease
。
ARC
Automatic Reference Couting
自动内存管理,系统自动的在合适的地方发送retain
和release
消息。
本质也是引用计数器为0的时候,自动释放。对于我们开发而言,只要没有强指针指向这个对象,这个对象就会立即回收。
在ARC
下,不要写retain
、release
、autorelease
,以及手动调用dealloc
函数。但是有时候避免不了,在维护项目的时候,会发现有些类的编译模式是MRC
机制,一般就会造成MRC
和ARC
共存,在开发的时候需要看清楚了。
强、弱对象
Person *p1;这就是强类型指针
__strong Person *p1;显示标记这是强类型指针
__weak Person *p2;显示标记这个是弱指针
举个例子
int main(int argc, const char * argv[]){
__waak Person *p2;
{
__strong Person *p1 = [Person new];
p2 = p1;
}//这里就释放了,出了作用域就会被释放
//也会被回收,因为p2是弱指针
}
如果我们想针对属性指定强弱类型,可以这样做
@property(nonatomic,) Car *car;
等价于
@property(nonatomic, strong)Car *car;
弱类型可以这样写
@property(nonatomic, weak)Car *car;
五、分类
看到这样的代码也不要惊讶,例如下面的,Student
是类名,company
是分类名。通过XCode
可以创建。它肯定还写了一个Student
类在其他定义了。可以找找看。
分类相当于所属模块的意思,将一个类分成多个模块。但是记得不要在分类中使用@property
属性,因为它不会自动生成实现。而且分类之间属性只能通过get、set
方法来相互访问。针对分类和本类同名方法,会优先调用分类的方法。如果多个分类有同名方法,会优先调用最后编译的分类中的方法。
并且分类中只能新增方法,不能加属性。
@interface Student(company)
@property(nonatomic, assign)int age;//只会生成属性的get 、set声明
@end
@implementation Student(company)
@end
延展
延展这个特殊的分类没有名字。只有声明没有实现。和本类共享一个实现。
@interface 本类名()
@end
通过XCode
创建的文件名是 : 本类名_文件名.h
作用:相当于java
的接口,属性和方法都可以添加,可以用来写私有方法和属性
一般写在.m
文件里,和实现类放在一起。但是延展可以用@property
,它会在本类生成set
实现。
例如
//本类
@interface Person :NSObject
@end
//延展
@interface Person()
@property(nonatomic,assign)float weight; //延展会在本类的实现中生成
- (void)run
- (void)sleep();
@end
@implementation Person
- (void)run
{}
- (void)sleep
{}
@end
Person *p1 = [Person new];
[p1 run];
[p1 sleep];
如果要实现私有属性,外部访问不了,可以这样实现
//放到一个文件内
@interface Person()
@property(nonatomic,assign)float weight;
- (void)run
- (void)sleep();
@end
@implementation Person
- (void)run
{}
- (void)sleep
{}
@end
六、常见的一些集合
NSMutableString
对象来存储字符串,内容可以修改
NSMutableString *str = [NSMUtableString string];
[str appendString:@"jack"];
[str appendString:@"rose"];
[str appendFormat:@"&d岁", age];
copy
NSString copy 浅拷贝,引用计数器加1, mutableCopy是可变对象,深拷贝
NSMutableString copy 新对象,深拷贝,但是copy后是一个不可变的字符串NSString类型, mutableCopy也是深拷贝,也是可变对象
针对NSMutableString等可变的对象属性,我们可以利用@property(copy) 自动会在set中调用copy函数
针对自定义类没有copy方法,我们就需要实现NSCopying的copy方法
NSArray、NSMutableArray
NSArray * arr = [NSArray new];
NSArray *arr1 = [[NSArray alloc] init];
NSArray *arr2 = [NSArray arrayWithObject:@"jack"];
NSArray *arr3 = @[@"jack",@"data"];
子类 NSMutableArray,元素可以动态增加和删除
NSMutableArray *arr4 = @[@10,@20,@30]; //@10其实就是NSNumber
NSDictionary与NSMutableDictionary,例如
NSDictionary *dict = @{@"name":@"rose", @"age":@"18"};
使用@[] 或者@{} 创建的集合已经是被atuorelease的了
直接调用和类同名的类方法创建的对象也是被autorelease的了
NSDictionary、NSMutableDictionary
这个和Map
一样,一个是不可变的,一个是可变的。