Wednesday, February 20, 2019

xCode 10 Swift 4.2 slow autocomplete and compile times. BAD.

At some point I started to have dismal swift autocomplete delays in xCode 10 and slower compile times. Having googled this and that and optimizing what I could I also found into these type of warnings:



My first guess was that longer tuples are causing the problem. I changed this part to use structs, no joy.

One more part in the code gave me another hint:



Looked to me as if just adding up literals via "+" would be tricky for swift compiler's type checking.

Changing everything above to String(format:) resolved the type checking speed.

If you ask on using string interpolation with \() - it didn't solve the problem. Plus I don't like these messy strings when they get longer.

I have no good words for the guys that work on Swift programming language and its design/tools. I don't think this part and types of optimization should really ever bother us, regular devs.

Bad, bad, bad.... I wish I could have loved Swift more. I love the language even if I don't think it is superior that much to Objective-C. Moving to it from Objective C with all new and updated stuff, but this is simply bad.

P.S. You can find a really good digest of Swift compile time optimizations here:

https://github.com/fastred/Optimizing-Swift-Build-Times

Tuesday, January 1, 2019

TIL - Elixir Jason.Encoder - handling optional fields

At some point along my learning road I had a location struct with the following fields:

@derive Jason.Encoder
defstruct latitude: -999, longitude: -999, speed: -1.0, altitude: -1.0, bearing: -1.0

I added one more field:

defstruct latitude: -999, longitude: -999, speed: -1.0, altitude: -1.0, bearing: -1.0, timestamp: 0.0

And my tests for encoding the struct values after reading these older location records from a file started to fail.

Turns out this @derive part is generating the code similar to this:

defimpl Jason.Encoder, for: [Location] do
def encode(%{altitude: _, bearing: _, latitude: _, longitude: _, speed: _, timestamp: _} = value, opts) do
Jason.Encode.map(Map.take(value, [:altitude, :bearing, :latitude, :longitude, :speed, :timestamp]), opts)
end
end

ref: https://hexdocs.pm/jason/Jason.Encoder.html#content

So older records can't be matched by this automatically generated definition. 

Considering that as a part of "support older data with a fallback value for the new field" exercise and based on Jason.Encoder documentation:

1. I removed the @derive Jason.Encoder line to avoid the automatic protocol implementation generation.
2. Provided own implementation that supports the old data stored:

require Jason

defimpl Jason.Encoder, for: [Location] do
# Matches the default implementation of @derive Jason.Encoder
def encode(%{altitude: _, bearing: _, latitude: _, longitude: _, speed: _, timestamp: _} = value, opts) do
Jason.Encode.map(Map.take(value, [:altitude, :bearing, :latitude, :longitude, :speed, :timestamp]), opts)
end
# Support for older data
def encode(%{altitude: _, bearing: _, latitude: _, longitude: _, speed: _} = value, opts) do
Jason.Encode.map(Map.take(value, [:altitude, :bearing, :latitude, :longitude, :speed]), opts)
end
end

This it for my Today I Learnt.

References:
https://github.com/michalmuskala/jason

And as this post started with adding a timestamp field, here you can read on timestamps in Elixir. From the Jason.Encoder author:
https://michal.muskala.eu/2017/02/02/unix-timestamps-in-elixir-1-4.html

Monday, August 14, 2017

Copying comments in Google spreadsheet into a column

Will copy every comment on a cell in a column into a cell next to the source column (the one with the comments):

function commentsToColumn() {
  // find selection (the source comments)
  var workbook = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = workbook.getActiveSheet();
  var source = sheet.getActiveSelection();

  // determine target (the column to the left of the selection)
  var target = source.offset(0, +1);

  // copy comments
  comments = source.getComments();
  target.setValues(comments);
}

If you need more info for usage, please ask in comments :).

Sunday, July 2, 2017

iOS 11 beta 1,2 - NSString sizeWithAttributes - breaking change and potential bug

Edge behavior of [NSString sizeWithAttributes] seems to be changed in iOS 11 beta 1 and 2. When asked for the


float size around 0.0;
    
    NSString *testString = @"Some test string";
    NSString *fontName = @".SFUIText-Semibold";
    
    UIFont *targetFont = [UIFont fontWithName:fontName size:size];
    
    CGSize stringUISize = [testString sizeWithAttributes:@
                           {
                           NSFontAttributeName: targetFont
                           }];

You may get very different from iOS 7- 10 values. Here is the test:



While running it on iOS 7-10, everything converges to zero string UI width:

2017-07-02 10:11:45.996 speedometer[23301:7084973] attempted font size: 1, calculated string width: 12.157471, delta: -12
2017-07-02 10:11:45.997 speedometer[23301:7084973] attempted font size: 0, calculated string width: 4.052490, delta: -4

2017-07-02 10:11:46.005 speedometer[23301:7084973] attempted font size: 0, calculated string width: 0.000000, delta: 0

 while on iOS 11 beta 1 and 2 the same test case is "stuck" on value 97 something on iPhone 6 Plus:

2017-07-02 08:29:07.157511+0200 speedometer[36517:257340] attempted font size: 4, calculated string size: 36.437256, delta: -36
2017-07-02 08:29:07.158646+0200 speedometer[36517:257340] attempted font size: 3, calculated string size: 28.340088, delta: -28
2017-07-02 08:29:07.159723+0200 speedometer[36517:257340] attempted font size: 2, calculated string size: 20.242920, delta: -20
2017-07-02 08:29:07.160738+0200 speedometer[36517:257340] attempted font size: 1, calculated string size: 12.145752, delta: -12
2017-07-02 08:29:07.161953+0200 speedometer[36517:257340] attempted font size: 0, calculated string size: 4.048584, delta: -4
2017-07-02 08:29:07.166740+0200 speedometer[36517:257340] attempted font size: 0, calculated string size: 97.166016, delta: -97
2017-07-02 08:29:07.167756+0200 speedometer[36517:257340] attempted font size: -1, calculated string size: 97.166016, delta: -97

2017-07-02 08:29:07.168736+0200 speedometer[36517:257340] attempted font size: -2, calculated string size: 97.166016, delta: -97

Looks to me as a broken "contract" and invariant. One would really expect the consistent behavior as it was since iOS7 and having the width of the string calculated as zero for zero font size and zero it should be downwards? Also string width jumping from about approaching zero values to 97.166016 when value got a bit on below zero looks inconsistent. Probably a missing unit test :)!

For me it looks like a bug and I'm reporting it to Apple now. Hopefully it will be fixed to the same consistent behavior as it used to be on iOS 7 - 10.

Defensive coding is like an alloc/release for me, I try to make it right and upfront, but doing static analysis later I found myself being "mentally away" way too often.

Yours and wishing you happy coding!
Stan.

Tuesday, April 11, 2017

UINavigationController - ask for confirmation on Back

Didn't expect this to be such a problem, but once you tap into the UINavigationController things become hairy.

Requirement:

When user leaves a screen by tapping on a "back" in navigation bar and there are changed data in the screen I should ask for a confirmation and keep user at the current UIViewController if she decided to continue editing the data.

Solution.

You may run into this: http://stackoverflow.com/questions/1214965/setting-action-for-back-button-in-navigation-controller/19132881#19132881 (particularly this: https://github.com/onegray/UIViewController-BackButtonHandler).

Once, I was trying to solve the keyboard accessory to be shown each time for each UITextField on shouldBeginEditing by writing a category for a UITextField. And here is something I learned in a hard way:

When you plan or see any category re-writing the existing framework method, STOP! Simple as this and go read on what can turn wrong with this.

The solution mentioned above use this:

1
2
3
4
5
@implementation UINavigationController (ShouldPopOnBackButton)

- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {

 if([self.viewControllers count] < [navigationBar.items count]) {

No go for me, no need to study, excuse for being abrupt :). But one part of code from this solution turned actually to be useful.

One of the comments on another post: http://stackoverflow.com/questions/20327165/popviewcontroller-strange-behaviour got me here: http://www.hkwebentrepreneurs.com/2013/11/ios-prevent-back-button-navigating-to.html

And was not I lucky? It really makes sense, no private APIs, framework's UIViewController gets the chance to do its stuff always. What I wanted to improve though was that "safeDelegate" property and the way it is established. So I added a new method (into UISafeNavigationController.m):


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
-(id<UISafeNavigationDelegate>) popDelegate
{
    UIViewController *topController = [self topViewController];
    
    if ([topController conformsToProtocol:@protocol(UISafeNavigationDelegate)]) {
        return (id<UISafeNavigationDelegate>)topController;
    }
    
    return nil;
}

And then you can just substitute safeDelegate with popDelegate:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
- (UIViewController *)popViewControllerAnimated:(BOOL)animated
{
    if (self.popDelegate && ![self.popDelegate navigationController:self
                                             shouldPopViewController:[self.viewControllers lastObject]
                                                                 pop:^{ [super popViewControllerAnimated:animated]; }])
    {
        if (self.navigationBar) {
            [self restoreViewsForNavigationBar:self.navigationBar];
        }
        return nil;
    }
    
    return [super popViewControllerAnimated:animated];
}

Also note lines 7-9 where I call a new method (borrowed from the first stackoverflow solution that I actually criticize :)):


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
-(void) restoreViewsForNavigationBar: (UINavigationBar *) navigationBar
{
    for(UIView *subview in [navigationBar subviews]) {
        if(0. < subview.alpha && subview.alpha < 1.) {
            [UIView animateWithDuration:.25 animations:^{
                subview.alpha = 1.;
            }];
        }
    }
}

