写在前面

这篇文章包含了两方面的内容:

注:如果你刚刚入门 iOS 开发,笔者相信了解第一部分的内容会对你的日常开发中有所帮助,不过第二部分的内容可能有些难以理解。

如果你对关联对象的使用非常熟悉,可以直接跳过第一部分的内容,从这里开始深入了解其底层实现。

关联对象的应用

关于关联对象的使用相信已经成为了一个老生常谈的问题了,不过为了保证这篇文章的完整性,笔者还是会在这里为各位介绍这部分的内容的。

分类中的 @property

@property 可以说是一个 Objective-C 编程中的“宏”,它有元编程的思想。

@interface DKObject : NSObject@property (nonatomic, strong) NSString *property;@end

在使用上述代码时会做三件事:

  • 生成实例变量 _property

  • 生成 getter 方法 - property

  • 生成 setter 方法 - setProperty:

@implementation DKObject {NSString *_property;
}- (NSString *)property {return _property;
}- (void)setProperty:(NSString *)property {_property = property;
}@end

这些代码都是编译器为我们生成的,虽然你看不到它,但是它确实在这里,我们既然可以在类中使用 @property 生成一个属性,那么为什么在分类中不可以呢?

我们来做一个小实验:创建一个 DKObject 的分类 Category,并添加一个属性 categoryProperty

@interface DKObject (Category)@property (nonatomic, strong) NSString *categoryProperty;@end

看起来还是很不错的,不过 Build 一下这个 Demo,会发现有这么一个警告:

objc-ao-warning-category-property

在这里的警告告诉我们 categoryProperty 属性的存取方法需要自己手动去实现,或者使用 @dynamic在运行时实现这些方法。

换句话说,分类中的 @property 并没有为我们生成实例变量以及存取方法,而需要我们手动实现。

使用关联对象

Q:我们为什么要使用关联对象?

A:因为在分类中 @property 并不会自动生成实例变量以及存取方法,所以一般使用关联对象为已经存在的类添加『属性』

上一小节的内容已经给了我们需要使用关联对象的理由。在这里,我们会介绍 ObjC 运行时为我们提供的与关联对象有关的 API,并在分类中实现一个伪属性

#import "DKObject+Category.h"
#import <objc/runtime.h>@implementation DKObject (Category)- (NSString *)categoryProperty {return objc_getAssociatedObject(self, _cmd);
}- (void)setCategoryProperty:(NSString *)categoryProperty {objc_setAssociatedObject(self, @selector(categoryProperty), categoryProperty, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}@end

这里的 _cmd 代指当前方法的选择子,也就是 @selector(categoryProperty)

我们使用了两个方法 objc_getAssociatedObject 以及 objc_setAssociatedObject 来模拟『属性』的存取方法,而使用关联对象模拟实例变量。

在这里有必要解释两个问题:

  1. 为什么向方法中传入 @selector(categoryProperty)

  2. OBJC_ASSOCIATION_RETAIN_NONATOMIC 是干什么的?

关于第一个问题,我们需要看一下这两个方法的原型:

id objc_getAssociatedObject(id object, const void *key);
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);

@selector(categoryProperty) 也就是参数中的 key,其实可以使用静态指针 static void * 类型的参数来代替,不过在这里,笔者强烈推荐使用 @selector(categoryProperty) 作为 key 传入。因为这种方法省略了声明参数的代码,并且能很好地保证 key 的唯一性。

OBJC_ASSOCIATION_RETAIN_NONATOMIC 又是什么呢?如果我们使用 Command 加左键查看它的定义:

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. *   The association is not made atomically. */OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. *   The association is not made atomically. */OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.*   The association is made atomically. */OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.*   The association is made atomically. */
};

从这里的注释我们能看到很多东西,也就是说不同的 objc_AssociationPolicy 对应了不通的属性修饰符:

objc_AssociationPolicymodifier
OBJC_ASSOCIATION_ASSIGNassign
OBJC_ASSOCIATION_RETAIN_NONATOMICnonatomic, strong
OBJC_ASSOCIATION_COPY_NONATOMICnonatomic, copy
OBJC_ASSOCIATION_RETAINatomic, strong
OBJC_ASSOCIATION_COPYatomic, copy

