iOS中使用全局变量的方式:单例、static、extern

iOS中使用全局变量,无外乎以下几种方式:

  • 单例
  • static
  • extern
    下面我们一一介绍。

方式一:AppDelegate.h文件中设置属性充当全局变量

AppDelegate.h

1
2
3
4
5
6
7
8
9
10
#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

//当前使用的ViewController名称
@property (strong, nonatomic) NSString *currentVCName;

@property (strong, nonatomic) UIWindow *window;

@end

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#import "ViewController.h"
#import "AppDelegate.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

//1、设置和使用AppDelegate.h文件中的全局变量
AppDelegate *myDelegate = [[UIApplication sharedApplication] delegate];
myDelegate.currentVCName = NSStringFromClass([self class]);
NSLog(@"Value of global var 'currentVCName' in AppDelegate is : %@",myDelegate.currentVCName);
}

@end

方式二:自定义单例Class的.h文件中设置属性充当全局变量

方式一、二原理都是一样的:通过单例模式的使用,达到全局变量的使用目的。

AccountManager.h

1
2
3
4
5
6
7
8
9
10
#import <Foundation/Foundation.h>

@interface AccountManager : NSObject

//当前账户名称
@property (strong, nonatomic) NSString *currentAccountName;

+ (instancetype)sharedInstance;

@end

AccountManager.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#import "AccountManager.h"

static AccountManager *_sharedInstance = nil;

@implementation AccountManager

+ (instancetype)sharedInstance {
if (_sharedInstance == nil) {
_sharedInstance = [[self alloc] init];
}
return _sharedInstance;
}

@end

ViewController.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#import "ViewController.h"
#import "AppDelegate.h"
#import "AccountManager.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

//2、设置和使用单例Class中的全局变量
AccountManager *accountManager = [AccountManager sharedInstance];
accountManager.currentAccountName = @"LYL";
NSLog(@"Value of global var 'currentVCName' in custom singleton - AccountManager is : %@",accountManager.currentAccountName);
}

@end

补充知识:单例

单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。

单例模式这种特性,可以广泛应用于某些需要全局共享的资源中,比如管理类,引擎类,也可以通过单例来实现传值。UIApplication、NSUserDefaults等都是IOS中的系统单例。我们方式一、二正是利用这种特性,达到全局变量的使用目的。

上例中单例的写法没有考虑多线程问题,当多个线程同时调用sharedInstance时,可能会产生多个实例。我们可以测试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (void)viewDidLoad {
[super viewDidLoad];
//补充:多线程调用没加线程锁的情况,会产生问题
//同在主线程获取sharedInstance,只会产生一个实例
for (NSUInteger i = 0; i <10; i++) {
AccountManager *accountManager = [AccountManager sharedInstance];
NSLog(@"第%ld次获取AccountManager对象内存地址----%@",i+1,accountManager);
}

//多个线程同时获取sharedInstance,会发现可能会产生多个实例
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (NSUInteger i = 0; i <10; i++) {
dispatch_async(queue, ^{
AccountManager *accountManager = [AccountManager sharedInstance];
//NSLog(@"第%ld次获取指向AccountManager对象的指针的地址----%p",i+1,&accountManager);
//NSLog(@"第%ld次获取accountManagern指针指向的地址----%p,即AccountManager对象内存地址%@",i+1,accountManager,accountManager);
NSLog(@"第%ld次获取AccountManager对象内存地址----%@",i+1,accountManager);
});
}
}

解决办法也非常简单,加上线程锁就行了。修改AccountManager中sharedInstance方法如下:

1
2
3
4
5
6
7
8
+ (id)sharedInstance {
@synchronized(self) {
if (_sharedInstance == nil) {
_sharedInstance = [[self alloc] init];
}
}
return _sharedInstance;
}

这种方法虽说没有问题,但现在大家推荐的做法是GCD的dispatch_once方法,据说更加高效:

1
void dispatch_once(dispatch_once_t *predicate, dispatch_block_t block);

我们可以看到这个函数接收一个dispatch_once_t的参数,还有一个块参数。对于一个给定的predicate来说,该函数会保证相关的块必定会执行,而且只执行一次,最重要的是——这个方法是完全线程安全的。需要注意的是,对于只需要执行一次的块来说,传入的predicate必须是完全相同的,所以predicate常常会用static或者global来修饰。

