OC属性关键字和单例模式

news2024/10/5 20:12:30

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1699088.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue | 自定义组件双向绑定基础用法

Vue | 自定义组件双向绑定基础用法 vue 中,由于单向数据流,常规的父子组件属性更新,需要 在父组件绑定相应属性,再绑定相应事件,事件里去做更新的操作,利用语法糖 可以减少绑定事件的操作。 这里就简单的梳…

【MySQL】聊聊count的相关操作

在平时的操作中,经常使用count进行操作,计算统计的数据。那么具体的原理是如何的?为什么有时候执行count很慢。 count的实现方式 select count(*) from student;对于MyISAM引擎来说,会把一个表的总行数存储在磁盘上,…

【智能算法应用】模拟退火算法求解多车型车辆路径问题HFVRP

目录 1.算法原理2.多车型车辆路径HFVRP数学模型3.结果展示4.参考文献5.代码获取 1.算法原理 模拟退火算法(Simulated Annealing, SA)是一种通用概率算法,用于在给定一个大的搜索空间内寻找问题的近似最优解。这种算法受到物理中退火过程的启…

如何对Linode Windows虚拟机进行“本地”访问

大部分时候,IT运维工作都可以远程进行,只要能通过网络访问被管理的系统,就可以执行几乎所有任务。如果因为某些原因导致无法通过网络访问呢?此时可能需要亲自到达相关硬件设备旁,通过“本地访问”来排错。 如果这些硬…

Hadoop 客户端 FileSystem加载过程

如何使用hadoop客户端 public class testCreate {public static void main(String[] args) throws IOException {System.setProperty("HADOOP_USER_NAME", "hdfs");String pathStr "/home/hdp/shanshajia";Path path new Path(pathStr);Confi…

英语学习笔记28——Where are they?

Where are they? 他们在哪里? 课文部分

数据集001:安全帽检测数据集 (Helmet Detection) (含数据集下载链接)

安全帽检测 安全帽识别是一个目标检测任务,及时排查安全帽佩戴的规范性并给予提醒,可以大大降低施工安全隐患。这是CV领域入门级的项目,能快速了解从数据预处理、模型构建、训练到部署的整体流程。 数据集格式 数据集中包含了5000张已经标注…

基于ssm的蛋糕商城系统java项目jsp项目javaweb

文章目录 蛋糕商城系统一、项目演示二、项目介绍三、系统部分功能截图四、部分代码展示五、底部获取项目源码(9.9¥带走) 蛋糕商城系统 一、项目演示 蛋糕商城管理系统 二、项目介绍 系统角色 : 管理员、用户 一,管理员 管理员有…

【iOS安全】BurpSuite iOS https抓包 | DNS Spoofing

BurpSuite 完整版安装 参考:Admin Team 小数智 的博客 下载 Burp Suite 2021.6 官网下载地址 https://portswigger.net/burp/releases (下载并安装好) jdk 使用的是11.0.10 https://www.oracle.com/java/technologies/javase-jdk11-downl…

奇偶数递增递减-第13届蓝桥杯选拔赛Python真题精选

[导读]:超平老师的Scratch蓝桥杯真题解读系列在推出之后,受到了广大老师和家长的好评,非常感谢各位的认可和厚爱。作为回馈,超平老师计划推出《Python蓝桥杯真题解析100讲》,这是解读系列的第70讲。 奇偶数递增递减&a…

AIGC 006-textual-inversion使用文本反转实现个性化文本到图像生成!

AIGC 006-textual-inversion使用文本反转实现个性化文本到图像生成! 文章目录 0 论文工作1 论文方法2 效果 0 论文工作 这篇论文 (An Image is Worth One Word: Personalizing Text-to-Image Generation using Textual Inversion) 提出了一种新颖的技术&#xff0c…

C++系列-explicit关键字

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 首先&#xff0c;我们先来看一段正常的构造和拷贝构造的代码&#xff1a; #include<iostream> using namespace std; class A { public://单参数构造函数//explicit A(in…

Mysql中表之间的关系

表之间的关系 一对一、多对一&#xff08;其实就是主从关系&#xff0c;在从表中设置一个外键关联上主表&#xff09;、多对多关系&#xff08;需要一个中间表&#xff0c;设置两个外键&#xff0c;分别关联到两个表的主键&#xff09; 比如订单和商品之间&#xff1a;一个订单…

Jupyter Lab 软件安装与使用

软件简介 Jupyter Lab 软件是一个基于web 的交互式开发环境&#xff0c;集成了代码编辑器、终端、文件管理器等功能&#xff0c;使得开发者可以在一个界面中完成各种任务。JupyterLab是Jupyter Notebook的全面升级&#xff0c;是一个集文本编辑器、终端以及各种个性化组件于一…

《最新出炉》系列入门篇-Python+Playwright自动化测试-41-录制视频

宏哥微信粉丝群&#xff1a;https://bbs.csdn.net/topics/618423372 有兴趣的可以扫码加入 1.简介 上一篇讲解和分享了录制自动生成脚本&#xff0c;索性连带录制视频也一股脑的在这里就讲解和分享了。今天我们将学习如何使用Playwright和Python来录制浏览器操作的视频&#…

Python零基础-中【详细】

接上篇继续&#xff1a; Python零基础-上【详细】-CSDN博客 目录 十、函数式编程 1、匿名函数lambda表达式 &#xff08;1&#xff09;匿名函数理解 &#xff08;2&#xff09;lambda表达式的基本格式 &#xff08;3&#xff09;lambda表达式的使用场景 &#xff08;4&…

基础—SQL—DDL—建表、查表、修改表以及总结

一、DDL—表—创建表与数据类型的设定 &#xff08;1&#xff09;要求 根据需求创建表(设计合理的数据类型、长度) 设计一张员工信息表&#xff0c;要求如下: 1、编号&#xff08;纯数字) 2、员工工号(字符串类型&#xff0c;长度不超过10位) 3、员工姓名&#xff08;字符串类…

CAD二次开发(5)-用户交互仿系统命令

1. 工具类&#xff1a;PromptTool.cs using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.Geometry; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace _04用户交互 {public static…

总是等不是办法,向媒体投稿你得学会用新方法

初入信息宣传领域,我怀揣着对文字的热爱与传播价值的热情,肩负起了单位活动的宣传报道重任。那时的我,满脑子都是传统的投稿思维:精心撰写每一篇稿件,然后逐一搜寻各大媒体的投稿邮箱,一封封邮件满怀期待地发出,像播撒希望的种子,渴望在广袤的媒体土壤中生根发芽。然而,理想很丰…