Programming Notes
nacho4d avatar

Programming Notes

@nacho4d
Custom Window Widgets:

This is tricky and didn't really think it would work because all the info found was more than 3 years old. But it did!
I could get a window with custom widgets (Close/Miminize/Maximize and theorically fullscreen buttons), :) . I hope someday I know photoshop as much as objc and make my own cool buttons, for now I have to play with iCal's buttons.



Normal Cocoa NSWindowButtons


Customized NSWindowButtons

The left picture shows the normal cocoa NSWindowButtons, right picture on the right shows darker and yellowish buttons, not very nice on the gray titlebar but ... Is because I don't know photoshop! (涙) But with great potential huh?! (笑)

Introduction:

In cocoa you get the buttons of a window :
NSButton *button = [window standardWindowButton:NSWindowZoomButton];

But button is not really of kind NSButton is in fact an instance of _NSThemeWidget. Furthermore, it does not use a normal NSButtonCell but it has its own _NSThemeWidgetCell. Furthermore, the close button is a subclass of them: _NSThemeCloseWidget and _NSThemeCloseWidget because it has a dirty state of the document

In this post I show how to customize the images of these private classes because setImage: does nothing :(

How this works
I tried to make a new class and pose it as an _NSThemeWidgetCell but posing is being deprecated and is not possible anymore, at least not that I know.

Crayson told me that I should better try the method swizzling approach. This was a huge hint!.
So using a couple of objective-c runtime functions I added a method dynamically to _NSThemeWidgetCell class.

Then it was very easy to exchange methods : the one newly added alt_drawWithFrame:inView: with the other that does the drawing as usual drawWithFrame:inView:.

This part the code and the whole source + the images I borrowed from Lion's iCal can be downloaded from github :)

void drawWithFrameInView(id self, SEL _cmd, NSRect frame, id view)
{   
    NSLog(@"hacking drawWithFrameInView ...");
 
    NSString *imageName = @"titlebarcontrols_regularwin";

    //Get button ID
    int buttonID = (int)[self buttonID];
    NSLog(@"%d", buttonID);
    switch (buttonID)
    {
        case 127: // Close button
            imageName = [imageName stringByAppendingFormat:@"_close"];
            break;
        case 128: // Minimize button
            imageName = [imageName stringByAppendingFormat:@"_minimize"];
            break;
        case 129: // Zoom button
            imageName = [imageName stringByAppendingFormat:@"_zoom" ];
            break;
        case 130: // Toolbar button
            imageName = [imageName stringByAppendingFormat:@"_toolbar_button" ];
            break;
    }

    //Get System preferences: Window style: (Aqua or graphite)
    NSString * const kAppleAquaColorVariant = @"AppleAquaColorVariant";
    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    [userDefaults addSuiteNamed:NSGlobalDomain]; 
    NSNumber *color = [userDefaults objectForKey:kAppleAquaColorVariant];
    if ([color intValue] == 6) {//graphite is 6 
        imageName = [imageName stringByAppendingFormat:@"_graphite"];
    }else{//defaults to aqua, (aqua is 1)
        imageName = [imageName stringByAppendingFormat:@"_colorsryg"];
    }

    //Get button state
    if ([self respondsToSelector:@selector(getState:)]) {
        int state = (int)[self getState:view];
        //NSLog(@"state %d", state);
        switch (state) {
            //Known states
            //active = 0
            //activenokey = not used?
            //disabled = not used?
            //inactive = 3
            //pressed = 2
            //rollover = 1
            case 0: 
                imageName = [imageName stringByAppendingFormat:@"_active"];
                break;
            case 1:
                imageName = [imageName stringByAppendingFormat:@"_rollover"];
                break;
            case 2:
                imageName = [imageName stringByAppendingFormat:@"_pressed"];
                break;
            case 3:
                imageName = [imageName stringByAppendingFormat:@"_inactive"];
                break;
            case 4:
                break;//disabled? activenokey?
            case 5:
                break;//disabled? activenokey?
     default:
                break;
        }

        NSImage *img = [NSImage imageNamed:imageName];
        if (img){
            [img dissolveToPoint:NSMakePoint(frame.origin.x, frame.origin.y + frame.size.height) fraction:1.0];
        }else{
            [(NSButtonCell*)self alt_drawWithFrame:frame inView:view];//original implementation 
        }
    }
}

And the part of code that does the objective-c runtime magic:

@implementation TestAppDelegate
@synthesize window;
- (void)applicationWillFinishLaunching:(NSNotification *)notification
{

    Class class = NSClassFromString(@"_NSThemeWidgetCell");
    SEL new_selector = @selector(alt_drawWithFrame:inView:);
    SEL orig_selector = @selector(drawWithFrame:inView:);

    //Add a new method dinamically because _NSThemeWidgetCell is a private class
    BOOL success = class_addMethod(class, new_selector, 
                (IMP)drawWithFrameInView, 
                "v@:{CGRect={CGPoint=dd}{CGSize=dd}}@");
    if (success) {
        //Get the methods to exchange
        Method originalMethod = class_getInstanceMethod(class, orig_selector);
        Method newMethod = class_getInstanceMethod(class, new_selector);

        // If both are found, swizzle them
        if ((originalMethod != nil) && (newMethod != nil)){
            method_exchangeImplementations(originalMethod, newMethod);
        }
    }

//TEST:a new method should appear "alt_drawWithFrame:inView:" in the console
//uint methodCount = 0;
//class = NSClassFromString(@"UIWebDocumentView");
//Method *mlist = class_copyMethodList(class, &methodCount);
//for (int i = 0; i < methodCount; ++i){
// NSLog(@"%@", NSStringFromSelector(method_getName(mlist[i])));
//}

//TEST: "hacking drawWithFrameInView ..." should appear in the console
//NSButton *but = [window standardWindowButton:NSWindowZoomButton];
//[[but cell] drawWithFrame:NSZeroRect inView:nil];
}
References and useful Links

Instrospection in Objective-C
Sometimes public APIs in cocoa/cocoa-touch frameworks is simply not enough or maybe a private API could save you some work.

So, this is not something new: If you read Objective-c runtime reference you will find really handy and hacky functions there.
These are some snippets:

List up methods of a class
uint methodCount = 0;
Class class = NSClassFromString(@"UIWebDocumentView");
Method *mlist = class_copyMethodList(class, &methodCount);
for (int i = 0; i < methodCount; ++i){
    NSLog(@"%@", NSStringFromSelector(method_getName(mlist[i])));
}
List up protocols conformed by a class
Class class = [UITextView class];
Protocol **p1 = class_copyProtocolList(class, NULL);
for (int i = 0; p1[i]; i++) {
    printf(@"%s\n", protocol_getName(p1[i]));
}
free(p1);
List up ivars of a class
Class class = [UITextView class];
uint ivarsNum = 0;
Ivar *ivars = class_copyIvarList(class, &ivarsNum);
for (int i = 0; i < ivarsNum; i++) {
    printf(@"%s\n", ivar_getName(ivars[i]));
}
Introspect arguments of a method
Class class = [UITextView class];
SEL selector = @selector(keyboardInput:shouldInsertText:isMarkedText:);
Method method = class_getInstanceMethod(class, selector);
char *arg = method_copyArgumentType(method, 0);
printf(@"_%s_\n", arg);
free(arg);

Here you will get: @ for objects, i for integers, f for floats. That is all you get.

Using GDB In Xcode set a symbolic break point to:
-[UITextView keyboardInput:shouldInsertText:isMarkedText:]
or in gdb type:
b -[UITextView keyboardInput:shouldInsertText:isMarkedText:]
so the debugger will stop at that method. When stopped is possible to show the registers values hence is possible to inspect the arguments passed :) This is a more complete list of how to call the registers in different architectures. Since the iOS Simulator is in i386 (after prolog) I can inspect this particular method doing:
(gdb) po *(id*)($ebp + 8)
<MyTextView: 0x5911270; baseClass = UITextView; frame = (80 70; 240 323); text = 'Lorem ipsum dolor sit er ...'; clipsToBounds = YES; autoresize = RM+BM; layer = <CALayer: 0x5c0c7d0>; contentOffset: {0, 0}>

