Categories
english

Elegant Objects by Yegor Bugayenko

How I came to follow Yegor Bugayenko’s blog

I’ve been reading Yegor Bugayenko’s blog (yegor256.com) for a year or so. At the time I was struggling with Core Data, and I could not really explain why until I stumbled upon one of its blog posts entitled along the lines of “Why ORMs are evil”. This post made explicit in my mind what I felt was wrong with ORMs but I could not articulate, and also provided an alternative.

At the time, I was studying a little of Functional Programming and was begining to think that what was wrong might be Object-Oriented Programming in the first place. And then Yegor’s blog opened up my eyes, and I discovered I have been doing it wrong for ages. Not that my code was terrible; it was actually very close to the standards of our industry — which means not so good.

His blog confirmed I was on the right direction on some things: for example, my latest code was written so my objects were immutable, which proved to make them easier to design and test, without any inconvenient in practice. It also made me reconsider my use of abstract classes, by using small protocols (you would say “interfaces” in Java) instead.

A manifesto for Object Thinking

The book is a kind of collection of the most emblematic blog posts he had written. However, it is certainly not copy-and-paste. The book is well organised into four parts — Birth, Education, Employment, Retirement — carefully chosen to emphasize the anthropomorphic nature of Objects. The chapters and paragraphs themselves were rewritten to make the whole book consistent.

Yegor Bugayenko thinks our industry is all wrong with OOP. People on its blog frequently treat him of an «OOP extremist», which he would take as a compliment! As such, the book is very cleaving, with frequent words like “evil”, “all wrong”, “you must”, “I think”. It is very opiniated, which is its greatest quality, a book is meant to present things an other way; otherwise you would not learn anything.

What distingues its discourse from trolling is that each point is argumented. The author tries to convince with examples, how they are wrong and how they could be made better. Most examples are great, a few are awkward, but in all manners, they have the merit to make the reader think.

I would recommend the book to any seasoned OOP programmer, although it is not perfect. In its current state, it looks a lot like a manifesto: it strongly tells what the author is against, but not enough what can be done instead. I wished the author had better explained alternatives that he uses, like the Decorator design pattern, or how he passes dependencies around the application, for example when they are shared resources.

But maybe this first edition had to look like a manifesto, because this thinking is too radical. I wish the second edition will be less defensive and will provide more practical examples.

Categories
français

Solicitation pour un développement sous iOS

Je reçois régulièrement des solicitations pour développer des applications iOS, en échange de parts sur des gains éventuels. La dernière demande était bien plus sérieuse que d’habitude, j’ai donc décidé de répondre de façon détaillée.

J’y explique pourquoi j’ai refusé toutes les demandes qui m’ont été faites jusqu’ici.

Bonjour,

Je suis actuellement pris dans une mission de longue durée; je ne peux donc pas répondre favorablement à votre demande.

Pour être tout à fait honnête, j’aurais probablement refusé. Laissez-moi vous expliquer pourquoi:
Depuis quatre ans, j’ai déjà été sollicité de nombreuses fois pour réaliser des développements, avec des projets plus ou moins sérieux.

L’une de ces solicitations venait d’un ami d’un ami que j’avais déjà eu l’occasion de rencontrer. À cette époque, je débutais comme indépendant, et je n’avais pas de missions, aussi j’avais du temps, et le besoin de faire mes premières réalisations. La première discussion s’est très bien passée: le projet semblait un peu trop ambitieux, mais a priori rentable. Un truc dans l’hôtellerie. Il s’agissait d’une sorte d’appli iPhone en marque blanche que nous allions personnaliser pour chaque hôtel.

Evidemment, ils n’avaient pas d’argent, donc je devais travailler en échange d’une part sur les gains de la vente de l’appli. Tout le monde avait l’air heureux, aussi je me suis mis au travail; nous allions régler les questions contractuelles dans la semaine.

La première douche froide fut quand je reçus les premières conditions contractuelles: non seulement on me proposait peu (parce qu’ils avaient beaucoup de frais de déplacement pour acquérir les clients), mais ils voulaient même que je leur cède la propriété intellectuelle de mon travail.

