lecture 2:more Objective-C and Demo

86437913 2019-06-20

这一课新建了几个其他的类以及在做出了第一个小demo


objectivec#import <Foundation/Foundation.h>
#import "Card.h"

@interface Deck : NSObject

- (void)addCard:(Card *)card atTop:(BOOL)atTop;
- (void)addCard:(Card *)card;

- (Card *)drawRandomCard;

@end

老爷爷讲希望能够“通用化”自己建的一些类,比如card,比如deck
所以这里的card,deck不仅仅能用于纸牌

比如我以后要做一个塔罗牌游戏也可以用同样的card deck类

  1. Obj-C中没有默认参数的概念,也没有方法重载的概念,所以addCard因为参数不同,必须定义两次,但实际implement当然可以借助另一个
  2. Deck是由card组成的,所以当然要import card,更何况方法都是针对card的

Deck的实现方法

objectivec#import "Deck.h"

@interface Deck ()
@property (strong, nonatomic) NSMutableArray *cards;// of Card 
@end

@implementation Deck

- (NSMutableArray *)cards
{
    if (!_cards) _cards = [[NSMutableArray alloc]init];
    return _cards;
}

- (void)addCard:(Card *)card atTop:(BOOL)atTop{
    if (atTop) {
        [self.cards insertObject:card atIndex:0];
    } else {
        [self.cards addObject:card];
    }
}

-(void)addCard:(Card *)card{
    [self addCard:card atTop:NO];
}

- (Card *)drawRandomCard{
    Card *randomCard = nil;

    if ([self.cards count]) {
        unsigned index = arc4random() % [self.cards count];
        randomCard = self.cards[index];
        [self.cards removeObjectAtIndex:index];
    }
    return randomCard;
}

@end
  1. 注意少一个参数的addCard是如何巧妙的使用多一个参数的addCard Method的
  2. 因为需要存储card,Array再度出现,但是NSMutableArray,需要具有Mutable的特性
  3. 定义了cards只是定义了pointer,并没有allocate heap,所以有多么巧妙的lazy instantiation,_cards一开始只是nil,然后如果它是nil,我们就[[NSMutableArray alloc]init],,这是一个getter,于是我们首次去调用self.cards的时候它就会帮我们alloc和init,然后就不用怕cards是nil,因为消息是可以发送给nil的(如果不采取这样的措施cards始终是nil?反正就是会出错)
  4. 注意这里的语法[self.cards insertObject:card atIndex:0]
  5. 注意这一篇的代码中用了各种个样的保护措施,比如- (NSMutableArray *)cards中的if和- (Card *)drawRandomCard中的if

PlayingCard是用来描述具体的Card的是怎样的
比如如果我要做一个塔罗牌那么这里就该是TaloCard
然后里面的内容也随之变化?

objectivec//Playingcard.h
#import "Card.h"

@interface PlayingCard : Card

@property (strong, nonatomic) NSString *suit;
@property (nonatomic) NSUInteger rank;
+ (NSArray *)validSuits;
+ (NSUInteger)maxRank;
+ (NSArray *)rankStrings;

@end
  1. PlayingCard是Card的子类,@interface处可看出
  2. 使用NSUInteger或unsigned int是习惯问题,还是跟着老师走吧,同样,没有*号,因为不是object
  3. 出现了类的method,所以是+,并且声明在public interface

objectivec//PlayingCard.m
#import "PlayingCard.h"

@implementation PlayingCard

- (NSString *) contents
{
    NSArray *rankStrings = [PlayingCard rankStrings];
    return [rankStrings[self.rank] stringByAppendingString:self.suit];
}

@synthesize suit = _suit;// because we provide setter AND getter

+ (NSArray *)validSuits
{
    return @[@"♠︎",@"♣︎",@"♥︎",@"♦︎"];
}
- (void)setSuit:(NSString *)suit
{
    if ([[PlayingCard validSuits] containsObject: suit]) {
        _suit = suit;
    }
}
- (NSString *)suit
{
    return _suit ? _suit : @"?";
}

