多线程、锁、队列

信号量

1
2
3
①dispatch_semaphore_t sem = dispatch_semaphore_create(0);
②dispatch_semaphore_signal(sem);
③dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

GCD

1
2
3
4
5
6
①dispatch_group_t group = dispatch_group_create();
②dispatch_group_enter(group);
③dispatch_group_leave(group);
④dispatch_group_notify(group, dispatch_get_main_queue(), ^{
这里跑去干自己的事情。。。。
});

用信号量时候,

某个线程锁住后,就处于等待状态,即该线程也就无法再继续操作,所以试图尝试在该线程锁住的时候去发送信号,增加信号量,会造成死锁。

举例:

信号量使用场景

任务

iOS 实现并发和串行任务
串行队列如何知道任务已经完成
iOS开发中的并发、串行队列,同步、异步任务

iOS代码同步到主线程的三种方法

autoreleasepool 使用场景、什么时候释放

临时变量什么时候释放

RunLoop和线程间的关系

每条线程都有唯一的一个与之对应的RunLoop对象,RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value,主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建,RunLoop在第一次获取时创建,在线程结束时销毁。

1

线程-⑤线程安全

六、队列 -> 任务 -> 线程 -> 数据安全

有队列,就有需要处理的任务,有任务就有线程相关的要考虑(附队列本身也处在线程中),有多线程的考虑,就要考虑数据安全。

常见笔试/面试题

3、队列和数据安全

那我们先来知道一个非常重要的事情:

——- 队列只是负责任务的调度,而不负责任务的执行 ———

——- 任务是在线程中执行的 ———

所以,这个问题其实问得就不大对。让人不知怎么答。

我们这里说下,分情况。

如果是串行队列,数据是安全的。

如果是并发队列,在读写的时候,数据是不安全的。我们可以通过以下几种方式将其处理成安全的。

①、并发队列,改成串行执行。

可以通过

任务是同步任务;

异步任务用依赖;

异步任务用信号量控制并发量为1

②、加锁

@synchronized

NSLock

dispatch_semaphore

语言-OC Runtime

Runtime

前言:

Objective C的runtime技术功能非常强大,能够在运行时获取并修改类的各种信息,包括获取方法列表、属性列表、变量列表,修改方法、属性,增加方法,属性等等,本文对相关的几个要点做了一个小结。

目录:

(1)使用class_replaceMethod/class_addMethod函数在运行时对函数进行动态替换或增加新函数

(2)重载forwardingTargetForSelector,将无法处理的selector转发给其他对象

(3)重载resolveInstanceMethod,从而在无法处理某个selector时,动态添加一个selector

(4)使用class_copyPropertyList及property_getName获取类的属性列表及每个属性的名称

(5) 使用class_copyMethodList获取类的所有方法列表

应用:

viewWillAppear 埋点

KVO的本质

问: Category的实现原理,以及Category为什么只能加方法不能直接加属性(需要自己在分类中实现setter、getter方法)?

答:分类的实现原理是将category中的方法,属性,协议数据放在category_t结构体中,然后将结构体内的方法列表拷贝到类对象的方法列表中。 Category可以添加属性,但是并不会自动生成成员变量及set/get方法。因为category_t结构体中并不存在成员变量。通过之前对对象的分析我们知道成员变量是存放在实例对象中的,并且编译的那一刻就已经决定好了。而分类是在运行时才去加载的。那么我们就无法再程序运行时将分类的成员变量中添加到实例对象的结构体中。因此分类中不可以添加成员变量。

一些参考

Objective C运行时(runtime)技术总结

需求1、当项目中,需要继承某一个父类,但是父类中并没有提供我们需要的调用方法,一般情况下,这种时候,我们可能会再派生一个类,或者会直接为这个类写一个分类(category),在这个分类中,我们可以新增或者替换某个方法(注意:不推荐在分类中重写方法,而且也无法通过 super 来获取所谓父类的方法)。大致在这两种情况下,我们可以通过 class_addMethod 来实现我们想要的效果。

先从一个场景问题带出吧,比如一个应用都是继承于UIViewController,刚开始时候不要求要转屏,到后面才决定加上旋转屏适配,这个时候如果我们去创建一个父类,并让去修改每个类的继承这个方法显然是效率太低了。而且有时候,我们也不想多创个父类,于是我们就只能决定一次过替换掉这些controller里的viewWillAppear:和 willAnimateRotationToInterfaceOrientation:duration:,换成自己的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class clazz = object_getClass((id)self);