La deuxième douche froide fut qu’au fil de la semaine, ils ont commencé à réclamer fonctionnalité sur fonctionnalité, ce qui signifiait concrètement pour moi, que je devais travailler plus longtemps. En d’autres termes, j’investissais également les gains financiers que j’aurais eu en travaillant pour d’autres clients.

Finalement, j’ai mi le hola: j’acceptais le partage des gains proposé, mais je délimitais clairement ce que ferait l’appli iPhone. Les discussions se sont arrêtées là. Je ne l’ai jamais regretté.

Ce jour-là, j’ai compris une chose: c’est une relation commerciale qui n’est pas saine. Quand je fais de la prestation, le client et moi délimitons un périmètre, et un prix. Si c’est trop cher, nous pouvons réduire le périmètre.
Mais dans la situation évoquée avant, le client et moi avons des intérêts divergents: moi de travailler le moins longtemps possible pour investir le moins possible, et lui d’avoir le maximum de fonctionnalités pour que son offre soit la plus sexy possible.

Par ailleurs, je ne suis pas un investisseur. Je ne dispose pas d’une réserve pécuniaire suffisante pour travailler des mois pour un gain potentiel. D’autant plus qu’avant d’investir, on se doit d’évaluer le potentiel du projet, et surtout l’équipe.
Parce qu’en pratique, ce que j’ai pu souvent observé, c’est que le porteur de projet n’y apporte rien qu’une idée, une vague expérience et un vague réseau professionnel. À me demander quel serait mon intérêt de m’associer avec un tel partenaire: c’est moi qui travaille et nous devons nous partager les gains.

(Je précise que la phrase précédente ne s’applique pas forcément à vous. En fait, je ne sais rien de votre situation, vous avez sans doute plus qu’une idée, d’autant plus que votre projet a l’air déjà assez avancé).

Bref, j’espère que vous ne m’en tiendrez pas rigueur, et j’ai pris le temps de vous écrire en toute honnêteté le fond de ma pensée; j’espère que cette honnêteté vous aidera à comprendre pourquoi il vous sera difficile de trouver un développeur iOS qualifié pour votre projet. Mais pourquoi pas aussi éventuellement à convaincre un éventuel développeur qui serait éventuellement prêt à se lancer dans l’aventure.

Amicalement,
Renaud

Categories
english

SCNSceneRenderer hitTest:options: limitations

I have met two serious limitations when using Scene Kit’s -[SCNSceneRenderer hitTest:options:] method:

  • it is not sufficient to add a node to the scene for the hit test to find it. The scene must also have been rendered once.
  • the method takes a long time. In my example, about 20 ms in a very simple scene which only contains a couple of spheres. Unfortunately, I had to do it 2000 times, so it takes 40 s ! Totally unusable in my case.

The method seems to have been designed for user interaction, and is only suitable for that case.

(I finally solved the problem by coding my own hit testing, which was possible because I work with a simple sphere. It was not easy because of the lack of information on what the matrix of the SCNCamera really contains, but I eventually managed to reduce the time from 40 s to a couple of milliseconds).

Categories
english

NSFileWarper pitfalls

Always use the .fileWarpers property

Altough keeping a child File Wrapper as a property does not look stupid beforehand, you will run into problems when doing so. The reason is because the parent wrapper might replace its instances of child wrappers by other instances if its see fit.
The solution is to always consult the .fileWarpers property of the parent Wrapper and enumerate child wrappers from it.

Updating files

There is no method to update a FileWrapper. If the content changes, remove the wrapper from its parent and add a new one.

Files named 1_#%$#%$_MyFile are created

This mechanism is used to ensure that the file names are unique. Therefore, if a file named “MyFile” already exists in the directory, and a child File Warper is added with its preferedName set as “MyFile” too, the second file will be named “1_#%$#%$_MyFile”. Hence the name of the “preferedName” property.

In general, this happens because you’ve messed up with the child wrappers — for example, when updating (see above), you forgot to remove the old wrapper before adding the new one with the same name.

# “File already exists” error when calling writeToURL

I’ve run into this during unit testing.
Say you have a file package on disk, and you want to update it. Calling writeToURL:options:originalContentsURL:error: will provoke an error “File already exists”. Pass the NSFileWrapperWritingAtomic and it will work.

