Category: iOS Development

English Posting for iOS development

  • Xcode Cloud – Auto-Generated Test Notes for TestFlight Build (PR Title, Commits, and JIRA Ticket Links)

    Xcode Cloud – Auto-Generated Test Notes for TestFlight Build (PR Title, Commits, and JIRA Ticket Links)

    No more put ‘No test notes’ at Test Flight

    bd086 c016e img 3297

    No test notes.

    It’s not clear what to test using this build

    Setup Xcode Cloud Workflow

    To get a Github PR infos from Xcode Cloud, You should enable settings like below

    860c8 56aac screenshot2023 08 20at4.24.10am

    8df7d d55a3 screenshot2023 08 20at4.24.19am

    Environment

    • Set your Github Token

    • Set Xcode Version – Latest Release

    Start Conditions -> See Apple Document

    • CI_PULL_REQUEST_NUMBER

    • CI_PULL_REQUEST_SOURCE_COMMIT

    • CI_PULL_REQUEST_TARGET_COMMIT

    To get CI_PULL_REQUEST variables You should set Pull Request Changes as Start Condition

    Post Actions

    • Set Test Flight Internal Testing

     

    ci_scripts/ci_post_clone.sh

    #!/bin/sh
    brew install jq

     

    ci_scripts/ci_post_xcodebuild.sh

    #!/bin/zsh
    #  ci_post_xcodebuild.sh
    
    # PR Description
    GITHUB_REST_API_PR_INFO=$(curl -L\
      -H "Accept: vnd.github+json" \
      -H "Authorization: Bearer $GITHUB_TOKEN" \
      -H "X-GitHub-Api-Version: 2022-11-28" \
      "https://api.github.com/repos/$CI_PULL_REQUEST_TARGET_REPO/pulls/$CI_PULL_REQUEST_NUMBER")
    
    # Fixed: parse error: Invalid string: control characters from U+0000 through U+001F must be escaped, https://stackoverflow.com/questions/52399819/invalid-string-control-characters-from-u0000-through-u001f-must-be-escaped-us
    PR_TITLE=$(printf '%s\n' "$GITHUB_REST_API_PR_INFO" | jq -r '.title')
    
    PR_DESCRIPTION=$(printf '%s\n' "GITHUB_REST_API_PR_INFO" | jq -r '.body')
    PR_DESCRIPTION_URLS=$(echo "$PR_DESCRIPTION" | grep -Eo 'https?://[^[:space:]]+')
    PR_DESCRIPTION_JIRA_TICKETS=$(echo "$PR_DESCRIPTION_URLS" | grep 'atlassian')
    
    # GIT COMMITS
    COMMIT_MESSAGES=$(git log $CI_PULL_REQUEST_TARGET_COMMIT^..$CI_PULL_REQUEST_SOURCE_COMMIT --pretty=format:"%s\n%b")
    FORMATTED_COMMIT_MESSAGES=$(echo -e "$COMMIT_MESSAGES" | awk 'NF {print}' ORS='\n')
    
    # TEST FLIGHT - What To Test
    WHAT_TO_TEST="$PR_TITLE\nCommits:\n$FORMATTED_COMMIT_MESSAGES\nJIRA Tickets:\n$PR_DESCRIPTION_JIRA_TICKETS"
    
    if [[ -d "$CI_APP_STORE_SIGNED_APP_PATH" ]]; then
      TESTFLIGHT_DIR_PATH=../TestFlight
      mkdir $TESTFLIGHT_DIR_PATH
      echo "$WHAT_TO_TEST" >! $TESTFLIGHT_DIR_PATH/WhatToTest.en-US.txt
    fi

     

    Auto-Generated Test Notes

    0a253 d913a screenshot2023 08 20at5.30.42am

  • Xcode Cloud – How to check App Version (MARKETING_VERSION) from ci_post_xcodebuild.sh

    Xcode Cloud – How to check App Version (MARKETING_VERSION) from ci_post_xcodebuild.sh

    Check App Version using xcodebuild

    Update ci_scripts/ci_post_xcodebuild.sh

    CURRENT_PROJECT_VERSION=$(xcodebuild -project $CI_PROJECT_FILE_PATH -showBuildSettings | grep "MARKETING_VERSION" | sed 's/[ ]*MARKETING_VERSION = //')
    
    echo "🐥 App Version is $CURRENT_PROJECT_VERSION"

    704cb df295 screenshot2023 08 19at5.14.25pm

  • Apple Vision Pro Developer Lab in Singapore

    Apple Vision Pro Developer Lab in Singapore

    Today I attended Apple office in Singapore to test Vision Pro. Office is located in One North. From my office to here It took around 5-7 mins by walk

    Vision Pro

    I tested some apps using Vision Pro simulator already. So Today I focused on testing some feature that can’t be tested on simulator. Here is the list what I checked using Vision Pro device

    ARKitSession

    It enabled to render some thing by adding an entity. I can positioning objects around the space by using ImmersiveSpace and RealityView. That means we can use entire spaces what I see for your app. I also tested PlanDetectionProvider to fill the color on the planes. Also I can render the text to display the categories such as floor, wall and window. Using this I can reconstruct spaces. For example you can make your room looks like movie theater.

    Here is example

    Interaction

    Vision Pro allow me to control UI using eye tracking and hand gesture. Especially eye tracking is very convenient to focus on UI. Hand gesture also detected my posture very well.

    Room Plan

    It is one of the feature I really want to use on Vision Pro. But It is not supported for Vision Pro yet. I tried to install Room Plan iOS app on Vision Pro. I can compile and install it but can’t run it. I hope to use it on Vision Pro in the near future.

    Camera API

    Vision Pro doesn’t allow to access to camera from 3rd party apps. It is known as security and privacy issue.

    Sound

    I tested it by using 3D model from Apple Augmented Reality Quick Look web. I drag and drop it in to space. Once model is loaded I can hear the sound from Vision Pro device. There is no ear plugin but I can hear the sound. I’m not sure It played from speaker or bone conduction.

    Developer Lab

    I arrived at office at 10:00 AM. Apple provided Vision Pro device for all attendees. I could forced on development. Also Apple engineers helped me when I faced issue.

    Lunch and Coffee also provided. Very nice.

    Developer Lab start from 10:00 AM to 16:00 PM. I highly recommend preparing development setup before attend developer lab. It helps you save your time.

  • How to write a unit test for validating regex without using XCUIElement’s typeText function

    How to write a unit test for validating regex without using XCUIElement’s typeText function

    This post might be helpful if you are considering using the typeText function for validating user input using regular expressions, such as for validating passwords or usernames.

    Although XCTest enables you to write unit tests for validating the results of regular expressions, it is not suitable for writing test cases that involve inputting characters one by one.

    Let me show the example.

    d164f 7eaac screenshot2023 07 09at2.27.33pm

    Business requirements

    • Username allows alphabet + numbers only

    • Password allows alphabet + numbers + special characters

    • Address allows alphabet + numbers only

    • Phone number format is 4 digits – 4 digits

    • Postal Code is 5 numbers

    • Birthday format is yyyyMMDD

    • Username, Password, Phone, Address are required fields

    • Error message will be displayed when entering text into the TextField

    • Search button will be enabled once the postal code is validated.

    Let’s write a test cases

    ProfileInputValidatorTests: XCTestCase

    It has a sut for checking unitTests and typeText function (like a XCUIElement’s typeText) to put the characters into the active UITextField.

    4716d dcc87 screenshot2023 07 09at3.06.51pm

    class ProfileInputValidatorTests: XCTestCase {
        var sut: MockClass!
    
        override func setUp() {
            super.setUp()
            sut = MockClass()
        }
    
        override func tearDown() {
            super.tearDown()
            sut.activeTextField = nil
            sut.inputCheckType = nil
            sut.errorMessage = nil
        }
    
        //UnitTests
    }
    
    private extension ProfileInputValidatorTests {
        func typeText(_ input: String) {
            guard let activeTextField = sut.activeTextField else {
                return
            }
            for (location, character) in input.enumerated() {
                let canInputText = activeTextField.delegate?.textField?(
                    activeTextField,
                    shouldChangeCharactersIn: NSRange(location: location, length: 0),
                    replacementString: String(character)
                )
                if canInputText ?? false {
                    let lastPosition = activeTextField.endOfDocument
                    activeTextField.selectedTextRange = activeTextField
                        .textRange(from: lastPosition, to: lastPosition)
                    activeTextField.insertText(String(character))
                }
            }
        }
    }

    Here is the example of unit tests

    func test_textField_should_not_exceed_maxCharacter() {
        ProfileInputType.allCases.forEach {
            sut.setActiveTextField($0)
            let dummyText = """
            Lorem Ipsum is simply dummy text of the printing and typesetting industry.
            """
            typeText(dummyText)
            XCTAssertTrue(
                sut.activeTextField?.text?.count == sut.inputValidator.activeTextFieldType?.maxCharacter,
                "TextField tag: \($0.rawValue) should not exceed maxCharacter"
            )
        }
    }
    
    func test_username_allowance_character() {
        let username = "skboard"
        sut.setActiveTextField(.username)
        typeText(username)
        sut.activeTextField?.delegate?.textFieldDidEndEditing?(sut.activeTextField!)
    
        XCTAssertNil(sut.errorMessage)
        XCTAssertEqual(sut.inputCheckType, ProfileInputType.username)
    }
    
    func test_username_not_allowance_character() {
        let username = "マーン224s한글djfタklsdf"
        sut.setActiveTextField(.username)
        typeText(username)
        sut.activeTextField?.delegate?.textFieldDidEndEditing?(sut.activeTextField!)
    
        XCTAssertEqual(ProfileInputType.username.errorMessage, sut.errorMessage)
        XCTAssertEqual(sut.inputCheckType, ProfileInputType.username)
    }
    
    func test_postal_code_allowance_character() {
        let postalCode = "13456"
        sut.setActiveTextField(.postalCode)
        typeText(postalCode)
        sut.activeTextField?.delegate?.textFieldDidEndEditing?(sut.activeTextField!)
    
        XCTAssertNil(sut.errorMessage)
        XCTAssertEqual(sut.inputCheckType, ProfileInputType.postalCode)
    }
    
    func test_is_enabled_search_button_while_editing() {
        sut.setActiveTextField(.postalCode)
        let postalCode = "89761"
        typeText(postalCode)
        XCTAssertEqual(sut.activeTextField?.text, "89761")
        XCTAssertTrue(sut.searchButton.isEnabled, "searchButton should enabled")
    }
    
    func test_is_enabled_search_button_after_didEndEditing() {
        sut.setActiveTextField(.postalCode)
        let postalCode = "24028"
        typeText(postalCode)
    
        XCTAssertEqual(sut.activeTextField?.text, "24028")
        sut.activeTextField?.delegate?.textFieldDidEndEditing?(sut.activeTextField!)
        XCTAssertTrue(sut.searchButton.isEnabled, "searchButton should enabled")
    }
    
    func test_phone_number_format() {
        sut.setActiveTextField(.phone)
        let phoneNumber = "78761241"
        typeText(phoneNumber)
        XCTAssertEqual(sut.activeTextField?.text, "7876-1241")
    }
    
    func test_birthday_format() {
        let birthday = "19980115"
        sut.setActiveTextField(.birthday)
        typeText(birthday)
        sut.activeTextField?.delegate?.textFieldDidEndEditing?(sut.activeTextField!)
    
        XCTAssertNil(sut.errorMessage)
        XCTAssertEqual(sut.inputCheckType, ProfileInputType.birthday)
    }
    
    func test_phone_number_only_allow_one_hyphen_character() {
        sut.setActiveTextField(.phone)
        let phoneNumber = "1467834527891471247"
        typeText(phoneNumber)
        let hyphenCount = numberOfHyphen(in: sut.activeTextField?.text ?? "")
        XCTAssertTrue(
            hyphenCount == 1,
            "phone number only allow one hyphen character"
        )
    }

    InputValidator

    InputValidator checks the inputTypes and validates strings.

    protocol ProfileInputValidatorDelegate: AnyObject {
        var activeTextField: UITextField? { get set }
        func setActiveTextField(_ inputType: ProfileInputType)
        func updateAddressSearchButtonState(isEnabled: Bool)
        func showValidateResult(_ inputType: ProfileInputType?, error message: String?)
    }
    
    final class ProfileInputValidator: NSObject {
        var activeTextFieldType: ProfileInputType?
        var dateFormatter: DateFormatter?
        weak var delegate: ProfileInputValidatorDelegate?
        
        func validate(
            text: String,
            type: ProfileInputType,
            needToSetType: Bool = false,
            placeholder: String? = nil
        ) {
            guard isNotEmpty(text: text) else {
                showEmptyMessage(type: type, needToSetType: needToSetType, placeholder: placeholder)
                return
            }
            let errorMessage: String?
            switch type {
            case .username:
                errorMessage = isValidUsername(text) ? nil : type.errorMessage
            case .password:
                errorMessage = isValidPassword(text) ? nil : type.errorMessage
            case .phone:
                errorMessage = isValidPhoneNumber(text) ? nil : type.errorMessage
            case .address:
                errorMessage = isValidAddress(text) ? nil : type.errorMessage
            case .postalCode:
                errorMessage = isValidPostalCode(text) ? nil : type.errorMessage
            case .birthday:
                errorMessage = isValidBirthday(text) ? nil : type.errorMessage
            }
            delegate?.showValidateResult(needToSetType ? type : activeTextFieldType, error: errorMessage)
        }
        
        private var isRequiredField: Bool {
            if activeTextFieldType == .some(.username) ||
                activeTextFieldType == .some(.password) ||
                activeTextFieldType == .some(.address) ||
                activeTextFieldType == .some(.phone) {
                return true
            }
            else {
                return false
            }
        }
        
        private func showEmptyMessage(type: ProfileInputType, needToSetType: Bool, placeholder: String?) {
            let textFieldTitle = activeTextFieldType?.title ?? ""
            var emptyErrorMessage: String?
            if isRequiredField {
                emptyErrorMessage =
                    "\(placeholder ?? textFieldTitle) is required"
            }
            delegate?.showValidateResult(
                needToSetType ? type : activeTextFieldType,
                error: emptyErrorMessage
            )
        }
    
        private func isNotEmpty(text: String?) -> Bool {
            guard let inputText = text else {
                return false
            }
            let textWithOutWhiteSpace = inputText.trimmingCharacters(in: .whitespacesAndNewlines)
            return !textWithOutWhiteSpace.isEmpty
        }
    
        private func isValidDateFormat(text: String) -> Bool {
            guard convertBirthdayFormat(text)?.count == ProfileInputType.birthday.maxCharacter else {
                return false
            }
            return true
        }
    }

    This class conforms to UITextFieldDelegate to check strings

    //MARK: UITextFieldDelegate
    extension ProfileInputValidator: UITextFieldDelegate {
        func textFieldDidBeginEditing(_ textField: UITextField) {
            guard let fieldType = ProfileInputType(textField.tag) else {
                return
            }
            delegate?.activeTextField = textField
            activeTextFieldType = fieldType
        }
    
        func textFieldDidEndEditing(_ textField: UITextField) {
            guard let inputText = textField.text, let inputType = activeTextFieldType else {
                return
            }
            validate(text: inputText, type: inputType)
        }
    
        func textField(
            _ textField: UITextField,
            shouldChangeCharactersIn range: NSRange,
            replacementString string: String
        ) -> Bool {
            let text = textField.text ?? ""
            guard let stringRange = Range(range, in: text),
                  let activeTextFieldMaxCount = activeTextFieldType?.maxCharacter
            else {
                return false
            }
            if activeTextFieldType == .phone, let phoneNumber = formattingPhoneNumber(text: text, to: string, range: range) {
                textField.text = phoneNumber
                return false
            }
            let updatedText = text.replacingCharacters(in: stringRange, with: string)
            if activeTextFieldType == .postalCode {
                delegate?.updateAddressSearchButtonState(isEnabled: updatedText.count == activeTextFieldMaxCount)
            }
            return updatedText.count <= activeTextFieldMaxCount
        }
    
        func textFieldShouldReturn(_ textField: UITextField) -> Bool {
            guard let activeType = activeTextFieldType else {
                return false
            }
            textField.endEditing(false)
            // Focus on the next field
            switch activeType {
            case .username:
                delegate?.setActiveTextField(.password)
            case .password:
                delegate?.setActiveTextField(.phone)
            case .phone:
                delegate?.setActiveTextField(.address)
            case .address:
                delegate?.setActiveTextField(.postalCode)
            case .postalCode:
                delegate?.setActiveTextField(.birthday)
            default:
                break
            }
            return false
        }
    }

    MockClass

    This class has a InputValidator and conforms to ProfileInputValidatorDelegate. It is use for XCTestCase.

    class MockClass {
        var activeTextField: UITextField?
        var searchButton = UIButton()
        var inputCheckType: ProfileInputType?
        var errorMessage: String?
    
        fileprivate let inputValidator = ProfileInputValidator()
        
        var usernameTextField: UITextField!
        var passwordTextField: UITextField!
        var addressTextField: UITextField!
        var phoneTextField: UITextField!
        var postalCodeTextField: UITextField!
        var birthdayTextField: UITextField!
        
        lazy var dateFormatter: DateFormatter = {
            let formatter = DateFormatter()
            formatter.dateFormat = "yyyy.MM.dd"
            formatter.calendar = Calendar(identifier: .gregorian)
            return formatter
        }()
    
        init() {
            inputValidator.delegate = self
            inputValidator.dateFormatter = dateFormatter
    
            setupTextFields()
            searchButton.isEnabled = false
        }
    
        private func setupTextFields() {
            usernameTextField = UITextField()
            passwordTextField = UITextField()
            addressTextField = UITextField()
            phoneTextField = UITextField()
            postalCodeTextField = UITextField()
            birthdayTextField = UITextField()
        
            let textFields = [
                usernameTextField,
                passwordTextField,
    
                addressTextField,
                phoneTextField,
    
                postalCodeTextField,
                birthdayTextField
            ]
    
            for (index, textField) in textFields.enumerated() {
                textField?.delegate = inputValidator
                textField?.tag = index
            }
        }
    }
    
    extension MockClass: ProfileInputValidatorDelegate {
        func setActiveTextField(_ inputType: ProfileInputType) {
            switch inputType {
            case .username:
                usernameTextField.delegate?.textFieldDidBeginEditing?(usernameTextField)
            case .password:
                passwordTextField.delegate?.textFieldDidBeginEditing?(passwordTextField)
            case .address:
                addressTextField.delegate?.textFieldDidBeginEditing?(addressTextField)
            case .phone:
                phoneTextField.delegate?.textFieldDidBeginEditing?(phoneTextField)
            case .postalCode:
                postalCodeTextField.delegate?.textFieldDidBeginEditing?(postalCodeTextField)
            case .birthday:
                birthdayTextField.delegate?.textFieldDidBeginEditing?(birthdayTextField)
            }
        }
    
        func updateAddressSearchButtonState(isEnabled: Bool) {
            searchButton.isEnabled = isEnabled
        }
    
        func showValidateResult(_ inputType: ProfileInputType?, error message: String?) {
            errorMessage = message
            inputCheckType = inputType
        }
    }

    Conclusion

    XCTest is a powerful framework for testing unit tests, but it can be challenging to test UI-related logic. In mobile development, UI logic is closely intertwined with the user interface itself. The conventional approach for testing UI logic is to use XCUITest. However, XCUITest tends to be slower compared to XCTest. If we have multiple views designed for registering or updating information using input fields, we would need to write multiple UI testing code with XCUITest.

    An alternative approach is to use XCTest and create a function that simulates user actions, such as typing text. This allows us to write a unit test for common input validator logic while considering user actions as well. This way, we can test the UI-related logic using XCTest in a more efficient manner compared to relying solely on XCUITest.

  • How to Find Unused Swift Code with Periphery Static Analyzer

    How to Find Unused Swift Code with Periphery Static Analyzer

    Recently I faced an issue with compile -O for product build. I assumed that It caused by dead function issue. I reported it

    Apple Swift Git Repo

    This post I’ll introduce static analyzer tool for checking the unused swift codes

    Install Periphery

    Link You can install periphery tool using homebrew

    Here is homebrew install command.

    brew install peripheryapp/periphery/periphery

    And then setup peripheryapp on your project

    periphery scan --setup

    34ff6 72338 screenshot2023 06 03at11.33.08pm

    Select your App’s target name. Don’t forget to save configuration to .periphery.yml

    Check unused codes

    You can see the results.

    50103 2554a screenshot2023 06 03at11.38.15pm

    Make a script file to save result as text file

    I recommend make a bash script file to save results as text file to keep track progress of reducing unused codes. Here is the bash script. (I changed ruby code (https://www.youtube.com/watch?v=2OQiCj-BG2o) to bash script)

    #!/bin/bash
    
    echo "🔍 start dead code analysis"
    
    result="$(periphery scan --config .periphery.yml)"
    
    current_dir=$(pwd)
    
    result_stripped_of_absolute_path_prefix=$(echo "$result" | sed "s|$current_dir/||g")
    
    filtered_out_result=$(echo "$result_stripped_of_absolute_path_prefix" | awk '/:[0-9]+:[0-9]+:/{ print }')
    sorted_result=$(echo "$filtered_out_result" | sort)
    
    result_with_removed_code_line_number=$(echo "$sorted_result" | sed "s|:[0-9]+:[0-9]+:|:|")
    output=$(echo "$result_with_removed_code_line_number" | tr '\n' '\n' | sort)
    
    echo "$output" > unused-codes.txt 2>&1
    echo "🙌 done with dead code analysis"

    I saved bash script file as check-unused-codes.sh

    You can run bash script file by running sh check-unused.codes.sh in terminal (You can see the results of the unused codes)

    25550 79731 screenshot2023 06 04at1.24.26am

    fd2e5 cc6e4 screenshot2023 06 04at1.24.38am

    Results will be saved into the unused-codes.txt

    Reference

    Inside iOS Dev

    https://www.parasoft.com/blog/false-positives-in-static-code-analysis/

  • City Commuter App Privacy Policy

    City Commuter App Privacy Policy

    At City Commuter App, we are committed to protecting the privacy of our users. This privacy policy explains the types of information we do not collect, how we use the information we do not collect, and the steps we take to protect it.

    Information We Do Not Collect

    • Personal information: We do not collect any personal information from our users such as names, email addresses, or contact information.

    • Usage information: We do not collect any information about how our users use our app, such as the features accessed or actions taken.

    • Device information: We do not collect any information about the devices used to access our app, such as the model, operating system, or screen resolution.

    Use of Information

    We do not collect any information from our users, so we do not use any information for any purposes.

    Sharing Information

    We do not collect any information from our users, so we do not share any information with third parties.

    Changes to Our Privacy Policy

    We may update this privacy policy from time to time to reflect changes to our information practices. We will notify you of any changes by posting the new privacy policy on this page.

    Contact Us

    If you have any questions or concerns about our privacy policy, please contact us at shawn@shawnbaek.com

    Please note that this is a sample privacy policy, but as you said your app doesn’t collect any user data, you don’t have to share anything about data collection. However, it’s always a good idea to have a privacy policy in place to inform users about your app’s data practices and to give them peace of mind.

  • Swift Compile Optimization Levels Explained – O, Onone, Osize Benchmarks

    Swift Compile Optimization Levels Explained – O, Onone, Osize Benchmarks

    Optimization Level

    Swift compiler has 3 optimization options (link)

    • -O

      • for product code

    • -Onone

      • for development and debugging mode.

    • -Osize

      • for size optimization over the optimizing code

     

    How to compile swift with optimization level?

    Here is a simple swift code for checking execution time. (main.swift)

    import Foundation
    func sum(from: Int, to: Int) -> Int {
        var result = 0
        for i in from...to {
            result += i
        }
        return result
    }
    
    let startTime = Date()
    let result = sum(from: 1, to: 10000000)
    let endTime = Date()
    
    print("Result: \(result)")
    print("Calculation Time: \(endTime.timeIntervalSince(startTime)) seconds")

    Compiled it with -Onone

    swiftc -Onone main.swift && ./main
    //Result
    Result: 50000005000000
    Time: 1.4896910190582275 seconds

    Compiled it with -O

    swiftc -O main.swift && ./main
    //Result
    Result: 50000005000000
    Time: 0.006083011627197266 seconds

    Results

    -O option is 24,389% faster than -Onone option

     

    Check SIL file

    swiftc -O main.swift is generating executable file. It is not a human readable file. The optimization process transforms the source code into machine code, which is the binary code that can be executed directly by the computer.

    Do you want to see the intermediate representation of the Swift code generated during the optimization process?

    You can use the -emit-sil flag with the swiftc command. This will output the SIL (Swift Intermediate Language) code generated by the compiler, which is a human-readable representation of the Swift code that has been optimized

    swiftc -O -emit-sil main.swift -o main.sil
    swiftc -Onone -emit-sil main.swift -o main.sil

    908c7 f2128 screenshot2023 04 15at6.09.29pm

    You can see the main.sil file. Let’s open it using TextEdit

    0b60c f50c7 screenshot2023 04 15at6.11.21pm

    Now we can see the details by checking sil file. If you want to learn more about it visit here

     

    Conclusion

    I wanted to test how Swift’s optimization levels work, so I used the -emit-sil option to generate an SIL (Swift Intermediate Language) file. This file serves as a bridge between the Swift source code and the machine code, and allows the Swift compiler to perform optimizations such as dead code elimination, constant folding, inlining, and loop unrolling. By inspecting and analyzing the SIL file, we can better understand how the Swift compiler is optimizing our code. Additionally, SIL can be used for debugging purposes since it provides a more detailed and low-level view of the generated code compared to the original Swift source

    Notes
    This blog post has been edited using ChatGPT to refine some of the sentences

     

  • AWS Hosting Costs for Vapor Swift Server – Monthly $65 Breakdown

    AWS Hosting Costs for Vapor Swift Server – Monthly $65 Breakdown

    I’ve been using AWS to host my Vapor Swift server, but currently, there’s no traffic as I am just running the server 24/7.

    Monthly Costs:

    The total cost is approximately $65 per month, with the most expensive service being RDB (using PostgreSQL). Here’s a list of the AWS services I’m using:

    • EC2 T2.nano Instance for AWS Cloud9

    • RDB (PostgreSQL)

    • Elastic Load Balancer

    • ECS Fargate, CPU: .25 vCPU, Memory: .5GB (500MB)

    f2068 86563 screenshot2023 02 19at8.39.12am

     

    Breakdown of Service Costs:

    00e80 91aac screenshot2023 02 19at8.39.46am

    Conclusion

    I’m sharing the expenses with two friends, so it’s not too burdensome for me at the moment. However, I’m considering turning off the EC2-Other (Cloud 9 Service) as I only use it to access the terminal for checking database tables.

    Despite this, I plan to continue using AWS as switching to another service would be time-consuming. This year, my focus is on monetizing my app through in-app purchases or Adsense. Hopefully, this will cover my server hosting costs.

  • How to Find the Latest SwiftUI Preview Crash Log on Mac

    How to Find the Latest SwiftUI Preview Crash Log on Mac

    SwiftUI Preview crashed

    6a3a6 a621f screenshot2023 02 12at3.15.18pm

    Preview Crashed logs are saved at “~/Library/Logs/DiagnosticReports

    on Xcode, It’s hard to check the logs. so I made a bash script to open the latest crash log file “latestPreviewCrash.sh“

    #!/bin/bash
    
    cd ~/Library/Logs/DiagnosticReports
    
    file=$(ls XCPreview* | head -1)
    
    open $file

    run this script

    sh latestPreviewCrash.sh

    It will open the crash logs.

  • nativeMobile, Privacy Policy

    nativeMobile, Privacy Policy

    At nativeMobile, we are committed to protecting the privacy of our users. This privacy policy explains the types of information we do not collect, how we use the information we do not collect, and the steps we take to protect it.

    Information We Do Not Collect

    • Personal information: We do not collect any personal information from our users such as names, email addresses, or contact information.

    • Usage information: We do not collect any information about how our users use our app, such as the features accessed or actions taken.

    • Device information: We do not collect any information about the devices used to access our app, such as the model, operating system, or screen resolution.

    Use of Information

    We do not collect any information from our users, so we do not use any information for any purposes.

    Sharing Information

    We do not collect any information from our users, so we do not share any information with third parties.

    Changes to Our Privacy Policy

    We may update this privacy policy from time to time to reflect changes to our information practices. We will notify you of any changes by posting the new privacy policy on this page.

    Contact Us

    If you have any questions or concerns about our privacy policy, please contact us at shawn@shawnbaek.com

    Please note that this is a sample privacy policy, but as you said your app doesn’t collect any user data, you don’t have to share anything about data collection. However, it’s always a good idea to have a privacy policy in place to inform users about your app’s data practices and to give them peace of mind.