Objective_C
参考链接
https://blog.csdn.net/qq_33750826/article/details/54948514
https://www.jianshu.com/p/a1427fea4379
https://blog.csdn.net/weixin_39624536/article/details/90053954
https://blog.csdn.net/weixin_39624536/article/details/84454256
https://www.jianshu.com/p/6f39771de2de
https://www.cnblogs.com/echo-imax/p/4576757.html
参考书籍
《Objective_C编程全解》
《Objective_C基础教程》
内容
【Objective_C总体描述】
就是对C语言进行了一定封装,有了自己的特性。
【适合人群】
有一定C语言或者Java基础的同学。
【尚未涉及的知识点】
按照大模块尚有:
- 抽象类和类簇
- 消息发送模式
- 应用的构造
- 并行编程
- 异常和错误
- 键值编码
一、#import
**【知识初析】**引入头文件的一个语法。
【知识理解】就是说在c++语言里面a文件被b文件和c文件同时引用,然后b文件又引用了c文件。这个情况会出现重复引用的问题了。oc里面使用#import解决这个问题。在c++里面是通过#ifdef解决的。但是注意重复嵌套的情况。
**【知识联系】**c语言里面的引入头文件的弊端;引入头文件的用处;c语言里面的预编译;
**【知识总结】**替代了c语言里面引入文件的#include
二、关于NSLog 、 @“字符串”、NS前缀、NSString
1.NSLog
【知识初析】 NSLog是一个用来输出字符到控制台的函数。
【知识理解】NS前缀使用来告诉函数是来自Cococa (使用#import<Foundation/Foundation.h> 可以引入此框架)。带NS的前缀都是指明这个东西来自cococa。好处就是会自动换行
**【知识联系】**c语言的printf函数。
**【知识总结】**替代了c语言里面的printf,能自动换行。
2.@“字符串”
**【知识初析】**加载字符串前面的一个标识符
【知识理解】@"字符串"的作用使用来告诉这个字符串应该是被看作NSString处理。****
**【知识联系】**oc里面特有的NSString
NSLog(@"Hello, objective_C");
3. NSString
**【知识初析】**约等于JAVA里面 的 String 数据类型。
**【知识理解】**在OC中的理解就是可以使用NSString去保存一个字符串数组的地址(指针指向这个字符串的地址)。
**【知识联系】**JAVA里面的String类型;c的指针;
【知识总结】
? 1.OC字符串必须以@开头
? 2.@必须写在”“前面
? 3.在OC中打印字符串使用%@
? 4 .NSString只能储存字符串数据地址
三、在OC中调用C代码
**【知识初析】**可以在OC语言中任意调用C书写的代码
【知识理解】OC其实就是对C语言一些语法的封装,是完全兼容C语言的。
**【知识联系】**通过一个demo演示体验OC对C的兼容性
1.创建一个Show.c
#include "Show.h"
void showTest(){
printf("我是C语言写的,何人敢调用我啊\n");
}
2.Show.h里面声明方法
#ifndef Show_h
#define Show_h
#include <stdio.h>
extern void showTest();
#endif
3.在OC里面引进show.h
4.调用showTest();
四、OC中的布尔类型
**【知识初析】**语言中用来表示true 或 false的数据类型。
**【知识理解】**c语言中是使用int类型去表示真与假,OC里面BOOL和Boolean去表示真假。底层实质上 就是 c语言 typedef unsigned char BOOL 。它,具有两个数值YES/NO,表示0/1。 #define YES = 1 #define NO = 0;
【知识联系】#define;#typedef;布尔数值;通过下面代码感受一下OC的布尔值
五、Objective_C中方法的声明
【知识关键】
掌握如何在OC中声明一个方法。
【知识分析】
+/-(返回值) 方法名 : (方法参数) 参数名 ;。其实总体逻辑一样都涉及到3个要素。返回值,方法名,方法参数。其中**+/-**代表的是静态函数还是一个普通函数。通过一个demo来体验一下声明方法。
【知识联系】
函数的3要素;
六、类的声明和实现
【知识关键】
掌握OC里面声明和实现类的套路。
【知识分析】
通过demo来体验声明和实现的流程。
1.下图是一个.h文件,用于类的声明。.h文件为类的声明文件,用于声明成员变量、方法。类的声明使用关键字@interface和@end ,.h中的方法只是做一个声明,并不对方法进行实现,也就是说,只是说明一个方法名,方法的返回值类型、方法接受的参数类型而已,并不会编写方法内部的代码。
2.使用一个{…}来声明成员变量。@public代表全局都可以访问,@protected代表只能在内部和子类中访问。@private代表只能在内部访问。成员变量必须写在{}里面,方法必须写在{}的外面
3.方法中的声明和实现都必须使用+、-。
4.下图是一个.m文件。用于实现.h中里面声明的方法。声明的方法都必要要实现,无论是使用与否。
以上我们可以看到: 1、类的声明:@interface 类名:父类类名(NSObject是所有类的父类) 2、类的实现:@implementaion 需要实现的类名
【知识联系】
【知识总结】
- 联想之后约等于java里面创建了一个类,但是此时阶段是还没有创建一个对象的。只是定义。
七、创建类对象
【知识关键】
掌握如何创建类对象,访问类中的元素,调用类中的方法。
【知识分析】
通过demo一边分析一边总结。
【知识联系】
- 本质上就是创建对象后访问对象中的元素以及访问类中的方法。逻辑没变,只是形式变了
八、#pragma mark指令的使用
【知识关键】
你得知道这个指令的用处是什么?场景是什么?
【知识分析】
就是添加了#pragma mark 之后,可以在文件的导航栏里面发现所标注的位置。
【知识联系】
- 类似于java里面的todo关键字,可以通过某个汇总的地方进行查看。
九、NSString类简单介绍及用法
【知识关键】
熟悉NSString的各种字符串的操作。创建、切割、获取长度…等等
【知识分析】
通过简单在的例子抛砖引玉,不做过多的总结,遇上需要的再查询即可。
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
//1.
NSString *name=@"张三";
NSLog(@"%@",name);
//2.
NSString *string = [NSString new];
string=@"李四";
NSLog(@"%@",string);
//3.
NSString *string2= [[NSString alloc] initWithFormat:name];
NSLog(@"%@",string2);
//4.
NSString *string3=[NSString
stringWithFormat:@"图片 xxx %02d- %02d",0x13,10];
NSLog(@"%@",string3);
}
return 0;
}
【知识联系】
- 类似于java里面的字符串操作,使用的时候遇上相似的地方在进行相关资料的查询即可。
- 逻辑相同,形式不同而已。
十、self的用法
【知识关键】
self的含义是什么?使用场景是什么?
【知识分析】
含义就是一个指针,指向了自己(类,或者变量)。
类似于java中在方法里面调用了this…();就会调用本类里面对应名字的函数。
下面通过一个demo感受一下self的用法
#import <Foundation/Foundation.h>
#import "Student.h"
@implementation Student
- (void) study{
NSLog(@"我在学习啊。。");
[self eat];//self中调用了“自己”,的eat函数。
}
- (void) eat{
NSLog(@"我在吃啊。。");
}
@end
【知识联系】
十一、description与Super的使用
【知识关键】
含义+场景
【知识分析】
是什么?:description 是一个方法,所有的类都有这个方法(所有类都是继承NSObject),作用是赋值NSLog进行输出。下面通过一个demo来体验一下description方法。没有重写的时候输出的是对象的地址,重写之后就可以自定义自己想要定义的内容 了。
场景:自己想要自定义输出内容的时候使用此函数。
-(NSString *)description()
{
return [NSString stringWithFormat:@"姓名: %@ 年龄: %d",name,age]
}
super的含义:就是获得一个指向父类的指针,可以通过此指针
【知识联系】
java中的toString();
十二、SEL的使用
【知识关键】
一个拿来判断对象中有没有这个方法的选择器
【知识分析】
//声明SEL类型
SEL sel = @selector(funName);
ClassName *p = [[ClassName alloc] init];
//调用p对象中sel类型对应的方法
[p performSelector:sel];
//声明SEL类型
//需要传参的方法
SEL sel1 = @selector(funName:);
//withObject:需要传递的参数
//注意:如果通过performSelector调用有参数的办法,那么参数一定是对象类型
//也就是方法的形参必须是一个对象,withObject只能传递一个对象
//performSelector最多只能传递2个参数
[p performSelector:sel1 withObject:@"121212"];
【知识联系】
无。一个机制,判断对象中是否存在这个方法,如果存在就返回true,反之返回false。
十三、点语法的使用
【知识关键】
本质上等于调用get/set()方法。
【知识分析】
@interface Person:NSObject {
int age;
int num;
}
- (void)setAge:(int)newAge;//注意,该方法的方法名为:setAge:
- (int)age;//在oc中,习惯上get方法名就是成员变量名
- (void)setAge:(int)newAge andNum:(int)newNum;//该方法的方法名为:setAge:andNum:
- (int)num;
@end
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p=[Person new];
p.name=@"张三";
p.age=10;
p.sex=1;
NSLog(@"name=%@,age=%d,sex=%d",p.name,p.age,p.sex);
}
return 0;
}
【知识联系】
java中的get/set方法
十四、@property、@synthesize关键字的使用
【知识关键】
加注解,生成get/set方法。
【知识分析】
@synthesize实xCode在4.4之前需要配合@property一起来使用的。通过demo感受一下
【Person.h xcode4.4之前】
@interface Person : NSObject
{
@public
NSString *_name;
NSString *name;
int _age;
int _sex;
}
/**- (void)setName:(NSString*)name;
- (NSString*)name;
- (void)setAge:(int)age;
- (int)age;
- (void)setSex:(int)sex;
- (int)sex;
*/
@property NSString *name;// 注意这里,去掉了下划线
@property int age,sex;
@end
【Person.m xcode4.4之前】
#import <Foundation/Foundation.h>
#import "Person.h"
@implementation Person
@synthesize name,age,sex;
@end
【Person.h xcode4.4之后】
@interface Person : NSObject
@property NSString *name;
@property int age;
@property int sex;
@end
【Person.m xcode4.4之后】
#import <Foundation/Foundation.h>
#import "Person.h"
@implementation Person
@end
【知识联系】
可以结合oc里面的点语法,可以很快实现建立对象的目的。
十五、了解动态类型和静态类型
【知识关键】
两个词语的含义是什么?静态类型和java的泛型有一定程度的联系
【知识分析】
- 动态类型指的就是此类可能存在子类,在编译的时候不确定是生成哪个子类。
- 静态类型就是加了“泛型”,在编译的时候就已经知道你是要生成哪个子类了。
【知识联系】
java的泛型;
十六、id类型的使用
【知识关键】
? 万能指针。
【知识分析】
id :万能指针 能够指向任何OC对象 id自带*。
#import <Foundation/Foundation.h>
#import "Cat.h"
#import "Animal.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Animal *cat = [Cat new];
[(Cat *)cat jump];
NSObject *obj=[Cat new];
[(Cat*)obj jump];
//编译器对NSObject做类型检测,但是不对id做类型检测
//所以id不用强制类型转换也可以运行成功,编译也报警告。
id c=[Cat new];
[c jump];
}
return 0;
}
//id用法:
//1.作为参数
//2.作为成员变量
【知识联系】
指针;
十七、动态类型检测
【知识关键】
在代码运行的到时候判断一个类是不是某个class。或者是能不能执行某个方法。在NSObject里面有isMemberOfClass、isSubclassOfClass等等。
【知识分析】
通过demo演示。感受一下代码
#import <Foundation/Foundation.h>
#import "DogSon.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
//1.判断对象是不是指定类对象或者指定类的子类对象
Class dog=[Dog class];
DogSon *son=[DogSon new];
BOOL res=[son isMemberOfClass:dog];
NSLog(@"iskindOfClass:%d",res);
//2.判断对象是不是1个特定类型的对象,不包括子类,父类
Class dog1=[Dog class];
DogSon *son1=[DogSon new];
res=[son1 isMemberOfClass:dog1];
NSLog(@"isMemberOfClass:%d",res);
//3.判断1个类是不是另外一个类的子类
Class dog2=[Dog class];
res=[DogSon isSubclassOfClass:dog2];
NSLog(@"isSubclassOfClass:%d",res);
//4.判断对象中是否能响应指定的方法,这个最常用
SEL sel=@selector(play);
Dog *son2=[DogSon new];
res=[son2 respondsToSelector:sel];
NSLog(@"respondsToSelector:%d",res);
//5.判断类中是否能响应指定方法
sel=@selector(play);
res=[Dog instancesRespondToSelector:sel];
NSLog(@"respondsToSelector:%d",res);
}
return 0;
}
【知识联系】
场景使用:在不确定实现了哪个子类的时候可以通过动态类型检测进行相关的逻辑判断操作。
十八、构造方法
【知识关键】
oc语言里面的方法,如果是传进两个参数,和平时的java语言很不类似,需要去留意方法结构。
调用对象里面的一个带两个参数的结构:
[ 对象 标识:参数 标识:参数]
【知识分析】
通过demo进行演示
Dog.h
#import <Foundation/Foundation.h>
@interface Dog : NSObject
{
@public
NSString *_name;
int _age;
}
//带参数的构造方法必须以 initWithXXX 开头,W必须大写!!!!!!!!!!!!!!!!!!!!!
-(instancetype)initWithName:(NSString*)name withAge:(int)age;
@end
Dog.m
#import "Dog.h"
@implementation Dog
/**
重写构造方法
- (instancetype)init
{
self = [super init];
if (self) {
//子类的初始化等等其他操作,在这个花括号里做。
}
return self;
}
*/
//自定义构造方法
//我们刚刚开始是先加载了父类对象之后才进行self的加载
-(instancetype)initWithName:(NSString*)name withAge:(int)age{
if ([super init]) {
_age=age;
_name=name;
}
return self;
}
@end
main.m
/**
new一下,就能有一个对象
1.分配内存空间
2.初始化
构造方法:指的就是初始化方法
*/
#import <Foundation/Foundation.h>
#import "Dog.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Dog *dog=[[Dog alloc] initWithName:@"旺财" withAge:1];
NSLog(@"name=%@,age=%d",dog->_name,dog->_age);
}
return 0;
}
【知识联系】
构造函数的使用,意义;
十九、计数器的使用
Objective-C语言使用引用计数来管理内存,也就是说,每个对象都有个可以递增或递减的计数器。如果想使某个对象继续存活,那就递增其引用计数;用完了之后,就递减其计数。计数为0,就表示没人关注此对象了,于是,就可以把它销毁。----和java的内存管理有一定的相似之处。
OC内存管理机制:https://www.cnblogs.com/echo-imax/p/4576757.html
熟悉dealloc / retain/ release 等函数
【知识关键】
知道计数器使用的原则。OC里面MRC还有ARC使用的情况,怎么才能是ARC进行使用?
【知识分析】
Person.m
#import "Person.h"
@implementation Person
- (void)dealloc
{
//对象只要调用该方法,就代表对象即将被释放
NSLog(@"我被杀了");
//重写该方法,就必须调回父类的方法
[super dealloc];
}
@end
main.m
/**
MRC:手动内存管理
ARC:默认就是ARC automatic Refrence Count 自动引用计数器
操作引用计数器的方法:
1.retainCount:获得对象的引用计数器的值
2.retain:能够让对象的计数器+1;
3.release:让对象的计数器-1;
怎么判断对象被释放?
dealloc 只要调用此方法,就表明此对象将被释放
僵尸对象:已经被释放的对象
野指针:指向僵尸对象的指针
空指针:指向nil的指针,给空指针发送消息(调用方法)不会报任何错误
规定:谁relase谁retain
*/
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person =[[Person alloc] init];
//retainCount:能够输出对象引用计数器的值
NSLog(@"%lu",person.retainCount);
//那怎么知道对象被回收呢?重写父类的dealloc
[person release];
//为了防止后边调用某些方法会报错,此处赋值nil,给空指针发送任何消息都不会报错。
person=nil;
//如果该对象被回收,则下面会报错
NSLog(@"%lu",person.retainCount);
}
return 0;
}
【知识联系】
java的内存管理模式。
二十、多对象内存管理
【知识关键】
本质:在引用指向堆里面某个对象的时候(有很多相同的对象),这时候通过判断是不是引用了之前相同的对象,如果不是,则释放之前的对象,retain新的对象
【知识分析】
(1)通过demo演示分析
#import "Gamer.h"
@implementation Gamer
//游戏者去选择自己想进去的房子,进入之前判断是不是自己想要的那个房子
- (void)dealloc
{
NSLog(@"玩家挂了");
[_house release];
[super dealloc];
}
-(void)setHouse:(House*)house{
/**
1.判断当是同一个房间的时候,就不再对计数器+1
2.判断当进入不同的一个房间,首先把之前的房间给释放,然后再对新房间的计数器+1.
*/
if(_house!=house){
[_house release];
_house= [house retain];
}
}
-(House*)house{
return _house;
}
@end
(2)与@property的联系(知识点十四)。可以帮我们生成这样set的方式,去做内存管理,不需要我们手动去管理了。
【知识联系】
(1)@property
(2)多次引用类的不同对象的处理情况。
二十一、@class
【三句话总结知识点】
(1)@class告诉编译器这是个类。(只是告诉,不包含任何其他的内容)
(2)和#import有一定的联系。(.h文件里面使用@class,.m文件里面使用#import)
(3)特别适合使用在.h文件里面
【知识分析】
(1)使用场景
在.h文件中使用@class引用一个类,在.m文件中使用#import包含这个类的.h文件
(2)与#import相比较
如果是存在相互嵌套关系,使用#import的话就会报错。#import会将所有类的内容都加载到对应的地方。但是如果使用@class就不会出现这样的情况。
二十二、Objective-C - 循环引用问题
【三句话总结知识点】
(1)问题场景:两个类:A,B,A中有成员变量B,B中有成员变量A。
(2)问题原因:成员类变量中你中有我,我中有你,计数器不断增加。
(3)让一端成员变量使用assign修饰。
【知识分析】
(1)demo场景,A,B其中一个使用assign(不考虑内存管理)
#import <Foundation/Foundation.h>
@class Cat;
@interface Gril : NSObject
//对于循环retain的情况,对象不能使用retain,只能使用assign
@property(nonatomic,assign)Cat *cat;
@end
(2)特殊情况。
NSString:无论怎么调用它的计数器都不会增长。
二十三、block的使用
【三句话总结知识点】
(1)和c的函数很像,有返回值,形参等等。
(2)声明方式和c函数指针类似,只是把*置换成了^。但是要注意实现的方式有所出入
(3)场景:暂时当作c函数使用吧。
【知识分析】
/**
block用来保存一段代码
block的标志:^
block跟函数很像:
1.可以保存代码
2.有返回值
3.有形参。
4.调用方式一样
block定义:
返回值类型 (^变量名)(参数1变量类型,参数2变量类型) =^(参数1变量类型 参数1变量名,参数2变量类型 参数2变量名){操作代码};
block访问外部变量:
*block内部可以访问外面的变量
*默认情况下,block不能修改外面的局部变量,给局部变量加上__block关键字,这个局部变量就可以在block内修改
*/
//块的声明 块的声明类似于函数指针,只不过把 * 换成了 ^ 。声明时最前面是返回类型,中间是块名,最后是参数类型。
//块的声明demo1
int (^someBlock) (int, int);
//块的实现
^int(int a, int b) {
return a+b;
};
//如果块的参数是空的
^{
return 100;
};
//demo2
typedef int (^MyBlock)(int,int);
MyBlock sumBlock = ^(int a,int b){
c=a+b;
return c;
};
//demo3//声明
typedef int (^MyBlock)(int,int);
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int c;
//如果block没有形参,=后面可以将()省略
void (^myBlock)()= ^{
NSLog(@"---------------");
NSLog(@"---------------");
};
//实现
MyBlock sumBlock = ^(int a,int b){
c=a+b;
return c;
};
NSLog(@"sumBlock(10,20):%d",sumBlock(10,20));
}
return 0;
}
//下面补充一下typedef在声明比较复杂的函数的用处
typedef void (*PFunCallBack)(char* pMsg, unsigned int nMsgLen);
//使用了typedef
RedisSubCommand(const string& strKey, PFunCallBack pFunCallback, bool bOnlyOne);
//没使用typedef
RedisSubCommand(const string& strKey, void (*pFunCallback)(char* pMsg, unsigned int nMsgLen), bool bOnlyOne);
二十四、@protocol(协议)的使用
【三句话总结知识点】
(1)@interface 是OC中类头文件的声明并不是真正的接口。@protocol 才算是OC里面的“接口的概念”,符号<…>。
(2)java中接口声明的方法必须全部实现,protocol可以不用全部实现(但是会发出警告 ),单继承多实现。
(3)一般定义协议是在.h文件中,而谁采用这个协议就在谁的.m文件中实现方法。(写好东西在丢给原始类)
【知识分析】
(1)使用方法
Protocol有两种声明的方式:
- 在单独的声明文件(.h文件)中声明。
- 在某个类的声明的文件中声明。
以上两种方式视具体情况而定,但是在代码规范上都是一致的:
// HandleDeckDelegate.h
@protocol HandleDeckDelegate <NSObject>
@required
- (void)ShuffleDeck;
@optional
- (void)CuttingDeck;
@end
上述代码中有两个关键字,@required 和@optional ,表示如果要实现这个协议,那么ShuffleDeck 方法是必须要实现的,CuttingDeck 则是可选的,如果不注明,那么方法默认是@required 的,必须实现。
那么如何实现这个Protocol呢,很简单,创建一个普通的Objective-C类,如果Protocol使用单独的.h文件声明,那么在该类的.h声明文件中引入包含Protocol的.h文件,如果Protocol是声明在一个相关类中,那么就需要引入该类的.h文件。之后声明采用这个Protocol即可:
// Deck.h
#import <Foundation/Foundation.h>
#import "Card.h"
#import "HandleDeckDelegate.h"
@interface Deck : NSObject<HandleDeckDelegate>
- (Card *)randomDrawCard;
@end
用尖括号(<…>)括起来的HandleDeckDelegate 就是我们创建的Protocol。如果要采用多个Protocol,可以在尖括号内引入多个Protocol名称,并用逗号隔开即可。例如<HandleDeckDelegate,xxxDelegate> 。
// Deck.m
#import "Card.h"
@implementation Deck
- (Card *)drawCardFromTop
{
//TODO.....
}
- (void)ShuffleDeck
{
//TODO.....
}
@end
由于CuttingDeck 方法是可选的,所以我们只实现了ShuffleDeck 。
(2)需要注意的问题。
- 根据约定,框架中后缀为Delegate的都是Protocol,例如
UIApplicationDelegate ,UIWebViewDelegate 等。 - Protocol本身是可以继承的,比如:
@protocol A
-(void)methodA;
@end
@protocol B <A>
-(void)methodB;
@end
如果你要实现B,那么methodA和methodB都需要实现。
- Protocol是与任何类都无关的,任何类都可以实现定义好的Protocol,如果我们想知道某个类是否实现了某个Protocol,那么我们可以用
conformsToProtocol 方法进行判断:
[obj conformsToProtocol:@protocol(ProcessDataDelegate)]
二十五、Category(分类、范畴)的使用
【三句话总结知识点】
(1)使用场景:已经存在了一个类,但是你想为这个很复杂的类添加一个新的方法,应该怎么做?OC提供这样的一个场景对类进行扩展时不需要访问其源码,也不需要创建子类。
(2)在一个新的头文件里面引入原始文件的头文件,类名旁边加上(…扩展类名字…),实现这个新的头文件的.m文件。
(3)只能添加方法,不能添加成员变量。(对原始类进行加工)
【知识分析】
(1)使用方法
Category的实现很简单,我们举例说明。
// Deck.h
#import <Foundation/Foundation.h>
#import "Card.h"
@interface Deck : NSObject
- (Card *)randomDrawCard;
@end
这是类Deck的声明文件,其中包含一个实例方法randomDrawCard ,如果我们想在不修改原始类、不增加子类的情况下,为该类增加一个drawCardFromTop 方法,只需要定义两个文件Deck+DrawCardFromTop.h 和Deck+DrawCardFromTop.m ,在声明文件和实现文件中用() 把Category的名称括起来即可,声明文件如下:
// Deck+DrawCardFromTop.h
#import "Deck.h"
#import "Card.h"
@interface Deck(DrawCardFromTop)
- (Card *)drawCardFromTop;
@end
实现文件如下:
// Deck+DrawCardFromTop.m
#import "Deck+DrawCardFromTop.h"
#import "Card.h"
@implementation Deck(DrawCardFromTop)
- (Card *)drawCardFromTop
{
//TODO.....
}
@end
DrawCardFromTop 是Category的名称。这里一般使用约定俗成的习惯,将声明文件和实现文件统一采用”原类名+Category名”的方式命名。 使用也非常简单,引入Category的声明文件,然后正常调用即可:
// main.m
#import "Deck+DrawCardFromTop.h"
#import "Card.h"
int main(int argc, char * argv[])
{
Deck *deck = [[Deck alloc] init];
Card *card = [deck drawCardFromTop];
return 0;
}
(2)使用场景
- 需求变更在整个开发周期是司空见惯的事情,那么我们可能就需要对某个或某几个类中添加新的方法以满足需求。
- 我们在团队协作开发时候,经常需要多个人来实现一个类中的不同方法,在这种情况下采用Category是一个较好的选择。
- 当一些基础类库满足不了我们的需求时,我们希望能扩展基础类库,这时就需要Category。
(3)需要注意的问题
-
Category可以访问原始类的实例变量(不能访问private,只能访问protected和public修饰的变量),但不能添加变量,如果想添加变量,可以考虑通过继承创建子类。 -
Category可以重载原始类的方法,但不推荐这么做,这么做的后果是你再也不能访问原来的方法。如果确实要重载,正确的选择是创建子类。如果存在分类和原始类有相同的方法的时候,优先使用 分类的方法。 -
和普通接口有所区别的是,在分类的实现文件中可以不必实现所有声明的方法,只要你不去调用它。 -
子类可以使用父类分类中的方法。 -
注意类旁边的(…),<>, : 。这些符号的用处啦!
二十六、 类扩展
【三句话总结知识点】
(1)一个原始类的.h头文件被import到另外一个.h头文件。第二个在类名字旁边加上()就可以进行类扩展的操作了。
(2)类扩展是需要.h文件,不需要.m文件。只能在原始文件的.m文件里面进行实现。
(3)两种实现方式,一种方式.h文件在另外的.h文件当中,另外一种方式实是在 原始文件的.m文件当中**(能实现真正的私有)。**
【知识分析】
(1)使用演示。
创建Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
{
@protected
int _age;
@public
int _sex;
@private
int _num;
}
@property (nonatomic,retain)NSString *name;
@end
创建1个类扩展:Person_Sun.h
/**
能够扩展类的成员变量和方法
正常类的声明格式:@interface 类名:NSObject
分类格式:@interface 类名(分类名称)
类扩展:@interface 类名()
*/
#import "Person.h"
@interface Person ()
{
@public
int _height;
}
-(void)run;
@end
因为类扩展只要.h文件,所以我们只能在Person.m中实现
#import "Person.h"
#import "Person_Sun.h"
@implementation Person
- (void)run{
_name=@"张三";
_height=30;
_weight=40;
_age=10;
_sex=20;
NSLog(@"%@在跑跑跑",_name);
}
可以看到run方法能访问本类中的所有成员变量,我们在main.m中运行
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Person_Sun.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person=[[[Person alloc] init ] autorelease];
[person run];
person->_height=20; }
return 0;
}
以上方式通过创建了一个文件,不能达到方法和成员变量真正的私有,下面演示怎么私有:
Person.m
/**
类创建的第二种方式:@interface 类名()
里面可以声明成员变量
里面可以声明方法,类扩展声明的方法,必须实现
@end
这种创建类扩展的方式,是真正的私有
这种私有,子类无法通过任何方式访问父类中类扩展的方法和成员变量
*/
#import "Person.h"
//这种在.m中创建的类扩展,最常用,真正私有
@interface Person()
{
NSString *_name;
int _age;
}
@property (nonatomic,assign)int _sex;
-(void)show;
@end
@implementation Person
-(void)show{
_age=20;
_name=@"张三";
NSLog(@"%d的%@在泡泡跑",_age,_name);
}
@end
外界访问类扩展:
main.m
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person =[[[Person alloc] init]autorelease];
// person->_name;私有成员变量,外界无法直接访问
//[person show];私有方法,外界无法直接访问
//person->_sex;在.m的类扩展文件中,使用@property修饰的成员变量,外界仍然无法访问。
}
return 0;
}
(2)注意事项
1.分类中只能增加方法
2.类扩展不仅可以增加方法,还可以增加成员变量,只是该成员变量是私有的,作用范围只在自身类,而不再子类或者其他类。
3.类扩展不能像分类那样拥有独立的部分(@implementation)也就是说,类扩展中声明的方法,必须依托对应的本类的.m文件来实现
4.定义在.m文件中的类扩展的成员变量和方法是私有的,定义在.h文件中的类扩展的成员变量和属性是公有的,类扩展是.m文件中声明私有方法非常好的方式。
二十七、Objective-C中的Alloc 和init
MyClass* myObj = [[MyClass alloc] init];
【三句话总结知识点】
(1)alloc是分配一个物理地址,并且返回对应的指针。
(2)init是初始化构造方法,做出一定的对象初始化动作之后再返回,但是不一定等于原来的对象。
(3)不建议使用new初始化对象,因为没有调用初始化方法。如果自定义了构造函数,将得不到调用(只会调用默认的构造方法)。
【知识分析】
alloc 是在物理内存中分配了一块内存,并且返回一个指针。
MyClass* myObj = [MyClass alloc];
此时myObj 还不能使用,因为它的内存状态还没有得到正确的设置。 init 设置对象的初始化状态并且将其返回。注意:[a init] 的返回值并不一定是a自身 ,原因如下:
-init{
self = [super init];
if(self){
....
}
return self;
- 首先,你需要调用超类的
init 方法去设置超类的实例变量等,其返回值不不一定等于原来的self ,所以你需要将返回值重新赋值给self. - 如果
self 非空,则表明被超类控制的部分已经得到正确的初始化。词时,所有的实例变量都设置为nil (如果是对象),或者0(如果是整型)。接下来,你可以执行额外的初始化设置。 - 返回self
因为init 和alloc 的返回值不一定相等,所以不要讲两个方法分开使用,如以下代码:
MyClass* myObj = [MyClass alloc];
[myObj init];
也不建议使用以下写法:
MyClass* myObj = [MyClass alloc];
myObj=[myObj init];
因为你很有可能忘记写myObj= 获取新的指针。 永远使用以下写法:
MyClass* myObj = [[MyClass alloc] init];
也不建议使用new 方法
MyClass* myObj = [MyClass new];
因为它不没有正确地调用初始化方法,一些类不并可以使用简单的init 方法,如NSView 需要initWithFrame: ,这样就不能简单地对其使用new 方法,所以不建议使用new 方法创建对象。
二十八、autorelease的使用及其自动释放池的介绍
【三句话总结知识点】
(1)创建一个池子,不断存放使用了autorelease的对象,在最后调用一次释放池子方法,会把池子里面的对象全部释放掉。
(2)流程:
- 初始化池子
- 对象调用
autorelease (把对象放在了释放池的栈顶) - 释放池子(显示调用或者自动调用)
(3)注意想要进入池子都必须使用autorelease
【知识分析】
Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic,assign)int age;
+(id)person;
+(id)personWithAge:(int)age;
@end
Person.m
#import "Person.h"
@implementation Person
- (void)dealloc
{
NSLog(@"%d,dealloc",_age);
[super dealloc];
}
//方便对象释放
//创建对象时,不要直接用类名,一般用self.
+(id)person{
return [[[self alloc] init] autorelease];
}
//方便释放的同时给属性初始化
+(id)personWithAge:(int)age{
Person *person= [self person];
person.age=age;
return person;
}
@end
main.m
/**
自动释放池的作用:
1>对池子里面所有的对象,在池子被释放的时候,统一做一次release操作。前提:调用autorelease,当池子释放时,才会对对象做release操作
2>当一个对象调用autorelease方法时,会将这个对象放到栈顶的释放池
1.autorelease的基本用法:
1>autorelease会将对象放入自动释放池
2>调用玩autorelease方法后,对象的计数器不会+1;
3>调用autorelease回返回对象本身
4>当自动释放池被销毁时,会对池子里面的所有对象做一次release操作
2.autorelease的好处:
1>不用在关心对象释放的时间
2>不用再关心什么时候调用release
3.autorelease的使用注意:
1>占用内存大的对象不要随便使用autorelease
2>占用内存小的兑现更实用autorelease,没有太大影响
4.自动释放池的创建方式:
1>iOS 5.0前:
NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
Person *person =[[[Person alloc] init] autorelease];
[pool release];
// [pool drain];
2>iOS 5.0后:
@autoreleasepool{
}
5.系统自带的方法没有包含alloc,new,copy,所明返回的对象都是autorelease的
6. 开发中经常会提供一些类方法,快速创建一个已经autorelease的对象
*/
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
//以栈的结构进行存储的,先进后出
@autoreleasepool {//开始代表创建了一个释放池
Person *person =[[[Person alloc] init] autorelease];
person.age=10;
@autoreleasepool {
Person *person =[[[Person alloc] init] autorelease];
person.age=20;
@autoreleasepool {
Person *person=[Person person];
person.age=30;
}
}
}//出了花括号,会对池子里面的所有调用了autorelease方法的对象做一次release操作。
return 0;
}
二十九、ARC
【三句话总结知识点】
(1)简单点说这个东西在编译的时候在他认为合适的地方帮我们加上了retain、release等方法。自动帮我们进行了内存管理
(2)引入强指针,弱指针等关键词替代retain,assign等作用,主要注意一下循环引用的情况(用强弱指针控制)
(3)@property,在非OC对象里面,assign依然可以使用
【知识分析】
(1)弱引用最好理解的例子。第一行橙色框框代表了dog是一个强引用指向一个对象,第二行的意思是一个弱引用和强引用一样指向了同一个对象,那么这个对象的生命周期就是和强引用指向的生命周期一样。
(2)demo演示,主要演示强引用和弱引用的的特性情况。
Person.h
#import <Foundation/Foundation.h>
@class Dog;
@interface Person : NSObject
@property (nonatomic,strong)Dog *dog;
@property (nonatomic,weak)Dog *dog2;
@property (nonatomic,strong)Dog *dog3;
@end
Person.m
#import "Person.h"
@implementation Person
- (void)dealloc
{
NSLog(@" Person dealloc.....");
}
@end
Dog.h
#import <Foundation/Foundation.h>
@class Person;
@interface Dog : NSObject
@property (nonatomic,weak) Person* person;
@end
Dog.m
#import "Dog.h"
@implementation Dog
- (void)dealloc
{
NSLog(@" Dog dealloc.....");
}
@end
main.m
/**
ARC的判断准则:只要没有强指针指向对象,就会释放对象
1.ARC特点:
1>不允许调用release、retain、retainCount
2>允许重写dealloc,但是不允许调用[super dealloc]
3>@property的参数
strong:成员变量是强指针(适用于OC对象类型)
weak:成员变量是弱指针(适用于OC对象类型)
assign:适用于非OC对象类型
4>以前的retain改为strong
指针分2种
1>强指针:默认情况下,所有指针都是强指针 __strong
2>弱指针:__weak
当两端循环引用时候,解决方案:
1>ARC
1端用strong,另1端用weak
2>非ARC
1端用retain,另1端用assign
*/
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Dog.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
//对于循环strong,必须一端strong,一段weak;
Person *p=[[Person alloc]init];
Dog *dog=[[Dog alloc] init];
p.dog=dog;
dog.person=p;
}
return 0;
}
void test(){
//弱指针指向对象立马会被回收,所以下面p为null
__weak Person *p=[[Person alloc]init];
NSLog(@"%@",p);
//默认就是强指针__strong,所以下面p2有地址值,出了大括号之后才被回收
Person *p2=[[Person alloc]init];
NSLog(@"%@",p2);
//因为Dog在Person中是strong修饰的,所以出了大括号才被回收
Dog *dog=[[Dog alloc] init];
p2.dog=dog;
//因为dog2是weak修饰的,所以会跟着dog的释放一起释放。
p2.dog2=dog;
//这时候将dog和dog2都改为nil,将2个强指针释放了,那么dog2也会释放,所以他下面的地址打印为null
dog=nil;
p2.dog=nil;
NSLog(@"%@..",p2.dog2);
//因为dog3也是strong强指针,所以到了大括号和dog一起释放
dog=[[Dog alloc] init];
p2.dog3=dog;
NSLog(@"%@..",p2.dog3);
NSLog(@"------");
}
三十、dealloc方法
【三句话总结知识点】
(1)执行时机:一个对象的引用计数器变成了0.即将回收的时候就会调用这个函数。
(2)不可以直接调用这个函数,只能由系统进行调用。
(3)一旦重写了dealloc方法,就必须调用[super dealloc] ,并且放在最后执行。(ARC中并不是如此)
三十一、Foundation框架中常用的类
本章首先讲述对象的可变性的概念,然后介绍Foundation框架中常用的字符串类,数据类,数组类和词典类等内容。重点放在去了解一下可以通过这些类实现什么样的功能,细节的地方暂不做过深的研究了。用到的时候才进行资料查询即可。
一、可变对象和不可变对象
【三句话总结知识点】
(1)借助java里面的String类型,它是一个类,他被static final修饰了,所以他是不可改变的对象。称:不可变对象。
(2)可变对象就是指针指向的内存的内容是可以进行修改的。不可变对象指针指向的内存对应的内容是不可以修改的。
(3)Foundation框架里面的类总体分成了两类。一类是可变对象,另外一类是不可变对象。**其中,可变类是不可以变类的子类。**如果想将不可变类对象转化成可变类对象的话,使用mutableCopy 函数。
二、NSString
链接:https://blog.csdn.net/weixin_39624536/article/details/90053954
(1)操作大纲
- 初始化字符串
- 获取字符串长度
- 检测字符串是否以“…”开头
- 字符串截取
- 字符串比较
- 字符串拼接
- 字符串转化成其他类型
- 其他类型转化成字符串
三、NSRang
链接:https://blog.csdn.net/weixin_39624536/article/details/84454256
(1)总结:一个结构体:位置 + 长度。可以描述一段范围的起始位置和该范围内的长度。
typedef struct _NSRange {
NSUInteger location; //表示该范围的起始位置
NSUInteger length; //表示该范围内的长度
} NSRange;
四、NSData
【三句话总结知识点】
(1)是用来包装数据的,里面存放的是二进制数据。
(2)文本,音频,视频数据之间的差距都被屏蔽了。可以通过NSData的封装将不同类型的对象写入到同一个文件
(3)多用于多媒体开发,经常和归档,解档一起使用。
【知识分析】
(1)操作大纲
- NSData的初始化。
- NSData和不同数据之间的转化。
- 结合归档、解档实现数据的保存。
五、数组NSArray、NSMutableArray
【三句话总结知识点】
(1)能存放不同类型的对象(这是和java不相同的点),不能存放基本数据类型。实质上存放的是对象的地址。
(2)其他数组的功能类似于java数组,当你想到需要什么操作的时候再去查找相关的资料吧。
(3)有个他自己的特性,可以将数组中的文件直接写入文件。
【知识分析】
(1)操作大纲。(针对可变数组NSMutableArray)
剩下的内容以后再总结
|