文章目录
第一部分 类和对象
1.1 基础知识点
1.1.1 输入输出
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
int a,b,result;
a = rand() % 10;
b = rand() % 10;
NSLog(@"The sum of %d and %d is :",a,b);
scanf("%d",&result);
if(result == a + b){
NSLog(@"TRUE");
}
else{
NSLog(@"FALSE");
}
}
return 0;
}
1.1.2 字符串
NSString *str = @"Hello world!";
NSLog(@"%@",str);
//组合C语言里的变量
NSString *str1 = [[NSString alloc] initWithFormat:@"%d + %d = %d",3,5,8];
NSLog(@"%@",str1);
[str1 release];
如果这里的release报错,就把这个地方的YES改为NO
1.1.3 老版本自动池的创建和释放
//以前版本自动池的创建
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
NSString *name = @"Tom";
NSLog(@"My name is %@",name);//输出NSString类型
int age = 20;
NSLog(@"My age is %d",age);
BOOL isAdult =age > 18 ? YES:NO;
if(isAdult == YES)
NSLog(@"I am an adult!");
else
NSLog(@"I am under age!");
//池的释放
[pool drain];
1.1.4 结构体
struct rectangle{
int width;
int high;
};
struct rectangle rec = {24,16};
int area;
area = rec.width * rec.high;
NSLog(@"长方形的面积为:%d",area);
1.1.5 OC方法的签名
方法签名由多部分组成,每一部分签名都说明参数含义,所以方法的签名具有自说明性,例如:
-(void)setName:(NSString*)aName age:(int)aAge
方法签名为setName:age:
说明这个方法是用来设置姓名和年龄的,具有两个参数
- 方法的第一个参数必须以:开头,
:是签名的一部分
- 方法的第二部分签名age可以省略,但:不可以省略,省略后方法的签名为
setName::
-(void)setName:(NSString*)aName :(int)aAge
- 如果无参,则省略冒号
1.1.6 方法不能重载
在同一个类的方法不能重载,即方法的签名不能完全一样,但类方法和实例方法签名可以相同。
- 方法的签名和参数类型,参数名称无关
- 方法签名和方法的返回值类型无关
-(int) g:(int) x;
-(int) g:(float) x;//Error
-(int) g:(int) x:(int) y;//OK
-(int) g:(int) x:(float) y;//Error
-(int) g:(int) x andY:(int)y;//OK
-(int) g:(int) x andY:(float) y;//Error
-(int) g:(int) x andAlsoY:(int)y;//OK
1.2 类
1.2.1 类的定义
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface ASStudent : NSObject{
@protected
NSString * name;
@private
NSString *sid;
@public
unsigned int age;
}
//"+"号为类方法,可以直接用类名来调用,作用是创建一个实例
+(void)print;
//"-"号为实例方法,类的实例才可以调用
-(NSString*)name;
-(NSString*)setName:(NSString*)aName;
-(int)age;
-(void)setAge:(int)aAge;
@end
NS_ASSUME_NONNULL_END
1.2.2 类的实现
#import "ASStudent.h"
@implementation ASStudent
-(void)setName:(NSString*)aName{
name = aName;
}
-(NSString*)name{
return name;
}
-(int)age{
return age;
}
-(void)setAge:(int)aAge{
age = aAge;
}
+(void)print{
NSLog(@"ok");
}
@end
这里有两个地方是需要注意⚠️
实例方法可以直接引用类的实例变量和其他实例方法
类的方法都是public的,没有protected和private方法,但是如果一个方法只出现在类的视线里,没有出现在类的声明里,那么这个方法就是私有的。
1.2.3 类的实例化
ASStudent* student = [[ASStudent alloc] init];
student->age =20;
[student setName:@"Tom"];
NSLog(@"%@",[student name]);
对象是类的一个实例,类的实例化的过程就是对象被创建的过程
类的实例化分为两步:
第一步:首先分配实例所占有的内存空间(allocate)
+(id)alloc方法分配内存
ASStudent * zhang = [ASStudent alloc];
- alloc在堆区分配实例的内存空间,返回内存空间的首地址
- alloc将实例变量(除了isa)的值默认初始化为0
- alloc方法继承自NSObject,子类一般不需要重载
- alloc是类方法,只能通过类名调用
- 所有的对象都在堆区创建,在栈区创建对象是语法错误
//栈区会报错,不能够静态分配
ASStudent wen;
第二步:对实例的每一个实例变量进行初始化(initialize)
调用alloc分配完内存后还需要使用init方法将实例变量初始化为有意义的值
zhang = [zhang init];
一般把内存分配和初始化合在一起采用链式表达式
ASStudent * wang = [[ASStudent alloc]init];
#import <Foundation/Foundation.h>
#import "ASStudent.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
ASStudent * zhang = [ASStudent alloc];
zhang = [zhang init];
[zhang setName:@"张三"];
[zhang setAge:18];
NSLog(@"%@ %u",[zhang name],[zhang age]);
[zhang release];
}
return 0;
}
⚠️:需要注意
1、 id li = [ASStudent alloc];
- alloc是个类方法,通过类名调用方法
- 返回的是一个id类型 +(id)alloc;给对象分配地址空间
- id可以转换成任何其他类型的指针,是一个通用类型对象指针
- 通过指针访问堆区对象,(对象就是指对象的指针)
- id是通用对象指针类型
2、id pid = nil;
ASStudent* jiang;
[jiang setName:@"江"];
- 在上述两行代码中,按道理来说是野指针,但是不报错呀,有些奇怪,后来查了资料,是因为在oc中,居然可以默认为0
- 想复制为java里的NULL试试,发现是错误的呀,通过代码提示,原来这里面可以设置为nil
- nil只能被应用在可以使用id类型的地方,就是Java与C++中指向对象的指针。而NULL用于值类型的指针
- nil和NULL的区别就在于,nil是一个对象,NULL只是一个值。而且对于nil调用方法,不会产生crash或者抛出异常
id pid = nil;//ok
int *plnt = NULL;//ok
pid = NULL;//error
1.2.4 消息的传递
oc面向对象最大的特色貌似就是消息传递(message passing)模型。
目前还是很不习惯这个呢,因为对象不调用方法,而是互相传递消息。
在oc中,如果让对象完成某事,可以向对象发送相应的消息告诉对象执行某个方法
消息语法:
[receiver message]
消息和方法的调用对应
[zhang setName:@"张三"];//向zhang对象发送setName
消息设置名字为“张三”
1.3 对象的初始化
初始化方法的实现
- 自定义初始化方法需要先调用父类的初始化方法,初始化继承的父类的数据成员
- 如果父类的初始化方法执行失败,则直接返回nil,确认父类的初始化函数执行成功后,初始化本类新增的实例变量
- 最后返回self给方法的调用者
-(id) init{
if(self = [super init]){
width = 20;
high = 16;
}
return self;
}
- C++中构造函数可以有多个,oc中的初始化方法可以有多个
一般初始化方法都以init开头
-(id) init;
-(id) initWithName:(NSString*)aName;
-(id)initWithID:(NSString*)aID name:(NSString*)aName;
- 参数少的初始化函数调用指定初始化方法实现初始化
指定初始化方法
- 一个类可以多个初始化方法,但只能有一个指定初始化(designated initializer)方法。
- 指定初始化方法是可以初始化每一个实例变量的方法,其它的自定义初始化犯法都通过调用指定初始化方法进行实例变量的初始化。
- 一般有最多的函数或做最多工作的初始化方法
初始化方法如下:
-(id)initWithSID:(NSString*)asid name:(NSString*)aName age:(unsigned int)aAge{
if(self =[super init]){
[self setName:aName];
name = aName;
[self setSid:asid];
age = aAge;
}
return self;
}
-(id)initWithName:(NSString*)aName
{
if(self = [super init]){
name = aName;
}
return self;
}
初始化如下:
ASStudent* zhao = [[ASStudent alloc]init];
ASStudent* wang = [[ASStudent alloc]initWithSID:@"101" name:@"王五" age:18];
ASStudent* jian = [[ASStudent alloc]initWithName:@"tom"];
1.4 存取器方法
得到实例变量值的方法getter方法
设置实例变量值的方法setter方法
和java里的一样嘛,找了半天没找到快捷生成getter,setter方法,查了资料原来是使用属性声明机制才可以自动生成,下个小节再使用这个方法试试。
-(void)setName:(NSString*)aName{
name = aName;
}
-(NSString*)name{
return name;
}
-(int)age{
return age;
}
-(void)setAge:(int)aAge{
age = aAge;
}
有了存取器方法,可以使用点语法,两种方式等价
[zhang setAge 18];
zhang.age = 18;
也可以直接输出
NSLog(@"%@ %u",Zhang.name,zhang.age);
点语法针对的是存取方法,只要写了对应的存储器方法,就可以使用点语法
点语法是语法“糖”,实际上还是调用相应的存储器方法
//设置方法如下,两个等价
zhang.name= @"张三";
[zhang setName:@"张三"];
//读取方法如下,两个等价
NSLog(@%d),zhang.age);
NSLog(@"%d",[zhang age]);
1.5 属性
- 如果类中存在大量实例变量,需要提供用户大量setter和getter方法,java是直接可以生成,但是oc中要添加属性声明机制才可以自动生成。这个机制又被称为合成存取器方法来自动生成对应的getter和setter方法
- 属性声明
@property(attributes)数据类型 实例变量; - 存取器方法实现
@synthesize 实例变量1,实例变量2,······实例变量n
属性的声明
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Rectangle : NSObject
{
NSString *width;
int heigh;
}
@property(nonatomic,retain)NSString *width;
@property(nonatomic,assign)int heigh;
@end
NS_ASSUME_NONNULL_END
属性的实现
#import "Rectangle.h"
@implementation Rectangle
@synthesize width,heigh;
@end
- 属性有多组,在@property后的小括号里列出,会影响生成的存取器方法,每一组内的属性是互斥的。
- 线程相关:1、原子性 atomic 2、非原子性 nonatomic
- 读/写
(1)只读 readonly 只产生getter方法,不生成setter方法
(2)读写 readwrite(默认)
属性也有其他用法呀
指定属性的存取器方法的名字,不用默认的存取器方法名
试了试可以是这样
@property(getter = isMarried) BOOL marry
经测试,不要实例变量只声明属性也是可以的,是个小惊喜哦😯
import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Rectangle : NSObject
//{
// NSString *width;
// int heigh;
//}
@property(nonatomic,retain)NSString *width;
@property(nonatomic,assign)int heigh;
@end
同样,以前的好习惯要延续过来呀,为了实例变量的安全,属性实现的时候不和实例变量同名,可以这样声明属性。方法里使用_age来引用实例变量,也就是self.age
如果是类外只能用object.age
不同的语言,思想还是相通的嘛,这就很开心了。
@property int age;
@synthesize age = _age;
写到这,感觉oc里的self和java里的this有点像啊
简单来说就是,谁调用self就代表谁。都是代表当前方法的一个调用者。
因为想要确认下这两个有没有区别,分别写了一个静态方法和动态方法来测试。
果然呢,java里的this只能用于动态不能用在静态方法中。这个是之前忽视的一个地方了吧,也没有很深入的研究过java的this,this一般就用在setter函数里,现在发现如果放在静态方法里一用一个错啊!!
然而然而,放在oc里我就很惊喜啦,动态静态方法都可以。只不过区别在于,动态中方法中,self代表对象。静态方法代表类。
1.6 总结
将自己了解到的一些知识点,转化为自己的理解记录了下来。
截止目前,大体感觉如下吧:
1、oc其实应该算是C语言的扩充
2、具备完善的面向对象特征,封装继承和多态
3、大致了解了iOS支持两种内存管理方式,ARC和MRC,还没太搞明白,后面再研究研究
4、核心思想其实也是类和对象呀
5、“:”是标识参数,不能省略,有冒号必须要有参数,冒号也是方法名的一部分
6、+是类方法,-是实例方法
7、在OC中使用消息发送机制:[receiver message]
①给received对象发送message消息
②received接收到消息,即方法message
③teacher找到message方法,并执行。
8、只要有封装,就一定逃不过setter设置器和getter访问器
第二部分 内存管理规则
内存啊,超级重要重要的一个东西呢
内存管理,就是管理程序运行时进行内存分配,使用内存,结束时释放内存。
要是写一个可糟糕的程序,占用很大内存,我想,是个失败的开发者。一个手机就那么大点内存,超出限额就要崩溃!
在oc里也如此啦。要尽量尽量写一个棒一点的程序,尽量少占点内存。依然还是,需要的时候分配,不使用就回收♻️。
2.1 内存管理概述
经了解得知,oc里有三种内存管理机制
- 引用计数
- ARC
- 垃圾回收
悄悄给一个对象写了两个release,果然很爽快的崩溃。
我想着试试这种写法吧,给自己挖了个大坑出来!!!
NSLog(@"zhang:%lu",zhang.retainCount);
[zhang retain];
NSLog(@"zhang:%lu",zhang.retainCount);
[zhang release];
第一次输出了1
第二次输出了2
只有为0才能释放掉呀,如果写了很多这种代码,我觉得程序要炸啦
2.1.1 对象所有权
找了一个比较官方的说法:
oc的内存管理模型,是基于对象的所有权,在oc中如果一个实体拥有一个对象,那么这个实体对这个对象有所有权。所有权就意味着该实体确保对其拥有的对象进行清理。
有权利就有义务!!
这个说法太官方来,我的理解就是,任何一个对象都可以有一个或者更多的“所有者”(owner),当一个对象有至少一个所有者的时候,这个对象就是存在的。如果没有所有者啦,那就没有用啦,系统就会析构它。
在oc中,所有权管理规则:
- 当使用alloc、new、copy、mutableCopy等方法创建对象的时候,就会拥有对象的所有权,但是目前我只用过alloc。。
- 可以用retain来实现一个对象的所有在oc里所有的对象都是在堆区里创建的,要自己释放对象所占用的内存。
让我想到了java里的java GC内存管理,一般不会专门去写内存回收和垃圾清理代码,对内存泄露和溢出的问题,好像不怎么去重视,感觉java GC挺完善的。
2.1.2 引用计数
所有权的策略是通过引用计数来实现的,通常称之为"retain count",每个对象都有一个retainCount
- 创建一个对象,引用计数为1
- 发送retain消息给一个对象时,引用数+1
- 发送release消息给一个对象时,引用数-1
- autorelease:如果对象不能立即释放,可以向对象发送sutorelease消息,使对象的引用计数在未来的某个时候减1,并且在那个时候放弃对象的所有权
- 如果引用计数为1,系统就会向对象发送一条dealloc消息,然后释放对象的内存
是0就回收啦
ASStudent * zhang = [[ASStudent alloc]init];
//ASStudent * zhang = [ASStudent new];
zhang.name = @"Tom";
NSLog(@"zhang:%lu",zhang.retainCount);
[zhang release];//成功回收
2.1.3 内存管理相关方法
- -(id) retain;
对象引用计数增1,返回对象本身 - -(void)release;
对象引用计数减1 - -(unsigned) retainCount;
不改变引用计数,返沪对象当前的引用计数的值 - -(void)dealloc;
这个方法在对象引用计数为0的情况下,当内存回收的时候会由系统自动调用
ASStudent * zhang = [[ASStudent alloc]init];
NSLog(@"zhang:%lu",zhang.retainCount);
[zhang retain];
NSLog(@"zhang:%lu",zhang.retainCount);
[zhang release];
NSLog(@"zhang:%lu",zhang.retainCount);
[zhang release];
2.1.4 内存管理规则-平衡法则
alloc、new、copy、retain
与
release、autorelease
要成对儿出现
2.2 自动释放池
自动释放池机制其实就是一个“延时”释放对象的机制。也就是想放弃对象所有权,又不想立即放弃,可真纠结呀。这个时候可以向对象发送一个autorelease消息,将对象加入到自动释放池。
- -(id) autorelease;将对象加入自动释放池,返回对象本身
- 当你向对象发送autorelease消息时,已经失去了对象的所有权,所以就不用对它负责了。也就是不用手动释放啦
- 自动释放池得到了对象的所有权,也就是当池子释放的时候,会自动向池子里的每一个对象都发送release消息,这个地方释放的不是对象,只是发个消息。
- 除了静态对象,其他对象在作用域内释放对象可以使用release,如果在作用域内不想马上释放,则使用autorelease释放。
- 现在都是自动生成的释放池,老版的也研究了一下,感觉老版更容易看出来本质,在1.1.3小节。
ASStudent * zhang = [[ASStudent alloc]init];
NSLog(@"zhang:%lu",zhang.retainCount);
[zhang autorelease];
NSLog(@"zhang:%lu",zhang.retainCount);
2.3 引用计数和存取器
仍需研究其原理
2.4 常见的内存错误
日后总结吧
第三部分 继承
3.1 继承的基本概念
简单写了两个类:
第一个,图的类,属性为原点(包含getter、setter),和一个初始化原点的方法
@interface ASGaphic : NSObject{
NSPoint origin;
}
@property(nonatomic) NSPoint origin;
-(id) initWithOrigin:(NSPoint)aPoint;
@end
第二个写了一个形状类,属性为原点和颜色包含getter、setter),和一个方法
@interface ASshape : NSObject{
NSPoint origin;
NSString * primaryColor;
}
@property(nonatomic,assign)NSPoint origin;
@property(nonatomic,retain)NSString* primaryColor;
-(id)initWithOrigin:(NSPoint)aPoint color:(NSString*)aColor;
@end
两个类中的origin属性、getter、setter方法是一样的
可以按照继承父类的实例变量和方法来写,实现代码复用
如下:
#import "ASGaphic.h"
#import "ASGaphic.h"
NS_ASSUME_NONNULL_BEGIN
@interface ASshape : NSObject{
NSString * primaryColor;
}
@property(nonatomic,retain)NSString* primaryColor;
-(id)initWithOrigin:(NSPoint)aPoint color:(NSString*)aColor;
@end