假期学习-- iOS 通知详解

news2024/11/25 6:42:20

iOS 通知详解

数据结构

从我们之前使用通知的流程和代码来看,通知其实就是一个单例,方便随时访问。

NSNotificationCenter:消息中心

这个单例类中主要定义了两个表,一个存储所有注册通知信息的表的结构体,一个保存单个注册信息的节点结构体。

typedef struct NCTbl {
  Observation       *wildcard;  // 添加观察者时既没有传入 NotificationName ,又没有传入object,就会加在这个链表上,它里边的观察者可以接收所有的系统通知
  GSIMapTable       nameless;   // 添加观察者时没有传入 NotificationName 的表
  GSIMapTable       named;      // 添加观察者时传入了 NotificationName 的表
} NCTable

观察者信息的结构:

typedef struct  Obs {
  id        observer;   // 观察者对象
  SEL       selector;   // 方法信息
  struct Obs    *next;      // 指向下一个节点
  int       retained;   /* Retain count for structure.  */
  struct NCTbl  *link;      /* Pointer back to chunk table  */
} Observation;
named表

在 named 表中,NotifcationName 作为表的 key,因为我们在注册观察者的时候是可以传入一个参数 object 用于只监听指定该对象发出的通知,并且一个通知可以添加多个观察者,所以还需要一张表来保存 object 和 Observer 的对应关系。这张表的是 key、Value 分别是以 object 为 Key,Observer 为 value。用了链表这种数据结构实现保存多个观察者的情况。

534534534

在实际开发过程中 object 参数我们经常传 nil,这时候系统会根据 nil 自动生成一个 key,相当于这个 key 对应的 value(链表)保存的就是当前通知传入了 NotificationName 没有传入 object 的所有观察者。

同理nameless表和wildcard表如下:

5345345

4234234

添加观察者

使用方法addObserver:selector:name:object添加观察者,根据 GNUStep 的源码分析:

- (void) addObserver: (id)observer
            selector: (SEL)selector
                name: (NSString*)name
              object: (id)object
{
  Observation        *list;
  Observation        *o;
  GSIMapTable        m;
  GSIMapNode        n;
// observer为空时的报错
  if (observer == nil)
    [NSException raise: NSInvalidArgumentException
                format: @"Nil observer passed to addObserver ..."];
// selector为空时的报错
  if (selector == 0)
    [NSException raise: NSInvalidArgumentException
                format: @"Null selector passed to addObserver ..."];
// observer不能响应selector时的报错
  if ([observer respondsToSelector: selector] == NO)
    {
      [NSException raise: NSInvalidArgumentException
        format: @"[%@-%@] Observer '%@' does not respond to selector '%@'",
        NSStringFromClass([self class]), NSStringFromSelector(_cmd),
        observer, NSStringFromSelector(selector)];
    }
// 给表上锁
  lockNCTable(TABLE);
// 建立一个新Observation,存储这次注册的信息
  o = obsNew(TABLE, selector, observer);
  // 如果有name
  if (name) {
      // 在named表中 以name为key寻找value
      n = GSIMapNodeForKey(NAMED, (GSIMapKey)(id)name);
      // named表中没有找到对应的value
      if (n == 0) {
          // 新建一个表
          m = mapNew(TABLE);
          // 由于这是对给定名称的首次观察,因此我们对该名称进行了复制,以便在map中无法对其进行更改(来自GNUStep的注释)
          name = [name copyWithZone: NSDefaultMallocZone()];
          // 新建表作为name的value添加在named表中
          GSIMapAddPair(NAMED, (GSIMapKey)(id)name, (GSIMapVal)(void*)m);
          GS_CONSUMED(name)
      } else { //named表中有对应的value
      	  // 取出对应的value
          m = (GSIMapTable)n->value.ptr;
      }
      // 将observation添加到正确object的列表中
      // 获取添加完后name对应的value的object对应的链表
      n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
      // n是object的value
      if (n == 0) { // 如果object对应value没有数据
          o->next = ENDOBS;
          // 将o作为object的value链表的头结点插入
          GSIMapAddPair(m, (GSIMapKey)object, (GSIMapVal)o);
      } else { // 如果有object对应的value那么就直接添加到原练表的尾部
          // 在链表尾部加入o
          list = (Observation*)n->value.ptr;
          o->next = list->next;
          list->next = o;
      }
      // 这个else if 就是没有name有object的Observation,对object进行的操作相同,
  } else if (object) {
  	  // 直接获取object对应的value链表
      n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
      if (n == 0) { // 这个对应链表如果没有数据
          o->next = ENDOBS;
          // 将该observation作为头节点插入
          GSIMapAddPair(NAMELESS, (GSIMapKey)object, (GSIMapVal)o);
      } else { // 有数据,将obsevation直接插在原链表的后面
          list = (Observation*)n->value.ptr;
          o->next = list->next;
          list->next = o;
      }
 } else {
 	  // 既没有name又没有object,就加在WILDCARD链表中
      o->next = WILDCARD;
      WILDCARD = o;
 }
  // 解锁
  unlockNCTable(TABLE);
}
流程

