一道网易面试题

news/2024/7/20 13:17:22 标签: 内存管理, runtime

一、题目描述

  题目来自网上一个博客,具体类似如下

  

@interface ViewController ()

@property (nonatomic, strong) NSString *target;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 1000000000 ; i++) {
        dispatch_async(queue, ^{
            self.target = [NSString stringWithFormat:@"ksddkjalkd2018-11-09 12:04:09.750846+0800 ARCTest2[525:168910] 1111sdsdsjd%d",i];
            NSLog(@"%@", self.target);
        });
    }
}

  问代码执行之后会发生什么?

二、解析

  在设置target的setter中,是非线程安全的,未加锁;因此多线程访问这个属性setter方法的时候潜在crash的情况

  因为setter大概如下

- (void)setTarget:(NSString *)target
{
    if(_target != target)
    {
        [_target release];
        _target = [target retain];
    }
}

  对应runtime代码

//objc_class.mm
void object_setIvar(id obj, Ivar ivar, id value)
{
    return _object_setIvar(obj, ivar, value, false /*not strong default*/);
}


static ALWAYS_INLINE 
void _object_setIvar(id obj, Ivar ivar, id value, bool assumeStrong)
{
    //判断是否是TaggedPointer
    if (!obj  ||  !ivar  ||  obj->isTaggedPointer()) return;

    ptrdiff_t offset;
    objc_ivar_memory_management_t memoryManagement;
    //找对应的内存管理语义和属性偏移值
    _class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement);

    //如果找不到默认是否为Strong,不然为unsafe_unretained
    if (memoryManagement == objc_ivar_memoryUnknown) {
        if (assumeStrong) memoryManagement = objc_ivar_memoryStrong;
        else memoryManagement = objc_ivar_memoryUnretained;
    }

    //根据偏移值找到属性对应位置
    id *location = (id *)((char *)obj + offset);
    
    //判断不同的内存管理语义,调用方法
    switch (memoryManagement) {
    case objc_ivar_memoryWeak:       objc_storeWeak(location, value); break;
    case objc_ivar_memoryStrong:     objc_storeStrong(location, value); break;
    case objc_ivar_memoryUnretained: *location = value; break;
    case objc_ivar_memoryUnknown:    _objc_fatal("impossible");
    }
}

  在release的方法最后会调用obj_release

//NSObject.mm
void
objc_storeStrong(id *location, id obj)
{   
    //如果新值指针和旧值一样,则不更新,直接return
    id prev = *location;
    if (obj == prev) {
        return;
    }
    //先对新值retain
    objc_retain(obj);
    //再赋值
    *location = obj;
    //最后对旧值release
    objc_release(prev);
}

  因为一个对象已经release了,但是这个指针指向的内存已经被回收,所以访问这个指针的内存会产生一个内存访问的错误

2018-11-09 15:22:04.860819+0800 ARCTest2[93017:2107037] *** -[CFString release]: message sent to deallocated instance 0x600000e70240

以上的代码用模拟器是比较容易出现的,因为GCD创建了64个线程,线程并发次数很多

如果使用iPhoneX的话,没有出现,(应该是比较难重现),但是存在crash的可能

可以看到GCD创建了6个线程,是6核的1倍

  

如果将target的修饰改为atomic,将不会crash,但是直接访问实例变量依旧会产生crash。

@interface ViewController ()

@property (atomic, strong) NSString *target;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    dispatch_queue_t queue = dispatch_queue_create("parallel", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 1000000000 ; i++) {
        dispatch_async(queue, ^{
            _target = [NSString stringWithFormat:@"ksddkjalkd2018-11-09 12:04:09.750846+0800 ARCTest2[525:168910] 1111sdsdsjd%d",i];
            NSLog(@"%@", self.target);
        });
    }
}

 

三、总结

  通过上面的例子,我们可以总结出来,对于一个变量,如果多线程访问之下,retain、release的顺序得不到保证的话,就会带来野指针的问题

  ARC只能保证在合适的地方插入retain、release;但是retain、release的顺序还需要业务来进行保证。

转载于:https://www.cnblogs.com/doudouyoutang/p/9935420.html


http://www.niftyadmin.cn/n/1804355.html

相关文章

CVE-2020-14882 WebLogic远程代码执行漏洞复现

Weblogic简介 WebLogic Server是美国Oracle公司的主要产品之一,其主要用于开发、集成、部署和管理大型分布式Web应用、网络应用和数据库应用,是商业市场上主要的Java(J2EE)应用服务器软件之一。漏洞描述 2020年10月28日,Oracle发布的10月安全更新中的Oracle WebLogic Server…

【NOIP2004】合唱队形

本题在洛谷上的链接&#xff1a;https://www.luogu.org/problemnew/show/P1091 水题。。。没啥好说的&#xff0c;求一遍正向的最长上升子序列和一遍逆向的最长上升子序列就可以了。 唯一需要注意的是&#xff0c;求最长上升子序列之类的那种O(n^2)算法中&#xff0c;定义dp[i]…

go学习笔记-错误处理

错误处理 通过内置的错误接口提供了非常简单的错误处理机制。 error类型是一个接口类型 type error interface {Error() string } 可以在编码中通过实现 error 接口类型来生成错误信息。 函数通常在最后的返回值中返回错误信息。使用errors.New 可返回一个错误信息 func testEr…

redis三(3-3)

一、Redis键值设计 1.1优雅的key结构 Redis的Key虽然可以自定义&#xff0c;但最好遵循下面的几个最佳实践约定&#xff1a; 遵循基本格式&#xff1a;[业务名称]:[数据名]:[id]长度不超过44字节不包含特殊字符 例如&#xff1a;我们的登录业务&#xff0c;保存用户信息&…

web漏洞之越权漏洞

越权介绍 越权漏洞十分常见,属于 OWASP TOP10的漏洞类型之一作为一个常见的逻辑漏洞,越权漏洞的危害和影响与对应业务的重要性成正相关越权漏洞的挖掘常常要求白帽子足够的细心&#xff0c;每一个可能产生问题的业务点都考虑到了权限问题 漏洞原理 逻辑越权本质上来说是设计…

文字隐藏,点击的时候显示

html代码&#xff1a; <div class"xq_text"><div class"yw" v-for" (item, i ) in wtjd" :key"i"><h4 class"ytext" click"active2 i">{{item.text}}</h4><p :class"{ click: a…

python对列表中的字典进行排序

数据显示为&#xff1a; rows[{日期: 2018-09-04, 测试1: 50.00 %, 测试2: 100.00%}, {日期: 2018-09-05, 测试1: 100.00%, 测试2: 无执行}, {日期: 2018-09-06, 测试1: 100.00%, 测试2: 100.00%}, {日期: 2018-08-31, 测试1: 无执行, 测试2: 无执行}, {日期: 2018-09-01, 测试…

redis(四)

一、数据结构 1.1动态字符串SDS 我们都知道Redis中保存的Key是字符串&#xff0c;value往往是字符串或者字符串的集合。可见字符串是Redis中最常用的一种数据结构。 不过Redis没有直接使用C语言中的字符串&#xff0c;因为C语言字符串存在很多问题&#xff1a; 1.获取字符串…