(gdb) p *(SEL*)($ebp + 12)
$1 = (SEL) 0xbd19

(gdb) po *(id*)($ebp + 16)
<UIWebDocumentView: 0xa02f000; frame = (0 0; 240 457); text = 'Lorem ipsum dolor sit er ...'; opaque = NO; userInteractionEnabled = NO; layer = <UIWebLayer: 0x5c35070>>

(gdb) po *(id*)($ebp + 20)
t

(gdb) p *(id*)($ebp + 24)
$2 = (id) 0x0
and finally to get the selector name from a SEL in gdb
(gdb) p (char*)$1
$1 = 0xbd19 "keyboardInput:shouldInsertText:isMarkedText:"

I found this super useful because now I can get the list of method of any class and dig in. Yippeee!

For example I wanted to see if I can customize some shortcuts in the iOS when using a hardware keyboard. But it turns out that the UITextView is not the one who controls that. UITextView seems to be a mere client of UIWebDocumentView which is the one who handles text input and also keyboard events. So overriding private methods of UITextView is just not enough. This is not as simple as I thought to I will save this for another post :)

Links

NSWindow private methods

So, this week I tried Lion almost for the first time, is getting really cool :)

My motivation to do this is because I want to make my non-standard window enter fullscreen (although now I now this is not the way of doing it) it was interesting. So in OSX 10.6 and earlier NSWindow has: performZoom:, performMiniaturize:, performClose:, etc. So I thought there should be something like:performFullscreen: No?