1.首先检查添加观察者方法的参数是否正确 ;

2.创建新的Observation存储这次注册的信息

3.先判断name是否存在,存在就把name作为key去named table查找value;没找到就新建表和对应的name作为key-value存入named table中;找到就获取value(object-observation的表) 然后这时在这个表中查找object ;没找到新建object-observation插入表中;找到先取出对应的observation链表,在链表尾部插入 ;

4.然后判断object是否存在,其他都和上面差不多,只不过这次是从nameless table中开始 ;

5.若既没有 NotificationName 也没有 object,那么就加在 WILDCARD 链表中。

发送通知

使用方法postNotification:postNotificationName:object:userInfo或者postNotificationName:object:发送通知,后者默认userInfo为nil,同样使用GNUStep源码进行分析:

- (void) postNotification: (NSNotification*)notification {
  if (notification == nil) {
      [NSException raise: NSInvalidArgumentException
                  format: @"Tried to post a nil notification."];
    }
  [self _postAndRelease: RETAIN(notification)];
}

- (void) postNotificationName: (NSString*)name
                       object: (id)object {
  [self postNotificationName: name object: object userInfo: nil];
}


- (void) postNotificationName: (NSString*)name
                       object: (id)object
                     userInfo: (NSDictionary*)info {
  GSNotification        *notification;

  notification = (id)NSAllocateObject(concrete, 0, NSDefaultMallocZone());
  notification->_name = [name copyWithZone: [self zone]];
  notification->_object = [object retain];
  notification->_info = [info retain];
  [self _postAndRelease: notification];
}

最终都只会调用_postAndRelease:方法。

