extern,翻译过来是“外面的、外部的”,作用就是声明外部全局变量或常量;需要注意extern只能声明,不能用于实现;开发中我们通常会单独创建一个类来管理一些全局的变量或常量,例如管理通过通知名称;extern只能用来修饰全局常量或变量
/*
extern都是写在.h文件中,声明全局变量或常量;这里仅仅是声明,实现是在.m文件中
如果这里只声明,而在.m文件中没有实现,外部如果使用的话编译会报错
*/
extern NSString *name1; //声明全局变量-外部可以修改
extern NSString *const name2; //声明全局常量-外部不能修改
@interface Person : NSObject
//显式声明extern表明其不是成员变量,而是全局变量
extern NSString *name;
extern int age;
@end
#import "Person.h"
/*
extern声明全局变量或常量的实现,必须实现,否则外部使用时,编译期会报错
*/
NSString *name1 = @"zhangsan";
NSString *const name2 = @"lisi";
@implementation Person
NSString *name = "zhangsan";
age = 18;
@end
#import "WGMainObjcVC.h"
@implementation WGMainObjcVC
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"修改前--全局变量name1:%@",name1); //
name1 = @"zhangsan11111";
NSLog(@"修改后--全局变量name1:%@",name1);
NSLog(@"修改前--全局常量name2:%@",name2);
//name2 = @"lisi"; 编译器会报错:Cannot assign to variable 'name2' with const-qualified
type 'NSString *const _Nonnull __strong'
}
@end
打印结果: 修改前--全局变量name1:zhangsan
修改后--全局变量name1:zhangsan11111
修改前--全局常量name2:lisi
分析,extern用来修饰全局变量或常量,一般在.h文件中声明,因为extern仅仅负责声明,而实现部分是需要我们在.m文件中实现的,如果不实现,外部使用变量/常量时会报错的;extern作用是用来获取全局变量或常量的,而不能用于定义变量;
如何引用:Person类中定义了全局变量,如果在WGBaseVC类中想要使用就需要import Person的.h文件或者Person有子类的话 import Person的子类Student的头文件都可以访问Person类中定义的全局变量; 第二种方式是通过不导入头文件的方式进行调用,通过extern调用,来获取全局变量的值
@implementation WGMainObjcVC
- (void)viewDidLoad {
[super viewDidLoad];
//只是用来获取全局变量(包括全局静态变量)的值,不能用于定义变量
extern NSString *name1;
NSLog(@"获取到的全局变量值是:%@",name1);
}
@end
- 生命周期: 这个变量能存活多久,它所占用的内存什么时候分配,什么时候收回
- 作用域:说白了就是这个变量在什么区域是可见的,可以拿来用的。
- 局部变量: 在函数或者说代码块内部声明的变量叫局部变量,局部变量存储在栈区,它的生命周期和作用域都是整个代码块
- static修饰局部变量
-
存储区由栈区变为静态区
-
修饰的局部变量只会初始化一次;局部变量在程序中只有一份内存
-
不会改变局部变量的作用域,仅仅改变了局部变量的生命周期(只有程序结束,这个局部变量才会销毁)
-
保证局部变量只会被初始化一次,在程序运行过程中,只会分配一次内存,生命周期类似全局变量,但作用域不变
-
好处: 定义后只会存在一份值,只会初始化一次,每次调用都是使用的同一个对象内存地址的值,并没有重新创建,节省空间
-
坏处: 存在的生命周期长,从定义直到程序结束
- (void)viewDidLoad { [super viewDidLoad]; [self test]; [self test]; NSLog(@"-----------"); [self test1]; [self test1]; } -(void)test { /* 局部变量 作用域: test函数内,出了test函数就不能再被访问了 声明周期: 和test函数声明周期一样,调用完test函数后就被销毁了 */ int num = 10; num = num + 1; NSLog(@"test: 当前的num值为:%d",num); } -(void)test1 { /* 被static修饰的局部变量-静态局部变量 作用域: test1函数内,出了test1函数就不能再被访问了 生命周期: test1函数执行结束后,该变量的生命周期仍然不会被销毁,它的生命周期是直到程序运行结束后才会被系统销毁 内存中只会存在一份,第一次调用后num=11,再次调用后num值仍然是11,然后执行num = num + 1后就变成12了 */ static int num = 10; num = num + 1; NSLog(@"test1: 当前的num值为:%d",num); } 打印结果: test: 当前的num值为:11 test: 当前的num值为:11 ----------- test1: 当前的num值为:11 test1: 当前的num值为:12
- static修饰全局变量
- 将全局变量的作用域限制在当前文件中,在其它文件中无法访问
- 全局变量的作用域仅限于当前文件内部,即当前文件内部才能访问该全局变量
⚠️ : 若全局变量在A文件的.h头文件中定义,那么在B文件中如果想访问,则impot A的头文件,则一样可以访问A文件中的静态全局变量,但是如果静态全局变量定义在A文件的.m文件中,则即便导入impot A的头文件也无法访问的- 好处:定义后只会指向固定的指针地址,供当前文件使用,同一源程序的其他文件中可以使用相同名字的变量,不会发生冲突
- 坏处: 存在的生命周期长,从定义直到程序结束
- 建议: 内存优化和程序编译的角度来说,尽量少用全局静态变量,因为存在的生命周期长,一直占用空间;
- 程序运行时会单独加载一次全局静态变量,过多的全局静态变量会造成程序启动慢
- 修饰函数
- 被修饰的函数被称为静态函数,使得外部文件无法访问这个函数,仅本文件可以访问
- 同一源程序的其他文件中可以使用相同名字的函数,不会发生冲突;编译器可对static静态函数进行更多的优化,因为已知该函数不会在其他源文件中使用
- static修饰全局变量和修饰局部变量共同点
- 被static修饰后,无论全局变量还是局部变量,都会存储在全局数据区(全局变量原本都存储在全局数据区,即使不加static)
- 全局数据区的数据在程序启动时就被初始化,一直到程序运行结束后才会被系统回收内存
- 全局数据区的数据只会被初始化一次,以后只能改变其值,不能再被初始化
- static作用
- 隐藏: 程序有多个文件时,将全局变量或函数的作用范围限制在当前文件,对其他文件隐藏。
- 保持变量内容的持久化: 将局部变量存储到全局数据区,使它不会随着函数调用结束而被销毁。
-
const修饰的变量是针对它右边的变量的,即const右边的变量值不能被修改
-
const修饰的变量是在编译阶段进行编译检查的
-
const修饰的变量仅在编译阶段初始化一次,存储在常量区,直到程序运行结束后由系统回收
const修饰变量的右边都不能被修改 1. const修饰基本数据类型变量 const int age1 = 10; //age1不能被修改 int const age2 = 20; //age2不能被修改 2. const修饰指针类型变量 const NSString *name1 = @"zhangsan1"; //*name1不能被修改,name1可以被修改 NSString const *name2 = @"zhangsan2"; //*name2不能被修改,name2可以被修改 NSString *const name3 = @"zhangsan3"; //name3不能被修改,*name3可以被修改 3. const嵌套使用 const NSString *const name4 = @"zhangsan4"; //name4和*name4都不能被修改 编译器会报错->Cannot assign to variable 'name3' with const-qualified type 'NSString *const __strong' //name3 = @"lisi";
-
一般是联合static和const使用,来定义一个只能在当前文件中访问的、不能被修改的变量;类似#define定义,不过优点就在于这种方式可以指定变量类型,而#define不能
static NSString *const name = @"zhangsan"; @implementation WGMainObjcVC - (void)viewDidLoad { [super viewDidLoad]; } @end
- 编译时刻不同:宏是预编译(编译之前处理); const是编译阶段
- 编译检查: 宏不做检查、不会报编译错误、只是替换;const会编译检查、会报编译错误
- 宏优点就是可以定义函数、方法;const不能
- 宏缺点: 大量使用宏,会造成编译时间太长,每次都需要重新替换
-
使用static和const定义全局静态变量(其实就是常量),只能在当前文件中使用
static NSString *const name = @"zhangsan"; -
使用extern和const,定义一个全局变量(其实就是常量),多个文件都可以访问,而且不需要导入对应的头文件都可以访问
extern NSString *const name; @interface Person : NSObject @end #import "Person.h" NSString *const name = @"zhangsan"; @implementation Person @end -
使用宏#define定义(切记后面不能添加分号;,否则编译器会报错)
#define WGName @"张三"
iOS 6 之后 LLVM 编译器引入property autosynthesis,即属性自动合成,下面定义的属性会自定生成成员变量_name、getter/setter方法声明、getter/setter方法实现
@interface WGMainObjcVC : UIViewController
@property(nonatomic, copy) NSString *name;
@end
@implementation WGMainObjcVC
@dynamic name;
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.name = @"zhangsan";
}
@end
@dynamic name; 告诉编译器,不要自动生成对应属性name的getter/setter方法实现,同时也不会自动生成属性对应的成员变量,方法实现需要我们程序员自己实现,如果我们没有实现,那么在访问属性过程中程序就会crash
@interface WGMainObjcVC : UIViewController
@property(nonatomic, copy) NSString *name;
@end
@implementation WGMainObjcVC
//这句代码也可以不写,默认生成成员变量的名称是_name
@synthesize name = AAA;
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.name = @"zhangsan";
}
@end
@synthesize name = AAA; 告诉编译期生成成员变量的名称为AAA,即在iOS6之后,@synthesize作用就是给成员变量起别名;@dynamic和@synthesize都没有写时,@property默认是@synthesize XXX = _XXX,@synthesize表示如果我们没有手动实现setter/getter方法,编译器会自动加上这两个方法,如果我们手动实现了setter/getter,那么系统就不会再自动生成setter/getter方法了
synchronized是递归锁(@synchronized关键字在多线程环境中还具备递归锁定的能力。这意味着同一线程可以多次获取同一把锁,而不会导致死锁的产生,极大地提升了递归调用的安全性和可行性),使用该关键字,可以将一段代码限制在一个线程内使用,其它线程想要访问,就必须等上一个线程访问完后才能访问,即保证了线程安全
@interface WGMainObjcVC()
@property(nonatomic, assign) int totalTicket; //总票数
@end
@implementation WGMainObjcVC
- (void)viewDidLoad {
[super viewDidLoad];
_totalTicket = 10;
//线程1 卖5张
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 5; i++) {
[self sealTicket];
}
});
//线程1 卖5张
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 5; i++) {
[self sealTicket];
}
});
}
-(void)sealTicket {
_totalTicket -= 1;
NSLog(@"当前剩余票数:%d",_totalTicket);
}
@end
打印结果: 当前剩余票数:8
当前剩余票数:9
当前剩余票数:7
当前剩余票数:7
当前剩余票数:6
当前剩余票数:5
当前剩余票数:4
当前剩余票数:3
当前剩余票数:2
当前剩余票数:1
-(void)sealTicket {
@synchronized (self) { //会对传入的对象分配一个递归锁
_totalTicket -= 1;
NSLog(@"当前剩余票数:%d",_totalTicket);
}
}
@synchronized关键字的递归锁定能力是通过在运行时维护一个线程到锁计数器的映射来实现的。当同一线程多次进入同一个@synchronized代码块时,锁计数器会递增,而不是重新获取锁。这样,即使线程多次进入临界区,也不会导致死锁。当线程离开@synchronized代码块时,锁计数器会递减,直到计数器归零,此时锁才会被释放,允许其他线程进入临界区https://www.51cto.com/article/795665.html
fallthrough贯穿,主要用在switch条件语句中,Swift中的switch不会从上一个case分支落入到下一个 case 分支中;fallthrough 语句让 case 之后的语句会按顺序继续运行,且不论条件是否满足都会执行
var score = 70 var score = 70
switch score { switch score {
case 0...60: case 0...60:
print("成绩不及格") print("成绩不及格")
case 61...70: case 61...70:
print("成绩良好") print("成绩良好")
default: fallthrough
print("成绩优秀") case 31...60:
} print("成绩不及格")
fallthrough
default:
print("成绩优秀")
}
打印结果: 成绩良好 打印结果: 成绩良好
成绩不及格
成绩优秀
typealias Location = CGPoint
- 类型别名允许您为程序中的现有数据类型提供新名称。声明类型别名后,可以在整个程序中使用别名代替现有类型
- 类型别名不会创建新类型。它们只是为现有类型提供一个新名称
- typealias 的主要目的是使我们的代码更具可读性,并且在上下文中更清晰易懂,以供人类理解
protocol WGOneProtocol : class {
//设置关联类型
associatedtype GenderType
var sex: GenderType {get} //性别
}
class Student : WGOneProtocol{
//实现协议中的属性 必须通过typealias指定类型
typealias GenderType = Bool
var sex: GenderType = false
}
class Animal : WGOneProtocol {
//实现协议中的属性 必须通过typealias指定类型
typealias GenderType = Int
var sex: GenderType = 0
}
-
在 Swift 中 associatedtype 是一种与协议(Protocol)相关的高级特性
-
它允许协议在定义时包含一个或多个占位类型,而不具体指明这些类型是什么,留待采用协议的具体类型来定义
-
associatedtype 用于定义协议中的一个或多个关联类型;它们作为占位符,用来表示协议中某些属性或方法的类型, 但这些类型在协议定义时是不确定的。关联类型的具体类型由遵循协议的类型在实现时提供。
-
associatedtype 只能在 protocol 中使用,class 中应该使用typealias进行代替。
-
associatedtype 在协议中引入了一种灵活性,使得协议可以用于更广泛的类型,而不需要在协议定义时明确具体类型
-
协议中不支持泛型,如果在协议中需要达到泛型这种类似的效果,可以使用 associatedtype 关键字
//错误例子: protocol 不支持范型,需要使用 associatedtype 来代替 protocol Stack<Element> { } //正确例子: associatedtype 支持在 protocol 中实现范型的功能。 protocol Stack { associatedtype Element func push(e: Element) -> Void func pop() -> Element }
mutating关键字指的是可变即可修改。用在结构体struct和枚举enum中,虽然结构体和枚举可以定义自己的方法,但是默认情况下,实例方法中是不可以修改值类型的属性的(值类型的属性不能被自身的实例方法修改)。为了能够在实例方法中修改属性值,可以在方法定义前添加关键字mutating。本质上mutating这个关键字就做了一件事情,默认给结构体LGStack添加了一个intou关键字,这个inout关键字传递的过程中传递的就是所谓的引用
struct MyStruct {
var name = ""
var age = 0
mutating func testFunc() {
age = 18
}
}
public override func viewDidLoad() {
super.viewDidLoad()
//这里必须是var修饰,否则编译器会报错
var a = MyStruct()
a.testFunc()
NSLog("age:\(a.age)")
}
打印结果: age:18
- Swift的结构体或者枚举的方法中,如果方法中需要修改当前结构体或者枚举的属性值,则需要再func前面加上mutating关键字,否则编译器会直接报错
- 普通函数传值参数是值传递,加mutating关键字后参数会变成地址传递
- mutating关键字本质是包装了inout关键字,加上mutating关键字后参数值会变成地址传递。 类对象是指针,传递的本身就是地址值,所以 mutating关键字对类是透明的,加不加效果都一样
class WGMyClass {
final var name = ""
final func testFunc(){
NSLog("WGMyClass->testFunc")
}
}
- final关键字表示不允许对其修饰的内容进行继承或者重新操作(重写)
- final在swift中,可以指定函数派发机制,通过final可以显示的指定函数的派发机制采用直接派发的方式,即直接调用函数地址进行方法调用
- 便利构造函数通常都是写在extension里面
- 便利函数init前面需要加载convenience
- 在便利构造函数中需要明确的调用self.init()
- 对象销毁
- KVO移除
- 移除通知
- NSTimer销毁
@objc修饰符的根本目的是用来暴露接口给Objective-C的运行时(类、协议、属性、方法等);添加@objc修饰并不意味着这个方法或属性会采用Objective-C的方式变成动态派发,swift仍可能会将其优化为静态调用;
- selector调用的方法前需要加@objc,目的是允许函数在“运行时”通过oc消息机制调用
- 协议的方法可选时,协议和可选方法前要用@objc声明
- 用weak修饰协议时,协议前面要用@objc声明
- 类前加上 @objcMembers,那么它及其子类、扩展里的方法都会隐式的加上 @objc
- 扩展前加上 @objc,那么里面的方法都会隐式加上 @objc
public override func viewDidLoad() {
super.viewDidLoad()
NSLog("1111")
defer {
NSLog("------")
}
NSLog("2222")
}
打印结果: 1111
2222
------
public override func viewDidLoad() {
super.viewDidLoad()
NSLog("1111")
defer {
NSLog("2222------")
}
defer {
NSLog("3333------")
}
defer {
NSLog("4444------")
}
NSLog("5555")
}
打印结果: 1111
5555
4444------
3333------
2222------
- 可变参数不能标记为inout
- inout参数不能有默认值
- inout参数的本质是地址传递(引用传递)
- inout参数只能传入可以被多次赋值的
- throws:标记函数可能会抛出异常,调用函数的地方需要处理可能抛出的异常
- throw是 Swift 中实现错误处理的核心机制。它允许函数将错误信息传递给调用者,使得调用者能够根据具体的错误类型进行相应的处理
- rethrows:函数本身不会抛出异常,如果作为参数的闭包抛出了异常,则会继续往上抛出异常,可以理解为传递throw
- rethrow用于函数本身不产生新的错误,但会将调用其他函数产生的错误重新抛出的情况。通常出现在高阶函数(接受函数作为参数的函数)中
- 错误传递和抽象:rethrow提供了一种简洁的方式来传递错误,特别是在高阶函数的场景中。它允许函数将错误处理的责任进一步传递给调用者, 而自身不需要处理具体的错误类型
- rethrow一般用在【高阶函数】【函数组合场景】【代理或回调场景】
- swift语法简洁,类型安全的语言.swift在编译期会进行类型检查,使我们开发过程中更早的发现问题
- swift多了元组/反射的概念
- swift面向协议编程,函数式编程,面向对象编程,swift函数是一等公民(函数可以作为变量,可以作为其它函数的参数,可以作为其它函数的返回值)
- swift中值类型增强,swift中的struct/enum/元组等都是值类型;使用值类型好处就是它的不可变性/独立性
- swift中的枚举增强,枚举可以使用整型/浮点型/字符串等,枚举还可以拥有属性和方法,还支持范型/协议/扩展等
- swift支持泛型,也支持泛型约束,swift支持可选类型
- swift的协议和扩展更丰富,扩展性更好. swift中的结构体+协议可以模拟class继承
- copy
- assign
- strong
- weak
- readonly
- readwrite
- automic
- nonautomic
- retain
MRC手动管理(7个): assign/retain/copy/readwrite/readonly/nonatomic/atomic
ARC自定管理(8个): assign/strong/copy/readwrite/readonly/nonatomic/atomic/weak
assign修饰符一般用来修饰基本数据类型(NSInteger/CGFloat/Int/Float/Double等),被assign修饰的属性的setter方法是直接赋值的,不会进行任何retain操作,在MRC和ARC下都可以使用assign, 它的setter方法如下
直接进行赋值操作
-(void)setAge:(NSInteger)age {
_age = age;
}
@interface WGMainObjcVC()
@property(nonatomic, assign) Person *p1;
@end
@implementation WGMainObjcVC
- (void)viewDidLoad {
[super viewDidLoad];
self.p1 = nil;
{
Person *p0 = [[Person alloc]init];
//p1是用assign修饰的,所以既不持有对象的强引用也不持有对象的弱引用
self.p1 = p0;
NSLog(@"p1的地址是:%@---p0的地址是:%@",self.p1, p0);
}
NSLog(@"p1的地址是:%@",self.p1);
}
@end
打印结果: p1的地址是:<Person: 0x600003438430>---p0的地址是:<Person: 0x600003438430>
编辑可以通过,但是在运行的时候会报错,报错信息如下
message sent to deallocated instance 0x600003438430
- 野指针:指针指向的对象/内容已经被销毁了,即指针指向了一块“垃圾内存”;给野指针发消息会crash的,野指针并不是nil指针;
- 空指针:指的是没有存储任何内存地址的指针;给空指针发消息不会报错的;
- 僵尸对象: 一个OC对象引用计数为0被释放后就变成僵尸对象了,僵尸对象的内存已经被系统回收,虽然可能该对象还存在,数据依然在内存中,但僵尸对象已经是不稳定对象了,不可以再访问或者使用,它的内存是随时可能被别的对象申请而占用的。
总结:assign一般用来修饰基本数据类型,若修饰对象,会出现野指针的问题;assign和weak的区别:就是weak在对象销毁的时,会自动将对象置为nil,而assign不会从而导致野指针问题(对象销毁了,指针指向了一个垃圾内存);另外一个原因就是assgin修饰的基本数据类型,内存是分配在栈上的,栈内存的分配和销毁是由系统控制的,而对象类型的数据是分配在堆上的,用assign修饰的对象,在对象销毁时,对象的指针地址是还存在的,也就是说指针并没有被置为nil,
atomic原子属性是线程安全的,即多线程访问能够保证数据的完整性,为什么是线程安全的?因为系统在atomic属性的setter方法中添加了自旋锁来保证多线程访问的安全性,但是这样就耗费了系统资源。使用atomic原子属性并不都是线程安全的,因为atomic属性只有在setter/getter方法中是原子操作,是安全的,但是在setter/getter方法外不是原子操作的,例如++/--运算符情况下
nonatomic非原子属性,不是线程安全的,因为系统没有在属性的setter方法中添加自旋锁,它的特点是多线程并发访问性能高但不安全,所以nonatomic要注意多线程间通信的线程安全,项目中我们仍然会大量使用nonatomic非原子属性的原因也是因为访问性能高
Runtime源代码
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offset == 0) {
return object_getClass(self);
}
// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock(); //加锁
id value = objc_retain(*slot);
slotlock.unlock(); //解锁
//for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value);
}
using spinlock_t = mutex_tt<LOCKDEBUG>;
class mutex_tt : nocopy_t {
os_unfair_lock mLock; //这是iOS10之后用到的互斥锁
}
- 自旋锁: 自旋锁会忙等: 所谓忙等,即在访问被锁资源时,调用者线程不会休眠,而是不停循环在那里,直到被锁资源释放锁。自旋锁的优点在于,因为自旋锁不会引起调用者睡眠,所以不会进行线程调度、CPU时间片轮转等耗时操作。所有如果能在很短的时间内获得锁,自旋锁的效率远高于互斥锁,缺点在于,自旋锁一直占用CPU,他在未获得锁的情况下,一直运行--自旋,所以占用着CPU,如果不能在很短的时 间内获得锁,这无疑会使CPU效率降低。自旋锁不能实现递归调用。
- 互斥锁: 互斥锁会休眠: 所谓休眠,即在访问被锁资源时,调用者线程会休眠,此时CPU可以调度其他线程工作。直到被锁资源释放锁。此时会唤醒休眠线程。互斥锁可以传入不同参数,实现递归锁
MRC下使用的,会使引用计数+1,但在ARC下已经被舍弃了,改用strong来修饰了,如果我们在ARC环境的工程中想让某些文件支持MRC,可以在Build Phaes—>Compile Sources—>XXX文件 找到对应文件配置 -fno-objc-arc来支持MRC
setter方法释放旧对象,retain新对象
-(void)setName:(NSString *)name {
if (_name != name) {
[_name release];
_name = [name retain];
}
}
ARC环境下才使用的,对应MRC环境下的retain。表示对对象的强引用,对象的引用计数+1,只要有一个strong指针指向对象,该对象就不会被销毁,ARC 下不显式指定任何属性关键字时,基本数据默认的关键字是 atomic、readwrite、assign,普通的OC对象: atomic、readwrite、strong。strong只能用来修饰对象类型,如果修饰基本数据类型,编译器会报错
readonly声明你的属性是只读的,并且告诉编译器不用自动生成setter方法;当你尝试给一个readonly的属性赋值时,会Xcode提示错误; readwrite声明的属性是可读可写的,编译器会自动生成setter/getter方法;readwrite是默认的;
一般我们声明@property属性时,系统会自动为我们生成对应的成员变量(也叫实例变量)+setter/getter方法的声明和实现,当我们使用@dynamic XXX,此时系统就不会自动生成XXX对应的setter/getter方法实现,也不会生成对应的成员变量,但是不影响setter/getter方法的声明,如果我们自己又没有手动实现setter/getter方法,那么在调用存取方法时程序运行就会crash(编译期不会报错);@synthesize XXX = _XXX,当我们@dynamic和@synthesize都没有写时,@property默认是@synthesize XXX = _XXX,@synthesize表示如果属性没有手动实现setter和getter方法,编译器会自动加上这两个方法,如果我们手动实现了setter/getter,那么系统就不会再自动生成setter/getter
@interface Person : NSObject
@property(nonatomic, strong) NSString *name;
@end
//@dynamic 告诉编辑器不自动生成name属性的getter和setter方法实现和对应的成员变量
@implementation Person
@dynamic name;
@end
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc]init];
//程序编译期不会报错,但在运行时会报错:因为@dynamic的声明,系统没有生成对应的setter/getter方法
//reason: '-[Person setName:]: unrecognized selector sent to instance 0x600001d6a9a0'
p.name = @"zhangsan";
}
- @dynamic name1
- @synthesize name1 = _name1
作用:告诉编译器生成成员变量_name1;生成name1属性的getter/setter的声明;生成name1属性的getter/setter的实现,如果自己手动实现了setter/getter方法,那么@synthesize就告诉编译器不再生成setter/getter方法的实现了,Xcode中我们编写属性后,默认就是这种模式,@synthesize的另一个作用就是给自定义生成成员变量的名称
ARC环境下才会使用,weak弱引用不增加引用计数也不持有对象,适用于NSObject对象,weak修饰的对象在释放之后,指针地址会被置为nil,weak弱引用作用可以用来解决循环引用问题,并且weak不会造成野指针的问题
weak修饰基本数据类型时,编译器会报错,weak只能用于对象类型
@property(nonatomic, weak) NSInteger age;
Property with 'weak' attribute must be of object type
/**
* The global weak references table. Stores object ids as keys,
* and weak_entry_t structs as their values.
*/
struct weak_table_t {
weak_entry_t *weak_entries; //保存了所有指向指定对象的weak指针
size_t num_entries; //weak对象的存储空间
uintptr_t mask; //参与判断引用计数辅助量
uintptr_t max_hash_displacement; //hash key 最大偏移值
};
//weak全局表中存储weak定义的对象的表结构weak_entry_t,它负责维护和存储指向一个对象的所有弱引用hash表
struct weak_entry_t {
//是对泛型对象的指针做了一个封装,通过这个泛型类来解决内存泄漏的问题
DisguisedPtr<objc_object> referent;
union {
struct {
weak_referrer_t *referrers;
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
...
}
weak_table_t(weak全局表),采用hash哈希表的方式,来存储所有引用weak的对象,用weak指向的对象的内存地址作为key,用weak指针的地址(这个地址的值是所指对象指针的地址)数组作为Value来存储了,为什么value是数组?因为一个对象可能被多个弱引用指针指向
-
初始化时,Runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
objc_initWeak(id *location, id newObj) { if (!newObj) { //查看对象实例是否有效,无效对象直接导致指针释放 *location = nil; return nil; } //对象实例有效,则通过storeWeak函数,对象实例被注册为一个指向value的__weak对象 return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object*)newObj); } -
添加引用时,objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
-
释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
- 调用objc_release
- 因为对象的引用计数为0,所以执行dealloc
- 在dealloc中,调用了_objc_rootDealloc函数
- 在_objc_rootDealloc中,调用了object_dispose函数
- 调用objc_destructInstance
- 最后调用objc_clear_deallocating。
- 对象准备释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
总结: weak是Runtime维护了一个全局的哈希表,用来存放指向某个对象的所有weak指针,weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。
源对象类型 拷贝方法 副本对象类型 是否产生新对象 拷贝类型
NSString copy NSString NO 浅拷贝(指针拷贝)
mutableCopy NSMutableString YES 深拷贝(内容拷贝)
NSMutableString copy NSString YES 深拷贝(内容拷贝)
mutableCopy NSMutableString YES 深拷贝(内容拷贝)
NSArray copy NSArray NO 浅拷贝(指针拷贝)
mutableCopy NSMutableArray YES 深拷贝(内容拷贝)
NSMutableArray copy NSArray YES 深拷贝(内容拷贝)
mutableCopy NSMutableArray YES 深拷贝(内容拷贝)
NSXXXX copy NSXXXX NO 浅拷贝(指针拷贝)
mutableCopy NSMutableArray YES 深拷贝(内容拷贝)
NSMutableXXXX copy NSXXXX YES 深拷贝(内容拷贝)
mutableCopy NSMutableXXXX YES 深拷贝(内容拷贝)
array1 浅拷贝 array2 array1 深拷贝 array2 深拷贝
0------->A<--------0 0------->A 0------->A
1------->B<--------1 1------->B 1------->B
2------->C<--------2 2------->C 2------->C
3------->D<--------3 3------->D 3------->D
- 修饰block,在MRC下,copy可用来修饰block,block内部的代码块是在栈区的,使用copy关键字可以把它放在堆区;在ARC环境下,使用strong和copy效果是一样的
- 修饰NSString、NSArray、NSDictionary
//.m文件
@interface WGMainObjcVC()
@property(nonatomic, strong) NSString *strongStr;
@property(nonatomic, copy) NSString *copyyStr;
@end
@implementation WGMainObjcVC
- (void)viewDidLoad {
[super viewDidLoad];
//1. 用不可变字符串赋值操作, 无论strong或者copy,其指向的地址都是baseStr的地址,即copy拷贝的是对象的地址
NSString *baseStr = [NSString stringWithFormat:@"123"];
_strongStr = baseStr;
_copyyStr = baseStr;
NSLog(@"baseStr对象地址: %p,对象的指针地址:%p, 值:%@",baseStr,&baseStr,baseStr);
NSLog(@"strongStr对象地址: %p,对象的指针地址:%p, 值:%@",_strongStr,&_strongStr,_strongStr);
NSLog(@"copyyStr对象地址: %p,对象的指针地址:%p, 值:%@",_copyyStr,&_copyyStr,_copyyStr);
}
@end
打印结果: baseStr对象地址: 0x8c43b6f85e3255c4,对象的指针地址:0x7ffee68ae790, 值:123
strongStr对象地址: 0x8c43b6f85e3255c4,对象的指针地址:0x7f9726c08790, 值:123
copyyStr对象地址: 0x8c43b6f85e3255c4,对象的指针地址:0x7f9726c08798, 值:123
总结,对于用不可变的源对象NSString/NSArray/NSDictionary来给copy和strong修饰的对象进行赋值操作,都是对源对象的地址拷贝并没有开辟新的内存,即指针都是指向了源对象,copy进行的是浅拷贝
//1. 用不可变字符串赋值操作, 无论strong或者copy,其指向的地址都是baseStr的地址,即copy拷贝的是对象的地址
NSString *baseStr = [NSString stringWithFormat:@"123"];
NSLog(@"baseStr对象地址: %p,对象的指针地址:%p, 值:%@",baseStr,&baseStr,baseStr);
_strongStr = baseStr;
_copyyStr = baseStr;
//当重新对baseStr进行赋值时,因为baseStr是不可变字符串,为了保持不可变性,系统会另外开辟内存空间来存放变更后的内容
//但是这并不会影响copy和strong修饰的对象
baseStr = @"456";
NSLog(@"baseStr对象地址: %p,对象的指针地址:%p, 值:%@",baseStr,&baseStr,baseStr);
NSLog(@"strongStr对象地址: %p,对象的指针地址:%p, 值:%@",_strongStr,&_strongStr,_strongStr);
NSLog(@"copyyStr对象地址: %p,对象的指针地址:%p, 值:%@",_copyyStr,&_copyyStr,_copyyStr);
打印结果: baseStr对象地址: 0xf113747087f4ecf8,对象的指针地址:0x7ffee2874790, 值:123
baseStr对象地址: 0x10d390610,对象的指针地址:0x7ffee2874790, 值:456
strongStr对象地址: 0xf113747087f4ecf8,对象的指针地址:0x7f8391a0e980, 值:123
copyyStr对象地址: 0xf113747087f4ecf8,对象的指针地址:0x7f8391a0e988, 值:123
- 不可变数组1->copy=不可变数组2,不可变数组1和不可变数组2的地址是一样的,即浅拷贝
- 不可变数组1->mutableCopy=可变数组2,不可变数组1和可变数组2的地址是不一样的,即深拷贝
- 可变数组1->copy=不可变数组2,可变数组1和不可变数组2的地址是不一样的,即深拷贝
- 可变数组1->mutableCopy=可变数组2,可变数组1和可变数组2的地址是不一样的,即深拷贝
Person *p1 = [[Person alloc]init];
Person *p2 = [[Person alloc]init];
Person *p3 = [[Person alloc]init];
NSArray *baseArr = @[p1, p2, p3];
NSArray *copyArr = [baseArr copy];
NSMutableArray *mutableCopyArr = [baseArr mutableCopy];
NSLog(@"\n源数组地址:%p\n---元素1:%p\n---元素2:%p\n---元素3:%p\n",baseArr,baseArr[0],baseArr[1],baseArr[2]);
NSLog(@"\n浅拷贝copy后数组地址:%p\n---元素1:%p\n---元素2:%p\n---元素3:%p\n",copyArr,copyArr[0],copyArr[1],copyArr[2]);
NSLog(@"\n深拷贝copy后数组地址:%p\n---元素1:%p\n---元素2:%p\n---元素3:%p\n",mutableCopyArr,mutableCopyArr[0],mutableCopyArr[1],mutableCopyArr[2]);
打印结果: 源数组地址:0x600000045eb0
---元素1:0x60000001ecf0
---元素2:0x60000001ed20
---元素3:0x60000001ed40
浅拷贝copy后数组地址:0x600000045eb0
---元素1:0x60000001ecf0
---元素2:0x60000001ed20
---元素3:0x60000001ed40
深拷贝copy后数组地址:0x600000046000
---元素1:0x60000001ecf0
---元素2:0x60000001ed20
---元素3:0x60000001ed40
如果数组里面装的是模型数据,那么经过copy和mutableCopy后仍然遵守上面的拷贝规则,但是深拷贝后,数组中的模型数据的地址仍然指向的是之前的模型元素对象,即深拷贝中,模型数据是不会进行深拷贝的; 如果我们想深拷贝的时候,也把模型数据拷贝一份,该如何解决?
方案1:数组拷贝时不再调用mutableCopy,而是调用initWithArray:baseArr copyItems:YES 方法,该方法要求数组中的元素要遵守NSCopying协议 ,实现copyWithZone方法
@interface Person : NSObject
@property(nonatomic, strong) NSString *name;
@end
@interface Person()<NSCopying>
@end
@implementation Person
-(id)copyWithZone:(NSZone *)zone {
Person *p = [[self class] allocWithZone:zone];
p.name = [_name copy];
return p;
}
@end
Person *p1 = [[Person alloc]init];
Person *p2 = [[Person alloc]init];
Person *p3 = [[Person alloc]init];
NSArray *baseArr = @[p1, p2, p3];
NSArray *copyArr = [baseArr copy];
NSMutableArray *mutableCopyArr = [[NSMutableArray alloc]initWithArray:baseArr copyItems:YES];
NSLog(@"\n源数组地址:%p\n---元素1:%p\n---元素2:%p\n---元素3:%p\n",
baseArr,baseArr[0],baseArr[1],baseArr[2]);
NSLog(@"\n浅拷贝copy后数组地址:%p\n---元素1:%p\n---元素2:%p\n---元素3:%p\n",
copyArr,copyArr[0],copyArr[1],copyArr[2]);
NSLog(@"\n深拷贝copy后数组地址:%p\n---元素1:%p\n---元素2:%p\n---元素3:%p\n",
mutableCopyArr,mutableCopyArr[0],mutableCopyArr[1],mutableCopyArr[2]);
打印结果: 源数组地址:0x610000055ea0
---元素1:0x610000006180
---元素2:0x610000006190
---元素3:0x6100000061a0
浅拷贝copy后数组地址:0x610000055ea0
---元素1:0x610000006180
---元素2:0x610000006190
---元素3:0x6100000061a0
深拷贝copy后数组地址:0x610000055ff0
---元素1:0x6100000061b0
---元素2:0x6100000061c0
---元素3:0x6100000061d0
-
那么Student类也要遵守NSCopying协议并实现copyWithZone方法,这样就是开辟了新的空间,实现了内容的深拷贝
@interface Person : NSObject @property(nonatomic, strong) NSString *name; @property(nonatomic, strong) Student *stu; @end
@interface Person : NSObject
@property(nonatomic, strong) NSString *name;
@property(nonatomic, strong) NSArray *studentArr;
@end
@interface Person : NSObject
@property(nonatomic, strong) NSString *name;
@end
@interface Person()<NSCoding>
@end
@implementation Person
-(instancetype)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self) {
self.name = [coder decodeObjectForKey:@"name"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.name forKey:@"name"];
}
@end
Person *p1 = [[Person alloc]init];
Person *p2 = [[Person alloc]init];
Person *p3 = [[Person alloc]init];
NSArray *baseArr = @[p1, p2, p3];
NSArray *copyArr = [baseArr copy];
//2、归档和解档
//归档
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:baseArr];
//接档
NSMutableArray *mutableCopyArr = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NSLog(@"\n源数组地址:%p\n---元素1:%p\n---元素2:%p\n---元素3:%p\n",
baseArr,baseArr[0],baseArr[1],baseArr[2]);
NSLog(@"\n浅拷贝copy后数组地址:%p\n---元素1:%p\n---元素2:%p\n---元素3:%p\n",
copyArr,copyArr[0],copyArr[1],copyArr[2]);
NSLog(@"\n深拷贝copy后数组地址:%p\n---元素1:%p\n---元素2:%p\n---元素3:%p\n",
mutableCopyArr,mutableCopyArr[0],mutableCopyArr[1],mutableCopyArr[2]);
打印结果:源数组地址:0x600000243300
---元素1:0x60000000ea80
---元素2:0x60000000ea90
---元素3:0x60000000eaa0
浅拷贝copy后数组地址:0x600000243300
---元素1:0x60000000ea80
---元素2:0x60000000ea90
---元素3:0x60000000eaa0
深拷贝copy后数组地址:0x600000243900
---元素1:0x60000000eab0
---元素2:0x60000000eaf0
---元素3:0x60000000eb00
-
initWithArray:copyItems:模型遵守NSCopying(对象可以copy)或NSMutableCopying(对象可以mutableCopy)协议,并实现copyWithZone和mutableCopyWithZone方法,
@interface Person : NSObject @property(nonatomic, strong) NSString *name; @property(nonatomic, assign) NSInteger age; @end @interface Person()<NSCopying,NSMutableCopying> @end @implementation Person -(id)copyWithZone:(NSZone *)zone { Person *p = [[[self class] allocWithZone:zone]init]; p.name = [_name copy]; p.age = _age; return p; } -(id)mutableCopyWithZone:(NSZone *)zone { Person *p = [[[self class] allocWithZone:zone]init]; p.name = [_name mutableCopy]; p.age = _age; return p; } @end -
归档解档:模型遵守NSCoding协议,并实现initWithCoder和encodeWithCoder方法
@interface Person : NSObject @property(nonatomic, strong) NSString *name; @property(nonatomic, assign) NSInteger age; @end @interface Person()<NSCoding> @end @implementation Person -(instancetype)initWithCoder:(NSCoder *)coder { self = [super init]; if (self) { self.name = [coder decodeObjectForKey:@"name"]; self.age = (int)[coder decodeObjectForKey:@"age"]; } return self; } -(void)encodeWithCoder:(NSCoder *)coder { [coder encodeObject:self.name forKey:@"name"]; [coder encodeInteger:self.age forKey:@"age"]; } @end -
两种方式中,initWithArray:copyItems方式只能对一级模型进行拷贝,如果模型中含有数组模型,它就无能为力了;而利用归档和解档就不存在这个问题
@interface WGMainObjcVC : UIViewController
@property(nonatomic, strong) NSArray *arr;
@end
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableArray *sourceArr = [NSMutableArray arrayWithObject:@"123"];
//用可变的数组给arr进行赋值
self.arr = sourceArr;
//修改sourceArr的元素
[sourceArr addObject:@"123"];
NSLog(@"arr地址:---%p----arr元素:---%@",self.arr,self.arr);
NSLog(@"sourceArr地址:---%p----sourceArr元素:---%@",sourceArr,sourceArr);
}
打印结果: arr地址:---0x600000059a70----arr元素:---(123, 123)
sourceArr地址:---0x600000059a70----sourceArr元素:---(123, 123)