OC语言疯狂讲义(下)v2.0


OC语言疯狂讲义(下)V2.0 分区 OC语言疯狂讲义(下)V2.0 的第 1 页 1)配图更加详细和有针对性 2)NSString内存管理讲解方法 3)copy 知识点调整和更新 4)其他细小更新 OC语言疯狂讲义(下)V2.0更新说明 分区 OC语言疯狂讲义(下)V2.0 的第 2 页 本小节知识点: 1、【了解】上课和作息时间 2、【了解】课程预习方法 3、【了解】授课资料获取方法 1、上课和作息时间 每天上课时间:上午 9:00 ~ 12:00 下午14:30 ~ 17:30 (一般会延长到17:50左右) 上课周期: 一般情况下是 上两天课会安排一天自习时间 注意:自习时间不是休息哦 2、课程预习方法 上课周期: 每天的课程资料中有个预习文件夹 3、资料获取方法 下载软件 0-【了解】开班须知 分区 第一天(@传智如意大师) 的第 3 页 安装 使用 分区 第一天(@传智如意大师) 的第 4 页 桌面目录根据自己的用户名不同,路径也不同 分区 第一天(@传智如意大师) 的第 5 页 本小节知识点: 1、【掌握】内存管理的基本概念 2、【掌握】OC内存管理的范围 1、OC内存管理基本概念 为什么要进行内存管理? 分析下图有哪些对象? 1-【掌握】内存管理的基本概念及范围 分区 第一天(@传智如意大师) 的第 6 页 由于移动设备的内存极其有限,所以每个APP所占的内存也是有限制的,当app所占用的内存 较多时,系统就会发出内存警告,个app可用的内存是被限制的,如果一个app使用的内存超 过20M,则系统会向该app发送Memory Warning消息。收到此消息后,需要回收一些不需要再 继续使用的内存空间,比如回收一些不再使用的对象和变量等,否则程序会崩溃。 管理范围: 管理任何继承NSObject的对象,对其他的基本数据类型无 效。 本质原因是因为对象和其他数据类型在系统中的存储空间不一样,其它局部变量主要存放于 栈中,而对象存储于堆中,当代码块结束时这个代码块中涉及的所有局部变量会被回收,指 向对象的指针也被回收,此时对象已经没有指针指向,但依然存在于内存中,造成内存泄 露。 2、OC内存管理的范围 分区 第一天(@传智如意大师) 的第 7 页 分区 第一天(@传智如意大师) 的第 8 页 本小节知识点: 1、【理解】内存管理的原理 2、【了解】内存管理的分类 1、OC内存管理的原理 1)对象的所有权及引用计数 任何对象都可能拥有一个或多个所有者。只要一个对象至少还拥有一个所有者,它就会 继续存在 Person *p = [[Person alloc] init]; 对象所有权概念 Cocoa所有权策略 任何自己创建的对象都归自己所有,可以使用名字以“alloc”或“new”开头或名字中 包含“copy”的方法创建对象,可以使用retain来获得一个对象的所有权 对象的引用计数器 每个OC对象都有自己的引用计数器,是一个整数表示对象被引用的次数,即现在有多少 东西在使用这个对象。对象刚被创建时,默认计数器值为1,当计数器的值变为0时,则对象 销毁。 对象的结构: 2-【理解】内存管理的原理及分类 分区 第一天(@传智如意大师) 的第 9 页 对象的结构: 在每个OC对象内部,都专门有4个字节的存储空间来存储引用计数器。 判断对象要不要回收的唯一依据(存在一种例外:对象值为nil时,引用计数为0,但不回收 空间)就是计数器是否为0,若不为0则存在。 3)引用计数器的作用 给对象发送消息,进行相应的计数器操作。 retain消息:使计数器+1,该方法返回对象本身 release消息:使计数器-1(并不代表释放对象) retainCount消息:获得对象当前的引用计数器值 %ld %tu 4) 对引用计数器的操作 当一个对象的引用计数器为0时,那么它将被销毁,其占用的内存被系统回收。 当对象被销毁时,系统会自动向对象发送一条dealloc消息,一般会重写dealloc方法,在这 里释放相关的资源,dealloc就像是对象的“临终遗言”。 一旦重写了dealloc方法就必须调用[super dealloc],并且放在代码块的最后调用(不能直 接调用dealloc方法)。 一旦对象被回收了,那么他所占据的存储空间就不再可用,坚持使用会导致程序崩溃(野指 针错误)。 注意; 1) 如果对象的计数器不为0,那么在整个程序运行过程,它占用的内存就不可能被回收(除 非整个程序已经退出 ) 5) 对象的销毁 分区 第一天(@传智如意大师) 的第 10 页 非整个程序已经退出 ) 2)任何一个对象, 刚生下来的时候, 引用计数器都为1。(对象一旦创建好,默认引用计数 器就是3)当使用alloc、new或者copy创建一个对象时,对象的引用计数器默认就是1 2、OC内存管理分类 Objective-C供了三种内存管理方式: MannulReference Counting(MRC,手动管理,在开发 iOS4.1之前的版本的项目时我们要 自己负责使用引用计数来管理内存,比如要手动 retain、release、autorelease 等,而在其后 的版本可以使用 ARC,让系统自己管理内存。) automatic reference counting(ARC,自动引用计数,iOS4.1 之后推出的) garbage collection(垃圾回收)。iOS不支持垃圾回收; ARC作为苹果新供的技术,苹果推荐开发者使用ARC技术来管理内存; 开发中如何使用:需要理解MRC,但实际使用时尽量用ARC MRC和ARC的区别 分区 第一天(@传智如意大师) 的第 11 页 本小节知识点: 1、【掌握】关闭ARC的方法 2、【掌握】手动管理(MRC)快速入门 1、关闭ARC的方法(其中之一) 1)设置项目信息 设置ARC 为NO 3-【掌握】手动内存管理快速入门 分区 第一天(@传智如意大师) 的第 12 页 2、手动管理快速入门 内存管理的关键如何判断对象被回收? (1)一定要[super dealloc],而且要放到最后,意义是:先释放子类占用的空间在释放父 类占用的空间 (2)对self(当前)所拥有的的其他对象做一次release操作 -(void)dealloc [_car release]; [super dealloc]; { } 注意 1)永远不要直接通过对象调用dealloc方法(实际上调用并不会出错) 一旦对象被回收了, 它占用的内存就不再可用, 坚持使用会导致程序崩溃(野指针错误)为 了防止调用出错,可以将“野指针”指向nil(0)。 Person.h 重写dealloc方法,代码规范 分区 第一天(@传智如意大师) 的第 13 页 #import @interface Person : NSObject @property int age; @end Person.m #import "Person.h" @implementation Person //监测对象是否被回收的方法 -(void)dealloc{ //释放子类对象 NSLog(@"对象被销毁了~"); //释放父类 [super dealloc]; } @end main.m 分区 第一天(@传智如意大师) 的第 14 页 本小节知识点: 1、【理解】内存管理的原则 2、【理解】内存管理研究的内容 1、内存管理的原则 只要还有人在使用某个对象,那么这个对象就不会被回收; 只要你想使用这个对象,那么就应该让这个对象的引用计数器+1; 当你不想使用这个对象时,应该让对象的引用计数器-1; 1)原则 (1)如果你通过alloc,new,copy来创建了一个对象,那么你就必须调用release或者 autorelease方法 (2)不是你创建的就不用你去负责 2)谁创建,谁release 只要你调用了retain,无论这个对象时如何生成的,你都要调用release 3)谁retain,谁release 有始有终,有加就应该有减。曾经让某个对象计数器加1,就应该让其在最后-1. 4)总结 2、内存管理研究内容 1)野指针(僵尸对象) 2)内存泄露 4-【理解】内存管理的原则 分区 第一天(@传智如意大师) 的第 15 页 本小节知识点: 1、【掌握】单个对象的野指针问题 1、单个对象的野指针问题 思考&实现1: 对象在堆区的空间已经释放了,还能再使用p1吗? 野指针错误:访问了一块坏的内存(已经被回收的,不可用的内存)。 僵尸对象:所占内存已经被回收的对象,僵尸对象不能再被使用。(默认情况下xcode为了 高编码效率,不会时时检查僵尸对象,打开僵尸对象检测) 打开僵尸对象监测的方法 5-【掌握】单个对象内存管理(野指针) 分区 第一天(@传智如意大师) 的第 16 页 分区 第一天(@传智如意大师) 的第 17 页 注意: 1)空指针:没有指向任何东西的指针,给空指针发送消息不会报错 关于nil和Nil及NULL的区别: nil: A null pointer to an Objective-C object. ( #define nil ((id)0) ) nil 是一个对象值。 Nil: A null pointer to an Objective-C class. NULL: A null pointer to anything else. ( #define NULL ((void *)0) ) NULL是一个通用指针(泛型指针)。 NSNull: A class defines a singleton object used to represent null values in collection objects (which don't allow nil values). [NSNull null]: The singleton instance of NSNull. [NSNull null]是一个对象,他用在不能使用nil的场合。 2)不能使用[p retain]让僵尸对象起死复生。 3)野指针操作 分区 第一天(@传智如意大师) 的第 18 页 3)野指针操作 分区 第一天(@传智如意大师) 的第 19 页 本小节知识点: 1、【掌握】避免使用僵尸对象的方法 2、【掌握】对象的内存泄漏 1、避免使用僵尸对象的方法 为了防止不小心调用了僵尸对象,可以将对象赋值nil(对象的空值) 2、对象的内存泄露 1)retain 和 release 个数不匹配,导致内存泄露 6-【掌握】单个对象内存管理 分区 第一天(@传智如意大师) 的第 20 页 2)对象使用的过程中被赋值了nil,导致内存泄露 Person *p = [[Person alloc] init]; NSLog(@" p->retainCount = %ld",p.retainCount); p = nil; //设置对象为nil,实际上让p指向了一个特殊的地址(无效地址) [p release];// [nil release]; 向nil发送release消息,不会让引用计数-1 值为 nil 的对象的 retainCount = 0的 NSLog(@" p->retainCount = %ld",p.retainCount); 3)在函数或者方法中不当的使用retain 或者 relase 造成的问题 分区 第一天(@传智如意大师) 的第 21 页 分区 第一天(@传智如意大师) 的第 22 页 本小节知识点: 1、【掌握】多个对象野指针问题 1、多个对象野指针问题 思考&实现1: 凤姐坐车去拉萨 Person.h #import #import "Car.h" @interface Person : NSObject { Car *_car; } -(void)setCar:(Car*)car; -(Car*)car; -(void)driver; @end Person.m #import "Person.h" @implementation Person 7-【掌握】多个对象内存管理(野指针) 分区 第一天(@传智如意大师) 的第 23 页 -(void)dealloc{ NSLog(@"Person被销毁"); [super dealloc]; } -(void)setCar:(Car*)car{ _car = car; } -(Car*)car{ return _car; } -(void)driver{ NSLog(@"开车去拉萨!"); [_car run]; } @end Car.h #import @interface Car : NSObject -(void)run; @end Car.m #import "Car.h" @implementation Car -(void)dealloc{ NSLog(@"Car被销毁"); [super dealloc]; } -(void)run{ NSLog(@"车正在跑"); } @end 分区 第一天(@传智如意大师) 的第 24 页 一种看似合理的解决方案: 分区 第一天(@传智如意大师) 的第 25 页 一句代码引发的bug p对象在没销毁之前,可以任意多次调用自己的对象方法,但是此时会报错(注意开启僵尸对象检 测).... 解决方法 分区 第一天(@传智如意大师) 的第 26 页 -(void)setCar:(Car*)car{ _car = [car retain]; //如果车对象先释放了,人就不能开车了 } 分区 第一天(@传智如意大师) 的第 27 页 本小节知识点: 1、【掌握】多个对象内存泄漏问题 1、多个对象内存泄漏问题 思考&实现1: 凤姐坐车去拉萨 Person.h #import #import "Car.h" @interface Person : NSObject { Car *_car; } -(void)setCar:(Car*)car; -(Car*)car; -(void)driver; @end Person.m #import "Person.h" @implementation Person 8-【掌握】多个对象内存管理 分区 第一天(@传智如意大师) 的第 28 页 -(void)dealloc{ NSLog(@"Person被销毁"); [super dealloc]; } -(void)setCar:(Car*)car{ _car = car; } -(Car*)car{ return _car; } -(void)driver{ NSLog(@"开车去拉萨!"); [_car run]; } @end Car.h #import @interface Car : NSObject -(void)run; @end Car.m #import "Car.h" @implementation Car -(void)dealloc{ NSLog(@"Car被销毁"); [super dealloc]; } -(void)run{ NSLog(@"车正在跑"); } @end 分区 第一天(@传智如意大师) 的第 29 页 多次调用driver后,能够正常调用方法,但是Car没有被释放 解决办法: 在保证在p对象存在的时候,car对象一定存在,对象p被销毁的时候,car对象销毁 分区 第一天(@传智如意大师) 的第 30 页 分区 第一天(@传智如意大师) 的第 31 页 本小节知识点: 1、【掌握】set方法存在的问题 set方法写法(一)1) 原对象无法释放造成的内存泄露 -(void)setCar:(Car*)car{ _car = [car retain]; } 1、set方法存在的问题 set方法写法(二)2) -(void)setCar:(Car*)car{ [_car release];//先释放上一个对象,(注意第一次是向nil发送release消息) _car = [car retain]; } 原对象能够释放,但是引起新的问题,set自己的时候,造成的野指针 9-【掌握】set方法内存管理 分区 第一天(@传智如意大师) 的第 32 页 Person *person = [Person new];// person 1 Car *car1 = [Car new]; //car1 1 car1.speed = 100; //把car1给person [person setCar:car1]; //car1 2 //开车 [person driver]; [car1 release]; //car1 1 //重复设置car1 car1.speed = 220; [person setCar:car1]; set方法写法(三)3) 解决上述问题: 判断新传递的对象是否是原来的对象,如果不是原来的对象则释放,然后再retain -(void)setCar:(Car*)car{ //判断_car 存放的是否是 形参对象,如果不是,则执行 [_car realease]; if (_car!=car) { [_car release];//先释放上一个对象,(注意第一次是向nil发送release消息) _car = [car retain]; } 分区 第一天(@传智如意大师) 的第 33 页 } 2、set方法的内存管理 int float double long struct enum -(void)setAge:(int)age _age=age; { } 1)基本数据类型:直接赋值 -(void)setCar:(Car *)car //1.先判断是不是新传进来的对象 If(car!=_car) //2 对旧对象做一次release [_car release];//若没有旧对象,则没有影响 //3.对新对象做一次retain _car=[car retain]; { } { } 2)OC对象类型 分区 第一天(@传智如意大师) 的第 34 页 本小节知识点: 1、【掌握】@property参数(一) @property xcode 4.4 前 @property 帮我们生成 get和set方法的声明 我们自己实现 get和set方法 @property 和 @synthesize 联合 @property xcode 4.4后 @property 增强 @property int age; 1)生成一个_age 2)生成get和set方法的声明 3)生成 get和set方法的实现 @property (......) int age; 1、@property 参数 格式:@property (参数1,参数2) 数据类型 方法名 int _age; -(void)setAge:(int) age{ _age = age; 10-【掌握】@property参数(一) 分区 第一天(@传智如意大师) 的第 35 页 } @property (assign) int age; Car _car; -(void)setCar:(int) car{ if(_car ! = car){ [_car release]; _car = [car retain]; } } @property (retain) Car *car ; 1)内存管理相关参数 retain:对对象release旧值,retain新值(适用于OC对象类型) assign:直接赋值(默认,适用于非oc对象类型) copy:release旧值,copy新值 验证assign如果作用在对象上,实际上就是直接赋值 //使用@property增强型 生成get和set方法 @property(nonatomic,assign)Car *car; /* .m文件中实际上生成的是 _car = car; //当对象release后,将无法使用该对象 */ 分区 第一天(@传智如意大师) 的第 36 页 //使用@property增强型 生成get和set方法 @property(nonatomic,retain)Car *car; /* .m文件中实际上生成的是 if(_car != car){ [_car release]; //释放旧对象 _car = [car retain]; //Person.m的dealloc方法中注释掉 [_car release]; //观察是否存在内存泄露 } */ 证明set方法中确实让对象retain了 分区 第一天(@传智如意大师) 的第 37 页 证明set方法中确实让对象retain了 证明set方法中存在if(_car != car) car 和 car2两个对象都被释放了,说明在多个对象set的时候先 release旧对象,然后retain新对象 即 [_car release]; 存在set方法中! 分区 第一天(@传智如意大师) 的第 38 页 分区 第一天(@传智如意大师) 的第 39 页 本小节知识点: 1、【掌握】@property参数(二) 1、@property 参数(二) 1)是否要生成set方法(若为只读属性,则不生成) readonly:只读,只会生成getter的声明和实现 readwrite:默认的,同时生成setter和getter的声明和实现 2)多线程管理(苹果在一定程度上屏蔽了多线程操作) nonatomic:高性能,一般使用这个 atomic:低性能,默认 3)set和get方法的名称 修改set和get方法的名称,主要用于布尔类型。因为返回布尔类型的方法名一般以is开头,修改名称一般用 在布尔类型中的getter。 @property(nonatomic,assign, setter=abc:,getter=haha)int age 可以理解为把[p setAge: ]------> [p abc:], [p age] ---------> [p haha]; @property(nonatomic,assign, setter=setVip:,getter=isVip) BOOL vip; 用法: 设置值 获取值 11-【掌握】@property参数(二) 分区 第一天(@传智如意大师) 的第 40 页 获取值 分区 第一天(@传智如意大师) 的第 41 页 思考&实现1: 电商App类的设计 要求:利用OC+面向对象设计下面的三个类: 商品名称 单价 重量 商品展示图片 生产日期(暂时用结构体表示)produceDate 过期日期 expireDate 属性: 一、商品类-Goods 姓名 性别(不要用 int,用枚举) 年龄 身高(单位:cm) 属性: 二、买家类(用户) Buyer 姓名 性别(不要用int,用枚举) 年龄 身高(单位:cm) 所出售商品(假设一个卖家就卖一件商品) 属性: 三、卖家类 - Seller 四、买家类、卖家类(抽象父类Person) 五、在main函数中创建卖家、商品、买家类的对象。 // 创建一个表示日期时间的结构体 int year; int month; int day; typedef struct { 12-【理解】应用:电商App练习 分区 第一天(@传智如意大师) 的第 42 页 int day; int hour; int minute; int second; } CZDate; CZDate produceDate = (CZDate){2011, 9, 10, 15, 16, 30}; // 创建一个表示性别的枚举 typedef enum { GenderMale, // 男 GenderFemale // 女 } SteveGender; 分区 第一天(@传智如意大师) 的第 43 页 本小节知识点: 1、【理解】@class的使用 1、@class的使用 作用 可以简单地引用一个类 简单使用 @class Dog; //类的引入 仅仅是告诉编译器: Dog是一个类; 并不会包含Dog这个类的所有内容 具体使用 在.h文件中使用@class引用一个类 在.m文件中使用#import包含这个类的.h文件 如下面代码: A.h文件 #import "B.h" @interface A : NSObject { B *b; } @end 为了简单起见:A类是引用类,B类是被引用类,这里先不考虑A类的实现文件。 通常引用一个类有两种办法: 一种是通过#import方式引入;另一种是通过@class引入; 这两种的方式的区别在于: 1)#import方式会包含被引用类的所有信息,包括被引用类的变量和方法;@class方式只是告诉 编译器在A.h文件中 B *b 只是类的声明,具体这个类里有什么信息,这里不需要知道,等实现文 件中真正要用到时,才会真正去查看B类中信息; 13-【理解】@class的使用 分区 第一天(@传智如意大师) 的第 44 页 2)使用@class方式由于只需要只要被引用类(B类)的名称就可以了,而在实现类由于要用到被 引用类中的实体变量和方法,所以需要使用#import来包含被引用类的头文件; 3)通过上面2点也很容易知道在编译效率上,如果有上百个头文件都#import了同一 个文件,或 者这些文件依次被#improt(A->B, B->C,C->D…),一旦最开始的头文件稍有改动,后面引用到这 个文件的所有类都需要重新编译一遍,这样的效率也是可想而知的,而相对来 讲,使用@class方 式就不会出现这种问题了; 所以:我们实际开发中尽量在.h头文件中使用@class 分区 第一天(@传智如意大师) 的第 45 页 由上可知,@class是放在interface中的,只是在引用一个类,将这个被引用类作为一个类型,在 实现文件中,如果需要引用到被引用类的实体变量或者方法时,还需要使用#import方式引入被引 用类。 #import "A.h" #import "B.h" @implementation A ...... 如: 4)对于循环依赖关系来说,比方A类引用B类,同时B类也引用A类,B类的代码: 当程序运行时,编译会报错, 分区 第一天(@传智如意大师) 的第 46 页 当使用@class在两个类相互声明,就不会出现编译报错。 把其中的一个头文件中的import换成@class 思考&实现1: 面试题#import和@class的区别。 作用上的区别 #import会包含引用类的所有信息(内容), 包括引用类的变量和方法 @class仅仅是告诉编译器有这么一个类, 具体这个类里有什么信息, 完全不知 效率上的区别 如果有上百个头文件都#import了同一个文件,或者这些文件依次被#import,那么一旦最开始的头 文件稍有改动,后面引用到这个文件的所有类都需要重新编译一遍 , 编译效率非常低 相对来讲,使用@class方式就不会出现这种问题了 分区 第一天(@传智如意大师) 的第 47 页 本小节知识点: 1、【理解】循环retain的问题 1、循环retain的使用 看如下问题: Dog.h Person.h main.m //创建对象 Dog *d = [Dog new]; Person *p = [Person new]; //循环引用 p.dog = d; d.owner = p; //看似正确的释放代码 [d release]; [p release]; 程序执行结果: p和d都没有被释放掉 原理分析: 14-【理解】循环retain问题 分区 第一天(@传智如意大师) 的第 48 页 p.dog = d; 因为dog的set方法中是 进行了 [dog retain]; //dog 2 d.owner = p; //会让p的引用计数+1 //owner 2 当执行了 [d release]; [p release]; 后 dog和owner 的引用计数变成1 如图: 学习误区 [d release]; [p release]; //d retainCount 1 p 1 分区 第一天(@传智如意大师) 的第 49 页 [d release]; //p [p release]; 循环retain的场景 比如A对象retain了B对象,B对象retain了A对象 循环retain的弊端 这样会导致A对象和B对象永远无法释放 循环retain的解决方案 当两端互相引用时,应该一端用retain、一端用assign 分区 第一天(@传智如意大师) 的第 50 页 本小节知识点: 1、【了解】NSString 等Foundation框架供的类的内存管理 2、【了解】几个危险操用法 1、NSString 等Foundation框架中类的内存管理 先看看以下这几种写法: NSString *testStr1 = @"a"; NSString *testStr2 = [NSString stringWithString:@"a"]; NSString *testStr3 = [NSString stringWithFormat:@"b"]; NSString *testStr4 = [[NSString alloc] initWithString:@"c"]; NSString *testStr5 = [[NSString alloc] initWithFormat:@"d"]; NSString *testStr6 = [[NSString alloc] init]; NSLog(@"testStr1 ->%p",testStr1); NSLog(@"testStr2 ->%p",testStr2); NSLog(@"testStr3 ->%p",testStr3); NSLog(@"testStr4 ->%p",testStr4); NSLog(@"testStr5 ->%p",testStr5); NSLog(@"testStr6 ->%p",testStr6); 15-【了解】NSString 类的内存管理问题 分区 第一天(@传智如意大师) 的第 51 页 通过对比地址可以看到,从上可以看出,testStr1,testStr2,testStr4都是在一个内存区域,也 就是常量内存区, 引用计数: 1---> NSString *str = [[NSString alloc] initWithString:@"ABC"]; 2---> str = @"123"; 3---> [str release]; 4---> NSLog(@"%@",str); 首先,咱们先对这段代码进行分析。 第一句 声明了一个NSString类型的实例 str, 并将其初始化init后赋值为@"ABC" 第二行,将str的指针指向了一个常量@"123"。 理论上讲在第一行初始化的@"ABC"没有任何任何 指针指向了。 所以造成了内存泄露 然后第三行, 将str的引用计数-1 第四行输出str的值 为123. 首先回答为什么不会崩溃, 因为第三行的release 实际上是release了一个常量@"123" 而作为 常量,其默认的引用计数值是很大的(100k+) NSLog(@"retainCount = %tu",[@"123" retainCount]); 最终的输出值会是一个很大很大的数。 所以单单一个release是不会将其释放掉的。 然后再回答这样会不会造成内存泄露。 其实…………理论上讲 会! 但是实际上,Objective-C对NSString类型有特殊照顾。所有的NSString的引用计数器默认初始值 都会非常非常大。 分区 第一天(@传智如意大师) 的第 52 页 查阅了一下官方的文档,第一句就是“Do not use this method.”,后面给出了说明,因为 Autorelease pool的存在,对于内存的管理会相当复杂,retainCount就不能用作调试内存时的依 据了。这样对于第一段的结果就可以理解了,可能系统对于这一个特殊的对象有特殊的处理(没 准framework里面有早就创建了这个对象了),于是我们拿到了一个非常出人意料的结果。 2、危险的用法 while ([a retainCount] > 0) { [a release]; } 如果运行结果正确,那么这是多么幸运的一个人啊! 分区 第一天(@传智如意大师) 的第 53 页 本小节知识点: 1、【了解】autorelease是什么? 2、【理解】为什么会有autorelease? 3、【理解】autorelease的原理? 4、【理解】autorelease什么时候被释放? 5、【理解】autorelease释放的具体原理是什么? 学习引入: Person *p = [[Person alloc] init]; [p release]; [p run]; [p run]; [p run]; 1、自动释放池及autorelease介绍 自动释放池 (1)在iOS程序运行过程中,会创建无数个池子,这些池子都是以栈结构(先进后出)存在的。 (2)当一个对象调用autorelease时,会将这个对象放到位于栈顶的释放池中 自动释放池的创建方式 NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init]; ````````````````` [pool release];//[pool drain];用于mac (1)iOS 5.0以前的创建方式 @autoreleasepool {//开始代表创建自动释放池 ······· (2)iOS5.0以后 16-【掌握】autorelease基本使用 分区 第一天(@传智如意大师) 的第 54 页 }//结束代表销毁自动释放池 autorelease 是一种支持引用计数的内存管理方式 它可以暂时的保存某个对象(object),然后在内存池自己的排干(drain)的时候对其中的每个 对象发送release消息 注意,这里只是发送release消息,如果当时的引用计数(reference-counted)依然不为0,则该 对象依然不会被释放。可以用该方法来保存某个对象,也要注意保存之后要释放该对象。 2、为什么会有autorelease? OC的内存管理机制中比较重要的一条规律是:谁申请,谁释放 考虑这种情况,如果一个方法需要返回一个新建的对象,该对象何时释放? 方法内部是不会写release来释放对象的,因为这样做会将对象立即释放而返回一个空对象;调用 者也不会主动释放该对象的,因为调用者遵循“谁申请,谁释放”的原则。那么这个时候,就发 生了内存泄露。 不使用autorelease存在的问题 针对这种情况,Objective-C的设计了autorelease,既能确保对象能正确释放,又能返回有效的 对象。 使用autorelease的好处 (1)不需要再关心对象释放的时间 (2)不需要再关心什么时候调用release 3、autorelease基本用法 基本用法 (1)会将对象放到一个自动释放池中 (2)当自动释放池被销毁时,会对池子里的所有对象做一次release (3)会返回对象本身 (4)调用完autorelease方法后,对象的计数器不受影响(销毁时影响) 在autorelease的模式下,下述方法是合理的,即可以正确返回结果,也不会造成内存泄露 ClassA *Func1() 分区 第一天(@传智如意大师) 的第 55 页 ClassA *Func1() { ClassA *obj = [[[ClassA alloc]init]autorelease]; return obj; } 3、autorelease是什么原理? autorelease实际上只是把对release的调用延迟了,对于每一个Autorelease,系统只是把该 Object放入了当 前的Autorelease pool中,当该pool被释放时,该pool中的所有Object会被调用 Release。 4、autorelease何时释放? 对于autorelease pool本身,会在如下两个条件发生时候被释放(详细信息请参见第5条) 1)手动释放Autorelease pool 2)Runloop结束后自动释放 对于autorelease pool内部的对象 在引用计数的retain == 0的时候释放。release和autorelease pool 的 drain都会触发retain-- 事件。 5、autorelease释放的具体原理是什么? 要搞懂具体原理,则要先要搞清楚autorelease何时会创建。 我们的程序在main()调用的时候会自动调用一个autorelease,然后在每一个Runloop, 系统会隐 式创建一个Autorelease pool,这样所有的release pool会构成一个象CallStack一样的一个栈式 结构,在每一个Runloop结束时,当前栈顶的 Autorelease pool(main()里的autorelease)会被 销毁,这样这个pool里的每个Object会被release。 可以把autorelease pool理解成一个类似父类与子类的关系,main()创建了父类,每个Runloop自 动生成的或者开发者自定义的autorelease pool都会成为该父类的子类。当父类被释放的时候, 没有被释放的子类也会被释放,这样所有子类中的对象也会收到release消息。 那什么是一个Runloop呢? 一个UI事件,Timer call, delegate call, 一个鼠标事件,键盘按 下(MAC OSX),或者iphone上的触摸事件,异步http连接下后当接收完数据时,都会是一个新的 Runloop。 一般来说,消息循环运行一次是毫秒级甚至微秒级的,因此autorelease的效率仍然是非常高的, 确实是一个巧妙的设计。 分区 第一天(@传智如意大师) 的第 56 页 ================================ 今天的主要知识点 ================================ 1. 内存管理概念理解 2. 引用计数器 3. dealloc方法 4. 多对象内存管理 5. @property的修饰关键字 6. @class用法 7. 内存管理时的循环retain问题 8. 对象自动释放池的使用 ================================ 今天的主要知识点 ================================ ------------------------------- 知识点细节 ---------------------------- 1. 内存管理的范围 * 所有的OC对象(继承自NSObject类) 2. 为什么内存管理只管理OC对象? * 堆中内存不连续, 无法自动释放 3. 我们如何对OC对象进行内存管理? * 通过操作对象的"引用计数器" 4. 引用计数器 * 什么是引用计数器? 1> 每个OC对象都有自己的引用计数器 17-【了解】内容总结 分区 第一天(@传智如意大师) 的第 57 页 1> 每个OC对象都有自己的引用计数器 2> 它是一个整数(int类型, 占用4个字节) 3> 从字面上, 可以理解为"对象被引用的次数" 4> 也可以理解为: 它表示有多少人正在用这个对象 * 引用计数器的作用? 1> 系统通过"引用计数器"来判断当前对象是否可以被释放 * 对象的"引用计数器"的操作方式 1> retain, +1 2> release, -1 3> retainCount, 获取对象引用计数器的值 5. 关闭ARC的方法 * 选中项目 -> 选中Build Settings -> 选中All -> 搜索 Automatic Reference Counting -> 修改为No 6. dealloc方法 * 当对象即将被销毁, 系统自动给对象发送一条dealloc消息 * 因此, 从dealloc方法有没有被调用, 就可以判断出对象是否被销毁 * 重写了dealloc方法, 必须调用[super dealloc], 并且放在最后面调用 * 不要自己直接调用dealloc方法 7. 野指针\空指针\僵尸对象 * 僵尸对象: 已经被销毁的对象(不能再使用的对象) * 野指针: 指向僵尸对象(不可用内存)的指针 * 空指针: 没有指向存储空间的指针(里面存的是nil, 也就是0) ** 注意: 给空指针发消息是没有任何反应的, 不会示出错 8. 开启僵尸对象监控 * Edit Scheme -> Diagnostics -> Enable Zombie Objects 9. 多对象内存管理 -(void)setCar:(Car*)car{ _car = car; 分区 第一天(@传智如意大师) 的第 58 页 _car = car; } 对象已经释放了,再次去使用 [car release]; [p driver]; // _car = [car retain]; 给p.car 设置不同对象的时候会造成原对象内存泄露 p.car = car1; [car1 release]; //car1 1 p.car = car2; // [_car release]; _car = [car retain]; // p.car = car1; //car1 2 [car1 release]; // 1 p.car = car1; // if(_car != car){_car release]; _car = [car retain];} * set方法的内存管理 * dealloc方法的内存管理 -(void)setCar:(Car*)car{ if( _car != car){ [_car release]; _car = [car retain]; } } -(void)dealloc{ [_car release]; [super dealloc]; } 10. 苹果官方的内存管理原则: * 谁创建谁release, 如果你通过alloc、new或copy、mutableCopy来创建一个对象, 那么你必须 调用release或autorelease * 谁retain谁release, 只要你调用了retain, 就必须调用一次release 分区 第一天(@传智如意大师) 的第 59 页 11. @property的修饰关键字 1> 控制set方法的内存管理 * retain: release旧值,retain新值(用于OC对象),要配合nonatomic使用 * assign: 直接赋值, 不做任何内存管理(默认, 用于非OC对象类型) * copy: release旧值, copy新值(一般用于NSString *) 2> 控制是否需要生成set方法 * readwrite: 同时生成set方法和get方法(默认) * readonly: 只会生成get方法 3> 多线程管理 * atomic: 性能低(默认) * nonatomic: 性能高(为iOS系统开发软件建议使用,为mac开发软件可以使用atomic) 4> 控制set方法和get方法的名称 * setter: 设置set方法的名称, 一定有个冒号: * getter: 设置get方法的名称 12. @class用法 * 为什么要使用@class .h @class Dog .m #import "Dog.h" * @class和#import的区别 * 通过@class解决循环依赖问题 13. 内存管理时的循环retain问题 * 解决办法: 一端用retain、一端用assign ** 注意: 使用了assign后, dealloc中就不需要release了 ------------------------------- 知识点细节 ---------------------------- 分区 第一天(@传智如意大师) 的第 60 页 18-【了解】作业布置 分区 第一天(@传智如意大师) 的第 61 页 1-【理解】知识回顾 分区 第二天(@传智如意大师) 的第 62 页 本小节知识点: 1、【掌握】autorelease使用注意 2、【理解】autorelease错误用法 1)并不是放到自动释放池代码中,都会自动加入到自动释放池 因为没有调用 autorelease 方法,所以对象没有加入到自动释放池 在自动释放池的外部发送autorelease 不会被加入到自动释放池中 1、autorelease使用注意 2-【掌握】autorelease注意及错误用法 分区 第二天(@传智如意大师) 的第 63 页 不管这个对象是在自动释放池内还是外创建的,只要在自动释放池内写一个[p1 autorelease];p1就会被放到自动释放池中。注意autorelease是一个方法,且只有在自动释 放池中使用才有效。 2)自动释放池的嵌套使用 //不管再自动释放池内、还是外部创建爱 Person *p = [Person new]; [p retain]; //自动释放池的嵌套 @autoreleasepool { @autoreleasepool { [p autorelease]; //此时p被加入到自动释放池 } //再次将p加入到另一个自动释放池 [p autorelease]; } 分区 第二天(@传智如意大师) 的第 64 页 自动释放池栈 @autoreleasepool { @autoreleasepool { @autoreleasepool { [p autorelease]; //此时p被加入到自动释放池 } } //再次将p加入到另一个自动释放池 [p autorelease]; } 3)自动释放池中不适宜放占用内存比较大的对象 1)尽量避免对大内存使用该方法,对于这种延迟释放机制,还是尽量少用 分区 第二天(@传智如意大师) 的第 65 页 1)尽量避免对大内存使用该方法,对于这种延迟释放机制,还是尽量少用 2)不要把大量循环操作放到同一个 @autoreleasepool 之间,这样会造成内存峰值的上升 (1)连续调用多次autorelease,释放池销毁时执行两次release(-1吗?) (2)Alloc之后调用了autorelease,之后又调用了release。 //不管再自动释放池内、还是外部创建爱 Person *p = [Person new]; @autoreleasepool { //再次将p加入到另一个自动释放池 [p autorelease]; } [p release]; 2、autorelease错误用法 分区 第二天(@传智如意大师) 的第 66 页 本小节知识点: 1、【掌握】autorelease的应用场景 2、【掌握】完善快速创建对象的方法 1、autorelease的应用场景 经常用来在类方法中快速创建一个对象 person.h +(Person *)person; //声明类方法 person.m -(Person *)person{ return [[[Person alloc] init] autorelease]; } 应用: @autoreleasepool { //再自动释放池中使用类方法快速创建对象 Person *p = [Person person]; [p run]; } 误区: 3-【掌握】autorelease 的应用场景 分区 第二天(@传智如意大师) 的第 67 页 2、完善快速创建对象的方法 问题1: 如果定义一个学生类Student,继承自Person, 此时还能使用快速创建对象的方法吗?能否调用学生类的run方法 解决办法: 分区 第二天(@传智如意大师) 的第 68 页 把Person alloc -----> self alloc 此处self指代的是方法的调用者 [Person person]; self--->Person 如果[Student person] 此处 self ---> Student 问题2: NSString *str = [Person person]; NSLog(@"%zd",str.length); 这是一个运行时错误,显然不够友好 改进: 这样编译时就会看到警告 不至于到程序运行时才发现这个错误 instancetype 可以智能判断返回的类型和接受的类型是否一致 分区 第二天(@传智如意大师) 的第 69 页 正常使用: 分区 第二天(@传智如意大师) 的第 70 页 思考&实现1: 创建一个学生类Student ,通过重写构造方法实现创建学生对象的时候 默认的年龄值指定的年龄 -(instancetype) initWithXXX:(int)age; student.h #import @interface Student : NSObject @property(nonatomic,assign) int age; -(instancetype)initWithAge:(int)age; +(instancetype)studentWithAge:(int)age; @end student.m #import "Student.h" @implementation Student - (void)dealloc { NSLog(@"Student 被释放"); [super dealloc]; } //自定义构造方法 -(instancetype)initWithAge:(int)age{ if (self = [super init]) { _age = age; } return self; } //自定义初始化方法 +(instancetype)studentWithAge:(int)age{ return [[[self alloc] initWithAge:age] autorelease]; } @end 4-【理解】应用:创建一个学生类初始化年龄 分区 第二天(@传智如意大师) 的第 71 页 main.m #import #import "Student.h" int main(int argc, const char * argv[]) { @autoreleasepool { // Student *stu = [[[Student alloc] initWithAge:10] autorelease]; Student *stu1 = [Student studentWithAge:100]; NSLog(@"age = %d",stu1.age); } return 0; } 分区 第二天(@传智如意大师) 的第 72 页 本小节知识点: 1、【了解】指针分类 2、【了解】什么是ARC? 3、【理解】ARC工作原理 4、【理解】ARC机制图解 (1)强指针:默认的情况下,所有的指针都是强指针,关键字strong (2)弱指针:_ _weak关键字修饰的指针 声明一个弱指针如下: _ _weak Person *p; 1、指针分类 2、什么是ARC? Automatic Reference Counting,自动引用计数,即ARC,可以说是WWDC2011和iOS5所引入 的最大的变革和最激动人心的变化。ARC是新的LLVM 3.0编译器的一项特性,使用ARC,可以说一 举解决了广大iOS开发者所憎恨的手动内存管理的麻烦。 在工程中使用ARC非常简单:只需要像往常那样编写代码,只不过永远不写retain,release和 autorelease三个关键字就好~这是ARC的基本原则。 当ARC开启时,编译器将自动在代码合适的地方插入retain, release和autorelease,而作为 开发者,完全不需要担心编译器会做错(除非开发者自己错用ARC了)。 手动管理内存, 可以简称MRC (Manual Reference Counting) ARC与其他语言的”垃圾回收”机制不同。ARC:编译器特性;“垃圾回收”运行时特性 3、ARC工作原理及判断准则 ARC是Objective-C编译器的特性,而不是运行时特性或者垃圾回收机制,ARC所做的只不过是 在代码编译时为你自动在合适的位置插入release或autorelease, 只要没有强指针指向对象,对象就会被释放。 注意:当使用ARC的时候,暂时忘记“引用计数器”,因为判断标准变了。 ARC的判断准则: 5-【掌握】ARC概念及原理 分区 第二天(@传智如意大师) 的第 73 页 4、ARC机制图解 NSString *firstName = @"oneV"; 这个时候firstName持有了@"OneV"。 当然,一个对象可以拥有不止一个的持有者(这个类似MRC中的retainCount>1的情况)。在这个 例子中显然self.textField.text也是@“OneV",那么现在有两个指针指向对象@"OneV”(被持有 两次,retainCount=2,其实对NSString对象说retainCount是有问题的,不过anyway~就这个意 思而已.)。 过了一会儿,也许用户在textField里输入了其他的东西,那么self.textField.text指针显然现 在指向了别的字符串,比如@“onevcat",但是这时候原来的对象已然是存在的,因为还有一个指 针firstName持有它。现在指针的指向关系是这样的: 分区 第二天(@传智如意大师) 的第 74 页 只有当firstName也被设定了新的值,或者是超出了作用范围的空间(比如它是局部变量但是这个 方法执行完了或者它是实例变量但是这个实例被销毁了),那么此时firstName也不再持有 @“OneV",此时不再有指针指向@"OneV",在ARC下这种状况发生后对象@"OneV"即被销毁,内存释 放。 类似于firstName和self.textField.text这样的指针使用关键字strong进行标志,它意味着只要 该指针指向某个对象,那么这个对象就不会被销毁。反过来说,ARC的一个基本规则即是,只要某 个对象被任一strong指针指向,那么它将不会被销毁。如果对象没有被任何strong指针指向,那 么就将被销毁。在默认情况下,所有的实例变量和局部变量都是strong类型的。可以说strong类 型的指针在行为上和MRC时代retain的property是比较相似的。 既然有strong,那肯定有weak咯~weak类型的指针也可以指向对象,但是并不会持有该对象。比 如: __weak NSString *weakName = self.textField.text 得到的指向关系是: 分区 第二天(@传智如意大师) 的第 75 页 这里声明了一个weak的指针weakName,它并不持有@“onevcat"。如果self.textField.text的内 容发生改变的话,根据之前到的"只要某个对象被任一strong指针指向,那么它将不会被销毁。 如果对象没有被任何strong指针指向,那么就将被销毁”原则,此时指向@“onevcat"的指针中没 有strong类型的指针,@"onevcat"将被销毁。 同时,在ARC机制作用下,所有指向这个对象的weak指针将被置为nil。这个特性相当有用,相信 无数的开发者都曾经被指针指向已释放对象所造成的EXC_BAD_ACCESS困扰过,使用ARC以后,不论 是strong还是weak类型的指针,都不再会指向一个dealloced的对象,从根源上解决了意外释放导 致的crash。 不过在大部分情况下,weak类型的指针可能并不会很常用。比较常见的用法是在两个对象间存在 包含关系时:对象1有一个strong指针指向对象2,并持有它,而对象2中只有一个weak指针指回对 象1,从而避免了循环持有。 一个常见的例子就是oc中常见的delegate设计模式,viewController中有一个strong指针指向它 所负责管理的UITableView,而UITableView中的dataSource和delegate指针都是指向 viewController的weak指针。可以说,weak指针的行为和MRC时代的assign有一些相似点,但是考 虑到weak指针更聪明些(会自动指向nil),因此还是有所不同的。 细节的东西我们稍后再说。 分区 第二天(@传智如意大师) 的第 76 页 分区 第二天(@传智如意大师) 的第 77 页 本小节知识点: 1、【了解】ARC机制判断 2、【了解】ARC快速使用 1、ARC机制判断 iOS5以后,创建项目默认的都是ARC ARC机制下有几个明显的标志: 1) 不允许调用对象的 release方法 2)不允许调用 autorelease方法 3) 再重写父类的dealloc方法时,不能再调用 [super dealloc]; 6-【理解】ARC快速入门 分区 第二天(@传智如意大师) 的第 78 页 2、ARC机制快速使用 分区 第二天(@传智如意大师) 的第 79 页 本小节知识点: 1、【掌握】ARC下单对象内存管理 2、【掌握】强弱指针 1、ARC工作原理详述 ARC是Objective-C编译器的特性,而不是运行时特性或者垃圾回收机制,ARC所做的只不过 是在代码编译时为你自动在合适的位置插入release或autorelease, 只要没有强指针指向对象,对象就会被释放。 注意:当使用ARC的时候,暂时忘记“引用计数器”,因为判断标准变了。 ARC的判断准则: car 不赋值nil为什么也能被释放? 7-【掌握】ARC下单对象内存管理 分区 第二天(@传智如意大师) 的第 80 页 2、强弱指针 1)强指针 所有的指针默认就是强指针 强指针使用__strong 标识 2)弱指针 所有的指针默认就是强指针 弱指针使用__week 标识 分区 第二天(@传智如意大师) 的第 81 页 注意:p1是一个若指针,所以当空间释放后,p1也被设置为了nil 所以,此时再次使用 p.age 即便是开启了僵尸对象检测,也不会报错的。 分区 第二天(@传智如意大师) 的第 82 页 本小节知识点: 1、【掌握】ARC下多对象内存管理 1、ARC下多个内存管理 person.h person.m #import "Person.h" @implementation Person - (void)dealloc { NSLog(@"Person dealloc"); } @end main.m Person *p = [Person new]; Dog *d = [Dog new]; //设置人拥有dog p.dog = d; //设置狗为nil d = nil; //此句话执行后,狗会被立即释放 NSLog(@"%@,%@",p,p.dog); 如图: 8-【掌握】ARC下多对象内存管理 分区 第二天(@传智如意大师) 的第 83 页 如果换成 强指针 @property (nonatomic,strong) Dog *dog; 分区 第二天(@传智如意大师) 的第 84 页 分区 第二天(@传智如意大师) 的第 85 页 本小节知识点: 1、【掌握】ARC下循环引入问题 2、【掌握】ARC下@property 参数 1、ARC下循环引用问题 person.h #import @class Dog; @interface Person : NSObject //dog是strong 强指针 @property (nonatomic,strong) Dog *dog; @end person.m #import @class Person; @interface Dog : NSObject //狗的主人 也是strong 强指针 @property (nonatomic,strong) Person *owner; @end main.m Person *p = [Person new]; Dog *d = [Dog new]; 9-【掌握】ARC下循环引用问题 分区 第二天(@传智如意大师) 的第 86 页 当p.dog = d后,仍然能够正常释放 当增加d.owner = p;时形成循环引用 分区 第二天(@传智如意大师) 的第 87 页 造成循环引入 解决方案: 循环引入的对象中其中一个对象设置为 strong 另一个设置为 weak 2、ARC下@property参数 ARC中的@property strong : 用于OC对象, 相当于MRC中的retain weak : 用于OC对象, 相当于MRC中的assign assign : 用于基本数据类型, 跟MRC中的assign一样 copy : 一般用于NSString, 跟MRC中的copy一样 在ARC情况下解决”循环retain”的问题:@property一边用strong,一边用weak。 分区 第二天(@传智如意大师) 的第 88 页 本小节知识点: 1、【了解】ARC特点总结 2、【了解】ARC使用注意事项 1、ARC特点总结 (1)不允许调用release,retain,retainCount (2)允许重写dealloc,但是不允许调用[super dealloc] strong:相当于原来的retain(适用于OC对象类型),成员变量是强指针 weak:相当于原来的assign,(适用于oc对象类型),成员变量是弱指针 assign:适用于非OC对象类型(基础类型) (3)@property的参数: 2、ARC使用注意事项 1)ARC中,只要弱指针指向的对象不在了,就直接把弱指针做清空(赋值为nil)操作。 2)__weak Person *p=[[Person alloc] init];//不合理,对象一创建出来就被释放掉,对象释 放掉后,ARC把指针设置为nil。 3)ARC中在property处不再使用retain,而是使用strong,在dealloc中不需要再 [super dealloc]。 @property(nonatomic,strong)Dog *dog; // 意味着生成的成员变量_dog是一个强指针,相当于以前的retain。 4)如果换成是弱指针,则换成weak,不需要加_ _。 10-【了解】ARC使用特点及注意事项 分区 第二天(@传智如意大师) 的第 89 页 本小节知识点: 1、【了解】ARC模式下如何兼容非ARC的类 2、【了解】将MRC转换为ARC 1、ARC模式下如何兼容非ARC的类 让程序兼容ARC和非ARC部分。转变为非ARC -fno-objc-arc 转变为ARC的, -f-objc-arc 。 ARC也需要考虑循环引用问题:一端使用strong,另一端使用week。 2、将MRC转换为ARC 11-【掌握】ARC的兼容和转换 分区 第二天(@传智如意大师) 的第 90 页 ARC也需要考虑循环引用问题:一端使用strong,另一端使用week。 示:字符串是特殊的对象,但不需要使用release手动释放,这种字符串对象默认就是autorelease 的,不用额外的去管内存。 如果一个项目是MRC的,那么我们可以吧这个项目转换为ARC的 切换target 把项目改成 MRC的 通过Xcode菜单修改 分区 第二天(@传智如意大师) 的第 91 页 选中要转换的Target 分区 第二天(@传智如意大师) 的第 92 页 转换成功后,项目编程了ARC的了 分区 第二天(@传智如意大师) 的第 93 页 本小节知识点: 1、【了解】类别的概念及作用 2、【了解】类别的使用流程 分类、类目、Category 学习引入: @interface Person :NSObject -(void)run; -(void)eat; @end -(void)playlol; 1、类别的概念及作用 类别概念 Category有很多种翻译: 分类 \ 类别 \ 类目 (一般叫分类) Category是OC特有的语法, 其他语言没有的语法(类似于C#语言中的”扩展方法”和”partial” 关键字) 类别的作用 在不修改原有的类的基础上增加新的方法 一个庞大的类可以分模块开发 一个庞大的类可以由多个人来编写,更有利于团队合作 使用类别的目的 1)对现有类进行扩展: 比如,你可以扩展Cocoa touch框架中的类,你在类别中增加的方法会被子类所继承,而且在运行 时跟其他的方法没有区别。 2)作为子类的替代手段: 不需要定义和使用一个子类,你可以通过类别直接向已有的类里增加方法。 3)对类中的方法归类: 利用category把一个庞大的类划分为小块来分别进行开发,从而更好的对类中的方法进行更新和 维护。 12-【掌握】分类(Category)概念及使用流程 分区 第二天(@传智如意大师) 的第 94 页 维护。 2、使用类别的步骤 先声明类别--->实现类别--->使用类别 Person base -- 张三 (Person+base) eat run playgame-- 李四 (Person+playgame) playlol,playdota study --麻子 (Person+study) studyc studyios 注意:类别的命名规则:类名+扩展方法,如“NSString+countNum”。 类别的接口声明与类的定义十分相似,但类别不继承父类,只需要带有一个括号,表明该类别的 主要用途。 分区 第二天(@传智如意大师) 的第 95 页 本小节知识点: 1、【了解】声明和实现一个类别 2、【了解】调用类别中的方法 1、声明和实现一个类别 在.h文件中,声明类别: @interface ClassName (CategoryName) NewMethod; //在类别中添加方法 //不允许在类别中添加变量 @end 说明: 声明类别格式 1)新添加的方法必须写在 @interface 与 @end 之间 2)ClassName 现有类的类名(要为哪个类扩展方法) 3)CategoryName 待声明的类别名称 4)NewMethod 新添加的方法 注意: 1)不允许在声明类别的时候定义变量 在.m文件中(也可以在.h中实现),实现类别: @implementation ClassName(CategoryName) NewMethod { …… } @end 说明: 实现类别格式 1)新方法的实现必须写在 @ implementation与@end之间 2)ClassName 现有类的类名 3)CategoryName 待声明的类别名称 13-【掌握】分类(Category)声明和实现 分区 第二天(@传智如意大师) 的第 96 页 3)CategoryName 待声明的类别名称 4)NewMethod 新添加的方法的实现 也可以通过图形界面生成类别 2、调用类别中的方法 与一般成员方法调用形式,完全一样 需要包含类别的头文件 调用类别中的方法 分区 第二天(@传智如意大师) 的第 97 页 本小节知识点: 1、【了解】分类的使用注意事项 2、【了解】分类的编译的顺序 1、分类的使用注意事项 1)分类只能增加方法, 不能增加成员变量、@property(可能编译不报错,但是运行有问题) @interface Person (study) @property int age; -(void)study; -(void)work; @end #import #import "Person.h" #import "Person+study.h" int main(int argc, const char * argv[]) { @autoreleasepool { Person *p = [Person new]; [p eat]; [p run]; p.age = 10; } return 0; } 编译不报错,运行报错 2)分类可以访问原来类中的成员变量 14-【掌握】分类(Category)使用注意事项 分区 第二天(@传智如意大师) 的第 98 页 2)分类可以访问原来类中的成员变量 3)如果分类和原来类出现同名的方法, 优先调用分类中的方法, 原来类中的方法会被忽略 分区 第二天(@传智如意大师) 的第 99 页 2、分类的编译顺序 最下面的是最后编译,当多个分类中有同名方法,则执行最后编译的文件方法 分区 第二天(@传智如意大师) 的第 100 页 本小节知识点: 1、【了解】非正式协议的概念 1、非正式协议 非正式协议就是给NSObject类创建的类目(分类)又叫做非正式协议, 非正式协议一般不需 要进行实现,一般在子类中进行方法的重写。 思考&实现1: 统计一个字符串中阿拉伯数字的个数 NSString *str = @"ada12sfasdf1213234asfda 32424 121 aa11afdsf"; 思路: 取出字符串的每一个字符 判断字符 是否是 0-9 之间 计数器+1 @interface NSString (count) -(void)countStrNum; @end @implementation NSString (count) -(void)countStrNum{ int count=0; //遍历字符串 for(int i=0;i='0' && c<='9') { //如果是阿拉伯数字,则+1 count++; } } NSLog(@"count = %d",count); } 分区 第二天(@传智如意大师) 的第 102 页 本小节知识点: 1、【了解】类的延展的概念 1、类的延展的概念 延展类别又称为扩展(Extendsion) Extension是Category的一个特例 其名字为匿名(为空),并且新添加的方法一定要予以实现。(Category没有这个限制) @interface Student () @end 2、延展的实现 通过延展来实现方法的私有,延展的头文件独立。这种方法不能实现真正的方法私有,当在别的 文件中引入延展的头文件,那么在这个文件中定义的类的对象就可以直接调用在延展中定义所谓 私有的方法 // 对类的延展 - 隐藏方法 wrap的一种手段(非强制) .h文件 @interface SceneManager () + (void) wrap; @end .m类的实现文件中 @implementation SceneManager + (void) wrap { NSLog(@"method of wrap"); } @end 调用 [SceneManager wrap]; // 这里会抱一个警告:Class method of "+ wrap" not found // 不过虽然是警告,不过运行是正常的,不过这么写在自我规范上不好,即使编译器没有做强制 限制,我们自己也要限制自己。 // 不然,延展方法就毫无意义可言。 16-【理解】分类(Category)延展 分区 第二天(@传智如意大师) 的第 103 页 通过界面创建延展: 第二种实现延展的方式是延展没有独立的头文件,在类的实现文件.m中声明和实现延展,这种方 法可以很好的实现方法的私有,因为在OC中是不能引入.m的文件的 .m文件 @interface SceneManager () + (void) wrap; @end @implementation SceneManager + (void) wrap { NSLog(@"method of wrap"); } @end 调用 [SceneManager wrap]; 第三种实现方法私有的方式是在.m文件中得@implementation中直接实现在@interface中没有声明 的方法,这样也可以很好的实现方法的私有。 分区 第二天(@传智如意大师) 的第 104 页 本小节知识点: 1、【了解】block的基本概念 2、【理解】block的基本用法 1、block的基本概念 Block对象是一个C级别的语法和运行机制。它与标准的C函数类似,不同之处在于,它除了有 可执行代码以外,它还包含了与堆、栈内存绑定的变量。因此,Block对象包含着一组状态数据, 这些数据在程序执行时用于对行为产生影响。 你可以用Block来写一些可以传到API中的函数语句,可选择性地存储,并可以使用多线程。 作为一个回调,Block特别的有用,因为block既包含了回调期间的代码,又包含了执行期间需要 的数据。 由于Objective-C 和 C++ 都是衍生自 C,block被设计为可同时兼容这三种语言。 2、block的基本用法 用^操作符来声明一个Block变量,并指明Block述句的开始。Block的主体部分包含在 {}内,像下 面的例子中一样(与C语法一样,“;”指明语句的结束): block最简单形式1) 定义格式: void (^block名)() = ^{代码块;} 使用格式: 17-【掌握】block的概念及基本使用 分区 第二天(@传智如意大师) 的第 105 页 使用格式: block名(); /** 定义时,把block当成数据类型 特点: 1. 类型比函数定义多了一个 ^ 2. 设置数值,有一个 ^,内容是 {} 括起的一段代码 最简单的定义方式 void (^myBlock)() = ^ { // 代码实现; } */ void (^myBlock)() = ^ { NSLog(@"hello"); }; // 执行时,把block当成函数 myBlock(); block带有参数的block的定义和使用2) 格式: void (^block名称)(参数列表) = ^ (参数列表) { // 代码实现; } //定义有参数的block void (^sumBlock)(int, int) = ^ (int x, int y) { NSLog(@"%d", x + y); }; //调用block sumBlock(10, 20); 带有参数和返回值的block3) 格式: 返回类型 (^block名称)(参数列表) = ^ 返回类型 (参数列表) { // 代码实现; } //定义有返回值和参数的block 分区 第二天(@传智如意大师) 的第 106 页 //定义有返回值和参数的block int (^sumBlock2)(int, int) = ^ int (int a, int b) { return a + b; }; //调用有返回值的block NSLog(@"%d", sumBlock2(4, 8)); 分区 第二天(@传智如意大师) 的第 107 页 ================================ 今天的主要知识点 ================================ 1、对象的自动释放池的使用 2、ARC机制介绍和使用 3、Category分类 4、block ================================ 今天的主要知识点 ================================ ---------------- 知识点细节 ----------------------------------------------- 1. 对象自动释放池的使用 * autorelease方法 * @autoreleasepool关键字的使用 * 封装一个initWithXxx方法, 在其中使用"对象自动释放池" 2. ARC介绍 * ARC概念介绍 * ARC的判断原则/强弱指针介绍 * 循环strong问题 * MRC代码转ARC代码 1. @autoreleasepool的嵌套使用(栈方式来存储) * instancetype只能用作返回值, 不能使用instancetype来声明变量 * id 和 instancetype 的区别 /* id -> NSObject * id类型是可以用来声明变量的 instancetype -> 在哪个类中, 表示的就是哪个类型的指针 XXXX * instancetype 只能用作方法的返回值, 不能通过instancetype来声明变量 */ * 自动释放池是以栈结构来存储的 2. Category分类 * 分类的作用: 1> 将一个类中的不同方法分到多个不同的文件中存储, 便于对类进行模块化设计、团队合作 开发。 18-【了解】内容总结 分区 第二天(@传智如意大师) 的第 108 页 开发。 2> 可以在不修改原来类的基础上, 为这个类扩充一些方法. * 分类中只能增加"方法", 不能增加成员变量、@property等。 * 分类可以访问原来类中的成员变量 * 主类与分类中方法的调用优先级问题: 1> 当分类与主类中都有同一个方法的时候, 优先调用分类中的方法。 2> 当多个分类中都有同样的一个方法的时候, 优先调用最后一个参与编译的分类中的方法。 * 类扩展(匿名分类) 0> 类扩展是写在.m文件中的, 为当前类扩展一些私有的成员. 1> 作用: 为某个类扩充一些私有的成员变量和方法. 2> "类扩展"对比"分类", 就少了一个分类名称, 因此也有人称它为"匿名分类". 3. block * block是一种数据类型, 可以使用这种数据类型定义变量, 并赋值。 * block数据类型在使用前需要先定义该数据类型, 然后再使用(就像使用Person类一样, 先 定义一个Person类, 然后再通过Person类声明一个Person类型变量)。 * block这种数据类型用来保存一个方法、函数、一段代码 * 必须掌握: block的定义语法、使用场景 * 使用inlineBlock辅助编写block代码 //int (^block)(int,int) = ^(int n, int m){}; int (^block)(int,int) = ^(int n, int m){}; -------------------------------- 知识点细节 --------------------------------- 分区 第二天(@传智如意大师) 的第 109 页 19-【了解】作业布置 分区 第二天(@传智如意大师) 的第 110 页 1-【理解】知识回顾 分区 第三天(@传智如意大师) 的第 111 页 本小节知识点: 1、【了解】函数指针回顾 2、【掌握】block的typedef 1、函数指针回顾 1)函数指针使用 函数指针变量定义的一般形式为: 类型说明符 (*指针变量名)(); 其中“类型说明符”表示被指函数的返回值的类型。 “(* 指针变量名)”表示“*”后面的变量是定义的指针变量。 函数指针的使用 int sum(int x,int y){ return x+y; } int jian(int x,int y){ return x-y; } int cheng(int x,int y){ return x*y; } int chu(int x,int y){ return x/y; } 定义函数指针 技巧: 1)把函数的声明拷贝过来 2)把函数名换成 (*函数指针变量名) 3)形参名可写可不写 int (*p)(int x,int y); //其中p是函数指针变量名 2-【掌握】block的typedef 分区 第三天(@传智如意大师) 的第 112 页 int (*p)(int x,int y); //其中p是函数指针变量名 用法:p = sum; p = jian; 调用:p(2,3); 2)函数指针别名 typedef int (*NewType)(int x,int y); NewType f1,f2,f3; //f1实际类型 int (*f1)(int x,int y); 2、block的typedef 利用typedef定义block类型(和指向函数的指针很像) 格式: Typedef int(^MyBlock)(int ,int); 速记符: typedef 返回值类型 (^block变量名)(参数类型列表); 以后就可以利用这种类型来定义block变量了。 //给无参无返回值block变量起别名; //格式: void (^新类型名)(); typedef void (^newType)(); //用新的类型定义block变量 newType t1; t1 = ^{ NSLog(@"我是使用newType定义出来的变量t1的值"); }; //使用block t1(); 分区 第三天(@传智如意大师) 的第 113 页 有参数block别名 有参有返回值block定义新的类型 分区 第三天(@传智如意大师) 的第 114 页 分区 第三天(@传智如意大师) 的第 115 页 本小节知识点: 1、【掌握】block访问外部变量 1、block访问外部变量 1)在block内部可以访问block外部的变量 在block内部可以变量, 但是注意,这是一个新的内存空间变量 block内部也可以定义和block外部的同名的变量(局部变量),此时局部变量会暂时屏蔽外部 变量的作用域 3-【掌握】block访问外部变量 分区 第三天(@传智如意大师) 的第 116 页 2)在block内部不可以修改block外部的变量 默认情况下,Block内部不能修改外部的局部变量 给局部变量加上__block关键字,则这个局部变量可以在block内部进行修改。 分区 第三天(@传智如意大师) 的第 117 页 给局部变量加上__block关键字,则这个局部变量可以在block内部进行修改。 MRC 环境 一、静态变量 和 全局变量 在加和不加 __block 都会直接引用变量地址。也就意味着 可以修 改变量的值。在没有加__block 参数的情况下。 全局block 和 栈block 区别为 是否引用了外部变量,堆block 则是对栈block copy 得来。对全局block copy 不会有任何作用,返回的依然是全局block。 • 二, 常量变量(NSString *a = @"hello";a 为常量变量,@“hello”为常量。)-----不 加__block类型 block 会引用常量的地址(浅拷贝)。加__block类型 block会去引用常量变 量(如:a变量,a = @"abc".可以任意修改a 指向的内容。)的地址。 如果不加__block 直接在block 内部修改变量 ,会编译报错。block 内部改变量是 只读的。 但是 就一定可以推断 block 会深拷贝 该变量吗??? 对于常量 @“hello” 存储在 内存中的常量区, 程序结束才会释放 内存。 如: NSString *str = @"hello"; NSString *abcStr = @"hello"; 分区 第三天(@传智如意大师) 的第 118 页 思考&实现1: 假设一个程序员每天的工作是基本固定的,那么可以分析使用block day1 起床 刷牙 去车站 坐车 了解项目 去车站 坐车回家 吃饭 睡觉 day2 起床 刷牙 去车站 坐车 分析项目 去车站 坐车回家 吃饭 睡觉 day3 起床 刷牙 去车站 坐车 写代码 去车站 坐车回家 吃饭 睡觉 day4 起床 刷牙 4-【理解】应用:block的使用场景1 分区 第三天(@传智如意大师) 的第 119 页 刷牙 去车站 坐车 写调试代码 去车站 坐车回家 吃饭 睡觉 day5 起床 刷牙 去车站 坐车 离职 去车站 坐车回家 吃饭 睡觉 1)常见做法,封装成若干个函数 void day1(); void day2(); .... void day5(); 2)重构和封装 (1)抽取重复 void work(void (^startWrokBlock)()){ NSLog(@"起床"); NSLog(@"刷牙"); NSLog(@"去车站"); NSLog(@"坐车"); // NSLog(@"了解项目"); startWrokBlock(); //block NSLog(@"去车站"); NSLog(@"坐车回家"); NSLog(@"吃饭"); NSLog(@"睡觉"); } (2)每天工作 void daywork(int day){ switch (day) { case 1: 分区 第三天(@传智如意大师) 的第 120 页 case 1: work(^{ NSLog(@"了解项目"); }); break; case 2:...... ....... } } (3)main for (int i=1; i<=5; i++) { daywork(i); } 分区 第三天(@传智如意大师) 的第 121 页 本小节知识点: 1、【理解】block作为函数的返回值 1、block作为函数的返回值 步骤: 1)使用typedef 定义一个新的类型 //给block起一个别名 typedef int(^newType)(int num1,int num2); 2)使用新类型作为函数的返回值 //定义一个返回值是block类型的函数 newType test4(){ //定义一个newType 类型的block变量 newType work1=^(int x,int y){ return x+y; }; return work1; } 3)定义变量接收函数返回的值(block类型) 4)调用block //在main函数中调用返回值是newType类型的函数 newType nt = test4(); NSLog(@"nt = %d",nt(45,23)); 5-【理解】block作为函数的返回值 分区 第三天(@传智如意大师) 的第 122 页 分区 第三天(@传智如意大师) 的第 123 页 思考&实现1: 用返回值是block的函数,修改我们应用场景的代码 1)使用typedef定义新类型 workBlockType wrokFun(int d); 2)定义返回值函数(做了大量判断的) void dayWork(int d){ NSLog(@"------"); NSLog(@"xxxxxx"); workBlockType wt=wrokFun(d); wt(); NSLog(@"xxxxxx"); NSLog(@"xxxxxx"); } 3)修改函数 4)for循环调用 dayWork(i) 6-【理解】应用:block的使用场景2 分区 第三天(@传智如意大师) 的第 124 页 本小节知识点: 1、【掌握】block的使用技巧及注意 1、block使用技巧及注意 block 结构的快速示:1) 输入:inlineBlock 得到如下示 生成如下代码: block变量用作方法的参数的时候2) 最好把参数类型列表部分加上 具体的形参名 直接写代码就可以了 7-【掌握】block使用技巧及注意 分区 第三天(@传智如意大师) 的第 125 页 注意: 分区 第三天(@传智如意大师) 的第 126 页 本小节知识点: 1、【理解】protocol 的概念 2、【掌握】protocol 的使用流程 思考&实现1: 在OC中能否让一个类必须实现某一个方法? 怎么做? Person ——————> run 1、protocol的基本概念 在写java的时候都会有接口interface这个概念,接口就是一堆方法的声明没有实现,而在OC 里面,Interface是一个类的头文件的声明,并不是真正意义上的接口的意思,在OC中,接口是由 一个叫做协议的protocol来实现的。 这个里面可以声明一些方法,和java不同的是,它可以声明一些必须实现的方法和选择实现 的方法。这个和java是完全不同的。 8-【掌握】protocol概念及基本使用 分区 第三天(@传智如意大师) 的第 127 页 2、protocol的使用流程 协议的使用流程: 定义协议----> 在类中采用指定的协议 ---->实现协议中的方法(注意方法有必须实现和选择实现两种) 如图: 1)定义协议 分区 第三天(@传智如意大师) 的第 128 页 协议的定义 @protocol 协议名称 //默认遵守NSObject协议 //方法声明列表 @end; 注意: 协议默认的要采纳NSObject的协议 2)采纳协议 (1)类遵守协议 创建类的时候遵守某个或者某几个协议 @interface 类名 : 父类 <协议名称> @end @interface 类名 : 父类 <协议名称1,协议名称2> @end 某个协议也遵守某个或者某些协议,一个协议可以遵守其他多个协议, (1)协议遵守协议 @protocol 协议名称 <其他协议名称> @end (2)多个协议之间用逗号 , 隔开。 @protocol 协议名称 <其他协议名称1, 其他协议名称2> @end 3)实现协议中的方法 分区 第三天(@传智如意大师) 的第 129 页 本小节知识点: 1、【掌握】protocol 的基本使用 1、protocol的基本使用 1)定义协议 协议的定义 @protocol 协议名称 //方法声明列表 @end; 注意: 协议默认的要采纳NSObject的协议 Run.h #import @protocol Run -(void)eat; -(void)run; @end Work.h #import @protocol Work -(void)myWork; @end 2)采纳协议 注意:采纳协议要先把要采纳的协议引入过来 9-【掌握】protocol的基本使用 分区 第三天(@传智如意大师) 的第 130 页 (1)类遵守协议 某个类遵守某个协议 @interface 类名 : 父类 <协议名称> @end //引入要采纳的协议 #import "Run.h" // 采纳协议 @interface Person : NSObject @end 某个协议遵守某个或者某些协议,一个协议可以遵守其他多个协议, 多个协议之间用逗号 , 隔开。 @protocol 协议名称 <协议名称1,协议名称2> @end //引入要采纳的协议 #import "Run.h" #import "Work.h" // 采纳协议 @interface Person : NSObject @end (2)协议遵守协议 @protocol 协议名称 <其他协议名称> @end; 如果一个类遵守了某个协议 则只需要在这个类的实现(.m)文件中,实现协议的方法就可以了 3)实现协议中的方法 分区 第三天(@传智如意大师) 的第 131 页 Person.h #import "Person.h" @implementation Person -(void)run{ NSLog(@"人会跑"); } -(void)eat{ NSLog(@"人开始吃东西"); } -(void)myWork{ NSLog(@"人要工作"); } @end 使用 2、使用图形界面创建协议 分区 第三天(@传智如意大师) 的第 132 页 分区 第三天(@传智如意大师) 的第 133 页 分区 第三天(@传智如意大师) 的第 134 页 本小节知识点: 1、【理解】protocol 的使用注意 2、【掌握】protocol 基协议介绍 1、protocol的使用注意 OC中的协议(protocol)使用注意: 1)Protocol:就一个用途,用来声明一大堆的方法(不能声明成员变量),不能写实现。 2)只要某个类遵守了这个协议,就拥有了这个协议中的所有方法声明。 3)只要父类遵守了某个协议,那么子类也遵守。 4)Protocol声明的方法可以让任何类去实现,protocol就是协议。 5)OC不能继承多个类(单继承)但是能够遵守多个协议。继承(:),遵守协议(< >) 6)基协议:是基协议,是最根本最基本的协议,其中声明了很多最基本的方法。 7)协议可以遵守协议,一个协议遵守了另一个协议,就可以拥有另一份协议中的方法声明 1)协议之间可以有继承关系 使用Protocol时还需要注意的是: 1、protocol也可以采纳其他的协议,比如: @protocol A -(void)methodA; @end @protocol B -(void)methodB; @end 如果你要遵守B协议,那么methodA和methodB都需要实现。 10-【掌握】protocol其他用法 分区 第三天(@传智如意大师) 的第 135 页 2)类如果采纳协议后,实现了协议的方法,这些方法可以被子类继承 比如: Person类采纳了Run和Work两个协议,并实现了两个方法 Student类继承了Person类,则此时Student 类中可以调用和重写协议中的方法(实际上是子类 Student 继承自Person的方法) Person.h Student.h #import "Person.h" @interface Student : Person @end 分区 第三天(@传智如意大师) 的第 136 页 2、protocol基协议介绍 NSObject是一个基类,最根本最基本的类,任何其他类最终都要继承它,它还有名字也叫 NSObject的协议,它是一个基协议,最根本最基本的协议 NSObject协议中声明很多最基本的方法 description retain release 建议每个新的协议都要遵守NSObject协议 分区 第三天(@传智如意大师) 的第 137 页 本小节知识点: 1、【理解】protocol中@required和@optional 1、protocol中@required和@optional @required和@optional是 协议方法声明中的两个关键字 他们主要用于控制方法是否要实现(默认是@required),在大多数情况下, 用途在于程序员之间的交流 @public @protected @private @package @required:这个方法必须要实现(若不实现,编译器会发出警告) @protocol RunProtocol //@required表示必须要实现的方法 @required -(void)run; //可以选择实现的方法 @optional -(void)eat; @end @optional这个方法不一定要实现 @optional -(void)eat; 11-【掌握】protocol中@required和@optional 分区 第三天(@传智如意大师) 的第 138 页 分区 第三天(@传智如意大师) 的第 139 页 本小节知识点: 1、【掌握】protocol类型限制 2、【理解】id和instancetype的区别 1、protocol类型限制 设定情景: 某逗比程序员A 希望找一个会做饭、洗衣服的女生做女朋友,有国企工作的优先。 满足条件的女生都可以向他发送消息 从题目中我们得到要求: 1)会做饭 2)会洗衣服 3)有份好工作 得到协议如下: houseHold.h @protocol houseHold -(void)zuoFan; -(void)xiyifu; @optional -(void)job; @end 1)使用id存储对象时,对象的类型限制 格式:id <协议名称> 变量名 如:id obj1; //这样写以后,表示只要是个人都满足条件,不满足题意 id girlFriend = [[Person alloc] init]; 12-【掌握】protocol类型限制 分区 第三天(@传智如意大师) 的第 140 页 //下面写法表示,赋值给girlFriend的对象必须采纳了houseHold协议 //如果不满足,则给出警告 id girlFriend = [[Person alloc] init]; 如果修改Person,让Person 采纳houseHold协议,则此时不会报错了 #import #import "houseHold.h" @interface Person : NSObject @end 2)对象赋值时类型限制 使用类也同样如此 格式:类名<协议名称> *变量名 NSObject *obj; 3)对象关联关系下,对象的类型限制 设定情景: 某屌丝程序员B 想养一只会算术的狗。 分区 第三天(@传智如意大师) 的第 141 页 某屌丝程序员B 想养一只会算术的狗。 从题目中我们得到要求: 1)人拥有一条狗 2)狗必须会算算术 dogProtocol.h #import @protocol dogProtocol //进行求余运算 -(void)mod; @end person.h #import #import "houseHold.h" #import "Dog.h" #import "dogProtocol.h" @interface Person : NSObject @property (nonatomic,strong) Dog *dog; @end 此时再随便给一条狗,则不能满足要求了 修改Dog.h文件 #import #import "dogProtocol.h" @interface Dog : NSObject @end 分区 第三天(@传智如意大师) 的第 142 页 2、id和instancetype的区别 id和instancetype的区别 1)instancetype只能作为函数或者方法的返回值 2)id能作为方法或者函数的返回值、参数类型,也能用来定义变量 3)instancetype对比id的好处 * 能精确地限制返回值的具体类型 分区 第三天(@传智如意大师) 的第 143 页 本小节知识点: 1、【掌握】Protocol代理设计模式引入 1、protocol代理设计模式引入 代理模式是在oc中经常遇到的一种设计模式,那什么叫做代理模式呢? 举个例子:有一 个婴儿,他本身不会自己吃饭和洗澡等等一些事情,于是婴儿就请了一个保姆,于是婴儿和保 姆之间商定了一个协议,协议中写明了保姆需要做什么事情, 而保姆就是这个代理 人, 即:婴儿和保姆之间有个协议,保姆遵守该协议,于是保姆就需要实现该协议中的条款 成为代理人。 思考&实现1: 学习引入: 婴儿饿了要吃东西,困了要睡觉,使用类实现此功能 婴儿类 类名:Baby 属性:保姆 行为: 饿了 - 保姆喂奶 困了 - 保姆哄他睡觉 保姆类 类名: Baomu 行为: 能够照顾小孩 - 喂奶 - 哄睡 13-【理解】protocol代理设计模式引入 分区 第三天(@传智如意大师) 的第 144 页 本小节知识点: 1、【掌握】Protocol代理设计模式概念 2、【掌握】Protocol代理模式应用场合 MVC m - model v - view c - controller 1、protocol代理设计模式概念 代理模式概念 传入的对象,代替当前类完成了某个功能,称为代理模式 利用协议实现代理模式的主要思路为: 1)定义一个协议,里面声明代理类需要实现的方法列表, 比如这里一个代理类需要实现 feedBaby与HongSleep方法 2)创建一个代理类(比如BaoMu),遵守上面的代理协议 3)在需要代理的类中(Baby),定义一个对象类型为id且遵守代理协议的成员变量 4)在Baby类中调用成员变量_delegate(代理)的方法,调用代理类的方法 5)main.m或其他使用Baby类的文件中,为Baby类的成员变量(代理类)赋值,比如 2、protocol代理模式应用场合 代理设计模式的场合: * 当对象A发生了一些行为,想告知对象B (让对象B成为对象A的代理对象) * 对象B想监听对象A的一些行为 (让对象B成为对象A的代理对象) * 当对象A无法处理某些行为的时候,想让对象B帮忙处理 (让对象B成为对象A的代理对象) 1)监听器的场合 * Teacher想监听Baby的一些行为 2)通知的场合 * Baby发生了一些行为,想告知Teacher 3)有些事情,不想自己处理,想交给别人处理 * Baby发生了一些行为, 但是自己不会处理, 可以交给Teacher处理 14-【理解】protocol代理设计模式 分区 第三天(@传智如意大师) 的第 145 页 思考&实现1: 通过代理模式设计,学生通过中介找房子的过程 学生的类 属性:id<找房子的协议> 目标:找到房子 中介类:链家 行为:找房子 中介类:我爱我家 行为:找房子 找房子协议: 必须帮我找房子 15-【理解】应用:通过中介找房子 分区 第三天(@传智如意大师) 的第 146 页 本小节知识点: 1、【了解】Foundation框架介绍 1、Foundation框架介绍 1)什么是框架? 框架是由许多类、方法、函数、文档按照一定的逻辑组织起来的集合,以便使研发程序变得 更容易在OS X下的Mac操作系统中大约有80个框架为所有程序开发奠定基础的框架称为Foundation 框架 Cocoa是Foundation和AppKit Cocoa Touch是Foundation和UIKit Foundation框架允许使用一些基本对象,如数字和字符串,以及一些对象集合,如数组,字 典和集合,其他功能包括处理日期和时间、内存管理、处理文件系统、存储(或归档)对象、处 理几何数据结构(如点和长方形) Foundation框架中大约有125个可用的头文件,作为一个简单的形式,可以简单地使用以下语 句导入 : #import 因为Foundation.h文件实际上导入其他所有 Foundation框架中的头文件 2)Foundation框架的作用 Foundation框架是Mac\iOS中其他框架的基础 Foundation框架包含了很多开发中常用的数据类型: 结构体 枚举 类 3)如何使用Foundation框架 要想使用Foundation框架中的功能,包含它的主文件即可 #import 4)不小心修改了系统的文件,引起的错误 有时候会在不经意之间修改了系统自带的头文件, 比如NSString.h, 这时会出现以下错误: 16-【了解】Foundation框架介绍 分区 第三天(@传智如意大师) 的第 147 页 • 解决方案很简单, 只需要删除Xcode的缓存即可, 缓存路径是 /Users/用户名/Library/Developer/Xcode/DerivedData (默认情况下, 这是一个隐藏文件夹) 要想看到上述文件夹, 必须在终端敲指令显示隐藏文件夹, 指令如下 显示隐藏文件 : defaults write com.apple.finder AppleShowAllFiles –bool true 隐藏隐藏文件 : defaults write com.apple.finder AppleShowAllFiles –bool false (输入指令后, 一定要重新启动Finder) 分区 第三天(@传智如意大师) 的第 148 页 本小节知识点: 1、【了解】NSString介绍及使用 2、【掌握】NSString创建方式 char *s = "asdafasdf"; char str[] = "adfafsd"; 1、NSString类介绍及使用 什么是NSString? 一个NSString对象就代表一个字符串(文字内容) 一般称NSString为字符串类 右图中的文字内容普遍都是用NSString来表示的 17-【掌握】NSString介绍及基本使用 分区 第三天(@传智如意大师) 的第 149 页 2、NSString的创建方式 NSString的创建方式比较多 最直接的方式(这是常量字符串)1) NSString *str = @"abc"; NSString *st3 = @"abc"; 注意这种写法,字符串存储在内存的常量区 格式化的方式2) NSString *st2 = [NSString stringWithFormat:@"%@", @"jack"]; NSString *str = [[NSString alloc] initWithFormat:@"My age is %d", 10]; 分区 第三天(@传智如意大师) 的第 150 页 NSString *str = [[NSString alloc] initWithFormat:@"My age is %d", 10]; 注意这种写法,字符串存储在内存的堆区(地址不一样) 从文件中读取3) //************* 从写入字符串到文件中 *************** //************* 从文件中读取字符串显示到控制台上 *************** NSString *str5 = [NSString stringWithContentsOfFile:@"/Users/apple/Desktop/t.txt" encoding:NSUTF8StringEncoding error:&err]; //注意如果读取到内容了, if (err == nil) { NSLog(@"读取成功的情况"); NSLog(@"%@",str5); }else { NSLog(@"读取失败的情况"); //可以打印err的信息 NSLog(@"%@",err); //可以通过 [err localizedDescription]; 打印简单的错误信息 //NSLog(@"%@",[err localizedDescription]); } 分区 第三天(@传智如意大师) 的第 151 页 从URL中读取4) 分区 第三天(@传智如意大师) 的第 152 页 本小节知识点: 1、【了解】NSURL简介 2、【掌握】使用NSURL读写字符串 1、NSURL简介 什么是URL? URL的全称是Uniform Resource Locator(统一资源定位符) URL是互联网上标准资源的地址 互联网上的每个资源都有一个唯一的URL,它包含的信息指出资源的位置 根据一个URL就能找到唯一的一个资源 URL的格式? 基本URL包含协议、主机域名(服务器名称\IP地址)、路径 协议 服务器的域名 路径 举例: http://ios.itcast.cn/ios/images/content_25.jpg 可以简单认为: URL == 协议头://主机域名/路径 常见的URL协议头(URL类型) http\https :超文本传输协议资源, 网络资源 在URL前加https://前缀表明是用SSL加密的。 你的电脑与服务器之间收发的信息传输将更 加安全。 Web服务器启用SSL需要获得一个服务器证书并将该证书与要使用SSL的服务器绑定。 http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。http 的连接很简单,是无状态的 https协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议 要比http协议安全 ftp :文件传输协议 2、使用NSURL读写字符串 传入完整的字符串创建 18-【掌握】使用NSURL读写字符串 分区 第三天(@传智如意大师) 的第 153 页 传入完整的字符串创建 NSURL *url = [NSURL URLWithString:@"file:///Users/apple/Desktop/str.txt"]; 通过文件路径创建(默认就是file协议的) NSURL *url = [NSURL fileURLWithPath:@"/Users/apple/Desktop/str.txt"]; 分区 第三天(@传智如意大师) 的第 154 页 ================================ 今天的主要知识点 ================================ 1. 复习block的使用 2. 复习协议 3. 讲解代理设计模式 4. Foundation框架介绍 5. NSString介绍 、NSMutableString介绍 6. NSArray介绍 、NSMutableArray介绍 7. NSDictionary介绍 、 NSMutableDictionary介绍 ================================ 今天的主要知识点 ================================ ---------------------------------- 知识点细节 ------------------------ 1. 复习block的使用 、 复习分类、类扩展 1.1 block的基本定义(无参数无返回值的、有参数无返回值的、有参数有返回值的) 1.2 inlineblock自动生成block 1.3 通过"函数的方式"来演示block作为参数的使用 1.4 通过类的属性的方式来演示block的使用 1.5 把block作为返回值来使用 ** 如果要将block作为返回值, 需要先单独定义一个block类型。 /** 参考代码: // 1. 定义block // -------------------- 定义无参数,无返回值的block -------------------- void (^block1)() = ^{ 19-【理解】内容总结 分区 第三天(@传智如意大师) 的第 155 页 void (^block1)() = ^{ NSLog(@"无参数五返回值的block1."); }; void (^block2)() = ^(){ NSLog(@"无参数五返回值的block2."); }; block1(); block2(); // -------------------- 定义带1个参数, 无返回值的block -------------------- void (^block3)(NSString *s1) = ^(NSString *msg){ NSLog(@"%@", msg); }; block3(@"hello"); // -------------------- 定义带2个参数, 带一个返回值的 block -------------------- int (^block4)(int, int) = ^(int x, int y){ return x + y; }; int sum = block4(10, 20); NSLog(@"%d", sum); 2. 把block作为参数 void f1(void (^blockArg)()) { NSLog(@"-----1-----"); NSLog(@"-----2-----"); NSLog(@"-----3-----"); if (blockArg != nil) { blockArg(); } 分区 第三天(@传智如意大师) 的第 156 页 NSLog(@"-----4-----"); NSLog(@"-----5-----"); NSLog(@"-----6-----"); } 3. 把block作为返回值 // 1> 定义一个block类型 typedef void (^MyBlock)(); // 2> 编写带block返回值的函数 MyBlock f2() { return ^(){ NSLog(@"---这个是block中的代码"); }; } // 3> 调用该方法, 获取返回的block, 并且执行该block MyBlock block = f2(); block(); 4. 把block作为类的属性 @property (nonatomic, copy) void (^block)(); Person *p1 = [[Person alloc] init]; p1.block = ^(){ NSLog(@"哈哈"); }; p1.block(); 5. 把block作为OC对象的方法的参数和返回值 ================================================ - (void)test:(void (^)())block; - (void (^)())getABlock; - (MyBlock)getABlock2; ================================================ 分区 第三天(@传智如意大师) 的第 157 页 ================================================ - (void)test:(void (^)())block { // 调用block if (block != nil) { block(); } } - (void (^)())getABlock { return ^{ NSLog(@"返回的block"); }; } - (MyBlock)getABlock2 { return ^{ NSLog(@"返回的block222222"); }; } ================================================ Person *p1 = [[Person alloc] init]; [p1 test:^{ NSLog(@"test"); }]; MyBlock block1 = [p1 getABlock]; MyBlock block2 = [p1 getABlock2]; block1(); block2(); */ 1.6 分类复习 * 分类的主要目的: 把一个类的不同方法写在不同的文件中。 1.7 类扩展复习 分区 第三天(@传智如意大师) 的第 158 页 1.7 类扩展复习 * 写在.m文件中 * 作用: 为类扩展私有成员 2. 复习协议 2.0 什么是协议? * 就是一个头文件, 里面包含一系列的方法声明。(就是一个约定, 所有遵守了协议的类型, 我们就认为具有了这些约定中的功能) * 一个Protocol是由一系列的方法声明组成的 * 任何类只要遵守了Protocol, 就相当于拥有了Protocol的所有方法声明 2.1 协议的语法 * Protocol的定义 @protocol 协议名称 // 方法声明列表 @end * 类遵守协议 @interface 类名 : 父类 <协议名称1, 协议名称2,…> @end 2.2 协议的作用? * 解耦、实现多态 * 复习保镖案例(这个案例中哪里使用到了协议?使用协议解决了什么问题?) 2.3 @required 关键字和 @optional 关键字 * @required:这个方法必须要实现(若不实现,编译器会发出警告) * @optional:这个方法不一定要实现 2.4 同时遵守多个协议 * 某个类, 同时遵守多个协议 * 某个协议, 同时遵守多个协议 3. 讲解代理设计模式 * 代理设计模式的核心思想: 自己(A)要做某事, 但是做不了, 找别人(B)代做, 此时B就是 A的代理。 * 代理设计模式的主要目的: 分区 第三天(@传智如意大师) 的第 159 页 * 代理设计模式的主要目的: 解耦(主类不再依赖特定的某个类型,而是依赖代理,而很多类型都可以作为代理) 增加了程序的灵活性、可扩展性 通知、事件监听、…… Protocol(协议)是实现代理的一种手段。 * 为婴儿找保姆案例(理解协议、代理在该案例中的作用) ** 思路: 1> 婴儿一定有一个吃和喝得方法。 2> 但是婴儿无法自己实现这两个方法, 那么需要扎一个保姆, 这个保姆就可以理解为一 个代理 3> 把保姆案例改成代理设计模式 * 用父类还是id<协议> ** 不使用父类的原因: ** 如果抽象一个父类的话, 还是有局限性, 因为很多时候, 不同类是无法抽象出共同的父类 的. ** 有时候, 某个类已经继承了其他类, 这里不能再继承类了(因为不支持多继承). 4. Foundation框架介绍 * 简要介绍Foundation框架 * 说明为什么要写#import 5. NSString介绍 、NSMutableString介绍 * NSString介绍: 1> 说明什么是字符串 2> 演示3种创建字符串的不同方式(演示字符串内存问题的时候, 使用iOS程序来演示, mac程 序在xcode6.1 + Yosemite下有问题) { /** 参考代码: // 三种不同的方式, 创建同样的字符串 NSString *str1 = @"你好123"; NSString *str2 = [[NSString alloc] initWithFormat:@"你好%d", 123]; NSString *str3 = [NSString stringWithFormat:@"你好%d", 123]; 分区 第三天(@传智如意大师) 的第 160 页 NSString *str3 = [NSString stringWithFormat:@"你好%d", 123]; NSLog(@"%@", str1); NSLog(@"%@", str2); NSLog(@"%@", str3); */ } 3> 说明"字符串常量池"问题 { /** 思考: NSString *str1 = @"你好123"; NSString *str2 = [[NSString alloc] initWithFormat:@"你好%d", 123]; NSString *str3 = [NSString stringWithFormat:@"你好%d", 123]; // 比较下面的3个字符串与上面的3个字符串的地址是否相同? NSString *str11 = @"你好123"; NSString *str21 = [[NSString alloc] initWithFormat:@"你好%d", 123]; NSString *str31 = [NSString stringWithFormat:@"你好%d", 123]; NSLog(@"%p----%p", str1, str11); // 相同吗? NSLog(@"%p----%p", str2, str21); // 相同吗? NSLog(@"%p----%p", str3, str31); // 相同吗? */ /** 补充思考: NSString *str1 = @"你好123"; NSString *str2 = [[NSString alloc] initWithFormat:@"你好%d", 123]; 分区 第三天(@传智如意大师) 的第 161 页 NSString *str2 = [[NSString alloc] initWithFormat:@"你好%d", 123]; NSString *str3 = [NSString stringWithFormat:@"你好%d", 123]; // 下面的方式是直接把字符串的地址进行赋值, 所以地址一定都是一样的。 NSString *s1 = str1; NSLog(@"%@----%@", s1, str1); // 输出的内容一样吗? NSLog(@"%p---%p", s1, str1); // 输出的地址一样吗? NSString *s2 = str2; NSLog(@"%@----%@", s2, str2); // 输出的内容一样吗? NSLog(@"%p----%p", s1, str2); // 输出的地址一样吗? NSString *s3 = str3; NSLog(@"%@----%@", s3, str3); // 输出的内容一样吗? NSLog(@"%p----%p", s3, str3); // 输出的地址一样吗? */ } 4> 通过NSString进行文件读写 **** 方式一, 直接根据给定的路径来读写文件(一般用来读取本地文件) **** * 文件写入: [str1 writeToFile:@"路径" atomically:YES encoding:NSUTF8StringEncoding error:nil]; * 文件读取: [NSString stringWithContentsOfFile:@"路径" encoding:NSUTF8StringEncoding error:nil]; **** 方式二, 根据给定的NSURL对象来读写文件(一般用来读取网络资源文件) **** 1> 根据路径创建NSURL对象。 ** 创建NSURL的两种方式: 1. NSURL *url = [NSURL URLWithString:@"file:///Users/Steve/Desktop/传智iOS13 期-OC基础加强/PPTs/str1.txt"]; ** 通过这种方式创建NSURL对象, 需要指定协议类型, 比如"http://" 、 "ftp://" 、 "file://" 、"https://"等协议。 ** 通过这种方式创建的NSURL, 如果路径中有中文, 则无法正常使用, 需要使用下面的方 式来创建。 分区 第三天(@传智如意大师) 的第 162 页 2. NSURL *url = [NSURL fileURLWithPath:@"/Users/Steve/Desktop/传智iOS13期-OC基 础加强/PPTs/str1.txt"]; ** 这种方式创建的NSURL对象, 默认就是读取本地文件的NSURL对象, 以"file://" 开头 2> 根据NSURL对象来读取文件内容。 * [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error: &err]; 5> NSString的常见方法: - (NSString *)uppercaseString; 全部字符转为大写字母 - (NSString *)lowercaseString 全部字符转为小写字母 - (NSString *)capitalizedString 首字母变大写,其他字母都变小写。如果一个句子中有多个单词,那么会将每个单词的首字母 变成大写,其余字母变成小写 - (BOOL)isEqualToString:(NSString *)aString; 两个字符串的内容相同就返回YES, 否则返回NO 注意: == 比较的是两个对象的地址是否相同, 不是字符串内容 - (NSComparisonResult)compare:(NSString *)string; 这个方法可以用来比较两个字符串内容的大小 比较方法: 逐个字符地进行比较ASCII值,返回NSComparisonResult作为比较结果 NSComparisonResult是一个枚举,有3个值: 如果左侧 > 右侧,返回NSOrderedDescending, 如果左侧 < 右侧,返回NSOrderedAscending, 如果左侧 == 右侧返回NSOrderedSame - (NSComparisonResult) caseInsensitiveCompare:(NSString *)string; 忽略大小写进行比较,返回值与compare:一致 - (BOOL)hasPrefix:(NSString *)aString; 是否以aString开头 分区 第三天(@传智如意大师) 的第 163 页 - (BOOL)hasSuffix:(NSString *)aString; 是否以aString结尾 - (NSRange)rangeOfString:(NSString *)aString; 用来检查字符串内容中是否包含了aString 如果包含, 就返回aString的范围 如果不包含, NSRange的location为NSNotFound, length为0 反方向搜索:[str rangeOfString: @”str” options: NSBackwardsSearch]; // 从后向前 搜索 - (NSString *)substringFromIndex:(NSUInteger)from; 从指定位置from开始(包括指定位置的字符)到尾部 - (NSString *)substringToIndex:(NSUInteger)to; 从字符串的开头一直截取到指定的位置to,但不包括该位置的字符 - (NSString *)substringWithRange:(NSRange)range; 按照所给出的NSRange从字符串中截取子串 - (NSString *)stringByReplacingOccurrencesOfString:(NSString *)target withString:(NSString *)replacement; 用replacement替换target - (NSUInteger)length; 返回字符串的长度(有多少个字符,无论中文字符、英文字符等等,一个字符就是一个字符) - (unichar)characterAtIndex:(NSUInteger)index; 返回index位置对应的字符 字符串转为基本数据类型 - (double)doubleValue; - (float)floatValue; - (int)intValue; - (char *)UTF8String; // 在操作SQLite数据库时才会用到 转为C语言中的字符串。 以后可能会用到别人的框架,都是纯C语言的,所以可能会用到把OC字符串转换为C语言的字 符串。 分区 第三天(@传智如意大师) 的第 164 页 符串。 /** 参考代码: C语言字符串: char *name = “steve”; NSString *s1 = @"123”; char *s2 = s1.UTF8String; printf("%s", s2); */ 去除所有的空格 [str stringByReplacingOccurrencesOfString:@" " withString:@""] 去除首尾的空格 [str stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; 6. NSArray介绍 、NSMutableArray介绍 7. NSDictionary介绍 、 NSMutableDictionary介绍 ---------------------------------- 知识点细节 --------------------------------- 分区 第三天(@传智如意大师) 的第 165 页 20-【理解】作业布置 分区 第三天(@传智如意大师) 的第 166 页 1、知识回顾 分区 第四天(@传智如意大师) 的第 167 页 本小节知识点: 1、【理解】protocol的使用补充 1、protocol的使用补充 1)protocol的引用方式 类似 @class 的使用方式 在需要遵守协议的地方,使用@protocol 协议名; 注意在.m文件中 1)使用#import 引入协议的头文件 2)实现协议对应的方法(需要引入头文件) 3)在main中使用的时候也需要导入头文件 2-【理解】protocol的使用补充 分区 第四天(@传智如意大师) 的第 168 页 2)在类的声明文件中,定义协议(不再单独把协议的定义放到xxx.h文件中) .m文件中直接实现协议对应的方法即可 这种方式的缺点是不够灵活。 分区 第四天(@传智如意大师) 的第 169 页 本小节知识点: 1、【理解】字符串比较函数 1、字符串比较函数 1)比较字符串大小 字符串根据排列顺序的不同,会有大小的差异,如果我们要比较两个字符串的大小的话,可 以使用compare方法。 compare 方法返回的结果有三种: NSOrderedAscending,NSOrderedSame,NSOrderedDescending, str > str2 NSOrderedDescending 1 b a str < str2 NSOrderedAscending -1 a str2"); break; case NSOrderedAscending: NSLog(@"str < str2"); break; case NSOrderedSame: NSLog(@"str == str2"); break; } 3-【理解】NSString字符串比较 分区 第四天(@传智如意大师) 的第 170 页 2)比较字符串大小,指定条件 如果要忽略大小写的话,可以在options 里面加一个参数NSCaseInsensitiveSearch;具体如下 NSComparisonResult result2 =[str1 compare:str2 options:NSCaseInsensitiveSearch]; 常见的有3个 NSCaseInsensitiveSearch:不区分大小写字符。 NSLiteralSearch:进行完全比较,区分大小写。(默认) NSNumericSearch:比较字符串的字符个数,而不是字符值。 分区 第四天(@传智如意大师) 的第 171 页 并可以使用"|"来同时满足多个条件。 例如,如果你想进行字符串比较,要忽略大小写但按字符个数的多少正确排列,那么应该这么 做: 丨 NSNumericSearch] options:NSCaseInsensitiveSearch == NSOrderedSame) { NSlog (@”They match!”); if([thing1 compare:thing2 } 3)判断字符串是否相等 思考&实现1: 字符串判断相等是否能够使用 “==” 进行判断 正确的方法: -(BOOL) isEqualToString: (NSString *) aString; 分区 第四天(@传智如意大师) 的第 172 页 比较接收方和当作参数传递来的字符串 表示两个字符串的内容是否相同,注意区分大小写 返回BOOL(YES或NO)型数据 -(BOOL) isEqualToString: (NSString *) aString; 区分大小写 完全相等 分区 第四天(@传智如意大师) 的第 173 页 本小节知识点: 1、【理解】字符串前后缀检查 2、【理解】字符串查找 1、NSString前后缀检查 1)判断字符串前缀 判断地址是否是网址 file:// ftp:// https:// http:// 检查字符串是否以另一个字符串开头 -(BOOL) hasPrefix: (NSString *) aString; 2)判断字符串后缀 判断字符串是否以另一个字符串结尾 -(BOOL) hasSuffix: (NSString *) aString; 4-【理解】NSString前后缀检查及搜索 分区 第四天(@传智如意大师) 的第 174 页 2、字符串查找 - (NSRange)rangeOfString:(NSString *)aString; 用来检查字符串内容中是否包含了aString 如果包含, 就返回aString在字符串中第一次出现的范围 反方向搜索: // 从后向前搜索,遇到第一次出现的位置,则结束 [str rangeOfString: @"str" options: NSBackwardsSearch]; typedef struct _NSRange { NSUInteger location; //位置 NSUInteger length; //长度 } NSRange; 如果不包含, NSRange的location为NSNotFound, length为0 分区 第四天(@传智如意大师) 的第 175 页 如果不包含, NSRange的location为NSNotFound, length为0 正确的使用NSNotFound 分区 第四天(@传智如意大师) 的第 176 页 本小节知识点: 1、【理解】NSRange的使用 2、【理解】NSRange创建的几种方式 1、NSRange的使用 NSRange是Foundation框架中比较常用的结构体, 它的定义如下: typedef struct _NSRange { NSUInteger location; NSUInteger length; } NSRange; // NSUInteger的定义 typedef unsigned int NSInteger; typedef unsigned long NSUInteger; NSRange用来表示事物的一个范围,通常是字符串里的字符范围或者数组里的元素范围 NSRange有2个成员 NSUInteger location : 表示该范围的起始位置 NSUInteger length : 表示该范围内的长度 比如@“I love iOS”中的@“iOS”可以用location为7,length为3的范围来表示 2、NSRange创建的几种方式 有4种方式创建一个NSRange变量 方式1 NSRange range; range.location = 7; range.length = 3; 方式2 NSRange range = {7, 3}; 或者 NSRange range = {.location = 7, .length = 3}; 5-【理解】NSRange使用 分区 第四天(@传智如意大师) 的第 177 页 方式3 : 使用NSMakeRange函数 NSRange range = NSMakeRange(7, 3); 方式4 : 最直接的方式 NSRange range = {5,8}; 查看Range的值,可以使用 NSLog(@"%@",NSStringFromRange(range)); 分区 第四天(@传智如意大师) 的第 178 页 本小节知识点: 1、【理解】字符串的截取 2、【理解】字符串的替换函数 1、字符串的截取 - (NSString *)substringFromIndex:(NSUInteger)from; 从指定位置from开始(包括指定位置的字符)到尾部 - (NSString *)substringToIndex:(NSUInteger)to; 从字符串的开头一直截取到指定的位置to,但不包括该位置的字符 - (NSString *)substringWithRange:(NSRange)range; 按照所给出的NSRange从字符串中截取子串 思考&实现1: 将标签内的 字符串取出来 传智播客 先找 @">" 出现的位置 loc 再找 @"传智播客"; //获取位置 NSInteger loc = [str rangeOfString:@">"].location+1; //获取长度 NSInteger length = [str rangeOfString:@"C C----->OC 以后可能会用到别人的框架,都是纯C语言的,所以可能会用到把OC字符串转换为C语言的字符 串。 去除所有的空格 [str stringByReplacingOccurrencesOfString:@" " withString:@""] 去除首尾的空格 [str stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; 分区 第四天(@传智如意大师) 的第 183 页 练习:去除头、尾的所有大写字母、小写字母、*号。 + (id)whitespaceCharacterSet; //去掉头尾空格 + (id)lowercaseLetterCharacterSet; // 去掉头尾的小写字母 + (id)uppercaseLetterCharacterSet; //去掉头尾的大写字母 + (id)characterSetWithCharactersInString:(NSString *)aString; // 去掉头尾的指定字符串 分区 第四天(@传智如意大师) 的第 184 页 本小节知识点: 1、【理解】NSMutableString 基本概念 2、【理解】字符串中的可变和不可变 1、NSMutableString 基本概念 NSMutableString 类 继承NSString类,那么NSString 供的方法在NSMutableString中基 本都可以使用,NSMutableString好比一个字符串链表,它可以任意的动态在字符串中添加字符 串 删除字符串 指定位置插入字符串,使用它来操作字符串会更加灵活。 NSString是不可变的, 里面的文字内容是不能进行修改的 NSMutableString是可变的, 里面的文字内容可以随时更改 NSMutableString和NSString的区别 NSMutableString能使用NSString的所有方法 2、字符串的可变和不可变 不可变:指的是字符串在内存中占用的存储空间固定,并且存储的内容不能发生变化 可变:指的是字符串在内存中占用的存储空间可以不固定,并且存储的内容可以被修改 8-【理解】NSMutableString 介绍和使用 分区 第四天(@传智如意大师) 的第 185 页 可变字符串: 指的是字符串在内存中占用的存储空间可以不固定,并且存储的内容可以被修改 内存结构图: 思考&实现1: 把10个itcast连接到一起,组成一个字符串 分区 第四天(@传智如意大师) 的第 186 页 NSString *substr = @"itcast"; NSMutableString *destStr = [NSMutableString string]; for (int i = 0; i<10; i++) { // if (i != 0) { if (i) { // 只有i不等于 [destStr appendString:@" "]; } [destStr appendString:substr]; } 分区 第四天(@传智如意大师) 的第 187 页 本小节知识点: 1、【理解】NSMutableString常用方法 2、【理解】字符串使用注意事项 1、NSMutableString 常用方法 通过调用string方法, 创建一个空的NSMutableString - (void)appendString:(NSString *)aString; 拼接aString到最后面 - (void)appendFormat:(NSString *)format, ...; 拼接一段格式化字符串到最后面 - (void)deleteCharactersInRange:(NSRange)range; 删除range范围内的字符串。一般可以配合rangeOfString删除。 - (void)insertString:(NSString *)aString atIndex:(NSUInteger)loc; 在loc这个位置中插入aString 9-【理解】NSMutableString常用方法 分区 第四天(@传智如意大师) 的第 188 页 - (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)aString; 使用aString替换range范围内的字符串,一般不是以string开头的一般都不会产生一个新的字符 串。replaceOccurrencesOfString方法不需要传范围参数。 2、字符串使用注意事项 1)NSMutableString易犯错误 @”hello”这种方式创建的字符串始终是NSString,不是NSMutalbeString.所以下面的代码 创建的还是NSString,此时使用可变字符串的函数,无法操作字符串。 NSMutalbeString *s1 = @”hello”; 注意: 分区 第四天(@传智如意大师) 的第 189 页 2)NSMutableString的string属性:会将源对象的所有字符串都覆盖掉。 .string 属性可以修改字符串的内容 3)开发中到底使用NSString还是NSMutableString? 开发中绝大多数用到的都是NSString 只是如果需要做特殊处理的时候(截取、拼接、替换)等操作,才考虑使用NSMutableString 4)位枚举,枚举值是2的0次方、1次方、2次方...... NSString *s1 = @"hello"; NSRange range = [s1 rangeOfString:@"o" options:0 range:NSMakeRange(0, 5)]; if (range.location == NSNotFound) { NSLog(@"没有找到。"); }else { NSLog(@"location = %ld, length = %ld", range.location, range.length); } 分区 第四天(@传智如意大师) 的第 190 页 本小节知识点: 1、【理解】NSArray的基本介绍 2、【理解】NSArray的创建方式 3、【理解】NSArray 的使用注意事项 思考&实现1: 1)回顾C语言中的数组使用方法 int a[5]={1,2}; int *pa[3]; 1、NSArray的基本介绍 1)什么是NSArray? NSArray是OC中的数组类, 开发中建议尽量使用NSArray替代C语言中的数组 C语言中数组的弊端 int array[4] = {10, 89, 27, 76}; 只能存放一种类型的数据.(类型必须一致) 不能很方便地动态添加数组元素、不能很方便地动态删除数组元素(长度固定) 2)NSArray的使用注意 只能存放任意OC对象, 并且是有顺序的 不能存储非OC对象, 比如int\float\double\char\enum\struct等 它是不可变的, 一旦初始化完毕后, 它里面的内容就永远是固定的, 不能删除里面的元素, 也不 能再往里面添加元素 2、NSArray的创建方式 1)NSArray的类方法创建 + (instancetype)array; + (instancetype)arrayWithObject:(id)anObject; + (instancetype)arrayWithObjects:(id)firstObj, ...; 10-【理解】NSArray 的介绍和基本使用 分区 第四天(@传智如意大师) 的第 191 页 + (instancetype)arrayWithObjects:(id)firstObj, ...; + (instancetype)arrayWithArray:(NSArray *)array; + (id)arrayWithContentsOfFile:(NSString *)path; // 读取一个xml文件. + (id)arrayWithContentsOfURL:(NSURL *)url; // 读取一个xml文件. 2)使用NSArray的对象方法创建 3)使用已经存在数组创建新的数组 分区 第四天(@传智如意大师) 的第 192 页 3、NSArray的使用注意 数组使用注意: NSArray直接使用NSLog()作为字符串输出时是小括号括起来的形式。 NSArray中不能存储nil,因为NSArray认为nil是数组的结束(nil是数组元素结束的标记)。nil 就是0。0也是基本数据类型,不能存放到NSArray中。 NSArray *array = [NSArray array]; // 这样的数组永远是空的,不可能向其中增加值。(不可 变性) 分区 第四天(@传智如意大师) 的第 193 页 本小节知识点: 1、【理解】NSArray的常见用法 2、【理解】NSArray的简写形式 3、【理解】NSArray 的使用注意事项 1、NSArray的常见用法 - (NSUInteger)count; 获取集合元素个数 - (id)objectAtIndex:(NSUInteger)index; 获得index位置的元素 - (BOOL)containsObject:(id)anObject; 是否包含某一个元素 - (id)lastObject; 返回最后一个元素 - (id)firstObject; 返回最后一个元素 - (NSUInteger)indexOfObject:(id)anObject; 查找anObject元素在数组中的位置(如果找不到,返回-1)NSNotFound - (NSUInteger)indexOfObject:(id)anObject inRange:(NSRange)range; 在range范围内查找anObject元素在数组中的位置 11-【理解】NSArray 的常见用法 分区 第四天(@传智如意大师) 的第 194 页 2、NSArray的简写形式 自从2012年开始, Xcode的编译器多了很多自动生成代码的功能, 使得OC代码更加精简 数组的创建 之前 [NSArray arrayWithObjects:@"Jack", @"Rose", @"Jim", nil]; 现在 @[@"Jack", @"Rose", @"Jim"]; 数组元素的访问 之前 [array objectAtIndex:0]; 现在 array[0]; 3、NSArray的使用注意 数组使用注意: NSArray直接使用NSLog()作为字符串输出时是小括号括起来的形式。 NSArray中不能存储nil,因为NSArray认为nil是数组的结束(nil是数组元素结束的标记)。nil 就是0。0也是基本数据类型,不能存放到NSArray中。 分区 第四天(@传智如意大师) 的第 195 页 NSArray *array = [NSArray array]; // 这样的数组永远是空的,不可能向其中增加值。(不可 变性) 分区 第四天(@传智如意大师) 的第 196 页 本小节知识点: 1、【理解】NSArray的下标遍历 2、【理解】NSArray的快速遍历 3、【理解】NSArray 使用block进行遍历 1、NSArray的下标遍历 2、NSArray的快速遍历 也是使用for循环的增强形式 3、NSArray使用block遍历 12-【理解】NSArray 的遍历方法 分区 第四天(@传智如意大师) 的第 197 页 *stop = YES; 分区 第四天(@传智如意大师) 的第 198 页 本小节知识点: 1、【理解】NSArray数据写入到文件中 2、【理解】从文件中读取数据到NSArray中 1、NSArray数据写入到文件中 //创建一个数组 NSArray *arr = [NSArray arrayWithObjects:@"1",@"2",@"3",@"4",@"5", nil]; //写入数据到文件中 if([arr writeToFile:@"/Users/liwei/Desktop/arr.plist" atomically:YES]){ NSLog(@"写入成功!"); } 13-【理解】NSArray 读写文件 分区 第四天(@传智如意大师) 的第 199 页 2、从文件读取数据到NSArray中 //创建一个数组,读取xml文件 NSArray *arr = [NSArray arrayWithContentsOfFile:@"/Users/liwei/Desktop/arr.xml"]; NSLog(@"%@",arr); //创建一个数组,读取plist NSArray *arr = [NSArray arrayWithContentsOfFile:@"/Users/liwei/Desktop/arr.plist"]; NSLog(@"%@",arr); 分区 第四天(@传智如意大师) 的第 200 页 本小节知识点: 1、【理解】把数组元素链接成字符串 2、【理解】字符串分割方法 1、把数组元素链接成字符串 - (NSString *)componentsJoinedByString:(NSString *)separator; 这是NSArray的方法, 用separator作拼接符将数组元素拼接成一个字符串 } 2、字符串分割方法 //定义字符串 NSString *str =@"400-588-1688"; NSArray *arr = [str componentsSeparatedByString:@"-"]; NSLog(@"%@",arr); 14-【理解】NSArray 与字符串 分区 第四天(@传智如意大师) 的第 201 页 本小节知识点: 1、【理解】NSMutableArray介绍 2、【理解】NSMutableArray基本用法 3、【理解】NSMutableArray 错误用法 1、NSMutableArray介绍 什么是NSMutableArray NSMutableArray是NSArray的子类 NSArray是不可变的, 一旦初始化完毕后, 它里面的内容就永远是固定的, 不能删除里面的元素, 也不能再往里面添加元素 NSMutableArray是可变的, 随时可以往里面添加\更改\删除元素 // 创建一个空的NSMutableArray NSMutableArray *array = [NSMutableArray array]; 2、NSMutableArray基本使用方法 //创建空数组 NSMutableArray *arr = [NSMutableArray array]; //创建数组,并且指定长度为5,此时也是空数组 NSMutableArray *arr2 = [[NSMutableArray alloc] initWithCapacity:5]; //创建一个数组,包含两个元素 NSMutableArray *arr3 = [NSMutableArray arrayWithObjects:@"1",@"2", nil]; //调用对象方法创建数组 NSMutableArray *arr4 = [[NSMutableArray alloc] initWithObjects:@"1",@"2", nil]; - (void)addObject:(id)object; 添加一个元素 15-【理解】NSMutableArray及基本使用 分区 第四天(@传智如意大师) 的第 202 页 - (void)addObjectsFromArdObray:(NSArray *)array; 添加otherArray的全部元素到当前数组中 - (void)insertObject:(id)anObject atIndex:(NSUInteger)index; 在index位置插入一个元素 - (void)removeLastObject; 删除最后一个元素 - (void)removeAllObjects; 删除所有的元素 分区 第四天(@传智如意大师) 的第 203 页 - (void)removeObjectAtIndex:(NSUInteger)index; 删除index位置的元素 - (void)removeObject:(id)object; 删除特定的元素 - (void)removeObjectsInRange:(NSRange)range; 删除range范围内的所有元素 - (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject; 用anObject替换index位置对应的元素 更便捷写法:arr[0] = @"one"; - (void)exchangeObjectAtIndex:(NSUInteger)idx1 withObjectAtIndex:(NSUInteger)idx2; 交换idx1和idx2位置的元素 分区 第四天(@传智如意大师) 的第 204 页 3、NSMutableArray 错误用法 NSMutableArray *array = @[@"bob", @"steve", @"john"]; [array addObject:@“Peter”];// 错误,使用@[@"bob", @"steve", @"john"]这种方式创建的永 远是NSArray(不可变数组) // 正确 NSMutableArray *array = [NSMutableArray arrayWithArray:@[@"bob", @"steve", @"john"]]; [array addObject:@"Peter"]; 分区 第四天(@传智如意大师) 的第 205 页 本小节知识点: 1、【理解】NSDictionar的介绍和使用 1、NSDictionary介绍 1)什么是NSDictionary NSDictionary翻译过来叫做”字典” 日常生活中, “字典”的作用: 通过一个拼音或者汉字, 就能找到对应的详细解释 NSDictionary的作用类似: 通过一个key(键), 就能找到对应的value(值) NSDictionary是不可变的, 一旦初始化完毕, 里面的内容就无法修改 2)NSDictionary的创建 + (instancetype)dictionary; + (instancetype)dictionaryWithObject:(id)object forKey:(id )key; // objectForKey,根据键取值。 + (instancetype)dictionaryWithObjectsAndKeys:(id)firstObject, ...; NSDictionary *dict2 = [NSDictionary dictionaryWithObjectsAndKeys:@"value1",@"key1", @"value2",@"key2",nil]; 3)快速创建字典 @{@"zs" : @"zhangsan", @"ls" : @"lisi", @"ww" : @"Wangwu"}; 16-【理解】NSDictionary的介绍及使用 分区 第四天(@传智如意大师) 的第 206 页 4)键值对集合的特点 1. 字典存储的时候,必须是"键值对"的方式来存储(同时键不要重复) 2. 键值对中存储的数据是"无序的". 3. 键值对集合可以根据键, 快速获取数据. 2、NSDictionary基本使用方法 - (NSUInteger)count; 返回字典的键值对数目 - (id)objectForKey:(id)aKey; 根据key取出value 3、NSDictionary遍历方法 1)快速遍历 for (NSString *key in dict) { } 解决方案: 分区 第四天(@传智如意大师) 的第 207 页 2)Block遍历 [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { }]; 分区 第四天(@传智如意大师) 的第 208 页 本小节知识点: 1、【理解】NSDictionary 的简写形式 2、【理解】NSDictionary 的文件操作 3、【理解】NSDictionary 的使用注意 1、NSDictionary的简写形式 1)NSDictionary的创建 以前 [NSDictionary dictionaryWithObjectsAndKeys:@"Jack", @"name", @"男", @"sex", nil]; 现在 @{@"name": @"Jack", @"sex" : @"男”}; 2)NSDictionary获取元素 以前 [dict objectForKey:@"name”]; 现在 dict[@"name”]; 2、NSDictionary文件操作 将字典写入文件中 - (BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile; - (BOOL)writeToURL:(NSURL *)url atomically:(BOOL)atomically; 写入一个字典数据 //快速创建字典 NSDictionary *dict3 = @{@"zs" : @"zhangsan", @"ls" : @"lisi", @"ww" : @"Wangwu"}; ; //将字典写入到文件中 [dict3 writeToFile:@"/Users/liwei/Desktop/dict.plist" atomically:YES]; 17-【理解】NSDictionary的简写及文件操作 分区 第四天(@传智如意大师) 的第 209 页 把多个dict加到array中调用array的writeToFile方法。 //快速创建字典 NSDictionary *dict2 = @{@"x" : @"xyz", @"a" : @"abc", @"d" : @"def"}; NSDictionary *dict3 = @{@"zs" : @"zhangsan", @"ls" : @"lisi", @"ww" : @"Wangwu"}; ; NSArray *arr = @[dict2,dict3]; //将数组写入到文件中 [arr writeToFile:@"/Users/liwei/Desktop/dict.plist" atomically:YES]; NSLog(@"写入成功!"); 从文件中读取字典到内存中 保存结果是xml文件格式,但苹果官方推荐为plist后缀。 把字典放到NSArray中,调用array的writeToFile + (id)dictionaryWithContentsOfFile:(NSString *)path; + (id)dictionaryWithContentsOfURL:(NSURL *)url; 3、NSDictionary使用注意 字典中的键不要重复。(虽然重复也不报错,会自动取在前面的那个) 分区 第四天(@传智如意大师) 的第 210 页 本小节知识点: 1、【理解】NSMutableDictionary 介绍 2、【理解】NSMutableDictionary 的常用方法 3、【理解】NSMutableDictionary 的使用注意 1、NSMutableDictionary的介绍 什么是NSMutableDictionary NSMutableDictionary是NSDictionary的子类 NSDictionary是不可变的, 一旦初始化完毕后, 它里面的内容就永远是固定的, 不能删除里面的 元素, 也不能再往里面添加元素 NSMutableDictionary是可变的, 随时可以往里面添加\更改\删除元素 2、NSMutableDictionary常用方法 - (void)setObject:(id)anObject forKey:(id )aKey; 添加一个键值对(会把aKey之前对应的值给替换掉) - (void)removeObjectForKey:(id)aKey; 通过aKey删除对应的value - (void)removeAllObjects; 删除所有的键值对 3、NSMutableDictionary简写形式 快速创建可变字典 dict.dictionary = @{@"name" : @"bob", @"age" : @"18"}; 18-【理解】NSMutableDictionary介绍和使用 分区 第四天(@传智如意大师) 的第 211 页 设置键值对 以前 [dict setObject:@"Jack" forKey:@"name”]; 现在 dict[@"name"] = @"Jack"; 4、NSDictionary和NSArray对比 NSArray和NSDictionary的区别 1> NSArray是有序的,NSDictionary是无序的 2> NSArray是通过下标访问元素,NSDictionary是通过key访问元素 1> 创建 @[@"Jack", @"Rose"] (返回是不可变数组) 2> 访问 id d = array[1]; 3> 赋值 array[1] = @"jack"; NSArray的用法 NSDictionary的用法 @{ @"name" : @"Jack", @"phone" : @"10086" } (返回是不可变字典) 1> 创建 分区 第四天(@传智如意大师) 的第 212 页 2> 访问 id d = dict[@"name"]; 3> 赋值 dict[@"name"] = @"jack"; 分区 第四天(@传智如意大师) 的第 213 页 1. NSString介绍 、NSMutableString介绍 1> 通过NSString进行文件读写 **** 方式一, 直接根据给定的路径来读写文件(一般用来读取本地文件) **** * 文件写入: [str1 writeToFile:@"路径" atomically:YES encoding:NSUTF8StringEncoding error:nil]; * 文件读取: [NSString stringWithContentsOfFile:@"路径" encoding:NSUTF8StringEncoding error:nil]; **** 方式二, 根据给定的NSURL对象来读写文件(一般用来读取网络资源文件) **** 1> 根据路径创建NSURL对象。 ** 创建NSURL的两种方式: 1. NSURL *url = [NSURL URLWithString:@"file:///Users/Steve/Desktop/传智iOS13 期-OC基础加强/PPTs/str1.txt"]; ** 通过这种方式创建NSURL对象, 需要指定协议类型, 比如"http://" 、 "ftp://" 、 "file://" 、"https://"等协议。 ** 通过这种方式创建的NSURL, 如果路径中有中文, 则无法正常使用, 需要使用下面的方 式来创建。 2. NSURL *url = [NSURL fileURLWithPath:@"/Users/Steve/Desktop/传智iOS13期-OC基 础加强/PPTs/str1.txt"]; ** 这种方式创建的NSURL对象, 默认就是读取本地文件的NSURL对象, 以"file://" 开头 2> 根据NSURL对象来读取文件内容。 * [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error: &err]; 2. NSString的常见方法: - (NSString *)uppercaseString; 全部字符转为大写字母 - (NSString *)lowercaseString 全部字符转为小写字母 19-【理解】内容总结 分区 第四天(@传智如意大师) 的第 214 页 - (NSString *)capitalizedString 首字母变大写,其他字母都变小写。如果一个句子中有多个单词,那么会将每个单词的首字母 变成大写,其余字母变成小写 - (BOOL)isEqualToString:(NSString *)aString; 两个字符串的内容相同就返回YES, 否则返回NO 注意: == 比较的是两个对象的地址是否相同, 不是字符串内容 - (NSComparisonResult)compare:(NSString *)string; 这个方法可以用来比较两个字符串内容的大小 比较方法: 逐个字符地进行比较ASCII值,返回NSComparisonResult作为比较结果 NSComparisonResult是一个枚举,有3个值: 如果左侧 > 右侧,返回NSOrderedDescending, 如果左侧 < 右侧,返回NSOrderedAscending, 如果左侧 == 右侧返回NSOrderedSame - (NSComparisonResult) caseInsensitiveCompare:(NSString *)string; 忽略大小写进行比较,返回值与compare:一致 - (BOOL)hasPrefix:(NSString *)aString; 是否以aString开头 - (BOOL)hasSuffix:(NSString *)aString; 是否以aString结尾 - (NSRange)rangeOfString:(NSString *)aString; 用来检查字符串内容中是否包含了aString 如果包含, 就返回aString的范围 如果不包含, NSRange的location为NSNotFound, length为0 反方向搜索:[str rangeOfString: @”str” options: NSBackwardsSearch]; // 从后向前 搜索 - (NSString *)substringFromIndex:(NSUInteger)from; 从指定位置from开始(包括指定位置的字符)到尾部 - (NSString *)substringToIndex:(NSUInteger)to; 从字符串的开头一直截取到指定的位置to,但不包括该位置的字符 分区 第四天(@传智如意大师) 的第 215 页 - (NSString *)substringWithRange:(NSRange)range; 按照所给出的NSRange从字符串中截取子串 - (NSString *)stringByReplacingOccurrencesOfString:(NSString *)target withString:(NSString *)replacement; 用replacement替换target - (NSUInteger)length; 返回字符串的长度(有多少个字符,无论中文字符、英文字符等等,一个字符就是一个字符) - (unichar)characterAtIndex:(NSUInteger)index; 返回index位置对应的字符 字符串转为基本数据类型 - (double)doubleValue; - (float)floatValue; - (int)intValue; - (char *)UTF8String; // 在操作SQLite数据库时才会用到 转为C语言中的字符串。 以后可能会用到别人的框架,都是纯C语言的,所以可能会用到把OC字符串转换为C语言的字 符串。 /** 参考代码: C语言字符串: char *name = “steve”; NSString *s1 = @"123”; char *s2 = s1.UTF8String; printf("%s", s2); */ 去除所有的空格 [str stringByReplacingOccurrencesOfString:@" " withString:@""] 去除首尾的空格 [str stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; 分区 第四天(@传智如意大师) 的第 216 页 6> NSString的不可变性 * NSString *s1 = @"aa"; s1 = @"bb"; // 问: 这种情况是字符串发生改变了吗? 回答: 这种情况只是指针变量s1 重新指向了, 分配好的字符串空间中的内容并没有发生改变。 * 为什么要有NSMutableString? 因为如果使用NSString进行字符串拼接的时候, 会产生大量 垃圾内存, 创建很多无用的对象, 浪费内存, 消耗cpu性能。 1> C语言中数组的弊端, int array[4] = {10, 89, 27, 76}; * 只能存放一种类型的数据.(类型必须一致) * 不能很方便地动态添加数组元素、不能很方便地动态删除数组元素(长度固定) 2> NSArray的创建方式: + (instancetype)array; + (instancetype)arrayWithObject:(id)anObject; + (instancetype)arrayWithObjects:(id)firstObj, ...; + (instancetype)arrayWithArray:(NSArray *)array; @[item1, item2, item3, ......]; --------------- 根据文件来创建 ---------------- + (id)arrayWithContentsOfFile:(NSString *)path; // 读取一个xml文件. + (id)arrayWithContentsOfURL:(NSURL *)url; // 读取一个xml文件. 3> NSArray的特点: * 只能存放任意OC对象, 并且是有顺序的 * 不能存储非OC对象, 比如int\float\double\char\enum\struct等 * 它是不可变的, 一旦初始化完毕后, 它里面的内容就永远是固定的, 不能删除里面的元素, 也 不能再往里面添加元素 * NSArray直接使用NSLog()作为字符串输出时是小括号括起来的形式。 * NSArray中不能存储nil,因为NSArray认为nil是数组的结束(nil是数组元素结束的标 记)。nil就是0。0也是基本数据类型,不能存放到NSArray中。 nil Nil NULL [NSNULL null]; * NSArray *array = [NSArray array]; // 这样的数组永远是空的,不可能向其中增加值。(不 可变性) 4> NSArray的常见方法: - (NSUInteger)count; 获取集合元素个数 分区 第四天(@传智如意大师) 的第 217 页 - (id)objectAtIndex:(NSUInteger)index; 获得index位置的元素, 等价于array[index]; - (BOOL)containsObject:(id)anObject; 是否包含某一个元素 - (id)lastObject; 返回最后一个元素 - (id)firstObject; 返回最后一个元素 - (NSUInteger)indexOfObject:(id)anObject; 查找anObject元素在数组中的位置(如果找不到,返回-1)NSNotFound - (NSUInteger)indexOfObject:(id)anObject inRange:(NSRange)range; 在range范围内查找anObject元素在数组中的位置 - (void)makeObjectsPerformSelector:(SEL)aSelector; - (void)makeObjectsPerformSelector:(SEL)aSelector withObject:(id)argument; 5> NSArray遍历元素 * 遍历, 就是将NSArray里面的所有元素一个一个取出来查看 * 常见的遍历方式有 *普通遍历 for (int i = 0; i 将一个NSArray保存到文件中 - (BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile; - (BOOL)writeToURL:(NSURL *)url atomically:(BOOL)atomically; 分区 第四天(@传智如意大师) 的第 218 页 - (BOOL)writeToURL:(NSURL *)url atomically:(BOOL)atomically; ** 演示: 把一个NSArray中的所有字符串元素写入到一个xml文件中。 ** 注意: 目前这里只支持NSArray中保存字符串、Dictionary或一写OC对象的数字类型写入文 件。(其他类型需要用到NSCoding协议) 7> 字符串拼接, 字符串(NSString)的方法: - (NSString *)componentsJoinedByString:(NSString *)separator; 这是NSArray的方法, 用separator作拼接符将数组元素拼接成一个字符串 8> 字符串分割, 数组(NSArray)的方法: - (NSArray *)componentsSeparatedByString:(NSString *)separator; 这是NSString的方法, 将字符串用separator作为分隔符切割成数组元素 9> NSMutableArray的特点: * NSMutableArray是NSArray的子类 * NSMutableArray是可变的, 随时可以往里面添加\更改\删除元素, NSArray是不可变的, 一旦初 始化完毕后, 它里面的内容就永远是固定的, 不能删除里面的元素, 也不能再往里面添加元素 * 创建一个空的NSMutableArray: NSMutableArray *array = [NSMutableArray array]; 10> NSMutableArray的常见方法: - (void)addObject:(id)object; 添加一个元素 - (void)addObjectsFromArray:(NSArray *)array; 添加otherArray的全部元素到当前数组中 - (void)insertObject:(id)anObject atIndex:(NSUInteger)index; 在index位置插入一个元素 - (void)removeLastObject; 删除最后一个元素 - (void)removeAllObjects; 删除所有的元素 - (void)removeObjectAtIndex:(NSUInteger)index; 删除index位置的元素 分区 第四天(@传智如意大师) 的第 219 页 - (void)removeObject:(id)object; 删除特定的元素 - (void)removeObjectsInRange:(NSRange)range; 删除range范围内的所有元素 - (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject; 用anObject替换index位置对应的元素, 等价于array[index] = anObject; - (void)exchangeObjectAtIndex:(NSUInteger)idx1 withObjectAtIndex:(NSUInteger)idx2; 交换idx1和idx2位置的元素 *** 注意: @[item1, item2, item3]这种方式创建的数组永远都是不可变的NSArray。 7. NSDictionary介绍 、 NSMutableDictionary介绍 1> NSDictionary是不可变的, 一旦初始化完毕, 里面的内容就无法修改 2> NSDictionary是以"键值对"的方式来保存数据。 3> NSDictionary的创建方式: + (instancetype)dictionary; + (instancetype)dictionaryWithObject:(id)object forKey:(id )key; // objectForKey,根据键取值。 + (instancetype)dictionaryWithObjectsAndKeys:(id)firstObject, ...; @{@"zs" : @"zhangsan", @"ls" : @"lisi", @"ww" : @"Wangwu"}; 4> NSDictionary的常见方法: - (NSUInteger)count; 返回字典的键值对数目 分区 第四天(@传智如意大师) 的第 220 页 - (id)objectForKey:(id)aKey; 根据key取出value, 等价于: dict[aKey]; * 快速遍历 for (NSString *key in dict) { } * Block遍历 [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { }]; ** 注意: 字典遍历的顺序是不一定的。] 5> 将字典中的内容写入到文件: * 将字典写入文件中 - (BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile; - (BOOL)writeToURL:(NSURL *)url atomically:(BOOL)atomically; * 将文件中的数据读取到字典中: + (id)dictionaryWithContentsOfFile:(NSString *)path; + (id)dictionaryWithContentsOfURL:(NSURL *)url; * 要求掌握: 1.直接调用dict 的wrieToFile方法 2.把多个dict加到array中调用array的writeToFile方法。 3.从文件中读取数据到字典 ** 注意: 字典中的键不要重复。(虽然重复也不报错,会自动取在前面的那个) 6> NSMutableDictionary * NSMutableDictionary是NSDictionary的子类 分区 第四天(@传智如意大师) 的第 221 页 * NSMutableDictionary是NSDictionary的子类 * NSDictionary是不可变的, 一旦初始化完毕后, 它里面的内容就永远是固定的, 不能删除里面 的元素, 也不能再往里面添加元素 * NSMutableDictionary是可变的, 随时可以往里面添加\更改\删除元素 7> NSMutableDictionary的常见操作 - (void)setObject:(id)anObject forKey:(id )aKey; 添加一个键值对(会把aKey之前对应的值给替换掉), 等价于: dict[@"name"] = @"Jack"; - (void)removeObjectForKey:(id)aKey; 通过aKey删除对应的value - (void)removeAllObjects; 删除所有的键值对 //通过dictionary属性快速初始化一个NSMutableDictionary dict.dictionary = @{@"name" : @"bob", @"age" : @"18"}; 8> 总结 * NSArray和NSDictionary的区别 1> NSArray是有序的,NSDictionary是无序的 2> NSArray是通过下标访问元素,NSDictionary是通过key访问元素 * NSArray的用法 1> 创建 @[@"Jack", @"Rose"] (返回是不可变数组) 2> 访问 id d = array[1]; 3> 赋值 array[1] = @"jack"; * NSDictionary的用法 1> 创建 @{ @"name" : @"Jack", @"phone" : @"10086" } (返回是不可变字典) 分区 第四天(@传智如意大师) 的第 222 页 @{ @"name" : @"Jack", @"phone" : @"10086" } (返回是不可变字典) 2> 访问 id d = dict[@"name"]; 3> 赋值 dict[@"name"] = @"jack"; 分区 第四天(@传智如意大师) 的第 223 页 思考&实现1: 统计出下面的字符串中“咳嗽”出现的次数: NSString *s1 = @"患者:「大夫,我咳嗽得很厉害。」大夫:「你多大 年纪?」患者:「七十五岁。」大夫:「二十岁咳嗽吗?」患者:「不咳 嗽。」大夫:「四十岁时咳嗽吗?」患者:「也不咳嗽。」大夫:「那现 在不咳嗽,还要等到什么时候咳嗽?」”; 20-【理解】作业布置 分区 第四天(@传智如意大师) 的第 224 页 1、知识回顾 分区 第五天(@传智如意大师) 的第 225 页 本小节知识点: 1、【理解】NSFileManager介绍 2、【理解】NSFileManager用法(常见的判断) 1、NSFileManager介绍 什么是NSFileManager 顾名思义, NSFileManager是用来管理文件系统的 它可以用来进行常见的文件\文件夹操作(拷贝、剪切、创建等) NSFileManager使用了单例模式singleton 使用defaultManager方法可以获得那个单例对象 [NSFileManager defaultManager] 2、NSFileManager基本(常见的判断) - (BOOL)fileExistsAtPath:(NSString *)path; path这个文件或文件夹(目录)是否存在 - (BOOL)fileExistsAtPath:(NSString *)path isDirectory:(BOOL *)isDirectory; path这个文件或文件夹是否存在, isDirectory代表是否为文件夹 2-【理解】NSFileManager介绍和用法 分区 第五天(@传智如意大师) 的第 226 页 - (BOOL)isReadableFileAtPath:(NSString *)path; path这个文件或文件夹是否可读 - (BOOL)isWritableFileAtPath:(NSString *)path; path这个文件或文件夹是否可写 分区 第五天(@传智如意大师) 的第 227 页 可写入的目录 - (BOOL)isDeletableFileAtPath:(NSString *)path; path这个文件或文件夹是否可删除 系统目录不允许删除 分区 第五天(@传智如意大师) 的第 228 页 分区 第五天(@传智如意大师) 的第 229 页 本小节知识点: 1、【理解】NSFileManager文件访问 1、NSFileManager文件访问 - (NSDictionary *)attributesOfItemAtPath:(NSString *)path error:(NSError **)error; 获得path这个文件\文件夹的属性 - (NSArray *)subpathsAtPath:(NSString *)path; 查找给定路径下的所有子路径,返回一个数组, 深度查找,不限于当前层,也会查找package的 内容。 - (NSArray *)subpathsOfDirectoryAtPath:(NSString *)path error:(NSError **)error; 获得path的所有子路径(后代路径),上面两个方法功能一样。 - (NSArray *)contentsOfDirectoryAtPath:(NSString *)path error:(NSError **)error; 3-【理解】NSFileManager用法深入(一) 分区 第五天(@传智如意大师) 的第 230 页 - (NSArray *)contentsOfDirectoryAtPath:(NSString *)path error:(NSError **)error; 获得path的当前子路径(path下的所有直接子内容,path必须是一个目录) - (NSData *)contentsAtPath:(NSString *)path; 获得文件内容 分区 第五天(@传智如意大师) 的第 231 页 本小节知识点: 1、【理解】NSFileManager文件操作 1、NSFileManager文件操作 - (BOOL)createDirectoryAtPath:(NSString *)path withIntermediateDirectories:(BOOL) createIntermediates attributes:(NSDictionary *)attributes error:(NSError **)error; 只能创建文件夹(createIntermediates为YES代表自动创建中间的文件夹) 注意如果要创建的目录已经存在,则本次创建失败 //1、定义路径 NSString *path = @"/Users/liwei/Desktop/test/subcc/"; //2、创建对象 NSFileManager *fileManager = [NSFileManager defaultManager]; //3、创建目录 // createDirectoryAtPath 路径 // withIntermediateDirectories 是否自动创建路径中的所有文件夹 // attributes 文件夹属性 // error 错误信息 BOOL flag = [fileManager createDirectoryAtPath:path withIntermediateDirectories:NO attributes:nil error:nil]; if (flag) { NSLog(@"目录创建成功!"); //写入文件 NSString *str =@"测试文件内容"; flag = [str writeToFile:[NSString stringWithFormat:@"%@/1.txt",path] atomically:YES encoding:NSUTF8StringEncoding error:nil]; 4-【理解】NSFileManager用法深入(二) 分区 第五天(@传智如意大师) 的第 232 页 if (flag) { NSLog(@"文件写入成功!"); } } - (BOOL)copyItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error; 拷贝,如果目标目录已经存在同名文件,则无法拷贝 - (BOOL)moveItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error; 移动(剪切) 分区 第五天(@传智如意大师) 的第 233 页 - (BOOL)removeItemAtPath:(NSString *)path error:(NSError **)error; 删除 - (BOOL)createFileAtPath:(NSString *)path contents:(NSData *)data attributes:(NSDictionary *)attr; 创建文件 (NSData是用来存储二进制字节数据的) // 把字符串转换为NSData NSString *s1 =@"hello”; NSData *data = [s1 dataUsingEncoding:NSUTF8StringEncoding]; 分区 第五天(@传智如意大师) 的第 234 页 分区 第五天(@传智如意大师) 的第 235 页 本小节知识点: 1、【理解】NSFileManager文件下载思路 1、NSFileManager文件下载思路 1.发送请求给服务器,要求下载某个文件 2.服务器发出响应,返回文件数据 3.手机客户端利用NSData来存放服务器返回的文件数据 4.利用NSFileManager将NSData里面的文件数据写到新的文件中.(createFileAtPath) 5-【理解】文件下载思路 分区 第五天(@传智如意大师) 的第 236 页 本小节知识点: 1、【了解】iOS沙盒(sandbox)机制及获取沙盒路径 1、iOS沙盒(sandbox)机制及获取沙盒路径 1)沙盒的基本概念和作用 每个ios应用都有自己的应用沙盒,应用沙盒就是文件系统目录,与其他应用的文件系统隔离, ios系统不允许访问其他应用的应用沙盒。在ios8中已经开放访问(extension)。 扩展阅读: extension是iOS8新开放的一种对几个固定系统区域的扩展机制,它可以在一定程度上弥补iOS的 沙盒机制对应用间通信的限制。 extension的出现,为用户供了在其它应用中使用我们应用供的服务的便捷方式,比如用户可 以在Today的widgets中查看应用展示的简略信息,而不用再进到我们的应用中,这将是一种全新 的用户体验;但是,extension的出现可能会减少用户启动应用的次数,同时还会增大开发者的工 作量。 应用沙盒一般包括以下几个文件目录:应用程序包、Documents、Libaray(下面有Caches和 Preferences目录)、tmp。 应用程序包:包含所有的资源文件和可执行文件。 Documents:保存应用运行时生成的需要持久化的数据,iTunes会自动备份该目录。苹果建议将程 序中建立的或在程序中浏览到的文件数据保存在该目录下,iTunes备份和恢复的时候会包括此目 录 tmp:保存应用运行时所需的临时数据,使用完毕后再将相应的文件从该目录删除。应用没有运行 时,系统也有可能会清除该目录下的文件,iTunes不会同步该目录。iphone重启时,该目录下的 文件会丢失。 Library:存储程序的默认设置和其他状态信息,iTunes会自动备份该目录。 Libaray/Caches:存放缓存文件,iTunes不会备份此目录,此目录下文件不会在应用退出删除。一 般存放体积比较大,不是特别重要的资源。 Libaray/Preferences:保存应用的所有偏好设置,ios的Settings(设置)应用会在该目录中查找 6-【了解】iOS沙盒(sandbox)机制 分区 第五天(@传智如意大师) 的第 237 页 Libaray/Preferences:保存应用的所有偏好设置,ios的Settings(设置)应用会在该目录中查找 应用的设置信息,iTunes会自动备份该目录。 2)沙盒的位置 (1)Xcode5中模拟器路径为:/Users/用户名/Library/Application Support/iPhone Simulator (2)Xcode6中模拟器路径为:/Users/用户名/ Library/Developer/CoreSimulator 下面给大家看看我的app沙盒找法 分区 第五天(@传智如意大师) 的第 238 页 其中Devices文件夹下的16个文件对应Xcode6下的16个模拟器,可以根据各个文件夹下的 device.plist文件得到具体的是哪个模拟器: 3)获取沙盒路径 获取沙盒根目录的方法,有以下几种: //Home目录 NSString *homeDirectory = NSHomeDirectory(); 分区 第五天(@传智如意大师) 的第 239 页 获取绝对 路径的方法 NSSearchPathForDirectoriesInDomains 返回一个绝对路径用来存放我们需要储存的文件 第一个参数:要获取的沙盒文件夹名称 第二个参数: NSUserDomainMask = 1,//用户主目录中 NSLocalDomainMask = 2,//当前机器中 NSNetworkDomainMask = 4,//网络中可见的主机 NSSystemDomainMask = 8,//系统目录,不可修改(/System) NSAllDomainsMask = 0x0ffff,//全部 第三个参数:YES/NO 是否获取全路径 YES NO //Document目录 NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomai nMask, YES); NSString *path = [paths objectAtIndex:0]; //Cache目录 NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainM ask, YES); NSString *path = [paths objectAtIndex:0]; //Libaray目录 NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomain Mask, YES); 分区 第五天(@传智如意大师) 的第 240 页 Mask, YES); NSString *path = [paths objectAtIndex:0]; //tmp目录 NSString *tmpDir = NSTemporaryDirectory(); 常见用法: 在沙盒文件中创建文件夹 如:在Document目录下创建新的文件夹test 分区 第五天(@传智如意大师) 的第 241 页 本小节知识点: 1、【理解】NSPoint和CGPoint的使用 2、【理解】NSSize和CGSize的使用 3、【理解】NSRect和CGRect的使用 4、【理解】常见的结构体使用注意 1、NSPoint和CGPoint的使用 CGPoint和NSPoint是同义的 typedef CGPoint NSPoint; CGPoint的定义 struct CGPoint { CGFloat x; CGFloat y; }; typedef struct CGPoint CGPoint; typedef double CGFloat; //64位是double, 32位是float CGPoint代表的是二维平面中的一个点 可以使用CGPointMake和NSMakePoint函数创建CGPoint 2、NSSize和CGSize的使用 CGSize和NSSize是同义的 typedef CGSize NSSize; CGSize的定义 struct CGSize { CGFloat width; CGFloat height; }; 7-【理解】常见的结构体 分区 第五天(@传智如意大师) 的第 242 页 }; typedef struct CGSize CGSize; CGSize代表的是二维平面中的某个物体的尺寸(宽度和高度) 可以使用CGSizeMake和NSMakeSize函数创建CGSize CGRect和NSRect是同义的 typedef CGRect NSRect; CGRect的定义 struct { CGPoint origin; CGSize size; }; typedef struct CGRect CGRect; CGRect代表的是二维平面中的某个物体的位置和尺寸 可以使用CGRectMake和NSMakeRect函数创建CGRect 3、NSRect和CGRect的使用 4、常见的结构体使用注意 CGPoint CGSize CGRect 苹果官方推荐使用CG开头的: 分区 第五天(@传智如意大师) 的第 243 页 本小节知识点: 1、【理解】NSNumber的介绍和作用 2、【理解】NSNumber的创建 3、【理解】从NSNumber对象中的到基本类型数据 1、NSNumber的介绍和作用 NSArray\NSDictionary中只能存放OC对象, 不能存放int\float\double等基本数据类 如果真想把基本数据(比如int)放进数组或字典中, 需要先将基本数据类型包装成OC对象 NSNumber可以将基本数据类型包装成对象,这样就可以间接将基本数据类型存进NSArray \NSDictionary中 2、NSNumber的创建 以前的写法 + (NSNumber *)numberWithInt:(int)value; + (NSNumber *)numberWithDouble:(double)value; + (NSNumber *)numberWithBool:(BOOL)value; 8-【理解】NSNumber的使用 分区 第五天(@传智如意大师) 的第 244 页 现在的写法 @10; @10.5; @YES; @(num); // 如果是变量必须加() @这种写法,只能包装数字,返回值就是NSNumber *类型。 - (char)charValue; - (int)intValue; - (long)longValue; - (double)doubleValue; 2、从NSNumber对象中的到基本类型数据 分区 第五天(@传智如意大师) 的第 245 页 - (double)doubleValue; - (BOOL)boolValue; - (NSString *)stringValue; - (NSComparisonResult)compare:(NSNumber *)otherNumber; - (BOOL)isEqualToNumber:(NSNumber *)number; 分区 第五天(@传智如意大师) 的第 246 页 本小节知识点: 1、【理解】NSValue的介绍和使用 1、NSValue的介绍和使用 NSValue包装对象指针,CGRect结构体等 一个NSValue对象是用来存储一个C或者Objective-C数据的简单容器。它可以保存任意类型的数 据,比如int,float,char,当然也可以是指pointers, structures, and object ids。NSValue 类的目标就是允许以上数据类型的数据结构能够被添加到集合里,例如那些需要其元素是对象的 数据结构,如NSArray或者NSSet的实例。需要注意的是NSValue对象一直是不可枚举的。 为了方便 结构体 和 NSValue 的转换, Foundation供了以下方法 将结构体包装成NSValue对象 -------> 存储结构体到集合中 + (NSValue *)valueWithPoint:(NSPoint)point; + (NSValue *)valueWithSize:(NSSize)size; + (NSValue *)valueWithRect:(NSRect)rect; 从NSValue对象取出之前包装的结构体 - (NSPoint)pointValue; - (NSSize)sizeValue; - (NSRect)rectValue; 不能直接放NSPoint类型的数据在数组中 使用 9-【理解】NSValue的介绍和使用 分区 第五天(@传智如意大师) 的第 247 页 创建一个NSValue value:对象地址 objCType:通常是一个用来述对象类型和大小的字符串,@encode可以接受一个数据类型 的名称自动生成一个合适的述字符串 1、+ (NSValue *)valueWithBytes:(const void *)value objCType:(const char *)type; 从接受value的对象中 取数值 取的数值,存放在这个指针所指向的内存块里。 2、- (void)getValue:(void *)value(出参); typedef struct { int year; int month; int day; } Date; Date d = {2015, 2, 1}; // 将结构体d包装成NSValue对象 NSValue *dValue = [NSValue valueWithBytes:&d objCType:@encode(Date)]; NSArray *array = @[dValue]; // 取出value之前包装的结构体数据 Date newD; [dValue getValue:&newD]; 分区 第五天(@传智如意大师) 的第 248 页 本小节知识点: 1、【理解】NSDate的介绍和使用 2、【理解】格式化日期 3、【理解】计算日期 4、【理解】日期时间对象 NSDate可以用来表示时间, 可以进行一些常见的日期\时间处理 一个NSDate对象就代表一个时间 [NSDate date]返回的就是当前时间 获取当前时区的时间, 1、NSDate的介绍和使用 修改办法: NSDate *d = [NSDate date]; NSLog(@"d = %@",d); //设置时区 NSTimeZone *zone = [NSTimeZone systemTimeZone]; //设置时间间隔 NSInteger interval = [zone secondsFromGMTForDate: d]; //重新生成时间 NSDate *localeDate = [d dateByAddingTimeInterval: interval]; NSLog(@"%@", localeDate); 10-【理解】NSDate的介绍和使用 分区 第五天(@传智如意大师) 的第 249 页 2、格式化日期 NSDate ----> dateString //定义NSDate NSDate *d1 = [NSDate date]; //定义日期时间格式化的类 NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss"; //把Date转换为dataStr NSString *dateStr = [formatter stringFromDate:d1]; // 设置格式 // fmt.dateFormat = @"今天 HH:mm"; // HH : 24小时制 // hh : 12小时制 // yyyy : 年 // MM : 月 // dd : 号 // mm : 分钟 // ss : 秒 // Z : 时区 fmt.dateFormat = @"yyyy年MM月dd号 hh:mm:ss Z"; dataString ----->Date //定义日期时间格式化的类 NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss"; //把dataStr转换为Date NSDate *date = [formatter dateFromString:@"2015-02-01 02:30:19"]; NSLog(@"%@",date); 3、计算日期 分区 第五天(@传智如意大师) 的第 250 页 3、计算日期 //1)明天的此刻 NSTimeInterval secondsPerDay = 24*60*60; NSDate *tomorrow = [NSDate dateWithTimeIntervalSinceNow:secondsPerDay]; NSLog(@"myDate = %@",tomorrow); //2)昨天的现在 NSTimeInterval secondsPerDay1 = 24*60*60; NSDate *now = [NSDate date]; NSDate *yesterDay = [now addTimeInterval:-secondsPerDay1]; NSLog(@"yesterDay = %@",yesterDay); 4、日期时间对象 结合NSCalendar和NSDate能做更多的日期\时间处理 获得NSCalendar对象 NSCalendar *calendar = [NSCalendar currentCalendar]; 获得年月日 - (NSDateComponents *)components:(NSCalendarUnit)unitFlags fromDate:(NSDate *)date; //创建日期 NSDate *d = [NSDate date]; //创建日期对象 NSCalendar *ca = [NSCalendar currentCalendar]; //获得时间组件 NSDateComponents *cms= [ca components:NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay fromDate:d]; NSLog(@"%ld-%ld-%ld",cms.year,cms.month,cms.day); 比较两个日期的差距 - (NSDateComponents *)components:(NSCalendarUnit)unitFlags fromDate:(NSDate *) startingDate toDate:(NSDate *)resultDate options:(NSCalendarOptions)opts; NSString *time1 = @"2014-04-08 20:50:40"; NSString *time2 = @"2014-04-04 18:45:30"; NSDateFormatter *fmt = [[NSDateFormatter alloc] init]; fmt.dateFormat = @"yyyy-MM-dd HH:mm:ss"; NSDate *date1 = [fmt dateFromString:time1]; NSDate *date2 = [fmt dateFromString:time2]; // 1.创建一个日历对象 NSCalendar *calendar = [NSCalendar currentCalendar]; 分区 第五天(@传智如意大师) 的第 251 页 NSCalendar *calendar = [NSCalendar currentCalendar]; // 2.比较时间的差距 int unit = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond; NSDateComponents *cmps = [calendar components:unit fromDate:date2 toDate:date1 options:0]; NSLog(@"相差%ld年%ld月%ld天%ld小时%ld分钟%ld秒", cmps.year, cmps.month, cmps.day, cmps.hour, cmps.minute, cmps.second); 分区 第五天(@传智如意大师) 的第 252 页 本小节知识点: 1、【掌握】集合对象的内存管理 2、【理解】集合对象内存管理总结 当一个对象加入到集合中,那么该对象的引用计数会+1 当集合被销毁的时候,集合会向集合中的元素发送release消息 NSArray *arr = @[.........]; //会调用autolease方法 NSArray *arr2 = [[NSArray alloc] init]; //需要我们手工 [arr release]; [arr2 release]; 1、集合对象的内存管理 11-【掌握】集合对象的内存管理 分区 第五天(@传智如意大师) 的第 253 页 [arr2 release]; 思考&实现1: 如果数组arr先 release了,对我们的对象会有影响吗? 分区 第五天(@传智如意大师) 的第 254 页 autorelease 的影响 2、集合内存管理总结 1.官方内存管理原则 1> 当调用alloc、new、copy(mutableCopy)方法产生一个新对象的时候,就必须在最后调用一 次release或者autorelease 分区 第五天(@传智如意大师) 的第 255 页 次release或者autorelease 2> 当调用retain方法让对象的计数器+1,就必须在最后调用一次release或者autorelease 2.集合的内存管理细节 1> 当把一个对象添加到集合中时,这个对象会做了一次retain操作,计数器会+1 2> 当一个集合被销毁时,会对集合里面的所有对象做一次release操作,计数器会-1 3> 当一个对象从集合中移除时,这个对象会一次release操作,计数器会-1 3.普遍规律 1> 如果方法名是add\insert开头,那么被添加的对象,计数器会+1 2> 如果方法名是remove\delete开头,那么被移除的对象,计数器-1 分区 第五天(@传智如意大师) 的第 256 页 本小节知识点: 1、【理解】copy的概念 2、【理解】copy快速入门 1)什么是copy Copy的字面意思是“复制”、“拷贝”,是一个产生副本的过程 对象拷贝的目的:要使用某个对象的数据,但是在修改对象的时候不影响原来的对象内容。 常见的复制有用一个源文件产生:文件复制 作用:利一个副本文件 2)特点: 修改源文件的内容,不会影响副本文件 修改副本文件的内容,不会影响源文件 OC中的copy,就是指的对象的拷贝 作用:利用一个源对象产生一个副本对象 修改源对象的属性和行为,不会影响副本对象 修改副本对象的属性和行为,不会影响源对象 3)如何使用copy功能 一个对象可以调用copy或mutableCopy方法来创建一个副本对象 copy : 创建的是不可变副本(如NSString、NSArray、NSDictionary) mutableCopy :创建的是可变副本(如NSMutableString、NSMutableArray、NSMutableDictionary) 4)使用copy功能的前提 copy : 需要遵守NSCopying协议,实现copyWithZone:方法 @protocol NSCopying - (id)copyWithZone:(NSZone *)zone; @end mutableCopy : 需要遵守NSMutableCopying协议,实现mutableCopyWithZone:方法 @protocol NSMutableCopying 1、对象copy的概念 12-【理解】copy概念及入门 分区 第五天(@传智如意大师) 的第 257 页 @protocol NSMutableCopying - (id)mutableCopyWithZone:(NSZone *)zone; @end 2、copy快速入门 以NSString 字符串为例,进行copy 和 mutableCopy 演示 copy 就是浅复制,因为没有产生新的对象(有没有产生新的对象也是判断深浅复制的标准) 此处的copy 后产生还是不可变的字符串 mutableCopy 就是深复制,会再内存中重新分配一块内存空间 分区 第五天(@传智如意大师) 的第 258 页 还有一种情况的深拷贝 分区 第五天(@传智如意大师) 的第 259 页 分区 第五天(@传智如意大师) 的第 260 页 本小节知识点: 1、【理解】copy与内存管理 2、【理解】深浅拷贝总结 1、copy与内存管理 注意为了确保测试通过,最好创建iOS项目测试 浅复制, 源对象的引用计数器+1 NSString的copy 深复制, 创建了一个新对象,新对象的引用计数器是1 NSString的mutableCopy 13-【理解】copy与内存管理 分区 第五天(@传智如意大师) 的第 261 页 • 2、copy与内存管理 深复制(深拷贝,内容拷贝,deep copy) 源对象和副本对象是不同的两个对象 源对象引用计数器不变, 副本对象计数器为1(因为是新产生的) 本质是:产生了新的对象 浅复制(浅拷贝,指针拷贝,shallow copy) 源对象和副本对象是同一个对象 源对象(副本对象)引用计数器 + 1, 相当于做一次retain操作 本质是:没有产生新的对象 分区 第五天(@传智如意大师) 的第 262 页 本小节知识点: 1、【理解】@property中的copy的作用 2、【理解】@property内存管理策略选择 分析代码: //创建可变字符串 NSMutableString *str = [NSMutableString string]; //设定字符串的内容 str.string = @"zhangsan"; //创建对象 Person *person = [Person new]; //给person的实例变量赋值 person.name = str; //修改字符串内容 [str appendString:@"xxxx"]; NSLog(@"name = %@",person.name); NSLog(@"str = %@",str); 这显然不符合我们的要求,因为str修改后,会影响person.name 的值 解决方法: @property (nonatomic,copy) NSString *name; set方法展开形式为: - (void)setName:(NSString *)name { if (_name != name) { [_name release]; 1、@property中copy的作用 14-【理解】@property中的copy关键字 分区 第五天(@传智如意大师) 的第 263 页 [_name release]; _name = [name copy]; } } @property内存管理策略的选择 1.非ARC 1> copy : 只用于NSString\block 2> retain : 除NSString\block以外的OC对象 3> assign : 基本数据类型、枚举、结构体(非OC对象),当2个对象相互引用,一端用 retain,一端用assign 2.ARC 1> copy : 只用于NSString\block 2> strong : 除NSString\block以外的OC对象 3> weak : 当2个对象相互引用,一端用strong,一端用weak 4> assgin : 基本数据类型、枚举、结构体(非OC对象) 2、@property内存管理策略选择 分区 第五天(@传智如意大师) 的第 264 页 本小节知识点: 1、【理解】为自定义类实现copy操作 自定义对象copy步骤 新建Person类 1.让Person类遵守NSCopying协议 2.实现 copyWithZone: 方法, 在该方法中返回一个对象的副本即可。 3. 在copyWithZone方法中, 创建一个新的对象,并设置该对象的数据与现有对象一致, 并返回该对象. 为Person类实现copy操作 创建Person对象, 调用copy方法, 查看地址。 1. 调用copy其实就是调用copyWithZone方法,所以要实现copyWithZone方法。(查看 NSObject协议中的copy方法的介绍) 2. copyWithZone方法返回值类型是id类型, 需要返回一个对象的副本。 3. 关于copyWithZone的参数zone问题: * zone: 表示空间,分配对象是需要内存空间的,如果指定了zone,就可以指定 新建对象对应的内存空间。但是:zone是一个非常古老的技术,为了避免在堆中出现 内存碎片而使用的。在今天的开发中,zone几乎可以忽略 * 查看NSObject协议中的allocWithZone:方法介绍(zone参数可以被忽略,是历史 原因) 4.如果对象没有 可变/不可变 的版本区别,只要实现 copyWithZone 方法即可. 5. copyWithZone:方法的具体实现: - (id)copyWithZone:(NSZone *)zone{ //创建对象 Person *p1 = [[Person alloc] init]; //用当前对象值给新对象的实例变量赋值 p1.age = self.age; //返回新对象 return p1; } 细节介绍: 1、为自定义类实现copy操作 15-【理解】为自定义的类实现copy操作 分区 第五天(@传智如意大师) 的第 265 页 为Person类实现mutableCopy操作 1.遵守NSMutableCopying协议 2.实现协议对你的方法 - (id)mutableCopyWithZone:(NSZone *)zone{ //创建对象 Person *p1 = [[Person alloc] init]; //用当前对象值给新对象的实例变量赋值 p1.age = self.age; //返回新对象 return p1; } 分区 第五天(@传智如意大师) 的第 266 页 本小节知识点: 1、【理解】单例模式概念 2、【理解】简单的单例模式实现 1)什么是单例模式:(Singleton) 单例模式的意图是是的类的对象成为系统中唯一的实例,供一个访问点,供客户类 共享资源。 2)什么情况下使用单例? 1、类只能有一个实例,而且必须从一个为人熟知的访问点对其进行访问,比如工厂方 法。 2、这个唯一的实例只能通过子类化进行扩展,而且扩展的对象不会破坏客户端代码。 3)单例设计模式的要点: (1) 某个类只能有一个实例。 (2)他必须自行创建这个对象 (3)必须自行向整个系统供这个实例; (4)为了保证实例的唯一性,我们必须将 -(id)copyWithZone:(NSZone *)zone +(id)allocWithZone:(NSZone *)zone -(id)retain -(NSUInteger)retainCount -(oneway void)release -(id)autorelease 的实现方法进行覆盖。 (5)这个方法必须是一个静态类 4)在OC中实现单例模式: 先创建一个单例类,即: #import @interface Bee : NSObject//注意此处调用了NScoping协议 +(Bee *)shareIsrance;//此处定义了一个工厂方法,用工厂方法来限制实例化过程 (下文中将会到) @end //调用NScoping协议,从而在实现文件中覆盖 1、单例模式概念 16-【理解】简单的单例模式实现 分区 第五天(@传智如意大师) 的第 267 页 @end //调用NScoping协议,从而在实现文件中覆盖 2、单例模式实现 Tools.h #import @interface Tools : NSObject @property (nonatomic,assign) int num; @property (nonatomic,copy) NSString *text; +(instancetype)shareIntances; @end Tools.m static Tools *instance = nil; @implementation Tools +(instancetype)shareIntances{ if (instance==nil) { instance = [[self allocWithZone:NULL] init]; } return instance; } -(id)copyWithZone:(NSZone *)zone{ return self; } +(id)allocWithZone:(NSZone *)zone{ @synchronized(self) { if (instance == nil) { instance = [super allocWithZone:zone]; return instance; } } return instance; } -(id)retain{ return self; } -(NSUInteger)retainCount{ #import "Tools.h" 分区 第五天(@传智如意大师) 的第 268 页 return NSUIntegerMax; } -(oneway void)release{ } -(id)autorelease{ return self; } @end 注意:所谓单例,即我们自己创建一个单例类,该类只能生成唯一的对象,即 用 if (sharesingleton == nil) 方法来保证唯一性,为了防止该对象被复制(copy) 或者retain 和 release 等操作,我们必须在所创建的单例的实现文件( .m 文件)中将父 类的这些方法给覆盖,该目的是为了保证单例模式的一个严谨性。 分区 第五天(@传智如意大师) 的第 269 页
还剩268页未读

继续阅读

pdf贡献者

jiyug108

贡献于2015-08-19

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!