I think the reason is because, by default, Apple engineers wanted to ensure that the file would remain untouched if the saving failed. It looks like NSDocument will ensure this, so it does not even set this option.

Categories
english

Zbar support on armv7 and upper

For one of my client project, I needed a QR code reader. I do know that Apple introduced such a reader on iOS 7, but I needed it to run on iOS 6 as well.

Two open-source librairies looked like serious candidates at first:

Zbar

zxing

zxing dropped support for the iOS platform recently. This leaves us with Zbar.

Zbar pre-compiled library does not support modern architectures

However, there is a problem with the Zbar library found in the SDK: it’s been compiled for the armv6, armv7 and i386 architectures only. Therefore, if your application supports more modern architectures like armv7s or arm64, it won’t link with the precompiled library.

Compiling from the source code

The solution is to compile Zbar yourself from the source code, which you can clone using Mercurial. You shall find explanations for Xcode 4 here, and Xcode 5 here.

However, there is an easier way. Someone had the great idea to clone the original Mercurial repository to github and also created a Podspec, so Zbar may be included in your project using Cocoapods.

In your Podfile, write:

pod 'ZBarSDK', '~> 1.3.1'

and voilà! You might still need the documentation for the Zbar SDK.

Categories
english

UILocalizedIndexedCollation shows empty sections

A client wanted his app to show a list of contacts. He wanted each contacts whose name began with the same letter to be in the same section of a table view. However, he was not satisfied with the first version I coded, because empty sections would show. For instance, if there was no contact whose name began with a B, there would be an empty B section.

So, I went back to my code, and I came up with this result:

Capture d’écran 2013-11-18 à 11.29.20

My code only handles letters from A to Z, with no diacritics.  I was not aware of the UILocalizedIndexedCollation class at the time I programmed the first version, and when I discovered it, it seemed to present an interesting feature: it can handle other locales, in particular, Arabic or Asian alphabets.

For the second version, I thought it would be better using it, but I really wasted two hours:

Capture d’écran 2013-11-18 à 11.04.49

UILocalizedIndexedCollation uses a fixed list of indexes, so empty sessions are shown. Just like the first version I came up with. I thought Apple would have had produced a much better implementation.

By the way, Apple’s documentation is way too spare. NSHipster provides us with a good starting point to understand how to use this class.

Categories
français

dopop version 1

J’en avis déjà parlé ici et , mon application iPhone pour apprendre le solfège est disponible. Le mieux pour voir à quoi ça ressemble est d’aller voir le site officiel:

dopop.net

Pour l’instant, l’application ne comporte qu’un seul jeu, qui s’appelle « Quelle est cette note ? ». Les autres jeux arriveront au fur et à mesure, de même que les améliorations de l’existant. Pour l’instant, je me suis avant-tout attaché à faire un premier jeu jouable, intéressant et pédagogique.

Je vais partager les difficultés que j’ai eu lors du développement de cette première version de dopop.

 Sprite Kit

La première version était jouable en juin. Cependant, elle était basée sur l’animation de UIViews, et même si j’aurais pu en rester à cette solution, je lui trouvais plein de défauts:

  • le code était crade
  • les animations étaient parfois étranges, en particulier à cause des courbes Ease in-out de Core Animation.
  • il était difficile de faire des transitions d’écran, ou même d’ajouter des petits éléments marrants.

Ce que j’aurais pu faire était d’utiliser Cocos2D dès le départ, seulement, je n’aime pas cette bibliothèque dont les API sont mal fichues et l’intégration avec UIKit encore pire. Quand Apple a annoncé Sprite Kit à la WWDC, j’ai tout de suite essayé et j’ai été séduit.

Je ne regrette pas cette décision, même si elle a le défaut de limiter le jeu à iOS 7. Finalement, Sprite Kit a bien eu quelques bugs et quelques incohérences dans sa conception, mais tous mes radars furent corrigés pendant l’été, y compris ceux relatifs à Xcode 5.

Sprites et polices de caractères

