目录
- 属性
- 修饰词
- 循环引用
- typeof与typedef
1. 属性
objc所有类和对象都是c结构体,category当然也一样
成员变量与实例变量
成员变量就是我们写在花括中的变量, 声明方法的括号中的 NSString *a 和 NSInteger b 都是成员变量。
@interface MyObject : NSObject {
NSString *a;
NSInteger b;
}
@end
实例变量是成员变量的一部分,虽然a 和b都是成员变量,但是它们是不同的,a是一个对象指针(前面带*的),a又被称之为实例变量,成员变量包含实例变量。
成员变量中除了b这样的基本数据类型,其它的都是实例变量;
属性本质(@property)
@property (assign, nonatomic) NSString *a;
@property = ivar + getter + setter;
- 属性就是通过@property 定义的变量,默认会生成带下划线的成员变量,在这里xcode编译器会自动生成一个成员变量 NSString *_a。
- 属性相比较于成员变量添加了存取方法(通过@synthesize,它是Xcode自动添加的)。
- 工程师可以使用@dynamic自己实现成员变量的getter和setter方法 , @synthesize 是让 Xcode 帮你生成getter和setter方法 。
@synthesize和@dynamic
- @property有两个对应的词,一个是 @synthesize,一个是 @dynamic。如果 @synthesize和 @dynamic都没写,那么默认的就是@syntheszie var = _var;
@synthesize语法来指定实例变量的名字.
@implementation Person
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end
- @synthesize 的语义是如果你没有手动实现 setter 方法和 getter 方法,那么编译器会自动为你加上这两个方法。
- @dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。(当然对于 readonly 的属性只需提供 getter 即可)。假如一个属性被声明为 @dynamic var,然后你没有提供 @setter方法和 @getter 方法,编译的时候没问题,但是当程序运行到 instance.var = someVar,由于缺 setter 方法会导致程序崩溃;或者当运行到 someVar = var 时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。
@private、@public 、 @protect
属性和成员变量可以被@private、@public 、 @protect 修饰。
- @private 表示这个类私有的 只允许该类内部和该类的对象访问,其它类和他的子类不累访问。
- @protect 表示只允许该类和该类的子类访问。
- @public 表示公共的,所有的对象都能访问。
怎么简单的理解getter和setter
- setter:给外部提供的一个修改内部属性值的接口,调用setter方法可以做到修改内部属性值。
- getter:外界提供的一个查看内部变量的接口。
- self.name和 _name的区别:
- 前者:self.name = @"happy" 是通过调用setter方法设置属性值,而 NSString *str = self.name 就是通过调用getter 方法获取属性值;
- 后者:直接去访问成员变量;
2. 修饰词
属性(@property)根据属性中的设定的修饰词(retain, copy, assign)将会生成不同的setter方法。这些不同的setter方法中,对指针和内存地址的处理是不同的,决定了不同的修饰词的用途。
assign
- assign适用于基本数据类型,weak是适用于NSObject对象,并且是一个弱引用。
- assign其实也可以用来修饰对象,只是对象的计数器不会+1 ,那么我们为什么不用它呢?因为被assign修饰的对象在释放之后,指针的地址还是存在的,也就是说指针并没有被置为nil。如果在后续的内存分配中,刚好分到了这块地址,程序就会崩溃掉。
而weak修饰的对象在释放之后,指针地址会被置为nil。所以现在一般弱引用就是用weak。
weak ( ARC )(对象)
- 弱指针是针对对象的修饰词 , 就是说它不能修饰基本数据类型(int float) 。
- weak 修饰的引用计数器不会+1 , 也就是直接赋值 。
- 弱引用是为打破循环引用而生的,比如在Block中,block在copy的时候,会对内部使用到的对象的引用技术+1,如果使用[self 方法名],那么就会有一个强指针指向self所在的class的内存地址,class的引用计数会+1,这样一来会导致class所在的内存地址无法被释放,造成内存泄漏 。
- 它最被人所喜欢的原因是 它所指向的对象如果被销毁 , 它会指向 nil . 从而不会出现野指针错误 。
注意:什么情况使用 weak 关键字?
在 ARC 中,在有可能出现循环引用的时候,往往要通过让其中一端使用 weak 来解决,
比如: delegate 代理属性
自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,
自定义 IBOutlet 控件属性一般也使用 weak;当然,也可以使用strong。
strong
- 直接赋值并且对象的引用计数器 +1 。在ARC下,实例变量本身是强引用,当ARC将传入值赋给实例变量时,它会保留传入的值,释放现有
实例变量的值。 - 在 ARC 里替代了 retain 的作用 .
retain ( MRC )
- release 旧对象( 旧对象计数器 -1 ) , retain 新对象( 新对象计数器 +1 ) , 然后指向新对象 .
copy
- 如果是不可变的值,行为与strong相同。
- 如果是可变的值,会将一个副本赋给实例变量。当一个不可变类有一个可变的子类时
(NSString NSMutableString,NSArray NSMutableArray)可以防止setter 方法传递一个
可变的子类的对象。会导致我们在不知情的情况下修改对象的值。
注意:
1.修饰的属性本身要不可变的。例如 NSMutableArray 采用 copy 修饰 , 在addObject时会出现Crash, 因为NSMutableArray的对象在copy 过后就会变成NSArray。如果需要copy NSMutableArray对象,用:mutablecopy。
2.遵守 NSCopying 协议的对象使用 .
**nonatomic **
- 不对set方法加同步锁 .
- 性能好
- 线程不安全
atomic
- 原子属性就是对生成的 set 方法加互斥锁 @synchronized(锁对象) .
@synchronized(self) { _delegate = delegate;} - 需要消耗系统资源 .
- 互斥锁是利用线程同步实现的 , 意在保证同一时间只有一个线程调用 set 方法 .
- 其实还有 get 方法 , 要是同时 set 和 get 一起调用还是会有问题的 . 所以即使用了 atomic 修饰 还是不够安全 .
readonly (只读)
1.让 Xcode 只生成get方法 .
2.不想把暴露的属性被人随便替换时 , 可以使用 .
readwrite (读写)(默认)
- 让 Xcode 生成get/set方法 .
- 不用 readonly 修饰时 , 默认就是 readwrite .
3. 循环引用
不知道大家有没有听过一句话:人生就像打电话,不是你先挂,就是我先挂。其实用到这里是在合适不过了。假设有两个人打电话,彼此都强引用的对方的电话号码,但是如果双方都不想挂电话的话,那么电话就会一直通着。在OC中也是同样的道理,正常情况下,当一个类或者对象即将释放或者retainCount=0的时候,就会调用dealloc方法,释放相应的内存。但是,当这个对象被其他对象强引用的时候,那么它就不会被回收。此时如果处理不当的话就会造成循环引用。
循环引用对 app 有潜在的危害,会使内存消耗过高,性能变差和 app 闪退等。
循环引用有哪些具体的情况?block ? delegate ? NSTimer?
一、Block
block在copy时都会对block内部用到的对象进行强引用的。
该类又将block作为自己的属性变量,而该类在block的方法体里面又使用了该类本身,此时就很简单的形成了一个环。
引发循环引用,是因为当前self在强引用着block,而block又引用着self,这样就造成了循环引用。而需不需要使用[weak self]就是由循环引用来决定,如果造成了循环引用,就必须使用[weak self]来打破循环.
1.直接强引用
来分析一个自己设计的block模块:
- 这种情况不必要弱引用
[self oneBlockSucess:^{
[self doSomething];
}];
- 这种情况就有必需用weakself来打破引用环
self.secondBlock = ^{
[self doSomething];
};
self.secondBlock说明当前self持有了secondBlock这个block属性,
所以如果在block回调内想拿到self去做一些业务处理时,如果直接使用self,就会造成block持有了self,两者互相持有,造成循环引用.打破这个环,就要使用如typeof(self) __weak weakSelf = self;
的weakSelf方式;
2.间接强引用
再来分析一个自己设计的block模块:
控制器self并没有直接持有block属性,但是却强引用了bottomView,bottomView强引用了block属性,这就造成了间接循环引用. block回调内必须使用[weak self]来打破这个循环,否则就会导致这个控制器self永远都不会被释放掉产生常驻内存。如果self.bottomView与bottomView.block有一环不是强引用,就不会产生循环引用问题,因为不会形成一个引用环. 如果一个应用程序里面你有很多循环引用,那么内存占用就会比较大,并且由于一些特殊操作,会产生一个控制器的N个对象实例常驻内存无法释放,造成大量的系统内存泄漏,这当然是谁都不想看到的结果.
打破这个环,就要使用如typeof(self) __weak weakSelf = self;的weakSelf方式;
3. 还有一些例子
//循环引用例子:比如控制器在使用一个Block,这个block又在使用控制器就会出现循环引用
- (void)setcycle
{
//例子1.
NSMutableArray *firstArray = [NSMutableArray array];
NSMutableArray *secondArray = [NSMutableArray array];
[firstArray addObject:secondArray];
[secondArray addObject:firstArray];
//例子2.
//代码解释:定义一个和self相同数据类型的bself ,并赋值为self,在block中使用
__weak typeof (self) weakSelf = self;
/*
注意: typeof 括号中的值和等于后面的值是相同的类型。
__weak typeof(self.contentView) ws = self.contentView;
*/
self.passValueBlock = ^(NSString *str){
//循环引用1
[self test];
//解决方法:
//[weakSelf test];
//以下调用注释掉的代码同样会造成循环引用,因为不管是通过self.blockString还是_blockString,或是函数调用[self doSomething],因为只要 block中用到了对象的属性或者函数,block就会持有该对象而不是该对象中的某个属性或者函数。
//NSString *localString = self.blockString;//循环引用2
//NSString *localString = _blockString;//循环引用3
//解决方法
//NSString *localString = weakSelf.blockString;
};
self.passValueBlock(@"s");
//例子3.
//宏定义一个block
typedef void(^blockAct)();
//利用宏定义来定义变量
blockAct blockAct_1;
//定义一个block变量来实现
blockAct_1 = ^(){
[self test];
};
//调用block
blockAct_1();
}
- (void)test
{
NSLog(@"BLOCK");
}
4. 这里直接用一个需求来探究循环引用的问题:
如果我想在Block中延时来运行某段代码,这里就会出现一个问题,看这段代码:
- (void)viewDidLoad {
[super viewDidLoad];
MitPerson*person = [[MitPerson alloc]init];
__weak MitPerson * weakPerson = person;
person.mitBlock = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakPerson test];
});
};
person.mitBlock();
}
直接运行这段代码会发现[weakPerson test];并没有执行,打印一下会发现,weakPerson 已经是 Nil 了,这是由于当我们的 viewDidLoad 方法运行结束,由于是局部变量,无论是 MitPerson 和 weakPerson 都会被释放掉,那么这个时候在 Block 中就无法拿到正真的 person 内容了。
按如下方法修改代码:
- (void)viewDidLoad {
[super viewDidLoad];
MitPerson*person = [[MitPerson alloc]init];
__weak MitPerson * weakPerson = person;
person.mitBlock = ^{
__strong MitPerson * strongPerson = weakPerson;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[strongPerson test];
});
};
person.mitBlock();
}
这样当2秒过后,计时器依然能够拿到想要的 person 对象。
深入探究原理
这里将会对每行代码逐步进行说明
1、开辟一段控件存储 person 类对象内容,创建 person 强指针。
MitPerson*person = [[MitPerson alloc]init];
2、创建一个弱指针 weakPerson 指向person对象内容
__weak MitPerson * weakPerson = person;
person.mitBlock = ^{
3、在 person 对象的 Block 内部创建一个强指针来指向 person 对象,为了保证当计时器执行代码的时候,person 对象没有被系统销毁所以我们必须在系统内部进行一次强引用,并用 GCD 计时器引用 strongPerson,为了保留 person 对象,在下面会对这里更加详细的说明。
__strong MitPerson * strongPerson = weakPerson;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[strongPerson test];
});
};
4、执行 Block 代码
person.mitBlock();
首先需要明白一些关于 Block 的概念:
- 默认情况下,block 是放在栈里面的
- 一旦blcok进行了copy操作,block的内存就会被放在堆里面
- 堆立面的block(被copy过的block)有以下现象:
- block内部如果通过外面声明的强引用来使用,那么block内部会自动产生一个强引用指向所使用的对象。
- block内部如果通过外面声明的弱引用来使用,那么block内部会自动产生一个弱引用指向所使用的对象。
我们进行这段代码的目的:
- 首先,我们需要在 Block 块中调用,person 对象的方法,既然是在 Block 块中我们就应该使用弱指针来引用外部变量,以此来避免循环引用。但是又会出现问题,什么问题呢?就是当我计时器要执行方法的时候,发现对象已经被释放了。
- 接下来就是为了避免 person 对象在计时器执行的时候被释放掉:那么为什么 person 对象会被释放掉呢?因为无论我们的person强指针还是 weakPerson 弱指针都是局部变量,当执行完ViewDidLoad 的时候,指针会被销毁。对象只有被强指针引用的时候才不会被销毁,而我们如果直接引用外部的强指针对象又会产生循环引用,这个时候我们就用了一个巧妙的代码来完成这个需求。
- 首先在 person.mitBlock 引用外部 weakPerson,并在内部创建一个强指针去指向 person 对象,因为在内部声明变量,Block 是不会强引用这个对象的,这也就在避免的 person.mitBlock 循环引用风险的同时,又创建出了一个强指针指向对象。
- 之后再用 GCD 延时器 Block 来引用相对于它来说是外部的变量 strongPerson ,这时延时器 Block 会默认创建出来一个强引用来引用 person 对象,当 person.mitBlock 作用域结束之后 strongPerson 会跟着被销毁,内存中就仅剩下了 延时器 Block 强引用着 person 对象,2秒之后触发 test 方法,GCD Block 内部方法执行完毕之后,延时器和对象都被销毁,这样就完美实现了我们的需求。
二、Delegate
@property (nonatomic, weak) id delegate;
说白了就是循环使用的问题,假如我们是写的strong,那么 两个类之间调用代理就是这样的
BViewController *bViewController = [[BViewController alloc] init];
bViewController.delegate = self; //假设 self 是AViewController
[self.navigationController pushViewController:bViewController animated:YES];
假如是 strong 的情况
bViewController.delegate ===> AViewController (也就是 A 的引用计数 + 1)
AViewController 本身又是引用了 ===> delegate 引用计数 + 1.
导致: AViewController <======> Delegate ,也就循环引用
Delegate创建并强引用了 AViewController;(strong ==> A 强引用、weak ==> 引用计数不变)
所以用 strong的情况下,相当于 Delegate 和 A 两个互相引用,A 永远会有一个引用计数 1 不会被释放,所以造成了永远不能被内存释放,因此weak是必须的。
** NSTimer**
self.timer = [NSTimer timerWithTimeInterval:_time < 2? DEFAULTTIME: _time target:self selector:@selector(nextPage) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
是有风险的:
- timer会强持有target, 同时NSRunLoop会去强持有timer
- 而你需要注意到, 通常我们是把timer当做某一个类的实例的属性去看待的, 也做了强持有操作
所以这样做的结果是, 循环引用!于是基于以上代码的写法, 在系统自动dealloc那个实例的时候你会发现, 实例与timer两者都无法free!
但是,如果你把
[_timer invalidate];
_timer = nil;
写在除了该类的dealloc方法的其他地方, 然后在dealloc该类的实例对象之前调用, 再dealloc操作, 那是一点问题都没有.
三、关于NSTimer循环引用的处理方法:
构造一个中介类给NSTimer, 中介类代码如下:
#import
/// justForText
@interface NSSafeObject : NSObject
- (instancetype)initWithObject:(id)object;
- (instancetype)initWithObject:(id)object withSelector:(SEL)selector;
- (void)excute;
@end
#import "NSSafeObject.h"
@interface NSSafeObject()
{
__weak id _object;
SEL _sel;
}
@end
@implementation NSSafeObject
- (instancetype)initWithObject:(id)object
{
if (self = [super init]) {
_object = object;
_sel = nil;
}
return self;
}
- (instancetype)initWithObject:(id)object withSelector:(SEL)selector
{
if(self = [super init])
{
_object = object;
_sel = selector;
}
return self;
}
- (void)excute
{
if (_object && _sel && [_object respondsToSelector:_sel]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[_object performSelector:_sel withObject:nil];
#pragma clang diagnostic pop
}
}
调用:
有了这样一个类这样就符合我们编写代码的思维习惯, 你依然可以把NSTimer当做某一个类的对象, 无需考虑循化引用问题, 而只需要在每次创建之初多创建一个中介对象,演示代码如下
NSSafeObject * safeObj = [[NSSafeObject alloc]initWithObject:self withSelector:@selector(autoSendBarrage)];
_timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:safeObj selector:@selector(excute) userInfo:nil repeats:YES];
或者给NSTimer写一个类目, 复写我们经常使用的那个NSTimer的类方法(scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:)
并且将WeakObject这个类的创建封装进去。
5.无需解决循环引用的闭包
- UIView Block动画
[UIView animateWithDuration:0.2 animations:^{
self.alpha = 0.0f;
} completion:^(BOOL finished) {
[self stopLoading];
}];
- 同步执行
[_array enumerateObjectsUsingBlock:^(TimelineMessageData *nodeData, NSUInteger index, BOOL *stop) {
[_tableView reloadRow:index inSection:kSectionOffset rowAnimation:UITableViewRowAnimationNone];
}];
- 大部分GCD方法
因为self并没有对GCD的block进行持有,没有形成循环引用。目前我还没碰到使用GCD导致循环引用的场景,如果某种场景self对GCD的block进行了持有,则才有可能造成循环引用。
dispatch_async(dispatch_get_main_queue(), ^{
[self doSomething];
});
- block并不是属性值,而是临时变量
- (void)doSomething {
[self testWithBlock:^{
[self test];
}];
}
- (void)testWithBlock:(void(^)())block {
block();
}
- (void)test {
NSLog(@"test");
}
5. typeof与typedef
理解
- typeof 是一个一元运算,放在一个运算数之前,运算数可以是任意类型。可以理解为:我们根据typeof()括号里面的变量,自动识别变量类型并返回该类型。
typeof 常见运用于Block中,避免循环引用发生的问题。
用法:
//定义一个和self相同数据类型的bself ,并赋值为self,在block中使用
__weak typeof (self) weakSelf = self;
注意: typeof 括号中的值和等于后面的值是相同的类型。
__weak typeof(self.contentView) ws = self.contentView;
- typedef:定义一种类型的别名,而不只是简单的宏替换。
typedef 常用于命名(枚举和Block)
用法:
用法1-结构体
//结构体
typedef struct Myrect {
float width;
float height;
}myRect;
用法2-枚举
//枚举值 它是一个整形(int) 并且,它不参与内存的占用和释放,枚举定义变量即可直接使用,不用初始化.
//在代码中使用枚举的目的只有一个,那就是增加代码的可读性.
typedef NS_ENUM (NSUInteger,direction){
left=0,
right=1,
top =2,
down =3
};
typedef NS_ENUM (NSUInteger,direction_2){
left2 = 0,
right2 = 1 << 0,
top2 = 1 << 1,
down2 = 1 << 2
};
用法3-Block
//1.重新起个名字
typedef void(^PassValueBlock)(NSString *);
@interface BlockViewController : BaseViewController
//2.声明block属性
@property(nonatomic,copy)PassValueBlock passValueBlock;