(002)iOS属性浅析

今天简单的看了一下oc的属性和修饰符,当然还有一些简单的强弱引用等,就命名为属性吧。

列举一下一些简单的属性,及其修饰符。

static int countNum = 1;

@protocol LJLDelegate <NSObject>
-(void)showDelegate;
@end

typedef void(^LJLBlock)(int);

@interface LJLPropertyViewController ()

@property(nonatomic, assign) int num;
@property(nonatomic, assign) double height;
@property(nonatomic, assign) int age;
@property(nonatomic, strong) NSString * str;
@property(nonatomic, copy) NSString * str2;
@property(nonatomic, weak) id <LJLDelegate>delegate;
@property(nonatomic, copy) LJLBlock block;
@property(nonatomic, strong) UIButton * button;
@property(nonatomic, assign, readwrite) BOOL circular;//允许读写

@end

通过如上方式创建的属性,这里系统自动创建 set/get 方法
用真机运行,在arm64。会调用方法objc_msgSend  前面会先准备需要的参数id self  、  SEL  、数值
   

self.num = 10;
self.height = 1.85;
self.age = 30;
self.str = @"ljl";


 寄存器用来存放参数,一般 0x0 ~ 0x7
 此时的寄存器存放的参数是
 0x0: id self
 0x1: setNum:
 0x2: 10
    
strong在底层自动进行了retain 和 release操作。使用copy修饰的话就会区分原子性和非原子性。
 1、非原装性(nonatomic)

在底层实现

oldValue = *slot
*slot = newValue;


 2、原子性(atomic)

spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;        
slotlock.unlock();

加锁,然后
 oldValue = *slot
 *slot = newValue;
 解锁
 这个锁只能保证set/get的安全
 

self.str = @"ljl123";

创建对象所需开辟的内存大小
 创建一个对象的最小内存空间为16。
参考: https://www.bilibili.com/video/av79839999?from=search&seid=7187104079046109220
 创建一个继承于NSObject的类,成功之后也是一个对象(类对象),对象和结构体的内存对,这个地方有个疑问。先记录一下后面查到更详细资料再来解释,如又哪位大神指导不吝赐教:

@interface LGPerson : NSObject
@property(nonatomic, assign) int num;
@property(nonatomic, assign) double height;
@property(nonatomic, assign) int age;
@end
    
LGPerson *p1 = [LGPerson alloc];
p1.age = 10;
p1.num = 5;
p1.height = 1.8;

(lldb) x/4gx p1
0x600000a899a0: 0x0000000109a79b00 0x0000000a00000005
0x600000a899b0: 0x3ffccccccccccccd 0x0000000000000000

这个时候除了前8位是isa(对象的第一个属性是isa,隐藏属性)之外
int是4位  double是8位。这个时候第二个8位放了int num,剩下4位放不下double height,就会放到第三个8位。接下来是int 这个int放到 第二个8位的后面。如上代码中的 0x0000000a00000005。 这里包含了10和5.我们设置的值。这个地方是系统进行了内存优化,将第二位后面的4位分给了num的值5.但是跟下面要说的结合起来的话我有点不理解这个好像不太符合内存对齐原则,所以有疑惑,带找到准备答案来解答,先记录。
 

@implementation LJLPropertyViewController

struct StructTwo {
    double b;       //8字节  0-7
    int c;          //4字节  8-12  后面补齐 13-15  总大小为16
} MyStruct3;

struct StructTwo1 {
    double b;       //8字节  0-7
    int c;          //4字节  8-12  后面补齐 13-15
    double f;       //8字节  16-23
    int e;          //4字节  24-28  后面补齐 29-31  总大小为32
} MyStruct2;

NSLog(@"%lu---%lu", sizeof(MyStruct3), sizeof(MyStruct2));
16  32

但是经上面的结构体 MyStruct3(16)  MyStruct2(32)验证可知,结构体的内存占用是按属性顺序排的。根据属性所占的内存大小确定其其实位置(例如MyStruct2中,bc从0开始0-7,c从8开始8-11,f从16开始16-23,11之后和16之前的位置放不下f所以补齐。)

 MRC 手动内存管理
    delegate 使用assign
 ARC 自动内存管理
    delegate 使用weak
 

