Objective-Cの別のActiveRecord実装

Objective-C、特にiOS甚のActiveRecordパタヌンの別の実装を共有したいず思いたす。

iOS開発でCoreDataを䜿甚し始めたばかりのずきでも、この盞互䜜甚は䜕らかの圢で単玔化できるず考えられおいたした。 しばらくしおから、RubyOnRailsのActiveRecordに䌚ったずころ、䜕が欠けおいるのかがわかりたした。
githubを少し怜玢しお、倚くの実装を芋぀けたしたが、さたざたな理由でそれらが奜きではありたせんでした。 CoreData甚に䜜成されたものもありたすが、私は奜きではありたせん。他のものでは、手でテヌブルを䜜成するか、生のSQLク゚リを䜜成する必芁がありたす。 たた、堎合によっおはコヌドがひどくひどかったので、私は時々自分であたりきれいに曞かないこずがありたすが、囲たれたif / switch / if / switchからの巚倧なフェンスが倚すぎたす。
最終的に、CoreDataずナヌザヌのSQLを䜿甚せずに自転車を䜜成するこずにしたした。
この開発の䞻な理由は、開発に興味があるこずです。

これがすべおの結果です。
そしお、猫の䞋には、可胜性ず実装に぀いおの小さな説明がありたす実際、倚くのテキストずコヌド、蚘事の最埌の芁玄。

テヌブル䜜成


最初の問題はテヌブルの䜜成でした。
CoreDataの堎合、䜕も䜜成する必芁はなく、゚ンティティを蚘述するだけで、あずはCDが凊理したす。
私はそれをより良く敎理する方法に぀いお長い間考えおいたしたが、しばらくするずそれが私に気付きたした。
Objective-Cでは、任意のクラスのすべおのサブクラスのリストを取埗でき、さらにそのすべおのプロパティのリストを取埗できたす。 したがっお、゚ンティティの説明はクラスの簡単な説明になりたす。この情報を収集し、それに基づいおSQLク゚リを䜜成するだけです。
゚ンティティの説明

@interface User : ActiveRecord @property (nonatomic, retain) NSString *name; @end 

すべおのサブクラスを取埗する

 static NSArray *class_getSubclasses(Class parentClass) { int numClasses = objc_getClassList(NULL, 0); Class *classes = NULL; classes = malloc(sizeof(Class) * numClasses); numClasses = objc_getClassList(classes, numClasses); NSMutableArray *result = [NSMutableArray array]; for (NSInteger i = 0; i < numClasses; i++) { Class superClass = classes[i]; do{ superClass = class_getSuperclass(superClass); } while(superClass && superClass != parentClass); if (superClass == nil) { continue; } [result addObject:classes[i]]; } return result; } 

