NSString 's AppDelegate ' s if (A and (B or C)) then use Pattern_N; Any pattern describes a task that arises again and again in our work, as well as the principle of its solution, and in such a way that this solution can then be used a million times without reinventing anything.
Although Alexander was referring to the patterns that arise in the design of buildings and cities, but his words are true in relation to the patterns of object-oriented design. Our solutions are expressed in terms of objects and interfaces, not walls and doors, but in both cases the meaning of the pattern is to offer a solution to a specific task in a specific context.




Album , and it will be a subclass of NSObject .@interface and @end : @property (nonatomic, copy, readonly) NSString * title; @property (nonatomic, copy, readonly) NSString * artist; @property (nonatomic, copy, readonly) NSString * genre; @property (nonatomic, copy, readonly) NSString * coverUrl; @property (nonatomic, copy, readonly) NSString * year; - (id)initWithTitle:(NSString *)title artist:(NSString *)artist coverUrl:(NSString *)coverUrl year:(NSString *)year; readonly flag, since we do not need to change them after creating the Album object.@implementation and @end : - (id)initWithTitle:(NSString *)title artist:(NSString *)artist coverUrl:(NSString *)coverUrl year:(NSString *)year { self = [super init]; if (self) { _title = title; _artist = artist; _coverUrl = coverUrl; _year = year; _genre = @"Pop"; } return self; } AlbumView class - a subclass of UIView .@interface and @end : - (id)initWithFrame:(CGRect)frame albumCover:(NSString *)albumCover; @implementation and @end with this one: @implementation AlbumView { UIImageView * coverImage; UIActivityIndicatorView * indicator; } - (id)initWithFrame:(CGRect)frame albumCover:(NSString *)albumCover { self = [super initWithFrame:frame]; if (self) { // : self.backgroundColor = [UIColor blackColor]; // - 5 : coverImage = [[UIImageView alloc] initWithFrame:CGRectMake(5, 5, frame.size.width-10, frame.size.height-10)]; [self addSubview:coverImage]; // : indicator = [[UIActivityIndicatorView alloc] init]; indicator.center = self.center; indicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge; [indicator startAnimating]; [self addSubview:indicator]; } return self; } @end coverImage . This variable is an image from a cover album. The second variable indicator is an indicator that spins, depicting activity while the cover is loading.*.m ) and not in the header file ( *.h )? Because other classes (outside AlbumView ) do not need to know about the existence of these variables, since they are used only inside the class. This moment is extremely important if you create a library (or framework) for other developers.
Album .AlbumView .ViewController .
AlbumView to display movie and book objects! And if we want to create a completely different project that will deal with albums, we can use the Album class in it - a model that does not depend on the presentation. This is the power of MVC!Album and AlbumView .ViewController 's play two or three roles at the same time. - Approx. per.Model , View and Controller :
Model . Do the same for Controller and View .Model group.View group.Controller group.[NSUserDefaults standardUserDefaults] , [UIApplication sharedApplication] , [UIScreen mainScreen] , [NSFileManager defaultManager] - each of these methods returns a singleton object.Logger (only if you do not write several different logs at the same time). Or the global configuration reference class: it is much better to provide thread-safe access to a certain shared resource (for example, to the settings file) than to have many classes that modify the settings file, possibly at the same time.
Logger class has one instance property (a pointer to a single instance) and two methods: sharedInstance() and init() .sharedInstance() method, the instance property has not yet been initialized, then a new instance of the class is created and a pointer to it is returned.sharedInstance() , we are immediately returned to the instance without initialization. Such a scheme guarantees the existence of only one instance for the entire duration of the program.API . There we will add classes that provide services for our application. In this group, create a class from the template iOS> Cocoa Touch> Objective-C class , a subclass of NSObject , and name it LibraryAPI . @interface LibraryAPI : NSObject + (LibraryAPI *)sharedInstance; @end @implentation line: + (LibraryAPI *)sharedInstance { // 1 static LibraryAPI * _sharedInstance = nil; // 2 static dispatch_once_t oncePredicate; // 3 dispatch_once(&oncePredicate, ^{ _sharedInstance = [[LibraryAPI alloc] init]; }); return _sharedInstance; } LibraryAPI instance. This is the essence of the “loner” pattern: the initialization block will never be executed again.sharedInstance() again, the code inside the dispatch_once block will not be executed (because it has already been executed before), and you will receive a pointer to the previous created instance of the LibraryAPI .API group, create a new class: iOS> Cocoa Touch> Objective-C class , a subclass of NSObject , and call it PersistencyManager . #import "Album.h" @interface : - (NSArray *)albums; - (void)addAlbum:(Album *)album atIndex:(NSUInteger)index; - (void)deleteAlbumAtIndex:(NSUInteger)index; @implementation line: @interface PersistencyManager () { NSMutableArray * albums; // } @end PersistencyManager class after the @implementation line: - (id)init { self = [super init]; if (self) { albums = [NSMutableArray arrayWithArray: @[[[Album alloc] initWithTitle:@"Best of Bowie" artist:@"David Bowie" coverUrl:@"https://s3.amazonaws.com/CoverProject/album/album_david_bowie_best_of_bowie.png" year:@"1992"], [[Album alloc] initWithTitle:@"It's My Life" artist:@"No Doubt" coverUrl:@"https://s3.amazonaws.com/CoverProject/album/album_no_doubt_its_my_life_bathwater.png" year:@"2003"], [[Album alloc] initWithTitle:@"Nothing Like The Sun" artist:@"Sting" coverUrl:@"https://s3.amazonaws.com/CoverProject/album/album_sting_nothing_like_the_sun.png" year:@"1999"], [[Album alloc] initWithTitle:@"Staring at the Sun" artist:@"U2" coverUrl:@"https://s3.amazonaws.com/CoverProject/album/album_u2_staring_at_the_sun.png" year:@"2000"], [[Album alloc] initWithTitle:@"American Pie" artist:@"Madonna" coverUrl:@"https://s3.amazonaws.com/CoverProject/album/album_madonna_american_pie.png" year:@"2000"]]]; } return self; } init method, we collect an array of five albums for example (if you have nothing to do, you can replace them with your favorite music). - (NSArray *)albums { return albums; } - (void)addAlbum:(Album *)album atIndex:(NSUInteger)index { if (albums.count >= index) [albums insertObject:album atIndex:index]; else [albums addObject:album]; } - (void)deleteAlbumAtIndex:(NSUInteger)index { [albums removeObjectAtIndex:index]; } PersistencyManager do in the singleton chapter? The relationship between LibraryAPI and PersistencyManager will be shown in the next chapter.


PersistencyManager for local data storage and HTTPClient for network interaction.Other classes in your project should not think about these two things.LibraryAPI- must refer to instances PersistencyManagerand HTTPClient. LibraryAPI- this is the facade that provides a simple interface for accessing services (storage and transmission of data).
LibraryAPI , HTTPClient PersistencyManager . #import "Album.h" - (NSArray *)albums; - (void)addAlbum:(Album *)album atIndex:(int)index; - (void)deleteAlbumAtIndex:(int)index; #import "PersistencyManager.h" #import "HTTPClient.h" @implementation ): @interface LibraryAPI () { PersistencyManager * persistencyManager; HTTPClient * httpClient; BOOL isOnline; } @end isOnline : , (, ) ? - (id)init { self = [super init]; if (self) { persistencyManager = [[PersistencyManager alloc] init]; httpClient = [[HTTPClient alloc] init]; isOnline = NO; } return self; } isOnline NO .LibraryAPI : - (NSArray *)albums { return [persistencyManager albums]; } - (void)addAlbum:(Album *)album atIndex:(int)index { [persistencyManager addAlbum:album atIndex:index]; if (isOnline) { [httpClient postRequest:@"/api/addAlbum" body:[album description]]; } } - (void)deleteAlbumAtIndex:(int)index { [persistencyManager deleteAlbumAtIndex:index]; if (isOnline) { [httpClient postRequest:@"/api/deleteAlbum" body:[@(index) description]]; } } addAlbum:atIndex: . , , , . «»: - 
Album , :
Album — «», , . ( Album ) , Album , Album .Album . , UITableView .
TableRepresentationAlbumAlbum+TableRepresentation , Album . , ( - ). - (NSDictionary *)tr_tableRepresentation; tr_ — ( TableRepresentation ). , . - (NSDictionary *)tr_tableRepresentation { return @{@"titles":@[@"", @"", @"", @""], @"values":@[self.artist, self.title, self.genre, self.year]}; } Album.Albumwithout inheritance. (If you want to inherit from Album, this can also be done.)UITableView“smart view Album” without modifying the code itself Album.@interface NSString — : NSStringExtensionMethods , NSExtendedStringPropertyListParsing , NSStringDeprecated . , .UITableView , , tableView:numberOfRowsInSection: ( ).UITableView , . , . UITableView « » . : UITableView .UITableView :
UITableView , . , . ( ), . , , ( ).UITableView , UITextView , UITextField , UIWebView , UIAlert , UIActionSheet , UICollectionView , UIPickerView , UIGestureRecognizer , UIScrollView , … , . #import "LibraryAPI.h" #import "Album+TableRepresentation.h" @interface @end : @interface ViewController () { UITableView * dataTable; NSArray * allAlbums; NSDictionary * currentAlbumData; int currentAlbumIndex; } @end @interface ViewController () <UITableViewDataSource, UITableViewDelegate> UITableViewDataSource , UITableViewDelegate — . , . «» .UITableView , .viewDidLoad : - (void)viewDidLoad { [super viewDidLoad]; // 1 self.view.backgroundColor = [UIColor colorWithRed:0.76f green:0.81f blue:0.87f alpha:1.f]; currentAlbumIndex = 0; // 2 allAlbums = [[LibraryAPI sharedInstance] albums]; // 3 // UITableView, CGRect frame = CGRectMake(0.f, 120.f, self.view.frame.size.width, self.view.frame.size.height - 120.f); dataTable = [[UITableView alloc] initWithFrame:frame style:UITableViewStyleGrouped]; dataTable.delegate = self; dataTable.dataSource = self; dataTable.backgroundView = nil; [self.view addSubview:dataTable]; } PersistencyManager !UITableView . , ViewController — (data source) UITableView , , UITableView , ViewController '. - (void)showDataForAlbumAtIndex:(int)albumIndex { // : , if (albumIndex < allAlbums.count) { // : Album * album = allAlbums[albumIndex]; // , TableView: currentAlbumData = [album tr_tableRepresentation]; } else { currentAlbumData = nil; } // , . TableView [dataTable reloadData]; } showDataForAlbumAtIndex: . , reloadData . , UITableView , , , , , .viewDidLoad : [self showDataForAlbumAtIndex:currentAlbumIndex]; currentAlbumIndexwas previously set to 0, we see the 
ViewControllerdelegate and data source UITableView. But!Having done this, we must implement all the required methods (including tableView:numberOfRowsInSection:), but we have not done it yet.@implementationand @end: - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [currentAlbumData[@"titles"] count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"cell"]; } cell.textLabel.text = currentAlbumData[@"titles"][indexPath.row]; cell.detailTextLabel.text = currentAlbumData[@"values"][indexPath.row]; return cell; } tableView:numberOfRowsInSection: , TableView , .tableView:cellForRowAtIndexPath: ( title ) ( value ).
UITableView . .
UITableViewDelegate , UIScrollViewDelegate , NSCoding , NSCopying . , NSCopying copy .
HorizontalScroller UIView .@end : @protocol HorizontalScrollerDelegate <NSObject> // @end HorizontalScrollerDelegate — NSObject ( , ). — NSObject , / NSObject . , NSObject , HorizontalScroller . , .@protocol @end : @required // , - (NSInteger)numberOfViewsForHorizontalScroller:(HorizontalScroller *)scroller; // <index> - (UIView *)horizontalScroller:(HorizontalScroller *)scroller viewAtIndex:(int)index; // <index> - (void)horizontalScroller:(HorizontalScroller *)scroller clickedViewAtIndex:(int)index; @optional // , // ( , 0, ) - (NSInteger)initialViewIndexForHorizontalScroller:(HorizontalScroller *)scroller; HorizontalScroller .HorizontalScroller . , .. HorizontalScroller . What to do?HorizontalScrollerDelegate. So that the compiler knows that we have such a protocol (but will be announced later). Add above line @interface: @protocol HorizontalScrollerDelegate; @interfaceand @end: @property (weak) id<HorizontalScrollerDelegate> delegate; - (void)reload; weak («») ( ) delegate . , «retain-». , , . .id<HorizontalScrollerDelegate> , , HorizontalScrollerDelegate ( ).reload reloadData UITableView : , . #import "HorizontalScroller.h" // 1 #define VIEW_PADDING 10 #define VIEW_DIMENSIONS 100 #define VIEWS_OFFSET 100 // 2 @interface HorizontalScroller () <UIScrollViewDelegate> @end // 3 @implementation HorizontalScroller { UIScrollView * scroller; } @end - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { scroller = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)]; scroller.delegate = self; [self addSubview:scroller]; UITapGestureRecognizer * tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(scrollerTapped:)]; [scroller addGestureRecognizer:tapRecognizer]; } return self; } scroller ) HorizontalScroller . UITapGestureRecognizer ScrollView , . , HorizontalScroller . - (void)scrollerTapped:(UITapGestureRecognizer *)gesture { CGPoint location = [gesture locationInView:gesture.view]; // enumerator, .. . // subviews, : for (int index = 0; index < [self.delegate numberOfViewsForHorizontalScroller:self]; index++) { UIView * view = scroller.subviews[index]; if (CGRectContainsPoint(view.frame, location)) { [self.delegate horizontalScroller:self clickedViewAtIndex:index]; CGPoint offset = CGPointMake(view.frame.origin.x - self.frame.size.width/2 + view.frame.size.width/2, 0); [scroller setContentOffset:offset animated:YES]; break; } } } gesture ), , ( locationInView: ).numberOfViewsForHorizontalScroller: . HorizontalScroller , , ( HorizontalScrollerDelegate ).CGRectContainsPoint . , horizontalScroller:clickedViewAtIndex: . - (void)reload { // 1 - , : if (self.delegate == nil) return; // 2 - subviews: [scroller.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL * stop) { [obj removeFromSuperview]; }]; // 3 - xValue - : CGFloat xValue = VIEWS_OFFSET; for (int i = 0; i < [self.delegate numberOfViewsForHorizontalScroller:self]; i++) { // 4 - : xValue += VIEW_PADDING; UIView * view = [self.delegate horizontalScroller:self viewAtIndex:i]; view.frame = CGRectMake(xValue, VIEW_PADDING, VIEW_DIMENSIONS, VIEW_DIMENSIONS); [scroller addSubview:view]; xValue += VIEW_DIMENSIONS + VIEW_PADDING; } // 5 [scroller setContentSize:CGSizeMake(xValue + VIEWS_OFFSET, self.frame.size.height)]; // 6 - initialView, : if ([self.delegate respondsToSelector:@selector(initialViewIndexForHorizontalScroller:)]) { int initialView = [self.delegate initialViewIndexForHorizontalScroller:self]; CGPoint offset = CGPointMake(initialView * (VIEW_DIMENSIONS + (2 * VIEW_PADDING)), 0); [scroller setContentOffset:offset animated:YES]; } } VIEWS_OFFSET ). 100 , #define .HorizontalScroller ( UIView ) , .HorizontalScrollerLooks at whether the delegate responds to the message selector initialViewIndexForHorizontalScroller:. This check is necessary because this protocol method is optional. If the delegate does not implement this method, the default value is taken 0. This part of the code sets the scrolling view to the center of the view defined by the delegate ( initialView).reloadin the event that our data has changed. Also this method needs to be called when we add HorizontalScrollerto a new view. To do this, add this method to the class HorizontalScroller: - (void)didMoveToSuperview { [self reload]; } didMoveToSuperview , , : , - . .HorizontalScroller — , scroll view. , . - (void)centerCurrentView { int xFinal = scroller.contentOffset.x + (VIEWS_OFFSET / 2) + VIEW_PADDING; int viewIndex = xFinal / (VIEW_DIMENSIONS + (2 * VIEW_PADDING)); xFinal = viewIndex * (VIEW_DIMENSIONS + (2 * VIEW_PADDING)); [scroller setContentOffset:CGPointMake(xFinal, 0) animated:YES]; [self.delegate horizontalScroller:self clickedViewAtIndex:viewIndex]; } UIScrollViewDelegate : - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { if (!decelerate) { [self centerCurrentView]; } } - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { [self centerCurrentView]; } scrollViewDidEndDragging:willDecelerate: , . decelerate («») true , , « ». , scrollViewDidEndDecelerating . ( centerCurrentView ), , , .HorizontalScroller ! . Album AlbumView . Excellent!This means our new scroller has turned out really independent of the content, and it can be reused. #import "HorizontalScroller.h" #import "AlbumView.h" HorizontalScrollerDelegateto the list of protocols that implements ViewController: @interface ViewController () <UITableViewDataSource, UITableViewDelegate, HorizontalScrollerDelegate> HorizontalScroller * scroller; #pragma mark - HorizontalScrollerDelegate methods - (void)horizontalScroller:(HorizontalScroller *)scroller clickedViewAtIndex:(int)index { currentAlbumIndex = index; [self showDataForAlbumAtIndex:index]; } #pragma mark . , IDE — . , AppCode ⌘F12 :
- (NSInteger)numberOfViewsForHorizontalScroller:(HorizontalScroller *)scroller { return allAlbums.count; } - (UIView *)horizontalScroller:(HorizontalScroller *)scroller viewAtIndex:(int)index { Album * album = allAlbums[index]; return [[AlbumView alloc] initWithFrame:CGRectMake(0.f, 0.f, 100.f, 100.f) albumCover:album.coverUrl]; } AlbumViewand transfer it to HorizontalScroller. - (void)reloadScroller { allAlbums = [[LibraryAPI sharedInstance] albums]; if (currentAlbumIndex < 0) currentAlbumIndex = 0; else if (currentAlbumIndex >= allAlbums.count) currentAlbumIndex = allAlbums.count - 1; [scroller reload]; [self showDataForAlbumAtIndex:currentAlbumIndex]; } LibraryAPI, and then sets the current view. Just in case - check for going beyond the array.viewDidLoadfront of the line[self showDataForAlbumAtIndex:currentAlbumIndex]; scroller = [[HorizontalScroller alloc] initWithFrame:CGRectMake(0.f, 20.f, self.view.frame.size.width, 120.f)]; scroller.backgroundColor = [UIColor colorWithRed:0.24f green:0.35f blue:0.49f alpha:1]; scroller.delegate = self; [self.view addSubview:scroller]; [self reloadScroller]; HorizontalScroller, sets the background color, designates itself as a delegate. Then it adds a scroller to the main screen and updates the data in it.UITableViewDelegateand UITableViewDataSource. Both of them are protocols UITableView, i.e. technically could exist in the same protocol, but broken up for convenience. Try to develop protocols so that everyone is responsible for their own functional area.
LibraryAPI, the new method will have to go there. But first you need to make out a few points:AlbumViewshould not work directly with LibraryAPI. We don't want to mix UI code with network interaction, right?LibraryAPIshould not know about AlbumView.LibraryAPImust report AlbumViewas soon as the covers are loaded so that AlbumViewthey are displayed.UIKeyboardWillShowNotification UIKeyboardWillHideNotification , . , UIApplicationDidEnterBackgroundNotification . #import <UIKit/UIApplication.h> UIApplication.h .
initWithFrame:albumCover:after[self addSubview:indicator]; [[NSNotificationCenter defaultCenter] postNotificationName:@"BLDownloadImageNotification" object:self userInfo:@{@"coverUrl":albumCover, @"imageView":coverImage}]; NSNotificationCenter. The notification contains the URL of the image you want to upload, and UIImageViewwhere you want to put this image. That's all that our “subscriber” needs to know, who will receive this notification in order to complete the download task.initimmediately after isOnline = NO: [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(downloadImage:) name:@"BLDownloadImageNotification" object:nil]; AlbumView "BLDownloadImageNotification" , .. LibraryAPI , LibraryAPI . , LibraryAPI downloadImage: .BlueLibrary . - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)saveImage:(UIImage *)image filename:(NSString *)filename; - (UIImage *)getImage:(NSString *)filename; - (void)saveImage:(UIImage *)image filename:(NSString *)filename { filename = [NSHomeDirectory() stringByAppendingFormat:@"/Documents/%@", filename]; NSData * data = UIImagePNGRepresentation(image); [data writeToFile:filename atomically:YES]; } - (UIImage *)getImage:(NSString *)filename { filename = [NSHomeDirectory() stringByAppendingFormat:@"/Documents/%@", filename]; NSData * data = [NSData dataWithContentsOfFile:filename]; return [UIImage imageWithData:data]; } getImage: nil , . ( , , UIImage , . — . . ) - (void)downloadImage:(NSNotification *)notification { // 1 NSString * coverUrl = notification.userInfo[@"coverUrl"]; UIImageView * imageView = notification.userInfo[@"imageView"]; // 2 imageView.image = [persistencyManager getImage:[coverUrl lastPathComponent]]; if (imageView.image == nil) { // 3 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ UIImage * image = [httpClient downloadImage:coverUrl]; // 4 dispatch_sync(dispatch_get_main_queue(), ^{ imageView.image = image; [persistencyManager saveImage:image filename:[coverUrl lastPathComponent]]; }); }); } } downloadImage NSNotification . URL UIImageView .PersistencyManager , .HTTPClient .UIImageView .HorizontalScroller :
UIImageView .initWithFrame:albumCover: [self addSubview:indicator]; [coverImage addObserver:self forKeyPath:@"image" options:0 context:nil]; self ( AlbumView ) image coverImage .@end : - (void)dealloc { [coverImage removeObserver:self forKeyPath:@"image"]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:@"image"]) { [indicator stopAnimating]; } } image . , , .change , . , . nil , => . — . . )
dealloc . , ! - (void)saveCurrentState { // , // , . // . , // NSUserDefaults: [[NSUserDefaults standardUserDefaults] setInteger:currentAlbumIndex forKey:@"currentAlbumIndex"]; } - (void)loadPreviousState { currentAlbumIndex = [[NSUserDefaults standardUserDefaults] integerForKey:@"currentAlbumIndex"]; [self showDataForAlbumAtIndex:currentAlbumIndex]; } saveCurrentState . NSUserDefaults — , iOS.loadPreviousState , . «», .viewDidLoad : [self loadPreviousState]; UIApplicationDidEnterBackgroundNotification , . , saveCurrentState . Conveniently? Yes.viewDidLoad : [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveCurrentState) name:UIApplicationDidEnterBackgroundNotification object:nil]; ViewController , saveCurrentState . - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } ViewController , .
TableView , . Why?initialViewIndexForHorizontalScroller: — . ( ViewController ). . - (NSInteger)initialViewIndexForHorizontalScroller:(HorizontalScroller *)scroller { return currentAlbumIndex; } currentAlbumIndex . .
PersistencyManager init , , Album . , PersistencyManager . . ?Album . , :Album . , NSCoding . Album.h @interface : @interface Album : NSObject <NSCoding> - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.year forKey:@"year"]; [aCoder encodeObject:self.title forKey:@"album"]; [aCoder encodeObject:self.artist forKey:@"artist"]; [aCoder encodeObject:self.coverUrl forKey:@"cover_url"]; [aCoder encodeObject:self.genre forKey:@"genre"]; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super init]; if (self) { _year = [aDecoder decodeObjectForKey:@"year"]; _title = [aDecoder decodeObjectForKey:@"album"]; _artist = [aDecoder decodeObjectForKey:@"artist"]; _coverUrl = [aDecoder decodeObjectForKey:@"cover_url"]; _genre = [aDecoder decodeObjectForKey:@"genre"]; } return self; } encodeWithCoder: . initWithCoder: . , .Album , , / . - (void)saveAlbums; - (void)saveAlbums { NSString * filename = [NSHomeDirectory() stringByAppendingString:@"/Documents/albums.bin"]; NSData * data = [NSKeyedArchiver archivedDataWithRootObject:albums]; [data writeToFile:filename atomically:YES]; } NSKeyedArchiver " albums.bin ".albums ( Album ). NSArray Album NSCoding , .init : - (id)init { self = [super init]; if (self) { NSData * data = [NSData dataWithContentsOfFile:[NSHomeDirectory() stringByAppendingString:@"/Documents/albums.bin"]]; albums = [NSKeyedUnarchiver unarchiveObjectWithData:data]; if (albums == nil) { albums = [NSMutableArray arrayWithArray: @[[[Album alloc] initWithTitle:@"Best of Bowie" artist:@"David Bowie" coverUrl:@"https://s3.amazonaws.com/CoverProject/album/album_david_bowie_best_of_bowie.png" year:@"1992"], [[Album alloc] initWithTitle:@"It's My Life" artist:@"No Doubt" coverUrl:@"https://s3.amazonaws.com/CoverProject/album/album_no_doubt_its_my_life_bathwater.png" year:@"2003"], [[Album alloc] initWithTitle:@"Nothing Like The Sun" artist:@"Sting" coverUrl:@"https://s3.amazonaws.com/CoverProject/album/album_sting_nothing_like_the_sun.png" year:@"1999"], [[Album alloc] initWithTitle:@"Staring at the Sun" artist:@"U2" coverUrl:@"https://s3.amazonaws.com/CoverProject/album/album_u2_staring_at_the_sun.png" year:@"2000"], [[Album alloc] initWithTitle:@"American Pie" artist:@"Madonna" coverUrl:@"https://s3.amazonaws.com/CoverProject/album/album_madonna_american_pie.png" year:@"2000"]]]; [self saveAlbums]; } } return self; } NSKeyedUnarchiver , . , . , . - (void)saveAlbums; LibraryAPI , , . PersistencyManager ', . - (void)saveAlbums { [persistencyManager saveAlbums]; } saveCurrentState : [[LibraryAPI sharedInstance] saveAlbums]; ViewController .Documents ( iExplorer). , . , . Forrest->Run(speed, distance); Forrest ;Run ;Array(speed, distance).NSInvocation , ( ), . , , . «», ( => ). , .UIToolbar NSMutableArray (undo stack).ViewController , : UIToolbar * toolbar; NSMutableArray * undoStack; // , push pop viewDidLoad: " // 2 ": toolbar = [[UIToolbar alloc] init]; UIBarButtonItem * undoItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemUndo target:self action:@selector(undoAction)]; undoItem.enabled = NO; UIBarButtonItem * space = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; UIBarButtonItem * delete = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@selector(deleteAlbum)]; [toolbar setItems:@[undoItem, space, delete]]; [self.view addSubview:toolbar]; undoStack = [[NSMutableArray alloc] init]; viewDidLoad . , ViewController «». ViewController.m : - (void)viewWillLayoutSubviews { toolbar.frame = CGRectMake(0, self.view.frame.size.height - 44, self.view.frame.size.width, 44); dataTable.frame = CGRectMake(0, 130, self.view.frame.size.width, self.view.frame.size.height - 200); } - (void)addAlbum:(Album *)album atIndex:(int)index { [[LibraryAPI sharedInstance] addAlbum:album atIndex:index]; currentAlbumIndex = index; [self reloadScroller]; } - (void)deleteAlbum { // 1 Album * deletedAlbum = allAlbums[currentAlbumIndex]; // 2 NSMethodSignature * sig = [self methodSignatureForSelector:@selector(addAlbum:atIndex:)]; NSInvocation * undoDeleteAction = [NSInvocation invocationWithMethodSignature:sig]; [undoDeleteAction setTarget:self]; [undoDeleteAction setSelector:@selector(addAlbum:atIndex:)]; [undoDeleteAction setArgument:&deletedAlbum atIndex:2]; [undoDeleteAction setArgument:¤tAlbumIndex atIndex:3]; [undoDeleteAction retainArguments]; // 3 [undoStack addObject:undoDeleteAction]; // 4 [[LibraryAPI sharedInstance] deleteAlbumAtIndex:currentAlbumIndex]; [self reloadScroller]; // 5 [toolbar.items[0] setEnabled:YES]; } NSMethodSignature ). NSInvocation , — undoDeleteAction .NSInvocation :undoDeleteAction, add it to the imaginary "stack" ...pushand pop. Everything. - Approx. per.LibraryAPIand update the scroller.NSInvocation, you need to keep in mind the following:2. Indexes 0and are 1reserved for the target and selector.retainArguments . - (void)undoAction { if (undoStack.count > 0) { NSInvocation * undoAction = [undoStack lastObject]; [undoStack removeLastObject]; [undoAction invoke]; } if (undoStack.count == 0) { [toolbar.items[0] setEnabled:NO]; } } lastObject + removeLastObject . , ViewController . — .NSInvocation invoke . , , ( ) — .NSMutableArray — » . , (« , , »).showDataForAlbumAtIndex: if (albumIndex < allAlbums.count) , (albumIndex >= 0) ? , NSArray , AppCode :
int NSUInteger , — — «by design».@ x128 . ru , - .
Source: https://habr.com/ru/post/202960/
All Articles