Singletons in Objective-C

One of my most used design patterns when developing for iOS is the singleton pattern. It’s an extremely powerful way to share data between different parts of code without having to pass the data around manually. More about the singleton pattern and other patterns can be found in this excellent book:

Background

Singleton classes are an important concept to understand because they exhibit an extremely useful design pattern. This idea is used throughout the iPhone SDK, for example, UIApplication has a method called sharedApplication which when called from anywhere will return the UIApplication instance which relates to the currently running application.

How to implement

You can implement a singleton class in Objective-C using the following code:

MyManager.h

1
2
3
4
5
6
7
8
9
10
11
#import <foundation/Foundation.h>
@interface MyManager : NSObject {
NSString *someProperty;
}
@property (nonatomic, retain) NSString *someProperty;
+ (id)sharedManager;
@end

MyManager.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#import "MyManager.h"
@implementation MyManager
@synthesize someProperty;
#pragma mark Singleton Methods
+ (id)sharedManager {
static MyManager *sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] init];
});
return sharedMyManager;
}
- (id)init {
if (self = [super init]) {
someProperty = [[NSString alloc] initWithString:@"Default Property Value"];
}
return self;
}
- (void)dealloc {
// Should never be called, but just here for clarity really.
}
@end

What this does is it defines a static variable (but only global to this translation unit)) called sharedMyManager which is then initialised once and only once in sharedManager. The way we ensure that it’s only created once is by using the dispatch_once method from Grand Central Dispatch (GCD). This is thread safe and handled entirely by the OS for you so that you don’t have to worry about it at all.

However, if you would rather not use GCD then you should use the following code for sharedManager:

1
2
3
4
5
6
7
8
+ (id)sharedManager {
static MyManager *sharedMyManager = nil;
@synchronized(self) {
if (sharedMyManager == nil)
sharedMyManager = [[self alloc] init];
}
return sharedMyManager;
}

Then you can reference the singleton from anywhere by calling the following function:

1
MyManager *sharedManager = [MyManager sharedManager];

I’ve used this extensively throughout my code for things such as creating a singleton to handle CoreLocation or CoreData functions.

Non-ARC code

Not that I recommend it, but if you are not using Automatic Reference Counting (ARC), then you should use the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import "MyManager.h"
static MyManager *sharedMyManager = nil;
@implementation MyManager
@synthesize someProperty;
#pragma mark Singleton Methods
+ (id)sharedManager {
@synchronized(self) {
if(sharedMyManager == nil)
sharedMyManager = [[self alloc] init];
}
return sharedMyManager;
}
+ (id)allocWithZone:(NSZone *)zone {
@synchronized(self){
if(sharedMyManager == nil){
sharedMyManager = [super allocWithZone:zone];
}
}
return sharedMyManager;
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)retain {
return self;
}
- (unsigned)retainCount {
return UINT_MAX; //denotes an object that cannot be released
}
- (oneway void)release {
// never release
}
- (id)autorelease {
return self;
}
- (id)init {
if (self = [super init]) {
someProperty = [[NSString alloc] initWithString:@"Default Property Value"];
}
return self;
}
- (void)dealloc {
// Should never be called, but just here for clarity really.
[someProperty release];
[super dealloc];
}
@end

Objective-C的属性和成员变量用法及关系浅析

  1. 属性

    关于属性的用法在苹果的官方文档《The Objective-C Programming Language》中有详细的说明,在这里就不再赘述,链接如下:

    《The Objective-c Programming Language》

    如果你的英文不好,没关系,已经有人把这个文档全部翻译完了,链接如下:

    Objective-C编程语言官方文档翻译

  2. 关于@synthesize object = _object 的解释

    我们在很多代码中会见到这样的写法:

1
2
3
4
5
6
7
8
@interface MyClass:NSObject{  
MyObjecct *_object;
}
@property(nonamtic, retain) MyObjecct *object;
@end
@implementatin MyClass
@synthesize object=_object;
我在网上查阅了一些资料和其他人写的博文,总结了这样写的几条原因如下:  

(1)32位系统和64位系统的差异

在32位系统中,如果类的@interface部分没有进行ivar(instance variable)声明,但有@property声明,在类的@implementation部分有响应的@synthesize,则会得到类似下面的编译错误:

Synthesize property ‘xX’ must either be named the same as a compatible ivar or must explicitly name an ivar

在64位系统中,运行时系统会自动给类添加ivar,添加的ivar以一个下划线“_”做前缀。

