【Effective Objective - C 2.0】——读书笔记(二)

文章目录

  • 前言
  • 六、理解“属性”这一概念
  • 七、在对象内部尽量直接访问实例变量
  • 八、理解“对象等同性”这一概念
  • 九、以“类族模式”隐藏实现细节
  • 十、在既有类中使用关联对象存放自定义数据
  • 十一、理解objc_msgSend的作用
  • 十二、理解消息转发机制
    • 动态方法解析
    • 备援接受者
    • 完整的消息转发
    • 消息转发全流程
  • 十四、理解“类对象”的用意


前言

这是一篇为了快手✌️xmy学长写的博客

六、理解“属性”这一概念

“属性”(property)

是OC的一项特性,用于封装对象中的数据。OC对象通常会把其所需要的数据保存为各种实例变量。其中“获取方法”用于读取变量值,而“设置方法”用于写入变量值。切此特性引入了一种新的“点语法”,使开发者可以更为容易地依照类对象来访问存放于其中的数据。

我们在类接口的public区段中声明一些实例变量:

@interface EOCPerson: NSObject {
@public NSString *_firstName;NSString *_lastName;
@privateNSString *_someInternalData;
}
@end

然后我们添加一个实例变量:

@interface EOCPerson: NSObject {
@public NSString *_dateOfBirth;NSString *_firstName;NSString *_lastName;
@privateNSString *_someInternalData;
}
@end

我们新添加的实例变量就会代替原第一个位置实例变量的偏移量。
这样的话,如果代码使用了编译器计算出来的偏移量,那么在修改类定义之后必须重新编译,否则就会出错。例如:某个代码库中的代码使用了一份旧的类定义。如果和其相链接的代码使用了新的类定义,那么运行时就会出现不兼容现象。对此类问题,OC的解决方法是,把实例变量当做一种存储偏移量所用的“特殊变量”,交由“类对象”保管。偏移量会在运行期查找,如果类的定义变了,那么存储的偏移量也就变了,这样的话,无论何时访问实例变量,总能使用正确的偏移量。这就是稳固的“应用程序二进制接口”

这里我们的解决方法就是尽力使用存取方法来访问实例变量,这时@property语法就派上用场了。这种规范的命名方法OC会自动创建出存取方法。

简单来说,以下两部分代码的效果是相同的:

@interface EOCPerson: NSObject
@property NSString *firstName;
@property NSString *lastName;
@end
@interface EOCPerson: NSObject
- (NSString *)firstName;
- (void)setFirstName: (NSString *)firstName;
- (NSString *)lastName;
- (void)setLastName: (NSString *)lastName;
- @end

@dynamic关键字

如果我们不想令编译器自动合成存取方法,那我们应该怎么做呢?
那就是使用@dynamic关键字了,它会告诉编译器:不要自动创建实现属性所用的实例变量,也不要为其创建存取方法。然后我们就需要在运行期动态创建存取方法了。

@interface EOCPerson : NSManageObject
@property NSString *firstName;
@end@implementation
@dynamic firstName;
@end

要点总结

  • 可以通过**@property**语法来定义对象中所封装的数据。
  • 通过“特质”来指定存储数据所需的正确语义。尤其指代属性关键字
  • 在设置属性所对应的实例变量时,一定要遵从该属性所声明的语义。
  • 开发iOS程序时应该使用nonatomic属性,因为atomic属性会严重影响性能,nonatomic的加锁维护了线程安全

七、在对象内部尽量直接访问实例变量

在书中作者说建议大家在读取实例变量的时候采用直接访问的形式,而在设置实例变量的时候采用属性操作

直接访问和属性访问的区别:

  • 由于不经过Objective-C的〝方法派发” (methoddispatch,参见第11条)步骤,所以
    直接访问实例变量的速度当然比较快。 在这种情况 下,编译器所生成的代码会直接访 问保存对象实例变量的那块内存。
  • 直接访问实例变量时,不会调用其〝设置方法”,这就绕过了为相关属性所定义的 “内 存 管 理 语 义 ” 。 比 方 说 , 如 果 在 A
    R C 下 直 接 访 问 一个 声 明 为 c o p y 的 属 性 , 那 么 并 不 会拷贝该属性,只会保留新值并释放旧值。
  • 如果直接访问实例变量,那么不会触发“键值观测"(Key-ValueObserving, Kvo)。通
    知。这样做是否会产生问题,还取决于具体的对象行为。
  • 通过属性来访问有助于排查与之相关的错误,因为可以给 “获取方法〞和/ 或 “设置 方法〞中新增
    “断点”(breakpoint),监控该属性的调用者及其访问时机。

