OC属性关键字和单例模式

OC的属性关键字和单例模式

单例模式

单例模式是因为在某些时候,程序多次创建这一个类的对象没有实际上的意义,那么我们就只用在程序运行的时候只初始化一次,自然就产生了我们的一个单例模式。

定义

如果一个类始终只能创建一个实例,则这个类被称为单例类。在程序中,单例类只在程序中初始化一次,所以单例类是储存在去全局区域,在编译时分配内存,只要程序还在运行就会一直占用内存,只有在程序结束的时候释放这部分内存。

有三个注意点

  • 单例类只有一个实例
  • 单例类只能自己创建自己的实例
  • 单例类必须给其他对象提供这一实例

基本创建

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;
@end

NS_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。

本次考核发现自己对于知识点的掌握还是不够熟练,而且有很多内容并不是真正˙意义上的掌握,只是停留在浅薄的了解过的层面。之后的学习要更加深入理解知识点,不能停留在表面。

  • 28
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
OD门(Open Drain Gate)和OC门(Open Collector Gate)是两种输出模式,常用于数字电路中。 在OD门模式下,输出端的晶体管开关是一个开漏结构,即当输出为低电平时,晶体管导通,输出端为低电平;而当输出为高电平时,晶体管截止,输出端悬空(开路状态)。这意味着在OD门模式下,输出端可以接受高电平输入信号,但无法提供高电平输出信号。为了将输出信号转换为电平信号,通常需要外部上拉电阻将输出端连接到高电平。 在OC门模式下,输出端的晶体管开关是一个开集电极结构,即当输出为低电平时,晶体管导通,输出端为低电平;而当输出为高电平时,晶体管截止,输出端悬空(开路状态)。与OD门模式类似,OC门模式的输出也需要外部上拉电阻将输出端连接到高电平。 GPIO(General Purpose Input/Output)模式是一种通用输入/输出模式,在单片机和微控制器中常见。GPIO引脚可以根据需要配置为输入或输出。在输入模式下,GPIO引脚可以接收外部信号;在输出模式下,GPIO引脚可以提供外部信号。GPIO引脚通常具有上拉或下拉电阻选项,用于配置输入引脚的默认状态。 总结起来,OD门和OC门模式是输出模式,用于数字电路中,需要外部上拉电阻将输出转换为电平信号;而GPIO模式是一种多功能模式,可以配置为输入或输出,并具有上拉或下拉电阻选项。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值