(2)避免莫名其妙的Bug

在这里简单说一下_object和object的区别。_object是MyClass类的成员变量,object是属性。property和synthesize定义了一 对getter和setter方法,在这里的getter方法是object,setter方法是setObject,事实上getter和setter方法操作的是变量_object。

如果写synthesize objec = _object 时getter方法为:

1
2
3
4
-(MyObject *)object
{
return _object;
}

如果写synthesize object 时getter方法为:

1
2
3
4
-(MyObject *)object
{
return object;
}

当函数名和属性名重名的时候会出现意想不到的错误,为了避免这种Bug,Apple给的Demo Code里面多数也采用这种方式。

(3)属性和变量的用法

属性是用self.object,通过getter方法来调用的,可以在类外使用。而变量是通过_object来调用,只能在该类对应的implementation中使用,在类外不能使用。

下面看一下两种赋值操作:

1
2
self.object = [[MyObject alloc] init];
_object = [[MyObject alloc] init];

第一种的方式和@property(nonamtic,retain)有关,实际上是通过调用setter方法setObject来实现赋值的。第二种方式是简单的指针赋值,没有调用setter方法。

下面是retainCount的变化:

1
2
3
4
MyObject *tmp = [[MyObject alloc] init];
self.object = tmp; //retainCount = 2
[tmp release]; //retainCount = 1
_object = [[MyObject alloc] init]; //retainCount = 1

Objective-C中不同方式实现锁

为什么需要使用锁,当然熟悉多线程的你,自然不会对它觉得陌生。

那你在代码中是否很好的使用了锁的机制呢?你又知道几种实现锁的方法呢?

今天一起来探讨一下Objective-C中几种不同方式实现的锁,在这之前我们先构建一个测试用的类,假想它是我们的一个共享资源,method1与method2是互斥的,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@implementation TestObj
- (void)method1
{
NSLog(@"%@",NSStringFromSelector(_cmd));
}
- (void)method2
{
NSLog(@"%@",NSStringFromSelector(_cmd));
}
@end

1.使用NSLock实现的锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//主线程中
TestObj *obj = [[TestObj alloc] init];
NSLock *lock = [[NSLock alloc] init];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lock];
[obj method1];
sleep(10);
[lock unlock];
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);//以保证让线程2的代码后执行
[lock lock];
[obj method2];
[lock unlock];
});

看到打印的结果了吗,你会看到线程1锁住之后,线程2会一直等待走到线程1将锁置为unlock后,才会执行method2方法。

NSLock是Cocoa提供给我们最基本的锁对象,这也是我们经常所使用的,除lock和unlock方法外,NSLock还提供了tryLock和lockBeforeDate:两个方法,前一个方法会尝试加锁,如果锁不可用(已经被锁住),刚并不会阻塞线程,并返回NO。lockBeforeDate:方法会在所指定Date之前尝试加锁,如果在指定时间之前都不能加锁,则返回NO。

2.使用synchronized关键字构建的锁

当然在Objective-C中你还可以用@synchronized指令快速的实现锁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//主线程中
TestObj *obj = [[TestObj alloc] init];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized(obj){
[obj method1];
sleep(10);
}
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
@synchronized(obj){
[obj method2];
}
});

@synchronized指令使用的obj为该锁的唯一标识,只有当标识相同时,才为满足互斥.

如果线程2中的@synchronized(obj)改为@synchronized(other),刚线程2就不会被阻塞,@synchronized指令实现锁的优点就是我们不需要在代码中显式的创建锁对象,便可以实现锁的机制,但作为一种预防措施,@synchronized块会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁。所以如果不想让隐式的异常处理例程带来额外的开销,你可以考虑使用锁对象。

3.使用C语言的pthread_mutex_t实现的锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//主线程中
TestObj *obj = [[TestObj alloc] init];
__block pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
pthread_mutex_lock(&mutex);
[obj method1];
sleep(5);
pthread_mutex_unlock(&mutex);
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
pthread_mutex_lock(&mutex);
[obj method2];
pthread_mutex_unlock(&mutex);
});

pthread_mutex_t定义在pthread.h,所以记得#include

4.使用GCD来实现的”锁”

以上代码构建多线程我们就已经用到了GCD的dispatch_async方法,其实在GCD中也已经提供了一种信号机制,使用它我们也可以来构建一把”锁”(从本质意义上讲,信号量与锁是有区别,具体差异参加信号量与互斥锁之间的区别):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//主线程中
TestObj *obj = [[TestObj alloc] init];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[obj method1];
sleep(10);
dispatch_semaphore_signal(semaphore);
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[obj method2];
dispatch_semaphore_signal(semaphore);
});