すべおのプロパティを基本クラスたで取埗する

 Class BaseClass = NSClassFromString(@"NSObject"); id CurrentClass = aRecordClass; while(nil != CurrentClass && CurrentClass != BaseClass){ unsigned int outCount, i; objc_property_t *properties = class_copyPropertyList(CurrentClass, &outCount); for (i = 0; i < outCount; i++) { // do something with concrete property => properties[i] } CurrentClass = class_getSuperclass(CurrentClass); } 


デヌタ型


柔軟性を高めるために、基本デヌタ型int、doubleなどを攟棄し、クラスをテヌブルフィヌルドずしおのみ䜿甚する必芁がありたした。
したがっお、任意のクラスをテヌブルフィヌルドずしお䜿甚できたす。唯䞀の芁件は、それ自䜓を保存およびロヌドできる必芁があるこずです。
これを行うには、ARRepresentationProtocolを実装する必芁がありたす

 @protocol ARRepresentationProtocol @required + (const char *)sqlType; - (NSString *)toSql; + (id)fromSql:(NSString *)sqlData; @end 

カテゎリを䜿甚しおFoundationフレヌムワヌクタむプにこれらのメ゜ッドを実装したした
-NSDecimalNumber-実数
-NSNumber-æ•Žæ•°
-NSString-テキスト
-NSData-ブロブ
-NSDate-日付実数
ただし、これらのクラスのセットはい぀でも拡匵でき、それほど難しくありたせん。

Transformableデヌタ型のCoreDataを䜿甚しおも同じこずが実珟できたすが、それをどのように䜿甚するかはただわかりたせん。

レコヌドのCRUD


䜜成する

新しいレコヌドを䜜成するプロセスは非垞にシンプルで透過的です。

 User *user = [User newRecord]; user.name = @"Alex"; [user save]; 

読む

すべおのレコヌドを取埗する

 NSArray *users = [User allRecords]; 

倚くの堎合、すべおのレコヌドが必芁ではないため、フィルタヌの実装を远加したしたが、それらに぀いおは埌で詳しく説明したす。

曎新する

 User *user = [User newRecord]; user.name = @"Alex"; [user save]; NSArray *users = [User allRecords]; User *userForUpdate = [users first]; userForUpdate.name = @"John"; [userForUpdate update]; //  [userForUpdate save]; 

ActiveRecordはすべおのプロパティの倉曎を監芖し、曎新時には倉曎されたフィヌルドのみを曎新するリク゚ストを䜜成したす。

削陀する

 NSArray *users = [User allRecords]; User *userForRemove = [users first]; [userForRemove dropRecord]; 

すべおのレコヌドには、削陀に䜿甚されるidNSNumberプロパティがありたす。

䞍芁なフィヌルド


デヌタベヌスに保存する必芁のないフィヌルドはどうですか 無芖しおください:)
これを行うには、クラスの実装で次の構成を远加する必芁がありたす。これは単玔なマクロシステムです。

 @implementation User ... @synthesize ignoredProperty; ... ignore_fields_do( ignore_field(ignoredProperty) ) ... @end 


怜蚌


開発で自分自身に蚭定した芁求の1぀は、怜蚌のサポヌトです。
珟圚、可甚性ず䞀意性の2皮類の怜蚌が実装されおいたす。
構文は単玔で、colマクロも䜿甚したす。 さらに、クラスはARValidatableProtocolを実装する必芁がありたす。ナヌザヌからの芁求はありたせん。これは、それを䜿甚しないクラスの怜蚌メカニズムを開始しないようにするためです。

 // User.h @interface User : ActiveRecord <ARValidatableProtocol> ... @property (nonatomic, copy) NSString *name; ... @end // User.m @implementation User ... validation_do( validate_uniqueness_of(name) validate_presence_of(name) ) ... @end 

さらに、ナヌザヌ自身が远加できるカスタムバリデヌタヌのサポヌトを実装したした。
これを行うには、ARValidatorProtocolを実装し、怜蚌枈みのクラスで蚘述する必芁がある怜蚌クラスを䜜成する必芁がありたす。
ARValidatorProtocol

 @protocol ARValidatorProtocol <NSObject> @optional - (NSString *)errorMessage; @required - (BOOL)validateField:(NSString *)aField ofRecord:(id)aRecord; @end 

カスタム怜蚌

 // PrefixValidator.h @interface PrefixValidator : NSObject <ARValidatorProtocol> @end // PrefixValidator.m @implementation PrefixValidator - (NSString *)errorMessage { return @"Invalid prefix"; } - (BOOL)validateField:(NSString *)aField ofRecord:(id)aRecord { NSString *aValue = [aRecord valueForKey:aField]; BOOL valid = [aValue hasPrefix:@"LOL"]; return valid; } @end 

゚ラヌ凊理


save、update、isValidメ゜ッドはブヌル倀を返したす; false / NOが返された堎合、゚ラヌのリストを取埗できたす

 [user errors]; 

その埌、クラスARErrorのオブゞェクトの配列が返されたす

 @interface ARError : NSObject @property (nonatomic, copy) NSString *modelName; @property (nonatomic, copy) NSString *propertyName; @property (nonatomic, copy) NSString *errorName; - (id)initWithModel:(NSString *)aModel property:(NSString *)aProperty error:(NSString *)anError; @end 

このクラスには詳现な゚ラヌメッセヌゞは含たれたせんが、ロヌカラむズされたメッセヌゞを䜜成しおアプリケヌションナヌザヌに衚瀺できるキヌワヌドのみが含たれたす。

移行


移行はプリミティブレベルで実装されたす。゚ンティティぞの新しいフィヌルドの远加たたは新しい゚ンティティの远加にのみ応答したす。
移行を䜿甚するために、どこにも登録する必芁はありたせん。
アプリケヌションを初めお起動するず、すべおのテヌブルが䜜成され、その埌の起動時に、新しいフィヌルドたたはテヌブルが存圚するかどうかが確認され、存圚する堎合は、テヌブルク゚リが倉曎されたす。
テヌブル構造の倉曎のチェックをむンスタンス化しないために、ActiveRecordを呌び出す前に次のメッセヌゞを送信する必芁がありたす

 [ActiveRecord disableMigrations]; 

取匕


トランザクションを䜿甚する機胜も実装したした。これにはブロックが䜿甚されたす

 [ActiveRecord transaction:^{ User *alex = [User newRecord]; alex.name = @"Alex"; [alex save]; rollback }]; 

rollback-タむプARExceptionの䟋倖をスロヌする通垞のマクロ。
Tarnzaktsiiは、障害が発生した堎合のロヌルバックだけでなく、レコヌドを远加する際のク゚リ実行の速床を䞊げるためにも䜿甚できたす。
プロゞェクトの1぀は、9000以䞊のレコヌドを䜜成しようずしたずきにひどいブレヌキをかけたした。 ダンプの実行時間は、BEGINトランザクションでラップした埌、玄180秒でした; ... COMMIT; 時間は玄4〜5秒に短瞮されたした。 だから、私は知らない人にアドバむスしたす。

コミュニケヌションズ


RoRでのActiveRecordの実装に粟通したずき、゚ンティティ間の関係を簡単に䜜成できるこずに満足したした。 抂しお、このシンプルさは、このフレヌムワヌクを䜜成するための最初の前提条件ずなりたした。 そしお今、私は自転車の最も重芁な機胜は、゚ンティティ間の接続ずそれらの盞察的なシンプルさであるず考えおいたす。
HasMany <-> BelongsTo

 // User.h @interface User : ActiveRecord ... @property (nonatomic, retain) NSNumber *groupId; ... belongs_to_dec(Group, group, ARDependencyNullify) ... @end // User.m @implementation User ... @synthesize groupId; ... belonsg_to_imp(Group, group, ARDependencyNullify) ... @end 


belongs_to_dec belonsg_to_impマクロは3぀のパラメヌタヌを受け入れたす「コンタクト」しおいるクラスの名前、ゲッタヌの名前、䟝存関係のタむプ。
䟝存関係には、ARDependencyNullifyずARDependencyDestroyの2皮類がありたす。1぀目はモデルを削陀するず関係が無効になり、2぀目は関連するすべおの゚ンティティが削陀されたす。
この関係のフィヌルドはモデル名ず䞀臎し、小文字で始たる必芁がありたす
グルヌプ<-> groupId
ナヌザヌ<-> userId
ContentManager <-> contentManagerId
EMCategory <-> eMCategory //少し䞍噚甚ですが、歎史的には

フィヌドバックHasMany

 // Group.h @interface Group : ActiveRecord ... has_many_dec(User, users, ARDependencyDestroy) ... @end // Group.m @implementation Group ... has_many_imp(User, users, ARDependencyDestroy) ... @end 

BelongsToタむプの通信ず同じです。
芚えおおくべき䞻なこずリンクを䜜成する前に、䞡方のレコヌドを保存する必芁がありたす。保存しないず、IDがなく、リンクがそれに関連付けられたす。

HasManyThrough


この接続を䜜成するには、別のモデル、䞭間モデルを䜜成する必芁がありたす。

 // User.h @interface User : ActiveRecord ... has_many_through_dec(Project, UserProjectRelationship, projects, ARDependencyNullify) ... @end // User.m @implementation User ... has_many_through_imp(Project, UserProjectRelationship, projects, ARDependencyNullify) ... @end // Project.h @interface Project : ActiveRecord ... has_many_through_dec(User, UserProjectRelationship, users, ARDependencyDestroy) ... @end // Project.m @implementation Project ... has_many_through_imp(User, UserProjectRelationship, users, ARDependencyDestroy) ... @end 

䞭間バむンディングモデル

 // UserProjectRelationship.h @interface UserProjectRelationship : ActiveRecord @property (nonatomic, retain) NSNumber *userId; @property (nonatomic, retain) NSNumber *projectId; @end // UserProjectRelationship.m @implementation UserProjectRelationship @synthesize userId; @synthesize projectId; @end 

この接続には、HasManyず同じ欠点がありたす。

* _Dec / * _ impマクロはヘルパヌメ゜ッドを远加しおリンクを远加したす
 set#ModelName:(ActiveRecord *)aRecord; // BelongsTo add##ModelName:(ActiveRecord *)aRecord; // HasMany, HasManyThrough remove##ModelName:(ActiveRecord *)aRecord; // HasMany, HasManyThrough 

ク゚リフィルタヌ


非垞に頻繁に、デヌタベヌスからの遞択を䜕らかの方法でフィルタリングする必芁がありたす。
-あるテンプレヌトに察応するレコヌドの怜玢UISearchBar
-1000のうち5レコヌドのみをテヌブルに出力
-「重い」写真のデヌタベヌスヒヌプから取埗せずに、レコヌドのテキストフィヌルドのみを受信する
-ただ倚くのオプション:)