- (void) _postAndRelease: (NSNotification*)notification {
  Observation        *o;
  unsigned        count;
  NSString        *name = [notification name];
  id                object;
  GSIMapNode        n;
  GSIMapTable        m;
  GSIArrayItem        i[64];
  GSIArray_t        b;
  GSIArray        a = &b;
   // name为空的报错,注册时可以注册无名,注册无名就等于说是所有的通知都能接收,但是发送通知时不可以
  if (name == nil) {
      RELEASE(notification);
      [NSException raise: NSInvalidArgumentException
                  format: @"Tried to post a notification with no name."];
    }
  object = [notification object];

  GSIArrayInitWithZoneAndStaticCapacity(a, _zone, 64, i);
  lockNCTable(TABLE);
  // 查找所有未指定name或object的观察者,加在a数组中,即将wildcard表中的数据都加在新建链表中
  for (o = WILDCARD = purgeCollected(WILDCARD); o != ENDOBS; o = o->next)
    {
      GSIArrayAddItem(a, (GSIArrayItem)o);
    }
  // 查找与通知的object相同但是没有name的观察者,加在a数组中
  if (object) {
  	  // 在nameless中找object对应的数据节点
      n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
      if (n != 0) { // 将其加入到新建链表中
          o = purgeCollectedFromMapNode(NAMELESS, n);
          while (o != ENDOBS) {
              GSIArrayAddItem(a, (GSIArrayItem)o);
              o = o->next;
            }
        }
    }

  // 查找name的观察者,但观察者的非零对象与通知的object不匹配时除外,加在a数组中
  if (name) {
  	  // 先匹配name
      n = GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name));
      if (n) { // m指向name匹配到的数据
          m = (GSIMapTable)n->value.ptr;
      } else {
          m = 0;
      }
      if (m != 0) { // 如果上述name查找到了数据
          // 首先,查找与通知的object相同的观察者
          n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
          if (n != 0) { // 找到了与通知的object相同的观察者,就加入到新建链表中
              o = purgeCollectedFromMapNode(m, n);
              while (o != ENDOBS) {
                  GSIArrayAddItem(a, (GSIArrayItem)o);
                  o = o->next;
                }
            }

          if (object != nil) {
          // 接着是没有object的观察者,都加在新建链表中
              n = GSIMapNodeForSimpleKey(m, (GSIMapKey)nil);
              if (n != 0) { // 如果没有object并且有数据,就把其中的数据加到新建链表中
                  o = purgeCollectedFromMapNode(m, n);
                  while (o != ENDOBS) {
                      GSIArrayAddItem(a, (GSIArrayItem)o);
                      o = o->next;
                    }
                }
            }
        }
    }
  unlockNCTable(TABLE);

  // 发送通知,给之前新建链表中的所有数据
  count = GSIArrayCount(a);
  while (count-- > 0) {
      o = GSIArrayItemAtIndex(a, count).ext;
      if (o->next != 0) {
          NS_DURING {
              // 给observer发送selector,让其处理
              [o->observer performSelector: o->selector
                                withObject: notification];
            }
          NS_HANDLER {
              BOOL        logged;
              // 尝试将通知与异常一起报告,但是如果通知本身有问题,我们只记录异常。
              NS_DURING
                NSLog(@"Problem posting %@: %@", notification, localException);
                logged = YES;
              NS_HANDLER
                logged = NO;
              NS_ENDHANDLER
                if (NO == logged)
                { 
                  NSLog(@"Problem posting notification: %@", localException);
                }  
            }
          NS_ENDHANDLER
        }
    }
  lockNCTable(TABLE);
  GSIArrayEmpty(a);
  unlockNCTable(TABLE);

  RELEASE(notification);
}
流程

简单的说就是查找到对应的根据通知的参数查找到对应的observation(不需要添加插入),然后按顺序存到链表中,找完之后按顺序遍历执行 ;

1.首先检查参数中的name是否存在 ,不存在报错 ;

2.首先把wildcard中可以接收所有的observation存入链表中 (查找所有未指定name或object的观察者,加在a数组中,即将wildcard表中的数据都加在新建链表中)

3.然后在nameless table中查找与参数object(先判断obect为nil的情况,然后在判断object为nil的情况,两种情况用于查找的key不同)对应的observation链表,把其中的元素也遍历插入执行链表中 (查找与通知的object相同但是没有name的观察者,加在a数组中)

4.最后在name table中查找,同理,先找name,然后再从中找对应object的obsevation链表,把其中的元素也遍历插入执行链表中 (先匹配name,首先,查找与通知的object相同的观察者,接着是没有object的观察者,都加在新建链表中)

5.最后遍历执行链表中的observation,给observer发送selector,让其处理

  • 注意:关于能不能查找到的问题,我们只需要知道它是从表外往表里找的就行了,下面会提到一个例子 ;

移除通知

不给出源码了:

流程

1.若 NotificationName 和 object 都为 nil,则清空 wildcard 链表。
2.若 NotificationName 为 nil,遍历 named table,若 object 为 nil,则清空 named table,若 object 不为 nil,则以 object 为 key 找到对应的链表,然后清空链表。在 nameless table 中以 object 为 key 找到对应的 observer 链表,然后清空,若 object 也为 nil,则清空 nameless table。
3.若 NotificationName 不为nil,在 named table 中以 NotificationName 为 key 找到对应的 table,若 object 为 nil,则清空找到的 table,若 object 不为 nil,则以 object 为 key 在找到的 table 中取出对应的链表,然后清空链表。

一些问题

下面的方法不会接收到通知?
// 添加观察
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"TestNotification" object:@1];
// 通知发送
[NSNotificationCenter.defaultCenter postNotificationName:@"TestNotification" object:nil];

这里我理解的就两点:

  • 注册是无论如何都可以注册的,而且也是从外往里注册的
  • 通知发送(查找)是先从wildcard到nameless table到name table的顺序查找的,而且都是从外往里找的;这样也能知道通知执行的顺序 ;
  • 或者是只要发送通知的参数和观察者的关系是前者包含后者,就可以找到
