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,一经查实,立即删除!

相关文章

Python期末复习知识点大合集(期末不挂科版)

Python期末复习知识点大合集(期末不挂科版) 引言 Python是一种高级的、解释型的编程语言,以其清晰的语法和强大的功能而广受欢迎。为了帮助你在Python期末考试中取得优异的成绩,本文将为你提供一个全面的复习知识点合集。 基础…

一千题,No.0018(DFS)

小艾维卡每天都会解决填字游戏。如果你还没有看到,纵横字谜从一个由RC方块组成的网格开始,每个方块要么是空的,要么是阻塞的。玩家的任务是在垂直(自上而下)或水平(从左到右)的连续空白方块中书…

MySQL--存储引擎

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

VSCode CC++ 配置:

vscode配置C/C编译环境_vscode compiler path-CSDN博客 之后还有bash的配置设置

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…

银河麒麟桌面版操作系统配置远程SSH连接

一、启动ssh服务(已安装openss服务) sudo netstat -ntlp #查看22端口是否已启动 sudo Systemctl start ssh sudo systemctl enable ssh sudo systemctl status ssh sudo iptables -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT 二、启动ssh服务(未安装opens…

bambustudio debian en_GB

启动出现错误,报…en_GB… sudo nano /etc/locale.gen 取消 en_GB.UTF-8 的注释,并保存 sudo locale-gen

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

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

学习java第八十一天

解释Spring Bean的声明周期 Spring容器 从XML 文件中读取bean的定义,并实例化bean。 Spring填充bean定义(DI)中指定的所有属性。 如果bean实现了BeanNameAware接口,spring会将bean的id传递给setBeanName()方法。 如果有任何BeanBe…

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 管…

21、设计模式之模板方法模式

模板模式 在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。 介绍 意图: 定义…

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…

OpenGL梁友栋-Barsky算法裁剪线段

使用C语言 裁剪线段P1&#xff08;3&#xff0c;3&#xff09;&#xff0c;P2&#xff08;-2&#xff0c;-1&#xff09;,裁剪窗口为wxl0,wxr2,wyb0,wyt2。 // 梁友栋-barsky算法.cpp : 定义控制台应用程序的入口点。 //#include "stdafx.h"#include <GL/glut.h…

算法训练营day46

一、单词拆分 元素无重可复选 base case is.length return true&#xff0c;遍历到了最后, 因为ilen s.length&#xff0c;len初始值为1&#xff0c;那么i1 s.length&#xff0c;那么i s.lenth -1 也就是最后一个字符位置 dp(s,i)函数定义&#xff1a;返回 s[i…] 是否能够…

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;通过对各种密码学技术…