This is to avoid the back arrow in the navigation bar looking as disabled when answer from our controller to the shouldPop is NO.

Then protocol method in the related view controller may look like:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
- (BOOL)navigationController:(UINavigationController *)navigationController
     shouldPopViewController:(UIViewController *)controller pop:(void(^)())pop
{
    if (!_item.id) {
        UIAlertController *alert = [UIAlertController alertControllerWithTitle:LSSTRING(@"Save the item?") message:LSSTRING(@"You are closing this screen by using Back button and have not saved the item.") preferredStyle:UIAlertControllerStyleAlert];
        
        [alert addAction:[UIAlertAction actionWithTitle:LSSTRING(@"Save and close") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
            [self done:nil];
        }]];
        
        [alert addAction:[UIAlertAction actionWithTitle:LSSTRING(@"Don't save and close") style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action) {
            [self doCancellationCleanup];
            if (pop) {
                pop();
            }
            
        }]];
        
        [alert addAction:[UIAlertAction actionWithTitle:LSSTRING(@"Cancel") style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
            
            
        }]];
        
        [alert show];
        return false;
    }
    
    return true;
}

Hope this can help someone! All the credit goes to Hong Kong Web Entrepreneur guy! Once again, here: http://www.hkwebentrepreneurs.com/2013/11/ios-prevent-back-button-navigating-to.html

[UPDATE] In the end I had to rework the final part presented here - showing the confirmation as the way it is presented was not releasing the controller correctly and was not cleaning up the views as well. Quite bigger effort is required to get it right and it is not that generic in the end. But this is a good start! :).

Sunday, April 9, 2017

Debugging in elixir. Old erlang version got into my way.

Today, some dialyzer stuff, but mainly debugging with Elixir was my study subject and so first I ran into iex telling me "Could not find 'wxe_driver.so'" on running :debugger.start(). Then I read that debugger starts anyway, so I tried simple :int.ni(Module), but was greeted with "no beam found for Elixir.Module". Read more and it pointed to Erlang version being lower than 19. Really iex and elixir --version were telling its 18 that is being started. Using brew upgrade erlang or elixir, brew reinstall - same thing. A year ago I installed erlang from the official installer here (https://www.erlang-solutions.com/resources/download.html). Looked there and there is an even version manager for the Erlang on OSX now?! Cool:



This surely helped to have version of erlang as 19.3 for elixir now and all the debugger stuff worked as expected.

I'm looking more and more into the Sasa Juric blackjack example as I'm kind of getting a bit sad about the book I wanted to read as a practical guide to building elixir/OTP apps: https://pragprog.com/book/lhelph/functional-web-development-with-elixir-otp-and-phoenix
I'm kinda on Sasa's side of analysis how the functional apps should be built and the book mentioned is not leading in the right way I believe. I'm still at its start though, author may still refactor his approach later... :).

Sasa uses dialyzer in his example and I read before that it is a good idea to use it, so studied today on this and typespecs in Elixir as well: http://elixir-lang.org/getting-started/typespecs-and-behaviours.html. I also loved a TDD example on building roman numbers converter here: https://medium.com/@barruumrex/seeking-simple-satisfaction-2a098902ddff


Wednesday, April 5, 2017

Elixir - bind the match subject in case statement


Before I forget again on how to bind the whole match subject in case statement in Elixir:



"state" as fn argument name and "state" in case surely are having the different scope, that's just I wanted to have the same name saying "state" :).

Reading now "Functional Web Development with Elixir, OTP, and Phoenix", so this is my version of authors to_string:

def​ to_string(coordinate) ​do​
 ​"​​(in_island:​​#{​island(coordinate)​}​​, guessed:​​#{​guessed?(coordinate)​}​​)"​
​end​

I thought I'd write it in a way that I would not call the wrapping functions on Agent for each interpolated field, plus would avoid changing to_string implementation with every struct change.

By the way, new cool link for learning Elixir:


Also, just in time for reading the book mentioned above comes an article from Saša Jurić:
To spawn, or not to spawn? Definitely going to validate the book suggested solution/architecture with what Saša Jurić is writing.
Done with learning for today, back to creating some added value to my customers!