惰性初始化:

也叫做“延迟初始化”。在惰性初始化的情况下,必须通过“获取方法”来访问属性,否则,实例变量就永远不会初始化。
一般用于:一个属性不常用,而且创建该属性的成本较高的情况。

- (EOCBrain *) brain {if (!_brain) {_brain = [Brain new];}return _brain;
}

在这种情况下我们就必须使用存取方法来访问我们的属性

要点

  • 在对象内部读取数据时,应该直接通过实例变量来读,而写人数据时,则应通过属性 来写。
  • 在初始化方法及dealloe 方法中,总是应该直接通过实例变量来读写数据。
  • 有时会使用惰性初始化技术配置某份数据,这种情况下,需要通过属性来读取数据。

八、理解“对象等同性”这一概念

“==”和“isEqual:”区别

我们先来回顾一下之前学过的“==”和“isEqual:”区别:

====是看地址(指针)==来进行判断,地址不一致即返回false
isEqual:是专门用于判断的方法,不一定是看地址,也可以是其他的标准。

在NSObject类中,==与isEqual:没有明显区别,但在NSString中,已经完成了重写,只要字符串字符序列相同,isEqual:方法就返回true。

以如下代码为例:

NSString *foo = @"Badger 123";
NSString *bar = [NSString stringWithFormat:@"Badger %i", 123];
BOOL equalA = (foo == bar);//NO
BOOL equalB = [foo isEqual:bar];//YES
BoOL equalC = [foo isEqualToString:bar];//YES

可以看到== 与等同性判断方法之间的差别。NSString类实现了一个自己独有的等 同性判断方法,名叫 “isEqualToString: ” 。传递给该方法的对象必领是NSString,否则结果 末定义(u deined)。调用该方法比调用“ isEqual:〞方法快,因为“ isEqual:〞方法还要执行额外的步骤,因为isEqual不知道受测对象的类型。

NSObject 协议中有两个用于判断等同性的关键方法:

 - (BOOL) isEqual: (id) object;- (NSUInteger) hash;

NSObject 类 对 这 两 个 方 法 的 默 认 实 现 是 : 当 且 仅 当 其 “ 指 针 值 ”
(pointervalue)日 完 全 相 等时,这两个对象才相等。若想在自定义的对象中正确後写这些方法,就必领先理解其约定
(contract)。如果“ isEqual:〞方法判定两个对象相等,那么其hash 方法也必须返回同 一个
值。但是,如果两个对象的hash 方法返回同一个值,那么“ isEqual:〞方法末必会认为两者 相 等 。

容器中可变类的等同性

当把某个对象放入collection后,就不应该再改变其哈希码了。因为collection会把各个对象按照其哈希码分装到不同的“箱子数组”中。如果某对象在放入“箱子”之后,哈希码又发生变化,那么其所处的这个箱子对他来说就是“错误”的。

要点

  • 若想检测对象的等同性,请提供“isEqual:”与hash方法。
  • 相同的对象必须具有相同的哈希码,但是两个哈希码相同的对象却未必相同。
  • 不要盲目地逐个检测每条属性,而是应该依照具体需求来指定检测方案。
  • 编写hash方法时,应该使用计算速度快而且哈希码碰撞几率低的算法。

九、以“类族模式”隐藏实现细节

类方法

“类族”是一种很有用的模式,可以隐藏“抽象基类”背后的实现细节。Objective-C的系统框架中普遍使用此模式。比奶,ios 的用户 界面框架(user interface framework)UIKit中就有一个名为UIButton的类。想创建按钮,需 要 调 用 下面 这 个 “ 类方法” ( class method )

+ (UIButton*)buttonWithType:(UIButtonType)type;

该方法所返回的对象,其类型取决于传人的按钮类型(button type)。然而,不管返回什 么类型的对象,它们都继承自同 一个基类:UIButton。这么做的意义在于:UIButton 类的使 用者无领关心创建出来的按钮具体属于哪个 子类,也不用考虑按钮的绘制方式等实现细节。 使用者只需明白如何创建按钮,如何设置像“标题” (title)这样的属性,如何增加触摸动作 的目标对象等问题就好。