通知的发送时同步的,还是异步的?发送消息与接收消息的线程是同一个线程么?

通知中心发送通知给观察者是同步的,也可以用通知队列(NSNotificationQueue)异步发送通知。

而且要注意**接收通知的线程,和发送通知所处的线程是同一个线程。**也就是说如果要在接收通知的时候更新 UI,需要注意发送通知的线程是否为主线程。

如何使用异步发送通知?

1.让通知事件处理方法的内部实现再次说明在子线程实现 :

- (void)viewDidLoad {
    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"NotificationName" object:nil];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"--current thread: %@", [NSThread currentThread]);
    NSLog(@"Begin post notification");
    [[NSNotificationCenter defaultCenter] postNotificationName:@"NotificationName" object:nil];
    NSLog(@"End");
}

//- (void)test {
//   NSLog(@"--current thread: %@", [NSThread currentThread]);
//    NSLog(@"Handle notification and sleep 3s");
//    sleep(3);
//}

- (void)test {
    dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{    // 异步执行 + 串行队列
        NSLog(@"--current thread: %@", [NSThread currentThread]);
        NSLog(@"Handle notification and sleep 3s");
        sleep(3);
    });
}

2.可以通过 NSNotificationQueue 的 enqueueNotification: postingStyle: 和 enqueueNotification: postingStyle: coalesceMask: forModes: 方法将通告放入队列,实现异步发送,在把通告放入队列之后,这些方法会立即将控制权返回给调用对象。

这里的异步可能有点奇怪,我的理解是:这里的异步是指和发送通知这个任务异步,而不是队列中的通知异步发送 ;

比如:

- (void)viewDidLoad {
    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"NotificationName" object:nil];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{    // 异步执行 + 串行队列
        NSLog(@"--current thread: %@", [NSThread currentThread]);
        NSLog(@"Begin post notification");
        [[NSNotificationCenter defaultCenter] postNotificationName:@"NotificationName" object:nil];
        NSLog(@"End");
    });
}

- (void)test {
    NSLog(@"--current thread: %@", [NSThread currentThread]);
    NSLog(@"Handle notification and sleep 3s");
    sleep(3);
}

这里[[NSNotificationCenter defaultCenter] postNotificationName:@“NotificationName” object:nil];在队列中执行时,会添加test的同步执行任务进队列,这样导致了要先等待test执行完毕才会解除阻塞完成[[NSNotificationCenter defaultCenter] postNotificationName:@“NotificationName” object:nil];的任务,最后才能执行NSLog(@“End”);

但如果使用通知队列:

- (void)viewDidLoad {
    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"NotificationName" object:nil];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"--current thread: %@", [NSThread currentThread]);
    NSLog(@"Begin post notification");
    NSNotification *notification = [NSNotification notificationWithName:@"NotificationName" object:nil];
    [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP];
    NSLog(@"End");
}

- (void)test {
    NSLog(@"--current thread: %@", [NSThread currentThread]);
    NSLog(@"Handle notification and sleep 3s");
    sleep(3);
}