Ceci m’a fait perdre beaucoup de temps. Je voulais utiliser un format vectoriel pour les notes et la portée. Mon idée était de créer une police de caractère pour cela. En pratique, dessiner les éléments dans Sketch fut rapide, par contre, il fallait utiliser un logiciel pour créer la fonte, et là c’est la cata. En gratuit, le moins pire est FontForge, mais il faut l’installer avec MacPorts, il fonctionne sous X11, et ça se voit.

Ensuite, je pouvais certes dessiner la portée avec la fonte, mais il y avait toujours des problèmes de taille ou d’alignement, d’un pixel ou un demi-pixel. Un beau jour, je me suis dit: « fini c’est conneries ! ». J’ai exporté mes graphismes en bitmap, je les ai mi dans le projet Xcode et je n’ai plus eu de problème.

La leçon apprise: les sprites, par définition, sont des bitmaps.

Niveaux

Une des difficultés quand on crée un jeu vidéo, est qu’il faut définir les niveaux. Pour ma part, j’ai créé un langage un peu spécial pour cela. Par exemple:

[T=76 (F3 G3 A3 B3 C4)%12]
  • Les [ ] délimitent le niveau.
  • T=76 indique le tempo.
  • F3 G3 A3 B3 C4 sont des notes (une lettre qui donne le nom de la note, suivie de l’octave).
  • Enfin, %12 indique qu’il va falloir répéter aléatoirement ces notes 12 fois.

Ce système me permet de décrire précisément le niveau, tout en n’ayant pas à écrire toutes les notes (dans cet exemple il y a 5×12 = 60 notes).

La première difficulté fut de créer un parseur pour ce format. Je n’avais aucune expérience dans ce domaine. Au final, je m’en suis sorti avec des regex et une sorte de machine d’état.

La deuxième difficulté fut de doser la difficulté: les premières versions était quasiment injouables. Il a aussi fallu réduire la durée des niveaux. Vous verrez que le jeu est encore assez difficile, et que vous perdrez souvent: c’est normal. On gagne quand on sait les notes, or cela demande des répétitions pour mémoriser.

Son des notes

Là encore, une grosse perte de temps. Il est important dans le jeu que les notes soit dites. En effet, le but n’est pas que vous deveniez bon à taper sur le clavier à l’écran, mais vous mémorisiez les noms des notes.

Mon idée de départ était de m’enregistrer à chanter toutes les notes: do ré mi fa sol la si, mais aussi A B C D E F G, puisque le logiciel permet de changer de notation. Il fallait le faire pour toutes langues. Le résultat fut décevant. Franchement, c’était horrible, tous les testeurs me l’ont dit.

Une deuxième piste était de créer un synthétiseur vocal chantant. J’ai fait un peu de recherche, et même si ce que je voulais restait simple, j’ai vite compris que c’était un travail bien trop gros. Au final, la version actuelle est un compromis: j’ai utilisé le synthétiseur vocal introduit par Apple dans iOS 7, et je joue la note en même temps.

D’ailleurs, jouer la note n’a rien de simple. Je ne suis pas certain que ce soit la technique la plus facile, mais j’ai carrément créé un synthétiseur basé sur une AudioQueue. Le synthé sonne vraiment bien, c’est une grande fierté.

Organisation

C’est la première fois que j’écris un jeu vidéo. J’avais bien quelques idées sur la manière de faire, mais pas vraiment d’expérience. La difficulté fut donc de savoir comment organiser l’appli. Déjà, j’ai respecté le principe du MVC, avec succès. Ensuite, la conception a émergé progressivement, au fil des remaniements. Au final, ce n’est pas parfait, mais c’est assez modulaire, assez gérable.

Achats in-app

Étant dans le cas le plus simples (achats non-renouvelables), je pensais que ce serait vite réglé, en fait, ça m’a pris une semaine. Pour ceux qui n’en n’ont jamais faits, disons qu’il faut gérer la transaction, et surtout, présenter tout ça dans une interface utilisateur perso, Apple ne fournissant rien de standard.

Au final, le code n’est pas très lourd, mais il y a un tas de petits détails à régler. La doc d’Apple est répétitive et manque d’exemples (comment savoir si un achat a déjà été effectué ?).

Voilà, si vous avez des questions ou des commentaires, ils sont les bienvenus.