创建类族:

首先要定义抽象基类,也就是一个新的类,其中可以包括你的类型选取,使用枚举器和switch语句来完成,并且还的定义你的类的相关方法,再创建一个新的类,继承你之前的类,并且完成之前的定义方法,使用覆盖的原理,完成这些方法。这种“工厂模式”是创建类族的办法之一。
如果你想创建的类中没有init初始化的方法,那么这就是在暗示你该类的实例也许不应该由用户直接创建。总而言之,以后创建对象一定不要被其的表象迷惑住了,你可能觉得自己创建了某个类的实例,然而实际上创建的却是其子类的实例。

//定义员工类型
typedef NS_ENUM(NSUInteger, EOCEmployeeType) {EOCEmployeeTypeDeveloper,EOCEmployeeTypeDesiner,EOCEmployeeTypeFinance
};@interface EOCEmployee : NSObject
//定义属性
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSUInteger salary;
//定义方法
+ (EOCEmployee *)employeeWithType:(EOCEmployeeType)type;- (void)doADaysWork;@end
@implementation EOCEmployee+ (EOCEmployee *)employeeWithType:(EOCEmployeeType)type {switch (type) {case EOCEmployeeTypeDeveloper:return [EOCEmployeeTypeDeveloper new];break;case EOCEmployeeTypeDesiner:return [EOCEmployeeTypeDesiner new];break;case EOCEmployeeTypeFinance:return [EOCEmployeeTypeFinance new];break;}
}- (void)doADaysWork {// Subclasses implement this.
}@end
@interface EOCEmployeeTypeDeveloper : EOCEmployee
@end@implementation  EOCEmployeeTypeDeveloper- (void)doADaysWork {[self writeCode];
}@end

Cocoa里的类族

系统框架中有许多类族,就用我们经常使用的NSArray和NSMutableArray来说,这样来看,它是两个抽象基类,但是他们两个拥有相同的方法,这个方法可能就是他们共同类族中的方法,而可变数组的特殊方法就是只适用于可变数组的方法,其他的共同方法可能就是类族中的方法。

在使用NSArray的alloc方法来获取实例时,该方法首先会分配一个属于某个类的实例,此实例充当一个“占位数组”,也就是说,你把这个位置是先分配给其类族的,后来其类族才将这个位置分配给你创建的具体数据类型的。
所以像这些类的背后其实是一个类族,在对一些if条件进行判断的时候一定要注意,例如:

id maybeAnArray = /* ... */;
if ([maybeAnArray class] == [NSArray class]) {//Will never be hit
}

使用这种方法来判断两个类是否属于同一类族很明显是错的

因为NSArray是一个类族,NSArray初始化返回的对象并非NSArray类,而是隐藏在类族公共接口中的某个内部类型

就像这段代码:
在这里插入图片描述

不过OC仍旧提供了判断实例所属的类 是否位于类族之中 isKindOfClass

id maybeAnArray = /*...*/
if(maybeAnArray isKindOfClass:[NSArray class]) {//will be hit
}

使用这种方法即可判断是否属于同一类族

手动增加实体子类的规则

  • 子类应该继承自类族中的抽象基类。
  • 子类应该定义自己的数据存储方式。
  • 子类应当覆写超类文档中指明需要覆写的方法。

要点总结

  • 类族模式可以把实现细节隐藏在一套简单的公共接口后面。
  • 系统框架中经常使用类族。(可变和不可变是最直接的例子)
  • 从类族的公共抽象基类中继承子类时要当心,若有开发文档,则应首先阅读。

十、在既有类中使用关联对象存放自定义数据

关联对象的出现

在iOS开发里,分类是不能添加成员变量的,只允许给分类添加属性,所以出现了关联对象

具体使用后面应该会学到,这里直接进行要点总结。

要点总结

  • 可以通过“关联对象”机制来把两个对象连起来。
  • 定义关联对象时可指定内存管理语义,用以模仿定义属性时所采用的“拥有关系”与“非拥有关系”。
  • 只有在其他做法不可行时才应选用关联对象,因为这种做法通常会引入难于查找的bug

十一、理解objc_msgSend的作用

对于C语言来说,静态绑定意味着在编译期就能决定运行时所需要调用的函数

以以下代码为例子:
在这里插入图片描述