最初は、これらすべおを䟿利な圢匏で実装する方法も考えられたせんでしたが、Rubyずその固有の「遅延」を思い出し、最終的に、芁求に応じおのみレコヌドを取埗するが、任意の順序でフィルタヌを受け入れるクラスを䜜成するこずにしたした。
これがその結果です。

制限/オフセット

 NSArray *users = [[[User lazyFetcher] limit:5] fetchRecords]; NSArray *users = [[[User lazyFetcher] offset:5] fetchRecords]; NSArray *users = [[[[User lazyFetcher] offset:5] limit:2] fetchRecords]; 

のみ/を陀く

 ARLazyFetcher *fetcher = [[User lazyFetcher] only:@"name", @"id", nil]; ARLazyFetcher *fetcher = [[User lazyFetcher] except:@"veryBigImage", nil]; 

どこで

iActiveRecordはWHERE基本条件をサポヌトしたす

 - (ARLazyFetcher *)whereField:(NSString *)aField equalToValue:(id)aValue; - (ARLazyFetcher *)whereField:(NSString *)aField notEqualToValue:(id)aValue; - (ARLazyFetcher *)whereField:(NSString *)aField in:(NSArray *)aValues; - (ARLazyFetcher *)whereField:(NSString *)aField notIn:(NSArray *)aValues; - (ARLazyFetcher *)whereField:(NSString *)aField like:(NSString *)aPattern; - (ARLazyFetcher *)whereField:(NSString *)aField notLike:(NSString *)aPattern; - (ARLazyFetcher *)whereField:(NSString *)aField between:(id)startValue and:(id)endValue; - (ARLazyFetcher *)where:(NSString *)aFormat, ...; 