1
2
3
4
5
6
7
8
9
+ (instancetype)sharedInstance {
//利用GCD创建一个单例模式
//第一个参数predicate,该参数是检查后面第二个参数所代表的代码块是否被调用的谓词,第二个参数则是在整个应用程序中只会被调用一次的代码块。dispach_once函数中的代码块只会被执行一次,而且还是线程安全的。
static dispatch_once_t once;
dispatch_once(&once, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}

但是,这并不完整,聪明的你一定想到了,如果在项目中,其它人调用了你写的这个单例,但他可能并不知道这是个单例,他使用时可能会用init来初始化,这就会产生多个实例,这显然不是你想见到的。如何解决?我们要重载任何一个涉及到allocation的方法,这些方法包括+new+alloc+allocWithZone:-copyWithZone:-mutableCopyWithZone:。同时,由于重写了+alloc方法,因此sharedInstance方法也要修改下.

下面是完整代码:

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#import "AccountManager.h"

static AccountManager *_sharedInstance = nil;

@implementation AccountManager

//+ (instancetype)sharedInstance {
// if (_sharedInstance == nil) {
// _sharedInstance = [[AccountManager alloc] init];
// }
// return _sharedInstance;
//}

//+ (id)sharedInstance {
// @synchronized(self) {
// if (_sharedInstance == nil) {
// _sharedInstance = [[self alloc] init];
// }
// }
// return _sharedInstance;
//}

//+ (instancetype)sharedInstance {
// //利用GCD创建一个单例模式
// //第一个参数predicate,该参数是检查后面第二个参数所代表的代码块是否被调用的谓词,第二个参数则是在整个应用程序中只会被调用一次的代码块。dispach_once函数中的代码块只会被执行一次,而且还是线程安全的。
// static dispatch_once_t once;
// dispatch_once(&once, ^{
// _sharedInstance = [[self alloc] init];
// });
// return _sharedInstance;
//}

+ (instancetype)sharedInstance {
static dispatch_once_t once;
dispatch_once(&once, ^{
_sharedInstance = [super allocWithZone:nil];
//_sharedInstance = [[self alloc] init];//self alloc已被重写,肯定不能调用
//_sharedInstance = [[super alloc] init];//nil
});
return _sharedInstance;
}

+ (id)alloc
{
NSLog(@"%@: use +sharedInstance instead of +alloc", NSStringFromClass([self class]));
return nil;
}

+ (id)new
{
return [self alloc];
}

+ (id)allocWithZone:(NSZone*)zone
{
return [self alloc];
}

- (id)copyWithZone:(NSZone *)zone
{
NSLog(@"SingletonClass: attempt to -copy may be a bug.");
return self;
}

- (id)mutableCopyWithZone:(NSZone *)zone
{
return [self copyWithZone:zone];
}

@end

方式三:static

static 关键字会在声明变量的时候分配内存,所以在程序运行期间只会分配一次内存。之后 在程序中访问该变量时,实际上都是在访问原先分配的内存。

使用static修饰的变量,也称私有全局变量,只在该类中可用。和Java不同,Objective-C中声明后的static静态变量在其他类中是不能通过类名直接访问,它的作用域只能是在声明的这个.m文件中 。不过可以调用这个类的方法间接的修改这个静态变量的值。

我们声明static变量,可以放在.h文件的@interface上面:

1
2
3
4
5
static NSInteger staticInteger = 1;

@interface SataticVarMAnager : NSObject

@end

也可以放在.m文件的@implementation上面:

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 "SataticVarMAnager.h"

static NSString *staticStr = @"AA";

@implementation SataticVarMAnager

- (instancetype)init {
if (self = [super init]) {
staticStr = @"BB";
staticInteger = 2;
NSLog(@"%@---%ld",staticStr,staticInteger);

[self declareStaticVarInMethord];
[self declareStaticVarInMethord];
}
return self;
}

- (void)declareStaticVarInMethord {
static NSInteger staticInteger2 = 1;
staticInteger2++;
NSLog(@"declareStaticVarInMethord : %ld",staticInteger2);
}

+ (void)useStaticVarInClassMethord {
NSLog(@"useStaticVarInClassMethord : %@---%ld",staticStr,staticInteger);
}

@end

需要注意的是

  • static变量也可以在方法体内定义,出方法体乃至出该类的作用域其值依然保留。
  • 类方法中可以使用static变量,不能使用实例变量。
  • static变量也可以在@implementation内部定义,但一般不这么做。

方式四:extern

还有一种全局变量,如下面代码中的normalInteger,和static变量十分相似,其值出该类的作用域依然保留,且也可以在类方法中调用。感觉就是编译时,自动加了static。

1
2
3
4
static NSString *staticStr = @"AA";
NSInteger normalInteger = 0;

@implementation SataticVarMAnager

不同的是:
这种写法不能出现在.h文件中,在其它类中也可以用,其它类中在前面加extern就能使用了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#import "ViewController.h"
#import "AppDelegate.h"
#import "AccountManager.h"
#import "SataticVarMAnager.h"

extern NSInteger globalInteger;

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

//3、4 satatic、extern
globalInteger = 3;
SataticVarMAnager *sta = [[SataticVarMAnager alloc] init];
SataticVarMAnager *sta2 = [[SataticVarMAnager alloc] init];
}

@end

SataticVarMAnager.h

1
2
3
4
5
6
7
#import <Foundation/Foundation.h>

static NSInteger staticInteger = 1;

@interface SataticVarMAnager : NSObject

@end

SataticVarMAnager.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
30
31
32
33
34
35
#import "SataticVarMAnager.h"

static NSString *staticStr = @"AA";

NSInteger globalInteger = 1;

@implementation SataticVarMAnager

- (instancetype)init {
if (self = [super init]) {
staticStr = @"BB";
staticInteger = 2;
NSLog(@"%@---%ld---%ld",staticStr,staticInteger,globalInteger);

[self declareStaticVarInMethord];
[self declareStaticVarInMethord];
}
return self;
}

- (void)declareStaticVarInMethord {

static NSInteger staticInteger2 = 1;
staticInteger2++;

globalInteger++;
NSLog(@"declareStaticVarInMethord : %ld,----%ld",staticInteger2,globalInteger);
}

+ (void)useStaticVarInClassMethord {
globalInteger ++;
NSLog(@"useStaticVarInClassMethord : %@---%ld",staticStr,staticInteger);
}

@end