I dug into 10.7 SDK documentation hoping to find something. Unfortunately no, there seems to be no new things in NSWindow?, at least not that I have noticed. Weird... Maybe I am not looking at the correct docs?

I just thought of this:
#import <objc/runtime.h>

unsigned int methodCount = 0;
Method *mlist = class_copyMethodList([NSWindow class], &methodCount);
for (int i = 0; i < methodCount; ++i){
    NSLog(@"%@", NSStringFromSelector(method_getName(mlist[i])));
}
which will print a list of all methods implemented by NSWindow and it worked! I got the whole list. Including the private methods :)

These Objective-C runtime functions must be the Swiss knife of cocoa-hackers. I still have to try this in 10.7 but This is definitely a good start.

PS: Off-course there was no performFullscreen: method. Later I realized that toggleFullScreen: will do the job :)

Bezier Paths : making rectangles with rounded corners

Learning at school Bezier paths was fun for me because is when I finally got to know where all those curves come from. I like the maths, and there are a lot of them behind Bezier paths ;) .

Once the theory is known is not complicated to imagine how a path would look like just by knowing its control points position. But, off-course a normal human like me can only make an approximation inside of his/her head. In real life, you don't approximated paths, specially if you are the kind of person who care about small details like me. So, I decided to do a small research and find out the exact position the control points should have in order to approximate an arc or circle or a rounded rectangle.

My choices were: 1. get a computer graphics book or go to wikipedia, remember the maths and then get a pencil and paper and try to solve it mathematically. 2. Trial and error approach. Even though I like maths I like even more programming so decided to go for number 2 and program test my results in javascript(and put the results here)


In order to make a rounded top-left corner I need to...

First trial:

In first place, I thought: I need a cubic bezier path with one control point in the top-left corner and the other at the end of the curve. This is what I got:

Canvas not supported.

That curve starts at point P0 and ends at P1 and use a couple of control points pc0 and pc1. I obtained a nice curve ... nothing new. However the corner I have just drawn is not exactly overlapping the circle in the picture. It's actually outside.

Second trial:

Having the control points in the middle makes sense as well :
Canvas not supported.

This also looks OK. But Why now it looks like the corner was cut or cropped. It seems logical to think that control points should be at the middle like in this case.

Third Trial:

What about quadratic bezier? This seems to be fine but since we have a circle in there we notice this is not correct too.

Canvas not supported.

Fourth Trial:

So I google it and found approximation of a circle using cubic bezier curve There is a paper that has the maths I decided to skip in first place ... In page 3 Dr. Murtaza found the value of kappa: 0.55... which is the (approximated) distance of control point 1 and 2 should have from start and end point respectively. Control points should be placed as follows:

Canvas not supported.

So, if we want to have a rounded rectangle with corner radius r we have to place pc0 and pc1at (0,k)and (k,0) where k is kappa = 0.552228474...

Here we have an almost perfect rounded corner! ;)

I have implemented this in javascript with the canvas element and all the graphics here were rendered using that function. And also I have made an extension for NSBezierPath class so now I can have paths like:

Here are the sources:
NSBezierPath+Extensions.h
NSBezierPath+Extensions.m

And BezierPaths:
Canvas+BezierPaths.js

References:
Mathworks - Approximation of circle using cubic bezier
Wikipedia - Bezier Paths
MDC Docs - Drawing shapes
And maybe this too:Cocoa Dev Central - Intro to Quartz



Notes on git + github

I've been using SVN for quite a long time. Recently, started using git and github and really like it! I still need to take notes on some commands so this is what this post is about.
There is an excellent book but is too long to get it every time I need something. So here, I summarize what I believe are the most important/frequent things.

Installation

Download the installer from here or use MacPorts or Homebrew and issue:
# for MacPorts
$ sudo port install git-core +svn +doc +bash_completion +gitweb
# for Homebrew
$ brew install git-core

Setup

Required configuration:
#required:
$ git config --global user.name "firstname lastname" 
$ git config --global user.email nacho4d@nacho4d.com
Specially when working with github, these strings must be the same strings as the ones appearing in github Settings
Optional configurations that I find handy
#optional
$ git config --global core.editor emacs
$ git config --global merge.tool vimdiff
$ git config --global color.ui true 
$ git config format.pretty oneline

To see settings inside /etc/gitconfig and ~/.gitconfig do:
$ git config --list

Creating a repo from scratch