+ (NSArray *)rankStrings
{
    return @[@"?",@"A",@"2",@"3",@"4",@"5",@"6",@"7",@"8",@"9",@"10",@"J",@"Q",@"K"];
}

+ (NSUInteger)maxRank{ return [[self rankStrings] count]- 1; }

- (void)setRank:(NSUInteger)rank
{
    if (rank <= [PlayingCard maxRank]) {
        _rank = rank;
    }
}

@end

这一段简直是信息量无穷,复制粘贴,磕磕碰碰最终的PlayingCard.m看起来很简洁又好看,信息量太大,我只能随手抓一点:

  1. @[...]用来做Array
  2. @synthesize suit= _suit;出现是因为我们给了这个property的setter getter method,setter是setSuit,getter是suit
  3. 调用一个类的method也用类名字,比如值得注意的是这里用[PlayingCard rankStrings]而非[self rankStrings]
  4. 三目运算符 \ stringByAppendingString method \ dot.运算符,信息量.......

objectivec//PlayingCardDeck.h
#import "Deck.h"
@interface PlayingCardDeck : Deck
@end

最后出现的这一个类是真实的包含52张牌的PlayingCardDeck,Deck的子类


objectivec//PlayingCardDeck.m
#import "PlayingCardDeck.h"
#import "PlayingCard.h"

@implementation PlayingCardDeck

-(instancetype)init
{
    self = [super init];

    if (self) {
        for (NSString *suit in [PlayingCard validSuits]) {
            for (NSUInteger rank = 1; rank <= [PlayingCard maxRank]; rank++) {
                PlayingCard * card = [[PlayingCard alloc]init];
                card.rank = rank;
                card.suit = suit;
                [self addCard:card];
            }
        }
    }

    return self;
}

@end

同样,信息量很大

  1. "instancetype " tells the compiler that this method returns an object which will be the same type as the object that this message was sent to.
  2. self = [super init] return self同样是保护措施
  3. 用循序造出了4*13张牌,同时把他们都add到了自身,也就是自己这个deck中
  4. 记得这是deck的子类,所以addCard drawRandomCard都继续可用

一堂课真的很大的信息量,假装理清楚几个类之间的关系了

Card Deck是通用类
PlayingCard PlayingCardDeck是针对这个游戏的具体类

假设做一个塔罗牌游戏的话,Card,Deck不用改,直接拿来用
PlayingCard → TaloCard 然后相应的内容需要改变
PlayingCardDeck → TaloCardDeck 相应的改变其init的方式,但是Talo的init就不会这么简单了,还要加上正立倒立等参数,drawRandomCard可能需要重写,或者就简单的加上一个概率数


Demo非常的精巧,虽然是一个简单的Flip Card 和 计数 Demo

objectivec#import "CardGameViewController.h"

@interface CardGameViewController ()
@property (weak, nonatomic) IBOutlet UILabel *flipsLabel;
@property (nonatomic) int flipCount;

@end

@implementation CardGameViewController

- (void) setFlipCount:(int)flipCount
{
    _flipCount = flipCount;
    self.flipsLabel.text = [NSString stringWithFormat:@"Flips: %d",self.flipCount];
    NSLog(@"flipCount changed to %d",self.flipCount);
}


- (IBAction)touchCardButton:(UIButton *)sender
{
    if ([sender.currentTitle length]) {
        [sender setBackgroundImage:[UIImage imageNamed:@"cardback"]
                          forState:UIControlStateNormal];
        [sender setTitle:@"" forState:UIControlStateNormal];
    } else {
        [sender setBackgroundImage:[UIImage imageNamed: @"cardfront"]
                          forState:UIControlStateNormal];
        [sender setTitle:@"A♣︎" forState:UIControlStateNormal];
    }
    self.flipCount++;
}


@end

很精巧,巧妙在于两处

  1. 要制造卡片来回翻装的使用if / else, 利用卡片(实际上是button)两面不同的title长度来翻转卡片
  2. 使用私有属性flipCount, 然后将flipsLabel上的数字进行更新

好棒的课程,好大的信息量

相关推荐