修饰符

1、strong(与retain的作用基本相同)
    强引用
    是修饰符的默认修饰符
    持有对象,引用计数+1 。修饰的对象在引用计数为0时被销毁。
 2、weak
    弱引用
    引用计数不增加(相当于一个新的指针指向了原来的对象,当原来的对象释放的时候也会释放),对象的引用计数为0时,对象释放的同时指针地址置位nil。一般用来打破循环引用和修饰delegate。
 3、assign
    弱引用
    一般用来修饰 基本数据类型 和 id类型
    修饰对象时。对象的引用计数为0时,对象释放但是指针不会置位nil,就会出现野指针。所以一般不能y使用assign修饰对象。
    因为基本数据类型一般分配在栈上,栈的内存由系统自动处理,不会造成野指针,所以可以用assign修饰。
 4、copy
    强引用
    copy修饰的对象是不可变的。
    相当于在调用对象的set方法的时候拷贝了一份。

@property (nonatomic, copy) NSArray *datas;
NSMutableArray *datas = [NSMutableArray arrayWithObject:@"data"];
self.datas = datas
//相当于 self.datas = [datas copy];

所以说就算你在后面修改datas里面的对象个数,也不会影响self.datas里面的对象个数。
 
 5、atomic
    原子性
    默认属性
    保证set/get的安全,但是不能保证线程安全。而且性能要比 nonatomic 慢了将近20倍。
    在底层执行set/get方法的时候系统会加锁,这样保证在set/get的时候是安全的,但是不能保证整个对象是线程安全的。
    举例说明:线程A 的get方法运行了一半,线程B 调用set这个时候因为有锁所以需要等线程A 执行完,那么线程A 的get拿到的就是一个完好无损的对象。
    但是如果线程A 调用get,同时线程B 、线程C 都去调用set。那么A 拿到的值就有可能是B、C set之前的,也可能是一个或多个set之后的。
 6、nonatomic
    允许多个线程同时访问,不能保证线程安全。性能较高,一般使用nonatomic。
 
 7、readwrite
    允许读写操作
    默认修饰符,系统会自动生成set/get方法。
 8、readonly
    只读
    系统只会生成get方法,不会生成set方法。

ACLStudent.h

@interface ACLStudent : NSObject

@property (nonatomic, assign, readonly) NSInteger studentId;
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, copy, readonly) NSString *lastName;
- (instancetype)initWithStudentId:(NSInteger)studentId firstName:(NSString *)firstName lastName:(NSString *)lastName;
@end

--------------------------
ACLStudent.m
@implementation ACLStudent
- (instancetype)initWithStudentId:(NSInteger)studentId firstName:(NSString *)firstName lastName:(NSString *)lastName {
    self = [super init];
    if (self) {
        _studentId = studentId;
        _firstName = [firstName copy];
        _lastName = [lastName copy];
    }
    return self;
}

如果直接调用 firstName 的 setter 方法,student.firstName = @"Qiu", 那么就直接报错,提示不能够给声明为 readonly 的属性赋值。那么使用 KVC 呢?

[student setValue:@"Qiu" forKey:NSStringFromSelector(@selector(firstName))];

发现通过KVC可以修改成功,具体为什么请看       或者官方文档 Accessor Search Implementation Details

 9、retain
    对其他NSObject和其子类对参数进行release旧值,再retain新值。
    指定retain会在赋值是唤醒传入值的retain消息。只能用于Objective-C对象(因为retain会增加对对象的i引用计数,而基本类型和Core Foundation对象都没有引用计数)
retain 和 copy 的区别
    例如两个 NSString * a 和 NSString * b
    copy是拷贝了一份新的数据到新的内存地址上,a和b指向的地址不同,但是内容是一样的。
    retain 新的对象 retain 为1,旧的对象retain没有变化。到另外一个NSString之后,地址相同(建立一个指针,指针拷贝)这个对象的retain+1.
    retain 是指针拷贝,copy 是内容拷贝
引用计数
    例如a、b都指向一个同一个内存,给这个内存设一个引用计数,当内存被分配并且赋值给 a 时,引用计数是1,当a 赋值给b 时引用计数+1。当其中一个释放时,引用计数-1.当引用计数为0 时,x代表该内存不被任何指针所引用,系统可以把它直接释放掉。