$ cd My/LocalRepo/Directory/
$ git init
$ git add *.m 
$ git add README.txt 
$ git commit -m 'This is the message for the first commit'

Creating a remote repo in github

Follow the instructions in github.
(**Warning**: Github needs a README file to show the project correctly on the web. Make sure you have one)

Cloning a repo from github

Get the address of the repo from github and enter like so:
$ git clone git://github.com/schacon/grit.git

File lifecycle

Explaining the lifecycle is not easy (specially if you come from SVN). But I think below explanation is not incorrect :)
  • Untracked: when a file is not version controlled by git
  • Unmodified: when the file is controlled by git and is not modified since the last staging
  • Modified: when the files is controlled by git and it was modified since the last staging
  • Stagged: when the file is in the index, git commit only commits stagged files

Staging a file:

The add command also is for staging not only for tracking files
$ git add filename.txt
$ git status

Ignoring file:

$ cat .gitignore 
*.[oa]     #ignores .o and .a files
*~         #ignores files that end in ~
build/     #ignores build directory
doc/*.txt  #ignores txt files inside of doc directory, is not recursive
(Glob patterns are accepted)

Seeing stagged and unstagged changes
$ git status
$ git diff
$ git diff --cached
$ git diff --stagged

Committing

$ git commit
and enter the message in the editor. Or:
$ git commit -m "message for this commit" 
Or: just commit everything (including not staged files)
$ git commit -a -m "message for my commit with -a option"

If a file was forgotten you can always amend current staged files to the last commit
$ git add forgotten_file.txt 
$ git commit --amend
# and to change the commuter date too:
$ GIT_COMMITTER_DATE="`date`" git commit --amend --date "`date`"

Removing files

$ rm fileToRemove.txt
$ git rm fileToRemove.txt
and remove a file only from the stagged are
$ git rm --cached readme.txt

Working with Branches

# list of local branches
$ git branch

# list of all (local and remote) branches
$ git branch -a

# list of remote branches only
$ git branch -r
Create a local branch named uiFixes (from current branch). Then rename it to ui-fixes, check it out, commit something to it, publish it and finally delete it from the remote
$ git branch uiFixes
$ git branch -m uiFixes ui-fixes
$ git branch ui-fixes
$ git checkout ui-fixes
$ git branch
... some changes happened here ...
$ git commit -am "fixes where done"
$ git push origin ui-fixes
... now others can see/use/merge,etc this branch
$ git push origin :ui-fixes

Tagging

Create a tag v1.0 and push it to the remote. Then push all tags.
Delete tag v1.0 from local repository and finally delete it from the remote.
If a branch is not merged it cannot be deleted so in that case you can force it with -D option
$ git tag v1.0
$ git push origin v1.0          
$ git push --tags               
$ git tag -d v1.0
$ git push origin :refs/tags/v1.0

Renaming a branch:
Create a new tag with that references to the old tag then delete the old one.
# create the new tag
$ git tag new_tag old_tag
# delete the old tag locally
$ git tag -d old_tag
# delete the old tag from the remote
$ git push origin :refs/tags/old_tag

Checking out a tag:
# Create a branch and checkout
git checkout -b 1.6 refs/tags/v1.6
# Just checkout out the tag
# (Useful when looking at it and no changes are planned to be done)
$ git checkout refs/tags/v1.6

Logging

Shows a list of commits:
# Show last 10 commits
$ git log -n 10

# Show commits with a graph
$ git log --graph

# Compact 
# git log --graph --pretty=oneline --abbrev-commit

Diffs

compare a file with current HEAD
$ git diff file.h

Stash

# stash current changes without a label
$ git stash
# stash current changes with a label
$ git stash save "Put a message here"
#
# stash only unstaged (or non-added) changes
# Note that newly created (and non-added) files will remain in
# your working directory. Also, your working directory must be
# clean (i.e. all changes need to be added) when you `git stash
# pop` later on.
$ git stash -k

$ ...

# list of all stashed things
$ git stash list
# will show something similar to:
stash@{0}: On feature/52: Put a message here
 
$ ...


# Apply the stash to the current branch
$ git stash apply 
# Apply and remove it from the stash list
$ git stash pop

$ git stash clear

Misc:

# show info about remote branches
$ git remote show origin
# export last commit in the current branch
$ git archive -o latest.zip HEAD

Rebase

# interactively rebase (change/modify) the last 4 commits:
$ git rebase -i HEAD~4

# Mainly I use it for:
# "squash" various commits in a single one
# "reword" the message of the commit

# forced push of a branch (not merging)
$ git push origin feature/new-feature -f



Patches

# Make a patch of a certain commit:
$ git format-patch <SHA>^..<SHA>
# It will create a file, let's say: file.path.

# Check the changes that are in the path:
$ git apply --stat file.path

# Apply the patch
$ git apply file.path
# or
$ git am file.path

Update your fork from Original repo

# Add (additional) remote
$ git remote add --track master mleung git://github.com/mleung/feather.git
# 'master' is the tracking branch and 'mleung' is the tracked branch from the given repo

# verify 
$ git remote

# get lattest changes from mleung
$ git fetch mleung

# merge those changes (mleung/master) into your current branch
$ git merge mleung/master

Gitflow, etc

# check out "develop" remote branch and then start git flow
$ git fetch
$ git checkout -t origin/develop
$ git branch

# initialize git flow
$ git flow init

# publish and track "develop" manually
$ git push origin develop
$ git branch --set-upstream develop origin/develop
$ git remote show origin

# feature start and finish
$ git flow feature start markdown-links
$ git commit -am "markdown now shows links in blue color"
$ git flow finish markdown-links

# hotfix start and finish
$ git flow hotfix start my-hotfix
$ git commit -am "I have solved a really serious bug"
$ git flow hotfix finish my-hotfix

TODO:

creating and merging local branches (?)
merging and pushing to remote repository (?)
git merge (?) not only to master branch but to any branch
git revert (?) to any point in the past
git reset (?)
git remote (?)
git help **commmand**(?)


References:


Grand Central Dispatch

最近、研究のPCのプログラムをiPhoneに移植することに挑戦していてマルチスレッドプログラミングを授業とかでやったが実践で使うのは初めてで、色々と勉強になったから 参考になったもののまとめとしてこの記事を書くことにした。
色々と忘れそうで...

以下GCDと呼ぶGrand Central DispatchはMacOS 10.6から使えてブロックを使いまくるライブラリです( 笑)WWDC2009に参加したときブロックの話が多くてイメージしていたが実際に使う機会がなくて やはり研究の様な重い処理や時間が掛かる様なものじゃないと使うまでもないし-[NSObject performSelectorOn...]やNSOperationなどがあって低レベルのブロックを普段使うまでもありませんね。
WWDC2010ではGCDで盛り上がった.GCDはオプンソースになって現時点でMacOSとiOS以外に実装したOSはFreeBSDのみ

特に参考になったサイトは:
などなど


では、

インポート

まず、GCDはフレームワークではない、dylibなので
#import <dispatch/dispatch.h>
と書くだけでGCDのAPIが使える。

キューの作成

  • メインキュー : メインスレッドで実行
    dispatch_queue_t main = dispatch_get_main_queue();

  • グローバルキュー : バックグラウンドで実行
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
    //priories:
    //DISPATCH_QUEUE_PRIORITY_HIGH
    //DISPATCH_QUEUE_PRIORITY_DEFAULT
    //DISPATCH_QUEUE_PRIORITY_LOW

  • プライベートキュー : バックグラウンドで実行、名前付きのキュー
    dispatch_queue_t queue = dispatch_queue_create("com.nacho4d.myApp.myQueue",NULL);
    デフォルトではプライオリティは DISPATCH_QUEUE_PRIORITY_DEFAULT だが変えることが出来る:
    dispatch_queue_t high = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,NULL);
    dispatch_set_target_queue(queue,high);
    

    他のタイプのキューとの大きな違いはブロックや関数が逐次的に実行されていくこと。他のキューではタスクをエンキューしても実際にどの順番で実行されるかが分からないが、プライベートなキューでは必ずキューの頭からタスクを実行していくのでシリアルなキューを実現できる。
    プライベートなキューを作成するとRetainカウントが1なので、 最後にリリースをすることを忘れずに
    dispatch_release(high);
    

キューの停止

停止できるが、実行中のブロックを止めることができません。停止がその次のブロックから適用される、つまりNSOperationQueueのsetSuspended:と全く同じ
dispatch_suspend(queue);

非同期と同期

実行するキューを指定し、ブロックを非同期的にまたは同期的に実行
dispatch_async(queue,^{/* 非同期 */});
dispatch_sync(queue,^{/* 同期 */});
例:
dispatch_queue_t queue = dispatch_queue_create(“com.app.task”,NULL)
dispatch_queue_t main = dispatch_get_main_queue();

