OC属性关键字和单例模式

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。

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

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

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

相关文章

MySQL--存储引擎

一、存储引擎介绍 1.介绍 存储引擎相当于Linux的文件系统,以插件的模式存在,是作用在表的一种属性 2.MySQL中的存储引擎类型 InnoDB、MyISAM、CSV、Memory 3.InnoDB核心特性的介绍 聚簇索引、事务、MVCC多版本并发控制、行级锁、外键、AHI、主从复制特…

Python | Leetcode Python题解之第112题路径总和

题目: 题解: class Solution:def hasPathSum(self, root: TreeNode, sum: int) -> bool:if not root:return Falseif not root.left and not root.right:return sum root.valreturn self.hasPathSum(root.left, sum - root.val) or self.hasPathSum…

关于在子线程中获取不到HttpServletRequest对象的问题

这篇文章主要分享一下项目里遇到的获取request对象为null的问题,具体是在登录的时候触发的邮箱提醒,获取客户端ip地址,然后通过ip地址定位获取定位信息,从而提示账号在哪里登录。 但是登录却发现获取request对象的时候报错了。 具…

Docker提示某网络不存在如何解决,添加完网络之后如何删除?

Docker提示某网络不存在如何解决? 创建 Docker 网络 假设现在需要创建一个名为my-mysql-network的网络 docker network create my-mysql-network运行容器 创建网络之后,再运行 mysqld_exporter 容器。完整命令如下: docker run -d -p 9104…

认识K8s集群的声明式资源管理方法

前言 Kubernetes 集群的声明式资源管理方法是当今云原生领域中的核心概念之一,使得容器化应用程序的部署和管理变得更加高效和可靠。本文将认识了解 Kubernetes 中声明式管理的相关理念、实际应用以及优势。 目录 一、管理方法介绍 1. 概述 2. 语法格式 2.1 管…

Spring Boot Interceptor(拦截器使用及原理)

之前的博客中讲解了关于 Spring AOP的思想和原理,而实际开发中Spring Boot对于AOP的思想的具体实现就是Spring Boot Interceptor。在 Spring Boot 应用程序开发中,拦截器(Interceptor)是一个非常有用的工具。它允许我们在 HTTP 请…

Redis可视化工具:Another Redis Desktop Manager下载安装使用

1.Github下载 github下载地址: Releases qishibo/AnotherRedisDesktopManager GitHub 2. 安装 直接双击exe文件进行安装 3. 连接Redis服务 先启动Redis服务,具体启动过程可参考: Windows安装并启动Redis服务端(zip包&#xff09…

Golang | Leetcode Golang题解之第111题二叉树的最小深度

题目&#xff1a; 题解&#xff1a; func minDepth(root *TreeNode) int {if root nil {return 0}queue : []*TreeNode{}count : []int{}queue append(queue, root)count append(count, 1)for i : 0; i < len(queue); i {node : queue[i]depth : count[i]if node.Left …

FileZilla“服务器发回了不可路由的地址,使用服务器地址代替

问题&#xff1a;在宝塔创建的FTP无法使用&#xff0c;提示“服务器回应不可路由的地址。使用服务器地址代替 第一种解决办法&#xff1a;由于宝塔把FTP被动模式端口范围设置成了39000-40000&#xff0c;所以只需要把阿里云服务器上相应的端口范围开放即可。 第二种解决办法&am…

网络安全之安全协议浅谈

安全协议 安全协议概述安全协议分类IPSecIPSec安全协议IPSec架构IPSec封装模式AH协议ESP协议SET协议SET协议电子交易模型SET协议安全目标认证中心CA 安全协议概述 安全协议是信息交换安全的核心&#xff0c;它在网络不同层次上、针对不同应用&#xff0c;通过对各种密码学技术…

LiveGBS流媒体平台GB/T28181用户手册-云端录像:查看录像、列表视图、时间轴视图、下载、删除

LiveGBS流媒体平台GB/T28181用户手册-云端录像:查看录像、列表视图、时间轴视图、下载、删除 1、云端录像1.1、查看录像1.1.1、时间轴视图1.1.2、列表视图1.1.3、日期切换1.1.4、删除当天 1.2、录像计划1.2.1、录像计划列表1.2.2、编辑录像计划1.2.3、关联通道1.2.4、删除录像计…

解决在cmd里下载的库,但IDLE还是显示不存在的问题

原因一&#xff1a; 环境变量配置 首先&#xff0c;你需要确认你安装库的时候使用的Python环境是否和IDLE使用的Python环境是同一个。如果cmd中你使用的是系统路径下的Python&#xff0c;而IDLE使用的是另一个路径下的Python&#xff0c;那么你在cmd中下载的库&#xff0c;IDL…

存在重复元素 II[简单]

优质博文&#xff1a;IT-BLOG-CN 一、题目 给你一个整数数组nums和一个整数k&#xff0c;判断数组中是否存在两个不同的索引i和j&#xff0c;满足nums[i] nums[j]且abs(i - j) < k。如果存在&#xff0c;返回true&#xff1b;否则&#xff0c;返回false。 示例 1&#…

OpenAI撤回有争议的决定:终止永久性非贬损协议

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

[windows系统安装/重装系统][step-4][番外篇-2]N卡驱动重装 |解决:开机几小时后电脑卡顿 | 后台自动运行了上千个Rundll32进程问题

现象 开机几小时后&#xff0c;电脑变卡&#xff0c;打开后台管理器都卡&#xff0c;后台管理去转圈圈一小会儿后看到后台进程上千个&#xff0c;好多个Rundll32进程 重启下运行会稍快 重启后运行快&#xff0c;后台管理器反应也快 打开后台管理器不卡&#xff08;几小时后打…

力扣刷题---2215. 找出两数组的不同【简单】

题目描述 给你两个下标从 0 开始的整数数组 nums1 和 nums2 &#xff0c;请你返回一个长度为 2 的列表 answer &#xff0c;其中&#xff1a; answer[0] 是 nums1 中所有 不 存在于 nums2 中的 不同 整数组成的列表。 answer[1] 是 nums2 中所有 不 存在于 nums1 中的 不同 整…

营销短信XML接口对接发送示例

在现代社会中&#xff0c;通信技术日新月异&#xff0c;其中&#xff0c;短信作为一种快速、简便的通信方式&#xff0c;仍然在日常生活中占据着重要的地位。为了满足各种应用场景的需求&#xff0c;短信接口应运而生&#xff0c;成为了实现高能有效通信的关键。 短信接口是一种…

L01_JVM 高频知识图谱

这些知识点你都掌握了吗&#xff1f;大家可以对着问题看下自己掌握程度如何&#xff1f;对于没掌握的知识点&#xff0c;大家自行网上搜索&#xff0c;都会有对应答案&#xff0c;本文不做知识点详细说明&#xff0c;只做简要文字或图示引导。 类的生命周期 类加载器 JVM 的内存…

KVM热迁移虚拟机+KSM内存页合并

KVM高级功能部署 文章目录 KVM高级功能部署资源列表基础环境一、静态迁移1.1.在源宿主机上准备虚拟机1.1.1、调试VNC1.1.2、创建虚拟机test011.1.3、console登录test01虚拟机1.1.4、标记虚拟机test01当前IP地址 2.1、提取磁盘和配置文件2.2.1、查看虚拟机test01当前状态2.2.2、…