至于代码产生的效果当然和上一例是一模一样的,关于信号机制,熟悉C编程的你肯定也不会陌生的,关于GCD中更多关于dispatch_semaphore_t的信息,可以跳转到本博客的这一往篇文章:GCD介绍(三): Dispatch Sources

好了,以上就是我所列举了几种方式来实现锁,当然锁大多数情况下也是配合多线程一起使用的,关于多线程编程,我这儿就不赘述了。

参考:http://www.tanhao.me/pieces/616.html

Objective-C 之 @property

@property属性值三类分别是:原子性访问器控制内存管理

原子性

atomic(默认):

atomic意为操作是原子的,意味着只有一个线程访问实例变量。atomic是线程安全的至少在当前的访器上我是安全的。它是一个默认的,但是很少使用。它的比较慢,这跟ARM平台和内部锁机制有关。

nonatomic:

nonatomic跟atomic刚好相反。表示非原子的,可以被多个线程访问。它的速度比atomic快。但不能保证在多线程环境下的安全性,在单线程和明确只有一个线程访问的情况下广泛使用。

访问器控制

readwrite

readonly

内存管理

retain

使用了retain意味着实例变量要获取传入参数的所有权。具体表现在setter中对实例变量先release然后将参数 retain之后传给它。下面这段代码展示了retain类似的行为:

1
2
3
4
5
6
7
8
-(void)setStuName:(NSString *)stuName
{
if (_stuName != stuName)
{
[_stuName release];
_stuName = [stuName retain];
}
}

assign

用于值类型,如int、float、double和NSInteger,CGFloat等表示单纯的复制。还包括不存在所有权关系的对象,比如常见的delegate。

strong

是在ARC伴随IOS引入的时候引入的关键字是retain的一个可选的替代。表示实例变量对传入的参数要有所有权关系即强引用。strong跟retain的意思相同并产生相同的代码,但是语意上更好更能体现对象的关系。

weak

weak跟assign的效果相似,不同的是weak在对象被回收之后自动设置为nil。而且weak智能用在iOS 5或以后的版本,对于之前的版本,使用unsafe_unretained。

unsafe_unretained

weak的低版本替代。

copy

查看上一篇深浅复制

补充:

NSString 应用 retain 还是 copy

  1. 对NSString应用retain,效率无疑是最好的

  2. 用copy最安全,因为NSString 为 NSMutableString 的基类,如果将NSMutableString 以retain的形式赋值给NSString后,后续修改NSMutableString会导致NSString内容的变化,这通常不是我们希望的,所以用copy最安全。

  3. 到底用哪个?貌似还是用copy,因为copy并不一定导致一个新对对象创建,而牺牲效率。copy会调用NSCopying中的 -(id)copyWithZone:(NSZone *),我们可以判断下self是NSString还是NSMutableString,如果是NSString,就地[self retain],[return self]。否则老老实实拷贝对象赋值,这样可以实现效率和安全的结合

copy到底是深复制还是浅复制仅仅由NSCopying中的 copyWithZone 来决定

OBJECT-C 深拷贝和浅拷贝

类型转换

第一种:非容器类不可变对象

第二种:容器类不可变对象

第三种:非容器类可变对象

第四种:容器类可变对象

概念

浅 复 制:对于被复制的对象的每一层复制都是指针复制(复制对象的指针)。

深 复 制:在复制操作时,对于被复制的对象至少有一层复制是对象复制(复制引用对象内容)。

完全复制:在复制操作时,对于被复制的对象的每一层复制都是对象复制。

  1. 在复制操作时,对于对象有n层是对象复制,我们可称作n级深复制,此处n应大于等于1。

  2. 对于完全复制如何实现(目前通用的办法是:迭代法和归档),这里后续是否添加视情况而定,暂时不做讲解。

  3. 指针复制俗称指针拷贝,对象复制也俗称内容拷贝。

retain

始终是浅复制。引用计数每次加一。返回对象是否可变与被复制的对象保持一致。

copy

对于可变对象为深复制,引用计数不改变;

对于不可变对象是浅复制,引用计数每次加一。

始终返回一个不可变对象。

mutableCopy

始终是深复制,引用计数不改变。始终返回一个可变对象。

不可变对象:值发生改变,其内存首地址随之改变。

可变对象:无论值是否改变,其内存首地址都不随之改变。