dispatch_async(queue,^{
 CGFLoat num = [self doSomeMassiveComputation];

 dispatch_async(main,^{
  [self updateUIWithNumber:num];
 });
});
または
dispatch_queue_t queue = dispatch_queue_create(“com.app.task”,NULL);

__block CGFloat num = 0;

dispatch_sync(queue,^{
 num = [self doSomeMassiveComputation];
});

[self updateUIWithNumber:num];

ブロックを後で実行

-[NSObject performSelector:afterDelay]などのメソッドの代わりに使えるdispatch_after関数がある.利点:メソッドは不要

void dispatch_after(
   dispatch_time_t when,
   dispatch_queue_t queue,
   dispatch_block_t block);

例:0.3秒後にメインスレッドでブロックを実行する(UIKitのUITextViewを扱うのでメインスレッドに変えている).
dispatch_after(300, dispatch_get_main_queue(), ^{
        UITextView *textView = (UITextView *)[controller.view viewWithTag:12345];
        [textView removeFromSuperview];
        textView = nil;
    });

Forループの並列化

重い処理を分けて行って高速化を!
void dispatch_apply( size_t iterations, dispatch_queue_t queue, void (^block)(size_t));
例えば、CoreVideoのイメージバッファーの中から特定なチャネルだけコピーしたい場合は普段このように行うでしょう:
uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer); 
size_t width = CVPixelBufferGetWidth(imageBuffer); 
size_t height = CVPixelBufferGetHeight(imageBuffer); 