编译器在编译代码的时候就已经知道程序中有 printflhelloprintGodbye这两个函数了,于是会直接生成调用这些函数的指令。而函数地址实际上是硬编码在指令之中

若是对于这段代码:
在这里插入图片描述
这时就得使用“动态绑定” (dynamicbinding)了,因为所要调用的函数直到运行期才能 确定。编译器在这种情况下生成的指令与刚才那个例子不同,在第一个例子中,其与else 语 句里都有函数调用指令。而在第二个例子中,只有 一个函数调用指令,不过待调用的函数地址无法硬编码在指令之中,而是要在运行期读取出来。

在OC中,如果向某对象传递消息,那就会使用动态绑定机制来决定需要调用的方法。在底层,所有的方法都是普通的C语言函数,然而对象收到消息之后究竟该调用那个方法则完全取决于运行期决定,甚至可以在程序运行时改变,这些特性使得OC成为一门真正的动态语言。

消息转发过程

来看下面一个对象发送消息:

id returnValue = [someObject messageName:parameter]

在这个例子中

someObject叫做“接收者”(receiver),messageName叫做 “选择子”(selector)。 选择子与参数合起来称为 “消息” (message)。编译器看到此消息后,将其转换为一条标准的 C语言两数调用,所调用的函数乃是消息传递机制中的核心两数,叫做objc_msgSend,其 “ 原型” ( prototype )如下:

void objc_msgsend(id self, SEL cmd, ...)

这是个“ 参数个数可变的两数” ( variadic function)。能接受两个或两个以上的参数。第一 个参数代表接收者,第二个参数代表选择子(SEL 是选择子的类型),后续参数就是消息中的 那些参数,其顺序不变。选择子指的就是方法的名字。“选择子〞与“方法” 这两个词经常 交替使用。编译器会把刚才那个例 子中的消息转换为如下函数:

id returnValue = objc msgSend(someobject, @selector (messageName:), parameter);

边界情况:
在这里插入图片描述

要点

  • 消息由接收者、选择子及参数构成。给某对象“发送消息”也就相当于在该对象上“调用方法”。
  • 发给某对象的全部消息都要由“动态消息派发系统”来处理,该系统会查出对应的方法,并执行其代码。

十二、理解消息转发机制

上一条强调了消息是如何传递下去的,这一条深入理解一下在某些出现问题的时刻系统是如何解决问题的。

消息转发

  • 某个对象收到了无法解读的消息之后会发生什么情况?这就是OC的消息转发机制。
  • 对于在编译期向类发出的无法解读的消息之后不会报错,因为可以在运行期继续向类里面添加方法,所以在编译时期出现了对象无法解读的消息就会启动消息转转发机制。
  • 消息转发分为两大阶段,第一阶段先征询接收者,所属的类,看其是否能动态添加方法,处理当前这个“未知的选择子”,这叫做“动态方法解析”。第二阶段涉及“完整的消息转发机制”。

动态方法解析

对象在收到无法解读的消息后,首先将调用其所属类的下列类方法:

+ (BOOL)resolveInstanceMethod:(SEL)selector

该方法的参数就是那个未知的选择子,其返回值为Boolean类型,表示这个类是否能新增一个实例方法用以处理此选择子。在继续往下执行转发机制之前,本类有机会新增一个处理此选择子的方法,假如尚未实现的方法不是实例方法而是类方法,那么运行期系统就会调用另一个方法,该方法与“resolveInstanceMethod:”类似,叫做 “resolveClassMethod”

动态方法解析的前提

对于上述的消息转发第一步,前提是我们相关的实现代码已经写好了,只需要等着运行时的时候插入类里面即可

