如何使用Objective-C模仿Swift:权威指南。
注意:本文的目标是成为现代Typed Objective-C的入门文章。 超过一半的材料和想法是原创的。 对于那些不是,我链接了原始来源。 如果您有好主意或我错过了什么,请通知我。 您的资料将被发布,并被添加为作者。
这一年差不多是2019年。Swift现在是4.2版。 自从用Objective-C编写不再是“流行”并且在会议或聚会上“脱颖而出”甚至是危险的以来已经过去了六年。 但是,那些认识到swift的好处的人应该怎么做,就已经写了一些项目,但是由于某些原因而坚持使用Objective-C(也许是由于次要版本之间ABI不稳定)。 让我们看一下swift的功能,以及我们是否可以尝试“模仿”或超越它的某些功能……
PS:那些达到本文7/8要求的人将获得Dessert,并且肯定会喜欢它!
目录
- 结构
- 纯功能
- 默认接口实现
- 泛型
- 抽象方法
- 最后
- 铸件
- 枚举
- 开关范围:
- 默认参数
- 阻止可见性
- 选装件
- 甜点
- 自动打字
- 类型迭代
- 复制中
- 延期
- 检查密钥路径
Swift有结构,但C也有。但是,直到Xcode 10,他们才不支持ARC for Objective C类型,只支持__unsafe_unretained和自由跳舞 可以救我们
但是自这次WWDC演讲以来,一切都发生了变化:https://developer.apple.com/videos/play/wwdc2018/409/
那么,我们现在该怎么办? 乍一看似乎不太方便。 如果没有丑陋的循环释放,我们就不能在数组中使用它。 在放入NSArray之前,我们仍然必须转换为NSValue,但是要感谢__attribute __((objc_boxable))的语法更简单。
实际上有大量的实现…
例如,初始化程序。 我相信少数人会与Bob叔叔不同意,函数的最大参数应为3个参数,如果更多,则应努力使用结构。 好吧,在现代C结构出现之前,我们仅限于使用这样的东西。
@interface SomeService
-(非空实例类型)initWithBlobData :(非空BlobData * const)blob NS_DESIGNATED_INITIALIZER; @end
而且我们所有丑陋的初始化方法都将迁移到模仿值类型的类:
@interface BlobData
@属性(复制,非原子,只读,可为空)NSString * param1;
...
@property(强,非原子,只读,可为空)id param10; +(非空实例类型)initWithParam1 :(可空NSString * const)param1 param2 :(可空NSString * const)param2 param3 :(可空NSString * const)param3 param4 :(可空NSString * const)param; @end
现在,Blob数据可能只是Struct! OC可以完美地工作。
typedef struct {
NSString * _Nullable const param1;
NSString * _Nullable const param2;
...
NSString * _Nullable const param10;} BlobData;
我们可以像swift一样使用它!
[[SomeSerivce alloc] initWithBlobData:(BlobData){
.param1 = @“ 1”,
.param2 = @“ 2”,
...
.param10 = @“ 10”
}];
您不能传递nil指针而不是结构,您不能更改创建后的变量。 现在考虑再添加一个参数,您无需更改服务签名,这是否很棒?
在开始之前,我想向您介绍这个方便的宏:
#define DEFAULT_CONSTRUCTORS_UNAVAILABE \
–(实例类型)init NS_UNAVAILABLE; \
+(instancetype)新的NS_UNAVAILABLE;没什么新东西,您很可能已经在使用它,但是对于那些不喜欢它的人来说,它非常方便
现在让我们说我们正在开发类似类的结构(类似于值类型)。
@interface存储:NSObject
-(nonnull NSString *)标识符:
-(nonnull NSNumber *)numberOfElements;-(nonnull实例类型)initWithIdentifier:(nonnull NSString * const)标识符numberOfElements:(nonnull NSNumber * const)numberOfElements;
DEFAULT_CONSTRUCTORS_UNAVAILABE
@结束
并且您想添加一个非常方便的功能,该功能不会被视为快速变异。 我们在ObjC中没有mutating关键字,但是我们有一个非常方便的属性来显示函数不会更改状态,这样我们就可以将所有其他函数默认情况下视为变异
#定义PURE_FUNCTION __attribute __((纯))
从优化的角度来看, 它不是很有用,并且编译器不会检查方法,您可以显示您的意图并使其与合约明确。
@interface存储:NSObject
-(nonnull NSString *)标识符:
-(nonnull NSNumber *)numberOfElements;-(id )getSmthWithCompletion:(nonnull dipatch_queue_t)完成PURE_FUNCTION;-(nonnull instancetype)initWithIdentifier:(nonnull NSString * const)identifiernumberOfElements:(nonnull NSNumber * const)numberOfElements
DEFAULT_CONSTRUCTORS_UNAVAILABE
@结束
对于C函数,您可以使用__attribute __(const)进行操作,但是它将仅控制您不访问全局内存。
更新:忘记添加。 一个简单的clang编译器检查器-可以使const和pure真正保证很多。
Swift为iOS带来了一个古老的泛型编程概念。 因此,我们只需确认协议即可获取默认实现。
协议PrettyProtocol {
var o:字符串{get}
func doSmth()
}扩展PrettyProtocol {
var o:字符串{
返回“利润!”
} func doSmth()
// TADA!
}
}
但是,并不是每个人都知道我们实际上可以通过OC并通过一些肮脏的运行时魔术来做同样的事情,我们甚至可以提供默认属性!
#define DoSmthProtocol_h
@protocol DoSmthProtocol
@属性(非原子,副本,可为空)NSString * o;
-(void)kaa_doSmth;
@ end#endif / * DoSmthProtocol_h * /
然后创建一个类别: NSObject + DoSmthProtocol
#import "DoSmthProtocol.h"@interface NSObject(DoSmth)
@ end @ implementation NSObject @ dynamic o;-(void)kaa_doSmth {
NSLog(@“ TADA”);
}-(void)setO:(NSString *)object {
objc_setAssociatedObject(self,@selector(o),object,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}-(NSString *)o {
返回objc_getAssociatedObject(self,@selector(o));
}@结束
我们将如何使用它。
#import "DoSmthProtocol.h"@interface购物车:NSObject
@ end#import“ NSObject + DoSmth” // <---这是我们使其仅在内部可见的方式!@implementation购物车
@结束
libextobjc中也将类似的技巧用于ConcreteProtocols 。 https://github.com/jspahrsummers/libextobjc
即使Objective C没有真正的泛型,它也具有轻量级泛型,并具有更多功能:协方差,逆方差,不变性。 尽管我不是第一个写这本书的人,但是可以很公平地提及原始的分类:
https://mikeash.com/pyblog/friday-qa-2015-11-20-covariance-and-contravariance.html
我想提供一个示例,说明我们如何使用它们来节省自身的铸造工作。
@protocol AnimalProtocol
-(无效)运行;
-(无效)睡觉;
-(无效)尖叫;
@ end @ interface BaseFarm :NSObject
+(非空实例类型)initWithFood :(非空食品* const )餐饮;
-(nonnull NSArray * const)makeAnimals;
@结束
@interface Cow:NSObject,AnimalProtocol
@结束
@interface CowFarm:BaseFarm
-(非空NSArray * const)makeAnimals;
@结束
@interface Yak:NSObject,AnimalProtocol
-(void)doSmthingYakish;
@结束
@interface YakFarm:BaseFarm
-(nonnull NSArray * const)makeAnimals;
@结束
-(void)someWhereDeepInTheApp {
NSArray * acc = [[YakFarm new] makeAnimals]; //没有类型问题,数组有牛。
for(Y牛* y acc){
[y doSmthingYakish];
}
}
尽管此示例看起来有些虚构,但我想向您指出在使用集合时我们如何不需要强制转换 。 通常,如果我们想继承第一工厂的子类,我们将只能使用协议,并使用类似的东西。
-(void)somewhereDeepInYourApp {
NSArray <id > * yaks = [[[YakFarm new] makeAnimals]; //类型问题!
for(id ak牛in牛){
if([yak isKindOfClass:[Yak class]]){
k牛* y =(Y牛*)yak;
[y doSmthYakish];
}
}
}
通用宏:
C11标准已启用_Generic宏支持:http://www.robertgamble.net/2012/01/c11-generic-selections.html
现在我们可以做这样的事情了……
id a = _Generic(@“”,NSString *:[self doSmthWithString:some],
NSNumber *:doSmthWithNumber(some),
默认值:@“ boom!”);
NSLog(@“%@”,a);
-(NSString *)doSmthWithString:(NSString *)string {
返回字符串;
}
NSNumber * doSmthWithNumber(NSNumber * number){
返回编号;
}
尽管它不是特别有用,但值得一提。
PS。 如果此支持的运行时,OC将会有多好……
在使用轻量级泛型时,您肯定要创建一个抽象类,因为OC没有用于协议的轻量级泛型。 不用担心,实际上有一个明确的出路。
#define KAA_ABSTRACT_INTERFACE __attribute __((unavailable(“ Abstract method”)))) @interface测试:NSObject
+(非空实例类型 )doSmth KAA_ABSTRACT_INTERFACE;
DEFAULT_CONSTRUCTORS_UNAVAILABE
@结束
现在,由于编译器错误,您将无法直接调用它。
Test * t = [Test doSmth]; /// <-错误
因此,也许我们会以某种方式影响间接调用? 顺便说一句,您将必须实现void方法并从中返回一些信息?
#define KAA_ABSTRACT_METHOD {\ NSString * errorMessage = [NSString stringWithFormat:@“%@是一种抽象方法”,NSStringFromSelector(_cmd)]; \
@throw [NSException exceptionWithName:@“直接调用抽象类方法”原因:errorMessage userInfo:nil]; \
__builtin_unreachable(); \
}
这个小宏解决了这两个问题。
@实现测试+(非空实例类型 )doSmth KAA_ABSTRACT_METHOD @end
当然,您可以轻松地从中子类化并实现自己的方法。
更新:一段时间后,我发现了一种更轻松,更流畅的方法。
- 创建虚拟方法的协议
@protocol 示例协议
+ (nonnull Class < SomeClass > )viewModelClass;
@结束
2.创建带有方法实现的类
@interface MyClassExample < SomeObject >: NSObject
- ( void )执行: (SomeObject)someObj;
@结束
@implementation MyClassExample
- ( void ) execute :(SomeObject)someObj {
//做某事
}
@结束
3.通过typedef统一它们!
typedef MyClassExample 元素;
4.利润! (现在,您将获得默认实现)(带有需求方法的协议)!
@interface MyObject : 元素< NSString *> @end
我们很快就有了一个非常有用的关键字final。 这使我们能够限制子类化,并再次使与阅读程序员的联系更加清晰。 好吧,实际上我们也可以通过此方便的宏在OC中完成此操作
#定义KAA_FINAL__属性__ ((objc_subclassing_restricted))KAA_FINAL
@interface测试:NSObject
+(非空instancetype )doSmth;
@end //不会编译@interface ChildTest:测试// <-将导致错误。
+(非空instancetype )doSmth;
@结束
由于我们已经深入主题,因此我也想在这里分享另一个方便的宏(尽管它与快速模仿没有直接关系)。
-(void)foo NS_REQUIRES_SUPER;
以防万一您厌倦了通常在viewWillAppear等中错过它的那些人。
在OC中,我们基本上可以将任何内容转换为任何内容,并在运行时崩溃。 但是通过实现一些简单的扩展,我们可以完全避免它。
@interface NSObject(SafeCast)
+(nullable 实例类型 )kaa_optionalCast:(nonnull id )optionalObject;
+(非空实例类型 )kaa_strongCast :(可空ID )strongObject;
@ end @ implementation NSObject(SafeCast)+( 实例类型 )kaa_optionalCast :( id )optionalObject {
返回 [optionalObject isKindOfClass: self ]? optionalObject:nil;
} +( 实例类型 )kaa_strongCast :( id )strongObject {
如果 ([strongObject isKindOfClass: self ]){
返回 strongObject;
} 其他 {
@throw ([NSException exceptionWithName:@“ Wast cast错误的原因:@”对象的类型错误或为nil“ userInfo: nil ]);
}
}
例:
id a = @ 1;
NSString * b = [NSString kaa_optionalCast:a];
// b是nilNSString * c = [NSString kaa_strongCast:a]; //例外
另一个可能的用例是从字典中提取值,同时确保类型。
NSString * s = [NSString kaa_strongCast:dict [key]];
枚举是swift的一个非常令人鼓舞的功能,它使我们免于大量的铸造工作。
我知道有4种方法可以使用OC来模仿它,所以请继续,这将是一个很大的部分
带结构的标记联合
我们可以尝试使用…联合来模仿枚举! 这不是世界上最安全的方法,但仍然非常值得一提。 并且结构现在支持ARC。 因此,我们可以使用旧的C技术-带标记的联合。
typedef 枚举 {
BlobUnionTagA,BlobUnionTagB
} BlobUnionTag; typedef union {
BlobDataA a;
BlobDataB b;
} BlobUnion; typedef struct {
BlobUnion值;
BlobUnionTag标记;
} BlobEnum;
现在,我们可以创建枚举并打开类型。
BlobEnum e = {
.tag = BlobUnionTagA,
.value =(BlobDataA){
.identifier = @“我们的标识符”
}
} switch(e.tag){case BlobUnionTagA:
doSmth(e.value.a);
打破;
案例BlobUnionTagB:
doSmth(e.value.b);
打破;
}
但要注意, 在分配对时 , 我们可能会将数据弄乱类型 ,因此您应该考虑使用C函数的变通方法,并且可能与metamacro混合使用以生成代码。 在编写它时,您当然可以使用可重载的宏 !
#define KAA_OVERLOADABLE __attribute __((overloadable))BlobEnum KAA_OVERLOADABLE makeBlobEnumWithData(BlobDataA data){
return (BlobEnum){
.tag = BlobUnionTagA,
.value.a =数据
};
}
BlobEnum KAA_OVERLOADABLE makeBlobEnumWithData(BlobDataB data){
return (BlobEnum){
.tag = BlobUnionTagB,
.value.b =数据
};
}
所以我们可以这样称呼:
BlobEnum e = makeBlobEnumWithData((BlobDataA){
.identifier = @“我们的标识符”
})
永远不要将标记类型与结构类型混淆。
游客
与工会黑客相比,该解决方案更加安全可靠。 让我们看看如何使用它:
@protocol BlobDataVisitorHost
-(void)acceptVisitor:(nonnull BlobDataVisitor * const)visitor;
@ end @协议BlobData
@protocol(非原子,副本,非空)NSString * identifier;
@ end @ interface BlobDataA:NSObject < BlobData >
@property( 非原子复制非 )NSString *标识符;
@属性(非原子,副本,非空)NSString *消息;
@结束
@interface BlobDataB:NSObject < BlobData >
@property( 非原子复制非 )NSString *标识符;
@属性(非原子,副本,非空)NSNumber * count;
@ end @ interface BlobDataVisitor:NSObject
-(void)visitDataA:(nonnull BlobDataA * const)blobDataA;
-(void)visitDataB:(nonnull BlobDataB * const)blobDataB;
-(非空实例类型)initWithBlobAHandler:((非空BlobAHandler)blobAHandler
blobBHandler :(非null BlobBHandler)blobBHandler;
@ end @ implementation BlobDataVisitor
...
-(void)visitDataA:(nonnull BlobDataA * const)blobDataA {
self.blobAHandler(blobDataA);
}
//等等。
...
@ end @ interface BlobDataA(访问者)< BlobDataVisitorHost >
@结束
@implementation BlobDataA
-(void)acceptVisitor:(nonnull BlobDataVisitor * const)visitor {
[访问者visitDataA:self];
}
@结束
因此,让我们看看它如何在代码中工作。
-(void)doSmthWithBlob:(nonnull NSArray <id > * const)blob {
让v = [[BlobDataVisitor alloc] initWithBlobAHandler:^ {
//一些东西
} blobBHandler:^ {
}];
for(id blob,以blob为单位){
[blob acceptVisitor:v];
}
}
尽管绝对包含大量样板,但是此解决方案对ARC,类型没有限制,并且不需要铸造。
从协议类型转换
即使您通常应该避免盲目铸造,使用已检查的铸造也是安全的。
typedef NS_ENUM(NSUInteger,BlobDataType){
BlobDataTypeA,
BlobDataTypeB
}; @ protocol BlobData
@ 属性 (非原子的)BlobDataType类型;
@ 属性 (非原子,副本,非空)NSString * identifier;
@ end @ interface BlobDataA:NSObject < BlobData >
@属性(非原子,副本,非空)NSString *消息;
@结束
@interface BlobDataB:NSObject < BlobData >
@属性(非原子,副本,非空)NSNumber * count;
@ end-(void)doSmthWithBlob:(nonnull NSArray <id > * const)blobs {
for(id blob,以blob为单位){switch(blob.type){
案件 BlobDataTypeA:
BlobDataA * b = [BlobDataA kaa_strongCast:blob];
}
打破; 案例BlobDataTypeB:
//相同
}
}
}
NS_TYPED_EXTENSIBLE_ENUM
尽管这是非常有限的,仅用于与Swift桥接。 NS_TYPED_EXTENSIBLE_ENUM为我们提供了非常方便的自动补全功能。
您很可能已经遇到了它的派生NS_EXTENSIBLE_STRING_ENUM
typedef NSString * const MoneyResource NS_EXTENSIBLE_STRING_ENUM ;
静态MoneyResource卡= @“卡”;
静态MoneyResource帐户= @“帐户”;
现在,您可以开始输入值键,它将自动完成。
MoneyResource c =卡;
但是基本上,我们可以使用NS_TYPED_EXTENSIBLE_ENUM放置任何内容,因为这只是一个静态变量。
typedef MyCard * const MoneyResource NS_TYPED_EXTENSIBLE_ENUM ;
静态MoneyResource CreditCard = [MyCard cardWithIdentidier:@“ 123131”];
静态MoneyResource DebitCard = [MyCard cardWithIdentidier:@“ 77777”];
很快您将无法分配超出范围的枚举。 您也可以在Objective C中实现。 __attribute __(((enum_extensibility(closed)))和NS_CLOSED_ENUM可以在构建设置中启用警告标志时提供帮助:超出范围的枚举分配。
#定义CLOSED_ENUM __attribute__ ((enum_extensibility(closed))) typedef NS_CLOSED_ENUM(NSUInteger,ClosedEnum){
一种,
乙
};
用这个小宏关闭enum或enum不会分配原始值,但超出了这样的范围:
ClosedEnum e = 100; // <-将发出警告。
键入安全性的又一步!
不推荐使用的枚举参数:
您很可能在Objective-C属性和方法中看到了__deprecated宏。 但是您也可以将枚举参数标记为已弃用。
typedef NS_CLOSED_ENUM(NSUInteger,ClosedEnum){
A __deprecated_enum_msg(“请仅使用B!”),
乙
};
现在,当您使用案例时:
ClosedEnum t = A; // <-将发出警告:请仅使用B!
当我发现目标C和C的支持范围之快时,这真让我感到惊讶!
int i = 0;
切换(i){
案例0 ... 100:// doSmth!
默认:
// doOther
} char c ='A'switch(c){
案例'A'...'Z'://做asci !!!!
默认:
//其他!!
}
但是要注意前后的空格!
好吧,在这里您会很失望,因为周围没有肮脏的骇客,但是如果您绝对需要它,我绝对想向您建议生成器模式,因为在我的项目中对于大部分参数(10个或更多)它非常方便。
typedef NS_ENUM(NSUInteger,CakeSize){
蛋糕尺寸小,
蛋糕尺寸大
};
@interface Cake:NSObject
@property(非原子)NSIntegerberriesCount;
属性(非原子)加倍;
@property(非原子)CakeSize大小;
@结束
@实现蛋糕
@ end @ interface CakeBuilder:NSObject
-(void)setBerriesCount:(NSInteger)berriesCount;
-(void)setWeight:(double)weight;
-(void)setSize:(CakeSize)size;
-(nonnull Cake *)构建;
@结束
@interface CakeBuilder()
@property(强,非原子,可为空)Cake * cake;
@结束
@implementation CakeBuilder
-(蛋糕*)蛋糕{
if(!_ cake){
_cake = [Cake new];
_cake.berriesCount = 6;
_cake.weight = 1.0;
_cake.size = CakeSizeSmall;
}
返回_cake;
}
-(void)setBerriesCount:(NSInteger)berriesCount {
self.cake.berriesCount = berriesCount;
}
-(void)setWeight:(double)weight {
self.cake.weight =体重;
}
-(void)setSize:(CakeSize)size {
self.cake.size =大小;
}
-(蛋糕*)构建{
返回self.cake;
}
@ end @ interface CakeFactory:NSObject
-(nonnull Cake *)makeCakeWithBuildBlock:(nonnull Cake *(^)(CakeBuilder *))buildBlock;
@结束
#import“ CakeFactory.h”
#import“ Cake.h”
#import“ CakeBuilder.h”
@implementation CakeFactory
-(Cake *)makeCakeWithBuildBlock:(Cake *(^)(CakeBuilder *))buildBlock {
如果(!buildBlock){
返回零;
}
返回buildBlock([CakeBuilder new]);
}
@结束
所以……这是大量的样板代码! 但是,让我们看看它是如何工作的!
[[CakeFactory new] makeCakeWithBuildBlock:^ Cake * _Nonnull(CakeBuilder * _Nonnull builder){
[builder setBerriesCount:10];
[builder setSize:CakeSizeBig];
[builder setWeight:15.0];
}];
织物,平滑得多。
更新:如果您没有逻辑,并且默认情况下元素应为nil,则可以仅使用结构,而无需为对象指定任何内容。
提到NS_NOESCAPE以及它如何使我们的代码更具描述性,这将是不公平的。 因此,基本上,如果您希望其他开发人员阅读您的意图:我们将不会存储此代码块,并且该代码将无法在此函数调用中保留,您可以这样指出:
-(void)doSomethingWithCompletion:(NS_NOESCAPE dispatch_block_t)block {
块();
}
但是不幸的是 ,如果您偶然决定将其存储在某个地方,编译器将不会通知您。
人工的
好吧,我们实际上可以做些事情,OC本质上是一种动态语言,尽管我们可以通过制作巨大的样板代码来逃脱。 (https://engineering.facile.it/blog/eng/optionals-in-objective-c/)
更新:一段时间后,我也改用ObjC可选件。 当前格式:https://medium.com/@alex.a.kazartsev/implementing-modern-objective-c-optionals-3f05650ed779
关于空性的另一种说法
即使到处都提到了nullable和nonnull ,但我想指出一下它如何使我们的语言更具描述性。
@协议可取消 ; @interface FooSerivce:NSObject-(nonnull id )getFooWithMessage:( nullable NSString * const)消息完成:( nonnull id (^)(NSString *,NSError *))完成; @结束
因此,即使我们不考虑与Swift的兼容性,它实际上带给我们的是服务作者的意图 。
只看这里的FooService我们能说什么? 对于正常功能而言,不必传递消息参数,但以这种方式传递完成块非常重要-他/她保证将返回Cancellable 。 那么,我们可以对实现进行猜测吗?
@implementation FooService-(nonnull id )getFooWithMessage :( nullable NSString * const)消息完成:( nonnull id (^)(NSString *,NSError *))完成{NSParameterAssert(completion!= nil );
// doSmthAsync id c = // NSOperation在cancellable中涉及
返回 c;} @ end
正如我们所看到的,作者警告您有关联系的信息,而现在,我们断言是断言还是异常!
我还想向您指出这个方便的宏:
OS_NONNULL_ALL // __属性__((__ nonnull__))
如名称所示,它已经存在于系统中。 它允许将所有函数参数标记为nonnull 。
-(instancetype)initWithMessage:(NSString *)消息其他:(id)other OS_NONNULL_ALL ;
我非常喜欢与阅读开发人员签订合同时要尽可能严格和描述性的想法。 但是此属性提供了强大的功能,因此,如果可以进一步发展此思想,我们可以使OC和C具有IDRIS之类的语言的功能。 让我们向您展示这个惊人的属性:
__attribute __((enable_if))
它允许在参数上指定合同!
就像这样:
int doSomethingWithString(NSString * c,int count)__attribute __((enable_if(count> 3 && count <100,“当'c'超出范围时选择”))){
// doSmth 返回0; }
那么,它实际上是做什么的呢? 好吧,如果count不在4–99范围内,则编译器将无法找到doSomethingWithString。
整数= 100;
int * pCount =&count;
doSomethingWithString(@“ Oh my my”,* pCount); //不会编译!
尽管您经验丰富的C程序员可能会笑,但如果我们在方法中拥有如此强大的功能,那么在Objective-C的世界中,如果没有内部异常,断言和更轻松的测试,我们可以开发出更好的API。
但是功能远非完美,并且无法编译。
对于 ( int i = 5; i <6; i ++){
isdigitOla(i);
}
现在所有这些还远远没有结束 ,但是我应该指出,在这些原始资料中首先发现了下一个技巧,您肯定必须订阅PSPDFKit!:
Swifty Objective-C |中文 PSPDFKit内部
Objective-C起源于1980年代初期,尽管语言在过去的几年中发展了很多,但仍然不行。
pspdfkit.com
更快速的Objective-C | PSPDFKit内部
另一个引人入胜的WWDC正在我们后面。 今年,我们再次目睹了许多新出现的功能,并…
pspdfkit.com
只需在您的.pch文件中的某个位置添加此小宏
#define var __auto_type
#定义let const __auto_type
您将获得类似于swift的更精简的语法。
之前:
void(^ onCartActivation)(购物车* Cart,NSError * error,NSDictionary 有效载荷)=
^(购物车*购物车,NSError *错误,NSDictionary 有效载荷){
//代码
};
后:
let onCartActivation = ^(购物车*购物车,NSError *错误,NSDictionary 有效载荷){
//代码
};
这些小的扩展(没有实现)可以获取少量的foreach宏,使您的代码更漂亮。
@protocol KAAFastEnumeration -( id )kaa_enumeratedType; @end //用法:foreach(s,字符串){...}#为(collection)中的(typeof((collection).kaa_enumeratedType)元素定义foreach(element,collection) @interface NSArray (KAAFastEnumeration)
-(ElementType)kaa_enumeratedType;
@end @interface NSSet (KAAFastEnumeration)
-(ElementType)kaa_enumeratedType;
@end @interface NSDictionary (KAAFastEnumeration)
-(KeyType)kaa_enumeratedType;
@结束
现在您可以开始使用宏:
foreach(e,acc){
// e已经是acc的元素类型
}
您可以在PSPDFKit的页面上查看完整的实现。
另一个小的扩展可以在复制时节省我们的轻量级泛型。
@interface NSArray < ElementType > (KAASafeCopy)
///与`copy`相同,但保留通用类型。
- (NSArray < ElementType > * )副本;
///与`mutableCopy`相同,但保留通用类型。
- (NSMutableArray < ElementType > * )mutableCopy;
@结束
您可以在PSPDFKit的页面上查看所有集合的完整实现。
为了模仿快速延迟,我们可以使用cleanup属性和__LINE__作为唯一名称:
#define延迟__strong void(^(defer_ ## __LINE __))(void)__attribute __((cleanup(defer_cleanup_block),未使用))= ^ 静态 void defer_cleanup_block( __strong void (^ * block)( void )){
(*块)();
}
这样,我们可以像这样使用它:
推迟{
NSLog(@“ Hello”);
}; NSArray * a = @ [@“ Hi”,@“ There”];
foreach(e,a){
NSLog(@“%@”,e);
} //输出为“嗨,在那里,你好”
您很可能已经看过https://github.com/jspahrsummers/libextobjc/blob/experimental/extobjc/EXTSafeCategory.h
PSPDFKit家伙将其进一步移动:
#if调试
#定义PSPDF_KEYPATH(对象,属性)((无效)(否&&((无效)object.property,NO)),@ #property)
#其他
#定义PSPDF_KEYPATH(对象,属性)@#属性
#万一
它被这样使用:
[player addObserver :self forKeyPath :PSPDF_KEYPATH(player,rate)选项: NSKeyValueObservingOptionNew上下文:& PSPDFMediaPlayerKVOToken];