而我们在代码中实现的属性 categoryProperty 就相当于使用了 nonatomic 和 strong 修饰符。

关于属性修饰符的区别,并不是这篇文章的主要内容,如果你需要了解它们的区别,Google 是一个很好的选择。

到这里,我们已经完成了对关联对象应用的介绍,再来回顾一下小节的内容。

@property` 其实有元编程的思想,它能够为我们自动生成实例变量以及存取方法,而这三者构成了属性这个类似于语法糖的概念,为我们提供了更便利的点语法来访问属性:

self.property <=> [self property]
self.property = value <=> [self setProperty:value]

在分类中,因为类的实例变量的布局已经固定,使用 @property 已经无法向固定的布局中添加新的实例变量(这样做可能会覆盖子类的实例变量),所以我们需要使用关联对象以及两个方法来模拟构成属性的三个要素

如果你是一个 iOS 开发方面的新手,我相信这篇文章的前半部分对已经足够使用了,不过,如果你还对关联对象的实现非常感兴趣,也可以尝试阅读下面的内容。

关联对象的实现

探索关联对象的实现一直是我想要做的一件事情,直到最近,我才有足够的时间来完成这篇文章,希望能够对各位读者有所帮助。

这一部分会从三个 objc 运行时的方法为入口来对关联对象的实现一探究竟,其中两个方法是上一部分使用到的方法:

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
id objc_getAssociatedObject(id object, const void *key);
void objc_removeAssociatedObjects(id object);

三个方法的作用分别是:

  • 以键值对形式添加关联对象

  • 根据 key 获取关联对象

  • 移除所有关联对象

而接下来的内容自然就是围绕这三个方法进行的,我们会对它们的实现进行分析。

objc_setAssociatedObject

首先是 objc_setAssociatedObject 方法,这个方法的调用栈并不复杂:

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) 
└── void objc_setAssociatedObject_non_gc(id object, const void *key, id value, objc_AssociationPolicy policy)└── void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy)

调用栈中的 _object_set_associative_reference 方法实际完成了设置关联对象的任务:

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {ObjcAssociation old_association(0, nil);id new_value = value ? acquireValue(value, policy) : nil;{AssociationsManager manager;AssociationsHashMap &associations(manager.associations());ObjectAssociationMap *refs = i->second;...}if (old_association.hasValue()) ReleaseValue()(old_association);
}

在这里的实现省略了大多的实现代码,而且忽略了很多逻辑上的顺序,不过不要在意这里的代码能否执行。

我们需要注意其中的几个类和数据结构,因为在具体分析这个方法的实现之前,我们需要了解其中它们的作用:

  • AssociationsManager

  • AssociationsHashMap

  • ObjcAssociationMap

  • ObjcAssociation

AssociationsManager

AssociationsManager 在源代码中的定义是这样的:

class AssociationsManager {static spinlock_t _lock;static AssociationsHashMap *_map;
public:AssociationsManager()   { _lock.lock(); }~AssociationsManager()  { _lock.unlock(); }AssociationsHashMap &associations() {if (_map == NULL)_map = new AssociationsHashMap();return *_map;}
};spinlock_t AssociationsManager::_lock;
AssociationsHashMap *AssociationsManager::_map = NULL;

它维护了 spinlock_t 和 AssociationsHashMap 的单例,初始化它的时候会调用 lock.lock() 方法,在析构时会调用 lock.unlock(),而 associations 方法用于取得一个全局的 AssociationsHashMap单例。

也就是说 AssociationsManager 通过持有一个自旋锁 spinlock_t 保证对 AssociationsHashMap 的操作是线程安全的,即每次只会有一个线程对 AssociationsHashMap 进行操作

如何存储 ObjcAssociation

ObjcAssociation 就是真正的关联对象的类,上面的所有数据结构只是为了更好的存储它。

首先,AssociationsHashMap 用与保存从对象的 disguised_ptr_t 到 ObjectAssociationMap 的映射:

class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
public:void *operator new(size_t n) { return ::malloc(n); }void operator delete(void *ptr) { ::free(ptr); }
};

而 ObjectAssociationMap 则保存了从 key 到关联对象 ObjcAssociation 的映射,这个数据结构保存了当前对象对应的所有关联对象

class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
public:void *operator new(size_t n) { return ::malloc(n); }void operator delete(void *ptr) { ::free(ptr); }
};

最关键的 ObjcAssociation 包含了 policy 以及 value

class ObjcAssociation {uintptr_t _policy;id _value;
public:ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}ObjcAssociation() : _policy(0), _value(nil) {}uintptr_t policy() const { return _policy; }id value() const { return _value; }bool hasValue() { return _value != nil; }
};

举一个简单的例子来说明关联对象在内存中以什么形式存储的,以下面的代码为例:

int main(int argc, const char * argv[]) {@autoreleasepool {NSObject *obj = [NSObject new];objc_setAssociatedObject(obj, @selector(hello), @"Hello", OBJC_ASSOCIATION_RETAIN_NONATOMIC);}return 0;
}

这里的关联对象 ObjcAssociation(OBJC_ASSOCIATION_RETAIN_NONATOMIC, @"Hello") 在内存中是这么存储的:

objc-ao-associateobjcect


接下来我们可以重新回到对 objc_setAssociatedObject 方法的分析了。

在这里会将方法的执行分为两种情况:

  • new_value != nil 设置/更新关联对象的值

  • new_value == nil 删除一个关联对象

new_value != nil

先来分析在 new_value != nil 的情况下,该方法的执行是什么样的:

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {ObjcAssociation old_association(0, nil);id new_value = value ? acquireValue(value, policy) : nil;{AssociationsManager manager;AssociationsHashMap &associations(manager.associations());disguised_ptr_t disguised_object = DISGUISE(object);AssociationsHashMap::iterator i = associations.find(disguised_object);if (i != associations.end()) {ObjectAssociationMap *refs = i->second;ObjectAssociationMap::iterator j = refs->find(key);if (j != refs->end()) {old_association = j->second;j->second = ObjcAssociation(policy, new_value);} else {(*refs)[key] = ObjcAssociation(policy, new_value);}} else {ObjectAssociationMap *refs = new ObjectAssociationMap;associations[disguised_object] = refs;(*refs)[key] = ObjcAssociation(policy, new_value);object->setHasAssociatedObjects();}}if (old_association.hasValue()) ReleaseValue()(old_association);
}
  1. 使用 old_association(0, nil) 创建一个临时的 ObjcAssociation 对象(用于持有原有的关联对象,方便在方法调用的最后释放值)

  2. 调用 acquireValue 对 new_value 进行 retain 或者 copy

    static id acquireValue(id value, uintptr_t policy) {switch (policy & 0xFF) {case OBJC_ASSOCIATION_SETTER_RETAIN:return ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);case OBJC_ASSOCIATION_SETTER_COPY:return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);}return value;
    }
  3. 初始化一个 AssociationsManager,并获取唯一的保存关联对象的哈希表 AssociationsHashMap

    AssociationsManager manager;
    AssociationsHashMap &associations(manager.associations());
  4. 先使用 DISGUISE(object) 作为 key 寻找对应的 ObjectAssociationMap

  5. 如果没有找到,初始化一个 ObjectAssociationMap,再实例化 ObjcAssociation 对象添加到 Map 中,并调用 setHasAssociatedObjects 方法,表明当前对象含有关联对象

    ObjectAssociationMap *refs = new ObjectAssociationMap;
    associations[disguised_object] = refs;
    (*refs)[key] = ObjcAssociation(policy, new_value);
    object->setHasAssociatedObjects();
  1. 如果找到了对应的 ObjectAssociationMap,就要看 key 是否存在了,由此来决定是更新原有的关联对象,还是增加一个

    ObjectAssociationMap *refs = i->second;
    ObjectAssociationMap::iterator j = refs->find(key);
    if (j != refs->end()) {old_association = j->second;j->second = ObjcAssociation(policy, new_value);
    } else {(*refs)[key] = ObjcAssociation(policy, new_value);
    }
  2. 最后的最后,如果原来的关联对象有值的话,会调用 ReleaseValue() 释放关联对象的值

    struct ReleaseValue {void operator() (ObjcAssociation &association) {releaseValue(association.value(), association.policy());}
    };static void releaseValue(id value, uintptr_t policy) {if (policy & OBJC_ASSOCIATION_SETTER_RETAIN) {((id(*)(id, SEL))objc_msgSend)(value, SEL_release);}
    }

到这里,该条件下的方法实现就结束了。

new_value == nil

如果 new_value == nil,就说明我们要删除对应 key 的关联对象,实现如下:

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {ObjcAssociation old_association(0, nil);id new_value = value ? acquireValue(value, policy) : nil;{AssociationsManager manager;AssociationsHashMap &associations(manager.associations());disguised_ptr_t disguised_object = DISGUISE(object);AssociationsHashMap::iterator i = associations.find(disguised_object);if (i !=  associations.end()) {ObjectAssociationMap *refs = i->second;ObjectAssociationMap::iterator j = refs->find(key);if (j != refs->end()) {old_association = j->second;refs->erase(j);}}}if (old_association.hasValue()) ReleaseValue()(old_association);
}

这种情况下方法的实现与前面的唯一区别就是,我们会调用 erase 方法,擦除 ObjectAssociationMap中 key 对应的节点。

setHasAssociatedObjects()

其实上面的两种情况已经将 objc_setAssociatedObject 方法的实现分析得很透彻了,不过,这里还有一个小问题来等待我们解决,setHasAssociatedObjects() 方法的作用是什么?

inline void objc_object::setHasAssociatedObjects() {if (isTaggedPointer()) return;retry:isa_t oldisa = LoadExclusive(&isa.bits);isa_t newisa = oldisa;if (!newisa.indexed) return;if (newisa.has_assoc) return;newisa.has_assoc = true;if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
}

它会将 isa 结构体中的标记位 has_assoc 标记为 true,也就是表示当前对象有关联对象,在这里我还想祭出这张图来介绍 isa 中的各个标记位都是干什么的。

objc-ao-isa-struct

如果想要了解关于 isa 的知识,可以阅读从 NSObject 的初始化了解 isa

objc_getAssociatedObject

我们既然已经对 objc_setAssociatedObject 的实现已经比较熟悉了,相信对于 objc_getAssociatedObject 的理解也会更加容易。

方法的调用栈和 objc_setAssociatedObject 非常相似:

id objc_getAssociatedObject(id object, const void *key)
└── id objc_getAssociatedObject_non_gc(id object, const void *key);└── id _object_get_associative_reference(id object, void *key) 

而 _object_get_associative_reference 相比于前面方法的实现更加简单。

id _object_get_associative_reference(id object, void *key) {id value = nil;uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;{AssociationsManager manager;AssociationsHashMap &associations(manager.associations());disguised_ptr_t disguised_object = DISGUISE(object);AssociationsHashMap::iterator i = associations.find(disguised_object);if (i != associations.end()) {ObjectAssociationMap *refs = i->second;ObjectAssociationMap::iterator j = refs->find(key);if (j != refs->end()) {ObjcAssociation &entry = j->second;value = entry.value();policy = entry.policy();if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);}}}if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);}return value;
}

代码中寻找关联对象的逻辑和 objc_setAssociatedObject 差不多:

  1. 获取静态变量 AssociationsHashMap

  2. 以 DISGUISE(object) 为 key 查找 AssociationsHashMap

  3. 以 void *key 为 key 查找 ObjcAssociation

  4. 根据 policy 调用相应的方法

    if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);
    }
  5. 返回关联对象 ObjcAssociation 的值

objc_removeAssociatedObjects

关于最后的 objc_removeAssociatedObjects 方法,其实现也相对简单,这是方法的调用栈:

void objc_removeAssociatedObjects(id object)
└── void _object_remove_assocations(id object)

这是简化版本的 objc_removeAssociatedObjects 方法实现:

void objc_removeAssociatedObjects(id object) {if (object && object->hasAssociatedObjects()) {_object_remove_assocations(object);}
}

为了加速移除对象的关联对象的速度,我们会通过标记位 has_assoc 来避免不必要的方法调用,在确认了对象和关联对象的存在之后,才会调用 _object_remove_assocations 方法移除对象上所有的关联对象:

void _object_remove_assocations(id object) {vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;{AssociationsManager manager;AssociationsHashMap &associations(manager.associations());if (associations.size() == 0) return;disguised_ptr_t disguised_object = DISGUISE(object);AssociationsHashMap::iterator i = associations.find(disguised_object);if (i != associations.end()) {ObjectAssociationMap *refs = i->second;for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {elements.push_back(j->second);}delete refs;associations.erase(i);}}for_each(elements.begin(), elements.end(), ReleaseValue());
}

方法会将对象包含的所有关联对象加入到一个 vector 中,然后对所有的 ObjcAssociation 对象调用 ReleaseValue() 方法,释放不再被需要的值。

小结

关于应用

本来在这个系列的文章中并不会涉及关联对象这个话题,不过,有人问过我这么一个问题:在分类中到底能否实现属性?其实在回答这个问题之前,首先要知道到底属性是什么?而属性的概念决定了这个问题的答案。

  • 如果你把属性理解为通过方法访问的实例变量,我相信这个问题的答案是不能,因为分类不能为类增加额外的实例变量

  • 不过如果属性只是一个存取方法以及存储值的容器的集合,那么分类是可以实现属性的。

分类中对属性的实现其实只是实现了一个看起来像属性的接口而已

关于实现

关联对象又是如何实现并且管理的呢:

  • 关联对象其实就是 ObjcAssociation 对象

  • 关联对象由 AssociationsManager 管理并在  AssociationsHashMap 存储

  • 对象的指针以及其对应 ObjectAssociationMap 以键值对的形式存储在 AssociationsHashMap 中

  • ObjectAssociationMap 则是用于存储关联对象的数据结构

  • 每一个对象都有一个标记位 has_assoc 指示对象是否含有关联对象

关注仓库,及时获得更新:iOS-Source-Code-Analyze

Follow: Draveness · Github

原文链接:http://draveness.me/ao

一人前行
发布了163 篇原创文章 · 获赞 2 · 访问量 1万+
私信关注
查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 越权漏洞简单分析

    一、原理越权漏洞是Web应用程序中一种常见的安全漏洞。它的威胁在于一个账户即可控制全站用户数据。当然这些数据仅限于存在漏洞功能对应的数据。越权漏洞的成因主要是因为开发人员在对数据进行增、删、改、查询时对客户端请求的数据过分相信而遗漏了权限的判定。二、分类主要分…...

    2024/3/29 10:11:26
  2. Eigen(6)-Linear algebra and decompositions(线性代数和分解)

    线性代数、分解 介绍如何求解线性系统,计算几种分解,比如LU,QR,SVD等。 基本线性求解 问题:假设有一个系统方程写成如下矩阵的形式Ax=b Ax=bAx=b 其中A,b是矩阵,b也可以是向量,当想要求解x时,可以选择多种分解方式,取决于矩阵A的形式以及考虑的速度和精度,下面是一个…...

    2024/4/18 1:19:03
  3. ionic安装环境时,执行ionic cordova build android报错解决方法

    点赞收藏分享文章举报qq_33543227发布了25 篇原创文章 获赞 6 访问量 4万+私信关注...

    2024/4/11 3:37:05
  4. RPA手把手——Python 节省内存的循环写法 (一)

    艺赛旗 RPA10.0全新首发免费下载 点击下载 www.i-search.com.cn/index.html?from=line1 0 前言 说到处理循环,我们习惯使用 for, while 等,比如依次打印每个列表中的字符: lis = [‘I’, ‘love’, ‘python’] for i in lis: print(i) 输出: I love python 在打印内容字节…...

    2024/4/18 3:15:38
  5. 040.linux环境变量

    点赞收藏分享文章举报weixin_43435675发布了43 篇原创文章 获赞 100 访问量 1万+私信关注...

    2024/4/13 20:52:36
  6. 利用Navicat实现mysql数据库导出与读入

    1 Navicate 导出SQL数据步骤 1 :选中所需导出数据步骤2 :选中 Dump SQL File -> Structure And Data步骤3 :输出.sql文件Navicate 读取SQL数据 步骤1 :选中数据库,tables右键 -> Execute SQL File步骤 2 :根据.sql文件地址导入数据,注意编码格式,start开启读取…...

    2024/3/29 5:35:52
  7. 解决Apache命令systemctl status httpd.service报错Unit httpd.service could not be found

    报错原因,Apache安装是编译安装,安装路径不是默认路径,Apache服务没有添加到Linux服务中解决办法,将Apache服务添加到Linux系统服务中1、找到Apache安装路径 find / -name httpd,这是我已经将Apache服务添加到Linux系统服务中,所以出现第一个路径/etc/rc.d/init.d/httpd2…...

    2024/4/18 20:24:58
  8. Python基于面向对象思想完成如下需求(彩票只需要实现最简单的彩票玩法就行) (购买1-10的之内的3个数,可以重复,没有顺序要求,只要3个号码与开奖号码一致,就算中奖,中奖金额200)

    import random class Lottery:def __init__(self):# 用来存储彩票的号码self.number_list = []# 彩票价格self.price = 2def show(self):self.number_list.sort()print(f{self.number_list}) class System:def __init__(self):pass# 得到一组包含随机号码的彩票对象def creat_l…...

    2024/4/17 20:27:33
  9. list集合排序大体格式

    Collections.sort(list, new Comparator<BaseConStaticListBO>() {@Overridepublic int compare(BaseConStaticListBO o1, BaseConStaticListBO o2) {return Integer.parseInt(o1.getName().substring(0,1))-Integer.parseInt(o2.getName().substring(0,1));//从小到大} …...

    2024/3/29 1:33:43
  10. 同步串口与异步串口

    串口:是与并口相对应的一种接口,两者都是设备与设备之间通信的物理接口。 同步串口与异步串口:这里我们指的是串口的通信,通信双方是否同步。同步通信即收发双方交换数据是同步的,异步通信表示收发双方数据交换是异步的。 UART:通用异步收发器,异步通信的协议。规定好通…...

    2024/4/6 4:41:41
  11. Ht使用总结

    HtEditor使用总结 最近在公司学习到ht编辑器的使用,关于使用方法上总结了一下,避免入坑。ht是做大屏数据可视化比较好的一款软件,不过多介绍,官网上有具体使用方法和展示样例,这里我整理一下我用的最多的功能。 ##1、如何将HT编辑器生成的图纸json问价嵌入到C#解决方案中?…...

    2024/4/14 17:15:09
  12. Windows10安装ubuntu16.04双系统教程

    这是我看过装双系统教程写的最不错的!!向博主致敬!!!!转发链接https://www.cnblogs.com/masbay/p/10844857.html写在前面:本教程为windows10安装ubuntu16.04(64位)双系统教程,是我多次安装双系统的经验总结,安装方法同样适用于ubuntu18.04(64位)。为了直观和易于理…...

    2024/4/7 22:50:18
  13. bootstrap-table分别实现前端和后端的分页项目

    https://blog.csdn.net/zzq272804553/article/details/89882126//前端分页<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%String path = request.getContextPath();String basePath = request.getScheme() + "://&q…...

    2024/3/29 10:19:35
  14. 使用通配符进行过滤

    %通配符 select * from tb_teacher where tb_teachername like ‘J%’; 查询所有以J开头的名字select * from tb_teacher where tb_teachername like ‘%J%’; 其中j不区分大小写,%J%表示任何位置包含j的文本下划线通配符 _的用途和%的用途一样,但是_只能匹配单个字符而不是多…...

    2024/3/31 1:33:30
  15. 一张图理解接口

    点赞收藏分享文章举报HenrlyLiu发布了51 篇原创文章 获赞 19 访问量 6298私信关注...

    2024/3/29 10:19:33
  16. Redis 面试

    1.应用场景用于面试, 同时也是为了学习查询知识. 深入知识, 快速开发高性能程序.2.学习/操作Redis 面试题1、什么是 Redis?.2、Redis 的数据类型?3、使用 Redis 有哪些好处?4、Redis 相比 Memcached 有哪些优势?5、Memcache 与 Redis 的区别都有哪些?6、Redis 是单进程单线…...

    2024/3/29 10:11:36
  17. C语言 qsort的用法 模拟EXCEL排序

    C语言 qsort的用法 模拟EXCEL排序 题目 Excel可以对一组记录按任意指定列排序。现请编写程序实现类似的功能。 输入 输入的第一行包含两个正整数N(<= 10^5)和C,其中N是记录的条数,C是指定排序的列号。之后有N行,每行包含一条学生记录。每条学生记录由学号(6位数字,保…...

    2024/4/7 22:51:55
  18. NGAPI接口创建会员账户

    官方网址:http://www.ng-api.cn接口地址:域名/v1/user/register请求参数:字段名称 类型 长度 必选 说明 username string 5-11 是 会员名称,不需要前缀 (用户名为5-13的字母加数字) sign_key string 32 是 加密签名 plat_type string 32 是 平台类型(参见附录平台类型)…...

    2024/3/29 10:11:33
  19. STM3产品名称表示意义

    ** STM3产品名称表示意义 **点赞收藏分享文章举报ak.xu_40810378发布了1 篇原创文章 获赞 0 访问量 2私信关注...

    2024/3/29 10:11:32
  20. vivado 多线程设置笔记

    vivado 多线程设置:设置多线程的命令为: set_param general.maxThreads 8vivado2017.4里测试下来最多只能8个,但是到了2018.3 我测试下来可以设置成16,32 貌似没限制。读取当前线程数的命令: 为get_param general.maxThreads点赞收藏分享文章举报huamingshen发布了40 篇…...

    2024/4/13 0:15:18

最新文章

  1. C++修炼之路之反向迭代器和非模板参数,模板特化,分离编译

    目录 前言 一&#xff1a;反向迭代器 二&#xff1a;非类型模板参数 三&#xff1a;模板的特化 四&#xff1a;模板的分离编译 五&#xff1a;模板的优点与缺点 接下来的日子会顺顺利利&#xff0c;万事胜意&#xff0c;生活明朗-----------林辞忧 前言 在vector&am…...

    2024/4/19 3:31:32
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. C# 抽象类、接口

    &#xff08;1&#xff09;、抽象类和抽象方法的定义和实现&#xff1a;abstract override abstract class Vehicle{ public abstract void Run(); } 继承抽象类并且实现抽象方法 class RaceCar : Vehicle{ public override void Run(){ } } &#xff08;2&#xff09;、接口的…...

    2024/4/16 16:41:40
  4. 【C++】C++中的list

    一、介绍 官方给的 list的文档介绍 简单来说就是&#xff1a; list是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代。list的底层是双向链表结构&#xff0c;双向链表中每个元素存储在互不相关的独立节点中&#xff0c;在节点中…...

    2024/4/18 20:00:31
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/4/18 0:33:31
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/4/18 22:36:36
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/18 9:45:31
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/4/17 2:33:17
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/17 7:50:46
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/18 3:56:01
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/18 3:56:04
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/18 3:55:30
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/18 3:55:54
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/4/18 3:55:45
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/17 21:50:30
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/15 13:53:08
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/15 9:16:52
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/18 9:24:29
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/4/18 3:56:18
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/18 3:55:57
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/18 3:55:50
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/15 23:28:22
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/4/18 3:56:20
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/18 3:56:11
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57