以以下代码举例子:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>@interface MyClass : NSObject
@end@implementation MyClass// 动态方法解析
+ (BOOL)resolveInstanceMethod:(SEL)sel {if (sel == @selector(dynamicMethod)) {class_addMethod([self class], sel, (IMP)dynamicMethodImplementation, "v@:");return YES;}return [super resolveInstanceMethod:sel];
}void dynamicMethodImplementation(id self, SEL _cmd) {NSLog(@"Dynamic method has been resolved and called.");
}@endint main() {@autoreleasepool {MyClass *obj = [MyClass new];// 调用未实现的方法,触发动态方法解析[obj dynamicMethod];}return 0;
}

备援接受者

在第一步还是没有找到写好的方法之后,当前接受者还有第二次机会处理未知的选择子,在这一步里运行期的系统会询问接受者能不能找到其他接受者处理该消息,这里也有一个方法:

- (id)forwardingTargetForSelector:(SEL)selector;

方法参数代表未知的选择子,若当前接收者能找到各授对象,则将其返回,若找不到, 就 返 回 nil。 通过此方案,我们可以用“组合”(composition) 来模拟出“多重继承”( multipleinheritance )的某些特性。在一个对象内部,可能还有一系列其他对象,该对象可经由此方法 将能够处理某选择 子的相关内部对象返回,这样的话,在外界看来,好像是该对象亲自处理了这些消息似的。
请注意,我们无法操作经由这 一步所转发的消息。若是想在发送给备援接收者之前先修改消息内容,那就得通过完整的消息转发机制来做了。

例子:

#import <Foundation/Foundation.h>// 定义备用接收者对象
@interface AnotherObject : NSObject
- (void)anotherMethod;
@end@implementation AnotherObject- (void)anotherMethod {NSLog(@"Method implemented in AnotherObject.");
}@end// 主要对象
@interface MyClass : NSObject
@end@implementation MyClass// 备用接收者
- (id)forwardingTargetForSelector:(SEL)aSelector {if (aSelector == @selector(anotherMethod)) {return [AnotherObject new];}return [super forwardingTargetForSelector:aSelector];
}@endint main() {@autoreleasepool {MyClass *obj = [MyClass new];// 调用未实现的方法,触发备用接收者[obj performSelector:@selector(anotherMethod)];}return 0;
}

在这个方法中,当选择器为 @selector(anotherMethod) 时,返回了 AnotherObject 的实例。因此,当你在 MyClass 对象上调用 anotherMethod 时,实际上是调用了 AnotherObject 的实例上的方法。

完整的消息转发

如果转发算法已经来到这一步的话,那么唯一能做的就是启用完整的消息转发机制 了。首先创建NSInvocation 对象,把与尚未处理的那条消息有关的全部细节都封于其中。 此对象包含选择子、目标(target )及参数。在触发NSInvocation 对象时,“ 消息派发系统”( message-dispatchsystem )将亲自出马,把消息指派给目标对象。

此步骤会调用 下列方法来转发消息:

 - (void)forwardInvocation:(NSInvocation*)invocation
  • 这个方法的实现方式有2种,一种是只需要改变调用目标,和备援接受者方法实现的等效,第二种则是在触发消息前,先以某种方式改变消息内容,比如追加另一个参数,或者改变选择子等
  • 实现此方法时,若发现某调用操作不应由本类来处理,则需调用超类的同名方法。这样的话,继承体系中的每个类都有机会处理此调用请求,直至NSObject。如果最后调用NSObject类的方法,那么该方法还会继而调用“doesNotRecognizeSelector:”以抛出异常,此异常表明选择子最终未能得到处理。

消息转发全流程

在这里插入图片描述

要点

  • 若对象无法响应某个选择子,则进人消息转发流程。
  • 通过运行期的动态方法解析功能,我们可以在需要用到某个方法时再将其加人类中。
  • 对象可以把其无法解读的某些选择子转交给其他对象来处理。
  • 经过上述两步之后,如果还是没办法处理选择 子,那就启动完整的消息转发机制。

十四、理解“类对象”的用意

1.id类型:

一般情况下,应该指明消息接收者的具体类型,这样的话,如果向其发送了无法解读的消息,那么编译器就会产生警告信息。而类型为id的对象则不然,编译器假定它能响应所有信息。
每个OC对象实例都是指向某块内存数据的指针,所以在声明变量时,类型后面要跟一个*字符。

描述Objective-C对象所用的数据结构定义在运行期程序库的头文件里,id类型本身也在定义在这里:

typedef struct objc_object {Class isa;
} *id;

2.Class对象:
在这里插入图片描述

typedef struct objc_class *Class;
struct objc_class {Class isa;Class super_class;const char *name;long version;long info;long instance_size;struct objc_ivar_list *ivars;struct objc_method_list **methodLists;struct objc_cache *cache;struct objc_protocol_list *protocols;
};

此结构体存放类的“元数据”。其中的super_class,它定义了本类的父类。类对象所属的类型(也就是isa指针所指向的类型)是另一个类,叫做“元类”(metaclass)。并且每个类仅有一个“类对象”,而每个“类对象”仅有一个与之相关的“元类”。

这里注意isa是元类,而super_class是父类在这里插入图片描述

而对于isMemberOfClassisKindOfClass在此博客【iOS】类、元类、父类的关系已经讲述,不再赘述

要点:

  • 每个实例都有一个指向Class对象的指针,用以表明其类型,而这些Class对象则构成了类的继承体系。

  • 如果对象类型无法在编译期确定,那么就应该使用类型信息查询方法来探知。

  • 尽量使用类型信息查询方法来确定对象类型,而不要直接比较类对象,因为某些对象可能实现了消息转发功能。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Hive-架构与设计

架构与设计 一、背景和起源二、框架概述1.设计特点 三、架构图1.UI交互层2.Driver驱动层3.Compiler4.Metastore5.Execution Engine 四、执行流程1.发起请求2.获取执行计划3.获取元数据4.返回元数据5.返回执行计划6.运行执行计划7.运行结果获取 五、数据模型1.DataBase数据库2.T…

Java并发基础:LinkedBlockingDeque全面解析!

内容概要 LinkedBlockingDeque提供了线程安全的双端队列实现&#xff0c;它支持在队列两端高效地进行插入和移除操作&#xff0c;同时具备阻塞功能&#xff0c;能够很好地协调生产者与消费者之间的速度差异&#xff0c;其内部基于链表结构&#xff0c;使得并发性能优异&#x…

【开源项目阅读】Java爬虫抓取豆瓣图书信息

原项目链接 Java爬虫抓取豆瓣图书信息 本地运行 运行过程 另建项目&#xff0c;把四个源代码文件拷贝到自己的包下面 在代码爆红处按ALTENTER自动导入maven依赖 直接运行Main.main方法&#xff0c;启动项目 运行结果 在本地磁盘上生成三个xml文件 其中的内容即位爬取…

Stable Diffusion 模型下载:RealCartoon-Realistic - V13

文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八案例九案例十下载地址模型介绍 该检查点是 RealCartoon3D 检查点的一个分支。这个目标是在背景和人物中产生更“真实”的外观。我试图避免这个模型中更多的动漫、卡通和“完美”外观。这是一个肯

【算法】排序详解(快速排序,堆排序,归并排序,插入排序,希尔排序,选择排序,冒泡排序)

目录 排序的概念&#xff1a; 排序算法的实现&#xff1a; 插入排序&#xff1a; 希尔排序&#xff1a; 选择排序&#xff1a; 堆排序&#xff1a; 冒泡排序&#xff1a; 快速排序&#xff1a; 快速排序的基本框架&#xff1a; 1.Hoare法 2. 挖坑法 3.前后指针法 快…

优质项目追踪平台一览:助力项目管理与监控

项目追踪平台是现代项目管理中不可或缺的工具&#xff0c;它可以帮助团队高效地跟踪和管理项目进度、任务和资源分配。在当今快节奏的商业环境中&#xff0c;有许多热门的项目追踪平台可供选择。 本文总结了当下热门的项目追踪平台&#xff0c;供您参考~ 1、Zoho Projects&…

【Vue】Vue基础入门

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;Vue ⛺️稳重求进&#xff0c;晒太阳 Vue概念 是一个用于构建用户界面的渐进式框架优点&#xff1a;大大提高开发效率缺点&#xff1a;需要理解记忆规则 创建Vue实例 步骤&#xff1a; …

SpringBoot-基础篇03

之前搭建了整个开发环境实现了登录注册&#xff0c;springBoot整合mybatis完成增删改查&#xff0c;今天完成分页查询&#xff0c;使用阿里云oss存储照片等资源&#xff0c;后期会尝试自己搭建分布式文件系统来实现。 一&#xff0c;SpringBootMybatis完成分页查询 1&#xff…

npm 上传一个自己的应用(3) 在项目中导入及使用自己上传到NPM的工具

上文 npm 上传一个自己的应用(2) 创建一个JavaScript函数 并发布到NPM 我们创建了一个函数 并发上了npm 最后 我们这里 我们可以看到它的安装指令 这里 我们可以打开一个vue项目 终端输入 我们的安装指令 npm i 自己的包 如下代码 npm i grtest我们在 node_modules目录 下…

【Linux】构建模块

&#x1f525;博客主页&#xff1a;PannLZ &#x1f38b;系列专栏&#xff1a;《Linux系统之路》 &#x1f94a;不要让自己再留有遗憾&#xff0c;加油吧&#xff01; 文章目录 构建第一个模块1模块的makefile2内核树内构建3内核树外构建 构建第一个模块 可以在两个地方构建模…

【Spring】Spring 对 Ioc 的实现

一、Ioc 控制反转 控制反转是一种思想 控制反转是为了降低程序耦合度&#xff0c;提高程序扩展力&#xff0c;达到 OCP 原则&#xff0c;达到 DIP 原则 控制反转&#xff0c;反转的是什么&#xff1f; 将对象的创建权利交出去&#xff0c;交给第三方容器负责 将对象和对象之…

Windows下搭建Redis Sentinel

下载安装程序 下载Redis关于Windows安装程序&#xff0c;下载地址 下载成功后进行解压&#xff0c;解压如下&#xff1a; 配置redis和sentinel 首先复制三份redis.windows.conf&#xff0c;分别命名为&#xff1a;redis.6379.conf、redis.6380.conf、redis.6381.conf&…

C++笔记之regex(正则表达式)

C++笔记之regex(正则表达式) ——2024-02-10 ——《C++标准库》(第2版,侯捷译) Page 717 code review! 文章目录 C++笔记之regex(正则表达式)例1:使用正则表达式进行搜索(`std::regex_search`)例2:使用正则表达式进行全文匹配(`std::regex_match`)例3:使用正则表达式…

01-Spring实现重试和降级机制

主要用于在模块调用中&#xff0c;出现失败、异常情况下&#xff0c;仍需要进行重复调用。并且在最终调用失败时&#xff0c;可以采用降级措施&#xff0c;返回一般结果。 1、重试机制 我们采用spring 提供的retry 插件&#xff0c;其原理采用aop机制&#xff0c;所以需要额外…

分享3款开源免费好用的Docker可视化管理工具安装部署教程

文章目录 1.前言2.Docker Desktop3.Portainer3.1 Portainer默认英文版本安装3.2 Portainer汉化版本安装3.3官方镜像说明3.3.1ssl访问3.3.2Nginx反代3.3.3Nginx反代设置子目录3.3.4docker-compose部署 3.4登录 4.DockerUI4.1简介4.2项目地址4.3部署启动命令4.4登录4.5首页 5.总结…

vue day06

1、路由模块封装 2、声明式导航 实现导航高亮效果 直接通过这两个类名对相应标签设置样式 点击a链接进入my页面时&#xff0c;a链接 我的音乐高亮&#xff0c;同时my下的a、b页面中的 我的音乐也有router-link-active类&#xff0c;但没有精确匹配的类&#xff08;只有my页…

Android 粒子喷泉动效

一、前言&#xff1a; 在学习open gl es实现动效的时候&#xff0c;打算回顾了一下用普通的2D坐标系实现粒子效果和 open gl 3d 坐标系的区别&#xff0c;以及难易程度&#xff0c;因此本篇以Canvas 2D坐标系实现了一个简单的demo。 粒子动效原理&#xff1a; 粒子动效本质上…

读完《王志纲谈生涯规划》后感

(点击即可收听) 经常在短视频刷到,这位王志钢老师,在微信读书里面也看到过,于是拜读了一下,这是一本生涯规划书,但更多的是他个人经历的一个描述 有大道理&#xff0c;有些话还是值得认可的 比如&#xff1a;他谈到,想要减少个人乃至社会的悲剧&#xff0c;最好的办法就是尽自己…

svg基础(八)滤镜-feTurbulence(湍流)

feTurbulence&#xff1a;湍流滤镜 湍流滤镜&#xff0c;不稳定气流&#xff0c;能够实现半透明的烟熏或波状图像。 通常用于实现一些特殊的纹理。滤镜利用 Perlin 噪声函数创建了一个图像。噪声在模拟云雾效果时非常有用&#xff0c;能产生非常复杂的质感&#xff0c;利用它可…

【Unity】QFramework通用背包系统优化:使用Odin优化编辑器

前言 在学习凉鞋老师的课程《QFramework系统设计&#xff1a;通用背包系统》第四章时&#xff0c;笔者使用了Odin插件&#xff0c;对Item和ItemDatabase的SO文件进行了一些优化&#xff0c;使物品页面更加紧凑、更易拓展。 核心逻辑和功能没有改动&#xff0c;整体代码量减少…