引用计数:为了让使用者清楚的知道,该对象有多少个拥有者(即有多少个指针指向同一内存地址)。

  1. 不可变对象→可变对象的转换:
1
2
NSArray *array1= [NSArray arrayWithObjects:@"a",@"b",@"c",@"d",nil];
NSMutableArray *str2=[array1 mutableCopy];
  1. 可变对象→不可变对象的转换:
1
2
NSMutableArray *array2 = [NSMutableArray arrayWithObjects:@"aa",@"bb",@"cc",@"dd",nil];
NSArray *array1=[ array2 Copy];
  1. 可变对象→可变对象的转换(不同指针变量指向不同的内存地址):
1
2
NSMutableArray *array1= [NSMutableArray arrayWithObjects:@"a",@"b",@"c",@"d",nil];
NSMutableArray *str2=[array1 mutableCopy];

通过上边的两个例子,我们可轻松的将一个对象在可变和不可变之间转换,并且这里不用考虑内存使用原则(即引用计数的问题)。没错,这就是深拷贝的魅力了。

  1. 同类型对象之间的指针复制(不同指针变量指向同一块内存地址):

a

1
2
3
NSMutableString *str1=[NSMutableString stringWithString:@"two day"];
NSMutableString *str2=[str1 retain];
[str1 release];

b

1
2
3
NSArray *array1= [NSArray arrayWithObjects:@"a",@"b",@"c",@"d",nil];
NSArray *str2=[array1 Copy];
[array1 release];

通俗的讲,多个指针同时指向同一块内存区域,那么这些个指针同时拥有对该内存区的所有权。所有权的瓜分过程,这时候就要用到浅拷贝了。

则简化为:

问:什么时候用到深浅拷贝?

答:深拷贝是在要将一个对象从可变(不可变)转为不可变(可变)或者将一个对象内容克隆一份时用到;浅拷贝是在要复制一个对象的指针时用到。

#测试以下代码时需将Project中的Ojective-C automic Reference couting设置为NO(Apple LLVM 5.0-language -Modules)

第一种:非容器类不可变对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
NSString *str1=@"one day";
printf("初始化赋值引用计数为::::%lu",str1.retainCount);
NSString *strCopy1=[str1 retain];
printf("\n继续retain引用计数为:::%lu",str1.retainCount);
NSString *strCopy2=[str1 copy];
printf("\n继续copy后引用计数为::::%lu",str1.retainCount);
NSString *strCopy3=[str1 mutableCopy];
printf("\n继续mutableCopy后为:::%lu\n",str1.retainCount);
printf("\n非容器类不可变对象\n原始地址::::::::::%p",str1);
printf("\nretain复制::::::::%p",strCopy1);
printf("\ncopy复制::::::::::%p",strCopy2);
printf("\nmutableCopy复制:::%p\n",strCopy3);
// 初始化赋值引用计数为:18446744073709551615
// 继续retain引用计数为:18446744073709551615
// 继续copy后引用计数为:18446744073709551615
// 继续mutableCopy后为:18446744073709551615
//原始地址::::::::::0x1000033d0
//retain复制::::::::0x1000033d0//浅复制
//copy复制::::::::::0x1000033d0//浅复制
//mutableCopy复制:::0x10010c420//深复制

小提示:这里很多人都说是赋值,所以就好解释这里没引用计数的概念。而且也能解释为什么

1
2
3
NSString *strCopy2=[str1 copy];
NSMutableString *strCopy2=[str1 copy];

这样都不会报错的原因了。那既然只是简单赋值为什么要这么麻烦呢,直接

1
2
3
NSString *strCopy2=*str1;
NSMutableString *strCopy2=*str1;

其实大家都看出来了,这里是指针变量,只存在“指针的复制”,

跟赋值概念完全不同,虽然这里看起来很像。

原来该类型是字符串常量时,系统会为我们优化,声明了多个字符串,但是都是常量,且内容相等,那么系统就只为我们申请一块空间。

疑问: 深复制=浅复制+赋值吗?

赋值过程:输入数据→寄存器处理→开辟内存→写入数据。

一次深复制,可以得到被复制对象指针,并进行一次赋值操作。

第二种:容器类不可变对象

  1. 不可变对象→可变对象的转换:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