Categories
english

Reading Barcodes on iOS 7

iOS 7 introduced APIs to read bar codes using the camera. I could have easily overlooked it without this excellent post from doubleencore.

NSHipster provided sample code, but it missed some details to work. Here is a sample which does.

Sample code

@import AVFoundation;

@interface CEViewController () <AVCaptureMetadataOutputObjectsDelegate>

@property (strong) AVCaptureSession *captureSession;

@end

@implementation CEViewController

- (void)viewDidLoad
{
	[super viewDidLoad];

	self.captureSession = [[AVCaptureSession alloc] init];
	AVCaptureDevice *videoCaptureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
	NSError *error = nil;
	AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoCaptureDevice error:&error];
	if(videoInput)
		[self.captureSession addInput:videoInput];
	else
		NSLog(@"Error: %@", error);

	AVCaptureMetadataOutput *metadataOutput = [[AVCaptureMetadataOutput alloc] init];
	[self.captureSession addOutput:metadataOutput];
	[metadataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
	[metadataOutput setMetadataObjectTypes:@[AVMetadataObjectTypeQRCode, AVMetadataObjectTypeEAN13Code]];

	AVCaptureVideoPreviewLayer *previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.captureSession];
	previewLayer.frame = self.view.layer.bounds;
	[self.view.layer addSublayer:previewLayer];

	[self.captureSession startRunning];
}

#pragma mark AVCaptureMetadataOutputObjectsDelegate

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
	for(AVMetadataObject *metadataObject in metadataObjects)
	{
		AVMetadataMachineReadableCodeObject *readableObject = (AVMetadataMachineReadableCodeObject *)metadataObject;
		if([metadataObject.type isEqualToString:AVMetadataObjectTypeQRCode])
		{
			NSLog(@"QR Code = %@", readableObject.stringValue);
		}
		else if ([metadataObject.type isEqualToString:AVMetadataObjectTypeEAN13Code])
		{
			NSLog(@"EAN 13 = %@", readableObject.stringValue);
		}
	}
}

@end

Pitfalls

The issue I had with NSHipster’s sample code is the delegate method was not called at all. I quickly understood this was because the AVCaptureMetadataOutput must be configured to tell which metadata to recognise.

But, and this was not obvious to me, -[AVCaptureMetadataOuput setMetadataObjectTypes:] must be called after -[AVCaptureSession addOutput:]. Otherwise, the following message shows in the console:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[AVCaptureMetadataOutput setMetadataObjectTypes:] - unsupported type found.  Use -availableMetadataObjectTypes.'

I did try to look at the output of -availableMetadataObjectTypes, and it returns an empty array.

Therefore, -addOuput: must be called before setMetadataObjectTypes. In retrospect, it makes sense: the output object must know it is linked to a video session to know which metadata it may provide.

Categories
français

Une fenêtre qui change de view controller

Ce billet intéressera les programmeurs débutants sur le Mac. Je vais ici vous montrer comment passer d’un panneau à un autre, chacun défini dans son propre xib et géré par une sous-classe de NSViewController.

Capture d’écran 2013-09-16 à 10.09.24

Création du projet et mise en place de la toolbar

J’utilise Xcode 5. Commencez par créer un projet d’application pour Mac.

J’ajoute deux images de 32 x 32 pixels, qui serviront d’icônes pour la toolbar:

Square Circle

 

 

Xcode a créé un premier MainMenu.xib pour votre appli, comprenant une barre des menus et surtout une fenêtre.

Nous allons ajouter une NSToolbar à cette fenêtre. Pour cela, il suffit de glisser une toolbar depuis la rubrique Object Library (dans le coin inférieur droit de la fenêtre de Xcode) vers la fenêtre. La toolbar comporte déjà des icônes. Configurez-la pour qu’elle présente deux icônes en son centre:

Capture d’écran 2013-09-16 à 09.33.27

Pour cela, supprimez tous les NSToolbarItems présents,  glissez de nouveaux items, puis configurez-les.

Maintenant, tirez des actions allant de chaque icône vers l’AppDelegate.

@interface CEAppDelegate : NSObject <NSApplicationDelegate>