同じこずがおなじみの䟿利なNSPredicate'ovのスタむルで説明できたす。

 NSArray *ids = [NSArray arrayWithObjects: [NSNumber numberWithInt:1], [NSNumber numberWithInt:15], nil]; NSString *username = @"john"; ARLazyFetcher *fetcher = [User lazyFetcher]; [fetcher where:@"'user'.'name' = %@ or 'user'.'id' in %@", username, ids, nil]; NSArray *records = [fetcher fetchRecords]; 

参加する


私はこれを自分で䜿甚したこずはほずんどありたせんでしたが、完党を期すために実装する必芁があるず刀断したした。

 - (ARLazyFetcher *)join:(Class)aJoinRecord useJoin:(ARJoinType)aJoinType onField:(NSString *)aFirstField andField:(NSString *)aSecondField; 

さたざたなタむプの結合がサポヌトされおいたす
-ARJoinLeft
-ARJoinRight
-ARJoinInner
-ARJoinOuter
名前はそれ自䜓を物語っおいるず思いたす。
1぀の小さな束葉杖がこの機胜に関連付けられおいるため、結合レコヌドを取埗するには、呌び出す必芁がありたす

 - (NSArray *)fetchJoinedRecords; 

の代わりに

 - (NSArray *)fetchRecords; 

このメ゜ッドは、キヌが゚ンティティ名であり、倀がデヌタベヌスからのデヌタである蟞曞から配列を返したす。

仕分け


 - (ARLazyFetcher *)orderBy:(NSString *)aField ascending:(BOOL)isAscending; - (ARLazyFetcher *)orderBy:(NSString *)aField;// ASC   ARLazyFetcher *fetcher = [[[User lazyFetcher] offset:2] limit:10]; [[fetcher whereField:@"name" equalToValue:@"Alex"] orderBy:@"name"]; NSArray *users = [fetcher fetchRecords]; 


保管


デヌタベヌスはキャッシュずドキュメントの䞡方に保存できたす。ドキュメントに保存する堎合、バックアップを切断する属性がファむルに远加されたす

 u_int8_t b = 1; setxattr([[url path] fileSystemRepresentation], "com.apple.MobileBackup", &b, 1, 0, 0); 

そうでない堎合、アプリケヌションはAppleから拒吊を受け取りたす。

たずめ


githubプロゞェクトはiActiveRecordです。
特城


おわりに



結論を正圓化するために 、私はプロゞェクトがただの楜しみのために始たったず蚀いたいず思いたす、そしお、それは最終的にたくさんの「汚い」コヌドをきれいにし、他の有甚な機胜を远加する蚈画で、開発を続けおいたす。

適切な批刀を聞いおうれしいです。

PSはLANに゚ラヌメッセヌゞを曞き蟌みたす。

Source: https://habr.com/ru/post/J145108/


All Articles