10、const
    变量修饰符,只读。
    意思是这个参数只能读,不能修改内容。谁近修饰谁。
    用途:
    便于测试快速找到问题
    大型算法可以快速找到哪个模块出错误

    static NSString * const MB_NET_URL_Dev = @"http://";//开发环境地址
    对于不能修改的数据进行修饰

    NSString * a = @"1234";
    NSString * b = @"5678";
    NSString * c = @"90";

   const NSString * p1 = a;
    p1 = b;
    *p1 = c;//错误  指针指向的地方不能修改

    NSString const *  p2 = a;
    p2 = b;
    *p2 = c;//错误  指针指向的地方不能修改

    NSString * const p3 = a;
    p3 = b;//错误 指针本身不能修改
    *p3 = c;

    const NSString * const p4 = a;
    p4 = b;//错误 指针本身不能修改
    *p4 = c;//错误 指针指向的地方不能修改

11、register
    注册。
    系统会优先将其修饰的变量放入寄存器,这样这个变量进行的各种操作及运算,会很快。
    但是CPU走遍的用户可用空闲寄存器有限,如果放满之后还是需要在普通内存中开辟空间。
 12、static
    静态的 s da tik
    static变量,不在栈区,在数据区。
    static作用在局部变量前,函数结束此变量的值不清空,即改变了此变量的生命期,而且知道整个程序结束,并且此变量的值只有在定义它的函数中才可以被使用和重新赋值。
 13、extern
    外面的 x de ai
    extern可以用来扩展函数的作用范围,可以跨文件扩展,前提是被扩展全局遍历或者函数在定义时没有被 static 修饰。
 

强引用弱引用

    strong 等强引用持有对象
    weak、assign 等弱引用不持有对象

    __strong NSObject *obj1=[[NSObject alloc] init];
    __strong NSObject *obj2=obj1;
    __weak NSObject *obj3=obj1;
    NSLog(@"%@,%@",obj1,obj2,obj3);
    obj1=nil;
    NSLog(@"%@,%@",obj1,obj2,obj3);
    //输出 :
    //<NSObject: 0x7fef53708b80>,<NSObject: 0x7fef53708b80><NSObject: 0x7fef53708b80
    //(null),<NSObject: 0x7fef53708b80>,(null)

strong 的对象retainCount+1,weak 的不会。所以当释放objc1 的时候 objc2的对象retainCount-1  =1。而objc3 在objc1 释放之后retainCount 就成0了,内存也跟着释放了,所以objc3 也是nil。
 
 应用
    当两个不同的对象各有一个强引用指向对方时就会造成循环引用,会导致对象无法释放。--循环引用
    所以就需要破除循环引用,例如申明代理的时候:@property (nonatomic, weak) id<Delegate>delegate(MRC下使用assign)
    block 的使用就会造成循环引用。必须self 持有 block,block又持有 self

    [self block:^{
        self.value=1;
    }];

    这个时候就需要一个弱引用来持有self ,然后self 持有block,block 持有的是这个弱引用,在使用后释放并不会造成循环引用。block其他解除循环引用的方法后面分析block的时候再详细说。

    __weak typeof(self) weakself=self;
     [self block:^{
         weakself.value=1;
     }];

但是如下代码就不会造成循环引用。但是如果嵌套的比较深的话也会出现循环引用。

    [UIView animateWithDuration:0.2 animations:^{
         self.value=1;
     }];

是否造成循环引用主要是看是否互相持有。
    NSTimer 会强引用当前的对象,例如在当前 viewController 中启用了一个NSTimer,如果直接退出页面这个时候因为NSTimer 强引用当前VC,当前VC不能释放,所以就会造成内存泄露。需要再退出页面之前先销毁NSTimer。这个相近解析在另一篇文章 NSTimer浅析  中。
    [Timer invalidate];
    Timer = nil;
 

属性的内容还有一些没有分析到的,还有上面提到的KVC修改具体的后面分析分析了在更新。以上为个人的初略简介和产考学习来的内容,多多交流,有问题还望各位大神给与纠正。

发布了83 篇原创文章 · 获赞 12 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/shengdaVolleyball/article/details/104698154
002