iOS-@inerface的11条规范写法
总结一些interface声明时的规范,相关宏的介绍,定义方法时有用的修饰符,编写注释的规范,最终写出一个合格的头文件。
- 1.读写权限
- 1.1实例变量的@public,@protected,@private关键字
- 1.2属性的readonly,readwrite关键字
- 2.前向声明
- 3.只暴露必要的接口和实现
- 3.1不要暴露任何只在类内部使用的私有方法
- 3.2不要在头文件里声明类内部遵循的protocol
- 4.nullability说明
- 5.定义枚举
- 5.1 NS_ENUM
- 5.2 NS_OPTIONS
- 5.3 字符串枚举
- 6.使用extern向外部提供只读常量
- 7.向子类和category提供父类的私有属性
- 8.标明designated initializer
- 9.API版本控制
- 9.1 available
- 9.2 unavailable
- 9.3 deprecated
- 10.额外的修饰符
- 10.1泛型
- 10.2 NS_REQUIRES_SUPER
- 10.3 NS_NOESCAPE
- 11.写注释
- 11.1单行注释
- 11.2多行注释
- 11.3枚举注释
- 11.4几个注释约定
1.读写权限
.h文件里的声明是用于暴露给外部的接口,而类内部的私有方法、私有属性和实例变量,应该放到.m文件的interface extension里。
1.1 实例变量的@public,@protected,@private关键字
这3个关键字用于修饰实例变量,不能用于修饰属性。当错误地使用了实例变量时,Xcode会报错提示。
关键字 | 说明 |
---|---|
@private | 作用范围只能在自身类 |
@protected | 作用范围在自身类和继承自己的子类,什么都不写,默认是此属性。 |
@public | 作用范围最大,在任何地方。 |
示例代码:
//SearchManager.h
@interface SearchManager : NSObject {@public NSInteger *state;@public NSInteger *timeout;@protected id *searchAPI;@private id _privateIvar;
}
@end
由于会暴露私有变量,并且没有@property的一些高级关键字,很少在头文件里声明实例变量。优先使用@property。
1.2 属性的readonly,readwrite关键字
头文件中的属性是用于描述这个对象的一系列特性集合。 声明@property时,在.h里使用readonly,让外部只有读的权限,在.m里使用readwrite,使内部拥有读写权限。
示例代码:
//SearchManager.h
@interface SearchManager : NSObject
@property (nonatomic, readonly) NSInteger * state;
@end
//SearchManager.m
@interface SearchManager : NSObject
@property (nonatomic, readwrite) NSInteger * state;
@end
2.前向声明
当在@interface的接口里用到了其他类,不要在.h里直接导入类的头文件,这样会让使用此头文件的地方也导入这些不必要的其他头文件。正确的做法是使用关键字@class
进行前向声明。当然,如果是继承了父类,还是需要import父类的头文件。 示例代码:
//SearchManager.h
#import "SearchManagerBase.h"//导入父类的头文件@class LocationModel;//前向声明LocationModel类typedef void(^LocationSearchCompletionHandler)(LocationModel *location, NSError *error);
@interface LocationSearchManager : SearchManagerBase
- (void)searchLocationWithKeyword:(NSString *)keyword completionHandler:(LocationSearchCompletionHandler)completionHandler;
@end
使用@class会告诉编译器有这么一个类存在,但是现在并不关心这个类的具体实现,等到调用者在.m里使用的时候再import这个类即可。使用@class和@protocol分别声明一个类和一个protocol。 使用前向引用的原因有两个:
- 提升编译效率。 如果import了
LocationModel.h
,那么当LocationModel.h
的内容发生变化时,所有import了LocationModel.h
的地方都需要重新编译。如果.m引用了SearchManager.h
,但是并没有使用LocationModel
,就会增加不必要的编译,降低开发效率。 - 解决交叉引用的问题。 如果类A的头文件import了B,类B的头文件import了A,这样在编译时会报错:“can not find interface declaration”,这是因为Objective-C不允许交叉引用。
3.只暴露必要的接口和实现
3.1不要暴露任何只在类内部使用的私有方法
头文件里只声明那些给外部使用的公开方法,并且在设计时需要考虑到可测试性,遵循单一职责。 私有方法只定义在类内部,并且为了进行区别,建议在私有方法前加上前缀,例如- (void)p_myPrivateMethod
。 由于Apple在它的编码规范里声明了,Apple公司拥有下划线的方法前缀,就像它拥有NS
,UI
这些类名前缀一样,因此不建议我们的私有方法直接使用下划线作为前缀。否则,当你在继承Cocoa Touch的类时,有可能会覆盖父类的私有方法,造成难以调试的错误。
3.2不要在头文件里声明类内部遵循的protocol
错误的示例代码:
//SearchManager.h
@interface SearchManager : NSObject<NSCoding, UITableViewDelegate>
@property (nonatomic, readonly) NSInteger * state;
@end
UITableViewDelegate
是类内部使用时遵循的protocol,没有必要暴露给外部,因此应该放到.m文件里。 而NSCoding
则描述了类的特性,用于告诉外部本类可以使用归档,因此应该放在头文件里。
4.nullability说明
在声明时,可以使用下列关键字描述对象是否可以为nil。
关键字 | 说明 |
---|---|
nullable | 可空,用于描述objc对象 |
nonnull | 不可空,用于描述objc对象 |
null_unspecified | 不确定,用于描述objc对象 |
null_resettable | set可空,get不为空。仅用于property |
_Nullable | 可空,用于描述C指针和block |
_Nonnull | 不可空,用于描述C指针和block |
_Null_unspecified | 不确定,用于描述C指针和block |
示例代码:
//SearchManager.h
#import "SearchManagerBase.h"
@class LocationModel;typedef void(^LocationSearchCompletionHandler)(LocationModel *_Nullable location, NSError *_Nullable error);
@interface LocationSearchManager : SearchManagerBase
- (void)searchLocationWithKeyword:(nonnull NSString *)keyword completionHandler:(LocationSearchCompletionHandler _Nonnull)completionHandler;
@end
如果向一个使用nonnull修饰的值赋空,编译器会给出警告。 在开发时,大部分时候使用的都是nonnull,因此Apple提供了一对宏NS_ASSUME_NONNULL_BEGIN
和NS_ASSUME_NONNULL_END
来进行快速修饰,写在两个宏之间的属性、方法,均会使用nonnull
修饰。 示例代码:
//LocationSearchManager.h#import "SearchManagerBase.h"
@class LocationModel;NS_ASSUME_NONNULL_BEGIN
typedef void(^LocationSearchCompletionHandler)(LocationModel *_Nullable location, NSError *_Nullable error);
@interface LocationSearchManager : SearchManagerBase
- (void)searchLocationWithKeyword:(NSString *)keyword completionHandler:(LocationSearchCompletionHandler)completionHandler;
@end
NS_ASSUME_NONNULL_END
5.定义枚举
关于NS_ENUM和NS_OPTIONS的区别,参考这里。 简单来说,NS_OPTIONS提供了按位掩码的功能。
5.1 NS_ENUM
示例代码:
typedef NS_ENUM(NSInteger,SearchState) {SearchStateNotSearch,SearchStateSearching,SearchStateSearchFinished,SearchStateSearchFailed
};
5.2 NS_OPTIONS
示例代码,参考NSKeyValueObserving.h
:
typedef NS_OPTIONS(NSUInteger, NSKeyValueObservingOptions) {NSKeyValueObservingOptionNew,NSKeyValueObservingOptionOld,NSKeyValueObservingOptionInitial,NSKeyValueObservingOptionPrior
};
在使用时就可以用|
组合多个option:
[_webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:NULL];
5.3 字符串枚举
当使用字典作为参数传递,或者作为返回值时,往往难以直接提供字典的key,现在使用字符串枚举即可解决这个问题。 示例代码,参考NSKeyValueObserving.h
:
//使用NS_STRING_ENUM宏,定义了一个枚举类型
typedef NSString * NSKeyValueChangeKey NS_STRING_ENUM;FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeKindKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeNewKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeOldKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeIndexesKey;
FOUNDATION_EXPORT NSKeyValueChangeKey const NSKeyValueChangeNotificationIsPriorKey;//使用泛型,声明了change参数用到的key,是在NSKeyValueChangeKey的枚举范围中
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context;
6.使用extern向外部提供只读常量
这不关@interface的事,但是和头文件有关,就放在一起说明了。
//SearchManager.h
extern NSString *const SearchErrorDomain;
extern NSInteger SearchDefaultTimeout;@interface SearchManager : NSObject
@end
//SearchManager.m
NSString *const SearchErrorDomain = @"SearchErrorDomain";
const NSInteger SearchDefaultTimeout = 20;@interface SearchManager()
@end
7.向子类和category提供父类的私有属性
由于类的头文件只存放那些暴露给外部的属性和方法,在遇到这些情况时,会遇到障碍:
- 在子类里或者category里,想要使用父类定义在.m里的私有属性。
- 在类的头文件里属性是readonly,但是在子类或者category里,需要readwrite权限。 由于这些属性并没有暴露在头文件里,因此需要另外建立一个私有头文件,用来存放这些需要暴露给子类和category的属性。 可以参考Apple官方的
UIGestureRecognizerSubclass.h
。 示例代码:
//SearchManager.h
@interface SearchManager : NSObject
///外部访问,只有读权限
@property (nonatomic, readonly) SearchState state;
@end
//SearchManager.m
@interface SearchManager()
///内部使用,有读写权限
@property (nonatomic, assign) SearchState state;
///只在内部使用的私有属性
@property (nonatomic, strong) id searchAPI;
@end
///暴露给子类和category的私有属性和私有方法
//SearchManagerInternal.h
///限制使用此头文件,防止被别的类误用
#ifdef SEARCHMANAGER_PROTECTED_ACCESS#import "SearchManager.h"
@interface SearchManager()
///在internal.h里,重新声明为readwrite权限
@property (nonatomic, readwrite, assign) SearchState state;
///暴露私有属性
@property (nonatomic, strong) id searchAPI;
///暴露私有方法
- (void)p_privateMethod;
@end#else
#error Only be included by SearchManager's subclass or category!
#endif
///category的实现文件
//SearchManager+Category.m
///声明私有头文件的使用权限
#define SEARCHMANAGER_PROTECTED_ACCESS
///导入私有头文件
#import "SearchManagerInternal.h"@implementation SearchManager(Category)
- (void)categoryMethod {//拥有了读写权限self.state = SearchStateSearching;//可以访问私有属性[self.searchAPI startSearch];//可以使用私有方法[self p_privateMethod];
}
@end
SearchManagerInternal.h
其实也是公开的,其他类也能够导入并使用,只能在开发时进行约定。如果想要限制其他类导入,并且提示错误,Internal.h
可以使用如下方式:
#ifdef MYCLASS_PROTECTED_ACCESS
//声明部分
#else
#error Only be included by MYCLASS's subclass or category!
#endif
这样在别的类内意外地导入了Internal.h
时就会产生编译警告,并且无法直接使用。缺点是需要在所有使用到Internal.h
的地方都#define MYCLASS_PROTECTED_ACCESS
。
8.标明designated initializer
指定初始化方法,即接收参数最多的那个初始化方法,其他初始化方法调用它即可,这样设计的目的是为了保证所有初始化方法都正确地初始化实例变量。 在方法后面加上NS_DESIGNATED_INITIALIZER
宏即可。这样,当你子类化这个类时,在子类的初始化方法里如果没有正确地调用父类的designated initializer,编译器就会给出警告。 实例代码:
@interface WKWebView : UIView
- (instancetype)initWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;
@end
关于designated initializer更详细的说明,参考:
- Objective-C 拾遗:designated initializer
- 正确编写Designated Initializer的几个原则
9.API版本控制
在更新接口,或者开发framework时,需要标明版本信息,告诉使用者此接口的平台限制、操作系统版本、是否可用、是否已弃用等。 苹果给出了几个自带的宏用于标明版本,Xcode在检测到错误使用时会给出警告。只需要在方法名后面加上对应的宏即可。
9.1 available
声明本接口最低支持的操作系统版本。 当你的接口使用了新系统的API,例如iOS8以上才有的UIAlertController,但是项目的deployment target却是iOS7时,需要标明此接口的版本信息,让使用者进行兼容。 示例:
//SearchManager.htypedef NS_ENUM(NSInteger,SearchState) {SearchStateNotSearch,SearchStateSearching,SearchStateSearchFinished,SearchStateSearchFailed
} NS_ENUM_AVAILABLE_IOS(2_0);//此枚举在iOS2.0以上才能使用NS_CLASS_AVAILABLE_IOS(2_0) //此类在iOS2.0以上才能使用
@interface SearchManager : NSObject
- (void)reSearch NS_AVAILABLE_IOS(5_0);//此方法在iOS5.0以上才能使用
@end
这几个宏有对应平台的版本,例如NS_AVAILABLE_MAC, NS_AVAILABLE_IOS, NS_AVAILABLE_IPHONE。 iOS10开始提供了新的available宏API_AVAILABLE
,用来统一macOS、iOS、watchOS、tvOS几个平台。
API_AVAILABLE(macos(10.10))
API_AVAILABLE(macos(10.9), ios(10.0))
API_AVAILABLE(macos(10.4), ios(8.0), watchos(2.0), tvos(10.0))
9.2 unavailable
声明此接口不可用,大多数时候是用于声明所在平台限制。 示例:
@interface SearchManager : NSObject
- (void)searchInWatch NS_UNAVAILABLE;//不能用此接口
- (void)searchInHostApp NS_EXTENSION_UNAVAILABLE_IOS;//extension里不能用此接口
- (void)search __TVOS_PROHIBITED;//tvOS里不能用此接口,可修饰枚举,类,方法,参数
@end
iOS10开始提供了新的unavailable宏API_UNAVAILABLE
:
API_UNAVAILABLE(macos)
API_UNAVAILABLE(watchos, tvos)
9.3 deprecated
声明此接口已经被弃用,可以同时加注释注明替代接口。 当deployment target版本号设置成大于或等于方法被弃用的版本号时,Xcode会给出警告。 示例:
//注明废弃类
NS_CLASS_DEPRECATED_IOS(2_0, 9_0, "UIAlertView is deprecated. Use UIAlertController with a preferredStyle of UIAlertControllerStyleAlert instead")
@interface UIAlertView : UIView
@end
//注明废弃API
@interface UIViewController : UIResponder
- (void)viewDidUnload NS_DEPRECATED_IOS(3_0,6_0);
@end
//注明废弃枚举
typedef NS_ENUM(NSInteger, UIStatusBarStyle) {UIStatusBarStyleDefault = 0, // Dark content, for use on light backgroundsUIStatusBarStyleLightContent NS_ENUM_AVAILABLE_IOS(7_0) = 1, // Light content, for use on dark backgroundsUIStatusBarStyleBlackTranslucent NS_ENUM_DEPRECATED_IOS(2_0, 7_0, "Use UIStatusBarStyleLightContent") = 1,UIStatusBarStyleBlackOpaque NS_ENUM_DEPRECATED_IOS(2_0, 7_0, "Use UIStatusBarStyleLightContent") = 2,
}
iOS10开始提供了新的deprecated宏API_DEPRECATED
和API_DEPRECATED_WITH_REPLACEMENT
。前者可以注明弃用原因,后者可以注明替代接口。
API_DEPRECATED("No longer supported", macos(10.4, 10.8))
API_DEPRECATED("No longer supported", macos(10.4, 10.8), ios(2.0, 3.0), watchos(2.0, 3.0), tvos(9.0, 10.0))API_DEPRECATED_WITH_REPLACEMENT("-setName:", tvos(10.0, 10.4), ios(9.0, 10.0))
API_DEPRECATED_WITH_REPLACEMENT("SomeClassName", macos(10.4, 10.6), watchos(2.0, 3.0))
10.额外的修饰符
10.1 泛型
在声明时,对集合类型的对象增加泛型的修饰,就可以声明集合内存储的数据类型。 例如:
@property (nonatomic, strong) NSMutableArray<NSString *> *myArray;
当你向myArray
里放入一个非NSString *
类型的对象时,编译器会给出警告。
@property(nonatomic, strong) NSMutableArray<__kindof UIView *> * viewArray;
_kindof
只限定了存储类型为UIView
,因此也可以存储UIView
的子类,例如UIButton
。 更详细的介绍,参考:Objective—C语言的新魅力——Nullability、泛型集合与类型延拓
10.2 NS_REQUIRES_SUPER
NS_REQUIRES_SUPER
宏用于声明子类在重载父类的这个方法时,需要调用父类的方法。例如:
- (void)viewWillAppear:(BOOL)animated NS_REQUIRES_SUPER;
10.3 NS_NOESCAPE
NS_NOESCAPE
用于修饰方法中的block类型参数,例如:
@interface NSArray: NSObject
- (NSArray *)sortedArrayUsingComparator:(NSComparator NS_NOESCAPE)cmptr
@end
作用是告诉编译器,cmptr
这个block在sortedArrayUsingComparator:
方法返回之前就会执行完毕,而不是被保存起来在之后的某个时候再执行。 类似于这样的实现:
- (void)performWithLock:(NS_NOESCAPE void (^)())block { // exposed as @noescape to Swift[myLock lock];block();[myLock unlock];
}
编译器知道之后,就会相应地做一些优化,例如去掉一些多余的对self
的捕获、retain、release操作。因为block的存活范围仅限于本方法内,没有必要再在block内保留self
了。 更详细的介绍,参考这里。
11.写注释
头文件就是文档,需要让使用者快速知道这个类的作用。一个好的方法名可以让使用者快速理解,但大部分时候还是需要相应的注释。 写好格式化注释后,当光标停留在方法名和属性上时,在Xcode右侧的Quick Help栏里会出现注释内容,按住option
并单击,也会弹出注释框。
11.1单行注释
直接在方法或者属性声明的上一行使用///
,后面加注释,同时兼容Xcode和appleDoc。Xcode也支持//!
,但是appleDoc不支持。
//SearchManagerBase.h///搜索manager的基类
@interface SearchManagerBase : NSObject
///搜索状态
@property (nonatomic, readonly) NSInteger * state;
@end
11.2多行注释
多行注释使用:
/**注释内容
*/
Xcode8提供了快速生成格式化注释的快捷键:option
+command
+/
。如果方法有参数,会自动添加@param关键字,用于描述对应的参数。 Apple提供了官方的headDoc语法,但是很多都已经在Xcode中失效了,而且有些关键字也和appleDoc不兼容。下面几种列举出了在Xcode中仍然有效的一些关键字:
/**演示苹果headDoc的语法。这里可以写方法简介@brief 方法的简介(appleDoc不支持此关键字)@discussion 方法的详细说明@code //示例代码(这个在Xcode里常用,但是appleDoc不支持此关键字)UIView *view;@endcode@bug 存在的bug的说明@note 需要注意的提示@warning 警告@since iOS7.0@exception 方法会抛出的异常的说明@attention 注意,从这里开始往下的关键字,appleDoc都不支持@author 编写者@copyright 版权@date 日期@invariant 不变量@post 后置条件@pre 前置条件@remarks 备注@todo todo text@version 版本*/
- (void)sampleMethod;
在Xcode中,就会显示为这样:
11.3 枚举注释
如果要给枚举注释,需要在每个枚举值前注释,按照如下格式:
///搜索状态
typedef NS_ENUM(NSInteger,SearchState) {///没有开始搜索SearchStateNotSearch,///搜索中SearchStateSearching,///搜索结束SearchStateSearchFinished,///搜索失败SearchStateSearchFailed
};
11.4 几个注释约定
需要注释的内容:
- 尽量为类添加描述,即便只有一句话。
- 标明某些参数和属性的默认值,比如超时time。
- 如果属性是KVO兼容的,即外部可以使用KVO监听此属性,则在属性注释里声明。
- 回调block参数需要说明回调所在的线程,避免让使用者在block里进行多余的线程判断。
- 如果需要的话,说明使用此API需要的前置条件,防止被错误地调用。
- 对使用了method swizzling的API进行统一形式的标注,方便遇到runtime的bug时进行排查。
参考
- blog.sunnyxx.com/2014/04/13/…
- blog.sunnyxx.com/2015/06/12/…
转载于:https://www.cnblogs.com/LifeTechnologySupporter/p/8848610.html
iOS-@inerface的11条规范写法相关推荐
- 新增11条新规约,阿里Java开发手册|黄山版,拥抱规范,远离伤害
前言 阿里开发手册是阿里近万名开发同学集体智慧的结晶,以开发视角为中心,详细列举如何开发更加高效.更加容错.更加有协作性,力求知其然,更知其不然,结合正反例,让Java开发者能够提升协作效率.提高代码 ...
- Kubernetes管理员的11条 安全军规
自从Kubernetes项目开天辟地以来, 其安全性已经取得了长足的发展, 但它目前依然还有些要点值得注意. 本文列举了11条军规来帮助让你的集群在稳定运行时加固安全,以及在受到危害时对抗冲击.这些军 ...
- 优秀程序员写代码一定会用的 11 条经验!
这是一篇值得收藏起来,隔三差五就拿来重读的文章!因为作者向你保证,他"遇到的所有糟糕的代码,都是因为没采纳这些实践经验.而任何一段优秀的代码,都采纳了至少部分实践经验." 还等什么 ...
- 一位总经理谈11条管理错误
在管理工作中,有些传统的做法是错误的,我们要避免犯这些错误.以下这11条都是常犯之错. 1 拒绝承担个人责任 有一次,有一项工作出了差错,董事长把我叫去骂了一顿.我对董事长说,"这是我的错! ...
- 告别狗屎代码,请记住这 11 条编码秘诀!
这是一篇值得收藏起来,隔三差五就拿来重读的文章!因为作者向你保证,他"遇到的所有糟糕的代码,都是因为没采纳这些实践经验.而任何一段优秀的代码,都采纳了至少部分实践经验." 还等什么 ...
- 福利 | 如何创造可信的AI?人工智能大牛盖瑞·马库斯的11条建议
文末有福利哦~ (本文来自湛庐文化策划出版新书<如何创造可信的AI>,作者盖瑞·马库斯(Gary Marcus ) 欧内斯特·戴维斯(Ernest Davis)) 在人工智能领域中,深度 ...
- Xamarin iOS教程之进度条和滚动视图
Xamarin iOS教程之进度条和滚动视图 Xamarin iOS 进度条 进度条可以看到每一项任务现在的状态.例如在下载的应用程序中有进度条,用户可以很方便的看到当前程序下载了多少,还剩下多少.Q ...
- 优秀程序员写代码一定会用的 11 条经验
这是一篇值得收藏起来,隔三差五就拿来重读的文章!因为作者向你保证,他"遇到的所有糟糕的代码,都是因为没采纳这些实践经验.而任何一段优秀的代码,都采纳了至少部分实践经验." 还等什么 ...
- pgsql 前10条_白沙湾南片区11条新建道路最新进度及建成时间,已建成一条!还有一条将通车...
原标题:白沙湾南片区11条新建道路最新进度及建成时间,已建成一条!还有一条将通车 近日,@白沙河畔 从流亭街道办事处获悉:为了完善白沙湾南片区及仙家寨南片区路网及基础设施配套,为周边区域提供良好的生活 ...
- 11条建议让你成为优秀的JavaScript程序员
原文: 11条建议让你成为优秀的JavaScript程序员 每个编程语言都有他自己的个性,那么我们如何高效地掌握JavaScript呢?本 JavaScript教程 给你11条编程建议,让你成为优秀的 ...
最新文章
- 【问链-EOS公开课】第十四课 EOS从单签名到多签名
- JAVA API 中文文档 下载
- Python这门语言为什么适合初学者?88.7%的小白听了会感谢选择它
- leetcode 743. Network Delay Time | 743. 网络延迟时间(邻接矩阵,Dijkstra 算法)
- illegal to have multiple occurrences of contentType with different values 解决
- excel超级工具箱_这6个Excel高效办公插件,你都用过吗?
- 怎么在gif表情包上添加文字?
- python复合赋值语句语法_复合结构的赋值语句理解
- privilege权限级别的命令介绍及实例分析
- sql服务器显示空白,sql服务器空白
- 见过世面的程序员,到底有多厉害
- 串口转以太网与监控软件modbusTCP客户端通信配置
- Python 3.x 安装 web.py ,血泪!!
- android 锁屏音乐控制
- 华为P30销量破千万有多少含金量?
- Web应用程序 [/XXX_war_exploded] 注册了JDBC驱动程序 [com.mysql.cj.jdbc.Driver],但在Web应用程序停止时无法注销它。
- 黑苹果NVIDIA显卡驱动程序【 WebDriver-378.10.10.10.20.108+支持macOS 10.13.1 High Sierra (17B1002)版本】
- 给你的应用程序添加动态鼠标
- 京东云开发者|IoT运维 - 如何部署一套高可用K8S集群
- 网络电视显示未找到服务器请检查线路,有线电视无信号请检查线路是怎么回事...