OC的属性关键字和单例模式
文章目录
- OC的属性关键字和单例模式
- 单例模式
- 基本创建
- 重写allocWithZone方法的同时使用dispatch_once
- 属性和属性关键字
- @property和@synthesize,@dynamic
- 属性关键字
- atomic和nonatomic
- strong和weak
- readonly和readwrite
- strong和copy
单例模式
单例模式是因为在某些时候,程序多次创建这一个类的对象没有实际上的意义,那么我们就只用在程序运行的时候只初始化一次,自然就产生了我们的一个单例模式。
定义:
如果一个类始终只能创建一个实例,则这个类被称为单例类。在程序中,单例类只在程序中初始化一次,所以单例类是储存在去全局区域,在编译时分配内存,只要程序还在运行就会一直占用内存,只有在程序结束的时候释放这部分内存。
有三个注意点
- 单例类只有一个实例
- 单例类只能自己创建自己的实例
- 单例类必须给其他对象提供这一实例
基本创建
static id instance = nil;
@implementation Singleton
+ (id) instance{if (instance == nil) {instance = [[super alloc] init];}return instance;
}
@end
这是我们之前学习的单例模式的创建方式,但是这个方式会出现问题,比方说下面这个例子
int main(int argc, const char * argv[]) {@autoreleasepool {id instance1 = [Singleton instance];id instance2 = [[Singleton alloc] init];NSLog(@"%d", instance1 == instance2);}return 0;
}
很显然我们创建了两个不同的对象这就不符合我们的单例模式的定义
为了解决这个问题,我们就需要重写alloc,然后发现其实我们这里需要重写的是allocWithZone:zone方法(因为alloc仅仅只是调用了这个方法)。
同时这里要注意一个点就是在于这种实现方式仅仅只能在单线程下面进行它并不能保证在多线程的情况下面,只生成一个单例。
重写allocWithZone方法的同时使用dispatch_once
static id instance = nil;
@implementation Singleton
+ (id) instance{static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{instance = [[self alloc] init];});return instance;
}
+ (id)allocWithZone:(struct _NSZone *)zone {return [Singleton instance];
}
@end
但是这样重写我们又发现一个问题就是在于我们这里出现了一个循环的问题,就是我们直接嗲用alloc方法的时候,这时候进入了instance
方法,这时候instance由于还没被创建所以我们这里有重新进入了alloc
方法,这样就进入了一个死循环,从而导致了我们的代码出现了问题,所以我们需要重新修正这部分代码。我们把创建单例的代码放在我们的allocWithZone:
中.
这里解释一下有关dispatch_once(dispatch_once_t*predicate,dispatch_block_t block);
这个函数是用来保证只执行一次的.此操作保证线程安全。(这里笔者对于多线程也没有学习,仅仅只是了解这个函数来实现一个正确的单例模式)。
static id instance = nil;
@implementation Singleton
+ (id) instance{return [[Singleton alloc] init];
}
+ (id)allocWithZone:(struct _NSZone *)zone {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{instance = [super allocWithZone:zone];});return instance;
}
@end
这时候我们主函数:
int main(int argc, const char * argv[]) {@autoreleasepool {id instance1 = [Singleton instance];id instance2 = [[Singleton alloc] init];NSLog(@"%d", instance1 == instance2);}return 0;
}
我们重新打印结果:
这时候我们就实现了符合单例模式的规则。
为了保证逻辑的正确,我们其实还要重写两个复制的函数。
- (id)copyWithZone:(NSZone *)zone {return instance;
}
- (id)mutableCopyWithZone:(NSZone *)zone {return instance;
}
这样我们就基本上实现了单例模式的一个创建
属性和属性关键字
@property和@synthesize,@dynamic
@property
:这个会自动生成相应的setter和getter方法
@synthesize
:关键字会指定一个以下划线 ( _ ) 为前缀,加上属性名的成员变量。并且由编译器自动进行该属性setter和getter方法的实现
@dynamic
:这个关键字就可以让编译器不为上面那个类提供自动合取方法。
在Xcode4.5之后@property
这个关键字已经会自动给我们提供setter和getter方法。所以现在可以不提供@synthesize
来实现了。但是如果是在协议中定义的@property
那么这个就需要我们自己在实现中用@synathesize
去合成了。
这里面就可以发现我们遵守的协议需要我们自己手动去实现一个setter和getter方法。
属性关键字
关键字 | 解释 |
---|---|
atomic | 原子性访问,可以保证属性的赋值和取值的原子性操作是线程安全的。 |
nonatomic | 非原子性,一般属性都用 nonatomic 进行修饰,因为 atomic 非常耗时。 |
readwrite | 可读可写(默认),同时生成 setter 方法和 getter 方法的声明和实现。 |
readonly | 只读,只生成 getter 方法的声明和实现。 |
strong | 强引用,当一个对象被声明为strong属性,ARC会增加该对象的引用计数 |
weak | 只能修饰对象类型;2. ARC 下才能使用;3. 修饰弱引用,不增加对象引用计数,主要可以用于避免循环引用;4. weak 修饰的对象在被释放之后,会自动将指针置为 nil,不会产生悬垂指针 |
assign | 用于修饰基本类型; setter 方法的实现是直接赋值,一般用于基本数据类型 ; 修饰基本数据类型,如 NSInteger、BOOL、int、float 等; |
copy | 指定属性为拷贝引用,即属性会拷贝对象的值,而不是持有原始对象的引用 |
unsafe_unretained | 基本和weak相似,但是他不会在该指针所引用的对象被回收后将指针赋为nil |
atomic和nonatomic
- atomic:在OC中属性的默认声明为atomic,他可以保证对于属性的赋值和取值是一定线程安全的,但是如果对于数组这种对象的话他有存在问题,他对于数组对象的删除和添加操作是不安全的。保证读写操作的安全。
- nonatomic:这个是不保证线程安全的,这个访问速度更快
strong和weak
- strong:这个关键字会让修饰的对象引用计数加一。strong可以保证被该属性引用的对象被不被回收
- weak:这个关键字表示对这个对象进行一个弱引用,该指示符主要的用处是可以避免循环引用。
比方说我们这里定义了一个类,这里重写了dealloc方法,它只会在对象的引用计数降到 0 时被自动调用。
NS_ASSUME_NONNULL_BEGIN@interface ClassA : NSObject
@property (nonatomic, strong)ClassA* clsa;
@endNS_ASSUME_NONNULL_END
NS_ASSUME_NONNULL_BEGIN
@implementation ClassA
- (void)dealloc {NSLog(@"Dealloc: %@", self);
}
NS_ASSUME_NONNULL_END
然后我们在主函数中这样去写代码。
int main(int argc, const char * argv[]) {@autoreleasepool {ClassA* c1 = [[ClassA alloc] init];ClassA* c2 = [[ClassA alloc] init];c1.clsa = c2;c2.clsa = c1;NSLog(@"%lu",CFGetRetainCount((__bridge CFTypeRef)c1));//返回引用计数的个数NSLog(@"%lu",CFGetRetainCount((__bridge CFTypeRef)c2));}return 0;
}
我们声明两个对象 c1和c2,其对应内部也有自己的成员变量clsa,通过set方法给两个对象的成员变量分别赋值另一个对象所持有的c1/c2对象.这里就出现了一个问题就是我们在这里变成了c1持有c2的,c2持有了c1这就造成了一个循环引用的问题。这里我们发现没有自动调用dealloc函数,这就说明没有被内存没有被完全释放
从而这样就造成了一个内存泄漏的问题。那么该如何解决这个问题呢,我们这里可以将关键字修改为weak
这样他的结果就变成了
这里调用了dealloc函数就说明了他已经实现了一个内存的释放,这样就解决了内存泄漏的问题。
readonly和readwrite
- readwrite:属性拥有setter方法和getter方法
- readonly:仅有get方法
strong和copy
如果属性声明中指定了copy特性,合成方法会使用类的copy方法,这里注意:属性并没有mutableCopy特性。即使是可变的实例变量,也是使用copy特性,正如方法 copyWithZone:的执行结果。所以,按照约定会生成一个对象的不可变副本。
这里就是对于一个可变类型和不可变类型的选择问题了,我们以字符串类型来作为例子。
@interface Person : NSObject
@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) BOOL age;
@end
这里我们使用copy修饰符来修饰我们的NSString
int main(int argc, const char * argv[]) {@autoreleasepool {Person* p1 = [[Person alloc] init];NSMutableString *s1 = [NSMutableString stringWithString:@"nanxun"];p1.name = s1;[s1 appendString:@"911"];NSLog(@"%@", p1.name);}return 0;
}
这里的打印结果为:
但是如果我们使用strong去修饰NSString的话,他的结果为:
这里引用一段学长的话:
因为s1是可变的,person.name属性是copy,所以创建了新的字符串,属于深拷贝,内容拷贝,我们拷贝出来了一个对象,后面的赋值操作都是针对新建的对象进行操作,而我们实际的调用还是原本的对象。所以值并不会改变。
如果设置为strong,strong会持有原来的对象,使原来的对象引用计数+1,其实就是浅拷贝、指针拷贝。这时我们进行操作,更改其值就使本对象发生了改变。
既然我们创建的是一个不可变类型,我们就尽量要让他不可变,所以对于不可变类型我们采用一个copy
去修饰
如果是一个可变副本的话,我们采用copy标识符的话就会无法对于可变字符串进行一个修改的操作,
所以综上所述:对于可变类型采用strong,对于不可变类型采用copy。
本次考核发现自己对于知识点的掌握还是不够熟练,而且有很多内容并不是真正˙意义上的掌握,只是停留在浅薄的了解过的层面。之后的学习要更加深入理解知识点,不能停留在表面。