char *imageData = llahProcessor.inputImageDataBaseAddress;
int stepWidth = width*3; //stepWith of output image
for (int j = 0; j < height; j++) {
 for (int i = 0; i < width; i++) {
  int out_pixIndex = j*stepWidth + i*3;
  int in_pixIndex = j*bytesPerRow +i*4;  
  imageData[out_pixIndex]   = baseAddress[in_pixIndex]; //B
  imageData[out_pixIndex+1] = baseAddress[in_pixIndex+1]; //G
  imageData[out_pixIndex+2] = baseAddress[in_pixIndex+2]; //R
 }
}

しかし、dispatch_apply関数を用いて並列化を行うことができます:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_apply(height, queue, ^(size_t j) {
   for(int i = 0; i < width; ++i ) {
    int out_pixIndex = j*stepWidth + i*3;
    int in_pixIndex = j*bytesPerRow +i*4;
    imageData[out_pixIndex]   = baseAddress[in_pixIndex]; //B
    imageData[out_pixIndex+1] = baseAddress[in_pixIndex+1];//G
    imageData[out_pixIndex+2] = baseAddress[in_pixIndex+2];//R
   }
});
GCD並列処理を行っているので理論上では早いですが、例えば640x480の画像でforループの480個を同時に走らせとうしている!つまり実際に並列処理をするには480個のキューが必要だがそんな膨大な数のキューを作るには少し時間が掛かるしおそらく全部作れないのでエンキューされるforループのあるでしょうし 640x480の画像を処理を一瞬で終わるのでこの例は現実的ではないですえ。dispatch_apply関数の使い方だけに着目してください。マルチスレッドの話は難しくて長過ぎて教科書を読むが一番ですね

シングルトン

Colin Wheeler氏が指摘するようにブロックを使ったシングルトンの方がマルチスレッドセフティでその為に dispatch_onceを使います. dispatch_onceはアプリケーションが実行している間に一回のみ実行されると保証してくれる関数で便利ですね。
+(MyClass *)singleton {
 static dispatch_once_t pred;
 static MyClass *shared = nil;
 
 dispatch_once(&pred, ^{
  shared = [[MyClass alloc] init];
 });
 return shared;
}
まぁ〜 これでGCDのことをちょっと忘れたときにちらっと見れば思い出すかと ... うむ

怠け者の為のObjective-Cのメモリ管理

私はメモリ管理関連のの記事を沢山読います.特にiPhone開発初めてからリークなきクラッシュなきアプリの開発に心掛けた。(モバイルデバイスだから下手に扱うとすぐメモり不足の警告が出されたりするから)

そして、最近ツイッターでこの記事(英文)を見つけたので、簡潔で非常に分かりやすくて日本の方に是非読み頂ければという意思でこの記事を翻訳することにした.(簡潔で非常に分かりやすいですよ!)
私はネイティブではないので、おそらく間違っているところがあるだろうが、エッセンスを理解して頂ければと思う.(→最近友人に見てもらって色々と修正をしてくれたので大感謝!)



James Gregoryさんが想像しているよりObj-Cでメモリの管理は簡単

メモリ管理はということばを聞くとスクリプティングする若者たちとJava世界の人が怖がることを私は知っています。でも、実はそんな怖くないんだよ。詳細を言う前にJames Gregoryさんはどう思うかを見てみよう.


iPhoneにないの?!。マジ、アップルよ 一体なんだよ!? ガーベージコレックションがなぜそんなに凄いかと説明しようとするギークが沢山いるから、ここは短く言う。すばらしい理由が基本的に2つある.
一つ目、さらに他の言語のメモリのオーナーシップ(所有権)、いつリリースするベキかとかを理解する暇はないから.
二つ目、開発言語の特徴の中で 開発のプロセスを最も加速するガーベージコレックション(GC)の他に思い当たらないからだだ.
アンドロイドにはあるし、ハードウエアに直接関係しているプログラム以外(つまり、ほど全て)の場合は効率的だ。初代のiPhoneにはなかった理由が分かるかもしれないが 最近のiPhoneにない訳ないだろう。メモリ管理があまりにも大変でこれだけで初心者の開発者にiPhoneよりはアンドロイドを始めることを薦めると思うよ.