//将系统的resolveInstanceMethod替换成自己的myResolveInstanceMethod,而系统的resolveInstanceMethod方法就是当运行时对象调用了一个找不到的方法的时候系统会去寻找的机制。
[clazz swizzleMethod:@selector(resolveInstanceMethod:) withMethod:@selector(myResolveInstanceMethod:)];
});
}


/**
* 用什么方法替换掉什么方法
*
* @param origSel 原方法选择子
* @param aftSel 新方法选择子
*/
+ (BOOL)swizzleMethod:(SEL)origSel withMethod:(SEL)aftSel {
//通过class_getInstanceMethod()拿到对应的Method
Method originMethod = class_getInstanceMethod(self, origSel);
Method newMethod = class_getInstanceMethod(self, aftSel);

if(originMethod && newMethod){ //必须两个Method都要拿到
//class_addMethod将本来不存在于被操作的Class里的newMethod的实现添加在被操作的Class里,并使用origSel作为其选择子
if(class_addMethod(self, origSel, method_getImplementation(newMethod),method_getTypeEncoding(newMethod))) {
//当addMethod成功完成后,利用class_replaceMethod换掉method_getImplaementation(oiginMethod)的选择子,将原方法的实现的SEL换成新方法的SEL:aftSel,ok目的达成了。想一想,现在通过旧方法SEL来调用,就会实现新方法的IMP,通过新方法的SEL来调用,就会实现旧方法的IMP。
class_replaceMethod(self, aftSel, method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
}
return YES;
}
return NO;
}

语言-消息转发

CJBaseHelper/HookCJHelper

pod search CJBaseHelper

image-20240822165215629

库的使用方法,见我简书中的以下文章:

CJHook

pod search CJHook

image-20240822165517533

在调用原始方法之前插入额外的执行逻辑

1、使用场景

  1. 日志记录与监控: 在方法执行前后添加日志记录,可以帮助开发者监控应用的行为,特别是在调试和性能分析时。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    - (void)swizzleMethod:(Class)class withSelector:(SEL)selector {
    Method originalMethod = class_getInstanceMethod(class, selector);
    IMP originalIMP = method_getImplementation(originalMethod);
    IMP newIMP = imp_implementationWithBlock(^(id self) {
    NSLog(@"Before method execution");
    ((void(*)(id, SEL))originalIMP)(self, selector);
    NSLog(@"After method execution");
    });
    method_setImplementation(originalMethod, newIMP);
    }

2、调用

以在进入个人主页前,需要先判断是否登录为例

常规:使用Util式、使用宏

1
2
3
4
5
6
7
8
- (void)goMineHomePage {
// 在调用原始方法之前执行的代码(进入个人主页前,需要先判断是否登录)
bool canPush = [LoginCheckerUtil checkLogin];
if (!canPush) return;

MineHomeViewController *viewController = [[MineHomeViewController alloc] init];
[self.navigationController pushViewController:viewController animated:YES];
}

改进(改进后发现,其实可读性不高,所以还不如不这么改):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// LoginCheckUtil.m
#define NeedLoginAnnotation(Class originalSelOwnerClass, SEL originalSelector) \
HookCJHelper_addPreExecutionToClassMethod(originalSelOwnerClass, originalSelector, [LoginCheckUtil class], @selector(loginCheckMethod));
class LoginCheckUtil: NSObject {
+ (bool)loginCheckMethod {
if (UserManager.isLogin) {
return YES;
}
LoginViewController *viewController = [[MineHomeViewController alloc] init];
[topVC pushViewController:viewController animated:YES];
return NO;
}
}

// HomeViewController.m
+ (void) initialize {
@NeedLoginAnnotation([self class], @selector(goMineHomePage)) // 在 + (void) initialize 中调用
}

- (void)goMineHomePage {

}

3、内部核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 在调用原始方法之前插入额外的执行逻辑
bool HookCJHelper_addPreExecutionToClassMethod(Class originalSelOwnerClass, SEL originalSelector, Class preExecSelOwnerClass, SEL preExecutionSelector) {
// 原始方法检查
if (!originalSelOwnerClass || !class_getClassMethod(originalSelOwnerClass, originalSelector)) {
return false;
}
Method originalMethod = class_getClassMethod(originalSelOwnerClass, originalSelector);
IMP originalIMP = method_getImplementation(originalMethod);
if (!originalIMP) {
return false;
}

// 要插入的方法
if (!preExecSelOwnerClass || !class_getClassMethod(preExecSelOwnerClass, preExecutionSelector)) {
return false;
}

// 创建新的方法实现
IMP newIMP = imp_implementationWithBlock(^(Class self) {
// 调用额外的逻辑(预执行类方法)
((void(*)(Class, SEL))objc_msgSend)(preExecSelOwnerClass, preExecutionSelector);
// 调用原始的类方法
((void(*)(Class, SEL))originalIMP)(self, originalSelector);
});

// 替换原始方法的实现为新的实现
method_setImplementation(originalMethod, newIMP);

return true;
}

  1. 什么时候会报unrecognized selector的异常
  • 当调用该对象上某个方法,而该对象上没有实现这个方法的时候。可以通过消息转发进行解决,流程见下图

    img

    image

  • OC在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果在最顶层的父类中依然找不到相应的方法时,程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX
    但是在这之前,OC的运行时会给出三次拯救程序崩溃的机会

  • Method resolution(消息动态解析
    OC运行时会调用+resolveInstanceMethod:或者+resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程,否则,运行时就会移到下一步,消息转发(Message Forwarding

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 重写 resolveInstanceMethod: 添加对象方法实现
    + (BOOL)resolveInstanceMethod:(SEL)sel {
    // 如果是执行 run 函数,就动态解析,指定新的 IMP
    if (sel == NSSelectorFromString(@"run:")) {
    // class: 给哪个类添加方法
    // SEL: 添加哪个方法
    // IMP: 方法实现 => 函数 => 函数入口 => 函数名
    // type: 方法类型:void用v来表示,id参数用@来表示,SEL用:来表示
    class_addMethod(self, sel, (IMP)runMethod, "v@:@");
    return YES;
    }
    return [super resolveInstanceMethod:sel];
    }

    //新的 run 函数
    void runMethod(id self, SEL _cmd, NSNumber *meter) {
    NSLog(@"跑了%@", meter);
    }
  • Fast forwarding(消息接受者重定向
    如果目标对象实现了-forwardingTargetForSelector:Runtime这时就会调用这个方法,给你把这个消息转发给其他对象的机会。只要这个方法返回的不是nilself,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续Normal Fowarding。 这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但下一步转发会创建一个NSInvocation对象,所以相对更快点

    1
    2
    3
    4
    5
    6
    7
    8
    // 消息接受者重定向
    - (id)forwardingTargetForSelector:(SEL)aSelector{
    if (aSelector == @selector(run:)) {
    return [[Person alloc] init];
    // 返回 Person 对象,让 Person 对象接收这个消息
    }
    return [super forwardingTargetForSelector:aSelector];
    }
    • Normal forwarding(消息重定向
      这一步是Runtime最后一次给你挽救的机会。首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nilRuntime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    // 获取函数的参数和返回值类型,返回签名
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if ([NSStringFromSelector(aSelector) isEqualToString:@"run:"]) {
    return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    }
    return [super methodSignatureForSelector:aSelector];
    }

    // 消息重定向
    - (void)forwardInvocation:(NSInvocation *)anInvocation {
    // 从 anInvocation 中获取消息
    SEL sel = anInvocation.selector;
    if (sel == NSSelectorFromString(@"run:")) {
    // 1\. 指定当前类的一个方法作为IMP
    // anInvocation.selector = @selector(readBook:);
    // [anInvocation invoke];

    // 2\. 指定其他类来执行这个IMP
    Person *p = [[Person alloc] init];
    // 判断 Person 对象方法是否可以响应 sel
    if([p respondsToSelector:sel]) {
    // 若可以响应,则将消息转发给其他对象处理
    [anInvocation invokeWithTarget:p];
    } else {
    // 若仍然无法响应,则报错:找不到响应方法
    [self doesNotRecognizeSelector:sel];
    }
    }else{
    [super forwardInvocation:anInvocation];
    }
    }

    - (void)doesNotRecognizeSelector:(SEL)aSelector {
    [super doesNotRecognizeSelector:aSelector];
    }

    既然-forwardingTargetForSelector:-forwardInvocation:都可以将消息转发给其他对象处理,那么两者的区别在哪?
    区别就在于-forwardingTargetForSelector:只能将消息转发给一个对象。而-forwardInvocation:可以把消息存储,在你觉得合适的时机转发出去,或者不处理这个消息。修改消息的target,selector,参数等。将消息转发给多个对象

语言-消息转发NSProxy

一、NSProxy

NSProxy,你可以通过继承它,并重写它的以下两个方法以实现消息转发到另一个实例。说白了,NSProxy专为代理而生(负责将消息转发到真正的target的代理类)。从类名来看是代理类,专门负责代理对象转发消息的。相比NSObject类来说NSProxy更轻量级,通过NSProxy可以帮助Objective-C间接的实现多重继承的功能

1
2
3
- (void)forwardInvocation:(NSInvocation *)anInvocation;

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;

1、通过NSProxy可以帮助Objective-C间接的实现多重继承的功能的例子

现在比较流行的说法是用它来模拟多重继承,大致过程就是让它Hold住/包含你要实现多继承的类的对象,然后被hold住的对象的行为定义在接口中,并让Proxy去实现这些接口。然后再转发的时候把消息转发到实现了该接口的对象去执行,这样就好像实现了多重继承一样。注意:这个真不是多重继承,只是包含,然后把消息路由到指定的对象而已,其实完全可以用NSObject类来实现。

天使=人+鸟

1
2
3
4
5
6
Person *person = [Person new];
Bird *bird = [Bird new];

id angelProxy = [AngelProxy proxyForObject1:person object2:bird];
[proxy speak]; // 人可以说话
[proxy fly]; // 鸟可以飞

另一个例子:NSProxy的学习使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// AngelProxy.h
#import <Foundation/Foundation.h>

@interface AngelProxy : NSProxy

+ (id)proxyForObject1:(id)obj1 object2:(id)obj2;

@end




// AngelProxy.m
#import "AngelProxy.h"

@interface AngelProxy()

@property (nonatomic, weak) id target1;
@property (nonatomic, weak) id target2;
@end

@implementation AngelProxy

- (instancetype)initWithTarget1:(id)obj1 target2:(id)obj2 {
_target1 = obj1;
_target2 = obj2;

return self;
}

+ (id)proxyForObject1:(id)obj1 object2:(id)obj2 {
return [[AngelProxy alloc] initWithTarget1:obj1 target2:obj2];
}

// 重写NSProxy如下两个方法,在处理消息转发时,将消息转发给真正的Target处理
- (void)forwardInvocation:(NSInvocation *)invocation {
id target;
if ([_target1 methodSignatureForSelector:invocation.selector]) {
target = _target1;
}else{
target = _target2;
}
[invocation invokeWithTarget:target];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
NSMethodSignature *sig;

sig = [_target1 methodSignatureForSelector:selector];
if (sig) {
return sig;
}

sig = [_target2 methodSignatureForSelector:selector];
return sig;
}


@end

2、NSProxy 和 NSObject 的比较

相比NSObject,NSProxy更轻量级,做消息转发效率更高。

NSObject寻找方法顺序:本类->(父类-)>(动态方法解析)-> 消息转发;
NSproxy顺序:本类->消息转发

3、NSProxy的用途

  1. AOP面向切片编程

    iOS中面向切片编程一般有两种方式 ,一个是直接基于runtime 的method-Swizzling.还有一种就是基于NSProxy

  2. 解决NSTimer, CADisplayLink等强引用target引起的无法释放问题。如NSTimer:利用消息转发来断开NSTimer对象与视图之间的强引用关系。初始化NSTimer时把触发事件的target替换成一个单独的对象,然后这个对象中NSTimer的SEL方法触发时让这个方法在当前的视图self中实现。

    1
    2
    3
    4
    self.timer = [NSTimer timerWithTimeInterval:1 target:self.proxy 
    selector:@selector(timerEvent) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
    // 这里我们timer的target使用AYProxy对象,它会实现你这个timer想要实现的timerEvent消息转发给self。从而避免了循环引用

    协议类代码为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    #import

    @interface AYProxy : NSProxy

    @property (nonatomic, weak) id obj;

    @end

    #import "AYProxy.h"

    @implementation AYProxy

    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    NSMethodSignature *sig = nil;
    sig = [self.obj methodSignatureForSelector:aSelector];
    return sig;
    }

    - (void)forwardInvocation:(NSInvocation *)anInvocation{
    [anInvocation invokeWithTarget:self.obj];

    }

    @end
  3. 多重继承

    实现类似CAAnimation类族.

语言-请求拦截NSURLProtocol

一、NSURLProtocol

如果开发者自定义的一个 NSURLProtocol 并且注册到 app 中,那么在这个自定义的 NSURLProtocol 中我们可以拦截所有的请求((注意,不只是webView内的请求,而是整个app内的所有请求)),进行修改,或者修改 response。即NSURLProtocol这个东西的作用可以让我们在app的内部拦截一切url请求。

「或者这么说吧: NSURLProtocol 就是一个苹果允许的中间人攻击。」

注意事项:

如果我们顺序注册 A B C 三个 Protocol,那么一个 connection 在发送的时候,处理的顺序是 C B A,而且最多只有一个 Protocol 会触发处理。

拦截 UIWebview 的请求,会有被拒的风险。

附:

iOS的Foundation框架提供了 URL Loading System 这个库(后面简写为ULS),所有基于URL(例如http://,https:// ,ftp://这些应用层的传输协议)的协议都可以通过ULS提供的基础类和协议来实现。而ULS库里提供了一个强有力的武器 NSURLProtocol。

NSURLProtocol能做什么?

举几个例子:

  • 全局网络请求设置:我们的APP内的所有请求都需要增加公共的头,像这种我们就可以直接通过NSURLProtocol来实现,当然实现的方式有很多种
  • 自定义 Response (过滤敏感信息)
  • 再比如我们需要将APP某个API进行一些访问的统计
  • 再比如我们需要统计APP内的网络请求失败率

语言:Swift相关知识

Swift相关知识

一、iOS开发中使用OC和swift的对比

1
2
3
1.import的类
OC:某个只要要使用某个类就要将该类import。
swift:如果是用户自己创建类,其他类无需import可以直接使用。pod的一些三方类和系统的一些类,在使用的时候需要import

Guard语句的语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
guard expression else { 
//语句
//必须包含一个控制语句:return,break,continue或throw。
}
这里,expression是一个布尔表达式(返回true或者false
如果对表达式求值falseguard则执行代码块内的语句
如果对表达式求值trueguard则从执行中跳过代码块内的语句

func someFunction() {
guard false else {
print("Condition not met")
return
}
print("Condition met")
}

二、【iOS】Swift和OC协议的区别?为何Swift是面向协议的?

【iOS】Swift和OC协议的区别?为何Swift是面向协议的?

相比于OC,Swift 可以做到协议方法的具体实现,而 OC 则不行。

OC中协议(Protocol)类似一个类的接口,只声明,不实现。
只能定义公用的一套接口,但不能提供具体的实现方法。也就是说,它只告诉你要做什么,但具体怎么做不关心。具体的实现要在遵守这个协议的类中实现。

OC的Protocol与继承的区别

继承连同方法的实现也继承了,而protocol只有声明没有实现;
protocol能够作用到不同类型的类上。

OC的Protocol与Category的区别

Category可以给一个类扩充方法,既有申明也有实现;而Protocol只有声明,没有实现。
同:CategoryProtocol都可以声明方法,不能声明属性。

Swift 面试题解

?? 的作用

可选值的默认值, 当可选值为nil 的时候, 会返回后面的值. 如
let someValue = optional1 ?? 0

远程共享文件夹

一、MacBook Pro 怎么设置smb共享文件夹

在Mac要设置smb的共享文件夹,那么如何设置的好的呢?如何去设置一共享文件夹?

MacBook Pro 怎么设置smb共享文件夹

方法/步骤

1.

右键一个桌面新建文件夹,然后点击新建文件夹。

MacBook Pro 怎么设置smb共享文件夹

MacBook Pro 怎么设置smb共享文件夹

2.

把要共享文件,拖入新建文件夹,然后点击logo。

MacBook Pro 怎么设置smb共享文件夹

MacBook Pro 怎么设置smb共享文件夹

3.

点击设置,点击共享。

MacBook Pro 怎么设置smb共享文件夹

MacBook Pro 怎么设置smb共享文件夹

4.

文件共享勾上,点击加。

MacBook Pro 怎么设置smb共享文件夹

MacBook Pro 怎么设置smb共享文件夹

5.

然后点击桌面选择新建文件夹添加,右边默认的读权限,如果要写的,那么该为读写。

MacBook Pro 怎么设置smb共享文件夹

MacBook Pro 怎么设置smb共享文件夹

6.

之后点击用户,点击锁。

MacBook Pro 怎么设置smb共享文件夹

MacBook Pro 怎么设置smb共享文件夹

7.

输入密码解锁,然后把两个允许勾上。

MacBook Pro 怎么设置smb共享文件夹

MacBook Pro 怎么设置smb共享文件夹

远程桌面连接

一、mac被远程连接

1、先请在苹果 Mac 电脑上的“系统偏好设置”窗口中打开“共享”功能

2、接着在共享窗口中的左侧点击启用“屏幕共享”选项

img

3、当屏幕共享功能打开以后,请点击“电脑设置”按钮

img

4、随后请勾选二个选项: VNC 显示程序可以使用密码控制屏幕,并且设置一个连接密码

img

5、随后会提示我们是否真的要连接此电脑,点击 Continue 按钮继续。

img

二、mac连接已设置允许远程连接的mac