@property (assign) IBOutlet NSWindow *window;
- (IBAction)item0Pushed:(NSToolbarItem *)sender;
- (IBAction)item1Pushed:(NSToolbarItem *)sender;

@end

 Création des panneaux

Chaque panneau sera géré par un view controller et sera contenu dans un xib.

Commencez par créer une sous-classe de NSViewController (que j’appelle CECustom0ViewController):

Capture d’écran 2013-09-16 à 09.42.16

Éditez CECustom0ViewController.xib. Ajoutez simplement une NSBox et fixez son titre pour savoir de quel xib il s’agit:

Capture d’écran 2013-09-16 à 09.43.50

Créez une deuxième sous-classe de NSViewController sur le même principe (CECustom1ViewController).

Passage d’un view controller à l’autre

Pour gérer le changement de view controller, je créé une classe CEViewControllerSwitcher, qui hérite de NSObject:

CEViewControllerSwitcher.h

#import <Foundation/Foundation.h>
@interface CEViewControllerSwitcher : NSObject

- (id) initWithParentView:(NSView *)parentView viewControllers:(NSArray *)viewControllers;
@property (nonatomic, assign) NSUInteger viewControllerIndex;

@end

CEViewControllerSwitcher.m

#import "CEViewControllerSwitcher.h"

@interface CEViewControllerSwitcher ()

@property (strong) NSView *parentView;
@property (strong) NSArray *viewControllers;
@property (weak) NSViewController *currentViewController;

@end

@implementation CEViewControllerSwitcher

- (id) initWithParentView:(NSView *)parentView viewControllers:(NSArray *)viewControllers
{
	self = [super init];
	if (self) {
		_parentView = parentView;
		_viewControllers = viewControllers;
		self.viewControllerIndex = 0;  // Afficher le premier VC au départ
	}
	return self;
}

- (void) setViewControllerIndex:(NSUInteger)viewControllerIndex
{
	_viewControllerIndex = viewControllerIndex; 
	// Retirer le VC précédent
	if(self.currentViewController)
	{
		[self.currentViewController.view removeFromSuperview];
	} 

	// Ajouter le nouveau VC
	self.currentViewController = [self.viewControllers objectAtIndex:viewControllerIndex];
	[self.parentView addSubview:self.currentViewController.view];
}

@end

Liaison avec l’AppDelegate

CEAppDelegate.m

#import "CEAppDelegate.h"
#import "CEViewControllerSwitcher.h"
#import "CECustom0ViewController.h"
#import "CECustom1ViewController.h"

@interface CEAppDelegate ()

@property (strong, nonatomic) CEViewControllerSwitcher *viewControllerSwitcher;

@end

@implementation CEAppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
	NSView *contentView = self.window.contentView;
	CECustom0ViewController *viewController0 = [[CECustom0ViewController alloc] initWithNibName:@"CECustom0ViewController" bundle:nil];
	CECustom1ViewController *viewController1 = [[CECustom1ViewController alloc] initWithNibName:@"CECustom1ViewController" bundle:nil];
	self.viewControllerSwitcher = [[CEViewControllerSwitcher alloc] initWithParentView:contentView viewControllers:[NSArray arrayWithObjects:viewController0, viewController1, nil]];
}

- (IBAction)item0Pushed:(NSToolbarItem *)sender {
	self.viewControllerSwitcher.viewControllerIndex = 0;
}

- (IBAction)item1Pushed:(NSToolbarItem *)sender {
	self.viewControllerSwitcher.viewControllerIndex = 1;
}

@end

C’est terminé !

Voilà, ça fonctionne, on passe bien d’un View Controller à l’autre. Il s’agissait de la technique de base, je vous laisse régler l’autolayout des vues pour qu’elles remplissent bien la contentView de la fenêtre comme on le souhaite.

Le projet Xcode complet: ChangeVues

 

Categories
Non classé

dopop

Je me suis lancé depuis quelques semaines dans un nouveau projet. Il s’agit d’une application iPhone qui regroupera plusieurs jeux en rapport avec l’apprentissage du solfège, mais réalisés comme des jeux d’arcade pour ne pas être ennuyeux.

Je vous laisse jeter un œil au site consacré au jeu: dopop.net.