えっ!? それは違うよ。メモリ管理について怠け者たちが、大変だと誤解しているんじゃないかなぁ〜。

「さらに他の言語のメモリのオーナーシップ(所有権)、いつリリースするベキかとかを理解する暇はないから」というのは、デベロッパーがめんどくさいな と思っているだけじゃないかなぁ〜。

オブジェクト指向的なメモリ管理の基本は一緒。C++やらObjectPascalでもGCなしの言語なら みんな一緒だ. コンピュータサイエンスの基本の概念でObjective-Cに使うのはほぼささいなことだ.

もし、私は面接官で面接受けている人がそういうこと言ったら、その時点で面接が終了したなぁ、きっと.

しかし、私はGCがあるべきだと思うよ. GCの利点が知らなかったり、分っていないと勘違いしないでください。だから「手動のメモリ管理が大した苦労ではない」というときに あなたはそれを聞いた方がいいと思う。特にObjective-Cでは.

デスクトップのCocoaで開発する(私は結構書くなぁ)ときにCGを使うときがある. プログラムが少し大きくなって、ちょっと調べたり研究したりしているときにCGを使うことが多い。一旦自分が調べたことや試していたことが証明できたらCGを無効にして、全てをリファクタリングするから.

だから、時々GCを使うなら、いつも使えばいいじゃ〜. メモリの管理しなくてもいいから. それにプライベートフレームワークを作って再利用する傾向があるから、それらを動かす為にCGが無効になっているか有効になっているかを見なくてもよくなるし.

Objective-Cのretain/releaseの仕組みは 根本的なメモリ管理とGCの中間的な仕組みだから. C++とかと違って実際にオブジェクトをfreeするわけではない.代わりに「僕は このオブジェクトを使い終えたよ!」と言ってやって あとランライムに任せばいい.




基本の4つのルール

Objective-Cを学ぶときに覚えなければルールが4つだけ.
  • オーナーシップを持つなら、リリースするベキ
  • オーナーシップを持たないなら、リリースしちゃダメ
  • 自分のクラスの実装でdeallocをオバーライドし、オーナーシップを持ったものをリリースするベキ
  • deallocを直接呼び出すベキでない
それは全てだ. 上の2つが最も重要で オーナーシップの基本を次に説明する.

ルール1: オーナーシップを持つとき

Objective-Cでは オブジェクトをallocか、initか、newをしたらオーナーシップを持つころになる.例えば、


簡単だね、これより詳細な説明も思いつかないぐらい。しかし、確認の為に
allocしたらオーナーシップを持つことになる.
copyしたらオーナーシップを持つことになる.
newしたらオーナーシップを持つことになる.(newはalloc/initのショットカットだけ)
〜〜〜Jamesさん、もしまだ読んでいたら 既に4分の1を終えているから、もう少し読んだらソフト掛けるようになるゼ〜〜〜

ルール2:オーナーシップを持っていないとき

これはちょっとややこしいんだ. もっていないときはもっていないんだよ. だから、もしalloc/init/newいずれもしていなかったら 持っていないのだ. 次の例を見てみよう:

そのNSStringのオーナーシップを持っている?いいえ、alloc/copy/newもしなかったから持っていない.
そのNSImageは?はい、持っている.allocしたから.
そして、そのNSData? これも、いいえ. alloc/copy/newどれもしなかったからだ.
ちなみに、本当はこう直すベキ:

〜〜〜そうだね、GCより遥かに大変だわ! 普段ココアの開発者はどういうふうにやってんの?〜〜〜

ルール3と4: dealloc

これは一番大変なところだ. クラスの中にretainしているインスタンス変数があったら、オブジェクトがdeallocされるときにそれらのインスタンス変数をreleaseしないといけない.これを示すのは次の例:

上記の例では、SomeObjectは二つのインスタンス変数がある. thingsとsomeOtherThingsだ. initメソッドを見て分かるのは 二つをオブジェクトを生成して、それらをインスタンス変数にアサインしていること.
thingsに関しては [NSMutableArray arrayWithObjects]を使っているからretainを呼び出す必要がある. alloc/copy/newいずれもしない. autoreleaseしたオブジェクトを返すコンビニエンスメソッドを使ったから 明示的にretainをしなければいけない. autoreleaseしたオブジェクトとは何かと思っているかもしれないが それはその次に説明する. とりあえず、上記に説明したメモリ管理の基本だけを頭に入れておいてください.
deallocメソッドに 生成したオブジェクトをリリースしているだけです。それ以上はなにもありません.
(ちなみに、コンビニエンスメソッドとは:alloc/init/newで始まらないオブジェクトを生成してくれるメソッド。例: [NSArray array]はコンビニエンスメソッド、[[NSArray alloc] init]はコンビニエンスメソッドではない。ご存知の通り、コンビニエンスというのは便利の意味を持っていて、releaseしなくてもいいから便利という理由で名前持っているらしい. @nacho4d)


プローパーティ

Objective-C 2.0はプローパティーを提供してくれる.その詳細はここでは話さないが プローパーティのライフサイクルの中で混乱が生じやすいところがあるので 少しだけ触れたいと思う. ルールは2つのみ
プローパティーの属性はretainまたはcopyだったら deallocにnilをセットするベキ.
initメソッドなどでプローパーティを初期化(init)しているならば autoreleaseにするベキ.
例を見てみよう.


titleプローパーティに関して NSStringのコンビニエンスメソッドを使って生成しているからautoreleaseしたオブジェクトが返ってきて 何もする必要がありません.
subtitleプローパティーに関して 私たち、自分で、オブジェクトを新しく(alloc/initを使って)生成しているのでautoreleaseをする必要があります.
理由はこれから説明します. alloc/initを使ってretainカウントが1になり、subtitleにアサインすると一個増えるので2になってしまう. autorelease無しで行うとリテインカウントが決して1に戻らなず メモリリークが発生してしまう. だからautoreleaseを入れることで リテインカウントが1個減らしている.

オートリリースとは?

実は完全に嘘をついた、完全に.これは実は結構複雑な仕組みで その複雑さはオートリリースプール(Autorelease pool)という形で来ている。それは一体なんだ? オートリリースプールはreleaseメッセージを送られる対象オブジェクトのリストを持っている。いつ送られるかというとプール
は破壊される(destroyed)また排出させた(drained)ときです。 しかし、それはいつ起きるかを気にしなくても大丈夫です. 正しくautoreleaseメッセージを送っておければ オブジェクトがちゃんとリリースされるからだ.

さて、基本のルールに従って行えば オブジェクトを返すメソッドについての疑問がある筈だなぁ。このオブジェクトのオーナーシップを誰が持っているの?ここはオートリリースプールの出番だ.
例えば:

そのNSImageのオーナーシップを誰が持っている? ここはねぇ、この例では、メッセージを送ったオブジェクトはオーナーシップを持つことになる.よって送られた側にreleaseメッセージを送る必要がある。しかし、これは悪い例です.正しいのは次のよう:

Autoreleaseメッセージが最後にあることをに着目しよう.autoreleaseをすると そのオブジェクトがオートリリースプールに追加され poolが破壊されるときに releaseメッセージが送られます.
(なぜ一個上のが悪い例でこれは良い例かという説明を次にします。
実はメモリ管理の観点から見ると両方とも問題はありません。後でreleaseするか、さきにautoreleaseをするかどっちも一緒です。
しかし、前者の方がCocoaのコンベンションの一つに違反しています。 
私は上に書いたコンビニエンスメソッドの定義によると、alloc/init/newで始まるメソッドを送るとオーナーシップを持つことになります。
逆に言えば、そうで始まらないメソッドを送るとオーナーシップを持たないことになりますね。そして、getAnImageは何かの形でオブジェクトを生成しています。かつ、alloc/init/newで始まらないから コンビニエンスメソッドですよね?こう言ったコンビニエンスメソッドが CocoaのコンベンションによるとAutoreleasedオブジェクトを返す筈だよね?、だから コンベンションに従って書くようにしましょう. @nacho4d)

オートリリースプールの中にさらに別のオートリリースプールはあってもいいので、大量の一時的なオブジェクトを生成するときに便利. そして、もしマルチスレッドの処理をしていれば、新しいスレッド用のオートリリースプールを作る必要があります.

まとめ

思ったより、そんな怖くないし、複雑ではなかろう. 勿論GCを全然簡単だが、例えGCがあっても手動メモリ管理に向いている処理が沢山があって、手動メモリ管理がメーディアやゲームなどのアプリケーションでもっとも使われている。
このようなアプリケーションにはメモリ管理は極めて重要な役割で、知らないとうまくいかない場合が沢山あるよ. ありがたいことに CocoaとObjective-Cを使うとそんなに苦労しなくて済む.

参考文献

実はアップルのドキュメンテーションで十分な筈だが、すばらしい記事が沢山かあって...
などなど...

This work is licensed under BSD Zero Clause License | nacho4d ®