这时执行[[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP];时,会把通知的任务加入通知队列,这时发送消息的任务已经完成了,于是就可以向主队列中同步添加NSLog(@“End”);,而通知队列中的通知时异步添加到串行队列队列中的,虽然没有创建新的线程,任务的执行也是顺序执行的,但这也意味着这里的通知执行没有阻塞发送消息的任务,所以这里的通知执行和发送消息的任务是异步的 ;

页面销毁时不移除通知会崩溃吗?

在观察者对象释放之前,需要调用 removeOberver 方法将观察者从通知中心移除,否则程序可能会出现崩溃。**(因为这个时候可能出现野指针)**但从 iOS9 开始,即使不移除观察者对象,程序也不会出现异常。

这是因为在 iOS9 以后,通知中心持有的观察者由 unsafe_unretained 引用变为weak引用。即使不对观察者手动移除,持有的观察者的引用也会在观察者被回收后自动置空。但是通过 addObserverForName:object: queue:usingBlock: 方法注册的观察者需要手动释放,因为通知中心持有的是它们的强引用。

多次添加同一个通知会是什么结果?多次移除通知呢?
  • 多次添加同一个通知,观察者方法会调用多次(可以看之前的源码,在注册观察者时不会对链表里面原来的判断,而是直接加入链表末尾,等到发送通知在表中查找观察者时,只要找到就会执行) ;
  • 多次移除,没关系。
为什么注册通知时可以空名注册,但是发送通知时却不可以?

具体的原因不好说,但实际出现这种现象的原因是因为在发送通知的方法里面最开始就有判段是否空名,注册就没有 ;

object是干嘛的?是不是可以用来传值?

object是用来过滤Notification的,只接收指定的sender所发的Notification,传值请用userInfo,而不是object。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2118186.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

自定义一个maven插件,deploy(推送)依赖到私服的时候企业微信群里通知

很多时候&#xff0c;项目里面会引入各种同公司写的依赖&#xff0c;而这些依赖有时候更新到了nexus私服&#xff0c;其他人又不知道&#xff0c;导致本地的代码执行不符合预期。 我们这就有这种情况&#xff0c;因为依赖还处于开发阶段&#xff0c;并不是一个release版本&…

基于vue框架的车辆维修管理系统的设计与实现pvno9(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目管理&#xff1a;普管,驾驶员,维修员,维修出库,采购入库,配件信息,车辆信息,维修申请,维修费用 开题报告内容 基于Vue框架的车辆维修管理系统的设计与实现开题报告 一、项目背景与意义 随着汽车保有量的不断增加&#xff0c;车辆维修行业面临着前所未…

计算机毕业设计选题推荐-流浪动物领养管理系统-Java/Python项目实战(亮点:数据可视化分析、智能推荐)

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

Node.js构建千万级高可用企业级应用:‌深入六大核心业务场景

Node.js构建千万级高可用企业级应用&#xff1a;‌深入六大核心业务场景 Node.js&#xff0c;‌作为一种基于Chrome V8 JavaScript引擎构建的开源运行时环境&#xff0c;‌已经在企业级应用中展现出其独特的优势。‌特别是在处理高并发、‌实时性要求高的应用场景中&#xff0…

解码数字化转型顶层规划(附236页PPT:xx企业数字化转型项目顶层规划方案)

写在前面&#xff1a;PPT分享见后文~ 企业数字化转型顶层规划的制定是一个系统性的过程&#xff0c;需要综合考虑多个方面。以下是制定企业数字化转型顶层规划的一些关键步骤和要点&#xff0c;以供参考&#xff1a; 1、明确数字化转型战略定位&#xff1a; 将数字化转型作为…

分布式共识(一致性)算法(协议) Paxos 简介

分布式共识&#xff1a;Paxos算法简介 需求背景 在分布式系统中&#xff0c;确保各节点间的数据一致性是核心挑战之一。Paxos算法&#xff0c;作为解决此问题的经典方法&#xff0c;通过分布式共识机制允许网络中的多数节点就某值达成一致&#xff0c;即便面对消息延迟、节点…

京东图标点选验证码识别代码

如上图所示&#xff0c;京东会让你根据小图中的图案&#xff0c;在大图中点击对应的图标。 识别代码如下&#xff1a; 需要两张图片&#xff0c;可以是原图2张&#xff0c;或者是截图2张。运行下面代码可以看到点击的位置。 import base64 import requests import datetime …

python-新冠病毒

题目描述 假设我们掌握了特定时间段内特定城市的新冠病毒感染病例的信息。在排名 i 的当天有 i 个案例&#xff0c;即&#xff1a; 第一天有一例感染第二天有两例感染第三天有三例感染以此类推...... 请计算 n 天内的感染总数和每天平均感染数。 输入 整数 n 表示天数&…

【脊线图】:附Origin详细画图流程

目录 No.1 理解脊线图 No.2 画图流程 1 导入数据&#xff0c;绘制图形 2 设置绘图细节 3 设置颜色标尺并进行色阶控制 4 设置坐标轴 5 效果图 No.1 理解脊线图 脊线图&#xff0c;在统计学和数据分析领域&#xff0c;是一种高级且专业的可视化工具&#xff0c;用于展示…

如何摸透大模型?看完《实战AI大模型》你算是懂了!

今天&#xff0c;人工智能技术的快速发展和广泛应用已经引起了大众的关注和兴趣&#xff0c;它不仅成为技术发展的核心驱动力&#xff0c;更是推动着社会生活的全方位变革。特别是作为AI重要分支的深度学习&#xff0c;通过不断刷新的表现力已引领并定义了一场科技革命。大型深…

酸奶刺客打折,瑜伽裤冲锋衣熄火…中产消费正全线崩溃?

如果把消费市场这两个月的热搜放在一起看&#xff0c;你会发现中产好像越来越抠了&#xff0c;消费市场的天要变了……‍‍‍‍‍‍ 比如几十块钱一支的钟薛高降价降到几块钱也没多少人买了&#xff0c;创始人更是放下身段&#xff0c;直播卖起了红薯……‍ 还有前不久冲上热搜…

1比25万基础电子地图(辽宁版)

我们为你分享过四川、云南、江西、贵州、重庆、青海、西藏、新疆、甘肃、黑龙江、吉林、湖北、内蒙古、广东、广西、浙江、河南、湖南、宁夏、山西、陕西、天津、山东、河北、江苏和福建的1比25万基础电子地图&#xff0c;现在再为你分享辽宁版的电子地图。 如果你需要这些省份…

哈希表的底层实现(2)---C++版

目录 链地址法Separate Chaining——哈希桶的模拟实现 超大重点分析&#xff1a; 两种方法对比 由于在上次的哈希表的底层实现(1)---C版已经详细的阐述了相关的结构和原理&#xff0c;哈希表的实现方法主要分为链地址法和开放定址法。开放定址法上次已经实现过了&#xff0c…

MySQL record 04 part

高级查询&#xff1a; order by 对查询结果排序&#xff0c; 注意&#xff0c;使用order by的时候&#xff0c;如果某条记录的order by 指定的字段值是 null&#xff0c;那么包含 null 的这条记录会排在第一位&#xff0c;因为 null 被认为是最小值。 group by 对字段的值进行…

【系统分析师】-软件设计

目录 1、概要设计 1&#xff09;层次图&#xff08;H图&#xff09; 2&#xff09;HIPO图 2、详细设计 1&#xff09;流程图 2&#xff09;盒图&#xff08;N-S图&#xff09; 3&#xff09;PAD 问题分析图 4&#xff09;PDL伪代码图 3、软件设计过程 4、软件设计活动…

MQTT工业网关的工作原理及其在实际生产中的重要作用

在智能制造与工业4.0的浪潮中&#xff0c;MQTT工业网关作为连接传统工业设备与现代物联网技术的桥梁&#xff0c;正发挥着不可或缺的作用。MQTT协议以其轻量级、开放性和可靠性&#xff0c;在工业物联网领域得到了广泛应用。本文将通过一个实际应用案例&#xff0c;解析MQTT工业…

网络安全工程师填补人才缺口

近年来&#xff0c;新兴技术如人工智能、5G和量子信息技术等的迅猛发展&#xff0c;极大地推动了互联网技术的革新。 然而&#xff0c;随之而来的网络安全威胁也日益增多&#xff0c;对国家、企业及个人安全构成了严重挑战。 网络安全问题就在我们身边&#xff0c;因此&#…

关于电影票api接口你了解多少?

电影票API接口是连接第三方平台与电影院票务系统的一种技术手段&#xff0c;它允许第三方应用程序如网站、移动应用或小程序集成电影票购买服务。通过API&#xff0c;用户可以在第三方平台上查询电影信息、影院排期、选择座位并完成购票支付。 电影票API接口的主要功能通常包括…

智能头盔语音识别声控芯片,AI离线语音识别ic方案,NRK3301

头盔是交通事故中保护电动车车主安全的最后一道屏障。为了增加骑行用户的安全保护&#xff0c;改善骑行用户的出行体验&#xff0c;让用户从被动使用头盔到主动佩戴头盔&#xff0c;头盔厂家与九芯电子合作&#xff0c;推出了语音智能头盔&#xff0c;它具备首家骑行专用的智能…

关于SpringBoot项目yml配置数据库、redis、mq等中间件的用户密码敏感信息加密问题的解决方案

一、问题描述 一般情况下,yml里边的配置信息 都是在项目部署时动态管理的,一般不存在泄密或者不安全的情况,但是,不凡有一些脑袋有泡的客户,要对你项目源码进行安全性检测。故提供如下解决方案: 二、关于中间件Redis、MQ等对用户名或密码进行加密。 一般可在其对应的配…