NSArray *array1= [NSArray arrayWithObjects:@"a",@"b",@"c",@"d",nil];
printf("\n初始化赋值引用计数为::::::::::::%lu",[array1 retainCount]);
NSArray *arrayCopy1 = [array1 retain];
printf("\n继续retain后引用计数为:::::::::%lu",array1.retainCount);
NSArray *arrayCopy2 = [array1 copy];
printf("\n继续copy后引用计数为:::::::::::%lu",array1.retainCount);
NSArray *arrayCopy3 = [array1 mutableCopy];
printf("\n继续mutableCopy后引用计数为::::%lu\n",array1.retainCount);
printf("\n容器类不可变数组\n原始地址::::::::::%p\t\t%p",array1,[array1 objectAtIndex:1]);
printf("\nretain复制::::::::%p\t%p",arrayCopy1,[arrayCopy1 objectAtIndex:1]);
printf("\ncopy复制::::::::::%p\t%p",arrayCopy2,[arrayCopy2 objectAtIndex:1]);
printf("\nmutableCopy复制:::%p\t%p",arrayCopy3,[arrayCopy3 objectAtIndex:1]);
//结果
//初始化赋值引用计数为::::::::::::1
//继续retain后引用计数为:::::::::2
//继续copy后引用计数为:::::::::::3
//继续mutableCopy后引用计数为::::3
//容器类不可变数组
//原始地址::::::::::0x10010c6b0 0x100003410
//retain复制::::::::0x10010c6b0 0x100003410//浅复制
//copy复制::::::::::0x10010c6b0 0x100003410//浅复制
//mutableCopy复制:::0x10010c760 0x100003410//深复制

第三种:非容器类可变对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
NSMutableString *str2=[NSMutableString stringWithString:@"two day"];
printf("\n初始化赋值引用计数为::::::::::::%lu",str2.retainCount);
NSMutableString *strCpy1=[str2 retain];
printf("\n继续retain后引用计数为:::::::::%lu",str2.retainCount);
NSMutableString *strCpy2=[str2 copy];
printf("\n继续copy后引用计数为:::::::::::%lu",str2.retainCount);
NSMutableString *strCpy3=[str2 mutableCopy];
printf("\n继续mutableCopy后引用计数为::::%lu\n",str2.retainCount);
printf("\n非容器类可变对象\n原始地址::::::::::%p",str2);
printf("\nretin复制::::::::%p",strCpy1);
printf("\ncopy复制::::::::::%p",strCpy2);
printf("\nmutableCopy复制:::%p",strCpy3);
//结果
//初始化赋值引用计数为::::::::::::1
//继续retain后引用计数为:::::::::2
//继续copy后引用计数为:::::::::::2
//继续mutableCopy后引用计数为::::2
//非容器类可变对象
//原始地址::::::::::0x10010c560
//retain复制::::::::0x10010c560//浅复制
//copy复制::::::::::0x100102720//深复制
//mutableCopy复制:::0x10010c880//深复制

第四种:容器类可变对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
NSMutableArray *array2 = [NSMutableArray arrayWithObjects:@"aa",@"bb",@"cc",@"dd",nil];
printf("\n初始化赋值引用计数为::::::::::%lu",array2.retainCount);
NSMutableArray *arrayCpy1 = [array2 retain];
printf("\n继续retain后引用计数为:::::::%lu",array2.retainCount);
NSMutableArray *arrayCpy2=[array2 copy];
printf("\n继续copy后引用计数为:::::::::%lu",array2.retainCount);
NSMutableArray *arrayCpy3 = [array2 mutableCopy];
printf("\n继续mutableCopy后引用计数为::%lu\n",array2.retainCount);
printf("\n容器类可变数组\n原始地址:::::::::::%p\t%p",array2,[array2 objectAtIndex:1]);
printf("\nretain复制:::::::::%p\t%p",arrayCpy1,[arrayCpy1 objectAtIndex:1]);
printf("\ncopy复制:::::::::::%p\t%p",arrayCpy2,[arrayCpy2 objectAtIndex:1]);
printf("\nnmutableCopy复制:::%p\t%p",arrayCpy3,[arrayCpy3 objectAtIndex:1]);
//结果
//初始化赋值引用计数为::::::::::1
//继续retain后引用计数为:::::::2
//继续copy后引用计数为:::::::::2
//继续mutableCopy后引用计数为::2
//容器类可变数组
//原始地址:::::::::::0x10010e6c0 0x1000034b0
//retain复制:::::::::0x10010e6c0 0x1000034b0//浅复制
//copy复制:::::::::::0x10010e790 0x1000034b0//深复制
//nmutableCopy复制:::0x10010e7c0 0x1000034b0//深复制