Objective-C
1. import的用法
- 拷贝文件内容
可以自动防止文件的内容被重复拷贝(#define宏定义) - Foundation 框架头文件的路径
Xcode.app 显示包内容
Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/Foundation.framework - 命令行指令
- 编写 Oc 源文件: .m .c
- 编译: cc -c xxx.m xxx.c
- 链接: cc xxx.o xxx.o -framework Foundation(用到的时候才加)
- 运行: ./a.out
- 主头文件
主头文件:最主要的头文件,名字一般跟框架名称一样,包含了框架中的所有其他头文件 - Foundation框架的主头文件名称就是Foundation.h
只需要包含Foundation框架主头文件,就可以使用整个框架的东西
2. 对象方法和类方法
- 对象方法: - 开头
- 只能由对象调用
- 对象方法中能访问当前对象的成员变量(实例变量)
- 类方法: + 开头
- 只能由类名来调用
- 类方法中不能访问成员变量
- 类方法的优点和使用场合:
- 不依赖于对象,执行效率高
- 能用类方法,尽量用类方法
- 场合:当方法内部不需要使用到成员变量时,就用类方法
- 可以允许类方法和对象方法同名
3. 成员变量和局部变量
-
成员变量:
- 写在类声明的大括号中的变量, 我们称之为 成员变量(属性, 实例变量)成员变量只能通过对象来访问
- 注意: 成员变量不能离开类,,离开类之后就不是成员变量,成员变量不能在定义的同时进行初始化
- 存储: 堆(当前对象对应的堆的存储空间中)
- 存储在堆中的数据, 不会被自动释放, 只能程序员手动释放
局部变量:
-
写在函数或者代码块中的变量, 我们称之为局部变量
- 作用域: 从定义的那一行开始, 一直到遇到大括号或者return
- 局部变量可以先定义再初始化, 也可以定义的同时初始化
- 存储 : 栈
- 存储在栈中的数据有一个特点, 系统会自动给我们释放
-
全局变量
- 写在函数和大括号外部的变量, 我们称之为全局变量
- 作用域: 从定义的那一行开始, 一直到文件末尾
- 局部变量可以先定义在初始化, 也可以定义的同时初始化
- 存储: 静态区
- 程序一启动就会分配存储空间, 直到程序结束才会释放
4. 方法和函数
- 方法
- 函数属于整个文件,, 方法属于某一个类,方法如果离开类就不行
- 函数可以直接调用, 方法必须用对象或者类来调用
- 函数
- 能写在文件中的任意位置(@interface和@end之间除外),函数归文件所有
- 函数调用不依赖于对象
- 函数内部不能直接通过成员变量名访问某个对象的成员变量
5. setter 和 getter 用法简介
- setter
- 作用: 给成员变量赋值
- 格式:
- 必须是对象方法
- 一定没有返回值
- 方法名称一定以set开头, set后面跟上成员变量的名称, 并去掉下划线, 然后将首字母大写
- 一定有参数, 并且参数类型和成员变量的类型一致, 参数名称就是成员变量的名称去掉下划线
- getter
- 作用: 返回成员变量的值
- 格式:
- 必须是对象方法
- 一定有返回值, 返回值类型和成员变量的类型一致
- 方法名称就是成员变量的名称去掉下划线
- 一定没有参数
- readLoad 和 readWrite
- 一个属性可以只有getter方法, 没有setter方法, 这种属性我们称之为只读属性
- 一个属性也可以只有setter方法, 没有getter方法, 这种属性我们称之为只写属性
- 如果既有setter方法又有getter方法, 那么这种属性我们称之为可读可写的属性
- 一个属性也可以没有getter和setter, 这种属性我们称之为私有属性
6. self 的基本使用
- 用法
- 那个调用了当前方法,self就代表谁
- self出现在对象方法中, self就代表对象
- self出现在类方法中, self就代表类
- 在对象方法利用 "self->成员变量名” 访问当前对象内部的成员变量(实例变量)
- [self 方法名] 可以调用其他对象方法\类方法
- self会自动区分类方法和对象方法, 如果在类方法中使用self调用对象方法, 那么会直接报错
- 不能在对象方法或者类方法中利用self调用当前self所在的方法
- 动态绑定:
动态类型能使程序直到执行时才确定对象的真实类型
动态类型绑定能使程序直到执行时才确定要对那个对象调用的方法 - 使用self调用本方法,导致死循环调用
- #pragma mark
- 将代码分隔开,方便我们进行查找。
7. super 基本使用
- 编译器指令符号.
- 利用super给父类的方法发送一个消息, 那么系统就会自动调用父类的方法
- 如果以后想在子类中调用父类的方法可以使用super
- 如果想在给父类方法进行扩展的同时保留父类的方法, 那么可以使用super调用父类同名的方法
- super 在什么方法中就调用父类的什么方法
8. 面向对象基本思想
封装
- 原理:屏蔽内部实现的细节,仅仅对外提供共有的方法/接口
- 好处:保证数据的安全性
- 规范:一般情况下不会对外直接暴露成员变量, 都会提供一些共有的方法进行赋值成员变量都需要封装起来
继承
- 父类必须声明在子类的前面
- 不允许子类和父类拥有相同名称的成员变量, 因为子类继承父类,子类将会拥有父类的所有成员变量,若在子类中定义父类同名成员变量 属于重复定义。
- 调用某个对象的方法时, 优先去当前类中找, 如果么有, 去父类中找
- 基类的私有属性能被继承, 不能在子类中访问。
- OC中的继承是单继承:也就是说一个类只能一个父类, 不能继承多个父类
- 缺点:耦合性太强
多态
- 事物的多种形态
- 没有继承就没有多态
- 代码的体现: 父类类型的指针指向子类对象
- 好处: 如果函数\方法参数中使用的是父类类型, 可以传入父类, 子类对象
- 局限性: 父类类型的变量 不能 直接调用子类特有的方法,必须强转为子类类型变量后, 才能直接调用子类特有的方法
- 动态绑定:
- 动态类型能使程序直到执行时才确定对象的真实类型
- 动态类型绑定能使程序直到执行时才确定要对那个对象调用的方法
假设 子类 Dog 有一个特有的方法bark [dog bark]; Animal *an = [Dog new]; [(Dog*)an bark]; //把父类的指针,强制类型转换
9. 成员变量的作用域
- @public 在任何地方都能直接访问对象的成员变量
- @private 只能在当前类的对象方法中直接访问(子类可以通过seter geter方法访问父类的私有的成员变量)
- @protected 能在当前类和子类的对象方法中直接访问 (默认是 protected)
- @package 只要处在同一个框架中, 就能直接访问对象的成员变量(不常用)
点语法使用注意 - 点语法的本质还是方法调用
p.age = 10; // [p setAge:10]
- 引发死循环
self.age = age; // [self setAge:age]
- 私有成员变量
- 写在@implementation 中的成员变量,默认就是私有成员变量,并且和利用@private 修饰的不太一样,@implementation 中定义的成员变量在其他类中无法查看,也无法访问
在@implementation 中定义的私有变量只能在本类中查看 - 私有方法:只有实现没有声明,OC 中美有真正的私有方法,因为 OC 是消息机制。私有方法外面不能访问,只能通过包装成 sel 就可以访问
10. @property 用法
Property 编译器指令
-
生成setter 和 getter 方法声明(未加强版)
-(void)setAge:(int)age; -(int)age; @property int age;
@synthesize age = _age;
setter和getter实现中会访问成员变量_age, 如果成员变量_age不存在,就会自动生成一个@private的成员变量_age
@synthesize age;
setter和getter实现中会访问@synthesize后同名成员变量age
如果成员变量age不存在,就会自动生成一个@private的成员变量age-
多个属性可以通过一行@synthesize搞定,多个属性之间用逗号连接
@synthesize age = _age, number = _number, name = _name;
Property 增强
只要利用一个@property就可以同时生成setter/getter方法的声明和实现传入的属性赋值给
_开头的成员变量
@property有一个弊端: 它只会生成最简单的getter/setter方法的声明和实现, 并不会对传入的数据进行过滤
如果想对传入的数据进行过滤, 那么我们就必须重写getter/setter方法如果不想对传入的数据进行过滤, 仅仅是提供一个方法给外界操作成员变量, 那么就可以使用@property
注意:
如果没有会自动生成一个_开头的成员变量,自动生成的成员变量是私有变量, 声明在.m中,在其它文件中无法查看,但当可以在本类中查看
-
有就不生成,没有就生成
- 如果重写了setter方法, 那么property就只会生成getter方法
- 如果重写了getter方法, 那么property就只会生成setter方法
- 如果同时重写了getter/setter方法, 那么property就不会自动帮我们生成私有的成员变量
@property(属性修饰符) 数据类型 变量名称;
readwrite:代表生成 getter 和 setter 方法,默认就是
readonly:代表只生成 getter 方法,(只读)修改 getter 方法名(常用)
程序员之间有一个约定, 一般情况下获取BOOL类型的属性的值, 我们都会将获取的方法名称改为isXXX
11. new alloc init 的基本用法及区别
- alloc
- 开辟存储空间
- 将所有成员变量设为0
- 返回当前的对象地址
- init
- 初始化成员变量, 但是默认情况下init的实现是什么都没有做 2.返回初始化后的实例对象地址
- alloc和 init 返回的地址是一样的
12. id和 instancetype
- 静态类型和动态类型
- 静态类型:将一个指针变量定义为特定类的对象时,使用的是静态类型,在编译的时候就知道这个指针变量所属的类,这个变量总是存储特定类的对象。
Person *p = [Person alloc] init]]
- 动态类型:这一特性是程序直到执行时才确定对象所属的类
id p = [[Person alloc] init];
- Id
- id 是一种通用的对象类型,它可以指向属于任何类的对象,也可以理解为万能指针
- id是动态类型,所以可以通过id类型直接调用指向对象中的方法, 编译器不会报错
- 优点
- 通过静态数据类型定义变量, 不能调用子类特有的方法
- 通过动态数据类型定义变量, 可以调用子类特有的方法
- 通过动态数据类型定义的变量, 可以调用私有方法
- 弊端: 由于动态数据类型可以调用任意方法, 所以有可能调用到不属于自己的方法, 而编译时又不会报错, 所以可能导致运行时的错误
- 应用场景
- 多态, 可以减少代码量, 避免调用子类特有的方法需要强制类型转换
- 为了避免动态数据类型引发的运行时的错误, 一般情况下如果使用动态数据类型定义一个变量, 在调用这个变量的方法之前会进行一次判断, 判断当前变量是否能够调用这个方法
id obj = [Student new];
[obj isKindOfClass:[Student class]]
//isKindOfClass , 判断指定的对象是否是某一个类, 或者是某一个类的子类
- instancetype
- instancetype == id == 万能指针 == 指向一个对象
- id在编译的时候不能判断对象的真实类型
- instancetype在编译的时候可以判断对象的真实类型
- id和instancetype除了一个在编译时不知道真实类型, 一个在编译时知道真实类型以外, 还有一个区别
- id可以用来定义变量, 可以作为返回值, 可以作为形参
- instancetype只能用于作为返回值
- 注意: 以后但凡自定义构造方法, 返回值尽量使用instancetype, 不要使用id
13. 构造方法(- 开头的对象方法)
用来初始化对象的方法.
重写构造方法的注意:
先调用父类的构造方法.
再进行子类内部的成员变量的初始化
-
返回当前对象的地址
-(instancetype)init { // 注意: 不要把 = 号写为 == // 一定要将[super init]的返回值赋值给self if (self = [super init]) { // 初始化子类 _age = 6; } return self; }
自定义构造方法
自己做自己的事情
父类的属性交给父类的方法来处理,子类的方法处理子类自己独有的属性
自定义构造方法必须以intiWith开头,并且’W’必须大写
类工厂方法:
用于快速创建对象的类方法, 我们称之为类工厂方法
类工厂方法中主要用于 给对象分配存储空间和初始化这块存储空间
-
规范:
- 一定是类方法 +
- 方法名称以类的名称开头, 首字母小写
- 一定有返回值, 返回值是id/instancetype
- 注意: 以后但凡自定义类工厂方法, 在类工厂方法中创建对象一定不要使用类名来创建,
一定要使用self来创建
return [[self alloc] init];
14. Category - 分类
- 在不改变原来类模型的前提下, 给类扩充一些方法. 有2种方式 : 继承 分类
- 好处: 一个庞大的类可以分模块开发, 一个庞大的类可以由多个人来编写, 便于团队合作.
- 使用注意:
- 分类中写property, 只会生成getter/setter方法的声明, 不会生成实现和私有成员变量
- Category 可以
访问原始类的成员变量
, 但不能添加变量, 只能添加方法. 如果想 添加变量, 可以考虑通过继承创 建子类 - Category 可以实现原始类的方法, 不推荐这么做, 因为它是替换掉原始类的方 法, 这么做以后就不能访问原来的 方法.
- 多个Category 中如果实现了相同的方法, 只有最后一个参与编译的才会有效.
- 方法调用的优先级 : 分类(最后参与编译的分类优先) —>原来类—>父类
- 类扩展(Extendsion)
- 某个类扩充一些私有的成员变量和方法
- 写在.m文件中
- 英文名是Class Extension
- 格式(俗称匿名分类)
@interface 类名 () @end
15. 类的本质(typedef struct objc_class * Class)
- 类也是一个对象, 是 class 类型的对象, 简称 “类对象”, 类名就代表着类对象, 每个类只有一个类对象
- + load
- + load : 在程序启动的时候会加载所有的类和分类, 并调用所有类和分类的 + load 方法,并且只会调用一次。
- 加载顺序
父类 → 子类→ 分类
。不管程序运行过程有没有用到这个类, 都会调用 + load 方法。 - + initialize
- + initialize : 在第一次使用某个类时(比如创建对象等), 且只会调用一次 + initialize 方法.
- 主要用于对某一个类一次性初始化
- 一个类只会调用一次 + initialize方法, 先调用父类的, 再调用子类的
- 获取类对象的2种方式(获取内存中的类对象)
```
Class c = [Person class] // 类方法
Person *p = [Person new];
Class c1 = [p class]; // 对象方法
```
- 类在内存的表现
- 实例对象 → 类对象(对象方法)→ 元类对象(类方法)→ 根元类 (isa 指向自己)
- 元类保存了类方法的列表。当一个类方法被调用时,元类会首先查找它本身是否有该类方法的实现,如果没有则该元类会向它的父类查找该方法,直到一直找到继承链的头。
- 元类(metaclass)也是一个对象,那么元类的isa指针又指向哪里呢?为了设计上的完整,所有的元类的isa指针都会指向一个根元类(root metaclass)。
- 根元类(root metaclass)本身的isa指针指向自己,这样就行成了一个闭环。上面说到,一个对象能够接收的消息列表是保存在它所对应的类中的。在实际编程中,我们几乎不会遇到向元类发消息的情况,那它的isa 指针在实际上很少用到。不过这么设计保证了面向对象的干净,即所有事物都是对象,都有isa指针。
- 由于类方法的定义是保存在元类(metaclass)中,而方法调用的规则是,如果该类没有一个方法的实现,则向它的父类继续查找。所以为了保证父类的类方法可以在子类中可以被调用,所以子类的元类会继承父类的元类,换而言之,类对象和元类对象有着同样的继承关系。
-
如下图 :
16. NSLog
- 使用 NSLog 和 %@输出某个类对象时, 会调用类对象 + description 方法, 并拿到返回值(NSString *)进行输出
- - description方法默认返回对象的描述信息(默认实现是返回类名和对象的内存地址)
- description方法是基类NSObject 所带的方法. 使用NSLog输出OC对象,意义就不是很大,因为我们并不关心对象的内存地址,比较关心的是对象内部的一些成变量的值。因此,会经常重写description方法,覆盖description方法 的默认实现
-(NSString *) description { return [NSString stringWithFormat:@"age = %d", _age] }
- description方法是基类NSObject 所带的方法. 使用NSLog输出OC对象,意义就不是很大,因为我们并不关心对象的内存地址,比较关心的是对象内部的一些成变量的值。因此,会经常重写description方法,覆盖description方法 的默认实现
- + descrption 方法
- 当使用NSLog输出该类的类对象的时候调用*/(
不常用
) - 注意: 死循环. 如果在 - description方法中使用 NSLog %@ 输出self对象会引发死循环
17. SEL 基本使用
代表方法的签名,在
类对象的方法列表
中存储着该签名与方法代码的对应关系-
每个方法都有一个与之对应的 SEL类型的对象
- SEL 其实是对方法的一种包装, 将方法包装成一个 SEL 类型的数据, 去找对应的方法地址, 进而进行调用
- 注意:在这个操作过程中又缓存,第一次找的时候一个一个的找,非常耗性能,之后再用到的时候就直接使用
-
对象是否实现了某个方法
- - (BOOL) respondsToSelector: (SEL)selector 判断实例是否实现这样方法
- + (BOOL)instancesRespondToSelector:(SEL)aSelector; (类对象)
让对象执行某个方法
- (id)performSelector:(SEL)aSelector;
-
SEL 类型的定义 typedef struct objc_selector *SEL
//SEL 对象的创建 SEL s = @selector(test); SEL s2 = NSSelectorFromString(@"test”); // 将SEL对象转为NSString对象 NSString *str = NSStringFromSelector(@selector(test)); Person *p = [Person new]; // 每个类都有以个_cmd 代表当前方法 // 调用对象p的test方法 [p performSelector : @selector (test)];
18. 内存管理
Automatic Reference Couting
- 什么是自动引用计数器
- 每个OC对象都有自己的引用计数器,它是一个整数,从字面上, 可以理解为”对象被引用的次数”
- 也可以理解为: 它表示有多少人正在用这个对象
占4个字节
Manul Refrence Counting
-
什么是手动引用计数?
- 所有对象的内容都需要我们手动管理, 需要程序员自己编写release/retain等代码
-
方法的基本使用
- retain : 计数器 +1 , 会返回对象本身
- release : 计数器 -1 , 没有返回值(
release并不代表销毁对象, 仅仅是计数器-1
) - retainCount : 获取当前的计数器
-
概念
- 僵尸对象: 所占用内存已经被回收的对象, 僵尸对象不能再使用
- 野指针: 指向僵尸对象(不可用内存)的指针, 给野指针发送消息会报错(EXC_BAD_ACCES)
- 空指针: 没有指向任何东西的指针(储存的东西是nil NULL 0), 给空指针发送消息不会报错
- 内存管理代码规范
- 只要调用了alloc, 必须有relese(autorelease), 如果对象不是通过alloc产生的, 就不需要release
- set方法的代码规范
- 基本数据类型: 直接复制
-(void)setAge:(int)age { _age = age; } ``` - OC对象类型 ``` -(void)setCar:(Car *)car { // 先判断是不是新传进来对象 if( car != _car) { // 对旧对象做一次release [_car release] // 对新对象做一次retain _car = [car retain] } } ```
- dealloc方法的代码规范
- 对self(当前)所拥有的其他对象做一次release
- 当一个对象要被回收的时候, 就会调用
- 一定要调用
[super dealloc]
, 这句调用放在最后面
@Property 参数
- set 方法内存管理相关的参数
- retain : release 旧值 , retain 新值 (适用于OC对象类型)
- assign : 直接赋值(默认, 适用于非OC对象类型)
- copy : release 旧值, copy 新值
- 是否要生成set方法
- readwrite : 同时生成setter 和 getter的声明, 实现(默认)
- readonly : 只会生成getter的声明, 实现
- 多线程管理
- nonatomic : 性能高 (一般就用这个)
- atomic : 性能低(默认)
- setter 和 getter方法的名称
- setter : 决定了set方法的名称, 一定要有个冒号 :
- getter : 决定了get方法的名称(一般用在BOOL类型)
@Class(循环引用)
仅仅告诉编译器,某个名称是一个类
-
开发中引用一个类的规范
- 在.h 文件中用@class 来声明类
- 在.m 文件中用#import 来包含类的所有东西
-
和#import 的区别(面试)
- import会包含引用类的所有信息(内容),包括引用类的变量和方法
- @class仅仅是告诉编译器有这么一个类, 具体这个类里有什么信息, 完全不知
-
总结:
- 如果都在.h中import, 假如A拷贝了B, B拷贝了C , 如果C被修改了, 那么B和A都需要重新拷贝. 因为C修改了那么B就会重新拷贝, 而B重新拷贝之后相当于B也被修改了, 那么A也需要重新拷贝. 也就是说如果都在.h中拷贝, 只要有间接关系都会重新拷贝
- 如果在.h中用@class, 在.m中用import, 那么如果一个文件发生了变化, 只有和这个文件有直接关系的那个文件才会重新拷贝
- 所以在.h中用@class可以提升编译效率
两端循环引用(面试)
retain
* 比如A对象retain了B对象,B对象retain了A对象,这样会导致A对象和B对象永远无法释放。
* 当两端互相引用时,应该一端用retain、一端用assign。import
* 如果两个类相互(#import<>
)拷贝, 例如A拷贝B, B拷贝A, 这样会报错
- 如何解决: 在.h中用@class, 在.m中用import
- 因为如果.h中都用import, 那么A拷贝B, B又拷贝A, 会形成死循环
- 如果在.h中用@class, 那么不会做任何拷贝操作, 而在.m中用import只会拷贝对应的文件, 并不会形成死循环
@ autorelease基本用法
- 会将对象放到一个自动释放池中,并且会返回对象本身
- 当自动释放池被销毁时, 会对池子里面的所有对象做一次release 操作
- 调用完@autorelease 方法后,对象计数器不变
- @autorelease 的好处
- 不用关心对象释放的时间
- 不用关心什么时候调用 release
- @autorelease 使用注意
- 占用内存较大的对象不要随便用 autorelease
- 占用内存较小的对象使用 autorelease,没有太大影响(影响:不能控制对象的释放时间)
- 错误写法
- alloc 之后调用了 autorelease ,又调用 release
Person *p = [[[Person alloc] init] autorelease];
[p release]; - 连续调用autorelease(野指针错误,每个autorelease 释放时都会调用 release)
Person *p = [[[[Person alloc] init] auturelease] autorelease]
- alloc 之后调用了 autorelease ,又调用 release
- 系统自带方法里面没有alloc、new、copy,说明返回的对象是autorelease的
- 开发中经常会提供一些类方法,快速创建一个已经autorelease过的对象
- 创建对象时不要直接用类名,一般用 self
+ (id)person
{
return [[[self alloc] init] autorelease];
}
自动释放池
在 IOS 程序运行中,会创建无数个池子。这些池子都是以“栈”结构存在(先进后出,“杯子”)
当一个对象调用 autorelease 方法时,会将这个对象放到栈顶的释放池 (”栈顶“ 相当于杯子底)
-
集合对象的内存管理
- 当把一个对象添加到集合中时,这个对象会做了一次retain操作,计数器会+1
- 当一个集合被销毁时,会对集合里面的所有对象做一次release操作,计数器会-1
- 当一个对象从集合中移除时,这个对象会一次release操作,计数器会-1
ARC
ARC 的判断准则:主要没有强指针指向对象,就会释放对象
指针的2种类型
强指针:_strong 默认情况,所有指针都是强指针
-
弱指针:_weak
- _weak Person *p = [[Person alloc] init] 错误写法,没有意义的写法,一创建就释放
-
ARC 特点
- 不允许调用 release、retain、retainCount
- 允许重写 dealloc,但是不允许调用[super dealloc]
- @property 的参数
- strong : 成员变量是强指针(适用于 OC 对象类型)
- weak : 成员变量是弱指针(适用于 OC 对象类型)
- assign:适用于非 OC 对象
循环引用
一端用:strong,另一端用:weak在Compiler Flags一列加上-fno-objc-arc就表示禁止这个.m文件的ARC
mrc 可以转 arc ,系统转换
19. copy
- copy的基本原则
- 因为拷贝要求修改原来的对象不能影响到拷贝出来得对象
- 修改拷贝出来的对象也不能影响到原来的对象, 所以需要生成一个新的对象
互不影响
- copy的使用
- 实现拷贝的方法有2个
- copy:返回不可变副本
- mutableCopy:返回可变副本
- 实现拷贝的方法有2个
- 普通对象实现拷贝的步骤
- 遵守NSCopying协议
- 实现-copyWithZone:方法
- 创建新对象
- 给新对象的属性赋值
20. Block
- block访问外面变量
- block内部可以访问外面的变量
- 默认情况下,block内部不能修改外面的局部变量
- 给局部变量加上__block关键字,这个局部变量就可以在block内部修改
- 利用typedef定义block类型
typedef int (^MyBlock)(int, int);
// 以后就可以利用MyBlock这种类型来定义block变量 - block是存储在堆中还是栈中
- 默认情况下block存储在栈中, 如果对block进行一个copy操作, block会转移到堆中
- 如果block在栈中, block中访问了外界的对象, 那么不会对对象进行retain操作
- 但是如果block在堆中, block中访问了外界的对象, 那么会对外界的对象进行一次retain
- 如果在block中访问了外界的对象, 一定要给对象加上__block, 只要加上了__block, 哪怕block在堆中, 也不会对外界的对象进行retain
- 如果是在ARC开发中就需要在前面加上__weak
Person *p = [Person new]; __weak Person *weakP = p; void (^myBlock) () = ^{ weakP.age = 10; }; myBlock(); NSLog(@"age = %li", p.age);
21. Protocol
协议
@protocol 协议名称 < NSObeject >
// 方法声明列表....
@end如何遵守协议
类遵守协议
@interface 类名 : 父类名 <协议名称1, 协议名称2>
@end协议遵守协议
@protocol 协议名称 <其他协议名称1, 其他协议名称2>
@end协议中方法声明的关键字
@required (默认):
要求实现,如果没有实现,会发出警告
@optional:
不要求实现,不会有警告
定义一个变量的时候,限制这个变量保存的对象遵守某个协议
-
如果没有遵守对应的协议,编译器会警告
类名<协议名称> *变量名; id<协议名称> 变量名; NSObject<MyProtocol> *obj; id<MyProtocol> obj2;
-
@property中声明的属性也可用做一个遵守协议的限制
@property (nonatomic, strong) 类名<协议名称> *属性名; @property (nonatomic, strong) id<协议名称> 属性名; @property (nonatomic, strong) Dog<MyProtocol> *dog; @property (nonatomic, strong) id<MyProtocol> dog2;
协议可用定义在单独.h文件中,也可用定义在某个类中
如果这个协议只用在某个类中,应该把协议定义在该类中
如果这个协议用在很多类中,就应该定义在单独文件中
分类可用定义在单独.h和.m文件中,也可用定义在原来类中
一般情况下,都是定义在单独文件
定义在原来类中的分类,只要求能看懂语法